fix(claude): stop downgrading adaptive thinking on claude-haiku-5-5 (#15841)

⚠️ base-red inherited: #15306

Validated in local merge-train _artifacts/merge-wave-13/train2.log on devbox-vm-06-dev002 @ 59b137edc8bd23f9c95db60d65fa6dbc5ef00743 (FAST). All 13 boarded. Static gates green except inherited translation drift of docs/reference/ENVIRONMENT.md, the same violations on origin/release/v3.8.52. Changed node:test files passed 90/90 on rerun. The RTL sidebar test passed 19/19. Vitest 5s timeouts in autoCombo (#8601, #8603) reproduced on the pure origin/release/v3.8.52 tip; those files are not in this train's diff.
This commit is contained in:
Aldo 2026-10-08 07:29:54 -06:00 • committed by GitHub
parent 4b04b0f6b9
commit 52efa6f7bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 50 additions and 3 deletions

View file

@ -0,0 +1 @@
- **fix(claude):** `claude-haiku-5-5` requests keep `thinking.type: "adaptive"` instead of being downgraded to `"enabled"`, which Anthropic rejected with HTTP 400 ([#15841](https://github.com/diegosouzapw/OmniRoute/pull/15841)) — thanks @aldoeliacim

View file

@ -1,9 +1,13 @@
import { isAdaptiveThinkingOnly } from "../../src/shared/constants/modelSpecs.ts";
type JsonRecord = Record<string, unknown>;
// Models that reject `thinking.type:"adaptive"` and `output_config.effort` —
// today, all Claude Haiku-tier models (4.5 / 3.5 / dated and aliased). Adaptive
// thinking + the effort knob landed on Sonnet 4.6 / Opus 4.5+ only; sending
// either on Haiku is a hard 400 from the Messages API.
// Claude Haiku-tier models that predate adaptive thinking (4.5 / 3.5 / dated and
// aliased). Haiku 5.5+ is adaptive-only (spec `adaptiveThinkingOnly`), so it
// must NOT be rewritten here: the family regex alone wrongly downgrades it.
// Adaptive thinking + the effort knob landed on Sonnet 4.6 / Opus 4.5+ and
// Haiku 5.5; sending `enabled` to those is a hard 400 from the Messages API.
const HAIKU_CONSTRAINT_PATTERN = /haiku/i;
// Default budget when collapsing `thinking.type:"adaptive"` to a manual shape
@ -18,6 +22,9 @@ function asRecord(value: unknown): JsonRecord | null {
function modelRejectsAdaptiveAndEffort(modelId: string | null | undefined): boolean {
if (typeof modelId !== "string" || modelId.length === 0) return false;
// Spec is authoritative: an adaptive-only model accepts adaptive+effort, so
// it is never rejected here regardless of its family name.
if (isAdaptiveThinkingOnly(modelId)) return false;
return HAIKU_CONSTRAINT_PATTERN.test(modelId);
}

View file

@ -417,6 +417,21 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
aliases: BEDROCK_CLAUDE_ALIASES("claude-sonnet-5"),
},
// ── Claude Haiku 5.5 ────────────────────────────────────────────
// Adaptive-thinking-only, like Sonnet 5.5: `thinking.type:"enabled"` returns
// 400 ("use thinking.type.adaptive and output_config.effort"). Haiku 4.5 and
// earlier still accept manual budgets, so this spec must NOT be widened to
// the /haiku/ family. Limits from the gateway catalog (context 1M, output 128K).
"claude-haiku-5-5": {
maxOutputTokens: 128000,
contextWindow: 1000000,
supportsThinking: true,
supportsTools: true,
supportsVision: true,
adaptiveThinkingOnly: true,
aliases: BEDROCK_CLAUDE_ALIASES("claude-haiku-5-5"),
},
// ── Claude Sonnet 5.5 ───────────────────────────────────────────
"claude-sonnet-5-5": {
// Same shape as Sonnet 5, but it rejects thinking.type:"disabled"

View file

@ -72,6 +72,30 @@ describe("normalizeClaudeHaikuConstraints", () => {
assert.strictEqual(out, input);
});
// Regression: Haiku 5.5 is adaptive-only (spec adaptiveThinkingOnly). The
// family regex used to rewrite its adaptive+effort to manual `enabled`, which
// Anthropic rejects with a 400 ("use thinking.type.adaptive ...").
it("leaves claude-haiku-5-5 adaptive thinking + effort untouched", () => {
const input = {
model: "claude-haiku-5-5",
thinking: { type: "adaptive" },
output_config: { effort: "high" },
};
const out = normalizeClaudeHaikuConstraints(input, input.model);
assert.strictEqual(out, input);
assert.deepEqual(out.thinking, { type: "adaptive" });
assert.deepEqual(out.output_config, { effort: "high" });
});
it("still downgrades adaptive on pre-5 Haiku (4.5 keeps the manual-only contract)", () => {
const input = {
model: "claude-haiku-4-5-20251001",
thinking: { type: "adaptive" },
};
const out = normalizeClaudeHaikuConstraints(input, input.model);
assert.deepEqual(out.thinking, { type: "enabled", budget_tokens: 10000 });
});
it("leaves haiku bodies that have no adaptive thinking or effort untouched", () => {
const input = {
model: "claude-haiku-4-5-20251001",