1use std::collections::HashMap;
4use std::env;
5
6use crate::client::messages::{DEFAULT_MAX_TOKENS, MIN_THINKING_BUDGET};
7
8#[derive(Clone, Debug, Default)]
9pub struct CloudConfig {
10 pub provider: ProviderSelection,
11 pub tokens: TokenLimits,
12 pub caching: CacheConfig,
13 pub gateway: GatewayOptions,
14}
15
16#[derive(Clone, Debug, Default)]
17pub struct ProviderSelection {
18 pub use_bedrock: bool,
19 pub use_vertex: bool,
20 pub use_foundry: bool,
21}
22
23#[derive(Clone, Debug)]
24pub struct TokenLimits {
25 pub max_output: u32,
26 pub max_thinking: u32,
27}
28
29impl Default for TokenLimits {
30 fn default() -> Self {
31 Self {
32 max_output: DEFAULT_MAX_TOKENS,
33 max_thinking: MIN_THINKING_BUDGET,
34 }
35 }
36}
37
38#[derive(Clone, Debug, Default)]
39pub struct CacheConfig {
40 pub disable_prompt_caching: bool,
41}
42
43#[derive(Clone, Debug, Default)]
44pub struct GatewayOptions {
45 pub disable_experimental_betas: bool,
46}
47
48#[derive(Clone, Debug)]
49pub struct BedrockConfig {
50 pub region: Option<String>,
51 pub small_model_region: Option<String>,
52 pub bearer_token: Option<String>,
53 pub auth_refresh_cmd: Option<String>,
54 pub credential_export_cmd: Option<String>,
55 pub use_global_endpoint: bool,
58 pub enable_1m_context: bool,
60}
61
62impl Default for BedrockConfig {
63 fn default() -> Self {
64 Self {
65 region: None,
66 small_model_region: None,
67 bearer_token: None,
68 auth_refresh_cmd: None,
69 credential_export_cmd: None,
70 use_global_endpoint: true, enable_1m_context: false,
72 }
73 }
74}
75
76#[derive(Clone, Debug, Default)]
77pub struct VertexConfig {
78 pub project_id: Option<String>,
79 pub region: Option<String>,
80 pub model_regions: HashMap<String, String>,
81 pub enable_1m_context: bool,
82}
83
84#[derive(Clone, Debug, Default)]
85pub struct FoundryConfig {
86 pub resource: Option<String>,
87 pub base_url: Option<String>,
89 pub api_key: Option<String>,
90}
91
92impl CloudConfig {
93 pub fn from_env() -> Self {
94 Self {
95 provider: ProviderSelection::from_env(),
96 tokens: TokenLimits::from_env(),
97 caching: CacheConfig::from_env(),
98 gateway: GatewayOptions::from_env(),
99 }
100 }
101
102 pub fn active_provider(&self) -> Option<&'static str> {
103 if self.provider.use_bedrock {
104 Some("bedrock")
105 } else if self.provider.use_vertex {
106 Some("vertex")
107 } else if self.provider.use_foundry {
108 Some("foundry")
109 } else {
110 None
111 }
112 }
113}
114
115impl ProviderSelection {
116 pub fn from_env() -> Self {
117 Self {
118 use_bedrock: is_flag_set("CLAUDE_CODE_USE_BEDROCK"),
119 use_vertex: is_flag_set("CLAUDE_CODE_USE_VERTEX"),
120 use_foundry: is_flag_set("CLAUDE_CODE_USE_FOUNDRY"),
121 }
122 }
123}
124
125impl TokenLimits {
126 pub fn from_env() -> Self {
127 Self {
128 max_output: parse_env("CLAUDE_CODE_MAX_OUTPUT_TOKENS").unwrap_or(DEFAULT_MAX_TOKENS),
129 max_thinking: parse_env("MAX_THINKING_TOKENS")
130 .unwrap_or(MIN_THINKING_BUDGET)
131 .max(MIN_THINKING_BUDGET),
132 }
133 }
134}
135
136impl CacheConfig {
137 pub fn from_env() -> Self {
138 Self {
139 disable_prompt_caching: is_flag_set("DISABLE_PROMPT_CACHING"),
140 }
141 }
142}
143
144impl GatewayOptions {
145 pub fn from_env() -> Self {
146 Self {
147 disable_experimental_betas: is_flag_set("CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS"),
148 }
149 }
150}
151
152impl BedrockConfig {
153 pub fn from_env() -> Self {
154 Self {
155 region: env::var("AWS_REGION").ok(),
156 small_model_region: env::var("ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION").ok(),
157 bearer_token: env::var("AWS_BEARER_TOKEN_BEDROCK").ok(),
158 auth_refresh_cmd: None, credential_export_cmd: None,
160 use_global_endpoint: !is_flag_set("BEDROCK_USE_REGIONAL_ENDPOINT"),
161 enable_1m_context: is_flag_set("BEDROCK_ENABLE_1M_CONTEXT"),
162 }
163 }
164
165 pub fn with_global_endpoint(mut self, enable: bool) -> Self {
167 self.use_global_endpoint = enable;
168 self
169 }
170
171 pub fn with_1m_context(mut self, enable: bool) -> Self {
173 self.enable_1m_context = enable;
174 self
175 }
176}
177
178impl VertexConfig {
179 pub fn from_env() -> Self {
180 let mut model_regions = HashMap::new();
181
182 let region_vars = [
183 ("VERTEX_REGION_CLAUDE_3_5_HAIKU", "claude-3-5-haiku"),
184 ("VERTEX_REGION_CLAUDE_3_5_SONNET", "claude-3-5-sonnet"),
185 ("VERTEX_REGION_CLAUDE_3_7_SONNET", "claude-3-7-sonnet"),
186 ("VERTEX_REGION_CLAUDE_4_0_OPUS", "claude-4-0-opus"),
187 ("VERTEX_REGION_CLAUDE_4_0_SONNET", "claude-4-0-sonnet"),
188 ("VERTEX_REGION_CLAUDE_4_1_OPUS", "claude-4-1-opus"),
189 ("VERTEX_REGION_CLAUDE_4_5_SONNET", "claude-4-5-sonnet"),
190 ("VERTEX_REGION_CLAUDE_4_5_HAIKU", "claude-4-5-haiku"),
191 ];
192
193 for (env_var, model_key) in region_vars {
194 if let Ok(region) = env::var(env_var) {
195 model_regions.insert(model_key.to_string(), region);
196 }
197 }
198
199 Self {
200 project_id: env::var("ANTHROPIC_VERTEX_PROJECT_ID")
201 .or_else(|_| env::var("GOOGLE_CLOUD_PROJECT"))
202 .or_else(|_| env::var("GCLOUD_PROJECT"))
203 .ok(),
204 region: env::var("CLOUD_ML_REGION")
205 .or_else(|_| env::var("GOOGLE_CLOUD_REGION"))
206 .ok(),
207 model_regions,
208 enable_1m_context: is_flag_set("VERTEX_ENABLE_1M_CONTEXT"),
209 }
210 }
211
212 pub fn region_for_model(&self, model: &str) -> Option<&str> {
213 for (key, region) in &self.model_regions {
214 if model.contains(key) {
215 return Some(region);
216 }
217 }
218 self.region.as_deref()
219 }
220
221 pub fn is_global(&self) -> bool {
222 self.region.as_deref() == Some("global")
223 }
224}
225
226impl FoundryConfig {
227 pub fn from_env() -> Self {
228 Self {
229 resource: env::var("ANTHROPIC_FOUNDRY_RESOURCE")
230 .or_else(|_| env::var("AZURE_RESOURCE_NAME"))
231 .ok(),
232 base_url: env::var("ANTHROPIC_FOUNDRY_BASE_URL").ok(),
233 api_key: env::var("ANTHROPIC_FOUNDRY_API_KEY")
234 .or_else(|_| env::var("AZURE_API_KEY"))
235 .ok(),
236 }
237 }
238
239 pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
241 self.resource = Some(resource.into());
242 self
243 }
244
245 pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
247 self.base_url = Some(base_url.into());
248 self
249 }
250
251 pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
253 self.api_key = Some(api_key.into());
254 self
255 }
256}
257
258fn is_flag_set(var: &str) -> bool {
259 env::var(var)
260 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
261 .unwrap_or(false)
262}
263
264fn parse_env<T: std::str::FromStr>(var: &str) -> Option<T> {
265 env::var(var).ok().and_then(|v| v.parse().ok())
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn test_cloud_config_default() {
274 let config = CloudConfig::default();
275 assert!(!config.provider.use_bedrock);
276 assert!(!config.provider.use_vertex);
277 assert!(!config.provider.use_foundry);
278 assert_eq!(config.tokens.max_output, DEFAULT_MAX_TOKENS);
279 assert_eq!(config.tokens.max_thinking, MIN_THINKING_BUDGET);
280 }
281
282 #[test]
283 fn test_token_limits_default() {
284 let limits = TokenLimits::default();
285 assert_eq!(limits.max_output, DEFAULT_MAX_TOKENS);
286 assert_eq!(limits.max_thinking, MIN_THINKING_BUDGET);
287 }
288
289 #[test]
290 fn test_vertex_region_for_model() {
291 let mut config = VertexConfig::default();
292 config
293 .model_regions
294 .insert("claude-4-5-sonnet".into(), "us-east5".into());
295
296 assert_eq!(
297 config.region_for_model("claude-4-5-sonnet@20250929"),
298 Some("us-east5")
299 );
300 }
301}