feat(providers): dedicated BYOK-vendor providers make hermes/openclaw vendor menus routable (cp#529) #2262

Merged
hongming merged 1 commits from feat/cp529-byok-vendor-providers into main 2026-06-04 23:38:34 +00:00
4 changed files with 154 additions and 15 deletions
@@ -16,7 +16,7 @@ const SchemaVersion = 1
// Fingerprint is a stable content hash of the generated projection (schema
// version + provider catalog + runtime native sets). It changes iff the
// registry DATA changes (comment-only YAML edits do not churn it).
const Fingerprint = "5a741b326b6f812c"
const Fingerprint = "ec6b93409e7b9cf8"
// GenProvider is the generated projection of one provider catalog entry —
// the subset a downstream consumer needs to derive + display a provider.
@@ -71,6 +71,11 @@ var Providers = []GenProvider{
{Name: "nvidia", DisplayName: "NVIDIA NIM", Protocol: "openai", AuthMode: "third_party_anthropic_compat", AuthEnv: []string{"NVIDIA_API_KEY"}, ModelPrefixMatch: "^nvidia[:/]", IsPlatform: false},
{Name: "arcee", DisplayName: "Arcee", Protocol: "openai", AuthMode: "third_party_anthropic_compat", AuthEnv: []string{"ARCEE_API_KEY"}, ModelPrefixMatch: "^arcee[:/]", IsPlatform: false},
{Name: "custom", DisplayName: "Custom OpenAI-compat endpoint", Protocol: "openai", AuthMode: "third_party_anthropic_compat", AuthEnv: []string{"CUSTOM_API_KEY", "OPENAI_API_KEY"}, ModelPrefixMatch: "^custom[:/]", IsPlatform: false},
{Name: "byok-anthropic", DisplayName: "Anthropic (BYOK)", Protocol: "anthropic", AuthMode: "anthropic_api", AuthEnv: []string{"ANTHROPIC_API_KEY"}, ModelPrefixMatch: "^anthropic/", IsPlatform: false},
{Name: "byok-openai", DisplayName: "OpenAI (BYOK)", Protocol: "openai", AuthMode: "anthropic_api", AuthEnv: []string{"OPENAI_API_KEY"}, ModelPrefixMatch: "^openai[:/]", IsPlatform: false},
{Name: "byok-gemini", DisplayName: "Google Gemini (BYOK)", Protocol: "openai", AuthMode: "third_party_anthropic_compat", AuthEnv: []string{"GEMINI_API_KEY", "GOOGLE_API_KEY"}, ModelPrefixMatch: "^gemini/", IsPlatform: false},
{Name: "byok-minimax", DisplayName: "MiniMax (BYOK)", Protocol: "openai", AuthMode: "third_party_anthropic_compat", AuthEnv: []string{"MINIMAX_API_KEY"}, ModelPrefixMatch: "(?i)^(minimax[:/]|codex-minimax-)", IsPlatform: false},
{Name: "groq", DisplayName: "Groq", Protocol: "openai", AuthMode: "third_party_anthropic_compat", AuthEnv: []string{"GROQ_API_KEY"}, ModelPrefixMatch: "^groq:", IsPlatform: false},
}
// Runtimes maps each runtime to its native provider+model set, runtime names
@@ -90,6 +95,7 @@ var Runtimes = map[string][]GenRuntimeRef{
{Name: "openai-subscription", Models: []string{"gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.3-codex-spark", "gpt-5.2"}},
{Name: "openai-api", Models: []string{"gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.3-codex-spark", "gpt-5.2"}},
{Name: "platform", Models: []string{"openai/gpt-5.4", "openai/gpt-5.4-mini"}},
{Name: "byok-minimax", Models: []string{}},
},
"google-adk": {
{Name: "platform", Models: []string{"platform:gemini-2.5-pro", "platform:gemini-2.5-flash"}},
@@ -114,11 +120,18 @@ var Runtimes = map[string][]GenRuntimeRef{
{Name: "zai", Models: []string{}},
{Name: "xiaomi-mimo", Models: []string{}},
{Name: "alibaba", Models: []string{}},
{Name: "byok-anthropic", Models: []string{}},
{Name: "byok-gemini", Models: []string{}},
{Name: "byok-openai", Models: []string{}},
{Name: "byok-minimax", Models: []string{}},
},
"openclaw": {
{Name: "kimi-coding", Models: []string{"moonshot:kimi-k2.6", "moonshot:kimi-k2.5"}},
{Name: "platform", Models: []string{"moonshot/kimi-k2.6", "moonshot/kimi-k2.5"}},
{Name: "openrouter", Models: []string{}},
{Name: "custom", Models: []string{}},
{Name: "byok-openai", Models: []string{}},
{Name: "byok-minimax", Models: []string{}},
{Name: "groq", Models: []string{}},
},
}
@@ -627,6 +627,108 @@ providers:
model_prefix_match: "^custom[:/]"
model_aliases: []
# ===========================================================================
# DEDICATED BYOK-VENDOR providers (cp#529). These exist so the NAMESPACED
# BYOK ids the hermes/openclaw/codex templates offer for the SHARED upstream
# vendors (anthropic, openai, gemini, minimax, groq) become routable with the
# TENANT's OWN vendor key — WITHOUT routing them through the platform-shared
# `platform` provider (which would bill the platform's key: a money bug).
#
# Each is NON-PLATFORM (name != "platform") -> IsPlatform()==false -> BYOK
# billing: the workspace env supplies the vendor key, never the platform key.
#
# COLLISION-FREE BY CONSTRUCTION: every matcher is NAMESPACED (anchored on the
# `vendor/` slash form or `vendor:` colon form) so it is DISJOINT from the
# platform vendors' BARE matchers (anthropic-api `^claude`, openai-subscription
# `^gpt-`, openai-api `^openai-api[:/]`, minimax `(?i)^minimax-m`,
# google `^gemini-`, minimax-cn `^minimax-cn[:/]`). DeriveProvider's overlap
# guard (no slug may match two native providers) stays green — verified for all
# 20 residual ids (cp#529).
#
# These siblings of the platform/upstream vendor entries point at the SAME
# PUBLIC upstream base URLs, but carry NO upstream_vendor (they are BYOK
# passthroughs, not proxy upstream targets — the proxy never dials a tenant's
# own key) and use the namespaced matchers above instead of the bare proxy
# prefixes.
# ===========================================================================
- name: byok-anthropic
display_name: "Anthropic (BYOK)"
vendor_logo: "anthropic"
protocol: anthropic
auth_mode: anthropic_api
base_url_template: "https://api.anthropic.com/v1"
base_url_anthropic: "https://api.anthropic.com/v1"
auth_env: [ANTHROPIC_API_KEY]
auth_token_env: ANTHROPIC_API_KEY
# Namespaced BYOK form `anthropic/<model>` (hermes). DISJOINT from
# anthropic-api's bare `^claude` and anthropic-oauth's alias set.
model_prefix_match: "^anthropic/"
model_aliases: []
- name: byok-openai
display_name: "OpenAI (BYOK)"
vendor_logo: "openai"
protocol: openai
auth_mode: anthropic_api # openai-protocol; auth is a bearer API key.
base_url_template: "https://api.openai.com/v1"
base_url_anthropic: null
auth_env: [OPENAI_API_KEY]
auth_token_env: OPENAI_API_KEY
# Namespaced BYOK forms `openai/<model>` (hermes) + `openai:<model>`
# (openclaw). DISJOINT from openai-subscription's bare `^gpt-` and
# openai-api's `^openai-api[:/]` (the dash after `openai` keeps the two
# apart: `openai:` / `openai/` never start with `openai-api`).
model_prefix_match: "^openai[:/]"
model_aliases: []
- name: byok-gemini
display_name: "Google Gemini (BYOK)"
vendor_logo: "google"
protocol: openai
auth_mode: third_party_anthropic_compat
base_url_template: "https://generativelanguage.googleapis.com/v1beta/openai"
base_url_anthropic: null
auth_env: [GEMINI_API_KEY, GOOGLE_API_KEY]
auth_token_env: ANTHROPIC_AUTH_TOKEN
# Namespaced BYOK form `gemini/<model>` (hermes). DISJOINT from the `google`
# vendor's bare `^gemini-` and `vertex`'s `^vertex:`.
model_prefix_match: "^gemini/"
model_aliases: []
- name: byok-minimax
display_name: "MiniMax (BYOK)"
vendor_logo: "minimax"
protocol: openai
auth_mode: third_party_anthropic_compat
base_url_template: "https://api.minimax.io/v1"
base_url_anthropic: null
auth_env: [MINIMAX_API_KEY]
auth_token_env: ANTHROPIC_AUTH_TOKEN
# Namespaced BYOK forms `minimax:<model>` (openclaw) + `minimax/<model>`
# (hermes), PLUS the codex-runtime alias `codex-minimax-m2.7` (the codex
# template's `minimax-token-plan` route — same upstream api.minimax.io,
# tenant MINIMAX_API_KEY). The `codex-minimax-` leg is NARROWLY anchored so
# it resolves that one codex id WITHOUT a broad matcher: it is DISJOINT from
# `minimax` (?i)^minimax-m (which needs `minimax-m`, not `codex-`) and from
# `minimax-cn` ^minimax-cn[:/]. Verified collision-free for all 20 residual
# ids + codex-minimax-m2.7 (cp#529).
model_prefix_match: "(?i)^(minimax[:/]|codex-minimax-)"
model_aliases: []
- name: groq
display_name: "Groq"
vendor_logo: "groq"
protocol: openai
auth_mode: third_party_anthropic_compat
base_url_template: "https://api.groq.com/openai/v1"
base_url_anthropic: null
auth_env: [GROQ_API_KEY]
auth_token_env: ANTHROPIC_AUTH_TOKEN
# Namespaced BYOK form `groq:<model>` (openclaw). No other provider matches
# the `groq:` prefix.
model_prefix_match: "^groq:"
model_aliases: []
# =============================================================================
# RUNTIME NATIVE SUPPORT MATRIX (RFC #340 — CTO correction 2026-05-26)
# =============================================================================
@@ -792,11 +894,6 @@ runtimes:
# bare-vendor providers into its NATIVE prefix-routing set so the BYOK
# ids the hermes template offers (openrouter/…, huggingface/…, deepseek/…,
# zai:…, etc.) resolve via DeriveProvider. ALL tenant-key (BYOK).
# GUARDRAIL: the platform-shared vendors (openai/gemini/minimax/anthropic
# and groq) are DELIBERATELY ABSENT here — wiring them would route a
# customer model through the platform's key (a money bug); so hermes ids
# like anthropic/claude-*, gemini/*, openai/*, minimax/*, groq:* remain
# unroutable (residual drift) until dedicated BYOK-vendor providers exist.
- name: openrouter
- name: huggingface
- name: ai-gateway
@@ -813,6 +910,17 @@ runtimes:
- name: zai
- name: xiaomi-mimo
- name: alibaba
# DEDICATED BYOK-VENDOR arms (cp#529): the namespaced ids hermes offers for
# the SHARED upstream vendors (anthropic/claude-*, gemini/*, openai/*,
# minimax/*) NOW resolve to these tenant-key BYOK-vendor providers — NOT
# the platform-shared `platform` provider (which would bill the platform's
# key). NAME-ONLY (no models) → no platform-menu change, prefix-routing
# only, BYOK-billed. This converts the last 12 hermes residual ids from
# cp#529 drift to routable.
- name: byok-anthropic
- name: byok-gemini
- name: byok-openai
- name: byok-minimax
# codex: OpenAI — BYOK split across TWO native providers
# (openai-subscription + openai-api), mirroring claude-code's anthropic
@@ -864,6 +972,14 @@ runtimes:
models:
- openai/gpt-5.4
- openai/gpt-5.4-mini
# NAME-ONLY BYOK arm (cp#529): the codex template offers a BYOK MiniMax
# token-plan model `codex-minimax-m2.7` (its `minimax-token-plan` provider:
# base_url api.minimax.io, tenant MINIMAX_API_KEY, model_id_override
# codex-MiniMax-M2.7). It resolves to byok-minimax via the narrowly-anchored
# `codex-minimax-` leg of byok-minimax's matcher (same upstream, tenant key)
# — NOT a broad matcher. NAME-ONLY → no platform-menu change, BYOK-billed.
# Converts the last codex residual id from cp#529 drift to routable.
- name: byok-minimax
# openclaw: native Kimi only. openclaw's moonshot: model prefix + a
# KIMI_API_KEY (sk-kimi-*) routes to api.kimi.com/coding (kimi-for-coding),
@@ -886,11 +1002,17 @@ runtimes:
# but wire openclaw's CONFIRMED-NON-PLATFORM passthroughs into its NATIVE
# prefix-routing set so the BYOK colon/slash ids the openclaw template
# offers (openrouter:…, custom:…) resolve via DeriveProvider. BYOK only.
# GUARDRAIL: the platform-shared openclaw ids openai:*, minimax:*, groq:*
# are DELIBERATELY ABSENT (groq has no provider at all) — they stay
# unroutable residual drift rather than billing the platform's key.
- name: openrouter
- name: custom
# DEDICATED BYOK-VENDOR arms (cp#529): openclaw's default model is
# `minimax:MiniMax-M2.7`, plus it offers `openai:*` and `groq:*` BYOK ids.
# These NOW resolve to the tenant-key BYOK-vendor providers (NOT the
# platform key). NAME-ONLY → prefix-routing only, BYOK-billed. This converts
# the last 7 openclaw residual ids from cp#529 drift to routable AND makes
# the runtime's DEFAULT model (minimax:MiniMax-M2.7) resolve.
- name: byok-openai
- name: byok-minimax
- name: groq
# google-adk: Gemini via Vertex AI, keyless ADC (Workload Identity
@@ -38,14 +38,18 @@ var runtimeNativeProviders = map[string][]string{
"hermes": {"kimi-coding", "platform",
"openrouter", "huggingface", "ai-gateway", "opencode-zen", "opencode-go",
"kilocode", "custom", "nvidia", "arcee", "ollama-cloud", "minimax-cn",
"nousresearch", "deepseek", "zai", "xiaomi-mimo", "alibaba"},
"nousresearch", "deepseek", "zai", "xiaomi-mimo", "alibaba",
// cp#529 dedicated BYOK-vendor name-only arms (shared-vendor namespaced ids).
"byok-anthropic", "byok-gemini", "byok-openai", "byok-minimax"},
// codex's OpenAI BYOK is split across the OAuth subscription arm
// (openai-subscription) and the direct-key arm (openai-api), mirroring
// claude-code's anthropic oauth+api split; platform openai via the proxy
// Responses surface. No name-only BYOK arms (its templates offer no
// passthrough ids).
"codex": {"openai-subscription", "openai-api", "platform"},
"openclaw": {"kimi-coding", "platform", "openrouter", "custom"},
// Responses surface. cp#529 adds the byok-minimax name-only arm so the
// template's BYOK MiniMax token-plan id (codex-minimax-m2.7) resolves.
"codex": {"openai-subscription", "openai-api", "platform", "byok-minimax"},
"openclaw": {"kimi-coding", "platform", "openrouter", "custom",
// cp#529 dedicated BYOK-vendor name-only arms (openai:/minimax:/groq:).
"byok-openai", "byok-minimax", "groq"},
}
func sortedCopy(in []string) []string {
@@ -29,7 +29,7 @@ import (
// canonicalProvidersYAMLSHA256 is the sha256 of the canonical providers.yaml as
// synced from molecule-controlplane. Bumped deliberately on each re-sync (see
// file doc). Cross-checked live by the sync-providers-yaml CI workflow.
const canonicalProvidersYAMLSHA256 = "bd54d8a4b4139175edca1e723496e283e3bb82a5be8da01fd195835338f505db"
const canonicalProvidersYAMLSHA256 = "846ddef11ec423ebf2e96b5da21bd89129dbc3f0a2d14ac086940e005c079387"
func TestSyncedYAMLMatchesCanonicalSHA(t *testing.T) {
sum := sha256.Sum256(embeddedYAML)