Skip to main content

cc_switch/config/
config.rs

1use anyhow::{Context, Result};
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5use crate::config::types::Configuration;
6
7/// Type alias for environment variable map
8type EnvVarMap = BTreeMap<String, String>;
9/// Type alias for environment variable tuples
10type EnvVarTuple = (String, String);
11/// Type alias for environment variable tuples vector
12type EnvVarTuples = Vec<EnvVarTuple>;
13
14/// Environment variable manager for API configuration
15///
16/// Handles setting environment variables for the Claude CLI process
17#[derive(Default, Clone)]
18pub struct EnvironmentConfig {
19    /// Environment variables to be set
20    pub env_vars: EnvVarMap,
21}
22
23impl EnvironmentConfig {
24    /// Create a new environment configuration from a Claude configuration
25    ///
26    /// # Arguments
27    /// * `config` - Configuration containing token, URL, and optional model settings
28    ///
29    /// # Returns
30    /// EnvironmentConfig with the appropriate environment variables set
31    pub fn from_config(config: &Configuration) -> Self {
32        let mut env_vars = EnvVarMap::new();
33
34        // Set required environment variables
35        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        // Set model configurations only if provided
39        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        // Set max thinking tokens only if provided
55        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        // Set API timeout only if provided
63        if let Some(timeout) = config.api_timeout_ms {
64            env_vars.insert("API_TIMEOUT_MS".to_string(), timeout.to_string());
65        }
66
67        // Set disable nonessential traffic flag only if provided
68        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        // Set default Sonnet model only if provided
76        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        // Set default Opus model only if provided
83        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        // Set default Haiku model only if provided
90        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        // Set subagent model only if provided
97        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        // Set disable non-streaming fallback only if provided
104        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        // Set effort level only if provided
112        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    /// Create an empty environment configuration (for reset)
122    pub fn empty() -> Self {
123        EnvironmentConfig {
124            env_vars: EnvVarMap::new(),
125        }
126    }
127
128    /// Get environment variables as a Vec of (key, value) tuples
129    /// for use with Command::envs()
130    pub fn as_env_tuples(&self) -> EnvVarTuples {
131        self.env_vars
132            .iter()
133            .map(|(k, v)| (k.clone(), v.clone()))
134            .collect()
135    }
136}
137
138/// Get the path to the configuration storage file
139///
140/// Returns `~/.claude/cc_auto_switch_setting.json`
141///
142/// # Errors
143/// Returns error if home directory cannot be found
144pub fn get_config_storage_path() -> Result<PathBuf> {
145    let home_dir = dirs::home_dir().context("Could not find home directory")?;
146    Ok(home_dir.join(".claude").join("cc_auto_switch_setting.json"))
147}
148
149/// Validate alias name
150///
151/// # Arguments
152/// * `alias_name` - The alias name to validate
153///
154/// # Returns
155/// Ok(()) if valid, Err with message if invalid
156pub fn validate_alias_name(alias_name: &str) -> Result<()> {
157    if alias_name.is_empty() {
158        anyhow::bail!("Alias name cannot be empty");
159    }
160    if alias_name == "cc" {
161        anyhow::bail!("Alias name 'cc' is reserved and cannot be used");
162    }
163    if alias_name.chars().any(|c| c.is_whitespace()) {
164        anyhow::bail!("Alias name cannot contain whitespace");
165    }
166    Ok(())
167}