Skip to content

📨 feat: Custom Headers on Built-in Provider Endpoints#13742

Merged
danny-avila merged 5 commits into
devfrom
claude/stupefied-bhaskara-ccbcc1
Jun 14, 2026
Merged

📨 feat: Custom Headers on Built-in Provider Endpoints#13742
danny-avila merged 5 commits into
devfrom
claude/stupefied-bhaskara-ccbcc1

Conversation

@danny-avila

Copy link
Copy Markdown
Owner

Summary

I added a headers config option to the built-in openAI, anthropic, and google endpoints (including Anthropic and Google Vertex), mirroring the dynamic-header mechanism that custom endpoints already have. This lets operators route native providers through an AI gateway / reverse proxy and attach per-user/per-conversation metadata headers — without giving up provider-native request shaping (Anthropic system/Vertex rawPredict, Google SDK behavior, streaming, provider-specific params).

Resolves #13082 and covers #13713. It also paves the way for the maintainer plan: the existing {{LIBRECHAT_BODY_CONVERSATIONID}} convention now works on the built-in Anthropic endpoint, so forwarding the conversation id to a reverse proxy is simply a header — and because native provider APIs ignore unknown headers, there is no 400 and no reverse-proxy gating to reason about.

Example:

endpoints:
  anthropic:
    headers:
      cf-aig-metadata: '{"user_email":"{{LIBRECHAT_USER_EMAIL}}","app":"librechat"}'
      X-Conversation-Id: '{{LIBRECHAT_BODY_CONVERSATIONID}}'
  openAI:
    headers:
      cf-aig-metadata: '{"user_email":"{{LIBRECHAT_USER_EMAIL}}"}'
  all:
    headers:
      X-App: 'librechat'   # global default; endpoint values win on key collisions
  • Added headers to baseEndpointSchema so openAI, google, anthropic, and all accept it (values keep their placeholders until request time).
  • Added mergeHeaders and resolveConfigHeaders utilities that centralize the three provider-specific header carriers — OpenAI/Azure/custom configuration.defaultHeaders, native Anthropic clientOptions.defaultHeaders, and native Google customHeaders — and resolve env/user/body placeholders across all of them.
  • Threaded configured headers (endpoint layered over endpoints.all) into each initializer (anthropic, openai, google), keeping provider-managed headers authoritative: anthropic-beta is comma-unioned, and auth/version headers always win on collision so a metadata header can never override them.
  • Replaced the custom-endpoint-only header resolution in the main agent run and title-generation flows with resolveConfigHeaders, so native providers resolve request-time placeholders (e.g. conversationId) too.
  • Refactored getOpenAIConfig to reuse the shared mergeHeaders helper, removing the duplicate local merge function.
  • Documented the option for openAI, google, and anthropic in librechat.example.yaml.

Change Type

  • New feature (non-breaking change which adds functionality)
  • Documentation update

Testing

Default behavior is unchanged for anyone not configuring headers. To verify the feature, point a provider at a gateway/reverse-proxy (e.g. ANTHROPIC_REVERSE_PROXY), add a headers block under that endpoint in librechat.yaml, send a message, and confirm the resolved headers (with {{LIBRECHAT_BODY_CONVERSATIONID}} / {{LIBRECHAT_USER_EMAIL}} substituted) arrive at the proxy while native request formatting is intact.

Test Configuration:

Automated coverage added/updated and passing:

  • packages/api/src/utils/headers.spec.tsmergeHeaders precedence + anthropic-beta union; resolveConfigHeaders across all three carriers, including env-var and conversationId placeholders.
  • packages/api/src/endpoints/anthropic/{llm,initialize}.spec.ts — native Anthropic formatting preserved while a custom metadata header is attached; anthropic-beta not clobbered; endpoints.all layering; placeholders left unresolved at init.
  • packages/api/src/endpoints/google/llm.spec.ts — headers merged into customHeaders, provider Authorization wins.
  • packages/api/src/endpoints/openai/initialize.spec.ts — endpoint headers (merged over all) forwarded to getOpenAIConfig.

tsc --noEmit, ESLint, and the import-sort check pass for all changed files; the affected packages/api (endpoints, utils, agents, app) and api agents-client suites are green.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • I have made pertinent documentation changes
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

Add a `headers` config option to the built-in `openAI`, `anthropic`, and
`google` endpoints (incl. Anthropic/Google Vertex), mirroring the custom
endpoint header mechanism. Values support the same placeholder resolution
(env vars, `{{LIBRECHAT_USER_*}}`, `{{LIBRECHAT_BODY_CONVERSATIONID}}`) and
are resolved at request time so dynamic values like conversationId resolve
against the live request — without losing provider-native request shaping.

Closes #13082. Covers #13713: forwarding conversationId to a reverse proxy
is now `X-Conversation-Id: '{{LIBRECHAT_BODY_CONVERSATIONID}}'` — an unknown
header is ignored by the native Anthropic API, so no 400 and no metadata
gating needed.

- Schema: `headers` on `baseEndpointSchema` (openAI/google/anthropic/all).
- New `mergeHeaders`/`resolveConfigHeaders` utils centralize the per-provider
  header locations (`configuration.defaultHeaders`, Anthropic
  `clientOptions.defaultHeaders`, Google `customHeaders`); provider-managed
  headers (auth, `anthropic-beta`) always win on collision.
- Each initializer threads configured headers (endpoint over `all`) into the
  right place; request-time resolution runs across all locations in the main
  and title flows.
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review

Adding `headers` (a Record) to `baseEndpointSchema` makes `DeepPartial<TCustomConfig>`
widen its value type to `string | undefined`, which is not assignable to the
concrete `TEndpoint['headers']: Record<string, string>` at the `loadedEndpoints.all`
assignment. Cast at the assignment site, mirroring the existing
`anthropicConfig as TAnthropicEndpoint` cast in the same function.
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1600abebf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/controllers/agents/client.js Outdated
Comment on lines +1642 to +1646
* (OpenAI `configuration.defaultHeaders`, Google `customHeaders`). The
* Anthropic `clientOptions` carrier is stripped from title requests via
* `omitTitleOptions`, so its headers do not apply to title generation.
*/
if (clientOptions?.configuration?.defaultHeaders != null) {
clientOptions.configuration.defaultHeaders = resolveHeaders({
headers: clientOptions.configuration.defaultHeaders,
user: createSafeUser(this.options.req?.user),
body: {
messageId: this.responseMessageId,
conversationId: this.conversationId,
parentMessageId: this.parentMessageId,
},
});
}
resolveConfigHeaders({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Anthropic headers on title requests

When the title provider is Anthropic and endpoints.anthropic.headers or endpoints.all.headers is used for a gateway/reverse proxy, this call runs after omitTitleOptions has already removed the Anthropic clientOptions carrier, so resolveConfigHeaders never sees clientOptions.defaultHeaders. Normal agent calls include the configured headers, but title generation omits them and can fail or be routed without the required metadata; keep the default headers for title requests or move them into a carrier that is not stripped.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 998a945. Title flow now captures clientOptions.defaultHeaders before the omitTitleOptions strip and restores just that carrier afterward (api/server/controllers/agents/client.js), so Anthropic gateway metadata reaches title requests while thinking/streaming options stay stripped. Added a test in client.test.js ("preserves Anthropic custom headers on title requests despite omitTitleOptions").

Comment thread packages/api/src/utils/headers.ts Outdated
Comment on lines +26 to +29
const merged: Record<string, string> = { ...(base ?? {}), ...(override ?? {}) };

const baseBeta = base?.['anthropic-beta'];
const overrideBeta = override?.['anthropic-beta'];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize header names before merging

HTTP header names are case-insensitive, but this merge only detects exact key matches and only unions the exact lowercase anthropic-beta key. If an operator configures a case variant such as authorization or Anthropic-Beta while the provider code adds Authorization/anthropic-beta, the outbound object contains both names; clients commonly collapse those into one header value, which can invalidate auth or drop/garble the beta header instead of making provider-managed headers win.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 998a945. mergeHeaders now matches header names case-insensitively: an override key replaces any case variant from the base (keeping the override's casing) instead of emitting both, and anthropic-beta is unioned case-insensitively. Covered by two new cases in headers.spec.ts.

Comment on lines +130 to +132
const headers = mergeHeaders(allConfig?.headers, openAIConfig?.headers);
if (headers) {
clientOptions.headers = headers;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip headers for user-provided OpenAI URLs

When OPENAI_REVERSE_PROXY is user_provided, baseURL is controlled by the user, but this branch still attaches admin-configured headers that can contain ${...} gateway secrets or user/OpenID placeholders resolved later by resolveConfigHeaders. In that configuration a user can point the built-in OpenAI endpoint at their own URL and receive those headers; avoid forwarding configured headers when userProvidesURL is true.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 998a945. initializeOpenAI now withholds configured headers when userProvidesURL is true (OPENAI_REVERSE_PROXY=user_provided), so ${SECRET}/token placeholders never reach a user-controlled base URL — mirroring the existing fetchModels/custom-endpoint guard. Test: "withholds configured headers when the user supplies the base URL".

clientOptions.azure =
userProvidesKey && userValues?.apiKey ? JSON.parse(userValues.apiKey) : getAzureCredentials();
apiKey = clientOptions.azure ? clientOptions.azure.azureOpenAIApiKey : undefined;
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply global headers to Azure OpenAI too

Because the header merge lives only in this non-Azure branch, requests through azureOpenAI never receive endpoints.all.headers even though the new schema/docs present all.headers as the global default and Azure uses the same OpenAI configuration.defaultHeaders carrier. In deployments where the Azure endpoint is routed through a gateway that requires the global metadata/auth header, OpenAI/Google/Anthropic calls work but Azure calls are sent without it; merge the global headers into the Azure path while keeping Azure's managed api-key/version headers authoritative.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 998a945. endpoints.all.headers now applies to both Azure paths (mapped-config and env-based) via the shared OpenAI configuration.defaultHeaders carrier; the Azure-managed api-key/version headers are still applied last in getOpenAIConfig so they stay authoritative. Test: "applies endpoints.all headers to the env-based Azure path".

Address Codex P2 findings on the custom-headers feature:

- Anthropic title requests: `omitTitleOptions` strips the `clientOptions`
  carrier, which dropped its `defaultHeaders`. Preserve just the header carrier
  so gateway/reverse-proxy metadata still reaches title generation.
- mergeHeaders: match header names case-insensitively so an override (e.g. a
  provider-managed `Authorization`/`anthropic-beta`) replaces/uniones a
  case-variant from the base instead of emitting two names a client may collapse.
- OpenAI: withhold admin-configured headers when the user supplies the base URL
  (`user_provided`), since values may carry `${SECRET}`/token placeholders that
  must not reach a user-controlled endpoint — mirrors the custom-endpoint guard.
- Azure: honor global `endpoints.all` headers (same OpenAI carrier) while keeping
  Azure-managed `api-key`/version headers authoritative.

Adds tests for each.
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 998a945aad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* `api-key`/version headers added in `getOpenAIConfig`) stay authoritative. */
clientOptions.headers = resolveHeaders({
headers: { ...headers, ...(clientOptions.headers ?? {}) },
headers: { ...globalHeaders, ...headers, ...(clientOptions.headers ?? {}) },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep Azure global headers unresolved until request time

In the Azure model-group path, the newly added globalHeaders are passed through resolveHeaders during initialization, but the same configuration.defaultHeaders are resolved again later by resolveConfigHeaders in the agent/title request flow. If an endpoints.all.headers value contains a user placeholder and the user's name/email contains a ${SOME_ENV} pattern, the first pass substitutes the user-controlled value and the second pass expands it as an environment variable, violating the env-before-user invariant and forwarding that env value to the Azure/gateway request. Keep these new global headers unresolved here and let request-time resolution run once.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ceecdd6. The global endpoints.all headers are no longer passed through resolveHeaders at init in the Azure model-group path — they're merged in unresolved and resolved exactly once at request time by resolveConfigHeaders, so an already-substituted user value can't be re-expanded as an env var. Test: openai initialize spec now asserts the global header reaches getOpenAIConfig unresolved ({{LIBRECHAT_USER_ID}}).

Comment thread packages/api/src/utils/headers.ts Outdated
Comment on lines +111 to +112
if (googleConfig.customHeaders != null) {
googleConfig.customHeaders = resolve(googleConfig.customHeaders as Record<string, string>);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not template-resolve Google auth headers

When GOOGLE_AUTH_HEADER=true, getGoogleConfig builds customHeaders.Authorization from the API key, and this new resolver now treats that provider-managed header as an admin-authored template. With GOOGLE_KEY=user_provided, a user can save a key containing ${OPENAI_API_KEY} (or another non-blocklisted env name), and this line expands it before the outbound Google/gateway request, letting user-controlled key material reference server environment values. Only the configured metadata headers should be resolved, or auth headers should be excluded from placeholder expansion.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ceecdd6. resolveConfigHeaders now skips the provider-managed Authorization (case-insensitive) when resolving Google customHeaders, so a user-provided key containing ${ENV} is never env-expanded; admin metadata headers in the same map still resolve. Scoped to the Google customHeaders carrier only — custom/OpenAI configuration.defaultHeaders still resolve Authorization (custom endpoints rely on it). Test added in headers.spec.ts.

streamRate: z.number().optional(),
baseURL: z.string().optional(),
/**
* Custom request headers forwarded to the provider on every request. Values

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Thread built-in headers into model fetches

This makes built-in endpoint headers a valid config and describes them as forwarded on every provider request, but the /api/models path still loads OpenAI/Anthropic defaults via loadDefaultModelsgetOpenAIModels/fetchAnthropicModels without passing appConfig.endpoints.*.headers. When a built-in provider is behind a gateway that requires one of these headers for routing/auth, chat calls include it but model-list fetches omit it and fall back to stale defaults or fail to expose gateway models, so the header config needs to be threaded through model loading too.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ceecdd6. loadDefaultModels now threads the configured headers (endpoint over all) plus the user object into getOpenAIModels/getAnthropicModelsfetchModels. Also fixed fetchModels to merge custom headers for Anthropic rather than overwriting them with just x-api-key/anthropic-version (managed headers still win). Body placeholders can't resolve without conversation context, same as the custom-endpoint model-fetch path. Tests added in models.spec.ts.

…und 2)

Address Codex P2 findings:

- Azure: keep global `endpoints.all` headers unresolved at init and let
  request-time `resolveConfigHeaders` resolve them once, avoiding a
  second-order env expansion of already-substituted user values.
- Google: `resolveConfigHeaders` no longer template-resolves the
  provider-managed `Authorization` header (built from a possibly user-provided
  key), so a user key like `${ENV}` can't leak server environment values.
- Model fetches: thread configured headers (endpoint over `all`) + user object
  through `getOpenAIModels`/`getAnthropicModels` → `fetchModels`, so a
  gateway-fronted built-in provider receives the header on `/models` too. Fixed
  `fetchModels` to merge custom headers for Anthropic instead of overwriting
  them (managed `x-api-key`/version still win).

Adds/updates tests for each.
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ceecdd6ab5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/utils/headers.ts Outdated
Comment on lines +132 to +134
for (const [key, value] of Object.entries(headers)) {
(key.toLowerCase() === 'authorization' ? managed : templated)[key] = value;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve admin-supplied Google Authorization headers

When a Google/Vertex endpoint is configured to authenticate to a gateway with headers.Authorization: 'Bearer {{LIBRECHAT_OPENID_ID_TOKEN}}' (or another placeholder) and GOOGLE_AUTH_HEADER is not enabled, this branch classifies that admin-supplied header as provider-managed and skips resolveHeaders, so the gateway receives the literal placeholder instead of the user token. Only the SDK-generated Authorization: Bearer ${apiKey} needs this protection; custom Authorization headers still need the same placeholder resolution as other configured headers.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 828169a. Reworked the approach: native Google headers are now resolved once at init in initializeGoogle (before the GOOGLE_AUTH_HEADER key-derived header is built), and resolveConfigHeaders no longer touches Google customHeaders. So an admin-supplied Authorization: Bearer {{LIBRECHAT_OPENID_ID_TOKEN}} resolves again (the previous round's name-based skip was too broad — thanks for catching that), while the SDK auth header still never passes through env expansion.

...(model_parameters ?? {}),
user: req.user?.id,
},
...(headers && { headers }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve native headers for memory runs

When a memory agent uses the built-in Anthropic endpoint with configured headers containing placeholders, the headers are added here intentionally unresolved, but the memory extraction path never calls resolveConfigHeaders and only resolves OpenAI-style configuration.defaultHeaders in packages/api/src/agents/memory.ts. The normal chat/title paths are covered, but memory requests will forward literals like {{LIBRECHAT_USER_EMAIL}} or ${GATEWAY_KEY}, so gateways that require these resolved metadata/auth headers reject memory extraction.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 828169a. processMemory now calls resolveConfigHeaders (with { conversationId, messageId }) instead of the OpenAI-only configuration.defaultHeaders resolution, so native Anthropic clientOptions.defaultHeaders resolve for memory runs too. Native Google headers are covered by the init-time resolution above. Test added in memory.spec.ts via the updated mock.

Comment thread api/server/controllers/agents/client.js Outdated
Comment on lines +1639 to +1640
if (anthropicDefaultHeaders != null && clientOptions.clientOptions == null) {
clientOptions.clientOptions = { defaultHeaders: anthropicDefaultHeaders };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the Vertex clientOptions object for titles

For Anthropic Vertex title generation with templated headers, this recreates clientOptions after omitTitleOptions removes the original, but the preserved createClient closure from getLLMConfig still closes over the original requestOptions.clientOptions. resolveConfigHeaders then replaces the headers on this new object, while the Vertex client is created with the old unresolved headers, so title calls through a gateway can receive {{LIBRECHAT_BODY_CONVERSATIONID}} literally. Keep the original clientOptions object (or update the object captured by createClient) when preserving the headers.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 828169a. The title flow now restores the original clientOptions object reference (not a fresh { defaultHeaders }) after omitTitleOptions, so the Vertex createClient closure and resolveConfigHeaders operate on the same object — the client is built with the resolved headers. Good catch on the closure aliasing.

Comment on lines +897 to +901
resolveConfigHeaders({
llmConfig,
user: createSafeUser(user),
body: requestBody,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid resolving the same header map twice

When the same initialized built-in-provider agent object is reused as both a root agent and a subagent target (or by multiple parents), buildAgentInput can run more than once on the same nested header objects. Because resolveConfigHeaders mutates those objects in place, the second call runs env expansion after user/body substitution from the first call; a user-controlled value such as a name containing ${GATEWAY_SECRET} can then be expanded against process.env, violating the env-before-user invariant documented in resolveHeaders.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 828169a. resolveConfigHeaders is now idempotent — it tracks resolved header maps in a WeakSet (keyed by object identity, per-request fresh) and skips re-resolution, so a second buildAgentInput pass over a reused agent's nested header objects can't re-run env expansion over already-substituted user/body values. Test added in headers.spec.ts.

@github-actions

Copy link
Copy Markdown
Contributor

GitNexus: ❌ deploy failed

The deploy failed — the previous index (if any) continues to be served.
Deploy run

…ound 3)

Address Codex P2 findings, including two regressions from the prior round:

- Google auth (findings 6 & 8): move native Google header resolution to init
  (`initializeGoogle`), resolving admin templates BEFORE the key-derived auth
  header is built. resolveConfigHeaders no longer touches Google `customHeaders`,
  so admin `Authorization` templates resolve again (fixes the round-2 regression)
  while the SDK auth header (possibly a user-provided key) is never env-expanded.
- Memory runs: memory extraction now calls `resolveConfigHeaders`, so native
  Anthropic (and OpenAI) headers resolve for memory requests too.
- Vertex titles: restore the ORIGINAL `clientOptions` object reference (not a
  copy) when preserving headers across `omitTitleOptions`, so the Vertex
  `createClient` closure and the resolved headers stay on the same object.
- Reuse: `resolveConfigHeaders` is now idempotent (resolve-once per header map),
  preventing a second pass from env-expanding values already substituted with
  user/body data when an agent object flows through buildAgentInput twice.

Adds/updates tests for each.
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 828169a2a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

Copy link
Copy Markdown
Contributor

GitNexus: ❌ deploy failed

The deploy failed — the previous index (if any) continues to be served.
Deploy run

@danny-avila danny-avila linked an issue Jun 14, 2026 that may be closed by this pull request
@danny-avila
danny-avila merged commit 2350ebb into dev Jun 14, 2026
32 checks passed
@danny-avila
danny-avila deleted the claude/stupefied-bhaskara-ccbcc1 branch June 14, 2026 21:02
fuuuzzy pushed a commit to fuuuzzy/LibreChat that referenced this pull request Jun 18, 2026
)

* 📨 feat: Custom Headers on Built-in Provider Endpoints

Add a `headers` config option to the built-in `openAI`, `anthropic`, and
`google` endpoints (incl. Anthropic/Google Vertex), mirroring the custom
endpoint header mechanism. Values support the same placeholder resolution
(env vars, `{{LIBRECHAT_USER_*}}`, `{{LIBRECHAT_BODY_CONVERSATIONID}}`) and
are resolved at request time so dynamic values like conversationId resolve
against the live request — without losing provider-native request shaping.

Closes danny-avila#13082. Covers danny-avila#13713: forwarding conversationId to a reverse proxy
is now `X-Conversation-Id: '{{LIBRECHAT_BODY_CONVERSATIONID}}'` — an unknown
header is ignored by the native Anthropic API, so no 400 and no metadata
gating needed.

- Schema: `headers` on `baseEndpointSchema` (openAI/google/anthropic/all).
- New `mergeHeaders`/`resolveConfigHeaders` utils centralize the per-provider
  header locations (`configuration.defaultHeaders`, Anthropic
  `clientOptions.defaultHeaders`, Google `customHeaders`); provider-managed
  headers (auth, `anthropic-beta`) always win on collision.
- Each initializer threads configured headers (endpoint over `all`) into the
  right place; request-time resolution runs across all locations in the main
  and title flows.

* 🩹 fix: Cast endpoints.all to TEndpoint for headers DeepPartial widening

Adding `headers` (a Record) to `baseEndpointSchema` makes `DeepPartial<TCustomConfig>`
widen its value type to `string | undefined`, which is not assignable to the
concrete `TEndpoint['headers']: Record<string, string>` at the `loadedEndpoints.all`
assignment. Cast at the assignment site, mirroring the existing
`anthropicConfig as TAnthropicEndpoint` cast in the same function.

* 🛡️ fix: Harden built-in endpoint custom headers (Codex review)

Address Codex P2 findings on the custom-headers feature:

- Anthropic title requests: `omitTitleOptions` strips the `clientOptions`
  carrier, which dropped its `defaultHeaders`. Preserve just the header carrier
  so gateway/reverse-proxy metadata still reaches title generation.
- mergeHeaders: match header names case-insensitively so an override (e.g. a
  provider-managed `Authorization`/`anthropic-beta`) replaces/uniones a
  case-variant from the base instead of emitting two names a client may collapse.
- OpenAI: withhold admin-configured headers when the user supplies the base URL
  (`user_provided`), since values may carry `${SECRET}`/token placeholders that
  must not reach a user-controlled endpoint — mirrors the custom-endpoint guard.
- Azure: honor global `endpoints.all` headers (same OpenAI carrier) while keeping
  Azure-managed `api-key`/version headers authoritative.

Adds tests for each.

* 🔐 fix: Resolve-once + provider-managed header safety (Codex review round 2)

Address Codex P2 findings:

- Azure: keep global `endpoints.all` headers unresolved at init and let
  request-time `resolveConfigHeaders` resolve them once, avoiding a
  second-order env expansion of already-substituted user values.
- Google: `resolveConfigHeaders` no longer template-resolves the
  provider-managed `Authorization` header (built from a possibly user-provided
  key), so a user key like `${ENV}` can't leak server environment values.
- Model fetches: thread configured headers (endpoint over `all`) + user object
  through `getOpenAIModels`/`getAnthropicModels` → `fetchModels`, so a
  gateway-fronted built-in provider receives the header on `/models` too. Fixed
  `fetchModels` to merge custom headers for Anthropic instead of overwriting
  them (managed `x-api-key`/version still win).

Adds/updates tests for each.

* 🧯 fix: Header provenance, memory/title coverage, idempotency (Codex round 3)

Address Codex P2 findings, including two regressions from the prior round:

- Google auth (findings 6 & 8): move native Google header resolution to init
  (`initializeGoogle`), resolving admin templates BEFORE the key-derived auth
  header is built. resolveConfigHeaders no longer touches Google `customHeaders`,
  so admin `Authorization` templates resolve again (fixes the round-2 regression)
  while the SDK auth header (possibly a user-provided key) is never env-expanded.
- Memory runs: memory extraction now calls `resolveConfigHeaders`, so native
  Anthropic (and OpenAI) headers resolve for memory requests too.
- Vertex titles: restore the ORIGINAL `clientOptions` object reference (not a
  copy) when preserving headers across `omitTitleOptions`, so the Vertex
  `createClient` closure and the resolved headers stay on the same object.
- Reuse: `resolveConfigHeaders` is now idempotent (resolve-once per header map),
  preventing a second pass from env-expanding values already substituted with
  user/body data when an agent object flows through buildAgentInput twice.

Adds/updates tests for each.
ThomasVuNguyen pushed a commit to ThomasVuNguyen/LibreChat that referenced this pull request Jul 15, 2026
)

* 📨 feat: Custom Headers on Built-in Provider Endpoints

Add a `headers` config option to the built-in `openAI`, `anthropic`, and
`google` endpoints (incl. Anthropic/Google Vertex), mirroring the custom
endpoint header mechanism. Values support the same placeholder resolution
(env vars, `{{LIBRECHAT_USER_*}}`, `{{LIBRECHAT_BODY_CONVERSATIONID}}`) and
are resolved at request time so dynamic values like conversationId resolve
against the live request — without losing provider-native request shaping.

Closes danny-avila#13082. Covers danny-avila#13713: forwarding conversationId to a reverse proxy
is now `X-Conversation-Id: '{{LIBRECHAT_BODY_CONVERSATIONID}}'` — an unknown
header is ignored by the native Anthropic API, so no 400 and no metadata
gating needed.

- Schema: `headers` on `baseEndpointSchema` (openAI/google/anthropic/all).
- New `mergeHeaders`/`resolveConfigHeaders` utils centralize the per-provider
  header locations (`configuration.defaultHeaders`, Anthropic
  `clientOptions.defaultHeaders`, Google `customHeaders`); provider-managed
  headers (auth, `anthropic-beta`) always win on collision.
- Each initializer threads configured headers (endpoint over `all`) into the
  right place; request-time resolution runs across all locations in the main
  and title flows.

* 🩹 fix: Cast endpoints.all to TEndpoint for headers DeepPartial widening

Adding `headers` (a Record) to `baseEndpointSchema` makes `DeepPartial<TCustomConfig>`
widen its value type to `string | undefined`, which is not assignable to the
concrete `TEndpoint['headers']: Record<string, string>` at the `loadedEndpoints.all`
assignment. Cast at the assignment site, mirroring the existing
`anthropicConfig as TAnthropicEndpoint` cast in the same function.

* 🛡️ fix: Harden built-in endpoint custom headers (Codex review)

Address Codex P2 findings on the custom-headers feature:

- Anthropic title requests: `omitTitleOptions` strips the `clientOptions`
  carrier, which dropped its `defaultHeaders`. Preserve just the header carrier
  so gateway/reverse-proxy metadata still reaches title generation.
- mergeHeaders: match header names case-insensitively so an override (e.g. a
  provider-managed `Authorization`/`anthropic-beta`) replaces/uniones a
  case-variant from the base instead of emitting two names a client may collapse.
- OpenAI: withhold admin-configured headers when the user supplies the base URL
  (`user_provided`), since values may carry `${SECRET}`/token placeholders that
  must not reach a user-controlled endpoint — mirrors the custom-endpoint guard.
- Azure: honor global `endpoints.all` headers (same OpenAI carrier) while keeping
  Azure-managed `api-key`/version headers authoritative.

Adds tests for each.

* 🔐 fix: Resolve-once + provider-managed header safety (Codex review round 2)

Address Codex P2 findings:

- Azure: keep global `endpoints.all` headers unresolved at init and let
  request-time `resolveConfigHeaders` resolve them once, avoiding a
  second-order env expansion of already-substituted user values.
- Google: `resolveConfigHeaders` no longer template-resolves the
  provider-managed `Authorization` header (built from a possibly user-provided
  key), so a user key like `${ENV}` can't leak server environment values.
- Model fetches: thread configured headers (endpoint over `all`) + user object
  through `getOpenAIModels`/`getAnthropicModels` → `fetchModels`, so a
  gateway-fronted built-in provider receives the header on `/models` too. Fixed
  `fetchModels` to merge custom headers for Anthropic instead of overwriting
  them (managed `x-api-key`/version still win).

Adds/updates tests for each.

* 🧯 fix: Header provenance, memory/title coverage, idempotency (Codex round 3)

Address Codex P2 findings, including two regressions from the prior round:

- Google auth (findings 6 & 8): move native Google header resolution to init
  (`initializeGoogle`), resolving admin templates BEFORE the key-derived auth
  header is built. resolveConfigHeaders no longer touches Google `customHeaders`,
  so admin `Authorization` templates resolve again (fixes the round-2 regression)
  while the SDK auth header (possibly a user-provided key) is never env-expanded.
- Memory runs: memory extraction now calls `resolveConfigHeaders`, so native
  Anthropic (and OpenAI) headers resolve for memory requests too.
- Vertex titles: restore the ORIGINAL `clientOptions` object reference (not a
  copy) when preserving headers across `omitTitleOptions`, so the Vertex
  `createClient` closure and the resolved headers stay on the same object.
- Reuse: `resolveConfigHeaders` is now idempotent (resolve-once per header map),
  preventing a second pass from env-expanding values already substituted with
  user/body data when an agent object flows through buildAgentInput twice.

Adds/updates tests for each.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support custom headers on built-in provider endpoints

1 participant