mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 05:02:15 +03:00
6ff2e7b2c2fdc19ed2e9637ea4325af689728ea3
103 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
20fcb8d205 |
fix(affinity): evict the sticky session pin on a combo per-model timeout (#10016)
A combo target that stalls past comboTargetTimeoutMs is aborted by buildTargetTimeoutRunner, which swallows the resulting rejection behind its synthetic 524. Nothing marks the account unavailable — correctly, since a stall is not a quota/auth failure — so the #6219 eviction on the generic markAccountUnavailable -> shouldFallback path in chat.ts never ran. The session pin therefore survived its full TTL and every following request in that session was handed straight back to the account that had just stalled. Seen in production on combo "coding" [priority]: one codex account pinned for a 30-minute TTL, four consecutive requests, four 120s timeouts, "all targets exhausted" each time, while four sibling codex accounts stayed healthy and unused. Classify the abort reason (new dependency-free leaf comboAbortReasons.ts) and evict the connection-matched pin. Only a genuine per-model timeout evicts: a client disconnect or a hedge cancellation says nothing about account health, so those keep the pin and its prompt-cache locality. Eviction is best-effort and never breaks the dispatch path. The dispatch itself moves into a new seam, chatDispatch.ts, which merges the per-model abort signal into the outgoing request, runs executeChatWithBreaker, and owns the eviction on both the rejection and failed-result paths. Keeping that logic out of the frozen god-file leaves chat.ts one line SHORTER than before (1844 -> 1843). Co-authored-by: alexey.nazarov@softmg.ru <alexey.nazarov@softmg.ru> Co-authored-by: fenix007 <fenix007@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> |
||
|
|
e168b2347e |
fix(combo): restrict auto combo pools to user-visible models (#10456)
Auto combos (virtual auto/* pools via virtualFactory and pure-auto named combos via expandAutoComboCandidatePool) expanded their candidate pool from the provider's STATIC registry catalog, which can include models the operator never synced or approved (e.g. openrouter/auto). The visibility filter (getHiddenModelsByProvider) only caught models explicitly flagged isHidden, so catalog-only models passed through and got routed upstream. Build the credentialed pool from the models the user actually has available (synced + custom non-hidden), falling back to the static catalog only when the operator has no synced/custom models for that provider. Applies to every provider uniformly (openai, kilocode, openrouter, ...), with per-connection scoping for synced models. Provider wildcards (providerWildcard.ts) already used the active synced catalog as the authoritative source. Regression coverage: tests/unit/combo-auto-pool-visible-only.test.ts Co-authored-by: herjarsa <herjarsa@users.noreply.github.com> |
||
|
|
e05ac345da |
feat(sse): honor provider-rule lock scope for agentrouter (connection vs model) (#10419)
Makes the ProviderErrorRule `scope` field real at the persistence layer, exclusively for agentrouter (owner decision; every other provider keeps byte-identical behavior). checkFallbackError now surfaces `ruleScope` behind the HONORS_RULE_LOCK_SCOPE_PROVIDERS allowlist, and the agentrouter 403 path consults the rules before the generic apikey-FORBIDDEN early-return. markAccountUnavailable honors scope "connection" with a temporary connection cooldown instead of a per-model lockout — guarded so a permanent state can never be downgraded to a transient retry loop — and combo now skips the exhausted account within the same request, which also stops force-reusing the just-cooled connection via allowRateLimitedConnection. Documented in RESILIENCE_GUIDE §7 with the honest limits (disableCooling connections keep per-model behavior; the 6h model-access cooldown is clamped by mlSettings.maxCooldownMs, 30min by default; same-request skip needs targets carrying their own connectionId). Closes #10334 |
||
|
|
90458a613c |
fix(sse): stop the executor-contract guard from hot-looping the router (#10373)
The `instanceof Response` guard from #10256 broke two ways: 1. `instanceof` is nominal against `globalThis.Response`, but proxyFetch dispatches through the npm undici package's fetch, whose Response is a different class — so valid upstream responses were rejected as contract violations. Replaced with `isResponseLike()` (instanceof fast path + structural brand/member probe); genuinely malformed shapes still throw. 2. The thrown error had no `.status`, so it fell through to chatCore's BAD_GATEWAY default — an internal defect was treated as a flaky provider, cooling the connection down and retrying forever. It now carries status 500 + `executor_contract_violation`, registered as request-scoped and terminal (no cooldown, no breaker, no retry). batch_api.test.ts went from exit 124 (infinite hang, pinning Unit shard 4/4 in every open PR) to exit 0, 22/22 passing. Closes #10360 |
||
|
|
06f41cda63 |
fix(combo): isolate session stickiness by combo (#10137)
Co-authored-by: Bryan Nathan <bryan@users.noreply.github.com> |
||
|
|
3898305df0 |
fix(rate-limit): separate queue wait from execution timeout (#9164)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
a99c795a67 |
Add native ChatGPT Web provider for Codex clients (#8949)
* Bypass proxy compaction for native Codex context
* Add native ChatGPT Web provider pipeline
* Add managed browser and tunnel deployment
* Add ChatGPT Web setup and doctor UI
* Document and test ChatGPT Web integration
* fix(security): register chatgpt-web-codex-doctor in LOCAL_ONLY_API_PATTERNS
The diagnostic route under /api/providers/{id}/chatgpt-web-codex-doctor
was not registered in the spawn-capable route guard. Adding it for
parity with the existing /login pattern.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(providers): route chatgpt-web-codex admin routes through a service boundary
The provider CRUD/doctor routes imported chatgpt-web-codex helpers
(finalizeValidatedChatGptWebCodexSecrets, encode/decodeChatGptWebCodexSecrets,
getChatGptWebCodexDoctorStatus) directly from open-sse/executors/**, which
no-restricted-imports (EXECUTOR_IMPORT_RESTRICTION) forbids for src/app/**
files — executor implementations must stay behind an open-sse handler or
service boundary.
Add open-sse/services/chatgptWebCodexAdmin.ts as a thin re-export boundary
(mirroring the existing tokenRefresh.ts re-export pattern) and import from
there instead. No behavior change.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
|
||
|
|
d774ccecac | fix(routing): account for active OAuth sessions (#8940) | ||
|
|
3e2b166869 |
feat(combo): add quota-only priority fallback (#9983)
Add a per-target priority option that advances only after trusted quota exhaustion while preserving retry, nested Combo, quality, and Global Fallback semantics. |
||
|
|
0cbdc95023 |
feat(sse): server-side template expansion for combo system prompts (#5501) (#9414)
* feat(sse): server-side template expansion for combo system prompts (#5501) * fix(quality-gates): register combo-system-prompt-templates-5501 test in stryker tap.testFiles check:mutation-test-coverage --strict flagged tests/unit/combo-system-prompt-templates-5501.test.ts as covering src/shared/utils/circuitBreaker.ts without being listed in stryker.conf.json tap.testFiles, so its mutant kills wouldn't count. Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com> --------- Co-authored-by: Max <maxmad64@gmail.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com> |
||
|
|
8bdd29f835 |
fix(resilience): recover idle-capacity limiter wedges early (#9041)
* fix(resilience): recover idle-capacity limiter wedges early * docs(changelog): note limiter wedge recovery * fix(resilience): harden limiter wedge recovery * fix(resilience): close limiter recovery review gaps * test(resilience): preserve scoped exhaustion guards * docs(changelog): remove self-credit suffix * test: include limiter regressions in mutation coverage * chore(quality): reconcile v3.8.50 file-size baselines * fix(docs): add WAF MDX title frontmatter * fix(docs): complete WAF frontmatter metadata |
||
|
|
32f8340719 |
Fix custom tool output pairing during context compression (#8933)
* Fix custom tool output pairing during compression (#8932) * Bypass proxy compaction for native Codex context * fix(sse): extract Codex tool-call output repair to leaf module for file-size gate repairMissingCodexToolCallOutputs (added by #8932 for custom_tool_call pairing) pushed codex.ts past the frozen file-size baseline. Extract it to open-sse/executors/codex/toolCallRepair.ts, leaving only the wiring call in codex.ts. Rebaseline the test file's genuine +41 line growth from #8932's new custom_tool_call_output coverage. Co-authored-by: JxnLexn <JxnLexn@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: JxnLexn <JxnLexn@users.noreply.github.com> |
||
|
|
21fd0a94f8 |
feat(alibaba): free-tier routing with live quota sync (#8893)
* feat(alibaba): add free-tier routing with console quota and builtin allowlist Classify DashScope free vs paid models via console quota API, a hardcoded operator allowlist fallback, and per-connection drained tracking. Wire wildcard combo expansion, model refresh, combo exhaustion, and audit redaction for Alibaba console credentials. * fix(routing): reset forced connection pin and persist Alibaba free-tier drain Drop session affinity pins when a forced connection is excluded after 429, and record Alibaba free-tier exhaustion on upstream 403 so per-key drained lists stay accurate without blocking sibling keys. * fix(alibaba): prefer live quota sync over static free-tier allowlist Stop unioning the builtin text allowlist when a console quota snapshot exists, treat expired quotaValidityPeriod as not_capable, and add a dated JSON pack plus sync-alibaba-allowlist script for operator refresh without code edits. * docs(alibaba): document free-tier console path + allowlist env overrides Adds the 4 ALIBABA_FREE_TIER_*_FE_PATH / ALIBABA_FREE_TIER_ALLOWLIST_PATH env vars (referenced by alibabaFreeTierQuotaFetcher.ts and alibabaFreeTierAllowlist.ts) to .env.example and docs/reference/ENVIRONMENT.md so the env/docs contract check passes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(open-sse): split alibabaFreeTierQuotaFetcher.ts under file-size cap Extract pure parsing/classification/eligibility-filtering logic into alibabaFreeTierQuotaClassify.ts and shared types/primitives into alibabaFreeTierQuotaTypes.ts, leaving the HTTP/console-fetch flow in the original file. Public API is unchanged (re-exported), behavior is identical. Co-authored-by: AndrianBalanescu <AndrianBalanescu@users.noreply.github.com> * fix: resolve typecheck errors in alibaba-free-tier routing --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: AndrianBalanescu <AndrianBalanescu@users.noreply.github.com> Co-authored-by: AndrianBalanescu <andrian@balanescu.dev> |
||
|
|
4fc04a5604 |
fix(combo): classify Cloudflare 1010 fingerprint rejection as non-auth (#9929)
opencode.ai/zen/v1 rejects non-browser clients (urllib) with 403 error_code 1010 while curl on the same key succeeds. The 403 was treated as an auth-level failure and two of them crystallized a misleading ALL_ACCOUNTS_INACTIVE on the free pool. - errorClassifier: new FINGERPRINT_REJECTION type; a 403 carrying error_code 1010 / browser_signature_banned is the CDN refusing the client TLS/UA signature, not the account credentials. - combo/targetExhaustion: fingerprint rejections skip auth-level exhaustion so remaining targets stay eligible. - auth: resolveTerminalConnectionStatus no longer treats the fingerprint rejection as a terminal banned account state. UA passthrough is deliberately untouched: #5997/#5720 make the forward-only behavior load-bearing. Signed-off-by: Minxi Hou <houminxi@gmail.com> |
||
|
|
326a975084 |
fix(combo): restore hard capability filtering
Restore the shared media detector and the hard-reason set lost by the maintainer cherry-pick. Re-document the two live low-memory controls and cover nested case-insensitive image indicators. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
2afaab52a1 |
maint: final follow-up cherry-pick #9619 (#9901)
* fix(quality): clears two release/v3.8.50 base-red gates 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. * fix(i18n): completes Vietnamese parity, fixes empty migration query 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. * fix(types): clears 6 pre-existing release/v3.8.50 typecheck errors 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<QuotaInfo | null>` 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. * 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. * fix(sse): update second stale ALL_ACCOUNTS_INACTIVE assertion (T24) Same pre-existing upstream test-drift as |
||
|
|
3a87712b56 | chore: merges release/v3.8.50 into basered-changelog-docs | ||
|
|
5f75abe4a2 |
cherry-pick(pr-9634): fix(test): reconcile base-drifted test expectations on release/v3.8.50 (#9874)
* fix(combo): restore routing module load * fix(db): resolve ccr migration version collision Renumber the CCR block-store migration from 134 to 139, reconcile databases that already applied the legacy slot, and add regression coverage for both upgrade paths. Co-Authored-By: GPT-5 <noreply@openai.com> * fix(changelog): format the aggregator balance fragment as a bullet The fragment landed with YAML frontmatter rather than the bullet the aggregator reads, so check:changelog-integrity exits 1 on every branch and takes the merge-integrity job down with it regardless of what the branch changed. Only the format changes. The entry text is the author's, unedited, and now carries the link to the pull request that shipped it. * fix(test): update expected auth/vision/provider schema for base-drifted expectations * fix(test): narrow this branch to the drifted test expectations Three other PRs already cover what this one was carrying. #9618 renumbers the colliding ccr_blocks migration, #9632 repairs the malformed aggregator changelog fragment, and #9676 restores the combo module load by implementing the selection helper the import was reaching for, rather than deleting the caller the way this branch did. Keeping any of it here would put two files back on the same migration slot and overwrite a better fix with a worse one. What survives is the part none of them touch. Once the combo barrel loads again, three assertions in the context-window filter suite start failing: they demand that catalog-too-small targets be dropped, while the file's own header and its four neighbouring tests say those targets stay available as runtime fallback. The unresolved import was masking them. A new case pins the output-token limit as a genuine hard requirement so the relaxation cannot drift further. The provider count assertion kept one literal at the old value after the rest of the file moved to 198, so the partition check failed on a sum that was correct. * chore(quality): re-time migrationRunner for the 139 guard on the new tip --------- Co-authored-by: alexey.nazarov@softmg.ru <alexey.nazarov@softmg.ru> Co-authored-by: GPT-5 <noreply@openai.com> Co-authored-by: Minxi Hou <houminxi@gmail.com> |
||
|
|
69647b3b94 |
fix(combo): ignore benign empty error fields in streaming quality validation
isStreamingUpstreamError used a key-presence check (parsed.error != null)
which false-positives on benign values some backends emit on every chunk
({}, '', false, 0). When opencode issues a tool-call turn, the upstream SSE
opens with role-only frames (no recognized content) and a later chunk that
carries real tool_calls content PLUS a benign empty error field. The error
gate runs BEFORE content recognizers, so that single frame short-circuits
to 'error' -> 502 'streaming upstream error'. Same combo via kilocode works
because its wire format never emits the empty error field.
Fix: isSubstantiveError() helper — only treat error as real when it carries
non-empty string, non-empty object, or explicit true. Empty object {}, empty
string '', false, and 0 are benign.
TDD: tests/unit/quality-validation-benign-error.test.ts proves tool_calls
chunk with error:{} or error:'' is valid (was 502), while a real error
{message, code} still correctly fails.
|
||
|
|
439854ce65 |
Merge remote-tracking branch 'upstream/release/v3.8.50' into fix/basered-changelog-integrity-fabricated-docs
# Conflicts: # config/quality/file-size-baseline.json # src/i18n/messages/vi.json # tests/unit/combo-routing-engine.test.ts # tests/unit/t23-t24-fallback-resilience.test.ts |
||
|
|
71c85f31cd |
feat(guardrails): modality bridge core — vision mode/task-aware/cache/input_image + modalityBridge settings (#9759)
* feat(sse): unified media-part detection helper (image+audio, input_image) * refactor(guardrails): extractImageParts/comboStructure delegate to unified media detector * fix(sse): media detector — audio parts no longer shadow sibling/nested image indicators * fix(guardrails): close extract↔replace contract for input_image (allowlist + splice) * perf(guardrails): skip media traversal when bridge disabled; short-circuit combo image check * feat(guardrails): in-memory LRU bridge cache (sha256 keyed) * feat(settings): modalityBridge* schema with legacy visionBridge* fallback * feat(db): migrate visionBridge* settings to modalityBridge* (idempotent) * refactor(guardrails): harden bridge cache key/config + settings resolution (review minors) * feat(guardrails): vision bridge mode selector (auto/describe/reroute) short-circuit * feat(guardrails): task-aware vision description prompt (default on) * feat(guardrails): describe-path cache integration * docs(guardrails): review polish — cache-key coupling notes + helper header * feat(guardrails): in-memory bridge stats + modality-bridge response header * feat(api): modality bridge stats endpoint + header wiring in chat handler * docs(guardrails): document modality bridge mode/task-aware/cache/header + stats endpoint * chore: untrack _tasks symlink (inherited from base tip; blocks pre-commit tracked-artifacts gate) * fix(db): renumber modality bridge migration 139->140 (base renumbered ccr_blocks to 139) * docs(guardrails): migration filename touch-up 139->140 * docs(db): stale comment touch-ups after 139->140 renumber and #9688 landing * fix(db): renumber modality bridge migration 140->141 (base renumbered connection_runtime_state to 140) * test(db): migration test titles 139->141 --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
062d48b1ad |
fix(combo): keep operator-defined model order for deterministic strategies (#9420)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679). |
||
|
|
696435efa8 |
fix(routing): normalize time remaining in reset-window strategy (#9330) (#9353)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679). |
||
|
|
da771f8d86 |
Merge remote-tracking branch 'upstream/release/v3.8.50' into fix/basered-changelog-integrity-fabricated-docs
# Conflicts: # changelog.d/features/9415-newapi-sub2api-aggregator-balance.md # docs/ops/VM_DEPLOYMENT_GUIDE.md # open-sse/services/accountSemaphore.ts # open-sse/services/antigravityProjectPersistence.ts # open-sse/services/combo/comboStructure.ts # open-sse/services/combo/fusionPanel.ts # open-sse/services/compression/engines/ccr/index.ts # open-sse/services/firecrawlQuotaFetcher.ts |
||
|
|
c40d4b17ee |
fix(ci): clear the NEW base-reds from the 08-06 merge batch (migration collision #2 + broken import) (#9688)
* test(base): realign six suites with contracts that #9100/#8990/#9009 deliberately changed Continuing the base-red drain — every one of these reproduces on the pure tip. - tests/snapshots/provider/translate-path.json: regenerated via UPDATE_GOLDEN=1. The diff is ADDITION-ONLY — the unorouter block from #9009; no existing provider entry changed. 3/3. - tests/unit/provider-models-route.test.ts: |
||
|
|
fad3539a69 |
fix(sse): replace timer-based waits with polling to fix flaky chatCore/SSE tests under CI load (#9567)
Closes #9567 |
||
|
|
3ea174d531 |
fix(types): clears 6 pre-existing release/v3.8.50 typecheck errors
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<QuotaInfo | null>` 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. |
||
|
|
7f36b192f0 |
fix(compression): bound session-dedup suffix-block scan to prevent OOM (#8438)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip) |
||
|
|
7bb4bfc4fb |
fix(combo): fail-fast concurrency gate and execute-mode overflow (#8890)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip) |
||
|
|
d69f521491 |
fix: reconcile active live model catalogs (#9294)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip) |
||
|
|
a598fbb090 |
fix(combo): least-used quota strategy and wildcard UI preservation (#8894)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip) |
||
|
|
714a315a1a |
Treat context metadata as a routing hint (#8944)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip) |
||
|
|
f2e36ad0ce |
fix(ci): clear base-reds on release/v3.8.50 (migration collision + 4 masked gates) (#9600)
Validated in local merge-train (diegosouzapw batch) |
||
|
|
8a7b2467fd |
[v3.8.50] feat(combo): add maxContextWindow to contextRequirements (fixes #8777) (#8790)
Validated in local merge-train T6 (ungrouped batch 1) |
||
|
|
749fc75beb |
fix(combo): exclude hidden leaves from catalog and dispatch (#8878)
Validated in local merge-train T5 (base49+contributors+pacocartones) |
||
|
|
9099df4484 |
feat(models): unify token limit overrides (#8908)
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates — only pre-existing audit.test.ts flake). |
||
|
|
fed64abc2e |
fix(model): normalize client context-window suffixes (#9193)
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates — only pre-existing audit.test.ts flake). |
||
|
|
ce764bc6f3 |
fix(combo): classify local target timeouts as gateway timeouts
Return a typed HTTP 504 for OmniRoute's per-target timer, keep fallback active, and classify the local timeout as request-scoped so it cannot degrade provider connection health. |
||
|
|
7b8055c7f8 |
fix(resilience): count STREAM_EARLY_EOF as a provider failure in combo routing (#9251)
* fix(resilience): count STREAM_EARLY_EOF as a provider failure in combo routing A STREAM_EARLY_EOF is an upstream that accepted the request (HTTP 200), opened the SSE stream, then closed it without emitting a single non-ping event. The combo path classified it together with STREAM_READINESS_TIMEOUT through isStreamReadinessFailureErrorBody(), and the readiness exemption in shouldRecordProviderBreakerFailure meant the whole-provider circuit breaker never saw it. During a provider-wide outage that makes the breaker blind. Over a 7-day window on our router we recorded 311 of these events, 302 of them on one model, 265 inside the upstream's published incident window — and the provider breaker sat at CLOSED / failure_count=0 the entire time. Every request kept being dispatched to the failing provider instead of shedding to the next combo target. The two codes are different signals. The readiness probe is a pre-flight liveness check on a connection we have not committed to, so failing it means "this connection looks stale". An early EOF means the provider took the request and then failed to serve it. The single-model path already treats it that way: shouldTripProviderBreakerForResult has no readiness exemption, so a 502 early EOF trips the breaker there. This makes the combo path consistent. isStreamReadinessFailureErrorBody keeps matching both codes, because the transient-retry and round-robin semaphore-cooldown paths in combo.ts do want identical treatment for both. Only the breaker needs to tell them apart, so the distinction is added as a narrow predicate and an optional argument rather than by changing the shared classifier. Omitting the new argument reproduces the previous behaviour exactly. Follows the additive-override pattern established by the isProxyUnreachable work, and leaves the existing exclusions for client aborts and plain 429s untouched. * test: register stream-early-eof-breaker in stryker tap.testFiles The mutation test-coverage gate (check:mutation-test-coverage --strict) detects unit tests that cover a mutated module but are missing from stryker.conf.json tap.testFiles, so their mutant kills would not count. comboPredicates.ts is one of the mutated modules, and the new stream-early-eof-breaker.test.ts covers it, so the gate correctly flagged the omission. 8376-econnrefused-breaker.test.ts -- the test this one is modeled on -- is already registered; this just brings the new file in line. No production code change. --------- Co-authored-by: Nick Sullivan <nick@technick.ai> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
0ef50886ef |
feat(g1): rewrite combo-strategy check to runtime-import approach (#9131)
G1 (v3.8.51): section (2) of check-known-symbols no longer regex-scans strategy === "..." literals from combo source. The handled set now comes from a runtime-imported dispatch registry (open-sse/services/combo/ strategyDispatch.ts) that imports the real ordering functions and enumerates which strategies they implement. This keeps the canonical-not-handled gate correct under the upcoming R0.3 registry dispatch, which removes the strategy === branches the regex relied on. - Adds HANDLED_COMBO_STRATEGIES registry (all 20 canonical strategies) + binds the real dispatch leaves (applyStrategyOrdering, resolveAutoStrategyOrder, tryFusionDispatch, tryPipelineDispatch, resolveComboTargetPipeline). - main() imports the registry instead of reading/sourcing combo files. - extractHandledStrategies + diffComboStrategies stay exported (pure, tested). - New TDD test proves the runtime enumeration covers canonical exactly. Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
1c2143182c |
fix(combo): fail open when strict context filter empties unknown-only pools (#8786) (#8798)
Strict contextFilterMode excluded every target whose context limit was missing from the capability catalog, so otherwise-executable combos returned 404 no_executable_targets. Restore unknown-context targets when no known-good survivor remains, surface context_requirements_exhausted from targetResolution, and keep the empty-pool payload in pinRecovery after the #8592 split. |
||
|
|
f8487648c8 | fix(resilience): keep resource 404s from cooling models (#8756) | ||
|
|
0eeb8f45c0 |
refactor(sse): extract combo target resolution into combo/targetResolution.ts (#8592)
* refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts
Pure move, no behaviour change. First of ~7 PRs decomposing the combo.ts
god-file (#3501).
handleComboChat evaluates a series of dispatch branches before it ever
reaches target resolution or the sequential attempt loop. None of them
iterate targets in priority order or need the failover/retry/credential
gate machinery that follows, so they move to a leaf:
- context-cache pin routing (Fix #679), including the
pinIsDurablyUnhealthy / isPinnedModelDurablyUnhealthy health gate
- fusion panel dispatch + the #6455 misconfiguration warn
- pipeline chaining
- nested combo-ref execute-mode runtime-unit dispatch
Only the chaos and round-robin hand-offs stay inline (11 and 13 lines);
extracting those would be pure indirection.
open-sse/services/combo.ts 3642 -> 3341 (-301)
open-sse/services/combo/dispatchPrelude.ts: 619 (under the 800 cap)
Each helper keeps the fall-through protocol the inline blocks had: return
a Response to OWN the request, return null to fall through. A flipped
null/Response would silently bypass the whole combo strategy, so the new
tests pin both directions for every branch.
combo.ts re-exports pinIsDurablyUnhealthy so combo-pin-health-gate.test.ts
keeps resolving. The leaf takes handleComboChat as a `runCombo` parameter
instead of importing it, so combo/ keeps zero back-edges into combo.ts.
Complexity-neutral: the first cut added +3 violations (two
max-lines-per-function, one complexity) inside the new leaf, so
evaluatePinnedResponse, orderRuntimeUnits, recordRuntimeUnitStickySuccess
and buildBaseOptions were split out. check:complexity now measures 2169
and check:cognitive-complexity 956 — identical to the pristine base.
* test(sse): close the dispatch-prelude coverage holes found by mutation testing
An adversarial mutation audit of the suite added in the previous commit
found it guarded the fall-through protocol well but asserted almost
nothing about what the helpers do once they OWN the request. 5 of 12
seeded mutations survived. Worst case: deleting the pinned-model
dispatch call outright left all 12 tests green.
Three holes, now closed (8 tests -> 20):
Hole A — the honored-pin path had zero coverage. Both existing pin tests
DROP the pin, so the dispatch, the 200-but-empty quality gate, the
[408, 429, 500, 502, 503, 504] failover list and the catch(pinErr)
branch were unguarded — exactly the logic the 2026-06-21 / 2026-06-22
incident comments call load-bearing. Adds five tests over a seeded
healthy provider connection so the pin is actually honored.
Hole B — orderRuntimeUnits was only ever driven with `priority`, which
is a no-op through it. Four of five strategy branches could be deleted
with nothing failing. Adds round-robin rotation and weighted sticky
ordering tests.
Hole C — recordRuntimeUnitStickySuccess never did anything under test:
both its guards need weighted/round-robin, so an early return changed
nothing. Covered by the new sticky-batch test.
Verified by re-running the mutations rather than assuming: all 7 that
previously survived (delete-pin-dispatch, serve-despite-failed-quality,
never-fail-over-on-transient, rr-counter-not-advanced, rotation-removed,
weighted-sticky-skipped, sticky-recording-no-op) are now killed.
The first sticky-batch test I wrote was itself vacuous — asserting "same
unit twice" holds equally when the recording helper is stubbed out, since
nothing advances the counter either. It now asserts the batch runs out
and rotation resumes on the third dispatch, which is what actually
distinguishes the two.
Also restores API_KEY_SECRET in test.after; it was set at module load and
never put back, inconsistent with the DATA_DIR handling beside it.
* fix(ci): teach known-symbols gate the relocated fusion/pipeline dispatch
The combo sub-check of check:known-symbols asserts every canonical routing
strategy has a real dispatch branch. It scanned a hardcoded file list and
matched only `strategy === "..."`, so the prelude extraction tripped it twice:
[combo] 2 estratégia(s) canônica(s) sem branch de despacho em combo.ts:
✗ fusion
✗ pipeline
Both branches are still wired — they just moved to combo/dispatchPrelude.ts and
took the early-return guard form `if (strategy !== "fusion") return null;` that
extracting a branch into a `tryXDispatch()` leaf naturally produces.
Two changes, both extending existing precedent (the list already carries the
Block J leaves for the same reason):
- register combo/dispatchPrelude.ts in comboDispatchFiles
- widen the extractor to `strategy [!=]== "..."` so the inverted guard counts
Loose `==`/`!=` stay rejected, and no `handledNotCanonical` fallout: the gate
now reports 20 canonical strategies, all 20 via despacho.
* chore(ci): register combo-dispatch-prelude test in stryker tap.testFiles
check:mutation-test-coverage --strict failed once the known-symbols fix let
Fast Quality Gates advance to it:
✗ 2 covering unit test(s) across 2 module(s) are missing from
stryker.conf.json tap.testFiles
open-sse/services/combo/comboStructure.ts
open-sse/services/combo/rrState.ts
The new tests/unit/combo-dispatch-prelude.test.ts exercises both modules, and
both are already in stryker's mutate list, so without the registration its
mutant kills would not have counted toward the nightly mutation gate.
Note (unchanged, still out of scope): combo/dispatchPrelude.ts itself is not in
stryker's `mutate` list. Adding it would widen the nightly mutation surface,
which is a separate call from fixing this drift.
* docs(changelog): add fragment for #8582 combo dispatch prelude
* refactor(sse): extract combo target resolution into combo/targetResolution.ts
Pure move, no behaviour change. Lifts the target-resolution stage of
handleComboChat — everything between the dispatch prelude and the attempt
loop — into a new leaf, open-sse/services/combo/targetResolution.ts.
Moved verbatim: provider-wildcard expansion, weighted step-group resolution
+ sticky-weighted eligibility, request-tag routing, the known-context-overflow
early return, the smart/pipeline-enabled auto dispatch, auto-strategy
ordering, per-strategy ordering, cache-strategy affinity, session stickiness,
eval scores, request-compatibility + context-requirement filters, task-aware
reordering, prompt-cache affinity, and the priority-strategy pre-screen.
The three early exits become an { earlyResponse } result so the host decides
to return them (same pattern as resolveAutoStrategyOrder). The values the
attempt loop still reads — orderedTargets, stickyWeightedLimit,
getWeightedStepKeyForTarget, the session-stickiness result and preScreenMap —
are returned instead of closed over. Loop config (maxRetries, retryDelayMs,
fallbackDelayMs, maxSetRetries, setRetryDelayMs) stays in combo.ts.
buildAutoCandidates is dependency-injected because it lives in combo.ts, so
the leaf keeps zero back-edges into its host.
combo.ts 3640 -> 3321 lines; new leaf 484 lines (under the 800 cap).
Part of the #3501 god-file decomposition campaign.
* refactor(sse): split targetResolution into stage helpers, ratchet combo.ts file-size baseline
Follow-up to the target-resolution extraction: the moved region landed as one
311-line function, which converted inline code inside the (already-violating)
handleComboChat into a NEW separately-counted violating function — check:complexity
2169 -> 2171 and check:cognitive-complexity 956 -> 957.
Split resolveComboTargetPipeline along its natural stage boundaries into 14
helpers (wildcard expansion, weighted eviction/eligibility/sticky-key/selection,
step-key mapper, context-overflow response, pool-size log, smart-pipeline dispatch
and its fall-through logger, strategy ordering, continuity filters, task-aware
ordering, prompt-cache enablement/first-target protection/affinity stage). Each
stage takes the previous stage's output and returns the next; still a pure move.
The leaf now contributes ZERO complexity, max-lines-per-function and
cognitive-complexity violations. Both ratchets are back at base
|
||
|
|
39e777248c |
fix(auto-combo): short-circuit expandAutoComboCandidatePool when models[] is non-empty (#8598)
When an auto-combo has models[] populated by the operator but config.auto.candidatePool is empty (the default for combos created via the dashboard), expandAutoComboCandidatePool silently expands the candidate pool to every model of every active provider connection. This overrides the explicit list in models[] and lets unintended models (e.g. gemini-3.1-flash-lite) win the auto-strategy scoring contest. In omniroute@3.8.48 (npm) only one guard exists before the expansion loop (GUARD A: if (config.auto.candidatePool populated) return eligibleTargets). The upstream release branch release/v3.8.49 added a second guard (combo-ref check, PR #7301) but it does not cover the common pattern where models[] holds explicit kind:"model" entries. Both gaps share the same root mechanism and the same fix. The new guard short-circuits whenever models[] is a non-empty array, covering both kind:"model" entries (the dashboard default) and kind:"combo-ref" entries (which #7301 already handles). With this guard in place, the existing combo-ref check becomes redundant; it is left in place for the minimal-scope surgical fix, and can be removed in a follow-up cleanup. Validation (in isolated Docker, 3 providers + 4 controlled combos): - 3 explicit models, empty candidatePool: pool 60 -> 6 - 1 combo-ref + 2 explicit, empty candidatePool: pool 64 -> 10 - 3 explicit models, populated candidatePool (GUARD A path): 6 -> 6 - empty models[] virtual auto: 57 -> 57 (expansion preserved) Closes #8597 Co-authored-by: Michael de Souza Marcos <michael.smarcos@hotmail.com> |
||
|
|
1d44ee00f8 |
refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts (#8582)
* refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts Pure move, no behaviour change. First of ~7 PRs decomposing the combo.ts god-file (#3501). handleComboChat evaluates a series of dispatch branches before it ever reaches target resolution or the sequential attempt loop. None of them iterate targets in priority order or need the failover/retry/credential gate machinery that follows, so they move to a leaf: - context-cache pin routing (Fix #679), including the pinIsDurablyUnhealthy / isPinnedModelDurablyUnhealthy health gate - fusion panel dispatch + the #6455 misconfiguration warn - pipeline chaining - nested combo-ref execute-mode runtime-unit dispatch Only the chaos and round-robin hand-offs stay inline (11 and 13 lines); extracting those would be pure indirection. open-sse/services/combo.ts 3642 -> 3341 (-301) open-sse/services/combo/dispatchPrelude.ts: 619 (under the 800 cap) Each helper keeps the fall-through protocol the inline blocks had: return a Response to OWN the request, return null to fall through. A flipped null/Response would silently bypass the whole combo strategy, so the new tests pin both directions for every branch. combo.ts re-exports pinIsDurablyUnhealthy so combo-pin-health-gate.test.ts keeps resolving. The leaf takes handleComboChat as a `runCombo` parameter instead of importing it, so combo/ keeps zero back-edges into combo.ts. Complexity-neutral: the first cut added +3 violations (two max-lines-per-function, one complexity) inside the new leaf, so evaluatePinnedResponse, orderRuntimeUnits, recordRuntimeUnitStickySuccess and buildBaseOptions were split out. check:complexity now measures 2169 and check:cognitive-complexity 956 — identical to the pristine base. * test(sse): close the dispatch-prelude coverage holes found by mutation testing An adversarial mutation audit of the suite added in the previous commit found it guarded the fall-through protocol well but asserted almost nothing about what the helpers do once they OWN the request. 5 of 12 seeded mutations survived. Worst case: deleting the pinned-model dispatch call outright left all 12 tests green. Three holes, now closed (8 tests -> 20): Hole A — the honored-pin path had zero coverage. Both existing pin tests DROP the pin, so the dispatch, the 200-but-empty quality gate, the [408, 429, 500, 502, 503, 504] failover list and the catch(pinErr) branch were unguarded — exactly the logic the 2026-06-21 / 2026-06-22 incident comments call load-bearing. Adds five tests over a seeded healthy provider connection so the pin is actually honored. Hole B — orderRuntimeUnits was only ever driven with `priority`, which is a no-op through it. Four of five strategy branches could be deleted with nothing failing. Adds round-robin rotation and weighted sticky ordering tests. Hole C — recordRuntimeUnitStickySuccess never did anything under test: both its guards need weighted/round-robin, so an early return changed nothing. Covered by the new sticky-batch test. Verified by re-running the mutations rather than assuming: all 7 that previously survived (delete-pin-dispatch, serve-despite-failed-quality, never-fail-over-on-transient, rr-counter-not-advanced, rotation-removed, weighted-sticky-skipped, sticky-recording-no-op) are now killed. The first sticky-batch test I wrote was itself vacuous — asserting "same unit twice" holds equally when the recording helper is stubbed out, since nothing advances the counter either. It now asserts the batch runs out and rotation resumes on the third dispatch, which is what actually distinguishes the two. Also restores API_KEY_SECRET in test.after; it was set at module load and never put back, inconsistent with the DATA_DIR handling beside it. * fix(ci): teach known-symbols gate the relocated fusion/pipeline dispatch The combo sub-check of check:known-symbols asserts every canonical routing strategy has a real dispatch branch. It scanned a hardcoded file list and matched only `strategy === "..."`, so the prelude extraction tripped it twice: [combo] 2 estratégia(s) canônica(s) sem branch de despacho em combo.ts: ✗ fusion ✗ pipeline Both branches are still wired — they just moved to combo/dispatchPrelude.ts and took the early-return guard form `if (strategy !== "fusion") return null;` that extracting a branch into a `tryXDispatch()` leaf naturally produces. Two changes, both extending existing precedent (the list already carries the Block J leaves for the same reason): - register combo/dispatchPrelude.ts in comboDispatchFiles - widen the extractor to `strategy [!=]== "..."` so the inverted guard counts Loose `==`/`!=` stay rejected, and no `handledNotCanonical` fallout: the gate now reports 20 canonical strategies, all 20 via despacho. * chore(ci): register combo-dispatch-prelude test in stryker tap.testFiles check:mutation-test-coverage --strict failed once the known-symbols fix let Fast Quality Gates advance to it: ✗ 2 covering unit test(s) across 2 module(s) are missing from stryker.conf.json tap.testFiles open-sse/services/combo/comboStructure.ts open-sse/services/combo/rrState.ts The new tests/unit/combo-dispatch-prelude.test.ts exercises both modules, and both are already in stryker's mutate list, so without the registration its mutant kills would not have counted toward the nightly mutation gate. Note (unchanged, still out of scope): combo/dispatchPrelude.ts itself is not in stryker's `mutate` list. Adding it would widen the nightly mutation surface, which is a separate call from fixing this drift. * docs(changelog): add fragment for #8582 combo dispatch prelude |
||
|
|
f3fa27f384 |
fix(backend): fail closed when capability filters empty the combo pool (#8494)
* fix(backend): fail closed when capability filters empty the combo pool Tools/vision/structured_output filters no longer re-admit the full pool when every target is incompatible. Opt-in via combo config compatFilterFailOpen. Closes #8488 * fix(backend): keep tool-emulation providers under fail-closed filters Carve out providers with toolCalling:"emulated" (#5240) from tools capability_mismatch so chatgpt-web combos still reach the prompt shim. Align round-robin compatFilterFailOpen with settings fallback and drop new any-typed params from the #8488 combo-routing tests. * chore(lint): prune stale combo-routing-engine any suppressions Test cleanup in #8488 dropped four no-explicit-any hits; sync the freeze file. * chore(quality): rebaseline combo.ts + freeze new-above-cap comboStructure.ts check:file-size was red for this PR's own growth: combo.ts grew 3640->3693 (+53, the capability-filter fail-closed guard + compatFilterFailOpen escape hatch at both call sites) and combo/comboStructure.ts crossed the 800-line new-file cap at 918 (describeCapabilityFilterExhaustion + providerSupportsEmulatedToolCalling for the #5240 emulated-tool-calling exemption). Both are irreducible orchestration wiring at the existing combo filter chokepoint (same precedent as #7301's cooldown-retry generalization). Companion test tests/unit/combo-routing-engine.test.ts frozen at its own grown size (3409->3449). No logic change; 95/95 tests pass. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: ikelvingo <im.kelvinwong@gmail.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: Prudhvivuda <Prudhvivuda@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
3be0a5d290 |
fix: combo input-bound, Responses->Chat image strip, qwen-web toolCalling, empty-response exhaustion (#8476)
* test(tail): retire stale i18n __MISSING__ repro + fix qianfan website URL
Base-red slice 6, rebased onto the advanced release/v3.8.49 (
|
||
|
|
1a7079599c | chore(combo): extract pure error predicates and quota status helpers to comboPredicates (#8548) | ||
|
|
30709255c9 |
fix(sse): stop combo's aggregated failure response from mixing fields across targets (#8486) (#8508)
handleComboChat/handleRoundRobinCombo tracked lastStatus (first-write-wins), lastError (last-write-wins), and earliestRetryAfter (global MIN across all targets) independently, so the final unavailableResponse() could surface a status/message pair from two different failing targets and decorate a config-class error (e.g. Antigravity's 422 missing_project_id, which carries no retryAfter of its own) with an unrelated target's long reset window. - lastStatus now overwrites on every failure (last-write-wins), matching lastError, so status and message always come from the same target. - the "(reset after ...)" decoration is only applied when the surfaced status is itself rate-limit-class (429/503) — see the new open-sse/services/combo/unavailableRetryGate.ts leaf module (both combo.ts and chat.ts are already over their file-size baseline, so the gate logic lives in a new module and combo.ts only wires it in). Co-authored-by: ikelvingo <im.kelvinwong@gmail.com> |
||
|
|
09e9ecef97 | fix(resilience): treat an unreachable-proxy ECONNREFUSED as a circuit-breaker event so combo fails over instead of hitting the 503 max-retry limit (#8376) (#8403) |