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("XAI_API_KEY"))
108 .or_else(|_| std::env::var("GOOGLE_API_KEY"))
109 .or_else(|_| std::env::var("DEEPSEEK_API_KEY"))
110 .or_else(|_| std::env::var("GROQ_API_KEY"))
111 .or_else(|_| std::env::var("MISTRAL_API_KEY"))
112 .or_else(|_| std::env::var("ZHIPU_API_KEY"))
113 .or_else(|_| std::env::var("TOGETHER_API_KEY"))
114 .or_else(|_| std::env::var("OPENROUTER_API_KEY"))
115 .or_else(|_| std::env::var("COHERE_API_KEY"))
116 .or_else(|_| std::env::var("PERPLEXITY_API_KEY"))
117 .ok();
118
119 let use_bedrock = std::env::var("AGENT_CODE_USE_BEDROCK").is_ok()
122 || std::env::var("AWS_REGION").is_ok() && api_key.is_some();
123 let use_vertex = std::env::var("AGENT_CODE_USE_VERTEX").is_ok();
124
125 let has_generic = std::env::var("AGENT_CODE_API_KEY").is_ok();
126 let base_url = if use_bedrock {
127 let region = std::env::var("AWS_REGION").unwrap_or_else(|_| "us-east-1".to_string());
129 format!("https://bedrock-runtime.{region}.amazonaws.com")
130 } else if use_vertex {
131 let project = std::env::var("GOOGLE_CLOUD_PROJECT").unwrap_or_default();
133 let location = std::env::var("GOOGLE_CLOUD_LOCATION")
134 .unwrap_or_else(|_| "us-central1".to_string());
135 format!(
136 "https://{location}-aiplatform.googleapis.com/v1/projects/{project}/locations/{location}/publishers/anthropic/models"
137 )
138 } else if has_generic {
139 "https://api.openai.com/v1".to_string()
141 } else if std::env::var("GOOGLE_API_KEY").is_ok() {
142 "https://generativelanguage.googleapis.com/v1beta/openai".to_string()
143 } else if std::env::var("DEEPSEEK_API_KEY").is_ok() {
144 "https://api.deepseek.com/v1".to_string()
145 } else if std::env::var("XAI_API_KEY").is_ok() {
146 "https://api.x.ai/v1".to_string()
147 } else if std::env::var("GROQ_API_KEY").is_ok() {
148 "https://api.groq.com/openai/v1".to_string()
149 } else if std::env::var("MISTRAL_API_KEY").is_ok() {
150 "https://api.mistral.ai/v1".to_string()
151 } else if std::env::var("TOGETHER_API_KEY").is_ok() {
152 "https://api.together.xyz/v1".to_string()
153 } else if std::env::var("OPENROUTER_API_KEY").is_ok() {
154 "https://openrouter.ai/api/v1".to_string()
155 } else if std::env::var("COHERE_API_KEY").is_ok() {
156 "https://api.cohere.com/v2".to_string()
157 } else if std::env::var("PERPLEXITY_API_KEY").is_ok() {
158 "https://api.perplexity.ai".to_string()
159 } else {
160 "https://api.openai.com/v1".to_string()
162 };
163
164 Self {
165 base_url,
166 model: "gpt-5.4".to_string(),
167 api_key,
168 max_output_tokens: Some(16384),
169 thinking: None,
170 effort: None,
171 max_cost_usd: None,
172 timeout_secs: 120,
173 max_retries: 3,
174 }
175 }
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180#[serde(default)]
181pub struct PermissionsConfig {
182 pub default_mode: PermissionMode,
184 pub rules: Vec<PermissionRule>,
186}
187
188impl Default for PermissionsConfig {
189 fn default() -> Self {
190 Self {
191 default_mode: PermissionMode::Ask,
192 rules: Vec::new(),
193 }
194 }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
201#[serde(rename_all = "snake_case")]
202pub enum PermissionMode {
203 Allow,
205 Deny,
207 Ask,
209 AcceptEdits,
211 Plan,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct PermissionRule {
218 pub tool: String,
220 pub pattern: Option<String>,
222 pub action: PermissionMode,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228#[serde(default)]
229pub struct UiConfig {
230 pub markdown: bool,
232 pub syntax_highlight: bool,
234 pub theme: String,
236 pub edit_mode: String,
238}
239
240impl Default for UiConfig {
241 fn default() -> Self {
242 Self {
243 markdown: true,
244 syntax_highlight: true,
245 theme: "dark".to_string(),
246 edit_mode: "emacs".to_string(),
247 }
248 }
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
254#[serde(default)]
255pub struct FeaturesConfig {
256 pub token_budget: bool,
258 pub commit_attribution: bool,
260 pub compaction_reminders: bool,
262 pub unattended_retry: bool,
264 pub history_snip: bool,
266 pub auto_theme: bool,
268 pub mcp_rich_output: bool,
270 pub fork_conversation: bool,
272 pub verification_agent: bool,
274 pub extract_memories: bool,
276 pub context_collapse: bool,
278 pub reactive_compact: bool,
280}
281
282impl Default for FeaturesConfig {
283 fn default() -> Self {
284 Self {
285 token_budget: true,
286 commit_attribution: true,
287 compaction_reminders: true,
288 unattended_retry: true,
289 history_snip: true,
290 auto_theme: true,
291 mcp_rich_output: true,
292 fork_conversation: true,
293 verification_agent: true,
294 extract_memories: true,
295 context_collapse: true,
296 reactive_compact: true,
297 }
298 }
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
305#[serde(rename_all = "snake_case")]
306pub enum HookEvent {
307 SessionStart,
308 SessionStop,
309 PreToolUse,
310 PostToolUse,
311 UserPromptSubmit,
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
316#[serde(tag = "type")]
317pub enum HookAction {
318 #[serde(rename = "shell")]
320 Shell { command: String },
321 #[serde(rename = "http")]
323 Http { url: String, method: Option<String> },
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct HookDefinition {
329 pub event: HookEvent,
330 pub action: HookAction,
331 pub tool_name: Option<String>,
333}