From 702b57c30d0e4f3cae2d188f342710d3dd10cf82 Mon Sep 17 00:00:00 2001 From: Ridho Perdana Date: Thu, 17 Sep 2026 17:59:45 +0700 Subject: [PATCH] fix(opencode-go): route every responses-only model (incl. thinking variants) to /responses Derive responses-only routing from the model registry's targetFormat instead of hardcoding model checks, and strip thinking suffixes when looking up models in providerModels so variants like gpt-5.6-luna(high) are routed correctly to /responses. --- open-sse/config/providerModels.js | 9 ++++++--- open-sse/executors/opencode-go.js | 8 ++++++-- tests/unit/opencode-go-models.test.js | 14 +++++++++++++- .../opencode-go-muse-spark-responses.test.js | 16 ++++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/open-sse/config/providerModels.js b/open-sse/config/providerModels.js index 065def24..afa98fa7 100644 --- a/open-sse/config/providerModels.js +++ b/open-sse/config/providerModels.js @@ -27,11 +27,14 @@ const DOT_VERSION_PROVIDERS = new Set(["kr", "kiro"]); // ("claude-sonnet-4-5" ~= "claude-sonnet-4.5"). Other providers use exact match only. function findModel(models, modelId, aliasOrId) { if (!models) return undefined; - const found = models.find(m => m.id === modelId); + const baseModelId = typeof modelId === "string" + ? modelId.replace(/\([^()]+\)\s*$/, "").trim() + : modelId; + const found = models.find(m => m.id === modelId || m.id === baseModelId); if (found) return found; if (!DOT_VERSION_PROVIDERS.has(aliasOrId)) return undefined; - const normalized = normalizeModelId(modelId); - if (normalized === modelId) return undefined; + const normalized = normalizeModelId(baseModelId); + if (normalized === baseModelId) return undefined; return models.find(m => m.id === normalized); } diff --git a/open-sse/executors/opencode-go.js b/open-sse/executors/opencode-go.js index efb656ac..c7065ea7 100644 --- a/open-sse/executors/opencode-go.js +++ b/open-sse/executors/opencode-go.js @@ -1,7 +1,8 @@ import crypto from "node:crypto"; import { DefaultExecutor } from "./default.js"; import { resolveSessionId } from "../utils/sessionManager.js"; -import { isMuseSparkModel } from "../providers/models/helpers.js"; +import { modelTargetFormat } from "../providers/models/schema.js"; +import { getProviderModels } from "../config/providerModels.js"; import { normalizeResponsesInput, clampResponsesCallId, @@ -45,8 +46,11 @@ function baseModelId(model) { return String(model || "").replace(/\([^()]+\)\s*$/, "").trim(); } +// Responses-only per the provider registry (grok-4.6, gpt-5.6-luna, muse-spark, …). +// Reading the registry keeps this in sync with config — never hardcode model ids here. function isResponsesModel(model) { - return isMuseSparkModel(baseModelId(model)); + const entry = getProviderModels("opencode-go").find((m) => m.id === baseModelId(model)); + return modelTargetFormat(entry) === "openai-responses"; } // Flatten Chat Completions tool declarations into the Responses flat shape and diff --git a/tests/unit/opencode-go-models.test.js b/tests/unit/opencode-go-models.test.js index 4ee2042a..92e7a059 100644 --- a/tests/unit/opencode-go-models.test.js +++ b/tests/unit/opencode-go-models.test.js @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { PROVIDER_MODELS, getModelSupportedFormats } from "../../open-sse/config/providerModels.js"; +import { PROVIDER_MODELS, getModelSupportedFormats, getModelTargetFormat } from "../../open-sse/config/providerModels.js"; import { PROVIDERS } from "../../open-sse/config/providers.js"; import { resolveTransport } from "../../open-sse/services/provider.js"; @@ -37,6 +37,18 @@ describe("OpenCode Go model catalog", () => { }); }); +describe("OpenCode Go thinking-suffix model lookup", () => { + it("preserves Responses routing for gpt-5.6-luna thinking variants", () => { + expect(getModelSupportedFormats("opencode-go", "gpt-5.6-luna(high)")).toEqual(["openai-responses"]); + expect(getModelTargetFormat("opencode-go", "gpt-5.6-luna(high)")).toBe("openai-responses"); + }); + + it("preserves Responses routing for grok-4.6 thinking variants", () => { + expect(getModelSupportedFormats("opencode-go", "grok-4.6(high)")).toEqual(["openai-responses"]); + expect(getModelTargetFormat("opencode-go", "grok-4.6(high)")).toBe("openai-responses"); + }); +}); + describe("OpenCode Go per-model supportedFormats", () => { it("declares [openai, claude] for MiniMax + Qwen models", () => { for (const m of CLAUDE_CAPABLE) { diff --git a/tests/unit/opencode-go-muse-spark-responses.test.js b/tests/unit/opencode-go-muse-spark-responses.test.js index 4f7dcb3d..cbe5c349 100644 --- a/tests/unit/opencode-go-muse-spark-responses.test.js +++ b/tests/unit/opencode-go-muse-spark-responses.test.js @@ -48,6 +48,22 @@ describe("ocg/muse-spark-1.3-contributor catalog", () => { }); describe("OpenCodeGoExecutor routing + sanitization", () => { + it("routes gpt-5.6-luna to /responses", () => { + const ex = new OpenCodeGoExecutor(); + expect(ex.buildUrl("gpt-5.6-luna")).toBe("https://opencode.ai/zen/go/v1/responses"); + expect(ex.buildUrl("gpt-5.6-luna(high)", true, 0, { + runtimeTransport: { baseUrl: "https://opencode.ai/zen/go/v1/chat/completions" }, + })).toBe("https://opencode.ai/zen/go/v1/responses"); + }); + + it("routes every responses-only registry model (grok-4.6) to /responses", () => { + const ex = new OpenCodeGoExecutor(); + expect(ex.buildUrl("grok-4.6")).toBe("https://opencode.ai/zen/go/v1/responses"); + expect(ex.buildUrl("grok-4.6(high)", true, 0, { + runtimeTransport: { baseUrl: "https://opencode.ai/zen/go/v1/chat/completions" }, + })).toBe("https://opencode.ai/zen/go/v1/responses"); + }); + it("is wired for opencode-go and routes muse-spark to /responses", () => { expect(getExecutor("opencode-go")).toBeInstanceOf(OpenCodeGoExecutor); const ex = new OpenCodeGoExecutor();