claude_code_switcher/templates/
kat_coder.rs

1//! KatCoder (WanQing) AI provider template implementation
2
3use crate::{
4    settings::{
5        ClaudeSettings, EndpointConfig, HTTPConfig, ModelConfig, Permissions, ProviderConfig,
6    },
7    snapshots::SnapshotScope,
8    templates::Template,
9};
10use anyhow::{Result, anyhow};
11use atty;
12use inquire::{Confirm, Text};
13use std::collections::HashMap;
14
15/// KatCoder Pro AI provider template
16#[derive(Debug, Clone)]
17pub struct KatCoderProTemplate;
18
19impl Template for KatCoderProTemplate {
20    fn template_type(&self) -> crate::templates::TemplateType {
21        crate::templates::TemplateType::KatCoderPro
22    }
23
24    fn env_var_name(&self) -> &'static str {
25        "KAT_CODER_API_KEY"
26    }
27
28    fn display_name(&self) -> &'static str {
29        "KatCoder Pro (WanQing)"
30    }
31
32    fn description(&self) -> &'static str {
33        "WanQing KAT-Coder Pro V1 - Professional coding AI with advanced capabilities"
34    }
35
36    fn requires_additional_config(&self) -> bool {
37        true
38    }
39
40    fn get_additional_config(&self) -> Result<HashMap<String, String>> {
41        let endpoint_id = get_kat_coder_endpoint_id()?;
42        let mut config = HashMap::new();
43        config.insert("endpoint_id".to_string(), endpoint_id);
44        Ok(config)
45    }
46
47    fn create_settings(&self, api_key: &str, scope: &SnapshotScope) -> ClaudeSettings {
48        let mut settings = ClaudeSettings::new();
49
50        // Get endpoint ID for KatCoder
51        let endpoint_id = get_kat_coder_endpoint_id().unwrap_or_else(|_| "default".to_string());
52        let base_url = format!(
53            "https://wanqing.streamlakeapi.com/api/gateway/v1/endpoints/{}/claude-code-proxy",
54            endpoint_id
55        );
56
57        if matches!(scope, SnapshotScope::Common | SnapshotScope::All) {
58            settings.provider = Some(ProviderConfig {
59                id: "wanqing".to_string(),
60                metadata: None,
61            });
62
63            settings.model = Some(ModelConfig {
64                name: "KAT-Coder-Pro-V1".to_string(),
65                metadata: None,
66            });
67
68            settings.endpoint = Some(EndpointConfig {
69                id: "wanqing".to_string(),
70                api_base: base_url.clone(),
71                api_key: None,
72                endpoint_id: Some(endpoint_id.clone()),
73                metadata: None,
74            });
75
76            settings.http = Some(HTTPConfig {
77                timeout_ms: Some(30000),
78                max_retries: Some(3),
79                retry_backoff_factor: Some(2.0),
80            });
81
82            settings.permissions = Some(Permissions {
83                allow_network_access: Some(true),
84                allow_filesystem_access: Some(true),
85                allow_command_execution: Some(false),
86            });
87        }
88
89        if matches!(scope, SnapshotScope::Env | SnapshotScope::All) {
90            let mut env = HashMap::new();
91            env.insert("ANTHROPIC_AUTH_TOKEN".to_string(), api_key.to_string());
92            env.insert("ANTHROPIC_BASE_URL".to_string(), base_url);
93            env.insert(
94                "ANTHROPIC_MODEL".to_string(),
95                "KAT-Coder-Pro-V1".to_string(),
96            );
97            env.insert(
98                "ANTHROPIC_SMALL_FAST_MODEL".to_string(),
99                "KAT-Coder-Pro-V1".to_string(),
100            );
101            env.insert("API_TIMEOUT_MS".to_string(), "600000".to_string());
102            env.insert(
103                "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC".to_string(),
104                "1".to_string(),
105            );
106            settings.environment = Some(env);
107        }
108
109        settings
110    }
111}
112
113/// KatCoder Air AI provider template
114#[derive(Debug, Clone)]
115pub struct KatCoderAirTemplate;
116
117impl Template for KatCoderAirTemplate {
118    fn template_type(&self) -> crate::templates::TemplateType {
119        crate::templates::TemplateType::KatCoderAir
120    }
121
122    fn env_var_name(&self) -> &'static str {
123        "KAT_CODER_API_KEY"
124    }
125
126    fn display_name(&self) -> &'static str {
127        "KatCoder Air (WanQing)"
128    }
129
130    fn description(&self) -> &'static str {
131        "WanQing KAT-Coder Air V1 - Lightweight coding AI with fast response"
132    }
133
134    fn requires_additional_config(&self) -> bool {
135        true
136    }
137
138    fn get_additional_config(&self) -> Result<HashMap<String, String>> {
139        let endpoint_id = get_kat_coder_endpoint_id()?;
140        let mut config = HashMap::new();
141        config.insert("endpoint_id".to_string(), endpoint_id);
142        Ok(config)
143    }
144
145    fn create_settings(&self, api_key: &str, scope: &SnapshotScope) -> ClaudeSettings {
146        let mut settings = ClaudeSettings::new();
147
148        // Get endpoint ID for KatCoder
149        let endpoint_id = get_kat_coder_endpoint_id().unwrap_or_else(|_| "default".to_string());
150        let base_url = format!(
151            "https://wanqing.streamlakeapi.com/api/gateway/v1/endpoints/{}/claude-code-proxy",
152            endpoint_id
153        );
154
155        if matches!(scope, SnapshotScope::Common | SnapshotScope::All) {
156            settings.provider = Some(ProviderConfig {
157                id: "wanqing".to_string(),
158                metadata: None,
159            });
160
161            settings.model = Some(ModelConfig {
162                name: "KAT-Coder-Air-V1".to_string(),
163                metadata: None,
164            });
165
166            settings.endpoint = Some(EndpointConfig {
167                id: "wanqing".to_string(),
168                api_base: base_url.clone(),
169                api_key: None,
170                endpoint_id: Some(endpoint_id.clone()),
171                metadata: None,
172            });
173
174            settings.http = Some(HTTPConfig {
175                timeout_ms: Some(30000),
176                max_retries: Some(3),
177                retry_backoff_factor: Some(2.0),
178            });
179
180            settings.permissions = Some(Permissions {
181                allow_network_access: Some(true),
182                allow_filesystem_access: Some(true),
183                allow_command_execution: Some(false),
184            });
185        }
186
187        if matches!(scope, SnapshotScope::Env | SnapshotScope::All) {
188            let mut env = HashMap::new();
189            env.insert("ANTHROPIC_AUTH_TOKEN".to_string(), api_key.to_string());
190            env.insert("ANTHROPIC_BASE_URL".to_string(), base_url);
191            env.insert(
192                "ANTHROPIC_MODEL".to_string(),
193                "KAT-Coder-Air-V1".to_string(),
194            );
195            env.insert(
196                "ANTHROPIC_SMALL_FAST_MODEL".to_string(),
197                "KAT-Coder-Air-V1".to_string(),
198            );
199            env.insert("API_TIMEOUT_MS".to_string(), "600000".to_string());
200            env.insert(
201                "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC".to_string(),
202                "1".to_string(),
203            );
204            settings.environment = Some(env);
205        }
206
207        settings
208    }
209}
210
211/// Get KatCoder endpoint ID from environment or prompt user
212fn get_kat_coder_endpoint_id() -> Result<String> {
213    // Try to get from environment first
214    let env_var = "WANQING_ENDPOINT_ID";
215
216    if let Ok(id) = std::env::var(env_var) {
217        println!(
218            "  ✓ Using endpoint ID from environment variable {}",
219            env_var
220        );
221        return Ok(id);
222    }
223
224    // If not found and we're in non-interactive mode, error
225    if !atty::is(atty::Stream::Stdin) {
226        return Err(anyhow!(
227            "Endpoint ID required for kat-coder template. Set {} environment variable or use interactive mode.",
228            env_var
229        ));
230    }
231
232    // Prompt user for endpoint ID
233    let prompt = "Enter WanQing endpoint ID (format: ep-xxx-xxx):";
234    let endpoint_id = Text::new(prompt)
235        .prompt()
236        .map_err(|e| anyhow!("Failed to read input: {}", e))?;
237
238    if endpoint_id.trim().is_empty() {
239        return Err(anyhow!("Endpoint ID cannot be empty"));
240    }
241
242    // Ask if user wants to save to environment
243    let save_env = Confirm::new(&format!(
244        "Save {} to environment variable for future use?",
245        env_var
246    ))
247    .with_default(false)
248    .prompt()
249    .unwrap_or(false);
250
251    if save_env {
252        println!("  💡 To save permanently, add this to your shell profile:");
253        println!("     export {}=\"***\"", env_var);
254    }
255
256    Ok(endpoint_id)
257}
258
259/// Create KatCoder Pro template settings (legacy compatibility function)
260pub fn create_kat_coder_pro_template(api_key: &str, scope: &SnapshotScope) -> ClaudeSettings {
261    let template = KatCoderProTemplate;
262    template.create_settings(api_key, scope)
263}
264
265/// Create KatCoder Air template settings (legacy compatibility function)
266pub fn create_kat_coder_air_template(api_key: &str, scope: &SnapshotScope) -> ClaudeSettings {
267    let template = KatCoderAirTemplate;
268    template.create_settings(api_key, scope)
269}