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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
# OpenRouter (multi-provider gateway)
# OPENROUTER_API_KEY=sk-or-...

# Z.AI
# ZAI_API_KEY=...

# Z.AI Global (coding plan endpoint)
# ZAI_GLOBAL_API_KEY=...

# Together AI
# TOGETHER_API_KEY=...

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,11 @@ Each adapter supports per-channel model overrides, DM/group policies, rate limit

---

## 27 LLM Providers — 123+ Models
## 29 LLM Providers — 130+ Models

3 native drivers (Anthropic, Gemini, OpenAI-compatible) route to 27 providers:
3 native drivers (Anthropic, Gemini, OpenAI-compatible) route to 29 providers:

Anthropic, Gemini, OpenAI, Groq, DeepSeek, OpenRouter, Together, Mistral, Fireworks, Cohere, Perplexity, xAI, AI21, Cerebras, SambaNova, HuggingFace, Replicate, Ollama, vLLM, LM Studio, Qwen, MiniMax, Zhipu, Moonshot, Qianfan, Bedrock, and more.
Anthropic, Gemini, OpenAI, Groq, DeepSeek, OpenRouter, Together, Mistral, Fireworks, Cohere, Perplexity, xAI, AI21, Cerebras, SambaNova, HuggingFace, Replicate, Ollama, vLLM, LM Studio, Qwen, MiniMax, Zhipu, Z.AI, Z.AI Global, Moonshot, Qianfan, Bedrock, and more.

Intelligent routing with task complexity scoring, automatic fallback, cost tracking, and per-model pricing.

Expand Down
8 changes: 6 additions & 2 deletions crates/openfang-api/static/js/pages/wizard.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ function wizardPage() {
},

get popularProviders() {
var popular = ['anthropic', 'openai', 'gemini', 'groq', 'deepseek', 'openrouter'];
var popular = ['anthropic', 'openai', 'gemini', 'groq', 'deepseek', 'openrouter', 'zai', 'zai-global'];
return this.providers.filter(function(p) {
return popular.indexOf(p.id) >= 0;
}).sort(function(a, b) {
Expand All @@ -329,7 +329,7 @@ function wizardPage() {
},

get otherProviders() {
var popular = ['anthropic', 'openai', 'gemini', 'groq', 'deepseek', 'openrouter'];
var popular = ['anthropic', 'openai', 'gemini', 'groq', 'deepseek', 'openrouter', 'zai', 'zai-global'];
return this.providers.filter(function(p) {
return popular.indexOf(p.id) < 0;
});
Expand All @@ -350,6 +350,8 @@ function wizardPage() {
groq: { url: 'https://console.groq.com/keys', text: 'Get your key from the Groq Console (free tier available)' },
deepseek: { url: 'https://platform.deepseek.com/api_keys', text: 'Get your key from the DeepSeek Platform (very affordable)' },
openrouter: { url: 'https://openrouter.ai/keys', text: 'Get your key from OpenRouter (access 100+ models with one key)' },
zai: { url: 'https://docs.z.ai/api-reference/introduction', text: 'Get your key from Z.AI dashboard and use OpenAI-compatible API' },
'zai-global': { url: 'https://docs.z.ai/guides/coding-plan/overview', text: 'Use Z.AI Coding Global plan endpoint for coding-focused workloads' },
mistral: { url: 'https://console.mistral.ai/api-keys', text: 'Get your key from the Mistral Console' },
together: { url: 'https://api.together.xyz/settings/api-keys', text: 'Get your key from Together AI' },
fireworks: { url: 'https://fireworks.ai/account/api-keys', text: 'Get your key from Fireworks AI' },
Expand Down Expand Up @@ -468,6 +470,8 @@ function wizardPage() {
gemini: 'gemini-2.5-flash',
groq: 'llama-3.3-70b-versatile',
deepseek: 'deepseek-chat',
zai: 'glm-5',
'zai-global': 'glm-4.5-air',
openrouter: 'openrouter/auto',
mistral: 'mistral-large-latest',
together: 'meta-llama/Llama-3-70b-chat-hf',
Expand Down
17 changes: 17 additions & 0 deletions crates/openfang-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,13 @@ fn provider_list() -> Vec<(&'static str, &'static str, &'static str, &'static st
"openrouter/auto",
"OpenRouter",
),
("zai", "ZAI_API_KEY", "glm-5", "Z.AI"),
(
"zai-global",
"ZAI_GLOBAL_API_KEY",
"glm-4.5-air",
"Z.AI Global",
),
]
}

Expand Down Expand Up @@ -3712,6 +3719,8 @@ fn provider_to_env_var(provider: &str) -> String {
"perplexity" => "PERPLEXITY_API_KEY".to_string(),
"cohere" => "COHERE_API_KEY".to_string(),
"xai" => "XAI_API_KEY".to_string(),
"zai" | "z.ai" => "ZAI_API_KEY".to_string(),
"zai-global" | "zai_global" | "z.ai-global" => "ZAI_GLOBAL_API_KEY".to_string(),
"brave" => "BRAVE_API_KEY".to_string(),
"tavily" => "TAVILY_API_KEY".to_string(),
other => format!("{}_API_KEY", other.to_uppercase()),
Expand Down Expand Up @@ -3763,6 +3772,14 @@ pub(crate) fn test_api_key(provider: &str, env_var: &str) -> bool {
.get("https://openrouter.ai/api/v1/models")
.bearer_auth(&key)
.send(),
"zai" | "z.ai" => client
.get("https://api.z.ai/api/paas/v4/models")
.bearer_auth(&key)
.send(),
"zai-global" | "zai_global" | "z.ai-global" => client
.get("https://api.z.ai/api/coding/paas/v4/models")
.bearer_auth(&key)
.send(),
_ => return true, // unknown provider — skip test
};

Expand Down
16 changes: 16 additions & 0 deletions crates/openfang-cli/src/tui/screens/init_wizard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ const PROVIDERS: &[ProviderInfo] = &[
needs_key: true,
hint: "",
},
ProviderInfo {
name: "zai",
display: "Z.AI",
env_var: "ZAI_API_KEY",
default_model: "glm-5",
needs_key: true,
hint: "",
},
ProviderInfo {
name: "zai-global",
display: "Z.AI Global",
env_var: "ZAI_GLOBAL_API_KEY",
default_model: "glm-4.5-air",
needs_key: true,
hint: "",
},
ProviderInfo {
name: "ollama",
display: "Ollama",
Expand Down
12 changes: 12 additions & 0 deletions crates/openfang-cli/src/tui/screens/wizard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ const PROVIDERS: &[ProviderInfo] = &[
default_model: "accounts/fireworks/models/llama-v3p3-70b-instruct",
needs_key: true,
},
ProviderInfo {
name: "zai",
env_var: "ZAI_API_KEY",
default_model: "glm-5",
needs_key: true,
},
ProviderInfo {
name: "zai-global",
env_var: "ZAI_GLOBAL_API_KEY",
default_model: "glm-4.5-air",
needs_key: true,
},
ProviderInfo {
name: "ollama",
env_var: "OLLAMA_API_KEY",
Expand Down
4 changes: 4 additions & 0 deletions crates/openfang-migrate/src/openclaw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,8 @@ fn map_provider(openclaw_provider: &str) -> String {
"fireworks" => "fireworks".to_string(),
"google" | "gemini" => "google".to_string(),
"xai" | "grok" => "xai".to_string(),
"z.ai" | "zai" => "zai".to_string(),
"z.ai-global" | "zai-global" | "zai_global" => "zai-global".to_string(),
"cerebras" => "cerebras".to_string(),
"sambanova" => "sambanova".to_string(),
other => other.to_string(),
Expand All @@ -665,6 +667,8 @@ fn default_api_key_env(provider: &str) -> String {
"fireworks" => "FIREWORKS_API_KEY".to_string(),
"google" => "GOOGLE_API_KEY".to_string(),
"xai" => "XAI_API_KEY".to_string(),
"zai" => "ZAI_API_KEY".to_string(),
"zai-global" => "ZAI_GLOBAL_API_KEY".to_string(),
"cerebras" => "CEREBRAS_API_KEY".to_string(),
"sambanova" => "SAMBANOVA_API_KEY".to_string(),
"ollama" => String::new(), // Ollama doesn't need an API key
Expand Down
45 changes: 40 additions & 5 deletions crates/openfang-runtime/src/drivers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ 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, 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, VLLM_BASE_URL, XAI_BASE_URL,
ZHIPU_BASE_URL,
OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL, QWEN_BASE_URL, REPLICATE_BASE_URL,
SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VLLM_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
ZAI_GLOBAL_BASE_URL, ZHIPU_BASE_URL,
};
use std::sync::Arc;

Expand Down Expand Up @@ -152,6 +152,16 @@ fn provider_defaults(provider: &str) -> Option<ProviderDefaults> {
api_key_env: "ZHIPU_API_KEY",
key_required: true,
}),
"zai" | "z.ai" => Some(ProviderDefaults {
base_url: ZAI_BASE_URL,
api_key_env: "ZAI_API_KEY",
key_required: true,
}),
"zai-global" | "zai_global" | "z.ai-global" => Some(ProviderDefaults {
base_url: ZAI_GLOBAL_BASE_URL,
api_key_env: "ZAI_GLOBAL_API_KEY",
key_required: true,
}),
"qianfan" | "baidu" => Some(ProviderDefaults {
base_url: QIANFAN_BASE_URL,
api_key_env: "QIANFAN_API_KEY",
Expand Down Expand Up @@ -251,6 +261,13 @@ pub fn create_driver(config: &DriverConfig) -> Result<Arc<dyn LlmDriver>, LlmErr
.api_key
.clone()
.or_else(|| std::env::var(defaults.api_key_env).ok())
.or_else(|| {
if provider == "zai-global" {
std::env::var("ZAI_API_KEY").ok()
} else {
None
}
})
.unwrap_or_default();

if defaults.key_required && api_key.is_empty() {
Expand Down Expand Up @@ -282,7 +299,8 @@ pub fn create_driver(config: &DriverConfig) -> Result<Arc<dyn LlmDriver>, LlmErr
message: format!(
"Unknown provider '{}'. Supported: anthropic, gemini, openai, groq, openrouter, \
deepseek, together, mistral, fireworks, ollama, vllm, lmstudio, perplexity, \
cohere, ai21, cerebras, sambanova, huggingface, xai, replicate, github-copilot. \
cohere, ai21, cerebras, sambanova, huggingface, xai, replicate, github-copilot, \
moonshot, qwen, minimax, zhipu, qianfan, zai, zai-global. \
Or set base_url for a custom OpenAI-compatible endpoint.",
provider
),
Expand Down Expand Up @@ -317,6 +335,8 @@ pub fn known_providers() -> &'static [&'static str] {
"qwen",
"minimax",
"zhipu",
"zai",
"zai-global",
"qianfan",
]
}
Expand Down Expand Up @@ -409,8 +429,10 @@ mod tests {
assert!(providers.contains(&"qwen"));
assert!(providers.contains(&"minimax"));
assert!(providers.contains(&"zhipu"));
assert!(providers.contains(&"zai"));
assert!(providers.contains(&"zai-global"));
assert!(providers.contains(&"qianfan"));
assert_eq!(providers.len(), 26);
assert_eq!(providers.len(), 28);
}

#[test]
Expand Down Expand Up @@ -450,4 +472,17 @@ mod tests {
assert_eq!(d.api_key_env, "HF_API_KEY");
assert!(d.key_required);
}

#[test]
fn test_provider_defaults_zai_variants() {
let d = provider_defaults("zai").unwrap();
assert_eq!(d.base_url, "https://api.z.ai/api/paas/v4");
assert_eq!(d.api_key_env, "ZAI_API_KEY");
assert!(d.key_required);

let g = provider_defaults("zai-global").unwrap();
assert_eq!(g.base_url, "https://api.z.ai/api/coding/paas/v4");
assert_eq!(g.api_key_env, "ZAI_GLOBAL_API_KEY");
assert!(g.key_required);
}
}
76 changes: 72 additions & 4 deletions crates/openfang-runtime/src/model_catalog.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Model catalog — registry of known models with metadata, pricing, and auth detection.
//!
//! Provides a comprehensive catalog of 130+ builtin models across 27 providers,
//! Provides a comprehensive catalog of 130+ builtin models across 29 providers,
//! with alias resolution, auth status detection, and pricing lookups.

use openfang_types::model_catalog::{
Expand All @@ -10,7 +10,7 @@ use openfang_types::model_catalog::{
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, VLLM_BASE_URL, XAI_BASE_URL,
ZHIPU_BASE_URL,
ZAI_BASE_URL, ZAI_GLOBAL_BASE_URL, ZHIPU_BASE_URL,
};
use std::collections::HashMap;

Expand Down Expand Up @@ -54,6 +54,9 @@ impl ModelCatalog {
// Special case: Gemini also accepts GOOGLE_API_KEY
if provider.id == "gemini" && std::env::var("GOOGLE_API_KEY").is_ok() {
provider.auth_status = AuthStatus::Configured;
// Special case: Z.AI Global can reuse ZAI_API_KEY
} else if provider.id == "zai-global" && std::env::var("ZAI_API_KEY").is_ok() {
provider.auth_status = AuthStatus::Configured;
} else {
provider.auth_status = AuthStatus::Missing;
}
Expand Down Expand Up @@ -413,6 +416,24 @@ fn builtin_providers() -> Vec<ProviderInfo> {
auth_status: AuthStatus::Missing,
model_count: 0,
},
ProviderInfo {
id: "zai".into(),
display_name: "Z.AI".into(),
api_key_env: "ZAI_API_KEY".into(),
base_url: ZAI_BASE_URL.into(),
key_required: true,
auth_status: AuthStatus::Missing,
model_count: 0,
},
ProviderInfo {
id: "zai-global".into(),
display_name: "Z.AI Global (Coding)".into(),
api_key_env: "ZAI_GLOBAL_API_KEY".into(),
base_url: ZAI_GLOBAL_BASE_URL.into(),
key_required: true,
auth_status: AuthStatus::Missing,
model_count: 0,
},
ProviderInfo {
id: "moonshot".into(),
display_name: "Moonshot (Kimi)".into(),
Expand Down Expand Up @@ -2115,8 +2136,53 @@ fn builtin_models() -> Vec<ModelCatalogEntry> {
aliases: vec![],
},
// ══════════════════════════════════════════════════════════════
// Moonshot / Kimi (3)
// Z.AI (3)
// ══════════════════════════════════════════════════════════════
// Z.AI (official OpenAI-compatible endpoint)
ModelCatalogEntry {
id: "glm-5".into(),
display_name: "GLM-5".into(),
provider: "zai".into(),
tier: ModelTier::Frontier,
context_window: 131_072,
max_output_tokens: 8_192,
input_cost_per_m: 0.0,
output_cost_per_m: 0.0,
supports_tools: true,
supports_vision: true,
supports_streaming: true,
aliases: vec![],
},
ModelCatalogEntry {
id: "glm-4.7".into(),
display_name: "GLM-4.7".into(),
provider: "zai".into(),
tier: ModelTier::Smart,
context_window: 128_000,
max_output_tokens: 8_192,
input_cost_per_m: 0.0,
output_cost_per_m: 0.0,
supports_tools: true,
supports_vision: true,
supports_streaming: true,
aliases: vec![],
},
// Z.AI Global coding plan endpoint
ModelCatalogEntry {
id: "glm-4.5-air".into(),
display_name: "GLM-4.5 Air".into(),
provider: "zai-global".into(),
tier: ModelTier::Fast,
context_window: 128_000,
max_output_tokens: 8_192,
input_cost_per_m: 0.0,
output_cost_per_m: 0.0,
supports_tools: true,
supports_vision: true,
supports_streaming: true,
aliases: vec![],
},
// Moonshot / Kimi (3)
ModelCatalogEntry {
id: "moonshot-v1-128k".into(),
display_name: "Moonshot V1 128K".into(),
Expand Down Expand Up @@ -2307,7 +2373,7 @@ mod tests {
#[test]
fn test_catalog_has_providers() {
let catalog = ModelCatalog::new();
assert_eq!(catalog.list_providers().len(), 27);
assert_eq!(catalog.list_providers().len(), 29);
}

#[test]
Expand Down Expand Up @@ -2525,6 +2591,8 @@ mod tests {
assert!(catalog.get_provider("qwen").is_some());
assert!(catalog.get_provider("minimax").is_some());
assert!(catalog.get_provider("zhipu").is_some());
assert!(catalog.get_provider("zai").is_some());
assert!(catalog.get_provider("zai-global").is_some());
assert!(catalog.get_provider("moonshot").is_some());
assert!(catalog.get_provider("qianfan").is_some());
assert!(catalog.get_provider("bedrock").is_some());
Expand Down
2 changes: 2 additions & 0 deletions crates/openfang-types/src/model_catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub const GITHUB_COPILOT_BASE_URL: &str = "https://api.githubcopilot.com";
pub const QWEN_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1";
pub const MINIMAX_BASE_URL: &str = "https://api.minimax.chat/v1";
pub const ZHIPU_BASE_URL: &str = "https://open.bigmodel.cn/api/paas/v4";
pub const ZAI_BASE_URL: &str = "https://api.z.ai/api/paas/v4";
pub const ZAI_GLOBAL_BASE_URL: &str = "https://api.z.ai/api/coding/paas/v4";
pub const MOONSHOT_BASE_URL: &str = "https://api.moonshot.cn/v1";
pub const QIANFAN_BASE_URL: &str = "https://qianfan.baidubce.com/v2";

Expand Down
2 changes: 1 addition & 1 deletion docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ openfang config set-key <PROVIDER>

| Argument | Description |
|---|---|
| `<PROVIDER>` | Provider name (e.g. `groq`, `anthropic`, `openai`, `gemini`, `deepseek`, `openrouter`, `together`, `mistral`, `fireworks`, `perplexity`, `cohere`, `xai`, `brave`, `tavily`). |
| `<PROVIDER>` | Provider name (e.g. `groq`, `anthropic`, `openai`, `gemini`, `deepseek`, `openrouter`, `zai`, `zai-global`, `together`, `mistral`, `fireworks`, `perplexity`, `cohere`, `xai`, `brave`, `tavily`). |

**Behavior:**

Expand Down
Loading