Skip to main content

ai_agent/
tool_errors.rs

1//! Tool error formatting utilities.
2//!
3//! Re-exports and enhances utilities from utils/tool_errors.rs.
4
5pub use crate::utils::tool_errors::*;
6
7use crate::AgentError;
8
9/// Format an AgentError for display in tool result messages.
10/// Matches TypeScript's formatError from toolErrors.ts.
11pub fn format_tool_error(error: &AgentError) -> String {
12    match error {
13        AgentError::UserAborted => "User rejected tool use".to_string(),
14        AgentError::ApiConnectionTimeout(msg) => msg.clone(),
15        AgentError::StreamEndedWithoutEvents => "Stream ended without events".to_string(),
16        AgentError::Stream404CreationError(msg) => msg.clone(),
17        AgentError::Tool(msg) if msg.contains("exit") || msg.contains("Exit") => {
18            // Extract exit code, stderr, stdout pattern
19            // Format: "Exit code N: stderr\nstdout"
20            let parts: Vec<&str> = msg.splitn(2, ": ").collect();
21            if parts.len() == 2 {
22                let exit_info = parts[0];
23                let detail = parts[1];
24                let stderr_start = detail.find("stderr:");
25                let stdout_start = detail.find("stdout:");
26
27                let mut result = vec![exit_info.to_string()];
28                if let Some(pos) = stderr_start {
29                    let after_stderr = &detail[pos..];
30                    let stderr_end = after_stderr.find("stdout:").unwrap_or(after_stderr.len());
31                    let stderr_content = after_stderr[..stderr_end]
32                        .trim_start_matches("stderr:")
33                        .trim();
34                    if !stderr_content.is_empty() {
35                        result.push(stderr_content.to_string());
36                    }
37                }
38                if let Some(pos) = stdout_start {
39                    let stdout_content = detail[pos..].trim_start_matches("stdout:").trim();
40                    if !stdout_content.is_empty() {
41                        result.push(stdout_content.to_string());
42                    }
43                }
44                return result.join("\n");
45            }
46            msg.clone()
47        }
48        AgentError::Tool(msg) => msg.clone(),
49        AgentError::Internal(msg) if msg.contains("Interrupt") => msg.clone(),
50        _ => error.to_string(),
51    }
52    .chars()
53    .take(10_000)
54    .collect()
55}
56
57/// Format input validation error for tool call.
58pub fn format_input_validation_error(tool_name: &str, details: &str) -> String {
59    let issue_word = if details.contains('\n') {
60        "issues"
61    } else {
62        "issue"
63    };
64    format!(
65        "{} failed due to the following {}:\n{}",
66        tool_name, issue_word, details
67    )
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_format_tool_error_user_aborted() {
76        let err = AgentError::UserAborted;
77        let formatted = format_tool_error(&err);
78        assert_eq!(formatted, "User rejected tool use");
79    }
80
81    #[test]
82    fn test_format_tool_error_tool() {
83        let err = AgentError::Tool("ls: command not found".to_string());
84        let formatted = format_tool_error(&err);
85        assert_eq!(formatted, "ls: command not found");
86    }
87}