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: PromptCachingConfig,
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 PromptCachingConfig {
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: PromptCachingConfig::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 PromptCachingConfig {
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 global_endpoint(mut self, enable: bool) -> Self {
167 self.use_global_endpoint = enable;
168 self
169 }
170
171 pub fn extended_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_4_5_SONNET", "claude-4-5-sonnet"),
184 ("VERTEX_REGION_CLAUDE_4_5_HAIKU", "claude-4-5-haiku"),
185 ("VERTEX_REGION_CLAUDE_4_6_OPUS", "claude-4-6-opus"),
186 ];
187
188 for (env_var, model_key) in region_vars {
189 if let Ok(region) = env::var(env_var) {
190 model_regions.insert(model_key.to_string(), region);
191 }
192 }
193
194 Self {
195 project_id: env::var("ANTHROPIC_VERTEX_PROJECT_ID")
196 .or_else(|_| env::var("GOOGLE_CLOUD_PROJECT"))
197 .or_else(|_| env::var("GCLOUD_PROJECT"))
198 .ok(),
199 region: env::var("CLOUD_ML_REGION")
200 .or_else(|_| env::var("GOOGLE_CLOUD_REGION"))
201 .ok(),
202 model_regions,
203 enable_1m_context: is_flag_set("VERTEX_ENABLE_1M_CONTEXT"),
204 }
205 }
206
207 pub fn region_for_model(&self, model: &str) -> Option<&str> {
208 for (key, region) in &self.model_regions {
209 if model.contains(key) {
210 return Some(region);
211 }
212 }
213 self.region.as_deref()
214 }
215
216 pub fn is_global(&self) -> bool {
217 self.region.as_deref() == Some("global")
218 }
219}
220
221impl FoundryConfig {
222 pub fn from_env() -> Self {
223 Self {
224 resource: env::var("ANTHROPIC_FOUNDRY_RESOURCE")
225 .or_else(|_| env::var("AZURE_RESOURCE_NAME"))
226 .ok(),
227 base_url: env::var("ANTHROPIC_FOUNDRY_BASE_URL").ok(),
228 api_key: env::var("ANTHROPIC_FOUNDRY_API_KEY")
229 .or_else(|_| env::var("AZURE_API_KEY"))
230 .ok(),
231 }
232 }
233
234 pub fn resource(mut self, resource: impl Into<String>) -> Self {
236 self.resource = Some(resource.into());
237 self
238 }
239
240 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
242 self.base_url = Some(base_url.into());
243 self
244 }
245
246 pub fn api_key(mut self, api_key: impl Into<String>) -> Self {
248 self.api_key = Some(api_key.into());
249 self
250 }
251}
252
253fn is_flag_set(var: &str) -> bool {
254 env::var(var)
255 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
256 .unwrap_or(false)
257}
258
259fn parse_env<T: std::str::FromStr>(var: &str) -> Option<T> {
260 env::var(var).ok().and_then(|v| v.parse().ok())
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_cloud_config_default() {
269 let config = CloudConfig::default();
270 assert!(!config.provider.use_bedrock);
271 assert!(!config.provider.use_vertex);
272 assert!(!config.provider.use_foundry);
273 assert_eq!(config.tokens.max_output, DEFAULT_MAX_TOKENS);
274 assert_eq!(config.tokens.max_thinking, MIN_THINKING_BUDGET);
275 }
276
277 #[test]
278 fn test_token_limits_default() {
279 let limits = TokenLimits::default();
280 assert_eq!(limits.max_output, DEFAULT_MAX_TOKENS);
281 assert_eq!(limits.max_thinking, MIN_THINKING_BUDGET);
282 }
283
284 #[test]
285 fn test_vertex_region_for_model() {
286 let mut config = VertexConfig::default();
287 config
288 .model_regions
289 .insert("claude-4-5-sonnet".into(), "us-east5".into());
290
291 assert_eq!(
292 config.region_for_model("claude-4-5-sonnet@20250929"),
293 Some("us-east5")
294 );
295 }
296}