Files
OmniRoute/tests/unit/thinking-binding-controls-beta-forward.test.ts
Felipe Fidelix 1533f16ed8 fix(claude): forward client-negotiated thinking-binding-controls and thinking-display-updates betas (#12989)
* fix(claude): forward client-negotiated thinking-binding-controls and thinking-display-updates betas

Gateways drops anthropic-beta tokens not on FORWARDABLE_CLIENT_BETAS, so
@ai-sdk/anthropic Fable 5.1 requests carrying thinking.block_binding were
rejected upstream with thinking.adaptive.block_binding: Extra inputs are
not permitted even when the client negotiated
thinking-binding-controls-2026-08-01 correctly. Same class for
thinking.display updates (thinking-display-updates-2026-08-18).

Regression guard: tests/unit/thinking-binding-controls-beta-forward.test.ts

* chore(changelog): fragment for #12989

* chore: trim comments
2026-09-18 11:29:54 -03:00

70 lines
3.1 KiB
TypeScript

/**
* Fable 5.1 sends `thinking.block_binding` + `thinking-binding-controls-2026-08-01`
* (and `thinking.display` + `thinking-display-updates-2026-08-18`) automatically.
* The gateway stripped both betas, so upstream 400'd with `Extra inputs are not
* permitted`. Both must be forwarded when the client negotiates them.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { ANTHROPIC_BETA_API_KEY, mergeClientAnthropicBeta, FORWARDABLE_CLIENT_BETAS } =
await import("../../open-sse/config/anthropicHeaders.ts");
const BINDING_CONTROLS = "thinking-binding-controls-2026-08-01";
const DISPLAY_UPDATES = "thinking-display-updates-2026-08-18";
// ── allowlist membership ────────────────────────────────────────────────────
test("FORWARDABLE_CLIENT_BETAS must include thinking-binding-controls beta", () => {
assert.ok(FORWARDABLE_CLIENT_BETAS.includes(BINDING_CONTROLS));
});
test("FORWARDABLE_CLIENT_BETAS must include thinking-display-updates beta", () => {
assert.ok(FORWARDABLE_CLIENT_BETAS.includes(DISPLAY_UPDATES));
});
// ── client-negotiated beta forwarding ───────────────────────────────────────
test("mergeClientAnthropicBeta must forward client-negotiated thinking-binding-controls beta", () => {
const out = mergeClientAnthropicBeta(
ANTHROPIC_BETA_API_KEY,
`claude-code-20250219,${BINDING_CONTROLS}`
);
const tokens = out.split(",").map((s) => s.trim());
assert.ok(tokens.includes(BINDING_CONTROLS), `client beta dropped: ${out}`);
});
test("mergeClientAnthropicBeta must forward client-negotiated thinking-display-updates beta", () => {
const out = mergeClientAnthropicBeta(
ANTHROPIC_BETA_API_KEY,
`claude-code-20250219,${DISPLAY_UPDATES}`
);
const tokens = out.split(",").map((s) => s.trim());
assert.ok(tokens.includes(DISPLAY_UPDATES), `client beta dropped: ${out}`);
});
test("mergeClientAnthropicBeta forwards both betas together without duplication", () => {
const out = mergeClientAnthropicBeta(
`${ANTHROPIC_BETA_API_KEY},${BINDING_CONTROLS}`,
`claude-code-20250219,${BINDING_CONTROLS},${DISPLAY_UPDATES}`
);
const tokens = out.split(",").map((s) => s.trim());
assert.ok(tokens.includes(BINDING_CONTROLS), `binding-controls missing: ${out}`);
assert.ok(tokens.includes(DISPLAY_UPDATES), `display-updates missing: ${out}`);
assert.equal(
tokens.filter((t) => t.toLowerCase() === BINDING_CONTROLS).length,
1,
`binding-controls duplicated: ${out}`
);
});
// ── guard: allowlist still closed ───────────────────────────────────────────
test("mergeClientAnthropicBeta still ignores non-allowlisted client betas", () => {
const out = mergeClientAnthropicBeta(
ANTHROPIC_BETA_API_KEY,
"some-random-future-beta-2099-01-01"
);
assert.ok(!out.includes("some-random-future-beta"), `unknown beta leaked: ${out}`);
});