Skip to main content

agent_base/types/
config.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
5pub enum Language {
6    En,
7    Zh,
8}
9
10impl Default for Language {
11    fn default() -> Self {
12        Language::En
13    }
14}
15
16impl std::fmt::Display for Language {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            Language::En => write!(f, "en"),
20            Language::Zh => write!(f, "zh"),
21        }
22    }
23}
24
25#[derive(Clone, Debug)]
26pub struct RetryConfig {
27    pub max_retries: u32,
28    pub initial_backoff_ms: u64,
29    pub max_backoff_ms: u64,
30    pub backoff_multiplier: f64,
31    pub jitter: bool,
32}
33
34impl Default for RetryConfig {
35    fn default() -> Self {
36        Self {
37            max_retries: 3,
38            initial_backoff_ms: 500,
39            max_backoff_ms: 10_000,
40            backoff_multiplier: 2.0,
41            jitter: true,
42        }
43    }
44}
45
46impl RetryConfig {
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    pub fn max_retries(mut self, n: u32) -> Self {
52        self.max_retries = n;
53        self
54    }
55
56    pub fn initial_backoff_ms(mut self, ms: u64) -> Self {
57        self.initial_backoff_ms = ms;
58        self
59    }
60
61    pub fn max_backoff_ms(mut self, ms: u64) -> Self {
62        self.max_backoff_ms = ms;
63        self
64    }
65
66    pub fn no_jitter(mut self) -> Self {
67        self.jitter = false;
68        self
69    }
70}
71
72#[derive(Clone, Debug)]
73pub enum ResponseFormat {
74    JsonObject,
75    JsonSchema { name: String, schema: Value },
76}
77
78impl ResponseFormat {
79    pub fn to_api_value(&self) -> Value {
80        match self {
81            ResponseFormat::JsonObject => {
82                serde_json::json!({ "type": "json_object" })
83            }
84            ResponseFormat::JsonSchema { name, schema } => {
85                serde_json::json!({
86                    "type": "json_schema",
87                    "json_schema": {
88                        "name": name,
89                        "schema": schema,
90                    }
91                })
92            }
93        }
94    }
95}
96
97use crate::llm::ReasoningConfig;
98
99/// Safety configuration for agent runtime guardrails.
100///
101/// These limits are hard constraints enforced by code, not prompt —
102/// the model cannot bypass them regardless of its capability.
103#[derive(Clone, Debug)]
104pub struct SafetyConfig {
105    /// Maximum number of tool calls allowed per turn.
106    /// When exceeded, tool calls are discarded and the LLM is forced to summarize.
107    /// Default: 128.
108    pub max_tool_calls_per_turn: usize,
109
110    /// Maximum consecutive failures for the same tool before stopping retries.
111    /// Default: 3.
112    pub max_consecutive_failures: usize,
113}
114
115impl Default for SafetyConfig {
116    fn default() -> Self {
117        Self {
118            max_tool_calls_per_turn: 128,
119            max_consecutive_failures: 3,
120        }
121    }
122}
123
124#[derive(Clone, Debug)]
125pub struct AgentConfig {
126    pub system_prompt: Option<String>,
127    /// Controls whether to include the reasoning content in LLM responses.
128    ///
129    /// Distinction from `reasoning.enabled`:
130    /// - `enable_thought`: controls whether the `reasoning_content` field is forwarded
131    ///   to consumers (i.e., "show the thinking process")
132    /// - `reasoning.enabled`: controls whether the model's extended thinking / reasoning
133    ///   mode is enabled (i.e., "let the model think deeply")
134    ///
135    /// Both are usually kept in sync, but can be controlled independently. For example,
136    /// to enable deep thinking without showing the process, set `enable_thought = false`
137    /// and `reasoning.enabled = true`.
138    pub enable_thought: bool,
139    /// Reasoning/thinking configuration that controls LLM reasoning behavior.
140    ///
141    /// - `enabled`: whether to enable extended thinking mode (equivalent to builder's `enable_thinking`)
142    /// - `budget_tokens`: thinking token budget cap
143    /// - `effort`: reasoning intensity/depth (semantics vary by provider)
144    pub reasoning: Option<ReasoningConfig>,
145    pub language: Language,
146    pub execution: ExecutionConfig,
147    pub llm: LlmConfig,
148    pub tool: ToolConfig,
149    pub session: SessionConfig,
150    pub safety: SafetyConfig,
151}
152
153impl Default for AgentConfig {
154    fn default() -> Self {
155        Self {
156            system_prompt: None,
157            enable_thought: false,
158            reasoning: None,
159            language: Language::default(),
160            execution: ExecutionConfig::default(),
161            llm: LlmConfig::default(),
162            tool: ToolConfig::default(),
163            session: SessionConfig::default(),
164            safety: SafetyConfig::default(),
165        }
166    }
167}
168
169impl AgentConfig {
170    /// Validate the configuration, returning an error for invalid values.
171    pub fn validate(&self) -> crate::types::AgentResult<()> {
172        use crate::types::AgentError;
173
174        if let Some(max_turns) = self.execution.max_turns {
175            if max_turns == 0 {
176                return Err(AgentError::config_error(
177                    "execution.max_turns must be > 0".to_string(),
178                ));
179            }
180        }
181
182        if let Some(max_sessions) = self.session.max_sessions {
183            if max_sessions == 0 {
184                return Err(AgentError::config_error(
185                    "session.max_sessions must be > 0".to_string(),
186                ));
187            }
188        }
189
190        if let Some(tool_timeout_ms) = self.tool.tool_timeout_ms {
191            if tool_timeout_ms == 0 {
192                return Err(AgentError::config_error(
193                    "tool.tool_timeout_ms must be > 0".to_string(),
194                ));
195            }
196        }
197
198        if self.safety.max_tool_calls_per_turn == 0 {
199            return Err(AgentError::config_error(
200                "safety.max_tool_calls_per_turn must be > 0".to_string(),
201            ));
202        }
203
204        Ok(())
205    }
206}
207
208#[derive(Clone, Debug)]
209pub struct ExecutionConfig {
210    pub max_turns: Option<u32>,
211    pub approval_timeout_ms: Option<u64>,
212    pub fail_on_persist_error: bool,
213}
214
215impl Default for ExecutionConfig {
216    fn default() -> Self {
217        Self {
218            max_turns: None,
219            approval_timeout_ms: None,
220            fail_on_persist_error: false,
221        }
222    }
223}
224
225#[derive(Clone, Debug)]
226pub struct LlmConfig {
227    pub response_format: Option<ResponseFormat>,
228    pub llm_retry: Option<RetryConfig>,
229}
230
231impl Default for LlmConfig {
232    fn default() -> Self {
233        Self {
234            response_format: None,
235            llm_retry: None,
236        }
237    }
238}
239
240#[derive(Clone, Debug)]
241pub struct ToolConfig {
242    pub tool_timeout_ms: Option<u64>,
243    pub max_tool_output_chars: Option<usize>,
244    pub tool_error_retry_prompt: Option<String>,
245}
246
247impl Default for ToolConfig {
248    fn default() -> Self {
249        Self {
250            tool_timeout_ms: None,
251            max_tool_output_chars: None,
252            tool_error_retry_prompt: None,
253        }
254    }
255}
256
257#[derive(Clone, Debug)]
258pub struct SessionConfig {
259    /// 最大 session 数量,超过时 LRU 逐出整个 session(从内存卸载,数据保留)
260    /// None = 不限制
261    pub max_sessions: Option<usize>,
262
263    /// 单个 session 最大保留轮数,超过从前面截掉最旧轮次
264    /// None = 不限制
265    pub max_turns_per_session: Option<usize>,
266
267    /// 单条消息 token 上限(安全阀),超过不存入 session 历史
268    /// 阈值应设很高(如 100k),只拦异常情况
269    /// None = 不限制
270    pub max_message_tokens: Option<usize>,
271}
272
273impl Default for SessionConfig {
274    fn default() -> Self {
275        Self {
276            max_sessions: None,
277            max_turns_per_session: None,
278            max_message_tokens: None,
279        }
280    }
281}