#10248 changed the contract: a custom row for an id that already exists is the
operator-owned overlay for that model (catalog.ts:1330) — its explicitly stored fields
win over discovered metadata and the merged entry is flagged `custom`. Before #10248 the
duplicate was skipped, so the test asserted `custom === false` and started failing.
The stale expectation is corrected (not weakened) and an identity assertion is added:
the overlay must keep the catalog id rather than becoming a detached entry.
models-catalog-route.test.ts: 44 pass, 0 fail (was 43 pass / 1 fail).
`CustomModelEntry` never declared `outputTokenLimit`, but the DB persists it
(src/lib/db/models.ts) and the catalog reads it (src/app/api/v1/models/catalog.ts),
producing TS2551 under the open-sse typecheck gate.
Verified locally against release/v3.8.50 @ 90458a613c: TS2551 count in
models/catalog.ts goes 2 -> 0, and model-token-limit-catalog.test.ts passes 5/5
with the added max_output_tokens projection assertion.
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
* 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.
* feat(codex): converge OAuth fingerprints
* test(codex): preserve identity assertions
* fix(codex): preserve explicit off identity
* fix(codex): close fingerprint transport gaps
---------
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>
Fixes#10117 — countTextTokens can block the worker event loop for tens of
seconds when a Codex request carries a large base64 image payload, wedging
/healthz and every concurrent request.
- Strip base64 image data URIs before encoding (images are not text)
- Fast-path length guard: over 50k chars, skip the near-quadratic pure-JS
tokenizer and return the chars/4 heuristic
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* 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 <...> 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>
- Route: map the connection's providerSpecificData.baseUrl onto
credentials.baseUrl (resolveOcrCredentials) so azure-document-intelligence
connections resolve their endpoint the same way every other custom-endpoint
provider does (src/lib/providers/validation/*); previously handleOcr only
saw a baseUrl when a caller set it directly, so the DB-backed Azure
connection endpoint was never forwarded.
- v1OcrSchema.model is already a free-form string, no schema change needed.
- Docs: add the /v1/ocr provider table + example + Azure poll-flow note to
API_REFERENCE.md, and describe the provider/model prefix + async poll
behavior in openapi.yaml.
- Test: tests/unit/ocr-route-contract.test.ts covers getAllOcrModels/
parseOcrModel for both providers and resolveOcrCredentials's mapping.
pollOcrOperation now checks pollRes.ok and returns a sanitized 502
immediately (logging the upstream status via console.error) instead of
looping until the 30-attempt cap and surfacing a misleading timeout for
what was actually an auth/upstream error during polling.
The test's own title ("returns a sanitized 500") describes the new
behavior mandated by HR#12 (never leak err.message in a response body).
The old regex asserted the pre-sanitization leak (`OCR request failed:
socket closed`) as expected output, which contradicted its own title
and the sanitization this task intentionally introduced in
open-sse/handlers/ocr.ts. Scoped to this single assertion only.
- 12,141 strings translated to European Portuguese
- Built on top of the existing pt.json with full coverage of the v3.8.50 catalog
- Remaining ~440 strings are technical terms/brand names kept in English
Co-authored-by: DarkEsteves <DarkEsteves@users.noreply.github.com>
Perplexity moved the answer text out of `markdown_block` into
`workflow_block` (`intended_usage: "workflow_root"`), streaming it as
RFC-6902 patches whose `field` is `"workflow_block"` and whose paths
address `/steps/<n>/items/<m>/payload/text_payload/chunks/<k>`.
`extractContent` recognised neither shape. Two independent guards dropped
every answer frame:
- `isAnswerTextUsage("workflow_root")` is false, so the block loop
`continue`d before any accumulation.
- the diff guard skipped every patch whose `field !== "markdown_block"`.
The stream therefore ran to `COMPLETED` with an empty accumulator and the
executor surfaced `Provider returned empty content` (502) even though the
upstream SSE carried the full answer. Every model was affected — the
carrying block is model-independent — so the provider was unusable.
Adds `workflow_block` to `PplxBlock`, an `applyWorkflowDiff` patch
applier for the streaming path, and `applyWorkflowBlock` for a
materialized block on the terminal frame. Answer tracks are keyed per
step+item so concurrent items cannot overwrite each other's chunk
indices, and only `variant: "answer"` payloads are accumulated — search
queries, sources and "thinking" items stay out of the message.
Fixtures in the regression test are trimmed from a live capture
(pplx-auto, mode=copilot); replaying the full 96 KB capture through the
patched extractor yields the complete 247-char answer over 7 incremental
deltas, against an empty string before the fix.
Co-authored-by: Jeyhun F. Aslanov <jeyhun.f.aslanov@Jeyhuns-MacBook-Pro.local>
The direct Claude<->Gemini translator (claude-to-gemini.ts / gemini-to-claude.ts)
never persisted the thoughtSignature Gemini returns on functionCall parts, and
never re-attached one on the next turn. Gemini 3+/2.5 strictly reject a native
functionCall part with no signature (400), which surfaces whenever a combo falls
back onto a Gemini model mid-conversation (the fallback tool_use never went
through Gemini, so no signature exists for it).
- gemini-to-claude.ts: store the signature (keyed by tool_use id + connection
namespace) when Gemini's response carries one, mirroring the existing
gemini-to-openai.ts hub-path behavior.
- claude-to-gemini.ts: resolve a stored signature for historical tool_use
blocks; when none exists and the target model requires one, downgrade the
tool_use/tool_result pair to inert text instead of sending a signature-less
native part, matching the "context" fallback already used by the OpenAI hub
path (#3358) rather than the removed fake-signature injection.
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(providers): raise default provider probe timeout from 5s to 8s
The validationRead and modelsProbe presets in safeOutboundFetch.ts used a
fixed 5000ms timeout for the periodic credential health check and on-demand
connection test. Several real free-tier providers (Cerebras, Cloudflare AI
observed in practice) routinely take close to 5s to answer a lightweight
/models probe, which is indistinguishable from a real outage under that
budget — the connection flaps between "active" and "error" in the
dashboard/topology view purely from being near the edge of the timeout, not
from any actual failure.
Raised the default to 8000ms and made it configurable via
OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS (validated: falls back to 8000ms for
non-numeric or sub-1000ms values) so it can be tuned per-deployment without a
code change. validationWrite and modelsPagination presets are untouched.
Added tests/unit/safe-outbound-fetch-probe-timeout.test.ts covering the
default, env override, invalid-value fallback, and that the other two
presets are unaffected.
* docs(.env.example): document OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS
* Merge branch 'release/v3.8.50' into fix/provider-probe-timeout
Resolved merge conflict in .env.example: kept both Provider probe section (PR)
and Proxy/relay fetch section (release branch).
Added docs/reference/ENVIRONMENT.md entry for OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS.
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* test: reproduce model param filter close persistence
* fix(dashboard): persist model param filters on popover close (#8910)
ModelCompatPopover declared providerId/modelId in its props type but never
destructured them, so both param-filter fetches referenced undefined
identifiers (TS2304, frozen in the dashboard-typecheck baseline) and threw
into a silent catch. CustomModelsSection also never passed the two props.
- Destructure providerId/modelId; pass them from CustomModelsSection.
- Save pending block/allow drafts when the popover closes or unmounts, so an
outside mousedown no longer discards them.
- Read drafts from refs at save time and guard concurrent saves, avoiding
stale-closure payloads and duplicate PUTs.
- Keep dirty state and drafts on non-OK/failed GET or PUT instead of silently
clearing them; skip state updates after unmount.
- Provider-level block/allow, autoLearn, and other model entries are preserved;
an empty block+allow still removes only the selected model entry.
- Ratchet the three now-clean dashboard-typecheck baseline entries.
Compat-toggle and upstream-header paths are unchanged.
* fix(dashboard): avoid lost update and surface failed param-filter saves (#8910)
The close-time save could clear the dirty flag for a payload snapshotted
before the PUT resolved, silently discarding any keystroke that landed in
that window. Track a monotonic draft revision and only acknowledge the
revision that was actually written, re-running the save (bounded) otherwise.
A failed save previously stayed dirty to 'retry on a later close', but
reopening the popover reloaded server state and silently reverted the
draft. Keep a dirty draft for the same provider/model on reopen and show a
failure marker next to the saving indicator instead.
* fix(dashboard): protect dirty param-filter drafts from load-effect clobber (#8910)
The retained-draft guard in the param-filters load effect required
paramLoadedKeyRef to match the current target, but that ref was only
assigned after a successful GET. Any draft typed before a successful load
for that target was therefore unguarded, and the clean-slate write
overwrote both the text and the dirty flag:
- a draft typed while the INITIAL load GET was still in flight was
overwritten and its dirty flag cleared, so the close-path save became a
no-op and the keystrokes vanished with no feedback;
- after a FAILED initial load, the retained draft was destroyed by the next
successful reopen load — the exact moment the user reopens to retry —
and the failure indicator was cleared as if the save had succeeded.
Track the target on the dirty flag itself (paramDirtyKeyRef, set when the
draft is marked dirty) instead of deriving it from a completed load, and
re-check the guard after the GET await so a load result never overwrites
text, clears dirty, or clears the failure indicator for a draft that is
not on the server.
* fix(dashboard): bind the param-filter save to the draft's own target (#8910)
saveModelParamFilters guarded on paramDirtyRef alone and read the
providerId/modelId it closed over, never the target the draft was typed
for. ModelCompatPopover is not always keyed by a stable identity
(CompatibleModelsSection keys by `${alias}:${modelId}`,
PassthroughModelsSection by the full model string, and providerId is
threaded from route/page state), so a re-render can re-point a live,
mounted popover at a different provider/model. If the old target's save
had failed or never ran, the still-dirty draft was then PUT into the NEW
target — writing a filter list under a model/provider the user never
edited and destroying that target's real config.
Replace the dirty flag / revision counter / dirty-key trio with a single
ParamFilterDraft ref that carries the provider, model and both field
values captured at edit time. The save drives its GET, PUT and payload
from that draft instead of the current props, re-reads the ref after
each await (restarting the attempt if the draft was replaced by one for
another target), and only clears it when the exact draft object it wrote
is still pending. Object identity replaces the revision counter, keeping
the existing lost-update protection.
A load no longer clears the draft or the failure indicator: a draft
pending here belongs to another target and is still owed a write to it.
An orphaned draft is therefore neither dropped nor redirected — it keeps
its own provider/model, keeps the failure marker visible, and is retried
by the next blur/close/unmount save. The cleanup effect also depends on
the target key so re-pointing the popover flushes the old draft.
* fix(dashboard): keep param-filter fields and drafts bound to their own target (#8910)
Two remaining defects of the #8910 silent-data-loss family, both reached through
the re-point path of a live ModelCompatPopover.
1. The inputs render blockText/allowText, whose only writer was the load effect —
and that effect early-returned whenever a draft was dirty for the target. So
re-pointing A -> B -> A left B's server values on screen under A, and the next
keystroke snapshotted them into A's draft, persisting B's content into A's
entry. The fields are now a function of the target: on return to a target with
a pending draft the draft is restored into the inputs, and on a target with no
draft the previous target's values are cleared instead of being left behind.
An edit also no longer trusts the counterpart field unless the values on
screen belong to the target being edited.
2. The pending draft lived in a single slot that every edit overwrote, so typing
into a newly pointed target destroyed the previous target's unsaved work while
the new target's successful save cleared the failure indicator — a green UI
over data that was never written. Drafts are now keyed by provider/model; the
save drains every pending draft against its own target, and the indicator
reflects unsaved work across all targets rather than the last write.
Regression tests: modelCompatPopover-param-filter-target-repoint.test.tsx
(3 cases, RED at ecd111489, GREEN here). Scope limited to this component.
* fix(dashboard): drain midflight param-filter drafts (#8910)
* fix(dashboard): serialize cross-row param-filter saves (#8910)
* docs(changelog): add fragment for #9013
* chore: remove debug console.log and O5 test prefix
---------
Co-authored-by: 千乘妍 (Xiaoyaner) <xiaoyaner0201@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
* test(db): RED for LKGP pin invalidation on provider connection delete (#8887)
* fix(db): invalidate LKGP pins when their provider connection is deleted (#8887)
setLKGP() persists { provider, connectionId } under the `lkgp` namespace of
key_value, but none of the three delete paths in db/providers.ts touched that
namespace, so a pin outlived the connection it referenced and became unbounded
stale state.
- Add deleteLKGPByConnectionIds() to its owning module src/lib/db/settings/lkgp.ts
(no raw lkgp SQL inside providers.ts). Pins without a connectionId and legacy
plain-string pins are left untouched.
- Wire it into deleteProviderConnection, deleteProviderConnections and
deleteProviderConnectionsByProvider.
- Add invalidateCachedLKGP() to readCache.ts so the 5s in-memory lkgpCache cannot
serve a pin that was just deleted; called via the lazy-import pattern already
used there, so no import cycle (npm run check:cycles OK, 391 files).
No change to updateProviderConnection semantics, no session_model_history change,
no new API route, no migration.
---------
Co-authored-by: 千乘妍 (Xiaoyaner) <xiaoyaner0201@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
fitThinkingToMaxTokens() clamps the synthesized max_tokens to the model
output cap, but resolved that cap from a bare model id via
safeCapMaxOutputTokens(model) -> capMaxOutputTokens(model). A cap that is
only known per provider -- an operator max_output_tokens override, a
synced catalog limit_output, or a registry entry -- is invisible to a
bare-model lookup, so modelCap came back null and the unbounded
responseRoom + requestedBudget branch ran.
When the client sends no max-token field at all, adjustMaxTokens()
supplies DEFAULT_MAX_TOKENS (64000) and reasoning_effort: "high" supplies
a 131072 thinking budget, so the provider request carried
max_tokens: 195072 and every such request was rejected upstream with a
bare 400.
Thread the already-in-scope routedProvider (openai-to-claude.ts:122, used
two lines later for the Kimi-coding check) through fitThinkingToMaxTokens()
into capMaxOutputTokens({ provider, model }), which already supports
provider-scoped resolution via resolveCapabilityInput() -- no new lookup
path needed. Omitting the provider (existing callers, tests) keeps the
bare-model behavior unchanged; verified in the added regression test.
Follow-up to #6637, whose token-budgeting half was never addressed: #6893
fixed only the combo fallback classification. Rebased onto the
open-sse/translator/request/openai-to-claude/thinkingBudget.ts extraction
that landed after the original patch was written against the inline code
in openai-to-claude.ts.
* 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>
The Poolside entry landed with an empty `models` list because the public
matrix could only reach the unauthenticated endpoint, which answers 401
`No Authorization header provided` — the same response that a generic probe
once read back as "invalid key" and that got the provider dropped (#2723,
#3054). An authenticated probe against `/v1/models` (2026-08-07, #9085)
returned 200 and the full Preview catalog, so the two models are now static:
poolside/laguna-xs-2.1 Laguna XS 2.1
poolside/laguna-s-2.1 Laguna S 2.1
Both report 262144 context, 32768 max completion tokens, `tools` and
`reasoning`, and are text-only and free during Preview. The XS id is the
catalog form; the `laguna-xs.2` variant circulating in third-party listings
does not address this host. `passthroughModels` stays on, so live discovery
still admits models the Preview adds later.
Closes#9085
neuralwatt's /v1/models wraps capabilities and reasoning under a metadata
object (metadata.reasoning.supported_efforts + metadata.capabilities
.reasoning_effort), one level deeper than the shapes detectSupported
ThinkingEfforts recognized. Synced openai-compatible rows therefore carried
no supportedThinkingEfforts and no effort aliases were advertised.
Recognize the metadata-nested shape with the same schema and validation as
the top-level #7694 reasoning.supported_efforts, placed right after it in
precedence so a top-level declaration still wins when both are present.
Covered by three regression tests (parse, precedence, malformed-degradation).
* 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>
* perf(logging): bound call-log rotation work
* refactor(usage): extract call-log rotation/pruning from callLogs.ts to satisfy the file-size gate
Move the bounded rotation scheduler, orphan-artifact scanner, and row/overflow
pruning helpers (deleteCallLogsBefore, trimCallLogsToMaxRows,
cleanupOverflowCallLogFiles, cleanupOrphanCallLogFiles, rotateCallLogs,
scheduleCallLogRotation) into a new src/lib/usage/callLogRotation.ts module.
Pure extraction, no behavior change — callLogs.ts re-exports the same public
symbols so existing importers (usageDb.ts, compliance/index.ts, the
purge-logs route, and the rotation/cap test suite) are unaffected. Brings
callLogs.ts from 1108 to 787 lines, under the 1000-line file-size cap.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
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.