cc_switch/config/
config.rs1use anyhow::{Context, Result};
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5use crate::config::types::Configuration;
6
7type EnvVarMap = BTreeMap<String, String>;
9type EnvVarTuple = (String, String);
11type EnvVarTuples = Vec<EnvVarTuple>;
13
14#[derive(Default, Clone)]
18pub struct EnvironmentConfig {
19 pub env_vars: EnvVarMap,
21}
22
23impl EnvironmentConfig {
24 pub fn from_config(config: &Configuration) -> Self {
32 let mut env_vars = EnvVarMap::new();
33
34 env_vars.insert("ANTHROPIC_AUTH_TOKEN".to_string(), config.token.clone());
36 env_vars.insert("ANTHROPIC_BASE_URL".to_string(), config.url.clone());
37
38 if let Some(model) = &config.model
40 && !model.is_empty()
41 {
42 env_vars.insert("ANTHROPIC_MODEL".to_string(), model.clone());
43 }
44
45 if let Some(small_fast_model) = &config.small_fast_model
46 && !small_fast_model.is_empty()
47 {
48 env_vars.insert(
49 "ANTHROPIC_SMALL_FAST_MODEL".to_string(),
50 small_fast_model.clone(),
51 );
52 }
53
54 if let Some(max_thinking_tokens) = config.max_thinking_tokens {
56 env_vars.insert(
57 "ANTHROPIC_MAX_THINKING_TOKENS".to_string(),
58 max_thinking_tokens.to_string(),
59 );
60 }
61
62 if let Some(timeout) = config.api_timeout_ms {
64 env_vars.insert("API_TIMEOUT_MS".to_string(), timeout.to_string());
65 }
66
67 if let Some(flag) = config.claude_code_disable_nonessential_traffic {
69 env_vars.insert(
70 "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC".to_string(),
71 flag.to_string(),
72 );
73 }
74
75 if let Some(model) = &config.anthropic_default_sonnet_model
77 && !model.is_empty()
78 {
79 env_vars.insert("ANTHROPIC_DEFAULT_SONNET_MODEL".to_string(), model.clone());
80 }
81
82 if let Some(model) = &config.anthropic_default_opus_model
84 && !model.is_empty()
85 {
86 env_vars.insert("ANTHROPIC_DEFAULT_OPUS_MODEL".to_string(), model.clone());
87 }
88
89 if let Some(model) = &config.anthropic_default_haiku_model
91 && !model.is_empty()
92 {
93 env_vars.insert("ANTHROPIC_DEFAULT_HAIKU_MODEL".to_string(), model.clone());
94 }
95
96 if let Some(model) = &config.claude_code_subagent_model
98 && !model.is_empty()
99 {
100 env_vars.insert("CLAUDE_CODE_SUBAGENT_MODEL".to_string(), model.clone());
101 }
102
103 if let Some(flag) = config.claude_code_disable_nonstreaming_fallback {
105 env_vars.insert(
106 "CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK".to_string(),
107 flag.to_string(),
108 );
109 }
110
111 if let Some(level) = &config.claude_code_effort_level
113 && !level.is_empty()
114 {
115 env_vars.insert("CLAUDE_CODE_EFFORT_LEVEL".to_string(), level.clone());
116 }
117
118 EnvironmentConfig { env_vars }
119 }
120
121 pub fn empty() -> Self {
123 EnvironmentConfig {
124 env_vars: EnvVarMap::new(),
125 }
126 }
127
128 pub fn with_alias(mut self, alias: &str) -> Self {
131 self.env_vars
132 .insert("CC_SWITCH_CURRENT_ALIAS".to_string(), alias.to_string());
133 self
134 }
135
136 pub fn as_env_tuples(&self) -> EnvVarTuples {
139 self.env_vars
140 .iter()
141 .map(|(k, v)| (k.clone(), v.clone()))
142 .collect()
143 }
144}
145
146pub fn get_config_storage_path() -> Result<PathBuf> {
153 let home_dir = dirs::home_dir().context("Could not find home directory")?;
154 Ok(home_dir.join(".claude").join("cc_auto_switch_setting.json"))
155}
156
157pub fn validate_alias_name(alias_name: &str) -> Result<()> {
165 if alias_name.is_empty() {
166 anyhow::bail!("Alias name cannot be empty");
167 }
168 if alias_name == "cc" {
169 anyhow::bail!("Alias name 'cc' is reserved and cannot be used");
170 }
171 if alias_name.chars().any(|c| c.is_whitespace()) {
172 anyhow::bail!("Alias name cannot contain whitespace");
173 }
174 Ok(())
175}