Skip to main content

agent_sdk/
config.rs

1use serde::{Deserialize, Serialize};
2
3/// Well-known project-local config directory name, analogous to `.claude/`.
4/// Mutable runtime state is stored separately under `~/.agent/projects/...`.
5pub const AGENT_DIR: &str = ".agent";
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
8#[serde(rename_all = "snake_case")]
9pub enum LlmProvider {
10    #[default]
11    Claude,
12    OpenAi,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct LlmConfig {
17    #[serde(default)]
18    pub provider: LlmProvider,
19    pub model: String,
20    pub max_tokens: usize,
21    pub requests_per_minute: u32,
22    pub tokens_per_minute: u32,
23    #[serde(default)]
24    pub api_key: Option<String>,
25    #[serde(default)]
26    pub api_base_url: Option<String>,
27
28    /// HTTP request timeout in seconds (applies to each LLM API call).
29    #[serde(default = "default_http_timeout_secs")]
30    pub http_timeout_secs: u64,
31    /// Maximum number of automatic retries on transient errors (429, 5xx).
32    #[serde(default = "default_max_retries")]
33    pub max_retries: u32,
34    /// Base delay in milliseconds for exponential back-off on retries.
35    #[serde(default = "default_retry_base_delay_ms")]
36    pub retry_base_delay_ms: u64,
37}
38
39fn default_http_timeout_secs() -> u64 {
40    120
41}
42
43fn default_max_retries() -> u32 {
44    3
45}
46
47fn default_retry_base_delay_ms() -> u64 {
48    1000
49}
50
51impl LlmConfig {
52    pub fn resolve_api_key(&self) -> Option<String> {
53        if let Some(ref key) = self.api_key {
54            if !key.is_empty() {
55                return Some(key.clone());
56            }
57        }
58        let env_var = match self.provider {
59            LlmProvider::Claude => "ANTHROPIC_API_KEY",
60            LlmProvider::OpenAi => "OPENAI_API_KEY",
61        };
62        std::env::var(env_var).ok()
63    }
64
65    pub fn resolve_base_url(&self) -> String {
66        if let Some(ref url) = self.api_base_url {
67            if !url.is_empty() {
68                return url.clone();
69            }
70        }
71        let env_var = match self.provider {
72            LlmProvider::Claude => "ANTHROPIC_API_BASE_URL",
73            LlmProvider::OpenAi => "OPENAI_API_BASE_URL",
74        };
75        if let Ok(url) = std::env::var(env_var) {
76            if !url.is_empty() {
77                return url;
78            }
79        }
80        match self.provider {
81            LlmProvider::Claude => "https://api.anthropic.com".to_string(),
82            LlmProvider::OpenAi => "https://api.openai.com".to_string(),
83        }
84    }
85}
86
87impl Default for LlmConfig {
88    fn default() -> Self {
89        Self {
90            provider: LlmProvider::Claude,
91            model: "claude-sonnet-4-20250514".to_string(),
92            max_tokens: 4096,
93            requests_per_minute: 50,
94            tokens_per_minute: 80_000,
95            api_key: None,
96            api_base_url: None,
97            http_timeout_secs: default_http_timeout_secs(),
98            max_retries: default_max_retries(),
99            retry_base_delay_ms: default_retry_base_delay_ms(),
100        }
101    }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct AgentConfig {
106    pub max_parallel_agents: usize,
107    pub poll_interval_ms: u64,
108    pub max_task_retries: u32,
109    #[serde(default = "default_max_loop_iterations")]
110    pub max_loop_iterations: usize,
111    #[serde(default = "default_max_context_tokens")]
112    pub max_context_tokens: usize,
113    /// How many consecutive idle polling cycles before a teammate exits.
114    #[serde(default = "default_max_idle_cycles")]
115    pub max_idle_cycles: u32,
116    /// Seconds a teammate will wait for plan approval before proceeding.
117    #[serde(default = "default_plan_approval_timeout_secs")]
118    pub plan_approval_timeout_secs: u64,
119    /// Default timeout in seconds for `run_command` tool invocations.
120    #[serde(default = "default_command_timeout_secs")]
121    pub command_timeout_secs: u64,
122}
123
124fn default_max_loop_iterations() -> usize {
125    50
126}
127
128fn default_max_context_tokens() -> usize {
129    200_000
130}
131
132fn default_max_idle_cycles() -> u32 {
133    50
134}
135
136fn default_plan_approval_timeout_secs() -> u64 {
137    300
138}
139
140fn default_command_timeout_secs() -> u64 {
141    30
142}
143
144impl Default for AgentConfig {
145    fn default() -> Self {
146        Self {
147            max_parallel_agents: 4,
148            poll_interval_ms: 200,
149            max_task_retries: 3,
150            max_loop_iterations: default_max_loop_iterations(),
151            max_context_tokens: default_max_context_tokens(),
152            max_idle_cycles: default_max_idle_cycles(),
153            plan_approval_timeout_secs: default_plan_approval_timeout_secs(),
154            command_timeout_secs: default_command_timeout_secs(),
155        }
156    }
157}