mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 05:32:19 +03:00
b754e44e260ba888035b8ec077b4da8ea4483e48
903 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d7368e243d |
docs(guides): DASHBOARD_ALLOW_EMBED is a build-time flag, not a runtime one (#10697)
* docs(guides): DASHBOARD_ALLOW_EMBED is build-time, not a runtime flag The VS Code guide told operators to "start OmniRoute with DASHBOARD_ALLOW_EMBED=vscode". Next.js compiles headers() into the route manifest, so next.config.mjs reads the variable while the bundle is built — exporting it in front of an already-built server does nothing, which is the exact trap anyone on `npm install -g omniroute` or the Docker image falls into. Documents the build-time nature, the working from-source recipe, and which install paths can enable it at all. ENVIRONMENT.md and .env.example already said build-time; this aligns the how-to with them and with the extension's own fallback message. * docs(changelog): announce the VS Code Copilot Chat integration The release notes only mentioned OmniCopilot in passing, inside the DASHBOARD_ALLOW_EMBED bullet — a reader would never learn the extension exists. Adds the fragment that says it plainly, with both store links. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me> |
||
|
|
0cd107b9ae |
docs(i18n): retranslate the CLI reference and integrations guide across all 42 locales
The translated CLI docs predated the relay-like CLI work: every locale still shipped the legacy Codex `config.yaml` quickstart (dropped from the English source when the generator moved to TOML), none mentioned the `omniroute run` launcher or the Gemini target, and CLI-INTEGRATIONS.md existed only in Polish. Regenerated through the project pipeline (npm run i18n:run) for the two guides the CLI effort changed: - docs/i18n/*/docs/reference/CLI-TOOLS.md — 42 locales updated; the obsolete YAML quickstart is gone from all of them (the remaining config.yaml mentions mirror the English legacy note and Continue's own config) - docs/i18n/*/docs/guides/CLI-INTEGRATIONS.md — 42 locales, 41 of them new files ENVIRONMENT.md is deliberately not included: at ~26 chunks per locale it exceeds the pipeline's 60s per-chunk timeout and fails after retries. It needs a raised OMNIROUTE_TRANSLATION_TIMEOUT_MS, which is a separate maintenance run. Verified: check:docs-all exits 0, doc-links reports no broken internal links, and spot-checks confirm technical identifiers, front-matter and language bars survive translation intact. |
||
|
|
c02d7988a6 |
docs: close the accepted doc-drift backlog — counts, undocumented CLI bins, stale front-matter
Counts (check:docs-counts STRICT, 8 drifts → 0): - 153 → 154 migrations in README.md, AGENTS.md and llm.txt (+ its 42 i18n mirrors, which must stay byte-identical to the root file) - 340 → 341 providers in README.md, AGENTS.md, llm.txt, the package.json description and the 4 SVG diagrams; PROVIDER_REFERENCE.md regenerated via gen:provider-reference (the new entry is the cloudflare-playground no-auth provider) Undocumented environment variables: - 13 CLI_*_BIN vars that exist in cliRuntime but were in neither ENVIRONMENT.md nor .env.example (kilo, opencode, hermes, forge, jcode, deepseek-tui, codewhale, smelt, pi, crush, omp, letta, windsurf — windsurf ships no default command) - OMNIROUTE_DEBUG and OMNIROUTE_HEALTHCHECK_PATH, read in code but absent from .env.example - CLI_CURSOR_BIN documents both fallbacks (agent, then cursor) Front-matter: bump the seven CLI/reference docs this effort touched from the stale 3.8.40/2026-06-28 stamp to the current release. Out of scope but blocking check:docs-all, fixed with evidence: ENVIRONMENT.md and .env.example still documented the Adobe Firefly CDP Chrome runtime removed in #9255. Seven of its variables are read nowhere in the codebase and its source file no longer exists; surviving vars are repointed at adobeFireflyBrowserLogin.ts and CHROME_PATH at its real readers. The two Gemini CLI auth vars the run launcher scrubs from the child env are added to the fabricated-docs external-tool allowlist, next to the existing CODEX_HOME/COPILOT_PROVIDER_BASE_URL entries. npm run check:docs-all now exits 0 for the first time on this base. |
||
|
|
40e8084b8c |
docs(cli): fix drifts found in the post-relay documentation audit
- ENVIRONMENT.md: CLI_ALLOW_CONFIG_WRITES default is true (matches cliRuntime), CLI_QODER_BIN default is qodercli (also in .env.example), CLI_GEMINI_BIN is server-side detection only (omniroute run resolves from PATH) - CLI-TOOLS.md: catalog counts 26 code / 8 agents (adds the missing zcode row), setup targets without auto-discovery list Qwen (not Gemini; Gemini is launch-only), hostSetupCommand only for the six tools with a host recipe, global env block uses GOOGLE_GEMINI_BASE_URL at the root, mention omniroute run as the generic launcher - CLI-INTEGRATIONS.md: manifest aliases, per-target --model wiring (openai/ and omniroute/ prefixes, qwen hard-requires --model), run exit-code contract, gemini child-env scrub notes - CODEX-CLI-CONFIGURATION.md: document omniroute configure codex / run codex - SETUP_GUIDE.md + QUICK-START.md: surface the generic omniroute run launcher - ENVIRONMENT.md: disambiguate OMNIROUTE_SMOKE_API_KEY (canary) from the OMNIROUTE_SMOKE_* CLI smoke-harness variables |
||
|
|
08860f5cae |
fix(docs): remove stray unresolved conflict marker in ENVIRONMENT.md
A single orphaned "<<<<<<< HEAD" line (no matching =======/>>>>>>> pair) leaked into release/v3.8.50 via PR #10039's merge-conflict resolution during this session's serial-merge sweep. Repo-wide sweep confirms no other stray markers exist. Table structure verified intact before/after removal. |
||
|
|
04af8b1517 |
feat(compression): adota omniglyph 1.4.0, perfis semânticos e contabilidade com evidência (#10647)
* feat(compression): target-wire OmniGlyph stage and transport fidelity gate Roda o OmniGlyph depois da tradução para o wire real do provedor, em vez do corpo de origem. Um cliente OpenAI roteado para Claude deixava de comprimir com skip:source_format_not_claude porque o corpo ainda estava em formato OpenAI quando a engine era avaliada. - dispatch nativo por wire: Anthropic Messages, OpenAI Chat Completions e OpenAI Responses (input[] preservado, sem achatar para messages[]); - estágio target-wire pós-translateRequest, com guarda contra dupla compressão no caminho Claude→OpenAI; - preserveSystemPrompt do OmniRoute mapeado para compressSystem: false; - imageTransportPolicy: fidelidade de bytes/dimensões separada de supportsVision; só Anthropic/Claude tem recibo byte-preserving, o resto é fail-closed; - contagem de tokens de data URL PNG no wire OpenAI (marcador ;base64,); - README e i18n en/pt-BR com claims escopados ao caminho medido. * feat(compression): adota omniglyph 1.4.0 e tira o gate de modelo da env do host O 1.4.0 introduziu escopos de segurança e passou a resolvê-los dentro de isOmniGlyphSupportedModel() lendo process.env.OMNIGLYPH_PROFILE. Somado ao OMNIGLYPH_MODELS que já existia, duas variáveis do ambiente do host decidiam em silêncio o gate de TODO request do OmniRoute: passthrough desligaria a engine inteira e OMNIGLYPH_MODELS admitiria modelos sem recibo medido, enquanto a UI segue prometendo "Claude Fable 5 na rota direta medida". O adapter passa a usar isOmniGlyphSupportedModelForScope() com escopo explícito e fixa o escopo mais restrito como teto: a env só pode ESTREITAR a allowlist, nunca alargar. Os dois wires compartilham a mesma lista no pacote desde o 1.4.0, então uma checagem cobre Anthropic e GPT. - omniglyph ^1.3.1 -> ^1.4.0 (lock em 1.4.0); - testes de regressão para os dois caminhos de sequestro por env; - teste de contrato dos exports novos (escopo, perfis, accounting). O 1.4.0 também traz, sem mudança de código aqui: correção do glyph K que era lido como H, remoção do backtracking polinomial no secret-guard, overrides do pnpm em pnpm-workspace.yaml e as transitivas vulneráveis resolvidas. * feat(compression): expõe os perfis semânticos do omniglyph nos três wires O 1.4.0 trouxe perfis nomeados (coding-safe, balanced, aggressive, passthrough), mas só transformAnthropicMessages() os resolve sozinho: os transformadores OpenAI recebem TransformOptions cru e ignorariam o campo. Um perfil escolhido pelo operador valeria no wire Claude e sumiria no OpenAI. O adapter passa a mesclar o perfil com mergeCompressionProfileOptions() antes de chamar Chat Completions e Responses. O default segue aggressive — a política que os recibos publicados mediram. Medido nesta base: com coding-safe/balanced, uma sessão sem histórico acumulado para em below_min_chars e a engine não faz nada, porque os dois fixam minCompressChars no máximo e desligam system/tools/tool-results. Como a engine é opt-in, um default assim entregaria "ligado, 0% de ganho". O perfil é TETO, não piso: mergeCompressionProfileOptions não deixa um override do chamador reabrir uma lane lossy que o perfil fechou. Coberto por teste, por ser contra-intuitivo. Também fecha um caminho em que o OmniRoute violaria a própria política: o wire OpenAI do pacote não tem compressSystem — honra apenas compressTools, gptHistory, minCompressChars e reflow, e sempre troca a instrução por um ponteiro para a imagem. Com preserveSystemPrompt ligado, imagear assim queimaria o prefixo quente que a decisão cache-aware está protegendo, sem nada no corpo devolvido denunciando. A engine agora pula com skip:system_preservation_unsupported_on_wire. * feat(compression): contabilidade física do omniglyph com grau de evidência O adapter descartava o TransformInfo inteiro, então a UI mostrava um número de economia sem dizer de onde ele vinha — contagem do provider, estimativa ou só diferença de bytes. O 1.4.0 expõe normalizeAccounting(), que classifica essa evidência e resolve a semântica de cache por família: Anthropic reporta input, cache-create e cache-read em buckets DISJUNTOS, enquanto OpenAI e xAI reportam cached como SUBCONJUNTO do input. Somar à mão dá double-count silencioso. O novo omniglyphTelemetry.ts não filtra por denylist — MONTA um objeto novo, campo a campo, só com número e enum. TransformInfo mistura contadores inofensivos com material que não pode ser persistido: bytes PNG, imageSourceText(s), recoverable[].text, os sha8 de system/CLAUDE.md/primeira mensagem, nomes de tags observadas e o bloco env (cwd, branch, versões). Copiar o objeto inteiro transformaria telemetria de compressão em vazamento de prompt. O teste de negação prova que segredo, caminho do operador, texto do system e base64 não aparecem, e varre a allowlist exigindo que toda string seja de um enum conhecido. - provider threaded do chatCore e do bridge Codex WS até a engine; ausente vira `unknown`, que faz o upstream recusar adivinhar buckets de cache; - contabilidade propagada para o engineBreakdown do passo (o agregado do pipeline soma todas as engines e não serviria); - skip não emite contabilidade: zeros ali seriam indistinguíveis de "a engine nem rodou". * feat(compression): perfil do omniglyph configurável, persistido e documentado Fecha o caminho do operador: o perfil já existia no adapter, mas só como default de código. Agora atravessa schema Zod, normalizador do banco, API de settings e a página dedicada do engine. - OmniglyphConfig tipado + omniglyphConfigSchema (z.enum dos quatro perfis); - normalizeOmniglyphConfig: nome desconhecido vindo do storage cai para o default em vez de virar "roda com a política padrão"; - seletor na página do engine, com PATCH próprio — o perfil vive fora do mapa `engines`, e mandá-lo junto reescreveria o mapa inteiro (o store persiste o mapa como uma linha JSON só); - i18n en/pt-BR descrevendo o custo medido de cada perfil, não só o nome; - README e COMPRESSION_ENGINES.md com a regra do teto e o motivo de o default não ser o perfil mais seguro. Corrige de passagem um teste-irmão que ninguém via: o gate de transporte na UI deixou de dizer "direct Anthropic" quando os wires OpenAI nativos entraram, mas tests/unit/ui/omniglyphContextPage.test.tsx continuou afirmando a cópia antiga. O arquivo inteiro estava excluído do vitest.config.ts como "#8618 pre-existing failure", então a quebra passou silenciosa. Com a asserção alinhada o arquivo fecha 3/3, e a exclusão sai — o próprio comentário mandava removê-la quando corrigida. A doc não nomeia OMNIGLYPH_MODELS: o gate de docs fabricadas está certo em apontar que o OmniRoute nunca lê essa env — quem lê é o pacote. * fix(i18n): paridade do locale vi com as chaves novas do perfil do omniglyph `tests/unit/i18n-vi-completeness.test.ts` exige paridade ESTRITA de chaves entre en e vi — diferente do ratchet `i18n:check-ui-coverage`, que passa com 80%. As 11 chaves do seletor de perfil entraram só em en e pt-BR, e o gate de cobertura seguiu verde, então a quebra só apareceu na matriz completa do CI. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> |
||
|
|
233ac40e9d |
refactor(sse): ExecutorRegistry — route executor lookup through a runtime registry (R0.3) (#10633)
* test(sse): golden characterization of the executor map before the R0.3 registry refactor Freezes the 137-entry provider-id → executor mapping (class, provider identity, backing PROVIDERS config), the no-shared-instances invariant, and the getExecutor() dispatch rules (memoized DefaultExecutor fallback, cloud-agent guard #6699, search-provider guard #10274) as stable JSON snapshots. The upcoming ExecutorRegistry must keep both snapshots byte-identical. * refactor(sse): route executor lookup through ExecutorRegistry (R0.3) Adds open-sse/executors/registry.ts (Map-based registry mirroring translator/registry.ts): the built-in table in executors/index.ts stays declarative, every entry is registered at module load, and getExecutor()/hasSpecializedExecutor() resolve through the registry. DefaultExecutor fallback, its memoization, and the cloud-agent (#6699) / search-provider (#10274) guards are unchanged. Also fixes a latent lookup leak: the old object-literal lookup treated Object.prototype names (constructor, toString, ...) as specialized executors; the Map registry resolves them to the DefaultExecutor fallback like any unknown provider. Parity proof: executor-map golden (137 entries, byte-identical before/after), check:known-symbols green, 1018 tests across the 65 executor test files green. Docs: OPEN_SSE_ARCHITECTURE factory section corrected (it claimed generation from providerRegistry). Refs #3501 * test(executors): regenerate ExecutorRegistry golden snapshots after release sync release/v3.8.50 sunset mimocode and added cloudflare-playground + jina-search since this PR's snapshots were captured; refresh the golden fixtures to match. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> |
||
|
|
72d761fb50 |
docs(cli): document run/configure surface, Gemini launcher and smoke harness across README and guides
- README: 'run any supported CLI in one command' block (7 targets incl. gemini), updated one-command setup bullet with run/configure - CLI-INTEGRATIONS: gemini in the master table + run examples + base-URL row (GOOGLE_GEMINI_BASE_URL → /v1beta), opt-in smoke sweep section - REMOTE-MODE: 'launching a CLI against the remote' section (run + contexts) - CLI-TOOLS: gemini install step in Quick Start - ENVIRONMENT/.env.example: CLI_AIDER_BIN, CLI_GOOSE_BIN, CLI_GEMINI_BIN - API_REFERENCE: apply endpoint row documents dryRun/422/migration contract - smoke harness fixes proven against a live local OmniRoute: node:test treats timeout:0 as 'time out immediately' (sized budget from the per-target cap), and resolve on child 'exit' instead of 'close' so grandchildren holding the stdio pipes cannot hang a target (qwen was blocked 431s past its 120s cap). Live evidence: gemini exit=0 pass via /v1beta against localhost; all four installed CLIs (codex/opencode/qwen/gemini) reached the upstream end-to-end with correctly classified upstream errors (free-tier 429 / ddgw 400). |
||
|
|
8dec11530e |
fix(docker): use lightweight /healthz for container lifecycle healthcheck instead of the heavy monitoring route (#10311) (#10504)
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> |
||
|
|
885cd8c411 |
feat(gemini-web): expose image generation through /v1/images/generations (closes #10466) (#10494)
* feat(providers): add Cloudflare AI Playground as No Auth provider (closes #10389) Reverse-engineered access to the free, anonymous Cloudflare AI Playground: chat runs over a PartySocket WebSocket speaking Cloudflare's cf_agent RPC protocol with zero credentials (no account, no API key, no cookies). The WS upgrade is gated on a browser-grade TLS fingerprint, so the executor drives a headless Chromium via Playwright and speaks the protocol from inside the page context. - registry entry: cloudflare-playground (alias cfp), authType none, curated 20-model catalog (GLM 5.2, Kimi K2.7 Code, DeepSeek V4 Pro, gpt-oss-120B, Llama 3.3 70B, Qwen2.5 Coder 32B, ...) captured from the live getModels RPC (2026-08-15) - executor: cf_agent frame stream -> OpenAI SSE translation, id-filtered parser (RPC done:true frames cannot kill the stream), in-band upstream errors mapped to HTTP 429/502, abort + timeout handling, clean errors - noauth UI entry with reverse-engineered-endpoint notice - tests: 12 unit tests using real captured frames (incl. the 3021 rate-limit error) + fake transport; ESLint clean; open-sse typecheck clean * fix(providers): define __name helper in page context before evaluate Bundlers with keepNames (esbuild/tsx, webpack) inject a __name() call into serialized function bodies. page.evaluate(openPlaygroundSession) therefore threw ReferenceError: __name is not defined in real browser sessions. Define the helper on window before evaluating the session opener. * fix(providers): sync docs counts, golden snapshots and add reasoning_content support for cloudflare-playground * chore: remove ad-hoc cfp-shim debug script per review feedback The standalone shim duplicated the executor's frame-parsing and transport logic and is superseded by open-sse/executors/cloudflare-playground.ts. Requested in PR #10442 review. * feat(gemini-web): expose image generation through /v1/images/generations (closes #10466) Adds a gemini-web image-generation path following the chatgpt-web precedent: - imageRegistry: gemini-web provider entry (format gemini-web, cookie auth) with the nano-banana-web model. The -web suffix keeps the bare nano-banana id owned by adobe-firefly (operator decision 2026-07-31). - gemini-web executor: new parseStreamResponseImages() extracts generated image URLs from the StreamGenerate candidate extension block (inner[4][0][12][7][0], url at entry[0][3][3] — string or list form), dedupes cumulative frames, upgrades to =s2048, and deliberately skips web-search thumbnails at [12][1]. Image mode (x_gemini_web_image_mode) captures every StreamGenerate frame, resolves on first image, and gets a 90s window; chat mode is byte-for-byte unchanged. - handlers/imageGeneration/providers/geminiWeb.ts: drives the executor in image mode with an explicit generation directive prompt (the web UI otherwise answers with web-search images), caps n at 4, returns URLs or b64_json (downloads the public googleusercontent asset), and surfaces refusal text when no image was produced. - Dispatch branch on format gemini-web in handleImageGeneration. Tests: 21 new tests with fixtures built from the documented frame layout (string/list url forms, cumulative-frame dedupe, web-image exclusion, size-directive handling, refusal visibility, n-cap, b64_json, registry wiring incl. the bare nano-banana → adobe-firefly regression guard). Adjacent suites: gemini-web (6 files), chatgpt-web image, image handler, route, registry, adobe-firefly, freepik, designer — all green. ESLint clean on touched files (2 pre-existing any warnings unchanged); tsc -p open-sse 0 errors. * fix(media): close browser leak, surface timeout errors, and fall back accounts for gemini-web images Addresses pre-merge review findings on #10494 (closes #10466): - cloudflare-playground executor: close the launched browser on EVERY non-success start() path, including the detected Cloudflare "Attention Required" challenge branch (was leaking a Chromium process per blocked request). - cloudflare-playground executor: a streaming chat timeout now emits an explicit timeout_error SSE chunk before [DONE] instead of silently completing, so a client can no longer mistake an empty/partial timed-out stream for a successful answer. Timeout duration is now injectable for deterministic tests. - gemini-web image handler + imageCredentialRetry: classify the underlying GeminiWebExecutor's expired/blocked-session failure modes (400/500, per its own Playwright timeout/catch-all branches) as retryable, so executeImageWithCredentialFallback advances to the next eligible account instead of only doing so on a plain 401. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs: regenerate provider counts after merging release/v3.8.50 (341 -> 342) The previous merge commit resolved all 51 auto-generated-file conflicts by taking release/v3.8.50's content, which still said 341 providers. Merging in this branch's Cloudflare Playground provider brings the live catalog to 342, so npm run check:docs-counts-sync now flags stale claims. Fix: - docs/reference/PROVIDER_REFERENCE.md: regenerated via `npm run gen:provider-reference`. - README.md/AGENTS.md/llm.txt/package.json description: 341 -> 342. - docs/diagrams/{readme-hero,promise-pillars,comparison-table,cli-terminal}.svg: 341 -> 342 in the embedded "NNN providers" text (targeted replace, matched against the exact pattern check-docs-counts-sync.mjs validates). check:docs-counts-sync and check:changelog-integrity are both clean after this commit. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(env): document CLOUDFLARE_PLAYGROUND_CHROME_PATH Used by open-sse/executors/cloudflare-playground.ts but missing from .env.example and docs/reference/ENVIRONMENT.md, caught by the env-doc-sync gate when combined with other PRs in the release merge-train. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: user.email <freakymustard67@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
beb6ec857b |
feat(dashboard): agentic conversation tracking — v4, decoupled + storage-architecture concern resolved (#10263)
* feat(responses): virtualize previous_response_id continuation regardless of upstream support OmniRoute now exposes OpenAI-compatible previous_response_id/store continuation to clients unconditionally, even when the selected upstream provider has no native Responses-API state support. Reconstruction happens server-side in handleChatImplementation, before any downstream validation or provider translation: OmniRoute resolves the response id back to the full input/output it previously produced, prepends it to the client's delta, and forwards the full reconstructed history upstream exactly as it does today. Client<->OmniRoute traffic shrinks to the new delta only; OmniRoute<->provider traffic is unchanged. Storage reuses the existing call-log pipeline artifact (already gated by call_log_pipeline_enabled, already retained/cleaned up by the existing call-log lifecycle) instead of duplicating conversation content into a second store -- only a lightweight call_logs.response_id index is new. Every lookup is scoped by api_key_id so one client can never resolve another client's stored conversation, and any unresolvable/missing/ size-limit-omitted state fails closed with OpenAI's own previous_response_not_found contract. Stacked on feat/openai-responses-store-toggle (#10121). * feat(dashboard): agentic conversation tracking with live transcript view Every agentic chat request now gets a conversation id (X-ConversationId response header). OmniRoute detects when a follow-up request continues the same conversation via fingerprint + bounded prefix-hash matching, with a strict-growth invariant to prevent false merges between independent single-shot requests that happen to share identical opening content. Continuation detection excludes the system message from the identity anchor, since real coding-agent CLIs commonly regenerate it every request with live context (timestamp, cwd, git status) — without this, that volatility alone broke every continuation check against real traffic. - `/dashboard/logs`: new toggleable Conversation column. - `/dashboard/logs/timeline`: requests sharing a conversation id share a timeline lane, connected by an arrow, with a configurable lane-reuse window. - Request detail panel: new Full Conversation transcript above the raw SSE event stream — Markdown rendering, per-turn timestamps, turn-relative view, click-any-turn navigation, live auto-refresh building the transcript in real time from the in-flight SSE chunk buffer while a request is still streaming, auto-scroll-to-bottom as the live turn grows. - New `/dashboard/conversations` page listing conversations with 2+ turns, no-forking model (an edited/duplicated mid-history turn mints its own independent conversation instead of merging), pagination, duplicate- anchor fix. - Configurable auto-refresh intervals on both the timeline and conversations list pages. - Responses API tool-call gap fix: turnsFromOpenAiMessages only handled role-based Chat Completions messages, so bare {type:"function_call"} / {type:"function_call_output"} / {type:"reasoning"} items (real Responses API traffic) silently vanished from the Conversation Context panel. - truncateForLog now counts input[] (Responses API), not just messages[] (Chat Completions), so a truncated /v1/responses request still shows a placeholder instead of nothing. - RequestTimeline.tsx now reads the same debugEnabled/emailsVisible settings RequestLoggerV2.tsx already used, instead of hardcoding both false — the timeline view never showed SSE/stream-chunk events or respected email-masking, regardless of the actual setting. Migrations 147/148 (agentic_conversations, conversation_turn_nodes) — 135 and 136 are now taken upstream; 143-145 are documented KNOWN_GAPS, so this uses the next free slot past upstream's current highest. Test plan: - npm run typecheck:core — clean - npm run lint — clean - node --import tsx/esm scripts/check/check-migration-numbering.mjs — OK, 0 collisions - 109 unit tests across the conversation-tracking, migration-renumber, and dashboard-wiring surface — 0 failures * refactor(dashboard): reuse call-log artifacts for conversation transcript content conversation_turn_nodes no longer stores turn text/tool-call content (text_preview/block_kind/tool_name) -- it's identity-only now (id/parent/ content_hash), matching agentic_conversations' existing lightweight-index shape. Every node's originating request is already fully captured by the call-log pipeline artifact its last_correlation_id points at, so the /dashboard/conversations tree view resolves each node's actual display content on demand from there (open-sse/services/conversationTurnContent.ts), re-running the same extractCanonicalTurns/hashTurnContent the write path used and matching by content_hash, instead of duplicating conversation content into a second store under a separate retention/gating policy. This also drops the old 8000-char text_preview truncation entirely -- resolved content is always full and untruncated. The frontend contract is unchanged (tree API still returns {textPreview, blockKind, toolName} per node), so the dashboard UI itself (page.tsx, RequestLoggerDetail/RequestTimeline, sidebar, i18n) needed no changes. Renumbered the cherry-picked 147/148 migrations to 153/154 -- 147 now collides with 147_api_keys_model_access_mode.sql, which landed on release/v3.8.50 after this work was originally built. Also includes a standalone, unrelated fix carried along from this rebase: close isProviderModelHidden's missing function-body brace in modelSelectModalHelpers.ts (separately landed as #10206). Stacked on feat/responses-previous-response-id-virtualization (#3), which is itself stacked on feat/openai-responses-store-toggle (#10121). * fix(dashboard): resync conversation list on open so the live-text poll starts immediately openConversation() seeded activeConversation (and therefore activeCallLogId, which gates the live-partial-text poll effect) from whatever row snapshot the list's own fixed-interval poll last produced. A conversation opened right after a reply started streaming -- after that tick, before the next -- had activeCallLogId still null, so the live-text poll never started; only a subsequent background list-poll resync (already existed) picked it up, which is why closing and reopening the same conversation "just worked". loadConversations() is now a shared callback so openConversation can force one immediately on open instead of waiting on pollSeconds. Live-verified against omniroute-dev: opening a conversation mid-stream now shows live reasoning on the first open. * style: prettier formatting for conversationTurnContent.test.ts * fix(db): close migration numbering gap left by decoupling from #3/#10262 153/154 (originally 154/155) were chosen back when this branch stacked on top of the previous_response_id migration (153_call_logs_response_id.sql). Decoupling removed that migration from this branch's history, leaving an unused 153 slot that check-migration-numbering.test.ts correctly flags as a gap. * refactor(dashboard): split RequestTimeline/RequestLoggerDetail under the 1000-line file-size cap Both files exceeded check-file-size's new-file cap after this PR's own additions (RequestTimeline 1048, RequestLoggerDetail 1163). Extracted pure non-component logic (types, constants, allocateLanes and its helpers) out of RequestTimeline.tsx into RequestTimeline.utils.ts, and the two self-contained presentational sub-components (PayloadSection, ConversationContextSection + its private helper) out of RequestLoggerDetail.tsx into RequestLoggerDetail.sections.tsx. No behavior change; existing external imports (default exports, allocateLanes, TimelineLog, CONVERSATION_LANE_REUSE_STORAGE_KEY) still resolve from the original file paths. * fix(db): renumber agentic-conversation migrations to clear 153 collision + sync migration-count docs The refresh-merge of release/v3.8.50 exposed that the feature's three migrations collided at slot 153 with the base's radar_local_model_state (153) and its own call_logs_response_id. Migration runner enforces unique numeric prefixes -> every DB init threw, red-ing Vitest, all Unit shards and the DB-backed quality gates. Renumber the feature's pair to 155_agentic_conversations / 156_conversation_turn_nodes and move call_logs_response_id to 154 (keeps 153_radar base-owned, preserves agentic-before-turn_nodes ordering). Update SQL headers and the 154/156 references in feature code + tests. Migration count is now 151 (was 148 stale in README/AGENTS/llm.txt) — sync the doc counts to clear the docs-accuracy gate. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(ui): drop unused CONVERSATION_LANE_REUSE_STORAGE_KEY re-export from RequestTimeline Knip 6.32 (baseline 415) flags the public re-export of CONVERSATION_LANE_REUSE_STORAGE_KEY from RequestTimeline.tsx as dead: no external consumer imports it through that re-export (it is imported and used directly from RequestTimeline.utils.ts inside the component). Removed the unused re-export; the internal import stays. DEAD_TOTAL 416 -> 415, back to the frozen baseline. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(agentic-conversations): guard resolveConversationId, drop dead whole-chain export - Wrap resolveConversationId() in try/catch in chat.ts, matching the defensive pattern used by every other best-effort side call nearby, so a DB hiccup in conversation tracking can't turn a working chat request into a hard failure. - Remove getConversationTurnTree: knip's project scope excludes tests/**, so an export used only by tests can never register as used there. Swap its 8 test call sites to the paginated getConversationTurnPage (already the dashboard's canonical query) with a generous limit, collapsing to one query path instead of keeping a second whole-chain export alive solely for test convenience. - Regenerate i18n llm.txt mirrors from root (pre-existing drift on this branch, unrelated to the above, caught by the docs-sync pre-commit gate). Addresses PR review feedback. * fix(i18n): close requestLogger conversation-column gap, fix domain-modules count drift - fr.json, vi.json were missing requestLogger.columns.conversation (added in the conversation-tracking feature), failing i18n-vi-completeness.test.ts. - docs/i18n/*/llm.txt mirrors still said 117 domain-specific files after an earlier rebase fixed the migration count but missed this companion number, failing check-docs-sync.mjs across all 42 locales. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(docs): restore PROXY_LOG_INCLUDE_IPS env/doc entries (env-doc-sync red) .env.example and docs/reference/ENVIRONMENT.md were both missing the PROXY_LOG_INCLUDE_IPS entry that src/lib/proxyLogger.ts already reads (confirmed present at this branch's merge-base too, so this predates the conversation-tracking work and is unrelated to it) -- the entry was added on release/v3.8.50 after this branch's last sync and this branch never picked it up. That gap red-lines tests/unit/check-env-doc-sync.test.ts and tests/unit/issue-7793-env-doc-sync-repro.test.ts (Unit Tests fast-path 2/4 in CI). Restore both entries verbatim from the current release/v3.8.50 tip -- no feature-code change. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: hartmark <hartmark@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
d93b24e761 |
feat(api): add provider quota telemetry, adaptive routing, and status inventory (#10148)
* feat(api): add provider quota telemetry, adaptive routing, and status inventory Adds a read-only OmniRoute status/inventory surface plus supporting resilience and usage-tracking infrastructure: - src/lib/quota/providerQuotaTelemetry.ts, providerCapabilities.ts: provider quota state and capability signals, sourced from configured metadata rather than invented values; unknown stays unknown. - src/lib/resilience/adaptiveCircuit.ts, failureClassification.ts: circuit state with lazy recovery and explicit failure classification. - src/lib/usage/usageLedger.ts, budgetGuard.ts, modelPricingRegistry.ts: internal usage tracking and budget allow/warn/deny decisions, kept separate from upstream-reported quota (never conflated). - src/lib/routing/adaptiveRouting.ts: excludes exhausted-quota and open-circuit candidates from routing, penalizes approaching-limit. - src/lib/omnirouteStatus.ts + src/app/api/omniroute/status, route/preview: read-only status endpoint; never issues a live upstream model request (asserted via liveRequestExecuted: false). - src/lib/db/quotaPools.ts: adds ensurePool() for idempotent pool management by automation/CLI callers, following the existing group-demo default-group convention. - scripts/omniroute-verify.mjs (+ omniroute:verify script): local verification against the running gateway. 9 new unit tests, all passing. typecheck:core clean relative to base (release/v3.8.50) -- the 2 pre-existing gateways.ts errors are tracked separately in #9985 and untouched by this change. * test(cli): align cli-machine-token assertions with HMAC-SHA256 64-char format The quota-telemetry feature hardens cliToken to HMAC-SHA256(machineId, SALT) (64-char hex, pristine machine id). Update the regression test to the new format and mirror the production derivation in the different-machine-id check. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> Co-authored-by: desamours-hub <desamours-hub@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
6615a5445b |
feat: combo-lane awareness + activation UX + MCP visibility (Wave 2 of #9654) (#10039)
* feat(admission): per-target lane-aware probes for combo/fusion fan-out (#9654 Wave 2)
Combo and fusion fan out N targets without ever consulting the adaptive-admission
layer: the parent request holds one lease, but each fan-out target is dispatched
unconditionally. With virtual lanes enabled (OMNIROUTE_CHAT_VIRTUAL_LANES=1), a
connection whose lane queue is full now SKIPS additional fan-out targets instead
of piling more queued work onto an already-congested session.
Adds PerTargetAdmissionHook (admission/types.ts) + createPerTargetAdmissionHook
factory (chatAdmission.ts): strictly non-blocking (maxWaitMs 0 - skip, never
queue), a no-op when virtual lanes are off, keyed to the parent tenantKey, and
release-on-admit so the probe is a capacity gate, not a hold.
Threaded through every parallel fan-out path:
- priority/weighted executeTarget + round-robin skip chains (combo.ts)
- fusion panel before fan-out (fusion.ts), judge fallback prefers survivors
- chaos parallel panel (autoCombo/chaosEngine.ts)
- tryFusionDispatch / tryRuntimeUnitDispatch / buildBaseOptions (dispatchPrelude.ts)
- chat.ts primary + safety-net redirect call sites
Snapshot exposes virtualLanes so the no-op gate is cheap and honest.
Tests: tests/unit/combo-lane-awareness-9654.test.ts (10 tests) - factory
semantics, priority/RR skip, fusion panel drop + all-skipped 503, no-hook
backward-compat baseline.
* feat(flags): activation UX - env-wins adaptive virtual-lanes flag + env docs (#9654 Wave 2)
U7: make adaptive virtual admission lanes discoverable + activatable.
- New OMNIROUTE_CHAT_VIRTUAL_LANES feature flag (boolean/runtime/requiresRestart) in featureFlagDefinitions + en.json i18n key.
- lib/admissionVirtualLanes.ts: env-wins resolver (env > DB > default) + boot warm folding a DB-sourced override into the process-global runtime env via reloadAdaptiveAdmissionRuntime(options.env) - no process.env mutation, no open-sse changes. Env still wins; DB toggle gates at next boot.
- GET /api/settings/feature-flags special-cases the flag to report the gate true source (ccDiscoveryAliases precedent); flagPayload helper dedupes the payload shape.
- Wire the warm into instrumentation-node registerNodejs (non-fatal, DB-ready).
- Document the master switch in .env.example + ENVIRONMENT.md with the system-1/system-2 distinction; zero new env-doc-sync drift.
- 11 new tests (resolver precedence + warm); 60/60 across feature-flag suites; typecheck core clean; ESLint + doc gates green.
* feat(mcp): surface adaptive admission lane data in omniroute_get_health (#9654 Wave 2)
U8: make adaptive virtual-lane admission visible to agents via the MCP health tool. handleGetHealth now surfaces a curated adaptiveAdmission block from the health payload (which already carried the runtime snapshot but was dropping it): virtualLanes/pressure/utilization/laneCount/laneQueuedCount/laneQueuedCost, laneTenants capped at top-10 by queued cost, admitted/rejected/wouldReject counts, shutdown. Block omitted entirely when the health endpoint reports none.
isLaneFlagOn mirrors the runtime 1|true convention so a string serialization can never invert a boolean lane report. getHealthOutput schema extended with the matching optional shape; tool description updated.
4 new dispatch tests (full block, top-10 cap/order, omission, defensive coercion of string flags + malformed lane entries) - 22/22 in essentialTools.test.ts. README: Adaptive Admission Lane Data table + Skills & Tool Navigability audit (29/43 schema entries covered, 14 undocumented, tool_search keyword runtime discovery, full catalog in docs/frameworks/MCP-SERVER.md).
No new lint errors (4 pre-existing in server.ts), typecheck core clean, doc counts + fabricated-docs gates green.
* docs: add changelog entry for #9654 Wave 2 (#10039)
* fix(codeql): suppress js/insufficient-password-hash false positive in lane-key fingerprinting (#10039)
resolveSessionId sha256-hashes bearer/x-api-key/x-goog-api-key to derive a deterministic, non-reversible per-key lane-bucket ID for virtual admission lanes (#9654). This is not password storage or verification, so the rule is a false positive; suppress it inline (same house style as src/lib/sync/tokens.ts) to clear the codeqlAlerts ratchet (2 > baseline 1) that blocks #10039 and every PR against release/v3.8.50.
* docs(mcp): complete MCP server README tool reference (#10039)
The MCP server README covered only 29 of the 43 schema entries, listing the
remaining tools solely as a gap note with omniroute_tool_search as the runtime
fallback. Add tool-reference tables for the agent-skills trio, oneproxy trio,
web_fetch/web_search, tool_search, create_combo, set_routing_strategy,
pick_fastest_model, sync_pricing, and db_health_check so the README covers the
full schemas catalog, and fold the coverage note into the tool_search discovery
paragraph.
* fix(chat): drop unused correlationId from safety-net combo redirect (#10039)
handleComboChat's HandleComboChatOptions has no correlationId member and
the combo pipeline never consumes it; the property was copied from the
handleSingleModelChat options shape by accident and introduced a new
TS2353 under the open-sse workspace typecheck gate.
* fix(i18n): translate featureFlagChatVirtualLanesEnabledDescription into 42 locales (#10039)
en.json gained the flag description in this PR but the locale catalogs
were never mirrored, failing the pt-BR key-parity (#6695) and vi
completeness gates. Adds a real translation to every locale, keeping the
zh-CN/zh-TW glossary canonical terms (提供者/儀表板) and no ICU drift.
* chore(quality): ratchet open-sse-typecheck baseline down (#10039)
The Wave 2 admission refactor removed 66 baselined open-sse type errors;
re-freeze the baseline so the gate pins the new, tighter state.
* docs: resync provider reference to 341 and CLI tools to 34
The release branch gained an 11th no-auth provider (freeaiapikey registry
resync, #10233) and a 26th CLI Code tool without regenerating the
auto-generated docs, leaving every PR against release/v3.8.50 failing the
Docs Gates strict validator (code 341 vs doc 340, CLI 34 vs "33 tools").
Regenerate docs/reference/PROVIDER_REFERENCE.md and sync the provider/tool
counts across README.md, AGENTS.md, llm.txt plus 42 i18n mirrors,
package.json description, and the four diagram SVGs.
* fix(tests): align count expectations with live catalogs (pre-existing release drift)
Release/v3.8.50 currently fails five gates on its own tree; this PR inherits
them. Fix the stale expectations to match live code:
- feature-flags-settings: 48 -> 49 flags (Wave 2 adds OMNIROUTE_CHAT_VIRTUAL_LANES)
- cli-tools-schema / cli-catalog-counts: 33 -> 34 tools (zcode added; 26 code = 21 visible + 5 none)
- optional-transformers-dependency: onnxruntime-node ~1.24.3 -> ~1.27.0 (bump #10382)
- stryker.conf.json: register chatcore-header-drop-warn-dedupe-10315 test
- check-public-creds: freeze zcodeProtocol clientId false positive (client identifier, not a credential)
* fix(tests): follow release's onnxruntime-node revert to ~1.24.3
release/v3.8.50's #10543 pinned onnxruntime-node back to ~1.24.3 after
#10403's ~1.27.0 bump caused npm to nest a second native copy under
@huggingface/transformers and broke the Docker SONAME contract. This
PR's own drift-alignment commit (
|
||
|
|
8acd799af7 |
feat(routing): add exclusive managed session connection leases (#10362)
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
6b823aa441 |
fix(logging,sse): redact sensitive log fields and default SSE comments to disabled (#10539)
* fix(logging): redact client IPs and account prefixes by default ProxyEgress and AUTH logs exposed client IPs, egress IPs, and account prefixes at info level — a privacy leak in multi-tenant/shared-log environments. Now redacted by default, only shown when debugMode=true. Fixes #10348 * fix(sse): default SSE comment lines to disabled Strict SSE clients (WorkBuddy, etc.) JSON.parse every SSE line and crash on comment lines. Changed OMNIROUTE_SSE_COMMENTS default from enabled to disabled. Operators can opt in with OMNIROUTE_SSE_COMMENTS=on. Fixes #10524 * fix(logging): gate AUTH account-prefix redaction on a narrow flag, not debugMode The proxy-log redaction half of #10348 is superseded by an already-merged fix (PROXY_LOG_INCLUDE_IPS, decoupled from debugMode). The remaining gap was the chat.ts AUTH log line ("Using <provider> account: <prefix>..."), which this PR gated on the broad `debugMode` setting. `debugMode` is a general dashboard-visibility toggle unrelated to log privacy — coupling redaction to it means any future, unrelated change to debugMode's default silently changes whether account prefixes leak into logs. Add a dedicated AUTH_LOG_INCLUDE_ACCOUNT_ID feature flag (default off, security category) and gate the AUTH log line on it via isFeatureFlagEnabled(), which reads the DB override synchronously on every call (no stale in-memory cache to invalidate) and fails safe to redacted on any lookup error. Also update the SSE-comments tests/docs that still asserted the old enabled-by-default behavior (tests/unit/sseHeartbeat.test.ts, tests/unit/sse-comments-optout-9305.test.ts, docs/reference/ENVIRONMENT.md) to match the new default-off behavior from this PR's earlier commit. Refs #10348, #10524 Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
5a44c46b1d |
feat(resilience): scope auto-disable banned accounts to subscriptions (#10617)
* feat(resilience): scope auto-disable banned accounts to subscriptions Prepaid API keys should stay in the routing pool after a permanent-ban signal; subscription/OAuth accounts can still be deactivated. Default scope remains all so existing installs do not change. * docs(security): document auto-disable scope and log skipped prepaid keys Keep the operator ban-detection page aligned with the new setting and reuse the shared scope enum in the settings schema and dashboard radios. * chore(changelog): name the auto-disable scope fragment for #10617 * docs(settings): treat free login seats as auto-disable targets The first-cut scope is still all vs login-style auth. Copy now states that paid subscriptions and free accounts both disable, while prepaid API keys stay in the pool until per-account overrides exist. * i18n: backfill autoDisableBannedScope keys across all locales npm run i18n:sync-ui — the 6 new autoDisableBannedScope* keys landed in en.json and vi.json but not the other 40 locales (including pt-BR), tripping the pt-BR no-drift regression test (#6695). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
6003612000 |
fix(audio): fall back nested STT models when the prefix provider has no credentials (#10584)
* fix(audio): fall back nested STT models when the prefix provider has no credentials Bare ids such as deepgram/nova-3 prefix-match the native provider and 400 when that key is missing, even if OpenRouter lists the same model. Retry the gateway and mention qualified catalog ids in the error. Closes #10583 * test(audio): scope whisper-1 fallback test to a 2-provider registry nanogpt was added to AUDIO_TRANSCRIPTION_PROVIDERS (already merged, unrelated to this fix) with a bare "whisper-1" model id, which now intercepts findAlternateAudioProvider's first candidate before the qualified-alias branch this test exists to cover. Scope the test to a local {openai, openrouter} registry subset so it deterministically exercises the qualified `${provider}/${model}` fallback regardless of future providers that also list a bare "whisper-1" id. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
3d0ffb49a4 |
feat(providers): complete Jina + Gemini Embedding 2 multimodal via OmniRoute (#10581)
* feat(providers): complete Jina AI via OmniRoute including Omni multimodal
Dashboard and env keys share one Jina credential pool, native v5 Omni
{text}/{image}/{content} docs pass through /v1/embeddings intact, and
classify/segment/search are proxied without a third unused Jina card.
* chore(changelog): name Jina complete-provider fragment for #10581
* feat(providers): make Gemini Embedding 2 multimodal work via OmniRoute
Route gemini-embedding-2 through embedContent/batchEmbedContents so N
OpenAI input items become N vectors, pass through native multimodal
parts, and use dashboard Gemini keys (GEMINI_API_KEY only as fallback).
* fix(providers): resolve rebase fallout for Jina/Gemini embeddings
- narrow the two new no-explicit-any violations introduced by this PR
(validateJinaFoundationProvider's params + catch, search.ts's
normalizeJinaSearchResponse data param)
- cast credentials to Record<string, unknown> at the two quota-preflight
call sites in src/sse/services/auth.ts so the new JinaEnvCredentials /
GeminiEnvCredentials union members type-check without loosening the
allRateLimited narrowing used elsewhere in the same function
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
|
||
|
|
9222528bdd |
fix(opencode): session stability, free-tier routing, and CLI defaults (#10571)
* fix(opencode): session stability, free-tier routing, and CLI defaults - Wire generateSessionId() into opencodeHeaders so x-opencode-session is a deterministic fingerprint instead of randomUUID() per request, enabling upstream prompt caching across a conversation - Thread request body through buildHeaders() so session fingerprint has access to model, system, messages, and tools - Default CLI header synthesis to ON (opt-out via false), align values with 9router proven defaults (opencode/desktop/global) - Auto-echo listing-valid model names for noAuth providers so response.model matches /v1/models listing - Short-circuit free-tier model resolution to opencode provider first to prevent prefix inference misrouting when catalog is unreachable * fix(opencode): make free-tier default flip self-consistent + add coverage PR #10571 flipped OPENCODE_SYNTHESIZE_CLI_HEADERS to on-by-default and changed the synthesized UA/client/project default values, but shipped with 2 broken assertions in the existing #5997 regression test and no coverage for the new session-fingerprinting, free-tier routing, or noAuth echoModel logic (Hard Rule #18). - Update tests/unit/opencode-cli-headers-synthesis-5997.test.ts to match the new on-by-default behavior and new default values; add an explicit opt-out coverage test so the forward-only path is still guarded. - Fix 20 further test failures in tests/unit/opencode-executor.test.ts and tests/unit/refactor-buildHeaders-opencode.test.ts caused by the same default flip (pin OPENCODE_SYNTHESIZE_CLI_HEADERS=false for the characterization suites that predate #10571; use a genuinely CLI-looking UA where the preserved-UA test requires one). - Fix a real bug found via TDD while adding the mandated free-tier routing regression test: the big-pickle/*-free short-circuit in open-sse/services/model.ts checked activeProviders?.has("opencode") literally, but getActiveProviderSet() canonicalizes every connection's provider id through resolveProviderAlias(), which rewrites "opencode" to "opencode-zen" via a manual override — so an active no-auth opencode connection could never satisfy the check. Now checks both opencode-family candidate ids. Proven with a test that fails on the original code and passes with the fix (both connections active with a stale synced catalog omitting big-pickle). - Extract the noAuth-provider echoModel aliasing in chatCore.ts into a pure, directly-testable helper (open-sse/handlers/chatCore/noAuthEchoModel.ts), matching the existing chatCore god-file decomposition pattern. - Add regression tests for generateSessionId()-based x-opencode-session fingerprinting (stable within a conversation, changes on model/message changes), the free-tier routing short-circuit, and the noAuth echoModel aliasing. - Add the changelog.d/ fragment and sync docs/reference/ENVIRONMENT.md's OPENCODE_SYNTHESIZE_CLI_HEADERS/OPENCODE_USER_AGENT/OPENCODE_CLIENT/ OPENCODE_PROJECT rows to the new defaults. Does NOT resolve whether flipping OPENCODE_SYNTHESIZE_CLI_HEADERS's default was the right call, and does NOT touch the separate open PR #10357 which flips the same flag with a different literal default value - that decision is left to the maintainer at merge time. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
c2dbe2f1fb |
docs: add embeddings client runbook for Gemini 2 and Jina omni (#10569)
Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c767494ae4 |
feat(api): alias /v1/multimodal-embeddings to /v1/embeddings (#10568)
Jina-compatible clients POST /v1/multimodal-embeddings and currently get HTTP 404 unknown_route from the catch-all. Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
458ab1aac0 |
fix(vision): preserve high detail for inline images (#10554)
* fix(vision): preserve high detail for inline images * fix(vision): scope high-detail image default to OpenCode clients defaultImageDetail() was applied at prepareUpstreamBody, the shared upstream-body prep path for every provider and format, not just the OpenCode path the fix targets. Gate it on isOpencodeClient (the existing User-Agent/x-opencode-* header signal already used for bypassDefaultToolLimit at this call site) so non-OpenCode callers keep the provider's own image detail default. Adds a regression test covering a non-OpenCode caller against the same opencode-zen provider. * fix(vision): document and test the global vs OpenCode-only detail scope The OpenCode-only high-detail default in chatCore/upstreamBody.ts (defaultImageDetail, gated on isOpencodeClient) forwards the caller's own image_url.detail and was already correctly scoped in a prior commit on this branch. The internal vision-bridge describe self-loop (visionBridgeHelpers.ts) is architecturally global: VisionBridgeGuardrail runs for every caller/provider whenever the target model lacks vision support, and there is no client-identity signal at that layer to gate on. Its describe prompt explicitly asks the vision model to transcribe visible text, so requesting "high" detail unconditionally is justified on its own merits (OCR accuracy), independent of the OpenCode motivation. Adds a compatibility assertion proving the Anthropic wire-format branch of the same describe self-loop carries no `detail` field (it has no such concept) and is therefore unaffected by this default, and documents the split (OpenCode-only forwarding vs. global describe default) in docs/security/GUARDRAILS.md. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: rinseaid <rinseaid@rinseaid.net> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
15386495c2 |
fix(compression): add i18n support for less-code and terse-prose (#10498)
* fix(compression): add i18n support for less-code and terse-prose Translates less-code output style to pt-BR, vi, ja, and id. Adds missing vi translation to terse-prose caveman mode. Removes less-code from English-only allowlist and updates matrix tests. Fixes #10426 * docs(compression): add output styles coverage table Adds the requested Output Styles matrix to the compression guide covering styles, supported languages, and intensity levels. Fixes #10426 |
||
|
|
4c5535be4e |
Update SETUP_GUIDE.md (#10490)
* Update SETUP_GUIDE.md * docs: correct Windows data directory note Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
497dd6f357 |
fix(memory): auto-check Qdrant health on mount and stop false-red badge (#10489)
* fix(memory): auto-check Qdrant health on mount and stop false-red badge The Qdrant engine card on /dashboard/memory?tab=engine showed a red "Error" badge after every page refresh even when Qdrant was healthy: the badge derives its state from a health check, but the mount effect only fetched settings + embedding models — health started as null and the render treated `health?.ok` (undefined) as a failure. Clicking "Test connection" (which runs the same server-side /readyz check) immediately turned it green, proving the connection was fine. Two changes: - Auto-run the health check on mount once settings load and Qdrant is enabled, so a refreshed page reflects the real state (verified live: /api/settings/qdrant/health returns ok:true in ~2ms on a healthy compose deployment). - While health has not been checked yet (null), render a neutral gray "Testing..." state instead of red — red is now reserved for an actual failed health check. Regression test added (fails on the old code): with enabled settings and a healthy mock, the card must hit /api/settings/qdrant/health on mount and show statusActive, never statusError. * chore(changelog): fragment for #10489 * Merge branch 'release/v3.8.50' into fix/qdrant-health-badge * test(fix): refresh expired alibaba quota sample validity and onnxruntime pin for v3.8.50 base - alibaba-free-tier-quota-fetcher.test.ts: sample quotaValidityPeriod (2026-08-16 16:00 UTC) is in the past, making every quota entry classify as expired/not_capable; bump to 2028-01-01 UTC so the text/merge classification tests exercise the intended path again. - optional-transformers-dependency.test.ts: onnxruntime-node pin assertion updated from ~1.24.3 to ~1.27.0 to match package.json (bumped by #10403); the regular-not-optional intent is unchanged. * test(fix): align optional-transformers-dependency with onnxruntime ~1.24.3 pin (base #10543) * docs(fix): sync 150-migration count and document PROXY_LOG_INCLUDE_IPS (base drift #10348/#10507) * fix(memory): re-check Qdrant health after saving settings save() optimistically flipped enabled and started the PUT while the mount effect could immediately GET /api/settings/qdrant/health against the OLD persisted settings. If that GET won, it returned not_configured/failed and - because health was non-null - the effect never retried after the PUT succeeded, leaving a healthy Qdrant red until a manual Test connection. Invalidate health (generation counter + setHealth(null)) at save start and after a successful PUT, then explicitly schedule a fresh check: setting health to null alone is not enough, React bails on the no-op when health is already null (the exact GET-wins ordering). Stale responses are dropped via the sequence guard so an in-flight pre-save check can never overwrite the post-save result. Adds a regression test covering enable ordering. Addresses PR #10489 review finding (issuecomment-5312271806). * fix: narrow omniglyph transform result union (merge base |
||
|
|
d49ccdaaf1 |
fix(sse): gate structural chat admission shedding on real heap pressure (#10437)
* fix(sse): gate structural chat admission shedding on real heap pressure Closes #10183, Closes #10268 3.8.49 (#9654/#9940) replaced the 3.8.48 heap-ratio shed (heapUsed/heapLimit >= 0.75) in chatBodyAdmission.ts with an unconditional CHAT_MAX_HEAVY_IN_FLIGHT=1 structural lease. A second concurrent "structurally heavy" chat request (>=200 messages, >=64 tools, or >=32k estimated tokens — routine for coding-agent fan-out like Hermes/Cursor/Claude Code) was hard-rejected with a retryable HTTP 503 chat_admission_busy/structure_limit regardless of actual heap pressure, even on a host with ample free RAM. Restore the heap-conditional gate as an ADDITIONAL check layered on top of (not a replacement for) the #9654 bounded-concurrency / per-connection-lane protection: when heavyweight capacity is busy, only enter the bounded-wait/shed path when a live heap-pressure probe (heapUsed / v8 heap_size_limit >= OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO, default 0.75) confirms real pressure. A healthy heap now admits the second heavy request immediately via a no-op lease instead of parking or shedding it. The probe is injectable via admitChatStructure({ heapPressureCheck }) for deterministic tests. Regression tests: - tests/unit/bug-10183-admission-heavy-healthy-heap.test.ts (new, permanent): healthy-heap 2nd heavy request now admitted (was RED); genuinely pressured heap still sheds it. - tests/unit/probe-10268-structural-503.test.ts (promoted to permanent): the exact reported 503 chat_admission_busy shape is still produced under real heap pressure, and the same fan-out is admitted on a healthy heap. - tests/unit/chat-body-admission.test.ts, tests/unit/chat-body-admission-queue.test.ts, tests/unit/per-connection-admission-9654.test.ts updated to inject heapPressureCheck: () => true where they exercise the busy/shed path, preserving #9654/#4380 coverage. Gates run: npm run typecheck:core (clean), eslint --suppressions-location config/quality/eslint-suppressions.json on changed files (clean), scripts/check/check-file-size.mjs (OK), scripts/check/check-test-discovery.mjs (OK), focused admission suite (68/68 passing) and npm run test:unit (in progress at commit time under heavy shared-devbox contention from a 13-way parallel session fan-out; no admission-related failures observed through 1873 lines of output, the sole failure seen was a pre-existing unrelated proxy/search timeout consistent with known load-induced flakiness, not a regression from this change). ⚠️ base-red inherited: #9985 — ESLint errors (2) from #10250 * docs(env): document OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO (#10183, #10268) * fix(sse): bound the healthy-heap admission fast path (#10437) The #10183/#10268 fix admitted a busy heavyweight request immediately whenever the heap was healthy, via an unconditional no-op lease with no bound of its own -- an unlimited number of "healthy heap" requests could pile in ahead of the heap-pressure shed path, defeating the point of admission control. Adds an independent, bounded healthy-heap headroom budget (CHAT_ADMISSION_HEALTHY_HEADROOM, tryAcquireHealthyHeadroom()) that the healthy-heap fast path draws from; once exhausted, requests fall through to the same bounded-wait/shed path used under real heap pressure, which is otherwise unchanged. Also fixes a pre-existing gap in per-connection-admission-9654.test.ts's shared-budget test, which needed an explicit heapPressureCheck override to keep exercising the #10110 invariant now that a healthy heap gets bounded headroom instead of an outright reject. * docs(env): document OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM in .env.example Documented in docs/reference/ENVIRONMENT.md but missing from .env.example, caught by the env-doc-sync gate when combined with other PRs in the release merge-train. --------- Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> |
||
|
|
514573b1f6 |
fix(proxy-subscriptions): allow local/loopback proxy-subscription fetch URLs (#10416)
* fix(proxy-subscriptions): allow local/loopback proxy-subscription fetch URLs
The subscription fetch guard (fetchGuard.ts) unconditionally blocked all
loopback/private IP ranges as SSRF protection, but the same feature already
permits loopback for the routing half (coreEndpoint.ts's
ALLOWED_LOCAL_CORE_HOSTS) — so an operator could route traffic through a
loopback core but could not fetch a proxy list from a loopback HTTP server.
Make the fetch guard local-first by reusing the existing
areLocalProviderUrlsAllowed() policy (default ON) from
outboundUrlGuardPolicy.ts: loopback/private hosts are now allowed as fetch
targets by default, while cloud-metadata/link-local (169.254.0.0/16, incl.
169.254.169.254 IMDS) and the unspecified address stay blocked
unconditionally, mirroring the provider-validation guard's "block-metadata"
mode. Callers that want the old strict behavior can pass
{ allowLocal: false }.
Closes #10158.
* fix(proxy-subscriptions): unwrap IPv4-mapped IPv6 + full fe80::/10 range (#10416)
The #10158 SSRF guard left two gaps on the IPv6 side: an IPv4-mapped IPv6
literal (::ffff:a.b.c.d) skipped IPv4 range checking entirely, and the
link-local check only matched strings literally prefixed with "fe80"
instead of the full fe80::/10 range (fe80:: - febf:ffff::), so fe90::,
febf:ffff::, etc. were wrongly allowed through.
isIpv6Blocked() now unwraps mapped IPv4 addresses (both the dotted-quad
and WHATWG-normalized hex-group forms) and re-checks them against the
IPv4 rules, and link-local detection parses the first hex group's numeric
value against the 0xfe80-0xfebf range instead of a string prefix.
---------
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
|
||
|
|
1089c24bc8 |
Remove/mimocode sunset provider (#10186)
* remove: drop sunset MiMoCode provider from model catalog * remove: drop sunset MiMoCode provider from model catalog (shared.ts) Remove unused imports, types, and comments from shared.ts. * remove: MiMoCode provider (Xiaomi sunset) — executor, registry, no-auth config, icon, tests * refactor(providers): finish MiMoCode removal — sweep remaining no-auth references Drop the leftover mimocode entries from the no-auth provider controls, the translate-path snapshot, the eslint suppressions, and the #3061 auth-loop test. Re-point the fingerprint-pin (#6696) and proxy-noauth (#6272) tests at opencode, which exercises the same fingerprint path, so the removal does not break runtime behavior. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(providers): reconcile provider/executor counts after MiMoCode sunset The base's parallel doc-count sync (#10433) pinned 340 providers / 101 executors. With mimocode removed, live code has 339 providers and 100 executors; refresh the user-facing counts (package.json description, llm.txt, README/AGENTS, i18n llm.txt, provider reference, diagrams) so the check-docs-counts STRICT gate stays green. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * test(providers): fix orphaned mimocode references after MiMoCode sunset The sunset removed mimocode/mcode from the free-onboarding candidates and from FINGERPRINT_PROVIDERS, but two tests still referenced them: - free-provider-onboarding-setup: the mimocode->theoldllm substitution introduced duplicate 'opencode' rows (impossible given the request-set dedupe) and the wrong display name; align expectations with the actual {opencode, theoldllm} dedupe behavior and 'The Old LLM (Free)' name. - combo-system-prompt-templates-5501: resolveTargetFingerprint tested with provider 'mcode', which is no longer a fingerprint provider; point it at the remaining fingerprint provider 'opencode'. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: Tushar49 <Tushar49@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: diegosouzapw <diegosouza.pw@gmail.com> |
||
|
|
2d50ec0789 |
feat(routing): add quota-aware provider scheduling — Phase 2 (#10126)
* feat(quota): Phase 2 adapters, reset timers, analytics, and dashboard API
* feat(routing): add quota-aware provider scheduling (opt-in)
* fix(db): rename migration to 148_provider_quota_state.sql
* fix(quota): harden quota state route, isolate phase2 tests, slim env diff
- route: requireManagementAuth + Zod body validation + buildErrorBody
sanitization (Hard Rule #12); fix clearProviderQuotaState -> clearProviderQuota
- .env.example/ENVIRONMENT.md: drop ~20 foreign vars, keep only
OMNIROUTE_QUOTA_AWARE_ROUTING (migration 148)
- tests/unit/quota-phase2.test.ts: DATA_DIR mkdtemp + resetDbInstance teardown
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* chore(ci): fix docs-sync + eslint-suppression drift for quota branch
CI gates flagged on PR #10126 head
|
||
|
|
0a74bfbdea |
feat(cli): relay-like CLI closure — target manifest, Codex TOML, Gemini launcher, guards
- canonical executable manifest (bin/cli/cli-manifest.mjs): run/configure/completion derive targets, aliases and --model wiring from one table; drift test cross-checks manifest x cliRuntime x UI catalog (tests/unit/cli/cli-manifest-drift.test.ts) - dashboard Codex generator converged to ~/.codex/config.toml (modern Codex v0.137+, verified against codex-cli 0.147.0): conservative merge, env_key auth (key never written), refuses invalid TOML, reports legacy config.yaml as migration note - omniroute run gemini: launcher over OmniRoute's /v1beta surface via GOOGLE_GEMINI_BASE_URL + isolated GEMINI_CLI_HOME forcing gemini-api-key auth (contract proven against @google/gemini-cli 0.50.0); ACP registration kept distinct - opt-in real smoke harness for upstream CLIs (RUN_CLI_SMOKE=1, credential by env NAME, redacted output): tests/integration/upstream-cli-smoke.int.test.ts - container-guard homologation for POST /api/cli-tools/apply (422 in container, dry-run preview allowed, host write passes) + docs; guard untouched - typecheck: omniglyphAdapter union narrowing, usageTracking typed signatures (UsageLike, no any), models.ts isValidModel params — typecheck:core and typecheck:noimplicit:core now clean - relay core (prior session of this effort): omniroute run for 6 CLIs, configure picker with per-context favorites/recents, contexts with optional keychain + 0600 fallback, provider CRUD with recursive redaction, completion updates, docs |
||
|
|
34bb018d21 | docs(video): document fusion telemetry, drill-down byte budget, cache key dimensions and fixed dedup threshold | ||
|
|
ebf3312fe7 | docs(video): document timestamped contact sheets | ||
|
|
bb22eeba8d | feat(video): add isolated drill-down cache | ||
|
|
edb3abf323 | feat(video): add segment-aware sampling | ||
|
|
bcd58975c5 | docs(video): describe optional audio fusion | ||
|
|
ad9384c3ea | feat(video): preserve transcript provenance | ||
|
|
ffa6849cc8 | feat(video): add validated focus windows | ||
|
|
596a1035c3 | feat(video): add conservative frame deduplication | ||
|
|
2c33638643 | feat(video): add scene-aware sampling fallback | ||
|
|
ea0cdc559c |
docs(compression): document the output-style catalog and its extension point (#10649)
The five output styles (terse-prose, less-code, ponytail, i-have-adhd, terse-cjk) shipped in Phase 4 but COMPRESSION_GUIDE.md had zero mention of them. Add the catalog table with per-style language coverage, the injection contract (catalog order, single marker, shared boundaries once), the config shape and back-compat note, plus an 'Adding an Output Style' recipe in EXTENDING_COMPRESSION.md covering the matrix guard and translation floor. Refs #10426 Co-authored-by: Xiangzhe <bakryun0718@proton.me> |
||
|
|
8ba25e9318 |
docs: add the VS Code Copilot Chat guide and document the /v1/models prefix modes (#10648)
Adds docs/guides/VSCODE-COPILOT.md covering the OmniCopilot extension: install from either store, connection setup, what the picker actually shows and why, the dashboard-in-a-tab mode, and a troubleshooting table. Documents two contracts that existed in code but nowhere in the docs: - The ?prefix= query parameter on GET /v1/models, with the warning that "canonical" omits providers whose alias already is the canonical id — so "alias" is the safe direction for a de-duplicated list. - MODELS_CATALOG_PREFIX_MODE in .env.example and ENVIRONMENT.md, matching how ARENA_ELO_SYNC_ENABLED and PII_REDACTION_ENABLED are already documented. The fabricated-docs gate cannot see this flag being read, because resolveFeatureFlag() indexes process.env by key rather than naming it; added an allowlist entry explaining that, in the style of the existing entries. Co-authored-by: Xiangzhe <bakryun0718@proton.me> |
||
|
|
c164ed962b |
fix(providers): validate bailian-coding-plan against the Token Plan host (#10634)
* fix(providers): validate bailian-coding-plan against the Token Plan host The catalog entry is the personal Alibaba Token Plan, but the region map still resolved the retired Coding Plan hosts. #10290 moved only the open-sse registry (inference) to token-plan.ap-southeast-1.maas.aliyuncs.com, leaving the dashboard's key validation pointed at coding-intl.dashscope.aliyuncs.com. That host rejects Token Plan keys with 401, and validateBailianCodingPlanProvider maps 401/403 to "Invalid API key" — so adding a working key failed at the modal while the same key served inference fine. Verified live 2026-08-18 with a valid key: legacy host 401 invalid_api_key, Token Plan host 429 quota (auth OK). - point both regions of ALIBABA_PROVIDER_ENDPOINTS at the Token Plan hosts, matching what docs/providers/ALIBABA-QWEN-PROVIDER-FAMILIES.md already stated - keep the retired hosts recognized as presets, so connections saved with the old URL still follow the region selector instead of being pinned to a dead host - keep image/video generation on the DashScope AIGC hosts, which the Token Plan host does not serve - probe with a model this plan actually serves (qwen3-coder-plus was Coding Plan) * test(providers): compare parsed hostnames in the legacy-host guard CodeQL flags URL .includes() checks as js/incomplete-url-substring-sanitization. The guard is an assertion, not a sanitizer, but comparing new URL().hostname is strictly more precise anyway — same coverage, no substring pattern. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me> |
||
|
|
aa912c42a7 | docs: update omni route video guides ranking layout | ||
|
|
63a6618d34 | chore(release): synchronize localized llm mirrors | ||
|
|
fb2585530d | chore(release): sync v3.8.50 base quality docs | ||
|
|
e3bca29bbc |
fix(docker): real image tags (bifrost/cliproxyapi) + complete OMNIROUTE_BASE_PATH runtime patcher (#10482)
* fix(docker): real image tags + complete OMNIROUTE_BASE_PATH runtime patcher
Three docker issues fixed:
1. Images that do not exist:
- bifrost: ghcr.io/maximhq/bifrost:1.5.21 never existed (1.5.x tops at
v1.5.16, all tags carry the v prefix) -> ghcr.io/maximhq/bifrost:v1.6.11
- cliproxyapi: ghcr.io/router-for-me/* is not publicly pullable (403);
the official prebuilt image is docker.io/eceasy/cli-proxy-api, where
the pinned v6.9.7 exists -> docker.io/eceasy/cli-proxy-api:v6.9.7
- Verified still-current: redis:8.6.5-alpine (already on Redis 8 since
#9065; ioredis 5.10 is RESP2/3-compatible, no modules used) and
qdrant:v1.12.4 -- both exist, unchanged.
2. OMNIROUTE_BASE_PATH ignored on prebuilt images (root cause):
Next 16 (webpack and Turbopack) app-router renders SSR asset URLs from
assetPrefix ALONE; basePath only affects routing. The runtime patcher
(ensure-docker-base-path) rewrote basePath literals only, so a prebuilt
root-path image patched to /omniroute served the page but every
/_next/static shell reference stayed unprefixed (404 behind a subpath
proxy), the RSC flight-payload chunk refs came from client-reference
manifests baked with unprefixed paths, and the Turbopack client process
shim ships an empty env object so the client never learns the subpath.
Extended patch-standalone-base-path.mjs to also rewrite:
- assetPrefix literals (mirrors the subpath for SSR asset URLs)
- the NEXT_PUBLIC_OMNIROUTE_BASE_PATH env mirror in the inline config
- the client process.env shim (.env={}) with the two basePath keys
- every baked "/_next/static URL (manifests, media imports, .html pages)
next.config.mjs now mirrors basePath into assetPrefix so REBUILT images
bake prefixed assets too. E2E-verified on the published main-web image:
HTML under /omniroute now has 16/16 prefixed JS srcs and 82/82 prefixed
flight refs (was 13/9 + ~150 unprefixed), prefixed assets return 200.
* chore(changelog): fragment for #10482 (docker images + basepath patcher)
* chore(changelog): bullet-form fragment for #10482
* Merge branch 'release/v3.8.50' into fix/docker-compose-images-and-basepath
* test(fix): refresh expired alibaba quota sample validity and onnxruntime pin for v3.8.50 base
- alibaba-free-tier-quota-fetcher.test.ts: sample quotaValidityPeriod
(2026-08-16 16:00 UTC) is in the past, making every quota entry classify
as expired/not_capable; bump to 2028-01-01 UTC so the text/merge
classification tests exercise the intended path again.
- optional-transformers-dependency.test.ts: onnxruntime-node pin assertion
updated from ~1.24.3 to ~1.27.0 to match package.json (bumped by #10403);
the regular-not-optional intent is unchanged.
---------
Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
|
||
|
|
24ef1dc3d4 |
Sanitize test fixtures, add developer .env guidance, and add gitleaks… (#10411)
* Sanitize test fixtures, add developer .env guidance, and add gitleaks workflow - Replace realistic-looking AWS keys and PEM fixtures in unit tests with synthetic placeholders to avoid false positives from secret scanners. - Add docs/DEVELOPER-ENVIRONMENT.md describing postinstall .env behavior and remediation guidance. - Add .github/workflows/gitleaks.yml to run gitleaks on pull requests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add gitleaks baseline and CI baseline support; update ignore and PR body\n\n- Copy gitleaks-local.json -> gitleaks-baseline.json\n- Add --baseline-path to workflow\n- Allowlist baseline in .gitleaks.toml\n- Ignore gitleaks-local.json\n- Add PR_BODY.md with scan summary\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(security): fix gitleaks config, drop redundant baseline/CI, clean doc artifacts - Fix the malformed .gitleaks.toml [[rules]] block: an inline [rules.allowlist] with only paths (no regex/path at rule level) made gitleaks refuse to load the config (`FTL Failed to load config ... both |regex| and |path| are empty`), turning the project's blocking check-secrets ratchet into a hard failure. Verified: check-secrets config now loads and exits 0. - Reconcile with the existing gitleaks gate: remove the redundant .github/workflows/gitleaks.yml and root gitleaks-baseline.json (a second, differently-scoped scanning mechanism + an unreviewed 430-finding blanket baseline) — the project already runs scripts/check/check-secrets.mjs as a blocking ratchet in ci.yml/quality.yml and its .gitleaks.toml policy is to fix real findings, not blanket-allowlist them. - Remove the stray PR_BODY.md automation artifact from the repo root. - Fix the duplicated <div align="center"> tag in README.md. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: OmniRoute Bot <noreply@omniroute.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: blarovse <312250233+blarovse@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
fbc67f1338 |
fix(models): honor MODELS_DEV_SYNC_ENABLED=0 over dashboard settings (#10299)
* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190) Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13 (with monaco-editor scoped override). Closes Dependabot #189, #190. Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge — awaiting Dependabot re-scan. npm audit → 0 vulnerabilities. * fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks) _tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential _tasks symlink can slip in via git add -A and, once pulled, checkout materializes it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks ignores the symlink too, preventing re-capture. * Hide health-check excluded models from /v1/models catalog (#10026) Mirror the request-time exclusion rule (provider_specific_data.excludedModels) in the unified catalog builder: a model is hidden when its provider has connections but none of them is eligible for it. Applied across the PROVIDER_MODELS, synced, custom, alias-backed, and managed-fallback loops so ghost models no longer appear as available. Co-authored-by: ritheshcn25 <ritheshcn25@users.noreply.github.com> * fix(models): memoize getModelsDevPricing (event loop / healthz) (#10055) * fix(models): memoize getModelsDevPricing for /v1/models catalog resolveCatalogPricing called getModelsDevPricing once per model while building GET /v1/models. Each call re-scanned models_dev_pricing and JSON.parsed every row (~10k SQL scans + multi-GB parse work), pegging the event loop so even /healthz timed out (#9685, #10052). Memoize the parsed map until saveModelsDevPricing / clearModelsDevPricing and add a unit test for invalidation. Signed-off-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> * fix(db): invalidate modelsDevPricing cache on DB reset (#10055) Copilot review fixes: 1. Register invalidateModelsDevPricingCache() with DB state reset system so resetDbInstance() clears the process-local memo, preventing stale pricing data from surviving across DB reset/restore operations. 2. Add test assertion verifying DB reset bypasses the memo (Copilot #10055). The process-local memo at modelsDevSync.ts:204 caches getModelsDevPricing() results until saveModelsDevPricing()/clearModelsDevPricing() to avoid re-scanning all pricing rows on every /v1/models request. Without this hook, backup restore and test DB resets would serve stale cached data from the previous connection. Tests: npm run test:unit:serial -- tests/unit/modelsDevSync-extended.test.ts --------- Signed-off-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> * fix(models): honor MODELS_DEV_SYNC_ENABLED=0 over dashboard settings The file header already advertised this env var but nothing read it. When catalog/compression pin the event loop, the dashboard (same process) cannot turn models.dev sync off. Let 0/false/off win over sqlite so an operator can recover with env + restart. Skip getModelsDevPricing SQL scans while the kill switch is set. * fix(models): restore prettier formatting after base merge Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * test(models): cover env kill switch during live settings updates Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Signed-off-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: ritheshcn25 <rithesh.chandran@snb.ca> Co-authored-by: ritheshcn25 <ritheshcn25@users.noreply.github.com> Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
b1a2ff6887 |
feat(proxy): non-destructive auto-disable mode for the proxy health scheduler (#10342)
* feat(proxy): add non-destructive auto-disable mode for the proxy health scheduler PROXY_AUTO_REMOVE was the only opt-in action the background proxy health scheduler could take on a consistently failing proxy, and it deletes the row. For a manually-maintained proxy chain (multi-proxy pool/rotation, #6365) that is too destructive just to exclude a temporarily-dead member. Add PROXY_AUTO_DISABLE as a sibling flag: at the same consecutive-failure threshold it soft-disables the proxy (status "dead") instead of removing it. "dead" is already one of the statuses the pool/rotation alive-filter excludes, so a disabled proxy drops out of the active chain immediately with no other code changes. The scheduler keeps probing dead proxies on its normal interval, and the existing recovery branch (previously autoRemove-only) re-activates it automatically once it starts answering again. decision.ts's decideProxyHealthAction() gets an optional `autoDisable` input (defaults to false, so existing callers are unaffected) and a "dead" status value; scheduler.ts wires the new PROXY_AUTO_DISABLE env flag through. If both flags are set, auto-remove wins. getProxyHealthStats() now also surfaces the registry `status` so operators can see when a proxy was auto-disabled, and ProxyStatusBadge now treats the full "not alive" status set (not just the literal string "inactive") as inactive in the dashboard. * test(proxy): assert registry status in getProxyHealthStats output The non-destructive auto-disable change added the live registry status to the stats object returned by getProxyHealthStats. Align the pre-existing db-proxies-crud assertion with the intended output shape. Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> * fix(proxy): preserve auto-disabled status in dashboard edits Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> Co-authored-by: Gi99lin <Gi99lin@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
b082d0735b |
fix(api-manager): allow empty combo restrictions (#10066)
* fix(api-manager): allow empty combo restrictions Represent unrestricted Combo access explicitly as combo/* so an empty Allowed Combos list can deny every Combo without affecting direct model routes. Preserve existing keys through migration 149 and cover Dashboard, policy, routing-target, and migration behavior. * docs: sync migration count to 149 after api-key combo-access migration Merging release/v3.8.50 forward landed 149_api_key_combo_access.sql, bumping the real migration count from 148 to 149. Updates README.md, AGENTS.md, llm.txt (root + all 42 i18n mirrors, exact-copy requirement) so the strict docs-counts-sync gate matches the live count again. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> Co-authored-by: xz-dev <xz-dev@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> |