Skip to content

Commit 3a3d618

Browse files
authored
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).
1 parent 1a73dd2 commit 3a3d618

3 files changed

Lines changed: 164 additions & 89 deletions

File tree

open-sse/executors/base.ts

Lines changed: 16 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ import {
6666
stripProxyToolPrefix,
6767
} from "./claudeIdentity.ts";
6868
import { withForcedResponsesUpstream } from "./forceResponsesUpstream.ts";
69+
import {
70+
mergeUpstreamExtraHeaders,
71+
setUserAgentHeader,
72+
applyConfiguredUserAgent,
73+
stripStainlessHeadersForOpenAICompat,
74+
} from "./base/headers.ts";
75+
// Header helpers extracted to a pure leaf; re-exported for external importers
76+
// (executors + tests) that import them from "./base.ts".
77+
export {
78+
mergeUpstreamExtraHeaders,
79+
getCustomUserAgent,
80+
setUserAgentHeader,
81+
applyConfiguredUserAgent,
82+
isOpenAICompatibleEndpoint,
83+
stripStainlessHeadersForOpenAICompat,
84+
} from "./base/headers.ts";
6985

7086
/**
7187
* Sanitizes a custom API path to prevent path traversal attacks.
@@ -155,95 +171,6 @@ export type CountTokensInput = {
155171
signal?: AbortSignal | null;
156172
};
157173

158-
/** Apply model-level extra upstream headers (e.g. Authentication, X-Custom-Auth). */
159-
export function mergeUpstreamExtraHeaders(
160-
headers: Record<string, string>,
161-
extra?: Record<string, string> | null
162-
): void {
163-
if (!extra) return;
164-
for (const [k, v] of Object.entries(extra)) {
165-
if (typeof k === "string" && k.length > 0 && typeof v === "string") {
166-
if (k.toLowerCase() === "user-agent") {
167-
setUserAgentHeader(headers, v);
168-
continue;
169-
}
170-
headers[k] = v;
171-
}
172-
}
173-
}
174-
175-
export function getCustomUserAgent(providerSpecificData?: JsonRecord | null): string | null {
176-
const customUserAgent =
177-
typeof providerSpecificData?.customUserAgent === "string"
178-
? providerSpecificData.customUserAgent.trim()
179-
: "";
180-
return customUserAgent || null;
181-
}
182-
183-
export function setUserAgentHeader(headers: Record<string, string>, userAgent: string): void {
184-
headers["User-Agent"] = userAgent;
185-
if ("user-agent" in headers) {
186-
headers["user-agent"] = userAgent;
187-
}
188-
}
189-
190-
export function applyConfiguredUserAgent(
191-
headers: Record<string, string>,
192-
providerSpecificData?: JsonRecord | null
193-
): void {
194-
const customUserAgent = getCustomUserAgent(providerSpecificData);
195-
if (customUserAgent) {
196-
setUserAgentHeader(headers, customUserAgent);
197-
}
198-
}
199-
200-
/**
201-
* Returns true when the outbound request targets an OpenAI-compatible endpoint
202-
* (a `openai-compatible-*` provider, or a Chat Completions / Responses URL).
203-
* Used to scope the X-Stainless strip narrowly so genuine SDK-spoofing paths
204-
* (e.g. Claude Code compat, which legitimately ADDS X-Stainless-*) are untouched.
205-
*/
206-
export function isOpenAICompatibleEndpoint(provider: string, url: string): boolean {
207-
if (provider?.startsWith?.("openai-compatible-")) return true;
208-
return url.includes("/v1/chat/completions") || url.includes("/v1/responses");
209-
}
210-
211-
/**
212-
* Strip OpenAI SDK (`X-Stainless-*`) metadata headers and normalize an SDK-derived
213-
* User-Agent for OpenAI-compatible passthrough requests. Some upstream gateways
214-
* 403 on these SDK-identifying headers. Only applied to OpenAI-compatible endpoints —
215-
* other providers (Claude/Claude Code compat) may legitimately send X-Stainless-*.
216-
*
217-
* Mutates `headers` in place and returns the list of stripped header keys (for logging).
218-
*/
219-
export function stripStainlessHeadersForOpenAICompat(
220-
headers: Record<string, string>,
221-
provider: string,
222-
url: string
223-
): string[] {
224-
if (!isOpenAICompatibleEndpoint(provider, url)) return [];
225-
226-
const strippedKeys: string[] = [];
227-
for (const key of Object.keys(headers)) {
228-
if (key.toLowerCase().startsWith("x-stainless-")) {
229-
delete headers[key];
230-
strippedKeys.push(key);
231-
}
232-
}
233-
234-
// Normalize User-Agent: SDK-based clients send verbose product strings that some
235-
// upstreams block. Replace with a clean browser-like UA only when it looks SDK-derived.
236-
const ua = (headers["User-Agent"] || headers["user-agent"] || "").toLowerCase();
237-
if (
238-
ua.includes("openai") &&
239-
(ua.includes("node") || ua.includes("axios") || ua.includes("undici"))
240-
) {
241-
setUserAgentHeader(headers, "Mozilla/5.0 (compatible; OpenAI Compatible)");
242-
}
243-
244-
return strippedKeys;
245-
}
246-
247174
export function mergeAbortSignals(primary: AbortSignal, secondary: AbortSignal): AbortSignal {
248175
const controller = new AbortController();
249176

open-sse/executors/base/headers.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Pure upstream header helpers (User-Agent, extra headers, OpenAI-compat stripping).
2+
// Extracted verbatim from base.ts. Module-private JsonRecord kept local to avoid a cycle.
3+
4+
type JsonRecord = Record<string, unknown>;
5+
6+
/** Apply model-level extra upstream headers (e.g. Authentication, X-Custom-Auth). */
7+
export function mergeUpstreamExtraHeaders(
8+
headers: Record<string, string>,
9+
extra?: Record<string, string> | null
10+
): void {
11+
if (!extra) return;
12+
for (const [k, v] of Object.entries(extra)) {
13+
if (typeof k === "string" && k.length > 0 && typeof v === "string") {
14+
if (k.toLowerCase() === "user-agent") {
15+
setUserAgentHeader(headers, v);
16+
continue;
17+
}
18+
headers[k] = v;
19+
}
20+
}
21+
}
22+
23+
export function getCustomUserAgent(providerSpecificData?: JsonRecord | null): string | null {
24+
const customUserAgent =
25+
typeof providerSpecificData?.customUserAgent === "string"
26+
? providerSpecificData.customUserAgent.trim()
27+
: "";
28+
return customUserAgent || null;
29+
}
30+
31+
export function setUserAgentHeader(headers: Record<string, string>, userAgent: string): void {
32+
headers["User-Agent"] = userAgent;
33+
if ("user-agent" in headers) {
34+
headers["user-agent"] = userAgent;
35+
}
36+
}
37+
38+
export function applyConfiguredUserAgent(
39+
headers: Record<string, string>,
40+
providerSpecificData?: JsonRecord | null
41+
): void {
42+
const customUserAgent = getCustomUserAgent(providerSpecificData);
43+
if (customUserAgent) {
44+
setUserAgentHeader(headers, customUserAgent);
45+
}
46+
}
47+
48+
/**
49+
* Returns true when the outbound request targets an OpenAI-compatible endpoint
50+
* (a `openai-compatible-*` provider, or a Chat Completions / Responses URL).
51+
* Used to scope the X-Stainless strip narrowly so genuine SDK-spoofing paths
52+
* (e.g. Claude Code compat, which legitimately ADDS X-Stainless-*) are untouched.
53+
*/
54+
export function isOpenAICompatibleEndpoint(provider: string, url: string): boolean {
55+
if (provider?.startsWith?.("openai-compatible-")) return true;
56+
return url.includes("/v1/chat/completions") || url.includes("/v1/responses");
57+
}
58+
59+
/**
60+
* Strip OpenAI SDK (`X-Stainless-*`) metadata headers and normalize an SDK-derived
61+
* User-Agent for OpenAI-compatible passthrough requests. Some upstream gateways
62+
* 403 on these SDK-identifying headers. Only applied to OpenAI-compatible endpoints —
63+
* other providers (Claude/Claude Code compat) may legitimately send X-Stainless-*.
64+
*
65+
* Mutates `headers` in place and returns the list of stripped header keys (for logging).
66+
*/
67+
export function stripStainlessHeadersForOpenAICompat(
68+
headers: Record<string, string>,
69+
provider: string,
70+
url: string
71+
): string[] {
72+
if (!isOpenAICompatibleEndpoint(provider, url)) return [];
73+
74+
const strippedKeys: string[] = [];
75+
for (const key of Object.keys(headers)) {
76+
if (key.toLowerCase().startsWith("x-stainless-")) {
77+
delete headers[key];
78+
strippedKeys.push(key);
79+
}
80+
}
81+
82+
// Normalize User-Agent: SDK-based clients send verbose product strings that some
83+
// upstreams block. Replace with a clean browser-like UA only when it looks SDK-derived.
84+
const ua = (headers["User-Agent"] || headers["user-agent"] || "").toLowerCase();
85+
if (
86+
ua.includes("openai") &&
87+
(ua.includes("node") || ua.includes("axios") || ua.includes("undici"))
88+
) {
89+
setUserAgentHeader(headers, "Mozilla/5.0 (compatible; OpenAI Compatible)");
90+
}
91+
92+
return strippedKeys;
93+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { test } from "node:test";
2+
import assert from "node:assert/strict";
3+
import { readFileSync } from "node:fs";
4+
import { fileURLToPath } from "node:url";
5+
import { dirname, join } from "node:path";
6+
7+
// Split-guard for the base executor header-helper extraction.
8+
// The pure upstream-header helpers live in base/headers.ts (no host state, no fetch).
9+
// base.ts re-exports all 6 so the ~18 executors + tests that import them from "./base.ts"
10+
// keep resolving unchanged.
11+
const HERE = dirname(fileURLToPath(import.meta.url));
12+
const EXE = join(HERE, "../../open-sse/executors");
13+
const HOST = join(EXE, "base.ts");
14+
const LEAF = join(EXE, "base/headers.ts");
15+
16+
test("leaf hosts the header helpers and does not import the host", () => {
17+
const src = readFileSync(LEAF, "utf8");
18+
for (const sym of [
19+
"mergeUpstreamExtraHeaders",
20+
"setUserAgentHeader",
21+
"isOpenAICompatibleEndpoint",
22+
"stripStainlessHeadersForOpenAICompat",
23+
]) {
24+
assert.match(src, new RegExp(`export function ${sym}\\b`));
25+
}
26+
assert.doesNotMatch(src, /from "\.\.\/base\.ts"/);
27+
});
28+
29+
test("host re-exports all 6 header helpers for external importers", () => {
30+
const host = readFileSync(HOST, "utf8");
31+
for (const sym of [
32+
"mergeUpstreamExtraHeaders",
33+
"getCustomUserAgent",
34+
"setUserAgentHeader",
35+
"applyConfiguredUserAgent",
36+
"isOpenAICompatibleEndpoint",
37+
"stripStainlessHeadersForOpenAICompat",
38+
]) {
39+
assert.match(host, new RegExp(`\\b${sym}\\b`));
40+
}
41+
assert.match(host, /from "\.\/base\/headers\.ts"/);
42+
});
43+
44+
test("header helpers behave via base.ts (the public import path)", async () => {
45+
const mod = await import("../../open-sse/executors/base.ts");
46+
assert.equal(mod.isOpenAICompatibleEndpoint("openai-compatible-x", "https://x/y"), true);
47+
const headers: Record<string, string> = { "x-stainless-lang": "js", "User-Agent": "openai-node" };
48+
const stripped = mod.stripStainlessHeadersForOpenAICompat(
49+
headers,
50+
"openai-compatible-x",
51+
"https://x/v1/chat/completions"
52+
);
53+
assert.ok(stripped.includes("x-stainless-lang"));
54+
assert.equal(headers["x-stainless-lang"], undefined);
55+
});

0 commit comments

Comments
 (0)