fix(sse): make anthropic-version default-guard case-insensitive (#4823)

Integrated into release/v3.8.37 — conflict with #4729 Bearer-fallback resolved (kept both Bearer fallback + case-insensitive anthropic-version guard); 48/48 green.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-25 23:41:22 -03:00
committed by GitHub
parent 86e487bdb3
commit 885134b192
3 changed files with 47 additions and 1 deletions

View File

@@ -10,6 +10,8 @@ _In development — bullets added per PR; finalized at release._
### 🔧 Bug Fixes
- **sse**: make the `anthropic-version` default-guard case-insensitive for `anthropic-compatible-*` providers, so a caller/operator-supplied `Anthropic-Version` (any casing) is no longer clobbered by a second lowercase `anthropic-version: 2023-06-01` header. (thanks @zakirkun)
- **huggingface**: validate API tokens via the `whoami-v2` endpoint as a pure auth probe so fine-grained Inference-Provider tokens (valid even when model/task endpoints reject them) are no longer falsely marked invalid; only 401/403 means an invalid key, other non-OK statuses surface as transient upstream errors. (thanks @Delcado19)
- **sse/kiro**: reject the Anthropic-only `[1m]` context-1m suffix in `buildKiroPayload` before it reaches AWS Bedrock — Kiro is Bedrock-backed and cannot honor the beta, so a forwarded `kr/*[1m]` model id was malformed upstream; callers now get a clear error pointing them at a direct-Anthropic provider for 1M-context routing (thanks @Delcado19).

View File

@@ -491,7 +491,14 @@ export class DefaultExecutor extends BaseExecutor {
headers["Authorization"] = `Bearer ${effectiveKey}`;
}
}
if (!headers["anthropic-version"]) {
// Default the anthropic-version header only when the caller/operator
// has not already supplied one. The lookup is case-insensitive so a
// pre-set "Anthropic-Version" (e.g. from this.config.headers or a
// custom header) is not clobbered with a duplicate lowercase entry.
const hasAnthropicVersion = Object.keys(headers).some(
(key) => key.toLowerCase() === "anthropic-version"
);
if (!hasAnthropicVersion) {
headers["anthropic-version"] = "2023-06-01";
}
} else {

View File

@@ -435,6 +435,43 @@ test("DefaultExecutor.buildHeaders handles GLM, default auth and anthropic-compa
assert.equal(anthropicHeaders.Accept, "text/event-stream");
});
test("DefaultExecutor.buildHeaders keeps a caller-supplied Anthropic-Version (case-insensitive guard) for anthropic-compatible providers", () => {
// An operator may configure a Title-Case "Anthropic-Version" via the provider
// config headers. The default-guard at the anthropic-compatible-* branch must
// detect it case-insensitively and NOT add a second lowercase
// "anthropic-version" key, which undici would otherwise combine into
// "2025-01-01, 2023-06-01" and break the upstream request.
const anthropicCompat = new DefaultExecutor("anthropic-compatible-test");
// `config` is shared across instances via the provider registry, so snapshot
// and restore `config.headers` to avoid leaking the Title-Case override into
// other tests.
const originalConfigHeaders = anthropicCompat.config.headers;
anthropicCompat.config.headers = {
...originalConfigHeaders,
"Anthropic-Version": "2025-01-01",
};
try {
const headers = anthropicCompat.buildHeaders({ apiKey: "anth-key" }, true);
const versionKeys = Object.keys(headers).filter(
(key) => key.toLowerCase() === "anthropic-version"
);
assert.equal(versionKeys.length, 1, "Duplicate anthropic-version header keys found");
assert.equal(headers["Anthropic-Version"], "2025-01-01");
assert.equal(headers["anthropic-version"], undefined);
assert.equal(headers["x-api-key"], "anth-key");
} finally {
anthropicCompat.config.headers = originalConfigHeaders;
}
});
test("DefaultExecutor.buildHeaders still defaults anthropic-version when no variant is present", () => {
const anthropicCompat = new DefaultExecutor("anthropic-compatible-test");
const headers = anthropicCompat.buildHeaders({ apiKey: "anth-key" }, true);
assert.equal(headers["anthropic-version"], "2023-06-01");
});
test("DefaultExecutor local OpenAI-style providers honor custom base URLs and skip empty bearer headers", () => {
const lmStudio = new DefaultExecutor("lm-studio");
const vllm = new DefaultExecutor("vllm");