Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions crates/openfang-runtime/src/drivers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ pub mod openai;
use crate::llm_driver::{DriverConfig, LlmDriver, LlmError};
use openfang_types::model_catalog::{
AI21_BASE_URL, ANTHROPIC_BASE_URL, CEREBRAS_BASE_URL, COHERE_BASE_URL, DEEPSEEK_BASE_URL,
FIREWORKS_BASE_URL, GEMINI_BASE_URL, GROQ_BASE_URL, HUGGINGFACE_BASE_URL, LEMONADE_BASE_URL,
LMSTUDIO_BASE_URL,
MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, OLLAMA_BASE_URL, OPENAI_BASE_URL,
OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL, QWEN_BASE_URL,
REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, VLLM_BASE_URL,
VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
FIREWORKS_BASE_URL, GEMINI_BASE_URL, GROQ_BASE_URL, HUGGINGFACE_BASE_URL, KIMI_CODING_BASE_URL,
LEMONADE_BASE_URL, LMSTUDIO_BASE_URL, MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL,
OLLAMA_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL,
QWEN_BASE_URL, REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL,
VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
};
use std::sync::Arc;
Expand Down Expand Up @@ -155,6 +154,11 @@ fn provider_defaults(provider: &str) -> Option<ProviderDefaults> {
api_key_env: "MOONSHOT_API_KEY",
key_required: true,
}),
"kimi_coding" => Some(ProviderDefaults {
base_url: KIMI_CODING_BASE_URL,
api_key_env: "KIMI_API_KEY",
key_required: true,
}),
"qwen" | "dashscope" | "model_studio" => Some(ProviderDefaults {
base_url: QWEN_BASE_URL,
api_key_env: "DASHSCOPE_API_KEY",
Expand Down Expand Up @@ -251,6 +255,22 @@ pub fn create_driver(config: &DriverConfig) -> Result<Arc<dyn LlmDriver>, LlmErr
return Ok(Arc::new(anthropic::AnthropicDriver::new(api_key, base_url)));
}

// Kimi for Code — Anthropic-compatible endpoint
if provider == "kimi_coding" {
let api_key = config
.api_key
.clone()
.or_else(|| std::env::var("KIMI_API_KEY").ok())
.ok_or_else(|| {
LlmError::MissingApiKey("Set KIMI_API_KEY environment variable".to_string())
})?;
let base_url = config
.base_url
.clone()
.unwrap_or_else(|| KIMI_CODING_BASE_URL.to_string());
return Ok(Arc::new(anthropic::AnthropicDriver::new(api_key, base_url)));
}

// Gemini uses a different API format — special case
if provider == "gemini" || provider == "google" {
let api_key = config
Expand Down Expand Up @@ -444,6 +464,7 @@ pub fn known_providers() -> &'static [&'static str] {
"replicate",
"github-copilot",
"moonshot",
"kimi_coding",
"qwen",
"minimax",
"zhipu",
Expand Down Expand Up @@ -545,11 +566,12 @@ mod tests {
assert!(providers.contains(&"minimax"));
assert!(providers.contains(&"zhipu"));
assert!(providers.contains(&"zhipu_coding"));
assert!(providers.contains(&"kimi_coding"));
assert!(providers.contains(&"qianfan"));
assert!(providers.contains(&"volcengine"));
assert!(providers.contains(&"codex"));
assert!(providers.contains(&"claude-code"));
assert_eq!(providers.len(), 31);
assert_eq!(providers.len(), 32);
}

#[test]
Expand Down Expand Up @@ -635,6 +657,14 @@ mod tests {
std::env::remove_var("NVIDIA_API_KEY");
}

#[test]
fn test_provider_defaults_kimi_coding() {
let d = provider_defaults("kimi_coding").unwrap();
assert_eq!(d.base_url, "https://api.kimi.com/coding");
assert_eq!(d.api_key_env, "KIMI_API_KEY");
assert!(d.key_required);
}

#[test]
fn test_custom_provider_explicit_key_with_url() {
// When api_key is explicitly passed, it should be used regardless of env var.
Expand Down
47 changes: 34 additions & 13 deletions crates/openfang-runtime/src/model_catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ use openfang_types::model_catalog::{
AuthStatus, ModelCatalogEntry, ModelTier, ProviderInfo, AI21_BASE_URL, ANTHROPIC_BASE_URL,
BEDROCK_BASE_URL, CEREBRAS_BASE_URL, COHERE_BASE_URL, DEEPSEEK_BASE_URL, FIREWORKS_BASE_URL,
GEMINI_BASE_URL, GITHUB_COPILOT_BASE_URL, GROQ_BASE_URL, HUGGINGFACE_BASE_URL,
LMSTUDIO_BASE_URL, MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, OLLAMA_BASE_URL,
LEMONADE_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL,
QIANFAN_BASE_URL, QWEN_BASE_URL,
REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, VLLM_BASE_URL,
VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, LMSTUDIO_BASE_URL, MINIMAX_BASE_URL, MISTRAL_BASE_URL,
MOONSHOT_BASE_URL, OLLAMA_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL,
QIANFAN_BASE_URL, QWEN_BASE_URL, REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL,
VENICE_BASE_URL, VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL,
ZAI_BASE_URL, ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
};
use std::collections::HashMap;

Expand Down Expand Up @@ -80,8 +79,7 @@ impl ModelCatalog {
let has_fallback = match provider.id.as_str() {
"gemini" => std::env::var("GOOGLE_API_KEY").is_ok(),
"codex" => {
std::env::var("OPENAI_API_KEY").is_ok()
|| read_codex_credential().is_some()
std::env::var("OPENAI_API_KEY").is_ok() || read_codex_credential().is_some()
}
// claude-code is handled above (before key_required check)
_ => false,
Expand Down Expand Up @@ -678,6 +676,15 @@ fn builtin_providers() -> Vec<ProviderInfo> {
auth_status: AuthStatus::Missing,
model_count: 0,
},
ProviderInfo {
id: "kimi_coding".into(),
display_name: "Kimi for Code".into(),
api_key_env: "KIMI_API_KEY".into(),
base_url: KIMI_CODING_BASE_URL.into(),
key_required: true,
auth_status: AuthStatus::Missing,
model_count: 0,
},
ProviderInfo {
id: "qianfan".into(),
display_name: "Baidu Qianfan".into(),
Expand Down Expand Up @@ -2938,6 +2945,23 @@ fn builtin_models() -> Vec<ModelCatalogEntry> {
aliases: vec!["kimi-k2.5".into()],
},
// ══════════════════════════════════════════════════════════════
// Kimi for Code (1)
// ══════════════════════════════════════════════════════════════
ModelCatalogEntry {
id: "kimi-for-coding".into(),
display_name: "Kimi For Coding".into(),
provider: "kimi_coding".into(),
tier: ModelTier::Frontier,
context_window: 262_144,
max_output_tokens: 32_768,
input_cost_per_m: 0.0, // Subscription-based (Coding Plan)
output_cost_per_m: 0.0, // Subscription-based (Coding Plan)
supports_tools: true,
supports_vision: true,
supports_streaming: true,
aliases: vec![],
},
// ══════════════════════════════════════════════════════════════
// Baidu Qianfan / ERNIE (3)
// ══════════════════════════════════════════════════════════════
ModelCatalogEntry {
Expand Down Expand Up @@ -3293,7 +3317,7 @@ mod tests {
#[test]
fn test_catalog_has_providers() {
let catalog = ModelCatalog::new();
assert_eq!(catalog.list_providers().len(), 36);
assert_eq!(catalog.list_providers().len(), 37);
}

#[test]
Expand Down Expand Up @@ -3328,10 +3352,7 @@ mod tests {
#[test]
fn test_resolve_alias() {
let catalog = ModelCatalog::new();
assert_eq!(
catalog.resolve_alias("sonnet"),
Some("claude-sonnet-4-6")
);
assert_eq!(catalog.resolve_alias("sonnet"), Some("claude-sonnet-4-6"));
assert_eq!(
catalog.resolve_alias("haiku"),
Some("claude-haiku-4-5-20251001")
Expand Down
1 change: 1 addition & 0 deletions crates/openfang-types/src/model_catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub const ZHIPU_CODING_BASE_URL: &str = "https://open.bigmodel.cn/api/coding/paa
pub const ZAI_BASE_URL: &str = "https://api.z.ai/api/paas/v4";
pub const ZAI_CODING_BASE_URL: &str = "https://api.z.ai/api/coding/paas/v4";
pub const MOONSHOT_BASE_URL: &str = "https://api.moonshot.cn/v1";
pub const KIMI_CODING_BASE_URL: &str = "https://api.kimi.com/coding";
pub const QIANFAN_BASE_URL: &str = "https://qianfan.baidubce.com/v2";
pub const VOLCENGINE_BASE_URL: &str = "https://ark.cn-beijing.volces.com/api/v3";
pub const VOLCENGINE_CODING_BASE_URL: &str = "https://ark.cn-beijing.volces.com/api/coding/v3";
Expand Down