mod convert; use std::fmt::Display; use std::ops::Range; use std::time::SystemTime; use chrono::{DateTime, Local}; use galaxy_core::command::ExitCode; use galaxy_terminal::model::BlockId; use itertools::Itertools as _; use serde::{Deserialize, Serialize}; use warp_multi_agent_api::apply_file_diffs_result::success::UpdatedFileContent; use crate::agent::FileLocations; use crate::document::{AIDocumentId, AIDocumentVersion}; #[derive(Debug, Clone, PartialEq)] pub enum AIAgentActionResultType { /// The output of a requested command. RequestCommandOutput(RequestCommandOutputResult), /// The result of sending some input to a long-running command. WriteToLongRunningShellCommand(WriteToLongRunningShellCommandResult), /// The output of a requested file edits. RequestFileEdits(RequestFileEditsResult), /// The output of a read files action. ReadFiles(ReadFilesResult), /// The output of an upload artifact action. UploadArtifact(UploadArtifactResult), /// The output of a search codebase action. SearchCodebase(SearchCodebaseResult), /// The output of a grep action. Grep(GrepResult), /// The output of a file glob action. FileGlob(FileGlobResult), /// The output of a file glob V2 action. FileGlobV2(FileGlobV2Result), /// The output of reading an MCP resource. ReadMCPResource(ReadMCPResourceResult), /// The output of calling an MCP tool. CallMCPTool(CallMCPToolResult), /// The output of reading a skill. ReadSkill(ReadSkillResult), /// The output of suggesting a new conversation. SuggestNewConversation(SuggestNewConversationResult), /// The result of suggesting a prompt. SuggestPrompt(SuggestPromptResult), OpenCodeReview, InitProject, /// The output of a read documents action. ReadDocuments(ReadDocumentsResult), /// The output of an edit documents action. EditDocuments(EditDocumentsResult), /// The output of a create documents action. CreateDocuments(CreateDocumentsResult), /// The output of reading shell command output. ReadShellCommandOutput(ReadShellCommandOutputResult), /// The output of using computer. UseComputer(UseComputerResult), /// The result of inserting code review comments. InsertReviewComments(InsertReviewCommentsResult), /// The output of requesting computer use. RequestComputerUse(RequestComputerUseResult), /// The result of fetching a conversation's tasks. FetchConversation(FetchConversationResult), /// The result of starting a child agent. StartAgent(StartAgentResult), /// The result of sending a message to another agent. SendMessageToAgent(SendMessageToAgentResult), /// The output of transferring shell command control to the user. TransferShellCommandControlToUser(TransferShellCommandControlToUserResult), /// The result of asking the user a question. AskUserQuestion(AskUserQuestionResult), /// The result of an orchestrate tool call: launched (with per-agent /// outcomes), launch denied (Stage 2), failure, or cancelled. RunAgents(RunAgentsResult), /// Result of the client-side wait_for_events watchdog or inbound /// resume. WaitForEvents(WaitForEventsResult), } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] pub enum StartAgentVersion { #[default] V1, V2, } impl AIAgentActionResultType { /// Returns the effective command string for command-related results, if any. /// /// This is used by UIs (e.g. requested command views) that want to display the /// final executed command rather than the original suggestion. pub fn command_str(&self) -> Option<&str> { match self { AIAgentActionResultType::RequestCommandOutput( RequestCommandOutputResult::Completed { command, .. }, ) | AIAgentActionResultType::RequestCommandOutput( RequestCommandOutputResult::LongRunningCommandSnapshot { command, .. }, ) | AIAgentActionResultType::ReadShellCommandOutput( ReadShellCommandOutputResult::CommandFinished { command, .. }, ) | AIAgentActionResultType::ReadShellCommandOutput( ReadShellCommandOutputResult::LongRunningCommandSnapshot { command, .. }, ) => Some(command.as_str()), _ => None, } } /// Returns the authoritative result content to send back to a model. /// /// `Display` is intentionally concise for UI summaries, so content-bearing /// results must not use it directly when constructing the next model turn. pub fn model_content(&self) -> String { match self { Self::RequestCommandOutput(result) => match result { RequestCommandOutputResult::Completed { command, output, exit_code, .. } => command_result_content(Some(command), output, exit_code.value()), RequestCommandOutputResult::LongRunningCommandSnapshot { block_id, command, grid_contents, cursor, is_alt_screen_active, .. } => shell_snapshot_content( block_id, Some(command), grid_contents, cursor, *is_alt_screen_active, None, ), RequestCommandOutputResult::CancelledBeforeExecution | RequestCommandOutputResult::Denylisted { .. } => result.to_string(), }, Self::WriteToLongRunningShellCommand(result) => match result { WriteToLongRunningShellCommandResult::Snapshot { block_id, grid_contents, cursor, is_alt_screen_active, is_preempted, .. } => shell_snapshot_content( block_id, None, grid_contents, cursor, *is_alt_screen_active, Some(*is_preempted), ), WriteToLongRunningShellCommandResult::CommandFinished { output, exit_code, .. } => command_result_content(None, output, exit_code.value()), WriteToLongRunningShellCommandResult::Cancelled | WriteToLongRunningShellCommandResult::Error(_) => result.to_string(), }, Self::ReadFiles(result) => match result { ReadFilesResult::Success { files } => file_contexts_content(files), ReadFilesResult::Error(_) | ReadFilesResult::Cancelled => result.to_string(), }, Self::SearchCodebase(result) => match result { SearchCodebaseResult::Success { files } => file_contexts_content(files), SearchCodebaseResult::Failed { .. } | SearchCodebaseResult::Cancelled => { result.to_string() } }, Self::ReadSkill(result) => match result { ReadSkillResult::Success { content } => file_context_content(content), ReadSkillResult::Error(_) | ReadSkillResult::Cancelled => result.to_string(), }, Self::ReadDocuments(result) => match result { ReadDocumentsResult::Success { documents } => document_contexts_content(documents), ReadDocumentsResult::Error(_) | ReadDocumentsResult::Cancelled => { result.to_string() } }, Self::EditDocuments(result) => match result { EditDocumentsResult::Success { updated_documents } => { document_contexts_content(updated_documents) } EditDocumentsResult::Error(_) | EditDocumentsResult::Cancelled => { result.to_string() } }, Self::CreateDocuments(result) => match result { CreateDocumentsResult::Success { created_documents } => { document_contexts_content(created_documents) } CreateDocumentsResult::Error(_) | CreateDocumentsResult::Cancelled => { result.to_string() } }, Self::ReadShellCommandOutput(result) => match result { ReadShellCommandOutputResult::CommandFinished { command, output, exit_code, .. } => command_result_content(Some(command), output, exit_code.value()), ReadShellCommandOutputResult::LongRunningCommandSnapshot { block_id, command, grid_contents, cursor, is_alt_screen_active, is_preempted, .. } => shell_snapshot_content( block_id, Some(command), grid_contents, cursor, *is_alt_screen_active, Some(*is_preempted), ), ReadShellCommandOutputResult::Cancelled | ReadShellCommandOutputResult::Error(_) => result.to_string(), }, Self::TransferShellCommandControlToUser(result) => match result { TransferShellCommandControlToUserResult::Snapshot { block_id, grid_contents, cursor, is_alt_screen_active, is_preempted, .. } => format!( "{}\nControl has been transferred to the user. Do not write to the command until control is returned.", shell_snapshot_content( block_id, None, grid_contents, cursor, *is_alt_screen_active, Some(*is_preempted), ) ), TransferShellCommandControlToUserResult::CommandFinished { output, exit_code, .. } => command_result_content(None, output, exit_code.value()), TransferShellCommandControlToUserResult::Cancelled | TransferShellCommandControlToUserResult::Error(_) => result.to_string(), }, Self::RequestFileEdits(_) | Self::UploadArtifact(_) | Self::Grep(_) | Self::FileGlob(_) | Self::FileGlobV2(_) | Self::ReadMCPResource(_) | Self::CallMCPTool(_) | Self::SuggestNewConversation(_) | Self::SuggestPrompt(_) | Self::OpenCodeReview | Self::InitProject | Self::UseComputer(_) | Self::InsertReviewComments(_) | Self::RequestComputerUse(_) | Self::FetchConversation(_) | Self::StartAgent(_) | Self::SendMessageToAgent(_) | Self::AskUserQuestion(_) | Self::WaitForEvents(_) => self.to_string(), Self::RunAgents(result) => result.model_content(), } } } fn command_result_content(command: Option<&str>, output: &str, exit_code: i32) -> String { let command = command .map(|command| format!("Command: {command}\n")) .unwrap_or_default(); let output = if output.is_empty() { "(no output)" } else { output }; format!("{command}Command finished with exit code {exit_code}.\nOutput:\n{output}") } fn shell_snapshot_content( block_id: &BlockId, command: Option<&str>, grid_contents: &str, cursor: &str, is_alt_screen_active: bool, is_preempted: Option, ) -> String { let command = command .map(|command| format!("Command: {command}\n")) .unwrap_or_default(); let preempted = is_preempted .map(|is_preempted| format!("\nPreempted: {is_preempted}")) .unwrap_or_default(); format!( "{command}Command is still running.\nCommand ID: {block_id}\nCurrent output:\n{grid_contents}\nCursor: {cursor}\nAlt screen active: {is_alt_screen_active}{preempted}\nThe next assistant output MUST be a tool call: continue monitoring with `read_shell_command_output` using command_id `{block_id}` and a short wait. If the user's explicit stop condition is met, call `interrupt_shell_command` immediately. Do not end this turn with prose or report completion while the command is still running." ) } fn file_contexts_content(files: &[FileContext]) -> String { files .iter() .map(file_context_content) .collect::>() .join("\n\n") } fn file_context_content(file: &FileContext) -> String { match &file.content { AnyFileContent::StringContent(content) => format!("{file}:\n{content}"), AnyFileContent::BinaryContent(content) => { format!("{file}:\n[binary file, {} bytes]", content.len()) } } } fn document_contexts_content(documents: &[DocumentContext]) -> String { documents .iter() .map(|document| format!("{document}:\n{}", document.content)) .collect::>() .join("\n\n") } #[cfg(test)] #[path = "mod_tests.rs"] mod tests; impl Display for AIAgentActionResultType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AIAgentActionResultType::RequestCommandOutput(result) => result.fmt(f), AIAgentActionResultType::WriteToLongRunningShellCommand(result) => result.fmt(f), AIAgentActionResultType::RequestFileEdits(result) => result.fmt(f), AIAgentActionResultType::ReadFiles(result) => result.fmt(f), AIAgentActionResultType::UploadArtifact(result) => result.fmt(f), AIAgentActionResultType::SearchCodebase(result) => result.fmt(f), AIAgentActionResultType::Grep(result) => result.fmt(f), AIAgentActionResultType::FileGlob(result) => result.fmt(f), AIAgentActionResultType::FileGlobV2(result) => result.fmt(f), AIAgentActionResultType::ReadMCPResource(result) => result.fmt(f), AIAgentActionResultType::CallMCPTool(result) => result.fmt(f), AIAgentActionResultType::ReadSkill(result) => result.fmt(f), AIAgentActionResultType::SuggestNewConversation(result) => result.fmt(f), AIAgentActionResultType::SuggestPrompt(result) => result.fmt(f), AIAgentActionResultType::ReadDocuments(result) => result.fmt(f), AIAgentActionResultType::EditDocuments(result) => result.fmt(f), AIAgentActionResultType::CreateDocuments(result) => result.fmt(f), AIAgentActionResultType::ReadShellCommandOutput(result) => result.fmt(f), AIAgentActionResultType::UseComputer(result) => result.fmt(f), AIAgentActionResultType::InsertReviewComments(result) => result.fmt(f), AIAgentActionResultType::RequestComputerUse(result) => result.fmt(f), AIAgentActionResultType::FetchConversation(result) => result.fmt(f), AIAgentActionResultType::StartAgent(result) => result.fmt(f), AIAgentActionResultType::SendMessageToAgent(result) => result.fmt(f), AIAgentActionResultType::TransferShellCommandControlToUser(result) => result.fmt(f), AIAgentActionResultType::AskUserQuestion(result) => result.fmt(f), AIAgentActionResultType::RunAgents(result) => result.fmt(f), AIAgentActionResultType::WaitForEvents(result) => result.fmt(f), AIAgentActionResultType::OpenCodeReview | AIAgentActionResultType::InitProject => { Ok(()) } } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum RequestCommandOutputResult { Completed { block_id: BlockId, command: String, output: String, exit_code: ExitCode, start_ts: Option>, completed_ts: Option>, }, LongRunningCommandSnapshot { block_id: BlockId, command: String, grid_contents: String, cursor: String, is_alt_screen_active: bool, }, /// A running command canceled via ctrl-c /// would have Completed result with exit code 130. CancelledBeforeExecution, /// The command was denied because it was present on the denylist. Denylisted { command: String }, } impl RequestCommandOutputResult { pub fn is_successful(&self) -> bool { match self { Self::Completed { exit_code, .. } => exit_code.was_successful(), Self::LongRunningCommandSnapshot { .. } => true, Self::CancelledBeforeExecution | Self::Denylisted { .. } => false, } } pub fn failed(&self) -> bool { match self { Self::Completed { exit_code, .. } => !exit_code.was_successful(), Self::Denylisted { .. } => true, Self::CancelledBeforeExecution | Self::LongRunningCommandSnapshot { .. } => false, } } /// Returns a stable identity string for loop detection (command input only, no output). pub fn loop_description(&self) -> String { match self { Self::Completed { command, .. } | Self::LongRunningCommandSnapshot { command, .. } | Self::Denylisted { command } => command.clone(), Self::CancelledBeforeExecution => "cancelled".to_string(), } } } impl Display for RequestCommandOutputResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RequestCommandOutputResult::Completed { command, output, exit_code, .. } => { write!( f, "Command '{}' completed with exit code {}:\n{}", command, exit_code.value(), output ) } RequestCommandOutputResult::LongRunningCommandSnapshot { command, .. } => { write!(f, "Command '{command}' is long-running") } RequestCommandOutputResult::CancelledBeforeExecution => { write!(f, "Command output cancelled") } RequestCommandOutputResult::Denylisted { .. } => { write!(f, "Command output was on denylist") } } } } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub enum ShellCommandError { BlockNotFound, } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub enum WriteToLongRunningShellCommandResult { Snapshot { block_id: BlockId, grid_contents: String, cursor: String, is_alt_screen_active: bool, is_preempted: bool, }, CommandFinished { block_id: BlockId, output: String, exit_code: ExitCode, start_ts: Option>, completed_ts: Option>, }, Cancelled, Error(ShellCommandError), } impl Display for WriteToLongRunningShellCommandResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Snapshot { .. } => { write!(f, "Sent snapshot of long-running shell command to agent") } Self::CommandFinished { output, exit_code, .. } => write!( f, "Long-running shell command finished with exit code {}:\n{output}", exit_code.value() ), Self::Cancelled => write!(f, "Writing to long-running shell command cancelled"), Self::Error(e) => write!(f, "Write to long-running shell command failed: {e:?}"), } } } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub enum SuggestNewConversationResult { Accepted { message_id: String }, Rejected, Cancelled, } impl Display for SuggestNewConversationResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Accepted { message_id } => { write!( f, "Suggest new conversation accepted for message {message_id}" ) } Self::Rejected => { write!(f, "Suggest new conversation rejected for message") } Self::Cancelled => write!(f, "Suggest new conversation cancelled"), } } } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub enum AnyFileContent { StringContent(String), BinaryContent(Vec), } impl AnyFileContent { pub fn len(&self) -> usize { match self { Self::StringContent(content) => content.len(), Self::BinaryContent(content) => content.len(), } } pub fn line_count(&self) -> usize { match self { Self::StringContent(content) => content.lines().count(), Self::BinaryContent(_) => 0, } } pub fn is_empty(&self) -> bool { match self { Self::StringContent(content) => content.is_empty(), Self::BinaryContent(content) => content.is_empty(), } } } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct FileContext { pub file_name: String, pub content: AnyFileContent, pub line_range: Option>, pub last_modified: Option, pub line_count: usize, } impl FileContext { // create a new FileContext and autocalculate number of lines in the given file pub fn new( file_name: String, content: AnyFileContent, line_range: Option>, last_modified: Option, ) -> Self { let string_content = if let AnyFileContent::StringContent(content) = content.clone() { content } else { return Self { file_name, content, line_range, last_modified, line_count: 0, }; }; let line_count = string_content.lines().count(); Self { file_name, content: AnyFileContent::StringContent(string_content), line_range, last_modified, line_count, } } } impl Display for FileContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.line_range { None => write!(f, "{}", self.file_name), Some(range) => write!(f, "{} ({}-{})", self.file_name, range.start, range.end), } } } impl From<&FileContext> for FileLocations { fn from(context: &FileContext) -> Self { FileLocations { name: context.file_name.clone(), lines: context.line_range.clone().into_iter().collect(), } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum ReadFilesResult { Success { files: Vec }, Error(String), Cancelled, } impl Display for ReadFilesResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ReadFilesResult::Success { files } => { write!(f, "Read files: {}", files.iter().format(", ")) } ReadFilesResult::Error(error) => write!(f, "Read files error: {error}"), ReadFilesResult::Cancelled => write!(f, "Read files cancelled"), } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum UploadArtifactResult { Success { artifact_uid: String, filepath: Option, mime_type: String, description: Option, size_bytes: i64, }, Error(String), Cancelled, } impl Display for UploadArtifactResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { UploadArtifactResult::Success { artifact_uid, filepath, .. } => match filepath { Some(filepath) => write!(f, "Uploaded artifact {artifact_uid} from {filepath}"), None => write!(f, "Uploaded artifact {artifact_uid}"), }, UploadArtifactResult::Error(error) => write!(f, "Upload artifact error: {error}"), UploadArtifactResult::Cancelled => write!(f, "Upload artifact cancelled"), } } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct DocumentContext { pub document_id: AIDocumentId, pub document_version: AIDocumentVersion, pub content: String, pub line_ranges: Vec>, } impl Display for DocumentContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.line_ranges.is_empty() { return write!(f, "Document {}", self.document_id); } let line_ranges = self .line_ranges .iter() .map(|range| format!("{}-{}", range.start, range.end)) .collect_vec(); write!( f, "Document {} ({})", self.document_id, line_ranges.join(", ") ) } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum ReadDocumentsResult { Success { documents: Vec }, Error(String), Cancelled, } impl Display for ReadDocumentsResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ReadDocumentsResult::Success { documents } => { write!(f, "Read documents: {}", documents.iter().format(", ")) } ReadDocumentsResult::Error(error) => write!(f, "Read documents error: {error}"), ReadDocumentsResult::Cancelled => write!(f, "Read documents cancelled"), } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum EditDocumentsResult { Success { updated_documents: Vec, }, Error(String), Cancelled, } impl Display for EditDocumentsResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { EditDocumentsResult::Success { updated_documents } => { write!( f, "Edited documents: {}", updated_documents.iter().format(", ") ) } EditDocumentsResult::Error(error) => write!(f, "Edit documents error: {error}"), EditDocumentsResult::Cancelled => write!(f, "Edit documents cancelled"), } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum CreateDocumentsResult { Success { created_documents: Vec, }, Error(String), Cancelled, } impl Display for CreateDocumentsResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CreateDocumentsResult::Success { created_documents } => { write!( f, "Created documents: {}", created_documents.iter().format(", ") ) } CreateDocumentsResult::Error(error) => write!(f, "Create documents error: {error}"), CreateDocumentsResult::Cancelled => write!(f, "Create documents cancelled"), } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum ReadShellCommandOutputResult { CommandFinished { command: String, block_id: BlockId, output: String, exit_code: ExitCode, start_ts: Option>, completed_ts: Option>, }, LongRunningCommandSnapshot { command: String, block_id: BlockId, grid_contents: String, cursor: String, is_alt_screen_active: bool, is_preempted: bool, }, Cancelled, Error(ShellCommandError), } impl Display for ReadShellCommandOutputResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ReadShellCommandOutputResult::CommandFinished { output, exit_code, .. } => { write!( f, "Shell command output finished with exit code{}:\n{output}", exit_code.value() ) } ReadShellCommandOutputResult::LongRunningCommandSnapshot { .. } => { write!(f, "Sent snapshot of long-running shell command to agent") } ReadShellCommandOutputResult::Cancelled => { write!(f, "Read shell command output cancelled") } ReadShellCommandOutputResult::Error(e) => { write!(f, "Read shell command output failed: {e:?}") } } } } #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum SearchCodebaseFailureReason { CodebaseNotIndexed, InvalidFilePaths, GetRelevantFilesError, ClientError, MissingCurrentWorkingDirectory, } #[derive(Debug, Clone, Eq, PartialEq)] pub enum SearchCodebaseResult { Success { files: Vec, }, Failed { reason: SearchCodebaseFailureReason, /// The message to be sent back to the LLM to inform it why the search failed. message: String, }, Cancelled, } impl Display for SearchCodebaseResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SearchCodebaseResult::Success { files } => { write!(f, "Codebase search found: {}", files.iter().format(", ")) } SearchCodebaseResult::Failed { reason, message } => { write!(f, "Codebase search failed ({reason:?}): {message}") } SearchCodebaseResult::Cancelled => write!(f, "Codebase search cancelled"), } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum RequestFileEditsResult { Success { diff: String, updated_files: Vec, deleted_files: Vec, lines_added: usize, lines_removed: usize, }, Cancelled, /// Diff application failed. DiffApplicationFailed { error: String, }, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct UpdatedFileContext { pub was_edited_by_user: bool, pub file_context: FileContext, } impl Display for UpdatedFileContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "user_edited {}, file {}", self.was_edited_by_user, self.file_context ) } } impl From for Vec { fn from(value: UpdatedFileContext) -> Self { // Note: This method only makes sense for FileContexts that have a string content. // TODO: How do we gracefully fail binary files here? let file_content: Vec = value.file_context.into(); file_content .into_iter() .map(|content| UpdatedFileContent { was_edited_by_user: value.was_edited_by_user, file: Some(content), }) .collect() } } impl Display for RequestFileEditsResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RequestFileEditsResult::Success { diff, updated_files, .. } => { write!( f, "File edits completed:\n\tDiff:\n{diff}\n\tUpdatedFiles: [{}]", updated_files.iter().format(", ") ) } RequestFileEditsResult::Cancelled => write!(f, "File edits cancelled"), RequestFileEditsResult::DiffApplicationFailed { error } => { write!(f, "File edits failed: {error}") } } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum SuggestPromptResult { Accepted { query: String }, Cancelled, } impl Display for SuggestPromptResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SuggestPromptResult::Accepted { query } => { write!(f, "Suggest prompt accepted: {query}") } SuggestPromptResult::Cancelled => write!(f, "Suggest prompt cancelled"), } } } impl AIAgentActionResultType { /// A user visible description of what the result contains. /// Used to display error messages when the content is too large for /// the LLM context window. pub fn description(&self) -> &str { match self { AIAgentActionResultType::RequestCommandOutput(_) => { "The output of your last command executed by Agent Mode" } AIAgentActionResultType::WriteToLongRunningShellCommand(_) => { "A snapshot of the command currently being executed by Agent Mode" } AIAgentActionResultType::RequestFileEdits(_) => { "The diff from editing the last file in Agent Mode" } AIAgentActionResultType::ReadFiles(_) => "The requested file content", AIAgentActionResultType::UploadArtifact(_) => "The uploaded artifact metadata", AIAgentActionResultType::SearchCodebase(_) => "The codebase search results", AIAgentActionResultType::Grep(_) => "The results of the grep operation", AIAgentActionResultType::FileGlob(_) => "The results of the file glob operation", AIAgentActionResultType::FileGlobV2(_) => "The results of the file glob operation", AIAgentActionResultType::CallMCPTool(_) => "The MCP tool call", AIAgentActionResultType::ReadSkill(_) => "The results of reading a skill from file", AIAgentActionResultType::ReadMCPResource(_) => "The MCP resource", AIAgentActionResultType::SuggestNewConversation(_) => { "Your decision on whether to start a new conversation" } AIAgentActionResultType::SuggestPrompt(_) => "The suggested prompt", AIAgentActionResultType::OpenCodeReview => "Open code review", AIAgentActionResultType::InsertReviewComments(_) => "Insert code review comments", AIAgentActionResultType::InitProject => "Initialize project", AIAgentActionResultType::ReadDocuments(_) => "The requested document content", AIAgentActionResultType::EditDocuments(_) => "The edited document content", AIAgentActionResultType::CreateDocuments(_) => "The newly created documents", AIAgentActionResultType::ReadShellCommandOutput(_) => "The shell command output", AIAgentActionResultType::UseComputer(_) => "The computer use result", AIAgentActionResultType::RequestComputerUse(_) => "The computer use request result", AIAgentActionResultType::FetchConversation(_) => "The fetched conversation tasks", AIAgentActionResultType::StartAgent(_) => "The result of starting a child agent", AIAgentActionResultType::SendMessageToAgent(_) => "The result of sending a message", AIAgentActionResultType::TransferShellCommandControlToUser(_) => { "The result of transferring shell command control to user" } AIAgentActionResultType::AskUserQuestion(_) => { "The user's answers to clarifying questions" } AIAgentActionResultType::RunAgents(_) => { "The result of an orchestrate batch of child agents" } AIAgentActionResultType::WaitForEvents(_) => { "The local watchdog timed out while waiting for inbound events" } } } pub fn is_successful(&self) -> bool { match self { Self::RequestCommandOutput(r) => r.is_successful(), Self::RequestFileEdits(RequestFileEditsResult::Success { .. }) | Self::ReadFiles(ReadFilesResult::Success { .. }) | Self::UploadArtifact(UploadArtifactResult::Success { .. }) | Self::SearchCodebase(SearchCodebaseResult::Success { .. }) | Self::Grep(GrepResult::Success { .. }) | Self::FileGlob(FileGlobResult::Success { .. }) | Self::FileGlobV2(FileGlobV2Result::Success { .. }) | Self::ReadMCPResource(ReadMCPResourceResult::Success { .. }) | Self::CallMCPTool(CallMCPToolResult::Success { .. }) | Self::SuggestNewConversation(SuggestNewConversationResult::Accepted { .. }) | Self::SuggestPrompt(SuggestPromptResult::Accepted { .. }) | Self::ReadDocuments(ReadDocumentsResult::Success { .. }) | Self::EditDocuments(EditDocumentsResult::Success { .. }) | Self::CreateDocuments(CreateDocumentsResult::Success { .. }) | Self::ReadShellCommandOutput( ReadShellCommandOutputResult::CommandFinished { .. } | ReadShellCommandOutputResult::LongRunningCommandSnapshot { .. }, ) | Self::UseComputer(UseComputerResult::Success(_)) | Self::InsertReviewComments(InsertReviewCommentsResult::Success { .. }) | Self::RequestComputerUse(RequestComputerUseResult::Approved { .. }) | Self::OpenCodeReview | Self::ReadSkill(ReadSkillResult::Success { .. }) | Self::FetchConversation(FetchConversationResult::Success { .. }) | Self::StartAgent(StartAgentResult::Success { .. }) | Self::SendMessageToAgent(SendMessageToAgentResult::Success { .. }) | Self::TransferShellCommandControlToUser( TransferShellCommandControlToUserResult::Snapshot { .. } | TransferShellCommandControlToUserResult::CommandFinished { .. }, ) => true, Self::AskUserQuestion(AskUserQuestionResult::Success { .. }) => true, Self::RunAgents(RunAgentsResult::Launched { .. }) => true, Self::WaitForEvents(WaitForEventsResult::Completed) => true, _ => false, } } pub fn is_failed(&self) -> bool { match self { Self::RequestCommandOutput(r) => r.failed(), Self::RequestFileEdits(RequestFileEditsResult::DiffApplicationFailed { .. }) | Self::ReadFiles(ReadFilesResult::Error(_)) | Self::UploadArtifact(UploadArtifactResult::Error(_)) | Self::SearchCodebase(SearchCodebaseResult::Failed { .. }) | Self::Grep(GrepResult::Error(_)) | Self::FileGlob(FileGlobResult::Error(_)) | Self::FileGlobV2(FileGlobV2Result::Error(_)) | Self::ReadMCPResource(ReadMCPResourceResult::Error(_)) | Self::CallMCPTool(CallMCPToolResult::Error(_)) | Self::ReadDocuments(ReadDocumentsResult::Error(_)) | Self::EditDocuments(EditDocumentsResult::Error(_)) | Self::CreateDocuments(CreateDocumentsResult::Error(_)) | Self::UseComputer(UseComputerResult::Error(_)) | Self::InsertReviewComments(InsertReviewCommentsResult::Error { .. }) | Self::RequestComputerUse(RequestComputerUseResult::Error(_)) | Self::FetchConversation(FetchConversationResult::Error(_)) | Self::StartAgent(StartAgentResult::Error { .. }) | Self::SendMessageToAgent(SendMessageToAgentResult::Error(_)) | Self::AskUserQuestion(AskUserQuestionResult::Error(_)) | Self::TransferShellCommandControlToUser( TransferShellCommandControlToUserResult::Error(_), ) | Self::RunAgents(RunAgentsResult::Failure { .. } | RunAgentsResult::Denied { .. }) => { true } _ => false, } } /// Stable identity for loop detection: tool type + input, no variable output. pub fn loop_description(&self) -> String { match self { Self::RequestCommandOutput(r) => { format!("run_shell_command: {}", r.loop_description()) } Self::ReadFiles(ReadFilesResult::Success { files }) => { format!( "read_files: [{}]", files .iter() .map(|f| f.file_name.as_str()) .collect::>() .join(", ") ) } Self::ReadFiles(ReadFilesResult::Error(error)) => { format!("read_files: error={error}") } Self::ReadFiles(ReadFilesResult::Cancelled) => "read_files: cancelled".to_string(), Self::RequestFileEdits(RequestFileEditsResult::Success { updated_files, deleted_files, .. }) => { let mut names: Vec<&str> = updated_files .iter() .map(|f| f.file_context.file_name.as_str()) .collect(); names.extend(deleted_files.iter().map(|s| s.as_str())); format!("apply_file_diffs: [{}]", names.join(", ")) } Self::RequestFileEdits(RequestFileEditsResult::DiffApplicationFailed { error }) => { format!("apply_file_diffs: failed={error}") } Self::RequestFileEdits(RequestFileEditsResult::Cancelled) => { "apply_file_diffs: cancelled".to_string() } Self::Grep(GrepResult::Success { matched_files }) => { format!( "grep: [{}]", matched_files .iter() .map(|f| f.file_path.as_str()) .collect::>() .join(", ") ) } Self::Grep(GrepResult::Error(error)) => format!("grep: error={error}"), Self::Grep(GrepResult::Cancelled) => "grep: cancelled".to_string(), Self::FileGlobV2(FileGlobV2Result::Success { matched_files, .. }) => { format!( "file_glob: [{}]", matched_files .iter() .map(|f| f.file_path.as_str()) .collect::>() .join(", ") ) } Self::FileGlobV2(FileGlobV2Result::Error(error)) => format!("file_glob: error={error}"), Self::FileGlobV2(FileGlobV2Result::Cancelled) => "file_glob: cancelled".to_string(), Self::SearchCodebase(SearchCodebaseResult::Success { files }) => { format!( "search_codebase: [{}]", files .iter() .map(|f| f.file_name.as_str()) .collect::>() .join(", ") ) } Self::SearchCodebase(SearchCodebaseResult::Failed { reason, message }) => { format!("search_codebase: failed={reason:?} {message}") } Self::SearchCodebase(SearchCodebaseResult::Cancelled) => { "search_codebase: cancelled".to_string() } other => format!("{other}"), } } pub fn is_cancelled(&self) -> bool { match self { Self::RequestCommandOutput(RequestCommandOutputResult::CancelledBeforeExecution) => { true } Self::RequestCommandOutput(RequestCommandOutputResult::Completed { exit_code, .. }) if exit_code.value() == 130 => true, Self::RequestFileEdits(RequestFileEditsResult::Cancelled) | Self::ReadFiles(ReadFilesResult::Cancelled) | Self::UploadArtifact(UploadArtifactResult::Cancelled) | Self::SearchCodebase(SearchCodebaseResult::Cancelled) | Self::Grep(GrepResult::Cancelled) | Self::FileGlob(FileGlobResult::Cancelled) | Self::FileGlobV2(FileGlobV2Result::Cancelled) | Self::ReadMCPResource(ReadMCPResourceResult::Cancelled) | Self::CallMCPTool(CallMCPToolResult::Cancelled) | Self::SuggestNewConversation(SuggestNewConversationResult::Cancelled) | Self::SuggestPrompt(SuggestPromptResult::Cancelled) | Self::ReadDocuments(ReadDocumentsResult::Cancelled) | Self::EditDocuments(EditDocumentsResult::Cancelled) | Self::CreateDocuments(CreateDocumentsResult::Cancelled) | Self::ReadShellCommandOutput(ReadShellCommandOutputResult::Cancelled) | Self::UseComputer(UseComputerResult::Cancelled) | Self::InsertReviewComments(InsertReviewCommentsResult::Cancelled) | Self::RequestComputerUse(RequestComputerUseResult::Cancelled) | Self::TransferShellCommandControlToUser( TransferShellCommandControlToUserResult::Cancelled, ) | Self::WriteToLongRunningShellCommand( WriteToLongRunningShellCommandResult::Cancelled, ) | Self::ReadSkill(ReadSkillResult::Cancelled) | Self::FetchConversation(FetchConversationResult::Cancelled) | Self::StartAgent(StartAgentResult::Cancelled { .. }) | Self::SendMessageToAgent(SendMessageToAgentResult::Cancelled) // SkippedByAutoApprove is intentionally excluded: the agent should continue. | Self::AskUserQuestion(AskUserQuestionResult::Cancelled) | Self::RunAgents(RunAgentsResult::Cancelled) | Self::WaitForEvents(WaitForEventsResult::Cancelled) => true, _ => false, } } pub fn is_cancelled_during_requested_command_execution(&self) -> bool { matches!(self, Self::RequestCommandOutput(RequestCommandOutputResult::Completed { exit_code, .. }) if exit_code.value() == 130) } /// Returns `true` if this completion of this action result should trigger a follow-up request. pub fn should_trigger_request_upon_completion(&self) -> bool { !self.is_cancelled() } pub fn is_requested_command(&self) -> bool { matches!(self, AIAgentActionResultType::RequestCommandOutput(_)) } pub fn is_call_mcp_tool(&self) -> bool { matches!(self, AIAgentActionResultType::CallMCPTool(_)) } /// Returns `true` if this result will cause the server to route the next /// turn to a subagent (e.g. the CLI subagent) rather than the orchestrator. /// LRC snapshot variants are the current indicators of this. pub fn triggers_server_subagent(&self) -> bool { matches!( self, Self::RequestCommandOutput( RequestCommandOutputResult::LongRunningCommandSnapshot { .. } ) | Self::WriteToLongRunningShellCommand( WriteToLongRunningShellCommandResult::Snapshot { .. } ) | Self::ReadShellCommandOutput( ReadShellCommandOutputResult::LongRunningCommandSnapshot { .. } ) | Self::TransferShellCommandControlToUser( TransferShellCommandControlToUserResult::Snapshot { .. } ) ) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum GrepResult { Success { matched_files: Vec }, Error(String), Cancelled, } impl Display for GrepResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { GrepResult::Success { matched_files } => { write!( f, "Grep found matches in: [{}]", matched_files.iter().format(", ") ) } GrepResult::Error(error) => write!(f, "Grep error: {error}"), GrepResult::Cancelled => write!(f, "Grep cancelled"), } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct GrepFileMatch { /// The absolute path to the file that was matched. pub file_path: String, /// The lines that matched the query. pub matched_lines: Vec, } impl Display for GrepFileMatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{} on lines [{}]", self.file_path, self.matched_lines.iter().format(", ") ) } } /// Info about a line that matched the grep query. This only contains the line /// number for now, but can be extended in the future to include more info, e.g. /// line contents. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct GrepLineMatch { /// The line number of the line that matched the query. pub line_number: usize, } impl Display for GrepLineMatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.line_number) } } /// The result of a file globbing operation #[derive(Debug, Clone, PartialEq, Eq)] pub enum FileGlobResult { Success { matched_files: String }, Error(String), Cancelled, } impl Display for FileGlobResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FileGlobResult::Success { matched_files } => { write!(f, "File glob completed: {matched_files}") } FileGlobResult::Error(error) => write!(f, "File glob error: {error}"), FileGlobResult::Cancelled => write!(f, "File glob cancelled"), } } } // The result of a v2 file globbing operation. #[derive(Debug, Clone, PartialEq, Eq)] pub enum FileGlobV2Result { Success { matched_files: Vec, warnings: Option, }, Error(String), Cancelled, } impl Display for FileGlobV2Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FileGlobV2Result::Success { matched_files, warnings, } => { write!( f, "File glob V2 completed: [{}] warnings: {:?}", matched_files.iter().format(", "), warnings ) } FileGlobV2Result::Error(error) => write!(f, "File glob V2 error: {error}"), FileGlobV2Result::Cancelled => write!(f, "File glob V2 cancelled"), } } } // A match of a single file. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct FileGlobV2Match { pub file_path: String, } impl Display for FileGlobV2Match { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.file_path) } } #[derive(Debug, Clone, PartialEq)] pub enum CallMCPToolResult { Success { result: rmcp::model::CallToolResult }, Error(String), Cancelled, } impl Display for CallMCPToolResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CallMCPToolResult::Success { result } => { write!( f, "MCP tool call completed: [{result:?}]", // results.iter().format(", ") ) } CallMCPToolResult::Error(error) => write!(f, "MCP tool call error: {error}"), CallMCPToolResult::Cancelled => write!(f, "MCP tool call cancelled"), } } } #[derive(Debug, Clone, PartialEq)] pub enum ReadMCPResourceResult { Success { resource_contents: Vec, }, Error(String), Cancelled, } impl Display for ReadMCPResourceResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ReadMCPResourceResult::Success { resource_contents } => { write!(f, "MCP resource read completed: [{resource_contents:?}]",) } ReadMCPResourceResult::Error(error) => write!(f, "MCP resource error: {error}"), ReadMCPResourceResult::Cancelled => write!(f, "MCP resource read cancelled"), } } } #[derive(Debug, Clone, PartialEq)] pub enum ReadSkillResult { Success { content: FileContext }, Error(String), Cancelled, } impl Display for ReadSkillResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ReadSkillResult::Success { content } => { write!(f, "Skill read successfully: {}", content.file_name) } ReadSkillResult::Error(error) => write!(f, "Skill read error: {error}"), ReadSkillResult::Cancelled => write!(f, "Skill read cancelled"), } } } #[derive(Debug, Clone, PartialEq)] pub enum UseComputerResult { /// Computer use succeeded, with one result per requested action. Success(computer_use::ActionResult), Error(String), Cancelled, } impl Display for UseComputerResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { UseComputerResult::Success(_) => write!(f, "Use computer completed"), UseComputerResult::Error(error) => write!(f, "Use computer error: {error}"), UseComputerResult::Cancelled => write!(f, "Use computer cancelled"), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum InsertReviewCommentsResult { Success { repo_path: String }, Error { repo_path: String, message: String }, Cancelled, } impl Display for InsertReviewCommentsResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { InsertReviewCommentsResult::Success { repo_path } => { write!(f, "Inserted code review comments for {repo_path}") } InsertReviewCommentsResult::Error { repo_path, message } => { write!( f, "Error inserting code review comments for {repo_path}: {message}" ) } InsertReviewCommentsResult::Cancelled => { write!(f, "Cancelled inserting code review comments") } } } } /// Screen dimensions for computer use. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ScreenDimensions { pub width_px: i32, pub height_px: i32, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum RequestComputerUseResult { /// Request was accepted, with the screen dimensions. Approved { screenshot: computer_use::Screenshot, platform: computer_use::Platform, /// The on-screen windows the agent may target. windows: Vec, }, /// Request errored. Error(String), /// Request was cancelled or rejected by the user. Cancelled, } impl Display for RequestComputerUseResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RequestComputerUseResult::Approved { screenshot, .. } => { write!( f, "Request computer use accepted ({}x{})", screenshot.original_width, screenshot.original_height ) } RequestComputerUseResult::Error(error) => { write!(f, "Request computer use error: {error}") } RequestComputerUseResult::Cancelled => write!(f, "Request computer use cancelled"), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum FetchConversationResult { Success { directory_path: String }, Error(String), Cancelled, } impl Display for FetchConversationResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FetchConversationResult::Success { directory_path } => { write!(f, "Fetched conversation to {directory_path}") } FetchConversationResult::Error(error) => { write!(f, "Fetch conversation error: {error}") } FetchConversationResult::Cancelled => write!(f, "Fetch conversation cancelled"), } } } // TODO(QUALITY-788): Delete legacy start_agent/start_agent_v2 result support once // old preview orchestration history no longer needs parse/display/result compatibility. // Linear issue: QUALITY-788. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum StartAgentResult { Success { agent_id: String, #[serde(default)] version: StartAgentVersion, }, Error { error: String, #[serde(default)] version: StartAgentVersion, }, Cancelled { #[serde(default)] version: StartAgentVersion, }, } impl StartAgentResult { /// Returns which start-agent tool schema version produced this result. pub fn version(&self) -> StartAgentVersion { match self { StartAgentResult::Success { version, .. } | StartAgentResult::Error { version, .. } | StartAgentResult::Cancelled { version } => *version, } } } impl Display for StartAgentResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { StartAgentResult::Success { agent_id, .. } => { write!(f, "Started agent with id {agent_id}") } StartAgentResult::Error { error, .. } => write!(f, "Start agent error: {error}"), StartAgentResult::Cancelled { .. } => write!(f, "Start agent cancelled"), } } } /// The terminal outcome of an orchestrate tool call. /// /// Mirrors the proto `RunAgentsResult` oneof, with an additional /// `Cancelled` variant used internally by the action machinery when the /// user clicks Reject. The proto wire form for cancellation is the /// generic `ToolCallResult.Cancel` marker; the conversion code emits /// `ConvertToAPITypeError::Ignore` for `Cancelled` so the input /// interceptor can synthesize the marker on the next outbound input. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum RunAgentsResult { /// Orchestration launched. Carries the resolved configuration and one /// `AgentOutcome` per `agent_run_configs[]` entry, in input order. Launched { model_id: String, harness_type: String, execution_mode: RunAgentsLaunchedExecutionMode, agents: Vec, }, /// Declined for a non-error reason (currently disapproval). Denied { reason: String }, /// Actual error path: server-side validation rejected the call, or the /// client could not begin the launch sequence at all. Failure { error: String }, /// User rejected via the Reject button. Wire form is the generic /// `ToolCallResult.Cancel` marker, synthesized by the server's input /// interceptor on the next user input. Cancelled, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum RunAgentsLaunchedExecutionMode { Local, Remote { environment_id: String, worker_host: String, computer_use_enabled: bool, }, } /// Per-agent outcome reported in `RunAgentsResult::Launched.agents`. /// Order mirrors the input order of `RunAgents.agent_run_configs[]`, /// regardless of which `CreateAgentTask` call returned first. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct RunAgentsAgentOutcome { pub name: String, pub kind: RunAgentsAgentOutcomeKind, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum RunAgentsAgentOutcomeKind { Launched { agent_id: String }, Failed { error: String }, } impl RunAgentsResult { fn model_content(&self) -> String { let value = match self { Self::Launched { model_id, harness_type, execution_mode, agents, } => { let execution_mode = match execution_mode { RunAgentsLaunchedExecutionMode::Local => serde_json::json!({ "type": "local", }), RunAgentsLaunchedExecutionMode::Remote { environment_id, worker_host, computer_use_enabled, } => serde_json::json!({ "type": "remote", "environment_id": environment_id, "worker_host": worker_host, "computer_use_enabled": computer_use_enabled, }), }; let agents = agents .iter() .map(|agent| match &agent.kind { RunAgentsAgentOutcomeKind::Launched { agent_id } => serde_json::json!({ "name": agent.name, "status": "launched", "agent_id": agent_id, }), RunAgentsAgentOutcomeKind::Failed { error } => serde_json::json!({ "name": agent.name, "status": "failed", "error": error, }), }) .collect::>(); serde_json::json!({ "status": "launched", "model_id": model_id, "harness_type": harness_type, "execution_mode": execution_mode, "agents": agents, }) } Self::Denied { reason } => serde_json::json!({ "status": "denied", "reason": reason, }), Self::Failure { error } => serde_json::json!({ "status": "failure", "error": error, }), Self::Cancelled => serde_json::json!({ "status": "cancelled", }), }; value.to_string() } } impl Display for RunAgentsResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RunAgentsResult::Launched { agents, .. } => { let launched = agents .iter() .filter(|a| matches!(a.kind, RunAgentsAgentOutcomeKind::Launched { .. })) .count(); write!( f, "Orchestrate launched ({launched}/{} agents started)", agents.len() ) } RunAgentsResult::Denied { reason } => { write!(f, "Orchestrate launch denied: {reason}") } RunAgentsResult::Failure { error } => write!(f, "Orchestrate failure: {error}"), RunAgentsResult::Cancelled => write!(f, "Orchestrate cancelled"), } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum SendMessageToAgentResult { Success { message_id: String }, Error(String), Cancelled, } impl Display for SendMessageToAgentResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SendMessageToAgentResult::Success { message_id } => { write!(f, "Sent message with id {message_id}") } SendMessageToAgentResult::Error(error) => write!(f, "Send message error: {error}"), SendMessageToAgentResult::Cancelled => write!(f, "Send message cancelled"), } } } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub enum TransferShellCommandControlToUserResult { Snapshot { block_id: BlockId, grid_contents: String, cursor: String, is_alt_screen_active: bool, is_preempted: bool, }, CommandFinished { block_id: BlockId, output: String, exit_code: ExitCode, start_ts: Option>, completed_ts: Option>, }, Cancelled, Error(ShellCommandError), } impl Display for TransferShellCommandControlToUserResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Snapshot { .. } => { write!(f, "Transferred control to user, sent snapshot") } Self::CommandFinished { output, exit_code, .. } => write!( f, "Command finished while user had control, exit code {}:\n{output}", exit_code.value() ), Self::Cancelled => write!(f, "Transfer shell command control to user cancelled"), Self::Error(e) => write!(f, "Transfer shell command control to user failed: {e:?}"), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum AskUserQuestionAnswerItem { Answered { question_id: String, selected_options: Vec, other_text: String, }, Skipped { question_id: String, }, } impl AskUserQuestionAnswerItem { pub fn is_skipped(&self) -> bool { matches!(self, Self::Skipped { .. }) } pub fn display_text(&self) -> String { match self { Self::Answered { selected_options, other_text, .. } => { let mut parts = selected_options.clone(); if !other_text.is_empty() { parts.push(format!("Other: {other_text}")); } parts.join(", ") } Self::Skipped { .. } => "Skipped".to_string(), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum AskUserQuestionResult { Success { answers: Vec, }, Error(String), Cancelled, /// The question was skipped automatically because the conversation is in auto-approve mode. SkippedByAutoApprove { question_ids: Vec, }, } impl Display for AskUserQuestionResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AskUserQuestionResult::Success { answers } => { write!( f, "Ask user question completed with {} answer(s)", answers.len() ) } AskUserQuestionResult::Error(msg) => write!(f, "Ask user question error: {msg}"), AskUserQuestionResult::Cancelled => write!(f, "Ask user question cancelled"), AskUserQuestionResult::SkippedByAutoApprove { question_ids } => { write!( f, "Ask user question skipped (auto-approve) with {} skipped question(s)", question_ids.len() ) } } } } /// Result of a client-side wait_for_events action. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum WaitForEventsResult { /// Watchdog fired or an inbound resume signal closed the wait. The /// agent's next turn observes an empty WaitForEvents result on the /// wire and decides how to proceed. Completed, /// User cancelled the conversation while waiting. Mirrors /// RunAgents::Cancelled: no tool-call result is sent on the wire. Cancelled, } impl Display for WaitForEventsResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Completed => write!(f, "Wait for events completed"), Self::Cancelled => write!(f, "Wait for events cancelled"), } } }