From ab7c181b77fa1a8bdac263b8b50eb6ca42ae46e1 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:00:21 -0300 Subject: [PATCH] fix(executors): anthropic-compatible-* gateways get Bearer alongside x-api-key (#4729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green. --- open-sse/executors/default.ts | 15 ++++ ...efault-anthropic-compatible-bearer.test.ts | 68 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 tests/unit/executor-default-anthropic-compatible-bearer.test.ts diff --git a/open-sse/executors/default.ts b/open-sse/executors/default.ts index 0eceec4530..a1bdaba8a5 100644 --- a/open-sse/executors/default.ts +++ b/open-sse/executors/default.ts @@ -469,6 +469,21 @@ export class DefaultExecutor extends BaseExecutor { } else if (credentials.accessToken) { headers["Authorization"] = `Bearer ${credentials.accessToken}`; } + // Port of decolua/9router commit b977bf74: + // Third-party Anthropic-compatible gateways frequently require + // Authorization: Bearer ALONGSIDE x-api-key — without it they + // return 401 missing_api_key on every forward. Only emit the + // Bearer fallback for non-official upstreams; api.anthropic.com + // (and the empty/default baseUrl that targets it) must keep the + // x-api-key-only behavior to avoid regressing the official path. + if (effectiveKey && !headers["Authorization"]) { + const baseUrl = credentials?.providerSpecificData?.baseUrl || ""; + const isOfficialAnthropic = + baseUrl === "" || baseUrl.includes("api.anthropic.com"); + if (!isOfficialAnthropic) { + headers["Authorization"] = `Bearer ${effectiveKey}`; + } + } if (!headers["anthropic-version"]) { headers["anthropic-version"] = "2023-06-01"; } diff --git a/tests/unit/executor-default-anthropic-compatible-bearer.test.ts b/tests/unit/executor-default-anthropic-compatible-bearer.test.ts new file mode 100644 index 0000000000..fdbe553463 --- /dev/null +++ b/tests/unit/executor-default-anthropic-compatible-bearer.test.ts @@ -0,0 +1,68 @@ +/** + * Port of decolua/9router commit b977bf74: + * Some third-party Anthropic-compatible gateways (configured via + * `anthropic-compatible-*` provider IDs) require Authorization: Bearer + * in addition to x-api-key. Without the Bearer header, those gateways + * return 401 "missing_api_key" on every forward. + * + * For NON-official anthropic-compatible endpoints (any `baseUrl` that is + * not empty AND does not contain "api.anthropic.com"), the default + * executor's buildHeaders must emit BOTH `x-api-key` and `Authorization: + * Bearer `. Official api.anthropic.com upstreams are unchanged + * (x-api-key only). + */ + +import test from "node:test"; +import assert from "node:assert/strict"; + +import { DefaultExecutor } from "../../open-sse/executors/default.ts"; + +const BASE_CREDS_THIRD_PARTY = { + apiKey: "k-third-party", + providerSpecificData: { baseUrl: "https://gateway.example/v1" }, +} as Record; + +const BASE_CREDS_OFFICIAL = { + apiKey: "k-official", + providerSpecificData: { baseUrl: "https://api.anthropic.com/v1" }, +} as Record; + +test("anthropic-compatible (third-party gateway): sends x-api-key AND Authorization: Bearer", () => { + const executor = new DefaultExecutor("anthropic-compatible-thirdparty"); + const headers = executor.buildHeaders(BASE_CREDS_THIRD_PARTY, true) as Record< + string, + string + >; + assert.equal(headers["x-api-key"], "k-third-party"); + assert.equal( + headers["Authorization"], + "Bearer k-third-party", + "third-party anthropic-compatible upstreams need the Bearer fallback too" + ); +}); + +test("anthropic-compatible (official api.anthropic.com): only x-api-key, no Bearer", () => { + const executor = new DefaultExecutor("anthropic-compatible-official"); + const headers = executor.buildHeaders(BASE_CREDS_OFFICIAL, true) as Record< + string, + string + >; + assert.equal(headers["x-api-key"], "k-official"); + assert.equal( + headers["Authorization"], + undefined, + "official anthropic upstream must NOT receive a Bearer header alongside x-api-key" + ); +}); + +test("anthropic-compatible (no baseUrl): treated as official, no Bearer", () => { + // Empty/missing baseUrl means: "talk to api.anthropic.com" — keep the legacy + // behavior (x-api-key only). + const executor = new DefaultExecutor("anthropic-compatible-empty"); + const headers = executor.buildHeaders( + { apiKey: "k-empty", providerSpecificData: {} } as Record, + true + ) as Record; + assert.equal(headers["x-api-key"], "k-empty"); + assert.equal(headers["Authorization"], undefined); +});