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
200#[derive(Clone, Debug, Default)]
201pub struct ToolConfig {
202 pub tool_timeout_ms: Option<u64>,
203 pub max_tool_output_chars: Option<usize>,
204 pub tool_error_retry_prompt: Option<String>,
205}
206
207#[derive(Clone, Debug, Default)]
208pub struct SessionConfig {
209 pub max_sessions: Option<usize>,
212
213 pub max_turns_per_session: Option<usize>,
216
217 pub max_message_tokens: Option<usize>,
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::types::AgentError;
227
228 #[test]
229 fn language_display_and_default() {
230 assert_eq!(Language::default(), Language::En);
231 assert_eq!(Language::En.to_string(), "en");
232 assert_eq!(Language::Zh.to_string(), "zh");
233 }
234
235 #[test]
236 fn retry_config_defaults_and_builders() {
237 let cfg = RetryConfig::default();
238 assert_eq!(cfg.max_retries, 3);
239 assert_eq!(cfg.initial_backoff_ms, 500);
240 assert_eq!(cfg.max_backoff_ms, 10_000);
241 assert_eq!(cfg.backoff_multiplier, 2.0);
242 assert!(cfg.jitter);
243
244 let cfg = RetryConfig::new()
245 .max_retries(7)
246 .initial_backoff_ms(100)
247 .max_backoff_ms(20_000)
248 .no_jitter();
249 assert_eq!(cfg.max_retries, 7);
250 assert_eq!(cfg.initial_backoff_ms, 100);
251 assert_eq!(cfg.max_backoff_ms, 20_000);
252 assert!(!cfg.jitter);
253 }
254
255 #[test]
256 fn response_format_to_api_value() {
257 let v = ResponseFormat::JsonObject.to_api_value();
258 assert_eq!(v["type"], "json_object");
259
260 let v = ResponseFormat::JsonSchema {
261 name: "event".to_string(),
262 schema: serde_json::json!({"type": "object"}),
263 }
264 .to_api_value();
265 assert_eq!(v["type"], "json_schema");
266 assert_eq!(v["json_schema"]["name"], "event");
267 assert_eq!(v["json_schema"]["schema"]["type"], "object");
268 }
269
270 #[test]
271 fn safety_config_defaults() {
272 let cfg = SafetyConfig::default();
273 assert_eq!(cfg.max_tool_calls_per_turn, 128);
274 assert_eq!(cfg.max_consecutive_failures, 3);
275 }
276
277 #[test]
278 fn validate_accepts_default() {
279 assert!(AgentConfig::default().validate().is_ok());
280 }
281
282 #[test]
283 fn validate_rejects_zero_max_turns() {
284 let mut cfg = AgentConfig::default();
285 cfg.execution.max_turns = Some(0);
286 let err = cfg.validate().unwrap_err();
287 assert!(matches!(err, AgentError::ConfigError(_)));
288 assert!(err.to_string().contains("max_turns"));
289 }
290
291 #[test]
292 fn validate_rejects_zero_max_sessions() {
293 let mut cfg = AgentConfig::default();
294 cfg.session.max_sessions = Some(0);
295 assert!(matches!(cfg.validate(), Err(AgentError::ConfigError(_))));
296 }
297
298 #[test]
299 fn validate_rejects_zero_tool_timeout() {
300 let mut cfg = AgentConfig::default();
301 cfg.tool.tool_timeout_ms = Some(0);
302 assert!(matches!(cfg.validate(), Err(AgentError::ConfigError(_))));
303 }
304
305 #[test]
306 fn validate_rejects_zero_max_tool_calls() {
307 let mut cfg = AgentConfig::default();
308 cfg.safety.max_tool_calls_per_turn = 0;
309 assert!(matches!(cfg.validate(), Err(AgentError::ConfigError(_))));
310 }
311}