From 6dda49e90d5c423802fcb453aa473c1995c9cfd7 Mon Sep 17 00:00:00 2001 From: claude-ceo-assistant Date: Mon, 25 May 2026 07:13:23 -0700 Subject: [PATCH] Support explicit platform provider labels --- .../src/components/ProviderModelSelector.tsx | 7 ++++++ .../__tests__/ProviderModelSelector.test.tsx | 24 +++++++++++++++++++ canvas/src/lib/deploy-preflight.ts | 1 + .../internal/handlers/templates.go | 1 + .../internal/handlers/templates_test.go | 4 ++++ 5 files changed, 37 insertions(+) diff --git a/canvas/src/components/ProviderModelSelector.tsx b/canvas/src/components/ProviderModelSelector.tsx index 628a31adc..e04fbc6d9 100644 --- a/canvas/src/components/ProviderModelSelector.tsx +++ b/canvas/src/components/ProviderModelSelector.tsx @@ -28,6 +28,7 @@ import { useId, useMemo } from "react"; export interface SelectorModel { id: string; name?: string; + provider?: string; required_env?: string[]; } @@ -88,6 +89,7 @@ interface Props { /** Vendor keys → human label. Add new vendors here when templates pick * up new model families. */ const VENDOR_LABELS: Record = { + "platform": "Platform", "anthropic-oauth": "Claude Code subscription", anthropic: "Anthropic API", minimax: "MiniMax", @@ -118,6 +120,8 @@ const VENDOR_LABELS: Record = { /** Optional per-vendor tooltip shown on hover. */ const VENDOR_TOOLTIPS: Record = { + "platform": + "Use the Molecule platform-managed LLM proxy. No vendor API key is required.", "anthropic-oauth": "Use your Claude.ai (Pro/Max/Team) subscription via OAuth. Run `claude login` in the workspace terminal to mint the token, then paste it here. No API spend.", anthropic: @@ -165,6 +169,9 @@ const BARE_VENDOR_PATTERNS: Array<{ test: (id: string) => boolean; vendor: strin /** Infer a vendor key from a model spec. Combines id-prefix and env * signals. Exported for tests. */ export function inferVendor(model: SelectorModel): string { + const explicitProvider = model.provider?.trim().toLowerCase(); + if (explicitProvider) return explicitProvider; + const id = model.id || ""; const envSet = new Set(model.required_env ?? []); diff --git a/canvas/src/components/__tests__/ProviderModelSelector.test.tsx b/canvas/src/components/__tests__/ProviderModelSelector.test.tsx index c98a4dbe1..18ef5c4e2 100644 --- a/canvas/src/components/__tests__/ProviderModelSelector.test.tsx +++ b/canvas/src/components/__tests__/ProviderModelSelector.test.tsx @@ -44,6 +44,14 @@ const HERMES_MODELS: SelectorModel[] = [ ]; describe("inferVendor", () => { + it("uses explicit provider metadata before slug heuristics", () => { + expect(inferVendor({ + id: "moonshot/kimi-k2.6", + provider: "platform", + required_env: [], + })).toBe("platform"); + }); + it("uses slash prefix when present", () => { expect(inferVendor({ id: "nousresearch/hermes-4-70b", required_env: ["HERMES_API_KEY"] })) .toBe("nousresearch"); @@ -105,6 +113,22 @@ describe("buildProviderCatalog", () => { expect(oauth!.models.map((m) => m.id).sort()).toEqual(["haiku", "opus", "sonnet"]); }); + it("labels explicit platform-managed providers", () => { + const catalog = buildProviderCatalog([ + { + id: "moonshot/kimi-k2.6", + name: "Kimi K2.6", + provider: "platform", + required_env: [], + }, + ]); + expect(catalog[0]).toMatchObject({ + vendor: "platform", + label: "Platform", + envVars: [], + }); + }); + it("flags wildcard providers", () => { const catalog = buildProviderCatalog(HERMES_MODELS); const hf = catalog.find((p) => p.vendor === "huggingface"); diff --git a/canvas/src/lib/deploy-preflight.ts b/canvas/src/lib/deploy-preflight.ts index 82f699e5e..aa108e9f7 100644 --- a/canvas/src/lib/deploy-preflight.ts +++ b/canvas/src/lib/deploy-preflight.ts @@ -21,6 +21,7 @@ import { api } from "./api"; export interface ModelSpec { id: string; name?: string; + provider?: string; required_env?: string[]; } diff --git a/workspace-server/internal/handlers/templates.go b/workspace-server/internal/handlers/templates.go index 810aabf5a..7ccdde618 100644 --- a/workspace-server/internal/handlers/templates.go +++ b/workspace-server/internal/handlers/templates.go @@ -77,6 +77,7 @@ func NewTemplatesHandler(configsDir string, dockerCli *client.Client, wh *Worksp type modelSpec struct { ID string `json:"id" yaml:"id"` Name string `json:"name,omitempty" yaml:"name"` + Provider string `json:"provider,omitempty" yaml:"provider"` RequiredEnv []string `json:"required_env,omitempty" yaml:"required_env"` } diff --git a/workspace-server/internal/handlers/templates_test.go b/workspace-server/internal/handlers/templates_test.go index 7640f0a32..31508d207 100644 --- a/workspace-server/internal/handlers/templates_test.go +++ b/workspace-server/internal/handlers/templates_test.go @@ -155,6 +155,7 @@ runtime_config: required_env: [HERMES_API_KEY] - id: minimax/minimax-m2.7 name: MiniMax M2.7 (via OpenRouter) + provider: platform required_env: [OPENROUTER_API_KEY] skills: [] ` @@ -197,6 +198,9 @@ skills: [] if got.Models[1].ID != "minimax/minimax-m2.7" { t.Errorf("Models[1].ID: got %q", got.Models[1].ID) } + if got.Models[1].Provider != "platform" { + t.Errorf("Models[1].Provider: got %q", got.Models[1].Provider) + } if len(got.Models[1].RequiredEnv) != 1 || got.Models[1].RequiredEnv[0] != "OPENROUTER_API_KEY" { t.Errorf("Models[1] required_env: want [OPENROUTER_API_KEY], got %+v", got.Models[1].RequiredEnv) } -- 2.52.0