Skip to content

Commit 6a23625

Browse files
committed
Give Muse Spark its own reasoning effort ladder
muse-spark-1.3-contributor fell through to UNKNOWN_MODEL_EFFORTS, so `minimal` was unreachable from the picker, the flag, and agent profiles. Measured against the Go Responses endpoint, reasoning tokens are ~95% of every completion and scale 6x from minimal to medium with no change in pass rate on two objectively graded tasks. The gateway accepts minimal and rejects `none` with HTTP 400, so the rung set is minimal/low/medium/ high and the family default is `low`. Also pins SOURCE_MAX_TOKENS above the measured truncation floor: reasoning consumes max_output_tokens before any answer text, so a 512 cap at medium effort returns 3 tokens of answer, not a shorter answer. CL-7867
1 parent d9ec82c commit 6a23625

3 files changed

Lines changed: 62 additions & 1 deletion

File tree

‎src/config.test.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,15 @@ describe("buildOpenAISource", () => {
14601460
expect(source.baseURL).toBe("https://fp/v1");
14611461
});
14621462

1463+
test("stays above the reasoning truncation floor", () => {
1464+
// Reasoning tokens consume max_output_tokens before any answer text is
1465+
// emitted. Measured on muse-spark-1.3-contributor, a 512-token cap at
1466+
// medium effort spent 397 tokens reasoning and returned 3 tokens of
1467+
// answer; 1024 was the lowest cap that answered on every rung. 4096 is
1468+
// the floor we will not drop below. See CL-7867.
1469+
expect(SOURCE_MAX_TOKENS).toBeGreaterThanOrEqual(4096);
1470+
});
1471+
14631472
test("omits reasoning_effort when effort is absent", () => {
14641473
const source = buildOpenAISource({
14651474
id: "fp",

‎src/provider/reasoning-effort.test.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,28 @@ describe("supportedEfforts", () => {
163163
expect(supportedEfforts("glm-5.3")).toEqual(["low", "high", "max"]);
164164
expect(supportedEfforts("glm-5.3-flash")).toEqual(["low", "high", "max"]);
165165
});
166+
167+
test("Muse Spark supports minimal through high", () => {
168+
expect(supportedEfforts("muse-spark-1.3-contributor")).toEqual([
169+
"minimal",
170+
"low",
171+
"medium",
172+
"high",
173+
]);
174+
expect(supportedEfforts("muse-spark-1.2-contributor")).toEqual([
175+
"minimal",
176+
"low",
177+
"medium",
178+
"high",
179+
]);
180+
});
181+
182+
test("Muse Spark never offers none", () => {
183+
// The Go gateway answers HTTP 400 on reasoning.effort: "none".
184+
expect(supportedEfforts("muse-spark-1.3-contributor")).not.toContain(
185+
"none",
186+
);
187+
});
166188
});
167189

168190
describe("validateEffort", () => {
@@ -189,6 +211,13 @@ describe("validateEffort", () => {
189211
expect(validateEffort("grok-4.5", "xhigh").ok).toBe(false);
190212
});
191213

214+
test("accepts minimal on Muse Spark and rejects none", () => {
215+
expect(validateEffort("muse-spark-1.3-contributor", "minimal")).toEqual({
216+
ok: true,
217+
});
218+
expect(validateEffort("muse-spark-1.3-contributor", "none").ok).toBe(false);
219+
});
220+
192221
test("rejects medium on glm-5.3 family", () => {
193222
expect(validateEffort("glm-5.3", "medium").ok).toBe(false);
194223
expect(validateEffort("glm-5.3-flash", "medium").ok).toBe(false);
@@ -472,6 +501,11 @@ describe("defaultEffortForModel", () => {
472501
expect(defaultEffortForModel("glm-5.3-flash")).toBe("max");
473502
});
474503

504+
test("Muse Spark defaults to low", () => {
505+
expect(defaultEffortForModel("muse-spark-1.3-contributor")).toBe("low");
506+
expect(defaultEffortForModel("muse-spark-1.2-contributor")).toBe("low");
507+
});
508+
475509
test("gpt-5 and o-series default to medium", () => {
476510
expect(defaultEffortForModel("gpt-5")).toBe("medium");
477511
expect(defaultEffortForModel("o1")).toBe("medium");

‎src/provider/reasoning-effort.ts‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ const UNKNOWN_MODEL_EFFORTS: readonly ReasoningEffort[] = [
6161
"high",
6262
];
6363

64+
// Muse Spark (OpenCode Go, Responses protocol) accepts minimal through high.
65+
// Not `none` — the Go gateway rejects it with HTTP 400 on `reasoning.effort`.
66+
// Measured against https://opencode.ai/zen/go/v1/responses; see CL-7867.
67+
const MUSE_SPARK_EFFORTS: readonly ReasoningEffort[] = [
68+
"minimal",
69+
"low",
70+
"medium",
71+
"high",
72+
];
73+
const MUSE_SPARK_MODELS: readonly string[] = [
74+
"muse-spark-1.3-contributor",
75+
"muse-spark-1.2-contributor",
76+
];
77+
6478
// grok-4.6 accepts xhigh; grok-4.5 and composer stay on the unknown-model subset.
6579
const GROK_46_EFFORTS: readonly ReasoningEffort[] = [
6680
"low",
@@ -143,6 +157,9 @@ export function supportedEfforts(
143157
if (GLM_53_MODELS.includes(model)) {
144158
return [...GLM_53_EFFORTS];
145159
}
160+
if (MUSE_SPARK_MODELS.includes(model)) {
161+
return [...MUSE_SPARK_EFFORTS];
162+
}
146163
return [...UNKNOWN_MODEL_EFFORTS];
147164
}
148165

@@ -196,7 +213,7 @@ export function cycleReasoningEffort(
196213
* (`defaultEffortForDirector`): this is what the prompt shows and what Shift+Tab
197214
* advances from when the operator has not picked a level.
198215
*
199-
* Family table: grok* → high; glm-5.3* → max; Codex → medium; gpt-5.1 chat (`none` on the
216+
* Family table: grok* → high; glm-5.3* → max; muse-spark* → low; Codex → medium; gpt-5.1 chat (`none` on the
200217
* ladder, not Codex) → none; gpt-5/gpt-6/o1/o3/o4 → medium. Unknown models with a
201218
* conservative rung set stay undefined so we do not invent a family default.
202219
*/
@@ -210,6 +227,7 @@ export function defaultEffortForModel(
210227
supported.includes(desired) ? desired : undefined;
211228
if (model.startsWith("grok")) return pick("high");
212229
if (GLM_53_MODELS.includes(model)) return pick("max");
230+
if (MUSE_SPARK_MODELS.includes(model)) return pick("low");
213231
if (!isCodex && supported.includes("none")) return "none";
214232
if (isCodex || isKnownOpenAIReasoningModel(model)) return pick("medium");
215233
return undefined;

0 commit comments

Comments
 (0)