Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ff6353b88 | |||
| 926fd8abf4 | |||
| 211a7a4cfe | |||
| c1835cd9cc | |||
| 5700044393 | |||
| 36fbd3d018 | |||
| d1178390a9 | |||
| 8182825e92 | |||
| 2392006246 | |||
| a6e78cd5dc | |||
| 8752790352 | |||
| 3976c79e12 | |||
| 5c1cf7f4ac | |||
| 912321a030 | |||
| ab0a905499 | |||
| 3c6b3c02df | |||
| bcb2e91d97 | |||
| 766ef94605 | |||
| e3f016e262 | |||
| 65833f1ae0 | |||
| 2602cd9ab2 | |||
| 8333f3d9de | |||
| dee1d9ba74 | |||
| ed2e0c5080 | |||
| 7db810d7d0 | |||
| 8dae4e5038 | |||
| b9b28edefe | |||
| 58120f435f | |||
| 027b8e52da | |||
| aad510a9d5 | |||
| 9852a805a1 | |||
| b2cabf0122 | |||
| 521ce15f86 | |||
| fb97c11140 | |||
| 1c5c62e311 | |||
| 77148f7f97 | |||
| a329d2f2bc | |||
| 39e9e4446b | |||
| b32de54944 | |||
| 071b874e1b | |||
| 9ba65d3323 | |||
| 890a851bbf | |||
| 5f6ca23da4 | |||
| 58df1c06ee | |||
| 95f8599dc2 | |||
| 8a11242d7f | |||
| 948513ef5f | |||
| c497a35d21 | |||
| e0a539bc64 | |||
| 44b8395ead | |||
| 1bc8878490 | |||
| ded2ac493d | |||
| 57b3319ac0 | |||
| eba7ba25b8 | |||
| df774892c8 | |||
| f3b4ce6b67 | |||
| bb8545b3e1 | |||
| 600149fc2b | |||
| f4de3c8748 | |||
| 6e7e04839f | |||
| f62dcc12a0 | |||
| bef591c2e6 | |||
| 5907296d36 | |||
| aa2a7d12be | |||
| 33fee5dcc5 | |||
| e9ae50be0c | |||
| 5886c0fd5e | |||
| 35538e6f77 | |||
| ea924f3bbf | |||
| 7bc15a2fc9 | |||
| 2bf7db92ee | |||
| 95260f56ba | |||
| c5ace0376a | |||
| 7ee09388fa | |||
| a15b0ef060 | |||
| 57cfd9a315 | |||
| 5fb4149c32 | |||
| 03d97ba617 | |||
| 5205f5f4b4 | |||
| 6eda0f4d00 | |||
| 9e640cac6b | |||
| 061521f87f | |||
| b15eb278e1 | |||
| 142ac8eb96 | |||
| 88705bb6e9 | |||
| 60d4fcfe7e | |||
| 038d19ec98 | |||
| e1b98768c7 | |||
| b82af2b849 | |||
| 703591d76a | |||
| 7142688a77 | |||
| a12622b3d8 | |||
| 9248ab4dfd | |||
| 5a8c6440f0 | |||
| 74b694a4dd | |||
| 896b52d5fb | |||
| 1429fea27a | |||
| 3218563f32 | |||
| d412edbbe1 | |||
| 968159a85d | |||
| 18a3741fc2 | |||
| f1be3e6bb0 | |||
| b717a02394 | |||
| d68143e63d | |||
| 0d306b8b1c | |||
| a655863855 | |||
| 58264c80dd | |||
| 6f9f1aec65 | |||
| 97b1ee5b02 | |||
| fe033cd0b3 | |||
| afbd07c62a | |||
| 9b15996545 | |||
| 1dbbd7241d | |||
| 6c0ef48d45 | |||
| 8b57f88ca3 | |||
| 3e9fdc777e | |||
| a8ca88797a | |||
| 71540b5dc0 | |||
| b5a145d7b3 | |||
| 21d6a0a2dd | |||
| 80cc7340ac | |||
| 45b272ee2f | |||
| f765664580 | |||
| 10b44f036d | |||
| 1bf4ee3a3c | |||
| 5d82ffa503 | |||
| 5dc3fd2ec0 | |||
| 4562fdda92 | |||
| 18258b9b0d | |||
| 92e0f242c7 | |||
| 428fa9404c | |||
| 3cccc480fb | |||
| acb94216c8 | |||
| 5fa97841b2 | |||
| 4ad66bf7b9 | |||
| 64860ed5e5 | |||
| b17faf6e1e | |||
| 0ea73bd527 | |||
| b2f0820560 | |||
| 7ad5d42982 | |||
| 3912734498 | |||
| 0fa3f9a057 | |||
| 0fbabdcf25 | |||
| 67b7ae98a6 | |||
| 0f703c95dd | |||
| c34b3f41bd | |||
| e003b17280 | |||
| e003d58c60 | |||
| 0546d06c0a | |||
| 5337111990 | |||
| bb06f8eb0c | |||
| 23e3a1c269 | |||
| e47740e02e | |||
| d9ff0035f5 | |||
| 7a7f3be0d2 | |||
| 91e45fbe95 | |||
| 7d7e9da28c | |||
| 24a9739604 | |||
| 4fb9687782 | |||
| 95ffc21b60 | |||
| f3c5e55b26 | |||
| 40183c6a5c | |||
| 457c59e38a |
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35)
|
||||
---
|
||||
|
||||
# Deploy to Akamai VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Akamai VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Akamai VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to BOTH the Akamai VPS and the Local VPS
|
||||
---
|
||||
|
||||
# Deploy to VPS (Both) Workflow
|
||||
|
||||
Deploy OmniRoute to the production VPSs using `npm pack + scp` + PM2.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to both VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Local VPS (192.168.0.15)
|
||||
---
|
||||
|
||||
# Deploy to Local VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Local VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
@@ -1,102 +0,0 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35) via npm
|
||||
---
|
||||
|
||||
# Deploy to VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the production VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**VPS:** `69.164.221.35` (Akamai, Ubuntu 24.04, 1GB RAM + 2.5GB swap)
|
||||
**Local VPS:** `192.168.0.15` (same setup)
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> PM2 runs from the global npm package at `/usr/lib/node_modules/omniroute`.
|
||||
> The Next.js standalone build is at `app/server.js` inside that directory.
|
||||
> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**.
|
||||
|
||||
> [!CAUTION]
|
||||
> **NEVER** use `pm2 restart omniroute` after `npm install -g`. This drops env vars.
|
||||
> Always use `pm2 delete omniroute && pm2 start <ecosystem.config.cjs> --update-env`.
|
||||
> After `npm install -g`, always rebuild better-sqlite3: `cd .../app && npm rebuild better-sqlite3`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
Run the full build (includes hash-strip patch) and create the .tgz:
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to both VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "pm2 list && cat \$(npm root -g)/omniroute/app/package.json | grep version | head -1 && curl -s -o /dev/null -w 'HTTP %{http_code}' http://localhost:20128/"
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "pm2 list && cat \$(npm root -g)/omniroute/app/package.json | grep version | head -1 && curl -s -X POST http://localhost:20128/api/auth/login -H 'Content-Type: application/json' -d '{\"password\":\"123456\"}'"
|
||||
```
|
||||
|
||||
Expected: PM2 shows `online`, version matches, login returns `{"success":true}`.
|
||||
|
||||
## How it works
|
||||
|
||||
1. `npm run build:cli` builds Next.js standalone → `app/` and strips Turbopack hashed require() calls from chunks
|
||||
2. `npm pack --ignore-scripts` packages without re-running the build
|
||||
3. `scp` transfers the .tgz to each VPS (~286MB)
|
||||
4. `npm install -g /tmp/omniroute-*.tgz --ignore-scripts` installs pre-built package
|
||||
5. `npm rebuild better-sqlite3` recompiles native bindings for the VPS Node.js version
|
||||
6. `pm2 delete` + `pm2 start ecosystem.config.cjs --update-env` restarts with env vars
|
||||
7. `pm2 save` persists the process list for reboot survival
|
||||
|
||||
## Ecosystem Config
|
||||
|
||||
Both VPSs have `ecosystem.config.cjs` at `/root/.omniroute/ecosystem.config.cjs`.
|
||||
This file defines env vars (PORT, DATA_DIR, INITIAL_PASSWORD, OAuth secrets, etc.)
|
||||
that `pm2 restart` does NOT inject — only `pm2 start --update-env` does.
|
||||
|
||||
## PM2 Setup (one-time — if reconfiguring from scratch)
|
||||
|
||||
```bash
|
||||
ssh root@<VPS> "
|
||||
pm2 delete omniroute 2>/dev/null;
|
||||
cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 &&
|
||||
pm2 start /root/.omniroute/ecosystem.config.cjs --update-env &&
|
||||
pm2 save && pm2 startup
|
||||
"
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Ensure `/root/.omniroute/ecosystem.config.cjs` exists with all required env vars.
|
||||
> For fresh installs, copy from the existing VPS or create from the template in `.env`.
|
||||
|
||||
## Notes
|
||||
|
||||
- Env vars are in `/root/.omniroute/ecosystem.config.cjs` (NOT `.env` in app dir)
|
||||
- PM2 is configured with `pm2 startup` to auto-restart on reboot
|
||||
- Nginx proxies `omniroute.online` → `localhost:20128`
|
||||
- The VPS has only 1GB RAM — builds happen locally, never on the VPS
|
||||
- After `npm install -g`, `better-sqlite3` MUST be rebuilt in the `app/` subdir
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
description: Read all open GitHub Discussions, summarize them, respond to pending ones, and create issues from actionable feature requests
|
||||
---
|
||||
|
||||
# /review-discussions — GitHub Discussions Review & Response Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow reads all open GitHub Discussions, generates a categorized summary, identifies which ones need a response, drafts and posts replies, and optionally creates issues from actionable feature requests. It follows the same flow used for Issues but adapted for the Discussions forum.
|
||||
|
||||
// turbo-all
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Fetch All Open Discussions
|
||||
|
||||
- Use `read_url_content` to fetch `https://github.com/<owner>/<repo>/discussions`
|
||||
- Parse the discussion list to get all discussion titles, IDs, authors, categories, and dates
|
||||
- For each discussion, fetch the individual page to read the full content and all comments/replies
|
||||
|
||||
### 3. Summarize All Discussions
|
||||
|
||||
For each discussion, extract:
|
||||
|
||||
- **Title** and **#Number**
|
||||
- **Author** (GitHub username)
|
||||
- **Category** (Announcements, General, Ideas, Q&A, Show and tell)
|
||||
- **Date** created
|
||||
- **Summary** of the original post (1-2 sentences)
|
||||
- **Comments count** and key participants
|
||||
- **Your previous response** (if any)
|
||||
- **Pending action** — whether a response or follow-up is needed
|
||||
|
||||
### 4. Present Summary Report to User
|
||||
|
||||
Present the full summary to the user organized by category, using a table:
|
||||
|
||||
| # | Category | Title | Author | Date | Status |
|
||||
| --- | -------- | ----- | ------ | ------ | ----------------- |
|
||||
| #N | Ideas | Title | @user | Mar 23 | ⚠️ Needs response |
|
||||
| #N | Q&A | Title | @user | Mar 9 | ✅ Answered |
|
||||
| #N | General | Title | @user | Mar 19 | ⚠️ Needs response |
|
||||
|
||||
Highlight:
|
||||
|
||||
- **⚠️ Needs response** — No reply from maintainer, or a follow-up comment was left unanswered
|
||||
- **✅ Answered** — Maintainer already responded
|
||||
- **🐛 Bug reported** — A bug was mentioned that needs tracking
|
||||
- **💡 Actionable** — Contains a concrete feature request that could become an issue
|
||||
|
||||
### 5. Draft & Post Responses
|
||||
|
||||
For each discussion that needs a response, draft a reply following these guidelines:
|
||||
|
||||
#### Response Style
|
||||
|
||||
- **Friendly and professional** — Start with "Hey @username!"
|
||||
- **Acknowledge the contribution** — Thank the user for their input
|
||||
- **Be specific** — Reference existing features, settings, or dashboard pages if the feature already exists
|
||||
- **Provide workarounds** — If the request isn't implemented yet, suggest current alternatives
|
||||
- **Commit to action** — If the request is valid, state that you'll open an issue or add it to the roadmap
|
||||
- **Keep it concise** — 3-5 paragraphs max
|
||||
|
||||
#### Posting via Browser
|
||||
|
||||
- Use `browser_subagent` to navigate to each discussion and post the comment
|
||||
- **IMPORTANT**: When typing text in GitHub comment boxes via the browser, use only plain ASCII characters:
|
||||
- Use regular hyphens `-` instead of em-dashes
|
||||
- Use `->` instead of arrow symbols
|
||||
- Do NOT use emoji Unicode characters (the browser keyboard may fail on them)
|
||||
- Use `**bold**` and `\`code\`` markdown formatting
|
||||
- Click the green "Comment" button (or "Reply" for threaded replies) after typing
|
||||
- Verify the comment was posted by checking the page shows the new comment
|
||||
|
||||
### 6. Create Issues from Actionable Feature Requests
|
||||
|
||||
For discussions that contain concrete, actionable feature requests:
|
||||
|
||||
1. Ask the user which ones should become issues
|
||||
2. For each approved request, create a GitHub issue via `browser_subagent`:
|
||||
- Navigate to `https://github.com/<owner>/<repo>/issues/new`
|
||||
- **Title**: `<Feature Name> - <Short description>`
|
||||
- **Body** should include:
|
||||
- `## Feature Request` header
|
||||
- `**Source:** Discussion #N by @author`
|
||||
- `## Problem` — What limitation the user hit
|
||||
- `## Proposed Solution` — How it could work
|
||||
- `### Implementation Ideas` — Technical approach
|
||||
- `### Current Workarounds` — What users can do today
|
||||
- `## Additional Context` — Links to related issues/discussions
|
||||
- Add `enhancement` label
|
||||
- Click "Submit new issue" / "Create"
|
||||
3. After creation, go back to the original discussion and post a comment linking to the new issue:
|
||||
- "I've opened Issue #N to track this feature request. Follow along there for updates!"
|
||||
|
||||
### 7. Final Report
|
||||
|
||||
Present a final summary to the user:
|
||||
|
||||
| Discussion | Action Taken |
|
||||
| ---------- | ---------------------------------- |
|
||||
| #N — Title | Responded with workarounds |
|
||||
| #N — Title | Responded + created Issue #N |
|
||||
| #N — Title | Already answered, no action needed |
|
||||
| #N — Title | Responded to follow-up comment |
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow is **interactive** — always present the summary and wait for user approval before posting responses or creating issues
|
||||
- If the user says "pode responder" (or similar approval), proceed with posting all drafted responses
|
||||
- For discussions in non-English languages, respond in the same language as the original post
|
||||
- Always reference specific dashboard paths, config options, or code files when explaining existing features
|
||||
- When a discussion reveals a bug, note it separately from feature requests
|
||||
@@ -0,0 +1,145 @@
|
||||
name: Bug Report
|
||||
description: Report a bug or unexpected behavior in OmniRoute
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to report a bug. Please fill out the sections below so we can reproduce and fix the issue.
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: OmniRoute Version
|
||||
description: "Run `omniroute --version` or check the left sidebar in the dashboard."
|
||||
placeholder: "e.g. 3.0.9"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: install-method
|
||||
attributes:
|
||||
label: Installation Method
|
||||
options:
|
||||
- npm (global)
|
||||
- Docker / Docker Compose
|
||||
- Electron desktop app
|
||||
- Built from source
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: os-version
|
||||
attributes:
|
||||
label: OS Version
|
||||
placeholder: "e.g. Windows 11 23H2, macOS 15.3, Ubuntu 24.04"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: node-version
|
||||
attributes:
|
||||
label: Node.js Version
|
||||
description: "Run `node --version`. Skip if using Docker."
|
||||
placeholder: "e.g. 22.12.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: provider
|
||||
attributes:
|
||||
label: Provider(s) Involved
|
||||
description: "Which AI provider(s) does this affect?"
|
||||
placeholder: "e.g. Antigravity, OpenRouter, Ollama, Qwen"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: model
|
||||
attributes:
|
||||
label: Model(s) Involved
|
||||
placeholder: "e.g. claude-sonnet-4-20250514, gpt-4o, gemini-2.5-pro"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: client-tool
|
||||
attributes:
|
||||
label: Client Tool
|
||||
description: "Which tool are you using OmniRoute with?"
|
||||
placeholder: "e.g. Claude Code, Cursor, Roo Code, OpenClaw, Gemini CLI, cURL"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: "A clear description of what the bug is."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: "Step-by-step instructions to reproduce the behavior."
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: "What did you expect to happen?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: "What actually happened?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Error Logs / Output
|
||||
description: "Paste any relevant error messages, logs, or terminal output. This will be automatically formatted as code."
|
||||
render: shell
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: "If applicable, add screenshots to help explain the problem. Please also include the text of any error messages above — screenshots alone are not searchable."
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: "Any other context about the problem (e.g. proxy config, number of accounts, network setup)."
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Question / Help
|
||||
url: https://github.com/diegosouzapw/OmniRoute/discussions
|
||||
about: For questions or help with setup, please use GitHub Discussions instead of opening an issue.
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature or improvement for OmniRoute
|
||||
title: "[Feature] "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for suggesting a feature! Please describe the problem you're trying to solve and how you'd like it to work.
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem / Use Case
|
||||
description: "What problem does this feature solve? Why do you need it?"
|
||||
placeholder: "I'm trying to ... but currently ..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Proposed Solution
|
||||
description: "How would you like this to work?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives Considered
|
||||
description: "Have you considered any workarounds or alternative approaches?"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
description: "Which part of OmniRoute does this relate to?"
|
||||
multiple: true
|
||||
options:
|
||||
- Dashboard / UI
|
||||
- Proxy / Routing
|
||||
- Provider Support
|
||||
- CLI Tools Integration
|
||||
- OAuth / Authentication
|
||||
- Analytics / Usage Tracking
|
||||
- Docker / Deployment
|
||||
- Documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: provider
|
||||
attributes:
|
||||
label: Related Provider(s)
|
||||
description: "If this relates to specific providers, list them."
|
||||
placeholder: "e.g. Antigravity, OpenRouter, Ollama"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: "Any other context, mockups, or references."
|
||||
validations:
|
||||
required: false
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -89,6 +89,7 @@ docs/*
|
||||
!docs/MCP-SERVER.md
|
||||
!docs/CLI-TOOLS.md
|
||||
|
||||
|
||||
# open-sse tests
|
||||
open-sse/test/*
|
||||
|
||||
@@ -130,3 +131,6 @@ vscode-extension/
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
*.sqlite-journal
|
||||
|
||||
# Compiled npm-package build artifact (not source, should not be in git)
|
||||
/app
|
||||
|
||||
@@ -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 model’s 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/`)
|
||||
|
||||
|
||||
+840
@@ -4,6 +4,846 @@
|
||||
|
||||
---
|
||||
|
||||
## [3.1.2] — 2026-03-26
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Critical: Tool Calling Regression** — Fixed `proxy_Bash` errors by disabling the `proxy_` tool name prefix in the Claude passthrough path. Tools like `Bash`, `Read`, `Write` were being renamed to `proxy_Bash`, `proxy_Read`, etc., causing Claude to reject them (#618)
|
||||
- **Kiro Account Ban Documentation** — Documented as upstream AWS anti-fraud false positive, not an OmniRoute issue (#649)
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- **936 tests, 0 failures**
|
||||
|
||||
---
|
||||
|
||||
## [3.1.1] — 2026-03-26
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Vision Capability Metadata**: Added `capabilities.vision`, `input_modalities`, and `output_modalities` to `/v1/models` entries for vision-capable models (PR #646)
|
||||
- **Gemini 3.1 Models**: Added `gemini-3.1-pro-preview` and `gemini-3.1-flash-lite-preview` to the Antigravity provider (#645)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Ollama Cloud 401 Error**: Fixed incorrect API base URL — changed from `api.ollama.com` to official `ollama.com/v1/chat/completions` (#643)
|
||||
- **Expired Token Retry**: Added bounded retry with exponential backoff (5→10→20 min) for expired OAuth connections instead of permanently skipping them (PR #647)
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- **936 tests, 0 failures**
|
||||
|
||||
---
|
||||
|
||||
## [3.1.0] — 2026-03-26
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **GitHub Issue Templates**: Added standardized bug report, feature request, and config/proxy issue templates (#641)
|
||||
- **Clear All Models**: Added a "Clear All Models" button to the provider detail page with i18n support in 29 languages (#634)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Locale Conflict (`in.json`)**: Renamed the Hindi locale file from `in.json` (Indonesian ISO code) to `hi.json` to fix translation conflicts in Weblate (#642)
|
||||
- **Codex Empty Tool Names**: Moved tool name sanitization before the native Codex passthrough, fixing 400 errors from upstream providers when tools had empty names (#637)
|
||||
- **Streaming Newline Artifacts**: Added `collapseExcessiveNewlines` to the response sanitizer, collapsing runs of 3+ consecutive newlines from thinking models into a standard double newline (#638)
|
||||
- **Claude Reasoning Effort**: Converted OpenAI `reasoning_effort` param to Claude's native `thinking` budget block across all request paths, including automatic `max_tokens` adjustment (#627)
|
||||
- **Qwen Token Refresh**: Implemented proactive pre-expiry OAuth token refreshes (5-minute buffer) to prevent requests from failing when using short-lived tokens (#631)
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- **936 tests, 0 failures** (+10 tests since 3.0.9)
|
||||
|
||||
---
|
||||
|
||||
## [3.0.9] — 2026-03-26
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **NaN tokens in Claude Code / client responses (#617):**
|
||||
- `sanitizeUsage()` now cross-maps `input_tokens`→`prompt_tokens` and `output_tokens`→`completion_tokens` before the whitelist filter, fixing responses showing NaN/0 token counts when providers return Claude-style usage field names
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- Updated `yaml` package to fix stack overflow vulnerability (GHSA-48c2-rrv3-qjmp)
|
||||
|
||||
### 📋 Issue Triage
|
||||
|
||||
- Closed #613 (Codestral — resolved with Custom Provider workaround)
|
||||
- Commented on #615 (OpenCode dual-endpoint — workaround provided, tracked as feature request)
|
||||
- Commented on #618 (tool call visibility — requesting v3.0.9 test)
|
||||
- Commented on #627 (effort level — already supported)
|
||||
|
||||
---
|
||||
|
||||
## [3.0.8] — 2026-03-25
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Translation Failures for OpenAI-format Providers in Claude CLI (#632):**
|
||||
- Handle `reasoning_details[]` array format from StepFun/OpenRouter — converts to `reasoning_content`
|
||||
- Handle `reasoning` field alias from some providers → normalized to `reasoning_content`
|
||||
- Cross-map usage field names: `input_tokens`↔`prompt_tokens`, `output_tokens`↔`completion_tokens` in `filterUsageForFormat`
|
||||
- Fix `extractUsage` to accept both `input_tokens`/`output_tokens` and `prompt_tokens`/`completion_tokens` as valid usage fields
|
||||
- Applied to both streaming (`sanitizeStreamingChunk`, `openai-to-claude.ts` translator) and non-streaming (`sanitizeMessage`) paths
|
||||
|
||||
---
|
||||
|
||||
## [3.0.7] — 2026-03-25
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Antigravity Token Refresh:** Fixed `client_secret is missing` error for npm-installed users — the `clientSecretDefault` was empty in providerRegistry, causing Google to reject token refresh requests (#588)
|
||||
- **OpenCode Zen Models:** Added `modelsUrl` to the OpenCode Zen registry entry so "Import from /models" works correctly (#612)
|
||||
- **Streaming Artifacts:** Fixed excessive newlines left in responses after thinking-tag signature stripping (#626)
|
||||
- **Proxy Fallback:** Added automatic retry without proxy when SOCKS5 relay fails
|
||||
- **Proxy Test:** Test endpoint now resolves real credentials from DB via proxyId
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Playground Account/Key Selector:** Persistent, always-visible dropdown to select specific provider accounts/keys for testing — fetches all connections at startup and filters by selected provider
|
||||
- **CLI Tools Dynamic Models:** Model selection now dynamically fetches from `/v1/models` API — providers like Kiro now show their full model catalog
|
||||
- **Antigravity Model List:** Updated with Claude Sonnet 4.5, Claude Sonnet 4, GPT 5, GPT 5 Mini; enabled `passthroughModels` for dynamic model access (#628)
|
||||
|
||||
### 🔧 Maintenance
|
||||
|
||||
- Merged PR #625 — Provider Limits light mode background fix
|
||||
|
||||
---
|
||||
|
||||
## [3.0.6] — 2026-03-25
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Limits/Proxy:** Fixed Codex limit fetching for accounts behind SOCKS5 proxies — token refresh now runs inside proxy context
|
||||
- **CI:** Fixed integration test `v1/models` assertion failure in CI environments without provider connections
|
||||
- **Settings:** Proxy test button now shows success/failure results immediately (previously hidden behind health data)
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Playground:** Added Account selector dropdown — test specific connections individually when a provider has multiple accounts
|
||||
|
||||
### 🔧 Maintenance
|
||||
|
||||
- Merged PR #623 — LongCat API base URL path correction
|
||||
|
||||
---
|
||||
|
||||
## [3.0.5] — 2026-03-25
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Limits UI:** Added tag grouping feature to the connections dashboard to improve visual organization for accounts with custom tags.
|
||||
|
||||
---
|
||||
|
||||
## [3.0.4] — 2026-03-25
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Streaming:** Fixed `TextDecoder` state corruption inside combo `sanitize` TransformStream which caused SSE garbled output matching multibyte characters (PR #614)
|
||||
- **Providers UI:** Safely render HTML tags inside provider connection error tooltips using `dangerouslySetInnerHTML`
|
||||
- **Proxy Settings:** Added missing `username` and `password` payload body properties allowing authenticated proxies to be successfully verified from the Dashboard.
|
||||
- **Provider API:** Bound soft exception returns to `getCodexUsage` preventing API HTTP 500 failures when token fetch fails
|
||||
|
||||
---
|
||||
|
||||
## [3.0.3] — 2026-03-25
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Auto-Sync Models:** Added a UI toggle and `sync-models` endpoint to automatically synchronise model lists per provider using a scheduled interval scheduler (PR #597)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Timeouts:** Elevated default proxies `FETCH_TIMEOUT_MS` and `STREAM_IDLE_TIMEOUT_MS` to 10 minutes to properly support deep reasoning models (like o1) without aborting requests (Fixes #609)
|
||||
- **CLI Tool Detection:** Improved cross-platform detection handling NVM paths, Windows `PATHEXT` (preventing `.cmd` wrappers issue), and custom NPM prefixes (PR #598)
|
||||
- **Streaming Logs:** Implemented `tool_calls` delta accumulation in streaming response logs so function calls are tracked and persisted accurately in DB (PR #603)
|
||||
- **Model Catalog:** Removed auth exemption, properly hiding `comfyui` and `sdwebui` models when no provider is explicitly configured (PR #599)
|
||||
|
||||
### 🌐 Translations
|
||||
|
||||
- **cs:** Improved Czech translation strings across the app (PR #601)
|
||||
|
||||
## [3.0.2] — 2026-03-25
|
||||
|
||||
### 🚀 Enhancements & Features
|
||||
|
||||
#### feat(ui): Connection Tag Grouping
|
||||
|
||||
- Added a Tag/Group field to `EditConnectionModal` (stored in `providerSpecificData.tag`) without requiring DB schema migrations.
|
||||
- Connections in the provider view now dynamically group by tag with visual dividers.
|
||||
- Untagged connections appear first without a header, followed by tagged groups in alphabetical order.
|
||||
- The tag grouping automatically applies to the Codex/Copilot/Antigravity Limits section since toggles exist inside connection rows.
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
#### fix(ui): Proxy Management UI Stabilization
|
||||
|
||||
- **Missing badges on connection cards:** Fixed by using `resolveProxyForConnection()` rather than static mapping.
|
||||
- **Test Connection disabled in saved mode:** Enabled the Test button by resolving proxy config from the saved list.
|
||||
- **Config Modal freezing:** Added `onClose()` calls after save/clear to prevent the UI from freezing.
|
||||
- **Double usage counting:** `ProxyRegistryManager` now loads usage eagerly on mount with deduplication by `scope` + `scopeId`. Usage counts were replaced with a Test button displaying IP/latency inline.
|
||||
|
||||
#### fix(translator): `function_call` prefix stripping
|
||||
|
||||
- Repaired an incomplete fix from PR #607 where only `tool_use` blocks stripped Claude's `proxy_` tool prefix. Now, clients using the OpenAI Responses API format will also correctly receive tool tools without the `proxy_` prefix.
|
||||
|
||||
---
|
||||
|
||||
## [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 (T01–T15 + T23–T42)
|
||||
|
||||
- **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
|
||||
|
||||
- **#563** — Per-model Combo Routing: map model name patterns (glob) to specific combos for automatic routing
|
||||
- New `model_combo_mappings` table (migration 010) with pattern, combo_id, priority, enabled
|
||||
- `resolveComboForModel()` DB function with glob-to-regex matching (case-insensitive, `*` and `?` wildcards)
|
||||
- `getComboForModel()` in `model.ts`: augments `getCombo()` with model-pattern fallback
|
||||
- `chat.ts`: routing decision now checks model-combo mappings before single-model handling
|
||||
- API: `GET/POST /api/model-combo-mappings`, `GET/PUT/DELETE /api/model-combo-mappings/:id`
|
||||
- Dashboard: "Model Routing Rules" section added to Combos page with inline add/edit/toggle/delete
|
||||
- Examples: `claude-sonnet*` → code-combo, `gpt-4o*` → openai-combo, `gemini-*` → google-combo
|
||||
|
||||
### 🌐 i18n
|
||||
|
||||
- **Full i18n Sync**: 2,788 missing keys added across 30 language files — all languages now at 100% parity with `en.json`
|
||||
- **Agents page i18n**: OpenCode Integration section fully internationalized (title, description, scanning, download labels)
|
||||
- **6 new keys** added to `agents` namespace for OpenCode section
|
||||
|
||||
### 🎨 UI/UX
|
||||
|
||||
- **Provider Icons**: 16 missing provider icons added (3 copied, 2 downloaded, 11 SVG created)
|
||||
- **SVG fallback**: `ProviderIcon` component updated with 4-tier strategy: Lobehub → PNG → SVG → Generic icon
|
||||
- **Agents fingerprinting**: Synced with CLI tools — added droid, openclaw, copilot, opencode to fingerprint list (14 total)
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- **CVE fix**: Resolved dompurify XSS vulnerability (GHSA-v2wj-7wpq-c8vv) via npm overrides forcing `dompurify@^3.3.2`
|
||||
- `npm audit` now reports **0 vulnerabilities**
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- Test suite: **923 tests, 0 failures** (+15 new model-combo mapping tests)
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.14] — 2026-03-23
|
||||
|
||||
### 🔀 Community PRs Merged
|
||||
|
||||
| PR | Author | Summary |
|
||||
| -------- | -------- | -------------------------------------------------------------------------------------------- |
|
||||
| **#562** | @coobabm | fix(ux): MCP session management, Claude passthrough normalization, OAuth modal, detectFormat |
|
||||
| **#561** | @zen0bit | fix(i18n): Czech translation corrections — HTTP method names and documentation updates |
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- Test suite: **908 tests, 0 failures**
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.13] — 2026-03-23
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **config:** resolve real API key from `keyId` in CLI settings routes (`codex-settings`, `droid-settings`, `kilo-settings`) to prevent writing masked strings (#549)
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.12] — 2026-03-23
|
||||
|
||||
### 🔀 Community PRs Merged
|
||||
|
||||
| PR | Author | Summary |
|
||||
| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **#546** | @k0valik | fix(cli): `--version` returning `unknown` on Windows — use `JSON.parse(readFileSync)` instead of ESM import |
|
||||
| **#555** | @k0valik | fix(sse): centralized `resolveDataDir()` for path resolution in credentials, autoCombo, responses logger, and request logger |
|
||||
| **#544** | @k0valik | fix(cli): secure CLI tool detection via known installation paths (8 tools) with symlink validation, file-type checks, size bounds, minimal env in healthcheck |
|
||||
| **#542** | @rdself | fix(ui): improve light mode contrast — add missing CSS theme variables (`bg-primary`, `bg-subtle`, `text-primary`) and fix dark-only colors in log detail |
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **TDZ fix in `cliRuntime.ts`** — `validateEnvPath` was used before initialization at module startup by `getExpectedParentPaths()`. Reordered declarations to fix `ReferenceError`.
|
||||
- **Build fixes** — Added `pino` and `pino-pretty` to `serverExternalPackages` to prevent Turbopack from breaking Pino's internal worker loading.
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- Test suite: **905 tests, 0 failures**
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.10] — 2026-03-23
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **#509 / #508** — Electron build regression: downgraded Next.js from `16.1.x` to `16.0.10` to eliminate Turbopack module-hashing instability that caused blank screens in the Electron desktop bundle.
|
||||
- **Unit test fixes** — Corrected two stale test assertions (`nanobanana-image-handler` aspect ratio/resolution, `thinking-budget` Gemini `thinkingConfig` field mapping) that had drifted after recent implementation changes.
|
||||
- **#541** — Responded to user feedback about installation complexity; no code changes required.
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.9] — 2026-03-23
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **T29** — Vertex AI SA JSON Executor: implemented using the `jose` library to handle JWT/Service Account auth, along with configurable regions in the UI and automatic partner model URL building.
|
||||
- **T42** — Image generation aspect ratio mapping: created `sizeMapper` logic for generic OpenAI formats (`size`), added native `imagen3` handling, and updated NanoBanana endpoints to utilize mapped aspect ratios automatically.
|
||||
- **T38** — Centralized model specifications: `modelSpecs.ts` created for limits and parameters per model.
|
||||
|
||||
### 🔧 Improvements
|
||||
|
||||
- **T40** — OpenCode CLI tools integration: native `opencode-zen` and `opencode-go` integration completed in earlier PR.
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.8] — 2026-03-23
|
||||
|
||||
### 🔧 Bug Fixes & Improvements (Fallback, Quota & Budget)
|
||||
|
||||
- **T24** — `503` cooldown await fix + `406` mapping: mapped `406 Not Acceptable` to `503 Service Unavailable` with proper cooldown intervals.
|
||||
- **T25** — Provider validation fallback: graceful fallback to standard validation models when a specific `validationModelId` is not present.
|
||||
- **T36** — `403` vs `429` provider handling refinement: extracted into `errorClassifier.ts` to properly segregate hard permissions failures (`403`) from rate limits (`429`).
|
||||
- **T39** — Endpoint Fallback for `fetchAvailableModels`: implemented a tri-tier mechanism (`/models` -> `/v1/models` -> local generic catalog) + `list_models_catalog` MCP tool updates to reflect `source` and `warning`.
|
||||
- **T33** — Thinking level to budget conversion: translates qualitative thinking levels into precise budget allocations.
|
||||
- **T41** — Background task auto redirect: routes heavy background evaluation tasks to flash/efficient models automatically.
|
||||
- **T23** — Intelligent quota reset fallback: accurately extracts `x-ratelimit-reset` / `retry-after` header values or maps static cooldowns.
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.7] — 2026-03-23 _(What's New vs v2.9.5 — will be released as v3.0.0)_
|
||||
|
||||
> **Upgrade from v2.9.5:** 16 issues resolved · 2 community PRs merged · 2 new providers · 7 new API endpoints · 3 new features · DB migration 008+009 · 832 tests passing · 15 sub2api gap improvements (T01–T15 complete).
|
||||
|
||||
### 🆕 New Providers
|
||||
|
||||
| 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) |
|
||||
|
||||
Both providers use the new `OpencodeExecutor` with multi-format routing (`/chat/completions`, `/messages`, `/responses`, `/models/{model}:generateContent`).
|
||||
|
||||
---
|
||||
|
||||
### ✨ New 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` | Get key metadata |
|
||||
| `/api/v1/registered-keys/{id}` | `DELETE` | Revoke a key |
|
||||
| `/api/v1/registered-keys/{id}/revoke` | `POST` | Revoke (for clients without DELETE support) |
|
||||
| `/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 |
|
||||
|
||||
**DB — Migration 008:** Three new tables: `registered_keys`, `provider_key_limits`, `account_key_limits`.
|
||||
**Security:** Keys stored as SHA-256 hashes. Raw key shown once on creation, never retrievable again.
|
||||
**Quota types:** `maxActiveKeys`, `dailyIssueLimit`, `hourlyIssueLimit` per provider and per account.
|
||||
**Idempotency:** `idempotency_key` field prevents duplicate issuance. Returns `409 IDEMPOTENCY_CONFLICT` if key was already used.
|
||||
**Budget per key:** `dailyBudget` / `hourlyBudget` — limits how many requests a key can route per window.
|
||||
**GitHub reporting:** Optional. Set `GITHUB_ISSUES_REPO` + `GITHUB_ISSUES_TOKEN` to auto-create GitHub issues on quota exceeded or issuance failures.
|
||||
|
||||
#### 🎨 Provider Icons — @lobehub/icons (#529)
|
||||
|
||||
All provider icons in the dashboard now use `@lobehub/icons` React components (130+ providers with SVG).
|
||||
Fallback chain: **Lobehub SVG → existing `/providers/{id}.png` → generic icon**. Uses a proper React `ErrorBoundary` pattern.
|
||||
|
||||
#### 🔄 Model Auto-Sync Scheduler (#488)
|
||||
|
||||
OmniRoute now automatically refreshes model lists for connected providers every **24 hours**.
|
||||
|
||||
- Runs on server startup via the existing `/api/sync/initialize` hook
|
||||
- Configurable via `MODEL_SYNC_INTERVAL_HOURS` environment variable
|
||||
- Covers 16 major providers
|
||||
- Records last sync time in the settings database
|
||||
|
||||
---
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
#### OAuth & Auth
|
||||
|
||||
- **#537 — Gemini CLI OAuth:** Clear actionable error when `GEMINI_OAUTH_CLIENT_SECRET` is missing in Docker/self-hosted deployments. Previously showed cryptic `client_secret is missing` from Google. Now provides specific `docker-compose.yml` and `~/.omniroute/.env` instructions.
|
||||
|
||||
#### Providers & Routing
|
||||
|
||||
- **#536 — LongCat AI:** Fixed `baseUrl` (`api.longcat.chat/openai`) and `authHeader` (`Authorization: Bearer`).
|
||||
- **#535 — Pinned model override:** `body.model` is now correctly set to `pinnedModel` when context-cache protection is active.
|
||||
- **#532 — OpenCode Go key validation:** Now uses the `zen/v1` test endpoint (`testKeyBaseUrl`) — same key works for both tiers.
|
||||
|
||||
#### CLI & Tools
|
||||
|
||||
- **#527 — Claude Code + Codex loop:** `tool_result` blocks are now converted to text instead of dropped, stopping infinite tool-result loops.
|
||||
- **#524 — OpenCode config save:** Added `saveOpenCodeConfig()` handler (XDG_CONFIG_HOME aware, writes TOML).
|
||||
- **#521 — Login stuck:** Login no longer freezes after skipping password setup — redirects correctly to onboarding.
|
||||
- **#522 — API Manager:** Removed misleading "Copy masked key" button (replaced with a lock icon tooltip).
|
||||
- **#532 — OpenCode Go config:** Guide settings handler now handles `opencode` toolId.
|
||||
|
||||
#### Developer Experience
|
||||
|
||||
- **#489 — Antigravity:** Missing `googleProjectId` returns a structured 422 error with reconnect guidance instead of a cryptic crash.
|
||||
- **#510 — Windows paths:** MSYS2/Git-Bash paths (`/c/Program Files/...`) are now normalized to `C:\\Program Files\\...` automatically.
|
||||
- **#492 — CLI startup:** `omniroute` CLI now detects `mise`/`nvm`-managed Node when `app/server.js` is missing and shows targeted fix instructions.
|
||||
|
||||
---
|
||||
|
||||
### 📖 Documentation Updates
|
||||
|
||||
- **#513** — Docker password reset: `INITIAL_PASSWORD` env var workaround documented
|
||||
- **#520** — pnpm: `pnpm approve-builds better-sqlite3` step documented
|
||||
|
||||
---
|
||||
|
||||
### ✅ Issues Resolved in v3.0.0
|
||||
|
||||
`#464` `#488` `#489` `#492` `#510` `#513` `#520` `#521` `#522` `#524` `#527` `#529` `#532` `#535` `#536` `#537`
|
||||
|
||||
---
|
||||
|
||||
### 🔀 Community PRs Merged
|
||||
|
||||
| PR | Author | Summary |
|
||||
| -------- | ------------ | ---------------------------------------------------------------------- |
|
||||
| **#530** | @kang-heewon | OpenCode Zen + Go providers with `OpencodeExecutor` and improved tests |
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.7] - 2026-03-23
|
||||
|
||||
### 🔧 Improvements (sub2api Gap Analysis — T05, T08, T09, T13, T14)
|
||||
|
||||
- **T05** — Rate-limit DB persistence: `setConnectionRateLimitUntil()`, `isConnectionRateLimited()`, `getRateLimitedConnections()` in `providers.ts`. The existing `rate_limited_until` column is now exposed as a dedicated API — OAuth token refresh must NOT touch this field to prevent rate-limit loops.
|
||||
- **T08** — Per-API-key session limit: `max_sessions INTEGER DEFAULT 0` added to `api_keys` via auto-migration. `sessionManager.ts` gains `registerKeySession()`, `unregisterKeySession()`, `checkSessionLimit()`, and `getActiveSessionCountForKey()`. Callers in `chatCore.js` can enforce the limit and decrement on `req.close`.
|
||||
- **T09** — Codex vs Spark rate-limit scopes: `getCodexModelScope()` and `getCodexRateLimitKey()` in `codex.ts`. Standard models (`gpt-5.x-codex`, `codex-mini`) get scope `"codex"`; spark models (`codex-spark*`) get scope `"spark"`. Rate-limit keys should be `${accountId}:${scope}` so exhausting one pool doesn't block the other.
|
||||
- **T13** — Stale quota display fix: `getEffectiveQuotaUsage(used, resetAt)` returns `0` when the reset window has passed; `formatResetCountdown(resetAt)` returns a human-readable countdown string (e.g. `"2h 35m"`). Both exported from `providers.ts` + `localDb.ts` for dashboard consumption.
|
||||
- **T14** — Proxy fast-fail: new `src/lib/proxyHealth.ts` with `isProxyReachable(proxyUrl, timeoutMs=2000)` (TCP check, ≤2s instead of 30s timeout), `getCachedProxyHealth()`, `invalidateProxyHealth()`, and `getAllProxyHealthStatuses()`. Results cached 30s by default; configurable via `PROXY_FAST_FAIL_TIMEOUT_MS` / `PROXY_HEALTH_CACHE_TTL_MS`.
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- Test suite: **832 tests, 0 failures**
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.6] - 2026-03-23
|
||||
|
||||
### 🔧 Bug Fixes & Improvements (sub2api Gap Analysis — T01–T15)
|
||||
|
||||
- **T01** — `requested_model` column in `call_logs` (migration 009): track which model the client originally requested vs the actual routed model. Enables fallback rate analytics.
|
||||
- **T02** — Strip empty text blocks from nested `tool_result.content`: prevents Anthropic 400 errors (`text content blocks must be non-empty`) when Claude Code chains tool results.
|
||||
- **T03** — Parse `x-codex-5h-*` / `x-codex-7d-*` headers: `parseCodexQuotaHeaders()` + `getCodexResetTime()` extract Codex quota windows for precise cooldown scheduling instead of generic 5-min fallback.
|
||||
- **T04** — `X-Session-Id` header for external sticky routing: `extractExternalSessionId()` in `sessionManager.ts` reads `x-session-id` / `x-omniroute-session` headers with `ext:` prefix to avoid collision with internal SHA-256 session IDs. Nginx-compatible (hyphenated header).
|
||||
- **T06** — Account deactivated → permanent block: `isAccountDeactivated()` in `accountFallback.ts` detects 401 deactivation signals and applies a 1-year cooldown to prevent retrying permanently dead accounts.
|
||||
- **T07** — X-Forwarded-For IP validation: new `src/lib/ipUtils.ts` with `extractClientIp()` and `getClientIpFromRequest()` — skips `unknown`/non-IP entries in `X-Forwarded-For` chains (Nginx/proxy-forwarded requests).
|
||||
- **T10** — Credits exhausted → distinct fallback: `isCreditsExhausted()` in `accountFallback.ts` returns 1h cooldown with `creditsExhausted` flag, distinct from generic 429 rate limiting.
|
||||
- **T11** — `max` reasoning effort → 131072 budget tokens: `EFFORT_BUDGETS` and `THINKING_LEVEL_MAP` updated; reverse mapping now returns `"max"` for full-budget responses. Unit test updated.
|
||||
- **T12** — MiniMax M2.7 pricing entries added: `minimax-m2.7`, `MiniMax-M2.7`, `minimax-m2.7-highspeed` added to pricing table (sub2api PR #1120). M2.5/GLM-4.7/GLM-5/Kimi pricing already existed.
|
||||
- **T15** — Array content normalization: `normalizeContentToString()` helper in `openai-to-claude.ts` correctly collapses array-formatted system/tool messages to string before sending to Anthropic.
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- Test suite: **832 tests, 0 failures** (unchanged from rc.5)
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.5] - 2026-03-22
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **#464** — Registered Keys Provisioning API: auto-issue API keys with per-provider & per-account quota enforcement
|
||||
- `POST /api/v1/registered-keys` — issue keys with idempotency support
|
||||
- `GET /api/v1/registered-keys` — list (masked) registered keys
|
||||
- `GET /api/v1/registered-keys/{id}` — get key metadata
|
||||
- `DELETE /api/v1/registered-keys/{id}` / `POST ../{id}/revoke` — revoke keys
|
||||
- `GET /api/v1/quotas/check` — pre-validate before issuing
|
||||
- `PUT /api/v1/providers/{id}/limits` — set provider issuance limits
|
||||
- `PUT /api/v1/accounts/{id}/limits` — set account issuance limits
|
||||
- `POST /api/v1/issues/report` — optional GitHub issue reporting
|
||||
- DB migration 008: `registered_keys`, `provider_key_limits`, `account_key_limits` tables
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.4] - 2026-03-22
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **#530 (PR)** — OpenCode Zen and OpenCode Go providers added (by @kang-heewon)
|
||||
- New `OpencodeExecutor` with multi-format routing (`/chat/completions`, `/messages`, `/responses`)
|
||||
- 7 models across both tiers
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.3] - 2026-03-22
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -26,6 +26,28 @@ _Your universal API proxy — one endpoint, 44+ providers, zero downtime. Now wi
|
||||
|
||||
---
|
||||
|
||||
## 🆕 What's New in v3.0.0
|
||||
|
||||
> **Upgrading from v2.9.5?** — See the [full CHANGELOG](CHANGELOG.md#300--2026-03-22-release-candidate--not-yet-merged-to-main) for all changes.
|
||||
|
||||
| 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 and manual UI toggle to sync model lists for built-in and custom OpenAI-compatible providers |
|
||||
| 🌐 **OpenCode Zen/Go** | Two new providers from @kang-heewon via PR #530: free tier + subscription tier via `OpencodeExecutor` |
|
||||
| 🐛 **Gemini CLI OAuth** | Actionable error when `GEMINI_OAUTH_CLIENT_SECRET` is missing in Docker (was cryptic Google error) |
|
||||
| 🐛 **OpenCode config** | `saveOpenCodeConfig()` now correctly writes TOML to `XDG_CONFIG_HOME` |
|
||||
| 🐛 **Pinned model override** | `body.model` correctly set to `pinnedModel` on context-cache protection |
|
||||
| 🐛 **Codex/Claude loop** | `tool_result` blocks now converted to text to stop infinite loops |
|
||||
| 🐛 **Login redirect** | Login no longer freezes after skipping password setup |
|
||||
| 🐛 **Windows paths** | MSYS2/Git-Bash paths (`/c/...`) normalized to `C:\...` automatically |
|
||||
|
||||
---
|
||||
|
||||
## 🖼️ Main Dashboard
|
||||
|
||||
<div align="center">
|
||||
@@ -236,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
|
||||
@@ -322,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>
|
||||
|
||||
@@ -1216,6 +1238,8 @@ OmniRoute v2.0 is built as an operational platform, not just a relay proxy.
|
||||
| 🎮 **Model Playground** | Test any provider/model/endpoint from the dashboard |
|
||||
| 🔏 **CLI Fingerprint Toggle** | Per-provider fingerprint matching in Settings > Security |
|
||||
| 🌐 **i18n (30 languages)** | Full dashboard + docs language support with RTL coverage |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Custom Data Directory** | `DATA_DIR` override for storage location |
|
||||
|
||||
### Feature Deep Dive
|
||||
|
||||
@@ -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**。
|
||||
|
||||
### 根因 2(P0):强制使用 Webpack 而非 Turbopack
|
||||
|
||||
`scripts/run-next.mjs` 中硬编码了 `--webpack` 标志:
|
||||
|
||||
```javascript
|
||||
if (mode === "dev") {
|
||||
args.splice(2, 0, "--webpack");
|
||||
}
|
||||
```
|
||||
|
||||
Next.js 16 默认使用 Turbopack(Rust 编写的增量打包器),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 | 不会触发 |
|
||||
|
||||
### 根因 3(P1):`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` 中声明为服务端外部包。
|
||||
|
||||
### 根因 4(P1):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+ 条警告重复刷一遍**,严重污染终端输出,干扰开发调试。
|
||||
|
||||
### 根因 5(P2):启动 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 互不依赖,完全可以并行。
|
||||
|
||||
---
|
||||
|
||||
## 三、修复方案
|
||||
|
||||
### 修复 1:globalThis 单例守卫(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:支持通过环境变量切换 Turbopack(run-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 Instrumentation(instrumentation.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。 |
|
||||
|
||||
### 修复 6:bootstrap-env 测试(tests/unit/bootstrap-env.test.mjs)
|
||||
|
||||
在每个用例的 `withTempEnv` 回调开头增加 `process.env.DATA_DIR = dataDir`,使脚本在任意平台(含 Windows)都使用测试临时目录,而不是依赖 `HOME`/`APPDATA`。
|
||||
|
||||
### 修复 7:domain-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` 这类业务关键值,避免把真实累计错误放过去。
|
||||
|
||||
### 修复 8:fixes-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` 重试删除 |
|
||||
@@ -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`。
|
||||
|
||||
**影响**:跨协议场景下用户只能设置一个全局值,无法精确控制。
|
||||
|
||||
### 根因 2(P1):客户端构建拉入 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 模块的路径。
|
||||
|
||||
### 根因 3(P2):前端查找性能
|
||||
|
||||
`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=false,preserveOpenAIDeveloperRole=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:请求管线传入 sourceFormat(chatCore.ts)
|
||||
|
||||
```typescript
|
||||
const normalizeToolCallId = getModelNormalizeToolCallId(
|
||||
provider || "",
|
||||
model || "",
|
||||
sourceFormat // 新增第三参
|
||||
);
|
||||
const preserveDeveloperRole = getModelPreserveOpenAIDeveloperRole(
|
||||
provider || "",
|
||||
model || "",
|
||||
sourceFormat // 新增第三参
|
||||
);
|
||||
```
|
||||
|
||||
`sourceFormat` 由已有的 `detectFormat(body)` 返回,无需新增检测逻辑。
|
||||
|
||||
**优点**:
|
||||
|
||||
- 改动仅 2 行,精准传参。
|
||||
- 不影响其他 handler(embeddings、imageGeneration 等不涉及 developer 角色和 tool call ID)。
|
||||
|
||||
### 修复 3:API 路由支持 compatByProtocol(route.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,不依赖父组件。
|
||||
|
||||
### 修复 7:ModelCompatPatch 类型修正(models.ts)
|
||||
|
||||
```typescript
|
||||
// 修复后 — 显式允许 null
|
||||
export type ModelCompatPatch = {
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean | null; // null = 取消设置/恢复默认
|
||||
compatByProtocol?: CompatByProtocolMap;
|
||||
};
|
||||
```
|
||||
|
||||
与 `mergeModelCompatOverride()` 内的 `=== null` 判断逻辑一致,类型安全。
|
||||
|
||||
### 修复 8:CompatByProtocolMap 类型收紧(page.tsx)
|
||||
|
||||
客户端 `CompatByProtocolMap` 从 `Record<string, ...>` 改为 `Record<ModelCompatProtocolKey, ...>`,增强类型安全,防止传入未知协议键。
|
||||
|
||||
### 修复 9:i18n 文案新增
|
||||
|
||||
| 键名 | 中文 | 英文 |
|
||||
| ------------------------------- | --------------------------------------------- | -------------------------------------------------------------- |
|
||||
| `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.get(useMemo 缓存) |
|
||||
| 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 校验确保不会接受畸形数据。
|
||||
+2
-4
@@ -116,10 +116,8 @@ if (args.includes("--help") || args.includes("-h")) {
|
||||
|
||||
if (args.includes("--version") || args.includes("-v")) {
|
||||
try {
|
||||
const pkg = await import(join(ROOT, "package.json"), {
|
||||
with: { type: "json" },
|
||||
});
|
||||
console.log(pkg.default.version);
|
||||
const { version } = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
|
||||
console.log(version);
|
||||
} catch {
|
||||
console.log("unknown");
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
container_name: omniroute-prod
|
||||
build:
|
||||
context: .
|
||||
target: runner-base
|
||||
target: runner-cli
|
||||
image: omniroute:prod
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
|
||||
+20
-15
@@ -38,15 +38,20 @@ Content-Type: application/json
|
||||
|
||||
### Custom Headers
|
||||
|
||||
| Header | Direction | Description |
|
||||
| ------------------------ | --------- | --------------------------------- |
|
||||
| `X-OmniRoute-No-Cache` | Request | Set to `true` to bypass cache |
|
||||
| `X-OmniRoute-Progress` | Request | Set to `true` for progress events |
|
||||
| `Idempotency-Key` | Request | Dedup key (5s window) |
|
||||
| `X-Request-Id` | Request | Alternative dedup key |
|
||||
| `X-OmniRoute-Cache` | Response | `HIT` or `MISS` (non-streaming) |
|
||||
| `X-OmniRoute-Idempotent` | Response | `true` if deduplicated |
|
||||
| `X-OmniRoute-Progress` | Response | `enabled` if progress tracking on |
|
||||
| Header | Direction | Description |
|
||||
| ------------------------ | --------- | ------------------------------------------------ |
|
||||
| `X-OmniRoute-No-Cache` | Request | Set to `true` to bypass cache |
|
||||
| `X-OmniRoute-Progress` | Request | Set to `true` for progress events |
|
||||
| `X-Session-Id` | Request | Sticky session key for external session affinity |
|
||||
| `x_session_id` | Request | Underscore variant also accepted (direct HTTP) |
|
||||
| `Idempotency-Key` | Request | Dedup key (5s window) |
|
||||
| `X-Request-Id` | Request | Alternative dedup key |
|
||||
| `X-OmniRoute-Cache` | Response | `HIT` or `MISS` (non-streaming) |
|
||||
| `X-OmniRoute-Idempotent` | Response | `true` if deduplicated |
|
||||
| `X-OmniRoute-Progress` | Response | `enabled` if progress tracking on |
|
||||
| `X-OmniRoute-Session-Id` | Response | Effective session ID used by OmniRoute |
|
||||
|
||||
> Nginx note: if you rely on underscore headers (for example `x_session_id`), enable `underscores_in_headers on;`.
|
||||
|
||||
---
|
||||
|
||||
@@ -137,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:
|
||||
@@ -213,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 |
|
||||
@@ -226,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
|
||||
|
||||
@@ -274,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
@@ -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
@@ -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"
|
||||
|
||||
@@ -578,6 +578,22 @@ Configure via **Dashboard → Settings → Routing**.
|
||||
| **Least Used** | Routes to the account with the oldest `lastUsedAt` timestamp, distributing traffic evenly |
|
||||
| **Cost Optimized** | Routes to the account with the lowest priority value, optimizing for lowest-cost providers |
|
||||
|
||||
#### External Sticky Session Header
|
||||
|
||||
For external session affinity (for example, Claude Code/Codex agents behind reverse proxies), send:
|
||||
|
||||
```http
|
||||
X-Session-Id: your-session-key
|
||||
```
|
||||
|
||||
OmniRoute also accepts `x_session_id` and returns the effective session key in `X-OmniRoute-Session-Id`.
|
||||
|
||||
If you use Nginx and send underscore-form headers, enable:
|
||||
|
||||
```nginx
|
||||
underscores_in_headers on;
|
||||
```
|
||||
|
||||
#### Wildcard Model Aliases
|
||||
|
||||
Create wildcard patterns to remap model names:
|
||||
|
||||
+38
-75
@@ -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">
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
[](https://omniroute.online)
|
||||
[](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>
|
||||
|
||||
| صفحة | لقطة شاشة |
|
||||
| --------------------- | -------------------------------------------------- |
|
||||
| ** مقدمو الخدمة ** |  |
|
||||
| **المجموعات** |  |
|
||||
| **تحليلات** |  |
|
||||
| **الصحة** |  |
|
||||
| **مترجم** |  |
|
||||
| **الإعدادات** |  |
|
||||
| **أدوات سطر الأوامر** |  |
|
||||
| **سجلات الاستخدام** |  |
|
||||
| **نقطة النهاية** |  |
|
||||
|
||||
</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,34 @@ _قم بتوصيل أي أداة 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؟
|
||||
|
||||
**توقف عن إهدار المال وضرب الحدود:**
|
||||
@@ -967,14 +928,16 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
|
||||
### ☁️ النشر والمنصة
|
||||
|
||||
| ميزة | ماذا يفعل |
|
||||
| -------------------------------- | ------------------------------------------------ | --- | ------------------------ | ------------------------------- |
|
||||
| 🌐 **النشر في أي مكان** | المضيف المحلي، VPS، Docker، البيئات السحابية | | 💾 **المزامنة السحابية** | مزامنة التكوين عبر عامل السحابة |
|
||||
| 🔄 **النسخ الاحتياطي/الاستعادة** | تدفقات التصدير/الاستيراد والتعافي من الكوارث |
|
||||
| 🧙 **معالج الإعداد** | الإعداد الموجه لأول مرة |
|
||||
| 🔧 **لوحة تحكم أدوات CLI** | إعداد بنقرة واحدة لأدوات الترميز الشائعة |
|
||||
| 🌐 **i18n (30 لغة)** | لوحة تحكم كاملة + دعم لغة المستندات مع تغطية RTL |
|
||||
| 📂 **دليل البيانات المخصصة** | تجاوز `DATA_DIR` لموقع التخزين |
|
||||
| ميزة | ماذا يفعل |
|
||||
| -------------------------------- | --------------------------------------------------- | --- | ------------------------ | ------------------------------- |
|
||||
| 🌐 **النشر في أي مكان** | المضيف المحلي، VPS، Docker، البيئات السحابية | | 💾 **المزامنة السحابية** | مزامنة التكوين عبر عامل السحابة |
|
||||
| 🔄 **النسخ الاحتياطي/الاستعادة** | تدفقات التصدير/الاستيراد والتعافي من الكوارث |
|
||||
| 🧙 **معالج الإعداد** | الإعداد الموجه لأول مرة |
|
||||
| 🔧 **لوحة تحكم أدوات CLI** | إعداد بنقرة واحدة لأدوات الترميز الشائعة |
|
||||
| 🌐 **i18n (30 لغة)** | لوحة تحكم كاملة + دعم لغة المستندات مع تغطية RTL |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **دليل البيانات المخصصة** | تجاوز `DATA_DIR` لموقع التخزين |
|
||||
|
||||
### ميزة الغوص العميق
|
||||
|
||||
|
||||
+30
-67
@@ -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">
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
[](https://omniroute.online)
|
||||
[](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>
|
||||
|
||||
| Страница | Екранна снимка |
|
||||
| -------------------------- | ----------------------------------------------------- |
|
||||
| **Доставчици** |  |
|
||||
| **Комбота** |  |
|
||||
| **Анализ** |  |
|
||||
| **Здраве** |  |
|
||||
| **Преводач** |  |
|
||||
| **Настройки** |  |
|
||||
| **CLI инструменти** |  |
|
||||
| **Регистри за използване** |  |
|
||||
| **Крайна точка** |  |
|
||||
|
||||
</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,34 @@ _Свържете всеки базиран на 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?
|
||||
|
||||
**Спрете да пилеете пари и да достигате лимити:**
|
||||
@@ -976,6 +937,8 @@ OmniRoute v2.0 е създаден като операционна платфо
|
||||
| 🧙 **Съветник за присъединяване** | Насочвана настройка при първо стартиране |
|
||||
| 🔧 **CLI Tools Dashboard** | Настройка с едно щракване за популярни инструменти за кодиране |
|
||||
| 🌐 **i18n (30 езика)** | Пълно табло за управление + езикова поддръжка на документи с RTL покритие |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Директория с персонализирани данни** | `DATA_DIR` отмяна за място за съхранение |
|
||||
|
||||
### Функция Deep Dive
|
||||
|
||||
+115
-115
@@ -38,15 +38,15 @@ Content-Type: application/json
|
||||
|
||||
### Vlastní záhlaví
|
||||
|
||||
Záhlaví | Směr | Popis
|
||||
--- | --- | ---
|
||||
`X-OmniRoute-No-Cache` | Žádost | Nastavením na `true` se vynechá mezipaměť
|
||||
`X-OmniRoute-Progress` | Žádost | Nastaveno na `true` pro události průběhu
|
||||
`Idempotency-Key` | Žádost | Klíč pro deduplikaci (okno 5 s)
|
||||
`X-Request-Id` | Žádost | Alternativní klíč pro odstranění duplicitních dat
|
||||
`X-OmniRoute-Cache` | Odpověď | `HIT` or `MISS` (nestreamované)
|
||||
`X-OmniRoute-Idempotent` | Odpověď | `true` , pokud je odstraněna duplikace
|
||||
`X-OmniRoute-Progress` | Odpověď | `enabled` pokud je zapnuto sledování průběhu
|
||||
| Záhlaví | Směr | Popis |
|
||||
| ------------------------ | ------- | ------------------------------------------------- |
|
||||
| `X-OmniRoute-No-Cache` | Žádost | Nastavením na `true` se vynechá mezipaměť |
|
||||
| `X-OmniRoute-Progress` | Žádost | Nastaveno na `true` pro události průběhu |
|
||||
| `Idempotency-Key` | Žádost | Klíč pro deduplikaci (okno 5 s) |
|
||||
| `X-Request-Id` | Žádost | Alternativní klíč pro odstranění duplicitních dat |
|
||||
| `X-OmniRoute-Cache` | Odpověď | `HIT` or `MISS` (nestreamované) |
|
||||
| `X-OmniRoute-Idempotent` | Odpověď | `true` , pokud je odstraněna duplikace |
|
||||
| `X-OmniRoute-Progress` | Odpověď | `enabled` pokud je zapnuto sledování průběhu |
|
||||
|
||||
---
|
||||
|
||||
@@ -108,18 +108,18 @@ Authorization: Bearer your-api-key
|
||||
|
||||
## Koncové body kompatibility
|
||||
|
||||
Metoda | Cesta | Formát
|
||||
--- | --- | ---
|
||||
ZVEŘEJNIT | `/v1/chat/completions` | OpenAI
|
||||
ZVEŘEJNIT | `/v1/messages` | Antropický
|
||||
ZVEŘEJNIT | `/v1/responses` | Reakce OpenAI
|
||||
ZVEŘEJNIT | `/v1/embeddings` | OpenAI
|
||||
ZVEŘEJNIT | `/v1/images/generations` | OpenAI
|
||||
ZÍSKAT | `/v1/models` | OpenAI
|
||||
ZVEŘEJNIT | `/v1/messages/count_tokens` | Antropický
|
||||
ZÍSKAT | `/v1beta/models` | Blíženci
|
||||
ZVEŘEJNIT | `/v1beta/models/{...path}` | Gemini generuje obsah
|
||||
ZVEŘEJNIT | `/v1/api/chat` | Ollama
|
||||
| Metoda | Cesta | Formát |
|
||||
| ------ | --------------------------- | --------------------- |
|
||||
| POST | `/v1/chat/completions` | OpenAI |
|
||||
| POST | `/v1/messages` | Anthropic |
|
||||
| POST | `/v1/responses` | Reakce OpenAI |
|
||||
| POST | `/v1/embeddings` | OpenAI |
|
||||
| POST | `/v1/images/generations` | OpenAI |
|
||||
| GET | `/v1/models` | OpenAI |
|
||||
| POST | `/v1/messages/count_tokens` | Anthropic |
|
||||
| GET | `/v1beta/models` | Blíženci |
|
||||
| POST | `/v1beta/models/{...path}` | Gemini generuje obsah |
|
||||
| POST | `/v1/api/chat` | Ollama |
|
||||
|
||||
### Vyhrazené trasy poskytovatelů
|
||||
|
||||
@@ -166,154 +166,154 @@ Příklad odpovědi:
|
||||
|
||||
### Ověřování
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/auth/login` | ZVEŘEJNIT | Přihlášení
|
||||
`/api/auth/logout` | ZVEŘEJNIT | Odhlásit se
|
||||
`/api/settings/require-login` | ZÍSKAT/VLOŽIT | Vyžaduje se přepnutí přihlášení
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| ----------------------------- | ------- | ------------------------------- |
|
||||
| `/api/auth/login` | POST | Přihlášení |
|
||||
| `/api/auth/logout` | POST | Odhlásit se |
|
||||
| `/api/settings/require-login` | GET/PUT | Vyžaduje se přepnutí přihlášení |
|
||||
|
||||
### Správa poskytovatelů
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/providers` | ZÍSKAT/ODESLAT | Seznam / vytvoření poskytovatelů
|
||||
`/api/providers/[id]` | ZÍSKAT/VLOŽIT/ODSTRANIT | Správa poskytovatele
|
||||
`/api/providers/[id]/test` | ZVEŘEJNIT | Testovací připojení poskytovatele
|
||||
`/api/providers/[id]/models` | ZÍSKAT | Seznam modelů poskytovatelů
|
||||
`/api/providers/validate` | ZVEŘEJNIT | Ověření konfigurace poskytovatele
|
||||
`/api/provider-nodes*` | Různé | Správa uzlů poskytovatelů
|
||||
`/api/provider-models` | ZÍSKAT/ODESLAT/SMAZAT | Vlastní modely
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| ---------------------------- | --------------- | --------------------------------- |
|
||||
| `/api/providers` | GET/POST | Seznam / vytvoření poskytovatelů |
|
||||
| `/api/providers/[id]` | GET/PUT/DELETE | Správa poskytovatele |
|
||||
| `/api/providers/[id]/test` | POST | Testovací připojení poskytovatele |
|
||||
| `/api/providers/[id]/models` | GET | Seznam modelů poskytovatelů |
|
||||
| `/api/providers/validate` | POST | Ověření konfigurace poskytovatele |
|
||||
| `/api/provider-nodes*` | Různé | Správa uzlů poskytovatelů |
|
||||
| `/api/provider-models` | GET/POST/DELETE | Vlastní modely |
|
||||
|
||||
### Toky OAuth
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/oauth/[provider]/[action]` | Různé | OAuth specifický pro poskytovatele
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| -------------------------------- | ------ | ---------------------------------- |
|
||||
| `/api/oauth/[provider]/[action]` | Různé | OAuth specifický pro poskytovatele |
|
||||
|
||||
### Směrování a konfigurace
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/models/alias` | ZÍSKAT/ODESLAT | Aliasy modelů
|
||||
`/api/models/catalog` | ZÍSKAT | Všechny modely podle poskytovatele + typu
|
||||
`/api/combos*` | Různé | Správa kombinací
|
||||
`/api/keys*` | Různé | Správa klíčů API
|
||||
`/api/pricing` | ZÍSKAT | Cena modelu
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| --------------------- | -------- | ----------------------------------------- |
|
||||
| `/api/models/alias` | GET/POST | Aliasy modelů |
|
||||
| `/api/models/catalog` | GET | Všechny modely podle poskytovatele + typu |
|
||||
| `/api/combos*` | Různé | Správa kombinací |
|
||||
| `/api/keys*` | Různé | Správa klíčů API |
|
||||
| `/api/pricing` | GET | Cena modelu |
|
||||
|
||||
### Využití a analýzy
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/usage/history` | ZÍSKAT | Historie používání
|
||||
`/api/usage/logs` | ZÍSKAT | Protokoly používání
|
||||
`/api/usage/request-logs` | ZÍSKAT | Protokoly na úrovni požadavků
|
||||
`/api/usage/[connectionId]` | ZÍSKAT | Využití na připojení
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| --------------------------- | ------ | ----------------------------- |
|
||||
| `/api/usage/history` | GET | Historie používání |
|
||||
| `/api/usage/logs` | GET | Protokoly používání |
|
||||
| `/api/usage/request-logs` | GET | Protokoly na úrovni požadavků |
|
||||
| `/api/usage/[connectionId]` | GET | Využití na připojení |
|
||||
|
||||
### Nastavení
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/settings` | ZÍSKAT/VLOŽIT | Obecná nastavení
|
||||
`/api/settings/proxy` | ZÍSKAT/VLOŽIT | Konfigurace síťového proxy serveru
|
||||
`/api/settings/proxy/test` | ZVEŘEJNIT | Testovací připojení k proxy serveru
|
||||
`/api/settings/ip-filter` | ZÍSKAT/VLOŽIT | Seznam povolených/blokovaných IP adres
|
||||
`/api/settings/thinking-budget` | ZÍSKAT/VLOŽIT | Zdůvodnění rozpočtu tokenů
|
||||
`/api/settings/system-prompt` | ZÍSKAT/VLOŽIT | Globální systémový výzva
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| ------------------------------- | ------- | -------------------------------------- |
|
||||
| `/api/settings` | GET/PUT | Obecná nastavení |
|
||||
| `/api/settings/proxy` | GET/PUT | Konfigurace síťového proxy serveru |
|
||||
| `/api/settings/proxy/test` | POST | Testovací připojení k proxy serveru |
|
||||
| `/api/settings/ip-filter` | GET/PUT | Seznam povolených/blokovaných IP adres |
|
||||
| `/api/settings/thinking-budget` | GET/PUT | Zdůvodnění rozpočtu tokenů |
|
||||
| `/api/settings/system-prompt` | GET/PUT | Globální systémový výzva |
|
||||
|
||||
### Monitorování
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/sessions` | ZÍSKAT | Sledování aktivních relací
|
||||
`/api/rate-limits` | ZÍSKAT | Limity sazeb na účet
|
||||
`/api/monitoring/health` | ZÍSKAT | Kontrola stavu
|
||||
`/api/cache` | ZÍSKAT/SMAZAT | Statistiky mezipaměti / vymazat
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| ------------------------ | ---------- | ------------------------------- |
|
||||
| `/api/sessions` | GET | Sledování aktivních relací |
|
||||
| `/api/rate-limits` | GET | Limity sazeb na účet |
|
||||
| `/api/monitoring/health` | GET | Kontrola stavu |
|
||||
| `/api/cache` | GET/DELETE | Statistiky mezipaměti / vymazat |
|
||||
|
||||
### Zálohování a export/import
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/db-backups` | ZÍSKAT | Seznam dostupných záloh
|
||||
`/api/db-backups` | DÁT | Vytvořte ruční zálohu
|
||||
`/api/db-backups` | ZVEŘEJNIT | Obnovení z konkrétní zálohy
|
||||
`/api/db-backups/export` | ZÍSKAT | Stáhnout databázi jako soubor .sqlite
|
||||
`/api/db-backups/import` | ZVEŘEJNIT | Nahrajte soubor .sqlite pro nahrazení databáze
|
||||
`/api/db-backups/exportAll` | ZÍSKAT | Stáhnout plnou zálohu jako archiv .tar.gz
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| --------------------------- | ------ | ---------------------------------------------- |
|
||||
| `/api/db-backups` | GET | Seznam dostupných záloh |
|
||||
| `/api/db-backups` | DÁT | Vytvořte ruční zálohu |
|
||||
| `/api/db-backups` | POST | Obnovení z konkrétní zálohy |
|
||||
| `/api/db-backups/export` | GET | Stáhnout databázi jako soubor .sqlite |
|
||||
| `/api/db-backups/import` | POST | Nahrajte soubor .sqlite pro nahrazení databáze |
|
||||
| `/api/db-backups/exportAll` | GET | Stáhnout plnou zálohu jako archiv .tar.gz |
|
||||
|
||||
### Synchronizace s cloudem
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/sync/cloud` | Různé | Operace synchronizace s cloudem
|
||||
`/api/sync/initialize` | ZVEŘEJNIT | Inicializovat synchronizaci
|
||||
`/api/cloud/*` | Různé | Správa cloudu
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| ---------------------- | ------ | ------------------------------- |
|
||||
| `/api/sync/cloud` | Různé | Operace synchronizace s cloudem |
|
||||
| `/api/sync/initialize` | POST | Inicializovat synchronizaci |
|
||||
| `/api/cloud/*` | Různé | Správa cloudu |
|
||||
|
||||
### Nástroje CLI
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/cli-tools/claude-settings` | ZÍSKAT | Stav Clauda CLI
|
||||
`/api/cli-tools/codex-settings` | ZÍSKAT | Stav příkazového řádku Codexu
|
||||
`/api/cli-tools/droid-settings` | ZÍSKAT | Stav příkazového řádku Droidu
|
||||
`/api/cli-tools/openclaw-settings` | ZÍSKAT | Stav rozhraní příkazového řádku OpenClaw
|
||||
`/api/cli-tools/runtime/[toolId]` | ZÍSKAT | Generické běhové prostředí CLI
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| ---------------------------------- | ------ | ---------------------------------------- |
|
||||
| `/api/cli-tools/claude-settings` | GET | Stav Clauda CLI |
|
||||
| `/api/cli-tools/codex-settings` | GET | Stav příkazového řádku Codexu |
|
||||
| `/api/cli-tools/droid-settings` | GET | Stav příkazového řádku Droidu |
|
||||
| `/api/cli-tools/openclaw-settings` | GET | Stav rozhraní příkazového řádku OpenClaw |
|
||||
| `/api/cli-tools/runtime/[toolId]` | GET | Generické běhové prostředí CLI |
|
||||
|
||||
Mezi odpovědi CLI patří: `installed` , `runnable` , `command` , `commandPath` , `runtimeMode` , `reason` .
|
||||
|
||||
### Agenti ACP
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/acp/agents` | ZÍSKAT | Zobrazit seznam všech detekovaných agentů (vestavěných + vlastních) se stavem
|
||||
`/api/acp/agents` | ZVEŘEJNIT | Přidat vlastního agenta nebo obnovit mezipaměť detekce
|
||||
`/api/acp/agents` | VYMAZAT | Odebrání vlastního agenta podle parametru dotazu `id`
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| ----------------- | ------- | ----------------------------------------------------------------------------- |
|
||||
| `/api/acp/agents` | GET | Zobrazit seznam všech detekovaných agentů (vestavěných + vlastních) se stavem |
|
||||
| `/api/acp/agents` | POST | Přidat vlastního agenta nebo obnovit mezipaměť detekce |
|
||||
| `/api/acp/agents` | VYMAZAT | Odebrání vlastního agenta podle parametru dotazu `id` |
|
||||
|
||||
Odpověď GET obsahuje `agents[]` (id, name, binary, version, installed, protocol, isCustom) a `summary` (total, installed, notFound, builtIn, custom).
|
||||
|
||||
### Odolnost a limity rychlosti
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/resilience` | ZÍSKAT/VLOŽIT | Získání/aktualizace profilů odolnosti
|
||||
`/api/resilience/reset` | ZVEŘEJNIT | Resetujte jističe
|
||||
`/api/rate-limits` | ZÍSKAT | Stav limitu sazby na účet
|
||||
`/api/rate-limit` | ZÍSKAT | Konfigurace globálního limitu rychlosti
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| ----------------------- | ------- | --------------------------------------- |
|
||||
| `/api/resilience` | GET/PUT | Získání/aktualizace profilů odolnosti |
|
||||
| `/api/resilience/reset` | POST | Resetujte jističe |
|
||||
| `/api/rate-limits` | GET | Stav limitu sazby na účet |
|
||||
| `/api/rate-limit` | GET | Konfigurace globálního limitu rychlosti |
|
||||
|
||||
### Evals
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/evals` | ZÍSKAT/ODESLAT | Vypsat eval sady / spustit vyhodnocení
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| ------------ | -------- | -------------------------------------- |
|
||||
| `/api/evals` | GET/POST | Vypsat eval sady / spustit vyhodnocení |
|
||||
|
||||
### Zásady
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/policies` | ZÍSKAT/ODESLAT/SMAZAT | Správa směrovacích zásad
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| --------------- | --------------- | ------------------------ |
|
||||
| `/api/policies` | GET/POST/DELETE | Správa směrovacích zásad |
|
||||
|
||||
### Dodržování
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/compliance/audit-log` | ZÍSKAT | Protokol auditu shody (poslední N)
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| --------------------------- | ------ | ---------------------------------- |
|
||||
| `/api/compliance/audit-log` | GET | Protokol auditu shody (poslední N) |
|
||||
|
||||
### v1beta (kompatibilní s Gemini)
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/v1beta/models` | ZÍSKAT | Seznam modelů ve formátu Gemini
|
||||
`/v1beta/models/{...path}` | ZVEŘEJNIT | Koncový bod Gemini `generateContent`
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| -------------------------- | ------ | ------------------------------------ |
|
||||
| `/v1beta/models` | GET | Seznam modelů ve formátu Gemini |
|
||||
| `/v1beta/models/{...path}` | POST | Koncový bod Gemini `generateContent` |
|
||||
|
||||
Tyto koncové body zrcadlí formát API Gemini pro klienty, kteří očekávají nativní kompatibilitu sady Gemini SDK.
|
||||
|
||||
### Interní / systémová API
|
||||
|
||||
Koncový bod | Metoda | Popis
|
||||
--- | --- | ---
|
||||
`/api/init` | ZÍSKAT | Kontrola inicializace aplikace (používá se při prvním spuštění)
|
||||
`/api/tags` | ZÍSKAT | Tagy modelů kompatibilní s Ollamou (pro klienty Ollamy)
|
||||
`/api/restart` | ZVEŘEJNIT | Spustit řádný restart serveru
|
||||
`/api/shutdown` | ZVEŘEJNIT | Spustit řádné vypnutí serveru
|
||||
| Koncový bod | Metoda | Popis |
|
||||
| --------------- | ------ | --------------------------------------------------------------- |
|
||||
| `/api/init` | GET | Kontrola inicializace aplikace (používá se při prvním spuštění) |
|
||||
| `/api/tags` | GET | Tagy modelů kompatibilní s Ollamou (pro klienty Ollamy) |
|
||||
| `/api/restart` | POST | Spustit řádný restart serveru |
|
||||
| `/api/shutdown` | POST | Spustit řádné vypnutí serveru |
|
||||
|
||||
> **Poznámka:** Tyto koncové body používá interně systém nebo pro kompatibilitu s klienty Ollama. Koncoví uživatelé je obvykle nevolají.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
🌐 **Jazyky:** 🇺🇸 [angličtina](ARCHITECTURE.md) | 🇧🇷 [Português (Brazílie)](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) | 🇩🇰 [Dánsko](i18n/da/ARCHITECTURE.md) | 🇫🇮 [Suomi](i18n/fi/ARCHITECTURE.md) | 🇮🇱 [עברית](i18n/he/ARCHITECTURE.md) | 🇭🇺 [maďarština](i18n/hu/ARCHITECTURE.md) | 🇮🇩 [Bahasa Indonésie](i18n/id/ARCHITECTURE.md) | 🇰🇷 [한국어](i18n/ko/ARCHITECTURE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/ARCHITECTURE.md) | 🇳🇱 [Nizozemsko](i18n/nl/ARCHITECTURE.md) | 🇳🇴 [Norsk](i18n/no/ARCHITECTURE.md) | 🇵🇹 [Português (Portugalsko)](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) | 🇵🇭 [Filipínec](i18n/phi/ARCHITECTURE.md) | 🇨🇿 [Čeština](i18n/cs/ARCHITECTURE.md)
|
||||
|
||||
*Poslední aktualizace: 2026-03-04*
|
||||
_Poslední aktualizace: 2026-03-04_
|
||||
|
||||
## Shrnutí pro manažery
|
||||
|
||||
@@ -590,45 +590,45 @@ flowchart LR
|
||||
|
||||
Každý poskytovatel má specializovaný exekutor rozšiřující `BaseExecutor` (v `open-sse/executors/base.ts` ), který zajišťuje vytváření URL adres, konstrukci hlaviček, opakování s exponenciálním odkladem, hooky pro obnovení pověření a orchestrační metodu `execute()` .
|
||||
|
||||
Vykonavatel | Poskytovatel(é) | Speciální manipulace
|
||||
--- | --- | ---
|
||||
`DefaultExecutor` | OpenAI, Claude, Gemini, Qwen, iFlow, OpenRouter, GLM, Kimi, MiniMax, DeepSeek, Groq, xAI, Mistral, Perplexity, Together, Fireworks, Cerebras, Cohere, NVIDIA | Konfigurace dynamické adresy URL/záhlaví pro každého poskytovatele
|
||||
`AntigravityExecutor` | Google Antigravitace | Vlastní ID projektů/relací, analýza Opakování po
|
||||
`CodexExecutor` | Kodex OpenAI | Vkládá systémové instrukce, vynucuje úsilí k uvažování
|
||||
`CursorExecutor` | IDE kurzoru | Protokol ConnectRPC, kódování Protobuf, podepisování požadavků pomocí kontrolního součtu
|
||||
`GithubExecutor` | GitHub Copilot | Aktualizace tokenu Copilot, hlavičky napodobující VSCode
|
||||
`KiroExecutor` | AWS CodeWhisperer/Kiro | Binární formát AWS EventStream → konverze SSE
|
||||
`GeminiCLIExecutor` | Rozhraní příkazového řádku Gemini | Cyklus obnovy tokenu Google OAuth
|
||||
| Vykonavatel | Poskytovatel(é) | Speciální manipulace |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
|
||||
| `DefaultExecutor` | OpenAI, Claude, Gemini, Qwen, iFlow, OpenRouter, GLM, Kimi, MiniMax, DeepSeek, Groq, xAI, Mistral, Perplexity, Together, Fireworks, Cerebras, Cohere, NVIDIA | Konfigurace dynamické adresy URL/záhlaví pro každého poskytovatele |
|
||||
| `AntigravityExecutor` | Google Antigravity | Vlastní ID projektů/relací, analýza Opakování po |
|
||||
| `CodexExecutor` | OpenAI Codex | Vkládá systémové instrukce, vynucuje úsilí k uvažování |
|
||||
| `CursorExecutor` | IDE kurzoru | Protokol ConnectRPC, kódování Protobuf, podepisování požadavků pomocí kontrolního součtu |
|
||||
| `GithubExecutor` | GitHub Copilot | Aktualizace tokenu Copilot, hlavičky napodobující VSCode |
|
||||
| `KiroExecutor` | AWS CodeWhisperer/Kiro | Binární formát AWS EventStream → konverze SSE |
|
||||
| `GeminiCLIExecutor` | Gemini CLI | Cyklus obnovy tokenu Google OAuth |
|
||||
|
||||
Všichni ostatní poskytovatelé (včetně uzlů kompatibilních s vlastními funkcemi) používají `DefaultExecutor` .
|
||||
|
||||
## Matice kompatibility poskytovatelů
|
||||
|
||||
Poskytovatel | Formát | Autorizace | Proud | Nestreamované | Obnovení tokenu | API pro použití
|
||||
--- | --- | --- | --- | --- | --- | ---
|
||||
Claude | Claude | Klíč API / OAuth | ✅ | ✅ | ✅ | ⚠️ Pouze pro administrátory
|
||||
Blíženci | Blíženci | Klíč API / OAuth | ✅ | ✅ | ✅ | ⚠️ Cloudová konzole
|
||||
Rozhraní příkazového řádku Gemini | gemini-cli | OAuth | ✅ | ✅ | ✅ | ⚠️ Cloudová konzole
|
||||
Antigravitace | antigravitace | OAuth | ✅ | ✅ | ✅ | ✅ Plná kvóta API
|
||||
OpenAI | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
Kodex | openai-odpovědi | OAuth | ✅ vynucený | ❌ | ✅ | ✅ Limity sazeb
|
||||
GitHub Copilot | otevřeno | OAuth + token Copilota | ✅ | ✅ | ✅ | ✅ Snímky kvót
|
||||
Kurzor | kurzor | Vlastní kontrolní součet | ✅ | ✅ | ❌ | ❌
|
||||
Kiro | Kiro | OIDC pro jednotné přihlašování AWS | ✅ (Stream událostí) | ❌ | ✅ | ✅ Limity použití
|
||||
Qwen | otevřeno | OAuth | ✅ | ✅ | ✅ | ⚠️ Na vyžádání
|
||||
iFlow | otevřeno | OAuth (základní) | ✅ | ✅ | ✅ | ⚠️ Na vyžádání
|
||||
OpenRouter | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
GLM/Kimi/MiniMax | Claude | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
Hluboké vyhledávání | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
Groq | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
xAI (Grok) | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
Mistral | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
Zmatek | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
Společně s umělou inteligencí | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
Ohňostroj s umělou inteligencí | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
Mozky | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
Soudržný | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
NVIDIA NIM | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌
|
||||
| Poskytovatel | Formát | Autorizace | Proud | Nestreamované | Obnovení tokenu | API pro použití |
|
||||
| ------------------------------ | --------------- | ---------------------------------- | -------------------- | ------------- | --------------- | --------------------------- |
|
||||
| Claude | Claude | Klíč API / OAuth | ✅ | ✅ | ✅ | ⚠️ Pouze pro administrátory |
|
||||
| Blíženci | Blíženci | Klíč API / OAuth | ✅ | ✅ | ✅ | ⚠️ Cloudová konzole |
|
||||
| Gemini CLI | gemini-cli | OAuth | ✅ | ✅ | ✅ | ⚠️ Cloudová konzole |
|
||||
| Antigravity | antigravitace | OAuth | ✅ | ✅ | ✅ | ✅ Plná kvóta API |
|
||||
| OpenAI | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| Kodex | openai-odpovědi | OAuth | ✅ vynucený | ❌ | ✅ | ✅ Limity sazeb |
|
||||
| GitHub Copilot | otevřeno | OAuth + token Copilota | ✅ | ✅ | ✅ | ✅ Snímky kvót |
|
||||
| Kurzor | kurzor | Vlastní kontrolní součet | ✅ | ✅ | ❌ | ❌ |
|
||||
| Kiro | Kiro | OIDC pro jednotné přihlašování AWS | ✅ (Stream událostí) | ❌ | ✅ | ✅ Limity použití |
|
||||
| Qwen | otevřeno | OAuth | ✅ | ✅ | ✅ | ⚠️ Na vyžádání |
|
||||
| iFlow | otevřeno | OAuth (základní) | ✅ | ✅ | ✅ | ⚠️ Na vyžádání |
|
||||
| OpenRouter | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| GLM/Kimi/MiniMax | Claude | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| Hluboké vyhledávání | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| Groq | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| xAI (Grok) | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| Mistral | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| Zmatek | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| Společně s umělou inteligencí | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| Ohňostroj s umělou inteligencí | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| Mozky | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| Soudržný | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
| NVIDIA NIM | otevřeno | Klíč API | ✅ | ✅ | ❌ | ❌ |
|
||||
|
||||
## Pokrytí překladů formátů
|
||||
|
||||
@@ -643,7 +643,7 @@ Cílové formáty zahrnují:
|
||||
|
||||
- Chat/Odpovědi v OpenAI
|
||||
- Claude
|
||||
- Obálka Gemini/Gemini-CLI/Antigravitace
|
||||
- Obálka Gemini/Gemini-CLI/Antigravity
|
||||
- Kiro
|
||||
- Kurzor
|
||||
|
||||
@@ -664,25 +664,25 @@ Další vrstvy zpracování v překladovém kanálu:
|
||||
|
||||
## Podporované koncové body API
|
||||
|
||||
Koncový bod | Formát | Psovod
|
||||
--- | --- | ---
|
||||
`POST /v1/chat/completions` | Chat s OpenAI | `src/sse/handlers/chat.ts`
|
||||
`POST /v1/messages` | Claude Messages | Stejný obslužný program (automaticky detekováno)
|
||||
`POST /v1/responses` | Reakce OpenAI | `open-sse/handlers/responsesHandler.ts`
|
||||
`POST /v1/embeddings` | Vkládání OpenAI | `open-sse/handlers/embeddings.ts`
|
||||
`GET /v1/embeddings` | Seznam modelů | Trasa API
|
||||
`POST /v1/images/generations` | Obrázky OpenAI | `open-sse/handlers/imageGeneration.ts`
|
||||
`GET /v1/images/generations` | Seznam modelů | Trasa API
|
||||
`POST /v1/providers/{provider}/chat/completions` | Chat s OpenAI | Vyhrazené pro každého poskytovatele s ověřováním modelu
|
||||
`POST /v1/providers/{provider}/embeddings` | Vkládání OpenAI | Vyhrazené pro každého poskytovatele s ověřováním modelu
|
||||
`POST /v1/providers/{provider}/images/generations` | Obrázky OpenAI | Vyhrazené pro každého poskytovatele s ověřováním modelu
|
||||
`POST /v1/messages/count_tokens` | Počet žetonů Claude | Trasa API
|
||||
`GET /v1/models` | Seznam modelů OpenAI | Trasa API (chat + vkládání + obrázek + vlastní modely)
|
||||
`GET /api/models/catalog` | Katalog | Všechny modely seskupené podle poskytovatele + typu
|
||||
`POST /v1beta/models/*:streamGenerateContent` | Rodák z Blíženců | Trasa API
|
||||
`GET/PUT/DELETE /api/settings/proxy` | Konfigurace proxy serveru | Konfigurace síťového proxy serveru
|
||||
`POST /api/settings/proxy/test` | Připojení proxy serveru | Koncový bod testu stavu/připojení proxy serveru
|
||||
`GET/POST/DELETE /api/provider-models` | Vlastní modely | Správa vlastních modelů pro každého poskytovatele
|
||||
| Koncový bod | Formát | Psovod |
|
||||
| -------------------------------------------------- | ------------------------- | ------------------------------------------------------- |
|
||||
| `POST /v1/chat/completions` | Chat s OpenAI | `src/sse/handlers/chat.ts` |
|
||||
| `POST /v1/messages` | Claude Messages | Stejný obslužný program (automaticky detekováno) |
|
||||
| `POST /v1/responses` | Reakce OpenAI | `open-sse/handlers/responsesHandler.ts` |
|
||||
| `POST /v1/embeddings` | Vkládání OpenAI | `open-sse/handlers/embeddings.ts` |
|
||||
| `GET /v1/embeddings` | Seznam modelů | Trasa API |
|
||||
| `POST /v1/images/generations` | Obrázky OpenAI | `open-sse/handlers/imageGeneration.ts` |
|
||||
| `GET /v1/images/generations` | Seznam modelů | Trasa API |
|
||||
| `POST /v1/providers/{provider}/chat/completions` | Chat s OpenAI | Vyhrazené pro každého poskytovatele s ověřováním modelu |
|
||||
| `POST /v1/providers/{provider}/embeddings` | Vkládání OpenAI | Vyhrazené pro každého poskytovatele s ověřováním modelu |
|
||||
| `POST /v1/providers/{provider}/images/generations` | Obrázky OpenAI | Vyhrazené pro každého poskytovatele s ověřováním modelu |
|
||||
| `POST /v1/messages/count_tokens` | Počet žetonů Claude | Trasa API |
|
||||
| `GET /v1/models` | Seznam modelů OpenAI | Trasa API (chat + vkládání + obrázek + vlastní modely) |
|
||||
| `GET /api/models/catalog` | Katalog | Všechny modely seskupené podle poskytovatele + typu |
|
||||
| `POST /v1beta/models/*:streamGenerateContent` | Rodák z Blíženců | Trasa API |
|
||||
| `GET/PUT/DELETE /api/settings/proxy` | Konfigurace proxy serveru | Konfigurace síťového proxy serveru |
|
||||
| `POST /api/settings/proxy/test` | Připojení proxy serveru | Koncový bod testu stavu/připojení proxy serveru |
|
||||
| `GET/POST/DELETE /api/provider-models` | Vlastní modely | Správa vlastních modelů pro každého poskytovatele |
|
||||
|
||||
## Obejít obslužnou rutinu
|
||||
|
||||
|
||||
+33
-33
@@ -27,19 +27,19 @@ Claude / Codex / Gemini CLI / OpenCode / Cline / KiloCode / Continue / Kiro CLI
|
||||
|
||||
## Podporované nástroje
|
||||
|
||||
Nástroj | Příkaz | Typ | Metoda instalace
|
||||
--- | --- | --- | ---
|
||||
**Claude Code** | `claude` | Rozhraní příkazového řádku | npm
|
||||
**Kodex OpenAI** | `codex` | Rozhraní příkazového řádku | npm
|
||||
**Rozhraní příkazového řádku Gemini** | `gemini` | Rozhraní příkazového řádku | npm
|
||||
**OpenCode** | `opencode` | Rozhraní příkazového řádku | npm
|
||||
**Cline** | `cline` | Rozšíření CLI + VS kódu | npm
|
||||
**KiloCode** | `kilocode` / `kilo` | Rozšíření CLI + VS kódu | npm
|
||||
**Pokračovat** | průvodce | VS Code ext | VS kód
|
||||
**Kiro CLI** | `kiro-cli` | Rozhraní příkazového řádku | instalační program Curl
|
||||
**Kurzor** | `cursor` | Aplikace pro stolní počítače | Stáhnout
|
||||
**Droid** | webový | Vestavěný agent | OmniRoute
|
||||
**OpenClaw** | webový | Vestavěný agent | OmniRoute
|
||||
| Nástroj | Příkaz | Typ | Instalace |
|
||||
| ---------------- | ------------------- | --------------- | -------------- |
|
||||
| **Claude Code** | `claude` | CLI | npm |
|
||||
| **OpenAI Codex** | `codex` | CLI | npm |
|
||||
| **Gemini CLI** | `gemini` | CLI | npm |
|
||||
| **OpenCode** | `opencode` | CLI | npm |
|
||||
| **Cline** | `cline` | CLI + VS Code | npm |
|
||||
| **KiloCode** | `kilocode` / `kilo` | CLI + VS Code | npm |
|
||||
| **Continue** | průvodce | VS Code ext | VS kód |
|
||||
| **Kiro CLI** | `kiro-cli` | CLI | curl instalace |
|
||||
| **Kurzor** | `cursor` | Aplikace pro PC | Download |
|
||||
| **Droid** | webový | Built-in agent | OmniRoute |
|
||||
| **OpenClaw** | webový | Built-in agent | OmniRoute |
|
||||
|
||||
---
|
||||
|
||||
@@ -136,7 +136,7 @@ EOF
|
||||
|
||||
---
|
||||
|
||||
### Kodex OpenAI
|
||||
### OpenAI Codex
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.codex && cat > ~/.codex/config.yaml << EOF
|
||||
@@ -150,7 +150,7 @@ EOF
|
||||
|
||||
---
|
||||
|
||||
### Rozhraní příkazového řádku Gemini
|
||||
### Gemini CLI
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gemini && cat > ~/.gemini/settings.json << EOF
|
||||
@@ -220,7 +220,7 @@ Nebo použijte dashboard OmniRoute → **CLI Tools → KiloCode → Apply Config
|
||||
|
||||
---
|
||||
|
||||
### Pokračovat (rozšíření kódu VS)
|
||||
### Continue (rozšíření kódu VS)
|
||||
|
||||
Upravit `~/.continue/config.yaml` :
|
||||
|
||||
@@ -286,28 +286,28 @@ Ovládací panel OmniRoute automatizuje konfiguraci většiny nástrojů:
|
||||
|
||||
## Dostupné koncové body API
|
||||
|
||||
Koncový bod | Popis | Použití pro
|
||||
--- | --- | ---
|
||||
`/v1/chat/completions` | Standardní chat (všichni poskytovatelé) | Všechny moderní nástroje
|
||||
`/v1/responses` | API pro odpovědi (formát OpenAI) | Kodex, agentické pracovní postupy
|
||||
`/v1/completions` | Doplňování starších textů | Starší nástroje používající `prompt:`
|
||||
`/v1/embeddings` | Vkládání textu | RAG, vyhledávání
|
||||
`/v1/images/generations` | Generování obrázků | DALL-E, Flux atd.
|
||||
`/v1/audio/speech` | Převod textu na řeč | ElevenLabs, OpenAI TTS
|
||||
`/v1/audio/transcriptions` | Převod řeči na text | Deepgram, AssemblyAI
|
||||
| Koncový bod | Popis | Použití pro |
|
||||
| -------------------------- | --------------------------------------- | ------------------------------------- |
|
||||
| `/v1/chat/completions` | Standardní chat (všichni poskytovatelé) | Všechny moderní nástroje |
|
||||
| `/v1/responses` | API pro odpovědi (formát OpenAI) | Kodex, agentické pracovní postupy |
|
||||
| `/v1/completions` | Doplňování starších textů | Starší nástroje používající `prompt:` |
|
||||
| `/v1/embeddings` | Vkládání textu | RAG, vyhledávání |
|
||||
| `/v1/images/generations` | Generování obrázků | DALL-E, Flux atd. |
|
||||
| `/v1/audio/speech` | Převod textu na řeč | ElevenLabs, OpenAI TTS |
|
||||
| `/v1/audio/transcriptions` | Převod řeči na text | Deepgram, AssemblyAI |
|
||||
|
||||
---
|
||||
|
||||
## Odstraňování problémů
|
||||
|
||||
Chyba | Příčina | Opravit
|
||||
--- | --- | ---
|
||||
`Connection refused` | OmniRoute neběží | `pm2 start omniroute`
|
||||
`401 Unauthorized` | Chybný klíč API | Zkontrolovat `/dashboard/api-manager`
|
||||
`No combo configured` | Žádná aktivní routingová kombinace | Nastavení v `/dashboard/combos`
|
||||
`invalid model` | Model není v katalogu | Použijte `auto` nebo zkontrolujte `/dashboard/providers`
|
||||
CLI zobrazuje „není nainstalováno“ | Binární soubor není v cestě PATH | Zkontrolujte, `which <command>`
|
||||
`kiro-cli: not found` | Není v PATH | `export PATH="$HOME/.local/bin:$PATH"`
|
||||
| Chyba | Příčina | Opravit |
|
||||
| ---------------------------------- | ---------------------------------- | -------------------------------------------------------- |
|
||||
| `Connection refused` | OmniRoute neběží | `pm2 start omniroute` |
|
||||
| `401 Unauthorized` | Chybný klíč API | Zkontrolovat `/dashboard/api-manager` |
|
||||
| `No combo configured` | Žádná aktivní routingová kombinace | Nastavení v `/dashboard/combos` |
|
||||
| `invalid model` | Model není v katalogu | Použijte `auto` nebo zkontrolujte `/dashboard/providers` |
|
||||
| CLI zobrazuje „není nainstalováno“ | Binární soubor není v cestě PATH | Zkontrolujte, `which <command>` |
|
||||
| `kiro-cli: not found` | Není v PATH | `export PATH="$HOME/.local/bin:$PATH"` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -110,14 +110,14 @@ omniroute/
|
||||
|
||||
Jediný **zdroj pravdivých informací** pro všechny konfigurace poskytovatelů.
|
||||
|
||||
Soubor | Účel
|
||||
--- | ---
|
||||
`constants.ts` | Objekt `PROVIDERS` se základními URL adresami, přihlašovacími údaji OAuth (výchozí), záhlavími a výchozími systémovými výzvami pro každého poskytovatele. Definuje také `HTTP_STATUS` , `ERROR_TYPES` , `COOLDOWN_MS` , `BACKOFF_CONFIG` a `SKIP_PATTERNS` .
|
||||
`credentialLoader.ts` | Načte externí přihlašovací údaje z `data/provider-credentials.json` a sloučí je s pevně zakódovanými výchozími hodnotami v `PROVIDERS` . Uchovává tajné údaje mimo kontrolu zdrojového kódu a zároveň zachovává zpětnou kompatibilitu.
|
||||
`providerModels.ts` | Centrální registr modelů: mapuje aliasy poskytovatelů → ID modelů. Funkce jako `getModels()` , `getProviderByAlias()` .
|
||||
`codexInstructions.ts` | Systémové instrukce vložené do požadavků Codexu (omezení úprav, pravidla sandboxu, zásady schvalování).
|
||||
`defaultThinkingSignature.ts` | Výchozí „myšlenkové“ podpisy pro modely Claude a Gemini.
|
||||
`ollamaModels.ts` | Definice schématu pro lokální Ollama modely (název, velikost, rodina, kvantizace).
|
||||
| Soubor | Účel |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `constants.ts` | Objekt `PROVIDERS` se základními URL adresami, přihlašovacími údaji OAuth (výchozí), záhlavími a výchozími systémovými výzvami pro každého poskytovatele. Definuje také `HTTP_STATUS` , `ERROR_TYPES` , `COOLDOWN_MS` , `BACKOFF_CONFIG` a `SKIP_PATTERNS` . |
|
||||
| `credentialLoader.ts` | Načte externí přihlašovací údaje z `data/provider-credentials.json` a sloučí je s pevně zakódovanými výchozími hodnotami v `PROVIDERS` . Uchovává tajné údaje mimo kontrolu zdrojového kódu a zároveň zachovává zpětnou kompatibilitu. |
|
||||
| `providerModels.ts` | Centrální registr modelů: mapuje aliasy poskytovatelů → ID modelů. Funkce jako `getModels()` , `getProviderByAlias()` . |
|
||||
| `codexInstructions.ts` | Systémové instrukce vložené do požadavků Codexu (omezení úprav, pravidla sandboxu, zásady schvalování). |
|
||||
| `defaultThinkingSignature.ts` | Výchozí „myšlenkové“ podpisy pro modely Claude a Gemini. |
|
||||
| `ollamaModels.ts` | Definice schématu pro lokální Ollama modely (název, velikost, rodina, kvantizace). |
|
||||
|
||||
#### Postup načítání přihlašovacích údajů
|
||||
|
||||
@@ -194,17 +194,17 @@ classDiagram
|
||||
BaseExecutor <|-- GithubExecutor
|
||||
```
|
||||
|
||||
Vykonavatel | Poskytovatel | Klíčové specializace
|
||||
--- | --- | ---
|
||||
`base.ts` | — | Abstraktní základ: tvorba URL adres, hlavičky, logika opakování, aktualizace přihlašovacích údajů
|
||||
`default.ts` | Claude, Gemini, OpenAI, GLM, Kimi, MiniMax | Aktualizace generického tokenu OAuth pro standardní poskytovatele
|
||||
`antigravity.ts` | Kód Google Cloud | Generování ID projektu/relace, záložní více URL adres, vlastní analýza opakovaných pokusů z chybových zpráv („reset po 2h7m23s“)
|
||||
`cursor.ts` | IDE kurzoru | **Nejsložitější** : autorizace kontrolního součtu SHA-256, kódování požadavků Protobuf, analýza binárních EventStream → SSE odpovědí
|
||||
`codex.ts` | Kodex OpenAI | Vkládá systémové instrukce, spravuje úrovně myšlení, odstraňuje nepodporované parametry
|
||||
`gemini-cli.ts` | Rozhraní příkazového řádku Google Gemini | Vytvoření vlastní URL adresy ( `streamGenerateContent` ), aktualizace tokenu Google OAuth
|
||||
`github.ts` | GitHub Copilot | Systém duálních tokenů (GitHub OAuth + Copilot token), napodobování hlaviček VSCode
|
||||
`kiro.ts` | AWS CodeWhisperer | Binární parsování AWS EventStream, rámce událostí AMZN, odhad tokenů
|
||||
`index.ts` | — | Továrna: název poskytovatele map → třída exekutoru s výchozím záložním nastavením
|
||||
| Vykonavatel | Poskytovatel | Klíčové specializace |
|
||||
| ---------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `base.ts` | — | Abstraktní základ: tvorba URL adres, hlavičky, logika opakování, aktualizace přihlašovacích údajů |
|
||||
| `default.ts` | Claude, Gemini, OpenAI, GLM, Kimi, MiniMax | Aktualizace generického tokenu OAuth pro standardní poskytovatele |
|
||||
| `antigravity.ts` | Kód Google Cloud | Generování ID projektu/relace, záložní více URL adres, vlastní analýza opakovaných pokusů z chybových zpráv („reset po 2h7m23s“) |
|
||||
| `cursor.ts` | IDE kurzoru | **Nejsložitější** : autorizace kontrolního součtu SHA-256, kódování požadavků Protobuf, analýza binárních EventStream → SSE odpovědí |
|
||||
| `codex.ts` | OpenAI Codex | Vkládá systémové instrukce, spravuje úrovně myšlení, odstraňuje nepodporované parametry |
|
||||
| `gemini-cli.ts` | Google Gemini CLI | Vytvoření vlastní URL adresy ( `streamGenerateContent` ), aktualizace tokenu Google OAuth |
|
||||
| `github.ts` | GitHub Copilot | Systém duálních tokenů (GitHub OAuth + Copilot token), napodobování hlaviček VSCode |
|
||||
| `kiro.ts` | AWS CodeWhisperer | Binární parsování AWS EventStream, rámce událostí AMZN, odhad tokenů |
|
||||
| `index.ts` | — | Továrna: název poskytovatele map → třída exekutoru s výchozím záložním nastavením |
|
||||
|
||||
---
|
||||
|
||||
@@ -212,12 +212,12 @@ Vykonavatel | Poskytovatel | Klíčové specializace
|
||||
|
||||
**Orchestrační vrstva** – koordinuje překlad, provádění, streamování a zpracování chyb.
|
||||
|
||||
Soubor | Účel
|
||||
--- | ---
|
||||
`chatCore.ts` | **Centrální orchestrátor** (~600 řádků). Zvládá kompletní životní cyklus požadavku: detekce formátu → překlad → odeslání exekutoru → streamovaná/nestreamovaná odpověď → aktualizace tokenu → zpracování chyb → protokolování využití.
|
||||
`responsesHandler.ts` | Adaptér pro OpenAI Responses API: převádí formát odpovědí → Dokončení chatu → odesílá do `chatCore` → převádí SSE zpět do formátu odpovědí.
|
||||
`embeddings.ts` | Obslužná rutina generování embeddingu: řeší model embeddingu → poskytovatele, odesílá do API poskytovatele, vrací odpověď na embedding kompatibilní s OpenAI. Podporuje 6+ poskytovatelů.
|
||||
`imageGeneration.ts` | Obslužná rutina generování obrázků: řeší model obrázku → poskytovatele, podporuje režimy kompatibilní s OpenAI, Gemini-image (Antigravity) a fallback (Nebius). Vrací obrázky v base64 nebo URL.
|
||||
| Soubor | Účel |
|
||||
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `chatCore.ts` | **Centrální orchestrátor** (~600 řádků). Zvládá kompletní životní cyklus požadavku: detekce formátu → překlad → odeslání exekutoru → streamovaná/nestreamovaná odpověď → aktualizace tokenu → zpracování chyb → protokolování využití. |
|
||||
| `responsesHandler.ts` | Adaptér pro OpenAI Responses API: převádí formát odpovědí → Dokončení chatu → odesílá do `chatCore` → převádí SSE zpět do formátu odpovědí. |
|
||||
| `embeddings.ts` | Obslužná rutina generování embeddingu: řeší model embeddingu → poskytovatele, odesílá do API poskytovatele, vrací odpověď na embedding kompatibilní s OpenAI. Podporuje 6+ poskytovatelů. |
|
||||
| `imageGeneration.ts` | Obslužná rutina generování obrázků: řeší model obrázku → poskytovatele, podporuje režimy kompatibilní s OpenAI, Gemini-image (Antigravity) a fallback (Nebius). Vrací obrázky v base64 nebo URL. |
|
||||
|
||||
#### Životní cyklus požadavku (chatCore.ts)
|
||||
|
||||
@@ -262,22 +262,22 @@ sequenceDiagram
|
||||
|
||||
Obchodní logika, která podporuje obslužné rutiny a vykonavatele.
|
||||
|
||||
Soubor | Účel
|
||||
--- | ---
|
||||
`provider.ts` | **Detekce formátu** ( `detectFormat` ): analyzuje strukturu těla požadavku a identifikuje formáty Claude/OpenAI/Gemini/Antigravity/Responses (včetně heuristiky `max_tokens` pro Claude). Dále: tvorba URL, tvorba hlaviček, normalizace konfigurace thinking. Podporuje dynamické poskytovatele kompatibilní `openai-compatible-*` a `anthropic-compatible-*` .
|
||||
`model.ts` | Analýza řetězců modelu ( `claude/model-name` → `{provider: "claude", model: "model-name"}` ), rozlišení aliasů s detekcí kolizí, sanitizace vstupu (odmítá průchod cestou/řídicí znaky) a rozlišení informací o modelu s podporou asynchronních metod pro získávání aliasů.
|
||||
`accountFallback.ts` | Ovládání limitů rychlosti: exponenciální upomínka (1 s → 2 s → 4 s → max. 2 min), správa doby zpoždění účtu, klasifikace chyb (které chyby spouštějí fallback a které ne).
|
||||
`tokenRefresh.ts` | Aktualizace tokenu OAuth pro **všechny poskytovatele** : Google (Gemini, Antigravity), Claude, Codex, Qwen, iFlow, GitHub (duální token OAuth + Copilot), Kiro (AWS SSO OIDC + sociální ověřování). Zahrnuje mezipaměť deduplikace promise za provozu a opakování s exponenciálním zpožděním.
|
||||
`combo.ts` | **Kombinované modely** : řetězce záložních modelů. Pokud model A selže s chybou způsobilou pro záložní model, zkuste model B, poté C atd. Vrací skutečné stavové kódy upstreamu.
|
||||
`usage.ts` | Načítá data o kvótách/využití z API poskytovatelů (kvóty GitHub Copilot, kvóty modelu Antigravity, limity rychlosti Codexu, rozpisy využití Kiro, nastavení Claude).
|
||||
`accountSelector.ts` | Inteligentní výběr účtu s algoritmem bodování: pro výběr optimálního účtu pro každý požadavek se zohledňuje priorita, zdravotní stav, pozice v systému round robin a stav ochlazování.
|
||||
`contextManager.ts` | Správa životního cyklu kontextu požadavku: vytváří a sleduje objekty kontextu pro každý požadavek s metadaty (ID požadavku, časová razítka, informace o poskytovateli) pro ladění a protokolování.
|
||||
`ipFilter.ts` | Řízení přístupu založené na IP adrese: podporuje režimy povolených seznamů a blokovaných seznamů. Před zpracováním požadavků API ověřuje IP adresu klienta podle nakonfigurovaných pravidel.
|
||||
`sessionManager.ts` | Sledování relací s otisky prstů klientů: sleduje aktivní relace pomocí hašovaných identifikátorů klientů, monitoruje počty požadavků a poskytuje metriky relací.
|
||||
`signatureCache.ts` | Mezipaměť deduplikace na základě signatur požadavků: zabraňuje duplicitním požadavkům ukládáním nedávných signatur požadavků do mezipaměti a vrácením odpovědí z mezipaměti pro identické požadavky v rámci časového okna.
|
||||
`systemPrompt.ts` | Globální vložení systémového výzvy: přidá konfigurovatelnou systémovou výzvu ke všem požadavkům s možností kompatibility pro jednotlivé poskytovatele.
|
||||
`thinkingBudget.ts` | Správa rozpočtu tokenů uvažování: podporuje režimy průchodu, automatický (konfigurace strip thinking), vlastní (pevný rozpočet) a adaptivní (měřítko složitosti) pro řízení tokenů myšlení/uvažování.
|
||||
`wildcardRouter.ts` | Směrování podle vzorů zástupných znaků: rozpoznává vzory zástupných znaků (např. `*/claude-*` ) na konkrétní páry poskytovatel/model na základě dostupnosti a priority.
|
||||
| Soubor | Účel |
|
||||
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `provider.ts` | **Detekce formátu** ( `detectFormat` ): analyzuje strukturu těla požadavku a identifikuje formáty Claude/OpenAI/Gemini/Antigravity/Responses (včetně heuristiky `max_tokens` pro Claude). Dále: tvorba URL, tvorba hlaviček, normalizace konfigurace thinking. Podporuje dynamické poskytovatele kompatibilní `openai-compatible-*` a `anthropic-compatible-*` . |
|
||||
| `model.ts` | Analýza řetězců modelu ( `claude/model-name` → `{provider: "claude", model: "model-name"}` ), rozlišení aliasů s detekcí kolizí, sanitizace vstupu (odmítá průchod cestou/řídicí znaky) a rozlišení informací o modelu s podporou asynchronních metod pro získávání aliasů. |
|
||||
| `accountFallback.ts` | Ovládání limitů rychlosti: exponenciální upomínka (1 s → 2 s → 4 s → max. 2 min), správa doby zpoždění účtu, klasifikace chyb (které chyby spouštějí fallback a které ne). |
|
||||
| `tokenRefresh.ts` | Aktualizace tokenu OAuth pro **všechny poskytovatele** : Google (Gemini, Antigravity), Claude, Codex, Qwen, iFlow, GitHub (duální token OAuth + Copilot), Kiro (AWS SSO OIDC + sociální ověřování). Zahrnuje mezipaměť deduplikace promise za provozu a opakování s exponenciálním zpožděním. |
|
||||
| `combo.ts` | **Kombinované modely** : řetězce záložních modelů. Pokud model A selže s chybou způsobilou pro záložní model, zkuste model B, poté C atd. Vrací skutečné stavové kódy upstreamu. |
|
||||
| `usage.ts` | Načítá data o kvótách/využití z API poskytovatelů (kvóty GitHub Copilot, kvóty modelu Antigravity, limity rychlosti Codexu, rozpisy využití Kiro, nastavení Claude). |
|
||||
| `accountSelector.ts` | Inteligentní výběr účtu s algoritmem bodování: pro výběr optimálního účtu pro každý požadavek se zohledňuje priorita, zdravotní stav, pozice v systému round robin a stav ochlazování. |
|
||||
| `contextManager.ts` | Správa životního cyklu kontextu požadavku: vytváří a sleduje objekty kontextu pro každý požadavek s metadaty (ID požadavku, časová razítka, informace o poskytovateli) pro ladění a protokolování. |
|
||||
| `ipFilter.ts` | Řízení přístupu založené na IP adrese: podporuje režimy povolených seznamů a blokovaných seznamů. Před zpracováním požadavků API ověřuje IP adresu klienta podle nakonfigurovaných pravidel. |
|
||||
| `sessionManager.ts` | Sledování relací s otisky prstů klientů: sleduje aktivní relace pomocí hašovaných identifikátorů klientů, monitoruje počty požadavků a poskytuje metriky relací. |
|
||||
| `signatureCache.ts` | Mezipaměť deduplikace na základě signatur požadavků: zabraňuje duplicitním požadavkům ukládáním nedávných signatur požadavků do mezipaměti a vrácením odpovědí z mezipaměti pro identické požadavky v rámci časového okna. |
|
||||
| `systemPrompt.ts` | Globální vložení systémového výzvy: přidá konfigurovatelnou systémovou výzvu ke všem požadavkům s možností kompatibility pro jednotlivé poskytovatele. |
|
||||
| `thinkingBudget.ts` | Správa rozpočtu tokenů uvažování: podporuje režimy průchodu, automatický (konfigurace strip thinking), vlastní (pevný rozpočet) a adaptivní (měřítko složitosti) pro řízení tokenů myšlení/uvažování. |
|
||||
| `wildcardRouter.ts` | Směrování podle vzorů zástupných znaků: rozpoznává vzory zástupných znaků (např. `*/claude-*` ) na konkrétní páry poskytovatel/model na základě dostupnosti a priority. |
|
||||
|
||||
#### Deduplikace obnovení tokenů
|
||||
|
||||
@@ -374,13 +374,13 @@ graph TD
|
||||
end
|
||||
```
|
||||
|
||||
Adresář | Soubory | Popis
|
||||
--- | --- | ---
|
||||
`request/` | 8 překladatelů | Převod těl požadavků mezi formáty. Každý soubor se při importu sám zaregistruje pomocí `register(from, to, fn)` .
|
||||
`response/` | 7 překladatelů | Převádí bloky odpovědí streamovaných dat mezi formáty. Zpracovává typy událostí SSE, myšlenkové bloky a volání nástrojů.
|
||||
`helpers/` | 6 pomocníků | Sdílené utility: `claudeHelper` (extrakce systémových prompts, thinking config), `geminiHelper` (mapování částí/obsahu), `openaiHelper` (filtrování formátů), `toolCallHelper` (generování ID, vkládání chybějících odpovědí), `maxTokensHelper` , `responsesApiHelper` .
|
||||
`index.ts` | — | Překladový engine: `translateRequest()` , `translateResponse()` , správa stavu, registr.
|
||||
`formats.ts` | — | Formátovací konstanty: `OPENAI` , `CLAUDE` , `GEMINI` , `ANTIGRAVITY` , `KIRO` , `CURSOR` , `OPENAI_RESPONSES` .
|
||||
| Adresář | Soubory | Popis |
|
||||
| ------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `request/` | 8 překladatelů | Převod těl požadavků mezi formáty. Každý soubor se při importu sám zaregistruje pomocí `register(from, to, fn)` . |
|
||||
| `response/` | 7 překladatelů | Převádí bloky odpovědí streamovaných dat mezi formáty. Zpracovává typy událostí SSE, myšlenkové bloky a volání nástrojů. |
|
||||
| `helpers/` | 6 pomocníků | Sdílené utility: `claudeHelper` (extrakce systémových prompts, thinking config), `geminiHelper` (mapování částí/obsahu), `openaiHelper` (filtrování formátů), `toolCallHelper` (generování ID, vkládání chybějících odpovědí), `maxTokensHelper` , `responsesApiHelper` . |
|
||||
| `index.ts` | — | Překladový engine: `translateRequest()` , `translateResponse()` , správa stavu, registr. |
|
||||
| `formats.ts` | — | Formátovací konstanty: `OPENAI` , `CLAUDE` , `GEMINI` , `ANTIGRAVITY` , `KIRO` , `CURSOR` , `OPENAI_RESPONSES` . |
|
||||
|
||||
#### Klíčový design: Samoregistrující se pluginy
|
||||
|
||||
@@ -397,15 +397,15 @@ import "./request/claude-to-openai.js"; // ← self-registers
|
||||
|
||||
### 4.6 Nástroje ( `open-sse/utils/` )
|
||||
|
||||
Soubor | Účel
|
||||
--- | ---
|
||||
`error.ts` | Vytváření chybové odezvy (formát kompatibilní s OpenAI), parsování chyb v upstreamu, extrakce doby opakování Antigravity z chybových zpráv, streamování chyb SSE.
|
||||
`stream.ts` | **SSE Transform Stream** — základní streamovací kanál. Dva režimy: `TRANSLATE` (plný překlad formátu) a `PASSTHROUGH` (normalizace + extrakce využití). Zpracovává ukládání bloků do vyrovnávací paměti, odhad využití a sledování délky obsahu. Instance kodéru/dekodéru pro každý stream se vyhýbají sdílenému stavu.
|
||||
`streamHelpers.ts` | Nízkoúrovňové utility SSE: `parseSSELine` (tolerantní k bílým znakům), `hasValuableContent` (filtruje prázdné segmenty pro OpenAI/Claude/Gemini), `fixInvalidId` , `formatSSE` (serializace SSE s ohledem na formát s čištěním `perf_metrics` ).
|
||||
`usageTracking.ts` | Extrakce využití tokenů z libovolného formátu (Claude/OpenAI/Gemini/Responses), odhad s oddělenými poměry znaků na token pro jednotlivé nástroje/zprávy, přidání vyrovnávací paměti (bezpečnostní rezerva 2000 tokenů), filtrování polí specifických pro formát, protokolování konzole s barvami ANSI.
|
||||
`requestLogger.ts` | Protokolování požadavků na základě souborů (přihlášení pomocí `ENABLE_REQUEST_LOGS=true` ). Vytváří složky relací s očíslovanými soubory: `1_req_client.json` → `7_res_client.txt` . Veškeré I/O operace jsou asynchronní (aktivní a zapomenutý). Maskuje citlivé hlavičky.
|
||||
`bypassHandler.ts` | Zachycuje specifické vzory z Claude CLI (extrakce názvu, zahřívání, počet) a vrací falešné odpovědi bez volání jakéhokoli poskytovatele. Podporuje streamování i nestreamování. Záměrně omezeno na rozsah Claude CLI.
|
||||
`networkProxy.ts` | Rozpozná URL odchozí proxy pro daného poskytovatele s prioritou: konfigurace specifická pro poskytovatele → globální konfigurace → proměnné prostředí ( `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` ). Podporuje výjimky `NO_PROXY` . Ukládá konfiguraci do mezipaměti po dobu 30 sekund.
|
||||
| Soubor | Účel |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `error.ts` | Vytváření chybové odezvy (formát kompatibilní s OpenAI), parsování chyb v upstreamu, extrakce doby opakování Antigravity z chybových zpráv, streamování chyb SSE. |
|
||||
| `stream.ts` | **SSE Transform Stream** — základní streamovací kanál. Dva režimy: `TRANSLATE` (plný překlad formátu) a `PASSTHROUGH` (normalizace + extrakce využití). Zpracovává ukládání bloků do vyrovnávací paměti, odhad využití a sledování délky obsahu. Instance kodéru/dekodéru pro každý stream se vyhýbají sdílenému stavu. |
|
||||
| `streamHelpers.ts` | Nízkoúrovňové utility SSE: `parseSSELine` (tolerantní k bílým znakům), `hasValuableContent` (filtruje prázdné segmenty pro OpenAI/Claude/Gemini), `fixInvalidId` , `formatSSE` (serializace SSE s ohledem na formát s čištěním `perf_metrics` ). |
|
||||
| `usageTracking.ts` | Extrakce využití tokenů z libovolného formátu (Claude/OpenAI/Gemini/Responses), odhad s oddělenými poměry znaků na token pro jednotlivé nástroje/zprávy, přidání vyrovnávací paměti (bezpečnostní rezerva 2000 tokenů), filtrování polí specifických pro formát, protokolování konzole s barvami ANSI. |
|
||||
| `requestLogger.ts` | Protokolování požadavků na základě souborů (přihlášení pomocí `ENABLE_REQUEST_LOGS=true` ). Vytváří složky relací s očíslovanými soubory: `1_req_client.json` → `7_res_client.txt` . Veškeré I/O operace jsou asynchronní (aktivní a zapomenutý). Maskuje citlivé hlavičky. |
|
||||
| `bypassHandler.ts` | Zachycuje specifické vzory z Claude CLI (extrakce názvu, zahřívání, počet) a vrací falešné odpovědi bez volání jakéhokoli poskytovatele. Podporuje streamování i nestreamování. Záměrně omezeno na rozsah Claude CLI. |
|
||||
| `networkProxy.ts` | Rozpozná URL odchozí proxy pro daného poskytovatele s prioritou: konfigurace specifická pro poskytovatele → globální konfigurace → proměnné prostředí ( `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` ). Podporuje výjimky `NO_PROXY` . Ukládá konfiguraci do mezipaměti po dobu 30 sekund. |
|
||||
|
||||
#### Streamovací kanál SSE
|
||||
|
||||
@@ -449,32 +449,32 @@ logs/
|
||||
|
||||
### 4.7 Aplikační vrstva ( `src/` )
|
||||
|
||||
Adresář | Účel
|
||||
--- | ---
|
||||
`src/app/` | Webové uživatelské rozhraní, trasy API, middleware Express, obslužné rutiny zpětných volání OAuth
|
||||
`src/lib/` | Přístup k databázi ( `localDb.ts` , `usageDb.ts` ), ověřování, sdílení
|
||||
`src/mitm/` | Nástroje proxy typu „man-in-the-middle“ pro zachycení provozu poskytovatelů
|
||||
`src/models/` | Definice modelů databáze
|
||||
`src/shared/` | Obálky kolem funkcí open-sse (provider, stream, error atd.)
|
||||
`src/sse/` | Obslužné rutiny koncových bodů SSE, které propojují knihovnu open-sse s trasami Express
|
||||
`src/store/` | Správa stavu aplikací
|
||||
| Adresář | Účel |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `src/app/` | Webové uživatelské rozhraní, trasy API, middleware Express, obslužné rutiny zpětných volání OAuth |
|
||||
| `src/lib/` | Přístup k databázi ( `localDb.ts` , `usageDb.ts` ), ověřování, sdílení |
|
||||
| `src/mitm/` | Nástroje proxy typu „man-in-the-middle“ pro zachycení provozu poskytovatelů |
|
||||
| `src/models/` | Definice modelů databáze |
|
||||
| `src/shared/` | Obálky kolem funkcí open-sse (provider, stream, error atd.) |
|
||||
| `src/sse/` | Obslužné rutiny koncových bodů SSE, které propojují knihovnu open-sse s trasami Express |
|
||||
| `src/store/` | Správa stavu aplikací |
|
||||
|
||||
#### Významné trasy API
|
||||
|
||||
Trasa | Metody | Účel
|
||||
--- | --- | ---
|
||||
`/api/provider-models` | ZÍSKAT/ODESLAT/SMAZAT | CRUD pro vlastní modely na poskytovatele
|
||||
`/api/models/catalog` | ZÍSKAT | Agregovaný katalog všech modelů (chat, embedding, image, custom) seskupených podle poskytovatele
|
||||
`/api/settings/proxy` | ZÍSKAT/VLOŽIT/ODSTRANIT | Konfigurace hierarchické odchozí proxy ( `global/providers/combos/keys` )
|
||||
`/api/settings/proxy/test` | ZVEŘEJNIT | Ověřuje připojení proxy a vrací veřejnou IP adresu/latenci
|
||||
`/v1/providers/[provider]/chat/completions` | ZVEŘEJNIT | Vyhrazené dokončování chatu pro jednotlivé poskytovatele s ověřováním modelu
|
||||
`/v1/providers/[provider]/embeddings` | ZVEŘEJNIT | Vyhrazené vkládání pro jednotlivé poskytovatele s ověřováním modelu
|
||||
`/v1/providers/[provider]/images/generations` | ZVEŘEJNIT | Vyhrazené generování obrázků pro každého poskytovatele s ověřováním modelu
|
||||
`/api/settings/ip-filter` | ZÍSKAT/VLOŽIT | Správa povolených/blokovaných IP adres
|
||||
`/api/settings/thinking-budget` | ZÍSKAT/VLOŽIT | Konfigurace rozpočtu tokenů zdůvodnění (průchozí/automatická/vlastní/adaptivní)
|
||||
`/api/settings/system-prompt` | ZÍSKAT/VLOŽIT | Globální vložení systémového promptu pro všechny požadavky
|
||||
`/api/sessions` | ZÍSKAT | Sledování a metriky aktivních relací
|
||||
`/api/rate-limits` | ZÍSKAT | Stav limitu sazby na účet
|
||||
| Trasa | Metody | Účel |
|
||||
| --------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `/api/provider-models` | GET/POST/DELETE | CRUD pro vlastní modely na poskytovatele |
|
||||
| `/api/models/catalog` | GET | Agregovaný katalog všech modelů (chat, embedding, image, custom) seskupených podle poskytovatele |
|
||||
| `/api/settings/proxy` | GET/PUT/DELETE | Konfigurace hierarchické odchozí proxy ( `global/providers/combos/keys` ) |
|
||||
| `/api/settings/proxy/test` | POST | Ověřuje připojení proxy a vrací veřejnou IP adresu/latenci |
|
||||
| `/v1/providers/[provider]/chat/completions` | POST | Vyhrazené dokončování chatu pro jednotlivé poskytovatele s ověřováním modelu |
|
||||
| `/v1/providers/[provider]/embeddings` | POST | Vyhrazené vkládání pro jednotlivé poskytovatele s ověřováním modelu |
|
||||
| `/v1/providers/[provider]/images/generations` | POST | Vyhrazené generování obrázků pro každého poskytovatele s ověřováním modelu |
|
||||
| `/api/settings/ip-filter` | GET/PUT | Správa povolených/blokovaných IP adres |
|
||||
| `/api/settings/thinking-budget` | GET/PUT | Konfigurace rozpočtu tokenů zdůvodnění (průchozí/automatická/vlastní/adaptivní) |
|
||||
| `/api/settings/system-prompt` | GET/PUT | Globální vložení systémového promptu pro všechny požadavky |
|
||||
| `/api/sessions` | GET | Sledování a metriky aktivních relací |
|
||||
| `/api/rate-limits` | GET | Stav limitu sazby na účet |
|
||||
|
||||
---
|
||||
|
||||
@@ -512,38 +512,38 @@ K hlášenému využití je přidána vyrovnávací paměť o kapacitě 2000 tok
|
||||
|
||||
## 6. Podporované formáty
|
||||
|
||||
Formát | Směr | Identifikátor
|
||||
--- | --- | ---
|
||||
Dokončení chatu OpenAI | zdroj + cíl | `openai`
|
||||
API pro odpovědi OpenAI | zdroj + cíl | `openai-responses`
|
||||
Antropický Claude | zdroj + cíl | `claude`
|
||||
Google Gemini | zdroj + cíl | `gemini`
|
||||
Rozhraní příkazového řádku Google Gemini | pouze cíl | `gemini-cli`
|
||||
Antigravitace | zdroj + cíl | `antigravity`
|
||||
AWS Kiro | pouze cíl | `kiro`
|
||||
Kurzor | pouze cíl | `cursor`
|
||||
| Formát | Směr | Identifikátor |
|
||||
| ----------------------- | ----------- | ------------------ |
|
||||
| OpenAI Chat Completions | zdroj + cíl | `openai` |
|
||||
| OpenAI Responses API | zdroj + cíl | `openai-responses` |
|
||||
| Anthropic Claude | zdroj + cíl | `claude` |
|
||||
| Google Gemini | zdroj + cíl | `gemini` |
|
||||
| Google Gemini CLI | jen cíl | `gemini-cli` |
|
||||
| Antigravity | zdroj + cíl | `antigravity` |
|
||||
| AWS Kiro | jen cíl | `kiro` |
|
||||
| Cursor | jen cíl | `cursor` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Podporovaní poskytovatelé
|
||||
|
||||
Poskytovatel | Metoda ověřování | Vykonavatel | Klíčové poznámky
|
||||
--- | --- | --- | ---
|
||||
Antropický Claude | Klíč API nebo OAuth | Výchozí | Používá hlavičku `x-api-key`
|
||||
Google Gemini | Klíč API nebo OAuth | Výchozí | Používá hlavičku `x-goog-api-key`
|
||||
Rozhraní příkazového řádku Google Gemini | OAuth | GeminiCLI | Používá koncový bod `streamGenerateContent`
|
||||
Antigravitace | OAuth | Antigravitace | Záložní více URL adres, vlastní analýza opakovaných pokusů
|
||||
OpenAI | Klíč API | Výchozí | Autorizace standardního nosiče
|
||||
Kodex | OAuth | Kodex | Vkládá systémové instrukce, řídí myšlení
|
||||
GitHub Copilot | OAuth + token Copilot | Github | Duální token, napodobování záhlaví VSCode
|
||||
Kiro (AWS) | AWS SSO OIDC nebo sociální sítě | Kiro | Analýza binárního EventStreamu
|
||||
IDE kurzoru | Autorizace kontrolního součtu | Kurzor | Kódování Protobuf, kontrolní součty SHA-256
|
||||
Qwen | OAuth | Výchozí | Standardní ověřování
|
||||
iFlow | OAuth (základní + nosič) | Výchozí | Duální hlavička pro autorizaci
|
||||
OpenRouter | Klíč API | Výchozí | Autorizace standardního nosiče
|
||||
GLM, Kimi, MiniMax | Klíč API | Výchozí | Kompatibilní s Claude, použijte `x-api-key`
|
||||
`openai-compatible-*` | Klíč API | Výchozí | Dynamické: jakýkoli koncový bod kompatibilní s OpenAI
|
||||
`anthropic-compatible-*` | Klíč API | Výchozí | Dynamický: jakýkoli koncový bod kompatibilní s Claude
|
||||
| Poskytovatel | Metoda ověřování | Vykonavatel | Klíčové poznámky |
|
||||
| ------------------------ | ------------------------ | ----------- | -------------------------------------------- |
|
||||
| Anthropic Claude | API klíč nebo OAuth | Výchozí | Používá hlavičku `x-api-key` |
|
||||
| Google Gemini | API klíč nebo OAuth | Výchozí | Používá hlavičku `x-goog-api-key` |
|
||||
| Google Gemini CLI | OAuth | GeminiCLI | Používá koncový bod `streamGenerateContent` |
|
||||
| Antigravity | OAuth | Antigravity | Záložní více URL, analýza opakovaných pokusů |
|
||||
| OpenAI | API klíč | Výchozí | Autorizace standardního nosiče |
|
||||
| Codex | OAuth | Codex | Vkládá systémové instrukce, řídí myšlení |
|
||||
| GitHub Copilot | OAuth + Copilot token | Github | Duální token, napodobování záhlaví VSCode |
|
||||
| Kiro (AWS) | AWS SSO OIDC nebo Social | Kiro | Analýza binárního EventStreamu |
|
||||
| Cursor IDE | Checksum auth | Cursor | Kódování Protobuf, kontrolní součty SHA-256 |
|
||||
| Qwen | OAuth | Výchozí | Standardní ověřování |
|
||||
| iFlow | OAuth (Basic + Bearer) | Výchozí | Duální hlavička pro autorizaci |
|
||||
| OpenRouter | API klíč | Výchozí | Autorizace standardního nosiče |
|
||||
| GLM, Kimi, MiniMax | API klíč | Výchozí | Kompatibilní s Claude, použijte `x-api-key` |
|
||||
| `openai-compatible-*` | API klíč | Výchozí | Dynamické: jakýkoli OpenAI kompatibilní |
|
||||
| `anthropic-compatible-*` | API klíč | Výchozí | Dynamické: jakýkoli Claude kompatibilní |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+65
-137
@@ -18,6 +18,34 @@ _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>
|
||||
@@ -58,9 +86,9 @@ _Připojte libovolný nástroj IDE nebo CLI s umělou inteligencí přes OmniRou
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="110"> <a href="https://github.com/anomalyco/opencode"><img src="./public/providers/opencode.svg" alt="OpenCode" width="48"><br><b>OpenCode</b></a><br> <sub>⭐ 106 tisíc</sub> </td>
|
||||
<td align="center" width="110"> <a href="https://github.com/openai/codex"><img src="./public/providers/codex.png" alt="CLI Codexu" width="48"><br><b>CLI Codexu</b></a><br> <sub>⭐ 60,8 tisíc</sub> </td>
|
||||
<td align="center" width="110"> <a href="https://github.com/openai/codex"><img src="./public/providers/codex.png" alt="Codex CLI" width="48"><br><b>Codex CLI</b></a><br> <sub>⭐ 60,8 tisíc</sub> </td>
|
||||
<td align="center" width="110"> <a href="https://github.com/anthropics/claude-code"><img src="./public/providers/claude.png" alt="Claude Code" width="48"><br><b>Claude Code</b></a><br> <sub>⭐ 67,3 tisíc</sub> </td>
|
||||
<td align="center" width="110"> <a href="https://github.com/google-gemini/gemini-cli"><img src="./public/providers/gemini-cli.png" alt="Rozhraní příkazového řádku Gemini" width="48"><br> <b>Rozhraní příkazového řádku Gemini</b></a><br> <sub>⭐ 94,7 tisíc</sub> </td>
|
||||
<td align="center" width="110"> <a href="https://github.com/google-gemini/gemini-cli"><img src="./public/providers/gemini-cli.png" alt="Gemini CLI" width="48"><br> <b>Gemini CLI</b></a><br> <sub>⭐ 94,7 tisíc</sub> </td>
|
||||
<td align="center" width="110"> <a href="https://github.com/Kilo-Org/kilocode"><img src="./public/providers/kilocode.png" alt="Kilo kód" width="48"><br><b>Kilo kód</b></a><br> <sub>⭐ 15,5 tisíc</sub> </td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -165,7 +193,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
|
||||
@@ -799,29 +827,29 @@ Po minimalizaci se OmniRoute nachází v systémové liště a nabízí rychlé
|
||||
|
||||
## 💰 Přehled cen
|
||||
|
||||
| Úroveň | Poskytovatel | Náklady | Obnovení kvóty | Nejlepší pro |
|
||||
| --------------------------------- | -------------------------------- | ------------------------------------ | ------------------------------------------ | --------------------------------------------------------- |
|
||||
| **💳 PŘEDPLATNÉ** | Claude Code (profesionál) | 20 dolarů měsíčně | 5 hodin + týdně | Již přihlášen/a k odběru |
|
||||
| Kodex (Plus/Pro) | 20–200 USD/měsíc | 5 hodin + týdně | Uživatelé OpenAI |
|
||||
| Rozhraní příkazového řádku Gemini | **UVOLNIT** | 180 tisíc měsíčně + 1 tisíc denně | Každý! |
|
||||
| GitHub Copilot | 10–19 USD/měsíc | Měsíční | Uživatelé GitHubu |
|
||||
| **🔑 KLÍČ API** | NVIDIA NIM | **ZDARMA** (vývoj navždy) | ~40 ot./min | 70+ otevřených modelů |
|
||||
| Mozky | **ZDARMA** (1 milion tok/den) | 60 000 otáček za minutu / 30 ot./min | Nejrychlejší na světě |
|
||||
| Groq | **ZDARMA** (30 ot./min.) | 14,4 tisíc otáček za minutu | Ultrarychlá lama/gema |
|
||||
| DeepSeek V3.2 | 0,27/1,10 USD za 1 milion | Žádný | Nejlepší zdůvodnění ceny a kvality |
|
||||
| xAI Grok-4 Rychlý | **0,20/0,50 USD za 1 milion** 🆕 | Žádný | Nejrychlejší + volání nástroje, ultranízké |
|
||||
| xAI Grok-4 (standardní) | 0,20/1,50 USD za 1 milion 🆕 | Žádný | Vlajková loď Reasoning od xAI |
|
||||
| Mistral | Zkušební verze zdarma + placené | Omezená sazba | Evropská umělá inteligence |
|
||||
| OpenRouter | Platba za použití | Žádný | Více než 100 modelů agregováno. |
|
||||
| **💰 LEVNÉ** | GLM-5 (přes Z.AI) 🆕 | 0,5 USD/1 milion | Denně v 10:00 | Výstup 128 tisíc obrazových bodů, nejnovější vlajková loď |
|
||||
| GLM-4.7 | 0,6 USD/1 milion | Denně v 10:00 | Záloha rozpočtu |
|
||||
| MiniMax M2.5 🆕 | Vstup 0,3 USD/1 milion | 5hodinové válcování | Úvaha + agentní úkoly |
|
||||
| MiniMax M2.1 | 0,2 USD/1 milion | 5hodinové válcování | Nejlevnější varianta |
|
||||
| Kimi K2.5 (Moonshot API) 🆕 | Platba za použití | Žádný | Přímý přístup k Moonshot API |
|
||||
| Kimi K2 | 9 dolarů měsíčně bez závazků | 10 milionů tokenů/měsíc | Předvídatelné náklady |
|
||||
| **🆓 ZDARMA** | iFlow | **0 dolarů** | Neomezený | 5 modelů neomezeně |
|
||||
| Qwen | **0 dolarů** | Neomezený | 4 modely neomezeně |
|
||||
| Kiro | **0 dolarů** | Neomezený | Claude Sonnet/Haiku (tvorce AWS) |
|
||||
| Úroveň | Poskytovatel | Náklady | Obnovení kvóty | Nejlepší pro |
|
||||
| --------------------------- | -------------------------------- | ------------------------------------ | ------------------------------------------ | --------------------------------------------------------- |
|
||||
| **💳 PŘEDPLATNÉ** | Claude Code (profesionál) | 20 dolarů měsíčně | 5 hodin + týdně | Již přihlášen/a k odběru |
|
||||
| Kodex (Plus/Pro) | 20–200 USD/měsíc | 5 hodin + týdně | Uživatelé OpenAI |
|
||||
| Gemini CLI | **UVOLNIT** | 180 tisíc měsíčně + 1 tisíc denně | Každý! |
|
||||
| GitHub Copilot | 10–19 USD/měsíc | Měsíční | Uživatelé GitHubu |
|
||||
| **🔑 KLÍČ API** | NVIDIA NIM | **ZDARMA** (vývoj navždy) | ~40 ot./min | 70+ otevřených modelů |
|
||||
| Mozky | **ZDARMA** (1 milion tok/den) | 60 000 otáček za minutu / 30 ot./min | Nejrychlejší na světě |
|
||||
| Groq | **ZDARMA** (30 ot./min.) | 14,4 tisíc otáček za minutu | Ultrarychlá lama/gema |
|
||||
| DeepSeek V3.2 | 0,27/1,10 USD za 1 milion | Žádný | Nejlepší zdůvodnění ceny a kvality |
|
||||
| xAI Grok-4 Rychlý | **0,20/0,50 USD za 1 milion** 🆕 | Žádný | Nejrychlejší + volání nástroje, ultranízké |
|
||||
| xAI Grok-4 (standardní) | 0,20/1,50 USD za 1 milion 🆕 | Žádný | Vlajková loď Reasoning od xAI |
|
||||
| Mistral | Zkušební verze zdarma + placené | Omezená sazba | Evropská umělá inteligence |
|
||||
| OpenRouter | Platba za použití | Žádný | Více než 100 modelů agregováno. |
|
||||
| **💰 LEVNÉ** | GLM-5 (přes Z.AI) 🆕 | 0,5 USD/1 milion | Denně v 10:00 | Výstup 128 tisíc obrazových bodů, nejnovější vlajková loď |
|
||||
| GLM-4.7 | 0,6 USD/1 milion | Denně v 10:00 | Záloha rozpočtu |
|
||||
| MiniMax M2.5 🆕 | Vstup 0,3 USD/1 milion | 5hodinové válcování | Úvaha + agentní úkoly |
|
||||
| MiniMax M2.1 | 0,2 USD/1 milion | 5hodinové válcování | Nejlevnější varianta |
|
||||
| Kimi K2.5 (Moonshot API) 🆕 | Platba za použití | Žádný | Přímý přístup k Moonshot API |
|
||||
| Kimi K2 | 9 dolarů měsíčně bez závazků | 10 milionů tokenů/měsíc | Předvídatelné náklady |
|
||||
| **🆓 ZDARMA** | iFlow | **0 dolarů** | Neomezený | 5 modelů neomezeně |
|
||||
| Qwen | **0 dolarů** | Neomezený | 4 modely neomezeně |
|
||||
| Kiro | **0 dolarů** | Neomezený | Claude Sonnet/Haiku (tvorce AWS) |
|
||||
|
||||
> 🆕 **Přidány nové modely (březen 2026):** řada Grok-4 Fast za 0,20 USD/0,50 USD/M (benchmarkováno na 1143 ms – o 30 % rychlejší než Gemini 2.5 Flash), GLM-5 přes Z.AI s výstupem 128K, uvažování MiniMax M2.5, aktualizované ceny DeepSeek V3.2, Kimi K2.5 přes Moonshot Direct API.
|
||||
|
||||
@@ -951,31 +979,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í > 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á |
|
||||
@@ -1064,6 +1067,8 @@ OmniRoute v2.0 je navržen jako operační platforma, nikoli pouze jako proxy pr
|
||||
| 🎮 **Modelové hřiště** | Otestujte libovolného poskytovatele/model/koncový bod z řídicího panelu |
|
||||
| 🔏 **Přepínač otisků prstů v příkazovém řádku** | Porovnávání otisků prstů podle poskytovatele v Nastavení > Zabezpečení |
|
||||
| 🌐 **i18n (30 jazyků)** | Plná jazyková podpora dashboardu a dokumentace s psaním zprava doleva |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Adresář vlastních dat** | Přepsání `DATA_DIR` pro umístění úložiště |
|
||||
|
||||
### Hluboký pohled na funkce
|
||||
@@ -1439,7 +1444,7 @@ Settings → Models → Advanced:
|
||||
|
||||
Pro konfiguraci jedním kliknutím použijte stránku **Nástroje CLI** na řídicím panelu nebo ručně upravte soubor `~/.claude/settings.json` .
|
||||
|
||||
### CLI Codexu
|
||||
### Codex CLI
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="http://localhost:20128"
|
||||
@@ -1676,94 +1681,17 @@ Pokud si teď nechcete nastavovat vlastní přihlašovací údaje, můžete stá
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><b>🇧🇷 Versão em Português</b></summary>
|
||||
</details>
|
||||
#### Dočasné řešení (bez vlastních přihlašovacích údajů)
|
||||
|
||||
#### Pokud používáte OAuth pro Antigravity / Gemini CLI?
|
||||
Chcete-li získat přístup k přihlašovacím údajům bez vlastní konfigurace, můžete použít následující postup:
|
||||
|
||||
Ověřeno **Antigravity** a **Gemini CLI** pomocí **Google OAuth 2.0** pro autenticitu. O Google exige que a `redirect_uri` usada no fluxo OAuth seja **exatamente** uma das URIs pre-cadastradas no Google Cloud Console to use.
|
||||
1. OmniRoute otevře URL autorizace Google
|
||||
2. Po autorizaci se Google pokusí přesměrovat na `localhost` (což selže na vzdáleném serveru)
|
||||
3. **Zkopírujte celou URL adresu** z adresního řádku prohlížeče
|
||||
4. Vložte tuto URL adresu do pole zobrazeného v modálním okně připojení OmniRoute
|
||||
5. Klikněte na **„Připojit"**
|
||||
|
||||
Jako credenciais OAuth embutidas no OmniRoute estão cadastradas **apenas para `localhost`** . Quando você acessa o OmniRoute em um servidor remote (ex: `https://omniroute.meuservidor.com` ), o Google rejeita a autenticação com:
|
||||
|
||||
```
|
||||
Error 400: redirect_uri_mismatch
|
||||
```
|
||||
|
||||
#### Řešení: Nakonfigurujte souas próprias credenciais OAuth
|
||||
|
||||
Você precisa criar um **OAuth 2.0 Client ID** no Google Cloud Console com a URI do seu server.
|
||||
|
||||
#### Přejít na přejezd
|
||||
|
||||
**1. Přístup ke službě Google Cloud Console**
|
||||
|
||||
Abra: [https://console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials)
|
||||
|
||||
**2. Crie um novo OAuth 2.0 Client ID**
|
||||
|
||||
- Klikněte na **„+ Vytvořit přihlašovací údaje“** → **„ID klienta OAuth“**
|
||||
- Typ aplikace: **"Webová aplikace"**
|
||||
- Název: escolha qualquer nome (např.: `OmniRoute Remote` )
|
||||
|
||||
**3. Adicione jako autorizované URI pro přesměrování**
|
||||
|
||||
Žádné pole **"URI autorizovaného přesměrování"** , adicione:
|
||||
|
||||
```
|
||||
https://seu-servidor.com/callback
|
||||
```
|
||||
|
||||
> Substitua `seu-servidor.com` pelo domínio ou IP do seu servidor (včetně portu se necessário, např.: `http://45.33.32.156:20128/callback` ).
|
||||
|
||||
**4. Uložte a kopii jako credenciais**
|
||||
|
||||
Após criar, o Google Mostrará o **Client ID** eo **Client Secret** .
|
||||
|
||||
**5. Nakonfigurujte jako variáveis de ambiente**
|
||||
|
||||
No seu `.env` (ou nas variáveis de ambiente do Docker):
|
||||
|
||||
```bash
|
||||
# Para Antigravity:
|
||||
ANTIGRAVITY_OAUTH_CLIENT_ID=seu-client-id.apps.googleusercontent.com
|
||||
ANTIGRAVITY_OAUTH_CLIENT_SECRET=GOCSPX-seu-secret
|
||||
|
||||
# Para Gemini CLI:
|
||||
GEMINI_OAUTH_CLIENT_ID=seu-client-id.apps.googleusercontent.com
|
||||
GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-seu-secret
|
||||
GEMINI_CLI_OAUTH_CLIENT_SECRET=GOCSPX-seu-secret
|
||||
```
|
||||
|
||||
**6. Reinicie o OmniRoute**
|
||||
|
||||
```bash
|
||||
# Se usando npm:
|
||||
npm run dev
|
||||
|
||||
# Se usando Docker:
|
||||
docker restart omniroute
|
||||
```
|
||||
|
||||
**7. Připojte se znovu**
|
||||
|
||||
Řídicí panel → Poskytovatelé → Antigravity (nebo Gemini CLI) → OAuth
|
||||
|
||||
Agora nebo Google redirecionará corretamente para `https://seu-servidor.com/callback` ea autenticação funcionará.
|
||||
|
||||
---
|
||||
|
||||
#### Řešení temporário (sem configurar credenciais próprias)
|
||||
|
||||
Chcete-li získat přístup k kriterii pověření, můžete použít adresu **URL** :
|
||||
|
||||
1. O OmniRoute abrirá a URL autorização Google
|
||||
2. Após você autorizar, nebo Google tentará redirecionar para `localhost` (que falha no servidor remoto)
|
||||
3. **Zkopírujte úplnou** adresu URL prohlížeče do svého prohlížeče (mesmo que a pagina não carregue)
|
||||
4. Cole essa URL no campo que aparece no modal de conexão do OmniRoute
|
||||
5. Klikněte na **„Připojit se“**
|
||||
|
||||
> Toto řešení funguje na základě autorizačního kódu na adrese URL a nezávislého přesměrování přesměrování nebo jiného.
|
||||
> Toto řešení funguje, protože autorizační kód v URL adrese je platný bez ohledu na načtení přesměrovací stránky.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+112
-118
@@ -20,30 +20,30 @@ Kompletní průvodce konfigurací poskytovatelů, vytvářením kombinací, inte
|
||||
|
||||
## 💰 Přehled cen
|
||||
|
||||
Úroveň | Poskytovatel | Náklady | Obnovení kvóty | Nejlepší pro
|
||||
--- | --- | --- | --- | ---
|
||||
**💳 PŘEDPLATNÉ** | Claude Code (profesionál) | 20 dolarů měsíčně | 5 hodin + týdně | Již přihlášen/a k odběru
|
||||
| Kodex (Plus/Pro) | 20–200 USD/měsíc | 5 hodin + týdně | Uživatelé OpenAI
|
||||
| Rozhraní příkazového řádku Gemini | **UVOLNIT** | 180 tisíc měsíčně + 1 tisíc denně | Každý!
|
||||
| GitHub Copilot | 10–19 USD/měsíc | Měsíční | Uživatelé GitHubu
|
||||
**🔑 KLÍČ API** | Hluboké vyhledávání | Platba za použití | Žádný | Laciné uvažování
|
||||
| Groq | Platba za použití | Žádný | Ultrarychlá inference
|
||||
| xAI (Grok) | Platba za použití | Žádný | Grok 4 uvažování
|
||||
| Mistral | Platba za použití | Žádný | Modely hostované v EU
|
||||
| Zmatek | Platba za použití | Žádný | Rozšířené vyhledávání
|
||||
| Společně s umělou inteligencí | Platba za použití | Žádný | Modely s otevřeným zdrojovým kódem
|
||||
| Ohňostroj s umělou inteligencí | Platba za použití | Žádný | Rychlé snímky FLUX
|
||||
| Mozky | Platba za použití | Žádný | Rychlost v měřítku destičky
|
||||
| Soudržný | Platba za použití | Žádný | Příkaz R+ RAG
|
||||
| NVIDIA NIM | Platba za použití | Žádný | Podnikové modely
|
||||
**💰 LEVNÉ** | GLM-4.7 | 0,6 USD/1 milion | Denně v 10:00 | Záloha rozpočtu
|
||||
| MiniMax M2.1 | 0,2 USD/1 milion | 5hodinové válcování | Nejlevnější varianta
|
||||
| Kimi K2 | 9 dolarů měsíčně bez závazků | 10 milionů tokenů/měsíc | Předvídatelné náklady
|
||||
**🆓 ZDARMA** | iFlow | 0 dolarů | Neomezený | 8 modelů zdarma
|
||||
| Qwen | 0 dolarů | Neomezený | 3 modely zdarma
|
||||
| Kiro | 0 dolarů | Neomezený | Claude zdarma
|
||||
| Úroveň | Poskytovatel | Náklady | Obnovení kvóty | Nejlepší pro |
|
||||
| ----------------- | ----------------- | ---------------- | ------------------- | -------------------------- |
|
||||
| **💳 PŘEDPLATNÉ** | Claude Code (pro) | 20 USD měsíc | 5h + týdně | Již přihlášené |
|
||||
| | Kodex (Plus/Pro) | 20–200 USD/měsíc | 5h + týdně | Uživatele OpenAI |
|
||||
| | Gemini CLI | **ZDARMA** | 180K/mo + 1K/den | Každého! |
|
||||
| | GitHub Copilot | 10–19 USD/měsíc | Měsíční | Uživatele GitHubu |
|
||||
| **🔑 KLÍČ API** | DeepSeek | Dle užití | Žádné | Laciné uvažování |
|
||||
| | Groq | Dle užití | Žádné | Ultrarychlá inference |
|
||||
| | xAI (Grok) | Dle užití | Žádné | Grok 4 uvažování |
|
||||
| | Mistral | Dle užití | Žádné | Modely hostované v EU |
|
||||
| | Perplexity | Dle užití | Žádné | Rozšířené vyhledávání |
|
||||
| | Together AI | Dle užití | Žádné | Open Source modely |
|
||||
| | Fireworks AI | Dle užití | Žádné | Rychlé FLUX obrázky |
|
||||
| | Cerebras | Dle užití | Žádné | Rychlost destičkového čipu |
|
||||
| | Cohere | Dle užití | Žádné | Command R+ RAG |
|
||||
| | NVIDIA NIM | Dle užití | Žádné | Podnikové modely |
|
||||
| **💰 LEVNÉ** | GLM-4.7 | $0.6/1M | Denně 10:00 | Levná záloha |
|
||||
| | MiniMax M2.1 | $0.2/1M | 5hodinové válcování | Nejlevnější varianta |
|
||||
| | Kimi K2 | 9 USD měsíc | 10M tokens/měsíc | Předvídatelné náklady |
|
||||
| **🆓 ZDARMA** | iFlow | $0 | Neomezený | 8 modelů zdarma |
|
||||
| | Qwen | $0 | Neomezený | 3 modely zdarma |
|
||||
| | Kiro | $0 | Neomezený | Claude zdarma |
|
||||
|
||||
**💡 Tip pro profesionály:** Začněte s kombinací Gemini CLI (180 tisíc zdarma/měsíc) + iFlow (neomezeně zdarma) = 0 dolarů!
|
||||
**💡 Pro Tip:** Začněte s kombinací Gemini CLI (180K zdarma/měsíc) + iFlow (neomezeně zdarma) = $0!
|
||||
|
||||
---
|
||||
|
||||
@@ -271,7 +271,7 @@ Upravit `~/.claude/config.json` :
|
||||
}
|
||||
```
|
||||
|
||||
### CLI Codexu
|
||||
### Codex CLI
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="http://localhost:20128"
|
||||
@@ -335,7 +335,7 @@ omniroute
|
||||
omniroute --port 3000
|
||||
```
|
||||
|
||||
Rozhraní příkazového řádku automaticky načte `.env` z adresáře `~/.omniroute/.env` nebo `./.env` .
|
||||
CLI automaticky načte `.env` z adresáře `~/.omniroute/.env` nebo `./.env` .
|
||||
|
||||
### Nasazení VPS
|
||||
|
||||
@@ -407,23 +407,23 @@ Informace o režimu integrovaném s hostitelem s binárními soubory CLI nalezne
|
||||
|
||||
### Proměnné prostředí
|
||||
|
||||
Proměnná | Výchozí | Popis
|
||||
--- | --- | ---
|
||||
`JWT_SECRET` | `omniroute-default-secret-change-me` | Tajný klíč podpisu JWT ( **změna v produkčním prostředí** )
|
||||
`INITIAL_PASSWORD` | `123456` | První přihlašovací heslo
|
||||
`DATA_DIR` | `~/.omniroute` | Datový adresář (db, využití, protokoly)
|
||||
`PORT` | výchozí nastavení rámce | Servisní port ( `20128` v příkladech)
|
||||
`HOSTNAME` | výchozí nastavení rámce | Vázat hostitele (Docker má výchozí hodnotu `0.0.0.0` )
|
||||
`NODE_ENV` | výchozí nastavení za běhu | Nastavení `production` pro nasazení
|
||||
`BASE_URL` | `http://localhost:20128` | Interní základní URL na straně serveru
|
||||
`CLOUD_URL` | `https://omniroute.dev` | Základní adresa URL koncového bodu synchronizace s cloudem
|
||||
`API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | Tajný klíč HMAC pro generované klíče API
|
||||
`REQUIRE_API_KEY` | `false` | Vynutit klíč rozhraní Bearer API na `/v1/*`
|
||||
`ENABLE_REQUEST_LOGS` | `false` | Povoluje protokolování požadavků/odpovědí
|
||||
`AUTH_COOKIE_SECURE` | `false` | Vynutit soubor cookie `Secure` ověřování (za reverzní proxy HTTPS)
|
||||
`OMNIROUTE_MEMORY_MB` | `512` | Limit haldy Node.js v MB
|
||||
`PROMPT_CACHE_MAX_SIZE` | `50` | Maximální počet položek mezipaměti výzev
|
||||
`SEMANTIC_CACHE_MAX_SIZE` | `100` | Maximální počet položek sémantické mezipaměti
|
||||
| Proměnná | Výchozí | Popis |
|
||||
| ------------------------- | ------------------------------------ | ------------------------------------------------------------------ |
|
||||
| `JWT_SECRET` | `omniroute-default-secret-change-me` | Tajný klíč podpisu JWT ( **změna v produkčním prostředí** ) |
|
||||
| `INITIAL_PASSWORD` | `123456` | První přihlašovací heslo |
|
||||
| `DATA_DIR` | `~/.omniroute` | Datový adresář (db, využití, protokoly) |
|
||||
| `PORT` | výchozí nastavení rámce | Servisní port ( `20128` v příkladech) |
|
||||
| `HOSTNAME` | výchozí nastavení rámce | Vázat hostitele (Docker má výchozí hodnotu `0.0.0.0` ) |
|
||||
| `NODE_ENV` | výchozí nastavení za běhu | Nastavení `production` pro nasazení |
|
||||
| `BASE_URL` | `http://localhost:20128` | Interní základní URL na straně serveru |
|
||||
| `CLOUD_URL` | `https://omniroute.dev` | Základní adresa URL koncového bodu synchronizace s cloudem |
|
||||
| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | Tajný klíč HMAC pro generované klíče API |
|
||||
| `REQUIRE_API_KEY` | `false` | Vynutit klíč rozhraní Bearer API na `/v1/*` |
|
||||
| `ENABLE_REQUEST_LOGS` | `false` | Povoluje protokolování požadavků/odpovědí |
|
||||
| `AUTH_COOKIE_SECURE` | `false` | Vynutit soubor cookie `Secure` ověřování (za reverzní proxy HTTPS) |
|
||||
| `OMNIROUTE_MEMORY_MB` | `512` | Limit haldy Node.js v MB |
|
||||
| `PROMPT_CACHE_MAX_SIZE` | `50` | Maximální počet položek mezipaměti výzev |
|
||||
| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Maximální počet položek sémantické mezipaměti |
|
||||
|
||||
Úplný přehled proměnných prostředí naleznete v souboru [README](../README.md) .
|
||||
|
||||
@@ -439,7 +439,7 @@ Proměnná | Výchozí | Popis
|
||||
|
||||
**Codex ( `cx/` )** — Plus/Pro: `cx/gpt-5.2-codex` , `cx/gpt-5.1-codex-max`
|
||||
|
||||
**Rozhraní příkazového řádku Gemini ( `gc/` )** — ZDARMA: `gc/gemini-3-flash-preview` , `gc/gemini-2.5-pro`
|
||||
**Gemini CLI ( `gc/` )** — ZDARMA: `gc/gemini-3-flash-preview` , `gc/gemini-2.5-pro`
|
||||
|
||||
**GitHub Copilot ( `gh/` )** : `gh/gpt-5` , `gh/claude-4.5-sonnet`
|
||||
|
||||
@@ -473,9 +473,6 @@ Proměnná | Výchozí | Popis
|
||||
|
||||
**NVIDIA NIM ( `nvidia/` )** : `nvidia/nvidia/llama-3.3-70b-instruct`
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Pokročilé funkce
|
||||
@@ -552,12 +549,12 @@ Vrátí modely seskupené podle poskytovatele s typy ( `chat` , `embedding` , `i
|
||||
|
||||
Přístup přes **Dashboard → Translator** . Ladění a vizualizace toho, jak OmniRoute překládá požadavky API mezi poskytovateli.
|
||||
|
||||
Režim | Účel
|
||||
--- | ---
|
||||
**Dětské hřiště** | Vyberte zdrojový/cílový formát, vložte požadavek a okamžitě si prohlédněte přeložený výstup
|
||||
**Tester chatu** | Odesílejte zprávy živého chatu přes proxy a kontrolujte celý cyklus požadavku/odpovědi
|
||||
**Zkušební stolice** | Spusťte dávkové testy napříč různými kombinacemi formátů pro ověření správnosti překladu
|
||||
**Živý monitor** | Sledujte překlady v reálném čase, jak požadavky procházejí proxy serverem
|
||||
| Režim | Účel |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| **Dětské hřiště** | Vyberte zdrojový/cílový formát, vložte požadavek a okamžitě si prohlédněte přeložený výstup |
|
||||
| **Tester chatu** | Odesílejte zprávy živého chatu přes proxy a kontrolujte celý cyklus požadavku/odpovědi |
|
||||
| **Zkušební stolice** | Spusťte dávkové testy napříč různými kombinacemi formátů pro ověření správnosti překladu |
|
||||
| **Živý monitor** | Sledujte překlady v reálném čase, jak požadavky procházejí proxy serverem |
|
||||
|
||||
**Případy použití:**
|
||||
|
||||
@@ -571,14 +568,14 @@ Režim | Účel
|
||||
|
||||
Konfigurace přes **Dashboard → Nastavení → Routing** .
|
||||
|
||||
Strategie | Popis
|
||||
--- | ---
|
||||
**Nejprve vyplňte** | Používá účty podle priority – primární účet zpracovává všechny požadavky, dokud není k dispozici.
|
||||
**Round Robin** | Cykluje mezi všemi účty s nastavitelným trvalým limitem (výchozí: 3 volání na účet)
|
||||
**P2C (Síla dvou možností)** | Vybere 2 náhodné účty a nasměruje je k tomu zdravějšímu – vyvažuje zátěž s povědomím o zdraví
|
||||
**Náhodný** | Náhodně vybere účet pro každý požadavek pomocí Fisher-Yatesova náhodného výběru.
|
||||
**Nejméně používané** | Směruje k účtu s nejstarším časovým razítkem `lastUsedAt` a rovnoměrně rozděluje provoz.
|
||||
**Optimalizované náklady** | Směruje k účtu s nejnižší prioritou a optimalizuje pro poskytovatele s nejnižšími náklady.
|
||||
| Strategie | Popis |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| **Nejprve vyplňte** | Používá účty podle priority – primární účet zpracovává všechny požadavky, dokud není k dispozici. |
|
||||
| **Round Robin** | Cykluje mezi všemi účty s nastavitelným trvalým limitem (výchozí: 3 volání na účet) |
|
||||
| **P2C (Síla dvou možností)** | Vybere 2 náhodné účty a nasměruje je k tomu zdravějšímu – vyvažuje zátěž s povědomím o zdraví |
|
||||
| **Náhodný** | Náhodně vybere účet pro každý požadavek pomocí Fisher-Yatesova náhodného výběru. |
|
||||
| **Nejméně používané** | Směruje k účtu s nejstarším časovým razítkem `lastUsedAt` a rovnoměrně rozděluje provoz. |
|
||||
| **Optimalizované náklady** | Směruje k účtu s nejnižší prioritou a optimalizuje pro poskytovatele s nejnižšími náklady. |
|
||||
|
||||
#### Aliasy zástupných znaků modelů
|
||||
|
||||
@@ -611,24 +608,21 @@ Konfigurace přes **Dashboard → Settings → Resilience** .
|
||||
OmniRoute implementuje odolnost na úrovni poskytovatele se čtyřmi komponentami:
|
||||
|
||||
1. **Profily poskytovatelů** – Konfigurace pro jednotlivé poskytovatele pro:
|
||||
|
||||
- Práh selhání (počet selhání před otevřením)
|
||||
- Doba zchlazení
|
||||
- Citlivost detekce limitu frekvence
|
||||
- Exponenciální backoff parametry
|
||||
- Práh selhání (počet selhání před otevřením)
|
||||
- Doba zchlazení
|
||||
- Citlivost detekce limitu frekvence
|
||||
- Exponenciální backoff parametry
|
||||
|
||||
2. **Upravitelné limity rychlosti** – Výchozí nastavení na úrovni systému konfigurovatelná na řídicím panelu:
|
||||
|
||||
- **Požadavky za minutu (RPM)** — Maximální počet požadavků za minutu na účet
|
||||
- **Minimální doba mezi požadavky** — Minimální mezera v milisekundách mezi požadavky
|
||||
- **Max. počet souběžných požadavků** — Maximální počet souběžných požadavků na účet
|
||||
- Klikněte na **Upravit** pro úpravu a poté **na Uložit** nebo **Zrušit** . Hodnoty se ukládají prostřednictvím rozhraní API pro odolnost.
|
||||
- **Požadavky za minutu (RPM)** — Maximální počet požadavků za minutu na účet
|
||||
- **Minimální doba mezi požadavky** — Minimální mezera v milisekundách mezi požadavky
|
||||
- **Max. počet souběžných požadavků** — Maximální počet souběžných požadavků na účet
|
||||
- Klikněte na **Upravit** pro úpravu a poté **na Uložit** nebo **Zrušit** . Hodnoty se ukládají prostřednictvím rozhraní API pro odolnost.
|
||||
|
||||
3. **Jistič** – Sleduje poruchy u jednotlivých poskytovatelů a automaticky rozpojuje obvod, když je dosaženo prahové hodnoty:
|
||||
|
||||
- **ZAVŘENO** (v pořádku) – Požadavky probíhají normálně.
|
||||
- **OTEVŘENO** — Poskytovatel je dočasně zablokován po opakovaných selháních
|
||||
- **HALF_OPEN** — Testování, zda se poskytovatel zotavil
|
||||
- **ZAVŘENO** (v pořádku) – Požadavky probíhají normálně.
|
||||
- **OTEVŘENO** — Poskytovatel je dočasně zablokován po opakovaných selháních
|
||||
- **HALF_OPEN** — Testování, zda se poskytovatel zotavil
|
||||
|
||||
4. **Zásady a uzamčené identifikátory** – Zobrazuje stav jističe a uzamčené identifikátory s možností vynuceného odemčení.
|
||||
|
||||
@@ -642,11 +636,11 @@ OmniRoute implementuje odolnost na úrovni poskytovatele se čtyřmi komponentam
|
||||
|
||||
Správa záloh databáze se provádí v **nabídce Ovládací panel → Nastavení → Systém a úložiště** .
|
||||
|
||||
Akce | Popis
|
||||
--- | ---
|
||||
**Exportovat databázi** | Stáhne aktuální databázi SQLite jako soubor `.sqlite`
|
||||
**Exportovat vše (.tar.gz)** | Stáhne kompletní zálohu včetně: databáze, nastavení, kombinací, připojení k poskytovatelům (bez přihlašovacích údajů) a metadat klíče API.
|
||||
**Importovat databázi** | Nahrajte soubor `.sqlite` , který nahradí aktuální databázi. Záloha před importem se vytvoří automaticky.
|
||||
| Akce | Popis |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Exportovat databázi** | Stáhne aktuální databázi SQLite jako soubor `.sqlite` |
|
||||
| **Exportovat vše (.tar.gz)** | Stáhne kompletní zálohu včetně: databáze, nastavení, kombinací, připojení k poskytovatelům (bez přihlašovacích údajů) a metadat klíče API. |
|
||||
| **Importovat databázi** | Nahrajte soubor `.sqlite` , který nahradí aktuální databázi. Záloha před importem se vytvoří automaticky. |
|
||||
|
||||
```bash
|
||||
# API: Export database
|
||||
@@ -674,13 +668,13 @@ curl -X POST http://localhost:20128/api/db-backups/import \
|
||||
|
||||
Stránka nastavení je pro snadnou navigaci uspořádána do 5 záložek:
|
||||
|
||||
Záložka | Obsah
|
||||
--- | ---
|
||||
**Zabezpečení** | Nastavení přihlášení/hesla, řízení přístupu k IP adrese, autorizace API pro `/models` a blokování poskytovatelů
|
||||
**Směrování** | Globální strategie směrování (6 možností), aliasy zástupných znaků, záložní řetězce, kombinované výchozí hodnoty
|
||||
**Odolnost** | Profily poskytovatelů, upravitelné limity sazeb, stav jističů, zásady a uzamčené identifikátory
|
||||
**Umělá inteligence** | Konfigurace rozpočtu promyšleného projektu, globální vkládání promptu do systému, statistiky mezipaměti promptu
|
||||
**Moderní** | Globální konfigurace proxy (HTTP/SOCKS5)
|
||||
| Záložka | Obsah |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| **Zabezpečení** | Nastavení přihlášení/hesla, řízení přístupu k IP adrese, autorizace API pro `/models` a blokování poskytovatelů |
|
||||
| **Směrování** | Globální strategie směrování (6 možností), aliasy zástupných znaků, záložní řetězce, kombinované výchozí hodnoty |
|
||||
| **Odolnost** | Profily poskytovatelů, upravitelné limity sazeb, stav jističů, zásady a uzamčené identifikátory |
|
||||
| **Umělá inteligence** | Konfigurace rozpočtu promyšleného projektu, globální vkládání promptu do systému, statistiky mezipaměti promptu |
|
||||
| **Moderní** | Globální konfigurace proxy (HTTP/SOCKS5) |
|
||||
|
||||
---
|
||||
|
||||
@@ -688,10 +682,10 @@ Záložka | Obsah
|
||||
|
||||
Přístup přes **Dashboard → Náklady** .
|
||||
|
||||
Záložka | Účel
|
||||
--- | ---
|
||||
**Rozpočet** | Nastavte limity útrat pro každý klíč API s denními/týdenními/měsíčními rozpočty a sledováním v reálném čase
|
||||
**Ceny** | Zobrazení a úprava cenových položek modelu – cena za 1000 vstupních/výstupních tokenů na poskytovatele
|
||||
| Záložka | Účel |
|
||||
| ------------ | ----------------------------------------------------------------------------------------------------------- |
|
||||
| **Rozpočet** | Nastavte limity útrat pro každý klíč API s denními/týdenními/měsíčními rozpočty a sledováním v reálném čase |
|
||||
| **Ceny** | Zobrazení a úprava cenových položek modelu – cena za 1000 vstupních/výstupních tokenů na poskytovatele |
|
||||
|
||||
```bash
|
||||
# API: Set a budget
|
||||
@@ -733,14 +727,14 @@ Podporované zvukové formáty: `mp3` , `wav` , `m4a` , `flac` , `ogg` , `webm`
|
||||
|
||||
Nastavte vyvažování jednotlivých kombinací v **nabídce Dashboard → Kombinace → Vytvořit/Upravit → Strategie** .
|
||||
|
||||
Strategie | Popis
|
||||
--- | ---
|
||||
**Round-Robin** | Postupně prochází modely
|
||||
**Přednost** | Vždy se pokusí o první model; vrací se pouze v případě chyby.
|
||||
**Náhodný** | Pro každý požadavek vybere náhodný model z komba
|
||||
**Vážené** | Trasy proporcionálně na základě přiřazených vah pro každý model
|
||||
**Nejméně používané** | Směruje k modelu s nejmenším počtem nedávných požadavků (používá kombinované metriky)
|
||||
**Optimalizované z hlediska nákladů** | Trasy k nejlevnějšímu dostupnému modelu (používá ceník)
|
||||
| Strategie | Popis |
|
||||
| ------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| **Round-Robin** | Postupně prochází modely |
|
||||
| **Přednost** | Vždy se pokusí o první model; vrací se pouze v případě chyby. |
|
||||
| **Náhodný** | Pro každý požadavek vybere náhodný model z komba |
|
||||
| **Vážené** | Trasy proporcionálně na základě přiřazených vah pro každý model |
|
||||
| **Nejméně používané** | Směruje k modelu s nejmenším počtem nedávných požadavků (používá kombinované metriky) |
|
||||
| **Optimalizované z hlediska nákladů** | Trasy k nejlevnějšímu dostupnému modelu (používá ceník) |
|
||||
|
||||
Globální výchozí hodnoty kombinací lze nastavit v **nabídce Dashboard → Settings → Routing → Combo Defaults** .
|
||||
|
||||
@@ -750,14 +744,14 @@ Globální výchozí hodnoty kombinací lze nastavit v **nabídce Dashboard →
|
||||
|
||||
Přístup přes **Dashboard → Stav** . Přehled stavu systému v reálném čase se 6 kartami:
|
||||
|
||||
Karta | Co to ukazuje
|
||||
--- | ---
|
||||
**Stav systému** | Doba provozuschopnosti, verze, využití paměti, datový adresář
|
||||
**Zdraví poskytovatelů** | Stav jističe podle dodavatele (Zapnuto/Vypnuto/Napůl vypnuto)
|
||||
**Limity sazeb** | Aktivní limit rychlosti cooldownů na účet se zbývajícím časem
|
||||
**Aktivní výluky** | Poskytovatelé dočasně blokovaní politikou uzamčení
|
||||
**Mezipaměť podpisů** | Statistiky mezipaměti pro deduplikaci (aktivní klíče, míra zásahů)
|
||||
**Telemetrie latence** | Agregace latence p50/p95/p99 na poskytovatele
|
||||
| Karta | Co to ukazuje |
|
||||
| ------------------------ | ------------------------------------------------------------------ |
|
||||
| **Stav systému** | Doba provozuschopnosti, verze, využití paměti, datový adresář |
|
||||
| **Zdraví poskytovatelů** | Stav jističe podle dodavatele (Zapnuto/Vypnuto/Napůl vypnuto) |
|
||||
| **Limity sazeb** | Aktivní limit rychlosti cooldownů na účet se zbývajícím časem |
|
||||
| **Aktivní výluky** | Poskytovatelé dočasně blokovaní politikou uzamčení |
|
||||
| **Mezipaměť podpisů** | Statistiky mezipaměti pro deduplikaci (aktivní klíče, míra zásahů) |
|
||||
| **Telemetrie latence** | Agregace latence p50/p95/p99 na poskytovatele |
|
||||
|
||||
**Tip pro profesionály:** Stránka Zdraví se automaticky obnovuje každých 10 sekund. Pomocí karty jističe můžete zjistit, kteří poskytovatelé mají problémy.
|
||||
|
||||
@@ -795,20 +789,20 @@ Výstup → `electron/dist-electron/`
|
||||
|
||||
### Klíčové vlastnosti
|
||||
|
||||
Funkce | Popis
|
||||
--- | ---
|
||||
**Připravenost serveru** | Před zobrazením okna se dotazuje server (žádná prázdná obrazovka)
|
||||
**Systémový zásobník** | Minimalizovat do zásobníku, změnit port, ukončit menu v zásobníku
|
||||
**Správa přístavů** | Změna portu serveru z panelu úloh (automatické restartování serveru)
|
||||
**Zásady zabezpečení obsahu** | Omezující CSP prostřednictvím záhlaví relace
|
||||
**Jedna instance** | V daném okamžiku může běžet pouze jedna instance aplikace
|
||||
**Offline režim** | Dodávaný server Next.js funguje bez internetu
|
||||
| Funkce | Popis |
|
||||
| ----------------------------- | -------------------------------------------------------------------- |
|
||||
| **Připravenost serveru** | Před zobrazením okna se dotazuje server (žádná prázdná obrazovka) |
|
||||
| **Systémový zásobník** | Minimalizovat do zásobníku, změnit port, ukončit menu v zásobníku |
|
||||
| **Správa přístavů** | Změna portu serveru z panelu úloh (automatické restartování serveru) |
|
||||
| **Zásady zabezpečení obsahu** | Omezující CSP prostřednictvím záhlaví relace |
|
||||
| **Jedna instance** | V daném okamžiku může běžet pouze jedna instance aplikace |
|
||||
| **Offline režim** | Dodávaný server Next.js funguje bez internetu |
|
||||
|
||||
### Proměnné prostředí
|
||||
|
||||
Proměnná | Výchozí | Popis
|
||||
--- | --- | ---
|
||||
`OMNIROUTE_PORT` | `20128` | Port serveru
|
||||
`OMNIROUTE_MEMORY_MB` | `512` | Limit haldy Node.js (64–16384 MB)
|
||||
| Proměnná | Výchozí | Popis |
|
||||
| --------------------- | ------- | --------------------------------- |
|
||||
| `OMNIROUTE_PORT` | `20128` | Port serveru |
|
||||
| `OMNIROUTE_MEMORY_MB` | `512` | Limit haldy Node.js (64–16384 MB) |
|
||||
|
||||
📖 Úplná dokumentace: [`electron/README.md`](../electron/README.md)
|
||||
|
||||
+30
-67
@@ -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">
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
[](https://omniroute.online)
|
||||
[](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** |  |
|
||||
| **Komboer** |  |
|
||||
| **Analyse** |  |
|
||||
| **Sundhed** |  |
|
||||
| **Oversætter** |  |
|
||||
| **Indstillinger** |  |
|
||||
| **CLI-værktøjer** |  |
|
||||
| **Brugslogfiler** |  |
|
||||
| **Endpunkt** |  |
|
||||
|
||||
</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,34 @@ _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:**
|
||||
@@ -977,6 +938,8 @@ OmniRoute v2.0 er bygget som en operationel platform, ikke kun en relæ-proxy.
|
||||
| 🧙 **Onboarding Wizard** | Første kørsel guidet opsætning |
|
||||
| 🔧 **CLI Tools Dashboard** | Et-klik opsætning til populære kodningsværktøjer |
|
||||
| 🌐 **i18n (30 sprog)** | Fuldt dashboard + understøttelse af docs-sprog med RTL-dækning |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Tilpasset datakatalog** | `DATA_DIR` tilsidesættelse af lagerplacering |
|
||||
|
||||
### Feature Deep Dive
|
||||
|
||||
+30
-67
@@ -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">
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
[](https://omniroute.online)
|
||||
[](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** |  |
|
||||
| **Kombinationen** |  |
|
||||
| **Analytik** |  |
|
||||
| **Gesundheit** |  |
|
||||
| **Übersetzer** |  |
|
||||
| **Einstellungen** |  |
|
||||
| **CLI-Tools** |  |
|
||||
| **Nutzungsprotokolle** |  |
|
||||
| **Endpunkt** |  |
|
||||
|
||||
</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,34 @@ _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 +863,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 |
|
||||
|
||||
| ------------------------------------ | -------------------------------------------------------------------------------- |
|
||||
@@ -981,6 +942,8 @@ OmniRoute v2.0 ist als Betriebsplattform konzipiert und nicht nur als Relay-Prox
|
||||
| 🧙 **Onboarding-Assistent** | Erstmaliges geführtes Setup |
|
||||
| 🔧 **CLI-Tools-Dashboard** | Ein-Klick-Setup für beliebte Codierungstools |
|
||||
| 🌐 **i18n (30 Sprachen)** | Vollständige Sprachunterstützung für Dashboard und Dokumente mit RTL-Abdeckung |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Benutzerdefiniertes Datenverzeichnis** | `DATA_DIR`-Überschreibung für Speicherort |
|
||||
|
||||
### Feature Deep Dive
|
||||
|
||||
+32
-22
@@ -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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -928,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **DB-varmuuskopiot** | Automaattinen varmuuskopiointi, palautus, vienti ja tuonti kaikille asetuksille |
|
||||
| 🌐 **Kansainvälistyminen** | Täysi i18n next-intl:llä — Englanti + portugali (Brasilia) tuki |
|
||||
| 🌍 **Kielenvalitsin** | Maapallokuvake otsikossa reaaliaikaista kielenvaihtoa varten (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Muokattu tietohakemisto** | `DATA_DIR` env var ohittaa oletusarvoisen `~/.omniroute`-tallennuspolun |
|
||||
|
||||
<details>
|
||||
|
||||
+32
-22
@@ -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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
|
||||
+30
-22
@@ -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,34 @@ _חבר כל כלי 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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -927,6 +933,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **גיבויים של DB** | גיבוי, שחזור, ייצוא וייבוא אוטומטי עבור כל ההגדרות |
|
||||
| 🌐 **בינלאומיות** | i18n מלא עם next-intl — תמיכה באנגלית + פורטוגזית (ברזיל) |
|
||||
| 🌍 **בורר שפה** | סמל גלובוס בכותרת למעבר שפה בזמן אמת (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **ספריית נתונים מותאמים אישית** | `DATA_DIR` env var כדי לעקוף את ברירת המחדל `~/.omniroute` נתיב אחסון |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -928,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **DB biztonsági mentések** | Automatikus biztonsági mentés, visszaállítás, exportálás és importálás az összes beállításhoz |
|
||||
| 🌐 **Nemzetközivé válás** | Teljes i18n next-intl-vel – angol + portugál (Brazília) támogatás |
|
||||
| 🌍 **Nyelvválasztó** | Globe ikon a fejlécben a valós idejű nyelvváltáshoz (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Egyéni adattár** | `DATA_DIR` env var felülírja az alapértelmezett `~/.omniroute` tárolási útvonalat |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -928,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **Cadangan DB** | Pencadangan, pemulihan, ekspor & impor otomatis untuk semua pengaturan |
|
||||
| 🌐 **Internasionalisasi** | I18n lengkap dengan next-intl — Dukungan Inggris + Portugis (Brasil) |
|
||||
| 🌍 **Pemilih Bahasa** | Ikon bola dunia di header untuk peralihan bahasa secara real-time (🇮🇩/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Direktori Data Khusus** | `DATA_DIR` env var untuk mengganti jalur penyimpanan `~/.omniroute` default |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -824,6 +830,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **डीबी बैकअप** | सभी सेटिंग्स के लिए स्वचालित बैकअप, पुनर्स्थापना, निर्यात और आयात |
|
||||
| 🌐 **अंतर्राष्ट्रीयकरण** | नेक्स्ट-इंटल के साथ पूर्ण i18n - अंग्रेजी + पुर्तगाली (ब्राजील) समर्थन |
|
||||
| 🌍 **भाषा चयनकर्ता** | रीयल-टाइम भाषा स्विचिंग के लिए हेडर में ग्लोब आइकन (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **कस्टम डेटा निर्देशिका** | `DATA_DIR` env var डिफ़ॉल्ट `~/.omniroute` संग्रहण पथ को ओवरराइड करने के लिए |
|
||||
|
||||
<summary><b>📖 सुविधा विवरण</b></summary>
|
||||
|
||||
+33
-23
@@ -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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -929,6 +935,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **DB バックアップ** | すべての設定の自動バックアップ、復元、エクスポートとインポート |
|
||||
| 🌐 **国際化** | next-intl を備えた完全な i18n — 英語 + ポルトガル語 (ブラジル) のサポート |
|
||||
| 🌍 **言語セレクター** | リアルタイム言語切り替え用のヘッダーの地球儀アイコン (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **カスタム データ ディレクトリ** | デフォルトの `~/.omniroute` ストレージ パスをオーバーライドする `DATA_DIR` 環境変数 |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _무제한 코딩을 위한 무료 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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -927,6 +933,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **DB 백업** | 모든 설정에 대한 자동 백업, 복원, 내보내기 및 가져오기 |
|
||||
| 🌐 **국제화** | next-intl이 포함된 전체 i18n — 영어 + 포르투갈어(브라질) 지원 |
|
||||
| 🌍 **언어 선택기** | 실시간 언어 전환을 위한 헤더의 지구본 아이콘(🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **사용자 정의 데이터 디렉터리** | `DATA_DIR` env var는 기본 `~/.omniroute` 저장 경로를 재정의합니다 |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -928,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **Sandaran DB** | Sandaran automatik, pulihkan, eksport & import untuk semua tetapan |
|
||||
| 🌐 **Pengantarabangsaan** | i18n penuh dengan next-intl — sokongan Inggeris + Portugis (Brazil) |
|
||||
| 🌍 **Pemilih Bahasa** | Ikon glob dalam pengepala untuk penukaran bahasa masa nyata (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Direktori Data Tersuai** | `DATA_DIR` env var untuk mengatasi laluan storan lalai `~/.omniroute` |
|
||||
|
||||
<details>
|
||||
|
||||
+31
-23
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -246,7 +252,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
|
||||
@@ -927,6 +933,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **DB-back-ups** | Automatische back-up, herstel, export en import voor alle instellingen |
|
||||
| 🌐 **Internationalisering** | Volledige i18n met next-intl — Engels + Portugees (Brazilië) ondersteuning |
|
||||
| 🌍 **Taalkiezer** | Wereldbolpictogram in koptekst voor realtime taalwisseling (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Aangepaste gegevensmap** | `DATA_DIR` env var om standaard `~/.omniroute` opslagpad te overschrijven |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -928,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **DB-sikkerhetskopier** | Automatisk sikkerhetskopiering, gjenoppretting, eksport og import for alle innstillinger |
|
||||
| 🌐 **Internasjonalisering** | Full i18n med neste-intl — støtte for engelsk + portugisisk (Brasil) |
|
||||
| 🌍 **Språkvelger** | Globusikon i overskriften for sanntids språkbytte (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Tilpasset datakatalog** | `DATA_DIR` env var for å overstyre standard `~/.omniroute` lagringsbane |
|
||||
|
||||
<details>
|
||||
|
||||
+33
-25
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -246,7 +252,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 +337,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>
|
||||
|
||||
@@ -929,6 +935,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **Mga Backup ng DB** | Awtomatikong pag-backup, pagpapanumbalik, pag-export at pag-import para sa lahat ng mga setting |
|
||||
| 🌐 **Internasyonalisasyon** | Buong i18n na may next-intl — suporta sa English + Portuguese (Brazil) |
|
||||
| 🌍 **Pili ng Wika** | Globe icon sa header para sa real-time na paglipat ng wika (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Custom na Direktoryo ng Data** | `DATA_DIR` env var to override default `~/.omniroute` storage path |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -927,6 +933,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **Kopie zapasowe DB** | Automatyczne tworzenie kopii zapasowych, przywracanie, eksportowanie i importowanie wszystkich ustawień |
|
||||
| 🌐 **Internacjonalizacja** | Pełny i18n z next-intl — obsługa języka angielskiego i portugalskiego (Brazylia) |
|
||||
| 🌍 **Wybór języka** | Ikona kuli ziemskiej w nagłówku umożliwiająca zmianę języka w czasie rzeczywistym (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Niestandardowy katalog danych** | `DATA_DIR` env var, aby zastąpić domyślną ścieżkę przechowywania `~/.omniroute` |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -938,6 +944,8 @@ Por que isso é relevante:
|
||||
| 🔄 **Backups de DB** | Backup, restauração, exportação e importação automática de todas as configurações |
|
||||
| 🌐 **Internacionalização** | i18n completo com next-intl — suporte a 30 idiomas com RTL |
|
||||
| 🌍 **Seletor de Idioma** | Ícone de globo no cabeçalho para troca entre 30 idiomas em tempo real |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Diretório de Dados Custom** | Variável `DATA_DIR` para sobrescrever o caminho padrão `~/.omniroute` |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -928,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **Backups de banco de dados** | Backup, restauração, exportação e importação automáticos para todas as configurações |
|
||||
| 🌐 **Internacionalização** | i18n completo com next-intl — Suporte Inglês + Português (Brasil) |
|
||||
| 🌍 **Seletor de idioma** | Ícone de globo no cabeçalho para troca de idioma em tempo real (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Diretório de dados personalizado** | `DATA_DIR` env var para substituir o caminho de armazenamento padrão `~/.omniroute` |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -930,6 +936,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **Backup-uri DB** | Backup automat, restaurare, export și import pentru toate setările |
|
||||
| 🌐 **Internaționalizare** | I18n complet cu next-intl — suport engleză + portugheză (Brazilia) |
|
||||
| 🌍 **Selector de limbă** | Pictograma glob în antet pentru schimbarea limbii în timp real (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Director de date personalizate** | `DATA_DIR` env var pentru a înlocui calea de stocare implicită `~/.omniroute` |
|
||||
|
||||
<details>
|
||||
|
||||
+35
-25
@@ -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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/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), подсчёт, булева логика
|
||||
|
||||
### Стратегии оценки
|
||||
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -931,6 +937,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **Zálohy DB** | Automatické zálohovanie, obnovenie, export a import všetkých nastavení |
|
||||
| 🌐 **Internacionalizácia** | Plný i18n s next-intl — podpora angličtiny + portugalčiny (Brazília) |
|
||||
| 🌍 **Výber jazyka** | Ikona zemegule v hlavičke na prepínanie jazyka v reálnom čase (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Custom Data Directory** | `DATA_DIR` env var na prepísanie predvolenej cesty úložiska `~/.omniroute` |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -929,6 +935,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **DB-säkerhetskopior** | Automatisk säkerhetskopiering, återställning, export och import för alla inställningar |
|
||||
| 🌐 **Internationalisering** | Fullständig i18n med nästa-intl — stöd för engelska + portugisiska (Brasilien) |
|
||||
| 🌍 **Språkväljare** | Globikon i rubriken för språkväxling i realtid (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Anpassad datakatalog** | `DATA_DIR` env var för att åsidosätta standard `~/.omniroute` lagringssökväg |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _เชื่อมต่อเครื่องมือ 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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -928,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **การสำรองฐานข้อมูล** | สำรองข้อมูล กู้คืน ส่งออก & นำเข้าอัตโนมัติสำหรับการตั้งค่าทั้งหมด |
|
||||
| 🌐 **ความเป็นสากล** | i18n เต็มรูปแบบพร้อม next-intl — รองรับภาษาอังกฤษ + โปรตุเกส (บราซิล) |
|
||||
| 🌍 **ตัวเลือกภาษา** | ไอคอนลูกโลกในส่วนหัวสำหรับการสลับภาษาแบบเรียลไทม์ (USA/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **ไดเรกทอรีข้อมูลที่กำหนดเอง** | `DATA_DIR` env var เพื่อแทนที่ค่าเริ่มต้น `~/.omniroute` พาธหน่วยเก็บข้อมูล |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _Підключіть будь-який інструмент 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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -932,6 +938,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **Резервне копіювання БД** | Автоматичне резервне копіювання, відновлення, експорт і імпорт для всіх налаштувань |
|
||||
| 🌐 **Інтернаціоналізація** | Повний i18n із next-intl — підтримка англійської та португальської (Бразилія) |
|
||||
| 🌍 **Вибір мови** | Значок глобуса в заголовку для перемикання мов у реальному часі (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Каталог користувацьких даних** | `DATA_DIR` змінна env для перевизначення типового шляху зберігання `~/.omniroute` |
|
||||
|
||||
<details>
|
||||
|
||||
+30
-22
@@ -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,34 @@ _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 |
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
@@ -928,6 +934,8 @@ npm run electron:build:linux # Linux (.AppImage)
|
||||
| 🔄 **Sao lưu DB** | Tự động sao lưu, khôi phục, xuất và nhập cho tất cả cài đặt |
|
||||
| 🌐 **Quốc tế hóa** | I18n đầy đủ với hỗ trợ next-intl — Tiếng Anh + Tiếng Bồ Đào Nha (Brazil) |
|
||||
| 🌍 **Bộ chọn ngôn ngữ** | Biểu tượng quả địa cầu trong tiêu đề để chuyển đổi ngôn ngữ theo thời gian thực (🇺🇸/🇧🇷) |
|
||||
| 🧹 **Clear All Models** | One-click model list clearing in provider details |
|
||||
| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features |
|
||||
| 📂 **Thư mục dữ liệu tùy chỉnh** | `DATA_DIR` env var để ghi đè đường dẫn lưu trữ `~/.omniroute` mặc định |
|
||||
|
||||
<details>
|
||||
|
||||
+32
-22
@@ -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
|
||||
|
||||
_通过 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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](https://hub.docker.com/r/diegosouzapw/omniroute)
|
||||
[](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE)
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: OmniRoute API
|
||||
version: 3.0.0-rc.3
|
||||
version: 3.1.2
|
||||
description: |
|
||||
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
|
||||
endpoint that routes requests to multiple AI providers with load balancing,
|
||||
|
||||
@@ -29,6 +29,7 @@ const eslintConfig = [
|
||||
ignores: [
|
||||
// Next.js build output
|
||||
".next/**",
|
||||
"src/.next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+14
-1
@@ -13,7 +13,11 @@ const nextConfig = {
|
||||
},
|
||||
output: "standalone",
|
||||
serverExternalPackages: [
|
||||
"pino",
|
||||
"pino-pretty",
|
||||
"thread-stream",
|
||||
"better-sqlite3",
|
||||
"keytar",
|
||||
"zod",
|
||||
"child_process",
|
||||
"fs",
|
||||
@@ -37,8 +41,16 @@ const nextConfig = {
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
webpack: (config, { isServer }) => {
|
||||
webpack: (config, { isServer, webpack }) => {
|
||||
if (isServer) {
|
||||
// Webpack IgnorePlugin: skip thread-stream test files that contain
|
||||
// intentionally broken syntax/imports (they cause Turbopack build errors)
|
||||
config.plugins.push(
|
||||
new webpack.IgnorePlugin({
|
||||
resourceRegExp: /\/test\//,
|
||||
contextRegExp: /thread-stream/,
|
||||
})
|
||||
);
|
||||
// ── Turbopack / Next.js 16 module-hash patch (#394, #396, #398) ────────
|
||||
//
|
||||
// Next.js 16 (with or without Turbopack) compiles the instrumentation hook
|
||||
@@ -59,6 +71,7 @@ const nextConfig = {
|
||||
|
||||
const KNOWN_EXTERNALS = new Set([
|
||||
"better-sqlite3",
|
||||
"keytar",
|
||||
"zod",
|
||||
"pino",
|
||||
"pino-pretty",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { loadProviderCredentials } from "./credentialLoader.ts";
|
||||
|
||||
// Timeout for non-streaming fetch requests (ms). Prevents stalled connections.
|
||||
export const FETCH_TIMEOUT_MS = parseInt(process.env.FETCH_TIMEOUT_MS || "120000", 10);
|
||||
export const FETCH_TIMEOUT_MS = parseInt(process.env.FETCH_TIMEOUT_MS || "600000", 10);
|
||||
|
||||
// Idle timeout for SSE streams (ms). Closes stream if no data for this duration.
|
||||
// Default: 120s balances deep-reasoning pauses with fast zombie stream detection (#473).
|
||||
// Extended-thinking models rarely pause >90s between chunks. Override with STREAM_IDLE_TIMEOUT_MS env var.
|
||||
export const STREAM_IDLE_TIMEOUT_MS = parseInt(process.env.STREAM_IDLE_TIMEOUT_MS || "120000", 10);
|
||||
export const STREAM_IDLE_TIMEOUT_MS = parseInt(process.env.STREAM_IDLE_TIMEOUT_MS || "600000", 10);
|
||||
|
||||
// Provider configurations
|
||||
// OAuth credentials read from env vars with hardcoded fallbacks for backward compatibility.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { readFileSync, existsSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { resolveDataDir } from "../../src/lib/dataPaths";
|
||||
|
||||
// Fields that can be overridden per provider
|
||||
const CREDENTIAL_FIELDS = ["clientId", "clientSecret", "tokenUrl", "authUrl", "refreshUrl"];
|
||||
@@ -25,13 +26,21 @@ 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() {
|
||||
const dataDir = process.env.DATA_DIR || join(process.cwd(), "data");
|
||||
return join(dataDir, "provider-credentials.json");
|
||||
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();
|
||||
@@ -93,7 +103,11 @@ export function loadProviderCredentials(providers) {
|
||||
`[CREDENTIALS] ${isReload ? "Reloaded" : "Loaded"} external credentials: ${overrideCount} field(s) from ${credPath}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(`[CREDENTIALS] Error reading credentials file: ${err.message}. Using defaults.`);
|
||||
const reason =
|
||||
err instanceof SyntaxError
|
||||
? "Invalid JSON format"
|
||||
: (err as NodeJS.ErrnoException).code || "read error";
|
||||
console.log(`[CREDENTIALS] Error reading credentials file (${reason}). Using defaults.`);
|
||||
}
|
||||
|
||||
cachedProviders = providers;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* is auto-generated from this registry.
|
||||
*/
|
||||
|
||||
import { platform, arch } from "os";
|
||||
|
||||
// ── Types ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface RegistryModel {
|
||||
@@ -14,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.
|
||||
@@ -63,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 {
|
||||
@@ -96,6 +102,32 @@ const KIMI_CODING_SHARED = {
|
||||
] as RegistryModel[],
|
||||
} as const;
|
||||
|
||||
function mapStainlessOs() {
|
||||
switch (platform()) {
|
||||
case "darwin":
|
||||
return "MacOS";
|
||||
case "win32":
|
||||
return "Windows";
|
||||
case "linux":
|
||||
return "Linux";
|
||||
default:
|
||||
return `Other::${platform()}`;
|
||||
}
|
||||
}
|
||||
|
||||
function mapStainlessArch() {
|
||||
switch (arch()) {
|
||||
case "x64":
|
||||
return "x64";
|
||||
case "arm64":
|
||||
return "arm64";
|
||||
case "ia32":
|
||||
return "x86";
|
||||
default:
|
||||
return `other::${arch()}`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Registry ──────────────────────────────────────────────────────────────
|
||||
|
||||
export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
@@ -109,22 +141,23 @@ 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":
|
||||
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14,context-management-2025-06-27",
|
||||
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05",
|
||||
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
||||
"User-Agent": "claude-cli/1.0.83 (external, cli)",
|
||||
"User-Agent": "claude-cli/2.1.63 (external, cli)",
|
||||
"X-App": "cli",
|
||||
"X-Stainless-Helper-Method": "stream",
|
||||
"X-Stainless-Retry-Count": "0",
|
||||
"X-Stainless-Runtime-Version": "v24.3.0",
|
||||
"X-Stainless-Package-Version": "0.55.1",
|
||||
"X-Stainless-Package-Version": "0.74.0",
|
||||
"X-Stainless-Runtime": "node",
|
||||
"X-Stainless-Lang": "js",
|
||||
"X-Stainless-Arch": "arm64",
|
||||
"X-Stainless-Os": "MacOS",
|
||||
"X-Stainless-Timeout": "60",
|
||||
"X-Stainless-Arch": mapStainlessArch(),
|
||||
"X-Stainless-Os": mapStainlessOs(),
|
||||
"X-Stainless-Timeout": "600",
|
||||
},
|
||||
oauth: {
|
||||
clientIdEnv: "CLAUDE_OAUTH_CLIENT_ID",
|
||||
@@ -152,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",
|
||||
@@ -159,9 +193,13 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
clientSecretDefault: "",
|
||||
},
|
||||
models: [
|
||||
{ id: "gemini-3.1-pro-high", name: "Gemini 3.1 Pro High" },
|
||||
{ id: "gemini-3.1-pro-low", name: "Gemini 3.1 Pro Low" },
|
||||
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
|
||||
{ id: "gemini-3-1-pro", name: "Gemini 3.1 Pro (Alt ID)" },
|
||||
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
|
||||
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
|
||||
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview" },
|
||||
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
|
||||
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
|
||||
{ id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash Lite" },
|
||||
@@ -184,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",
|
||||
@@ -191,9 +230,13 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
clientSecretDefault: "",
|
||||
},
|
||||
models: [
|
||||
{ id: "gemini-3.1-pro-high", name: "Gemini 3.1 Pro High" },
|
||||
{ id: "gemini-3.1-pro-low", name: "Gemini 3.1 Pro Low" },
|
||||
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
|
||||
{ id: "gemini-3-1-pro", name: "Gemini 3.1 Pro (Alt ID)" },
|
||||
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
|
||||
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
|
||||
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview" },
|
||||
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
|
||||
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
|
||||
{ id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash Lite" },
|
||||
@@ -211,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",
|
||||
@@ -322,7 +366,11 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
alias: "ag",
|
||||
format: "antigravity",
|
||||
executor: "antigravity",
|
||||
baseUrls: ["https://daily-cloudcode-pa.googleapis.com", "https://cloudcode-pa.googleapis.com"],
|
||||
baseUrls: [
|
||||
"https://daily-cloudcode-pa.googleapis.com",
|
||||
"https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
"https://cloudcode-pa.googleapis.com",
|
||||
],
|
||||
urlBuilder: (base, model, stream) => {
|
||||
const path = stream
|
||||
? "/v1internal:streamGenerateContent?alt=sse"
|
||||
@@ -332,22 +380,29 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
authType: "oauth",
|
||||
authHeader: "bearer",
|
||||
headers: {
|
||||
"User-Agent": "antigravity/1.104.0 darwin/arm64",
|
||||
"User-Agent": `antigravity/1.107.0 ${platform()}/${arch()}`,
|
||||
},
|
||||
oauth: {
|
||||
clientIdEnv: "ANTIGRAVITY_OAUTH_CLIENT_ID",
|
||||
clientIdDefault: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
|
||||
clientSecretEnv: "ANTIGRAVITY_OAUTH_CLIENT_SECRET",
|
||||
clientSecretDefault: "",
|
||||
clientSecretDefault: "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",
|
||||
},
|
||||
models: [
|
||||
{ id: "claude-opus-4-6-thinking", name: "Claude Opus 4.6 Thinking" },
|
||||
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
|
||||
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" },
|
||||
{ id: "claude-sonnet-4", name: "Claude Sonnet 4" },
|
||||
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
|
||||
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
|
||||
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
|
||||
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
|
||||
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
|
||||
{ id: "gpt-oss-120b-medium", name: "GPT OSS 120B Medium" },
|
||||
{ id: "gpt-5", name: "GPT 5" },
|
||||
{ id: "gpt-5-mini", name: "GPT 5 Mini" },
|
||||
],
|
||||
passthroughModels: true,
|
||||
},
|
||||
|
||||
github: {
|
||||
@@ -359,11 +414,12 @@ 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.107.1",
|
||||
"editor-plugin-version": "copilot-chat/0.26.7",
|
||||
"user-agent": "GitHubCopilotChat/0.26.7",
|
||||
"editor-version": "vscode/1.110.0",
|
||||
"editor-plugin-version": "copilot-chat/0.38.0",
|
||||
"user-agent": "GitHubCopilotChat/0.38.0",
|
||||
"openai-intent": "conversation-panel",
|
||||
"x-github-api-version": "2025-04-01",
|
||||
"x-vscode-user-agent-library-version": "electron-fetch",
|
||||
@@ -407,6 +463,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",
|
||||
@@ -433,6 +490,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",
|
||||
@@ -461,6 +519,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" },
|
||||
@@ -482,6 +541,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
urlSuffix: "?beta=true",
|
||||
authType: "apikey",
|
||||
authHeader: "x-api-key",
|
||||
defaultContextLength: 200000,
|
||||
headers: {
|
||||
"Anthropic-Version": "2023-06-01",
|
||||
},
|
||||
@@ -508,6 +568,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" },
|
||||
@@ -522,9 +583,11 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
format: "openai",
|
||||
executor: "opencode",
|
||||
baseUrl: "https://opencode.ai/zen/v1",
|
||||
modelsUrl: "https://opencode.ai/zen/v1/models",
|
||||
authType: "apikey",
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer",
|
||||
defaultContextLength: 200000,
|
||||
models: [
|
||||
{ id: "minimax-m2.5-free", name: "MiniMax M2.5 Free" },
|
||||
{ id: "big-pickle", name: "Big Pickle" },
|
||||
@@ -540,6 +603,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",
|
||||
@@ -553,6 +617,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",
|
||||
@@ -746,6 +811,10 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14",
|
||||
},
|
||||
models: [
|
||||
// T12/T28: MiniMax default upgraded from M2.5 to M2.7
|
||||
{ id: "minimax-m2.7", name: "MiniMax M2.7" },
|
||||
{ id: "MiniMax-M2.7", name: "MiniMax M2.7 (Legacy Alias)" },
|
||||
{ id: "minimax-m2.7-highspeed", name: "MiniMax M2.7 Highspeed" },
|
||||
{ id: "minimax-m2.5", name: "MiniMax M2.5" },
|
||||
{ id: "MiniMax-M2.5", name: "MiniMax M2.5 (Legacy Alias)" },
|
||||
{ id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
@@ -767,6 +836,9 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
},
|
||||
models: [
|
||||
// Keep parity with minimax to ensure model discovery works for minimax-cn connections.
|
||||
{ id: "minimax-m2.7", name: "MiniMax M2.7" },
|
||||
{ id: "MiniMax-M2.7", name: "MiniMax M2.7 (Legacy Alias)" },
|
||||
{ id: "minimax-m2.7-highspeed", name: "MiniMax M2.7 Highspeed" },
|
||||
{ id: "minimax-m2.5", name: "MiniMax M2.5" },
|
||||
{ id: "MiniMax-M2.5", name: "MiniMax M2.5 (Legacy Alias)" },
|
||||
{ id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
@@ -971,8 +1043,8 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
alias: "ollamacloud",
|
||||
format: "openai",
|
||||
executor: "default",
|
||||
baseUrl: "https://api.ollama.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.ollama.com/v1/models",
|
||||
baseUrl: "https://ollama.com/v1/chat/completions",
|
||||
modelsUrl: "https://ollama.com/api/tags",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
// Note: rate limits vary by plan (free = "Light usage", Pro = more, Max = 5x Pro).
|
||||
@@ -1146,7 +1218,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
alias: "vertex",
|
||||
// Vertex AI uses Google's generateContent format (same as Gemini)
|
||||
format: "gemini",
|
||||
executor: "default",
|
||||
executor: "vertex",
|
||||
// URL uses {project_id} and {region} from providerSpecificData — handled by custom executor or fallback
|
||||
// Default to us-central1 / generic endpoint; users configure project via providerSpecificData
|
||||
baseUrl: "https://us-central1-aiplatform.googleapis.com/v1/projects",
|
||||
@@ -1160,10 +1232,16 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
models: [
|
||||
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview (Vertex)" },
|
||||
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview (Vertex)" },
|
||||
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview (Vertex)" },
|
||||
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro (Vertex)" },
|
||||
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash (Vertex)" },
|
||||
{ id: "gemini-2.0-flash-thinking-exp", name: "Gemini 2.0 Flash Thinking Exp (Vertex)" },
|
||||
{ id: "gemma-2-27b-it", name: "Gemma 2 27B (Vertex)" },
|
||||
{ id: "deepseek-v3.2", name: "DeepSeek V3.2 (Vertex Partner)" },
|
||||
{ id: "qwen3-next-80b", name: "Qwen3 Next 80B (Vertex Partner)" },
|
||||
{ id: "glm-5", name: "GLM-5 (Vertex Partner)" },
|
||||
{ id: "claude-opus-4-5@20251101", name: "Claude Opus 4.5 (Vertex)" },
|
||||
{ id: "claude-sonnet-4-5@20251101", name: "Claude Sonnet 4.5 (Vertex)" },
|
||||
],
|
||||
@@ -1205,10 +1283,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
alias: "lc",
|
||||
format: "openai",
|
||||
executor: "default",
|
||||
// (#536) Correct OpenAI-compatible base URL — was longcat.chat/api/v1/chat/completions
|
||||
// which is the chat endpoint directly, not the base. Key validation and routing must
|
||||
// use https://api.longcat.chat/openai which resolves /v1/models and /v1/chat/completions
|
||||
baseUrl: "https://api.longcat.chat/openai",
|
||||
baseUrl: "https://api.longcat.chat/openai/v1/chat/completions",
|
||||
authType: "apikey",
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer",
|
||||
@@ -1241,6 +1316,68 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
],
|
||||
},
|
||||
|
||||
puter: {
|
||||
id: "puter",
|
||||
alias: "pu",
|
||||
format: "openai",
|
||||
executor: "puter",
|
||||
// OpenAI-compatible gateway with 500+ models (GPT, Claude, Gemini, Grok, DeepSeek, Qwen…)
|
||||
// Auth: Bearer <puter_auth_token> from puter.com/dashboard → Copy Auth Token
|
||||
// Model IDs use provider/model-name format for non-OpenAI models.
|
||||
// Only chat completions (incl. streaming) are available via REST.
|
||||
// Image gen, TTS, STT, video are puter.js SDK-only (browser).
|
||||
baseUrl: "https://api.puter.com/puterai/openai/v1/chat/completions",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
models: [
|
||||
// OpenAI — use bare IDs
|
||||
{ id: "gpt-4o-mini", name: "GPT-4o Mini (🆓 Puter)" },
|
||||
{ id: "gpt-4o", name: "GPT-4o (Puter)" },
|
||||
{ id: "gpt-4.1", name: "GPT-4.1 (Puter)" },
|
||||
{ id: "gpt-4.1-mini", name: "GPT-4.1 Mini (Puter)" },
|
||||
{ id: "gpt-5-nano", name: "GPT-5 Nano (Puter)" },
|
||||
{ id: "gpt-5-mini", name: "GPT-5 Mini (Puter)" },
|
||||
{ id: "gpt-5", name: "GPT-5 (Puter)" },
|
||||
{ id: "o3-mini", name: "OpenAI o3-mini (Puter)" },
|
||||
{ id: "o3", name: "OpenAI o3 (Puter)" },
|
||||
{ id: "o4-mini", name: "OpenAI o4-mini (Puter)" },
|
||||
// Anthropic Claude — use bare IDs (confirmed working)
|
||||
{ id: "claude-haiku-4-5", name: "Claude Haiku 4.5 (Puter)" },
|
||||
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5 (Puter)" },
|
||||
{ id: "claude-opus-4-5", name: "Claude Opus 4.5 (Puter)" },
|
||||
{ id: "claude-sonnet-4", name: "Claude Sonnet 4 (Puter)" },
|
||||
{ id: "claude-opus-4", name: "Claude Opus 4 (Puter)" },
|
||||
// Google Gemini — use google/ prefix (confirmed working)
|
||||
{ id: "google/gemini-2.0-flash", name: "Gemini 2.0 Flash (Puter)" },
|
||||
{ id: "google/gemini-2.5-flash", name: "Gemini 2.5 Flash (Puter)" },
|
||||
{ id: "google/gemini-2.5-pro", name: "Gemini 2.5 Pro (Puter)" },
|
||||
{ id: "google/gemini-3-flash", name: "Gemini 3 Flash (Puter)" },
|
||||
{ id: "google/gemini-3-pro", name: "Gemini 3 Pro (Puter)" },
|
||||
// DeepSeek — use deepseek/ prefix (confirmed working)
|
||||
{ id: "deepseek/deepseek-chat", name: "DeepSeek Chat (Puter)" },
|
||||
{ id: "deepseek/deepseek-r1", name: "DeepSeek R1 (Puter)" },
|
||||
{ id: "deepseek/deepseek-v3.2", name: "DeepSeek V3.2 (Puter)" },
|
||||
// xAI Grok — use x-ai/ prefix
|
||||
{ id: "x-ai/grok-3", name: "Grok 3 (Puter)" },
|
||||
{ id: "x-ai/grok-3-mini", name: "Grok 3 Mini (Puter)" },
|
||||
{ id: "x-ai/grok-4", name: "Grok 4 (Puter)" },
|
||||
{ id: "x-ai/grok-4-fast", name: "Grok 4 Fast (Puter)" },
|
||||
// Meta Llama — bare IDs (confirmed ✅)
|
||||
{ id: "llama-4-scout", name: "Llama 4 Scout (Puter)" },
|
||||
{ id: "llama-4-maverick", name: "Llama 4 Maverick (Puter)" },
|
||||
{ id: "llama-3.3-70b-instruct", name: "Llama 3.3 70B (Puter)" },
|
||||
// Mistral — bare IDs (confirmed ✅)
|
||||
{ id: "mistral-small-latest", name: "Mistral Small (Puter)" },
|
||||
{ id: "mistral-medium-latest", name: "Mistral Medium (Puter)" },
|
||||
{ id: "open-mistral-nemo", name: "Mistral Nemo (Puter)" },
|
||||
// Qwen — use qwen/ prefix (confirmed ✅)
|
||||
{ id: "qwen/qwen3-235b-a22b", name: "Qwen3 235B (Puter)" },
|
||||
{ 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 arbitrary Puter model IDs
|
||||
},
|
||||
|
||||
"cloudflare-ai": {
|
||||
id: "cloudflare-ai",
|
||||
alias: "cf",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +3,112 @@ import { CODEX_DEFAULT_INSTRUCTIONS } from "../config/codexInstructions.ts";
|
||||
import { PROVIDERS } from "../config/constants.ts";
|
||||
import { refreshCodexToken } from "../services/tokenRefresh.ts";
|
||||
|
||||
// ─── T09: Codex vs Spark Scope-Aware Rate Limiting ────────────────────────
|
||||
// Codex has two independent quota pools: "codex" (standard) and "spark" (premium).
|
||||
// Exhausting one should NOT block requests to the other.
|
||||
// Ref: sub2api PR #1129 (feat(openai): split codex spark rate limiting from codex)
|
||||
|
||||
/**
|
||||
* Maps model name substrings to their rate-limit scope.
|
||||
* Checked in order — first match wins.
|
||||
*/
|
||||
const CODEX_SCOPE_PATTERNS: Array<{ pattern: string; scope: "codex" | "spark" }> = [
|
||||
{ pattern: "codex-spark", scope: "spark" },
|
||||
{ pattern: "spark", scope: "spark" },
|
||||
{ pattern: "codex", scope: "codex" },
|
||||
{ pattern: "gpt-5", scope: "codex" }, // gpt-5.2-codex, gpt-5.3-codex, etc.
|
||||
];
|
||||
|
||||
/**
|
||||
* T09: Determine the rate-limit scope for a Codex model.
|
||||
* Use this key as the suffix for per-scope rate limit state:
|
||||
* `${accountId}:${getModelScope(model)}`
|
||||
*
|
||||
* @param model - The Codex model ID (e.g. "gpt-5.3-codex", "codex-spark-mini")
|
||||
* @returns "codex" | "spark"
|
||||
*/
|
||||
export function getCodexModelScope(model: string): "codex" | "spark" {
|
||||
const lower = model.toLowerCase();
|
||||
for (const { pattern, scope } of CODEX_SCOPE_PATTERNS) {
|
||||
if (lower.includes(pattern)) return scope;
|
||||
}
|
||||
return "codex"; // default scope
|
||||
}
|
||||
|
||||
/**
|
||||
* T09: Get the scope-keyed rate limit identifier for an account+model combination.
|
||||
* Use this as the key for rateLimitState maps to ensure scope isolation.
|
||||
*/
|
||||
export function getCodexRateLimitKey(accountId: string, model: string): string {
|
||||
return `${accountId}:${getCodexModelScope(model)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* T03: Parsed quota snapshot from Codex response headers.
|
||||
* Codex includes per-account usage windows that allow precise reset scheduling.
|
||||
* Ref: sub2api PR #357 (feat(oauth): persist usage snapshots and window cooldown)
|
||||
*/
|
||||
export interface CodexQuotaSnapshot {
|
||||
usage5h: number; // tokens used in 5h window
|
||||
limit5h: number; // token limit for 5h window
|
||||
resetAt5h: string | null; // ISO timestamp when 5h window resets
|
||||
usage7d: number; // tokens used in 7d window
|
||||
limit7d: number; // token limit for 7d window
|
||||
resetAt7d: string | null; // ISO timestamp when 7d window resets
|
||||
}
|
||||
|
||||
/**
|
||||
* T03: Parse Codex-specific quota headers from a provider response.
|
||||
* Returns null if none of the relevant headers are present.
|
||||
*
|
||||
* Extracts:
|
||||
* x-codex-5h-usage / x-codex-5h-limit / x-codex-5h-reset-at
|
||||
* x-codex-7d-usage / x-codex-7d-limit / x-codex-7d-reset-at
|
||||
*/
|
||||
export function parseCodexQuotaHeaders(headers: Headers): CodexQuotaSnapshot | null {
|
||||
const usage5h = headers.get("x-codex-5h-usage");
|
||||
const limit5h = headers.get("x-codex-5h-limit");
|
||||
const resetAt5h = headers.get("x-codex-5h-reset-at");
|
||||
const usage7d = headers.get("x-codex-7d-usage");
|
||||
const limit7d = headers.get("x-codex-7d-limit");
|
||||
const resetAt7d = headers.get("x-codex-7d-reset-at");
|
||||
|
||||
// Return null if none of the quota headers are present (not a quota-aware response)
|
||||
if (!usage5h && !limit5h && !resetAt5h && !usage7d && !limit7d && !resetAt7d) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
usage5h: usage5h ? parseFloat(usage5h) : 0,
|
||||
limit5h: limit5h ? parseFloat(limit5h) : Infinity,
|
||||
resetAt5h: resetAt5h ?? null,
|
||||
usage7d: usage7d ? parseFloat(usage7d) : 0,
|
||||
limit7d: limit7d ? parseFloat(limit7d) : Infinity,
|
||||
resetAt7d: resetAt7d ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* T03: Get the soonest quota reset time from a CodexQuotaSnapshot.
|
||||
* 7d window takes priority (wider window, harder limit) but we use whichever
|
||||
* is further in the future to avoid releasing the block too early.
|
||||
*
|
||||
* @returns Unix timestamp (ms) of the soonest effective reset, or null
|
||||
*/
|
||||
export function getCodexResetTime(quota: CodexQuotaSnapshot): number | null {
|
||||
const times: number[] = [];
|
||||
if (quota.resetAt7d) {
|
||||
const t = new Date(quota.resetAt7d).getTime();
|
||||
if (!isNaN(t) && t > Date.now()) times.push(t);
|
||||
}
|
||||
if (quota.resetAt5h) {
|
||||
const t = new Date(quota.resetAt5h).getTime();
|
||||
if (!isNaN(t) && t > Date.now()) times.push(t);
|
||||
}
|
||||
if (times.length === 0) return null;
|
||||
return Math.max(...times); // Use furthest-out reset to avoid premature unblock
|
||||
}
|
||||
|
||||
// Ordered list of effort levels from lowest to highest
|
||||
const EFFORT_ORDER = ["none", "low", "medium", "high", "xhigh"] as const;
|
||||
type EffortLevel = (typeof EFFORT_ORDER)[number];
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BaseExecutor } from "./base.ts";
|
||||
import { BaseExecutor, ExecuteInput } from "./base.ts";
|
||||
import { PROVIDERS, OAUTH_ENDPOINTS } from "../config/constants.ts";
|
||||
import { getModelTargetFormat } from "../config/providerModels.ts";
|
||||
|
||||
@@ -19,15 +19,82 @@ export class GithubExecutor extends BaseExecutor {
|
||||
return this.config.baseUrl;
|
||||
}
|
||||
|
||||
injectResponseFormat(messages: any[], responseFormat: any) {
|
||||
if (!responseFormat) return messages;
|
||||
|
||||
let formatInstruction = "";
|
||||
if (responseFormat.type === "json_object") {
|
||||
formatInstruction =
|
||||
"Respond only with valid JSON. Do not include any text before or after the JSON object.";
|
||||
} else if (responseFormat.type === "json_schema" && responseFormat.json_schema) {
|
||||
formatInstruction = `Respond only with valid JSON matching this schema:\n${JSON.stringify(
|
||||
responseFormat.json_schema.schema,
|
||||
null,
|
||||
2
|
||||
)}\nDo not include any text before or after the JSON.`;
|
||||
}
|
||||
|
||||
if (!formatInstruction) return messages;
|
||||
|
||||
const systemIdx = messages.findIndex((m: any) => m.role === "system");
|
||||
if (systemIdx >= 0) {
|
||||
return messages.map((m: any, i: number) =>
|
||||
i === systemIdx ? { ...m, content: `${m.content}\n\n${formatInstruction}` } : m
|
||||
);
|
||||
}
|
||||
|
||||
return [{ role: "system", content: formatInstruction }, ...messages];
|
||||
}
|
||||
|
||||
transformRequest(model: string, body: any, stream: boolean, credentials: any): any {
|
||||
const modifiedBody = JSON.parse(JSON.stringify(body));
|
||||
if (modifiedBody.response_format && model.toLowerCase().includes("claude")) {
|
||||
modifiedBody.messages = this.injectResponseFormat(
|
||||
modifiedBody.messages,
|
||||
modifiedBody.response_format
|
||||
);
|
||||
delete modifiedBody.response_format;
|
||||
}
|
||||
return modifiedBody;
|
||||
}
|
||||
|
||||
async execute(input: ExecuteInput) {
|
||||
const result = await super.execute(input);
|
||||
if (!result || !result.response?.body) return result;
|
||||
|
||||
const isStreaming = input.stream === true;
|
||||
if (isStreaming) {
|
||||
const decoder = new TextDecoder();
|
||||
const transformStream = new TransformStream({
|
||||
transform(chunk, controller) {
|
||||
const text = decoder.decode(chunk, { stream: true });
|
||||
if (text.includes("data: [DONE]")) {
|
||||
return;
|
||||
}
|
||||
controller.enqueue(chunk);
|
||||
},
|
||||
});
|
||||
|
||||
const newResponse = new Response(result.response.body.pipeThrough(transformStream), {
|
||||
status: result.response.status,
|
||||
statusText: result.response.statusText,
|
||||
headers: result.response.headers, // Headers class carries over correctly
|
||||
});
|
||||
result.response = newResponse;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
buildHeaders(credentials, stream = true) {
|
||||
const token = credentials.copilotToken || credentials.accessToken;
|
||||
return {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
"copilot-integration-id": "vscode-chat",
|
||||
"editor-version": "vscode/1.107.1",
|
||||
"editor-plugin-version": "copilot-chat/0.26.7",
|
||||
"user-agent": "GitHubCopilotChat/0.26.7",
|
||||
"editor-version": "vscode/1.110.0",
|
||||
"editor-plugin-version": "copilot-chat/0.38.0",
|
||||
"user-agent": "GitHubCopilotChat/0.38.0",
|
||||
"openai-intent": "conversation-panel",
|
||||
"x-github-api-version": "2025-04-01",
|
||||
"x-request-id":
|
||||
@@ -44,7 +111,7 @@ export class GithubExecutor extends BaseExecutor {
|
||||
headers: {
|
||||
Authorization: `token ${githubAccessToken}`,
|
||||
"User-Agent": "GithubCopilot/1.0",
|
||||
"Editor-Version": "vscode/1.100.0",
|
||||
"Editor-Version": "vscode/1.110.0",
|
||||
"Editor-Plugin-Version": "copilot/1.300.0",
|
||||
Accept: "application/json",
|
||||
},
|
||||
|
||||
@@ -9,6 +9,8 @@ import { DefaultExecutor } from "./default.ts";
|
||||
import { PollinationsExecutor } from "./pollinations.ts";
|
||||
import { CloudflareAIExecutor } from "./cloudflare-ai.ts";
|
||||
import { OpencodeExecutor } from "./opencode.ts";
|
||||
import { PuterExecutor } from "./puter.ts";
|
||||
import { VertexExecutor } from "./vertex.ts";
|
||||
|
||||
const executors = {
|
||||
antigravity: new AntigravityExecutor(),
|
||||
@@ -25,6 +27,9 @@ const executors = {
|
||||
cf: new CloudflareAIExecutor(), // Alias
|
||||
"opencode-zen": new OpencodeExecutor("opencode-zen"),
|
||||
"opencode-go": new OpencodeExecutor("opencode-go"),
|
||||
puter: new PuterExecutor(),
|
||||
pu: new PuterExecutor(), // Alias
|
||||
vertex: new VertexExecutor(),
|
||||
};
|
||||
|
||||
const defaultCache = new Map();
|
||||
@@ -51,3 +56,5 @@ export { DefaultExecutor } from "./default.ts";
|
||||
export { PollinationsExecutor } from "./pollinations.ts";
|
||||
export { CloudflareAIExecutor } from "./cloudflare-ai.ts";
|
||||
export { OpencodeExecutor } from "./opencode.ts";
|
||||
export { PuterExecutor } from "./puter.ts";
|
||||
export { VertexExecutor } from "./vertex.ts";
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { BaseExecutor } from "./base.ts";
|
||||
import { PROVIDERS } from "../config/constants.ts";
|
||||
|
||||
/**
|
||||
* PuterExecutor — OpenAI-compatible proxy for Puter AI.
|
||||
*
|
||||
* Puter exposes 500+ models (GPT, Claude, Gemini, Grok, DeepSeek, Qwen, Mistral...)
|
||||
* through a single OpenAI-compatible REST endpoint.
|
||||
*
|
||||
* Endpoint: https://api.puter.com/puterai/openai/v1/chat/completions
|
||||
* Auth: Bearer <puter_auth_token> (from puter.com/dashboard → Copy Auth Token)
|
||||
* Docs: https://docs.puter.com/AI/
|
||||
*
|
||||
* Model ID examples:
|
||||
* OpenAI: "gpt-4o-mini", "gpt-4o", "gpt-4.1"
|
||||
* Claude: "claude-sonnet-4-5", "claude-opus-4", "claude-haiku-4-5"
|
||||
* Gemini: "google/gemini-2.0-flash", "google/gemini-2.5-pro"
|
||||
* DeepSeek: "deepseek/deepseek-chat", "deepseek/deepseek-r1"
|
||||
* Grok: "x-ai/grok-3", "x-ai/grok-4"
|
||||
* Mistral: "mistralai/mistral-small-3.2"
|
||||
* Meta: "meta-llama/llama-3.3-70b-instruct"
|
||||
*
|
||||
* Note: Image generation, TTS, STT, and video are puter.js SDK-only features.
|
||||
* Only text chat completions (with streaming SSE) are available via REST.
|
||||
*/
|
||||
export class PuterExecutor extends BaseExecutor {
|
||||
constructor() {
|
||||
super("puter", PROVIDERS["puter"] || { format: "openai" });
|
||||
}
|
||||
|
||||
buildUrl(_model: string, _stream: boolean, _urlIndex = 0, _credentials = null): string {
|
||||
return "https://api.puter.com/puterai/openai/v1/chat/completions";
|
||||
}
|
||||
|
||||
buildHeaders(credentials: any, stream = true): Record<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const key = credentials?.apiKey || credentials?.accessToken;
|
||||
if (key) {
|
||||
headers["Authorization"] = `Bearer ${key}`;
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
headers["Accept"] = "text/event-stream";
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
transformRequest(model: string, body: any, _stream: boolean, _credentials: any): any {
|
||||
// Puter accepts model IDs directly from its catalog.
|
||||
// No transformation required — model string is passed as-is.
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
export default PuterExecutor;
|
||||
@@ -0,0 +1,150 @@
|
||||
import { SignJWT, importPKCS8 } from "jose";
|
||||
import { BaseExecutor, ExecuteInput } from "./base.ts";
|
||||
import { PROVIDERS } from "../config/constants.ts";
|
||||
|
||||
interface ServiceAccount {
|
||||
type: string;
|
||||
project_id: string;
|
||||
private_key_id: string;
|
||||
private_key: string;
|
||||
client_email: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const TOKEN_CACHE = new Map<string, { token: string; expiresAt: number }>();
|
||||
|
||||
function parseSAFromApiKey(apiKey: string): ServiceAccount {
|
||||
try {
|
||||
return JSON.parse(apiKey);
|
||||
} catch {
|
||||
throw new Error("Vertex AI requires a valid Service Account JSON as the API key");
|
||||
}
|
||||
}
|
||||
|
||||
async function getAccessToken(sa: ServiceAccount): Promise<string> {
|
||||
if (!sa.client_email || !sa.private_key) {
|
||||
throw new Error(
|
||||
"Service Account JSON is missing required fields (client_email or private_key)"
|
||||
);
|
||||
}
|
||||
|
||||
const cacheKey = sa.client_email;
|
||||
const cached = TOKEN_CACHE.get(cacheKey);
|
||||
|
||||
// Buffer of 60 seconds
|
||||
if (cached && Date.now() < cached.expiresAt - 60_000) {
|
||||
return cached.token;
|
||||
}
|
||||
|
||||
const privateKey = await importPKCS8(sa.private_key, "RS256");
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
|
||||
const jwt = await new SignJWT({
|
||||
iss: sa.client_email,
|
||||
sub: sa.client_email,
|
||||
aud: "https://oauth2.googleapis.com/token",
|
||||
iat: now,
|
||||
exp: now + 3600,
|
||||
scope: "https://www.googleapis.com/auth/cloud-platform",
|
||||
})
|
||||
.setProtectedHeader({ alg: "RS256", kid: sa.private_key_id })
|
||||
.sign(privateKey);
|
||||
|
||||
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
||||
assertion: jwt,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!tokenRes.ok) {
|
||||
const errorText = await tokenRes.text();
|
||||
throw new Error(
|
||||
`Failed to exchange JWT for Vertex access token: ${tokenRes.status} ${errorText}`
|
||||
);
|
||||
}
|
||||
|
||||
const tokenData = await tokenRes.json();
|
||||
const accessToken = tokenData.access_token;
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error("Vertex AI token exchange succeeded but no access_token found");
|
||||
}
|
||||
|
||||
TOKEN_CACHE.set(cacheKey, {
|
||||
token: accessToken,
|
||||
expiresAt: (now + 3600) * 1000,
|
||||
});
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
const PARTNER_MODELS = new Set([
|
||||
"claude-3-5-sonnet",
|
||||
"claude-3-opus",
|
||||
"claude-3-haiku",
|
||||
"deepseek-v3",
|
||||
"deepseek-v3.2",
|
||||
"deepseek-deepseek-r1",
|
||||
"qwen3-next-80b",
|
||||
"llama-3.1",
|
||||
"mistral-",
|
||||
"glm-5",
|
||||
"meta/llama",
|
||||
]);
|
||||
|
||||
function isPartnerModel(model: string) {
|
||||
return [...PARTNER_MODELS].some((prefix) => model.startsWith(prefix));
|
||||
}
|
||||
|
||||
export class VertexExecutor extends BaseExecutor {
|
||||
constructor() {
|
||||
super("vertex", PROVIDERS.vertex);
|
||||
}
|
||||
|
||||
async execute(input: ExecuteInput) {
|
||||
const { credentials, log } = input;
|
||||
if (credentials.apiKey && !credentials.accessToken) {
|
||||
try {
|
||||
const sa = parseSAFromApiKey(credentials.apiKey);
|
||||
credentials.accessToken = await getAccessToken(sa);
|
||||
} catch (err: any) {
|
||||
log?.error?.("VERTEX", `Failed to generate JWT token: ${err.message}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return super.execute(input);
|
||||
}
|
||||
|
||||
buildUrl(model: string, stream: boolean, urlIndex = 0, credentials: any = null) {
|
||||
const region = credentials?.providerSpecificData?.region || "us-central1";
|
||||
let project = "unknown-project";
|
||||
|
||||
if (credentials?.apiKey) {
|
||||
try {
|
||||
const sa = parseSAFromApiKey(credentials.apiKey);
|
||||
if (sa.project_id) project = sa.project_id;
|
||||
} catch {
|
||||
// Ignored, handled in execute
|
||||
}
|
||||
}
|
||||
|
||||
if (isPartnerModel(model)) {
|
||||
return `https://aiplatform.googleapis.com/v1/projects/${project}/locations/global/endpoints/openapi/chat/completions`;
|
||||
}
|
||||
return `https://aiplatform.googleapis.com/v1/projects/${project}/locations/${region}/publishers/google/models/${model}:${stream ? "streamGenerateContent?alt=sse" : "generateContent"}`;
|
||||
}
|
||||
|
||||
buildHeaders(credentials: any, stream = true) {
|
||||
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
||||
if (credentials.accessToken) {
|
||||
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
|
||||
}
|
||||
if (stream) {
|
||||
headers["Accept"] = "text/event-stream";
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
+307
-54
@@ -1,5 +1,5 @@
|
||||
import { getCorsOrigin } from "../utils/cors.ts";
|
||||
import { detectFormat, getTargetFormat } from "../services/provider.ts";
|
||||
import { detectFormatFromEndpoint, getTargetFormat } from "../services/provider.ts";
|
||||
import { translateRequest, needsTranslation } from "../translator/index.ts";
|
||||
import { FORMATS } from "../translator/formats.ts";
|
||||
import {
|
||||
@@ -16,6 +16,9 @@ import { resolveModelAlias } from "../services/modelDeprecation.ts";
|
||||
import { getUnsupportedParams } from "../config/providerRegistry.ts";
|
||||
import { createErrorResult, parseUpstreamError, formatProviderError } from "../utils/error.ts";
|
||||
import { HTTP_STATUS } from "../config/constants.ts";
|
||||
import { classifyProviderError, PROVIDER_ERROR_TYPES } from "../services/errorClassifier.ts";
|
||||
import { updateProviderConnection } from "@/lib/db/providers";
|
||||
import { logAuditEvent } from "@/lib/compliance";
|
||||
import { handleBypassRequest } from "../utils/bypassHandler.ts";
|
||||
import {
|
||||
saveRequestUsage,
|
||||
@@ -23,8 +26,17 @@ 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,
|
||||
getCodexResetTime,
|
||||
getCodexModelScope,
|
||||
} from "../executors/codex.ts";
|
||||
import { translateNonStreamingResponse } from "./responseTranslator.ts";
|
||||
import { extractUsageFromResponse } from "./usageExtractor.ts";
|
||||
import { parseSSEToOpenAIResponse, parseSSEToResponsesOutput } from "./sseParser.ts";
|
||||
@@ -44,11 +56,17 @@ import { getIdempotencyKey, checkIdempotency, saveIdempotency } from "@/lib/idem
|
||||
import { createProgressTransform, wantsProgress } from "../utils/progressTracker.ts";
|
||||
import { isModelUnavailableError, getNextFamilyFallback } from "../services/modelFamilyFallback.ts";
|
||||
import { computeRequestHash, deduplicate, shouldDeduplicate } from "../services/requestDedup.ts";
|
||||
import {
|
||||
getBackgroundTaskReason,
|
||||
getDegradedModel,
|
||||
getBackgroundDegradationConfig,
|
||||
} from "../services/backgroundTaskDetector.ts";
|
||||
import {
|
||||
shouldUseFallback,
|
||||
isFallbackDecision,
|
||||
EMERGENCY_FALLBACK_CONFIG,
|
||||
} from "../services/emergencyFallback.ts";
|
||||
import { resolveStreamFlag, stripMarkdownCodeFence } from "../utils/aiSdkCompat.ts";
|
||||
|
||||
export function shouldUseNativeCodexPassthrough({
|
||||
provider,
|
||||
@@ -62,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");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +112,9 @@ export async function handleChatCore({
|
||||
userAgent,
|
||||
comboName,
|
||||
}) {
|
||||
const { provider, model, extendedContext } = modelInfo;
|
||||
let { provider, model, extendedContext } = modelInfo;
|
||||
const requestedModel =
|
||||
typeof body?.model === "string" && body.model.trim().length > 0 ? body.model : model;
|
||||
const startTime = Date.now();
|
||||
const persistFailureUsage = (statusCode: number, errorCode?: string | null) => {
|
||||
saveRequestUsage({
|
||||
@@ -112,6 +133,67 @@ export async function handleChatCore({
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
const persistCodexQuotaState = async (
|
||||
headers: Headers | Record<string, string> | null,
|
||||
status = 0
|
||||
) => {
|
||||
if (provider !== "codex" || !connectionId || !headers) return;
|
||||
|
||||
try {
|
||||
const quota = parseCodexQuotaHeaders(headers as Headers);
|
||||
if (!quota) return;
|
||||
|
||||
const existingProviderData =
|
||||
credentials?.providerSpecificData && typeof credentials.providerSpecificData === "object"
|
||||
? credentials.providerSpecificData
|
||||
: {};
|
||||
const scope = getCodexModelScope(model || requestedModel || "");
|
||||
const quotaState = {
|
||||
usage5h: quota.usage5h,
|
||||
limit5h: quota.limit5h,
|
||||
resetAt5h: quota.resetAt5h,
|
||||
usage7d: quota.usage7d,
|
||||
limit7d: quota.limit7d,
|
||||
resetAt7d: quota.resetAt7d,
|
||||
scope,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const nextProviderData: Record<string, unknown> = {
|
||||
...existingProviderData,
|
||||
codexQuotaState: quotaState,
|
||||
};
|
||||
|
||||
// T03/T09: on 429, persist exact reset time per scope to avoid global over-blocking.
|
||||
if (status === 429) {
|
||||
const resetTimeMs = getCodexResetTime(quota);
|
||||
if (resetTimeMs && resetTimeMs > Date.now()) {
|
||||
const scopeUntil = new Date(resetTimeMs).toISOString();
|
||||
const scopeMapRaw =
|
||||
existingProviderData &&
|
||||
typeof existingProviderData === "object" &&
|
||||
existingProviderData.codexScopeRateLimitedUntil &&
|
||||
typeof existingProviderData.codexScopeRateLimitedUntil === "object"
|
||||
? existingProviderData.codexScopeRateLimitedUntil
|
||||
: {};
|
||||
|
||||
nextProviderData.codexScopeRateLimitedUntil = {
|
||||
...(scopeMapRaw as Record<string, unknown>),
|
||||
[scope]: scopeUntil,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await updateProviderConnection(connectionId, {
|
||||
providerSpecificData: nextProviderData,
|
||||
});
|
||||
|
||||
credentials.providerSpecificData = nextProviderData;
|
||||
} catch (err) {
|
||||
log?.debug?.("CODEX", `Failed to persist codex quota state: ${err?.message || err}`);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Phase 9.2: Idempotency check ──
|
||||
const idempotencyKey = getIdempotencyKey(clientRawRequest?.headers);
|
||||
const cachedIdemp = checkIdempotency(idempotencyKey);
|
||||
@@ -139,9 +221,10 @@ export async function handleChatCore({
|
||||
credentials.connectionId = connectionId;
|
||||
}
|
||||
|
||||
const sourceFormat = detectFormat(body);
|
||||
const endpointPath = String(clientRawRequest?.endpoint || "");
|
||||
const isResponsesEndpoint = /(?:^|\/)responses(?:\/.*)?$/i.test(endpointPath);
|
||||
const sourceFormat = detectFormatFromEndpoint(body, endpointPath);
|
||||
const isResponsesEndpoint =
|
||||
/\/responses(?=\/|$)/i.test(endpointPath) || /^responses(?=\/|$)/i.test(endpointPath);
|
||||
const nativeCodexPassthrough = shouldUseNativeCodexPassthrough({
|
||||
provider,
|
||||
sourceFormat,
|
||||
@@ -157,6 +240,37 @@ export async function handleChatCore({
|
||||
// Detect source format and get target format
|
||||
// Model-specific targetFormat takes priority over provider default
|
||||
|
||||
// ── Background Task Redirection (T41) ──
|
||||
const bgConfig = getBackgroundDegradationConfig();
|
||||
const backgroundReason = bgConfig.enabled
|
||||
? getBackgroundTaskReason(body, clientRawRequest?.headers)
|
||||
: null;
|
||||
if (backgroundReason) {
|
||||
const degradedModel = getDegradedModel(model);
|
||||
if (degradedModel !== model) {
|
||||
const originalModel = model;
|
||||
log?.info?.(
|
||||
"BACKGROUND",
|
||||
`Background task redirect (${backgroundReason}): ${originalModel} → ${degradedModel}`
|
||||
);
|
||||
model = degradedModel;
|
||||
if (body && typeof body === "object") {
|
||||
body.model = model;
|
||||
}
|
||||
|
||||
logAuditEvent({
|
||||
action: "routing.background_task_redirect",
|
||||
actor: apiKeyInfo?.name || "system",
|
||||
target: connectionId || provider || "chat",
|
||||
details: {
|
||||
original_model: originalModel,
|
||||
redirected_to: degradedModel,
|
||||
reason: backgroundReason,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Apply custom model aliases (Settings → Model Aliases → Pattern→Target) before routing (#315, #472)
|
||||
// Custom aliases take priority over built-in and must be resolved here so the
|
||||
// downstream getModelTargetFormat() lookup AND the actual provider request use
|
||||
@@ -172,8 +286,30 @@ 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 stream = body.stream === true;
|
||||
const acceptHeader =
|
||||
clientRawRequest?.headers && typeof clientRawRequest.headers.get === "function"
|
||||
? clientRawRequest.headers.get("accept") || clientRawRequest.headers.get("Accept")
|
||||
: (clientRawRequest?.headers || {})["accept"] || (clientRawRequest?.headers || {})["Accept"];
|
||||
|
||||
const stream = resolveStreamFlag(body?.stream, acceptHeader);
|
||||
|
||||
// ── Phase 9.1: Semantic cache check (non-streaming, temp=0 only) ──
|
||||
if (isCacheable(body, clientRawRequest?.headers)) {
|
||||
@@ -211,6 +347,38 @@ export async function handleChatCore({
|
||||
|
||||
log?.debug?.("FORMAT", `${sourceFormat} → ${targetFormat} | stream=${stream}`);
|
||||
|
||||
// ── Common input sanitization (runs for ALL paths including passthrough) ──
|
||||
// #291: Strip empty name fields from messages/input items
|
||||
// Upstream providers (OpenAI, Codex) reject name:"" with 400 errors.
|
||||
if (Array.isArray(body.messages)) {
|
||||
body.messages = body.messages.map((msg: Record<string, unknown>) => {
|
||||
if (msg.name === "") {
|
||||
const { name: _n, ...rest } = msg;
|
||||
return rest;
|
||||
}
|
||||
return msg;
|
||||
});
|
||||
}
|
||||
if (Array.isArray(body.input)) {
|
||||
body.input = body.input.map((item: Record<string, unknown>) => {
|
||||
if (item.name === "") {
|
||||
const { name: _n, ...rest } = item;
|
||||
return rest;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
// #346/#637: Strip tools with empty name
|
||||
// Clients sometimes forward tool definitions with empty names, causing
|
||||
// upstream providers to reject with 400 "Invalid 'tools[0].name': empty string."
|
||||
if (Array.isArray(body.tools)) {
|
||||
body.tools = body.tools.filter((tool: Record<string, unknown>) => {
|
||||
const fn = tool.function as Record<string, unknown> | undefined;
|
||||
const name = fn?.name ?? tool.name;
|
||||
return name && String(name).trim().length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Translate request (pass reqLogger for intermediate logging)
|
||||
let translatedBody = body;
|
||||
const isClaudePassthrough = sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE;
|
||||
@@ -219,55 +387,54 @@ export async function handleChatCore({
|
||||
translatedBody = { ...body, _nativeCodexPassthrough: true };
|
||||
log?.debug?.("FORMAT", "native codex passthrough enabled");
|
||||
} else if (isClaudePassthrough) {
|
||||
// Claude-to-Claude passthrough: forward body completely untouched.
|
||||
// No translation, no field stripping, no thinking normalization.
|
||||
// We are just a gateway -- do not interfere with the request in the slightest.
|
||||
translatedBody = { ...body };
|
||||
log?.debug?.("FORMAT", "claude->claude passthrough -- forwarding untouched");
|
||||
// Claude OAuth expects the same Claude Code prompt + structural normalization
|
||||
// as the OpenAI-compatible chat path. Round-trip through OpenAI to reuse the
|
||||
// working Claude translator instead of forwarding raw Messages payloads.
|
||||
const normalizeToolCallId = getModelNormalizeToolCallId(
|
||||
provider || "",
|
||||
model || "",
|
||||
sourceFormat
|
||||
);
|
||||
const preserveDeveloperRole = getModelPreserveOpenAIDeveloperRole(
|
||||
provider || "",
|
||||
model || "",
|
||||
sourceFormat
|
||||
);
|
||||
translatedBody = translateRequest(
|
||||
FORMATS.CLAUDE,
|
||||
FORMATS.OPENAI,
|
||||
model,
|
||||
{ ...body },
|
||||
stream,
|
||||
credentials,
|
||||
provider,
|
||||
reqLogger,
|
||||
{ normalizeToolCallId, preserveDeveloperRole }
|
||||
);
|
||||
translatedBody = translateRequest(
|
||||
FORMATS.OPENAI,
|
||||
FORMATS.CLAUDE,
|
||||
model,
|
||||
translatedBody,
|
||||
stream,
|
||||
credentials,
|
||||
provider,
|
||||
reqLogger,
|
||||
{ normalizeToolCallId, preserveDeveloperRole }
|
||||
);
|
||||
log?.debug?.("FORMAT", "claude->openai->claude normalized passthrough");
|
||||
} else {
|
||||
translatedBody = { ...body };
|
||||
|
||||
// Issue #199: Disable tool name prefix when routing Claude-format requests
|
||||
// to non-Claude backends (prefix causes tool name mismatches)
|
||||
const claudeProviders = ["claude", "anthropic"];
|
||||
if (targetFormat === FORMATS.CLAUDE && !claudeProviders.includes(provider?.toLowerCase?.())) {
|
||||
// Issue #199 + #618: Always disable tool name prefix in Claude passthrough.
|
||||
// The proxy_ prefix was designed for OpenAI→Claude translation to avoid
|
||||
// conflicts with Claude OAuth tools, but in the passthrough path the tools
|
||||
// are already in Claude format. Applying the prefix turns "Bash" into
|
||||
// "proxy_Bash", which Claude rejects ("No such tool available: proxy_Bash").
|
||||
if (targetFormat === FORMATS.CLAUDE) {
|
||||
translatedBody._disableToolPrefix = true;
|
||||
}
|
||||
|
||||
// ── #291: Strip empty name fields from messages/input items ──
|
||||
// Upstream providers (OpenAI, Codex) reject name:"" with 400 errors.
|
||||
// Clients like PocketPaw may forward empty name fields from assistant turns.
|
||||
if (Array.isArray(translatedBody.messages)) {
|
||||
translatedBody.messages = translatedBody.messages.map((msg: Record<string, unknown>) => {
|
||||
if (msg.name === "") {
|
||||
const { name: _n, ...rest } = msg;
|
||||
return rest;
|
||||
}
|
||||
return msg;
|
||||
});
|
||||
}
|
||||
if (Array.isArray(translatedBody.input)) {
|
||||
translatedBody.input = translatedBody.input.map((item: Record<string, unknown>) => {
|
||||
if (item.name === "") {
|
||||
const { name: _n, ...rest } = item;
|
||||
return rest;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
// ── #346: Strip tools with empty name ──
|
||||
// Claude Code sometimes forwards tool definitions with empty names, causing
|
||||
// OpenAI-compatible upstream providers to reject with:
|
||||
// "Invalid 'input[N].name': empty string. Expected minimum length 1."
|
||||
// Handles both OpenAI format ({ function: { name } }) and Anthropic format ({ name }).
|
||||
if (Array.isArray(translatedBody.tools)) {
|
||||
translatedBody.tools = translatedBody.tools.filter((tool: Record<string, unknown>) => {
|
||||
const fn = tool.function as Record<string, unknown> | undefined;
|
||||
const name = fn?.name ?? tool.name;
|
||||
return name && String(name).trim().length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Strip empty text content blocks from messages.
|
||||
// Anthropic API rejects {"type":"text","text":""} with 400 "text content blocks must be non-empty".
|
||||
// Some clients (LiteLLM passthrough, @ai-sdk/anthropic) may forward these empty blocks as-is.
|
||||
@@ -449,6 +616,7 @@ export async function handleChatCore({
|
||||
signal: streamController.signal,
|
||||
log,
|
||||
extendedContext,
|
||||
upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -457,7 +625,7 @@ export async function handleChatCore({
|
||||
// Non-stream responses need cloning for shared dedup consumers.
|
||||
const status = rawResult.response.status;
|
||||
const statusText = rawResult.response.statusText;
|
||||
const headers = Array.from(rawResult.response.headers.entries());
|
||||
const headers = Array.from(rawResult.response.headers.entries()) as [string, string][];
|
||||
const payload = await rawResult.response.text();
|
||||
|
||||
return {
|
||||
@@ -532,6 +700,7 @@ export async function handleChatCore({
|
||||
path: clientRawRequest?.endpoint || "/v1/chat/completions",
|
||||
status: error.name === "AbortError" ? 499 : HTTP_STATUS.BAD_GATEWAY,
|
||||
model,
|
||||
requestedModel,
|
||||
provider,
|
||||
connectionId,
|
||||
duration: Date.now() - startTime,
|
||||
@@ -579,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) {
|
||||
@@ -603,6 +775,8 @@ export async function handleChatCore({
|
||||
}
|
||||
}
|
||||
|
||||
await persistCodexQuotaState(providerResponse.headers, providerResponse.status);
|
||||
|
||||
// Check provider response - return error info for fallback handling
|
||||
if (!providerResponse.ok) {
|
||||
trackPendingRequest(model, provider, connectionId, false);
|
||||
@@ -610,6 +784,54 @@ export async function handleChatCore({
|
||||
providerResponse,
|
||||
provider
|
||||
);
|
||||
|
||||
// T06/T10/T36: classify provider errors and persist terminal account states.
|
||||
const errorType = classifyProviderError(statusCode, message);
|
||||
if (connectionId && errorType) {
|
||||
try {
|
||||
if (errorType === PROVIDER_ERROR_TYPES.FORBIDDEN) {
|
||||
await updateProviderConnection(connectionId, {
|
||||
isActive: false,
|
||||
testStatus: "banned",
|
||||
lastErrorType: errorType,
|
||||
lastError: message,
|
||||
errorCode: statusCode,
|
||||
});
|
||||
console.warn(
|
||||
`[provider] Node ${connectionId} banned (${statusCode}) — disabling permanently`
|
||||
);
|
||||
} else if (errorType === PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED) {
|
||||
await updateProviderConnection(connectionId, {
|
||||
testStatus: "credits_exhausted",
|
||||
lastErrorType: errorType,
|
||||
lastError: message,
|
||||
errorCode: statusCode,
|
||||
});
|
||||
console.warn(`[provider] Node ${connectionId} exhausted quota (${statusCode})`);
|
||||
} else if (errorType === PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED) {
|
||||
await updateProviderConnection(connectionId, {
|
||||
isActive: false,
|
||||
testStatus: "expired",
|
||||
lastErrorType: errorType,
|
||||
lastError: message,
|
||||
errorCode: statusCode,
|
||||
});
|
||||
console.warn(
|
||||
`[provider] Node ${connectionId} account deactivated (${statusCode}) — marked expired`
|
||||
);
|
||||
} else if (errorType === PROVIDER_ERROR_TYPES.UNAUTHORIZED) {
|
||||
// Normal 401 (token/session auth issue): keep account active for refresh/re-auth.
|
||||
await updateProviderConnection(connectionId, {
|
||||
lastErrorType: errorType,
|
||||
lastError: message,
|
||||
errorCode: statusCode,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Best-effort state update; request flow should continue with fallback handling.
|
||||
}
|
||||
}
|
||||
|
||||
appendRequestLog({ model, provider, connectionId, status: `FAILED ${statusCode}` }).catch(
|
||||
() => {}
|
||||
);
|
||||
@@ -618,6 +840,7 @@ export async function handleChatCore({
|
||||
path: clientRawRequest?.endpoint || "/v1/chat/completions",
|
||||
status: statusCode,
|
||||
model,
|
||||
requestedModel,
|
||||
provider,
|
||||
connectionId,
|
||||
duration: Date.now() - startTime,
|
||||
@@ -808,6 +1031,7 @@ export async function handleChatCore({
|
||||
path: clientRawRequest?.endpoint || "/v1/chat/completions",
|
||||
status: 200,
|
||||
model,
|
||||
requestedModel,
|
||||
provider,
|
||||
connectionId,
|
||||
duration: Date.now() - startTime,
|
||||
@@ -844,10 +1068,38 @@ 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
|
||||
if (sourceFormat === "claude" && !stream) {
|
||||
if (typeof translatedResponse?.choices?.[0]?.message?.content === "string") {
|
||||
translatedResponse.choices[0].message.content = stripMarkdownCodeFence(
|
||||
translatedResponse.choices[0].message.content
|
||||
) as string;
|
||||
}
|
||||
}
|
||||
|
||||
// T18: Normalize finish_reason to 'tool_calls' if tool calls are present
|
||||
if (translatedResponse?.choices) {
|
||||
for (const choice of translatedResponse.choices) {
|
||||
if (
|
||||
choice.message?.tool_calls &&
|
||||
choice.message.tool_calls.length > 0 &&
|
||||
choice.finish_reason !== "tool_calls"
|
||||
) {
|
||||
choice.finish_reason = "tool_calls";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize response for OpenAI SDK compatibility
|
||||
// Strips non-standard fields (x_groq, usage_breakdown, service_tier, etc.)
|
||||
// Extracts <think> tags into reasoning_content
|
||||
@@ -921,6 +1173,7 @@ export async function handleChatCore({
|
||||
path: clientRawRequest?.endpoint || "/v1/chat/completions",
|
||||
status: streamStatus || 200,
|
||||
model,
|
||||
requestedModel,
|
||||
provider,
|
||||
connectionId,
|
||||
duration: Date.now() - startTime,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
import { getImageProvider, parseImageModel } from "../config/imageRegistry.ts";
|
||||
import { mapImageSize } from "../translator/image/sizeMapper.ts";
|
||||
import { saveCallLog } from "@/lib/usageDb";
|
||||
import {
|
||||
submitComfyWorkflow,
|
||||
@@ -95,11 +96,21 @@ export async function handleImageGeneration({ body, credentials, log, resolvedPr
|
||||
});
|
||||
}
|
||||
|
||||
// Route to format-specific handler
|
||||
if (providerConfig.format === "gemini-image") {
|
||||
return handleGeminiImageGeneration({ model, providerConfig, body, credentials, log });
|
||||
}
|
||||
|
||||
if (providerConfig.format === "imagen3") {
|
||||
return handleImagen3ImageGeneration({
|
||||
model,
|
||||
provider,
|
||||
providerConfig,
|
||||
body,
|
||||
credentials,
|
||||
log,
|
||||
});
|
||||
}
|
||||
|
||||
if (providerConfig.format === "hyperbolic") {
|
||||
return handleHyperbolicImageGeneration({
|
||||
model,
|
||||
@@ -539,7 +550,7 @@ async function handleNanoBananaImageGeneration({
|
||||
? body.aspectRatio
|
||||
: typeof body.aspect_ratio === "string"
|
||||
? body.aspect_ratio
|
||||
: inferAspectRatioFromSize(body.size) || "1:1";
|
||||
: mapImageSize(body.size);
|
||||
|
||||
let resolution =
|
||||
typeof body.resolution === "string"
|
||||
@@ -856,18 +867,6 @@ async function normalizeNanoBananaTaskResult(taskData, body, log) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function inferAspectRatioFromSize(size) {
|
||||
if (typeof size !== "string") return null;
|
||||
const [wRaw, hRaw] = size.split("x");
|
||||
const width = Number(wRaw);
|
||||
const height = Number(hRaw);
|
||||
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
|
||||
|
||||
const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
|
||||
const div = gcd(Math.round(width), Math.round(height));
|
||||
return `${Math.round(width / div)}:${Math.round(height / div)}`;
|
||||
}
|
||||
|
||||
function inferResolutionFromSize(size) {
|
||||
if (typeof size !== "string") return null;
|
||||
const [wRaw, hRaw] = size.split("x");
|
||||
@@ -1081,3 +1080,129 @@ async function handleComfyUIImageGeneration({ model, provider, providerConfig, b
|
||||
return { success: false, status: 502, error: `Image provider error: ${err.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
async function handleImagen3ImageGeneration({
|
||||
model,
|
||||
provider,
|
||||
providerConfig,
|
||||
body,
|
||||
credentials,
|
||||
log,
|
||||
}: Imagen3ImageGenArgs) {
|
||||
const startTime = Date.now();
|
||||
const token = credentials.apiKey || credentials.accessToken;
|
||||
const aspectRatio = mapImageSize(body.size);
|
||||
|
||||
const upstreamBody = {
|
||||
prompt: body.prompt,
|
||||
aspect_ratio: aspectRatio,
|
||||
number_of_images: body.n ?? 1,
|
||||
};
|
||||
|
||||
if (log) {
|
||||
const promptPreview = String(body.prompt ?? "").slice(0, 60);
|
||||
log.info(
|
||||
"IMAGE",
|
||||
`${provider}/${model} (imagen3) | prompt: "${promptPreview}..." | aspect_ratio: ${aspectRatio}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(providerConfig.baseUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(upstreamBody),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
if (log)
|
||||
log.error("IMAGE", `${provider} error ${response.status}: ${errorText.slice(0, 200)}`);
|
||||
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/images/generations",
|
||||
status: response.status,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
error: errorText.slice(0, 500),
|
||||
requestBody: upstreamBody,
|
||||
}).catch(() => {});
|
||||
|
||||
return { success: false, status: response.status, error: errorText };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Normalize response to OpenAI format
|
||||
const images: Imagen3NormalizedImage[] = [];
|
||||
if (Array.isArray(data.images)) {
|
||||
images.push(
|
||||
...data.images.map((img: Record<string, unknown>) => ({
|
||||
b64_json: img.image ?? img.b64_json ?? img.url ?? img,
|
||||
revised_prompt: body.prompt,
|
||||
}))
|
||||
);
|
||||
} else if (Array.isArray(data.data)) {
|
||||
images.push(...data.data);
|
||||
} else if (data.url || data.b64_json || data.image) {
|
||||
images.push({
|
||||
b64_json: data.image || data.b64_json || data.url,
|
||||
url: data.url,
|
||||
revised_prompt: body.prompt,
|
||||
});
|
||||
}
|
||||
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/images/generations",
|
||||
status: 200,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
responseBody: { images_count: images.length },
|
||||
}).catch(() => {});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { created: data.created || Math.floor(Date.now() / 1000), data: images },
|
||||
};
|
||||
} catch (err: unknown) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
if (log) log.error("IMAGE", `${provider} fetch error: ${errMsg}`);
|
||||
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/images/generations",
|
||||
status: 502,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
error: errMsg,
|
||||
}).catch(() => {});
|
||||
|
||||
return { success: false, status: 502, error: `Image provider error: ${errMsg}` };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,13 @@ function toNumber(value: unknown): number | undefined {
|
||||
// Matches <think>...</think> blocks (greedy, dotAll)
|
||||
const THINK_TAG_REGEX = /<think>([\s\S]*?)<\/think>/gi;
|
||||
|
||||
// #638: Collapse runs of 3+ consecutive newlines into \n\n
|
||||
// Tool call responses from thinking models often accumulate excessive newlines
|
||||
const EXCESSIVE_NEWLINES = /\n{3,}/g;
|
||||
function collapseExcessiveNewlines(text: string): string {
|
||||
return text.replace(EXCESSIVE_NEWLINES, "\n\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract <think> blocks from text content and return separated parts.
|
||||
* @returns {{ content: string, thinking: string | null }}
|
||||
@@ -157,7 +164,7 @@ function sanitizeMessage(msg: unknown): unknown {
|
||||
// Handle content — extract <think> tags
|
||||
if (typeof msgRecord.content === "string") {
|
||||
const { content, thinking } = extractThinkingFromContent(msgRecord.content);
|
||||
sanitized.content = content;
|
||||
sanitized.content = collapseExcessiveNewlines(content);
|
||||
|
||||
// Set reasoning_content from <think> tags (if not already set)
|
||||
if (thinking && !msgRecord.reasoning_content) {
|
||||
@@ -172,6 +179,44 @@ function sanitizeMessage(msg: unknown): unknown {
|
||||
sanitized.reasoning_content = msgRecord.reasoning_content;
|
||||
}
|
||||
|
||||
// Handle 'reasoning' field alias (some providers use this instead of reasoning_content)
|
||||
if (
|
||||
msgRecord.reasoning &&
|
||||
typeof msgRecord.reasoning === "string" &&
|
||||
!sanitized.reasoning_content
|
||||
) {
|
||||
sanitized.reasoning_content = msgRecord.reasoning;
|
||||
}
|
||||
|
||||
// Handle reasoning_details[] array (StepFun/OpenRouter format)
|
||||
// Structure: [{ type: "reasoning.text", text: "...", format: "unknown", index: 0 }]
|
||||
if (Array.isArray(msgRecord.reasoning_details) && !sanitized.reasoning_content) {
|
||||
const reasoningParts: string[] = [];
|
||||
for (const detail of msgRecord.reasoning_details) {
|
||||
const detailObj = detail && typeof detail === "object" ? (detail as JsonRecord) : null;
|
||||
if (!detailObj) continue;
|
||||
const detailType = typeof detailObj.type === "string" ? detailObj.type : "";
|
||||
const detailText =
|
||||
typeof detailObj.text === "string"
|
||||
? detailObj.text
|
||||
: typeof detailObj.content === "string"
|
||||
? detailObj.content
|
||||
: "";
|
||||
if (
|
||||
detailText &&
|
||||
(detailType === "reasoning" ||
|
||||
detailType === "reasoning.text" ||
|
||||
detailType === "thinking" ||
|
||||
detailType === "")
|
||||
) {
|
||||
reasoningParts.push(detailText);
|
||||
}
|
||||
}
|
||||
if (reasoningParts.length > 0) {
|
||||
sanitized.reasoning_content = reasoningParts.join("");
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve tool_calls
|
||||
if (msgRecord.tool_calls) {
|
||||
sanitized.tool_calls = msgRecord.tool_calls;
|
||||
@@ -193,6 +238,17 @@ function sanitizeUsage(usage: unknown): unknown {
|
||||
if (!usageRecord) return usage;
|
||||
|
||||
const sanitized: JsonRecord = {};
|
||||
|
||||
// Cross-map Claude-style → OpenAI-style field names.
|
||||
// Some providers return input_tokens/output_tokens instead of prompt_tokens/completion_tokens.
|
||||
// Without this mapping, the whitelist filter below strips them, resulting in NaN/0 tokens (#617).
|
||||
if (usageRecord.input_tokens !== undefined && usageRecord.prompt_tokens === undefined) {
|
||||
usageRecord.prompt_tokens = usageRecord.input_tokens;
|
||||
}
|
||||
if (usageRecord.output_tokens !== undefined && usageRecord.completion_tokens === undefined) {
|
||||
usageRecord.completion_tokens = usageRecord.output_tokens;
|
||||
}
|
||||
|
||||
for (const key of ALLOWED_USAGE_FIELDS) {
|
||||
if (usageRecord[key] !== undefined) {
|
||||
sanitized[key] = usageRecord[key];
|
||||
@@ -255,9 +311,34 @@ export function sanitizeStreamingChunk(parsed: unknown): unknown {
|
||||
if (deltaRecord) {
|
||||
const delta: JsonRecord = {};
|
||||
if (deltaRecord.role !== undefined) delta.role = deltaRecord.role;
|
||||
if (deltaRecord.content !== undefined) delta.content = deltaRecord.content;
|
||||
if (deltaRecord.content !== undefined) {
|
||||
delta.content =
|
||||
typeof deltaRecord.content === "string"
|
||||
? collapseExcessiveNewlines(deltaRecord.content)
|
||||
: deltaRecord.content;
|
||||
}
|
||||
if (deltaRecord.reasoning_content !== undefined) {
|
||||
delta.reasoning_content = deltaRecord.reasoning_content;
|
||||
} else if (typeof deltaRecord.reasoning === "string" && deltaRecord.reasoning) {
|
||||
// Alias: some providers use 'reasoning' instead of 'reasoning_content'
|
||||
delta.reasoning_content = deltaRecord.reasoning;
|
||||
} else if (Array.isArray(deltaRecord.reasoning_details)) {
|
||||
// StepFun/OpenRouter: reasoning_details[{type:"reasoning.text", text:"..."}]
|
||||
const parts: string[] = [];
|
||||
for (const detail of deltaRecord.reasoning_details) {
|
||||
const d = detail && typeof detail === "object" ? (detail as JsonRecord) : null;
|
||||
if (!d) continue;
|
||||
const text =
|
||||
typeof d.text === "string"
|
||||
? d.text
|
||||
: typeof d.content === "string"
|
||||
? d.content
|
||||
: "";
|
||||
if (text) parts.push(text);
|
||||
}
|
||||
if (parts.length > 0) {
|
||||
delta.reasoning_content = parts.join("");
|
||||
}
|
||||
}
|
||||
if (deltaRecord.tool_calls !== undefined) delta.tool_calls = deltaRecord.tool_calls;
|
||||
if (deltaRecord.function_call !== undefined)
|
||||
|
||||
@@ -20,14 +20,62 @@ function toNumber(value: unknown, fallback = 0): number {
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
function extractMessageOutputText(item: JsonRecord): string {
|
||||
if (!Array.isArray(item.content)) return "";
|
||||
let text = "";
|
||||
for (const part of item.content) {
|
||||
if (!part || typeof part !== "object") continue;
|
||||
const partObj = toRecord(part);
|
||||
if (partObj.type === "output_text" && typeof partObj.text === "string") {
|
||||
text += partObj.text;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* T19: Pick the last non-empty message output text from Responses API output.
|
||||
* Falls back to the last message item even when all message texts are empty.
|
||||
*/
|
||||
function findBestMessageText(output: unknown[]): {
|
||||
text: string;
|
||||
selectedMessageIndex: number;
|
||||
messageItems: JsonRecord[];
|
||||
} {
|
||||
const messageItems = output
|
||||
.map((item) => toRecord(item))
|
||||
.filter((item) => item.type === "message" && Array.isArray(item.content));
|
||||
|
||||
for (let i = messageItems.length - 1; i >= 0; i -= 1) {
|
||||
const text = extractMessageOutputText(messageItems[i]);
|
||||
if (text.trim().length > 0) {
|
||||
return { text, selectedMessageIndex: i, messageItems };
|
||||
}
|
||||
}
|
||||
|
||||
if (messageItems.length > 0) {
|
||||
const lastIndex = messageItems.length - 1;
|
||||
return {
|
||||
text: extractMessageOutputText(messageItems[lastIndex]),
|
||||
selectedMessageIndex: lastIndex,
|
||||
messageItems,
|
||||
};
|
||||
}
|
||||
|
||||
return { text: "", selectedMessageIndex: -1, messageItems: [] };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@@ -44,7 +92,8 @@ export function translateNonStreamingResponse(
|
||||
const output = Array.isArray(response.output) ? response.output : [];
|
||||
const usage = toRecord(response.usage ?? responseRoot.usage);
|
||||
|
||||
let textContent = "";
|
||||
const messageSelection = findBestMessageText(output);
|
||||
let textContent = messageSelection.text;
|
||||
let reasoningContent = "";
|
||||
const toolCalls: JsonRecord[] = [];
|
||||
|
||||
@@ -56,9 +105,7 @@ export function translateNonStreamingResponse(
|
||||
for (const part of itemObj.content) {
|
||||
if (!part || typeof part !== "object") continue;
|
||||
const partObj = toRecord(part);
|
||||
if (partObj.type === "output_text" && typeof partObj.text === "string") {
|
||||
textContent += partObj.text;
|
||||
} else if (partObj.type === "summary_text" && typeof partObj.text === "string") {
|
||||
if (partObj.type === "summary_text" && typeof partObj.text === "string") {
|
||||
reasoningContent += partObj.text;
|
||||
}
|
||||
}
|
||||
@@ -78,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,
|
||||
},
|
||||
});
|
||||
@@ -103,6 +153,18 @@ export function translateNonStreamingResponse(
|
||||
message.content = "";
|
||||
}
|
||||
|
||||
if (process.env.DEBUG_RESPONSES_SSE_TO_JSON === "true") {
|
||||
console.log(
|
||||
`[ResponsesSSE] ${output.length} output items, ${messageSelection.messageItems.length} message items`
|
||||
);
|
||||
messageSelection.messageItems.forEach((item, idx) => {
|
||||
const textLen = extractMessageOutputText(item).length;
|
||||
console.log(` [${idx}] text length: ${textLen}`);
|
||||
});
|
||||
console.log(` → Selected message index: ${messageSelection.selectedMessageIndex}`);
|
||||
console.log(` → Final text content length: ${textContent.length}`);
|
||||
}
|
||||
|
||||
const createdAt = toNumber(response.created_at, Math.floor(Date.now() / 1000));
|
||||
const model = toString(response.model || responseRoot.model, "openai-responses");
|
||||
const finishReason = toolCalls.length > 0 ? "tool_calls" : "stop";
|
||||
@@ -278,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 || {}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,9 +23,25 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
const first = chunks[0];
|
||||
const contentParts = [];
|
||||
const reasoningParts = [];
|
||||
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: Record<string, unknown>) => {
|
||||
if (toolCall?.id) return `id:${toolCall.id}`;
|
||||
if (Number.isInteger(toolCall?.index)) return `idx:${toolCall.index}`;
|
||||
unknownToolCallSeq += 1;
|
||||
return `seq:${unknownToolCallSeq}`;
|
||||
};
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const choice = chunk?.choices?.[0];
|
||||
const delta = choice?.delta || {};
|
||||
@@ -36,6 +52,40 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
||||
reasoningParts.push(delta.reasoning_content);
|
||||
}
|
||||
|
||||
// T18: Accumulate tool calls correctly across streamed chunks
|
||||
if (delta.tool_calls) {
|
||||
for (const tc of delta.tool_calls) {
|
||||
const key = getToolCallKey(tc);
|
||||
const existing = accumulatedToolCalls.get(key);
|
||||
const deltaArgs = typeof tc?.function?.arguments === "string" ? tc.function.arguments : "";
|
||||
|
||||
if (!existing) {
|
||||
accumulatedToolCalls.set(key, {
|
||||
id: tc?.id ?? null,
|
||||
index: Number.isInteger(tc?.index) ? tc.index : accumulatedToolCalls.size,
|
||||
type: tc?.type || "function",
|
||||
function: {
|
||||
name: tc?.function?.name || "unknown",
|
||||
arguments: deltaArgs,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
existing.id = existing.id || tc?.id || null;
|
||||
if (!Number.isInteger(existing.index) && Number.isInteger(tc?.index)) {
|
||||
existing.index = tc.index;
|
||||
}
|
||||
if (tc?.function?.name && !existing.function?.name) {
|
||||
existing.function = existing.function || {};
|
||||
existing.function.name = tc.function.name;
|
||||
}
|
||||
existing.function = existing.function || {};
|
||||
existing.function.arguments = `${existing.function.arguments || ""}${deltaArgs}`;
|
||||
accumulatedToolCalls.set(key, existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (choice?.finish_reason) {
|
||||
finishReason = choice.finish_reason;
|
||||
}
|
||||
@@ -46,12 +96,22 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
|
||||
const message: Record<string, unknown> = {
|
||||
role: "assistant",
|
||||
content: contentParts.join(""),
|
||||
content: contentParts.length > 0 ? contentParts.join("") : null,
|
||||
};
|
||||
if (reasoningParts.length > 0) {
|
||||
message.reasoning_content = reasoningParts.join("");
|
||||
}
|
||||
|
||||
const finalToolCalls = [...accumulatedToolCalls.values()].filter(Boolean).sort((a, b) => {
|
||||
const ai = Number.isInteger(a?.index) ? a.index : 0;
|
||||
const bi = Number.isInteger(b?.index) ? b.index : 0;
|
||||
return ai - bi;
|
||||
});
|
||||
if (finalToolCalls.length > 0) {
|
||||
finishReason = "tool_calls"; // T18 normalization
|
||||
message.tool_calls = finalToolCalls;
|
||||
}
|
||||
|
||||
const result: Record<string, unknown> = {
|
||||
id: first.id || `chatcmpl-${Date.now()}`,
|
||||
object: "chat.completion",
|
||||
|
||||
@@ -36,6 +36,7 @@ export {
|
||||
// Services
|
||||
export {
|
||||
detectFormat,
|
||||
detectFormatFromEndpoint,
|
||||
getProviderConfig,
|
||||
buildProviderUrl,
|
||||
buildProviderHeaders,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* MCP HTTP Transport Layer — Singleton server + SSE/Streamable HTTP handlers.
|
||||
* MCP HTTP Transport Layer — session-aware handlers for SSE and Streamable HTTP.
|
||||
*
|
||||
* Runs the MCP server **inside** the Next.js process so it can be toggled
|
||||
* from the dashboard without requiring `omniroute --mcp`.
|
||||
@@ -14,58 +14,188 @@ import { createMcpServer } from "./server.ts";
|
||||
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
|
||||
// ────── Singleton ──────────────────────────────────────────
|
||||
let _sseServer: McpServer | null = null;
|
||||
let _sseTransport: WebStandardStreamableHTTPServerTransport | null = null;
|
||||
let _sseStartedAt: number | null = null;
|
||||
|
||||
let _server: McpServer | null = null;
|
||||
let _transport: WebStandardStreamableHTTPServerTransport | null = null;
|
||||
let _startedAt: number | null = null;
|
||||
let _activeTransportMode: "sse" | "streamable-http" | null = null;
|
||||
type StreamableSession = {
|
||||
sessionId: string;
|
||||
server: McpServer;
|
||||
transport: WebStandardStreamableHTTPServerTransport;
|
||||
startedAt: number;
|
||||
};
|
||||
|
||||
function ensureServer(mode: "sse" | "streamable-http"): {
|
||||
const _streamableSessions = new Map<string, StreamableSession>();
|
||||
|
||||
function closeSseTransport(): void {
|
||||
if (_sseTransport) {
|
||||
try {
|
||||
_sseTransport.close();
|
||||
} catch {
|
||||
// ignore shutdown errors
|
||||
}
|
||||
}
|
||||
_sseServer = null;
|
||||
_sseTransport = null;
|
||||
_sseStartedAt = null;
|
||||
}
|
||||
|
||||
function closeStreamableSession(sessionId: string): void {
|
||||
const session = _streamableSessions.get(sessionId);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
session.transport.close();
|
||||
} catch {
|
||||
// ignore shutdown errors
|
||||
}
|
||||
_streamableSessions.delete(sessionId);
|
||||
}
|
||||
|
||||
function closeAllStreamableSessions(): void {
|
||||
for (const sessionId of _streamableSessions.keys()) {
|
||||
closeStreamableSession(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSseServer(): {
|
||||
server: McpServer;
|
||||
transport: WebStandardStreamableHTTPServerTransport;
|
||||
} {
|
||||
if (_server && _transport && _activeTransportMode === mode) {
|
||||
return { server: _server, transport: _transport };
|
||||
if (_sseServer && _sseTransport) {
|
||||
return { server: _sseServer, transport: _sseTransport };
|
||||
}
|
||||
|
||||
// Shutdown previous if switching modes
|
||||
if (_transport) {
|
||||
try { _transport.close(); } catch { /* ignore */ }
|
||||
}
|
||||
closeAllStreamableSessions();
|
||||
|
||||
_server = createMcpServer();
|
||||
_transport = new WebStandardStreamableHTTPServerTransport({
|
||||
_sseServer = createMcpServer();
|
||||
_sseTransport = new WebStandardStreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
});
|
||||
_activeTransportMode = mode;
|
||||
_startedAt = Date.now();
|
||||
_sseStartedAt = Date.now();
|
||||
|
||||
// Connect server to transport (fire-and-forget, will be ready by first request)
|
||||
void _server.connect(_transport);
|
||||
void _sseServer.connect(_sseTransport);
|
||||
|
||||
console.log(`[MCP] HTTP transport started (${mode})`);
|
||||
return { server: _server, transport: _transport };
|
||||
console.log("[MCP] HTTP transport started (sse)");
|
||||
return { server: _sseServer, transport: _sseTransport };
|
||||
}
|
||||
|
||||
// ────── Streamable HTTP Handler ────────────────────────────
|
||||
function createStreamableSession(): StreamableSession {
|
||||
closeSseTransport();
|
||||
|
||||
const sessionId = randomUUID();
|
||||
const server = createMcpServer();
|
||||
const transport = new WebStandardStreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => sessionId,
|
||||
});
|
||||
const session = {
|
||||
sessionId,
|
||||
server,
|
||||
transport,
|
||||
startedAt: Date.now(),
|
||||
};
|
||||
|
||||
void server.connect(transport);
|
||||
_streamableSessions.set(sessionId, session);
|
||||
console.log(`[MCP] HTTP transport started (streamable-http:${sessionId})`);
|
||||
return session;
|
||||
}
|
||||
|
||||
async function isInitializeRequest(request: Request): Promise<boolean> {
|
||||
if (request.method !== "POST") {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const body = (await request.clone().json()) as { method?: unknown };
|
||||
return body?.method === "initialize";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function errorResponse(message: string, code: number, status = 400): Response {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
error: { code, message },
|
||||
id: null,
|
||||
}),
|
||||
{
|
||||
status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function withSessionHeader(response: Response, sessionId: string): Response {
|
||||
if (response.headers.get("mcp-session-id")) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const headers = new Headers(response.headers);
|
||||
headers.set("mcp-session-id", sessionId);
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
async function handleStreamableRequest(request: Request): Promise<Response> {
|
||||
const sessionId = request.headers.get("mcp-session-id");
|
||||
|
||||
if (sessionId) {
|
||||
const session = _streamableSessions.get(sessionId);
|
||||
if (!session) {
|
||||
return errorResponse("Bad Request: Unknown Mcp-Session-Id header", -32000);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await session.transport.handleRequest(request);
|
||||
if (request.method === "DELETE") {
|
||||
closeStreamableSession(sessionId);
|
||||
}
|
||||
return withSessionHeader(response, sessionId);
|
||||
} catch (err) {
|
||||
console.error("[MCP] Streamable HTTP error:", err);
|
||||
if (request.method === "DELETE") {
|
||||
closeStreamableSession(sessionId);
|
||||
}
|
||||
return new Response(JSON.stringify({ error: "MCP transport error" }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!(await isInitializeRequest(request))) {
|
||||
return errorResponse("Bad Request: Mcp-Session-Id header is required", -32000);
|
||||
}
|
||||
|
||||
const session = createStreamableSession();
|
||||
|
||||
try {
|
||||
const response = await session.transport.handleRequest(request);
|
||||
return withSessionHeader(response, session.sessionId);
|
||||
} catch (err) {
|
||||
closeStreamableSession(session.sessionId);
|
||||
console.error("[MCP] Streamable HTTP error:", err);
|
||||
return new Response(JSON.stringify({ error: "MCP transport error" }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Streamable HTTP requests (POST / GET / DELETE).
|
||||
* Used by the Next.js route at /api/mcp/stream.
|
||||
*/
|
||||
export async function handleMcpStreamableHTTP(request: Request): Promise<Response> {
|
||||
const { transport } = ensureServer("streamable-http");
|
||||
|
||||
try {
|
||||
return await transport.handleRequest(request);
|
||||
} catch (err) {
|
||||
console.error("[MCP] Streamable HTTP error:", err);
|
||||
return new Response(
|
||||
JSON.stringify({ error: "MCP transport error" }),
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
return handleStreamableRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,47 +204,47 @@ export async function handleMcpStreamableHTTP(request: Request): Promise<Respons
|
||||
* and POST for messages (the Streamable HTTP transport supports both patterns).
|
||||
*/
|
||||
export async function handleMcpSSE(request: Request): Promise<Response> {
|
||||
const { transport } = ensureServer("sse");
|
||||
const { transport } = ensureSseServer();
|
||||
|
||||
try {
|
||||
return await transport.handleRequest(request);
|
||||
} catch (err) {
|
||||
console.error("[MCP] SSE error:", err);
|
||||
return new Response(
|
||||
JSON.stringify({ error: "MCP SSE transport error" }),
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
return new Response(JSON.stringify({ error: "MCP SSE transport error" }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ────── Status & Lifecycle ─────────────────────────────────
|
||||
|
||||
export function getMcpHttpStatus(): {
|
||||
online: boolean;
|
||||
transport: string | null;
|
||||
startedAt: number | null;
|
||||
uptime: string | null;
|
||||
} {
|
||||
const online = _transport !== null && _activeTransportMode !== null;
|
||||
const streamableStartedAt =
|
||||
_streamableSessions.size > 0
|
||||
? Math.min(...Array.from(_streamableSessions.values(), (session) => session.startedAt))
|
||||
: null;
|
||||
const startedAt = streamableStartedAt ?? _sseStartedAt;
|
||||
const transport = _streamableSessions.size > 0 ? "streamable-http" : _sseTransport ? "sse" : null;
|
||||
const online = transport !== null;
|
||||
|
||||
return {
|
||||
online,
|
||||
transport: _activeTransportMode,
|
||||
startedAt: _startedAt,
|
||||
uptime: _startedAt ? `${Math.floor((Date.now() - _startedAt) / 1000)}s` : null,
|
||||
transport,
|
||||
startedAt,
|
||||
uptime: startedAt ? `${Math.floor((Date.now() - startedAt) / 1000)}s` : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function shutdownMcpHttp(): void {
|
||||
if (_transport) {
|
||||
try { _transport.close(); } catch { /* ignore */ }
|
||||
}
|
||||
_server = null;
|
||||
_transport = null;
|
||||
_activeTransportMode = null;
|
||||
_startedAt = null;
|
||||
closeSseTransport();
|
||||
closeAllStreamableSessions();
|
||||
console.log("[MCP] HTTP transport shutdown");
|
||||
}
|
||||
|
||||
export function isMcpHttpActive(): boolean {
|
||||
return _transport !== null;
|
||||
return _sseTransport !== null || _streamableSessions.size > 0;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const TaskInputSchema = z.object({
|
||||
role: z
|
||||
.enum(["coding", "review", "planning", "analysis", "debugging", "documentation"])
|
||||
.optional(),
|
||||
metadata: z.record(z.unknown()).optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
|
||||
export const CostEnvelopeSchema = z.object({
|
||||
@@ -120,7 +120,7 @@ export type PolicyVerdict = z.infer<typeof PolicyVerdictSchema>;
|
||||
export const JsonRpcRequestSchema = z.object({
|
||||
jsonrpc: z.literal("2.0"),
|
||||
method: z.enum(["message/send", "message/stream", "tasks/get", "tasks/cancel"]),
|
||||
params: z.record(z.unknown()),
|
||||
params: z.record(z.string(), z.unknown()),
|
||||
id: z.union([z.string(), z.number()]),
|
||||
});
|
||||
|
||||
@@ -151,7 +151,7 @@ export const MessageSendParamsSchema = z.object({
|
||||
message: z.object({
|
||||
role: z.string().default("user"),
|
||||
content: z.string(),
|
||||
metadata: z.record(z.unknown()).optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
}),
|
||||
config: z
|
||||
.object({
|
||||
|
||||
@@ -433,23 +433,48 @@ async function handleListModelsCatalog(args: { provider?: string; capability?: s
|
||||
const start = Date.now();
|
||||
try {
|
||||
let path = "/v1/models";
|
||||
const params = new URLSearchParams();
|
||||
if (args.provider) params.set("provider", args.provider);
|
||||
if (args.capability) params.set("capability", args.capability);
|
||||
if (params.toString()) path += `?${params.toString()}`;
|
||||
let isProviderSpecific = false;
|
||||
let source = "local_catalog";
|
||||
let warning: string | undefined;
|
||||
|
||||
if (args.provider && !args.capability) {
|
||||
// Use direct provider fetch to get real-time API status
|
||||
path = `/api/providers/${encodeURIComponent(args.provider)}/models`;
|
||||
isProviderSpecific = true;
|
||||
} else {
|
||||
const params = new URLSearchParams();
|
||||
if (args.provider) params.set("provider", args.provider);
|
||||
if (args.capability) params.set("capability", args.capability);
|
||||
if (params.toString()) path += `?${params.toString()}`;
|
||||
}
|
||||
|
||||
const raw = toRecord(await omniRouteFetch(path));
|
||||
|
||||
// If we used the direct provider endpoint
|
||||
let rawModels: unknown[] = [];
|
||||
if (isProviderSpecific) {
|
||||
rawModels = Array.isArray(raw.models) ? raw.models : [];
|
||||
source = typeof raw.source === "string" ? raw.source : "api";
|
||||
if (raw.warning) warning = String(raw.warning);
|
||||
} else {
|
||||
rawModels = Array.isArray(raw.data) ? raw.data : [];
|
||||
source = "local_catalog";
|
||||
// OmniRoute's global /v1/models is always a cached/local catalog
|
||||
}
|
||||
|
||||
const result = {
|
||||
models: toArray(raw.data).map((rawModel) => {
|
||||
models: rawModels.map((rawModel) => {
|
||||
const model = toRecord(rawModel);
|
||||
return {
|
||||
id: toString(model.id, ""),
|
||||
provider: toString(model.owned_by, toString(model.provider, "unknown")),
|
||||
provider: toString(model.owned_by, toString(model.provider, args.provider || "unknown")),
|
||||
capabilities: toStringArray(model.capabilities, ["chat"]),
|
||||
status: toString(model.status, "available"),
|
||||
pricing: model.pricing,
|
||||
};
|
||||
}),
|
||||
source,
|
||||
...(warning ? { warning } : {}),
|
||||
};
|
||||
|
||||
await logToolCall(
|
||||
|
||||
@@ -8,6 +8,46 @@ import {
|
||||
} from "../config/constants.ts";
|
||||
import { getProviderCategory } from "../config/providerRegistry.ts";
|
||||
|
||||
// T06 (sub2api PR #1037): Signals that indicate permanent account deactivation.
|
||||
// When a 401 body contains these strings, the account is permanently dead
|
||||
// and should NOT be retried after token refresh.
|
||||
export const ACCOUNT_DEACTIVATED_SIGNALS = [
|
||||
"account_deactivated",
|
||||
"account has been deactivated",
|
||||
"account has been disabled",
|
||||
"your account has been suspended",
|
||||
"this account is deactivated",
|
||||
];
|
||||
|
||||
// T10 (sub2api PR #1169): Signals that indicate billing credits are exhausted.
|
||||
// Distinct from rate-limit 429 — the account won't recover until credits are added.
|
||||
export const CREDITS_EXHAUSTED_SIGNALS = [
|
||||
"insufficient_quota",
|
||||
"billing_hard_limit_reached",
|
||||
"exceeded your current quota",
|
||||
"credit_balance_too_low",
|
||||
"your credit balance is too low",
|
||||
"credits exhausted",
|
||||
"out of credits",
|
||||
"payment required",
|
||||
];
|
||||
|
||||
/**
|
||||
* T06: Returns true if response body indicates the account is permanently deactivated.
|
||||
*/
|
||||
export function isAccountDeactivated(errorText: string): boolean {
|
||||
const lower = String(errorText || "").toLowerCase();
|
||||
return ACCOUNT_DEACTIVATED_SIGNALS.some((sig) => lower.includes(sig));
|
||||
}
|
||||
|
||||
/**
|
||||
* T10: Returns true if response body indicates credits/quota are permanently exhausted.
|
||||
*/
|
||||
export function isCreditsExhausted(errorText: string): boolean {
|
||||
const lower = String(errorText || "").toLowerCase();
|
||||
return CREDITS_EXHAUSTED_SIGNALS.some((sig) => lower.includes(sig));
|
||||
}
|
||||
|
||||
// ─── Provider Profile Helper ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -201,6 +241,14 @@ export function classifyErrorText(errorText) {
|
||||
) {
|
||||
return RateLimitReason.QUOTA_EXHAUSTED;
|
||||
}
|
||||
// T10: credits_exhausted signals
|
||||
if (isCreditsExhausted(errorText)) {
|
||||
return RateLimitReason.QUOTA_EXHAUSTED;
|
||||
}
|
||||
// T06: account_deactivated signals
|
||||
if (isAccountDeactivated(errorText)) {
|
||||
return RateLimitReason.AUTH_ERROR;
|
||||
}
|
||||
if (
|
||||
lower.includes("rate limit") ||
|
||||
lower.includes("too many requests") ||
|
||||
@@ -294,13 +342,67 @@ export function checkFallbackError(
|
||||
errorText,
|
||||
backoffLevel = 0,
|
||||
model = null,
|
||||
provider = null
|
||||
provider = null,
|
||||
headers = null
|
||||
) {
|
||||
const errorStr = (errorText || "").toString();
|
||||
|
||||
function parseResetFromHeaders(headers, errorStr = "") {
|
||||
if (!headers) return null;
|
||||
|
||||
// Retry-After header
|
||||
const retryAfter =
|
||||
typeof headers.get === "function"
|
||||
? headers.get("retry-after")
|
||||
: headers["retry-after"] || headers["Retry-After"];
|
||||
|
||||
if (retryAfter) {
|
||||
const seconds = parseInt(retryAfter, 10);
|
||||
if (!isNaN(seconds) && String(seconds) === String(retryAfter).trim()) {
|
||||
return Date.now() + seconds * 1000;
|
||||
}
|
||||
const date = new Date(retryAfter);
|
||||
if (!isNaN(date.getTime())) return date.getTime();
|
||||
}
|
||||
|
||||
// X-RateLimit-Reset
|
||||
const rlReset =
|
||||
typeof headers.get === "function"
|
||||
? headers.get("x-ratelimit-reset")
|
||||
: headers["x-ratelimit-reset"] || headers["X-RateLimit-Reset"];
|
||||
|
||||
if (rlReset) {
|
||||
const ts = parseInt(rlReset, 10);
|
||||
if (!isNaN(ts)) {
|
||||
return ts > 10000000000 ? ts : ts * 1000;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Check error message FIRST - specific patterns take priority over status codes
|
||||
if (errorText) {
|
||||
const errorStr = typeof errorText === "string" ? errorText : JSON.stringify(errorText);
|
||||
const lowerError = errorStr.toLowerCase();
|
||||
|
||||
// T06 (sub2api #1037): Permanent account deactivation — do NOT retry, mark as permanent failure
|
||||
if (isAccountDeactivated(errorStr)) {
|
||||
return {
|
||||
shouldFallback: true,
|
||||
cooldownMs: 365 * 24 * 60 * 60 * 1000, // 1 year = effectively permanent
|
||||
reason: RateLimitReason.AUTH_ERROR,
|
||||
permanent: true,
|
||||
};
|
||||
}
|
||||
|
||||
// T10 (sub2api #1169): Credits/quota exhausted — long cooldown, distinct from rate limit
|
||||
if (isCreditsExhausted(errorStr)) {
|
||||
return {
|
||||
shouldFallback: true,
|
||||
cooldownMs: COOLDOWN_MS.paymentRequired ?? 3600 * 1000, // 1h cooldown
|
||||
reason: RateLimitReason.QUOTA_EXHAUSTED,
|
||||
creditsExhausted: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (lowerError.includes("no credentials")) {
|
||||
return {
|
||||
shouldFallback: true,
|
||||
@@ -325,6 +427,18 @@ export function checkFallbackError(
|
||||
lowerError.includes("capacity") ||
|
||||
lowerError.includes("overloaded")
|
||||
) {
|
||||
const resetTime = parseResetFromHeaders(headers);
|
||||
if (resetTime) {
|
||||
const waitMs = resetTime - Date.now();
|
||||
if (waitMs > 60_000) {
|
||||
return {
|
||||
shouldFallback: true,
|
||||
cooldownMs: waitMs,
|
||||
newBackoffLevel: 0,
|
||||
reason: RateLimitReason.RATE_LIMIT_EXCEEDED,
|
||||
};
|
||||
}
|
||||
}
|
||||
const newLevel = Math.min(backoffLevel + 1, BACKOFF_CONFIG.maxLevel);
|
||||
const reason = classifyErrorText(errorStr);
|
||||
return {
|
||||
@@ -362,6 +476,19 @@ export function checkFallbackError(
|
||||
|
||||
// 429 - Rate limit with exponential backoff
|
||||
if (status === HTTP_STATUS.RATE_LIMITED) {
|
||||
const resetTime = parseResetFromHeaders(headers);
|
||||
if (resetTime) {
|
||||
const waitMs = resetTime - Date.now();
|
||||
if (waitMs > 60_000) {
|
||||
return {
|
||||
shouldFallback: true,
|
||||
cooldownMs: waitMs,
|
||||
newBackoffLevel: 0,
|
||||
reason: RateLimitReason.RATE_LIMIT_EXCEEDED,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const newLevel = Math.min(backoffLevel + 1, BACKOFF_CONFIG.maxLevel);
|
||||
return {
|
||||
shouldFallback: true,
|
||||
@@ -381,6 +508,19 @@ export function checkFallbackError(
|
||||
HTTP_STATUS.GATEWAY_TIMEOUT,
|
||||
];
|
||||
if (transientStatuses.includes(status)) {
|
||||
const resetTime = parseResetFromHeaders(headers, errorStr);
|
||||
if (resetTime) {
|
||||
const waitMs = resetTime - Date.now();
|
||||
if (waitMs > 60_000) {
|
||||
return {
|
||||
shouldFallback: true,
|
||||
cooldownMs: waitMs,
|
||||
newBackoffLevel: 0,
|
||||
reason: RateLimitReason.SERVER_ERROR,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const profile = provider ? getProviderProfile(provider) : null;
|
||||
const baseCooldown = profile?.transientCooldown ?? COOLDOWN_MS.transientInitial;
|
||||
const maxLevel = profile?.maxBackoffLevel ?? BACKOFF_CONFIG.maxLevel;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { resolveDataDir } from "../../../src/lib/dataPaths";
|
||||
|
||||
export interface AdaptationState {
|
||||
comboId: string;
|
||||
@@ -23,7 +24,7 @@ export interface AdaptationState {
|
||||
lastUpdated: string;
|
||||
}
|
||||
|
||||
const PERSISTENCE_DIR = path.join(process.cwd(), "data");
|
||||
const PERSISTENCE_DIR = resolveDataDir();
|
||||
const STATE_FILE = path.join(PERSISTENCE_DIR, "auto_combo_state.json");
|
||||
|
||||
let stateCache = new Map<string, AdaptationState>();
|
||||
|
||||
@@ -47,16 +47,16 @@ const DEFAULT_DETECTION_PATTERNS = [
|
||||
|
||||
const DEFAULT_DEGRADATION_MAP: Record<string, string> = {
|
||||
// Premium → Cheap alternatives
|
||||
"claude-opus-4-6": "gemini-2.5-flash",
|
||||
"claude-opus-4-6-thinking": "gemini-2.5-flash",
|
||||
"claude-opus-4-5-20251101": "gemini-2.5-flash",
|
||||
"claude-sonnet-4-5-20250929": "gemini-2.5-flash",
|
||||
"claude-sonnet-4-20250514": "gemini-2.5-flash",
|
||||
"claude-sonnet-4": "gemini-2.5-flash",
|
||||
"gemini-3.1-pro": "gemini-3.1-flash",
|
||||
"gemini-3.1-pro-high": "gemini-3.1-flash",
|
||||
"claude-opus-4-6": "gemini-3-flash",
|
||||
"claude-opus-4-6-thinking": "gemini-3-flash",
|
||||
"claude-opus-4-5-20251101": "gemini-3-flash",
|
||||
"claude-sonnet-4-5-20250929": "gemini-3-flash",
|
||||
"claude-sonnet-4-20250514": "gemini-3-flash",
|
||||
"claude-sonnet-4": "gemini-3-flash",
|
||||
"gemini-3.1-pro": "gemini-3-flash",
|
||||
"gemini-3.1-pro-high": "gemini-3-flash",
|
||||
"gemini-3-pro-preview": "gemini-3-flash-preview",
|
||||
"gemini-2.5-pro": "gemini-2.5-flash",
|
||||
"gemini-2.5-pro": "gemini-3-flash",
|
||||
"gpt-4o": "gpt-4o-mini",
|
||||
"gpt-5": "gpt-5-mini",
|
||||
"gpt-5.1": "gpt-5-mini",
|
||||
@@ -114,12 +114,93 @@ interface BackgroundMessage {
|
||||
interface BackgroundTaskBody {
|
||||
messages?: BackgroundMessage[];
|
||||
input?: BackgroundMessage[];
|
||||
max_tokens?: unknown;
|
||||
max_completion_tokens?: unknown;
|
||||
max_output_tokens?: unknown;
|
||||
}
|
||||
|
||||
function toMessageArray(value: unknown): BackgroundMessage[] {
|
||||
return Array.isArray(value) ? (value as BackgroundMessage[]) : [];
|
||||
}
|
||||
|
||||
function toFiniteNumber(value: unknown): number | null {
|
||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function headerValue(headers: Record<string, string> | null, key: string): string {
|
||||
if (!headers) return "";
|
||||
const value = headers[key] ?? headers[key.toLowerCase()] ?? headers[key.toUpperCase()];
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reason label when request is a background/utility task.
|
||||
*
|
||||
* @param {object} body - Request body
|
||||
* @param {object} [headers] - Request headers (optional)
|
||||
* @returns {string | null} Reason label or null when not detected
|
||||
*/
|
||||
export function getBackgroundTaskReason(
|
||||
body: BackgroundTaskBody | unknown,
|
||||
headers: Record<string, string> | null = null
|
||||
): string | null {
|
||||
if (!body || typeof body !== "object") return null;
|
||||
const typedBody = body as BackgroundTaskBody;
|
||||
|
||||
// 1. Check explicit header
|
||||
if (headers) {
|
||||
const taskType = headerValue(headers, "x-task-type");
|
||||
const priority = headerValue(headers, "x-request-priority");
|
||||
const initiator = headerValue(headers, "x-initiator");
|
||||
const explicitValue = [taskType, priority, initiator].find(Boolean);
|
||||
if (explicitValue && explicitValue.toLowerCase() === "background") {
|
||||
return "header_background";
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Very low max tokens usually indicates utility/background tasks
|
||||
const maxTokens = toFiniteNumber(
|
||||
typedBody.max_tokens ?? typedBody.max_completion_tokens ?? typedBody.max_output_tokens
|
||||
);
|
||||
if (maxTokens !== null && maxTokens > 0 && maxTokens < 50) {
|
||||
return "low_max_tokens";
|
||||
}
|
||||
|
||||
// 3. Check system prompt for background task patterns
|
||||
const messages = toMessageArray(typedBody.messages ?? typedBody.input ?? []);
|
||||
if (!Array.isArray(messages) || messages.length === 0) return null;
|
||||
|
||||
// Find system message
|
||||
const systemMsg = messages.find(
|
||||
(message: BackgroundMessage) => message.role === "system" || message.role === "developer"
|
||||
);
|
||||
if (!systemMsg) return null;
|
||||
|
||||
const systemContent =
|
||||
typeof systemMsg.content === "string" ? systemMsg.content.toLowerCase() : "";
|
||||
|
||||
if (!systemContent) return null;
|
||||
|
||||
// Check against detection patterns
|
||||
const matched = _config.detectionPatterns.some((pattern) =>
|
||||
systemContent.includes(pattern.toLowerCase())
|
||||
);
|
||||
|
||||
if (!matched) return null;
|
||||
|
||||
// 4. Additional heuristic: background tasks typically have very few messages
|
||||
// (system + 1-2 user messages)
|
||||
const userMessages = messages.filter((message: BackgroundMessage) => message.role === "user");
|
||||
if (userMessages.length > 3) return null; // Too many turns for a background task
|
||||
|
||||
return "system_prompt_pattern";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a request is a background/utility task.
|
||||
*
|
||||
@@ -131,44 +212,7 @@ export function isBackgroundTask(
|
||||
body: BackgroundTaskBody | unknown,
|
||||
headers: Record<string, string> | null = null
|
||||
): boolean {
|
||||
if (!body || typeof body !== "object") return false;
|
||||
const typedBody = body as BackgroundTaskBody;
|
||||
|
||||
// 1. Check explicit header
|
||||
if (headers) {
|
||||
const priority =
|
||||
headers["x-request-priority"] || headers["X-Request-Priority"] || headers["x-initiator"];
|
||||
if (priority === "background" || priority === "Background") return true;
|
||||
}
|
||||
|
||||
// 2. Check system prompt for background task patterns
|
||||
const messages = toMessageArray(typedBody.messages ?? typedBody.input ?? []);
|
||||
if (!Array.isArray(messages) || messages.length === 0) return false;
|
||||
|
||||
// Find system message
|
||||
const systemMsg = messages.find(
|
||||
(message: BackgroundMessage) => message.role === "system" || message.role === "developer"
|
||||
);
|
||||
if (!systemMsg) return false;
|
||||
|
||||
const systemContent =
|
||||
typeof systemMsg.content === "string" ? systemMsg.content.toLowerCase() : "";
|
||||
|
||||
if (!systemContent) return false;
|
||||
|
||||
// Check against detection patterns
|
||||
const matched = _config.detectionPatterns.some((pattern) =>
|
||||
systemContent.includes(pattern.toLowerCase())
|
||||
);
|
||||
|
||||
if (!matched) return false;
|
||||
|
||||
// 3. Additional heuristic: background tasks typically have very few messages
|
||||
// (system + 1-2 user messages)
|
||||
const userMessages = messages.filter((message: BackgroundMessage) => message.role === "user");
|
||||
if (userMessages.length > 3) return false; // Too many turns for a background task
|
||||
|
||||
return true;
|
||||
return getBackgroundTaskReason(body, headers) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -522,10 +522,47 @@ 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.
|
||||
//
|
||||
// IMPORTANT: Use a SEPARATE TextDecoder from the transform stream above.
|
||||
// The transform stream's decoder accumulates UTF-8 state; reusing it here
|
||||
// would corrupt multi-byte characters split across chunk boundaries.
|
||||
const sanitizeDecoder = new TextDecoder();
|
||||
const sanitize = new TransformStream({
|
||||
transform(chunk, controller) {
|
||||
const text = sanitizeDecoder.decode(chunk, { stream: true });
|
||||
if (text) {
|
||||
if (text.includes("<omniModel>")) {
|
||||
const cleaned = text.replace(/\n?<omniModel>[^<]+<\/omniModel>\n?/g, "");
|
||||
if (cleaned) controller.enqueue(encoder.encode(cleaned));
|
||||
} else {
|
||||
controller.enqueue(encoder.encode(text));
|
||||
}
|
||||
}
|
||||
},
|
||||
flush(controller) {
|
||||
const tail = sanitizeDecoder.decode();
|
||||
if (tail) {
|
||||
if (tail.includes("<omniModel>")) {
|
||||
const cleaned = tail.replace(/\n?<omniModel>[^<]+<\/omniModel>\n?/g, "");
|
||||
if (cleaned) controller.enqueue(encoder.encode(cleaned));
|
||||
} else {
|
||||
controller.enqueue(encoder.encode(tail));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -841,7 +878,8 @@ export async function handleComboChat({
|
||||
errorText,
|
||||
0,
|
||||
null,
|
||||
provider
|
||||
provider,
|
||||
result.headers
|
||||
);
|
||||
|
||||
// Record failure in circuit breaker for transient errors
|
||||
@@ -865,6 +903,12 @@ export async function handleComboChat({
|
||||
if (!lastStatus) lastStatus = result.status;
|
||||
if (i > 0) fallbackCount++;
|
||||
log.warn("COMBO", `Model ${modelStr} failed, trying next`, { status: result.status });
|
||||
|
||||
if ([502, 503, 504].includes(result.status) && cooldownMs > 0 && cooldownMs <= 5000) {
|
||||
log.info("COMBO", `Waiting ${cooldownMs}ms before fallback to next model`);
|
||||
await new Promise((r) => setTimeout(r, cooldownMs));
|
||||
}
|
||||
|
||||
break; // Move to next model
|
||||
}
|
||||
}
|
||||
@@ -886,7 +930,20 @@ export async function handleComboChat({
|
||||
);
|
||||
}
|
||||
|
||||
const status = lastStatus || 406;
|
||||
if (!lastStatus) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
message: "Service temporarily unavailable: all upstream accounts are inactive",
|
||||
type: "service_unavailable",
|
||||
code: "ALL_ACCOUNTS_INACTIVE",
|
||||
},
|
||||
}),
|
||||
{ status: 503, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const status = lastStatus;
|
||||
const msg = lastError || "All combo models unavailable";
|
||||
|
||||
if (earliestRetryAfter) {
|
||||
@@ -941,7 +998,7 @@ async function handleRoundRobinCombo({
|
||||
|
||||
const modelCount = orderedModels.length;
|
||||
if (modelCount === 0) {
|
||||
return unavailableResponse(406, "Round-robin combo has no models");
|
||||
return unavailableResponse(503, "Round-robin combo has no models");
|
||||
}
|
||||
|
||||
// Get and increment atomic counter
|
||||
@@ -1077,7 +1134,8 @@ async function handleRoundRobinCombo({
|
||||
errorText,
|
||||
0,
|
||||
null,
|
||||
provider
|
||||
provider,
|
||||
result.headers
|
||||
);
|
||||
|
||||
// Transient errors → mark in semaphore AND record circuit breaker failure
|
||||
@@ -1106,6 +1164,12 @@ async function handleRoundRobinCombo({
|
||||
if (!lastStatus) lastStatus = result.status;
|
||||
if (offset > 0) fallbackCount++;
|
||||
log.warn("COMBO-RR", `${modelStr} failed, trying next model`, { status: result.status });
|
||||
|
||||
if ([502, 503, 504].includes(result.status) && cooldownMs > 0 && cooldownMs <= 5000) {
|
||||
log.info("COMBO-RR", `Waiting ${cooldownMs}ms before fallback to next model`);
|
||||
await new Promise((r) => setTimeout(r, cooldownMs));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
@@ -1136,7 +1200,20 @@ async function handleRoundRobinCombo({
|
||||
);
|
||||
}
|
||||
|
||||
const status = lastStatus || 406;
|
||||
if (!lastStatus) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
message: "Service temporarily unavailable: all upstream accounts are inactive",
|
||||
type: "service_unavailable",
|
||||
code: "ALL_ACCOUNTS_INACTIVE",
|
||||
},
|
||||
}),
|
||||
{ status: 503, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const status = lastStatus;
|
||||
const msg = lastError || "All round-robin combo models unavailable";
|
||||
|
||||
if (earliestRetryAfter) {
|
||||
|
||||
@@ -9,7 +9,7 @@ const DEFAULT_COMBO_CONFIG = {
|
||||
strategy: "priority",
|
||||
maxRetries: 1,
|
||||
retryDelayMs: 2000,
|
||||
timeoutMs: 120000,
|
||||
timeoutMs: 600000,
|
||||
concurrencyPerModel: 3, // max simultaneous requests per model (round-robin)
|
||||
queueTimeoutMs: 30000, // max wait time in semaphore queue (round-robin)
|
||||
healthCheckEnabled: true,
|
||||
|
||||
@@ -5,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { isAccountDeactivated, isCreditsExhausted } from "./accountFallback.ts";
|
||||
|
||||
export const PROVIDER_ERROR_TYPES = {
|
||||
RATE_LIMITED: "rate_limited", // 429 — transient, retry with backoff
|
||||
UNAUTHORIZED: "unauthorized", // 401 — token expired, refresh
|
||||
ACCOUNT_DEACTIVATED: "account_deactivated", // 401 + deactivation signal
|
||||
FORBIDDEN: "forbidden", // 403 — account banned/revoked, disable node
|
||||
SERVER_ERROR: "server_error", // 500/502/503 — retry limited
|
||||
QUOTA_EXHAUSTED: "quota_exhausted", // 402/429/400 + billing signals
|
||||
};
|
||||
|
||||
function responseBodyToString(responseBody: unknown): string {
|
||||
if (typeof responseBody === "string") return responseBody;
|
||||
if (responseBody !== null && typeof responseBody === "object") {
|
||||
try {
|
||||
return JSON.stringify(responseBody);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function classifyProviderError(statusCode: number, responseBody: unknown): string | null {
|
||||
const bodyStr = responseBodyToString(responseBody);
|
||||
const creditsExhausted = isCreditsExhausted(bodyStr);
|
||||
const accountDeactivated = isAccountDeactivated(bodyStr);
|
||||
|
||||
// T10: credits exhausted is terminal and can appear as 400/402/429 depending on provider.
|
||||
if (
|
||||
creditsExhausted &&
|
||||
(statusCode === 400 || statusCode === 402 || statusCode === 429 || statusCode === 403)
|
||||
) {
|
||||
return PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED;
|
||||
}
|
||||
|
||||
if (statusCode === 429) {
|
||||
return PROVIDER_ERROR_TYPES.RATE_LIMITED;
|
||||
}
|
||||
|
||||
// T06: only deactivation-like 401s should be treated as permanent account expiry.
|
||||
if (statusCode === 401) {
|
||||
return accountDeactivated
|
||||
? PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED
|
||||
: PROVIDER_ERROR_TYPES.UNAUTHORIZED;
|
||||
}
|
||||
|
||||
if (statusCode === 402) return PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED;
|
||||
if (statusCode === 403) return PROVIDER_ERROR_TYPES.FORBIDDEN;
|
||||
if (statusCode >= 500) return PROVIDER_ERROR_TYPES.SERVER_ERROR;
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
* IP-based access control with blacklist, whitelist, priority modes, and temporary bans.
|
||||
*/
|
||||
|
||||
import { isIP } from "node:net";
|
||||
|
||||
// In-memory IP lists
|
||||
let _config = {
|
||||
enabled: false,
|
||||
@@ -161,10 +163,10 @@ export function createIPFilterMiddleware() {
|
||||
*/
|
||||
export function checkRequestIP(request) {
|
||||
const ip =
|
||||
request.headers?.get?.("x-forwarded-for")?.split(",")[0].trim() ||
|
||||
request.headers?.get?.("x-real-ip") ||
|
||||
request.headers?.get?.("cf-connecting-ip") ||
|
||||
request.ip ||
|
||||
pickFirstValidIp(request.headers?.get?.("cf-connecting-ip")) ||
|
||||
pickFirstValidIp(request.headers?.get?.("x-forwarded-for")) ||
|
||||
pickFirstValidIp(request.headers?.get?.("x-real-ip")) ||
|
||||
normalizeIP(request.ip || "") ||
|
||||
"unknown";
|
||||
return checkIP(ip);
|
||||
}
|
||||
@@ -177,6 +179,18 @@ function normalizeIP(ip) {
|
||||
return ip.replace(/^::ffff:/, "").trim();
|
||||
}
|
||||
|
||||
function pickFirstValidIp(rawValue) {
|
||||
if (typeof rawValue !== "string" || rawValue.trim().length === 0) return null;
|
||||
const candidates = rawValue.split(",");
|
||||
for (const candidate of candidates) {
|
||||
const normalized = normalizeIP(candidate);
|
||||
if (normalized && isIP(normalized) !== 0) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function matchesAny(ip, ipSet) {
|
||||
// Direct match
|
||||
if (ipSet.has(ip)) return true;
|
||||
@@ -225,12 +239,13 @@ function matchesWildcard(ip, pattern) {
|
||||
}
|
||||
|
||||
function extractClientIP(req) {
|
||||
const headers = req.headers || {};
|
||||
return (
|
||||
req.headers?.["x-forwarded-for"]?.split(",")[0].trim() ||
|
||||
req.headers?.["x-real-ip"] ||
|
||||
req.headers?.["cf-connecting-ip"] ||
|
||||
req.socket?.remoteAddress ||
|
||||
req.ip ||
|
||||
pickFirstValidIp(headers["cf-connecting-ip"]) ||
|
||||
pickFirstValidIp(headers["x-forwarded-for"]) ||
|
||||
pickFirstValidIp(headers["x-real-ip"]) ||
|
||||
pickFirstValidIp(req.socket?.remoteAddress) ||
|
||||
pickFirstValidIp(req.ip) ||
|
||||
"unknown"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,6 +18,8 @@ const BUILT_IN_ALIASES: Record<string, string> = {
|
||||
"gemini-1.5-flash": "gemini-2.5-flash",
|
||||
"gemini-1.0-pro": "gemini-2.5-pro",
|
||||
"gemini-2.0-flash": "gemini-2.5-flash",
|
||||
"gemini-3-pro-high": "gemini-3.1-pro-high",
|
||||
"gemini-3-pro-low": "gemini-3.1-pro-low",
|
||||
|
||||
// Claude legacy → current
|
||||
"claude-3-opus-20240229": "claude-opus-4-20250514",
|
||||
|
||||
@@ -101,6 +101,7 @@ const MODEL_UNAVAILABLE_FRAGMENTS = [
|
||||
"does not support",
|
||||
"not enabled for",
|
||||
"access to model",
|
||||
"improperly formed request", // Kiro 400 (model unavailable)
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,27 @@ function buildAnthropicCompatibleUrl(baseUrl) {
|
||||
return `${normalized}/messages`;
|
||||
}
|
||||
|
||||
// Detect request format from endpoint first when the route is known.
|
||||
// This avoids ambiguous bodies like OpenAI /chat/completions requests that also
|
||||
// contain max_tokens or Claude model names.
|
||||
export function detectFormatFromEndpoint(body, endpointPath = "") {
|
||||
const path = String(endpointPath || "");
|
||||
|
||||
if (/\/responses(?=\/|$)/i.test(path) || /^responses(?=\/|$)/i.test(path)) {
|
||||
return "openai-responses";
|
||||
}
|
||||
|
||||
if (/\/messages(?=\/|$)/i.test(path) || /^messages(?=\/|$)/i.test(path)) {
|
||||
return "claude";
|
||||
}
|
||||
|
||||
if (/\/(?:chat\/completions|completions)(?=\/|$)/i.test(path) || /^(?:chat\/completions|completions)(?=\/|$)/i.test(path)) {
|
||||
return "openai";
|
||||
}
|
||||
|
||||
return detectFormat(body);
|
||||
}
|
||||
|
||||
// Detect request format from body structure
|
||||
export function detectFormat(body) {
|
||||
// OpenAI Responses API:
|
||||
|
||||
@@ -12,6 +12,7 @@ import Bottleneck from "bottleneck";
|
||||
import { parseRetryAfterFromBody, lockModel } from "./accountFallback.ts";
|
||||
import { getProviderCategory } from "../config/providerRegistry.ts";
|
||||
import { DEFAULT_API_LIMITS } from "../config/constants.ts";
|
||||
import { getCodexRateLimitKey } from "../executors/codex.ts";
|
||||
|
||||
interface LearnedLimitEntry {
|
||||
provider: string;
|
||||
@@ -195,8 +196,15 @@ export function isRateLimitEnabled(connectionId) {
|
||||
/**
|
||||
* Get or create a limiter for a given provider+connection combination
|
||||
*/
|
||||
function getLimiterKey(provider, connectionId, model = null) {
|
||||
if (provider === "codex" && model) {
|
||||
return `${provider}:${getCodexRateLimitKey(connectionId, model)}`;
|
||||
}
|
||||
return `${provider}:${connectionId}`;
|
||||
}
|
||||
|
||||
function getLimiter(provider, connectionId, model = null) {
|
||||
const key = model ? `${provider}:${connectionId}:${model}` : `${provider}:${connectionId}`;
|
||||
const key = getLimiterKey(provider, connectionId, model);
|
||||
|
||||
if (!limiters.has(key)) {
|
||||
const limiter = new Bottleneck({
|
||||
@@ -235,7 +243,7 @@ export async function withRateLimit(provider, connectionId, model, fn) {
|
||||
return fn();
|
||||
}
|
||||
|
||||
const limiter = getLimiter(provider, connectionId, null);
|
||||
const limiter = getLimiter(provider, connectionId, model);
|
||||
return limiter.schedule(fn);
|
||||
}
|
||||
|
||||
@@ -320,7 +328,7 @@ export function updateFromHeaders(provider, connectionId, headers, status, model
|
||||
if (!enabledConnections.has(connectionId)) return;
|
||||
if (!headers) return;
|
||||
|
||||
const limiter = getLimiter(provider, connectionId, null);
|
||||
const limiter = getLimiter(provider, connectionId, model);
|
||||
const headerMap =
|
||||
provider === "claude" || provider === "anthropic" ? ANTHROPIC_HEADERS : STANDARD_HEADERS;
|
||||
|
||||
@@ -340,7 +348,7 @@ export function updateFromHeaders(provider, connectionId, headers, status, model
|
||||
if (status === 429) {
|
||||
const retryAfterMs = parseResetTime(retryAfterStr) || 60000; // Default 60s
|
||||
const counts = limiter.counts();
|
||||
const limiterKey = `${provider}:${connectionId}`;
|
||||
const limiterKey = getLimiterKey(provider, connectionId, model);
|
||||
console.log(
|
||||
`🚫 [RATE-LIMIT] ${provider}:${connectionId.slice(0, 8)} — 429 received, pausing for ${Math.ceil(retryAfterMs / 1000)}s, dropping ${counts.QUEUED} queued request(s)`
|
||||
);
|
||||
@@ -397,7 +405,12 @@ export function updateFromHeaders(provider, connectionId, headers, status, model
|
||||
limiter.updateSettings(updates);
|
||||
|
||||
// Persist learned limits (debounced)
|
||||
recordLearnedLimit(provider, connectionId, { limit, remaining, minTime: updates.minTime });
|
||||
recordLearnedLimit(
|
||||
provider,
|
||||
connectionId,
|
||||
{ limit, remaining, minTime: updates.minTime },
|
||||
model
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,9 +472,10 @@ export function getLearnedLimits() {
|
||||
function recordLearnedLimit(
|
||||
provider: string,
|
||||
connectionId: string,
|
||||
limits: Partial<Omit<LearnedLimitEntry, "provider" | "connectionId" | "lastUpdated">>
|
||||
limits: Partial<Omit<LearnedLimitEntry, "provider" | "connectionId" | "lastUpdated">>,
|
||||
model: string | null = null
|
||||
) {
|
||||
const key = `${provider}:${connectionId}`;
|
||||
const key = getLimiterKey(provider, connectionId, model);
|
||||
learnedLimits[key] = {
|
||||
...limits,
|
||||
provider,
|
||||
|
||||
@@ -41,7 +41,13 @@ const SESSION_TTL_MS = 30 * 60 * 1000;
|
||||
const _cleanupTimer = setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const [key, entry] of sessions) {
|
||||
if (now - entry.lastActive > SESSION_TTL_MS) sessions.delete(key);
|
||||
if (now - entry.lastActive > SESSION_TTL_MS) {
|
||||
sessions.delete(key);
|
||||
for (const [apiKeyId, sessionSet] of activeSessionsByKey) {
|
||||
sessionSet.delete(key);
|
||||
if (sessionSet.size === 0) activeSessionsByKey.delete(apiKeyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 60_000);
|
||||
_cleanupTimer.unref();
|
||||
@@ -173,6 +179,114 @@ export function getActiveSessions(): Array<SessionEntry & { sessionId: string; a
|
||||
*/
|
||||
export function clearSessions(): void {
|
||||
sessions.clear();
|
||||
activeSessionsByKey.clear();
|
||||
}
|
||||
|
||||
// ─── T08: Per-API-Key Session Limit ─────────────────────────────────────────
|
||||
// Tracks concurrent sticky sessions per API key and enforces max_sessions limits.
|
||||
// Ref: sub2api PR #634 (fix: stabilize session hash + add user-level session limit)
|
||||
|
||||
// Map: apiKeyId → Set<sessionId>
|
||||
const activeSessionsByKey = new Map<string, Set<string>>();
|
||||
|
||||
/**
|
||||
* T08: Get the number of currently active sessions for an API key.
|
||||
* @param apiKeyId - The API key's UUID from the database
|
||||
*/
|
||||
export function getActiveSessionCountForKey(apiKeyId: string): number {
|
||||
return activeSessionsByKey.get(apiKeyId)?.size ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot of active session counts per API key.
|
||||
*/
|
||||
export function getAllActiveSessionCountsByKey(): Record<string, number> {
|
||||
const out: Record<string, number> = {};
|
||||
for (const [apiKeyId, sessionIds] of activeSessionsByKey) {
|
||||
out[apiKeyId] = sessionIds.size;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* T08: Register a session as belonging to an API key.
|
||||
* Call this after session creation is allowed (i.e., limit check passed).
|
||||
*/
|
||||
export function registerKeySession(apiKeyId: string, sessionId: string): void {
|
||||
if (!activeSessionsByKey.has(apiKeyId)) {
|
||||
activeSessionsByKey.set(apiKeyId, new Set());
|
||||
}
|
||||
activeSessionsByKey.get(apiKeyId)!.add(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given session is already registered for an API key.
|
||||
*/
|
||||
export function isSessionRegisteredForKey(apiKeyId: string, sessionId: string): boolean {
|
||||
return activeSessionsByKey.get(apiKeyId)?.has(sessionId) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* T08: Unregister a session from an API key's active set.
|
||||
* Call this when the request closes or the session TTL expires.
|
||||
*/
|
||||
export function unregisterKeySession(apiKeyId: string, sessionId: string): void {
|
||||
activeSessionsByKey.get(apiKeyId)?.delete(sessionId);
|
||||
// Clean up empty sets to avoid memory leaks
|
||||
if (activeSessionsByKey.get(apiKeyId)?.size === 0) {
|
||||
activeSessionsByKey.delete(apiKeyId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* T08: Check whether adding a new session would exceed the key's max_sessions limit.
|
||||
* Returns null if allowed, or an error object to return as a 429 response.
|
||||
*
|
||||
* @param apiKeyId - The API key's UUID
|
||||
* @param maxSessions - The limit from the DB (0 = unlimited)
|
||||
*/
|
||||
export function checkSessionLimit(
|
||||
apiKeyId: string,
|
||||
maxSessions: number
|
||||
): { code: "SESSION_LIMIT_EXCEEDED"; message: string; limit: number; current: number } | null {
|
||||
if (!maxSessions || maxSessions <= 0) return null; // unlimited
|
||||
const current = getActiveSessionCountForKey(apiKeyId);
|
||||
if (current < maxSessions) return null;
|
||||
return {
|
||||
code: "SESSION_LIMIT_EXCEEDED",
|
||||
message:
|
||||
`You have reached the maximum number of active sessions (${maxSessions}). ` +
|
||||
`Please close unused sessions or wait for them to expire.`,
|
||||
limit: maxSessions,
|
||||
current,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* T04: Extract an external session ID from request headers.
|
||||
* Accepts both hyphenated and underscore forms for Nginx compatibility.
|
||||
* Nginx drops headers with underscores by default — use `underscores_in_headers on`
|
||||
* in nginx.conf, or use X-Session-Id (hyphenated) which passes cleanly.
|
||||
*
|
||||
* Ref: sub2api README + PR #634
|
||||
*
|
||||
* @param headers - Request headers (Headers object or plain object with .get())
|
||||
* @returns External session ID with "ext:" prefix, or null
|
||||
*/
|
||||
export function extractExternalSessionId(
|
||||
headers: Headers | { get?: (n: string) => string | null } | null | undefined
|
||||
): string | null {
|
||||
if (!headers || typeof (headers as Headers).get !== "function") return null;
|
||||
const h = headers as Headers;
|
||||
const raw =
|
||||
h.get("x-session-id") ?? // Preferred: hyphenated (passes through Nginx)
|
||||
h.get("x_session_id") ?? // Underscore variant (direct HTTP / custom clients)
|
||||
h.get("x-omniroute-session") ?? // OmniRoute-specific form
|
||||
h.get("session-id") ?? // Bare session-id
|
||||
null;
|
||||
if (!raw || !raw.trim()) return null;
|
||||
// Prefix "ext:" to ensure no collision with internal SHA-256 hash IDs
|
||||
return `ext:${raw.trim().slice(0, 64)}`; // max 64 chars to avoid abuse
|
||||
}
|
||||
|
||||
// ─── Internal Helpers ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -132,6 +132,11 @@ export function detectAndLearn(
|
||||
}
|
||||
}
|
||||
|
||||
// Collapse excessive consecutive newlines left after tag removal (fixes #626)
|
||||
if (found.length > 0) {
|
||||
cleaned = cleaned.replace(/\n{3,}/g, "\n\n");
|
||||
}
|
||||
|
||||
return { found, cleaned: cleaned.trim() || cleaned };
|
||||
}
|
||||
|
||||
|
||||
@@ -13,21 +13,27 @@ export const ThinkingMode = {
|
||||
ADAPTIVE: "adaptive", // Scale based on request complexity
|
||||
};
|
||||
|
||||
import { capThinkingBudget, getDefaultThinkingBudget } from "@/shared/constants/modelSpecs";
|
||||
|
||||
// Effort → budget token mapping
|
||||
export const EFFORT_BUDGETS = {
|
||||
none: 0,
|
||||
low: 1024,
|
||||
medium: 10240,
|
||||
high: 131072,
|
||||
high: 131072, // Handled globally by capThinkingBudget later
|
||||
max: 131072, // T11: Claude "max" / "xhigh" — full budget
|
||||
xhigh: 131072, // T11: explicit alias used internally
|
||||
};
|
||||
|
||||
// thinkingLevel string → budget token mapping
|
||||
// Used when clients send string-based thinking levels (e.g., VS Code Copilot)
|
||||
export const THINKING_LEVEL_MAP = {
|
||||
none: 0,
|
||||
low: 1024,
|
||||
medium: 10240,
|
||||
high: 131072,
|
||||
low: 4096,
|
||||
medium: 8192,
|
||||
high: 24576,
|
||||
max: 131072, // T11: max = full Claude budget (sub2api: xhigh)
|
||||
xhigh: 131072, // T11: explicit xhigh alias
|
||||
};
|
||||
|
||||
// Default config (passthrough = backward compatible)
|
||||
@@ -68,8 +74,9 @@ export function normalizeThinkingLevel(body) {
|
||||
|
||||
// Handle top-level thinkingLevel or thinking_level string fields
|
||||
const levelStr = result.thinkingLevel || result.thinking_level;
|
||||
if (typeof levelStr === "string" && THINKING_LEVEL_MAP[levelStr] !== undefined) {
|
||||
const budget = THINKING_LEVEL_MAP[levelStr];
|
||||
if (typeof levelStr === "string" && THINKING_LEVEL_MAP[levelStr.toLowerCase()] !== undefined) {
|
||||
const rawBudget = THINKING_LEVEL_MAP[levelStr.toLowerCase()];
|
||||
const budget = capThinkingBudget(result.model || "", rawBudget);
|
||||
// Convert to Claude thinking format as canonical representation
|
||||
result.thinking = {
|
||||
type: budget > 0 ? "enabled" : "disabled",
|
||||
@@ -83,15 +90,22 @@ export function normalizeThinkingLevel(body) {
|
||||
const geminiLevel =
|
||||
result.generationConfig?.thinkingConfig?.thinkingLevel ||
|
||||
result.generationConfig?.thinking_config?.thinkingLevel;
|
||||
if (typeof geminiLevel === "string" && THINKING_LEVEL_MAP[geminiLevel] !== undefined) {
|
||||
const budget = THINKING_LEVEL_MAP[geminiLevel];
|
||||
if (
|
||||
typeof geminiLevel === "string" &&
|
||||
THINKING_LEVEL_MAP[geminiLevel.toLowerCase()] !== undefined
|
||||
) {
|
||||
const rawBudget = THINKING_LEVEL_MAP[geminiLevel.toLowerCase()];
|
||||
const budget = capThinkingBudget(result.model || "", rawBudget);
|
||||
result.generationConfig = {
|
||||
...result.generationConfig,
|
||||
thinking_config: { thinking_budget: budget },
|
||||
thinkingConfig: { ...result.generationConfig.thinkingConfig, thinkingBudget: budget },
|
||||
};
|
||||
// Clean up camelCase variant if it was the source
|
||||
// Clean up string variants
|
||||
if (result.generationConfig.thinkingConfig) {
|
||||
delete result.generationConfig.thinkingConfig;
|
||||
delete result.generationConfig.thinkingConfig.thinkingLevel;
|
||||
}
|
||||
if (result.generationConfig.thinking_config) {
|
||||
delete result.generationConfig.thinking_config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +132,7 @@ export function ensureThinkingConfig(body) {
|
||||
const result = { ...body };
|
||||
result.thinking = {
|
||||
type: "enabled",
|
||||
budget_tokens: EFFORT_BUDGETS.medium, // 10240 default
|
||||
budget_tokens: getDefaultThinkingBudget(model) || EFFORT_BUDGETS.medium,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
@@ -198,7 +212,7 @@ function setCustomBudget(body, budget) {
|
||||
};
|
||||
}
|
||||
|
||||
// OpenAI reasoning_effort mapping
|
||||
// OpenAI reasoning_effort mapping (T11: add 'max' tier for full budget)
|
||||
if (result.reasoning_effort !== undefined || result.reasoning !== undefined) {
|
||||
if (budget <= 0) {
|
||||
delete result.reasoning_effort;
|
||||
@@ -207,8 +221,10 @@ function setCustomBudget(body, budget) {
|
||||
result.reasoning_effort = "low";
|
||||
} else if (budget <= 10240) {
|
||||
result.reasoning_effort = "medium";
|
||||
} else {
|
||||
} else if (budget < 131072) {
|
||||
result.reasoning_effort = "high";
|
||||
} else {
|
||||
result.reasoning_effort = "max"; // T11: full budget → "max"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,8 +267,11 @@ function applyAdaptiveBudget(body, cfg) {
|
||||
if (toolCount > 3) multiplier += 0.5;
|
||||
if (lastMsgLength > 2000) multiplier += 0.3;
|
||||
|
||||
const baseBudget = EFFORT_BUDGETS[cfg.effortLevel] || EFFORT_BUDGETS.medium;
|
||||
const budget = Math.min(Math.ceil(baseBudget * multiplier), 131072);
|
||||
const baseBudget =
|
||||
EFFORT_BUDGETS[cfg.effortLevel] ||
|
||||
getDefaultThinkingBudget(body.model || "") ||
|
||||
EFFORT_BUDGETS.medium;
|
||||
const budget = capThinkingBudget(body.model || "", Math.ceil(baseBudget * multiplier));
|
||||
|
||||
return setCustomBudget(body, budget);
|
||||
}
|
||||
|
||||
+23
-10
@@ -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,
|
||||
};
|
||||
@@ -843,7 +856,7 @@ async function getCodexUsage(accessToken, providerSpecificData: Record<string, u
|
||||
quotas,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to fetch Codex usage: ${error.message}`);
|
||||
return { message: `Failed to fetch Codex usage: ${(error as Error).message}` };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user