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("TOGETHER_API_KEY"))
101 .ok();
102
103 let use_bedrock = std::env::var("AGENT_CODE_USE_BEDROCK").is_ok()
106 || std::env::var("AWS_REGION").is_ok() && api_key.is_some();
107 let use_vertex = std::env::var("AGENT_CODE_USE_VERTEX").is_ok();
108
109 let has_generic = std::env::var("AGENT_CODE_API_KEY").is_ok();
110 let base_url = if use_bedrock {
111 let region = std::env::var("AWS_REGION").unwrap_or_else(|_| "us-east-1".to_string());
113 format!("https://bedrock-runtime.{region}.amazonaws.com")
114 } else if use_vertex {
115 let project = std::env::var("GOOGLE_CLOUD_PROJECT").unwrap_or_default();
117 let location = std::env::var("GOOGLE_CLOUD_LOCATION")
118 .unwrap_or_else(|_| "us-central1".to_string());
119 format!(
120 "https://{location}-aiplatform.googleapis.com/v1/projects/{project}/locations/{location}/publishers/anthropic/models"
121 )
122 } else if has_generic {
123 "https://api.openai.com/v1".to_string()
125 } else if std::env::var("GOOGLE_API_KEY").is_ok() {
126 "https://generativelanguage.googleapis.com/v1beta/openai".to_string()
127 } else if std::env::var("DEEPSEEK_API_KEY").is_ok() {
128 "https://api.deepseek.com/v1".to_string()
129 } else if std::env::var("XAI_API_KEY").is_ok() {
130 "https://api.x.ai/v1".to_string()
131 } else if std::env::var("GROQ_API_KEY").is_ok() {
132 "https://api.groq.com/openai/v1".to_string()
133 } else if std::env::var("MISTRAL_API_KEY").is_ok() {
134 "https://api.mistral.ai/v1".to_string()
135 } else if std::env::var("TOGETHER_API_KEY").is_ok() {
136 "https://api.together.xyz/v1".to_string()
137 } else {
138 "https://api.openai.com/v1".to_string()
140 };
141
142 Self {
143 base_url,
144 model: "gpt-5.4".to_string(),
145 api_key,
146 max_output_tokens: Some(16384),
147 thinking: None,
148 effort: None,
149 max_cost_usd: None,
150 timeout_secs: 120,
151 max_retries: 3,
152 }
153 }
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(default)]
159pub struct PermissionsConfig {
160 pub default_mode: PermissionMode,
162 pub rules: Vec<PermissionRule>,
164}
165
166impl Default for PermissionsConfig {
167 fn default() -> Self {
168 Self {
169 default_mode: PermissionMode::Ask,
170 rules: Vec::new(),
171 }
172 }
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
177#[serde(rename_all = "snake_case")]
178pub enum PermissionMode {
179 Allow,
181 Deny,
183 Ask,
185 AcceptEdits,
187 Plan,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct PermissionRule {
194 pub tool: String,
196 pub pattern: Option<String>,
198 pub action: PermissionMode,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(default)]
205pub struct UiConfig {
206 pub markdown: bool,
208 pub syntax_highlight: bool,
210 pub theme: String,
212 pub edit_mode: String,
214}
215
216impl Default for UiConfig {
217 fn default() -> Self {
218 Self {
219 markdown: true,
220 syntax_highlight: true,
221 theme: "dark".to_string(),
222 edit_mode: "emacs".to_string(),
223 }
224 }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(default)]
231pub struct FeaturesConfig {
232 pub token_budget: bool,
234 pub commit_attribution: bool,
236 pub compaction_reminders: bool,
238 pub unattended_retry: bool,
240 pub history_snip: bool,
242 pub auto_theme: bool,
244 pub mcp_rich_output: bool,
246 pub fork_conversation: bool,
248 pub verification_agent: bool,
250 pub extract_memories: bool,
252 pub context_collapse: bool,
254 pub reactive_compact: bool,
256}
257
258impl Default for FeaturesConfig {
259 fn default() -> Self {
260 Self {
261 token_budget: true,
262 commit_attribution: true,
263 compaction_reminders: true,
264 unattended_retry: true,
265 history_snip: true,
266 auto_theme: true,
267 mcp_rich_output: true,
268 fork_conversation: true,
269 verification_agent: true,
270 extract_memories: true,
271 context_collapse: true,
272 reactive_compact: true,
273 }
274 }
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
281#[serde(rename_all = "snake_case")]
282pub enum HookEvent {
283 SessionStart,
284 SessionStop,
285 PreToolUse,
286 PostToolUse,
287 UserPromptSubmit,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292#[serde(tag = "type")]
293pub enum HookAction {
294 #[serde(rename = "shell")]
296 Shell { command: String },
297 #[serde(rename = "http")]
299 Http { url: String, method: Option<String> },
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct HookDefinition {
305 pub event: HookEvent,
306 pub action: HookAction,
307 pub tool_name: Option<String>,
309}