fix(anthropic-compatible): send Claude beta flags to nodes fronting Anthropic (#3797)

This commit is contained in:
Federico Liva
2026-09-05 21:25:28 +07:00
committed by decolua
parent 28cfd9facf
commit fb9fab0206
2 changed files with 52 additions and 1 deletions

View File

@@ -154,7 +154,18 @@ export class DefaultExecutor extends BaseExecutor {
for (const hook of desc.hooks || []) HEADER_HOOKS[hook]?.(headers, credentials);
applyAuth(headers, desc, credentials);
if (this.provider === "claude" && model) {
// anthropic-compatible-* nodes serving a real Claude model sit in front of
// Anthropic itself (a rotating multi-account proxy, a corporate gateway),
// so the request needs the same beta flags the `claude` provider sends:
// without `context-management-2025-06-27` upstream rejects the
// `context_management` block Claude Code puts in every request with
// "context_management: Extra inputs are not permitted" (HTTP 400), and the
// combo silently falls through to the next model. The model id gates this:
// a node fronting Kimi or GLM answers on its own ids and never matches, so
// gateways that would choke on unknown beta flags are left untouched.
const isClaudeModel = typeof model === "string" && /^claude-/.test(model);
if (model && (this.provider === "claude"
|| (this.provider?.startsWith?.("anthropic-compatible-") && isClaudeModel))) {
headers["Anthropic-Beta"] = selectAnthropicBeta(model);
}

View File

@@ -187,6 +187,46 @@ describe("DefaultExecutor.buildHeaders() — anthropic-compatible stripping", ()
headers["Anthropic-Version"] || headers["anthropic-version"];
expect(hasVersion).toBeDefined();
});
// A node fronting Anthropic (rotating multi-account proxy, corporate gateway)
// needs the same beta flags the `claude` provider sends. Without
// context-management-2025-06-27 upstream answers HTTP 400
// "context_management: Extra inputs are not permitted" and the combo falls
// through to the next model without anyone noticing.
it("sends context-management beta for a Claude model on a custom host", () => {
const executor = new DefaultExecutor("anthropic-compatible-custom");
const headers = executor.buildHeaders(
{
apiKey: "key",
providerSpecificData: { baseUrl: "https://myproxy.example.com/v1" },
},
true,
undefined,
"claude-opus-5"
);
const betaFlags = (headers["Anthropic-Beta"] || headers["anthropic-beta"] || "")
.split(",").map(s => s.trim());
expect(betaFlags).toContain("context-management-2025-06-27");
// The first-party identity flag is still stripped for a non-Anthropic host.
expect(betaFlags).not.toContain("claude-code-20250219");
});
it("gates the beta flags on the model id, not the provider prefix", () => {
const executor = new DefaultExecutor("anthropic-compatible-custom");
const headers = executor.buildHeaders(
{
apiKey: "key",
providerSpecificData: { baseUrl: "https://myproxy.example.com/v1" },
},
true,
undefined,
"kimi-k3"
);
const betaVal = headers["Anthropic-Beta"] || headers["anthropic-beta"] || "";
expect(betaVal).not.toContain("context-management-2025-06-27");
});
});
// ─── proxyFetch anthropicFetch routing ────────────────────────────────────────