fix(providers): route Muse Spark 1.3 to the Responses API on OpenCode Zen, OpenCode and OpenCode Go (#13471)

Registers Muse Spark 1.3 (with its effort aliases) on OpenCode Zen, OpenCode and OpenCode Go with `targetFormat: openai-responses` and a 1M context window, so the model no longer falls back to `/chat/completions` (#12674, #12698). Superset of #12675, #12973 and #13111, whose authors are credited in the PR.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
This commit is contained in:
Dizzle
2026-09-15 16:08:16 +02:00
committed by GitHub
parent aaf0777a98
commit 5cc3362ef0
7 changed files with 279 additions and 0 deletions

View File

@@ -0,0 +1 @@
- **fix(providers):** Muse Spark 1.3 works on OpenCode Zen, OpenCode and OpenCode Go instead of failing with a 500, and gets its real 1M context window ([#13471](https://github.com/diegosouzapw/OmniRoute/pull/13471)) — thanks @maxmad64bis (with thanks to @bacnh85, @shermzy and @atakhadiviom for #12675, #12973 and #13111)

View File

@@ -226,6 +226,78 @@ export const opencode_goProvider: RegistryEntry = {
supportsVideo: true,
targetFormat: "openai-responses",
},
// #12674: Muse Spark 1.3 Contributor — base + effort-tier aliases from the
// OpenCode Go registry (`opencode models opencode-go --refresh --verbose`;
// exact suffix set: minimal/low/medium/high/xhigh, no max — same as 1.2).
// Upstream serves Muse Spark only on the Responses API; without
// targetFormat:"openai-responses" these fall through to /chat/completions
// and the upstream returns 500 (same class as #12196).
{
id: "muse-spark-1.3-contributor",
name: "Muse Spark 1.3 Contributor",
contextLength: 1048576,
maxOutputTokens: 131072,
supportsReasoning: true,
supportsVision: true,
supportsAudio: true,
supportsVideo: true,
targetFormat: "openai-responses",
},
{
id: "muse-spark-1.3-contributor-minimal",
name: "Muse Spark 1.3 Contributor (minimal effort)",
contextLength: 1048576,
maxOutputTokens: 131072,
supportsReasoning: true,
supportsVision: true,
supportsAudio: true,
supportsVideo: true,
targetFormat: "openai-responses",
},
{
id: "muse-spark-1.3-contributor-low",
name: "Muse Spark 1.3 Contributor (low effort)",
contextLength: 1048576,
maxOutputTokens: 131072,
supportsReasoning: true,
supportsVision: true,
supportsAudio: true,
supportsVideo: true,
targetFormat: "openai-responses",
},
{
id: "muse-spark-1.3-contributor-medium",
name: "Muse Spark 1.3 Contributor (medium effort)",
contextLength: 1048576,
maxOutputTokens: 131072,
supportsReasoning: true,
supportsVision: true,
supportsAudio: true,
supportsVideo: true,
targetFormat: "openai-responses",
},
{
id: "muse-spark-1.3-contributor-high",
name: "Muse Spark 1.3 Contributor (high effort)",
contextLength: 1048576,
maxOutputTokens: 131072,
supportsReasoning: true,
supportsVision: true,
supportsAudio: true,
supportsVideo: true,
targetFormat: "openai-responses",
},
{
id: "muse-spark-1.3-contributor-xhigh",
name: "Muse Spark 1.3 Contributor (xhigh effort)",
contextLength: 1048576,
maxOutputTokens: 131072,
supportsReasoning: true,
supportsVision: true,
supportsAudio: true,
supportsVideo: true,
targetFormat: "openai-responses",
},
// #8353: Grok 4.5 + effort tiers from the OpenCode Go registry.
{
id: "grok-4.5",

View File

@@ -50,6 +50,25 @@ export const opencodeProvider: RegistryEntry = {
contextLength: 1048576,
maxOutputTokens: 131072,
},
// Muse Spark 1.3 is served only on the Responses API, same as 1.2 above.
// Its window matches the published OpenCode catalog instead of the
// 200000 provider default.
{
id: "muse-spark-1.3",
name: "Muse Spark 1.3",
supportsReasoning: true,
targetFormat: "openai-responses",
contextLength: 1048576,
maxOutputTokens: 131072,
},
{
id: "muse-spark-1.3-contributor-free",
name: "Muse Spark 1.3 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;
// minimax-m3-free, minimax-m2.5-free, ling-2.6-1t-free,

View File

@@ -85,6 +85,25 @@ export const opencode_zenProvider: RegistryEntry = {
contextLength: 1048576,
maxOutputTokens: 131072,
},
// Muse Spark 1.3 is served only on the Responses API, same as 1.2 above.
// Its window matches the published OpenCode catalog instead of the
// 200000 provider default.
{
id: "muse-spark-1.3",
name: "Muse Spark 1.3",
supportsReasoning: true,
targetFormat: "openai-responses",
contextLength: 1048576,
maxOutputTokens: 131072,
},
{
id: "muse-spark-1.3-contributor-free",
name: "Muse Spark 1.3 Contributor Free",
supportsReasoning: true,
targetFormat: "openai-responses",
contextLength: 1048576,
maxOutputTokens: 131072,
},
// ── DeepSeek ────────────────────────────────────────────────
// #10788: same tier vocabulary as opencode-go's DeepSeek rows — the Zen

View File

@@ -94,6 +94,8 @@ const OPENCODE_FREE_MODELS = new Set([
* grok-4.5 low/medium/high; hy3 none/low/high; kimi-k3 max;
* qwen3.6-plus / qwen3.7-max / qwen3.7-plus high/max;
* muse-spark-1.2-contributor minimal/low/medium/high/xhigh (no max)
* - #12674 Muse Spark 1.3 Contributor: minimal/low/medium/high/xhigh (no max),
* verified via `opencode models opencode-go --refresh --verbose`
*/
const EFFORT_TIERS: Record<string, readonly string[]> = {
"deepseek-v4-pro": EFFORT_LEVELS,
@@ -107,6 +109,7 @@ const EFFORT_TIERS: Record<string, readonly string[]> = {
"qwen3.7-max": ["high", "max"],
"qwen3.7-plus": ["high", "max"],
"muse-spark-1.2-contributor": ["minimal", "low", "medium", "high", "xhigh"],
"muse-spark-1.3-contributor": ["minimal", "low", "medium", "high", "xhigh"],
};
/**

View File

@@ -0,0 +1,75 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
parseEffortLevel,
resolveOpencodeTargetFormat,
} from "../../open-sse/executors/opencode.ts";
const { REGISTRY } = (await import("../../open-sse/config/providerRegistry.ts")) as {
REGISTRY: Record<
string,
{
models?: Array<{
id: string;
targetFormat?: string;
supportsReasoning?: boolean;
contextLength?: number;
maxOutputTokens?: number;
}>;
}
>;
};
// #12674: opencode-go/muse-spark-1.3-contributor (upstream release 2026-09-02)
// 500s via /v1/chat/completions because the upstream serves Muse Spark only on
// the Responses API and the model had no registry targetFormat entry, so the
// executor fell back to "openai". Base + effort-tier alias set verified via
// `opencode models opencode-go --refresh --verbose` (minimal/low/medium/high/
// xhigh, no max — same as 1.2).
const BASE = "muse-spark-1.3-contributor";
const ALIASES = ["minimal", "low", "medium", "high", "xhigh"].map((effort) => ({
alias: `${BASE}-${effort}`,
effort,
}));
function goModels() {
const entry = REGISTRY["opencode-go"];
assert.ok(entry, "opencode-go registry entry must exist");
return entry.models ?? [];
}
test("#12674 registry: 1.3 base + effort aliases target the Responses API", () => {
const models = goModels();
for (const id of [BASE, ...ALIASES.map((a) => a.alias)]) {
const model = models.find((m) => m.id === id);
assert.ok(model, `${id} must be registered on opencode-go`);
assert.equal(model?.targetFormat, "openai-responses", `${id} must target Responses`);
assert.equal(model?.supportsReasoning, true, `${id} must support reasoning`);
assert.equal(model?.contextLength, 1048576, `${id} context must be 1M`);
assert.equal(model?.maxOutputTokens, 131072, `${id} max output must be 128K`);
}
});
test("#12674 executor: 1.3 resolves to openai-responses (URL selection)", () => {
assert.equal(resolveOpencodeTargetFormat("opencode-go", BASE), "openai-responses");
assert.equal(resolveOpencodeTargetFormat("opencode-go", `${BASE}-high`), "openai-responses");
});
for (const { alias, effort } of ALIASES) {
test(`#12674 parseEffortLevel: ${alias}${effort}`, () => {
assert.deepEqual(parseEffortLevel(alias), { baseModel: BASE, effort });
});
}
test("#12674 parseEffortLevel: 1.3 has no max tier", () => {
assert.equal(parseEffortLevel(`${BASE}-max`), null);
});
test("#12674 catalog: 1.3 aliases stay Go-only (not on opencode-zen)", () => {
const zenIds = new Set((REGISTRY["opencode-zen"]?.models ?? []).map((m) => m.id));
assert.equal(zenIds.has(BASE), false, "opencode-zen must not expose 1.3 base");
for (const { alias } of ALIASES) {
assert.equal(zenIds.has(alias), false, `opencode-zen must not expose ${alias}`);
}
});

View File

@@ -0,0 +1,90 @@
import assert from "node:assert/strict";
import test from "node:test";
import { opencode_zenProvider } from "../../open-sse/config/providers/registry/opencode/zen/index.ts";
import { opencodeProvider } from "../../open-sse/config/providers/registry/opencode/index.ts";
import { getTokenLimit } from "../../open-sse/services/contextManager.ts";
import {
OpencodeExecutor,
resolveOpencodeTargetFormat,
} from "../../open-sse/executors/opencode.ts";
// Issue 12674: Muse Spark 1.3 is served on the Responses API with a 1M
// context window. These entries only set the wire format; without the
// explicit window the limit fell back to the 200000 provider default.
const PROVIDERS = [
{ id: "opencode-zen", entry: opencode_zenProvider },
{ id: "opencode", entry: opencodeProvider },
] as const;
const MODEL_IDS = ["muse-spark-1.3", "muse-spark-1.3-contributor-free"] as const;
test("muse-spark-1.3 models target the Responses API with reasoning enabled", () => {
for (const { id, entry } of PROVIDERS) {
for (const modelId of MODEL_IDS) {
const model = entry.models.find((m) => m.id === modelId);
assert.ok(model, `${modelId} should be registered on ${id}`);
assert.equal(
model?.targetFormat,
"openai-responses",
`${modelId} on ${id} must target the Responses API, not the default chat/completions pass-through`
);
assert.equal(model?.supportsReasoning, true);
}
}
});
test("muse-spark-1.3 models declare the real 1M context window and 128K output limit", () => {
for (const { id, entry } of PROVIDERS) {
for (const modelId of MODEL_IDS) {
const model = entry.models.find((m) => m.id === modelId);
assert.ok(model, `${modelId} should be registered on ${id}`);
assert.equal(model?.contextLength, 1048576, `${modelId} on ${id} must declare a 1M window`);
assert.equal(
model?.maxOutputTokens,
131072,
`${modelId} on ${id} must declare a 128K output limit`
);
}
}
});
test("context limit resolves muse-spark-1.3 models to the 1M window, not the provider default", () => {
for (const { id } of PROVIDERS) {
for (const modelId of MODEL_IDS) {
assert.equal(
getTokenLimit(id, modelId),
1048576,
`getTokenLimit(${id}, ${modelId}) must return the 1M window`
);
}
}
});
test("executor resolves muse-spark-1.3 models to the Responses API", () => {
for (const { id } of PROVIDERS) {
for (const modelId of MODEL_IDS) {
assert.equal(
resolveOpencodeTargetFormat(id, modelId),
"openai-responses",
`resolveOpencodeTargetFormat(${id}, ${modelId}) must return openai-responses`
);
}
}
});
test("Responses-format executor hits /responses with x-api-key auth and no Bearer header", () => {
for (const { id } of PROVIDERS) {
for (const modelId of MODEL_IDS) {
const executor = new OpencodeExecutor(id);
executor._requestFormat = resolveOpencodeTargetFormat(id, modelId);
const url = executor.buildUrl(modelId, true);
assert.ok(
url.endsWith("/responses"),
`${id}/${modelId} must build a /responses URL, got ${url}`
);
const headers = executor.buildHeaders({ apiKey: "sk-test" }, true, null, modelId);
assert.equal(headers["x-api-key"], "sk-test");
assert.equal(headers["Authorization"], undefined);
}
}
});