From 3a3d618fe58df7b6eff7a69ab58e5321779e41bf Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Thu, 2 Jul 2026 20:59:36 -0300 Subject: [PATCH] refactor(executors): extract pure upstream-header helpers from base (#6008) Extract the pure upstream-header helpers (mergeUpstreamExtraHeaders, getCustomUserAgent, setUserAgentHeader, applyConfiguredUserAgent, isOpenAICompatibleEndpoint, stripStainlessHeadersForOpenAICompat) verbatim into the leaf base/headers.ts. base.ts is imported by ~18 executors, so the host re-exports all 6 to keep those import paths intact; it also imports the 4 it uses internally in the BaseExecutor class. The trivial JsonRecord type alias is redefined locally in the leaf to avoid a base<->leaf cycle. Host 1539 -> 1451 LOC. Byte-identical bodies (verbatim 78/78), leaf does not import the host (no cycle). typecheck:core validates all base importers still resolve via the re-export. Adds a split-guard; consumer tests stay green (executor-base-utils 22, executor-default-base 49, executor-strip-stainless-openai-compat 6, plus executor sanity via typecheck). --- open-sse/executors/base.ts | 105 ++++---------------------- open-sse/executors/base/headers.ts | 93 +++++++++++++++++++++++ tests/unit/base-headers-split.test.ts | 55 ++++++++++++++ 3 files changed, 164 insertions(+), 89 deletions(-) create mode 100644 open-sse/executors/base/headers.ts create mode 100644 tests/unit/base-headers-split.test.ts diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 96685152c2..40ffc95f84 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -66,6 +66,22 @@ import { stripProxyToolPrefix, } from "./claudeIdentity.ts"; import { withForcedResponsesUpstream } from "./forceResponsesUpstream.ts"; +import { + mergeUpstreamExtraHeaders, + setUserAgentHeader, + applyConfiguredUserAgent, + stripStainlessHeadersForOpenAICompat, +} from "./base/headers.ts"; +// Header helpers extracted to a pure leaf; re-exported for external importers +// (executors + tests) that import them from "./base.ts". +export { + mergeUpstreamExtraHeaders, + getCustomUserAgent, + setUserAgentHeader, + applyConfiguredUserAgent, + isOpenAICompatibleEndpoint, + stripStainlessHeadersForOpenAICompat, +} from "./base/headers.ts"; /** * Sanitizes a custom API path to prevent path traversal attacks. @@ -155,95 +171,6 @@ export type CountTokensInput = { signal?: AbortSignal | null; }; -/** Apply model-level extra upstream headers (e.g. Authentication, X-Custom-Auth). */ -export function mergeUpstreamExtraHeaders( - headers: Record, - extra?: Record | null -): void { - if (!extra) return; - for (const [k, v] of Object.entries(extra)) { - if (typeof k === "string" && k.length > 0 && typeof v === "string") { - if (k.toLowerCase() === "user-agent") { - setUserAgentHeader(headers, v); - continue; - } - headers[k] = v; - } - } -} - -export function getCustomUserAgent(providerSpecificData?: JsonRecord | null): string | null { - const customUserAgent = - typeof providerSpecificData?.customUserAgent === "string" - ? providerSpecificData.customUserAgent.trim() - : ""; - return customUserAgent || null; -} - -export function setUserAgentHeader(headers: Record, userAgent: string): void { - headers["User-Agent"] = userAgent; - if ("user-agent" in headers) { - headers["user-agent"] = userAgent; - } -} - -export function applyConfiguredUserAgent( - headers: Record, - providerSpecificData?: JsonRecord | null -): void { - const customUserAgent = getCustomUserAgent(providerSpecificData); - if (customUserAgent) { - setUserAgentHeader(headers, customUserAgent); - } -} - -/** - * Returns true when the outbound request targets an OpenAI-compatible endpoint - * (a `openai-compatible-*` provider, or a Chat Completions / Responses URL). - * Used to scope the X-Stainless strip narrowly so genuine SDK-spoofing paths - * (e.g. Claude Code compat, which legitimately ADDS X-Stainless-*) are untouched. - */ -export function isOpenAICompatibleEndpoint(provider: string, url: string): boolean { - if (provider?.startsWith?.("openai-compatible-")) return true; - return url.includes("/v1/chat/completions") || url.includes("/v1/responses"); -} - -/** - * Strip OpenAI SDK (`X-Stainless-*`) metadata headers and normalize an SDK-derived - * User-Agent for OpenAI-compatible passthrough requests. Some upstream gateways - * 403 on these SDK-identifying headers. Only applied to OpenAI-compatible endpoints — - * other providers (Claude/Claude Code compat) may legitimately send X-Stainless-*. - * - * Mutates `headers` in place and returns the list of stripped header keys (for logging). - */ -export function stripStainlessHeadersForOpenAICompat( - headers: Record, - provider: string, - url: string -): string[] { - if (!isOpenAICompatibleEndpoint(provider, url)) return []; - - const strippedKeys: string[] = []; - for (const key of Object.keys(headers)) { - if (key.toLowerCase().startsWith("x-stainless-")) { - delete headers[key]; - strippedKeys.push(key); - } - } - - // Normalize User-Agent: SDK-based clients send verbose product strings that some - // upstreams block. Replace with a clean browser-like UA only when it looks SDK-derived. - const ua = (headers["User-Agent"] || headers["user-agent"] || "").toLowerCase(); - if ( - ua.includes("openai") && - (ua.includes("node") || ua.includes("axios") || ua.includes("undici")) - ) { - setUserAgentHeader(headers, "Mozilla/5.0 (compatible; OpenAI Compatible)"); - } - - return strippedKeys; -} - export function mergeAbortSignals(primary: AbortSignal, secondary: AbortSignal): AbortSignal { const controller = new AbortController(); diff --git a/open-sse/executors/base/headers.ts b/open-sse/executors/base/headers.ts new file mode 100644 index 0000000000..5aebfbb5cb --- /dev/null +++ b/open-sse/executors/base/headers.ts @@ -0,0 +1,93 @@ +// Pure upstream header helpers (User-Agent, extra headers, OpenAI-compat stripping). +// Extracted verbatim from base.ts. Module-private JsonRecord kept local to avoid a cycle. + +type JsonRecord = Record; + +/** Apply model-level extra upstream headers (e.g. Authentication, X-Custom-Auth). */ +export function mergeUpstreamExtraHeaders( + headers: Record, + extra?: Record | null +): void { + if (!extra) return; + for (const [k, v] of Object.entries(extra)) { + if (typeof k === "string" && k.length > 0 && typeof v === "string") { + if (k.toLowerCase() === "user-agent") { + setUserAgentHeader(headers, v); + continue; + } + headers[k] = v; + } + } +} + +export function getCustomUserAgent(providerSpecificData?: JsonRecord | null): string | null { + const customUserAgent = + typeof providerSpecificData?.customUserAgent === "string" + ? providerSpecificData.customUserAgent.trim() + : ""; + return customUserAgent || null; +} + +export function setUserAgentHeader(headers: Record, userAgent: string): void { + headers["User-Agent"] = userAgent; + if ("user-agent" in headers) { + headers["user-agent"] = userAgent; + } +} + +export function applyConfiguredUserAgent( + headers: Record, + providerSpecificData?: JsonRecord | null +): void { + const customUserAgent = getCustomUserAgent(providerSpecificData); + if (customUserAgent) { + setUserAgentHeader(headers, customUserAgent); + } +} + +/** + * Returns true when the outbound request targets an OpenAI-compatible endpoint + * (a `openai-compatible-*` provider, or a Chat Completions / Responses URL). + * Used to scope the X-Stainless strip narrowly so genuine SDK-spoofing paths + * (e.g. Claude Code compat, which legitimately ADDS X-Stainless-*) are untouched. + */ +export function isOpenAICompatibleEndpoint(provider: string, url: string): boolean { + if (provider?.startsWith?.("openai-compatible-")) return true; + return url.includes("/v1/chat/completions") || url.includes("/v1/responses"); +} + +/** + * Strip OpenAI SDK (`X-Stainless-*`) metadata headers and normalize an SDK-derived + * User-Agent for OpenAI-compatible passthrough requests. Some upstream gateways + * 403 on these SDK-identifying headers. Only applied to OpenAI-compatible endpoints — + * other providers (Claude/Claude Code compat) may legitimately send X-Stainless-*. + * + * Mutates `headers` in place and returns the list of stripped header keys (for logging). + */ +export function stripStainlessHeadersForOpenAICompat( + headers: Record, + provider: string, + url: string +): string[] { + if (!isOpenAICompatibleEndpoint(provider, url)) return []; + + const strippedKeys: string[] = []; + for (const key of Object.keys(headers)) { + if (key.toLowerCase().startsWith("x-stainless-")) { + delete headers[key]; + strippedKeys.push(key); + } + } + + // Normalize User-Agent: SDK-based clients send verbose product strings that some + // upstreams block. Replace with a clean browser-like UA only when it looks SDK-derived. + const ua = (headers["User-Agent"] || headers["user-agent"] || "").toLowerCase(); + if ( + ua.includes("openai") && + (ua.includes("node") || ua.includes("axios") || ua.includes("undici")) + ) { + setUserAgentHeader(headers, "Mozilla/5.0 (compatible; OpenAI Compatible)"); + } + + return strippedKeys; +} diff --git a/tests/unit/base-headers-split.test.ts b/tests/unit/base-headers-split.test.ts new file mode 100644 index 0000000000..7fe9106ada --- /dev/null +++ b/tests/unit/base-headers-split.test.ts @@ -0,0 +1,55 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +// Split-guard for the base executor header-helper extraction. +// The pure upstream-header helpers live in base/headers.ts (no host state, no fetch). +// base.ts re-exports all 6 so the ~18 executors + tests that import them from "./base.ts" +// keep resolving unchanged. +const HERE = dirname(fileURLToPath(import.meta.url)); +const EXE = join(HERE, "../../open-sse/executors"); +const HOST = join(EXE, "base.ts"); +const LEAF = join(EXE, "base/headers.ts"); + +test("leaf hosts the header helpers and does not import the host", () => { + const src = readFileSync(LEAF, "utf8"); + for (const sym of [ + "mergeUpstreamExtraHeaders", + "setUserAgentHeader", + "isOpenAICompatibleEndpoint", + "stripStainlessHeadersForOpenAICompat", + ]) { + assert.match(src, new RegExp(`export function ${sym}\\b`)); + } + assert.doesNotMatch(src, /from "\.\.\/base\.ts"/); +}); + +test("host re-exports all 6 header helpers for external importers", () => { + const host = readFileSync(HOST, "utf8"); + for (const sym of [ + "mergeUpstreamExtraHeaders", + "getCustomUserAgent", + "setUserAgentHeader", + "applyConfiguredUserAgent", + "isOpenAICompatibleEndpoint", + "stripStainlessHeadersForOpenAICompat", + ]) { + assert.match(host, new RegExp(`\\b${sym}\\b`)); + } + assert.match(host, /from "\.\/base\/headers\.ts"/); +}); + +test("header helpers behave via base.ts (the public import path)", async () => { + const mod = await import("../../open-sse/executors/base.ts"); + assert.equal(mod.isOpenAICompatibleEndpoint("openai-compatible-x", "https://x/y"), true); + const headers: Record = { "x-stainless-lang": "js", "User-Agent": "openai-node" }; + const stripped = mod.stripStainlessHeadersForOpenAICompat( + headers, + "openai-compatible-x", + "https://x/v1/chat/completions" + ); + assert.ok(stripped.includes("x-stainless-lang")); + assert.equal(headers["x-stainless-lang"], undefined); +});