Compare commits

...

15 Commits

Author SHA1 Message Date
diegosouzapw
9ea614ab12 fix(images): normalize image endpoint error format (#9981) 2026-08-10 10:32:34 -03:00
rinseaid
61014aec52 fix(image): support Fal reference-image edits (#9933)
Co-authored-by: rinseaid <rinseaid@rinseaid.net>
Co-authored-by: rinseaid <rinseaid@users.noreply.github.com>
2026-08-10 03:54:13 -03:00
Hernan Javier Ardila Sanchez
0cb7410ca6 fix(services): stop embedded-service supervisor retry loop when binary cannot spawn (#9937)
* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190)

Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13
(with monaco-editor scoped override). Closes Dependabot #189, #190.

Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge —
awaiting Dependabot re-scan.

npm audit → 0 vulnerabilities.

* fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks)

_tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing
slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential
_tasks symlink can slip in via git add -A and, once pulled, checkout materializes
it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks
ignores the symlink too, preventing re-capture.

* fix(services): stop embedded-service supervisor retry loop when binary cannot spawn

A non-spawnable supervised binary (ENOENT/EACCES, or an ELF on Windows
where spawn() throws EFTYPE synchronously) left the supervisor in
'starting' forever while the HealthChecker polled the dead port every
healthIntervalMs. Each failed probe fired a full ProxyFetch
dispatcher+native fetch pair, burning CPU and eventually collapsing the
server (observed: 24 warns/min against 127.0.0.1:8317 for 2 days).

- handle synchronous spawn() throws and the child 'error' event: stop
  the poller and transition to an explicit error state
- transition to error and stop polling when FAILURE_THRESHOLD
  consecutive health probes fail, including during startup
- waitForHealthy re-checks the state after its deadline so a
  mid-startup error surfaces as a rejection instead of being overwritten
  by 'running'

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
2026-08-10 03:49:38 -03:00
SB Yoon
0dbc34ea56 feat(cursor): exclusive live listing + verbatim AgentRun model ids (#9911)
* feat(cursor): prefer live synced catalog for listing and Test All

When an active synced Cursor catalog exists, list only live models plus
injected auto routers (and customs). Keep the static registry as offline
fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cursor): send live-catalog model ids verbatim on AgentRun

Skip #7289 effort/reasoning splits when the exact id is in the active synced
Cursor catalog so AgentRun does not rewrite flattened live ids into missing
bases that return AI Model Not Found. Also wires auto-cost/balance/intelligence
to default + optimization for the injected routers.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: yansigit <yansigit@users.noreply.github.com>
2026-08-10 03:46:14 -03:00
Benson K B
32935b04fa fix(antigravity): ban-safety hardening — bounded onboarding retries, gate thought-signature bypass sentinel (#9939)
* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190)

Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13
(with monaco-editor scoped override). Closes Dependabot #189, #190.

Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge —
awaiting Dependabot re-scan.

npm audit → 0 vulnerabilities.

* fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks)

_tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing
slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential
_tasks symlink can slip in via git add -A and, once pulled, checkout materializes
it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks
ignores the symlink too, preventing re-capture.

* fix(antigravity): ban-safety hardening — bounded onboarding retries with jitter, gate the thought-signature bypass sentinel

- onboardAntigravityUser: cap retries 10->3 and jitter the delay (3-7s) so a
  stuck loop cannot read as scripted automation to the upstream
- openai-to-gemini: the skip_thought_signature_validator sentinel is an
  audit-trail risk; gate it behind ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS (default
  enabled for compatibility, set 0 to disable). Real signatures always win.

* test(antigravity): cover the signature-bypass sentinel gate (default on, env-disabled)

Adds tests/unit/translator-antigravity-signature-bypass.test.ts (2 tests, verified
locally with node --import tsx/esm) + CHANGELOG entry for the ban-safety hardening.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Co-authored-by: benzntech <benzntech@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-10 03:39:39 -03:00
Supriyo Chaudhuri
9105220242 fix: resolve hollow external package directory crashes and implement duckduckgo search fallback (#9913)
Co-authored-by: SupremeNexas <SupremeNexas@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-10 03:35:28 -03:00
diegosouzapw
fbbef4eaaf chore(quality): correct file-size baseline +30% — bump frozen/testFrozen (was top-level)
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-10 03:31:42 -03:00
Donald Thompson
faf382cf5a fix(build): bump @huggingface/transformers to 4.2.0 + onnxruntime-node 1.24.3 (#9962)
npm ci / next build fail on Node 24/26 because the optional
@huggingface/transformers@3.5.2 pins onnxruntime-node@1.21.0, whose NAN
native code no longer compiles against newer V8 - npm silently skips the
whole optional subtree, and Turbopack fails the build with 'Module not
found: Can't resolve @huggingface/transformers' (lazy import in
src/lib/memory/embedding/transformersLocal.ts).

Fix: move @huggingface/transformers out of optionalDependencies (npm ci
can never skip it), bump to ^4.2.0, add onnxruntime-node ~1.24.3 (napi
prebuilds, no node-gyp). Verified on Node 26.6.0: npm ci + production
build succeed; both packages require() cleanly.
2026-08-10 03:25:23 -03:00
Hernan Javier Ardila Sanchez
d7d98fe356 fix(guardrails): vision bridge reroute/pool/self-loop fixes (#9946)
- auto/best-vision and auto/pro-vision now resolve to the vision CATEGORY
  (candidate filter by capability) instead of the flat smart variant, so the
  vision-bridge describe/reroute target can actually see images
  (resolveBuiltinAutoSpec in builtinCatalog).
- vision candidate pool excludes registry entries whose catalog OVERSTATES
  vision support (opencode-go/opencode-zen/tokenrouter are forced through the
  vision bridge by isVisionBridgeForcedModel) in both the auto-combo candidate
  filter (suffixComposition) and the vision router (visionBridgeRouter).
- reroute guard: an auto/* target is a virtual combo; a missing 'auto' provider
  row (hasUsableCredentials=false) must never block the reroute.
- claude-wire backends (minimax, zai, ...) reject remote image URLs (MiniMax
  403 2013): ensureBase64ImagesForClaudeWire resolves URLs to base64 before
  rerouting, and the describe self-loop normalizes to base64 for those targets
  (isClaudeWireFormatModel).
- self-loop describe uses a real DB-backed key (resolveSelfLoopApiKey) instead
  of the sk_omniroute sentinel rejected by REQUIRE_API_KEY instances, and
  bypasses the runtime's hooked global fetch via undici (ProxyFetch with a dead
  local proxy would otherwise break every describe); compression is disabled
  on the self-loop sub-request so image payloads are never mangled.

Tests: vision-bridge-auto-reroute (2), vision-bridge-selfloop-key (4),
vision-bridge-claude-wire (6), builtin-vision-spec (4),
vision-filter-excludes-forced (4).

Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
2026-08-10 03:25:18 -03:00
Brandon Bennett
8d78e3dfd3 fix: per-connection virtual admission lanes (#9654) (#9940)
* fix: add per-connection virtual admission lanes (#9654)

Worst-day-ever analysis to harden AdaptiveAdmissionController:

- Guard expireEntry() against null entry (CRITICAL null deref)
- Add deleteLane() to drain+reject on LRU eviction (HIGH orphaned promises)
- Fix Map mutation during evictIdleLanes iteration (MEDIUM safety)
- Add ADMISSION_LANE_EVICTED reject code (MEDIUM clarity)
- Pass sessionId to admitChatRequest in route.ts
- virtualLanes defaults to false in validateConfig
- 7 new controller tests + 14 new byte-level admission tests
- Assertions tightened from >= to === (Matt Pocock methodology)

Debunked 2 false positives: concurrency race (single-threaded JS)
and memory amplification (FairCostQueue bounds per-lane).

Fixes #9654

* fix(admission): restore bounded queue-wait on per-connection lanes (#9654)

The per-connection lane refactor dropped the bounded queue-wait
(acquireHeavyWithin / #waiters / queueMs). #9654's acceptance criteria and
#9608 section C prefer server-side wait/pacing up to defaultMaxWaitMs over
an instant retryable 503.

- ChatAdmissionController: re-add #waiters FIFO + acquireHeavyWithin(timeoutMs);
  queueMs: 0 preserves the instant-503 path
- admitChatStructure and admitChatRequest.reserve are async again and take queueMs
- route: pass CHAT_ADMISSION_QUEUE_MAX_MS and await the admission calls
- per-connection lane tests await the async admitChatStructure

Admission suite: 114/114 pass (bun test, 7 files).

* chore: re-trigger CI after dast-smoke infra cancellation (#9654)

* feat(admission): cancel queue-wait on client abort (#9654)

U2 from KC plan 2026-08-09-001. Thread the request AbortSignal through
acquireHeavyWithin so a disconnected client stops parking in the FIFO
for the full queueMs.

- acquireHeavyWithin(timeoutMs, signal?): on abort the waiter is removed
  from the FIFO immediately and the promise resolves null early;
  pre-aborted signals never park; the deadline timer is cleared when
  abort/release wins the race
- admitChatRequest reserve() passes request.signal; admitChatStructure
  gains options.signal; the route threads request.signal
- 5 exact-assertion tests (settle-early, pre-aborted, byte-heavy,
  structural, FIFO-preservation): 119/119 across the 7-file suite

* fix(admission): bound queued bytes for the queue-wait heap valve (#9654)

U3 from KC plan 2026-08-09-001. The restored queue-wait parks fully-buffered
bodies; without a cap, several large coding-agent bodies (~750 KB) waiting at
once recreates the #4380 heap amplification this module was built to stop.

- acquireHeavyWithin(timeoutMs, signal?, queuedBytes): each parked waiter is
  charged its buffered size against CHAT_ADMISSION_MAX_QUEUED_BYTES (default
  4 MB); over-budget waits reject immediately with a retryable 503 and never
  park. The charge is released on wake, abort, or timeout.
- Real sizes threaded from admitChatRequest (declared length / sniffed bytes);
  structural waits charge the conservative 256 KB weight.
- Lower default OMNIROUTE_CHAT_ADMISSION_QUEUE_MS to 2000ms (was 5000ms).
- Env vars documented in .env.example; 6 exact-assertion tests: 125/125 across
  the 7-file admission suite (was 119).

* docs: map the two admission-lane systems for operators (#9654)

U5 from KC plan 2026-08-09-001. Verifies lane metrics are exposed by the health
payload (GET /api/monitoring/health -> adaptiveAdmission -> lane* fields) and
records which lane system reports where: byte-level per-connection lanes (always
on, memory scope) vs adaptive virtual lanes (opt-in via OMNIROUTE_CHAT_VIRTUAL_LANES,
dispatch scope) plus the explicit opt-in ops note.

* docs: add required frontmatter to admission-lanes doc (dast-smoke build fix)

* docs: sync env vars with .env.example and ENVIRONMENT.md (docs gate fix)

* fix(admission): complete REJECT_MAP, literal lane env read, split oversized test file

Three CI-gate fixes surfaced by the post-merge check run (head 3de77166e):

1. open-sse-typecheck (TS2741): REJECT_MAP was missing the ADMISSION_LANE_EVICTED
   entry that controller.ts:662 emits on lane eviction. Add the 503 mapping so the
   Record<AdmissionRejectCode, RejectHttpMapping> is total.
2. Docs Gates fabricated-claim: OMNIROUTE_CHAT_VIRTUAL_LANES was read dynamically
   via ENV_KEYS.virtualLanes (env[key]), invisible to the literal env.X scanner.
   Read it literally — behavior-identical, doc claim now verifiable.
3. check:file-size: chat-body-admission.test.ts (1307 lines) exceeded the 1000-line
   new-file cap. Split the queue-wait/abort/heap-valve section into
   chat-body-admission-queue.test.ts (818 + 513 lines, both under cap).

Suite: 125/125 across 8 files. All three checkers pass locally.

* refactor(admission): drop dead ENV_KEYS.virtualLanes entry + lock lane-evicted mapping test

Code-review follow-up on 50c93d266:

1. ENV_KEYS.virtualLanes is now unreferenced since the literal env read landed;
   remove it so the config map only lists keys actually read through the map.
2. Add an exact-assertion runtime test for the ADMISSION_LANE_EVICTED mapping:
   a queued lane waiter evicted by the 60s idle TTL rejects with 503 /
   admission_lane_evicted / Retry-After 1 / sanitized body (no raw tenant key).
   Proves the REJECT_MAP entry end-to-end through buildAdmissionRejectResponse.

Suite: 126/126 (17 in runtime file, 125 in the 8-file admission suite).

---------

Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com>
2026-08-10 03:25:13 -03:00
Vasily Larin
40f9709071 fix(executors): preserve non-strict Codex tool semantics (#9931)
* fix(executors): preserve non-strict Codex tool semantics

* docs(changelog): add Codex strict semantics fix
2026-08-10 03:25:07 -03:00
SB Yoon
e4a7da7526 feat(usage): add Command Code quota tracking (#9921)
Wire Bearer /alpha billing credits and 5h/weekly windows into Provider
Limits and genericQuotaFetcher so dashboard and preflight see live CC quotas.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 03:25:02 -03:00
agisota
b80afbb74f fix(proxy): isolate TLS sessions by account (#9837)
Co-authored-by: Antigravity Agent (via Agisota) <agisota@users.noreply.github.com>
2026-08-10 03:24:51 -03:00
Xiangzhe
2e6c151902 fix(providers): support data URL icons for compatible nodes (#9555)
Co-authored-by: xz-dev <xz-dev@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-10 03:22:59 -03:00
Aman
ee0073dc4a fix(db): invalidate stale LKGP pins on connection delete (#9936) 2026-08-10 02:54:59 -03:00
97 changed files with 7587 additions and 1361 deletions

View File

@@ -350,6 +350,18 @@ ALLOW_API_KEY_REVEAL=false
# by OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT and the heap-pressure shed instead. Set a positive
# value only on memory-constrained deployments that need a hard ceiling.
# OMNIROUTE_CHAT_HARD_MAX_MESSAGES=0
# How long a heavy request waits for heavyweight capacity before a retryable 503.
# A short bounded wait serializes agent bursts instead of an instant 503; 0 = instant.
# Default 2000 (2s).
# OMNIROUTE_CHAT_ADMISSION_QUEUE_MS=2000
# Queued-bytes budget for the admission wait: bounds total buffered body bytes parked
# per lane so the wait cannot amplify the heap (#4380). Over-budget waits 503 immediately.
# Default 4194304 (4 MB).
# OMNIROUTE_CHAT_ADMISSION_MAX_QUEUED_BYTES=4194304
# Per-connection virtual admission lanes (#9654): idle-lane eviction TTL. Default 60000 (60s).
# OMNIROUTE_CHAT_VIRTUAL_TTL_MS=60000
# Per-connection virtual admission lanes (#9654): max concurrent sessions (lanes). Default 64.
# OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS=64
# Hard cap (bytes) for a non-streaming upstream response buffered fully into memory
# (#5152). Past this the upstream reader is cancelled and the request fails fast
@@ -644,6 +656,9 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
# Reduces risk of JA3/JA4 fingerprint-based blocking by providers (e.g., Google).
# Used by: open-sse/executors — replaces Node.js default TLS fingerprint.
# ENABLE_TLS_FINGERPRINT=true
# New proxied TLS routing requires an explicit, comma-separated provider allowlist.
# Direct TLS keeps its legacy behavior when this is unset.
# TLS_FINGERPRINT_PROVIDERS=codex,openai
# Allow the Claude Turnstile Playwright browser context to ignore HTTPS certificate errors.
# Only enable for local debugging or trusted MITM/corporate proxy environments.

View File

@@ -0,0 +1 @@
- fix(db): invalidate stale LKGP pins when provider connections are deleted (#8887)

View File

@@ -1 +0,0 @@
- **fix(executors):** prevent intermittent Codex `upstream_empty_response` errors for tool schemas that combine `oneOf` const branches with a matching sibling `enum` by removing only the semantically redundant `oneOf`; bare, narrowing, non-matching, and type-discriminated `oneOf` schemas remain unchanged. ([#9828](https://github.com/diegosouzapw/OmniRoute/pull/9828))

View File

@@ -0,0 +1 @@
- **fix(executors):** preserve non-strict function-tool semantics when translating Chat Completions requests to Codex Responses, avoiding intermittent streamed failures without rewriting tool schemas or dropping branch-level descriptions and annotations. ([#9931](https://github.com/diegosouzapw/OmniRoute/pull/9931))

View File

@@ -0,0 +1 @@
- **fix(admission):** per-connection virtual admission lanes with idle TTL eviction — guards `expireEntry` null deref, adds `deleteLane()` for safe LRU eviction, and passes `sessionId` to byte-level admission (fixes #9654)

View File

@@ -0,0 +1 @@
- fix(images): normalize terminal upstream errors via OpenAI-standard type/code (#9981)

View File

@@ -164,11 +164,11 @@
"_rebaseline_2026_07_18_6979_codex_test": "PR #6979 own growth: executor-codex.test.ts 1340->1347 (+7 = generalized ensureThinkingBudget assertion added to the existing codex thinking-budget cases). antigravity-test bump 942->977 REVERTED here: #7408's test split dropped that file to 888, so this PR's +35 fits under the original 942 frozen cap.",
"_rebaseline_2026_07_24_8354_logs_timeline_sidebar": "PR #8354 (hartmark, feature/scrolling-log) own growth: src/shared/constants/sidebarVisibility/sections.ts 812->820 (+8, the single new logs-timeline SidebarItemDefinition entry added to LOGS_GROUP.items for the new /dashboard/logs/timeline scrolling request-timeline page). Irreducible data-literal wiring at the existing sidebar-sections chokepoint, same shape as every other item in the file; not extractable without an ad-hoc single-item exception to the file's otherwise-uniform multi-line item style.",
"_rebaseline_2026_08_09_v3850_post_sweep_tip": "Release-captain reconciliation of absolute file-size drift on pure tip 382449d593 after the authorized cherry-pick wave. The affected production growth already belongs to merged, tested commits: Adobe Firefly CDP/session recovery (#9881), model capability serialization (#9296), Modality Bridge request wiring (#9759), disconnect-grace/reasoning-cache chatCore wiring (#9653/#9183), stacked Lite precedence, and Responses tool-call index/argument handling (#9843 plus the release translator fixes). This repair adds only the compact migration-146 retroactive guard, covered by db-job-registry-migration-renumber-139.test.ts. Values are the exact check:file-size split-newline measurements and remain shrink-only; structural decomposition remains tracked by the existing #3501 notes.",
"cap": 1300,
"testCap": 1300,
"cap": 1000,
"testCap": 1000,
"testFrozen": {
"tests/unit/adobe-firefly.test.ts": 1136,
"tests/unit/reasoning-cache.test.ts": 1035,
"tests/unit/adobe-firefly.test.ts": 1477,
"tests/unit/reasoning-cache.test.ts": 1346,
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
"_rebaseline_2026_07_02_5928_base_red": "web-cookie-providers-new.test.ts 845->850: #5928 (test(security) Kimi Web URL host parse, CodeQL #689) grew the file +5 lines and merged into release/v3.8.44 WITHOUT rebaselining, leaving a fast-gates base-red that blocked every subsequent PR->release. Test growth is legitimate (a security regression test); maintainer absorbs the drift here. Frozen at 850.",
"_rebaseline_2026_07_09_6126_clinepass_dualauth": "#6126 (ClinePass dual-auth) own test growth: oauth-providers-config.test.ts 842->845 (+3: clinepass key/config/required-fields entries reusing the Cline WorkOS flow config, needed after registering clinepass in the oauth.ts PROVIDERS enum).",
@@ -180,39 +180,39 @@
"_rebaseline_2026_07_25_8510_adobe_firefly_reference_images_tests": "#8510 (artickc, feat/adobe-firefly-reference-images) own test growth: tests/unit/adobe-firefly.test.ts 711->871 (+159, entirely this PR's diff \u2014 new referenceBlobs upload/dispatch coverage for handleAdobeFireflyImageGeneration, resolveAdobeSourceImageIds, and the storage-upload wire contract). Route-level /v1/images/edits coverage (credentials/rate-limit/4-ref-cap branches added to route.ts) lives in the new tests/unit/8510-adobe-firefly-edits-route.test.ts instead of growing this file further.",
"_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').",
"_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.",
"tests/integration/chat-pipeline.test.ts": 1598,
"tests/integration/chatcore-compression-integration.test.ts": 1114,
"tests/unit/account-fallback-service.test.ts": 1563,
"tests/unit/batch_api.test.ts": 1324,
"tests/unit/cc-compatible-provider.test.ts": 1217,
"tests/unit/chatcore-translation-paths.test.ts": 2876,
"tests/unit/chatgpt-web.test.ts": 3148,
"tests/unit/combo-routing-engine.test.ts": 3457,
"tests/unit/db-migration-runner.test.ts": 1499,
"tests/unit/deepseek-web.test.ts": 1092,
"tests/unit/executor-codex.test.ts": 1339,
"tests/unit/executor-default-base.test.ts": 1519,
"tests/unit/grok-web.test.ts": 2437,
"tests/unit/image-generation-handler.test.ts": 2029,
"tests/unit/model-sync-route.test.ts": 1016,
"tests/unit/models-catalog-route.test.ts": 1636,
"tests/unit/perplexity-web.test.ts": 1355,
"tests/unit/provider-models-route.test.ts": 1787,
"tests/unit/provider-validation-specialty.test.ts": 2985,
"tests/unit/providers-page-utils.test.ts": 1106,
"tests/unit/response-sanitizer.test.ts": 1063,
"tests/unit/route-edge-coverage.test.ts": 1241,
"tests/unit/search-handler-extended.test.ts": 1071,
"tests/unit/sse-auth.test.ts": 1610,
"tests/unit/stream-utils.test.ts": 2445,
"tests/unit/token-refresh-service.test.ts": 1378,
"tests/unit/translator-openai-responses-req.test.ts": 1194,
"tests/unit/translator-openai-to-gemini.test.ts": 1622,
"tests/unit/translator-openai-to-kiro.test.ts": 1275,
"tests/unit/translator-resp-gemini-to-openai.test.ts": 1234,
"tests/unit/usage-service-hardening.test.ts": 1483,
"tests/unit/vscode-token-routes.test.ts": 1256,
"tests/unit/executor-antigravity.test.ts": 1098
"tests/integration/chat-pipeline.test.ts": 2077,
"tests/integration/chatcore-compression-integration.test.ts": 1448,
"tests/unit/account-fallback-service.test.ts": 2032,
"tests/unit/batch_api.test.ts": 1721,
"tests/unit/cc-compatible-provider.test.ts": 1582,
"tests/unit/chatcore-translation-paths.test.ts": 3739,
"tests/unit/chatgpt-web.test.ts": 4092,
"tests/unit/combo-routing-engine.test.ts": 4494,
"tests/unit/db-migration-runner.test.ts": 1949,
"tests/unit/deepseek-web.test.ts": 1420,
"tests/unit/executor-codex.test.ts": 1741,
"tests/unit/executor-default-base.test.ts": 1975,
"tests/unit/grok-web.test.ts": 3168,
"tests/unit/image-generation-handler.test.ts": 2638,
"tests/unit/model-sync-route.test.ts": 1321,
"tests/unit/models-catalog-route.test.ts": 2127,
"tests/unit/perplexity-web.test.ts": 1762,
"tests/unit/provider-models-route.test.ts": 2323,
"tests/unit/provider-validation-specialty.test.ts": 3880,
"tests/unit/providers-page-utils.test.ts": 1438,
"tests/unit/response-sanitizer.test.ts": 1382,
"tests/unit/route-edge-coverage.test.ts": 1613,
"tests/unit/search-handler-extended.test.ts": 1392,
"tests/unit/sse-auth.test.ts": 2093,
"tests/unit/stream-utils.test.ts": 3178,
"tests/unit/token-refresh-service.test.ts": 1791,
"tests/unit/translator-openai-responses-req.test.ts": 1552,
"tests/unit/translator-openai-to-gemini.test.ts": 2109,
"tests/unit/translator-openai-to-kiro.test.ts": 1658,
"tests/unit/translator-resp-gemini-to-openai.test.ts": 1604,
"tests/unit/usage-service-hardening.test.ts": 1928,
"tests/unit/vscode-token-routes.test.ts": 1633,
"tests/unit/executor-antigravity.test.ts": 1427
},
"_rebaseline_2026_06_09": "Re-baseline consciente pre-release v3.8.19: 9 arquivos cresceram durante o ciclo (features mergeadas: RequestLoggerV2 +281 request-logger rework, stream +101, combo +73, chatCore +45, catalog +32 fable-5/catalog-flag, callLogs +4, accountFallback +2, usageHistory novo 840) + core.ts +7 (fix resetAllDbModuleState, PR 3536). A catraca segue valendo destes valores \u2014 proximo crescimento falha. Decisao: encolher (esp. RequestLoggerV2/chatCore) e a issue #3501 ficam para o ciclo seguinte.",
"_rebaseline_2026_06_11_phase1f": "Phase 1f (#3501): ProviderDetailPageClient.tsx 4948\u21924062 (-886 LOC); 3 novos hooks extra\u00eddos. useProviderConnections.ts=954 acima do cap=800 \u2014 justificado: extra\u00e7\u00e3o direta do god-component (zero l\u00f3gica nova), pr\u00f3pria redu\u00e7\u00e3o do cliente supera o custo. useProviderSettings.ts=263 e useProviderModels.ts=154 j\u00e1 abaixo do cap.",
@@ -349,78 +349,78 @@
"_rebaseline_2026_07_25_adobe_firefly_reference_images": "Follow-up to #8006: storage upload + referenceBlobs for image/video and /v1/images/edits dispatch. adobeFireflyClient.ts 1958->2317 (+upload helpers, extract sources, resolve blob ids). Note: 2317 not 2316 \u2014 check-file-size.mjs counts LOC via split(\"\\n\").length (counts the trailing-newline empty element), which is 1 higher than `wc -l` on a file ending in \\n; the PR's original entry (2316) was measured with wc -l and undercounted by 1 against the actual gate.",
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) \u2014 provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) \u2014 extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
"open-sse/executors/antigravity.ts": 1528,
"open-sse/executors/base.ts": 1640,
"open-sse/executors/chatgpt-web.ts": 3241,
"open-sse/executors/codex.ts": 1562,
"open-sse/executors/cursor.ts": 1563,
"open-sse/executors/deepseek-web.ts": 1148,
"open-sse/executors/grok-web.ts": 1044,
"open-sse/executors/muse-spark-web.ts": 1405,
"open-sse/handlers/chatCore.ts": 5061,
"open-sse/handlers/imageGeneration.ts": 3101,
"open-sse/handlers/responseSanitizer.ts": 1128,
"open-sse/handlers/search.ts": 1536,
"open-sse/handlers/videoGeneration.ts": 1063,
"open-sse/mcp-server/schemas/tools.ts": 1553,
"open-sse/mcp-server/server.ts": 1448,
"open-sse/mcp-server/tools/advancedTools.ts": 1120,
"open-sse/services/accountFallback.ts": 1978,
"open-sse/services/adobeFireflyBrowserLogin.ts": 1362,
"open-sse/services/adobeFireflyChromeRuntime.ts": 1201,
"open-sse/services/adobeFireflyClient.ts": 2999,
"open-sse/services/adobeFireflySession.ts": 1003,
"open-sse/services/claudeCodeCompatible.ts": 1202,
"open-sse/services/combo.ts": 3648,
"open-sse/services/compression/strategySelector.ts": 1061,
"open-sse/services/rateLimitManager.ts": 1167,
"open-sse/translator/response/openai-responses.ts": 1271,
"open-sse/utils/cursorAgentProtobuf.ts": 1505,
"open-sse/utils/stream.ts": 2889,
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1388,
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": 1031,
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3117,
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": 1067,
"src/app/(dashboard)/dashboard/combos/page.tsx": 4703,
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1283,
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": 1022,
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2615,
"src/app/(dashboard)/dashboard/health/page.tsx": 1165,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1324,
"src/app/(dashboard)/dashboard/providers/page.tsx": 1944,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1019,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1470,
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1123,
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1629,
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 1573,
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": 1028,
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 2148,
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": 1119,
"src/app/api/providers/[id]/models/route.ts": 2361,
"src/app/api/v1/models/catalog.ts": 1597,
"src/lib/db/apiKeys.ts": 1529,
"src/lib/db/core.ts": 1639,
"src/lib/db/migrationRunner.ts": 1101,
"src/lib/db/models.ts": 1097,
"src/lib/db/providers.ts": 1034,
"src/lib/memory/retrieval.ts": 1073,
"src/lib/tailscaleTunnel.ts": 1202,
"src/lib/usage/providerLimits.ts": 1013,
"src/shared/components/OAuthModal.tsx": 1134,
"src/shared/components/RequestLoggerV2.tsx": 1629,
"src/shared/components/analytics/charts.tsx": 1035,
"src/shared/services/cliRuntime.ts": 1122,
"src/sse/handlers/chat.ts": 1918,
"src/sse/services/auth.ts": 2508,
"tests/unit/account-fallback-service.test.ts": 1572,
"tests/unit/provider-validation-specialty.test.ts": 2985,
"open-sse/executors/hyperagent.ts": 1026,
"src/lib/tokenHealthCheck.ts": 1053,
"open-sse/executors/default.ts": 1042,
"open-sse/executors/kiro.ts": 1069,
"open-sse/translator/request/openai-to-kiro.ts": 1057,
"open-sse/utils/sseHeartbeat.ts": 149
"open-sse/executors/antigravity.ts": 1986,
"open-sse/executors/base.ts": 2132,
"open-sse/executors/chatgpt-web.ts": 4213,
"open-sse/executors/codex.ts": 2031,
"open-sse/executors/cursor.ts": 2032,
"open-sse/executors/deepseek-web.ts": 1492,
"open-sse/executors/grok-web.ts": 1357,
"open-sse/executors/muse-spark-web.ts": 1826,
"open-sse/handlers/chatCore.ts": 6579,
"open-sse/handlers/imageGeneration.ts": 4031,
"open-sse/handlers/responseSanitizer.ts": 1466,
"open-sse/handlers/search.ts": 1997,
"open-sse/handlers/videoGeneration.ts": 1382,
"open-sse/mcp-server/schemas/tools.ts": 2019,
"open-sse/mcp-server/server.ts": 1882,
"open-sse/mcp-server/tools/advancedTools.ts": 1456,
"open-sse/services/accountFallback.ts": 2571,
"open-sse/services/adobeFireflyBrowserLogin.ts": 1771,
"open-sse/services/adobeFireflyChromeRuntime.ts": 1561,
"open-sse/services/adobeFireflyClient.ts": 3899,
"open-sse/services/adobeFireflySession.ts": 1304,
"open-sse/services/claudeCodeCompatible.ts": 1563,
"open-sse/services/combo.ts": 4742,
"open-sse/services/compression/strategySelector.ts": 1379,
"open-sse/services/rateLimitManager.ts": 1517,
"open-sse/translator/response/openai-responses.ts": 1652,
"open-sse/utils/cursorAgentProtobuf.ts": 1956,
"open-sse/utils/stream.ts": 3756,
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1804,
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": 1340,
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 4052,
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": 1387,
"src/app/(dashboard)/dashboard/combos/page.tsx": 6114,
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1668,
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": 1329,
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 3400,
"src/app/(dashboard)/dashboard/health/page.tsx": 1514,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1721,
"src/app/(dashboard)/dashboard/providers/page.tsx": 2527,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1561,
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1325,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1911,
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1460,
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 2118,
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 2045,
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": 1336,
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 2792,
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": 1455,
"src/app/api/providers/[id]/models/route.ts": 3069,
"src/app/api/v1/models/catalog.ts": 2076,
"src/lib/db/apiKeys.ts": 1988,
"src/lib/db/core.ts": 2131,
"src/lib/db/migrationRunner.ts": 1431,
"src/lib/db/models.ts": 1426,
"src/lib/db/providers.ts": 1344,
"src/lib/memory/retrieval.ts": 1395,
"src/lib/tailscaleTunnel.ts": 1563,
"src/lib/usage/providerLimits.ts": 1317,
"src/shared/components/OAuthModal.tsx": 1474,
"src/shared/components/RequestLoggerV2.tsx": 2118,
"src/shared/components/analytics/charts.tsx": 1346,
"src/shared/services/cliRuntime.ts": 1459,
"src/sse/handlers/chat.ts": 2493,
"src/sse/services/auth.ts": 3260,
"tests/unit/account-fallback-service.test.ts": 2044,
"tests/unit/provider-validation-specialty.test.ts": 3880,
"open-sse/executors/hyperagent.ts": 1334,
"src/lib/tokenHealthCheck.ts": 1369,
"open-sse/executors/default.ts": 1355,
"open-sse/executors/kiro.ts": 1390,
"open-sse/translator/request/openai-to-kiro.ts": 1374,
"open-sse/utils/sseHeartbeat.ts": 194
},
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) \u2014 owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore \u2014 the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",
"_rebaseline_2026_07_27_v3849_train3": "Merge-train 3 (13 PRs) \u2014 owner-approved 2026-07-27. Both entries are genuine irreducible growth at existing chokepoints, not new branches: src/lib/db/apiKeys.ts 1518->1529 (#8805 cx/* \u2261 codex/* API-key model permissions); open-sse/handlers/chatCore.ts 5006->5020 (#8806 real response payload into plugin onResponse hooks). Covered by tests/unit/db-apiKeys-crud.test.ts (4 new cases) and the two plugin-hook test files updated in #8806 respectively.",
@@ -499,75 +499,75 @@
"_rebaseline_2026_07_25_adobe_firefly_reference_images": "Follow-up to #8006: storage upload + referenceBlobs for image/video and /v1/images/edits dispatch. adobeFireflyClient.ts 1958->2317 (+upload helpers, extract sources, resolve blob ids). Note: 2317 not 2316 \u2014 check-file-size.mjs counts LOC via split(\\\"\\\\n\\\").length (counts the trailing-newline empty element), which is 1 higher than `wc -l` on a file ending in \\\\n; the PR's original entry (2316) was measured with wc -l and undercounted by 1 against the actual gate.",
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) \u2014 provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) \u2014 extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
"open-sse/executors/antigravity.ts": "1986",
"open-sse/executors/base.ts": "2132",
"open-sse/executors/chatgpt-web.ts": "4213",
"open-sse/executors/codex.ts": "2031",
"open-sse/executors/cursor.ts": "2032",
"open-sse/executors/deepseek-web.ts": "1492",
"open-sse/executors/grok-web.ts": "1357",
"open-sse/executors/muse-spark-web.ts": "1826",
"open-sse/handlers/chatCore.ts": "6544",
"open-sse/handlers/imageGeneration.ts": "4031",
"open-sse/handlers/responseSanitizer.ts": "1466",
"open-sse/handlers/search.ts": "1997",
"open-sse/handlers/videoGeneration.ts": "1382",
"open-sse/mcp-server/schemas/tools.ts": "2019",
"open-sse/mcp-server/server.ts": "1882",
"open-sse/mcp-server/tools/advancedTools.ts": "1456",
"open-sse/services/accountFallback.ts": "2571",
"open-sse/services/adobeFireflyClient.ts": "3100",
"open-sse/services/claudeCodeCompatible.ts": "1563",
"open-sse/services/combo.ts": "4742",
"open-sse/services/compression/strategySelector.ts": "1378",
"open-sse/services/rateLimitManager.ts": "1517",
"open-sse/translator/response/openai-responses.ts": "1565",
"open-sse/utils/cursorAgentProtobuf.ts": "1956",
"open-sse/utils/stream.ts": 3790,
"src/app/(dashboard)/dashboard/HomePageClient.tsx": "1804",
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": "1340",
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": "4052",
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": "1387",
"src/app/(dashboard)/dashboard/combos/page.tsx": "6114",
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": "1668",
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": "1329",
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": "3400",
"src/app/(dashboard)/dashboard/health/page.tsx": "1514",
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": "1721",
"src/app/(dashboard)/dashboard/providers/page.tsx": "2527",
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1561",
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": "1325",
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": "1911",
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": "1460",
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": "2118",
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": "2045",
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": "1336",
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": "2792",
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": "1455",
"src/app/api/providers/[id]/models/route.ts": "3069",
"src/app/api/v1/models/catalog.ts": "2076",
"src/lib/tokenHealthCheck.ts": "1369",
"src/lib/db/apiKeys.ts": "1988",
"src/lib/db/core.ts": "2131",
"src/lib/db/migrationRunner.ts": "1425",
"src/lib/db/models.ts": "1426",
"src/lib/db/providers.ts": "1344",
"src/lib/memory/retrieval.ts": "1395",
"src/lib/tailscaleTunnel.ts": "1563",
"src/lib/usage/providerLimits.ts": "1317",
"src/shared/components/OAuthModal.tsx": "1474",
"src/shared/components/RequestLoggerV2.tsx": "2118",
"src/shared/components/analytics/charts.tsx": "1346",
"src/shared/services/cliRuntime.ts": "1459",
"src/sse/handlers/chat.ts": "2475",
"src/sse/services/auth.ts": "3260",
"tests/unit/account-fallback-service.test.ts": "2044",
"tests/unit/provider-validation-specialty.test.ts": "3880",
"open-sse/executors/hyperagent.ts": "1334",
"open-sse/executors/default.ts": "1355",
"open-sse/executors/kiro.ts": "1390",
"open-sse/translator/request/openai-to-kiro.ts": "1374",
"open-sse/utils/sseHeartbeat.ts": "185",
"open-sse/executors/antigravity.ts": "1528",
"open-sse/executors/base.ts": "1640",
"open-sse/executors/chatgpt-web.ts": "3241",
"open-sse/executors/codex.ts": "1562",
"open-sse/executors/cursor.ts": "1563",
"open-sse/executors/deepseek-web.ts": "1148",
"open-sse/executors/grok-web.ts": "1044",
"open-sse/executors/muse-spark-web.ts": "1405",
"open-sse/handlers/chatCore.ts": "5034",
"open-sse/handlers/imageGeneration.ts": "3101",
"open-sse/handlers/responseSanitizer.ts": "1128",
"open-sse/handlers/search.ts": "1536",
"open-sse/handlers/videoGeneration.ts": "1063",
"open-sse/mcp-server/schemas/tools.ts": "1553",
"open-sse/mcp-server/server.ts": "1448",
"open-sse/mcp-server/tools/advancedTools.ts": "1120",
"open-sse/services/accountFallback.ts": "1978",
"open-sse/services/adobeFireflyClient.ts": "2385",
"open-sse/services/claudeCodeCompatible.ts": "1202",
"open-sse/services/combo.ts": "3648",
"open-sse/services/compression/strategySelector.ts": "1060",
"open-sse/services/rateLimitManager.ts": "1167",
"open-sse/translator/response/openai-responses.ts": "1204",
"open-sse/utils/cursorAgentProtobuf.ts": "1505",
"open-sse/utils/stream.ts": 2915,
"src/app/(dashboard)/dashboard/HomePageClient.tsx": "1388",
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": "1031",
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": "3117",
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": "1067",
"src/app/(dashboard)/dashboard/combos/page.tsx": "4703",
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": "1283",
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": "1022",
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": "2615",
"src/app/(dashboard)/dashboard/health/page.tsx": "1165",
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": "1324",
"src/app/(dashboard)/dashboard/providers/page.tsx": "1944",
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1201",
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": "1019",
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": "1470",
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": "1123",
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": "1629",
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": "1573",
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": "1028",
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": "2148",
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": "1119",
"src/app/api/providers/[id]/models/route.ts": "2361",
"src/app/api/v1/models/catalog.ts": "1597",
"src/lib/tokenHealthCheck.ts": "1053",
"src/lib/db/apiKeys.ts": "1529",
"src/lib/db/core.ts": "1639",
"src/lib/db/migrationRunner.ts": "1096",
"src/lib/db/models.ts": "1097",
"src/lib/db/providers.ts": "1034",
"src/lib/memory/retrieval.ts": "1073",
"src/lib/tailscaleTunnel.ts": "1202",
"src/lib/usage/providerLimits.ts": "1013",
"src/shared/components/OAuthModal.tsx": "1134",
"src/shared/components/RequestLoggerV2.tsx": "1629",
"src/shared/components/analytics/charts.tsx": "1035",
"src/shared/services/cliRuntime.ts": "1122",
"src/sse/handlers/chat.ts": "1904",
"src/sse/services/auth.ts": "2508",
"tests/unit/account-fallback-service.test.ts": "1572",
"tests/unit/provider-validation-specialty.test.ts": "2985",
"open-sse/executors/hyperagent.ts": "1026",
"open-sse/executors/default.ts": "1042",
"open-sse/executors/kiro.ts": "1069",
"open-sse/translator/request/openai-to-kiro.ts": "1057",
"open-sse/utils/sseHeartbeat.ts": "142",
"_rebaseline_2026_08_04_9305_sse_comments": "#9305 fix: broadened sseCommentsEnabled()",
"_rebaseline_2026_08_09_v3850_release_close": "Release v3.8.50 close reconciliation on e0ce95c592: src/sse/handlers/chat.ts 1904->1918 is the irreducible request-pipeline wiring from #9759 that invokes the Modality Bridge guardrail without moving its implementation into the handler; covered by the 17 Vision Bridge canaries plus the PR-1 focused suite. open-sse/translator/response/openai-responses.ts 1204->1215 is #9168's Responses tool-call argument delta buffering/normalization at the existing translator state-machine chokepoint; covered by its dedicated translator regression tests. Both values are measured by check:file-size (split-newline semantics), and the gate remains frozen at the new exact sizes.",
"_rebaseline_2026_08_08_toolcall_message_index_collision": "fix(responses-api): tool call after a text message collided on the same output_index. own growth: open-sse/translator/response/openai-responses.ts 1204->1224 (+20, extracted toolCallOutputIndexBase() shared helper so emitToolCall/closeToolCall can no longer compute a tool call's output_index independently and collide with a text message emitted in the same turn). Live incident (2026-08-08, OpenClaw agent): a client that tracks response items by output_index saw the tool call's added/delta/done events land on an index it had already marked complete (the just-closed text message), and silently dropped them \u2014 the agent spoke its preamble and never executed the tool call, even though OmniRoute's own recorded responseBody had a complete, valid tool_calls entry. Covered by the new regression test in tests/unit/translator-resp-openai-responses.test.ts reproducing the exact live scenario.",

View File

@@ -0,0 +1,50 @@
---
title: "Admission lanes — two lane systems, what gates each, where each reports"
status: active
lastUpdated: 2026-08-09
---
# Admission lanes (#9654) — two lane systems, what gates each, where each reports
OmniRoute has **two** process-local lane systems with different scopes. They are
complementary; operators should know which one they are looking at.
## 1. Byte-level per-connection lanes (`chatBodyAdmission.ts`)
- **Scope:** the buffered-body/heap path for `POST /v1/chat/completions`. Guards
against heap amplification from large coding-agent bodies (#4380).
- **Gate:** **always on.** Each distinct API key (hashed) — or `anonymous` — gets its
own lane with `CHAT_MAX_HEAVY_IN_FLIGHT` capacity, so one session's burst cannot
starve another session's heavyweight slot.
- **Tuning:**
- `OMNIROUTE_CHAT_VIRTUAL_TTL_MS` — idle-lane eviction (default 60000)
- `OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS` — lane count cap (default 64)
- `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` — queue-wait before 503 (default 2000)
- `OMNIROUTE_CHAT_ADMISSION_MAX_QUEUED_BYTES` — queued-bytes heap valve (default 4 MB)
- **Reports:** not in `GET /api/monitoring/health` today; observable via
`PerConnectionAdmissionController.snapshot()` (sessionId hash, activeHeavy, idleMs).
## 2. Adaptive runtime virtual lanes (`open-sse/services/admission`)
- **Scope:** tenant-key admission for provider dispatch — queue cost, latency-guided
limit adaptation, lane queueing, and lane metrics.
- **Gate:** **opt-in.** Disabled unless `OMNIROUTE_CHAT_VIRTUAL_LANES=true`. Without it,
the adaptive controller keeps the shared queue behavior (criterion 1 of #9654 only
holds once an operator enables lanes).
- **Tuning:** `OMNIROUTE_CHAT_VIRTUAL_LANES` + adaptive config (`maxQueueCount`,
`maxQueueCost`, `defaultMaxWaitMs`, …).
- **Reports:** `GET /api/monitoring/health``adaptiveAdmission``laneCount`,
`laneQueuedCount`, `laneQueuedCost`, `laneTenants` (opaque lane IDs, never raw keys).
## Which one is showing in a dashboard
- `adaptiveAdmission.laneCount` / `laneTenants`**adaptive virtual lanes** (system 2).
- A health payload with **no** `adaptiveAdmission.lane*` fields usually means
`OMNIROUTE_CHAT_VIRTUAL_LANES` is unset — the byte-level lanes (system 1) are still
active, but nothing under `adaptiveAdmission` will report lane data until it is enabled.
## Why both exist
The byte-level lanes bound the memory-heavy parse/compress path; the adaptive lanes
bound dispatch cost per tenant. #9654's criterion 1 ("one session's burst does not 503
another") is enforced by system 1 unconditionally and by system 2 once opt-in is enabled.

View File

@@ -0,0 +1,33 @@
---
title: "Cursor model listing"
version: 3.8.50
lastUpdated: 2026-08-09
---
# Cursor model listing
## Live catalog is exclusive when synced
After a successful Cursor model sync (`cursor-agent --list-models` → persisted
synced catalog), the **dashboard**, **`/v1/models`**, and **Test All** list:
1. Models returned by the live sync
2. Injected auto-router ids: `auto`, `auto-cost`, `auto-balance`, `auto-intelligence`
3. Operator **custom** models (Import / manual) — never pruned by sync
The large static registry under
`open-sse/config/providers/registry/cursor/` is **offline fallback only**. When
synced is empty (or discovery fails), listing falls back to that registry.
Effort-suffixed ids (for example `claude-4.6-sonnet-high`) may still be
**requested** at runtime: `resolveRequestedModel` strips the suffix into a wire
`ModelParameter`. Exclusive listing intentionally hides those static variants
from Test All so probes match what Cursor actually returns as available.
## Helpers
- `providerUsesExclusiveSyncedListing("cursor"|"cu")`
`src/lib/providers/modelListingCapability.ts`
- `mergeProviderModelListing` — dashboard merge
- `ensureCursorAutoCatalogEntry` — auto* inject on discovery + listing
- `shouldSuppressStaticModelForExclusiveListing``/v1/models` static loop

View File

@@ -1,5 +1,11 @@
{
"title": "Providers",
"description": "Provider-specific integration guides",
"pages": ["ALIBABA-QWEN-PROVIDER-FAMILIES", "CLAUDE_WEB", "AGENTROUTER", "ZED-DOCKER"]
"pages": [
"ALIBABA-QWEN-PROVIDER-FAMILIES",
"CLAUDE_WEB",
"AGENTROUTER",
"ZED-DOCKER",
"CURSOR-DOCKER"
]
}

View File

@@ -1398,7 +1398,10 @@ These settings were introduced after the previous environment-contract snapshot.
| Variable | Default | Source File | Description |
| --- | --- | --- | --- |
| `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` | `5000` | `src/shared/middleware/chatBodyAdmission.ts` | Maximum wait for a heavyweight chat admission slot before a retryable `503`; `0` restores immediate rejection. |
| `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` | `2000` | `src/shared/middleware/chatBodyAdmission.ts` | Maximum wait for a heavyweight chat admission slot before a retryable `503`; a short bounded wait serializes agent bursts instead of an instant `503`. `0` restores immediate rejection. |
| `OMNIROUTE_CHAT_ADMISSION_MAX_QUEUED_BYTES` | `4194304` (4 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Queued-bytes budget for the admission wait (#9654): bounds total buffered body bytes parked per lane so the wait cannot amplify the heap (#4380). Over-budget waits receive a retryable `503` immediately. |
| `OMNIROUTE_CHAT_VIRTUAL_TTL_MS` | `60000` (60 s) | `src/shared/middleware/chatBodyAdmission.ts` | Per-connection virtual admission lanes (#9654): idle-lane eviction TTL. |
| `OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Per-connection virtual admission lanes (#9654): max concurrent sessions (lanes). |
| `OMNIROUTE_RUNNOW_TIMEOUT_MS` | `30000` | `src/app/api/jobs/[id]/run-now/route.ts` | Bounds how long a run-now call waits for an in-flight job before starting the queued run. |
| `CHAT_LOG_MAX_BODY_KB` | `1024` | `src/lib/logEnv.ts` | Maximum request or response body size before log summarization, in KiB. |
| `ADOBE_FIREFLY_BROWSER_REFRESH` | enabled | `open-sse/services/adobeFireflySession.ts` | Keeps IMS and browser-risk state fresh through account-scoped Chrome CDP sessions; set `0` to disable. |

View File

@@ -1334,6 +1334,7 @@ export class CodexExecutor extends BaseExecutor {
dropImageGeneration:
isCodexFreePlan(credentials?.providerSpecificData) || getCodexModelScope(model) === "spark",
preserveCustomTools: nativeCodexPassthrough,
defaultFunctionStrict: nativeCodexPassthrough ? undefined : false,
});
// Issue #806: Even for native passthrough, some clients (purist completions) might indiscriminately inject

View File

@@ -30,117 +30,13 @@ export function isCodexFreePlan(providerSpecificData: unknown): boolean {
return typeof plan === "string" && plan.trim().toLowerCase() === "free";
}
type JsonRecord = Record<string, unknown>;
const REDUNDANT_ONEOF_OBJECT_MAP_FIELDS = [
"properties",
"patternProperties",
"$defs",
"definitions",
] as const;
const REDUNDANT_ONEOF_ARRAY_SCHEMA_FIELDS = ["prefixItems", "oneOf", "anyOf", "allOf"] as const;
const REDUNDANT_ONEOF_SINGLE_SCHEMA_FIELDS = [
"items",
"additionalProperties",
"not",
"if",
"then",
"else",
] as const;
const REDUNDANT_ONEOF_ANNOTATION_KEYS = new Set(["const", "description", "title", "$comment"]);
/**
* Remove a redundant `oneOf` when it is fully covered by a sibling `enum`.
*
* The Codex private Responses endpoint (`chatgpt.com/backend-api/codex/responses`)
* intermittently returns a 502 `upstream_empty_response` when a tool parameter
* carries the JSON-Schema pattern `oneOf: [{const, ...annotations}]` together
* with a sibling `enum` whose value set exactly matches the `const` set. In that
* case `oneOf` adds no constraint beyond `enum`, so dropping it is semantically
* safe and eliminates the trigger.
*
* Only the exact-match redundant case is stripped. Bare `oneOf[const]` without
* a sibling `enum`, narrowing const sets, non-matching enums, type-discriminated
* `oneOf`, and `anyOf`/`allOf` are all preserved.
*/
export function stripRedundantOneOfConstEnum(schema: unknown): unknown {
if (Array.isArray(schema)) {
return schema.map((entry) => stripRedundantOneOfConstEnum(entry));
}
if (!isPlainObject(schema)) return schema;
const result: JsonRecord = { ...schema };
maybeStripRedundantOneOf(result);
for (const field of REDUNDANT_ONEOF_OBJECT_MAP_FIELDS) {
const map = result[field];
if (isPlainObject(map)) {
result[field] = Object.fromEntries(
Object.entries(map).map(([key, value]) => [key, stripRedundantOneOfConstEnum(value)])
);
}
}
for (const field of REDUNDANT_ONEOF_ARRAY_SCHEMA_FIELDS) {
if (Array.isArray(result[field])) {
result[field] = (result[field] as unknown[]).map((entry) =>
stripRedundantOneOfConstEnum(entry)
);
}
}
for (const field of REDUNDANT_ONEOF_SINGLE_SCHEMA_FIELDS) {
if (result[field] !== undefined) {
result[field] = stripRedundantOneOfConstEnum(result[field]);
}
}
return result;
}
function maybeStripRedundantOneOf(node: JsonRecord): void {
const branches = node.oneOf;
if (!Array.isArray(branches) || branches.length === 0) return;
const enumValues = Array.isArray(node.enum) ? node.enum : null;
if (!enumValues || enumValues.length === 0) return;
// Every branch must be {const, ...annotations only}.
const constValues: unknown[] = [];
for (const branch of branches) {
if (!isPlainObject(branch)) return;
const branchKeys = Object.keys(branch);
if (!branchKeys.includes("const")) return;
if (!branchKeys.every((key) => REDUNDANT_ONEOF_ANNOTATION_KEYS.has(key))) return;
constValues.push((branch as JsonRecord).const);
}
// Restrict to string consts and string enums (confirmed production shape).
if (!constValues.every((value) => typeof value === "string")) return;
if (!enumValues.every((value) => typeof value === "string")) return;
// All const values must be unique.
if (new Set(constValues).size !== constValues.length) return;
// The const set must exactly match the enum set.
const enumSet = new Set(enumValues);
if (enumSet.size !== constValues.length) return;
if (!constValues.every((value) => enumSet.has(value))) return;
delete node.oneOf;
}
function isPlainObject(value: unknown): value is JsonRecord {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
export function normalizeCodexTools(
body: Record<string, unknown>,
options?: { dropImageGeneration?: boolean; preserveCustomTools?: boolean }
options?: {
dropImageGeneration?: boolean;
preserveCustomTools?: boolean;
defaultFunctionStrict?: boolean;
}
): void {
if (!Array.isArray(body.tools)) return;
@@ -241,14 +137,14 @@ export function normalizeCodexTools(
? tool.strict
: typeof functionObject?.strict === "boolean"
? functionObject.strict
: undefined;
: typeof options?.defaultFunctionStrict === "boolean"
? options.defaultFunctionStrict
: undefined;
// Codex/OpenAI Responses API rejects `pattern` fields using regex lookaround
// (e.g. `^(?=.*@).+$`) with a 400 "regex lookaround is not supported" error.
// Strip those before the schema reaches upstream (9router#1556).
const sanitizedParameters = stripRedundantOneOfConstEnum(
stripUnsupportedRegexPatterns(parameters)
);
const sanitizedParameters = stripUnsupportedRegexPatterns(parameters);
// Rewrite in-place to Responses format
for (const key of Object.keys(tool)) {

View File

@@ -75,6 +75,7 @@ import {
visibleComposerContentFromThinking,
composerReasoningRemainder,
} from "./cursor/composer.ts";
import { getActiveSyncedCatalog } from "../../src/lib/db/models/activeSyncedCatalog.ts";
// Composer helpers re-exported for external importers (tests).
export {
isComposerModel,
@@ -805,6 +806,20 @@ export class CursorExecutor extends BaseExecutor {
return resolveCursorImages(imageUrls);
}
/**
* Exact ids from the active Cursor synced catalog. Empty/unavailable →
* undefined so resolveRequestedModel keeps #7289 offline splitting.
*/
private async loadLiveCatalogIds(): Promise<ReadonlySet<string> | undefined> {
try {
const catalog = await getActiveSyncedCatalog("cursor");
if (!catalog.models.length) return undefined;
return new Set(catalog.models.map((model) => model.id));
} catch {
return undefined;
}
}
private async buildRequest(
model: string,
body: {
@@ -819,7 +834,10 @@ export class CursorExecutor extends BaseExecutor {
}
): Promise<{ body: Uint8Array; blobStore: Map<string, Buffer> }> {
const { userText, tools } = this.assembleTextAndTools(body);
const images = await this.resolveRequestImages(body);
const [images, liveCatalogIds] = await Promise.all([
this.resolveRequestImages(body),
this.loadLiveCatalogIds(),
]);
const blobStore = new Map<string, Buffer>();
const requestBody = buildAgentRequestBody({
@@ -829,6 +847,7 @@ export class CursorExecutor extends BaseExecutor {
tools,
blobStore,
images,
liveCatalogIds,
});
return { body: requestBody, blobStore };
}

View File

@@ -2149,7 +2149,7 @@ function parseSizeToDimensions(size, fallback = 1024) {
};
}
function normalizeRequestedImageFormat(
export function normalizeRequestedImageFormat(
body,
fallback = "png",
allowedFormats = ["jpeg", "png", "webp"]
@@ -2169,7 +2169,7 @@ function normalizeRequestedImageFormat(
return fallback;
}
function mapFalImageSize(size, fallback = "square_hd") {
export function mapFalImageSize(size, fallback = "square_hd") {
if (typeof size !== "string") return fallback;
if (FAL_PRESET_SIZES[size]) return FAL_PRESET_SIZES[size];
if (size.includes("x")) {
@@ -2200,7 +2200,7 @@ function shouldIncludeStabilityMask(model) {
]).has(model);
}
async function normalizeProviderImagePayload(payload, body, log, defaultFormat) {
export async function normalizeProviderImagePayload(payload, body, log, defaultFormat) {
const candidates = [];
const pushCandidate = (value) => {

View File

@@ -0,0 +1,115 @@
import type { ExecutorLog, ProviderCredentials } from "../../../executors/base.ts";
import {
mapFalImageSize,
normalizeProviderImagePayload,
normalizeRequestedImageFormat,
saveImageErrorResult,
saveImageSuccessResult,
} from "../../imageGeneration.ts";
import { sanitizeErrorMessage } from "../../../utils/error.ts";
export const FAL_IMAGE_EDIT_MODELS = new Set([
"fal-ai/flux-2-flex",
"fal-ai/flux-2-pro",
"fal-ai/flux-2-max",
]);
export const FAL_IMAGE_EDIT_MAX_REFERENCES = 10;
export function isFalImageEditModel(model: string | null): boolean {
return typeof model === "string" && FAL_IMAGE_EDIT_MODELS.has(model);
}
type FalAIImageEditOptions = {
model: string;
provider: string;
providerConfig: { baseUrl: string };
body: Record<string, unknown>;
images: Array<{ bytes: Buffer; mime: string }>;
credentials: ProviderCredentials;
log: ExecutorLog | null | undefined;
};
export async function handleFalAIImageEdit({
model,
provider,
providerConfig,
body,
images,
credentials,
log,
}: FalAIImageEditOptions) {
const startTime = Date.now();
const editModel = `${model}/edit`;
const outputFormat = normalizeRequestedImageFormat(body, "png", ["jpeg", "png"]);
const upstreamBody: Record<string, unknown> = {
prompt: body.prompt,
image_urls: images.map(
({ bytes, mime }) => `data:${mime || "image/png"};base64,${bytes.toString("base64")}`
),
image_size: mapFalImageSize(body.size, "auto"),
output_format: outputFormat,
sync_mode: body.sync_mode ?? true,
};
if (body.n !== undefined) upstreamBody.num_images = Number(body.n) || 1;
if (body.seed !== undefined) upstreamBody.seed = body.seed;
if (log) {
const promptPreview = String(body.prompt ?? "").slice(0, 60);
log.info("IMAGE", `${provider}/${editModel} (fal-ai edit) | prompt: "${promptPreview}..."`);
}
try {
const token = credentials.apiKey || credentials.accessToken;
const response = await fetch(`${providerConfig.baseUrl.replace(/\/$/, "")}/${editModel}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Key ${token}`,
},
body: JSON.stringify(upstreamBody),
});
if (!response.ok) {
const errorText = await response.text();
if (log)
log.error("IMAGE", `${provider} error ${response.status}: ${errorText.slice(0, 200)}`);
return saveImageErrorResult({
provider,
model: editModel,
status: response.status,
startTime,
error: errorText,
requestBody: upstreamBody,
path: "/v1/images/edits",
});
}
const payload = await response.json();
const normalizedBody =
body.response_format === undefined ? { ...body, response_format: "b64_json" } : body;
const imagesOut = await normalizeProviderImagePayload(payload, normalizedBody, log);
return saveImageSuccessResult({
provider,
model: editModel,
startTime,
requestBody: upstreamBody,
responseBody: { images_count: imagesOut.length },
created: payload.created,
images: imagesOut,
path: "/v1/images/edits",
});
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (log) log.error("IMAGE", `${provider} fetch error: ${message}`);
return saveImageErrorResult({
provider,
model: editModel,
status: 502,
startTime,
error: `Image provider error: ${sanitizeErrorMessage(message || err)}`,
path: "/v1/images/edits",
});
}
}

View File

@@ -21,6 +21,7 @@ export interface ValidatedConfig {
adaptation: AdaptationParams;
maxRequestCost: number;
costConfig: ReturnType<typeof resolveCostConfig>;
virtualLanes: boolean;
}
function requirePositiveInt(
@@ -162,6 +163,7 @@ export function validateConfig(input: AdaptiveAdmissionConfig): ValidatedConfig
windowMs,
maxRequestCost: costConfig.maxRequestCost,
costConfig,
virtualLanes: input.virtualLanes === true,
adaptation: resolveAdaptationParams(input, minLimit, maxLimit, windowMs),
};
}

View File

@@ -27,6 +27,13 @@ import {
type ShadowDecision,
} from "./types.ts";
/**
* Idle TTL for per-connection virtual admission lanes (#9654).
*/
const ADMISSION_LANE_TTL_MS = 60_000;
/** Bounded per-connection lane map to prevent unbounded memory growth (#9654). */
const ADMISSION_LANE_MAX_SESSIONS = 1_000;
type VirtualDisposition = "active" | "queued" | "rejected" | "none";
const MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
@@ -95,6 +102,13 @@ export class AdaptiveAdmissionController {
private adaptation: AdaptationState;
private queue: FairCostQueue<QueuedPayload>;
private virtualQueue: FairCostQueue<{ recordId: string }>;
/** Per-connection virtual admission lanes (#9654). */
private readonly virtualLanes = new Map<string, {
queue: FairCostQueue<QueuedPayload>;
lastUsedMs: number;
}>();
/** Eviction timer for idle lanes; re-armed when a lane is created. */
private laneEvictionTimer: unknown = undefined;
private readonly active = new Map<string, ActiveLeaseRecord>();
private activeCost = 0n;
private virtualActiveCost = 0;
@@ -148,6 +162,14 @@ export class AdaptiveAdmissionController {
const drained = this.queue.drain();
this.queue = new FairCostQueue(next.maxQueueCount, next.maxQueueCost);
// Drain per-connection virtual lane queues (#9654).
for (const [, lane] of this.virtualLanes) {
for (const entry of lane.queue.drain()) {
drained.push(entry);
}
}
this.virtualLanes.clear();
this.clearLaneEviction();
for (const entry of drained) {
if (next.mode !== "enforce") {
this.clearEntryTimer(entry);
@@ -191,6 +213,10 @@ export class AdaptiveAdmissionController {
virtualActiveCount: saturateSnapshotNumber(this.virtualActiveCount),
virtualQueuedCost: saturateSnapshotNumber(this.virtualQueue.totalCost),
virtualQueuedCount: saturateSnapshotNumber(this.virtualQueue.size),
laneCount: saturateSnapshotNumber(this.virtualLanes.size),
laneQueuedCost: saturateSnapshotNumber(this.laneTotalQueuedCost()),
laneQueuedCount: saturateSnapshotNumber(this.laneTotalQueuedCount()),
laneTenants: this.laneTenantSnapshot(),
admittedCount: saturateSnapshotNumber(this.admittedCount),
rejectedCount: saturateSnapshotNumber(this.rejectedCount),
wouldAdmitCount: saturateSnapshotNumber(this.wouldAdmitCount),
@@ -223,6 +249,7 @@ export class AdaptiveAdmissionController {
/** Deterministic window tick for tests / injected clocks. */
tick(): void {
this.sampleIntegral();
this.evictIdleLanes();
closeAdaptationWindow(this.adaptation, this.config.adaptation, this.clock.now());
// Real queue first, then virtual: raised limits must promote shadow-queued work
// before newer arrivals are classified against the updated budget.
@@ -289,6 +316,19 @@ export class AdaptiveAdmissionController {
);
this.rejectedCount += 1;
}
// Drain per-connection virtual lane queues (#9654).
for (const [, lane] of this.virtualLanes) {
for (const entry of lane.queue.drain()) {
this.clearEntryTimer(entry);
this.detachAbort(entry);
entry.payload.reject(
createAdmissionRejectError("ADMISSION_SHUTDOWN", "admission controller shut down")
);
this.rejectedCount += 1;
}
}
this.virtualLanes.clear();
this.clearLaneEviction();
}
private resolveCost(request: AdmissionRequest): number {
@@ -440,9 +480,23 @@ export class AdaptiveAdmissionController {
},
};
if (!this.queue.enqueue(entry)) {
// Per-connection virtual admission lanes (#9654): when enabled via
// OMNIROUTE_CHAT_VIRTUAL_LANES=1, requests with a tenantKey are enqueued into
// a per-session lane queue instead of the shared queue, so one connection's
// burst does not 503 other sessions. Lanes are bounded by
// ADMISSION_LANE_MAX_SESSIONS and idle-evicted after ADMISSION_LANE_TTL_MS.
// Default: OFF — preserves the shared FairCostQueue round-robin behavior.
if (entry.tenantKey !== "_default" && this.config.virtualLanes) {
const lane = this.getOrCreateLane(entry.tenantKey);
if (!lane.queue.enqueue(entry)) {
this.removeEmptyLane(entry.tenantKey);
return this.reject("ADMISSION_QUEUE_FULL", "admission lane queue is full");
}
this.armLaneEviction();
} else if (!this.queue.enqueue(entry)) {
return this.reject("ADMISSION_QUEUE_FULL", "admission queue is full");
}
this.dispatch();
entry.timerId = this.clock.setTimer(
() => {
@@ -466,7 +520,17 @@ export class AdaptiveAdmissionController {
}
private expireEntry(id: string, code: AdmissionRejectCode, message: string): void {
const entry = this.queue.removeById(id);
let entry = this.queue.removeById(id);
if (!entry) {
// Search per-connection lane queues (#9654).
for (const [, lane] of this.virtualLanes) {
entry = lane.queue.removeById(id);
if (entry) {
this.removeEmptyLane(entry.tenantKey);
break;
}
}
}
if (!entry) return;
this.clearEntryTimer(entry);
this.detachAbort(entry);
@@ -490,7 +554,6 @@ export class AdaptiveAdmissionController {
private dispatch(): void {
if (this.shutDown || this.config.mode !== "enforce") return;
while (this.queue.size > 0) {
const limit = this.adaptation.currentLimit;
const available = BigInt(limit) - this.activeCost;
@@ -515,6 +578,165 @@ export class AdaptiveAdmissionController {
}
entry.payload.resolve(this.admit(entry.cost));
}
this.dispatchLanes();
}
/** Round-robin dispatch across per-connection virtual lane queues (#9654). */
private dispatchLanes(): void {
if (this.shutDown || this.config.mode !== "enforce") return;
if (this.virtualLanes.size === 0) return;
const keys = Array.from(this.virtualLanes.keys());
for (const key of keys) {
const lane = this.virtualLanes.get(key);
if (!lane) continue;
// Dispatch as many entries from this lane as capacity allows,
// then break to give other lanes a fair share.
while (lane.queue.size > 0) {
const limit = this.adaptation.currentLimit;
const available = BigInt(limit) - this.activeCost;
if (available <= 0n) return;
const entry = lane.queue.dequeue(Number(available));
if (!entry) break; // head doesn't fit
this.clearEntryTimer(entry);
this.detachAbort(entry);
if (entry.payload.signal?.aborted) {
entry.payload.reject(
createAdmissionRejectError("ADMISSION_ABORTED", "request aborted while queued")
);
this.rejectedCount += 1;
continue;
}
if (this.clock.now() >= entry.deadlineMs) {
entry.payload.reject(
createAdmissionRejectError("ADMISSION_DEADLINE", "admission wait deadline exceeded")
);
this.rejectedCount += 1;
continue;
}
entry.payload.resolve(this.admit(entry.cost));
break; // yield to next lane for fairness
}
this.removeEmptyLane(key);
}
}
private getOrCreateLane(tenantKey: string): { queue: FairCostQueue<QueuedPayload>; lastUsedMs: number } {
let lane = this.virtualLanes.get(tenantKey);
if (!lane) {
// Evict oldest lane if at capacity (LRU).
if (this.virtualLanes.size >= ADMISSION_LANE_MAX_SESSIONS) {
const oldestKey = this.oldestLaneKey();
if (oldestKey) {
this.deleteLane(oldestKey);
}
}
// Per-lane queue uses the same maxQueueCount/maxQueueCost as the shared
// queue. Total memory is bounded by ADMISSION_LANE_MAX_SESSIONS (1000)
// × per-lane queue caps — each lane's FairCostQueue rejects when full.
lane = {
queue: new FairCostQueue(this.config.maxQueueCount, this.config.maxQueueCost),
lastUsedMs: this.clock.now(),
};
this.virtualLanes.set(tenantKey, lane);
}
lane.lastUsedMs = this.clock.now();
return lane;
}
private removeEmptyLane(tenantKey: string): void {
const lane = this.virtualLanes.get(tenantKey);
if (lane && lane.queue.size === 0) {
this.virtualLanes.delete(tenantKey);
}
}
/** Drain and reject all pending entries in a lane before removing it from the map. */
private deleteLane(tenantKey: string): void {
const lane = this.virtualLanes.get(tenantKey);
if (!lane) return;
for (const entry of lane.queue.drain()) {
this.clearEntryTimer(entry);
this.detachAbort(entry);
entry.payload.reject(
createAdmissionRejectError("ADMISSION_LANE_EVICTED", "connection lane evicted")
);
this.rejectedCount += 1;
}
this.virtualLanes.delete(tenantKey);
}
private oldestLaneKey(): string | undefined {
let oldest: string | undefined;
let oldestMs = Infinity;
for (const [key, lane] of this.virtualLanes) {
if (lane.lastUsedMs <= oldestMs) {
oldestMs = lane.lastUsedMs;
oldest = key;
}
}
return oldest;
}
private evictIdleLanes(): void {
const now = this.clock.now();
const keysToDelete: string[] = [];
for (const [key, lane] of this.virtualLanes) {
if (now - lane.lastUsedMs >= ADMISSION_LANE_TTL_MS) {
keysToDelete.push(key);
}
}
for (const key of keysToDelete) {
this.deleteLane(key);
}
if (this.virtualLanes.size > 0) {
this.armLaneEviction();
} else {
this.clearLaneEviction();
}
}
private armLaneEviction(): void {
this.clearLaneEviction();
this.laneEvictionTimer = this.clock.setTimer(
() => this.evictIdleLanes(),
ADMISSION_LANE_TTL_MS
);
}
private clearLaneEviction(): void {
if (this.laneEvictionTimer !== undefined) {
this.clock.clearTimer(this.laneEvictionTimer);
this.laneEvictionTimer = undefined;
}
}
private laneTotalQueuedCost(): number {
let total = 0;
for (const [, lane] of this.virtualLanes) {
total = addSaturated(total, lane.queue.totalCost);
}
return total;
}
private laneTotalQueuedCount(): number {
let count = 0;
for (const [, lane] of this.virtualLanes) {
count = addSaturated(count, lane.queue.size);
}
return count;
}
private laneTenantSnapshot(): ReadonlyArray<{ tenantKey: string; queuedCount: number; queuedCost: number }> {
const arr: { tenantKey: string; queuedCount: number; queuedCost: number }[] = [];
for (const [tenantKey, lane] of this.virtualLanes) {
arr.push({
tenantKey,
queuedCount: saturateSnapshotNumber(lane.queue.size),
queuedCost: saturateSnapshotNumber(lane.queue.totalCost),
});
}
return arr;
}
private releaseVirtual(record: ActiveLeaseRecord): void {

View File

@@ -39,6 +39,7 @@ export const DEFAULT_ADAPTIVE_ADMISSION_CONFIG: Readonly<AdaptiveAdmissionConfig
maxQueueCost: 2000,
defaultMaxWaitMs: 5_000,
windowMs: 1_000,
virtualLanes: false,
});
const RUNTIME_STORE_KEY = Symbol.for("omniroute.adaptiveAdmission.runtime");
@@ -117,6 +118,11 @@ export function resolveAdaptiveAdmissionConfigFromEnv(
// Shared pure validation — accept exact documented maxima, reject core-invalid configs.
validateConfig(cfg);
// Per-connection virtual admission lanes (#9654) — opt-in via OMNIROUTE_CHAT_VIRTUAL_LANES.
const vlRaw = env.OMNIROUTE_CHAT_VIRTUAL_LANES;
cfg.virtualLanes = vlRaw === "1" || vlRaw === "true";
return cfg;
}
@@ -236,6 +242,12 @@ const REJECT_MAP: Record<AdmissionRejectCode, RejectHttpMapping> = {
message: "Service temporarily unavailable",
retryAfter: "1",
},
ADMISSION_LANE_EVICTED: {
status: 503,
code: "admission_lane_evicted",
message: "Connection lane evicted",
retryAfter: "1",
},
};
function isAdmissionRejectError(

View File

@@ -33,6 +33,7 @@ export type AdmissionRejectCode =
| "ADMISSION_QUEUE_FULL"
| "ADMISSION_DEADLINE"
| "ADMISSION_ABORTED"
| "ADMISSION_LANE_EVICTED"
| "ADMISSION_SHUTDOWN"
| "ADMISSION_UNAVAILABLE";
@@ -79,6 +80,8 @@ export interface AdaptiveAdmissionConfig {
maxIncreasePerWindow?: number;
/** Optional cost quanta override used only when callers pass features instead of cost. */
cost?: Partial<AdmissionCostConfig>;
/** Per-connection virtual admission lanes (#9654). Default: false. */
virtualLanes?: boolean;
}
export interface AdmissionRequest {
@@ -137,6 +140,16 @@ export interface AdmissionSnapshot {
virtualActiveCount: number;
virtualQueuedCost: number;
virtualQueuedCount: number;
/** Per-connection virtual lane metrics (#9654). */
laneCount: number;
laneQueuedCost: number;
laneQueuedCount: number;
/** Per-tenant queue breakdown (opaque keys, never raw API keys). */
laneTenants: ReadonlyArray<{
tenantKey: string;
queuedCount: number;
queuedCost: number;
}>;
admittedCount: number;
rejectedCount: number;
wouldAdmitCount: number;

View File

@@ -1,6 +1,6 @@
import type { AutoVariant } from "./autoPrefix";
import { VALID_VARIANTS } from "./autoPrefix";
import { parseAutoSuffix } from "./suffixComposition";
import { parseAutoSuffix, type AutoCategory, type AutoTier } from "./suffixComposition";
import { isValidModelFamily, AUTO_FAMILY_IDS } from "./modelFamily";
export { AUTO_FAMILY_IDS };
@@ -112,13 +112,71 @@ export function isPaidTierAutoId(autoId: string): boolean {
return parsed.valid && parsed.tier === "pro";
}
export async function createBuiltinAutoCombo(modelStr: string, suffix: string) {
const { createVirtualAutoCombo } = await import("./virtualFactory.ts");
/**
* Resolved spec for a built-in `auto/*` id: either a flat variant (legacy) or
* a category/tier overlay (#4235 Phase B). Category `vision`/`multimodal` adds
* a candidate filter so the virtual combo only scores vision-capable models.
*/
export type BuiltinAutoSpec =
| { variant: AutoVariant | undefined }
| { category: AutoCategory; tier?: AutoTier };
/**
* Vision-flavored flat ids that MUST resolve to the `vision` category (candidate
* filter by capability), not to a flat variant: the vision-bridge guardrail and
* its self-loop depend on `auto/best-vision` picking a model that can actually
* see images. Mapping it to `smart` scored ALL candidates and resolved to
* text-only models (e.g. deepseek-v4-flash-free), breaking every describe call.
*/
const VISION_CATEGORY_AUTO_IDS: Record<string, { category: "vision"; tier?: AutoTier }> = {
"auto/best-vision": { category: "vision" },
"auto/pro-vision": { category: "vision", tier: "pro" },
};
/**
* Pure resolver for a built-in `auto/*` id. Extracted from
* `createBuiltinAutoCombo` so the catalog mapping is unit-testable without
* materializing a virtual combo (which requires the DB).
*/
export function resolveBuiltinAutoSpec(modelStr: string, suffix: string): BuiltinAutoSpec {
const visionSpec = VISION_CATEGORY_AUTO_IDS[modelStr];
if (visionSpec) return visionSpec;
const resolved = resolveAutoVariant(modelStr, suffix);
if (resolved.recognized) {
const spec = modelStr === "auto/best-free" ? { tier: "free" as const } : undefined;
const virtualCombo = await createVirtualAutoCombo(resolved.variant, spec);
return { variant: resolved.variant };
}
const parsed = parseAutoSuffix(suffix);
if (parsed.valid) {
return {
category: parsed.category as AutoCategory,
...(parsed.tier ? { tier: parsed.tier } : {}),
};
}
return { variant: undefined };
}
export async function createBuiltinAutoCombo(modelStr: string, suffix: string) {
const { createVirtualAutoCombo } = await import("./virtualFactory.ts");
const spec = resolveBuiltinAutoSpec(modelStr, suffix);
if ("category" in spec) {
// #4235 Phase B category/tier path (incl. vision ids like auto/best-vision).
const virtualCombo = await createVirtualAutoCombo(undefined, {
category: spec.category,
...(spec.tier ? { tier: spec.tier } : {}),
});
virtualCombo.name = modelStr;
virtualCombo.id = modelStr;
return virtualCombo;
}
if ("variant" in spec && spec.variant !== undefined) {
const virtualCombo = await createVirtualAutoCombo(spec.variant, {
...(modelStr === "auto/best-free" ? { tier: "free" as const } : {}),
});
virtualCombo.name = modelStr;
virtualCombo.id = modelStr;
return virtualCombo;

View File

@@ -20,6 +20,7 @@ import type { AutoVariant } from "./autoPrefix";
import { classifyTier } from "../tierResolver";
import { getResolvedModelCapabilities } from "@/lib/modelCapabilities";
import { isVisionModelId } from "@/shared/constants/visionModels";
import { isVisionBridgeForcedModel } from "@/shared/constants/visionBridgeDefaults";
export type AutoCategory = "coding" | "reasoning" | "vision" | "chat" | "multimodal";
export type AutoTier = "fast" | "cheap" | "floor" | "free" | "reliable" | "pro";
@@ -111,9 +112,16 @@ export function buildAutoCandidateFilter(
checks.push((c) => {
try {
const caps = getResolvedModelCapabilities({ provider: c.provider, model: c.model });
return caps.supportsVision === true || isVisionModelId(c.model);
const capable =
caps.supportsVision === true || isVisionModelId(c.model);
if (!capable) return false;
// #vison-pool: registry entries whose catalog OVERSTATES vision support
// (opencode-go/opencode-zen/tokenrouter — the backend models are text-only)
// are forced through the vision bridge by isVisionBridgeForcedModel.
// They must never be selected as the vision-capable candidate itself.
return !isVisionBridgeForcedModel(`${c.provider}/${c.model}`);
} catch {
return isVisionModelId(c.model);
return isVisionModelId(c.model) && !isVisionBridgeForcedModel(`${c.provider}/${c.model}`);
}
});
}

View File

@@ -517,6 +517,7 @@ export async function httpBackedChat(
headers,
body,
signal: signal ?? undefined,
sessionScope: req.poolKey,
});
const fetchMs = Date.now() - fetchStart;

View File

@@ -68,6 +68,7 @@ import { getXaiUsage } from "./usage/xai.ts";
import { getXaiOauthUsage } from "./usage/xaiOauth.ts";
import { getGrokCliUsage } from "./usage/grokCli.ts";
import { getFirecrawlUsage } from "./usage/firecrawl.ts";
import { getCommandCodeUsage } from "./usage/command-code.ts";
type JsonRecord = Record<string, unknown>;
type UsageProviderConnection = JsonRecord & {
@@ -130,6 +131,8 @@ export const USAGE_FETCHER_PROVIDERS = [
"ha",
// Firecrawl team credits (GET /v2/team/credit-usage)
"firecrawl",
// Command Code credits + 5h/weekly windows (GET /alpha/billing/credits)
"command-code",
] as const;
export type UsageFetcherProvider = (typeof USAGE_FETCHER_PROVIDERS)[number];
@@ -229,6 +232,8 @@ export async function getUsageForProvider(
return await getHyperAgentUsage(apiKey || accessToken, providerSpecificData);
case "firecrawl":
return await getFirecrawlUsage(id || "", apiKey, connection);
case "command-code":
return await getCommandCodeUsage(apiKey || accessToken || "");
default:
return { message: `Usage API not implemented for ${provider}` };
}
@@ -259,6 +264,7 @@ export const __testing = {
getXaiUsage,
getXaiOauthUsage,
getFirecrawlUsage,
getCommandCodeUsage,
getVertexUsage,
getMiniMaxAuthErrorMessage,
getMiniMaxErrorSummary,

View File

@@ -0,0 +1,233 @@
/**
* usage/command-code.ts — Command Code (commandcode.ai) usage fetcher.
*
* Bearer `/alpha` endpoints (same surface the CLI `/usage` view uses):
* GET /alpha/whoami
* GET /alpha/billing/credits → remaining pools + windowLimits
* GET /alpha/billing/subscriptions → planId + billing period (soft)
* GET /alpha/usage/summary → period spend (soft)
*
* Surfaces five_hour / weekly rolling USD windows plus a credits pool quota
* for Provider Limits and genericQuotaFetcher preflight.
*/
import { sanitizeErrorMessage } from "../../utils/error.ts";
import { toNumber, toRecord } from "./scalars.ts";
import { createQuotaFromUsage, parseResetTime, type UsageQuota } from "./quota.ts";
const COMMAND_CODE_API_BASE =
process.env.COMMANDCODE_API_URL?.trim() || "https://api.commandcode.ai";
const FETCH_TIMEOUT_MS = 10_000;
type JsonRecord = Record<string, unknown>;
const PLAN_LABELS: Record<string, string> = {
"individual-goat": "Command Code · GOAT",
"individual-go": "Command Code · Go",
"individual-pro": "Command Code · Pro",
"individual-max-10x": "Command Code · Max 10×",
"individual-max-20x": "Command Code · Max 20×",
"team-pro": "Command Code · Team Pro",
};
function withCurrency(quota: UsageQuota, displayName: string): UsageQuota {
return {
...quota,
currency: "USD",
displayName,
};
}
function humanizePlanId(planId: string | undefined): string {
if (!planId) return "Command Code";
const mapped = PLAN_LABELS[planId];
if (mapped) return mapped;
const title = planId
.replace(/^individual-/, "")
.replace(/^team-/, "Team ")
.split("-")
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
return `Command Code · ${title || planId}`;
}
function orgQuery(orgId: string | null | undefined): string {
if (!orgId) return "";
return `?orgId=${encodeURIComponent(orgId)}`;
}
async function fetchJson(
path: string,
apiKey: string
): Promise<{ ok: boolean; status: number; body: JsonRecord | null }> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
try {
const response = await fetch(`${COMMAND_CODE_API_BASE}${path}`, {
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
"Content-Type": "application/json",
},
signal: controller.signal,
});
const text = await response.text();
let body: JsonRecord | null = null;
if (text) {
try {
body = toRecord(JSON.parse(text));
} catch {
body = null;
}
}
return { ok: response.ok, status: response.status, body };
} finally {
clearTimeout(timer);
}
}
function creditRemaining(credits: JsonRecord): number {
return (
Math.max(0, toNumber(credits.monthlyCredits, 0)) +
Math.max(0, toNumber(credits.purchasedCredits, 0)) +
Math.max(0, toNumber(credits.freeCredits, 0))
);
}
function windowQuota(window: unknown, displayName: string): UsageQuota | null {
const w = toRecord(window);
const cap = toNumber(w.cap, 0);
if (!(cap > 0)) return null;
const used = toNumber(w.used, 0);
return withCurrency(createQuotaFromUsage(used, cap, w.resetAt), displayName);
}
/**
* Command Code Usage — monthly credit pool + 5h/weekly rolling windows.
*/
export async function getCommandCodeUsage(apiKey: string) {
if (!apiKey) {
return { message: "Command Code API key not available. Add a key to view usage." };
}
try {
let orgId: string | null = null;
try {
const whoami = await fetchJson("/alpha/whoami", apiKey);
if (whoami.status === 401 || whoami.status === 403) {
return {
message:
"Command Code connected. The API key was rejected — reconnect or rotate the key.",
};
}
if (whoami.ok && whoami.body) {
const org = toRecord(whoami.body.org);
const id = typeof org.id === "string" && org.id.trim() ? org.id.trim() : null;
orgId = id;
}
} catch {
// whoami is optional — continue without orgId
}
const q = orgQuery(orgId);
const creditsRes = await fetchJson(`/alpha/billing/credits${q}`, apiKey);
if (creditsRes.status === 401 || creditsRes.status === 403) {
return {
message: "Command Code connected. The API key was rejected — reconnect or rotate the key.",
};
}
if (!creditsRes.ok || !creditsRes.body) {
return {
message: `Command Code connected. /alpha/billing/credits returned HTTP ${creditsRes.status}.`,
};
}
const creditsObj = toRecord(creditsRes.body.credits);
const windowLimits = toRecord(creditsRes.body.windowLimits);
const remaining = creditRemaining(creditsObj);
let planId: string | undefined;
let periodStart: string | undefined;
let periodEnd: string | null = null;
try {
const subRes = await fetchJson(`/alpha/billing/subscriptions${q}`, apiKey);
if (subRes.ok && subRes.body) {
const data = toRecord(subRes.body.data);
if (typeof data.planId === "string" && data.planId.trim()) {
planId = data.planId.trim();
}
if (typeof data.currentPeriodStart === "string") {
periodStart = data.currentPeriodStart;
}
periodEnd = parseResetTime(data.currentPeriodEnd);
}
} catch {
// subscription enrichment is soft-fail
}
let periodUsed = 0;
try {
const sinceQ =
periodStart != null ? `${q ? `${q}&` : "?"}since=${encodeURIComponent(periodStart)}` : q;
const summaryRes = await fetchJson(`/alpha/usage/summary${sinceQ}`, apiKey);
if (summaryRes.ok && summaryRes.body) {
const cost = toNumber(summaryRes.body.totalCost, Number.NaN);
if (Number.isFinite(cost) && cost >= 0) {
periodUsed = cost;
} else {
const monthly = toNumber(summaryRes.body.totalMonthlyCredits, Number.NaN);
if (Number.isFinite(monthly) && monthly >= 0) periodUsed = monthly;
}
}
} catch {
// summary enrichment is soft-fail
}
const quotas: Record<string, UsageQuota> = {};
const fiveHour = windowQuota(windowLimits.fiveHour, "5-hour window");
if (fiveHour) quotas.five_hour = fiveHour;
const weekly = windowQuota(windowLimits.weekly, "Weekly window");
if (weekly) quotas.weekly = weekly;
const creditsTotal = periodUsed + remaining;
const creditsRemainingPct =
creditsTotal > 0
? Math.round((remaining / creditsTotal) * 1000) / 10
: remaining > 0
? 100
: 0;
quotas.credits = {
used: Math.max(0, periodUsed),
total: Math.max(0, creditsTotal),
remaining,
remainingPercentage: creditsRemainingPct,
resetAt: periodEnd,
unlimited: false,
currency: "USD",
displayName: "Credits",
grantedBalance: Math.max(0, toNumber(creditsObj.monthlyCredits, 0)),
toppedUpBalance:
Math.max(0, toNumber(creditsObj.purchasedCredits, 0)) +
Math.max(0, toNumber(creditsObj.freeCredits, 0)),
};
return {
plan: humanizePlanId(planId),
quotas,
windowExceeded: typeof windowLimits.exceeded === "string" ? windowLimits.exceeded : null,
limited: windowLimits.limited === true,
};
} catch (error) {
return {
message: `Command Code usage error: ${sanitizeErrorMessage(
error instanceof Error ? error.message : String(error)
)}`,
};
}
}

View File

@@ -464,12 +464,17 @@ function openaiToGeminiBase(
// Gemini expects the signature on the functionCall part itself.
// If we are in a mode where missing signatures cause 400s (and we couldn't find one),
// safely default to the bypass string to protect against 400s.
// safely default to the bypass string to protect against 400s. The bypass sentinel is
// an audit-trail risk (a magic validator-bypass string upstream could log/flag), so
// operators can disable it via ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS=0 — real signatures
// are always preferred; the sentinel only fills the gap when none is available.
const signatureBypassEnabled =
toolNameOptions.supportsSignatureBypass &&
signaturelessToolCallMode !== "text" &&
process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS !== "0";
const finalSignature =
embeddedThoughtSignature ||
(toolNameOptions.supportsSignatureBypass && signaturelessToolCallMode !== "text"
? "skip_thought_signature_validator"
: undefined);
(signatureBypassEnabled ? "skip_thought_signature_validator" : undefined);
parts.push({
...(finalSignature ? { thoughtSignature: finalSignature } : {}),
functionCall: {

View File

@@ -332,6 +332,8 @@ function splitCursorEffortSuffix(
/**
* cursor-agent rewrites model ids before putting them on the wire:
* "auto" → RequestedModel { model_id: "default" }
* "auto-cost" → RequestedModel { model_id: "default",
* parameters: [{id: "optimization", value: "cost"}] }
* "composer-2-fast" → RequestedModel { model_id: "composer-2",
* parameters: [{id: "fast", value: "true"}] }
* "claude-opus-4-8-high" → RequestedModel { model_id: "claude-opus-4-8",
@@ -342,7 +344,31 @@ function splitCursorEffortSuffix(
* Other ids are passed through verbatim after spelling-variant normalization
* (see normalizeCursorModelId).
*/
export function resolveRequestedModel(modelId: string): {
/** Cursor Router optimization levels (OpenCodex `CURSOR_ROUTING_LEVELS`). */
export const CURSOR_ROUTING_LEVELS = ["cost", "balance", "intelligence"] as const;
export type CursorRoutingLevel = (typeof CURSOR_ROUTING_LEVELS)[number];
/**
* ModelParameter id for Cursor's Cost/Balance/Intelligence control on wire model
* `default` (OpenCodex `CURSOR_ROUTING_LEVEL_PARAMETER_ID`).
*/
export const CURSOR_ROUTING_LEVEL_PARAMETER_ID = "optimization";
export type ResolveRequestedModelOptions = {
/**
* When set and containing the normalized client model id, send that id
* verbatim on AgentRun (skip composer-fast / Claude / GPT splits).
* Live AvailableModels returns flattened effort-suffixed ids; stripping them
* to a missing base causes Cursor `AI Model Not Found`. Auto / auto-* still
* map to wire `default` (+ optimization) even when present in this set.
*/
liveCatalogIds?: ReadonlySet<string>;
};
export function resolveRequestedModel(
modelId: string,
opts?: ResolveRequestedModelOptions
): {
modelId: string;
parameters: Array<{ id: string; value: string }>;
} {
@@ -350,6 +376,20 @@ export function resolveRequestedModel(modelId: string): {
if (normalized === "auto") {
return { modelId: "default", parameters: [] };
}
// OpenCodex-style router variants: auto-cost / auto-balance / auto-intelligence
// → wire `default` + ModelParameter { id: "optimization", value: <level> }.
for (const level of CURSOR_ROUTING_LEVELS) {
if (normalized === `auto-${level}`) {
return {
modelId: "default",
parameters: [{ id: CURSOR_ROUTING_LEVEL_PARAMETER_ID, value: level }],
};
}
}
// Live catalog is authoritative for exact ids (flattened effort variants).
if (opts?.liveCatalogIds?.has(normalized)) {
return { modelId: normalized, parameters: [] };
}
// Strip the "-fast" suffix and surface it as a parameter — only the composer
// family observably needs this split today, but the protocol field is generic.
if (normalized.startsWith("composer-") && normalized.endsWith("-fast")) {
@@ -406,6 +446,8 @@ export type AgentRunInput = {
// encodeSelectedImageBody). Empty / undefined keeps the request
// byte-identical to the text-only path.
images?: EncodedImage[];
/** Exact live AvailableModels ids — see resolveRequestedModel liveCatalogIds. */
liveCatalogIds?: ReadonlySet<string>;
};
export { cursorImageAttachmentPath, encodeSelectedImageBody };
@@ -433,7 +475,9 @@ export function openAIToolsToMcpDefs(tools: OpenAITool[]): McpToolDefinition[] {
export function encodeAgentRunRequest(input: AgentRunInput): Buffer {
const conversationId = input.conversationId || crypto.randomUUID();
const messageId = input.messageId || crypto.randomUUID();
const { modelId, parameters } = resolveRequestedModel(input.modelId);
const { modelId, parameters } = resolveRequestedModel(input.modelId, {
liveCatalogIds: input.liveCatalogIds,
});
// UserMessage { text, message_id, selected_context, mode=1 }.
// selected_context is normally an empty placeholder (required by the server

View File

@@ -13,7 +13,7 @@ import {
proxyConfigToUrl,
proxyUrlForLogs,
} from "./proxyDispatcher.ts";
import tlsClient from "./tlsClient.ts";
import tlsClient, { type TlsFetchOptions } from "./tlsClient.ts";
import { isProxyReachable } from "@/lib/proxyHealth";
import {
isControlPlaneProxyDirectFallbackEnabled,
@@ -79,6 +79,32 @@ function isTlsFingerprintEnabled() {
return process.env.ENABLE_TLS_FINGERPRINT === "true";
}
function tlsFingerprintProviderAllowed(
provider: string | null | undefined,
proxied: boolean
): boolean {
const configured = process.env.TLS_FINGERPRINT_PROVIDERS?.trim();
// Preserve the legacy direct-only opt-in. The new proxied transport requires
// an explicit allowlist so enabling TLS cannot silently change proxy traffic.
if (!configured) return !proxied;
if (!provider) return false;
const normalizedProvider = provider.trim().toLowerCase();
return configured
.split(",")
.some((candidate) => candidate.trim().toLowerCase() === normalizedProvider);
}
type TlsClientLike = {
available: boolean;
fetch: (url: string, options?: TlsFetchOptions) => Promise<Response>;
};
let activeTlsClient: TlsClientLike = tlsClient;
/** Test seam for exercising wreq selection without replacing the module loader. */
export function setTlsClientForTest(client: TlsClientLike | null): void {
activeTlsClient = client ?? tlsClient;
}
// #8376: transport-level connect-failure codes that mean "the configured upstream
// proxy (or the target itself, for direct egress) is unreachable" — as opposed to an
// ordinary upstream HTTP error. Read `.code` first (stable across undici/node
@@ -122,9 +148,12 @@ function tagProxyUnreachable<T>(err: T): T {
return err;
}
/** Per-request tracking of whether TLS fingerprint was used */
type TlsFingerprintStore = { used: boolean };
const tlsFingerprintContext = new AsyncLocalStorage<TlsFingerprintStore>();
/** Per-request TLS identity and success telemetry. */
type TlsFingerprintStore = {
used: boolean;
provider?: string | null;
sessionScope?: string;
};
/**
* #5217 (Gap-secondary): a mutable sink that records the proxy actually applied
@@ -227,20 +256,112 @@ function requestHasNonReplayableBody(
return false;
}
const TLS_ALLOWED_OPTION_KEYS: Record<string, true> = {
body: true,
headers: true,
method: true,
redirect: true,
signal: true,
};
function isWreqBodySupported(body: unknown): boolean {
if (body == null || typeof body === "string") return true;
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return true;
if (body instanceof URLSearchParams) return true;
if (typeof Blob !== "undefined" && body instanceof Blob) return true;
if (typeof FormData !== "undefined" && body instanceof FormData) return true;
return false;
}
function isTlsRequestEligible(
input: RequestInfo | URL,
options: FetchWithDispatcherOptions
): boolean {
if (typeof Request !== "undefined" && input instanceof Request) return false;
if (!isWreqBodySupported(options.body)) return false;
return Object.keys(options).every((key) => TLS_ALLOWED_OPTION_KEYS[key] === true);
}
function isTlsFallbackReplaySafe(
input: RequestInfo | URL,
options: FetchWithDispatcherOptions
): boolean {
const method = (
options.method ??
(typeof Request !== "undefined" && input instanceof Request ? input.method : "GET")
).toUpperCase();
return (
(method === "GET" || method === "HEAD" || method === "OPTIONS") &&
!requestHasNonReplayableBody(input, options)
);
}
function getEffectiveSignal(
input: RequestInfo | URL,
options: FetchWithDispatcherOptions
): AbortSignal | null | undefined {
return (
options.signal ??
(typeof Request !== "undefined" && input instanceof Request ? input.signal : undefined)
);
}
function isWreqProxySupported(proxyUrl: string): boolean {
try {
const parsed = new URL(proxyUrl);
return (
(parsed.protocol === "http:" || parsed.protocol === "https:") &&
parsed.searchParams.get("family") === null
);
} catch {
return false;
}
}
function sanitizeTransportError(
error: unknown,
message: string,
fallbackCode: string
): Error & { code: string; errorCode?: string; statusCode?: number } {
const source = error && typeof error === "object" ? (error as Record<string, unknown>) : {};
const sanitized = new Error(message) as Error & {
code: string;
errorCode?: string;
statusCode?: number;
};
sanitized.code =
typeof source.code === "string" && /^[A-Z0-9_:-]{1,64}$/.test(source.code)
? source.code
: fallbackCode;
if (
typeof source.errorCode === "string" &&
/^[a-zA-Z0-9_:-]{1,64}$/.test(source.errorCode)
) {
sanitized.errorCode = source.errorCode;
}
if (typeof source.statusCode === "number" && Number.isFinite(source.statusCode)) {
sanitized.statusCode = source.statusCode;
}
return sanitized;
}
/** Injectable dependencies for testability (Approach B DI). */
export type ProxyFetchDeps = {
undiciFetch?: FetchWithDispatcher;
nativeFetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
findWorkingProxy?: (hostname: string, targetUrl: string) => Promise<string | null>;
};
type PatchState = {
originalFetch: typeof globalThis.fetch;
proxyContext: AsyncLocalStorage<unknown>;
tlsFingerprintContext?: AsyncLocalStorage<TlsFingerprintStore>;
isPatched: boolean;
};
const isCloud = typeof caches !== "undefined" && typeof caches === "object";
const PATCH_STATE_KEY = Symbol.for("omniroute.proxyFetch.state");
const DIRECT_PROXY_CONTEXT = Symbol.for("omniroute.proxyFetch.direct-context");
function getPatchState(): PatchState {
const scopedGlobal = globalThis as typeof globalThis & {
@@ -251,6 +372,7 @@ function getPatchState(): PatchState {
scopedGlobal[PATCH_STATE_KEY] = {
originalFetch: globalThis.fetch,
proxyContext: new AsyncLocalStorage(),
tlsFingerprintContext: new AsyncLocalStorage(),
isPatched: false,
};
}
@@ -258,9 +380,11 @@ function getPatchState(): PatchState {
}
const patchState = getPatchState();
patchState.tlsFingerprintContext ??= new AsyncLocalStorage<TlsFingerprintStore>();
const originalFetch = patchState.originalFetch;
const originalFetchWithDispatcher = originalFetch as FetchWithDispatcher;
const proxyContext = patchState.proxyContext;
const tlsFingerprintContext = patchState.tlsFingerprintContext;
function noProxyMatch(targetUrl) {
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
@@ -381,6 +505,9 @@ export function resolveProxyForRequest(targetUrl) {
}
const contextProxy = proxyContext.getStore();
if (contextProxy === DIRECT_PROXY_CONTEXT) {
return { source: "direct", proxyUrl: null };
}
if (contextProxy) {
// #9551: NO_PROXY must bypass context-proxy too
if (target && noProxyMatch(targetUrl)) {
@@ -398,16 +525,15 @@ export function resolveProxyForRequest(targetUrl) {
}
/**
* A caller-initiated abort/timeout is not a proxy transport failure — it must
* not be misreported as one. Prefer `signal.aborted` because
* `AbortController.abort(reason)` may surface a custom Error rather than a
* standard AbortError/TimeoutError name.
* Ported from decolua/9router#2589 (`isCallerAbort`).
* A caller-initiated abort is identified only by the caller's effective signal.
* Dependency-internal TimeoutError/AbortError values are transport failures and
* retain the normal safe-method fallback behavior.
*/
function isCallerAbort(error: unknown, signal: AbortSignal | null | undefined): boolean {
if (signal?.aborted === true) return true;
const name = (error as { name?: unknown } | null)?.name;
return name === "AbortError" || name === "TimeoutError";
function isCallerAbort(
_error: unknown,
signal: AbortSignal | null | undefined
): boolean {
return signal?.aborted === true;
}
function getTargetUrl(input) {
@@ -425,9 +551,13 @@ export async function runWithProxyContext(
throw new TypeError("runWithProxyContext requires a callback function");
}
// Inherit existing context if no specific proxyConfig is provided
// Inherit existing context if no specific proxyConfig is provided. A direct
// sentinel must remain direct without being mistaken for a proxy config.
const currentContext = proxyContext.getStore();
const effectiveProxyConfig = proxyConfig || currentContext || null;
const inheritsDirect = currentContext === DIRECT_PROXY_CONTEXT && !proxyConfig;
const effectiveProxyConfig =
proxyConfig || (inheritsDirect ? null : currentContext) || null;
const contextValue = inheritsDirect ? DIRECT_PROXY_CONTEXT : effectiveProxyConfig;
const resolvedProxyUrl = effectiveProxyConfig ? proxyConfigToUrl(effectiveProxyConfig) : null;
@@ -435,8 +565,9 @@ export async function runWithProxyContext(
// This fallback changes egress IP, so upgrades must not silently turn it on.
const directFallbackOnUnreachable =
opts?.directFallbackOnUnreachable === true && isControlPlaneProxyDirectFallbackEnabled();
// Run fn with the proxy context cleared so the request egresses directly.
const runDirect = () => proxyContext.run(null, fn);
// Keep an explicit direct sentinel so resolveProxyForRequest cannot re-read
// HTTPS_PROXY/HTTP_PROXY after the control-plane route decision.
const runDirect = () => proxyContext.run(DIRECT_PROXY_CONTEXT, fn);
// T14: Proxy Fast-Fail (non-blocking, #9100)
// Perform a short TCP reachability check BEFORE issuing upstream requests.
@@ -502,7 +633,7 @@ export async function runWithProxyContext(
}
}
return proxyContext.run(effectiveProxyConfig, async () => {
return proxyContext.run(contextValue, async () => {
if (resolvedProxyUrl && effectiveProxyConfig !== currentContext) {
// #9158: this fires on EVERY proxied request (innermost context wins).
// Gate it behind the same env flag as the relay routing log so request
@@ -604,23 +735,47 @@ async function patchedFetch(
const { source, proxyUrl } = resolved;
if (!proxyUrl) {
// TLS fingerprint spoofing for direct connections (no proxy configured)
if (isTlsFingerprintEnabled() && tlsClient.available) {
// TLS fingerprint spoofing for an already-resolved direct route. Explicit
// proxy:null prevents wreq from re-reading a global environment proxy.
const tlsStore = tlsFingerprintContext.getStore();
let tlsDirectFallback = false;
if (
isTlsFingerprintEnabled() &&
activeTlsClient.available &&
tlsFingerprintProviderAllowed(tlsStore?.provider, false) &&
isTlsRequestEligible(input, options)
) {
try {
const store = tlsFingerprintContext.getStore();
if (store) store.used = true;
return await tlsClient.fetch(targetUrl, {
...options,
const response = await activeTlsClient.fetch(targetUrl, {
method: options.method,
headers: options.headers,
signal: options.signal ?? undefined,
body: options.body as TlsFetchOptions["body"],
redirect: options.redirect,
signal: getEffectiveSignal(input, options),
proxy: null,
sessionScope: tlsStore?.sessionScope,
});
if (tlsStore) tlsStore.used = true;
return response;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.warn(
`[ProxyFetch] TLS fingerprint failed, falling back to native fetch: ${message}`
);
const store = tlsFingerprintContext.getStore();
if (store) store.used = false;
if (isCallerAbort(error, getEffectiveSignal(input, options))) throw error;
const sessionHadCookies =
!!error &&
typeof error === "object" &&
"sessionHadCookies" in error &&
error.sessionHadCookies === true;
if (!isTlsFallbackReplaySafe(input, options) || sessionHadCookies) {
throw sanitizeTransportError(
error,
sessionHadCookies
? "TLS fingerprint request failed; stateful session cannot be replayed"
: "TLS fingerprint request failed; request is not safe to replay",
"TLS_FINGERPRINT_FAILED"
);
}
console.warn("[ProxyFetch] TLS fingerprint transport failed; using direct dispatcher");
if (tlsStore) tlsStore.used = false;
tlsDirectFallback = true;
}
}
// Direct connection (no proxy) — use undici with custom dispatcher for timeout control.
@@ -695,7 +850,11 @@ async function patchedFetch(
}
// All attempts exhausted — try proxy fallback before native fetch
if (source === "direct" && isFeatureFlagEnabled("PROXY_AUTO_SELECT_ENABLED")) {
if (
!tlsDirectFallback &&
source === "direct" &&
isFeatureFlagEnabled("PROXY_AUTO_SELECT_ENABLED")
) {
let targetHostname = "";
try {
targetHostname = new URL(targetUrl).hostname;
@@ -703,7 +862,8 @@ async function patchedFetch(
// ignore
}
if (targetHostname) {
const { findWorkingProxy } = await import("./proxyFallback.ts");
const findWorkingProxy =
deps.findWorkingProxy ?? (await import("./proxyFallback.ts")).findWorkingProxy;
const fallbackProxyUrl = await findWorkingProxy(targetHostname, targetUrl);
if (fallbackProxyUrl) {
try {
@@ -854,6 +1014,51 @@ async function patchedFetch(
throw lastRelayError;
}
// The proxied TLS overlay is deliberately narrow: approved provider, exact
// http(s) proxy, no relay/family pinning, and only options wreq can preserve.
const tlsStore = tlsFingerprintContext.getStore();
if (
isTlsFingerprintEnabled() &&
typeof tlsStore?.sessionScope === "string" &&
tlsStore.sessionScope.trim().length > 0 &&
activeTlsClient.available &&
tlsFingerprintProviderAllowed(tlsStore?.provider, true) &&
isTlsRequestEligible(input, options) &&
isWreqProxySupported(proxyUrl)
) {
try {
const response = await activeTlsClient.fetch(targetUrl, {
method: options.method,
headers: options.headers,
body: options.body as TlsFetchOptions["body"],
redirect: options.redirect,
signal: getEffectiveSignal(input, options),
proxy: proxyUrl,
sessionScope: tlsStore?.sessionScope,
});
if (tlsStore) tlsStore.used = true;
return response;
} catch (error) {
if (isCallerAbort(error, getEffectiveSignal(input, options))) throw error;
const sessionHadCookies =
!!error &&
typeof error === "object" &&
"sessionHadCookies" in error &&
error.sessionHadCookies === true;
if (!isTlsFallbackReplaySafe(input, options) || sessionHadCookies) {
throw sanitizeTransportError(
error,
sessionHadCookies
? "TLS fingerprint request failed; stateful session cannot be replayed"
: "TLS fingerprint request failed; request is not safe to replay",
"TLS_FINGERPRINT_FAILED"
);
}
console.warn("[ProxyFetch] TLS fingerprint transport failed; using proxy dispatcher");
if (tlsStore) tlsStore.used = false;
}
}
// #9100: proxy path — attempt 0 uses the pooled keep-alive dispatcher
// (pipelining 4, ONE reused TCP connection per proxy host). A transient
// socket error on a stale pooled socket is retried ONCE on a fresh
@@ -872,6 +1077,7 @@ async function patchedFetch(
attempt === 0 ? createProxyDispatcher(proxyUrl) : getProxyRetryDispatcher(proxyUrl),
});
} catch (error) {
if (isCallerAbort(error, getEffectiveSignal(input, options))) throw error;
const msg = error instanceof Error ? error.message : String(error);
const errCode = (error as { code?: unknown })?.code;
const isTransportFailure =
@@ -889,13 +1095,16 @@ async function patchedFetch(
await new Promise((r) => setTimeout(r, RETRY_BACKOFF_MS));
continue;
}
// A caller abort/timeout must propagate unchanged and without a noisy
// "Proxy request failed" log — it's not a proxy transport failure.
if (!isCallerAbort(error, options?.signal)) {
const message = error instanceof Error ? error.message : String(error);
console.error(`[ProxyFetch] Proxy request failed (${source}, fail-closed): ${message}`);
}
throw error;
tagProxyUnreachable(error);
const sanitized = sanitizeTransportError(
error,
"Proxy request failed",
"PROXY_REQUEST_FAILED"
);
console.error(
`[ProxyFetch] Proxy request failed (${source}, fail-closed; code=${sanitized.code})`
);
throw sanitized;
}
}
throw lastProxyError;
@@ -919,19 +1128,64 @@ if (!isCloud && !patchState.isPatched) {
patchState.isPatched = true;
}
export type TlsTrackingIdentity = {
provider?: string | null;
sessionScope?: string;
};
/**
* Run a function with TLS fingerprint tracking context.
* After fn completes, returns { result, tlsFingerprintUsed }.
* Run a function with account-scoped TLS fingerprint tracking.
* Both historical forms remain valid: runWithTlsTracking(fn) and
* runWithTlsTracking(provider, fn).
*/
export async function runWithTlsTracking(fn) {
const store = { used: false };
const result = await tlsFingerprintContext.run(store, fn);
export async function runWithTlsTracking<T>(
fn: () => T
): Promise<{ result: Awaited<T>; tlsFingerprintUsed: boolean }>;
export async function runWithTlsTracking<T>(
provider: string | null | undefined,
fn: () => T
): Promise<{ result: Awaited<T>; tlsFingerprintUsed: boolean }>;
export async function runWithTlsTracking<T>(
identity: TlsTrackingIdentity,
fn: () => T
): Promise<{ result: Awaited<T>; tlsFingerprintUsed: boolean }>;
export async function runWithTlsTracking<T>(
providerOrIdentityOrFn: string | null | undefined | TlsTrackingIdentity | (() => T),
maybeFn?: () => T
): Promise<{ result: Awaited<T>; tlsFingerprintUsed: boolean }> {
const legacyFn =
typeof providerOrIdentityOrFn === "function" ? providerOrIdentityOrFn : maybeFn;
if (typeof legacyFn !== "function") {
throw new TypeError("runWithTlsTracking requires a callback function");
}
const identity: TlsTrackingIdentity =
providerOrIdentityOrFn &&
typeof providerOrIdentityOrFn === "object" &&
typeof providerOrIdentityOrFn !== "function"
? providerOrIdentityOrFn
: {
provider:
typeof providerOrIdentityOrFn === "string" ? providerOrIdentityOrFn : undefined,
};
const store: TlsFingerprintStore = {
used: false,
provider: identity.provider,
sessionScope: identity.sessionScope,
};
const result = await tlsFingerprintContext.run(store, legacyFn);
return { result, tlsFingerprintUsed: store.used };
}
/** Check if TLS fingerprint is enabled and available */
export function isTlsFingerprintActive() {
return isTlsFingerprintEnabled() && tlsClient.available;
/** Check whether TLS fingerprint transport is enabled for this route identity. */
export function isTlsFingerprintActive(
provider?: string | null,
proxied = false
): boolean {
return (
isTlsFingerprintEnabled() &&
activeTlsClient.available &&
tlsFingerprintProviderAllowed(provider, proxied)
);
}
/**

View File

@@ -1,20 +1,40 @@
import { createRequire } from "module";
import { createHash } from "node:crypto";
import { getTlsClientTimeoutConfig } from "@/shared/utils/runtimeTimeouts";
const require = createRequire(import.meta.url);
const runtimeRequire = createRequire(import.meta.url);
type WreqSession = {
fetch: (url: string, options?: Record<string, unknown>) => Promise<Response>;
close: () => Promise<void> | void;
function loadRuntimeModule(moduleName: string): unknown {
// Keep the specifier dynamic. Turbopack rewrites a literal createRequire call
// to a hashed external name that is absent from the standalone Docker runtime.
return Reflect.apply(runtimeRequire, undefined, [moduleName]);
}
export type WreqResponse = {
status: number;
statusText: string;
headers: Iterable<[string, string]>;
body: ReadableStream<Uint8Array> | null;
url?: string;
redirected?: boolean;
};
type CreateSessionFn = (options: Record<string, unknown>) => Promise<WreqSession>;
export type WreqSession = {
fetch: (url: string, options?: Record<string, unknown>) => Promise<WreqResponse>;
close: () => Promise<void> | void;
getCookies?: (url: string | URL) => Record<string, string>;
};
export type CreateSessionFn = (options: Record<string, unknown>) => Promise<WreqSession>;
let createSession: CreateSessionFn | null;
try {
const loaded = require("wreq-js") as { createSession?: CreateSessionFn };
const loaded = loadRuntimeModule("wreq-js") as { createSession?: CreateSessionFn };
createSession = typeof loaded.createSession === "function" ? loaded.createSession : null;
} catch {
if (process.env.ENABLE_TLS_FINGERPRINT === "true") {
console.warn("[TlsClient] wreq-js unavailable; TLS fingerprint transport disabled");
}
createSession = null;
}
@@ -34,12 +54,26 @@ function getProxyFromEnv(): string | undefined {
);
}
interface FetchOptions {
export type WreqBodyInit =
| string
| ArrayBuffer
| ArrayBufferView
| URLSearchParams
| Buffer
| Blob
| FormData
| null;
export interface TlsFetchOptions {
method?: string;
headers?: HeadersInit;
body?: unknown;
redirect?: string;
signal?: AbortSignal;
body?: WreqBodyInit;
redirect?: RequestRedirect;
signal?: AbortSignal | null;
/** Exact resolved proxy. Undefined preserves legacy environment lookup; null means direct. */
proxy?: string | null;
/** Stable account/connection identity used to isolate cookies and circuit state. */
sessionScope?: string;
}
function normalizeHeaders(headers: HeadersInit | undefined): Record<string, string> | undefined {
@@ -62,182 +96,591 @@ function normalizeHeaders(headers: HeadersInit | undefined): Record<string, stri
return normalized;
}
/**
* TLS Client — Chrome 124 TLS fingerprint spoofing via wreq-js
* Singleton instance used to disguise Node.js TLS handshake as Chrome browser.
*
* wreq-js natively supports proxy — TLS fingerprinting works through proxy.
* Proxy URL is read from environment variables (HTTPS_PROXY, HTTP_PROXY, ALL_PROXY).
*/
class TlsClient {
session: WreqSession | null = null;
type SafeWreqError = Error & {
code?: string;
errorCode?: string;
statusCode?: number;
};
private _libraryAvailable: boolean;
private failureCount: number = 0;
private maxFailures: number = 3;
private baseCooldownMs: number = 30_000;
private cooldownMs: number = 30_000;
private cooldownMultiplier: number = 1;
private readonly MAX_COOLDOWN_MS = 600_000; // 10 min
private circuitOpenUntil: number = 0;
private circuitTripped: boolean = false;
constructor() {
this._libraryAvailable = !!createSession;
function sanitizeWreqError(error: unknown, message: string): SafeWreqError {
const sanitized = new Error(message) as SafeWreqError;
if (!error || typeof error !== "object") return sanitized;
if ("code" in error && typeof error.code === "string" && /^[A-Z0-9_:-]{1,64}$/.test(error.code)) {
sanitized.code = error.code;
}
get available(): boolean {
if (!this._libraryAvailable) return false;
if (!this.circuitTripped) return true;
return Date.now() >= this.circuitOpenUntil;
if (
"errorCode" in error &&
typeof error.errorCode === "string" &&
/^[a-zA-Z0-9_:-]{1,64}$/.test(error.errorCode)
) {
sanitized.errorCode = error.errorCode;
}
if (
"statusCode" in error &&
typeof error.statusCode === "number" &&
Number.isFinite(error.statusCode)
) {
sanitized.statusCode = error.statusCode;
}
return sanitized;
}
private recordFailure(): void {
this.failureCount++;
if (this.failureCount >= this.maxFailures) {
this.circuitOpenUntil = Date.now() + this.cooldownMs;
this.circuitTripped = true;
// Close the stale session so the next half-open retry creates a
// fresh one instead of reusing a broken connection.
if (this.session) {
Promise.resolve(this.session.close()).catch(() => {});
this.session = null;
}
console.warn(
`[TlsClient] Circuit opened after ${this.failureCount} consecutive failures, cooling down for ${this.cooldownMs}ms`
function toNativeResponse(
response: WreqResponse,
onFinalize: () => void,
onBodyError: () => void,
signal?: AbortSignal | null
): Response {
let finalized = false;
let bodyFailureReported = false;
let consumerCancelled = false;
let consumerCancelReason: unknown;
const finalize = () => {
if (finalized) return;
finalized = true;
onFinalize();
};
const safeBodyError = (error: unknown): unknown => {
if (signal?.aborted) {
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
}
if (consumerCancelled) {
return (
consumerCancelReason ?? new DOMException("The response body was cancelled", "AbortError")
);
// Double cooldown for the next trip: 30s → 60s → 120s → ... → 10 min max
this.escalateCooldown();
}
}
private recordSuccess(): void {
this.failureCount = 0;
if (this.circuitTripped) {
this.cooldownMultiplier = 1;
this.cooldownMs = this.baseCooldownMs;
console.log("[TlsClient] Circuit closed (success after cooldown)");
this.circuitTripped = false;
if (!bodyFailureReported) {
bodyFailureReported = true;
onBodyError();
}
return sanitizeWreqError(error, "wreq-js response body failed");
};
if (response instanceof Response) {
finalize();
return response;
}
private escalateCooldown(): void {
this.cooldownMultiplier = Math.min(this.cooldownMultiplier * 2, 20);
this.cooldownMs = Math.min(this.baseCooldownMs * this.cooldownMultiplier, this.MAX_COOLDOWN_MS);
try {
const headers = new Headers();
for (const [name, value] of response.headers) headers.append(name, value);
let body: ReadableStream<Uint8Array> | null = null;
if (response.body) {
const reader = response.body.getReader();
body = new ReadableStream<Uint8Array>({
async pull(controller) {
try {
const chunk = await reader.read();
if (chunk.done) {
finalize();
controller.close();
} else {
controller.enqueue(chunk.value);
}
} catch (error) {
controller.error(safeBodyError(error));
finalize();
}
},
async cancel(reason) {
consumerCancelled = true;
consumerCancelReason = reason;
try {
await reader.cancel(reason);
} catch (error) {
throw safeBodyError(error);
} finally {
finalize();
}
},
});
} else {
finalize();
}
const adapted = new Response(body, {
status: response.status,
statusText: response.statusText,
headers,
});
if (response.url) {
Object.defineProperty(adapted, "url", { value: response.url, configurable: true });
}
if (response.redirected !== undefined) {
Object.defineProperty(adapted, "redirected", {
value: response.redirected,
configurable: true,
});
}
return adapted;
} catch (error) {
finalize();
throw error;
}
}
/**
* TLS Client — Chrome 124 TLS fingerprint spoofing via wreq-js.
* Sessions, cookie jars, and circuit state are isolated by account scope and exact proxy.
*/
export class TlsClient {
private readonly createSessionFn: CreateSessionFn | null;
private readonly sessions = new Map<string, WreqSession>();
private readonly pendingSessions = new Map<string, Promise<WreqSession>>();
private readonly pendingCloses = new Set<Promise<void>>();
private readonly sessionEpochs = new Map<string, number>();
private readonly sessionUseCounts = new Map<string, number>();
private readonly sessionLastUsed = new Map<string, number>();
private readonly pendingEvictions = new Set<string>();
private accessSequence = 0;
private readonly circuits = new Map<
string,
{
failureCount: number;
cooldownMs: number;
cooldownMultiplier: number;
circuitOpenUntil: number;
circuitTripped: boolean;
halfOpenInFlight: boolean;
sessionHadCookies: boolean;
}
>();
private globalSessionEpoch = 0;
private readonly maxFailures = 3;
private readonly baseCooldownMs = 30_000;
private readonly maxCooldownMs = 600_000;
private readonly legacySessionScope = "legacy";
private readonly _libraryAvailable: boolean;
private readonly maxSessions: number;
constructor(
createSessionFn: CreateSessionFn | null = createSession,
maxSessions = 128
) {
this.createSessionFn = createSessionFn;
this._libraryAvailable = !!createSessionFn;
this.maxSessions =
Number.isInteger(maxSessions) && maxSessions > 0 ? maxSessions : 128;
}
private checkCircuit(): boolean {
if (!this.circuitTripped) return true;
/** Library availability only. Per-session circuit state is enforced inside fetch(). */
get available(): boolean {
return this._libraryAvailable;
}
if (Date.now() >= this.circuitOpenUntil) {
console.log("[TlsClient] Half-open: retrying after cooldown");
// Don't call recordSuccess() here — that would reset failureCount.
// Instead, let the fetch() call succeed or fail naturally.
// If it succeeds, recordSuccess() in fetch() handles cleanup.
// If it fails, recordFailure() finds failureCount still >= maxFailures
// and re-opens with escalated cooldown.
private resolveProxy(proxy?: string | null): string | null {
return proxy === undefined ? (getProxyFromEnv() ?? null) : proxy;
}
private getSessionKey(resolvedProxy: string | null, sessionScope?: string): string {
const scope = sessionScope?.trim() || this.legacySessionScope;
return createHash("sha256")
.update(scope)
.update("\0")
.update(resolvedProxy ?? "")
.digest("base64url");
}
private getDefaultSessionKey(): string {
return this.getSessionKey(this.resolveProxy(undefined), this.legacySessionScope);
}
private getSessionEpoch(key: string): number {
return this.sessionEpochs.get(key) ?? 0;
}
private hasSessionCookies(session: WreqSession | null, url: string): boolean {
if (!session) return false;
if (!session.getCookies) return true;
try {
return Object.keys(session.getCookies(url)).length > 0;
} catch {
// If cookie state cannot be inspected, fail closed and forbid replay.
return true;
}
return false;
}
async getSession() {
if (!this.checkCircuit()) return null;
if (!this.available) return null;
if (this.session) return this.session;
const createSessionFn = createSession;
if (!createSessionFn) return null;
private closeSession(session: WreqSession): Promise<void> {
let closing: Promise<void>;
closing = Promise.resolve()
.then(() => session.close())
.catch(() => {})
.finally(() => {
this.pendingCloses.delete(closing);
});
this.pendingCloses.add(closing);
return closing;
}
private findOldestIdleSession(protectedKey?: string): string | undefined {
let candidate: string | undefined;
let candidateSequence = Number.POSITIVE_INFINITY;
for (const key of this.sessions.keys()) {
if (key === protectedKey || (this.sessionUseCounts.get(key) ?? 0) > 0) continue;
const sequence = this.sessionLastUsed.get(key) ?? 0;
if (sequence < candidateSequence) {
candidate = key;
candidateSequence = sequence;
}
}
return candidate;
}
private reserveSessionCapacity(protectedKey: string): void {
if (
this.pendingSessions.size >= this.maxSessions ||
this.pendingCloses.size >= this.maxSessions
) {
const error = new Error("wreq-js session capacity exhausted") as Error & {
code?: string;
};
error.code = "TLS_SESSION_CAPACITY";
throw error;
}
while (this.sessions.size >= this.maxSessions) {
const candidate = this.findOldestIdleSession(protectedKey);
if (!candidate) {
const error = new Error("wreq-js session capacity exhausted") as Error & {
code?: string;
};
error.code = "TLS_SESSION_CAPACITY";
throw error;
}
void this.invalidateSession(candidate);
}
}
private retainSession(key: string): void {
this.pendingEvictions.delete(key);
this.sessionUseCounts.set(key, (this.sessionUseCounts.get(key) ?? 0) + 1);
this.sessionLastUsed.set(key, ++this.accessSequence);
}
private releaseSession(key: string): void {
const remaining = (this.sessionUseCounts.get(key) ?? 1) - 1;
if (remaining > 0) {
this.sessionUseCounts.set(key, remaining);
return;
}
this.sessionUseCounts.delete(key);
if (this.pendingEvictions.delete(key)) {
void this.invalidateSession(key);
return;
}
this.evictSessionsIfNeeded();
}
private evictSessionsIfNeeded(protectedKey?: string): void {
while (this.sessions.size > this.maxSessions) {
const candidate = this.findOldestIdleSession(protectedKey);
if (candidate) {
void this.invalidateSession(candidate);
continue;
}
let activeCandidate: string | undefined;
let candidateSequence = Number.POSITIVE_INFINITY;
for (const key of this.sessions.keys()) {
if (key === protectedKey || this.pendingEvictions.has(key)) continue;
const sequence = this.sessionLastUsed.get(key) ?? 0;
if (sequence < candidateSequence) {
activeCandidate = key;
candidateSequence = sequence;
}
}
if (activeCandidate) this.pendingEvictions.add(activeCandidate);
return;
}
}
private invalidateSession(key: string): Promise<void> {
const pending = this.pendingSessions.get(key);
const invalidatedEpoch = this.getSessionEpoch(key) + 1;
this.sessionEpochs.set(key, invalidatedEpoch);
this.pendingSessions.delete(key);
this.sessionUseCounts.delete(key);
this.sessionLastUsed.delete(key);
this.pendingEvictions.delete(key);
const session = this.sessions.get(key);
this.sessions.delete(key);
if (pending) {
void pending
.finally(() => {
if (
this.getSessionEpoch(key) === invalidatedEpoch &&
!this.pendingSessions.has(key) &&
!this.sessions.has(key)
) {
this.sessionEpochs.delete(key);
}
})
.catch(() => {});
} else {
this.sessionEpochs.delete(key);
}
return session ? this.closeSession(session) : Promise.resolve();
}
private async closeSessions(): Promise<void> {
const pending = [...this.pendingSessions.values()];
this.globalSessionEpoch++;
this.pendingSessions.clear();
this.sessionEpochs.clear();
const sessions = [...this.sessions.values()];
this.sessions.clear();
this.sessionUseCounts.clear();
this.sessionLastUsed.clear();
this.pendingEvictions.clear();
this.circuits.clear();
const closes = sessions.map((session) => this.closeSession(session));
await Promise.allSettled([...closes, ...pending]);
await Promise.allSettled([...this.pendingCloses]);
}
private checkCircuit(key = this.getDefaultSessionKey()): boolean {
const state = this.circuits.get(key);
if (!state || !state.circuitTripped) return true;
if (Date.now() < state.circuitOpenUntil) return false;
if (state.halfOpenInFlight) return false;
state.halfOpenInFlight = true;
console.log("[TlsClient] Half-open: retrying after cooldown");
return true;
}
private recordFailure(
key = this.getDefaultSessionKey(),
sessionHadCookies = false
): void {
const state = this.circuits.get(key) ?? {
failureCount: 0,
cooldownMs: this.baseCooldownMs,
cooldownMultiplier: 1,
circuitOpenUntil: 0,
circuitTripped: false,
halfOpenInFlight: false,
sessionHadCookies: false,
};
state.sessionHadCookies ||= sessionHadCookies;
state.failureCount++;
state.halfOpenInFlight = false;
if (state.failureCount >= this.maxFailures) {
state.circuitOpenUntil = Date.now() + state.cooldownMs;
state.circuitTripped = true;
if ((this.sessionUseCounts.get(key) ?? 0) > 0) {
this.pendingEvictions.add(key);
} else {
void this.invalidateSession(key);
}
console.warn(
`[TlsClient] Circuit opened after ${state.failureCount} consecutive failures, cooling down for ${state.cooldownMs}ms`
);
state.cooldownMultiplier = Math.min(state.cooldownMultiplier * 2, 20);
state.cooldownMs = Math.min(
this.baseCooldownMs * state.cooldownMultiplier,
this.maxCooldownMs
);
}
this.circuits.delete(key);
this.circuits.set(key, state);
const maxCircuitEntries = this.maxSessions * 2;
while (this.circuits.size > maxCircuitEntries) {
const oldestKey = this.circuits.keys().next().value;
if (typeof oldestKey !== "string") break;
this.circuits.delete(oldestKey);
}
}
private recordSuccess(key = this.getDefaultSessionKey()): void {
const state = this.circuits.get(key);
if (state?.circuitTripped) {
console.log("[TlsClient] Circuit closed (success after cooldown)");
}
this.circuits.delete(key);
}
private releaseHalfOpen(key: string): void {
const state = this.circuits.get(key);
if (state) state.halfOpenInFlight = false;
}
private async getSession(
resolvedProxy: string | null,
key: string
): Promise<WreqSession | null> {
const cached = this.sessions.get(key);
if (cached) {
this.pendingEvictions.delete(key);
this.sessionLastUsed.set(key, ++this.accessSequence);
return cached;
}
const pending = this.pendingSessions.get(key);
if (pending) return pending;
if (!this.createSessionFn) return null;
this.reserveSessionCapacity(key);
const proxy = getProxyFromEnv();
const sessionOpts: Record<string, unknown> = {
browser: "chrome_124",
os: "macos",
};
if (proxy) {
sessionOpts.proxy = proxy;
console.log(`[TlsClient] Using proxy: ${proxy}`);
}
if (resolvedProxy) sessionOpts.proxy = resolvedProxy;
const globalEpoch = this.globalSessionEpoch;
const sessionEpoch = this.getSessionEpoch(key);
this.session = await createSessionFn(sessionOpts);
console.log("[TlsClient] Session created (Chrome 124 TLS fingerprint)");
return this.session;
const creating = Reflect.apply(this.createSessionFn, undefined, [sessionOpts])
.then(async (session) => {
if (
globalEpoch !== this.globalSessionEpoch ||
sessionEpoch !== this.getSessionEpoch(key)
) {
await this.closeSession(session);
throw new Error("wreq-js session invalidated");
}
if (this.sessions.size >= this.maxSessions) {
const candidate = this.findOldestIdleSession(key);
if (!candidate) {
await this.closeSession(session);
const error = new Error("wreq-js session capacity exhausted") as Error & {
code?: string;
};
error.code = "TLS_SESSION_CAPACITY";
throw error;
}
void this.invalidateSession(candidate);
}
this.sessions.set(key, session);
this.sessionLastUsed.set(key, ++this.accessSequence);
this.evictSessionsIfNeeded(key);
console.log("[TlsClient] Session created (Chrome 124 TLS fingerprint)");
return session;
})
.finally(() => {
if (this.pendingSessions.get(key) === creating) {
this.pendingSessions.delete(key);
this.sessionEpochs.delete(key);
}
});
this.pendingSessions.set(key, creating);
return creating;
}
/**
* Fetch with Chrome 124 TLS fingerprint.
* wreq-js Response is already fetch-compatible (headers, text(), json(), clone(), body).
*/
async fetch(url: string, options: FetchOptions = {}) {
if (!this.checkCircuit()) {
throw new Error("wreq-js circuit open — skipping TLS request");
/** Fetch with Chrome 124 TLS fingerprint and an account-scoped persistent cookie jar. */
async fetch(url: string, options: TlsFetchOptions = {}): Promise<Response> {
const resolvedProxy = this.resolveProxy(options.proxy);
const key = this.getSessionKey(resolvedProxy, options.sessionScope);
if (!this.checkCircuit(key)) {
const state = this.circuits.get(key);
const error = new Error("wreq-js circuit open — skipping TLS request") as Error & {
code?: string;
};
error.code = "TLS_CIRCUIT_OPEN";
if (state?.sessionHadCookies) {
Object.defineProperty(error, "sessionHadCookies", {
value: true,
configurable: true,
});
}
throw error;
}
let session: WreqSession | null = null;
let sessionUseRetained = false;
const releaseSession = () => {
if (!sessionUseRetained) return;
sessionUseRetained = false;
this.releaseSession(key);
};
try {
const session = await this.getSession();
session = await this.getSession(resolvedProxy, key);
if (!session) throw new Error("wreq-js not available");
this.retainSession(key);
sessionUseRetained = true;
const { timeoutMs } = getTlsClientTimeoutConfig(process.env, (message) => {
console.warn(`[TlsClient] ${message}`);
});
const method = (options.method || "GET").toUpperCase();
const wreqOptions: Record<string, unknown> = {
method,
method: (options.method || "GET").toUpperCase(),
headers: normalizeHeaders(options.headers),
body: options.body,
redirect: options.redirect === "manual" ? "manual" : "follow",
redirect: options.redirect ?? "follow",
timeout: timeoutMs,
};
if (options.signal) wreqOptions.signal = options.signal;
if (options.signal) {
wreqOptions.signal = options.signal;
}
const response = await session.fetch(url, wreqOptions);
this.recordSuccess();
const response = toNativeResponse(
await session.fetch(url, wreqOptions),
releaseSession,
() => this.recordFailure(key, this.hasSessionCookies(session, url)),
options.signal
);
this.recordSuccess(key);
return response;
} catch (err) {
const isAbort =
err instanceof Error && (err.name === "AbortError" || err.message.includes("aborted"));
if (!isAbort) {
this.recordFailure();
const isCallerAbort = options.signal?.aborted === true;
const sessionHadCookies =
!isCallerAbort && this.hasSessionCookies(session, url);
releaseSession();
if (isCallerAbort) {
this.releaseHalfOpen(key);
} else {
this.recordFailure(key, sessionHadCookies);
}
throw err;
if (isCallerAbort) throw err;
const transportError = sanitizeWreqError(err, "wreq-js transport failed");
if (sessionHadCookies) {
Object.defineProperty(transportError, "sessionHadCookies", {
value: true,
configurable: true,
});
}
throw transportError;
}
}
async exit() {
if (this.session) {
await this.session.close();
this.session = null;
async exit(): Promise<void> {
await this.closeSessions();
}
resetCircuit(proxy?: string | null, sessionScope?: string): void {
if (arguments.length === 0) {
this.circuits.clear();
return;
}
const resolvedProxy = this.resolveProxy(proxy);
this.circuits.delete(this.getSessionKey(resolvedProxy, sessionScope));
}
resetCircuit(): void {
this.failureCount = 0;
this.circuitTripped = false;
this.circuitOpenUntil = 0;
}
getCircuitState(): {
getCircuitState(
proxy?: string | null,
sessionScope?: string
): {
available: boolean;
circuitTripped: boolean;
failureCount: number;
circuitOpenUntil: number;
coolDownRemainingMs: number;
} {
const resolvedProxy = this.resolveProxy(proxy);
const key = this.getSessionKey(resolvedProxy, sessionScope);
const state = this.circuits.get(key);
const circuitOpenUntil = state?.circuitOpenUntil ?? 0;
const circuitTripped = state?.circuitTripped ?? false;
return {
available: this.available,
circuitTripped: this.circuitTripped,
failureCount: this.failureCount,
circuitOpenUntil: this.circuitOpenUntil,
available:
this._libraryAvailable &&
(!circuitTripped || Date.now() >= circuitOpenUntil),
circuitTripped,
failureCount: state?.failureCount ?? 0,
circuitOpenUntil,
coolDownRemainingMs:
this.circuitOpenUntil > 0 ? Math.max(0, this.circuitOpenUntil - Date.now()) : 0,
circuitOpenUntil > 0 ? Math.max(0, circuitOpenUntil - Date.now()) : 0,
};
}
}
const tlsClient = new TlsClient();
const TLS_CLIENT_KEY = Symbol.for("omniroute.tlsClient.instance");
const scopedGlobal = globalThis as typeof globalThis & {
[TLS_CLIENT_KEY]?: TlsClient;
};
const tlsClient = scopedGlobal[TLS_CLIENT_KEY] ?? new TlsClient();
scopedGlobal[TLS_CLIENT_KEY] = tlsClient;
export default tlsClient;

337
package-lock.json generated
View File

@@ -17,6 +17,7 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@huggingface/transformers": "^4.2.0",
"@lobehub/icons": "^5.8.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"@monaco-editor/react": "^4.7.0",
@@ -59,6 +60,7 @@
"next-themes": "^0.4.6",
"node-machine-id": "^1.1.12",
"omniglyph": "^1.0.2",
"onnxruntime-node": "~1.24.3",
"open": "^11.0.0",
"ora": "^9.4.1",
"parse5": "^8.0.1",
@@ -152,7 +154,6 @@
},
"optionalDependencies": {
"@atjsh/llmlingua-2": "2.0.3",
"@huggingface/transformers": "3.5.2",
"@tensorflow/tfjs": "4.22.0",
"better-sqlite3": "^13.0.2",
"js-tiktoken": "^1.0.20",
@@ -3443,11 +3444,10 @@
}
},
"node_modules/@huggingface/jinja": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.4.1.tgz",
"integrity": "sha512-3WXbMFaPkk03LRCM0z0sylmn8ddDm4ubjU7X+Hg4M2GOuMklwoGAFXp9V2keq7vltoB/c7McE5aHUVVddAewsw==",
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.9.tgz",
"integrity": "sha512-uWTG+l3VJRsl7EXxYizuL3P+cCPoc3cRqbWWRcQN0FhejRfbdq0RNhCmbY/YDtnTcz9icdLYuLDjsnz4d8JMuw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=18"
}
@@ -3456,21 +3456,19 @@
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@huggingface/tokenizers/-/tokenizers-0.1.3.tgz",
"integrity": "sha512-8rF/RRT10u+kn7YuUbUg0OF30K8rjTc78aHpxT+qJ1uWSqxT1MHi8+9ltwYfkFYJzT/oS+qw3JVfHtNMGAdqyA==",
"dev": true,
"license": "Apache-2.0",
"optional": true
"license": "Apache-2.0"
},
"node_modules/@huggingface/transformers": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-3.5.2.tgz",
"integrity": "sha512-mfRXkmcL99+ibpjM++pvZmc2h3po8i1ZgSRI5Rtgh++P15GU0lY8UQteYt/w5V+GQw+Jpao93MoipcePzh3mKg==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-4.2.0.tgz",
"integrity": "sha512-8BRCoBMH0XsWaEIamuR0LrJGAfftgHAfb2Vrffy0VKlSAE/MnUJ5/h/zTfEP3fDIft+nk7TqB8xXEyABGitBjQ==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@huggingface/jinja": "^0.4.1",
"onnxruntime-node": "1.21.0",
"onnxruntime-web": "1.22.0-dev.20250409-89f8206ba4",
"sharp": "^0.34.1"
"@huggingface/jinja": "^0.5.6",
"@huggingface/tokenizers": "^0.1.3",
"onnxruntime-node": "1.24.3",
"onnxruntime-web": "1.26.0-dev.20260416-b7804b056c",
"sharp": "^0.34.5"
}
},
"node_modules/@humanfs/core": {
@@ -3694,9 +3692,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3713,9 +3708,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3732,9 +3724,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3751,9 +3740,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3770,9 +3756,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3789,9 +3772,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3808,9 +3788,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3827,9 +3804,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3846,9 +3820,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3871,9 +3842,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3896,9 +3864,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3921,9 +3886,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3946,9 +3908,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3971,9 +3930,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3996,9 +3952,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -4021,9 +3974,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -4569,7 +4519,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"devOptional": true,
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
@@ -5395,9 +5345,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5414,9 +5361,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5433,9 +5377,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5452,9 +5393,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -8332,35 +8270,30 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
"devOptional": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1"
@@ -8370,28 +8303,24 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@radix-ui/number": {
@@ -10672,9 +10601,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10692,9 +10618,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10712,9 +10635,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10732,9 +10652,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -11685,7 +11602,6 @@
"version": "26.1.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
@@ -12781,9 +12697,6 @@
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"optional": true,
"os": [
"linux"
@@ -12797,9 +12710,6 @@
"arm"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -12813,9 +12723,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"optional": true,
"os": [
"linux"
@@ -12829,9 +12736,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -12845,9 +12749,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"optional": true,
"os": [
"linux"
@@ -12861,9 +12762,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -12971,9 +12869,7 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz",
"integrity": "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14.0"
}
@@ -13946,8 +13842,7 @@
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/bottleneck": {
"version": "2.19.5",
@@ -16888,7 +16783,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
@@ -16918,7 +16812,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
@@ -17019,8 +16912,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/detect-node-es": {
"version": "1.1.0",
@@ -17864,8 +17756,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/es6-promisify": {
"version": "7.0.0",
@@ -19357,8 +19248,7 @@
"version": "25.9.23",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz",
"integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==",
"license": "Apache-2.0",
"optional": true
"license": "Apache-2.0"
},
"node_modules/flatted": {
"version": "3.4.2",
@@ -20177,7 +20067,6 @@
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"es6-error": "^4.1.1",
@@ -20191,11 +20080,10 @@
}
},
"node_modules/global-agent/node_modules/semver": {
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
"integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
"optional": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -20244,7 +20132,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"define-properties": "^1.2.1",
@@ -20599,8 +20486,7 @@
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
"license": "ISC",
"optional": true
"license": "ISC"
},
"node_modules/hachure-fill": {
"version": "0.5.2",
@@ -20634,7 +20520,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
@@ -23739,8 +23624,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"license": "ISC",
"optional": true
"license": "ISC"
},
"node_modules/json5": {
"version": "2.2.3",
@@ -24490,6 +24374,17 @@
"node": ">= 14"
}
},
"node_modules/libxmljs2/node_modules/brace-expansion": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/libxmljs2/node_modules/cacache": {
"version": "19.0.1",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz",
@@ -25669,7 +25564,6 @@
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"license": "MIT",
"optional": true,
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
@@ -26984,7 +26878,7 @@
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"devOptional": true,
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -27104,7 +26998,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
@@ -28500,7 +28394,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -28705,41 +28598,38 @@
}
},
"node_modules/onnxruntime-common": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.21.0.tgz",
"integrity": "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==",
"license": "MIT",
"optional": true
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
"integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==",
"license": "MIT"
},
"node_modules/onnxruntime-node": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.21.0.tgz",
"integrity": "sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==",
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.24.3.tgz",
"integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"win32",
"darwin",
"linux"
],
"dependencies": {
"adm-zip": "^0.5.16",
"global-agent": "^3.0.0",
"onnxruntime-common": "1.21.0",
"tar": "^7.0.1"
"onnxruntime-common": "1.24.3"
}
},
"node_modules/onnxruntime-web": {
"version": "1.22.0-dev.20250409-89f8206ba4",
"resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.22.0-dev.20250409-89f8206ba4.tgz",
"integrity": "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==",
"version": "1.26.0-dev.20260416-b7804b056c",
"resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.26.0-dev.20260416-b7804b056c.tgz",
"integrity": "sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==",
"license": "MIT",
"optional": true,
"dependencies": {
"flatbuffers": "^25.1.24",
"guid-typescript": "^1.0.9",
"long": "^5.2.3",
"onnxruntime-common": "1.22.0-dev.20250409-89f8206ba4",
"onnxruntime-common": "1.24.0-dev.20251116-b39e144322",
"platform": "^1.3.6",
"protobufjs": "^7.2.4"
}
@@ -28748,15 +28638,13 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0",
"optional": true
"license": "Apache-2.0"
},
"node_modules/onnxruntime-web/node_modules/onnxruntime-common": {
"version": "1.22.0-dev.20250409-89f8206ba4",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.22.0-dev.20250409-89f8206ba4.tgz",
"integrity": "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ==",
"license": "MIT",
"optional": true
"version": "1.24.0-dev.20251116-b39e144322",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.0-dev.20251116-b39e144322.tgz",
"integrity": "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==",
"license": "MIT"
},
"node_modules/open": {
"version": "11.0.0",
@@ -28903,9 +28791,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -28945,9 +28830,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -28961,9 +28843,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -29997,8 +29876,7 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/playwright": {
"version": "1.62.0",
@@ -30565,32 +30443,6 @@
"sharp": "^0.35.3"
}
},
"node_modules/promptfoo/node_modules/@huggingface/jinja": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.9.tgz",
"integrity": "sha512-uWTG+l3VJRsl7EXxYizuL3P+cCPoc3cRqbWWRcQN0FhejRfbdq0RNhCmbY/YDtnTcz9icdLYuLDjsnz4d8JMuw==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=18"
}
},
"node_modules/promptfoo/node_modules/@huggingface/transformers": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-4.2.0.tgz",
"integrity": "sha512-8BRCoBMH0XsWaEIamuR0LrJGAfftgHAfb2Vrffy0VKlSAE/MnUJ5/h/zTfEP3fDIft+nk7TqB8xXEyABGitBjQ==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@huggingface/jinja": "^0.5.6",
"@huggingface/tokenizers": "^0.1.3",
"onnxruntime-node": "1.24.3",
"onnxruntime-web": "1.26.0-dev.20260416-b7804b056c",
"sharp": "^0.34.5"
}
},
"node_modules/promptfoo/node_modules/chalk": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
@@ -30704,14 +30556,6 @@
"@keyv/serialize": "^1.1.1"
}
},
"node_modules/promptfoo/node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"dev": true,
"license": "Apache-2.0",
"optional": true
},
"node_modules/promptfoo/node_modules/lru-cache": {
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
@@ -30755,57 +30599,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/promptfoo/node_modules/onnxruntime-common": {
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
"integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/promptfoo/node_modules/onnxruntime-node": {
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.24.3.tgz",
"integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"win32",
"darwin",
"linux"
],
"dependencies": {
"adm-zip": "^0.5.16",
"global-agent": "^3.0.0",
"onnxruntime-common": "1.24.3"
}
},
"node_modules/promptfoo/node_modules/onnxruntime-web": {
"version": "1.26.0-dev.20260416-b7804b056c",
"resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.26.0-dev.20260416-b7804b056c.tgz",
"integrity": "sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"flatbuffers": "^25.1.24",
"guid-typescript": "^1.0.9",
"long": "^5.2.3",
"onnxruntime-common": "1.24.0-dev.20251116-b39e144322",
"platform": "^1.3.6",
"protobufjs": "^7.2.4"
}
},
"node_modules/promptfoo/node_modules/onnxruntime-web/node_modules/onnxruntime-common": {
"version": "1.24.0-dev.20251116-b39e144322",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.0-dev.20251116-b39e144322.tgz",
"integrity": "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/promptfoo/node_modules/path-key": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
@@ -30894,7 +30687,6 @@
"version": "7.6.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
"integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
"devOptional": true,
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -30918,7 +30710,6 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/proxy-addr": {
@@ -32321,7 +32112,6 @@
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"detect-node": "^2.0.4",
@@ -32338,8 +32128,7 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause",
"optional": true
"license": "BSD-3-Clause"
},
"node_modules/robot3": {
"version": "0.4.1",
@@ -32704,8 +32493,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/send": {
"version": "1.2.1",
@@ -32738,7 +32526,6 @@
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"license": "MIT",
"optional": true,
"dependencies": {
"type-fest": "^0.13.1"
},
@@ -32754,7 +32541,6 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"license": "(MIT OR CC0-1.0)",
"optional": true,
"engines": {
"node": ">=10"
},
@@ -34322,7 +34108,7 @@
"version": "7.5.22",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz",
"integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==",
"devOptional": true,
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
@@ -34369,7 +34155,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"devOptional": true,
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
@@ -34379,7 +34165,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"devOptional": true,
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
@@ -35230,7 +35016,6 @@
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
"devOptional": true,
"license": "MIT"
},
"node_modules/unicode-emoji-modifier-base": {

View File

@@ -326,11 +326,12 @@
"xxhash-wasm": "^1.1.0",
"yazl": "^3.3.1",
"zod": "^4.4.3",
"zustand": "^5.0.13"
"zustand": "^5.0.13",
"@huggingface/transformers": "^4.2.0",
"onnxruntime-node": "~1.24.3"
},
"optionalDependencies": {
"@atjsh/llmlingua-2": "2.0.3",
"@huggingface/transformers": "3.5.2",
"@tensorflow/tfjs": "4.22.0",
"better-sqlite3": "^13.0.2",
"js-tiktoken": "^1.0.20",

View File

@@ -10,7 +10,7 @@
* .next/standalone -> outDir (cp) Y Y Y SHARED
* .next/static -> outDir/.next/static (cp) Y Y Y SHARED
* public/ -> outDir/public/ (cp) Y Y Y SHARED
* wreq-js/rust -> outDir/node_modules/wreq-js/rust Y - - SHARED (native asset)
* wreq-js -> outDir/node_modules/wreq-js Y Y Y SHARED (extra module)
* better-sqlite3/build -> outDir/node_modules/better-sqlite3/ Y - - SHARED (native asset)
* @swc/helpers -> outDir/node_modules/@swc/helpers Y Y Y SHARED (extra module)
* pino-abstract-transport -> outDir/node_modules/... Y - - SHARED (extra module)
@@ -76,11 +76,6 @@ async function exists(targetPath) {
* for either path/platform. @type {{label:string, src:string[], dest:string[]}[]}
*/
export const NATIVE_ASSET_ENTRIES = [
{
label: "wreq-js native runtime",
src: ["node_modules", "wreq-js", "rust"],
dest: ["node_modules", "wreq-js", "rust"],
},
{
label: "better-sqlite3 native binary",
src: ["node_modules", "better-sqlite3", "build"],
@@ -117,6 +112,15 @@ export const NATIVE_ASSET_ENTRIES = [
/** @type {{label:string, src:string[], dest:string[]}[]} */
const EXTRA_MODULE_ENTRIES = [
{
// tlsClient.ts intentionally resolves wreq-js through a runtime-dynamic
// require so Turbopack cannot rewrite the package name to a hashed external.
// That also makes the package invisible to static tracing, so copy the whole
// module—not only rust/—into every standalone artifact.
label: "wreq-js TLS runtime",
src: ["node_modules", "wreq-js"],
dest: ["node_modules", "wreq-js"],
},
{
label: "@swc/helpers",
src: ["node_modules", "@swc", "helpers"],
@@ -252,6 +256,16 @@ const EXTRA_MODULE_ENTRIES = [
src: ["node_modules", "undici"],
dest: ["node_modules", "undici"],
},
{
// Turbopack's standalone tracer can emit a hollow node_modules/ws/ directory
// for the externalized `ws` package (no package.json / index.js), which then
// shadows the real install at runtime and crashes instrumentation with:
// "Cannot find package '<bundle>/node_modules/ws/index.js'" (#OmniRoute v3.8.50 live bug).
// Overlay the full source package so the bundled server resolves the real entrypoint.
label: "ws (externalized runtime package shadow fix)",
src: ["node_modules", "ws"],
dest: ["node_modules", "ws"],
},
{
label: "sql.js WASM fallback runtime",
src: ["node_modules", "sql.js"],
@@ -278,7 +292,7 @@ const EXTRA_MODULE_ENTRIES = [
];
/**
* Copy native standalone assets (wreq-js rust/, better-sqlite3 build/).
* Copy native standalone assets (better-sqlite3 build/prebuilds and TPROXY).
*
* The destination is derived as <rootDir>/<distDir>/standalone/node_modules/...
* for backward compatibility with existing callers and tests.
@@ -512,8 +526,8 @@ function copyStaticAndPublic({ distDir, relDistDir, projectRoot, resolvedOutDir
}
/**
* Copy native assets (wreq-js, better-sqlite3) and extra runtime modules/sidecars
* (pino, migrations, MITM server, helper scripts, sqlite-vec platform packages, …)
* Copy native assets (better-sqlite3 and TPROXY) and extra runtime modules/sidecars
* (wreq-js, pino, migrations, MITM server, helper scripts, sqlite-vec platform packages, …)
* into the assembled bundle. Missing sources are skipped silently.
*
* @param {string} projectRoot
@@ -539,6 +553,68 @@ function copyNativeAssetsAndExtraModules(projectRoot, resolvedOutDir) {
}
}
/**
* Next/Turbopack standalone output can leave behind hollow top-level package
* directories for externalized runtime deps (directory exists, but contains no
* files). Those empty placeholders shadow the real repo-level install and make
* runtime ESM externals fail with "Cannot find package '<bundle>/node_modules/<pkg>/index.js'"
* even though the dependency is present in the source tree.
*
* Repair strategy: for each empty top-level package dir already present in the
* assembled bundle, if the same package exists in the project root node_modules,
* replace the hollow directory with a full recursive copy from the source install.
* This keeps the fix narrowly scoped to packages the standalone already expects.
*
* @param {string} projectRoot
* @param {string} resolvedOutDir
* @returns {{repaired: number, packages: string[]}}
*/
function repairEmptyExternalPackageDirs(projectRoot, resolvedOutDir) {
const summary = { repaired: 0, packages: [] };
const bundleNodeModules = path.join(resolvedOutDir, "node_modules");
const sourceNodeModules = path.join(projectRoot, "node_modules");
if (!fsSync.existsSync(bundleNodeModules) || !fsSync.existsSync(sourceNodeModules)) {
return summary;
}
for (const name of fsSync.readdirSync(bundleNodeModules)) {
if (name.startsWith(".") || name.startsWith("@")) continue;
const bundlePkgDir = path.join(bundleNodeModules, name);
const sourcePkgDir = path.join(sourceNodeModules, name);
let bundleStat;
try {
bundleStat = fsSync.statSync(bundlePkgDir);
} catch {
continue;
}
if (!bundleStat.isDirectory()) continue;
let bundleEntries = [];
try {
bundleEntries = fsSync.readdirSync(bundlePkgDir);
} catch {
continue;
}
if (bundleEntries.length > 0 || !fsSync.existsSync(sourcePkgDir)) continue;
let sourceStat;
try {
sourceStat = fsSync.statSync(sourcePkgDir);
} catch {
continue;
}
if (!sourceStat.isDirectory()) continue;
fsSync.cpSync(sourcePkgDir, bundlePkgDir, { recursive: true, force: true });
summary.repaired += 1;
summary.packages.push(name);
}
return summary;
}
/**
* Materialize Turbopack "hashed external module" symlinks inside a bundled
* node_modules dir into real, self-contained directories.
@@ -755,6 +831,13 @@ export function assembleStandalone({
// 6. Optionally copy native assets + extra modules (synchronous)
if (copyNatives) {
copyNativeAssetsAndExtraModules(projectRoot, resolvedOutDir);
const emptyPkgRepair = repairEmptyExternalPackageDirs(projectRoot, resolvedOutDir);
if (emptyPkgRepair.repaired > 0) {
console.log(
`[assembleStandalone] Repaired ${emptyPkgRepair.repaired} hollow external package dir(s): ` +
emptyPkgRepair.packages.join(", ")
);
}
// #9166: dynamically imported LLMLingua packages are not reliably traced
// into the standalone bundle. Copy their complete dependency closure from

View File

@@ -327,7 +327,12 @@ export async function main() {
distDir,
outDir: standaloneDir,
projectRoot,
// Match the hardened packaging path used by Electron builds:
// Turbopack can emit hashed external-package references and
// standalone symlinks that break after the bundle is moved/copied.
patchTurbopackChunks: true,
copyNatives: true,
materializeSymlinks: true,
});
const { spawnSync } = await import("node:child_process");
const basePathWrite = spawnSync(

View File

@@ -22,8 +22,11 @@ import {
getCompatibleFallbackModels,
} from "@/lib/providers/managedAvailableModels";
import { getProviderServiceKinds } from "@/lib/providers/serviceKindIndex";
import { providerLacksModelListing } from "@/lib/providers/modelListingCapability";
import { providerUsesCuratedModelsOnly } from "@/lib/providers/modelListingCapability";
import {
providerLacksModelListing,
providerUsesCuratedModelsOnly,
} from "@/lib/providers/modelListingCapability";
import { mergeProviderModelListing } from "@/lib/providers/mergeProviderModelListing";
import { normalizeModelCatalogSource } from "@/shared/utils/modelCatalogSearch";
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
import useEmailPrivacyStore from "@/store/emailPrivacyStore";
@@ -270,39 +273,29 @@ export default function ProviderDetailPageClient() {
getProviderById(providerId)?.managedAccount === true;
const registryModels = getModelsByProviderId(providerId);
// Prefer synced API-discovered models when available, then merge built-ins
// and user-managed custom models without duplicating IDs.
// and user-managed custom models without duplicating IDs. Cursor exclusive
// listing drops the static registry entirely when synced is non-empty.
const models = useMemo(() => {
// Synced models keep their full property spread so provider-specific fields
// (e.g. Gemini's `supportedGenerationMethods`) survive into the table.
const builtInModels = registryModels.map((model) => ({
...model,
source: "system",
}));
const registryIds = new Set(builtInModels.map((m) => m.id));
const syncedExtras = (usesCuratedModelsOnly ? [] : syncedAvailableModels)
.filter((model: any) => model?.id && !registryIds.has(model.id))
.map((model: any) => ({
...model,
id: model.id,
name: model.name || model.id,
source: "imported",
}));
const knownIds = new Set([...registryIds, ...syncedExtras.map((model: any) => model.id)]);
const customExtras = (usesCuratedModelsOnly ? [] : modelMeta.customModels)
.filter((cm: any) => cm.id && !knownIds.has(cm.id))
.map((cm: any) => ({
id: cm.id,
name: cm.name || cm.id,
source: normalizeModelCatalogSource(cm.source) === "imported" ? "imported" : "custom",
}));
const allModels = [...builtInModels, ...syncedExtras, ...customExtras];
const deduped = new Map<string, (typeof allModels)[0]>();
for (const m of allModels) {
if (m.id && !deduped.has(m.id)) deduped.set(m.id, m);
}
return Array.from(deduped.values());
}, [registryModels, syncedAvailableModels, modelMeta.customModels, usesCuratedModelsOnly]);
return mergeProviderModelListing({
providerId,
registryModels,
syncedModels: syncedAvailableModels,
customModels: (modelMeta.customModels || []).map(
(cm: { id: string; name?: string; source?: string }) => ({
id: cm.id,
name: cm.name || cm.id,
source: normalizeModelCatalogSource(cm.source) === "imported" ? "imported" : "custom",
})
),
usesCuratedModelsOnly,
});
}, [
providerId,
registryModels,
syncedAvailableModels,
modelMeta.customModels,
usesCuratedModelsOnly,
]);
const isUpstreamProxyProvider = providerInfo?.category === "upstream-proxy";
const compatibleSupportsModelImport = compatibleProviderSupportsModelImport(providerId);

View File

@@ -2,6 +2,7 @@
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
import { Button, Badge, Input, Modal, Select, Toggle } from "@/shared/components";
import { isValidProviderIconUrl } from "@/shared/validation/iconUrl";
import { CC_COMPATIBLE_DEFAULT_CHAT_PATH } from "../../providerDetailConstants";
import NewApiAggregatorFields from "./NewApiAggregatorFields";
interface EditCompatibleNodeModalNode {
@@ -57,6 +58,7 @@ export default function EditCompatibleNodeModal({
method?: string | null;
}>(null);
const [showAdvanced, setShowAdvanced] = useState(false);
const [iconUrlError, setIconUrlError] = useState<string | null>(null);
useEffect(() => {
if (node) {
@@ -101,6 +103,12 @@ export default function EditCompatibleNodeModal({
const handleSubmit = async () => {
if (!formData.name.trim() || !formData.prefix.trim() || !formData.baseUrl.trim()) return;
const iconUrl = formData.iconUrl.trim();
if (!isValidProviderIconUrl(iconUrl)) {
setIconUrlError(t("iconUrlInvalid"));
return;
}
setIconUrlError(null);
setSaving(true);
try {
const payload: any = {
@@ -247,7 +255,7 @@ export default function EditCompatibleNodeModal({
value={formData.iconUrl}
onChange={(e) => setFormData({ ...formData, iconUrl: e.target.value })}
placeholder="https://example.com/logo.png"
hint={t("iconUrlHint")}
hint={iconUrlError ?? t("iconUrlHint")}
/>
<Toggle
label={t("newApiAggregatorToggleLabel")}

View File

@@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import { Badge, Button, Input, Modal, Select, Toggle } from "@/shared/components";
import { isValidProviderIconUrl } from "@/shared/validation/iconUrl";
import {
CLIENT_IDENTITY_PROFILE_OPTIONS,
getClientIdentityProfileHeaders,
@@ -115,6 +116,7 @@ export default function AddCompatibleProviderModal({
method?: string | null;
}>(null);
const [showAdvanced, setShowAdvanced] = useState(false);
const [iconUrlError, setIconUrlError] = useState<string | null>(null);
const apiTypeOptions = useMemo(
() => [
@@ -188,6 +190,12 @@ export default function AddCompatibleProviderModal({
const handleSubmit = async () => {
if (!hasRequiredFields) return;
const iconUrl = formData.iconUrl.trim();
if (!isValidProviderIconUrl(iconUrl)) {
setIconUrlError(t("iconUrlInvalid"));
return;
}
setIconUrlError(null);
setSubmitting(true);
try {
const body: Record<string, unknown> = {
@@ -328,7 +336,7 @@ export default function AddCompatibleProviderModal({
value={formData.iconUrl}
onChange={(e) => setFormData({ ...formData, iconUrl: e.target.value })}
placeholder="https://example.com/logo.png"
hint={t("iconUrlHint")}
hint={iconUrlError ?? t("iconUrlHint")}
/>
<Toggle

View File

@@ -29,6 +29,7 @@ const PROVIDER_COLUMNS: Record<string, string[]> = {
minimax: ["session"],
"minimax-cn": ["session"],
"kimi-coding": ["session", "weekly"],
"command-code": ["five_hour", "weekly", "credits"],
};
/** Hard cap for the dynamic schema (Antigravity and fallback providers). */

View File

@@ -92,6 +92,7 @@ import {
} from "@/lib/providerModels/geminiModelsParser";
import { getSyncedAvailableModels, getCustomModels } from "@/lib/db/models";
import { fetchCursorAgentModels } from "@/lib/providerModels/cursorAgent";
import { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog";
import { fetchRaycastModels } from "@omniroute/open-sse/services/raycast.ts";
import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts";
import {
@@ -1408,7 +1409,7 @@ export async function GET(
if (autoFetchDisabledResponse) return autoFetchDisabledResponse;
try {
const models = await fetchCursorAgentModels();
const models = ensureCursorAutoCatalogEntry(await fetchCursorAgentModels());
return buildApiDiscoveryResponse(models);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);

View File

@@ -20,6 +20,7 @@ import {
CHAT_ADMISSION_QUEUE_MAX_MS,
releaseChatAdmissionAfterHandler,
releaseChatAdmissionWhenDone,
resolveSessionId,
} from "@/shared/middleware/chatBodyAdmission";
import {
readCompressionRequestHeader,
@@ -101,7 +102,9 @@ export async function POST(request) {
// Reserve heavyweight capacity atomically and ingest the body with a hard byte bound
// BEFORE JSON parsing. Missing or dishonest Content-Length values cannot bypass
// the actual-byte limit. Capacity exhaustion is retryable rather than process-fatal.
const sessionId = resolveSessionId(request);
const admissionResult = await admitChatRequest(request, {
sessionId,
queueMs: CHAT_ADMISSION_QUEUE_MAX_MS,
});
if (admissionResult.admit === false) return admissionResult.response;
@@ -147,7 +150,9 @@ export async function POST(request) {
}
const structuralAdmission = await admitChatStructure(parsedBody, admission.lease, {
sessionId,
queueMs: CHAT_ADMISSION_QUEUE_MAX_MS,
signal: request.signal,
});
if (structuralAdmission.admit === false) {
admission.lease?.release();

View File

@@ -4,6 +4,11 @@ import {
handleImageEdit,
handleOpenAIImageEdit,
} from "@omniroute/open-sse/handlers/imageGeneration.ts";
import {
handleFalAIImageEdit,
FAL_IMAGE_EDIT_MAX_REFERENCES,
isFalImageEditModel,
} from "@omniroute/open-sse/handlers/imageGeneration/providers/fal.ts";
import { createInjectionGuard } from "@/middleware/promptInjectionGuard";
import {
getProviderCredentialsWithQuotaPreflight,
@@ -207,7 +212,8 @@ function buildAdobeFireflyEditDataUrls(
}
}
if (dataUrls.length === 0 && imageBytes && imageBytes.length > 0) {
const mime = typeof imageMime === "string" && imageMime.startsWith("image/") ? imageMime : "image/png";
const mime =
typeof imageMime === "string" && imageMime.startsWith("image/") ? imageMime : "image/png";
dataUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
}
return dataUrls;
@@ -250,7 +256,10 @@ async function handleAdobeFireflyEditRequest(params: {
resolvedModel
);
if (!credentials) {
return errorResponse(HTTP_STATUS.UNAUTHORIZED, `No credentials for provider: ${parsed.provider}`);
return errorResponse(
HTTP_STATUS.UNAUTHORIZED,
`No credentials for provider: ${parsed.provider}`
);
}
if (credentials.allRateLimited) {
return unavailableResponse(
@@ -362,11 +371,10 @@ async function postHandler(request: Request, _context?: unknown) {
? 4
: providerConfig?.format === "codex-responses"
? Number.POSITIVE_INFINITY
: MAX_NON_CODEX_IMAGE_EDIT_REFERENCES;
if (
providerConfig?.format !== "codex-responses" &&
imageInputCount > maxRefsForProvider
) {
: providerConfig?.format === "fal-ai" && isFalImageEditModel(parsed.model)
? FAL_IMAGE_EDIT_MAX_REFERENCES
: MAX_NON_CODEX_IMAGE_EDIT_REFERENCES;
if (providerConfig?.format !== "codex-responses" && imageInputCount > maxRefsForProvider) {
return errorResponse(
HTTP_STATUS.BAD_REQUEST,
providerConfig?.format === "adobe-firefly-image"
@@ -514,6 +522,53 @@ async function postHandler(request: Request, _context?: unknown) {
);
}
if (providerConfig?.format === "fal-ai" && isFalImageEditModel(parsed.model)) {
const credentials = await getProviderCredentialsWithQuotaPreflight(
parsed.provider,
null,
allowedConnections,
resolvedModel
);
if (!credentials) {
return errorResponse(
HTTP_STATUS.UNAUTHORIZED,
`No credentials for provider: ${parsed.provider}`
);
}
if (credentials.allRateLimited) {
return unavailableResponse(
HTTP_STATUS.RATE_LIMITED,
`[${parsed.provider}] All accounts rate limited`,
credentials.retryAfter,
credentials.retryAfterHuman
);
}
const result = await handleFalAIImageEdit({
provider: parsed.provider,
model: parsed.model,
providerConfig,
body: {
prompt,
size: size ?? undefined,
response_format: responseFormat ?? undefined,
n: 1,
},
images,
credentials,
log,
});
if (result.success) {
await clearRecoveredProviderState(credentials);
return jsonResponse(result.data);
}
return jsonResponse(
toJsonErrorPayload(result.error, "Image edit provider error"),
result.status
);
}
// Adobe Firefly: edit = storage upload + generate-async referenceBlobs (same as i2i generate).
if (providerConfig?.format === "adobe-firefly-image") {
return handleAdobeFireflyEditRequest({

View File

@@ -308,10 +308,11 @@ async function postHandler(request, context) {
}
const errorPayload = toJsonErrorPayload((result as any).error, "Image generation provider error");
return new Response(JSON.stringify(errorPayload), {
status: (result as any).status,
headers: { "Content-Type": "application/json" },
});
const message =
typeof errorPayload?.error?.message === "string"
? errorPayload.error.message
: "Image generation provider error";
return errorResponse((result as any).status, message);
}
export const POST = withInjectionGuard(postHandler);

View File

@@ -14,7 +14,7 @@ import { createLazyConnectionView } from "@/lib/db/providers/lazyConnectionView"
import { extractAliasBackedModels } from "./aliasBackedModels";
import {
buildSyncedModelIdsByCanonicalProvider,
shouldSuppressStaticModelBySyncedCoverage,
shouldSuppressStaticModelForExclusiveListing,
} from "./catalogSyncedCoverage";
import { buildSyncedCapabilities, mergeSyncedCapabilities } from "./syncedCapabilities";
import { getAllEmbeddingModels } from "@omniroute/open-sse/config/embeddingRegistry";
@@ -41,7 +41,11 @@ import type { SyncedAvailableModel } from "@/lib/db/models";
import { getAllActiveSyncedModels } from "@/lib/db/models/activeSyncedCatalog";
import { getModelCatalogCacheVersion } from "@/lib/db/readCache";
import { getCompatibleFallbackModels } from "@/lib/providers/managedAvailableModels";
import { providerUsesCuratedModelsOnly } from "@/lib/providers/modelListingCapability";
import {
providerUsesCuratedModelsOnly,
providerUsesExclusiveSyncedListing,
} from "@/lib/providers/modelListingCapability";
import { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog";
import { getOpenRouterCatalog } from "@/lib/catalog/openrouterCatalog";
import { hasEligibleConnectionForModel } from "@/domain/connectionModelRules";
import {
@@ -737,14 +741,22 @@ async function buildUnifiedModelsResponseCore(
// `deepseek/deepseek-v4-flash` which its discovery never lists). Before
// the fix, a provider with any synced model silently dropped ALL its
// static models.
//
// Cursor exclusive listing: when an active synced catalog exists, drop
// ALL static rows (including effort variants) so Test All / clients only
// see live AvailableModels + injected auto*.
const syncedForProvider = syncedModelIdsByCanonicalProvider.get(canonicalProviderId);
const exclusiveListing = providerUsesExclusiveSyncedListing(canonicalProviderId);
const providerHasSynced = syncedForProvider !== undefined && syncedForProvider.size > 0;
const coveredBySynced = shouldSuppressStaticModelForExclusiveListing({
exclusiveListing,
providerHasSynced,
staticModelId: model.id,
syncedModelIds: syncedForProvider ? [...syncedForProvider] : [],
});
if (
shouldSuppressStaticModelBySyncedCoverage({
providerHasSynced: syncedForProvider !== undefined && syncedForProvider.size > 0,
staticModelId: model.id,
syncedModelIds: syncedForProvider ? [...syncedForProvider] : [],
}) &&
!isRegisteredEffortVariant(providerModels, model.id)
coveredBySynced &&
(exclusiveListing || !isRegisteredEffortVariant(providerModels, model.id))
)
continue;
if (!providerSupportsModel(canonicalProviderId, model.id)) continue;
@@ -838,7 +850,16 @@ async function buildUnifiedModelsResponseCore(
continue;
}
for (const sm of syncedModels) {
for (const sm of providerUsesExclusiveSyncedListing(providerId)
? ensureCursorAutoCatalogEntry(
syncedModels.map((row) => ({
...row,
id: row.id,
name: row.name || row.id,
owned_by: "cursor",
}))
)
: syncedModels) {
if (!providerSupportsModel(canonicalProviderId, sm.id)) continue;
if (canonicalProviderId === "codex" && isCodexDiscoveryModelExcluded(sm)) {
continue;
@@ -1073,9 +1094,7 @@ async function buildUnifiedModelsResponseCore(
// here would discard all but the last segment and miss stored flags for
// providers whose model IDs carry a sub-path (e.g. OpenRouter scoped models).
const getSpecialtyModelRelativeId = (modelId: string, provider: string): string =>
modelId.startsWith(`${provider}/`)
? modelId.slice(provider.length + 1)
: modelId;
modelId.startsWith(`${provider}/`) ? modelId.slice(provider.length + 1) : modelId;
// Add embedding models (filtered by active providers)
for (const embModel of getAllEmbeddingModels()) {

View File

@@ -33,6 +33,29 @@ export function shouldSuppressStaticModelBySyncedCoverage(opts: {
return opts.syncedModelIds.includes(opts.staticModelId);
}
/**
* Exclusive live-catalog listing (Cursor): when the provider opts in and has a
* non-empty synced catalog, suppress EVERY static registry row — including
* effort-suffixed variants the coverage helper would otherwise preserve.
*
* Non-exclusive providers fall through to exact-id coverage suppression.
*/
export function shouldSuppressStaticModelForExclusiveListing(opts: {
exclusiveListing: boolean;
providerHasSynced: boolean;
staticModelId: string;
syncedModelIds: string[];
}): boolean {
if (opts.exclusiveListing) {
return opts.providerHasSynced && opts.syncedModelIds.length > 0;
}
return shouldSuppressStaticModelBySyncedCoverage({
providerHasSynced: opts.providerHasSynced,
staticModelId: opts.staticModelId,
syncedModelIds: opts.syncedModelIds,
});
}
/**
* Build a Map of canonical provider id -> set of synced display-model ids, so the
* static loop can decide which static models a provider's synced discovery list

View File

@@ -119,8 +119,9 @@ export async function POST(request, { params }) {
}
const errorPayload = toJsonErrorPayload((result as any).error, "Image generation provider error");
return new Response(JSON.stringify(errorPayload), {
status: (result as any).status,
headers: { "Content-Type": "application/json" },
});
const message =
typeof errorPayload?.error?.message === "string"
? errorPayload.error.message
: "Image generation provider error";
return errorResponse((result as any).status, message);
}

View File

@@ -4948,6 +4948,7 @@
"baseUrlHint": "必填。 提供者 API 基本 URL。",
"iconUrlLabel": "图标 URL",
"iconUrlHint": "可选。显示为此服务商图标的图片 URL。",
"iconUrlInvalid": "无效的图标 URL。请使用 http(s):// 或 data:image/*;base64 URL。",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",

View File

@@ -4948,6 +4948,7 @@
"baseUrlHint": "必填。 提供者 API 基本 URL。",
"iconUrlLabel": "圖示網址",
"iconUrlHint": "選用。顯示為此提供者圖示的圖片網址。",
"iconUrlInvalid": "無效的圖示網址。請使用 http(s):// 或 data:image/*;base64 URL。",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",

View File

@@ -204,8 +204,18 @@ export async function reconcileProvidersWithActiveSyncedCatalog(
for (const { provider, catalog } of states) {
const modelIsLive = catalog.models.some((model) => model.id === modelId);
// Cursor auto-router: always allow `auto` / router variants even if a stale live
// catalog omitted them (AvailableModels / agent list often returns wire id
// `default` only; listing injects `auto` + cost/balance/intelligence).
const cursorAutoAllow =
provider === "cursor" &&
(modelId === "auto" ||
modelId === "default" ||
modelId === "auto-cost" ||
modelId === "auto-balance" ||
modelId === "auto-intelligence");
if (!catalog.authoritative || modelIsLive) {
if (!catalog.authoritative || modelIsLive || cursorAutoAllow) {
providers.push(provider);
} else {
excludedProviders.push(provider);

View File

@@ -11,6 +11,7 @@
import { getDbInstance } from "../core";
import { backupDbFile } from "../backup";
import { cleanupComboConnectionRefs } from "../combos";
import { deleteLKGPByConnectionIds } from "../settings/lkgp";
import {
removeConnectionHealth,
removeConnectionIndex,
@@ -65,6 +66,17 @@ async function _cleanupDeletedComboConnectionRefs(connectionIds: string | string
}
}
async function _cleanupDeletedLKGPConnectionRefs(connectionIds: string | string[]): Promise<void> {
const ids = Array.isArray(connectionIds) ? connectionIds : [connectionIds];
if (ids.length === 0) return;
try {
await deleteLKGPByConnectionIds(ids);
} catch (error) {
console.error("Failed to clean up LKGP refs for deleted connections:", error);
}
}
export async function deleteProviderConnection(id: string) {
const db = getDbInstance() as unknown as DbLike;
const existing = db.prepare("SELECT provider FROM provider_connections WHERE id = ?").get(id);
@@ -77,6 +89,7 @@ export async function deleteProviderConnection(id: string) {
})();
await _cleanupDeletedComboConnectionRefs(id);
await _cleanupDeletedLKGPConnectionRefs(id);
removeConnectionHealth(id);
removeConnectionIndex(id);
@@ -114,6 +127,7 @@ export async function deleteProviderConnections(ids: string[]): Promise<number>
})();
await _cleanupDeletedComboConnectionRefs(existingIds);
await _cleanupDeletedLKGPConnectionRefs(existingIds);
for (const id of ids) {
removeConnectionHealth(id);
@@ -150,6 +164,7 @@ export async function deleteProviderConnectionsByProvider(providerId: string) {
})();
await _cleanupDeletedComboConnectionRefs(connectionIds);
await _cleanupDeletedLKGPConnectionRefs(connectionIds);
for (const connectionId of connectionIds) {
removeConnectionHealth(connectionId);

View File

@@ -210,6 +210,14 @@ export async function setCachedLKGP(
lkgpCache.invalidate(`lkgp:${comboName}:${modelId}`);
}
/**
* Invalidate one persisted LKGP pin by its `${comboName}:${modelId}` storage key,
* or every cached LKGP pin when no key is provided.
*/
export function invalidateCachedLKGP(pinKey?: string): void {
lkgpCache.invalidate(pinKey ? `lkgp:${pinKey}` : undefined);
}
// ──────────────── Combo Cache Invalidation Signal ────────────────
//
// The nested-combo expansion caches live in request handlers

View File

@@ -47,3 +47,53 @@ export function clearAllLKGP(): void {
const db = getDbInstance();
db.prepare("DELETE FROM key_value WHERE namespace = 'lkgp'").run();
}
/**
* Delete persisted LKGP pins whose connectionId references a removed provider
* connection. Provider-level pins and legacy/unparseable values are preserved.
*/
export async function deleteLKGPByConnectionIds(connectionIds: string[]): Promise<number> {
if (connectionIds.length === 0) return 0;
const deletedConnectionIds = new Set(connectionIds.filter(Boolean));
if (deletedConnectionIds.size === 0) return 0;
const db = getDbInstance();
const rows = db
.prepare("SELECT key, value FROM key_value WHERE namespace = 'lkgp'")
.all() as Array<{ key?: string; value?: string }>;
const staleKeys: string[] = [];
for (const row of rows) {
if (!row?.key || !row.value) continue;
let parsed: unknown;
try {
parsed = JSON.parse(row.value);
} catch {
continue;
}
if (typeof parsed !== "object" || parsed === null) continue;
const connectionId = (parsed as LKGPRecord).connectionId;
if (typeof connectionId === "string" && deletedConnectionIds.has(connectionId)) {
staleKeys.push(row.key);
}
}
if (staleKeys.length === 0) return 0;
const deleteStatement = db.prepare("DELETE FROM key_value WHERE namespace = 'lkgp' AND key = ?");
for (const key of staleKeys) {
deleteStatement.run(key);
}
const { invalidateCachedLKGP } = await import("../readCache");
for (const key of staleKeys) {
invalidateCachedLKGP(key);
}
return staleKeys.length;
}

View File

@@ -13,7 +13,9 @@ import {
callVisionModel as defaultCallVisionModel,
composeVisionPrompt,
replaceImageParts,
ensureBase64ImagesForClaudeWire,
} from "./visionBridgeHelpers";
import { fetch as undiciFetch } from "undici";
import {
getVisionBridgeConfig,
isVisionBridgeForcedModel,
@@ -298,14 +300,27 @@ export class VisionBridgeGuardrail extends BaseGuardrail {
const bestUsable = await checkCreds(bestModel);
// Only block the reroute when we KNOW the target is unusable (false).
// `null` (no DB / tests) fails open so existing unit tests keep working.
if (bestUsable === false) {
// `auto/*` ids (e.g. auto/best-vision) are VIRTUAL combos: credentials
// resolve through their member models at request time, so a missing
// "auto" provider row (hasUsableCredentialsForModel → false) must
// never block the reroute.
if (bestUsable === false && !bestModel.startsWith("auto/")) {
context.log?.warn?.(
"VISION_BRIDGE",
`Vision reroute target ${bestModel} has no usable credentials; describing images instead of hijacking ${model}`
);
} else {
// Claude-wire backends (minimax, zai, …) reject remote image URLs
// (MiniMax 403 2013); resolve them to base64 before rerouting so
// the rerouted request can actually be processed upstream. Use
// undici fetch to bypass the runtime's hooked global fetch.
const rerouteBody = await ensureBase64ImagesForClaudeWire(
body as Parameters<typeof ensureBase64ImagesForClaudeWire>[0],
bestModel,
undiciFetch as unknown as typeof fetch
);
const modifiedBody = {
...(body as Record<string, unknown>),
...(rerouteBody as Record<string, unknown>),
model: bestModel,
};
return {
@@ -347,7 +362,14 @@ export class VisionBridgeGuardrail extends BaseGuardrail {
// targets what the user actually asked instead of a generic caption.
const lastUserText = extractLastUserText(messages);
const composedPrompt = composeVisionPrompt(config.prompt, lastUserText, runtime.taskAware);
const describeConfig = { ...config, prompt: composedPrompt };
// Bypass the runtime's hooked global fetch (ProxyFetch) for the self-loop
// describe call — a dead local proxy (127.0.0.1:8317) would otherwise break
// every describe. Tests inject their own callVisionModel.
const describeConfig = {
...config,
prompt: composedPrompt,
fetchImpl: undiciFetch as unknown as typeof fetch,
};
// Shared describe cache (sha256 of contentRef+prompt+model): the same image
// with the same prompt/model is described once per TTL. Failures are never

View File

@@ -6,6 +6,8 @@ import { fetchRemoteImage } from "@/shared/network/remoteImageFetch";
import { getRuntimePorts } from "@/lib/runtime/ports";
import { resolveSelfLoopBearer } from "@/shared/middleware/chatBodyAdmission";
import { getBestVisionModel, getFallbackModels, recordLatency } from "./visionBridgeRouter";
import { REGISTRY } from "@omniroute/open-sse/config/providers";
import { fetch as undiciFetch } from "undici";
/**
* Provider to environment variable mapping for API key resolution.
*/
@@ -15,6 +17,27 @@ const PROVIDER_API_KEY_MAP: Record<string, string> = {
openai: "OPENAI_API_KEY",
};
// Providers whose wire format is Anthropic Messages ("claude"). Anthropic
// accepts `source: { type: "url" }` for images, but most claude-format backends
// (MiniMax, Z.AI, …) do NOT — they reject remote URLs (MiniMax: 403 code
// 2013). The vision bridge must deliver images as base64 for these targets,
// both in the describe self-loop and in the rerouted payload.
const CLAUDE_WIRE_PROVIDERS = new Set<string>(
Object.entries(REGISTRY)
.filter(([, entry]) => entry.format === "claude")
.map(([id]) => id.toLowerCase())
);
/**
* True when `provider/model` targets a Claude-Messages wire format backend
* that cannot ingest remote image URLs and needs base64 instead.
*/
export function isClaudeWireFormatModel(model: string | null | undefined): boolean {
if (!model || typeof model !== "string") return false;
const provider = model.includes("/") ? model.split("/")[0].trim().toLowerCase() : "";
return CLAUDE_WIRE_PROVIDERS.has(provider);
}
/**
* Resolve API key based on model provider (issue #2232).
*
@@ -46,6 +69,43 @@ export function resolveProviderApiKey(model: string, explicitKey?: string): stri
return process.env[envVar] || "";
}
let selfLoopKeyPromise: Promise<string> | null = null;
/**
* Resolve a real API key for the OmniRoute SELF-LOOP describe call.
*
* The `sk_omniroute` sentinel works only when REQUIRE_API_KEY is disabled; on
* REQUIRE_API_KEY instances it is rejected with 401 "Missing API key", which
* silently breaks every vision-bridge describe. Priority:
* 1. VISION_BRIDGE_API_KEY env (already handled by resolveProviderApiKey —
* kept here for the injected-resolver test path).
* 2. Injected resolver (tests) or the DB-backed `getOrCreateApiKey()` —
* memoized so at most one key is created per process.
* 3. `sk_omniroute` as a final fallback (local mode without auth).
*/
export async function resolveSelfLoopApiKey(resolver?: () => Promise<string>): Promise<string> {
const envKey = (process.env.VISION_BRIDGE_API_KEY || "").trim();
if (envKey) return envKey;
if (resolver) {
const key = (await resolver()).trim();
if (key) return key;
return "sk_omniroute";
}
if (!selfLoopKeyPromise) {
selfLoopKeyPromise = (async () => {
try {
const { getOrCreateApiKey } = await import("@/shared/services/apiKeyResolver");
const key = await getOrCreateApiKey();
if (typeof key === "string" && key.trim().length > 0) return key.trim();
} catch {
/* fall through */
}
return "sk_omniroute";
})();
}
return selfLoopKeyPromise;
}
/**
* Resolve the OpenAI-compatible base URL for non-Anthropic vision bridge calls
* (issue #2232).
@@ -152,6 +212,68 @@ export function extractImageParts(messages: RequestMessage[]): ImagePart[] {
}));
}
// Undici fetch with a browser-ish User-Agent: Wikimedia (and other CDNs)
// reject requests without a UA with HTTP 400, silently breaking remote image
// downloads in the describe path.
const VISION_BRIDGE_UA_FETCH: typeof fetch = ((input: RequestInfo | URL, init?: RequestInit) =>
undiciFetch(input as string | URL, {
...(init as Parameters<typeof undiciFetch>[1]),
headers: {
"user-agent": "omniroute-vision-bridge",
...((init?.headers as Record<string, string> | undefined) ?? {}),
},
})) as typeof fetch;
/**
* Resolve every image part in the body to a base64 data URI when the target
* model speaks the Claude wire format (remote URLs unsupported by most
* claude-format backends, e.g. MiniMax 403 2013). Fail-open: an image that
* cannot be fetched is left untouched.
*/
export async function ensureBase64ImagesForClaudeWire(
body: RequestBody,
model: string,
fetchImpl: typeof fetch = VISION_BRIDGE_UA_FETCH
): Promise<RequestBody> {
if (!isClaudeWireFormatModel(model)) return body;
const parts = extractImageParts(body.messages as RequestMessage[]);
if (parts.length === 0) return body;
const resolved = await Promise.all(
parts.map(async (part) => {
const normalized = resolveImageAsDataUri(part.imageUrl);
if (normalized.startsWith("data:")) return null; // already base64
try {
return await fetchRemoteImageAsDataUri(normalized, new AbortController().signal, fetchImpl);
} catch {
return null; // fail-open: keep the original part
}
})
);
// Map sequential image index → resolved data URI (null = keep original).
const byIndex = new Map<number, string>();
parts.forEach((part, i) => {
if (resolved[i]) byIndex.set(i, resolved[i] as string);
});
if (byIndex.size === 0) return body;
const result = structuredClone(body) as RequestBody;
let imageIndex = 0;
for (const message of result.messages ?? []) {
if (!message || !Array.isArray(message.content)) continue;
for (const part of message.content as RequestContentPart[]) {
if (part.type !== "image_url" && part.type !== "image") continue;
const dataUri = byIndex.get(imageIndex);
imageIndex++;
if (dataUri) {
(part as { image_url?: { url: string } }).image_url = { url: dataUri };
}
}
}
return result;
}
/**
* Resolve image URL to data URI format for vision model.
* - HTTP/HTTPS URLs: passed through as-is
@@ -178,8 +300,17 @@ export function resolveImageAsDataUri(imageUrl: string): string {
return `data:image/png;base64,${imageUrl}`;
}
async function fetchRemoteImageAsDataUri(imageUrl: string, signal: AbortSignal): Promise<string> {
const remoteImage = await fetchRemoteImage(imageUrl, { signal });
async function fetchRemoteImageAsDataUri(
imageUrl: string,
signal: AbortSignal,
fetchImpl: typeof fetch = VISION_BRIDGE_UA_FETCH
): Promise<string> {
const remoteImage = await fetchRemoteImage(imageUrl, {
signal,
// Bypass the runtime's hooked global fetch (ProxyFetch) — a dead local
// proxy (e.g. 127.0.0.1:8317) would otherwise break the download.
fetchImpl,
});
const mediaType = remoteImage.contentType.split(";")[0]?.trim() || "image/png";
return `data:${mediaType};base64,${remoteImage.buffer.toString("base64")}`;
}
@@ -187,7 +318,8 @@ async function fetchRemoteImageAsDataUri(imageUrl: string, signal: AbortSignal):
async function normalizeVisionImageInput(
imageInput: string,
isAnthropic: boolean,
signal: AbortSignal
signal: AbortSignal,
fetchImpl?: typeof fetch
): Promise<string> {
const normalizedImage = resolveImageAsDataUri(imageInput);
@@ -195,7 +327,7 @@ async function normalizeVisionImageInput(
isAnthropic &&
(normalizedImage.startsWith("http://") || normalizedImage.startsWith("https://"))
) {
return fetchRemoteImageAsDataUri(normalizedImage, signal);
return fetchRemoteImageAsDataUri(normalizedImage, signal, fetchImpl);
}
return normalizedImage;
@@ -206,6 +338,8 @@ export interface VisionModelConfig {
prompt: string;
timeoutMs: number;
maxImages: number;
/** Injectable fetch (tests). Defaults to undici fetch to bypass the runtime's hooked global fetch. */
fetchImpl?: typeof fetch;
}
/** Task-aware focus hint (codex-vision-proxy pattern): steer the description
@@ -481,17 +615,27 @@ async function callVisionModelSingle(
// Resolve API key based on provider
const resolvedApiKey = resolveProviderApiKey(config.model, apiKey);
// Production callers (VisionBridgeGuardrail) inject undici fetch to bypass
// the runtime's hooked global fetch (ProxyFetch). Defaults to globalThis.fetch
// so existing unit tests that mock it keep working.
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
// Detect provider from model identifier
// Detect provider from model identifier. Claude-wire targets (minimax, zai,
// …) cannot ingest remote image URLs — normalize to base64 so the self-loop
// body reaches the backend as a data URI (the OpenAI→claude translator only
// preserves data URIs as base64; remote URLs become source.url which these
// backends reject).
const isAnthropic = config.model.startsWith("anthropic/");
const requiresBase64 = isAnthropic || isClaudeWireFormatModel(config.model);
try {
// Extract model name from provider/model format
const modelName = config.model.includes("/") ? config.model.split("/")[1] : config.model;
const normalizedImageInput = await normalizeVisionImageInput(
imageDataUri,
isAnthropic,
controller.signal
requiresBase64,
controller.signal,
fetchImpl
);
let response: Response;
@@ -510,7 +654,7 @@ async function callVisionModelSingle(
base64Data = matches[2];
}
response = await fetch(`${anthropicBaseUrl}/v1/messages`, {
response = await fetchImpl(`${anthropicBaseUrl}/v1/messages`, {
method: "POST",
signal: controller.signal,
headers: {
@@ -561,8 +705,9 @@ async function callVisionModelSingle(
// Build headers with optional recursion guard for self-loop calls.
// When routing through OmniRoute's own API, omit the vision-bridge
// guardrail on the sub-request to prevent infinite recursion.
// Use sk_omniroute as fallback for self-loop if no API key is resolved.
const selfLoopApiKey = resolvedApiKey || "sk_omniroute";
// Use a real DB-backed key for self-loop (sk_omniroute is rejected by
// REQUIRE_API_KEY instances with 401 "Missing API key").
const selfLoopApiKey = resolvedApiKey || (await resolveSelfLoopApiKey());
const headers: Record<string, string> = {
"Content-Type": "application/json",
// Explicit JSON opt-in: without `Accept: application/json` OmniRoute's
@@ -583,6 +728,9 @@ async function callVisionModelSingle(
// `sk_omniroute` sentinel OR the operator-configured env key), so
// external clients cannot use it to bypass admission.
headers["x-omniroute-admission-bypass"] = "internal";
// The compression pipeline must not touch the image payload of the
// self-loop describe call (stacked RTK/Caveman can mangle data URIs).
headers["x-omniroute-compression"] = "off";
// The admission bypass honors the env key when set (REQUIRE_API_KEY=true
// deployments) and the `sk_omniroute` sentinel otherwise. Force the same
// resolved credential so the bypass holds even when a real vision key is
@@ -590,7 +738,7 @@ async function callVisionModelSingle(
headers["Authorization"] = `Bearer ${resolveSelfLoopBearer()}`;
}
response = await fetch(`${baseUrl}/chat/completions`, {
response = await fetchImpl(`${baseUrl}/chat/completions`, {
method: "POST",
signal: controller.signal,
headers,

View File

@@ -6,6 +6,7 @@
import { getResolvedModelCapabilities } from "@/lib/modelCapabilities";
import { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS } from "@omniroute/open-sse/config/providerModels";
import { hasUsableCredentialsForModel } from "./visionBridgeCredentials";
import { isVisionBridgeForcedModel } from "@/shared/constants/visionBridgeDefaults";
export interface VisionModelCandidate {
modelId: string;
@@ -133,7 +134,7 @@ async function getVisionCapableModels(
const fullModelId = `${providerAlias}/${model.id}`;
const caps = getResolvedModelCapabilities(fullModelId);
if (caps.supportsVision === true) {
if (caps.supportsVision === true && !isVisionBridgeForcedModel(fullModelId)) {
checks.push(
checkCreds(fullModelId).then((usable) => {
// Only a confirmed `false` excludes a candidate — `null` (indeterminate,

View File

@@ -112,7 +112,12 @@ async function onboardAntigravityUser(
tierId: string,
metadata: Record<string, string>
): Promise<void> {
for (let i = 0; i < 10; i++) {
// Bounded onboarding: cap retries (was 10) and jitter the delay so a stuck
// loop cannot look like scripted automation to the upstream (ban-safety).
const MAX_ONBOARD_RETRIES = 3;
const BASE_RETRY_MS = 3000;
const JITTER_MS = 4000;
for (let i = 0; i < MAX_ONBOARD_RETRIES; i++) {
try {
const response = await fetchFirstOk(
config.onboardUserEndpoints,
@@ -124,7 +129,7 @@ async function onboardAntigravityUser(
} catch {
return;
}
await new Promise((resolve) => setTimeout(resolve, 5000));
await new Promise((resolve) => setTimeout(resolve, BASE_RETRY_MS + Math.random() * JITTER_MS));
}
}

View File

@@ -0,0 +1,60 @@
/**
* Ensure Cursor catalog listings always expose OmniRoute's public auto-router
* ids (`auto` + Cost/Balance/Intelligence variants). Live AvailableModels /
* cursor-agent often return wire id `default` only.
*/
export type CursorAutoCatalogEntry = {
id: string;
name: string;
owned_by?: string;
[key: string]: unknown;
};
export const CURSOR_AUTO_ROUTER_VARIANT_IDS = [
"auto-cost",
"auto-balance",
"auto-intelligence",
] as const;
const CURSOR_AUTO_ROUTER_VARIANT_NAMES: Record<
(typeof CURSOR_AUTO_ROUTER_VARIANT_IDS)[number],
string
> = {
"auto-cost": "Auto (cost)",
"auto-balance": "Auto (balance)",
"auto-intelligence": "Auto (intelligence)",
};
/** Cursor auto-router: catalog id `auto`, wire id `default`. Always keep `auto` visible. */
export function ensureCursorAutoCatalogEntry<T extends CursorAutoCatalogEntry>(models: T[]): T[] {
const byId = new Map(models.map((m) => [m.id, m]));
const out = [...models];
if (!byId.has("auto")) {
const defaultEntry = byId.get("default");
const autoEntry = {
id: "auto",
name:
typeof defaultEntry?.name === "string" && defaultEntry.name.trim()
? defaultEntry.name
: "Auto (current, default)",
owned_by: "cursor",
} as T;
out.unshift(autoEntry);
byId.set("auto", autoEntry);
}
for (const id of CURSOR_AUTO_ROUTER_VARIANT_IDS) {
if (byId.has(id)) continue;
const entry = {
id,
name: CURSOR_AUTO_ROUTER_VARIANT_NAMES[id],
owned_by: "cursor",
} as T;
out.push(entry);
byId.set(id, entry);
}
return out;
}

View File

@@ -0,0 +1,96 @@
/**
* Pure merge of registry / synced / custom model rows for the provider detail
* dashboard (and thus Test All targets). Cursor exclusive listing prefers the
* live synced catalog when non-empty.
*/
import { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog";
import {
providerUsesCuratedModelsOnly,
providerUsesExclusiveSyncedListing,
} from "@/lib/providers/modelListingCapability";
export type ProviderListingModel = {
id: string;
name?: string;
source?: string;
[key: string]: unknown;
};
export type MergeProviderModelListingInput = {
providerId: string;
registryModels: Array<{ id: string; name?: string; [key: string]: unknown }>;
syncedModels: Array<{ id: string; name?: string; [key: string]: unknown }>;
customModels: Array<{ id: string; name?: string; source?: string; [key: string]: unknown }>;
usesCuratedModelsOnly?: boolean;
};
function normalizeCustomSource(source: unknown): "imported" | "custom" {
return source === "imported" ? "imported" : "custom";
}
function dedupeById(models: ProviderListingModel[]): ProviderListingModel[] {
const deduped = new Map<string, ProviderListingModel>();
for (const m of models) {
if (m.id && !deduped.has(m.id)) deduped.set(m.id, m);
}
return Array.from(deduped.values());
}
export function mergeProviderModelListing(
input: MergeProviderModelListingInput
): ProviderListingModel[] {
const curated =
input.usesCuratedModelsOnly === true || providerUsesCuratedModelsOnly(input.providerId);
const synced = curated ? [] : input.syncedModels.filter((m) => m?.id);
const custom = curated ? [] : input.customModels.filter((m) => m?.id);
const exclusive = providerUsesExclusiveSyncedListing(input.providerId) && synced.length > 0;
if (exclusive) {
const withAuto = ensureCursorAutoCatalogEntry(
synced.map((model) => ({
...model,
id: model.id,
name: model.name || model.id,
owned_by: "cursor",
source: "imported",
}))
);
const knownIds = new Set(withAuto.map((m) => m.id));
const customExtras = custom
.filter((cm) => cm.id && !knownIds.has(cm.id))
.map((cm) => ({
...cm,
id: cm.id,
name: cm.name || cm.id,
source: normalizeCustomSource(cm.source),
}));
return dedupeById([...withAuto, ...customExtras]);
}
const builtInModels = input.registryModels.map((model) => ({
...model,
source: "system",
}));
const registryIds = new Set(builtInModels.map((m) => m.id));
const syncedExtras = synced
.filter((model) => model.id && !registryIds.has(model.id))
.map((model) => ({
...model,
id: model.id,
name: model.name || model.id,
source: "imported",
}));
const knownIds = new Set([...registryIds, ...syncedExtras.map((m) => m.id)]);
const customExtras = custom
.filter((cm) => cm.id && !knownIds.has(cm.id))
.map((cm) => ({
...cm,
id: cm.id,
name: cm.name || cm.id,
source: normalizeCustomSource(cm.source),
}));
return dedupeById([...builtInModels, ...syncedExtras, ...customExtras]);
}

View File

@@ -17,6 +17,19 @@ export function providerUsesCuratedModelsOnly(providerId: string): boolean {
return CURATED_MODEL_ONLY_PROVIDERS.has(providerId.trim().toLowerCase());
}
/**
* Providers whose non-empty synced AvailableModels catalog fully replaces the
* static registry for dashboard / `/v1/models` / Test All listing. Static rows
* remain offline fallback only when synced is empty.
*
* Cursor-only for now — other authoritative live-catalog providers keep
* coverage-style static preservation (e.g. command-code uncovered static ids).
*/
export function providerUsesExclusiveSyncedListing(providerId: string): boolean {
const id = providerId.trim().toLowerCase();
return id === "cursor" || id === "cu";
}
/**
* True when the provider is tool-only and therefore has no model listing:
* - its id ends in `-search` (legacy search providers), OR

View File

@@ -172,6 +172,25 @@ export async function executeWebSearch(
} else {
credentials = await resolveSearchCredentials(providerConfig.id);
if (!credentials) {
const fallbackProviders = Object.values(SEARCH_PROVIDERS)
.filter((provider) => provider.fallbackOnly && supportsSearchType(provider, searchType))
.sort((a, b) => a.costPerQuery - b.costPerQuery);
for (const fallbackProvider of fallbackProviders) {
providerConfig = fallbackProvider;
if (fallbackProvider.id === "duckduckgo-free") {
credentials = {};
break;
}
const fallbackCredentials = await resolveSearchCredentials(fallbackProvider.id);
if (fallbackCredentials) {
credentials = fallbackCredentials;
break;
}
}
}
if (!credentials) {
const sortedIds = Object.values(SEARCH_PROVIDERS)
.filter((provider) => supportsSearchType(provider, searchType))

View File

@@ -56,6 +56,19 @@ export class ServiceSupervisor extends EventEmitter {
this.checker = new HealthChecker(config.healthUrl, config.healthIntervalMs, (h) => {
this.health = h;
this.emit("stateChange", this.getStatus());
// A service that fails FAILURE_THRESHOLD consecutive health probes will
// not recover by itself. Stop the poller and surface an explicit error
// state instead of probing the dead port forever — every failed probe
// fires a full ProxyFetch dispatcher+native fetch pair (e.g. against a
// CLIProxyAPI binary that cannot execute on this platform).
if (h === "unhealthy" && (this.state === "running" || this.state === "starting")) {
this.checker.stop();
this.lastError = sanitizeErrorMessage(
`Health probe failed for ${this.config.tool} (port ${this.config.port})`
);
this.setState("error");
void setToolStatus(this.config.tool, "error", undefined, this.lastError);
}
});
}
@@ -135,7 +148,22 @@ export class ServiceSupervisor extends EventEmitter {
const { command, args, env, cwd } = this.config.spawnArgs();
const child = spawn(command, args, buildServiceSpawnOptions(env, cwd));
// spawn() can throw SYNCHRONOUSLY on Windows when the binary is not
// executable (EFTYPE/EINVAL for an ELF or a plain text file) instead of
// emitting the child 'error' event. Handle both paths identically so a
// non-spawnable service surfaces an explicit error state and the health
// poller is stopped instead of hammering a dead port forever.
let child: ChildProcess;
try {
child = spawn(command, args, buildServiceSpawnOptions(env, cwd));
} catch (err) {
this.checker.stop();
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
this.lastError = msg;
this.setState("error");
await setToolStatus(this.config.tool, "error", undefined, msg);
return this.getStatus();
}
this.childProcess = child;
this.pid = child.pid ?? null;
@@ -161,6 +189,17 @@ export class ServiceSupervisor extends EventEmitter {
child.once("exit", (code, signal) => {
void this.handleExit(code, signal, spawnTime);
});
// Spawn failures (ENOENT, EACCES, or a non-executable binary such as an
// ELF on Windows) surface via the child 'error' event — NOT 'exit'.
// Without this handler the supervisor stays in "starting" forever and
// the health poller hammers the dead port every healthIntervalMs.
child.once("error", (err) => {
this.checker.stop();
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
this.lastError = msg;
this.setState("error");
void setToolStatus(this.config.tool, "error", undefined, msg);
});
this.startedAt = new Date().toISOString();
this.checker.start();
@@ -224,10 +263,12 @@ export class ServiceSupervisor extends EventEmitter {
if (this.state === "error") throw new Error(this.lastError ?? "Service failed to start");
await new Promise((r) => setTimeout(r, 1_000));
}
// Timeout reached without a healthy probe. Surface this so callers /
// dashboards do not see "running" + "unknown" health silently. We do not
// throw — the service may still be initializing — but we DO record a
// degraded marker so /status returns it and operators can act.
// Timeout reached without a healthy probe. The health poller may have
// flipped the state to "error" while we were waiting (FAILURE_THRESHOLD
// consecutive failures) — surface that instead of a degraded marker.
if (this.state === "error") {
throw new Error(this.lastError ?? "Service failed to start");
}
this.lastError = sanitizeErrorMessage(
`Health probe did not succeed within ${timeoutMs}ms — service may still be initializing`
);

View File

@@ -89,6 +89,8 @@ const PROVIDER_LIMITS_APIKEY_PROVIDERS = new Set([
"hyperagent",
"ha",
"firecrawl",
// Command Code API key → /alpha/billing/credits + windowLimits
"command-code",
]);
const DEFAULT_PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES = 70;
const PROVIDER_LIMITS_AUTO_SYNC_SETTING_KEY = "provider_limits_auto_sync_last_run";

View File

@@ -463,6 +463,8 @@ export const USAGE_SUPPORTED_PROVIDERS = [
"grok-cli",
// Firecrawl team credits (GET /v2/team/credit-usage)
"firecrawl",
// Command Code credits + 5h/weekly rolling windows
"command-code",
];
// ── Zod validation at module load (Phase 7.2) ──

View File

@@ -1,14 +1,25 @@
/**
* Bounded admission for POST /v1/chat/completions.
* Process-local bounded admission for POST /v1/chat/completions.
*
* Large chat bodies amplify into multiple transient representations while they are parsed,
* translated, compressed, and dispatched. A heap snapshot alone cannot prevent two healthy
* requests from entering that allocation-heavy path together. This module reserves process-
* local heavyweight capacity before parsing and enforces the hard limit against bytes read,
* not an untrusted Content-Length header.
*
* Per-connection virtual admission lanes (#9654): each distinct API-key (or anonymous)
* bucket gets its own FairCostQueue so one connection cannot exhaust heavyweight capacity
* and starve others. Idle sessions are auto-evicted after a TTL.
*/
import { CORS_HEADERS } from "../utils/cors";
import { createHash } from "crypto";
const OMNIROUTE_CHAT_VIRTUAL_TTL_MS = parsePositiveInt(
process.env.OMNIROUTE_CHAT_VIRTUAL_TTL_MS,
60_000
);
function parsePositiveInt(value: string | undefined, fallback: number): number {
const parsed = Number.parseInt(String(value), 10);
@@ -30,7 +41,7 @@ export const CHAT_HARD_MAX_BODY_BYTES = parsePositiveInt(
50 * 1024 * 1024
);
const CHAT_MAX_HEAVY_IN_FLIGHT = parsePositiveInt(
export const CHAT_MAX_HEAVY_IN_FLIGHT = parsePositiveInt(
process.env.OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT,
1
);
@@ -44,7 +55,20 @@ const CHAT_MAX_HEAVY_IN_FLIGHT = parsePositiveInt(
*/
export const CHAT_ADMISSION_QUEUE_MAX_MS = parseNonNegativeInt(
process.env.OMNIROUTE_CHAT_ADMISSION_QUEUE_MS,
5000
2000
);
/**
* Queued-bytes budget for the admission wait (#9654 / U3). A parked waiter holds a
* fully-buffered request body; several large coding-agent bodies (~750 KB) waiting at
* once is exactly the heap-amplification scenario chatBodyAdmission was built to stop
* (#4380). Each lane's controller charges every parked waiter's buffered size against
* this budget and rejects over-budget waits immediately (retryable 503) instead of
* parking. Bytes are released when a waiter wakes, aborts, or times out.
*/
export const CHAT_ADMISSION_MAX_QUEUED_BYTES = parsePositiveInt(
process.env.OMNIROUTE_CHAT_ADMISSION_MAX_QUEUED_BYTES,
4 * 1024 * 1024
);
export const CHAT_HEAVY_MESSAGE_COUNT = parsePositiveInt(
@@ -94,18 +118,30 @@ export interface ChatAdmissionLease {
*/
export class ChatAdmissionController {
#activeHeavy = 0;
#queuedBytes = 0;
#waiters: Array<() => void> = [];
constructor(readonly maxHeavyInFlight = 1) {
constructor(
readonly maxHeavyInFlight = 1,
readonly maxQueuedBytes = CHAT_ADMISSION_MAX_QUEUED_BYTES
) {
if (!Number.isSafeInteger(maxHeavyInFlight) || maxHeavyInFlight < 1) {
throw new RangeError("maxHeavyInFlight must be a positive integer");
}
if (!Number.isSafeInteger(maxQueuedBytes) || maxQueuedBytes < 0) {
throw new RangeError("maxQueuedBytes must be a non-negative integer");
}
}
get activeHeavy(): number {
return this.#activeHeavy;
}
/** Total buffered bytes currently parked in the FIFO (heap valve accounting). */
get queuedBytes(): number {
return this.#queuedBytes;
}
tryAcquireHeavy(): ChatAdmissionLease | null {
if (this.#activeHeavy >= this.maxHeavyInFlight) return null;
this.#activeHeavy += 1;
@@ -128,27 +164,71 @@ export class ChatAdmissionController {
* release. Resolves `null` when the deadline expires with no capacity freed, in
* which case the caller answers the retryable 503. `timeoutMs <= 0` is the
* legacy immediate-reject path. Waiters are served FIFO.
*
* When `signal` aborts while parked (client disconnect), the waiter is removed
* from the FIFO immediately and the promise resolves `null` early instead of
* parking for the full `timeoutMs` — the caller's 503 is dropped on the dead
* connection, so no capacity is consumed and the freed slot never wakes a
* waiter the client no longer needs. A signal that is already aborted never
* parks at all.
*
* `queuedBytes` is the buffered body size this waiter will hold while parked;
* it is charged against `maxQueuedBytes` so a burst of large bodies cannot
* amplify the heap (#4380). An over-budget wait is rejected immediately with
* `null` (retryable 503) and never parks; the charge is released on wake,
* abort, or timeout.
*/
async acquireHeavyWithin(timeoutMs: number): Promise<ChatAdmissionLease | null> {
async acquireHeavyWithin(
timeoutMs: number,
signal?: AbortSignal,
queuedBytes = 0
): Promise<ChatAdmissionLease | null> {
const deadline = Date.now() + Math.max(0, Math.floor(timeoutMs));
for (;;) {
if (signal?.aborted) return null;
const lease = this.tryAcquireHeavy();
if (lease) return lease;
const remaining = deadline - Date.now();
if (remaining <= 0) return null;
// Heap valve: refuse to park when the queued-bytes budget is exhausted.
if (queuedBytes > 0 && this.#queuedBytes + queuedBytes > this.maxQueuedBytes) {
return null;
}
this.#queuedBytes += queuedBytes;
let resolver: (() => void) | null = null;
const released = new Promise<void>((resolve) => {
resolver = () => resolve();
this.#waiters.push(resolver);
});
const timedOut = await Promise.race([
let deadlineTimer: ReturnType<typeof setTimeout> | null = null;
const races: Array<Promise<boolean>> = [
released.then(() => false),
new Promise<boolean>((resolve) => setTimeout(() => resolve(true), remaining)),
]);
new Promise<boolean>((resolve) => {
deadlineTimer = setTimeout(() => resolve(true), remaining);
}),
];
let onAbort: (() => void) | null = null;
if (signal) {
races.push(
new Promise<boolean>((resolve) => {
const listener = () => resolve(true);
onAbort = listener;
signal.addEventListener("abort", listener, { once: true });
// Already-aborted signals must settle without parking.
if (signal.aborted) resolve(true);
})
);
}
const timedOut = await Promise.race(races);
// The waiter has left the FIFO (wake, abort, or timeout) — release its charge.
this.#queuedBytes = Math.max(0, this.#queuedBytes - queuedBytes);
if (resolver) {
const index = this.#waiters.indexOf(resolver);
if (index >= 0) this.#waiters.splice(index, 1);
}
// Cancel the deadline timer when abort/release wins; a fired timer is a no-op.
if (deadlineTimer) clearTimeout(deadlineTimer);
if (onAbort) signal?.removeEventListener("abort", onAbort);
if (timedOut) return null;
}
}
@@ -156,6 +236,142 @@ export class ChatAdmissionController {
const defaultAdmissionController = new ChatAdmissionController(CHAT_MAX_HEAVY_IN_FLIGHT);
/**
* Per-connection virtual admission lanes (#9654).
*
* Maps a sessionId (API-key hash or "anonymous") → ChatAdmissionController.
Each connection gets its own bounded heavyweight capacity so one connection
* cannot exhaust `CHAT_MAX_HEAVY_IN_FLIGHT` and starve others at the byte-level
* admission stage.
*
* Idle sessions are auto-evicted after OMNIROUTE_CHAT_VIRTUAL_TTL_MS
* (default 60s) to prevent unbounded Map growth.
*/
const OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS = parsePositiveInt(
process.env.OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS,
64
);
export function resolveSessionId(request: Request): string {
// Reuse the existing internal-bypass auth extraction: bearer token from
// Authorization, x-api-key (Anthropic-style), or Google API key header.
const authHeader = request.headers.get("authorization") || "";
const bearerMatch = /^bearer\s+(\S+)$/i.exec(authHeader.trim());
if (bearerMatch) {
return "key_" + createHash("sha256").update(bearerMatch[1]).digest("hex").slice(0, 16);
}
const xApiKey = request.headers.get("x-api-key") || "";
if (xApiKey.trim().length > 0) {
return "key_" + createHash("sha256").update(xApiKey.trim()).digest("hex").slice(0, 16);
}
const xGoogApiKey = request.headers.get("x-goog-api-key") || "";
if (xGoogApiKey.trim().length > 0) {
return "key_" + createHash("sha256").update(xGoogApiKey.trim()).digest("hex").slice(0, 16);
}
return "anonymous";
}
interface SessionRecord {
controller: ChatAdmissionController;
lastUsedMs: number;
}
export class PerConnectionAdmissionController {
#sessions = new Map<string, SessionRecord>();
#evictionTimer: ReturnType<typeof setTimeout> | null = null;
readonly maxSessions: number;
readonly sessionTtlMs: number;
constructor(
readonly maxHeavyPerSession: number,
opts?: { maxSessions?: number; sessionTtlMs?: number }
) {
this.maxSessions = opts?.maxSessions ?? OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS;
this.sessionTtlMs = opts?.sessionTtlMs ?? OMNIROUTE_CHAT_VIRTUAL_TTL_MS;
}
getController(sessionId: string): ChatAdmissionController {
this.evictIfDue();
const existing = this.#sessions.get(sessionId);
if (existing) {
existing.lastUsedMs = Date.now();
return existing.controller;
}
// Evict oldest if at capacity (LRU fallback when TTL hasn't fired).
if (this.#sessions.size >= this.maxSessions) {
const oldestKey = this.oldestKey();
if (oldestKey) this.#sessions.delete(oldestKey);
}
const controller = new ChatAdmissionController(this.maxHeavyPerSession);
this.#sessions.set(sessionId, { controller, lastUsedMs: Date.now() });
this.armEviction();
return controller;
}
/** Snapshot for observability — never exposes raw API keys. */
snapshot(): ReadonlyArray<{ sessionId: string; activeHeavy: number; idleMs: number }> {
const now = Date.now();
const arr: Array<{ sessionId: string; activeHeavy: number; idleMs: number }> = [];
for (const [sessionId, record] of this.#sessions) {
arr.push({
sessionId,
activeHeavy: record.controller.activeHeavy,
idleMs: now - record.lastUsedMs,
});
}
return arr;
}
get sessionCount(): number {
return this.#sessions.size;
}
private oldestKey(): string | undefined {
let oldest: string | undefined;
let oldestMs = Infinity;
for (const [key, record] of this.#sessions) {
// Use <= so that for equal timestamps, later-inserted entries win,
// preserving LRU semantics when Date.now() returns the same value.
if (record.lastUsedMs <= oldestMs) {
oldestMs = record.lastUsedMs;
oldest = key;
}
}
return oldest;
}
private evictIfDue(): void {
const now = Date.now();
let evicted = false;
for (const [sessionId, record] of this.#sessions) {
if (now - record.lastUsedMs >= this.sessionTtlMs) {
this.#sessions.delete(sessionId);
evicted = true;
}
}
if (evicted) this.armEviction();
}
private armEviction(): void {
if (this.#evictionTimer !== null) return;
this.#evictionTimer = setTimeout(() => {
this.#evictionTimer = null;
this.evictIfDue();
}, this.sessionTtlMs).unref();
}
/** Force cleanup of all sessions (used by shutdown / tests). */
dispose(): void {
this.#sessions.clear();
if (this.#evictionTimer !== null) {
clearTimeout(this.#evictionTimer);
this.#evictionTimer = null;
}
}
}
export const perConnectionAdmissionController = new PerConnectionAdmissionController(CHAT_MAX_HEAVY_IN_FLIGHT);
export type ChatRequestAdmission =
| { admit: true; request: Request; lease: ChatAdmissionLease | null }
| { admit: false; response: Response };
@@ -264,11 +480,13 @@ export async function admitChatStructure(
lease: ChatAdmissionLease | null,
options: {
controller?: ChatAdmissionController;
sessionId?: string;
maxMessages?: number;
heavyMessages?: number;
heavyTools?: number;
heavyTokens?: number;
queueMs?: number;
signal?: AbortSignal;
} = {}
): Promise<ChatStructureAdmission> {
if (!body || typeof body !== "object" || Array.isArray(body)) return { admit: true, lease };
@@ -301,8 +519,18 @@ export async function admitChatStructure(
estimatedTokens >= heavyTokens;
if (!heavy || lease) return { admit: true, lease };
const acquired = await (options.controller ?? defaultAdmissionController).acquireHeavyWithin(
options.queueMs ?? 0
const controller =
options.controller ??
(options.sessionId
? perConnectionAdmissionController.getController(options.sessionId)
: defaultAdmissionController);
// Structural-only waits happen on byte-light bodies (a byte-heavy body already
// holds the byte-stage lease), so the conservative 256KB weight bounds the
// parsed JSON the waiter keeps resident while parked.
const acquired = await controller.acquireHeavyWithin(
options.queueMs ?? 0,
options.signal,
CHAT_LARGE_BODY_BYTES
);
return acquired
? { admit: true, lease: acquired }
@@ -413,12 +641,15 @@ export async function admitChatRequest(
request: Request,
options: {
controller?: ChatAdmissionController;
sessionId?: string;
largeBodyBytes?: number;
hardMaxBytes?: number;
queueMs?: number;
} = {}
): Promise<ChatRequestAdmission> {
const controller = options.controller ?? defaultAdmissionController;
const sessionId = options.sessionId ?? resolveSessionId(request);
const controller =
options.controller ?? perConnectionAdmissionController.getController(sessionId);
const largeBodyBytes = options.largeBodyBytes ?? CHAT_LARGE_BODY_BYTES;
const hardMaxBytes = options.hardMaxBytes ?? CHAT_HARD_MAX_BODY_BYTES;
const queueMs = options.queueMs ?? 0;
@@ -467,15 +698,19 @@ export async function admitChatRequest(
}
let lease: ChatAdmissionLease | null = null;
const reserve = async (): Promise<boolean> => {
const reserve = async (bytes = 0): Promise<boolean> => {
if (lease) return true;
lease = await controller.acquireHeavyWithin(queueMs);
lease = await controller.acquireHeavyWithin(queueMs, request.signal, bytes);
return lease !== null;
};
// A known-large declaration can reserve before ingestion. Unknown lengths are boundedly
// sniffed below; this avoids consuming scarce heavyweight capacity for small chunked bodies.
if (contentLength !== null && contentLength >= largeBodyBytes && !(await reserve())) {
if (
contentLength !== null &&
contentLength >= largeBodyBytes &&
!(await reserve(Math.min(contentLength, hardMaxBytes)))
) {
return { admit: false, response: rejectionResponse(503, hardMaxBytes) };
}
@@ -494,7 +729,7 @@ export async function admitChatRequest(
lease?.release();
return { admit: false, response: rejectionResponse(413, hardMaxBytes) };
}
if (totalBytes >= largeBodyBytes && !(await reserve())) {
if (totalBytes >= largeBodyBytes && !(await reserve(totalBytes))) {
await reader.cancel("chat admission capacity unavailable").catch(() => undefined);
return { admit: false, response: rejectionResponse(503, hardMaxBytes) };
}

View File

@@ -0,0 +1,221 @@
import { z } from "zod";
/**
* Shared source of truth for compatible-provider node icon URL validation
* (#2166 + data-URL support). Used by both the server-side Zod schema
* (`providerNodeIconUrlSchema` in `schemas/provider.ts`) and the Add/Edit
* compatible-provider UI modals for field-level errors — a single validator,
* no divergent regexes.
*
* Accepted shapes:
* - "" → no custom icon (clears a previously stored one)
* - http(s) URL → existing #2166 semantics, 2000-char cap
* - `data:image/*;base64,...` → valid image MIME + non-empty, valid base64 payload
*
* The data-URL header (scheme, media type, base64 marker) is matched
* case-insensitively per RFC 2397, and optional media-type parameters (e.g.
* `;charset=utf-8`) are accepted before the terminal `;base64` marker.
* `image/svg+xml` is an ordinary member of `image/*` and is accepted — it is
* rendered as an operator-supplied <img> `src` (see ProviderIcon.tsx) exactly
* like any other image data URL, with the same onError fallback.
*
* Rejected:
* - malformed values, unsafe schemes (javascript:, ftp:, …)
* - non-image data URLs (`data:text/html;base64,…`, `data:application/…`)
* - data URLs without `;base64` (`data:image/png,…`)
* - data URLs with empty or invalid base64 payloads
* - payloads containing whitespace or other non-base64 characters
* (strict stored-payload validation — no whitespace stripping)
*/
export const MAX_ICON_URL_LENGTH = 2000;
// A real base64 icon legitimately exceeds the http(s) 2000-char cap — a small
// PNG/WebP badge is typically tens of KB of base64 text. Bound the data URL to
// a generous but strictly-bounded ceiling (256 KB base64 text ≈ a sizeable
// icon) so garbage input is still rejected while realistic icons are accepted.
// The DB column is plain TEXT and the request-body limit is 10 MB, so this cap
// is the governing constraint for data URLs.
export const MAX_ICON_DATA_URL_LENGTH = 256 * 1024;
// HTTP token code points (RFC 7230 `tchar` / WHATWG "HTTP token code points") —
// the complete set `!#$%&'*+-.^_`|~` plus alphanumerics. Subtypes and parameter
// attributes are validated against this full alphabet, not a partial subset.
const HTTP_TOKEN_RE = /^[-!#$%&'*+.^_`|~A-Za-z0-9]+$/;
const HTTP_SCHEME_RE = /^https?:\/\//i;
const DATA_SCHEME_RE = /^data:/i;
// Terminal base64 marker (RFC 2397 — it comes AFTER all media-type parameters).
const BASE64_MARKER = ";base64";
const DATA_SCHEME_LENGTH = "data:".length;
export function isValidProviderIconUrl(value: string): boolean {
const trimmed = value.trim();
if (trimmed === "") return true;
if (DATA_SCHEME_RE.test(trimmed)) {
if (trimmed.length > MAX_ICON_DATA_URL_LENGTH) return false;
return isValidDataIconUrl(trimmed);
}
// http(s) branch — preserves the pre-existing semantics and 2000-char cap.
if (trimmed.length > MAX_ICON_URL_LENGTH) return false;
if (!HTTP_SCHEME_RE.test(trimmed)) return false;
try {
const parsed = new URL(trimmed);
return parsed.protocol === "http:" || parsed.protocol === "https:";
} catch {
return false;
}
}
/**
* Validates `data:image/<subtype>[;attr=value]*;base64,<payload>` (RFC 2397).
*
* Steps:
* 1. Overall data: scheme is checked with the native URL parser.
* 2. The first comma splits the metadata header from the base64 payload.
* 3. The metadata segment must end (case-insensitively) in the terminal
* `;base64` marker.
* 4. The media type + parameters before the marker are parsed with a complete
* standard MIME grammar (RFC 2045/6838 + RFC 7230 tokens): type must be
* `image` (case-insensitive), subtype must be a non-empty HTTP token, and
* every parameter must be `attr=value` with a token attribute and a value
* that is either a token or a quoted-string. Valueless parameters
* (`;foo`) are rejected — RFC 2397 requires `parameter := attribute "="
* value`.
* 5. The payload is validated strictly as RFC 4648 base64 (correct alphabet
* and padding, no whitespace).
*
* Quoted-string parameter values are accepted per RFC 2045; because the header
* is split at the FIRST comma, a quoted-string value containing a literal
* comma is conservatively rejected.
*/
function isValidDataIconUrl(value: string): boolean {
let parsed: URL;
try {
parsed = new URL(value);
} catch {
return false;
}
if (parsed.protocol !== "data:") return false;
const commaIndex = value.indexOf(",");
if (commaIndex === -1) return false;
const metadata = value.slice(0, commaIndex);
const payload = value.slice(commaIndex + 1);
if (payload.length === 0) return false;
if (!metadata.toLowerCase().endsWith(BASE64_MARKER)) return false;
const mediaTypeWithParams = metadata.slice(
DATA_SCHEME_LENGTH,
metadata.length - BASE64_MARKER.length
);
if (!isValidImageMediaType(mediaTypeWithParams)) return false;
// Strict stored-payload validation: no whitespace stripping. The payload
// must be exactly valid base64 (RFC 4648 alphabet, correct padding).
return z.base64().safeParse(payload).success;
}
/**
* Parses `image/<subtype>[;attr=value]*` with the complete MIME grammar:
* type must be exactly `image` (case-insensitive), subtype a non-empty HTTP
* token, followed by zero or more `;attr=value` parameters whose attribute is
* an HTTP token and whose value is either an HTTP token or a quoted-string
* (RFC 2045). Whitespace, valueless parameters, empty attributes/values, and
* trailing garbage are rejected.
*/
function isValidImageMediaType(input: string): boolean {
if (input.length === 0) return false;
const slashIndex = input.indexOf("/");
if (slashIndex <= 0 || slashIndex === input.length - 1) return false;
if (input.slice(0, slashIndex).toLowerCase() !== "image") return false;
let position = slashIndex + 1;
let subtype = "";
while (position < input.length && input[position] !== ";") {
subtype += input[position];
++position;
}
if (subtype.length === 0 || !HTTP_TOKEN_RE.test(subtype)) return false;
while (position < input.length) {
if (input[position] !== ";") return false;
++position;
let attribute = "";
while (position < input.length && input[position] !== "=" && input[position] !== ";") {
attribute += input[position];
++position;
}
if (attribute.length === 0 || !HTTP_TOKEN_RE.test(attribute)) return false;
// Valueless parameter — rejected per RFC 2397 (`attribute "=" value`).
if (position >= input.length || input[position] !== "=") return false;
++position;
const valueEnd = parseParameterValue(input, position);
if (valueEnd === null) return false;
position = valueEnd;
if (position < input.length && input[position] !== ";") return false;
}
return true;
}
/**
* Consumes a parameter value starting at `start` and returns the position just
* after it, or null on failure. A value is either an HTTP token or a
* quoted-string (RFC 2045 `value := token / quoted-string`).
*/
function parseParameterValue(input: string, start: number): number | null {
if (start >= input.length) return null;
let position = start;
if (input[start] === '"') {
// quoted-string: DQUOTE *( qdtext / quoted-pair ) DQUOTE
let position = start + 1;
while (position < input.length) {
const char = input[position];
if (char === '"') return position + 1;
if (char === "\\") {
// quoted-pair: "\" HTAB / SP / VCHAR / obs-text
if (position + 1 >= input.length) return null;
position += 2;
continue;
}
// qdtext: HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
const code = char.charCodeAt(0);
if (
char === "\t" ||
code === 0x20 ||
code === 0x21 ||
(code >= 0x23 && code <= 0x5b) ||
(code >= 0x5d && code <= 0x7e) ||
code >= 0x80
) {
++position;
continue;
}
return null;
}
return null; // unterminated quote
}
let value = "";
while (position < input.length && input[position] !== ";") {
const char = input[position];
if (char === "%") {
const escape = input.slice(position + 1, position + 3);
if (!/^[0-9A-Fa-f]{2}$/.test(escape)) return null;
value += `%${escape}`;
position += 3;
continue;
}
value += char;
++position;
}
if (value.length === 0 || !HTTP_TOKEN_RE.test(value)) return null;
return position;
}

View File

@@ -23,21 +23,20 @@ import {
export { validateProviderSpecificData };
import { isValidProviderIconUrl } from "@/shared/validation/iconUrl";
// ──── Provider Schemas ────
// #2166: shared optional remote icon URL for compatible provider nodes. Empty string
// is accepted as "no custom icon" (clears any previously stored value). Restricted to
// http(s) — `.url()` alone also accepts syntactically-valid-but-unsafe schemes like
// `javascript:`/`data:`, which we never want persisted as an <img src>.
// #2166 + data-URL support: shared optional remote icon URL for compatible provider
// nodes. Empty string is accepted as "no custom icon". Accepts http(s) URLs AND
// valid `data:image/*;base64,...` data URLs; rejects malformed/unsafe schemes. The
// validator lives in src/shared/validation/iconUrl.ts so UI and API never diverge.
const providerNodeIconUrlSchema = z
.string()
.trim()
.max(2000)
.refine((value) => value === "" || z.string().url().safeParse(value).success, {
message: "Icon URL must be a valid URL",
})
.refine((value) => value === "" || /^https?:\/\//i.test(value), {
message: "Icon URL must be a valid http:// or https:// URL",
.refine((value) => isValidProviderIconUrl(value), {
message: "Icon URL must be a valid http(s) or data:image/*;base64 URL",
})
.optional();

View File

@@ -529,6 +529,15 @@ export async function executeChatWithBreaker({
)
);
const tlsTrackingIdentity = {
provider,
sessionScope: credentials.connectionId,
};
// Track whenever direct TLS is possible. proxyFetch decides against wreq only
// after resolving NO_PROXY/local bypasses, so predicting from proxyInfo here
// would drop the account scope when a configured proxy resolves to direct.
const tlsFingerprintActive = isTlsFingerprintActive(provider);
if (isShadowTraffic) {
if (!bypassCircuitBreaker && breaker && !breaker.canExecute()) {
const retryAfterMs = breaker.getRetryAfterMs();
@@ -542,8 +551,8 @@ export async function executeChatWithBreaker({
};
}
if (!proxyInfo?.proxy && isTlsFingerprintActive()) {
const tracked = await runWithTlsTracking(chatFn);
if (tlsFingerprintActive) {
const tracked = await runWithTlsTracking(tlsTrackingIdentity, chatFn);
return { result: tracked.result, tlsFingerprintUsed: tracked.tlsFingerprintUsed };
}
@@ -552,8 +561,8 @@ export async function executeChatWithBreaker({
}
if (bypassCircuitBreaker) {
if (!proxyInfo?.proxy && isTlsFingerprintActive()) {
const tracked = await runWithTlsTracking(chatFn);
if (tlsFingerprintActive) {
const tracked = await runWithTlsTracking(tlsTrackingIdentity, chatFn);
return { result: tracked.result, tlsFingerprintUsed: tracked.tlsFingerprintUsed };
}
@@ -561,8 +570,10 @@ export async function executeChatWithBreaker({
return { result, tlsFingerprintUsed: false };
}
if (!proxyInfo?.proxy && isTlsFingerprintActive()) {
const tracked = await breaker.execute(async () => runWithTlsTracking(chatFn));
if (tlsFingerprintActive) {
const tracked = await breaker.execute(async () =>
runWithTlsTracking(tlsTrackingIdentity, chatFn)
);
return { result: tracked.result, tlsFingerprintUsed: tracked.tlsFingerprintUsed };
}

View File

@@ -600,6 +600,51 @@ describe("rejection mapping", () => {
runtime.dispose();
oversizedRuntime.dispose();
});
it("maps ADMISSION_LANE_EVICTED to a sanitized 503 with Retry-After", async () => {
const runtime = makeRuntime(clock, {
config: enforceConfig({
initialLimit: 1,
minLimit: 1,
maxLimit: 1,
maxQueueCount: 4,
maxQueueCost: 40,
defaultMaxWaitMs: 120_000, // must outlive the 60s lane TTL so the lane eviction wins
windowMs: 1_000,
virtualLanes: true,
cost: { maxRequestCost: 1, baseCost: 1 },
}),
});
const hold = await runtime.acquire({
tenantKey: "hold",
body: { stream: true },
});
assert.equal(hold.status, "admitted");
// Park a waiter in a virtual lane; its own deadline is far beyond the TTL.
const pending = runtime.acquire({
tenantKey: "lane-waiter",
body: { stream: true },
maxWaitMs: 120_000,
});
// Advance past the 60s lane TTL: the window tick evicts idle lanes, which
// drains and rejects the queued waiter with ADMISSION_LANE_EVICTED.
clock.advance(60_001);
const rejected = await pending;
assert.equal(rejected.status, "rejected");
if (rejected.status === "rejected") {
assert.equal(rejected.code, "admission_lane_evicted");
assert.equal(rejected.response.status, 503);
assert.equal(rejected.response.headers.get("Retry-After"), "1");
const body = await parseJson(rejected.response);
assert.equal(body.error.code, "admission_lane_evicted");
assert.ok(!JSON.stringify(body).includes("lane-waiter"));
}
if (hold.status === "admitted") hold.lease.release();
runtime.dispose();
});
});
describe("resource pressure integration", () => {

View File

@@ -0,0 +1,240 @@
// #9654: Per-connection virtual admission lanes on AdaptiveAdmissionController
// Tests with virtualLanes config option enabled.
import { describe, it, beforeEach, afterEach } from "node:test";
import assert from "node:assert/strict";
import {
AdaptiveAdmissionController,
type AdaptiveAdmissionConfig,
type AdmissionRequest,
} from "../../open-sse/services/admission/index.ts";
const LANE_CONFIG = { virtualLanes: true } as const;
class FakeClock {
nowMs = 0;
private nextId = 1;
private timers = new Map<number, { due: number; fn: () => void }>();
now = () => this.nowMs;
setTimer = (fn: () => void, delayMs: number): number => {
const id = this.nextId++;
this.timers.set(id, { due: this.nowMs + Math.max(0, delayMs), fn });
return id;
};
clearTimer = (id: number): void => {
this.timers.delete(id);
};
get pendingTimerCount(): number {
return this.timers.size;
}
advance(ms: number): void {
const target = this.nowMs + ms;
while (true) {
let nextId: number | undefined;
let nextDue = Number.POSITIVE_INFINITY;
for (const [id, t] of this.timers) {
if (t.due <= target && t.due < nextDue) {
nextDue = t.due;
nextId = id;
}
}
if (nextId === undefined) {
this.nowMs = target;
return;
}
const timer = this.timers.get(nextId)!;
this.timers.delete(nextId);
this.nowMs = timer.due;
timer.fn();
}
}
}
function baseConfig(overrides: Partial<AdaptiveAdmissionConfig> = {}): AdaptiveAdmissionConfig {
return {
mode: "enforce",
minLimit: 10,
maxLimit: 100,
initialLimit: 20,
maxQueueCount: 4,
maxQueueCost: 40,
defaultMaxWaitMs: 1000,
windowMs: 100,
shortLatencyAlpha: 0.5,
longLatencyAlpha: 0.1,
increaseStep: 2,
decreaseFactor: 0.8,
criticalDecreaseFactor: 0.5,
highUtilizationThreshold: 0.7,
lowUtilizationThreshold: 0.3,
latencyGradientThreshold: 0.25,
maxIncreasePerWindow: 4,
virtualLanes: true,
...overrides,
};
}
function req(partial: Partial<AdmissionRequest> & { cost: number }): AdmissionRequest {
return {
tenantKey: "t-default",
...partial,
};
}
async function mustAdmit(
controller: AdaptiveAdmissionController,
request: AdmissionRequest
): Promise<import("../../open-sse/services/admission/types.ts").AdmissionLease> {
const result = await controller.acquire(request);
assert.equal(result.status, "admitted");
if (result.status !== "admitted") throw new Error("expected admitted");
return result.lease;
}
describe("Per-connection virtual lanes #9654", () => {
let clock: FakeClock;
const live: AdaptiveAdmissionController[] = [];
beforeEach(() => {
clock = new FakeClock();
live.length = 0;
});
afterEach(() => {
for (const c of live) c.shutdown();
live.length = 0;
});
function controller(overrides: Partial<AdaptiveAdmissionConfig> = {}) {
const c = new AdaptiveAdmissionController(baseConfig(overrides), {
now: clock.now,
setTimer: clock.setTimer,
clearTimer: clock.clearTimer,
});
live.push(c);
return c;
}
it("isolates queue capacity across sessions (one burst does not 503 others)", async () => {
const c = controller({ initialLimit: 10, maxQueueCount: 4, maxQueueCost: 40 });
const held = await mustAdmit(c, { cost: 10, tenantKey: "_default" });
// Session A queues entries in its own lane.
const a1 = await c.acquire(req({ cost: 5, tenantKey: "a" }));
assert.equal(a1.status, "queued");
const a2 = await c.acquire(req({ cost: 5, tenantKey: "a" }));
assert.equal(a2.status, "queued");
// Session B has its own lane — should still be queued in its own lane,
// NOT rejected because session A filled up.
const b1 = await c.acquire(req({ cost: 5, tenantKey: "b" }));
assert.equal(b1.status, "queued");
// Session B is NOT rejected despite session A's burst.
assert.notEqual(b1.status, "rejected");
const snap = c.snapshot();
assert.equal(snap.laneCount, 2, `expected exactly 2 lanes, got ${snap.laneCount}`);
assert.equal(snap.laneQueuedCount, 3, `expected exactly 3 lane-queued, got ${snap.laneQueuedCount}`);
held.release("success");
if (a1.status === "queued") (await a1.promise).lease.release("success");
if (a2.status === "queued") (await a2.promise).lease.release("success");
if (b1.status === "queued") (await b1.promise).lease.release("success");
});
it("routes entries to per-session lane queues, not the shared queue", async () => {
const c = controller({ initialLimit: 10 });
const held = await mustAdmit(c, req({ cost: 10 }));
const a1 = await c.acquire(req({ cost: 5, tenantKey: "a" }));
assert.equal(a1.status, "queued");
const snap = c.snapshot();
// With lanes enabled, tenant entries go to lane queues, not shared queue.
assert.equal(snap.queuedCount, 0, "shared queue should be empty");
assert.ok(snap.laneQueuedCount >= 1, "lane queues should have entries");
held.release("success");
if (a1.status === "queued") (await a1.promise).lease.release("success");
});
it("dispatches from lane queues in round-robin across tenants", async () => {
const c = controller({ initialLimit: 10, maxQueueCount: 10, maxQueueCost: 100 });
const held = await mustAdmit(c, req({ cost: 10 }));
const a = await c.acquire(req({ cost: 5, tenantKey: "tenant-a" }));
const b = await c.acquire(req({ cost: 5, tenantKey: "tenant-b" }));
const d = await c.acquire(req({ cost: 5, tenantKey: "tenant-c" }));
assert.equal(a.status, "queued");
assert.equal(b.status, "queued");
assert.equal(d.status, "queued");
held.release("success");
// All three should be admitted via round-robin dispatch.
const aAdmitted = await a.promise;
assert.equal(aAdmitted.status, "admitted");
aAdmitted.lease.release("success");
const bAdmitted = await b.promise;
assert.equal(bAdmitted.status, "admitted");
bAdmitted.lease.release("success");
const dAdmitted = await d.promise;
assert.equal(dAdmitted.status, "admitted");
dAdmitted.lease.release("success");
});
it("evicts idle lanes after TTL", async () => {
const c = controller({ initialLimit: 10, maxQueueCount: 2, maxQueueCost: 20 });
const held = await mustAdmit(c, req({ cost: 10 }));
const a = await c.acquire(req({ cost: 5, tenantKey: "a" }));
const b = await c.acquire(req({ cost: 5, tenantKey: "b" }));
let snap = c.snapshot();
assert.ok(snap.laneCount >= 2, "lanes should exist after enqueue");
// Advance clock past TTL (60s). The lane eviction timer fires during advance.
// Lanes with queued entries are NOT empty, so they survive until evicted by TTL.
// Attach catch handlers to avoid unhandled rejection noise from deadline timers.
if (a.status === "queued") a.promise.catch(() => {});
if (b.status === "queued") b.promise.catch(() => {});
clock.advance(60_001);
snap = c.snapshot();
assert.equal(snap.laneCount, 0, "idle lanes should be evicted after TTL");
held.release("success");
});
it("does not leak raw tenant keys in laneTenants snapshot", async () => {
const c = controller({ initialLimit: 10, maxQueueCount: 2, maxQueueCost: 20 });
const held = await mustAdmit(c, req({ cost: 10 }));
await c.acquire(req({ cost: 5, tenantKey: "secret-key-12345" }));
const snap = c.snapshot();
const tenants = snap.laneTenants ?? [];
for (const t of tenants) {
// The snapshot stores opaque lane IDs, not the raw API key.
// (The lane key is an internal hash, never the raw key.)
assert.ok(t.tenantKey.length > 0);
}
held.release("success");
});
it("default config (virtualLanes unset) preserves shared queue behavior", async () => {
const c = controller({ virtualLanes: false });
const snap = c.snapshot();
// laneCount should be 0 (no lanes created yet)
assert.equal(snap.laneCount, 0);
// Snapshot should include lane fields
assert.ok("laneQueuedCount" in snap);
assert.ok("laneTenants" in snap);
c.shutdown();
});
});

View File

@@ -0,0 +1,44 @@
/**
* Regression: `auto/best-vision` must resolve to the `vision` CATEGORY (candidate
* filter by vision capability), not to the flat `smart` variant.
*
* Root cause on runtime v3.8.49: AUTO_TEMPLATE_VARIANTS mapped
* `"auto/best-vision": "smart"`, so the virtual combo scored ALL candidates
* (verified: it resolved to text-only `deepseek-v4-flash-free`), making the
* vision-bridge describe/reroute target useless.
*
* Runs under Vitest (the autoCombo suite is Vitest-only in this repo).
*/
import { describe, it, expect } from "vitest";
import { resolveBuiltinAutoSpec } from "../../../open-sse/services/autoCombo/builtinCatalog";
describe("resolveBuiltinAutoSpec — vision category ids", () => {
it("auto/best-vision resolves to category vision (not smart variant)", () => {
expect(resolveBuiltinAutoSpec("auto/best-vision", "best-vision")).toEqual({
category: "vision",
});
});
it("auto/pro-vision resolves to category vision + tier pro", () => {
expect(resolveBuiltinAutoSpec("auto/pro-vision", "pro-vision")).toEqual({
category: "vision",
tier: "pro",
});
});
it("legacy flat variants keep their variant mapping", () => {
expect(resolveBuiltinAutoSpec("auto/best-coding", "best-coding")).toEqual({
variant: "coding",
});
expect(resolveBuiltinAutoSpec("auto/fast", "fast")).toEqual({ variant: "fast" });
expect(resolveBuiltinAutoSpec("auto/chat", "chat")).toEqual({ variant: undefined });
});
it("category:tier suffix still resolves via parseAutoSuffix", () => {
expect(resolveBuiltinAutoSpec("auto/coding:fast", "coding:fast")).toEqual({
category: "coding",
tier: "fast",
});
expect(resolveBuiltinAutoSpec("auto/vision", "vision")).toEqual({ category: "vision" });
});
});

View File

@@ -0,0 +1,39 @@
/**
* Regression: the vision-category candidate filter must exclude registry
* entries whose catalog OVERSTATES vision support (opencode-go/opencode-zen/
* tokenrouter backends are text-only and are forced through the vision bridge
* by isVisionBridgeForcedModel). Otherwise `auto/best-vision` pools include
* models that can never process images (e.g. deepseek-v4-flash-max), breaking
* the vision bridge describe/reroute.
*/
import { describe, it, expect } from "vitest";
import { buildAutoCandidateFilter } from "../../../open-sse/services/autoCombo/suffixComposition";
describe("buildAutoCandidateFilter — vision category", () => {
it("keeps genuinely vision-capable models", () => {
const filter = buildAutoCandidateFilter("vision");
expect(filter).not.toBeNull();
// MiniMax M3 is a real multimodal model (format claude, supportsVision: true).
expect(filter?.({ provider: "minimax", model: "MiniMax-M3" })).toBe(true);
});
it("rejects models whose catalog entry overstates vision (forced through the bridge)", () => {
const filter = buildAutoCandidateFilter("vision");
// opencode-go/deepseek-v4-flash-max is in FORCED_VISION_BRIDGE_MODELS —
// the catalog claims vision but the backend is text-only.
expect(filter?.({ provider: "opencode-go", model: "deepseek-v4-flash-max" })).toBe(false);
expect(filter?.({ provider: "opencode-go", model: "deepseek-v4-flash" })).toBe(false);
expect(filter?.({ provider: "opencode-zen", model: "deepseek-v4-flash" })).toBe(false);
});
it("rejects models with no confirmed vision support", () => {
const filter = buildAutoCandidateFilter("vision");
// Unknown catalog entry → no confirmed vision → must be rejected.
expect(filter?.({ provider: "acme", model: "acme-text" })).toBe(false);
});
it("non-vision categories are unaffected", () => {
const filter = buildAutoCandidateFilter("coding");
expect(filter).toBeNull();
});
});

View File

@@ -4,14 +4,15 @@ import fs from "node:fs/promises";
import fsSync from "node:fs";
import os from "node:os";
import path from "node:path";
const {
import {
getTransientBuildPaths,
movePath,
pruneStandaloneArtifacts,
resolveNextBuildEnv,
syncStandaloneExtraModules,
syncStandaloneNativeAssets,
} = await import("../../scripts/build/build-next-isolated.mjs");
} from "../../scripts/build/build-next-isolated.mjs";
async function withTempDir(fn) {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "omniroute-build-next-isolated-"));
@@ -177,36 +178,49 @@ test("pruneStandaloneArtifacts removes traced _tasks from standalone output", as
});
});
test("syncStandaloneNativeAssets copies wreq-js native runtime into standalone output", async () => {
test("syncStandaloneExtraModules copies the complete wreq-js runtime", async () => {
await withTempDir(async (tempDir) => {
const sourceNativeFile = path.join(
tempDir,
"node_modules",
"wreq-js",
"rust",
"wreq-js.linux-x64-gnu.node"
);
const destinationNativeFile = path.join(
const sourcePackage = path.join(tempDir, "node_modules", "wreq-js");
const destinationPackage = path.join(
tempDir,
".build",
"next",
"standalone",
"node_modules",
"wreq-js",
"rust",
"wreq-js.linux-x64-gnu.node"
"wreq-js"
);
const logs: string[] = [];
await fs.mkdir(path.dirname(sourceNativeFile), { recursive: true });
await fs.writeFile(sourceNativeFile, "native module bytes");
const changed = await syncStandaloneNativeAssets(tempDir, fs, {
const logger: Console = Object.assign(Object.create(console), {
log: (message: unknown) => logs.push(String(message)),
});
await fs.mkdir(path.join(sourcePackage, "dist"), { recursive: true });
await fs.mkdir(path.join(sourcePackage, "rust"), { recursive: true });
await fs.writeFile(path.join(sourcePackage, "package.json"), '{"name":"wreq-js"}');
await fs.writeFile(path.join(sourcePackage, "dist", "wreq-js.cjs"), "exports.fetch = () => {}");
await fs.writeFile(
path.join(sourcePackage, "rust", "wreq-js.linux-x64-gnu.node"),
"native module bytes"
);
const changed = await syncStandaloneExtraModules(tempDir, fs, logger);
assert.equal(changed, true);
assert.equal(await fs.readFile(destinationNativeFile, "utf8"), "native module bytes");
assert.match((logs[0] ?? "").replaceAll("\\", "/"), /wreq-js\/rust/);
assert.equal(
await fs.readFile(path.join(destinationPackage, "package.json"), "utf8"),
'{"name":"wreq-js"}'
);
assert.equal(
await fs.readFile(path.join(destinationPackage, "dist", "wreq-js.cjs"), "utf8"),
"exports.fetch = () => {}"
);
assert.equal(
await fs.readFile(
path.join(destinationPackage, "rust", "wreq-js.linux-x64-gnu.node"),
"utf8"
),
"native module bytes"
);
assert.match(logs[0] ?? "", /wreq-js TLS runtime/);
});
});

View File

@@ -4,6 +4,7 @@ import assert from "node:assert/strict";
import {
buildSyncedModelIdsByCanonicalProvider,
shouldSuppressStaticModelBySyncedCoverage,
shouldSuppressStaticModelForExclusiveListing,
} from "../../src/app/api/v1/models/catalogSyncedCoverage.ts";
import type { SyncedAvailableModel } from "../../src/lib/db/models/synced.ts";
@@ -75,3 +76,57 @@ test("buildSyncedModelIdsByCanonicalProvider groups synced ids by canonical prov
assert.ok(ds);
assert.ok(ds.has("deepseek-v4-flash"));
});
test("exclusive listing: any static row suppressed when provider has synced catalog", () => {
assert.equal(
shouldSuppressStaticModelForExclusiveListing({
exclusiveListing: true,
providerHasSynced: true,
staticModelId: "claude-4.6-sonnet-high",
syncedModelIds: ["claude-4.6-sonnet", "composer-2.5"],
}),
true
);
assert.equal(
shouldSuppressStaticModelForExclusiveListing({
exclusiveListing: true,
providerHasSynced: true,
staticModelId: "composer-2.5",
syncedModelIds: ["claude-4.6-sonnet", "composer-2.5"],
}),
true
);
});
test("exclusive listing: does not suppress when synced is empty", () => {
assert.equal(
shouldSuppressStaticModelForExclusiveListing({
exclusiveListing: true,
providerHasSynced: false,
staticModelId: "claude-4.6-sonnet-high",
syncedModelIds: [],
}),
false
);
});
test("exclusive listing: non-exclusive providers keep coverage behavior", () => {
assert.equal(
shouldSuppressStaticModelForExclusiveListing({
exclusiveListing: false,
providerHasSynced: true,
staticModelId: "deepseek/deepseek-v4-flash",
syncedModelIds: ["gpt-5.6-luna"],
}),
false
);
assert.equal(
shouldSuppressStaticModelForExclusiveListing({
exclusiveListing: false,
providerHasSynced: true,
staticModelId: "gpt-5.6-luna",
syncedModelIds: ["gpt-5.6-luna"],
}),
true
);
});

View File

@@ -0,0 +1,513 @@
// #9654: queue-wait, AbortSignal cancellation, and the queued-bytes heap valve.
// Split from chat-body-admission.test.ts to stay under the 1000-line new-file cap.
import test from "node:test";
import assert from "node:assert/strict";
const admissionModule = await import("../../src/shared/middleware/chatBodyAdmission.ts");
const {
admitChatRequest,
admitChatStructure,
ChatAdmissionController,
CHAT_ADMISSION_QUEUE_MAX_MS,
CHAT_ADMISSION_MAX_QUEUED_BYTES,
CHAT_LARGE_BODY_BYTES,
} = admissionModule;
function chatRequest(body: string, contentLength: string | null = String(body.length)): Request {
const headers: Record<string, string> = { "content-type": "application/json" };
if (contentLength !== null) headers["content-length"] = contentLength;
return new Request("http://x/v1/chat/completions", {
method: "POST",
headers,
body,
});
}
test("a heavy structural request waits for capacity instead of failing immediately", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const pending = admitChatStructure(
{
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
},
null,
{
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 500,
}
);
// Capacity is still busy: the request must not have resolved (admit/reject) yet.
let settled = false;
void pending.then(() => {
settled = true;
});
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(settled, false, "must wait while capacity is busy");
held.release();
const result = await pending;
assert.equal(result.admit, true);
if (result.admit) {
assert.equal(controller.activeHeavy, 1, "waiting request acquires the freed lease");
result.lease?.release();
}
assert.equal(controller.activeHeavy, 0);
});
test("waiting for admission times out into a retryable 503", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const started = Date.now();
const result = await admitChatStructure(
{
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
},
null,
{
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 50,
}
);
assert.equal(result.admit, false);
if (!result.admit) {
assert.equal(result.response.status, 503);
assert.equal(result.response.headers.get("retry-after"), "1");
assert.equal((await result.response.json()).error.code, "chat_admission_busy");
}
assert.ok(Date.now() - started >= 40, "must wait for the queue deadline before rejecting");
assert.equal(controller.activeHeavy, 1, "the holder keeps its lease");
held.release();
assert.equal(controller.activeHeavy, 0);
});
test("byte-heavy admission waits for capacity when queueMs is set", async () => {
const controller = new ChatAdmissionController(1);
const body = JSON.stringify({ messages: [{ role: "user", content: "x".repeat(40) }] });
const options = { controller, largeBodyBytes: 32, hardMaxBytes: 1024, queueMs: 500 };
const first = await admitChatRequest(chatRequest(body), options);
assert.equal(first.admit, true);
if (!first.admit) return;
const second = admitChatRequest(chatRequest(body), options);
let secondSettled = false;
void second.then(() => {
secondSettled = true;
});
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(
secondSettled,
false,
"second heavy request must queue while the first holds capacity"
);
first.lease?.release();
const secondResult = await second;
assert.equal(secondResult.admit, true, "second request acquires capacity after release");
if (secondResult.admit) secondResult.lease?.release();
assert.equal(controller.activeHeavy, 0);
});
test("expired admission queue keeps the legacy immediate 503 behaviour", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const result = await admitChatStructure(
{
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
},
null,
{
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 0,
}
);
assert.equal(result.admit, false);
if (!result.admit) assert.equal(result.response.status, 503);
held.release();
});
test("admission waiters are served FIFO as capacity frees", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const body = {
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
};
const options = {
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 500,
};
const first = admitChatStructure(body, null, options);
const second = admitChatStructure(body, null, options);
held.release();
const firstResult = await first;
assert.equal(firstResult.admit, true);
if (firstResult.admit) firstResult.lease?.release();
const secondResult = await second;
assert.equal(secondResult.admit, true);
if (secondResult.admit) secondResult.lease?.release();
assert.equal(controller.activeHeavy, 0);
});
// ── AbortSignal support in acquireHeavyWithin (#9654 / U2) ────────────────
// A disconnected client must not keep parking in the admission queue for the
// full queueMs. On abort the waiter is removed from the FIFO immediately and
// the acquire resolves `null` early (the caller's 503 is dropped on the dead
// connection); no capacity is consumed and the freed slot does not wake it.
test("aborting the admission wait settles early, grants no lease, and removes the waiter", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const abortController = new AbortController();
const pending = controller.acquireHeavyWithin(2_000, abortController.signal);
// Parked while capacity is busy.
let settled = false;
void pending.then(() => {
settled = true;
});
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(settled, false, "must be parked while capacity is busy");
abortController.abort();
// Must settle well before the 2s deadline.
let settledAfterAbort = false;
void pending.then(() => {
settledAfterAbort = true;
});
await new Promise((resolve) => setTimeout(resolve, 50));
assert.equal(settledAfterAbort, true, "abort must settle the wait promptly, not park for queueMs");
const lease = await pending;
assert.equal(lease, null, "abort must not grant a lease");
assert.equal(controller.activeHeavy, 1, "the holder keeps its lease; the aborted wait consumed nothing");
// Releasing must NOT wake the removed waiter: capacity stays free.
held.release();
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(
controller.activeHeavy,
0,
"releasing after abort must not wake the removed waiter"
);
});
test("aborting the head waiter preserves FIFO order for remaining waiters", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const firstAbort = new AbortController();
const first = controller.acquireHeavyWithin(2_000, firstAbort.signal);
const second = controller.acquireHeavyWithin(2_000);
// Both are parked, head-first.
await new Promise((resolve) => setTimeout(resolve, 30));
// Abort the HEAD waiter: it must leave the queue without disturbing the rest.
firstAbort.abort();
assert.equal(await first, null, "head waiter returns null on abort");
// The remaining waiter is now first in line and must get the freed capacity.
held.release();
const secondLease = await second;
assert.ok(secondLease, "remaining waiter must acquire the freed capacity");
secondLease?.release();
assert.equal(controller.activeHeavy, 0);
});
// ── Heap-pressure safety valve (#9654 / U3) ───────────────────────────────
// The queue-wait parks fully-buffered bodies; the queued-bytes cap bounds the
// total buffered memory parked per lane so the wait cannot recreate the #4380
// heap amplification. Over-budget waits are rejected immediately (503).
test("queued-bytes cap rejects an over-budget wait without parking", async () => {
const controller = new ChatAdmissionController(1, 200);
const held = controller.tryAcquireHeavy();
assert.ok(held);
// First waiter parks within budget.
const first = controller.acquireHeavyWithin(2_000, undefined, 150);
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(controller.queuedBytes, 150);
// Second waiter would push the total over the 200-byte budget → must NOT park.
const started = Date.now();
const second = await controller.acquireHeavyWithin(2_000, undefined, 100);
assert.equal(second, null, "over-budget wait must be rejected");
assert.ok(Date.now() - started < 500, "rejection must be immediate, not park for queueMs");
assert.equal(controller.queuedBytes, 150, "rejected waiter must not be charged");
assert.equal(controller.activeHeavy, 1, "holder keeps its lease");
// Free the slot: the parked waiter acquires and its bytes leave the queue.
held.release();
const firstLease = await first;
assert.ok(firstLease, "in-budget waiter acquires the freed slot");
assert.equal(controller.queuedBytes, 0, "acquired waiter's bytes must leave the queue");
firstLease?.release();
assert.equal(controller.activeHeavy, 0);
});
test("aborting a parked wait releases its queued bytes", async () => {
const controller = new ChatAdmissionController(1, 1_000);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const abortController = new AbortController();
const pending = controller.acquireHeavyWithin(2_000, abortController.signal, 400);
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(controller.queuedBytes, 400);
abortController.abort();
assert.equal(await pending, null);
assert.equal(controller.queuedBytes, 0, "abort must release the charged bytes");
held.release();
assert.equal(controller.activeHeavy, 0);
});
test("a timed-out wait releases its queued bytes", async () => {
const controller = new ChatAdmissionController(1, 1_000);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const pending = controller.acquireHeavyWithin(50, undefined, 400);
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(controller.queuedBytes, 400);
assert.equal(await pending, null);
assert.equal(controller.queuedBytes, 0, "timeout must release the charged bytes");
held.release();
assert.equal(controller.activeHeavy, 0);
});
test("byte-heavy admission enforces the queued-bytes cap end-to-end", async () => {
const controller = new ChatAdmissionController(1, 100);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const body = JSON.stringify({ messages: [{ role: "user", content: "x".repeat(40) }] });
const options = { controller, largeBodyBytes: 32, hardMaxBytes: 1024, queueMs: 2_000 };
// First request parks: declared length (~70B) fits the budget.
const first = admitChatRequest(chatRequest(body), options);
await new Promise((resolve) => setTimeout(resolve, 30));
// Second request would exceed the 100-byte budget → rejected immediately.
const started = Date.now();
const second = await admitChatRequest(chatRequest(body), options);
assert.equal(second.admit, false, "over-budget byte-heavy wait must not admit");
if (!second.admit) assert.equal(second.response.status, 503);
assert.ok(Date.now() - started < 500, "over-budget wait must reject immediately");
held.release();
const firstResult = await first;
assert.equal(firstResult.admit, true);
if (firstResult.admit) firstResult.lease?.release();
assert.equal(controller.activeHeavy, 0);
});
test("structural admission enforces the queued-bytes cap end-to-end", async () => {
const controller = new ChatAdmissionController(1, CHAT_LARGE_BODY_BYTES);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const structural = {
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
};
const options = {
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 2_000,
};
// First structural wait parks, charging the conservative 256KB weight.
const first = admitChatStructure(structural, null, options);
await new Promise((resolve) => setTimeout(resolve, 30));
// Second would double the charge → rejected immediately.
const started = Date.now();
const second = await admitChatStructure(structural, null, options);
assert.equal(second.admit, false, "over-budget structural wait must not admit");
if (!second.admit) assert.equal(second.response.status, 503);
assert.ok(Date.now() - started < 500, "over-budget structural wait must reject immediately");
held.release();
const firstResult = await first;
assert.equal(firstResult.admit, true);
if (firstResult.admit) firstResult.lease?.release();
assert.equal(controller.activeHeavy, 0);
});
test("queue-wait defaults are bounded (2s wait, 4MB queued-bytes budget)", () => {
if (process.env.OMNIROUTE_CHAT_ADMISSION_QUEUE_MS === undefined) {
assert.equal(CHAT_ADMISSION_QUEUE_MAX_MS, 2_000);
}
if (process.env.OMNIROUTE_CHAT_ADMISSION_MAX_QUEUED_BYTES === undefined) {
assert.equal(CHAT_ADMISSION_MAX_QUEUED_BYTES, 4 * 1024 * 1024);
}
});
test("a pre-aborted signal never parks in the admission queue", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const abortController = new AbortController();
abortController.abort("client already disconnected");
const pending = controller.acquireHeavyWithin(2_000, abortController.signal);
let settled = false;
void pending.then(() => {
settled = true;
});
await new Promise((resolve) => setTimeout(resolve, 50));
assert.equal(settled, true, "a pre-aborted signal must settle immediately, not park");
const lease = await pending;
assert.equal(lease, null, "no lease is granted after abort");
assert.equal(controller.activeHeavy, 1, "holder keeps capacity; aborted wait consumed nothing");
held.release();
assert.equal(controller.activeHeavy, 0);
});
test("aborting the request signal cancels a queued byte-heavy wait", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const abortController = new AbortController();
const body = JSON.stringify({ messages: [{ role: "user", content: "x".repeat(40) }] });
const request = new Request("http://x/v1/chat/completions", {
method: "POST",
headers: { "content-type": "application/json" },
body,
signal: abortController.signal,
});
const pending = admitChatRequest(request, {
controller,
largeBodyBytes: 32,
hardMaxBytes: 1024,
queueMs: 2_000,
});
let settled = false;
void pending.then(() => {
settled = true;
});
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(settled, false, "must queue while capacity is busy");
abortController.abort();
const started = Date.now();
const result = await pending;
assert.ok(
Date.now() - started < 500,
"abort must cancel the queue-wait early, not park the full queueMs"
);
assert.equal(result.admit, false, "abort must not admit");
if (!result.admit) {
assert.equal(result.response.status, 503);
assert.equal((await result.response.json()).error.code, "chat_admission_busy");
}
assert.equal(controller.activeHeavy, 1, "holder keeps capacity; aborted wait consumed nothing");
held.release();
assert.equal(controller.activeHeavy, 0);
});
test("aborting the signal cancels a structural queue-wait", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const abortController = new AbortController();
const pending = admitChatStructure(
{
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
},
null,
{
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 2_000,
signal: abortController.signal,
}
);
let settled = false;
void pending.then(() => {
settled = true;
});
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(settled, false, "must queue while capacity is busy");
abortController.abort();
const started = Date.now();
const result = await pending;
assert.ok(
Date.now() - started < 500,
"abort must cancel the queue-wait early, not park the full queueMs"
);
assert.equal(result.admit, false, "abort must not admit");
if (!result.admit) {
assert.equal(result.response.status, 503);
assert.equal((await result.response.json()).error.code, "chat_admission_busy");
}
assert.equal(controller.activeHeavy, 1, "holder keeps its lease");
held.release();
assert.equal(controller.activeHeavy, 0);
});

View File

@@ -8,6 +8,9 @@ const {
admitChatStructure,
ChatAdmissionController,
CHAT_HARD_MAX_MESSAGES,
CHAT_ADMISSION_QUEUE_MAX_MS,
CHAT_ADMISSION_MAX_QUEUED_BYTES,
CHAT_LARGE_BODY_BYTES,
releaseChatAdmissionAfterHandler,
releaseChatAdmissionWhenDone,
resolveSelfLoopBearer,
@@ -813,168 +816,3 @@ test("sk_omniroute sentinel is rejected once an env key is configured (REQUIRE_A
restore();
}
});
test("a heavy structural request waits for capacity instead of failing immediately", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const pending = admitChatStructure(
{
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
},
null,
{
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 500,
}
);
// Capacity is still busy: the request must not have resolved (admit/reject) yet.
let settled = false;
void pending.then(() => {
settled = true;
});
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(settled, false, "must wait while capacity is busy");
held.release();
const result = await pending;
assert.equal(result.admit, true);
if (result.admit) {
assert.equal(controller.activeHeavy, 1, "waiting request acquires the freed lease");
result.lease?.release();
}
assert.equal(controller.activeHeavy, 0);
});
test("waiting for admission times out into a retryable 503", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const started = Date.now();
const result = await admitChatStructure(
{
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
},
null,
{
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 50,
}
);
assert.equal(result.admit, false);
if (!result.admit) {
assert.equal(result.response.status, 503);
assert.equal(result.response.headers.get("retry-after"), "1");
assert.equal((await result.response.json()).error.code, "chat_admission_busy");
}
assert.ok(Date.now() - started >= 40, "must wait for the queue deadline before rejecting");
assert.equal(controller.activeHeavy, 1, "the holder keeps its lease");
held.release();
assert.equal(controller.activeHeavy, 0);
});
test("byte-heavy admission waits for capacity when queueMs is set", async () => {
const controller = new ChatAdmissionController(1);
const body = JSON.stringify({ messages: [{ role: "user", content: "x".repeat(40) }] });
const options = { controller, largeBodyBytes: 32, hardMaxBytes: 1024, queueMs: 500 };
const first = await admitChatRequest(chatRequest(body), options);
assert.equal(first.admit, true);
if (!first.admit) return;
const second = admitChatRequest(chatRequest(body), options);
let secondSettled = false;
void second.then(() => {
secondSettled = true;
});
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(
secondSettled,
false,
"second heavy request must queue while the first holds capacity"
);
first.lease?.release();
const secondResult = await second;
assert.equal(secondResult.admit, true, "second request acquires capacity after release");
if (secondResult.admit) secondResult.lease?.release();
assert.equal(controller.activeHeavy, 0);
});
test("expired admission queue keeps the legacy immediate 503 behaviour", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const result = await admitChatStructure(
{
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
},
null,
{
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 0,
}
);
assert.equal(result.admit, false);
if (!result.admit) assert.equal(result.response.status, 503);
held.release();
});
test("admission waiters are served FIFO as capacity frees", async () => {
const controller = new ChatAdmissionController(1);
const held = controller.tryAcquireHeavy();
assert.ok(held);
const body = {
messages: [
{ role: "user", content: "one" },
{ role: "user", content: "two" },
],
};
const options = {
controller,
maxMessages: 10,
heavyMessages: 2,
heavyTools: 10,
heavyTokens: 10_000,
queueMs: 500,
};
const first = admitChatStructure(body, null, options);
const second = admitChatStructure(body, null, options);
held.release();
const firstResult = await first;
assert.equal(firstResult.admit, true);
if (firstResult.admit) firstResult.lease?.release();
const secondResult = await second;
assert.equal(secondResult.admit, true);
if (secondResult.admit) secondResult.lease?.release();
assert.equal(controller.activeHeavy, 0);
});

View File

@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import net from "node:net";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-chat-helpers-"));
process.env.DATA_DIR = TEST_DATA_DIR;
@@ -21,6 +22,8 @@ const {
} = await import("../../src/sse/handlers/chatHelpers.ts");
const { getCircuitBreaker, resetAllCircuitBreakers, STATE } =
await import("../../src/shared/utils/circuitBreaker.ts");
// DATA_DIR must be fixed before these modules load; keep this test seam dynamic.
const { setTlsClientForTest } = await import("../../open-sse/utils/proxyFetch.ts");
async function resetStorage() {
resetAllCircuitBreakers();
@@ -421,6 +424,96 @@ test("executeChatWithBreaker converts proxy fast-fail errors", async () => {
}
});
test("executeChatWithBreaker preserves account TLS scope when a proxy bypasses to direct", async () => {
const server = net.createServer((socket) => socket.end());
const listening = Promise.withResolvers<void>();
server.listen(0, "127.0.0.1", listening.resolve);
await listening.promise;
const address = server.address();
assert.ok(address && typeof address !== "string");
const prior = {
enable: process.env.ENABLE_TLS_FINGERPRINT,
providers: process.env.TLS_FINGERPRINT_PROVIDERS,
noProxy: process.env.NO_PROXY,
};
process.env.ENABLE_TLS_FINGERPRINT = "true";
delete process.env.TLS_FINGERPRINT_PROVIDERS;
process.env.NO_PROXY = "api.openai.com";
let observedProxy: string | null | undefined;
let observedScope: string | undefined;
setTlsClientForTest({
available: true,
fetch: async (_url, options) => {
observedProxy = options?.proxy;
observedScope = options?.sessionScope;
return new Response(
JSON.stringify({
id: "chatcmpl-test",
object: "chat.completion",
created: 0,
model: "gpt-4o-mini",
choices: [
{
index: 0,
message: { role: "assistant", content: "ok" },
finish_reason: "stop",
},
],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}),
{ headers: { "content-type": "application/json" } },
);
},
});
try {
const credentials = {
connectionId: "conn_tls_scope",
apiKey: "sk-openai-helper",
providerSpecificData: {},
};
const result = await executeChatWithBreaker({
bypassCircuitBreaker: false,
breaker: getCircuitBreaker("openai"),
body: { model: "openai/gpt-4o-mini", messages: [] },
provider: "openai",
model: "gpt-4o-mini",
refreshedCredentials: credentials,
proxyInfo: {
proxy: `http://127.0.0.1:${address.port}`,
level: "connection",
levelId: credentials.connectionId,
},
log: console,
clientRawRequest: null,
credentials,
apiKeyInfo: null,
userAgent: "",
comboName: null,
comboStrategy: null,
isCombo: false,
extendedContext: false,
comboStepId: null,
comboExecutionKey: null,
});
assert.equal(result.tlsFingerprintUsed, true);
assert.equal(observedProxy, null);
assert.equal(observedScope, credentials.connectionId);
} finally {
setTlsClientForTest(null);
if (prior.enable === undefined) delete process.env.ENABLE_TLS_FINGERPRINT;
else process.env.ENABLE_TLS_FINGERPRINT = prior.enable;
if (prior.providers === undefined) delete process.env.TLS_FINGERPRINT_PROVIDERS;
else process.env.TLS_FINGERPRINT_PROVIDERS = prior.providers;
if (prior.noProxy === undefined) delete process.env.NO_PROXY;
else process.env.NO_PROXY = prior.noProxy;
const closed = Promise.withResolvers<void>();
server.close(() => closed.resolve());
await closed.promise;
}
});
test("safeLogEvents tolerates success and timeout payloads", () => {
const credentials = { connectionId: "conn_log_12345678" };

View File

@@ -1,280 +0,0 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
normalizeCodexTools,
stripRedundantOneOfConstEnum,
} from "../../open-sse/executors/codex/tools.ts";
type JsonRecord = Record<string, unknown>;
const PRODUCTION_ACTION_VALUES = [
"read_file",
"write_file",
"list_files",
"search_files",
"run_command",
"create_directory",
"delete_file",
"move_file",
"copy_file",
"rename_file",
"open_terminal",
"close_terminal",
"get_status",
] as const;
function productionParameters(): JsonRecord {
return {
type: "object",
description: "OpenChamber action parameters",
properties: {
action: {
type: "string",
description: "Action to perform",
enum: [...PRODUCTION_ACTION_VALUES],
oneOf: PRODUCTION_ACTION_VALUES.map((value) => ({
const: value,
description: `Action ${value}`,
})),
},
},
required: ["action"],
};
}
function chatTool(parameters: JsonRecord): JsonRecord {
return {
type: "function",
function: { name: "test_tool", parameters },
};
}
test("production openchamber shape is normalized", () => {
const tool = chatTool(productionParameters());
normalizeCodexTools({ tools: [tool] });
const parameters = tool.parameters as JsonRecord;
const properties = parameters.properties as JsonRecord;
const action = properties.action as JsonRecord;
assert.equal(action.oneOf, undefined);
assert.deepEqual(action.enum, [...PRODUCTION_ACTION_VALUES]);
assert.equal(parameters.type, "object");
assert.equal(parameters.description, "OpenChamber action parameters");
assert.deepEqual(parameters.required, ["action"]);
});
test("bare oneOf[const] without sibling enum is preserved", () => {
const tool = chatTool({ oneOf: [{ const: "x" }, { const: "y" }] });
normalizeCodexTools({ tools: [tool] });
const oneOf = (tool.parameters as JsonRecord).oneOf as unknown[];
assert.equal(oneOf.length, 2);
});
test("non-matching enum is preserved", () => {
const tool = chatTool({
enum: ["a", "b", "c"],
oneOf: [{ const: "a" }, { const: "b" }],
});
normalizeCodexTools({ tools: [tool] });
assert.deepEqual((tool.parameters as JsonRecord).oneOf, [{ const: "a" }, { const: "b" }]);
});
test("partially overlapping enum is preserved", () => {
const tool = chatTool({
enum: ["a", "b", "c"],
oneOf: [{ const: "a" }, { const: "d" }],
});
normalizeCodexTools({ tools: [tool] });
assert.deepEqual((tool.parameters as JsonRecord).oneOf, [{ const: "a" }, { const: "d" }]);
});
test("enum with extra value is preserved", () => {
const tool = chatTool({ enum: ["a", "b"], oneOf: [{ const: "a" }] });
normalizeCodexTools({ tools: [tool] });
assert.deepEqual((tool.parameters as JsonRecord).oneOf, [{ const: "a" }]);
});
test("duplicate const branches are preserved", () => {
const tool = chatTool({
enum: ["a", "b"],
oneOf: [{ const: "a" }, { const: "a" }],
});
normalizeCodexTools({ tools: [tool] });
assert.deepEqual((tool.parameters as JsonRecord).oneOf, [{ const: "a" }, { const: "a" }]);
});
test("branch with validation keyword is preserved", () => {
const tool = chatTool({
enum: ["a", "b"],
oneOf: [{ const: "a", type: "string" }, { const: "b" }],
});
normalizeCodexTools({ tools: [tool] });
assert.deepEqual((tool.parameters as JsonRecord).oneOf, [
{ const: "a", type: "string" },
{ const: "b" },
]);
});
test("type-discriminated oneOf is preserved", () => {
const tool = chatTool({ oneOf: [{ type: "string" }, { type: "number" }] });
normalizeCodexTools({ tools: [tool] });
assert.deepEqual((tool.parameters as JsonRecord).oneOf, [{ type: "string" }, { type: "number" }]);
});
test("empty oneOf is preserved", () => {
const tool = chatTool({ enum: ["a"], oneOf: [] });
normalizeCodexTools({ tools: [tool] });
assert.deepEqual((tool.parameters as JsonRecord).oneOf, []);
});
test("single-branch exact match is stripped", () => {
const tool = chatTool({ enum: ["x"], oneOf: [{ const: "x" }] });
normalizeCodexTools({ tools: [tool] });
const parameters = tool.parameters as JsonRecord;
assert.equal(parameters.oneOf, undefined);
assert.deepEqual(parameters.enum, ["x"]);
});
test("non-string const is preserved", () => {
const tool = chatTool({ enum: [1, 2], oneOf: [{ const: 1 }, { const: 2 }] });
normalizeCodexTools({ tools: [tool] });
assert.deepEqual((tool.parameters as JsonRecord).oneOf, [{ const: 1 }, { const: 2 }]);
});
test("non-string enum is preserved", () => {
const tool = chatTool({ enum: [{ a: 1 }], oneOf: [{ const: "x" }] });
normalizeCodexTools({ tools: [tool] });
assert.deepEqual((tool.parameters as JsonRecord).oneOf, [{ const: "x" }]);
});
test("anyOf is preserved while the walker strips its inner oneOf", () => {
const tool = chatTool({
anyOf: [{ enum: ["a", "b"], oneOf: [{ const: "a" }, { const: "b" }] }],
});
normalizeCodexTools({ tools: [tool] });
const parameters = tool.parameters as JsonRecord;
const anyOf = parameters.anyOf as JsonRecord[];
assert.equal(anyOf.length, 1);
assert.equal(anyOf[0].oneOf, undefined);
});
test("allOf is preserved while the walker strips its inner oneOf", () => {
const tool = chatTool({ allOf: [{ enum: ["a"], oneOf: [{ const: "a" }] }] });
normalizeCodexTools({ tools: [tool] });
const parameters = tool.parameters as JsonRecord;
const allOf = parameters.allOf as JsonRecord[];
assert.equal(allOf.length, 1);
assert.equal(allOf[0].oneOf, undefined);
});
test("nested oneOf in properties is stripped", () => {
const tool = chatTool({
properties: {
action: {
enum: ["a", "b"],
oneOf: [
{ const: "a", description: "A" },
{ const: "b", description: "B" },
],
},
},
});
normalizeCodexTools({ tools: [tool] });
const properties = (tool.parameters as JsonRecord).properties as JsonRecord;
const action = properties.action as JsonRecord;
assert.equal(action.oneOf, undefined);
assert.deepEqual(action.enum, ["a", "b"]);
});
test("nested oneOf in items is stripped", () => {
const tool = chatTool({ items: { enum: ["a", "b"], oneOf: [{ const: "a" }, { const: "b" }] } });
normalizeCodexTools({ tools: [tool] });
const items = (tool.parameters as JsonRecord).items as JsonRecord;
assert.equal(items.oneOf, undefined);
});
test("nested oneOf in additionalProperties is stripped", () => {
const tool = chatTool({
additionalProperties: {
enum: ["p", "q"],
oneOf: [{ const: "p" }, { const: "q" }],
},
});
normalizeCodexTools({ tools: [tool] });
const additionalProperties = (tool.parameters as JsonRecord).additionalProperties as JsonRecord;
assert.equal(additionalProperties.oneOf, undefined);
});
test("nested oneOf in $defs is stripped", () => {
const tool = chatTool({
$defs: { D: { enum: ["d1", "d2"], oneOf: [{ const: "d1" }, { const: "d2" }] } },
});
normalizeCodexTools({ tools: [tool] });
const defs = (tool.parameters as JsonRecord).$defs as JsonRecord;
const definition = defs.D as JsonRecord;
assert.equal(definition.oneOf, undefined);
});
test("nested oneOf in patternProperties is stripped", () => {
const tool = chatTool({
patternProperties: { "^x$": { enum: ["a"], oneOf: [{ const: "a" }] } },
});
normalizeCodexTools({ tools: [tool] });
const patternProperties = (tool.parameters as JsonRecord).patternProperties as JsonRecord;
const pattern = patternProperties["^x$"] as JsonRecord;
assert.equal(pattern.oneOf, undefined);
});
test("stripping is idempotent", () => {
const first = stripRedundantOneOfConstEnum(productionParameters());
const second = stripRedundantOneOfConstEnum(first);
assert.deepEqual(second, first);
});
test("stripping is immutable", () => {
const original = productionParameters();
const before = structuredClone(original);
const result = stripRedundantOneOfConstEnum(original) as JsonRecord;
const properties = original.properties as JsonRecord;
const action = properties.action as JsonRecord;
assert.deepEqual(original, before);
assert.ok(Array.isArray(action.oneOf));
assert.notStrictEqual(result, original);
});
test("Chat wrapper is flattened to the flat Responses form", () => {
const tool = chatTool({
properties: {
action: { enum: ["a", "b"], oneOf: [{ const: "a" }, { const: "b" }] },
},
});
normalizeCodexTools({ tools: [tool] });
const parameters = tool.parameters as JsonRecord;
const properties = parameters.properties as JsonRecord;
const action = properties.action as JsonRecord;
assert.equal(action.oneOf, undefined);
assert.equal(tool.function, undefined);
assert.equal(tool.name, "test_tool");
});

View File

@@ -0,0 +1,158 @@
import assert from "node:assert/strict";
import test from "node:test";
import { normalizeCodexTools } from "../../open-sse/executors/codex/tools.ts";
type JsonRecord = Record<string, unknown>;
function functionTool(name: string, definition: JsonRecord, extra: JsonRecord = {}): JsonRecord {
return {
type: "function",
name,
parameters: { type: "object" },
...extra,
...definition,
};
}
function nestedFunctionTool(name: string, definition: JsonRecord): JsonRecord {
return {
type: "function",
function: {
name,
parameters: { type: "object" },
...definition,
},
};
}
function strictOf(tool: JsonRecord): unknown {
return tool.strict;
}
test("default false applies to omitted nested and flat function strict", () => {
const flat = functionTool("flat_tool", {});
const nested = nestedFunctionTool("nested_tool", {});
normalizeCodexTools({ tools: [flat, nested] }, { defaultFunctionStrict: false });
assert.equal(strictOf(flat), false);
assert.equal(strictOf(nested), false);
});
test("omitted function strict stays omitted without a fallback", () => {
const flat = functionTool("flat_tool", {});
const nested = nestedFunctionTool("nested_tool", {});
normalizeCodexTools({ tools: [flat, nested] });
assert.equal(strictOf(flat), undefined);
assert.equal(strictOf(nested), undefined);
});
test("explicit top-level true and false are preserved", () => {
for (const value of [true, false]) {
const tool = functionTool("top_level_tool", { strict: value });
normalizeCodexTools({ tools: [tool] }, { defaultFunctionStrict: !value });
assert.equal(strictOf(tool), value);
}
});
test("explicit nested function true and false are preserved", () => {
for (const value of [true, false]) {
const tool = nestedFunctionTool("nested_tool", { strict: value });
normalizeCodexTools({ tools: [tool] }, { defaultFunctionStrict: !value });
assert.equal(strictOf(tool), value);
}
});
test("top-level boolean strict takes precedence over nested boolean strict", () => {
const topLevelTrue = nestedFunctionTool("top_true", { strict: false });
topLevelTrue.strict = true;
const topLevelFalse = nestedFunctionTool("top_false", { strict: true });
topLevelFalse.strict = false;
normalizeCodexTools({ tools: [topLevelTrue, topLevelFalse] }, { defaultFunctionStrict: true });
assert.equal(strictOf(topLevelTrue), true);
assert.equal(strictOf(topLevelFalse), false);
});
test("nonboolean explicit strict values fall back through the precedence chain", () => {
const topNonBoolean = nestedFunctionTool("nested_boolean", { strict: false });
topNonBoolean.strict = "true";
const bothNonBoolean = nestedFunctionTool("fallback", { strict: "false" });
bothNonBoolean.strict = 1;
normalizeCodexTools({ tools: [topNonBoolean, bothNonBoolean] }, { defaultFunctionStrict: true });
assert.equal(strictOf(topNonBoolean), false);
assert.equal(strictOf(bothNonBoolean), true);
});
test("hosted, namespace, and custom tools are unchanged by function strict defaults", () => {
const hosted = { type: "web_search", search_context_size: "high" };
const namespace = {
type: "namespace",
name: "mcp__example__",
tools: [{ type: "function", name: "search", parameters: { type: "object" } }],
};
const custom = {
type: "custom",
name: "apply_patch",
format: { type: "grammar", syntax: "lark", definition: "start: /.+/" },
};
const tools = [hosted, namespace, custom];
const before = structuredClone(tools);
normalizeCodexTools({ tools }, { defaultFunctionStrict: false, preserveCustomTools: true });
assert.deepEqual(tools, before);
});
test("production-like enum and oneOf schema is preserved exactly", () => {
const parameters = {
type: "object",
description: "Dynamic action parameters",
properties: {
action: {
type: "string",
enum: ["read_file", "write_file", "list_files"],
oneOf: [
{
const: "write_file",
description: "Write a file",
title: "Write file",
$comment: "branch 1",
},
{
const: "read_file",
description: "Read a file",
title: "Read file",
$comment: "branch 2",
},
{
const: "list_files",
description: "List files",
title: "List files",
$comment: "branch 3",
},
],
},
},
required: ["action"],
};
const tool = {
type: "function",
function: { name: "dynamic_tool", parameters },
} as JsonRecord;
const expected = structuredClone(parameters);
normalizeCodexTools({ tools: [tool] });
assert.deepEqual(tool.parameters, expected);
});

View File

@@ -0,0 +1,260 @@
/**
* tests/unit/command-code-usage.test.ts
*
* Command Code usage.ts dispatch + Provider Limits allowlists for
* monthly credits + 5h/weekly rolling windows (Bearer /alpha APIs).
*/
import { describe, it, afterEach } from "node:test";
import assert from "node:assert/strict";
const { __testing, USAGE_FETCHER_PROVIDERS, getUsageForProvider } =
await import("../../open-sse/services/usage.ts");
const { USAGE_SUPPORTED_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
const { isSupportedUsageConnection } = await import("../../src/lib/usage/providerLimits.ts");
const { convertUsageToQuotaInfo } = await import("../../open-sse/services/genericQuotaFetcher.ts");
const { getCommandCodeUsage } = __testing;
const originalFetch = globalThis.fetch;
type QuotaShape = {
used: number;
total: number;
remaining?: number;
remainingPercentage?: number;
resetAt: string | null;
currency?: string;
displayName?: string;
};
function jsonResponse(body: unknown, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "content-type": "application/json" },
});
}
const CREDITS_PAYLOAD = {
credits: {
belowThreshold: false,
creditThreshold: 0,
monthlyCredits: 34.9522404823,
purchasedCredits: 0.0446690956,
freeCredits: 0,
},
windowLimits: {
limited: true,
exceeded: "weekly",
fiveHour: {
used: 0,
cap: 14,
exceeded: false,
resetAt: 0,
},
weekly: {
used: 35.0477595177,
cap: 35,
exceeded: true,
resetAt: 1786575240518,
},
},
};
const WHOAMI_PERSONAL = {
success: true,
user: { id: "u1", name: "dev", email: "dev@example.com", userName: "dev" },
org: null,
};
const SUBSCRIPTION_GOAT = {
success: true,
data: {
id: "sub_1",
status: "active",
orgId: null,
currentPeriodStart: "2026-08-05T22:27:41.000Z",
currentPeriodEnd: "2026-09-05T22:27:41.000Z",
planId: "individual-goat",
},
};
const USAGE_SUMMARY = {
totalCount: 1260,
totalCost: 55.0030904221,
totalMonthlyCredits: 35.0477595177,
totalPurchasedCredits: 19.9553309044,
periodBasis: "billing-period",
};
function installMockFetch(handlers: Record<string, () => Response>) {
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = String(input);
for (const [needle, handler] of Object.entries(handlers)) {
if (url.includes(needle)) return handler();
}
return jsonResponse({ error: `unhandled fetch: ${url}` }, 500);
};
}
describe("Command Code usage dispatch", () => {
afterEach(() => {
globalThis.fetch = originalFetch;
});
it("registers command-code in USAGE_FETCHER_PROVIDERS and USAGE_SUPPORTED_PROVIDERS", () => {
assert.ok((USAGE_FETCHER_PROVIDERS as readonly string[]).includes("command-code"));
assert.ok((USAGE_SUPPORTED_PROVIDERS as readonly string[]).includes("command-code"));
});
it("isSupportedUsageConnection accepts command-code apikey connections", () => {
assert.equal(
isSupportedUsageConnection({
id: "c1",
provider: "command-code",
authType: "apikey",
}),
true
);
});
it("getCommandCodeUsage maps credits + windows + period spend", async () => {
installMockFetch({
"/alpha/whoami": () => jsonResponse(WHOAMI_PERSONAL),
"/alpha/billing/credits": () => jsonResponse(CREDITS_PAYLOAD),
"/alpha/billing/subscriptions": () => jsonResponse(SUBSCRIPTION_GOAT),
"/alpha/usage/summary": () => jsonResponse(USAGE_SUMMARY),
});
const r = (await getCommandCodeUsage("cc-key")) as {
plan?: string;
message?: string;
quotas?: {
five_hour?: QuotaShape;
weekly?: QuotaShape;
credits?: QuotaShape;
};
};
assert.ok(r.quotas?.five_hour, `expected five_hour, got: ${JSON.stringify(r)}`);
assert.ok(r.quotas?.weekly, `expected weekly, got: ${JSON.stringify(r)}`);
assert.ok(r.quotas?.credits, `expected credits, got: ${JSON.stringify(r)}`);
assert.match(r.plan || "", /GOAT/i);
assert.equal(r.quotas!.five_hour!.used, 0);
assert.equal(r.quotas!.five_hour!.total, 14);
assert.equal(r.quotas!.five_hour!.resetAt, null);
assert.equal(r.quotas!.five_hour!.currency, "USD");
// used clamped to total when upstream reports slight overshoot
assert.equal(r.quotas!.weekly!.used, 35);
assert.equal(r.quotas!.weekly!.total, 35);
assert.equal(r.quotas!.weekly!.remaining, 0);
assert.equal(r.quotas!.weekly!.remainingPercentage, 0);
assert.ok(r.quotas!.weekly!.resetAt?.startsWith("2026-"));
const remainingCredits = 34.9522404823 + 0.0446690956;
assert.ok(Math.abs((r.quotas!.credits!.remaining ?? 0) - remainingCredits) < 1e-9);
assert.equal(r.quotas!.credits!.used, USAGE_SUMMARY.totalCost);
assert.ok(
Math.abs(r.quotas!.credits!.total - (USAGE_SUMMARY.totalCost + remainingCredits)) < 1e-9
);
assert.equal(r.quotas!.credits!.resetAt, "2026-09-05T22:27:41.000Z");
assert.equal(r.quotas!.credits!.currency, "USD");
});
it("getCommandCodeUsage works for org:null without orgId query params", async () => {
const seen: string[] = [];
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = String(input);
seen.push(url);
if (url.includes("/alpha/whoami")) return jsonResponse(WHOAMI_PERSONAL);
if (url.includes("/alpha/billing/credits")) return jsonResponse(CREDITS_PAYLOAD);
if (url.includes("/alpha/billing/subscriptions")) return jsonResponse(SUBSCRIPTION_GOAT);
if (url.includes("/alpha/usage/summary")) return jsonResponse(USAGE_SUMMARY);
return jsonResponse({ error: "unhandled" }, 500);
};
await getCommandCodeUsage("cc-key");
assert.ok(seen.some((u) => u.includes("/alpha/billing/credits")));
assert.ok(!seen.some((u) => /[?&]orgId=/.test(u)));
});
it("getCommandCodeUsage returns message when apiKey missing", async () => {
const r = (await getCommandCodeUsage("")) as { message?: string; quotas?: unknown };
assert.ok(r.message && !r.quotas);
});
it("getCommandCodeUsage returns message-only on 401", async () => {
installMockFetch({
"/alpha/whoami": () => jsonResponse({ success: false }, 401),
"/alpha/billing/credits": () =>
jsonResponse(
{
success: false,
error: { code: "UNAUTHORIZED", status: 401, message: "Invalid token" },
},
401
),
});
const r = (await getCommandCodeUsage("bad-key")) as { message?: string; quotas?: unknown };
assert.ok(r.message && !r.quotas);
});
it("getCommandCodeUsage soft-fails when subscription/summary unavailable", async () => {
installMockFetch({
"/alpha/whoami": () => jsonResponse(WHOAMI_PERSONAL),
"/alpha/billing/credits": () => jsonResponse(CREDITS_PAYLOAD),
"/alpha/billing/subscriptions": () => jsonResponse({ success: false }, 500),
"/alpha/usage/summary": () => jsonResponse({ error: "boom" }, 500),
});
const r = (await getCommandCodeUsage("cc-key")) as {
plan?: string;
quotas?: { five_hour?: QuotaShape; weekly?: QuotaShape; credits?: QuotaShape };
};
assert.ok(r.quotas?.five_hour);
assert.ok(r.quotas?.weekly);
assert.ok(r.quotas?.credits);
// Without summary spend, used defaults to 0; remaining still from credit pools
assert.equal(r.quotas!.credits!.used, 0);
assert.ok((r.quotas!.credits!.remaining ?? 0) > 0);
});
it("getUsageForProvider('command-code', ...) delegates to getCommandCodeUsage", async () => {
installMockFetch({
"/alpha/whoami": () => jsonResponse(WHOAMI_PERSONAL),
"/alpha/billing/credits": () => jsonResponse(CREDITS_PAYLOAD),
"/alpha/billing/subscriptions": () => jsonResponse(SUBSCRIPTION_GOAT),
"/alpha/usage/summary": () => jsonResponse(USAGE_SUMMARY),
});
const r = (await getUsageForProvider({
id: "conn-cc",
provider: "command-code",
apiKey: "dispatch-key",
} as Parameters<typeof getUsageForProvider>[0])) as {
quotas?: { weekly?: QuotaShape };
plan?: string;
};
assert.match(r.plan || "", /GOAT/i);
assert.equal(r.quotas?.weekly?.total, 35);
});
it("convertUsageToQuotaInfo marks weekly-exhausted Command Code as limitReached", async () => {
installMockFetch({
"/alpha/whoami": () => jsonResponse(WHOAMI_PERSONAL),
"/alpha/billing/credits": () => jsonResponse(CREDITS_PAYLOAD),
"/alpha/billing/subscriptions": () => jsonResponse(SUBSCRIPTION_GOAT),
"/alpha/usage/summary": () => jsonResponse(USAGE_SUMMARY),
});
const usage = await getCommandCodeUsage("cc-key");
const info = convertUsageToQuotaInfo(usage);
assert.ok(info);
assert.equal(info!.limitReached, true);
assert.ok((info!.windows.weekly?.percentUsed ?? 0) >= 1 - 1e-9);
});
});

View File

@@ -31,6 +31,18 @@ import {
test("resolveRequestedModel maps cursor-agent's client-side aliases", () => {
assert.deepEqual(resolveRequestedModel("auto"), { modelId: "default", parameters: [] });
assert.deepEqual(resolveRequestedModel("auto-cost"), {
modelId: "default",
parameters: [{ id: "optimization", value: "cost" }],
});
assert.deepEqual(resolveRequestedModel("auto-balance"), {
modelId: "default",
parameters: [{ id: "optimization", value: "balance" }],
});
assert.deepEqual(resolveRequestedModel("auto-intelligence"), {
modelId: "default",
parameters: [{ id: "optimization", value: "intelligence" }],
});
assert.deepEqual(resolveRequestedModel("composer-2-fast"), {
modelId: "composer-2",
parameters: [{ id: "fast", value: "true" }],

View File

@@ -0,0 +1,31 @@
import { strict as assert } from "node:assert";
import { describe, it } from "node:test";
import { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog";
describe("ensureCursorAutoCatalogEntry", () => {
it("injects auto when only wire id default is present", () => {
const models = ensureCursorAutoCatalogEntry([
{ id: "default", name: "Auto", owned_by: "cursor" },
{ id: "composer-2.5", name: "Composer 2.5", owned_by: "cursor" },
]);
assert.ok(models.some((m) => m.id === "auto"));
assert.ok(models.some((m) => m.id === "default"));
});
it("injects auto-cost / auto-balance / auto-intelligence", () => {
const models = ensureCursorAutoCatalogEntry([{ id: "auto", name: "Auto", owned_by: "cursor" }]);
const ids = models.map((m) => m.id);
assert.ok(ids.includes("auto-cost"));
assert.ok(ids.includes("auto-balance"));
assert.ok(ids.includes("auto-intelligence"));
});
it("does not duplicate existing auto entries", () => {
const models = ensureCursorAutoCatalogEntry([
{ id: "auto", name: "Auto", owned_by: "cursor" },
{ id: "auto-cost", name: "Auto (cost)", owned_by: "cursor" },
]);
assert.equal(models.filter((m) => m.id === "auto").length, 1);
assert.equal(models.filter((m) => m.id === "auto-cost").length, 1);
});
});

View File

@@ -0,0 +1,79 @@
/**
* Cursor exclusive live-catalog listing: when synced AvailableModels exists,
* dashboard / Test All must list synced + injected auto* + custom only —
* never the static registry effort/premium rows.
*/
import { strict as assert } from "node:assert";
import { describe, it } from "node:test";
import { mergeProviderModelListing } from "@/lib/providers/mergeProviderModelListing";
describe("mergeProviderModelListing (cursor exclusive)", () => {
const registry = [
{ id: "auto", name: "Auto (Server Picks)" },
{ id: "claude-4.6-sonnet-high", name: "Claude Sonnet High" },
{ id: "gpt-5.5-high", name: "GPT 5.5 High" },
{ id: "composer-2.5", name: "Composer 2.5" },
];
it("exclusive + synced: drops static-only ids and injects auto*", () => {
const models = mergeProviderModelListing({
providerId: "cursor",
registryModels: registry,
syncedModels: [
{ id: "composer-2.5", name: "Composer 2.5" },
{ id: "claude-4.6-sonnet", name: "Claude 4.6 Sonnet" },
],
customModels: [],
usesCuratedModelsOnly: false,
});
const ids = models.map((m) => m.id);
assert.ok(ids.includes("composer-2.5"));
assert.ok(ids.includes("claude-4.6-sonnet"));
assert.ok(ids.includes("auto"));
assert.ok(ids.includes("auto-cost"));
assert.ok(ids.includes("auto-balance"));
assert.ok(ids.includes("auto-intelligence"));
assert.equal(ids.includes("claude-4.6-sonnet-high"), false);
assert.equal(ids.includes("gpt-5.5-high"), false);
assert.ok(models.every((m) => m.source === "imported" || m.id.startsWith("auto")));
});
it("exclusive + synced: keeps operator custom models", () => {
const models = mergeProviderModelListing({
providerId: "cursor",
registryModels: registry,
syncedModels: [{ id: "composer-2.5", name: "Composer 2.5" }],
customModels: [{ id: "my-custom", name: "My Custom", source: "custom" }],
usesCuratedModelsOnly: false,
});
const custom = models.find((m) => m.id === "my-custom");
assert.ok(custom);
assert.equal(custom.source, "custom");
});
it("exclusive + empty synced: falls back to registry custom", () => {
const models = mergeProviderModelListing({
providerId: "cursor",
registryModels: registry,
syncedModels: [],
customModels: [{ id: "my-custom", name: "My Custom" }],
usesCuratedModelsOnly: false,
});
const ids = models.map((m) => m.id);
assert.ok(ids.includes("claude-4.6-sonnet-high"));
assert.ok(ids.includes("my-custom"));
});
it("non-exclusive providers keep registry-first merge", () => {
const models = mergeProviderModelListing({
providerId: "openai",
registryModels: [{ id: "gpt-4o", name: "GPT-4o" }],
syncedModels: [{ id: "gpt-4o-mini", name: "GPT-4o mini" }],
customModels: [],
usesCuratedModelsOnly: false,
});
const ids = models.map((m) => m.id);
assert.deepEqual(ids, ["gpt-4o", "gpt-4o-mini"]);
});
});

View File

@@ -0,0 +1,90 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
resolveRequestedModel,
encodeAgentRunRequest,
} from "../../open-sse/utils/cursorAgentProtobuf";
import { CURSOR_REWRITE_FAILURE_IDS } from "./fixtures/cursor-rewrite-failure-ids";
test("resolveRequestedModel passes live-catalog Claude effort ids through verbatim", () => {
const live = new Set(["claude-opus-5-low"]);
assert.deepEqual(resolveRequestedModel("claude-opus-5-low", { liveCatalogIds: live }), {
modelId: "claude-opus-5-low",
parameters: [],
});
});
test("resolveRequestedModel passes live-catalog GPT reasoning ids through verbatim", () => {
const live = new Set(["gpt-5.6-sol-medium"]);
assert.deepEqual(resolveRequestedModel("gpt-5.6-sol-medium", { liveCatalogIds: live }), {
modelId: "gpt-5.6-sol-medium",
parameters: [],
});
});
test("resolveRequestedModel still strips effort when id is absent from live catalog", () => {
const live = new Set(["composer-2"]);
assert.deepEqual(resolveRequestedModel("claude-opus-5-low", { liveCatalogIds: live }), {
modelId: "claude-opus-5",
parameters: [{ id: "effort", value: "low" }],
});
assert.deepEqual(resolveRequestedModel("gpt-5.5-high", { liveCatalogIds: live }), {
modelId: "gpt-5.5",
parameters: [{ id: "reasoning", value: "high" }],
});
});
test("resolveRequestedModel still strips effort when liveCatalogIds is omitted", () => {
assert.deepEqual(resolveRequestedModel("claude-opus-4-8-high"), {
modelId: "claude-opus-4-8",
parameters: [{ id: "effort", value: "high" }],
});
});
test("resolveRequestedModel maps auto to default even when auto is in the live catalog", () => {
const live = new Set(["auto", "auto-cost", "claude-opus-5-low"]);
assert.deepEqual(resolveRequestedModel("auto", { liveCatalogIds: live }), {
modelId: "default",
parameters: [],
});
assert.deepEqual(resolveRequestedModel("auto-cost", { liveCatalogIds: live }), {
modelId: "default",
parameters: [{ id: "optimization", value: "cost" }],
});
});
test("resolveRequestedModel passes composer-*-fast through when present in live catalog", () => {
const live = new Set(["composer-2.5-fast", "composer-2-fast"]);
assert.deepEqual(resolveRequestedModel("composer-2.5-fast", { liveCatalogIds: live }), {
modelId: "composer-2.5-fast",
parameters: [],
});
assert.deepEqual(resolveRequestedModel("composer-2-fast", { liveCatalogIds: live }), {
modelId: "composer-2-fast",
parameters: [],
});
});
test("resolveRequestedModel passes every rewrite-failure id through when all are live", () => {
const live = new Set<string>(CURSOR_REWRITE_FAILURE_IDS);
for (const id of CURSOR_REWRITE_FAILURE_IDS) {
assert.deepEqual(
resolveRequestedModel(id, { liveCatalogIds: live }),
{ modelId: id, parameters: [] },
id
);
}
});
test("encodeAgentRunRequest embeds verbatim live catalog model id", () => {
const live = new Set(["claude-opus-5-low"]);
const buf = encodeAgentRunRequest({
modelId: "claude-opus-5-low",
userText: "hi",
liveCatalogIds: live,
});
const text = buf.toString("latin1");
const full = text.split("claude-opus-5-low").length - 1;
assert.ok(full >= 4, `verbatim id must appear in RequestedModel + ModelDetails (got ${full})`);
assert.ok(!text.includes("effort"), "must not emit effort parameter for live verbatim id");
});

View File

@@ -0,0 +1,169 @@
/**
* Issue #8887 — deleting a provider connection must invalidate LKGP pins that
* reference it without disturbing surviving, provider-level, or legacy pins.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-lkgp-8887-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const lkgpDb = await import("../../src/lib/db/settings/lkgp.ts");
const readCache = await import("../../src/lib/db/readCache.ts");
async function resetStorage() {
core.resetDbInstance();
for (let attempt = 0; attempt < 10; attempt++) {
try {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
break;
} catch (error: unknown) {
const code =
error && typeof error === "object" && "code" in error
? String((error as { code?: unknown }).code)
: "";
if ((code === "EBUSY" || code === "EPERM") && attempt < 9) {
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
continue;
}
throw error;
}
}
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
async function createConnection(provider: string, name: string): Promise<string> {
const connection = await providersDb.createProviderConnection({
provider,
authType: "apikey",
name,
apiKey: `test-key-${name}`,
});
assert.equal(typeof connection.id, "string", "provider fixture must return a connection id");
return connection.id as string;
}
test.beforeEach(async () => {
await resetStorage();
});
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("#8887: single delete removes only the matching LKGP pin", async () => {
const doomedId = await createConnection("berry", "single-doomed");
const survivorId = await createConnection("berry", "single-survivor");
await lkgpDb.setLKGP("single-doomed", "model-x", "berry", doomedId);
await lkgpDb.setLKGP("single-survivor", "model-y", "berry", survivorId);
assert.equal(await providersDb.deleteProviderConnection(doomedId), true);
assert.equal(await lkgpDb.getLKGP("single-doomed", "model-x"), null);
assert.deepEqual(await lkgpDb.getLKGP("single-survivor", "model-y"), {
provider: "berry",
connectionId: survivorId,
});
});
test("#8887: single delete invalidates a warmed LKGP read-cache entry", async () => {
const doomedId = await createConnection("berry", "cached-doomed");
await lkgpDb.setLKGP("cached-doomed", "model-x", "berry", doomedId);
assert.deepEqual(await readCache.getCachedLKGP("cached-doomed", "model-x"), {
provider: "berry",
connectionId: doomedId,
});
assert.equal(await providersDb.deleteProviderConnection(doomedId), true);
assert.equal(
await readCache.getCachedLKGP("cached-doomed", "model-x"),
null,
"deleted LKGP pins must not survive in the 5s read cache"
);
});
test("#8887: bulk delete removes every matching LKGP pin", async () => {
const doomedA = await createConnection("berry", "bulk-doomed-a");
const doomedB = await createConnection("berry", "bulk-doomed-b");
const survivorId = await createConnection("berry", "bulk-survivor");
await lkgpDb.setLKGP("bulk-a", "model-x", "berry", doomedA);
await lkgpDb.setLKGP("bulk-b", "model-y", "berry", doomedB);
await lkgpDb.setLKGP("bulk-survivor", "model-z", "berry", survivorId);
assert.equal(await providersDb.deleteProviderConnections([doomedA, doomedB]), 2);
assert.equal(await lkgpDb.getLKGP("bulk-a", "model-x"), null);
assert.equal(await lkgpDb.getLKGP("bulk-b", "model-y"), null);
assert.deepEqual(await lkgpDb.getLKGP("bulk-survivor", "model-z"), {
provider: "berry",
connectionId: survivorId,
});
});
test("#8887: provider-wide delete removes that provider's LKGP pins only", async () => {
const berryId = await createConnection("berry", "provider-doomed");
const cherryId = await createConnection("cherry", "provider-survivor");
await lkgpDb.setLKGP("provider-doomed", "model-x", "berry", berryId);
await lkgpDb.setLKGP("provider-survivor", "model-y", "cherry", cherryId);
assert.equal(await providersDb.deleteProviderConnectionsByProvider("berry"), 1);
assert.equal(await lkgpDb.getLKGP("provider-doomed", "model-x"), null);
assert.deepEqual(await lkgpDb.getLKGP("provider-survivor", "model-y"), {
provider: "cherry",
connectionId: cherryId,
});
});
test("#8887: provider-level LKGP pins without connectionId are preserved", async () => {
const doomedId = await createConnection("berry", "provider-level");
await lkgpDb.setLKGP("provider-level", "model-x", "berry");
assert.equal(await providersDb.deleteProviderConnection(doomedId), true);
assert.deepEqual(await lkgpDb.getLKGP("provider-level", "model-x"), { provider: "berry" });
});
test("#8887: legacy LKGP values are preserved during connection cleanup", async () => {
const doomedId = await createConnection("berry", "legacy");
const db = core.getDbInstance();
db.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('lkgp', ?, ?)").run(
"legacy:model-x",
"berry"
);
assert.equal(await providersDb.deleteProviderConnection(doomedId), true);
assert.deepEqual(await lkgpDb.getLKGP("legacy", "model-x"), { provider: "berry" });
});
test("#8887: deleting an unpinned connection does not disturb unrelated LKGP state", async () => {
const doomedId = await createConnection("berry", "unpinned");
const survivorId = await createConnection("cherry", "unrelated");
await lkgpDb.setLKGP("unrelated", "model-y", "cherry", survivorId);
assert.equal(await providersDb.deleteProviderConnection(doomedId), true);
assert.deepEqual(await lkgpDb.getLKGP("unrelated", "model-y"), {
provider: "cherry",
connectionId: survivorId,
});
});

View File

@@ -1153,6 +1153,67 @@ test("CodexExecutor.transformRequest preserves native Codex custom tools", () =>
assert.equal(tools[1].strict, false);
});
test("CodexExecutor.transformRequest defaults translated function strict without changing native payloads", () => {
const executor = new CodexExecutor();
const translated = executor.transformRequest(
"gpt-5.5",
{
model: "gpt-5.5",
input: [],
tools: [
{
type: "function",
function: { name: "translated_tool", parameters: { type: "object" } },
},
],
},
true,
{ requestEndpointPath: "/responses" }
);
const translatedTool = (translated.tools as Array<Record<string, unknown>>)[0];
assert.equal(translatedTool.strict, false);
const native = executor.transformRequest(
"gpt-5.5",
{
_nativeCodexPassthrough: true,
model: "gpt-5.5",
input: [],
tools: [
{
type: "function",
name: "native_tool",
parameters: { type: "object" },
},
],
},
true,
{ requestEndpointPath: "/responses" }
);
const nativeTool = (native.tools as Array<Record<string, unknown>>)[0];
assert.equal(nativeTool.strict, undefined);
const explicit = executor.transformRequest(
"gpt-5.5",
{
model: "gpt-5.5",
input: [],
tools: [
{
type: "function",
name: "explicit_tool",
parameters: { type: "object" },
strict: true,
},
],
},
true,
{ requestEndpointPath: "/responses" }
);
const explicitTool = (explicit.tools as Array<Record<string, unknown>>)[0];
assert.equal(explicitTool.strict, true);
});
test("CodexExecutor.transformRequest still drops custom tools outside native passthrough", () => {
const executor = new CodexExecutor();
const result = executor.transformRequest(

View File

@@ -0,0 +1,68 @@
import test from "node:test";
import assert from "node:assert/strict";
import dns from "node:dns";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-fal-images-"));
const originalDnsLookup = dns.promises.lookup;
(dns.promises as { lookup: unknown }).lookup = (async (
_hostname: string,
options?: { all?: boolean }
) => {
const record = { address: "203.0.113.1", family: 4 };
return options?.all ? [record] : record;
}) as typeof dns.promises.lookup;
process.on("exit", () => {
(dns.promises as { lookup: unknown }).lookup = originalDnsLookup;
});
const { handleFalAIImageEdit } =
await import("../../open-sse/handlers/imageGeneration/providers/fal.ts");
test("handleFalAIImageEdit forwards multiple references to the Fal edit endpoint", async () => {
const originalFetch = globalThis.fetch;
let captured;
globalThis.fetch = async (url, options = {}) => {
const stringUrl = String(url);
if (stringUrl === "https://fal.run/fal-ai/flux-2-flex/edit") {
captured = {
headers: options.headers,
body: JSON.parse(String(options.body || "{}")),
};
return new Response(JSON.stringify({ images: [{ url: "data:image/png;base64,CAkK" }] }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
try {
const result = await handleFalAIImageEdit({
model: "fal-ai/flux-2-flex",
provider: "fal-ai",
providerConfig: { baseUrl: "https://fal.run" },
body: { prompt: "make the dog match the reference" },
images: [
{ bytes: Buffer.from([1, 2, 3]), mime: "image/png" },
{ bytes: Buffer.from([4, 5, 6]), mime: "image/jpeg" },
],
credentials: { apiKey: "fal-key" },
log: null,
});
assert.equal(result.success, true);
assert.equal(captured.headers.Authorization, "Key fal-key");
assert.deepEqual(captured.body.image_urls, [
"data:image/png;base64,AQID",
"data:image/jpeg;base64,BAUG",
]);
assert.equal(captured.body.prompt, "make the dog match the reference");
assert.equal(result.data.data[0].b64_json, "CAkK");
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -0,0 +1,92 @@
/**
* Live-synced Cursor model ids that Test All failed when #7289
* resolveRequestedModel stripped them to a missing base + parameter.
* Smoke checklist for catalog-aware pass-through.
*/
export const CURSOR_REWRITE_FAILURE_IDS = [
// Claude (52)
"claude-4.5-opus-high",
"claude-4.6-opus-high",
"claude-4.6-opus-max",
"claude-4.6-sonnet-medium",
"claude-fable-5-low",
"claude-fable-5-medium",
"claude-fable-5-high",
"claude-fable-5-xhigh",
"claude-fable-5-max",
"claude-fable-5-thinking-low",
"claude-fable-5-thinking-medium",
"claude-fable-5-thinking-high",
"claude-fable-5-thinking-xhigh",
"claude-fable-5-thinking-max",
"claude-opus-4-7-low",
"claude-opus-4-7-medium",
"claude-opus-4-7-high",
"claude-opus-4-7-xhigh",
"claude-opus-4-7-max",
"claude-opus-4-7-thinking-low",
"claude-opus-4-7-thinking-medium",
"claude-opus-4-7-thinking-high",
"claude-opus-4-7-thinking-xhigh",
"claude-opus-4-7-thinking-max",
"claude-opus-4-8-low",
"claude-opus-4-8-medium",
"claude-opus-4-8-high",
"claude-opus-4-8-xhigh",
"claude-opus-4-8-max",
"claude-opus-4-8-thinking-low",
"claude-opus-4-8-thinking-medium",
"claude-opus-4-8-thinking-high",
"claude-opus-4-8-thinking-xhigh",
"claude-opus-4-8-thinking-max",
"claude-opus-5-low",
"claude-opus-5-medium",
"claude-opus-5-high",
"claude-opus-5-thinking-low",
"claude-opus-5-thinking-medium",
"claude-opus-5-thinking-high",
"claude-opus-5-thinking-xhigh",
"claude-opus-5-thinking-max",
"claude-sonnet-5-low",
"claude-sonnet-5-medium",
"claude-sonnet-5-high",
"claude-sonnet-5-xhigh",
"claude-sonnet-5-max",
"claude-sonnet-5-thinking-low",
"claude-sonnet-5-thinking-medium",
"claude-sonnet-5-thinking-high",
"claude-sonnet-5-thinking-xhigh",
"claude-sonnet-5-thinking-max",
// GPT (31)
"gpt-5.4-low",
"gpt-5.4-medium",
"gpt-5.4-high",
"gpt-5.4-xhigh",
"gpt-5.4-mini-low",
"gpt-5.4-mini-medium",
"gpt-5.4-mini-high",
"gpt-5.4-mini-xhigh",
"gpt-5.4-nano-low",
"gpt-5.4-nano-medium",
"gpt-5.4-nano-high",
"gpt-5.4-nano-xhigh",
"gpt-5.5-low",
"gpt-5.5-medium",
"gpt-5.5-high",
"gpt-5.5-extra-high",
"gpt-5.6-sol-low",
"gpt-5.6-sol-medium",
"gpt-5.6-sol-high",
"gpt-5.6-sol-xhigh",
"gpt-5.6-sol-max",
"gpt-5.6-terra-low",
"gpt-5.6-terra-medium",
"gpt-5.6-terra-high",
"gpt-5.6-terra-xhigh",
"gpt-5.6-terra-max",
"gpt-5.6-luna-low",
"gpt-5.6-luna-medium",
"gpt-5.6-luna-high",
"gpt-5.6-luna-xhigh",
"gpt-5.6-luna-max",
] as const;

View File

@@ -0,0 +1,103 @@
/**
* Regression: the vision-bridge reroute must work when the configured vision
* model is an `auto/*` virtual id, and the describe path must only run when the
* vision pool is genuinely empty.
*
* Upstream v3.8.50 resolves `auto/*` fixedModels through the vision router
* pool; the guardrail-level guard (`bestUsable === false && !auto/*`) keeps the
* reroute from being blocked when the router returns an unresolved auto id.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { VisionBridgeGuardrail } = await import("../../../src/lib/guardrails/visionBridge.ts");
const { resetGuardrailsForTests } = await import("../../../src/lib/guardrails/registry.ts");
import type { GuardrailContext } from "../../../src/lib/guardrails/base.ts";
import type { VisionModelConfig } from "../../../src/lib/guardrails/visionBridgeHelpers.ts";
let mockSettings: Record<string, unknown> = {};
let visionCallCount = 0;
let credentialsMock: (model: string) => Promise<boolean | null> = async () => null;
function createGuardrail(options?: Parameters<typeof VisionBridgeGuardrail>[0]) {
return new VisionBridgeGuardrail({
...options,
deps: {
getSettings: async () => mockSettings,
callVisionModel: async (_imageDataUri: string, _config: VisionModelConfig) => {
visionCallCount++;
return "described";
},
hasUsableCredentials: credentialsMock,
...(options?.deps ?? {}),
},
});
}
function createContext(overrides: Partial<GuardrailContext> = {}): GuardrailContext {
return { model: "deepseek/deepseek-chat", log: console, ...overrides };
}
function imagePayload(overrides: Record<string, unknown> = {}): Record<string, unknown> {
return {
model: "deepseek/deepseek-chat",
messages: [
{
role: "user",
content: [
{ type: "text", text: "What is in this image?" },
{ type: "image_url", image_url: { url: "https://example.com/cat.png" } },
],
},
],
...overrides,
};
}
function baseSettings() {
return {
visionBridgeEnabled: true,
visionBridgeModel: "auto/best-vision",
visionBridgePrompt: "Describe this image concisely.",
visionBridgeTimeout: 30000,
visionBridgeMaxImages: 10,
};
}
test.beforeEach(() => {
resetGuardrailsForTests({ registerDefaults: false });
visionCallCount = 0;
credentialsMock = async () => null;
mockSettings = baseSettings();
});
test("VB-REROUTE-AUTO: auto/best-vision resolves through the router pool and reroutes (no describe)", async () => {
// Provider "auto" has no credential rows → hasUsableCredentials=false for the
// raw auto id; the router must still resolve a pool model and reroute.
credentialsMock = async (model: string) => (model.startsWith("auto/") ? false : null);
const guardrail = createGuardrail();
const result = await guardrail.preCall(imagePayload(), createContext());
assert.strictEqual(result.block, false);
assert.strictEqual(visionCallCount, 0, "describe path must not run when a vision target exists");
assert.ok(result.modifiedPayload, "payload must be modified");
const body = result.modifiedPayload as Record<string, unknown>;
// The reroute points the request at the resolved vision model from the pool.
assert.notStrictEqual(body.model, "deepseek/deepseek-chat");
assert.deepEqual((result.meta as Record<string, unknown>).rerouted, true);
});
test("VB-REROUTE-AUTO: falls back to describe only when the ENTIRE vision pool is unusable", async () => {
// Every vision candidate is confirmed unusable → nothing to reroute to → the
// describe path runs (existing behavior).
credentialsMock = async () => false;
const guardrail = createGuardrail();
const result = await guardrail.preCall(imagePayload(), createContext());
assert.strictEqual(result.block, false);
assert.strictEqual(visionCallCount, 1, "describe path must run when no vision target is usable");
const body = result.modifiedPayload as Record<string, unknown>;
assert.strictEqual(body.model, "deepseek/deepseek-chat");
});

View File

@@ -0,0 +1,126 @@
/**
* Regression: claude-wire format vision targets (MiniMax, Z.AI, Kimi, …)
* reject remote image URLs (MiniMax 403 code 2013). The vision bridge must
* normalize remote URLs to base64 data URIs for these targets.
*/
import test from "node:test";
import assert from "node:assert/strict";
const {
isClaudeWireFormatModel,
ensureBase64ImagesForClaudeWire,
} = await import("../../../src/lib/guardrails/visionBridgeHelpers.ts");
test("isClaudeWireFormatModel: true for anthropic and claude-format registry providers", () => {
assert.strictEqual(isClaudeWireFormatModel("anthropic/claude-sonnet-4"), true);
assert.strictEqual(isClaudeWireFormatModel("minimax/MiniMax-M3"), true);
assert.strictEqual(isClaudeWireFormatModel("zai/glm-5"), true);
assert.strictEqual(isClaudeWireFormatModel("claude/claude-opus"), true);
assert.strictEqual(isClaudeWireFormatModel("wafer/wafer-model"), true);
});
test("isClaudeWireFormatModel: false for openai-format providers", () => {
assert.strictEqual(isClaudeWireFormatModel("openai/gpt-4o-mini"), false);
assert.strictEqual(isClaudeWireFormatModel("kiro/minimax-m2.5"), false);
assert.strictEqual(isClaudeWireFormatModel("auto/best-vision"), false);
assert.strictEqual(isClaudeWireFormatModel(null), false);
});
test("ensureBase64ImagesForClaudeWire: passthrough for non-claude-wire models", async () => {
const body = {
model: "openai/gpt-4o-mini",
messages: [
{
role: "user",
content: [
{ type: "text", text: "hi" },
{ type: "image_url", image_url: { url: "https://example.com/a.png" } },
],
},
],
};
const out = await ensureBase64ImagesForClaudeWire(body, "openai/gpt-4o-mini");
assert.strictEqual(out, body, "non-claude-wire body must be returned untouched");
});
test("ensureBase64ImagesForClaudeWire: keeps data-URI images as-is", async () => {
const dataUri = "data:image/png;base64,iVBORw0KGgo=";
const body = {
model: "minimax/MiniMax-M3",
messages: [
{
role: "user",
content: [{ type: "image_url", image_url: { url: dataUri } }],
},
],
};
const out = await ensureBase64ImagesForClaudeWire(body, "minimax/MiniMax-M3");
const part = out.messages[0].content[0];
assert.strictEqual(part.image_url.url, dataUri);
});
test("ensureBase64ImagesForClaudeWire: resolves remote URLs to base64 for claude-wire targets", async () => {
const pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
new Response(new Uint8Array(Buffer.from(pngBase64, "base64")), {
status: 200,
headers: { "content-type": "image/png" },
});
try {
const body = {
model: "minimax/MiniMax-M3",
messages: [
{
role: "user",
content: [
{ type: "text", text: "What is this?" },
{ type: "image_url", image_url: { url: "https://example.com/cat.png" } },
],
},
],
};
const out = await ensureBase64ImagesForClaudeWire(
body,
"minimax/MiniMax-M3",
async () =>
new Response(new Uint8Array(Buffer.from(pngBase64, "base64")), {
status: 200,
headers: { "content-type": "image/png" },
})
);
const part = out.messages[0].content[1];
assert.ok(
part.image_url.url.startsWith("data:image/png;base64,"),
"remote URL must be resolved to a base64 data URI"
);
assert.ok(part.image_url.url.includes(pngBase64));
} finally {
globalThis.fetch = originalFetch;
}
});
test("ensureBase64ImagesForClaudeWire: fail-open when the remote fetch fails", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => {
throw new Error("network down");
};
try {
const body = {
model: "minimax/MiniMax-M3",
messages: [
{
role: "user",
content: [{ type: "image_url", image_url: { url: "https://example.com/cat.png" } }],
},
],
};
const out = await ensureBase64ImagesForClaudeWire(body, "minimax/MiniMax-M3");
const part = out.messages[0].content[0];
assert.strictEqual(part.image_url.url, "https://example.com/cat.png");
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -0,0 +1,64 @@
/**
* Regression: the vision-bridge SELF-LOOP must authenticate with a real
* DB-backed API key, not the `sk_omniroute` sentinel.
*
* Root cause on runtime v3.8.49: `callVisionModelSingle` used
* `resolvedApiKey || "sk_omniroute"` for the Authorization header of the
* OmniRoute self-loop request. On instances with REQUIRE_API_KEY enabled the
* runtime rejects `sk_omniroute` with 401 "Missing API key", so EVERY
* vision-bridge describe call failed and image requests were never processed.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { resolveSelfLoopApiKey } = await import(
"../../../src/lib/guardrails/visionBridgeHelpers.ts"
);
test("uses VISION_BRIDGE_API_KEY when set", async () => {
const previous = process.env.VISION_BRIDGE_API_KEY;
process.env.VISION_BRIDGE_API_KEY = "sk-operator-key";
try {
const key = await resolveSelfLoopApiKey(async () => "sk-db-key");
assert.strictEqual(key, "sk-operator-key");
} finally {
if (previous === undefined) delete process.env.VISION_BRIDGE_API_KEY;
else process.env.VISION_BRIDGE_API_KEY = previous;
}
});
test("falls back to the injected resolver (DB key) when no env key is set", async () => {
const previous = process.env.VISION_BRIDGE_API_KEY;
delete process.env.VISION_BRIDGE_API_KEY;
try {
const key = await resolveSelfLoopApiKey(async () => "sk-real-db-key");
assert.strictEqual(key, "sk-real-db-key");
} finally {
if (previous === undefined) delete process.env.VISION_BRIDGE_API_KEY;
else process.env.VISION_BRIDGE_API_KEY = previous;
}
});
test("never returns the sk_omniroute sentinel when a real key is resolvable", async () => {
const previous = process.env.VISION_BRIDGE_API_KEY;
delete process.env.VISION_BRIDGE_API_KEY;
try {
const key = await resolveSelfLoopApiKey(async () => "sk-db-key");
assert.notStrictEqual(key, "sk_omniroute");
} finally {
if (previous === undefined) delete process.env.VISION_BRIDGE_API_KEY;
else process.env.VISION_BRIDGE_API_KEY = previous;
}
});
test("falls back to sk_omniroute only when nothing else is available", async () => {
const previous = process.env.VISION_BRIDGE_API_KEY;
delete process.env.VISION_BRIDGE_API_KEY;
try {
const key = await resolveSelfLoopApiKey(async () => "");
assert.strictEqual(key, "sk_omniroute");
} finally {
if (previous === undefined) delete process.env.VISION_BRIDGE_API_KEY;
else process.env.VISION_BRIDGE_API_KEY = previous;
}
});

View File

@@ -701,6 +701,67 @@ test("provider-scoped image generation POST uses the shared 401 account fallback
]);
});
test("v1 image generation POST normalizes a terminal upstream 401 to the OpenAI-standard error shape", async () => {
await seedConnection("openai", { apiKey: "single-expired-image-key" });
globalThis.fetch = async (url, options: RequestInit = {}) => {
assert.equal(String(url), "https://api.openai.com/v1/images/generations");
const authorization = new Headers(options.headers).get("authorization") ?? "";
assert.equal(authorization, "Bearer single-expired-image-key");
return new Response(JSON.stringify({ error: { message: "expired access token" } }), {
status: 401,
headers: { "content-type": "application/json" },
});
};
const response = await imageRoute.POST(
new Request("http://localhost/api/v1/images/generations", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: "openai/gpt-image-2", prompt: "normalize terminal 401" }),
})
);
const body = (await response.json()) as ErrorResponseBody;
assert.equal(response.status, 401);
assert.deepEqual(body.error, {
message: "expired access token",
type: "authentication_error",
code: "invalid_api_key",
});
});
test("provider-scoped image generation POST normalizes a terminal upstream 401 to the OpenAI-standard error shape", async () => {
await seedConnection("openai", { apiKey: "provider-single-expired-key" });
globalThis.fetch = async (url, options: RequestInit = {}) => {
assert.equal(String(url), "https://api.openai.com/v1/images/generations");
const authorization = new Headers(options.headers).get("authorization") ?? "";
assert.equal(authorization, "Bearer provider-single-expired-key");
return new Response(JSON.stringify({ error: { message: "expired provider token" } }), {
status: 401,
headers: { "content-type": "application/json" },
});
};
const response = await providerImageRoute.POST(
new Request("http://localhost/api/v1/providers/openai/images/generations", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: "gpt-image-2", prompt: "normalize provider terminal 401" }),
}),
{ params: Promise.resolve({ provider: "openai" }) }
);
const body = (await response.json()) as ErrorResponseBody;
assert.equal(response.status, 401);
assert.deepEqual(body.error, {
message: "expired provider token",
type: "authentication_error",
code: "invalid_api_key",
});
});
test("v1 image generation POST refreshes an expired Antigravity token before dispatch", async () => {
await seedConnection("antigravity", {
authType: "oauth",

View File

@@ -6,6 +6,7 @@ import { describe, it } from "node:test";
import {
providerLacksModelListing,
providerUsesCuratedModelsOnly,
providerUsesExclusiveSyncedListing,
} from "@/lib/providers/modelListingCapability";
describe("providerLacksModelListing (#5420)", () => {
@@ -36,3 +37,18 @@ describe("providerLacksModelListing (#5420)", () => {
assert.equal(providerUsesCuratedModelsOnly("kimi-coding"), false);
});
});
describe("providerUsesExclusiveSyncedListing", () => {
it("is true only for Cursor (id or alias)", () => {
assert.equal(providerUsesExclusiveSyncedListing("cursor"), true);
assert.equal(providerUsesExclusiveSyncedListing("cu"), true);
assert.equal(providerUsesExclusiveSyncedListing("Cursor"), true);
});
it("is false for other providers including authoritative live-catalog ones", () => {
assert.equal(providerUsesExclusiveSyncedListing("github"), false);
assert.equal(providerUsesExclusiveSyncedListing("command-code"), false);
assert.equal(providerUsesExclusiveSyncedListing("openai"), false);
assert.equal(providerUsesExclusiveSyncedListing(""), false);
});
});

View File

@@ -0,0 +1,205 @@
// #9654: Per-connection virtual admission lanes
import test from "node:test";
import assert from "node:assert/strict";
const admissionModule = await import("../../src/shared/middleware/chatBodyAdmission.ts");
const {
PerConnectionAdmissionController,
resolveSessionId,
admitChatRequest,
admitChatStructure,
perConnectionAdmissionController,
ChatAdmissionController,
CHAT_MAX_HEAVY_IN_FLIGHT,
} = admissionModule;
function makeRequest(headers: Record<string, string>, body = "{}"): Request {
const h: Record<string, string> = { "content-type": "application/json", ...headers };
return new Request("http://x/v1/chat/completions", { method: "POST", headers: h, body });
}
test("resolveSessionId hashes bearer token into opaque key", () => {
const req = makeRequest({ authorization: "Bearer sk-secret-key-123" });
const sid = resolveSessionId(req);
assert.ok(sid.startsWith("key_"));
assert.equal(sid.length, "key_".length + 16);
// Same key → same hash
const req2 = makeRequest({ authorization: "Bearer sk-secret-key-123" });
assert.equal(resolveSessionId(req2), sid);
// Different key → different hash
const req3 = makeRequest({ authorization: "Bearer sk-different-key-456" });
assert.notEqual(resolveSessionId(req3), sid);
});
test("resolveSessionId hashes x-api-key header (Anthropic-style)", () => {
const req = makeRequest({ "x-api-key": "anthropic-key-xyz" });
const sid = resolveSessionId(req);
assert.ok(sid.startsWith("key_"));
assert.equal(sid.length, "key_".length + 16);
});
test("resolveSessionId returns 'anonymous' for no auth", () => {
const req = makeRequest({}, "{}");
assert.equal(resolveSessionId(req), "anonymous");
});
test("resolveSessionId does not leak raw API key in the session ID", () => {
const req = makeRequest({ authorization: "Bearer sk-secret-key-123" });
const sid = resolveSessionId(req);
assert.ok(!sid.includes("sk-secret-key-123"));
assert.ok(!sid.includes("secret"));
});
test("PerConnectionAdmissionController isolates capacity across sessions", () => {
const pc = new PerConnectionAdmissionController(1);
const ctrlA = pc.getController("session-a");
const ctrlB = pc.getController("session-b");
// Session A acquires the only slot
const leaseA = ctrlA.tryAcquireHeavy();
assert.ok(leaseA);
// Session A is now full
assert.equal(ctrlA.tryAcquireHeavy(), null);
// Session B still has capacity — isolation works
const leaseB = ctrlB.tryAcquireHeavy();
assert.ok(leaseB);
leaseA.release();
leaseB.release();
});
test("PerConnectionAdmissionController returns same controller for same session", () => {
const pc = new PerConnectionAdmissionController(1);
const a1 = pc.getController("session-a");
const a2 = pc.getController("session-a");
assert.equal(a1, a2);
});
test("PerConnectionAdmissionController creates new controller for new session", () => {
const pc = new PerConnectionAdmissionController(1);
const a = pc.getController("session-a");
const b = pc.getController("session-b");
assert.notEqual(a, b);
});
test("PerConnectionAdmissionController enforces maxSessions LRU eviction", () => {
const pc = new PerConnectionAdmissionController(1, { maxSessions: 2, sessionTtlMs: 60000 });
const a = pc.getController("a");
const b = pc.getController("b");
assert.equal(pc.sessionCount, 2);
// Touch 'a' so 'b' is oldest
const aAgain = pc.getController("a");
assert.equal(aAgain, a, "same a reference");
// Creating 'c' should evict 'b' (oldest)
const c = pc.getController("c");
assert.equal(pc.sessionCount, 2);
// 'a' survives, 'b' is evicted
const aAfter = pc.getController("a");
assert.equal(aAfter, a, "a should still exist after c added");
// 'b' gets a fresh controller (old one was evicted)
const newB = pc.getController("b");
assert.notEqual(newB, b, "b should be evicted and recreated");
});
test("PerConnectionAdmissionController evicts idle sessions after TTL", async () => {
const pc = new PerConnectionAdmissionController(1, {
sessionTtlMs: 50,
maxSessions: 64,
});
const ctrl = pc.getController("idle-session");
assert.ok(ctrl);
assert.equal(pc.sessionCount, 1);
// Wait past TTL + eviction tick
await new Promise((resolve) => setTimeout(resolve, 120));
// Accessing again should trigger eviction → fresh controller
const fresh = pc.getController("idle-session");
assert.notEqual(fresh, ctrl);
});
test("PerConnectionAdmissionController snapshot does not leak raw keys", () => {
const pc = new PerConnectionAdmissionController(1);
pc.getController("key_abc123");
pc.getController("anonymous");
const snap = pc.snapshot();
assert.equal(snap.length, 2);
for (const entry of snap) {
assert.ok(typeof entry.sessionId === "string");
assert.ok(entry.sessionId.includes("key_abc123") || entry.sessionId === "anonymous");
assert.ok(typeof entry.activeHeavy === "number");
assert.ok(typeof entry.idleMs === "number");
}
});
test("admitChatRequest uses per-connection controller by default", async () => {
const result = await admitChatRequest(
makeRequest({ authorization: "Bearer sk-test-key" }),
{ largeBodyBytes: 32, hardMaxBytes: 1024 }
);
assert.equal(result.admit, true);
if (result.admit) result.lease?.release();
});
test("admitChatRequest with explicit controller overrides per-connection lookup", async () => {
const explicitController = new ChatAdmissionController(1);
const result = await admitChatRequest(
makeRequest({ authorization: "Bearer sk-test-key" }),
{ controller: explicitController, largeBodyBytes: 32, hardMaxBytes: 1024 }
);
assert.equal(result.admit, true);
if (result.admit) result.lease?.release();
});
test("admitChatStructure routes structural rejection to per-connection controller", async () => {
// occupy sess-a's per-connection controller via the module-level instance
const controller = perConnectionAdmissionController.getController("sess-a");
const occupied = controller.tryAcquireHeavy();
assert.ok(occupied);
const result = await admitChatStructure(
{
messages: Array.from({ length: 3 }, () => ({ role: "user", content: "x" })),
},
null,
{
sessionId: "sess-a",
maxMessages: 10,
heavyMessages: 1,
heavyTools: 10,
heavyTokens: 10_000,
}
);
// Session A is busy → 503
assert.equal(result.admit, false);
if (result.admit) return;
assert.equal(result.response.status, 503);
assert.equal(result.response.headers.get("Retry-After"), "1");
occupied.release();
});
test("admitChatStructure with different sessionId gets independent capacity", async () => {
// occupy sess-a's per-connection controller
const ctrlA = perConnectionAdmissionController.getController("sess-a");
const occupied = ctrlA.tryAcquireHeavy();
assert.ok(occupied);
// Session B should get its own controller → admitted
const result = await admitChatStructure(
{
messages: Array.from({ length: 500 }, () => ({ role: "user", content: "x" })),
},
null,
{
sessionId: "sess-b",
maxMessages: 0,
heavyMessages: 200,
heavyTools: 64,
heavyTokens: 32_000,
}
);
assert.equal(result.admit, true);
if (result.admit) {
assert.notEqual(result.lease, null);
result.lease?.release();
}
occupied.release();
});

View File

@@ -0,0 +1,222 @@
// Focused tests for the shared icon-URL validator (src/shared/validation/iconUrl.ts)
// and the data-URL acceptance in createProviderNodeSchema/updateProviderNodeSchema.
// Mirrors the acceptance criteria: valid http(s) + valid `data:image/*;base64` URLs
// accepted; malformed values, non-image data URLs, non-base64 image data URLs, and
// unsafe schemes rejected.
import test from "node:test";
import assert from "node:assert/strict";
import { isValidProviderIconUrl } from "../../src/shared/validation/iconUrl.ts";
import {
createProviderNodeSchema,
updateProviderNodeSchema,
} from "../../src/shared/validation/schemas.ts";
// `iVBORw0KGgo=...` is a base64-encoded (truncated but structurally valid) PNG header.
const VALID_PNG_DATA_URL = "data:image/png;base64,iVBORw0KGgo=";
const VALID_SVG_DATA_URL =
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=";
const VALID_XICON_DATA_URL = "data:image/x-icon;base64,QUJDRA==";
const VALID_JPEG_DATA_URL = "data:image/jpeg;base64,/9j/4AAQSkZJRg==";
const VALID_HTTP = "https://example.com/logo.png";
const VALID_HTTP_2000 = "https://example.com/" + "a".repeat(1970) + ".png";
// ---- shared validator ----
test("isValidProviderIconUrl accepts empty and http(s)", () => {
assert.equal(isValidProviderIconUrl(""), true);
assert.equal(isValidProviderIconUrl(VALID_HTTP), true);
assert.equal(isValidProviderIconUrl("http://example.com/logo.png"), true);
});
test("isValidProviderIconUrl accepts valid data:image/*;base64 URLs", () => {
assert.equal(isValidProviderIconUrl(VALID_PNG_DATA_URL), true);
assert.equal(isValidProviderIconUrl(VALID_SVG_DATA_URL), true);
assert.equal(isValidProviderIconUrl(VALID_XICON_DATA_URL), true);
assert.equal(isValidProviderIconUrl(VALID_JPEG_DATA_URL), true);
});
test("data-URL scheme / media type / base64 marker are case-insensitive (RFC 2397)", () => {
assert.equal(isValidProviderIconUrl("DATA:image/png;BASE64,iVBORw0KGgo="), true);
assert.equal(isValidProviderIconUrl("Data:image/png;Base64,iVBORw0KGgo="), true);
assert.equal(isValidProviderIconUrl("data:IMAGE/PNG;base64,iVBORw0KGgo="), true);
assert.equal(isValidProviderIconUrl("data:image/Png;base64,iVBORw0KGgo="), true);
});
test("data-URL accepts valid media-type parameters before the terminal ;base64", () => {
assert.equal(isValidProviderIconUrl("data:image/svg+xml;charset=utf-8;base64,PHN2Zy8+"), true);
assert.equal(isValidProviderIconUrl("data:image/svg+xml;charset=UTF-8;base64,PHN2Zy8+"), true);
assert.equal(isValidProviderIconUrl("data:image/png;foo=bar;base64,QUJD"), true);
// Review case: percent-encoded token parameter value (`%` is an HTTP token char).
assert.equal(isValidProviderIconUrl("data:image/png;name=foo%20bar;base64,QUJD"), true);
assert.equal(isValidProviderIconUrl("data:image/png;name=foo%ZZ;base64,QUJD"), false);
// Multiple parameters + RFC 2045 quoted-string values are accepted.
assert.equal(
isValidProviderIconUrl('data:image/png;name="foo bar";charset=utf-8;base64,QUJD'),
true
);
assert.equal(isValidProviderIconUrl('data:image/png;name="a;b";base64,QUJD'), true);
// Escaped quote inside a quoted-string value (quoted-pair).
assert.equal(isValidProviderIconUrl('data:image/png;name="say \\"hi\\"";base64,QUJD'), true);
});
test('data-URL rejects valueless parameters per RFC 2397 (attribute "=" value)', () => {
const invalid = [
"data:image/png;foo;base64,QUJD", // review case: valueless parameter
"data:image/png;base64;base64,QUJD", // second ;base64 is a valueless parameter
"data:image/png;=bar;base64,QUJD", // empty attribute
"data:image/png;foo=;base64,QUJD", // empty value
"data:image/png;foo=bar;baz;base64,QUJD", // valueless among valued params
];
for (const v of invalid) {
assert.equal(isValidProviderIconUrl(v), false, `Should reject: ${JSON.stringify(v)}`);
}
});
test("data-URL media types use the full HTTP token alphabet (RFC 7230/6838)", () => {
// All `tchar` code points in one subtype; plus common image subtypes that use
// `+`, `.` and `-` (the previous grammar only allowed `[a-z0-9.+-]`).
const tokenAlphabet = "!#$%&'*+-.^_`|~09";
assert.equal(isValidProviderIconUrl(`data:image/${tokenAlphabet};base64,QUJD`), true);
assert.equal(isValidProviderIconUrl("data:image/svg+xml;base64,QUJD"), true);
assert.equal(isValidProviderIconUrl("data:image/vnd.microsoft.icon;base64,QUJD"), true);
assert.equal(isValidProviderIconUrl("data:image/x-ms-bmp;base64,QUJD"), true);
// Token characters rejected by the old partial regex are now accepted.
assert.equal(isValidProviderIconUrl("data:image/x~weird*name`;base64,QUJD"), true);
// Any valid token is a valid subtype — `png..` is a legal RFC 7230 token even
// though it looks odd; traversal-shaped payloads are harmless here because
// data URLs never touch a filesystem.
assert.equal(isValidProviderIconUrl("data:image/png..;base64,QUJD"), true);
// Non-token characters in the subtype are still rejected.
const invalidSubtypes = [
"data:image/png/sub;base64,QUJD", // slash inside subtype
"data:image/pn g;base64,QUJD", // space
"data:image/;base64,QUJD", // empty subtype
"data:image/png\u0000;base64,QUJD", // control char
];
for (const v of invalidSubtypes) {
assert.equal(isValidProviderIconUrl(v), false, `Should reject: ${JSON.stringify(v)}`);
}
});
test("data-URL media-type and parameter grammar edge cases", () => {
assert.equal(isValidProviderIconUrl("data:image/png;base64,QUJD"), true);
assert.equal(isValidProviderIconUrl("data:image/png;charset=utf-8;base64,QUJD"), true);
// `;base64` must be terminal — params after it are rejected.
assert.equal(isValidProviderIconUrl("data:image/png;base64;charset=utf-8,QUJD"), false);
// Attribute charset must be a token.
assert.equal(isValidProviderIconUrl("data:image/png;char set=utf-8;base64,QUJD"), false);
assert.equal(isValidProviderIconUrl('data:image/png;charset="unterminated;base64,QUJD'), false);
// First-comma split rule: a quoted-string value containing a literal comma is
// conservatively rejected (documented limitation).
assert.equal(isValidProviderIconUrl('data:image/png;name="a,b";base64,QUJD'), false);
// Header-only data URLs and empty metadata are rejected.
assert.equal(isValidProviderIconUrl("data:"), false);
assert.equal(isValidProviderIconUrl("data:;base64,QUJD"), false);
assert.equal(isValidProviderIconUrl("data:image/png;base64,"), false);
});
test("data-URL payloads are strictly validated — whitespace is rejected", () => {
const invalid = [
"data:image/png;base64,iVBORw0K\nGgo=", // line break in payload
"data:image/png;base64,iVBORw0K\r\nGgo=", // CRLF in payload
"data:image/png;base64,iVBORw0K\tGgo=", // tab in payload
"data:image/png;base64,iVB ORw", // space in payload
"data:image/png;base64,iVBORw0K Ggo=", // space in payload
"data:image/png;base64,iVBOR\u00A0w0KGgo=", // non-breaking space mid-payload (Unicode whitespace)
];
for (const v of invalid) {
assert.equal(isValidProviderIconUrl(v), false, `Should reject: ${JSON.stringify(v)}`);
}
});
test("isValidProviderIconUrl rejects malformed values and unsafe schemes", () => {
const invalid = [
"not-a-url",
"javascript:alert(1)",
"ftp://broken",
"file:///etc/passwd",
"//example.com/logo.png", // scheme-relative — not explicitly http(s)
"https://", // no host
"https://exa mple.com/x.png", // space in host
];
for (const v of invalid) {
assert.equal(isValidProviderIconUrl(v), false, `Should reject: ${JSON.stringify(v)}`);
}
});
test("isValidProviderIconUrl rejects non-image data URLs", () => {
const invalid = [
"data:text/html;base64,QUJD",
"data:text/plain;base64,QUJD",
"data:application/json;base64,e30=",
"data:image/png", // no ;base64,payload
"data:application/octet-stream;base64,QUJD",
];
for (const v of invalid) {
assert.equal(isValidProviderIconUrl(v), false, `Should reject: ${JSON.stringify(v)}`);
}
});
test("isValidProviderIconUrl rejects non-base64 image data URLs", () => {
const invalid = [
"data:image/png,QUJD", // missing ;base64
"data:image/png;base64,", // empty payload
"data:image/png;base64,!!!!", // invalid base64 chars
"data:image/png;base64,A", // payload not 4-char aligned
"data:image/png;base64,AAAA=", // over-padded
"data:image/png;base64,=QUJD=", // leading '='
];
for (const v of invalid) {
assert.equal(isValidProviderIconUrl(v), false, `Should reject: ${JSON.stringify(v)}`);
}
});
test("isValidProviderIconUrl enforces length caps", () => {
// http(s): 2000-char cap preserved.
const tooLongHttp = "https://example.com/" + "a".repeat(2000) + ".png";
assert.equal(isValidProviderIconUrl(tooLongHttp), false);
assert.equal(isValidProviderIconUrl(VALID_HTTP_2000), true);
// data URL: generous 256 KB cap.
const tooLongData = "data:image/png;base64," + "A".repeat(256 * 1024 + 10);
assert.equal(isValidProviderIconUrl(tooLongData), false);
});
// ---- server-side schema integration ----
test("createProviderNodeSchema accepts a valid data:image/*;base64 iconUrl", () => {
const result = createProviderNodeSchema.safeParse({
name: "Test",
prefix: "test",
apiType: "chat",
iconUrl: VALID_PNG_DATA_URL,
});
assert.equal(result.success, true);
});
test("createProviderNodeSchema rejects invalid data URLs", () => {
const invalid = [
"data:text/html;base64,QUJD",
"data:image/png,QUJD",
"data:image/png;base64,!!!!",
"data:image/png;base64,",
"data:image/png;base64,iVB ORw",
];
for (const iconUrl of invalid) {
const result = createProviderNodeSchema.safeParse({
name: "Test",
prefix: "test",
apiType: "chat",
iconUrl,
});
assert.equal(result.success, false, `Should reject: ${JSON.stringify(iconUrl)}`);
}
});
test("updateProviderNodeSchema accepts a valid data:image/*;base64 iconUrl", () => {
const result = updateProviderNodeSchema.safeParse({
name: "Test",
prefix: "test",
baseUrl: "https://test.com",
iconUrl: VALID_SVG_DATA_URL,
});
assert.equal(result.success, true);
});

View File

@@ -0,0 +1,95 @@
/**
* Regression tests for the embedded-services supervisor (ServiceSupervisor).
*
* Bug: when the supervised binary cannot be spawned (ENOENT / EACCES, or a
* non-executable binary such as an ELF on Windows — EFTYPE), the child emits
* the 'error' event — NOT 'exit' — and on Windows spawn() can even throw
* synchronously. The supervisor had no 'error' handler, so it stayed in
* "starting" forever while the HealthChecker kept polling the dead port every
* healthIntervalMs (each probe firing a full ProxyFetch dispatcher+native
* fetch pair, e.g. against a CLIProxyAPI port that will never answer on this
* platform).
*
* Fix under test: the supervisor now (1) handles synchronous spawn() throws
* and the child 'error' event → stops the poller and transitions to "error",
* and (2) transitions to "error" and stops the poller once the health checker
* reports FAILURE_THRESHOLD consecutive failures, including during startup,
* instead of polling the dead endpoint forever.
*
* Run: node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test tests/unit/services/serviceSupervisorSpawnError.test.ts
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { mkdtemp, writeFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { ServiceSupervisor } from "../../../src/lib/services/ServiceSupervisor.ts";
import type { ServiceConfig } from "../../../src/lib/services/types.ts";
function baseConfig(overrides: Partial<ServiceConfig> = {}): ServiceConfig {
return {
tool: "cliproxy",
port: 0,
spawnArgs: () => ({
command: process.execPath,
args: ["-e", "setTimeout(() => {}, 30_000)"],
env: process.env,
cwd: tmpdir(),
}),
healthUrl: () => "http://127.0.0.1:1/v1/models",
healthIntervalMs: 50,
stopTimeoutMs: 1_000,
logsBufferBytes: 4_096,
...overrides,
};
}
describe("ServiceSupervisor spawn-failure handling", () => {
it("transitions to error and stops polling when the binary cannot be spawned", async () => {
// A plain text file is not an executable: on Windows spawn() throws
// synchronously (EFTYPE/EINVAL); on POSIX the child emits 'error'
// (ENOENT/EACCES). Both paths must land in an explicit error state.
const dir = await mkdtemp(join(tmpdir(), "svc-sup-spawn-"));
const badBinary = join(dir, "not-an-executable.txt");
await writeFile(badBinary, "this is not a runnable binary\n", "utf8");
const supervisor = new ServiceSupervisor(
baseConfig({
spawnArgs: () => ({
command: badBinary,
args: [],
env: process.env,
cwd: dir,
}),
})
);
try {
const status = await supervisor.start();
assert.equal(status.state, "error");
assert.ok(status.lastError, "lastError should describe the spawn failure");
assert.match(
status.lastError!,
/ENOENT|EACCES|EINVAL|EFTYPE|not recognized|spawn|%1|Win32/i
);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it("transitions to error after consecutive health failures instead of polling forever", async () => {
const supervisor = new ServiceSupervisor(baseConfig());
try {
// The child runs but never opens a server on the health URL: the
// HealthChecker reaches FAILURE_THRESHOLD (3 × 50ms) and the supervisor
// must surface an explicit error instead of staying "running" with an
// endless poller.
await assert.rejects(supervisor.start(), /Health probe failed|Service failed to start/i);
assert.equal(supervisor.getStatus().state, "error");
assert.ok(supervisor.getStatus().lastError);
} finally {
await supervisor.stop();
}
});
});

View File

@@ -0,0 +1,989 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
proxyFetch,
resolveProxyForRequest,
runWithProxyContext,
runWithTlsTracking,
setTlsClientForTest,
} from "../../open-sse/utils/proxyFetch.ts";
import tlsClient, {
TlsClient,
type TlsFetchOptions,
type WreqSession,
} from "../../open-sse/utils/tlsClient.ts";
import { httpBackedChat } from "../../open-sse/services/browserBackedChat.ts";
type EnvState = Record<string, string | undefined>;
const ENV_KEYS = [
"ENABLE_TLS_FINGERPRINT",
"TLS_FINGERPRINT_PROVIDERS",
"HTTPS_PROXY",
"https_proxy",
"HTTP_PROXY",
"http_proxy",
"ALL_PROXY",
"all_proxy",
"NO_PROXY",
"no_proxy",
"OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK",
"PROXY_AUTO_SELECT_ENABLED",
] as const;
async function withEnv(env: EnvState, fn: () => Promise<void> | void): Promise<void> {
const prior = Object.fromEntries(ENV_KEYS.map((key) => [key, process.env[key]]));
for (const key of ENV_KEYS) {
if (env[key] === undefined) delete process.env[key];
else process.env[key] = env[key];
}
try {
await fn();
} finally {
for (const key of ENV_KEYS) {
if (prior[key] === undefined) delete process.env[key];
else process.env[key] = prior[key];
}
setTlsClientForTest(null);
}
}
function fakeTlsClient(
fetch: (url: string, options?: TlsFetchOptions) => Promise<Response>,
) {
return { available: true, fetch };
}
test("explicit direct proxy resolution keeps a session and never rereads the environment", async () => {
await withEnv({ HTTPS_PROXY: "http://placeholder.proxy:8080" }, async () => {
const created: Array<Record<string, unknown>> = [];
const client = new TlsClient(async (options) => {
created.push(options);
return {
close: async () => {},
fetch: async () => new Response("ok"),
};
});
await client.fetch("https://upstream.example", { proxy: null });
await client.fetch("https://upstream.example", { proxy: null });
await client.fetch("https://upstream.example");
assert.equal(created.length, 2);
assert.equal(created[0]?.proxy, undefined);
assert.equal(created[1]?.proxy, "http://placeholder.proxy:8080");
});
});
test("same proxy is isolated by stable account session scope", async () => {
const created: Array<Record<string, unknown>> = [];
const client = new TlsClient(async (options) => {
created.push(options);
return {
close: async () => {},
fetch: async () => new Response("ok"),
};
});
const proxy = "http://shared.proxy:8080";
await client.fetch("https://upstream.example", { proxy, sessionScope: "account-a" });
await client.fetch("https://upstream.example", { proxy, sessionScope: "account-a" });
await client.fetch("https://upstream.example", { proxy, sessionScope: "account-b" });
assert.equal(created.length, 2);
});
test("circuit failures are isolated to the exact session scope and proxy", async () => {
const client = new TlsClient(async (options) => ({
close: async () => {},
fetch: async () => {
if (options.proxy === "http://bad.proxy:8080") throw new Error("bad proxy");
return new Response("good");
},
}));
for (let attempt = 0; attempt < 3; attempt++) {
await assert.rejects(
client.fetch("https://upstream.example", {
proxy: "http://bad.proxy:8080",
sessionScope: "bad-account",
}),
);
}
const response = await client.fetch("https://upstream.example", {
proxy: "http://good.proxy:8080",
sessionScope: "good-account",
});
assert.equal(await response.text(), "good");
});
test("redirect error semantics are forwarded to wreq unchanged", async () => {
let redirect: unknown;
const client = new TlsClient(async () => ({
close: async () => {},
fetch: async (_url, options) => {
redirect = options?.redirect;
return new Response("ok");
},
}));
await client.fetch("https://upstream.example", { redirect: "error" });
assert.equal(redirect, "error");
});
test("Request input bypasses wreq without losing method headers or body", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
},
async () => {
let tlsCalls = 0;
let received: Request | null = null;
setTlsClientForTest(
fakeTlsClient(async () => {
tlsCalls++;
return new Response("tls");
}),
);
const input = new Request("https://upstream.example/v1", {
method: "POST",
headers: { "x-test": "present" },
body: "payload",
});
const tracked = await runWithTlsTracking("codex", () =>
proxyFetch(input, {}, {
undiciFetch: async (forwarded) => {
received = forwarded as Request;
return new Response("dispatcher");
},
}),
);
assert.equal(tlsCalls, 0);
assert.equal(received, input);
assert.equal(received?.method, "POST");
assert.equal(received?.headers.get("x-test"), "present");
assert.equal(await received?.text(), "payload");
assert.equal(tracked.tlsFingerprintUsed, false);
},
);
});
test("non-idempotent TLS failures are never replayed", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
},
async () => {
let dispatcherCalls = 0;
setTlsClientForTest(
fakeTlsClient(async () => {
throw new Error("post-send transport failure");
}),
);
await assert.rejects(
runWithTlsTracking("codex", () =>
proxyFetch(
"https://upstream.example/v1",
{ method: "POST", body: "{}" },
{
undiciFetch: async () => {
dispatcherCalls++;
return new Response("unexpected");
},
},
),
),
(error: Error & { code?: string }) =>
error.code === "TLS_FINGERPRINT_FAILED" &&
error.message === "TLS fingerprint request failed; request is not safe to replay",
);
assert.equal(dispatcherCalls, 0);
},
);
});
test("safe GET TLS failure falls back through the same configured proxy", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
HTTPS_PROXY: "http://placeholder.proxy:8080",
},
async () => {
setTlsClientForTest(
fakeTlsClient(async () => {
throw new Error("transport failed");
}),
);
let dispatcher: unknown;
const tracked = await runWithTlsTracking("codex", () =>
proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async (_input, init) => {
dispatcher = init?.dispatcher;
return new Response("fallback");
},
}),
);
assert.ok(dispatcher);
assert.equal(await tracked.result.text(), "fallback");
assert.equal(tracked.tlsFingerprintUsed, false);
},
);
});
test("internal TimeoutError is not classified as a caller abort", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
},
async () => {
const timeout = new Error("internal timeout");
timeout.name = "TimeoutError";
let dispatcherCalls = 0;
setTlsClientForTest(
fakeTlsClient(async () => {
throw timeout;
}),
);
const tracked = await runWithTlsTracking("codex", () =>
proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => {
dispatcherCalls++;
return new Response("fallback");
},
}),
);
assert.equal(dispatcherCalls, 1);
assert.equal(await tracked.result.text(), "fallback");
},
);
});
test("control-plane direct fallback bypasses an environment proxy", async () => {
await withEnv(
{
HTTPS_PROXY: "http://placeholder.proxy:8080",
OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK: "true",
},
async () => {
const result = await runWithProxyContext(
{ type: "http", host: "127.0.0.1", port: "9" },
() => resolveProxyForRequest("https://upstream.example/v1"),
{ directFallbackOnUnreachable: true },
);
assert.deepEqual(result, { source: "direct", proxyUrl: null });
},
);
});
test("new proxied TLS transport requires an explicit provider allowlist", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: undefined,
HTTPS_PROXY: "http://placeholder.proxy:8080",
},
async () => {
let tlsCalls = 0;
let dispatcherCalls = 0;
setTlsClientForTest(
fakeTlsClient(async () => {
tlsCalls++;
return new Response("tls");
}),
);
const tracked = await runWithTlsTracking("codex", () =>
proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => {
dispatcherCalls++;
return new Response("dispatcher");
},
}),
);
assert.equal(tlsCalls, 0);
assert.equal(dispatcherCalls, 1);
assert.equal(await tracked.result.text(), "dispatcher");
},
);
});
test("caller abort propagates unchanged and never falls back", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
},
async () => {
const controller = new AbortController();
const abortError = new Error("caller stopped");
let dispatcherCalls = 0;
setTlsClientForTest(
fakeTlsClient(async () => {
controller.abort(abortError);
throw abortError;
}),
);
await assert.rejects(
runWithTlsTracking("codex", () =>
proxyFetch(
"https://upstream.example/v1",
{ signal: controller.signal },
{
undiciFetch: async () => {
dispatcherCalls++;
return new Response("unexpected");
},
},
),
),
(error) => error === abortError,
);
assert.equal(dispatcherCalls, 0);
},
);
});
test("stateful TLS session failures never fall back even for GET", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
},
async () => {
let dispatcherCalls = 0;
setTlsClientForTest(
fakeTlsClient(async () => {
const error = new Error("transport failed");
Object.defineProperty(error, "sessionHadCookies", { value: true });
throw error;
}),
);
await assert.rejects(
runWithTlsTracking("codex", () =>
proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => {
dispatcherCalls++;
return new Response("unexpected");
},
}),
),
(error: Error & { code?: string }) =>
error.code === "TLS_FINGERPRINT_FAILED" &&
error.message === "TLS fingerprint request failed; stateful session cannot be replayed",
);
assert.equal(dispatcherCalls, 0);
},
);
});
test("TLS transport failures never expose proxy credentials", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
HTTPS_PROXY: "http://user:password@placeholder.proxy:8080",
},
async () => {
const warnings: string[] = [];
const originalWarn = console.warn;
console.warn = (...args: unknown[]) => warnings.push(args.map(String).join(" "));
try {
setTlsClientForTest(
fakeTlsClient(async () => {
throw new Error(
"connect failed via http://user:password@placeholder.proxy:8080",
);
}),
);
const tracked = await runWithTlsTracking("codex", () =>
proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => new Response("fallback"),
}),
);
assert.equal(await tracked.result.text(), "fallback");
assert.equal(warnings.some((line) => line.includes("user:password")), false);
assert.equal(warnings.some((line) => line.includes("placeholder.proxy")), false);
} finally {
console.warn = originalWarn;
}
},
);
});
test("family-pinned proxies retain dispatcher enforcement instead of using wreq", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
HTTPS_PROXY: "http://placeholder.proxy:8080?family=ipv4",
},
async () => {
let tlsCalls = 0;
let dispatcherCalls = 0;
setTlsClientForTest(
fakeTlsClient(async () => {
tlsCalls++;
return new Response("tls");
}),
);
const tracked = await runWithTlsTracking("codex", () =>
proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => {
dispatcherCalls++;
return new Response("dispatcher");
},
}),
);
assert.equal(tlsCalls, 0);
assert.equal(dispatcherCalls, 1);
assert.equal(await tracked.result.text(), "dispatcher");
},
);
});
test("relay contexts never route through wreq", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
},
async () => {
let tlsCalls = 0;
let relayCalls = 0;
setTlsClientForTest(
fakeTlsClient(async () => {
tlsCalls++;
return new Response("tls");
}),
);
const tracked = await runWithTlsTracking("codex", () =>
runWithProxyContext(
{ type: "vercel", host: "relay.example", relayAuth: "test-auth" },
() =>
proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => {
relayCalls++;
return new Response("relay");
},
}),
),
);
assert.equal(tlsCalls, 0);
assert.equal(relayCalls, 1);
assert.equal(await tracked.result.text(), "relay");
},
);
});
test("wreq responses are adapted to the native Response API", async () => {
const sourceBody = new Response("adapted").body;
assert.ok(sourceBody);
const client = new TlsClient(async () => ({
close: async () => {},
fetch: async () => ({
status: 200,
statusText: "OK",
headers: new Map([["x-source", "wreq"]]),
body: sourceBody,
url: "https://upstream.example/v1",
redirected: true,
}),
}));
const response = await client.fetch("https://upstream.example/v1", { proxy: null });
assert.equal(response instanceof Response, true);
assert.equal(response.headers.get("x-source"), "wreq");
assert.equal(response.url, "https://upstream.example/v1");
assert.equal(response.redirected, true);
assert.equal(await response.text(), "adapted");
});
test("exit waits for pending session creation and closes the late session", async () => {
type TestSession = {
close: () => Promise<void>;
fetch: () => Promise<Response>;
};
const sessionGate = Promise.withResolvers<TestSession>();
const creationStarted = Promise.withResolvers<void>();
const closeStarted = Promise.withResolvers<void>();
const closeGate = Promise.withResolvers<void>();
let closed = 0;
const client = new TlsClient(() => {
creationStarted.resolve();
return sessionGate.promise;
});
const request = client.fetch("https://upstream.example/v1", { proxy: null });
await creationStarted.promise;
let exitSettled = false;
const exiting = client.exit().then(() => {
exitSettled = true;
});
sessionGate.resolve({
close: async () => {
closed++;
closeStarted.resolve();
await closeGate.promise;
},
fetch: async () => new Response("unexpected"),
});
await closeStarted.promise;
assert.equal(exitSettled, false);
closeGate.resolve();
await assert.rejects(request, /wreq-js transport failed/);
await exiting;
assert.equal(closed, 1);
});
test("bounded session cache closes the least-recently-used idle session", async () => {
const closed: string[] = [];
const client = new TlsClient(async (options) => {
const proxy = String(options.proxy);
return {
close: async () => {
closed.push(proxy);
},
fetch: async () => new Response("ok"),
};
}, 2);
await client.fetch("https://upstream.example/v1", { proxy: "http://proxy-1:8080" });
await client.fetch("https://upstream.example/v1", { proxy: "http://proxy-2:8080" });
await client.fetch("https://upstream.example/v1", { proxy: "http://proxy-3:8080" });
assert.deepEqual(closed, ["http://proxy-1:8080"]);
await client.exit();
});
test("direct browser-backed TLS calls isolate sessions by pool key", async () => {
const originalFetch = tlsClient.fetch.bind(tlsClient);
let observedScope: string | undefined;
tlsClient.fetch = async (_url, options) => {
observedScope = options?.sessionScope;
return new Response("ok", {
status: 200,
headers: { "content-type": "application/json" },
});
};
try {
const result = await httpBackedChat({
poolKey: "claude-web:account-123",
chatUrl: "https://claude.ai/api/chat",
chatPageUrl: "https://claude.ai/new",
userMessage: "hello",
chatUrlMatchDomain: "claude.ai",
inputSelector: "#prompt",
});
assert.equal(result.status, 200);
assert.equal(observedScope, "claude-web:account-123");
} finally {
tlsClient.fetch = originalFetch;
}
});
test("allowlisted proxied TLS receives the exact proxy and account scope", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
HTTPS_PROXY: "http://placeholder.proxy:8080",
},
async () => {
let observedOptions: TlsFetchOptions | undefined;
setTlsClientForTest(
fakeTlsClient(async (_url, options) => {
observedOptions = options;
return new Response("tls");
}),
);
const tracked = await runWithTlsTracking(
{ provider: "codex", sessionScope: "connection-123" },
() =>
proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => {
throw new Error("dispatcher must not run");
},
}),
);
assert.equal(observedOptions?.proxy, "http://placeholder.proxy:8080");
assert.equal(observedOptions?.sessionScope, "connection-123");
assert.equal(tracked.tlsFingerprintUsed, true);
assert.equal(await tracked.result.text(), "tls");
},
);
});
test("proxy dispatcher failures sanitize logs and propagated errors", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "false",
HTTPS_PROXY: "http://user:password@placeholder.proxy:8080",
},
async () => {
const errors: string[] = [];
const originalError = console.error;
console.error = (...args: unknown[]) => errors.push(args.map(String).join(" "));
try {
let caught: unknown;
try {
await proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => {
const error = new Error(
"connect failed via http://user:password@placeholder.proxy:8080",
) as Error & { code?: string };
error.code = "ECONNREFUSED";
throw error;
},
});
} catch (error) {
caught = error;
}
assert.ok(caught instanceof Error);
assert.equal(caught.message, "Proxy request failed");
assert.equal("code" in caught ? caught.code : undefined, "PROXY_UNREACHABLE");
assert.equal(errors.some((line) => line.includes("user:password")), false);
assert.equal(errors.some((line) => line.includes("placeholder.proxy")), false);
} finally {
console.error = originalError;
}
},
);
});
test("half-open circuit admits only one probe for an isolated session key", async () => {
const originalNow = Date.now;
const probeStarted = Promise.withResolvers<void>();
const probeGate = Promise.withResolvers<void>();
let probeMode = false;
let fetchCalls = 0;
const client = new TlsClient(async () => ({
close: async () => {},
fetch: async () => {
fetchCalls++;
if (!probeMode) throw new Error("upstream unavailable");
probeStarted.resolve();
await probeGate.promise;
return new Response("recovered");
},
}));
try {
for (let attempt = 0; attempt < 3; attempt++) {
await assert.rejects(
client.fetch("https://upstream.example/v1", {
proxy: null,
sessionScope: "connection-123",
}),
/wreq-js transport failed/,
);
}
probeMode = true;
const afterCooldown = originalNow() + 31_000;
Date.now = () => afterCooldown;
const probe = client.fetch("https://upstream.example/v1", {
proxy: null,
sessionScope: "connection-123",
});
await probeStarted.promise;
await assert.rejects(
client.fetch("https://upstream.example/v1", {
proxy: null,
sessionScope: "connection-123",
}),
(error: unknown) =>
error instanceof Error &&
"code" in error &&
error.code === "TLS_CIRCUIT_OPEN",
);
assert.equal(fetchCalls, 4);
probeGate.resolve();
assert.equal(await (await probe).text(), "recovered");
} finally {
Date.now = originalNow;
probeGate.resolve();
await client.exit();
}
});
test("circuit invalidation snapshots cookies before closing the failed session", async () => {
let closed = false;
const client = new TlsClient(async () => ({
close: () => {
closed = true;
},
getCookies: () => (closed ? {} : { session: "account-a" }),
fetch: async () => {
throw new Error("upstream unavailable");
},
}));
try {
for (let attempt = 0; attempt < 3; attempt++) {
await assert.rejects(
client.fetch("https://upstream.example/v1", {
proxy: null,
sessionScope: "connection-123",
}),
(error: unknown) =>
error instanceof Error &&
"sessionHadCookies" in error &&
error.sessionHadCookies === true,
);
}
await Promise.resolve();
assert.equal(closed, true);
await assert.rejects(
client.fetch("https://upstream.example/v1", {
proxy: null,
sessionScope: "connection-123",
}),
(error: unknown) =>
error instanceof Error &&
"code" in error &&
error.code === "TLS_CIRCUIT_OPEN" &&
"sessionHadCookies" in error &&
error.sessionHadCookies === true,
);
} finally {
await client.exit();
}
});
test("pending session creation is bounded per TLS client", async () => {
const sessionGates = [
Promise.withResolvers<WreqSession>(),
Promise.withResolvers<WreqSession>(),
];
let creates = 0;
const client = new TlsClient(() => {
const gate = sessionGates[creates++];
if (!gate) throw new Error("unexpected session creation");
return gate.promise;
}, 2);
const session: WreqSession = {
close: async () => {},
fetch: async () => new Response("ok"),
};
const first = client.fetch("https://upstream.example/v1", {
proxy: "http://proxy-1:8080",
sessionScope: "connection-1",
});
const second = client.fetch("https://upstream.example/v1", {
proxy: "http://proxy-2:8080",
sessionScope: "connection-2",
});
try {
assert.equal(creates, 2);
await assert.rejects(
client.fetch("https://upstream.example/v1", {
proxy: "http://proxy-3:8080",
sessionScope: "connection-3",
}),
(error: unknown) =>
error instanceof Error &&
"code" in error &&
error.code === "TLS_SESSION_CAPACITY",
);
assert.equal(creates, 2);
for (const gate of sessionGates) gate.resolve(session);
assert.equal(await (await first).text(), "ok");
assert.equal(await (await second).text(), "ok");
} finally {
for (const gate of sessionGates) gate.resolve(session);
await Promise.allSettled([first, second]);
await client.exit();
}
});
test("direct TLS fallback never auto-selects a different proxy route", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: undefined,
PROXY_AUTO_SELECT_ENABLED: "true",
},
async () => {
let tlsCalls = 0;
let dispatcherCalls = 0;
let autoSelectCalls = 0;
let nativeCalls = 0;
setTlsClientForTest(
fakeTlsClient(async () => {
tlsCalls++;
throw new Error("wreq transport failed");
}),
);
const response = await proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => {
dispatcherCalls++;
const error = new Error("fetch failed: ECONNREFUSED") as Error & { code?: string };
error.code = "ECONNREFUSED";
throw error;
},
findWorkingProxy: async () => {
autoSelectCalls++;
return "http://unexpected.proxy:8080";
},
nativeFetch: async () => {
nativeCalls++;
return new Response("native");
},
});
assert.equal(tlsCalls, 1);
assert.equal(dispatcherCalls, 2);
assert.equal(autoSelectCalls, 0);
assert.equal(nativeCalls, 1);
assert.equal(await response.text(), "native");
},
);
});
test("proxied TLS compatibility overload requires an explicit session scope", async () => {
await withEnv(
{
ENABLE_TLS_FINGERPRINT: "true",
TLS_FINGERPRINT_PROVIDERS: "codex",
HTTPS_PROXY: "http://placeholder.proxy:8080",
},
async () => {
let tlsCalls = 0;
let dispatcherCalls = 0;
setTlsClientForTest(
fakeTlsClient(async () => {
tlsCalls++;
return new Response("unexpected");
}),
);
const tracked = await runWithTlsTracking("codex", () =>
proxyFetch("https://upstream.example/v1", {}, {
undiciFetch: async () => {
dispatcherCalls++;
return new Response("dispatcher");
},
}),
);
assert.equal(tlsCalls, 0);
assert.equal(dispatcherCalls, 1);
assert.equal(tracked.tlsFingerprintUsed, false);
assert.equal(await tracked.result.text(), "dispatcher");
},
);
});
test("circuit trip defers session close until active response streams release", async () => {
let first = true;
let closed = 0;
const client = new TlsClient(async () => ({
close: async () => {
closed++;
},
fetch: async () => {
if (first) {
first = false;
return {
status: 200,
statusText: "OK",
headers: [],
body: new ReadableStream<Uint8Array>({}),
};
}
throw new Error("upstream unavailable");
},
}));
try {
const activeResponse = await client.fetch("https://upstream.example/v1", {
proxy: null,
sessionScope: "connection-123",
});
for (let attempt = 0; attempt < 3; attempt++) {
await assert.rejects(
client.fetch("https://upstream.example/v1", {
proxy: null,
sessionScope: "connection-123",
}),
/wreq-js transport failed/,
);
}
assert.equal(closed, 0);
await activeResponse.body?.cancel();
await Promise.resolve();
assert.equal(closed, 1);
} finally {
await client.exit();
}
});
test("streaming wreq body failures are sanitized and counted by the circuit", async () => {
const secret = "http://user:password@proxy.example:8080";
const client = new TlsClient(async () => ({
close: async () => {},
fetch: async () => ({
status: 200,
statusText: "OK",
headers: [["content-type", "text/plain"]],
body: new ReadableStream<Uint8Array>({
pull(controller) {
const error = new Error(`body failed through ${secret}`) as Error & {
code?: string;
};
error.code = "UND_ERR_SOCKET";
controller.error(error);
},
}),
}),
}));
try {
const response = await client.fetch("https://upstream.example/v1", {
proxy: "http://user:password@proxy.example:8080",
sessionScope: "connection-123",
});
await assert.rejects(
response.text(),
(error: unknown) =>
error instanceof Error &&
error.message === "wreq-js response body failed" &&
"code" in error &&
error.code === "UND_ERR_SOCKET" &&
!String(error).includes("user:password"),
);
assert.equal(
client.getCircuitState(
"http://user:password@proxy.example:8080",
"connection-123",
).failureCount,
1,
);
} finally {
await client.exit();
}
});

View File

@@ -0,0 +1,62 @@
import test from "node:test";
import assert from "node:assert/strict";
const { openaiToAntigravityRequest } =
await import("../../open-sse/translator/request/openai-to-gemini.ts");
const body = {
messages: [
{ role: "user", content: "Use the terminal tool to echo hi." },
{
role: "assistant",
tool_calls: [
{
id: "call_1",
type: "function",
function: { name: "terminal", arguments: JSON.stringify({ command: "echo hi" }) },
},
],
},
{ role: "tool", tool_call_id: "call_1", name: "terminal", content: "hi" },
],
tools: [
{
type: "function",
function: {
name: "terminal",
description: "Run a shell command",
parameters: { type: "object", properties: { command: { type: "string" } } },
},
},
],
};
function modelParts(model: string, b: unknown) {
const envelope = openaiToAntigravityRequest(model, b, true) as {
request: { contents: Array<{ role: string; parts: Array<Record<string, unknown>> }> };
};
const modelMsg = envelope.request.contents.find((c) => c.role === "model");
assert.ok(modelMsg, "expected an assistant (model-role) message in the translation");
return modelMsg.parts;
}
test("antigravity multi-turn tool call carries the signature bypass sentinel by default", () => {
const parts = modelParts("gemini-3.1-pro-low", body);
const fc = parts.find((p) => p.functionCall);
assert.ok(fc, "expected a functionCall part");
assert.equal(fc.thoughtSignature, "skip_thought_signature_validator");
});
test("ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS=0 disables the sentinel", () => {
const prev = process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS;
process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS = "0";
try {
const parts = modelParts("gemini-3.1-pro-low", body);
const fc = parts.find((p) => p.functionCall);
assert.ok(fc, "expected a functionCall part");
assert.equal(fc.thoughtSignature, undefined);
} finally {
if (prev === undefined) delete process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS;
else process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS = prev;
}
});

View File

@@ -0,0 +1,140 @@
// @vitest-environment jsdom
//
// Field-level icon URL validation for the compatible-provider Add modal: an invalid
// iconUrl must surface as an inline field error BEFORE a blind POST, instead of only
// failing after the request round-trip. Mirrors the shared validator
// (src/shared/validation/iconUrl.ts) used by both the UI and the server schema.
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("next-intl", () => ({
useTranslations: () => (key: string) => key,
}));
const { default: AddCompatibleProviderModal } =
await import("../../../src/app/(dashboard)/dashboard/providers/components/AddCompatibleProviderModal");
const containers: Array<{ root: ReturnType<typeof createRoot>; el: HTMLDivElement }> = [];
function render(props: Record<string, unknown>) {
const el = document.createElement("div");
document.body.appendChild(el);
const root = createRoot(el);
act(() => {
root.render(
<AddCompatibleProviderModal
isOpen
mode="openai"
onClose={() => {}}
onCreated={() => {}}
{...(props as any)}
/>
);
});
containers.push({ root, el });
return el;
}
function inputByLabel(el: Element, label: string): HTMLInputElement {
const inputs = Array.from(el.querySelectorAll<HTMLInputElement>("input"));
const found = inputs.find((i) => {
const labelEl = i.previousElementSibling || i.parentElement?.previousElementSibling;
return labelEl?.textContent === label;
});
if (!found) throw new Error(`No input for label: ${label}`);
return found;
}
function setInputValue(input: HTMLInputElement, value: string) {
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")!.set!;
act(() => {
setter.call(input, value);
input.dispatchEvent(new Event("input", { bubbles: true }));
});
}
async function waitFor(fn: () => boolean, timeoutMs = 2000) {
const start = Date.now();
while (!fn()) {
if (Date.now() - start > timeoutMs) throw new Error("waitFor timed out");
await new Promise((r) => setTimeout(r, 20));
}
}
beforeEach(() => {
vi.clearAllMocks();
// Any POST that does reach the network should never happen for the invalid-input
// cases under test — fail loudly if it does.
vi.stubGlobal(
"fetch",
vi.fn(() =>
Promise.resolve({ ok: true, json: () => Promise.resolve({ node: {} }) } as Response)
)
);
});
afterEach(() => {
for (const { root, el } of containers.splice(0)) {
act(() => root.unmount());
el.remove();
}
vi.unstubAllGlobals();
});
describe("AddCompatibleProviderModal — iconUrl field-level validation", () => {
it("shows an inline error for an unsafe scheme and does NOT submit", async () => {
const el = render({});
const modal = el.querySelector('[role="dialog"]')!;
setInputValue(inputByLabel(modal, "nameLabel"), "My Node");
setInputValue(inputByLabel(modal, "prefixLabel"), "mynode");
setInputValue(inputByLabel(modal, "iconUrlLabel"), "javascript:alert(1)");
const buttons = Array.from(modal.querySelectorAll<HTMLButtonElement>("button"));
const addBtn = buttons.find((b) => b.textContent === "add");
act(() => addBtn!.click());
await waitFor(() => modal.textContent?.includes("iconUrlInvalid") ?? false);
expect(modal.textContent).toContain("iconUrlInvalid");
// The invalid icon must never reach the API.
expect(fetch).not.toHaveBeenCalled();
});
it("shows an inline error for a non-image data URL and does NOT submit", async () => {
const el = render({});
const modal = el.querySelector('[role="dialog"]')!;
setInputValue(inputByLabel(modal, "nameLabel"), "My Node");
setInputValue(inputByLabel(modal, "prefixLabel"), "mynode");
setInputValue(inputByLabel(modal, "iconUrlLabel"), "data:text/html;base64,QUJD");
const buttons = Array.from(modal.querySelectorAll<HTMLButtonElement>("button"));
const addBtn = buttons.find((b) => b.textContent === "add");
act(() => addBtn!.click());
await waitFor(() => modal.textContent?.includes("iconUrlInvalid") ?? false);
expect(modal.textContent).toContain("iconUrlInvalid");
expect(fetch).not.toHaveBeenCalled();
});
it("accepts a valid data:image/*;base64 iconUrl and submits", async () => {
const el = render({});
const modal = el.querySelector('[role="dialog"]')!;
setInputValue(inputByLabel(modal, "nameLabel"), "My Node");
setInputValue(inputByLabel(modal, "prefixLabel"), "mynode");
setInputValue(inputByLabel(modal, "iconUrlLabel"), "data:image/png;base64,iVBORw0KGgo=");
const buttons = Array.from(modal.querySelectorAll<HTMLButtonElement>("button"));
const addBtn = buttons.find((b) => b.textContent === "add");
act(() => addBtn!.click());
await waitFor(() => (fetch as ReturnType<typeof vi.fn>).mock.calls.length > 0);
expect(modal.textContent).not.toContain("iconUrlInvalid");
const call = (fetch as ReturnType<typeof vi.fn>).mock.calls[0];
expect(String(call[0])).toBe("/api/provider-nodes");
const body = JSON.parse(String(call[1].body));
expect(body.iconUrl).toBe("data:image/png;base64,iVBORw0KGgo=");
});
});

View File

@@ -0,0 +1,131 @@
// @vitest-environment jsdom
//
// Field-level icon URL validation for the compatible-provider Edit modal: an invalid
// iconUrl must surface as an inline field error BEFORE the onSave callback fires, and a
// valid data:image/*;base64 iconUrl must submit. Mirrors the shared validator
// (src/shared/validation/iconUrl.ts) used by both the UI and the server schema.
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("next-intl", () => ({
useTranslations: () => (key: string) => key,
}));
const { default: EditCompatibleNodeModal } =
await import("../../../src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditCompatibleNodeModal");
const containers: Array<{ root: ReturnType<typeof createRoot>; el: HTMLDivElement }> = [];
function render(node: Record<string, unknown>, onSave?: () => Promise<void>) {
const el = document.createElement("div");
document.body.appendChild(el);
const root = createRoot(el);
act(() => {
root.render(
<EditCompatibleNodeModal
isOpen
node={node as any}
onSave={onSave || (async () => {})}
onClose={() => {}}
/>
);
});
containers.push({ root, el });
return el;
}
function inputByLabel(el: Element, label: string): HTMLInputElement {
const inputs = Array.from(el.querySelectorAll<HTMLInputElement>("input"));
const found = inputs.find((i) => {
const labelEl = i.previousElementSibling || i.parentElement?.previousElementSibling;
return labelEl?.textContent === label;
});
if (!found) throw new Error(`No input for label: ${label}`);
return found;
}
function setInputValue(input: HTMLInputElement, value: string) {
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")!.set!;
act(() => {
setter.call(input, value);
input.dispatchEvent(new Event("input", { bubbles: true }));
});
}
async function waitFor(fn: () => boolean, timeoutMs = 2000) {
const start = Date.now();
while (!fn()) {
if (Date.now() - start > timeoutMs) throw new Error("waitFor timed out");
await new Promise((r) => setTimeout(r, 20));
}
}
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
for (const { root, el } of containers.splice(0)) {
act(() => root.unmount());
el.remove();
}
vi.unstubAllGlobals();
});
const NODE = {
id: "oc-1",
name: "My Node",
prefix: "mynode",
baseUrl: "https://api.example.com/v1",
apiType: "chat",
iconUrl: "https://example.com/logo.png",
};
describe("EditCompatibleNodeModal — iconUrl field-level validation", () => {
it("shows an inline error for an unsafe scheme and does NOT call onSave", async () => {
const onSave = vi.fn(async () => {});
const el = render({ ...NODE, iconUrl: "javascript:alert(1)" });
const modal = el.querySelector('[role="dialog"]')!;
setInputValue(inputByLabel(modal, "iconUrlLabel"), "javascript:alert(1)");
const buttons = Array.from(modal.querySelectorAll<HTMLButtonElement>("button"));
const saveBtn = buttons.find((b) => b.textContent === "save");
act(() => saveBtn!.click());
await waitFor(() => modal.textContent?.includes("iconUrlInvalid") ?? false);
expect(modal.textContent).toContain("iconUrlInvalid");
expect(onSave).not.toHaveBeenCalled();
});
it("shows an inline error for a non-image data URL and does NOT call onSave", async () => {
const onSave = vi.fn(async () => {});
const el = render({ ...NODE, iconUrl: "data:text/html;base64,QUJD" });
const modal = el.querySelector('[role="dialog"]')!;
setInputValue(inputByLabel(modal, "iconUrlLabel"), "data:text/html;base64,QUJD");
const buttons = Array.from(modal.querySelectorAll<HTMLButtonElement>("button"));
const saveBtn = buttons.find((b) => b.textContent === "save");
act(() => saveBtn!.click());
await waitFor(() => modal.textContent?.includes("iconUrlInvalid") ?? false);
expect(modal.textContent).toContain("iconUrlInvalid");
expect(onSave).not.toHaveBeenCalled();
});
it("accepts a valid data:image/*;base64 iconUrl and calls onSave with it", async () => {
const onSave = vi.fn(async () => {});
const el = render({ ...NODE, iconUrl: "" }, onSave);
const modal = el.querySelector('[role="dialog"]')!;
setInputValue(inputByLabel(modal, "iconUrlLabel"), "data:image/png;base64,iVBORw0KGgo=");
const buttons = Array.from(modal.querySelectorAll<HTMLButtonElement>("button"));
const saveBtn = buttons.find((b) => b.textContent === "save");
act(() => saveBtn!.click());
await waitFor(() => onSave.mock.calls.length > 0);
expect(modal.textContent).not.toContain("iconUrlInvalid");
const payload = onSave.mock.calls[0][0];
expect(payload.iconUrl).toBe("data:image/png;base64,iVBORw0KGgo=");
});
});