use crate::{settings::ClaudeSettings, snapshots::SnapshotScope};
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub trait Template {
fn template_type(&self) -> TemplateType;
fn env_var_names(&self) -> Vec<&'static str>;
fn create_settings(&self, api_key: &str, scope: &SnapshotScope) -> ClaudeSettings;
fn display_name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn api_key_url(&self) -> Option<&'static str> {
None
}
fn api_host(&self) -> Option<&'static str> {
None
}
fn requires_additional_config(&self) -> bool {
false
}
fn get_additional_config(&self) -> Result<HashMap<String, String>> {
Ok(HashMap::new())
}
fn has_variants(&self) -> bool {
false
}
fn get_variants() -> Result<Vec<Self>>
where
Self: Sized,
{
Ok(Vec::new())
}
fn create_interactively() -> Result<Self>
where
Self: Sized,
{
Err(anyhow!(
"This template does not support interactive creation"
))
}
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum TemplateType {
DeepSeek,
Zai,
KatCoder,
Kimi, Longcat,
Fishtrip,
MiniMax,
SeedCode,
Zenmux,
Duojie,
AnyRouter,
OpenRouter,
}
impl<'de> Deserialize<'de> for TemplateType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"KatCoderPro" | "KatCoderAir" => Ok(TemplateType::KatCoder),
"DeepSeek" => Ok(TemplateType::DeepSeek),
"Zai" => Ok(TemplateType::Zai),
"K2" | "K2Thinking" => Ok(TemplateType::Kimi), "KatCoder" => Ok(TemplateType::KatCoder),
"Kimi" => Ok(TemplateType::Kimi),
"Longcat" => Ok(TemplateType::Longcat),
"Fishtrip" => Ok(TemplateType::Fishtrip),
"MiniMax" => Ok(TemplateType::MiniMax),
"SeedCode" => Ok(TemplateType::SeedCode),
"Zenmux" => Ok(TemplateType::Zenmux),
"Duojie" => Ok(TemplateType::Duojie),
"AnyRouter" => Ok(TemplateType::AnyRouter),
"OpenRouter" => Ok(TemplateType::OpenRouter),
_ => Err(serde::de::Error::custom(format!(
"unknown template type: {}",
s
))),
}
}
}
impl std::str::FromStr for TemplateType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"deepseek" | "ds" => Ok(TemplateType::DeepSeek),
"glm" | "zhipu" | "zai" | "zai-china" | "zai-ch" | "zai-international" | "zai-int" => {
Ok(TemplateType::Zai)
}
"k2" | "moonshot" | "k2-thinking" | "k2thinking" | "kimi" | "kimi-for-coding" => {
Ok(TemplateType::Kimi)
}
"kat-coder" | "katcoder" | "kat" => Ok(TemplateType::KatCoder), "kat-coder-pro" | "katcoder-pro" | "katpro" => Ok(TemplateType::KatCoder), "kat-coder-air" | "katcoder-air" | "katair" => Ok(TemplateType::KatCoder), "longcat" => Ok(TemplateType::Longcat),
"fishtrip" | "fish" => Ok(TemplateType::Fishtrip),
"minimax"
| "minimax-anthropic"
| "minimax-china"
| "minimax-ch"
| "minimax-international"
| "minimax-int"
| "minimax-io" => Ok(TemplateType::MiniMax),
"seed-code" | "seedcode" | "seed_code" => Ok(TemplateType::SeedCode),
"zenmux" => Ok(TemplateType::Zenmux),
"duojie" | "dj" => Ok(TemplateType::Duojie),
"anyrouter" | "anyr" | "ar" | "anyrouter-china" | "anyrouter-fast" | "anyr-china"
| "anyr-fast" | "ar-china" | "ar-fast" | "anyrouter-fallback" | "anyrouter-stable"
| "anyr-fallback" | "anyr-stable" | "ar-fallback" | "ar-stable" => {
Ok(TemplateType::AnyRouter)
}
"openrouter" | "or" => Ok(TemplateType::OpenRouter),
_ => Err(anyhow!(
"Unknown template: {}. Available templates: deepseek, glm, k2, k2-thinking, kat-coder, kimi, longcat, fishtrip, fish, minimax, seed-code, zenmux, duojie, anyrouter, openrouter",
s
)),
}
}
}
impl std::fmt::Display for TemplateType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TemplateType::DeepSeek => write!(f, "deepseek"),
TemplateType::Zai => write!(f, "zai"),
TemplateType::KatCoder => write!(f, "kat-coder"),
TemplateType::Kimi => write!(f, "kimi"), TemplateType::Longcat => write!(f, "longcat"),
TemplateType::Fishtrip => write!(f, "fishtrip"),
TemplateType::MiniMax => write!(f, "minimax"),
TemplateType::SeedCode => write!(f, "seed-code"),
TemplateType::Zenmux => write!(f, "zenmux"),
TemplateType::Duojie => write!(f, "duojie"),
TemplateType::AnyRouter => write!(f, "anyrouter"),
TemplateType::OpenRouter => write!(f, "openrouter"),
}
}
}
pub fn get_template_type(template_str: &str) -> Result<TemplateType> {
template_str.parse()
}
pub fn get_all_templates() -> Vec<TemplateType> {
vec![
TemplateType::DeepSeek,
TemplateType::Zai,
TemplateType::KatCoder,
TemplateType::Kimi, TemplateType::Longcat,
TemplateType::Fishtrip,
TemplateType::MiniMax,
TemplateType::SeedCode,
TemplateType::Zenmux,
TemplateType::Duojie,
TemplateType::AnyRouter,
TemplateType::OpenRouter,
]
}
pub fn get_env_var_names(template_type: &TemplateType) -> Vec<&'static str> {
let template_instance = get_template_instance(template_type);
template_instance.env_var_names()
}
pub fn get_template_instance_with_input(
template_type: &TemplateType,
input: &str,
) -> Box<dyn Template> {
match template_type {
TemplateType::DeepSeek => Box::new(deepseek::DeepSeekTemplate),
TemplateType::Zai => {
match input.to_lowercase().as_str() {
"zai-china" | "zai-ch" => Box::new(zai::ZaiTemplate::china()),
"zai-international" | "zai-int" => Box::new(zai::ZaiTemplate::international()),
_ => Box::new(zai::ZaiTemplate::china()), }
}
TemplateType::KatCoder => {
match input.to_lowercase().as_str() {
"kat-coder-pro" | "katcoder-pro" | "katpro" => {
Box::new(kat_coder::KatCoderTemplate::pro())
}
"kat-coder-air" | "katcoder-air" | "katair" => {
Box::new(kat_coder::KatCoderTemplate::air())
}
_ => Box::new(kat_coder::KatCoderTemplate::pro()), }
}
TemplateType::Kimi => {
match input.to_lowercase().as_str() {
"k2" | "moonshot" => Box::new(kimi::KimiTemplate::k2()),
"k2-thinking" | "k2thinking" => Box::new(kimi::KimiTemplate::k2_thinking()),
"kimi" | "kimi-for-coding" => Box::new(kimi::KimiTemplate::kimi_for_coding()),
_ => Box::new(kimi::KimiTemplate::k2()), }
}
TemplateType::Longcat => Box::new(longcat::LongcatTemplate),
TemplateType::Fishtrip => Box::new(fishtrip::FishtripTemplate),
TemplateType::MiniMax => {
match input.to_lowercase().as_str() {
"minimax-international" | "minimax-int" | "minimax-io" => {
Box::new(minimax::MiniMaxTemplate::international())
}
_ => Box::new(minimax::MiniMaxTemplate::china()), }
}
TemplateType::SeedCode => Box::new(seed_code::SeedCodeTemplate),
TemplateType::Zenmux => Box::new(zenmux::ZenmuxTemplate),
TemplateType::Duojie => Box::new(duojie::DuojieTemplate),
TemplateType::AnyRouter => {
match input.to_lowercase().as_str() {
"anyrouter-china" | "anyrouter-fast" | "anyr-china" | "anyr-fast" | "ar-china"
| "ar-fast" => Box::new(anyrouter::AnyRouterTemplate::china()),
"anyrouter-fallback" | "anyrouter-stable" | "anyr-fallback" | "anyr-stable"
| "ar-fallback" | "ar-stable" => Box::new(anyrouter::AnyRouterTemplate::fallback()),
_ => Box::new(anyrouter::AnyRouterTemplate::china()), }
}
TemplateType::OpenRouter => Box::new(openrouter::OpenRouterTemplate::with_model(
"anthropic/claude-3.5-sonnet",
)),
}
}
pub fn get_template_instance(template_type: &TemplateType) -> Box<dyn Template> {
get_template_instance_with_input(template_type, "")
}
pub fn resolve_template_cli(
template_type: &TemplateType,
target: &str,
) -> Result<Box<dyn Template>> {
let initial = get_template_instance_with_input(template_type, target);
if !initial.has_variants() || !is_generic_target(target) {
return Ok(initial);
}
let suggestions = match template_type {
TemplateType::Zai => "Use 'zai-china' or 'zai-international'",
TemplateType::KatCoder => "Use 'kat-coder-pro' or 'kat-coder-air'",
TemplateType::Kimi => "Use 'k2', 'k2-thinking', or 'moonshot'",
TemplateType::AnyRouter => "Use 'anyr-china' or 'anyr-fallback'",
TemplateType::OpenRouter => "Specify a model directly or use interactive mode",
_ => "Use a specific variant name",
};
Err(anyhow::anyhow!(
"CLI mode requires a specific variant for '{}'. {}",
target,
suggestions
))
}
pub fn resolve_template_interactive(
template_type: &TemplateType,
target: &str,
) -> Result<Box<dyn Template>> {
let initial = get_template_instance_with_input(template_type, target);
if !initial.has_variants() || !is_generic_target(target) {
return Ok(initial);
}
match template_type {
TemplateType::KatCoder => Ok(Box::new(
kat_coder::KatCoderTemplate::create_interactively()?
)),
TemplateType::Kimi => Ok(Box::new(kimi::KimiTemplate::create_interactively()?)),
TemplateType::Zai => Ok(Box::new(zai::ZaiTemplate::create_interactively()?)),
TemplateType::AnyRouter => Ok(Box::new(
anyrouter::AnyRouterTemplate::create_interactively()?,
)),
TemplateType::OpenRouter => Ok(Box::new(
openrouter::OpenRouterTemplate::create_with_model_selection()?,
)),
_ => Ok(initial),
}
}
fn is_generic_target(target: &str) -> bool {
matches!(
target.to_lowercase().as_str(),
"kat-coder"
| "katcoder"
| "kat"
| "kimi"
| "zai"
| "glm"
| "zhipu"
| "anyrouter"
| "anyr"
| "ar"
| "openrouter"
| "or"
)
}
pub fn get_template(template_type: &TemplateType) -> fn(&str, &SnapshotScope) -> ClaudeSettings {
match template_type {
TemplateType::DeepSeek => create_deepseek_template,
TemplateType::Zai => create_zai_template,
TemplateType::KatCoder => create_kat_coder_template,
TemplateType::Kimi => create_k2_template, TemplateType::Longcat => create_longcat_template,
TemplateType::Fishtrip => create_fishtrip_template,
TemplateType::MiniMax => create_minimax_template,
TemplateType::SeedCode => create_seed_code_template,
TemplateType::Zenmux => create_zenmux_template,
TemplateType::Duojie => create_duojie_template,
TemplateType::AnyRouter => create_anyrouter_template,
TemplateType::OpenRouter => create_openrouter_template,
}
}
pub mod anyrouter;
pub mod deepseek;
pub mod duojie;
pub mod fishtrip;
pub mod kat_coder;
pub mod kimi; pub mod longcat;
pub mod minimax;
pub mod openrouter;
pub mod seed_code;
pub mod zai;
pub mod zenmux;
pub use anyrouter::*;
pub use deepseek::*;
pub use duojie::*;
pub use fishtrip::*;
pub use kat_coder::*;
pub use kimi::*; pub use longcat::*;
pub use minimax::*;
pub use openrouter::*;
pub use seed_code::*;
pub use zai::*;
pub use zenmux::*;