Skip to content
Closed
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
2 changes: 2 additions & 0 deletions docs/content/docs/(configuration)/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ deepseek_key = "env:DEEPSEEK_API_KEY"
xai_key = "env:XAI_API_KEY"
mistral_key = "env:MISTRAL_API_KEY"
opencode_zen_key = "env:OPENCODE_ZEN_API_KEY"
opencode_go_key = "env:OPENCODE_GO_API_KEY"

# Custom LLM providers (alternative to legacy keys)
[llm.provider.my_anthropic]
Expand Down Expand Up @@ -337,6 +338,7 @@ If you define a custom provider with the same ID as a legacy key, your custom co
| `xai_key` | string | None | XAI API key (or `env:VAR_NAME`) |
| `mistral_key` | string | None | Mistral API key (or `env:VAR_NAME`) |
| `opencode_zen_key` | string | None | OpenCode Zen API key (or `env:VAR_NAME`) |
| `opencode_go_key` | string | None | OpenCode Go API key (or `env:VAR_NAME`) |

#### Custom Providers

Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/(deployment)/roadmap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The full message-in → LLM → response-out pipeline is wired end-to-end across
- **Config** — hierarchical TOML with `Config`, `AgentConfig`, `ResolvedAgentConfig`, `Binding`, `MessagingConfig`. File watcher with event filtering and content hash debounce for hot-reload.
- **Multi-agent** — per-agent database isolation, `Agent` struct bundles all dependencies
- **Database connections** — SQLite + LanceDB + redb per-agent, migrations for all tables
- **LLM** — `SpacebotModel` implements Rig's `CompletionModel`, routes through `LlmManager` via HTTP with retries and fallback chains across 11 providers (Anthropic, OpenAI, OpenRouter, Z.ai, Groq, Together, Fireworks, DeepSeek, xAI, Mistral, OpenCode Zen)
- **LLM** — `SpacebotModel` implements Rig's `CompletionModel`, routes through `LlmManager` via HTTP with retries and fallback chains across 12 providers (Anthropic, OpenAI, OpenRouter, Z.ai, Groq, Together, Fireworks, DeepSeek, xAI, Mistral, OpenCode Zen, OpenCode Go)
- **Model routing** — `RoutingConfig` with process-type defaults, task overrides, fallback chains
- **Memory** — full stack: types, SQLite store (CRUD + graph), LanceDB (embeddings + vector + FTS), fastembed, hybrid search (RRF fusion). `memory_type` filter wired end-to-end through SearchConfig. `total_cmp` for safe sorting.
- **Memory maintenance** — decay + prune implemented
Expand Down
1 change: 1 addition & 0 deletions interface/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ export interface ProviderStatus {
gemini: boolean;
ollama: boolean;
opencode_zen: boolean;
opencode_go: boolean;
nvidia: boolean;
minimax: boolean;
minimax_cn: boolean;
Expand Down
2 changes: 2 additions & 0 deletions interface/src/components/ModelSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const PROVIDER_LABELS: Record<string, string> = {
zhipu: "Z.ai (GLM)",
ollama: "Ollama",
"opencode-zen": "OpenCode Zen",
"opencode-go": "OpenCode Go",
minimax: "MiniMax",
"minimax-cn": "MiniMax CN",
};
Expand Down Expand Up @@ -141,6 +142,7 @@ export function ModelSelect({
"fireworks",
"zhipu",
"opencode-zen",
"opencode-go",
"minimax",
"minimax-cn",
];
Expand Down
1 change: 1 addition & 0 deletions interface/src/lib/providerIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export function ProviderIcon({ provider, className = "text-ink-faint", size = 24
"zai-coding-plan": ZAI,
ollama: OllamaIcon,
"opencode-zen": OpenCodeZenIcon,
"opencode-go": OpenCodeZenIcon,
nvidia: NvidiaIcon,
minimax: Minimax,
"minimax-cn": Minimax,
Expand Down
8 changes: 8 additions & 0 deletions interface/src/routes/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ const PROVIDERS = [
envVar: "OPENCODE_ZEN_API_KEY",
defaultModel: "opencode-zen/kimi-k2.5",
},
{
id: "opencode-go",
name: "OpenCode Go",
description: "Lite OpenCode model catalog and limits",
placeholder: "...",
envVar: "OPENCODE_GO_API_KEY",
defaultModel: "opencode-go/kimi-k2.5",
},
{
id: "anthropic",
name: "Anthropic",
Expand Down
122 changes: 10 additions & 112 deletions src/api/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ fn direct_provider_mapping(models_dev_id: &str) -> Option<&'static str> {
"togetherai" => Some("together"),
"fireworks-ai" => Some("fireworks"),
"zhipuai" => Some("zhipu"),
"opencode" => Some("opencode-zen"),
"opencode-go" => Some("opencode-go"),
"zai-coding-plan" => Some("zai-coding-plan"),
"minimax" => Some("minimax"),
"moonshotai" => Some("moonshot"),
_ => None,
}
}
Expand Down Expand Up @@ -141,108 +146,7 @@ fn as_openai_chatgpt_model(model: &ModelInfo) -> Option<ModelInfo> {
/// Models from providers not in models.dev (private/custom endpoints).
fn extra_models() -> Vec<ModelInfo> {
vec![
ModelInfo {
id: "opencode-zen/kimi-k2.5".into(),
name: "Kimi K2.5".into(),
provider: "opencode-zen".into(),
context_window: None,
tool_call: true,
reasoning: true,
input_audio: false,
},
ModelInfo {
id: "opencode-zen/kimi-k2".into(),
name: "Kimi K2".into(),
provider: "opencode-zen".into(),
context_window: None,
tool_call: true,
reasoning: false,
input_audio: false,
},
ModelInfo {
id: "opencode-zen/kimi-k2-thinking".into(),
name: "Kimi K2 Thinking".into(),
provider: "opencode-zen".into(),
context_window: None,
tool_call: true,
reasoning: true,
input_audio: false,
},
ModelInfo {
id: "opencode-zen/glm-5".into(),
name: "GLM 5".into(),
provider: "opencode-zen".into(),
context_window: None,
tool_call: true,
reasoning: false,
input_audio: false,
},
ModelInfo {
id: "opencode-zen/minimax-m2.5".into(),
name: "MiniMax M2.5".into(),
provider: "opencode-zen".into(),
context_window: None,
tool_call: true,
reasoning: false,
input_audio: false,
},
ModelInfo {
id: "opencode-zen/qwen3-coder".into(),
name: "Qwen3 Coder 480B".into(),
provider: "opencode-zen".into(),
context_window: None,
tool_call: true,
reasoning: false,
input_audio: false,
},
ModelInfo {
id: "opencode-zen/big-pickle".into(),
name: "Big Pickle".into(),
provider: "opencode-zen".into(),
context_window: None,
tool_call: true,
reasoning: false,
input_audio: false,
},
// Z.AI Coding Plan
ModelInfo {
id: "zai-coding-plan/glm-4.7".into(),
name: "GLM 4.7 (Coding)".into(),
provider: "zai-coding-plan".into(),
context_window: None,
tool_call: true,
reasoning: false,
input_audio: false,
},
ModelInfo {
id: "zai-coding-plan/glm-5".into(),
name: "GLM 5 (Coding)".into(),
provider: "zai-coding-plan".into(),
context_window: None,
tool_call: true,
reasoning: false,
input_audio: false,
},
ModelInfo {
id: "zai-coding-plan/glm-4.5-air".into(),
name: "GLM 4.5 Air (Coding)".into(),
provider: "zai-coding-plan".into(),
context_window: None,
tool_call: true,
reasoning: false,
input_audio: false,
},
// MiniMax
ModelInfo {
id: "minimax/MiniMax-M2.5".into(),
name: "MiniMax M2.5".into(),
provider: "minimax".into(),
context_window: Some(200000),
tool_call: true,
reasoning: true,
input_audio: false,
},
// MiniMax CN
// MiniMax CN - China-specific endpoint, not on models.dev
ModelInfo {
id: "minimax-cn/MiniMax-M2.5".into(),
name: "MiniMax M2.5".into(),
Expand All @@ -252,16 +156,7 @@ fn extra_models() -> Vec<ModelInfo> {
reasoning: true,
input_audio: false,
},
// Moonshot AI (Kimi)
ModelInfo {
id: "moonshot/kimi-k2.5".into(),
name: "Kimi K2.5".into(),
provider: "moonshot".into(),
context_window: None,
tool_call: true,
reasoning: true,
input_audio: false,
},
// Moonshot AI (Kimi) - moonshot-v1-8k not on models.dev
ModelInfo {
id: "moonshot/moonshot-v1-8k".into(),
name: "Moonshot V1 8K".into(),
Expand Down Expand Up @@ -433,6 +328,9 @@ pub(super) async fn configured_providers(config_path: &std::path::Path) -> Vec<&
if has_key("opencode_zen_key", "OPENCODE_ZEN_API_KEY") {
providers.push("opencode-zen");
}
if has_key("opencode_go_key", "OPENCODE_GO_API_KEY") {
providers.push("opencode-go");
}
if has_key("minimax_key", "MINIMAX_API_KEY") {
providers.push("minimax");
}
Expand Down
22 changes: 19 additions & 3 deletions src/api/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub(super) struct ProviderStatus {
gemini: bool,
ollama: bool,
opencode_zen: bool,
opencode_go: bool,
nvidia: bool,
minimax: bool,
minimax_cn: bool,
Expand Down Expand Up @@ -137,6 +138,7 @@ fn provider_toml_key(provider: &str) -> Option<&'static str> {
"gemini" => Some("gemini_key"),
"ollama" => Some("ollama_base_url"),
"opencode-zen" => Some("opencode_zen_key"),
"opencode-go" => Some("opencode_go_key"),
"nvidia" => Some("nvidia_key"),
"minimax" => Some("minimax_key"),
"minimax-cn" => Some("minimax_cn_key"),
Expand Down Expand Up @@ -253,6 +255,13 @@ fn build_test_llm_config(provider: &str, credential: &str) -> crate::config::Llm
name: None,
use_bearer_auth: false,
}),
"opencode-go" => Some(ProviderConfig {
api_type: ApiType::OpenAiCompletions,
base_url: "https://opencode.ai/zen/go".to_string(),
api_key: credential.to_string(),
name: None,
use_bearer_auth: false,
}),
"nvidia" => Some(ProviderConfig {
api_type: ApiType::OpenAiCompletions,
base_url: "https://integrate.api.nvidia.com".to_string(),
Expand Down Expand Up @@ -310,6 +319,7 @@ fn build_test_llm_config(provider: &str, credential: &str) -> crate::config::Llm
ollama_key: None,
ollama_base_url: (provider == "ollama").then(|| credential.to_string()),
opencode_zen_key: (provider == "opencode-zen").then(|| credential.to_string()),
opencode_go_key: (provider == "opencode-go").then(|| credential.to_string()),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
nvidia_key: (provider == "nvidia").then(|| credential.to_string()),
minimax_key: (provider == "minimax").then(|| credential.to_string()),
minimax_cn_key: (provider == "minimax-cn").then(|| credential.to_string()),
Expand Down Expand Up @@ -460,6 +470,7 @@ pub(super) async fn get_providers(
gemini,
ollama,
opencode_zen,
opencode_go,
nvidia,
minimax,
minimax_cn,
Expand Down Expand Up @@ -502,6 +513,7 @@ pub(super) async fn get_providers(
has_value("ollama_base_url", "OLLAMA_BASE_URL")
|| has_value("ollama_key", "OLLAMA_API_KEY"),
has_value("opencode_zen_key", "OPENCODE_ZEN_API_KEY"),
has_value("opencode_go_key", "OPENCODE_GO_API_KEY"),
has_value("nvidia_key", "NVIDIA_API_KEY"),
has_value("minimax_key", "MINIMAX_API_KEY"),
has_value("minimax_cn_key", "MINIMAX_CN_API_KEY"),
Expand All @@ -524,6 +536,7 @@ pub(super) async fn get_providers(
std::env::var("GEMINI_API_KEY").is_ok(),
std::env::var("OLLAMA_BASE_URL").is_ok() || std::env::var("OLLAMA_API_KEY").is_ok(),
std::env::var("OPENCODE_ZEN_API_KEY").is_ok(),
std::env::var("OPENCODE_GO_API_KEY").is_ok(),
std::env::var("NVIDIA_API_KEY").is_ok(),
std::env::var("MINIMAX_API_KEY").is_ok(),
std::env::var("MINIMAX_CN_API_KEY").is_ok(),
Expand All @@ -547,6 +560,7 @@ pub(super) async fn get_providers(
gemini,
ollama,
opencode_zen,
opencode_go,
nvidia,
minimax,
minimax_cn,
Expand All @@ -567,6 +581,7 @@ pub(super) async fn get_providers(
|| providers.gemini
|| providers.ollama
|| providers.opencode_zen
|| providers.opencode_go
|| providers.nvidia
|| providers.minimax
|| providers.minimax_cn
Expand Down Expand Up @@ -891,7 +906,8 @@ pub(super) async fn update_provider(
pub(super) async fn test_provider_model(
Json(request): Json<ProviderModelTestRequest>,
) -> Result<Json<ProviderModelTestResponse>, StatusCode> {
if provider_toml_key(&request.provider).is_none() {
let normalized_provider = request.provider.trim().to_lowercase();
if provider_toml_key(&normalized_provider).is_none() {
return Ok(Json(ProviderModelTestResponse {
success: false,
message: format!("Unknown provider: {}", request.provider),
Expand Down Expand Up @@ -921,7 +937,7 @@ pub(super) async fn test_provider_model(
}));
}

if !model_matches_provider(&request.provider, &request.model) {
if !model_matches_provider(&normalized_provider, &request.model) {
return Ok(Json(ProviderModelTestResponse {
success: false,
message: format!(
Expand All @@ -934,7 +950,7 @@ pub(super) async fn test_provider_model(
}));
}

let llm_config = build_test_llm_config(&request.provider, request.api_key.trim());
let llm_config = build_test_llm_config(&normalized_provider, request.api_key.trim());
let llm_manager = match crate::llm::LlmManager::new(llm_config).await {
Ok(manager) => Arc::new(manager),
Err(error) => {
Expand Down
Loading