claude_code_switcher/templates/
mod.rs

1//! Template module for AI provider configurations
2//!
3//! This module provides a modular approach to managing different AI provider templates.
4//! Each template is implemented as a separate module with the Template trait.
5
6use crate::{settings::ClaudeSettings, snapshots::SnapshotScope};
7use anyhow::{Result, anyhow};
8
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Trait that all AI provider templates must implement
13pub trait Template {
14    /// Get the template type identifier
15    fn template_type(&self) -> TemplateType;
16
17    /// Get the environment variable name for the API key
18    fn env_var_name(&self) -> &'static str;
19
20    /// Create Claude settings for this template
21    fn create_settings(&self, api_key: &str, scope: &SnapshotScope) -> ClaudeSettings;
22
23    /// Get display name for the template
24    fn display_name(&self) -> &'static str;
25
26    /// Get description for the template
27    fn description(&self) -> &'static str;
28
29    /// Get API key acquisition URL (if available)
30    fn api_key_url(&self) -> Option<&'static str> {
31        None
32    }
33
34    /// Check if this template requires additional configuration (like endpoint ID)
35    fn requires_additional_config(&self) -> bool {
36        false
37    }
38
39    /// Get additional configuration if needed
40    fn get_additional_config(&self) -> Result<HashMap<String, String>> {
41        Ok(HashMap::new())
42    }
43
44    /// Check if this template has sub-variants (like Pro/Air versions)
45    fn has_variants(&self) -> bool {
46        false
47    }
48
49    /// Get available variants if this template supports them
50    fn get_variants() -> Result<Vec<Self>>
51    where
52        Self: Sized,
53    {
54        Ok(Vec::new())
55    }
56
57    /// Create a template instance interactively (for templates with variants)
58    fn create_interactively() -> Result<Self>
59    where
60        Self: Sized,
61    {
62        Err(anyhow!(
63            "This template does not support interactive creation"
64        ))
65    }
66}
67
68/// Type of AI provider template
69#[derive(Debug, Clone, Serialize, PartialEq)]
70pub enum TemplateType {
71    DeepSeek,
72    Zai,
73    KatCoder,
74    Kimi, // Unified Moonshot services (K2, K2 Thinking, Kimi For Coding)
75    Longcat,
76    MiniMax,
77    SeedCode,
78}
79
80impl<'de> Deserialize<'de> for TemplateType {
81    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
82    where
83        D: serde::Deserializer<'de>,
84    {
85        let s = String::deserialize(deserializer)?;
86
87        // Handle backward compatibility for old variant names
88        match s.as_str() {
89            "KatCoderPro" | "KatCoderAir" => Ok(TemplateType::KatCoder),
90            "DeepSeek" => Ok(TemplateType::DeepSeek),
91            "Zai" => Ok(TemplateType::Zai),
92            "K2" | "K2Thinking" => Ok(TemplateType::Kimi), // Backward compatibility
93            "KatCoder" => Ok(TemplateType::KatCoder),
94            "Kimi" => Ok(TemplateType::Kimi),
95            "Longcat" => Ok(TemplateType::Longcat),
96            "MiniMax" => Ok(TemplateType::MiniMax),
97            "SeedCode" => Ok(TemplateType::SeedCode),
98            _ => Err(serde::de::Error::custom(format!(
99                "unknown template type: {}",
100                s
101            ))),
102        }
103    }
104}
105
106impl std::str::FromStr for TemplateType {
107    type Err = anyhow::Error;
108
109    fn from_str(s: &str) -> Result<Self> {
110        match s.to_lowercase().as_str() {
111            "deepseek" | "ds" => Ok(TemplateType::DeepSeek),
112            "glm" | "zhipu" | "zai" | "zai-china" | "zai-ch" | "zai-international" | "zai-int" => {
113                Ok(TemplateType::Zai)
114            }
115            // K2 and K2 Thinking are now part of unified Kimi template
116            "k2" | "moonshot" | "k2-thinking" | "k2thinking" | "kimi" | "kimi-for-coding" => {
117                Ok(TemplateType::Kimi)
118            }
119            "kat-coder" | "katcoder" | "kat" => Ok(TemplateType::KatCoder), // Unified KatCoder
120            "kat-coder-pro" | "katcoder-pro" | "katpro" => Ok(TemplateType::KatCoder), // Points to KatCoder with variant selection
121            "kat-coder-air" | "katcoder-air" | "katair" => Ok(TemplateType::KatCoder), // Points to KatCoder with variant selection
122            "longcat" => Ok(TemplateType::Longcat),
123            "minimax" | "minimax-anthropic" => Ok(TemplateType::MiniMax),
124            "seed-code" | "seedcode" | "seed_code" => Ok(TemplateType::SeedCode),
125            _ => Err(anyhow!(
126                "Unknown template: {}. Available templates: deepseek, glm, k2, k2-thinking, kat-coder, kimi, longcat, minimax, seed-code",
127                s
128            )),
129        }
130    }
131}
132
133impl std::fmt::Display for TemplateType {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        match self {
136            TemplateType::DeepSeek => write!(f, "deepseek"),
137            TemplateType::Zai => write!(f, "zai"),
138            TemplateType::KatCoder => write!(f, "kat-coder"),
139            TemplateType::Kimi => write!(f, "kimi"), // Unified Moonshot services
140            TemplateType::Longcat => write!(f, "longcat"),
141            TemplateType::MiniMax => write!(f, "minimax"),
142            TemplateType::SeedCode => write!(f, "seed-code"),
143        }
144    }
145}
146
147/// Get template type from string
148pub fn get_template_type(template_str: &str) -> Result<TemplateType> {
149    template_str.parse()
150}
151
152/// Get all available template types
153pub fn get_all_templates() -> Vec<TemplateType> {
154    vec![
155        TemplateType::DeepSeek,
156        TemplateType::Zai,
157        TemplateType::KatCoder,
158        TemplateType::Kimi, // Unified Moonshot services
159        TemplateType::Longcat,
160        TemplateType::MiniMax,
161        TemplateType::SeedCode,
162    ]
163}
164
165/// Get the environment variable name for a template type
166/// Note: For Kimi template, this returns a default. The actual template instance
167/// will determine the correct env var based on the specific variant.
168pub fn get_env_var_name(template_type: &TemplateType) -> &'static str {
169    match template_type {
170        TemplateType::DeepSeek => "DEEPSEEK_API_KEY",
171        TemplateType::Zai => "Z_AI_API_KEY",
172        TemplateType::KatCoder => "KAT_CODER_API_KEY",
173        TemplateType::Kimi => "MOONSHOT_API_KEY", // Default for K2 variants
174        TemplateType::Longcat => "LONGCAT_API_KEY",
175        TemplateType::MiniMax => "MINIMAX_API_KEY",
176        TemplateType::SeedCode => "ARK_API_KEY",
177    }
178}
179
180/// Get a template instance by type and original input string
181pub fn get_template_instance_with_input(
182    template_type: &TemplateType,
183    input: &str,
184) -> Box<dyn Template> {
185    match template_type {
186        TemplateType::DeepSeek => Box::new(deepseek::DeepSeekTemplate),
187        TemplateType::Zai => {
188            // Check if specific region was requested
189            match input.to_lowercase().as_str() {
190                "zai-china" | "zai-ch" => Box::new(zai::ZaiTemplate::china()),
191                "zai-international" | "zai-int" => Box::new(zai::ZaiTemplate::international()),
192                _ => Box::new(zai::ZaiTemplate::china()), // Default to China for general "zai"
193            }
194        }
195        TemplateType::KatCoder => {
196            // Check if specific variant was requested
197            match input.to_lowercase().as_str() {
198                "kat-coder-pro" | "katcoder-pro" | "katpro" => {
199                    Box::new(kat_coder::KatCoderTemplate::pro())
200                }
201                "kat-coder-air" | "katcoder-air" | "katair" => {
202                    Box::new(kat_coder::KatCoderTemplate::air())
203                }
204                _ => Box::new(kat_coder::KatCoderTemplate::pro()), // Default to Pro for general "kat-coder"
205            }
206        }
207        TemplateType::Kimi => {
208            // Check if specific Moonshot service was requested
209            match input.to_lowercase().as_str() {
210                "k2" | "moonshot" => Box::new(kimi::KimiTemplate::k2()),
211                "k2-thinking" | "k2thinking" => Box::new(kimi::KimiTemplate::k2_thinking()),
212                "kimi" | "kimi-for-coding" => Box::new(kimi::KimiTemplate::kimi_for_coding()),
213                _ => Box::new(kimi::KimiTemplate::k2()), // Default to K2 for general "kimi"
214            }
215        }
216        TemplateType::Longcat => Box::new(longcat::LongcatTemplate),
217        TemplateType::MiniMax => Box::new(minimax::MiniMaxTemplate),
218        TemplateType::SeedCode => Box::new(seed_code::SeedCodeTemplate),
219    }
220}
221
222/// Get a template instance by type (for backward compatibility)
223pub fn get_template_instance(template_type: &TemplateType) -> Box<dyn Template> {
224    get_template_instance_with_input(template_type, "")
225}
226
227/// Legacy compatibility function - creates a settings function for backwards compatibility
228pub fn get_template(template_type: &TemplateType) -> fn(&str, &SnapshotScope) -> ClaudeSettings {
229    match template_type {
230        TemplateType::DeepSeek => create_deepseek_template,
231        TemplateType::Zai => create_zai_template,
232        TemplateType::KatCoder => create_kat_coder_template,
233        TemplateType::Kimi => create_k2_template, // Default to K2 for backward compatibility
234        TemplateType::Longcat => create_longcat_template,
235        TemplateType::MiniMax => create_minimax_template,
236        TemplateType::SeedCode => create_seed_code_template,
237    }
238}
239
240// Import all template modules
241pub mod deepseek;
242pub mod kat_coder;
243pub mod kimi; // Unified module for all Moonshot services
244pub mod longcat;
245pub mod minimax;
246pub mod seed_code;
247pub mod zai;
248
249// Re-export for backward compatibility
250pub use deepseek::*;
251pub use kat_coder::*;
252pub use kimi::*; // Includes legacy k2 functions
253pub use longcat::*;
254pub use minimax::*;
255pub use seed_code::*;
256pub use zai::*;