Skip to content
Merged
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
1 change: 1 addition & 0 deletions interface/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ export interface ProviderStatus {
ollama: boolean;
opencode_zen: boolean;
nvidia: boolean;
minimax: boolean;
}

export interface ProvidersResponse {
Expand Down
2 changes: 2 additions & 0 deletions interface/src/lib/providerIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Fireworks from "@lobehub/icons/es/Fireworks";
import Together from "@lobehub/icons/es/Together";
import XAI from "@lobehub/icons/es/XAI";
import ZAI from "@lobehub/icons/es/ZAI";
import Minimax from "@lobehub/icons/es/Minimax";

interface IconProps {
size?: number;
Expand Down Expand Up @@ -107,6 +108,7 @@ export function ProviderIcon({ provider, className = "text-ink-faint", size = 24
ollama: OllamaIcon,
"opencode-zen": OpenCodeZenIcon,
nvidia: NvidiaIcon,
minimax: Minimax,
};

const IconComponent = iconMap[provider.toLowerCase()];
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 @@ -159,6 +159,14 @@ const PROVIDERS = [
envVar: "NVIDIA_API_KEY",
defaultModel: "nvidia/meta/llama-3.1-405b-instruct",
},
{
id: "minimax",
name: "MiniMax",
description: "MiniMax M1 (Anthropic message format)",
placeholder: "eyJ...",
envVar: "MINIMAX_API_KEY",
defaultModel: "MiniMax-M1-80k",
},
{
id: "ollama",
name: "Ollama",
Expand Down
10 changes: 10 additions & 0 deletions src/api/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ fn extra_models() -> Vec<ModelInfo> {
tool_call: true,
reasoning: false,
},
// MiniMax
ModelInfo {
id: "minimax/MiniMax-M1-80k".into(),
name: "MiniMax M1 80K".into(),
provider: "minimax".into(),
context_window: Some(80000),
tool_call: true,
reasoning: false,
},
]
}

Expand Down Expand Up @@ -274,6 +283,7 @@ pub(super) async fn configured_providers(config_path: &std::path::Path) -> Vec<&
if has_key("xai_key", "XAI_API_KEY") { providers.push("xai"); }
if has_key("mistral_key", "MISTRAL_API_KEY") { providers.push("mistral"); }
if has_key("opencode_zen_key", "OPENCODE_ZEN_API_KEY") { providers.push("opencode-zen"); }
if has_key("minimax_key", "MINIMAX_API_KEY") { providers.push("minimax"); }

providers
}
Expand Down
12 changes: 10 additions & 2 deletions src/api/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub(super) struct ProviderStatus {
xai: bool,
mistral: bool,
opencode_zen: bool,
minimax: bool,
}

#[derive(Serialize)]
Expand All @@ -44,7 +45,7 @@ pub(super) async fn get_providers(
) -> Result<Json<ProvidersResponse>, StatusCode> {
let config_path = state.config_path.read().await.clone();

let (anthropic, openai, openrouter, zhipu, groq, together, fireworks, deepseek, xai, mistral, opencode_zen) = if config_path.exists() {
let (anthropic, openai, openrouter, zhipu, groq, together, fireworks, deepseek, xai, mistral, opencode_zen, minimax) = if config_path.exists() {
let content = tokio::fs::read_to_string(&config_path)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Expand Down Expand Up @@ -78,6 +79,7 @@ pub(super) async fn get_providers(
has_key("xai_key", "XAI_API_KEY"),
has_key("mistral_key", "MISTRAL_API_KEY"),
has_key("opencode_zen_key", "OPENCODE_ZEN_API_KEY"),
has_key("minimax_key", "MINIMAX_API_KEY"),
)
} else {
(
Expand All @@ -92,6 +94,7 @@ pub(super) async fn get_providers(
std::env::var("XAI_API_KEY").is_ok(),
std::env::var("MISTRAL_API_KEY").is_ok(),
std::env::var("OPENCODE_ZEN_API_KEY").is_ok(),
std::env::var("MINIMAX_API_KEY").is_ok(),
)
};

Expand All @@ -107,6 +110,7 @@ pub(super) async fn get_providers(
xai,
mistral,
opencode_zen,
minimax,
};
let has_any = providers.anthropic
|| providers.openai
Expand All @@ -118,7 +122,8 @@ pub(super) async fn get_providers(
|| providers.deepseek
|| providers.xai
|| providers.mistral
|| providers.opencode_zen;
|| providers.opencode_zen
|| providers.minimax;

Ok(Json(ProvidersResponse { providers, has_any }))
}
Expand All @@ -139,6 +144,7 @@ pub(super) async fn update_provider(
"xai" => "xai_key",
"mistral" => "mistral_key",
"opencode-zen" => "opencode_zen_key",
"minimax" => "minimax_key",
_ => {
return Ok(Json(ProviderUpdateResponse {
success: false,
Expand Down Expand Up @@ -208,6 +214,7 @@ pub(super) async fn update_provider(
"xai" => has_provider_key("xai_key", "XAI_API_KEY"),
"mistral" => has_provider_key("mistral_key", "MISTRAL_API_KEY"),
"opencode-zen" => has_provider_key("opencode_zen_key", "OPENCODE_ZEN_API_KEY"),
"minimax" => has_provider_key("minimax_key", "MINIMAX_API_KEY"),
_ => false,
};

Expand Down Expand Up @@ -273,6 +280,7 @@ pub(super) async fn delete_provider(
"xai" => "xai_key",
"mistral" => "mistral_key",
"opencode-zen" => "opencode_zen_key",
"minimax" => "minimax_key",
_ => {
return Ok(Json(ProviderUpdateResponse {
success: false,
Expand Down
42 changes: 40 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ pub struct LlmConfig {
pub ollama_base_url: Option<String>,
pub opencode_zen_key: Option<String>,
pub nvidia_key: Option<String>,
pub minimax_key: Option<String>,
pub providers: HashMap<String, ProviderConfig>,
}

Expand All @@ -165,13 +166,15 @@ impl LlmConfig {
|| self.ollama_base_url.is_some()
|| self.opencode_zen_key.is_some()
|| self.nvidia_key.is_some()
|| self.minimax_key.is_some()
|| !self.providers.is_empty()
}
}

const ANTHROPIC_PROVIDER_BASE_URL: &str = "https://api.anthropic.com";
const OPENAI_PROVIDER_BASE_URL: &str = "https://api.openai.com";
const OPENROUTER_PROVIDER_BASE_URL: &str = "https://openrouter.ai/api";
const MINIMAX_PROVIDER_BASE_URL: &str = "https://api.minimax.io/anthropic";

/// Defaults inherited by all agents. Individual agents can override any field.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -1100,6 +1103,7 @@ struct TomlLlmConfigFields {
ollama_base_url: Option<String>,
opencode_zen_key: Option<String>,
nvidia_key: Option<String>,
minimax_key: Option<String>,
#[serde(default)]
providers: HashMap<String, TomlProviderConfig>,
#[serde(default)]
Expand All @@ -1123,6 +1127,7 @@ struct TomlLlmConfig {
ollama_base_url: Option<String>,
opencode_zen_key: Option<String>,
nvidia_key: Option<String>,
minimax_key: Option<String>,
providers: HashMap<String, TomlProviderConfig>,
}

Expand Down Expand Up @@ -1171,6 +1176,7 @@ impl<'de> Deserialize<'de> for TomlLlmConfig {
ollama_base_url: fields.ollama_base_url,
opencode_zen_key: fields.opencode_zen_key,
nvidia_key: fields.nvidia_key,
minimax_key: fields.minimax_key,
providers: fields.providers,
})
}
Expand Down Expand Up @@ -1509,8 +1515,9 @@ impl Config {
|| std::env::var("NVIDIA_API_KEY").is_ok()
|| std::env::var("OLLAMA_API_KEY").is_ok()
|| std::env::var("OLLAMA_BASE_URL").is_ok()
|| std::env::var("OPENCODE_ZEN_API_KEY").is_ok();

|| std::env::var("OPENCODE_ZEN_API_KEY").is_ok()
|| std::env::var("MINIMAX_API_KEY").is_ok();

// If we have any legacy keys, no onboarding needed
if has_legacy_keys {
return false;
Expand Down Expand Up @@ -1571,6 +1578,7 @@ impl Config {
ollama_base_url: std::env::var("OLLAMA_BASE_URL").ok(),
opencode_zen_key: std::env::var("OPENCODE_ZEN_API_KEY").ok(),
nvidia_key: std::env::var("NVIDIA_API_KEY").ok(),
minimax_key: std::env::var("MINIMAX_API_KEY").ok(),
providers: HashMap::new(),
};

Expand Down Expand Up @@ -1608,6 +1616,17 @@ impl Config {
});
}

if let Some(minimax_key) = llm.minimax_key.clone() {
llm.providers
.entry("minimax".to_string())
.or_insert_with(|| ProviderConfig {
api_type: ApiType::Anthropic,
base_url: MINIMAX_PROVIDER_BASE_URL.to_string(),
api_key: minimax_key,
name: None,
});
}

// Note: We allow boot without provider keys now. System starts in setup mode.
// Agents are initialized later when keys are added via API.

Expand Down Expand Up @@ -1784,6 +1803,12 @@ impl Config {
.as_deref()
.and_then(resolve_env_value)
.or_else(|| std::env::var("NVIDIA_API_KEY").ok()),
minimax_key: toml
.llm
.minimax_key
.as_deref()
.and_then(resolve_env_value)
.or_else(|| std::env::var("MINIMAX_API_KEY").ok()),
providers: toml
.llm
.providers
Expand Down Expand Up @@ -1836,6 +1861,17 @@ impl Config {
});
}

if let Some(minimax_key) = llm.minimax_key.clone() {
llm.providers
.entry("minimax".to_string())
.or_insert_with(|| ProviderConfig {
api_type: ApiType::Anthropic,
base_url: MINIMAX_PROVIDER_BASE_URL.to_string(),
api_key: minimax_key,
name: None,
});
}

// Note: We allow boot without provider keys now. System starts in setup mode.
// Agents are initialized later when keys are added via API.

Expand Down Expand Up @@ -2882,6 +2918,7 @@ pub fn run_onboarding() -> anyhow::Result<Option<PathBuf>> {
"Mistral AI",
"Ollama",
"OpenCode Zen",
"MiniMax",
];
let provider_idx = Select::new()
.with_prompt("Which LLM provider do you want to use?")
Expand All @@ -2902,6 +2939,7 @@ pub fn run_onboarding() -> anyhow::Result<Option<PathBuf>> {
9 => ("Mistral AI API key", "mistral_key", "mistral"),
10 => ("Ollama base URL", "ollama_base_url", "ollama"),
11 => ("OpenCode Zen API key", "opencode_zen_key", "opencode-zen"),
12 => ("MiniMax API key", "minimax_key", "minimax"),
_ => unreachable!(),
};
let is_secret = provider_id != "ollama";
Expand Down
6 changes: 5 additions & 1 deletion src/llm/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ pub async fn init_providers(config: &LlmConfig) -> Result<()> {
if config.opencode_zen_key.is_some() {
tracing::info!("OpenCode Zen provider configured");
}


if config.minimax_key.is_some() {
tracing::info!("MiniMax provider configured");
}

Ok(())
}
2 changes: 2 additions & 0 deletions src/llm/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ pub fn defaults_for_provider(provider: &str) -> RoutingConfig {
"mistral" => RoutingConfig::for_model("mistral/mistral-large-latest".into()),
"nvidia" => RoutingConfig::for_model("nvidia/meta/llama-3.1-405b-instruct".into()),
"opencode-zen" => RoutingConfig::for_model("opencode-zen/kimi-k2.5".into()),
"minimax" => RoutingConfig::for_model("minimax/MiniMax-M1-80k".into()),
_ => RoutingConfig::default(),
}
}
Expand All @@ -163,6 +164,7 @@ pub fn provider_to_prefix(provider: &str) -> &str {
"mistral" => "mistral/",
"nvidia" => "nvidia/",
"opencode-zen" => "opencode-zen/",
"minimax" => "minimax/",
_ => "",
}
}
Expand Down