From 4b6c652b486f8e2f29a8beacd5b8db0696552ab4 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:16:31 -0300 Subject: [PATCH] fix(providers): correct opencode-zen muse-spark context length and Responses auth header (#12681, #12633) - Declare the real ~1M contextLength/maxOutputTokens on the muse-spark-1.2 / muse-spark-1.2-contributor-free registry entries (opencode + opencode-zen) instead of silently falling back to the 200000 provider default (#12681). - Send x-api-key instead of Authorization: Bearer for the openai-responses format on the main OpenCode Zen host, fixing a 401 on Muse Spark Contributor's /v1/responses route; scoped by baseUrl so opencode-go (a different upstream) keeps Bearer (#12633). --- ...2633-opencode-zen-responses-auth-header.md | 1 + ...2681-opencode-muse-spark-context-length.md | 1 + .../providers/registry/opencode/index.ts | 8 +++ .../providers/registry/opencode/zen/index.ts | 8 +++ open-sse/executors/opencode.ts | 23 +++++++- ...-12633-opencode-zen-responses-auth.test.ts | 54 +++++++++++++++++++ ...-12681-opencode-muse-spark-context.test.ts | 33 ++++++++++++ 7 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/12633-opencode-zen-responses-auth-header.md create mode 100644 changelog.d/fixes/12681-opencode-muse-spark-context-length.md create mode 100644 tests/unit/issue-12633-opencode-zen-responses-auth.test.ts create mode 100644 tests/unit/issue-12681-opencode-muse-spark-context.test.ts diff --git a/changelog.d/fixes/12633-opencode-zen-responses-auth-header.md b/changelog.d/fixes/12633-opencode-zen-responses-auth-header.md new file mode 100644 index 0000000000..9877207035 --- /dev/null +++ b/changelog.d/fixes/12633-opencode-zen-responses-auth-header.md @@ -0,0 +1 @@ +- fix(providers): send `x-api-key` instead of `Authorization: Bearer` for OpenCode Zen's `/v1/responses` endpoint (Muse Spark Contributor models), fixing a 401 on OmniRoute's auth header (#12633) diff --git a/changelog.d/fixes/12681-opencode-muse-spark-context-length.md b/changelog.d/fixes/12681-opencode-muse-spark-context-length.md new file mode 100644 index 0000000000..aaec199255 --- /dev/null +++ b/changelog.d/fixes/12681-opencode-muse-spark-context-length.md @@ -0,0 +1 @@ +- fix(models): declare the real ~1M contextLength for OpenCode Zen's Muse Spark 1.2 models instead of falling back to the 200000 provider default (#12681) diff --git a/open-sse/config/providers/registry/opencode/index.ts b/open-sse/config/providers/registry/opencode/index.ts index 07f228b77e..e402a6e9cf 100644 --- a/open-sse/config/providers/registry/opencode/index.ts +++ b/open-sse/config/providers/registry/opencode/index.ts @@ -30,17 +30,25 @@ export const opencodeProvider: RegistryEntry = { // content (see issue #10867). The opencode provider is passthrough, so // declaring them here only sets the wire format / capability flags — the // live upstream model list already advertises both ids. + // #12681: real window confirmed against the opencode-go registry's own + // muse-spark-1.2-contributor entries (contextLength: 1048576, maxOutputTokens: + // 131072) — without an explicit value here resolution fell back to the + // provider-wide defaultContextLength (200000), understating the real window. { id: "muse-spark-1.2", name: "Muse Spark 1.2", supportsReasoning: true, targetFormat: "openai-responses", + contextLength: 1048576, + maxOutputTokens: 131072, }, { id: "muse-spark-1.2-contributor-free", name: "Muse Spark 1.2 Contributor Free", supportsReasoning: true, targetFormat: "openai-responses", + contextLength: 1048576, + maxOutputTokens: 131072, }, { id: "deepseek-v4-flash-free", name: "DeepSeek V4 Flash Free", supportsReasoning: true }, // #6998: 2026-07-14 refresh — the upstream free tier rotated its lineup; diff --git a/open-sse/config/providers/registry/opencode/zen/index.ts b/open-sse/config/providers/registry/opencode/zen/index.ts index 64a9ccf0cb..75849d65a6 100644 --- a/open-sse/config/providers/registry/opencode/zen/index.ts +++ b/open-sse/config/providers/registry/opencode/zen/index.ts @@ -63,11 +63,17 @@ export const opencode_zenProvider: RegistryEntry = { // targetFormat declaration, so requests routed here still hit // /chat/completions with a mismatched or unanswerable body and the // upstream returns an empty message. + // #12681: real window confirmed against the opencode-go registry's own + // muse-spark-1.2-contributor entries (contextLength: 1048576, maxOutputTokens: + // 131072) — without an explicit value here resolution fell back to the + // provider-wide defaultContextLength (200000), understating the real window. { id: "muse-spark-1.2", name: "Muse Spark 1.2", supportsReasoning: true, targetFormat: "openai-responses", + contextLength: 1048576, + maxOutputTokens: 131072, }, // Explicit wire-format overlay of the base opencode provider's muse-spark entry // (targetFormat: openai-responses). Keep in sync with base on catalog syncs. @@ -76,6 +82,8 @@ export const opencode_zenProvider: RegistryEntry = { name: "Muse Spark 1.2 Contributor Free", supportsReasoning: true, targetFormat: "openai-responses", + contextLength: 1048576, + maxOutputTokens: 131072, }, // ── DeepSeek ──────────────────────────────────────────────── diff --git a/open-sse/executors/opencode.ts b/open-sse/executors/opencode.ts index 3431b5c5e6..3eb4180ac3 100644 --- a/open-sse/executors/opencode.ts +++ b/open-sse/executors/opencode.ts @@ -31,6 +31,13 @@ import { import { isOpencodeGeoBlocked, proxyKeyOf } from "./opencodeGeoBlock.ts"; import { isNetworkRotationSharedEgressGuardEnabled } from "@/shared/utils/featureFlags"; +/** + * The main OpenCode Zen host, shared by the `opencode` and `opencode-zen` + * registry entries. Used to scope the `x-api-key` auth override (#12633) away + * from `opencode-go`, which serves a different upstream (`.../zen/go/v1`). + */ +const ZEN_BASE_URL = "https://opencode.ai/zen/v1"; + /** * Per-account proxy configuration, persisted by NoAuthAccountCard under * `providerSpecificData.accountProxies` (keyed by the account id, which the UI @@ -776,6 +783,20 @@ export class OpencodeExecutor extends BaseExecutor { } } + /** + * #12633: OpenCode Zen's `/v1/responses` endpoint (reached when + * `_requestFormat === "openai-responses"`, e.g. Muse Spark Contributor + * models) requires `x-api-key`, not `Authorization: Bearer` — unlike the + * default `/chat/completions` endpoint on the same host, which accepts + * Bearer. Scoped by baseUrl (not provider id/alias) so this only applies to + * the main Zen host (`opencode` / `opencode-zen`, both `https://opencode.ai/zen/v1`) + * and never to opencode-go, which serves Responses-format models from a + * different upstream (`https://opencode.ai/zen/go/v1`) that expects Bearer. + */ + private usesZenApiKeyAuth(): boolean { + return this._requestFormat === "openai-responses" && this.config?.baseUrl === ZEN_BASE_URL; + } + buildHeaders( credentials: ProviderCredentials | null, stream = true, @@ -792,7 +813,7 @@ export class OpencodeExecutor extends BaseExecutor { : undefined; if (key) { - if (this._requestFormat === "claude") { + if (this._requestFormat === "claude" || this.usesZenApiKeyAuth()) { headers["x-api-key"] = key; } else { headers["Authorization"] = `Bearer ${key}`; diff --git a/tests/unit/issue-12633-opencode-zen-responses-auth.test.ts b/tests/unit/issue-12633-opencode-zen-responses-auth.test.ts new file mode 100644 index 0000000000..707ecdae86 --- /dev/null +++ b/tests/unit/issue-12633-opencode-zen-responses-auth.test.ts @@ -0,0 +1,54 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts"; + +test("#12633: openai-responses format on opencode-zen sends x-api-key, not Authorization Bearer", () => { + const executor = new OpencodeExecutor("opencode-zen"); + executor._requestFormat = "openai-responses"; + const headers = executor.buildHeaders( + { apiKey: "sk-zen-test" }, + true, + null, + "muse-spark-1.2-contributor-free" + ); + + assert.equal(headers["x-api-key"], "sk-zen-test"); + assert.equal(headers["Authorization"], undefined); +}); + +test("#12633: openai-responses format on the base opencode (oc) provider also sends x-api-key", () => { + const executor = new OpencodeExecutor("opencode"); + executor._requestFormat = "openai-responses"; + const headers = executor.buildHeaders( + { apiKey: "sk-oc-test" }, + true, + null, + "muse-spark-1.2-contributor-free" + ); + + assert.equal(headers["x-api-key"], "sk-oc-test"); + assert.equal(headers["Authorization"], undefined); +}); + +test("#12633: openai-responses format on opencode-go (different upstream endpoint) keeps Authorization Bearer", () => { + const executor = new OpencodeExecutor("opencode-go"); + executor._requestFormat = "openai-responses"; + const headers = executor.buildHeaders( + { apiKey: "sk-go-test" }, + true, + null, + "muse-spark-1.2-contributor" + ); + + assert.equal(headers["Authorization"], "Bearer sk-go-test"); + assert.equal(headers["x-api-key"], undefined); +}); + +test("#12633: claude format keeps sending x-api-key (unchanged behavior)", () => { + const executor = new OpencodeExecutor("opencode-zen"); + executor._requestFormat = "claude"; + const headers = executor.buildHeaders({ apiKey: "sk-claude-test" }, true, null, "some-model"); + + assert.equal(headers["x-api-key"], "sk-claude-test"); + assert.equal(headers["Authorization"], undefined); +}); diff --git a/tests/unit/issue-12681-opencode-muse-spark-context.test.ts b/tests/unit/issue-12681-opencode-muse-spark-context.test.ts new file mode 100644 index 0000000000..b08a7af242 --- /dev/null +++ b/tests/unit/issue-12681-opencode-muse-spark-context.test.ts @@ -0,0 +1,33 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { REGISTRY } from "../../open-sse/config/providerRegistry.ts"; +import { getTokenLimit } from "../../open-sse/services/contextManager.ts"; + +test("#12681: opencode registry declares an explicit real contextLength for muse-spark-1.2 models", () => { + const opencode = REGISTRY["opencode"]; + const museSpark = opencode.models.find((m) => m.id === "muse-spark-1.2"); + const museSparkFree = opencode.models.find((m) => m.id === "muse-spark-1.2-contributor-free"); + assert.notEqual( + museSpark?.contextLength, + undefined, + "muse-spark-1.2 should declare its own real contextLength instead of relying on the 200000 provider default" + ); + assert.notEqual( + museSparkFree?.contextLength, + undefined, + "muse-spark-1.2-contributor-free should declare its own real contextLength instead of relying on the 200000 provider default" + ); +}); + +test("#12681: opencode-zen registry declares an explicit real contextLength for muse-spark-1.2 models", () => { + const zen = REGISTRY["opencode-zen"]; + const museSpark = zen.models.find((m) => m.id === "muse-spark-1.2"); + const museSparkFree = zen.models.find((m) => m.id === "muse-spark-1.2-contributor-free"); + assert.notEqual(museSpark?.contextLength, undefined); + assert.notEqual(museSparkFree?.contextLength, undefined); +}); + +test("#12681: contextManager.getTokenLimit resolves muse-spark-1.2-contributor-free to its real 1M+ window, not the 200000 provider default", () => { + assert.equal(getTokenLimit("opencode", "muse-spark-1.2-contributor-free"), 1048576); + assert.equal(getTokenLimit("opencode-zen", "muse-spark-1.2-contributor-free"), 1048576); +});