use chrono::{DateTime, Local}; use warp_multi_agent_api::apply_file_diffs_result::success::UpdatedFileContent; use warp_multi_agent_api::ask_user_question_result::answer_item::{ self, Answer as AskUserQuestionAnswer, }; use warp_multi_agent_api::{self as api}; use super::*; use crate::agent::action_result::ShellCommandError; use crate::agent::convert::ConvertToAPITypeError; fn local_datetime_to_timestamp(timestamp: DateTime) -> prost_types::Timestamp { prost_types::Timestamp { seconds: timestamp.timestamp(), nanos: timestamp.timestamp_subsec_nanos() as i32, } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: RequestCommandOutputResult) -> Result { match result { RequestCommandOutputResult::Completed { command, block_id, output, exit_code, start_ts, completed_ts, } => Ok( api::request::input::tool_call_result::Result::RunShellCommand( #[allow(deprecated)] api::RunShellCommandResult { command, output: Default::default(), exit_code: Default::default(), result: Some(api::run_shell_command_result::Result::CommandFinished( api::ShellCommandFinished { command_id: block_id.to_string(), output, exit_code: exit_code.value(), start_ts: start_ts.map(local_datetime_to_timestamp), finish_ts: completed_ts.map(local_datetime_to_timestamp), }, )), }, ), ), RequestCommandOutputResult::LongRunningCommandSnapshot { command, block_id, grid_contents, cursor, is_alt_screen_active, } => Ok( api::request::input::tool_call_result::Result::RunShellCommand( #[allow(deprecated)] api::RunShellCommandResult { command, output: Default::default(), exit_code: Default::default(), result: Some( api::run_shell_command_result::Result::LongRunningCommandSnapshot( api::LongRunningShellCommandSnapshot { command_id: block_id.to_string(), output: grid_contents, cursor: cursor.to_owned(), is_alt_screen_active, is_preempted: false, }, ), ), }, ), ), RequestCommandOutputResult::CancelledBeforeExecution => Ok( api::request::input::tool_call_result::Result::RunShellCommand( #[allow(deprecated)] api::RunShellCommandResult { command: "cancelled".to_string(), output: Default::default(), exit_code: Default::default(), result: Some(api::run_shell_command_result::Result::CommandFinished( api::ShellCommandFinished { command_id: String::new(), output: "User cancelled the command before it executed." .to_string(), exit_code: 130, start_ts: None, finish_ts: None, }, )), }, ), ), RequestCommandOutputResult::Denylisted { command } => { #[allow(deprecated)] Ok( api::request::input::tool_call_result::Result::RunShellCommand( api::RunShellCommandResult { command, output: Default::default(), exit_code: Default::default(), result: Some(api::run_shell_command_result::Result::PermissionDenied( api::PermissionDenied { reason: Some( api::permission_denied::Reason::DenylistedCommand(()), ), }, )), }, ), ) } } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: WriteToLongRunningShellCommandResult) -> Result { match result { WriteToLongRunningShellCommandResult::Snapshot { block_id, grid_contents, cursor, is_alt_screen_active, is_preempted } => Ok( api::request::input::tool_call_result::Result::WriteToLongRunningShellCommand( api::WriteToLongRunningShellCommandResult { result: Some(api::write_to_long_running_shell_command_result::Result::LongRunningCommandSnapshot( api::LongRunningShellCommandSnapshot { command_id: block_id.to_string(), output: grid_contents, cursor: cursor.to_owned(), is_alt_screen_active, is_preempted, } )) }, ), ), WriteToLongRunningShellCommandResult::CommandFinished { block_id, output, exit_code, start_ts, completed_ts } => Ok( api::request::input::tool_call_result::Result::WriteToLongRunningShellCommand( api::WriteToLongRunningShellCommandResult { result: Some(api::write_to_long_running_shell_command_result::Result::CommandFinished( api::ShellCommandFinished { command_id: block_id.to_string(), output, exit_code: exit_code.value(), start_ts: start_ts.map(local_datetime_to_timestamp), finish_ts: completed_ts.map(local_datetime_to_timestamp), } )) }, ), ), WriteToLongRunningShellCommandResult::Cancelled => { Ok( api::request::input::tool_call_result::Result::WriteToLongRunningShellCommand( api::WriteToLongRunningShellCommandResult { result: Some(api::write_to_long_running_shell_command_result::Result::CommandFinished( api::ShellCommandFinished { command_id: String::new(), output: "User cancelled the long-running shell command.".to_string(), exit_code: 130, start_ts: None, finish_ts: None, } )) }, ), ) } WriteToLongRunningShellCommandResult::Error(ShellCommandError::BlockNotFound) => { Ok(api::request::input::tool_call_result::Result::WriteToLongRunningShellCommand( api::WriteToLongRunningShellCommandResult { result: Some( api::write_to_long_running_shell_command_result::Result::Error( api::ShellCommandError { r#type: Some(api::shell_command_error::Type::CommandNotFound(())), }, ), ), }, ), ) } } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: ReadFilesResult) -> Result { match result { ReadFilesResult::Success { files } => Ok( api::request::input::tool_call_result::Result::ReadFiles(api::ReadFilesResult { result: Some(api::read_files_result::Result::AnyFilesSuccess( api::read_files_result::AnyFilesSuccess { files: files .into_iter() .flat_map(Into::>::into) .collect(), }, )), }), ), ReadFilesResult::Error(error) => Ok( api::request::input::tool_call_result::Result::ReadFiles(api::ReadFilesResult { result: Some(api::read_files_result::Result::Error( api::read_files_result::Error { message: error }, )), }), ), ReadFilesResult::Cancelled => Ok( api::request::input::tool_call_result::Result::ReadFiles(api::ReadFilesResult { result: Some(api::read_files_result::Result::Error( api::read_files_result::Error { message: "User cancelled the file read.".to_string(), }, )), }), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: UploadArtifactResult) -> Result { match result { UploadArtifactResult::Success { artifact_uid, mime_type, size_bytes, .. } => Ok( api::request::input::tool_call_result::Result::UploadFileArtifact( api::UploadFileArtifactResult { result: Some(api::upload_file_artifact_result::Result::Success( api::upload_file_artifact_result::Success { artifact_uid, mime_type, size_bytes, }, )), }, ), ), UploadArtifactResult::Error(message) => Ok( api::request::input::tool_call_result::Result::UploadFileArtifact( api::UploadFileArtifactResult { result: Some(api::upload_file_artifact_result::Result::Error( api::upload_file_artifact_result::Error { message }, )), }, ), ), UploadArtifactResult::Cancelled => Ok( api::request::input::tool_call_result::Result::UploadFileArtifact( api::UploadFileArtifactResult { result: Some(api::upload_file_artifact_result::Result::Error( api::upload_file_artifact_result::Error { message: "User cancelled the upload.".to_string(), }, )), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: SearchCodebaseResult) -> Result { match result { SearchCodebaseResult::Success { files } => Ok( api::request::input::tool_call_result::Result::SearchCodebase( api::SearchCodebaseResult { result: Some(api::search_codebase_result::Result::Success( api::search_codebase_result::Success { files: files .into_iter() .flat_map(Into::>::into) .collect(), }, )), }, ), ), SearchCodebaseResult::Failed { message, .. } => Ok( api::request::input::tool_call_result::Result::SearchCodebase( api::SearchCodebaseResult { result: Some(api::search_codebase_result::Result::Error( api::search_codebase_result::Error { message }, )), }, ), ), SearchCodebaseResult::Cancelled => Ok( api::request::input::tool_call_result::Result::SearchCodebase( api::SearchCodebaseResult { result: Some(api::search_codebase_result::Result::Error( api::search_codebase_result::Error { message: "User cancelled the codebase search.".to_string(), }, )), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: RequestFileEditsResult) -> Result { match result { RequestFileEditsResult::Success { updated_files, deleted_files, .. } => Ok( api::request::input::tool_call_result::Result::ApplyFileDiffs( api::ApplyFileDiffsResult { result: Some(api::apply_file_diffs_result::Result::Success( api::apply_file_diffs_result::Success { updated_files_v2: updated_files .into_iter() .flat_map(Into::>::into) .collect(), deleted_files: deleted_files .into_iter() .map(|file_path| { api::apply_file_diffs_result::success::DeletedFile { file_path, } }) .collect(), ..Default::default() }, )), }, ), ), RequestFileEditsResult::DiffApplicationFailed { error } => Ok( api::request::input::tool_call_result::Result::ApplyFileDiffs( api::ApplyFileDiffsResult { result: Some(api::apply_file_diffs_result::Result::Error( api::apply_file_diffs_result::Error { message: error }, )), }, ), ), RequestFileEditsResult::Cancelled => Ok( api::request::input::tool_call_result::Result::ApplyFileDiffs( api::ApplyFileDiffsResult { result: Some(api::apply_file_diffs_result::Result::Error( api::apply_file_diffs_result::Error { message: "User cancelled the file edits.".to_string(), }, )), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: SuggestNewConversationResult) -> Result { match result { SuggestNewConversationResult::Accepted { message_id } => Ok( api::request::input::tool_call_result::Result::SuggestNewConversation( api::SuggestNewConversationResult { result: Some(api::suggest_new_conversation_result::Result::Accepted( api::suggest_new_conversation_result::Accepted { message_id }, )), }, ), ), SuggestNewConversationResult::Rejected => Ok( api::request::input::tool_call_result::Result::SuggestNewConversation( api::SuggestNewConversationResult { result: Some(api::suggest_new_conversation_result::Result::Rejected( api::suggest_new_conversation_result::Rejected {}, )), }, ), ), SuggestNewConversationResult::Cancelled => Err(ConvertToAPITypeError::Ignore), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: SuggestPromptResult) -> Result { match result { SuggestPromptResult::Accepted { .. } => Ok( api::request::input::tool_call_result::Result::SuggestPrompt( api::SuggestPromptResult { result: Some(api::suggest_prompt_result::Result::Accepted(())), }, ), ), SuggestPromptResult::Cancelled => Ok( api::request::input::tool_call_result::Result::SuggestPrompt( api::SuggestPromptResult { result: Some(api::suggest_prompt_result::Result::Rejected(())), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: GrepResult) -> Result { match result { GrepResult::Success { matched_files } => Ok( api::request::input::tool_call_result::Result::Grep(api::GrepResult { result: Some(api::grep_result::Result::Success( api::grep_result::Success { matched_files: matched_files.into_iter().map(Into::into).collect(), }, )), }), ), GrepResult::Error(error) => Ok(api::request::input::tool_call_result::Result::Grep( api::GrepResult { result: Some(api::grep_result::Result::Error(api::grep_result::Error { message: error, })), }, )), GrepResult::Cancelled => Ok(api::request::input::tool_call_result::Result::Grep( api::GrepResult { result: Some(api::grep_result::Result::Error(api::grep_result::Error { message: "User cancelled the grep.".to_string(), })), }, )), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: FileGlobResult) -> Result { match result { FileGlobResult::Success { matched_files } => Ok( api::request::input::tool_call_result::Result::FileGlob(api::FileGlobResult { result: Some(api::file_glob_result::Result::Success( api::file_glob_result::Success { matched_files }, )), }), ), FileGlobResult::Error(error) => Ok( api::request::input::tool_call_result::Result::FileGlob(api::FileGlobResult { result: Some(api::file_glob_result::Result::Error( api::file_glob_result::Error { message: error }, )), }), ), FileGlobResult::Cancelled => Ok( api::request::input::tool_call_result::Result::FileGlob(api::FileGlobResult { result: Some(api::file_glob_result::Result::Error( api::file_glob_result::Error { message: "User cancelled the file glob.".to_string(), }, )), }), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: FileGlobV2Result) -> Result { match result { FileGlobV2Result::Success { matched_files, warnings, } => Ok(api::request::input::tool_call_result::Result::FileGlobV2( api::FileGlobV2Result { result: Some(api::file_glob_v2_result::Result::Success( api::file_glob_v2_result::Success { matched_files: matched_files.into_iter().map(Into::into).collect(), warnings: warnings.unwrap_or_default(), }, )), }, )), FileGlobV2Result::Error(error) => Ok( api::request::input::tool_call_result::Result::FileGlobV2(api::FileGlobV2Result { result: Some(api::file_glob_v2_result::Result::Error( api::file_glob_v2_result::Error { message: error }, )), }), ), FileGlobV2Result::Cancelled => Ok( api::request::input::tool_call_result::Result::FileGlobV2(api::FileGlobV2Result { result: Some(api::file_glob_v2_result::Result::Error( api::file_glob_v2_result::Error { message: "User cancelled the file glob.".to_string(), }, )), }), ), } } } // Convert FileGlobV2Result to FileGlobResult. impl From for FileGlobResult { fn from(value: FileGlobV2Result) -> Self { match value { FileGlobV2Result::Success { matched_files, warnings: _, } => FileGlobResult::Success { matched_files: matched_files .into_iter() .map(|matched_file| matched_file.file_path) .join("\n"), }, FileGlobV2Result::Error(e) => FileGlobResult::Error(e), FileGlobV2Result::Cancelled => FileGlobResult::Cancelled, } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: ReadMCPResourceResult) -> Result { match result { ReadMCPResourceResult::Success { resource_contents } => Ok( api::request::input::tool_call_result::Result::ReadMcpResource( api::ReadMcpResourceResult { result: Some(api::read_mcp_resource_result::Result::Success( api::read_mcp_resource_result::Success { contents: resource_contents .into_iter() .map(convert_mcp_resource_content) .collect(), }, )), }, ), ), ReadMCPResourceResult::Error(error) => Ok( api::request::input::tool_call_result::Result::ReadMcpResource( api::ReadMcpResourceResult { result: Some(api::read_mcp_resource_result::Result::Error( api::read_mcp_resource_result::Error { message: error }, )), }, ), ), ReadMCPResourceResult::Cancelled => Ok( api::request::input::tool_call_result::Result::ReadMcpResource( api::ReadMcpResourceResult { result: Some(api::read_mcp_resource_result::Result::Error( api::read_mcp_resource_result::Error { message: "User cancelled the MCP resource read.".to_string(), }, )), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: CallMCPToolResult) -> Result { match result { CallMCPToolResult::Success { result } => { Ok(api::request::input::tool_call_result::Result::CallMcpTool( api::CallMcpToolResult { result: Some(convert_mcp_tool_call_result(result)), }, )) } CallMCPToolResult::Error(error) => { Ok(api::request::input::tool_call_result::Result::CallMcpTool( api::CallMcpToolResult { result: Some(api::call_mcp_tool_result::Result::Error( api::call_mcp_tool_result::Error { message: error }, )), }, )) } CallMCPToolResult::Cancelled => { Ok(api::request::input::tool_call_result::Result::CallMcpTool( api::CallMcpToolResult { result: Some(api::call_mcp_tool_result::Result::Error( api::call_mcp_tool_result::Error { message: "User cancelled the MCP tool call.".to_string(), }, )), }, )) } } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: ReadSkillResult) -> Result { match result { ReadSkillResult::Success { content } => { let file_contents: Vec = content.into(); // There should only be one file content if file_contents.len() != 1 { return Err(ConvertToAPITypeError::Ignore); } Ok(api::request::input::tool_call_result::Result::ReadSkill( api::ReadSkillResult { result: Some(api::read_skill_result::Result::Success( api::read_skill_result::Success { content: Some(file_contents[0].clone()), }, )), }, )) } ReadSkillResult::Error(error) => Ok( api::request::input::tool_call_result::Result::ReadSkill(api::ReadSkillResult { result: Some(api::read_skill_result::Result::Error( api::read_skill_result::Error { message: error }, )), }), ), ReadSkillResult::Cancelled => Ok( api::request::input::tool_call_result::Result::ReadSkill(api::ReadSkillResult { result: Some(api::read_skill_result::Result::Error( api::read_skill_result::Error { message: "User cancelled reading the skill.".to_string(), }, )), }), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: ReadDocumentsResult) -> Result { match result { ReadDocumentsResult::Success { documents } => { let docs: Vec = documents .into_iter() .flat_map(Into::>::into) .collect(); Ok( api::request::input::tool_call_result::Result::ReadDocuments( api::ReadDocumentsResult { result: Some(api::read_documents_result::Result::Success( api::read_documents_result::Success { documents: docs }, )), }, ), ) } ReadDocumentsResult::Error(error) => Ok( api::request::input::tool_call_result::Result::ReadDocuments( api::ReadDocumentsResult { result: Some(api::read_documents_result::Result::Error( api::read_documents_result::Error { message: error }, )), }, ), ), ReadDocumentsResult::Cancelled => Ok( api::request::input::tool_call_result::Result::ReadDocuments( api::ReadDocumentsResult { result: Some(api::read_documents_result::Result::Error( api::read_documents_result::Error { message: "User cancelled reading the documents.".to_string(), }, )), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: EditDocumentsResult) -> Result { match result { EditDocumentsResult::Success { updated_documents } => { let docs: Vec = updated_documents .into_iter() .flat_map(Into::>::into) .collect(); Ok( api::request::input::tool_call_result::Result::EditDocuments( api::EditDocumentsResult { result: Some(api::edit_documents_result::Result::Success( api::edit_documents_result::Success { updated_documents: docs, }, )), }, ), ) } EditDocumentsResult::Error(error) => Ok( api::request::input::tool_call_result::Result::EditDocuments( api::EditDocumentsResult { result: Some(api::edit_documents_result::Result::Error( api::edit_documents_result::Error { message: error }, )), }, ), ), EditDocumentsResult::Cancelled => Ok( api::request::input::tool_call_result::Result::EditDocuments( api::EditDocumentsResult { result: Some(api::edit_documents_result::Result::Error( api::edit_documents_result::Error { message: "User cancelled editing the documents.".to_string(), }, )), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: CreateDocumentsResult) -> Result { match result { CreateDocumentsResult::Success { created_documents } => { let docs: Vec = created_documents .into_iter() .flat_map(Into::>::into) .collect(); Ok( api::request::input::tool_call_result::Result::CreateDocuments( api::CreateDocumentsResult { result: Some(api::create_documents_result::Result::Success( api::create_documents_result::Success { created_documents: docs, }, )), }, ), ) } CreateDocumentsResult::Error(error) => Ok( api::request::input::tool_call_result::Result::CreateDocuments( api::CreateDocumentsResult { result: Some(api::create_documents_result::Result::Error( api::create_documents_result::Error { message: error }, )), }, ), ), CreateDocumentsResult::Cancelled => Ok( api::request::input::tool_call_result::Result::CreateDocuments( api::CreateDocumentsResult { result: Some(api::create_documents_result::Result::Error( api::create_documents_result::Error { message: "User cancelled creating the documents.".to_string(), }, )), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: ReadShellCommandOutputResult) -> Result { match result { ReadShellCommandOutputResult::CommandFinished { command, block_id, output, exit_code, start_ts, completed_ts, } => Ok( api::request::input::tool_call_result::Result::ReadShellCommandOutput( api::ReadShellCommandOutputResult { command, result: Some(api::read_shell_command_output_result::Result::CommandFinished( api::ShellCommandFinished { command_id: block_id.to_string(), output, exit_code: exit_code.value(), start_ts: start_ts.map(local_datetime_to_timestamp), finish_ts: completed_ts.map(local_datetime_to_timestamp), }, )), }, ), ), ReadShellCommandOutputResult::LongRunningCommandSnapshot { command, block_id, grid_contents, cursor, is_alt_screen_active, is_preempted, } => Ok( api::request::input::tool_call_result::Result::ReadShellCommandOutput( api::ReadShellCommandOutputResult { command, result: Some( api::read_shell_command_output_result::Result::LongRunningCommandSnapshot( api::LongRunningShellCommandSnapshot { command_id: block_id.to_string(), output: grid_contents, cursor: cursor.to_owned(), is_alt_screen_active, is_preempted, }, ), ), }, ), ), ReadShellCommandOutputResult::Error(ShellCommandError::BlockNotFound) => { Ok(api::request::input::tool_call_result::Result::ReadShellCommandOutput( api::ReadShellCommandOutputResult { command: "".to_owned(), result: Some( api::read_shell_command_output_result::Result::Error( api::ShellCommandError { r#type: Some(api::shell_command_error::Type::CommandNotFound(())), }, ), ), }, ), ) } _ => Err(ConvertToAPITypeError::Ignore), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: TransferShellCommandControlToUserResult) -> Result { match result { TransferShellCommandControlToUserResult::Snapshot { block_id, grid_contents, cursor, is_alt_screen_active, is_preempted, } => Ok( api::request::input::tool_call_result::Result::TransferShellCommandControlToUser( api::TransferShellCommandControlToUserResult { result: Some( api::transfer_shell_command_control_to_user_result::Result::LongRunningCommandSnapshot( api::LongRunningShellCommandSnapshot { command_id: block_id.to_string(), output: grid_contents, cursor, is_alt_screen_active, is_preempted, }, ), ), }, ), ), TransferShellCommandControlToUserResult::CommandFinished { block_id, output, exit_code, start_ts, completed_ts, } => Ok( api::request::input::tool_call_result::Result::TransferShellCommandControlToUser( api::TransferShellCommandControlToUserResult { result: Some( api::transfer_shell_command_control_to_user_result::Result::CommandFinished( api::ShellCommandFinished { command_id: block_id.to_string(), output, exit_code: exit_code.value(), start_ts: start_ts.map(local_datetime_to_timestamp), finish_ts: completed_ts.map(local_datetime_to_timestamp), }, ), ), }, ), ), TransferShellCommandControlToUserResult::Cancelled => Ok( api::request::input::tool_call_result::Result::TransferShellCommandControlToUser( api::TransferShellCommandControlToUserResult { result: Some(api::transfer_shell_command_control_to_user_result::Result::Error( api::ShellCommandError { r#type: Some(api::shell_command_error::Type::CommandNotFound(())), }, )), }, ), ), TransferShellCommandControlToUserResult::Error(ShellCommandError::BlockNotFound) => { Ok(api::request::input::tool_call_result::Result::TransferShellCommandControlToUser( api::TransferShellCommandControlToUserResult { result: Some( api::transfer_shell_command_control_to_user_result::Result::Error( api::ShellCommandError { r#type: Some(api::shell_command_error::Type::CommandNotFound(())), }, ), ), }, ), ) } } } } impl From for Vec { fn from(context: FileContext) -> Self { match context.content.clone() { AnyFileContent::StringContent(content) => { vec![api::FileContent { file_path: context.file_name.clone(), content, line_range: context.line_range.map(|range| api::FileContentLineRange { start: range.start as u32, end: range.end as u32, }), }] } // Ignore any binary context since they can't be converted to FileContent AnyFileContent::BinaryContent(_content) => vec![], } } } impl From for Vec { fn from(context: FileContext) -> Self { match context.content.clone() { AnyFileContent::StringContent(content) => { vec![api::AnyFileContent { content: Some(api::any_file_content::Content::TextContent( api::FileContent { file_path: context.file_name.clone(), content, line_range: context.line_range.map(|range| api::FileContentLineRange { start: range.start as u32, end: range.end as u32, }), }, )), }] } AnyFileContent::BinaryContent(content) => { // Binary content: drop any line range and return binary content as-is. vec![api::AnyFileContent { content: Some(api::any_file_content::Content::BinaryContent( api::BinaryFileContent { file_path: context.file_name.clone(), data: content, }, )), }] } } } } impl From for api::grep_result::success::grep_file_match::GrepLineMatch { fn from(value: GrepLineMatch) -> Self { api::grep_result::success::grep_file_match::GrepLineMatch { line_number: value.line_number as u32, } } } impl From for api::grep_result::success::GrepFileMatch { fn from(value: GrepFileMatch) -> Self { api::grep_result::success::GrepFileMatch { file_path: value.file_path, matched_lines: value.matched_lines.into_iter().map(Into::into).collect(), } } } impl From for api::file_glob_v2_result::success::FileGlobMatch { fn from(value: FileGlobV2Match) -> Self { api::file_glob_v2_result::success::FileGlobMatch { file_path: value.file_path, } } } impl From for Vec { fn from(context: DocumentContext) -> Self { let content = context.content.clone(); if context.line_ranges.is_empty() { return vec![api::DocumentContent { document_id: context.document_id.to_string(), content, line_range: None, }]; } let lines: Vec<_> = content.lines().collect(); context .line_ranges .iter() .filter_map(|range| { let start = range.start.saturating_sub(1).min(lines.len()); let end = range.end.min(lines.len()); if start >= end { None } else { let fragment = lines[start..end].join("\n"); Some(api::DocumentContent { document_id: context.document_id.to_string(), content: fragment, line_range: Some(api::FileContentLineRange { start: range.start as u32, end: range.end as u32, }), }) } }) .collect() } } fn convert_mcp_resource_content(val: rmcp::model::ResourceContents) -> api::McpResourceContent { use api::mcp_resource_content::*; match val { rmcp::model::ResourceContents::TextResourceContents { uri, mime_type, text, .. } => api::McpResourceContent { uri, content_type: Some(ContentType::Text(Text { content: text, mime_type: mime_type.unwrap_or_default(), })), }, rmcp::model::ResourceContents::BlobResourceContents { uri, mime_type, blob, .. } => api::McpResourceContent { uri, content_type: Some(ContentType::Binary(Binary { data: blob.into_bytes(), mime_type: mime_type.unwrap_or_default(), })), }, } } impl From for AIAgentActionResultType { fn from(result: CreateDocumentsResult) -> Self { AIAgentActionResultType::CreateDocuments(result) } } impl From for AIAgentActionResultType { fn from(result: EditDocumentsResult) -> Self { AIAgentActionResultType::EditDocuments(result) } } impl From for AIAgentActionResultType { fn from(result: ReadDocumentsResult) -> Self { AIAgentActionResultType::ReadDocuments(result) } } impl From for AIAgentActionResultType { fn from(result: ReadSkillResult) -> Self { AIAgentActionResultType::ReadSkill(result) } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: RequestComputerUseResult) -> Result { match result { RequestComputerUseResult::Approved { screenshot, platform, windows, } => Ok( api::request::input::tool_call_result::Result::RequestComputerUse( api::RequestComputerUseResult { result: Some(api::request_computer_use_result::Result::Approved( api::request_computer_use_result::Approved { screen_dimensions: Some(api::ScreenDimensions { width_px: screenshot.original_width as i32, height_px: screenshot.original_height as i32, }), initial_screenshot: Some(api::RawImage { data: screenshot.data, mime_type: screenshot.mime_type.to_string(), width: screenshot.width as i32, height: screenshot.height as i32, }), platform: convert_platform(platform).into(), windows: windows.into_iter().map(convert_window_info).collect(), }, )), }, ), ), RequestComputerUseResult::Cancelled => Ok( api::request::input::tool_call_result::Result::RequestComputerUse( api::RequestComputerUseResult { result: Some(api::request_computer_use_result::Result::Rejected( api::request_computer_use_result::Rejected {}, )), }, ), ), RequestComputerUseResult::Error(error) => Ok( api::request::input::tool_call_result::Result::RequestComputerUse( api::RequestComputerUseResult { result: Some(api::request_computer_use_result::Result::Error( api::request_computer_use_result::Error { message: error }, )), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: UseComputerResult) -> Result { match result { UseComputerResult::Success(result) => { // Copy out the captured-window metadata (if any) before the owned fields of // `result` are moved into the message below. let captured = result.captured_window; Ok(api::request::input::tool_call_result::Result::UseComputer( api::UseComputerResult { result: Some(api::use_computer_result::Result::Success( api::use_computer_result::Success { screenshot: result.screenshot.map(|s| api::RawImage { data: s.data, mime_type: s.mime_type.to_string(), width: s.width as i32, height: s.height as i32, }), cursor_position: result.cursor_position.map(vec_to_coordinates), windows: result .windows .into_iter() .map(convert_window_info) .collect(), // The window id is an opaque string on the wire; on macOS it is a // CGWindowID, so format the u32 back to a string at the boundary. captured_window: captured.map(|c| { api::use_computer_result::success::CapturedWindow { window_id: c.window_id.to_string(), width_px: c.width_px, height_px: c.height_px, } }), }, )), }, )) } UseComputerResult::Error(error) => { Ok(api::request::input::tool_call_result::Result::UseComputer( api::UseComputerResult { result: Some(api::use_computer_result::Result::Error( api::use_computer_result::Error { message: error }, )), }, )) } UseComputerResult::Cancelled => { Ok(api::request::input::tool_call_result::Result::UseComputer( api::UseComputerResult { result: Some(api::use_computer_result::Result::Error( api::use_computer_result::Error { message: "User cancelled the computer use.".to_string(), }, )), }, )) } } } } fn vec_to_coordinates(vec: computer_use::Vector2I) -> api::Coordinates { api::Coordinates { x: vec.x(), y: vec.y(), } } /// Converts a computer_use window record into the API `WindowInfo` message. fn convert_window_info(window: computer_use::WindowInfo) -> api::WindowInfo { api::WindowInfo { // The window id travels as an opaque string; on macOS it is a CGWindowID (u32). window_id: window.window_id.to_string(), pid: window.pid, app_name: window.app_name, title: window.title, layer: window.layer, } } fn convert_platform( platform: computer_use::Platform, ) -> api::request_computer_use_result::approved::Platform { use api::request_computer_use_result::approved::Platform; match platform { computer_use::Platform::Mac => Platform::Macos, computer_use::Platform::Windows => Platform::Windows, computer_use::Platform::LinuxX11 => Platform::LinuxX11, computer_use::Platform::LinuxWayland => Platform::LinuxWayland, } } fn convert_mcp_tool_call_result( val: rmcp::model::CallToolResult, ) -> api::call_mcp_tool_result::Result { if val.is_error.unwrap_or_default() { return api::call_mcp_tool_result::Result::Error(api::call_mcp_tool_result::Error { message: val .structured_content .map(|content| content.to_string()) .unwrap_or_default(), }); } use api::call_mcp_tool_result::success::{self, result}; api::call_mcp_tool_result::Result::Success(api::call_mcp_tool_result::Success { results: val .content .into_iter() .filter_map(|content| { use rmcp::model::RawContent::*; match content.raw { Text(raw_text_content) => Some(result::Result::Text(result::Text { text: raw_text_content.text, })), Image(raw_image_content) => Some(result::Result::Image(result::Image { data: raw_image_content.data.into_bytes(), mime_type: raw_image_content.mime_type, })), Resource(raw_embedded_resource) => Some(result::Result::Resource( convert_mcp_resource_content(raw_embedded_resource.resource), )), Audio(_) => { log::warn!("Audio content not supported"); None } ResourceLink(_) => { log::warn!("Resource link content not supported"); None } } }) .map(|result| success::Result { result: Some(result), }) .collect(), }) } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: FetchConversationResult) -> Result { match result { FetchConversationResult::Success { directory_path } => Ok( api::request::input::tool_call_result::Result::FetchConversation( api::FetchConversationResult { result: Some(api::fetch_conversation_result::Result::Success( api::fetch_conversation_result::Success { directory_path }, )), }, ), ), FetchConversationResult::Error(message) => Ok( api::request::input::tool_call_result::Result::FetchConversation( api::FetchConversationResult { result: Some(api::fetch_conversation_result::Result::Error( api::fetch_conversation_result::Error { message }, )), }, ), ), FetchConversationResult::Cancelled => Ok( api::request::input::tool_call_result::Result::FetchConversation( api::FetchConversationResult { result: Some(api::fetch_conversation_result::Result::Error( api::fetch_conversation_result::Error { message: "User cancelled fetching the conversation.".to_string(), }, )), }, ), ), } } } impl From for api::request::input::tool_call_result::Result { fn from(result: StartAgentResult) -> Self { match result { StartAgentResult::Success { agent_id, version: StartAgentVersion::V1, } => api::request::input::tool_call_result::Result::StartAgent(api::StartAgentResult { result: Some(api::start_agent_result::Result::Success( api::start_agent_result::Success { agent_id }, )), }), StartAgentResult::Error { error, version: StartAgentVersion::V1, } => api::request::input::tool_call_result::Result::StartAgent(api::StartAgentResult { result: Some(api::start_agent_result::Result::Error( api::start_agent_result::Error { error }, )), }), StartAgentResult::Cancelled { version: StartAgentVersion::V1, } => api::request::input::tool_call_result::Result::StartAgent(api::StartAgentResult { result: Some(api::start_agent_result::Result::Error( api::start_agent_result::Error { error: "Cancelled by user".to_string(), }, )), }), // The remaining arms translate the v2 result schema back into the shared client // StartAgentResult so downstream UI/rendering code can stay version-agnostic. StartAgentResult::Success { agent_id, version: StartAgentVersion::V2, } => api::request::input::tool_call_result::Result::StartAgentV2( api::StartAgentV2Result { result: Some(api::start_agent_v2_result::Result::Success( api::start_agent_v2_result::Success { agent_id }, )), }, ), StartAgentResult::Error { error, version: StartAgentVersion::V2, } => api::request::input::tool_call_result::Result::StartAgentV2( api::StartAgentV2Result { result: Some(api::start_agent_v2_result::Result::Error( api::start_agent_v2_result::Error { error }, )), }, ), StartAgentResult::Cancelled { version: StartAgentVersion::V2, } => api::request::input::tool_call_result::Result::StartAgentV2( api::StartAgentV2Result { result: Some(api::start_agent_v2_result::Result::Error( api::start_agent_v2_result::Error { error: "Cancelled by user".to_string(), }, )), }, ), } } } impl From for api::request::input::tool_call_result::Result { fn from(result: SendMessageToAgentResult) -> Self { api::request::input::tool_call_result::Result::SendMessageToAgent( api::SendMessageToAgentResult { result: match result { SendMessageToAgentResult::Success { message_id } => { Some(api::send_message_to_agent_result::Result::Success( api::send_message_to_agent_result::Success { message_id }, )) } SendMessageToAgentResult::Error(error) => { Some(api::send_message_to_agent_result::Result::Error( api::send_message_to_agent_result::Error { message: error }, )) } SendMessageToAgentResult::Cancelled => { Some(api::send_message_to_agent_result::Result::Error( api::send_message_to_agent_result::Error { message: "Cancelled by user".to_string(), }, )) } }, }, ) } } impl From for api::ask_user_question_result::AnswerItem { fn from(item: AskUserQuestionAnswerItem) -> Self { match item { AskUserQuestionAnswerItem::Answered { question_id, selected_options, other_text, } => api::ask_user_question_result::AnswerItem { question_id, answer: Some(AskUserQuestionAnswer::MultipleChoice( answer_item::MultipleChoiceAnswer { selected_options, other_text, }, )), }, AskUserQuestionAnswerItem::Skipped { question_id } => { api::ask_user_question_result::AnswerItem { question_id, answer: Some(AskUserQuestionAnswer::Skipped(())), } } } } } impl From for api::request::input::tool_call_result::Result { fn from(result: AskUserQuestionResult) -> Self { let api_result = match result { AskUserQuestionResult::Success { answers } => { let api_answers = answers.into_iter().map(Into::into).collect(); Some(api::ask_user_question_result::Result::Success( api::ask_user_question_result::Success { answers: api_answers, }, )) } AskUserQuestionResult::SkippedByAutoApprove { question_ids } => { let api_answers = question_ids .into_iter() .map(|question_id| api::ask_user_question_result::AnswerItem { question_id, answer: Some(AskUserQuestionAnswer::Skipped(())), }) .collect(); Some(api::ask_user_question_result::Result::Success( api::ask_user_question_result::Success { answers: api_answers, }, )) } AskUserQuestionResult::Error(message) => { Some(api::ask_user_question_result::Result::Error( api::ask_user_question_result::Error { message }, )) } AskUserQuestionResult::Cancelled => Some(api::ask_user_question_result::Result::Error( api::ask_user_question_result::Error { message: "Cancelled by user".to_string(), }, )), }; api::request::input::tool_call_result::Result::AskUserQuestion(api::AskUserQuestionResult { result: api_result, }) } } impl From for api::run_agents_result::launched::ResolvedExecutionMode { fn from(mode: RunAgentsLaunchedExecutionMode) -> Self { match mode { RunAgentsLaunchedExecutionMode::Local => { api::run_agents_result::launched::ResolvedExecutionMode::Local( api::run_agents::Local {}, ) } RunAgentsLaunchedExecutionMode::Remote { environment_id, worker_host, computer_use_enabled, } => api::run_agents_result::launched::ResolvedExecutionMode::Remote( api::run_agents::Remote { environment_id, worker_host, computer_use_enabled, }, ), } } } impl From for api::run_agents_result::AgentOutcome { fn from(outcome: RunAgentsAgentOutcome) -> Self { let result = match outcome.kind { RunAgentsAgentOutcomeKind::Launched { agent_id } => { api::run_agents_result::agent_outcome::Result::Launched( api::run_agents_result::LaunchedAgent { agent_id }, ) } RunAgentsAgentOutcomeKind::Failed { error } => { api::run_agents_result::agent_outcome::Result::Failed( api::run_agents_result::FailedAgent { error }, ) } }; api::run_agents_result::AgentOutcome { name: outcome.name, result: Some(result), } } } /// Maps a client-side harness string identifier (e.g. "oz", "claude") /// to the new proto `Harness` oneof. Returns `None` for empty, /// unrecognized, or `"unknown"` strings; callers leave /// `resolved_harness` unset in that case. pub(super) fn build_api_harness(harness_type: &str) -> Option { let normalized = harness_type.trim().to_ascii_lowercase().replace('_', "-"); let variant = match normalized.as_str() { "oz" => api::harness::Variant::Oz(api::harness::Oz {}), "claude" | "claude-code" => api::harness::Variant::ClaudeCode(api::harness::ClaudeCode {}), "opencode" | "open-code" => api::harness::Variant::OpenCode(api::harness::OpenCode {}), "gemini" => api::harness::Variant::Gemini(api::harness::Gemini {}), "codex" => api::harness::Variant::Codex(api::harness::Codex {}), _ => return None, }; Some(api::Harness { variant: Some(variant), }) } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: RunAgentsResult) -> Result { match result { RunAgentsResult::Launched { model_id, harness_type, execution_mode, agents, } => Ok( api::request::input::tool_call_result::Result::RunAgentsResult( api::RunAgentsResult { outcome: Some(api::run_agents_result::Outcome::Launched( api::run_agents_result::Launched { resolved_model_id: model_id, resolved_harness: build_api_harness(&harness_type), resolved_execution_mode: Some(execution_mode.into()), agents: agents.into_iter().map(Into::into).collect(), }, )), }, ), ), RunAgentsResult::Denied { reason } => Ok( api::request::input::tool_call_result::Result::RunAgentsResult( api::RunAgentsResult { outcome: Some(api::run_agents_result::Outcome::Denied( api::run_agents_result::Denied { reason }, )), }, ), ), RunAgentsResult::Failure { error } => Ok( api::request::input::tool_call_result::Result::RunAgentsResult( api::RunAgentsResult { outcome: Some(api::run_agents_result::Outcome::Failure( api::run_agents_result::Failure { error }, )), }, ), ), // Reject is conveyed by the generic ToolCallResult.Cancel marker // synthesized server-side on the next user input; nothing for the // client to send on the wire here. RunAgentsResult::Cancelled => Err(ConvertToAPITypeError::Ignore), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; fn try_from(result: InsertReviewCommentsResult) -> Result { match result { InsertReviewCommentsResult::Success { repo_path } => Ok( api::request::input::tool_call_result::Result::InsertReviewComments( api::InsertReviewCommentsResult { repo_path, result: Some(api::insert_review_comments_result::Result::Success( api::insert_review_comments_result::Success {}, )), }, ), ), InsertReviewCommentsResult::Error { repo_path, message } => Ok( api::request::input::tool_call_result::Result::InsertReviewComments( api::InsertReviewCommentsResult { repo_path, result: Some(api::insert_review_comments_result::Result::Error( api::insert_review_comments_result::Error { message }, )), }, ), ), InsertReviewCommentsResult::Cancelled => Ok( api::request::input::tool_call_result::Result::InsertReviewComments( api::InsertReviewCommentsResult { repo_path: String::new(), result: Some(api::insert_review_comments_result::Result::Error( api::insert_review_comments_result::Error { message: "User cancelled inserting review comments.".to_string(), }, )), }, ), ), } } } impl TryFrom for api::request::input::tool_call_result::Result { type Error = ConvertToAPITypeError; /// Completed → wire form; Cancelled → drop (mirrors RunAgents). fn try_from(result: WaitForEventsResult) -> Result { match result { WaitForEventsResult::Completed => Ok( api::request::input::tool_call_result::Result::WaitForEvents( api::WaitForEventsResult {}, ), ), WaitForEventsResult::Cancelled => Err(ConvertToAPITypeError::Ignore), } } } #[cfg(test)] #[path = "convert_tests.rs"] mod tests;