From dd3045a961b32188a49e41a3399082295e56d4ec Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Tue, 23 Jun 2026 14:23:15 -0300 Subject: [PATCH] fix(sse): strip X-Stainless-* headers and normalize SDK User-Agent for OpenAI-compatible endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenAI SDK clients attach X-Stainless-* metadata headers and verbose SDK-derived User-Agent strings. Some upstream gateways 403 on these SDK-identifying headers. Strip them and normalize the User-Agent on the OpenAI-compatible passthrough path (Chat Completions / Responses / openai-compatible-* providers) via pure, unit-tested helpers wired into BaseExecutor.execute(). The isOpenAICompatibleEndpoint guard is kept narrow so Claude / Claude-Code compat paths — which legitimately ADD X-Stainless-* — are untouched. Co-authored-by: Anurag Saxena Inspired-by: https://github.com/decolua/9router/pull/656 --- open-sse/executors/base.ts | 54 ++++++++++++ ...utor-strip-stainless-openai-compat.test.ts | 83 +++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 tests/unit/executor-strip-stainless-openai-compat.test.ts diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 51a0e1389c1..7d36eb7e1d1 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -192,6 +192,50 @@ export function applyConfiguredUserAgent( } } +/** + * 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(); @@ -817,6 +861,16 @@ export class BaseExecutor { const headers = this.buildHeaders(activeCredentials, stream, clientHeaders, model); applyConfiguredUserAgent(headers, activeCredentials?.providerSpecificData); + // Strip OpenAI SDK (X-Stainless-*) metadata + normalize SDK-derived User-Agent + // on OpenAI-compatible passthrough requests — some upstream gateways 403 on them. + const strippedStainless = stripStainlessHeadersForOpenAICompat(headers, this.provider, url); + if (strippedStainless.length > 0) { + log?.debug?.( + "HEADERS", + `Stripped X-Stainless-* from OpenAI-compatible request: ${strippedStainless.join(", ")}` + ); + } + const ccRequestDefaults = isClaudeCodeCompatible(this.provider) ? getClaudeCodeCompatibleRequestDefaults(activeCredentials?.providerSpecificData) : {}; diff --git a/tests/unit/executor-strip-stainless-openai-compat.test.ts b/tests/unit/executor-strip-stainless-openai-compat.test.ts new file mode 100644 index 00000000000..02c693faa7c --- /dev/null +++ b/tests/unit/executor-strip-stainless-openai-compat.test.ts @@ -0,0 +1,83 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const base = await import("../../open-sse/executors/base.ts"); + +test("isOpenAICompatibleEndpoint matches openai-compatible-* providers", () => { + assert.equal(base.isOpenAICompatibleEndpoint("openai-compatible-foo", "https://x/y"), true); + assert.equal( + base.isOpenAICompatibleEndpoint("claude", "https://api.anthropic.com/v1/messages"), + false + ); +}); + +test("isOpenAICompatibleEndpoint matches chat/completions and responses URLs", () => { + assert.equal( + base.isOpenAICompatibleEndpoint("groq", "https://api.groq.com/openai/v1/chat/completions"), + true + ); + assert.equal(base.isOpenAICompatibleEndpoint("groq", "https://x/v1/responses"), true); + assert.equal(base.isOpenAICompatibleEndpoint("groq", "https://x/v1/embeddings"), false); +}); + +test("strips X-Stainless-* headers on OpenAI-compatible passthrough", () => { + const headers: Record = { + "Content-Type": "application/json", + Authorization: "Bearer sk-test", + "X-Stainless-Lang": "js", + "X-Stainless-OS": "Linux", + "x-stainless-runtime": "node", + }; + const stripped = base.stripStainlessHeadersForOpenAICompat( + headers, + "openai-compatible-acme", + "https://acme.example/v1/chat/completions" + ); + assert.deepEqual(stripped.sort(), ["X-Stainless-Lang", "X-Stainless-OS", "x-stainless-runtime"]); + assert.equal(headers["X-Stainless-Lang"], undefined); + assert.equal(headers["X-Stainless-OS"], undefined); + assert.equal(headers["x-stainless-runtime"], undefined); + // Non-stainless headers untouched. + assert.equal(headers["Authorization"], "Bearer sk-test"); + assert.equal(headers["Content-Type"], "application/json"); +}); + +test("does NOT strip X-Stainless-* for non-OpenAI-compatible endpoints", () => { + const headers: Record = { + "X-Stainless-Lang": "js", + "X-Stainless-Package-Version": "1.0.0", + }; + const stripped = base.stripStainlessHeadersForOpenAICompat( + headers, + "claude", + "https://api.anthropic.com/v1/messages" + ); + assert.deepEqual(stripped, []); + // Claude-code-compat spoofing path keeps its X-Stainless-* headers intact. + assert.equal(headers["X-Stainless-Lang"], "js"); + assert.equal(headers["X-Stainless-Package-Version"], "1.0.0"); +}); + +test("normalizes SDK-derived User-Agent on OpenAI-compatible request", () => { + const headers: Record = { + "User-Agent": "OpenAI/NodeJS 4.20.0 undici", + }; + base.stripStainlessHeadersForOpenAICompat( + headers, + "openai-compatible-acme", + "https://acme.example/v1/chat/completions" + ); + assert.equal(headers["User-Agent"], "Mozilla/5.0 (compatible; OpenAI Compatible)"); +}); + +test("leaves a non-SDK User-Agent untouched", () => { + const headers: Record = { + "User-Agent": "my-custom-agent/2.0", + }; + base.stripStainlessHeadersForOpenAICompat( + headers, + "openai-compatible-acme", + "https://acme.example/v1/chat/completions" + ); + assert.equal(headers["User-Agent"], "my-custom-agent/2.0"); +});