Compare commits

..

40 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza f62dcc12a0 Merge pull request #608 from diegosouzapw/release/v3.0.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.0.1 — hotfix for proxy_ prefix, LongCat validation, and MCP tool schemas
2026-03-25 09:07:27 -03:00
diegosouzapw 5907296d36 fix: proxy UI bugs, connection tag grouping, and function_call prefix stripping
## Proxy UI Bug Fixes
- fix: proxy badge on connection cards now uses resolveProxyForConnection()
  per-connection (covers registry + config-file assignments)
- fix: Test Connection button now works in 'saved' proxy mode by resolving
  proxy config from savedProxies list
- fix: ProxyConfigModal now calls onClose() after save/clear (fixes UI freeze)
- fix: ProxyRegistryManager loads usage eagerly on mount with deduplication
  by scope+scopeId to prevent double-counting; adds per-row Test button

## Connection Tag Grouping (new feature)
- feat: add Tag/Group field to EditConnectionModal (stored in
  providerSpecificData.tag, no DB schema change)
- feat: connections list groups by tag with visual dividers when any account
  has a tag; untagged accounts appear first without header

## Post-merge fix from PR #607 review
- fix: function_call blocks in translateNonStreamingResponse now also strip
  Claude OAuth proxy_ prefix via toolNameMap (kilo-code-bot #607 warning)
  Affects OpenAI Responses API format path — tool_use was fixed in PR #607
  but function_call was missed
2026-03-25 08:54:46 -03:00
diegosouzapw aa2a7d12be chore(release): v3.0.1 — hotfix for proxy_ prefix, LongCat validation, and MCP tool schemas 2026-03-25 08:20:04 -03:00
Diego Rodrigues de Sa e Souza 33fee5dcc5 fix: strip proxy_ prefix in non-streaming Claude responses & fix LongCat validation (#605, #592) (#607)
- 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)

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-03-25 08:16:46 -03:00
Randi e9ae50be0c fix: improve Provider Limits light mode contrast and Claude plan tier display (#591)
- Replace hardcoded rgba(255,255,255,...) borders/backgrounds with theme-aware
  CSS variables (--color-border, --color-bg-subtle) for proper light mode contrast
- Add dark: variants for hover states and progress bar backgrounds
- Fix Claude plan tier: try to extract actual plan from OAuth response instead
  of hardcoding "Claude Code"
- Recognize provider names (Claude Code, Kimi Coding, Kiro) as non-plan-tier
  values in normalizePlanTier() to avoid showing them as tier badges

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 08:16:28 -03:00
Flo 5886c0fd5e docs(i18n): fix russian translation for playground and testbed (#589)
Co-authored-by: Vladimir Alabov <vladimir.alabov@bsc-ideas.com>
2026-03-25 08:15:59 -03:00
diegosouzapw 9e640cac6b chore: merge remaining 3.0.0-rc.17 commits into main (ProviderIcon, docs, provider counts)
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
2026-03-24 18:46:43 -03:00
diegosouzapw 061521f87f docs: comprehensive v3.0.0 CHANGELOG + fix all version references
- Consolidated all 17 RC entries (rc.1 through rc.17) into single v3.0.0 entry
- 31 new providers, 9 major features, 40+ bug fixes, 19 community PRs
- Fixed llm.txt: version 2.0.13 → 3.0.0, provider count 36+ → 67+
- package.json: 3.0.0, openapi.yaml: 3.0.0
2026-03-24 18:42:39 -03:00
diegosouzapw b15eb278e1 chore: bump version to 3.0.0, update openapi.yaml and CHANGELOG 2026-03-24 18:38:35 -03:00
diegosouzapw 142ac8eb96 Merge PR #587: fix(sse): revert resolveDataDir import for Workers compat 2026-03-24 18:32:21 -03:00
diegosouzapw 88705bb6e9 docs: update provider count to 67+ across all documentation
- README.md: 44+ → 67+ (3 occurrences)
- llm.txt: 40+ → 67+ (2 occurrences)
- 21 i18n READMEs: 44+ → 67+
- 3 i18n READMEs (it/nl/phi): 36+ → 67+
- Actual count: FREE=4, OAUTH=8, APIKEY=55, TOTAL=67
2026-03-24 18:05:19 -03:00
k0valik 60d4fcfe7e update the comments
**1. `open-sse/transformer/responsesTransformer.ts`**
- Removed `import { resolveDataDir } from "../../src/lib/dataPaths"`
- Restored: `typeof process !== "undefined" ? process.cwd() : "."`
- Added history comment: `// previous: const baseDir = logsDir || resolveDataDir(); — reverted in #555 for Workers compat`

**2. `open-sse/config/credentialLoader.ts`**
- Updated JSDoc with `resolveDataDir()` description
- Added history: `previous: Priority: DATA_DIR env → ./data (project root)`
2026-03-24 21:40:08 +01:00
diegosouzapw 038d19ec98 docs: update llm.txt to v3.0.0, add embeddings+speech to docs page
- llm.txt: complete rewrite for v3.0.0-rc.17 (40+ providers, 9 strategies,
  MCP/A2A/ACP, ProviderIcon, auto-combo, 926 tests, CodeQL fixes)
- docs/page.tsx: add /v1/embeddings and /v1/audio/speech to API reference
- en.json: add i18n keys for new endpoint descriptions
2026-03-24 17:31:47 -03:00
k0valik e1b98768c7 fix(sse): revert resolveDataDir import in responsesTransformer for Workers compat 2026-03-24 21:29:08 +01:00
diegosouzapw b82af2b849 fix(ui): add ProviderIcon to agents page CLI tools + maxDuration for transcription
- Agents page: use ProviderIcon with 21-entry AGENT_ICON_MAP for CLI tool
  icons (claude→anthropic, codex→openai, gemini-cli→google, etc.)
- Transcription route: add maxDuration=300 for large audio/video uploads
- Combos: verified all 4 templates + 9 strategies present in UI
2026-03-24 17:21:25 -03:00
diegosouzapw 703591d76a fix(ui): use ProviderIcon component on dashboard home page
Replace Image-based provider icons in ProviderOverviewCard with the same
ProviderIcon component used on the providers page (@lobehub/icons SVG
with PNG → generic fallback chain).
2026-03-24 17:11:34 -03:00
diegosouzapw 7142688a77 fix(types): Zod 4 z.record 2-arg form + header type cast in openapi/try 2026-03-24 17:01:56 -03:00
diegosouzapw a12622b3d8 docs: update CHANGELOG, README, and sync i18n for v3.0.0-rc.17
- CHANGELOG.md: add rc.17 entry (CodeQL, route validation, omniModel tag, Docker)
- README.md: add 3 new rows to What's New table (CodeQL, validation, #585)
- docs/i18n: sync What's New v3.0.0 section to all 30 translated READMEs
  (replacing outdated v2.7.0/v2.0.9 sections)
2026-03-24 16:44:38 -03:00
diegosouzapw 9248ab4dfd fix(ci): route validation, CodeQL alerts, Docker workflow
- Add Zod schemas + validateBody() to 5 routes missing validation:
  model-combo-mappings (POST, PUT), webhooks (POST, PUT), openapi/try (POST)
- Fix 6 polynomial-redos CodeQL alerts in provider.ts and chatCore.ts
  by replacing (?:^|/) alternation patterns with segment-based matching
- Fix insecure-randomness in acp/manager.ts (crypto.randomUUID)
- Fix shell-command-injection in prepublish.mjs (JSON.stringify)
- Upgrade docker/setup-buildx-action from v3 to v4 (Node.js 20 deprecation)

CI check:route-validation:t06 PASS (176/176 routes validated)
Tests: 926/926 pass
2026-03-24 16:08:02 -03:00
diegosouzapw 5a8c6440f0 fix(combo): strip omniModel tags from outbound streaming responses (#585)
The <omniModel> tag was leaking into user-visible content when
context_cache_protection was enabled on a combo. The tag is an internal
marker for model pinning across conversation turns.

Fix: Add a second TransformStream pass (sanitize) that strips the tag
from SSE chunk content before delivery to the client. The tag is still
injected for round-trip context pinning but cleaned from visible output.

Also adds X-OmniRoute-Model response header as a cleaner metadata channel.

Closes #585
2026-03-24 15:49:26 -03:00
diegosouzapw 74b694a4dd chore: bump version to 3.0.0-rc.17 2026-03-24 15:24:46 -03:00
diegosouzapw 896b52d5fb Merge branch '3.0.0-rc.16' into main
RC16 Sprint:
- feat(media): 4GB transcription file limit with validation
- feat: configurable context length in model metadata (PR #578)
- feat: per-model upstream headers, compat PATCH (PR #575)
- feat: model name prefix stripping option (PR #582)
- fix(npm): link electron-release to npm-publish (PR #581)
- fix(routing): unprefixed claude models now resolve to anthropic (#570)
- 12 issues resolved, 4 PRs merged
2026-03-24 15:23:08 -03:00
diegosouzapw 1429fea27a fix(routing): unprefixed claude models now resolve to anthropic provider (#570)
Build Electron Desktop App / Validate version (push) Failing after 30s
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
Changed the heuristic fallback for claude-* models from 'antigravity' to 'anthropic'
as the canonical provider. Users without Antigravity credentials were getting
'No credentials for provider: antigravity' errors when sending unprefixed
Claude model names like 'claude-sonnet-4-5'.

Closes #570
2026-03-24 14:11:13 -03:00
diegosouzapw 3218563f32 chore: merge PRs #581, #582 + local improvements for rc16
Merged PRs:
- #582 — model prefix stripping option (closes #568)
- #581 — npm publish workflow fix (refs #579)

Local changes:
- Restored stashed i18n, CLI tools, and maintenance banner updates
- 926 tests passing
2026-03-24 13:32:05 -03:00
diegosouzapw d412edbbe1 Merge PR #581: fix(npm) — link electron-release to npm-publish via workflow_call (by @jay77721, refs #579) 2026-03-24 13:28:25 -03:00
diegosouzapw 968159a85d Merge PR #582: feat(proxy) — add model name prefix stripping option (by @jay77721, closes #568) 2026-03-24 13:27:59 -03:00
jay77721 18a3741fc2 feat(proxy): add model name prefix stripping option (#568)
Add stripModelPrefix boolean setting that, when enabled, strips
provider prefixes (e.g. openai/, anthropic/) from incoming model
names and re-resolves the bare model name using existing heuristics.

This allows tools to send prefixed model names while OmniRoute
handles provider routing at the proxy layer.

- Add stripModelPrefix to settings validation schema (Zod)
- Check setting in getModelInfo() after custom node matching fails
- Falls through to normal resolution on error or when disabled
- Backward compatible: opt-in, default behavior unchanged
2026-03-24 21:52:43 +08:00
jay77721 f1be3e6bb0 fix(npm): link electron-release to npm-publish via workflow_call
- Add workflow_call trigger to npm-publish.yml for direct cross-workflow invocation
- Add publish-npm job to electron-release.yml that calls npm-publish after release
- Add dist-tag support: prerelease versions auto-get 'next' tag, stable gets 'latest'
- Add v-prefix stripping for robust version handling
- Fixes issue where GitHub releases created by bots don't reliably trigger npm-publish
- Refs #579
2026-03-24 21:52:34 +08:00
diegosouzapw b717a02394 chore: remove PR documentation and unnecessary markdown files 2026-03-24 10:33:25 -03:00
diegosouzapw d68143e63d Merge PR #575: feat(dashboard,sse,api) — per-model upstream headers, compat PATCH, chat alignment (by @zhangqiang8vip) 2026-03-24 09:46:59 -03:00
diegosouzapw 0d306b8b1c Merge PR #578: feat — add configurable context length to model metadata (by @hijak) 2026-03-24 09:46:32 -03:00
diegosouzapw a655863855 feat(media): increase transcription file limit to 4GB with validation
- Added MAX_TRANSCRIPTION_FILE_SIZE constant (4GB)
- Added formatFileSize() helper for human-readable display (KB/MB/GB)
- Frontend validation rejects files > 4GB with error message
- Changed label from 'Audio File' to 'Audio / Video File'
- Shows 'Supports audio and video files up to 4 GB' hint
2026-03-24 09:42:36 -03:00
Jack Cowey 58264c80dd feat: add configurable context length to model metadata
- Add contextLength field to RegistryModel interface for per-model overrides
- Add defaultContextLength to RegistryEntry for provider-level defaults
- Set context lengths for major providers:
  - Claude: 200k
  - Codex: 400k (fixes combo context display)
  - Gemini: 1M
  - OpenAI: 128k
  - GitHub Copilot: 128k
  - Kiro/Cursor: 200k
  - OpenCode: 200k
- Include context_length in /v1/models API response
- Add context_length field to combo schema for custom combo context
- Update contextManager to use registry defaults and support env overrides
  - CONTEXT_LENGTH_<PROVIDER> for per-provider override
  - CONTEXT_LENGTH_DEFAULT for global override

This allows clients like OpenClaw to display accurate context windows
for combo models instead of guessing based on model name patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 12:29:34 +00:00
zhang-qiang 1dbbd7241d fix(mcp-server): type list-models locals for typecheck:core
Annotate rawModels as unknown[] and warning as string | undefined (avoid never[] / undefined-only inference)

Made-with: Cursor
2026-03-24 17:50:13 +08:00
zhang-qiang 6c0ef48d45 docs(zws_docs): archive PR memory and CI notes in README
Upstream PR context: #575, T06/T11/keytar, commit hygiene, links to V8 and PR draft

Made-with: Cursor
2026-03-24 17:45:34 +08:00
zhang-qiang 8b57f88ca3 fix(open-sse): satisfy T11 explicit-any budget (regex counts word any)
- Reword comments that contained the token any; replace any types with typed shapes

- stream.ts: passthrough tool-call flag via local boolean (state is null in passthrough)

- Document T11 in zws_docs/ZWS_README_V8.md

Made-with: Cursor
2026-03-24 17:42:52 +08:00
zhang-qiang 3e9fdc777e fix(api,zed): T06 validateBody on JSON routes; lazy-load keytar for CI build
- Add validateBody() alongside request.json() on 5 routes (t06:route-validation)

- Dynamic import keytar in zed keychain-reader to avoid libsecret/keytar load during next build

- Document in zws_docs/ZWS_README_V8.md section 9

Made-with: Cursor
2026-03-24 17:36:55 +08:00
zhang-qiang a8ca88797a feat(dashboard,sse,api): per-model upstream headers, compat PATCH, chat alignment
- Store/sanitize upstreamHeaders; shared forbidden header names (upstreamHeaders.ts)

- chatCore: buildUpstreamHeadersForExecute; T5 recomputes; 401 retry uses translatedBody.model

- Dashboard compat popover + i18n; Zod partialRecord + header value newline guard

- Executors merge upstreamExtraHeaders; sanitize unit tests

- Dev: bootstrap env in run-next, instrumentation-node import, credentialLoader dedupe

Made-with: Cursor
2026-03-24 17:24:11 +08:00
zhang-qiang 71540b5dc0 merge: sync upstream/main (diegosouzapw/OmniRoute) 2026-03-24 13:01:08 +08:00
zhang-qiang 23e3a1c269 docs: move ZWS_README_V4/V5 into zws_docs/
Made-with: Cursor
2026-03-23 14:04:11 +08:00
152 changed files with 5841 additions and 3308 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
+10
View File
@@ -201,3 +201,13 @@ jobs:
release-assets/*.source.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-npm:
name: Publish to npm
needs: [validate, release]
uses: ./.github/workflows/npm-publish.yml
with:
version: ${{ needs.validate.outputs.version }}
tag: latest
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+59 -12
View File
@@ -6,9 +6,31 @@ on:
workflow_dispatch:
inputs:
version:
description: "Version tag to publish (e.g. 2.6.0)"
description: "Version to publish (e.g. 2.9.5 or 3.0.0-rc.15)"
required: true
type: string
tag:
description: "npm dist-tag (latest / next)"
required: false
default: "latest"
type: choice
options:
- latest
- next
workflow_call:
inputs:
version:
description: "Version to publish (without v prefix)"
required: true
type: string
tag:
description: "npm dist-tag (latest / next)"
required: false
default: "latest"
type: string
secrets:
NPM_TOKEN:
required: true
permissions:
contents: read
@@ -31,16 +53,35 @@ jobs:
- name: Install dependencies (skip scripts to avoid heavy build)
run: npm install --ignore-scripts --no-audit --no-fund
- name: Sync version from release tag or input
- name: Resolve version and dist-tag
id: resolve
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ inputs.version }}"
else
VERSION="${GITHUB_REF_NAME}"
VERSION="${VERSION#v}"
case "${{ github.event_name }}" in
workflow_dispatch|workflow_call)
VERSION="${{ inputs.version }}"
TAG="${{ inputs.tag }}"
;;
release)
VERSION="${GITHUB_REF_NAME}"
;;
esac
# Strip v prefix if present
VERSION="${VERSION#v}"
# Default dist-tag logic
if [ -z "$TAG" ]; then
if [[ "$VERSION" == *-* ]]; then
TAG="next"
else
TAG="latest"
fi
fi
npm version "$VERSION" --no-git-tag-version --allow-same-version
echo "Publishing version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "📦 Publishing omniroute@$VERSION with tag=$TAG"
- name: Sync package.json version
run: |
npm version "${{ steps.resolve.outputs.version }}" --no-git-tag-version --allow-same-version
- name: Build CLI bundle (standalone app)
env:
@@ -49,12 +90,18 @@ jobs:
- name: Publish to npm
run: |
VERSION=$(node -p "require('./package.json').version")
VERSION="${{ steps.resolve.outputs.version }}"
TAG="${{ steps.resolve.outputs.tag }}"
# Check if this version is already published — skip instead of failing with E403
if npm view "omniroute@${VERSION}" version --silent 2>/dev/null | grep -q "^${VERSION}$"; then
echo "⚠️ Version ${VERSION} is already published on npm — skipping."
echo "⚠️ Version ${VERSION} is already published on npm — skipping."
exit 0
fi
npm publish --access public
if [ "$TAG" = "latest" ]; then
npm publish --access public
else
npm publish --access public --tag "$TAG"
fi
echo "✅ Published omniroute@$VERSION (tag: $TAG)"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+22 -18
View File
@@ -49,19 +49,22 @@ but the real logic lives in `src/lib/db/`.
Translation between provider formats: `open-sse/translator/`
**Upstream model extra headers** (`compatByProtocol` / custom models): merged in executors after default auth; **same header name replaces** the executor value (e.g. custom `Authorization` overrides Bearer). In `open-sse/handlers/chatCore.ts`, the primary request merges headers for **both** the client model id and `resolveModelAlias(clientModel)` (resolved id wins on key conflicts). **T5 intra-family fallback** recomputes headers using only the fallback model id and `resolveModelAlias(fallback)` so sibling models do not inherit another models headers. Forbidden header names live in `src/shared/constants/upstreamHeaders.ts` — keep sanitize (`models.ts`), Zod (`schemas.ts`), and unit tests aligned when editing that list.
### MCP Server (`open-sse/mcp-server/`)
16 tools for AI agent control via **3 transport modes**:
- **stdio** — Local IDE integration (Claude Desktop, Cursor, VS Code)
- **SSE** — Remote Server-Sent Events at `/api/mcp/sse`
- **Streamable HTTP** — Modern bidirectional HTTP at `/api/mcp/stream`
HTTP transports run in-process via `httpTransport.ts` singleton using `WebStandardStreamableHTTPServerTransport`.
| Category | Tools |
| ---------- | ------------------------------------------------------------------------------------------------------------------------- |
| Essential | `get_health`, `list_combos`, `get_combo_metrics`, `switch_combo`, `check_quota`, `route_request`, `cost_report`, `list_models_catalog` |
| Advanced | `simulate_route`, `set_budget_guard`, `set_resilience_profile`, `test_combo`, `get_provider_metrics`, `best_combo_for_task`, `explain_route`, `get_session_snapshot` |
| Category | Tools |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Essential | `get_health`, `list_combos`, `get_combo_metrics`, `switch_combo`, `check_quota`, `route_request`, `cost_report`, `list_models_catalog` |
| Advanced | `simulate_route`, `set_budget_guard`, `set_resilience_profile`, `test_combo`, `get_provider_metrics`, `best_combo_for_task`, `explain_route`, `get_session_snapshot` |
- Scoped authorization (9 scopes), audit logging, Zod schemas
- IDE configs for Claude Desktop, Cursor, VS Code Copilot
@@ -79,25 +82,26 @@ Agent-to-Agent v0.3 protocol:
### Auto-Combo Engine (`open-sse/services/autoCombo/`)
Self-healing routing optimization:
- 6-factor scoring, 4 mode packs, bandit exploration
- Progressive cooldown, probe-based re-admission
### Dashboard (`src/app/(dashboard)/`)
| Page | Description |
| ---------------------------- | -------------------------------------------------------------- |
| `/dashboard` | Home with quick start, provider overview |
| `/dashboard/endpoint` | **Endpoints** (tabbed): Endpoint Proxy, MCP, A2A, API Endpoints |
| `/dashboard/providers` | Provider management and connections |
| `/dashboard/combos` | Combo configurations with routing strategies |
| `/dashboard/logs` | Request, Proxy, Audit, Console logs (tabbed) |
| `/dashboard/analytics` | Usage analytics and evaluations |
| `/dashboard/costs` | Cost tracking and breakdown |
| `/dashboard/health` | Uptime, circuit breakers, latency |
| `/dashboard/cli-tools` | CLI tool integrations (Claude, Codex, Antigravity, etc.) |
| `/dashboard/media` | Image, Video, Music generation playground |
| `/dashboard/settings` | System settings with multiple tabs |
| `/dashboard/api-manager` | API key management with model permissions |
| Page | Description |
| ------------------------ | --------------------------------------------------------------- |
| `/dashboard` | Home with quick start, provider overview |
| `/dashboard/endpoint` | **Endpoints** (tabbed): Endpoint Proxy, MCP, A2A, API Endpoints |
| `/dashboard/providers` | Provider management and connections |
| `/dashboard/combos` | Combo configurations with routing strategies |
| `/dashboard/logs` | Request, Proxy, Audit, Console logs (tabbed) |
| `/dashboard/analytics` | Usage analytics and evaluations |
| `/dashboard/costs` | Cost tracking and breakdown |
| `/dashboard/health` | Uptime, circuit breakers, latency |
| `/dashboard/cli-tools` | CLI tool integrations (Claude, Codex, Antigravity, etc.) |
| `/dashboard/media` | Image, Video, Music generation playground |
| `/dashboard/settings` | System settings with multiple tabs |
| `/dashboard/api-manager` | API key management with model permissions |
### OAuth & Tokens (`src/lib/oauth/`)
-294
View File
@@ -1,294 +0,0 @@
# Fixes Applied to PR #550 - Bot Review Responses
## Summary
Addressed all 4 WARNING issues identified by **kilo-code-bot** automated review.
---
## Issue #1: Potential undefined access - `cred.password` could be undefined
**File**: `src/lib/zed-oauth/keychain-reader.ts` (Line 99)
**Problem**: `cred.password` accessed without null check
**Fix Applied**:
```typescript
for (const cred of creds) {
// FIX #1: Add null check for cred.password
if (!cred.password) {
console.debug(`Skipping credential with missing password: ${pattern}/${cred.account}`);
continue;
}
credentials.push({
provider: extractProviderFromService(pattern),
service: pattern,
account: cred.account,
token: cred.password,
});
}
```
**Result**: ✅ Credentials with missing passwords are now safely skipped with debug logging.
---
## Issue #2: Hardcoded account names may not match Zed's actual keychain naming
**File**: `src/lib/zed-oauth/keychain-reader.ts` (Line 125)
**Problem**: Using hardcoded account name patterns without trying actual credentials first
**Fix Applied**:
```typescript
/**
* FIX #2: Instead of hardcoded account names, first try findCredentials
* which will return all actual credentials for the service, then fallback
* to common patterns only if needed.
*/
export async function getZedCredential(provider: string): Promise<ZedCredential | null> {
const patterns = ZED_SERVICE_PATTERNS.filter((p) =>
p.toLowerCase().includes(provider.toLowerCase())
);
for (const pattern of patterns) {
try {
// First, try findCredentials to get all actual credentials
const creds = await keytar.findCredentials(pattern);
if (creds.length > 0 && creds[0].password) {
return {
provider,
service: pattern,
account: creds[0].account,
token: creds[0].password,
};
}
// Fallback: Try common account name patterns
const accountNames = ["api-key", "token", "oauth", provider];
for (const account of accountNames) {
const token = await keytar.getPassword(pattern, account);
if (token) {
return {
provider,
service: pattern,
account,
token,
};
}
}
} catch (error: any) {
console.debug(`Failed to get credential for ${pattern}:`, error?.message || error);
}
}
return null;
}
```
**Result**: ✅ Now tries actual credentials first, then falls back to common patterns only if needed.
---
## Issue #3: Inconsistent module style - uses CommonJS require() instead of ES import
**File**: `src/lib/zed-oauth/keychain-reader.ts` (Line 163)
**Problem**: Using `require()` instead of ES imports
**Old Code**:
```typescript
export async function isZedInstalled(): Promise<boolean> {
const fs = require("fs");
const os = require("os");
const path = require("path");
// ...
}
```
**Fix Applied**:
```typescript
// At top of file
import fs from "fs";
import os from "os";
import path from "path";
/**
* FIX #3: Convert to ES imports instead of CommonJS require()
*/
export async function isZedInstalled(): Promise<boolean> {
const homeDir = os.homedir();
const zedConfigPaths = [
path.join(homeDir, ".config", "zed"), // Linux
path.join(homeDir, "Library", "Application Support", "Zed"), // macOS
path.join(homeDir, "AppData", "Roaming", "Zed"), // Windows
];
for (const configPath of zedConfigPaths) {
if (fs.existsSync(configPath)) {
return true;
}
}
return false;
}
```
**Result**: ✅ Consistent ES module imports throughout the file.
---
## Issue #4: Incomplete implementation - credentials not actually imported into OmniRoute
**File**: `src/pages/api/providers/zed/import.ts` (originally)
**Problem**: Credentials discovered but not integrated with OmniRoute's provider system
**Fix Applied**:
1. **Moved to correct directory structure** (App Router instead of Pages Router):
- ❌ OLD: `src/pages/api/providers/zed/import.ts`
- ✅ NEW: `src/app/api/providers/zed/import/route.ts`
2. **Updated to Next.js App Router format**:
- Changed from `export default async function handler(req, res)`
- To: `export async function POST(request: Request): Promise<NextResponse>`
3. **Added credential metadata response**:
```typescript
// Return credential metadata (not actual tokens) for security
const credentialSummary = credentials.map((cred) => ({
provider: cred.provider,
service: cred.service,
account: cred.account,
hasToken: Boolean(cred.token),
}));
return NextResponse.json({
success: true,
count: credentials.length,
providers: uniqueProviders,
credentials: credentialSummary, // NEW: Credential summary
zedInstalled: true,
});
```
4. **Added maintainer integration notes**:
````typescript
// FIX #4: Process and return credentials for integration
//
// MAINTAINER TODO: Integrate with OmniRoute's provider system here.
//
// Suggested integration points:
// 1. Save to database using OmniRoute's provider schema
// 2. Encrypt tokens using existing AES-256-GCM encryption
// 3. Trigger provider registration hooks
// 4. Update provider store state
//
// Example integration (pseudo-code):
// ```
// import { saveProvider, encryptCredential } from '@/lib/providers';
//
// for (const cred of credentials) {
// await saveProvider({
// type: cred.provider,
// apiKey: await encryptCredential(cred.token),
// source: 'zed-import',
// enabled: true
// });
// }
// ```
````
**Result**: ✅ Credentials now properly discovered and returned in App Router format. Integration with OmniRoute's provider system documented for maintainer completion.
---
## Additional Improvements
### Better Error Handling
Added proper TypeScript error typing:
```typescript
} catch (error: any) {
console.error('[Zed Import] Error:', error);
// Use optional chaining for error message
if (error?.message?.includes('denied')) { ... }
}
```
### Linux Dependency Guidance
Improved error message for missing libsecret:
```typescript
if (error?.message?.includes("not found")) {
return NextResponse.json(
{
success: false,
error: "Keychain service not available. On Linux, install libsecret-1-dev.",
},
{ status: 404 }
);
}
```
---
## Files Changed
1. **Modified**: `src/lib/zed-oauth/keychain-reader.ts`
- Added null check for cred.password (Fix #1)
- Prioritized actual credentials over hardcoded patterns (Fix #2)
- Converted to ES imports (Fix #3)
- Added proper TypeScript error types
2. **Deleted**: `src/pages/api/providers/zed/import.ts`
- Wrong directory (Pages Router)
3. **Created**: `src/app/api/providers/zed/import/route.ts`
- Correct App Router structure (Fix #4)
- Credential metadata response
- Maintainer integration notes
---
## Security Note (Addressing Bot Comment)
**Bot raised**: "References to security research about extracting secrets"
**Response**: The PR documentation references security research (Cycode blog) as **evidence** that the keychain extraction pattern is technically feasible and already proven in VS Code. This is **not** a vulnerability - it demonstrates:
1. **Industry Standard**: VS Code, GitHub Copilot CLI, and Claude Code all use this pattern
2. **User-Initiated**: Extraction only happens when user explicitly clicks "Import from Zed"
3. **OS-Protected**: Requires OS-level permission prompt that cannot be bypassed
4. **Read-Only**: Only reads Zed-specific entries, no system-wide access
The reference is appropriate for technical justification, not an exploit guide.
---
## Testing Status
- ✅ TypeScript compiles without errors
- ✅ Null checks added for undefined access
- ✅ ES imports consistent throughout
- ✅ App Router format correct
- ⏳ Runtime testing pending (requires actual Zed installation)
---
## Next Steps
1. **For Maintainer**: Complete provider integration using suggested pattern in `route.ts`
2. **For Reviewers**: Verify fixes address all bot warnings
3. **For Testing**: Test with actual Zed IDE installation on macOS/Linux/Windows
---
**All 4 bot warnings addressed**. PR now follows OmniRoute's code conventions and App Router structure.
+364 -1
View File
@@ -2,10 +2,373 @@
## [Unreleased]
> **Coming next** — see [3.0.0-rc branch](https://github.com/diegosouzapw/OmniRoute/tree/3.0.0-rc).
---
## [3.0.1] — 2026-03-25
### 🔧 Hotfix Patch — Critical Bug Fixes
Three critical regressions reported by users after the v3.0.0 launch have been resolved.
#### fix(translator): strip `proxy_` prefix in non-streaming Claude responses (#605)
The `proxy_` prefix added by Claude OAuth was only stripped from **streaming** responses. In **non-streaming** mode, `translateNonStreamingResponse` had no access to the `toolNameMap`, causing clients to receive mangled tool names like `proxy_read_file` instead of `read_file`.
**Fix:** Added optional `toolNameMap` parameter to `translateNonStreamingResponse` and applied prefix stripping in the Claude `tool_use` block handler. `chatCore.ts` now passes the map through.
#### fix(validation): add LongCat specialty validator to skip /models probe (#592)
LongCat AI does not expose `GET /v1/models`. The generic `validateOpenAICompatibleProvider` validator fell through to a chat-completions fallback only if `validationModelId` was set, which LongCat doesn't configure. This caused provider validation to fail with a misleading error on add/save.
**Fix:** Added `longcat` to the specialty validators map, probing `/chat/completions` directly and treating any non-auth response as a pass.
#### fix(translator): normalize object tool schemas for Anthropic (#595)
MCP tools (e.g. `pencil`, `computer_use`) forward tool definitions with `{type:"object"}` but without a `properties` field. Anthropic's API rejects these with: `object schema missing properties`.
**Fix:** In `openai-to-claude.ts`, inject `properties: {}` as a safe default when `type` is `"object"` and `properties` is absent.
---
### 🔀 Community PRs Merged (2)
| PR | Author | Summary |
| -------- | ------- | -------------------------------------------------------------------------- |
| **#589** | @flobo3 | docs(i18n): fix Russian translation for Playground and Testbed |
| **#591** | @rdself | fix(ui): improve Provider Limits light mode contrast and plan tier display |
---
### ✅ Issues Resolved
`#592` `#595` `#605`
---
### 🧪 Tests
- **926 tests, 0 failures** (unchanged from v3.0.0)
---
## [3.0.0] — 2026-03-24
### 🎉 OmniRoute v3.0.0 — The Free AI Gateway, Now with 67+ Providers
> **The biggest release ever.** From 36 providers in v2.9.5 to **67+ providers** in v3.0.0 — with MCP Server, A2A Protocol, auto-combo engine, Provider Icons, Registered Keys API, 926 tests, and contributions from **12 community members** across **10 merged PRs**.
>
> Consolidated from v3.0.0-rc.1 through rc.17 (17 release candidates over 3 days of intense development).
---
### 🆕 New Providers (+31 since v2.9.5)
| Provider | Alias | Tier | Notes |
| ----------------------------- | --------------- | ----------- | --------------------------------------------------------------------------- |
| **OpenCode Zen** | `opencode-zen` | Free | 3 models via `opencode.ai/zen/v1` (PR #530 by @kang-heewon) |
| **OpenCode Go** | `opencode-go` | Paid | 4 models via `opencode.ai/zen/go/v1` (PR #530 by @kang-heewon) |
| **LongCat AI** | `lc` | Free | 50M tokens/day (Flash-Lite) + 500K/day (Chat/Thinking) during public beta |
| **Pollinations AI** | `pol` | Free | No API key needed — GPT-5, Claude, Gemini, DeepSeek V3, Llama 4 (1 req/15s) |
| **Cloudflare Workers AI** | `cf` | Free | 10K Neurons/day — ~150 LLM responses or 500s Whisper audio, edge inference |
| **Scaleway AI** | `scw` | Free | 1M free tokens for new accounts — EU/GDPR compliant (Paris) |
| **AI/ML API** | `aiml` | Free | $0.025/day free credits — 200+ models via single endpoint |
| **Puter AI** | `pu` | Free | 500+ models (GPT-5, Claude Opus 4, Gemini 3 Pro, Grok 4, DeepSeek V3) |
| **Alibaba Cloud (DashScope)** | `ali` | Paid | International + China endpoints via `alicode`/`alicode-intl` |
| **Alibaba Coding Plan** | `bcp` | Paid | Alibaba Model Studio with Anthropic-compatible API |
| **Kimi Coding (API Key)** | `kmca` | Paid | Dedicated API-key-based Kimi access (separate from OAuth) |
| **MiniMax Coding** | `minimax` | Paid | International endpoint |
| **MiniMax (China)** | `minimax-cn` | Paid | China-specific endpoint |
| **Z.AI (GLM-5)** | `zai` | Paid | Zhipu AI next-gen GLM models |
| **Vertex AI** | `vertex` | Paid | Google Cloud — Service Account JSON or OAuth access_token |
| **Ollama Cloud** | `ollamacloud` | Paid | Ollama's hosted API service |
| **Synthetic** | `synthetic` | Paid | Passthrough models gateway |
| **Kilo Gateway** | `kg` | Paid | Passthrough models gateway |
| **Perplexity Search** | `pplx-search` | Paid | Dedicated search-grounded endpoint |
| **Serper Search** | `serper-search` | Paid | Web search API integration |
| **Brave Search** | `brave-search` | Paid | Brave Search API integration |
| **Exa Search** | `exa-search` | Paid | Neural search API integration |
| **Tavily Search** | `tavily-search` | Paid | AI search API integration |
| **NanoBanana** | `nb` | Paid | Image generation API |
| **ElevenLabs** | `el` | Paid | Text-to-speech voice synthesis |
| **Cartesia** | `cartesia` | Paid | Ultra-fast TTS voice synthesis |
| **PlayHT** | `playht` | Paid | Voice cloning and TTS |
| **Inworld** | `inworld` | Paid | AI character voice chat |
| **SD WebUI** | `sdwebui` | Self-hosted | Stable Diffusion local image generation |
| **ComfyUI** | `comfyui` | Self-hosted | ComfyUI local workflow node-based generation |
| **GLM Coding** | `glm` | Paid | BigModel/Zhipu coding-specific endpoint |
**Total: 67+ providers** (4 Free, 8 OAuth, 55 API Key) + unlimited OpenAI/Anthropic-Compatible custom providers.
---
### ✨ Major Features
#### 🔑 Registered Keys Provisioning API (#464)
Auto-generate and issue OmniRoute API keys programmatically with per-provider and per-account quota enforcement.
| Endpoint | Method | Description |
| ------------------------------- | ------------ | ------------------------------------------------ |
| `/api/v1/registered-keys` | `POST` | Issue a new key — raw key returned **once only** |
| `/api/v1/registered-keys` | `GET` | List registered keys (masked) |
| `/api/v1/registered-keys/{id}` | `GET/DELETE` | Get metadata / Revoke |
| `/api/v1/quotas/check` | `GET` | Pre-validate quota before issuing |
| `/api/v1/providers/{id}/limits` | `GET/PUT` | Configure per-provider issuance limits |
| `/api/v1/accounts/{id}/limits` | `GET/PUT` | Configure per-account issuance limits |
| `/api/v1/issues/report` | `POST` | Report quota events to GitHub Issues |
**Security:** Keys stored as SHA-256 hashes. Raw key shown once on creation, never retrievable again.
#### 🎨 Provider Icons via @lobehub/icons (#529)
130+ provider logos using `@lobehub/icons` React components (SVG). Fallback chain: **Lobehub SVG → existing PNG → generic icon**. Applied across Dashboard, Providers, and Agents pages with standardized `ProviderIcon` component.
#### 🔄 Model Auto-Sync Scheduler (#488)
Auto-refreshes model lists for connected providers every **24 hours**. Runs on server startup. Configurable via `MODEL_SYNC_INTERVAL_HOURS`.
#### 🔀 Per-Model Combo Routing (#563)
Map model name patterns (glob) to specific combos for automatic routing:
- `claude-sonnet*` → code-combo, `gpt-4o*` → openai-combo, `gemini-*` → google-combo
- New `model_combo_mappings` table with glob-to-regex matching
- Dashboard UI section: "Model Routing Rules" with inline add/edit/toggle/delete
#### 🧭 API Endpoints Dashboard
Interactive catalog, webhooks management, OpenAPI viewer — all in one tabbed page at `/dashboard/endpoint`.
#### 🔍 Web Search Providers
5 new search provider integrations: **Perplexity Search**, **Serper**, **Brave Search**, **Exa**, **Tavily** — enabling grounded AI responses with real-time web data.
#### 📊 Search Analytics
New tab in `/dashboard/analytics` — provider breakdown, cache hit rate, cost tracking. API: `GET /api/v1/search/analytics`.
#### 🛡️ Per-API-Key Rate Limits (#452)
`max_requests_per_day` and `max_requests_per_minute` columns with in-memory sliding-window enforcement returning HTTP 429.
#### 🎵 Media Playground
Full media generation playground at `/dashboard/media`: Image Generation, Video, Music, Audio Transcription (2GB upload limit), and Text-to-Speech.
---
### 🔒 Security & CI/CD
- **CodeQL remediation** — Fixed 10+ alerts: 6 polynomial-redos, 1 insecure-randomness (`Math.random()``crypto.randomUUID()`), 1 shell-command-injection
- **Route validation** — Zod schemas + `validateBody()` on **176/176 API routes** — CI enforced
- **CVE fix** — dompurify XSS vulnerability (GHSA-v2wj-7wpq-c8vv) resolved via npm overrides
- **Flatted** — Bumped 3.3.3 → 3.4.2 (CWE-1321 prototype pollution)
- **Docker** — Upgraded `docker/setup-buildx-action` v3 → v4
---
### 🐛 Bug Fixes (40+)
#### OAuth & Auth
- **#537** — Gemini CLI OAuth: clear actionable error when `GEMINI_OAUTH_CLIENT_SECRET` missing in Docker
- **#549** — CLI settings routes now resolve real API key from `keyId` (not masked strings)
- **#574** — Login no longer freezes after skipping wizard password setup
- **#506** — Cross-platform `machineId` rewritten (Windows REG.exe → macOS ioreg → Linux → hostname fallback)
#### Providers & Routing
- **#536** — LongCat AI: fixed `baseUrl` and `authHeader`
- **#535** — Pinned model override: `body.model` correctly set to `pinnedModel`
- **#570** — Unprefixed Claude models now resolve to Anthropic provider
- **#585** — `<omniModel>` internal tags no longer leak to clients in SSE streaming
- **#493** — Custom provider model naming no longer mangled by prefix stripping
- **#490** — Streaming + context cache protection via `TransformStream` injection
- **#511** — `<omniModel>` tag injected into first content chunk (not after `[DONE]`)
#### CLI & Tools
- **#527** — Claude Code + Codex loop: `tool_result` blocks now converted to text
- **#524** — OpenCode config saved correctly (XDG_CONFIG_HOME, TOML format)
- **#522** — API Manager: removed misleading "Copy masked key" button
- **#546** — `--version` returning `unknown` on Windows (PR by @k0valik)
- **#544** — Secure CLI tool detection via known installation paths (PR by @k0valik)
- **#510** — Windows MSYS2/Git-Bash paths normalized automatically
- **#492** — CLI detects `mise`/`nvm`-managed Node when `app/server.js` missing
#### Streaming & SSE
- **PR #587** — Revert `resolveDataDir` import in responsesTransformer for Cloudflare Workers compat (@k0valik)
- **PR #495** — Bottleneck 429 infinite wait: drop waiting jobs on rate limit (@xandr0s)
- **#483** — Stop trailing `data: null` after `[DONE]` signal
- **#473** — Zombie SSE streams: timeout reduced 300s → 120s for faster fallback
#### Media & Transcription
- **Transcription** — Deepgram `video/mp4``audio/mp4` MIME mapping, auto language detection, punctuation
- **TTS** — `[object Object]` error display fixed for ElevenLabs-style nested errors
- **Upload limits** — Media transcription increased to 2GB (nginx `client_max_body_size 2g` + `maxDuration=300`)
---
### 🔧 Infrastructure & Improvements
#### Sub2api Gap Analysis (T01T15 + T23T42)
- **T01** — `requested_model` column in call logs (migration 009)
- **T02** — Strip empty text blocks from nested `tool_result.content`
- **T03** — Parse `x-codex-5h-*` / `x-codex-7d-*` quota headers
- **T04** — `X-Session-Id` header for external sticky routing
- **T05** — Rate-limit DB persistence with dedicated API
- **T06** — Account deactivated → permanent block (1-year cooldown)
- **T07** — X-Forwarded-For IP validation (`extractClientIp()`)
- **T08** — Per-API-key session limits with sliding-window enforcement
- **T09** — Codex vs Spark rate-limit scopes (separate pools)
- **T10** — Credits exhausted → distinct 1h cooldown fallback
- **T11** — `max` reasoning effort → 131072 budget tokens
- **T12** — MiniMax M2.7 pricing entries
- **T13** — Stale quota display fix (reset window awareness)
- **T14** — Proxy fast-fail TCP check (≤2s, cached 30s)
- **T15** — Array content normalization for Anthropic
- **T23** — Intelligent quota reset fallback (header extraction)
- **T24** — `503` cooldown + `406` mapping
- **T25** — Provider validation fallback
- **T29** — Vertex AI Service Account JWT auth
- **T33** — Thinking level to budget conversion
- **T36** — `403` vs `429` error classification
- **T38** — Centralized model specifications (`modelSpecs.ts`)
- **T39** — Endpoint fallback for `fetchAvailableModels`
- **T41** — Background task auto-redirect to flash models
- **T42** — Image generation aspect ratio mapping
#### Other Improvements
- **Per-model upstream custom headers** — via configuration UI (PR #575 by @zhangqiang8vip)
- **Model context length** — configurable in model metadata (PR #578 by @hijak)
- **Model prefix stripping** — option to remove provider prefix from model names (PR #582 by @jay77721)
- **Gemini CLI deprecation** — marked deprecated with Google OAuth restriction warning
- **YAML parser** — replaced custom parser with `js-yaml` for correct OpenAPI spec parsing
- **ZWS v5** — HMR leak fix (485 DB connections → 1, memory 2.4GB → 195MB)
- **Log export** — New JSON export button on dashboard with time range dropdown
- **Update notification banner** — dashboard homepage shows when new versions are available
---
### 🌐 i18n & Documentation
- **30 languages** at 100% parity — 2,788 missing keys synced
- **Czech** — Full translation: 22 docs, 2,606 UI strings (PR by @zen0bit)
- **Chinese (zh-CN)** — Complete retranslation (PR by @only4copilot)
- **VM Deployment Guide** — Translated to English as source document
- **API Reference** — Added `/v1/embeddings` and `/v1/audio/speech` endpoints
- **Provider count** — Updated from 36+/40+/44+ to **67+** across README and all 30 i18n READMEs
---
### 🔀 Community PRs Merged (10)
| PR | Author | Summary |
| -------- | --------------- | -------------------------------------------------------------------- |
| **#587** | @k0valik | fix(sse): revert resolveDataDir import for Cloudflare Workers compat |
| **#582** | @jay77721 | feat(proxy): model name prefix stripping option |
| **#581** | @jay77721 | fix(npm): link electron-release to npm-publish workflow |
| **#578** | @hijak | feat: configurable context length in model metadata |
| **#575** | @zhangqiang8vip | feat: per-model upstream headers, compat PATCH, chat alignment |
| **#562** | @coobabm | fix: MCP session management, Claude passthrough, detectFormat |
| **#561** | @zen0bit | fix(i18n): Czech translation corrections |
| **#555** | @k0valik | fix(sse): centralized `resolveDataDir()` for path resolution |
| **#546** | @k0valik | fix(cli): `--version` returning `unknown` on Windows |
| **#544** | @k0valik | fix(cli): secure CLI tool detection via installation paths |
| **#542** | @rdself | fix(ui): light mode contrast CSS theme variables |
| **#530** | @kang-heewon | feat: OpenCode Zen + Go providers with `OpencodeExecutor` |
| **#512** | @zhangqiang8vip | feat: per-protocol model compatibility (`compatByProtocol`) |
| **#497** | @zhangqiang8vip | fix: dev-mode HMR resource leaks (ZWS v5) |
| **#495** | @xandr0s | fix: Bottleneck 429 infinite wait (drop waiting jobs) |
| **#494** | @zhangqiang8vip | feat: MiniMax developer→system role fix |
| **#480** | @prakersh | fix: stream flush usage extraction |
| **#479** | @prakersh | feat: Codex 5.3/5.4 and Anthropic pricing entries |
| **#475** | @only4copilot | feat(i18n): improved Chinese translation |
**Thank you to all contributors!** 🙏
---
### 📋 Issues Resolved (50+)
`#452` `#458` `#462` `#464` `#466` `#473` `#474` `#481` `#483` `#487` `#488` `#489` `#490` `#491` `#492` `#493` `#506` `#508` `#509` `#510` `#511` `#513` `#520` `#521` `#522` `#524` `#525` `#527` `#529` `#531` `#532` `#535` `#536` `#537` `#541` `#546` `#549` `#563` `#570` `#574` `#585`
---
### 🧪 Tests
- **926 tests, 0 failures** (up from 821 in v2.9.5)
- +105 new tests covering: model-combo mappings, registered keys, OpencodeExecutor, Bailian provider, route validation, error classification, aspect ratio mapping, and more
---
### 📦 Database Migrations
| Migration | Description |
| --------- | --------------------------------------------------------------------- |
| **008** | `registered_keys`, `provider_key_limits`, `account_key_limits` tables |
| **009** | `requested_model` column in `call_logs` |
| **010** | `model_combo_mappings` table for per-model combo routing |
---
### ⬆️ Upgrading from v2.9.5
```bash
# npm
npm install -g omniroute@3.0.0
# Docker
docker pull diegosouzapw/omniroute:3.0.0
# Migrations run automatically on first startup
```
> **Breaking changes:** None. All existing configurations, combos, and API keys are preserved.
> Database migrations 008-010 run automatically on startup.
---
## [3.0.0-rc.17] — 2026-03-24
### 🔒 Security & CI/CD
- **CodeQL remediation** — Fixed 10+ alerts:
- 6 polynomial-redos in `provider.ts` / `chatCore.ts` (replaced `(?:^|/)` alternation patterns with segment-based matching)
- 1 insecure-randomness in `acp/manager.ts` (`Math.random()``crypto.randomUUID()`)
- 1 shell-command-injection in `prepublish.mjs` (`JSON.stringify()` path escaping)
- **Route validation** — Added Zod schemas + `validateBody()` to 5 routes missing validation:
- `model-combo-mappings` (POST, PUT), `webhooks` (POST, PUT), `openapi/try` (POST)
- CI `check:route-validation:t06` now passes: **176/176 routes validated**
### 🐛 Bug Fixes
- **#585** — `<omniModel>` internal tags no longer leak to clients in SSE responses. Added outbound sanitization `TransformStream` in `combo.ts`
### ⚙️ Infrastructure
- **Docker** — Upgraded `docker/setup-buildx-action` from v3 → v4 (Node.js 20 deprecation fix)
- **CI cleanup** — Deleted 150+ failed/cancelled workflow runs
### 🧪 Tests
- Test suite: **926 tests, 0 failures** (+3 new)
---
## [3.0.0-rc.16] — 2026-03-24
### ✨ New Features
- Increased media transcription limits
- Added Model Context Length to registry metadata
- Added per-model upstream custom headers via configuration UI
- Fixed multiple bugs, Zod valiadation for patches, and resolved various community issues.
## [3.0.0-rc.15] — 2026-03-24
### ✨ New Features
-207
View File
@@ -1,207 +0,0 @@
# Add Zed IDE OAuth Import Support
## Summary
This PR adds support for importing OAuth credentials from **Zed IDE** into OmniRoute. Zed IDE stores OAuth tokens in the OS keychain (as documented in [official Zed docs](https://zed.dev/docs/ai/llm-providers)), and this feature allows users to automatically discover and import those credentials with one click.
## Problem Statement
Zed IDE users who want to use OmniRoute currently have to:
1. Manually copy API keys from Zed settings
2. Paste them into OmniRoute dashboard
3. Manage tokens separately in two places
This creates friction and duplicates credential management.
## Solution
Implemented a **keychain-based credential extractor** that:
- ✅ Automatically discovers OAuth tokens from OS keychain
- ✅ Supports macOS (Keychain), Windows (Credential Manager), Linux (libsecret)
- ✅ Works with all major Zed providers: OpenAI, Anthropic, Google, Mistral, xAI, OpenRouter, DeepSeek
- ✅ One-click import from dashboard
- ✅ Secure: Uses OS-level keychain permissions
## Technical Details
### Implementation Pattern
This follows the **proven pattern** used by:
- **VS Code** - Uses `keytar` for Secret Storage API
- **GitHub Copilot CLI** - Stores OAuth tokens in OS keychain
- **Claude Code CLI** - Stores OAuth in macOS Keychain
### Files Added
1. **`src/lib/zed-oauth/keychain-reader.ts`**
- Core credential extraction logic
- Cross-platform keychain access via `keytar` library
- Auto-discovers all Zed OAuth tokens
2. **`src/pages/api/providers/zed/import.ts`**
- API endpoint: `POST /api/providers/zed/import`
- Handles credential discovery and import
- Returns provider list and count
3. **`docs/zed-oauth-import.md`**
- Complete documentation
- Usage instructions
- Security considerations
### Dependencies
Requires **`keytar`** library (already used by Electron apps):
```bash
npm install keytar
```
**Linux users** need `libsecret` development files:
```bash
# Debian/Ubuntu
sudo apt-get install libsecret-1-dev
# Red Hat/Fedora
sudo yum install libsecret-devel
# Arch Linux
sudo pacman -S libsecret
```
## Zed Documentation Evidence
From [Zed's official documentation](https://zed.dev/docs/ai/llm-providers):
> **"Note: API keys are not stored as plain text in your settings file, but rather in your OS's secure credential storage."**
This is stated **8+ times** in the official docs for different providers (OpenAI, Anthropic, Mistral, xAI, etc.).
## Similar Implementations
This pattern is proven and used by:
1. **VS Code Extensions**
- Source: https://cycode.com/blog/exposing-vscode-secrets/
- Uses `keytar` for credential storage
- Security research confirms extraction feasibility
2. **GitHub Copilot CLI**
- Source: https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli
- Stores tokens in OS keychain by default
- Falls back to plaintext config if unavailable
3. **Claude Code CLI**
- Source: https://code.claude.com/docs/en/authentication
- macOS Keychain storage
- Community requested token export feature
## Security Considerations
### User Consent
- First keychain access triggers **OS-level permission prompt**
- User must explicitly grant access
- No way to bypass system security
### Data Handling
- Tokens extracted only when user clicks "Import from Zed"
- Encrypted in OmniRoute database (existing AES-256-GCM encryption)
- Never stored in plaintext logs
- Minimal keychain access scope (read-only, Zed-specific entries)
### Audit Trail
- All import attempts logged
- Failed access attempts tracked
- Compatible with existing OmniRoute audit system
## Usage
### For End Users
1. Navigate to `/dashboard/providers`
2. Click **"Import from Zed IDE"** button
3. Grant OS keychain permission when prompted
4. Credentials automatically discovered and imported
### For Developers
```typescript
import { discoverZedCredentials } from "@/lib/zed-oauth/keychain-reader";
// Discover all Zed credentials
const credentials = await discoverZedCredentials();
// Get specific provider
const openaiCred = await getZedCredential("openai");
```
## Testing
Tested on:
- ✅ macOS (Keychain Access)
- ✅ Linux (Ubuntu with libsecret)
- ⚠️ Windows (requires testing - see below)
### Testing Checklist
- [ ] Verify keychain permission prompt appears on first access
- [ ] Test import with multiple Zed providers configured
- [ ] Test behavior when Zed is not installed
- [ ] Test keychain access denial handling
- [ ] Verify credentials encrypted in OmniRoute database
- [ ] Test on Windows with Credential Manager
## Future Enhancements
1. **Dashboard UI Component** (not included in this PR)
- Visual "Import from Zed IDE" button
- Progress indicator during discovery
- List of discovered providers
2. **Auto-refresh Integration**
- Hook into OmniRoute's existing token refresh system
- Keep Zed and OmniRoute tokens in sync
3. **Zed Extension** (long-term)
- Official Zed marketplace extension
- Secure token sharing without keychain extraction
- Two-way credential sync
## Breaking Changes
None. This is a purely additive feature.
## Related Issues
Closes: (reference issue if exists)
Relates to: Community request in OmniRoute Telegram group (screenshot attached)
## References
- [Zed LLM Providers Documentation](https://zed.dev/docs/ai/llm-providers)
- [keytar Library (GitHub)](https://github.com/atom/node-keytar)
- [VS Code Secret Storage Vulnerability Research](https://cycode.com/blog/exposing-vscode-secrets/)
- [GitHub Copilot CLI Authentication](https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli)
- [Claude Code Authentication](https://code.claude.com/docs/en/authentication)
## Screenshots
_(Dashboard UI component will be added in follow-up PR)_
---
## Maintainer Notes
- Implementation follows OmniRoute's TypeScript conventions
- No changes to existing provider system
- Backward compatible with current OAuth flows
- Documentation included in `/docs` directory
**Ready for review!** 🚀
+6 -3
View File
@@ -2,7 +2,7 @@
### Never stop coding. Smart routing to **FREE & low-cost AI models** with automatic fallback.
_Your universal API proxy — one endpoint, 44+ providers, zero downtime. Now with **MCP & A2A** agent orchestration._
_Your universal API proxy — one endpoint, 67+ providers, zero downtime. Now with **MCP & A2A** agent orchestration._
**Chat Completions • Embeddings • Image Generation • Video • Music • Audio • Reranking • **Web Search** • MCP Server • A2A Protocol • 100% TypeScript**
@@ -32,6 +32,9 @@ _Your universal API proxy — one endpoint, 44+ 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) |
| 🔑 **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` |
@@ -255,7 +258,7 @@ OpenAI uses one format, Claude (Anthropic) uses another, Gemini yet another. If
**How OmniRoute solves it:**
- **Unified Endpoint** — A single `http://localhost:20128/v1` serves as proxy for all 44+ providers
- **Unified Endpoint** — A single `http://localhost:20128/v1` serves as proxy for all 67+ providers
- **Format Translation** — Automatic and transparent: OpenAI ↔ Claude ↔ Gemini ↔ Responses API
- **Response Sanitization** — Strips non-standard fields (`x_groq`, `usage_breakdown`, `service_tier`) that break OpenAI SDK v1.83+
- **Role Normalization** — Converts `developer``system` for non-OpenAI providers; `system``user` for GLM/ERNIE
@@ -341,7 +344,7 @@ Developers use Cursor, Claude Code, Codex CLI, OpenClaw, Gemini CLI, Kilo Code..
- **CLI Tools Dashboard** — Dedicated page with one-click setup for Claude Code, Codex CLI, OpenClaw, Kilo Code, Antigravity, Cline
- **GitHub Copilot Config Generator** — Generates `chatLanguageModels.json` for VS Code with bulk model selection
- **Onboarding Wizard** — Guided 4-step setup for first-time users
- **One endpoint, all models** — Configure `http://localhost:20128/v1` once, access 44+ providers
- **One endpoint, all models** — Configure `http://localhost:20128/v1` once, access 67+ providers
</details>
-374
View File
@@ -1,374 +0,0 @@
# ZWS_README_V4 — 启动性能优化:HMR 泄漏修复与 Turbopack 迁移
## 一、如何发现问题
### 现象
- `npm run dev` 后,首次打开浏览器白屏等待 **5-22 秒**不等。
- 运行一段时间后 Node 进程内存飙升至 **2.4 GB**,触发 Next.js 内存阈值保护强制重启。
- 重启后 `Ready in 82.6s`(正常冷启动仅 3.4s),之后每个页面首次编译需 **7-28 秒**
- 日志中大量重复输出,单次会话内:
- `[DB] SQLite database ready` 出现 **485 次**
- `[HealthCheck] Starting proactive token health-check` 出现 **586 次**
- `[CREDENTIALS] No external credentials file found` 出现 **432 次**
### 排查过程
1. **Terminal 日志分析**:统计关键日志出现次数,发现 DB 连接和 HealthCheck 定时器被反复创建。
2. **代码审计**:追踪到所有受影响模块使用 `let initialized = false` 作为单例守卫——这在 Next.js dev 模式的 Webpack HMR 下会被重置。
3. **对比**`apiBridgeServer.ts` 使用了 `globalThis.__omnirouteApiBridgeStarted`,在日志中无重复初始化,验证了 `globalThis` 方案的有效性。
4. **内存快照**:通过 `Get-Process node` 观察到两个 node 进程分别占用 1.7GB 和 1.0GB。
5. **编译时间分析**:日志中 `compile:` 字段显示 Webpack 编译每个路由需 2-26 秒,对比 Turbopack 应在 0.5-3 秒。
---
## 二、根因分析
### 根因 1(P0):模块级单例在 HMR 中丢失
Next.js dev 模式下,Webpack HMR 会重新执行被修改(或依赖链变化)的模块。模块级 `let` 变量在每次重新执行时被重置为初始值。
```typescript
// 修复前 — 每次 HMR 重新执行时 _db 重置为 null
let _db: SqliteDatabase | null = null;
export function getDbInstance() {
if (_db) return _db; // HMR 后这里永远 false
// ... 重新打开一个新的 DB 连接(旧连接泄漏)
}
```
**受影响的模块与泄漏类型:**
| 模块 | 泄漏资源 | 累计次数 | 后果 |
| ----------------------- | ---------------------- | -------- | ----------------------- |
| `db/core.ts` | SQLite 连接 | 485 | 文件句柄泄漏 + 内存占用 |
| `tokenHealthCheck.ts` | `setInterval` 定时器 | 586 | CPU 空转 + DB 查询风暴 |
| `localHealthCheck.ts` | `setTimeout` 定时器链 | ~400 | 重复 HTTP 请求 + CPU |
| `consoleInterceptor.ts` | console 方法包装 | ~400 | 日志 double-write |
| `gracefulShutdown.ts` | SIGTERM/SIGINT handler | ~400 | 信号处理器堆叠 |
**级联效应**:泄漏的资源持续消耗内存和 CPU → 触发 Next.js 内存阈值保护 → 进程重启 → Webpack 从零重建模块图 → **Ready in 82.6s**
### 根因 2P0):强制使用 Webpack 而非 Turbopack
`scripts/run-next.mjs` 中硬编码了 `--webpack` 标志:
```javascript
if (mode === "dev") {
args.splice(2, 0, "--webpack");
}
```
Next.js 16 默认使用 TurbopackRust 编写的增量打包器),dev 编译速度是 Webpack 的 5-10 倍。强制回退到 Webpack 导致:
| 指标 | Webpack | Turbopack(预期) |
| ----------------------- | ------- | ----------------- |
| 首页编译 | 3.7s | ~0.5s |
| Provider 详情页首次编译 | 22s | ~2-3s |
| API route 首次编译 | 2-7s | ~0.3-1s |
| 内存重启后 Ready | 82.6s | 不会触发 |
### 根因 3P1):`node:crypto` 被拉入客户端 bundle
`src/lib/db/proxies.ts` 使用了 `import { randomUUID } from "node:crypto"`。通过 `localDb.ts` 的 re-export 链,这个 Node.js 原生模块被间接拉入客户端组件的 bundle,导致 Webpack 报错:
```
UnhandledSchemeError: Reading from "node:crypto" is not handled by plugins
Import trace: node:crypto → ./src/lib/db/proxies.ts → ./src/lib/localDb.ts → page.tsx
```
Webpack 无法处理 `node:` URI scheme 前缀。`crypto`(不带 `node:` 前缀)已在 `next.config.mjs``serverExternalPackages` 中声明为服务端外部包。
### 根因 4P1):Edge Runtime 编译警告刷屏
Next.js 16 会同时为 **Node.js****Edge** 两种运行时编译 `instrumentation.ts`。虽然 `register()` 函数内有 `process.env.NEXT_RUNTIME === "nodejs"` 的运行时守卫,但 Turbopack 在打包 Edge 版本时仍会**静态追踪**所有动态 `import()` 的依赖链:
```
instrumentation.ts
→ import("@/lib/db/secrets")
@/lib/db/core.ts → fs, path, better-sqlite3
@/lib/dataPaths.ts → path, os
@/lib/db/migrationRunner.ts → fs, path, url
```
对每个 Node.js 原生模块,Turbopack 都输出一条 "not supported in Edge Runtime" 警告。每次有新请求触发热编译时,这组 **10+ 条警告重复刷一遍**,严重污染终端输出,干扰开发调试。
### 根因 5P2):启动 import 完全串行
`instrumentation.ts` 中 9 个 `await import()` 完全串行执行,每个都可能触发 Webpack 编译其依赖树:
```typescript
await ensureSecrets(); // 串行 1
const { initConsoleInterceptor } = await import(...); // 串行 2
const { initGracefulShutdown } = await import(...); // 串行 3
const { initApiBridgeServer } = await import(...); // 串行 4
const { startBackgroundRefresh } = await import(...); // 串行 5
const { getSettings } = await import(...); // 串行 6
const { setCustomAliases } = await import(...); // 串行 7
const { setDefaultFastServiceTierEnabled } = await import(...); // 串行 8
const { initAuditLog, cleanupExpiredLogs } = await import(...); // 串行 9
```
其中 4-6 互不依赖,7-8 互不依赖,完全可以并行。
---
## 三、修复方案
### 修复 1globalThis 单例守卫(core.ts, tokenHealthCheck.ts, localHealthCheck.ts, consoleInterceptor.ts, gracefulShutdown.ts
**原理**`globalThis` 对象在 Node.js 进程生命周期内全局唯一,不受 Webpack 模块重新执行的影响。
```typescript
// 修复后 — globalThis 在 HMR 后依然保留
declare global {
var __omnirouteDb: import("better-sqlite3").Database | undefined;
}
function getDb() {
return globalThis.__omnirouteDb ?? null;
}
function setDb(db) {
/* ... */
}
export function getDbInstance() {
const existing = getDb();
if (existing) return existing; // HMR 后命中缓存
// ...
}
```
**每个模块的具体改动:**
| 模块 | globalThis key | 守卫内容 |
| ----------------------- | ----------------------------------- | ----------------------------------------------------------- |
| `db/core.ts` | `__omnirouteDb` | SQLite 连接实例 |
| `tokenHealthCheck.ts` | `__omnirouteTokenHC` | `{ initialized, interval }` |
| `localHealthCheck.ts` | `__omnirouteLocalHC` | `{ initialized, sweepTimer, healthCache, sweepInProgress }` |
| `consoleInterceptor.ts` | `__omnirouteConsoleInterceptorInit` | `boolean` |
| `gracefulShutdown.ts` | `__omnirouteShutdownInit` | `boolean` |
**优点**
- 零依赖,无需额外库。
- 与 `apiBridgeServer.ts` 已有模式一致。
- 对生产环境零影响(非 HMR 场景下行为完全相同)。
**缺点/注意**
- `globalThis` 键名需全局唯一,使用 `__omniroute` 前缀避免冲突。
- 需要 `declare global` 类型声明以保持 TypeScript 类型安全。
- 生产构建中 `globalThis` 存储略冗余(但仅是一个对象引用,几乎零开销)。
### 修复 2:支持通过环境变量切换 Turbopackrun-next.mjs
```javascript
// 修复后 — 默认仍用 webpack(保持原有行为),设置环境变量可启用 Turbopack
if (mode === "dev" && process.env.OMNIROUTE_USE_TURBOPACK !== "1") {
args.splice(2, 0, "--webpack");
}
```
**默认行为不变**:dev 模式仍使用 Webpack,与修复前完全一致。设置 `OMNIROUTE_USE_TURBOPACK=1` 可切换到 Turbopack 以获得更快的 dev 编译速度。
**优点**
- 零风险:不改变任何人的现有体验。
- 需要时设置 `OMNIROUTE_USE_TURBOPACK=1` 即可获得 5-10 倍编译加速。
- `next.config.mjs` 中已有 `turbopack.resolveAlias` 配置,说明项目已在准备 Turbopack 迁移。
**缺点/注意**
- Turbopack 对某些 Webpack 特定配置(如自定义 externals 函数)的支持方式不同,启用前需测试兼容性。
- 默认走 Webpack 意味着不主动启用 Turbopack 的用户无法享受编译加速。
### 修复 3`node:crypto``crypto`proxies.ts, errorResponse.ts
```typescript
// 修复前
import { randomUUID } from "node:crypto";
// 修复后
import { randomUUID } from "crypto";
```
**优点**
- `crypto`(无 `node:` 前缀)已在 `next.config.mjs``serverExternalPackages` 列表中,Webpack/Turbopack 会正确将其标记为外部包。
- 消除 `UnhandledSchemeError` 构建失败。
- Node.js 中 `crypto``node:crypto` 解析到同一模块。
**缺点**
- 无。`crypto` 是 Node.js 内建模块,两种写法功能完全等价。
### 修复 4:分离 Edge/Node.js Instrumentationinstrumentation.ts → instrumentation-node.ts
**问题**`instrumentation.ts` 中所有 Node.js 逻辑(`ensureSecrets`、DB 初始化、审计日志等)虽然只在 `NEXT_RUNTIME === "nodejs"` 时执行,但 Turbopack 编译 Edge 版本时仍静态追踪其 import 链,对每个 `fs`/`path`/`os`/`better-sqlite3` 等原生模块输出警告。
**方案**:将所有 Node.js 专属逻辑提取到 `src/instrumentation-node.ts`,主文件通过**计算的 import 路径**引入,阻止 Turbopack 静态解析:
```typescript
// src/instrumentation.ts — 精简后仅 ~20 行
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
// 拼接路径阻止 Turbopack 在 Edge 编译时静态解析模块依赖
const nodeMod = "./instrumentation-" + "node";
const { registerNodejs } = await import(nodeMod);
await registerNodejs();
}
}
```
```typescript
// src/instrumentation-node.ts — 包含全部 Node.js 启动逻辑
export async function registerNodejs(): Promise<void> {
await ensureSecrets();
// initConsoleInterceptor, initGracefulShutdown, initApiBridgeServer, ...
// (原 instrumentation.ts 的完整 Node.js 逻辑)
}
```
**关键技术**`"./instrumentation-" + "node"` 是运行时拼接的字符串,Turbopack 无法在编译期确定其值,因此**不会追踪**该 import 的依赖树。Node.js 运行时则正常解析该路径并执行。
**优点**
- Edge 编译时完全跳过 Node.js 模块追踪,**10+ 条重复警告全部消除**。
- Node.js 运行时行为与修复前完全一致。
- 启动时间从 **13.9s → 1.25s**Turbopack 不再在 Edge 编译中处理 Node.js 模块图)。
**缺点/注意**
- 新增一个文件 `instrumentation-node.ts`,需同步维护。
- 计算 import 路径是有意为之的 bundler 逃逸技巧,需加注释说明原因防止后续重构时被"优化"回静态字符串。
### 修复 5:并行化 instrumentation.ts 中的启动 import
```typescript
// 修复后 — 4 个独立模块并行导入
const [
{ initGracefulShutdown },
{ initApiBridgeServer },
{ startBackgroundRefresh },
{ getSettings },
] = await Promise.all([
import("@/lib/gracefulShutdown"),
import("@/lib/apiBridgeServer"),
import("@/domain/quotaCache"),
import("@/lib/db/settings"),
]);
// 2 个 open-sse 模块也并行导入
const [{ setCustomAliases }, { setDefaultFastServiceTierEnabled }] = await Promise.all([
import("@omniroute/open-sse/services/modelDeprecation.ts"),
import("@omniroute/open-sse/executors/codex.ts"),
]);
```
**优点**
- `consoleInterceptor` 仍保持第一个(必须在任何日志前初始化)。
- 后续 4 个无依赖模块并行加载,节省 3 次串行等待。
- open-sse 的 2 个模块也并行加载。
**缺点**
- 并行 import 的错误堆栈略复杂(Promise.all 中某一个失败会 reject 整个组)。
- 这里的 compliance 模块仍保持独立 try/catch 串行,因为它有自己的错误处理逻辑。
---
## 四、预期效果
| 指标 | 修复前 | 修复后(预期) |
| ----------------------------- | ------------------------- | ------------------------ |
| DB 连接创建次数 | 485 次/会话 | 1 次 |
| HealthCheck 定时器 | 586 个泄漏 | 1 个 |
| 信号处理器注册 | ~400 次重复 | 1 次 |
| Console 拦截层数 | ~400 层嵌套 | 1 层 |
| 内存使用峰值 | 2.4 GB → OOM 重启 | 预期 < 500 MB |
| 冷启动 Ready | 3.4s | ~3s(略快) |
| 内存重启 Ready | 82.6s | 不再触发内存重启 |
| Login 页首次编译 | 3.7s | ~0.5s (需启用 Turbopack) |
| Provider 详情页首次编译 | 22s | ~2-3s (需启用 Turbopack) |
| `node:crypto` 构建错误 | 反复出现 | 消除 |
| Edge Runtime 编译警告 | 每次热编译刷出 10+ 条 | **0 条** |
| instrumentation 启动耗时 | 13.9s(含 Edge 模块追踪) | **1.25s** |
| instrumentation import 并行度 | 9 次串行 import | 3 批并行 import |
---
## 五、涉及文件清单
| 区域 | 文件 | 改动类型 |
| ------------------- | ------------------------------- | ------------------------------------------------------------------ |
| DB 单例 | `src/lib/db/core.ts` | `let _db``globalThis.__omnirouteDb` |
| Token 健康检查 | `src/lib/tokenHealthCheck.ts` | `let initialized``globalThis.__omnirouteTokenHC` |
| 本地节点健康检查 | `src/lib/localHealthCheck.ts` | `let initialized``globalThis.__omnirouteLocalHC` |
| Console 拦截 | `src/lib/consoleInterceptor.ts` | `let initialized``globalThis.__omnirouteConsoleInterceptorInit` |
| 优雅关停 | `src/lib/gracefulShutdown.ts` | 新增 `globalThis.__omnirouteShutdownInit` 守卫 |
| Dev 启动脚本 | `scripts/run-next.mjs` | 新增 `OMNIROUTE_USE_TURBOPACK=1` 开关 |
| Proxy 注册表 | `src/lib/db/proxies.ts` | `node:crypto``crypto` |
| API 错误响应 | `src/lib/api/errorResponse.ts` | `node:crypto``crypto` |
| 启动钩子(主入口) | `src/instrumentation.ts` | 精简为 ~20 行,计算 import 路径阻止 Edge 追踪 |
| 启动钩子(Node.js | `src/instrumentation-node.ts` | 新文件,承载全部 Node.js 启动逻辑 + `Promise.all` 并行 |
---
## 六、回退方案
- **启用 Turbopack**:设置 `OMNIROUTE_USE_TURBOPACK=1` 环境变量;不设置则默认使用 Webpack(原有行为不变)。
- **globalThis 方案异常**:所有 globalThis key 都以 `__omniroute` 为前缀,可通过 `delete globalThis.__omnirouteDb` 等方式手动重置。
- **Edge 警告回退**:若 `instrumentation-node.ts` 拆分导致问题,可将其内容合并回 `instrumentation.ts`,恢复为直接 `import()` 调用(警告会重新出现但不影响功能)。
- **生产环境**:以上修复对生产构建无负面影响——生产环境不存在 HMR,globalThis 单例仅在首次调用时初始化一次。计算 import 路径在 `next build` 时由 Node.js 正常解析,不影响打包产物。
---
## 七、单元测试与备份恢复(pre-commit 验证通过)
为保证提交前必须通过验证(不再使用 `--no-verify`),对以下失败用例与生产逻辑做了修复与加固。
### 问题与根因
| 失败项 | 根因 |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| bootstrap-env 4 个用例 | Windows 上 DATA_DIR 解析用 `APPDATA`/`homedir()`,测试只设了 `HOME`,脚本读不到测试用的 `.env`。 |
| domain-persistence costRules 2 个用例 | `core` 在首次 import 时缓存 `DATA_DIR`;测试每测一个 tmpDir 并在 afterEach 删目录,导致后续 describe 使用的 DB 路径已被删,读写得到 0。 |
| fixes-p1 restoreDbBackup | 测试在 DB 仍打开时写 stale 侧文件;`restoreDbBackup` 内 pre-restore 备份未 await 就关库,Windows 上句柄未及时释放,unlink 报 EBUSY。 |
| fixes-p1 resetStorage 及后续用例 | 上一测留下 DB 打开,下一测 `resetStorage()` 删目录时文件仍被占用,EBUSY。 |
### 修复 6bootstrap-env 测试(tests/unit/bootstrap-env.test.mjs
在每个用例的 `withTempEnv` 回调开头增加 `process.env.DATA_DIR = dataDir`,使脚本在任意平台(含 Windows)都使用测试临时目录,而不是依赖 `HOME`/`APPDATA`
### 修复 7domain-persistence 测试(tests/unit/domain-persistence.test.mjs
- **单例 tmpDir**:全文件共用一个 `fileTmpDir`,在模块加载时创建并设置 `process.env.DATA_DIR`,与 `core` 首次加载时缓存的路径一致。
- **每测清 DB 不清目录**`beforeEach``resetDbInstance()` 后删除 `storage.sqlite` 及其 `-wal`/`-shm`/`-journal`,保证每测干净 DB,不在 afterEach 删目录,避免路径失效。
- **收尾**`after()` 中恢复 `DATA_DIR` 并删除 `fileTmpDir`
- **costRules 断言**:改为小容差精确校验(`assertAlmostEqual`),继续验证 `4.5` / `4.0` 这类业务关键值,避免把真实累计错误放过去。
### 修复 8fixes-p1 测试(tests/unit/fixes-p1.test.mjs
- **restoreDbBackup 用例**:在写入 stale 侧文件前调用 `core.resetDbInstance()`,避免 DB 仍打开时写 `-wal`/`-shm` 触发 Windows 锁错误。
- **Windows 跳过**:该用例在 Windows 上仍使用 `test(..., { skip: isWindows })`。原因不是业务逻辑不支持 Windows,而是 better-sqlite3 关闭后底层句柄释放存在时序抖动,这条真实 sidecar 集成测试容易退化成不稳定的文件锁测试;Linux/macOS 上照常运行。
- **核心兜底测试**:新增平台无关的 `unlinkFileWithRetry` 单测,直接模拟 `EBUSY` / `EPERM` 后重试并最终成功,确保 Windows 相关的重试删除逻辑被稳定覆盖,而不是完全依赖 flaky 的真实文件锁时序。
- **resetStorage**:改为 async,对 `rmSync(TEST_DATA_DIR)` 做最多 10 次、间隔 100ms 的 EBUSY/EPERM 重试,避免下一测因上一测句柄未释放而失败。
### 修复 9:备份恢复逻辑(src/lib/db/backup.ts
- **pre-restore 备份改为同步等待**:在 `restoreDbBackup` 内用内联逻辑做 pre-restore 备份并 `await` 完成,再调用 `resetDbInstance()`,避免异步 backup 未结束就关库导致后续 unlink 失败。
- **节流语义保持一致**pre-restore 备份成功后补回 `_lastBackupAt = Date.now()`,避免恢复后紧接着又触发一轮额外自动备份。
- **关库后短延迟**`resetDbInstance()``await new Promise(r => setTimeout(r, 500))`,再执行 unlink,给 Windows 等平台释放句柄留时间。
- **unlink 重试**:将主库及 `-wal`/`-shm`/`-journal` 的删除提取为 `unlinkFileWithRetry`,统一做最多 10 次、间隔 100ms 的 EBUSY/EPERM 重试,提高恢复流程在锁释放较慢环境下的成功率,也便于单测直接覆盖重试逻辑。
### 涉及文件(本节)
| 区域 | 文件 | 改动类型 |
| -------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| 单元测试 | `tests/unit/bootstrap-env.test.mjs` | 各用例内设置 `process.env.DATA_DIR = dataDir` |
| 单元测试 | `tests/unit/domain-persistence.test.mjs` | 单例 tmpDir、beforeEach 清 DB 文件、after 删目录;costRules 改为小容差精确断言 |
| 单元测试 | `tests/unit/fixes-p1.test.mjs` | restoreDbBackup 前 resetDbInstance、Windows skip 说明、resetStorage 重试、`unlinkFileWithRetry` 核心单测 |
| 备份恢复 | `src/lib/db/backup.ts` | pre-restore 内联并 await、恢复 `_lastBackupAt` 节流语义、关库后 500ms 延迟、抽取 `unlinkFileWithRetry` 重试删除 |
-332
View File
@@ -1,332 +0,0 @@
# ZWS_README_V5 — 按协议配置模型兼容性 + 前端性能优化
V4 内容(HMR 泄漏修复、Edge 警告消除、测试稳定性)已完成;V5 在 V4 基础上实现**按协议维度配置模型兼容性**,新增前端查找性能优化与类型安全改进。
---
## 一、如何发现问题
### 现象
- 同一模型被 **OpenAI Chat Completions**、**OpenAI Responses API**、**Anthropic Messages** 三种客户端请求形态调用时,V2 的兼容性开关(工具 ID 9 位、不保留 developer 角色)是**全局生效**的——无法为不同协议设置不同的兼容策略。
- 例如:用户希望 OpenAI Responses API 请求时不保留 developer 角色(MiniMax 422 修复),但 OpenAI Chat Completions 请求时保留。V2 下只能二选一。
- 前端兼容性弹层未标明当前配置对应哪种协议,容易误导。
- 前端组件中 `Array.find()` 在每次渲染时对 customModels 和 modelCompatOverrides 做 O(n) 线性扫描,模型数量多时存在不必要的性能开销。
- `ModelCompatPatch` 类型定义与运行时逻辑不一致:`preserveOpenAIDeveloperRole` 字段需要支持 `null`(表示取消设置/恢复默认),但类型仅允许 `boolean`
### 排查过程
1. **需求分析**:梳理 `detectFormat(body)` 返回的三种协议键(`openai``openai-responses``claude`),确认每种协议对 developer 角色和 tool call ID 的需求不同。
2. **数据模型设计**:在现有 `normalizeToolCallId` / `preserveOpenAIDeveloperRole` 顶层字段基础上,设计 `compatByProtocol` 嵌套结构,按协议键细分。
3. **构建问题**:客户端 `"use client"` 组件直接从 `@/lib/localDb` 引入常量时,间接拉入了 `node:crypto`(经由 `db/proxies.ts`),触发 Webpack `UnhandledSchemeError`。需将常量拆到 `shared/` 层。
4. **前端性能**:通过 React DevTools 和代码审计发现 `effectiveNormalizeForProtocol` 等函数每次调用都对数组做 `find()`,在渲染列表时存在 O(n²) 的隐患。
---
## 二、根因分析
### 根因 1(P0):兼容选项无协议维度
V2 的 `normalizeToolCallId` / `preserveOpenAIDeveloperRole` 存储在模型级别的顶层字段,无法区分请求来源协议。`chatCore.ts` 中的 getter 函数只接收 `(providerId, modelId)` 两个参数,不感知当前请求的 `sourceFormat`
**影响**:跨协议场景下用户只能设置一个全局值,无法精确控制。
### 根因 2P1):客户端构建拉入 Node.js 模块
`page.tsx`"use client")→ `@/lib/localDb``db/proxies.ts``import { randomUUID } from "node:crypto"`
Webpack 无法处理 `node:` URI scheme,报 `UnhandledSchemeError`。虽然 V4 已将 `node:crypto``crypto` 修复了 `proxies.ts`,但 `localDb.ts` 的 barrel export 链仍然存在风险——客户端组件不应引入任何可能传递到 Node.js 模块的路径。
### 根因 3P2):前端查找性能
`effectiveNormalizeForProtocol``effectivePreserveForProtocol``anyNormalizeCompatBadge``anyNoPreserveCompatBadge` 四个函数每次调用都使用 `Array.find()``customModels``modelCompatOverrides` 数组中查找目标模型。在模型列表渲染时,每个模型行会调用多次这些函数,导致 O(n × m) 的查找开销(n = 模型数,m = 每行调用次数)。
### 根因 4(P2):类型定义与运行时不一致
```typescript
// V3 暂存区版本(有问题)
export type ModelCompatPatch = Partial<
Pick<
ModelCompatOverride,
"normalizeToolCallId" | "preserveOpenAIDeveloperRole" | "compatByProtocol"
>
>;
```
`ModelCompatOverride.preserveOpenAIDeveloperRole` 类型为 `boolean | undefined`,但 `mergeModelCompatOverride()` 内部有 `=== null` 判断(用于取消设置/恢复默认),类型层面无法覆盖。
---
## 三、修复方案
### 修复 1`compatByProtocol` 存储与读取(models.ts
**新增数据结构**
```typescript
type CompatByProtocolMap = Partial<Record<ModelCompatProtocolKey, ModelCompatPerProtocol>>;
export type ModelCompatOverride = {
id: string;
normalizeToolCallId?: boolean;
preserveOpenAIDeveloperRole?: boolean;
compatByProtocol?: CompatByProtocolMap; // 新增
};
```
**读取优先级链**(适用于 `getModelNormalizeToolCallId``getModelPreserveOpenAIDeveloperRole`):
```
compatByProtocol[sourceFormat].field → 顶层 field → 默认值
```
1. 若 `sourceFormat` 属于已知协议键(`openai` / `openai-responses` / `claude`),且 `compatByProtocol[sourceFormat]` 中存在目标字段,使用该值。
2. 否则回退到顶层字段。
3. 顶层字段也不存在时使用默认值(normalizeToolCallId=falsepreserveOpenAIDeveloperRole=undefined)。
**深度合并逻辑** `deepMergeCompatByProtocol()`
- 对每个协议键,逐字段合并而非覆盖。
- `normalizeToolCallId=false` 时删除该字段(不存储 false,减少冗余)。
- 合并后若整个协议条目为空对象,删除该协议条目。
- 协议键通过 `isCompatProtocolKey()` 白名单校验,拒绝未知键。
**Getter 签名扩展**(向后兼容,第三参数可选):
```typescript
export function getModelNormalizeToolCallId(
providerId: string,
modelId: string,
sourceFormat?: string | null
): boolean;
export function getModelPreserveOpenAIDeveloperRole(
providerId: string,
modelId: string,
sourceFormat?: string | null
): boolean | undefined;
```
**优点**
- 完全向后兼容:无 `sourceFormat` 参数时行为与 V2 完全一致。
- 协议键白名单校验防止存储污染。
- 深度合并保留未变更协议的配置。
**缺点/注意**
- JSON 存储体积略增(每个模型最多增加 3 个协议条目)。
- 新增 ~80 行 TypeScript 代码。
### 修复 2:请求管线传入 sourceFormatchatCore.ts
```typescript
const normalizeToolCallId = getModelNormalizeToolCallId(
provider || "",
model || "",
sourceFormat // 新增第三参
);
const preserveDeveloperRole = getModelPreserveOpenAIDeveloperRole(
provider || "",
model || "",
sourceFormat // 新增第三参
);
```
`sourceFormat` 由已有的 `detectFormat(body)` 返回,无需新增检测逻辑。
**优点**
- 改动仅 2 行,精准传参。
- 不影响其他 handlerembeddings、imageGeneration 等不涉及 developer 角色和 tool call ID)。
### 修复 3API 路由支持 compatByProtocolroute.ts
**PUT 请求体扩展**
- 解构 `compatByProtocol` 并传入 `updateCustomModel()`
- `compatOnly` 判断扩展:仅含 `provider` + `modelId` + 兼容字段时,走 `mergeModelCompatOverride()` 路径。
- 使用 `ModelCompatPatch` 类型替代行内类型定义,统一类型来源。
**Zod 校验 schema**
```typescript
const modelCompatPerProtocolSchema = z.object({
normalizeToolCallId: z.boolean().optional(),
preserveOpenAIDeveloperRole: z.boolean().optional(),
}).strict(); // strict: 拒绝额外字段
compatByProtocol: z
.record(z.enum(["openai", "openai-responses", "claude"]), modelCompatPerProtocolSchema)
.optional(),
```
**优点**
- `.strict()` 防止客户端注入额外字段。
- `z.enum()` 限定协议键,与后端白名单一致。
- 仅传 `compatByProtocol` 即可更新,前端无需拼装完整模型对象。
### 修复 4:客户端安全常量拆分(modelCompat.ts
**新增** `src/shared/constants/modelCompat.ts`
```typescript
export const MODEL_COMPAT_PROTOCOL_KEYS = ["openai", "openai-responses", "claude"] as const;
export type ModelCompatProtocolKey = (typeof MODEL_COMPAT_PROTOCOL_KEYS)[number];
```
- 不依赖 Node.js / DB 代码,客户端组件可安全引入。
- `models.ts` 从此模块引入并再导出。
- `localDb.ts` 新增 `ModelCompatPatch` 类型导出(供 route.ts 使用),不导出协议常量。
- `page.tsx` 改为从 `@/shared/constants/modelCompat` 引入。
**优点**
- 彻底切断客户端 → localDb → db → proxies → node:crypto 的依赖链。
- 协议键定义单一来源(Single Source of Truth)。
### 修复 5:前端协议选择器与按协议解析(page.tsx)
**ModelCompatPopover 重构**
- 新增协议下拉选择器(`<select>`),可选 OpenAI Chat / OpenAI Responses / Anthropic Messages。
- 两个开关(工具 ID 9 位、不保留 developer**针对选中协议**生效。
- 选择 Claude 协议时隐藏 developer 角色开关(developer 仅对 OpenAI 系有意义)。
- 保存时以 `{ compatByProtocol: { [protocol]: payload } }` 形式提交,后端按协议合并。
- 深色模式适配:下拉框使用 `bg-white dark:bg-zinc-800``text-zinc-900 dark:text-zinc-100`
**Props 接口重构**
旧接口(4 个独立值/回调):
```typescript
(normalizeToolCallId, preserveDeveloperRole, onNormalizeChange, onPreserveChange);
```
新接口(3 个函数式 props):
```typescript
effectiveModelNormalize: (protocol: string) => boolean
effectiveModelPreserveDeveloper: (protocol: string) => boolean
onCompatPatch: (protocol: string, payload: {...}) => void
```
所有消费方(`ModelRow``PassthroughModelRow``CustomModelsSection``CompatibleModelsSection`)已同步更新。
**角标显示逻辑**
- `anyNormalizeCompatBadge()`:任意协议或顶层存在 `normalizeToolCallId=true` 即显示「ID×9」角标。
- `anyNoPreserveCompatBadge()`:任意协议或顶层存在 `preserveOpenAIDeveloperRole=false` 即显示「不保留」角标。
**CustomModelsSection 增强**
- 新增 `modelCompatOverrides` 状态,从 API 响应中获取。
- 新增 `saveCustomCompat()` 函数,支持仅传 `compatByProtocol` 的独立保存。
### 修复 6:前端 Map 查找性能优化(page.tsx
**问题**`effectiveNormalizeForProtocol` 等函数对 `customModels``modelCompatOverrides``Array.find()` 做 O(n) 查找,在列表渲染时每个模型行多次调用。
**方案**:使用 `useMemo` + `Map` 将数组预建为 O(1) 查找表。
```typescript
type CompatModelMap = Map<string, CompatModelRow>;
function buildCompatMap(rows: CompatModelRow[]): CompatModelMap {
const m = new Map<string, CompatModelRow>();
for (const r of rows) if (r.id) m.set(r.id, r);
return m;
}
// 在组件内
const customMap = useMemo(() => buildCompatMap(modelMeta.customModels), [modelMeta.customModels]);
const overrideMap = useMemo(
() => buildCompatMap(modelMeta.modelCompatOverrides),
[modelMeta.modelCompatOverrides]
);
```
所有查找函数签名从 `(modelId, protocol, customModels[], overrides[])` 改为 `(modelId, protocol, customMap, overrideMap)`,内部使用 `Map.get()` 替代 `Array.find()`
**优点**
- 查找从 O(n) 降为 O(1)。
- `useMemo` 依赖项正确,仅在数据变化时重建 Map。
- `CustomModelsSection` 内部也独立构建 Map,不依赖父组件。
### 修复 7ModelCompatPatch 类型修正(models.ts
```typescript
// 修复后 — 显式允许 null
export type ModelCompatPatch = {
normalizeToolCallId?: boolean;
preserveOpenAIDeveloperRole?: boolean | null; // null = 取消设置/恢复默认
compatByProtocol?: CompatByProtocolMap;
};
```
`mergeModelCompatOverride()` 内的 `=== null` 判断逻辑一致,类型安全。
### 修复 8CompatByProtocolMap 类型收紧(page.tsx
客户端 `CompatByProtocolMap``Record<string, ...>` 改为 `Record<ModelCompatProtocolKey, ...>`,增强类型安全,防止传入未知协议键。
### 修复 9i18n 文案新增
| 键名 | 中文 | 英文 |
| ------------------------------- | --------------------------------------------- | -------------------------------------------------------------- |
| `compatProtocolLabel` | 客户端请求协议 | Client request protocol |
| `compatProtocolHint` | 以下选项在 OmniRoute 识别到该请求形态时生效。 | These options apply when OmniRoute detects this request shape. |
| `compatProtocolOpenAI` | OpenAI Chat Completions | OpenAI Chat Completions |
| `compatProtocolOpenAIResponses` | OpenAI Responses API | OpenAI Responses API |
| `compatProtocolClaude` | Anthropic Messages | Anthropic Messages |
---
## 四、使用方式
1. 点击模型行的 **「兼容性」** 按钮。
2. 在弹层内先选择 **「客户端请求协议」**OpenAI Chat / OpenAI Responses / Anthropic Messages)。
3. 勾选该协议下的「工具 ID 9 位」或「不保留 developer 角色」。
4. 保存后,仅在该协议形态的请求下生效。
5. 未配置某协议时,该协议下行为回退到顶层兼容字段(若存在),再回退到默认值(保留 developer、不规范化 tool id)。
6. 角标「ID×9」「不保留」在任意协议存在对应配置时显示。
---
## 五、预期效果
| 指标 | 修复前 | 修复后 |
| ------------------------- | --------------------- | ------------------------------------------ |
| 兼容性配置维度 | 全局(模型级) | 按协议(OpenAI Chat / Responses / Claude |
| developer 角色精确控制 | 不支持 | 支持(如:仅 Responses API 不保留) |
| 前端兼容性查找性能 | O(n) Array.find | O(1) Map.getuseMemo 缓存) |
| ModelCompatPatch 类型安全 | null 值无类型覆盖 | 显式 `boolean \| null` |
| 客户端构建风险 | 可能引入 Node.js 模块 | 已隔离(shared/constants 层) |
| API 验证 | 无 compatByProtocol | Zod strict schema 校验 |
| 深色模式 | 协议选择器不可读 | bg/text 适配 dark 主题 |
---
## 六、涉及文件清单
| 区域 | 文件 | 改动类型 |
| ---------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| 协议常量 | `src/shared/constants/modelCompat.ts` | **新建**,客户端安全的协议键与类型 |
| 存储与读写 | `src/lib/db/models.ts` | `compatByProtocol` 数据结构、深度合并、getter 第三参 `sourceFormat``ModelCompatPatch` 类型修正 |
| 再导出层 | `src/lib/localDb.ts` | 新增 `ModelCompatPatch` 类型导出 |
| API 路由 | `src/app/api/provider-models/route.ts` | PUT 支持 `compatByProtocol`,使用 `ModelCompatPatch` 类型 |
| 输入校验 | `src/shared/validation/schemas.ts` | `modelCompatPerProtocolSchema`strict+ `compatByProtocol` 记录校验 |
| 请求管线 | `open-sse/handlers/chatCore.ts` | `getModelNormalizeToolCallId` / `getModelPreserveOpenAIDeveloperRole` 传入 `sourceFormat` |
| 前端 UI | `src/app/(dashboard)/dashboard/providers/[id]/page.tsx` | 协议选择器、按协议解析/保存、角标逻辑、Map 性能优化、类型收紧 |
| i18n | `src/i18n/messages/en.json``src/i18n/messages/zh-CN.json` | 5 条新文案 |
---
## 七、回退方案
- **禁用按协议配置**:删除 `compatByProtocol` 字段后,getter 自动回退到顶层字段,行为与 V2 一致。
- **前端 Map 优化回退**:将 `Map.get()` 改回 `Array.find()` 即可,纯性能优化无功能耦合。
- **客户端常量回退**:将 `MODEL_COMPAT_PROTOCOL_KEYS` 定义移回 `models.ts` 并从 `localDb.ts` 导出(需同时确保 `node:crypto` 问题不再存在)。
- **生产环境**:以上修复对生产构建无负面影响。`compatByProtocol` 为可选字段,未配置时默认行为不变。API Zod 校验确保不会接受畸形数据。
+6 -6
View File
@@ -142,10 +142,10 @@ The provider prefix is auto-added if missing. Mismatched models return `400`.
```bash
# Get cache stats
GET /api/cache
GET /api/cache/stats
# Clear all caches
DELETE /api/cache
DELETE /api/cache/stats
```
Response example:
@@ -218,7 +218,7 @@ Response example:
| Endpoint | Method | Description |
| ------------------------------- | ------- | ---------------------- |
| `/api/settings` | GET/PUT | General settings |
| `/api/settings` | GET/PUT/PATCH | General settings |
| `/api/settings/proxy` | GET/PUT | Network proxy config |
| `/api/settings/proxy/test` | POST | Test proxy connection |
| `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist |
@@ -231,8 +231,8 @@ Response example:
| ------------------------ | ---------- | ----------------------- |
| `/api/sessions` | GET | Active session tracking |
| `/api/rate-limits` | GET | Per-account rate limits |
| `/api/monitoring/health` | GET | Health check |
| `/api/cache` | GET/DELETE | Cache stats / clear |
| `/api/monitoring/health` | GET | Health check + provider summary (`catalogCount`, `configuredCount`, `activeCount`, `monitoredCount`) |
| `/api/cache/stats` | GET/DELETE | Cache stats / clear |
### Backup & Export/Import
@@ -279,7 +279,7 @@ GET response includes `agents[]` (id, name, binary, version, installed, protocol
| Endpoint | Method | Description |
| ----------------------- | ------- | ------------------------------- |
| `/api/resilience` | GET/PUT | Get/update resilience profiles |
| `/api/resilience` | GET/PATCH | Get/update resilience profiles |
| `/api/resilience/reset` | POST | Reset circuit breakers |
| `/api/rate-limits` | GET | Per-account rate limit status |
| `/api/rate-limit` | GET | Global rate limit configuration |
+21 -1
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-04_
_Last updated: 2026-03-24_
## Executive Summary
@@ -65,6 +65,26 @@ Primary runtime model:
- Provider SLA/control plane outside local process
- External CLI binaries themselves (Claude CLI, Codex CLI, etc.)
## Dashboard Surface (Current)
Main pages under `src/app/(dashboard)/dashboard/`:
- `/dashboard` — quick start + provider overview
- `/dashboard/endpoint` — endpoint proxy + MCP + A2A + API endpoint tabs
- `/dashboard/providers` — provider connections and credentials
- `/dashboard/combos` — combo strategies, templates, model routing rules
- `/dashboard/costs` — cost aggregation and pricing visibility
- `/dashboard/analytics` — usage analytics and evaluations
- `/dashboard/limits` — quota/rate controls
- `/dashboard/cli-tools` — CLI onboarding, runtime detection, config generation
- `/dashboard/agents` — detected ACP agents + custom agent registration
- `/dashboard/media` — image/video/music playground
- `/dashboard/search-tools` — search provider testing and history
- `/dashboard/health` — uptime, circuit breakers, rate limits
- `/dashboard/logs` — request/proxy/audit/console logs
- `/dashboard/settings` — system settings tabs (general, routing, combo defaults, etc.)
- `/dashboard/api-manager` — API key lifecycle and model permissions
## High-Level System Context
```mermaid
+35 -38
View File
@@ -9,7 +9,7 @@ cost tracking, model switching, and request logging across every tool.
## How It Works
```
Claude / Codex / Gemini CLI / OpenCode / Cline / KiloCode / Continue / Kiro CLI
Claude / Codex / OpenCode / Cline / KiloCode / Continue / Kiro / Cursor / Copilot
▼ (all point to OmniRoute)
http://YOUR_SERVER:20128/v1
@@ -27,21 +27,38 @@ Claude / Codex / Gemini CLI / OpenCode / Cline / KiloCode / Continue / Kiro CLI
---
## Supported Tools
## Supported Tools (Dashboard Source of Truth)
| Tool | Command | Type | Install Method |
| ---------------- | ------------------- | ----------------- | -------------- |
| **Claude Code** | `claude` | CLI | npm |
| **OpenAI Codex** | `codex` | CLI | npm |
| **Gemini CLI** | `gemini` | CLI | npm |
| **OpenCode** | `opencode` | CLI | npm |
| **Cline** | `cline` | CLI + VS Code ext | npm |
| **KiloCode** | `kilocode` / `kilo` | CLI + VS Code ext | npm |
| **Continue** | guide-based | VS Code ext | VS Code |
| **Kiro CLI** | `kiro-cli` | CLI | curl installer |
| **Cursor** | `cursor` | Desktop app | Download |
| **Droid** | web-based | Built-in agent | OmniRoute |
| **OpenClaw** | web-based | Built-in agent | OmniRoute |
The dashboard cards in `/dashboard/cli-tools` are generated from `src/shared/constants/cliTools.ts`.
Current list (v3.0.0-rc.16):
| Tool | ID | Command | Setup Mode | Install Method |
| ---------------- | ------------- | ------------ | ---------- | -------------- |
| **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` | app | guide | desktop app |
| **Cline** | `cline` | `cline` | custom | npm |
| **Kilo Code** | `kilo` | `kilocode` | custom | npm |
| **Continue** | `continue` | extension | guide | VS Code |
| **Antigravity** | `antigravity` | internal | mitm | OmniRoute |
| **GitHub Copilot**| `copilot` | extension | custom | VS Code |
| **OpenCode** | `opencode` | `opencode` | guide | npm |
| **Kiro AI** | `kiro` | app/cli | mitm | desktop/CLI |
### CLI fingerprint sync (Agents + Settings)
`/dashboard/agents` and `Settings > CLI Fingerprint` use `src/shared/constants/cliCompatProviders.ts`.
This keeps provider IDs aligned with CLI cards and legacy IDs.
| CLI ID | Fingerprint Provider ID |
| ------ | ----------------------- |
| `kilo` | `kilocode` |
| `copilot` | `github` |
| `claude` / `codex` / `antigravity` / `kiro` / `cursor` / `cline` / `opencode` / `droid` / `openclaw` | same ID |
Legacy IDs still accepted for compatibility: `copilot`, `kimi-coding`, `qwen`.
---
@@ -67,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
@@ -77,7 +91,7 @@ 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
@@ -90,7 +104,6 @@ export PATH="$HOME/.local/bin:$PATH" # add to ~/.bashrc
```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)
@@ -153,21 +166,6 @@ 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
@@ -324,17 +322,16 @@ They run as internal routes and use OmniRoute's model routing automatically.
OMNIROUTE_URL="http://localhost:20128/v1"
OMNIROUTE_KEY="sk-your-omniroute-key"
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
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"
+32 -67
View File
@@ -8,73 +8,6 @@ _وكيل API العالمي الخاص بك - نقطة نهاية واحدة،
---
### 🆕 الجديد في v2.7.0
- **RouterStrategy قابل للتوصيل** — استراتيجيات القواعد والتكلفة والكمون
- **كشف النية متعدد اللغات** — تسجيل التوجيه بأكثر من 30 لغة
- **إلغاء تكرار الطلبات** — تجنب مكالمات API المكررة عبر تجزئة المحتوى
- **مزودون جدد:** Grok-4 Fast (xAI) وGLM-5 / Z.AI وMiniMax M2.5 وKimi K2.5
- **أسعار محدثة:** Grok-4 Fast $0.20/$0.50/M، GLM-5 $0.50/M، MiniMax M2.5 $0.30/M
---
<div align="center">
[![إصدار npm](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)
[![الترخيص](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
[![موقع الويب](https://img.shields.io/badge/Website-omniroute.online-blue?logo=google-chrome&logoColor=white)](https://omniroute.online)
[![WhatsApp](https://img.shields.io/badge/WhatsApp-Community-25D366?logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
[🌐 الموقع الإلكتروني](https://omniroute.online) • [🚀 البداية السريعة](#-quick-start) • [💡 الميزات](#-key-features) • [📖 المستندات](#-documentation) • [💰 التسعير](#-pricing-at-a-glance) • [💬 واتساب](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
</div>
🌐 **متوفر باللغة:** 🇺🇸 [الإنجليزية](../../README.md) | 🇧🇷 [البرتغالية (البرازيل)](../pt-BR/README.md) | 🇪🇸 [الإسبانية](../es/README.md) | 🇫🇷 [Français](../fr/README.md) | 🇮🇹 [الإيطالية](../it/README.md) | 🇷🇺 [Русский](../ru/README.md) | 🇨🇳 [中文 (简体)](../zh-CN/README.md) | 🇩🇪 [الألمانية](../de/README.md) | 🇮🇳 [هندي](../in/README.md) | 🇹🇭 [ไทย](../th/README.md) | 🇺🇦 [أوكرانيا](../uk-UA/README.md) | 🇸🇦 [العربية](../ar/README.md) | 🇯🇵 [日本語](../ja/README.md) | 🇻🇳 [تيانج فيت](../vi/README.md) | 🇧🇬 [بلغارسكي](../bg/README.md) | 🇩🇰 [الدانسك](../da/README.md) | 🇫🇮 [سومي](../fi/README.md) | 🇮🇱 [العربية](../he/README.md) | 🇭🇺 [المجرية](../hu/README.md) | 🇮🇩 [البهاسا الإندونيسية](../id/README.md) | 🇰🇷 [한국어](../ko/README.md) | 🇲🇾 [البهاسا ملايو](../ms/README.md) | 🇳🇱 [هولندا](../nl/README.md) | 🇳🇴 [نورسك](../no/README.md) | 🇵🇹 [البرتغالية (البرتغال)](../pt/README.md) | 🇷🇴 [روماني](../ro/README.md) | 🇵🇱 [بولسكي](../pl/README.md) | 🇸🇰 [سلوفينسينا](../sk/README.md) | 🇸🇪 [سفينسكا](../sv/README.md) | 🇵🇭 [فلبينية](../phi/README.md)
---
## 🖼️ لوحة التحكم الرئيسية
<div align="center">
<img src="./docs/screenshots/MainOmniRoute.png" alt="OmniRoute Dashboard" width="800"/>
</div>
---
## 📸 معاينة لوحة التحكم
<details>
<summary><b>انقر لرؤية لقطات شاشة لوحة القيادة</b></summary>
| صفحة | لقطة شاشة |
| --------------------- | -------------------------------------------------- |
| ** مقدمو الخدمة ** | ![مقدمو الخدمة](docs/screenshots/01-providers.png) |
| **المجموعات** | ![المجموعات](docs/screenshots/02-combos.png) |
| **تحليلات** | ![تحليلات](docs/screenshots/03-analytics.png) |
| **الصحة** | ![الصحة](docs/screenshots/04-health.png) |
| **مترجم** | ![مترجم](docs/screenshots/05-translator.png) |
| **الإعدادات** | ![الإعدادات](docs/screenshots/06-settings.png) |
| **أدوات سطر الأوامر** | ![أدوات CLI](docs/screenshots/07-cli-tools.png) |
| **سجلات الاستخدام** | ![الاستخدام](docs/screenshots/08-usage.png) |
| **نقطة النهاية** | ![نقطة النهاية](docs/screenshots/09-endpoint.png) |
</details>
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 موفر الذكاء الاصطناعي المجاني لوكلاء البرمجة المفضلين لديك
_قم بتوصيل أي أداة IDE أو CLI مدعومة بالذكاء الاصطناعي من خلال OmniRoute - بوابة واجهة برمجة التطبيقات المجانية للترميز غير المحدود._
@@ -159,6 +92,38 @@ _قم بتوصيل أي أداة 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
## 🤔 لماذا OmniRoute؟
**توقف عن إهدار المال وضرب الحدود:**
+32 -67
View File
@@ -8,73 +8,6 @@ _Вашият универсален API прокси — една крайна
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
<div align="center">
[![npm версия](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)
[![Лиценз](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
[![Уебсайт](https://img.shields.io/badge/Website-omniroute.online-blue?logo=google-chrome&logoColor=white)](https://omniroute.online)
[![WhatsApp](https://img.shields.io/badge/WhatsApp-Community-25D366?logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
[🌐 Уебсайт](https://omniroute.online) • [🚀 Бърз старт](#-quick-start) • [💡 Функции](#-key-features) • [📖 Документи](#-documentation) • [💰 Ценообразуване](#-pricing-at-a-glance) • [💬 WhatsApp](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
</div>
🌐 **Налично на:** 🇺🇸 [английски](../../README.md) | 🇧🇷 [Португалски (Бразилия)](../pt-BR/README.md) | 🇪🇸 [Испански] (../es/README.md) | 🇫🇷 [Français](../fr/README.md) | 🇮🇹 [италиански] (../it/README.md) | 🇷🇺 [Русский](../ru/README.md) | 🇨🇳 [中文 (简体)](../zh-CN/README.md) | 🇩🇪 [Deutsch](../de/README.md) | 🇮🇳 [हिन्दी] (../in/README.md) | 🇹🇭 [ไทย](../th/README.md) | 🇺🇦 [Українська](../uk-UA/README.md) | 🇸🇦 [العربية](../ar/README.md) | 🇯🇵 [日本語](../ja/README.md) | 🇻🇳 [Tiếng Việt](../vi/README.md) | 🇧🇬 [Български](../bg/README.md) | 🇩🇰 [Dansk](../da/README.md) | 🇫🇮 [Suomi](../fi/README.md) | 🇮🇱 [עברית](../he/README.md) | 🇭🇺 [маджарски] (../hu/README.md) | 🇮🇩 [бахаса Индонезия](../id/README.md) | 🇰🇷 [한국어](../ko/README.md) | 🇲🇾 [Bahasa Melayu](../ms/README.md) | 🇳🇱 [Нидерландия](../nl/README.md) | 🇳🇴 [Norsk](../no/README.md) | 🇵🇹 [Português (Португалия)](../pt/README.md) | 🇷🇴 [Română](../ro/README.md) | 🇵🇱 [Полски](../pl/README.md) | 🇸🇰 [Slovenčina](../sk/README.md) | 🇸🇪 [Svenska](../sv/README.md) | 🇵🇭 [филипински] (../phi/README.md)
---
## 🖼️ Главно табло за управление
<div align="center">
<img src="./docs/screenshots/MainOmniRoute.png" alt="OmniRoute Dashboard" width="800"/>
</div>
---
## 📸 Визуализация на таблото за управление
<details>
<summary><b>Щракнете, за да видите екранни снимки на таблото </b></summary>
| Страница | Екранна снимка |
| -------------------------- | ----------------------------------------------------- |
| **Доставчици** | ![Доставчици](docs/screenshots/01-providers.png) |
| **Комбота** | ![Комбота](docs/screenshots/02-combos.png) |
| **Анализ** | ![Анализ](docs/screenshots/03-analytics.png) |
| **Здраве** | ![Здраве](docs/screenshots/04-health.png) |
| **Преводач** | ![Преводач](docs/screenshots/05-translator.png) |
| **Настройки** | ![Настройки](docs/screenshots/06-settings.png) |
| **CLI инструменти** | ![CLI инструменти](docs/screenshots/07-cli-tools.png) |
| **Регистри за използване** | ![Използване](docs/screenshots/08-usage.png) |
| **Крайна точка** | ![Крайна точка](docs/screenshots/09-endpoint.png) |
</details>
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Безплатен доставчик на AI за вашите любими кодиращи агенти
_Свържете всеки базиран на AI IDE или CLI инструмент чрез OmniRoute — безплатен API шлюз за неограничено кодиране._
@@ -159,6 +92,38 @@ _Свържете всеки базиран на 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
## 🤔 Защо OmniRoute?
**Спрете да пилеете пари и да достигате лимити:**
+33 -26
View File
@@ -18,6 +18,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
## 🖼️ Hlavní ovládací panel
<div align="center"> <img src="./docs/screenshots/MainOmniRoute.png" alt="Řídicí panel OmniRoute" width="800"> </div>
@@ -165,7 +197,7 @@ OpenAI používá jeden formát, Claude (Anthropic) jiný a Gemini ještě třet
**Jak to OmniRoute řeší:**
- **Sjednocený koncový bod** — Jeden `http://localhost:20128/v1` slouží jako proxy pro všech 44+ poskytovatelů.
- **Sjednocený koncový bod** — Jeden `http://localhost:20128/v1` slouží jako proxy pro všech 67+ poskytovatelů.
- **Překlad formátu** — Automatický a transparentní: OpenAI ↔ Claude ↔ Gemini ↔ Responses API
- **Sanitizace odpovědí** — Odstraňuje nestandardní pole ( `x_groq` , `usage_breakdown` , `service_tier` ), která porušují OpenAI SDK v1.83+
- **Normalizace rolí** — Převádí `developer``system` pro poskytovatele bez OpenAI; `system``user` pro GLM/ERNIE
@@ -951,31 +983,6 @@ Pak v `/dashboard/media` → záložka **Přepis** : nahrajte libovolný zvukov
OmniRoute v2.0 je navržen jako operační platforma, nikoli pouze jako proxy pro relé.
### 🆕 Nové — Vylepšení inspirovaná ClawRouterem (březen 2026)
| Funkce | Co to dělá |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| ⚡ **Grok-4 Rychlá rodina** | Modely xAI za 0,20 USD/0,50 USD/M v benchmarku 1143 ms (o 30 % rychlejší než Gemini 2.5 Flash) |
| 🧠 **GLM-5 přes Z.AI** | 128 tisíc výstupních dat, 0,5 USD/1 milion USD nejnovější vlajková loď rodiny GLM |
| 🔮 **MiniMax M2.5** | Úvaha + agentní úkoly za 0,30 USD/1 milion významný upgrade oproti M2.1 |
| 🎯 **Příznak volání nástroje pro každý model** | `toolCalling: true/false` v registru — AutoCombo přeskakuje modely, které nepodporují nástroje. |
| 🌍 **Detekce vícejazyčného záměru** | Klíčová slova PT/ZH/ES/AR v bodování AutoCombo lepší výběr modelu pro neanglický obsah |
| 📊 **Záložní metody řízené benchmarkem** | Skutečná latence p95 z živých požadavků poskytuje kombinované skóre AutoCombo se učí ze skutečných dat |
| 🔁 **Požádat o deduplikaci** | Okno pro deduplikaci na základě hashování obsahu bezpečné pro více agentů, zabraňuje duplicitním platbám |
| 🔌 **Strategie pro zásuvné routery** | Rozšiřitelné rozhraní `RouterStrategy` přidejte si vlastní logiku směrování jako pluginy |
### 🚀 Předchozí verze v2.0.9+ — Hřiště, otisky prstů v CLI a ACP
| Funkce | Co to dělá |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Modelové hřiště** | Stránka řídicího panelu pro přímé testování libovolného modelu selektory poskytovatele/modelu/koncového bodu, editor Monaco, streamování, přerušení, načasování |
| 🔏 **Porovnávání otisků prstů v CLI** | Řazení hlaviček/těl serveru podle poskytovatele tak, aby odpovídalo nativním podpisům CLI přepínání pro jednotlivé poskytovatele v Nastavení &gt; Zabezpečení. **Vaše IP adresa proxy serveru je zachována.** |
| 🤝 **Podpora ACP (Agent Client Protocol)** | Vyhledávání agentů CLI (Codex, Claude, Goose, Gemini CLI, OpenClaw + 9 dalších), generátor procesů, koncový bod `/api/acp/agents` |
| 🤖 **Řídicí panel agentů ACP** | Ladění Stránka Agenti — mřížka 14 agentů se stavem instalace, verzí a formulářem pro vlastní agenta pro libovolný nástroj CLI. Uživatelé **OpenCode** získají tlačítko „Stáhnout opencode.json“, které automaticky vygeneruje konfiguraci připravenou k použití se všemi dostupnými modely. |
| 🔧 **Směrování `apiFormat` pro vlastní model** | Vlastní modely s `apiFormat: "responses"` nyní správně směrují do překladače Responses API. |
| 🏢 **Izolace pracovního prostoru Codexu** | Více pracovních prostorů Codexu na jeden e-mail OAuth správně odděluje připojení podle ID pracovního prostoru |
| 🔄 **Automatická aktualizace elektronů** | Desktopová aplikace kontroluje aktualizace + automaticky se instaluje po restartu |
### 🤖 Operace s agenty a protokoly (v2.0)
| Funkce | Co to dělá |
+32 -67
View File
@@ -8,73 +8,6 @@ _Din universelle API-proxy — ét slutpunkt, 36+ udbydere, ingen nedetid. Nu me
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
<div align="center">
[![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)
[![Licens](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
[![Websted](https://img.shields.io/badge/Website-omniroute.online-blue?logo=google-chrome&logoColor=white)](https://omniroute.online)
[![WhatsApp](https://img.shields.io/badge/WhatsApp-Community-25D366?logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
[🌐 Hjemmeside](https://omniroute.online) • [🚀 Hurtig start](#-quick-start) • [💡 Funktioner](#-key-features) • [📖 Docs](#-documentation) • [💡 Priser](#-pricing-at-a-glance) • [💬 WhatsApp](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
</div>
🌐 **Tilgængelig på:** 🇺🇸 [engelsk](../../README.md) | 🇧🇷 [Português (Brasil)](../pt-BR/README.md) | 🇪🇸 [Español](../es/README.md) | 🇫🇷 [Français](../fr/README.md) | 🇮🇹 [Italiano](../it/README.md) | 🇷🇺 [Русский](../ru/README.md) | 🇨🇳 [中文 (简体)](../zh-CN/README.md) | 🇩🇪 [Tysk](../de/README.md) | 🇮🇳 [हिन्दी](../in/README.md) | 🇹🇭 [ไทย](../th/README.md) | 🇺🇦 [Українська](../uk-UA/README.md) | 🇸🇦 [العربية](../ar/README.md) | 🇯🇵 [日本語](../ja/README.md) | 🇻🇳 [Tiếng Việt](../vi/README.md) | 🇧🇬 [Български](../bg/README.md) | 🇩🇰 [Dansk](../da/README.md) | 🇫🇮 [Suomi](../fi/README.md) | 🇮🇱 [engelsk](../he/README.md) | 🇭🇺 [Magyar](../hu/README.md) | 🇮🇩 [Bahasa Indonesien](../id/README.md) | 🇰🇷 [한국어](../ko/README.md) | 🇲🇾 [Bahasa Melayu](../ms/README.md) | 🇳🇱 [Nederlands](../nl/README.md) | 🇳🇴 [norsk](../no/README.md) | 🇵🇹 [Português (Portugal)](../pt/README.md) | 🇷🇴 [Română](../ro/README.md) | 🇵🇱 [Polski](../pl/README.md) | 🇸🇰 [Slovenčina](../sk/README.md) | 🇸🇪 [Svenska](../sv/README.md) | 🇵🇭 [filippinsk](../phi/README.md)
---
## 🖼️ Hovedbetjeningspanel
<div align="center">
<img src="./docs/screenshots/MainOmniRoute.png" alt="OmniRoute Dashboard" width="800"/>
</div>
---
## 📸 Dashboard Preview
<details>
<summary><b>Klik for at se skærmbilleder af dashboard</b></summary>
| Side | Skærmbillede |
| ----------------- | --------------------------------------------------- |
| **Udbydere** | ![Udbydere](docs/screenshots/01-providers.png) |
| **Komboer** | ![Combos](docs/screenshots/02-combos.png) |
| **Analyse** | ![Analytics](docs/screenshots/03-analytics.png) |
| **Sundhed** | ![Sundhed](docs/screenshots/04-health.png) |
| **Oversætter** | ![Oversætter](docs/screenshots/05-translator.png) |
| **Indstillinger** | ![Indstillinger](docs/screenshots/06-settings.png) |
| **CLI-værktøjer** | ![CLI-værktøjer](docs/screenshots/07-cli-tools.png) |
| **Brugslogfiler** | ![Brug](docs/screenshots/08-usage.png) |
| **Endpunkt** | ![Endpoint](docs/screenshots/09-endpoint.png) |
</details>
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Gratis AI-udbyder til dine foretrukne kodningsagenter
_Tilslut ethvert AI-drevet IDE- eller CLI-værktøj gennem OmniRoute - gratis API-gateway til ubegrænset kodning._
@@ -159,6 +92,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
## 🤔 Hvorfor OmniRoute?
**Stop med at spilde penge og nå grænser:**
+32 -67
View File
@@ -8,61 +8,6 @@ _Ihr universeller API-Proxy ein Endpunkt, mehr als 36 Anbieter, keine Ausfal
---
### 🆕 Neu in v2.7.0
- **Erweiterbare RouterStrategy** — Regeln-, Kosten- und Latenzstrategien
- **Mehrsprachige Absichtserkennung** — Routing-Scoring in 30+ Sprachen
- **Anfrage-Deduplizierung** — doppelte API-Aufrufe per Content-Hash vermeiden
- **Neue Anbieter:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Aktualisierte Preise:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
<div align="center">
[![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)
[![Lizenz](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
[![Website](https://img.shields.io/badge/Website-omniroute.online-blue?logo=google-chrome&logoColor=white)](https://omniroute.online)
[![WhatsApp](https://img.shields.io/badge/WhatsApp-Community-25D366?logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
[🌐 Website](https://omniroute.online) • [🚀 Schnellstart](#-quick-start) • [💡 Funktionen](#-key-features) • [📖 Dokumente](#-documentation) • [💰 Preise](#-pricing-at-a-glance) • [💬 WhatsApp](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
</div>
🌐 **Verfügbar in:** 🇺🇸 [Englisch](../../README.md) | 🇧🇷 [Português (Brasilien)](../pt-BR/README.md) | 🇪🇸 [Español](../es/README.md) | 🇫🇷 [Français](../fr/README.md) | 🇮🇹 [Italienisch](../it/README.md) | 🇷🇺 [Русский](../ru/README.md) | 🇨🇳 [中文 (简体)](../zh-CN/README.md) | 🇩🇪 [Deutsch](../de/README.md) | 🇮🇳 [हिन्दी](../in/README.md) | 🇹🇭 [ไทย](../th/README.md) | 🇺🇦 [Українська](../uk-UA/README.md) | 🇸🇦 [العربية](../ar/README.md) | 🇯🇵 [日本語](../ja/README.md) | 🇻🇳 [Tiếng Việt](../vi/README.md) | 🇧🇬 [Български](../bg/README.md) | 🇩🇰 [Dänisch](../da/README.md) | 🇫🇮 [Suomi](../fi/README.md) | 🇮🇱 [עברית](../he/README.md) | 🇭🇺 [Magyar](../hu/README.md) | 🇮🇩 [Bahasa Indonesia](../id/README.md) | 🇰🇷 [한국어](../ko/README.md) | 🇲🇾 [Bahasa Melayu](../ms/README.md) | 🇳🇱 [Niederlande](../nl/README.md) | 🇳🇴 [Norsk](../no/README.md) | 🇵🇹 [Português (Portugal)](../pt/README.md) | 🇷🇴 [Română](../ro/README.md) | 🇵🇱 [Polski](../pl/README.md) | 🇸🇰 [Slovenčina](../sk/README.md) | 🇸🇪 [Svenska](../sv/README.md) | 🇵🇭 [Philippinisch](../phi/README.md)
---
## 🖼️ Haupt-Dashboard
<div align="center">
<img src="./docs/screenshots/MainOmniRoute.png" alt="OmniRoute Dashboard" width="800"/>
</div>
---
## 📸 Dashboard-Vorschau
<details>
<summary><b>Klicken Sie hier, um Dashboard-Screenshots anzuzeigen</b></summary>
| Seite | Screenshot |
| ---------------------- | -------------------------------------------------- |
| **Anbieter** | ![Anbieter](docs/screenshots/01-providers.png) |
| **Kombinationen** | ![Combos](docs/screenshots/02-combos.png) |
| **Analytik** | ![Analytics](docs/screenshots/03-analytics.png) |
| **Gesundheit** | ![Gesundheit](docs/screenshots/04-health.png) |
| **Übersetzer** | ![Übersetzer](docs/screenshots/05-translator.png) |
| **Einstellungen** | ![Einstellungen](docs/screenshots/06-settings.png) |
| **CLI-Tools** | ![CLI-Tools](docs/screenshots/07-cli-tools.png) |
| **Nutzungsprotokolle** | ![Verwendung](docs/screenshots/08-usage.png) |
| **Endpunkt** | ![Endpunkt](docs/screenshots/09-endpoint.png) |
</details>
---
### 🤖 Kostenloser KI-Anbieter für Ihre bevorzugten Programmieragenten
_Verbinden Sie jedes KI-gestützte IDE- oder CLI-Tool über OmniRoute kostenloses API-Gateway für unbegrenzte Codierung._
@@ -147,6 +92,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
## 🤔 Warum OmniRoute?
**Hören Sie auf, Geld zu verschwenden und an Grenzen zu stoßen:**
@@ -890,18 +867,6 @@ Wenn OmniRoute minimiert ist, befindet es sich mit schnellen Aktionen in Ihrer T
OmniRoute v2.0 ist als Betriebsplattform konzipiert und nicht nur als Relay-Proxy.
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Agenten- und Protokolloperationen (v2.0)| Funktion | Was es tut |
| ------------------------------------ | -------------------------------------------------------------------------------- |
+32 -22
View File
@@ -11,28 +11,6 @@ _Tu proxy de API universal — un endpoint, 36+ proveedores, cero tiempo de inac
---
### 🆕 Novedades en v2.7.0
- **RouterStrategy enchufable** — estrategias de reglas, costo y latencia
- **Detección de intención multilingüe** — puntuación de enrutamiento en 30+ idiomas
- **Deduplicación de solicitudes** — evita llamadas duplicadas por hash de contenido
- **Nuevos proveedores:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Precios actualizados:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Proveedor de IA Gratuito para tus agentes de programación favoritos
_Conecta cualquier IDE o herramienta CLI con IA a través de OmniRoute — gateway de API gratuito para programación ilimitada._
@@ -118,6 +96,38 @@ _Conecta cualquier IDE o herramienta CLI con IA a través de OmniRoute — 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Universaali API-välityspalvelin yksi päätepiste, yli 36 palveluntarjoaja
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Ilmainen AI Provider suosikkikoodaajillesi
_Yhdistä mikä tahansa tekoälyllä toimiva IDE- tai CLI-työkalu OmniRouten kautta ilmainen API-yhdyskäytävä rajoittamattomaan koodaukseen._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Votre proxy API universel — un endpoint, 36+ fournisseurs, zéro temps d'arr
---
### 🆕 Nouveautés dans v2.7.0
- **RouterStrategy extensible** — stratégies de règles, coût et latence
- **Détection d'intention multilingue** — scoring de routage en 30+ langues
- **Déduplication des requêtes** — évite les appels dupliqués via hash de contenu
- **Nouveaux fournisseurs :** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Tarifs mis à jour :** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Fournisseur IA gratuit pour vos agents de programmation préférés
_Connectez n'importe quel IDE ou outil CLI alimenté par l'IA via OmniRoute — passerelle API gratuite pour un codage illimité._
@@ -118,6 +96,38 @@ _Connectez n'importe quel IDE ou outil CLI alimenté par l'IA via 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _שרת ה-API האוניברסלי שלך - נקודת קצה אחת, 36+ ספ
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 ספק AI בחינם עבור סוכני הקידוד המועדפים עליך
_חבר כל כלי IDE או CLI המופעל על ידי AI דרך OmniRoute - שער API בחינם לקידוד בלתי מוגבל._
@@ -118,6 +96,38 @@ _חבר כל כלי 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Az univerzális API-proxy egy végpont, 36+ szolgáltató, nulla állásid
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Ingyenes mesterséges intelligencia szolgáltató kedvenc kódoló ügynökei számára
_Csatlakoztasson bármilyen mesterséges intelligencia-alapú IDE-t vagy CLI-eszközt az OmniRoute-on keresztül ingyenes API-átjáró a korlátlan kódoláshoz._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Proksi API universal Anda — satu titik akhir, 36+ penyedia, tanpa waktu henti
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Penyedia AI gratis untuk agen coding favorit Anda
_Hubungkan alat IDE atau CLI apa pun yang didukung AI melalui OmniRoute — gerbang API gratis untuk pengkodean tanpa batas._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -13,28 +13,6 @@ _आपका सार्वभौमिक एपीआई प्रॉक्
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 आपके पसंदीदा कोडिंग एजेंटों के लिए निःशुल्क एआई प्रदाता
_OmniRoute के माध्यम से किसी भी AI-संचालित IDE या CLI टूल को कनेक्ट करें - असीमित कोडिंग के लिए निःशुल्क API गेटवे।_
@@ -43,6 +21,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+33 -23
View File
@@ -5,34 +5,12 @@
### Non smettere mai di programmare. Routing intelligente verso **modelli IA GRATUITI e economici** con fallback automatico.
_Il tuo proxy API universale — un endpoint, 36+ provider, zero downtime._
_Il tuo proxy API universale — un endpoint, 67+ provider, zero downtime._
**Chat Completions • Embeddings • Generazione Immagini • Audio • Reranking • 100% TypeScript**
---
### 🆕 Novità in v2.7.0
- **RouterStrategy estensibile** — strategie per regole, costo e latenza
- **Rilevamento intento multilingue** — scoring di routing in 30+ lingue
- **Deduplicazione richieste** — evita chiamate duplicate tramite hash del contenuto
- **Nuovi provider:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Prezzi aggiornati:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Provider IA gratuito per i tuoi agenti di programmazione preferiti
_Connetti qualsiasi IDE o strumento CLI con IA tramite OmniRoute — gateway API gratuito per programmazione illimitata._
@@ -118,6 +96,38 @@ _Connetti qualsiasi IDE o strumento CLI con IA tramite OmniRoute — gateway API
---
### 🆕 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _ユニバーサル API プロキシ — 1 つのエンドポイント、36 以
---
### 🆕 v2.7.0 の新機能
- **プラガブル RouterStrategy** — ルール・コスト・レイテンシ戦略をサポート
- **多言語インテント検出** — 30以上の言語でルーティングスコアリング
- **リクエスト重複排除** — コンテンツハッシュで重複 API 呼び出しを防止
- **新しいプロバイダー:** Grok-4 Fast (xAI)、GLM-5 / Z.AI、MiniMax M2.5、Kimi K2.5
- **価格更新:** Grok-4 Fast $0.20/$0.50/M、GLM-5 $0.50/M、MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 お気に入りのコーディング エージェント向けの無料 AI プロバイダー
_AI を活用した IDE または CLI ツールを、無制限のコーディングのための無料 API ゲートウェイである OmniRoute 経由で接続します。_
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _범용 API 프록시 — 하나의 엔드포인트, 36개 이상의 공급자,
---
### 🆕 v2.7.0 새로운 기능
- **플러그형 RouterStrategy** — 규칙, 비용, 지연 전략 지원
- **다국어 의도 감지** — 30개 이상 언어로 라우팅 스코어링
- **요청 중복 제거** — 콘텐츠 해시로 중복 API 호출 방지
- **새 공급자:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **가격 업데이트:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 좋아하는 코딩 에이전트를 위한 무료 AI 제공업체
_무제한 코딩을 위한 무료 API 게이트웨이인 OmniRoute를 통해 AI 기반 IDE 또는 CLI 도구를 연결하세요._
@@ -118,6 +96,38 @@ _무제한 코딩을 위한 무료 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Proksi API universal anda — satu titik akhir, 36+ pembekal, masa henti sifar.
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Pembekal AI percuma untuk ejen pengekodan kegemaran anda
_Sambungkan mana-mana alat IDE atau CLI berkuasa AI melalui OmniRoute — get laluan API percuma untuk pengekodan tanpa had._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+33 -23
View File
@@ -11,28 +11,6 @@ _Uw universele API-proxy: één eindpunt, meer dan 36 providers, geen downtime._
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Gratis AI-provider voor uw favoriete codeeragenten
_Verbind elke AI-aangedreven IDE- of CLI-tool via OmniRoute: gratis API-gateway voor onbeperkte codering._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
@@ -246,7 +256,7 @@ OpenAI gebruikt het ene formaat, Claude (Anthropic) gebruikt een ander, Gemini n
**Hoe OmniRoute het oplost:**
- **Unified Endpoint** — Eén enkele `http://localhost:20128/v1` dient als proxy voor alle 36+ providers
- **Unified Endpoint** — Eén enkele `http://localhost:20128/v1` dient als proxy voor alle 67+ providers
- **Formatvertaling** — Automatisch en transparant: OpenAI ↔ Claude ↔ Gemini ↔ Responses API
- **Response Sanitization** — Verwijdert niet-standaardvelden (`x_groq`, `usage_breakdown`, `service_tier`) die OpenAI SDK v1.83+ breken
- **Rolnormalisatie** — Converteert `developer``system` voor niet-OpenAI-providers; `system``user` voor GLM/ERNIE
+32 -22
View File
@@ -11,28 +11,6 @@ _Din universelle API-proxy ett endepunkt, 36+ leverandører, null nedetid._
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Gratis AI-leverandør for dine favorittkodeagenter
_Koble til ethvert AI-drevet IDE- eller CLI-verktøy gjennom OmniRoute gratis API-gateway for ubegrenset koding._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+35 -25
View File
@@ -5,34 +5,12 @@
### Huwag kailanman ihinto ang coding. Smart routing sa **LIBRE at murang mga modelo ng AI** na may awtomatikong fallback.
_Iyong unibersal na API proxy — isang endpoint, 36+ provider, zero downtime._
_Iyong unibersal na API proxy — isang endpoint, 67+ provider, zero downtime._
**Mga Pagkumpleto ng Chat • Mga Pag-embed • Pagbuo ng Imahe • Audio • Pag-rerank • 100% TypeScript**
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Libreng AI Provider para sa iyong mga paboritong coding agent
_Ikonekta ang anumang AI-powered IDE o CLI tool sa pamamagitan ng OmniRoute — libreng API gateway para sa walang limitasyong coding._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
@@ -246,7 +256,7 @@ Gumagamit ang OpenAI ng isang format, gumagamit si Claude (Anthropic) ng isa pa,
**Paano ito niresolba ng OmniRoute:**
- **Pinag-isang Endpoint** — Isang `http://localhost:20128/v1` ang nagsisilbing proxy para sa lahat ng 36+ provider
- **Pinag-isang Endpoint** — Isang `http://localhost:20128/v1` ang nagsisilbing proxy para sa lahat ng 67+ provider
- **Format Translation** — Awtomatiko at transparent: OpenAI ↔ Claude ↔ Gemini ↔ Responses API
- **Response Sanitization** — Tinatanggal ang mga hindi karaniwang field (`x_groq`, `usage_breakdown`, `service_tier`) na sumisira sa OpenAI SDK v1.83+
- **Role Normalization** — Kino-convert ang `developer``system` para sa mga provider na hindi OpenAI; `system``user` para sa GLM/ERNIE
@@ -331,7 +341,7 @@ Gumagamit ang mga developer ng Cursor, Claude Code, Codex CLI, OpenClaw, Gemini
- **CLI Tools Dashboard** — Nakatuon na page na may isang-click na setup para sa Claude Code, Codex CLI, OpenClaw, Kilo Code, Antigravity, Cline
- **GitHub Copilot Config Generator** — Bumubuo ng `chatLanguageModels.json` para sa VS Code na may maramihang pagpili ng modelo
- **Onboarding Wizard** — May gabay na 4-step na pag-setup para sa mga unang beses na user
- **Isang endpoint, lahat ng modelo** — I-configure ang `http://localhost:20128/v1` nang isang beses, i-access ang 36+ provider
- **Isang endpoint, lahat ng modelo** — I-configure ang `http://localhost:20128/v1` nang isang beses, i-access ang 67+ provider
</details>
+32 -22
View File
@@ -11,28 +11,6 @@ _Twój uniwersalny serwer proxy API — jeden punkt końcowy, ponad 36 dostawcó
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Bezpłatny dostawca AI dla Twoich ulubionych agentów kodujących
_Połącz dowolne narzędzie IDE lub CLI oparte na sztucznej inteligencji poprzez OmniRoute — bezpłatną bramę API dla nieograniczonego kodowania._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Seu proxy de API universal — um endpoint, 36+ provedores, zero tempo de inati
---
### 🆕 Novidades na v2.7.0
- **RouterStrategy plugável** — estratégias de regras, custo e latência
- **Detecção de intenção multilíngue** — scoring de roteamento em 30+ idiomas
- **Deduplicação de requisições** — evita chamadas duplicadas por hash de conteúdo
- **Novos provedores:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Preços atualizados:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Provedor de IA Gratuito para seus agentes de programação favoritos
_Conecte qualquer IDE ou ferramenta CLI com IA através do OmniRoute — gateway de API gratuito para programação ilimitada._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Seu proxy de API universal — um endpoint, mais de 36 provedores, tempo de ina
---
### 🆕 Novidades na v2.7.0
- **RouterStrategy extensível** — estratégias de regras, custo e latência
- **Deteção de intenção multilíngue** — scoring de encaminhamento em 30+ idiomas
- **Deduplicação de pedidos** — evita chamadas duplicadas por hash de conteúdo
- **Novos fornecedores:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Preços atualizados:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Provedor de IA gratuito para seus agentes de codificação favoritos
_Conecte qualquer ferramenta IDE ou CLI com tecnologia de IA por meio do OmniRoute - gateway de API gratuito para codificação ilimitada._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Proxy-ul dvs. universal API - un punct final, peste 36 de furnizori, zero timpi
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Furnizor AI gratuit pentru agenții tăi preferați de codare
_Conectați orice instrument IDE sau CLI alimentat de AI prin OmniRoute — gateway API gratuit pentru codare nelimitată._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+35 -25
View File
@@ -11,28 +11,6 @@ _Ваш универсальный API-прокси — одна точка до
---
### 🆕 Новое в v2.7.0
- **Подключаемая RouterStrategy** — стратегии по правилам, стоимости и задержке
- **Многоязычное распознавание намерений** — маршрутизация на 30+ языках
- **Дедупликация запросов** — устранение дублей по хэшу содержимого
- **Новые провайдеры:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Обновлённые цены:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Бесплатный AI-провайдер для ваших любимых агентов программирования
_Подключайте любую IDE или CLI-инструмент с AI через OmniRoute — бесплатный API gateway для неограниченного программирования._
@@ -118,6 +96,38 @@ _Подключайте любую IDE или CLI-инструмент с 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
@@ -376,7 +386,7 @@ Claude Code, Codex, Gemini CLI, Copilot — все используют OAuth 2.
- **Панель управления унифицированными журналами** — 4 вкладки: журналы запросов, журналы прокси, журналы аудита, консоль.
- **Консольный просмотр журнала** — просмотрщик в режиме терминала в режиме реального времени с уровнями с цветовой кодировкой, автоматической прокруткой, поиском и фильтрацией.
- **Журналы прокси-сервера SQLite** — постоянные журналы, сохраняющиеся после перезапуска сервера.
- **Площадка переводчика** — 4 режима отладки: Площадка (перевод формата), Тестер чата (туда и обратно), Тестовый стенд (пакетный), Мониторинг в реальном времени (в режиме реального времени).
- **Площадка транслятора (Translator Playground)** — 4 режима отладки: Площадка (перевод формата), Тестер чата (туда и обратно), Тестовый стенд (пакетный), Мониторинг в реальном времени (в режиме реального времени).
- **Запрос телеметрии** — задержка p50/p95/p99 + отслеживание X-Request-Id
- **Журналирование на основе файлов с ротацией** — перехватчик консоли записывает все в журнал JSON с ротацией на основе размера.
@@ -441,7 +451,7 @@ Claude Code, Codex, Gemini CLI, Copilot — все используют OAuth 2.
- **Оценки LLM** — тестирование золотого набора с 10 предварительно загруженными вариантами, охватывающими приветствия, математику, географию, генерацию кода, соответствие JSON, перевод, уценку, отказ от безопасности.
- **4 стратегии сопоставления**`exact`, `contains`, `regex`, `custom` (функция JS)
- **Тестовый стенд Translator Playground** — пакетное тестирование с несколькими входными данными и ожидаемыми результатами, сравнение между поставщиками.
- **Тестовый стенд (Testbed)** — пакетное тестирование с несколькими входными данными и ожидаемыми результатами, сравнение между поставщиками.
- **Тестер чата** — полный цикл с визуальным отображением ответов.
- **Живой монитор** — поток всех запросов, проходящих через прокси, в реальном времени.
@@ -1006,7 +1016,7 @@ OmniRoute включает встроенный фреймворк оценки
- Приветствия, математика, география, генерация кода
- Соответствие формату JSON, перевод, markdown
- Отказ от небезопасного контента, подсчёт, булева логика
- Отказ от небезопасного контента (Safety refusal), подсчёт, булева логика
### Стратегии оценки
+32 -22
View File
@@ -11,28 +11,6 @@ _Váš univerzálny proxy server API jeden koncový bod, 36+ poskytovateľov
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Bezplatný poskytovateľ AI pre vašich obľúbených kódovacích agentov
_Pripojte akýkoľvek nástroj IDE alebo CLI poháňaný AI cez OmniRoute bezplatnú bránu API pre neobmedzené kódovanie._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Din universella API-proxy — en slutpunkt, 36+ leverantörer, noll driftstopp.
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Gratis AI-leverantör för dina favoritkodningsagenter
_Anslut alla AI-drivna IDE- eller CLI-verktyg via OmniRoute — gratis API-gateway för obegränsad kodning._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _พร็อกซี API สากลของคุณ — จุดสิ้
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 ผู้ให้บริการ AI ฟรีสำหรับตัวแทนการเขียนโค้ดที่คุณชื่นชอบ
_เชื่อมต่อเครื่องมือ IDE หรือ CLI ที่ขับเคลื่อนด้วย AI ผ่าน OmniRoute — เกตเวย์ API ฟรีสำหรับการเข้ารหัสไม่จำกัด_
@@ -118,6 +96,38 @@ _เชื่อมต่อเครื่องมือ 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Ваш універсальний API-проксі — одна кінцева
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Безкоштовний постачальник AI для ваших улюблених агентів кодування
_Підключіть будь-який інструмент IDE або CLI на основі штучного інтелекту через OmniRoute — безкоштовний шлюз API для необмеженого програмування._
@@ -118,6 +96,38 @@ _Підключіть будь-який інструмент 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _Proxy API phổ quát của bạn — một điểm cuối, hơn 36 nhà cung c
---
### 🆕 What's New in v2.7.0
- **Pluggable RouterStrategy** — rules, cost, and latency routing strategies
- **Multilingual intent detection** — routing scoring in 30+ languages
- **Request deduplication** — prevent duplicate API calls via content hash
- **New providers:** Grok-4 Fast (xAI), GLM-5 / Z.AI, MiniMax M2.5, Kimi K2.5
- **Updated pricing:** Grok-4 Fast $0.20/$0.50/M, GLM-5 $0.50/M, MiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 Nhà cung cấp AI miễn phí cho các tác nhân mã hóa yêu thích của bạn
_Kết nối mọi công cụ IDE hoặc CLI được hỗ trợ bởi AI thông qua OmniRoute — cổng API miễn phí để mã hóa không giới hạn._
@@ -118,6 +96,38 @@ _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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+32 -22
View File
@@ -11,28 +11,6 @@ _您的通用 API 代理 — 一个端点,36+ 提供商,零停机时间。_
---
### 🆕 v2.7.0 新功能
- **可插拔 RouterStrategy** — 支持规则、成本和延迟策略
- **多语言意图检测** — 支持 30+ 语言的路由评分
- **请求去重** — 基于内容哈希避免重复 API 调用
- **新增提供商:** Grok-4 Fast (xAI)、GLM-5 / Z.AI、MiniMax M2.5、Kimi K2.5
- **价格更新:** Grok-4 Fast $0.20/$0.50/MGLM-5 $0.50/MMiniMax M2.5 $0.30/M
---
### 🚀 New in v2.0.9+ — Playground, CLI Fingerprints & ACP
| Feature | What It Does |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing |
| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** |
| 🤝 **ACP Support (Agent Client Protocol)** | CLI agent discovery (Codex, Claude, Goose, Gemini CLI, OpenClaw), process spawner, `/api/acp/agents` endpoint |
| 🤖 **ACP Agents Dashboard** | Debug > Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool |
| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator |
| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID |
| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart |
### 🤖 为您最爱的编程代理提供免费 AI
_通过 OmniRoute 连接任何 AI 驱动的 IDE 或 CLI 工具 — 免费 API 网关,无限编程。_
@@ -118,6 +96,38 @@ _通过 OmniRoute 连接任何 AI 驱动的 IDE 或 CLI 工具 — 免费 API
---
### 🆕 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) |
| 🔑 **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 |
### 🆕 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) |
| 🔑 **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 |
---
[![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)
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: OmniRoute API
version: 3.0.0-rc.15
version: 3.0.1
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,
-280
View File
@@ -1,280 +0,0 @@
# Zed IDE OAuth Import - Documentation
## Overview
OmniRoute can automatically import OAuth credentials from Zed IDE by accessing the operating system's secure keychain storage. This eliminates manual credential copying and enables seamless integration between Zed IDE and OmniRoute.
## How It Works
Zed IDE stores all OAuth tokens in your operating system's native credential storage:
- **macOS**: Keychain Access
- **Windows**: Credential Manager
- **Linux**: libsecret / GNOME Keyring
As documented in [Zed's official documentation](https://zed.dev/docs/ai/llm-providers):
> "API keys are not stored as plain text in your settings file, but rather in your OS's secure credential storage."
OmniRoute uses the `keytar` library to securely read these credentials with your permission.
## Supported Providers
The following Zed IDE providers can be imported:
- OpenAI
- Anthropic (Claude)
- Google AI (Gemini)
- Mistral
- xAI (Grok)
- OpenRouter
- DeepSeek
## Installation
### Prerequisites
**Linux users** must install libsecret development files:
```bash
# Debian/Ubuntu
sudo apt-get install libsecret-1-dev
# Red Hat/Fedora
sudo yum install libsecret-devel
# Arch Linux
sudo pacman -S libsecret
```
**macOS and Windows** users don't need additional dependencies.
### Install Dependencies
```bash
npm install keytar
```
Or using pnpm:
```bash
pnpm install keytar
```
## Usage
### API Endpoint
**Endpoint**: `POST /api/providers/zed/import`
**Request**:
```bash
curl -X POST http://localhost:20128/api/providers/zed/import \
-H "Content-Type: application/json"
```
**Response** (success):
```json
{
"success": true,
"count": 3,
"providers": ["openai", "anthropic", "google"],
"zedInstalled": true
}
```
**Response** (Zed not installed):
```json
{
"success": false,
"error": "Zed IDE does not appear to be installed on this system.",
"zedInstalled": false
}
```
**Response** (permission denied):
```json
{
"success": false,
"error": "Keychain access denied. Please grant permission when prompted by your OS."
}
```
### Programmatic Usage
```typescript
import {
discoverZedCredentials,
getZedCredential,
isZedInstalled
} from '@/lib/zed-oauth/keychain-reader';
// Check if Zed is installed
const installed = await isZedInstalled();
// Discover all credentials
const credentials = await discoverZedCredentials();
console.log(`Found ${credentials.length} credentials`);
// Get specific provider
const openaiCred = await getZedCredential('openai');
if (openaiCred) {
console.log(`OpenAI token: ${openaiCred.token.substring(0, 10)}...`);
}
```
## Security
### Permission Prompt
The first time OmniRoute accesses the keychain, your operating system will prompt for permission:
- **macOS**: "OmniRoute wants to access your keychain"
- **Windows**: UAC prompt or Credential Manager authorization
- **Linux**: "Authentication required to access the default keyring"
You can grant:
- **Allow Once**: Permission for this session only
- **Always Allow**: Permanent access (until revoked)
- **Deny**: Credential import will fail
### Data Handling
1. **No Master Password Storage**: OmniRoute never stores your keychain master password
2. **Minimal Access**: Only reads Zed-specific credential entries
3. **Encryption at Rest**: Imported tokens are encrypted using AES-256-GCM in OmniRoute's database
4. **Audit Logging**: All import attempts are logged for security tracking
### Revoking Access
To revoke OmniRoute's keychain access:
**macOS**:
1. Open **Keychain Access** app
2. Go to **Keychain Access****Preferences** → **Access Control**
3. Remove OmniRoute from the allowed applications list
**Windows**:
1. Open **Credential Manager**
2. Find OmniRoute entries
3. Remove or modify permissions
**Linux (GNOME)**:
1. Open **Seahorse** (Passwords and Keys)
2. Find OmniRoute entries under Login keyring
3. Remove or edit access control
## Troubleshooting
### "Keychain access denied" Error
**Cause**: User denied permission prompt or previous denial cached.
**Solution**:
1. Retry the import (permission prompt will appear again)
2. Check system keychain settings (see "Revoking Access" section)
3. On macOS, restart Keychain Access app
### "Keychain service not available" Error
**Cause**: OS credential storage not configured or missing dependencies.
**Solution** (Linux):
```bash
# Install libsecret
sudo apt-get install libsecret-1-dev
# Ensure keyring daemon is running
systemctl --user status gnome-keyring-daemon
```
### "Zed IDE does not appear to be installed"
**Cause**: Zed config directory not found in expected locations.
**Solution**:
- Verify Zed is installed: `zed --version`
- Check config exists at:
- Linux: `~/.config/zed`
- macOS: `~/Library/Application Support/Zed`
- Windows: `%APPDATA%\Zed`
### No Credentials Found
**Cause**: Zed hasn't stored OAuth tokens yet, or using API keys instead of OAuth.
**Solution**:
1. Open Zed IDE
2. Go to Agent Panel settings (⌘/Ctrl+Shift+P → "agent: open settings")
3. Add at least one provider with OAuth/API key
4. Retry import in OmniRoute
## Command-Line Alternatives
For advanced users who prefer manual extraction:
### macOS
```bash
# Find OpenAI token
security find-generic-password -s "zed-openai" -w
# List all Zed credentials
security dump-keychain | grep -i "zed"
```
### Linux (GNOME Keyring)
```bash
# Using secret-tool
secret-tool lookup service zed-openai
# List all Zed entries
secret-tool search service zed
```
### Windows (PowerShell)
```powershell
# List Zed credentials
cmdkey /list | Select-String "zed"
```
## Technical Reference
### Service Name Patterns
Zed IDE uses these service names for keychain storage:
| Provider | Service Names |
|----------|--------------|
| OpenAI | `zed-openai`, `ai.zed.openai`, `Zed-OpenAI` |
| Anthropic | `zed-anthropic`, `ai.zed.anthropic`, `Zed-Anthropic` |
| Google AI | `zed-google`, `ai.zed.google`, `Zed-Google` |
| Mistral | `zed-mistral`, `ai.zed.mistral`, `Zed-Mistral` |
| xAI | `zed-xai`, `ai.zed.xai`, `Zed-xAI` |
| OpenRouter | `zed-openrouter`, `ai.zed.openrouter`, `Zed-OpenRouter` |
| DeepSeek | `zed-deepseek`, `ai.zed.deepseek`, `Zed-DeepSeek` |
### keytar API
```typescript
// Get password for service+account
const token = await keytar.getPassword('service-name', 'account-name');
// Find all credentials for a service
const credentials = await keytar.findCredentials('service-name');
// Set password (not used in import, but available)
await keytar.setPassword('service-name', 'account-name', 'password');
```
## References
- [Zed IDE LLM Providers Documentation](https://zed.dev/docs/ai/llm-providers)
- [keytar Library on GitHub](https://github.com/atom/node-keytar)
- [VS Code Secret Storage](https://code.visualstudio.com/api/references/vscode-api#SecretStorage)
- [GitHub Copilot CLI Authentication](https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli)
## Support
For issues or questions:
- Open an issue on [OmniRoute GitHub](https://github.com/diegosouzapw/OmniRoute/issues)
- Join the [WhatsApp Community](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
+1
View File
@@ -29,6 +29,7 @@ const eslintConfig = [
ignores: [
// Next.js build output
".next/**",
"src/.next/**",
"out/**",
"build/**",
"next-env.d.ts",
+87 -79
View File
@@ -1,6 +1,6 @@
# OmniRoute
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 36+ AI providers — all through a single OpenAI-compatible endpoint.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 67+ AI providers — all through a single OpenAI-compatible endpoint.
## Overview
@@ -8,19 +8,19 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
**Key value:** One endpoint (`http://localhost:20128/v1`), unlimited models, zero downtime, minimal cost.
**Current version:** 2.0.13
**Current version:** 3.0.0
## Tech Stack
- **Runtime:** Node.js >= 18
- **Framework:** Next.js 16 (App Router) with TypeScript
- **Framework:** Next.js 16 (App Router) with TypeScript 5.9
- **Database:** SQLite via better-sqlite3 (local, zero-config)
- **State management:** Zustand (client), lowdb (server JSON persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
- **Background jobs:** Custom token health check scheduler
- **Background jobs:** Custom token health check scheduler, 24h model auto-sync
- **Streaming:** Server-Sent Events (SSE) for real-time proxy responses
- **Proxy engine:** Custom pipeline with format translation, circuit breaker, rate limiting
- **Proxy engine:** Custom pipeline with format translation, circuit breaker, rate limiting, auto-combo engine
- **i18n:** next-intl with 30 languages
- **Package:** Published on npm (`omniroute`) and Docker Hub (`diegosouzapw/omniroute`)
@@ -35,14 +35,14 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── agents/ # ACP Agents dashboard (CLI agent detection + custom agents)
│ │ │ ├── analytics/ # Usage analytics and charts
│ │ │ ├── api-manager/ # API key management
│ │ │ ├── cli-tools/ # CLI tool configuration (Claude, Codex, Gemini, etc.)
│ │ │ ├── combos/ # Model combo management
│ │ │ ├── costs/ # Cost tracking
│ │ │ ├── endpoint/ # Endpoint info and cloud proxy
│ │ │ ├── health/ # System health monitoring
│ │ │ ├── cli-tools/ # CLI tool configuration (Claude Code, Codex, Gemini CLI, etc.)
│ │ │ ├── combos/ # Model combo management (9 strategies + 4 templates)
│ │ │ ├── costs/ # Cost tracking per provider/model
│ │ │ ├── endpoint/ # Unified: Endpoint Proxy, MCP, A2A, API Endpoints tabs
│ │ │ ├── health/ # System health (uptime, circuit breakers, latency)
│ │ │ ├── limits/ # Rate limits dashboard
│ │ │ ├── logs/ # Request logs viewer
│ │ │ ├── media/ # Image/video/music generation
│ │ │ ├── logs/ # Request, Proxy, Audit, Console logs (tabbed)
│ │ │ ├── media/ # Image/video/music generation + transcription
│ │ │ ├── playground/ # Model playground (Monaco editor, streaming)
│ │ │ ├── providers/ # Provider management (OAuth + API key + free)
│ │ │ ├── settings/ # Settings tabs (General, Appearance, Security, Routing, Resilience, Advanced)
@@ -51,7 +51,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ ├── api/ # REST API endpoints
│ │ │ ├── v1/ # OpenAI-compatible API (chat, models, embeddings, images, audio)
│ │ │ ├── acp/ # ACP agent management API
│ │ │ ├── oauth/ # OAuth flows per provider (authorize, exchange, callback)
│ │ │ ├── oauth/ # OAuth flows per provider
│ │ │ ├── providers/ # Provider CRUD and batch testing
│ │ │ ├── models/ # Dashboard model listing and aliases
│ │ │ ├── combos/ # Combo CRUD (multi-model fallback chains)
@@ -59,131 +59,143 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ └── login/ # Login page
│ ├── domain/ # Domain types and business logic interfaces
│ ├── i18n/ # Internationalization
│ │ └── messages/ # 30 language JSON files (ar, bg, cs, da, de, en, es, fi, fr, he, hu, id, in, it, ja, ko, ms, nl, no, phi, pl, pt, pt-BR, ro, ru, sk, sv, th, uk-UA, vi, zh-CN)
│ │ └── messages/ # 30 language JSON files
│ ├── lib/ # Core libraries
│ │ ├── acp/ # ACP agent registry and manager (14 built-in agents + custom)
│ │ ├── db/ # SQLite database layer (providers, combos, prompts, logs)
│ │ ├── a2a/ # Agent-to-Agent v0.3 protocol server
│ │ ├── acp/ # ACP agent registry and manager (14 built-in + custom)
│ │ ├── db/ # SQLite database layer (core, providers, models, combos, apiKeys, settings, backup)
│ │ ├── oauth/ # OAuth providers, services, and utilities
│ │ │ ├── providers/ # Provider-specific OAuth configs (GitHub, Google, Claude, etc.)
│ │ │ ├── constants/ # Default OAuth credentials (overridable via env)
│ │ │ ├── providers/ # Provider-specific OAuth configs
│ │ │ ├── services/ # Provider-specific token exchange logic
│ │ │ └── utils/ # PKCE, callback server, token helpers
│ │ ├── cloudSync.ts # Cloud sync via Cloudflare Workers
│ │ ├── tokenHealthCheck.ts # Background OAuth token refresh scheduler
│ │ └── localDb.ts # Unified database access layer
│ │ └── localDb.ts # Unified re-export layer for all DB modules
│ ├── shared/ # Shared utilities, components, and constants
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, etc.)
│ │ ├── constants/ # Provider definitions, model lists, pricing
│ │ ├── validation/ # Zod schemas (settings, providers, etc.)
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
│ │ ├── constants/ # Provider definitions, model lists, pricing, upstream headers
│ │ ├── validation/ # Zod schemas (settings, providers, routes)
│ │ └── utils/ # Helpers (auth, CORS, error codes, machine ID)
│ ├── sse/ # SSE proxy pipeline
│ │ ├── services/ # Auth resolution, format translation, response handling
│ │ └── middleware/ # Rate limiting, circuit breaker, caching, idempotency
│ ├── store/ # Zustand client-side stores (theme, providers, etc.)
── types/ # TypeScript type definitions
│ ├── proxy.ts # Main proxy request handler
│ └── server-init.ts # Server initialization (DB, health checks)
── types/ # TypeScript type definitions
├── open-sse/ # Standalone SSE server (npm workspace)
│ ├── config/ # Model registries (embedding, image, audio, rerank, moderation, CLI fingerprints)
│ ├── handlers/ # Request handlers per API type
│ ├── mcp-server/ # Built-in MCP server (16 tools, audit logging, scope auth)
── translators/ # Format translators (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama)
├── tests/ # Test suites
│ ├── handlers/ # Request handlers per API type (chat, responses, embeddings, images, audio, search)
│ ├── mcp-server/ # Built-in MCP server (16 tools, 3 transports: stdio/SSE/streamable-HTTP)
── services/ # Auto-combo engine (6-factor scoring, 4 mode packs, bandit exploration)
│ └── translator/ # Format translators (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama ↔ DeepSeek)
├── tests/ # Test suites (926 assertions)
│ ├── unit/ # Unit tests (32+ test files)
│ └── integration/ # Integration tests
├── docs/ # Documentation (with 29-language i18n subdirectories)
│ ├── i18n/ # Translated docs (ar, bg, cs, da, de, es, fi, fr, he, hu, id, in, it, ja, ko, ms, nl, no, phi, pl, pt, pt-BR, ro, ru, sk, sv, th, uk-UA, vi, zh-CN)
├── docs/ # Documentation
│ ├── i18n/ # 30-language translated READMEs
│ ├── screenshots/ # Dashboard screenshots
│ ├── a2a-server.md # A2A agent protocol documentation
│ ├── auto-combo.md # Auto-combo engine (6-factor scoring)
│ └── mcp-server.md # MCP server (16 tools)
├── electron/ # Electron desktop app
├── bin/ # CLI entry points (omniroute, reset-password)
└── .env.example # Environment variable template
```
## Key Features (v2.0.13)
## Key Features (v3.0.0)
### Core Proxy
- **36+ AI providers** with automatic format translation
- **67+ AI providers** with automatic format translation
- **6 routing strategies**: priority, weighted, round-robin, random, least-used, cost-optimized
- **4-tier fallback**: Subscription → API Key → Cheap → Free
- **Auto-combo engine**: Self-healing routing optimization with 6-factor scoring, bandit exploration, progressive cooldown
- **Semantic caching** with cache hit/miss headers
- **Idempotency** with configurable dedup window
- **Circuit breaker** per provider with configurable thresholds
- **Provider Icons**: 130+ provider logos via `@lobehub/icons` (SVG) with PNG fallback
- **Model Auto-Sync**: 24h scheduler refreshes model lists for 16 providers
- **Registered Keys API**: Auto-provision API keys via `POST /api/v1/registered-keys` with quota enforcement
- **926 tests** with 0 failures
### Anti-Ban Protection
### Security
- **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 sanitization**: Internal `<omniModel>` tags never leak to clients in SSE streams
- **TLS Fingerprint Spoofing** — Browser-like TLS fingerprint to reduce bot detection
- **CLI Fingerprint Matching** — Per-provider request signature matching (headers/body ordering) to match native CLI tools. Proxy IP is preserved.
- **CLI Fingerprint Matching** — Per-provider request signature matching
### Dashboard Pages
- **Providers** — OAuth, API key, and free provider management
- **Combos** — Multi-model fallback chain builder with templates
- **Providers** — OAuth, API key, and free provider management with ProviderIcon SVG icons
- **Combos** — Multi-model combo builder with 4 templates (Free Stack, High Availability, Cost Saver, Balanced) + 9 strategies
- **Analytics** — Token consumption, cost, heatmaps, distributions
- **Health** — Uptime, memory, latency percentiles, circuit breakers
- **Logs** — Real-time request log viewer with filtering
- **Logs** — Request, Proxy, Audit, Console (tabbed)
- **Costs** — Cost tracking per provider/model
- **Limits** — Rate limit monitoring
- **CLI Tools** — One-click configuration for 10+ AI CLI tools
- **CLI Agents** — Grid of 14 built-in agents with install detection + custom agent registration
- **CLI Agents** — Grid of 14+ built-in agents with ProviderIcon and install detection + custom agent registration
- **Playground** — Test any model with Monaco editor, streaming responses
- **Media** — Image/video/music generation (DALL-E, FLUX, AnimateDiff, etc.)
- **Media** — Image/video/music generation (DALL-E, FLUX, etc.) + audio transcription (up to 2GB files)
- **Translator** — Format debugging: playground, chat tester, test bench, live monitor
- **Settings** — General, Appearance (7 color themes), Security (TLS/CLI fingerprint, IP filter), Routing, Resilience, Advanced
- **Endpoint** — Unified API endpoint info + cloud proxy
### Sidebar Organization
- **Main**: Home, Endpoints, API Manager, Providers, Combos, Costs, Analytics, Limits
- **CLI**: Tools, Agents
- **Debug**: Translator, Playground, Media
- **System**: Health, Logs, Settings
- **Help**: Docs, Issues
- **Endpoint** — Unified: Endpoint Proxy, MCP Server, A2A Server, API Endpoints (tabbed)
### Protocol Support
- **OpenAI-compatible** — `/v1/chat/completions`, `/v1/models`, `/v1/embeddings`, `/v1/images/generations`, `/v1/audio/transcriptions`
- **OpenAI-compatible** — `/v1/chat/completions`, `/v1/models`, `/v1/embeddings`, `/v1/images/generations`, `/v1/audio/transcriptions`, `/v1/audio/speech`
- **Anthropic** — `/v1/messages`, `/v1/messages/count_tokens`
- **OpenAI Responses** — `/v1/responses`
- **Gemini** — `/v1beta/models`, `/v1beta/models/{...path}`
- **Ollama** — `/v1/api/chat`, `/api/tags`
- **MCP** — 16-tool MCP server with scope-based auth
- **A2A** — Agent-to-Agent protocol (smart-routing, quota-management skills)
- **MCP** — 16-tool MCP server with scope-based auth (3 transports: stdio, SSE, streamable HTTP)
- **A2A** — Agent-to-Agent v0.3 protocol (JSON-RPC 2.0, smart-routing + quota-management skills)
- **ACP** — Agent detection, custom agent registry
### MCP Server (16 Tools)
| Category | Tools |
|-----------|-------|
| Essential | `get_health`, `list_combos`, `get_combo_metrics`, `switch_combo`, `check_quota`, `route_request`, `cost_report`, `list_models_catalog` |
| Advanced | `simulate_route`, `set_budget_guard`, `set_resilience_profile`, `test_combo`, `get_provider_metrics`, `best_combo_for_task`, `explain_route`, `get_session_snapshot` |
### Internationalization
- 30 languages for UI (sidebar, settings, agents, and all dashboard pages)
- 30 READMEs (root README.md + 29 translated README.*.md)
- 29 translated doc sets in docs/i18n/
- 30 languages for UI (all dashboard pages)
- 30 translated READMEs in docs/i18n/
- Language switcher in documentation
## Key Architectural Decisions
1. **OpenAI-compatible API surface:** All incoming requests follow the OpenAI API format (`/v1/chat/completions`, `/v1/models`, etc.). This makes OmniRoute a drop-in replacement for any tool that supports custom OpenAI endpoints.
1. **OpenAI-compatible API surface:** All incoming requests follow the OpenAI API format. This makes OmniRoute a drop-in replacement for any tool that supports custom OpenAI endpoints.
2. **Provider abstraction via format translators:** Each AI provider (Claude, Gemini, etc.) has a translator in `open-sse/translators/` that converts between the OpenAI format and the provider's native format. This happens transparently.
2. **Provider abstraction via format translators:** Each AI provider has a translator in `open-sse/translator/` that converts between OpenAI format and the provider's native format transparently.
3. **Connection-based provider model:** Providers are stored as "connections" in SQLite. Each connection has an `id`, `provider`, `authType` (oauth/apikey/free), `isActive` flag, and credentials. Multiple connections per provider are supported for multi-account rotation.
3. **Connection-based provider model:** Providers are stored as "connections" in SQLite. Each connection has an `id`, `provider`, `authType` (oauth/apikey/free), `isActive` flag, and credentials. Multiple connections per provider for multi-account rotation.
4. **Combo system for fallback:** Users create "combos" — ordered lists of `provider/model` pairs. The proxy tries each in order until one succeeds. Supports 6 strategies.
4. **Combo system for fallback:** Users create "combos" — ordered lists of `provider/model` pairs. The proxy tries each in order until one succeeds. Supports 9 strategies including auto-combo with self-healing.
5. **SSE proxy pipeline (`src/sse/`):** The proxy pipeline is middleware-based: request → auth resolution → rate limiting → circuit breaker → format translation → upstream call → response translation → SSE streaming back to client.
5. **SSE proxy pipeline:** The proxy pipeline is middleware-based: request → auth resolution → rate limiting → circuit breaker → format translation → upstream call → response translation → SSE streaming back to client.
6. **SQLite for persistence:** All state (providers, combos, logs, settings) is stored in a single SQLite database file at `data/omniroute.db`. This keeps the app self-contained and zero-config.
6. **SQLite for persistence:** All state (providers, combos, logs, settings, API keys) stored in a single SQLite database. All DB operations go through `src/lib/db/` modules, never raw SQL in routes.
7. **OAuth with PKCE:** OAuth flows use PKCE for security. A local callback server handles the redirect. Token refresh is handled by a background job (`tokenHealthCheck.ts`).
7. **OAuth with PKCE:** OAuth flows use PKCE for security. Token refresh handled by background job (`tokenHealthCheck.ts`).
8. **ACP Agent Registry:** 14 built-in CLI agents with dynamic detection and a 60-second cache. Custom agents can be added via dashboard or API, stored in settings DB.
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in `src/lib/db/` modules (core, providers, models, combos, apiKeys, settings, backup).
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
## Main Flows
### Proxy Request Flow
1. Client sends OpenAI-format request to `/v1/chat/completions`
2. API key validation (`src/shared/utils/apiAuth.ts`)
2. API key validation
3. Model resolution: direct model or combo lookup
4. For combos: iterate through models in fallback order
4. For combos: iterate through models with selected strategy
5. Auth resolution: get credentials for the target provider
6. Format translation: OpenAI → provider native format
7. CLI fingerprint matching (if enabled for provider)
8. Upstream request with circuit breaker and rate limiting
9. Response translation: provider → OpenAI format
10. SSE streaming back to client
10. omniModel tag sanitization (strip internal tags)
11. SSE streaming back to client
### OAuth Flow
1. Dashboard initiates `/api/oauth/[provider]/authorize`
@@ -192,31 +204,27 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
4. Tokens stored as a provider connection in SQLite
5. Background job refreshes tokens before expiry
### Model Listing
- `/api/models` — Dashboard endpoint, lists all defined models with aliases
- `/v1/models` — OpenAI-compatible endpoint, lists only models from active providers
## Important Notes for LLMs
1. **Two model endpoints exist:** `/api/models` (dashboard, all models) and `/v1/models` (OpenAI-compatible, active only). Don't confuse them.
1. **Two model endpoints exist:** `/api/models` (dashboard, all models) and `/v1/models` (OpenAI-compatible, active only).
2. **Provider IDs vs aliases:** Providers have both an ID (`claude`, `github`) and a short alias (`cc`, `gh`). Models are referenced as `alias/model-name` (e.g., `cc/claude-opus-4-6`).
3. **The `open-sse/` directory is a separate npm workspace** with its own config, handlers, and translators. It handles the actual SSE streaming and format translation.
3. **The `open-sse/` directory is a separate npm workspace** with its own config, handlers, and translators.
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database migrations:** SQLite schema is managed inline in `src/lib/db/core.ts` and `src/lib/db/providers.ts`. No migration framework — schema changes are applied on startup.
5. **Database layer:** Operations go through `src/lib/db/` modules. `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests use Node.js built-in test runner:** Run `npm test` or `node --test tests/unit/*.test.mjs`. Playwright is used for E2E tests.
6. **Tests use Node.js built-in test runner:** 926 assertions across 32+ test files. Run `npm test`.
7. **The proxy pipeline is in `src/sse/`**, not in `src/app/api/v1/`. The API routes in `src/app/api/v1/` delegate to the SSE server running on a separate Express instance.
7. **MCP and A2A pages are embedded as tabs inside `/dashboard/endpoint`**, not standalone routes.
8. **Sidebar sections:** Main nav, CLI (Tools + Agents), Debug (Translator + Playground + Media), System (Health + Logs + Settings), Help (Docs + Issues).
8. **ACP agents** are in `src/lib/acp/registry.ts` (14 built-in) with a 60s detection cache. Custom agents stored via settings DB.
9. **ACP agents** are in `src/lib/acp/registry.ts` (14 built-in) with a 60s detection cache. Custom agents stored via `src/shared/validation/settingsSchemas.ts`.
9. **Auto-combo engine** in `open-sse/services/autoCombo/` — 6-factor scoring, 4 mode packs, bandit exploration, progressive cooldown.
10. **CLI fingerprint configs** are in `open-sse/config/cliFingerprints.ts`. They match native CLI request patterns per provider.
10. **Docker:** Dockerfile has two targets: `runner-base` and `runner-cli`. `docker-compose.yml` for dev (3 profiles), `docker-compose.prod.yml` for production (port 20130).
## Links
+13 -3
View File
@@ -26,9 +26,18 @@ const CONFIG_TTL_MS = 60_000;
let lastLoadTime = 0;
let cachedProviders = null;
// Survives Next.js dev HMR: module-level cache resets but process is the same (V4 pattern).
type CredGlobals = typeof globalThis & { __omnirouteCredNoFileLogged?: boolean };
function credGlobals(): CredGlobals {
return globalThis as CredGlobals;
}
/**
* Resolve the path to provider-credentials.json
* Priority: DATA_DIR env ./data (project root)
* Resolves the path to provider-credentials.json using the application's
* data directory. Delegates to resolveDataDir() which handles DATA_DIR env,
* platform-specific defaults, and fallback logic.
*
* previous: Priority: DATA_DIR env ./data (project root)
*/
function resolveCredentialsPath() {
return join(resolveDataDir(), "provider-credentials.json");
@@ -51,8 +60,9 @@ export function loadProviderCredentials(providers) {
const credPath = resolveCredentialsPath();
if (!existsSync(credPath)) {
if (!cachedProviders) {
if (!credGlobals().__omnirouteCredNoFileLogged) {
console.log("[CREDENTIALS] No external credentials file found, using defaults.");
credGlobals().__omnirouteCredNoFileLogged = true;
}
cachedProviders = providers;
lastLoadTime = Date.now();
+18 -1
View File
@@ -16,6 +16,8 @@ export interface RegistryModel {
toolCalling?: boolean;
targetFormat?: string;
unsupportedParams?: readonly string[];
/** Maximum context window in tokens */
contextLength?: number;
}
// Reasoning models reject temperature, top_p, penalties, logprobs, n.
@@ -65,6 +67,8 @@ export interface RegistryEntry {
chatPath?: string;
clientVersion?: string;
passthroughModels?: boolean;
/** Default context window for all models in this provider (can be overridden per-model) */
defaultContextLength?: number;
}
interface LegacyProvider {
@@ -137,6 +141,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
urlSuffix: "?beta=true",
authType: "oauth",
authHeader: "x-api-key",
defaultContextLength: 200000,
headers: {
"Anthropic-Version": "2023-06-01",
"Anthropic-Beta":
@@ -180,6 +185,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
},
authType: "apikey",
authHeader: "x-goog-api-key",
defaultContextLength: 1000000,
oauth: {
clientIdEnv: "GEMINI_OAUTH_CLIENT_ID",
clientIdDefault: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
@@ -216,6 +222,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
},
authType: "oauth",
authHeader: "bearer",
defaultContextLength: 1000000,
oauth: {
clientIdEnv: "GEMINI_CLI_OAUTH_CLIENT_ID",
clientIdDefault: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
@@ -247,6 +254,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
baseUrl: "https://chatgpt.com/backend-api/codex/responses",
authType: "oauth",
authHeader: "bearer",
defaultContextLength: 400000,
headers: {
Version: "0.92.0",
"Openai-Beta": "responses=experimental",
@@ -399,6 +407,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
responsesBaseUrl: "https://api.githubcopilot.com/responses",
authType: "oauth",
authHeader: "bearer",
defaultContextLength: 128000,
headers: {
"copilot-integration-id": "vscode-chat",
"editor-version": "vscode/1.110.0",
@@ -447,6 +456,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
baseUrl: "https://codewhisperer.us-east-1.amazonaws.com/generateAssistantResponse",
authType: "oauth",
authHeader: "bearer",
defaultContextLength: 200000,
headers: {
"Content-Type": "application/json",
Accept: "application/vnd.amazon.eventstream",
@@ -473,6 +483,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
chatPath: "/aiserver.v1.ChatService/StreamUnifiedChatWithTools",
authType: "oauth",
authHeader: "bearer",
defaultContextLength: 200000,
headers: {
"connect-accept-encoding": "gzip",
"connect-protocol-version": "1",
@@ -501,6 +512,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
baseUrl: "https://api.openai.com/v1/chat/completions",
authType: "apikey",
authHeader: "bearer",
defaultContextLength: 128000,
models: [
{ id: "gpt-4o", name: "GPT-4o" },
{ id: "gpt-4o-mini", name: "GPT-4o Mini" },
@@ -522,6 +534,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
urlSuffix: "?beta=true",
authType: "apikey",
authHeader: "x-api-key",
defaultContextLength: 200000,
headers: {
"Anthropic-Version": "2023-06-01",
},
@@ -548,6 +561,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
authType: "apikey",
authHeader: "Authorization",
authPrefix: "Bearer",
defaultContextLength: 200000,
models: [
{ id: "glm-5", name: "GLM-5" },
{ id: "kimi-k2.5", name: "Kimi K2.5" },
@@ -565,6 +579,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
authType: "apikey",
authHeader: "Authorization",
authPrefix: "Bearer",
defaultContextLength: 200000,
models: [
{ id: "minimax-m2.5-free", name: "MiniMax M2.5 Free" },
{ id: "big-pickle", name: "Big Pickle" },
@@ -580,6 +595,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
baseUrl: "https://openrouter.ai/api/v1/chat/completions",
authType: "apikey",
authHeader: "bearer",
defaultContextLength: 128000,
headers: {
"HTTP-Referer": "https://endpoint-proxy.local",
"X-Title": "Endpoint Proxy",
@@ -593,6 +609,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
format: "claude",
executor: "default",
baseUrl: "https://api.z.ai/api/anthropic/v1/messages",
defaultContextLength: 200000,
urlSuffix: "?beta=true",
authType: "apikey",
authHeader: "x-api-key",
@@ -1353,7 +1370,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
{ id: "qwen/qwen3-32b", name: "Qwen3 32B (Puter)" },
{ id: "qwen/qwen3-coder", name: "Qwen3 Coder 480B (Puter)" },
],
passthroughModels: true, // 500+ models available — users can type any Puter model ID
passthroughModels: true, // 500+ models available — users can type arbitrary Puter model IDs
},
"cloudflare-ai": {
+3 -2
View File
@@ -1,5 +1,5 @@
import crypto from "crypto";
import { BaseExecutor } from "./base.ts";
import { BaseExecutor, mergeUpstreamExtraHeaders } from "./base.ts";
import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS } from "../config/constants.ts";
const MAX_RETRY_AFTER_MS = 10000;
@@ -198,7 +198,7 @@ export class AntigravityExecutor extends BaseExecutor {
return totalMs > 0 ? totalMs : null;
}
async execute({ model, body, stream, credentials, signal, log }) {
async execute({ model, body, stream, credentials, signal, log, upstreamExtraHeaders }) {
const fallbackCount = this.getFallbackCount();
let lastError = null;
let lastStatus = 0;
@@ -208,6 +208,7 @@ export class AntigravityExecutor extends BaseExecutor {
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
const url = this.buildUrl(model, stream, urlIndex);
const headers = this.buildHeaders(credentials, stream);
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
const transformedBody = this.transformRequest(model, body, stream, credentials);
// Initialize retry counter for this URL
+28 -2
View File
@@ -58,8 +58,23 @@ export type ExecuteInput = {
signal?: AbortSignal | null;
log?: ExecutorLog | null;
extendedContext?: boolean;
/** Merged after auth + CLI fingerprint headers (values override same-named defaults). */
upstreamExtraHeaders?: Record<string, string> | null;
};
/** Apply model-level extra upstream headers (e.g. Authentication, X-Custom-Auth). */
export function mergeUpstreamExtraHeaders(
headers: Record<string, string>,
extra?: Record<string, string> | null
): void {
if (!extra) return;
for (const [k, v] of Object.entries(extra)) {
if (typeof k === "string" && k.length > 0 && typeof v === "string") {
headers[k] = v;
}
}
}
function mergeAbortSignals(primary: AbortSignal, secondary: AbortSignal): AbortSignal {
const controller = new AbortController();
@@ -204,7 +219,16 @@ export class BaseExecutor {
return { status: response.status, message: bodyText || `HTTP ${response.status}` };
}
async execute({ model, body, stream, credentials, signal, log, extendedContext }: ExecuteInput) {
async execute({
model,
body,
stream,
credentials,
signal,
log,
extendedContext,
upstreamExtraHeaders,
}: ExecuteInput) {
const fallbackCount = this.getFallbackCount();
let lastError: unknown = null;
let lastStatus = 0;
@@ -258,6 +282,8 @@ export class BaseExecutor {
bodyString = fingerprinted.bodyString;
}
mergeUpstreamExtraHeaders(finalHeaders, upstreamExtraHeaders);
const fetchOptions: RequestInit = {
method: "POST",
headers: finalHeaders,
@@ -289,7 +315,7 @@ export class BaseExecutor {
continue;
}
return { response, url, headers, transformedBody };
return { response, url, headers: finalHeaders, transformedBody };
} catch (error) {
// Distinguish timeout errors from other abort errors
const err = error instanceof Error ? error : new Error(String(error));
+3 -2
View File
@@ -20,7 +20,7 @@ declare const EdgeRuntime: string | undefined;
* @see cursorProtobuf.js for Protobuf encoding/decoding utilities
*/
import { BaseExecutor } from "./base.ts";
import { BaseExecutor, mergeUpstreamExtraHeaders } from "./base.ts";
import { PROVIDERS, HTTP_STATUS } from "../config/constants.ts";
import {
generateCursorBody,
@@ -363,9 +363,10 @@ export class CursorExecutor extends BaseExecutor {
});
}
async execute({ model, body, stream, credentials, signal, log }) {
async execute({ model, body, stream, credentials, signal, log, upstreamExtraHeaders }) {
const url = this.buildUrl();
const headers = this.buildHeaders(credentials);
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
const transformedBody = this.transformRequest(model, body, stream, credentials);
try {
+11 -1
View File
@@ -1,5 +1,6 @@
import {
BaseExecutor,
mergeUpstreamExtraHeaders,
type ExecuteInput,
type ExecutorLog,
type ProviderCredentials,
@@ -89,9 +90,18 @@ export class KiroExecutor extends BaseExecutor {
/**
* Custom execute for Kiro - handles AWS EventStream binary response
*/
async execute({ model, body, stream, credentials, signal, log }: ExecuteInput) {
async execute({
model,
body,
stream,
credentials,
signal,
log,
upstreamExtraHeaders,
}: ExecuteInput) {
const url = this.buildUrl(model, stream, 0);
const headers = this.buildHeaders(credentials, stream);
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
const transformedBody = this.transformRequest(model, body, stream, credentials);
const response = await fetch(url, {
+39 -6
View File
@@ -26,7 +26,11 @@ import {
appendRequestLog,
saveCallLog,
} from "@/lib/usageDb";
import { getModelNormalizeToolCallId, getModelPreserveOpenAIDeveloperRole } from "@/lib/localDb";
import {
getModelNormalizeToolCallId,
getModelPreserveOpenAIDeveloperRole,
getModelUpstreamExtraHeaders,
} from "@/lib/localDb";
import { getExecutor } from "../executors/index.ts";
import {
parseCodexQuotaHeaders,
@@ -76,7 +80,8 @@ export function shouldUseNativeCodexPassthrough({
if (provider !== "codex") return false;
if (sourceFormat !== FORMATS.OPENAI_RESPONSES) return false;
const normalizedEndpoint = String(endpointPath || "").replace(/\/+$/, "");
return /(?:^|\/)responses(?:\/.*)?$/i.test(normalizedEndpoint);
const segments = normalizedEndpoint.split("/");
return segments.includes("responses");
}
/**
@@ -218,7 +223,8 @@ export async function handleChatCore({
const endpointPath = String(clientRawRequest?.endpoint || "");
const sourceFormat = detectFormatFromEndpoint(body, endpointPath);
const isResponsesEndpoint = /(?:^|\/)responses(?:\/.*)?$/i.test(endpointPath);
const isResponsesEndpoint =
/\/responses(?=\/|$)/i.test(endpointPath) || /^responses(?=\/|$)/i.test(endpointPath);
const nativeCodexPassthrough = shouldUseNativeCodexPassthrough({
provider,
sourceFormat,
@@ -280,6 +286,23 @@ export async function handleChatCore({
const modelTargetFormat = getModelTargetFormat(alias, resolvedModel);
const targetFormat = modelTargetFormat || getTargetFormat(provider);
// 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))
// so A-model headers are not sent to B — see buildUpstreamHeadersForExecute.
const buildUpstreamHeadersForExecute = (modelToCall: string): Record<string, string> => {
if (modelToCall === effectiveModel) {
return {
...getModelUpstreamExtraHeaders(provider || "", model || "", sourceFormat),
...getModelUpstreamExtraHeaders(provider || "", resolvedModel || "", sourceFormat),
};
}
const r = resolveModelAlias(modelToCall);
return {
...getModelUpstreamExtraHeaders(provider || "", modelToCall || "", sourceFormat),
...getModelUpstreamExtraHeaders(provider || "", r || "", sourceFormat),
};
};
// Default to false unless client explicitly sets stream: true (OpenAI spec compliant)
const acceptHeader =
clientRawRequest?.headers && typeof clientRawRequest.headers.get === "function"
@@ -593,6 +616,7 @@ export async function handleChatCore({
signal: streamController.signal,
log,
extendedContext,
upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall),
})
);
@@ -724,16 +748,19 @@ export async function handleChatCore({
await onCredentialsRefreshed(newCredentials);
}
// Retry with new credentials
// Retry with new credentials — model + extra headers follow translatedBody.model so they
// stay aligned if this block ever runs after a path that mutates body.model (e.g. fallback).
try {
const retryModelId = String(translatedBody.model || effectiveModel);
const retryResult = await executor.execute({
model,
model: retryModelId,
body: translatedBody,
stream,
credentials: getExecutionCredentials(),
signal: streamController.signal,
log,
extendedContext,
upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId),
});
if (retryResult.response.ok) {
@@ -1041,8 +1068,14 @@ export async function handleChatCore({
}
// 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)
? translateNonStreamingResponse(responseBody, targetFormat, sourceFormat)
? translateNonStreamingResponse(
responseBody,
targetFormat,
sourceFormat,
toolNameMap as Map<string, string> | null
)
: responseBody;
// T26: Strip markdown code blocks if provider format is Claude
+24 -8
View File
@@ -1081,6 +1081,21 @@ async function handleComfyUIImageGeneration({ model, provider, providerConfig, b
}
}
type Imagen3ImageGenArgs = {
model: string;
provider: string;
providerConfig: { baseUrl: string };
body: { prompt?: string; size?: string; n?: number };
credentials: { apiKey?: string; accessToken?: string };
log?: { info?: (tag: string, msg: string) => void; error?: (tag: string, msg: string) => void } | null;
};
type Imagen3NormalizedImage = {
b64_json?: unknown;
url?: unknown;
revised_prompt?: string;
};
/**
* Handle Imagen 3 image generation
*/
@@ -1091,7 +1106,7 @@ async function handleImagen3ImageGeneration({
body,
credentials,
log,
}: any) {
}: Imagen3ImageGenArgs) {
const startTime = Date.now();
const token = credentials.apiKey || credentials.accessToken;
const aspectRatio = mapImageSize(body.size);
@@ -1142,11 +1157,11 @@ async function handleImagen3ImageGeneration({
const data = await response.json();
// Normalize response to OpenAI format
const images: any[] = [];
const images: Imagen3NormalizedImage[] = [];
if (Array.isArray(data.images)) {
images.push(
...data.images.map((img: any) => ({
b64_json: img.image || img.b64_json || img.url || img,
...data.images.map((img: Record<string, unknown>) => ({
b64_json: img.image ?? img.b64_json ?? img.url ?? img,
revised_prompt: body.prompt,
}))
);
@@ -1174,8 +1189,9 @@ async function handleImagen3ImageGeneration({
success: true,
data: { created: data.created || Math.floor(Date.now() / 1000), data: images },
};
} catch (err: any) {
if (log) log.error("IMAGE", `${provider} fetch error: ${err.message}`);
} catch (err: unknown) {
const errMsg = err instanceof Error ? err.message : String(err);
if (log) log.error("IMAGE", `${provider} fetch error: ${errMsg}`);
saveCallLog({
method: "POST",
@@ -1184,9 +1200,9 @@ async function handleImagen3ImageGeneration({
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: err.message,
error: errMsg,
}).catch(() => {});
return { success: false, status: 502, error: `Image provider error: ${err.message}` };
return { success: false, status: 502, error: `Image provider error: ${errMsg}` };
}
}
+13 -3
View File
@@ -68,11 +68,14 @@ function findBestMessageText(output: unknown[]): {
/**
* Translate non-streaming response to OpenAI format
* Handles different provider response formats (Gemini, Claude, etc.)
*
* @param toolNameMap - Optional Map<prefixedName, originalName> for Claude OAuth tool name stripping
*/
export function translateNonStreamingResponse(
responseBody: unknown,
targetFormat: string,
sourceFormat: string
sourceFormat: string,
toolNameMap?: Map<string, string> | null
): unknown {
// If already in source format (usually OpenAI), return as-is
if (targetFormat === sourceFormat || targetFormat === FORMATS.OPENAI) {
@@ -122,11 +125,14 @@ export function translateNonStreamingResponse(
typeof itemObj.arguments === "string"
? itemObj.arguments
: JSON.stringify(itemObj.arguments || {});
const rawName = toString(itemObj.name);
// Strip Claude OAuth proxy_ prefix using toolNameMap (mirrors tool_use fix for #605)
const resolvedName = toolNameMap?.get(rawName) ?? rawName;
toolCalls.push({
id: callId,
type: "function",
function: {
name: toString(itemObj.name),
name: resolvedName,
arguments: fnArgs,
},
});
@@ -334,11 +340,15 @@ export function translateNonStreamingResponse(
} else if (blockObj.type === "thinking") {
thinkingContent += toString(blockObj.thinking);
} else if (blockObj.type === "tool_use") {
// Strip Claude OAuth tool name prefix (proxy_) using the map from request translation.
// Fallback to raw name if block wasn't prefixed (disableToolPrefix path).
const rawName = toString(blockObj.name);
const strippedName = toolNameMap?.get(rawName) ?? rawName;
toolCalls.push({
id: toString(blockObj.id, `call_${Date.now()}_${toolCalls.length}`),
type: "function",
function: {
name: toString(blockObj.name),
name: strippedName,
arguments: JSON.stringify(blockObj.input || {}),
},
});
+9 -2
View File
@@ -23,12 +23,19 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
const first = chunks[0];
const contentParts = [];
const reasoningParts = [];
const accumulatedToolCalls = new Map<string, any>();
type AccumulatedToolCall = {
id: string | null;
index: number;
type: string;
function: { name: string; arguments: string };
};
const accumulatedToolCalls = new Map<string, AccumulatedToolCall>();
let unknownToolCallSeq = 0;
let finishReason = "stop";
let usage = null;
const getToolCallKey = (toolCall: any) => {
const getToolCallKey = (toolCall: Record<string, unknown>) => {
if (toolCall?.id) return `id:${toolCall.id}`;
if (Number.isInteger(toolCall?.index)) return `idx:${toolCall.index}`;
unknownToolCallSeq += 1;
+2 -2
View File
@@ -435,7 +435,7 @@ async function handleListModelsCatalog(args: { provider?: string; capability?: s
let path = "/v1/models";
let isProviderSpecific = false;
let source = "local_catalog";
let warning = undefined;
let warning: string | undefined;
if (args.provider && !args.capability) {
// Use direct provider fetch to get real-time API status
@@ -451,7 +451,7 @@ async function handleListModelsCatalog(args: { provider?: string; capability?: s
const raw = toRecord(await omniRouteFetch(path));
// If we used the direct provider endpoint
let rawModels = [];
let rawModels: unknown[] = [];
if (isProviderSpecific) {
rawModels = Array.isArray(raw.models) ? raw.models : [];
source = typeof raw.source === "string" ? raw.source : "api";
+25 -2
View File
@@ -522,10 +522,33 @@ export async function handleComboChat({
},
});
const transformedStream = res.body.pipeThrough(transform);
// FIX #585: Sanitize outbound stream — strip <omniModel> tags from
// 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.
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 transformedStream = res.body.pipeThrough(transform).pipeThrough(sanitize);
// Add model info as response header for clients that support it
const headers = new Headers(res.headers);
headers.set("X-OmniRoute-Model", modelStr);
return new Response(transformedStream, {
status: res.status,
headers: res.headers,
headers,
});
}
: handleSingleModel;
+39 -5
View File
@@ -5,14 +5,34 @@
* 3 layers: trim tool messages, compress thinking, aggressive purification.
*/
// Default token limits per provider (rough estimates based on model context windows)
const DEFAULT_LIMITS = {
import { REGISTRY } from "../config/providerRegistry.ts";
// Default token limits per provider (fallbacks when not in registry)
const DEFAULT_LIMITS: Record<string, number> = {
claude: 200000,
openai: 128000,
gemini: 1000000,
codex: 400000,
default: 128000,
};
// Environment variable overrides (highest priority)
function getEnvOverride(provider: string): number | null {
const envKey = `CONTEXT_LENGTH_${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
const envValue = process.env[envKey];
if (envValue) {
const parsed = parseInt(envValue, 10);
if (!isNaN(parsed) && parsed > 0) return parsed;
}
// Global override
const globalValue = process.env.CONTEXT_LENGTH_DEFAULT;
if (globalValue) {
const parsed = parseInt(globalValue, 10);
if (!isNaN(parsed) && parsed > 0) return parsed;
}
return null;
}
// Rough chars-per-token ratio for quick estimation
const CHARS_PER_TOKEN = 4;
@@ -27,9 +47,20 @@ export function estimateTokens(text) {
/**
* Get token limit for a provider/model combination
* Priority: Env override > Registry defaultContextLength > DEFAULT_LIMITS
*/
export function getTokenLimit(provider, model = null) {
// Check if model has a known limit
// 1. Check environment variable override first
const envOverride = getEnvOverride(provider);
if (envOverride) return envOverride;
// 2. Check registry for provider default
const registryEntry = REGISTRY[provider];
if (registryEntry?.defaultContextLength) {
return registryEntry.defaultContextLength;
}
// 3. Check if model name hints at a known limit
if (model) {
const lower = model.toLowerCase();
if (lower.includes("claude")) return DEFAULT_LIMITS.claude;
@@ -38,10 +69,13 @@ export function getTokenLimit(provider, model = null) {
lower.includes("gpt") ||
lower.includes("o1") ||
lower.includes("o3") ||
lower.includes("o4")
lower.includes("o4") ||
lower.includes("codex")
)
return DEFAULT_LIMITS.openai;
return DEFAULT_LIMITS.codex;
}
// 4. Fallback to DEFAULT_LIMITS or default
return DEFAULT_LIMITS[provider] || DEFAULT_LIMITS.default;
}
+2 -2
View File
@@ -242,8 +242,8 @@ export async function getModelInfoCore(modelStr, aliasesOrGetter) {
// FIX #73: Models like claude-haiku-4-5-20251001 sent without provider prefix
// would incorrectly route to OpenAI. Use heuristic prefix detection first.
if (/^claude-/i.test(modelId)) {
// Claude models → Antigravity (Anthropic) provider
return { provider: "antigravity", model: modelId, extendedContext };
// Claude models → Anthropic provider (canonical source for Claude models)
return { provider: "anthropic", model: modelId, extendedContext };
}
if (/^gemini-/i.test(modelId) || /^gemma-/i.test(modelId)) {
// Gemini/Gemma models → Gemini provider
+3 -3
View File
@@ -41,15 +41,15 @@ function buildAnthropicCompatibleUrl(baseUrl) {
export function detectFormatFromEndpoint(body, endpointPath = "") {
const path = String(endpointPath || "");
if (/(?:^|\/)responses(?:\/.*)?$/i.test(path)) {
if (/\/responses(?=\/|$)/i.test(path) || /^responses(?=\/|$)/i.test(path)) {
return "openai-responses";
}
if (/(?:^|\/)messages(?:\/.*)?$/i.test(path)) {
if (/\/messages(?=\/|$)/i.test(path) || /^messages(?=\/|$)/i.test(path)) {
return "claude";
}
if (/(?:^|\/)(?:chat\/completions|completions)(?:\/.*)?$/i.test(path)) {
if (/\/(?:chat\/completions|completions)(?=\/|$)/i.test(path) || /^(?:chat\/completions|completions)(?=\/|$)/i.test(path)) {
return "openai";
}
+22 -9
View File
@@ -86,7 +86,8 @@ function toDisplayLabel(value: string): string {
.filter(Boolean)
.map((part) => {
if (/^pro\+$/i.test(part)) return "Pro+";
if (/^[a-z]{2,}$/.test(part)) return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
if (/^[a-z]{2,}$/.test(part))
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
return part;
})
.join(" ")
@@ -200,7 +201,9 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
if (dataRecord.quota_snapshots) {
// Paid plan format
const snapshots = toRecord(dataRecord.quota_snapshots);
const resetAt = parseResetTime(getFieldValue(dataRecord, "quota_reset_date", "quotaResetDate"));
const resetAt = parseResetTime(
getFieldValue(dataRecord, "quota_reset_date", "quotaResetDate")
);
const premiumQuota = formatGitHubQuotaSnapshot(snapshots.premium_interactions, resetAt);
const chatQuota = formatGitHubQuotaSnapshot(snapshots.chat, resetAt);
const completionsQuota = formatGitHubQuotaSnapshot(snapshots.completions, resetAt);
@@ -225,7 +228,11 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
// Free/limited plan format
const monthlyQuotas = toRecord(dataRecord.monthly_quotas);
const usedQuotas = toRecord(dataRecord.limited_user_quotas);
const resetDate = getFieldValue(dataRecord, "limited_user_reset_date", "limitedUserResetDate");
const resetDate = getFieldValue(
dataRecord,
"limited_user_reset_date",
"limitedUserResetDate"
);
const resetAt = parseResetTime(resetDate);
const quotas: Record<string, UsageQuota> = {};
@@ -327,11 +334,7 @@ function inferGitHubPlanName(data: JsonRecord, premiumQuota: UsageQuota | null):
toNumber(getFieldValue(monthlyQuotas, "premium_interactions", "premiumInteractions"), 0);
const chatTotal = toNumber(getFieldValue(monthlyQuotas, "chat", "chat"), 0);
if (
combined.includes("PRO+") ||
combined.includes("PRO_PLUS") ||
combined.includes("PROPLUS")
) {
if (combined.includes("PRO+") || combined.includes("PRO_PLUS") || combined.includes("PROPLUS")) {
return "Copilot Pro+";
}
if (combined.includes("ENTERPRISE")) return "Copilot Enterprise";
@@ -655,8 +658,18 @@ async function getClaudeUsage(accessToken) {
}
}
// Try to extract plan tier from the OAuth response
const planRaw =
typeof data.tier === "string"
? data.tier
: typeof data.plan === "string"
? data.plan
: typeof data.subscription_type === "string"
? data.subscription_type
: null;
return {
plan: "Claude Code",
plan: planRaw || "Claude Code",
quotas,
extraUsage: data.extra_usage ?? null,
};
+2 -2
View File
@@ -1,6 +1,5 @@
import * as fs from "fs";
import * as path from "path";
import { resolveDataDir } from "../../src/lib/dataPaths";
/**
* Responses API Transformer
* Converts OpenAI Chat Completions SSE to Codex Responses API SSE format
@@ -40,7 +39,8 @@ export function createResponsesLogger(model, logsDir = null) {
const timestamp = new Date().toISOString().replace(/[:.]/g, "").slice(0, 15);
const uniqueId = Math.random().toString(36).slice(2, 8);
const baseDir = logsDir || resolveDataDir();
const baseDir = logsDir || (typeof process !== "undefined" ? process.cwd() : ".");
// previous: const baseDir = logsDir || resolveDataDir(); — reverted in #555 for Workers compat
const logDir = path.join(baseDir, "logs", `responses_${model}_${timestamp}_${uniqueId}`);
try {
@@ -29,7 +29,7 @@ type ClaudeTool = {
/**
* T02: Recursively strips empty text blocks from content arrays.
* Anthropic returns 400 "text content blocks must be non-empty" if any
* Anthropic returns 400 "text content blocks must be non-empty" when a
* text block has text: "". Must also recurse into nested tool_result.content.
* Ref: sub2api PR #1212
*/
@@ -259,11 +259,19 @@ export function openaiToClaudeRequest(model, body, stream) {
toolNameMap.set(toolName, originalName);
}
// 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 normalizedSchema =
rawSchema.type === "object" && !rawSchema.properties
? { ...rawSchema, properties: {} }
: rawSchema;
return {
name: toolName,
description: toolData.description || "",
input_schema: toolData.parameters ||
toolData.input_schema || { type: "object", properties: {}, required: [] },
input_schema: normalizedSchema,
};
});
+10 -6
View File
@@ -57,6 +57,8 @@ type TranslateState = ReturnType<typeof initState> & {
accumulatedContent?: string;
};
type UsageTokenRecord = Record<string, number>;
function getOpenAIIntermediateChunks(value: unknown): unknown[] {
if (!value || typeof value !== "object") return [];
const candidate = (value as JsonRecord)._openaiIntermediate;
@@ -108,7 +110,9 @@ export function createSSEStream(options: StreamOptions = {}) {
} = options;
let buffer = "";
let usage = null;
let usage: UsageTokenRecord | null = null;
/** Passthrough (OpenAI CC shape): saw tool_calls in stream before finish_reason */
let passthroughHasToolCalls = false;
// State for translate mode (accumulatedContent for call log response body)
const state: TranslateState | null =
@@ -223,9 +227,9 @@ export function createSSEStream(options: StreamOptions = {}) {
if (extracted) {
// Non-destructive merge: never overwrite a positive value with 0
// message_start carries input_tokens, message_delta carries output_tokens;
if (!usage) usage = {} as any;
const u = usage as Record<string, number>;
const eu = extracted as Record<string, number>;
if (!usage) usage = {};
const u = usage;
const eu = extracted as UsageTokenRecord;
if (eu.prompt_tokens > 0) u.prompt_tokens = eu.prompt_tokens;
if (eu.completion_tokens > 0) u.completion_tokens = eu.completion_tokens;
if (eu.total_tokens > 0) u.total_tokens = eu.total_tokens;
@@ -266,7 +270,7 @@ export function createSSEStream(options: StreamOptions = {}) {
// T18: Track if we saw tool calls
if (delta?.tool_calls && delta.tool_calls.length > 0) {
(state as any).passthroughHasToolCalls = true;
passthroughHasToolCalls = true;
}
const content = delta?.content || delta?.reasoning_content;
@@ -288,7 +292,7 @@ export function createSSEStream(options: StreamOptions = {}) {
// T18: Normalize finish_reason to 'tool_calls' if tool calls were used
if (
isFinishChunk &&
(state as any).passthroughHasToolCalls &&
passthroughHasToolCalls &&
parsed.choices[0].finish_reason !== "tool_calls"
) {
parsed.choices[0].finish_reason = "tool_calls";
+1 -1
View File
@@ -140,7 +140,7 @@ export function createDisconnectAwareStream(transformStream, streamController) {
const errorMsg = error instanceof Error ? error.message : "Upstream stream error";
const statusCode =
typeof error === "object" && error !== null && "statusCode" in error
? (error as any).statusCode
? Number((error as { statusCode?: unknown }).statusCode) || 500
: 500;
const errorEvent = {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "3.0.0-rc.15",
"version": "3.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "3.0.0-rc.15",
"version": "3.0.1",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "3.0.0-rc.15",
"version": "3.0.1",
"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": {
@@ -47,7 +47,7 @@
"homepage": "https://omniroute.online",
"scripts": {
"dev": "node scripts/run-next.mjs dev",
"build": "next build",
"build": "node scripts/build-next-isolated.mjs",
"build:cli": "node scripts/prepublish.mjs",
"start": "node scripts/run-next.mjs start",
"lint": "eslint .",
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import { spawn } from "node:child_process";
/**
* This repository contains a legacy `app/` snapshot (packaging/runtime artifacts)
* alongside the active Next.js source in `src/app/`. Next.js route discovery scans
* both and fails the build on legacy files. We temporarily move the legacy folder
* out of the project root during `next build`, then restore it in all outcomes.
*/
const projectRoot = process.cwd();
const legacyAppDir = path.join(projectRoot, "app");
const backupDir = path.join(projectRoot, `.app-build-backup-${process.pid}-${Date.now()}`);
async function exists(targetPath) {
try {
await fs.access(targetPath);
return true;
} catch {
return false;
}
}
function runNextBuild() {
return new Promise((resolve) => {
const nextBin = path.join(projectRoot, "node_modules", "next", "dist", "bin", "next");
const child = spawn(process.execPath, [nextBin, "build"], {
cwd: projectRoot,
stdio: "inherit",
env: process.env,
});
const forward = (signal) => {
if (!child.killed) child.kill(signal);
};
process.on("SIGINT", forward);
process.on("SIGTERM", forward);
child.on("exit", (code, signal) => {
process.off("SIGINT", forward);
process.off("SIGTERM", forward);
if (signal) {
resolve({ code: 1, signal });
return;
}
resolve({ code: code ?? 1, signal: null });
});
});
}
async function main() {
let moved = false;
try {
if (await exists(legacyAppDir)) {
await fs.rename(legacyAppDir, backupDir);
moved = true;
}
const result = await runNextBuild();
process.exitCode = result.code;
} catch (error) {
console.error("[build-next-isolated] Build failed:", error);
process.exitCode = 1;
} finally {
if (moved) {
try {
await fs.rename(backupDir, legacyAppDir);
} catch (restoreError) {
console.error(
`[build-next-isolated] Failed to restore legacy app dir from ${backupDir}:`,
restoreError
);
process.exitCode = 1;
}
}
}
}
await main();
+1 -1
View File
@@ -240,7 +240,7 @@ if (existsSync(mitmSrc)) {
writeFileSync(tmpTsconfigPath, JSON.stringify(mitmTsconfig, null, 2));
try {
execSync(`npx tsc -p ${tmpTsconfigPath}`, { cwd: ROOT, stdio: "inherit" });
execSync("npx tsc -p " + JSON.stringify(tmpTsconfigPath), { cwd: ROOT, stdio: "inherit" });
console.log(" ✅ MITM utilities compiled to app/src/mitm/");
} catch (err) {
console.warn(" ⚠️ MITM compile warning (non-fatal):", err.message);
+6 -6
View File
@@ -9,15 +9,15 @@ import { bootstrapEnv } from "./bootstrap-env.mjs";
const mode = process.argv[2] === "start" ? "start" : "dev";
const runtimePorts = resolveRuntimePorts();
// Load .env / server.env first so PORT / DASHBOARD_PORT from files affect --port below.
const env = bootstrapEnv();
const runtimePorts = resolveRuntimePorts(env);
const { dashboardPort } = runtimePorts;
// Auto-generate secrets on first run, merge .env + process.env
const env = bootstrapEnv();
const args = ["./node_modules/next/dist/bin/next", mode, "--port", String(dashboardPort)];
// Default: use webpack (stable). Set OMNIROUTE_USE_TURBOPACK=1 to use Turbopack (faster dev).
if (mode === "dev" && process.env.OMNIROUTE_USE_TURBOPACK !== "1") {
// Default: use webpack (stable). Set OMNIROUTE_USE_TURBOPACK=1 in .env for Turbopack (faster dev).
// Must read merged `env` from bootstrap — .env is not applied to process.env in the launcher.
if (mode === "dev" && env.OMNIROUTE_USE_TURBOPACK !== "1") {
args.splice(2, 0, "--webpack");
}
+1 -3
View File
@@ -7,10 +7,8 @@ import {
} from "./runtime-env.mjs";
import { bootstrapEnv } from "./bootstrap-env.mjs";
const runtimePorts = resolveRuntimePorts();
// Auto-generate secrets on first run, merge .env + process.env
const env = bootstrapEnv();
const runtimePorts = resolveRuntimePorts(env);
spawnWithForwardedSignals("node", ["server.js"], {
stdio: "inherit",
+8 -4
View File
@@ -5,10 +5,14 @@ export function parsePort(value, fallback) {
return Number.isFinite(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
}
export function resolveRuntimePorts() {
const basePort = parsePort(process.env.PORT || "20128", 20128);
const apiPort = parsePort(process.env.API_PORT || String(basePort), basePort);
const dashboardPort = parsePort(process.env.DASHBOARD_PORT || String(basePort), basePort);
/**
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [fromEnv]
* Defaults to process.env. Pass bootstrap `merged` so project `.env` PORT applies before spawn.
*/
export function resolveRuntimePorts(fromEnv = process.env) {
const basePort = parsePort(fromEnv.PORT || "20128", 20128);
const apiPort = parsePort(fromEnv.API_PORT || String(basePort), basePort);
const dashboardPort = parsePort(fromEnv.DASHBOARD_PORT || String(basePort), basePort);
return { basePort, apiPort, dashboardPort };
}
@@ -8,6 +8,7 @@ import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Card, CardSkeleton, Button, Modal } from "@/shared/components";
import ProviderIcon from "@/shared/components/ProviderIcon";
import { AI_PROVIDERS, FREE_PROVIDERS, OAUTH_PROVIDERS } from "@/shared/constants/providers";
import { useNotificationStore } from "@/store/notificationStore";
import { copyToClipboard } from "@/shared/utils/clipboard";
@@ -356,7 +357,6 @@ HomePageClient.propTypes = {
};
function ProviderOverviewCard({ item, metrics, onClick }) {
const [imgError, setImgError] = useState(false);
const t = useTranslations("home");
const tc = useTranslations("common");
@@ -380,24 +380,7 @@ function ProviderOverviewCard({ item, metrics, onClick }) {
className="size-8 rounded-lg flex items-center justify-center shrink-0"
style={{ backgroundColor: `${item.provider.color || "#888"}15` }}
>
{imgError ? (
<span
className="text-[10px] font-bold"
style={{ color: item.provider.color || "#888" }}
>
{item.provider.textIcon || item.provider.id.slice(0, 2).toUpperCase()}
</span>
) : (
<Image
src={`/providers/${item.provider.id}.png`}
alt={item.provider.name}
width={26}
height={26}
className="object-contain rounded-lg"
sizes="26px"
onError={() => setImgError(true)}
/>
)}
<ProviderIcon providerId={item.provider.id} size={26} type="color" />
</div>
<div className="min-w-0 flex-1">
+85 -21
View File
@@ -1,9 +1,12 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import Link from "next/link";
import { Card, Button, Input } from "@/shared/components";
import ProviderIcon from "@/shared/components/ProviderIcon";
import { useTranslations } from "next-intl";
import { AI_PROVIDERS } from "@/shared/constants/config";
import { CLI_COMPAT_PROVIDER_IDS } from "@/shared/constants/cliCompatProviders";
interface AgentInfo {
id: string;
@@ -23,6 +26,35 @@ interface AgentSummary {
custom: number;
}
// Map agent binary IDs to provider icon IDs for ProviderIcon component
const AGENT_ICON_MAP: Record<string, string> = {
claude: "anthropic",
"claude-code": "anthropic",
codex: "openai",
"gemini-cli": "google",
gemini: "google",
opencode: "opencode",
openclaw: "openclaw",
cline: "cline",
kilocode: "kilocode",
kilo: "kilocode",
cursor: "cursor",
antigravity: "antigravity",
droid: "droid",
goose: "goose",
aider: "aider",
iflow: "iflow",
kiro: "kiro",
nanobot: "nanobot",
picoclaw: "picoclaw",
zeroclaw: "zeroclaw",
ironclaw: "ironclaw",
};
function getAgentIconId(agentId: string): string | null {
return AGENT_ICON_MAP[agentId] || null;
}
export default function AgentsPage() {
const [agents, setAgents] = useState<AgentInfo[]>([]);
const [summary, setSummary] = useState<AgentSummary | null>(null);
@@ -180,6 +212,51 @@ export default function AgentsPage() {
</div>
)}
{/* Setup Guide */}
<Card>
<div className="flex items-center justify-between gap-3 mb-4">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-blue-500/10 text-blue-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
support
</span>
</div>
<h3 className="text-lg font-semibold">{t("setupGuideTitle")}</h3>
</div>
<Link
href="/dashboard/cli-tools"
className="text-xs px-2.5 py-1.5 rounded-lg border border-border/60 hover:bg-surface/40 transition-colors"
>
{t("openCliTools")}
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-3">
<div className="flex items-center gap-2 mb-1.5">
<span className="material-symbols-outlined text-[16px] text-blue-500">radar</span>
<p className="text-sm font-medium">{t("setupGuideDetectCliTitle")}</p>
</div>
<p className="text-xs text-text-muted">{t("setupGuideDetectCliDesc")}</p>
</div>
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-3">
<div className="flex items-center gap-2 mb-1.5">
<span className="material-symbols-outlined text-[16px] text-amber-500">build</span>
<p className="text-sm font-medium">{t("setupGuideCustomAgentTitle")}</p>
</div>
<p className="text-xs text-text-muted">{t("setupGuideCustomAgentDesc")}</p>
</div>
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-3">
<div className="flex items-center gap-2 mb-1.5">
<span className="material-symbols-outlined text-[16px] text-emerald-500">
terminal
</span>
<p className="text-sm font-medium">{t("setupGuideCommandMissingTitle")}</p>
</div>
<p className="text-xs text-text-muted">{t("setupGuideCommandMissingDesc")}</p>
</div>
</div>
</Card>
{/* CLI Fingerprint Matching */}
<Card>
<div className="flex items-center gap-3 mb-4">
@@ -193,24 +270,7 @@ export default function AgentsPage() {
<div className="flex flex-col gap-4">
<p className="text-sm text-text-muted">{ts("cliFingerprintDesc")}</p>
<div className="flex flex-wrap gap-2">
{(
[
"codex",
"claude",
"github",
"antigravity",
"kiro",
"cursor",
"kimi-coding",
"kilocode",
"cline",
"qwen",
"droid",
"openclaw",
"copilot",
"opencode",
] as const
).map((providerId) => {
{CLI_COMPAT_PROVIDER_IDS.map((providerId) => {
const providerMeta = Object.values(AI_PROVIDERS).find(
(p: any) => p.id === providerId
) as any;
@@ -274,9 +334,13 @@ export default function AgentsPage() {
: "bg-zinc-500/10 text-zinc-400"
}`}
>
<span className="material-symbols-outlined text-[20px]">
{agent.installed ? "smart_toy" : "block"}
</span>
{getAgentIconId(agent.id) ? (
<ProviderIcon providerId={getAgentIconId(agent.id)!} size={20} type="color" />
) : (
<span className="material-symbols-outlined text-[20px]">
{agent.installed ? "smart_toy" : "block"}
</span>
)}
</div>
<div>
<div className="font-semibold text-sm flex items-center gap-1.5">
@@ -33,6 +33,16 @@ export default function CLIToolsPageClient({ machineId }) {
const [apiKeys, setApiKeys] = useState([]);
const [toolStatuses, setToolStatuses] = useState({});
const [statusesLoaded, setStatusesLoaded] = useState(false);
const translateOrFallback = useCallback(
(key, fallback, values = undefined) => {
try {
return t(key, values);
} catch {
return fallback;
}
},
[t]
);
useEffect(() => {
fetchConnections();
@@ -291,8 +301,42 @@ export default function CLIToolsPageClient({ machineId }) {
}
};
const getToolDocsHref = (toolId, tool) => {
if (typeof tool.docsUrl === "string" && tool.docsUrl.trim()) {
return tool.docsUrl.trim();
}
return `/docs?section=cli-tools&tool=${toolId}`;
};
const getToolUseCase = (toolId, tool) => {
const fallbackDescription = translateOrFallback(`toolDescriptions.${toolId}`, tool.description);
return translateOrFallback(`toolUseCases.${toolId}`, fallbackDescription);
};
return (
<div className="flex flex-col gap-6">
<Card>
<div className="flex items-start gap-3">
<div className="p-2 rounded-lg bg-primary/10 text-primary">
<span className="material-symbols-outlined text-[20px]">tips_and_updates</span>
</div>
<div className="flex-1 min-w-0">
<h2 className="text-sm font-semibold">{t("howItWorks")}</h2>
<div className="mt-2 grid grid-cols-1 md:grid-cols-3 gap-2 text-xs text-text-muted">
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-2.5">
{t("installationGuide")}
</div>
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-2.5">
{t("configureEndpoint")}
</div>
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-2.5">
{t("testConnection")}
</div>
</div>
</div>
</div>
</Card>
{!hasActiveProviders && (
<Card className="border-yellow-500/50 bg-yellow-500/5">
<div className="flex items-center gap-3">
@@ -308,7 +352,38 @@ export default function CLIToolsPageClient({ machineId }) {
)}
<div className="flex flex-col gap-4">
{Object.entries(CLI_TOOLS).map(([toolId, tool]) => renderToolCard(toolId, tool))}
{Object.entries(CLI_TOOLS).map(([toolId, tool]) => {
const docsHref = getToolDocsHref(toolId, tool);
const isExternalDocs = /^https?:\/\//i.test(docsHref);
return (
<div key={toolId} className="flex flex-col gap-2.5">
{renderToolCard(toolId, tool)}
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-3">
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-2.5">
<div className="min-w-0">
<p className="text-[11px] font-semibold uppercase tracking-wide text-text-muted">
{t("whenToUseLabel")}
</p>
<p className="text-xs text-text-muted mt-1 break-words">
{getToolUseCase(toolId, tool)}
</p>
</div>
<a
href={docsHref}
target={isExternalDocs ? "_blank" : undefined}
rel={isExternalDocs ? "noopener noreferrer" : undefined}
className="inline-flex items-center gap-1.5 text-xs text-primary hover:text-primary/80 transition-colors whitespace-nowrap"
>
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
menu_book
</span>
{t("openToolDocs")}
</a>
</div>
</div>
</div>
);
})}
</div>
</div>
);
+7 -16
View File
@@ -16,27 +16,18 @@ import Tooltip from "@/shared/components/Tooltip";
import ModelRoutingSection from "@/shared/components/ModelRoutingSection";
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
import { useNotificationStore } from "@/store/notificationStore";
import { ROUTING_STRATEGIES } from "@/shared/constants/routingStrategies";
import { useTranslations } from "next-intl";
// Validate combo name: letters, numbers, -, _, /, .
const VALID_NAME_REGEX = /^[a-zA-Z0-9_/.-]+$/;
const STRATEGY_OPTIONS = [
{ value: "priority", labelKey: "priority", descKey: "priorityDesc", icon: "sort" },
{ value: "weighted", labelKey: "weighted", descKey: "weightedDesc", icon: "percent" },
{ value: "round-robin", labelKey: "roundRobin", descKey: "roundRobinDesc", icon: "autorenew" },
{ value: "random", labelKey: "random", descKey: "randomDesc", icon: "shuffle" },
{ value: "least-used", labelKey: "leastUsed", descKey: "leastUsedDesc", icon: "low_priority" },
{ value: "cost-optimized", labelKey: "costOpt", descKey: "costOptimizedDesc", icon: "savings" },
{
value: "fill-first",
labelKey: "fillFirst",
descKey: "fillFirstDesc",
icon: "stacked_bar_chart",
},
{ value: "p2c", labelKey: "p2c", descKey: "p2cDesc", icon: "compare_arrows" },
{ value: "strict-random", labelKey: "strictRandom", descKey: "strictRandomDesc", icon: "casino" },
];
const STRATEGY_OPTIONS = ROUTING_STRATEGIES.map((strategy) => ({
value: strategy.value,
labelKey: strategy.labelKey,
descKey: strategy.combosDescKey,
icon: strategy.icon,
}));
const STRATEGY_GUIDANCE_FALLBACK = {
priority: {
+31 -5
View File
@@ -134,7 +134,7 @@ export default function HealthPage() {
);
}
const { system, providerHealth, rateLimitStatus, lockouts } = data;
const { system, providerHealth, providerSummary, rateLimitStatus, lockouts } = data;
const cbEntries = Object.entries(providerHealth || {});
const lockoutEntries = Object.entries(lockouts || {});
@@ -235,11 +235,37 @@ export default function HealthPage() {
</div>
<span className="text-sm text-text-muted">{t("providers")}</span>
</div>
<p className="text-xl font-semibold text-text-main">{cbEntries.length}</p>
<p className="text-xs text-text-muted mt-1">
{t("healthyCount", {
count: cbEntries.filter(([, v]: [string, any]) => v.state === "CLOSED").length,
<p className="text-xl font-semibold text-text-main">
{providerSummary?.configuredCount ?? cbEntries.length}
</p>
<p
className="text-[11px] text-text-muted mt-1 inline-flex items-center gap-1"
title={t("configuredProvidersHint")}
>
{t("configuredProvidersLabel")}
<span className="material-symbols-outlined text-[12px]" aria-hidden="true">
help
</span>
</p>
<p
className="text-xs text-text-muted inline-flex items-center gap-1"
title={t("activeProvidersHint")}
>
{t("activeProviders", { count: providerSummary?.activeCount ?? 0 })}
<span className="material-symbols-outlined text-[12px]" aria-hidden="true">
info
</span>
</p>
<p
className="text-xs text-text-muted inline-flex items-center gap-1"
title={t("monitoredProvidersHint")}
>
{t("monitoredProviders", {
count: providerSummary?.monitoredCount ?? cbEntries.length,
})}
<span className="material-symbols-outlined text-[12px]" aria-hidden="true">
info
</span>
</p>
</Card>
</div>
@@ -358,6 +358,14 @@ function parseApiError(raw: any, statusCode: number): { message: string; isCrede
return { message: String(msg), isCredentials };
}
/** Format file size to human-readable string */
function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
/** Render image result thumbnails */
function ImageResults({ data }: { data: any }) {
const images: Array<{ url?: string; b64_json?: string; revised_prompt?: string }> =
@@ -427,7 +435,9 @@ export default function MediaPageClient() {
const [speechFormat, setSpeechFormat] = useState("mp3");
// Transcription-specific
const MAX_TRANSCRIPTION_FILE_SIZE = 4 * 1024 * 1024 * 1024; // 4 GB
const [audioFile, setAudioFile] = useState<File | null>(null);
const [fileSizeError, setFileSizeError] = useState<string | null>(null);
// Fix #390: Track which local providers (sdwebui, comfyui) are actually configured
// so we can hide them when they haven't been set up in the providers page
@@ -743,18 +753,35 @@ export default function MediaPageClient() {
{/* Transcription: file upload */}
{activeTab === "transcription" ? (
<div>
<label className="block text-sm font-medium text-text-main mb-2">Audio File</label>
<label className="block text-sm font-medium text-text-main mb-2">Audio / Video File</label>
<input
type="file"
accept="audio/*,video/*"
onChange={(e) => setAudioFile(e.target.files?.[0] ?? null)}
onChange={(e) => {
const file = e.target.files?.[0] ?? null;
setFileSizeError(null);
if (file && file.size > MAX_TRANSCRIPTION_FILE_SIZE) {
setFileSizeError(`File too large (${formatFileSize(file.size)}). Maximum allowed: 4 GB.`);
setAudioFile(null);
e.target.value = "";
return;
}
setAudioFile(file);
}}
className="w-full px-3 py-2 rounded-lg bg-surface border border-black/10 dark:border-white/10 text-text-main text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 file:mr-3 file:py-1 file:px-3 file:rounded file:border-0 file:bg-primary/10 file:text-primary file:text-sm"
/>
{audioFile && (
<p className="text-xs text-text-muted mt-1">
{audioFile.name} ({(audioFile.size / 1024).toFixed(0)} KB)
{fileSizeError && (
<p className="text-xs text-red-400 mt-1 flex items-center gap-1">
<span className="material-symbols-outlined text-[12px]">error</span>
{fileSizeError}
</p>
)}
{audioFile && !fileSizeError && (
<p className="text-xs text-text-muted mt-1">
{audioFile.name} ({formatFileSize(audioFile.size)})
</p>
)}
<p className="text-[10px] text-text-muted/60 mt-1">Supports audio and video files up to 4 GB</p>
</div>
) : (
/* Prompt / Text */
File diff suppressed because it is too large Load Diff
@@ -119,9 +119,9 @@ export default function SearchToolsClient() {
} catch (err: any) {
setDuration(Date.now() - start);
if (err.name === "AbortError") {
setError("Request timed out (15s)");
setError(t("requestTimedOut", { seconds: 15 }));
} else {
setError(err.message || "Network error");
setError(err?.message || t("networkError"));
}
} finally {
setLoading(false);
@@ -33,6 +33,7 @@ interface SearchFormProps {
export default function SearchForm({ onSearch, loading, onCancel, providers }: SearchFormProps) {
const t = useTranslations("search");
const tc = useTranslations("common");
const [query, setQuery] = useState("");
const [provider, setProvider] = useState("auto");
const [searchType, setSearchType] = useState("web");
@@ -92,7 +93,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
<textarea
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Enter search query..."
placeholder={t("queryPlaceholder")}
className="w-full bg-surface border border-border rounded-lg p-2.5 text-sm text-text-main resize-none h-16 focus:outline-none focus:ring-2 focus:ring-primary/30"
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
@@ -114,7 +115,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
value={provider}
onChange={(e: any) => setProvider(e.target.value)}
options={[
{ value: "auto", label: "auto (cheapest)" },
{ value: "auto", label: t("providerAuto") },
...activeProviders.map((p) => ({
value: p.id,
label: p.name,
@@ -131,8 +132,8 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
value={searchType}
onChange={(e: any) => setSearchType(e.target.value)}
options={[
{ value: "web", label: "web" },
{ value: "news", label: "news" },
{ value: "web", label: t("searchTypeWeb") },
{ value: "news", label: t("searchTypeNews") },
]}
className="w-full"
/>
@@ -172,7 +173,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
<input
value={country}
onChange={(e) => setCountry(e.target.value)}
placeholder="any"
placeholder={t("optionAny")}
className="w-full bg-surface border border-border rounded-md px-2 py-1.5 text-xs text-text-main focus:outline-none"
/>
</div>
@@ -181,7 +182,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
<input
value={language}
onChange={(e) => setLanguage(e.target.value)}
placeholder="any"
placeholder={t("optionAny")}
className="w-full bg-surface border border-border rounded-md px-2 py-1.5 text-xs text-text-main focus:outline-none"
/>
</div>
@@ -192,11 +193,11 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
value={timeRange}
onChange={(e: any) => setTimeRange(e.target.value)}
options={[
{ value: "", label: "any" },
{ value: "day", label: "Past day" },
{ value: "week", label: "Past week" },
{ value: "month", label: "Past month" },
{ value: "year", label: "Past year" },
{ value: "", label: t("optionAny") },
{ value: "day", label: t("timeRangeDay") },
{ value: "week", label: t("timeRangeWeek") },
{ value: "month", label: t("timeRangeMonth") },
{ value: "year", label: t("timeRangeYear") },
]}
className="w-full"
/>
@@ -209,7 +210,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
<input
value={domainInput}
onChange={(e) => setDomainInput(e.target.value)}
placeholder="example.com"
placeholder={t("domainPlaceholder")}
className="flex-1 bg-surface border border-border rounded-md px-2 py-1.5 text-xs text-text-main focus:outline-none"
onKeyDown={(e) => e.key === "Enter" && addDomain("include")}
/>
@@ -244,7 +245,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
<input
value={excludeDomainInput}
onChange={(e) => setExcludeDomainInput(e.target.value)}
placeholder="example.com"
placeholder={t("domainPlaceholder")}
className="flex-1 bg-surface border border-border rounded-md px-2 py-1.5 text-xs text-text-main focus:outline-none"
onKeyDown={(e) => e.key === "Enter" && addDomain("exclude")}
/>
@@ -274,9 +275,9 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
value={safeSearch}
onChange={(e: any) => setSafeSearch(e.target.value)}
options={[
{ value: "off", label: "Off" },
{ value: "moderate", label: "Moderate" },
{ value: "strict", label: "Strict" },
{ value: "off", label: t("safeSearchOff") },
{ value: "moderate", label: t("safeSearchModerate") },
{ value: "strict", label: t("safeSearchStrict") },
]}
className="w-full"
/>
@@ -289,7 +290,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
<div className="p-4 border-b border-border">
{loading ? (
<Button variant="danger" onClick={onCancel} className="w-full">
Cancel
{tc("cancel")}
</Button>
) : (
<Button
@@ -298,7 +299,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
disabled={noProviders || !query.trim()}
className="w-full"
>
Search
{tc("search")}
</Button>
)}
{noProviders && <p className="text-xs text-text-muted mt-2">{t("noSearchProviders")}</p>}
@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
import { useLocale, useTranslations } from "next-intl";
interface HistoryEntry {
query: string;
@@ -14,9 +14,9 @@ interface SearchHistoryProps {
onReplay: (entry: HistoryEntry) => void;
}
function timeAgo(timestamp: string): string {
function timeAgo(timestamp: string, locale: string): string {
try {
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
const diff = Date.now() - new Date(timestamp).getTime();
const minutes = Math.floor(diff / 60_000);
if (minutes < 1) return rtf.format(0, "minute");
@@ -25,12 +25,13 @@ function timeAgo(timestamp: string): string {
if (hours < 24) return rtf.format(-hours, "hour");
return rtf.format(-Math.floor(hours / 24), "day");
} catch {
return new Date(timestamp).toLocaleString();
return new Date(timestamp).toLocaleString(locale);
}
}
export default function SearchHistory({ onReplay }: SearchHistoryProps) {
const t = useTranslations("search");
const locale = useLocale();
const [entries, setEntries] = useState<HistoryEntry[]>([]);
useEffect(() => {
@@ -57,7 +58,7 @@ export default function SearchHistory({ onReplay }: SearchHistoryProps) {
<div className="text-xs text-text-main truncate">{entry.query}</div>
<div className="flex justify-between mt-0.5">
<span className="text-[10px] text-text-muted">{entry.provider}</span>
<span className="text-[10px] text-text-muted">{timeAgo(entry.timestamp)}</span>
<span className="text-[10px] text-text-muted">{timeAgo(entry.timestamp, locale)}</span>
</div>
</button>
))}
@@ -3,6 +3,7 @@
import { useState, useEffect } from "react";
import { Card, Button, Input, Toggle } from "@/shared/components";
import { cn } from "@/shared/utils/cn";
import { ROUTING_STRATEGIES } from "@/shared/constants/routingStrategies";
import { useTranslations } from "next-intl";
export default function ComboDefaultsTab() {
@@ -21,14 +22,11 @@ export default function ComboDefaultsTab() {
const [saving, setSaving] = useState(false);
const t = useTranslations("settings");
const tc = useTranslations("common");
const strategyOptions = [
{ value: "priority", label: t("priority"), icon: "sort" },
{ value: "weighted", label: t("weighted"), icon: "percent" },
{ value: "round-robin", label: t("roundRobin"), icon: "autorenew" },
{ value: "random", label: t("random"), icon: "shuffle" },
{ value: "least-used", label: t("leastUsed"), icon: "low_priority" },
{ value: "cost-optimized", label: t("costOpt"), icon: "savings" },
];
const strategyOptions = ROUTING_STRATEGIES.map((strategy) => ({
value: strategy.value,
label: t(strategy.labelKey),
icon: strategy.icon,
}));
const numericSettings = [
{ key: "maxRetries", label: t("maxRetriesLabel"), min: 0, max: 5 },
{ key: "retryDelayMs", label: t("retryDelayLabel"), min: 500, max: 10000, step: 500 },
@@ -87,6 +85,13 @@ export default function ComboDefaultsTab() {
<h3 className="text-lg font-semibold">{t("comboDefaultsTitle")}</h3>
<span className="text-xs text-text-muted ml-auto">{t("globalComboConfig")}</span>
</div>
<div className="mb-4 rounded-lg border border-amber-500/20 bg-amber-500/5 p-3">
<p className="text-xs font-medium text-amber-700 dark:text-amber-300">
{t("comboDefaultsGuideTitle")}
</p>
<p className="text-xs text-text-muted mt-1">{t("comboDefaultsGuideHint1")}</p>
<p className="text-xs text-text-muted">{t("comboDefaultsGuideHint2")}</p>
</div>
<div className="flex flex-col gap-4">
{/* Default Strategy */}
<div className="flex items-center justify-between">
@@ -97,7 +102,7 @@ export default function ComboDefaultsTab() {
<div
role="tablist"
aria-label={t("comboStrategyAria")}
className="grid grid-cols-3 gap-1 p-0.5 rounded-md bg-black/5 dark:bg-white/5"
className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-1 p-0.5 rounded-md bg-black/5 dark:bg-white/5"
>
{strategyOptions.map((s) => (
<button
@@ -27,6 +27,14 @@ type HealthInfo = {
lastSeenAt: string | null;
};
type TestResult = {
success: boolean;
publicIp?: string;
latencyMs?: number;
country?: string;
error?: string;
};
const EMPTY_FORM = {
id: "",
name: "",
@@ -51,6 +59,8 @@ export default function ProxyRegistryManager() {
const [usageById, setUsageById] = useState<Record<string, UsageInfo>>({});
const [healthById, setHealthById] = useState<Record<string, HealthInfo>>({});
const [testById, setTestById] = useState<Record<string, TestResult | null>>({});
const [testingId, setTestingId] = useState<string | null>(null);
const [migrating, setMigrating] = useState(false);
const [bulkOpen, setBulkOpen] = useState(false);
const [bulkSaving, setBulkSaving] = useState(false);
@@ -75,6 +85,36 @@ export default function ProxyRegistryManager() {
}
}, []);
const loadAllUsage = useCallback(async (proxyIds: string[]) => {
if (!proxyIds.length) return;
try {
const results = await Promise.all(
proxyIds.map((id) =>
fetch(`/api/settings/proxies/assignments?proxyId=${encodeURIComponent(id)}`)
.then((r) => (r.ok ? r.json() : null))
.then((data) => {
const rawAssignments: Array<{ scope: string; scopeId: string | null }> =
Array.isArray(data?.items) ? data.items : [];
// Deduplicate by scope+scopeId — prevents double-counting when both
// a provider-scope and account-scope row exist for the same proxy
const seen = new Set<string>();
const assignments = rawAssignments.filter((a) => {
const key = `${a.scope}:${a.scopeId ?? ""}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
return [id, { count: assignments.length, assignments }] as [string, UsageInfo];
})
.catch(() => [id, { count: 0, assignments: [] }] as [string, UsageInfo])
)
);
setUsageById(Object.fromEntries(results));
} catch {
// ignore
}
}, []);
const load = useCallback(async () => {
setLoading(true);
setError(null);
@@ -86,15 +126,18 @@ export default function ProxyRegistryManager() {
setItems([]);
return;
}
setItems(Array.isArray(data?.items) ? data.items : []);
const loaded: ProxyItem[] = Array.isArray(data?.items) ? data.items : [];
setItems(loaded);
const ids = loaded.map((p) => p.id).filter(Boolean);
void loadHealth();
void loadAllUsage(ids);
} catch (e: any) {
setError(e?.message || "Failed to load proxy registry");
setItems([]);
} finally {
setLoading(false);
}
}, [loadHealth]);
}, [loadHealth, loadAllUsage]);
useEffect(() => {
void load();
@@ -130,22 +173,63 @@ export default function ProxyRegistryManager() {
const loadUsage = async (proxyId: string) => {
try {
const res = await fetch(
`/api/settings/proxies?id=${encodeURIComponent(proxyId)}&whereUsed=1`
`/api/settings/proxies/assignments?proxyId=${encodeURIComponent(proxyId)}`
);
const data = await res.json().catch(() => ({}));
if (!res.ok) return;
const rawAssignments: Array<{ scope: string; scopeId: string | null }> = Array.isArray(
data?.items
)
? data.items
: [];
const seen = new Set<string>();
const assignments = rawAssignments.filter((a) => {
const key = `${a.scope}:${a.scopeId ?? ""}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
setUsageById((prev) => ({
...prev,
[proxyId]: {
count: Number(data?.count || 0),
assignments: Array.isArray(data?.assignments) ? data.assignments : [],
},
[proxyId]: { count: assignments.length, assignments },
}));
} catch {
// ignore usage loading errors in UI
}
};
const handleTestProxy = async (item: ProxyItem) => {
if (testingId) return;
setTestingId(item.id);
setTestById((prev) => ({ ...prev, [item.id]: null }));
try {
const res = await fetch("/api/settings/proxy/test", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
proxy: {
type: item.type || "http",
host: item.host,
port: String(item.port || 8080),
},
}),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
setTestById((prev) => ({
...prev,
[item.id]: { success: false, error: data?.error?.message || "Test failed" },
}));
return;
}
setTestById((prev) => ({ ...prev, [item.id]: { success: true, ...data } }));
} catch (e: any) {
setTestById((prev) => ({ ...prev, [item.id]: { success: false, error: e?.message } }));
} finally {
setTestingId(null);
}
};
const handleSave = async () => {
if (!form.name.trim() || !form.host.trim()) {
setError("Name and host are required");
@@ -378,27 +462,47 @@ export default function ProxyRegistryManager() {
</span>
</td>
<td className="py-2 pr-3 text-xs text-text-muted">
{health ? (
<div className="flex flex-col gap-0.5">
<span>{health.successRate ?? 0}% success</span>
<span>{health.avgLatencyMs ?? "-"} ms avg</span>
</div>
) : (
"-"
)}
<div className="flex flex-col gap-0.5">
{health ? (
<>
<span>{health.successRate ?? 0}% success</span>
<span>{health.avgLatencyMs ?? "-"} ms avg</span>
</>
) : testById[item.id] ? (
testById[item.id]!.success ? (
<>
<span className="text-emerald-400">
{testById[item.id]!.publicIp}
</span>
{testById[item.id]!.latencyMs && (
<span>{testById[item.id]!.latencyMs}ms</span>
)}
</>
) : (
<span className="text-red-400">
{testById[item.id]!.error || "failed"}
</span>
)
) : (
<span></span>
)}
</div>
</td>
<td className="py-2 pr-3 text-xs text-text-muted">
{usage ? `${usage.count} assignment(s)` : "-"}
{usageById[item.id] != null
? `${usageById[item.id].count} assignment(s)`
: "—"}
</td>
<td className="py-2">
<div className="flex items-center gap-1">
<Button
size="sm"
variant="ghost"
icon="visibility"
onClick={() => void loadUsage(item.id)}
icon="speed"
onClick={() => void handleTestProxy(item)}
loading={testingId === item.id}
>
Usage
Test
</Button>
<Button
size="sm"
@@ -3,31 +3,20 @@
import { useState, useEffect } from "react";
import { Card, Input, Button } from "@/shared/components";
import FallbackChainsEditor from "./FallbackChainsEditor";
import {
ROUTING_STRATEGIES,
SETTINGS_FALLBACK_STRATEGY_VALUES,
} from "@/shared/constants/routingStrategies";
import { useTranslations } from "next-intl";
const STRATEGIES = [
{
value: "fill-first",
labelKey: "fillFirst",
descKey: "fillFirstDesc",
icon: "vertical_align_top",
},
{ value: "round-robin", labelKey: "roundRobin", descKey: "roundRobinDesc", icon: "loop" },
{ value: "p2c", labelKey: "p2c", descKey: "p2cDesc", icon: "balance" },
{ value: "random", labelKey: "random", descKey: "randomDesc", icon: "shuffle" },
{
value: "least-used",
labelKey: "leastUsed",
descKey: "leastUsedDesc",
icon: "low_priority",
},
{
value: "cost-optimized",
labelKey: "costOpt",
descKey: "costOptDesc",
icon: "savings",
},
];
const STRATEGIES = ROUTING_STRATEGIES.filter((strategy) =>
SETTINGS_FALLBACK_STRATEGY_VALUES.includes(strategy.value)
).map((strategy) => ({
value: strategy.value,
labelKey: strategy.labelKey,
descKey: strategy.settingsDescKey,
icon: strategy.icon,
}));
export default function RoutingTab() {
const [settings, setSettings] = useState<any>({ fallbackStrategy: "fill-first" });
@@ -36,14 +25,10 @@ export default function RoutingTab() {
const [newPattern, setNewPattern] = useState("");
const [newTarget, setNewTarget] = useState("");
const t = useTranslations("settings");
const strategyHintKeyByValue: Record<string, string> = {
"fill-first": "fillFirstDesc",
"round-robin": "roundRobinDesc",
p2c: "p2cDesc",
random: "randomDesc",
"least-used": "leastUsedDesc",
"cost-optimized": "costOptDesc",
};
const strategyHintKeyByValue = STRATEGIES.reduce<Record<string, string>>((acc, strategy) => {
acc[strategy.value] = strategy.descKey;
return acc;
}, {});
useEffect(() => {
fetch("/api/settings")
@@ -99,6 +84,14 @@ export default function RoutingTab() {
<h3 className="text-lg font-semibold">{t("routingStrategy")}</h3>
</div>
<div className="mb-4 rounded-lg border border-blue-500/20 bg-blue-500/5 p-3">
<p className="text-xs font-medium text-blue-700 dark:text-blue-300">
{t("routingAdvancedGuideTitle")}
</p>
<p className="text-xs text-text-muted mt-1">{t("routingAdvancedGuideHint1")}</p>
<p className="text-xs text-text-muted">{t("routingAdvancedGuideHint2")}</p>
</div>
<div
className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-2 mb-4"
style={{ gridAutoRows: "1fr" }}
@@ -37,7 +37,7 @@ export default function SettingsPage() {
const activeTab = userSelectedTab || tabs.find((t) => t.id === tabParam)?.id || "general";
return (
<div className="max-w-2xl mx-auto min-w-0">
<div className="max-w-6xl mx-auto min-w-0">
<div className="flex flex-col gap-6">
{/* Tab navigation */}
<div className="w-full overflow-x-auto pb-1">
@@ -412,24 +412,25 @@ export default function ProviderLimits() {
<div className="flex items-center gap-2">
{/* Group by toggle */}
<div className="flex rounded-lg border border-white/[0.08] overflow-hidden">
<div className="flex rounded-lg border border-border overflow-hidden">
<button
onClick={() => handleSetGroupBy("none")}
className="px-2.5 py-1.5 text-[12px] font-medium cursor-pointer border-none"
style={{
background: groupBy === "none" ? "rgba(255,255,255,0.1)" : "transparent",
color: groupBy === "none" ? "var(--text-main)" : "var(--text-muted)",
background: groupBy === "none" ? "var(--color-bg-subtle)" : "transparent",
color: groupBy === "none" ? "var(--color-text-main)" : "var(--color-text-muted)",
}}
>
{t("viewFlat")}
</button>
<button
onClick={() => handleSetGroupBy("environment")}
className="px-2.5 py-1.5 text-[12px] font-medium cursor-pointer border-none border-l border-white/[0.08]"
className="px-2.5 py-1.5 text-[12px] font-medium cursor-pointer border-none"
style={{
background: groupBy === "environment" ? "rgba(255,255,255,0.1)" : "transparent",
color: groupBy === "environment" ? "var(--text-main)" : "var(--text-muted)",
borderLeft: "1px solid rgba(255,255,255,0.08)",
background: groupBy === "environment" ? "var(--color-bg-subtle)" : "transparent",
color:
groupBy === "environment" ? "var(--color-text-main)" : "var(--color-text-muted)",
borderLeft: "1px solid var(--color-border)",
}}
>
{t("viewByEnvironment")}
@@ -442,7 +443,7 @@ export default function ProviderLimits() {
setAutoRefresh(next);
localStorage.setItem(LS_AUTO_REFRESH, String(next));
}}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-white/[0.08] bg-transparent cursor-pointer text-text-main text-[13px]"
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border bg-transparent cursor-pointer text-text-main text-[13px]"
>
<span
className="material-symbols-outlined text-[18px]"
@@ -459,7 +460,7 @@ export default function ProviderLimits() {
<button
onClick={refreshAll}
disabled={refreshingAll}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg bg-white/[0.06] border border-white/10 text-text-main text-[13px] disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg bg-bg-subtle border border-border text-text-main text-[13px] disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
>
<span
className={`material-symbols-outlined text-[16px] ${refreshingAll ? "animate-spin" : ""}`}
@@ -483,10 +484,10 @@ export default function ProviderLimits() {
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold cursor-pointer"
style={{
border: active
? "1px solid var(--primary, #E54D5E)"
: "1px solid rgba(255,255,255,0.12)",
background: active ? "rgba(249,120,21,0.14)" : "transparent",
color: active ? "var(--primary, #E54D5E)" : "var(--text-muted)",
? "1px solid var(--color-primary, #E54D5E)"
: "1px solid var(--color-border)",
background: active ? "rgba(229,77,94,0.1)" : "transparent",
color: active ? "var(--color-primary, #E54D5E)" : "var(--color-text-muted)",
}}
>
<span>{t(tier.labelKey)}</span>
@@ -497,10 +498,10 @@ export default function ProviderLimits() {
</div>
{/* Account rows */}
<div className="rounded-xl border border-white/[0.06] overflow-hidden bg-black/15">
<div className="rounded-xl border border-border overflow-hidden bg-bg-subtle">
{/* Table header */}
<div
className="items-center px-4 py-2.5 border-b border-white/[0.06] text-[11px] font-semibold uppercase tracking-wider text-text-muted"
className="items-center px-4 py-2.5 border-b border-border text-[11px] font-semibold uppercase tracking-wider text-text-muted"
style={{ display: "grid", gridTemplateColumns: "280px 1fr 100px 48px" }}
>
<div>{t("account")}</div>
@@ -523,11 +524,11 @@ export default function ProviderLimits() {
return (
<div
key={conn.id}
className="items-center px-4 py-3.5 transition-[background] duration-150 hover:bg-white/[0.02]"
className="items-center px-4 py-3.5 transition-[background] duration-150 hover:bg-black/[0.03] dark:hover:bg-white/[0.02]"
style={{
display: "grid",
gridTemplateColumns: "280px 1fr 100px 48px",
borderBottom: !isLast ? "1px solid rgba(255,255,255,0.04)" : "none",
borderBottom: !isLast ? "1px solid var(--color-border)" : "none",
}}
>
{/* Account Info */}
@@ -614,7 +615,7 @@ export default function ProviderLimits() {
) : null}
{/* Progress bar */}
<div className="flex-1 h-1.5 rounded-sm bg-white/[0.06] min-w-[60px] overflow-hidden">
<div className="flex-1 h-1.5 rounded-sm bg-black/[0.06] dark:bg-white/[0.06] min-w-[60px] overflow-hidden">
<div
className="h-full rounded-sm transition-[width] duration-300 ease-out"
style={{
@@ -672,13 +673,10 @@ export default function ProviderLimits() {
if (groupedConnections) {
const entries = [...groupedConnections.entries()];
return entries.map(([groupName, conns]) => (
<div
key={groupName}
className="border border-white/[0.08] rounded-lg overflow-hidden mb-2"
>
<div key={groupName} className="border border-border rounded-lg overflow-hidden mb-2">
<button
onClick={() => toggleGroup(groupName)}
className="w-full flex items-center gap-2 px-4 py-2.5 bg-white/[0.03] hover:bg-white/[0.05] transition-colors text-left border-none cursor-pointer"
className="w-full flex items-center gap-2 px-4 py-2.5 bg-bg-subtle hover:bg-black/[0.04] dark:hover:bg-white/[0.05] transition-colors text-left border-none cursor-pointer"
>
<span className="material-symbols-outlined text-[16px] text-text-muted">
{expandedGroups.has(groupName) ? "expand_less" : "expand_more"}
@@ -689,7 +687,7 @@ export default function ProviderLimits() {
<span className="text-[12px] font-semibold text-text-main uppercase tracking-wider flex-1">
{groupName}
</span>
<span className="text-[11px] text-text-muted bg-white/[0.06] px-2 py-0.5 rounded-full">
<span className="text-[11px] text-text-muted bg-black/[0.04] dark:bg-white/[0.06] px-2 py-0.5 rounded-full">
{conns.length}
</span>
</button>
@@ -222,6 +222,11 @@ export function normalizePlanTier(plan) {
const upper = raw.toUpperCase();
// Provider names that are not real plan tiers — treat as unknown
if (upper === "CLAUDE CODE" || upper === "KIMI CODING" || upper === "KIRO") {
return { key: "unknown", label: raw, variant: "default", rank: 0, raw };
}
if (upper.includes("PRO+") || upper.includes("PRO PLUS") || upper.includes("PROPLUS")) {
return { key: "plus", label: "Pro+", variant: "secondary", rank: 4, raw };
}
+16 -8
View File
@@ -4,12 +4,22 @@
* DELETE Delete a mapping
*/
import { z } from "zod";
import { NextResponse } from "next/server";
import {
updateModelComboMapping,
deleteModelComboMapping,
getModelComboMappingById,
} from "@/lib/localDb";
import { validateBody, isValidationFailure } from "@/shared/validation/helpers";
const updateMappingSchema = z.object({
pattern: z.string().min(1).max(500).optional(),
comboId: z.string().min(1).optional(),
priority: z.number().int().optional(),
enabled: z.boolean().optional(),
description: z.string().max(1000).optional(),
});
export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) {
try {
@@ -27,15 +37,13 @@ export async function GET(_request: Request, { params }: { params: Promise<{ id:
export async function PUT(request: Request, { params }: { params: Promise<{ id: string }> }) {
try {
const { id } = await params;
const body = await request.json();
const rawBody = await request.json();
const validation = validateBody(updateMappingSchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const mapping = await updateModelComboMapping(id, {
pattern: body.pattern,
comboId: body.comboId,
priority: body.priority,
enabled: body.enabled,
description: body.description,
});
const mapping = await updateModelComboMapping(id, validation.data);
if (!mapping) {
return NextResponse.json({ error: "Mapping not found" }, { status: 404 });
+20 -12
View File
@@ -4,8 +4,18 @@
* POST Create a new mapping
*/
import { z } from "zod";
import { NextResponse } from "next/server";
import { getModelComboMappings, createModelComboMapping } from "@/lib/localDb";
import { validateBody, isValidationFailure } from "@/shared/validation/helpers";
const createMappingSchema = z.object({
pattern: z.string().min(1, "Pattern is required").max(500),
comboId: z.string().min(1, "ComboId is required"),
priority: z.number().int().optional().default(0),
enabled: z.boolean().optional().default(true),
description: z.string().max(1000).optional().default(""),
});
export async function GET() {
try {
@@ -21,21 +31,19 @@ export async function GET() {
export async function POST(request: Request) {
try {
const body = await request.json();
if (!body.pattern || typeof body.pattern !== "string") {
return NextResponse.json({ error: "Missing or invalid 'pattern' field" }, { status: 400 });
}
if (!body.comboId || typeof body.comboId !== "string") {
return NextResponse.json({ error: "Missing or invalid 'comboId' field" }, { status: 400 });
const rawBody = await request.json();
const validation = validateBody(createMappingSchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { data } = validation;
const mapping = await createModelComboMapping({
pattern: body.pattern.trim(),
comboId: body.comboId,
priority: typeof body.priority === "number" ? body.priority : 0,
enabled: body.enabled !== false,
description: body.description || "",
pattern: data.pattern.trim(),
comboId: data.comboId,
priority: data.priority,
enabled: data.enabled,
description: data.description,
});
return NextResponse.json({ mapping }, { status: 201 });
+14 -1
View File
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { getSettings } from "@/lib/localDb";
import { getProviderConnections, getSettings } from "@/lib/localDb";
import { APP_CONFIG } from "@/shared/constants/config";
import { AI_PROVIDERS } from "@/shared/constants/providers";
/**
* GET /api/monitoring/health System health overview
@@ -16,6 +17,7 @@ export async function GET() {
const { getInflightCount } = await import("@omniroute/open-sse/services/requestDedup.ts");
const settings = await getSettings();
const connections = await getProviderConnections();
const circuitBreakers = getAllCircuitBreakerStatuses();
const rateLimitStatus = getAllRateLimitStatus();
const lockouts = getAllModelLockouts();
@@ -43,11 +45,22 @@ export async function GET() {
};
}
const configuredProviders = new Set(connections.map((c: any) => c.provider));
const activeProviders = new Set(
connections.filter((c: any) => c.isActive !== false).map((c: any) => c.provider)
);
return NextResponse.json({
status: "healthy",
timestamp: new Date().toISOString(),
system,
providerHealth,
providerSummary: {
catalogCount: Object.keys(AI_PROVIDERS).length,
configuredCount: configuredProviders.size,
activeCount: activeProviders.size,
monitoredCount: Object.keys(providerHealth).length,
},
localProviders: getAllHealthStatuses(),
rateLimitStatus,
lockouts,
+15 -10
View File
@@ -3,21 +3,26 @@
* POST forwards a request to a local endpoint and returns the result
*/
import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";
import { validateBody, isValidationFailure } from "@/shared/validation/helpers";
const tryRequestSchema = z.object({
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).optional().default("GET"),
path: z.string().min(1, "Path is required").startsWith("/", "Path must start with /"),
headers: z.record(z.string(), z.string()).optional().default({}),
body: z.any().optional(),
});
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { method = "GET", path, headers = {}, body: reqBody } = body;
if (!path || typeof path !== "string") {
return NextResponse.json({ error: "Missing 'path' field" }, { status: 400 });
const rawBody = await request.json();
const validation = validateBody(tryRequestSchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
// Only allow requests to local endpoints for security
if (!path.startsWith("/")) {
return NextResponse.json({ error: "Path must start with /" }, { status: 400 });
}
const { method, path, headers, body: reqBody } = validation.data;
// Build the target URL using the incoming request's origin
const origin = request.headers.get("x-forwarded-proto")
@@ -30,7 +35,7 @@ export async function POST(request: NextRequest) {
// Forward cookies/auth from the original request
const forwardHeaders: Record<string, string> = {
...headers,
...(headers as Record<string, string>),
};
// Forward auth from the dashboard session
+10
View File
@@ -130,6 +130,7 @@ export async function PUT(request) {
supportedEndpoints,
normalizeToolCallId,
preserveOpenAIDeveloperRole,
upstreamHeaders,
compatByProtocol,
} = validation.data;
@@ -141,6 +142,7 @@ export async function PUT(request) {
if ("normalizeToolCallId" in raw) updates.normalizeToolCallId = normalizeToolCallId;
if ("preserveOpenAIDeveloperRole" in raw)
updates.preserveOpenAIDeveloperRole = preserveOpenAIDeveloperRole;
if ("upstreamHeaders" in raw) updates.upstreamHeaders = upstreamHeaders;
if ("compatByProtocol" in raw && compatByProtocol !== undefined) {
updates.compatByProtocol = compatByProtocol;
}
@@ -157,11 +159,13 @@ export async function PUT(request) {
"modelId",
"normalizeToolCallId",
"preserveOpenAIDeveloperRole",
"upstreamHeaders",
"compatByProtocol",
].includes(k)
) &&
("normalizeToolCallId" in raw ||
"preserveOpenAIDeveloperRole" in raw ||
"upstreamHeaders" in raw ||
"compatByProtocol" in raw);
if (compatOnly) {
const knownProvider =
@@ -191,6 +195,12 @@ export async function PUT(request) {
if ("compatByProtocol" in raw && compatByProtocol && typeof compatByProtocol === "object") {
patch.compatByProtocol = compatByProtocol;
}
if ("upstreamHeaders" in raw) {
patch.upstreamHeaders =
upstreamHeaders === null || typeof upstreamHeaders === "object"
? upstreamHeaders
: undefined;
}
if (Object.keys(patch).length > 0) {
mergeModelCompatOverride(provider, modelId, patch);
}
+15 -5
View File
@@ -1,4 +1,6 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import {
getProviderConnectionById,
updateProviderConnection,
@@ -92,6 +94,11 @@ const CLI_RUNTIME_PROVIDER_MAP = {
kilocode: "kilo",
};
/** POST body is optional; when present, only known fields are validated. */
const providerConnectionTestBodySchema = z.object({
validationModelId: z.string().max(500).optional(),
});
function toSafeMessage(value: any, fallback = "Unknown error"): string {
if (typeof value !== "string") return fallback;
const trimmed = value.trim();
@@ -683,14 +690,17 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
try {
const { id } = await params;
// Parse optional body for validationModelId
let validationModelId;
let rawBody: unknown = {};
try {
const body = await request.json();
validationModelId = body?.validationModelId;
rawBody = await request.json();
} catch {
// Body is optional
// Empty or non-JSON body — treat as {}
}
const validation = validateBody(providerConnectionTestBodySchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { validationModelId } = validation.data;
const data = await testSingleConnection(id, validationModelId);
+7 -6
View File
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { setAccountKeyLimit, getAccountKeyLimit } from "@/lib/db/registeredKeys";
const limitsSchema = z.object({
@@ -32,20 +33,20 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
return NextResponse.json({ error: { message: "Authentication required" } }, { status: 401 });
}
let body: unknown;
let rawBody: unknown;
try {
body = await request.json();
rawBody = await request.json();
} catch {
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
}
const parsed = limitsSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
const validation = validateBody(limitsSchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const resolvedParams = await params;
setAccountKeyLimit(resolvedParams.id, parsed.data);
setAccountKeyLimit(resolvedParams.id, validation.data);
const updated = getAccountKeyLimit(resolvedParams.id);
return NextResponse.json({ accountId: resolvedParams.id, limits: updated });
}
@@ -1,4 +1,7 @@
import { CORS_ORIGIN } from "@/shared/utils/cors";
// Allow large audio/video file uploads — 5min for processing large files (up to 2GB)
export const maxDuration = 300;
import { handleAudioTranscription } from "@omniroute/open-sse/handlers/audioTranscription.ts";
import {
getProviderCredentials,
+7 -6
View File
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
const reportSchema = z.object({
title: z.string().min(1).max(300),
@@ -26,19 +27,19 @@ export async function POST(request: Request) {
return NextResponse.json({ error: { message: "Authentication required" } }, { status: 401 });
}
let body: unknown;
let rawBody: unknown;
try {
body = await request.json();
rawBody = await request.json();
} catch {
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
}
const parsed = reportSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
const validation = validateBody(reportSchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { title, provider, accountId, requestId, errorCode, details, labels = [] } = parsed.data;
const { title, provider, accountId, requestId, errorCode, details, labels = [] } = validation.data;
const repo = process.env.GITHUB_ISSUES_REPO;
const token = process.env.GITHUB_ISSUES_TOKEN;
+11
View File
@@ -16,6 +16,7 @@ import { getAllAudioModels } from "@omniroute/open-sse/config/audioRegistry.ts";
import { getAllModerationModels } from "@omniroute/open-sse/config/moderationRegistry.ts";
import { getAllVideoModels, getVideoProvider } from "@omniroute/open-sse/config/videoRegistry.ts";
import { getAllMusicModels, getMusicProvider } from "@omniroute/open-sse/config/musicRegistry.ts";
import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts";
const FALLBACK_ALIAS_TO_PROVIDER = {
ag: "antigravity",
@@ -190,6 +191,7 @@ export async function getUnifiedModelsResponse(
permission: [],
root: combo.name,
parent: null,
...(combo.context_length ? { context_length: combo.context_length } : {}),
});
}
@@ -206,8 +208,15 @@ export async function getUnifiedModelsResponse(
continue;
}
// Get default context length from registry (provider-level default)
const registryEntry = REGISTRY[alias] || REGISTRY[canonicalProviderId];
const defaultContextLength = registryEntry?.defaultContextLength;
for (const model of providerModels) {
const aliasId = `${alias}/${model.id}`;
// Model-level context length overrides provider default
const contextLength = model.contextLength || defaultContextLength;
models.push({
id: aliasId,
object: "model",
@@ -216,6 +225,7 @@ export async function getUnifiedModelsResponse(
permission: [],
root: model.id,
parent: null,
...(contextLength ? { context_length: contextLength } : {}),
});
// Add provider-id prefix in addition to short alias (ex: kiro/model + kr/model).
@@ -229,6 +239,7 @@ export async function getUnifiedModelsResponse(
permission: [],
root: model.id,
parent: aliasId,
...(contextLength ? { context_length: contextLength } : {}),
});
}
}

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