Repository navigation
feat(db): add backend operational skeleton - #1
Draft
xiaoyaner0201 wants to merge 1089 commits into
Draft
xiaoyaner0201 wants to merge 1089 commits into
xiaoyaner0201 wants to merge 1089 commits into
Conversation
…der (cheaper-inference#9257) * fix(command-code): preserve literal max effort for command-code provider * test(command-code): type the new sanitizeReasoningEffortForProvider assertions The 3 new command-code reasoning-effort test cases cast the function's unknown return value with `as any`, which pushes the file's frozen no-explicit-any suppression count (48) to 51 and trips the "No new ESLint warnings" gate. Use a minimal EffortCarrierResult shape instead of any, matching the fields the assertions actually read. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(v1-models): type the API key lookup in the cheaper-inference#9320 auth-leak regression test The release-tip test file added by cheaper-inference#9320 used `(k: any)` in an Array.find callback, which is not covered by config/quality/eslint-suppressions.json (the file was added after the suppressions snapshot was frozen). That leaves the "No new ESLint warnings" gate red for any branch that merges this exact release/v3.8.50 tip, unrelated to this PR's own diff. Fixing it here with a minimal derived type (Awaited<ReturnType<typeof getApiKeys>>[number]) unblocks the gate without touching the frozen suppressions baseline. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…heaper-inference#5501) (cheaper-inference#9414) * feat(sse): server-side template expansion for combo system prompts (cheaper-inference#5501) * fix(quality-gates): register combo-system-prompt-templates-5501 test in stryker tap.testFiles check:mutation-test-coverage --strict flagged tests/unit/combo-system-prompt-templates-5501.test.ts as covering src/shared/utils/circuitBreaker.ts without being listed in stryker.conf.json tap.testFiles, so its mutant kills wouldn't count. Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com> --------- Co-authored-by: Max <maxmad64@gmail.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com>
…inference#9423) * fix: preserve Codex cache usage for Claude suggestions Co-Authored-By: Claude <noreply@anthropic.com> * fix: normalize streamed optional tool arguments Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Kittisak Tangsiri <kittisak@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
…cheaper-inference#9457) Hoisting a mid-conversation `system`/`developer` message into the top-level `system` field carried its `cache_control` marker along. Anthropic assembles the cache prefix as tools -> system -> messages, so the marker ended the cached prefix at the system block and left the accumulated conversation without a breakpoint: that turn was billed as fresh input and the next one rebuilt the cache. `relocateHoistedCacheBoundary` moves the marker to the nearest preceding block that can carry a breakpoint, skipping thinking blocks, empty text and anything the upstream normalisation discards or empties out. If that block already carries the client's own marker, both are kept - unless the hoisted one, now ahead of the target in `system[]`, would put a 5m breakpoint before a 1h one, which Anthropic rejects; it is dropped in that case. Either way the breakpoint count never grows. normalizeClaudeUpstreamMessages rewrites tool_result and inlined file/document blocks into plain text after the hoist, which silently discarded any marker on them - including a relocated one. The replacement block now inherits it. Both hoisting implementations share the helper; a fix touching only claudeSystemRole.ts would leave extractSystemMessagesToBody broken, and the native Claude path reaches the former through normalizeClaudeUpstreamMessages. Capability-gated hoisting for strict providers (cheaper-inference#7293) is unaffected. Fixes cheaper-inference#9436 Co-authored-by: LeonG606 <leongudat01@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
cheaper-inference#9549) * fix(adobe-firefly): open browser sign-in and resolve provider slug in /login POST /api/providers/[id]/login passed the connection DB id to inAppLoginService.startLogin, but TOKEN_EXTRACTION_CONFIGS is keyed by provider slug — so browser login never launched for web-cookie providers. Adobe Firefly also cannot use cookie extraction: the IMS JWT only appears on Authorization headers to firefly-3p.ff.adobe.io. Add a dedicated Playwright interceptor and persist credentials with camelCase keys that updateProviderConnection actually reads. * fix(adobe-firefly): use system Chrome/Edge CDP for browser sign-in Playwright is not available inside the pkg-packaged VibeProxyServices.exe, so import('playwright') always failed with 'Playwright not installed' and never opened a window. Launch Chrome/Edge with --remote-debugging-port and capture the firefly-3p Authorization Bearer via pure CDP WebSocket instead. * fix(adobe-firefly): live x-arp-session-id / Arkose wire (stop 408 under load) Browser generate-async requires x-arp-session-id as base64({sid,ark,ftr}) with a real Arkose blob (sherlockToken). JWT alone frequently returns colligo HTTP 408 system under load while credits still work. - Match live ftr magic __UDF43-m4_31ck + Arkose pk in synthetic ARP fallback - Ranked extract of sherlockToken / x-arp from Cookie, HAR, fetch() paste, and space-joined JWT+ARP (PasswordBox newline collapse) - Reuse one ARP for storage upload + generate-async - Clearer 408 errors when browser ARP is missing vs stale - Unit suite 42/42 * fix(adobe-firefly): durable session ARP rebuild and aux_sid false-positive Rebuild x-arp-session-id from forterToken/arkose/ff_session_guid instead of ranking long Cookie pairs (e.g. aux_sid=…) as opaque ARP, which caused colligo HTTP 408. Cache IMS JWT + cookie sessions, rotate ARP on 408 retries, and keep Playwright warm-up opt-in only (headless Forter is rejected). Also expand synthetic ARP shape with bfp/fpjs to match live successful captures. * fix(adobe-firefly): renew sessions through durable CDP * fix(adobe-firefly): isolate browser sessions per account * fix(adobe-firefly): make account login fresh and deterministic * docs(adobe-firefly): document renewal controls * fix(adobe-firefly): harden CDP warm, risk session, and browser sign-in Stop colligo 408 thrash from stale Forter and frozen Google login during Sign in with browser: - CDP warm: clear Firefly origin storage + risk cookies (keep SSO); require forter age under 10 minutes on loop and timeout paths; dual CDP queues; await Runtime.runIfWaitingForDebugger; profile-lock launch retries - Session: connectionId fingerprint; write-back JWT+Cookie; warm-fail cooldown; fail closed risk_session_stale when forter is known-stale - Client: submit gate around generate-async; max 2 attempts when forter known-stale; poll 401 one refresh; pass sessionBrowserKey through handlers - Login route: pure system Chrome/Edge CDP only; camelCase credential persist - Unit: browser-login + firefly suites green (60) * fix(adobe-firefly): dedupe CDP session hardening blocks after rebase Remove duplicated guard blocks and test bodies introduced when rebasing the CDP session hardening work onto release/v3.8.50, which already carries the hardened implementation. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…nce#9556) * fix(translator): preserve Kimi K3 Responses reasoning * fix(translator): make K3 reasoning preservation model-driven * fix(translator): replay cached Kimi reasoning before fallback * fix(translator): keep authentic K3 reasoning through cleanup * refactor(reasoning): use replay policy for K3
…ence#8736) * feat(providers): add support for TinyCMS Web including WASM-based cryptographic signing and Proof-of-Work emulation * feat(providers): add unit tests, ESLint suppressions, and fix hardcoded userid for TinyCMS Web - Add unit tests for WASM init, UUID validation, challenge flow (15 tests) - Add WASM source comment explaining binary origin - Replace hardcoded userid with dynamic provider-specific data - Add ESLint suppressions for no-explicit-any in WASM bridge code - Add explanatory comments for DOM shim (runtime WASM-bindgen, not test mocks) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(providers): extract TinyCMS DOM shims into an explicit setup function tinycmsSigner.ts installed its window/document/HTMLCanvasElement/ CanvasRenderingContext2D shims for the wasm-bindgen glue as a module-load side effect. That meant merely importing the module (even transitively, e.g. through the provider registry from an unrelated test) mutated global state for the rest of the test process. Extract the shim installation into setupDomMocks(), which returns a restore callback: - initTinyCmsWasm() calls it once before instantiating the WASM module (production path — unchanged behavior, still automatic). - tests/unit/provider-tinycms-web.test.ts now calls it explicitly in a `before` hook and restores the previous globals in `after`, so the shims never leak into other test files. As a side effect, replacing five separate `as any` casts with a single typed `global as Record<string, any>` handle drops the file's no-explicit-any count from 5 to 1; eslint-suppressions.json updated to match. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(providers): regenerate PROVIDER_REFERENCE.md for tinycms-web Mechanical `npm run gen:provider-reference` run after merging release/ v3.8.50 into this branch — the generated table was stale for both the new tinycms-web entry this PR adds and the release's own cheaperinference addition. Total providers 290 -> 292, Web Cookie Providers 31 -> 32. 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>
…ek / MiniMax / Bedrock across streaming, non-streaming, and Dashboard paths (cheaper-inference#8591) * fix(cheaper-inference#8171): map DeepSeek prompt_cache_hit_tokens into prompt_tokens_details.cached_tokens DeepSeek native API returns cache stats in flat top-level fields (prompt_cache_hit_tokens / prompt_cache_miss_tokens) instead of the standard prompt_tokens_details.cached_tokens. The usage sanitizer (sanitizeUsage / sanitizeResponsesUsage) was stripping these non-standard fields, so clients never received real cache hit counts even when the upstream served cached responses. Changes: - sanitizeUsage(): map prompt_cache_hit_tokens into prompt_tokens_details.cached_tokens when the latter is unset - sanitizeResponsesUsage(): same mapping for input_tokens_details - filterUsageForFormat(): add prompt_cache_hit_tokens and prompt_cache_miss_tokens to the default format allow list so they survive field-level filtering * fix: passthrough non-standard cache token fields for DeepSeek / MiniMax / Bedrock across streaming, non-streaming, and Dashboard paths * fix(sse): shrink cache-hit token passthrough to fit file-size gate PR cheaper-inference#8591 added a DeepSeek/MiniMax/Bedrock flat cache-hit-token -> nested prompt_tokens_details.cached_tokens mapping (cheaper-inference#8171) that grew responseSanitizer.ts and stream.ts past their frozen file-size baselines. - Extract the chat-completions/Responses-API mapping logic into a new leaf module (responseSanitizer/cacheHitTokens.ts). - Move the streaming-path rebuild into filterUsageForFormat() (usageTracking.ts), the single conversion chokepoint both stream.ts call sites already used, eliminating the duplicated stream.ts patch entirely. - Rebaseline responseSanitizer.ts by the 2 lines that remain irreducible (the mandatory ES import for the extracted helper). Behavior verified unchanged via the existing response-sanitizer and stream-handler unit suites. Co-authored-by: ikelvingo <ikelvingo@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: ikelvingo <ikelvingo@users.noreply.github.com>
…nference#10002) The doc's own breakdown at line 11 (42+3+4+3+6+8+8+6+22+2) sums to 104, matching the two existing '104 unique tools' mentions. The '105 tools' mentions in the intro and cardinality-reduction section were stale and inconsistent with the documented source of truth.
…#9087) * docs: document provider model refresh fix Document the verified live-model refresh path for stale provider catalogs, record the current Pollinations anonymous-access limitation, and sync the provider-count references after regenerating the provider reference. Co-Authored-By: Oz <oz-agent@warp.dev> * docs: note codex local env and mac path Co-Authored-By: Oz <oz-agent@warp.dev> --------- Co-authored-by: Oz <oz-agent@warp.dev>
…heaper-inference#6674) (cheaper-inference#9421) Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
… work (cheaper-inference#9463) * fix(providers): switch minimax from claude to openai format so images work The Anthropic-compatible /anthropic/v1/messages endpoint rejects image input with 403. MiniMax's OpenAI-compatible /v1/chat/completions endpoint supports image_url natively for MiniMax-M3. - minimax + minimax-cn: format claude→openai, baseUrl→/v1/chat/completions - Remove Anthropic-Version header + ?beta=true suffix (not needed for openai) - Remove minimax/minimax-cn from ?beta=true executor case - Update cache-control tests (openai format uses different caching path) - Fix reasoning-split test names (no longer claude format) TDD: 2 registry tests assert format=openai (red→green). Refs: Hermes Agent cheaper-inference#15715, MiniMax OpenAI-compatible API docs. * fix(sse): re-align stream-readiness-policy tests with minimax's openai format PR cheaper-inference#9463 switched minimax/minimax-cn from claude to openai format so images work. The stream-readiness bump for Claude-format replicas is keyed off the registry's format field (single source of truth), so minimax legitimately falls out of that group now. Swap the "Claude-format replica" test fixtures to agentrouter (still format: "claude") and add explicit coverage that minimax no longer gets the claude_format_heavy_reasoning bump. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
…cheaper-inference#9572) * fix(providers): refuse to store the dashboard password as a connection API key A browser autofilled the management password into a connection's API-key field. The resulting credential authenticates against nothing, so every request routed through that connection came back 401, and because the field looks like any other password input the same autofill fired again while the connection was being repaired by hand. The refusal belongs on the write path rather than in the form. Twenty routes create or update connections and all of them funnel through createProviderConnection and updateProviderConnection, so one check there covers every entry point including a future one. The two other places that write api_key are left alone on purpose: one re-encrypts rows that already exist and the other is the one-time db.json import, and neither takes a value an operator just typed. Update checks the incoming value, never the merged one. A connection that already holds the password has to stay editable or the operator cannot repair the exact state this prevents, and re-checking the merged value would spend a bcrypt round on every unrelated field edit. Only a real match blocks the write. An unreadable settings row or a throwing bcrypt call logs and allows, because a guard against one specific mistake must not turn into a way to lock out every connection write. Signed-off-by: Minxi Hou <houminxi@gmail.com> * fix(providers): compare the untrimmed credential, and cover the guard's branches The guard trimmed the incoming value before comparing it, which catches a paste carrying whitespace the password does not have. It missed the mirror case: neither the login route nor the set-password route trims, so a dashboard password may itself begin or end with a space, and an autofill reproducing it exactly was trimmed into a value that no longer matched the stored hash. The write then went through, which is the state this guard exists to prevent. Both forms are compared now, the second only when the first fails on a string that differs, so an ordinary key still costs a single bcrypt round. Two branches carried no coverage and both are load-bearing. The catch that logs and allows is the only path that lets a write through; a stored hash bcrypt cannot parse reaches it without needing a mock, since the shape check accepts an impossible cost factor that the comparison then rejects. The early return is what keeps a token renewal -- a write carrying tokens but no apiKey -- from paying for a settings read and a bcrypt round every time it fires, and the same unparseable hash makes that path observable, so an absent warning is proof the return happened. The narrower scope is deliberate and now says so in the code: the OAuth tokens arrive from a provider's token endpoint rather than from a form, so extending the comparison to them would charge every renewal for a field no autofill can reach. Signed-off-by: Minxi Hou <houminxi@gmail.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
…ference#9605) * ci(test): route orphaned Vitest tests through blocking CI * docs: fix advisory status in AGENTS.md and refresh baseline note * fix(changelog): fix fragment format for cheaper-inference#9415 * fix(changelog): preserve upstream fragment format --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
…nference#9631) * feat(db): add a job registry for scheduled background work Background jobs each ship their own timer today, so there is no list of what is scheduled, no history of what ran, and no way to pause one without an environment variable and a restart. The registry gives them one home: a jobs table holding the schedule, a job_runs table holding the outcomes, and a loopback-only API to inspect and control both. Cron jobs read their expression through an optional cronGetter rather than the stored column, so an operator changing OMNIROUTE_WARMUP_CRON does not need the row rewritten. register() is an idempotent upsert that refreshes the schedule but never overwrites `enabled` or `created_at`, which is what lets a job be re-registered on every boot without discarding the operator's toggle. Run history is pruned per job rather than globally, and safeRun records a failure for a handler that throws as well as one that returns success:false, so a crashing job leaves a trail instead of a gap. The API is under /api/jobs and gated to loopback in the route guard. It can trigger a run and flip a job off, which is runtime administration and does not belong on a remotely reachable surface. Signed-off-by: Minxi Hou <houminxi@gmail.com> * feat(jobs): move the budget reset and token health check onto the registry Both jobs owned their own timer and started themselves as an import side effect, so nothing could report whether they were running, when they last ran, or why a run failed. They now register with the job registry and are started from it, which also means their schedule and run history are visible through /api/jobs. startAll() runs each interval job's first tick synchronously, so both entry points start the registry only after initializeCloudSync() has been awaited. The old wiring reached that ordering two different ways: the budget reset was started after the init call, and the health check's first sweep sat behind a 10s timer. Replacing both with one startAll() would otherwise have moved the two handlers in front of the initialisation they run against. Both entry points also register the same pair of jobs. Registering one and not the other is how a background job goes missing without anything failing. sweep() now returns how many connections it swept, so the health check can record a real records_affected the way the budget reset does. The migration documents that column as a per-job count, and hardcoding zero would have left one of the two jobs reporting a number the schema promises but the code never produces. A skipped or empty sweep reports zero. Every existing caller ignores the return value. The token health check keeps its own disable semantics: the handler still calls isHealthCheckDisabled() before sweeping, so OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK, the production-build phase and the automated-test guard behave as before. Its registry adapter lives in src/lib/jobs/ next to the budget reset rather than in tokenHealthCheck.ts, which is already above its frozen size ceiling on the base branch and should not grow further. The adapter lets a failing sweep throw rather than reporting it itself, matching the budget reset: safeRun records a thrown error as a failure run with its message. The warmup job is seeded disabled. Its handler arrives with the warmup scheduler, and startAll() filters on enabled before it looks for a handler, so seeding it enabled here would warn about the missing handler on every boot. * fix: allowlist cron-parser dep and document OMNIROUTE_RUNNOW_TIMEOUT_MS env var Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * chore: align rebased branch with release tip (migration renumbered 139->146 in release; feature already cherry-picked in cheaper-inference#9886) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
cheaper-inference#9634) * fix(combo): restore routing module load * fix(db): resolve ccr migration version collision Renumber the CCR block-store migration from 134 to 139, reconcile databases that already applied the legacy slot, and add regression coverage for both upgrade paths. Co-Authored-By: GPT-5 <noreply@openai.com> * fix(test): narrow this branch to the drifted test expectations Three other PRs already cover what this one was carrying. cheaper-inference#9618 renumbers the colliding ccr_blocks migration, cheaper-inference#9632 repairs the malformed aggregator changelog fragment, and cheaper-inference#9676 restores the combo module load by implementing the selection helper the import was reaching for, rather than deleting the caller the way this branch did. Keeping any of it here would put two files back on the same migration slot and overwrite a better fix with a worse one. What survives is the part none of them touch. Once the combo barrel loads again, three assertions in the context-window filter suite start failing: they demand that catalog-too-small targets be dropped, while the file's own header and its four neighbouring tests say those targets stay available as runtime fallback. The unresolved import was masking them. A new case pins the output-token limit as a genuine hard requirement so the relaxation cannot drift further. The provider count assertion kept one literal at the old value after the rest of the file moved to 198, so the partition check failed on a sum that was correct. * fix(release): restore base-relative reconcile to mergeable state Rebase fix/release-v3850-basereds onto release/v3.8.50 resolving conflicts. The substantive changes (ccr_blocks renumber cheaper-inference#9618, aggregator changelog well-formedness cheaper-inference#9632, combo module load cheaper-inference#9676) are already covered on the release tip. Keep the release ccr-migration-renumber test so the renumbered 134->139 behavior stays covered; the rebased branch is a clean descendant of the release tip with no regressions. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: alexey.nazarov@softmg.ru <alexey.nazarov@softmg.ru> Co-authored-by: GPT-5 <noreply@openai.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…heaper-inference#9675) Rebase of PR cheaper-inference#9675 onto origin/release/v3.8.50. This feature was already cherry-picked into the release branch (commit 58f0ff1, PR cheaper-inference#9873), so the branch is reconciled to the release tip, resolving the merge conflict without reintroducing duplicate i18n keys or stray content. Co-authored-by: Hermes Agent <hermes@hermes-chloe.hyades.io> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…erence#9773) The provider-connection dialog (AddApiKeyModal / EditConnectionModal) rendered humanized key names instead of real copy for providers.validationModelId{Label,Placeholder,Hint} in 34 of 43 locales — the values read "Validation Model Id Label", "Validation Model Id Placeholder" and "Validation Model Id Hint" verbatim. Each translation follows the terminology and register already used by the neighbouring provider keys in its own file — e.g. de Anbieter/API-Schlüssel with formal Sie, fr fournisseur/clé API, ru провайдер/ключ API — and each locale's own "e.g." convention (z. B., 例:, напр., ör., cth., hal.). Source of truth is en.json, which labels the field "Validation Model" (no "ID"); a few older locales say "validation model ID" and were left untouched rather than propagating that divergence.
…oviders (cheaper-inference#9777) The /v1/models catalog mirrors `claude/<provider>/<model>` ids purely from the alias gate -- ccAliasPredicate.ts consults no provider registry. The request path additionally required the prefix to be an open-sse REGISTRY entry or an operator-defined custom node. Enterprise-cloud providers such as azure-ai / azure-openai live only in the provider catalog (src/shared/constants/providers/apikey/enterprise-cloud.ts). They route fine directly -- `azure-ai/Phi-4` returns 200 -- but have no open-sse registry entry, so the two sides disagreed: the catalog advertised `claude/azure-ai/<model>` while stripCcDiscoveryAlias refused to strip it. The unstripped id then fell through to normal resolution, which splits on the first / and parsed `claude` as the provider. Every Claude Code request for an Azure model was routed to the Claude provider instead: ROUTING: Provider: claude, Model: azure-ai/DeepSeek-V4-Flash Extract the predicate as `isRoutableProviderPrefix()` and widen it to the provider catalog (id + alias) alongside the open-sse registry, so the request path recognises exactly what the catalog can advertise. Regression guard: tests/unit/cc-discovery-alias-routable-prefix.test.ts pins azure-ai/azure-openai/azure as routable, keeps openai/anthropic routable, and keeps an unknown prefix non-routable. Verified failing before the widening.
…-spoke pivot (cheaper-inference#9783) Step 1 of the pivot (openai-responses -> openai) flattens namespace sub-tools to a qualified wire name (cheaper-inference#8295) and records the `{namespace, name}` pair on a non-enumerable `_toolNameMap`. Step 2 (openai -> target) returns a brand-new object, so the property was dropped for every non-OpenAI target. chatCore then handed `null` to the cheaper-inference#7936 response seam and namespace sub-tool calls reached the client under their flattened name, which Codex rejects with `unsupported call: <name>` — the symptom cheaper-inference#7936 was opened to fix. Copying `_toolNameMap` through is not viable: openai-to-claude and openai-to-gemini publish their own `Map<string, string>` alias map on that same property during step 2, so it carries two incompatible types. This adds a dedicated `_namespaceToolIdentityMap`, propagated by translateRequest across the pivot; chatCore prefers it and falls back to `_toolNameMap` for the non-pivot producers. Both keys are stripped from the cliproxyapi wire body. Fixes cheaper-inference#9780
…utput tokens (cheaper-inference#9787) * fix(sse): apply Azure request-param rules on the azure-ai wire path Azure rejects several stock Chat Completions params on its newer deployments and returns HTTP 400 rather than ignoring them: max_tokens -> 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead. reasoning_effort -> Function tools with reasoning_effort are not supported. Those rules lived inline in AzureOpenAIExecutor, so they only covered the azure-openai provider. azure-ai (Azure AI Foundry) had no executor entry and fell through to the bare DefaultExecutor, so the SAME Azure deployment succeeded on one connection and 400'd on the other. Every agentic client sends tools on every turn, so azure-ai failed on the first request. Extract the rules to open-sse/executors/azureParamRules.ts, add an AzureAiExecutor that inherits DefaultExecutor's azure-ai URL/header/apiType handling unchanged and applies the shared rules, and register it for azure-ai. Also widen the deployment pattern to cover gpt-chat-latest: it is a moving alias that resolves to a GPT-5-era model and rejects max_tokens, but carries no version number for the token-boundary pattern to key on. Verified against the base regex - gpt-chat-latest did not match, which is exactly the observed 400. Regression guard: tests/unit/azure-param-rules.test.ts, including an assertion that getExecutor("azure-ai") no longer resolves to a bare DefaultExecutor. * fix(sse): clamp Azure gpt-4o-mini completion tokens to its 16384 ceiling Azure gpt-4o-mini deployments accept at most 16384 completion tokens and 400 on anything larger: max_tokens is too large: 32000. This model supports at most 16384 completion tokens, whereas you provided 32000. The 32000 is OmniRoute's own doing: adjustMaxTokens raises any smaller max_tokens to DEFAULT_MIN_TOKENS (32000) whenever tools are present, to avoid truncated tool arguments. That floor has no upper bound, so an agentic client asking for far less still trips the model ceiling on its first turn. Add scoped maxOutputCap rules in paramSupport.ts for both Azure wire paths. PROVIDER_MAX_TOKENS is the wrong lever here - it is provider-wide, and the same Azure resource also serves GPT-5 deployments with a much higher ceiling. Regression guard: tests/unit/azure-max-output-clamp.test.ts, which also pins that the clamp does not leak to gpt-5.1 or to gpt-4o-mini on other providers.
…g fields (cheaper-inference#9790) copyOpenAICompatibleReasoningFields only stripped the sentinel (NON_ANTHROPIC_THINKING_PLACEHOLDER = "(prior reasoning summary unavailable)") from reasoning_content and reasoning. Non-standard reasoning fields (reasoning_text, thinking, thought) and reasoning_details items passed through raw, leaking the internal replay sentinel to clients on providers that use those fields (e.g. Venice), where the model echo surfaces as a bogus thought block and can degrade into empty turns. Strip the sentinel from every forwarded reasoning field, including per-item text/content inside reasoning_details; drop items/fields that strip to nothing while preserving non-text details such as reasoning.encrypted. Fixes cheaper-inference#9765 Refs cheaper-inference#8081, cheaper-inference#9606
* feat(sse): add Vertex AI DeepSeek OCR transformation to the registry Adds VERTEX_DEEPSEEK_TRANSFORMATION (request/response mapping for the Vertex AI DeepSeek OCR MaaS endpoint) and registers the "vertex-deepseek-ocr" provider in OCR_PROVIDERS, modeled on litellm's VertexAIDeepSeekOCRConfig. buildRequest treats the resolved baseUrl as the complete Vertex endpoint URL (project/location resolved upstream), matching the existing Mistral passthrough pattern. * feat(sse): resolve Vertex AI DeepSeek OCR auth and endpoint URL Adds resolveVertexOcrAccessToken (mints a Vertex OAuth access token from a Service Account JSON apiKey, reusing open-sse/executors/vertex.ts's existing JWT-bearer exchange — no new OAuth flow) and resolveVertexOcrBaseUrl (derives the project/location "openapi/chat/ completions" endpoint from providerSpecificData or the Service Account JSON's project_id). Both live in open-sse/handlers/ocr.ts, not the src/app/api/v1/ocr route, since routes may not import executor implementations directly (EXECUTOR_IMPORT_RESTRICTION in eslint.config.mjs) — the route re-exports/consumes them across that boundary. handleOcr now prefers credentials.accessToken over apiKey so the minted token (not the raw Service Account JSON) is sent upstream. * docs(api): document the vertex-deepseek-ocr /v1/ocr provider Adds the vertex-deepseek-ocr row to the /v1/ocr provider table and a short section on its Vertex AI auth/endpoint resolution, and lists the new provider/model id in openapi.yaml alongside mistral and azure-document-intelligence. * docs(skills): regenerate omni-inference skill for the Vertex OCR provider --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me>
… and preflight (cheaper-inference#10290) * fix(sse): surface Qwen/Alibaba personal Token Plan quota in dashboard and preflight The personal Token Plan (5-hour / 7-day sliding windows) has no official OpenAPI and the inference API key cannot read it. Add a cookie-authenticated fetcher for the console gateway shared by home.qwencloud.com and the Model Studio console (contract captured live from a logged-in session): - open-sse/services/qwenTokenPlanQuotaFetcher.ts: POST /data/api.json (IntlBroadScopeAspnGateway / sfm_bailian) for usage + quota-config + subscription; sec_token resolved best-effort from the dashboard HTML; per-window parse (fields are omitted while a window is Temporarily Removed); 60s usage cache, 1h tier cache. - usage/qwen-token-plan.ts leaf + registration in the usage dispatcher, USAGE_FETCHER_PROVIDERS, USAGE_SUPPORTED_PROVIDERS, PROVIDER_LIMITS_APIKEY_PROVIDERS and bespoke preflight/monitor windows. - Also adds bailian-coding-plan to USAGE_SUPPORTED_PROVIDERS / PROVIDER_LIMITS_APIKEY_PROVIDERS: the coding-plan fetcher existed but the dashboard filtered those connections out (UI gap). Refs cheaper-inference#9603 (Problema 1 — quota missing; the 429 recovery half is a follow-up). * docs(env): document Qwen Token Plan quota env vars + regen omni-settings skill QWEN_CLOUD_COOKIE, QWEN_CLOUD_SEC_TOKEN, QWEN_TOKEN_PLAN_HOST and QWEN_TOKEN_PLAN_DASHBOARD_URL added to .env.example and docs/reference/ENVIRONMENT.md (check:env-doc-sync), with the generated omni-settings skill refreshed (check:agent-skills-sync). Refs cheaper-inference#9603 * revert: keep hand-tuned omni-settings thinking-budget section The agent-skills-sync drift predates this PR (hand improvement from cheaper-inference#10169 not yet synced into the generator source) — it fails on every open PR and belongs to a base-reds fix, not this branch. Regenerating here would erase the intentional content. * feat(dashboard): add the Qwen/Model Studio console cookie field to the connection modal The Token Plan quota fetcher is cookie-authenticated (the inference API key cannot read the console gateway), but no modal field existed to paste that cookie — so the quota was unconfigurable from the dashboard and the fetcher could only ever return its 'needs a cookie' message. Adds the field for qwen-cloud-token-plan and bailian-coding-plan alongside the existing ollama-cloud / alibaba console-cookie inputs (same password-input, blank-keeps-stored semantics), pre-fills it when editing a connection, and extends the providerSpecificData string/length validation to the two new keys. Tests: tests/unit/qwen-token-plan-cookie-field.test.ts (RED before, GREEN after) covers persistence + trimming, the blank-input no-overwrite rule and schema acceptance/rejection. Refs cheaper-inference#9603 * docs(dashboard): correct the Qwen console cookie instructions The placeholder claimed the cookie looks like 'token=...'; the qwencloud portal actually issues 'login_qwencloud_ticket=...' alongside cna/cnaui/aui (mirroring login_aliyunid_ticket on the Alibaba console), so the hint pointed at the wrong value. Replaces the guesswork with the verified retrieval steps in all three places an operator can hit — the modal field hint, the fetcher's 'needs a cookie' message and .env.example/ENVIRONMENT.md: log in to home.qwencloud.com > Billing > Subscription, F12 > Network, reload, filter by api.json, click a request to cs-data.qwencloud.com and copy the WHOLE Cookie request header. Also documents that the value must go on one line (it contains '=' and ';') and that it dies with the browser session. Refs cheaper-inference#9603 * fix(dashboard): tolerate partial form objects in the qwen cookie branch Adding bailian-coding-plan to QWEN_TOKEN_PLAN_PROVIDERS routed callers that previously matched NO branch in assignQuotaScrapingProviderData into the new one, which assumed the two new fields are always present. Older callers build a partial form object, so buildAddProviderSpecificData threw: TypeError: Cannot read properties of undefined (reading 'trim') (tests/unit/dashboard/agentrouter-connection-modal-fields.test.ts) Reads the new fields with optional chaining and adds a regression test that calls the helper with those keys deleted for both providers. Refs cheaper-inference#9603 * refactor(dashboard): move quota-scraping form logic into a UI-free module tests/unit/qwen-token-plan-cookie-field.test.ts imported QuotaScrapingFields directly, which pulls `@/shared/components` and, through that barrel, untranspiled ESM (@lobehub/icons). The node:test runner cannot parse it and the whole test file died in CI with: SyntaxError: Unexpected token 'export' at @lobehub/icons/es/Ai21/components/Mono.js (It passed locally, so only the CI shard surfaced it.) Extracts the pure pieces — QWEN_TOKEN_PLAN_PROVIDERS, QuotaScrapingFieldValues, EMPTY_QUOTA_SCRAPING_FIELDS and assignQuotaScrapingProviderData — into quotaScrapingFieldValues.ts. The component imports them and re-exports the public names, so every existing importer keeps its current path. The unit test now targets the UI-free module. Refs cheaper-inference#9603 * fix(providers): point bailian-coding-plan at the Token Plan endpoint and its console Two independent defects kept this provider unusable with a valid Alibaba Token Plan key (verified live 2026-08-14 with the owner's key and cookie): 1. Wrong inference host. The catalog entry is named "Alibaba Token Plan", links to token-plan-overview and its hint asks for a Token Plan key, but the registry pointed at coding-intl.dashscope.aliyuncs.com — the Coding Plan host, which rejects Token Plan keys with 401 invalid_api_key. The documented Anthropic base URL for Token Plan is token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic (https://www.alibabacloud.com/help/en/model-studio/more-tools). Against the new host the same key returns 200 for all six registry models and a real completion; auth stays on x-api-key. 2. Wrong console identity for quota. The personal Token Plan is sold through two consoles sharing one backend, and the gateway validates the session against the console declared in the request: an Alibaba console cookie (login_aliyunid_ticket) sent with the QwenCloud identity is refused with BailianGateway.Login.NotLogined. resolveConsoleSite() now picks host, cornerstoneParam.consoleSite/domain and Origin/Referer from the cookie's login ticket, falling back to the provider. With that switch the same cookie returns usage/subscription/quota-config. Also routes bailian-coding-plan quota through the Token Plan fetcher (the Coding Plan call returns "Bad Request" for these accounts), keeping the old fetcher as the fallback for real Coding Plan keys, and labels the plan by console ("Alibaba Token Plan (Pro)" vs "Qwen …"). Live validation: inference 200 (qwen3.7-plus answered "FUNCIONA"); quota 12,934/40,000 credits, 67.7% remaining, resets 2026-08-20. Refs cheaper-inference#9603 --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…oss-cumulative-integration
…en Plan endpoint (cheaper-inference#10410) cheaper-inference#10290 moved bailian-coding-plan from the Coding Plan host to the documented Token Plan one, but the provider/translate-path golden still pinned coding-intl.dashscope.aliyuncs.com, so tests/unit/provider-translate-path-golden.test.ts fails on the release tip. Regenerates the snapshot (UPDATE_GOLDEN=1) — the diff is exactly the two bailian-coding-plan URLs, every other provider byte-identical — and fixes the same stale host in the endpoint matrix of docs/providers/ALIBABA-QWEN-PROVIDER-FAMILIES.md. This golden covers every provider's resolved URL, which is why neither the focused tests nor typecheck caught the change: only the unit shard runs it. Refs cheaper-inference#9603 Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…ning (cheaper-inference#10408) * fix(ci): pin Build (advisory) to a hosted runner with memory provisioning `Build (advisory)` has been reporting a permanent red on every PR while producing no usable signal at all. Measured over the last 25 quality.yml runs (2026-08-14): not one instance of the job reached a conclusion. Every sample was either queued on the self-hosted pool — 2 runners, omniroute-113-6/7, both permanently busy; one job sat queued for over 2 hours and was still unclaimed — or, when it did land on a runner, killed mid-build by this workflow's own cancel-in-progress concurrency. All 6 sampled "failures" are exit 143 / "The runner has received a shutdown signal" at ~3.5 min into `npm run build`. Zero OOM, zero build errors. The job was consuming a runner the real gates compete for while telling every PR author it was broken. Gap 19 deliberately left USE_VPS_RUNNER governing build-like jobs, on the premise that the build needs the .113's RAM. That premise no longer holds: `Fast Production Build` (build.yml) runs `build:release` — a superset of this job's `npm run build`, plus the CLI bundle — on plain ubuntu-latest and passed 24 of its last 25 runs in ~15 min. The difference is memory PROVISIONING, not the machine: a 10 GB swapfile plus a 12 GB V8 heap. Swap is the part that matters, because --max-old-space-size bounds only V8's JS heap and never Turbopack's native Rust allocation (cheaper-inference#6409). Pins the job to ubuntu-latest and mirrors both settings from build.yml. USE_VPS_RUNNER keeps its other consumers (ci.yml Build, nightly-release-green, npm-publish), so the variable stays meaningful. Fork safety is strictly improved: no PR can reach the LAN runner through this job any more. check:workflows --ratchet: 186 zizmor findings, baseline 190, no regression. prettier + YAML parse: clean. * fix(ci): scope Build (advisory) to fork PRs Follow-up to the hosted-runner pin in this same PR, after measuring what the job is actually for. build.yml's `Fast Production Build` triggers on `push: branches: ["**"]` and runs `build:release` — a superset of this job's `npm run build`, plus the CLI bundle. For an own-origin branch that push fires here, so the tree was being built twice per PR. A fork contributor pushes to THEIR repo, so build.yml never runs in this repo and this job is their only pre-merge build signal. That could have argued for deleting the job, except the traffic says otherwise: 72 of the last 100 PRs into release/** come from forks. The fork case is the majority, not the exception. So the job earns its place — it just should not duplicate build.yml for the own-origin 28%. Added the fork filter to the existing `if`. Also corrects the reliability claim in the previous commit message. Over a wider window the job is not literally never-green: across 2026-08-13/14 it reached `success` on roughly 10-15% of runs (13/138 on 08-14, 7/53 sampled on 08-13). Chronically unreliable, not permanently dead — the conclusion and the fix are unchanged. The cheaper-inference#7307 guard in tests/unit/build/check-workflows.test.ts pinned the old self-hosted expression, so it is realigned here: it now asserts the hosted pin, the absence of self-hosted/USE_VPS_RUNNER in the job's DIRECTIVES (the comment legitimately explains why the pool was abandoned, so the scan strips comments), both memory settings, and the fork filter. Mutation-validated — restoring self-hosted, dropping the swapfile, or flipping the fork filter each turns it red. check-workflows.test.ts: 32 pass, 0 fail. check:workflows --ratchet: 186 findings, baseline 190, no regression. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…oss-cumulative-integration
… vs model) (cheaper-inference#10419) Makes the ProviderErrorRule `scope` field real at the persistence layer, exclusively for agentrouter (owner decision; every other provider keeps byte-identical behavior). checkFallbackError now surfaces `ruleScope` behind the HONORS_RULE_LOCK_SCOPE_PROVIDERS allowlist, and the agentrouter 403 path consults the rules before the generic apikey-FORBIDDEN early-return. markAccountUnavailable honors scope "connection" with a temporary connection cooldown instead of a per-model lockout — guarded so a permanent state can never be downgraded to a transient retry loop — and combo now skips the exhausted account within the same request, which also stops force-reusing the just-cooled connection via allowRateLimitedConnection. Documented in RESILIENCE_GUIDE §7 with the honest limits (disableCooling connections keep per-model behavior; the 6h model-access cooldown is clamped by mlSettings.maxCooldownMs, 30min by default; same-request skip needs targets carrying their own connectionId). Closes cheaper-inference#10334
…oss-cumulative-integration
… language matrix (cheaper-inference#10425) i-have-adhd shipped in cheaper-inference#10271 with en + pt-BR only; it now covers vi/ja/id as well, matching ponytail. Every level keeps the SHARED_BOUNDARIES clause. Adds a per-MATRIX guard (output-styles-i18n-matrix.test.ts). Every existing test is per-style, which is how less-code stayed English-only since the 9router port without anyone noticing. The guard fails on: a new style without pt-BR, a style losing a translation it had, a translation missing an intensity level, a translation that dropped the boundaries clause, and a stale KNOWN_ENGLISH_ONLY entry. Proven by mutation, not just by passing: dropping less-code from the allowlist and renaming the vi key both turned it red with the expected messages. less-code stays English-only as declared debt (KNOWN_ENGLISH_ONLY + comment); the remaining coverage work is tracked in cheaper-inference#10426.
…unded (cheaper-inference#10423) * fix(db): prune pre-migration backups so db_backups stops growing unbounded createPreMigrationBackup() wrote a VACUUM INTO snapshot on every migration run and never pruned. On a long-lived instance db_backups/ reached 48.999 files / 204 GB against a 5,3 MB live database; a second devbox showed the same shape (5.711 files / 24 GB). The retention policy already existed in cleanupDbBackups() but nothing on the migration path reached it — its only callers are backup.ts and the /api/db-backups route, neither of which runs during a migration. migrationRunner.ts cannot import backup.ts: core.ts imports migrationRunner.ts and backup.ts imports core.ts, so that edge would close a cycle. The policy therefore moves to a new core-free module, backupRetention.ts, which both call sites share — cleanupDbBackups() now delegates to it rather than duplicating it. At the migration call site the operator's maxFiles/retentionDays are read through the adapter already open for the run; going through getDbInstance() would re-enter database initialization. Pruning never throws, so housekeeping cannot fail a migration. Closes cheaper-inference#10421 * chore(db): declare backupRetention as an intentionally-internal db module check:db-rules requires every src/lib/db/ module to be either re-exported by localDb.ts or listed in INTENTIONALLY_INTERNAL. backupRetention.ts is a shared primitive consumed only by db/backup.ts and db/migrationRunner.ts — the same category as the migrationRunner entry — so it belongs in the allowlist rather than in the public re-export surface. * test(db): include backupRetention in the audited INTENTIONALLY_INTERNAL list check-db-rules-classification.test.ts freezes the exact membership of INTENTIONALLY_INTERNAL, so adding the 40th entry has to be reflected there too — the gate script and this test pin the same contract from opposite sides. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…429/400/401) (cheaper-inference#10112) * docs: add rate limiting guide for free providers (429/400/401) Community-reported troubleshooting for auto-discovered issues when rotating through free/no-auth providers (opencode, felo-web, auggie). Documents the verified env-var combo that eliminates intermittent 429/400/401 failures in cron/agent automation: OMNIROUTE_ROTATE_ON_400=true, OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT=4, OMNIROUTE_STRUCTURE_LIMIT=off Includes root-cause breakdown (provider quota vs passthrough 401 vs concurrency amplification), verification steps via /monitoring/health, and escalation for hard quota exhaustion. * docs(providers): fix fabricated env var and breaker states in rate-limit guide Replace OMNIROUTE_STRUCTURE_LIMIT (does not exist in the codebase) with OMNIROUTE_CHAT_ADMISSION_QUEUE_MS and document the real rate-limit knobs (RATE_LIMIT_MAX_WAIT_MS / RATE_LIMIT_MAX_QUEUE_DEPTH / RATE_LIMIT_AUTO_ENABLE). Correct the circuit breaker states to the actual enum (CLOSED/DEGRADED/OPEN/HALF_OPEN) and point the health-check note at circuitBreakers.providerBreakers[].state. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Bruno <bruno@nousresearch.com> Co-authored-by: mrcram2021 <mrcram2021@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
…oss-cumulative-integration
…e the free-forever number (cheaper-inference#10433) Audit of every numeric claim in the README, AGENTS.md and the README SVGs against live code, plus a changelog/credit reconciliation over the full v3.8.50 cycle. Corrected numbers (all measured, not estimated): - Provider circuit breaker thresholds were scaled up in code for 500+ connection deployments (`providerFailureThreshold`: OAuth 3 -> 10, API key 5 -> 15) but the docs still published the pre-scale values. Fixed in AGENTS.md (now a table that also separates the provider-level threshold from the per-connection one and lists the provider cooldowns), in the README alt text and inside resilience-layers.svg (visible label and aria-label). - "40+ free forever" was unsourced. Measured from the free-tier catalog as every provider whose free access renews or needs no key (recurring-monthly, -daily, -uncapped, -credit, keyless; one-time signup credits and discontinued pools excluded): 56. Updated in the README and promise-pillars.svg. - Cycle-evolution table: v3.8.49 shipped 290 providers, not 291, and the model row compared the v3.8.49 free-tier catalog (516) against today's full catalog. Both columns now use the same metric - distinct documented models, 1185 -> 1202. - Tech-stack row: 95 domain modules -> 117. The free-forever count is now enforced by check:docs-counts so it cannot drift again; it is derived from freeType in the live catalog, like every other gated number. Changelog reconciliation (`scripts/release/list-uncovered-commits.mjs`): uncovered cycle commits drop from 149 to 62. 75 user-facing commits gained a bullet with author attribution, the 45 ref-less direct pushes and 29 chore/ci/test/docs commits were consolidated into rollup bullets, and the contributors table grew from 147 to 161 rows - 14 contributors who had landed work with no credit at all (including @amartinawi, @pacocartones and @excessivechaos) are now credited. The remaining 62 carry no PR/issue ref, which is the ceiling of ref-based coverage. CHANGELOG.md and its i18n mirrors are added to .prettierignore: check:changelog- integrity compares base bullets as exact strings, and Prettier normalizes markdown emphasis inside them (*from* -> _from_), so any PR that staged the changelog turned the merge-integrity job red. scripts/release/* is the changelog's formatter of record, the same precedent already used for ENVIRONMENT.md and PROVIDER_REFERENCE.md. Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…oss-cumulative-integration # Conflicts: # README.md # docs/diagrams/promise-pillars.svg
…e stack Integrates the reviewed F2-F7 and Radar hardening stack into release/v3.8.50 after exact-head CI and freeze gates.
…erence#10432) Any process that opened the DB without setting DATA_DIR resolved to ~/.omniroute/storage.sqlite — the operator's live database, provider credentials included. tests/_setup/isolateDataDir.ts only covers the npm scripts; the documented single-file test command and ad-hoc probes bypassed it (one did exactly that during cheaper-inference#10334). resolveWritableDataDir now redirects a test-context process with no DATA_DIR to a throwaway temp dir, stable per process. Redirect rather than throw, so the documented single-file command keeps working; OMNIROUTE_ALLOW_DEFAULT_DATA_DIR=1 opts back in and records the intent. Closes cheaper-inference#10428
…heaper-inference#10444) The packaged artifact stamped dist/BUILD_SHA but nothing verified the SHA belonged to the release line, so a tarball built from a feature branch installed and served traffic indistinguishably from a release build. That is how the internal gateway ended up running a build that predated cheaper-inference#10373 and answered every request with 502 'Executor result must contain a Response' — identifying it required SSH plus grepping the compiled chunks. scripts/build/buildProvenance.ts classifies a build SHA against the release ref (pure functions, injected git probe). A missing SHA fails even with the canary override: an unidentifiable artifact cannot be vouched for. validate-pack-artifact enforces it on real packs (skipped under --policy-only, which runs without a build); OMNIROUTE_ALLOW_CANARY_BUILD=1 records a deliberate off-release-line build instead of failing it. /api/monitoring/health now exposes system.buildSha — absent when unknown, never fabricated. Closes cheaper-inference#10427
…k anchor (cheaper-inference#10446) Deploying the internal gateway was a manual build/pack/scp/npm-i/pm2-restart sequence with no record of what landed and no proof it served traffic. On 2026-08-14 that shipped a package built from a branch predating cheaper-inference#10373: the process came up, health said 'healthy', and every request returned 502 until a human hit it. scripts/ops/deployCanary.ts holds the policy as pure functions — refuse an artifact that is not traceable to the release line (reusing cheaper-inference#10427), and grade the deploy on health PLUS at least one real completion. Zero probes fails: 'no probe ran' must never read as 'everything is fine', which is exactly how a broken egress path hides behind a green health check. Remote steps are argv arrays, never shell strings (Hard Rule cheaper-inference#13), ordered so the rollback anchor is captured before the install overwrites it. scripts/ops/deploy-canary.mjs performs the side effects, supports --dry-run, and prints the rollback command when the smoke fails. Closes cheaper-inference#10429
…aper-inference#10174) * feat(providers): add tencent-aistudio-web cookie provider (tasw) * fix(sse): remove orphaned DevinDesktopExecutor import from executor index The "devin-desktop" executor key is unused (devin-desktop provider config resolves to executor "devin-cli"); the imported ./devin-desktop.ts file was never present, so executors/index.ts failed to load (ERR_MODULE_NOT_FOUND) and broke every unit test that imports the executor registry (e.g. tests/unit/deepseek-web.test.ts). Stale base sync carried this into the branch. Remove the dead import/registration/export. * fix(providers): restore DevinDesktopExecutor registration in executor index The previous commit removed the devin-desktop executor import/registration/ export from open-sse/executors/index.ts, but the devin-desktop provider registry still resolves executor "devin-desktop" and tests/unit/devin-providers.test.ts asserts hasSpecializedExecutor("devin-desktop") is true. The removal broke 6 tests in that file. Restore the three lines so the live Devin Desktop executor keeps serving the provider. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): correct tencent-aistudio-web wrapper shape + provider count sync Return {response,url,headers,transformedBody} instead of a raw fetch Response (the executor contract every other executor in this file follows) and re-wrap the upstream body so it uses the local Response constructor, not the undici-patched one from globalThis.fetch. Regenerate docs/reference/PROVIDER_REFERENCE.md and sync the 339->340 provider-count claims (README, AGENTS.md, llm.txt + 42 i18n mirrors, package.json, promise-pillars/comparison-table/cli-terminal SVGs) that this PR's new provider invalidated. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(providers): sync readme-hero.svg provider count claim (339->340) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): register tencent-aistudio-web web-session credential metadata + golden Add the WEB_SESSION_CREDENTIAL_REQUIREMENTS entry for tencent-aistudio-web (cookie-based, matching the executor's raw Cookie-header credential) and regenerate the translate-path golden snapshot to include the new provider — both were failing CI unit tests that enumerate every registered provider. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): align tencent-aistudio-web test with the wrapper-shape contract The test asserted res.status/res.json() directly against executor.execute()'s return value, matching the pre-fix (broken) raw-Response shape. Update it to read res.response.status/res.response.json() — the {response,url,headers, transformedBody} contract every executor in this codebase follows. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: MeRezaRezaei <MeRezaRezaei@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
diegosouzapw
added a commit
that referenced
this pull request
Aug 18, 2026
…se when translating upstream responses to Claude Messages API format (cheaper-inference#10392) * fix(translator): Normalize tool call names from lowercase to PascalCase (#1) * Fix: Map lowercase tool names from Antigravity (Gemini format) to Claude Code expected PascalCase * Fix: toolNameMap in fun restoreClaudePassthroughToolUseName * fix(translator): Normalize tool call names from lowercase to PascalCase when translating upstream responses (OpenAI, Gemini, Antigravity) to Claude Messages API format This resolves `Error: No such tool available: read`/`bash`/`write` errors when using Claude Code CLI with third-party providers that emit lowercase tool names. The fix adds case-insensitive tool name lookups in `openai-to-claude.ts`, `gemini-to-claude.ts`, and related translators, ensuring tool names like `read`/`bash` are mapped to `Read`/`Bash` before being sent to Claude Code. Includes unit tests and comprehensive changelog notes ([cheaper-inference#10250](cheaper-inference#10250)) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(translator): Parse <tool_call> JSON and TOOL_CALL text formats fr… (cheaper-inference#2) * fix(translator): Parse <tool_call> JSON and TOOL_CALL text formats from model output Some models (DeepSeek, Qwen) emit tool calls as text instead of proper tool_calls JSON: either <tool_call>{...}</tool_call> or TOOL_CALL Name: {...}. Extend extractXmlInvokeBlocks to handle all 3 formats in a single scan pass, picking whichever pattern appears first. Includes unit tests for all formats. * fix(translator): Parse text-format tool calls in gemini-to-claude translator Extend the Gemini->Claude translator to detect <invoke>, <tool_call> JSON, and TOOL_CALL text formats emitted inline in text parts (Antigravity/Gemini models), converting them to proper tool_use content blocks instead of leaking raw text to Claude Code. * docs(changelog): Add changelog entry for text tool call parsing fix * fix(translator): consolidate tool name casing normalization and restore thought-signature persistence (cheaper-inference#3) * fix(translator): sanitize tool_use.id and tool_result.tool_use_id to match Anthropic schema (cheaper-inference#4) Ensure tool IDs from OpenAI-compatible upstreams (which may contain dots, colons, or special characters) are sanitized to ^[a-zA-Z0-9_-]+$ in response translators and passthrough requests before reaching Claude endpoints. * fix(responses): preserve native tools for openai-compatible Responses targets (cheaper-inference#5) A Responses-shaped request to a custom openai-compatible connection whose outbound protocol is Responses took a Responses -> Chat -> Responses round trip, so Codex custom tools lost their grammar (`exec`), namespace groups were flattened (`collaboration`), and tool invocations failed upstream. Gate a native Responses passthrough on the connection's configured protocol (`apiType: "responses"` / `_omnirouteForceResponsesUpstream`) so the original tool definitions reach a Responses-capable upstream unchanged. Chat-only connections keep the existing downgrade. Closes cheaper-inference#10374 * fix(translator): add support for 'applypatch' tool name in tool call checks * test(translator): add unit test for apply_patch and applypatch tool name remapping * fix(translator): remove no-explicit-any lint errors in tool-use-id-sanitization test Type the openaiToClaudeResponse/translateNonStreamingResponse return values with narrow local shapes instead of `any`, satisfying the repo's no-explicit-any = error rule for tests/. No behavior change — the same 3 assertions still pass. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test: update 9568 casing regression to match cheaper-inference#10392's consolidated fix restoreClaudeToolName's static casing map now normalizes known lowercase tool names to canonical PascalCase unconditionally on the gemini-to-claude and openai-to-claude Claude Messages API paths (not gated behind toolNameMap), superseding the earlier per-map-only fix that the original cheaper-inference#9568 regression test locked in as "expected" (it was previously labeled a known bug case). The gemini-to-openai passthrough path is unaffected by cheaper-inference#10392 and keeps its original pass-through assertion. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw
added a commit
that referenced
this pull request
Sep 18, 2026
…start timeout (cheaper-inference#12906) * fix(sse): retry 0-byte empty_response 502 like STREAM_EARLY_EOF to stop autocompact 502 A genuine 0-byte upstream empty response (GLM-5.2 on a huge autocompact context returns ONLY reasoning_content or nothing, then closes) reaches stream.ts::emitClaudeEmptyStreamErrorAndAbort which emits a 502 with code "empty_response" via the onFailure callback AND propagates the failure down the pipeline as controller.error(new Error(msg)). The plain Error carries no .code, so getUpstreamErrorIdentifier (reads only error.code) returns undefined, result.errorCode/result.errorType become undefined, and the single-model retry guard (chat.ts) only matches errorType === "stream_early_eof" / errorCode === "STREAM_EARLY_EOF". The 502 surfaces to the client with no re-attempt (call logs 1788132529140-96ef4a / 1788142914004-062cf6, ~48s, tokens out=0). This is the same class of transient upstream glitch STREAM_EARLY_EOF was built for (HTTP 200 then zero useful frames — cheaper-inference#3758), but empty_response was never wired into the retry path. Fix (three chokepoints, all required for consistency): - stream.ts: emitClaudeEmptyStreamErrorAndAbort now propagates an Error carrying code="empty_response" so a downstream classifier can identify it (plain new Error(msg) dropped it). - chatHelpers.ts: shouldRetryStreamEarlyEof now treats "empty_response" the same as "STREAM_EARLY_EOF" via RETRYABLE_STREAM_EMPTY_CODES Set — ONE bounded same-connection re-attempt, never a loop (STREAM_EARLY_EOF_MAX_RETRIES=1 unchanged). - chat.ts: the single-model retry guard now also enters on errorCode === "empty_response". The bounded retry never marks the account unavailable (an empty response is a transient upstream glitch, not a bad key), mirroring cheaper-inference#3758. Tests: 5/5 (stream-empty-response-retry-96ef4a). Existing 3758 regression guard stays green (5/5). typecheck:core clean. * fix(sse): make direct response-start timeout reasoning-aware to stop 504 on high-effort TTFB Reasoning models (GLM-5.2/5.3 reasoning.effort=high/max, codex-gpt-5.x-high, third-party Claude-format replicas) warm up with a ~78s+ TTFB before emitting the first byte. The stream-readiness layer (streamReadinessPolicy) already budgets 180s for this class, but the fetch-layer guard (resolveDirectHeadersTimeoutMs) was a flat 30s — it pre-empted a warm reasoning response the readiness layer would have permitted, surfacing a 504 (regression introduced by 142ae93). Fix: resolveDirectHeadersTimeoutMs now accepts the request body and, when hasHighReasoningEffort(body) matches a quoted "reasoning_effort" or nested "effort" field with value high/max, raises the budget to REASONING_READINESS_CEILING_MS (180_000) — aligning to the same ceiling the readiness layer uses. The operator env override (OMNIROUTE_DIRECT_HEADERS TIMEOUT_MS) is treated as a FLOOR: reasoning awareness only raises the budget, never lowers it; an override above the ceiling (e.g. 240s) is preserved. proxyFetch.ts passes the request body (when it is a string) to resolveDirectHeadersTimeoutMs so the budget is per-request. The HIGH_REASONING_EFFORT_PATTERN is a bounded, non-overlapping regex (no variable-length quantifier overlap) — no ReDoS surface (PII rule #1). Tests: 7/7 (direct-response-start-timeout-reasoning-504 — flat default, env override, high/max ceiling bump, floor semantics, non-reasoning pass-through). typecheck:core clean. * docs(changelog): add fragments for empty_response 502 retry + reasoning-aware timeout Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Jihyun Son <jihyun.son@sk.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
xiaoyaner0201
pushed a commit
that referenced
this pull request
Oct 20, 2026
Release v3.8.52 — 1,442 documented changes (135 features, 1,112 fixes, 195 maintenance) from 1,386 cycle commits and 306 external contributors; full changelog in CHANGELOG.md → [3.8.52]. Local pre-flight on c754707: check:release-green --quick --full-ci RELEASE-GREEN (0 HARD / 0 drift), webpack production build + build:cli OK; the release-PR ci.yml run was never dispatched (Actions FIFO backlog), merged with --admin by owner decision at STOP #1.⚠️ base-red inherited: cheaper-inference#15306 (i18n translation drift pinned by 3 unit tests + Docs Sync strict — translation backlog, not code).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
Stacked Draft: this PR depends on upstream Draft PR diegosouzapw/OmniRoute#8757 and is based on its exact head
d2a5e1c71af621f31e39227904259c166f47bba1. Gate 0 in #8075 remains open. Do not merge or advertise this as external-database support. If cheaper-inference#8757 changes, this branch must be refreshed and the acceptance matrix rerun.Summary
This is an operational skeleton, not an external database implementation. SQLite remains the zero-configuration default.
Stacked dependency
feat/db-combo-repository-sliceatd2a5e1c71af621f31e39227904259c166f47bba1270c44733099472104299122616bfca1ebb21dcc534d77c47453ebaa16881d6237eb8d43672664ce166a8801e—feat(db): add operational backend skeletone6147abb2—fix(db): close operational lifecycle gaps270c44733—fix(db): classify synchronous initialize failuresThis fork-local stacked Draft intentionally isolates the PR4-only three-commit diff. After cheaper-inference#8757 is accepted/merged and Gate 0 direction is explicit, refresh onto the maintainer-selected release tip, rerun all gates, and open or retarget the upstream PR.
Behavior and safety boundaries
PersistenceError(code="unknown", operation="initialize")and terminal failed readiness.unsupported; callers cannot inject an executor that creates false async atomicity.Strict TDD and review evidence
The Kanban record preserves executable RED-before-GREEN evidence for portable errors, lifecycle implementations, error classification, transaction context lifetime/rollback, repository context rejection, lock exclusivity/timeout/abort/close-drain behavior, composition-root exposure, initialize/close races, stale readiness, readiness/close hook failures, terminal close semantics, extended SQLite codes, real SQLite lifecycle delegation, and synchronous initialize throws.
Independent FAIL reviews were retained and drove three repair rounds. A final read-only review passed the exact clean tree with no unresolved blocking findings.
Exact validation on the final tree
npm run typecheck:corenpm run check:cyclesnpm run check:db-rulesnpm run check:error-helpergit diff --check d2a5e1c7...HEADnpm run check:public-credsnpm run build:backendnpm run check:secretsgitleaksis absent; independent added-line secret-marker scan passednpm run lintnpm run teston exact final treeScope / non-goals
No PostgreSQL or MySQL driver, external dependency,
DATABASE_DRIVER, schema or migration change, startup/shutdown rewiring, offline migration tool, FTS/vector portability, broad repository conversion, or production/active-active support claim is included.Reviewer notes
release/v3.8.49has advanced and [v3.8.50] refactor(db): add combo repository boundary cheaper-inference/OmniRoute#8757 is markedneeds-rebase/merge-train-deferred.Related: diegosouzapw/OmniRoute#8075, #8261, #8262, #8757.