diff --git a/changelog.d/fixes/13628-grok46-default-effort.md b/changelog.d/fixes/13628-grok46-default-effort.md new file mode 100644 index 0000000000..1ee9f2f810 --- /dev/null +++ b/changelog.d/fixes/13628-grok46-default-effort.md @@ -0,0 +1,2 @@ +- fix(providers): restore grok-4.6/4.5 default reasoning effort so requests without an explicit effort keep reasoning enabled (#13628) +- fix(registry): declare supportedThinkingEfforts on claude-opus-5 and claude-fable-5 across the anthropic/claude/claude-web/ghe-copilot/github registries (#13628) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 5ffac46924..f1005c77c1 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -21,7 +21,7 @@ }, "open-sse/config/providers/registry/claude/index.ts": { "@typescript-eslint/no-unused-vars": { - "count": 7 + "count": 6 } }, "open-sse/config/providers/registry/vertex/index.ts": { diff --git a/open-sse/config/providers/registry/grok-cli/index.ts b/open-sse/config/providers/registry/grok-cli/index.ts index e65ced0e76..ffdcc7cd62 100644 --- a/open-sse/config/providers/registry/grok-cli/index.ts +++ b/open-sse/config/providers/registry/grok-cli/index.ts @@ -26,6 +26,7 @@ export const grok_cliProvider: RegistryEntry = { name: "Grok 4.6", contextLength: 500000, supportsReasoning: true, + supportedThinkingEfforts: ["low", "medium", "high"], toolCalling: true, targetFormat: "openai-responses", unsupportedParams: ["presencePenalty", "frequencyPenalty", "logprobs", "topLogprobs"], @@ -35,6 +36,7 @@ export const grok_cliProvider: RegistryEntry = { name: "Grok 4.5", contextLength: 500000, supportsReasoning: true, + supportedThinkingEfforts: ["low", "medium", "high"], toolCalling: true, targetFormat: "openai-responses", unsupportedParams: ["presencePenalty", "frequencyPenalty", "logprobs", "topLogprobs"], diff --git a/open-sse/executors/grok-cli.ts b/open-sse/executors/grok-cli.ts index fc37b23ce2..b9e004a538 100644 --- a/open-sse/executors/grok-cli.ts +++ b/open-sse/executors/grok-cli.ts @@ -127,13 +127,18 @@ function normalizeGrokBuildReasoning( model: string ): Record | null { const reasoning = asRequestRecord(value); + // Capture BEFORE stripping: an explicit (but unsupported/invalid, e.g. "none"/"off"/ + // "xhigh") effort must still count as an explicit off-switch below — only the true + // ABSENCE of an effort key gets the model default. Restores the #7358 behavior the + // 4.6 default accidentally regressed: without this, every explicit "none"/"off" from + // grok-cli got silently promoted to "high", leaving no way to disable reasoning. const hasExplicitEffort = Object.prototype.hasOwnProperty.call(reasoning, "effort"); if (!GROK_BUILD_REASONING_EFFORT_SET.has(String(reasoning.effort))) { delete reasoning.effort; } if (model === "grok-composer-2.5-fast") { delete reasoning.effort; - } else if (model === "grok-4.5" && !hasExplicitEffort) { + } else if ((model === "grok-4.5" || model === "grok-4.6") && !hasExplicitEffort) { reasoning.effort = GROK_BUILD_DEFAULT_REASONING_EFFORT; } return Object.keys(reasoning).length > 0 ? reasoning : null; diff --git a/tests/unit/grok-cli-reasoning-strip-6288.test.ts b/tests/unit/grok-cli-reasoning-strip-6288.test.ts index f9e066cbab..8035fd022c 100644 --- a/tests/unit/grok-cli-reasoning-strip-6288.test.ts +++ b/tests/unit/grok-cli-reasoning-strip-6288.test.ts @@ -96,6 +96,9 @@ test("grok-cli preserves explicit store and de-duplicates encrypted reasoning in assert.equal(out.store, true); assert.deepEqual(out.include, ["reasoning.encrypted_content"]); + // An explicit (but unsupported) effort like "xhigh" is still an EXPLICIT effort key — + // it gets stripped, not defaulted. Only the true absence of an "effort" key falls back + // to the model default. This is the off-switch #7358 relied on and #13628 regressed. assert.equal("reasoning" in out, false); }); @@ -113,3 +116,94 @@ test("grok-cli preserves an explicit Responses reasoning summary", () => { assert.deepEqual(out.reasoning, { summary: "concise", effort: "high" }); }); + +test("grok-4.6 applies default high when client omits effort", async () => { + const executor = new GrokCliExecutor(); + const body = { + model: "grok-4.6", + input: [{ role: "user", content: [{ type: "input_text", text: "hi" }] }], + }; + + const transformed = executor.transformRequest( + "grok-4.6", + body, + false, + {} as Record + ) as Record; + + assert.deepEqual(transformed.reasoning, { effort: "high" }); +}); + +test("grok-4.6 preserves an explicit supported effort", async () => { + const executor = new GrokCliExecutor(); + const body = { + model: "grok-4.6", + input: [{ role: "user", content: [{ type: "input_text", text: "hi" }] }], + reasoning: { effort: "medium", summary: "auto" }, + }; + + const transformed = executor.transformRequest( + "grok-4.6", + body, + false, + {} as Record + ) as Record; + + assert.deepEqual(transformed.reasoning, { effort: "medium", summary: "auto" }); +}); + +test("grok-4.6 strips an explicit but unsupported xhigh (no default restore — an explicit effort key is an explicit choice)", async () => { + const executor = new GrokCliExecutor(); + const body = { + model: "grok-4.6", + input: [{ role: "user", content: [{ type: "input_text", text: "hi" }] }], + reasoning: { effort: "xhigh" }, + reasoning_effort: "xhigh", + }; + + const transformed = executor.transformRequest( + "grok-4.6", + body, + false, + {} as Record + ) as Record; + + assert.equal("reasoning_effort" in transformed, false); + assert.equal("reasoning" in transformed, false); +}); + +test("grok-4.6 keeps an explicit none/off as a real off-switch (no default restore)", async () => { + const executor = new GrokCliExecutor(); + const body = { + model: "grok-4.6", + input: [{ role: "user", content: [{ type: "input_text", text: "hi" }] }], + reasoning: { effort: "none" }, + }; + + const transformed = executor.transformRequest( + "grok-4.6", + body, + false, + {} as Record + ) as Record; + + assert.equal("reasoning" in transformed, false); +}); + +test("grok-4.5 keeps an explicit none/off as a real off-switch (no default restore)", async () => { + const executor = new GrokCliExecutor(); + const body = { + model: "grok-4.5", + input: [{ role: "user", content: [{ type: "input_text", text: "hi" }] }], + reasoning: { effort: "off" }, + }; + + const transformed = executor.transformRequest( + "grok-4.5", + body, + false, + {} as Record + ) as Record; + + assert.equal("reasoning" in transformed, false); +});