1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(default)]
13#[derive(Default)]
14pub struct Config {
15 pub api: ApiConfig,
16 pub permissions: PermissionsConfig,
17 pub ui: UiConfig,
18 #[serde(default)]
20 pub features: FeaturesConfig,
21 #[serde(default)]
23 pub mcp_servers: std::collections::HashMap<String, McpServerEntry>,
24 #[serde(default)]
26 pub hooks: Vec<HookDefinition>,
27 #[serde(default)]
29 pub security: SecurityConfig,
30}
31
32#[derive(Debug, Clone, Default, Serialize, Deserialize)]
34#[serde(default)]
35pub struct SecurityConfig {
36 #[serde(default)]
38 pub additional_directories: Vec<String>,
39 #[serde(default)]
41 pub mcp_server_allowlist: Vec<String>,
42 #[serde(default)]
44 pub mcp_server_denylist: Vec<String>,
45 #[serde(default)]
47 pub disable_bypass_permissions: bool,
48 #[serde(default)]
50 pub env_allowlist: Vec<String>,
51 #[serde(default)]
53 pub disable_skill_shell_execution: bool,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct McpServerEntry {
59 pub command: Option<String>,
61 #[serde(default)]
63 pub args: Vec<String>,
64 pub url: Option<String>,
66 #[serde(default)]
68 pub env: std::collections::HashMap<String, String>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(default)]
78pub struct ApiConfig {
79 pub base_url: String,
81 pub model: String,
83 #[serde(skip_serializing)]
86 pub api_key: Option<String>,
87 pub max_output_tokens: Option<u32>,
89 pub thinking: Option<String>,
91 pub effort: Option<String>,
93 pub max_cost_usd: Option<f64>,
95 pub timeout_secs: u64,
97 pub max_retries: u32,
99}
100
101impl Default for ApiConfig {
102 fn default() -> Self {
103 let api_key = std::env::var("AGENT_CODE_API_KEY")
105 .or_else(|_| std::env::var("ANTHROPIC_API_KEY"))
106 .or_else(|_| std::env::var("OPENAI_API_KEY"))
107 .or_else(|_| std::env::var("AZURE_OPENAI_API_KEY"))
108 .or_else(|_| std::env::var("XAI_API_KEY"))
109 .or_else(|_| std::env::var("GOOGLE_API_KEY"))
110 .or_else(|_| std::env::var("DEEPSEEK_API_KEY"))
111 .or_else(|_| std::env::var("GROQ_API_KEY"))
112 .or_else(|_| std::env::var("MISTRAL_API_KEY"))
113 .or_else(|_| std::env::var("ZHIPU_API_KEY"))
114 .or_else(|_| std::env::var("TOGETHER_API_KEY"))
115 .or_else(|_| std::env::var("OPENROUTER_API_KEY"))
116 .or_else(|_| std::env::var("COHERE_API_KEY"))
117 .or_else(|_| std::env::var("PERPLEXITY_API_KEY"))
118 .ok();
119
120 let use_bedrock = std::env::var("AGENT_CODE_USE_BEDROCK").is_ok()
123 || std::env::var("AWS_REGION").is_ok() && api_key.is_some();
124 let use_vertex = std::env::var("AGENT_CODE_USE_VERTEX").is_ok();
125 let use_azure = std::env::var("AZURE_OPENAI_ENDPOINT").is_ok()
126 || std::env::var("AZURE_OPENAI_API_KEY").is_ok();
127
128 let has_generic = std::env::var("AGENT_CODE_API_KEY").is_ok();
129 let base_url = if use_bedrock {
130 let region = std::env::var("AWS_REGION").unwrap_or_else(|_| "us-east-1".to_string());
132 format!("https://bedrock-runtime.{region}.amazonaws.com")
133 } else if use_vertex {
134 let project = std::env::var("GOOGLE_CLOUD_PROJECT").unwrap_or_default();
136 let location = std::env::var("GOOGLE_CLOUD_LOCATION")
137 .unwrap_or_else(|_| "us-central1".to_string());
138 format!(
139 "https://{location}-aiplatform.googleapis.com/v1/projects/{project}/locations/{location}/publishers/anthropic/models"
140 )
141 } else if use_azure {
142 std::env::var("AZURE_OPENAI_ENDPOINT").unwrap_or_else(|_| {
144 "https://YOUR_RESOURCE.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT"
145 .to_string()
146 })
147 } else if has_generic {
148 "https://api.openai.com/v1".to_string()
150 } else if std::env::var("GOOGLE_API_KEY").is_ok() {
151 "https://generativelanguage.googleapis.com/v1beta/openai".to_string()
152 } else if std::env::var("DEEPSEEK_API_KEY").is_ok() {
153 "https://api.deepseek.com/v1".to_string()
154 } else if std::env::var("XAI_API_KEY").is_ok() {
155 "https://api.x.ai/v1".to_string()
156 } else if std::env::var("GROQ_API_KEY").is_ok() {
157 "https://api.groq.com/openai/v1".to_string()
158 } else if std::env::var("MISTRAL_API_KEY").is_ok() {
159 "https://api.mistral.ai/v1".to_string()
160 } else if std::env::var("TOGETHER_API_KEY").is_ok() {
161 "https://api.together.xyz/v1".to_string()
162 } else if std::env::var("OPENROUTER_API_KEY").is_ok() {
163 "https://openrouter.ai/api/v1".to_string()
164 } else if std::env::var("COHERE_API_KEY").is_ok() {
165 "https://api.cohere.com/v2".to_string()
166 } else if std::env::var("PERPLEXITY_API_KEY").is_ok() {
167 "https://api.perplexity.ai".to_string()
168 } else {
169 "https://api.openai.com/v1".to_string()
171 };
172
173 Self {
174 base_url,
175 model: "gpt-5.4".to_string(),
176 api_key,
177 max_output_tokens: Some(16384),
178 thinking: None,
179 effort: None,
180 max_cost_usd: None,
181 timeout_secs: 120,
182 max_retries: 3,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189#[serde(default)]
190pub struct PermissionsConfig {
191 pub default_mode: PermissionMode,
193 pub rules: Vec<PermissionRule>,
195}
196
197impl Default for PermissionsConfig {
198 fn default() -> Self {
199 Self {
200 default_mode: PermissionMode::Ask,
201 rules: Vec::new(),
202 }
203 }
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
210#[serde(rename_all = "snake_case")]
211pub enum PermissionMode {
212 Allow,
214 Deny,
216 Ask,
218 AcceptEdits,
220 Plan,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct PermissionRule {
227 pub tool: String,
229 pub pattern: Option<String>,
231 pub action: PermissionMode,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(default)]
238pub struct UiConfig {
239 pub markdown: bool,
241 pub syntax_highlight: bool,
243 pub theme: String,
245 pub edit_mode: String,
247}
248
249impl Default for UiConfig {
250 fn default() -> Self {
251 Self {
252 markdown: true,
253 syntax_highlight: true,
254 theme: "dark".to_string(),
255 edit_mode: "emacs".to_string(),
256 }
257 }
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
263#[serde(default)]
264pub struct FeaturesConfig {
265 pub token_budget: bool,
267 pub commit_attribution: bool,
269 pub compaction_reminders: bool,
271 pub unattended_retry: bool,
273 pub history_snip: bool,
275 pub auto_theme: bool,
277 pub mcp_rich_output: bool,
279 pub fork_conversation: bool,
281 pub verification_agent: bool,
283 pub extract_memories: bool,
285 pub context_collapse: bool,
287 pub reactive_compact: bool,
289}
290
291impl Default for FeaturesConfig {
292 fn default() -> Self {
293 Self {
294 token_budget: true,
295 commit_attribution: true,
296 compaction_reminders: true,
297 unattended_retry: true,
298 history_snip: true,
299 auto_theme: true,
300 mcp_rich_output: true,
301 fork_conversation: true,
302 verification_agent: true,
303 extract_memories: true,
304 context_collapse: true,
305 reactive_compact: true,
306 }
307 }
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
314#[serde(rename_all = "snake_case")]
315pub enum HookEvent {
316 SessionStart,
317 SessionStop,
318 PreToolUse,
319 PostToolUse,
320 UserPromptSubmit,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
325#[serde(tag = "type")]
326pub enum HookAction {
327 #[serde(rename = "shell")]
329 Shell { command: String },
330 #[serde(rename = "http")]
332 Http { url: String, method: Option<String> },
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct HookDefinition {
338 pub event: HookEvent,
339 pub action: HookAction,
340 pub tool_name: Option<String>,
342}