Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions app/src/ai/agent/api/convert_conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use ai::agent::action_result::{
RequestComputerUseResult, SendMessageToAgentResult, StartAgentResult, StartAgentVersion,
UseComputerResult,
};
use ai::skills::ParsedSkill;
use ai::skills::{ParsedSkill, SkillPathOrigin};
use chrono::{DateTime, Local, TimeZone};
use persistence::model::AgentConversationData;
use warp_core::command::ExitCode;
Expand Down Expand Up @@ -468,7 +468,10 @@ impl ConvertToExchanges for &api::Task {
}
api::message::Message::InvokeSkill(invoke_skill) => {
if let Some(api_skill) = invoke_skill.skill.clone() {
if let Ok(parsed_skill) = ParsedSkill::try_from(api_skill) {
if let Ok(parsed_skill) = ParsedSkill::try_from_api_with_origin(
api_skill,
&SkillPathOrigin::RestoredDisplayOnly,
) {
let user_query = invoke_skill
.user_query
.clone()
Expand Down Expand Up @@ -531,6 +534,7 @@ impl ConvertToExchanges for &api::Task {
// TODO(alokedesai): Support persistence for the code review state.
active_code_review: None,
task_id: &TaskId::new(api_message.task_id.clone()),
skill_path_origin: &SkillPathOrigin::Unavailable,
})
{
current_outputs.push(output_msg);
Expand Down
45 changes: 29 additions & 16 deletions app/src/ai/agent/api/convert_from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ use std::collections::HashMap;
use std::time::Duration;

use ai::agent::action::LifecycleEventType as StartAgentLifecycleEventType;
use ai::agent::action::ReadSkillRequest;
use ai::agent::action_result::StartAgentVersion;
use ai::agent::convert::ToolToAIAgentActionError;
use ai::agent::UnknownCitationTypeError;
use ai::skills::SkillReference;
use ai::skills::{
skill_reference_from_api_skill_ref, skill_reference_from_read_skill_ref, SkillPathOrigin,
};
use api::ask_user_question::question::QuestionType;
use warp_core::channel::ChannelState;
use warp_multi_agent_api as api;
Expand Down Expand Up @@ -50,6 +53,18 @@ impl TryFrom<api::Attachment> for AIAgentAttachment {
}
}

fn convert_read_skill(
read_skill: api::message::tool_call::ReadSkill,
skill_path_origin: &SkillPathOrigin,
) -> Result<AIAgentActionType, ToolToAIAgentActionError> {
let Some(reference) = read_skill.skill_reference else {
return Err(ToolToAIAgentActionError::MissingSkillReference);
};
let skill = skill_reference_from_read_skill_ref(reference, skill_path_origin)
.map_err(|_| ToolToAIAgentActionError::MissingSkillReference)?;
Ok(AIAgentActionType::ReadSkill(ReadSkillRequest { skill }))
}

/// Converts proto UserQueryMode to the internal UserQueryMode type
pub(crate) fn convert_user_query_mode(mode: Option<&api::UserQueryMode>) -> UserQueryMode {
let Some(mode) = mode else {
Expand Down Expand Up @@ -120,7 +135,10 @@ fn convert_run_agents_execution_mode(
}
}

fn convert_run_agents(run_agents: api::RunAgents) -> AIAgentActionType {
fn convert_run_agents(
run_agents: api::RunAgents,
skill_path_origin: &SkillPathOrigin,
) -> AIAgentActionType {
let api::RunAgents {
summary,
base_prompt,
Expand All @@ -136,7 +154,7 @@ fn convert_run_agents(run_agents: api::RunAgents) -> AIAgentActionType {
base_prompt,
skills: skills
.into_iter()
.filter_map(convert_skill_reference)
.filter_map(|skill| skill_reference_from_api_skill_ref(skill, skill_path_origin))
.collect(),
model_id,
harness_type: convert_run_agents_harness(harness.as_ref()).unwrap_or_default(),
Expand All @@ -159,6 +177,7 @@ fn convert_run_agents(run_agents: api::RunAgents) -> AIAgentActionType {

fn convert_start_agent_v2_execution_mode(
execution_mode: Option<api::start_agent_v2::ExecutionMode>,
skill_path_origin: &SkillPathOrigin,
) -> StartAgentExecutionMode {
match execution_mode.and_then(|execution_mode| execution_mode.mode) {
Some(api::start_agent_v2::execution_mode::Mode::Remote(remote)) => {
Expand All @@ -167,7 +186,9 @@ fn convert_start_agent_v2_execution_mode(
skill_references: remote
.skills
.into_iter()
.filter_map(convert_skill_reference)
.filter_map(|skill| {
skill_reference_from_api_skill_ref(skill, skill_path_origin)
})
.collect(),
model_id: remote.model_id,
computer_use_enabled: remote.computer_use_enabled,
Expand All @@ -189,16 +210,6 @@ fn convert_start_agent_v2_execution_mode(
}
}

fn convert_skill_reference(skill_ref: api::SkillRef) -> Option<SkillReference> {
match skill_ref.skill_reference {
Some(api::skill_ref::SkillReference::Path(path)) => Some(SkillReference::Path(path.into())),
Some(api::skill_ref::SkillReference::BundledSkillId(id)) => {
Some(SkillReference::BundledSkillId(id))
}
None => None,
}
}

/// Unexpected errors when trying to convert an [`api::Message`] to an [`AIAgentOutputMessage`].
#[derive(Debug, thiserror::Error)]
pub enum MessageToAIAgentOutputMessageError {
Expand Down Expand Up @@ -233,6 +244,7 @@ pub struct ConversionParams<'a> {
pub task_id: &'a TaskId,
pub current_todo_list: Option<&'a AIAgentTodoList>,
pub active_code_review: Option<&'a CodeReview>,
pub skill_path_origin: &'a SkillPathOrigin,
}

/// Trait for converting an [`api::Message`] to an [`AIAgentOutputMessage`].
Expand Down Expand Up @@ -828,6 +840,7 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall {
prompt: start_agent.prompt,
execution_mode: convert_start_agent_v2_execution_mode(
start_agent.execution_mode,
params.skill_path_origin,
),
lifecycle_subscription: start_agent.lifecycle_subscription.map(
|subscription| {
Expand All @@ -841,7 +854,7 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall {
})
}
api::message::tool_call::Tool::RunAgents(orchestrate) => {
create_standard_action(convert_run_agents(orchestrate))
create_standard_action(convert_run_agents(orchestrate, params.skill_path_origin))
}
api::message::tool_call::Tool::SendMessageToAgent(send_message) => {
create_standard_action(AIAgentActionType::SendMessageToAgent {
Expand All @@ -854,7 +867,7 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall {
create_standard_action(insert_review_comments.into())
}
api::message::tool_call::Tool::ReadSkill(read_skill) => {
create_standard_action(read_skill.try_into()?)
create_standard_action(convert_read_skill(read_skill, params.skill_path_origin)?)
}
api::message::tool_call::Tool::FetchConversation(fetch_conversation) => {
create_standard_action(fetch_conversation.into())
Expand Down
18 changes: 16 additions & 2 deletions app/src/ai/agent/api/convert_from_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use ai::agent::action::AskUserQuestionType;
use ai::skills::SkillReference;
use ai::skills::{SkillPathOrigin, SkillReference};
use std::path::PathBuf;
use warp_multi_agent_api as api;
use warp_util::local_or_remote_path::LocalOrRemotePath;

use super::{
convert_api_question, ConversionParams, ConvertAPIMessageToClientOutputMessage,
Expand Down Expand Up @@ -299,6 +301,7 @@ fn converts_start_agent_tool_call_to_action_with_prompt() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("conversion should succeed");

Expand Down Expand Up @@ -328,6 +331,7 @@ fn converts_local_start_agent_v2_without_harness_type_to_defaults() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("conversion should succeed");

Expand Down Expand Up @@ -355,6 +359,7 @@ fn converts_upload_artifact_tool_call_to_action() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("conversion should succeed");

Expand All @@ -378,6 +383,7 @@ fn converts_file_artifact_created_message_with_filename() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("conversion should succeed");

Expand Down Expand Up @@ -408,13 +414,15 @@ fn converts_start_agent_tool_calls_with_different_prompt_lengths() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("partial conversion should succeed");
let updated_output = updated_message
.to_client_output_message(ConversionParams {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("updated conversion should succeed");

Expand Down Expand Up @@ -443,6 +451,7 @@ fn converts_start_agent_with_explicit_empty_lifecycle_subscription() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("conversion should succeed");

Expand Down Expand Up @@ -472,6 +481,7 @@ fn converts_start_agent_with_cancelled_and_blocked_lifecycle_subscription() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("conversion should succeed");

Expand Down Expand Up @@ -504,6 +514,7 @@ fn converts_remote_start_agent_with_environment_id() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("conversion should succeed");

Expand Down Expand Up @@ -542,6 +553,7 @@ fn converts_remote_start_agent_v2_with_skill_references() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("conversion should succeed");

Expand All @@ -554,7 +566,7 @@ fn converts_remote_start_agent_v2_with_skill_references() {
StartAgentExecutionMode::Remote {
environment_id: "env-123".to_string(),
skill_references: vec![
SkillReference::Path("/tmp/SKILL.md".into()),
SkillReference::Path(LocalOrRemotePath::Local(PathBuf::from("/tmp/SKILL.md",))),
SkillReference::BundledSkillId("review-comments".to_string()),
],
model_id: "gpt-test".to_string(),
Expand Down Expand Up @@ -583,6 +595,7 @@ fn converts_local_start_agent_v2_with_harness_type() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("conversion should succeed");

Expand Down Expand Up @@ -625,6 +638,7 @@ fn transfer_control_tool_call_converts_to_action_message() {
task_id: &task_id,
current_todo_list: None,
active_code_review: None,
skill_path_origin: &SkillPathOrigin::Local,
})
.expect("transfer-control conversion should succeed");

Expand Down
32 changes: 23 additions & 9 deletions app/src/ai/agent/conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt::Display;

use ai::agent::orchestration_config::{OrchestrationConfig, OrchestrationConfigStatus};
use ai::document::AIDocumentId;
use ai::skills::SkillPathOrigin;
use chrono::{DateTime, Local, TimeZone};
use itertools::Itertools as _;
use serde::{Deserialize, Serialize};
Expand All @@ -26,8 +27,8 @@ use super::api::ServerConversationToken;
use super::task::helper::*;
use super::task::transaction::{SavedTask, Transaction};
use super::task::{
derive_todo_lists_from_root_task, ExtractMessagesError, Task, TaskId, UpdateTaskError,
UpgradeOptimisticTaskError,
derive_todo_lists_from_root_task, ExtractMessagesError, Task, TaskId, TaskMessageContext,
UpdateTaskError, UpgradeOptimisticTaskError,
};
use super::task_store::TaskStore;
use super::{
Expand Down Expand Up @@ -684,7 +685,7 @@ impl AIConversation {
.remove(&root_task_id)
.expect("root task should exist for upgrade-in-place test helper");
let server_root = root_task
.into_server_created_task(server_task, None, None, None)
.into_server_created_task(server_task, None, None, None, &SkillPathOrigin::Unavailable)
.expect("upgrading optimistic root to a server-backed task should succeed");
self.task_store.set_root_task(server_root);
}
Expand Down Expand Up @@ -2286,6 +2287,7 @@ impl AIConversation {
response_stream_id: &ResponseStreamId,
terminal_view_id: EntityId,
action: warp_multi_agent_api::client_action::Action,
skill_path_origin: &SkillPathOrigin,
ctx: &mut ModelContext<BlocklistAIHistoryModel>,
) -> Result<(), UpdateConversationError> {
use warp_multi_agent_api::client_action::*;
Expand Down Expand Up @@ -2334,6 +2336,7 @@ impl AIConversation {
parent_task.source(),
self.todo_lists.last(),
self.code_review.as_ref(),
skill_path_origin,
)?;
ctx.emit(BlocklistAIHistoryEvent::UpgradedTask {
optimistic_id: optimistic_id.clone(),
Expand Down Expand Up @@ -2370,6 +2373,7 @@ impl AIConversation {
existing_exchange,
self.todo_lists.last(),
self.code_review.as_ref(),
skill_path_origin,
// In shared-session viewers, we have to reconstruct what the original user input
// was using subsequent conversation messages (as the original input was not
// sent on this client). Once we reconstruct these inputs, we will insert them
Expand Down Expand Up @@ -2457,6 +2461,7 @@ impl AIConversation {
None,
self.todo_lists.last(),
self.code_review.as_ref(),
skill_path_origin,
)?;
ctx.emit(BlocklistAIHistoryEvent::UpgradedTask {
optimistic_id: old_id,
Expand Down Expand Up @@ -2684,8 +2689,11 @@ impl AIConversation {
task.add_messages(
messages,
exchange_id,
current_todo_list.as_ref(),
current_comment_state.as_ref(),
TaskMessageContext {
current_todo_list: current_todo_list.as_ref(),
active_code_review: current_comment_state.as_ref(),
skill_path_origin,
},
// In shared-session viewers, we have to reconstruct what the original user input
// was using subsequent conversation messages (as the original input was not
// sent on this client). Once we reconstruct these inputs, we will insert them
Expand Down Expand Up @@ -2790,8 +2798,11 @@ impl AIConversation {
task.upsert_message(
message,
exchange_id,
current_todo_list.as_ref(),
current_comment_state.as_ref(),
TaskMessageContext {
current_todo_list: current_todo_list.as_ref(),
active_code_review: current_comment_state.as_ref(),
skill_path_origin,
},
mask,
is_viewing_shared_session,
)
Expand Down Expand Up @@ -2835,8 +2846,11 @@ impl AIConversation {
task.append_to_message_content(
message,
exchange_id,
current_todo_list.as_ref(),
current_comment_state.as_ref(),
TaskMessageContext {
current_todo_list: current_todo_list.as_ref(),
active_code_review: current_comment_state.as_ref(),
skill_path_origin,
},
mask,
)
.map(|msg| msg.todos_op().cloned())
Expand Down
Loading
Loading