From 034db3c3dd976ed1f4e065dc3d2fbdada88d84be Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Thu, 6 Aug 2026 10:27:10 -0400 Subject: [PATCH 01/10] fix(quality): clears two release/v3.8.50 base-red gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unblocks Merge integrity and Docs Gates for every PR against release/v3.8.50, not just this branch: - changelog.d/features/9415-newapi-sub2api-aggregator-balance.md had a non-standard YAML frontmatter header that no other fragment in the tree uses. check-changelog-integrity.mjs reads a fragment's first non-blank line to validate it starts with a markdown bullet; the frontmatter's leading `---` made that check fail regardless of the actual bullet content further down. Removed the frontmatter and reformatted the body to match the documented changelog.d/README.md bullet convention. - docs/ops/VM_DEPLOYMENT_GUIDE.md documented OMNIROUTE_MAX_POOL_SIZE and OMNIROUTE_DB_POOL_SIZE as tunable env vars, but neither is read anywhere in the codebase (confirmed via full-repo grep) — this repo uses SQLite, which has no connection-pool concept these vars could plausibly control. check:fabricated-docs --strict correctly flags fabricated env-var claims; removed the bullet rather than implementing a feature to match invented documentation. --- .../features/9415-newapi-sub2api-aggregator-balance.md | 7 +------ docs/ops/VM_DEPLOYMENT_GUIDE.md | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/changelog.d/features/9415-newapi-sub2api-aggregator-balance.md b/changelog.d/features/9415-newapi-sub2api-aggregator-balance.md index 421c33b198..e2317e3cb8 100644 --- a/changelog.d/features/9415-newapi-sub2api-aggregator-balance.md +++ b/changelog.d/features/9415-newapi-sub2api-aggregator-balance.md @@ -1,6 +1 @@ ---- -kind: feature -ref: "#9415" ---- - -New-API / One-API / Sub2API aggregator balance detection for compatible nodes. When a compatible provider node has the "Aggregator Gateway" toggle enabled, OmniRoute will query the aggregator's `/api/user/self` endpoint to detect the account balance. The dashboard shows the balance badge and quota-preflight routing skips exhausted accounts. The feature is gated by the `NEWAPI_AGGREGATOR_BALANCE` feature flag (default: off). A custom `quotaPerUnit` override is supported for aggregators that use a different rate than the default 500000 units/$1. +- **feat(sse):** New-API/One-API/Sub2API aggregator balance detection for compatible provider nodes — when the "Aggregator Gateway" toggle is enabled, OmniRoute queries the aggregator's `/api/user/self` endpoint to detect the account balance; the dashboard shows a balance badge and quota-preflight routing skips exhausted accounts. Gated by the `NEWAPI_AGGREGATOR_BALANCE` feature flag (default: off), with a custom `quotaPerUnit` override for aggregators that use a different rate than the default 500000 units/$1 ([#9415](https://github.com/diegosouzapw/OmniRoute/issues/9415)) diff --git a/docs/ops/VM_DEPLOYMENT_GUIDE.md b/docs/ops/VM_DEPLOYMENT_GUIDE.md index 3a885626b0..69e3ac4209 100644 --- a/docs/ops/VM_DEPLOYMENT_GUIDE.md +++ b/docs/ops/VM_DEPLOYMENT_GUIDE.md @@ -429,6 +429,5 @@ For deployments on small VPS instances (1 GB RAM or less): - **Disable background services** — set `OMNIROUTE_DISABLE_BACKGROUND_SERVICES=1` to skip scheduler, MCP server, and periodic maintenance tasks. See `docs/reference/ENVIRONMENT.md`. - **Use SQLite WAL mode** — enabled by default, reduces peak memory during concurrent reads. -- **Limit connection concurrency** — reduce `OMNIROUTE_MAX_POOL_SIZE` and `OMNIROUTE_DB_POOL_SIZE` in your environment. - **Avoid `next build` on the VPS** — build locally and deploy the standalone output (`.next/standalone/`). - **Monitor with `top` / `free -m`** — OmniRoute typically uses 200-400 MB RSS at idle on a 1 GB VM. From f1fda940477ee05ec7cf8ff8c36b0b306123bec9 Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Thu, 6 Aug 2026 11:39:08 -0400 Subject: [PATCH 02/10] fix(i18n): completes Vietnamese parity, fixes empty migration query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two more release/v3.8.50 base-red items, both surfaced while chasing CI failures on unrelated PRs: - vi.json was missing 8 keys that #9539 (NewAPI/Sub2API aggregator balance) added to en.json without a matching i18n:sync-ui run — pt-BR.json already had all 8, only Vietnamese drifted. Added translations for the 6 provider-settings strings, the feature-flag description, and the quota tooltip; verified against tests/unit/i18n-vi-completeness.test.ts (parity, placeholder preservation, ICU parse — all 5 assertions pass). - src/lib/db/migrations/120_interception_rules.sql was pure comments documenting a no-schema-change key_value namespace, with no executable SQL statement — the migration runner logged "FAILED: 120_interception_rules — Query contained no valid SQL statement" on every fresh DB init. 118_provider_param_filters.sql (same pattern, two migrations earlier) already ends with a bare `SELECT 1;` no-op for exactly this reason; 120 was just missing it. Verified directly against better-sqlite3 that the file now executes without error. --- src/i18n/messages/vi.json | 8 ++++++++ src/lib/db/migrations/120_interception_rules.sql | 1 + 2 files changed, 9 insertions(+) diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index f6333012d9..de7cbfc75d 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -5475,6 +5475,13 @@ "newApiUserIdLabel": "ID người dùng New-API", "newApiUserIdPlaceholder": "vd. 12345", "newApiUserIdHint": "Giá trị tiêu đề New-Api-User của AgentRouter, dùng cùng với khóa API console để lấy số dư hạn mức.", + "newApiAggregatorToggleLabel": "Cổng tổng hợp", + "newApiAggregatorToggleHint": "Bật tính năng phát hiện số dư cho các node tổng hợp New-API / One-API / Sub2API. Bảng điều khiển sẽ hiển thị huy hiệu số dư và định tuyến kiểm tra hạn mức trước sẽ bỏ qua các tài khoản đã hết hạn mức.", + "newApiAggregatorConsoleApiKeyHint": "Token truy cập hệ thống cho endpoint /api/user/self của bộ tổng hợp. Không phải là khóa API định tuyến.", + "newApiAggregatorUserIdHint": "Giá trị tiêu đề New-Api-User dùng để lấy số dư hạn mức của người dùng bộ tổng hợp.", + "newApiAggregatorQuotaPerUnitLabel": "Hạn mức trên mỗi đơn vị", + "newApiAggregatorQuotaPerUnitHint": "Số đơn vị tín dụng New-API trên mỗi $1 (mặc định: 500000). Ghi đè nếu bộ tổng hợp của bạn sử dụng tỷ lệ khác.", + "featureFlagNewApiAggregatorBalanceDescription": "Bật tính năng phát hiện số dư cho các node tương thích với bộ tổng hợp New-API / One-API / Sub2API", "cpaModeDisabledTitle": "Chế độ tương thích CLIProxyAPI đã bị tắt", "cpaModeEnabledTitle": "Chế độ tương thích CLIProxyAPI đã được bật", "customUserAgentHint": "Gợi ý User Agent tùy chỉnh", @@ -5590,6 +5597,7 @@ "tagGroupPlaceholder": "Nhập nhóm thẻ...", "testModel": "Kiểm tra mô hình", "testingModel": "Đang kiểm tra mô hình", + "modelTestQuotaTooltip": "Đã hết hạn mức — sẽ được đặt lại vào ngày mai hoặc cần nạp thêm", "toggleOffShort": "Tắt", "toggleOnShort": "Bật", "tokenExpiredBadge": "Nhãn token đã hết hạn", diff --git a/src/lib/db/migrations/120_interception_rules.sql b/src/lib/db/migrations/120_interception_rules.sql index d042a5f035..7e7e1593be 100644 --- a/src/lib/db/migrations/120_interception_rules.sql +++ b/src/lib/db/migrations/120_interception_rules.sql @@ -14,3 +14,4 @@ -- falls back to the existing native web-search-bypass defaults in webSearchFallback.ts). -- -- See: src/lib/db/interceptionRules.ts +SELECT 1; From 3ea174d5316895a15fbdb04c3c7d8c255807f32a Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Thu, 6 Aug 2026 13:08:34 -0400 Subject: [PATCH 03/10] fix(types): clears 6 pre-existing release/v3.8.50 typecheck errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit typecheck:core is its own blocking CI job (quality.yml), separate from Docs Gates/Merge integrity. Confirmed pre-existing and unrelated to any current work by branching this worktree directly from upstream/release/v3.8.50 with no other merges applied. - accountSemaphore.ts: isBypassed() already excludes null/<=0 maxConcurrency before ensureGate() is called, but a boolean- returning helper isn't a type predicate TS can narrow through. Added a targeted `as number` at the one call site, with a comment explaining why it's safe. - combo/comboStructure.ts: two module-scope `const HARD_COMPAT_REASONS` declarations with different values — a genuine "can't redeclare" compile error, not a narrowing gap. The first (4-item set including "output_tokens") had zero usages between its own declaration and the second; the second (3-item set, matching the CompatFilterOptions doc comment exactly) is what hasHardCapabilityFailure/ describeCapabilityFilterExhaustion/the third call site all actually use. Removed the dead first declaration. - combo/comboStructure.ts + combo/fusionPanel.ts: both accessed `.prompt`/`.model` on a `ComboModelStep | ComboProviderWildcardStep` union after only excluding `combo-ref`, but `ComboProviderWildcardStep` has neither field — a real latent bug (fusionPanel would have pushed `undefined` into a fusion panel for a wildcard step). Narrowed to `step.kind === "model"` in comboStructure, and switched to the already-existing `getComboModelString()` helper in fusionPanel (which correctly resolves to null for unsupported step kinds, mirroring how combo-ref is already skipped there). Verified directly via a standalone script exercising both branches (wildcard vs. model step). - combo/quotaStrategies.ts: imported `preferAntigravityConnectionsWithStoredProject` from a module that never existed (`../antigravityProjectPersistence.ts`, distinct from the real `antigravityProjectPersist.ts`) — the function itself was referenced nowhere else in the codebase. Wrote the missing implementation: prefers Antigravity connections with a discovered `projectId` for reset-aware routing, failing open to the full list when none have one yet (per the file's own "Exclude... from reset-aware pool" changelog note, softened to a preference — strict exclusion would empty the pool entirely for a fleet of freshly-added accounts). Verified directly via a standalone script. - compression/engines/ccr/index.ts: `enforceGlobalBudget(owner, bytes)` was called with only `bytes` at one of its two call sites, missing the `owner` argument the other call site (and the function's own doc comment on preferring the calling principal's LRU eviction) already uses correctly. Added the missing `entry.principalId` argument. - firecrawlQuotaFetcher.ts: `fetchFirecrawlQuota` was annotated to return `Promise` but every return path constructs a `FirecrawlQuota` (QuotaInfo extended with remainingCredits/planCredits/ extraCreditsInferred/overPlan) — the type the file already defines and the type `parseFirecrawlCreditUsage` already correctly returns. Widened the annotation to match; `FirecrawlQuota extends QuotaInfo` so this stays compatible with the `QuotaFetcher` contract. npm run typecheck:core and npm run check:dashboard-typecheck both pass cleanly. A subset of DB-backed tests in this area also fail, but 100% attributably to an already-tracked, unrelated migration version collision (134 -> [ccr_blocks, proxy_logs_egress_ip], see _tasks/features-v3.8.4/9route/POST-MERGE-AUDIT.md) — confirmed by every failure's stack trace bottoming out at that exact error, not at anything touched here. --- open-sse/services/accountSemaphore.ts | 4 ++- .../services/antigravityProjectPersistence.ts | 35 +++++++++++++++++++ open-sse/services/combo/comboStructure.ts | 4 +-- open-sse/services/combo/fusionPanel.ts | 8 +++-- open-sse/services/combo/quotaStrategies.ts | 4 +-- .../services/compression/engines/ccr/index.ts | 5 ++- open-sse/services/firecrawlQuotaFetcher.ts | 2 +- 7 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 open-sse/services/antigravityProjectPersistence.ts diff --git a/open-sse/services/accountSemaphore.ts b/open-sse/services/accountSemaphore.ts index ddb629e12e..ec0f06f090 100644 --- a/open-sse/services/accountSemaphore.ts +++ b/open-sse/services/accountSemaphore.ts @@ -200,7 +200,9 @@ export function acquire( return Promise.reject(makeAbortError(signal)); } - const gate = ensureGate(semaphoreKey, maxConcurrency); + // isBypassed() above already excluded null/<=0 — ensureGate requires a plain + // number, but a boolean-returning helper isn't a type predicate TS can narrow on. + const gate = ensureGate(semaphoreKey, maxConcurrency as number); clearCleanupTimer(gate); if (gate.running < gate.maxConcurrency && !isBlocked(gate)) { diff --git a/open-sse/services/antigravityProjectPersistence.ts b/open-sse/services/antigravityProjectPersistence.ts new file mode 100644 index 0000000000..066179a890 --- /dev/null +++ b/open-sse/services/antigravityProjectPersistence.ts @@ -0,0 +1,35 @@ +/** + * Prefer Antigravity connections with a discovered/stored `projectId` for + * reset-aware quota routing (#7719 follow-up). + * + * Antigravity's Code Assist API is scoped per-project — a connection whose + * `projectId` was never discovered (no `loadCodeAssist` round-trip has + * completed yet, see antigravityProjectPersist.ts) cannot serve a request + * reliably. Preferring connections that already have one avoids routing + * reset-aware traffic to an account that will just re-trigger discovery. + * + * This is a preference, not a hard requirement: if none of the candidate + * connections have a stored projectId yet (e.g. a freshly added account), + * excluding all of them would empty the reset-aware pool entirely, which is + * worse than routing to an undiscovered connection. Fail open to the full + * list in that case. + */ + +function hasStoredProjectId(connection: Record): boolean { + if (typeof connection.projectId === "string" && connection.projectId.trim().length > 0) { + return true; + } + const providerSpecificData = connection.providerSpecificData; + if (providerSpecificData && typeof providerSpecificData === "object") { + const nested = (providerSpecificData as Record).projectId; + if (typeof nested === "string" && nested.trim().length > 0) return true; + } + return false; +} + +export function preferAntigravityConnectionsWithStoredProject< + T extends Record, +>(connections: T[]): T[] { + const withStoredProject = connections.filter(hasStoredProjectId); + return withStoredProject.length > 0 ? withStoredProject : connections; +} diff --git a/open-sse/services/combo/comboStructure.ts b/open-sse/services/combo/comboStructure.ts index 58a8b99538..5bb4bbaaf3 100644 --- a/open-sse/services/combo/comboStructure.ts +++ b/open-sse/services/combo/comboStructure.ts @@ -137,7 +137,7 @@ function normalizeRuntimeStep( : {}), weight, label, - prompt: step.prompt || null, + prompt: step.kind === "model" ? step.prompt || null : null, } satisfies ResolvedComboTarget; } @@ -533,8 +533,6 @@ function hasKnownCompatibleContextLimit( return evaluateContextLimit(capabilities, requirements, target.modelStr) === true; } -const HARD_COMPAT_REASONS = new Set(["tools", "vision", "structured_output", "output_tokens"]); - /** * #8332: vision is a hard requirement, not a soft preference — a target whose vision * support is not confirmed can never succeed on an image_url request. Callers diff --git a/open-sse/services/combo/fusionPanel.ts b/open-sse/services/combo/fusionPanel.ts index 6397c5120c..20540d5850 100644 --- a/open-sse/services/combo/fusionPanel.ts +++ b/open-sse/services/combo/fusionPanel.ts @@ -10,7 +10,7 @@ * literal `auto/*` string panel member already behaves via the single- * dispatch safety net in src/sse/handlers/chat.ts. */ -import { normalizeComboStep } from "../../../src/lib/combos/steps.ts"; +import { getComboModelString, normalizeComboStep } from "../../../src/lib/combos/steps.ts"; import { executeComboRefUnit } from "./runtimeUnits.ts"; import type { ComboCollectionLike, @@ -51,7 +51,11 @@ export function extractFusionPanelSpec( panel.push(step.comboName); return; } - panel.push(step.model); + // Provider-wildcard steps have no concrete model to dispatch — fusion is a + // fixed-size panel of literal models/combo-refs, not a wildcard-expanding + // strategy (see file header). Skip rather than push an undefined model. + const modelStr = getComboModelString(step); + if (modelStr) panel.push(modelStr); }); return { panel, comboRefUnits }; } diff --git a/open-sse/services/combo/quotaStrategies.ts b/open-sse/services/combo/quotaStrategies.ts index 2e117f74fe..822ee57409 100644 --- a/open-sse/services/combo/quotaStrategies.ts +++ b/open-sse/services/combo/quotaStrategies.ts @@ -89,9 +89,7 @@ async function getQuotaAwareConnectionsForTarget( ? (connections as Array>) : []; if (provider === "antigravity" || provider === "agy") { - activeConnections = preferAntigravityConnectionsWithStoredProject( - activeConnections - ) as Array>; + activeConnections = preferAntigravityConnectionsWithStoredProject(activeConnections); } if ( !resetAwareConnectionCache.has(provider) && diff --git a/open-sse/services/compression/engines/ccr/index.ts b/open-sse/services/compression/engines/ccr/index.ts index e854666182..6d8d1e2f03 100644 --- a/open-sse/services/compression/engines/ccr/index.ts +++ b/open-sse/services/compression/engines/ccr/index.ts @@ -292,7 +292,10 @@ function rehydrateEntry(hash: string, principalId: string, now: number): CcrEntr // Re-admit through the same budgets a fresh store would face. If the block no longer // fits, it stays on disk and is served straight from the row instead of being cached. - if (enforcePrincipalBudget(entry.principalId, entry.bytes) && enforceGlobalBudget(entry.bytes)) { + if ( + enforcePrincipalBudget(entry.principalId, entry.bytes) && + enforceGlobalBudget(entry.principalId, entry.bytes) + ) { const key = buildStoreKey(hash, principalId === ANON ? undefined : principalId); ccrStore.set(key, entry); ccrTotalBytes += entry.bytes; diff --git a/open-sse/services/firecrawlQuotaFetcher.ts b/open-sse/services/firecrawlQuotaFetcher.ts index 9f0784fa06..92a8bb0a87 100644 --- a/open-sse/services/firecrawlQuotaFetcher.ts +++ b/open-sse/services/firecrawlQuotaFetcher.ts @@ -120,7 +120,7 @@ export function getFirecrawlBaseUrl(connection?: Record): strin export async function fetchFirecrawlQuota( connectionId: string, connection?: Record -): Promise { +): Promise { const cached = quotaCache.get(connectionId); if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) { return cached.quota; From 3b411c7da7c1081b01a22ad772a87e11da1a978b Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Thu, 6 Aug 2026 14:08:39 -0400 Subject: [PATCH 04/10] ci: re-trigger checks after transient runner shutdown From cf7e4148c5ef6968425f9abe93ebe41c2d289701 Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Fri, 7 Aug 2026 10:56:52 -0400 Subject: [PATCH 05/10] ci: re-trigger checks after GitHub Actions incident (2026-08-07, resolved) From 7a0515038b2aa3061d5b7e0ddfa1c5d85c3b3cb0 Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Fri, 7 Aug 2026 15:05:03 -0400 Subject: [PATCH 06/10] ci: re-trigger checks (previous push event was dropped) From 038035f9373da019ada9aaa2ae8e9aa869a1cba5 Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Fri, 7 Aug 2026 15:50:53 -0400 Subject: [PATCH 07/10] fix(sse): update stale ALL_ACCOUNTS_INACTIVE test assertions to ALL_TARGETS_SKIPPED Two combo-routing-engine.test.ts cases assert the pre-dispatch-skip scenario (isModelAvailable always false, zero dispatch attempts) returns ALL_ACCOUNTS_INACTIVE. Production code already distinguishes this case via the recordedAttempts === 0 branch and returns the more precise ALL_TARGETS_SKIPPED -- the tests were never updated when that branch shipped upstream, so they fail on a clean release/v3.8.50 checkout independent of this PR's changes. --- tests/unit/combo-routing-engine.test.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/unit/combo-routing-engine.test.ts b/tests/unit/combo-routing-engine.test.ts index 42cf4d03ef..a13cbb1d7b 100644 --- a/tests/unit/combo-routing-engine.test.ts +++ b/tests/unit/combo-routing-engine.test.ts @@ -2318,7 +2318,11 @@ test("handleComboChat returns a 503 when every model is unavailable before execu const payload = (await result.json()) as any; assert.equal(result.status, 503); - assert.equal(payload.error.code, "ALL_ACCOUNTS_INACTIVE"); + // isModelAvailable always false means every target is skipped by the + // pre-dispatch filter with zero dispatch attempts — the more precise + // ALL_TARGETS_SKIPPED classification, not ALL_ACCOUNTS_INACTIVE (which + // implies targets were attempted and their accounts found inactive). + assert.equal(payload.error.code, "ALL_TARGETS_SKIPPED"); }); test("handleComboChat treats provider circuit breaker responses as ordinary target failures", async () => { @@ -2847,7 +2851,10 @@ test("handleComboChat round-robin resolves nested combos and returns inactive wh const payload = (await result.json()) as any; assert.equal(result.status, 503); - assert.equal(payload.error.code, "ALL_ACCOUNTS_INACTIVE"); + // isModelAvailable always false means every nested target is skipped by the + // pre-dispatch filter with zero dispatch attempts — ALL_TARGETS_SKIPPED, + // not ALL_ACCOUNTS_INACTIVE (see the analogous priority-strategy test above). + assert.equal(payload.error.code, "ALL_TARGETS_SKIPPED"); }); test("handleComboChat round-robin treats provider circuit breaker responses as ordinary target failures", async () => { From 58ab721fe2615ba4f7819c0f6393f8154278554d Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Fri, 7 Aug 2026 16:11:08 -0400 Subject: [PATCH 08/10] fix(sse): update second stale ALL_ACCOUNTS_INACTIVE assertion (T24) Same pre-existing upstream test-drift as 038035f93: t23-t24-fallback-resilience.test.ts's T24 case asserts the pre-dispatch-skip scenario returns ALL_ACCOUNTS_INACTIVE, but production code returns the more precise ALL_TARGETS_SKIPPED when recordedAttempts === 0. Caught by this PR's own fresh CI run after the dirty-mergeable-state fix. --- tests/unit/t23-t24-fallback-resilience.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/unit/t23-t24-fallback-resilience.test.ts b/tests/unit/t23-t24-fallback-resilience.test.ts index 6682581e69..22f9c5d7da 100644 --- a/tests/unit/t23-t24-fallback-resilience.test.ts +++ b/tests/unit/t23-t24-fallback-resilience.test.ts @@ -148,7 +148,11 @@ test("T24: all inactive accounts return 503 service_unavailable (not 406)", asyn assert.equal(result.status, 503); const body = (await result.json()) as any; - assert.equal(body.error?.code, "ALL_ACCOUNTS_INACTIVE"); + // isModelAvailable always false means every target is skipped by the + // pre-dispatch filter with zero dispatch attempts — the more precise + // ALL_TARGETS_SKIPPED classification, not ALL_ACCOUNTS_INACTIVE (which + // implies targets were attempted and their accounts found inactive). + assert.equal(body.error?.code, "ALL_TARGETS_SKIPPED"); }); test("combo falls through 400s and reaches the next model", async () => { From eb817932e47a00f1a411feb61a4b9c86999c620a Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Sat, 8 Aug 2026 09:22:02 -0400 Subject: [PATCH 09/10] fix(quality): rebaseline combo-routing-engine.test.ts own-comment growth The ALL_ACCOUNTS_INACTIVE->ALL_TARGETS_SKIPPED fix (58ab721fe) added explanatory comments (+7 lines), pushing the file past its frozen 3457 cap. CI's PR-mode check:file-size caught it; local check-file-size.mjs was not re-run after that specific commit. --- config/quality/file-size-baseline.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index fe16cc8abc..9d496b81f6 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,5 @@ { + "_rebaseline_2026_08_08_9619_own_comment_growth": "PR #9619's own follow-up commit (58ab721fe/15b9cb194): tests/unit/combo-routing-engine.test.ts 3457->3464 (+7) is this PR's own growth — explanatory comment blocks added alongside the ALL_ACCOUNTS_INACTIVE->ALL_TARGETS_SKIPPED stale-assertion fix (matching the identical fix applied to #9173/#9006/#8909 the same day; upstream's own test was never updated when the recordedAttempts===0 pre-dispatch-skip branch shipped). Caught by CI's PR-mode check:file-size (--base-ref) after the fix commit; missed locally because check-file-size.mjs was not re-run after that specific edit.", "_rebaseline_2026_08_07_9619_reconcile_onto_tip": "PR #9619 (fix/basered-changelog-integrity-fabricated-docs) rebase-onto-tip reconciliation. 10 files + 1 test file grew via already-merged release/v3.8.50 PRs since this branch's creation, none touched by this PR's own diff: open-sse/mcp-server/server.ts 1411->1444, open-sse/services/accountFallback.ts 1972->1978, src/app/(dashboard)/dashboard/combos/page.tsx 4647->4703, src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx 1316->1324, src/app/api/providers/[id]/models/route.ts 2250->2304, src/app/api/v1/models/catalog.ts 1549->1556, src/lib/tokenHealthCheck.ts 1021->1053, src/lib/db/core.ts 1637->1639, src/sse/handlers/chat.ts 1877->1878, tests/unit/translator-openai-to-gemini.test.ts 1619->1622. open-sse/mcp-server/schemas/tools.ts 1505->1553 is new growth not previously tracked. Same root cause as every other entry in this chain: fast-gates PR->release does not run check:file-size. No offending branch left to fix.", "_rebaseline_2026_08_02_9259_rolling_rpm": "PR #9259 (issue #8733) own growth: open-sse/services/rateLimitManager.ts baseline 1060->1167 (+107; final source 1153). The existing withRateLimit chokepoint now composes process-local rolling RPM leases with Bottleneck admission, releases pre-dispatch leases on queue timeout/abort/connection disable, preserves caller abort reasons, and wires 429/header state into the extracted rollingRpmGate.ts. The remaining growth is irreducible lifecycle wiring at the dispatch boundary plus the real watchdog test hooks needed to verify queued-wedge recovery; moving it further would obscure lease ownership and Bottleneck cleanup. Covered by the focused rate-limit manager/sliding-window suite (33/33); distributed multi-instance coordination remains explicitly out of scope.", "_rebaseline_2026_07_24_8470_hyperagent_sticky_thread": "PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) own growth: open-sse/executors/hyperagent.ts 936->1025 (wc -l; check-file-size.mjs counts via split(\"\\n\").length so the gate sees 937->1026, +89, crosses the 1000 cap). Fixes a real bug where a reverse-conversion proxy (text-Intent/JSON to Claude Code native tool_calls) rewrites assistant messages between agentic tool-loop turns, breaking HyperAgent’s conversation-prefix fingerprint and cold-starting the thread mid tool-loop. Adds Anthropic tool_use/tool_result flattening to extractMessageText() plus a new rootUserFingerprint()/root-key lookup tier in resolveHyperAgentThreadBinding()/storeHyperAgentThreadAfterTurn() so the thread stays sticky across the tool loop. Cohesive additions inside the existing single-file executor; not extractable without splitting the executor mid-request-flow. Covered by tests/unit/executor-hyperagent.test.ts (19/19, +5 new cases for tool_result/tool_use flattening + root-key stickiness). Pre-merge review flagged a cross-conversation root-key collision risk (tracked in the PR’s own mandatory pre-merge checklist, not yet addressed) — unrelated to this file-size ratchet, tracked separately by /fix-prs.", @@ -362,7 +363,7 @@ "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/combo-routing-engine.test.ts": 3464, "tests/unit/db-migration-runner.test.ts": 1499, "tests/unit/deepseek-web.test.ts": 1092, "tests/unit/executor-codex.test.ts": 1339, From 2c8093f73f42e3a1f8fb25d79b16a633bd192025 Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Sat, 8 Aug 2026 09:59:34 -0400 Subject: [PATCH 10/10] chore(tests): drop explanatory comments on ALL_TARGETS_SKIPPED assertions Kept the assertion value fix (ALL_ACCOUNTS_INACTIVE -> ALL_TARGETS_SKIPPED); the comments were unnecessary. Reverts the file-size baseline bump these comments caused (combo-routing-engine.test.ts back to its original 3457). --- config/quality/file-size-baseline.json | 3 +-- tests/unit/combo-routing-engine.test.ts | 7 ------- tests/unit/t23-t24-fallback-resilience.test.ts | 4 ---- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index cc1a124172..1a11742dbb 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,5 +1,4 @@ { - "_rebaseline_2026_08_08_9619_own_comment_growth": "PR #9619's own follow-up commit (58ab721fe/15b9cb194): tests/unit/combo-routing-engine.test.ts 3457->3464 (+7) is this PR's own growth — explanatory comment blocks added alongside the ALL_ACCOUNTS_INACTIVE->ALL_TARGETS_SKIPPED stale-assertion fix (matching the identical fix applied to #9173/#9006/#8909 the same day; upstream's own test was never updated when the recordedAttempts===0 pre-dispatch-skip branch shipped). Caught by CI's PR-mode check:file-size (--base-ref) after the fix commit; missed locally because check-file-size.mjs was not re-run after that specific edit.", "_rebaseline_2026_08_07_9619_reconcile_onto_tip": "PR #9619 (fix/basered-changelog-integrity-fabricated-docs) rebase-onto-tip reconciliation. 10 files + 1 test file grew via already-merged release/v3.8.50 PRs since this branch's creation, none touched by this PR's own diff: open-sse/mcp-server/server.ts 1411->1444, open-sse/services/accountFallback.ts 1972->1978, src/app/(dashboard)/dashboard/combos/page.tsx 4647->4703, src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx 1316->1324, src/app/api/providers/[id]/models/route.ts 2250->2304, src/app/api/v1/models/catalog.ts 1549->1556, src/lib/tokenHealthCheck.ts 1021->1053, src/lib/db/core.ts 1637->1639, src/sse/handlers/chat.ts 1877->1878, tests/unit/translator-openai-to-gemini.test.ts 1619->1622. open-sse/mcp-server/schemas/tools.ts 1505->1553 is new growth not previously tracked. Same root cause as every other entry in this chain: fast-gates PR->release does not run check:file-size. No offending branch left to fix.", "_rebaseline_2026_08_08_v3850_base_drift_batch_9757": "Base drift on release/v3.8.50, not own growth: the 08-06..08-08 merge batches grew 12 already-frozen (or newly-landed) files without carrying their rebaselines — the dedicated rebaseline PR #9616 was closed as 'superseded' but its file-size entries never actually reached the base, and later merges (#8894 combos page, #9539 EditConnectionModal, #8895 models route, #9294/#9293 catalog, #9541 db/core, #8970 tokenHealthCheck, #8925 mcp schemas+server, #8890 accountFallback, #9467 chat.ts, #8931 openai-to-kiro, ProxyRegistryManager) kept growing them. All 12 values re-measured on THIS branch's tree (= pure tip + this PR's 1-line chat.ts fix, which adds zero lines). This PR's own source changes (chat.ts identifier restore, stream.ts format carve-out) do not grow any frozen file past these values.", "_rebaseline_2026_08_08_migration_135_collision": "fix(db): resolve migration version 135 numbering collision — #9449's 135_connection_runtime_state.sql and #8908's 135_migrate_model_capability_max_token.sql both claimed version 135 (#9449 branched before #8908 merged and never got renumbered before landing on release/v3.8.50), which threw 'Migration version collision detected' the moment ANY code touched the database — a fresh install/deploy from this tip cannot even boot. Renumbered the later-landing file to 140 (next free slot) and added the matching isSchemaAlreadyApplied('140') retroactive guard, matching the established pattern already used for the prior 135/136 -> 137/138 renumber in the same file. Own growth: src/lib/db/migrationRunner.ts 1084->1094 (+10, the new case block) — irreducible, matches the existing per-case guard pattern exactly. Covered by tests/unit/migration-135-numbering-collision.test.ts (2/2), confirmed failing (reproducing the exact live crash) against the pre-fix colliding filenames, passing after.", @@ -311,7 +310,7 @@ "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": 3464, + "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, diff --git a/tests/unit/combo-routing-engine.test.ts b/tests/unit/combo-routing-engine.test.ts index a13cbb1d7b..e3f71e8052 100644 --- a/tests/unit/combo-routing-engine.test.ts +++ b/tests/unit/combo-routing-engine.test.ts @@ -2318,10 +2318,6 @@ test("handleComboChat returns a 503 when every model is unavailable before execu const payload = (await result.json()) as any; assert.equal(result.status, 503); - // isModelAvailable always false means every target is skipped by the - // pre-dispatch filter with zero dispatch attempts — the more precise - // ALL_TARGETS_SKIPPED classification, not ALL_ACCOUNTS_INACTIVE (which - // implies targets were attempted and their accounts found inactive). assert.equal(payload.error.code, "ALL_TARGETS_SKIPPED"); }); @@ -2851,9 +2847,6 @@ test("handleComboChat round-robin resolves nested combos and returns inactive wh const payload = (await result.json()) as any; assert.equal(result.status, 503); - // isModelAvailable always false means every nested target is skipped by the - // pre-dispatch filter with zero dispatch attempts — ALL_TARGETS_SKIPPED, - // not ALL_ACCOUNTS_INACTIVE (see the analogous priority-strategy test above). assert.equal(payload.error.code, "ALL_TARGETS_SKIPPED"); }); diff --git a/tests/unit/t23-t24-fallback-resilience.test.ts b/tests/unit/t23-t24-fallback-resilience.test.ts index 22f9c5d7da..f0f51d30a2 100644 --- a/tests/unit/t23-t24-fallback-resilience.test.ts +++ b/tests/unit/t23-t24-fallback-resilience.test.ts @@ -148,10 +148,6 @@ test("T24: all inactive accounts return 503 service_unavailable (not 406)", asyn assert.equal(result.status, 503); const body = (await result.json()) as any; - // isModelAvailable always false means every target is skipped by the - // pre-dispatch filter with zero dispatch attempts — the more precise - // ALL_TARGETS_SKIPPED classification, not ALL_ACCOUNTS_INACTIVE (which - // implies targets were attempted and their accounts found inactive). assert.equal(body.error?.code, "ALL_TARGETS_SKIPPED"); });