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