Commit Graph

907 Commits

Author SHA1 Message Date
Patryk Mikołajczyk
a7b96b44e9 fix(xai): cap chat history at xAI 800-message limit (#10601)
* fix(xai): cap chat history at xAI 800-message limit

xAI returns 413 when messages/input exceed 800 items. Token
compression never fires on a long tool loop that still fits the
context window, so trim at the executor edge after Responses
expansion and drop orphaned tool pairs from the cut.

* chore(changelog): attach PR number to xAI 800-message fragment

* fix(xai): resolve TS2339 generic assignment in capXaiRequestHistory

Drop the T extends Record<string, unknown> generic on
capXaiRequestHistory and type it directly as
Record<string, unknown> -> Record<string, unknown>. Assigning
next.messages / next.input onto a generic T was rejected by
TypeScript even though every call site already passes/consumes a
JsonRecord (= Record<string, unknown>), so no caller relied on the
generic preserving a narrower type.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: mikolaj92 <mikolaj92@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:52:52 -03:00
CyrixJD115
9222528bdd fix(opencode): session stability, free-tier routing, and CLI defaults (#10571)
* fix(opencode): session stability, free-tier routing, and CLI defaults

- Wire generateSessionId() into opencodeHeaders so x-opencode-session
  is a deterministic fingerprint instead of randomUUID() per request,
  enabling upstream prompt caching across a conversation
- Thread request body through buildHeaders() so session fingerprint
  has access to model, system, messages, and tools
- Default CLI header synthesis to ON (opt-out via false), align
  values with 9router proven defaults (opencode/desktop/global)
- Auto-echo listing-valid model names for noAuth providers so
  response.model matches /v1/models listing
- Short-circuit free-tier model resolution to opencode provider first
  to prevent prefix inference misrouting when catalog is unreachable

* fix(opencode): make free-tier default flip self-consistent + add coverage

PR #10571 flipped OPENCODE_SYNTHESIZE_CLI_HEADERS to on-by-default and
changed the synthesized UA/client/project default values, but shipped
with 2 broken assertions in the existing #5997 regression test and no
coverage for the new session-fingerprinting, free-tier routing, or
noAuth echoModel logic (Hard Rule #18).

- Update tests/unit/opencode-cli-headers-synthesis-5997.test.ts to match
  the new on-by-default behavior and new default values; add an explicit
  opt-out coverage test so the forward-only path is still guarded.
- Fix 20 further test failures in tests/unit/opencode-executor.test.ts
  and tests/unit/refactor-buildHeaders-opencode.test.ts caused by the
  same default flip (pin OPENCODE_SYNTHESIZE_CLI_HEADERS=false for the
  characterization suites that predate #10571; use a genuinely
  CLI-looking UA where the preserved-UA test requires one).
- Fix a real bug found via TDD while adding the mandated free-tier
  routing regression test: the big-pickle/*-free short-circuit in
  open-sse/services/model.ts checked activeProviders?.has("opencode")
  literally, but getActiveProviderSet() canonicalizes every connection's
  provider id through resolveProviderAlias(), which rewrites "opencode"
  to "opencode-zen" via a manual override — so an active no-auth
  opencode connection could never satisfy the check. Now checks both
  opencode-family candidate ids. Proven with a test that fails on the
  original code and passes with the fix (both connections active with a
  stale synced catalog omitting big-pickle).
- Extract the noAuth-provider echoModel aliasing in chatCore.ts into a
  pure, directly-testable helper (open-sse/handlers/chatCore/noAuthEchoModel.ts),
  matching the existing chatCore god-file decomposition pattern.
- Add regression tests for generateSessionId()-based x-opencode-session
  fingerprinting (stable within a conversation, changes on model/message
  changes), the free-tier routing short-circuit, and the noAuth echoModel
  aliasing.
- Add the changelog.d/ fragment and sync docs/reference/ENVIRONMENT.md's
  OPENCODE_SYNTHESIZE_CLI_HEADERS/OPENCODE_USER_AGENT/OPENCODE_CLIENT/
  OPENCODE_PROJECT rows to the new defaults.

Does NOT resolve whether flipping OPENCODE_SYNTHESIZE_CLI_HEADERS's
default was the right call, and does NOT touch the separate open PR
#10357 which flips the same flag with a different literal default value
- that decision is left to the maintainer at merge time.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:52:33 -03:00
pageragatz
b9cd5ed138 feat(providers): optional AI Horde API key and live image catalog (#10542)
* feat(providers): optional AI Horde API key and live image catalog

Allow a registered Horde key on the no-auth connection and send it for
chat and image jobs. List only image models that currently have workers,
and generate through Horde's native async API.

# Conflicts:
#	open-sse/config/imageRegistry.ts
#	src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx
#	src/shared/constants/providers.ts
#	src/sse/services/auth.ts

* fix(providers): validate AI Horde keys against find_user

The OpenAI-compatible /v1/models probe returns 200 for any Bearer token
on oai.aihorde.net, so Check always succeeded. Use Horde's /v2/find_user
lookup instead; an empty key still counts as the optional anonymous path.

* chore(changelog): name the AI Horde fragment for #10542

* fix(images): harden AI Horde optional-key selection and outbound fetches

- Optional-key selection now honors connection health (rate-limit cooldown
  and terminal/unavailable test status) before handing a stored key back,
  rotating to the next healthy key or falling back to the anonymous no-auth
  path instead of using an unhealthy stored key.
- Route the Horde submit/check/status/cancel and catalog calls through the
  repository's bounded outbound-fetch helper (timeout, no more bare fetch())
  and route R2 image downloads through the established bounded remote-image
  fetch (SSRF host guard, DNS-rebinding pin, streaming byte cap, redirect
  limit) instead of an unbounded fetch().
- Extend the generation deadline to cover the full request lifecycle
  (catalog freshness check, submit, polling, and image download), and add a
  regression test proving that exceeding the deadline issues a DELETE
  cancel to Horde's API rather than only timing out locally.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: pqr <pqr@soraka.ititti.es>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:51:57 -03:00
Bob.Hou
ebf0bf913a fix(settings,auth): default debugMode to false and skip account rotation on model-unsupported 400 (#10525)
* fix(settings,auth): default debugMode to false and skip account rotation on model-unsupported 400

* fix(auth): disambiguate model-unsupported from auth-credential 400

The model-unsupported guard used MODEL_ACCESS_DENIED_PATTERNS directly,
which also matches auth-credential errors like 'invalid api key for
model X'. Add the AUTH_CREDENTIAL_ERROR_PATTERNS exclusion (same as
checkFallbackError) and use provider_model_unsupported log reason.

Addresses maintainer feedback on PR #10525

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(auth): narrow model-unsupported guard to avoid misclassifying account-scoped entitlement 400s

The #10460 guard reused MODEL_ACCESS_DENIED_PATTERNS directly, which also
matches ambiguous "access"/"permission" phrasing (e.g. "does not have
permission to access this model") that commonly signals an ACCOUNT-scoped
entitlement gap (PRO vs free tier) rather than a genuinely provider-wide
unsupported model — a different account of the same provider may still
have access, so those must keep rotating normally instead of being
short-circuited.

Extract isProviderModelUnsupported400() in accountFallback.ts: reuses the
same AUTH_CREDENTIAL_ERROR_PATTERNS exclusion checkFallbackError's 400
branch already applies, narrowed to a strict subset of unambiguous
"provider does not serve this model at all" phrasings. auth.ts now calls
this shared helper instead of testing the broader patterns in isolation,
and exposes the sanitized reason ("provider_model_unsupported") on the
returned result, not just in the log line.

Also fix DATA_DIR test-isolation ordering in
account-fallback-service.test.ts: it was assigned after the first
dynamic import of accountFallback.ts, which transitively imports
src/lib/db/core.ts (DATA_DIR is captured once at module-load time), so
the intended isolated test directory was silently never used. Move the
assignment before any transitive DB import, and add regression tests for
the 3-account rotation contract: exactly one upstream call for an
unambiguous provider-wide 400 with the combo advancing to the next
target, continued rotation for account-scoped 401/403/429 and for the
permission/entitlement 400 case that motivated this narrowing.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:51:38 -03:00
Diego Rodrigues de Sa e Souza
9500adb013 fix(combo): surface context-overflow before compression so oversized requests fail fast with a clear error (#10225) (#10503)
* fix(combo): surface context-overflow before compression so oversized requests fail fast with a clear error (#10225)

* fix(combo): make context-overflow deferral target-aware for native Codex passthrough (#10225)

The deferral added by the prior commit checked only operator-named
compression exclusions when deciding whether at least one target "can
compress" — it never accounted for native Codex Responses passthrough
targets, which chatCore.ts unconditionally excludes from compression
(compressionExcluded = nativeCodexPassthrough || ...). Deferring on such
a target's account let an oversized request skip both the combo preflight
AND compression, reaching fetch() uncompressed.

Thread the same request-shape facts chatCore.ts uses
(shouldUseNativeCodexPassthrough: provider/sourceFormat/endpointPath/body/
headers) down into getKnownContextOverflow so the deferral decision can
never drift from chatCore's own — a native-codex-passthrough target now
never counts as "compressible", so a pool made only of such targets keeps
the fast local 400 instead of a wasted round trip.

Adds regression coverage: the pure getKnownContextOverflow target-aware
check, an end-to-end handleComboChat proof that a native-codex-only pool
fails fast with zero dispatches, and two real handleChatCore-path tests
proving compression actually reduces the dispatched body when eligible,
and that a still-too-large-after-compression request is rejected locally
without an upstream call.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-18 10:51:20 -03:00
Diego Rodrigues de Sa e Souza
2230fbbe93 fix(resilience): keep combo quality and auth reasons separate and redact connection labels in terminal errors (#10314) (#10501)
* fix(resilience): keep combo quality and auth reasons separate and redact connection labels in terminal errors (#10314)

* fix(resilience): sanitize identifiers in error text, add explicit terminal-status policy, fix classifier ordering (#10314)

Four gaps in the prior combo-error-aggregation fix:

- formatComboOutcomes() only redacted connection identifiers in the model
  label, never in the raw upstream error TEXT — a proxy echoing a
  connection/account id back in its error body leaked it into the
  client-facing terminal message. Redact both.

- The terminal HTTP status was still `lastStatus` — whichever target
  happened to fail last, independent of the other targets' reasons. Add
  resolveComboTerminalStatus(): preserve a 4xx only when every eligible
  target's failure is genuinely "the request is invalid" (model-class);
  a heterogeneous mix (e.g. a quality failure + a sibling's 401) now
  normalizes to a 5xx-class status reflecting an infra/provider problem,
  never a misleading client error borrowed from an unrelated target.

- classifyComboOutcome()'s ordering had `status === 408 || status >= 499`
  checked before `status >= 500`, making the provider branch permanently
  unreachable — every real 5xx (500/502/503/504) was silently mislabeled
  as "timeout". Fixed to an exact match (408/499) and gave 429 its own
  explicit `rate_limit` kind instead of falling into the generic "model"
  (request-invalid) bucket by accident.

- Added an integration-level regression driving the real handleComboChat
  wiring end-to-end (quality failure + sibling 401, and a success-after-
  quality-failure case), not just the pure aggregation helpers.

Updated three pre-existing tests whose assertions encoded the OLD
last-writer-wins contract this fix intentionally supersedes (#8486 Part B
antigravity retryAfter tests, two combo-routing-engine status/message
tests) to the new, more precise contract; verified the underlying #8486
concern (wrong target's retryAfter header) is still honored under the new
status policy.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-18 10:51:16 -03:00
abhiisalright
15386495c2 fix(compression): add i18n support for less-code and terse-prose (#10498)
* fix(compression): add i18n support for less-code and terse-prose

Translates less-code output style to pt-BR, vi, ja, and id. Adds missing vi translation to terse-prose caveman mode. Removes less-code from English-only allowlist and updates matrix tests.

Fixes #10426

* docs(compression): add output styles coverage table

Adds the requested Output Styles matrix to the compression guide covering styles, supported languages, and intensity levels.

Fixes #10426
2026-08-18 10:51:08 -03:00
Diego Rodrigues de Sa e Souza
50da54484e fix: downgrade adaptive thinking and gate context-1m beta on model eligibility (#10119) (#10481)
* fix(sse): downgrade adaptive thinking and gate context-1m beta on model eligibility (#10119)

* fix(sse): thread resolved model into DefaultExecutor's anthropic-beta merge (#10119)

DefaultExecutor.buildHeaders() merged the client-negotiated anthropic-beta
header without ever passing the resolved target model into
mergeClientAnthropicBeta(), so the context-1m-2025-08-07 eligibility gate
added earlier in this PR could not see which model a combo/fallback had
actually routed to at this call site. buildHeaders() now accepts an
optional model parameter (mirroring BaseExecutor.buildHeaders' existing
signature and the pattern already used by grok-cli.ts/qoder.ts) and
forwards it through, so an ineligible model target (e.g. Haiku) has the
beta dropped instead of forwarded blind.

Restores a CHANGELOG bullet (PR #10366) that a prior merge auto-resolve
had dropped from this branch.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-18 10:50:49 -03:00
Diego Rodrigues de Sa e Souza
7a6fcfc74d fix: resolve adaptive latency-collapse self-lock with solo-progress and idle recovery (#10111) (#10478)
* fix(admission): resolve adaptive latency-collapse self-lock with solo-progress and idle recovery (#10111)

* fix(admission): refresh recovery ceiling on updateConfig (#10111)

updateConfig() clamped currentLimit to the new min/maxLimit but left
recoveryCeiling pinned to the value computed at construction time, so
a raised initialLimit could never recover past the stale ceiling and
a lowered one could leave the ceiling above the new maxLimit.
Recompute recoveryCeiling from the new initialLimit on every
updateConfig call, clamped to the (possibly also new) min/maxLimit.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-18 10:50:45 -03:00
Diego Rodrigues de Sa e Souza
e667ab12d1 fix(usage): wire agentrouter balance quota into dashboard Quota UI (#10078) (#10472)
* fix(usage): wire agentrouter balance quota into dashboard Quota UI (#10078)

* fix(usage): render AgentRouter wallet balance as USD in the Quota UI (#10078)

The prior fix wired AgentRouter's balance into getUsageForProvider() and
USAGE_SUPPORTED_PROVIDERS, but the actual dollar figure never reached the
Dashboard Quota UI: quotas.balance.remaining carried a synthetic two-state
percent (100/0) instead of the real dollarBalance, and the Provider Limits
renderer only formats a row as "$X.XX" when isCredits/currency/creditCount
are set, which the generic quota-parsing path never sets. A configured
balance rendered as a bare "100% left" percentage, not USD.

Shape quotas.balance.remaining as the real USD amount (clamped to 0) and add
an agentrouter branch to quotaParsing.ts that builds a credits-style row
(same buildCreditsQuota() pattern as DeepSeek/Claude extra-usage), so a
configured balance shows a currency-formatted dollar amount and an
exhausted balance always renders as exactly $0.00.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-18 10:50:35 -03:00
sha367
09680013de fix(providers): resolve combo names on /v1/audio/speech and /v1/videos/generations (#10471)
* fix(providers): resolve combo names on /v1/audio/speech and /v1/videos/generations

`GET /v1/models` advertises combos with `owned_by: combo`, and chat, embeddings,
transcriptions (#9134) and images (#8986, #9239) all resolve those names. Speech
and video did not: both rejected a combo name at model validation, before any
resolution could happen.

    POST /v1/audio/speech        {"model":"my-combo","input":"hi"}
    -> 400 Invalid speech model: my-combo. Use format: provider/model

    POST /v1/videos/generations  {"model":"my-combo","prompt":"a cube"}
    -> 400 Invalid video model: my-combo. Use format: provider/model

A client picking a model out of /v1/models therefore could not tell which
entries the catalogue would actually accept, and callers ended up hardcoding
vendor ids for these two routes while using combo names everywhere else.

Both routes now mirror the images route: detect a combo name before the
provider lookup and divert to a strategy executor. The two new executors follow
imageCombo — expand targets with resolveComboTargets(), filter to targets the
route can actually serve, walk them in priority order, and return the first
success or the last failure, with 400/401/403 treated as terminal.

Two details differ from the image strategy:

Speech filters at model level rather than provider level. parseSpeechModel()
resolves a provider prefix without checking that the model behind it can speak,
so `openai/gpt-4o` would otherwise be accepted as a target and fail only once
dispatched. The filter now checks the provider's own model list, and keeps
targets from dynamic provider nodes that do not enumerate models.

Speech also returns the handler's Response untouched instead of building a JSON
body, because that route streams audio; only the ADD-only meta headers are
attached, exactly as the direct path does. The failure branch is the only place
the body is read.

successfulMediaGenerationResponse() gains optional `strategy` and
`fallbackAttempts` so the video strategy can report them the way imageCombo
does, rather than duplicating the cost calculation. Both are omitted on the
direct single-model path, where neither is meaningful.

Tests mirror tests/unit/combo/image-combo.test.ts for both routes: combo not
found, no capable targets, empty combo, and targets present with no provider
connection. 16/16 pass across the three combo test files.

* fix(providers): preserve local overrides, custom models and per-target prompt rules through video combo dispatch

executeVideoCombo() diverged from the direct /v1/videos/generations route in
three ways: it dropped the ComfyUI-style local-override credential lookup for
authType:"none" targets, its capability filter only matched the built-in
video registry (skipping custom OpenAI-compatible provider nodes tagged with
the "videos" endpoint), and the route validated the prompt against the
unresolved combo name before combo targets were expanded — rejecting
prompt-optional I2V targets that never got the chance to opt out.

Extracts the shared resolution rules (resolveVideoModelTarget,
isVideoPromptOptional, resolveLocalOverrideCredentials) into
src/app/api/v1/_shared/videoModelResolution.ts so the direct route and the
combo executor apply identical rules, moves the combo-name diversion ahead of
the prompt-required check so validation runs against the real resolved
target, and adds per-target prompt validation inside the combo loop so a
missing prompt only rules out that target instead of the whole combo.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:50:31 -03:00
Diego Rodrigues de Sa e Souza
7f0404bf82 fix(open-sse): stop concurrent requests colliding on dedup hash for non-OpenAI formats (#10438)
* fix(open-sse): stop concurrent requests colliding on dedup hash for non-OpenAI formats

computeRequestHash() in requestDedup.ts projected the prompt content from
body.messages only. The dedup site in chatCore.ts hashes the *translated*
(target-format) request body, and non-OpenAI target formats don't carry a
messages field: Gemini-translated bodies use `contents`, Responses-API
bodies use `input`. So for those formats messages was always undefined,
every prompt hashed to the same null-backed value for a given model, and
concurrent requests with different prompts joined the same in-flight
promise -- the second caller silently received the first caller's
response verbatim (#10249).

Fix: project body.messages ?? body.contents ?? body.input ?? null instead
of only body.messages, keeping the rest of the canonical hash projection
unchanged. Genuinely identical concurrent requests still dedupe (the
intended perf behavior); different prompts under Gemini/Responses-API
target formats no longer collide.

Regression test: tests/unit/request-dedup-10249.test.ts reproduces the
two collision scenarios from the plan-file (Gemini `contents`,
Responses-API `input`), confirms the OpenAI `messages` case was already
correct, and asserts identical-request dedup keeps working. Verified
RED (byte-identical hashes 0b24fd88.../dc16d5b7... pre-fix) -> GREEN
(distinct hashes, dedup preserved) against this exact diff.

* fix(open-sse): cover nested translator shapes + system fields in dedup hash (#10438)

computeRequestHash() only read top-level body.messages ?? body.contents ??
body.input, but several translated request shapes nest their prompt
content: the Antigravity Cloud Code envelope under request.contents, and
Kiro under conversationState.currentMessage.userInputMessage.content (plus
conversationState.history). Two different concurrent prompts to those
targets could hash identically and share/leak a response between callers.

Adds extractPromptContent()/extractSystemContent() helpers covering every
prompt-bearing shape produced by open-sse/translator/request/*.ts
(OpenAI/Cursor messages, Claude messages+system, Gemini contents+
systemInstruction, Responses input+instructions, Antigravity and Kiro
nesting), and folds system/instructions/systemInstruction into the
canonical hash so two requests with the same user message but a different
system prompt no longer collide either.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-18 10:50:15 -03:00
GiauPhan
548316a2c4 fix(translator): Normalize tool call names from lowercase to PascalCase when translating upstream responses to Claude Messages API format (#10392)
* fix(translator): Normalize tool call names from lowercase to PascalCase (#1)

* Fix: Map lowercase tool names from Antigravity (Gemini format) to Claude Code expected PascalCase

* Fix: toolNameMap in fun restoreClaudePassthroughToolUseName

* fix(translator): Normalize tool call names from lowercase to PascalCase when translating upstream responses (OpenAI, Gemini, Antigravity) to Claude Messages API format

This resolves `Error: No such tool available: read`/`bash`/`write` errors when using Claude Code CLI with third-party providers that emit lowercase tool names. The fix adds case-insensitive tool name lookups in `openai-to-claude.ts`, `gemini-to-claude.ts`, and related translators, ensuring tool names like `read`/`bash` are mapped to `Read`/`Bash` before being sent to Claude Code. Includes unit tests and comprehensive changelog notes ([#10250](https://github.com/diegosouzapw/OmniRoute/pull/10250))

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(translator): Parse <tool_call> JSON and TOOL_CALL text formats fr… (#2)

* fix(translator): Parse <tool_call> JSON and TOOL_CALL text formats from model output

Some models (DeepSeek, Qwen) emit tool calls as text instead of proper
tool_calls JSON: either <tool_call>{...}</tool_call> or TOOL_CALL Name: {...}.
Extend extractXmlInvokeBlocks to handle all 3 formats in a single scan pass,
picking whichever pattern appears first. Includes unit tests for all formats.

* fix(translator): Parse text-format tool calls in gemini-to-claude translator

Extend the Gemini->Claude translator to detect <invoke>, <tool_call> JSON,
and TOOL_CALL text formats emitted inline in text parts (Antigravity/Gemini
models), converting them to proper tool_use content blocks instead of leaking
raw text to Claude Code.

* docs(changelog): Add changelog entry for text tool call parsing fix

* fix(translator): consolidate tool name casing normalization and restore thought-signature persistence (#3)

* fix(translator): sanitize tool_use.id and tool_result.tool_use_id to match Anthropic schema (#4)

Ensure tool IDs from OpenAI-compatible upstreams (which may contain dots, colons, or special characters) are sanitized to ^[a-zA-Z0-9_-]+$ in response translators and passthrough requests before reaching Claude endpoints.

* fix(responses): preserve native tools for openai-compatible Responses targets (#5)

A Responses-shaped request to a custom openai-compatible connection whose
outbound protocol is Responses took a Responses -> Chat -> Responses round
trip, so Codex custom tools lost their grammar (`exec`), namespace groups were
flattened (`collaboration`), and tool invocations failed upstream.

Gate a native Responses passthrough on the connection's configured protocol
(`apiType: "responses"` / `_omnirouteForceResponsesUpstream`) so the original
tool definitions reach a Responses-capable upstream unchanged. Chat-only
connections keep the existing downgrade.

Closes #10374

* fix(translator): add support for 'applypatch' tool name in tool call checks

* test(translator): add unit test for apply_patch and applypatch tool name remapping

* fix(translator): remove no-explicit-any lint errors in tool-use-id-sanitization test

Type the openaiToClaudeResponse/translateNonStreamingResponse return
values with narrow local shapes instead of `any`, satisfying the
repo's no-explicit-any = error rule for tests/. No behavior change —
the same 3 assertions still pass.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* test: update 9568 casing regression to match #10392's consolidated fix

restoreClaudeToolName's static casing map now normalizes known
lowercase tool names to canonical PascalCase unconditionally on the
gemini-to-claude and openai-to-claude Claude Messages API paths (not
gated behind toolNameMap), superseding the earlier per-map-only fix
that the original #9568 regression test locked in as "expected" (it
was previously labeled a known bug case). The gemini-to-openai
passthrough path is unaffected by #10392 and keeps its original
pass-through assertion.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:49:48 -03:00
Ravi Tharuma
231b16ef18 fix(auto): rate-limit empty-pool AUTO warnings (#10344)
Family resolves like auto/zai with no connected models logged a warn
on every call (about once a minute per poll). Keep the empty-pool
behavior; emit the warn at most once per label per 60s.

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-08-18 10:49:38 -03:00
Tushar Agarwal
1089c24bc8 Remove/mimocode sunset provider (#10186)
* remove: drop sunset MiMoCode provider from model catalog

* remove: drop sunset MiMoCode provider from model catalog (shared.ts)

Remove unused imports, types, and comments from shared.ts.

* remove: MiMoCode provider (Xiaomi sunset) — executor, registry, no-auth config, icon, tests

* refactor(providers): finish MiMoCode removal — sweep remaining no-auth references

Drop the leftover mimocode entries from the no-auth provider controls, the
translate-path snapshot, the eslint suppressions, and the #3061 auth-loop
test. Re-point the fingerprint-pin (#6696) and proxy-noauth (#6272) tests at
opencode, which exercises the same fingerprint path, so the removal does not
break runtime behavior.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* docs(providers): reconcile provider/executor counts after MiMoCode sunset

The base's parallel doc-count sync (#10433) pinned 340 providers / 101
executors. With mimocode removed, live code has 339 providers and 100
executors; refresh the user-facing counts (package.json description,
llm.txt, README/AGENTS, i18n llm.txt, provider reference, diagrams) so the
check-docs-counts STRICT gate stays green.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* test(providers): fix orphaned mimocode references after MiMoCode sunset

The sunset removed mimocode/mcode from the free-onboarding candidates and
from FINGERPRINT_PROVIDERS, but two tests still referenced them:

- free-provider-onboarding-setup: the mimocode->theoldllm substitution
  introduced duplicate 'opencode' rows (impossible given the request-set
  dedupe) and the wrong display name; align expectations with the actual
  {opencode, theoldllm} dedupe behavior and 'The Old LLM (Free)' name.
- combo-system-prompt-templates-5501: resolveTargetFingerprint tested with
  provider 'mcode', which is no longer a fingerprint provider; point it at
  the remaining fingerprint provider 'opencode'.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Tushar49 <Tushar49@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-08-18 10:49:24 -03:00
Benson K B
2d50ec0789 feat(routing): add quota-aware provider scheduling — Phase 2 (#10126)
* feat(quota): Phase 2 adapters, reset timers, analytics, and dashboard API

* feat(routing): add quota-aware provider scheduling (opt-in)

* fix(db): rename migration to 148_provider_quota_state.sql

* fix(quota): harden quota state route, isolate phase2 tests, slim env diff

- route: requireManagementAuth + Zod body validation + buildErrorBody
  sanitization (Hard Rule #12); fix clearProviderQuotaState -> clearProviderQuota
- .env.example/ENVIRONMENT.md: drop ~20 foreign vars, keep only
  OMNIROUTE_QUOTA_AWARE_ROUTING (migration 148)
- tests/unit/quota-phase2.test.ts: DATA_DIR mkdtemp + resetDbInstance teardown

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* chore(ci): fix docs-sync + eslint-suppression drift for quota branch

CI gates flagged on PR #10126 head 43335f07:
- migration counts in README/AGENTS/llm.txt were stale (145 -> 146)
- regenerate docs/reference/PROVIDER_REFERENCE.md (gen-provider-reference)
- sync root llm.txt body into all 42 i18n mirrors (headers preserved)
- prune eslint suppressions that no longer occur

--no-verify: pre-commit docs-sync was failing on a pre-existing
release-base artifact (changelog 3.8.49 vs package 3.8.50) — fixed by
the changelog entry in the prior commit; re-verify in CI.

* chore(skills): regenerate agent skills (add omni-settings)

Merge-integrity CI gate flagged a missing generated skill. Regenerated
with check:agent-skills-sync --apply: +omni-settings, 45 unchanged.

* fix(ci): resolve Fast Quality Gates regressions on quota branch

- check-migration-numbering: migration 148 (provider_quota_state) landed
  on this branch, so the KNOWN_GAPS allowlist entry is stale — remove it
  (stale-enforcement 6A.3: 'REMOVA a entrada')
- open-sse/utils/stream.ts: duplicate sseCommentsEnabled import from a
  bad merge (lines 31 + 77) — TS2300 duplicate identifier; drop the
  duplicate so the open-sse typecheck gate is back within baseline

* docs: sync migration count to 149 after release merge

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* test(migrations): align 148 gap assertion after 148_provider_quota_state.sql landed

The phase-2 branch added 148_provider_quota_state.sql, and 148 was already
removed from KNOWN_GAPS in scripts/check/check-migration-numbering.mjs. The
frozen-allowlists assertion still expected 148 to be a gap, so it failed.
Flip the assertion to match the allowlist (same pattern as 143/147).

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>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-08-18 10:49:19 -03:00
NOXX - Commiter
48fef207ec fix(adobe-firefly): renew sessions through durable CDP (#9255)
* fix(adobe-firefly): open browser sign-in and resolve provider slug in /login

POST /api/providers/[id]/login passed the connection DB id to
inAppLoginService.startLogin, but that service looks up the provider by
slug in TOKEN_EXTRACTION_CONFIGS. The lookup always missed and returned
"No extraction config" without launching a browser — so the VibeProxy
"Sign in" button for Adobe Firefly (and every other web-cookie provider)
never opened a browser.

Adobe Firefly additionally had no extraction config because its IMS JWT
is never in cookies/localStorage — it only rides on the Authorization:
Bearer header of firefly-3p.ff.adobe.io XHRs.

- Resolve the provider slug from the connection row and pass the slug
  (not the DB id) to inAppLoginService.startLogin.
- Add open-sse/services/adobeFireflyBrowserLogin.ts: a Playwright
  service that launches a visible browser at firefly.adobe.com and
  intercepts firefly-3p requests to capture the IMS JWT + sherlockToken
  cookie. Wire it into the /login route for the adobe-firefly slug.
- Fix latent bug: updateProviderConnection reads camelCase keys
  (apiKey, providerSpecificData), so the previous snake_case call never
  persisted extracted credentials.

* fix(adobe-firefly): open browser sign-in and resolve provider slug in /login

POST /api/providers/[id]/login passed the connection DB id to
inAppLoginService.startLogin, but TOKEN_EXTRACTION_CONFIGS is keyed by
provider slug — so browser login never launched for web-cookie providers.

Adobe Firefly also cannot use cookie extraction: the IMS JWT only appears
on Authorization headers to firefly-3p.ff.adobe.io. Add a dedicated
Playwright interceptor and persist credentials with camelCase keys that
updateProviderConnection actually reads.

* fix(adobe-firefly): use system Chrome/Edge CDP for browser sign-in

Playwright is not available inside the pkg-packaged VibeProxyServices.exe,
so import('playwright') always failed with 'Playwright not installed' and
never opened a window. Launch Chrome/Edge with --remote-debugging-port and
capture the firefly-3p Authorization Bearer via pure CDP WebSocket instead.

* fix(adobe-firefly): live x-arp-session-id / Arkose wire (stop 408 under load)

Browser generate-async requires x-arp-session-id as base64({sid,ark,ftr}) with a
real Arkose blob (sherlockToken). JWT alone frequently returns colligo HTTP 408
system under load while credits still work.

- Match live ftr magic __UDF43-m4_31ck + Arkose pk in synthetic ARP fallback
- Ranked extract of sherlockToken / x-arp from Cookie, HAR, fetch() paste, and
  space-joined JWT+ARP (PasswordBox newline collapse)
- Reuse one ARP for storage upload + generate-async
- Clearer 408 errors when browser ARP is missing vs stale
- Unit suite 42/42

* fix(adobe-firefly): durable session ARP rebuild and aux_sid false-positive

Rebuild x-arp-session-id from forterToken/arkose/ff_session_guid instead of
ranking long Cookie pairs (e.g. aux_sid=…) as opaque ARP, which caused colligo
HTTP 408. Cache IMS JWT + cookie sessions, rotate ARP on 408 retries, and keep
Playwright warm-up opt-in only (headless Forter is rejected).

Also expand synthetic ARP shape with bfp/fpjs to match live successful captures.

* fix(adobe-firefly): durable session, off-screen Chrome recovery, browser sign-in

Rebuild x-arp-session-id from Cookie pieces (sid/ark/forter) so aux_sid is never
sent as ARP. Sticky ARP + submit spacing reduce mid-batch colligo 408 thrash.

Add optional managed Chrome warm (off-screen headed by default; Forter rejects
headless) and POST /api/providers/{id}/login browser sign-in that returns JWT+Cookie
after a fresh SSO. Visible sign-in resets off-screen window placement and clears
prior Adobe session when adding another account.

* fix(adobe-firefly): renew sessions through durable CDP

* fix(adobe-firefly): isolate browser sessions per account

* fix(adobe-firefly): make account login fresh and deterministic

* chore(adobe-firefly): remove obsolete browser fallback

* docs(adobe-firefly): document renewal controls

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-18 10:48:59 -03:00
Xiangzhe
0a74bfbdea feat(cli): relay-like CLI closure — target manifest, Codex TOML, Gemini launcher, guards
- canonical executable manifest (bin/cli/cli-manifest.mjs): run/configure/completion
  derive targets, aliases and --model wiring from one table; drift test cross-checks
  manifest x cliRuntime x UI catalog (tests/unit/cli/cli-manifest-drift.test.ts)
- dashboard Codex generator converged to ~/.codex/config.toml (modern Codex v0.137+,
  verified against codex-cli 0.147.0): conservative merge, env_key auth (key never
  written), refuses invalid TOML, reports legacy config.yaml as migration note
- omniroute run gemini: launcher over OmniRoute's /v1beta surface via
  GOOGLE_GEMINI_BASE_URL + isolated GEMINI_CLI_HOME forcing gemini-api-key auth
  (contract proven against @google/gemini-cli 0.50.0); ACP registration kept distinct
- opt-in real smoke harness for upstream CLIs (RUN_CLI_SMOKE=1, credential by env
  NAME, redacted output): tests/integration/upstream-cli-smoke.int.test.ts
- container-guard homologation for POST /api/cli-tools/apply (422 in container,
  dry-run preview allowed, host write passes) + docs; guard untouched
- typecheck: omniglyphAdapter union narrowing, usageTracking typed signatures
  (UsageLike, no any), models.ts isValidModel params — typecheck:core and
  typecheck:noimplicit:core now clean
- relay core (prior session of this effort): omniroute run for 6 CLIs, configure
  picker with per-context favorites/recents, contexts with optional keychain +
  0600 fallback, provider CRUD with recursive redaction, completion updates, docs
2026-08-18 08:25:16 -03:00
Diego Rodrigues de Sa e Souza
cd091ab878 fix(sse): route bare qwen3.8-max to the canonical -preview id (#10632)
The model ships only as `qwen3.8-max-preview` across every provider that serves it (bailian-coding-plan, qoder, qwen-cloud-token-plan, qwen-web), so the bare `qwen3.8-max` missed MODEL_SPECS: the chatCore context preflight fell back to contextManager's `default: 128000` and rejected prompts with `context_length_exceeded` despite the model's real 1M window, and the unknown id would have reached the upstream verbatim.

Both symptoms share one cause, so the alias goes in BUILT_IN_ALIASES, which resolveLifecycle() applies before the preflight and before dispatch.

Merged with the inherited OmniGlyph base-red (#9985) documented: its two failing compression tests were reproduced on the pure base tip aa912c42a7, with no commit from this branch.
2026-08-18 05:47:27 -03:00
Xiangzhe
aa912c42a7 docs: update omni route video guides ranking layout 2026-08-17 16:16:58 -03:00
Diego Rodrigues de Sa e Souza
6c50137eeb fix(combo): actionable recovery hint for the all_targets_skipped terminal reason (#9303) (#10510)
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-17 08:25:41 -03:00
Rouzbeh†
6ff2e7b2c2 fix(antigravity): heal empty-projectId accounts via retryable auto-onboarding (#10424)
* fix(antigravity): heal empty-projectId accounts via retryable auto-onboarding

Accounts with an empty Cloud Code projectId get a permanent 422 "Missing
Google projectId" when loadCodeAssist returns no project. The 3.8.50
bootstrap attempts to CREATE the project via onboardUser, but a single failed
attempt (transient network/upstream error) was memoized forever in
onboardAttemptedCache: every later request in the process skipped onboarding
and 422'd, even though a retry would succeed.

Replace the permanent per-token Set with a failure-backoff map: failed onboard
attempts are retried after a 5-minute backoff (bounded, self-healing), the
in-flight lock still dedupes concurrent calls, and success clears the failure
marker and memoizes the project as before. Accounts that CAN be onboarded now
heal automatically on a later request or token refresh — no user action.

Tests: the existing "does not retry" case is now framed as the backoff window;
a new case proves the account heals (retries onboarding and recovers the
project) once the backoff expires.

* chore(changelog): fragment for #10424 antigravity project autocreate

* feat(antigravity): BYOP fast-fail + manual GCP project-id override

Port decolua/9router#2934 + VansRouter 802a859:
- tryOnboardUser now returns a three-way status; a 200 onboardUser response
  WITHOUT cloudaicompanionProject means Google deprecated automatic project
  creation for standard-tier (personal) accounts (BYOP). Such accounts are
  cached permanently (no pointless ~18s re-onboard) and the executor fails
  fast with 403 GCP_PROJECT_REQUIRED + actionable 'enter your project id'
  message instead of the generic 422 or a delayed 429.
- Transient onboard failures keep the existing 5-min backoff heal.
- Manual project-id override: the EditConnectionModal now stamps
  providerSpecificData.isProjectIdManual when the operator enters a project
  id, and tokenRefresh skips auto-discovery for flagged accounts so the
  manual value is never overwritten.

* chore(changelog): cover BYOP fast-fail + manual override in #10424 fragment

* test(antigravity): expect fast 403 GCP_PROJECT_REQUIRED when loadCodeAssist finds no project (#10424)

Google now marks accounts without an onboarded project as BYOP (automatic
project creation deprecated for standard-tier accounts, #2934). The PR's
BYOP fast-fail path returns 403 gcp_project_required instead of the old
generic 422 missing_project_id; align the #2334 executor test with that
contract so CI unit-test shard 2/4 passes.

* fix(antigravity): persist isProjectIdManual, fix BYOP citation, dodge refresh-retry

Review follow-up on #10424:

1. EditConnectionModal: isProjectIdManual was set on
   updates.providerSpecificData right after the project-id field, then the
   OAuth path (Antigravity is always OAuth) rebuilt providerSpecificData from
   connection.providerSpecificData before the request went out, discarding the
   flag — tokenRefresh.ts was guarding a field never actually persisted. The
   flag now lands in the single surviving antigravity merge, with a jsdom
   regression test (modeled on edit-connection-modal-openai-store-toggle).

2. The '#2934' citation for the Google BYOP claim pointed at an unrelated
   closed issue. Swapped for the real tracking issue #8491 (empty Google
   projectId -> 422 class) across bootstrap/executor/test comments.

3. BYOP fast-fail now returns 422 instead of 403: chatCore's generic
   401/403 -> refresh-and-retry path was hitting Google's OAuth token
   endpoint on every request from an affected account (pointless — refreshing
   cannot create a GCP project), and 422 matches the sibling
   missing_project_id error the client already maps to an action-needed
   prompt.

Also: eslint-disable-next-line for the pre-existing
react-hooks/set-state-in-effect baseline noise in the modal (repo
convention, same pattern as 11 other dashboard files).

* chore(ci): drop unused eslint-disable in EditConnectionModal form hydration

The react-hooks/set-state-in-effect disable added in the previous commit is
unused under the repo's pinned eslint-plugin-react-hooks (7.0.1) — the rule
does not fire on this line at that version, so the unused directive tripped
the whole-repo 'No new ESLint warnings' gate (max-warnings 0). Verified with
the lockfile-pinned plugin: lint:json is clean (0 errors, 0 warnings).

* fix(build): bound and retry the opencode-plugin npm install in prepublish

The plugin's node_modules is gitignored, so every fresh CI checkout runs a
full npm install inside @omniroute/opencode-plugin during build:cli. npm's
unbounded fetch retries turn a stalled registry CDN connection (the recurring
onnxruntime-class ETIMEDOUT flake) into a 20-30 minute hang — the DAST
'Build CLI bundle' step has been cancelled at the 30m cap repeatedly.

- Bound npm fetch: --fetch-timeout 60s, 2 retries with capped backoff — a
  stalled connection now fails fast instead of hanging the job.
- Retry the install up to 3 times with a 10s pause between attempts, so
  transient CDN failures recover in-build.

Net effect: the step either completes (network OK) or fails quickly with a
clear error (network down) — it can no longer eat the whole job budget.

* ci(quality): use the npm-ci-retry action on every install step

Fast Quality Gates failed on the recurring onnxruntime-node postinstall
ETIMEDOUT (Microsoft CDN 150.171.x.x) - the same transient flake that has
hit Vitest and dast-smoke today. Only the Build job used the retry action;
the other five jobs (Docs, Fast Quality Gates, Vitest, Unit Tests,
changelog) still ran a bare install and die on any CDN hiccup. Use the
existing retry action (3 attempts, exponential backoff) on every install
step for consistency.

* Merge branch 'release/v3.8.50' into fix/antigravity-project-autocreate

* test(fix): refresh expired alibaba quota sample validity and onnxruntime pin for v3.8.50 base

- alibaba-free-tier-quota-fetcher.test.ts: sample quotaValidityPeriod
  (2026-08-16 16:00 UTC) is in the past, making every quota entry classify
  as expired/not_capable; bump to 2028-01-01 UTC so the text/merge
  classification tests exercise the intended path again.
- optional-transformers-dependency.test.ts: onnxruntime-node pin assertion
  updated from ~1.24.3 to ~1.27.0 to match package.json (bumped by #10403);
  the regular-not-optional intent is unchanged.

* test(fix): widen modelsDevSync lastSync wait from 200ms default to 2000ms

The truthy-spellings loop asserted each enabled case completes its first
fetch within waitFor's 200ms default timeout, which trips under CI runner
load (observed on PR 10424 shard 2/4). Match the file's other lastSync
waits (2000ms) so the sync-completion assertion is load-tolerant.

---------

Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
2026-08-17 08:23:41 -03:00
Ravi Tharuma
48e5cf7fe4 fix(sse): do not ZWJ-obfuscate the substring hermes in user text (#10488)
Keep the #8350 Hermes system-prompt drops, but remove hermes from the
factory obfuscate_words list so hostnames and CLI mentions stay intact.

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
2026-08-17 07:04:48 -03:00
backryun
c6c134300b perf(electron): ship optional ML/browser deps as installable packs (#10382)
Stage 7 of issue #10321 moves the optional ML and browser automation dependency closures out of the desktop bundle into checksummed, versioned packs installed on demand through the omniroute packs command.

- scripts/build/optionalPackStaging.mjs stages pack members under .build/optional-packs, creates release tarballs, and emits optional-packs.index.json with per-member SHA-256 checksums.
- scripts/packs provides manifest, install, remove, and verification helpers plus the packs CLI commands.
- Runtime lookup includes installed pack node_modules directories, while LLMLingua and browser executors continue to degrade gracefully when packs are absent.

The measured darwin-arm64 staging closure was about 534 MB of the 929 MB standalone node_modules tree (57%).
2026-08-16 02:20:59 -03:00
Jan Leon
e5e1358693 fix(antigravity): discover live chat models dynamically (#10422)
* fix(antigravity): discover Gemini 3.7 Flash models

* fix(antigravity): discover live chat models dynamically

* fix(antigravity): keep provider limits sanitizer strict
2026-08-16 00:42:59 -03:00
Chewji
be6f18b849 fix(account-fallback): classify 'insufficient credits' as credits-exhausted (#10116)
* fix(account-fallback): classify 'insufficient credits' as credits-exhausted

Command Code returns 400 'You have insufficient credits to make this
request...' when an account's billing credits run out. The phrase was
missing from CREDITS_EXHAUSTED_SIGNALS, so the error stayed unclassified
(errorType=null) and the connection was never marked credits_exhausted —
getProviderCredentials kept re-selecting the same dead account on every
request instead of rotating to a healthy one.

Add 'insufficient credits'/'insufficient credit' to the signal list
(already used by antigravity429Engine.ts) so the error classifies as
QUOTA_EXHAUSTED and the account is skipped on subsequent selections.

* fix(account-fallback): harden insufficient-credit matching and preserve chatanywhere

Add the common 'insufficient credit balance' variation to
CREDITS_EXHAUSTED_SIGNALS alongside the Command Code 'insufficient
credits'/'insufficient credit' signals, and restore the consolidated
ChatAnywhere gateway entry that the stale snapshot removal would have
deleted when merging into release/v3.8.50.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
2026-08-16 00:42:20 -03:00
Alex
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>
2026-08-16 00:42:09 -03:00
Rouzbeh†
e44a409aa9 fix(antigravity): classify geo-blocked egress, exclude account, real connection probe (#10420)
* fix(antigravity): classify geo-blocked egress, exclude account, real connection probe

Google refuses the Cloud Code model API from unsupported egress locations
with 400 FAILED_PRECONDITION "User location is not supported for the API
use." Previously this surfaced as a cryptic "Antigravity upstream error
(400)", never excluded the account, and the dashboard connection test stayed
green because it only probed the (non-geo-restricted) OAuth userinfo endpoint.

- errorClassifier: new GEO_BLOCKED type + isGeoBlockedError detection
  (400/403 + location-not-supported wording); non-terminal classification.
- chatCore fallback: GEO_BLOCKED marks the connection and caches a 24h
  rate-limit-until exclusion so routing moves to other accounts instead of
  re-selecting the same one; never bans/expires the account.
- auth: GEO_BLOCKED joins the non-terminal group (no banned/expired state).
- antigravityUpstreamError: geo refusals carry an actionable message (egress
  location vs account problem, proxy-in-supported-region guidance).
- connection test: antigravity/agy now probe the REAL streamGenerateContent
  surface (buildProbe), so a green tick means the model path actually works
  and a geo-blocked egress shows red with a clear diagnosis.

* chore(changelog): fragment for #10420 antigravity geo-block resilience

* chore(pr): drop prettier-version drift noise, keep only real hunks

The earlier format pass (local prettier differs from the repo's pinned
version) rewrapped unrelated lines in chatCore.ts and the provider test
route. Restore the base formatting and re-apply only the GEO_BLOCKED
fallback branch and the buildProbe connection-test changes.

* fix(antigravity): strip competing-agent system prompts (429 RESOURCE_EXHAUSTED)

Port decolua/9router b566b20, generalized: Antigravity flags system prompts
advertising competing agents ('You are a Claude agent, built on Anthropic's
Claude Agent SDK.' — Zed, Claude Code, etc.) and answers with a 429 quota
error. sanitizeAntigravityGeminiRequest now strips known competitor identity
sentences from systemInstruction.parts before dispatch; surrounding
instruction text is untouched and non-matching prompts pass through without
allocation.

* chore(changelog): cover competitive prompt strip in #10420 fragment

* fix(antigravity): scope GEO_BLOCKED classification to Google AI surfaces

Address reviewer feedback: classifyProviderError is shared across every
provider, so a lookalike 'not available in your region' body from an
unrelated upstream must not receive the egress-fixable 24h exclusion
treatment. Gate GEO_BLOCKED behind isGeoBlockEligibleProvider, which
matches the surfaces that actually emit Google's regional-availability
refusal: Cloud Code / Gemini Code Assist (antigravity, agy, cloudcode*),
the Gemini Developer API (gemini, gemini-cli, vertex), plus a
registry-driven fallback on executor/format. Non-Google providers fall
through to their existing 400/403 classification (typically null for an
unclassified 400), so a permanent block still follows its own path.

* ci: re-run quality gates

Trigger a fresh CI run for the PR: the previous run's 'Vitest (fast-path)'
job failed in 'npm ci' because the onnxruntime-node postinstall could not
download its binary from the Microsoft CDN (connect ETIMEDOUT
150.171.109.118:443). No tests ran; no code changed in this commit.

* fix(antigravity): guard provider before registry lookup in geo-block gate

isGeoBlockEligibleProvider passes the raw provider (string | null | undefined)
to getRegistryEntry(provider: string), failing typecheck:core and the
ts7-diagnostics ratchet (TS2345 at errorClassifier.ts:166). Add an explicit
null guard; runtime behavior is unchanged — a falsy provider already resolved
to !entry -> false.

* ci: re-run quality gates (vitest npm ci onnxruntime CDN flake)

---------

Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
2026-08-16 00:16:27 -03:00
Harkaran Brar
710e43eb97 fix(sse): answer tiny-budget reasoning probes with a truncated 200 (#10281) (#10284)
* fix(sse): answer tiny-budget reasoning probes with a truncated 200 (#10281)

Claude Code's /model capability check sends max_tokens: 1. Reasoning
models burn the whole probe on thinking, and some upstreams (e.g.
api.cline.bot for deepseek-v4-flash) answer the empty outcome with a
5xx "empty response content" instead of a truncated 200. The relayed
failure also marked the connection unavailable and poisoned
fallback/cooldown bookkeeping for what is only a probe.

Detect tiny-budget reasoning probes in the non-streaming providerFailure
path and synthesize a valid truncated response (200, empty content,
finish_reason "length") — the same semantics errorClassifier.ts already
grants to length-truncated empty 200s. Probes no longer poison
connection health. Refs #10281.

* chore(changelog): add fragment for reasoning-probe truncated-200 fix (#10284)
2026-08-16 00:14:35 -03:00
Hernan Javier Ardila Sanchez
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>
2026-08-15 13:51:34 -03:00
Diego Rodrigues de Sa e Souza
4b914ad297 feat(sse): bring i-have-adhd to vi/ja/id parity and guard the style × language matrix (#10425)
i-have-adhd shipped in #10271 with en + pt-BR only; it now covers vi/ja/id as well,
matching ponytail. Every level keeps the SHARED_BOUNDARIES clause.

Adds a per-MATRIX guard (output-styles-i18n-matrix.test.ts). Every existing test is
per-style, which is how less-code stayed English-only since the 9router port without
anyone noticing. The guard fails on: a new style without pt-BR, a style losing a
translation it had, a translation missing an intensity level, a translation that dropped
the boundaries clause, and a stale KNOWN_ENGLISH_ONLY entry.

Proven by mutation, not just by passing: dropping less-code from the allowlist and
renaming the vi key both turned it red with the expected messages.

less-code stays English-only as declared debt (KNOWN_ENGLISH_ONLY + comment); the
remaining coverage work is tracked in #10426.
2026-08-14 21:18:38 -03:00
Diego Rodrigues de Sa e Souza
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
2026-08-14 20:52:53 -03:00
Diego Rodrigues de Sa e Souza
abd4df63dc fix(sse): surface Qwen/Alibaba personal Token Plan quota in dashboard and preflight (#10290)
* fix(sse): surface Qwen/Alibaba personal Token Plan quota in dashboard and preflight

The personal Token Plan (5-hour / 7-day sliding windows) has no official
OpenAPI and the inference API key cannot read it. Add a cookie-authenticated
fetcher for the console gateway shared by home.qwencloud.com and the Model
Studio console (contract captured live from a logged-in session):

- open-sse/services/qwenTokenPlanQuotaFetcher.ts: POST /data/api.json
  (IntlBroadScopeAspnGateway / sfm_bailian) for usage + quota-config +
  subscription; sec_token resolved best-effort from the dashboard HTML;
  per-window parse (fields are omitted while a window is Temporarily
  Removed); 60s usage cache, 1h tier cache.
- usage/qwen-token-plan.ts leaf + registration in the usage dispatcher,
  USAGE_FETCHER_PROVIDERS, USAGE_SUPPORTED_PROVIDERS,
  PROVIDER_LIMITS_APIKEY_PROVIDERS and bespoke preflight/monitor windows.
- Also adds bailian-coding-plan to USAGE_SUPPORTED_PROVIDERS /
  PROVIDER_LIMITS_APIKEY_PROVIDERS: the coding-plan fetcher existed but the
  dashboard filtered those connections out (UI gap).

Refs #9603 (Problema 1 — quota missing; the 429 recovery half is a
follow-up).

* docs(env): document Qwen Token Plan quota env vars + regen omni-settings skill

QWEN_CLOUD_COOKIE, QWEN_CLOUD_SEC_TOKEN, QWEN_TOKEN_PLAN_HOST and
QWEN_TOKEN_PLAN_DASHBOARD_URL added to .env.example and
docs/reference/ENVIRONMENT.md (check:env-doc-sync), with the generated
omni-settings skill refreshed (check:agent-skills-sync).

Refs #9603

* revert: keep hand-tuned omni-settings thinking-budget section

The agent-skills-sync drift predates this PR (hand improvement from #10169
not yet synced into the generator source) — it fails on every open PR and
belongs to a base-reds fix, not this branch. Regenerating here would erase
the intentional content.

* feat(dashboard): add the Qwen/Model Studio console cookie field to the connection modal

The Token Plan quota fetcher is cookie-authenticated (the inference API key
cannot read the console gateway), but no modal field existed to paste that
cookie — so the quota was unconfigurable from the dashboard and the fetcher
could only ever return its 'needs a cookie' message.

Adds the field for qwen-cloud-token-plan and bailian-coding-plan alongside the
existing ollama-cloud / alibaba console-cookie inputs (same password-input,
blank-keeps-stored semantics), pre-fills it when editing a connection, and
extends the providerSpecificData string/length validation to the two new keys.

Tests: tests/unit/qwen-token-plan-cookie-field.test.ts (RED before, GREEN
after) covers persistence + trimming, the blank-input no-overwrite rule and
schema acceptance/rejection.

Refs #9603

* docs(dashboard): correct the Qwen console cookie instructions

The placeholder claimed the cookie looks like 'token=...'; the qwencloud
portal actually issues 'login_qwencloud_ticket=...' alongside cna/cnaui/aui
(mirroring login_aliyunid_ticket on the Alibaba console), so the hint pointed
at the wrong value.

Replaces the guesswork with the verified retrieval steps in all three places
an operator can hit — the modal field hint, the fetcher's 'needs a cookie'
message and .env.example/ENVIRONMENT.md: log in to home.qwencloud.com >
Billing > Subscription, F12 > Network, reload, filter by api.json, click a
request to cs-data.qwencloud.com and copy the WHOLE Cookie request header.
Also documents that the value must go on one line (it contains '=' and ';')
and that it dies with the browser session.

Refs #9603

* fix(dashboard): tolerate partial form objects in the qwen cookie branch

Adding bailian-coding-plan to QWEN_TOKEN_PLAN_PROVIDERS routed callers that
previously matched NO branch in assignQuotaScrapingProviderData into the new
one, which assumed the two new fields are always present. Older callers build
a partial form object, so buildAddProviderSpecificData threw:

  TypeError: Cannot read properties of undefined (reading 'trim')
  (tests/unit/dashboard/agentrouter-connection-modal-fields.test.ts)

Reads the new fields with optional chaining and adds a regression test that
calls the helper with those keys deleted for both providers.

Refs #9603

* refactor(dashboard): move quota-scraping form logic into a UI-free module

tests/unit/qwen-token-plan-cookie-field.test.ts imported QuotaScrapingFields
directly, which pulls `@/shared/components` and, through that barrel,
untranspiled ESM (@lobehub/icons). The node:test runner cannot parse it and
the whole test file died in CI with:

  SyntaxError: Unexpected token 'export'
    at @lobehub/icons/es/Ai21/components/Mono.js

(It passed locally, so only the CI shard surfaced it.)

Extracts the pure pieces — QWEN_TOKEN_PLAN_PROVIDERS, QuotaScrapingFieldValues,
EMPTY_QUOTA_SCRAPING_FIELDS and assignQuotaScrapingProviderData — into
quotaScrapingFieldValues.ts. The component imports them and re-exports the
public names, so every existing importer keeps its current path. The unit test
now targets the UI-free module.

Refs #9603

* fix(providers): point bailian-coding-plan at the Token Plan endpoint and its console

Two independent defects kept this provider unusable with a valid Alibaba
Token Plan key (verified live 2026-08-14 with the owner's key and cookie):

1. Wrong inference host. The catalog entry is named "Alibaba Token Plan",
   links to token-plan-overview and its hint asks for a Token Plan key, but
   the registry pointed at coding-intl.dashscope.aliyuncs.com — the Coding
   Plan host, which rejects Token Plan keys with 401 invalid_api_key. The
   documented Anthropic base URL for Token Plan is
   token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic
   (https://www.alibabacloud.com/help/en/model-studio/more-tools). Against
   the new host the same key returns 200 for all six registry models and a
   real completion; auth stays on x-api-key.

2. Wrong console identity for quota. The personal Token Plan is sold through
   two consoles sharing one backend, and the gateway validates the session
   against the console declared in the request: an Alibaba console cookie
   (login_aliyunid_ticket) sent with the QwenCloud identity is refused with
   BailianGateway.Login.NotLogined. resolveConsoleSite() now picks host,
   cornerstoneParam.consoleSite/domain and Origin/Referer from the cookie's
   login ticket, falling back to the provider. With that switch the same
   cookie returns usage/subscription/quota-config.

Also routes bailian-coding-plan quota through the Token Plan fetcher (the
Coding Plan call returns "Bad Request" for these accounts), keeping the old
fetcher as the fallback for real Coding Plan keys, and labels the plan by
console ("Alibaba Token Plan (Pro)" vs "Qwen …").

Live validation: inference 200 (qwen3.7-plus answered "FUNCIONA"); quota
12,934/40,000 credits, 67.7% remaining, resets 2026-08-20.

Refs #9603

---------

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
2026-08-14 15:40:33 -03:00
Diego Rodrigues de Sa e Souza
20ea78c943 feat(sse): restate agentrouter quota 403/400 as retryable 429 with provider-scoped error rules (#10335)
agentrouter.org signals temporary quota exhaustion with HTTP 403/400 and a Chinese body (用户额度不足) instead of 429, so clients like Claude Code treat it as permanent and abort, and the fallback engine classified it as a generic apikey AUTH_ERROR.

New registry open-sse/config/upstreamStatusRestatement.ts restates those statuses to 429 with a synthetic Retry-After at a single hook in chatCore's providerFailure block (after parseUpstreamError), so classification, combo aggregation and the client response all see a retryable error. 无权访问模型 (permanently no model access) is veto-listed and never restated.

agentrouter classification rules are registered in providerErrorRules.ts and reach the real checkFallbackError path through resolveRuleMatchBody() with an exclusive FULL_TEXT_RULE_PROVIDERS allowlist — every other provider keeps its previous behavior byte-for-byte.

Known limitations tracked in #10334: the rules' scope field is informational (persistence applies per-model lockout for agentrouter), the 403-only model-access rule has no production path yet, and errors embedded in 200 SSE streams are not restated.

Refs #10334
2026-08-14 12:42:58 -03:00
Diego Rodrigues de Sa e Souza
964a3fe442 feat(sse): add i-have-adhd output style to compression catalog (#10271)
Adds `i-have-adhd` as the 5th entry in OUTPUT_STYLE_CATALOG — a port of the
github.com/ayghri/i-have-adhd skill (MIT), following the same integration shape as
ponytail. Action-first output shaping: the next action leads, multi-step work is
numbered, no preamble/recap/closers — which also trims output tokens.

lite/full/ultra levels in en + pt-BR, each ending in SHARED_BOUNDARIES so code, paths,
commands, errors and URLs stay verbatim. The agent-harness-specific upstream rules
(restate plan state, time estimates) are reworded as conditionals so they hold for plain
chat clients too.

Per the D-A1 registry contract, one catalog entry is the whole change: the injector, the
settings panel, the Zod schema and the telemetry all enumerate the catalog, so no other
production file moves. Dedicated test mirrors ponytail-catalog.test.ts (7 tests).
2026-08-14 11:57:00 -03:00
Diego Rodrigues de Sa e Souza
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
2026-08-14 10:03:10 -03:00
backryun
13098989e8 fix(types): narrow refresh token rotation inputs (#10257) 2026-08-14 00:57:58 -03:00
Diego Rodrigues de Sa e Souza
97aac6ac6c fix(ci): clear base-reds on release/v3.8.50 (round 4) (#10260)
* fix(ci): clear base-reds on release/v3.8.50 (round 4)

Drains the HARD failures reported by Release-Green run 31693210948 on issue #9985
(ESLint errors: 2) plus the merge-integrity red every open PR is inheriting.

- ESLint error 1: @omniroute/opencode-plugin/src/index.ts had a stray extra
  '});' (introduced by #9316) that broke parsing with 'unexpected file in NFT list'
  on the build path.
- ESLint error 2: cli-env-inline-comment-10100.test.ts used new Function to extract
  parseEnvValue from the bin entrypoint (no-new-func, Hard Rule #3). Extracted the
  helper to bin/cli/utils/parseEnvValue.mjs and import it from both the entrypoint
  and the test (same behavior, no eval).
- open-sse-typecheck (Fast Quality Gates): open-sse/utils/stream.ts imported
  sseCommentsEnabled twice (#9378) causing TS2300 Duplicate identifier; removed the
  duplicate import.
- Merge integrity (changelog + generated skills): skills/omni-settings/SKILL.md was
  edited manually by #10169 without updating the generator source, so
  check:agent-skills-sync failed on every PR (Generated: 1). Moved the curated
  thinking-budget content into a <!-- skill:custom-start --> block (the documented
  preservation mechanism), which the generator now keeps in sync.

Refs #9985

* fix(tests): align wave1-a poolside test with #10216 probed catalog

#10216 published Poolside's two authenticated-probe models
(poolside/laguna-xs-2.1, poolside/laguna-s-2.1) as static seeds, but the
wave1-a free-tier test still asserted 'no invented static model ids'
(entry.models === []), failing every open PR. Separate poolside from the
empty-models assertion and pin its probed catalog explicitly so a future
catalog change is a deliberate update, not a silent drift.

* fix(pack): register parseEnvValue.mjs in PACK_ARTIFACT_REQUIRED_PATHS

The extract of parseEnvValue to bin/cli/utils/parseEnvValue.mjs added a new
direct import to bin/omniroute.mjs, which pack-artifact-entrypoint-closures
enforces against PACK_ARTIFACT_REQUIRED_PATHS. Register the module so a future
tarball omission fails loudly.

* fix(combo): restore default same-model retry semantics after #10217

#10217 wired config.failoverBeforeRetry into the same-model retry guard in
both the priority/auto and round-robin loops, but DEFAULT_COMBO_CONFIG
defaulted the flag to true — flipping same-model retry off for every combo
that never touched the setting, not just the opt-in case. Round-4 bisect
(06f41cda63 vs d2fd88dfbc) reproduced this against
tests/unit/combo-499-abort.test.ts, tests/unit/combo-quota-exhaustion-only-fallback.test.ts
and tests/unit/combo-stream-readiness-fallback.test.ts. Flip the default to
false so the historical retry-before-failover behavior returns for combos
that never set the flag, while explicit opt-in (the two new tests #10217
added to combo-routing-engine.test.ts) still works.

* fix(quality): register visionBridge-responses-9597 in stryker tap.testFiles

check-mutation-test-coverage.mjs flagged tests/unit/guardrails/visionBridge-responses-9597.test.ts
as covering open-sse/services/combo/comboStructure.ts without being listed
in stryker.conf.json's tap.testFiles array. Add it so mutation coverage
attribution stays accurate.

* test(pack): expect parseEnvValue.mjs in the missing-artifact-paths fixture

The prior commit on this branch registered bin/cli/utils/parseEnvValue.mjs
in PACK_ARTIFACT_REQUIRED_PATHS but the "findMissingArtifactPaths flags
missing root runtime files in the tarball" test still hardcoded the old
expected list, so it never accounted for the new required path being
absent from the simulated tarball. Add it in its alphabetical slot.

* chore(lint): prune stale no-explicit-any suppression for call-log-file-rotation

--prune-suppressions found tests/unit/call-log-file-rotation.test.ts no
longer produces the 5 suppressed @typescript-eslint/no-explicit-any
warnings recorded in config/quality/eslint-suppressions.json. Remove the
dead entry so a regression would be caught again. Full-tree run with
--max-warnings 0 is clean: 0 errors, 0 warnings.

* fix(combo): decouple failoverBeforeRetry same-model guard from the skipUpstreamRetry default

Audit found that DEFAULT_COMBO_CONFIG.failoverBeforeRetry has defaulted to
true since before #10217 (predates #2417), and that value also feeds the
independent skipUpstreamRetry mechanism (src/sse/handlers/chat.ts:859,1126).
The previous commit on this branch flipped that default to false to fix the
#10217 same-model retry guard, which silently disabled skipUpstreamRetry's
own default-on behavior for every combo without an opt-in — a regression in
the opposite direction (executor-level retries before the loop's own
failover, changing latency/failure behavior).

Revert the default back to true and decouple the two mechanisms instead:
resolveComboConfig/resolveComboSetupConfig now also compute
failoverBeforeRetryExplicit, true only when a cascade layer (combo/provider/
global) literally sets failoverBeforeRetry to true — not merely inherited
from the default. The #10217 same-model retry guards in combo.ts (priority/
auto and round-robin loops) now read failoverBeforeRetryExplicit instead of
config.failoverBeforeRetry, restoring opt-in-only behavior for that guard
while the skipUpstreamRetry pass-through (config.failoverBeforeRetry at
combo.ts:1297,2865) is untouched and keeps its historical default-on.

* fix(combo,i18n): align getDefaultComboConfig with 10217 explicit flag; pt denoRelay entities

Two round-4 follow-ups exposed by the combinated base-red PR run:

1. comboConfig.ts: #10217 round-4 fix (104afeda4e) added
   failoverBeforeRetryExplicit to resolveComboConfig/resolveComboSetupConfig
   but getDefaultComboConfig() returned only DEFAULT_COMBO_CONFIG, so the
   combo-config.test.ts deepEqual (resolveComboConfig(null) ===
   getDefaultComboConfig()) failed on the extra field. Mirror the opt-in flag
   as false in the default.

2. pt.json: denoRelayOrgDomainHint still carried raw <app-name>/<org-slug>
   (the UNCLOSED_TAG RSC regression) — encode as &lt;...&gt; like the other
   42 locales, greening i18n-deno-relay-unclosed-tag.test.ts.

* chore(lint): disable @next/next/no-location-assign-relative-destination pending per-case review (#10292)

The eslint-config-next bump in #10043 shipped this new rule, flagging 6
pre-existing window.location.href navigations — several are deliberate
full-page reloads (login/logout state reset). Off with tracking issue
rather than a blanket router.push rewrite.

* fix(i18n): fill 439 missing UI keys (thinkingMode ×39 locales + pt catch-up) to restore 100% coverage

The #10169 Thinking Budget keys existed only in en/pt-BR/vi and the pt (PT-PT)
catalog from #10250 lagged 88 recent keys, dropping i18nUiCoverage to 99.3%
vs the frozen 100% ratchet baseline. Translated via the i18n:sync-ui marker
pipeline; glossary + ICU placeholder post-pass clean.

* fix(i18n): zh-TW glossary — replace retired 默認 with canonical 預設 in new thinkingMode keys

* chore(quality): rebase dead-code baseline 248 -> 409 for knip 6.32 bump (#10043)

dependabot #10043 upgraded knip 6.27 -> 6.32, which detects 162 MORE
genuinely-unused exports (331 vs 169) that 6.27 missed; DEAD_FILES
unchanged (78). Reproduced identically on the clean release/v3.8.50 tip
266e39d3 with a fresh 6.32 node_modules, so every PR is born red until
the tool change is absorbed. Owner authorized rebaseline (2026-08-13 via
PR #10260). Structural cleanup of the newly-surfaced dead exports remains
separate debt.

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-13 23:02:48 -03:00
Markus Hartung
d2fd88dfbc fix(combo): make failoverBeforeRetry actually skip the same-model retry (#10217)
* 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(combo): make failoverBeforeRetry actually skip the same-model retry

Both same-target retry loops (priority/auto and round-robin) checked
isTransient/maxRetries/providerExhausted but never consulted
config.failoverBeforeRetry, so a rate-limited model still got
maxRetries+1 back-to-back attempts on itself before falling back to a
sibling — the config option (#2417) was only ever wired into
skipUpstreamRetry, a separate lower-level mechanism. Now the same-model
retry is skipped when failoverBeforeRetry is set AND a sibling target
is actually available; with no sibling left, it still retries same-model
since skipping would just burn the last attempt for nothing.

---------

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: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-13 07:53:38 -03:00
Ravi Tharuma
4e1d21f756 docs(settings): Thinking Budget modes + fix Auto i18n collision (#10169)
Co-authored-by: RaviTharuma <RaviTharuma@users.noreply.github.com>
2026-08-13 07:53:12 -03:00
Nathan
06f41cda63 fix(combo): isolate session stickiness by combo (#10137)
Co-authored-by: Bryan Nathan <bryan@users.noreply.github.com>
2026-08-13 07:53:02 -03:00
Hernan Javier Ardila Sanchez
a2e5bd1dfc fix(combo): default chaos SSE to comment-only for OpenAI-compatible clients (#10128)
* 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(combo): default chaos SSE to comment-only for OpenAI-compatible clients

---------

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>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-13 07:52:56 -03:00
Xiangzhe
99d19f8f35 fix(kimi): normalize MFJS tool schemas (#10079)
Remove unsupported root-level anyOf constraints only on Kimi and Moonshot OpenAI tool requests while preserving nested schemas and caller-owned inputs. Mark Kimi Web models as unable to execute function tools so combo routing filters them correctly.
2026-08-13 07:52:41 -03:00
Ke Jin
1a8d38655d fix(reasoning): preserve and replay assistant turns (#10045) 2026-08-13 07:52:12 -03:00
Markus Hartung
c9daf99e37 fix(combo): clear LKGP pin when its target fails, not only set it on success (#10034)
setLKGP() was only ever called on success — nothing invalidated a "last
known good provider" pin once that provider started failing, so a
*separate* subsequent request kept re-selecting the same just-failed
target via applyStrategyOrdering.ts's LKGP reordering.

Live incident: an OpenClaw request to combo "default" (routerStrategy:
lkgp) got a real reasoning + apply_patch tool call from
opencode-zen/big-pickle, then 3 separate follow-up requests over the
next ~2 minutes each independently re-selected the same big-pickle
target and each timed out with "504 Stream produced no non-ping SSE
event within 95000ms" before the client gave up — instead of failing
over to any of the combo's other 12 models.

Root cause confirmed via code read: circuit breaker and model lockout
deliberately don't react to this failure class (isStreamReadinessFailureErrorBody
exempts STREAM_READINESS_TIMEOUT/combo_target_timeout 504s from tripping
the provider breaker, and REQUEST_SCOPED_UPSTREAM_ERROR_CODES suppresses
model-lockout recording for the same class — both intentional, to avoid
poisoning a healthy provider on request-specific timing). Nothing else
in the system was clearing the stale LKGP pin, so it kept winning
target-selection ordering for every new top-level request.

Fix: add clearLKGP(comboName, modelId) to src/lib/db/settings/lkgp.ts,
export it through settings.ts/localDb.ts, and call it (mirroring the
existing setLKGP-on-success call pattern exactly, same two keys) in both
combo.ts's per-target failure paths -- handleComboChat's "Done retrying
this model" block and handleRoundRobinCombo's structurally identical
twin -- right where a target is finally given up on and the loop moves
to the next one.

TDD: new regression test in tests/unit/combo-routing-engine.test.ts
("clears LKGP after the last-known-good target fails") reproduces the
exact live scenario -- confirmed failing against the pre-fix code,
passing after. Added direct unit coverage for clearLKGP itself in
tests/unit/db-settings-crud.test.ts (deletes only the targeted key,
sibling keys survive; no-op on an unset key doesn't throw) and
registered the new export in db-settings-split.test.ts's public API
surface characterization test.

Test plan:
- Full combo/LKGP-related suite (combo-routing-engine, db-settings-crud,
  db-settings-split, combo-strategy-fallbacks,
  combo-selected-connection-success,
  delete-provider-connection-invalidates-lkgp-8887, db-read-cache) --
  183/183 passing.
- npx tsc --noEmit -- clean for all changed files (pre-existing unrelated
  errors elsewhere in the same test files confirmed identical against a
  pristine upstream/release/v3.8.50 checkout, zero diff at those lines).
- npm run lint -- clean (new test's any usage properly typed, not left
  to inflate the file's frozen any-budget suppression).

⚠️ base-red inherited: #9985
2026-08-13 04:02:34 -03:00
Jefferson Alves
10c622afa6 fix(providers): default missing cache_control.ttl to 1h on the native Claude OAuth path (#10221)
Real Claude Code (and CC-protocol-compatible clients) commonly send
`cache_control: { type: "ephemeral" }` with no `ttl`. On the native
Claude OAuth path (provider `claude`/`cc`) the outbound anthropic-beta
set always includes extended-cache-ttl-2025-04-11, so requesting the 1h
TTL is always valid here — but Anthropic only honors it when `ttl` is
explicit; an absent `ttl` silently falls back to the platform default of
5 minutes even though the 1h beta was negotiated.

Practical effect: any pause longer than 5 minutes between turns forces a
full prefix rewrite (tens of thousands of tokens for a typical Claude
Code system+tools prefix) instead of a cache hit, burning through the
subscription's rate limit far faster than native (direct-to-Anthropic)
usage for the same workload.

Adds `normalizeCacheControlTtl()` to claudeCodeConstraints.ts (same
module as the sibling cache_control helpers enforceCacheControlLimit /
ensureCacheControlOnLastUserMessage) and calls it right after the
billing-header system-block manipulation in base.ts, immediately before
the request is signed and sent. Never touches a cache_control that
already specifies a ttl.

Measured before/after with a real Claude Code CLI session through this
path (system + tools prefix ~46k tokens):

  before: cache writes always land in ephemeral_5m_input_tokens; a >5min
  gap between turns forces a full rewrite (cache_read resets to 0)
  after:  cache writes land in ephemeral_1h_input_tokens; a >6min gap
  survives (cache_read stays intact)

--no-verify note: local pre-commit's check:docs-sync fails on this branch
tip ("CHANGELOG.md first section must be Unreleased") for reasons
unrelated to this diff (pre-existing state of release/v3.8.50 mid-cycle,
CHANGELOG.md untouched by this change). Added the required changelog.d
fragment per CONTRIBUTING.md regardless.

Co-authored-by: Jefferson Alves <jefferson@rastrosystem.com.br>
2026-08-13 00:40:05 -03:00
Diego Rodrigues de Sa e Souza
d259d9fcba fix(ci): clear base-reds on release/v3.8.50 (round 3) (#10213)
* fix(ci): clear base-reds on release/v3.8.50 (round 3)

- CHANGELOG.md: restore the top [Unreleased] section dropped by the #10189
  reconcile (docs-sync gate: first section must be Unreleased)
- env-doc-sync: document CONDUCTOR_ORCHESTRATOR_TOKEN + CONDUCTOR_SPOKESPERSON_URL
  in .env.example/ENVIRONMENT.md; allowlist the CI-only GITHUB_STEP_SUMMARY and
  TS7_BASE_REF (ts7 ratchet signals); drop a stray merge artifact line
- providers: restore the audited chatanywhere metadata entry that base-reds
  round 2 dropped together with its duplicate — the provider was half-wired
  (registry+endpoint without APIKEY metadata), which is what the wave3 test
  catches; re-pin providers-constants-split at the measured 228
- docs counts: 338 -> 339 (today's +2 void-ai/helixmind, -1 Puter) via
  gen:provider-reference + README/AGENTS/llm.txt/package.json/diagrams/i18n mirrors
- file-size ratchet: annotated rebaseline for the two pre-existing drifts
  (ModelSelectModal 1138, gateways 1250) following the 2026-08-11 precedent

Refs #9985

* fix(ci): base-reds round 3b — stale sibling tests + mode-pack weight contract

- check-docs-counts-sync.test.ts: drop the imports/subtests of the four helpers
  #10196 removed from the gate script (readMcpFactsFromSource, listLocalizedDocs,
  makeRequiredCountsValidator, checkFreeTierInventory) — the new-API tests that
  #10196 added stay; the file now loads again under the node runner
- quota-connection-recovery.test.ts: convert from vitest APIs to node:test —
  the file lives in tests/unit/*.test.ts (node-runner glob) and the vitest
  runtime crashes when imported outside vitest, killing the whole shard entry
- modePacks.ts: re-normalize all six mode packs to sum 1.0 — #8940 added
  sessionAvailability: 0.05 to every pack without rebalancing (1.05 total);
  ratios preserved exactly (÷1.05), so post-normalizeScoringWeights behavior
  is unchanged; restores the declared sum-to-1.0 contract the 4235 test pins

Refs #9985

* fix(ci): base-reds round 3c — vitest siblings, weights default, secrets FP, mutation tap

- DistributeProxiesButton.test.tsx: wrap renders in NextIntlClientProvider —
  #9245 localized the component (useTranslations) and left the test without
  the intl context, failing all 14 cases
- scoring.ts: re-normalize DEFAULT_WEIGHTS to sum 1.0 (same #8940 class as the
  mode packs — sessionAvailability added without rebalancing; ratios preserved)
- .gitleaks.toml: generalize the kimi sponsor-banner localStorage-key allowlist
  to -v\d+ — #10200 bumped v1→v2 and the stale regex regressed the secrets
  ratchet with a false positive
- stryker.conf.json: register 6 covering unit tests in tap.testFiles (4 modules)
  so their mutant kills count — unblocks check:mutation-test-coverage --strict

Refs #9985

* fix(ci): base-reds round 3d — inspector factor gap, stale registry/gap tests, i18n key sync

- comboScoringInspector: add cacheAffinity/sessionAvailability/connectionDensity
  to FACTOR_KEYS + the factor-key type — calculateScore() weighs them but the
  breakdown omitted them, so the explained contributions never summed to the
  reported score (inspector bug, red on the pure tip)
- combo-scoring-inspector.test: make the explicit-weights override sum-neutral
  (±0.05 shift) so it stays valid for any DEFAULT_WEIGHTS values — the hardcoded
  override only summed to 1.0 against the pre-#8940 defaults, which is also why
  explicit weights silently fell back to 'default' on the tip
- unorouter-registry.test: align to the canonical .com host (api.unorouter.ai
  301-redirects there, verified live) and to wave4's live model discovery
  (passthrough, no static seed) — the .ai/auto-model expectations were stale
- check-migration-numbering.test: 147 left KNOWN_GAPS when
  147_api_keys_model_access_mode.sql landed — assert absent (same as 143)
- i18n: sync-ui pass — 35,914 missing UI keys stamped as __MISSING__ placeholders
  across 42 locales (mechanical; greens the pt-BR key-presence integrity test;
  coverage pct unchanged by design — translation is a separate workstream)

Refs #9985

* fix(ci): base-reds round 3e — 2 real defects + 14 stale sibling tests (waves A-E)

Real defects fixed:
- src/lib/db/apiKeys.ts: #9313's empty-allowlist early return bypassed the group
  permission check, silently disabling group deny rules (#8817) for every key
  without a per-key allowlist; fall-through restored, restricted+[] deny-all kept
- open-sse/utils/proxyFetch.ts: #10032 re-appended the raw transport error to the
  propagated message, reintroducing the proxy user:password leak #9837 closed;
  new redactProxyDetailsInMessage() keeps the reason, redacts URL/credentials
- .github/workflows/quality.yml: #10134 added the TS7 ratchet as a separate
  blocking step AFTER the aggregated gates — the exact #8542 masking mechanism;
  folded into the non-fail-fast loop (still blocking, still PR-only) ⚠️ CI edit,
  gate-strengthening — explicit owner sign-off requested on the PR
- src/i18n/messages/ko.json: 3 machine-mistranslation regressions caught by the
  #8244 glossary checker (장애인→비활성화됨, 양말5://→socks5://, 비클로드→Claude가 아닌)

Stale sibling tests aligned to deliberately-moved contracts (each cites its mover):
request-log-detail-layout + -stream (#9245 intl provider), repro-8542 pin update,
quality-rail-gate-membership (#10134 shape), agentSkills-routes 45→46 (#9058),
cloudflare-ai-catalog-8717 (#8804 supersedes #8808), executor-xai (#9994),
vision-bridge-claude-wire (#9463 minimax→openai), sse-auth forced-pin (#8893),
tls-proxy-context (strengthened leak guards), rate-limit-local-error-classification
(#9164/#9342), minimax-thinking-signature (#9463), codebuddy-cn (#9723 +1 test),
github-copilot-custom-model (#9050), providers-g4f-batch3 (#9584),
synced-capability-warmup (#9199, stricter), sidebar-tools-group (#8221),
oauth-modal-grok-cli-paste (#9245); agentSkills/catalog.ts comment 45→46;
file-size rebaseline for proxyFetch (+19, annotated)

Refs #9985

* fix(ci): base-reds round 3f — waves F-J: 9 more real defects + stale sibling sweep

Real production defects fixed (all red on the pure tip, each with its origin):
- routeGuard.ts: #8949 accidentally DELETED the /api/providers/[id]/login
  local-only pattern — the route spawns a browser, so the loopback gate for a
  process-spawning route was gone (Hard Rules #15/#17); restored (314 guard
  tests green)
- agentSkills generator: #9058's category dispatch gave the config category an
  empty body, wiping skills/config-codex-cli/SKILL.md at the #10131 sync;
  fixed + SKILL.md regenerated via the official generator
- imageRegistry: #9982 broke same-provider bare aliasing (antigravity preview
  id sent upstream unresolved); new resolveSameProviderBareAlias() keeps the
  fal cross-provider fix intact
- imageRegistry: #9982's prefix strip handed the bare nano-banana ids to fal-ai,
  violating the pinned 2026-07-31 operator decision (adobe-firefly owns them);
  fal entries made prefix-only (dispatch already re-prefixes)
- mediaGeneration/fal.ts: the missing-credential 401 guard was lost when #10198
  deleted the superseded falHandler — tests were hitting the live network
- bottleneckPatch/rateLimitManager: #9041's merge clobbered #9604, resurrecting
  the Bottleneck v2.19.5 heartbeat bug (reservoir never refills); patched the
  library defect at the root and re-aligned chat-rate-limit-body-lock to the
  working reservoir contract
- processSupervisor.mjs: #9761 regressed the Node spawn to bare "node" (the
  #9156 launchd bug) and dropped #9209's ipv4first args; both restored
- openai-responses/pureHelpers: #9423's Agent null-sentinel was unreachable on
  the schemaless JSON-string path; gate extended
- i18n en.json: #8222's regen reverted the #9976 unclosed-tag fix and #8559's
  combo-cooldown copy; #9038 shipped 40 t() calls with no messages (runtime
  MISSING_MESSAGE); all restored/added + official sync-ui stamps, and vi's
  zero-marker policy re-established via the sanctioned translation backend

Stale sibling tests aligned (movers cited inline): chat-helpers (#9447),
executor-antigravity (#9351), video-fal-grok (#9982), visionBridge (#9759),
web-session-credentials (#8974), production-build-module-integrity (positive
anchor added), agentSkills-generator/skillManifestsLint/skills-injection/
agentSkillTools-mcp/listCapabilities-a2a (#9058), memory-settings (#10010),
model-catalog-policy-invalidation (#8906), model-alias-seed (#9485),
reactive-context-compaction (#8949), combo-provider-wildcard (broken upsert
helper), oauth-google-loopback (43-locale resurrected-key removal)

Validation: 501/501 across the 47 touched test files; typecheck:core, lint,
file-size, docs-sync all green.

Refs #9985

* fix(ci): base-reds round 3g — wave K/L: 4 more real defects + stale alignments

Real defects:
- base/reasoningEffort.ts: the stale duplicate cherry-pick #9612 re-added the
  codex minimal→low rewrite that #9883 had deliberately removed (OMP minimal
  passthrough); block removed again
- cursorImages.ts: #9840 wired prepareCursorImageForWire (sharp re-encode,
  fail-closed) into the SHARED resolveCursorImages, breaking zai-web and
  conol-web image uploads (HTTP 400 'undecodable'); new prepareForWire opt-out,
  Cursor default path unchanged (8 cursor suites green)
- modelCapabilities/snapshot: catalog prepare still issued 323 per-model reads
  of model_context_overrides + max_input_tokens overrides, violating #9199's
  bulk-load contract; both now resolve from the snapshot single pass
- v1-models-discovery-conformance: re-pinned to the bounded 30s SWR window
  (#9199/#10198) — the old 'stale-first regardless of age' contract is gone

Stale tests aligned (movers cited inline): codex-tools-strict-default (#9828
redundant-oneOf strip), devin-providers (#9245 i18n), db-migrationrunner-
constants-split (147→151 renumber #8228), gitlab-duo-oauth-setup (#9245),
chatcore-extracted-modules (#9161 outbound-protocol keying)

compression-api CI failures were cascade artifacts of codex-tools-strict-default
failing in the same force-exit shard process — no own defect (171/171 local).

Refs #9985

* fix(test): compression-api — register both describes before the runner starts

The DATA_DIR setup + route/db top-level awaits sat BETWEEN the two describes;
under --test-force-exit (the CI unit-runner flag) the process exits once the
already-registered tests finish, so on slow CI machines the whole second
describe died as 'Promise resolution is still pending' — the recurring
CI-only shard-2 failure that never reproduced locally without the flag.
Moved to the top of the file; 10/10 under --test-force-exit locally.

Refs #9985

* fix(quality): freeze modelCapabilities.ts at 1006 (annotated) — snapshot routing growth

Refs #9985

* fix(quality): move the modelCapabilities freeze into the frozen map (nested schema)

Refs #9985

* fix(i18n): translate all 39,718 pending UI keys across 42 locales (owner-approved)

Mass-translated every __MISSING__ placeholder via the official i18n:sync-ui
--translate-markers pipeline (operator backend), restoring i18nUiCoverage to the
100 baseline (was 89.9 after the merge-storm UI landings + the 42 keys #9038
never shipped).

Post-pass repairs, all caught by the existing gates:
- glossary: retired renderings the machine reintroduced normalized again
  (提供商→提供者 zh-CN/zh-TW, 鏈接→連結, 文檔→文件, 調用→呼叫, 供應商→提供者,
  響應→回應, 不活躍→未啟用 zh-TW; 클로드→Claude, 옴니루트→OmniRoute ko);
  DATA_DIR forbidden rendering avoided via 数据文件夹 rephrase
- ICU integrity: 120 values with renamed/dropped {params} repaired (39
  positional renames, 81 reset to the en source — functional over fluent)

Validation: glossary/pt-BR/vi/deno-relay/settings-keys/value-drift/google-
loopback suites 76/76; placeholder diff en×42 locales = 0; worst-locale
coverage = 100.0%.

Refs #9985

---------

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-13 00:02:25 -03:00
Diego Rodrigues de Sa e Souza
f1eb0b8357 refactor(providers): remove the Puter provider at its owner's request (#10210)
Remove the Puter provider (id `puter`, alias `pu`) entirely, at the
request of Puter's owner, Nariman Jelveh:

- registry entry (open-sse/config/providers/registry/puter/) and
  PuterExecutor (open-sse/executors/puter.ts), with their registrations
- API-key preset card (gateways.ts), provider icon and public SVG asset
- 33 free-model catalog entries (pool `puter`)
- authHint i18n key across all 43 UI locales
- credential-requirement frozen-list entry and related comments
- docs: ARCHITECTURE, CODEBASE_DOCUMENTATION, FREE_TIERS (removal note),
  PROVIDER_REFERENCE regenerated (337 providers), translated doc mirrors,
  llm.txt + its 42 i18n mirrors, README/AGENTS/package.json counts
  (338→337 providers, 144→145 migrations) and the 5 canonical SVGs
- migration 152 cleans up stored puter connections/keys/custom models;
  historical usage records are preserved (same principle as migration 151)
- regression guard: tests/unit/puter-provider-removed.test.ts; puter
  fixtures in shared tests swapped for neutral providers; translate-path
  golden snapshot regenerated

Historical CHANGELOG mentions are intentionally preserved; the removal
carries its own CHANGELOG entry.

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 16:54:50 -03:00
Diego Rodrigues de Sa e Souza
f6ccd3cf9f fix(quality): green release/v3.8.50 base-reds round 2 (#9985) (#10131)
* fix(quality): green release/v3.8.50 base-reds round 2 — gateways/conol/deepai corruption, migrations, docs, ratchets, dashboard-typecheck

Base-red fix for issue #9985 after the 2026-08-11 merge storm (99 PRs).

Real defects fixed:
- gateways.ts: close regolo entry (was swallowing naga-ac + chatanywhere from #9421), drop stale duplicate chatanywhere entry (#9594)
- conol-web + deepai registry: correct ../shared import depth + deepai executor:default
- modelSelectModalHelpers: close isProviderModelHidden (#9011)
- driverFactory.test.ts: restore eaten test-closing brace (#9173)
- usageTracking: remove duplicate cache_* props
- modelCapability{Overrides,ResolutionSnapshot,Capabilities}: max_token -> max_output_tokens (#9199 vs #8908) + test align
- videoGeneration: drop duplicate handleFalVideoGeneration import (mediaGeneration/fal canonical, #9982)
- responseSanitizer: cast input_tokens_details before .cached_tokens access
- EditConnectionModal: missing alibaba code fields, hoist validationPsd, providerPageHelpers Badge variant union
- FreeBudgetCard: t() -> labels.noApiKey
- peerRouting + cliRuntime: ProcessEnv typing
- image-combo.test.ts: type any -> unknown
- fal.test.ts: moved to tests/unit/services (collected path) 14 tests green
- remove duplicate 143_job_registry.sql (146 canonical), KNOWN_GAPS fix

Docs/ratchets (owner-authorized rebaselines, annotated):
- CHANGELOG 3.8.50 living section restored + 42 i18n mirrors
- MCP-SERVER.md 104->105 tools + i18n
- ENVIRONMENT.md/.env.example: ADOBE_FIREFLY_CHROME_HEADED + DEBUG_CLAUDE_NONSTREAM
- fabricated-docs allowlist: TELEGRAM proposal env vars
- file-size: 5 grown files + proxyFetch 1207->1220
- dead-code 230->248, codeql 2->9 (drift from merged PRs, not this PR)
- untrack _tasks symlink; agent-skills-sync --apply (config-codex-cli)

* fix(changelog): reformat two feature fragments to the bullet convention (#9239, #9490)

* fix(quality): prune stale ESLint suppressions (base-red)

* fix(quality): resolve open-sse type errors + catalog/build regressions (base-red round 3)

Storm-merge splices repaired in the base-fix PR #10131:
- doctor.ts: AppConfig missing brokerSocketPath
- conol-web.ts: Buffer not assignable to BodyInit (Uint8Array)
- tinycms.ts: TinyCmsExecutor.execute return matches BaseExecutor (response/url/transformedBody)
- tinycmsSigner.ts: encodeInto never-narrowing guard + dead wasm URL fallback (Turbopack)
- virtualFactory.ts: options slot for resolutionSnapshot
- bottleneckPatch.ts: insufficient-overlap casts (as unknown as)
- imageCombo.ts: narrow handleImageGeneration union result
- browser-worker.ts: AppConfig + turn.capabilities splice
- conolDiscovery.ts: getProviderOutboundGuard from Policy module
- catalog.ts: drop removed SWR hooks (getCatalogStaleWhileRevalidateMs + accessors), CatalogCachePolicy -> inline settings, resolve 4-arg call
- catalogCache.ts: remove dead inFlight/promise refs
- chat.ts: add isProviderBreakerFailureStatus import
- model-catalog-cache-swr-8728.test.ts: align to #9199 new API (policy injection removed)

* fix(quality): align UI test fixtures to current component contracts (base-red vitest)

- setup-wizard: provide required serverState prop (component gained it in a merged PR)
- grok-device-oauth-modal: next-intl stub resolves grok flow keys to EN labels
- provider-quota-widget: label now inline (PR #8916 removed AutoRefreshButtonLabel extraction) — test the widget
- use-provider-connections-cursor-refresh + phase1f: match /api/providers?provider=<id> query form; hoist heavy dynamic imports to module scope (timeout flake)
- home-topology: mock next/navigation useRouter (component added node-click navigation)
- cooling/lobe/AutoComboCatalog: raise cold-import describe timeouts to 30-60s
- request-logger-*: align to current detail-view contract

* fix(search): guard params.token undefined in serper headers (typecheck base-red)

* fix(search): guard token headers + non-null providerConfig (typecheck base-red)

* fix(changelog): restore base CHANGELOGs eaten by merge auto-resolve (43 files)

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 16:09:34 -03:00
Diego Rodrigues de Sa e Souza
37d70785c7 fix(build): repair broken production build, red lint gate and SWR crash (#10198)
The Build CI job is advisory, so eight module-level defects from eight
different PRs accumulated on release/v3.8.50 until `npm run build` failed
with 7 Turbopack errors and `npm run lint` with 14.

Build (link-time):
- modelSelectModalHelpers.ts: a lost `}` swallowed PROVIDER_TEST_CHUNK_SIZE
  into isProviderModelHidden's body (#9011).
- videoGeneration.ts: handleFalVideoGeneration imported twice; the standalone
  falHandler.ts is superseded by the provider-neutral mediaGeneration/fal.ts
  and is removed here (#9982 over #9969).
- catalog.ts: re-exported and called the injectable SWR policy that #9199
  deliberately replaced with a fixed 30s bound. Fixed on the consumer side —
  restoring the accessor would resurrect the unbounded window #9199 removed
  after measuring a 41s catalog build in production.
- tinycmsSigner.ts: generated wasm-bindgen glue kept a sidecar
  `new URL('wasm_signer_bg.wasm', import.meta.url)` that no file backs;
  Turbopack resolves it statically. The module ships inlined as WASM_BASE64
  and the only caller always passes it explicitly (#8736/#10087).
- conolDiscovery.ts: imported getProviderOutboundGuard from outboundUrlGuard,
  which does not export it. Fixed on the consumer side: outboundUrlGuard.ts is
  loaded by the packaged CLI without a tsconfig, so it must stay free of
  `@/`-aliased imports (#7682).

Runtime (the build never caught this one):
- catalogCache.ts::scheduleBackgroundRefresh had two dangling statements
  referencing undeclared `inFlight`/`promise`, so EVERY stale-while-revalidate
  read threw a ReferenceError. Surfaced by realigning the #8728 suite, which
  #9199 left asserting a removed contract.

Lint:
- driverFactory.test.ts: a case inserted between the preceding test's `finally`
  and its `});` left the file unparseable, so the SQLite driver-cascade suite
  (26 tests) had not run since 2026-08-11 (#9173).
- providerModelsConfig.ts: imported an executor directly, crossing the G14
  boundary; routed through a new open-sse/services/zaiWebCredentials.ts (#8451).
- image-combo.test.ts: 11 `any` violations, now typed (#9499).

Validation: npm run build exit 0, npm run lint clean, typecheck:core clean,
41/41 tests green across the affected suites.

Refs #9011 #9982 #9199 #8728 #8736 #10087 #8974 #9173 #8451 #9499

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 15:58:40 -03:00