claude_code_switcher/templates/
kat_coder.rs1use 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#[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 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#[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 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
211fn get_kat_coder_endpoint_id() -> Result<String> {
213 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 !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 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 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
259pub 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
265pub fn create_kat_coder_air_template(api_key: &str, scope: &SnapshotScope) -> ClaudeSettings {
267 let template = KatCoderAirTemplate;
268 template.create_settings(api_key, scope)
269}