Skip to main content

codetether_agent/session/helper/
error.rs

1use super::text::role_label;
2use crate::provider::{ContentPart, Message};
3
4pub fn messages_to_rlm_context(messages: &[Message]) -> String {
5    let mut out = String::new();
6    for (idx, m) in messages.iter().enumerate() {
7        out.push_str(&format!("[{} {}]\n", idx, role_label(m.role)));
8
9        for part in &m.content {
10            match part {
11                ContentPart::Text { text } => {
12                    out.push_str(text);
13                    out.push('\n');
14                }
15                ContentPart::Thinking { text } => {
16                    if !text.trim().is_empty() {
17                        out.push_str("[Thinking]\n");
18                        out.push_str(text);
19                        out.push('\n');
20                    }
21                }
22                ContentPart::ToolCall {
23                    id,
24                    name,
25                    arguments,
26                    ..
27                } => {
28                    out.push_str(&format!(
29                        "[ToolCall id={id} name={name}]\nargs: {arguments}\n"
30                    ));
31                }
32                ContentPart::ToolResult {
33                    tool_call_id,
34                    content,
35                } => {
36                    out.push_str(&format!("[ToolResult id={tool_call_id}]\n"));
37                    out.push_str(content);
38                    out.push('\n');
39                }
40                ContentPart::Image { mime_type, url } => {
41                    out.push_str(&format!(
42                        "[Image mime_type={} url_len={}]\n",
43                        mime_type.clone().unwrap_or_else(|| "unknown".to_string()),
44                        url.len()
45                    ));
46                }
47                ContentPart::File { path, mime_type } => {
48                    out.push_str(&format!(
49                        "[File path={} mime_type={}]\n",
50                        path,
51                        mime_type.clone().unwrap_or_else(|| "unknown".to_string())
52                    ));
53                }
54            }
55        }
56
57        out.push_str("\n---\n\n");
58    }
59    out
60}
61
62pub fn is_prompt_too_long_error(err: &anyhow::Error) -> bool {
63    let msg = err.to_string().to_ascii_lowercase();
64    msg.contains("prompt is too long")
65        || msg.contains("context length")
66        || msg.contains("maximum context")
67        || (msg.contains("tokens") && msg.contains("maximum") && msg.contains("prompt"))
68}
69
70pub fn is_retryable_upstream_error(err: &anyhow::Error) -> bool {
71    let msg = err.to_string().to_ascii_lowercase();
72    [
73        " 500 ",
74        " 504 ",
75        " 502 ",
76        " 429 ",
77        "status code 500",
78        "status code 504",
79        "status code 502",
80        "status code 429",
81        "internal server error",
82        "gateway timeout",
83        "upstream request timeout",
84        "server error: 500",
85        "server error: 504",
86        "network error",
87        "connection reset",
88        "connection refused",
89        "timed out",
90        "timeout",
91        "broken pipe",
92        "unexpected eof",
93    ]
94    .iter()
95    .any(|needle| msg.contains(needle))
96}