Expand inference provider catalog (#3057) (#3062)

This commit is contained in:
Steven Enamakel
2026-05-30 22:37:06 -07:00
committed by GitHub
parent f64f10e3c1
commit 4e221e978b
12 changed files with 743 additions and 168 deletions
+2 -2
View File
@@ -1087,8 +1087,8 @@ pub(super) const CAPABILITIES: &[Capability] = &[
name: "Configure AI",
domain: "settings",
category: CapabilityCategory::Settings,
description: "Adjust AI-related settings and agent behavior preferences.",
how_to: "Settings > Developer Options > AI Configuration",
description: "Configure managed, local, custom, and built-in BYOK LLM providers, including SumoPod and other OpenAI-compatible gateways, plus per-workload routing preferences.",
how_to: "Settings > AI",
status: CapabilityStatus::Stable,
privacy: None,
},
+239 -26
View File
@@ -12,6 +12,179 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinCloudProvider {
pub slug: &'static str,
pub label: &'static str,
pub endpoint: &'static str,
pub auth_style: AuthStyle,
}
pub const BUILTIN_CLOUD_PROVIDERS: &[BuiltinCloudProvider] = &[
BuiltinCloudProvider {
slug: "openhuman",
label: "OpenHuman",
endpoint: "https://api.openhuman.ai/v1",
auth_style: AuthStyle::OpenhumanJwt,
},
BuiltinCloudProvider {
slug: "openai",
label: "OpenAI",
endpoint: "https://api.openai.com/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "anthropic",
label: "Anthropic",
endpoint: "https://api.anthropic.com/v1",
auth_style: AuthStyle::Anthropic,
},
BuiltinCloudProvider {
slug: "openrouter",
label: "OpenRouter",
endpoint: "https://openrouter.ai/api/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "orcarouter",
label: "OrcaRouter",
endpoint: "https://api.orcarouter.ai/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "gmi",
label: "GMI",
endpoint: "https://api.gmi-serving.com/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "fireworks",
label: "Fireworks",
endpoint: "https://api.fireworks.ai/inference/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "moonshot",
label: "Kimi (Moonshot)",
endpoint: "https://api.moonshot.ai/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "groq",
label: "Groq",
endpoint: "https://api.groq.com/openai/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "mistral",
label: "Mistral",
endpoint: "https://api.mistral.ai/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "deepseek",
label: "DeepSeek",
endpoint: "https://api.deepseek.com/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "together",
label: "Together AI",
endpoint: "https://api.together.xyz/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "google",
label: "Google Gemini",
endpoint: "https://generativelanguage.googleapis.com/v1beta/openai",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "cerebras",
label: "Cerebras",
endpoint: "https://api.cerebras.ai/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "xai",
label: "xAI",
endpoint: "https://api.x.ai/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "huggingface",
label: "Hugging Face",
endpoint: "https://router.huggingface.co/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "nvidia",
label: "NVIDIA",
endpoint: "https://integrate.api.nvidia.com/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "zai",
label: "Z.AI",
endpoint: "https://api.z.ai/api/paas/v4",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "minimax",
label: "MiniMax",
endpoint: "https://api.minimax.io/anthropic",
auth_style: AuthStyle::Anthropic,
},
BuiltinCloudProvider {
slug: "stepfun",
label: "StepFun",
endpoint: "https://api.stepfun.ai/step_plan/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "kilocode",
label: "Kilo Code",
endpoint: "https://api.kilo.ai/api/gateway",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "deepinfra",
label: "DeepInfra",
endpoint: "https://api.deepinfra.com/v1/openai",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "novita",
label: "Novita",
endpoint: "https://api.novita.ai/v3/openai",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "venice",
label: "Venice",
endpoint: "https://api.venice.ai/api/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "vercel-ai-gateway",
label: "Vercel AI Gateway",
endpoint: "https://ai-gateway.vercel.sh/v1",
auth_style: AuthStyle::Bearer,
},
BuiltinCloudProvider {
slug: "sumopod",
label: "SumoPod",
endpoint: "https://ai.sumopod.com/v1",
auth_style: AuthStyle::Bearer,
},
];
fn builtin_cloud_provider(type_str: &str) -> Option<&'static BuiltinCloudProvider> {
BUILTIN_CLOUD_PROVIDERS
.iter()
.find(|provider| provider.slug == type_str)
}
/// Authentication header style for a cloud provider.
///
/// Wire format is lowercase (e.g. `"bearer"`). Determines which HTTP headers
@@ -160,41 +333,24 @@ pub fn migrate_legacy_fields(entry: &mut CloudProviderCreds) {
// Auth style from legacy type when still at default Bearer.
if entry.auth_style == AuthStyle::Bearer {
match lt {
"anthropic" => {
entry.auth_style = AuthStyle::Anthropic;
}
"openhuman" => {
entry.auth_style = AuthStyle::OpenhumanJwt;
}
_ => {}
if let Some(provider) = builtin_cloud_provider(lt) {
entry.auth_style = provider.auth_style;
}
}
}
/// Map a legacy type string (or slug) to a human-readable label.
fn legacy_label_for(type_str: &str) -> &'static str {
match type_str {
"openhuman" => "OpenHuman",
"openai" => "OpenAI",
"anthropic" => "Anthropic",
"openrouter" => "OpenRouter",
"orcarouter" => "OrcaRouter",
"custom" => "Custom",
_ => "Custom",
}
builtin_cloud_provider(type_str)
.map(|provider| provider.label)
.unwrap_or("Custom")
}
/// Map a legacy type string to its well-known default endpoint.
fn legacy_default_endpoint(type_str: &str) -> &'static str {
match type_str {
"openhuman" => "https://api.openhuman.ai/v1",
"openai" => "https://api.openai.com/v1",
"anthropic" => "https://api.anthropic.com/v1",
"openrouter" => "https://openrouter.ai/api/v1",
"orcarouter" => "https://api.orcarouter.ai/v1",
_ => "",
}
builtin_cloud_provider(type_str)
.map(|provider| provider.endpoint)
.unwrap_or("")
}
/// Generate a short opaque id for a new provider entry.
@@ -303,7 +459,10 @@ impl CloudProviderType {
#[cfg(test)]
mod tests {
use super::is_slug_reserved;
use super::{
is_slug_reserved, migrate_legacy_fields, AuthStyle, CloudProviderCreds,
BUILTIN_CLOUD_PROVIDERS,
};
#[test]
fn reserved_slugs() {
@@ -329,4 +488,58 @@ mod tests {
"lmstudio is a free-form OpenAI-compatible slug"
);
}
#[test]
fn builtin_cloud_provider_defaults_cover_phase_one_presets() {
for (slug, label, endpoint, auth_style) in [
(
"groq",
"Groq",
"https://api.groq.com/openai/v1",
AuthStyle::Bearer,
),
(
"deepseek",
"DeepSeek",
"https://api.deepseek.com/v1",
AuthStyle::Bearer,
),
(
"minimax",
"MiniMax",
"https://api.minimax.io/anthropic",
AuthStyle::Anthropic,
),
(
"sumopod",
"SumoPod",
"https://ai.sumopod.com/v1",
AuthStyle::Bearer,
),
] {
let mut entry = CloudProviderCreds {
id: format!("p_{slug}"),
legacy_type: Some(slug.to_string()),
..Default::default()
};
migrate_legacy_fields(&mut entry);
assert_eq!(entry.slug, slug);
assert_eq!(entry.label, label);
assert_eq!(entry.endpoint, endpoint);
assert_eq!(entry.auth_style, auth_style);
}
}
#[test]
fn builtin_cloud_provider_slugs_are_unique() {
let mut slugs = std::collections::HashSet::new();
for provider in BUILTIN_CLOUD_PROVIDERS {
assert!(
slugs.insert(provider.slug),
"duplicate built-in cloud provider slug {}",
provider.slug
);
}
}
}
+40 -37
View File
@@ -12,46 +12,49 @@ Unified inference domain: the canonical home for everything LLM/STT/TTS/embeddin
- Run ChatGPT/Codex OAuth (PKCE) for the `openai` cloud slug and persist tokens in the encrypted auth-profile store.
- Expose an OpenAI-compatible `/v1/*` HTTP endpoint guarded by a stable user-managed external bearer.
- Detect device hardware profile and recommend/apply local model presets/tiers.
- Maintain the built-in BYOK provider preset catalog used by Settings > AI.
The current matrix lives in `docs/inference-provider-catalog.md`; credentials
are stored under `provider:<slug>` in the auth-profile store.
## Key files
| File / dir | Role |
| --- | --- |
| `mod.rs` | Domain root; module decls + re-exports; wires `inference.*` controller schemas/controllers. |
| `ops.rs` | Canonical handler file — `inference_*` business logic returning `RpcOutcome<T>`; delegates to `local`, `provider`, `sentiment`, `device`, `presets`, `openai_oauth`. Includes Sentry-noise suppression for expected provider/user-config failures. |
| `schemas.rs` | `inference.*` controller schemas + `handle_*` fns + param DTOs. |
| `types.rs` | Serde DTOs: `LocalAiStatus`, `LocalAiAssetsStatus`, `LocalAiDownloadsProgress`, `LocalAiEmbeddingResult`, `LocalAiSpeechResult`, `LocalAiTtsResult`, etc. |
| `device.rs` | `DeviceProfile` hardware detection (RAM/CPU/GPU/OS), cached. |
| `model_ids.rs` | Effective chat/vision/embedding/STT/TTS/quantization model id resolution from config. |
| `model_context.rs` | Known model context-window sizes (`context_window_for_model`) for pre-dispatch budgeting. |
| `presets.rs` | `ModelPreset`, `ModelTier`, `VisionMode`; tier recommendation + apply-to-config; MVP preset gating. |
| `sentiment.rs` | `SentimentResult` + emotion/valence analysis via the local model. |
| `parse.rs` / `paths.rs` | Output parsing helpers / on-disk model artifact paths. |
| `local/` | Local runtime manager (was `local_ai/`). |
| `local/core.rs` | `LocalAiService` singleton (`global`/`try_global`), `model_artifact_path`. |
| `local/ops.rs` | Local RPC entrypoints (`local_ai_status/prompt/summarize/vision_prompt/embed/should_react`, `ReactionDecision`); re-exported as `local::rpc`. |
| `local/schemas.rs` | `local_ai.*` controller schemas + handlers. |
| `local/ollama.rs`, `local/lm_studio.rs` | Provider-specific runtime drivers; base-url resolution. |
| `local/install*.rs`, `local/voice_install_common.rs` | Whisper/Piper install + shared download logic. |
| `local/model_requirements.rs` | `MIN_CONTEXT_TOKENS`, `evaluate_context`, `ContextEligibility`. |
| `local/service/` | `LocalAiService` impl split: `bootstrap`, `ollama_admin`, `public_infer`, `speech`, `vision_embed`, `whisper_engine`, `assets`, `spawn_marker`. |
| `provider/` | Unified provider abstraction (was `providers/`). |
| `provider/traits.rs` | `Provider` trait + `ChatMessage`/`ChatRequest`/`ChatResponse`/`ToolCall`/`UsageInfo`/`ProviderDelta` etc. |
| `provider/factory.rs` | `create_chat_provider`, `provider_for_role`, provider-string grammar, local/cloud construction; `BYOK_INCOMPLETE_SENTINEL`. |
| `provider/router.rs` | `RouterProvider` hint-based multi-model routing. |
| `provider/reliable.rs` | Retry/backoff wrapper. |
| `provider/compatible*.rs` | OpenAI-compatible provider (request dump/parse/stream/types). |
| `provider/openhuman_backend.rs` | Managed OpenHuman backend provider (session JWT). |
| `provider/claude_agent_sdk/` | Claude Agent SDK subprocess provider (`protocol.rs`, `subprocess.rs`). |
| `provider/config_rejection.rs`, `provider/billing_error.rs` | Error classifiers (unknown-model / config rejection / budget exhausted). |
| `provider/temperature.rs`, `provider/thread_context.rs` | Per-workload temperature override; thread context plumbing. |
| `provider/ops.rs` | `list_configured_models`, SessionExpired publishing on auth failure. |
| `provider/schemas.rs` | Provider-layer schemas. |
| `voice/` | Inference implementations imported by `crate::openhuman::voice`. |
| `voice/cloud_transcribe.rs`, `voice/local_transcribe.rs`, `voice/local_speech.rs` | STT (cloud + local) and local TTS. |
| `voice/streaming.rs`, `voice/postprocess.rs`, `voice/hallucination.rs` | Streaming transcription, post-processing, hallucination filtering. |
| `openai_oauth/` | ChatGPT/Codex OAuth: `config.rs` (Codex OAuth config), `flow.rs` (start/complete/status/disconnect), `store.rs` (token persistence). |
| `http/` | OpenAI-compatible endpoint: `server.rs` (`router()`), `types.rs`; `EXTERNAL_OPENAI_COMPAT_PROVIDER` bearer id. |
| File / dir | Role |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mod.rs` | Domain root; module decls + re-exports; wires `inference.*` controller schemas/controllers. |
| `ops.rs` | Canonical handler file — `inference_*` business logic returning `RpcOutcome<T>`; delegates to `local`, `provider`, `sentiment`, `device`, `presets`, `openai_oauth`. Includes Sentry-noise suppression for expected provider/user-config failures. |
| `schemas.rs` | `inference.*` controller schemas + `handle_*` fns + param DTOs. |
| `types.rs` | Serde DTOs: `LocalAiStatus`, `LocalAiAssetsStatus`, `LocalAiDownloadsProgress`, `LocalAiEmbeddingResult`, `LocalAiSpeechResult`, `LocalAiTtsResult`, etc. |
| `device.rs` | `DeviceProfile` hardware detection (RAM/CPU/GPU/OS), cached. |
| `model_ids.rs` | Effective chat/vision/embedding/STT/TTS/quantization model id resolution from config. |
| `model_context.rs` | Known model context-window sizes (`context_window_for_model`) for pre-dispatch budgeting. |
| `presets.rs` | `ModelPreset`, `ModelTier`, `VisionMode`; tier recommendation + apply-to-config; MVP preset gating. |
| `sentiment.rs` | `SentimentResult` + emotion/valence analysis via the local model. |
| `parse.rs` / `paths.rs` | Output parsing helpers / on-disk model artifact paths. |
| `local/` | Local runtime manager (was `local_ai/`). |
| `local/core.rs` | `LocalAiService` singleton (`global`/`try_global`), `model_artifact_path`. |
| `local/ops.rs` | Local RPC entrypoints (`local_ai_status/prompt/summarize/vision_prompt/embed/should_react`, `ReactionDecision`); re-exported as `local::rpc`. |
| `local/schemas.rs` | `local_ai.*` controller schemas + handlers. |
| `local/ollama.rs`, `local/lm_studio.rs` | Provider-specific runtime drivers; base-url resolution. |
| `local/install*.rs`, `local/voice_install_common.rs` | Whisper/Piper install + shared download logic. |
| `local/model_requirements.rs` | `MIN_CONTEXT_TOKENS`, `evaluate_context`, `ContextEligibility`. |
| `local/service/` | `LocalAiService` impl split: `bootstrap`, `ollama_admin`, `public_infer`, `speech`, `vision_embed`, `whisper_engine`, `assets`, `spawn_marker`. |
| `provider/` | Unified provider abstraction (was `providers/`). |
| `provider/traits.rs` | `Provider` trait + `ChatMessage`/`ChatRequest`/`ChatResponse`/`ToolCall`/`UsageInfo`/`ProviderDelta` etc. |
| `provider/factory.rs` | `create_chat_provider`, `provider_for_role`, provider-string grammar, local/cloud construction; `BYOK_INCOMPLETE_SENTINEL`. |
| `provider/router.rs` | `RouterProvider` hint-based multi-model routing. |
| `provider/reliable.rs` | Retry/backoff wrapper. |
| `provider/compatible*.rs` | OpenAI-compatible provider (request dump/parse/stream/types). |
| `provider/openhuman_backend.rs` | Managed OpenHuman backend provider (session JWT). |
| `provider/claude_agent_sdk/` | Claude Agent SDK subprocess provider (`protocol.rs`, `subprocess.rs`). |
| `provider/config_rejection.rs`, `provider/billing_error.rs` | Error classifiers (unknown-model / config rejection / budget exhausted). |
| `provider/temperature.rs`, `provider/thread_context.rs` | Per-workload temperature override; thread context plumbing. |
| `provider/ops.rs` | `list_configured_models`, SessionExpired publishing on auth failure. |
| `provider/schemas.rs` | Provider-layer schemas. |
| `voice/` | Inference implementations imported by `crate::openhuman::voice`. |
| `voice/cloud_transcribe.rs`, `voice/local_transcribe.rs`, `voice/local_speech.rs` | STT (cloud + local) and local TTS. |
| `voice/streaming.rs`, `voice/postprocess.rs`, `voice/hallucination.rs` | Streaming transcription, post-processing, hallucination filtering. |
| `openai_oauth/` | ChatGPT/Codex OAuth: `config.rs` (Codex OAuth config), `flow.rs` (start/complete/status/disconnect), `store.rs` (token persistence). |
| `http/` | OpenAI-compatible endpoint: `server.rs` (`router()`), `types.rs`; `EXTERNAL_OPENAI_COMPAT_PROVIDER` bearer id. |
## Public surface
@@ -225,6 +225,7 @@ fn subagent_lifecycle_records_and_clears_active() {
dedicated_thread: false,
prompt_chars: 42,
worker_thread_id: None,
display_name: None,
});
let s = m.snapshot();
assert_eq!(s.active_subagent.as_deref(), Some("researcher"));