Commit Graph

2512 Commits

Author SHA1 Message Date
Éder Costa
9108955323 fix(claude): classify native subscription quota 429 (#8628)
Co-authored-by: Escalada Online <aescaladaonline@gmail.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-07-27 19:08:05 -03:00
Jonathan Bailey
052cab3d46 fix(providers): complete OpenCode Go effort alias exposure (#8610)
* fix(providers): complete OpenCode Go effort aliases

* chore: number OpenCode Go changelog fragment
2026-07-27 19:07:43 -03:00
Prudhvi Vuda
ea678daad6 fix(auth): tag internal/loopback-origin failed logins in the audit log (#8606)
* fix(auth): tag internal/loopback-origin failed logins in the audit log

Failed dashboard logins (`auth.login.failed`) are emitted only after a
submitted, non-empty password fails verification, and the recorded IP is
accurate. On a single-process deployment with no reverse proxy, a loopback /
private source IP therefore means the attempt genuinely originated on the box
or the LAN (someone browsing http://localhost and mistyping, or a browser
autofill replaying a stale password) — but the Audit Log had no way to
distinguish those from an external intrusion attempt, so they read as
suspicious noise.

Add `classifyIpScope()` to `ipUtils` (loopback / private / public / unknown,
using bounded string-prefix checks — ReDoS-safe) and stamp `sourceScope` +
`internalOrigin` onto the `auth.login.failed` audit metadata so the audit view
can label internal-origin failures distinctly. No change to which events are
written or to IP attribution.

Closes #8336

* test(auth): assert the new origin tags on the failed-login audit event

This PR tags failed logins with sourceScope/internalOrigin, but the
pre-existing admin-audit-events assertion still deepEqual'd the old
two-field metadata shape and broke. The request under test carries a
public x-forwarded-for, so the expected tags are sourceScope: "public"
and internalOrigin: false — the assertion stays strict, it just covers
the fields this PR introduces.

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

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-27 19:07:33 -03:00
MumuTW
20aa796d97 fix(sse): route task-aware defaults by intent, fix fitness pattern shadowing (#8602, #8603) (#8605)
* fix(sse): restore task-aware routing config on restart (#8601)

The T05 Task-Aware Smart Routing config was persisted to settings.taskRouting
by PUT /api/settings/task-routing but never read back, so it silently reverted
to enabled:false + the hardcoded default model map on every restart.

Two root causes, both fixed:

- No boot hydration existed. Adds hydrateTaskRoutingConfig(settings), wired into
  src/instrumentation-node.ts next to the Thinking-Budget restore (#5312). It
  accepts either the JSON string the route persists or an already-parsed object,
  and fails open on malformed values. applyRuntimeSettings does not cover this
  key, same as the Global System Prompt (#2470).

- The config lived in a plain module-level `let`, which is duplicated per module
  graph — a boot hydration would have landed on the instrumentation graph's copy
  and never reached the one src/sse/handlers/chat.ts reads. This is the exact
  break #5312 fix-A hit on the VPS. Moves the store to the globalThis pattern
  already used by thinkingBudget.ts and systemPrompt.ts.

Runtime stats are never restored from the persisted blob.

Note the hydration is wired into instrumentation-node.ts, not the unused
src/server-init.ts.

* docs(changelog): add fragment for #8604 task-routing boot restore

* fix(sse): route task-aware defaults by intent, guard fitness pattern order (#8602, #8603)

Two related defects in the hand-maintained model-quality tables.

#8602 — DEFAULT_TASK_MODEL_MAP hardcoded literal provider/model ids
(openai/gpt-4o, gemini/gemini-2.5-flash-lite, deepseek/deepseek-chat, ...).
Wrong twice over: the ids rotted by a generation or two, and applyTaskAwareRouting
overwrites body.model directly, so a literal target skipped auto-combo's 13-factor
scoring (quota, circuit-breaker health, cost, latency, stability), connection
cooldown and model lockout — hard-failing for any operator with no connection for
that provider. Refreshing the strings would only reset the rot clock, so the
defaults now name auto/* INTENTS that resolve against the operator's actually
connected backends:

  coding        -> auto/coding
  analysis      -> auto/reasoning
  vision        -> auto/vision
  summarization -> auto/chat:fast
  background    -> auto/chat:cheap

creative and chat stay pass-through. Operators can still pin a specific model via
PUT /api/settings/task-routing; only the shipped defaults change. No provider/model
literal remains in the module.

#8603 — the pattern-shadowing fix LANDED UPSTREAM while this PR was open
(9f5be229b, Train 1D). lookupStaticFitnessTable now ranks patterns longest-first,
so gpt-4o-mini no longer inherits gpt-4o's 0.9 and deepseek-v3.2 no longer inherits
deepseek-v3's 0.85. This PR therefore no longer changes that behaviour — the
upstream scan is kept verbatim.

What remains for #8603 is the regression guard. The resolution chain hits the DB
(user_override / arena_elo / models.dev tier) before reaching layer 4, so asserting
the ordering through getTaskFitness would depend on DB fixture state. The layer is
exposed as getStaticFitnessTableScore and pinned directly by
taskFitness-pattern-order-8603.test.ts (7 cases), so the guarantee survives future
edits to FITNESS_TABLE. Those 7 cases were written against this PR's original
implementation and pass unchanged against the upstream one — independent
confirmation that the two are behaviourally equivalent.

* docs(changelog): add fragment for #8605 task-routing intent + fitness order
2026-07-27 19:07:30 -03:00
Prudhvi Vuda
4d24c0c4de fix(api): prevent silent lost updates on concurrent settings writes (#7784) (#8587)
Add opt-in settingsRevision / If-Match optimistic concurrency so stale
PATCH writers get 409 instead of silently clobbering map settings.
2026-07-27 19:07:10 -03:00
Alan Vb
6bdfd540ba feat(providers): live monthly credit quota for Firecrawl (#8759)
* feat(providers): live monthly credit quota for Firecrawl

Wire Firecrawl team credits into Provider Limits / preflight via
GET /v2/team/credit-usage (Bearer API key)

- firecrawlQuotaFetcher + usage/firecrawl leaf
- USAGE_FETCHER_PROVIDERS + USAGE_SUPPORTED_PROVIDERS + apikey allowlist
- register via quotaTrackersBatch
- unit tests for fetcher + usage dispatch

* chore(changelog) - add changelog on live monthly credit quota for Firecrawl

* fix(providers): satisfy provider limits file-size gate

---------

Co-authored-by: allanvb <allanvb@users.noreply.github.com>
2026-07-27 17:25:47 -03:00
MumuTW
5840d0961f fix(plugins): delete the generated host script synchronously so plugin loads stop leaking temp files (#8749)
* fix(plugins): delete the host script synchronously and stop two tests leaking child processes

Three related leaks in the plugin child-process lifecycle, found while tracing 27 node
processes on a developer machine.

1. loader.ts removed the generated omniroute-plugin-host-*.mjs with a fire-and-forget
   `rm(...).catch(() => {})`. That unlink loses the race against process exit: test:unit
   runs with --test-force-exit, which tears the process down before the promise settles,
   so every plugin load leaked one temp .mjs into TMPDIR. Measured at 6 files per
   full-suite run, 40 accumulated over a handful of local runs. rmSync closes the race;
   the throw stays swallowed because an exception raised from a child "exit" handler
   would take the server down, and a leftover temp script would not.

2. plugins-manager-lifecycle.test.ts "activates an installed plugin" called activate() --
   which spawns the plugin's child process -- but never deactivate(). deactivate() is the
   only path that reaches the loader's cleanup(), so the child outlived the test and its
   IPC channel kept the test process's event loop alive.

3. plugins-manager-restart-reload-7806.test.ts simulateRestart() deleted the entry from
   loadedPlugins without calling cleanup(), dropping the only handle that can kill child
   #1. The reload then spawned child #2, and the finally block's deactivate() could reach
   only child #2 -- one dangling child per test. A real restart takes the whole process
   tree down, so calling cleanup() here is both the faithful simulation and the fix.

Combined effect: a run without --test-force-exit deadlocks. The test process cannot exit
while its child holds the IPC channel open, and the child waits for messages that never
come. Observed as three plugin hosts alive for 4h51m under a runner that never finished.

Validation (Hard Rule #18, TDD): the new plugins-loader.test.ts case fails against the old
async unlink ("must delete the host script synchronously, not on a later tick") and passes
with rmSync. It redirects TMPDIR/TEMP/TMP to a private directory before counting, because
test:unit runs at --test-concurrency=20 and a concurrent file's host scripts would
otherwise land in the counted directory and flake the assertion.

After: 19/19 pass across the three files, 0 temp scripts created, 0 orphan processes.
tests/unit/build/** 334/334; typecheck:core and eslint clean.

* docs(changelog): add fragment for plugin host script sync delete
2026-07-27 17:25:38 -03:00
Austin Liu
0c1654b114 fix(guardrails): check feature flag helper in PIIMasker to honor DB overrides (#8708) (#8730)
Previously  checked  directly, ignoring database feature flag overrides configured via settings/UI. This update routes the check through  so DB overrides are respected as intended.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-07-27 17:25:25 -03:00
rinseaid
644da2924b fix(sse): hide synthetic OpenAI startup reasoning (#8729)
* fix(reasoning): sanitize Kimi K3 think tags

* ci: build patched OmniRoute image

* fix(sse): hide synthetic OpenAI startup reasoning

---------

Co-authored-by: rinseaid <rinseaid@rinseaid.net>
2026-07-27 17:25:21 -03:00
epsilonode
296765fd6a fix(db): preserve readonly and existing-file semantics in node:sqlite fallback (#8724)
* fix(db): preserve node sqlite open semantics

* test(db): keep node sqlite coverage off Bun

* chore(changelog): number native sqlite fallback

* test(db): register node sqlite tests only under Node
2026-07-27 17:25:08 -03:00
Mananz90
1f3f43ea39 fix: hydration mismatch, duplicate React keys, and missing ponytail i18n (#8723)
- KimiSponsorBanner/RiskNoticeBanner: read the localStorage dismissal flag
  via useSyncExternalStore (server snapshot = visible) instead of a
  useState lazy initializer, so the first client render matches SSR
  (which has no localStorage) instead of diverging on hydration.
- usage/analytics route: key the per-model aggregation map by model name
  alone instead of `${provider}::${model}`, matching the table's one-row-
  per-model display and eliminating duplicate `key={m.model}` rows when a
  model is served through multiple provider connections/accounts.
- CommandPalette: look up existing section/subgroup by id across the whole
  list instead of only comparing to the previous item, so sections whose
  children interleave root items and groups (e.g. omni-proxy) don't produce
  two subgroups sharing the same "_root" key.
- Add the missing `compressionOutputStyle.ponytail` label/description to
  all 43 locale message files (present in the style catalog but never
  added to any locale, causing a MISSING_MESSAGE crash).

Co-authored-by: Gillz <gillz@Gillzs-MacBook-Pro.local>
2026-07-27 17:25:02 -03:00
AmirHossein Rezaei
ba28e497fe fix(oauth): show GitLab Duo setup before authorize error (#8710)
* fix(oauth): show GitLab Duo setup before authorize error

Surface the OAuth app registration and env-var recipe in the Add
Connection modal before auto-starting authorize, and keep the same
shared copy for catalog authHint and the authorize fallback (#8688).

* fix(oauth): keep OAuthModal under file-size baseline for #8688

Extract waiting/error panels so the GitLab Duo setup step does not
trip the Fast Quality Gates file-size ratchet, and update the retry
Button regression guard for the extracted error step.
2026-07-27 17:24:52 -03:00
Austin Liu
f2ad55bd62 fix(providers): deprecate Monster API provider (fixes #8676) (#8691)
Monster API shuttered operations on 2026-06-30. Mark monsterapi entry as isDeprecated with deprecationReason in INFERENCE_HOSTS catalog.

Co-authored-by: Austin Liu <austinliu@Austins-MacBook-Air-3.local>
2026-07-27 17:24:31 -03:00
saroj_r
722de9d87e fix(dashboard): broken icon allignment in SegmentedControl.tsx (#8679) 2026-07-27 17:24:27 -03:00
backryun
116d583972 refactor(sse): declare the ArrayBuffer backing on media byte producers (#8665)
Six declarations spell a byte buffer as bare `Buffer` / `Uint8Array`. Without
its type argument that widens to `ArrayBufferLike`, which also admits
`SharedArrayBuffer` — so the value is rejected at every Web API boundary it is
actually passed to: `BodyInit` for `new Response(...)` and `BlobPart` for
`new Blob([...])`.

Every one of them is already ArrayBuffer-backed at runtime. `hexToBytes()`
allocates with `new Uint8Array(len)`; `synthesizeGtts()` and `pcmToWav()` return
`Buffer.concat(...)`; `fetchRemoteImage()` returns
`Buffer.from(await response.arrayBuffer())`; `readPageResponseBody()` returns
`Buffer.from(body)`, which copies. The declarations were simply less specific
than the values, so this states what the code already guarantees.

Same fix #8533 applied to the multipart and gRPC-web bodies.

Fixes 5 of the 208 `tsc -p open-sse/tsconfig.json` diagnostics with no new ones:
3 in audioSpeech.ts (MiniMax hex, gTTS, Vertex Gemini TTS), 1 in
imageGeneration.ts (Topaz Blob upload) and 1 in browserBackedChat.ts.

Refs #8484
2026-07-27 17:24:23 -03:00
Dizzle
4f3971f7ac fix(providers): handle space-separated search queries via matchesAnyToken (#8660)
Consolidates PR #8660 (matchesAnyToken with full-match priority over
token-level OR fallback) with the existing Turkish search normalization
shipped on release/v3.8.49.

The PR adds a new matchesAnyToken helper used by the providers search
to allow space-separated queries (e.g. 'pollinations sambanova') to
match when any of the tokens is present. Full query match takes
priority so that an exact 'pollinations sambanova' query still hits
even if a partial token would have been ambiguous.

The unit test file gains 12 PR-side matchesAnyToken tests on top of
the 6 release-side Turkish-normalization tests covering the same
function, totaling 27 tests.

Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-27 17:24:04 -03:00
Diego Rodrigues de Sa e Souza
10115000d8 perf(api): skip the full catalog build for quota-exclusive keys (#8771)
buildUnifiedModelsResponseCore builds the entire catalog first — every provider's
models, the auto/* combos with their candidate scoring, the embedding/image/rerank/
audio/moderation/video/music registries, the OpenRouter catalog, custom models —
and only at the very end, once the caller turns out to be scoped to a quota pool
(allowedQuotas non-empty), discards all of it and returns the pool's qtSd/* combos
instead.

Measured on a 1 vCPU shared-quota host: ~1.2s of CPU per cold build to return 10
models, and 4.4-7.1s under contention. Claude Code's gateway model discovery aborts
at 3s, so on a busy host the discovery silently falls back.

Everything the quota path needs (`combos`, `timestamp`, `buildComboCatalogMetadata`)
already exists before the expensive loops start, so resolve the key metadata there
and return early.

Extracts two helpers into ./catalogResponse so the short-circuit and the full build
share one implementation instead of two copies that drift:

- applyCatalogPostFilters — the chain that runs AFTER the API-key filter
  (configuredOnly, claude effort variants, no-thinking variants, cc-discovery
  mirrors, synced effort variants, dedupe)
- finalizeCatalogResponse — enrichment + the codex `models: []` compatibility
  field + the response envelope

That chain is not optional for the quota path: the cc-discovery mirrors are exactly
what lets Claude Code see a quota pool's models, and a first draft of this change
dropped them by returning before it. The regression is now pinned by a test that
was verified to fail without the call.

catalog.ts 1615 -> 1480 lines.

TDD: tests/unit/quota-exclusive-catalog-short-circuit.test.ts uses the OpenRouter
catalog fetch as the observable — it is part of the full build and reaches the
network, so a request that performs it did the whole build. The quota request runs
first, while OpenRouter's 24h cache is still cold, otherwise a cached second call
would make the assertion vacuous. Red before (1 fetch), green after (0), and a
normal key still fetches.

Behaviour guards green: quota-exclusive-catalog-4806 (2), quota-key-models-route
(18), cc-discovery-aliases-catalog (9), catalog-helpers-extraction (12),
cc-compatible-model-catalog (1), instrumentation-warm-catalog-cache (3),
catalog-pricing-surface-8018 (3), apikeypolicy-quota-only (6). typecheck, lint and
the file-size gate clean.
2026-07-27 13:59:20 -03:00
Diego Rodrigues de Sa e Souza
5f365bae7c feat(db): let the migration runner scan extra namespaced directories (#8770)
The runner reads exactly one directory and records the bare numeric prefix as the
version, so the numeric slots are a single global namespace. Any distribution that
ships its own migrations next to the upstream set has to draw from that same range
while upstream keeps appending to it — and when both sides claim a number, the
runner records one name for it and treats the other as already applied. That
migration then never runs, silently, on every already-provisioned database.

OMNIROUTE_EXTRA_MIGRATIONS_DIRS registers additional directories as
`namespace=dir` entries separated by the platform path delimiter. Files found
there are recorded as `<namespace>-<number>` (e.g. `ee-134`), a version space that
cannot collide with the upstream numeric one, and they are applied after the core
set. Unset — the default, and the only case for a plain install — nothing changes:
no filesystem access, identical behaviour.

Misconfiguration throws instead of being skipped. A malformed entry, a namespace
outside [a-z][a-z0-9]*, a duplicate namespace, or a directory that does not exist
aborts startup, because silently missing schema is the exact failure this exists to
prevent. Two files sharing a number inside the SAME namespace still collide and
throw, mirroring the runner's own guard.

Also fixes an inconsistency the tests surfaced: a missing core directory returned
early and took the extra directories down with it. They are an independent set.

The version-namespaced strings need no further plumbing — the applied set, the gap
reconciliation and the name-mismatch check all key on the version string, and the
numeric-only paths (`Number.parseInt`, the "032"/"041"/"042" special cases,
`isSchemaAlreadyApplied`) ignore them by construction.

11 new tests; the 7 neighbouring migration suites stay green (62 tests).
2026-07-27 12:56:06 -03:00
SteeleHu
cc53f45749 Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:30:54 -03:00
Austin Liu
899d19881f Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:30:39 -03:00
fuko2935
d78a836d3c Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:30:33 -03:00
Austin Liu
4a332fe2bd Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:30:22 -03:00
Austin Liu
419f8b4845 Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:30:18 -03:00
Austin Liu
7e2c687517 Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:30:06 -03:00
backryun
b59127ea95 Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:29:49 -03:00
backryun
da24c58c3a Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:29:44 -03:00
backryun
847d1ad84c Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:29:33 -03:00
backryun
0cf8223b27 Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:29:28 -03:00
Diego Rodrigues de Sa e Souza
ed7db3ee5f feat(dashboard): copy-paste settings.json block for Claude Code discovery (#8722)
The discovery-alias info button explains the gate and links to the flag, but the
operator still had to assemble the Claude Code config by hand from the guide.
Render it instead, on the same Claude tool card, from the base URL the card has
already resolved (custom override included, normalized — no /v1, no trailing
slash), with a copy button.

The key slot holds a placeholder, never the real key: this card renders before a
key is necessarily selected, and a real key in copyable text is an easy way to
leak one into a screenshot or a pasted snippet.

buildClaudeDiscoverySettingsSnippet is a pure builder in claudeCliConfig so the
shape is unit-tested (7 cases, including that no `sk-` ever reaches the output
and that an invalid CLAUDE_CODE_AUTO_COMPACT_WINDOW is dropped rather than
emitted). Guide gains the matching section; the five new strings are translated
for pt-BR and vi (the locales with strict parity/marker tests) and marked in the
rest, per the repo's convention.
2026-07-26 18:22:00 -03:00
Diego Rodrigues de Sa e Souza
cd9a631464 feat: Claude Code discovery aliases (surface non-Claude models in the /model picker) (#8666)
* feat(db): cc discovery alias gate storage + EXPOSE_CC_DISCOVERY_ALIASES flag

Adds the gate for claude/<provider>/<model> discovery-alias mirror ids on
the /v1/models catalog: a new runtime feature flag (env forces on and wins
over the dashboard DB override), per-provider and per-model "on"/"off"/null
overrides stored in key_value under the ccDiscoveryAliases namespace, and a
pure precedence resolver (model > provider > global). Catalog wiring is a
separate follow-up task; this only lands the gate + storage.

* feat(sse): synthesize claude/ discovery aliases for the model catalog

* feat(api): advertise cc discovery aliases on /v1/models behind the 3-level gate

* feat(sse): resolve claude/ discovery aliases on the request path

* fix(sse): import getComboByName from db/combos, not the localDb barrel

* fix(sse): cover custom-node prefixes and the Codex WS bridge in cc discovery alias resolution

* feat(dashboard): cc discovery alias toggles + flag-screen env warning

Adds the operator-facing UI/API layer for the Claude Code discovery-alias
gate (claude/<provider>/<model> mirror ids on /v1/models): REST endpoint
for provider/model overrides, a provider-detail card with 3-state
(inherit/on/off) toggles, an info button on the Claude Code tool card
linking to Feature Flags, and an env-source warning on the
EXPOSE_CC_DISCOVERY_ALIASES flag card when it's forced on via env.

* feat(api): cc discovery usage metrics

* fix(api): record cc alias metric in the production wrapper + atomic counter upsert

* docs: document cc discovery aliases (Claude Code guide + feature flag catalog)

* fix(sse): don't mirror built-in auto/* combos as discovery aliases (advertised-but-unroutable)

* i18n(vi): translate the discovery-alias strings instead of shipping placeholders

vi is the one locale with a strict "no internal missing markers" test, so the 17
__MISSING__ entries this branch added (the provider ccAlias panel, the info
button, the feature-flag description and the env warning) would have turned that
test red the moment the base itself was repaired. Translated, keeping every ICU
placeholder ({modelId}, {error}) and the literal claude/<provider>/<model> id
shape intact.

* chore(quality): raise the frozen caps this feature legitimately grows

catalog.ts 1615 -> 1639: the alias synthesis is wired into the catalog builder,
which is where the per-key-filtered list is assembled — the only place the mirror
entries can be appended after model hiding has been applied.

localDb.ts 808 -> 810: two re-export lines for the new ccDiscoveryAliases db
module, which is exactly what the "Adding a New DB Module" recipe prescribes.

* refactor(dashboard,api): keep the complexity ratchets flat

The feature added four cyclomatic violations and one cognitive one, which the
ratchets reject — the baseline only moves when a metric improves. Split the new
code instead:

- appendCcDiscoveryAliases: the four skip-guards become isMirrorableId().
- resolveCcDiscoveryAliasStripWith: alias parsing and gate resolution become
  parseCcAliasTarget() and resolveGateFor(), replacing a chain of ternaries that
  each re-tested isComboAlias.
- FeatureFlagCard: the env-precedence warning becomes its own component instead
  of a conditional branch inside an already-large render.
- ProviderCcAliasSection: the loader moves to useCcAliasData(), and the override
  list and add-row become ModelOverrideList / AddOverrideRow, bringing both
  oversized functions back under the 80-line rule.

Behavior unchanged — the 74 discovery-alias tests pass untouched. Both ratchets
now sit exactly at baseline (2188 / 971).
2026-07-26 17:19:19 -03:00
Diego Rodrigues de Sa e Souza
6706d5ff7d fix(api): serve /v1/models stale-first and sanitize its error bodies (#8703)
* fix(api): serve the model catalog stale-first and sanitize its error body

A client with a short discovery timeout (Claude Code allows 3s) hit a full
catalog rebuild — 290 providers plus SQLite reads — every time the memoized
entry expired, and got an empty model picker with no error. Serve an expired
entry immediately and revalidate in the background, bounded by a staleness
window so a permanently failing refresh cannot pin an old catalog forever.
Only a cached 200 is eligible; a state change still drops the cache outright.

The builder's catch block also returned the raw error message in the response
body. Route it through the shared sanitizer (hard rule #12).

* fix(api): reject a failed catalog refresh instead of resolving it stale

catalogInFlight is shared with the cold path, so resolving the background
refresh with the stale entry handed it to callers that had already aged past
CATALOG_STALE_WHILE_REVALIDATE_MS — a stale 200 they were no longer entitled to,
with a build failure disguised as success. The refresh now rejects; the stale
path never awaits it (the rejection is pre-handled, so it can never surface as
an unhandledRejection) and a cold-path caller that joins it gets the sanitized
500. A failed refresh still leaves the cached entry untouched.

Also sanitize the core builder's own catch — that is the realistically reachable
500 for this endpoint, and it still returned the raw error message (hard rule
#12); keep the cache-key format private to the module by having the two test
hooks take the Request and derive the key themselves.

* refactor(api): extract the model-catalog response cache into its own module

The stale-while-revalidate work pushed catalog.ts from 1615 to 1745 lines, past
its frozen size cap. Raising the cap on a file already flagged as too large is
the wrong answer: the caching layer is a self-contained concern (coalescing,
TTL memoization, staleness window, background refresh) that only needs a builder
callback from the catalog module.

catalogCache.ts now owns the maps, the cache key, the state-change invalidation,
the header merge, the background refresh and the test hooks; catalog.ts keeps
auth, the builder, and the error shape, and re-exports the hooks so the existing
tests keep importing them from where they always did.

Net effect: catalog.ts 1745 -> 1513, i.e. 102 lines below the cap it was frozen
at, and the test-only surface no longer sits in the production catalog module.
No behavior change — all 281 tests across every suite importing catalog.ts pass,
including the #6408 one-builder-run guard.
2026-07-26 16:59:36 -03:00
Diego Rodrigues de Sa e Souza
7f8a59ac66 fix: repair five base-red failures on release/v3.8.49 (#8706)
* fix: repair five base-red failures on release/v3.8.49

Every PR cut from this branch fails CI on the branch's own breakage. Five
distinct causes, none introduced by the PRs that trip over them:

1. dast-smoke / Turbopack build — src/sse/handlers/chat.ts imported
   PROVIDER_BREAKER_FAILURE_STATUSES twice in one statement. A duplicate import
   specifier is an ECMAScript syntax error, so the production build never
   compiled. Introduced by #8258, whose export fix landed on top of an import
   that already existed.

2. Unit Tests — the #8393 verified-cooldown bypass was renamed
   exactCooldownVerified -> exactCooldownIsUpstreamReset during the #8254
   conflict resolution, which also dropped the flag at the markAccountUnavailable
   call site entirely. The rename left the test passing the old key (so the flag
   was silently ignored and a verified upstream reset got clamped back to
   maxCooldownMs), and the dropped call site meant no real caller set it at all.
   Align the test on the surviving name, restore the call site, and restore the
   doc comment explaining #6863 vs #7940.

3. Unit Tests — #8526 added four common.* keys to en.json only, breaking the
   strict key-parity tests for pt-BR and vi. Translated into all 42 locales.

4. Unit Tests — vi carried 17 __MISSING__ placeholders from #8354 and #8463, and
   vi is the one locale with a no-placeholder test. Translated.

5. No new ESLint warnings — four suppressed `any`s in
   tests/unit/combo-routing-engine.test.ts no longer exist, and ESLint exits 2 on
   stale suppressions. Pruned (271 -> 267); no other entry moved.

Also regenerates skills/cli-backup-sync/SKILL.md, which still documented the
`backup status` flags #8512 removed — the merge-integrity gate compares the
generated output against the tree.

Not fixed here: the env/docs contract (NEXT_PUBLIC_OMNIROUTE_BASE_PATH and
OMNIROUTE_BACKUP_SCHEDULE_JOB_INTERVAL_MS missing from .env.example), which #8690
already covers, and the quality baselines, which #8686 covers.

* fix(quality): keep prettier off the generated SKILL.md files

check:agent-skills-sync diffs the generator's output against the tree byte for
byte, but lint-staged runs prettier over any staged *.md — and prettier inserts a
blank line after the frontmatter that the generator does not emit. Committing a
regenerated skill therefore made the gate fail again on the very file that was
just brought back in sync. The 44 untouched skills only escape this because they
never pass through lint-staged.

The generator is the formatter of record for these files, so ignore them.

* fix(i18n,quality): drop the stale zh-TW key; raise the auth.ts frozen cap

#8463 renamed `oauthModal.googleOAuthWarning` away but left the old key behind in
zh-TW, so the "the stale googleOAuthWarning key is GONE from every locale" guard
fails on the branch. Removed it.

The auth.ts frozen line cap goes 2486 -> 2492. Restoring the dropped
exactCooldownIsUpstreamReset call site costs 7 lines, and staging the file makes
lint-staged reformat four pre-existing over-100-column lines to prettier's rule —
unavoidable without bypassing the hook, which hard rule #10 forbids. The file
still sits 12 lines below where the cap was set relative to its actual size.
2026-07-26 16:16:42 -03:00
Diego Rodrigues de Sa e Souza
1f04333a19 merge: resolve conflicts for #7904 local corpus context (#8685)
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-26 12:29:13 -03:00
Diego Rodrigues de Sa e Souza
4cd1bbc9f5 fix(sse): export PROVIDER_BREAKER_FAILURE_STATUSES — fix ReferenceError in chat.ts (base-red slice 4) (#8258)
* fix(sse): export PROVIDER_BREAKER_FAILURE_STATUSES so chat.ts stops throwing ReferenceError

Base-red slice 4 (single root cause across the whole handleChat cluster).

src/sse/handlers/chat.ts:1273 references PROVIDER_BREAKER_FAILURE_STATUSES to decide
whether an all-rate-limited provider result should trip the provider breaker, but the
constant was only a FILE-LOCAL const in chatPredicates.ts (a refactor extracted it out
of chat.ts and never re-exported it). Every request that reached that branch threw
`ReferenceError: PROVIDER_BREAKER_FAILURE_STATUSES is not defined`, so the global
fallback and breaker-gate paths blew up — surfacing as "All models failed |
PROVIDER_BREAKER_FAILURE_STATUSES is not defined" and breaking the handleChat coverage
tests (combo-error passthrough, 503 for cooled-down/open-breaker, budget-error, model
cooldown, body-derived retry-after, non-JSON rate-limit bodies).

Fix: export the const from chatPredicates.ts and import it in chat.ts (one canonical
definition, restoring the pre-refactor behavior).

Validated: chat-route-coverage 15/0 (was 12/3), chat-cooldown-aware-retry 6/0,
chat-rate-limit-body-lock 2/0; breaker guards (7907, combo-breaker-429, openrouter-6842)
unchanged; typecheck:core clean.

* test(nvidia): read PROVIDER_BREAKER_FAILURE_STATUSES from chatPredicates.ts

Same root cause as the chat.ts import fix in this PR: the const was extracted out
of chat.ts into chatPredicates.ts, so the nvidia-quota Phase-1 guard (which greps
the source for the `= new Set([...])` declaration to prove 429 was not added to the
breaker classification) must read chatPredicates.ts, not chat.ts. Now 13/0.

---------

Co-authored-by: Probe Test <probe@example.com>
2026-07-26 12:11:42 -03:00
Diego Rodrigues de Sa e Souza
7faec9339d fix(resilience,translator): three release/v3.8.49 base-red regressions + eslint baseline — conflict resolved (#8254)
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
2026-07-26 12:11:35 -03:00
Gustavo Costa
763ed82d3c fix(guardrails): Vision Bridge describe-fallback ignores unreachable candidates (#8433)
* fix(guardrails): Vision Bridge describe-fallback ignores unreachable candidates

getVisionCapableModels() scanned the entire static PROVIDER_MODELS catalog
for a describe-fallback model without checking whether the provider has a
usable active connection on this instance. On an instance with no `openai`
provider connected, this let the hardcoded default `openai/gpt-4o-mini` win
selection every time, the describe call would fail, and
replaceImageParts()'s describe-failure fallback (#4012) intentionally
preserves the raw image part — which then reaches a non-vision backend and
gets rejected with an opaque upstream error (e.g. "unknown variant
`image_url`, expected `text`").

Extract the credential-usability check already used by the whole-request
reroute path (visionBridge.ts's hasUsableCredentialsForModel /
isProviderConnectionUsable) into a shared visionBridgeCredentials.ts module,
and apply the same check to the describe-fallback candidate list in
visionBridgeRouter.ts. A confirmed-unusable connection (`false`) excludes a
candidate; an indeterminate result (`null`, e.g. no DB) fails open to
preserve existing behavior.

getVisionCapableModels/getBestVisionModel/getFallbackModels become async to
support the credential lookup; call sites and the existing unit test suite
are updated accordingly, plus new coverage for the exclusion behavior.

* fix(guardrails): fix flaky credential-mock race and move Vision Bridge router tests to a CI-blocking runner

The two new assertions added in this PR (excludes a candidate with no
usable active connection / selects a credentialed candidate over an
uncredentialed one) were correct — the failure was a genuine Vitest
race: getVisionCapableModels() fans out to hasUsableCredentialsForModel()
once per catalog entry via Promise.all, and dozens of concurrent
first-load `await import("@/lib/db/providers")` calls for the same
specifier under vi.mock() nondeterministically resolved against the real
module instead of the mock for some callers. Memoize the dynamic import
in visionBridgeCredentials.ts so it resolves exactly once and is reused,
which removes the race entirely (and is cheaper at runtime too).

Also: tests/unit/guardrails/visionBridgeRouter.test.tsx was never
collected by any CI-blocking gate — `test:unit`'s guardrails glob is
`*.test.ts` only, and `test:vitest` (vitest.mcp.config.ts) doesn't
include this directory; only the advisory `test:vitest:ui` picked it up.
Moved the suite to visionBridgeRouter.test.ts under node:test, threading
an optional `deps.hasUsableCredentials` injection point through
getBestVisionModel()/getFallbackModels() (consistent with the existing
deps pattern in visionBridge.ts) since this project's native test runner
has no supported ESM module-mocking mechanism.

Running the full guardrails suite together also surfaced that this PR's
own credential-exclusion feature silently broke the pre-existing
vision-bridge-callmodel.test.ts fallback-retry test: with zero seeded
provider connections in that test's isolated DATA_DIR, every fallback
candidate is now confirmed-unusable and excluded, leaving no fallback to
retry. Seeded one credentialed connection there so the fallback-retry
mechanics stay independent of the (unrelated) credential filter.

Refs #8433

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-26 12:11:19 -03:00
Diego Rodrigues de Sa e Souza
a61ed5deb1 fix(oauth): actionable guidance for LAN-origin loopback mismatches (codex + Antigravity) (#8463)
* fix(oauth): explain the LAN-IP loopback mismatch with an actionable panel

#8046 already stops the doomed login when a PKCE_CALLBACK_SERVER_PROVIDERS
provider (codex / xai-oauth / grok-cli) is connected from a LAN IP, but it
explained itself as one long English sentence rendered in the generic red
"Connection failed" step. Two concrete problems with that:

- the operator had to parse prose to work out WHICH ports to forward, and the
  command shipped with `<port>` / `<omniroute-host>` placeholders to resolve
  by hand;
- it forwarded a single port. Both are required: the dashboard port is what
  makes the origin true-localhost (a LAN origin never reaches the
  callback-server branch at all), and the provider's fixed callback port is
  where the browser is actually sent back to. Forwarding either one alone
  still fails.

buildPkceLoopbackMismatchHint() now returns the diagnosis as structured data
with the detected host and both ports already filled in, and a dedicated
OAuthLoopbackMismatchPanel renders it as: what happened -> how to fix, in
three numbered steps with copy-to-clipboard fields. No "Try again" button —
retrying the same origin fails identically. The panel yields to the
paste-token tab so grok-cli (which is in both provider sets) never stacks the
two views.

The flat warning string stays exported for non-UI callers.

docs: REMOTE-MODE.md gains a "Connecting Codex / Grok on a remote install"
section with the fixed-callback table and the two-port tunnel, mirroring the
existing Antigravity section.

i18n: 9 new oauthModal keys, hand-written for en + pt-BR and propagated to the
remaining 40 locales as `__MISSING__:` sentinels (runtime falls back to the
clean English value per #7258).

* fix(oauth): correct the Antigravity remote-login guidance and drop the stale i18n copy

Same LAN-origin family as the codex fix in this branch, different mechanism and a
worse failure mode.

Google providers (antigravity / agy) have no fixed foreign port: OAuthModal builds
`http://127.0.0.1:<dashboardPort>/callback`. On a LAN origin that 127.0.0.1 is the
BROWSER's machine, and Google's firstparty/nativeapp consent only releases the code
once the loopback is reachable from the approving browser. When it is not, the consent
never redirects at all — it hangs. So unlike an ordinary provider there is no error
page and no callback URL in the address bar.

That made the existing copy actively wrong. `googleOAuthWarning` was corrected when
the login helper shipped (#5203), but a changed English value does not invalidate
existing translations and `i18n:sync-ui` only fills keys that are ABSENT, never ones
that are STALE — so 39 of 43 locales (pt-BR, pt, es, de, fr, ja, zh-CN, …) kept the
original "wait for the redirect, copy the full URL and paste it below", instructing a
flow that cannot complete. The drift gate that should have caught this is a no-op:
`check-translation-drift.mjs` needs `.i18n-state.json`, which is not in the repo, and
it runs `--warn`.

Because the key's MEANING changed, it is renamed rather than edited — a new key cannot
inherit a stale translation. `googleOAuthWarning` is removed from all 43 locales and
replaced by 7 `googleLoopback*` keys, hand-written for en + pt-BR and marked
`__MISSING__:` elsewhere so the runtime falls back to correct English (#7258).

UI: `OAuthGoogleLoopbackNotice` states what is happening and surfaces both real
remedies with the detected host and port filled in — the local login helper
(recommended; its blob is what the Step 2 field accepts) and a single-port SSH forward.
It also REPLACES `remoteAccessInfo` for this family instead of stacking on top of it:
that notice promises an error page whose URL you copy, true for ordinary providers and
false here.

`agy` deliberately gets no helper command. bin/cli/commands/login.mjs pins
PROVIDER = "antigravity" and parsePastedCredentials() rejects a blob whose embedded
provider does not match the route provider, so advertising the helper there would send
the operator to a blob guaranteed to be refused. It keeps the tunnel path.

Refactor: the shared `resolveDashboardPort` / `buildSshLocalForward` helpers move to
`loopbackTunnel.ts`, used by both hint builders. The codex builder's behaviour is
unchanged (its 11 tests still pass untouched).

docs: REMOTE-MODE.md notes that the dashboard now surfaces the remedies, states that
one forward is enough for Antigravity (contrasting the two-port codex case), and aligns
Option B's command on 127.0.0.1 to match what the UI generates.

---------

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-26 12:11:14 -03:00
Alan Vb
da20b96dc0 feat(providers): weekly quota for xAI OAuth (Grok) (#8471)
* feat(providers): weekly quota for xAI OAuth (Grok)

Live weekly credit pool for xai-oauth (alias xao) via the shared
cli-chat-proxy billing API (creditUsagePercent), using the connection OAuth access token.

- Export fetchGrokBillingWithToken from grokQuotaFetcher for reuse
- xaiOauthQuotaFetcher: 60s cache, fail-open, preflight + monitor
- Provider Limits allowlist and weekly window

* test(providers): cover xai-oauth usage dispatch + fix changelog

Address PR review:
- fix changelog file & rename to 8471-xai-oauth-weekly-quota.md
- export getXaiOauthUsage via __testing
- add xai-oauth-usage.test.ts

---------

Co-authored-by: allanvb <allanvb@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-26 12:11:02 -03:00
Markus Hartung
f6f0303be4 feat(log): Added new visual scrolling log page (#8354)
* feat(log): Added new visual scrolling log page

* chore(quality): rebaseline sections.ts own-growth for #8354 (logs-timeline sidebar item)

* feat(log): direct-link a request from the scrolling timeline

Clicking a request bar now sets ?id= on the URL (matching the regular
request log page), and the timeline opens the deep-linked request on
mount. The open/close handlers arm the same guard so a stale
initialSelectedId (router.replace() commits the URL after the render it
triggers) can never reopen the modal right after the user closes it.

* fix(dashboard): propagate logsTimelineSubtitle across all 43 locales

en.json was missing the logsTimelineSubtitle key entirely, breaking the
default locale for the new /dashboard/logs/timeline sidebar entry. Add
the real English string to en.json, add __MISSING__: placeholders to
the 11 locales that lacked the key outright, and convert the 30 locales
that had copied the English text literally to the repo's __MISSING__:
convention for untranslated strings.

Also pause the RequestTimeline 2s poll while the tab is backgrounded
(document.visibilityState), matching the existing pattern in
RequestLoggerV2 and UsageStats.

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

* fix(i18n,sidebar): add missing logsTimelineSubtitle + update sidebar tests

Address maintainer feedback on #8354:
- Add logsTimelineSubtitle key to en.json + 11 locales (ar, az, bg, bn,
  cs, da, de, es, fa, fi, fr) that were missing it
- Add logs-timeline to sidebar-visibility.test.ts expected arrays
- Add logs-timeline to sidebar-monitoring-reorg.test.ts logs group
- Add compression-exclusions to sidebar-visibility.test.ts (pre-existing)

* feat: add touch support for mobile panning and pinch-to-zoom on timeline

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-26 12:10:57 -03:00
Dizzle
6c93e74f3a feat(jobs): execute the backup-schedule.json cron server-side (#8513) (#8517)
Co-authored-by: Max <maxmad64@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-26 12:10:53 -03:00
Rouzbeh†
8115867c2d fix(dashboard): include OMNIROUTE_BASE_PATH in displayed API base URL (#8514)
* fix(dashboard): include OMNIROUTE_BASE_PATH in displayed API base URL

When OmniRoute is served under a reverse-proxy subpath (OMNIROUTE_BASE_PATH),
the Endpoints UI built display URLs from window.location.origin alone and
appended /v1, producing https://host/v1 instead of https://host/omniroute/v1.

- Prefer NEXT_PUBLIC_BASE_URL when it already includes a non-root path
- Append NEXT_PUBLIC_OMNIROUTE_BASE_PATH (mirrored from OMNIROUTE_BASE_PATH
  at build time) when resolving a bare public origin
- Document subpath display behavior in .env.example
- Add unit coverage for basePath-aware resolution

* docs(changelog): add fragment for #8514 display basePath fix

---------

Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-26 12:10:49 -03:00
Joshim Uddin
63341f2aed feat(combos): add select all / unselect all in Browse Catalog (#8526)
* feat(combos): add select all / unselect all in Browse Catalog

* fix(dashboard): guard combo Select all + test the real batch handlers (#8526)

Select all had no cap — with "Show configured only" off, or a large
provider catalog, one click could add hundreds of models to a combo.
ModelSelectModal now confirms above SELECT_ALL_CONFIRM_THRESHOLD (20)
before batch-adding, matching the native confirm() pattern already
used for bulk/destructive actions elsewhere in the dashboard.

Also extracts ComboFormModal's handleAddModels/handleDeselectModels
batching logic into computeBatchAddModelSteps/computeBatchDeselectModelSteps
(src/lib/combos/builderDraft.ts) so unit tests exercise the real
implementation instead of a hand-maintained mirror that could drift
from the component and stay green while production code broke.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-26 12:10:44 -03:00
nguyenha935
d4b9ce6016 fix(kiro): harden auth flows, quota lookup, and model discovery (#8565)
* fix(kiro): fetch builder id quota without profile arn

* fix(kiro): harden auth imports polling and model discovery

* fix(kiro): preserve auth identity and OAuth polling semantics

* docs(changelog): add Kiro auth and model discovery fix

---------

Co-authored-by: Nguyễn Thanh Hà <nguyenha@Mac-mini-M4.local>
Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com>
2026-07-26 03:53:47 -03:00
Adrian Rogala
2614e8a223 feat(cli-tools): add all Hermes Agent auxiliary model roles (#8543)
* feat(cli-tools): add all Hermes Agent auxiliary model roles

Extend HERMES_AGENT_ROLES from 7 to 18 slots to match the full
auxiliary.* set in Hermes Agent config.yaml:

- add: mcp, title_generation, memory_query_rewrite, tts_audio_tags,
  triage_specifier, kanban_decomposer, profile_describer, goal_judge,
  curator, monitor, background_review
- reorder: web_extract before compression (match upstream docs)

Backend generator/reader are generic on auxiliary.<role> — only the
role catalog, UI card, and i18n (en + pl) needed updating.

* fix(i18n): translate the 12 new Hermes auxiliary roles into vi and pt-BR

The 22 new en.json keys landed only in pl.json, breaking the vi and pt-BR
key-parity guards. Adds the same keys with real translations (no placeholder
strings), keeping both parity assertions exact.

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

* test(cli-helper): guard HERMES role catalog parity between backend and UI

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

---------

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-26 03:53:36 -03:00
Prudhvi Vuda
ac5691f04c fix(providers): resolve native vision for path-shaped multimodal model ids (#8495)
* fix(providers): resolve native vision for path-shaped multimodal model ids

Leaf-id static/registry metadata now wins over synced attachment=false without
modalities, so cp/cline-pass/kimi-k3 forwards images natively.

Closes #8032

* fix(providers): scope path-shaped leaf lookup to vision only

Move leaf MODEL_SPECS fallback out of shared getStaticSpec() so
aihorde/deepseek/deepseek-v4-flash no longer inherits DeepSeek tool-calling
metadata (#8212). Vision still resolves via getVisionStaticSpec().

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

---------

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-07-26 03:53:21 -03:00
NOXX - Commiter
26d50f1913 feat(adobe-firefly): reference image attach + /v1/images/edits (follow-up #8006) (#8510)
* feat(adobe-firefly): reference image attach + /v1/images/edits (follow-up #8006)

Upload source images to Firefly storage (POST /v2/storage/image) and attach
them as referenceBlobs on generate-async, matching live firefly.adobe.com
captures (usage:general for nano multi-ref; usage:subject for gpt-image).

Also wire built-in adobe-firefly through OpenAI-compatible POST /v1/images/edits
(multipart or JSON data URLs, up to 4 refs) so Media edit-with-references
and Open WebUI image-edit hit the same path as image2image generate.

Unit suite: tests/unit/adobe-firefly.test.ts 41/41.

* test(api): add route-level coverage for Adobe Firefly /v1/images/edits + fix typecheck/file-size drift

Covers the referenceBlobs upload path, the 4-reference cap error, and the
credentials/rate-limit branches added to the /v1/images/edits route for
adobe-firefly (#8510). Also fixes a Buffer/BodyInit typecheck mismatch in
uploadAdobeFireflyImage and corrects the adobeFireflyClient.ts file-size
baseline entry to match the gate's actual LOC count (it counts the trailing
newline, so the frozen value is 2317, not 2316), plus a testFrozen entry for
adobe-firefly.test.ts's own +159 line growth from this PR. Moves the
handleAdobeFireflyImageGeneration re-export out of the middle of the import
block in imageGeneration.ts for readability.

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

* fix(security): restore bounded JWT regex quantifiers dropped by edit-route refactor

The edits-route extraction (test commit 62785e972f) had accidentally
reverted 4 JWT-extraction regexes in adobeFireflyClient.ts from the
bounded {1,4096} quantifier back to unbounded +, undoing the ReDoS fix
from #8173 (CodeQL #754/#755/#756). Restored the bounded quantifiers;
behavior is unchanged (45/45 adobe-firefly tests still pass), only the
backtracking bound is back in place.

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

---------

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-07-26 03:53:10 -03:00
Diego Rodrigues de Sa e Souza
fae10668ba fix(api): expose responses-format models on all VS Code Ollama listing routes (#7587) (#8564)
isUsableChatModel() was copy-pasted into 5 vscode listing routes. PR #7012
widened only models/route.ts to accept api_format "responses"/"openai-responses"
alongside "chat-completions"; the other 4 copies (token+raw api/tags and
api/show) still rejected anything that wasn't literally "chat-completions",
silently dropping Codex-discovery-synced GPT models (apiFormat "responses")
from the Ollama-compatible /api/tags endpoint VS Code's "Ollama" provider
import flow actually calls.

Extracted the predicate into a single shared module
(vscode/[token]/usableChatModel.ts) imported by all 5 routes so this
one-fixed-four-left-behind drift cannot recur.

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-26 03:53:02 -03:00
Diego Rodrigues de Sa e Souza
a784c52d34 fix(api): warn (never reject) when a combo name shadows a real model id (#8530) (#8563)
POST /api/combos and PUT /api/combos/[id] had zero validation or
observability when a combo name collided with a real model id, and
sseModelService.getComboForModel() always resolves the combo first. That
combo-first precedence is not a bug: #6940 documents a combo named after a
bare model id (e.g. `gpt-5.5`) as the supported mechanism for per-model
provider fallback, reusing the #3227/#3233 machinery and covered by
tests/unit/responses-combo-resolution-3227.test.ts and
tests/unit/combo-name-codex-responses-rewrite.test.ts. Hard-rejecting a
colliding name (as #8530's literal acceptance criteria requested) would
regress that documented workflow.

Instead, both routes now attach a non-blocking `warning` field
(`COMBO_NAME_SHADOWS_MODEL`) to the create/rename response when the name
collides with a real model id, and a new boot-time scan
(scanComboModelNameCollisionsAtBoot in src/instrumentation-node.ts) logs a
startup warning enumerating existing collisions — so an operator who hits
this by accident has a signal, while the #6940-sanctioned pattern keeps
working exactly as before.

New tests/unit/combo-model-name-collision-8530.test.ts proves both: the
sanctioned shapes (create/rename to a colliding name) still return
201/200 with the warning attached, and non-colliding names get no warning
field at all.

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-26 03:52:58 -03:00
Diego Rodrigues de Sa e Souza
7b3ef477f3 fix(providers): persist runtime-discovered Antigravity projectId to the connection (#8491) (#8562)
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-26 03:52:54 -03:00
MumuTW
4c9292e66e chore(i18n): normalize zh-TW terminology and sync stale README figures (#8554)
The zh-TW catalog was machine-translated with mainland-habit vocabulary and
simplified->traditional conversions that picked the wrong homophone, and its
README still advertised the v3.7-era figures.

Terminology (docs/i18n/zh-TW/ + src/i18n/messages/zh-TW.json):
- wrong-character conversions: 上遊->上游, 後臺->後台, 儀錶板->儀表板
- mainland habits: 默認->預設, 緩存->快取, 模塊->模組, 調用->呼叫,
  字符串->字串, 全局->全域, 文檔->文件, 響應->回應
- consistency: 供應商/提供商->提供者 (提供者 was already 71% dominant),
  型別->類型 for UI labels, 不活躍->未啟用

README figures synced to the English source: 231->290 providers,
17->19 routing strategies, 1.6B->1.53B free tokens, 50+->90+ free tiers,
11->40+ free forever, 87->104 MCP tools, 30->31 scopes.

Root cause — the generator's post-translation pass was a hardcoded list that
duplicated the glossary and was wired only into the deprecated
generate-multilang.mjs, so the active run-translation.mjs pipeline applied
nothing. Worse, its blanket /代碼/g -> 程式碼 rule would corrupt 控制代碼
(handle), 語系代碼 (locale code) and 錯誤代碼 (error code) on the next
regeneration.

Both scripts and the drift gate now share scripts/i18n/glossary-normalize.mjs,
driven by scripts/i18n/glossary/<locale>.json as the single source of truth.
Ambiguous terms carry blockedPrefixes so 型別->類型 can stay enforced without
mangling 模型別名 (model alias) or 基本型別 (a programming data type); terms
whose synonym is also a legitimate rendering elsewhere (代碼, 項目) are seeded
with no synonyms and documented instead of blanket-rewritten.

CI now runs the glossary gate for zh-TW alongside zh-CN.
2026-07-26 03:52:50 -03:00