From 499a75f5df3d7a7383ac05951bc50f7e2c083f84 Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Thu, 30 Jul 2026 15:04:26 -0400 Subject: [PATCH] fix(dashboard): re-qualify no-think playground model ids correctly --- .../components/LlmChatCard.tsx | 12 +++++++ tests/unit/playground-model-qualify.test.ts | 34 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/app/(dashboard)/dashboard/media-providers/components/LlmChatCard.tsx b/src/app/(dashboard)/dashboard/media-providers/components/LlmChatCard.tsx index 27a900c541..588661aa37 100644 --- a/src/app/(dashboard)/dashboard/media-providers/components/LlmChatCard.tsx +++ b/src/app/(dashboard)/dashboard/media-providers/components/LlmChatCard.tsx @@ -35,6 +35,10 @@ function resolvePlaygroundKeyId( return keys.find((k) => k.key === selectedMaskedKey)?.id ?? null; } +// Mirrors NO_THINKING_PREFIX in open-sse/utils/noThinkingAlias.ts — kept as a local literal +// (not imported) to avoid pulling server-side catalog modules into the client bundle. +const NO_THINKING_PREFIX = "no-think/"; + /** * Qualify a provider-scoped playground model with its routing prefix so * OmniRoute can resolve it unambiguously. The previous heuristic only prefixed @@ -52,6 +56,14 @@ export function qualifyPlaygroundModel( ): string { const m = (model ?? "").trim(); if (!m || !routingPrefix) return m; + // A no-think id's real wire form is `no-think//` — the provider segment + // sits AFTER the prefix, not at the front, so it needs its own qualification branch instead + // of the generic leading-prefix check below. + if (m.startsWith(NO_THINKING_PREFIX)) { + const inner = m.slice(NO_THINKING_PREFIX.length); + const alreadyQualified = inner === routingPrefix || inner.startsWith(`${routingPrefix}/`); + return alreadyQualified ? m : `${NO_THINKING_PREFIX}${routingPrefix}/${inner}`; + } return m === routingPrefix || m.startsWith(`${routingPrefix}/`) ? m : `${routingPrefix}/${m}`; } diff --git a/tests/unit/playground-model-qualify.test.ts b/tests/unit/playground-model-qualify.test.ts index 14ede8549a..80a1765d84 100644 --- a/tests/unit/playground-model-qualify.test.ts +++ b/tests/unit/playground-model-qualify.test.ts @@ -37,3 +37,37 @@ test("OpenCode Free playground uses its routing alias instead of the reserved pr assert.equal(getProviderAlias("opencode"), "oc"); assert.equal(qualifyPlaygroundModel("big-pickle", getProviderAlias("opencode")), "oc/big-pickle"); }); + +test("qualifyPlaygroundModel inserts the provider after the no-think prefix, not before it", () => { + assert.equal( + qualifyPlaygroundModel("no-think/claude-sonnet-5", "vertex"), + "no-think/vertex/claude-sonnet-5" + ); +}); + +test("qualifyPlaygroundModel does not double-qualify an already-qualified no-think id", () => { + assert.equal( + qualifyPlaygroundModel("no-think/vertex/claude-sonnet-5", "vertex"), + "no-think/vertex/claude-sonnet-5" + ); +}); + +test("qualifyPlaygroundModel does not mistake a provider-name-prefix collision for already-qualified", () => { + // routingPrefix "vertex" must not match "vertex-eu/..." as already-qualified just because + // it starts with the same characters — the check requires an exact "vertex/" segment + // boundary. A naive `inner.startsWith(routingPrefix)` (no slash) would wrongly skip + // qualification here and leave the provider segment un-inserted. + assert.equal( + qualifyPlaygroundModel("no-think/vertex-eu/claude-sonnet-5", "vertex"), + "no-think/vertex/vertex-eu/claude-sonnet-5" + ); +}); + +test("LlmChatCard's local NO_THINKING_PREFIX literal matches the canonical constant", async () => { + // Drift guard: LlmChatCard.tsx deliberately hardcodes "no-think/" as a literal instead + // of importing NO_THINKING_PREFIX from open-sse/utils/noThinkingAlias.ts (avoids pulling + // server-side catalog modules into the client bundle — see Step 1). This test file is not + // client-bundled, so it can safely import the real constant and assert they never drift. + const { NO_THINKING_PREFIX } = await import("../../open-sse/utils/noThinkingAlias.ts"); + assert.equal(NO_THINKING_PREFIX, "no-think/"); +});