1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(default)]
8#[derive(Default)]
9pub struct Config {
10 pub api: ApiConfig,
11 pub permissions: PermissionsConfig,
12 pub ui: UiConfig,
13 #[serde(default)]
15 pub features: FeaturesConfig,
16 #[serde(default)]
18 pub mcp_servers: std::collections::HashMap<String, McpServerEntry>,
19 #[serde(default)]
21 pub hooks: Vec<HookDefinition>,
22 #[serde(default)]
24 pub security: SecurityConfig,
25}
26
27#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29#[serde(default)]
30pub struct SecurityConfig {
31 #[serde(default)]
33 pub additional_directories: Vec<String>,
34 #[serde(default)]
36 pub mcp_server_allowlist: Vec<String>,
37 #[serde(default)]
39 pub mcp_server_denylist: Vec<String>,
40 #[serde(default)]
42 pub disable_bypass_permissions: bool,
43 #[serde(default)]
45 pub env_allowlist: Vec<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct McpServerEntry {
51 pub command: Option<String>,
53 #[serde(default)]
55 pub args: Vec<String>,
56 pub url: Option<String>,
58 #[serde(default)]
60 pub env: std::collections::HashMap<String, String>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(default)]
66pub struct ApiConfig {
67 pub base_url: String,
69 pub model: String,
71 #[serde(skip_serializing)]
74 pub api_key: Option<String>,
75 pub max_output_tokens: Option<u32>,
77 pub thinking: Option<String>,
79 pub effort: Option<String>,
81 pub max_cost_usd: Option<f64>,
83 pub timeout_secs: u64,
85 pub max_retries: u32,
87}
88
89impl Default for ApiConfig {
90 fn default() -> Self {
91 let api_key = std::env::var("AGENT_CODE_API_KEY")
93 .or_else(|_| std::env::var("ANTHROPIC_API_KEY"))
94 .or_else(|_| std::env::var("OPENAI_API_KEY"))
95 .or_else(|_| std::env::var("XAI_API_KEY"))
96 .or_else(|_| std::env::var("GOOGLE_API_KEY"))
97 .or_else(|_| std::env::var("DEEPSEEK_API_KEY"))
98 .or_else(|_| std::env::var("GROQ_API_KEY"))
99 .or_else(|_| std::env::var("MISTRAL_API_KEY"))
100 .or_else(|_| std::env::var("ZHIPU_API_KEY"))
101 .or_else(|_| std::env::var("TOGETHER_API_KEY"))
102 .ok();
103
104 let use_bedrock = std::env::var("AGENT_CODE_USE_BEDROCK").is_ok()
107 || std::env::var("AWS_REGION").is_ok() && api_key.is_some();
108 let use_vertex = std::env::var("AGENT_CODE_USE_VERTEX").is_ok();
109
110 let has_generic = std::env::var("AGENT_CODE_API_KEY").is_ok();
111 let base_url = if use_bedrock {
112 let region = std::env::var("AWS_REGION").unwrap_or_else(|_| "us-east-1".to_string());
114 format!("https://bedrock-runtime.{region}.amazonaws.com")
115 } else if use_vertex {
116 let project = std::env::var("GOOGLE_CLOUD_PROJECT").unwrap_or_default();
118 let location = std::env::var("GOOGLE_CLOUD_LOCATION")
119 .unwrap_or_else(|_| "us-central1".to_string());
120 format!(
121 "https://{location}-aiplatform.googleapis.com/v1/projects/{project}/locations/{location}/publishers/anthropic/models"
122 )
123 } else if has_generic {
124 "https://api.openai.com/v1".to_string()
126 } else if std::env::var("GOOGLE_API_KEY").is_ok() {
127 "https://generativelanguage.googleapis.com/v1beta/openai".to_string()
128 } else if std::env::var("DEEPSEEK_API_KEY").is_ok() {
129 "https://api.deepseek.com/v1".to_string()
130 } else if std::env::var("XAI_API_KEY").is_ok() {
131 "https://api.x.ai/v1".to_string()
132 } else if std::env::var("GROQ_API_KEY").is_ok() {
133 "https://api.groq.com/openai/v1".to_string()
134 } else if std::env::var("MISTRAL_API_KEY").is_ok() {
135 "https://api.mistral.ai/v1".to_string()
136 } else if std::env::var("TOGETHER_API_KEY").is_ok() {
137 "https://api.together.xyz/v1".to_string()
138 } else {
139 "https://api.openai.com/v1".to_string()
141 };
142
143 Self {
144 base_url,
145 model: "gpt-5.4".to_string(),
146 api_key,
147 max_output_tokens: Some(16384),
148 thinking: None,
149 effort: None,
150 max_cost_usd: None,
151 timeout_secs: 120,
152 max_retries: 3,
153 }
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159#[serde(default)]
160pub struct PermissionsConfig {
161 pub default_mode: PermissionMode,
163 pub rules: Vec<PermissionRule>,
165}
166
167impl Default for PermissionsConfig {
168 fn default() -> Self {
169 Self {
170 default_mode: PermissionMode::Ask,
171 rules: Vec::new(),
172 }
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
178#[serde(rename_all = "snake_case")]
179pub enum PermissionMode {
180 Allow,
182 Deny,
184 Ask,
186 AcceptEdits,
188 Plan,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct PermissionRule {
195 pub tool: String,
197 pub pattern: Option<String>,
199 pub action: PermissionMode,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205#[serde(default)]
206pub struct UiConfig {
207 pub markdown: bool,
209 pub syntax_highlight: bool,
211 pub theme: String,
213 pub edit_mode: String,
215}
216
217impl Default for UiConfig {
218 fn default() -> Self {
219 Self {
220 markdown: true,
221 syntax_highlight: true,
222 theme: "dark".to_string(),
223 edit_mode: "emacs".to_string(),
224 }
225 }
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
231#[serde(default)]
232pub struct FeaturesConfig {
233 pub token_budget: bool,
235 pub commit_attribution: bool,
237 pub compaction_reminders: bool,
239 pub unattended_retry: bool,
241 pub history_snip: bool,
243 pub auto_theme: bool,
245 pub mcp_rich_output: bool,
247 pub fork_conversation: bool,
249 pub verification_agent: bool,
251 pub extract_memories: bool,
253 pub context_collapse: bool,
255 pub reactive_compact: bool,
257}
258
259impl Default for FeaturesConfig {
260 fn default() -> Self {
261 Self {
262 token_budget: true,
263 commit_attribution: true,
264 compaction_reminders: true,
265 unattended_retry: true,
266 history_snip: true,
267 auto_theme: true,
268 mcp_rich_output: true,
269 fork_conversation: true,
270 verification_agent: true,
271 extract_memories: true,
272 context_collapse: true,
273 reactive_compact: true,
274 }
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
282#[serde(rename_all = "snake_case")]
283pub enum HookEvent {
284 SessionStart,
285 SessionStop,
286 PreToolUse,
287 PostToolUse,
288 UserPromptSubmit,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
293#[serde(tag = "type")]
294pub enum HookAction {
295 #[serde(rename = "shell")]
297 Shell { command: String },
298 #[serde(rename = "http")]
300 Http { url: String, method: Option<String> },
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct HookDefinition {
306 pub event: HookEvent,
307 pub action: HookAction,
308 pub tool_name: Option<String>,
310}