* fix(cursor): use Agent CLI build id for x-cursor-client-version
Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR); the author's .env.example/docs deltas were
re-applied cleanly onto the release tip.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* chore(changelog): re-sync CHANGELOG.md to release tip (restore #6701 bullet)
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(providers): ensure DeepSeek Web SSE emits [DONE] after FINISHED
Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR); keeps only the author's changes.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* refactor(deepseek): extract done-terminator helper to keep frozen file under cap
Extracts the FINISHED-drain scheduler and finish-once guard added for
the [DONE] terminator fix (#6777) into a new
deepseek-web-done-terminator.ts module, so deepseek-web.ts stays under
its frozen line cap (1148). Behavior is unchanged.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: Pitchfork-and-Torch <Pitchfork-and-Torch@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR); keeps only the author's changes.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR). Resolved the release's OmniGlyph engine addition
additively (types.ts/compression.ts kept both 'relevance' and 'omniglyph') and extended
stackedPipelineStepSchema + STACKED_PIPELINE_ENGINE_INTENSITIES with the omniglyph branch
so the ENGINE_CATALOG-parity test passes.
Co-authored-by: Pitchfork-and-Torch <Pitchfork-and-Torch@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR) and the direct CHANGELOG.md edit (fragments-first);
the author's chatCore/errorClassifier deltas were re-applied cleanly onto the release tip.
Co-authored-by: Andrian B. <andrewbalanesq@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR); the author's constants/registry/snapshot deltas
were re-applied cleanly onto the release tip.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(codex): strip include from compact responses requests
Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR); keeps only the author's changes.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(6805): move include-strip assertion to standalone test file to keep executor-codex.test.ts under frozen size cap
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR); keeps only the author's translator + test changes.
Co-authored-by: Wital <witalorocha216@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR); keeps only the author's cursor registry + test changes.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(compression): reconcile outer vs per-engine token counts on degenerate output (#6488)
Outer originalTokens/compressedTokens (real tiktoken counter over extracted
message text) diverged from engineBreakdown[0]'s counts (a crude
JSON.stringify(requestBody).length/4 estimate), worst on small/degenerate
inputs where JSON structural overhead dominates. A single-engine breakdown
entry represents the exact same before/after transformation as the overall
response, so reconcileSingleEngineTokens() now overwrites that one entry's
counts with the outer, more accurate figures; multi-step pipeline
breakdowns are left untouched.
* chore(6741): resolve release sync — CHANGELOG.md restored to release tip, entry moved to changelog.d fragment (fragments-first)
The transform forwarded the Claude-style thinking.budget_tokens into
generationConfig.thinkingConfig.thinkingBudget, but the presence check was
truthy (&& thinking.budget_tokens). An explicit budget_tokens: 0 — the
natural way to disable thinking — is falsy, so it was dropped and the
request fell through to the default thinkingConfig injection, making the
model think despite an explicit request for zero. Use an explicit numeric
check so 0 is honored as thinkingBudget 0; includeThoughts is only set for
a non-zero budget.
The judge prompt said to write an answer 'grounded in that analysis',
implicitly capping output at the panel's union. When all panel members
miss or are collectively wrong on something, the judge should apply its
own reasoning as a full participant and override consensus, while keeping
an honesty guard against fabrication. Adds a regression test.
Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com>
applySessionStickiness() gated the sticky pin only on 5h/weekly usage headroom,
which is orthogonal to account availability, so a credits_exhausted/banned/
expired/rate-limited connection (or a quality-validation-rejected 200) kept
being re-promoted forever, defeating failover for that conversation.
* feat(oauth): add Kiro long-lived API key auth (#6587)
New /api/oauth/kiro/api-key route + KiroService.validateApiKey let a
Kiro account be linked with a long-lived AWS CodeWhisperer/Kiro API
key instead of the interactive OAuth device flow, with live
per-account model discovery (ListAvailableModels, 5-minute cache)
layered over the existing static registry fallback.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): re-restore #6587 bullet after release sync
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(merge): restore #6126 clinepass files reverted by release auto-resolve + baseline re-merge
The release sync's auto-resolve reverted sibling PR #6126's clinepass work
(registry, catalog, oauth constants, clineAuth.ts, token-refresh case, tests)
and the file-size baseline — all outside this PR's scope. Restored to the
release versions, re-applied only this PR's own baseline entries, restored the
#6126 CHANGELOG bullet (re-inserting only this PR's own).
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* chore(quality): freeze public-creds FP — AWS region default in validateApiKey signature
Same class as the existing minimax fn-param FPs: CRED_KEY_RE matches the
apiKey: param annotation and captures the region default "us-east-1",
which is not a credential.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(kiro): keep hard-failure reject semantics + kill public-creds fn-param FP at the source
- getKiroUsage: exhausted non-auth attempts now REJECT with the last HTTP-status
failure in the pre-#6587 format (usage-service-hardening relies on it); auth
failures keep the soft social-auth message.
- validateApiKey: region default moved out of the parameter list (the
check-public-creds CRED_KEY_RE matches the apiKey: annotation and flags any
literal in the signature); drops the brittle line-keyed allowlist entry.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: strangersp <strangersp@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>
* feat(kiro): support enterprise External IdP ("Your organization") logins
Kiro's enterprise "Your organization" sign-in federates through the org's own
identity provider (e.g. Microsoft Entra ID) and produces an `external_idp`
token that is fundamentally different from AWS Builder ID / IAM Identity Center
(AWS SSO-OIDC, refresh token starts with `aorAAAAAG`) and the Google/GitHub
social flow. Its `~/.aws/sso/cache/kiro-auth-token.json` carries an org-IdP JWT
access token, an IdP refresh token, a per-tenant `tokenEndpoint`, a public
`clientId` (no secret) and `scopes` (`codewhisperer:conversations …`).
Before this change every import path rejected these tokens (the
`aorAAAAAG` format gate + no client secret), and the runtime/quota calls would
have failed even if imported, so organization accounts could not be used.
This adds full external_idp support:
- New `open-sse/services/kiroExternalIdp.ts`: public-client refresh_token grant
builder (`buildExternalIdpRefreshParams`), a token-endpoint SSRF allowlist
(`validateExternalIdpTokenEndpoint` — Microsoft/Okta/Auth0/OneLogin/Ping/
Google/Cognito, https only), scope normalization, JWT identity extraction
(`preferred_username`/`upn`/`email`), and the `TokenType: EXTERNAL_IDP`
header constants.
- Runtime executor (`open-sse/executors/kiro.ts`): send
`TokenType: EXTERNAL_IDP` for external_idp accounts. CodeWhisperer only binds
the org-IdP bearer to the Amazon Q Developer profile with this header;
without it every call returns `ValidationException: Invalid ARN <clientId>`.
- Runtime + import token refresh (`open-sse/services/tokenRefresh.ts`,
`src/lib/oauth/services/kiro.ts`): refresh external_idp tokens with a
form-encoded public-client `refresh_token` grant against the org IdP's
`tokenEndpoint` instead of AWS OIDC / the Kiro social endpoint.
- Quota (`open-sse/services/usage/kiro.ts`): send the same header on
`GetUsageLimits` so organization quota resolves.
- Import routes: `POST /api/oauth/kiro/import` gains an external_idp branch
(skips the `aorAAAAAG` gate, refreshes via the org IdP, stores
clientId/tokenEndpoint/scope/region/profileArn); `GET /auto-import` now
recognizes external_idp tokens in `~/.aws/sso/cache`, reads the profile ARN
from the Kiro IDE `profile.json` (org tokens can't enumerate it via
`ListAvailableProfiles`), and persists the connection. The profile.json
reader is factored into a shared `readKiroIdeProfileArn()` helper.
- Validation schema (`kiroImportSchema`): accept `tokenEndpoint` + `scopes`.
Tests: new `tests/unit/kiro-external-idp.test.ts` (endpoint allowlist, scope
normalization, identity extraction, public-client refresh body, the org IdP
refresh path, and the `TokenType: EXTERNAL_IDP` header gating). Also hardens
`kiro-windows-auto-import-3363.test.ts` to isolate `USERPROFILE` (Windows
`os.homedir()` reads it, not `HOME`) so the probe never reads a real on-host
Kiro login.
* fix(changelog): restore #6363 bullet after release resync (CHANGELOG-eat guard)
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): re-restore #6363 bullet after release sync
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(merge): restore #6126 clinepass files reverted by release auto-resolve + rebaseline own tokenRefresh growth
The release sync's merge auto-resolve silently reverted sibling PR #6126's
clinepass work (registry entry, catalog, oauth constants, clineAuth.ts, the
clinepass token-refresh case, and its tests) — all outside this PR's Kiro
external-IdP scope. Restored every affected file to the release version; the
remaining diff is Kiro-IdP-only. Rebaselined tokenRefresh.ts 2182->2249 (+67,
this PR's own external_idp refresh branch) with justification, and restored
the #6126 CHANGELOG bullet (re-inserting only this PR's own).
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>
Co-authored-by: artickc <artickc@users.noreply.github.com>
* feat(providers): rebase ClinePass dual-auth (OAuth + BYOK) onto release/v3.8.47
ClinePass now offers both sign-in methods on its dashboard page: OAuth
(reusing the Cline WorkOS flow, primary "Connect" button) or a pasted
BYOK API key ("Manual API key"), instead of only the API-key-only
provider shipped in #5942.
- Registry: authType oauth + oauth urls, alias aligned to "cp" (matches
the OAUTH_PROVIDERS catalog alias so <alias>/<modelId> routing
resolves); keeps the #6165 forceStream:true fix (streaming-only API).
- Executor: new buildClinepassHeaders() (src/shared/utils/clineAuth.ts)
picks buildClineHeaders() for an OAuth accessToken or a plain Bearer +
Cline identification headers for a BYOK key — extracted to a leaf
module to avoid growing the frozen open-sse/executors/default.ts.
- Refresh: dispatch clinepass to the shared refreshClineToken() (was
falling through to the generic refresh and failing silently).
- Catalog: admit the BYOK path through a dedicated
DUAL_AUTH_APIKEY_PROVIDER_IDS gate (src/lib/providers/catalog.ts) so
POST /api/providers accepts an apikey connection without flipping
isOAuth off (which would break the primary Connect->OAuth routing).
- Dashboard: render both "Connect" + "Manual API key" buttons for
clinepass (ConnectionsHeaderToolbar.tsx, EmptyConnectionsPlaceholder.tsx).
- Dedup: removed the now-redundant API-key-only APIKEY_PROVIDERS_GATEWAYS
entry so ClinePass is listed once (OAuth-primary).
- oauth.ts: added the clinepass catalog entry (was reverted by staleness
during rebase); src/lib/oauth/providers/index.ts: clinepass -> cline.
This branch was ~167 commits / weeks behind release/v3.8.47; a real
merge surfaced 61 conflicting files, several of which are already-shipped
fixes (forceStream #6165, zed-hosted, requesty, agentrouter CC-wire-image,
NVIDIA/Mistral/kimi executor fixes, chatCore hardening) that a naive
resolution would have silently reverted. Reconstructed clean on top of
current release/v3.8.47, isolating and re-applying only the clinepass
dual-auth feature and preserving every already-shipped fix untouched.
tokenRefresh.ts's frozen-file cap raised by the irreducible 1-line
`case "clinepass":` switch label (config/quality/file-size-baseline.json,
justified inline); open-sse/executors/default.ts stays under its cap via
the buildClinepassHeaders() extraction.
Regression guard: tests/unit/clinepass-provider.test.ts (15/15, extended
with the dual-auth admission-gate and alias-consistency guards).
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* test(providers): update APIKEY_PROVIDERS spread-merge count 171->170
The ClinePass dual-auth rebase (this PR) removed the now-redundant
API-key-only APIKEY_PROVIDERS_GATEWAYS.clinepass entry (dedup — clinepass
is OAuth-primary now, with its BYOK path admitted through the
DUAL_AUTH_APIKEY_PROVIDER_IDS gate instead of a second catalog entry),
which drops the total APIKEY_PROVIDERS spread-merge count by one.
tests/unit/providers-constants-split.test.ts hardcoded the prior count
(171); updated to 170 to match, confirmed via CI (Unit Tests fast-path
1/2 and 2/2 both failed on the stale count).
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(oauth): register clinepass in PROVIDERS enum to fix Unknown provider error
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* chore(test): rebaseline oauth-providers-config.test.ts frozen size for clinepass entries
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): re-restore #6126 bullet after release sync
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: hajilok <hajilok@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
* fix(skills): gate skill-collector CLI detection behind management auth + loopback
PR #6294 fork-main bundled genuinely new skill-collector CLI-detection routes
(GET /api/skills/collect/detect, POST /api/skills/collect/install) on top of
content already shipped via #6186. This reconstructs the PR against the
current release tip, keeping only the new detect/install routes and their
SKILL.md, and drops the 3 already-merged commits so two post-merge quality
fixes on /api/github-skills (Zod validation + sanitizeErrorMessage) are not
reverted.
- GET /api/skills/collect/detect spawned a child process per CLI_TOOL_IDS
entry via getCliRuntimeStatus(), unauthenticated and reachable over any
tunnel. All 3 routes (github-skills GET/POST, skills/collect/detect,
skills/collect/install) now require requireManagementAuth(), matching
every sibling /api/skills/* route.
- Classified /api/skills/collect/ in LOCAL_ONLY_API_PREFIXES and
SPAWN_CAPABLE_PREFIXES (routeGuard.ts / spawnCapablePrefixes.ts) and added
src/app/api/skills/collect to SPAWN_CAPABLE_ROUTE_ROOTS in
check-route-guard-membership.ts so the automated gate actually scans it
(Hard Rules #15 + #17).
- omniroute_github_skills_install MCP tool now reports the honest
action: "planned" instead of "installed", matching the REST route.
- Dropped docker-compose.drive-d.yml, start.sh, and the unrelated
@types/node/settings.ts changes (personal dev-machine / out-of-scope).
- Added route-level tests for all 3 routes + the 3 MCP tools (auth-required
and no-stack-trace-leak assertions) and a route-guard regression test.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(quality): register new routeGuard covering test in stryker.conf.json
check:mutation-test-coverage --strict (Fast Quality Gates) flagged
tests/unit/authz/route-guard-skills-collect.test.ts as a covering unit test
for src/server/authz/routeGuard.ts that was missing from tap.testFiles, so
its mutant kills would silently not count toward the mutation-test baseline.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* chore: resync CHANGELOG after merging release/v3.8.47
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>
Co-authored-by: Moseyuh333 <Moseyuh333@users.noreply.github.com>
* feat(dashboard): 9router-parity Routing Strategy card + provider/combo sticky override (#6678)
Add a Routing Strategy settings card (Settings -> Routing) surfacing account
round-robin/sticky-limit knobs plus a new combo-level sticky round-robin
(comboStickyRoundRobinLimit), and a per-provider account-routing override
(providerStrategies) wired into getProviderCredentials() ahead of the global
fallback strategy. Rebased onto release/v3.8.47 (credit-preserving
reconstruction: unrelated package.json/electron/proxyDispatcher drift from the
PR's stale base was dropped, only the author's own 12 files were re-applied).
Split ProviderAccountRoutingCard/RoutingStrategyCard into smaller
hook+subcomponent pieces to stay under the frozen complexity/file-size gates;
rebaselined ProviderDetailPageClient.tsx/auth.ts's frozen file-size caps for
the small additive growth.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* chore(quality): register combo-rr-sticky-9router.test.ts in stryker tap.testFiles (#6678)
CI's Fast Quality Gates -> check:mutation-test-coverage --strict flagged the new
test as missing from stryker.conf.json's tap.testFiles (it covers the mutated
module open-sse/services/combo/rrState.ts). Adds the single entry, alphabetized
next to the existing combo-rr-fallback-advance-948.test.ts.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): restore CHANGELOG bullets eaten by release sync
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): re-restore CHANGELOG bullet after further release sync
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): re-restore CHANGELOG bullet after further release sync
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>
Co-authored-by: SeaXen <SeaXen@users.noreply.github.com>
* fix(providers): render ChatGPT-web citation markers as Markdown links
ChatGPT Web responses leaked raw chatgpt.com UI citation markup (private-use
marker tokens like `citeturn0search0`, `entity[...]`) instead of real
Markdown links, since these are normally resolved client-side by chatgpt.com's
own JS using `message.metadata.content_references`.
cleanChatGptText() now resolves content_references (grouped webpages, footnote
sources, inline webpage/url mentions) into `[label](url)` Markdown links for
the streaming and non-streaming response builders and the GPT-5.5 Pro
stream_handoff polled-answer path, falling back to stripping any marker with
no resolvable source.
The citation parsing/rendering logic was extracted into a new pure sibling
module (open-sse/executors/chatgpt-web/citations.ts), decomposed into small
per-reference-type helpers, to keep the executor under the frozen file-size
cap and the complexity/cognitive-complexity ratchets. Regression tests moved
to a dedicated tests/unit/chatgpt-web-citations.test.ts for the same reason.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): restore CHANGELOG bullets eaten by release sync
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): re-restore CHANGELOG bullet after further release sync
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): re-restore CHANGELOG bullet after further release sync
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>
Co-authored-by: Thinkscape <thinkscape@users.noreply.github.com>
* fix(sse): preserve server-tool literal names in message history and tool_choice
The v3.8.36 guard (isAnthropicServerToolType, #2943) protects Anthropic
server tools (web_search_20250305, bash_20250124, ...) from the tool-name
cloak only in the tools[] array. The same reserved literal names were still
rewritten in message-history tool_use blocks and in tool_choice, and
remapToolNamesInRequest had no guard at all (bash -> Bash).
The resulting asymmetry — tools[] keeps 'web_search' while the history
reference becomes 'WebSearch' — makes Anthropic reject every follow-up turn
of a native web-search conversation:
[400] Tool 'WebSearch' not found in provided tools
Collect the declared server-tool names once per request and skip them in
every rewrite path of both remapToolNamesInRequest and
cloakThirdPartyToolNames (tools[], message history, tool_choice). Plain
custom tools with the same names (no server type) remain remapped/cloaked
exactly as before, symmetrically in all sections.
Surfaced on Claude Code 2.1.x native WebSearch; same class as CLIProxyAPI
#1094/#1179. TDD: 5 failing repro tests -> guard -> 7/7 green (92/92 across
the remapper suite), typecheck:core clean.
* fix(sse): skip null entries in tools[] before server-tool type check
Review follow-up (gemini-code-assist): a null element in tools[] made the
new isAnthropicServerToolType(tool.type) check throw. The crash path is
pre-existing (String(tool.name) on the next line threw identically), but
the guard is cheap and mirrors the null checks already used in
cloakThirdPartyToolNames. Adds a regression test (8/8 green).
* fix(combos): default reasoning token buffer off
* feat(combos): expose reasoning token buffer toggle
* fix(combos): keep reasoning-token buffer default enabled, opt-out toggle
#6702 shipped bundled with #6536's own commit (identical SHA
37ac38f17f), flipping the combo-wide reasoningTokenBufferEnabled
default from true to false. #6536 was subsequently closed by the
author in favor of #6714, which explicitly keeps the existing
default-enabled buffer behavior and instead clamps the buffer to the
model's known output cap. Reconciled #6702 with that resolution:
dropped the default-flip changes across comboConfig.ts, combo.ts,
comboSetup.ts, ComboDefaultsTab.tsx, and the combo-defaults settings
route (plus their test assertions), and inverted the new per-combo
ReasoningTokenBufferToggle to opt-out semantics (`!== false`) so an
existing combo's behavior is unchanged unless the operator explicitly
unchecks it.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Drop dead catalog/registry entries, keep Synthetic as the GLHF replacement path,
regenerate provider reference/docs counts, and lock APIKEY family-split + file-size
gates so CI stays green.
Ignore prettier on freeModelCatalog.data.ts so dense one-line budget rows are not
expanded past the 800-line new-file cap.
* fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605)
fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148).
Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148).
Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.)
* deps: bump the development group across 1 directory with 6 updates (#6588)
deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605).
* fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620)
fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump.
undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green.
Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.)
* feat(db): add provider param filter config store (key_value namespace)
Add paramFilters.ts module for CRUD against provider_param_filters
namespace in the key_value table, with in-memory cache + generation
counter invalidation. Supports denylist/allowlist per provider and
per model, plus auto-learn flag.
Migration 118 documents the namespace (no schema change).
Issue: #6625
* feat(proxy): add detectUnsupportedParam regex for auto-learning
Add UNSUPPORTED_PARAM_RE and detectUnsupportedParam() to extract
the offending parameter name from upstream 400 error messages like
'Unsupported parameter(s): thinking'.
Issue: #6625
* feat(proxy): extend stripUnsupportedParams with config-driven denylist/allowlist
Add applyConfigFilters() called after hardcoded STRIP_RULES in
stripUnsupportedParams(). Config-driven rules (DB-backed via
paramFilters.ts) support provider-level and model-level:
1. Provider denylist (delete body[key])
2. Model denylist (delete body[key])
3. Provider allowlist (restore from pre-strip snapshot)
4. Model allowlist (restore from pre-strip snapshot)
Allowlist only restores keys the client actually sent — never
introduces new params.
Issue: #6625
* feat(proxy): wire auto-learn of unsupported params into 400-downgrade loop
When a provider returns 400 with 'Unsupported parameter: X' and the
provider config has autoLearn enabled, auto-detect the param name
via detectUnsupportedParam(), persist it to the provider's block
list via addParamToBlocklist(), then strip and retry.
Issue: #6625
* test: add tests for provider param filter denylist/allowlist/auto-learn
Three new test files:
- param-filters-apply.test.ts — hardcoded rules regression + direct
applyConfigFilters tests (no DB dependency)
- param-filters-db.test.ts — CRUD against key_value, cache invalidation,
full filter pipeline (DB-backed config → stripUnsupportedParams),
16 tests in isolated temp DB
- param-filters-auto-learn.test.ts — UNSUPPORTED_PARAM_RE regex matching
and detectUnsupportedParam edge cases
All existing tests unchanged and passing.
Issue: #6625
* feat(proxy): add global auto-learn flag for unsupported params
Add isAutoLearnGloballyEnabled() and setGlobalAutoLearnEnabled() to
paramFilters.ts. The global flag (stored as key __global__ in the
provider_param_filters namespace) acts as a master switch: when
enabled, ALL providers auto-learn unsupported params from 400 errors.
In base.ts, the auto-learn check now evaluates:
shouldAutoLearn = isAutoLearnGloballyEnabled() || perProviderConfig?.autoLearn
Global flag defaults to false (opt-in). Tests cover enable/disable/
default/no-interference-with-per-provider-config.
Issue: #6625
* fix: apply PR#6649 review feedback — model-scoped auto-learn and precedence order
Fixes from gemini-code-assist[bot] review:
- HIGH: Auto-learn now scoped to the specific model that triggered the 400
(addParamToBlocklist(this.provider, autoLearned, model)) instead of
adding to the provider-level blocklist globally
- HIGH: Reordered applyConfigFilters so model-level operations run AFTER
provider-level operations (model denylist → model allowlist override
provider allowlist → provider denylist)
- MEDIUM: Include model name in auto-learn log message
Adds regression test verifying model-level denylist beats provider-level
allowlist.
Issue: #6625
PR: #6649
* feat(ui): add provider-level param filter section to detail page
Add ProviderParamFilterSection component rendered on each provider
detail page, backed by GET|PUT|DELETE /api/providers/[id]/param-filters.
UI allows operators to configure:
- Blocked params (comma-separated, stripped from outgoing requests)
- Allowed params (comma-separated, re-added after denylist stripping)
- Auto-learn toggle (per-provider, enables auto-learning from 400 errors)
Wired into ProviderDetailPageClient.tsx between the Playground panel
and the Modals section.
Issue: #6625
PR: #6649
* feat(ui): add model-level param filter fields in compat popover
Extend ModelCompatPopover with Blocked params and Allowed params
text inputs for model-level denylist/allowlist overrides.
Model-specific block/allow data is persisted via the param-filters
API endpoint (PUT /api/providers/:id/param-filters) with the model
scope under the models key.
Both ModelRow and PassthroughModelRow now pass providerId and modelId
to the popover.
Issue: #6625
PR: #6649
* chore: gitignore .claude-flow/
* fix(param-filters): review follow-ups — auth gate, error sanitization, Zod body validation, typecheck, file-size, i18n keys
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* chore(param-filters): drop unrelated main-drift from the fork branch (deps/electron/proxy files belong to #6620/#6605/#6588, not this PR)
* refactor(param-filters): split oversized functions — keep complexity gate at baseline
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* refactor(param-filters): decompose config parser helpers — keep cognitive-complexity gate at baseline
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(changelog): restore sibling #6648 bullet eaten by merge auto-resolve + re-insert #6649 entry
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
* fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605)
fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148).
Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148).
Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.)
* deps: bump the development group across 1 directory with 6 updates (#6588)
deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605).
* fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620)
fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump.
undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green.
Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.)
* fix(mimocode): handle 400 with cooldown + account rotation
Treat HTTP 400 responses the same as 429: mark the account on cooldown
and continue to the next fingerprint/proxy. Previously, 400 fell through
to markSuccess and returned immediately, so only 1 of N accounts was ever
tried per request.
Refs: #5925
* chore(mimocode): drop unrelated dependency/electron drift from PR #6648's stale fork
package.json/package-lock.json (bun/eslint-config-next/cyclonedx bumps), electron/package.json,
electron/package-lock.json, open-sse/utils/proxyDispatcher.ts, prepare-electron-standalone.mjs
and tests/unit/proxy-dispatcher-family.test.ts were already present in the contributor's single
commit but are unrelated to the mimocode 400-handling fix — restored to release/v3.8.47's
versions so the PR stays scoped to open-sse/executors/mimocode.ts.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(mimocode): classify 400 body before rotating — rate-limit-text 400s rotate, malformed 400s fail fast (#2101/#4976 guard)
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* refactor(mimocode): extract auth-retry + 429/400 gating helpers — keep execute() under the cognitive-complexity gate
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: pizzav-xyz <pizzav-xyz@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
* feat(compression): dependência omniglyph (file:) + smoke de import
* feat(compression): engine omniglyph — contexto-como-imagem com gates fail-closed
* fix(compression): omniglyph adapter fail-open no transform (try/catch)
* feat(compression): registra omniglyph no registry e catálogo (single mode, stackPriority 90)
* feat(compression): modo único omniglyph (async), selecionar o modo é o enable
* feat(compression): plumbing supportsVision + providerTransport até os engines
* feat(compression): estimador de tokens image-aware — modo stacked mantém a saída do omniglyph
* docs(compression): corrige comentário do prefixo base64 no decode PNG (64 chars)
* feat(compression): registra omniglyph nas listas de modo/engine (db, combo, deriveDefaultPlan, mcp)
* feat(dashboard): dedicated OmniGlyph engine screen (context-as-image)
Adds a per-engine detail page at /dashboard/context/omniglyph, alongside the
other compression engines in the sidebar. Four sections: the economics (measured
savings), a REAL before→after (dense text vs the rendered PNG page, not a mockup),
the fail-closed gate flow, and the enable control wired to /api/settings/compression
(preview engine, off by default). Sidebar entry + i18n label across all locales.
* chore(compression): consume published omniglyph@^1.0.0 from the npm registry
Replaces the local file: dependency used during the preview phase — npm ci
now resolves omniglyph from the registry with integrity, unblocking CI.
* fix(compression): satisfy v3.8.47 quality gates for the omniglyph engine
- dependency-allowlist: approve omniglyph (own package, published from
diegosouzapw/OmniGlyph; supply-chain review done by the maintainer)
- ladder maps (#6533 guard): rank omniglyph 80 (stackPriority 90, runs after
every text engine) with expectedReductionFactor 0.35 (measured 0.23-0.33)
- drop the two explicit any casts in omniglyph tests (no-explicit-any is
error-level in tests since #6218)
* chore(compression): rebaseline strategySelector for the omniglyph mode dispatch
+18 lines of cohesive dispatch/type wiring at the existing mode chokepoints
(sync no-op + async single-mode branch + providerTransport on the options
types) — not extractable without hiding the dispatch boundary, mirroring the
prior compression rebaselines. Also drops an unused eslint-disable directive
in image-aware-tokens.test.ts (warning-level red under --max-warnings 0).
* chore(quality): register inherited base tests in stryker tap.testFiles
masked-200-exhaustion-fallback-6427 and headroom-codex-quota-snapshot-6379
arrived via the base merge without their stryker registration —
check:mutation-test-coverage --strict requires covering tests to be listed.
* refactor(compression): keep omniglyph wiring under the complexity gate
- extract the async single-mode resolution to engines/omniglyphSingleMode.ts
(runCompressionAsync was at complexity 17 after the mode branch; back <=15)
- split OmniglyphContextPageClient into section components (was 161 lines in
one function; every function now under the 80-line cap)
- complexity baseline 2052->2053: the +1 is inherited base drift (the ratchet
does not run on fast-path merges — same pattern as the v3.8.44/46
rebaselines); this PR's own code is measured complexity-net-zero
* chore(quality): register 3 more inherited base tests in stryker tap.testFiles
route-guard-middleware-local-only, combo-diagnostics-trace and
idempotency-fusion-collision arrived via the latest base merge without their
stryker registration (fast-path merges skip check:mutation-test-coverage).
* chore(quality): cognitive-complexity baseline 883->884 (inherited base drift)
check:cognitive-complexity measures 884 identically on the pristine
origin/release/v3.8.47 tip and on this HEAD — the PR itself is
cognitive-net-zero (single-mode resolution extracted to its own module,
page client split into section components). Same inherited-drift pattern
as the v3.8.4x release rebaselines.
---------
Co-authored-by: diegosouzapw <diegosouzapw@devbox.local>
Merged — thank you, @developerjillur! Sanitized diagnostic trace on an auto-combo terminal failure (ids/reason-codes only, capped), plus an actionable reasoning-budget-exhausted message. Integrated into release/v3.8.47.
Merged — thank you, @developerjillur! Namespaces the idempotency key by target provider/model + a messages digest so fusion panel/judge sub-requests can't collide on a shared client Idempotency-Key. Existing chatCore extracted-module tests were aligned to the composed-key contract. Integrated into release/v3.8.47.
chore(open-sse): remove vestigial @ts-nocheck from usageTracking.ts (#6173). Restores type-checking on the token-usage hot path under typecheck:core. Integrated into release/v3.8.47.
`validateResponseQuality()` only inspected a response's top-level `error`
field when `choices` was also missing/empty (the narrower #3424 case), so a
masked HTTP 200 that echoed a non-empty stub `choices` alongside a structured
error object — or a known exhaustion phrase like "insufficient credits" /
"quota exceeded" in the error envelope — slipped through as valid, and a
`priority` combo kept hammering the exhausted target instead of failing
over.
The check now inspects the error envelope (top-level `error` object, or a
bounded exhaustion-phrase match against error.message/code/type and
top-level message/detail) unconditionally, before any shape-specific
branch — never against `choices[].message.content`, so legitimate
completions that merely mention "quota" in prose are not misclassified.
Regression guard: tests/unit/masked-200-exhaustion-fallback-6427.test.ts
appendToolCallArgumentDelta() treated any non-string incoming fragment as
empty, silently dropping tool-call arguments delivered as an already-parsed
JSON object/array (a non-conformant shape some upstreams emit for
tool_calls[].function.arguments) instead of JSON-encoding them. This left
tool_use.input empty on the /anthropic streaming path and opened the door to
downstream [object Object] string coercion once buffers were concatenated.
Now JSON.stringify()s the non-string fragment instead of discarding it.
The fusion single-survivor degrade path (added for #6454) returned the
lone panel answer directly whenever only one panelist succeeded, ignoring
an explicitly configured judgeModel. With default minPanel=2 and a 2-model
panel, any single flaky panelist forced this path every request, so the
configured judge never ran and the response .model reflected a panel member.
The judge is now still invoked to synthesize a lone surviving answer when
judgeModel is explicitly configured; the direct-answer shortcut is kept only
for the implicit case (no judgeModel, judge defaults to panel[0]).
orderTargetsByHeadroom already loaded the per-connection DB snapshot (with
decrypted credentials) via expandTargetsByQuotaAwareConnections, but discarded
it before calling getSaturation. For Codex, fetchCodexSaturation forwards
straight to fetchCodexQuota(connectionId, connection), which needs the
connection object (or a prior registerCodexConnection() call that never
happens before headroom ranking runs) to read accessToken. Without it,
fetchCodexQuota returned null for every candidate, saturation failed open to
0 across the board, and headroom ranking fell back to the original combo
order regardless of actual free quota.
getSaturation() and the headroom SaturationFetcher seam now accept and thread
the loaded connection snapshot through to fetchCodexQuota.
Regression guard: tests/unit/headroom-codex-quota-snapshot-6379.test.ts
(seeds two real Codex connections in a throwaway SQLite DB with a fake
upstream fetch, confirms RED on unfixed code, GREEN after the fix).
* fix(api): exempt test-model requests from Output Styles injection (#6240)
Root cause: handleChatCore's Phase 4A Output Styles injection (chatCore.ts)
was gated only by the operator's global compression.enabled switch,
independent of the per-request x-omniroute-compression header. The
dashboard 'Test model' action (modelTestRunner.ts) never sent that header,
so a globally-enabled Output Style (e.g. 'Ultra terse') always leaked its
system-prompt injection into a plain connection test.
Fix: skip Output Styles injection when the request explicitly opts out via
x-omniroute-compression: off, and always send that header from
buildInternalChatRequest / buildInternalRerankRequest.
Regression guard: tests/integration/test-model-compression-off-6240.test.ts,
tests/unit/model-test-runner-compression-off-6240.test.ts
* chore: sync CHANGELOG to release tip (#6511; bullet re-added at merge)
Parallel-cycle model (2026-07-04): cut from the frozen release/v3.8.46 tip at the
v3.8.46 release freeze so development continues immediately on v3.8.47 while the
captain closes v3.8.46. Bumps package.json x3 + openapi + lockfile to 3.8.47, adds
the '## [3.8.47] — TBD' living CHANGELOG section, and syncs the 42 i18n mirrors.
Closing v3.8.46 fixes reach this branch via the Phase 5 sync-back.
randomNumericId builds a non-secret synthetic device/web id. The v3.8.45 switch
to crypto.getRandomValues closed js/insecure-randomness but 'cryptoByte % 10' is
biased (256 is not a multiple of 10), tripping js/biased-cryptographic-random.
Draw each digit by rejection sampling — discard bytes in the biased tail so the
remaining range divides evenly — giving a uniform distribution (verified) while
staying crypto-backed.
Exclude paid-only models from the auto/* candidate pool when hidePaidModels is on (#6512). Integrated into release/v3.8.46; fixed a phantom vitest guard, 4/4 green.