1use serde::{Deserialize, Serialize};
2
3pub use llm_trait::request::ResponseFormat;
5
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
7pub enum Language {
8 #[default]
9 En,
10 Zh,
11}
12
13impl std::fmt::Display for Language {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 match self {
16 Language::En => write!(f, "en"),
17 Language::Zh => write!(f, "zh"),
18 }
19 }
20}
21
22#[derive(Clone, Debug)]
23pub struct RetryConfig {
24 pub max_retries: u32,
25 pub initial_backoff_ms: u64,
26 pub max_backoff_ms: u64,
27 pub backoff_multiplier: f64,
28 pub jitter: bool,
29}
30
31impl Default for RetryConfig {
32 fn default() -> Self {
33 Self {
34 max_retries: 3,
35 initial_backoff_ms: 500,
36 max_backoff_ms: 10_000,
37 backoff_multiplier: 2.0,
38 jitter: true,
39 }
40 }
41}
42
43impl RetryConfig {
44 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn max_retries(mut self, n: u32) -> Self {
49 self.max_retries = n;
50 self
51 }
52
53 pub fn initial_backoff_ms(mut self, ms: u64) -> Self {
54 self.initial_backoff_ms = ms;
55 self
56 }
57
58 pub fn max_backoff_ms(mut self, ms: u64) -> Self {
59 self.max_backoff_ms = ms;
60 self
61 }
62
63 pub fn no_jitter(mut self) -> Self {
64 self.jitter = false;
65 self
66 }
67}
68
69use crate::llm::ReasoningConfig;
70
71#[derive(Clone, Debug)]
76pub struct SafetyConfig {
77 pub max_tool_calls_per_turn: usize,
81
82 pub max_consecutive_failures: usize,
85}
86
87impl Default for SafetyConfig {
88 fn default() -> Self {
89 Self {
90 max_tool_calls_per_turn: 128,
91 max_consecutive_failures: 3,
92 }
93 }
94}
95
96#[derive(Clone, Debug, Default)]
97pub struct AgentConfig {
98 pub system_prompt: Option<String>,
99 pub enable_thought: bool,
111 pub reasoning: Option<ReasoningConfig>,
117 pub language: Language,
118 pub execution: ExecutionConfig,
119 pub llm: LlmConfig,
120 pub tool: ToolConfig,
121 pub session: SessionConfig,
122 pub safety: SafetyConfig,
123}
124
125impl AgentConfig {
126 pub fn validate(&self) -> crate::types::AgentResult<()> {
128 use crate::types::AgentError;
129
130 if let Some(max_turns) = self.execution.max_turns
131 && max_turns == 0
132 {
133 return Err(AgentError::config_error(
134 "execution.max_turns must be > 0".to_string(),
135 ));
136 }
137
138 if let Some(max_sessions) = self.session.max_sessions
139 && max_sessions == 0
140 {
141 return Err(AgentError::config_error(
142 "session.max_sessions must be > 0".to_string(),
143 ));
144 }
145
146 if let Some(tool_timeout_ms) = self.tool.tool_timeout_ms
147 && tool_timeout_ms == 0
148 {
149 return Err(AgentError::config_error(
150 "tool.tool_timeout_ms must be > 0".to_string(),
151 ));
152 }
153
154 if self.safety.max_tool_calls_per_turn == 0 {
155 return Err(AgentError::config_error(
156 "safety.max_tool_calls_per_turn must be > 0".to_string(),
157 ));
158 }
159
160 Ok(())
161 }
162}
163
164#[derive(Clone, Debug, Default)]
165pub struct ExecutionConfig {
166 pub max_turns: Option<u32>,
167 pub approval_timeout_ms: Option<u64>,
168 pub fail_on_persist_error: bool,
169}
170
171#[derive(Clone, Debug, Default)]
172pub struct LlmConfig {
173 pub response_format: Option<ResponseFormat>,
174 pub llm_retry: Option<RetryConfig>,
175}
176
177pub const DEFAULT_TOOL_TIMEOUT_MS: u64 = 600_000;
181
182#[derive(Clone, Debug)]
183pub struct ToolConfig {
184 pub default_tool_timeout_ms: u64,
187 pub tool_timeout_ms: Option<u64>,
188 pub max_tool_output_chars: Option<usize>,
189 pub tool_error_retry_prompt: Option<String>,
190}
191
192impl Default for ToolConfig {
193 fn default() -> Self {
194 Self {
195 default_tool_timeout_ms: DEFAULT_TOOL_TIMEOUT_MS,
196 tool_timeout_ms: None,
197 max_tool_output_chars: None,
198 tool_error_retry_prompt: None,
199 }
200 }
201}
202
203#[derive(Clone, Debug, Default)]
204pub struct SessionConfig {
205 pub max_sessions: Option<usize>,
208
209 pub max_turns_per_session: Option<usize>,
212
213 pub max_message_tokens: Option<usize>,
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use crate::types::AgentError;
223
224 #[test]
225 fn language_display_and_default() {
226 assert_eq!(Language::default(), Language::En);
227 assert_eq!(Language::En.to_string(), "en");
228 assert_eq!(Language::Zh.to_string(), "zh");
229 }
230
231 #[test]
232 fn retry_config_defaults_and_builders() {
233 let cfg = RetryConfig::default();
234 assert_eq!(cfg.max_retries, 3);
235 assert_eq!(cfg.initial_backoff_ms, 500);
236 assert_eq!(cfg.max_backoff_ms, 10_000);
237 assert_eq!(cfg.backoff_multiplier, 2.0);
238 assert!(cfg.jitter);
239
240 let cfg = RetryConfig::new()
241 .max_retries(7)
242 .initial_backoff_ms(100)
243 .max_backoff_ms(20_000)
244 .no_jitter();
245 assert_eq!(cfg.max_retries, 7);
246 assert_eq!(cfg.initial_backoff_ms, 100);
247 assert_eq!(cfg.max_backoff_ms, 20_000);
248 assert!(!cfg.jitter);
249 }
250
251 #[test]
252 fn response_format_to_api_value() {
253 let v = ResponseFormat::JsonObject.to_api_value();
254 assert_eq!(v["type"], "json_object");
255
256 let v = ResponseFormat::JsonSchema {
257 name: "event".to_string(),
258 schema: serde_json::json!({"type": "object"}),
259 }
260 .to_api_value();
261 assert_eq!(v["type"], "json_schema");
262 assert_eq!(v["json_schema"]["name"], "event");
263 assert_eq!(v["json_schema"]["schema"]["type"], "object");
264 }
265
266 #[test]
267 fn safety_config_defaults() {
268 let cfg = SafetyConfig::default();
269 assert_eq!(cfg.max_tool_calls_per_turn, 128);
270 assert_eq!(cfg.max_consecutive_failures, 3);
271 }
272
273 #[test]
274 fn validate_accepts_default() {
275 assert!(AgentConfig::default().validate().is_ok());
276 }
277
278 #[test]
279 fn validate_rejects_zero_max_turns() {
280 let mut cfg = AgentConfig::default();
281 cfg.execution.max_turns = Some(0);
282 let err = cfg.validate().unwrap_err();
283 assert!(matches!(err, AgentError::ConfigError(_)));
284 assert!(err.to_string().contains("max_turns"));
285 }
286
287 #[test]
288 fn validate_rejects_zero_max_sessions() {
289 let mut cfg = AgentConfig::default();
290 cfg.session.max_sessions = Some(0);
291 assert!(matches!(cfg.validate(), Err(AgentError::ConfigError(_))));
292 }
293
294 #[test]
295 fn validate_rejects_zero_tool_timeout() {
296 let mut cfg = AgentConfig::default();
297 cfg.tool.tool_timeout_ms = Some(0);
298 assert!(matches!(cfg.validate(), Err(AgentError::ConfigError(_))));
299 }
300
301 #[test]
302 fn validate_rejects_zero_max_tool_calls() {
303 let mut cfg = AgentConfig::default();
304 cfg.safety.max_tool_calls_per_turn = 0;
305 assert!(matches!(cfg.validate(), Err(AgentError::ConfigError(_))));
306 }
307}