Compare commits

...

159 Commits

Author SHA1 Message Date
diegosouzapw fb8d187f8d chore(release): v3.2.2 — Four-Stage Request Logs & Bugfixes
Build Electron Desktop App / Validate version (push) Failing after 35s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
2026-03-28 22:11:22 -03:00
diegosouzapw 1a11301e1a Merge branch 'codex/request-log-pipeline-json' 2026-03-28 22:09:34 -03:00
R.D. 4c6cdd5c23 test: align pipeline integration assertions 2026-03-28 22:09:27 -03:00
R.D. 30a64b0dd3 test: align security hardening log helper checks 2026-03-28 22:09:27 -03:00
R.D. 04de492019 fix: add four-stage request log payloads 2026-03-28 22:09:27 -03:00
R.D. 07890df6cb test: align pipeline integration assertions 2026-03-28 22:07:20 -03:00
R.D. 2f23cfdf1c test: align security hardening log helper checks 2026-03-28 22:07:20 -03:00
R.D. 1832946d41 fix: add four-stage request log payloads 2026-03-28 22:07:20 -03:00
Diego Souza 6ec8745d2e ci: add GitHub Packages publish configuration for GHCR and NPM 2026-03-28 22:04:02 -03:00
diegosouzapw b6bbfe063b fix(sse): preserve cache_control in Claude passthrough mode (#708) 2026-03-28 22:01:38 -03:00
oyi77 48182edbd5 fix(translator): remove thoughtSignature from functionCall parts in Gemini translation
HTTP 400 "invalid argument" was triggered when OmniRoute translated OpenAI
tool_calls to Gemini format, because thoughtSignature was injected onto every
functionCall part unconditionally.

thoughtSignature is only valid on thinking/reasoning parts (those with
thought: true). The Gemini API rejects any request where a functionCall
part carries a thoughtSignature field, returning HTTP 400.

Fix: remove the thoughtSignature field from functionCall parts. The thinking
parts that legitimately require thoughtSignature (emitted when a message has
reasoning_content) are unchanged.

Adds regression test (T43) with three cases:
- single tool call: no thoughtSignature on functionCall part
- multiple tool calls: none carry thoughtSignature
- thinking part regression guard: thoughtSignature still present on thought parts

Fixes #725
2026-03-28 21:57:15 -03:00
Diego Rodrigues de Sa e Souza fc24361aa6 Merge pull request #726 from diegosouzapw/release/v3.2.1
Build Electron Desktop App / Validate version (push) Failing after 26s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.2.1 — context pinning fix + global fallback
2026-03-28 21:19:24 -03:00
diegosouzapw cec833afc6 chore(release): v3.2.1 — context pinning fix + global fallback provider 2026-03-28 21:13:14 -03:00
diegosouzapw f1cddba938 feat: add global fallback provider support (#689)
When all combo models are exhausted (502/503), OmniRoute now checks for
a globalFallbackModel setting and attempts one last request through it
before returning the error. Settings stored in key_value table, no
migration needed.
2026-03-28 21:10:29 -03:00
diegosouzapw a0acdfdcb9 fix: context pinning bypass during tool-call responses (#721)
Non-streaming: Fixed json.messages check to use json.choices[0].message
(OpenAI format). Streaming: inject pin tag before finish_reason chunk for
tool-call-only streams. injectModelTag now appends synthetic assistant
message when content is null/array (tool_calls).
2026-03-28 21:04:47 -03:00
Diego Rodrigues de Sa e Souza 6637f294df chore: release v3.2.0 (#722)
Build Electron Desktop App / Validate version (push) Failing after 28s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-03-28 20:45:18 -03:00
dependabot[bot] ad8a444105 deps: bump path-to-regexp from 8.3.0 to 8.4.0 (#715)
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 8.3.0 to 8.4.0.
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v8.3.0...v8.4.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-version: 8.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-28 20:39:29 -03:00
Chris 877cfa0071 feat: add GLM Coding usage/quota tracking with Z.AI session quota (#698)
* feat: add GLM Coding usage/quota tracking with Z.AI session quota

Add GLM to the usage tracking pipeline: usage API route, Z.AI quota
fetcher (TOKENS_LIMIT percentage-based), quota parser, and Provider
Limits UI. Adds API region dropdown (International/China) to Add/Edit
connection modals. Displays session quota with plan level.

* fix: address PR review feedback for GLM usage tracking

- Remove explicit `any` types from getGlmUsage (fix lint budget)
- Fix empty string fallback for plan level
- Remove duplicate `case "glm"` in quota parser (identical to default)
- Skip OAuth refresh flow for GLM (API key auth) in usage route

* fix: upgrade path-to-regexp to fix ReDoS vulnerability (GHSA-j3q9-mxjg-w52f, GHSA-27v5-c462-wpq7)

---------

Co-authored-by: Chris Staley <christopher-s@users.noreply.github.com>
2026-03-28 20:39:24 -03:00
Paijo e6f0a780b7 feat(dashboard): add Cache Management page with stats, hit rate, and targeted invalidation (#701)
Adds a new /dashboard/cache page that surfaces the existing but UI-less
semantic cache infrastructure.

Changes:
- New page: src/app/(dashboard)/dashboard/cache/page.tsx
  - Live stats: memory entries, DB entries, cache hits, tokens saved
  - Hit rate progress bar with color coding (green/yellow/red)
  - Hits/Misses/Total breakdown
  - Idempotency layer stats (active dedup keys + window)
  - Cache behavior info panel
  - Clear All button
  - Auto-refresh every 10s
- Enhanced API: src/app/api/cache/route.ts
  - DELETE ?model=<name> — invalidate by model
  - DELETE ?signature=<hex> — invalidate single entry
  - DELETE ?staleMs=<ms> — invalidate entries older than N ms
  - DELETE (no params) — clear all (existing behavior)
- Sidebar: added Cache nav item (icon: cached)
- i18n: added cache + sidebar.cache keys for all 31 supported locales

No new dependencies. All functionality builds on existing semanticCache.ts,
cacheLayer.ts, and idempotencyLayer.ts modules.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
2026-03-28 20:39:20 -03:00
Randi dd9de2efa9 fix: harden combo fallback and health checks (#704) 2026-03-28 20:39:16 -03:00
Randi f6b0811f78 [codex] fix provider limits ui (#718)
* fix provider limits ui

* restore remaining quota progress bars

* address provider limits review feedback
2026-03-28 20:39:06 -03:00
Randi eba9d854a9 fix model auto-sync startup and auth (#719) 2026-03-28 20:39:02 -03:00
Diego Rodrigues de Sa e Souza 437cf9bab0 chore(release): v3.1.10 — OmniRoute v3.1.9 remaining bug fixes sprint (#720)
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-03-28 19:54:45 -03:00
Diego Rodrigues de Sa e Souza 9ffad1005e Merge pull request #713 from diegosouzapw/release/v3.1.9
Build Electron Desktop App / Validate version (push) Failing after 38s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.1.9 — schema coercion, tool sanitization, bug fixes
2026-03-28 17:37:08 -03:00
diegosouzapw 65edddd62e refactor(open-sse): remove unused imports from translator/index.ts
remove unused imports coerceToolSchemas and sanitizeToolDescriptions from translator/index.ts to satisfy lint and prevent unused import issues
2026-03-28 17:26:55 -03:00
diegosouzapw a7cdcd8b3a chore(release): v3.1.9 — schema coercion, tool sanitization, clearAllModels i18n, bug fixes #605 #709 #710 #711 2026-03-28 16:35:20 -03:00
diegosouzapw 3d6b85ed20 fix: update Windsurf test to match merged config notes 2026-03-28 16:31:46 -03:00
diegosouzapw 7abea2020c Merge feature-tests: schema coercion, tool sanitization, Codex auth export, enhanced test suite 2026-03-28 16:27:32 -03:00
diegosouzapw e16c34f0e3 Merge feat/clear-all-models-button: clearAllModels i18n translations for 30 languages 2026-03-28 16:19:46 -03:00
diegosouzapw 4bfda6a145 Merge fix/issue-605: strip proxy_ prefix in non-streaming Claude responses (#605, #592) 2026-03-28 16:17:06 -03:00
diegosouzapw 98470e8551 Merge fix/issue-711: provider max_tokens cap + upstream sync tasks (#711) 2026-03-28 16:08:12 -03:00
diegosouzapw df558ab8d6 Merge fix/issue-710: A2A TaskManager globalThis singleton + E2E auth (#710) 2026-03-28 16:07:34 -03:00
diegosouzapw c07372b58c fix: ensure output directory exists for system-info (#709) 2026-03-28 15:54:15 -03:00
diegosouzapw 00f59b95ae fix: protocol clients e2e dev mode singleton and auth (#710) 2026-03-28 15:52:29 -03:00
diegosouzapw 8915a7c2cd fix: add provider-specific max_tokens cap (#711) 2026-03-28 15:41:59 -03:00
diegosouzapw 8595964ab8 feat/fix: implement upstream sync tasks 1-7 2026-03-28 14:48:57 -03:00
diegosouzapw 922dae8546 feat: add Codex auth.json export and apply-local buttons for CLI integration
- Add codexAuthFile.ts utility: builds Codex auth.json payload from OAuth connection
  (id_token, access_token, refresh_token, account_id) with auto-refresh if expired
- Add POST /api/providers/[id]/codex-auth/export: downloads auth.json file
- Add POST /api/providers/[id]/codex-auth/apply-local: writes auth.json to local CLI path
- Add 'Apply auth' and 'Export auth' buttons to ConnectionRow (Codex provider only)
- Add i18n keys for en and pt-BR
2026-03-28 13:28:06 -03:00
diegosouzapw 69b3e23400 test(tests): introduce feature-tests suite and update coverage tooling
- add unit tests for API auth, display/error utilities, login bootstrap,
  model combo mappings, provider validation branches, and usage analytics
- add COVERAGE_PLAN.md and extend CONTRIBUTING.md with coverage notes and
  workflow guidance
- update package.json to adjust test:coverage thresholds and add coverage:report;
  include c8 as a devDependency
- introduce test scaffolding and ensure compatibility with existing test runners
- align tests with open-sse changes and improve overall test coverage planning
2026-03-28 12:58:31 -03:00
diegosouzapw 55325773dc feat(open-sse): add schema coercion and tool sanitization
- introduce open-sse/translator/helpers/schemaCoercion.ts to coerce
  numeric JSON Schema fields encoded as strings
- wire coerceToolSchemas and sanitizeToolDescriptions into translator
  pipeline; ensure tool descriptions are sanitized
- inject empty reasoning content for tool calls when target is OpenAI
  format
- update qwen base URL to DashScope-compatible endpoint
- extend antigravity static catalog with Gemini 3.1 pro preview models and
  update Gemini model specs with preview aliases
- implement call log max cap caching with TTL; expose invalidateCallLogsMaxCache
  and invalidate on settings PATCH
- add tests: call-log-cap.test.mjs and tool-request-sanitization.test.mjs;
  extend tests for Windsurf integration and gemini previews
- update CLI runtime and tools to include Windsurf as a guide-only tool
- add maxCallLogs to validation schemas (settings and updateSettings)
- add Czech README (README.cs.md) to repository
2026-03-28 12:33:13 -03:00
Diego Rodrigues de Sa e Souza cfb390936a Merge pull request #697 from diegosouzapw/fix/issue-667-opencode-zen-models
fix: add opencode-zen to PROVIDER_MODELS_CONFIG (#667)
2026-03-28 01:55:06 -03:00
diegosouzapw c5f344f333 fix: add opencode-zen to PROVIDER_MODELS_CONFIG (#667)
The 'Import from /models' button failed because opencode-zen was not
registered in PROVIDER_MODELS_CONFIG. The provider's API at
https://opencode.ai/zen/v1/models returns standard OpenAI-compatible
format and is now properly configured for model import.
2026-03-28 01:54:39 -03:00
diegosouzapw ba4b496306 Merge PR #666: Add Claude prompt cache logging and exclude cache reads
Includes fixes applied during review:
- Removed duplicate imports in chatCore.ts
- Fixed stray translatedBody argument (stream boolean bug)
- Fixed truncated test file
- Fixed usageExtractor cached_tokens fallback

Closes #688, Closes #640
2026-03-28 01:53:25 -03:00
diegosouzapw c48554589c fix: repair test failures from PR #666 changes
- Fix usageExtractor cached_tokens fallback for Responses API (use cache_read_input_tokens when input_tokens_details is absent)
- Fix truncated claude-native-passthrough-tools.test.mjs that caused parse error
2026-03-28 01:50:04 -03:00
Diego Rodrigues de Sa e Souza da0851e21d Merge pull request #690 from alper-han/feat/i18n-tr
Reviewed and approved via consolidated analysis. Turkish locale (31st language) follows existing i18n patterns perfectly. Registered in config.ts, generate-multilang.mjs, and full tr.json translation file.
2026-03-28 01:46:04 -03:00
Diego Rodrigues de Sa e Souza d2d05abac0 Merge pull request #693 from christopher-s/main
Reviewed and approved via consolidated analysis. GLM-5.1 addition and pricing corrections match official Z.AI pricing page. All 5 files follow existing patterns.
2026-03-28 01:45:52 -03:00
Diego Rodrigues de Sa e Souza de3e0423cc Merge pull request #696 from benjaminkitt/fix/input-stream-invalid-boolean
Reviewed and approved via consolidated analysis. Fix is surgical (1 line removed) with 122 lines of regression tests covering stream=true, stream=false and guard scenarios. Resolves #677.
2026-03-28 01:45:39 -03:00
Benjamin Kitt 8d742d7938 test: add regression tests for stream boolean in claude passthrough
Three tests covering the fixed bug where translateRequest received an
object instead of a boolean for the stream parameter:
- stream=true round-trip produces boolean true
- stream=false round-trip produces boolean false
- guard test documenting that passing an object as stream breaks typing

Co-Authored-By: Craft Agent <agents-noreply@craft.do>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 22:38:34 -05:00
Benjamin Kitt 682fd550fa fix(core): remove extra arg in claude passthrough translateRequest call
The second translateRequest call in the claude->openai->claude passthrough
path had an extra `translatedBody` argument before `stream`, shifting all
parameters by one. This caused the `stream` field in the upstream request
to be set to an object instead of a boolean, producing:
  "stream: Input should be a valid boolean"

Co-Authored-By: Craft Agent <agents-noreply@craft.do>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 22:30:02 -05:00
Chris Staley abcf836a0c feat: add GLM-5.1 to GLM Coding provider, update GLM-5 pricing
- Add glm-5.1 model to GLM Coding provider with fitness scores
- Update glm-5 pricing to match Z.AI API page ($1/$3.2/$0.2)
- Set glm-5.1 pricing to $1.2/$5/$0.3 per Z.AI
- Remove glm-4-32b (deprecated, returns empty from upstream)
- Rename Z.AI provider display name from "Z.AI (GLM-5)" to "Z.AI"
- Update zai pricing section to match glm pricing
2026-03-27 16:23:44 -06:00
diegosouzapw b123fb2cc7 chore(release): bump version to v3.1.8 and global i18n sync
Build Electron Desktop App / Validate version (push) Failing after 36s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
2026-03-27 18:08:16 -03:00
Diego Rodrigues de Sa e Souza 0da3621a68 Merge pull request #692 from diegosouzapw/fix/recent-bugs
fix: resolve issues 681, 684, 685
2026-03-27 18:04:03 -03:00
alper-han 8ed452d9ea feat: add Turkish translations 2026-03-27 22:28:21 +03:00
diegosouzapw f380d44697 fix(core): hidden models flag, antigravity streaming, and i18n translation sync (#681, #684, #685) 2026-03-27 16:17:28 -03:00
Chris 86d377a2f0 fix: remove id/type from tool_calls delta chunks in Responses API streaming (#683)
In OpenAI Chat Completions streaming format, the tool call id and type
should only appear on the first chunk (tool declaration). Subsequent
argument delta chunks should only include index and function.arguments.

Including id on every delta chunk caused openai-to-claude.ts to emit
a new content_block_start for each chunk, breaking Claude Code ACP
sessions with malformed Claude-format streams.

Fixes #682

Co-authored-by: Chris Staley <christopher.staley@protonmail.com>
2026-03-27 15:25:16 -03:00
diegosouzapw 508a6d99f5 chore(release): bump version to v3.1.7 and fix SSE parsing bug
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
2026-03-27 15:17:13 -03:00
Paijo 63e42047e3 fix: hasValuableContent explicit boolean returns for SSE streaming (#676)
The hasValuableContent() function in streamHelpers.ts returned undefined
instead of explicit false when checking empty delta chunks. This caused
JavaScript type coercion issues where undefined !== '' evaluated to true,
passing empty chunks through to clients.

Fix: Replace implicit returns with explicit boolean returns using
typeof checks and length comparisons for all content fields (content,
reasoning_content, tool_calls, text, thinking, partial_json).

Test: Added unit tests covering OpenAI, Claude, and Gemini format edge cases.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
2026-03-27 15:12:51 -03:00
diegosouzapw 13829de0d9 release: v3.1.6 — Claude tool name fix + Clear All Models alias cleanup
Build Electron Desktop App / Validate version (push) Failing after 35s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Changes:
- fix: restore native Claude tool names in passthrough responses (PR #663 by @coobabm)
- fix: Clear All Models button now also removes aliases (PR #664 by @rdself)
- fix: completed truncated test from PR #663, added Claude-to-Claude passthrough test
- docs: update CHANGELOG and OpenAPI spec
2026-03-27 06:23:52 -03:00
Diego Rodrigues de Sa e Souza ad7f570be5 Merge pull request #664 from rdself/fix/clear-all-models-button
fix: Clear All Models button now also removes aliases
2026-03-27 06:14:16 -03:00
Diego Rodrigues de Sa e Souza 9ba4f966db Merge pull request #663 from coobabm/codex/claude-native-tool-fix-push
Fix Claude native tool names for Claude Code
2026-03-27 06:14:12 -03:00
cai kerui ae8d2ac2e1 Merge branch 'main' into codex/claude-cache-log-accounting 2026-03-27 17:25:38 +09:00
cai kerui 93beb068a3 Add Claude prompt cache logging and exclude cache reads 2026-03-27 15:14:54 +09:00
cai kerui e88d260acd Merge branch 'main' into codex/claude-native-tool-fix-push 2026-03-27 14:37:02 +09:00
R.D. 8121238872 fix: Clear All Models button now also removes associated aliases
The Clear All Models button was only deleting custom models from the
database but leaving their aliases intact, so the UI didn't reflect
the deletion. Now it also deletes all aliases belonging to the provider
and refreshes the alias state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 01:07:03 -04:00
cai kerui 161e377ec1 Fix Claude native tool names for Claude Code 2026-03-27 14:00:05 +09:00
diegosouzapw ad4bd800aa release: v3.1.5 — backoff auto-decay fix + Chinese i18n overhaul
Build Electron Desktop App / Validate version (push) Failing after 42s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Changes:
- fix: auto-decay backoffLevel when rate limit window expires (PR #657 by @brendandebeasi)
- i18n: comprehensive Chinese translation rewrite (PR #658 by @only4copilot)
- docs: update CHANGELOG and OpenAPI spec
2026-03-27 01:27:01 -03:00
Diego Rodrigues de Sa e Souza 2fba6f65f4 Merge pull request #658 from only4copilot/main
Merged! Thank you for the comprehensive Chinese translation update.
2026-03-27 01:19:55 -03:00
Diego Rodrigues de Sa e Souza a754ab4f10 Merge pull request #657 from brendandebeasi/fix/backoff-level-auto-decay
Merged! Great catch on the backoff deadlock.
2026-03-27 01:19:53 -03:00
gmw 86cfc468bd feat: Improve the Chinese translation 2026-03-27 11:04:57 +08:00
Brendan DeBeasi 7df0c1607e fix: auto-decay backoffLevel when rate limit window has passed
High backoffLevel (up to 15) persisted permanently in the DB after a burst of 429s. The account health score dropped to zero (100 - 15*10 = -50), causing the account selector to never pick the account again. Only a successful request could reset backoffLevel via clearAccountError, but the account was never selected — creating a deadlock.

Now, during account selection, any non-terminal connection whose rateLimitedUntil has passed gets its backoffLevel reset to 0 and testStatus restored to active. The DB update is fire-and-forget to avoid blocking the hot path.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 20:00:32 -07:00
Diego Rodrigues de Sa e Souza 6acd36e374 Merge pull request #655 from oSoWoSo/dev
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Merged! Thanks @zen0bit for polishing the Czech translations 🇨🇿
2026-03-26 23:50:54 -03:00
zenobit af51eecbac i18n: Improve some strings 2026-03-27 03:33:53 +01:00
diegosouzapw 3a23dc8b04 release: v3.1.3 — community i18n contributions (#652, #651)
Build Electron Desktop App / Validate version (push) Failing after 43s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Changes:
- i18n: ~70 missing translation keys for en.json + 12 languages (PR #652 by @zen0bit)
- i18n: Czech documentation updates — CLI-TOOLS, API_REFERENCE, VM_DEPLOYMENT (PR #652)
- feat: translation validation scripts for CI/QA (PR #651 by @zen0bit)
- docs: update CHANGELOG and OpenAPI spec
2026-03-26 21:32:52 -03:00
Diego Rodrigues de Sa e Souza ba13e44720 Merge pull request #651 from oSoWoSo/main-scripts
Merged! 🎉 Thank you @zen0bit for the translation validation tooling.
2026-03-26 21:31:48 -03:00
Diego Rodrigues de Sa e Souza e80420f6db Merge pull request #652 from oSoWoSo/main-i18n-fixes
Merged! 🎉 Thank you @zen0bit for the comprehensive i18n contribution — ~70 missing keys + Czech docs updates.
2026-03-26 21:31:42 -03:00
zenobit 21ddcfc866 feat: make validate_translation.py support any language
- Add --lang / -l argument for target language
- Add TRANSLATION_LANG environment variable support
- Default to cs for backwards compatibility
- Validate language file exists before processing

Usage:
  python validate_translation.py -l de
  TRANSLATION_LANG=fr python validate_translation.py
  python validate_translation.py --lang cs quick

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 01:24:18 +01:00
zenobit 20f82cb22c Apply suggestions from code review
Co-authored-by: zenobit <zenobit@disroot.org>
2026-03-27 01:15:45 +01:00
zenobit 7ef75bab23 Apply suggestions from code review
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-27 01:13:13 +01:00
zenobit 7224e03590 Add check_translations and validate_translations scripts 2026-03-27 01:05:40 +01:00
zenobit cf4f2991a5 i18n: add missing translation keys, Czech docs, and validation scripts
- Added ~70 common keys and auth keys to en.json
- Added cliTools.toolDescriptions for CLI tools
- Updated Czech documentation (CLI-TOOLS.md, API_REFERENCE.md, VM_DEPLOYMENT_GUIDE.md)
- Added check_translations.py and validate-translation.sh scripts
- Refactored auth keys from full sentences to structured keys (auth.waitingForAuthorization)
- Fixed grammatical error (své cloud -> svůj cloud)
- Removed duplicate toolDescriptions from common namespace
- Improved error handling with specific exceptions
- OAuth services use hardcoded English strings as fallback (when i18n unavailable)
- Fixed capitalization: Antigravity, iFlow

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit 9eb3c23494 Improve 2026-03-27 00:37:21 +01:00
zenobit c80d8898cc Fix toolDescriptions and remaining auth keys
Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit bc74dd88e0 Add missing translation keys: common, auth, templates, toolDescriptions
- Added ~70 common keys (id, authorization, proxy, etc.)
- Added auth keys for OAuth waiting messages
- Added templateNames, templateDescriptions, templatePayloads
- Added toolDescriptions for CLI tools
- Added TOOL_ALLOWLIST, TOOL_DENYLIST
- Added pricing error messages

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit da87c461ef Add missing home.updateNow, updating, updateAvailableDesc, updateStarted
Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit bf2e694f2c cs.json: Add missing translations for notes and providers
- cliTools.guides.continue.notes
- cliTools.guides.opencode.notes (2 entries)
- cliTools.guides.kiro.notes
- providers.autoSync (7 new keys)

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit e5150487c4 Add missing notes to cliTools.guides for continue, opencode, kiro
- continue: 'Continue uses JSON config file.'
- opencode: 'OpenCode requires API key configuration.', 'Set the base URL to your OmniRoute endpoint.'
- kiro: 'Kiro requires Amazon account.'

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
diegosouzapw 9ff6353b88 release: v3.1.2 — fix critical tool calling regression (#618)
Build Electron Desktop App / Validate version (push) Failing after 28s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Changes:
- fix: disable proxy_ tool prefix for Claude passthrough (Bash → proxy_Bash)
- docs: document Kiro account ban as upstream AWS issue (#649)
- docs: update CHANGELOG and OpenAPI spec

Fixes #618, closes #649, closes #615
2026-03-26 19:49:45 -03:00
diegosouzapw 926fd8abf4 fix: disable proxy_ tool prefix for all Claude-target passthrough (#618)
The openai-to-claude translator was prefixing tool names with 'proxy_'
(e.g. Bash → proxy_Bash) even when routing Claude-format requests to
native Claude/Anthropic providers. Claude rejects unknown tool names,
causing 'No such tool available: proxy_Bash' errors.

Root cause: the _disableToolPrefix condition only disabled the prefix
for non-Claude providers, but it should be disabled for ALL providers
in the Claude passthrough path since tools are already in Claude format.

Fixes #618
2026-03-26 19:44:44 -03:00
diegosouzapw 211a7a4cfe release: v3.1.1 — Ollama Cloud fix, Gemini 3.1, vision metadata, token retry
Build Electron Desktop App / Validate version (push) Failing after 27s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Changes:
- fix: Ollama Cloud 401 — wrong base URL (api.ollama.com → ollama.com) (#643)
- fix: Add Gemini 3.1 Pro/Flash to Antigravity provider (#645)
- feat: Vision capability metadata in /v1/models (PR #646)
- feat: Exponential backoff retry for expired OAuth tokens (PR #647)

Closes #643, closes #645
2026-03-26 15:56:44 -03:00
diegosouzapw c1835cd9cc fix: correct Ollama Cloud URL and add Gemini 3.1 to Antigravity (#643, #645)
- Fix Ollama Cloud base URL from api.ollama.com to ollama.com/v1/chat/completions
- Fix Ollama Cloud models URL to ollama.com/api/tags
- Add gemini-3.1-pro-preview and gemini-3.1-flash-lite-preview to Antigravity provider

Closes #643, closes #645
2026-03-26 15:53:31 -03:00
Diego Rodrigues de Sa e Souza 5700044393 Merge pull request #646 from brendandebeasi/feat/vision-capability-metadata
Thanks @brendandebeasi for another great contribution! 🎉 Vision capability metadata fixes real client compat issues. Merged for v3.1.1.
2026-03-26 15:49:54 -03:00
Diego Rodrigues de Sa e Souza 36fbd3d018 Merge pull request #647 from brendandebeasi/fix/expired-token-retry-healthcheck
Thanks @brendandebeasi for this excellent contribution! 🎉 The bounded retry with exponential backoff is exactly the right approach for expired connections. Merged and will be included in v3.1.1.
2026-03-26 15:49:52 -03:00
Diego Rodrigues de Sa e Souza d1178390a9 Merge pull request #648 from diegosouzapw/release/v3.0.10
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.1.0 — bug fixes, new features, i18n updates
2026-03-26 15:20:56 -03:00
diegosouzapw 8182825e92 chore(release): v3.1.0 — bug fixes, new features, i18n updates
Bug Fixes:
- #642: Locale conflict (in.json → hi.json for Hindi)
- #637: Codex empty tool names causing 400 errors
- #638: Streaming newline artifacts from thinking models
- #627: Claude reasoning effort parameter conversion
- #631: Qwen proactive token refresh (5-min buffer)

Features:
- #641: GitHub issue templates (bug, feature, config/proxy)
- #634: Clear All Models button with i18n (29 languages)

Docs:
- Updated README.md and 30 i18n translations with new features
- CHANGELOG.md finalized for v3.1.0

Tests: 936/936 pass (+10 since v3.0.9)
2026-03-26 15:18:06 -03:00
Brendan DeBeasi 2392006246 fix: retry expired connections in token health check instead of permanently skipping
Connections marked as 'expired' were permanently skipped by the health check scheduler (line 176: if testStatus === "expired" return). A single transient refresh failure could permanently disable auto-refresh, requiring manual re-authentication.

Replace the hard skip with a bounded retry mechanism: up to 3 attempts with exponential backoff (5min, 10min, 20min). On success, the connection is fully restored to active. On exhaustion, it remains expired (same as before). The existing circuit breaker (5 failures → 30min pause) provides additional protection.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:11:49 -07:00
Brendan DeBeasi a6e78cd5dc feat: add vision capability metadata to /v1/models response
OpenAI-compatible clients (OpenCode, etc.) check capabilities/input_modalities fields on the /v1/models response to determine if a model supports image input. Omniroute was not emitting these fields, causing clients to assume text-only for all models routed through the proxy.

Add keyword-based vision detection (matching the existing playground heuristic) that annotates model entries with capabilities:{vision:true}, input_modalities:["text","image"], and output_modalities:["text"] for known multimodal models (GPT-4o/4-turbo, Claude 3+, Gemini, Pixtral, Qwen-VL, etc.).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:00:29 -07:00
diegosouzapw 8752790352 fix: rename Hindi locale in→hi, global tool name filter, collapse \n artifacts (#642, #637, #638)
- Rename in.json → hi.json: 'in' is Indonesian (ISO 639-1), Hindi is 'hi'.
  Fixes Weblate locale conflict where id.json and in.json both claimed Indonesian.
- Move empty tool name filter before Codex passthrough: nativeCodexPassthrough
  skipped all input sanitization, causing 400 'empty tool name' from upstream.
- Collapse 3+ consecutive newlines to \n\n in response sanitizer: thinking
  models accumulate excessive line breaks between tool call blocks.
2026-03-26 09:22:10 -03:00
diegosouzapw 3976c79e12 fix: convert reasoning_effort to Claude thinking format & proactive token refresh (#627, #631)
- OpenAI-to-Claude translator now maps reasoning_effort (low/medium/high/max)
  to Claude's thinking.budget_tokens. Fixes clients like OpenCode sending
  reasoning_effort via @ai-sdk/openai-compatible losing thinking configuration.
- Ensures max_tokens > budget_tokens for all thinking configs.
- Token health check now proactively refreshes tokens within 5 min of expiry,
  regardless of the configured health check interval — addresses Qwen OAuth
  token refresh failures between scheduled checks.
2026-03-26 08:59:21 -03:00
Diego Rodrigues de Sa e Souza 5c1cf7f4ac Merge pull request #634 from rdself/feat/clear-all-models-button
feat: add Clear All Models button on provider detail page
2026-03-26 08:45:58 -03:00
diegosouzapw 7e90b8b7be i18n: add clearAllModels translations for all 30 languages 2026-03-26 08:43:52 -03:00
Diego Rodrigues de Sa e Souza 912321a030 Merge pull request #641 from ardaaltinors/feat/issue-templates
Add issue templates for bug reports and feature requests
2026-03-26 08:42:21 -03:00
ardaaltinors ab0a905499 feat: add GitHub issue templates for bug reports and feature requests
Adds structured YAML-based issue templates to improve issue quality.
Bug reports require version, install method, OS, repro steps, and
expected/actual behavior. Feature requests require use case and
proposed solution. Blank issues are still allowed for edge cases.
2026-03-26 13:54:01 +03:00
Diego Rodrigues de Sa e Souza 3c6b3c02df Merge pull request #636 from diegosouzapw/release/v3.0.9
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.0.9
2026-03-26 00:28:34 -03:00
diegosouzapw bcb2e91d97 chore(release): v3.0.9 — fix NaN tokens in sanitizeUsage, yaml security update (#617) 2026-03-26 00:26:22 -03:00
Diego Rodrigues de Sa e Souza 766ef94605 Merge pull request #635 from diegosouzapw/fix/sanitize-usage-crossmap-security
fix: sanitizeUsage cross-maps input_tokens→prompt_tokens; update yaml vulnerability (#617)
2026-03-26 00:25:21 -03:00
diegosouzapw e3f016e262 fix: sanitizeUsage cross-maps input_tokens→prompt_tokens; update yaml vulnerability (#617) 2026-03-25 23:53:29 -03:00
Diego Rodrigues de Sa e Souza 65833f1ae0 Merge pull request #633 from diegosouzapw/release/v3.0.8
Build Electron Desktop App / Validate version (push) Failing after 42s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.0.8 — fix translation failures for OpenAI-format providers (#632)
2026-03-25 23:30:48 -03:00
diegosouzapw 2602cd9ab2 chore(release): v3.0.8 — fix translation failures for OpenAI-format providers (#632) 2026-03-25 23:30:35 -03:00
R.D. 8333f3d9de feat: add "Clear All Models" button on provider detail page
Adds a button next to the Auto-Sync toggle to clear all custom models
for a provider. Extends DELETE /api/provider-models to support ?all=true
parameter for bulk deletion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 21:11:17 -04:00
diegosouzapw dee1d9ba74 fix: translation failures for OpenAI-format providers in Claude CLI (#632)
- Handle reasoning_details[] array (StepFun/OpenRouter format) in sanitizer and translator
- Handle 'reasoning' field alias → reasoning_content in streaming and non-streaming paths
- Cross-map input_tokens/output_tokens ↔ prompt_tokens/completion_tokens in filterUsageForFormat
- Fix extractUsage to accept input_tokens/output_tokens as alternative field names
- All 936 tests pass
2026-03-25 22:01:29 -03:00
Diego Rodrigues de Sa e Souza ed2e0c5080 Merge pull request #630 from diegosouzapw/dependabot/npm_and_yarn/multi-bf05dc1ecf
deps: bump picomatch
2026-03-25 21:11:21 -03:00
dependabot[bot] 7db810d7d0 deps: bump picomatch
Bumps  and [picomatch](https://github.com/micromatch/picomatch). These dependencies needed to be updated together.

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 22:51:05 +00:00
Diego Rodrigues de Sa e Souza 8dae4e5038 Merge pull request #629 from diegosouzapw/release/v3.0.7
Build Electron Desktop App / Validate version (push) Failing after 23s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.0.7 — Antigravity token fix, Playground selector, CLI models
2026-03-25 19:30:06 -03:00
diegosouzapw b9b28edefe chore(release): v3.0.7 — Antigravity token fix, Playground selector, CLI models
Bug Fixes:
- Antigravity token refresh clientSecret (#588)
- OpenCode Zen modelsUrl (#612)
- Streaming artifacts newline collapse (#626)
- Proxy fallback and test credential resolution

Features:
- Playground persistent Account/Key selector
- CLI Tools dynamic model listing
- Antigravity model list update + passthroughModels (#628)
2026-03-25 19:27:40 -03:00
diegosouzapw 58120f435f Merge feat/issue-628: Update Antigravity model list + passthroughModels (#628) 2026-03-25 19:24:16 -03:00
diegosouzapw 027b8e52da Merge fix/issue-588-612: Antigravity clientSecret + OpenCode Zen modelsUrl (#588, #612) 2026-03-25 19:24:07 -03:00
diegosouzapw aad510a9d5 feat: update Antigravity model list and enable passthrough (#628)
- Add Claude Sonnet 4.5, Claude Sonnet 4, GPT 5, GPT 5 Mini
- Enable passthroughModels: true so users can access any model
  Antigravity supports without waiting for registry updates
2026-03-25 19:18:00 -03:00
diegosouzapw 9852a805a1 fix: Antigravity token refresh clientSecret and OpenCode Zen modelsUrl (#588, #612)
- Set clientSecretDefault for Antigravity provider (was empty, causing
  'client_secret is missing' on token refresh for npm users)
- Add modelsUrl to opencode-zen registry for 'Import from /models'
2026-03-25 19:13:29 -03:00
diegosouzapw b2cabf0122 feat(playground): add persistent Account/Key selector
Rewrote the account selector with a simpler, reliable approach:
- Fetch ALL connections once at startup (not per-provider)
- Filter by selectedProvider using ALIAS_TO_ID mapping
- Account/Key dropdown always visible when provider selected
- Shows 'Auto (N accounts)' default or individual account names
- Works for both OAuth accounts and API key providers
2026-03-25 19:00:13 -03:00
diegosouzapw 521ce15f86 fix(playground): resolve provider alias-to-ID for account selector
Import ALIAS_TO_ID mapping and resolve provider aliases (cx→codex,
kr→kiro, etc.) in loadConnections before filtering connections from
the API. The /v1/models endpoint returns alias-prefixed model IDs
but /api/providers/client returns provider IDs.
2026-03-25 18:54:49 -03:00
diegosouzapw fb97c11140 feat(dashboard): fix Playground account selector & CLI Tools dynamic model listing
Playground:
- loadConnections() was parsing wrong API response shape (expected
  providers[].connections[] but API returns flat connections[])
- Account selector now shows for any provider with ≥1 connection
- Uses conn.email as name fallback for OAuth providers

CLI Tools:
- getAllAvailableModels() now also fetches from /v1/models API
- Dynamic models supplement static PROVIDER_MODELS definitions
- Fixes providers like Kiro, OpenCode Zen showing 0 models
2026-03-25 18:17:48 -03:00
diegosouzapw 1c5c62e311 fix(streaming): collapse excessive newlines after thinking tag removal (#626)
After stripping <antThinking>/<thinking> tags from streaming responses, the
surrounding newlines were left as artifacts (e.g. \n\n\n\n). Now collapses 3+
consecutive newlines to double-newline after any tag removal.

Also fixes PR #625 merge (Provider Limits light mode background).
2026-03-25 18:10:19 -03:00
diegosouzapw 77148f7f97 Merge pull request #625 from rdself/fix/provider-limits-light-mode-bg
fix: Provider Limits table background in light mode
2026-03-25 18:05:22 -03:00
diegosouzapw a329d2f2bc fix(proxy): test endpoint resolves real credentials from DB via proxyId
The proxy test button in Settings was always failing with 'Socks5 Authentication
failed' because the frontend sent redacted credentials (***) from listProxies().
The backend received '***' as the password and tried to authenticate with it.

Fix: Frontend now sends proxyId in the test request body. The test endpoint
looks up the proxy from the DB with includeSecrets: true and uses the real
stored credentials for the SOCKS5 handshake.

Also: removed username/password from the frontend test payload since they
are always redacted and useless for testing.
2026-03-25 17:54:19 -03:00
diegosouzapw 39e9e4446b fix(usage): proxy fallback — retry without proxy when SOCKS5 relay fails
Root cause: SOCKS5 proxies accept TCP connections (pass health check) but
can't relay HTTPS traffic. getCodexUsage() catches fetch errors internally
and returns {message: 'Failed to fetch...'} instead of throwing, so the
previous catch-based fallback never triggered.

Fix: After the initial proxied fetch, check the returned usage object for
network error indicators. If a proxy was active and the result contains
'fetch failed' / 'ECONNREFUSED' / etc., retry the entire operation
(credential refresh + usage fetch) without proxy context.

This is safe because usage fetching is read-only — showing limits data
without proxy is better than showing nothing.
2026-03-25 17:20:25 -03:00
R.D. b32de54944 fix: use bg-surface for Provider Limits table to match Card components in light mode
bg-bg-subtle (#f0f0f5) appears gray against the page background in
light mode. Changed to bg-surface (#ffffff) for consistency with other
Card-based UI sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 12:46:28 -04:00
Diego Rodrigues de Sa e Souza 071b874e1b Merge pull request #624 from diegosouzapw/release/v3.0.6
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Release v3.0.6 — Proxy Context, Playground Selector, CI Fix
2026-03-25 13:11:18 -03:00
diegosouzapw 9ba65d3323 fix(release): v3.0.6 — proxy context, playground selector, CI fix
- Fix: Limits usage fetch wraps BOTH token refresh and usage call inside proxy context (fixes SOCKS5 Codex accounts)
- Fix: CI integration test v1/models gracefully handles empty models list
- Fix: Settings proxy test button results now render with priority over health data
- Feat: Playground account selector dropdown for testing specific connections
- Merge: PR #623 LongCat API base URL path correction
2026-03-25 13:08:44 -03:00
Diego Rodrigues de Sa e Souza 890a851bbf Merge pull request #623 from razllivan/fix/longcat-base-url
fix: Correct LongCat API base URL path
2026-03-25 12:59:36 -03:00
Diego Rodrigues de Sa e Souza 5f6ca23da4 Merge pull request #620 from diegosouzapw/release/v3.0.5
Build Electron Desktop App / Validate version (push) Failing after 40s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.0.5 — Tags Grouping UI and Triage
2026-03-25 12:14:20 -03:00
Ivan 58df1c06ee fix: correct LongCat API base URL path 2026-03-25 18:14:19 +03:00
diegosouzapw 95f8599dc2 chore(release): v3.0.5 2026-03-25 12:11:46 -03:00
diegosouzapw 8a11242d7f feat(ui): group limits dashboard connections by tag field to improve configuration visibility 2026-03-25 12:08:05 -03:00
Diego Rodrigues de Sa e Souza 948513ef5f Merge pull request #619 from diegosouzapw/release/v3.0.4
Build Electron Desktop App / Validate version (push) Failing after 27s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.0.4 — TextDecoder corruption fix and dashboard regression fixes
2026-03-25 11:35:22 -03:00
diegosouzapw c497a35d21 chore(release): v3.0.4 — TextDecoder corruption fix and dashboard regression fixes 2026-03-25 11:33:21 -03:00
diegosouzapw e0a539bc64 fix(dashboard): post-release UI and proxy connection regressions 2026-03-25 11:31:05 -03:00
Diego Rodrigues de Sa e Souza 44b8395ead Merge pull request #614 from hijak/fix/combo-sanitize-textdecoder-corruption
fix(combo): sanitize TransformStream TextDecoder state corruption
2026-03-25 11:28:37 -03:00
Diego Rodrigues de Sa e Souza 1bc8878490 Merge pull request #616 from diegosouzapw/release/v3.0.3
Build Electron Desktop App / Validate version (push) Failing after 36s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.0.3 — Target Fixes & Feature Rollup
2026-03-25 10:54:25 -03:00
diegosouzapw ded2ac493d chore(release): v3.0.3 — Bump timeouts, auto-sync models, and CLI tool detection 2026-03-25 10:52:32 -03:00
Diego Rodrigues de Sa e Souza 57b3319ac0 Merge pull request #597 from rdself/feat/auto-sync-models
feat: add per-provider auto-sync for model lists
2026-03-25 10:47:30 -03:00
Diego Rodrigues de Sa e Souza eba7ba25b8 Merge pull request #598 from razllivan/fix/cli-tools-detection
fix(cli): cross-platform CLI tool detection for custom npm prefixes
2026-03-25 10:47:27 -03:00
Diego Rodrigues de Sa e Souza df774892c8 Merge pull request #599 from rdself/fix/hide-unconfigured-comfyui-sdwebui
fix: hide comfyui/sdwebui models when no provider configured
2026-03-25 10:47:24 -03:00
Diego Rodrigues de Sa e Souza f3b4ce6b67 Merge pull request #601 from oSoWoSo/cz
Improve Czech translation
2026-03-25 10:47:21 -03:00
Diego Rodrigues de Sa e Souza bb8545b3e1 Merge pull request #603 from ardaaltinors/fix/streaming-tool-calls-in-logs
fix(stream): include tool_calls in streaming response call logs
2026-03-25 10:47:18 -03:00
Jack Cowey 600149fc2b fix(combo): guard against empty text in sanitize transform
Aligns transform logic with flush — skip enqueuing when decoded text
is empty. Addresses review feedback on PR #614.
2026-03-25 13:28:34 +00:00
Jack Cowey f4de3c8748 fix(combo): sanitize TransformStream TextDecoder state corruption
The sanitize TransformStream (commit 5a8c644) shared the same TextDecoder
instance with the upstream transform stream. This corrupted UTF-8 state
when decoding SSE chunks, producing garbled output that broke clients
like openclaw that parse the stream.

- Use a separate TextDecoder for the sanitize stream
- Always decode→encode in sanitize (don't mix raw passthrough with decoded text)
- Add flush() handler to emit remaining buffered bytes
- Fix double-escaped regex (\\n → \n) for tag stripping
2026-03-25 13:23:04 +00:00
diegosouzapw ed146fcf07 fix: strip proxy_ prefix in non-streaming Claude responses & fix LongCat validation (#605, #592)
- fix(translator): pass toolNameMap to translateNonStreamingResponse so Claude
  OAuth proxy_ prefix is correctly stripped from tool_use block names in
  non-streaming responses (was only stripped in streaming path)
- fix(validation): add LongCat specialty validator that probes /chat/completions
  directly, bypassing the /v1/models endpoint that LongCat does not expose (#592)
2026-03-25 08:11:35 -03:00
ardaaltinors 35538e6f77 refactor(stream): add ToolCall type, replace any, simplify ternary 2026-03-25 10:57:09 +03:00
ardaaltinors ea924f3bbf fix(stream): correct tool_calls delta keying and normalize shapes 2026-03-25 10:18:41 +03:00
zenobit 7bc15a2fc9 Improve Czech translation 2026-03-25 08:16:57 +01:00
ardaaltinors 2bf7db92ee fix: include tool_calls in streaming response call logs 2026-03-25 10:06:20 +03:00
Ivan 95260f56ba fix: address PR review comments
- Fix test to verify >=30 bytes detection
- Add fs.existsSync checks for /usr paths
2026-03-25 07:22:40 +03:00
Ivan c5ace0376a test(cli): add unit tests for CLI tool detection
Add 10 tests covering:
- CLI_TOOL_IDS completeness
- Size threshold (files < 30B rejected, >= 30B detected)
- Healthcheck (--version runnable, exit 1 not runnable)
- Unknown tool handling
- requiresBinary: false tools
- resolveOpencodeConfigPath cross-platform
2026-03-25 07:01:18 +03:00
Ivan 7ee09388fa fix(cli): cross-platform CLI tool detection
- Add dynamic npm prefix detection via getNpmGlobalPrefix()
- Supports custom prefixes (e.g., pnpm .npm-global)
- Add npm prefix to EXPECTED_PARENT_PATHS
- Rewrite getKnownToolPaths() for cross-platform support
  - Windows: checks dynamic npm prefix, APPDATA\npm, NVM
  - Linux/macOS: checks node bin dir, npm prefix, ~/.local/bin, ~/.opencode/bin
- Remove isWindows() gate - known paths checked on all platforms
- Lower size threshold from 1024 to 30 bytes (Linux JS wrappers ~44B)
- Add PATHEXT to healthcheck env for .cmd/.bat resolution
- Cache npm prefix to avoid duplicate execFileSync calls
- Deduplicate paths when npmPrefix equals APPDATA\npm
2026-03-25 07:01:18 +03:00
R.D. a15b0ef060 fix: hide comfyui/sdwebui models from /v1/models when no provider configured
Video and music models had a special exemption for authType="none" providers
(comfyui, sdwebui), causing them to appear in the models list even without
any active provider connection. Now all model types consistently use
isProviderActive() filtering, matching the behavior of image models.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 23:57:51 -04:00
R.D. 57cfd9a315 fix: show provider name and dash protocol in model-sync logs
Provider field shows connection name (e.g. "BltCy API"),
Protocol (sourceFormat) shows "-" since model-sync is not
a chat/completion request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:49:32 -04:00
R.D. 5fb4149c32 fix: show dash instead of provider node ID in model-sync logs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:42:27 -04:00
R.D. 03d97ba617 fix: show readable provider name in model-sync logs
Use connection.name instead of the raw provider node ID
(e.g. "BltCy API" instead of "openai-compatible-chat-09fdb807-...")
in call logs and scheduler console output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:40:13 -04:00
R.D. 5205f5f4b4 fix: show auto-sync toggle for OpenAI/Anthropic compatible providers
The autoSyncToggle was defined after the isCompatible early return,
so it never rendered for compatible provider types. Move the toggle
definition before the isCompatible branch so it appears for all
provider types including third-party OpenAI-compatible ones.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:29:14 -04:00
R.D. 6eda0f4d00 feat: add per-provider auto-sync for model lists
- Add POST /api/providers/[id]/sync-models endpoint that fetches models
  from a provider's /models API and replaces the full custom models list,
  preserving per-model compatibility overrides
- Rewrite modelSyncScheduler to dynamically discover connections with
  autoSync enabled in providerSpecificData instead of a hardcoded list
- Add replaceCustomModels() to db/models.ts for full list replacement
  while preserving existing compat flags
- Log each model sync operation to call_logs for visibility in the
  Logs page
- Add Auto-Sync toggle button next to "Import from /models" in the
  provider detail page UI
- Add en/zh-CN i18n translations for auto-sync strings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:16:09 -04:00
226 changed files with 71013 additions and 3828 deletions
+39
View File
@@ -0,0 +1,39 @@
---
description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35)
---
# Deploy to Akamai VPS Workflow
Deploy OmniRoute to the Akamai VPS using `npm pack + scp` + PM2.
**Akamai VPS:** `69.164.221.35`
**Process manager:** PM2 (`omniroute`)
**Port:** `20128`
## Steps
### 1. Build + pack locally
// turbo
```bash
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
```
### 2. Copy to Akamai VPS and install
// turbo-all
```bash
scp omniroute-*.tgz root@69.164.221.35:/tmp/
```
```bash
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
```
### 3. Verify the deployment
```bash
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
```
+49
View File
@@ -0,0 +1,49 @@
---
description: Deploy the latest OmniRoute code to BOTH the Akamai VPS and the Local VPS
---
# Deploy to VPS (Both) Workflow
Deploy OmniRoute to the production VPSs using `npm pack + scp` + PM2.
**Akamai VPS:** `69.164.221.35`
**Local VPS:** `192.168.0.15`
**Process manager:** PM2 (`omniroute`)
**Port:** `20128`
**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js`
> [!IMPORTANT]
> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**.
## Steps
### 1. Build + pack locally
// turbo
```bash
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
```
### 2. Copy to both VPS and install
// turbo-all
```bash
scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/
```
```bash
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
```
```bash
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
```
### 3. Verify the deployment
```bash
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
```
+39
View File
@@ -0,0 +1,39 @@
---
description: Deploy the latest OmniRoute code to the Local VPS (192.168.0.15)
---
# Deploy to Local VPS Workflow
Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
**Local VPS:** `192.168.0.15`
**Process manager:** PM2 (`omniroute`)
**Port:** `20128`
## Steps
### 1. Build + pack locally
// turbo
```bash
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
```
### 2. Copy to Local VPS and install
// turbo-all
```bash
scp omniroute-*.tgz root@192.168.0.15:/tmp/
```
```bash
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
```
### 3. Verify the deployment
```bash
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
```
-102
View File
@@ -1,102 +0,0 @@
---
description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35) via npm
---
# Deploy to VPS Workflow
Deploy OmniRoute to the production VPS using `npm pack + scp` + PM2.
**VPS:** `69.164.221.35` (Akamai, Ubuntu 24.04, 1GB RAM + 2.5GB swap)
**Local VPS:** `192.168.0.15` (same setup)
**Process manager:** PM2 (`omniroute`)
**Port:** `20128`
**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js`
> [!IMPORTANT]
> PM2 runs from the global npm package at `/usr/lib/node_modules/omniroute`.
> The Next.js standalone build is at `app/server.js` inside that directory.
> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**.
> [!CAUTION]
> **NEVER** use `pm2 restart omniroute` after `npm install -g`. This drops env vars.
> Always use `pm2 delete omniroute && pm2 start <ecosystem.config.cjs> --update-env`.
> After `npm install -g`, always rebuild better-sqlite3: `cd .../app && npm rebuild better-sqlite3`
## Steps
### 1. Build + pack locally
Run the full build (includes hash-strip patch) and create the .tgz:
// turbo
```bash
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
```
### 2. Copy to both VPS and install
// turbo-all
```bash
scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/
```
```bash
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
```
```bash
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
```
### 3. Verify the deployment
```bash
ssh root@69.164.221.35 "pm2 list && cat \$(npm root -g)/omniroute/app/package.json | grep version | head -1 && curl -s -o /dev/null -w 'HTTP %{http_code}' http://localhost:20128/"
```
```bash
ssh root@192.168.0.15 "pm2 list && cat \$(npm root -g)/omniroute/app/package.json | grep version | head -1 && curl -s -X POST http://localhost:20128/api/auth/login -H 'Content-Type: application/json' -d '{\"password\":\"123456\"}'"
```
Expected: PM2 shows `online`, version matches, login returns `{"success":true}`.
## How it works
1. `npm run build:cli` builds Next.js standalone → `app/` and strips Turbopack hashed require() calls from chunks
2. `npm pack --ignore-scripts` packages without re-running the build
3. `scp` transfers the .tgz to each VPS (~286MB)
4. `npm install -g /tmp/omniroute-*.tgz --ignore-scripts` installs pre-built package
5. `npm rebuild better-sqlite3` recompiles native bindings for the VPS Node.js version
6. `pm2 delete` + `pm2 start ecosystem.config.cjs --update-env` restarts with env vars
7. `pm2 save` persists the process list for reboot survival
## Ecosystem Config
Both VPSs have `ecosystem.config.cjs` at `/root/.omniroute/ecosystem.config.cjs`.
This file defines env vars (PORT, DATA_DIR, INITIAL_PASSWORD, OAuth secrets, etc.)
that `pm2 restart` does NOT inject — only `pm2 start --update-env` does.
## PM2 Setup (one-time — if reconfiguring from scratch)
```bash
ssh root@<VPS> "
pm2 delete omniroute 2>/dev/null;
cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 &&
pm2 start /root/.omniroute/ecosystem.config.cjs --update-env &&
pm2 save && pm2 startup
"
```
> [!NOTE]
> Ensure `/root/.omniroute/ecosystem.config.cjs` exists with all required env vars.
> For fresh installs, copy from the existing VPS or create from the template in `.env`.
## Notes
- Env vars are in `/root/.omniroute/ecosystem.config.cjs` (NOT `.env` in app dir)
- PM2 is configured with `pm2 startup` to auto-restart on reboot
- Nginx proxies `omniroute.online``localhost:20128`
- The VPS has only 1GB RAM — builds happen locally, never on the VPS
- After `npm install -g`, `better-sqlite3` MUST be rebuilt in the `app/` subdir
+145
View File
@@ -0,0 +1,145 @@
name: Bug Report
description: Report a bug or unexpected behavior in OmniRoute
title: "[BUG] "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug. Please fill out the sections below so we can reproduce and fix the issue.
- type: input
id: version
attributes:
label: OmniRoute Version
description: "Run `omniroute --version` or check the left sidebar in the dashboard."
placeholder: "e.g. 3.0.9"
validations:
required: true
- type: dropdown
id: install-method
attributes:
label: Installation Method
options:
- npm (global)
- Docker / Docker Compose
- Electron desktop app
- Built from source
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating System
options:
- Windows
- macOS
- Linux
validations:
required: true
- type: input
id: os-version
attributes:
label: OS Version
placeholder: "e.g. Windows 11 23H2, macOS 15.3, Ubuntu 24.04"
validations:
required: false
- type: input
id: node-version
attributes:
label: Node.js Version
description: "Run `node --version`. Skip if using Docker."
placeholder: "e.g. 22.12.0"
validations:
required: false
- type: input
id: provider
attributes:
label: Provider(s) Involved
description: "Which AI provider(s) does this affect?"
placeholder: "e.g. Antigravity, OpenRouter, Ollama, Qwen"
validations:
required: false
- type: input
id: model
attributes:
label: Model(s) Involved
placeholder: "e.g. claude-sonnet-4-20250514, gpt-4o, gemini-2.5-pro"
validations:
required: false
- type: input
id: client-tool
attributes:
label: Client Tool
description: "Which tool are you using OmniRoute with?"
placeholder: "e.g. Claude Code, Cursor, Roo Code, OpenClaw, Gemini CLI, cURL"
validations:
required: false
- type: textarea
id: description
attributes:
label: Description
description: "A clear description of what the bug is."
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: "Step-by-step instructions to reproduce the behavior."
placeholder: |
1. Go to '...'
2. Click on '...'
3. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: "What did you expect to happen?"
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Behavior
description: "What actually happened?"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Error Logs / Output
description: "Paste any relevant error messages, logs, or terminal output. This will be automatically formatted as code."
render: shell
validations:
required: false
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: "If applicable, add screenshots to help explain the problem. Please also include the text of any error messages above — screenshots alone are not searchable."
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: "Any other context about the problem (e.g. proxy config, number of accounts, network setup)."
validations:
required: false
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Question / Help
url: https://github.com/diegosouzapw/OmniRoute/discussions
about: For questions or help with setup, please use GitHub Discussions instead of opening an issue.
@@ -0,0 +1,70 @@
name: Feature Request
description: Suggest a new feature or improvement for OmniRoute
title: "[Feature] "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a feature! Please describe the problem you're trying to solve and how you'd like it to work.
- type: textarea
id: problem
attributes:
label: Problem / Use Case
description: "What problem does this feature solve? Why do you need it?"
placeholder: "I'm trying to ... but currently ..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: "How would you like this to work?"
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: "Have you considered any workarounds or alternative approaches?"
validations:
required: false
- type: dropdown
id: area
attributes:
label: Area
description: "Which part of OmniRoute does this relate to?"
multiple: true
options:
- Dashboard / UI
- Proxy / Routing
- Provider Support
- CLI Tools Integration
- OAuth / Authentication
- Analytics / Usage Tracking
- Docker / Deployment
- Documentation
- Other
validations:
required: true
- type: input
id: provider
attributes:
label: Related Provider(s)
description: "If this relates to specific providers, list them."
placeholder: "e.g. Antigravity, OpenRouter, Ollama"
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: "Any other context, mockups, or references."
validations:
required: false
+9
View File
@@ -37,6 +37,13 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from release tag or input
id: version
run: |
@@ -59,6 +66,8 @@ jobs:
tags: |
${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
${{ env.IMAGE_NAME }}:latest
ghcr.io/diegosouzapw/omniroute:${{ steps.version.outputs.version }}
ghcr.io/diegosouzapw/omniroute:latest
cache-from: type=gha
cache-to: type=gha,mode=max
no-cache: false
+18
View File
@@ -105,3 +105,21 @@ jobs:
echo "✅ Published omniroute@$VERSION (tag: $TAG)"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish to GitHub Packages
run: |
VERSION="${{ steps.resolve.outputs.version }}"
TAG="${{ steps.resolve.outputs.tag }}"
echo "Configuring for GitHub Packages..."
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" > .npmrc
npm pkg set name="@diegosouzapw/omniroute"
if [ "$TAG" = "latest" ]; then
npm publish --registry=https://npm.pkg.github.com || echo "⚠️ Version ${VERSION} might already be published on GitHub."
else
npm publish --registry=https://npm.pkg.github.com --tag "$TAG" || echo "⚠️ Version ${VERSION} might already be published on GitHub."
fi
echo "✅ Action finished for GitHub Packages"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1
View File
@@ -112,6 +112,7 @@ app.log
# Backup directories
app.__qa_backup/
.app-build-backup-*/
# Production standalone build (created by scripts/prepublish.mjs)
# Conflicts with Next.js App Router detection in dev (root app/ shadows src/app/)
+313
View File
@@ -4,6 +4,319 @@
---
## [3.2.2] — 2026-03-29
### ✨ New Features
- **Four-Stage Request Log Pipeline (#705)** — Refactored log persistence to save comprehensive payloads at four distinct pipeline stages: Client Request, Translated Provider Request, Provider Response, and Translated Client Response. Introduced `streamPayloadCollector` for robust SSE stream truncation and payload serialization.
### 🐛 Bug Fixes
- **Mobile UI Fixes (#659)** — Prevented table components on the dashboard from breaking the layout on narrow viewports by adding proper horizontal scrolling and overflow containment to `DashboardLayout`.
- **Claude Prompt Cache Fixes (#708)** — Ensured `cache_control` blocks in Claude-to-Claude fallback loops are faithfully preserved and passed safely back to Anthropic models.
- **Gemini Tool Definitions (#725)** — Fixed schema translation errors when declaring simple `object` parameter types for Gemini function calling.
## [3.2.1] — 2026-03-29
### ✨ New Features
- **Global Fallback Provider (#689)** — When all combo models are exhausted (502/503), OmniRoute now attempts a configurable global fallback model before returning the error. Set `globalFallbackModel` in settings to enable.
### 🐛 Bug Fixes
- **Fix #721** — Fixed context pinning bypass during tool-call responses. Non-streaming tagging used wrong JSON path (`json.messages``json.choices[0].message`). Streaming injection now triggers on `finish_reason` chunks for tool-call-only streams. `injectModelTag()` now appends synthetic pin messages for non-string content.
- **Fix #709** — Confirmed already fixed (v3.1.9) — `system-info.mjs` creates directories recursively. Closed.
- **Fix #707** — Confirmed already fixed (v3.1.9) — empty tool name sanitization in `chatCore.ts`. Closed.
### 🧪 Tests
- Added 6 unit tests for context pinning with tool-call responses (null content, array content, roundtrip, re-injection)
## [3.2.0] — 2026-03-28
### ✨ New Features
- **Cache Management UI** — Added a dedicated semantic caching dashboard at \`/dashboard/cache\` with targeted API invalidation and 31-language i18n support (PR #701 by @oyi77)
- **GLM Quota Tracking** — Added real-time usage and session quota tracking for the GLM Coding (Z.AI) provider (PR #698 by @christopher-s)
- **Detailed Log Payloads** — Wired full four-stage pipeline payload capturing (original, translated, provider-response, streamed-deltas) directly into the UI (PR #705 by @rdself)
### 🐛 Bug Fixes
- **Fix #708** — Prevented token bleeding for Claude Code users routing through OmniRoute by correctly preserving native \`cache_control\` headers during Claude-to-Claude passthrough (PR #708 by @tombii)
- **Fix #719** — Setup internal auth boundaries for \`ModelSyncScheduler\` to prevent unauthenticated daemon failures on startup (PR #719 by @rdself)
- **Fix #718** — Rebuilt badge rendering in Provider Limits UI preventing bad quota boundaries overlap (PR #718 by @rdself)
- **Fix #704** — Fixed Combo Fallbacks breaking on HTTP 400 content-policy errors preventing model-rotation dead-routing (PR #704 by @rdself)
### 🔒 Security & Dependencies
- Bumped \`path-to-regexp\` to \`8.4.0\` resolving dependabot vulnerabilities (PR #715)
## [3.1.10] — 2026-03-28
### 🐛 Bug Fixes
- **Fix #706** — Fixed icon fallback rendering caused by Tailwind V4 `font-sans` override by applying `!important` to `.material-symbols-outlined`.
- **Fix #703** — Fixed GitHub Copilot broken streams by enabling `responses` to `openai` format translation for any custom models leveraging `apiFormat: "responses"`.
- **Fix #702** — Replaced flat-rate usage tracking with accurate DB pricing calculations for both streaming and non-streaming responses.
- **Fix #716** — Cleaned up Claude tool-call translation state, correctly parsing streaming arguments and preventing OpenAI `tool_calls` chunks from repeating the `id` field.
## [3.1.9] — 2026-03-28
### ✨ New Features
- **Schema Coercion** — Auto-coerce string-encoded numeric JSON Schema constraints (e.g. `"minimum": "1"`) to proper types, preventing 400 errors from Cursor, Cline, and other clients sending malformed tool schemas.
- **Tool Description Sanitization** — Ensure tool descriptions are always strings; converts `null`, `undefined`, or numeric descriptions to empty strings before sending to providers.
- **Clear All Models Button** — Added i18n translations for the "Clear All Models" provider action across all 30 languages.
- **Codex Auth Export** — Added Codex `auth.json` export and apply-local buttons for seamless CLI integration.
- **Windsurf BYOK Notes** — Added official limitation warnings to the Windsurf CLI tool card documenting BYOK constraints.
### 🐛 Bug Fixes
- **Fix #709** — `system-info.mjs` no longer crashes when the output directory doesn't exist (added `mkdirSync` with recursive flag).
- **Fix #710** — A2A `TaskManager` singleton now uses `globalThis` to prevent state leakage across Next.js API route recompilations in dev mode. E2E test suite updated to handle 401 gracefully.
- **Fix #711** — Added provider-specific `max_tokens` cap enforcement for upstream requests.
- **Fix #605 / #592** — Strip `proxy_` prefix from tool names in non-streaming Claude responses; fixed LongCat validation URL.
- **Call Logs Max Cap** — Upgraded `getMaxCallLogs()` with caching layer, env var support (`CALL_LOGS_MAX`), and DB settings integration.
### 🧪 Tests
- Test suite expanded from 964 → 1027 tests (63 new tests)
- Added `schema-coercion.test.mjs` — 9 tests for numeric field coercion and tool description sanitization
- Added `t40-opencode-cli-tools-integration.test.mjs` — OpenCode/Windsurf CLI integration tests
- Enhanced feature-tests branch with comprehensive coverage tooling
### 📁 New Files
| File | Purpose |
| -------------------------------------------------------- | ----------------------------------------------------------- |
| `open-sse/translator/helpers/schemaCoercion.ts` | Schema coercion and tool description sanitization utilities |
| `tests/unit/schema-coercion.test.mjs` | Unit tests for schema coercion |
| `tests/unit/t40-opencode-cli-tools-integration.test.mjs` | CLI tool integration tests |
| `COVERAGE_PLAN.md` | Test coverage planning document |
### 🐛 Bug Fixes
- **Claude Prompt Caching Passthrough** — Fixed cache_control markers being stripped in Claude passthrough mode (Claude → OmniRoute → Claude), which caused Claude Code users to deplete their Anthropic API quota 5-10x faster than direct connections. OmniRoute now preserves client's cache_control markers when sourceFormat and targetFormat are both Claude, ensuring prompt caching works correctly and dramatically reducing token consumption.
## [3.1.8] - 2026-03-27
### 🐛 Bug Fixes & Features
- **Platform Core:** Implemented global state handling for Hidden Models & Combos preventing them from cluttering the catalog or leaking into connected MCP agents (#681).
- **Stability:** Patched streaming crashes related to the native Antigravity provider integration failing due to unhandled undefined state arrays (#684).
- **Localization Sync:** Deployed a fully overhauled `i18n` synchronizer detecting missing nested JSON properties and retro-fitting 30 locales sequentially (#685).## [3.1.7] - 2026-03-27
### 🐛 Bug Fixes
- **Streaming Stability:** Fixed `hasValuableContent` returning `undefined` for empty chunks in SSE streams (#676).
- **Tool Calling:** Fixed an issue in `sseParser.ts` where non-streaming Claude responses with multiple tool calls dropped the `id` of subsequent tool calls due to incorrect index-based deduplication (#671).
---
## [3.1.6] — 2026-03-27
### 🐛 Bug Fixes
- **Claude Native Tool Name Restoration** — Tool names like `TodoWrite` are no longer prefixed with `proxy_` in Claude passthrough responses (both streaming and non-streaming). Includes unit test coverage (PR #663 by @coobabm)
- **Clear All Models Alias Cleanup** — "Clear All Models" button now also removes associated model aliases, preventing ghost models in the UI (PR #664 by @rdself)
---
## [3.1.5] — 2026-03-27
### 🐛 Bug Fixes
- **Backoff Auto-Decay** — Rate-limited accounts now auto-recover when their cooldown window expires, fixing a deadlock where high `backoffLevel` permanently deprioritized accounts (PR #657 by @brendandebeasi)
### 🌍 i18n
- **Chinese translation overhaul** — Comprehensive rewrite of `zh-CN.json` with improved accuracy (PR #658 by @only4copilot)
---
## [3.1.4] — 2026-03-27
### 🐛 Bug Fixes
- **Streaming Override Fix** — Explicit `stream: true` in request body now takes priority over `Accept: application/json` header. Clients sending both will correctly receive SSE streaming responses (#656)
### 🌍 i18n
- **Czech string improvements** — Refined terminology across `cs.json` (PR #655 by @zen0bit)
---
## [3.1.3] — 2026-03-26
### 🌍 i18n & Community
- **~70 missing translation keys** added to `en.json` and 12 languages (PR #652 by @zen0bit)
- **Czech documentation updated** — CLI-TOOLS, API_REFERENCE, VM_DEPLOYMENT guides (PR #652)
- **Translation validation scripts** — `check_translations.py` and `validate_translation.py` for CI/QA (PR #651 by @zen0bit)
---
## [3.1.2] — 2026-03-26
### 🐛 Bug Fixes
- **Critical: Tool Calling Regression** — Fixed `proxy_Bash` errors by disabling the `proxy_` tool name prefix in the Claude passthrough path. Tools like `Bash`, `Read`, `Write` were being renamed to `proxy_Bash`, `proxy_Read`, etc., causing Claude to reject them (#618)
- **Kiro Account Ban Documentation** — Documented as upstream AWS anti-fraud false positive, not an OmniRoute issue (#649)
### 🧪 Tests
- **936 tests, 0 failures**
---
## [3.1.1] — 2026-03-26
### ✨ New Features
- **Vision Capability Metadata**: Added `capabilities.vision`, `input_modalities`, and `output_modalities` to `/v1/models` entries for vision-capable models (PR #646)
- **Gemini 3.1 Models**: Added `gemini-3.1-pro-preview` and `gemini-3.1-flash-lite-preview` to the Antigravity provider (#645)
### 🐛 Bug Fixes
- **Ollama Cloud 401 Error**: Fixed incorrect API base URL — changed from `api.ollama.com` to official `ollama.com/v1/chat/completions` (#643)
- **Expired Token Retry**: Added bounded retry with exponential backoff (5→10→20 min) for expired OAuth connections instead of permanently skipping them (PR #647)
### 🧪 Tests
- **936 tests, 0 failures**
---
## [3.1.0] — 2026-03-26
### ✨ New Features
- **GitHub Issue Templates**: Added standardized bug report, feature request, and config/proxy issue templates (#641)
- **Clear All Models**: Added a "Clear All Models" button to the provider detail page with i18n support in 29 languages (#634)
### 🐛 Bug Fixes
- **Locale Conflict (`in.json`)**: Renamed the Hindi locale file from `in.json` (Indonesian ISO code) to `hi.json` to fix translation conflicts in Weblate (#642)
- **Codex Empty Tool Names**: Moved tool name sanitization before the native Codex passthrough, fixing 400 errors from upstream providers when tools had empty names (#637)
- **Streaming Newline Artifacts**: Added `collapseExcessiveNewlines` to the response sanitizer, collapsing runs of 3+ consecutive newlines from thinking models into a standard double newline (#638)
- **Claude Reasoning Effort**: Converted OpenAI `reasoning_effort` param to Claude's native `thinking` budget block across all request paths, including automatic `max_tokens` adjustment (#627)
- **Qwen Token Refresh**: Implemented proactive pre-expiry OAuth token refreshes (5-minute buffer) to prevent requests from failing when using short-lived tokens (#631)
### 🧪 Tests
- **936 tests, 0 failures** (+10 tests since 3.0.9)
---
## [3.0.9] — 2026-03-26
### 🐛 Bug Fixes
- **NaN tokens in Claude Code / client responses (#617):**
- `sanitizeUsage()` now cross-maps `input_tokens``prompt_tokens` and `output_tokens``completion_tokens` before the whitelist filter, fixing responses showing NaN/0 token counts when providers return Claude-style usage field names
### 🔒 Security
- Updated `yaml` package to fix stack overflow vulnerability (GHSA-48c2-rrv3-qjmp)
### 📋 Issue Triage
- Closed #613 (Codestral — resolved with Custom Provider workaround)
- Commented on #615 (OpenCode dual-endpoint — workaround provided, tracked as feature request)
- Commented on #618 (tool call visibility — requesting v3.0.9 test)
- Commented on #627 (effort level — already supported)
---
## [3.0.8] — 2026-03-25
### 🐛 Bug Fixes
- **Translation Failures for OpenAI-format Providers in Claude CLI (#632):**
- Handle `reasoning_details[]` array format from StepFun/OpenRouter — converts to `reasoning_content`
- Handle `reasoning` field alias from some providers → normalized to `reasoning_content`
- Cross-map usage field names: `input_tokens``prompt_tokens`, `output_tokens``completion_tokens` in `filterUsageForFormat`
- Fix `extractUsage` to accept both `input_tokens`/`output_tokens` and `prompt_tokens`/`completion_tokens` as valid usage fields
- Applied to both streaming (`sanitizeStreamingChunk`, `openai-to-claude.ts` translator) and non-streaming (`sanitizeMessage`) paths
---
## [3.0.7] — 2026-03-25
### 🐛 Bug Fixes
- **Antigravity Token Refresh:** Fixed `client_secret is missing` error for npm-installed users — the `clientSecretDefault` was empty in providerRegistry, causing Google to reject token refresh requests (#588)
- **OpenCode Zen Models:** Added `modelsUrl` to the OpenCode Zen registry entry so "Import from /models" works correctly (#612)
- **Streaming Artifacts:** Fixed excessive newlines left in responses after thinking-tag signature stripping (#626)
- **Proxy Fallback:** Added automatic retry without proxy when SOCKS5 relay fails
- **Proxy Test:** Test endpoint now resolves real credentials from DB via proxyId
### ✨ New Features
- **Playground Account/Key Selector:** Persistent, always-visible dropdown to select specific provider accounts/keys for testing — fetches all connections at startup and filters by selected provider
- **CLI Tools Dynamic Models:** Model selection now dynamically fetches from `/v1/models` API — providers like Kiro now show their full model catalog
- **Antigravity Model List:** Updated with Claude Sonnet 4.5, Claude Sonnet 4, GPT 5, GPT 5 Mini; enabled `passthroughModels` for dynamic model access (#628)
### 🔧 Maintenance
- Merged PR #625 — Provider Limits light mode background fix
---
## [3.0.6] — 2026-03-25
### 🐛 Bug Fixes
- **Limits/Proxy:** Fixed Codex limit fetching for accounts behind SOCKS5 proxies — token refresh now runs inside proxy context
- **CI:** Fixed integration test `v1/models` assertion failure in CI environments without provider connections
- **Settings:** Proxy test button now shows success/failure results immediately (previously hidden behind health data)
### ✨ New Features
- **Playground:** Added Account selector dropdown — test specific connections individually when a provider has multiple accounts
### 🔧 Maintenance
- Merged PR #623 — LongCat API base URL path correction
---
## [3.0.5] — 2026-03-25
### ✨ New Features
- **Limits UI:** Added tag grouping feature to the connections dashboard to improve visual organization for accounts with custom tags.
---
## [3.0.4] — 2026-03-25
### 🐛 Bug Fixes
- **Streaming:** Fixed `TextDecoder` state corruption inside combo `sanitize` TransformStream which caused SSE garbled output matching multibyte characters (PR #614)
- **Providers UI:** Safely render HTML tags inside provider connection error tooltips using `dangerouslySetInnerHTML`
- **Proxy Settings:** Added missing `username` and `password` payload body properties allowing authenticated proxies to be successfully verified from the Dashboard.
- **Provider API:** Bound soft exception returns to `getCodexUsage` preventing API HTTP 500 failures when token fetch fails
---
## [3.0.3] — 2026-03-25
### ✨ New Features
- **Auto-Sync Models:** Added a UI toggle and `sync-models` endpoint to automatically synchronise model lists per provider using a scheduled interval scheduler (PR #597)
### 🐛 Bug Fixes
- **Timeouts:** Elevated default proxies `FETCH_TIMEOUT_MS` and `STREAM_IDLE_TIMEOUT_MS` to 10 minutes to properly support deep reasoning models (like o1) without aborting requests (Fixes #609)
- **CLI Tool Detection:** Improved cross-platform detection handling NVM paths, Windows `PATHEXT` (preventing `.cmd` wrappers issue), and custom NPM prefixes (PR #598)
- **Streaming Logs:** Implemented `tool_calls` delta accumulation in streaming response logs so function calls are tracked and persisted accurately in DB (PR #603)
- **Model Catalog:** Removed auth exemption, properly hiding `comfyui` and `sdwebui` models when no provider is explicitly configured (PR #599)
### 🌐 Translations
- **cs:** Improved Czech translation strings across the app (PR #601)
## [3.0.2] — 2026-03-25
### 🚀 Enhancements & Features
+8 -1
View File
@@ -114,6 +114,7 @@ npm run test:fixes # Fix verification tests
# With coverage
npm run test:coverage
npm run coverage:report
# E2E tests (requires Playwright)
npm run test:e2e
@@ -123,7 +124,13 @@ npm run lint
npm run check
```
Current test status: **368+ unit tests** covering:
Coverage notes:
- `npm run test:coverage` measures source coverage for the main unit test suite, excludes `tests/**`, and includes `open-sse/**`
- `npm run coverage:report` prints the detailed file-by-file report from the latest coverage run
- `npm run test:coverage:legacy` preserves the older metric for historical comparison
Current test status: **968+ unit tests** covering:
- Provider translators and format conversion
- Rate limiting, circuit breaker, and resilience
+166
View File
@@ -0,0 +1,166 @@
# Test Coverage Plan
Last updated: 2026-03-28
## Baseline
There are multiple coverage numbers depending on how the report is computed. For planning, only one of them is useful.
| Metric | Scope | Statements / Lines | Branches | Functions | Notes |
| -------------------- | ----------------------------------------------------- | -----------------: | -------: | --------: | --------------------------------------------------- |
| Legacy | Old `npm run test:coverage` | 79.42% | 75.15% | 67.94% | Inflated: counts test files and excludes `open-sse` |
| Diagnostic | Source-only, excluding tests and excluding `open-sse` | 68.16% | 63.55% | 64.06% | Useful only to isolate `src/**` |
| Recommended baseline | Source-only, excluding tests and including `open-sse` | 56.95% | 66.05% | 57.80% | This is the project-wide baseline to improve |
The recommended baseline is the number to optimize against.
## Rules
- Coverage targets apply to source files, not to `tests/**`.
- `open-sse/**` is part of the product and must remain in scope.
- New code should not reduce coverage in touched areas.
- Prefer testing behavior and branch outcomes over implementation details.
- Prefer temp SQLite databases and small fixtures over broad mocks for `src/lib/db/**`.
## Current command set
- `npm run test:coverage`
- Main source coverage gate for the unit test suite
- Generates `text-summary`, `html`, `json-summary`, and `lcov`
- `npm run coverage:report`
- Detailed file-by-file report from the latest run
- `npm run test:coverage:legacy`
- Historical comparison only
## Milestones
| Phase | Target | Focus |
| ------- | ---------------------: | ------------------------------------------------- |
| Phase 1 | 60% statements / lines | Quick wins and low-risk utility coverage |
| Phase 2 | 65% statements / lines | DB and route foundations |
| Phase 3 | 70% statements / lines | Provider validation and usage analytics |
| Phase 4 | 75% statements / lines | `open-sse` translators and helpers |
| Phase 5 | 80% statements / lines | `open-sse` handlers and executor branches |
| Phase 6 | 85% statements / lines | Harder edge cases, branch debt, regression suites |
| Phase 7 | 90% statements / lines | Final sweep, gap closure, strict ratchet |
Branches and functions should ratchet upward with each phase, but the primary hard target is statements / lines.
## Priority hotspots
These files or areas offer the best return for the next phases:
1. `open-sse/handlers`
- `chatCore.ts` at 7.57%
- Overall directory at 29.07%
2. `open-sse/translator/request`
- Overall directory at 36.39%
- Many translators are still near single-digit coverage
3. `open-sse/translator/response`
- Overall directory at 8.07%
4. `open-sse/executors`
- Overall directory at 36.62%
5. `src/lib/db`
- `models.ts` at 20.66%
- `registeredKeys.ts` at 34.46%
- `modelComboMappings.ts` at 36.25%
- `settings.ts` at 46.40%
- `webhooks.ts` at 33.33%
6. `src/lib/usage`
- `usageHistory.ts` at 21.12%
- `usageStats.ts` at 9.56%
- `costCalculator.ts` at 30.00%
7. `src/lib/providers`
- `validation.ts` at 41.16%
8. Low-risk utility and API files for early gains
- `src/shared/utils/upstreamError.ts`
- `src/shared/utils/apiAuth.ts`
- `src/lib/api/errorResponse.ts`
- `src/app/api/settings/require-login/route.ts`
- `src/app/api/providers/[id]/models/route.ts`
## Execution checklist
### Phase 1: 56.95% -> 60%
- [x] Fix coverage metric so it reflects source code instead of test files
- [x] Keep a legacy coverage script for comparison
- [x] Record the baseline and hotspots in-repo
- [ ] Add focused tests for low-risk utilities:
- `src/shared/utils/upstreamError.ts`
- `src/shared/utils/fetchTimeout.ts`
- `src/lib/api/errorResponse.ts`
- `src/shared/utils/apiAuth.ts`
- `src/lib/display/names.ts`
- [ ] Add route tests for:
- `src/app/api/settings/require-login/route.ts`
- `src/app/api/providers/[id]/models/route.ts`
### Phase 2: 60% -> 65%
- [ ] Add DB-backed tests for:
- `src/lib/db/modelComboMappings.ts`
- `src/lib/db/settings.ts`
- `src/lib/db/registeredKeys.ts`
- [ ] Cover branch behavior in:
- `src/lib/providers/validation.ts`
- `src/app/api/v1/embeddings/route.ts`
- `src/app/api/v1/moderations/route.ts`
### Phase 3: 65% -> 70%
- [ ] Add usage analytics tests for:
- `src/lib/usage/usageHistory.ts`
- `src/lib/usage/usageStats.ts`
- `src/lib/usage/costCalculator.ts`
- [ ] Expand route coverage for proxy management and settings branches
### Phase 4: 70% -> 75%
- [ ] Cover translator helpers and central translation paths:
- `open-sse/translator/index.ts`
- `open-sse/translator/helpers/*`
- `open-sse/translator/request/*`
- `open-sse/translator/response/*`
### Phase 5: 75% -> 80%
- [ ] Add handler-level tests for:
- `open-sse/handlers/chatCore.ts`
- `open-sse/handlers/responsesHandler.js`
- `open-sse/handlers/imageGeneration.js`
- `open-sse/handlers/embeddings.js`
- [ ] Add executor branch coverage for provider-specific auth, retries, and endpoint overrides
### Phase 6: 80% -> 85%
- [ ] Merge more edge-case suites into the main coverage path
- [ ] Increase function coverage for DB modules with weak constructor/helper coverage
- [ ] Close branch gaps in `settings.ts`, `registeredKeys.ts`, `validation.ts`, and translator helpers
### Phase 7: 85% -> 90%
- [ ] Treat the remaining low-coverage files as blockers
- [ ] Add regression tests for every uncovered production bug fixed during the push to 90%
- [ ] Raise the coverage gate in CI only after the local baseline is stable for at least two consecutive runs
## Ratchet policy
Update `npm run test:coverage` thresholds only after the project actually exceeds the next milestone with a comfortable buffer.
Recommended ratchet sequence:
1. 55/60/55
2. 60/62/58
3. 65/64/62
4. 70/66/66
5. 75/70/72
6. 80/75/78
7. 85/80/84
8. 90/85/88
Order is `statements-lines / branches / functions`.
## Known gap
The current coverage command measures the main Node unit suite and includes source reached from it, including `open-sse`. It does not yet merge Vitest coverage into a single unified report. That merge is worth doing later, but it is not a blocker for starting the 60% -> 80% climb.
+8 -1
View File
@@ -1,13 +1,17 @@
FROM node:22-bookworm-slim AS builder
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends libsecret-1-0 \
&& rm -rf /var/lib/apt/lists/*
COPY package*.json ./
COPY scripts/postinstall.mjs ./scripts/postinstall.mjs
COPY scripts/native-binary-compat.mjs ./scripts/native-binary-compat.mjs
RUN if [ -f package-lock.json ]; then npm ci --no-audit --no-fund; else npm install --no-audit --no-fund; fi
COPY . ./
RUN mkdir -p /app/data && npm run build
RUN mkdir -p /app/data && npm run build -- --webpack
FROM node:22-bookworm-slim AS runner-base
WORKDIR /app
@@ -25,6 +29,9 @@ ENV NODE_OPTIONS="--max-old-space-size=256"
# Data directory inside Docker — must match the volume mount in docker-compose.yml
ENV DATA_DIR=/app/data
RUN apt-get update \
&& apt-get install -y --no-install-recommends libsecret-1-0 \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/data
COPY --from=builder /app/public ./public
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2081
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+1962
View File
File diff suppressed because it is too large Load Diff
+2073
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+35 -4
View File
@@ -32,12 +32,12 @@ _Your universal API proxy — one endpoint, 67+ providers, zero downtime. Now wi
| Area | Change |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection remediation |
| ✅ **Route Validation** | All 176 API routes now validated with Zod schemas + `validateBody()` — CI `check:route-validation:t06` passes |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streaming responses (#585) |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection remediation |
| ✅ **Route Validation** | All 176 API routes now validated with Zod schemas + `validateBody()` — CI `check:route-validation:t06` passes |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streaming responses (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with per-provider/account quota enforcement, idempotency, SHA-256 storage, and optional GitHub issue reporting |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG → generic fallback chain |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers on startup — configurable via `MODEL_SYNC_INTERVAL_HOURS` |
| 🔄 **Model Auto-Sync** | 24h scheduler and manual UI toggle to sync model lists for built-in and custom OpenAI-compatible providers |
| 🌐 **OpenCode Zen/Go** | Two new providers from @kang-heewon via PR #530: free tier + subscription tier via `OpencodeExecutor` |
| 🐛 **Gemini CLI OAuth** | Actionable error when `GEMINI_OAUTH_CLIENT_SECRET` is missing in Docker (was cryptic Google error) |
| 🐛 **OpenCode config** | `saveOpenCodeConfig()` now correctly writes TOML to `XDG_CONFIG_HOME` |
@@ -876,6 +876,35 @@ docker compose --profile base up -d
docker compose --profile cli up -d
```
**Using Docker Compose with Caddy (HTTPS Auto-TLS):**
OmniRoute can be securely exposed using Caddy's automatic SSL provisioning. Ensure your domain's DNS A record points to your server's IP.
```yaml
services:
omniroute:
image: diegosouzapw/omniroute:latest
container_name: omniroute
restart: unless-stopped
volumes:
- omniroute-data:/app/data
environment:
- PORT=20128
- NEXT_PUBLIC_BASE_URL=https://your-domain.com
caddy:
image: caddy:latest
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
command: caddy reverse-proxy --from https://your-domain.com --to http://omniroute:20128
volumes:
omniroute-data:
```
| Image | Tag | Size | Description |
| ------------------------ | -------- | ------ | --------------------- |
| `diegosouzapw/omniroute` | `latest` | ~250MB | Latest stable release |
@@ -1238,6 +1267,8 @@ OmniRoute v2.0 is built as an operational platform, not just a relay proxy.
| 🎮 **Model Playground** | Test any provider/model/endpoint from the dashboard |
| 🔏 **CLI Fingerprint Toggle** | Per-provider fingerprint matching in Settings > Security |
| 🌐 **i18n (30 languages)** | Full dashboard + docs language support with RTL coverage |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Custom Data Directory** | `DATA_DIR` override for storage location |
### Feature Deep Dive
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2080
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2079
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+12 -3
View File
@@ -2,7 +2,7 @@
🌐 **Languages:** 🇺🇸 [English](ARCHITECTURE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/ARCHITECTURE.md) | 🇪🇸 [Español](i18n/es/ARCHITECTURE.md) | 🇫🇷 [Français](i18n/fr/ARCHITECTURE.md) | 🇮🇹 [Italiano](i18n/it/ARCHITECTURE.md) | 🇷🇺 [Русский](i18n/ru/ARCHITECTURE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/ARCHITECTURE.md) | 🇩🇪 [Deutsch](i18n/de/ARCHITECTURE.md) | 🇮🇳 [हिन्दी](i18n/in/ARCHITECTURE.md) | 🇹🇭 [ไทย](i18n/th/ARCHITECTURE.md) | 🇺🇦 [Українська](i18n/uk-UA/ARCHITECTURE.md) | 🇸🇦 [العربية](i18n/ar/ARCHITECTURE.md) | 🇯🇵 [日本語](i18n/ja/ARCHITECTURE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/ARCHITECTURE.md) | 🇧🇬 [Български](i18n/bg/ARCHITECTURE.md) | 🇩🇰 [Dansk](i18n/da/ARCHITECTURE.md) | 🇫🇮 [Suomi](i18n/fi/ARCHITECTURE.md) | 🇮🇱 [עברית](i18n/he/ARCHITECTURE.md) | 🇭🇺 [Magyar](i18n/hu/ARCHITECTURE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/ARCHITECTURE.md) | 🇰🇷 [한국어](i18n/ko/ARCHITECTURE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/ARCHITECTURE.md) | 🇳🇱 [Nederlands](i18n/nl/ARCHITECTURE.md) | 🇳🇴 [Norsk](i18n/no/ARCHITECTURE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/ARCHITECTURE.md) | 🇷🇴 [Română](i18n/ro/ARCHITECTURE.md) | 🇵🇱 [Polski](i18n/pl/ARCHITECTURE.md) | 🇸🇰 [Slovenčina](i18n/sk/ARCHITECTURE.md) | 🇸🇪 [Svenska](i18n/sv/ARCHITECTURE.md) | 🇵🇭 [Filipino](i18n/phi/ARCHITECTURE.md) | 🇨🇿 [Čeština](i18n/cs/ARCHITECTURE.md)
_Last updated: 2026-03-24_
_Last updated: 2026-03-28_
## Executive Summary
@@ -274,8 +274,9 @@ Domain State DB (SQLite):
## 5) Cloud Sync
- Scheduler init: `src/lib/initCloudSync.ts`, `src/shared/services/initializeCloudSync.ts`
- Scheduler init: `src/lib/initCloudSync.ts`, `src/shared/services/initializeCloudSync.ts`, `src/shared/services/modelSyncScheduler.ts`
- Periodic task: `src/shared/services/cloudSyncScheduler.ts`
- Periodic task: `src/shared/services/modelSyncScheduler.ts`
- Control route: `src/app/api/sync/cloud/route.ts`
## Request Lifecycle (`/v1/chat/completions`)
@@ -355,7 +356,7 @@ flowchart TD
Q -- No --> R[Return all unavailable]
```
Fallback decisions are driven by `open-sse/services/accountFallback.ts` using status codes and error-message heuristics.
Fallback decisions are driven by `open-sse/services/accountFallback.ts` using status codes and error-message heuristics. Combo routing adds one extra guard: provider-scoped 400s such as upstream content-block and role-validation failures are treated as model-local failures so later combo targets can still run.
## OAuth Onboarding and Token Refresh Lifecycle
@@ -755,10 +756,18 @@ Runtime visibility sources:
- console logs from `src/sse/utils/logger.ts`
- per-request usage aggregates in SQLite (`usage_history`, `call_logs`, `proxy_logs`)
- four-stage detailed payload captures in SQLite (`request_detail_logs`) when `settings.detailed_logs_enabled=true`
- textual request status log in `log.txt` (optional/compat)
- optional deep request/translation logs under `logs/` when `ENABLE_REQUEST_LOGS=true`
- dashboard usage endpoints (`/api/usage/*`) for UI consumption
Detailed request payload capture stores up to four JSON payload stages per routed call:
- raw request received from the client
- translated request actually sent upstream
- provider response reconstructed as JSON (including streamed event sequences when applicable)
- final client response returned by OmniRoute
## Security-Sensitive Boundaries
- JWT secret (`JWT_SECRET`) secures dashboard session cookie verification/signing
+28 -30
View File
@@ -94,36 +94,32 @@ _قم بتوصيل أي أداة IDE أو CLI مدعومة بالذكاء الا
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
## 🤔 لماذا OmniRoute؟
**توقف عن إهدار المال وضرب الحدود:**
@@ -932,14 +928,16 @@ npm run electron:build:linux # Linux (.AppImage)
### ☁️ النشر والمنصة
| ميزة | ماذا يفعل |
| -------------------------------- | ------------------------------------------------ | --- | ------------------------ | ------------------------------- |
| 🌐 **النشر في أي مكان** | المضيف المحلي، VPS، Docker، البيئات السحابية | | 💾 **المزامنة السحابية** | مزامنة التكوين عبر عامل السحابة |
| 🔄 **النسخ الاحتياطي/الاستعادة** | تدفقات التصدير/الاستيراد والتعافي من الكوارث |
| 🧙 **معالج الإعداد** | الإعداد الموجه لأول مرة |
| 🔧 **لوحة تحكم أدوات CLI** | إعداد بنقرة واحدة لأدوات الترميز الشائعة |
| 🌐 **i18n (30 لغة)** | لوحة تحكم كاملة + دعم لغة المستندات مع تغطية RTL |
| 📂 **دليل البيانات المخصصة** | تجاوز `DATA_DIR` لموقع التخزين |
| ميزة | ماذا يفعل |
| -------------------------------- | --------------------------------------------------- | --- | ------------------------ | ------------------------------- |
| 🌐 **النشر في أي مكان** | المضيف المحلي، VPS، Docker، البيئات السحابية | | 💾 **المزامنة السحابية** | مزامنة التكوين عبر عامل السحابة |
| 🔄 **النسخ الاحتياطي/الاستعادة** | تدفقات التصدير/الاستيراد والتعافي من الكوارث |
| 🧙 **معالج الإعداد** | الإعداد الموجه لأول مرة |
| 🔧 **لوحة تحكم أدوات CLI** | إعداد بنقرة واحدة لأدوات الترميز الشائعة |
| 🌐 **i18n (30 لغة)** | لوحة تحكم كاملة + دعم لغة المستندات مع تغطية RTL |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **دليل البيانات المخصصة** | تجاوز `DATA_DIR` لموقع التخزين |
### ميزة الغوص العميق
+20 -22
View File
@@ -94,36 +94,32 @@ _Свържете всеки базиран на AI IDE или CLI инстру
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
## 🤔 Защо OmniRoute?
**Спрете да пилеете пари и да достигате лимити:**
@@ -941,6 +937,8 @@ OmniRoute v2.0 е създаден като операционна платфо
| 🧙 **Съветник за присъединяване** | Насочвана настройка при първо стартиране |
| 🔧 **CLI Tools Dashboard** | Настройка с едно щракване за популярни инструменти за кодиране |
| 🌐 **i18n (30 езика)** | Пълно табло за управление + езикова поддръжка на документи с RTL покритие |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Директория с персонализирани данни** | `DATA_DIR` отмяна за място за съхранение |
### Функция Deep Dive
+2
View File
@@ -48,6 +48,8 @@ Content-Type: application/json
| `X-OmniRoute-Idempotent` | Odpověď | `true` , pokud je odstraněna duplikace |
| `X-OmniRoute-Progress` | Odpověď | `enabled` pokud je zapnuto sledování průběhu |
> Poznámka Nginx: pokud spoléháte na hlavičky s podtržítkem (například `x_session_id`), povolte `underscores_in_headers on;`.
---
## Vložení
+125 -122
View File
@@ -1,60 +1,79 @@
# Průvodce nastavením nástrojů CLI — OmniRoute
Tato příručka vysvětluje, jak nainstalovat a nakonfigurovat všechny podporované nástroje CLI pro kódování umělé inteligence tak, aby **OmniRoute** fungoval jako jednotný backend, což vám umožní centralizovanou správu klíčů, sledování nákladů, přepínání modelů a protokolování požadavků napříč všemi nástroji.
Tato příručka vysvětluje, jak nainstalovat a nakonfigurovat všechny podporované nástroje CLI pro kódování umělé inteligence
tak, aby **OmniRoute** fungoval jako jednotný backend, což vám umožní centralizovanou správu klíčů,
sledování nákladů, přepínání modelů a protokolování požadavků napříč všemi nástroji.
---
## Jak to funguje
```
Claude / Codex / Gemini CLI / OpenCode / Cline / KiloCode / Continue / Kiro CLI
▼ (all point to OmniRoute)
http://YOUR_SERVER:20128/v1
▼ (OmniRoute routes to the right provider)
Anthropic / OpenAI / Gemini / DeepSeek / Groq / Mistral / ...
Claude / Codex / OpenCode / Cline / KiloCode / Continue / Kiro / Cursor / Copilot
▼ (všechny ukazují na OmniRoute)
http://VASE_SERVER:20128/v1
▼ (OmniRoute směruje ke správnému poskytovateli)
Anthropic / OpenAI / Gemini / DeepSeek / Groq / Mistral / ...
```
**Výhody:**
- Jeden klíč API pro správu všech nástrojů
- Sledování nákladů napříč všemi rozhraními příkazového řádku v dashboardu
- Jeden API klíč pro správu všech nástrojů
- Sledování nákladů napříč všemi CLI v dashboardu
- Přepínání modelů bez nutnosti překonfigurování každého nástroje
- Funguje lokálně i na vzdálených serverech (VPS)
---
## Podporované nástroje
## Podporované nástroje (Zdroj pravdy v dashboardu)
| Nástroj | Příkaz | Typ | Instalace |
| ---------------- | ------------------- | --------------- | -------------- |
| **Claude Code** | `claude` | CLI | npm |
| **OpenAI Codex** | `codex` | CLI | npm |
| **Gemini CLI** | `gemini` | CLI | npm |
| **OpenCode** | `opencode` | CLI | npm |
| **Cline** | `cline` | CLI + VS Code | npm |
| **KiloCode** | `kilocode` / `kilo` | CLI + VS Code | npm |
| **Continue** | průvodce | VS Code ext | VS kód |
| **Kiro CLI** | `kiro-cli` | CLI | curl instalace |
| **Kurzor** | `cursor` | Aplikace pro PC | Download |
| **Droid** | webový | Built-in agent | OmniRoute |
| **OpenClaw** | webový | Built-in agent | OmniRoute |
Karty dashboardu v `/dashboard/cli-tools` jsou generovány z `src/shared/constants/cliTools.ts`.
Aktuální seznam (v3.0.0-rc.16):
| Nástroj | ID | Příkaz | Režim nastavení | Metoda instalace |
| ------------------ | ------------- | ------------ | --------------- | ---------------- |
| **Claude Code** | `claude` | `claude` | env | npm |
| **OpenAI Codex** | `codex` | `codex` | custom | npm |
| **Factory Droid** | `droid` | `droid` | custom | bundled/CLI |
| **OpenClaw** | `openclaw` | `openclaw` | custom | bundled/CLI |
| **Cursor** | `cursor` | aplikace | guide | desktop app |
| **Cline** | `cline` | `cline` | custom | npm |
| **Kilo Code** | `kilo` | `kilocode` | custom | npm |
| **Continue** | `continue` | rozšíření | guide | VS Code |
| **Antigravity** | `antigravity` | interní | mitm | OmniRoute |
| **GitHub Copilot** | `copilot` | rozšíření | custom | VS Code |
| **OpenCode** | `opencode` | `opencode` | guide | npm |
| **Kiro AI** | `kiro` | aplikace/CLI | mitm | desktop/CLI |
### Synchronizace otisků CLI (Agenti + Nastavení)
`/dashboard/agents` a `Nastavení > CLI Otisk` používají `src/shared/constants/cliCompatProviders.ts`.
To udržuje ID poskytovatelů v souladu s kartami CLI a staršími ID.
| CLI ID | ID poskytovatele otisku |
| ---------------------------------------------------------------------------------------------------- | ----------------------- |
| `kilo` | `kilocode` |
| `copilot` | `github` |
| `claude` / `codex` / `antigravity` / `kiro` / `cursor` / `cline` / `opencode` / `droid` / `openclaw` | stejné ID |
Starší ID jsou stále přijímána pro kompatibilitu: `copilot`, `kimi-coding`, `qwen`.
---
## Krok 1 Získejte klíč OmniRoute API
## Krok 1 Získejte OmniRoute API klíč
1. Otevřete dashboard OmniRoute**Správce API** ( `/dashboard/api-manager` )
2. Klikněte na **Vytvořit klíč API**
3. Pojmenujte to (např. `cli-tools` ) a vyberte všechna oprávnění.
4. Zkopírujte klíč budete ho potřebovat pro každé níže uvedené rozhraní příkazového řádku.
1. Otevřete OmniRoute dashboard**Správce API** (`/dashboard/api-manager`)
2. Klikněte na **Vytvořit API klíč**
3. Dejte mu název (např. `cli-tools`) a vyberte všechna oprávnění
4. Zkopírujte klíč budete ho potřebovat pro každý CLI níže
> Váš klíč vypadá takto: `sk-xxxxxxxxxxxxxxxx-xxxxxxxxx`
---
## Krok 2 Instalace nástrojů CLI
## Krok 2 — Nainstalujte nástroje CLI
Všechny nástroje založené na npm vyžadují Node.js 18+:
@@ -65,9 +84,6 @@ npm install -g @anthropic-ai/claude-code
# OpenAI Codex
npm install -g @openai/codex
# Gemini CLI (Google)
npm install -g @google/gemini-cli
# OpenCode
npm install -g opencode-ai
@@ -75,47 +91,47 @@ npm install -g opencode-ai
npm install -g cline
# KiloCode
npm install -g kilecode
npm install -g kilocode
# Kiro CLI (Amazon — requires curl + unzip)
apt-get install -y unzip # on Debian/Ubuntu
# Kiro CLI (Amazon — vyžaduje curl + unzip)
apt-get install -y unzip # na Debian/Ubuntu
curl -fsSL https://cli.kiro.dev/install | bash
export PATH="$HOME/.local/bin:$PATH" # add to ~/.bashrc
export PATH="$HOME/.local/bin:$PATH" # přidat do ~/.bashrc
```
**Ověřit:**
**Ověření:**
```bash
claude --version # 2.x.x
codex --version # 0.x.x
gemini --version # 0.x.x
opencode --version # x.x.x
cline --version # 2.x.x
kilocode --version # x.x.x (or: kilo --version)
kilocode --version # x.x.x (nebo: kilo --version)
kiro-cli --version # 1.x.x
```
---
## Krok 3 Nastave globálních proměnných prostředí
## Krok 3 Nastavte globální proměnné prostředí
Přidejte do `~/.bashrc` (nebo `~/.zshrc` ) a poté spusťte `source ~/.bashrc` :
Přidejte do `~/.bashrc` (nebo `~/.zshrc`), pak spusťte `source ~/.bashrc`:
```bash
# OmniRoute Universal Endpoint
# OmniRoute Univerzální koncový bod
export OPENAI_BASE_URL="http://localhost:20128/v1"
export OPENAI_API_KEY="sk-your-omniroute-key"
export OPENAI_API_KEY="sk-vase-omniroute-klic"
export ANTHROPIC_BASE_URL="http://localhost:20128/v1"
export ANTHROPIC_API_KEY="sk-your-omniroute-key"
export ANTHROPIC_API_KEY="sk-vase-omniroute-klic"
export GEMINI_BASE_URL="http://localhost:20128/v1"
export GEMINI_API_KEY="sk-your-omniroute-key"
export GEMINI_API_KEY="sk-vase-omniroute-klic"
```
> Pro **vzdálený server** nahraďte `localhost:20128` IP adresou nebo doménou serveru, např. `http://192.168.0.15:20128` .
> Pro **vzdálený server** nahraďte `localhost:20128` IP adresou nebo doménou serveru,
> např. `http://192.168.0.15:20128`.
---
## Krok 4 Konfigurace jednotlivých nástrojů
## Krok 4 — Nakonfigurujte každý nástroj
### Claude Code
@@ -123,11 +139,11 @@ export GEMINI_API_KEY="sk-your-omniroute-key"
# Via CLI:
claude config set --global api-base-url http://localhost:20128/v1
# Or create ~/.claude/settings.json:
# Nebo vytvořte ~/.claude/settings.json:
mkdir -p ~/.claude && cat > ~/.claude/settings.json << EOF
{
"apiBaseUrl": "http://localhost:20128/v1",
"apiKey": "sk-your-omniroute-key"
"apiKey": "sk-vase-omniroute-klic"
}
EOF
```
@@ -141,7 +157,7 @@ EOF
```bash
mkdir -p ~/.codex && cat > ~/.codex/config.yaml << EOF
model: auto
apiKey: sk-your-omniroute-key
apiKey: sk-vase-omniroute-klic
apiBaseUrl: http://localhost:20128/v1
EOF
```
@@ -150,28 +166,13 @@ EOF
---
### Gemini CLI
```bash
mkdir -p ~/.gemini && cat > ~/.gemini/settings.json << EOF
{
"apiKey": "sk-your-omniroute-key",
"baseUrl": "http://localhost:20128/v1"
}
EOF
```
**Test:** `gemini "hello"`
---
### OpenCode
```bash
mkdir -p ~/.config/opencode && cat > ~/.config/opencode/config.toml << EOF
[provider.openai]
base_url = "http://localhost:20128/v1"
api_key = "sk-your-omniroute-key"
api_key = "sk-vase-omniroute-klic"
EOF
```
@@ -179,7 +180,7 @@ EOF
---
### Cline (CLI nebo VS kód)
### Cline (CLI nebo VS Code)
**Režim CLI:**
@@ -188,41 +189,42 @@ mkdir -p ~/.cline/data && cat > ~/.cline/data/globalState.json << EOF
{
"apiProvider": "openai",
"openAiBaseUrl": "http://localhost:20128/v1",
"openAiApiKey": "sk-your-omniroute-key"
"openAiApiKey": "sk-vase-omniroute-klic"
}
EOF
```
**Režim VS Code:** Nastavení rozšíření Cline → Poskytovatel API: `OpenAI Compatible` → Základní URL: `http://localhost:20128/v1`
**Režim VS Code:**
Nastavení rozšíření Cline → API Provider: `OpenAI Compatible` → Base URL: `http://localhost:20128/v1`
Nebo použijte dashboard OmniRoute**CLI Tools → Cline → Apply Config** .
Nebo použijte OmniRoute dashboard**CLI Nástroje → Cline → Použít konfiguraci**.
---
### KiloCode (CLI nebo VS kód)
### KiloCode (CLI nebo VS Code)
**Režim CLI:**
```bash
kilocode --api-base http://localhost:20128/v1 --api-key sk-your-omniroute-key
kilocode --api-base http://localhost:20128/v1 --api-key sk-vase-omniroute-klic
```
**Nastavení VS kódu:**
**Nastavení VS Code:**
```json
{
"kilo-code.openAiBaseUrl": "http://localhost:20128/v1",
"kilo-code.apiKey": "sk-your-omniroute-key"
"kilo-code.apiKey": "sk-vase-omniroute-klic"
}
```
Nebo použijte dashboard OmniRoute**CLI Tools → KiloCode → Apply Config** .
Nebo použijte OmniRoute dashboard**CLI Nástroje → KiloCode → Použít konfiguraci**.
---
### Continue (rozšíření kódu VS)
### Continue (Rozšíření VS Code)
Upravit `~/.continue/config.yaml` :
Upravte `~/.continue/config.yaml`:
```yaml
models:
@@ -230,7 +232,7 @@ models:
provider: openai
model: auto
apiBase: http://localhost:20128/v1
apiKey: sk-your-omniroute-key
apiKey: sk-vase-omniroute-klic
default: true
```
@@ -241,94 +243,95 @@ Po úpravě restartujte VS Code.
### Kiro CLI (Amazon)
```bash
# Login to your AWS/Kiro account:
# Přihlaste se ke svému AWS/Kiro účtu:
kiro-cli login
# The CLI uses its own auth — OmniRoute is not needed as backend for Kiro CLI itself.
# Use kiro-cli alongside OmniRoute for other tools.
# CLI používá vlastní autentifikaci — OmniRoute není potřeba jako backend pro samotný Kiro CLI.
# Používejte kiro-cli společně s OmniRoute pro ostatní nástroje.
kiro-cli status
```
---
### Kurzor (aplikace pro stolní počítače)
### Cursor (Desktop aplikace)
> **Poznámka:** Cursor směruje požadavky přes svůj cloud. Pro integraci OmniRoute povolte **cloudový koncový bod** v nastavení OmniRoute a použijte URL adresu vaší veřejné domény.
> **Poznámka:** Cursor směruje požadavky přes svůj cloud. Pro integraci s OmniRoute,
> povolte **Cloud Endpoint** v nastavení OmniRoute a použijte vaši veřejnou doménu.
Přes GUI: **Nastavení → Modely Klíč OpenAI API**
Via GUI: **Settings → Models → OpenAI API Key**
- Základní URL: `https://your-domain.com/v1`
- Klíč API: váš klíč OmniRoute
- Base URL: `https://vase-domena.com/v1`
- API Key: váš OmniRoute klíč
---
## Automatická konfigurace řídicího panelu
## Automatická konfigurace v dashboardu
Ovládací panel OmniRoute automatizuje konfiguraci většiny nástrojů:
OmniRoute dashboard automatizuje konfiguraci většiny nástrojů:
1. Přejděte na `http://localhost:20128/dashboard/cli-tools`
2. Rozbalit libovolnou kartu nástroje
3. Vyberte klíč API z rozbalovací nabídky
4. Klikněte **na Použít konfiguraci** (pokud je nástroj detekován jako nainstalovaný)
5. Nebo zkopírujte vygenerovaný konfigurační úryvek ručně
1. Jděte na `http://localhost:20128/dashboard/cli-tools`
2. Rozbalte libovolnou kartu nástroje
3. Vyberte svůj API klíč z rozbalovacího seznamu
4. Klikněte na **Použít konfiguraci** (pokud je nástroj detekován jako nainstalovaný)
5. Nebo ručně zkopírujte vygenerovaný konfigurační snippet
---
## Vestavění agenti: Droid a OpenClaw
## Vestavěný agenti: Droid & OpenClaw
**Droid** a **OpenClaw** jsou agenti umělé inteligence zabudovaní přímo do OmniRoute není nutná žádná instalace. Běží jako interní trasy a automaticky používají modelové směrování OmniRoute.
**Droid** a **OpenClaw** jsou AI agenti vestavění přímo do OmniRoute není potřeba žádná instalace.
Běží jako interní trasy a automaticky používají směrování modelů OmniRoute.
- Přístup: `http://localhost:20128/dashboard/agents`
- Konfigurace: stejné kombinace a poskytovatelé jako u všech ostatních nástrojů
- Není vyžadována instalace klíče API ani příkazového řádku
- Konfigurace: stejné kombinace a poskytovatelé jako všechny ostatní nástroje
- Není potřeba API klíč ani instalace CLI
---
## Dostupné koncové body API
## Dostupné API koncové body
| Koncový bod | Popis | Použití pro |
| -------------------------- | --------------------------------------- | ------------------------------------- |
| `/v1/chat/completions` | Standardní chat (všichni poskytovatelé) | Všechny moderní nástroje |
| `/v1/responses` | API pro odpovědi (formát OpenAI) | Kodex, agentické pracovní postupy |
| `/v1/completions` | Doplňování starších textů | Starší nástroje používající `prompt:` |
| `/v1/embeddings` | Vkládání textu | RAG, vyhledávání |
| `/v1/images/generations` | Generování obrázků | DALL-E, Flux atd. |
| `/v1/audio/speech` | Převod textu na řeč | ElevenLabs, OpenAI TTS |
| `/v1/audio/transcriptions` | Převod řeči na text | Deepgram, AssemblyAI |
| `/v1/responses` | Responses API (formát OpenAI) | Codex, agentní workflowy |
| `/v1/completions` | Legacy textové dokončení | Starší nástroje používající `prompt:` |
| `/v1/embeddings` | Textové vložení | RAG, vyhledávání |
| `/v1/images/generations` | Generování obrázků | DALL-E, Flux, atd. |
| `/v1/audio/speech` | Text-to-speech | ElevenLabs, OpenAI TTS |
| `/v1/audio/transcriptions` | Speech-to-text | Deepgram, AssemblyAI |
---
## Odstraňování problémů
## Řešení problémů
| Chyba | Příčina | Opravit |
| ---------------------------------- | ---------------------------------- | -------------------------------------------------------- |
| `Connection refused` | OmniRoute neběží | `pm2 start omniroute` |
| `401 Unauthorized` | Chybný klíč API | Zkontrolovat `/dashboard/api-manager` |
| `No combo configured` | Žádná aktivní routingová kombinace | Nastave v `/dashboard/combos` |
| `invalid model` | Model není v katalogu | Použijte `auto` nebo zkontrolujte `/dashboard/providers` |
| CLI zobrazuje „není nainstalováno“ | Binární soubor není v cestě PATH | Zkontrolujte, `which <command>` |
| `kiro-cli: not found` | Není v PATH | `export PATH="$HOME/.local/bin:$PATH"` |
| Chyba | Příčina | Oprava |
| ----------------------------- | ----------------------- | -------------------------------------------------------- |
| `Connection refused` | OmniRoute neběží | `pm2 start omniroute` |
| `401 Unauthorized` | Špatný API klíč | Zkontrolujte v `/dashboard/api-manager` |
| `No combo configured` | Žádná aktivní kombinace | Nastavte v `/dashboard/combos` |
| `invalid model` | Model není v katalogu | Použijte `auto` nebo zkontrolujte `/dashboard/providers` |
| CLI zobrazuje "not installed" | Binárka není v PATH | Zkontrolujte `which <příkaz>` |
| `kiro-cli: not found` | Není v PATH | `export PATH="$HOME/.local/bin:$PATH"` |
---
## Skript pro rychlé nastavení (jeden příkaz)
## Rychlý skript pro nastavení (jeden příkaz)
```bash
# Install all CLIs and configure for OmniRoute (replace with your key and server URL)
# Nainstalujte všechny CLI a nakonfigurujte pro OmniRoute (nahraďte svým klíčem a URL serveru)
OMNIROUTE_URL="http://localhost:20128/v1"
OMNIROUTE_KEY="sk-your-omniroute-key"
OMNIROUTE_KEY="sk-vase-omniroute-klic"
npm install -g @anthropic-ai/claude-code @openai/codex @google/gemini-cli opencode-ai cline kilecode
npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai cline kilocode
# Kiro CLI
apt-get install -y unzip 2>/dev/null; curl -fsSL https://cli.kiro.dev/install | bash
# Write configs
mkdir -p ~/.claude ~/.codex ~/.gemini ~/.config/opencode ~/.continue
# Zápis konfigurací
mkdir -p ~/.claude ~/.codex ~/.config/opencode ~/.continue
cat > ~/.claude/settings.json <<< "{\"apiBaseUrl\":\"$OMNIROUTE_URL\",\"apiKey\":\"$OMNIROUTE_KEY\"}"
cat > ~/.codex/config.yaml <<< "model: auto\napiKey: $OMNIROUTE_KEY\napiBaseUrl: $OMNIROUTE_URL"
cat > ~/.gemini/settings.json <<< "{\"apiKey\":\"$OMNIROUTE_KEY\",\"baseUrl\":\"$OMNIROUTE_URL\"}"
cat >> ~/.bashrc << EOF
export OPENAI_BASE_URL="$OMNIROUTE_URL"
export OPENAI_API_KEY="$OMNIROUTE_KEY"
@@ -337,5 +340,5 @@ export ANTHROPIC_API_KEY="$OMNIROUTE_KEY"
EOF
source ~/.bashrc
echo "✅ All CLIs installed and configured for OmniRoute"
echo "✅ Všechny CLI nainstalovány a nakonfigurovány pro OmniRoute"
```
+20 -23
View File
@@ -20,36 +20,32 @@ _Váš univerzální API proxy jeden endpoint, více než 44 poskytovatelů,
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
## 🖼️ Hlavní ovládací panel
<div align="center"> <img src="./docs/screenshots/MainOmniRoute.png" alt="Řídicí panel OmniRoute" width="800"> </div>
@@ -1071,6 +1067,8 @@ OmniRoute v2.0 je navržen jako operační platforma, nikoli pouze jako proxy pr
| 🎮 **Modelové hřiště** | Otestujte libovolného poskytovatele/model/koncový bod z řídicího panelu |
| 🔏 **Přepínač otisků prstů v příkazovém řádku** | Porovnávání otisků prstů podle poskytovatele v Nastavení &gt; Zabezpečení |
| 🌐 **i18n (30 jazyků)** | Plná jazyková podpora dashboardu a dokumentace s psaním zprava doleva |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Adresář vlastních dat** | Přepsání `DATA_DIR` pro umístění úložiště |
### Hluboký pohled na funkce
@@ -1592,7 +1590,6 @@ opencode
### 🔐 OAuth na vzdáleném serveru
<a name="oauth-on-a-remote-server"></a>
<a name="oauth-em-servidor-remoto"></a>
> **⚠️ Důležité pro uživatele, kteří provozují OmniRoute na VPS, Dockeru nebo jakémkoli vzdáleném serveru**
+56 -54
View File
@@ -1,21 +1,23 @@
# OmniRoute — Guia de Deploy em VM com Cloudflare
# Průvodce nasazením OmniRoute na VM s Cloudflare
Kompletní instalace a konfigurace OmniRoute u VM (VPS) s gerenciou přes Cloudflare.
🌐 **Jazyky:** 🇺🇸 [English](VM_DEPLOYMENT_GUIDE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/VM_DEPLOYMENT_GUIDE.md) | 🇪🇸 [Español](i18n/es/VM_DEPLOYMENT_GUIDE.md) | 🇫🇷 [Français](i18n/fr/VM_DEPLOYMENT_GUIDE.md) | 🇮🇹 [Italiano](i18n/it/VM_DEPLOYMENT_GUIDE.md) | 🇷🇺 [Русский](i18n/ru/VM_DEPLOYMENT_GUIDE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/VM_DEPLOYMENT_GUIDE.md) | 🇩🇪 [Deutsch](i18n/de/VM_DEPLOYMENT_GUIDE.md) | 🇮🇳 [हिन्दी](i18n/in/VM_DEPLOYMENT_GUIDE.md) | 🇹🇭 [ไทย](i18n/th/VM_DEPLOYMENT_GUIDE.md) | 🇺🇦 [Українська](i18n/uk-UA/VM_DEPLOYMENT_GUIDE.md) | 🇸🇦 [العربية](i18n/ar/VM_DEPLOYMENT_GUIDE.md) | 🇯🇵 [日本語](i18n/ja/VM_DEPLOYMENT_GUIDE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/VM_DEPLOYMENT_GUIDE.md) | 🇧🇬 [Български](i18n/bg/VM_DEPLOYMENT_GUIDE.md) | 🇩🇰 [Dansk](i18n/da/VM_DEPLOYMENT_GUIDE.md) | 🇫🇮 [Suomi](i18n/fi/VM_DEPLOYMENT_GUIDE.md) | 🇮🇱 [עברית](i18n/he/VM_DEPLOYMENT_GUIDE.md) | 🇭🇺 [Magyar](i18n/hu/VM_DEPLOYMENT_GUIDE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/VM_DEPLOYMENT_GUIDE.md) | 🇰🇷 [한국어](i18n/ko/VM_DEPLOYMENT_GUIDE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/VM_DEPLOYMENT_GUIDE.md) | 🇳🇱 [Nederlands](i18n/nl/VM_DEPLOYMENT_GUIDE.md) | 🇳🇴 [Norsk](i18n/no/VM_DEPLOYMENT_GUIDE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/VM_DEPLOYMENT_GUIDE.md) | 🇷🇴 [Română](i18n/ro/VM_DEPLOYMENT_GUIDE.md) | 🇵🇱 [Polski](i18n/pl/VM_DEPLOYMENT_GUIDE.md) | 🇸🇰 [Slovenčina](i18n/sk/VM_DEPLOYMENT_GUIDE.md) | 🇸🇪 [Svenska](i18n/sv/VM_DEPLOYMENT_GUIDE.md) | 🇵🇭 [Filipino](i18n/phi/VM_DEPLOYMENT_GUIDE.md) | 🇨🇿 [Čeština](i18n/cs/VM_DEPLOYMENT_GUIDE.md)
Kompletní průvodce instalací a konfigurací OmniRoute na virtuálním stroji (VPS) se správou domény prostřednictvím Cloudflare.
---
## Předpoklady
Položka | Mínimo | Doporučeno
--- | --- | ---
**Procesor** | 1 virtuální procesor | 2 vCPU
**BERAN** | 1 GB | 2 GB
**Disko** | 10GB SSD | 25GB SSD
**TAK** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS
**Domínio** | Registrován v Cloudflare | —
**Přístavní dělník** | Docker Engine 24+ | Docker 27+
| Položka | Minimální | Doporučeno |
| ------------ | --------------------------- | ---------------- |
| **Procesor** | 1 virtuální procesor | 2 vCPU |
| **RAM** | 1 GB | 2 GB |
| **Disk** | 10GB SSD | 25GB SSD |
| **CPU** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS |
| **Doména** | Zaregistrována v Cloudflare | — |
| **Docker** | Docker Engine 24+ | Docker 27+ |
**Testados poskytovatelů** : Akamai (Linode), DigitalOcean, Vultr, Hetzner, AWS Lightsail.
**Testovaní poskytovatelé**: Akamai (Linode), DigitalOcean, Vultr, Hetzner, AWS Lightsail.
---
@@ -23,12 +25,12 @@ Položka | Mínimo | Doporučeno
### 1.1 Vytvořit ihned
Žádný preferovaný poskytovatel seu VPS:
Žádný preferovaný poskytovatel VPS:
- Vyberte si Ubuntu 24.04 LTS
- Výběr nebo plano minimo (1 vCPU / 1 GB RAM)
- Definujte sílu pro root nebo konfiguraci klíče SSH
- Anote o **IP público** (např.: `203.0.113.10` )
- Vyberte minimální plán (1 vCPU / 1 GB RAM)
- Nastavte silné heslo pro root nebo konfiguraci SSH klíče
- Poznamenejte si **veřejnou IP** (např.: `203.0.113.10`)
### 1.2 Připojení přes SSH
@@ -45,10 +47,10 @@ apt update && apt upgrade -y
### 1.4 Instalace Dockeru
```bash
# Instalar dependências
# Nainstalovat závislosti
apt install -y ca-certificates curl gnupg
# Adicionar repositório oficial do Docker
# Přidat oficiální Docker repository
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
@@ -74,29 +76,29 @@ ufw allow 443/tcp # HTTPS
ufw enable
```
> **Dica** : Maximální zabezpečení, omezení jako porty 80 a 443 přístupů pro IP Cloudflare. Veja a seção [Segurança Avançada](#seguran%C3%A7a-avan%C3%A7ada) .
> **Tip**: Pro maximální zabezpečení omezte porty 80 a 443 pouze na IP Cloudflare. Viz sekce [Pokročilé zabezpečení](#pokrocilé-zabezpečení).
---
## 2. Instalace OmniRoute
### 2.1 Criar diretório de configuração
### 2.1 Vytvořit konfigurační adresář
```bash
mkdir -p /opt/omniroute
```
### 2.2 Criar arquivo de variáveis de ambiente
### 2.2 Vytvořit soubor s proměnnými prostředí
```bash
cat > /opt/omniroute/.env << 'EOF'
# === Segurança ===
JWT_SECRET=ALTERE-PARA-CHAVE-SECRETA-UNICA-64-CHARS
INITIAL_PASSWORD=SuaSenhaSegura123!
API_KEY_SECRET=ALTERE-PARA-OUTRA-CHAVE-SECRETA
STORAGE_ENCRYPTION_KEY=ALTERE-PARA-TERCEIRA-CHAVE-SECRETA
# === Bezpečnost ===
JWT_SECRET=CHANGE-TO-A-UNIQUE-64-CHAR-SECRET-KEY
INITIAL_PASSWORD=YourSecurePassword123!
API_KEY_SECRET=REPLACE-WITH-ANOTHER-SECRET-KEY
STORAGE_ENCRYPTION_KEY=REPLACE-WITH-THIRD-SECRET-KEY
STORAGE_ENCRYPTION_KEY_VERSION=v1
MACHINE_ID_SALT=ALTERE-PARA-SALT-UNICO
MACHINE_ID_SALT=CHANGE-TO-A-UNIQUE-SALT
# === App ===
PORT=20128
@@ -108,9 +110,9 @@ ENABLE_REQUEST_LOGS=true
AUTH_COOKIE_SECURE=false
REQUIRE_API_KEY=false
# === Domain (altere para seu domínio) ===
BASE_URL=https://llms.seudominio.com
NEXT_PUBLIC_BASE_URL=https://llms.seudominio.com
# === Doména (změňte na vaši doménu) ===
BASE_URL=https://llms.vasedomena.com
NEXT_PUBLIC_BASE_URL=https://llms.vasedomena.com
# === Cloud Sync (opcional) ===
# CLOUD_URL=https://cloud.omniroute.online
@@ -118,7 +120,7 @@ NEXT_PUBLIC_BASE_URL=https://llms.seudominio.com
EOF
```
> ⚠️ **DŮLEŽITÉ** : Gere chaves secretas únicas! Použijte `openssl rand -hex 32` para cada chave.
> ⚠️ **DŮLEŽITÉ**: Vygenerujte jedinečné tajné klíče! Použijte `openssl rand -hex 32` pro každý klíč.
### 2.3 Spuštění kontejneru
@@ -147,19 +149,19 @@ Vývojový příklad: `[DB] SQLite database ready` a `listening on port 20128` .
## 3. Konfigurace nginx (reverzní proxy)
### 3.1 Gerar Certificado SSL (Cloudflare Origin)
### 3.1 Vygenerovat SSL certifikát (Cloudflare Origin)
Cloudflare nic neřeší:
1. Používá **SSL/TLS → Origin Server**
2. **Certifikát Clique Create**
3. Deixe os padrões (15 ano, *.seudominio.com)
2. Klikněte na **Vytvořit certifikát**
3. Ponechte výchozí nastavení (15 let, \*.vasedomena.com)
4. Zkopírujte nebo zkopírujte **certifikát původu** a **soukromý klíč**
```bash
mkdir -p /etc/nginx/ssl
# Colar o certificado
# Vložit certifikát
nano /etc/nginx/ssl/origin.crt
# Colar a chave privada
@@ -188,7 +190,7 @@ server {
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name llms.seudominio.com; # Altere para seu domínio
server_name llms.vasedomena.com; # Změňte na vaši doménu
ssl_certificate /etc/nginx/ssl/origin.crt;
ssl_certificate_key /etc/nginx/ssl/origin.key;
@@ -220,7 +222,7 @@ server {
server {
listen 80;
listen [::]:80;
server_name llms.seudominio.com;
server_name llms.vasedomena.com;
return 301 https://$server_name$request_uri;
}
NGINX
@@ -245,11 +247,11 @@ nginx -t && systemctl reload nginx
### 4.1 Další DNS registr
No painel da Cloudflare → DNS:
V dashboardu Cloudflare → DNS:
Typ | Jméno | Obsah | Proxy
--- | --- | --- | ---
A | `llms` | `203.0.113.10` (IP adresa virtuálního počítače) | ✅ Proxy
| Typ | Jméno | Obsah | Proxy |
| --- | ------ | ----------------------------------------------- | -------- |
| A | `llms` | `203.0.113.10` (IP adresa virtuálního počítače) | ✅ Proxy |
### 4.2 Konfigurace SSL
@@ -257,7 +259,7 @@ Em **SSL/TLS → Přehled** :
- Režim: **Plný (Přísný)**
Em **SSL/TLS → Edge certifikáty** :
V **SSL/TLS → Edge Certificates**:
- Vždy používat HTTPS: ✅ Zapnuto
- Minimální verze TLS: TLS 1.2
@@ -266,7 +268,7 @@ Em **SSL/TLS → Edge certifikáty** :
### 4.3 Testar
```bash
curl -sI https://llms.seudominio.com/health
curl -sI https://llms.vasedomena.com/health
# Deve retornar HTTP/2 200
```
@@ -289,14 +291,14 @@ docker run -d --name omniroute --restart unless-stopped \
### Verzovní protokoly
```bash
docker logs -f omniroute # Stream em tempo real
docker logs -f omniroute # Živý stream
docker logs omniroute --tail 50 # Últimas 50 linhas
```
### Ruční zálohování banky
```bash
# Copiar dados do volume para o host
# Kopírovat data z volume do hostitele
docker cp omniroute:/app/data ./backup-$(date +%F)
# Ou comprimir todo o volume
@@ -321,7 +323,7 @@ docker start omniroute
```bash
cat > /etc/nginx/cloudflare-ips.conf << 'CF'
# Cloudflare IPv4 ranges — atualizar periodicamente
# Cloudflare IPv4 ranges — aktualizovat pravidelně
# https://www.cloudflare.com/ips-v4/
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
@@ -342,7 +344,7 @@ real_ip_header CF-Connecting-IP;
CF
```
Přidat `nginx.conf` dentro do bloco `http {}` :
Přidat do `nginx.conf` do bloku `http {}`:
```nginx
include /etc/nginx/cloudflare-ips.conf;
@@ -362,7 +364,7 @@ fail2ban-client status sshd
### Bloquear accesso direto na port do Docker
```bash
# Impedir acesso externo direto à porta 20128
# Zamezit přímému externímu přístupu k portu 20128
iptables -I DOCKER-USER -p tcp --dport 20128 -j DROP
iptables -I DOCKER-USER -i lo -p tcp --dport 20128 -j ACCEPT
@@ -389,11 +391,11 @@ Dokumenty jsou kompletní pro [omnirouteCloud/README.md](../omnirouteCloud/READM
---
## Resumo de Portas
## Přehled portů
Porta | Služba | Přístup
--- | --- | ---
22 | SSH | Veřejné (s fail2ban)
80 | nginx HTTP | Přesměrování → HTTPS
443 | nginx HTTPS | Prostřednictvím proxy serveru Cloudflare
20128 | OmniRoute | Někdy na localhostu (přes nginx)
| Port | Služba | Přístup |
| ----- | ----------- | ---------------------------------------- |
| 22 | SSH | Veřejné (s fail2ban) |
| 80 | nginx HTTP | Přesměrování → HTTPS |
| 443 | nginx HTTPS | Prostřednictvím proxy serveru Cloudflare |
| 20128 | OmniRoute | Někdy na localhostu (přes nginx) |
-164
View File
@@ -1,164 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>V&iacute;cejazy&ccaron;n&aacute; dokumentace</title>
<style>
/* From extension vscode.github */
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vscode-dark img[src$=\#gh-light-mode-only],
.vscode-light img[src$=\#gh-dark-mode-only],
.vscode-high-contrast:not(.vscode-high-contrast-light) img[src$=\#gh-light-mode-only],
.vscode-high-contrast-light img[src$=\#gh-dark-mode-only] {
display: none;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/markdown.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/highlight.css">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif;
font-size: 14px;
line-height: 1.6;
}
</style>
<style>
.task-list-item {
list-style-type: none;
}
.task-list-item-checkbox {
margin-left: -20px;
vertical-align: middle;
pointer-events: none;
}
</style>
<style>
:root {
--color-note: #0969da;
--color-tip: #1a7f37;
--color-warning: #9a6700;
--color-severe: #bc4c00;
--color-caution: #d1242f;
--color-important: #8250df;
}
</style>
<style>
@media (prefers-color-scheme: dark) {
:root {
--color-note: #2f81f7;
--color-tip: #3fb950;
--color-warning: #d29922;
--color-severe: #db6d28;
--color-caution: #f85149;
--color-important: #a371f7;
}
}
</style>
<style>
.markdown-alert {
padding: 0.5rem 1rem;
margin-bottom: 16px;
color: inherit;
border-left: .25em solid #888;
}
.markdown-alert>:first-child {
margin-top: 0
}
.markdown-alert>:last-child {
margin-bottom: 0
}
.markdown-alert .markdown-alert-title {
display: flex;
font-weight: 500;
align-items: center;
line-height: 1
}
.markdown-alert .markdown-alert-title .octicon {
margin-right: 0.5rem;
display: inline-block;
overflow: visible !important;
vertical-align: text-bottom;
fill: currentColor;
}
.markdown-alert.markdown-alert-note {
border-left-color: var(--color-note);
}
.markdown-alert.markdown-alert-note .markdown-alert-title {
color: var(--color-note);
}
.markdown-alert.markdown-alert-important {
border-left-color: var(--color-important);
}
.markdown-alert.markdown-alert-important .markdown-alert-title {
color: var(--color-important);
}
.markdown-alert.markdown-alert-warning {
border-left-color: var(--color-warning);
}
.markdown-alert.markdown-alert-warning .markdown-alert-title {
color: var(--color-warning);
}
.markdown-alert.markdown-alert-tip {
border-left-color: var(--color-tip);
}
.markdown-alert.markdown-alert-tip .markdown-alert-title {
color: var(--color-tip);
}
.markdown-alert.markdown-alert-caution {
border-left-color: var(--color-caution);
}
.markdown-alert.markdown-alert-caution .markdown-alert-title {
color: var(--color-caution);
}
</style>
</head>
<body class="vscode-body vscode-light">
<h1 id="vícejazyčná-dokumentace">Vícejazyčná dokumentace</h1>
<p>Tento adresář obsahuje strojově asistované překlady založené na anglické dokumentaci.</p>
<ul>
<li><strong>API_REFERENCE.md</strong> : 🇺🇸 <a href="../API_REFERENCE.html">Česky</a> | 🇧🇷 <a href="./pt-BR/API_REFERENCE.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/API_REFERENCE.html">Español</a> | 🇫🇷 <a href="./fr/API_REFERENCE.html">Français</a> | 🇮🇹 <a href="./it/API_REFERENCE.html">Italiano</a> | 🇷🇺 <a href="./ru/API_REFERENCE.html">Русский</a> | 🇨🇳<a href="./zh-CN/API_REFERENCE.html">中文 (简体)</a> | 🇩🇪 <a href="./de/API_REFERENCE.html">Deutsch</a> | 🇮🇳 <a href="./in/API_REFERENCE.html">हिन्दी</a> | 🇹🇭 <a href="./th/API_REFERENCE.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/API_REFERENCE.html">Українська</a> | 🇸🇦 <a href="./ar/API_REFERENCE.html">العربية</a> | 🇯🇵<a href="./ja/API_REFERENCE.html">日本語</a>| 🇻🇳 <a href="./vi/API_REFERENCE.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/API_REFERENCE.html">Български</a> | 🇩🇰 <a href="./da/API_REFERENCE.html">Dánsko</a> | 🇫🇮 <a href="./fi/API_REFERENCE.html">Suomi</a> | 🇮🇱 <a href="./he/API_REFERENCE.html">עברית</a> | 🇭🇺 <a href="./hu/API_REFERENCE.html">maďarština</a> | 🇮🇩 <a href="./id/API_REFERENCE.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/API_REFERENCE.html">한국어</a> | 🇲🇾 <a href="./ms/API_REFERENCE.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/API_REFERENCE.html">Nizozemsko</a> | 🇳🇴 <a href="./no/API_REFERENCE.html">Norsk</a> | 🇵🇹 <a href="./pt/API_REFERENCE.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/API_REFERENCE.html">Română</a> | 🇵🇱 <a href="./pl/API_REFERENCE.html">Polski</a> | 🇸🇰 <a href="./sk/API_REFERENCE.html">Slovenčina</a> | 🇸🇪 <a href="./sv/API_REFERENCE.html">Svenska</a> | 🇵🇭 <a href="./phi/API_REFERENCE.html">Filipínec</a></li>
<li><strong><a href="http://ARCHITECTURE.html">ARCHITECTURE.md</a></strong> : 🇺🇸 <a href="../ARCHITECTURE.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/ARCHITECTURE.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/ARCHITECTURE.html">Español</a> | 🇫🇷 <a href="./fr/ARCHITECTURE.html">Français</a> | 🇮🇹 <a href="./it/ARCHITECTURE.html">Italiano</a> | 🇷🇺 <a href="./ru/ARCHITECTURE.html">Русский</a> | 🇨🇳<a href="./zh-CN/ARCHITECTURE.html">中文 (简体)</a> | 🇩🇪 <a href="./de/ARCHITECTURE.html">Deutsch</a> | 🇮🇳 <a href="./in/ARCHITECTURE.html">हिन्दी</a> | 🇹🇭 <a href="./th/ARCHITECTURE.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/ARCHITECTURE.html">Українська</a> | 🇸🇦 <a href="./ar/ARCHITECTURE.html">العربية</a> | 🇯🇵<a href="./ja/ARCHITECTURE.html">日本語</a>| 🇻🇳 <a href="./vi/ARCHITECTURE.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/ARCHITECTURE.html">Български</a> | 🇩🇰 <a href="./da/ARCHITECTURE.html">Dánsko</a> | 🇫🇮 <a href="./fi/ARCHITECTURE.html">Suomi</a> | 🇮🇱 <a href="./he/ARCHITECTURE.html">עברית</a> | 🇭🇺 <a href="./hu/ARCHITECTURE.html">maďarština</a> | 🇮🇩 <a href="./id/ARCHITECTURE.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/ARCHITECTURE.html">한국어</a> | 🇲🇾 <a href="./ms/ARCHITECTURE.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/ARCHITECTURE.html">Nizozemsko</a> | 🇳🇴 <a href="./no/ARCHITECTURE.html">Norsk</a> | 🇵🇹 <a href="./pt/ARCHITECTURE.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/ARCHITECTURE.html">Română</a> | 🇵🇱 <a href="./pl/ARCHITECTURE.html">Polski</a> | 🇸🇰 <a href="./sk/ARCHITECTURE.html">Slovenčina</a> | 🇸🇪 <a href="./sv/ARCHITECTURE.html">Svenska</a> | 🇵🇭 <a href="./phi/ARCHITECTURE.html">Filipínec</a></li>
<li><strong>CODEBASE_DOCUMENTATION.md</strong> : 🇺🇸 <a href="../CODEBASE_DOCUMENTATION.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/CODEBASE_DOCUMENTATION.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/CODEBASE_DOCUMENTATION.html">Español</a> | 🇫🇷 <a href="./fr/CODEBASE_DOCUMENTATION.html">Français</a> | 🇮🇹 <a href="./it/CODEBASE_DOCUMENTATION.html">Italiano</a> | 🇷🇺 <a href="./ru/CODEBASE_DOCUMENTATION.html">Русский</a> | 🇨🇳<a href="./zh-CN/CODEBASE_DOCUMENTATION.html">中文 (简体)</a> | 🇩🇪 <a href="./de/CODEBASE_DOCUMENTATION.html">Deutsch</a> | 🇮🇳 <a href="./in/CODEBASE_DOCUMENTATION.html">हिन्दी</a> | 🇹🇭 <a href="./th/CODEBASE_DOCUMENTATION.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/CODEBASE_DOCUMENTATION.html">Українська</a> | 🇸🇦 <a href="./ar/CODEBASE_DOCUMENTATION.html">العربية</a> | 🇯🇵<a href="./ja/CODEBASE_DOCUMENTATION.html">日本語</a>| 🇻🇳 <a href="./vi/CODEBASE_DOCUMENTATION.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/CODEBASE_DOCUMENTATION.html">Български</a> | 🇩🇰 <a href="./da/CODEBASE_DOCUMENTATION.html">Dánsko</a> | 🇫🇮 <a href="./fi/CODEBASE_DOCUMENTATION.html">Suomi</a> | 🇮🇱 <a href="./he/CODEBASE_DOCUMENTATION.html">עברית</a> | 🇭🇺 <a href="./hu/CODEBASE_DOCUMENTATION.html">maďarština</a> | 🇮🇩 <a href="./id/CODEBASE_DOCUMENTATION.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/CODEBASE_DOCUMENTATION.html">한국어</a> | 🇲🇾 <a href="./ms/CODEBASE_DOCUMENTATION.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/CODEBASE_DOCUMENTATION.html">Nizozemsko</a> | 🇳🇴 <a href="./no/CODEBASE_DOCUMENTATION.html">Norsk</a> | 🇵🇹 <a href="./pt/CODEBASE_DOCUMENTATION.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/CODEBASE_DOCUMENTATION.html">Română</a> | 🇵🇱 <a href="./pl/CODEBASE_DOCUMENTATION.html">Polski</a> | 🇸🇰 <a href="./sk/CODEBASE_DOCUMENTATION.html">Slovenčina</a> | 🇸🇪 <a href="./sv/CODEBASE_DOCUMENTATION.html">Svenska</a> | 🇵🇭 <a href="./phi/CODEBASE_DOCUMENTATION.html">Filipínec</a></li>
<li><strong><a href="http://FEATURES.html">FEATURES.md</a></strong> : 🇺🇸 <a href="../FEATURES.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/FEATURES.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/FEATURES.html">Español</a> | 🇫🇷 <a href="./fr/FEATURES.html">Français</a> | 🇮🇹 <a href="./it/FEATURES.html">Italiano</a> | 🇷🇺 <a href="./ru/FEATURES.html">Русский</a> | 🇨🇳<a href="./zh-CN/FEATURES.html">中文 (简体)</a> | 🇩🇪 <a href="./de/FEATURES.html">Deutsch</a> | 🇮🇳 <a href="./in/FEATURES.html">हिन्दी</a> | 🇹🇭 <a href="./th/FEATURES.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/FEATURES.html">Українська</a> | 🇸🇦 <a href="./ar/FEATURES.html">العربية</a> | 🇯🇵<a href="./ja/FEATURES.html">日本語</a>| 🇻🇳 <a href="./vi/FEATURES.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/FEATURES.html">Български</a> | 🇩🇰 <a href="./da/FEATURES.html">Dánsko</a> | 🇫🇮 <a href="./fi/FEATURES.html">Suomi</a> | 🇮🇱 <a href="./he/FEATURES.html">עברית</a> | 🇭🇺 <a href="./hu/FEATURES.html">maďarština</a> | 🇮🇩 <a href="./id/FEATURES.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/FEATURES.html">한국어</a> | 🇲🇾 <a href="./ms/FEATURES.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/FEATURES.html">Nizozemsko</a> | 🇳🇴 <a href="./no/FEATURES.html">Norsk</a> | 🇵🇹 <a href="./pt/FEATURES.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/FEATURES.html">Română</a> | 🇵🇱 <a href="./pl/FEATURES.html">Polski</a> | 🇸🇰 <a href="./sk/FEATURES.html">Slovenčina</a> | 🇸🇪 <a href="./sv/FEATURES.html">Svenska</a> | 🇵🇭 <a href="./phi/FEATURES.html">Filipínec</a></li>
<li><strong><a href="http://TOUBLESHOOTING.html">TOUBLESHOOTING.md</a></strong> : 🇺🇸 <a href="../TROUBLESHOOTING.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/TROUBLESHOOTING.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/TROUBLESHOOTING.html">Español</a> | 🇫🇷 <a href="./fr/TROUBLESHOOTING.html">Français</a> | 🇮🇹 <a href="./it/TROUBLESHOOTING.html">Italiano</a> | 🇷🇺 <a href="./ru/TROUBLESHOOTING.html">Русский</a> | 🇨🇳<a href="./zh-CN/TROUBLESHOOTING.html">中文 (简体)</a> | 🇩🇪 <a href="./de/TROUBLESHOOTING.html">Deutsch</a> | 🇮🇳 <a href="./in/TROUBLESHOOTING.html">हिन्दी</a> | 🇹🇭 <a href="./th/TROUBLESHOOTING.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/TROUBLESHOOTING.html">Українська</a> | 🇸🇦 <a href="./ar/TROUBLESHOOTING.html">العربية</a> | 🇯🇵<a href="./ja/TROUBLESHOOTING.html">日本語</a>| 🇻🇳 <a href="./vi/TROUBLESHOOTING.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/TROUBLESHOOTING.html">Български</a> | 🇩🇰 <a href="./da/TROUBLESHOOTING.html">Dánsko</a> | 🇫🇮 <a href="./fi/TROUBLESHOOTING.html">Suomi</a> | 🇮🇱 <a href="./he/TROUBLESHOOTING.html">עברית</a> | 🇭🇺 <a href="./hu/TROUBLESHOOTING.html">maďarština</a> | 🇮🇩 <a href="./id/TROUBLESHOOTING.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/TROUBLESHOOTING.html">한국어</a> | 🇲🇾 <a href="./ms/TROUBLESHOOTING.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/TROUBLESHOOTING.html">Nizozemsko</a> | 🇳🇴 <a href="./no/TROUBLESHOOTING.html">Norsk</a> | 🇵🇹 <a href="./pt/TROUBLESHOOTING.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/TROUBLESHOOTING.html">Română</a> | 🇵🇱 <a href="./pl/TROUBLESHOOTING.html">Polski</a> | 🇸🇰 <a href="./sk/TROUBLESHOOTING.html">Slovenčina</a> | 🇸🇪 <a href="./sv/TROUBLESHOOTING.html">Svenska</a> | 🇵🇭 <a href="./phi/TROUBLESHOOTING.html">Filipínec</a></li>
<li><strong>USER_GUIDE.md</strong> : 🇺🇸 <a href="../USER_GUIDE.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/USER_GUIDE.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/USER_GUIDE.html">Español</a> | 🇫🇷 <a href="./fr/USER_GUIDE.html">Français</a> | 🇮🇹 <a href="./it/USER_GUIDE.html">Italiano</a> | 🇷🇺 <a href="./ru/USER_GUIDE.html">Русский</a> | 🇨🇳<a href="./zh-CN/USER_GUIDE.html">中文 (简体)</a> | 🇩🇪 <a href="./de/USER_GUIDE.html">Deutsch</a> | 🇮🇳 <a href="./in/USER_GUIDE.html">हिन्दी</a> | 🇹🇭 <a href="./th/USER_GUIDE.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/USER_GUIDE.html">Українська</a> | 🇸🇦 <a href="./ar/USER_GUIDE.html">العربية</a> | 🇯🇵<a href="./ja/USER_GUIDE.html">日本語</a>| 🇻🇳 <a href="./vi/USER_GUIDE.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/USER_GUIDE.html">Български</a> | 🇩🇰 <a href="./da/USER_GUIDE.html">Dánsko</a> | 🇫🇮 <a href="./fi/USER_GUIDE.html">Suomi</a> | 🇮🇱 <a href="./he/USER_GUIDE.html">עברית</a> | 🇭🇺 <a href="./hu/USER_GUIDE.html">maďarština</a> | 🇮🇩 <a href="./id/USER_GUIDE.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/USER_GUIDE.html">한국어</a> | 🇲🇾 <a href="./ms/USER_GUIDE.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/USER_GUIDE.html">Nizozemsko</a> | 🇳🇴 <a href="./no/USER_GUIDE.html">Norsk</a> | 🇵🇹 <a href="./pt/USER_GUIDE.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/USER_GUIDE.html">Română</a> | 🇵🇱 <a href="./pl/USER_GUIDE.html">Polski</a> | 🇸🇰 <a href="./sk/USER_GUIDE.html">Slovenčina</a> | 🇸🇪 <a href="./sv/USER_GUIDE.html">Svenska</a> | 🇵🇭 <a href="./phi/USER_GUIDE.html">Filipínec</a></li>
</ul>
<h2 id="nedávná-poznámka-zásady-limitů-pro-účty-codex">Nedávná poznámka: Zásady limitů pro účty Codex</h2>
<p>Dokumentace nyní zahrnuje chování zásad kvót na úrovni účtu Codex:</p>
<ul>
<li>Přepínání pro jednotlivé účty: <code>5h</code> a <code>Weekly</code> (ZAP/VYP).</li>
<li>Zásady prahových hodnot: povolené okno dosahující &gt;=90 % označuje účet jako nezpůsobilý k výběru.</li>
<li>Automatická rotace: provoz se přesune na další způsobilý účet Codex.</li>
<li>Automatické opětovné použití: účet se opět stane způsobilým po úspěšném <code>resetAt</code> poskytovatele.</li>
</ul>
<p>Vygenerováno 26. února 2026.</p>
</body>
</html>
+20 -22
View File
@@ -94,36 +94,32 @@ _Tilslut ethvert AI-drevet IDE- eller CLI-værktøj gennem OmniRoute - gratis AP
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
## 🤔 Hvorfor OmniRoute?
**Stop med at spilde penge og nå grænser:**
@@ -942,6 +938,8 @@ OmniRoute v2.0 er bygget som en operationel platform, ikke kun en relæ-proxy.
| 🧙 **Onboarding Wizard** | Første kørsel guidet opsætning |
| 🔧 **CLI Tools Dashboard** | Et-klik opsætning til populære kodningsværktøjer |
| 🌐 **i18n (30 sprog)** | Fuldt dashboard + understøttelse af docs-sprog med RTL-dækning |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Tilpasset datakatalog** | `DATA_DIR` tilsidesættelse af lagerplacering |
### Feature Deep Dive
+20 -22
View File
@@ -94,36 +94,32 @@ _Verbinden Sie jedes KI-gestützte IDE- oder CLI-Tool über OmniRoute kosten
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
## 🤔 Warum OmniRoute?
**Hören Sie auf, Geld zu verschwenden und an Grenzen zu stoßen:**
@@ -946,6 +942,8 @@ OmniRoute v2.0 ist als Betriebsplattform konzipiert und nicht nur als Relay-Prox
| 🧙 **Onboarding-Assistent** | Erstmaliges geführtes Setup |
| 🔧 **CLI-Tools-Dashboard** | Ein-Klick-Setup für beliebte Codierungstools |
| 🌐 **i18n (30 Sprachen)** | Vollständige Sprachunterstützung für Dashboard und Dokumente mit RTL-Abdeckung |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Benutzerdefiniertes Datenverzeichnis** | `DATA_DIR`-Überschreibung für Speicherort |
### Feature Deep Dive
+20 -22
View File
@@ -98,36 +98,32 @@ _Yhdistä mikä tahansa tekoälyllä toimiva IDE- tai CLI-työkalu OmniRouten ka
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -938,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **DB-varmuuskopiot** | Automaattinen varmuuskopiointi, palautus, vienti ja tuonti kaikille asetuksille |
| 🌐 **Kansainvälistyminen** | Täysi i18n next-intl:llä — Englanti + portugali (Brasilia) tuki |
| 🌍 **Kielenvalitsin** | Maapallokuvake otsikossa reaaliaikaista kielenvaihtoa varten (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Muokattu tietohakemisto** | `DATA_DIR` env var ohittaa oletusarvoisen `~/.omniroute`-tallennuspolun |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _חבר כל כלי IDE או CLI המופעל על ידי AI דרך OmniRoute -
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -937,6 +933,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **גיבויים של DB** | גיבוי, שחזור, ייצוא וייבוא ​​אוטומטי עבור כל ההגדרות |
| 🌐 **בינלאומיות** | i18n מלא עם next-intl — תמיכה באנגלית + פורטוגזית (ברזיל) |
| 🌍 **בורר שפה** | סמל גלובוס בכותרת למעבר שפה בזמן אמת (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **ספריית נתונים מותאמים אישית** | `DATA_DIR` env var כדי לעקוף את ברירת המחדל `~/.omniroute` נתיב אחסון |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Csatlakoztasson bármilyen mesterséges intelligencia-alapú IDE-t vagy CLI-esz
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -938,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **DB biztonsági mentések** | Automatikus biztonsági mentés, visszaállítás, exportálás és importálás az összes beállításhoz |
| 🌐 **Nemzetközivé válás** | Teljes i18n next-intl-vel angol + portugál (Brazília) támogatás |
| 🌍 **Nyelvválasztó** | Globe ikon a fejlécben a valós idejű nyelvváltáshoz (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Egyéni adattár** | `DATA_DIR` env var felülírja az alapértelmezett `~/.omniroute` tárolási útvonalat |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Hubungkan alat IDE atau CLI apa pun yang didukung AI melalui OmniRoute — gerb
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -938,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **Cadangan DB** | Pencadangan, pemulihan, ekspor & impor otomatis untuk semua pengaturan |
| 🌐 **Internasionalisasi** | I18n lengkap dengan next-intl — Dukungan Inggris + Portugis (Brasil) |
| 🌍 **Pemilih Bahasa** | Ikon bola dunia di header untuk peralihan bahasa secara real-time (🇮🇩/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Direktori Data Khusus** | `DATA_DIR` env var untuk mengganti jalur penyimpanan `~/.omniroute` default |
<details>
+20 -22
View File
@@ -23,36 +23,32 @@ _OmniRoute के माध्यम से किसी भी AI-संचा
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -834,6 +830,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **डीबी बैकअप** | सभी सेटिंग्स के लिए स्वचालित बैकअप, पुनर्स्थापना, निर्यात और आयात |
| 🌐 **अंतर्राष्ट्रीयकरण** | नेक्स्ट-इंटल के साथ पूर्ण i18n - अंग्रेजी + पुर्तगाली (ब्राजील) समर्थन |
| 🌍 **भाषा चयनकर्ता** | रीयल-टाइम भाषा स्विचिंग के लिए हेडर में ग्लोब आइकन (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **कस्टम डेटा निर्देशिका** | `DATA_DIR` env var डिफ़ॉल्ट `~/.omniroute` संग्रहण पथ को ओवरराइड करने के लिए |
<summary><b>📖 सुविधा विवरण</b></summary>
+20 -22
View File
@@ -98,36 +98,32 @@ _AI を活用した IDE または CLI ツールを、無制限のコーディン
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -939,6 +935,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **DB バックアップ** | すべての設定の自動バックアップ、復元、エクスポートとインポート |
| 🌐 **国際化** | next-intl を備えた完全な i18n — 英語 + ポルトガル語 (ブラジル) のサポート |
| 🌍 **言語セレクター** | リアルタイム言語切り替え用のヘッダーの地球儀アイコン (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **カスタム データ ディレクトリ** | デフォルトの `~/.omniroute` ストレージ パスをオーバーライドする `DATA_DIR` 環境変数 |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _무제한 코딩을 위한 무료 API 게이트웨이인 OmniRoute를 통해 AI
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -937,6 +933,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **DB 백업** | 모든 설정에 대한 자동 백업, 복원, 내보내기 및 가져오기 |
| 🌐 **국제화** | next-intl이 포함된 전체 i18n — 영어 + 포르투갈어(브라질) 지원 |
| 🌍 **언어 선택기** | 실시간 언어 전환을 위한 헤더의 지구본 아이콘(🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **사용자 정의 데이터 디렉터리** | `DATA_DIR` env var는 기본 `~/.omniroute` 저장 경로를 재정의합니다 |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Sambungkan mana-mana alat IDE atau CLI berkuasa AI melalui OmniRoute — get la
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -938,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **Sandaran DB** | Sandaran automatik, pulihkan, eksport & import untuk semua tetapan |
| 🌐 **Pengantarabangsaan** | i18n penuh dengan next-intl — sokongan Inggeris + Portugis (Brazil) |
| 🌍 **Pemilih Bahasa** | Ikon glob dalam pengepala untuk penukaran bahasa masa nyata (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Direktori Data Tersuai** | `DATA_DIR` env var untuk mengatasi laluan storan lalai `~/.omniroute` |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Verbind elke AI-aangedreven IDE- of CLI-tool via OmniRoute: gratis API-gateway
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -937,6 +933,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **DB-back-ups** | Automatische back-up, herstel, export en import voor alle instellingen |
| 🌐 **Internationalisering** | Volledige i18n met next-intl — Engels + Portugees (Brazilië) ondersteuning |
| 🌍 **Taalkiezer** | Wereldbolpictogram in koptekst voor realtime taalwisseling (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Aangepaste gegevensmap** | `DATA_DIR` env var om standaard `~/.omniroute` opslagpad te overschrijven |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Koble til ethvert AI-drevet IDE- eller CLI-verktøy gjennom OmniRoute grati
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -938,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **DB-sikkerhetskopier** | Automatisk sikkerhetskopiering, gjenoppretting, eksport og import for alle innstillinger |
| 🌐 **Internasjonalisering** | Full i18n med neste-intl — støtte for engelsk + portugisisk (Brasil) |
| 🌍 **Språkvelger** | Globusikon i overskriften for sanntids språkbytte (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Tilpasset datakatalog** | `DATA_DIR` env var for å overstyre standard `~/.omniroute` lagringsbane |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Ikonekta ang anumang AI-powered IDE o CLI tool sa pamamagitan ng OmniRoute —
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -939,6 +935,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **Mga Backup ng DB** | Awtomatikong pag-backup, pagpapanumbalik, pag-export at pag-import para sa lahat ng mga setting |
| 🌐 **Internasyonalisasyon** | Buong i18n na may next-intl — suporta sa English + Portuguese (Brazil) |
| 🌍 **Pili ng Wika** | Globe icon sa header para sa real-time na paglipat ng wika (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Custom na Direktoryo ng Data** | `DATA_DIR` env var to override default `~/.omniroute` storage path |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Połącz dowolne narzędzie IDE lub CLI oparte na sztucznej inteligencji poprze
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -937,6 +933,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **Kopie zapasowe DB** | Automatyczne tworzenie kopii zapasowych, przywracanie, eksportowanie i importowanie wszystkich ustawień |
| 🌐 **Internacjonalizacja** | Pełny i18n z next-intl — obsługa języka angielskiego i portugalskiego (Brazylia) |
| 🌍 **Wybór języka** | Ikona kuli ziemskiej w nagłówku umożliwiająca zmianę języka w czasie rzeczywistym (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Niestandardowy katalog danych** | `DATA_DIR` env var, aby zastąpić domyślną ścieżkę przechowywania `~/.omniroute` |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Conecte qualquer IDE ou ferramenta CLI com IA através do OmniRoute — gateway
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -948,6 +944,8 @@ Por que isso é relevante:
| 🔄 **Backups de DB** | Backup, restauração, exportação e importação automática de todas as configurações |
| 🌐 **Internacionalização** | i18n completo com next-intl — suporte a 30 idiomas com RTL |
| 🌍 **Seletor de Idioma** | Ícone de globo no cabeçalho para troca entre 30 idiomas em tempo real |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Diretório de Dados Custom** | Variável `DATA_DIR` para sobrescrever o caminho padrão `~/.omniroute` |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Conecte qualquer ferramenta IDE ou CLI com tecnologia de IA por meio do OmniRou
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -938,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **Backups de banco de dados** | Backup, restauração, exportação e importação automáticos para todas as configurações |
| 🌐 **Internacionalização** | i18n completo com next-intl — Suporte Inglês + Português (Brasil) |
| 🌍 **Seletor de idioma** | Ícone de globo no cabeçalho para troca de idioma em tempo real (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Diretório de dados personalizado** | `DATA_DIR` env var para substituir o caminho de armazenamento padrão `~/.omniroute` |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Conectați orice instrument IDE sau CLI alimentat de AI prin OmniRoute — gate
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -940,6 +936,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **Backup-uri DB** | Backup automat, restaurare, export și import pentru toate setările |
| 🌐 **Internaționalizare** | I18n complet cu next-intl — suport engleză + portugheză (Brazilia) |
| 🌍 **Selector de limbă** | Pictograma glob în antet pentru schimbarea limbii în timp real (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Director de date personalizate** | `DATA_DIR` env var pentru a înlocui calea de stocare implicită `~/.omniroute` |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Pripojte akýkoľvek nástroj IDE alebo CLI poháňaný AI cez OmniRoute be
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -941,6 +937,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **Zálohy DB** | Automatické zálohovanie, obnovenie, export a import všetkých nastavení |
| 🌐 **Internacionalizácia** | Plný i18n s next-intl — podpora angličtiny + portugalčiny (Brazília) |
| 🌍 **Výber jazyka** | Ikona zemegule v hlavičke na prepínanie jazyka v reálnom čase (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Custom Data Directory** | `DATA_DIR` env var na prepísanie predvolenej cesty úložiska `~/.omniroute` |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Anslut alla AI-drivna IDE- eller CLI-verktyg via OmniRoute — gratis API-gatew
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -939,6 +935,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **DB-säkerhetskopior** | Automatisk säkerhetskopiering, återställning, export och import för alla inställningar |
| 🌐 **Internationalisering** | Fullständig i18n med nästa-intl — stöd för engelska + portugisiska (Brasilien) |
| 🌍 **Språkväljare** | Globikon i rubriken för språkväxling i realtid (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Anpassad datakatalog** | `DATA_DIR` env var för att åsidosätta standard `~/.omniroute` lagringssökväg |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _เชื่อมต่อเครื่องมือ IDE หรือ CLI
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -938,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **การสำรองฐานข้อมูล** | สำรองข้อมูล กู้คืน ส่งออก & นำเข้าอัตโนมัติสำหรับการตั้งค่าทั้งหมด |
| 🌐 **ความเป็นสากล** | i18n เต็มรูปแบบพร้อม next-intl — รองรับภาษาอังกฤษ + โปรตุเกส (บราซิล) |
| 🌍 **ตัวเลือกภาษา** | ไอคอนลูกโลกในส่วนหัวสำหรับการสลับภาษาแบบเรียลไทม์ (USA/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **ไดเรกทอรีข้อมูลที่กำหนดเอง** | `DATA_DIR` env var เพื่อแทนที่ค่าเริ่มต้น `~/.omniroute` พาธหน่วยเก็บข้อมูล |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Підключіть будь-який інструмент IDE або CLI на
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -942,6 +938,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **Резервне копіювання БД** | Автоматичне резервне копіювання, відновлення, експорт і імпорт для всіх налаштувань |
| 🌐 **Інтернаціоналізація** | Повний i18n із next-intl — підтримка англійської та португальської (Бразилія) |
| 🌍 **Вибір мови** | Значок глобуса в заголовку для перемикання мов у реальному часі (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Каталог користувацьких даних** | `DATA_DIR` змінна env для перевизначення типового шляху зберігання `~/.omniroute` |
<details>
+20 -22
View File
@@ -98,36 +98,32 @@ _Kết nối mọi công cụ IDE hoặc CLI được hỗ trợ bởi AI thông
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
### 🆕 What's New in v3.0.0
| Area | Change |
| --- | --- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| Area | Change |
| -------------------------- | --------------------------------------------------------------------------------- |
| 🔒 **CodeQL Security** | Fixed 10+ CodeQL alerts: polynomial-redos, insecure-randomness, shell-injection |
| ✅ **Route Validation** | All 176 API routes validated with Zod schemas + `validateBody()` |
| 🐛 **omniModel Tag Leak** | Internal `<omniModel>` tags no longer leak to clients in SSE streams (#585) |
| 🔑 **Registered Keys API** | Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
| 🎨 **Provider Icons** | 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback |
| 🔄 **Model Auto-Sync** | 24h scheduler refreshes model lists for 16 providers |
| 🌐 **OpenCode Zen/Go** | Two new providers: free tier + subscription tier |
| 🔧 **926 Tests** | Full test suite passes with 0 failures |
---
[![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute)
[![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute)
[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
@@ -938,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
| 🔄 **Sao lưu DB** | Tự động sao lưu, khôi phục, xuất và nhập cho tất cả cài đặt |
| 🌐 **Quốc tế hóa** | I18n đầy đủ với hỗ trợ next-intl — Tiếng Anh + Tiếng Bồ Đào Nha (Brazil) |
| 🌍 **Bộ chọn ngôn ngữ** | Biểu tượng quả địa cầu trong tiêu đề để chuyển đổi ngôn ngữ theo thời gian thực (🇺🇸/🇧🇷) |
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
| 📂 **Thư mục dữ liệu tùy chỉnh** | `DATA_DIR` env var để ghi đè đường dẫn lưu trữ `~/.omniroute` mặc định |
<details>
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: OmniRoute API
version: 3.0.2
version: 3.2.2
description: |
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
endpoint that routes requests to multiple AI providers with load balancing,
+2
View File
@@ -18,6 +18,7 @@ const nextConfig = {
"thread-stream",
"better-sqlite3",
"keytar",
"wreq-js",
"zod",
"child_process",
"fs",
@@ -72,6 +73,7 @@ const nextConfig = {
const KNOWN_EXTERNALS = new Set([
"better-sqlite3",
"keytar",
"wreq-js",
"zod",
"pino",
"pino-pretty",
+11 -2
View File
@@ -1,12 +1,12 @@
import { loadProviderCredentials } from "./credentialLoader.ts";
// Timeout for non-streaming fetch requests (ms). Prevents stalled connections.
export const FETCH_TIMEOUT_MS = parseInt(process.env.FETCH_TIMEOUT_MS || "120000", 10);
export const FETCH_TIMEOUT_MS = parseInt(process.env.FETCH_TIMEOUT_MS || "600000", 10);
// Idle timeout for SSE streams (ms). Closes stream if no data for this duration.
// Default: 120s balances deep-reasoning pauses with fast zombie stream detection (#473).
// Extended-thinking models rarely pause >90s between chunks. Override with STREAM_IDLE_TIMEOUT_MS env var.
export const STREAM_IDLE_TIMEOUT_MS = parseInt(process.env.STREAM_IDLE_TIMEOUT_MS || "120000", 10);
export const STREAM_IDLE_TIMEOUT_MS = parseInt(process.env.STREAM_IDLE_TIMEOUT_MS || "600000", 10);
// Provider configurations
// OAuth credentials read from env vars with hardcoded fallbacks for backward compatibility.
@@ -66,6 +66,15 @@ export const DEFAULT_MAX_TOKENS = 64000;
// Minimum max tokens for tool calling (to prevent truncated arguments)
export const DEFAULT_MIN_TOKENS = 32000;
export const PROVIDER_MAX_TOKENS: Record<string, number> = {
groq: 16384, // Groq strict per-model enforcement
openai: 16384, // GPT-4/4o standard
anthropic: 65536, // Claude models
gemini: 65536, // Gemini Studio
};
export const DEFAULT_PROVIDER_MAX_TOKENS = 32000;
// HTTP status codes
export const HTTP_STATUS = {
BAD_REQUEST: 400,
+14 -9
View File
@@ -291,7 +291,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
alias: "qw",
format: "openai",
executor: "default",
baseUrl: "https://portal.qwen.ai/v1/chat/completions",
baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
authType: "oauth",
authHeader: "bearer",
headers: {
@@ -386,16 +386,23 @@ export const REGISTRY: Record<string, RegistryEntry> = {
clientIdEnv: "ANTIGRAVITY_OAUTH_CLIENT_ID",
clientIdDefault: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
clientSecretEnv: "ANTIGRAVITY_OAUTH_CLIENT_SECRET",
clientSecretDefault: "",
clientSecretDefault: "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",
},
models: [
{ id: "claude-opus-4-6-thinking", name: "Claude Opus 4.6 Thinking" },
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" },
{ id: "claude-sonnet-4", name: "Claude Sonnet 4" },
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
{ id: "gpt-oss-120b-medium", name: "GPT OSS 120B Medium" },
{ id: "gpt-5", name: "GPT 5" },
{ id: "gpt-5-mini", name: "GPT 5 Mini" },
],
passthroughModels: true,
},
github: {
@@ -576,6 +583,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
format: "openai",
executor: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
modelsUrl: "https://opencode.ai/zen/v1/models",
authType: "apikey",
authHeader: "Authorization",
authPrefix: "Bearer",
@@ -618,6 +626,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14",
},
models: [
{ id: "glm-5.1", name: "GLM 5.1" },
{ id: "glm-5", name: "GLM 5" },
{ id: "glm-5-turbo", name: "GLM 5 Turbo" },
{ id: "glm-4.7-flash", name: "GLM 4.7 Flash" },
@@ -627,7 +636,6 @@ export const REGISTRY: Record<string, RegistryEntry> = {
{ id: "glm-4.5v", name: "GLM 4.5V (Vision)" },
{ id: "glm-4.5", name: "GLM 4.5" },
{ id: "glm-4.5-air", name: "GLM 4.5 Air" },
{ id: "glm-4-32b", name: "GLM 4 32B" },
],
},
@@ -1035,8 +1043,8 @@ export const REGISTRY: Record<string, RegistryEntry> = {
alias: "ollamacloud",
format: "openai",
executor: "default",
baseUrl: "https://api.ollama.com/v1/chat/completions",
modelsUrl: "https://api.ollama.com/v1/models",
baseUrl: "https://ollama.com/v1/chat/completions",
modelsUrl: "https://ollama.com/api/tags",
authType: "apikey",
authHeader: "bearer",
// Note: rate limits vary by plan (free = "Light usage", Pro = more, Max = 5x Pro).
@@ -1275,10 +1283,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
alias: "lc",
format: "openai",
executor: "default",
// (#536) Correct OpenAI-compatible base URL — was longcat.chat/api/v1/chat/completions
// which is the chat endpoint directly, not the base. Key validation and routing must
// use https://api.longcat.chat/openai which resolves /v1/models and /v1/chat/completions
baseUrl: "https://api.longcat.chat/openai",
baseUrl: "https://api.longcat.chat/openai/v1/chat/completions",
authType: "apikey",
authHeader: "Authorization",
authPrefix: "Bearer",
+518 -137
View File
@@ -14,10 +14,16 @@ import { createRequestLogger } from "../utils/requestLogger.ts";
import { getModelTargetFormat, PROVIDER_ID_TO_ALIAS } from "../config/providerModels.ts";
import { resolveModelAlias } from "../services/modelDeprecation.ts";
import { getUnsupportedParams } from "../config/providerRegistry.ts";
import { createErrorResult, parseUpstreamError, formatProviderError } from "../utils/error.ts";
import { HTTP_STATUS } from "../config/constants.ts";
import {
buildErrorBody,
createErrorResult,
parseUpstreamError,
formatProviderError,
} from "../utils/error.ts";
import { HTTP_STATUS, PROVIDER_MAX_TOKENS } from "../config/constants.ts";
import { classifyProviderError, PROVIDER_ERROR_TYPES } from "../services/errorClassifier.ts";
import { updateProviderConnection } from "@/lib/db/providers";
import { isDetailedLoggingEnabled, saveRequestDetailLog } from "@/lib/db/detailedLogs";
import { logAuditEvent } from "@/lib/compliance";
import { handleBypassRequest } from "../utils/bypassHandler.ts";
import {
@@ -26,12 +32,17 @@ import {
appendRequestLog,
saveCallLog,
} from "@/lib/usageDb";
import { getLoggedInputTokens, getLoggedOutputTokens } from "@/lib/usage/tokenAccounting";
import { recordCost } from "@/domain/costRules";
import { calculateCost } from "@/lib/usage/costCalculator";
import { CLAUDE_OAUTH_TOOL_PREFIX } from "../translator/request/openai-to-claude.ts";
import {
getModelNormalizeToolCallId,
getModelPreserveOpenAIDeveloperRole,
getModelUpstreamExtraHeaders,
} from "@/lib/localDb";
import { getExecutor } from "../executors/index.ts";
import {
parseCodexQuotaHeaders,
getCodexResetTime,
@@ -67,6 +78,8 @@ import {
EMERGENCY_FALLBACK_CONFIG,
} from "../services/emergencyFallback.ts";
import { resolveStreamFlag, stripMarkdownCodeFence } from "../utils/aiSdkCompat.ts";
import { generateRequestId } from "@/shared/utils/requestId";
import { normalizePayloadForLog } from "@/lib/logPayloads";
export function shouldUseNativeCodexPassthrough({
provider,
@@ -84,6 +97,202 @@ export function shouldUseNativeCodexPassthrough({
return segments.includes("responses");
}
function buildClaudePassthroughToolNameMap(body: Record<string, unknown> | null | undefined) {
if (!body || !Array.isArray(body.tools)) return null;
const toolNameMap = new Map<string, string>();
for (const tool of body.tools) {
const toolRecord = tool as Record<string, unknown>;
const toolData =
toolRecord?.type === "function" &&
toolRecord.function &&
typeof toolRecord.function === "object"
? (toolRecord.function as Record<string, unknown>)
: toolRecord;
const originalName = typeof toolData?.name === "string" ? toolData.name.trim() : "";
if (!originalName) continue;
toolNameMap.set(`${CLAUDE_OAUTH_TOOL_PREFIX}${originalName}`, originalName);
}
return toolNameMap.size > 0 ? toolNameMap : null;
}
function restoreClaudePassthroughToolNames(
responseBody: Record<string, unknown>,
toolNameMap: Map<string, string> | null
) {
if (!toolNameMap || !Array.isArray(responseBody?.content)) return responseBody;
let changed = false;
const content = responseBody.content.map((block: Record<string, unknown>) => {
if (block?.type !== "tool_use" || typeof block?.name !== "string") return block;
const restoredName = toolNameMap.get(block.name) ?? block.name;
if (restoredName === block.name) return block;
changed = true;
return {
...block,
name: restoredName,
};
});
if (!changed) return responseBody;
return {
...responseBody,
content,
};
}
function getHeaderValueCaseInsensitive(
headers: Record<string, unknown> | null | undefined,
targetName: string
) {
if (!headers || typeof headers !== "object") return null;
const lowered = targetName.toLowerCase();
for (const [key, value] of Object.entries(headers)) {
if (key.toLowerCase() === lowered && typeof value === "string" && value.trim()) {
return value.trim();
}
}
return null;
}
function buildClaudePromptCacheLogMeta(
targetFormat: string,
finalBody: Record<string, unknown> | null | undefined,
providerHeaders: Record<string, unknown> | null | undefined
) {
if (targetFormat !== FORMATS.CLAUDE || !finalBody || typeof finalBody !== "object") return null;
const describeCacheControl = (cacheControl: Record<string, unknown> | undefined, extra = {}) => ({
type:
cacheControl && typeof cacheControl.type === "string" && cacheControl.type.trim()
? cacheControl.type.trim()
: "ephemeral",
ttl:
cacheControl && typeof cacheControl.ttl === "string" && cacheControl.ttl.trim()
? cacheControl.ttl.trim()
: null,
...extra,
});
const systemBreakpoints = Array.isArray(finalBody.system)
? finalBody.system.flatMap((block, index) => {
if (!block || typeof block !== "object") return [];
const cacheControl =
block.cache_control && typeof block.cache_control === "object"
? block.cache_control
: null;
return cacheControl ? [describeCacheControl(cacheControl, { index })] : [];
})
: [];
const toolBreakpoints = Array.isArray(finalBody.tools)
? finalBody.tools.flatMap((tool, index) => {
if (!tool || typeof tool !== "object") return [];
const cacheControl =
tool.cache_control && typeof tool.cache_control === "object" ? tool.cache_control : null;
const name = typeof tool.name === "string" && tool.name.trim() ? tool.name.trim() : null;
return cacheControl ? [describeCacheControl(cacheControl, { index, name })] : [];
})
: [];
const messageBreakpoints = Array.isArray(finalBody.messages)
? finalBody.messages.flatMap((message, messageIndex) => {
if (!message || typeof message !== "object" || !Array.isArray(message.content)) return [];
const role =
typeof message.role === "string" && message.role.trim() ? message.role.trim() : "unknown";
return message.content.flatMap((block, contentIndex) => {
if (!block || typeof block !== "object") return [];
const cacheControl =
block.cache_control && typeof block.cache_control === "object"
? block.cache_control
: null;
if (!cacheControl) return [];
return [
describeCacheControl(cacheControl, {
messageIndex,
contentIndex,
role,
blockType:
typeof block.type === "string" && block.type.trim() ? block.type.trim() : "unknown",
}),
];
});
})
: [];
const totalBreakpoints =
systemBreakpoints.length + toolBreakpoints.length + messageBreakpoints.length;
const anthropicBeta = getHeaderValueCaseInsensitive(providerHeaders, "Anthropic-Beta");
if (totalBreakpoints === 0 && !anthropicBeta) return null;
return {
applied: totalBreakpoints > 0,
totalBreakpoints,
anthropicBeta,
systemBreakpoints,
toolBreakpoints,
messageBreakpoints,
};
}
function toPositiveNumber(value: unknown) {
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
}
function buildCacheUsageLogMeta(usage: Record<string, unknown> | null | undefined) {
if (!usage || typeof usage !== "object") return null;
const promptTokenDetails =
usage.prompt_tokens_details && typeof usage.prompt_tokens_details === "object"
? (usage.prompt_tokens_details as Record<string, unknown>)
: undefined;
const hasCacheFields =
"cache_read_input_tokens" in usage ||
"cached_tokens" in usage ||
"cache_creation_input_tokens" in usage ||
(!!promptTokenDetails &&
("cached_tokens" in promptTokenDetails || "cache_creation_tokens" in promptTokenDetails));
const cacheReadTokens = toPositiveNumber(
usage.cache_read_input_tokens ?? usage.cached_tokens ?? promptTokenDetails?.cached_tokens
);
const cacheCreationTokens = toPositiveNumber(
usage.cache_creation_input_tokens ?? promptTokenDetails?.cache_creation_tokens
);
if (!hasCacheFields) return null;
return {
cacheReadTokens,
cacheCreationTokens,
};
}
function attachLogMeta(
payload: Record<string, unknown> | null | undefined,
meta: Record<string, unknown> | null | undefined
) {
if (!meta || typeof meta !== "object") return payload;
const compactMeta = Object.fromEntries(
Object.entries(meta).filter(([, value]) => value !== null && value !== undefined)
);
if (Object.keys(compactMeta).length === 0) return payload;
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
return { _omniroute: compactMeta, _payload: payload ?? null };
}
const existing =
payload._omniroute &&
typeof payload._omniroute === "object" &&
!Array.isArray(payload._omniroute)
? payload._omniroute
: {};
return {
...payload,
_omniroute: {
...existing,
...compactMeta,
},
};
}
/**
* Core chat handler - shared between SSE and Worker
* Returns { success, response, status, error } for caller to handle fallback
@@ -190,7 +399,8 @@ export async function handleChatCore({
credentials.providerSpecificData = nextProviderData;
} catch (err) {
log?.debug?.("CODEX", `Failed to persist codex quota state: ${err?.message || err}`);
const errMessage = err instanceof Error ? err.message : String(err);
log?.debug?.("CODEX", `Failed to persist codex quota state: ${errMessage}`);
}
};
@@ -285,6 +495,88 @@ export async function handleChatCore({
const alias = PROVIDER_ID_TO_ALIAS[provider] || provider;
const modelTargetFormat = getModelTargetFormat(alias, resolvedModel);
const targetFormat = modelTargetFormat || getTargetFormat(provider);
const noLogEnabled = apiKeyInfo?.noLog === true;
const detailedLoggingEnabled = !noLogEnabled && (await isDetailedLoggingEnabled());
const persistAttemptLogs = ({
status,
tokens,
responseBody,
error,
providerRequest,
providerResponse,
clientResponse,
claudeCacheMeta,
claudeCacheUsageMeta,
}: {
status: number;
tokens?: unknown;
responseBody?: unknown;
error?: string | null;
providerRequest?: unknown;
providerResponse?: unknown;
clientResponse?: unknown;
claudeCacheMeta?: any;
claudeCacheUsageMeta?: any;
}) => {
const callLogId = generateRequestId();
saveCallLog({
id: callLogId,
method: "POST",
path: clientRawRequest?.endpoint || "/v1/chat/completions",
status,
model,
requestedModel,
provider,
connectionId,
duration: Date.now() - startTime,
tokens: tokens || {},
requestBody: attachLogMeta(body, {
claudePromptCache: claudeCacheMeta,
}),
responseBody: attachLogMeta(responseBody ?? undefined, {
claudePromptCache: claudeCacheMeta
? {
applied: claudeCacheMeta.applied,
totalBreakpoints: claudeCacheMeta.totalBreakpoints,
anthropicBeta: claudeCacheMeta.anthropicBeta,
}
: null,
claudePromptCacheUsage: claudeCacheUsageMeta,
}),
error: error || null,
sourceFormat,
targetFormat,
comboName,
apiKeyId: apiKeyInfo?.id || null,
apiKeyName: apiKeyInfo?.name || null,
noLog: noLogEnabled,
}).catch(() => {});
if (!detailedLoggingEnabled) {
return;
}
try {
saveRequestDetailLog({
call_log_id: callLogId,
client_request: clientRawRequest?.body ?? body,
translated_request: providerRequest ?? null,
provider_response: providerResponse ?? null,
client_response: clientResponse ?? null,
provider,
model,
source_format: sourceFormat,
target_format: targetFormat,
duration_ms: Date.now() - startTime,
api_key_id: apiKeyInfo?.id || null,
no_log: noLogEnabled,
});
} catch (err) {
const errMessage = err instanceof Error ? err.message : String(err);
log?.debug?.("DETAIL_LOG", `Failed to save detailed log: ${errMessage}`);
}
};
// Primary path: merge client model id + alias target so config on either key applies; resolved
// id wins on same header name. T5 family fallback uses only (nextModel, resolveModelAlias(next))
@@ -347,6 +639,38 @@ export async function handleChatCore({
log?.debug?.("FORMAT", `${sourceFormat}${targetFormat} | stream=${stream}`);
// ── Common input sanitization (runs for ALL paths including passthrough) ──
// #291: Strip empty name fields from messages/input items
// Upstream providers (OpenAI, Codex) reject name:"" with 400 errors.
if (Array.isArray(body.messages)) {
body.messages = body.messages.map((msg: Record<string, unknown>) => {
if (msg.name === "") {
const { name: _n, ...rest } = msg;
return rest;
}
return msg;
});
}
if (Array.isArray(body.input)) {
body.input = body.input.map((item: Record<string, unknown>) => {
if (item.name === "") {
const { name: _n, ...rest } = item;
return rest;
}
return item;
});
}
// #346/#637: Strip tools with empty name
// Clients sometimes forward tool definitions with empty names, causing
// upstream providers to reject with 400 "Invalid 'tools[0].name': empty string."
if (Array.isArray(body.tools)) {
body.tools = body.tools.filter((tool: Record<string, unknown>) => {
const fn = tool.function as Record<string, unknown> | undefined;
const name = fn?.name ?? tool.name;
return name && String(name).trim().length > 0;
});
}
// Translate request (pass reqLogger for intermediate logging)
let translatedBody = body;
const isClaudePassthrough = sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE;
@@ -383,7 +707,7 @@ export async function handleChatCore({
FORMATS.OPENAI,
FORMATS.CLAUDE,
model,
translatedBody,
{ ...translatedBody, _disableToolPrefix: true },
stream,
credentials,
provider,
@@ -394,47 +718,15 @@ export async function handleChatCore({
} else {
translatedBody = { ...body };
// Issue #199: Disable tool name prefix when routing Claude-format requests
// to non-Claude backends (prefix causes tool name mismatches)
const claudeProviders = ["claude", "anthropic"];
if (targetFormat === FORMATS.CLAUDE && !claudeProviders.includes(provider?.toLowerCase?.())) {
// Issue #199 + #618: Always disable tool name prefix in Claude passthrough.
// The proxy_ prefix was designed for OpenAI→Claude translation to avoid
// conflicts with Claude OAuth tools, but in the passthrough path the tools
// are already in Claude format. Applying the prefix turns "Bash" into
// "proxy_Bash", which Claude rejects ("No such tool available: proxy_Bash").
if (targetFormat === FORMATS.CLAUDE) {
translatedBody._disableToolPrefix = true;
}
// ── #291: Strip empty name fields from messages/input items ──
// Upstream providers (OpenAI, Codex) reject name:"" with 400 errors.
// Clients like PocketPaw may forward empty name fields from assistant turns.
if (Array.isArray(translatedBody.messages)) {
translatedBody.messages = translatedBody.messages.map((msg: Record<string, unknown>) => {
if (msg.name === "") {
const { name: _n, ...rest } = msg;
return rest;
}
return msg;
});
}
if (Array.isArray(translatedBody.input)) {
translatedBody.input = translatedBody.input.map((item: Record<string, unknown>) => {
if (item.name === "") {
const { name: _n, ...rest } = item;
return rest;
}
return item;
});
}
// ── #346: Strip tools with empty name ──
// Claude Code sometimes forwards tool definitions with empty names, causing
// OpenAI-compatible upstream providers to reject with:
// "Invalid 'input[N].name': empty string. Expected minimum length 1."
// Handles both OpenAI format ({ function: { name } }) and Anthropic format ({ name }).
if (Array.isArray(translatedBody.tools)) {
translatedBody.tools = translatedBody.tools.filter((tool: Record<string, unknown>) => {
const fn = tool.function as Record<string, unknown> | undefined;
const name = fn?.name ?? tool.name;
return name && String(name).trim().length > 0;
});
}
// Strip empty text content blocks from messages.
// Anthropic API rejects {"type":"text","text":""} with 400 "text content blocks must be non-empty".
// Some clients (LiteLLM passthrough, @ai-sdk/anthropic) may forward these empty blocks as-is.
@@ -566,7 +858,14 @@ export async function handleChatCore({
}
// Extract toolNameMap for response translation (Claude OAuth)
const toolNameMap = translatedBody._toolNameMap;
const translatedToolNameMap = translatedBody._toolNameMap;
const nativeClaudeToolNameMap = isClaudePassthrough
? buildClaudePassthroughToolNameMap(body)
: null;
const toolNameMap =
translatedToolNameMap instanceof Map && translatedToolNameMap.size > 0
? translatedToolNameMap
: nativeClaudeToolNameMap;
delete translatedBody._toolNameMap;
delete translatedBody._disableToolPrefix;
@@ -588,6 +887,22 @@ export async function handleChatCore({
}
}
// Provider-specific max_tokens caps (#711)
// Some providers reject requests when max_tokens exceeds their API limit.
// Cap before sending to avoid upstream HTTP 400 errors.
const providerCap = PROVIDER_MAX_TOKENS[provider];
if (providerCap) {
for (const field of ["max_tokens", "max_completion_tokens"] as const) {
if (typeof translatedBody[field] === "number" && translatedBody[field] > providerCap) {
log?.debug?.(
"PARAMS",
`Capping ${field} from ${translatedBody[field]} to ${providerCap} for ${provider}`
);
translatedBody[field] = providerCap;
}
}
}
// Get executor for this provider
const executor = getExecutor(provider);
const getExecutionCredentials = () =>
@@ -667,6 +982,7 @@ export async function handleChatCore({
let providerUrl;
let providerHeaders;
let finalBody;
let claudePromptCacheLogMeta = null;
try {
const result = await executeProviderRequest(effectiveModel, true);
@@ -675,6 +991,11 @@ export async function handleChatCore({
providerUrl = result.url;
providerHeaders = result.headers;
finalBody = result.transformedBody;
claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta(
targetFormat,
finalBody,
providerHeaders
);
// Log target request (final request to provider)
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
@@ -689,38 +1010,34 @@ export async function handleChatCore({
);
} catch (error) {
trackPendingRequest(model, provider, connectionId, false);
const failureStatus = error.name === "AbortError" ? 499 : HTTP_STATUS.BAD_GATEWAY;
const failureMessage =
error.name === "AbortError"
? "Request aborted"
: formatProviderError(error, provider, model, HTTP_STATUS.BAD_GATEWAY);
appendRequestLog({
model,
provider,
connectionId,
status: `FAILED ${error.name === "AbortError" ? 499 : HTTP_STATUS.BAD_GATEWAY}`,
}).catch(() => {});
saveCallLog({
method: "POST",
path: clientRawRequest?.endpoint || "/v1/chat/completions",
status: error.name === "AbortError" ? 499 : HTTP_STATUS.BAD_GATEWAY,
model,
requestedModel,
provider,
connectionId,
duration: Date.now() - startTime,
requestBody: body,
error: error.message,
sourceFormat,
targetFormat,
comboName,
apiKeyId: apiKeyInfo?.id || null,
apiKeyName: apiKeyInfo?.name || null,
noLog: apiKeyInfo?.noLog === true,
status: `FAILED ${failureStatus}`,
}).catch(() => {});
persistAttemptLogs({
status: failureStatus,
error: failureMessage,
providerRequest: finalBody || translatedBody,
clientResponse: buildErrorBody(failureStatus, failureMessage),
claudeCacheMeta: claudePromptCacheLogMeta,
});
if (error.name === "AbortError") {
streamController.handleError(error);
return createErrorResult(499, "Request aborted");
}
persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, error?.name || "upstream_error");
const errMsg = formatProviderError(error, provider, model, HTTP_STATUS.BAD_GATEWAY);
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, errMsg);
persistFailureUsage(
HTTP_STATUS.BAD_GATEWAY,
error instanceof Error && error.name ? error.name : "upstream_error"
);
console.log(`${COLORS.red}[ERROR] ${failureMessage}${COLORS.reset}`);
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, failureMessage);
}
// Handle 401/403 - try token refresh using executor
@@ -766,8 +1083,11 @@ export async function handleChatCore({
if (retryResult.response.ok) {
providerResponse = retryResult.response;
providerUrl = retryResult.url;
providerHeaders = retryResult.headers;
finalBody = retryResult.transformedBody;
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
}
} catch (retryError) {
} catch {
log?.warn?.("TOKEN", `${provider.toUpperCase()} | retry after refresh failed`);
}
} else {
@@ -780,10 +1100,12 @@ export async function handleChatCore({
// Check provider response - return error info for fallback handling
if (!providerResponse.ok) {
trackPendingRequest(model, provider, connectionId, false);
const { statusCode, message, retryAfterMs } = await parseUpstreamError(
providerResponse,
provider
);
const {
statusCode,
message,
retryAfterMs,
responseBody: upstreamErrorBody,
} = await parseUpstreamError(providerResponse, provider);
// T06/T10/T36: classify provider errors and persist terminal account states.
const errorType = classifyProviderError(statusCode, message);
@@ -835,24 +1157,7 @@ export async function handleChatCore({
appendRequestLog({ model, provider, connectionId, status: `FAILED ${statusCode}` }).catch(
() => {}
);
saveCallLog({
method: "POST",
path: clientRawRequest?.endpoint || "/v1/chat/completions",
status: statusCode,
model,
requestedModel,
provider,
connectionId,
duration: Date.now() - startTime,
requestBody: body,
error: message,
sourceFormat,
targetFormat,
comboName,
apiKeyId: apiKeyInfo?.id || null,
apiKeyName: apiKeyInfo?.name || null,
noLog: apiKeyInfo?.noLog === true,
}).catch(() => {});
const errMsg = formatProviderError(new Error(message), provider, model, statusCode);
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
@@ -864,6 +1169,12 @@ export async function handleChatCore({
// Log error with full request body for debugging
reqLogger.logError(new Error(message), finalBody || translatedBody);
reqLogger.logProviderResponse(
providerResponse.status,
providerResponse.statusText,
providerResponse.headers,
upstreamErrorBody
);
// Update rate limiter from error response headers
updateFromHeaders(provider, connectionId, providerResponse.headers, statusCode, model);
@@ -887,24 +1198,53 @@ export async function handleChatCore({
providerUrl = fallbackResult.url;
providerHeaders = fallbackResult.headers;
finalBody = fallbackResult.transformedBody;
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
// Continue processing with the fallback response — skip error return
log?.info?.("MODEL_FALLBACK", `Serving ${nextModel} as fallback for ${model}`);
// Jump to streaming/non-streaming handling below
// We fall through by NOT returning here
} else {
// Fallback also failed — return original error
persistAttemptLogs({
status: statusCode,
error: errMsg,
providerRequest: finalBody || translatedBody,
providerResponse: upstreamErrorBody,
clientResponse: buildErrorBody(statusCode, errMsg),
});
persistFailureUsage(statusCode, "model_unavailable");
return createErrorResult(statusCode, errMsg, retryAfterMs);
}
} catch {
persistAttemptLogs({
status: statusCode,
error: errMsg,
providerRequest: finalBody || translatedBody,
providerResponse: upstreamErrorBody,
clientResponse: buildErrorBody(statusCode, errMsg),
});
persistFailureUsage(statusCode, "model_unavailable");
return createErrorResult(statusCode, errMsg, retryAfterMs);
}
} else {
persistAttemptLogs({
status: statusCode,
error: errMsg,
providerRequest: finalBody || translatedBody,
providerResponse: upstreamErrorBody,
clientResponse: buildErrorBody(statusCode, errMsg),
});
persistFailureUsage(statusCode, "model_unavailable");
return createErrorResult(statusCode, errMsg, retryAfterMs);
}
} else {
persistAttemptLogs({
status: statusCode,
error: errMsg,
providerRequest: finalBody || translatedBody,
providerResponse: upstreamErrorBody,
clientResponse: buildErrorBody(statusCode, errMsg),
});
persistFailureUsage(statusCode, `upstream_${statusCode}`);
return createErrorResult(statusCode, errMsg, retryAfterMs);
}
@@ -949,6 +1289,10 @@ export async function handleChatCore({
});
if (fbResult.response.ok) {
providerResponse = fbResult.response;
providerUrl = fbResult.url;
providerHeaders = fbResult.headers;
finalBody = fbResult.transformedBody;
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
log?.info?.(
"EMERGENCY_FALLBACK",
`Serving ${fbDecision.provider}/${fbDecision.model} as budget fallback for ${provider}/${model}`
@@ -961,7 +1305,8 @@ export async function handleChatCore({
);
}
} catch (fbErr) {
log?.warn?.("EMERGENCY_FALLBACK", `Emergency fallback error: ${fbErr?.message}`);
const errMessage = fbErr instanceof Error ? fbErr.message : String(fbErr);
log?.warn?.("EMERGENCY_FALLBACK", `Emergency fallback error: ${errMessage}`);
}
}
}
@@ -974,6 +1319,7 @@ export async function handleChatCore({
const contentType = (providerResponse.headers.get("content-type") || "").toLowerCase();
let responseBody;
const rawBody = await providerResponse.text();
const normalizedProviderPayload = normalizePayloadForLog(rawBody);
const looksLikeSSE =
contentType.includes("text/event-stream") || /(^|\n)\s*(event|data):/m.test(rawBody);
@@ -991,11 +1337,16 @@ export async function handleChatCore({
connectionId,
status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`,
}).catch(() => {});
const invalidSseMessage = "Invalid SSE response for non-streaming request";
persistAttemptLogs({
status: HTTP_STATUS.BAD_GATEWAY,
error: invalidSseMessage,
providerRequest: finalBody || translatedBody,
providerResponse: normalizedProviderPayload,
clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, invalidSseMessage),
});
persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "invalid_sse_payload");
return createErrorResult(
HTTP_STATUS.BAD_GATEWAY,
"Invalid SSE response for non-streaming request"
);
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, invalidSseMessage);
}
responseBody = parsedFromSSE;
@@ -1009,11 +1360,35 @@ export async function handleChatCore({
connectionId,
status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`,
}).catch(() => {});
const invalidJsonMessage = "Invalid JSON response from provider";
persistAttemptLogs({
status: HTTP_STATUS.BAD_GATEWAY,
error: invalidJsonMessage,
providerRequest: finalBody || translatedBody,
providerResponse: normalizedProviderPayload,
clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, invalidJsonMessage),
});
persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "invalid_json_payload");
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, "Invalid JSON response from provider");
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, invalidJsonMessage);
}
}
if (sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE) {
responseBody = restoreClaudePassthroughToolNames(responseBody, toolNameMap);
}
reqLogger.logProviderResponse(
providerResponse.status,
providerResponse.statusText,
providerResponse.headers,
looksLikeSSE
? {
_streamed: true,
_format: "sse-json",
summary: responseBody,
}
: responseBody
);
// Notify success - caller can clear error status if needed
if (onRequestSuccess) {
await onRequestSuccess();
@@ -1026,27 +1401,9 @@ export async function handleChatCore({
);
// Save structured call log with full payloads
saveCallLog({
method: "POST",
path: clientRawRequest?.endpoint || "/v1/chat/completions",
status: 200,
model,
requestedModel,
provider,
connectionId,
duration: Date.now() - startTime,
tokens: usage,
requestBody: body,
responseBody,
sourceFormat,
targetFormat,
comboName,
apiKeyId: apiKeyInfo?.id || null,
apiKeyName: apiKeyInfo?.name || null,
noLog: apiKeyInfo?.noLog === true,
}).catch(() => {});
const cacheUsageLogMeta = buildCacheUsageLogMeta(usage);
if (usage && typeof usage === "object") {
const msg = `[${new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" })}] 📊 [USAGE] ${provider.toUpperCase()} | in=${usage?.prompt_tokens || 0} | out=${usage?.completion_tokens || 0}${connectionId ? ` | account=${connectionId.slice(0, 8)}...` : ""}`;
const msg = `[${new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" })}] 📊 [USAGE] ${provider.toUpperCase()} | in=${getLoggedInputTokens(usage)} | out=${getLoggedOutputTokens(usage)}${connectionId ? ` | account=${connectionId.slice(0, 8)}...` : ""}`;
console.log(`${COLORS.green}${msg}${COLORS.reset}`);
saveRequestUsage({
@@ -1067,6 +1424,11 @@ export async function handleChatCore({
});
}
if (apiKeyInfo?.id && usage) {
const estimatedCost = await calculateCost(provider, model, usage);
if (estimatedCost > 0) recordCost(apiKeyInfo.id, estimatedCost);
}
// Translate response to client's expected format (usually OpenAI)
// Pass toolNameMap so Claude OAuth proxy_ prefix is stripped in tool_use blocks (#605)
let translatedResponse = needsTranslation(targetFormat, sourceFormat)
@@ -1132,6 +1494,23 @@ export async function handleChatCore({
// ── Phase 9.2: Save for idempotency ──
saveIdempotency(idempotencyKey, translatedResponse, 200);
reqLogger.logConvertedResponse(translatedResponse);
persistAttemptLogs({
status: 200,
tokens: usage,
responseBody,
providerRequest: finalBody || translatedBody,
providerResponse: looksLikeSSE
? {
_streamed: true,
_format: "sse-json",
summary: responseBody,
}
: responseBody,
clientResponse: translatedResponse,
claudeCacheMeta: claudePromptCacheLogMeta,
claudeCacheUsageMeta: cacheUsageLogMeta,
});
return {
success: true,
@@ -1167,42 +1546,43 @@ export async function handleChatCore({
status: streamStatus,
usage: streamUsage,
responseBody: streamResponseBody,
providerPayload,
clientPayload,
}) => {
saveCallLog({
method: "POST",
path: clientRawRequest?.endpoint || "/v1/chat/completions",
const cacheUsageLogMeta = buildCacheUsageLogMeta(streamUsage);
persistAttemptLogs({
status: streamStatus || 200,
model,
requestedModel,
provider,
connectionId,
duration: Date.now() - startTime,
tokens: streamUsage || {},
requestBody: body,
responseBody: streamResponseBody ?? undefined,
sourceFormat,
targetFormat,
comboName,
apiKeyId: apiKeyInfo?.id || null,
apiKeyName: apiKeyInfo?.name || null,
noLog: apiKeyInfo?.noLog === true,
}).catch(() => {});
providerRequest: finalBody || translatedBody,
providerResponse: providerPayload,
clientResponse: clientPayload ?? streamResponseBody ?? undefined,
claudeCacheMeta: claudePromptCacheLogMeta,
claudeCacheUsageMeta: cacheUsageLogMeta,
});
if (apiKeyInfo?.id && streamUsage) {
calculateCost(provider, model, streamUsage)
.then((estimatedCost) => {
if (estimatedCost > 0) recordCost(apiKeyInfo.id, estimatedCost);
})
.catch(() => {});
}
};
// For Codex provider, translate response from openai-responses to openai (Chat Completions) format
// For providers using Responses API format, translate stream back to openai (Chat Completions) format
// UNLESS client is Droid CLI which expects openai-responses format back
const isDroidCLI =
userAgent?.toLowerCase().includes("droid") || userAgent?.toLowerCase().includes("codex-cli");
const needsCodexTranslation =
provider === "codex" &&
const needsResponsesTranslation =
targetFormat === FORMATS.OPENAI_RESPONSES &&
sourceFormat === FORMATS.OPENAI &&
!isResponsesEndpoint &&
!isDroidCLI;
if (needsCodexTranslation) {
// Codex returns openai-responses, translate to openai (Chat Completions) that clients expect
log?.debug?.("STREAM", `Codex translation mode: openai-responses → openai`);
if (needsResponsesTranslation) {
// Provider returns openai-responses, translate to openai (Chat Completions) that clients expect
log?.debug?.("STREAM", `Responses translation mode: openai-responses → openai`);
transformStream = createSSETransformStreamWithLogger(
"openai-responses",
"openai",
@@ -1235,6 +1615,7 @@ export async function handleChatCore({
transformStream = createPassthroughStreamWithLogger(
provider,
reqLogger,
toolNameMap,
model,
connectionId,
body,
+83 -2
View File
@@ -36,6 +36,13 @@ function toNumber(value: unknown): number | undefined {
// Matches <think>...</think> blocks (greedy, dotAll)
const THINK_TAG_REGEX = /<think>([\s\S]*?)<\/think>/gi;
// #638: Collapse runs of 3+ consecutive newlines into \n\n
// Tool call responses from thinking models often accumulate excessive newlines
const EXCESSIVE_NEWLINES = /\n{3,}/g;
function collapseExcessiveNewlines(text: string): string {
return text.replace(EXCESSIVE_NEWLINES, "\n\n");
}
/**
* Extract <think> blocks from text content and return separated parts.
* @returns {{ content: string, thinking: string | null }}
@@ -157,7 +164,7 @@ function sanitizeMessage(msg: unknown): unknown {
// Handle content — extract <think> tags
if (typeof msgRecord.content === "string") {
const { content, thinking } = extractThinkingFromContent(msgRecord.content);
sanitized.content = content;
sanitized.content = collapseExcessiveNewlines(content);
// Set reasoning_content from <think> tags (if not already set)
if (thinking && !msgRecord.reasoning_content) {
@@ -172,6 +179,44 @@ function sanitizeMessage(msg: unknown): unknown {
sanitized.reasoning_content = msgRecord.reasoning_content;
}
// Handle 'reasoning' field alias (some providers use this instead of reasoning_content)
if (
msgRecord.reasoning &&
typeof msgRecord.reasoning === "string" &&
!sanitized.reasoning_content
) {
sanitized.reasoning_content = msgRecord.reasoning;
}
// Handle reasoning_details[] array (StepFun/OpenRouter format)
// Structure: [{ type: "reasoning.text", text: "...", format: "unknown", index: 0 }]
if (Array.isArray(msgRecord.reasoning_details) && !sanitized.reasoning_content) {
const reasoningParts: string[] = [];
for (const detail of msgRecord.reasoning_details) {
const detailObj = detail && typeof detail === "object" ? (detail as JsonRecord) : null;
if (!detailObj) continue;
const detailType = typeof detailObj.type === "string" ? detailObj.type : "";
const detailText =
typeof detailObj.text === "string"
? detailObj.text
: typeof detailObj.content === "string"
? detailObj.content
: "";
if (
detailText &&
(detailType === "reasoning" ||
detailType === "reasoning.text" ||
detailType === "thinking" ||
detailType === "")
) {
reasoningParts.push(detailText);
}
}
if (reasoningParts.length > 0) {
sanitized.reasoning_content = reasoningParts.join("");
}
}
// Preserve tool_calls
if (msgRecord.tool_calls) {
sanitized.tool_calls = msgRecord.tool_calls;
@@ -193,6 +238,17 @@ function sanitizeUsage(usage: unknown): unknown {
if (!usageRecord) return usage;
const sanitized: JsonRecord = {};
// Cross-map Claude-style → OpenAI-style field names.
// Some providers return input_tokens/output_tokens instead of prompt_tokens/completion_tokens.
// Without this mapping, the whitelist filter below strips them, resulting in NaN/0 tokens (#617).
if (usageRecord.input_tokens !== undefined && usageRecord.prompt_tokens === undefined) {
usageRecord.prompt_tokens = usageRecord.input_tokens;
}
if (usageRecord.output_tokens !== undefined && usageRecord.completion_tokens === undefined) {
usageRecord.completion_tokens = usageRecord.output_tokens;
}
for (const key of ALLOWED_USAGE_FIELDS) {
if (usageRecord[key] !== undefined) {
sanitized[key] = usageRecord[key];
@@ -255,9 +311,34 @@ export function sanitizeStreamingChunk(parsed: unknown): unknown {
if (deltaRecord) {
const delta: JsonRecord = {};
if (deltaRecord.role !== undefined) delta.role = deltaRecord.role;
if (deltaRecord.content !== undefined) delta.content = deltaRecord.content;
if (deltaRecord.content !== undefined) {
delta.content =
typeof deltaRecord.content === "string"
? collapseExcessiveNewlines(deltaRecord.content)
: deltaRecord.content;
}
if (deltaRecord.reasoning_content !== undefined) {
delta.reasoning_content = deltaRecord.reasoning_content;
} else if (typeof deltaRecord.reasoning === "string" && deltaRecord.reasoning) {
// Alias: some providers use 'reasoning' instead of 'reasoning_content'
delta.reasoning_content = deltaRecord.reasoning;
} else if (Array.isArray(deltaRecord.reasoning_details)) {
// StepFun/OpenRouter: reasoning_details[{type:"reasoning.text", text:"..."}]
const parts: string[] = [];
for (const detail of deltaRecord.reasoning_details) {
const d = detail && typeof detail === "object" ? (detail as JsonRecord) : null;
if (!d) continue;
const text =
typeof d.text === "string"
? d.text
: typeof d.content === "string"
? d.content
: "";
if (text) parts.push(text);
}
if (parts.length > 0) {
delta.reasoning_content = parts.join("");
}
}
if (deltaRecord.tool_calls !== undefined) delta.tool_calls = deltaRecord.tool_calls;
if (deltaRecord.function_call !== undefined)
+1 -1
View File
@@ -36,8 +36,8 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
let usage = null;
const getToolCallKey = (toolCall: Record<string, unknown>) => {
if (toolCall?.id) return `id:${toolCall.id}`;
if (Number.isInteger(toolCall?.index)) return `idx:${toolCall.index}`;
if (toolCall?.id) return `id:${toolCall.id}`;
unknownToolCallSeq += 1;
return `seq:${unknownToolCallSeq}`;
};
+4 -1
View File
@@ -29,7 +29,10 @@ export function extractUsageFromResponse(responseBody, provider) {
return {
prompt_tokens: responsesUsage.input_tokens || 0,
completion_tokens: responsesUsage.output_tokens || 0,
cached_tokens: responsesUsage.cache_read_input_tokens,
cache_read_input_tokens: responsesUsage.cache_read_input_tokens,
cached_tokens:
responsesUsage.input_tokens_details?.cached_tokens ??
responsesUsage.cache_read_input_tokens,
cache_creation_input_tokens: responsesUsage.cache_creation_input_tokens,
reasoning_tokens:
responsesUsage.reasoning_tokens || responsesUsage.output_tokens_details?.reasoning_tokens,
+1 -1
View File
@@ -439,7 +439,7 @@ async function handleListModelsCatalog(args: { provider?: string; capability?: s
if (args.provider && !args.capability) {
// Use direct provider fetch to get real-time API status
path = `/api/providers/${encodeURIComponent(args.provider)}/models`;
path = `/api/providers/${encodeURIComponent(args.provider)}/models?excludeHidden=true`;
isProviderSpecific = true;
} else {
const params = new URLSearchParams();
+4 -1
View File
@@ -36,7 +36,8 @@ const FITNESS_TABLE: Record<string, Record<string, number>> = {
"grok-3": 0.8,
// Kimi K2.5 — agentic with tool calling, good at code tasks
"kimi-k2": 0.82,
// GLM-5 — Z.AI model with 128k output
// GLM-5.1 / GLM-5 — Z.AI reasoning models, 200K context / 128k output
"glm-5.1": 0.78,
"glm-5": 0.78,
// MiniMax M2.5 — reasoning support helps complex code
"minimax-m2.5": 0.75,
@@ -78,6 +79,7 @@ const FITNESS_TABLE: Record<string, Record<string, number>> = {
"deepseek-r1": 0.88,
"deepseek-chat": 0.8,
"kimi-k2": 0.82, // Kimi K2.5 agentic — good for analysis
"glm-5.1": 0.82, // GLM-5.1 free reasoning, 200K context for long analysis
"glm-5": 0.78, // GLM-5 with 128k output for long analysis
"minimax-m2.5": 0.76,
},
@@ -114,6 +116,7 @@ const FITNESS_TABLE: Record<string, Record<string, number>> = {
"grok-4": 0.74,
"grok-3": 0.73,
"kimi-k2": 0.76, // agentic multi-step tasks
"glm-5.1": 0.75,
"glm-5": 0.7,
"minimax-m2.5": 0.7,
},
+104 -19
View File
@@ -20,6 +20,15 @@ import { supportsToolCalling } from "./modelCapabilities.ts";
// Status codes that should mark semaphore + record circuit breaker failures
const TRANSIENT_FOR_BREAKER = [429, 502, 503, 504];
const COMBO_BAD_REQUEST_FALLBACK_PATTERNS = [
/\bprohibited_content\b/i,
/request blocked by .*api/i,
/provided message roles? is not valid/i,
/unsupported .*message role/i,
/no such tool available/i,
/unsupported content part type/i,
/tool(?:_call|_use)? .* not (?:available|found)/i,
];
const MAX_COMBO_DEPTH = 3;
@@ -258,6 +267,12 @@ function extractPromptForIntent(body) {
return "";
}
export function shouldFallbackComboBadRequest(status, errorText) {
if (status !== 400 || !errorText) return false;
const message = String(errorText);
return COMBO_BAD_REQUEST_FALLBACK_PATTERNS.some((pattern) => pattern.test(message));
}
function mapIntentToTaskType(intent) {
switch (intent) {
case "code":
@@ -449,14 +464,23 @@ export async function handleComboChat({
const res = await handleSingleModel(b, modelStr);
if (!res.ok) return res;
// Non-streaming: inject tag into JSON response (existing logic)
// Non-streaming: inject tag into JSON response
// Fix #721: Use OpenAI choices format (json.choices[0].message) not json.messages
if (!b.stream) {
try {
const json = await res.clone().json();
const msgs = Array.isArray(json?.messages) ? json.messages : [];
if (msgs.length > 0) {
const tagged = injectModelTag(msgs, modelStr);
return new Response(JSON.stringify({ ...json, messages: tagged }), {
const choice = json?.choices?.[0];
if (choice?.message) {
// Wrap single message in array for injectModelTag, then unwrap
const tagged = injectModelTag([choice.message], modelStr);
// If the message had tool_calls but no string content, injectModelTag
// appends a synthetic assistant message — use the last one
const taggedMsg = tagged[tagged.length - 1];
const updatedJson = {
...json,
choices: [{ ...choice, message: taggedMsg }, ...(json.choices?.slice(1) || [])],
};
return new Response(JSON.stringify(updatedJson), {
status: res.status,
headers: res.headers,
});
@@ -487,8 +511,9 @@ export async function handleComboChat({
const text = decoder.decode(chunk, { stream: true });
// Look for the first SSE data line with non-empty content
// Pattern: "content":"<non-empty>" — we inject tag at the start
// Fix #721: Look for either non-empty content OR tool_calls in the
// SSE data. Tool-call-only responses have content:null, so we inject
// the tag when we see a finish_reason approaching, or on first content.
const contentMatch = text.match(/"content":"([^"]+)/);
if (contentMatch) {
// Inject tag at the beginning of the first content value
@@ -501,6 +526,27 @@ export async function handleComboChat({
return;
}
// Fix #721: For tool-call-only streams, inject the tag when we see
// the finish_reason chunk (before it reaches the client SDK which
// would close the connection). This ensures the tag roundtrips
// through the conversation history even when there's no text content.
if (text.includes('"finish_reason"') && !text.includes('"finish_reason":null')) {
// Inject a content chunk with the tag just before this finish chunk
const tagChunk = `data: ${JSON.stringify({
choices: [
{
delta: { content: tagContent },
index: 0,
finish_reason: null,
},
],
})}\n\n`;
tagInjected = true;
controller.enqueue(encoder.encode(tagChunk));
controller.enqueue(chunk);
return;
}
// No content yet — passthrough
controller.enqueue(chunk);
},
@@ -526,18 +572,32 @@ export async function handleComboChat({
// visible content so they don't leak to the user. The tag is still
// present in the full response for round-trip context pinning, but
// we clean it from each SSE chunk's content field before delivery.
//
// IMPORTANT: Use a SEPARATE TextDecoder from the transform stream above.
// The transform stream's decoder accumulates UTF-8 state; reusing it here
// would corrupt multi-byte characters split across chunk boundaries.
const sanitizeDecoder = new TextDecoder();
const sanitize = new TransformStream({
transform(chunk, controller) {
const text = decoder.decode(chunk, { stream: true });
// Only run replacement if the chunk actually contains the tag
if (text.includes("<omniModel>")) {
const cleaned = text.replace(
/(?:\\\\n|\\n)?<omniModel>[^<]+<\/omniModel>(?:\\\\n|\\n)?/g,
""
);
controller.enqueue(encoder.encode(cleaned));
} else {
controller.enqueue(chunk);
const text = sanitizeDecoder.decode(chunk, { stream: true });
if (text) {
if (text.includes("<omniModel>")) {
const cleaned = text.replace(/\n?<omniModel>[^<]+<\/omniModel>\n?/g, "");
if (cleaned) controller.enqueue(encoder.encode(cleaned));
} else {
controller.enqueue(encoder.encode(text));
}
}
},
flush(controller) {
const tail = sanitizeDecoder.decode();
if (tail) {
if (tail.includes("<omniModel>")) {
const cleaned = tail.replace(/\n?<omniModel>[^<]+<\/omniModel>\n?/g, "");
if (cleaned) controller.enqueue(encoder.encode(cleaned));
} else {
controller.enqueue(encoder.encode(tail));
}
}
},
});
@@ -554,6 +614,15 @@ export async function handleComboChat({
: handleSingleModel;
// ─────────────────────────────────────────────────────────────────────────
// Route to pinned model if context caching specifies one (Fix #679)
if (pinnedModel) {
log.info(
"COMBO",
`Bypassing strategy — routing directly to pinned context model: ${pinnedModel}`
);
return handleSingleModelWrapped(body, pinnedModel);
}
// Route to round-robin handler if strategy matches
if (strategy === "round-robin") {
return handleRoundRobinCombo({
@@ -867,17 +936,25 @@ export async function handleComboChat({
provider,
result.headers
);
const comboBadRequestFallback = shouldFallbackComboBadRequest(result.status, errorText);
// Record failure in circuit breaker for transient errors
if (TRANSIENT_FOR_BREAKER.includes(result.status)) {
breaker._onFailure();
}
if (!shouldFallback) {
if (!shouldFallback && !comboBadRequestFallback) {
log.warn("COMBO", `Model ${modelStr} failed (no fallback)`, { status: result.status });
return result;
}
if (comboBadRequestFallback) {
log.info(
"COMBO",
`Treating provider-scoped 400 from ${modelStr} as model-local failure; trying next combo target`
);
}
// Check if this is a transient error worth retrying on same model
const isTransient = [408, 429, 500, 502, 503, 504].includes(result.status);
if (retry < maxRetries && isTransient) {
@@ -1123,6 +1200,7 @@ async function handleRoundRobinCombo({
provider,
result.headers
);
const comboBadRequestFallback = shouldFallbackComboBadRequest(result.status, errorText);
// Transient errors → mark in semaphore AND record circuit breaker failure
if (TRANSIENT_FOR_BREAKER.includes(result.status) && cooldownMs > 0) {
@@ -1134,11 +1212,18 @@ async function handleRoundRobinCombo({
);
}
if (!shouldFallback) {
if (!shouldFallback && !comboBadRequestFallback) {
log.warn("COMBO-RR", `${modelStr} failed (no fallback)`, { status: result.status });
return result;
}
if (comboBadRequestFallback) {
log.info(
"COMBO-RR",
`Treating provider-scoped 400 from ${modelStr} as model-local failure; trying next model`
);
}
// Transient error → retry same model
const isTransient = [408, 429, 500, 502, 503, 504].includes(result.status);
if (retry < maxRetries && isTransient) {
+11 -1
View File
@@ -67,7 +67,17 @@ export function injectModelTag(messages: Message[], providerModel: string): Mess
}
const msg = cleaned[lastAssistantIdx];
if (typeof msg.content !== "string") return cleaned;
// Fix #721: Handle messages where content is not a string (tool_calls responses).
// In this case, append a synthetic assistant message with the tag so the pin
// roundtrips through the conversation history.
if (typeof msg.content !== "string") {
// If the message has tool_calls but no string content, append a new assistant
// message with the tag rather than silently failing.
return [
...cleaned,
{ role: "assistant", content: `\n<omniModel>${providerModel}</omniModel>` },
];
}
const tagged = [...cleaned];
tagged[lastAssistantIdx] = {
+1 -1
View File
@@ -9,7 +9,7 @@ const DEFAULT_COMBO_CONFIG = {
strategy: "priority",
maxRetries: 1,
retryDelayMs: 2000,
timeoutMs: 120000,
timeoutMs: 600000,
concurrencyPerModel: 3, // max simultaneous requests per model (round-robin)
queueTimeoutMs: 30000, // max wait time in semaphore queue (round-robin)
healthCheckEnabled: true,
+5
View File
@@ -132,6 +132,11 @@ export function detectAndLearn(
}
}
// Collapse excessive consecutive newlines left after tag removal (fixes #626)
if (found.length > 0) {
cleaned = cleaned.replace(/\n{3,}/g, "\n\n");
}
return { found, cleaned: cleaned.trim() || cleaned };
}
+57 -2
View File
@@ -100,13 +100,66 @@ function shouldDisplayGitHubQuota(quota: UsageQuota | null): quota is UsageQuota
return quota.total > 0 || quota.remainingPercentage !== undefined;
}
// GLM (Z.AI) quota API config
const GLM_QUOTA_URLS: Record<string, string> = {
international: "https://api.z.ai/api/monitor/usage/quota/limit",
china: "https://open.bigmodel.cn/api/monitor/usage/quota/limit",
};
async function getGlmUsage(apiKey: string, providerSpecificData?: Record<string, unknown>) {
const region = providerSpecificData?.apiRegion || "international";
const quotaUrl = GLM_QUOTA_URLS[region] || GLM_QUOTA_URLS.international;
const res = await fetch(quotaUrl, {
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
},
});
if (!res.ok) {
if (res.status === 401) throw new Error("Invalid API key");
throw new Error(`GLM quota API error (${res.status})`);
}
const json = await res.json();
const data = toRecord(json.data);
const limits: unknown[] = Array.isArray(data.limits) ? data.limits : [];
const quotas: Record<string, UsageQuota> = {};
for (const limit of limits) {
const src = toRecord(limit);
if (src.type !== "TOKENS_LIMIT") continue;
const usedPercent = toNumber(src.percentage, 0);
const resetMs = toNumber(src.nextResetTime, 0);
const remaining = Math.max(0, 100 - usedPercent);
quotas["session"] = {
used: usedPercent,
total: 100,
remaining,
remainingPercentage: remaining,
resetAt: resetMs > 0 ? new Date(resetMs).toISOString() : null,
unlimited: false,
};
}
const levelRaw = typeof data.level === "string" ? data.level : "";
const plan = levelRaw
? levelRaw.charAt(0).toUpperCase() + levelRaw.slice(1).toLowerCase()
: "Unknown";
return { plan, quotas };
}
/**
* Get usage data for a provider connection
* @param {Object} connection - Provider connection with accessToken
* @returns {Promise<unknown>} Usage data with quotas
*/
export async function getUsageForProvider(connection) {
const { provider, accessToken, providerSpecificData } = connection;
const { provider, accessToken, apiKey, providerSpecificData } = connection;
switch (provider) {
case "github":
@@ -127,6 +180,8 @@ export async function getUsageForProvider(connection) {
return await getQwenUsage(accessToken, providerSpecificData);
case "iflow":
return await getIflowUsage(accessToken);
case "glm":
return await getGlmUsage(apiKey, providerSpecificData);
default:
return { message: `Usage API not implemented for ${provider}` };
}
@@ -856,7 +911,7 @@ async function getCodexUsage(accessToken, providerSpecificData: Record<string, u
quotas,
};
} catch (error) {
throw new Error(`Failed to fetch Codex usage: ${error.message}`);
return { message: `Failed to fetch Codex usage: ${(error as Error).message}` };
}
}
+46 -9
View File
@@ -86,14 +86,33 @@ export function fixToolUseOrdering(messages) {
return merged;
}
function ensureMessageContentArray(msg) {
if (Array.isArray(msg?.content)) return msg.content;
if (typeof msg?.content === "string" && msg.content.trim()) {
msg.content = [{ type: "text", text: msg.content }];
return msg.content;
}
return [];
}
function markMessageCacheControl(msg, ttl) {
const content = ensureMessageContentArray(msg);
if (content.length === 0) return false;
const lastIndex = content.length - 1;
content[lastIndex].cache_control =
ttl !== undefined ? { type: "ephemeral", ttl } : { type: "ephemeral" };
return true;
}
// Prepare request for Claude format endpoints
// - Cleanup cache_control
// - Cleanup cache_control (unless preserveCacheControl=true for passthrough)
// - Filter empty messages
// - Add thinking block for Anthropic endpoint (provider === "claude")
// - Fix tool_use/tool_result ordering
export function prepareClaudeRequest(body, provider = null) {
export function prepareClaudeRequest(body, provider = null, preserveCacheControl = false) {
// 1. System: remove all cache_control, add only to last block with ttl 1h
if (body.system && Array.isArray(body.system)) {
// In passthrough mode, preserve existing cache_control markers
if (body.system && Array.isArray(body.system) && !preserveCacheControl) {
body.system = body.system.map((block, i) => {
const { cache_control, ...rest } = block;
if (i === body.system.length - 1) {
@@ -109,11 +128,12 @@ export function prepareClaudeRequest(body, provider = null) {
let filtered = [];
// Pass 1: remove cache_control + filter empty messages
// In passthrough mode, preserve existing cache_control markers
for (let i = 0; i < len; i++) {
const msg = body.messages[i];
// Remove cache_control from content blocks
if (Array.isArray(msg.content)) {
// Remove cache_control from content blocks (skip in passthrough mode)
if (Array.isArray(msg.content) && !preserveCacheControl) {
for (const block of msg.content) {
delete block.cache_control;
}
@@ -156,15 +176,31 @@ export function prepareClaudeRequest(body, provider = null) {
const lastMessageIsUser = lastMessage?.role === "user";
const thinkingEnabled = body.thinking?.type === "enabled" && lastMessageIsUser;
// Claude Code-style prompt caching:
// - cache the second-to-last user turn for conversation reuse
// - cache the last assistant turn so the next user turn can reuse it
// Skip in passthrough mode to preserve client's cache_control markers
if (!preserveCacheControl) {
const userMessageIndexes = filtered.reduce((indexes, msg, index) => {
if (msg?.role === "user") indexes.push(index);
return indexes;
}, []);
const secondToLastUserIndex =
userMessageIndexes.length >= 2 ? userMessageIndexes[userMessageIndexes.length - 2] : -1;
if (secondToLastUserIndex >= 0) {
markMessageCacheControl(filtered[secondToLastUserIndex]);
}
}
// Pass 2 (reverse): add cache_control to last assistant + handle thinking for Anthropic
let lastAssistantProcessed = false;
for (let i = filtered.length - 1; i >= 0; i--) {
const msg = filtered[i];
if (msg.role === "assistant" && Array.isArray(msg.content)) {
if (msg.role === "assistant" && Array.isArray(ensureMessageContentArray(msg))) {
// Add cache_control to last block of first (from end) assistant with content
if (!lastAssistantProcessed && msg.content.length > 0) {
msg.content[msg.content.length - 1].cache_control = { type: "ephemeral" };
// Skip in passthrough mode to preserve client's cache_control markers
if (!preserveCacheControl && !lastAssistantProcessed && markMessageCacheControl(msg)) {
lastAssistantProcessed = true;
}
@@ -197,7 +233,8 @@ export function prepareClaudeRequest(body, provider = null) {
// 3. Tools: remove all cache_control, add only to last non-deferred tool with ttl 1h
// Tools with defer_loading=true cannot have cache_control (API rejects it)
if (body.tools && Array.isArray(body.tools)) {
// In passthrough mode, preserve existing cache_control markers
if (body.tools && Array.isArray(body.tools) && !preserveCacheControl) {
body.tools = body.tools.map((tool) => {
const { cache_control, ...rest } = tool;
return rest;
@@ -0,0 +1,207 @@
/**
* Shared sanitizers for tool payloads that arrive from IDEs/SDKs with
* JSON Schema numeric constraints encoded as strings or invalid descriptions.
*/
type JsonRecord = Record<string, unknown>;
const NUMERIC_SCHEMA_FIELDS = [
"minimum",
"maximum",
"exclusiveMinimum",
"exclusiveMaximum",
"minLength",
"maxLength",
"minItems",
"maxItems",
"minProperties",
"maxProperties",
"multipleOf",
] as const;
function isPlainObject(value: unknown): value is JsonRecord {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function coerceNumericString(value: unknown): unknown {
if (typeof value !== "string") return value;
const trimmed = value.trim();
if (trimmed.length === 0) return value;
const parsed = Number(trimmed);
return Number.isFinite(parsed) ? parsed : value;
}
function mapRecordValues(record: JsonRecord): JsonRecord {
return Object.fromEntries(
Object.entries(record).map(([key, value]) => [key, coerceSchemaNumericFields(value)])
);
}
function sanitizeDescriptionValue(value: unknown): string | undefined {
if (value === undefined) return undefined;
if (value === null) return "";
return typeof value === "string" ? value : String(value);
}
export function coerceSchemaNumericFields(schema: unknown): unknown {
if (Array.isArray(schema)) {
return schema.map((entry) => coerceSchemaNumericFields(entry));
}
if (!isPlainObject(schema)) return schema;
const result: JsonRecord = { ...schema };
for (const field of NUMERIC_SCHEMA_FIELDS) {
if (field in result) {
result[field] = coerceNumericString(result[field]);
}
}
if (isPlainObject(result.properties)) {
result.properties = mapRecordValues(result.properties);
}
if (isPlainObject(result.patternProperties)) {
result.patternProperties = mapRecordValues(result.patternProperties);
}
if (isPlainObject(result.definitions)) {
result.definitions = mapRecordValues(result.definitions);
}
if (isPlainObject(result.$defs)) {
result.$defs = mapRecordValues(result.$defs);
}
if (isPlainObject(result.dependentSchemas)) {
result.dependentSchemas = mapRecordValues(result.dependentSchemas);
}
if (result.items !== undefined) {
result.items = coerceSchemaNumericFields(result.items);
}
if (result.additionalProperties && typeof result.additionalProperties === "object") {
result.additionalProperties = coerceSchemaNumericFields(result.additionalProperties);
}
if (result.unevaluatedProperties && typeof result.unevaluatedProperties === "object") {
result.unevaluatedProperties = coerceSchemaNumericFields(result.unevaluatedProperties);
}
if (Array.isArray(result.prefixItems)) {
result.prefixItems = result.prefixItems.map((entry) => coerceSchemaNumericFields(entry));
}
if (Array.isArray(result.anyOf)) {
result.anyOf = result.anyOf.map((entry) => coerceSchemaNumericFields(entry));
}
if (Array.isArray(result.oneOf)) {
result.oneOf = result.oneOf.map((entry) => coerceSchemaNumericFields(entry));
}
if (Array.isArray(result.allOf)) {
result.allOf = result.allOf.map((entry) => coerceSchemaNumericFields(entry));
}
if (isPlainObject(result.not)) {
result.not = coerceSchemaNumericFields(result.not);
}
if (isPlainObject(result.if)) {
result.if = coerceSchemaNumericFields(result.if);
}
if (isPlainObject(result.then)) {
result.then = coerceSchemaNumericFields(result.then);
}
if (isPlainObject(result.else)) {
result.else = coerceSchemaNumericFields(result.else);
}
return result;
}
export function sanitizeToolDescription(tool: unknown): unknown {
if (!isPlainObject(tool)) return tool;
const result: JsonRecord = { ...tool };
if (isPlainObject(result.function) && "description" in result.function) {
const description = sanitizeDescriptionValue(result.function.description);
if (description !== undefined) {
result.function = { ...result.function, description };
}
}
if (!isPlainObject(result.function) && "description" in result) {
const description = sanitizeDescriptionValue(result.description);
if (description !== undefined) {
result.description = description;
}
}
if (Array.isArray(result.functionDeclarations)) {
result.functionDeclarations = result.functionDeclarations.map((declaration) => {
if (!isPlainObject(declaration) || !("description" in declaration)) return declaration;
const description = sanitizeDescriptionValue(declaration.description);
return description === undefined ? declaration : { ...declaration, description };
});
}
return result;
}
export function coerceToolSchemas(tools: unknown): unknown {
if (!Array.isArray(tools)) return tools;
return tools.map((tool) => {
if (!isPlainObject(tool)) return tool;
const result: JsonRecord = { ...tool };
if (isPlainObject(result.function) && "parameters" in result.function) {
result.function = {
...result.function,
parameters: coerceSchemaNumericFields(result.function.parameters),
};
}
if (result.input_schema !== undefined) {
result.input_schema = coerceSchemaNumericFields(result.input_schema);
}
if ("parameters" in result && !isPlainObject(result.function)) {
result.parameters = coerceSchemaNumericFields(result.parameters);
}
if (Array.isArray(result.functionDeclarations)) {
result.functionDeclarations = result.functionDeclarations.map((declaration) => {
if (!isPlainObject(declaration) || !("parameters" in declaration)) return declaration;
return {
...declaration,
parameters: coerceSchemaNumericFields(declaration.parameters),
};
});
}
return result;
});
}
export function sanitizeToolDescriptions(tools: unknown): unknown {
if (!Array.isArray(tools)) return tools;
return tools.map((tool) => sanitizeToolDescription(tool));
}
export function injectEmptyReasoningContentForToolCalls(
messages: unknown,
provider: unknown
): unknown {
if (!Array.isArray(messages) || String(provider || "").toLowerCase() !== "deepseek") {
return messages;
}
return messages.map((message) => {
if (!isPlainObject(message)) return message;
if (
message.role !== "assistant" ||
!Array.isArray(message.tool_calls) ||
message.tool_calls.length === 0 ||
message.reasoning_content !== undefined
) {
return message;
}
return { ...message, reasoning_content: "" };
});
}
+39 -1
View File
@@ -2,6 +2,11 @@ import { FORMATS } from "./formats.ts";
import { ensureToolCallIds, fixMissingToolResponses } from "./helpers/toolCallHelper.ts";
import { prepareClaudeRequest } from "./helpers/claudeHelper.ts";
import { filterToOpenAIFormat } from "./helpers/openaiHelper.ts";
import {
coerceToolSchemas,
injectEmptyReasoningContentForToolCalls,
sanitizeToolDescriptions,
} from "./helpers/schemaCoercion.ts";
import { getRequestTranslator, getResponseTranslator } from "./registry.ts";
import { bootstrapTranslatorRegistry } from "./bootstrap.ts";
import { normalizeThinkingConfig } from "../services/provider.ts";
@@ -144,8 +149,10 @@ export function translateRequest(
}
// Final step: prepare request for Claude format endpoints
// In Claude passthrough mode (Claude → Claude), preserve cache_control markers
if (targetFormat === FORMATS.CLAUDE) {
result = prepareClaudeRequest(result, provider);
const isClaudePassthrough = sourceFormat === FORMATS.CLAUDE;
result = prepareClaudeRequest(result, provider, isClaudePassthrough);
}
// Normalize openai-responses input shape for providers that require list input.
@@ -171,10 +178,41 @@ export function translateRequest(
);
}
if (result.tools !== undefined) {
result.tools = coerceToolSchemas(result.tools);
result.tools = sanitizeToolDescriptions(result.tools);
}
if (targetFormat === FORMATS.OPENAI && result.messages && Array.isArray(result.messages)) {
result.messages = injectEmptyReasoningContentForToolCalls(result.messages, provider);
}
// Ensure unique tool_call ids on final payload (translators may have introduced duplicates)
ensureToolCallIds(result, { use9CharId });
fixMissingToolResponses(result);
if (result.tools) {
result.tools = coerceToolSchemas(result.tools);
result.tools = sanitizeToolDescriptions(result.tools);
}
// Inject reasoning_content = "" for DeepSeek/Reasoning models assistant messages with tool_calls
// if omitted by the client, to avoid upstream 400 errors (e.g. "Messages with role 'assistant' that contain tool_calls must also include reasoning_content")
const isReasoner =
provider === "deepseek" || (typeof model === "string" && /r1|reason/i.test(model));
if (isReasoner && result.messages && Array.isArray(result.messages)) {
for (const msg of result.messages) {
if (
msg.role === "assistant" &&
Array.isArray(msg.tool_calls) &&
msg.tool_calls.length > 0 &&
msg.reasoning_content === undefined
) {
msg.reasoning_content = "";
}
}
}
return result;
}
@@ -261,8 +261,8 @@ export function openaiToClaudeRequest(model, body, stream) {
// Normalize input_schema: Anthropic requires `properties` when type is "object" (#595).
// MCP tools (e.g. pencil, computer_use) may omit properties on object-type schemas.
const rawSchema: Record<string, unknown> =
toolData.parameters || toolData.input_schema || { type: "object", properties: {}, required: [] };
const rawSchema: Record<string, unknown> = toolData.parameters ||
toolData.input_schema || { type: "object", properties: {}, required: [] };
const normalizedSchema =
rawSchema.type === "object" && !rawSchema.properties
? { ...rawSchema, properties: {} }
@@ -317,6 +317,33 @@ export function openaiToClaudeRequest(model, body, stream) {
...(body.thinking.budget_tokens && { budget_tokens: body.thinking.budget_tokens }),
...(body.thinking.max_tokens && { max_tokens: body.thinking.max_tokens }),
};
} else if (body.reasoning_effort) {
// Convert OpenAI reasoning_effort to Claude thinking format (#627)
// Clients like OpenCode send reasoning_effort via @ai-sdk/openai-compatible
const effortBudgetMap: Record<string, number> = {
low: 1024,
medium: 10240,
high: 131072,
max: 131072,
};
const effort = String(body.reasoning_effort).toLowerCase();
const budget = effortBudgetMap[effort];
if (budget !== undefined && budget > 0) {
result.thinking = {
type: "enabled",
budget_tokens: budget,
};
// Claude requires max_tokens > budget_tokens
if (result.max_tokens <= budget) {
result.max_tokens = budget + 8192;
}
}
}
// Ensure max_tokens > budget_tokens for all thinking configurations (#627)
const budgetTokens = Number(result.thinking?.budget_tokens) || 0;
if (budgetTokens > 0 && result.max_tokens <= budgetTokens) {
result.max_tokens = budgetTokens + 8192;
}
// Attach toolNameMap to result for response translation
@@ -167,8 +167,10 @@ function openaiToGeminiBase(model, body, stream) {
if (tc.type !== "function") continue;
const args = tryParseJSON(tc.function?.arguments || "{}");
// Do NOT include thoughtSignature on functionCall parts — it is only valid
// on thinking/reasoning parts and causes HTTP 400 "invalid argument" from the
// Gemini API when present on a functionCall part (#725).
parts.push({
thoughtSignature: DEFAULT_THINKING_GEMINI_SIGNATURE,
functionCall: {
id: tc.id,
name: tc.function.name,
@@ -90,7 +90,6 @@ export function claudeToOpenAIResponse(chunk, state) {
tool_calls: [
{
index: toolCall.index,
id: toolCall.id,
function: { arguments: delta.partial_json },
},
],
@@ -464,6 +464,9 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
}
// Function call arguments delta
// NOTE: Do NOT include `id` or `type` here - only first chunk (response.output_item.added)
// should have them. Including `id` on every chunk causes openai-to-claude.ts to emit
// a new content_block_start for each delta, breaking Claude Code ACP sessions.
if (eventType === "response.function_call_arguments.delta") {
const argsDelta = data.delta || "";
if (!argsDelta) return null;
@@ -480,8 +483,6 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
tool_calls: [
{
index: state.toolCallIndex,
id: state.currentToolCallId,
type: "function",
function: { arguments: argsDelta },
},
],
@@ -93,7 +93,18 @@ export function openaiToClaudeResponse(chunk, state) {
}
// Handle reasoning_content (thinking) - GLM, DeepSeek, etc.
const reasoningContent = delta?.reasoning_content || delta?.reasoning;
// Also supports 'reasoning' field alias and reasoning_details[] (StepFun/OpenRouter)
let reasoningContent = delta?.reasoning_content || delta?.reasoning;
if (!reasoningContent && Array.isArray(delta?.reasoning_details)) {
const parts: string[] = [];
for (const detail of delta.reasoning_details) {
if (detail && typeof detail === "object") {
const text = detail.text || detail.content;
if (typeof text === "string" && text) parts.push(text);
}
}
if (parts.length > 0) reasoningContent = parts.join("");
}
if (reasoningContent) {
stopTextBlock(state, results);
+9 -2
View File
@@ -13,10 +13,17 @@ export function clientWantsJsonResponse(acceptHeader: unknown): boolean {
/**
* Resolves stream behavior from request body + Accept header.
* OpenAI-compatible behavior: stream only when `stream: true` and client did not force JSON.
* Priority: explicit `stream: true/false` in body wins.
* Accept header only acts as fallback when stream is not explicitly set.
* Fixes #656: clients sending both `stream: true` and `Accept: application/json`
* should still get streaming responses body intent takes precedence.
*/
export function resolveStreamFlag(bodyStream: unknown, acceptHeader: unknown): boolean {
return bodyStream === true && !clientWantsJsonResponse(acceptHeader);
// Explicit body value always wins
if (bodyStream === true) return true;
if (bodyStream === false) return false;
// No explicit stream param — fall back to Accept header heuristic
return !clientWantsJsonResponse(acceptHeader);
}
/**
+6 -1
View File
@@ -1,5 +1,6 @@
import { getCorsOrigin } from "./cors.ts";
import { ERROR_TYPES, DEFAULT_ERROR_MESSAGES } from "../config/constants.ts";
import { normalizePayloadForLog } from "@/lib/logPayloads";
/**
* Build OpenAI-compatible error response body
@@ -91,14 +92,16 @@ export function parseAntigravityRetryTime(message) {
* Parse upstream provider error response
* @param {Response} response - Fetch response from provider
* @param {string} provider - Provider name (for Antigravity-specific parsing)
* @returns {Promise<{statusCode: number, message: string, retryAfterMs: number|null}>}
* @returns {Promise<{statusCode: number, message: string, retryAfterMs: number|null, responseBody: unknown}>}
*/
export async function parseUpstreamError(response, provider = null) {
let message = "";
let retryAfterMs = null;
let responseBody = null;
try {
const text = await response.text();
responseBody = normalizePayloadForLog(text);
// Try parse as JSON
try {
@@ -109,6 +112,7 @@ export async function parseUpstreamError(response, provider = null) {
}
} catch {
message = `Upstream error: ${response.status}`;
responseBody = { _rawText: message };
}
const messageStr = typeof message === "string" ? message : JSON.stringify(message);
@@ -122,6 +126,7 @@ export async function parseUpstreamError(response, provider = null) {
statusCode: response.status,
message: messageStr,
retryAfterMs,
responseBody,
};
}
+151 -11
View File
@@ -11,6 +11,7 @@ import {
COLORS,
} from "./usageTracking.ts";
import { parseSSELine, hasValuableContent, fixInvalidId, formatSSE } from "./streamHelpers.ts";
import { createStructuredSSECollector } from "./streamPayloadCollector.ts";
import { STREAM_IDLE_TIMEOUT_MS, HTTP_STATUS } from "../config/constants.ts";
import {
sanitizeStreamingChunk,
@@ -32,6 +33,8 @@ type StreamCompletePayload = {
usage: unknown;
/** Minimal response body for call log (streaming: usage + note; non-streaming not used) */
responseBody?: unknown;
providerPayload?: unknown;
clientPayload?: unknown;
};
type StreamOptions = {
@@ -57,6 +60,13 @@ type TranslateState = ReturnType<typeof initState> & {
accumulatedContent?: string;
};
type ToolCall = {
id: string | null;
index: number;
type: string;
function: { name: string; arguments: string };
};
type UsageTokenRecord = Record<string, number>;
function getOpenAIIntermediateChunks(value: unknown): unknown[] {
@@ -65,6 +75,22 @@ function getOpenAIIntermediateChunks(value: unknown): unknown[] {
return Array.isArray(candidate) ? candidate : [];
}
function restoreClaudePassthroughToolUseName(parsed: JsonRecord, toolNameMap: unknown): boolean {
if (!(toolNameMap instanceof Map)) return false;
if (!parsed || typeof parsed !== "object") return false;
const block =
parsed.content_block && typeof parsed.content_block === "object"
? (parsed.content_block as JsonRecord)
: null;
if (!block || block.type !== "tool_use" || typeof block.name !== "string") return false;
const restoredName = toolNameMap.get(block.name) ?? block.name;
if (restoredName === block.name) return false;
block.name = restoredName;
return true;
}
// Note: TextDecoder/TextEncoder are created per-stream inside createSSEStream()
// to avoid shared state issues with concurrent streams (TextDecoder with {stream:true}
// maintains internal buffering state between decode() calls).
@@ -113,6 +139,9 @@ export function createSSEStream(options: StreamOptions = {}) {
let usage: UsageTokenRecord | null = null;
/** Passthrough (OpenAI CC shape): saw tool_calls in stream before finish_reason */
let passthroughHasToolCalls = false;
/** Passthrough: accumulate tool_calls deltas for call log responseBody */
const passthroughToolCalls = new Map<string, ToolCall>();
let passthroughToolCallSeq = 0;
// State for translate mode (accumulatedContent for call log response body)
const state: TranslateState | null =
@@ -132,6 +161,12 @@ export function createSSEStream(options: StreamOptions = {}) {
// Guard against duplicate [DONE] events — ensures exactly one per stream
let doneSent = false;
const providerPayloadCollector = createStructuredSSECollector({
stage: "provider_response",
});
const clientPayloadCollector = createStructuredSSECollector({
stage: "client_response",
});
// Per-stream instances to avoid shared state with concurrent streams
const decoder = new TextDecoder();
@@ -186,6 +221,17 @@ export function createSSEStream(options: StreamOptions = {}) {
if (mode === STREAM_MODE.PASSTHROUGH) {
let output;
let injectedUsage = false;
let clientPayload: unknown = null;
if (trimmed.startsWith("data:")) {
const providerPayload = parseSSELine(trimmed);
if (providerPayload) {
providerPayloadCollector.push(providerPayload);
if ((providerPayload as { done?: unknown }).done === true) {
clientPayloadCollector.push(providerPayload);
}
}
}
if (trimmed.startsWith("data:") && trimmed.slice(5).trim() !== "[DONE]") {
try {
@@ -238,6 +284,7 @@ export function createSSEStream(options: StreamOptions = {}) {
if (eu.cache_creation_input_tokens)
u.cache_creation_input_tokens = eu.cache_creation_input_tokens;
}
const restoredToolName = restoreClaudePassthroughToolUseName(parsed, toolNameMap);
// Track content length and accumulate from Claude format
if (parsed.delta?.text) {
totalContentLength += parsed.delta.text.length;
@@ -247,6 +294,11 @@ export function createSSEStream(options: StreamOptions = {}) {
totalContentLength += parsed.delta.thinking.length;
passthroughAccumulatedContent += parsed.delta.thinking;
}
if (restoredToolName) {
output = `data: ${JSON.stringify(parsed)}
`;
injectedUsage = true;
}
} else {
// Chat Completions: full sanitization pipeline
parsed = sanitizeStreamingChunk(parsed);
@@ -268,9 +320,39 @@ export function createSSEStream(options: StreamOptions = {}) {
}
}
// T18: Track if we saw tool calls
// T18: Track if we saw tool calls & accumulate for call log
if (delta?.tool_calls && delta.tool_calls.length > 0) {
passthroughHasToolCalls = true;
for (const tc of delta.tool_calls) {
// Key by index first — id only appears on the first delta in OpenAI streaming
let key: string;
if (Number.isInteger(tc?.index)) {
key = `idx:${tc.index}`;
} else if (tc?.id) {
key = `id:${tc.id}`;
} else {
key = `seq:${++passthroughToolCallSeq}`;
}
const existing = passthroughToolCalls.get(key);
const deltaArgs =
typeof tc?.function?.arguments === "string" ? tc.function.arguments : "";
if (!existing) {
passthroughToolCalls.set(key, {
id: tc?.id ?? null,
index: Number.isInteger(tc?.index) ? tc.index : passthroughToolCalls.size,
type: tc?.type || "function",
function: {
name: tc?.function?.name || "",
arguments: deltaArgs,
},
});
} else {
if (tc?.id) existing.id = existing.id || tc.id;
if (tc?.function?.name && !existing.function.name)
existing.function.name = tc.function.name;
existing.function.arguments += deltaArgs;
}
}
}
const content = delta?.content || delta?.reasoning_content;
@@ -318,6 +400,8 @@ export function createSSEStream(options: StreamOptions = {}) {
injectedUsage = true;
}
}
clientPayload = parsed;
} catch {}
}
@@ -329,6 +413,10 @@ export function createSSEStream(options: StreamOptions = {}) {
}
}
if (clientPayload) {
clientPayloadCollector.push(clientPayload);
}
reqLogger?.appendConvertedChunk?.(output);
controller.enqueue(encoder.encode(output));
continue;
@@ -339,10 +427,12 @@ export function createSSEStream(options: StreamOptions = {}) {
const parsed = parseSSELine(trimmed);
if (!parsed) continue;
providerPayloadCollector.push(parsed);
if (parsed && parsed.done) {
if (!doneSent) {
doneSent = true;
clientPayloadCollector.push({ done: true });
const output = "data: [DONE]\n\n";
reqLogger?.appendConvertedChunk?.(output);
controller.enqueue(encoder.encode(output));
@@ -462,6 +552,7 @@ export function createSSEStream(options: StreamOptions = {}) {
}
const output = formatSSE(item, sourceFormat);
clientPayloadCollector.push(item);
reqLogger?.appendConvertedChunk?.(output);
controller.enqueue(encoder.encode(output));
}
@@ -489,6 +580,11 @@ export function createSSEStream(options: StreamOptions = {}) {
if (buffer.startsWith("data:") && !buffer.startsWith("data: ")) {
output = "data: " + buffer.slice(5);
}
const bufferedPayload = parseSSELine(buffer.trim());
if (bufferedPayload) {
providerPayloadCollector.push(bufferedPayload);
clientPayloadCollector.push(bufferedPayload);
}
reqLogger?.appendConvertedChunk?.(output);
controller.enqueue(encoder.encode(output));
}
@@ -516,13 +612,20 @@ export function createSSEStream(options: StreamOptions = {}) {
const prompt = Number(u?.prompt_tokens ?? u?.input_tokens ?? 0);
const completion = Number(u?.completion_tokens ?? u?.output_tokens ?? 0);
const content = passthroughAccumulatedContent.trim() || "";
const message: Record<string, unknown> = {
role: "assistant",
content: content || null,
};
if (passthroughToolCalls.size > 0) {
message.tool_calls = [...passthroughToolCalls.values()].sort(
(a, b) => a.index - b.index
);
}
const responseBody = {
choices: [
{
message: {
role: "assistant",
content,
},
message,
finish_reason: passthroughHasToolCalls ? "tool_calls" : "stop",
},
],
usage: {
@@ -532,7 +635,13 @@ export function createSSEStream(options: StreamOptions = {}) {
},
_streamed: true,
};
onComplete({ status: 200, usage, responseBody });
onComplete({
status: 200,
usage,
responseBody,
providerPayload: providerPayloadCollector.build(),
clientPayload: clientPayloadCollector.build(responseBody),
});
} catch {}
}
return;
@@ -542,6 +651,7 @@ export function createSSEStream(options: StreamOptions = {}) {
if (buffer.trim()) {
const parsed = parseSSELine(buffer.trim());
if (parsed && !parsed.done) {
providerPayloadCollector.push(parsed);
// Extract usage from remaining buffer — if the usage-bearing event
// (e.g. response.completed) is the last SSE line, it ends up here
// in the flush handler where extractUsage was not called.
@@ -578,6 +688,7 @@ export function createSSEStream(options: StreamOptions = {}) {
if (translated?.length > 0) {
for (const item of translated) {
const output = formatSSE(item, sourceFormat);
clientPayloadCollector.push(item);
reqLogger?.appendConvertedChunk?.(output);
controller.enqueue(encoder.encode(output));
}
@@ -597,6 +708,7 @@ export function createSSEStream(options: StreamOptions = {}) {
if (flushed?.length > 0) {
for (const item of flushed) {
const output = formatSSE(item, sourceFormat);
clientPayloadCollector.push(item);
reqLogger?.appendConvertedChunk?.(output);
controller.enqueue(encoder.encode(output));
}
@@ -615,6 +727,7 @@ export function createSSEStream(options: StreamOptions = {}) {
// Send [DONE] (only if not already sent during transform)
if (!doneSent) {
doneSent = true;
clientPayloadCollector.push({ done: true });
const doneOutput = "data: [DONE]\n\n";
reqLogger?.appendConvertedChunk?.(doneOutput);
controller.enqueue(encoder.encode(doneOutput));
@@ -643,13 +756,32 @@ export function createSSEStream(options: StreamOptions = {}) {
const prompt = Number(u?.prompt_tokens ?? u?.input_tokens ?? 0);
const completion = Number(u?.completion_tokens ?? u?.output_tokens ?? 0);
const content = (state?.accumulatedContent ?? "").trim() || "";
const message: Record<string, unknown> = {
role: "assistant",
content: content || null,
};
const hasToolCalls = state?.toolCalls?.size > 0;
if (hasToolCalls) {
// Normalize shape — translators may store different structures
message.tool_calls = [...state.toolCalls.values()]
.map(
(tc: Record<string, unknown>): ToolCall => ({
id: (tc.id as string) ?? null,
index: (tc.index as number) ?? (tc.blockIndex as number) ?? 0,
type: (tc.type as string) ?? "function",
function: (tc.function as ToolCall["function"]) ?? {
name: (tc.name as string) ?? "",
arguments: "",
},
})
)
.sort((a, b) => a.index - b.index);
}
const responseBody = {
choices: [
{
message: {
role: "assistant",
content,
},
message,
finish_reason: hasToolCalls ? "tool_calls" : "stop",
},
],
usage: {
@@ -659,7 +791,13 @@ export function createSSEStream(options: StreamOptions = {}) {
},
_streamed: true,
};
onComplete({ status: 200, usage: state?.usage, responseBody });
onComplete({
status: 200,
usage: state?.usage,
responseBody,
providerPayload: providerPayloadCollector.build(),
clientPayload: clientPayloadCollector.build(responseBody),
});
} catch {}
}
} catch (error) {
@@ -705,6 +843,7 @@ export function createSSETransformStreamWithLogger(
export function createPassthroughStreamWithLogger(
provider: string | null = null,
reqLogger: StreamLogger | null = null,
toolNameMap: unknown = null,
model: string | null = null,
connectionId: string | null = null,
body: unknown = null,
@@ -715,6 +854,7 @@ export function createPassthroughStreamWithLogger(
mode: STREAM_MODE.PASSTHROUGH,
provider,
reqLogger,
toolNameMap,
model,
connectionId,
apiKeyInfo,
+19 -17
View File
@@ -39,32 +39,34 @@ export function parseSSELine(line) {
// Check if chunk has valuable content (not empty)
export function hasValuableContent(chunk, format) {
// OpenAI format
if (format === FORMATS.OPENAI && chunk.choices?.[0]?.delta) {
if (format === FORMATS.OPENAI) {
if (!chunk.choices?.[0]?.delta) return false;
const delta = chunk.choices[0].delta;
return (
(delta.content && delta.content !== "") ||
(delta.reasoning_content && delta.reasoning_content !== "") ||
(delta.tool_calls && delta.tool_calls.length > 0) ||
chunk.choices[0].finish_reason ||
delta.role
);
if (typeof delta.content === "string" && delta.content.length > 0) return true;
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0)
return true;
if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) return true;
if (chunk.choices[0].finish_reason) return true;
if (typeof delta.role === "string" && delta.role.length > 0) return true;
return false;
}
// Claude format
if (format === FORMATS.CLAUDE) {
const isContentBlockDelta = chunk.type === "content_block_delta";
const hasText = chunk.delta?.text && chunk.delta.text !== "";
const hasThinking = chunk.delta?.thinking && chunk.delta.thinking !== "";
const hasInputJson = chunk.delta?.partial_json && chunk.delta.partial_json !== "";
if (isContentBlockDelta && !hasText && !hasThinking && !hasInputJson) {
return false;
if (isContentBlockDelta) {
const hasText = typeof chunk.delta?.text === "string" && chunk.delta.text.length > 0;
const hasThinking =
typeof chunk.delta?.thinking === "string" && chunk.delta.thinking.length > 0;
const hasInputJson =
typeof chunk.delta?.partial_json === "string" && chunk.delta.partial_json.length > 0;
if (!hasText && !hasThinking && !hasInputJson) return false;
}
return true;
}
// Gemini format: filter chunks with no actual content parts
if (format === FORMATS.GEMINI && chunk.candidates?.[0]) {
// Gemini / Antigravity format: filter chunks with no actual content parts
if ((format === FORMATS.GEMINI || format === FORMATS.ANTIGRAVITY) && chunk.candidates?.[0]) {
const candidate = chunk.candidates[0];
// Keep chunks with finish reason or safety ratings (they signal completion)
if (candidate.finishReason) return true;
@@ -73,7 +75,7 @@ export function hasValuableContent(chunk, format) {
if (!parts || parts.length === 0) return false;
// Filter out chunks where all parts have empty text
const hasContent = parts.some(
(p) => (p.text && p.text !== "") || p.functionCall || p.executableCode
(p) => (typeof p.text === "string" && p.text.length > 0) || p.functionCall || p.executableCode
);
return hasContent;
}
+72
View File
@@ -0,0 +1,72 @@
import { cloneLogPayload } from "@/lib/logPayloads";
type StructuredSSEEvent = {
index: number;
event?: string;
data: unknown;
};
type CollectorOptions = {
maxEvents?: number;
maxBytes?: number;
stage?: string;
};
function getEventName(payload: unknown): string | undefined {
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return undefined;
if (typeof (payload as { event?: unknown }).event === "string") {
return (payload as { event: string }).event;
}
if (typeof (payload as { type?: unknown }).type === "string") {
return (payload as { type: string }).type;
}
if ((payload as { done?: unknown }).done === true) {
return "[DONE]";
}
return undefined;
}
export function createStructuredSSECollector(options: CollectorOptions = {}) {
const { maxEvents = 200, maxBytes = 49152, stage } = options;
const events: StructuredSSEEvent[] = [];
let usedBytes = 0;
let droppedEvents = 0;
return {
push(payload: unknown, explicitEvent?: string) {
if (payload === null || payload === undefined) return;
const event: StructuredSSEEvent = {
index: events.length + droppedEvents,
data: cloneLogPayload(payload),
};
const eventName = explicitEvent || getEventName(payload);
if (eventName) {
event.event = eventName;
}
const serializedSize = JSON.stringify(event).length;
if (events.length >= maxEvents || usedBytes + serializedSize > maxBytes) {
droppedEvents += 1;
return;
}
usedBytes += serializedSize;
events.push(event);
},
build(summary?: unknown) {
return {
_streamed: true,
_format: "sse-json",
...(stage ? { _stage: stage } : {}),
_eventCount: events.length + droppedEvents,
...(droppedEvents > 0 ? { _truncated: true, _droppedEvents: droppedEvents } : {}),
events,
...(summary === undefined ? {} : { summary: cloneLogPayload(summary) }),
};
},
};
}
+56 -13
View File
@@ -3,6 +3,12 @@
*/
import { saveRequestUsage, appendRequestLog } from "@/lib/usageDb";
import {
getLoggedInputTokens,
getLoggedOutputTokens,
getPromptCacheCreationTokens,
getPromptCacheReadTokens,
} from "@/lib/usage/tokenAccounting";
import { FORMATS } from "../translator/formats.ts";
// ANSI color codes
@@ -66,12 +72,47 @@ export function addBufferToUsage(usage) {
export function filterUsageForFormat(usage, targetFormat) {
if (!usage || typeof usage !== "object") return usage;
// Cross-map between Claude-style and OpenAI-style field names before filtering.
// Some providers return input_tokens/output_tokens even when using OpenAI format.
const convertedUsage = { ...usage };
if (targetFormat === FORMATS.CLAUDE || targetFormat === FORMATS.OPENAI_RESPONSES) {
// OpenAI → Claude: prompt_tokens → input_tokens
if (convertedUsage.prompt_tokens !== undefined && convertedUsage.input_tokens === undefined) {
convertedUsage.input_tokens = convertedUsage.prompt_tokens;
}
if (
convertedUsage.completion_tokens !== undefined &&
convertedUsage.output_tokens === undefined
) {
convertedUsage.output_tokens = convertedUsage.completion_tokens;
}
} else {
// Claude → OpenAI: input_tokens → prompt_tokens
if (convertedUsage.input_tokens !== undefined && convertedUsage.prompt_tokens === undefined) {
convertedUsage.prompt_tokens = convertedUsage.input_tokens;
}
if (
convertedUsage.output_tokens !== undefined &&
convertedUsage.completion_tokens === undefined
) {
convertedUsage.completion_tokens = convertedUsage.output_tokens;
}
// Ensure total_tokens is set
if (
convertedUsage.total_tokens === undefined &&
convertedUsage.prompt_tokens !== undefined &&
convertedUsage.completion_tokens !== undefined
) {
convertedUsage.total_tokens = convertedUsage.prompt_tokens + convertedUsage.completion_tokens;
}
}
// Helper to pick only defined fields from usage
const pickFields = (fields) => {
const filtered = {};
for (const field of fields) {
if (usage[field] !== undefined) {
filtered[field] = usage[field];
if (convertedUsage[field] !== undefined) {
filtered[field] = convertedUsage[field];
}
}
return filtered;
@@ -230,10 +271,14 @@ export function extractUsage(chunk) {
}
// OpenAI format
if (chunk.usage && typeof chunk.usage === "object" && chunk.usage.prompt_tokens !== undefined) {
if (
chunk.usage &&
typeof chunk.usage === "object" &&
(chunk.usage.prompt_tokens !== undefined || chunk.usage.input_tokens !== undefined)
) {
return normalizeUsage({
prompt_tokens: chunk.usage.prompt_tokens,
completion_tokens: chunk.usage.completion_tokens || 0,
prompt_tokens: chunk.usage.prompt_tokens ?? chunk.usage.input_tokens ?? 0,
completion_tokens: chunk.usage.completion_tokens ?? chunk.usage.output_tokens ?? 0,
cached_tokens: chunk.usage.prompt_tokens_details?.cached_tokens,
reasoning_tokens: chunk.usage.completion_tokens_details?.reasoning_tokens,
});
@@ -376,8 +421,8 @@ export function logUsage(provider, usage, model = null, connectionId = null, api
// Support both formats:
// - OpenAI: prompt_tokens, completion_tokens
// - Claude: input_tokens, output_tokens
const inTokens = usage?.prompt_tokens || usage?.input_tokens || 0;
const outTokens = usage?.completion_tokens || usage?.output_tokens || 0;
const inTokens = getLoggedInputTokens(usage);
const outTokens = getLoggedOutputTokens(usage);
const accountPrefix = connectionId ? connectionId.slice(0, 8) + "..." : "unknown";
let msg = `[${getTimeString()}] 📊 ${COLORS.green}[USAGE] ${p} | in=${inTokens} | out=${outTokens} | account=${accountPrefix}${COLORS.reset}`;
@@ -388,10 +433,10 @@ export function logUsage(provider, usage, model = null, connectionId = null, api
}
// Add cache info if present (unified from different formats)
const cacheRead = usage.cache_read_input_tokens || usage.cached_tokens;
const cacheRead = getPromptCacheReadTokens(usage);
if (cacheRead) msg += ` | cache_read=${cacheRead}`;
const cacheCreation = usage.cache_creation_input_tokens;
const cacheCreation = getPromptCacheCreationTokens(usage);
if (cacheCreation) msg += ` | cache_create=${cacheCreation}`;
const reasoning = usage.reasoning_tokens;
@@ -399,11 +444,9 @@ export function logUsage(provider, usage, model = null, connectionId = null, api
console.log(msg);
// Save to usage DB
// input = total input tokens (non-cached + cache_read + cache_creation)
// This ensures analytics show correct totals for heavily-cached requests
// Save to usage DB with cache-read tracked separately from the main input counter.
const tokens = {
input: inTokens + (cacheRead || 0) + (cacheCreation || 0),
input: inTokens,
output: outTokens,
cacheRead: cacheRead || 0,
cacheCreation: cacheCreation || 0,
+349 -140
View File
@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "3.0.2",
"version": "3.2.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "3.0.2",
"version": "3.2.2",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
@@ -60,6 +60,7 @@
"@types/node": "^25.2.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"c8": "^11.0.0",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
@@ -511,6 +512,16 @@
}
}
},
"node_modules/@bcoe/v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@braintree/sanitize-url": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz",
@@ -1787,9 +1798,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1806,9 +1814,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1825,9 +1830,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1844,9 +1846,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1863,9 +1862,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1898,9 +1894,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1933,9 +1926,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1958,9 +1948,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1983,9 +1970,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2008,9 +1992,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2033,9 +2014,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2080,9 +2058,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2196,6 +2171,16 @@
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -2663,9 +2648,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2682,9 +2664,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2964,9 +2943,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2987,9 +2963,6 @@
"cpu": [
"arm"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -3010,9 +2983,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -3033,9 +3003,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -3150,9 +3117,9 @@
}
},
"node_modules/@parcel/watcher/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -4930,9 +4897,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4950,9 +4914,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4970,9 +4931,6 @@
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4990,9 +4948,6 @@
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5296,9 +5251,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5315,9 +5267,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5564,9 +5513,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5584,9 +5530,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6085,6 +6028,13 @@
"@types/node": "*"
}
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/js-cookie": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
@@ -6396,9 +6346,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6605,9 +6555,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6622,9 +6569,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6639,9 +6583,6 @@
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6656,9 +6597,6 @@
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6673,9 +6611,6 @@
"riscv64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6690,9 +6625,6 @@
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -7642,9 +7574,9 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7755,6 +7687,40 @@
"node": ">=6.0.0"
}
},
"node_modules/c8": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz",
"integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.1",
"@istanbuljs/schema": "^0.1.3",
"find-up": "^5.0.0",
"foreground-child": "^3.1.1",
"istanbul-lib-coverage": "^3.2.0",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.1.6",
"test-exclude": "^8.0.0",
"v8-to-istanbul": "^9.0.0",
"yargs": "^17.7.2",
"yargs-parser": "^21.1.1"
},
"bin": {
"c8": "bin/c8.js"
},
"engines": {
"node": "20 || >=22"
},
"peerDependencies": {
"monocart-coverage-reports": "^2"
},
"peerDependenciesMeta": {
"monocart-coverage-reports": {
"optional": true
}
}
},
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -10618,6 +10584,23 @@
"node": ">=0.10.0"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
@@ -10899,6 +10882,24 @@
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.2.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -10912,6 +10913,45 @@
"node": ">=10.13.0"
}
},
"node_modules/glob/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
@@ -11378,6 +11418,13 @@
"node": ">=16.9.0"
}
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/html-url-attributes": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@@ -12306,6 +12353,45 @@
"node": ">=0.10.0"
}
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/iterator.prototype": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -12788,9 +12874,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -12812,9 +12895,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -12943,9 +13023,9 @@
}
},
"node_modules/lint-staged/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13161,6 +13241,35 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/markdown-extensions": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz",
@@ -14563,6 +14672,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mixin-deep": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
@@ -15380,10 +15499,37 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "11.2.7",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
"integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz",
"integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -15412,9 +15558,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -18246,6 +18392,60 @@
"node": ">=6"
}
},
"node_modules/test-exclude": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz",
"integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
"glob": "^13.0.6",
"minimatch": "^10.2.2"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/test-exclude/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/test-exclude/node_modules/brace-expansion": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/test-exclude/node_modules/minimatch": {
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/thread-stream": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
@@ -18326,9 +18526,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -19004,6 +19204,21 @@
"uuid": "dist-node/bin/uuid"
}
},
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
"integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
"dev": true,
"license": "ISC",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.12",
"@types/istanbul-lib-coverage": "^2.0.1",
"convert-source-map": "^2.0.0"
},
"engines": {
"node": ">=10.12.0"
}
},
"node_modules/v8n": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/v8n/-/v8n-1.5.1.tgz",
@@ -19355,9 +19570,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -19379,9 +19591,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -19480,9 +19689,9 @@
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -19575,9 +19784,9 @@
}
},
"node_modules/vitest/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -19918,9 +20127,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"dev": true,
"license": "ISC",
"bin": {
+8 -3
View File
@@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "3.0.2",
"version": "3.2.2",
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
"type": "module",
"bin": {
@@ -72,7 +72,10 @@
"test:protocols:e2e": "node scripts/run-protocol-clients-tests.mjs",
"test:vitest": "vitest run open-sse/mcp-server/__tests__/*.test.ts open-sse/services/autoCombo/__tests__/*.test.ts",
"test:ecosystem": "node scripts/run-ecosystem-tests.mjs",
"test:coverage": "npx c8 --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.mjs",
"test:coverage": "c8 --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 55 --lines 55 --functions 55 --branches 60 node --import tsx/esm --test tests/unit/*.test.mjs",
"test:coverage:legacy": "c8 --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.mjs",
"coverage:report": "c8 report --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov",
"coverage:report:legacy": "c8 report --exclude=open-sse --reporter=text --reporter=text-summary",
"test:all": "npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e",
"check": "npm run lint && npm run test",
"prepublishOnly": "npm run build:cli",
@@ -124,6 +127,7 @@
"@types/node": "^25.2.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"c8": "^11.0.0",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
@@ -157,6 +161,7 @@
]
},
"overrides": {
"dompurify": "^3.3.2"
"dompurify": "^3.3.2",
"path-to-regexp": "^8.4.0"
}
}
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#4A90E2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
<path d="M8 14s1.5 2 4 2 4-2 4-2"/>
<line x1="9" y1="9" x2="9.01" y2="9"/>
<line x1="15" y1="9" x2="15.01" y2="9"/>
</svg>

After

Width:  |  Height:  |  Size: 364 B

Some files were not shown because too many files have changed in this diff Show More