-
Notifications
You must be signed in to change notification settings - Fork 0
fix(responses): preserve caller User-Agent through auth materialization #591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
f700c56
1074d31
332ca11
83dd725
23a26d6
759f486
feba174
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
|
|
||
|
|
||
| export { stripCanonicalForwardSamplingParams } from "./openai-responses/canonical-forward"; | ||
| export { FORWARD_HEADERS, createResponsesPassthroughAdapter } from "./openai-responses/passthrough"; | ||
| export { applyCallerUserAgentFallback, FORWARD_HEADERS, createResponsesPassthroughAdapter } from "./openai-responses/passthrough"; | ||
| export { sanitizeReasoningInputContent } from "./openai-responses/reasoning"; | ||
| export { stripOpenAiOnlyWebSearchFields } from "./openai-responses/web-search"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,7 @@ export const FORWARD_HEADERS = [ | |
| "session_id", | ||
| "session-id", | ||
| "thread-id", | ||
| "user-agent", | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — this touches the shared allowlist consumed by auth materialization, so it needs the explicit security review per
devin-ai-integration[bot] marked this conversation as resolved.
devin-ai-integration[bot] marked this conversation as resolved.
|
||
| "x-client-request-id", | ||
| "x-codex-beta-features", | ||
| "x-codex-installation-id", | ||
|
|
@@ -80,14 +81,50 @@ export const FORWARD_HEADERS = [ | |
| CODEX_RESPONSES_LITE_HEADER, | ||
| ]; | ||
|
|
||
| /** Preserve the caller fingerprint unless the provider explicitly owns that header. */ | ||
| function applyCallerUserAgentFallback( | ||
| headers: Record<string, string>, | ||
| incoming: IncomingMeta, | ||
| /** | ||
| * Preserve the caller fingerprint unless the provider explicitly owns that header. The one | ||
| * non-credential caller header this adapter forwards is applied here rather than in the | ||
| * FORWARD_HEADERS overlay loops so a configured provider header always wins case-insensitively. | ||
| * Exported so the web-search and vision sidecars and the standalone search/images/live/context | ||
| * relays apply the same precedence on their replays. Accepts either the mutable header record | ||
| * most callers build or a `Headers` object (context-history materializes into one). | ||
| * | ||
| * `providerHeaders` is consulted directly — not via the outbound set — because compact and audio | ||
| * materialize caller headers without ever merging provider.headers; a configured value must still | ||
| * win there. | ||
| */ | ||
| export function applyCallerUserAgentFallback( | ||
| headers: Record<string, string> | Headers, | ||
| callerHeaders: Headers, | ||
| providerHeaders?: Record<string, string> | Headers, | ||
| ): void { | ||
| const configured = providerHeaders === undefined ? null : readUserAgentHeader(providerHeaders); | ||
| if (headers instanceof Headers) { | ||
| if (configured !== null) headers.set("user-agent", configured); | ||
| else if (!headers.has("user-agent")) { | ||
| const caller = callerHeaders.get("user-agent"); | ||
| if (caller) headers.set("user-agent", caller); | ||
| } | ||
| return; | ||
| } | ||
| if (configured !== null) { | ||
| for (const name of Object.keys(headers)) { | ||
| if (name.toLowerCase() === "user-agent") delete headers[name]; | ||
| } | ||
| headers["User-Agent"] = configured; | ||
| return; | ||
| } | ||
| if (Object.keys(headers).some(name => name.toLowerCase() === "user-agent")) return; | ||
| const userAgent = incoming.headers.get("user-agent"); | ||
| if (userAgent) headers["User-Agent"] = userAgent; | ||
| const caller = callerHeaders.get("user-agent"); | ||
| if (caller) headers["User-Agent"] = caller; | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| function readUserAgentHeader(source: Record<string, string> | Headers): string | null { | ||
| if (source instanceof Headers) return source.get("user-agent"); | ||
| for (const [name, value] of Object.entries(source)) { | ||
| if (name.toLowerCase() === "user-agent") return value; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** Replace every `input_image` part under a routed-compaction body with a short marker. */ | ||
|
|
@@ -228,7 +265,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig): | |
| if (name.toLowerCase() === h) delete headers[name]; | ||
| } | ||
| } | ||
| headers[h] = v; // …so genuine forwarded fields win. | ||
| // user-agent stays available through auth materialization but is fallback-only | ||
| // here: applyCallerUserAgentFallback below keeps a configured header authoritative. | ||
| if (h !== "user-agent") headers[h] = v; // …so genuine forwarded fields win. | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -250,7 +289,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig): | |
| // Some Responses-compatible gateways select their Codex compatibility path from the real | ||
| // client fingerprint. This is a single non-credential fallback, not broader caller-header | ||
| // forwarding. Static provider headers remain authoritative in either auth mode. | ||
| applyCallerUserAgentFallback(headers, incoming); | ||
| applyCallerUserAgentFallback(headers, incoming.headers); | ||
|
|
||
| const forward = provider.authMode === "forward"; | ||
| let convertedRoutedCustomToolNames: Set<string> | undefined; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.