Skip to content

feat: Add site custom header override priority - #584

Open
aoguai wants to merge 3 commits into
cita-777:mainfrom
aoguai:site-custom-header-override
Open

feat: Add site custom header override priority#584
aoguai wants to merge 3 commits into
cita-777:mainfrom
aoguai:site-custom-header-override

Conversation

@aoguai

@aoguai aoguai commented Jun 14, 2026

Copy link
Copy Markdown

Summary

Adds an opt-in site-level setting that lets site custom headers override same-name outgoing request headers.

By default, metapi keeps the existing behavior: explicit request/runtime headers remain authoritative over site custom headers. When the new setting is enabled for a site, that site's configured custom headers are applied last, so headers like User-Agent, Originator, Version, or SDK-specific client headers can be forced per upstream site.

What changed

  • Add customHeadersOverrideRequestHeaders / custom_headers_override_request_headers to the site schema.
  • Add SQLite migration 0027_site_custom_headers_override_request_headers.
  • Update generated schema artifacts and MySQL/Postgres bootstrap SQL.
  • Add runtime schema compatibility for SQLite, MySQL, and Postgres.
  • Preserve the new field in database migration snapshots and backup export/import.
  • Accept the setting in site create/update API payloads.
  • Add a site editor checkbox for enabling custom header override behavior.
  • Extend mergeHeadersWithSiteCustomHeaders() with explicit merge priority:
    • request: current default behavior, request/runtime headers override site headers.
    • site: site custom headers override same-name request/runtime headers.
  • Apply the priority in both cached site proxy resolution and direct site record request init paths.
  • Add coverage for header merge priority, site proxy behavior, site API persistence, editor payloads, schema compatibility, schema artifacts, and backup roundtrip.

Why

metapi previously always merged headers as:

site custom headers -> explicit request/runtime headers

That means a site-configured User-Agent can be overwritten by downstream clients such as Hermes, Cherry Studio, OpenAI SDK, or Python SDK before the upstream request is sent.

This change keeps the current behavior as the default for compatibility, while giving each site an explicit opt-in override mode. It avoids broad downstream header passthrough and keeps the behavior scoped to existing site custom headers.

This is useful for upstream sites that require stable client-identifying headers while still preserving metapi's existing routing, fallback, account, check-in, and balance workflows.

Compatibility

  • Existing sites default to customHeadersOverrideRequestHeaders = false.
  • Existing request header priority remains unchanged unless the new checkbox is enabled.
  • The change does not relax the downstream header allowlist.
  • The change does not implement raw body passthrough.
  • The change does not introduce a global setting, so each upstream site can choose its own header policy.

Summary by CodeRabbit

  • New Features
    • Added customHeadersOverrideRequestHeaders to site settings, letting users choose whether custom site headers override same-named outbound request headers. Available in the site editor and supported across site create/update flows.
    • Included this setting in proxy resolution so header precedence follows the selected option.
  • Database / Compatibility
    • Added persistence and migration support for the new site field, including backup/export/import coverage.

@github-actions github-actions Bot added area: db Database and schema related changes area: web Web UI changes size: M 200 to 499 lines changed labels Jun 14, 2026
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (2)
  • src/server/db/generated/mysql.upgrade.sql is excluded by !**/generated/**
  • src/server/db/generated/postgres.upgrade.sql is excluded by !**/generated/**

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7179109b-8a4f-4438-95a3-4b3bcc45adb3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new per-site boolean field customHeadersOverrideRequestHeaders that controls whether site-defined custom headers take precedence over outbound request headers. The change spans DB schema and migration, multi-dialect schema compatibility, the header merge service, site proxy resolution, API route validation and persistence, backup/restore flows, database migration statements, and the frontend site editor UI.

Changes

customHeadersOverrideRequestHeaders end-to-end feature

Layer / File(s) Summary
DB schema, migration, and compatibility
src/server/db/schema.ts, drizzle/0027_site_custom_headers_override_request_headers.sql, drizzle/meta/_journal.json, src/server/db/siteSchemaCompatibility.ts, src/server/db/index.ts, src/server/db/schemaContract.test.ts, src/server/db/siteSchemaCompatibility.test.ts
Adds the custom_headers_override_request_headers boolean column to the Drizzle schema and SQL migration, registers the journal entry, adds per-dialect (SQLite/Postgres/MySQL) compatibility specs with NULL backfill, and wires a SQLite init helper. Schema contract and compatibility tests are extended accordingly.
Header merge priority types and function
src/server/services/siteCustomHeaders.ts, src/server/services/siteCustomHeaders.test.ts
Exports SiteCustomHeadersMergePriority ('request' | 'site') and SiteCustomHeadersMergeOptions. Reworks mergeHeadersWithSiteCustomHeadersto accept apriority` option controlling which header set wins for same-named keys. Unit tests cover default, site-priority, and null-site-headers behaviors.
Site proxy: DB query, resolution, and merge wiring
src/server/services/siteProxy.ts, src/server/services/siteProxy.test.ts
Extends internal row types and SiteProxyConfigLike with the new flag. Updates DB select/cache mapping, adds the flag to resolved config return values, introduces an internal priority mapper, and passes priority to mergeHeadersWithSiteCustomHeaders in both withSiteProxyRequestInit and withSiteRecordProxyRequestInit. Integration tests assert override behavior.
API route validation and persistence
src/server/contracts/siteRoutePayloads.ts, src/server/routes/api/sites.ts, src/server/routes/api/sites.proxyUrl.test.ts
Extends create and update payload schemas with the optional field. Adds normalizeCustomHeadersOverrideRequestHeadersFlag, validates the flag on both POST /api/sites and PUT /api/sites/:id (400 for non-boolean), and persists it in the insert/update transaction. Route tests cover create, update, and invalid-value rejection.
Backup and database migration services
src/server/services/backupService.ts, src/server/services/backupService.test.ts, src/server/services/databaseMigrationService.ts, src/server/services/databaseMigrationService.test.ts
Propagates customHeadersOverrideRequestHeaders (defaulting to false) through backup export/import for both ALL-API-Hub v2 and ref-format conversions, the import transaction site insert, and buildStatements(). Adds typed row aliases and updates query typing throughout. Backup roundtrip and migration statement builder tests are extended.
Frontend site editor form and UI
src/web/pages/helpers/sitesEditor.ts, src/web/pages/Sites.tsx, src/web/pages/helpers/sitesEditor.test.ts
Adds customHeadersOverrideRequestHeaders to SiteForm, SiteSavePayload, emptySiteForm(), and siteFormFromSite(). Extends SiteRow, wires the checkbox into handleSave, and renders a new labeled checkbox under the custom-headers section with state-dependent help text. sitesEditor unit tests are updated to cover all form paths.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Sites.tsx
  participant POST_PUT_sites as POST/PUT /api/sites
  participant siteProxy.ts
  participant mergeHeadersWithSiteCustomHeaders
  participant backupService.ts

  User->>Sites.tsx: Toggle customHeadersOverrideRequestHeaders checkbox
  Sites.tsx->>POST_PUT_sites: Save site with customHeadersOverrideRequestHeaders
  POST_PUT_sites->>POST_PUT_sites: normalizeCustomHeadersOverrideRequestHeadersFlag (400 if invalid)
  POST_PUT_sites-->>Sites.tsx: Persisted site row

  Note over siteProxy.ts: On outbound proxy request
  siteProxy.ts->>siteProxy.ts: resolveSiteRequestConfigByRequestUrl → customHeadersOverrideRequestHeaders
  siteProxy.ts->>mergeHeadersWithSiteCustomHeaders: siteCustomHeaders, requestHeaders,<br/>priority('site'|'request')
  mergeHeadersWithSiteCustomHeaders-->>siteProxy.ts: merged Headers

  Note over backupService.ts: On backup export/import
  backupService.ts->>backupService.ts: include customHeadersOverrideRequestHeaders<br/>(default false) in site rows
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • cita-777/metapi#59: Both PRs operate on the siteProxy.ts + mergeHeadersWithSiteCustomHeaders header-merge pipeline; this PR extends that merge with a configurable priority flag building directly on the same functionality.
  • cita-777/metapi#167: Both PRs modify buildAllApiHubV2AccountsSection in backupService.ts — this PR adds customHeadersOverrideRequestHeaders persistence during import while the referenced PR changed the same function's legacy/V2 account detection logic.
  • cita-777/metapi#230: Both PRs modify src/server/services/backupService.ts's account backup import path (importAccountsSection transaction), so the new customHeadersOverrideRequestHeaders site field integration is related to the backup-import behavior changes in that PR.

Suggested labels

area: server, area: web, area: db

Poem

🐇 A checkbox small, a flag so neat,
Now site headers can claim their seat.
The schema grows, migrations run,
Backup and proxy—all in one!
When override is true, site wins the race,
A bunny hops through every layer with grace. 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add site custom header override priority' directly and clearly summarizes the main change: introducing a feature that allows site custom headers to override request headers with configurable priority.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/server/services/databaseMigrationService.ts (1)

327-345: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve all persisted sites config columns in migration statements.

Line [327] adds the new header override field, but the sites statement still omits post_refresh_probe_enabled, post_refresh_probe_model, post_refresh_probe_scope, and post_refresh_probe_latency_threshold_ms. Those values are dropped during migration and reset to defaults in the target DB.

Proposed fix
-      columns: ['id', 'name', 'url', 'external_checkin_url', 'platform', 'proxy_url', 'use_system_proxy', 'custom_headers', 'custom_headers_override_request_headers', 'status', 'is_pinned', 'sort_order', 'global_weight', 'api_key', 'created_at', 'updated_at'],
+      columns: ['id', 'name', 'url', 'external_checkin_url', 'platform', 'proxy_url', 'use_system_proxy', 'custom_headers', 'custom_headers_override_request_headers', 'status', 'is_pinned', 'sort_order', 'global_weight', 'api_key', 'post_refresh_probe_enabled', 'post_refresh_probe_model', 'post_refresh_probe_scope', 'post_refresh_probe_latency_threshold_ms', 'created_at', 'updated_at'],
       values: [
         asNumber(row.id, 0),
         asNullableString(row.name),
         asNullableString(row.url),
         asNullableString(row.externalCheckinUrl),
         asNullableString(row.platform),
         asNullableString(row.proxyUrl),
         asBoolean(row.useSystemProxy, false),
         serializeColumnValue('sites', 'custom_headers', row.customHeaders, contract),
         asBoolean(row.customHeadersOverrideRequestHeaders, false),
         asNullableString(row.status) ?? 'active',
         asBoolean(row.isPinned, false),
         asNumber(row.sortOrder, 0),
         asNumber(row.globalWeight, 1),
         asNullableString(row.apiKey),
+        asBoolean((row as { postRefreshProbeEnabled?: unknown }).postRefreshProbeEnabled, false),
+        asNullableString((row as { postRefreshProbeModel?: unknown }).postRefreshProbeModel) ?? '',
+        asNullableString((row as { postRefreshProbeScope?: unknown }).postRefreshProbeScope) ?? 'single',
+        asNumber((row as { postRefreshProbeLatencyThresholdMs?: unknown }).postRefreshProbeLatencyThresholdMs, 0),
         asNullableString(row.createdAt),
         asNullableString(row.updatedAt),
       ],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/services/databaseMigrationService.ts` around lines 327 - 345, The
migration statement for the sites table is omitting four persisted columns that
should be preserved: post_refresh_probe_enabled, post_refresh_probe_model,
post_refresh_probe_scope, and post_refresh_probe_latency_threshold_ms. Add these
four column names to the columns array and add the corresponding converted
values from the row object to the values array in the INSERT statement. Use
appropriate type conversion functions (asBoolean for the enabled flag,
asNullableString for the model and scope, asNumber for the latency threshold)
consistent with the pattern used for existing fields in this migration block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/server/services/databaseMigrationService.ts`:
- Around line 327-345: The migration statement for the sites table is omitting
four persisted columns that should be preserved: post_refresh_probe_enabled,
post_refresh_probe_model, post_refresh_probe_scope, and
post_refresh_probe_latency_threshold_ms. Add these four column names to the
columns array and add the corresponding converted values from the row object to
the values array in the INSERT statement. Use appropriate type conversion
functions (asBoolean for the enabled flag, asNullableString for the model and
scope, asNumber for the latency threshold) consistent with the pattern used for
existing fields in this migration block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 70c39bfc-fbb2-49c6-b32d-d890b7ead934

📥 Commits

Reviewing files that changed from the base of the PR and between e72d19e and 102adf8.

⛔ Files ignored due to path filters (3)
  • src/server/db/generated/mysql.bootstrap.sql is excluded by !**/generated/**
  • src/server/db/generated/postgres.bootstrap.sql is excluded by !**/generated/**
  • src/server/db/generated/schemaContract.json is excluded by !**/generated/**
📒 Files selected for processing (21)
  • drizzle/0027_site_custom_headers_override_request_headers.sql
  • drizzle/meta/_journal.json
  • src/server/contracts/siteRoutePayloads.ts
  • src/server/db/index.ts
  • src/server/db/schema.ts
  • src/server/db/schemaContract.test.ts
  • src/server/db/siteSchemaCompatibility.test.ts
  • src/server/db/siteSchemaCompatibility.ts
  • src/server/routes/api/sites.proxyUrl.test.ts
  • src/server/routes/api/sites.ts
  • src/server/services/backupService.test.ts
  • src/server/services/backupService.ts
  • src/server/services/databaseMigrationService.test.ts
  • src/server/services/databaseMigrationService.ts
  • src/server/services/siteCustomHeaders.test.ts
  • src/server/services/siteCustomHeaders.ts
  • src/server/services/siteProxy.test.ts
  • src/server/services/siteProxy.ts
  • src/web/pages/Sites.tsx
  • src/web/pages/helpers/sitesEditor.test.ts
  • src/web/pages/helpers/sitesEditor.ts

@aoguai

aoguai commented Jun 14, 2026

Copy link
Copy Markdown
Author

Related issues

Why

This directly fixes the site custom header priority problem reported in #530: downstream clients such as Hermes / OpenAI Python SDK can currently send their own User-Agent, and that explicit request header wins over the site's configured User-Agent.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

bluicezhen added a commit to bluicezhen/metapi that referenced this pull request Jul 21, 2026
- Adds per-site 'customHeadersOverrideRequestHeaders' flag (default false).
- When true, site-level custom headers (User-Agent, etc.) override same-name
  downstream passthrough headers instead of being overridden by them.
- Schema: new sqlite migration 0027, mysql/postgres bootstrap updates.
- Frontend: new checkbox in site editor; non-boolean API payload returns 400.
- Backwards compatible: existing sites default to request-priority.
@aoguai
aoguai force-pushed the site-custom-header-override branch from 91f68d1 to fa35544 Compare August 8, 2026 12:46

@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: fa35544563

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/server/db/schema.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: db Database and schema related changes area: web Web UI changes size: M 200 to 499 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant