fix(claude): omit context-1m beta for Sonnet (#2568)

Integrated into release/v3.8.2
This commit is contained in:
Halil Tezcan KARABULUT
2026-05-22 15:46:59 +03:00
committed by GitHub
parent e23a93eb3b
commit 2fd5275833
4 changed files with 40 additions and 26 deletions

View File

@@ -285,18 +285,29 @@ export function parseUpstreamMetadataUserId(
// ---------- anthropic-beta selector --------------------------------------
/**
* Models that support the heavy-agent beta tier (context-1m, effort,
* advanced-tool-use). Only Opus/Sonnet are eligible — Haiku with OAuth
* authentication rejects `context-1m-2025-08-07` with
* "This authentication style is incompatible with the long context beta header"
* (issue #2454). Matches real Claude Desktop, which omits these flags for Haiku.
* Models that support the heavy-agent beta tier (effort, advanced-tool-use).
* Only Opus/Sonnet are eligible — Haiku with OAuth authentication rejects
* heavy agent flags (issue #2454). Matches real Claude Code captures.
*/
const HEAVY_AGENT_BETA_MODEL_PREFIXES = ["claude-opus", "claude-sonnet"];
/**
* Models that support the context-1m beta tier. Only Opus is eligible;
* Sonnet trips long-context credit gates under OAuth full-agent traffic.
*/
const CONTEXT_1M_BETA_MODEL_PREFIXES = ["claude-opus"];
function isHeavyAgentModel(model: unknown): boolean {
function matchesModelPrefix(model: unknown, prefixes: string[]): boolean {
if (typeof model !== "string") return false;
const normalized = model.toLowerCase();
return HEAVY_AGENT_BETA_MODEL_PREFIXES.some((prefix) => normalized.includes(prefix));
return prefixes.some((prefix) => normalized.includes(prefix));
}
function isHeavyAgentModel(model: unknown): boolean {
return matchesModelPrefix(model, HEAVY_AGENT_BETA_MODEL_PREFIXES);
}
function isContext1mModel(model: unknown): boolean {
return matchesModelPrefix(model, CONTEXT_1M_BETA_MODEL_PREFIXES);
}
/**
@@ -304,9 +315,10 @@ function isHeavyAgentModel(model: unknown): boolean {
* uses three patterns: minimal probe, structured-output, and full agent.
* Sending the full set on every shape is itself a fingerprint.
*
* The heavy-agent flags (context-1m, effort, advanced-tool-use) are gated on
* the model as well as the shape: Haiku must never receive `context-1m`, which
* Anthropic rejects under OAuth authentication.
* The heavy-agent flags are gated on the model as well as the shape. In direct
* Claude Code captures, Sonnet receives effort/advanced-tool-use but not
* context-1m; sending context-1m to Sonnet trips Anthropic's long-context credit
* gate for accounts where direct Claude Code works.
*/
export function selectBetaFlags(
body: Record<string, unknown> | null | undefined,
@@ -325,24 +337,27 @@ export function selectBetaFlags(
const isFullAgent = hasTools && hasSystem;
const effectiveModel = model ?? (typeof b.model === "string" ? b.model : "");
const isHeavyAgent = isFullAgent && isHeavyAgentModel(effectiveModel);
const isContext1m = isFullAgent && isContext1mModel(effectiveModel);
const flags: string[] = [];
if (isFullAgent) flags.push("claude-code-20250219");
flags.push("oauth-2025-04-20");
if (isHeavyAgent) flags.push("context-1m-2025-08-07");
if (isContext1m) flags.push("context-1m-2025-08-07");
flags.push(
"interleaved-thinking-2025-05-14",
"redact-thinking-2026-02-12",
"thinking-token-count-2026-05-13",
"context-management-2025-06-27",
"prompt-caching-scope-2026-01-05"
);
if (hasStructuredOutput || isFullAgent) flags.push("advisor-tool-2026-03-01");
if (hasStructuredOutput && !isFullAgent) flags.push("structured-outputs-2025-12-15");
// extended-cache-ttl is sent for all full-agent shapes (incl. Haiku); the
// heavier advanced-tool-use / effort flags are Opus/Sonnet-only.
if (isFullAgent) flags.push("extended-cache-ttl-2025-04-11", "cache-diagnosis-2026-04-07");
// heavier afk-mode / advanced-tool-use / effort flags are Opus/Sonnet-only.
if (isFullAgent) {
flags.push("extended-cache-ttl-2025-04-11", "cache-diagnosis-2026-04-07");
}
if (isHeavyAgent) {
flags.push("advanced-tool-use-2025-11-20", "effort-2025-11-24");
flags.push("afk-mode-2026-01-31", "advanced-tool-use-2025-11-20", "effort-2025-11-24");
}
return flags.join(",");
}

View File

@@ -50,13 +50,7 @@ const CLAUDE_CODE_COMPATIBLE_DEFAULT_SYSTEM_BLOCKS = [
text: "You are a Claude agent, built on Anthropic's Claude Agent SDK.",
},
];
const CONTEXT_1M_SUPPORTED_MODELS = [
"claude-opus-4-7",
"claude-opus-4-6",
"claude-sonnet-4-6",
"claude-sonnet-4-5",
"claude-sonnet-4",
];
const CONTEXT_1M_SUPPORTED_MODELS = ["claude-opus-4-7", "claude-opus-4-6"];
export const CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS = getStainlessTimeoutSeconds(
process.env
);

View File

@@ -5,8 +5,9 @@ const { selectBetaFlags } = await import("../../open-sse/executors/claudeIdentit
// Regression for #2454: Haiku with OAuth rejects `context-1m-2025-08-07` with
// 400 "This authentication style is incompatible with the long context beta header".
// The heavy-agent beta tier (context-1m, effort, advanced-tool-use) must be gated on
// The heavy-agent beta tier (effort, advanced-tool-use) must be gated on
// Opus/Sonnet only; Haiku still receives the general full-agent flags.
// context-1m is Opus-only in captured Claude Code traffic.
function fullAgentBody(model: string) {
return {
@@ -19,6 +20,7 @@ function fullAgentBody(model: string) {
test("#2454 Haiku full-agent omits heavy-agent beta flags", () => {
const flags = selectBetaFlags(fullAgentBody("claude-haiku-4-5-20251001"));
assert.ok(!flags.includes("context-1m-2025-08-07"), "Haiku must NOT receive context-1m");
assert.ok(!flags.includes("afk-mode-2026-01-31"), "Haiku must NOT receive afk-mode");
assert.ok(!flags.includes("effort-2025-11-24"), "Haiku must NOT receive effort");
assert.ok(
!flags.includes("advanced-tool-use-2025-11-20"),
@@ -31,11 +33,14 @@ test("#2454 Haiku full-agent omits heavy-agent beta flags", () => {
assert.ok(flags.includes("extended-cache-ttl-2025-04-11"));
});
test("#2454 Sonnet full-agent includes heavy-agent beta flags", () => {
test("#2454 Sonnet full-agent includes heavy-agent flags but omits context-1m", () => {
const flags = selectBetaFlags(fullAgentBody("claude-sonnet-4-6"));
assert.ok(flags.includes("context-1m-2025-08-07"), "Sonnet should receive context-1m");
assert.ok(!flags.includes("context-1m-2025-08-07"), "Sonnet must NOT receive context-1m");
assert.ok(flags.includes("effort-2025-11-24"));
assert.ok(flags.includes("advanced-tool-use-2025-11-20"));
assert.ok(flags.includes("thinking-token-count-2026-05-13"));
assert.ok(flags.includes("afk-mode-2026-01-31"));
assert.ok(!flags.includes("redact-thinking-2026-02-12"));
});
test("#2454 Opus full-agent includes heavy-agent beta flags", () => {

View File

@@ -569,7 +569,7 @@ test("DefaultExecutor.execute uses CC-compatible connection defaults to append 1
assert.equal(calls[0].headers["anthropic-beta"].includes(CONTEXT_1M_BETA_HEADER), false);
assert.equal(calls[1].headers["anthropic-beta"].includes(CONTEXT_1M_BETA_HEADER), true);
assert.equal(calls[2].headers["anthropic-beta"], CONTEXT_1M_BETA_HEADER);
assert.equal(calls[2].headers["anthropic-beta"], undefined);
});
test("DefaultExecutor.execute only injects adaptive thinking defaults for Claude models that support x-high effort", async () => {