From 295189d4a807cdf02cbb42924c9eb2426bc5acc9 Mon Sep 17 00:00:00 2001 From: Ravi Tharuma <25951435+RaviTharuma@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:37:57 +0200 Subject: [PATCH] fix(models): stop inventing chat capabilities for specialty surfaces (#8016) (#8022) Co-authored-by: RaviTharuma Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --- src/lib/modelCapabilities.ts | 50 ++++++++++++++++++- src/lib/modelMetadataRegistry.ts | 33 ++++++++++-- .../model-capabilities-specialty-8016.test.ts | 44 ++++++++++++++++ 3 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 tests/unit/model-capabilities-specialty-8016.test.ts diff --git a/src/lib/modelCapabilities.ts b/src/lib/modelCapabilities.ts index 68d89274b9..07a9daf2fa 100644 --- a/src/lib/modelCapabilities.ts +++ b/src/lib/modelCapabilities.ts @@ -16,7 +16,24 @@ import { getModelContextOverride } from "@/lib/db/modelContextOverrides"; import { getModelCapabilityOverride } from "@/lib/db/modelCapabilityOverrides"; import { isVisionModelId } from "@/shared/constants/visionModels"; -const TOOL_CALLING_UNSUPPORTED_PATTERNS: string[] = []; +const TOOL_CALLING_UNSUPPORTED_PATTERNS: string[] = [ + // Specialty / non-chat surfaces must never inherit optimistic tool defaults (#8016) + "whisper", + "tts-1", + "gpt-4o-mini-tts", + "omni-moderation", + "moderation", + "eleven_multilingual", + "eleven_turbo", + "seedance", + "/veo", + "veo-", + "rerank", + "embedding", + "dall-e", + "flux-", + "stable-diffusion", +]; const REASONING_UNSUPPORTED_PATTERNS = [ "antigravity/claude-sonnet-4-6", "antigravity/claude-sonnet-4-5", @@ -26,8 +43,39 @@ const REASONING_UNSUPPORTED_PATTERNS = [ "antigravity/gpt-oss-", "antigravity/gemini-3", "antigravity/tab_", + // Specialty / non-chat surfaces (#8016) + "whisper", + "tts-1", + "gpt-4o-mini-tts", + "omni-moderation", + "moderation", + "eleven_multilingual", + "eleven_turbo", + "seedance", + "/veo", + "veo-", + "rerank", + "embedding", + "dall-e", + "flux-", + "stable-diffusion", ]; +/** Catalog/API surface types that are not chat completions. */ +const NON_CHAT_SURFACE_TYPES = new Set([ + "audio", + "video", + "image", + "moderation", + "rerank", + "embedding", + "music", +]); + +export function isNonChatCatalogSurface(type: unknown): boolean { + return typeof type === "string" && NON_CHAT_SURFACE_TYPES.has(type); +} + const MAX_TOKENS_UNSUPPORTED_PATTERNS = [ "o1-preview", "o1-mini", diff --git a/src/lib/modelMetadataRegistry.ts b/src/lib/modelMetadataRegistry.ts index fdc61fff32..af1702adf0 100644 --- a/src/lib/modelMetadataRegistry.ts +++ b/src/lib/modelMetadataRegistry.ts @@ -2,7 +2,10 @@ import { randomUUID } from "node:crypto"; import { parseModel } from "@omniroute/open-sse/services/model.ts"; import { getModelInfo } from "@/sse/services/model"; import { getModelAliases } from "@/lib/db/models"; -import { getResolvedModelCapabilities } from "@/lib/modelCapabilities"; +import { + getResolvedModelCapabilities, + isNonChatCatalogSurface, +} from "@/lib/modelCapabilities"; import { getAuthoritativeContextWindow, getAuthoritativeProviderContextWindow, @@ -351,12 +354,24 @@ export function enrichCatalogModelEntry( getAuthoritativeProviderContextWindow(provider, model) ?? getAuthoritativeContextWindow(metadata.model) ?? getAuthoritativeContextWindow(model); + const specialtySurface = isNonChatCatalogSurface(entry.type); const capabilityFields = { ...(typeof metadata.capabilities.vision === "boolean" ? { vision: metadata.capabilities.vision } : {}), - tool_calling: metadata.capabilities.toolCalling, - reasoning: metadata.capabilities.reasoning, + // #8016: never invent chat tool/reasoning defaults onto specialty surfaces. + // Only copy boolean true when authoritative metadata says so; specialty rows + // default to false instead of optimistic chat heuristics. + tool_calling: specialtySurface + ? metadata.capabilities.supportsTools === true || metadata.capabilities.toolCalling === true + ? true + : false + : metadata.capabilities.toolCalling, + reasoning: specialtySurface + ? metadata.capabilities.supportsThinking === true || metadata.capabilities.reasoning === true + ? true + : false + : metadata.capabilities.reasoning, // #6241: surface thinking support + the canonical effort tiers so the frontend can // render the effort/thinking toggles. `thinking` is kept for back-compat; `supportsThinking` // is the explicit flag and `effort_tiers` lists the selectable reasoning levels @@ -403,10 +418,22 @@ export function enrichCatalogModelEntry( } if ( + !specialtySurface && (typeof nextEntry.context_length !== "number" || authoritativeContextWindow !== null) && typeof metadata.limits.contextWindow === "number" ) { nextEntry.context_length = metadata.limits.contextWindow; + } else if ( + specialtySurface && + authoritativeContextWindow !== null && + typeof authoritativeContextWindow === "number" + ) { + // Only authoritative static windows may decorate specialty rows. + nextEntry.context_length = authoritativeContextWindow; + } else if (specialtySurface && typeof nextEntry.context_length === "number") { + // Keep an explicit source-provided context if the emitter already set one. + } else if (specialtySurface) { + delete nextEntry.context_length; } if (typeof metadata.limits.maxOutputTokens === "number" && metadata.limits.maxOutputTokens > 0) { diff --git a/tests/unit/model-capabilities-specialty-8016.test.ts b/tests/unit/model-capabilities-specialty-8016.test.ts new file mode 100644 index 0000000000..a11ebe02ca --- /dev/null +++ b/tests/unit/model-capabilities-specialty-8016.test.ts @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import { + getResolvedModelCapabilities, + isNonChatCatalogSurface, +} from "../../src/lib/modelCapabilities.ts"; +import { enrichCatalogModelEntry } from "../../src/lib/modelMetadataRegistry.ts"; + +describe("specialty catalog surfaces (#8016)", () => { + it("recognizes non-chat catalog types", () => { + assert.equal(isNonChatCatalogSurface("audio"), true); + assert.equal(isNonChatCatalogSurface("video"), true); + assert.equal(isNonChatCatalogSurface("moderation"), true); + assert.equal(isNonChatCatalogSurface("chat"), false); + assert.equal(isNonChatCatalogSurface(undefined), false); + }); + + it("does not optimistically enable tools/reasoning for specialty model ids", () => { + const whisper = getResolvedModelCapabilities({ provider: "openai", model: "whisper-1" }); + assert.equal(whisper.toolCalling, false); + assert.equal(whisper.reasoning, false); + + const tts = getResolvedModelCapabilities({ provider: "openai", model: "tts-1" }); + assert.equal(tts.toolCalling, false); + assert.equal(tts.reasoning, false); + + const veo = getResolvedModelCapabilities({ provider: "veo-free", model: "veo" }); + assert.equal(veo.toolCalling, false); + assert.equal(veo.reasoning, false); + }); + + it("enrichment does not invent chat tool/reasoning on typed specialty rows", () => { + const enriched = enrichCatalogModelEntry({ + id: "openai/whisper-1", + owned_by: "openai", + root: "whisper-1", + type: "audio", + subtype: "transcription", + }); + const caps = (enriched.capabilities || {}) as Record; + assert.equal(caps.tool_calling, false); + assert.equal(caps.reasoning, false); + }); +});