From 2e6dfda90d00d6da52857ac16513681aac5efd05 Mon Sep 17 00:00:00 2001 From: lunkerchen Date: Wed, 22 Jul 2026 17:43:46 +0800 Subject: [PATCH] i18n(zh-TW): complete Traditional Chinese (Taiwan) translation overhaul (#8024) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UI messages: 100% coverage (was ~78%). Translated 2871 missing keys, eliminated all 420 __MISSING__ placeholders. 0 remaining. - Terminology: 提供商→提供者 (493 fixes), 令牌→權杖 (88 fixes), 激活→啟用 (1 fix), 配置→設定 (13 context-aware fixes) in UI messages - CLI locale: same terminology pass (69 fixes) - Docs: translated all 26 zh-TW docs (was 3/26). USER_GUIDE, ARCHITECTURE, API_REFERENCE, ENVIRONMENT and 20 more now in Traditional Chinese. - Preserved variable placeholders, ICU plurals, markdown, code blocks Co-authored-by: lunkerchen Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --- bin/cli/locales/zh-TW.json | 138 +- docs/i18n/zh-TW/CHANGELOG.md | 2539 +++++++-- docs/i18n/zh-TW/CODE_OF_CONDUCT.md | 142 +- docs/i18n/zh-TW/CONTRIBUTING.md | 399 +- docs/i18n/zh-TW/GEMINI.md | 57 +- docs/i18n/zh-TW/SECURITY.md | 255 +- .../zh-TW/docs/architecture/ARCHITECTURE.md | 1332 +++-- .../architecture/CODEBASE_DOCUMENTATION.md | 1291 +++-- .../zh-TW/docs/cloudflare-zero-trust-guide.md | 132 +- .../i18n/zh-TW/docs/features/context-relay.md | 130 +- docs/i18n/zh-TW/docs/frameworks/A2A-SERVER.md | 183 +- docs/i18n/zh-TW/docs/frameworks/MCP-SERVER.md | 438 +- docs/i18n/zh-TW/docs/guides/FEATURES.md | 307 +- docs/i18n/zh-TW/docs/guides/I18N.md | 600 +- .../i18n/zh-TW/docs/guides/TROUBLESHOOTING.md | 571 +- docs/i18n/zh-TW/docs/guides/UNINSTALL.md | 134 +- docs/i18n/zh-TW/docs/guides/USER_GUIDE.md | 1136 ++-- docs/i18n/zh-TW/docs/ops/COVERAGE_PLAN.md | 209 +- .../zh-TW/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md | 311 +- docs/i18n/zh-TW/docs/ops/RELEASE_CHECKLIST.md | 387 +- .../zh-TW/docs/ops/VM_DEPLOYMENT_GUIDE.md | 255 +- .../zh-TW/docs/reference/API_REFERENCE.md | 1346 ++++- docs/i18n/zh-TW/docs/reference/CLI-TOOLS.md | 788 ++- docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md | 916 ++- docs/i18n/zh-TW/docs/routing/AUTO-COMBO.md | 644 ++- src/i18n/messages/zh-TW.json | 4915 +++++++++++++---- 26 files changed, 13968 insertions(+), 5587 deletions(-) diff --git a/bin/cli/locales/zh-TW.json b/bin/cli/locales/zh-TW.json index 41a8974219..6880b8fb77 100644 --- a/bin/cli/locales/zh-TW.json +++ b/bin/cli/locales/zh-TW.json @@ -29,16 +29,16 @@ "setup": { "title": "OmniRoute 設定", "passwordPrompt": "管理員密碼", - "providerPrompt": "預設提供商(留空跳過)", + "providerPrompt": "預設提供者(留空跳過)", "done": "設定完成", "passwordSet": "管理員密碼已配置", - "providerSet": "提供商已配置:{name}", - "testingProvider": "正在測試提供商連線:{name}", - "testPassed": "提供商測試通過", - "testFailed": "提供商測試失敗:{error}", + "providerSet": "提供者已配置:{name}", + "testingProvider": "正在測試提供者連線:{name}", + "testPassed": "提供者測試通過", + "testFailed": "提供者測試失敗:{error}", "loginEnabled": "登入:已啟用(密碼已更新)", "loginDisabled": "登入:已停用", - "providerInfo": "提供商:{info}" + "providerInfo": "提供者:{info}" }, "doctor": { "title": "OmniRoute 診斷", @@ -52,29 +52,29 @@ "warnings": "{count} 個警告 — 見上文。" }, "providers": { - "title": "提供商", - "noProviders": "未配置提供商。執行:omniroute setup", + "title": "提供者", + "noProviders": "未配置提供者。執行:omniroute setup", "testing": "正在測試 {name}...", - "available": "{count} 個提供商可用", + "available": "{count} 個提供者可用", "connected": "已連線", "disconnected": "未連線", "validationFailed": "驗證失敗:{error}", "metrics": { - "description": "顯示提供商效能指標(延遲、成功率、成本)", - "provider": "按提供商 ID 篩選", + "description": "顯示提供者效能指標(延遲、成功率、成本)", + "provider": "按提供者 ID 篩選", "connection_id": "按連線 ID 篩選", "period": "時間範圍:1h|6h|24h|7d|30d(預設:24h)", "metric": "關注特定指標欄位", "sort": "按欄位排序(降序)", "limit": "最大行數(預設:50)", "watch": "每 5 秒重新整理(即時模式)", - "compare": "逗號分隔的提供商 ID,並排比較" + "compare": "逗號分隔的提供者 ID,並排比較" }, "metric_single": { "description": "獲取特定連線的單個指標值" }, "rotate": { - "description": "輪換提供商連線的上游 API 金鑰", + "description": "輪換提供者連線的上游 API 金鑰", "newKeyOpt": "新 API 金鑰值(避免使用:優先使用 --from-env)", "fromEnvOpt": "從環境變數 VAR 讀取新金鑰", "oauthOpt": "改為觸發 OAuth 重新認證流程", @@ -89,18 +89,18 @@ "testFailed": "輪換後測試失敗:{error}" }, "status": { - "description": "顯示所有提供商連線的金鑰健康狀態(期限、過期、冷卻)", - "providerOpt": "按提供商名稱篩選", - "header": "ID 提供商 名稱 過期狀態 測試狀態 冷卻至", - "noData": "沒有可用的提供商連線資料。", + "description": "顯示所有提供者連線的金鑰健康狀態(期限、過期、冷卻)", + "providerOpt": "按提供者名稱篩選", + "header": "ID 提供者 名稱 過期狀態 測試狀態 冷卻至", + "noData": "沒有可用的提供者連線資料。", "requiresServer": "providers status 需要 OmniRoute 伺服器正在執行。" } }, "keys": { "title": "API 金鑰", - "addDescription": "為提供商新增或更新 API 金鑰", + "addDescription": "為提供者新增或更新 API 金鑰", "listDescription": "列出所有已配置的 API 金鑰", - "removeDescription": "移除提供商的 API 金鑰", + "removeDescription": "移除提供者的 API 金鑰", "regenerateDescription": "重新生成 OmniRoute API 金鑰", "revokeDescription": "撤銷 OmniRoute API 金鑰", "revealDescription": "顯示未掩碼的 API 金鑰值", @@ -122,10 +122,10 @@ "revoked": "金鑰 {id} 已撤銷。", "rotated": "金鑰 {id} 已輪換。新金鑰 ID:{newId}", "revealWarning": "⚠ 這將顯示完整的未掩碼金鑰。請確保您的螢幕不被人看到。", - "providerRequired": "需要提供商。", + "providerRequired": "需要提供者。", "keyRequired": "需要 API 金鑰。", "stdinEmpty": "未通過標準輸入提供 API 金鑰。", - "unknownProvider": "未知提供商:{provider}", + "unknownProvider": "未知提供者:{provider}", "policy": { "title": "金鑰策略", "showDescription": "顯示金鑰的速率/成本策略", @@ -151,7 +151,7 @@ "model": "模型 ID(預設:auto)", "system": "系統提示", "combo": "強制指定組合名稱", - "max_tokens": "響應最大令牌數", + "max_tokens": "響應最大權杖數", "responses_api": "使用 /v1/responses 而不是 /v1/chat/completions", "raw": "列印接收到的原始 SSE 行", "debug": "在 stderr 中列印每塊的時間資訊", @@ -165,7 +165,7 @@ "analytics": { "description": "顯示彙總使用分析", "period": "時間範圍:1d|7d|30d|90d|ytd|all(預設:30d)", - "provider": "按提供商 ID 篩選" + "provider": "按提供者 ID 篩選" }, "budget": { "description": "管理成本預算", @@ -175,8 +175,8 @@ } }, "quota": { - "description": "顯示提供商配額使用情況", - "provider": "按提供商 ID 篩選", + "description": "顯示提供者配額使用情況", + "provider": "按提供者 ID 篩選", "check": "顯示新請求是否有可用配額" }, "logs": { @@ -201,7 +201,7 @@ } }, "cost": { - "description": "按提供商、模型、組合或 API 金鑰顯示成本報告", + "description": "按提供者、模型、組合或 API 金鑰顯示成本報告", "period": "時間範圍:1d|7d|30d|90d|ytd|all(預設:30d)", "since": "開始日期(ISO 格式,例如 2026-01-01)— 覆蓋 --period", "until": "結束日期(ISO 格式)", @@ -210,12 +210,12 @@ "limit": "顯示的最大行數(預設:100)" }, "simulate": { - "description": "模擬路由(空執行)— 顯示將選擇哪些提供商而不呼叫上游", + "description": "模擬路由(空執行)— 顯示將選擇哪些提供者而不呼叫上游", "file": "從 JSON 檔案載入完整請求體", "model": "模型 ID(預設:auto)", "combo": "強制指定組合名稱", "reasoning": "推理努力級別(low|medium|high)", - "thinking": "擴充套件思維令牌預算", + "thinking": "擴充套件思維權杖預算", "explain": "在 stderr 中列印回退樹和成本範圍", "noCombo": "未找到匹配的組合。使用以下命令配置:omniroute combo create" }, @@ -225,11 +225,11 @@ "stdin": "從標準輸入讀取提示", "system": "系統提示", "model": "模型 ID(預設:auto)", - "max_tokens": "響應最大令牌數", + "max_tokens": "響應最大權杖數", "temperature": "取樣溫度(0–2)", "top_p": "Top-p 核取樣", "reasoning_effort": "推理努力級別(low|medium|high)", - "thinking_budget": "擴充套件思維令牌預算", + "thinking_budget": "擴充套件思維權杖預算", "combo": "強制指定組合名稱", "responses_api": "使用 /v1/responses 而不是 /v1/chat/completions", "stream": "增量流式傳輸響應", @@ -301,7 +301,7 @@ "cost": "成本(24h):" }, "quota": { - "description": "顯示提供商配額使用情況", + "description": "顯示提供者配額使用情況", "noServer": "伺服器未執行。啟動:omniroute serve", "noData": "沒有可用的配額資訊。" }, @@ -315,12 +315,12 @@ "description": "一鍵啟動本地 Redis 容器(Podman 或 Docker),用於 OmniRoute 快取和配額跟蹤" }, "test": { - "description": "測試提供商連線", + "description": "測試提供者連線", "noServer": "伺服器未執行。啟動:omniroute serve", "testing": "正在測試 {provider} / {model}...", "passed": "連線成功!", "failed": "連線失敗:{error}", - "allProvidersOpt": "測試所有已配置的提供商", + "allProvidersOpt": "測試所有已配置的提供者", "latencyOpt": "顯示延遲測量值(平均/最小/最大 毫秒)", "repeatOpt": "重複測試 N 次並彙總結果", "compareOpt": "逗號分隔的要比較的模型(例如 gpt-4o,claude-3-5-sonnet)", @@ -328,7 +328,7 @@ "saved": "結果已儲存至 {path}", "compareTitle": "模型比較", "compareMinTwo": "--compare 需要至少兩個模型(逗號分隔)", - "noProviders": "未配置提供商。新增:omniroute keys add" + "noProviders": "未配置提供者。新增:omniroute keys add" }, "update": { "checking": "正在檢查更新...", @@ -445,7 +445,7 @@ "description": "配置壓縮設定", "engine": "壓縮引擎(caveman|rtk|hybrid|none)", "caveman_agg": "Caveman 激程序度 0.0–1.0", - "rtk_budget": "RTK 令牌預算", + "rtk_budget": "RTK 權杖預算", "language_pack": "要啟用的語言包" }, "engine": { @@ -509,7 +509,7 @@ }, "models": { "description": "列出可用模型(需要伺服器)", - "search": "按 ID、名稱、提供商或描述篩選模型", + "search": "按 ID、名稱、提供者或描述篩選模型", "noServer": "伺服器未執行。啟動:omniroute serve", "noModels": "未找到模型。" }, @@ -621,7 +621,7 @@ "type": "按記憶型別篩選(user|feedback|project|reference)", "limit": "最大結果數(預設:20)", "api_key": "按 API 金鑰篩選", - "token_budget": "限制結果中的總令牌數" + "token_budget": "限制結果中的總權杖數" }, "add": { "description": "新增新的記憶條目", @@ -656,25 +656,25 @@ } }, "oauth": { - "description": "管理 OAuth 提供商連線", + "description": "管理 OAuth 提供者連線", "providers": { - "description": "列出支援 OAuth 的提供商及其流程型別" + "description": "列出支援 OAuth 的提供者及其流程型別" }, "start": { - "description": "為提供商啟動 OAuth 授權流程", - "provider": "提供商 ID(gemini, copilot, cursor, ...)", + "description": "為提供者啟動 OAuth 授權流程", + "provider": "提供者 ID(gemini, copilot, cursor, ...)", "no_browser": "僅列印 URL — 不開啟瀏覽器", "import_system": "從本地系統配置自動匯入憑據", - "social": "社交登入提供商(google|github)— kiro 需要", + "social": "社交登入提供者(google|github)— kiro 需要", "timeout": "等待授權超時時間(毫秒,預設:300000)" }, "status": { "description": "列出活動的 OAuth 連線", - "provider": "按提供商 ID 篩選" + "provider": "按提供者 ID 篩選" }, "revoke": { "description": "撤銷 OAuth 連線", - "provider": "要撤銷的提供商 ID", + "provider": "要撤銷的提供者 ID", "connection_id": "按 ID 撤銷特定連線", "yes": "跳過確認提示" } @@ -884,20 +884,20 @@ "description": "管理模型定價資料", "sync": { "description": "從上游同步價格", - "provider": "按提供商篩選", + "provider": "按提供者篩選", "force": "強制重新同步" }, "list": { - "provider": "按提供商篩選", + "provider": "按提供者篩選", "model": "按模型篩選", "limit": "最大結果數" }, "defaults": { "description": "管理預設定價", - "input": "每 1M 令牌的輸入成本(美元)", - "output": "每 1M 令牌的輸出成本(美元)", - "cacheRead": "每 1M 令牌的快取讀取成本(美元)", - "cacheWrite": "每 1M 令牌的快取寫入成本(美元)" + "input": "每 1M 權杖的輸入成本(美元)", + "output": "每 1M 權杖的輸出成本(美元)", + "cacheRead": "每 1M 權杖的快取讀取成本(美元)", + "cacheWrite": "每 1M 權杖的快取寫入成本(美元)" }, "diff": { "description": "顯示與上游價格的差異", @@ -907,25 +907,25 @@ "resilience": { "description": "檢查和管理彈性機制", "status": { - "provider": "按提供商篩選" + "provider": "按提供者篩選" }, "breakers": { - "provider": "按提供商篩選" + "provider": "按提供者篩選" }, "cooldowns": { - "provider": "按提供商篩選", + "provider": "按提供者篩選", "connectionId": "按連線 ID 篩選" }, "lockouts": { - "provider": "按提供商篩選", + "provider": "按提供者篩選", "model": "按模型篩選" }, "reset": { "description": "重置斷路器/冷卻狀態", - "provider": "要重置的提供商", + "provider": "要重置的提供者", "connectionId": "要重置的連線 ID", "model": "要重置鎖定狀態的模型", - "allCooldowns": "重置提供商的所有冷卻", + "allCooldowns": "重置提供者的所有冷卻", "yes": "跳過確認" }, "profile": { @@ -940,13 +940,13 @@ } }, "nodes": { - "description": "管理提供商節點(端點)", + "description": "管理提供者節點(端點)", "list": { - "provider": "按提供商篩選", + "provider": "按提供者篩選", "enabled": "僅顯示已啟用的節點" }, "add": { - "provider": "提供商名稱", + "provider": "提供者名稱", "baseUrl": "節點的基礎 URL", "name": "節點名稱", "weight": "負載均衡權重", @@ -965,7 +965,7 @@ }, "validate": { "baseUrl": "要驗證的 URL", - "provider": "要驗證的提供商" + "provider": "要驗證的提供者" }, "test": { "description": "向節點發送測試請求" @@ -993,7 +993,7 @@ "description": "管理 RTK 上下文最佳化器", "config": { "description": "顯示或更新 RTK 配置", - "tokenBudget": "RTK 令牌預算", + "tokenBudget": "RTK 權杖預算", "reservePct": "保留百分比" }, "filters": { @@ -1091,7 +1091,7 @@ "oneproxy": { "description": "管理 OneProxy 上游代理池", "stats": { - "provider": "按提供商篩選", + "provider": "按提供者篩選", "period": "時間範圍(預設:24h)" }, "fetch": { @@ -1101,14 +1101,14 @@ }, "rotate": { "description": "強制輪換代理", - "provider": "要輪換的提供商", + "provider": "要輪換的提供者", "connectionId": "要輪換的特定連線 ID" }, "config": { "description": "顯示或更新 OneProxy 配置", "enabled": "啟用代理池(true|false)", "poolSize": "池大小", - "providerSource": "代理提供商的 URL", + "providerSource": "代理提供者的 URL", "rotationPolicy": "輪換策略:sticky|per-request|periodic" }, "pool": { @@ -1163,11 +1163,11 @@ "fromCloud": "從雲備份初始化" }, "tokens": { - "description": "管理同步令牌", + "description": "管理同步權杖", "create": { - "name": "令牌名稱", - "scope": "令牌作用域", - "ttl": "令牌有效期(例如 30d)" + "name": "權杖名稱", + "scope": "權杖作用域", + "ttl": "權杖有效期(例如 30d)" }, "revoke": { "yes": "跳過確認" @@ -1201,7 +1201,7 @@ "bash": "列印 bash 補全指令碼", "fish": "列印 fish 補全指令碼", "install": "為檢測到的 Shell 全域性安裝補全指令碼", - "refresh": "重新整理組合/提供商/模型快取" + "refresh": "重新整理組合/提供者/模型快取" }, "logs": { "description": "流式傳輸或匯出請求日誌", diff --git a/docs/i18n/zh-TW/CHANGELOG.md b/docs/i18n/zh-TW/CHANGELOG.md index cc981d58d8..7508f8c398 100644 --- a/docs/i18n/zh-TW/CHANGELOG.md +++ b/docs/i18n/zh-TW/CHANGELOG.md @@ -4,17 +4,339 @@ --- -## [3.8.31] — 2026-06-20 +## [Unreleased] + +--- ## [3.8.49] — TBD +_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._ + +### ✨ 新功能 + +- **feat:** generalize ensureThinkingBudget to all providers + preserve server-side tool invocations on antigravity ([#6979](https://github.com/diegosouzapw/OmniRoute/pull/6979)) — thanks @rafaumeu +- **feat(6922):** effort-tier aliases for glm-5.2 & mimo-v2.5 on opencode-go ([#6987](https://github.com/diegosouzapw/OmniRoute/pull/6987)) — thanks @rafaumeu +- **feat(providers):** curated OpenRouter embeddings catalog + specialty merge in live discovery (#6976) ([#6994](https://github.com/diegosouzapw/OmniRoute/pull/6994)) +- **feat(quota):** opt-in auto-ping to keep Codex quota windows warm (#6977) ([#6995](https://github.com/diegosouzapw/OmniRoute/pull/6995)) +- **feat(providers):** add Agnes AI native provider support ([#7035](https://github.com/diegosouzapw/OmniRoute/pull/7035)) — thanks @HouMinXi +- **feat(sse):** allow disabling `:` comment heartbeats via OMNIROUTE_SSE_COMMENTS=off ([#7036](https://github.com/diegosouzapw/OmniRoute/pull/7036)) — thanks @xier2012 +- **feat(perf):** add performance.mark/measure to SSE pipeline + request-size metric ([#7045](https://github.com/diegosouzapw/OmniRoute/pull/7045)) — thanks @oyi77 +- **feat(providers):** add Dahl free inference provider ([#7062](https://github.com/diegosouzapw/OmniRoute/pull/7062)) — thanks @growab +- **feat(ci):** boot-smoke the packed npm tarball (check:pack-boot, #7065 class killer) ([#7086](https://github.com/diegosouzapw/OmniRoute/pull/7086)) +- **feat(ci):** hotfix fast-lane + tests-only E2E skip (WS3.1) ([#7088](https://github.com/diegosouzapw/OmniRoute/pull/7088)) +- **feat(ci):** continuous release-green — on-push quick gate + 3x/day full sweep (WS5.1) ([#7089](https://github.com/diegosouzapw/OmniRoute/pull/7089)) +- **feat(ci):** duration-balanced E2E shards via LPT bin-packing (WS4.1) ([#7090](https://github.com/diegosouzapw/OmniRoute/pull/7090)) +- **feat(ci):** TypeScript 7 native shadow for typecheck:core (WS4.2, advisory) ([#7091](https://github.com/diegosouzapw/OmniRoute/pull/7091)) +- **feat(release):** npm staged publishing + pre-publish boot-smoke (WS1.3) ([#7092](https://github.com/diegosouzapw/OmniRoute/pull/7092)) +- **feat(release):** post-publish verifier — clean-container install + boot (WS1.4) ([#7109](https://github.com/diegosouzapw/OmniRoute/pull/7109)) +- **feat(ci):** Mergify merge queue + manual-train fallback runbook (WS3.4/WS3.2) ([#7112](https://github.com/diegosouzapw/OmniRoute/pull/7112)) +- **feat(ci):** Windows leg for Electron prepare smoke (WS1.5) ([#7113](https://github.com/diegosouzapw/OmniRoute/pull/7113)) +- **feat(ci):** Codecov patch coverage (informational) + fix missing lcov reporter (WS5.6) ([#7114](https://github.com/diegosouzapw/OmniRoute/pull/7114)) +- **feat(sidecar):** support conditional provider manifest refresh ([#7130](https://github.com/diegosouzapw/OmniRoute/pull/7130)) — thanks @KooshaPari +- **feat(homolog):** real-environment E2E homologation suite (npm run homolog) ([#7133](https://github.com/diegosouzapw/OmniRoute/pull/7133)) +- **feat(usage):** add Codex reset credit picker ([#7154](https://github.com/diegosouzapw/OmniRoute/pull/7154)) — thanks @JxnLexn +- **feat(ci):** Trunk Flaky Tests uploads for vitest + Playwright E2E (WS5.2/5.3) ([#7175](https://github.com/diegosouzapw/OmniRoute/pull/7175)) +- **feat(ci):** Trunk Flaky Tests upload on the fast-path vitest job (per-PR volume) ([#7205](https://github.com/diegosouzapw/OmniRoute/pull/7205)) +- **feat(kiro):** register GPT-5.6 Sol/Terra/Luna model family ([#7209](https://github.com/diegosouzapw/OmniRoute/pull/7209)) +- **feat(dashboard):** show Codex plan label in provider and quota views ([#7210](https://github.com/diegosouzapw/OmniRoute/pull/7210)) +- **feat(dashboard):** add reorder connections by availability button ([#7211](https://github.com/diegosouzapw/OmniRoute/pull/7211)) +- **feat(dashboard):** add 180D and 365D usage/cost analytics periods (#7213) ([#7213](https://github.com/diegosouzapw/OmniRoute/pull/7213)) +- **feat(api):** add Vary: Accept-Encoding to token-authenticated /v1* responses (#6737) ([#7217](https://github.com/diegosouzapw/OmniRoute/pull/7217)) +- **feat(api):** expose GET /api/usage/model-latency-stats (#6873) ([#7218](https://github.com/diegosouzapw/OmniRoute/pull/7218)) +- **feat(dashboard):** add compression-mode selector to Context & Cache combos page (#6760) ([#7219](https://github.com/diegosouzapw/OmniRoute/pull/7219)) +- **feat(sse):** route GitHub Copilot Claude models through native /v1/messages ([#7223](https://github.com/diegosouzapw/OmniRoute/pull/7223)) +- **feat(mitm):** add Antigravity reasoning-effort overrides ([#7228](https://github.com/diegosouzapw/OmniRoute/pull/7228)) +- **feat:** replace free-text model inputs with hidePaid-aware Selects (#6540) ([#7229](https://github.com/diegosouzapw/OmniRoute/pull/7229)) +- **feat:** editable ComfyUI base-URL field + per-connection override for image/video/music generation (#6928) ([#7232](https://github.com/diegosouzapw/OmniRoute/pull/7232)) +- **feat(sse):** add optional-enum null-omission idiom for codex strict-mode tools (#7023) ([#7233](https://github.com/diegosouzapw/OmniRoute/pull/7233)) +- **feat(sse):** preserve tools/tool_choice for tool-bearing requests through fusion combos (#6771) ([#7235](https://github.com/diegosouzapw/OmniRoute/pull/7235)) +- **feat(api):** accept x-goog-api-key header for client-facing auth (#7034) ([#7236](https://github.com/diegosouzapw/OmniRoute/pull/7236)) +- **feat(sse):** add native xAI Grok Imagine video generation provider ([#7238](https://github.com/diegosouzapw/OmniRoute/pull/7238)) +- **feat:** add Type filter and easiest-first sort to Free Provider Rankings (#6915) ([#7240](https://github.com/diegosouzapw/OmniRoute/pull/7240)) +- **feat(cli):** add Grok Build CLI tool setup (~/.grok/config.toml) ([#7241](https://github.com/diegosouzapw/OmniRoute/pull/7241)) +- **feat(provider):** add Chenzk API OpenAI-compatible gateway ([#7246](https://github.com/diegosouzapw/OmniRoute/pull/7246)) +- **feat(providers):** let custom connections opt into prompt-cache capability (#6880) ([#7257](https://github.com/diegosouzapw/OmniRoute/pull/7257)) +- **feat(db):** include xp_audit_log in automatic retention/prune (#6801) ([#7260](https://github.com/diegosouzapw/OmniRoute/pull/7260)) +- **feat(api):** structured X-Routing-Fallback-Reason header for relay routing (#6872) ([#7262](https://github.com/diegosouzapw/OmniRoute/pull/7262)) +- **feat(compression):** support RTK TOML schema v1 filters ([#7281](https://github.com/diegosouzapw/OmniRoute/pull/7281)) — thanks @JxnLexn +- **feat:** add principal-scoped CCR MCP lifecycle ([#7282](https://github.com/diegosouzapw/OmniRoute/pull/7282)) — thanks @JxnLexn +- **feat(morph):** refresh curated models ([#7314](https://github.com/diegosouzapw/OmniRoute/pull/7314)) — thanks @backryun +- **feat(issue-agent):** surface RecordedTriageTimeoutError as 504 ([#7315](https://github.com/diegosouzapw/OmniRoute/pull/7315)) — thanks @KooshaPari +- **feat(incident-response):** structured incident response templates ([#7334](https://github.com/diegosouzapw/OmniRoute/pull/7334)) — thanks @KooshaPari +- **feat(providers):** add xAI OAuth PKCE provider ([#7399](https://github.com/diegosouzapw/OmniRoute/pull/7399)) — thanks @fenix007 +- **feat(models):** advertise Claude reasoning-effort variants in /v1/models ([#7497](https://github.com/diegosouzapw/OmniRoute/pull/7497)) — thanks @thepigdestroyer +- **feat(kimi):** sync Code, Web, and Moonshot providers ([#7531](https://github.com/diegosouzapw/OmniRoute/pull/7531)) — thanks @backryun +- **feat(resilience):** guard OmniRoute peer routing loops ([#7555](https://github.com/diegosouzapw/OmniRoute/pull/7555)) — thanks @isiahw1 +- **feat:** add Mixedbread AI as embeddings provider (#6660) ([#7595](https://github.com/diegosouzapw/OmniRoute/pull/7595)) +- **feat(providers):** add Rev AI speech-to-text provider (#6655) ([#7596](https://github.com/diegosouzapw/OmniRoute/pull/7596)) +- **feat:** add Freepik (Magnific Mystic) image generation provider (#6654) ([#7597](https://github.com/diegosouzapw/OmniRoute/pull/7597)) +- **feat(sse):** add DeepInfra as a video-generation provider (#6653) ([#7598](https://github.com/diegosouzapw/OmniRoute/pull/7598)) +- **feat(providers):** add Felo chat-aggregator provider (#6666) ([#7599](https://github.com/diegosouzapw/OmniRoute/pull/7599)) +- **feat(sse):** add Notion AI Web (Unofficial/Experimental) provider (#6758) ([#7600](https://github.com/diegosouzapw/OmniRoute/pull/7600)) +- **feat:** add FreeTheAi as OpenAI-compatible gateway provider (#6670) ([#7602](https://github.com/diegosouzapw/OmniRoute/pull/7602)) +- **feat:** add Gladia as an async speech-to-text provider (#6657) ([#7603](https://github.com/diegosouzapw/OmniRoute/pull/7603)) +- **feat:** add EdgeTTS audio-tts provider (#6668) ([#7605](https://github.com/diegosouzapw/OmniRoute/pull/7605)) +- **feat(video):** add Novita AI as video-generation provider (#6658) ([#7606](https://github.com/diegosouzapw/OmniRoute/pull/7606)) +- **feat:** add Segmind image+video provider (#6656) ([#7608](https://github.com/diegosouzapw/OmniRoute/pull/7608)) +- **feat:** add Microsoft Designer as image provider (#6672) ([#7609](https://github.com/diegosouzapw/OmniRoute/pull/7609)) +- **feat:** per-model default reasoning_effort + no-think none on OpenAI path (#6879) ([#7631](https://github.com/diegosouzapw/OmniRoute/pull/7631)) +- **feat(sse):** per-model upstream header-response timeout override (#6354) ([#7632](https://github.com/diegosouzapw/OmniRoute/pull/7632)) +- **feat(dashboard):** in-product guidance for prompt compression engines (#7530) ([#7634](https://github.com/diegosouzapw/OmniRoute/pull/7634)) +- **feat(usage):** add TTFT/E2E-latency/tokens-per-second to model latency stats (#6875) ([#7635](https://github.com/diegosouzapw/OmniRoute/pull/7635)) +- **feat:** import providers from CSV/JSON file (#6836) ([#7636](https://github.com/diegosouzapw/OmniRoute/pull/7636)) +- **feat:** confirm before removing a single connection (#7361) ([#7640](https://github.com/diegosouzapw/OmniRoute/pull/7640)) +- **feat(sse):** honor excluded models in no-auth auto-combo candidate pool (#7622) ([#7646](https://github.com/diegosouzapw/OmniRoute/pull/7646)) +- **feat(providers):** add g4f.space no-key gateway (groq/gemini/pollinations/ollama/nvidia) (#6650) ([#7647](https://github.com/diegosouzapw/OmniRoute/pull/7647)) +- **feat:** rate-limit queue admission control (maxQueueDepth + 15s default) (#6593) ([#7649](https://github.com/diegosouzapw/OmniRoute/pull/7649)) +- **feat(sse):** generalize session affinity TTL to all providers (#7274) ([#7650](https://github.com/diegosouzapw/OmniRoute/pull/7650)) +- **feat:** OpenRouter quota tracking (key/credits + free-window counter) (#6842) ([#7651](https://github.com/diegosouzapw/OmniRoute/pull/7651)) +- **feat(sse):** quota tracking for AgentRouter, v0 (Vercel), FreeModel (#6850, #6845, #7075) ([#7653](https://github.com/diegosouzapw/OmniRoute/pull/7653)) +- **feat(providers):** Speechmatics STT, gTTS, VibeProxy preset (#6659, #6667, #6874) ([#7655](https://github.com/diegosouzapw/OmniRoute/pull/7655)) +- **feat(api):** route Google AI Studio Imagen through /v1/images/generations ([#7656](https://github.com/diegosouzapw/OmniRoute/pull/7656)) — thanks @danscMax + +### ⚡ 效能 + +- **perf(db):** project columns + composite index in getProviderConnections ([#6918](https://github.com/diegosouzapw/OmniRoute/pull/6918)) — thanks @oyi77 +- **perf(db):** add jitter to stagger due-on-restart connections ([#6919](https://github.com/diegosouzapw/OmniRoute/pull/6919)) — thanks @oyi77 +- **perf(startup):** warm model catalog cache at module init ([#6920](https://github.com/diegosouzapw/OmniRoute/pull/6920)) — thanks @oyi77 +- **perf(db):** add temp_store=MEMORY pragma to SQLite init ([#6921](https://github.com/diegosouzapw/OmniRoute/pull/6921)) — thanks @oyi77 +- **perf(db):** cap modelLockouts eviction at 1000 entries ([#6923](https://github.com/diegosouzapw/OmniRoute/pull/6923)) — thanks @oyi77 +- **perf:** wrap ComboCard, HeroSection in React.memo ([#7070](https://github.com/diegosouzapw/OmniRoute/pull/7070)) — thanks @oyi77 + +### 🐛 錯誤修正 + +- **fix:** add re-entrancy guard to token health check sweep ([#6917](https://github.com/diegosouzapw/OmniRoute/pull/6917)) — thanks @oyi77 +- **fix(grok):** strip reasoningEffort for grok cli models ([#6938](https://github.com/diegosouzapw/OmniRoute/pull/6938)) — thanks @CitrusIce +- **fix(6954,6953):** preserve system role + strip empty-signature thinking blocks ([#6982](https://github.com/diegosouzapw/OmniRoute/pull/6982)) — thanks @rafaumeu +- **fix(6980):** classify Cloudflare AI neuron exhaustion as quota_exhausted ([#6983](https://github.com/diegosouzapw/OmniRoute/pull/6983)) — thanks @rafaumeu +- **fix(dashboard):** hide disabled provider connections from combo builder ([#6984](https://github.com/diegosouzapw/OmniRoute/pull/6984)) +- **fix(providers):** cap grok-cli tools at 200 for cli-chat-proxy ([#6986](https://github.com/diegosouzapw/OmniRoute/pull/6986)) +- **fix(6848):** auto-cleanup for telemetry tables causing OOM ([#6988](https://github.com/diegosouzapw/OmniRoute/pull/6988)) — thanks @rafaumeu +- **fix(models):** preserve direct-model combo metadata ([#6993](https://github.com/diegosouzapw/OmniRoute/pull/6993)) — thanks @JxnLexn +- **fix:** DDG circuit breaker (#6999) + null content validation (#7000) ([#7001](https://github.com/diegosouzapw/OmniRoute/pull/7001)) — thanks @rafaumeu +- **fix(models):** preserve chat-capable image model rows ([#7004](https://github.com/diegosouzapw/OmniRoute/pull/7004)) — thanks @xz-dev +- **fix(codex):** preserve GPT-5.6 reasoning contract ([#7012](https://github.com/diegosouzapw/OmniRoute/pull/7012)) — thanks @xz-dev +- **fix(base-red):** align least-used combo tests with executionKey usage keying ([#7015](https://github.com/diegosouzapw/OmniRoute/pull/7015)) +- **fix:** infer bare models from active synced catalogs ([#7028](https://github.com/diegosouzapw/OmniRoute/pull/7028)) — thanks @guanbear +- **fix(auggie):** update model registry to match v0.32.0 CLI model IDs ([#7032](https://github.com/diegosouzapw/OmniRoute/pull/7032)) — thanks @oyi77 +- **fix(sse):** register ollama-cloud in USAGE_FETCHER_PROVIDERS (#7026) ([#7041](https://github.com/diegosouzapw/OmniRoute/pull/7041)) — thanks @alltomatos +- **fix(quality):** read cognitiveComplexity= machine line in validate-release-green (#7009) ([#7042](https://github.com/diegosouzapw/OmniRoute/pull/7042)) — thanks @alltomatos +- **fix(providers):** sanitize Claude native output_config.effort (#7044) ([#7050](https://github.com/diegosouzapw/OmniRoute/pull/7050)) — thanks @xier2012 +- **fix(combo):** treat maxInputTokens as an input-only cap in the context filter (#7039) ([#7052](https://github.com/diegosouzapw/OmniRoute/pull/7052)) — thanks @xier2012 +- **fix(antigravity):** collect native part.functionCall into tool calls (#7037) ([#7053](https://github.com/diegosouzapw/OmniRoute/pull/7053)) — thanks @xier2012 +- **fix(responses):** map mid-conversation system turns to developer role (#6954) ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)) — thanks @xier2012 +- **fix(combo):** least-used sorts by per-account executionKey (#7015) ([#7059](https://github.com/diegosouzapw/OmniRoute/pull/7059)) — thanks @xier2012 +- **fix(providers):** AgentRouter model import applies Claude Code wire image to /v1/models (#7016) ([#7060](https://github.com/diegosouzapw/OmniRoute/pull/7060)) — thanks @xier2012 +- **fix(translator):** preserve thinking.budget_tokens: 0 in Claude->Gemini (#6813) ([#7061](https://github.com/diegosouzapw/OmniRoute/pull/7061)) — thanks @xier2012 +- **fix(cloudflare-relay):** avoid invalid regex syntax in generated worker ([#7063](https://github.com/diegosouzapw/OmniRoute/pull/7063)) — thanks @SeaXen +- **fix(dashboard):** strip browser-extension attrs before hydration ([#7073](https://github.com/diegosouzapw/OmniRoute/pull/7073)) — thanks @MrFadiAi +- **fix(relay):** bound Bifrost stream lifetime ([#7093](https://github.com/diegosouzapw/OmniRoute/pull/7093)) — thanks @KooshaPari +- **fix(sse):** recognize xiaomi-tokenplan mimo as a thinking-mode model ([#7098](https://github.com/diegosouzapw/OmniRoute/pull/7098)) +- **fix(codex):** strip regex lookaround from tool schema patterns ([#7100](https://github.com/diegosouzapw/OmniRoute/pull/7100)) +- **fix(openai):** strip reasoning_effort when GPT-5.x models carry function tools ([#7101](https://github.com/diegosouzapw/OmniRoute/pull/7101)) +- **fix(compression):** Headroom SmartCrusher skips developer-role messages (port from 9router#2132) ([#7102](https://github.com/diegosouzapw/OmniRoute/pull/7102)) +- **fix(providers):** surface a warning on 404 model_not_found in OpenAI-compatible Check (port from 9router#2032) ([#7103](https://github.com/diegosouzapw/OmniRoute/pull/7103)) +- **fix(executors):** forward X-Session-ID/X-Title agent metadata headers ([#7104](https://github.com/diegosouzapw/OmniRoute/pull/7104)) +- **fix(cli):** verify better-sqlite3 native binary is actually loadable ([#7105](https://github.com/diegosouzapw/OmniRoute/pull/7105)) +- **fix(sse):** sanitize non-ok Antigravity streaming error body (port from 9router#2461) ([#7106](https://github.com/diegosouzapw/OmniRoute/pull/7106)) +- **fix(providers):** add MiniMax image-generation provider ([#7108](https://github.com/diegosouzapw/OmniRoute/pull/7108)) +- **fix(sse):** handle space-separated arg name/value in Composer tool calls (port from 9router#1811) ([#7116](https://github.com/diegosouzapw/OmniRoute/pull/7116)) +- **fix(cli):** remove MITM DNS spoof entries before killing server process ([#7117](https://github.com/diegosouzapw/OmniRoute/pull/7117)) +- **fix(dashboard):** include never-tested connections in combo builder active-provider list (port from 9router#2057) ([#7118](https://github.com/diegosouzapw/OmniRoute/pull/7118)) +- **fix(api):** check Vercel SSO-protection PATCH response on relay deploy ([#7119](https://github.com/diegosouzapw/OmniRoute/pull/7119)) +- **fix(combos):** reject oversized fusion panels before fan-out (port from 9router#1905) ([#7120](https://github.com/diegosouzapw/OmniRoute/pull/7120)) +- **fix(combo):** detect empty content_block in streaming SSE peek ([#7121](https://github.com/diegosouzapw/OmniRoute/pull/7121)) +- **fix(oauth):** resolve Kiro AWS SSO cache client credentials by clientId match (port from 9router#1253) ([#7122](https://github.com/diegosouzapw/OmniRoute/pull/7122)) +- **fix(tests):** vitest UI suite back to green (69 fails triaged — WS6.1) ([#7127](https://github.com/diegosouzapw/OmniRoute/pull/7127)) +- **fix(auto):** use p95 fallback in speed factors ([#7128](https://github.com/diegosouzapw/OmniRoute/pull/7128)) — thanks @KooshaPari +- **fix(models):** update Anthropic model contextLength to 1M ([#7129](https://github.com/diegosouzapw/OmniRoute/pull/7129)) — thanks @HouMinXi +- **fix(ci):** raise dast-smoke timeout 12->25min (build alone eats up to 11min) ([#7139](https://github.com/diegosouzapw/OmniRoute/pull/7139)) +- **fix(compression):** lazy-load typescript in RTK codeStripper so prod-lean deploys don't break (#7096) ([#7164](https://github.com/diegosouzapw/OmniRoute/pull/7164)) — thanks @alltomatos +- **fix(providers):** accept m365.cloud.microsoft for copilot-m365-web token (#7078) ([#7166](https://github.com/diegosouzapw/OmniRoute/pull/7166)) — thanks @xier2012 +- **fix(executors):** disable parallel tools for Codex Responses Lite ([#7171](https://github.com/diegosouzapw/OmniRoute/pull/7171)) — thanks @fenix007 +- **fix(tests+providers):** env-dependent tests exposed by GH-hosted runners (#6634 selfref shallow checkout + yuanbao live-network 401) ([#7174](https://github.com/diegosouzapw/OmniRoute/pull/7174)) +- **fix(combo):** reject known context overflow without exhausting providers ([#7177](https://github.com/diegosouzapw/OmniRoute/pull/7177)) — thanks @JxnLexn +- **fix:** add static.cloudflareinsights.com to CSP script-src ([#7178](https://github.com/diegosouzapw/OmniRoute/pull/7178)) — thanks @oyi77 +- **fix:** extend turbopack ignoreIssue suppression to compression module (#7051) ([#7180](https://github.com/diegosouzapw/OmniRoute/pull/7180)) +- **fix:** recognize Ollama Cloud session usage-limit 429 as quota-exhausted (#7071) ([#7181](https://github.com/diegosouzapw/OmniRoute/pull/7181)) +- **fix:** preserve relayAuth for pool-referenced relay proxies (#5716) ([#7182](https://github.com/diegosouzapw/OmniRoute/pull/7182)) +- **fix:** wire adaptive context-budget dial into settings schema and DB (#7005) ([#7183](https://github.com/diegosouzapw/OmniRoute/pull/7183)) +- **fix(providers):** DuckDuckGo VQD 429 misclassified as 503 (#6996) ([#7185](https://github.com/diegosouzapw/OmniRoute/pull/7185)) +- **fix(db):** cap OOM probe-failure cycle in getDbInstance() (#6835) ([#7186](https://github.com/diegosouzapw/OmniRoute/pull/7186)) +- **fix:** stop opencode-go quota lookup defaulting to Z.AI endpoint (#7022) ([#7187](https://github.com/diegosouzapw/OmniRoute/pull/7187)) +- **fix(providers):** refresh OpenCode (oc) free-tier model catalog (#6998) ([#7188](https://github.com/diegosouzapw/OmniRoute/pull/7188)) +- **fix:** include proxyId when testing a saved registry proxy (#7080) ([#7189](https://github.com/diegosouzapw/OmniRoute/pull/7189)) +- **fix:** sanitize non-Latin1 chars in combo diagnostic headers (#6612) ([#7190](https://github.com/diegosouzapw/OmniRoute/pull/7190)) +- **fix:** raise main server keepAliveTimeout/headersTimeout above Node's 5s default (#7003) ([#7191](https://github.com/diegosouzapw/OmniRoute/pull/7191)) +- **fix:** route zai-web (and other registry-entry web-cookie providers) connection-test cookie probe through the configured proxy (#7058) ([#7192](https://github.com/diegosouzapw/OmniRoute/pull/7192)) +- **fix(providers):** reject chat requests for cloud-agent-only jules provider (#6699) ([#7193](https://github.com/diegosouzapw/OmniRoute/pull/7193)) +- **fix:** restore mobile grid-cols-1 fallback on quota page card grid (#7072) ([#7194](https://github.com/diegosouzapw/OmniRoute/pull/7194)) +- **fix:** wire modelAliases fetch into HermesAgentToolCard (#7151) ([#7195](https://github.com/diegosouzapw/OmniRoute/pull/7195)) +- **fix:** surface real claude-web error body for non-SSE 400s (#7134) ([#7196](https://github.com/diegosouzapw/OmniRoute/pull/7196)) +- **fix(dashboard):** agent bridge dns toggle uses POST, not PUT (#7157) ([#7197](https://github.com/diegosouzapw/OmniRoute/pull/7197)) +- **fix:** stop duplicating text in Gemini Web streamed responses (#7163) ([#7198](https://github.com/diegosouzapw/OmniRoute/pull/7198)) +- **fix:** filter hidden custom models out of legacy combo model picker (#7156) ([#7199](https://github.com/diegosouzapw/OmniRoute/pull/7199)) +- **fix(dashboard):** implement missing handleToggleSource on Free Pool tab (#7161) ([#7200](https://github.com/diegosouzapw/OmniRoute/pull/7200)) +- **fix:** honor combo-level proxy assignments from the registry (#7149) ([#7201](https://github.com/diegosouzapw/OmniRoute/pull/7201)) +- **fix(ci):** run quality gates on Mergify merge-queue draft PRs (anchor check never ran, queue always dequeued) ([#7202](https://github.com/diegosouzapw/OmniRoute/pull/7202)) +- **fix:** add dashboard-scoped typecheck gate covering src/app/(dashboard) TSX (#7033) ([#7203](https://github.com/diegosouzapw/OmniRoute/pull/7203)) +- **fix(guardrails/chat):** stop Vision Bridge hijacking credentialed models to opencode-zen ([#7204](https://github.com/diegosouzapw/OmniRoute/pull/7204)) — thanks @artickc +- **fix(translator):** preserve Gemini thought parts as reasoning_content on the OpenAI bridge ([#7206](https://github.com/diegosouzapw/OmniRoute/pull/7206)) +- **fix(translator):** register openai response projection for gemini clients ([#7207](https://github.com/diegosouzapw/OmniRoute/pull/7207)) +- **fix(cli):** fast-path --version to skip full CLI bootstrap ([#7208](https://github.com/diegosouzapw/OmniRoute/pull/7208)) +- **fix:** honor PROVIDER_LIMITS_SYNC_SPACING_MS for local/API-key connections (#6916) ([#7214](https://github.com/diegosouzapw/OmniRoute/pull/7214)) +- **fix(api):** bulk-add API keys no longer overwrite existing connections ([#7234](https://github.com/diegosouzapw/OmniRoute/pull/7234)) +- **fix(sse):** route the public OpenAI GPT-5.6 family through the Responses API ([#7242](https://github.com/diegosouzapw/OmniRoute/pull/7242)) +- **fix(providers):** honor configured proxy on Grok Build egress ([#7244](https://github.com/diegosouzapw/OmniRoute/pull/7244)) +- **fix(nvidia):** expand NIM chat model catalog ([#7247](https://github.com/diegosouzapw/OmniRoute/pull/7247)) +- **fix(sse):** reconstruct Claude-format content in synthetic bypass responses ([#7248](https://github.com/diegosouzapw/OmniRoute/pull/7248)) +- **fix(build):** isolate Windows HOME/AppData during next build ([#7249](https://github.com/diegosouzapw/OmniRoute/pull/7249)) +- **fix(cli):** omniroute dashboard respects PORT env when --port is omitted (#7049) ([#7252](https://github.com/diegosouzapw/OmniRoute/pull/7252)) +- **fix(sse):** project non-streaming JSON back to the Gemini/Antigravity envelope ([#7255](https://github.com/diegosouzapw/OmniRoute/pull/7255)) +- **fix(combo):** fall back on Responses SSE failures ([#7256](https://github.com/diegosouzapw/OmniRoute/pull/7256)) — thanks @rushsinging +- **fix(routing):** resolve nested combo-ref panel members in fusion strategy (#6764) ([#7259](https://github.com/diegosouzapw/OmniRoute/pull/7259)) +- **fix(usage):** reset logs and show provider names in analytics ([#7264](https://github.com/diegosouzapw/OmniRoute/pull/7264)) — thanks @SeaXen +- **fix(sse):** silence noisy proxy-failure log on caller-initiated abort ([#7266](https://github.com/diegosouzapw/OmniRoute/pull/7266)) +- **fix(codex):** normalize nested Responses output content ([#7269](https://github.com/diegosouzapw/OmniRoute/pull/7269)) — thanks @JxnLexn +- **fix(combo):** derive session stickiness key from Responses API .input, not just .messages (#7270) ([#7277](https://github.com/diegosouzapw/OmniRoute/pull/7277)) — thanks @alltomatos +- **fix(antigravity):** wrap Pro fallback chain in try/catch for timeout resilience ([#7290](https://github.com/diegosouzapw/OmniRoute/pull/7290)) — thanks @HouMinXi +- **fix(logs):** show saved provider names in request/provider log views ([#7294](https://github.com/diegosouzapw/OmniRoute/pull/7294)) — thanks @SeaXen +- **fix(stream):** reconcile encrypted Codex reasoning visibility without mutating upstream item ([#7304](https://github.com/diegosouzapw/OmniRoute/pull/7304)) +- **fix(build):** packed tarball boot crash — server-ws timeout import escaped the package (#7065 class) ([#7308](https://github.com/diegosouzapw/OmniRoute/pull/7308)) +- **fix(skills):** register cli-skill-collector in the agent-skills catalog (Integration 2/2 base-red) ([#7310](https://github.com/diegosouzapw/OmniRoute/pull/7310)) +- **fix(db):** tolerate unavailable virtual table modules in stats ([#7313](https://github.com/diegosouzapw/OmniRoute/pull/7313)) — thanks @megamen32 +- **fix(router-eval):** retained-optimization gate cleanup ([#7318](https://github.com/diegosouzapw/OmniRoute/pull/7318)) — thanks @KooshaPari +- **fix(ci):** Coverage job timeout 10->20min (lcov reporter pushed it past the old cap) ([#7342](https://github.com/diegosouzapw/OmniRoute/pull/7342)) +- **fix(electron):** normalize hashed standalone externals ([#7353](https://github.com/diegosouzapw/OmniRoute/pull/7353)) — thanks @tianrking +- **fix(db):** stop a 'latest' path segment from disabling backups and migrations ([#7359](https://github.com/diegosouzapw/OmniRoute/pull/7359)) — thanks @danscMax +- **fix(branding):** regenerate raster favicons — white mark was shipped without its gradient tile ([#7390](https://github.com/diegosouzapw/OmniRoute/pull/7390)) — thanks @vzts +- **fix(test):** skip real DNS writes in MITM dynamic-import test ([#7398](https://github.com/diegosouzapw/OmniRoute/pull/7398)) — thanks @HouMinXi +- **fix(antigravity):** streaming passthrough for non-streaming clients ([#7408](https://github.com/diegosouzapw/OmniRoute/pull/7408)) — thanks @HouMinXi +- **fix(build):** align engines.node with SUPPORTED_NODE_RANGE (#7446) ([#7490](https://github.com/diegosouzapw/OmniRoute/pull/7490)) — thanks @alltomatos +- **fix(api):** await params in Agent Bridge DNS route (Next.js 16) (#7271) ([#7492](https://github.com/diegosouzapw/OmniRoute/pull/7492)) — thanks @alltomatos +- **fix(dashboard):** show Obsidian context source card ([#7500](https://github.com/diegosouzapw/OmniRoute/pull/7500)) — thanks @DKotsyuba +- **fix(ci):** fetch full base history in pr-test-policy (shallow graft broke merge-base) ([#7501](https://github.com/diegosouzapw/OmniRoute/pull/7501)) +- **fix(sse):** preserve chat quota across mixed windows ([#7504](https://github.com/diegosouzapw/OmniRoute/pull/7504)) — thanks @webmasterarbez +- **fix(oauth):** surface sanitized device-code error instead of a generic 500 ([#7511](https://github.com/diegosouzapw/OmniRoute/pull/7511)) — thanks @danscMax +- **fix(oauth):** repair qwen + codebuddy-cn device-code endpoints ([#7517](https://github.com/diegosouzapw/OmniRoute/pull/7517)) — thanks @danscMax +- **fix(mitm):** strip trailing assistant prefill to prevent upstream Anthropic 400 errors ([#7520](https://github.com/diegosouzapw/OmniRoute/pull/7520)) — thanks @chirag127 +- **fix(codex):** Test probe uses a ChatGPT-account-supported model (#7521) ([#7524](https://github.com/diegosouzapw/OmniRoute/pull/7524)) +- **fix(codex):** validate refresh_token on import before persisting (#7522) ([#7525](https://github.com/diegosouzapw/OmniRoute/pull/7525)) +- **fix(codex):** non-stream chat 502 'Response body is already used' (single-reader peek) ([#7526](https://github.com/diegosouzapw/OmniRoute/pull/7526)) +- **fix(oauth):** surface tunnel hint when Codex OAuth runs on a remote host (#7523) ([#7527](https://github.com/diegosouzapw/OmniRoute/pull/7527)) +- **fix(sse):** preserve custom tool output images ([#7540](https://github.com/diegosouzapw/OmniRoute/pull/7540)) — thanks @loulanyue +- **fix(combo):** failover when upstream SSE is truncated mid-lifecycle ([#7545](https://github.com/diegosouzapw/OmniRoute/pull/7545)) — thanks @Chewji9875 +- **fix(dashboard):** prefer public endpoint URLs ([#7547](https://github.com/diegosouzapw/OmniRoute/pull/7547)) — thanks @nguyenha935 +- **fix(cli):** refresh runtime detection accurately ([#7552](https://github.com/diegosouzapw/OmniRoute/pull/7552)) — thanks @nguyenha935 +- **fix(ui):** improve React Flow dark theme ([#7553](https://github.com/diegosouzapw/OmniRoute/pull/7553)) — thanks @nguyenha935 +- **fix(i18n):** treat **MISSING** sync placeholders as absent in EN fallback (#7258) ([#7556](https://github.com/diegosouzapw/OmniRoute/pull/7556)) +- **fix(cli):** Windows cert check/uninstall key off the real CA identity, not a hardcoded legacy host (#7275) ([#7557](https://github.com/diegosouzapw/OmniRoute/pull/7557)) +- **fix(sse):** feed compression pipeline the authoritative vision capability (#7237) ([#7560](https://github.com/diegosouzapw/OmniRoute/pull/7560)) +- **fix(dashboard):** providers model-name filter matches live/synced catalog (#7250) ([#7561](https://github.com/diegosouzapw/OmniRoute/pull/7561)) +- **fix(db):** pre-init sql.js WASM ahead of any getDbInstance() consumer (#7288) ([#7562](https://github.com/diegosouzapw/OmniRoute/pull/7562)) +- **fix(dashboard):** resolve costs page 500 from out-of-scope t() in TopListCard (#7272) ([#7564](https://github.com/diegosouzapw/OmniRoute/pull/7564)) +- **fix(dashboard):** surface rate-limit warning on 429 chat-probe (#7284) ([#7565](https://github.com/diegosouzapw/OmniRoute/pull/7565)) +- **fix(sse):** lazy-load playwright in claudeTurnstileSolver (#7265) ([#7566](https://github.com/diegosouzapw/OmniRoute/pull/7566)) +- **fix(sse):** combo failover for OpenAI streams truncated without finish_reason (#7285) ([#7568](https://github.com/diegosouzapw/OmniRoute/pull/7568)) +- **fix(cli):** reuse win32-aware locateCommand in tool-detector (#7279) ([#7569](https://github.com/diegosouzapw/OmniRoute/pull/7569)) +- **fix(codex):** #7536 check content-type before touching response.body in peek ([#7570](https://github.com/diegosouzapw/OmniRoute/pull/7570)) +- **fix(sse):** stop dropping tool_search and leaking OpenAI-only params in Responses->Chat translation ([#7571](https://github.com/diegosouzapw/OmniRoute/pull/7571)) +- **fix(api):** resolve provider display name and dedup byModel on normalized key (#7534, #7535) ([#7573](https://github.com/diegosouzapw/OmniRoute/pull/7573)) +- **fix(mitm):** route Claude Code standalone MITM traffic ([#7574](https://github.com/diegosouzapw/OmniRoute/pull/7574)) — thanks @dongwook-chan +- **fix(sse):** stop per-byte enumeration of binary image bytes in log redaction (#7297) ([#7576](https://github.com/diegosouzapw/OmniRoute/pull/7576)) +- **fix(sse):** split effort/reasoning suffix off pinned cursor model ids (#7289) ([#7577](https://github.com/diegosouzapw/OmniRoute/pull/7577)) +- **fix(chatgpt-web):** recognize update_content.messages[] celsius WS frames (#7357) ([#7578](https://github.com/diegosouzapw/OmniRoute/pull/7578)) +- **fix(sse):** 401 model-not-supported lockout + sticky quota-exhausted release (#7268, #7387) ([#7580](https://github.com/diegosouzapw/OmniRoute/pull/7580)) +- **fix(antigravity):** allow cloudcode envelope through messages guard ([#7582](https://github.com/diegosouzapw/OmniRoute/pull/7582)) — thanks @dongwook-chan +- **fix(sse):** sanitize empty-signature thinking blocks + hoist strict-provider system messages ([#7583](https://github.com/diegosouzapw/OmniRoute/pull/7583)) +- **fix(sse):** honor per-model targetFormat override for zai/glm-coding-apikey (#7364) ([#7584](https://github.com/diegosouzapw/OmniRoute/pull/7584)) +- **fix(sse):** clamp glm-4.6v max_tokens to the 32768 ceiling (#7364) ([#7585](https://github.com/diegosouzapw/OmniRoute/pull/7585)) +- **fix(cli):** log Codex Responses WebSocket history/usage per logical turn, not per connection ([#7588](https://github.com/diegosouzapw/OmniRoute/pull/7588)) +- **fix(providers):** derive static model catalogs for search providers from searchTypes ([#7589](https://github.com/diegosouzapw/OmniRoute/pull/7589)) +- **fix(stream-readiness):** bump timeout for heavy Claude-format reasoning replicas ([#7612](https://github.com/diegosouzapw/OmniRoute/pull/7612)) — thanks @herjarsa +- **fix(translator):** synthesize tool call chunks from response.completed batched output ([#7613](https://github.com/diegosouzapw/OmniRoute/pull/7613)) — thanks @ekinnee +- **fix(embeddings):** add lmstudio to embedding provider registry ([#7614](https://github.com/diegosouzapw/OmniRoute/pull/7614)) — thanks @ekinnee +- **fix(combo):** auto-clear stale session pins and emit recovery hints on combo exhaustion ([#7625](https://github.com/diegosouzapw/OmniRoute/pull/7625)) — thanks @herjarsa +- **fix(providers):** unify connection and routing flows ([#7629](https://github.com/diegosouzapw/OmniRoute/pull/7629)) — thanks @nguyenha935 +- **fix(db):** dedupe bulk-imported proxies by full credential tuple (#7594) ([#7644](https://github.com/diegosouzapw/OmniRoute/pull/7644)) — thanks @alltomatos +- **fix(api):** allow text-to-image on dual-modality models + revive HuggingFace image host ([#7648](https://github.com/diegosouzapw/OmniRoute/pull/7648)) — thanks @danscMax +- **fix(stryker):** add Microsoft Designer test to tap.testFiles ([#7659](https://github.com/diegosouzapw/OmniRoute/pull/7659)) +- **fix(dashboard):** cut UI import chain from connection persist module (CI shard base-red) ([#7677](https://github.com/diegosouzapw/OmniRoute/pull/7677)) + +### 📚 文件 + +- **docs(quality):** codify retry policy per runner + release-level drift rule (WS5.4/WS5.5) ([#7107](https://github.com/diegosouzapw/OmniRoute/pull/7107)) +- **docs(troubleshooting):** document Avast/AVG README.md false positive (#5946) ([#7295](https://github.com/diegosouzapw/OmniRoute/pull/7295)) +- **docs(perf):** add per-endpoint p50/p95/p99 latency + cost budget reference ([#7336](https://github.com/diegosouzapw/OmniRoute/pull/7336)) — thanks @KooshaPari +- **docs:** refresh revoked Discord invite + WhatsApp Brasil link ([#7604](https://github.com/diegosouzapw/OmniRoute/pull/7604)) +- **docs(readme):** animated SVG for the 4-tier auto-fallback cascade ([#7615](https://github.com/diegosouzapw/OmniRoute/pull/7615)) +- **docs:** sync provider count to 259 (unblocks docs-counts strict gate) ([#7616](https://github.com/diegosouzapw/OmniRoute/pull/7616)) +- **docs(readme):** animate pool + combo ASCII blocks as SMIL SVG diagrams ([#7626](https://github.com/diegosouzapw/OmniRoute/pull/7626)) +- **docs(readme):** animate CLI command list + compression flow as SMIL SVGs ([#7637](https://github.com/diegosouzapw/OmniRoute/pull/7637)) +- **docs(readme):** replace free-tier budget mockup with animated SMIL card ([#7665](https://github.com/diegosouzapw/OmniRoute/pull/7665)) +- **docs(readme):** standardize all README tables to full content width ([#7666](https://github.com/diegosouzapw/OmniRoute/pull/7666)) + +### 🧪 測試與品質 + +- **test(build):** derive pack-artifact closures for all npm-shipped entrypoints (#7065 class) ([#7081](https://github.com/diegosouzapw/OmniRoute/pull/7081)) +- **test(dashboard):** dedicated regression guard for #6815 density guarantee ([#7291](https://github.com/diegosouzapw/OmniRoute/pull/7291)) +- **test(ci):** make #6634 selfref guard hermetic — read file from disk, no git ref ([#7327](https://github.com/diegosouzapw/OmniRoute/pull/7327)) +- **test(ci):** mock route bridge surfaces error message, not raw stack ([#7354](https://github.com/diegosouzapw/OmniRoute/pull/7354)) +- **test(ci):** static body in codex e2e mock route bridge (CodeQL #737) ([#7558](https://github.com/diegosouzapw/OmniRoute/pull/7558)) +- **test(ci):** exact-line assert in grok-build config test (CodeQL #740/#741) ([#7628](https://github.com/diegosouzapw/OmniRoute/pull/7628)) + +### 🔧 雜務 / CI + +- **chore(release):** gate the sync-back push on release-green --quick (WS0.3) ([#7083](https://github.com/diegosouzapw/OmniRoute/pull/7083)) +- **chore(ci):** gate hygiene — secrets baseline 0, semgrep drop, hadolint (WS6/D3 + WS1.7) ([#7099](https://github.com/diegosouzapw/OmniRoute/pull/7099)) +- **chore(ops):** runner-box janitor + operations runbook (WS3.3) ([#7115](https://github.com/diegosouzapw/OmniRoute/pull/7115)) +- **chore(ci):** promote test:vitest:ui to blocking (suite green after #7127) ([#7147](https://github.com/diegosouzapw/OmniRoute/pull/7147)) +- **chore(ci):** stop dependabot proposing typescript majors — peer-blocked by typescript-eslint ([#7306](https://github.com/diegosouzapw/OmniRoute/pull/7306)) +- **chore(release):** script the 0a.0b PR re-home with a verified read-back ([#7312](https://github.com/diegosouzapw/OmniRoute/pull/7312)) +- **chore(quality):** tighten the coverage ratchet to the CI's real numbers ([#7326](https://github.com/diegosouzapw/OmniRoute/pull/7326)) +- **chore(ci):** make the Electron Windows leg advisory with bash stderr capture (first-run failure diagnosis) ([#7340](https://github.com/diegosouzapw/OmniRoute/pull/7340)) +- **chore(deps):** bump actions/setup-node from 6 to 7 ([#7348](https://github.com/diegosouzapw/OmniRoute/pull/7348)) +- **chore(deps):** bump codecov/codecov-action ([#7350](https://github.com/diegosouzapw/OmniRoute/pull/7350)) +- **ci(release-green):** add a main-green arm to detect when main goes red ([#7355](https://github.com/diegosouzapw/OmniRoute/pull/7355)) +- **chore(deps):** bump github/codeql-action/analyze from 4.37.0 to 4.37.1 ([#7641](https://github.com/diegosouzapw/OmniRoute/pull/7641)) +- **chore(deps):** bump github/codeql-action/init from 4.37.0 to 4.37.1 ([#7642](https://github.com/diegosouzapw/OmniRoute/pull/7642)) +- **chore(quality):** register #6672 test in stryker tap.testFiles (base-red unblock) ([#7652](https://github.com/diegosouzapw/OmniRoute/pull/7652)) +- **chore(release):** merge-train box-speed suite + --fast mode ([#7670](https://github.com/diegosouzapw/OmniRoute/pull/7670)) + +### 🔀 其他 + +- [needs-vps] fix(electron): materialize Turbopack hashed-module symlinks during packaging (#6724, #6594) ([#6794](https://github.com/diegosouzapw/OmniRoute/pull/6794)) — thanks @huohua-dev +- [codex] Keep mode-pack weights consistent in auto fallback ranking ([#7008](https://github.com/diegosouzapw/OmniRoute/pull/7008)) — thanks @KooshaPari +- Explain effective auto-combo scoring weights ([#7087](https://github.com/diegosouzapw/OmniRoute/pull/7087)) — thanks @KooshaPari +- [needs-vps] fix(dashboard): add vision-capability toggle for custom OpenAI-compatible models ([#7124](https://github.com/diegosouzapw/OmniRoute/pull/7124)) +- [needs-vps] fix(dashboard): align onboarding tier content ([#7125](https://github.com/diegosouzapw/OmniRoute/pull/7125)) — thanks @Wibias +- Use OpenAI chunks for early chat keepalives ([#7136](https://github.com/diegosouzapw/OmniRoute/pull/7136)) — thanks @KooshaPari +- Fix Codex Responses compression analytics ([#7273](https://github.com/diegosouzapw/OmniRoute/pull/7273)) — thanks @JxnLexn +- Add cache-aligned Live Zone compression ([#7280](https://github.com/diegosouzapw/OmniRoute/pull/7280)) — thanks @JxnLexn +- Fix routed target request parameters ([#7323](https://github.com/diegosouzapw/OmniRoute/pull/7323)) — thanks @JxnLexn +- deps: bump electron from 43.1.0 to 43.1.1 in /electron ([#7349](https://github.com/diegosouzapw/OmniRoute/pull/7349)) +- deps: bump the development group with 8 updates ([#7351](https://github.com/diegosouzapw/OmniRoute/pull/7351)) +- deps: bump the production group across 1 directory with 12 updates ([#7352](https://github.com/diegosouzapw/OmniRoute/pull/7352)) +- Add per-connection Provider Quota visibility ([#7360](https://github.com/diegosouzapw/OmniRoute/pull/7360)) — thanks @JxnLexn +- Stream model health probes for slow providers ([#7377](https://github.com/diegosouzapw/OmniRoute/pull/7377)) — thanks @JxnLexn +- Refresh NVIDIA free metadata and detect catalog drift ([#7378](https://github.com/diegosouzapw/OmniRoute/pull/7378)) — thanks @JxnLexn +- Reject invalid output token budgets ([#7379](https://github.com/diegosouzapw/OmniRoute/pull/7379)) — thanks @JxnLexn +- Honor provider proxies for The Old LLM Vercel blocks ([#7380](https://github.com/diegosouzapw/OmniRoute/pull/7380)) — thanks @JxnLexn +- Restore proxy navigation and sidebar accordion state ([#7381](https://github.com/diegosouzapw/OmniRoute/pull/7381)) — thanks @JxnLexn +- Expose proxy controls for no-auth providers ([#7419](https://github.com/diegosouzapw/OmniRoute/pull/7419)) — thanks @JxnLexn +- Add reasoning-based model and effort routing ([#7607](https://github.com/diegosouzapw/OmniRoute/pull/7607)) — thanks @JxnLexn + +### 🩹 Direct release-branch fixes (no PR — authorized base-red sweep, 2026-07-18) + +- **fix(base-red):** full-suite realignment after the 102-PR merge campaign: two real production fixes (legacy `refresh_token` column healed before its index is created; `shouldSkipCloudSyncInitialization` no longer swaps its `(env, argv)` arguments) plus 13 test files, goldens, provider counts, and env docs realigned to the live-validated behavior of the merged PRs. + --- ## [3.8.48] — 2026-07-13 > ⚠️ **Hotfix release.** The published npm package for 3.8.47 crashed on every boot ([#7065](https://github.com/diegosouzapw/OmniRoute/issues/7065)) and was deprecated — **3.8.48 is the first installable release of the v3.8.47 cycle**, so everything listed under [3.8.47] below ships here. -### 🐛 Bug Fixes +### 🐛 錯誤修正 - **fix(build):** ship `dist/head-response-guard.cjs` in the npm tarball — the prepublish prune allowlist lacked it, so every `omniroute` boot of the published 3.8.47 crashed with `ERR_MODULE_NOT_FOUND` (3rd occurrence of this class after tls-options/3.8.41); now allowlisted, enforced by `check:pack-artifact`, and guarded by a closure test that derives every `server-ws.mjs` sibling import ([#7065](https://github.com/diegosouzapw/OmniRoute/issues/7065), [#7040](https://github.com/diegosouzapw/OmniRoute/issues/7040)) - **fix(build):** Electron Windows packaging — the better-sqlite3 Electron-ABI rebuild now spawns `npx.cmd` through a shell (Node's CVE-2024-27980 hardening made the shell-less spawn fail with `status null` on Windows runners, breaking the v3.8.47 desktop build) @@ -23,19 +345,329 @@ --- -## [3.8.47] — TBD +## [3.8.47] — 2026-07-13 _Living section — bullets land here as PRs merge into `release/v3.8.47` (parallel-cycle model; cycle opened at the v3.8.46 release freeze). Finalized at the v3.8.47 release._ -### 📝 Maintenance +- **9router Codex import**: the Codex bulk-import endpoint (`POST /api/oauth/codex/import`) now accepts 9router's camelCase account export (`accessToken`/`refreshToken`/`idToken`/`expiresAt` + nested `providerSpecificData`), not just snake_case — `normalizeCodexImportRecord` maps the camelCase aliases onto the existing snake_case keys, filling each only when absent so snake_case/mixed exports keep working unchanged ([#6665](https://github.com/diegosouzapw/OmniRoute/issues/6665)) — thanks @deadcoder0904. Regression guard: `tests/unit/codexBulkImport.test.ts` (9router camelCase record, pre-supplied `providerSpecificData` without an id_token, snake_case-not-overridden, and a full `{accounts:[...]}` flatten). +### ✨ 新功能 + +- **feat(plugins):** Langfuse observability plugin. ([#6577](https://github.com/diegosouzapw/OmniRoute/pull/6577) — thanks @chirag127) + +- **feat(combo):** context requirements config for per-target filtering in combos. ([#6907](https://github.com/diegosouzapw/OmniRoute/pull/6907) — thanks @oyi77) +- **feat(providers):** icons for 46 providers that were missing images. ([#6926](https://github.com/diegosouzapw/OmniRoute/pull/6926) — thanks @oyi77) +- **feat(compression):** vendored GCF (Headroom) codec updated to spec v3.2 (nested flattening). ([#6838](https://github.com/diegosouzapw/OmniRoute/pull/6838) — thanks @blackwell-systems) +- **feat(proxy):** shorthand proxy formats + protocol header mode for bulk import. ([#6867](https://github.com/diegosouzapw/OmniRoute/pull/6867) — thanks @growab) +- **feat(provider):** OpenVecta AI inference gateway. ([#6833](https://github.com/diegosouzapw/OmniRoute/pull/6833) — thanks @hajilok) +- **feat(i18n):** Traditional Chinese (zh-TW) localization for frontend and CLI. ([#6320](https://github.com/diegosouzapw/OmniRoute/pull/6320) — thanks @lunkerchen) +- **feat(xai):** route xAI clients to Grok's native `/v1/responses` endpoint. ([#6709](https://github.com/diegosouzapw/OmniRoute/pull/6709) — thanks @diegosouzapw) +- **feat(routing):** per-model web-search/web-fetch interception rules. ([#3384](https://github.com/diegosouzapw/OmniRoute/issues/3384), [#6814](https://github.com/diegosouzapw/OmniRoute/pull/6814) — thanks @diegosouzapw) +- **feat(release):** `changelog.d/` fragments — eliminates the CHANGELOG merge-storm cascade. ([#6783](https://github.com/diegosouzapw/OmniRoute/pull/6783) — thanks @diegosouzapw) +- **feat(quality):** `validate-release-green --full-ci` reproduces the entire ci.yml static gate set locally. ([#6583](https://github.com/diegosouzapw/OmniRoute/pull/6583) — thanks @diegosouzapw) + +- **feat(dashboard):** sidebar quick-filter — a search input at the top of the expanded dashboard sidebar (`src/shared/components/Sidebar.tsx`) filters nav sections/groups/items client-side by label as you type, reusing the existing `common.search`/`common.noResults` i18n keys (zero new locale edits) and the shared `Input` `icon="search"` pattern; matching sections auto-expand while searching (bypassing the accordion/pin state) and collapse back to normal once the query is cleared. Pure filtering logic extracted into `filterSidebarSectionsByQuery()` (`src/shared/utils/sidebarSearch.ts`) for isolated unit testing. Regression guard: `tests/unit/sidebar-search-filter.test.ts`, `src/shared/components/Sidebar.search.test.tsx`. (#4013 — thanks @crochabe-cyber) +- **feat(combo):** `auto/*` combos gain a strict budget-cap fallback policy — `X-OmniRoute-Budget-Fallback: strict` (or the persisted `config.budgetFallback: "strict"`) makes an over-budget request fail fast with `HTTP 402` instead of the previous silent fallback to the globally cheapest candidate, which could still exceed the cap. The default (`cheapest`) preserves existing behavior. Builds on the existing `X-OmniRoute-Budget`/`X-OmniRoute-Mode` per-request controls (#6023/#6024/#6025), consolidated into `resolveRequestAutoControls()`. Regression guard: `tests/unit/auto-combo-budget-fallback-3470.test.ts`. (#3470) +- **Provider/model param filters**: config-driven parameter denylist/allowlist per provider/model with auto-learn from upstream 400s (#6649 — thanks @ThongAccount, closes #6625) +- **Per-combo reasoning token buffer toggle**: the combo builder now exposes an explicit checkbox for the `#3587` reasoning-model `max_tokens` buffer, defaulting to the existing enabled behavior, so a combo can opt out without hand-editing raw JSON config (#6702 — thanks @xz-dev) +- **feat(dashboard):** 9router-parity **Routing Strategy** settings card on Settings → Routing, plus a per-provider account-routing override on the provider detail page ([#6678](https://github.com/diegosouzapw/OmniRoute/pull/6678)) — surfaces the existing account round-robin / sticky-limit knobs and adds a new combo-level sticky round-robin (`comboStickyRoundRobinLimit`, resolved via `resolveComboStickyRoundRobinLimit()` — per-combo → global combo sticky → account sticky cascade) so combo targets can batch calls per target the same way account fallback already does. A new `providerStrategies` setting (Zod-validated map, `src/shared/validation/settingsSchemas.ts`) lets a specific provider override the global `fallbackStrategy`/`stickyRoundRobinLimit` without touching the account-wide default, wired into `getProviderCredentials()` (`src/sse/services/auth.ts`) ahead of the global fallback. Regression guard: `tests/unit/combo-rr-sticky-9router.test.ts`, `tests/unit/settings-ui-layout-static.test.ts`. (thanks @SeaXen) +- **feat(icons):** provider logos now resolve local SVG assets first for faster rendering, with a 5-tier fallback chain — local SVG → `@lobehub/icons` React components → `thesvg.org` CDN (external SVG for unknown providers) → local PNG → generic AI icon — replacing the previous LobeHub-first order. Adds dozens of first-party provider SVGs and migrates several bitmap logos (continue/copilot/cursor/deepgram/heroku/openclaw/ovhcloud) from PNG to SVG. Regression guard: `tests/unit/ui/ProviderIcon-icon-url.test.tsx`. ([#6317](https://github.com/diegosouzapw/OmniRoute/pull/6317) — thanks @hamsa0x7) +- **Skill Collector CLI detection**: new `GET /api/skills/collect/detect` + `POST /api/skills/collect/install` (and the `cli-skill-collector` agent skill) detect which coding CLIs (Claude Code, Codex, Cursor, Copilot, Cline, Hermes, OpenCode, etc.) are installed locally via `getCliRuntimeStatus()`, match them against GitHub agent-skill repos, and plan an install path per tool — replacing the standalone Skill Collector Python app. Both new routes and `GET/POST /api/github-skills` now require management auth (`requireManagementAuth()`) and are loopback-gated (`LOCAL_ONLY_API_PREFIXES` + `SPAWN_CAPABLE_PREFIXES`) since the detect route spawns a child process per candidate CLI tool (Hard Rules #15 + #17). The `omniroute_github_skills_install` MCP tool now reports the honest `action: "planned"` instead of `"installed"`, matching the REST route (#6294 — thanks @Moseyuh333) +- **ClinePass dual-auth**: ClinePass now offers both sign-in methods on its dashboard page — OAuth (reusing the Cline WorkOS flow) as the primary "Connect" path, or a pasted BYOK API key via "Manual API key", instead of only the API-key-only provider shipped in #5942. The registry alias was aligned to `cp` (matching the `OAUTH_PROVIDERS` catalog alias) so `/` routing resolves correctly, the OAuth refresh dispatch now routes `clinepass` to the shared Cline refresh flow, and the duplicate API-key-only catalog entry was removed to keep ClinePass listed once. Regression guard: `tests/unit/clinepass-provider.test.ts`. (#6126 — thanks @hajilok) +- **feat(oauth):** Kiro/Amazon Q auto-import now supports enterprise **External IdP** ("Your organization") logins via Microsoft Entra/Okta/Auth0/OneLogin/Ping/Google/Cognito — these org-issued tokens are not AWS SSO tokens (no `aorAAAAAG`-prefixed refresh token) and can't refresh through the AWS OIDC/Kiro-social path, so `tryAwsSsoCache()` now detects them (`authMethod`/`provider === "externalidp"`) and refreshes via the org IdP's own `tokenEndpoint` (public-client OAuth2 refresh grant, no client secret), persisting `TokenType: EXTERNAL_IDP` gating so the runtime executor sends the header the AWS CodeWhisperer API requires for these accounts; `tokenEndpoint` is SSRF-guarded against an HTTPS + known-IdP-host-suffix allowlist. (#6363 — thanks @artickc) +- **Kiro long-lived API key auth**: new `/api/oauth/kiro/api-key` route + `KiroService.validateApiKey` let a Kiro account be linked with a long-lived AWS CodeWhisperer/Kiro API key instead of the interactive OAuth device flow, with live per-account model discovery (`ListAvailableModels`, 5-minute cache) layered over the existing static registry fallback (#6587 — thanks @strangersp) +- **Chaos Mode**: multi-model parallel/collaborative task execution — dispatches a task to every active provider connection at once (parallel) or chains outputs sequentially so each model builds on the previous one's answer (collaborative), configurable via Dashboard → Chaos Mode (`GET`/`PUT`/`DELETE /api/chaos/config`) and gated per-API-key via a new `chaosModeEnabled` permission (opt-in — disabled by default globally and per key). `POST /api/chaos/run` (dashboard session) and `POST /api/skills/collect/chaos` (external Bearer-token) delegate to a shared `executeChaosRun()` engine (`src/lib/chaos/chaosExecutor.ts`) that dispatches in-process via the established synthetic-Request/route-handler pattern (no network hop, no hardcoded port), with a concurrency cap (max 10 parallel), configurable `max_tokens` (256–128k), a clear error when `stream` is requested, and collaborative-chain info (provider order + input size). Fixes external Bearer-auth bypass and stale config-cache leakage. Regression guard: `tests/unit/chaos-config.test.ts`, `tests/unit/chaos-executor.test.ts`, `tests/unit/chaos-api-routes.test.ts`. ([#6728](https://github.com/diegosouzapw/OmniRoute/pull/6728) — thanks @Moseyuh333) +- **feat(cli):** 2 new CLI tool integrations on Dashboard → CLI Tools — **omp** (Oh My Pi) and **letta** — each with binary detection, config apply/reset, and a settings card following the existing tool-card pattern. Both settings routes shell out to `which omp`/`which letta` to detect the local install, so they're loopback-gated (`LOCAL_ONLY_API_PREFIXES`, Hard Rules #15/#17) in addition to the shared `requireCliToolsAuth()` management-auth guard every cli-tools route requires, and route errors through `sanitizeErrorMessage()`; `src/lib/db/omp.ts` isolates the `omp` CLI's own local SQLite reads behind parameterized queries. (Note: the original PR also proposed **pi**, **codewhale**, and **jcode** integrations — those three had already shipped via a separate PR by the time this one was reconciled, so only omp+letta landed here.) Regression guard: `tests/unit/db/omp.test.ts`, `tests/unit/cli-tools-auth-hardening.test.ts`, `tests/integration/cli-settings-omp.test.ts`, `tests/integration/cli-settings-letta.test.ts`. ([#6318](https://github.com/diegosouzapw/OmniRoute/pull/6318) — thanks @hamsa0x7) +- **feat(providers):** custom models now support a manual **Context Window Override** so an operator can correct a provider's misreported context length (e.g. reports 1M when the real limit is 128K) instead of the model silently getting dropped from combo routing once the wrong value lands in the catalog ([#4125](https://github.com/diegosouzapw/OmniRoute/issues/4125) — thanks @rucciva). Reuses the existing Feature-5004 `model_context_overrides` table (`source: "manual"`) — already the priority-0 source `getModelContextLimit()` (the function combo's context-window filter calls) reads ahead of the models.dev/registry/static catalog — so no new resolver logic was needed, only the missing write path: `PUT /api/provider-models` now accepts an optional `contextWindowOverride` (number to set, `null` to clear), `GET` surfaces the current value back on each custom-model row, and the provider detail page's custom-model edit form gained a Context Window Override field + badge. Regression guard: `tests/unit/provider-models-context-window-override-4125.test.ts`. +- **fix(providers):** register OpenRouter as a rerank provider so `openrouter/cohere/rerank-*` models resolve instead of erroring `Invalid rerank model` (#6574 — thanks @rafpigna) +- **fix(api):** `HEAD` requests no longer hang until client timeout on any route — valid, unknown, authed, or unauthed ([#6400](https://github.com/diegosouzapw/OmniRoute/issues/6400)), broader follow-up to the route-specific #6517 (`/v1/models`). Root cause: Next.js 16's App Router _route-handler_ pipeline (`next/dist/server/send-response.js`) correctly skips piping a `Response` body for `HEAD`, but its _page_-rendering pipeline (`next/dist/server/pipe-readable.js` → `pipeToNodeResponse`, used for every app-router page/layout render — including the `not-found` boundary any unmatched path falls through to) has no such check and always streams the full rendered body regardless of method; combined with Node's default keep-alive framing this left some clients unsure whether the (implicitly bodyless) `HEAD` response had actually finished. A new `scripts/dev/head-response-guard.cjs`, wired into both the dev/start custom server (`scripts/dev/run-next.mjs`) and the packaged standalone server (`scripts/dev/standalone-server-ws.mjs`) at the same tier as the existing `http-method-guard.cjs`/`peer-stamp.mjs` wrappers, discards any body bytes written for a `HEAD` request and forces `Connection: close` once `.end()` is called — independent of route existence or auth state, satisfying RFC 9110 §9.3.2. Regression guard: `tests/unit/head-request-closes-6400.test.ts`. +- **feat(dashboard):** Provider Quota page (`Dashboard → Quota`) fills horizontal whitespace before stacking vertically ([#3520](https://github.com/diegosouzapw/OmniRoute/issues/3520)) — `QuotaCardGrid` previously stacked every provider group in a single vertical `flex flex-col`, and each group's own card grid didn't go multi-column until `md` (`grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4`). Provider groups now flow into a 2-column CSS multi-column layout on very wide (`2xl`) screens instead of an unconditional vertical stack, and each group's card grid starts at 2 columns immediately (`grid-cols-2 md:grid-cols-3 xl:grid-cols-4`), reaching higher density sooner on narrower-but-not-mobile viewports. Regression guard: `tests/unit/quota-card-grid-horizontal-layout.test.ts` (thanks @gdevenyi). +- **feat(ws):** the live-dashboard WebSocket server now auto-starts in-process (via `instrumentation-node.ts`) across every deployment mode — dev, production, Docker, Electron — with no separate sidecar script; the default WS port moved from 20129 to **20132** to avoid colliding with `API_PORT` in split-port setups, the deprecated `OMNIROUTE_DISABLE_LIVE_WS` env was consolidated into `OMNIROUTE_ENABLE_LIVE_WS` (default enabled), and the WS path is now derived from `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL`'s pathname (`/live-ws` fallback) (#6072 — thanks @ianriizky). +- **feat(compression):** new **omniglyph** engine (context-as-image) — renders system prompt, tool docs, and dense history as compact PNG pages the model reads instead of text (~10× fewer tokens on the converted block; 59–70% end-to-end measured). Works stacked with RTK/Caveman (`stackPriority: 90`) or standalone (`mode: omniglyph`); restricted to Claude Fable 5 over the direct Anthropic route, fail-closed gates with `skip:` techniques, preview (`stable: false`, off by default) (#6556). Dependency bumped to `omniglyph@^1.0.2` for upstream ReDoS fixes (#6661). +- **feat(sandbox):** the skill sandbox gained a container-provider abstraction that auto-detects and uses the best native runtime per host — Apple Container (macOS 26+), WSL container (`wslc.exe`), OrbStack, Podman — instead of hardcoding `docker run`, removing the Docker Desktop requirement on macOS/Windows (#6611 — thanks @KooshaPari). +- **fix(sse):** skip `thinkingConfig` for Gemma models on the OpenAI→Gemini path so OpenAI-shape clients no longer get a 400 from Vertex. (thanks @chy1211) +- **feat(xai):** route xAI clients to Grok's native `/v1/responses` endpoint instead of the chat-completions bridge. (thanks @ryanngit) +- **feat(models):** add a Settings → AI "Model Overrides" UI plus `/api/model-capability-overrides` CRUD and a `model_capability_overrides` table, letting operators set a manual max-output-token override per provider/model (#6727 — thanks @xz-dev). +- **feat(resilience):** operator-configurable account rotation policy — a new `rotationConfig` layer lets operators tune how connections rotate on failure, wired into `accountFallback` (#6763 — thanks @artickc). +- **chore(cursor): add Grok 4.5 effort/fast model IDs** (#6774 — thanks @andrewmunsell). +- **feat(codex):** Codex provider model discovery now fetches the live catalog from `chatgpt.com/backend-api/codex/models` using Codex-shaped headers, falling back to a GitHub-hosted model manifest and then to the local static catalog when the live/GitHub sources are unavailable or return an unexpected shape — new `src/app/api/providers/[id]/models/discovery/codex.ts` (normalization, version-gating, merge/enrich against the local catalog) covered by `tests/unit/provider-models-discovery-split.test.ts` and `tests/unit/provider-models-route-codex.test.ts` (#6776 — thanks @JxnLexn). +- **feat(cursor):** register the Opus 4.8, Fable 5, and Sonnet 5 model families for the Cursor Agent provider so the latest Claude/Fable model ids route correctly (#6779 — thanks @andrewmunsell). +- **Changelog fragments (`changelog.d/`)**: PRs now add their changelog entry as a new fragment file (`changelog.d/{features|fixes|maintenance}/-.md`) instead of editing `CHANGELOG.md` — two PRs never touch the same file, structurally eliminating the CHANGELOG-eat merge conflicts that forced a re-sync push + full CI re-run after every sibling merge (O(N²) CI runs in a merge-storm). `scripts/release/aggregate-changelog.mjs` (npm run changelog:aggregate) folds fragments into the living section at release reconciliation, and `check:changelog-integrity` now also validates fragment well-formedness. Regression guard: `tests/unit/changelog-fragments.test.ts`. +- **feat(proxy):** add a latency-optimized proxy rotation strategy that ranks pool entries by measured round-trip latency, extending the existing round-robin/random/sticky proxy-pool selection (#6798 — thanks @iamraydoan). +- **feat(fusion):** the fusion judge may now draw on its own knowledge and override the panel when every panel answer is wrong or incomplete, instead of being restricted to synthesizing only from panel output (#6804 — thanks @chirag127). +- **feat(dashboard):** search box on the Playground's raw model `` to enable Send, asserts `sendBtn.disabled === false` before clicking, and asserts the streamed SSE delta (`"Hello!"`) actually reached the response editor. Root cause on the detector side: `check-test-masking.mjs`'s tautology subcheck only compares base-vs-HEAD counts within a PR's own diff (`headExtTaut > baseExtTaut`) and no-ops locally when `GITHUB_BASE_SHA`/`GITHUB_BASE_REF` are unset ("sem base ref — pulando") — so a tautology merged once, or checked with a bare local run, was invisible forever after. Added a new always-on, PR-independent absolute-floor scan (`scanBareTautologies` + `countBareTautologies`) over every git-tracked test file for the bare `expect(true).toBe(true)` / `assert.equal(1,1)` / `assert.strictEqual(1,1)` patterns specifically (deliberately excluding `assert.ok(true)`, which has ~15 pre-existing verified-legitimate try/catch-fallback uses repo-wide and stays governed by the lenient diff-only subcheck) — verified zero pre-existing hits repo-wide once this file was fixed, so the new floor is safe to enforce unconditionally. Regression guard: `tests/unit/check-test-masking.test.ts` (new `scanBareTautologies`/`countBareTautologies` cases) + `tests/unit/ui/playground-api-tab.test.tsx`. (thanks @chirag127) +- **fix(oauth):** Codex/ChatGPT (and every other OAuth provider) connection stays stuck showing "Auth Failed" even after a genuinely successful token refresh ([#6352](https://github.com/diegosouzapw/OmniRoute/issues/6352)) — `updateProviderCredentials()` (the shared `onPersist` callback for the manual "Refresh token" route, the reactive per-request refresh in `chat.ts`, and the Codex/Claude auth-file importers) correctly reused the stored `refresh_token`, persisted the new `access_token`, and replaced a rotated `refresh_token`, but never cleared the stale `testStatus`/`lastError*`/`errorCode` fields left over from a prior expired/invalid refresh or upstream 401/403 — only the separate background health-check sweep did that clearing. A successful refresh now resets `testStatus` to `"active"` and clears `lastError`, `lastErrorAt`, `lastErrorType`, `lastErrorSource`, and `errorCode` (an explicit `testStatus` from the caller still wins). Regression guard: `tests/unit/codex-oauth-refresh-persist-6352.test.ts`. +- **perf(health):** short-TTL (1s) cache for the frequently-polled `GET /api/monitoring/health` payload — rebuilding it every request (DB reads + status aggregation across 8 subsystems) was wasteful under rapid polling; the cache stays near-real-time and is invalidated immediately on `DELETE` (circuit-breaker reset) so a manual reset is reflected at once. Regression guard: `tests/integration/monitoring-health-cache.test.ts`. ([#6553](https://github.com/diegosouzapw/OmniRoute/pull/6553)) — see PR. (thanks @developerjillur) +- **fix(resilience):** `headroom` combo routing did not always select the Codex account with the most free quota ([#6379](https://github.com/diegosouzapw/OmniRoute/issues/6379)) — `orderTargetsByHeadroom` (`open-sse/services/combo/quotaStrategies.ts`) already loaded the per-connection DB snapshot (with decrypted credentials) via `expandTargetsByQuotaAwareConnections`, but discarded it before calling `getSaturation`; for Codex, `fetchCodexSaturation` forwards straight to `fetchCodexQuota(connectionId, connection)`, which needs `connection` (or a prior `registerCodexConnection()` call, which never happens before headroom ranking runs) to read `accessToken` — so it returned `null` for every candidate, saturation failed open to `0` across the board, and ranking fell back to the original combo order regardless of actual free quota. `getSaturation()` and the headroom `SaturationFetcher` seam now accept and thread the loaded connection snapshot through to `fetchCodexQuota`. Kilo's dup flag vs #5903 was a false positive — that issue is about session-sticky reset-aware/least-used selection, not headroom's Codex saturation lookup. Regression guard: `tests/unit/headroom-codex-quota-snapshot-6379.test.ts`. (thanks @eidoog) +- **fix(providers):** custom models a provider actually has are no longer dropped from the Free Provider Rankings when both **"Configured only"** and **"Available only"** filters are applied ([#6368](https://github.com/diegosouzapw/OmniRoute/issues/6368)), follow-up to #6150 — `freeProviderRankings.ts::getProviderModels()` only ever walked the static `open-sse/config/providerRegistry.ts` catalog, so a user-added custom model (e.g. a Puter `claude-fable-5` model saved as "Claude Fable 5") never entered the candidate model list the ranking scores against, and could never survive the #6150 configured/available filters even when actually configured and available. It now additively merges the provider's custom models (`db/models.ts::getCustomModels`) into that candidate list via a new pure, de-duping `mergeProviderModels()` helper, before scoring/filtering runs — catalog free/paid filtering elsewhere is untouched. Regression guard: `tests/unit/free-provider-rankings-custom-models-6368.test.ts`. (thanks @shabeer) +- **fix(playground):** accept a valid dashboard session for `GET/POST /api/playground/presets` under `REQUIRE_API_KEY=true` — the Playground page calls this route with a cookie/session and no API key, which previously 401'd the authenticated dashboard; `checkAuth()` now accepts a management/dashboard session (`requireManagementAuth`) as an alternative to an API key, while a presented API key must still be valid and the anonymous-allowed default is preserved. Regression guard: `tests/integration/presets-dashboard-auth.test.ts`. ([#6554](https://github.com/diegosouzapw/OmniRoute/pull/6554)) — see PR. (thanks @developerjillur) +- **fix(providers):** `cloudflare-ai` no longer silently drops image/non-text content parts ([#6390](https://github.com/diegosouzapw/OmniRoute/issues/6390)) — `transformRequest()`'s `flattenContent()` (added for #2539 to satisfy the Workers AI `/ai/v1/chat/completions` plain-string `content` requirement) mapped any non-text OpenAI content part (e.g. `image_url`) to `""` and joined the rest, so a request carrying an image quietly went out as text-only with the attachment gone and no error surfaced. It now throws a clear error on the first non-text part instead of dropping it silently, which the existing top-level `chatCore.ts` catch already routes through `buildErrorBody()`/`sanitizeErrorMessage()` (same pattern as `buildUrl()`'s missing-Account-ID error). Regression guard: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. +- **fix(providers):** stop Antigravity connections from falsely reporting **all-accounts quota-exhausted** ([#6295](https://github.com/diegosouzapw/OmniRoute/issues/6295)) — `genericQuotaFetcher.ts::percentUsedForQuota()` ignored the `fractionReported` flag and defaulted an unreported model's `remainingPercentage` to 0, which computed as 100% used; since `convertUsageToQuotaInfo()` takes the worst-case window across a connection, a single model with no reported fraction dragged the whole account into `limitReached` and `quotaPreflight` skipped it. `percentUsedForQuota()` now returns `null` (unknown, window ignored) whenever `fractionReported === false`, before falling back to `remainingPercentage`. Regression guard: `tests/unit/generic-quota-fetcher.test.ts`. +- **fix(fusion):** the fusion judge no longer replays a panel member's answer via an idempotency-key collision — fusion's panel + judge sub-requests re-enter `chatCore` sharing the client's headers, so they derived the same `Idempotency-Key`/`x-request-id` and a panel answer saved under the key was replayed by the judge's check ~1ms later (inside the 5s window), returning a panel member's answer instead of the judge synthesis (observed live on `nexa/conversation-fusion`). `composeIdempotencyKey()` now namespaces the key by target provider/model + a digest of the request messages, so sub-requests can't collide while a genuine client retry (same key/model/body) still replays. Regression guard: `tests/unit/idempotency-fusion-collision.test.ts`. ([#6558](https://github.com/diegosouzapw/OmniRoute/pull/6558)) — see PR. (thanks @developerjillur) +- **fix(providers):** grok-cli (Grok Build) now strips `reasoning_effort`/`reasoning` before forwarding the request ([#6288](https://github.com/diegosouzapw/OmniRoute/issues/6288)) — Claude Code sends `reasoning_effort` on every request (routing the Opus slot), which Grok Build's upstream chat-proxy endpoint rejects with a 400; `transformRequest()`'s existing `UNSUPPORTED` sampling-param strip list (#5273) never covered it. Regression guard: `tests/unit/grok-cli-reasoning-strip-6288.test.ts`. +- **fix(cli):** `omniroute serve` no longer hangs silently on a readiness timeout ([#6321](https://github.com/diegosouzapw/OmniRoute/issues/6321)) — the child server's stdout was piped to `"ignore"` whenever `--log`/`OMNIROUTE_SHOW_LOG` wasn't set (the default), discarding any debug output, and `runWithSupervisor`'s `waitForServer(...).then((up) => { if (up) {...} })` had no `else` branch, so a boot that never became ready produced zero further output after "⏳ Starting server...". Stdout is now buffered alongside stderr (`ServerSupervisor.getRecentLog()`), and a timeout prints a clear diagnostic plus the buffered output instead of staying silent. Does not by itself explain why boot never completes on a given machine — see the issue for further reproduction. Regression guard: `tests/unit/cli-serve-readiness-timeout-6321.test.ts`. +- **fix(pricing):** Pricing Sync dashboard no longer stuck on "Next Sync: Never" / "Synced Models: 0" ([#6325](https://github.com/diegosouzapw/OmniRoute/issues/6325)) — `pricingSync.ts` kept sync state (`lastSyncTime`, `lastSyncModelCount`) in module-level vars, but the background periodic sync (`instrumentation-node.ts`) and the dashboard status route (`/api/pricing/sync`) each import the module from separate Next.js standalone webpack chunks, giving each its own independent state; `getSyncStatus()` read the (empty) API-route instance's vars. Sync status is now additionally persisted to a new `pricing_sync_status` `key_value` namespace and `getSyncStatus()` falls back to it when the local module instance never ran a sync itself. Regression guard: `tests/unit/pricing-sync-cross-instance.test.ts`. +- **fix(api):** stop spuriously 403-ing "Invalid request origin" on `POST /api/providers/health-autopilot/actions` for Docker/LAN dashboard requests ([#6277](https://github.com/diegosouzapw/OmniRoute/issues/6277)) — the route carried a duplicate per-route `validateBrowserMutationOrigin` check re-added by the v3.8.42 release squash after PR #5278 centralized origin enforcement in the authz pipeline; the pipeline strips `PEER_IP_HEADER` before forwarding, so the stale duplicate check could no longer resolve the LAN "direct-local-host" candidate and rejected legitimate same-origin LAN mutations (e.g. clicking "remove cooldown" when accessed via a LAN IP). Removed the duplicate check — origin validation is now solely enforced by the centralized pipeline check, which already handles this case correctly. Regression guard: `tests/unit/serial/provider-health-autopilot.test.ts`. +- **fix(resilience):** a bare, unrecognized `403` from a no-credential (`authType:"none"`) provider like mimocode or theoldllm no longer permanently bans the connection ([#6315](https://github.com/diegosouzapw/OmniRoute/issues/6315), [#6345](https://github.com/diegosouzapw/OmniRoute/issues/6345)) — `classifyProviderError()`'s 403 branch only exempted `apikey` providers from the terminal `FORBIDDEN` classification, so these free/stateless proxies (no real account/credential to revoke) fell through to `FORBIDDEN` on the first unmatched 403 and got `isActive:false, testStatus:"banned"` with no cooldown or retry. The exemption now also covers `authType:"none"` providers, returning `null` (recoverable) so the existing connection-cooldown/retry layer handles it. Regression guard: `tests/unit/errorClassifier-noauth-403-6315.test.ts`. +- **fix(providers):** the **Auggie (Augment CLI)** executor no longer fails on Windows with `spawn EINVAL` ([#6304](https://github.com/diegosouzapw/OmniRoute/issues/6304)) — the global-npm install exposes `auggie` as a `.cmd` shim, which Node's `child_process.spawn` cannot launch on win32 without `shell: true`. Both spawn sites (streaming + the `auggie --version` test) now go through a shared `buildAuggieSpawnOptions()` that sets `shell: process.platform === "win32"`; the argv (built by `buildAuggieArgs()` with a registry-validated `model` and a trailing `--` end-of-options marker) is unchanged, so the argument-injection surface stays closed on non-Windows. Regression guard: `tests/unit/auggie-win32-spawn-6304.test.ts`. +- **fix(api):** the dashboard **"Test model"** action is now a clean connection test ([#6240](https://github.com/diegosouzapw/OmniRoute/issues/6240)) — `modelTestRunner` sent its probe request without an explicit compression override, so whenever the operator's global `compression.enabled` flag was on the test call inherited compression (and any Output-Styles system prompt), polluting the result. The internal test requests now send `X-OmniRoute-Compression: off`, and `chatCore` honors an explicit `off` header even when `compression.enabled` is globally true. Regression guards: `tests/unit/model-test-runner-compression-off-6240.test.ts`, `tests/integration/test-model-compression-off-6240.test.ts`. +- **fix(startup):** an update/restart could crash the whole server at boot with `TypeError: Cannot create property 'message' on string 'Database closed'`, masking the real failure and 500-ing every request until manually restarted ([#6560](https://github.com/diegosouzapw/OmniRoute/issues/6560), plausibly the root cause of #6594's post-upgrade 500) — `driverFactory.ts::preInitSqlJs()` cached its sql.js WASM adapter per file path in a `globalThis`-backed map for idempotency, but never checked whether the cached adapter had since been closed (e.g. by `gracefulShutdown`/`resetDbInstance` racing a reload); reusing that dead handle made the very next query throw sql.js's own bare string `"Database closed"` (not an `Error`) straight out of `instrumentation-node.ts`'s previously-unguarded `ensureDbInitialized()` call, and Next.js's internal `registerInstrumentation()` wrapper unconditionally does `err.message = ...` on whatever `register()` rejects with — assigning `.message` on a primitive string throws in strict mode, so the secondary `TypeError` is what actually crashed the process. Fixed in two parts: `preInitSqlJs()` now evicts a closed cached adapter and creates a fresh one instead of returning it; a new `ensureDbReadyForBoot()` wraps the DB-init call, normalizes any non-Error throw via `normalizeBootError()`, and retries once specifically for a transient "database closed" message (now succeeding against the fresh adapter) before re-throwing anything else as a real `Error`. Regression guard: `tests/unit/instrumentation-database-closed-6560.test.ts`. +- **fix(api):** `POST /api/keys` no longer hangs 20–90+ seconds on a fresh install, even with valid auth ([#6570](https://github.com/diegosouzapw/OmniRoute/issues/6570)) — `cloudEnabled` defaults to `true` in `src/lib/db/settings.ts::getSettings()` on any install with no persisted settings row (i.e. every fresh install), so the create-key handler's unconditional `await syncKeysToCloudIfEnabled()` always attempted a real outbound `fetch()` to `CLOUD_URL` via `syncToCloud()`; when that endpoint is unset/unreachable/slow, the HTTP response blocked until the request settled or timed out — unlike sibling routes (`POST /api/keys/:id/regenerate`, `GET /api/combos`), which never touch this side effect at all. `src/app/api/keys/route.ts` now dispatches `syncKeysToCloudIfEnabled()` fire-and-forget (`void`) instead of awaiting it; its internal try/catch already logs failures, so cloud sync still runs, it just no longer blocks the response. Regression guard: `tests/unit/api-keys-create-no-hang-6570.test.ts` (asserts the route resolves in well under 2s even when the Cloud-sync `fetch()` is stubbed to never settle) + updated `tests/integration/api-keys.test.ts` (the pre-existing "triggers cloud sync"/"still succeeds when cloud sync fails" tests, which previously hung indefinitely on this exact path, now await the fire-and-forget sync tick before asserting). +- **fix(api):** editing any existing OpenAI Codex provider connection in the dashboard returned "Invalid request" and the edit could never be saved ([#6562](https://github.com/diegosouzapw/OmniRoute/issues/6562)) — `createProviderConnection()` (`src/lib/db/providers.ts`) auto-increments a new connection's `priority` to `MAX(priority)+1` per provider with no upper bound, and OAuth-imported connections (Codex `codex-auth/import` / `import-bulk`, up to 50 accounts per call, callable repeatedly — the standard Codex bulk-account-rotation workflow) never pass through `createProviderSchema`'s Zod validation at all, so nothing ever capped that value; `EditConnectionModal`'s `handleSubmit` always resends the connection's current `priority` unchanged on every save, and `updateProviderConnectionSchema` capped `priority`/`globalPriority` at `max(100)` — a UI-only ceiling the create path never enforced — so the first edit of any connection whose priority had already grown past 100 (routine once a Codex account count exceeds 100) failed validation regardless of which field the user actually changed. Raised the ceiling to `max(100_000)` on both fields — still bounded (a genuinely out-of-range value is still rejected), just wide enough to accept priorities the app itself already produces. Regression guard: `tests/unit/codex-connection-edit-6562.test.ts` (a Codex OAuth connection whose priority already exceeds the old 100 cap now validates + persists on edit; a control payload with a still-genuinely-invalid priority is still rejected with "Invalid request"). +- **mimocode**: rotate accounts on MiMoCode's rate-limit-style 400s (body-classified) instead of failing on the first account; malformed 400s still fail fast with the real upstream error (#6648 — thanks @pizzav-xyz) +- **fix(cli):** compression CLI REST fallback now reads/writes the canonical `defaultMode` field (surfaced as `strategy`) instead of a nonexistent `engine` key, and table output renders nested objects as JSON instead of `[object Object]` (#6571 — thanks @charleszolot) +- **fix(providers):** web-cookie providers without a `providerRegistry.ts` entry (`lmarena`, `gemini-business`, `poe-web`, `venice-web`, `v0-vercel-web`) now report `unsupported: true` instead of silently "OK" ([#6309](https://github.com/diegosouzapw/OmniRoute/pull/6309)) — `validateWebCookieProvider()` (`src/lib/providers/validation.ts`) previously required a registry entry and returned "Provider not found in registry" for these; a fallback to `WEB_COOKIE_PROVIDERS[provider].website` was proposed, but live verification showed the `${website}/models` probe does not reliably signal session validity for these providers (redirects/SPA 200s regardless of cookie validity — e.g. lmarena's real API is `arena.ai`, not `lmarena.ai`; Poe's real endpoint is a GraphQL POST, not a REST `/models`), so it would report an expired or garbage cookie as valid. Until each provider has a verified, side-effect-free auth probe against its real API host, the fallback now returns `unsupported` (no network call) instead of a false positive. Regression guard: `tests/unit/web-cookie-validation-fallback.test.ts`. (thanks @oyi77) +- **fix(api):** `POST /api/middleware/hooks` and `PUT /api/middleware/hooks/[name]` no longer leak raw internal error messages in their 500 responses (#6645 — thanks @chirag127) — both catch blocks returned `error?.message` directly (Hard Rule #12), which could surface internal SQLite path fragments on a DB failure; both now route through `sanitizeErrorMessage()` from `open-sse/utils/error.ts`. Regression guard: `tests/unit/middleware-hooks-error-sanitization.test.ts`. +- **fix(docker):** compile better-sqlite3 for the server Docker image (Dokploy/self-hosted builds) via a direct `node-gyp rebuild` inside `node_modules/better-sqlite3`, instead of `npm rebuild better-sqlite3` ([#6700](https://github.com/diegosouzapw/OmniRoute/pull/6700)) — the `builder` stage installs dependencies with `npm ci --ignore-scripts` (deliberate: closes the supply-chain surface where a transitive dep's install script runs arbitrary code) and re-enables the native build for the one package that needs it; `npm rebuild ` re-runs that indirectly through the package's own install script, which under npm 11 depends on npm's script-allowlist machinery correctly re-enabling it — some self-hosted build environments (e.g. Dokploy) hit a broken/mismatched native binding through that indirection. Invoking `node-gyp rebuild` directly bypasses npm's script-running layer entirely and is deterministic regardless of npm version. Regression guard: `tests/unit/dockerfile-better-sqlite3-node-gyp-6700.test.ts`. (thanks @nowhats-br) +- **fix(providers):** the Cloudflare relay Worker deploy fix in #6416/#6618 still failed uploads in practice — it changed the multipart Content-Type but kept the emitted worker source as an ES module (`export default { fetch(...) }`) with `main_module` metadata; Cloudflare's Workers upload API parses a plain `application/javascript` script part as Service Worker syntax regardless of the `main_module` metadata field, and `main_module` requires the script to actually be an ES module (top-level `export`), so the mismatch still rejected the upload ([#6496](https://github.com/diegosouzapw/OmniRoute/pull/6496)). `buildCloudflareWorkerScript()` (`src/lib/proxyRelay/cloudflareWorkerScript.ts`) now emits Service Worker syntax (`addEventListener("fetch", ...)`, no top-level `export`) and the upload metadata uses `body_part` instead of `main_module`. Regression guard: `tests/unit/relay-deploy-5128.test.ts` (asserts the emitted script has no `export default`, registers a `fetch` listener, and the upload metadata carries `body_part`/omits `main_module`; also proves the inlined `isPrivateHostname()` SSRF guard still rejects bracketed IPv6 loopback/ULA hosts like `[::1]`/`[fd00::1]` after the script-body rewrite). (thanks @SeaXen) +- **fix(providers):** ChatGPT Web (`chatgpt-web`) responses rendered raw ChatGPT UI citation markup — private-use marker tokens (e.g. `citeturn0search0`) and `url…` inline-link markers — instead of real Markdown links, since these only ever get resolved client-side by chatgpt.com's own JS using `message.metadata.content_references` ([#6635](https://github.com/diegosouzapw/OmniRoute/pull/6635)) — `cleanChatGptText()` now resolves `content_references` (grouped webpages, footnote sources, inline `webpage`/`url` mentions) into `[label](url)` Markdown links for both the streaming and non-streaming response builders, and for the GPT-5.5 Pro `stream_handoff` polled-answer path, falling back to stripping any marker that has no resolvable source instead of leaking the raw private-use bytes. The citation parsing/rendering logic was extracted into a new pure sibling module (`open-sse/executors/chatgpt-web/citations.ts`) to keep the executor under the frozen file-size cap. Regression guard: `tests/unit/chatgpt-web-citations.test.ts` (non-streaming citation resolution, streaming marker buffering across split SSE chunks, and the Pro-handoff polled-answer path). (thanks @Thinkscape) +- **fix(dashboard):** the live-dashboard WebSocket descriptor handshake (`GET /api/v1/ws?handshake=1`) and the lightweight `GET /api/health/ping` liveness probe both 401'd for unauthenticated callers, even though both are metadata-only reads intended to be public ([#6335](https://github.com/diegosouzapw/OmniRoute/pull/6335)) — `clientApiPolicy` required a bearer/dashboard-session before the WS route handler could even return its own `wsAuth`/protocol descriptor, and `/api/health/ping` was never added to `PUBLIC_READONLY_API_ROUTE_PREFIXES` despite its own docstring documenting it as "No auth required." `clientApiPolicy.evaluate()` now allows an anonymous `{kind:"anonymous", id:"ws-handshake"}` subject for GET/HEAD/OPTIONS on `/api/v1/ws?handshake=1` (the route handler still performs its own real wsAuth/dashboard/API-key decision before opening the socket), and `/api/health/ping` is now in `PUBLIC_READONLY_API_ROUTE_PREFIXES`. Regression guard: `tests/unit/authz/client-api-policy.test.ts` (WS handshake allowed, including relative request URLs), `tests/unit/public-api-routes.test.ts`, and `tests/unit/authz/classify.test.ts` (`/api/health/ping` classified `PUBLIC`). (thanks @JxnLexn) +- fix(providers): wire the Devin cloud-agent provider into the generic provider-page validator and static model catalog, matching the existing `jules` cloud-agent pattern (#6142) +- fix(providers): honor a provider-level proxy assigned to no-auth providers like MiMoCode Free (#6272) +- fix(api): merge tool_call continuation deltas that carry only `id` (no `index`) so tool-call arguments are no longer split/lost in request/response logs (#6276) +- **fix(providers):** modernize the `lmarena` provider for the Arena.ai rebrand — route chat through `arena.ai` create-evaluation with Chrome TLS impersonation, seed a static Direct-chat Text/Search + Image catalog, and keep the `lmarena`/`lma` wire id for back-compat ([#6280](https://github.com/diegosouzapw/OmniRoute/pull/6280)) — thanks @backryun +- **fix(providers):** web-provider model discovery updated — qwen-web uses the slash-terminated models endpoint (avoiding a blocked 307 redirect), and kimi-web matches the current request shape (POST with bearer + `kimi-auth` cookie replay) with its catalog refreshed to the current non-agent models (#6308 — thanks @janeza2). +- **fix(logs):** the request-log detail modal no longer reopens by itself after being closed — a stale in-flight detail refresh resolved after close and re-triggered the modal open state (#6323 — thanks @xz-dev). +- **fix(providers):** update SenseNova Token Plan support — register the token-plan model ids/constants and adjust the SenseNova registry so token-plan accounts route correctly (#6330 — thanks @xz-dev). +- fix(providers): give v0-vercel-web its own alias so its credentials are detected (#6343) +- fix(providers): route AgentRouter key validation through the CC wire image so a valid key no longer 403s as "Invalid API key" (#6377) +- fix(db): stop legacy log-archive migration from deleting the live app-logger directory and crashing startup on a stat/stream race (#6401, #6799) +- fix(docs): document Turbopack build memory tradeoff and `OMNIROUTE_USE_TURBOPACK=0` webpack fallback for RAM-constrained machines (#6409) +- fix(compression): surface silently-dropped stacked-pipeline steps (session-dedup, ccr) and stop the aggregate inflation guard from misfiring on a genuine no-op (#6479, #6480, #6491) +- fix(providers): honor the `max_token` capability override in the reasoning-token-buffer output cap (#6524) +- **fix(dashboard):** the onboarding tier-flow diagram rendered broken — its SVGs lived in the repo-root `images/` (not a served path); moved to `public/images/` so Next.js serves them (#6538 — thanks @ianriizky). +- **fix(routing):** the `auto` combo's no-auth candidate pool now honors a disabled provider connection's own `isActive=false` (the toggle on the main Providers grid card), not just the separate global `blockedProviders` setting — disabling opencode/mimocode/etc. via the grid toggle no longer leaves it in rotation ([#6557](https://github.com/diegosouzapw/OmniRoute/issues/6557)). +- **fix(sse):** server-tool literal names (e.g. `web_search`) are preserved in message history and `tool_choice` instead of being namespaced/rewritten, so follow-up turns referencing those tools keep working (#6586 — thanks @MikeTuev). +- fix(api): recognize OpenRouter reasoning/reasoning_details in non-streaming OpenAI-to-Claude conversion (#6623) +- fix(db): share one in-flight sql.js load across concurrent `preInitSqlJs()` callers to stop the boot-time thundering-herd re-decode of the whole database file (#6628) +- fix(db): unwrap lone named-parameter objects before `sql.js` `stmt.bind()` so `@`/`:`/`$`-style named placeholders bind correctly instead of throwing "Wrong API use" (#6802) +- **fix(db): break probe-failed/restore loop on large storage.sqlite** (#6632 — thanks @KooshaPari). +- fix(ci): exclude check-test-masking.test.ts's own tautology fixtures from the diff-based test-masking gate and recognize `✗` in validate-release-green's failure-line detector (#6634) +- fix(routing): recognize Kimi-style "exceeded model token limit" 400 as context overflow so combo fallback continues to the next target (#6637) +- **fix(cli):** Claude Code installed via WinGet is now detected on Windows (the WinGet install path was missing from the binary lookup) (#6647 — thanks @enjoyer-hub). +- **fix(providers):** removed obsolete/defunct providers from the catalog (glhf, kluster, cablyai, inclusionai) (#6675 — thanks @backryun). +- **fix(sse):** requests rejected before `handleChatCore` (circuit-breaker/cooldown gate or combo with all targets exhausted) are now recorded in `usage_history` too, so a key whose traffic was entirely gate-rejected no longer shows "zero requests" in the per-API-key usage counter (#6698). +- **fix(sse):** unwrap bare `{function:{…}}` tools so OpenAI-shape clients no longer have tools silently dropped in Claude translation. (thanks @samir-abis) +- **fix(oauth):** stop merging distinct Codex OAuth logins that share an email but lack a verifiable account id, preventing silent token overwrite. (thanks @lucasjustinudin) +- **fix(codex):** detect "model at capacity"/overloaded errors embedded in a 200-OK SSE stream and surface them as a real error so account fallback rotates, instead of passing them through as a successful response. (thanks @ryanngit) +- **fix(volcengine):** clamp `max_tokens` to the VolcEngine Ark endpoint cap for the Kimi model so oversized values no longer 400. (thanks @whale9820) +- **fix(antigravity):** surface aborted/malformed Gemini tool calls (e.g. `MALFORMED_FUNCTION_CALL`) as an explicit non-`end_turn` finish reason instead of a silent clean completion. (thanks @anhdiepmmk) +- **fix(routing):** the reasoning-token headroom buffer clamps to the model's explicit output cap instead of inflating past it, and `getExplicitModelOutputCap` falls through to the registry/spec cap when a synced capability row exists without a numeric `limit_output` ([#6714](https://github.com/diegosouzapw/OmniRoute/pull/6714)) — thanks @xz-dev +- **fix(api):** `omniroute health` (and `health components`/`health watch`) returned `Error: HTTP 404` ([#6677](https://github.com/diegosouzapw/OmniRoute/issues/6677)) — `bin/cli/commands/health.mjs` called `apiFetch("/api/health", ...)`, a route that was moved to `GET /api/monitoring/health` (`src/app/api/monitoring/health/route.ts`) without updating the CLI; `src/app/api/health/` on disk only has `degradation/route.ts` and `ping/route.ts`, no top-level handler. `runHealthCommand()`/`runHealthComponentsCommand()` now call `/api/monitoring/health` and read its actual payload shape (`activeConnections`, `circuitBreakers: {open, halfOpen, closed}`, `memoryUsage`) instead of the old, nonexistent `requests`/`breakers`/`cache`/`memory` fields. Regression guard: `tests/unit/cli-health-monitoring-route.test.ts`. +- **fix(startup):** webpack build broke on case-insensitive filesystems (macOS APFS default, Windows) with a casing-collision warning plus "not exported" errors in `StudioConfigPane.tsx`/`ChatTab.tsx` (#6584) — `src/app/(dashboard)/dashboard/playground/components/ReasoningControls.tsx` (the component) and `reasoningControls.ts` (the utils module) shared the same lower-cased stem in the same directory, and two importers used the extensionless form `from "./reasoningControls"`, the exact resolution path that becomes ambiguous once casing is folded. Renamed the utils module to `reasoningControlUtils.ts` (no collision) and updated the 3 import sites. Regression guard: `tests/unit/case-collision-6584.test.ts` (scans `src/`/`open-sse/` for any same-directory, case-only filename collision). (#6584) +- **fix(build):** Turbopack production build emitted an "Overly broad patterns can lead to build performance issues" warning per entry point importing `src/lib/agentSkills/generator.ts` (603 warnings reported on v3.8.46, up from 379 on v3.8.45) ([#6582](https://github.com/diegosouzapw/OmniRoute/issues/6582)) — `generator.ts`'s `outputBase` is built as `path.isAbsolute(outputDir) ? outputDir : path.join(process.cwd(), outputDir)`, where `outputDir` is a runtime function parameter, not a compile-time literal, so Turbopack's build-time file-tracing analyzer can't statically narrow the several dynamic `readdirSync`/`rmSync`/`readFileSync`/`writeFileSync` call sites a few lines below and falls back to a project-wide glob; #6366's commit message claimed to "anchor the base path with a literal" but the shipped code never did. Since this fs access is legitimate and bounded (`skills//SKILL.md`, ~48 known IDs), `next.config.mjs`'s `turbopack.ignoreIssue` (Next.js 16.2+) now suppresses this specific, known-benign diagnostic, mirroring the existing `webpack.ignoreWarnings`/`isNextIntlExtractorDynamicImportWarning` precedent already in the same file for the webpack path. Regression guard: `tests/unit/next-config.test.ts` (asserts the `turbopack.ignoreIssue` rule shape targeting `src/lib/agentSkills/**`). +- **fix(providers):** Codex Desktop requests to `gpt-5.3-codex-spark` failed with `[400]: Tool 'image_generation' is not supported with gpt-5.3-codex-spark`, even on paid-plan accounts ([#6651](https://github.com/diegosouzapw/OmniRoute/issues/6651)) — `CodexExecutor.transformRequest` (`open-sse/executors/codex.ts`) only dropped the Codex Desktop-injected `image_generation` hosted tool when `isCodexFreePlan()` matched the account's plan, with no awareness that Spark-scope Codex models reject `image_generation` upstream regardless of plan. `dropImageGeneration` now also drops it when `getCodexModelScope(model) === "spark"` (the existing Spark classifier from `open-sse/config/codexQuotaScopes.ts`), independent of account plan. Regression guard: `tests/unit/codex-spark-image-generation.test.ts` (thanks @alltomatos for independently catching and fixing it via #6819). +- **fix(providers):** the provider quota card's weekly/session bars re-sorted by remaining percentage instead of staying in a fixed, deterministic order ([#6687](https://github.com/diegosouzapw/OmniRoute/issues/6687)) — `QuotaCardExpanded.tsx`'s `sortQuotasByRemaining()` (added in #5977) was applied unconditionally via `useMemo(() => sortQuotasByRemaining(quotas), [quotas])`, undoing the deterministic `CODEX_QUOTA_ORDER`/`GLM_QUOTA_ORDER` window order `quotaParsing.ts`'s `sortCodexOrder()`/`sortGlmOrder()` (added in #6336) already established for Codex and the GLM family — since #6336 never touched `QuotaCardExpanded.tsx`, the two orderings never composed, so e.g. a Codex `session` window with less headroom than `weekly` rendered after it instead of staying first. A new `hasFixedQuotaOrder()` (`quotaParsing.ts`) and `resolveQuotaDisplayOrder()` (`QuotaCardExpanded.tsx`) now skip the remaining-% re-sort for providers with a fixed window order, threading `providerId` from `QuotaCard.tsx` through to the display layer; every other provider still gets the remaining-% sort. Regression guard: `tests/unit/quota-card-expanded-fixed-order-6687.test.ts`. +- **fix(i18n):** pt-BR was missing 194 UI keys present in `en.json` — a real, silent data-sync gap, not covered by any duplicate/mislabeled #6694 (that issue's 9 `providers.*` keys are disjoint, present-but-untranslated sentinels caused by a separate `providerText()` fallback bug) ([#6695](https://github.com/diegosouzapw/OmniRoute/issues/6695)) — `scripts/i18n/sync-ui-keys.mjs` (which mirrors newly-added `en.json` keys into every locale) wasn't re-run after recent `en.json` additions, and the CI `i18n:check-ui-coverage` gate only fails a locale below an 80% threshold, so pt-BR stayed green at 93.8% coverage despite the gap. Backfilled all 194 missing keys into `src/i18n/messages/pt-BR.json` (translated to Brazilian Portuguese, no leftover `__MISSING__` markers) via `npm run i18n:sync-ui -- --locale=pt-BR` + manual translation. Regression guard: `tests/unit/i18n-pt-br.test.ts` (new case asserting full `en.json`→`pt-BR.json` key parity, so a future drift fails a fast unit test instead of silently degrading the coverage percentage). +- **fix(startup):** `omniroute --mcp` crashed at Node ESM link time with `ERR_MODULE_NOT_FOUND` for `ioredis` on installs where the published MCP bundle didn't happen to have `ioredis` rescued from a parent `node_modules` ([#6559](https://github.com/diegosouzapw/OmniRoute/issues/6559)) — `src/shared/utils/rateLimiter.ts` had a top-level static `import Redis from "ioredis"`; that module is only ever reached via a lazy `await import(...)` several call-sites deep in the MCP tool chain, but esbuild's `--packages=external` bundling of the MCP server (`scripts/build/prepublish.ts` Step 8.5) still hoisted rateLimiter.ts's own static import into a real top-level ESM import in the compiled `dist/open-sse/mcp-server/server.js`, forcing Node to resolve `ioredis` at module-link time — before any `--mcp` startup code runs — and `ioredis` is not guaranteed to ship in the MCP-only bundle's `node_modules`. `getRedisClient()` now lazily imports `ioredis` on first use (matching the established soft-dependency pattern in `src/lib/quota/redisQuotaStore.ts`) while still throwing synchronously when Redis isn't configured. Regression guard: `tests/unit/build/mcp-bundle-no-eager-ioredis.test.ts` (bundles the real MCP server entrypoint with the exact publish-time esbuild flags and asserts no top-level static `ioredis` import remains, while the pre-existing lazy `await import("ioredis")` in `redisQuotaStore.ts` stays intact). +- **fix(providers):** Kiro sent the adaptive-thinking `additionalModelRequestFields` envelope for `claude-sonnet-4.5`/`claude-haiku-4.5`, which Kiro/CodeWhisperer rejects upstream with a raw `[400]: additionalModelRequestFields is not supported for this model` ([#6576](https://github.com/diegosouzapw/OmniRoute/issues/6576)) — `buildKiroPayload()` (`open-sse/translator/request/openai-to-kiro.ts`) gated the field on the generic Anthropic-API `supportsReasoning()` capability flag, which is `true` for both models on Anthropic's direct API but does not reflect what Kiro's CodeWhisperer backend actually accepts; only `claude-sonnet-5` is confirmed adaptive-thinking-capable there. A new Kiro-specific allowlist (`supportsKiroAdaptiveThinking()` in `open-sse/translator/request/openai-to-kiro/adaptiveThinking.ts`) now gates the envelope instead. Regression guard: `tests/unit/repro-6576-kiro-thinking-unsupported-model.test.ts`. +- **fix(translator):** Cursor's local **Subagent** tool call is no longer rejected with `cloud_base_branch may only be specified when environment equals cloud` — the Responses→Chat tool-arg cleanup (`stripEmptyOptionalToolArgs`) was scoped to Claude Code's `Read` tool only, so Cursor's `Subagent` tool passed through with the cloud-only `cloud_base_branch: ""` (Cursor treats an empty string as "specified" and rejects the call before starting the local subagent). The cleanup now covers an allowlist of `Read` + `Subagent`; arbitrary tools are still left untouched (empty strings/arrays can be valid payloads for them). Regression guard: `tests/unit/openai-responses-subagent-strip-2446.test.ts`. (thanks @like3213934360-lab) +- **fix(translator):** GLM 5.2 (and other OpenAI-compatible upstreams that stream a tool call's `id` and `function.name` in **separate** SSE chunks) no longer produce an empty tool name / `No such tool available:` error through the Claude `/messages` path — the `openai-to-claude` streaming translator emitted `content_block_start` immediately on the id-only chunk with an empty `name`, and the Claude SSE protocol cannot patch a block after it is emitted, so the later name-only chunk was silently dropped. It now **defers** `content_block_start` until the tool name arrives (falling back to starting the block when arguments arrive first), so the emitted `tool_use` always carries the real name. Regression guard: `tests/unit/openai-to-claude-glm-split-tool-name-2077.test.ts`. (thanks @itiwant) +- **fix(resilience):** OmniRoute didn't respect an exhausted Ollama Cloud (or any other apikey-category provider) quota — it retried the account seconds later instead of waiting out the real reset window ([#6638](https://github.com/diegosouzapw/OmniRoute/issues/6638)) — `shouldPreserveQuotaSignalsFor429()`/`checkFallbackError()` (`open-sse/services/accountFallback.ts`) only applied body-text quota classification (daily/monthly/weekly quota-exhausted detection) to OAuth-category providers; apikey-category 429s (Ollama Cloud, OpenAI, etc.) always fell through to the generic short rate-limit cooldown regardless of what the error body said, and `parseRetryFromErrorText()` also had no support for day-granularity reset hints ("Your quota will reset in 3 days.") — only Xh/Ym/Zs combos. An explicit quota-exhausted signal in the body (`looksLikeQuotaExhausted()`) now overrides the apikey-category default via the new `shouldPreserveQuotaSignals()` (`open-sse/services/quotaResetParsing.ts`), and `parseDayGranularityResetMs()` parses whole-day reset countdowns so the real multi-day window is honored instead of a few seconds of backoff. Regression guard: `tests/unit/issue-6638-ollama-quota.test.ts` + 2 aligned `tests/unit/account-fallback-service.test.ts` cases that previously asserted the buggy rate_limit_exceeded/undefined-dailyQuotaExhausted behavior for apikey-provider quota text. +- **fix(resilience):** a combo step "pinned" to one fingerprint account (mimocode/mcode/opencode multi-account providers) never actually resolved to that account, so it couldn't fail over when the pinned account was depleted ([#6696](https://github.com/diegosouzapw/OmniRoute/issues/6696), relates #6612) — the combo builder UI encodes an account pin as a composite connectionId (`${rowId}|fp|${fingerprint}`, `src/lib/combos/builderOptions.ts`), but `expandTargetsByFingerprints()` (`open-sse/services/combo/fingerprintExpansion.ts`) looked that composite string up directly in `connectionById` (keyed by real DB row ids), got `undefined`, and passed the target through unchanged, still carrying the bogus composite id — so downstream credential resolution could never match it either. `expandTargetsByFingerprints()` now splits the `|fp|` composite id back into the real connection row id + the pinned fingerprint (new `splitFingerprintPin()` helper) before any lookup, resolving the target to the real connectionId (with the pinned fingerprint carried on the new `pinnedFingerprint` field) instead of the inert composite string. Regression guard: `tests/unit/combo-fingerprint-pin-6696.test.ts`. +- **fix(api):** Responses passthrough emitted event-only SSE frames (no `data:` line) for every dropped commentary event, breaking the OpenAI Python SDK's `sse.json()` parser ([#6561](https://github.com/diegosouzapw/OmniRoute/issues/6561)), follow-up to #6199/#6232 — the commentary-drop `continue;` branches in `open-sse/utils/stream.ts` skipped the `data:` line for a dropped commentary event but never cleared the already-buffered `event:` line for that same frame, so the next blank line flushed the stale `event:` line alone. Both drop sites now call `clearPendingPassthroughEvent()` before `continue`, discarding the buffered prefix along with the dropped payload; the commentary-drop decision itself was extracted into a new `open-sse/utils/responsesCommentaryDrop.ts` so the fix does not grow the frozen `stream.ts`. Regression guard: `tests/unit/responses-commentary-event-frame-6561.test.ts` (realistic `event:\ndata:\n\n` frames — the existing #6199 test only used bare `data:` lines and never exercised this path). +- **fix(compression):** `/api/compression/preview`'s top-level `originalTokens`/`compressedTokens` diverged from `engineBreakdown[0]`'s counts for the same single-engine run (tiktoken outer counts vs the `JSON.stringify(...).length/4` estimate per engine), worst on small inputs. A new `reconcileSingleEngineTokens()` overwrites the single-engine breakdown entry with the outer, more accurate figures; multi-step pipeline breakdowns are left untouched ([#6488](https://github.com/diegosouzapw/OmniRoute/issues/6488)). Regression guard: `tests/unit/compression/preview-outer-engine-token-reconcile-6488.test.ts`. +- **fix(resilience):** account selection could pick an account already out of quota upstream on every credentialed route except `chat`/`codex` ([#6686](https://github.com/diegosouzapw/OmniRoute/issues/6686)) — `getProviderCredentials()` (`src/sse/services/auth.ts`) only skips a connection when a _local cache_ already flags it exhausted (`isQuotaExhaustedForRequest`/`src/domain/quotaCache.ts`); it never itself calls the registered upstream `QuotaFetcher`. Only `getProviderCredentialsWithQuotaPreflight()` performs that live upstream check, and it was wired into exactly 2 call sites (`src/sse/handlers/chat.ts`, `src/app/api/internal/codex-responses-ws/route.ts`) — every other credentialed route (`rerank`, `images/generations`, `images/edits`, `audio/transcriptions|speech|translations`, `videos/generations`, `music/generations`, `ocr`, `providers/[provider]/embeddings`, `providers/[provider]/images/generations`, `web/fetch`, `moderations`, `search`) called the plain, cache-only selector, so an account whose cache entry was never populated (e.g. its first request landed on one of these routes) could be selected even at 0% quota remaining. Those 14 call sites now go through `getProviderCredentialsWithQuotaPreflight()` instead, matching chat/codex coverage. Regression guard: `tests/unit/issue-6686-quota-preflight-coverage.test.ts` (static check that none of the routes call the plain selector anymore + a behavioral check that the preflight-aware selector blocks a 100%-used account). +- **fix(api):** `reasoning_content` (extended-thinking text) was silently dropped from `/v1/chat/completions` SSE on the `claude-web` and `v0-vercel-web` executors ([#6662](https://github.com/diegosouzapw/OmniRoute/issues/6662)) — every chunk builder in both adapters hardcoded `delta: { content: ... }` with no reasoning path, unlike the established pattern already used by `default.ts`/`deepseek-web.ts`/`bedrock.ts` and the real-Anthropic-API `claude-to-openai.ts` translator (`thinking_delta` → `reasoning_content`). `v0-vercel-web.ts` now forwards an upstream `delta.reasoning_content` field (streaming and non-streaming) the same way `deepseek-web.ts` does. `claude-web.ts`'s `buildClaudeStreamingResponse` now maps a `content_block_start`(`type: "thinking"`)/`content_block_delta`(`delta.thinking`) pair onto `delta.reasoning_content`, and `claude-web/payload.ts`'s `transformToClaude()` no longer hardcodes `thinking_mode: "off"` — a new `wantsExtendedThinking()` derives it from the request's `reasoning_effort`/`reasoning.effort`/`thinking.type` signal, so extended thinking can actually be requested. Regression guard: `tests/unit/issue-6662-repro.test.ts` (RED→GREEN for both adapters). +- **fix(api):** the compression config PUT schema now accepts `enableRenderers` for the RTK engine instead of rejecting the documented option (#6703, #6757 — thanks @alltomatos, with an independent duplicate fix from @chirag127 via #6756). +- **fix(api):** raised the provider `apiKey` length cap for cookie-based web providers, whose session-cookie credentials legitimately exceed the previous limit (#6715, #6759 — thanks @alltomatos). +- fix(ci): publish electron-updater `latest*.yml` manifests in electron release assets so auto-update can find them ([#6766](https://github.com/diegosouzapw/OmniRoute/issues/6766)) +- **fix(i18n): translate hardcoded Portuguese dashboard strings to English (#6761, #6768)** (#6769 — thanks @chirag127). +- fix(providers): strip redundant node prefix when resolving custom OpenAI/Anthropic-compatible connections by raw connection id, preventing double-namespaced model ids from 400ing upstream (#6772) +- fix(providers): scope nvidia NIM 404s to the single failing model instead of cooling down the whole connection (#6773) +- **fix(codex):** bump the default Codex CLI client identity from `0.142.0` to `0.144.0` for compatibility with newer Codex-backed models ([#6780](https://github.com/diegosouzapw/OmniRoute/pull/6780)) — thanks @quanturbo +- **fix(ci):** the blocking "Impacted unit tests (TIA)" step false-redded any PR whose impact graph reached a dashboard component — it ran every selected test under `--import tsx/esm`, but `tests/unit/dashboard/**` requires the `--import tsx` CJS transform (ESM-only deep imports like `@lobehub/icons/es/*`), exactly as the canonical `test:unit:ci:shard` already does per segment. The impacted selection is now split by segment with matching loaders (closes #6787). +- **fix(translator):** read PDF/video `file_data` attachments on the OpenAI→Gemini/Antigravity and OpenAI→Claude paths so multimodal documents (not just images) reach the upstream — PDFs map to `document`/`inlineData` and videos keep their `video/mp4` mime instead of being dropped (#6790 — thanks @Witroch4, with an independent report/fix from @samimozcan via #6762/#6753). +- **fix(providers): ensure DeepSeek Web SSE emits [DONE] after FINISHED** (#6791 — thanks @Pitchfork-and-Torch). +- **fix(api):** the compression config `PUT` schema (`stackedPipelineStepSchema`) now accepts every `ENGINE_CATALOG` id — the structural engines `session-dedup`/`ccr`/`headroom`/`relevance`/`llmlingua`/`omniglyph` and the `aggressive` `ultra` intensity — so a `GET`→`PUT` round-trip of a stacked pipeline no longer 400s on a valid engine the discriminated union had omitted (#6747 — thanks @Pitchfork-and-Torch). +- **fix(cursor):** send the Agent CLI build id as `x-cursor-client-version` so Cursor upstream accepts requests from the current CLI build instead of a stale hardcoded version (#6795 — thanks @andrewmunsell). +- fix(cli): waitForServer() no longer reports ready from a raw TCP accept alone — requires a fast HTTP rejection or a real health response, so the "OmniRoute is running!" banner no longer fires 30-60s before the server can actually answer requests (#6800) +- fix(sse): de-flake timing-sensitive combo cooldown/breaker tests + add explicit MCP audit shutdown timeout (#6803) +- **fix(codex): strip include from compact responses requests** (#6805 — thanks @yinaoxiong). +- fix(dashboard): surface Claude extraUsage credits in quota card when quotas is empty (#6806) +- **Request count by provider & date**: Dashboard → Analytics now shows a dedicated table of request counts grouped by provider and calendar date (plus token totals), for providers that bill per-request rather than per-token — sortable columns and a single-date filter. New `getProviderDailyUsageRows()` query (`src/lib/db/usageAnalytics.ts`) and its own `GET /api/usage/requests-by-provider-date` route (kept separate from the frozen `/api/usage/analytics` route). Regression guard: `tests/unit/db-provider-daily-usage-4009.test.ts`. (#4009 — thanks @tjengbudi) +- **fix(resilience):** an Ollama Cloud (or any apikey-category provider) account that hit a weekly usage cap kept getting retried every few minutes instead of backing off ([#3709](https://github.com/diegosouzapw/OmniRoute/issues/3709)) — the upstream 429 body ("you (\) have reached your weekly usage limit") was invisible to `checkFallbackError`'s existing subscription-quota-text classifier (Issue #2321) because that branch is gated by `shouldUseQuotaSignal`, which is oauth-only, so apikey providers like `ollama-cloud` fell through to the generic exponential backoff (~1s, capped at 2min) — one account took 285x429 in 48h. A new `isWeeklyUsageLimitText`/`buildWeeklyQuotaFallback` classifier (extracted, with the existing subscription-quota logic, into a new `open-sse/services/quotaTextCooldowns.ts` module so the frozen `accountFallback.ts` didn't have to grow) runs unconditionally and applies a 24h `QUOTA_EXHAUSTED` cooldown regardless of provider category. Regression guard: `tests/unit/ollama-cloud-weekly-quota-cooldown-3709.test.ts`. +- **fix(providers):** an explicit `thinking.budget_tokens: 0` is now honored in the OpenAI→Gemini transform (thinking disabled) instead of being treated as unset (#6813, #6821 — thanks @alltomatos). +- **fix(bootstrap):** filter empty `process.env` values before spawning embedded services so a blank env var no longer crashes the Docker bootstrap in a restart loop (#6828 — thanks @AndrianBalanescu). +- **fix(providers):** classify upstream `404` responses as `MODEL_NOT_FOUND` (model lockout) instead of a retryable provider error, stopping the retry storm when a single model is missing (#6829 — thanks @AndrianBalanescu). +- fix(mcp): de-duplicate `TOTAL_MCP_TOOL_COUNT` by tool name instead of double-counting collections (#6854) +- **fix(usage):** xAI's exact provider-reported `cost_in_usd_ticks` no longer silently accepts `null`/`""`/negative values as a valid `$0` exact cost — `extractUsageFromResponse()` (`open-sse/handlers/usageExtractor.ts`) and `normalizeUsage()` (`open-sse/utils/usageTracking.ts`) now require `typeof value === "number" && Number.isFinite(value) && value >= 0` instead of coercing with `Number(x)`, so a malformed exact cost correctly falls back to the token-based estimate instead of masking it with a bogus `$0`. Regression guard: `tests/unit/xai-exact-cost-2453.test.ts` (rejects null/empty/negative exact costs on both call sites). ([#6856](https://github.com/diegosouzapw/OmniRoute/pull/6856) — thanks @KooshaPari) +- **fix(plugin):** the `@omniroute/opencode-plugin` dynamic provider hook stopped embedding its OC-1.17.8+-gate-compatible `opencode-`-prefixed provider id into model routing fields (`ModelV2.id`/`providerID`, combo catalog keys) — OmniRoute's server has no `opencode-` provider alias, so every dispatched model failed credential lookup with "No credentials for opencode-omniroute" (#6859). +- fix(sse): apply cliproxyapiModelMapping at CLIProxyAPI dispatch time (#6876) +- fix(sse): defer `response.completed` until a trailing usage-only chunk arrives on `/v1/responses` streams (#6906) +- **fix(cli):** ship `head-response-guard.cjs` into the standalone bundle — `server-ws.mjs` imported it without a matching `EXTRA_MODULE_ENTRIES` entry, so every `build:release` dist crashed at boot with `ERR_MODULE_NOT_FOUND`; a new regression test derives the required sidecars from `server-ws.mjs` imports (#6908) +- fix(sse): wire the shared quota-fetch throttle into DeepSeek, Bailian, OpenCode, and Crof quota fetchers, not just Codex (#6911) +- fix(sse): rename client-sent `max_completion_tokens` to `max_tokens` for providers/models that only accept the legacy field (e.g. Volcengine Ark / DeepSeek), mirroring the existing reverse rename from #1961 (#6912) +- fix(sse): set includeServerSideToolInvocations on Antigravity tool cloak decoys (#6914) +- fix(sse): classify LAN embeddings providers (10/8, 192.168/16, CGNAT) as no-auth instead of forcing bearer auth (#6925) +- fix(sse): Qwen Web executor no longer sends `[object Object]` when a message uses structured (array) content — the text parts are now flattened (#6927) +- perf(api): relay chat-completions routes now thread the already-fetched `RelayToken` into `checkRateLimit`, skipping a redundant `SELECT * FROM relay_tokens WHERE id = ?` re-query on every request (#6930) +- fix(sse): `normalizeCodexMessageContentPart` now rewrites explicit `type: "input_text"` (not just `type: "text"`) to `output_text` on assistant-role Codex Responses input parts, so replayed assistant history sent by codex-cli as `input_text` is no longer rejected by the Codex/OpenAI backend (#6932) +- fix(sse): omit removed `attachments` field from Muse Spark Web (Meta AI) persisted GraphQL query to fix `Unknown type "AttachmentInput"` 502s (#6935) +- fix(dashboard): label audio/embeddings/image compatible providers by kind instead of "Chat" on ProviderCard (#6936) +- fix(api): model-list discovery for LAN-local OpenAI-compatible providers (e.g. LM Studio) now uses the same local-first SSRF guard as the connection test, instead of the stricter guard that always blocked LAN hosts (#6939) +- fix(providers): `openai->gemini` transform now maps `reasoning_effort: "none"` to `thinkingConfig.thinkingBudget: 0` (with `includeThoughts: false`), giving callers an explicit, documented off-switch for Gemini thinking; the no-knob-at-all default injection (#4170) is unchanged (#6813, thanks @rafaumeu) +- **fix(sse):** escape backslash before brackets in ChatGPT-web citation link text, preventing a citation label containing `\` from corrupting the generated Markdown link ([#6944](https://github.com/diegosouzapw/OmniRoute/pull/6944)) — thanks @brick30llc-ctrl +- fix(oauth): tokenHealthCheck now lowercase-normalizes `conn.provider` before checking `ROTATING_REFRESH_PROVIDERS.has()` and the GitHub Copilot sub-token refresh guard, so mixed-case provider values (e.g. "OpenAI", "Github") no longer bypass the rotating-refresh-token skip or the Copilot sub-token refresh (#6947) +- fix(sse): make Codex Responses tool-arg normalization schema-aware — drop values equal to the tool's declared JSON Schema `default`, generalize empty-optional stripping to any tool (not just `Read`/`Subagent`) via `schema.required`, and thread each tool's schema from the request's `tools[]` into the streaming response translator (#6951) +- fix(sse): drop internal commentary-phase Responses output in TRANSLATE-mode streams, not just PASSTHROUGH — codex/Responses-upstream routes translated into another client format (e.g. Claude Code) no longer leak duplicate prose and narrated tool-call arguments into the client text channel (#6952) +- **fix(combos):** embeddings-only and rerank-only models (e.g. JinaAI, Gemini auto-imported, OpenRouter custom, reranker models) no longer disappear from the combo builder's model picker — the leftover chat-only `isChatCapable` gate in `addModelOption()` has been removed (#6975). +- **fix(combos):** when 2+ distinct model ids from the same provider would render an identical display name in the combo builder picker (e.g. Mistral's `codestral-latest`/`codestral-2508` aliases sharing one upstream catalog name), each colliding entry now falls back to its own id as the display label so every row stays visually distinguishable and findable (#6957). + +### 📝 維護 + +- **chore(release-captain):** v3.8.47 pre-flight closed every deterministic release-tip base-red ([#6967](https://github.com/diegosouzapw/OmniRoute/issues/6967)): restored the `no-explicit-any` severity silently downgraded by #6786 (439 bulk suppressions had stopped matching), made the openadapter live-catalog test deterministic (`test.after()` was tearing the DB down mid-request), aligned the emergency-fallback and Cloud Code Gemini tests with the #6912/#6943 contracts, backfilled the #6909 i18n keys (en + pt-BR), re-exported `relayProbeStats` (db-rules), documented `OMNI_MAX_CONCURRENT_CONNECTIONS`, and allowlisted migration gap 121. (thanks @diegosouzapw) +- **chore(security):** unbiased crypto digits for the doubao synthetic device id (CodeQL `js/biased-cryptographic-random`); 405 method-first for `/api/keys/{id}/devices` (dast-smoke); Zod-validation for `POST /api/github-skills`; the missing `omni-github-skills` registry entry + catalog count alignment. (thanks @diegosouzapw) +- **chore(quality):** cycle-close ratchet work — cleared the cycle's 11 net-new ESLint errors and made `validate-release-green` suppressions-aware; cleared the 2 remaining heavy-gate reds on the release tip; cycle rebaselines (cognitive/file-size/zizmor/coverage pcts) with justification keys. (thanks @diegosouzapw) + +- **chore(open-sse):** removed the vestigial `// @ts-nocheck` directive from `open-sse/utils/usageTracking.ts` ([#6173](https://github.com/diegosouzapw/OmniRoute/pull/6173)) — `tsc` under the standard `typecheck:core` gate reports 0 errors for this 595-line hot-path file (executed for every provider response), so the suppression was no longer needed; removing it restores type-checking on the token-usage extraction/normalization path. (thanks @KooshaPari) +- **chore(cli):** the shell-completion cache paths (`readCache`/`refreshCache`/`writeCache`) in `bin/cli/commands/completion.mjs` no longer swallow errors into a bare `catch {}` ([#6257](https://github.com/diegosouzapw/OmniRoute/pull/6257)) — each now binds the error and, when the new `OMNIROUTE_DEBUG_COMPLETION` env var is set, emits a `[omniroute completion]` diagnostic to `stderr`; the caches still fail silently by default so a missing/corrupt cache never breaks tab-completion. (thanks @KooshaPari) - **chore(quality):** `validate-release-green --full-ci` reproduces the full `ci.yml` static gate set locally — the pre-flight now reads `ci.yml` itself and runs every `npm run check:*` from the `lint` / `quality-gate` / `quality-extended` / `docs-sync-strict` / `pr-test-policy` jobs (`--` ratchet flags preserved, `test-masking` against `GITHUB_BASE_REF=main`), skipping only the non-local `pr-evidence`/`codeql-ratchet`. Closes the gap where 11 static base-reds leaked to the v3.8.46 release PR in ~2h of layered CI. Also wired into `nightly-release-green` so a static base-red opens a tracking issue the night it lands. Regression guard: `tests/unit/validate-release-green.test.ts` (+5 `extractCiGates` cases). +- **refactor(usage):** `saveRequestUsage(entry: any)` is now typed with a new `UsageEntry` interface mirroring the `usage_history` columns 1:1 (#3512) — the other stray `any`s in `src/lib/usage/usageHistory.ts` (`getUsageHistory` filter, the `getUsageDb` next-cursor cast, `appendRequestLog`'s legacy `tokens` param, `getRecentLogs`'s catch) were cleaned in the same pass, so the file now sits in the `check:any-budget:t11` zero-`any` allowlist. The DB-entity ↔ TS-interface convention is documented in `docs/architecture/CODEBASE_DOCUMENTATION.md` §11. +- **Merge-train script** (`scripts/release/merge-train.sh`): batch-validates N queued PRs as ONE merged result on the runner box — merges every queued PR into a throwaway worktree cut from the release tip, runs the fast-gates parity suite once, and prints the `--admin` evidence block per PR (merge-gates §7). Replaces O(N²) per-PR CI re-runs in merge-storms. Regression guard: `tests/unit/merge-train-plan.test.ts`. +- **release:** `list-uncovered-commits.mjs` now unions the CHANGELOG scan window with `changelog.d/` fragment refs (filename `-` prefix + every `#N` in the body), so a commit covered only by a fragment is no longer reported as an uncovered reconciliation gap ([#6857](https://github.com/diegosouzapw/OmniRoute/issues/6857) via [#6878](https://github.com/diegosouzapw/OmniRoute/pull/6878)) +- chore(quality): restore no-explicit-any severity to error (silently downgraded by #6786, broke 439 bulk suppressions), fix 2 unsuppressed anys in repro-6912 test, allowlist migration gap 121, freeze-bump stream.ts/ProxyRegistryManager/tokenHealthCheck (combined-merge drift) +- chore(base): pt-BR/en i18n keys for #6909 relay-repair/free-pool UI + align Cloud Code Gemini defaults test with the #6943 non-thinking-model contract +- chore(base): re-export relayProbeStats from localDb (db-rules gate, #6909 follow-up) and document OMNI_MAX_CONCURRENT_CONNECTIONS in .env.example/ENVIRONMENT.md (env-doc gate, #6590 follow-up) +- **ci:** unit fast-path sharding doubled 2→4 (halves the heaviest job's wall time) (#6781); the 3 heaviest fast-path jobs can route to the self-hosted VPS runner pool behind `USE_VPS_RUNNER` (#6691); `VPS_ALWAYS_ON` keeps the dedicated 24/7 CI host up across releases (teardown becomes a no-op) (#6693). +- **docs:** routing-strategy count reconciled to 18 across AUTO-COMBO.md, README and AGENTS.md, and `p2c` casing fixed to match `ROUTING_STRATEGY_VALUES` (#6643, #6644, #6646 — thanks @chirag127); CLAUDE.md updated with the renamed review/triage/implement skill-family names (#6663). +- chore(release): v3.8.47 pre-flight — relocate #6943 orphan test to a collected path, restore no-explicit-any suppression match, testFrozen bump translator-openai-to-gemini (1541→1553), zizmor rebaseline 159→169 +- **docs(readme):** fix stale counts — 18 routing strategies (adds the missing `pipeline` row), 94 MCP tools, 12-factor Auto-Combo scoring. +- **chore(ci):** fix two shared base-reds on the release tip that blocked the PR queue — register `cliproxyapi-model-mapping-dispatch.test.ts` in `stryker.conf.json` `tap.testFiles` (mutation-coverage gap left by #6903) and update `provider-models-route-codex.test.ts` to expect Codex client version `0.144.0` (stale assertion left by #6780's production bump). +- **docs:** refresh `llm.txt` to the current project state (248 providers, 94 MCP tools / 30 scopes, 18 routing strategies, 12-factor Auto-Combo scoring, v3.8.47) and sync its 42 i18n mirrors; move the implemented design-system plan from the repo root to `docs/architecture/DESIGN_SYSTEM.md` rewritten as a reference doc. +- **chore(security):** scrub hardcoded live-instance credentials (API key + auth cookie + host URL) from the `tests/boundary/*.live.test.ts` files landed via #6786 — they now read `OMNIROUTE_TEST_BASE` / `OMNIROUTE_TEST_BEARER` / `OMNIROUTE_TEST_COOKIE` from the environment and stay gated behind `RUN_BOUNDARY_LIVE=1`. + +### 🙌 貢獻者 + +Thanks to everyone whose work landed in v3.8.47: + +| Contributor | PRs / Issues | +| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [@AgentKiller45](https://github.com/AgentKiller45) | #6863, #6866 | +| [@alltomatos](https://github.com/alltomatos) | #6703, #6715, #6756, #6757, #6759, #6813, #6819, #6821 | +| [@andrewmunsell](https://github.com/andrewmunsell) | #6774, #6779, #6795 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #6828, #6829 | +| [@anhdiepmmk](https://github.com/anhdiepmmk) | direct commit / report | +| [@artickc](https://github.com/artickc) | #6363, #6763 | +| [@backryun](https://github.com/backryun) | #6280, #6675, #6862 | +| [@blackwell-systems](https://github.com/blackwell-systems) | #6838 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #6944 | +| [@charleszolot](https://github.com/charleszolot) | #6571 | +| [@chirag127](https://github.com/chirag127) | #6402, #6458, #6460, #6461, #6463, #6515, #6519, #6523, #6534, #6546, #6577, #6643, #6644, #6645, #6646, #6769, #6804, #6868, #6869, #6870, #6871, #6883 | +| [@chy1211](https://github.com/chy1211) | direct commit / report | +| [@crochabe-cyber](https://github.com/crochabe-cyber) | #4013 | +| [@deadcoder0904](https://github.com/deadcoder0904) | #6665 | +| [@developerjillur](https://github.com/developerjillur) | direct commit / report | +| [@eidoog](https://github.com/eidoog) | direct commit / report | +| [@enjoyer-hub](https://github.com/enjoyer-hub) | #6647 | +| [@gdevenyi](https://github.com/gdevenyi) | direct commit / report | +| [@growab](https://github.com/growab) | #6867 | +| [@hajilok](https://github.com/hajilok) | #6126, #6833 | +| [@hamsa0x7](https://github.com/hamsa0x7) | #6317, #6318, #6338 | +| [@herjarsa](https://github.com/herjarsa) | direct commit / report | +| [@iamraydoan](https://github.com/iamraydoan) | #6798 | +| [@ianriizky](https://github.com/ianriizky) | #6072, #6538 | +| [@itiwant](https://github.com/itiwant) | direct commit / report | +| [@janeza2](https://github.com/janeza2) | #6308 | +| [@JxnLexn](https://github.com/JxnLexn) | #6776 | +| [@KooshaPari](https://github.com/KooshaPari) | #6611, #6632, #6856 | +| [@like3213934360-lab](https://github.com/like3213934360-lab) | direct commit / report | +| [@lucasjustinudin](https://github.com/lucasjustinudin) | direct commit / report | +| [@lunkerchen](https://github.com/lunkerchen) | #6320 | +| [@MikeTuev](https://github.com/MikeTuev) | #6586, #6830 | +| [@Moseyuh333](https://github.com/Moseyuh333) | #6294, #6728 | +| [@nowhats-br](https://github.com/nowhats-br) | direct commit / report | +| [@oyi77](https://github.com/oyi77) | #6907, #6926, #6929, #6946 | +| [@Pitchfork-and-Torch](https://github.com/Pitchfork-and-Torch) | #6747, #6791, #6792 | +| [@pizzav-xyz](https://github.com/pizzav-xyz) | #6648 | +| [@quanturbo](https://github.com/quanturbo) | #6780 | +| [@rafaumeu](https://github.com/rafaumeu) | #6813 | +| [@rafpigna](https://github.com/rafpigna) | #6574 | +| [@rucciva](https://github.com/rucciva) | #4125 | +| [@rushsinging](https://github.com/rushsinging) | #6807 | +| [@ryanngit](https://github.com/ryanngit) | direct commit / report | +| [@samir-abis](https://github.com/samir-abis) | direct commit / report | +| [@SeaXen](https://github.com/SeaXen) | direct commit / report | +| [@shabeer](https://github.com/shabeer) | direct commit / report | +| [@Squawk7777](https://github.com/Squawk7777) | #6565 | +| [@strangersp](https://github.com/strangersp) | #6587 | +| [@Thinkscape](https://github.com/Thinkscape) | direct commit / report | +| [@ThongAccount](https://github.com/ThongAccount) | #6625, #6649 | +| [@tjengbudi](https://github.com/tjengbudi) | #4009 | +| [@whale9820](https://github.com/whale9820) | direct commit / report | +| [@Witroch4](https://github.com/Witroch4) | #6753, #6762, #6790 | +| [@xz-dev](https://github.com/xz-dev) | #6323, #6330, #6702, #6714, #6727 | +| [@yinaoxiong](https://github.com/yinaoxiong) | #6805 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | --- ## [3.8.46] — 2026-07-07 -### ✨ New Features +### ✨ 新功能 - **feat(sse):** **hide paid-only models from `auto/*` routing** when `hidePaidModels` is on ([#6512](https://github.com/diegosouzapw/OmniRoute/issues/6512)) — follow-up to #6328/#6495. PR #6495 hid paid-only models from the `GET /v1/models` listing, but `auto/*` combos (`auto/best-coding`, `auto/glm`, …) could still pick a paid-only backend into their candidate pool → a 402/403 at request time. `createVirtualAutoCombo` now filters the candidate pool through the new pure `open-sse/services/autoCombo/paidModelFilter.ts` (`filterPaidOnlyCandidates`), applying the same free-model predicate #6495 uses in `catalog.ts` (`providerHasFreeModels(provider) && isFreeModel(provider, {id})`) whenever `settings.hidePaidModels === true`. Applied before the category/tier/family narrowing, so it covers every `auto/*` combo; an all-paid pool degrades to the existing graceful empty-pool path. **Opt-in — default OFF leaves the pool unchanged** (identity). Regression guard: `tests/unit/autoCombo/paid-model-filter-6512.test.ts` (4, incl. the default-off identity guard). - **feat(sse):** **provider-family auto combos** — `auto/glm`, `auto/minimax`, `auto/mimo`, `auto/zai`, `auto/gemma`, `auto/llama`, `auto/gemini` ([#6453](https://github.com/diegosouzapw/OmniRoute/issues/6453)) — new routable ids that materialize an on-demand virtual combo spanning whatever installed backends currently expose that model family, degrading gracefully as backends rotate. A new pure `open-sse/services/autoCombo/modelFamily.ts` (`detectModelFamily`) classifies by model-id prefix for six families; `zai` is instead resolved by provider id (z.ai's hosted API serves the same `glm-*` model ids as every other GLM backend, so `auto/zai` means "route to my z.ai backend specifically" vs `auto/glm`'s "any connected GLM backend"). Reuses the existing `createVirtualAutoCombo` on-demand materialization path (no DB writes) and the `/v1/models` catalog advertising loop. Regression guard: `tests/unit/autoCombo/provider-family-combos.test.ts` (11). @@ -60,7 +692,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral - **feat(providers):** add **Requesty** as an OpenAI-compatible gateway provider (BYOK, base `https://router.requesty.ai/v1`, ~200 free requests/day) — wired through the shared OpenAI-compatible registry with full model passthrough (`open-sse/config/providers/registry/requesty/`, `src/shared/constants/providers/apikey/gateways.ts`). ([#6120](https://github.com/diegosouzapw/OmniRoute/issues/6120)) Regression guard: `tests/unit/requesty-provider.test.ts`. (thanks @chirag127) - **feat(dashboard):** add **configured-only / available-only filters** to the Free Provider Rankings page ([#6150](https://github.com/diegosouzapw/OmniRoute/issues/6150)) — hide providers you haven't configured, or whose connections are all rate-limited / out of quota, via server-side query params (`?configuredOnly` / `?availableOnly` on `GET /api/free-provider-rankings`) backed by a testable lib helper reusing the in-process connection state (no Redis). Both filters default off, so the default view is unchanged; this supersedes the earlier client-side "Configured Only" toggle (#6245) with an available-only dimension and unit-tested logic. Regression guard: `tests/unit/freeProviderRankings-filters.test.ts`. -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **fix(dashboard):** adding a second API key connection for the same provider no longer silently overwrites the first — the Add-API-key modal now derives a unique default connection name (`main`, then `main-2`, `main-3`, …) so the backend name-based upsert can't collide (#6499 — thanks @dilneiss). - **fix(compression):** the session-dedup engine now also deduplicates a large multi-line block repeated **within a single message** (intra-message dedup), not just across turns; the compression-preview API surfaces a `fallbackReason`, and the fusion panel reports how many models were rate-limited vs failed on total-panel failure (#6501 — thanks @chirag127). @@ -173,20 +805,20 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral - **fix(quality):** clear the cycle's 11 net-new ESLint errors and make `validate-release-green` suppressions-aware. - **fix(security):** proxy-pool `random` rotation now selects via `crypto.randomInt` instead of `Math.random` — silences the post-release CodeQL `js/insecure-randomness` alerts (#698/#699) that flagged `Math.random` flowing into the selected proxy's credentials. Load-balancing selection is not a secret, but the crypto source is unbiased and clears the alert at the origin (#6365 follow-up). -### 📝 Maintenance +### 📝 維護 - **i18n(it):** add 118 missing Italian (`it`) translations (net-additive — no existing keys dropped, valid JSON), improving Italian UI coverage. ([#6212](https://github.com/diegosouzapw/OmniRoute/pull/6212) — thanks @serverless83) - **chore(providers):** remove deprecated **MiMo V2** model entries from the catalogs (xiaomi-mimo, opencode-go, zenmux-free, audio TTS) — the upstream V2 line is superseded by MiMo V2.5; drops `mimo-v2-tts`, `mimo-v2-pro`, `mimo-v2-omni`, `mimo-v2-flash`, `mimo-v2-flash-free` and realigns the provider-catalog tests. ([#6248](https://github.com/diegosouzapw/OmniRoute/pull/6248) — thanks @backryun) - **chore(release):** ~50 commits on this branch are v3.8.45 pre-flight/hardening fixes and CI-perf work that landed here via the parallel-cycle sync-back (`sync-next-cycle.mjs`, Hard Rule #21) **after** the `v3.8.45` git tag was cut, and are already fully documented under the **[3.8.45]** section below — listed here only so the per-cycle commit-coverage check (`npm run release:uncovered`) doesn't flag them as gaps. Provider/catalog/UX/backend fixes: #6041, #6078, #6108, #6135, #6148, #6149, #6154, #6158, #6161, #6162, #6163, #6164, #6165, #6170, #6177, #6178, #6181, #6186, #6187, #6191, #6193, #6194, #6195, #6200, #6205, #6208, #6209, #6211, #6213, #6223, #6224, #6225, #6226, #6227, #6228, #6229, #6230, #6235, #6291, #6292. CI/release-pipeline work: #6167, #6203, #6214, #6215, #6218, #6273, #6275, #6283, #6284, #6285, #6300, #6305. - **chore(release):** additional zero-ref release-cycle plumbing on this branch, kept out of `release:uncovered` on purpose (no `#N` in the commit subject to cite): opening the v3.8.46 cycle, opening/closing the v3.8.45 cycle, the finalized [3.8.45] CHANGELOG i18n sync-back to 42 mirrors, the v3.8.45 cognitive/cyclomatic and file-size drift rebaselines, ESLint stale-suppression pruning (4,273 → 4,233), and clearing test-masking/docs-all pre-flight reds for v3.8.45. -### ⚡ Performance & Infrastructure +### ⚡ 效能與基礎架構 - **perf(release-green):** the pre-flight validator (`scripts/quality/validate-release-green.mjs`) now runs its 4 slow suites (unit / vitest / integration / pack-artifact) **concurrently** via `Promise.all` — pre-flight wall time drops from ~the sum of the suites to ~the slowest one (~30min saved per round; Phase 0 was the nº1 bottleneck of the v3.8.45 release benchmark, 2h54 of 6h34 e2e). Guard: `tests/unit/validate-release-green.test.ts` ("runs the slow suites CONCURRENTLY"). ([#6319](https://github.com/diegosouzapw/OmniRoute/pull/6319)) - **fix(ci):** `scripts/release/sync-next-cycle.mjs` — two defects found live in its first production run (v3.8.45 Phase 5): (1) the `git()` helper's default 1 MiB `maxBuffer` crashed with `ENOBUFS` on `git show origin/main:CHANGELOG.md` (the CHANGELOG alone is >1 MiB) — widened to 64 MiB; (2) the i18n resync only propagated the `[NEXT]` (TBD) section, leaving the just-shipped finalized section as "— TBD" in all 42 mirrors — it now also syncs `[prevVersion]` bounded by the heading below it (new exported pure helper `versionAfter`). Guards: +5 tests in `tests/unit/sync-next-cycle.test.ts` (8/8). ([#6327](https://github.com/diegosouzapw/OmniRoute/pull/6327)) - **test(ci):** concurrency-sensitive flaky tests are **quarantined into a serial pass** (`tests/unit/serial/`, `--test-concurrency=1`, appended to every unit runner incl. sharded variants — the serial pass is sharded too so concurrent shard jobs never self-collide). Initial set: `glm-coding-plan-monthly-3580`, `quota-division-blocks`, `provider-health-autopilot`, `combo-health-autopilot` — the class behind the ~28min CI wedges/re-runs (two live 1h+ wedges cancelled during this PR's own validation). Discovery + TIA gates track the new glob; systemic root cause (async logger writing after teardown) tracked in [#6360](https://github.com/diegosouzapw/OmniRoute/issues/6360). Guard: `tests/unit/test-serial-quarantine.test.ts` (4). ([#6347](https://github.com/diegosouzapw/OmniRoute/pull/6347)) -### 🙌 Contributors +### 🙌 貢獻者 Thanks to everyone whose work landed in v3.8.46: @@ -243,7 +875,7 @@ Thanks to everyone whose work landed in v3.8.46: ## [3.8.45] — 2026-07-06 -### ✨ New Features +### ✨ 新功能 - **feat(providers):** add **Yuanbao (web)** as a cookie-session provider ([#6196](https://github.com/diegosouzapw/OmniRoute/issues/6196)) — `yuanbao-web` (Tencent Yuanbao, `yuanbao.tencent.com`) with cookie-only auth (`hy_user`/`hy_token` + public agent id), SSE→OpenAI translation incl. `reasoning_content`, exposing DeepSeek V3/R1 + Hunyuan / Hunyuan-T1. Regression guard: `tests/unit/providers-yuanbao-web.test.ts`. `together-web` was **deferred** (no verifiable web-session endpoint — needs a captured request) and `huggingchat-web` **dropped** (the existing `huggingchat` already is a web-cookie provider). (thanks @chirag127) - **feat(providers):** route the built-in **agentrouter** through the dynamic Claude-Code wire image ([#6056](https://github.com/diegosouzapw/OmniRoute/issues/6056)) — a small static allow-set (`CC_WIRE_IMAGE_BUILTINS` in `open-sse/services/ccWireImageBuiltins.ts`), consulted by `isClaudeCodeCompatible` / `isClaudeCodeCompatibleProvider` / `applyFingerprint`, makes agentrouter adopt the CC wire-image headers + fingerprint **while guarding the CC baseUrl/auth branches** so it keeps its own registry `baseUrl` and `x-api-key` auth. Regression guard: `tests/unit/agentrouter-cc-wire-image.test.ts` (asserts the wire image is applied AND agentrouter's baseUrl/auth are preserved). Live WAF-acceptance against agentrouter.org is a VPS validation follow-up (Hard Rule #18). @@ -255,7 +887,7 @@ Thanks to everyone whose work landed in v3.8.46: - **feat(dashboard):** add **configured-only / available-only filters** to the Free Provider Rankings page ([#6150](https://github.com/diegosouzapw/OmniRoute/issues/6150)) — hide providers you haven't configured, or whose connections are all rate-limited / out of quota, via server-side query params (`?configuredOnly` / `?availableOnly` on `GET /api/free-provider-rankings`) backed by a testable lib helper reusing the in-process connection state (no Redis). Both filters default off, so the default view is unchanged; this supersedes the earlier client-side "Configured Only" toggle (#6245) with an available-only dimension and unit-tested logic. Regression guard: `tests/unit/freeProviderRankings-filters.test.ts`. - **feat(rankings):** add a **'Configured Only'** filter to the Free Provider Rankings page, so the table can be narrowed to just the providers you have configured connections for (with an empty-state hint when none are configured). New `en.json` keys and a pure filter helper covered by `tests/unit/free-provider-rankings-configured-filter.test.ts`. ([#6245](https://github.com/diegosouzapw/OmniRoute/pull/6245), closes [#6150](https://github.com/diegosouzapw/OmniRoute/issues/6150) — thanks @Iammilansoni) -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** the test suite and CI can never mutate the OS trust store again — `OMNIROUTE_SKIP_SYSTEM_TRUST=1` (set by the global test setup and all CI workflows) makes `installCert`/`uninstallCert`/`installTproxyCa` skip the privileged OS dispatch while preserving the #4546 environment-skip contract. Root cause of the self-hosted runner incident: a cert-flow integration test installed a 105-byte fake PEM into `/usr/local/share/ca-certificates`, breaking ALL system TLS on the VM. Regression guard: `tests/unit/system-trust-test-guard.test.ts`. ([#6310](https://github.com/diegosouzapw/OmniRoute/pull/6310)) - **fix(security):** `/api/keys/{id}/devices` answers a clean method-first **405** for undocumented HTTP methods (e.g. the new `QUERY`) via a dedicated `http-method-guard` rule — the auth layer was answering 401 first, failing schemathesis's unsupported-methods check. Same pattern as the v3.8.44 TRACE fix. Regression guard: `tests/unit/dast-method-not-allowed.test.ts`. @@ -322,7 +954,7 @@ Thanks to everyone whose work landed in v3.8.46: - **fix(services):** 9Router embed panel no longer 404s (optional catch-all route) and the supervisor probes the port before spawning to avoid raw EADDRINUSE ([#6205](https://github.com/diegosouzapw/OmniRoute/issues/6205)). Regression guards: `tests/unit/ninerouter-embed-port-6205.test.ts`, `tests/unit/services/ServiceSupervisor.test.ts`. (thanks @jonlwheat2-gif) - fix(mcp): forward the MCP request `extra` context through static tool loops so stdio callers keep their scope/identity ([#6178](https://github.com/diegosouzapw/OmniRoute/issues/6178)) -### ⚡ Performance & Infrastructure +### ⚡ 效能與基礎架構 - **perf(test):** test-suite loader quick wins ([#6214](https://github.com/diegosouzapw/OmniRoute/pull/6214)) — the 19 test scripts switch `--import tsx` → `--import tsx/esm` (the repo is pure ESM; the unused CJS hook cost ~1.3s per test process × 2,462 processes — CI fast-path unit shards dropped 14.8→7.5 min, −49%), tsx bumped to ^4.23.0 (tsx#809 startup-regression fix), **37 orphan `.test.mjs` files (224 cases) recovered** into the canonical glob (they matched no runner and never ran in any CI job; `check:test-discovery` now scans `.mjs` too), and ci.yml/quality.yml unit jobs now call the canonical npm script `test:unit:ci:shard` (single source of truth — closes two silent drifts: missing `setupPolyfill` import in CI and `memory/`+`usage/` dirs absent from the fast-path glob). `tests/unit/dashboard/**` keeps the full tsx hook in its own invocation (`@lobehub/icons` es/ build internally `require()`s ESM-syntax files). - **ci:** heavy-pipeline dedup ([#6215](https://github.com/diegosouzapw/OmniRoute/pull/6215)) — the release-PR pipeline ran the unit suite 4× per sync (95 jobs, 208 machine-min; the v3.8.44 cycle fired 123 such runs, 88 cancelled). Now: Node 24/26 compat matrices move to a daily `nightly-compat.yml` (−28%/run; resolves the active release branch, opens a tracking issue on failure), coverage is collected inside the unit shards themselves via c8/`NODE_V8_COVERAGE` (−18%/run; the Coverage Shard ×8 matrix is gone — nodejs/node's own CI pattern), the ~40-job per-language i18n matrix becomes 1 job (the account has 20 concurrent-job slots total), and heavy jobs skip **draft** PRs — paired with `/generate-release` now opening the living release PR as draft (flipped ready at the new Phase 0a.0a), killing the per-merge churn for the whole cycle. Validated by a full `workflow_dispatch` of the new pipeline: 35 jobs, 0 failures, 23 min, merged coverage 80.16% (> ratchet baseline). @@ -333,7 +965,7 @@ Thanks to everyone whose work landed in v3.8.46: - **feat(docker):** the Docker image builds with Turbopack (`ENV OMNIROUTE_USE_TURBOPACK=1`) ([#6285](https://github.com/diegosouzapw/OmniRoute/pull/6285)) — the v3.8.27 ImportTracer panic ("unreachable: there must be a path to a root") does **not** reproduce on Next 16.2.9: amd64 (659 s) and arm64 (qemu) build clean, 0 panics, smoke health 200. - **ci:** opt-in **self-hosted VPS runners for the release window** ([#6284](https://github.com/diegosouzapw/OmniRoute/pull/6284)) — `scripts/vps/release-runner-up.sh`/`down.sh` manage the runner VM, and `build`/`test-unit`/`vitest` pick a dynamic `runs-on` gated by `vars.USE_VPS_RUNNER == 'true'` **and** own-origin (fork PRs never reach self-hosted runners). Wired into `/generate-release` (VM up at Phase 1, mandatory down at Phase 3). -### 📝 Maintenance +### 📝 維護 - **quality(release-green):** full pre-flight hardening for this release — the cycle's 11 net-new ESLint errors typed/fixed and `validate-release-green` made suppressions-aware with per-gate logs (`_artifacts/release-green/`) and a `--hermetic` mode; test-masking allowlist entries for the cycle's verified-legitimate assert reductions; stale ESLint suppressions pruned (4,273 → 4,233); the 7 net-new `as any` casts from #6292 typed; `githubSkillTools` MCP errors routed through `sanitizeErrorMessage()`; `combo-provider-cooldown-sibling` added to the Stryker tap set; executors/env docs count fixes. - **ci(quality):** merge-integrity fast-gates per PR — `check:changelog-integrity` (no base CHANGELOG bullet may vanish in the merge result — the auto-resolve "CHANGELOG-eat" pattern) and `check:agent-skills-sync` (generated SKILL.md ≡ catalog), blocking for own-origin branches and report-only for forks (Princípio Zero). ([#6300](https://github.com/diegosouzapw/OmniRoute/pull/6300)) @@ -344,7 +976,7 @@ Thanks to everyone whose work landed in v3.8.46: - **i18n(it):** add 118 missing Italian (`it`) translations (net-additive — no existing keys dropped, valid JSON), improving Italian UI coverage. ([#6212](https://github.com/diegosouzapw/OmniRoute/pull/6212) — thanks @serverless83) - **chore(providers):** remove deprecated **MiMo V2** model entries from the catalogs (xiaomi-mimo, opencode-go, zenmux-free, audio TTS) — the upstream V2 line is superseded by MiMo V2.5; drops `mimo-v2-tts`, `mimo-v2-pro`, `mimo-v2-omni`, `mimo-v2-flash`, `mimo-v2-flash-free` and realigns the provider-catalog tests. ([#6248](https://github.com/diegosouzapw/OmniRoute/pull/6248) — thanks @backryun) -### 🙌 Contributors +### 🙌 貢獻者 Thanks to everyone whose work landed in v3.8.45: @@ -382,9 +1014,9 @@ Thanks to everyone whose work landed in v3.8.45: --- -## [3.8.44] — TBD +## [3.8.44] — 2026-07-04 -### ✨ New Features +### ✨ 新功能 - **feat(resilience):** throttle upstream quota fetches on the per-request preflight path ([#6009](https://github.com/diegosouzapw/OmniRoute/issues/6009)) — a new global min-interval gate (`open-sse/services/quotaFetchThrottle.ts`) spaces the actual network calls made by the Codex quota fetcher so that many accounts on one IP no longer fetch quota in the same second (which, per `router-for-me/CLIProxyAPI#2385`, can get a Codex OAuth token revoked). Complements the existing bulk-sync spacing (`PROVIDER_LIMITS_SYNC_SPACING_MS`) which already serialized the periodic provider-limits sync — this covers the concurrent combo/preflight path it didn't. Cache hits are never delayed; fail-open (only ever awaits a timer). Configurable via `OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS` (default 250ms, clamped 0..5000; `0` disables). Regression guard: `tests/unit/quota-fetch-throttle-6009.test.ts` (5). (thanks @powellnorma) - **feat(autoCombo):** add **per-request Auto-Combo controls** via two headers ([#6024](https://github.com/diegosouzapw/OmniRoute/issues/6024) / [#6025](https://github.com/diegosouzapw/OmniRoute/issues/6025) / [#6023](https://github.com/diegosouzapw/OmniRoute/issues/6023)) — `X-OmniRoute-Mode` steers an `auto` combo's scoring for a single request (friendly presets `fast`/`balanced`/`quality`/`cheap`/`reliable`/`offline` **or** a raw mode-pack name; `balanced` forces the default weights), and `X-OmniRoute-Budget` sets a hard per-request USD cost ceiling. Both override the combo's stored config only for the request that carries them; unknown/garbage values are ignored so the saved config is preserved. The resolvers are pure (`open-sse/services/autoCombo/requestControls.ts`) and feed the engine's existing `config.modePack` / `config.budgetCap` inputs — no engine changes. Regression guard: `tests/unit/auto-combo-request-controls-6024.test.ts` (5). (thanks @chirag127) @@ -436,7 +1068,7 @@ Thanks to everyone whose work landed in v3.8.45: - **feat(relay):** gate **Bifrost auto-routing** by the provider plugin manifest — only manifest-eligible providers reach the sidecar; ineligible/unknown providers fall back to the existing TS routing path with explicit reasons. Regression guards: 4 provider-plugin-manifest + 11 relay-routing-backend tests. ([#5870](https://github.com/diegosouzapw/OmniRoute/pull/5870) — thanks @KooshaPari) - **feat(providers):** wire **Claude Sonnet 5** end-to-end across the model pipeline — registries, `modelSpecs`, pricing (×3), cost, Sonnet-family fallback, 1M-context, and static models. ([#5833](https://github.com/diegosouzapw/OmniRoute/pull/5833) — thanks @ggiak) -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **dashboard (`/dashboard/system/proxy` 500 on every render):** `ProxyRegistryManager` called `useProxyBatchOperations(load)` before the `const load = useCallback(...)` declaration in the component body, so every server render threw a TDZ `ReferenceError: Cannot access 'load' before initialization` and the whole proxy page 500'd (#5918 regression, caught by the release-PR e2e smoke — the PR→release fast-gates never render pages). The hook block now sits after the `load` declaration. Regression guard: `tests/unit/ui/ProxyRegistryManager-tdz-render.test.tsx` (SSR renderToString — the exact crash mode). @@ -508,7 +1140,7 @@ Thanks to everyone whose work landed in v3.8.45: - **fix(embeddings):** forward the connection-level proxy configuration to embedding requests — `src/lib/embeddings/service.ts` previously ignored a connection's configured proxy when making embedding calls, so proxy-only network setups leaked embedding traffic outside the proxy. Regression guard: `tests/unit/embeddings-proxy-forwarding.test.ts`. ([#5975](https://github.com/diegosouzapw/OmniRoute/pull/5975)) - **fix(resilience):** parse `Retry-After` from a 429's JSON body for cooldown calculation, not just the HTTP header — a new `retryAfterJson.ts` helper extracts a retry-after hint from common JSON error-body shapes and `accountFallback.ts`'s cooldown path now prefers it when the header is absent. Regression guard: `tests/unit/account-fallback-retry-after-json.test.ts`. (Includes #6013's retry-after-json extraction.) ([#5974](https://github.com/diegosouzapw/OmniRoute/pull/5974) — thanks @KooshaPari) -### 📝 Maintenance +### 📝 維護 - **release close (release-PR one-pass CI sweep):** restore Zod validation on the provider-scoped chat route with a `.passthrough()` schema that keeps #5907's relaxed semantics (t06 route-validation gate); point `/api/keys/{id}/devices`' 401 response at the management error envelope in `docs/openapi.yaml` (Schemathesis schema-conformance); rebaseline `i18nUiCoverage.pct` 77.5→76.8 (~1352 new en.json UI keys from the cycle await the async translation workflow — same shape as the v3.8.39 rebaseline); dismiss 2 CodeQL `js/incomplete-url-substring-sanitization` false positives on unit-test asserts (v3.8.35 precedent). @@ -529,7 +1161,7 @@ Thanks to everyone whose work landed in v3.8.45: - **chore (quality/CI housekeeping):** rebaseline residual ESLint/cognitive-complexity/file-size drift accumulated over the v3.8.44 cycle, move orphaned executor tests to a top-level location so a runner actually collects them, harden the release pipeline with a test-masking pre-flight gate plus contributors/uncovered helpers, and make the `pr-evidence` FAIL output tell the author to push (a body edit alone does not re-run the gate). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926), [#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944), [#5952](https://github.com/diegosouzapw/OmniRoute/pull/5952), [#6027](https://github.com/diegosouzapw/OmniRoute/pull/6027), [#5928](https://github.com/diegosouzapw/OmniRoute/pull/5928), plus a #5975-collateral test hardening pinning a seeded connection to direct egress in route-edge-coverage) - **docs (housekeeping):** normalize mixed-language documentation content, restore the OpenAPI coverage ratchet by documenting 9 newly-added routes, record Hard Rule #22 (cross-session safety — `git stash` + in-flight PR bans), and document the compression-engine's upstream sync policy for the RTK/Caveman engines. ([#6105](https://github.com/diegosouzapw/OmniRoute/pull/6105), [#5955](https://github.com/diegosouzapw/OmniRoute/pull/5955), [#5948](https://github.com/diegosouzapw/OmniRoute/pull/5948), plus docs-only commit 926b08aa8) -### 🙌 Contributors +### 🙌 貢獻者 Thanks to everyone whose work landed in v3.8.44: @@ -576,7 +1208,7 @@ Thanks to everyone whose work landed in v3.8.44: ## [3.8.43] — 2026-07-02 -### ✨ New Features +### ✨ 新功能 - **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4). @@ -644,7 +1276,7 @@ Thanks to everyone whose work landed in v3.8.44: - **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw) -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw) @@ -846,7 +1478,9 @@ Thanks to everyone whose work landed in v3.8.44: - **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw) -### 📝 Maintenance +### 📝 維護 + +- **docs(architecture):** sync stale DB-layer counts (45+/55 → 95+/110+) across `REPOSITORY_MAP.md`, the db-schema diagram and `llm.txt` (+42 i18n mirrors). ([#6167](https://github.com/diegosouzapw/OmniRoute/pull/6167) — thanks @diegosouzapw) - **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891)) @@ -864,7 +1498,7 @@ Thanks to everyone whose work landed in v3.8.44: - **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw) -### 🙌 Contributors +### 🙌 貢獻者 Thanks to everyone whose work landed in v3.8.43: @@ -908,7 +1542,7 @@ Thanks to everyone whose work landed in v3.8.43: ## [3.8.42] — 2026-06-30 -### ✨ New Features +### ✨ 新功能 - **compression (pipeline):** add an honest default-on **inflation guard** to the stacked compression pipeline (T02 / Headroom H1). If the fully-stacked engines produce a body that did not actually shrink — its token count is `>=` the original — the compressed body is discarded and the verbatim original request is sent upstream instead, with a `pipeline-inflation-guard` warning recorded in the compression stats. This is safe by construction (the only fallback is the unmodified original, always a valid payload) and complements the existing opt-in per-step TV1 bail-out, which governs step-to-step advancement rather than the final output. New `open-sse/services/compression/pipelineGuards.ts`; wired at the single `finalizeStackedResult` choke point shared by the sync and async stacked paths. Regression guards (incl. an inflating-engine integration test) in `tests/unit/compression-pipeline-inflation-guard.test.ts`. @@ -918,7 +1552,7 @@ Thanks to everyone whose work landed in v3.8.43: - **compression (RTK):** add **Gradle** and **.NET CLI (`dotnet`)** to the RTK tool-output filter catalog. Tool output for `gradle`/`gradlew` and `dotnet build|test|restore|publish` is now recognized (both by command and by output content) and compressed: Gradle daemon/welcome banners and no-op `> Task … UP-TO-DATE/SKIPPED/FROM-CACHE` lines are dropped while `BUILD SUCCESSFUL/FAILED`, "What went wrong", and stack traces are preserved; the .NET build banner, copyright, and `Determining projects to restore`/`Restored …` chatter are dropped while `Build succeeded/FAILED`, `error CS####`/`warning CS####`, and test summaries are preserved. New builtin filters `engines/rtk/filters/{gradle,dotnet}.json` (with inline tests run by the catalog gate) plus `gradle`/`dotnet` entries in the command detector. Regression guard: `tests/unit/rtk-gradle-dotnet-filters.test.ts`. gaps v3.8.42 — T07/R9. -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **providers (chatgpt-web):** fix `502 ChatGPT sentinel failed: Digest method not supported` on the **Electron desktop app**, which made every `chatgpt-web/*` request fail. The sentinel proof-of-work hashed with native `createHash("sha3-512")`, but Electron's Node is built against **BoringSSL, which does not implement the SHA-3 family** (electron/electron#30530), so the digest threw at construction — the provider was unusable on the desktop build (works under plain Node/OpenSSL). The PoW now hashes through a new runtime-portable helper (`open-sse/utils/sha3-512.ts`) that prefers the native digest and transparently falls back to a dependency-free pure-JS Keccak-f[1600] when native SHA-3 is absent. The fallback is validated bit-for-bit against native `createHash("sha3-512")` (300 random inputs) and the published FIPS-202 known-answer vectors. Regression guards in `tests/unit/chatgpt-web-sha3-boringssl-5531.test.ts`. ([#5531](https://github.com/diegosouzapw/OmniRoute/issues/5531)) @@ -972,7 +1606,7 @@ Thanks to everyone whose work landed in v3.8.43: - **providers (longcat):** correct the LongCat free tier — **LongCat-2.0** is now GA; the one-time 10M-token promo (KYC required) is correctly reflected in the catalog, replacing the stale legacy beta entry. ([#5508](https://github.com/diegosouzapw/OmniRoute/pull/5508) — thanks @backryun) -### 📝 Maintenance +### 📝 維護 - **dashboard (refactor):** consolidate the duplicate caveman on/off toggle from the compression settings tab onto the single-source panel (T11), eliminating the stale off-sync copy. ([#5524](https://github.com/diegosouzapw/OmniRoute/pull/5524)) @@ -1000,11 +1634,11 @@ Thanks to everyone whose work landed in v3.8.43: ## [3.8.41] — 2026-06-29 -### ✨ New Features +### ✨ 新功能 - **feat(relay): selectable relay backend (TS / Bifrost / `auto`)** — the OpenAI-compatible relay endpoint can now route its hot path through a native Bifrost sidecar without clients changing URLs. `OMNIROUTE_RELAY_BACKEND` / `RELAY_ROUTING_BACKEND` = `ts | bifrost | auto`: defaults to the existing TypeScript relay; `auto` selects Bifrost when `BIFROST_BASE_URL` is set (and `BIFROST_ENABLED` ≠ `0`) and falls back to TS automatically if the sidecar is unreachable; `bifrost` keeps strict failure behavior. Auth, per-IP/token rate limits, prompt-injection checks, and model allowlists still run in the Next relay route before dispatch (control plane stays in the app); responses carry `X-Routing-Backend` / `X-Routing-Fallback`. Regression guards: `tests/unit/api/v1/relay-routing-backend.test.ts`, `tests/unit/api/v1/bifrost-sidecar.test.ts`. ([#5315](https://github.com/diegosouzapw/OmniRoute/pull/5315), #5316 — thanks @KooshaPari) -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **translator (claude):** synthesize a minimal `user` turn when an OpenAI→Claude request carries **only** `system`/`developer` messages, so the request stops failing with `[400]: messages: at least one message is required`. `openaiToClaudeRequest` hoists every system/developer turn into Claude's top-level `system` field and filters them out of `messages`; an all-system input (OpenCode compaction / title-generation requests) left `messages: []`, which the Messages API rejects — surfacing in OpenCode as a mid-task `stream error` that drops the conversation. The guard fires only when `messages` would otherwise be empty (system instructions still drive the response), so non-empty requests are unaffected. ([#5342](https://github.com/diegosouzapw/OmniRoute/pull/5342) — thanks @wild-feather) - **providers (gemini):** drop retired Google AI Studio model ids and align the catalog to what the live GenAI API actually serves (verified 2026-06-29 against the official deprecations page). Removes long-retired `gemini-1.5-pro`/`gemini-1.5-flash`, the shut-down `gemini-2.0-flash`/`gemini-2.0-flash-lite`, and dead experimentals; renames `gemini-3.1-flash-lite-preview` → the GA `gemini-3.1-flash-lite`; swaps the retired `text-embedding-004` for the live `gemini-embedding-001`/`gemini-embedding-2`; and adds graceful `modelDeprecation` forwards so legacy/renamed ids redirect to the GA model instead of 404ing. Native AI-Studio-direct image/video/music registration is intentionally out of scope (needs real executor work; those models stay reachable via Antigravity/Vertex/aggregators). ([#5337](https://github.com/diegosouzapw/OmniRoute/pull/5337) — thanks @backryun) @@ -1024,29 +1658,97 @@ Thanks to everyone whose work landed in v3.8.43: - **rerank:** `/v1/rerank` no longer rejects SiliconFlow and DeepInfra Qwen3-Reranker models with `400 "Invalid rerank model"` even though `/v1/models` lists them. The model-ID parser was never the problem (it already splits on the first slash, so `siliconflow/Qwen/Qwen3-Reranker-8B` parses correctly) — `siliconflow` and `deepinfra` were just missing from the rerank provider registry. Added both: SiliconFlow as Cohere-compatible, DeepInfra via a new `deepinfra` adapter (model in the URL path `POST /v1/inference/`, `{queries,documents}` request, positional `{scores}` response mapped to Cohere `results[]`). Regression guard: `tests/unit/rerank-providers-5332.test.ts`. ([#5332](https://github.com/diegosouzapw/OmniRoute/issues/5332) — thanks @maikokan) - **authz/dashboard:** stop rejecting every dashboard mutation with `403 INVALID_ORIGIN` when the dashboard is reached over a LAN IP / non-localhost host. The origin-pinning check (#5278) only accepted the configured `*_PUBLIC_BASE_URL` (typically `http://localhost:20128`) plus the internal `request.url` origin — which Next.js standalone reports as the bind host, not the real `Host`. So opening the dashboard at e.g. `http://192.168.0.15:20128` made the browser's same-origin `Origin` match no candidate, and **every** POST/PUT/DELETE (save API key, save provider, test connection) failed while GETs still worked. Two fixes: **(a)** the request `Host` (or a trusted `X-Forwarded-Host`) is now accepted as a valid mutation origin, gated by two independent checks — the token-stamped socket peer must be loopback/private-LAN **and** the Host itself must be a loopback/private-LAN IP literal, so a DNS-rebinding domain (which classifies as `remote`) can never become a trusted origin and the protocol is pinned to the actual connection; **(b)** the `INVALID_ORIGIN` response now carries an actionable message (set `OMNIROUTE_PUBLIC_BASE_URL`) and the dashboard surfaces API error `.message` via a shared `extractApiErrorMessage` helper instead of rendering the raw error object. Regression guards: `tests/unit/authz/public-origin.test.ts` (direct LAN/loopback + DNS-rebinding defense), `tests/unit/api-error-message-5340.test.ts`. ([#5340](https://github.com/diegosouzapw/OmniRoute/issues/5340)) -### 📝 Maintenance +### 📝 維護 - **chore(dead-code):** repo-wide sweep of unused exported symbols and a matching dead-code baseline ratchet — trimmed unused exported helpers, validation/settings/encryption-config schemas, utility/domain/static-constant/formatting helpers, runtime test helpers, the request-timeout fetch wrapper, event-bus, semantic-cache (maintenance + expiry), correlation-middleware, MCP-scope, service-registry, build-profile, api-key-format, authz-class, models.dev-context, embedding-cache, provider-limits-scheduler, search-validator, webhook-example, agent-skills-repo-URL and command-code-auth-cleanup exports. Pure dead-code removal validated by `typecheck:core` (no remaining referencing site) — no behavior change. ([#5321](https://github.com/diegosouzapw/OmniRoute/pull/5321), [#5322](https://github.com/diegosouzapw/OmniRoute/pull/5322), [#5324](https://github.com/diegosouzapw/OmniRoute/pull/5324), [#5325](https://github.com/diegosouzapw/OmniRoute/pull/5325), [#5328](https://github.com/diegosouzapw/OmniRoute/pull/5328), [#5329](https://github.com/diegosouzapw/OmniRoute/pull/5329), [#5330](https://github.com/diegosouzapw/OmniRoute/pull/5330), [#5331](https://github.com/diegosouzapw/OmniRoute/pull/5331), [#5333](https://github.com/diegosouzapw/OmniRoute/pull/5333), [#5334](https://github.com/diegosouzapw/OmniRoute/pull/5334), [#5335](https://github.com/diegosouzapw/OmniRoute/pull/5335), [#5336](https://github.com/diegosouzapw/OmniRoute/pull/5336), [#5338](https://github.com/diegosouzapw/OmniRoute/pull/5338), [#5339](https://github.com/diegosouzapw/OmniRoute/pull/5339), [#5353](https://github.com/diegosouzapw/OmniRoute/pull/5353), [#5354](https://github.com/diegosouzapw/OmniRoute/pull/5354), [#5355](https://github.com/diegosouzapw/OmniRoute/pull/5355), [#5356](https://github.com/diegosouzapw/OmniRoute/pull/5356), [#5357](https://github.com/diegosouzapw/OmniRoute/pull/5357), [#5359](https://github.com/diegosouzapw/OmniRoute/pull/5359), [#5362](https://github.com/diegosouzapw/OmniRoute/pull/5362), [#5364](https://github.com/diegosouzapw/OmniRoute/pull/5364), [#5365](https://github.com/diegosouzapw/OmniRoute/pull/5365), [#5366](https://github.com/diegosouzapw/OmniRoute/pull/5366), [#5368](https://github.com/diegosouzapw/OmniRoute/pull/5368), [#5369](https://github.com/diegosouzapw/OmniRoute/pull/5369), [#5371](https://github.com/diegosouzapw/OmniRoute/pull/5371) — thanks @JxnLexn) --- -## [3.8.40] — TBD +## [3.8.40] — 2026-06-29 -_In development — bullets added per PR; finalized at release._ +### ✨ 新功能 + +- **feat(compression): relevance extractive engine** — a new opt-in compression engine that scores each sentence by term-overlap (Jaccard) with the user's last query minus a length/boilerplate penalty, greedily keeps the most relevant within a budget, and reconstructs the original order. Pure-string, deterministic, ReDoS-safe (char-code tokenization, no `RegExp` over user input), fail-open, default off. Ideal for trimming long pasted RAG context / tool output to what's relevant. Sentences carrying real signal (digits/URLs/errors/code/paths) are never dropped; `overlapThreshold`/`budgetPercent`/`boilerplateWeight` are configurable. Tier-2 item of the compression feature-extraction roadmap (#7). ([#5289](https://github.com/diegosouzapw/OmniRoute/pull/5289)) +- **feat(compression): hard-budget mode — compress to ≤ N tokens** — a deterministic post-pass (`targetTokens` / `targetRatio`, default unset → no-op) that trims a body to a token budget. It ranks sentences/lines by average `scoreToken` ascending and drops the lowest-saliency ones until the body fits (measured by the exact cl100k `countTextTokens`), preserving original order. Lines carrying real signal (digits, URLs, `Error:`-family, code fences, stack `at`-frames, multi-segment paths, `key=value`) are never dropped; the budget is distributed proportionally across messages so the total stays ≤ target; an unreachable target (all-preserved) surfaces a `validationWarnings` note instead of failing silently. Does NOT touch the `estimateCompressionTokens` budget-gate estimator. Tier-3 item of the compression feature-extraction roadmap (#17). ([#5288](https://github.com/diegosouzapw/OmniRoute/pull/5288), follow-up [#5291](https://github.com/diegosouzapw/OmniRoute/pull/5291)) +- **feat(compression): result memoization for deterministic engines (opt-in)** — caches `(input, config) → result` for provably pure, stateless modes (`lite`/`standard`/`rtk` and stacked pipelines of `{lite,caveman,rtk}`) to skip recompute on the hot path. Opt-in via `memoizeCompressionResults` (default off → zero behavior change). Conservative opt-in whitelist (stateful `ccr`/`session-dedup` — which write the cross-request CCR store — and model-backed `ultra`/`aggressive`/`llmlingua` are never cached), principal-scoped (skipped without a principal, so no cross-principal body leak), and clone-on-store + clone-on-read. Tier-3 item of the compression feature-extraction roadmap (#21). ([#5286](https://github.com/diegosouzapw/OmniRoute/pull/5286)) +- **feat(compression): inline transparency annotation** — surfaces `tokens=847→312; rules: filler×8, dedup×2` derived from existing compression stats. The `X-OmniRoute-Compression` response header is extended **append-only** (the `mode; source=X` prefix stays byte-identical, so existing header parsers don't break) and the compression studio cockpit shows a matching badge. Zero new computation — it aggregates the `rulesApplied`/`techniquesUsed` already on the stats. Tier-3 item of the compression feature-extraction roadmap (#18). ([#5284](https://github.com/diegosouzapw/OmniRoute/pull/5284)) +- **feat(compression): saliency heatmap in the compression studio** — the preview studio can now color each token by saliency: `ultra` per-token `scoreToken` (0–1, green→red gradient) or universal kept/removed from the existing diff. A dry-run visualization behind a toggle (no cost on a normal preview; backward-compatible when off). Completes the visualization half of roadmap item #13 (the A/B comparison shipped in [#5080](https://github.com/diegosouzapw/OmniRoute/pull/5080)). ([#5285](https://github.com/diegosouzapw/OmniRoute/pull/5285)) +- **feat(compression): composite-command splitter for RTK detection** — `cd /x && git status` now detects as `git-status` (previously the whole string was treated as one command and matched no filter). A quote-aware top-level tokenizer splits on `&&`/`||`/`;` (never inside quotes or `$(…)`/backtick subshells) and feeds the **last** segment to RTK command detection, so every RTK filter/renderer fires on commands wrapped in `cd … &&`/`||`/`;` chains. O(n), no RegExp over the command (ReDoS-safe). Tier-3 item of the compression feature-extraction roadmap (#16). ([#5283](https://github.com/diegosouzapw/OmniRoute/pull/5283)) +- **feat(mcp): `omniroute_tool_search` tool + one-line TS signatures** — new MCP tool that does lexical keyword search over every MCP tool's name/description and returns the top matches as compact one-line TypeScript signatures (~half the JSON-schema token cost), so agents discover tools on demand instead of carrying all ~88 schemas every turn. Search is ReDoS-safe (substring scoring, never `new RegExp` on the query) and deterministic; `tools/list` stays complete (no hidden tools). Adds the `read:tools` scope. Tier-1 item of the compression feature-extraction roadmap. ([#5269](https://github.com/diegosouzapw/OmniRoute/pull/5269)) +- **feat(compression): RTK semantic command-output renderers (opt-in)** — adds a second, opt-in compaction layer to the RTK engine that rewrites structured command output into a far more compact semantic form: `git diff` → file headers + `@@` hunks + changed lines only; an all-green `pytest`/`jest`/`vitest`/`eslint` run → its one-line summary; `terraform`/`tofu plan` → `Plan: +N ~M -K` plus the resource list; `kubectl`/`aws` JSON arrays → a minimal table. Each renderer is conservative (no-op when the shape doesn't match) and the integration is fail-open; the test-green renderer never collapses output that carries any failure signal. Gated by `RtkConfig.enableRenderers` (default off → zero behavioral change). Eighth item of the compression feature-extraction roadmap. ([#5268](https://github.com/diegosouzapw/OmniRoute/pull/5268)) +- **feat(compression): QuantumLock cache-prefix stabilization (opt-in, default off)** — recovers upstream prompt-cache hits that a volatile fragment in the system prompt would otherwise bust. When a caller injects a session UUID, unix timestamp, request-id, JWT, API-key shape, or long hex digest into the `role:system` message every turn, the longest common prefix across turns ends at that changing byte → the whole system prompt after it is re-billed and re-processed each turn. QuantumLock replaces each non-semantic volatile fragment with a **positional, value-independent** placeholder `⟦Q{i}⟧` and appends the real values in a delimited `⟦QUANTUMLOCK⟧` tail. The rewrite is **sent to the model** (lossless — not restored), so the system-prompt body becomes **byte-identical across turns** and the provider caches the long stable prefix while only the small tail differs. Opt-in, default off, applied only for caching providers (`isCachingProvider && config.quantumLock.enabled`); bounded ReDoS-safe patterns; idempotent; **no date/time patterns** (semantically meaningful — explicit non-goal). Studio gets a toggle + a "🔒 N volatile fragment(s) stabilized" dry-run badge. Seventh item of the compression feature-extraction roadmap (bench: [#5080](https://github.com/diegosouzapw/OmniRoute/pull/5080), gate: [#5127](https://github.com/diegosouzapw/OmniRoute/pull/5127), fuzzy: [#5143](https://github.com/diegosouzapw/OmniRoute/pull/5143), ionizer: [#5148](https://github.com/diegosouzapw/OmniRoute/pull/5148), TOON: [#5163](https://github.com/diegosouzapw/OmniRoute/pull/5163), CCR ranged: [#5187](https://github.com/diegosouzapw/OmniRoute/pull/5187), risk-gate: [#5243](https://github.com/diegosouzapw/OmniRoute/pull/5243)). ([#5260](https://github.com/diegosouzapw/OmniRoute/pull/5260)) +- **kilocode:** anonymous (no-auth) access to Kilo Code's free models, mirroring the `opencode`/`mimocode` pattern. With no Kilo account connected, requests now fall back to the gateway's anonymous tier (`Authorization: Bearer anonymous` on `api.kilo.ai/api/openrouter`) so the free models work without signup; a connected OAuth account is still used unchanged for the paid tier ([#5259](https://github.com/diegosouzapw/OmniRoute/pull/5259), #4019 — thanks @Theadd for the reference implementation) +- **feat(logging): call-log correlation ID (end-to-end)** — every request now gets a unique correlation id, returned in the `X-Correlation-Id` response header, persisted in `call_logs` (migration **109**), filterable via `/api/usage/call-logs`, and surfaced in the dashboard request logger (per-chunk stream timestamps + active-requests-first sort). This is the safe, cohesive core subset of the larger #5275 — landed on its own so the low-risk value isn't blocked by the parts of that PR still under review. ([#5279](https://github.com/diegosouzapw/OmniRoute/pull/5279) — thanks @hartmark) +- **feat(providers): Microsoft 365 Copilot individual provider** — adds the `copilot-m365-web` provider (the 237th), wiring the M365 BizChat framing/connection helpers into a selectable web-session provider backed by `m365.cloud.microsoft/chat` for individual Microsoft 365 plans. Builds on the M365 pure-framing groundwork from #4696. Regression guard: `tests/unit/copilot-m365-web-executor.test.ts`. ([#5302](https://github.com/diegosouzapw/OmniRoute/pull/5302) — thanks @skyzea1) + +### 🔧 錯誤修正 + +- **ci(docker):** re-point the Docker Hub / GHCR `:latest` (and `:latest-web`) tags to the just-published release. On a `release: released` event the freshly-created git tag is often not yet visible to `git fetch --tags` when `docker-publish` runs, so the `:latest`-promotion gate built its candidate set purely from `git tag -l` and resolved the highest semver to the **previous** version — leaving `latest` one release behind (3.8.39 published, `latest` still 3.8.38). The decision now lives in `scripts/ci/should-promote-latest.sh`, which folds the current `VERSION` into the candidate set before picking the highest stable semver, making promotion independent of tag-sync timing (a patch published after a higher minor still won't grab `latest`). Regression guard: `tests/unit/build/should-promote-latest-5301.test.ts` ([#5301](https://github.com/diegosouzapw/OmniRoute/issues/5301)) +- **command-code:** treat a non-positive `max_tokens`/`max_completion_tokens` (e.g. Zoo Code's `-1` "let the server choose") as "no limit" — omit the field instead of forcing it to `1`. `clampMaxTokens` previously did `Math.max(1, …)`, so a client `-1` was sent upstream as `max_tokens: 1`, truncating the response to a single token (the observed `completion_tokens: 1`, `content: null`, `reasoning_content: "The"` with `finish_reason: stop`). Now any value `≤ 0` is dropped so Command Code applies the model's native default; positive values are still floored and clamped to the 200k ceiling. Regression guard: `tests/unit/command-code-maxtokens-negative-5166.test.ts` ([#5166](https://github.com/diegosouzapw/OmniRoute/issues/5166) — thanks @Stazyu) +- **fix(auth): compare-and-swap guard on the OAuth refresh persist** — under multi-agent load, the per-connection refresh mutex makes `[network refresh + DB write]` atomic for **one** connection, but it does not protect against a **third** writer (a sibling request, a concurrent HealthCheck, or a replica) landing a fresher `refresh_token` rotation on the same `connection_id` between the staleness read and the persist. Overwriting that fresher row reverts the sibling's rotation; the next caller then loads the now-consumed token, Auth0/Anthropic flag it as `refresh_token_reused`, and the whole token family gets revoked (the 1352× claude/`aa5dd5cf` invalidation storm). `getAccessToken` now re-reads the row's current `refresh_token` immediately before persisting (inside the mutex) and **skips the write** when it has rotated past the token the caller presented — the caller still receives the freshly-issued access token, only the DB overwrite is skipped. Opt-in via `runWithCasGuard` (no active guard ⇒ byte-identical behavior); skip/persist counters exposed via `getCasGuardStats()`. Regression guard: `tests/unit/token-refresh-cas-guard-4038.test.ts`. ([#4038](https://github.com/diegosouzapw/OmniRoute/issues/4038) — thanks @KooshaPari for the root-cause diagnosis) +- **mcp:** break the `schemas/tools.ts ↔ schemas/toolSearch.ts` import cycle introduced when the `tool_search` defs (#5269) were extracted into their own module — `toolSearch.ts` imported `McpToolDefinition` from `tools.ts` while `tools.ts` imported `toolSearchTool` from `toolSearch.ts`, failing `check:cycles` on `release/v3.8.40`. The shared `AuditLevel` + `McpToolDefinition` types now live in a leaf `schemas/toolDefinition.ts` that both import; `tools.ts` re-exports them for backward compatibility. +- **compression (analytics):** record attempted-but-no-op compression runs so Stacked is no longer invisible when it saves nothing. Previously a `compression_analytics` row was written only on a net-positive saving, so a Stacked (RTK→Caveman) pipeline that ran on already-compact context produced no row — indistinguishable from "never dispatched" (`byMode.stacked.count` stayed flat while Ultra climbed). Such runs are now recorded with `skip_reason` and surfaced as a per-mode `skipped` count plus `totalSkipped`/`bySkipReason` in the analytics summary and the Mode Breakdown; the existing net-saving totals/averages are unchanged (skip rows are excluded from them) (#4268 — thanks @abdulkadirozyurt, @androw) +- **cli (tray):** fix `omniroute server --tray` showing no tray on macOS/Linux with no error printed. The wired Unix tray path loaded `systray2` through an inline loader that called `require("module")` inside an ESM `.mjs` file (`"type":"module"`) → `ReferenceError: require is not defined`, silently swallowed (regressed in v3.8.34); even if it had loaded, `systray2` isn't in `node_modules` (it's lazily installed into `~/.omniroute/runtime`). The loader now delegates to the runtime loader, the icon path (`icon.png`) is corrected, `isTemplateIcon` is `false` (the full-color icon rendered as a white square under macOS template mode), and tray start failures are surfaced to stderr instead of being swallowed (#4605 — thanks @ProgMEM-CC) +- **agent-bridge (antigravity):** unwrap the cloudcode-pa `.request` envelope when converting Antigravity IDE requests. The real IDE sends `cloudcode-pa.googleapis.com/v1internal:generateContent` with the Gemini request nested under `.request` (`{ project, model, request: { contents, systemInstruction, generationConfig } }`), but the bridge read those fields at the top level — yielding an empty conversation, so prompts hung mid-execution. The legacy `/v1beta/models/:generateContent` top-level shape still works (#4294 — thanks @shabeer) +- **dashboard:** add a GitHub releases fallback to the "Update Available" lookup. After the v3.8.28 fix added an npm-registry HTTP fallback, the banner could still stay hidden on networks that reach GitHub (where the news feed already loads) but not `registry.npmjs.org`. `resolveLatestVersion()` now tries npm CLI → npm registry → GitHub releases (`/repos/diegosouzapw/OmniRoute/releases/latest`) before giving up, and logs a warning only when all three fail (#4100) +- **command-code:** omit `max_tokens` when the client omits it so the upstream applies the model's native default, fixing `400 "expected <=200000"` on `/alpha/generate` for high-cap models; an explicit oversized client value is clamped to the 200k endpoint ceiling (#5221 — thanks @adivekar-utexas) +- **combo:** wire session stickiness into the round-robin dispatch path. Multi-turn conversations from clients that send no session id (Codex CLI, Claude Code, most OpenAI-compatible tools) were rotated to a different connection on every turn by round-robin combos, busting the upstream prompt-cache → cold high-reasoning starts, intermittent `504`s and throughput collapse under concurrency. The weighted/priority paths already honored per-conversation stickiness; the round-robin handler returned before reaching it. Round-robin now starts the rotation at the conversation's sticky connection (failover to the other targets is preserved), and different conversations still spread across connections — only intra-conversation rotation is removed ([#5248](https://github.com/diegosouzapw/OmniRoute/pull/5248), #3825 — thanks @bypanghu, @jpsn123, @xz-dev) +- **kiro:** replace the synthesized trailing `"Continue"` turn with a neutral filler (`"..."`) — when an OpenAI→Kiro request ends on an assistant/tool turn, the translator synthesizes the protocol-required trailing user turn, and the literal word `"Continue"` could be read by Kiro/CodeWhisperer as a real user instruction and trigger unintended agent action. A trailing tool-result turn is still promoted as-is (it already collapses to a real user turn); only the assistant-text-ending case is affected. Regression guards: `tests/unit/kiro-continue-filler-5231.test.ts`. ([#5231](https://github.com/diegosouzapw/OmniRoute/issues/5231)) +- **combo:** advance to the next combo target on a `400 "requested model is not supported"` instead of hard-failing. The 400 guard in the priority strategy treated `MODEL_CAPACITY` as a block-fallback reason, so a combo that hit a provider lacking a specific model returned a hard `400` even when other targets (different providers) supported it. Such 400s now fall through to the next target. ([#5249](https://github.com/diegosouzapw/OmniRoute/pull/5249) — thanks @Chewji9875) +- **dashboard:** disabled no-auth providers no longer vanish from the All Providers page. Disabling a no-auth provider (the "No authentication required" toggle, which adds it to `blockedProviders`) silently removed its card because the page _dropped_ blocked no-auth entries from its render list — the only way back was buried under Settings → Security → Blocked Providers. The page now **partitions** no-auth entries: visible providers render as before, blocked ones appear in a "Disabled" sub-group with an **Enable** button that un-blocks them in place. Aggregates, counts and `/v1/models` still consume the visible-only list (blocked providers stay out of routing). Regression guard: `tests/unit/noauth-blocked-partition-5183.test.ts`. ([#5183](https://github.com/diegosouzapw/OmniRoute/issues/5183), follow-up from [#5166](https://github.com/diegosouzapw/OmniRoute/issues/5166) — thanks @WslzGmzs) +- **dashboard:** add a parent `/dashboard/context` page so RSC prefetches of the compression-context hub no longer 404. The route only had sub-routes (`settings`, `combos`, `ultra`, …) and no parent page, so the App Router returned 404 for the bare segment. The parent now redirects to its canonical sub-route (`/dashboard/context/settings`), honoring a legacy `?tab=` query for deep links. Regression guard: `tests/unit/dashboard/context-parent-redirect-5298.test.ts` ([#5298](https://github.com/diegosouzapw/OmniRoute/issues/5298) — thanks @KooshaPari) +- **i18n:** add the missing `sidebar.gamificationGroup` message across all 42 locales — the Gamification sidebar group referenced a `titleKey` that existed in no locale, logging `MISSING_MESSAGE: sidebar.gamificationGroup (en)` at runtime (the group still rendered via its `titleFallback`). The key is now present everywhere so the warning is gone and locale coverage is unaffected ([#5298](https://github.com/diegosouzapw/OmniRoute/issues/5298) — thanks @KooshaPari) +- **api(stream):** `/v1/chat/completions` no longer returns SSE for a non-stream OpenAI-compatible request when `stream` is omitted and the client sends `Accept: application/json, text/event-stream` — the Vercel AI SDK / OpenAI SDK non-stream signature (`doGenerate()`/`generateText()`), which then failed with `Invalid JSON response` (`Unexpected token 'd', "data: {"id"...`). The route-level Accept override (#302) and `resolveStreamFlag` now treat an Accept header that explicitly lists `application/json` as a JSON opt-in even when it also lists `text/event-stream`; only a pure `Accept: text/event-stream` (no `application/json`) still opts an omitted-`stream` request into SSE, and an explicit body `stream` value always wins. The shared decision now lives in `acceptHeaderForcesStream`. Regression guard: `tests/unit/sse-nonstream-accept-5305.test.ts`. ([#5305](https://github.com/diegosouzapw/OmniRoute/issues/5305) — thanks @md-riaz) +- **providers:** drop the retired GPT‑5.2 / GPT‑4.5 models from the direct **ChatGPT‑web** and **Codex** surfaces (OpenAI removed them there), so OmniRoute stops advertising/routing models that no longer exist. Scoped on purpose to those two providers — third‑party proxies that still expose the ids are untouched. ([#5280](https://github.com/diegosouzapw/OmniRoute/pull/5280) — thanks @backryun) +- **codex:** drop the deprecated `local_shell` hosted tool type before forwarding to OpenAI's Responses API, resolving the omni-combo `400 "The local_shell tool is no longer supported."` spike. Inbound Responses `local_shell` is still accepted and mapped to a caller-side Chat `shell` function for compatibility. ([#5250](https://github.com/diegosouzapw/OmniRoute/pull/5250), [#5256](https://github.com/diegosouzapw/OmniRoute/pull/5256) — thanks @KooshaPari) +- **antigravity:** retry excluded accounts via the fallback LRU. The combo same-model retry loop accumulates excluded Antigravity connection ids after account-level failures, but auth selection only treated a single `excludeConnectionId` as a fallback scenario — once exclusions accumulated through `excludedConnectionIds`, selection could fall back to normal sticky/priority behavior instead of LRU-selecting the next eligible account for the same model/family. Any non-empty accumulated exclude set is now treated as fallback mode. Builds on the family-scoped lockout work in #5180 (v3.8.39). ([#5222](https://github.com/diegosouzapw/OmniRoute/pull/5222) — thanks @Ardem2025) +- **grok-cli:** strip unsupported sampling params (`presencePenalty`, `frequencyPenalty`, `logprobs`, `topLogprobs`) before sending to the Grok Build API, fixing `400 'Model does not support parameter presencePenalty'` when clients (MiMoCode, Cursor, etc.) send OpenAI-style params. ([#5273](https://github.com/diegosouzapw/OmniRoute/pull/5273) — thanks @fulorgnas) +- **grok-cli:** accept the full `~/.grok/auth.json` object in the dashboard import-token endpoint. The `oauthImportTokenSchema` only accepted a bare string token while the UI sends the whole auth.json object → `400 Bad Request`; the schema now accepts the object and stores the original under `providerSpecificData.rawAuthJson` for diagnostics and token refresh. ([#5258](https://github.com/diegosouzapw/OmniRoute/pull/5258) — thanks @fulorgnas) +- **qoder:** coalesce concurrent PAT→job-token exchanges per PAT so high-concurrency / multi-agent bursts no longer stampede `openapi.qoder.sh/api/v1/jobToken/exchange` before the first exchange populates the completed-token cache; the shared exchange is also decoupled from any single caller's `AbortSignal` so one aborted waiter can't cancel it for the others. ([#5254](https://github.com/diegosouzapw/OmniRoute/pull/5254), [#5265](https://github.com/diegosouzapw/OmniRoute/pull/5265) — thanks @KooshaPari) +- **proxy:** scope the fallback reachability cache by normalized target **URL** instead of by hostname, so a failed probe for one endpoint on a shared API host no longer suppresses a later probe for a different endpoint on that same host for the full TTL (host fallback is preserved for malformed URLs). ([#5261](https://github.com/diegosouzapw/OmniRoute/pull/5261) — thanks @KooshaPari) +- **proxy:** cache failed fast-fail health probes with a short negative TTL instead of the full positive health TTL, so a single transient timeout/load blip no longer marks a working residential SOCKS5 proxy unreachable for the whole window (#5109 regression coverage added). ([#5255](https://github.com/diegosouzapw/OmniRoute/pull/5255) — thanks @KooshaPari) +- **mcp:** forward HTTP auth to internal tool fetches so MCP tools that call back into the local API surface carry the caller's authorization. ([#5218](https://github.com/diegosouzapw/OmniRoute/pull/5218) — thanks @KooshaPari) +- **logging:** preserve the **outbound provider request** headers in the detailed call-log Provider Request payload (previously the upstream **response** headers were shown there). chatCore now keeps executor-returned request headers when wrapping streaming and non-streaming responses; response headers stay scoped to the `Response`. ([#5257](https://github.com/diegosouzapw/OmniRoute/pull/5257) — thanks @rdself) +- **sse:** scope textual ``/`` tag extraction so generic OpenAI-compatible paths don't rewrite prompt-format content into `reasoning_content`; an explicit opt-in keeps tag-native families (DeepSeek-R1, QwQ) working while Antigravity/Agy stay excluded by provider/model prefix. ([#5224](https://github.com/diegosouzapw/OmniRoute/pull/5224) — thanks @rdself) +- **mcp:** break the `schemas/tools.ts ↔ schemas/toolSearch.ts` import cycle introduced when the `tool_search` defs (#5269) were extracted into their own module — `toolSearch.ts` imported `McpToolDefinition` from `tools.ts` while `tools.ts` imported `toolSearchTool` from `toolSearch.ts`, failing `check:cycles` on `release/v3.8.40`. The shared `AuditLevel` + `McpToolDefinition` types now live in a leaf `schemas/toolDefinition.ts` that both import; `tools.ts` re-exports them for backward compatibility. ([#5282](https://github.com/diegosouzapw/OmniRoute/pull/5282)) +- **compression (analytics):** record attempted-but-no-op compression runs so Stacked is no longer invisible when it saves nothing. A `compression_analytics` row was previously written only on a net-positive saving, so a Stacked pipeline that ran on already-compact context produced no row — indistinguishable from "never dispatched". Such runs are now recorded with `skip_reason` and surfaced as a per-mode `skipped` count plus `totalSkipped`/`bySkipReason`; net-saving totals/averages are unchanged (skip rows excluded). ([#5277](https://github.com/diegosouzapw/OmniRoute/pull/5277), #4268 — thanks @abdulkadirozyurt, @androw) +- **cli (tray):** fix `omniroute server --tray` showing no tray on macOS/Linux with no error printed. The Unix tray path loaded `systray2` through an inline loader that called `require("module")` inside an ESM `.mjs` file → `ReferenceError: require is not defined`, silently swallowed (regressed in v3.8.34); even if loaded, `systray2` is lazily installed into `~/.omniroute/runtime`, not `node_modules`. The loader now delegates to the runtime loader, the icon path is corrected, `isTemplateIcon` is `false` (the full-color icon rendered as a white square under macOS template mode), and tray start failures surface to stderr. ([#5276](https://github.com/diegosouzapw/OmniRoute/pull/5276), #4605 — thanks @ProgMEM-CC) +- **agent-bridge (antigravity):** unwrap the cloudcode-pa `.request` envelope when converting Antigravity IDE requests. The real IDE sends `cloudcode-pa.googleapis.com/v1internal:generateContent` with the Gemini request nested under `.request`, but the bridge read those fields at the top level — yielding an empty conversation, so prompts hung mid-execution. The legacy `/v1beta/models/:generateContent` top-level shape still works. ([#5267](https://github.com/diegosouzapw/OmniRoute/pull/5267), #4294 — thanks @shabeer) +- **dashboard:** add a GitHub releases fallback to the "Update Available" lookup. After the v3.8.28 npm-registry fallback, the banner could still stay hidden on networks that reach GitHub but not `registry.npmjs.org`. `resolveLatestVersion()` now tries npm CLI → npm registry → GitHub releases before giving up. ([#5266](https://github.com/diegosouzapw/OmniRoute/pull/5266), #4100) +- **command-code:** omit `max_tokens` when the client omits it so the upstream applies the model's native default, fixing `400 "expected <=200000"` on `/alpha/generate` for high-cap models; an explicit oversized client value is clamped to the 200k endpoint ceiling. ([#5221](https://github.com/diegosouzapw/OmniRoute/pull/5221) — thanks @adivekar-utexas) + +### 🔒 Security + +- **authz:** require auth for the `/v1beta/*` Gemini-compatible client API. `next.config.mjs` rewrote `/v1beta/:path*` → `/api/v1beta/:path*`, but `src/proxy.ts` didn't match `/v1beta` before the rewrite and `classifyRoute()` didn't classify `/api/v1beta/*` as client API — so unauthenticated `/v1beta/models/...:generateContent` traffic could reach the model-serving route without the central client-API auth policy. Both alias and rewritten forms are now classified `CLIENT_API`, enforcing Bearer auth when `REQUIRE_API_KEY` is enabled. ([#5274](https://github.com/diegosouzapw/OmniRoute/pull/5274) — thanks @rdself) +- **sentinel:** security hardening pass across request handling. ([#5241](https://github.com/diegosouzapw/OmniRoute/pull/5241) — thanks @iamedwardngo) +- **providers:** refresh impersonation User-Agents + TLS fingerprint profiles to current real-client versions; several had drifted or were inconsistent across files, a bot-detection/blocking risk. ([#5237](https://github.com/diegosouzapw/OmniRoute/pull/5237) — thanks @backryun) +- **authz (public origin):** centralize browser-mutation origin validation into `src/server/origin/publicOrigin.ts` and wire it through the authz pipeline, replacing the per-route same-origin-only check that `403`'d dashboard mutations when served behind a reverse proxy on a different public origin. The module resolves the allowed public origin from configured base-URL env vars or trusted forwarded headers (only when `OMNIROUTE_TRUST_PROXY` is set **and** the peer is loopback/LAN via peer-stamp), validates `Sec-Fetch-Site` metadata, and sanitizes `Host`/`Forwarded` inputs (rejects control chars, userinfo, path/query in Host). Regression guards: `tests/unit/authz/public-origin.test.ts` + `tests/unit/authz/pipeline.test.ts`. ([#5278](https://github.com/diegosouzapw/OmniRoute/pull/5278) — thanks @Thinkscape / @abodera) + +### 📝 維護 + +- **docs:** reorganize `docs/`, run an accuracy audit, and drop Node 20 from the supported matrix (rebased onto the current release tip). ([#5262](https://github.com/diegosouzapw/OmniRoute/pull/5262)) +- **providers:** remove the discontinued Gemini CLI channel — Google shut it down on 2026-06-18; the supported migration path is Antigravity. ([#5246](https://github.com/diegosouzapw/OmniRoute/pull/5246) — thanks @rdself) +- **dashboard:** add more provider icons from Lobehub. ([#5220](https://github.com/diegosouzapw/OmniRoute/pull/5220) — thanks @backryun) +- **deps:** resolve `npm install` warnings, fix a runtime `Cannot find module` crash on `omniroute serve` (the runtime-env module was missing from the npm `files` allow-list), and clear the 4 moderate `npm audit` advisories. ([#5252](https://github.com/diegosouzapw/OmniRoute/pull/5252) — thanks @yunaamelia), ([#5230](https://github.com/diegosouzapw/OmniRoute/pull/5230), #5227) +- **docker:** harden the base image against container-scan CVEs and skip comment lines in the #4076 builder heap-ordering check. ([#5229](https://github.com/diegosouzapw/OmniRoute/pull/5229), [#5233](https://github.com/diegosouzapw/OmniRoute/pull/5233)) +- **ci:** Trivy advisory scan now ignores unfixed CVEs to cut Security-tab noise. ([#5235](https://github.com/diegosouzapw/OmniRoute/pull/5235)) +- **test(combo):** reconcile the #4279 stop-guard test with the #5249 advance policy. ([#5300](https://github.com/diegosouzapw/OmniRoute/pull/5300)) +- **test(targets):** add 14 unit tests for the shared `combo/targetExhaustion.ts` handler (provider-exhausted / connection-error / transient rate-limited classification), registered in the vitest discovery list. ([#5296](https://github.com/diegosouzapw/OmniRoute/pull/5296) — thanks @KooshaPari) --- ## [3.8.39] — 2026-06-28 -### ✨ New Features +### ✨ 新功能 - **feat(oauth): remote Antigravity login via local helper + paste-credentials** — Antigravity (and other Google "native/desktop" OAuth providers) use Google's `firstparty/nativeapp` consent, which only releases the auth code when the loopback redirect (`127.0.0.1:`) is reachable from the approving browser. On a remote VPS install that loopback lives on the server, so the consent hangs forever and never emits a code — the "paste the callback URL" fallback has nothing to paste (a Google-side constraint, identical in upstream 9router). A new `omniroute login antigravity` CLI helper runs the OAuth on the user's **own** machine (where 127.0.0.1 works), exchanges the code, and prints a single-line `omniroute-cred-v1.…` credential blob; the dashboard's Antigravity Connect → Step 2 field now accepts that blob (alongside callback URLs) and persists the connection via a new `paste-credentials` action (server-side onboarding, provider-allowlisted, with the blob's embedded provider required to match the route). The SSH local-forward tunnel is documented as a zero-tooling alternative. See [`docs/guides/REMOTE-MODE.md`](docs/guides/REMOTE-MODE.md). ([#5203](https://github.com/diegosouzapw/OmniRoute/pull/5203)) - **feat(agent-bridge): graceful cert-install fallback for containers / headless** — when the MITM root CA can't be installed into the system trust store automatically (Docker / headless / no sudo / read-only trust store), the Agent Bridge no longer hard-fails on start with a generic "Certificate install failed". It now starts in skip mode and the dashboard surfaces a platform-specific **manual-install guide** (plus a CA download link) so the operator can trust the certificate by hand. The trust-cert endpoints return a structured `{ skippable, manualGuide }` response (HTTP 200) for environment failures instead of a 500; an explicit user cancellation is still reported distinctly. ([#4546](https://github.com/diegosouzapw/OmniRoute/issues/4546) — thanks @phuchptty) - **feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat)** — extends the `omniroute_ccr_retrieve` MCP tool and `/api/compression/retrieve` endpoint with optional `range` (byte/line slice), `grep` (ReDoS-safe literal or bounded-pattern match against stored lines), and `stats` (byte/line/word counts) parameters so agents pull exactly the slice or summary they need instead of re-expanding the entire stored block. All parameters are optional — no parameters returns the full block byte-identical to the existing behavior; the CCR store written by ionizer/fuzzy/headroom is fully compatible. Sixth item of the compression roadmap. ([#5187](https://github.com/diegosouzapw/OmniRoute/pull/5187)) - **feat(compression): TOON best-of-N candidate encoder + encoder A/B table** — adds `@toon-format/toon` as a candidate encoder in the headroom compression engine via a best-of-N scheme: both GCF and TOON run per prompt and the shorter result is kept, rather than hard-swapping encoders (GCF already encodes the headroom block and TOON is not a lossless universal win). An encoder A/B comparison table (GCF vs TOON vs JSON — bytes and cl100k tokens) is now surfaced in the compression studio. Fifth item of the compression feature-extraction roadmap (bench: [#5080](https://github.com/diegosouzapw/OmniRoute/pull/5080), gate: [#5127](https://github.com/diegosouzapw/OmniRoute/pull/5127), fuzzy/gate: [#5143](https://github.com/diegosouzapw/OmniRoute/pull/5143), ionizer: [#5148](https://github.com/diegosouzapw/OmniRoute/pull/5148)). ([#5163](https://github.com/diegosouzapw/OmniRoute/pull/5163)) -### 🔧 Bug Fixes +### 🔧 錯誤修正 +- **fix(cli): global npm install no longer fails with "Cannot find module scripts/build/runtime-env.mjs"** — the v3.8.39 heap auto-calibration fix made `bin/cli/commands/serve.mjs` import `scripts/build/runtime-env.mjs`, but that file was never added to the `files` whitelist in `package.json`, so the published npm tarball shipped the importing CLI without the imported module — breaking **every** `npm install -g omniroute` at startup (regression, all platforms). The file is now whitelisted (`npm pack` confirms it ships) and added to the pack-artifact policy allow/required lists, and a new regression test scans all `bin/**` entrypoints for runtime imports resolving under `scripts/` and asserts each is covered by the package `files` whitelist, so any future unpackaged CLI import fails CI instead of users' installs. ([#5227](https://github.com/diegosouzapw/OmniRoute/issues/5227) — thanks @PriyomSaha, @m-Yaghoubi, @jonlwheat2-gif) - **fix(oauth): Antigravity refresh no longer nulls the stored refresh_token on an empty upstream response** — Google's OAuth token endpoint uses non-rotating refresh tokens: a refresh response normally OMITS `refresh_token` and occasionally returns it as an empty string. The Antigravity executor's `refreshCredentials` used `typeof tokens.refresh_token === "string" ? tokens.refresh_token : credentials.refreshToken`, and because `typeof "" === "string"` is true, an empty-string response overwrote the good token with `""` — nulling it on first refresh. The check now treats a non-string **or empty** value as absent and preserves the stored token, matching the canonical `refreshGoogleToken` (`tokens.refresh_token || refreshToken`) semantics. ([#3850](https://github.com/diegosouzapw/OmniRoute/issues/3850) — thanks @3xa228148) - **fix(api): LAN/Tailscale dashboard access — `ws:` CSP scheme, GET-exempt version route, surface combo field errors** — three failures when opening the dashboard from a non-loopback host: (1) CSP `connect-src` allowed the `ws:` scheme only for loopback origins, blocking the dashboard's `ws://:*` Live WebSocket from LAN/Tailscale clients; the bare `ws:` scheme is now permitted (symmetric with the bare `wss:` already allowed), kept declarative in `next.config.mjs` with no global middleware (the project has none by design); (2) `GET /api/system/version` was blocked by `LOCAL_ONLY_API_PREFIXES` for all methods despite only `POST` spawning child processes (git/npm/pm2) — a new `LOCAL_ONLY_API_GET_EXEMPTIONS` set exempts safe read methods for this path while keeping `POST`/`PUT`/`PATCH`/`DELETE` strictly loopback-only; (3) `COMBO_002` validation errors only surfaced the generic message — `firstField`/`firstMessage` are now extracted from the first Zod issue and included in the response body. ([#5083](https://github.com/diegosouzapw/OmniRoute/issues/5083) — thanks @KooshaPari for the diagnosis and original PR #5084) - **fix(sse): defer `` close so it never leaks before `tool_calls` in Claude→OpenAI streaming** — when a Claude thinking block was followed by a tool_use block, the translator unconditionally emitted a `content: ""` chunk at `content_block_stop`, injecting a spurious assistant text chunk immediately before the `tool_calls` delta and corrupting OpenAI-compatible clients (e.g. Kimi Coding). The close marker is now deferred: it is flushed at the first `text_delta` that follows the thinking block (preserving the #4633 / decolua/9router#454 behavior for Claude Code / Cursor) or at stream finish when no tool_calls were collected. Tool-use streams never get a `text_delta` after the thinking block, so `` is never emitted into content before `tool_calls`. ([#5123](https://github.com/diegosouzapw/OmniRoute/issues/5123)) @@ -1075,8 +1777,9 @@ _In development — bullets added per PR; finalized at release._ - **fix(request-logger): never render a negative percentage in the compression badge** — when every prompt token was compressed (`totalIn = 0, compressed > 0`), the compression pill displayed `(-100%)` because the badge format hard-coded a leading `-` before the percentage value. The badge now omits the negative sign in this case, correctly representing the saving as a positive ratio. ([#5201](https://github.com/diegosouzapw/OmniRoute/pull/5201) — thanks @KooshaPari) - **fix(dashboard): use amber for home update-step warning icon** — the warning-state icon in the home update steps (`HomePageClient.tsx`) used `text-yellow-500` (Tailwind `#eab308`), which has poor contrast on light backgrounds (~1.9:1, below WCAG AA) and is inconsistent with the `amber` warning convention used by every sibling element in the same component. Switched to `text-amber-500` — a one-line `className` change with no behavior change. ([#5176](https://github.com/diegosouzapw/OmniRoute/pull/5176)) -### 📝 Maintenance +### 📝 維護 +- **test(docker): de-brittle the #4076 builder-stage heap-ordering test (false-failing on a comment)** — `dockerfile-build-heap-4076.test.ts` located the `npm run build` step via a raw `findIndex(/npm run build\b/)`, which matched a **comment line** in the builder stage (`# … npm run build fails with "Module not…"`, added with the v3.8.40 workspace-deps Docker fix) sitting before the `NODE_OPTIONS` heap line — making the test report the heap ceiling as set _after_ the build and fail, even though the real `RUN … npm run build` correctly follows the `ENV NODE_OPTIONS` line. Instruction matching now skips Dockerfile comment lines (`# …`), so the ordering guard checks real `ENV`/`RUN` instructions only. The Dockerfile itself was already correct; this was a base-red blocking every PR into `release/v3.8.40`. ([#4076](https://github.com/diegosouzapw/OmniRoute/issues/4076)) - **test(combo): deterministic context-relay universal-handoff coverage** — covers the universal (provider-agnostic) session-handoff path in `context-relay` (`combo.ts:2099–2139`), which previously had only a definition-order assertion and a `TODO(phase-2)`. The test drives the real pipeline via session seams (`x-session-id` → `relayOptions.sessionId` → `maybeGenerateUniversalHandoff`) without live infrastructure. ([#5168](https://github.com/diegosouzapw/OmniRoute/pull/5168)) - **test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity)** — adds the missing E2E test for the `quota-share` strategy, driving the real `handleChat` → chatCore → `selectQuotaShareTarget` → executor pipeline via in-process seams and asserting which connection is dispatched. The DRR selector already had 29 unit tests; this closes the E2E gap and brings quota-share to parity with the 17-strategy public matrix. ([#5179](https://github.com/diegosouzapw/OmniRoute/pull/5179)) - **test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap)** — covers the codex-specific handoff block of `context-relay` (`combo.ts:2143–2183`), which #5168 left documented-but-untested because it requires a `codex` connection. All seams (`fetchCodexQuota`, handoff generation, session relay) are mocked deterministically without live infra. ([#5195](https://github.com/diegosouzapw/OmniRoute/pull/5195)) @@ -1091,7 +1794,7 @@ _In development — bullets added per PR; finalized at release._ ## [3.8.38] — 2026-06-27 -### ✨ New Features +### ✨ 新功能 - **feat(sidebar): colored menu icons** — sidebar menu icons now render with a per-item accent color: curated colors for known items (`SIDEBAR_ICON_ACCENTS`) plus a deterministic hash-based fallback (`getSidebarIconAccent`) so every item gets a stable, distinct color across sessions. ([#3812](https://github.com/diegosouzapw/OmniRoute/pull/3812) — thanks @rafacpti23) - **feat(providers): add Factory (factory.ai) as a subscription gateway provider** — `factory` (Factory Droids' hosted gateway) is now a first-class routing provider on the OpenAI-compatible `https://api.factory.ai/v1` endpoint with Bearer apikey auth; the key is supplied from the Dashboard connection (not env). ([#5065](https://github.com/diegosouzapw/OmniRoute/pull/5065) — thanks @KooshaPari) @@ -1114,8 +1817,9 @@ _In development — bullets added per PR; finalized at release._ - **feat(mcp): web-session robustness — cookie dedup + browser-pool observability** — the MCP web-session path now de-duplicates cookies when (re)hydrating a session (avoiding conflicting duplicate `Cookie` headers) and exposes browser-pool observability (pool size / in-use / acquisition metrics) for the headless web providers. ([#5121](https://github.com/diegosouzapw/OmniRoute/pull/5121), builds on [#3368](https://github.com/diegosouzapw/OmniRoute/issues/3368)) - **feat(compression): Ionizer engine — lossy JSON-array sampling reversible via CCR** — a new compression engine that down-samples large JSON arrays to a representative subset and records a Compact Change Representation (CCR) so the omitted rows can be reconstructed, trading exactness for a large token reduction on tabular/array-heavy payloads. ([#5148](https://github.com/diegosouzapw/OmniRoute/pull/5148)) -### 🔧 Bug Fixes +### 🔧 錯誤修正 +- **fix(sse): resolve nameless deepseek-web `` blocks via parameter-schema match** — when `chat.deepseek.com` emits a `` block with no tag suffix, no `` child, and no JSON body (only `` children), every existing name-resolution path returned `null` and the raw XML leaked to the client instead of being converted to a `tool_call`. `extractCall` now falls back to a conservative schema-based match: if the extracted parameter names are a subset of exactly one requested tool's declared schema keys, that tool name is used; zero or ambiguous (>1) schema matches still return `null` so no calls are misattributed. ([#5154](https://github.com/diegosouzapw/OmniRoute/issues/5154)) - **fix(proxy): make the SOCKS5 handshake timeout operator-tunable (`SOCKS_HANDSHAKE_TIMEOUT_MS`)** — under high concurrency against a single residential gateway host, the SOCKS5 connect handshake could exceed the hardcoded 10s even though the proxy was reachable, surfacing as a false `[Proxy Fast-Fail] Proxy unreachable` (the pool size is already tunable via `OMNIROUTE_PROXY_DISPATCHER_CONNECTIONS`). The handshake timeout now reads `SOCKS_HANDSHAKE_TIMEOUT_MS` (default unchanged at `10000`, capped at `120000`) so a concurrency-heavy deployment can raise it without a code change. Mitigation for #5109 (the full concurrency-100 collapse still needs the reporter's live load-test confirmation). ([#5109](https://github.com/diegosouzapw/OmniRoute/issues/5109)) - **fix(api): resolve `GET /v1/models/{id}` case-insensitively** — clients that normalise the model id (e.g. OpenCode requesting `minimax/minimax-m3` for the canonical catalog entry `minimax/MiniMax-M3`) missed the single-model lookup, which is case-sensitive, and fell back to advertising `context_length: 0`. `findModelById` now prefers an exact-case match and falls back to a case-insensitive match, so the real entry (and its context window) is returned regardless of casing. ([#5082](https://github.com/diegosouzapw/OmniRoute/issues/5082)) - **fix(services): embed WS proxy honours `LIVE_WS_HOST`; reject empty `messages` early** — two headless/Docker deployment fixes (#5110). The embed WebSocket proxy (`:20131`) only read `EMBED_WS_PROXY_HOST`, so behind a reverse proxy/tunnel it stayed bound to `127.0.0.1` even with `LIVE_WS_HOST=0.0.0.0` set and the Live dashboard showed "WebSocket disconnected"; it now falls back to `LIVE_WS_HOST` (default still loopback). Separately, a request with an explicitly empty `messages: []` array was forwarded upstream and bounced back as a confusing raw `400/502`; `handleChat` now rejects it up front with a clear `messages: at least one message is required` (Responses-API `input` requests are unaffected). ([#5110](https://github.com/diegosouzapw/OmniRoute/issues/5110)) @@ -1165,7 +1869,7 @@ _In development — bullets added per PR; finalized at release._ - **fix(security): exact-host Anthropic `baseUrl` check** — the Anthropic base-URL guard used a substring match that a crafted host could partially satisfy; it now requires an exact host match (resolves CodeQL `js/incomplete-url-substring-sanitization` alert #674). ([#5130](https://github.com/diegosouzapw/OmniRoute/pull/5130)) -### 📝 Maintenance +### 📝 維護 - **refactor(store): remove dead legacy store modules** — salvaged cleanup of unused legacy store code. ([#5138](https://github.com/diegosouzapw/OmniRoute/pull/5138) — thanks @JxnLexn / @diegosouzapw) - **test(combo): deterministic routing-decision matrix for all 17 strategies** — a deterministic E2E matrix pins the routing decision of every combo strategy. ([#5146](https://github.com/diegosouzapw/OmniRoute/pull/5146)) @@ -1176,7 +1880,7 @@ _In development — bullets added per PR; finalized at release._ ## [3.8.37] — 2026-06-26 -### ✨ New Features +### ✨ 新功能 - **feat(providers):** add DGrid AI gateway provider — OpenAI-compatible gateway at `api.dgrid.ai/v1` (alias `dgrid`, API-key auth, passthrough models). Free router tier (10 RPM / 100 RPD); a $5 lifetime top-up raises limits to 20 RPM / 1,000 RPD. ([#4931](https://github.com/diegosouzapw/OmniRoute/pull/4931) — thanks @dgridOP) @@ -1204,7 +1908,7 @@ _In development — bullets added per PR; finalized at release._ - **feat(api):** add the `MiniMax-M3` pricing row (canonical + lowercase alias) so the new MiniMax default model gets accurate per-request cost accounting instead of falling back to a zero/default rate. ([#4814](https://github.com/diegosouzapw/OmniRoute/pull/4814) — thanks @octo-patch) -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **fix(sse):** dense, deterministic `response.output` ordering in `response.completed` — items are now sorted by their actual `output_index` (via a recorded-as-emitted accumulator + stable sort) instead of being rebuilt from unordered state dicts; `normalizeOutputIndex` replaces fragile `parseInt` calls for robust index coercion; superseded tool calls (replaced at the same index mid-stream) are excluded from the final output array. ([#4906](https://github.com/diegosouzapw/OmniRoute/pull/4906) — thanks @Marco9113) @@ -1316,7 +2020,7 @@ _In development — bullets added per PR; finalized at release._ - **fix(oauth):** classify `/api/oauth/cursor/auto-import` as a local-only route in the route guard, so the loopback-enforced process-spawning endpoint can't be reached through a tunneled/leaked JWT (Hard Rule #17). ([#5070](https://github.com/diegosouzapw/OmniRoute/pull/5070) — thanks @diegosouzapw) -### 📝 Maintenance +### 📝 維護 - **chore(ci):** harden the release flow — decouple the Quality Ratchet from coverage-shard flakes (`if: !cancelled()` + `--allow-missing`), add fast-path drift gates (`check:complexity`, `check:cognitive-complexity`, `check:pack-policy`, `check:build-scope`), and raise the default build heap to 8 GB. ([#5054](https://github.com/diegosouzapw/OmniRoute/pull/5054) — thanks @diegosouzapw) @@ -1332,7 +2036,7 @@ _In development — bullets added per PR; finalized at release._ ## [3.8.36] — 2026-06-25 -### ✨ New Features +### ✨ 新功能 **Quota-Share system** @@ -1370,7 +2074,7 @@ _In development — bullets added per PR; finalized at release._ --- -### 🔧 Bug Fixes +### 🔧 錯誤修正 **Translator** @@ -1470,10 +2174,11 @@ _In development — bullets added per PR; finalized at release._ - **fix(security):** do not trust the loopback socket as local-only when the server is behind a reverse proxy, closing a potential auth bypass path. ([#4632](https://github.com/diegosouzapw/OmniRoute/pull/4632)) - **fix(security):** validate the Kiro region parameter to prevent SSRF via crafted region strings (GHSA-6mwv-4mrm-5p3m). ([#4629](https://github.com/diegosouzapw/OmniRoute/pull/4629)) - **fix(copilot):** replace `execSync` shell interpolation with `execFile` in the `runOmniRouteCli` tool to prevent command injection. The user-supplied command is now split into an argv array and passed to `execFile` (no shell), so shell metacharacters are treated as literal text; error output is routed through `sanitizeErrorMessage()`. ([#5024](https://github.com/diegosouzapw/OmniRoute/pull/5024) — thanks @hamsa0x7) +- **fix(db):** replace `Math.random` with `crypto.randomUUID` for database ID generation, removing a non-cryptographic source of collision/predictability in generated identifiers. ([#5026](https://github.com/diegosouzapw/OmniRoute/pull/5026) — thanks @hamsa0x7) --- -### 📝 Maintenance +### 📝 維護 **God-file decomposition (continued, #3501)** @@ -1507,12 +2212,12 @@ _In development — bullets added per PR; finalized at release._ ## [3.8.35] — 2026-06-23 -### ✨ New Features +### ✨ 新功能 - **Adaptive context compression (Phase 4)**: a four-layer compression upgrade landed across stacked PRs — an **Output Styles** registry (`terse-prose` / `less-code` / `terse-cjk`) ([#4694](https://github.com/diegosouzapw/OmniRoute/pull/4694) — thanks @diegosouzapw), an opt-in **SLM `ultra` tier** (two-tier LLMLingua with heuristic fallback) ([#4707](https://github.com/diegosouzapw/OmniRoute/pull/4707) — thanks @diegosouzapw), a **context-budget adaptive dial** (reserve-output ladder + floor) ([#4716](https://github.com/diegosouzapw/OmniRoute/pull/4716) — thanks @diegosouzapw), and an **offline evaluation harness** (PII-gated corpus, self-test judge, gold-grader, real-pipeline runner behind a `ModelClient` seam) ([#4720](https://github.com/diegosouzapw/OmniRoute/pull/4720) — thanks @diegosouzapw). All four layers share a single `CompressionRunTelemetry` contract. - **Redoc-rendered API docs**: a consolidated OpenAPI spec now lives at `docs/openapi.yaml` and is served as interactive Redoc documentation at `/api/docs`. ([#4781](https://github.com/diegosouzapw/OmniRoute/pull/4781) — thanks @KooshaPari / @diegosouzapw) -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **db-backups**: make the database-import size cap configurable via `OMNIROUTE_DB_IMPORT_MAX_MB` (default 100 MB, 4 GB ceiling) so backups larger than 100 MB can be restored; error message now points to the env var and to VACUUM ([#4757](https://github.com/diegosouzapw/OmniRoute/pull/4757) — closes #4719, thanks @diegosouzapw). - **Onboarding**: add the missing `onboarding.tiers` step-title translation so the setup wizard no longer crashes with `MISSING_MESSAGE: onboarding.tiers` ([#4755](https://github.com/diegosouzapw/OmniRoute/pull/4755) — closes #4698, thanks @diegosouzapw). @@ -1520,10 +2225,11 @@ _In development — bullets added per PR; finalized at release._ - **Dashboard**: remove the dead, unconditional `useLiveRequests()` call from `HomePageClient.tsx` — it crashed the `/home` page in production builds with `ReferenceError: useLiveRequests is not defined` (#4759, #4745) and opened the live-dashboard WebSocket even when Provider Topology was hidden (#4596). The live feed remains owned by the settings-gated `HomeProviderTopologySection` ([#4761](https://github.com/diegosouzapw/OmniRoute/pull/4761) — thanks @diegosouzapw). - **Providers dashboard**: dedupe provider nodes by id when adding a compatible provider (`upsertProviderNodeById`) so the same provider can no longer appear twice and no-op adds don't invalidate the compatible-provider memo ([#4768](https://github.com/diegosouzapw/OmniRoute/pull/4768) — closes #4746, thanks @diegosouzapw). - **Storage VACUUM**: the scheduled VACUUM job now follows the Storage page settings (`scheduledVacuum` / `vacuumHour`) as the single source of truth; the legacy env-flag control path was removed ([#4726](https://github.com/diegosouzapw/OmniRoute/pull/4726) — thanks @rdself). +- **Storage SQLite tuning**: `Cache Size` is now a positive KiB setting (for example, `16384`) that applies to SQLite as `PRAGMA cache_size = -16384`; Page Size and Cache Size changes are applied to the live database instead of being persisted only in the settings table. - **Tiers**: no-auth providers are now counted as free, and the free-tier filter returns an empty set instead of falling through to every provider ([#4753](https://github.com/diegosouzapw/OmniRoute/pull/4753) — thanks @megamen32 / @diegosouzapw). - **Combos**: auto-promote `zeroLatencyOptimizationsEnabled` so legacy configs (pre-3.8.33 `fallbackCompressionMode="lite"`) round-trip cleanly on the first GUI edit ([#4774](https://github.com/diegosouzapw/OmniRoute/pull/4774) — thanks @KooshaPari / @diegosouzapw). -### 📝 Maintenance +### 📝 維護 - **chatCore (#3501)**: continued the incremental decomposition of `executeProviderRequest` and the streaming/non-streaming hooks into pure leaf modules — top-level helpers + 6 pure leaves ([#4571](https://github.com/diegosouzapw/OmniRoute/pull/4571)), `resolveExecutorWithProxy` + `getExecutionCredentials` ([#4646](https://github.com/diegosouzapw/OmniRoute/pull/4646)), Claude message transforms ([#4708](https://github.com/diegosouzapw/OmniRoute/pull/4708)), `persistAttemptLogs` ([#4717](https://github.com/diegosouzapw/OmniRoute/pull/4717)), `stageTrace` + `compressionUsageReceipt` ([#4721](https://github.com/diegosouzapw/OmniRoute/pull/4721)), `prepareUpstreamBody` ([#4730](https://github.com/diegosouzapw/OmniRoute/pull/4730)), parse + non-streaming usage-stats ([#4762](https://github.com/diegosouzapw/OmniRoute/pull/4762)), `recordContextEditingTelemetryHook` ([#4779](https://github.com/diegosouzapw/OmniRoute/pull/4779)), `scheduleQuotaShareConsumption` ([#4780](https://github.com/diegosouzapw/OmniRoute/pull/4780)), `emitRequestGamificationEvent` ([#4776](https://github.com/diegosouzapw/OmniRoute/pull/4776)), `runPluginOnResponseHook` ([#4782](https://github.com/diegosouzapw/OmniRoute/pull/4782)), `scheduleStreamingQuotaShareConsumption` ([#4784](https://github.com/diegosouzapw/OmniRoute/pull/4784)), `recordCompressionCacheStats` ([#4792](https://github.com/diegosouzapw/OmniRoute/pull/4792)), `writeCavemanOutputAnalytics` ([#4794](https://github.com/diegosouzapw/OmniRoute/pull/4794)), `recordStreamingUsageStats` ([#4791](https://github.com/diegosouzapw/OmniRoute/pull/4791)), and `recordStreamingCost` ([#4790](https://github.com/diegosouzapw/OmniRoute/pull/4790)). (thanks @diegosouzapw) - **Quality**: expand `check:release-green` to reproduce the full release-PR gate set locally ([#4758](https://github.com/diegosouzapw/OmniRoute/pull/4758) — thanks @diegosouzapw). @@ -1537,7 +2243,7 @@ _In development — bullets added per PR; finalized at release._ ## [3.8.34] — 2026-06-23 -### ✨ New Features +### ✨ 新功能 - **feat(executors): Microsoft 365 Copilot pure framing + connection helpers** — adds the request/response framing and connection helpers to support `m365.cloud.microsoft/chat` for individual M365 plans. ([#4696](https://github.com/diegosouzapw/OmniRoute/pull/4696) — thanks @skyzea1 / @diegosouzapw) - **feat(compression): per-request `x-omniroute-compression` header (Phase 3)** — a request header now overrides the compression plan with the highest precedence (`request-header > routing > profile > auto-trigger > Default > off`), accepting `off` / `default` / `engine:` / ``. The response echoes `X-OmniRoute-Compression: ; source=`. ([#4645](https://github.com/diegosouzapw/OmniRoute/pull/4645) — thanks @diegosouzapw) @@ -1549,6 +2255,8 @@ _In development — bullets added per PR; finalized at release._ ### 🐛 Fixed +- **fix(tier): noAuth providers count as free; `auto/:free` returns an empty pool when no free candidate matches** — `freeProviders` is now the union of the legacy explicit list and every chat-tier `noAuth` provider derived from `NOAUTH_PROVIDERS` (so opencode / mimocode / duckduckgo-web are correctly classified free), the task-fitness lookup inherits a base model's `arena_elo` for its `-free` variant, and the `auto/:` filter no longer silently falls back to the full pool — a `:free` request that matches no connected free model returns empty instead of billing a paid model (opt back into the legacy fallback with `OMNIROUTE_AUTO_FREE_FALLBACK_TO_FULL_POOL=true`). Corrupted/invalid `tier_config` rows now log a structured warning and fall back to defaults instead of throwing. ([#4753](https://github.com/diegosouzapw/OmniRoute/pull/4753), [#4517](https://github.com/diegosouzapw/OmniRoute/issues/4517) — thanks @megamen32) +- **fix(db): scheduled VACUUM follows Storage settings** — the SQLite VACUUM scheduler now uses the existing Storage page `scheduledVacuum` / `vacuumHour` configuration as its single source of truth, refreshes immediately when those settings are saved, and no longer exposes a separate environment-variable control path. - **fix(db): scheduled cleanup actually runs + queries target the real tables (DB-bloat / OOM)** — `runAutoCleanup` was never scheduled, so retention cleanup never executed and tables (`compression_analytics`, `usage_history`, …) grew unbounded into multi-GB SQLite files driving high RSS. Worse, several cleanup queries referenced wrong table/column names (`call_logs.created_at`→`timestamp`, `compression_analytics.created_at`→`timestamp`, `mcp_audit_log`→`mcp_tool_audit`, `a2a_events`→`a2a_task_events`, `memory_entries`→`memories`), so even a manual run silently no-op'd or errored. Fixed the five queries to match the real schema, added `cleanupProxyLogs`, and wired a `startCleanupScheduler` (startup + every 6h, VACUUM after deletes) into `server-init` alongside the existing budget-reset and reasoning-cache jobs. ([#4691](https://github.com/diegosouzapw/OmniRoute/pull/4691), extracted from [#4428](https://github.com/diegosouzapw/OmniRoute/pull/4428) — thanks @oyi77 / @diegosouzapw) - **fix(routing): include all noAuth models in auto-combos + add reka-flash + best-free template** — noAuth provider models are no longer skipped when building auto-combos, `reka-flash` is registered, and a `best-free` combo template is added. ([#4621](https://github.com/diegosouzapw/OmniRoute/pull/4621) — thanks @oyi77) - **fix: noAuth provider validation + Kimi executor routing** — corrects noAuth provider membership checks and removes a mis-routed Kimi alias. (closes #4620) ([#4699](https://github.com/diegosouzapw/OmniRoute/pull/4699) — thanks @oyi77) @@ -1582,7 +2290,7 @@ _In development — bullets added per PR; finalized at release._ - **fix(dashboard): surface manual config CTA when Open Claw CLI auto-detect fails** — shows a manual-config call-to-action on the Open Claw CLI card when auto-detection fails. ([#4562](https://github.com/diegosouzapw/OmniRoute/pull/4562) — thanks @diegosouzapw) - **fix(oauth): update Qwen OAuth URLs from `chat.qwen.ai` to `qwen.ai`** — refreshes the Qwen OAuth endpoints (port of decolua/9router#683). ([#4561](https://github.com/diegosouzapw/OmniRoute/pull/4561) — thanks @diegosouzapw) -### 📝 Maintenance +### 📝 維護 - **refactor(imageGeneration): extract 8 provider families to co-located files** — splits the image-generation module into eight co-located per-provider files with no behavioral change. ([#4609](https://github.com/diegosouzapw/OmniRoute/pull/4609) — thanks @KooshaPari) - **deps: bump production + development groups; migrate js-yaml to v5 (ESM)** — dependency bumps plus a `js-yaml` v4→v5 migration to the ESM-only namespace import. ([#4697](https://github.com/diegosouzapw/OmniRoute/pull/4697) — thanks @diegosouzapw) @@ -1591,20 +2299,165 @@ _In development — bullets added per PR; finalized at release._ --- -## [3.8.33] — TBD +## [3.8.33] — 2026-06-21 -_See English CHANGELOG for v3.8.33 details._ +### ✨ 新功能 -## [3.8.32] — TBD +- **feat(combo): nested combo-ref execution (`nestedComboMode: execute`)** — selection strategies can now treat a combo-reference step as a black box, executing the referenced combo as a single unit instead of flattening its targets. ([#4537](https://github.com/diegosouzapw/OmniRoute/pull/4537) — thanks @adivekar-utexas) +- **feat(combo): sticky weighted selection limit with exhaustion-aware renormalization** — weighted strategies gain a configurable sticky-selection limit; once a target is exhausted, remaining weights renormalize so traffic is redistributed correctly. ([#4489](https://github.com/diegosouzapw/OmniRoute/pull/4489) — thanks @adivekar-utexas) +- **feat(combos): provider-wildcard expansion in combo steps** — a combo step may now reference a whole provider via wildcard and have it expand to that provider's models at resolution time. ([#4545](https://github.com/diegosouzapw/OmniRoute/pull/4545) — thanks @Rahulsharma0810) +- **feat(compression): Phase 2 — named profiles + active selector** — the compression settings panel becomes the single source of truth via a single active-profile selector (Default panel vs a named combo) wired into the runtime. ([#4521](https://github.com/diegosouzapw/OmniRoute/pull/4521) — thanks @diegosouzapw) +- **feat(sse): route `web_search` requests to a configured model** — CCR-style webSearch scenario: requests carrying a `web_search*` tool can be routed to a dedicated `webSearchRouteModel`, configurable from the Routing tab. ([#4509](https://github.com/diegosouzapw/OmniRoute/pull/4509) — thanks @shafqatevo / @diegosouzapw) +- **feat(mcp): `omniroute_web_fetch` tool for URL content extraction** — new MCP tool that fetches and extracts the content of a URL. ([#4510](https://github.com/diegosouzapw/OmniRoute/pull/4510) — thanks @ponkcore) +- **feat(models): qualify duplicate model names with their provider prefix** — when two providers expose a same-named model, the catalog now disambiguates each with its provider prefix. ([#4516](https://github.com/diegosouzapw/OmniRoute/pull/4516) — thanks @Rahulsharma0810) +- **feat(translator): accept OpenAI audio input parts in Gemini translation** — `input_audio` message parts are now translated through to Gemini. ([#4434](https://github.com/diegosouzapw/OmniRoute/pull/4434) — thanks @diegosouzapw) +- **feat(webhooks): enrich Telegram request notifications** — Telegram webhook payloads carry richer request context. ([#4524](https://github.com/diegosouzapw/OmniRoute/pull/4524) — thanks @mppata-glitch) +- **feat(bazaarlink): add `authHint` to the existing APIKEY_PROVIDERS entry** — surfaces the auth hint for the bazaarlink provider. ([#4522](https://github.com/diegosouzapw/OmniRoute/pull/4522) — thanks @adivekar-utexas) +- **feat(usage): API-key USD quota percent + reset hints, weekly-window cutoff** — usage dashboard surfaces API-key USD quota percentage and reset hints, honoring the weekly window cutoff. ([#4398](https://github.com/diegosouzapw/OmniRoute/pull/4398) — thanks @Witroch4) +- **feat(usage): surface Codex code-review weekly window + `additional_rate_limits` fallback** — exposes the Codex code-review weekly window and falls back to `additional_rate_limits` when present. ([#4494](https://github.com/diegosouzapw/OmniRoute/pull/4494) — thanks @diegosouzapw) +- **feat(dashboard): per-provider dropdown filter on the quota dashboard** — filter the quota dashboard by provider. ([#4495](https://github.com/diegosouzapw/OmniRoute/pull/4495) — thanks @diegosouzapw) +- **feat(dashboard): inline show/hide toggle for API keys on the API Manager page** ([#4505](https://github.com/diegosouzapw/OmniRoute/pull/4505) — thanks @diegosouzapw) +- **feat(dashboard): toggle-style model deselection in the combo builder modal** ([#4498](https://github.com/diegosouzapw/OmniRoute/pull/4498) — thanks @diegosouzapw) +- **feat(dashboard): Done button in the model picker for combo creation** ([#4496](https://github.com/diegosouzapw/OmniRoute/pull/4496) — thanks @diegosouzapw) +- **feat(providers): expose `gpt-4o` on the built-in GitHub Copilot (`gh`) provider** ([#4487](https://github.com/diegosouzapw/OmniRoute/pull/4487) — thanks @diegosouzapw) +- **feat(pricing): default pricing for the Qwen coder-model on the `qw` provider** ([#4488](https://github.com/diegosouzapw/OmniRoute/pull/4488) — thanks @diegosouzapw) -_See English CHANGELOG for v3.8.32 details._ +### 🔧 錯誤修正 + +- **fix(telemetry): back off the live-WS event bridge so a missing sidecar stops spamming ProxyFetch errors** — in single-port deployments the live-dashboard sidecar (port 20129) is not running, but `forwardDashboardEventToLiveWs` POSTed to it on every compression event. Since the global `fetch` is `proxyFetch`, each `ECONNREFUSED` logged a `[ProxyFetch] Undici dispatcher failed` warning (~272× in 42 min). The forwarder now backs off after consecutive failures (60s cooldown, lazy recovery) and clears the backoff on success, so a missing sidecar no longer floods the logs. ([#4604](https://github.com/diegosouzapw/OmniRoute/issues/4604) — thanks @FikFikk) +- **fix(api): resolve a compatible provider node by base type, not only exact id** — connection→node resolution now matches on the bare derived node type when the exact id isn't found and the match is unambiguous (ambiguous → 404), via a pure `providerNodeSelect` helper. ([#4576](https://github.com/diegosouzapw/OmniRoute/pull/4576) — thanks @aleksesipenko / @diegosouzapw) +- **fix(cli): supervisor restarts on spontaneous exit-0 (OOM cgroup) + waits for port before respawn** — a child that exits 0 because the cgroup OOM-killer reaped it is now restarted (not treated as a clean shutdown), the restart reset window widened 30s→60s, and the supervisor waits for the port to be free before respawning. ([#4578](https://github.com/diegosouzapw/OmniRoute/pull/4578) — thanks @oyi77 / @diegosouzapw) +- **fix(combo): attribute lockout decay & success telemetry to the dynamically-selected connection** — on the combo success path the actual connection chosen by dynamic account-selection is read from the `X-OmniRoute-Selected-Connection-Id` response header (instead of the often-empty static `target.connectionId`), so model-lockout decay, `recordProviderSuccess`, LKGP and success/failure telemetry attribute to the right connection on both the priority and round-robin paths. The pre-screen "unavailable" snapshot is also no longer a permanent skip — availability is re-checked on each retry since connection cooldowns can expire mid-request. ([#4550](https://github.com/diegosouzapw/OmniRoute/pull/4550) — thanks @Chewji9875) +- **fix(auto): enforce the quota cutoff before scoring (opt-in)** — auto-routing now evaluates a hard quota cutoff in `buildAutoCandidates` to drop low-quota candidates before scoring, with a 429 guard when all candidates fall below cutoff. The cutoff is **opt-in** behind `QuotaPreflightSettings.enabled` (default OFF via `QUOTA_PREFLIGHT_CUTOFF_ENABLED`), so default behavior is unchanged. ([#4483](https://github.com/diegosouzapw/OmniRoute/pull/4483) — thanks @megamen32) +- **fix(antigravity): reasoning/thinking models no longer 400 with `oneOf at '/' not met`** — the Cloud Code envelope passthrough also leaked the Claude/OpenAI-native thinking fields (`thinking`, `reasoning_effort`, `reasoning`, `enable_thinking`, `thinking_budget`) the unified thinking adapter sets at the body root; Google rejected them with `400 Bad input: oneOf at '/' not met`. The whole thinking family is now stripped before the envelope is built; Gemini's own `generationConfig.thinkingConfig` is unaffected. ([#4485](https://github.com/diegosouzapw/OmniRoute/pull/4485) — port from 9router#1926, thanks @theseven99 / @diegosouzapw) +- **fix(integration): restore the codex and memory pipeline contracts** — realigns the CLI fingerprint + memory-tools contracts so the codex and memory pipelines pass their integration checks again. ([#4474](https://github.com/diegosouzapw/OmniRoute/pull/4474) — thanks @KooshaPari) +- **fix(sse): RTK must preserve `cache_control`-marked `tool_result` blocks** — reasoning-token-keeping no longer drops tool_result blocks that carry a `cache_control` marker. ([#4560](https://github.com/diegosouzapw/OmniRoute/pull/4560) — thanks @diegosouzapw) +- **fix(auto-combo): respect model visibility (`isHidden`) in the auto-combo candidate pool** — hidden models are excluded from auto-combo candidates. ([#4558](https://github.com/diegosouzapw/OmniRoute/pull/4558) — thanks @herjarsa) +- **fix(dashboard): avoid overlapping provider health polls** — guards against concurrent provider-health poll cycles overlapping. ([#4557](https://github.com/diegosouzapw/OmniRoute/pull/4557) — thanks @KooshaPari) +- **fix(dashboard): make the API Manager key table usable on mobile** ([#4556](https://github.com/diegosouzapw/OmniRoute/pull/4556) — thanks @janeza2) +- **fix(executors): decode Composer/Cursor ``-marked visible output** — visible text wrapped in Cursor Composer's `` markers is now decoded correctly. ([#4554](https://github.com/diegosouzapw/OmniRoute/pull/4554) — thanks @diegosouzapw) +- **fix(oauth): improve Cursor auto-import reliability on macOS** ([#4552](https://github.com/diegosouzapw/OmniRoute/pull/4552) — thanks @diegosouzapw) +- **fix(providers/test): probe the real Codex `/responses` endpoint** — connection test hits the actual Codex `/responses` endpoint. ([#4551](https://github.com/diegosouzapw/OmniRoute/pull/4551) — thanks @diegosouzapw) +- **fix(mcp): `webFetchInput` emits `URL is required` for a missing url** — clearer validation error for the web-fetch tool. ([#4541](https://github.com/diegosouzapw/OmniRoute/pull/4541) — thanks @ponkcore / @diegosouzapw) +- **fix(compression): allow `enginesExplicit` through the PUT validation schema** — the compression settings PUT no longer rejects the `enginesExplicit` flag. ([#4532](https://github.com/diegosouzapw/OmniRoute/pull/4532) — thanks @DevEstacion) +- **fix(no-think): normalize provider prefix to canonical in no-think variants** ([#4531](https://github.com/diegosouzapw/OmniRoute/pull/4531) — thanks @Rahulsharma0810) +- **fix(combo): pass `maxCooldownMs` from settings to the `recordModelLockoutFailure` call sites** ([#4530](https://github.com/diegosouzapw/OmniRoute/pull/4530) — thanks @Chewji9875) +- **fix(combo): allow fallback on context-overflow & param-validation 400s; preserve upstream codes** — combo fallback now triggers on recoverable 400s while keeping the original upstream status. ([#4519](https://github.com/diegosouzapw/OmniRoute/pull/4519) — thanks @adivekar-utexas) +- **fix(command-code): cap `max_tokens` per model using the registry `maxOutputTokens`** ([#4518](https://github.com/diegosouzapw/OmniRoute/pull/4518) — thanks @adivekar-utexas) +- **fix(mitm): gate sudo prompts on server platform, not browser UA** ([#4514](https://github.com/diegosouzapw/OmniRoute/pull/4514) — thanks @diegosouzapw) +- **fix(mitm): graceful sudo degradation in slim Docker / non-root containers** ([#4513](https://github.com/diegosouzapw/OmniRoute/pull/4513) — thanks @diegosouzapw) +- **fix(usage): clear auth-expired message for Kiro social-auth accounts** ([#4512](https://github.com/diegosouzapw/OmniRoute/pull/4512) — thanks @diegosouzapw) +- **fix(pricing): default cost rows for Antigravity Gemini 3.5 Flash tiers + `gemini-pro-agent`** ([#4508](https://github.com/diegosouzapw/OmniRoute/pull/4508) — thanks @diegosouzapw) +- **fix(api): dedupe exact-duplicate ids in `/v1/models`** — low-noise model output without alias/canonical duplicates. ([#4506](https://github.com/diegosouzapw/OmniRoute/pull/4506) — thanks @Rahulsharma0810 / @diegosouzapw) +- **fix(dashboard): enable Codex Apply/Reset buttons when the CLI is installed** ([#4504](https://github.com/diegosouzapw/OmniRoute/pull/4504) — thanks @diegosouzapw) +- **fix(dashboard): show API-Key-compatible providers in the Antigravity CLI Tools model picker** ([#4503](https://github.com/diegosouzapw/OmniRoute/pull/4503) — thanks @diegosouzapw) +- **fix(dashboard): migrate ManualConfigModal copy to the shared `useCopyToClipboard` hook** ([#4502](https://github.com/diegosouzapw/OmniRoute/pull/4502) — thanks @diegosouzapw) +- **fix(sse): skip disabled providers in combo fallback** ([#4500](https://github.com/diegosouzapw/OmniRoute/pull/4500) — thanks @diegosouzapw) +- **fix(usage): parse numeric-string quota reset timestamps as Unix sec/ms** ([#4493](https://github.com/diegosouzapw/OmniRoute/pull/4493) — thanks @diegosouzapw) +- **fix(db): scheduled VACUUM + persist `lastVacuumAt`** — a new `vacuumScheduler.ts` persists the last run timestamp and last error to the `key_value` table (migration 102) and feeds the database settings panel; wired into the Next.js lifecycle (default 24h, window 02:00–04:00 local). The initial env-flag control path from this entry is superseded in v3.8.34 by the Storage page settings. ([#4480](https://github.com/diegosouzapw/OmniRoute/pull/4480) — thanks @KooshaPari / @oyi77) +- **perf(quota): stop writing redundant `quota_snapshots` rows from idle connections** — the 60s background refresh persisted a snapshot for every window of every connection regardless of change, generating 400K+ rows/day from idle accounts. `setQuotaCache` now skips the write when a window's `remaining_percentage`/`is_exhausted` is unchanged from the last cached observation; the first observation and every real change still persist. ([#4565](https://github.com/diegosouzapw/OmniRoute/pull/4565), [#4438](https://github.com/diegosouzapw/OmniRoute/issues/4438) — thanks @oyi77) + +### 🔒 Security + +- **fix(sse): crypto-secure RNG for combo/deck load-balancing selection** — replaces `Math.random()` with a crypto-secure source in the combo/deck weighted-selection path. ([#4455](https://github.com/diegosouzapw/OmniRoute/pull/4455) — thanks @diegosouzapw) + +### 📝 維護 + +- **perf(dashboard): shrink provider assets + fix the usage rollup cutoff** — recompresses oversized provider images (nanobot/picoclaw/zeroclaw) and adds a `check:provider-assets` gate, plus a usage-analytics rollup cutoff fix. ([#4464](https://github.com/diegosouzapw/OmniRoute/pull/4464) — thanks @KooshaPari) +- **refactor(chatCore): extract pure leaves from `chatCore.ts`** — incremental decomposition of the chat-core handler into pure, individually-testable leaves (system-role extraction, upstream-header build, failure usage-record builder, key-health, request-format, claude-effort, target-format, Background-Task-Redirect decision, Codex quota-state persistence). ([#4548](https://github.com/diegosouzapw/OmniRoute/pull/4548), [#4547](https://github.com/diegosouzapw/OmniRoute/pull/4547), [#4544](https://github.com/diegosouzapw/OmniRoute/pull/4544), [#4538](https://github.com/diegosouzapw/OmniRoute/pull/4538), [#4526](https://github.com/diegosouzapw/OmniRoute/pull/4526), [#4492](https://github.com/diegosouzapw/OmniRoute/pull/4492) — #3501, thanks @diegosouzapw) +- **chore(i18n): remove unused config helpers** ([#4482](https://github.com/diegosouzapw/OmniRoute/pull/4482) — thanks @KooshaPari) +- **chore(quality): reconcile quality baselines (complexity, cognitive-complexity, file-size) across the cycle** ([#4579](https://github.com/diegosouzapw/OmniRoute/pull/4579), [#4570](https://github.com/diegosouzapw/OmniRoute/pull/4570), [#4543](https://github.com/diegosouzapw/OmniRoute/pull/4543), [#4542](https://github.com/diegosouzapw/OmniRoute/pull/4542), [#4535](https://github.com/diegosouzapw/OmniRoute/pull/4535), [#4534](https://github.com/diegosouzapw/OmniRoute/pull/4534), [#4529](https://github.com/diegosouzapw/OmniRoute/pull/4529), [#4528](https://github.com/diegosouzapw/OmniRoute/pull/4528), [#4523](https://github.com/diegosouzapw/OmniRoute/pull/4523) — thanks @diegosouzapw) + +--- + +## [3.8.32] — 2026-06-20 + +### ✨ 新功能 + +- **feat(dashboard): inline show/hide toggle for API keys on the API Manager page** — each row in the API keys list now exposes an eye / eye-off button next to the masked key. Clicking it lazy-fetches the full key via the existing `/api/keys/{id}/reveal` endpoint (so the policy gate is unchanged), caches it client-side, and renders the full value inline; clicking again hides it. The toggle only appears when `allowKeyReveal` is true (server policy), so an installation that disables reveal still sees a locked stub. Reuses the existing i18n keys `apiManager.showKey` / `apiManager.hideKey` already shipped in every locale, and clears the cached reveal when the key is deleted. Inspired-by: toanalien. +- **feat(oauth): import accounts from CLIProxyAPI** — Settings → CLIProxyAPI now has an "Import accounts" button that reads the OAuth accounts CLIProxyAPI already saved in `~/.cli-proxy-api/` and imports them as OmniRoute connections, so you don't have to log into every account individually. CLIProxyAPI's unified auth-file format is parsed by `type` discriminator and the supported account types (Gemini, Codex, Claude/Anthropic, Antigravity, Qwen, Kimi) are upserted; unknown types are skipped. The preview never exposes tokens to the client. (thanks @powellnorma) +- **feat(routing): opt-in setting to echo the requested alias/combo name in the response model field** — Settings → Routing now has an "Echo requested model name in responses" toggle (default off). When enabled, the response `model` field (non-streaming and every streamed SSE chunk) reports the alias or combo name the client requested instead of the upstream model name, so strict clients such as Claude Desktop — which reject a response whose `model` does not match the request with a 401 — work with aliases and combos. (thanks @thaiphuong1202) +- **feat(providers): expand the openai and gemini direct registries with first-class variants already known elsewhere** — the `openai` provider entry now exposes `gpt-4.1-mini`, `gpt-4.1-nano`, `o3-mini`, and `o4-mini` (the latter two carry `REASONING_UNSUPPORTED` like `o3`), and the `gemini` entry now exposes `gemini-2.0-flash-lite` and `gemini-3-flash-lite-preview`. These models were already first-class throughout sibling subsystems (cost estimator, task fitness, free-model catalog, multiple aggregator registries) but happened to be missing from the direct openai/gemini namespaces. Embedding/TTS/image-gen models stay in their dedicated registries (`embeddingRegistry.ts`, `audioRegistry.ts`, `imageRegistry.ts`); legacy ids OmniRoute curated out (o1, gpt-4-turbo, …) are not restored. (thanks @East-rayyy) +- **feat(translator): OpenAI SSE → Gemini SSE conversion for `/v1beta/models/{model}:streamGenerateContent`** — the `@google/genai` SDK (Gemini CLI) always calls `:streamGenerateContent?alt=sse` for chat and expects Gemini SSE chunks (no `[DONE]` sentinel — the stream just closes). The v1beta route was forwarding OpenAI SSE from `handleChat` unchanged, so the SDK crashed on the OpenAI `[DONE]` line with `SyntaxError: Unexpected token 'D', "[DONE]" is not valid JSON`. A new `transformOpenAISSEToGeminiSSE()` (in `open-sse/translator/response/openai-to-gemini-sse.ts`) rewrites each OpenAI delta into `candidates[].content.parts[]`, maps `finish_reason` → `finishReason` (STOP / MAX_TOKENS / SAFETY), attaches `usageMetadata` + `modelVersion` on the final chunk, and surfaces `reasoning_content` as `{ thought: true }` parts for thinking models. The non-streaming `:generateContent` action gets a sibling `convertOpenAIResponseToGemini()` for the JSON path. Streaming intent is now keyed off the URL action suffix (canonical Gemini convention) rather than the non-standard `generationConfig.stream` body field. (thanks @SteelMorgan) +- **feat(compression): unified compression configuration panel (Phase 1)** — `/dashboard/context/settings` is now the single source of truth for compression: a master toggle plus per-engine on/off and level controls, with the dispatch pipeline derived from a stored `engines` map on `CompressionConfig`. A gate (`enginesExplicit`) ensures the new map only drives dispatch when an `engines` row was actually saved from the panel, so legacy/backfilled installs (the seeded default combo from migrations 042/043) keep their existing `defaultMode` behavior unchanged. The default-combo and per-engine routes are shimmed (410). ([#4432](https://github.com/diegosouzapw/OmniRoute/pull/4432) — thanks @diegosouzapw) +- **feat(mcp): register the web-session pool observability tools** — the `poolTools` MCP tool set (web-session pool stats/health) was defined but never wired into `createMcpServer()`, so it was dead. It is now registered in `server.ts` with `withScopeEnforcement` against the typed `read:health` / `write:resilience` scopes (no enum inflation), giving MCP clients visibility into the pooled web-session lifecycle. ([#4399](https://github.com/diegosouzapw/OmniRoute/pull/4399), [#3368](https://github.com/diegosouzapw/OmniRoute/issues/3368) — thanks @diegosouzapw) +- **feat(providers): stronger no-auth and web-cookie provider validation (`AUTH_007`)** — provider connection validation now handles no-auth and web-cookie providers explicitly: instead of returning a generic "Provider validation not supported", these providers report a precise `AUTH_007` status so the dashboard surfaces actionable validation feedback for cookie/no-auth flows. ([#4023](https://github.com/diegosouzapw/OmniRoute/pull/4023) — thanks @oyi77) +- **feat(combo): per-combo `stickyRoundRobinLimit` override on the combos page** — the round-robin sticky-affinity limit can now be set per combo from the combos page UI, overriding the global default, so a combo can pin (or loosen) how many consecutive requests stick to the same round-robin member independently of the others. ([#4472](https://github.com/diegosouzapw/OmniRoute/pull/4472) — thanks @adivekar-utexas) +- **feat(usage): quota fetch for `kimi-coding-apikey`** — usage/quota tracking now supports the `kimi-coding-apikey` provider, so its remaining quota is fetched and surfaced like the other quota-aware providers. ([#4435](https://github.com/diegosouzapw/OmniRoute/pull/4435) — thanks @janeza2) +- **feat(cluster): opt-in memory + Bifrost cluster profiles** — adds opt-in cluster profiles that wire the memory subsystem and the Bifrost Go sidecar into a clustered deployment (follow-up to #3932). ([#4433](https://github.com/diegosouzapw/OmniRoute/pull/4433) — thanks @KooshaPari) +- **feat(models): opt-in low-noise `/v1/models` catalog mode** — a new opt-in mode trims the `/v1/models` response to a quieter, lower-noise catalog for clients that choke on or don't need the full provider/model list. ([#4427](https://github.com/diegosouzapw/OmniRoute/pull/4427) — thanks @Rahulsharma0810) +- **feat(ui): expose a `targetFormat` selector in the custom-models form** — the custom-models form now lets you pick the upstream target format explicitly, so a custom model can be pinned to the right wire format instead of relying on inference. ([#4475](https://github.com/diegosouzapw/OmniRoute/pull/4475) — thanks @adivekar-utexas) +- **feat(providers): expose `gpt-4o` on the built-in GitHub Copilot (`gh`) provider** — GitHub Copilot still serves the original `gpt-4o` chat model via its `/chat/completions` endpoint, but the OmniRoute registry only shipped the GPT-5.x family, so clients that explicitly request `gpt-4o` against `gh` got an unknown-model error. `gpt-4o` is now registered under the `github` provider next to the GPT-5.x lineup (chat/completions, 128k context — no `openai-responses` targetFormat). Ported from [9router#98](https://github.com/decolua/9router/pull/98). (thanks @I3eka) +- **feat(pricing): default pricing for Qwen `coder-model` on the `qw` provider** — the Qwen Coder Free (`qw`) registry already exposed the `coder-model` id (Qwen3.5/3.6 Coder Model) but `DEFAULT_PRICING.qw` was missing the row, so usage tracking reported `$0.00` for that model. The pricing row is now added with the same shape as the sibling `vision-model` tier, restoring non-zero cost tracking. Ported from upstream 9router PR [decolua/9router#156](https://github.com/decolua/9router/pull/156). (thanks @LinearSakana) +- **feat(usage): Codex review-quota now surfaces the weekly window and the `additional_rate_limits` fallback shape** — the dashboard's Codex usage card showed only the **session** half of `code_review_rate_limit` and dropped review descriptors that arrived inside `additional_rate_limits` (the shape some ChatGPT Codex plans report). `buildCodexUsageQuotas` now emits the secondary window as `quotas.code_review_weekly` and, when the dedicated `code_review_rate_limit` block is empty, falls back to the matching descriptor in `additional_rate_limits` (matched on `limit_name`/`metered_feature`/`limit_id` containing `code_review` / `codex_review` / `review`). The new label `code_review_weekly → "Code Review Weekly"` is registered in `ProviderLimits/utils.tsx` so the card renders both windows side-by-side. The existing `quotas.code_review` key is preserved for back-compat. Inspired by upstream decolua/9router PR #836. (thanks @hiepau1231) +- **feat(dashboard): per-provider dropdown filter on the quota dashboard** — the Quota dashboard now has a "Provider" dropdown alongside the existing Status / Type / Tier / Env filters. Choosing a provider narrows the visible accounts to that provider only; the selection persists in `localStorage` (`omniroute:limits:providerFilter`) and the dropdown auto-falls back to "All providers" if the persisted key no longer matches a connection in the current session. The dropdown only renders when there are at least two distinct providers in view, so single-provider setups aren't cluttered. The upstream "Expiring first" toggle is intentionally not ported — `visibleConnections` already always sorts by soonest reset within each status group, so the toggle would be redundant. Inspired by [decolua/9router#769](https://github.com/decolua/9router/pull/769) — thanks @DEYLNN. +- **feat(dashboard): "Done" button in the model picker during combo creation** — `ModelSelectModal` now supports a `keepOpenOnSelect` prop (opt-in, off by default). When set — and the combos page now sets it — picking a model no longer auto-closes the modal, and a full-width "Done" button is rendered in the modal footer so users can add several models in a row and confirm explicitly. Single-select callers (e.g. CLI tool cards) are unchanged: the prop is opt-in, so they keep auto-close. The existing `multiSelect` mode (Clear + Done footer driven by `selectedModels`) takes precedence over `keepOpenOnSelect` to avoid two competing footers. Inspired by upstream PR [decolua/9router#1031](https://github.com/decolua/9router/pull/1031). (thanks @zanuartri) +- **feat(dashboard): toggle-style model deselection inside the combo builder modal** — `ModelSelectModal` (used by the combo builder) now treats clicks on an already-added model as an inline remove instead of a duplicate add: the click invokes a new `onDeselect` callback when one is supplied, and a new `closeOnSelect={false}` prop keeps the modal open so several models can be added or removed in one session before the user closes it manually. Wired into the combo builder so the existing green "✓" highlight is now actionable — clicking it removes every step that points at that qualified model. Inspired-by upstream decolua/9router PR #889. (thanks @fajarhide) + +### 🐛 Fixed + +- **fix(sse): combo routing now skips a provider whose credentials are all disabled instead of failing the whole request** — when a combo like `antigravity/opus → github/opus` hit a leg whose only configured connections were disabled (or where no connections existed at all), `handleNoCredentials` returned `400 BAD_REQUEST`, which the combo target loop treats as a hard stop (combo's 400-break guard from PR #4316 / issue #4279 prevents infinite fallback loops on body-specific 4xx errors). The combo therefore died on the first leg even when later targets were perfectly healthy. The no-active-credentials branch now returns `404 NOT_FOUND` with `"No active credentials for provider:

"` instead — `404` flows through `checkFallbackError` as `shouldFallback: true` (generic-error catch-all path in `open-sse/services/accountFallback.ts`), so the next combo target is tried. The log level for this branch also drops from `error` to `warn` because zero active credentials is an expected operator-driven state, not a server fault. Inspired-by upstream decolua/9router PR #336. (thanks @East-rayyy) +- **fix(dashboard): Manual Config modal "Copy" button now works on HTTP / non-secure deployments** — the copy handler in `ManualConfigModal` re-implemented the Clipboard-API-with-`execCommand`-fallback inline and gated the modern path on `window.isSecureContext`, so some non-secure-context browsers (and any future drift) silently lost the fallback. Migrated to the shared `useCopyToClipboard` hook (which delegates to `src/shared/utils/clipboard.ts`), giving consistent HTTP/HTTPS behavior with the rest of the dashboard and removing the duplicated code path. (thanks @anuragg-saxenaa) +- **fix(dashboard): enable Codex Apply / Reset buttons when the CLI is installed** — on the Codex CLI tool card the **Apply** button was disabled whenever `selectedApiKey` was empty, but the local default `sk_omniroute` key is a valid choice when cloud mode is off or no API keys are configured — so Apply was stuck disabled even when the configuration was otherwise complete. **Reset** was also disabled when `codexStatus.hasOmniRoute` was false, which made it impossible to clear Codex configuration on installs that had never been pointed at OmniRoute. The disabled logic is now extracted into a pure helper (`codexButtonState.ts`) covered by unit tests: Apply is disabled only when no model is selected, or when cloud mode is on **and** keys exist **and** none is picked; Reset is disabled only while a reset is in flight. (thanks @anuragg-saxenaa) +- **fix(mitm):** gate the sudo password prompt on the **server** platform, not the browser. The MITM control surface previously decided whether to ask for a sudo password by reading the browser's `navigator.userAgent`, which broke a Windows browser hitting a Linux server (no prompt → request rejected with `Missing sudoPassword`) and also forced an unnecessary modal on Linux hosts running as root, with NOPASSWD sudoers, or in minimal containers with no `sudo` binary on PATH. `GET /api/cli-tools/antigravity-mitm` now reports `isWin` and `needsSudoPassword` (probed via a safe `execFileSync("sudo", ["-n", "true"])`, per Hard Rule #13), and the Antigravity tool card uses the server-reported status to decide whether to show the modal. The POST/DELETE handlers stop returning 400 when sudo is genuinely not required. (thanks @hiepau1231) +- **fix(embeddings):** forward output dimensions to Gemini for consistent embedding dims. (thanks @nguyenha935) +- **fix(translator):** sanitize Read tool args from non-Anthropic models to prevent retry loops. (thanks @GodrezJr2) +- **fix(usage):** reuse Gemini CLI project ID for quota checks (avoid re-discovery). (thanks @Delcado19) +- **fix(dashboard):** surface manual config CTA when Claude CLI detection fails (remote deployments). (thanks @anuragg-saxenaa) +- **fix(executors):** granular reasoning_effort handling for Claude models on GitHub Copilot. (thanks @baslr) +- **fix(translator):** strip Claude output_config before MiniMax (rejected upstream). (thanks @hiepau1231) +- **fix(translator): OpenAI audio input now reaches Gemini/Antigravity instead of being silently dropped** — `input_audio`/`audio` content parts on the OpenAI→Gemini path matched no handler in `convertOpenAIContentToParts` and were discarded with no error. They are now mapped to a Gemini `inlineData` part with an `audio/` mime type (wav, mp3, …). (thanks @mugnimaestra) +- **fix(combo): model lockout now honors a long upstream quota reset instead of retrying within minutes** — when a combo target returned a quota error carrying an explicit long reset (e.g. Antigravity `Resets in 160h27m24s`, a `Retry-After` header), the per-model lockout capped at the short base cooldown (~minutes) and discarded the parsed reset, so the exhausted model kept being retried far too early. The lockout now applies the parsed reset when it exceeds the base cooldown, and the Antigravity error-message parser also matches the plural `Resets in …` phrasing. (thanks @Ansh7473) +- **fix(antigravity): Claude models no longer 400 with `Unknown name "output_config"`** — Anthropic/Claude-Code-only fields (`output_config`, legacy `output_format`) leaked into the Google Cloud Code request envelope via its top-level field passthrough, and Google rejects unknown envelope fields with `400 Invalid JSON payload received. Unknown name "output_config"` — breaking every Claude model served through Antigravity in IDEs. Those fields are now dropped before the envelope is built. (thanks @Duongkhanhtool) +- **fix(combo): round-robin members fail over faster under concurrency saturation via a configurable queue depth** — when a round-robin combo member was saturated, requests sat in the per-model semaphore's **unbounded** queue and only failed over to the next member after the full `queueTimeoutMs` (default 30s) elapsed — so a burst of agentic requests deep-queued one hot member instead of spilling to healthy ones. The per-model semaphore now accepts a bounded queue depth and emits `SEMAPHORE_QUEUE_FULL` once it is full (the round-robin loop already cascades on that code), so a configured low depth fails over immediately. A new `queueDepth` combo-config knob (global default / provider override / per-combo, default **20** for backward compatibility; **0** = never queue → fail over now) is exposed in Settings → Combo Defaults. ([#3872](https://github.com/diegosouzapw/OmniRoute/issues/3872) — thanks @KooshaPari) +- **fix(pricing): default cost rows for Antigravity's Gemini 3.5 Flash tiers + `gemini-pro-agent`** — the Antigravity public catalog (`ANTIGRAVITY_PUBLIC_MODELS`) ships `gemini-3-flash-agent`, `gemini-3.5-flash-low`, and `gemini-pro-agent` as user-callable client ids, but the `ag` block in the default pricing table only carried rows for `gemini-3-flash` / `gemini-3.1-pro-high`, so `getPricingForModel("ag", id)` returned `null` and cost / quota accounting silently fell back to `$0` for those three models. The missing rows are now seeded with the per-MTok rates the upstream quota tier bills at (Flash High/Medium share the legacy `gemini-3-flash` rate; `gemini-pro-agent` shares `gemini-3.1-pro-high`). (thanks @Ansh7473) +- **fix(pricing): align Claude Code (`cc`) pricing with current Anthropic per-MTok rates** — the `cc` provider block in the default pricing table had stale numbers across every Claude 4.x family entry — most visibly, `claude-opus-4-5-20251101` was billed at the deprecated Opus 4.1 rate (`input $15` / `output $75`), and `claude-haiku-4-5-20251001` was at half the current Haiku 4.5 rate. The `cached` (cache hit) and `cache_creation` (5-minute cache write) multipliers were also off across Opus 4.6/4.7/4.8, Sonnet 4.5/4.6, Haiku 4.5, and Fable 5. All eight entries now match the rates Anthropic publishes (input, 5m cache write at 1.25x input, cache hit at 0.1x input, output; reasoning billed at the output rate), so cost accounting on the dashboard and per-request usage events stop under- or over-reporting Claude Code spend. (thanks @chulanpro5) +- **fix(executors): sanitize Anthropic-shape content parts before GitHub Copilot `/chat/completions`** — Claude models on GitHub Copilot driven from clients like Cursor IDE (e.g. `gh/claude-sonnet-4.6`) failed with `Provider returned error: type has to be either 'image_url' or 'text' (reset after 30s)` because the client passed through Anthropic-shape content parts (`tool_use`, `tool_result`, `thinking`) untouched, and the Copilot chat-completions endpoint only accepts `text`/`image_url`. `GithubExecutor.transformRequest` now serializes any unsupported part type as `text` (preserving the model's context), drops empty parts, and collapses to `null` when an assistant message's only content was tool_calls — `tool_calls` ride alongside untouched. Codex-family models still route through `/responses` unchanged. (thanks @cngznNN) +- **fix(sse):** refactor stall detection to reduce false positives on slow but progressing streams. (thanks @zakirkun) +- **fix(executors): synthesize `x-opencode-request` for custom-named OpenCode providers** — the OpenCode CLI only emits the `x-opencode-*` header set when the provider id starts with `opencode`; a custom-named provider (e.g. `omniroute`) instead sends `x-session-affinity` / `x-session-id` (mapped to `x-opencode-session` since #4022) but no request-correlation id, so `x-opencode-request` was silently dropped. `OpencodeExecutor` now synthesizes a fresh `x-opencode-request` on that session-affinity fallback path so custom-named providers are not disadvantaged on the opencode.ai upstream. `x-opencode-client` / `x-opencode-project` are intentionally **not** fabricated (no valid client source — an invented value risks upstream rejection) and remain forward-only; `DefaultExecutor` is untouched. ([#4465](https://github.com/diegosouzapw/OmniRoute/issues/4465) — thanks @pizzav-xyz) +- **fix(compression): RTK now compresses Anthropic-shape `tool_result` blocks** — `applyRtkCompression` only compressed OpenAI-shape tool results (`role:"tool"`); Anthropic-shape tool results (`tool_result` content blocks inside a `role:"user"` message) were skipped, so coding agents speaking the Anthropic Messages format got zero RTK savings even though RTK's command-aware filters (e.g. `git-status`) would have compressed the output. RTK now treats a message containing a `tool_result` block as eligible (gated by `applyToToolResults`), captures Anthropic `tool_use` blocks for command resolution, and compresses each block's inner text (string or nested text-block array) while preserving `type` + `tool_use_id` exactly — matching what `caveman`/`aggressive` already did. ([#4468](https://github.com/diegosouzapw/OmniRoute/pull/4468) — thanks @diegosouzapw) +- **fix(dashboard): request-log auto-refresh no longer dies from a "ghost" load-more on first page load** — the request-log viewer's infinite-scroll `IntersectionObserver` uses a 200px rootMargin, so its sentinel was already intersecting on mount whenever the first page didn't fill the scroll container. That fired a `loadMore()` with no user interaction, growing the window past `PAGE_SIZE` — and auto-refresh only polls while on the first page (`limit <= pageSize`), so it stayed permanently paused (only a manual filter change re-armed it). The observer now grows the window only after a genuine user scroll (new pure `shouldTriggerInfiniteScroll` guard), and a filter change re-arms the guard, so the default first-page view resumes its ~10s auto-refresh. ([#4269](https://github.com/diegosouzapw/OmniRoute/issues/4269) — thanks @tjengbudi) +- **fix(sse): large `/v1/chat/completions` requests no longer crash the server with a Node heap OOM** — the chat request body was parsed multiple times along the route (route guard, injection guard, handler), buffering very large payloads several times and pushing concurrent agentic traffic into an out-of-memory crash. The body is now parsed **once** at the route guard and threaded through, so each request is buffered a single time. ([#4380](https://github.com/diegosouzapw/OmniRoute/issues/4380) — thanks @NakHalal) +- **fix(guardrails): tighten the `system_prompt_leak` heuristic to stop false positives on agent traffic** — the leak detector flagged normal agent/tool conversations as prompt-leak attempts; it now requires an additional qualifier before flagging, so legitimate agent traffic is no longer blocked. ([#4041](https://github.com/diegosouzapw/OmniRoute/issues/4041) — thanks @KooshaPari) +- **fix(translator): drop orphan tool results on the Claude→OpenAI request path** — a `tool_result` with no preceding matching `tool_use` (orphan) produced upstream 500/502 errors for Command Code / Custom OpenAI clients on ≥3.8.26. Orphan tool results are now filtered before the request is sent. ([#4385](https://github.com/diegosouzapw/OmniRoute/issues/4385) — thanks @adityapnusantara) +- **fix(providers): register API-key validators for Firecrawl and Jina Reader** — both providers returned "Provider validation not supported" when validating their API key; they now have proper validators registered in `SEARCH_VALIDATOR_CONFIGS`. ([#4401](https://github.com/diegosouzapw/OmniRoute/issues/4401) — thanks @ponkcore) +- **fix(providers): generic web-cookie validator must not shadow per-provider validators** — a follow-up to the `AUTH_007` validation work (#4023): the generic web-cookie validator was matching before more specific per-provider validators, so provider-specific validation was skipped. Validator resolution now prefers the per-provider validator. ([#4467](https://github.com/diegosouzapw/OmniRoute/pull/4467) — thanks @diegosouzapw) +- **fix(translator): inject a placeholder message when the Responses API `input[]` is empty** — a `POST /v1/responses` with `input: []` translated to `messages: []`, which every upstream Chat-Completions provider rejects (surfaced as a confusing 406); a single placeholder user message is now injected, mirroring the existing empty-string handling. ([#4393](https://github.com/diegosouzapw/OmniRoute/pull/4393) — thanks @diegosouzapw) +- **fix(providers): serve the api.airforce live `/models` catalog instead of the stale seed** — the api.airforce provider listed a stale hard-coded seed; it now serves the upstream live `/models` catalog. ([#4395](https://github.com/diegosouzapw/OmniRoute/pull/4395) — thanks @diegosouzapw) +- **fix(cli): non-interactive-safe prompts + `context` alias** — the CLI's `confirm()`/prompt helpers no longer hang in non-interactive (piped/CI) contexts, and a singular `context` alias is accepted alongside `contexts`; the contexts workflow is documented. ([#4439](https://github.com/diegosouzapw/OmniRoute/pull/4439), [#4397](https://github.com/diegosouzapw/OmniRoute/pull/4397) — thanks @diegosouzapw) +- **fix(cli): `omniroute update` no longer reports a stale "latest" version from npm's cache** — `getLatestVersion()` ran `npm view omniroute version` without `--prefer-online`, so npm could serve a cached value from its HTTP cache and tell users on an older build (e.g. 3.8.30) they were already "running the latest version" even after a newer one (3.8.31) was published. The version check now passes `--prefer-online` to force npm to revalidate against the registry. ([#4376](https://github.com/diegosouzapw/OmniRoute/issues/4376) — thanks @akbardwi) +- **fix(sse): `web_search_20250305` no longer 400s on MiniMax's Anthropic-compatible endpoint** — PR #2960 added a Claude→Claude bypass that forwards Anthropic's typed server tool `web_search_20250305` untouched, assuming the Claude-format upstream implements Anthropic server tools. MiniMax's `/anthropic` endpoint does not, so `claude → minimax` requests carrying that tool got `HTTP 400 "invalid params, function name or parameters is empty (2013)"`. `supportsNativeWebSearchFallbackBypass` now consults the (already-plumbed) `provider` and excludes providers known not to implement server tools (currently `minimax`) from the bypass, so the built-in web-search tool is converted to the `omniroute_web_search` function fallback — which MiniMax accepts as a normal function tool. ([#4481](https://github.com/diegosouzapw/OmniRoute/issues/4481) — thanks @shafqatevo) +- **fix(command-code): pass `reasoning` / `thinking` fields through to upstream params** — Command Code requests carrying `reasoning`/`thinking` controls had those fields dropped before the upstream call, so reasoning-effort and extended-thinking settings were silently ignored; they are now forwarded to the upstream params. ([#4473](https://github.com/diegosouzapw/OmniRoute/pull/4473) — thanks @adivekar-utexas) +- **fix(usage): keep Kiro overage-enabled accounts routable after base quota hits zero** — a Kiro account with overage enabled was excluded from routing once its base quota reached zero, even though overage billing should keep it serving; such accounts now stay routable past base-quota exhaustion. ([#4469](https://github.com/diegosouzapw/OmniRoute/issues/4469) — thanks @heaven321357 / @CleanDev-Fix) +- **fix(providers): model-aware `supportsRedactedThinking` for mixed-format providers** — the redacted-thinking capability was resolved per provider rather than per model, so a mixed-format provider (some models support redacted thinking, others don't) got the wrong answer for some models; the check is now model-aware. ([#4479](https://github.com/diegosouzapw/OmniRoute/pull/4479) — thanks @TF0rd) +- **fix(usage): parse numeric-string quota reset timestamps as Unix seconds/ms** — when a provider returned the quota reset timestamp as a numeric string (e.g. `"1700000000"`), `parseResetTime` passed it straight to `new Date(str)`, which returned `Invalid Date` and dropped the reset entirely (UI showed no reset). Numeric strings are now detected and treated as Unix timestamps with the same `< 1e12` seconds-vs-ms heuristic already applied to numeric values; ISO/parseable strings are untouched. Applied symmetrically in `codexUsageQuotas.parseResetTime`. (Inspired by upstream [decolua/9router#768](https://github.com/decolua/9router/pull/768) — thanks @DEYLNN) +- **fix(usage): clearer "auth expired" message for Kiro accounts added via Google/GitHub social-auth** — a Kiro account created through the `/api/oauth/kiro/social-exchange` flow (Google or GitHub social login) uses a token format that AWS CodeWhisperer's `GetUsageLimits` quota API frequently rejects with 401/403 even when `/messages` still works. The quota card was throwing the raw upstream error blob (`Failed to fetch Kiro usage: Kiro API error (401): {…}`); social-auth accounts now get the same friendly `Kiro quota API authentication expired. Chat may still work.` message that legacy social-auth users with a stored marker already see, while Builder-ID / IDC accounts keep the existing throw-on-failure behavior so transient upstream errors don't get silently masked. (thanks @anuragg-saxenaa) +- **fix(dashboard): Antigravity CLI Tools model picker now lists API-Key-Compatible custom providers** — the API-Key-compatible / passthrough provider groups in `ModelSelectModal` are derived from the user's `modelAliases`, but `AntigravityToolCard` was the only CLI tool card that didn't fetch `/api/models/alias` or forward the `modelAliases` prop, so a custom OpenAI-compatible provider added in OmniRoute never surfaced in the Antigravity tool's model picker — routing a custom model to Antigravity from there was impossible. The card now mirrors the pattern already used by every sibling tool card (Codex, Claude, Cline, Kilo, Droid, OpenClaw, HermesAgent). (thanks @mxskeen) +- **fix(mitm): cert/DNS operations no longer fail with `spawn sudo ENOENT` on slim Docker images** — slim Docker base images (e.g. `node:24-trixie-slim`) do not ship `sudo`, and OmniRoute's runtime stage runs as `USER node` (UID 1000, non-root), so `execFileWithPassword("sudo", …)` failed unconditionally for any MITM operation triggered from inside the container (cert install, DNS host-file write). A new `isSudoAvailable()` probe gates the `sudo -S` wrapper; when sudo is missing and the process is not root, the underlying command runs directly (same user, no elevation) — same path already taken when running as root. Privileged operations that genuinely need elevation (system trust store, `/etc/hosts`) still error explicitly so operators can mount the CA or hosts file from the host side. (thanks @lokinh) + +### 🔒 Security + +- **fix(sse): use a crypto-secure RNG for combo/deck load-balancing selection** — random combo/deck member selection used a non-cryptographic PRNG, flagged by CodeQL (`#665`); it now uses a crypto-secure RNG. ([#4457](https://github.com/diegosouzapw/OmniRoute/pull/4457) — thanks @diegosouzapw) +- **fix(sse): unbiased `crypto.randomInt` for combo selection (follow-up to #4457)** — the initial crypto-secure conversion used modulo reduction over the secure bytes, which introduces a small modulo bias; selection now uses `crypto.randomInt` (rejection sampling) for a uniform, unbiased distribution across combo/deck members. ([#4462](https://github.com/diegosouzapw/OmniRoute/pull/4462) — thanks @diegosouzapw) + +### 📝 維護 + +- **refactor(chatCore):** extract `resolveChatCoreRequestSetup` (first setup-phase slice) toward modularizing the chatCore god-file. ([#4392](https://github.com/diegosouzapw/OmniRoute/pull/4392) — thanks @diegosouzapw) +- **refactor(chatCore):** extract the Codex service-tier resolvers into a pure `chatCore/serviceTier.ts` leaf (continues the god-file split). ([#4477](https://github.com/diegosouzapw/OmniRoute/pull/4477), [#3501](https://github.com/diegosouzapw/OmniRoute/issues/3501) — thanks @diegosouzapw) +- **perf(dashboard):** lazy-load the usage analytics charts so the dashboard's initial bundle/paint is lighter (charts hydrate on demand). ([#4466](https://github.com/diegosouzapw/OmniRoute/pull/4466) — thanks @KooshaPari) +- **perf(kiro):** cut request-completion hot-path CPU and cap the DB-lock event-loop block so Kiro request completion does not stall the event loop under load. ([#4459](https://github.com/diegosouzapw/OmniRoute/pull/4459) — thanks @artickc) +- **fix(catalog):** restore-green — add OpenAI `gpt-4.1-mini`/`gpt-4.1-nano` + `o3-mini`/`o4-mini` pricing rows to keep the static-parity gate green after the registry expansion (#4394), plus the web-cookie validator shadowing fix. ([#4447](https://github.com/diegosouzapw/OmniRoute/pull/4447) — thanks @diegosouzapw) +- **chore(quality):** reconcile file-size + complexity baselines after the `/review-prs` round, and the `server.ts` file-size baseline after the pool-tools registration (#3368). ([#4461](https://github.com/diegosouzapw/OmniRoute/pull/4461), [#4423](https://github.com/diegosouzapw/OmniRoute/pull/4423) — thanks @diegosouzapw) +- **docs(remote-mode):** add a copy-paste end-to-end verification example. ([#4430](https://github.com/diegosouzapw/OmniRoute/pull/4430) — thanks @diegosouzapw) +- **docs:** add operational documentation (usage/quota, database, open-sse architecture, monitoring). ([#3455](https://github.com/diegosouzapw/OmniRoute/pull/3455) — thanks @oyi77) --- ## [3.8.31] — 2026-06-20 -### ✨ New Features +### ✨ 新功能 +- **feat(translator):** Gemini accepts OpenAI `input_audio` and `audio_url` content parts. (thanks @mugnimaestra) - **perf(dashboard): combos UI leaf-split, Next.js config tuning, 1-click Redis & Bifrost sidecar** — delivers four of the five performance/UX tracks from the #3932 thread: the combos dashboard page is split into focused leaf components (smaller bundles, faster reloads), `next.config` is tuned for the standalone build, Redis can be provisioned in one click, and a Bifrost sidecar option is wired in. (The fifth track — chatLogHelpers extraction — was already covered upstream and dropped.) ([#4381](https://github.com/diegosouzapw/OmniRoute/pull/4381) — thanks @KooshaPari) ### 🐛 Fixed @@ -1628,12 +2481,15 @@ _See English CHANGELOG for v3.8.32 details._ - **fix(api): persist `max_input_tokens` / `max_output_tokens` when adding a custom model** — `POST /api/provider-models` silently dropped the per-model token limits set in the "add custom model" form: the handler destructured the rest of the body but never read `max_input_tokens` / `max_output_tokens`, and `addCustomModel()` had no parameter for them, so the values were thrown away on write. The DB layer (`inputTokenLimit` / `outputTokenLimit`) and the `/v1/models` catalog already round-trip these fields — only the write path was missing. The validation schema now accepts the two optional limits, the handler forwards them, and `addCustomModel()` persists them so a custom model's context/output window survives into the catalog. ([#4349](https://github.com/diegosouzapw/OmniRoute/pull/4349) — thanks @codename-zen) - **fix(plugin): the OpenCode static-catalog plugin prefixes combo/raw model keys with the provider id** — OpenCode's static-catalog reader misdetected the `omniroute` provider: combo keys emitted as `combo/MASTER` were parsed as provider `combo` ("No credentials for provider: omniroute"), while a bare-`MASTER` form was misread as a model with no resolvable provider, and mixed `omniroute/MASTER` + bare-raw keys were rejected by OpenCode's schema. The plugin now emits every combo and raw model key prefixed with the `omniroute` provider id, emits the provider id explicitly, and drops the legacy `combo/` prefix — so the static-catalog reader detects the provider and the auth loader returns the right credentials (the catalog-fetch timeout was also raised so a cold-start server doesn't publish an empty stub). ([#4384](https://github.com/diegosouzapw/OmniRoute/pull/4384) — thanks @herjarsa) +- **fix(translator): inject placeholder message when Responses API input[] is empty (prevents upstream 400)** — a client (e.g. Fabric-AI) calling `POST /v1/responses` with `input: []` used to be translated into `messages: []`, which every upstream Chat-Completions provider rejects with `400: at least one message is required` (surfaced to the client as a confusing 406). The translator now treats an empty `input[]` the same as an empty string — a placeholder user message is injected so the request is always valid. (thanks @anuragg-saxenaa) +- **fix(embeddings): NVIDIA NIM asymmetric embedding models inject the required `input_type`** — NVIDIA NIM asymmetric embedders (e.g. `nvidia/nv-embedqa-e5-v5`) reject requests without an `input_type` parameter with `400 "'input_type' parameter is required"`, but OmniRoute only forwarded `input_type` when the client supplied it — so callers (and OpenAI-style SDKs that don't emit the field) got a hard failure. The embedding registry now carries a model-level default (`input_type: "query"`) for the asymmetric NVIDIA model, and the embeddings handler injects a model's default params into the upstream body **only** when the client didn't already send them — a client-supplied `input_type` (e.g. `"passage"`) is respected unchanged, and symmetric models that carry no default are unaffected. (thanks @hydraromania) + ### 🔒 Security - **fix(security): scope the OAuth callback `postMessage` to a trusted-origin allowlist** — the OAuth callback at `/callback` previously posted `{ code, state, … }` to `window.opener.postMessage(…, "*")` whenever the opener was cross-origin, so a hostile page that opened the well-known redirect URI in a popup could receive the OAuth code/state and complete the flow as the user. The wildcard fallback is replaced with iteration over a fixed allowlist (same-origin + Codex's `localhost:1455` / `127.0.0.1:1455` loopback helper); the browser silently drops `postMessage` to any opener whose origin isn't listed. ([#4372](https://github.com/diegosouzapw/OmniRoute/pull/4372) — ported from 9router#998, thanks @aeonframework / @diegosouzapw) - **fix(mitm): exact host membership in the MITM hosts test (CodeQL false positive)** — `tests/unit/mitm-tool-hosts.test.ts` checked host membership with `Array.includes(host)`, which CodeQL's `js/incomplete-url-substring-sanitization` heuristic misreads as a `String.includes()` URL-substring sanitization test (HIGH false positive). Switched to `.some((h) => h === host)` — identical semantics, no flagged pattern. ([#4386](https://github.com/diegosouzapw/OmniRoute/pull/4386)) -### 📝 Maintenance +### 📝 維護 - **docs: one-time feature-documentation catch-up (v3.8.20 → v3.8.30)** — reconciled the docs with every user-facing feature shipped since v3.8.20: a new README **✨ What's New** section; new guides for [CLI integrations](docs/guides/CLI-INTEGRATIONS.md), [MITM TPROXY transparent decrypt](docs/security/MITM-TPROXY-DECRYPT.md) and [delegated Anthropic Context Editing](docs/compression/CONTEXT_EDITING.md); refreshed AUTO-COMBO (`auto/:` + Arena-ELO), API_REFERENCE (`x-omniroute-no-memory`), MEMORY (int8 quantization, off-by-default), RESILIENCE (model-lockout success-decay), RTK, AGENTBRIDGE, TRAFFIC_INSPECTOR, GUARDRAILS, CLOUD_AGENT, ENVIRONMENT; regenerated PROVIDER_REFERENCE (231 providers) and synced the provider count in README/CLAUDE/AGENTS. Going forward this runs every release (generate-release step 6b). ([#4391](https://github.com/diegosouzapw/OmniRoute/pull/4391)) - **refactor(chatCore): extract the `checkHeapPressureGuard` leaf (god-file decomposition start)** — first increment of decomposing `chatCore.ts` (~5127 LOC, the hottest path — every chat request flows through `handleChatCore`). The V8 heap-pressure guard at the top of `handleChatCore` (rejects with 503 when `heapUsed` exceeds the shed threshold) is moved to a self-contained, co-located `utils/heapPressure.ts::checkHeapPressureGuard(...)` with no behavior change. ([#4371](https://github.com/diegosouzapw/OmniRoute/pull/4371) — thanks @diegosouzapw) @@ -1648,7 +2504,7 @@ _See English CHANGELOG for v3.8.32 details._ ## [3.8.30] — 2026-06-20 -### ✨ New Features +### ✨ 新功能 - **feat(dashboard): category (media serviceKind) filter on the providers page** — `/dashboard/providers` gains a media-category filter row (Image / Video / Music / Text→Speech / Speech→Text / Embedding) that composes with the existing search, free-only and "show configured only" filters. Membership is derived from the backend media registries (a provider that serves a kind is surfaced even if it never declared `serviceKinds`), keeping the UI in lockstep with the backend. ([#4240](https://github.com/diegosouzapw/OmniRoute/issues/4240)) - **feat(combo): per-step account allowlist — scope a round-robin/weighted step to a subset of a provider's connections** — a combo model step can now carry a first-class account allowlist so a round-robin (or weighted) strategy is scoped to a chosen subset of a provider's connections (e.g. only `foo1`+`foo2` out of `foo1..foo4`) without hand-pinning one step per account. Empty = the whole active pool (unchanged). When a step both has an allowlist and is tag-routed, the two intersect (most-restrictive wins); a single pinned account still takes precedence. The combo builder's Precision step editor gains an optional "Restrict to accounts" picker. ([#3266](https://github.com/diegosouzapw/OmniRoute/issues/3266)) @@ -1712,7 +2568,7 @@ _See English CHANGELOG for v3.8.32 details._ - **test: align two tests left red by merged PRs** — re-aligns the db-rules classification count (#4335) and the LMArena split-cookie metadata test (#4271) after concurrent merges. ([#4346](https://github.com/diegosouzapw/OmniRoute/pull/4346)) - **test(ci): reconcile the release/v3.8.30 baseline + test drift** — reconciles quality baselines and drifted tests accumulated on the release branch. ([#4276](https://github.com/diegosouzapw/OmniRoute/pull/4276)) -### 📝 Maintenance +### 📝 維護 - **refactor(combo): `ComboContext` + extract `phaseComboSetup` (god-file split, phase 1)** — begins decomposing the combo god-file by extracting combo setup into a context object, without touching dispatch/semaphore logic. ([#4326](https://github.com/diegosouzapw/OmniRoute/pull/4326)) - **feat(quality): cap test-file size — anti-reinflation Layer 1** — freezes the existing god-tests and caps new test files at 800 lines to stop re-inflation. ([#4273](https://github.com/diegosouzapw/OmniRoute/pull/4273)) @@ -1742,7 +2598,7 @@ _See English CHANGELOG for v3.8.32 details._ ## [3.8.29] — 2026-06-19 -### ✨ New Features +### ✨ 新功能 - **feat(cloud-agent): Cursor Cloud Agent via the official API-key REST API (no IDE-OAuth ban risk)** — adds a `cursor-cloud` cloud agent that drives Cursor's Background / Cloud Agents through the official REST API (`api.cursor.com`) authenticated with a user or service-account API key — the safer, first-party alternative to re-using the Cursor IDE's OAuth session (the existing `cursor` provider, which carries a ban-risk warning). Implemented as a plain REST adapter mirroring the Devin/Jules agents (`createTask`/`getStatus`/`sendMessage`/`listSources`), so it does **not** pull in the `@cursor/sdk` package and its per-platform native binaries (Cursor's SDK is itself a thin wrapper over this REST API). Cursor's UPPERCASE status enums (`CREATING`/`RUNNING`/`FINISHED`/`ERROR`) are mapped explicitly to the shared `CloudAgentStatus`, and `baseUrl` is overridable per-credential. Credentials are stored encrypted via the existing `cloud_agent_credentials` table; no schema change. ([#4227](https://github.com/diegosouzapw/OmniRoute/issues/4227) — thanks @MRDGH2821) - **feat(routing): OpenRouter-style `auto/:` combos** — auto-routing now understands suffixed combos that separate the _category_ (what kind of route) from the _tier_ (how to optimize): `auto/coding:fast`, `auto/coding:cheap` (alias `:floor`), `auto/coding:free`, `auto/coding:pro`, `auto/coding:reliable`, plus the new category roots `auto/reasoning`, `auto/vision`, `auto/multimodal`. The **tier** picks the scoring weights — `:fast` → ship-fast, `:cheap`/`:floor` → cost-saver, `:reliable` → a new reliability-first pack (circuit-breaker health + latency stability) — while `:free`/`:pro` filter the candidate pool by model tier (`classifyTier`: free-tier vs. premium models). The **category** filters the pool by capability (`vision`/`multimodal` → vision-capable models, `reasoning` → reasoning/thinking models). Any valid `auto/:` resolves on demand; a curated set is advertised in `/v1/models` and the dashboard. Filtering is fail-open — if a constraint matches no connected models the full pool is used so routing never breaks. All composition lives in the new `open-sse/services/autoCombo/suffixComposition.ts`; the core combo scorer (`combo.ts`) is untouched. Second slice of #4235 (premium account-tier weighting is a later follow-up). ([#4235](https://github.com/diegosouzapw/OmniRoute/issues/4235) — thanks @MRDGH2821) @@ -1844,7 +2700,7 @@ _See English CHANGELOG for v3.8.32 details._ - **test(db,sse): de-flake db-backup + chatcore streaming timing assertions** — stabilizes two timing-sensitive tests (fire-and-forget backup completion + a streaming race). ([#4132](https://github.com/diegosouzapw/OmniRoute/pull/4132)) - **test: align stale integration tests surfaced post-v3.8.28 on main** — realigns integration tests that drifted after the v3.8.28 merge. ([#4129](https://github.com/diegosouzapw/OmniRoute/pull/4129)) -### 📝 Maintenance +### 📝 維護 - **refactor(sse): split chatCore.ts pure helpers into chatCore/ modules (−561 LOC)** — extracts pure helpers out of the chatCore god-file into dedicated modules (Onda 3). ([#4159](https://github.com/diegosouzapw/OmniRoute/pull/4159)) - **refactor(chatcore): extract passthrough/header/telemetry helpers (QG v2 Fase 9 T5 C2-C3-C5)** — further chatCore decomposition. ([#4188](https://github.com/diegosouzapw/OmniRoute/pull/4188)) @@ -1880,7 +2736,7 @@ _See English CHANGELOG for v3.8.32 details._ ## [3.8.28] — 2026-06-17 -### ✨ New Features +### ✨ 新功能 - **feat(providers): add OrcaRouter (OpenAI-compatible routing gateway)** — OrcaRouter is now registered as an API-key provider. Its adaptive router is exposed as `orcarouter/auto` (smart routing across 150+ upstream models), alongside a curated flagship set (GPT-5.5, Gemini 3.5 Flash, Claude Opus 4.8, Grok 4.3, DeepSeek V4 Pro, MiniMax M2.7, Qwen3.7 Max). `passthroughModels` is enabled so any OrcaRouter model id works. OpenAI-compatible endpoint (`https://api.orcarouter.ai/v1`), Bearer (`sk-orca-…`) auth — no custom executor or translator required. ([#4070](https://github.com/diegosouzapw/OmniRoute/pull/4070) — thanks @jinhaosong-source) - **feat(providers): add Wafer AI (Anthropic-compatible, Bearer auth)** — Wafer AI is now a built-in provider speaking the Anthropic Messages format with Bearer authentication, registered with its model catalog so it works out of the box. ([#4098](https://github.com/diegosouzapw/OmniRoute/pull/4098) — thanks @diegosouzapw) @@ -1921,14 +2777,14 @@ _See English CHANGELOG for v3.8.32 details._ - **fix(combo): detach the per-target listener from the shared hedge abort signal** — combo hedging attached a per-target listener to a shared abort signal without detaching it, leaking listeners across requests; the listener is now detached. ([#4116](https://github.com/diegosouzapw/OmniRoute/pull/4116) — thanks @artickc) - **fix(timers): unref background interval timers so they don't block clean shutdown** — long-lived background interval timers kept the event loop alive and blocked a clean process exit; they are now `unref`'d. ([#4117](https://github.com/diegosouzapw/OmniRoute/pull/4117) — thanks @artickc) -### ⚡ Performance +### ⚡ 效能 - **perf(registry): precompute the model→provider index in `parseModelFromRegistry`** — model→provider lookups now use a precomputed index instead of scanning the registry on every call. ([#4110](https://github.com/diegosouzapw/OmniRoute/pull/4110) — thanks @artickc) - **perf(obfuscation): cache per-word regexes instead of recompiling every request** — the obfuscation pass now caches its per-word regexes rather than recompiling them on each request. ([#4109](https://github.com/diegosouzapw/OmniRoute/pull/4109) — thanks @artickc) - **perf(stream): use `structuredClone` instead of a JSON round-trip for per-chunk reasoning split** — the per-chunk reasoning split now clones with `structuredClone` rather than `JSON.parse(JSON.stringify(...))`. ([#4108](https://github.com/diegosouzapw/OmniRoute/pull/4108) — thanks @artickc) - **perf(gemini): cache the reasoning close-tag regex instead of recompiling per token** — the Gemini reasoning close-tag regex is now compiled once and reused instead of per token. ([#4106](https://github.com/diegosouzapw/OmniRoute/pull/4106) — thanks @artickc) -### 📝 Maintenance +### 📝 維護 - **ci(quality): flip the TIA impacted-unit-tests gate from advisory to blocking (Fase 9)** — the test-impact-analysis gate that runs the unit tests impacted by a diff is now blocking on PRs. ([#4069](https://github.com/diegosouzapw/OmniRoute/pull/4069) — thanks @diegosouzapw) - **ci(quality): dedup the doubly-run `check:docs-sync` + record the validated ROI backlog (Fase 9)** — `check:docs-sync` was running twice in CI; the duplicate was removed and the validated quality-gate ROI backlog recorded. ([#4099](https://github.com/diegosouzapw/OmniRoute/pull/4099) — thanks @diegosouzapw) @@ -1954,7 +2810,7 @@ _See English CHANGELOG for v3.8.32 details._ ## [3.8.27] — 2026-06-17 -### ✨ New Features +### ✨ 新功能 - **feat(combos): advertise combo capabilities (multimodal / reasoning / caching) on the import surfaces** — importing a combo package into a client (LobeHub / OpenCode / VS Code, via `/v1/combos` and the VS Code combo catalog) no longer requires manually enabling multimodal/image-input, reasoning, and caching afterwards. `projectCombo` now attaches a registry-derived `capabilities` block, gated conservatively: `multimodal`/`reasoning` are advertised only when **every** concrete model step proves the capability (an unprovable nested combo-ref drops them, since the strategy may route to any member), and `caching` reflects the combo's explicit Context-Cache-Protection setting (no surprise prompt-cache cost). The public `/v1/combos` default projection (#2300) is unchanged unless the caller opts in. ([#3979](https://github.com/diegosouzapw/OmniRoute/issues/3979) — thanks @xenstar) - **feat(sse): delegated Anthropic Context Editing for Claude (`clear_tool_uses`)** — Claude requests can now offload context trimming to Anthropic's server-side context-management API (beta `context-management-2025-06-27`, `clear_tool_uses_20250919`), pruning stale tool-use turns upstream instead of locally. Claude-only by nature (the edit runs server-side); multi-provider context trimming remains the job of the local compression engines. ([#4021](https://github.com/diegosouzapw/OmniRoute/pull/4021) — thanks @diegosouzapw) @@ -2008,7 +2864,7 @@ _See English CHANGELOG for v3.8.32 details._ - **refactor: modularize schemas (non-stacked)** — the request/response schema definitions were split into individual modules to reduce file size and improve maintainability. ([#3988](https://github.com/diegosouzapw/OmniRoute/pull/3988) — thanks @oyi77) - **fix: restore unit regressions dropped by the lossy schema/registry modularizations** — the schema/registry modularizations (#3988, #3993) silently dropped internal logic covered by unit tests; this PR restores the regressed units. ([#4030](https://github.com/diegosouzapw/OmniRoute/pull/4030) — thanks @diegosouzapw) - **refactor(dashboard): settings UI layout + API Keys naming** — the settings UI layout was reorganized and the "API Keys" naming clarified. ([#4020](https://github.com/diegosouzapw/OmniRoute/pull/4020) — thanks @rdself) -- **大量UI顯示和i18n優化 (dashboard UI display + i18n improvements)** — a batch of dashboard UI-display refinements and i18n string improvements. ([#3973](https://github.com/diegosouzapw/OmniRoute/pull/3973) — thanks @rdself) +- **大量UI显示和i18n优化 (dashboard UI display + i18n improvements)** — a batch of dashboard UI-display refinements and i18n string improvements. ([#3973](https://github.com/diegosouzapw/OmniRoute/pull/3973) — thanks @rdself) - **fix(ci): scope TIA to `node:test` unit files only** — test-impact analysis was matching files the `node:test` runner doesn't execute, producing 99 false failures; the TIA glob now mirrors the `test:unit` glob exactly. ([#4035](https://github.com/diegosouzapw/OmniRoute/pull/4035) — thanks @diegosouzapw) - **fix(ci): electron-release publish-npm needs `contents: write`** — the reusable npm-publish job invoked by the electron release lacked `contents: write`, causing a v3.8.26 `startup_failure`; the permission was granted. ([#3966](https://github.com/diegosouzapw/OmniRoute/pull/3966) — thanks @diegosouzapw) - **test(opencode-plugin): ESM default-export test (drop the stale CJS bundle test)** — replaces the stale CJS bundle test with an ESM default-export test, following up the #3883 ESM-only migration. ([#3967](https://github.com/diegosouzapw/OmniRoute/pull/3967) — thanks @diegosouzapw) @@ -2020,7 +2876,7 @@ _See English CHANGELOG for v3.8.32 details._ ## [3.8.26] — 2026-06-15 -### ✨ New Features +### ✨ 新功能 - **feat(media): Vertex AI (Google) speech, transcription, music & video generation** — Vertex AI's Google media models are now routable through dynamic discovery: speech synthesis, audio transcription, music generation, and video generation. ([#3929](https://github.com/diegosouzapw/OmniRoute/pull/3929) — thanks @artickc) - **feat(glm): add GLM-5.2 with effort-tier routing (high/max)** — GLM-5.2 is registered with high/max effort-tier routing. ([#3885](https://github.com/diegosouzapw/OmniRoute/pull/3885) — thanks @dhaern) @@ -2029,6 +2885,12 @@ _See English CHANGELOG for v3.8.32 details._ ### 🐛 Fixed +- **fix(executor): stop leaking `stream_options` onto non-streaming requests (NVIDIA NIM 400)** — clients that send `stream_options: { include_usage: true }` regardless of `stream` (e.g. the OpenAI Python SDK) had it passed through untouched on non-streaming calls, and NVIDIA NIM rejected it with `400 "Stream options can only be defined when stream=True"`. `DefaultExecutor.transformRequest` only injected/cleared `stream_options` on the streaming branch and had no branch to strip a client-sent value when the outbound request is non-streaming. It now strips `stream_options` whenever `stream` is false (the streaming injection path is unchanged). Affects all OpenAI-compatible providers; NIM is just the one that strictly rejects the violation. ([#3884](https://github.com/diegosouzapw/OmniRoute/issues/3884) — thanks @andrea-kingautomation / @daniij) +- **fix(claude): forward the client-negotiated `anthropic-beta: tool-search-tool-2025-10-19` on the Claude OAuth path** — with `ENABLE_TOOL_SEARCH` active, Claude Code sends deferred tools + a `tool_search_tool_*` and negotiates the `tool-search-tool-2025-10-19` beta, but OmniRoute dropped that beta on **both** Claude code paths (the `default` executor rebuilt the header from the static `ANTHROPIC_BETA_CLAUDE_OAUTH` set, and `selectBetaFlags` only read the client beta to gate thinking/effort), so the claude.ai backend rejected every deferred-tool request with `400 Tool reference '' not found in available tools`. A new allowlist-merge (`mergeClientAnthropicBeta`) now unions the client's negotiated beta into the outbound set on both paths — appending only allowlisted client betas (currently just `tool-search-tool-2025-10-19`) so it never forces betas the client didn't request (preserving the #3415 fix) nor leaks betas the backend rejects. ([#3974](https://github.com/diegosouzapw/OmniRoute/issues/3974) — thanks @huohua-dev) +- **fix(combo): strict-random spreads fallbacks across healthy peers instead of retrying a failing model** — with the `strict-random` strategy, a model that kept failing was retried on essentially every request and traffic concentrated on a few models. The strategy shuffled only the deck-selected slot 0 and left the fallback remainder in **fixed priority order**, so after any failing deck pick the dispatch chain always fell through to the same top-priority model next. The fallback remainder is now shuffled (like the `random` strategy), so the fallback load — and recovery from a persistently-failing target — spreads evenly across the healthy peers. (Note: when the client always sends `tools` (e.g. OpenCode), the combo still correctly routes only to the tool-capable models in the combo — that capability filtering is by design.) ([#3959](https://github.com/diegosouzapw/OmniRoute/issues/3959) — thanks @KeNJiKunG) +- **fix(dashboard): Logs auto-refresh now works even when the tab loads in the background** — the Logs page never auto-refreshed (only the manual Refresh button worked). The auto-refresh interval gated each tick on a visibility ref seeded once at mount and updated only by a `visibilitychange` event; when the tab mounted while the document reported `hidden` (background load, bfcache restore, embedded/Docker-proxied webviews) and no `visibilitychange` ever fired, the ref stayed `false` forever, so the interval ticked but never fetched. The tick now reads the live `document.visibilityState`, so polling self-heals as soon as the tab is visible — while still pausing when genuinely hidden. ([#3972](https://github.com/diegosouzapw/OmniRoute/issues/3972) — thanks @tjengbudi) +- **fix(providers): LLM7 (and BytePlus) now fetch the live `/models` catalog instead of a stale hardcoded list** — importing an LLM7 key surfaced only a small, outdated model list even though `GET https://api.llm7.io/v1/models` returns the full pro/standard catalog. Both providers carried a correct `modelsUrl` in the registry, but neither was classified by any live-fetch branch of the model-import route (not `openai-compatible-*`, not self-hosted, not in `NAMED_OPENAI_STYLE_PROVIDERS`), so the route skipped the upstream probe and served the registry's 4 hardcoded entries (`source: "local_catalog"`). Added `llm7` and `byteplus` to `NAMED_OPENAI_STYLE_PROVIDERS` so the route probes `/models` with the key and serves the live catalog, falling back to the local catalog only when the upstream fetch fails (so key import never breaks). ([#3976](https://github.com/diegosouzapw/OmniRoute/issues/3976) — thanks @FerLuisxd) +- **fix(resilience): respect connection cooldown stored as a numeric epoch (router kept hammering 429 accounts)** — the router kept dispatching to connections still inside their rate-limit cooldown, causing client timeouts and "connection cooldown isn't respected" reports. Root cause: `rate_limited_until` is a `TEXT` column, but the Antigravity full-quota path (`setConnectionRateLimitUntil`) persists a raw epoch **number**, which SQLite coerces to a numeric string like `"1781696905131.0"`. The account-selection predicate then did `new Date("1781696905131.0")` → `Invalid Date` → `NaN`, so `NaN > Date.now()` was false and the cooling connection was never skipped. The cooldown read predicates (`isAccountUnavailable`, `getEarliestRateLimitedUntil`, `filterAvailableAccounts`, `parseFutureDateMs`) now normalize numeric-epoch strings as well as ISO strings/Date/number via a shared `cooldownUntilMs()` helper — ISO behavior is unchanged. ([#3954](https://github.com/diegosouzapw/OmniRoute/issues/3954)) - **fix(compression/memory): stop memory + compression from poisoning the upstream prompt cache** — with compression and/or memory enabled, requests to caching providers (Anthropic-family) missed the prompt cache on every turn, multiplying cost. Two root causes: (1) memory injection prepended the retrieved memories — which **vary per user query** — at index 0 of the message array, shifting the entire cacheable prefix every turn; memory is now inserted just before the last user message when the request carries `cache_control` breakpoints, keeping the cacheable prefix (system prompt + prior turns) byte-stable. (2) the cache-aware `skipSystemPrompt` flag computed by `getCacheAwareStrategy()` was dropped by `selectCompressionStrategy()` (which can only return a mode), so the system prompt could still be compressed under caching; a new `resolveCacheAwareConfig()` now forces `preserveSystemPrompt` on for caching requests. ([#3936](https://github.com/diegosouzapw/OmniRoute/pull/3936), closes [#3890](https://github.com/diegosouzapw/OmniRoute/issues/3890) — thanks @xenstar / @diegosouzapw) - **fix(providers): register BytePlus ModelArk so its API key can be added** — adding a BytePlus (`ark-…`) key reported "invalid". `byteplus` was present in the provider catalog (`APIKEY_PROVIDERS`) but **never registered in the routing registry**, so key validation fell through to `{ unsupported: true }` → HTTP 400 → the UI rendered every key as invalid (and the provider was unusable for inference). Added a registry entry modeled on the existing Volcengine Ark provider: OpenAI-compatible format, base `https://ark.ap-southeast.bytepluses.com/api/v3` (region `ap-southeast-1`), `Authorization: Bearer` auth, seeded with the catalog's advertised models (Seed 2.0, Kimi K2 Thinking, GLM 4.7, GPT-OSS-120B). ([#3935](https://github.com/diegosouzapw/OmniRoute/pull/3935), closes [#3877](https://github.com/diegosouzapw/OmniRoute/issues/3877) — thanks @nikohd12 / @diegosouzapw) - **fix(providers): Nous Research key validation no longer fails on a stale probe model** — adding a valid Nous Research API key reported "invalid" even though the same key worked via the portal's copy-shell `curl`. The validation probe sent `model: "nousresearch/hermes-4-70b"`, which Nous does not serve, so the API returned `400` and the validator (which only treated `200`/`429` as success) reported the key invalid. The probe now uses the real `Hermes-4-70B` slug, and any non-auth 4xx (`400`/`404`/`422`) is treated as a valid key (the request shape was wrong, not the credentials) — mirroring the longcat/nvidia validators so a future model rename can't re-break key validation. ([#3934](https://github.com/diegosouzapw/OmniRoute/pull/3934), closes [#3881](https://github.com/diegosouzapw/OmniRoute/issues/3881) — thanks @FerLuisxd / @diegosouzapw) @@ -2056,8 +2918,13 @@ _See English CHANGELOG for v3.8.32 details._ ### 🧹 Internal / Quality / Docs - **fix(ci): grant `contents: write` to the npm publish job for SBOM attach** — the v3.8.25 TokenPermissions hardening set the npm-publish `publish` job to `contents: read`, but its "Attach SBOM to GitHub Release" step (`gh release upload`) needs `contents: write` and failed with HTTP 403 on the v3.8.25 release (npm / GitHub Packages / opencode-plugin / Docker / Electron all published fine; only the SBOM attach broke — the v3.8.25 SBOM was attached manually). ([#3874](https://github.com/diegosouzapw/OmniRoute/pull/3874) — thanks @diegosouzapw) +- **fix(providers): keep the `/v1/models` catalog alias-only (release-time follow-up to #3870)** — #3870 made `generateModels()` also key the registry by each provider's raw id, which surfaced phantom `opencode/*` entries in `/v1/models` that collide with the `opencode/` → opencode-zen route (a regression vs v3.8.25, caught by the #2798 catalog regression test). `getProviderModels()` now resolves a raw provider id to its alias at lookup time instead of mirroring raw-id keys into the model namespace, preserving #3870's intent (`getProviderModels("github")` returns the same models as the `gh` alias) without polluting the public catalog. ([#3870](https://github.com/diegosouzapw/OmniRoute/pull/3870) — thanks @diegosouzapw / @YunyunZhai) - **ci(quality): make zizmor / gitleaks / osv scanners functional + freeze advisory baselines** — the supply-chain scanners are now actually executed (correct install + invocation) with frozen advisory baselines so new findings surface as diffs. ([#3947](https://github.com/diegosouzapw/OmniRoute/pull/3947) — thanks @diegosouzapw) - **ci(quality): fix scanner install + size-limit preset, promote `codeqlAlerts` to blocking** — corrected the scanner install and the size-limit preset, and promoted the `codeqlAlerts` ratchet from advisory to blocking. ([#3945](https://github.com/diegosouzapw/OmniRoute/pull/3945) — thanks @diegosouzapw) +- **ci(quality): add an OpenAPI breaking-change gate (oasdiff, advisory) + fix dangling `$ref`s** — a CI gate diffs the OpenAPI spec against the base branch (`BASE_REF`) with oasdiff to surface breaking API changes, and the spec's dangling `$ref`s were repaired. ([#3951](https://github.com/diegosouzapw/OmniRoute/pull/3951) — thanks @diegosouzapw) +- **ci(quality): add a schemathesis API-fuzz nightly (advisory)** — a nightly schemathesis property/fuzz pass against the OpenAPI spec (Quality Gates Fase 8 · Bloco B.4, advisory). ([#3956](https://github.com/diegosouzapw/OmniRoute/pull/3956) — thanks @diegosouzapw) +- **ci(quality): flip the secret / workflow / bundle-size scanners to ratchet-blocking** — the secret-scan, workflow-lint and bundle-size gates moved from advisory to ratchet-blocking, with their baselines frozen and unit coverage for each scanner (Etapa 2). ([#3961](https://github.com/diegosouzapw/OmniRoute/pull/3961) — thanks @diegosouzapw) +- **chore(quality): re-baseline the ESLint-warning ratchet (3760 → 3769)** — absorbs the v3.8.26-cycle warning drift into `quality-baseline.json` (manual re-baseline, never an automatic upward ratchet). ([#3962](https://github.com/diegosouzapw/OmniRoute/pull/3962) — thanks @diegosouzapw) - **ci(quality): wire Stryker mutation testing as an advisory nightly** — Stryker mutation testing runs nightly (advisory) — Quality Gates Fase 7 · Task 11. ([#3898](https://github.com/diegosouzapw/OmniRoute/pull/3898) — thanks @diegosouzapw) - **ci(quality): freeze per-module coverage floors + wire require-tighten (advisory)** — per-module coverage floors are frozen with an advisory "require-tighten" check that flags modules drifting below their floor. ([#3901](https://github.com/diegosouzapw/OmniRoute/pull/3901) — thanks @diegosouzapw) - **ci(quality): enforce the stale-allowlist check on `check-known-symbols`** — stale allowlist entries (suppressing a symbol that no longer exists) now fail the gate — Fase 6A.3 follow-up. ([#3899](https://github.com/diegosouzapw/OmniRoute/pull/3899) — thanks @diegosouzapw) @@ -2079,7 +2946,7 @@ _See English CHANGELOG for v3.8.32 details._ ## [3.8.25] — 2026-06-14 -### ✨ New Features +### ✨ 新功能 - **feat(compression): pluggable compression engines + async pipeline + Compression Studios** — a new prompt-compression subsystem with selectable engines (Lite / Aggressive / Ultra), an asynchronous compression pipeline wired into the chat core, and "Compression Studios" tooling for inspecting and tuning compression. ([#3848](https://github.com/diegosouzapw/OmniRoute/pull/3848)) - **feat(compression-ui): unified compression configuration UI** — a Compression Hub with per-engine pages (Lite / Aggressive / Ultra), a combos editor, a dedicated sidebar entry, and live-WS default-on. ([#3860](https://github.com/diegosouzapw/OmniRoute/pull/3860)) @@ -2137,93 +3004,804 @@ _See English CHANGELOG for v3.8.32 details._ --- -## [3.8.24] — TBD +## [3.8.24] — 2026-06-13 -_See English CHANGELOG for v3.8.24 details._ +### ✨ 新功能 + +- **feat(plugins): custom plugin marketplace support** — the plugin registry now fetches from a custom URL set in system settings (`pluginMarketplaceUrl`), falling back to the local seed registry when none is configured. Adds a `GET /api/plugins/marketplace` endpoint and a revamped Marketplace UI. ([#3656](https://github.com/diegosouzapw/OmniRoute/pull/3656) — thanks @oyi77) +- **feat(api-keys): strict-mode controls for the Claude Code default routing path** — `Claude Code default` is now an explicit `cc/*` model permission, so an API key can allow the default path while blocking specific model families (e.g. Fable) in strict mode. Previously the default path received dynamic/unprefixed models (`sonnet`, `opus`, `claude-opus-4-8[1m]`, …) that no single permission represented, so it broke under strict permissions. ([#3776](https://github.com/diegosouzapw/OmniRoute/pull/3776) — thanks @Witroch4) +- **feat(flags): expose the emergency budget fallback in the Feature Flags page** — `OMNIROUTE_EMERGENCY_FALLBACK` is now a runtime boolean (enabled by default, applied without restart) resolved through the feature-flag stack, so a DB override can toggle it while still honoring the raw env fallback. Follow-up to [#3741](https://github.com/diegosouzapw/OmniRoute/pull/3741) by @zoispag. ([#3752](https://github.com/diegosouzapw/OmniRoute/pull/3752) — thanks @rdself) +- **feat(reasoning): preserve `xhigh` reasoning effort by default** — `xhigh` now passes through unless a model explicitly sets `supportsXHighEffort: false`, with the existing `max` normalization kept separate. ([#3756](https://github.com/diegosouzapw/OmniRoute/pull/3756) — thanks @rdself) +- **feat(codex): inject OmniRoute memory into Codex Responses WebSocket requests** — retrieved memory is injected into the Responses WebSocket prepare request via the `instructions` field, with the retrieval query derived from the latest user input (skipping tool/reasoning payloads) and duplicate-safe injection. ([#3749](https://github.com/diegosouzapw/OmniRoute/pull/3749) — thanks @kkkayye) +- **feat(dashboard): free provider rankings page** — a new dashboard page (with sidebar entry) that ranks free providers (no-auth / OAuth / API-key) by their models' Arena ELO / intelligence scores, joining the provider registry with the `model_intelligence` table via fuzzy model-name matching. Pure computation over existing data — no external calls. ([#3799](https://github.com/diegosouzapw/OmniRoute/pull/3799) — thanks @pizzav-xyz) + +### 🔒 Security + +- **security(proxy): IPv6-only egress enforcement + closing IP-leak paths (L1/L2/L3)** — de-brackets IPv6 literals at the SOCKS host and the proxy-health tcpCheck (so `socks5://[2001:db8::1]` and any v6-literal proxy connect instead of dying with `ENOTFOUND`), adds a per-proxy `family` policy (`auto`/`ipv4`/`ipv6`), and enforces it end-to-end across SOCKS5/HTTP/HTTPS × global/provider/key × literal/hostname. 16 commits, TDD+BDD, 73 tests. ([#3777](https://github.com/diegosouzapw/OmniRoute/pull/3777)) +- **security(marketplace): harden the custom-URL SSRF guard** against three bypasses found by automated security review — IPv6/AAAA records (only `dns.resolve4` was checked, so a private AAAA record or an IPv6 literal slipped through), redirect-following (a public URL could 30x to an internal one), and DNS-rebinding TOCTOU. The guard now resolves A+AAAA via the canonical `isPrivateHost`, routes the fetch through `safeOutboundFetch` (public-only, blocks redirects to private hosts), and re-validates on fetch. Reachable only with management auth + a custom `pluginMarketplaceUrl`. Follow-up to [#3656](https://github.com/diegosouzapw/OmniRoute/pull/3656). ([#3774](https://github.com/diegosouzapw/OmniRoute/pull/3774)) +- **security: resolve all open CodeQL + Dependabot alerts** — CodeQL `js/insufficient-password-hash` (the semantic-cache `apiKeyId` is now a plaintext key prefix, `${apiKeyId}.${digest}`, instead of being folded into the SHA-256 digest, clearing the false positive while preserving per-key cache isolation) and a URL-substring check tightened to an exact host match; Dependabot `esbuild < 0.28.1` pinned via override in both workspaces. ([#3778](https://github.com/diegosouzapw/OmniRoute/pull/3778)) The remaining `js/incomplete-url-substring-sanitization` instances in the api-key proxy-context test were also cleared by asserting on the parsed URL host/port instead of a substring `includes`. (thanks @diegosouzapw) + +### 🐛 Fixed + +- **fix(dashboard): surface the Plugins page (plugin manager + marketplace) in the sidebar** — the plugins page (`/dashboard/plugins`), which hosts the custom plugin marketplace shipped in [#3656](https://github.com/diegosouzapw/OmniRoute/pull/3656), had no menu entry and was reachable only by typing the URL. It now appears under **Agentic Features**. (thanks @diegosouzapw) +- **fix(proxy): add the IP-family selector (auto / IPv4-only / IPv6-only) to the proxy form** — the per-proxy `family` egress policy from [#3777](https://github.com/diegosouzapw/OmniRoute/pull/3777) was backend-only (the dashboard had no control, so every proxy stayed on `auto`). The proxy registry form now exposes the selector and the create/update schema accepts it, so IPv6-only egress can be enabled from the UI. (thanks @diegosouzapw) +- **fix(combo): deep audit of the combo + quota-shared routing system** — repairs 5 dead/broken rules (streaming-USD cost recording, quota-pool usage provider resolution, provider-diversity wiring, `maxComboDepth` threading, and scoring clamp/NaN-safety incl. `connectionDensity`) and revives the dead `tierAffinity`/`specificityMatch` scoring factors — root cause was a `require()` that throws under ESM, so both factors silently collapsed to `0.5`; now a static import. Validates every auto-router strategy (cost / latency / sla-aware / lkgp / `selectWithStrategy` + aliases) and the predictive-TTFT decision, adds E2E coverage (3-hop priority failover, per-target timeout failover, real `strategy:auto` dispatch), and introduces opt-in complexity-aware routing (2026) layered over the existing specificity detector. Per-target credential+proxy isolation verified clean (`AsyncLocalStorage`). 4 TDD waves, 10 new/updated test files. ([#3779](https://github.com/diegosouzapw/OmniRoute/pull/3779) — thanks @diegosouzapw) +- fix(anthropic): normalize sampling params under extended thinking — Claude models with extended thinking (e.g. Opus 4.8 via the Claude Code provider) returned **HTTP 400** when a request carried non-default `temperature`/`top_p` (`temperature may only be set to 1 …`, `top_p must be ≥ 0.95 or unset …`). Tools like VS Code Copilot's "Ollama" BYOK send `temperature: 0.7` + `top_p: 0.9`, so every thinking-enabled Claude request failed; the proxy now drops/normalizes these params at the chokepoint so the request succeeds. ([#3780](https://github.com/diegosouzapw/OmniRoute/pull/3780) — thanks @zhiru) +- fix(sse): pass Claude passthrough `thinking` blocks through unchanged — the Anthropic-native Claude OAuth passthrough rewrote every assistant `thinking` block to `redacted_thinking`, which the Messages API rejects (submitted thinking blocks are validated against the original response), so every multi-turn request with extended thinking failed with `400 … thinking blocks … cannot be modified` (very visible on long Claude Code tool-loops). The blocks are now passed through verbatim; the signature is validated server-side and stays valid on replay (including across an OAuth token switch), so the redaction was unnecessary. ([#3775](https://github.com/diegosouzapw/OmniRoute/pull/3775) — thanks @havockdev) +- fix(mcp): resolve the bundled MCP server entry from `dist/` instead of the legacy `app/` path — `omniroute --mcp` crashed on npm installs with `ERR_MODULE_NOT_FOUND: Cannot find package @/lib` because `bin/mcp-server.mjs` looked for the compiled entry under `app/` (a VPS-deploy path that never exists in the npm package) and fell back to the un-bundled `.ts`. ([#3765](https://github.com/diegosouzapw/OmniRoute/pull/3765) — thanks @megamen32) +- fix(sse): preserve streamed tool-call arguments end-to-end — incremental tool-call argument deltas could be truncated/duplicated through SSE parsing, transformation and response translation, corrupting tool calls in CLI tool-use output. Dedup now only collapses unambiguous snapshots. ([#3762](https://github.com/diegosouzapw/OmniRoute/pull/3762), closes [#3701](https://github.com/diegosouzapw/OmniRoute/issues/3701) — thanks @Mffff4) +- fix(dashboard): repair the Logs page light-mode controls — the "Clean history" button keeps readable contrast (while preserving the red destructive affordance), request-row hover uses a cool blue tint so it no longer reads like a failed-request row, and the custom auto-refresh interval persists in `localStorage` (clamped to 1–300s). Also refreshes the Feature Flags light-mode treatment. ([#3760](https://github.com/diegosouzapw/OmniRoute/pull/3760) — thanks @rdself) +- fix(dashboard): make the Request Logs "Clean history" action perform a full request-history purge — clears `call_logs`, legacy `request_detail_logs`, and local JSON artifacts under `DATA_DIR/call_logs` (including orphaned artifact files) via a dedicated maintenance API route, instead of a retention-only cleanup. ([#3751](https://github.com/diegosouzapw/OmniRoute/pull/3751) — thanks @rdself) +- fix(cli): detect CLI tools installed outside the GUI PATH on macOS. macOS GUI/Electron apps don't inherit the user's login-shell PATH, so Homebrew (`/opt/homebrew/bin`), nvm and volta-installed CLIs (Cline, Codex, OpenCode, Continue, Hermes, …) were reported "not installed" and the Cline runtime couldn't be spawned. CLI detection (`omniroute doctor`) and the provider-runtime lookup now enrich the lookup PATH with the login shell's PATH (`$SHELL -ilc`, darwin-only, cached, fail-safe). ([#3321](https://github.com/diegosouzapw/OmniRoute/issues/3321) — thanks @mikmaneggahommie) +- fix(dashboard): repair the Playground model selector for custom OpenAI/Anthropic-compatible providers. Two bugs left it unusable: (1) when the provider's catalog prefix didn't resolve, the list was filtered by the raw connection id (which matches nothing) so the selector showed nothing; and (2) selecting a provider reset the model to empty and nothing ever picked a default, so the chat failed with "Set a model in the config pane". The selector now falls back to the full catalog instead of emptying, and auto-selects the first available model. ([#3731](https://github.com/diegosouzapw/OmniRoute/issues/3731), [#3009](https://github.com/diegosouzapw/OmniRoute/issues/3009) — thanks @tjengbudi) + +- fix(cursor): send a `ModelDetails` envelope (`model_id` + `display_model_id` + `display_name`) in the Cursor agent request, alongside the existing `RequestedModel`. Pinned Cursor Claude/GPT _thinking_ variants (e.g. `cursor/claude-opus-4-7-thinking-xhigh`) were returning an empty turn → `502 Provider returned empty content`, because Cursor needs the `ModelDetails` envelope (which `cursor-agent`'s real wire format sends) to resolve them; `RequestedModel` alone only resolves server-routed ids (`auto`/`composer-*`). The `-fast` parameter path on `RequestedModel` is preserved. ([#3714](https://github.com/diegosouzapw/OmniRoute/issues/3714)) + +- fix(docs): correct the OAuth redirect URI in the Fly.io deployment guide. It told users to register `/api/oauth//callback`, but OmniRoute's browser OAuth flow uses a single `/callback` handler (there is no per-provider callback route). The mismatch caused GitLab Duo (and any OAuth provider) to reject the flow with "The redirect URI included is not valid". Added a regression guard test. ([#3732](https://github.com/diegosouzapw/OmniRoute/issues/3732)) + +- fix(providers): give Ollama Cloud's `kimi-k2.7-code` its real capabilities (262K context, 262K max output, vision + thinking + tools) instead of the degraded `128000 / 8192` defaults. The model had no spec/registry entry, so importing it via "Import from /models" (whose `/v1/models` upstream returns no per-model metadata) left it as a bare custom model with fallback capabilities. Added a global `kimi-k2.7-code` model spec (parity with `kimi-k2.6`) plus a registry entry on `ollama-cloud`. ([#3761](https://github.com/diegosouzapw/OmniRoute/issues/3761) — thanks @SultanKs4) + +- fix(providers): repair qwen-web (chat.qwen.ai) connection validation, which failed with a misleading `provider.validation.ssrf_blocked` error. qwen-web had no specialty validator, so the generic OpenAI-compatible path probed a non-existent `/api/v2/models` URL that answers with a 307 redirect — the outbound guard blocked the redirect and the route mislabeled it as an SSRF security block. Added a `qwen-web` specialty validator that probes the real session endpoint (`GET /api/v2/user`, mirroring the executor's anti-bot headers + cookie-jar replay). Also hardened `toValidationErrorResult` so a blocked redirect is only flagged `securityBlocked` when its target is a private/internal host — a benign 3xx to a public host is no longer mislabeled as an SSRF attempt (this affected every web-cookie provider, not just qwen). ([#3288](https://github.com/diegosouzapw/OmniRoute/issues/3288), [#3758](https://github.com/diegosouzapw/OmniRoute/issues/3758)) + +- fix(oauth): stop nulling the stored `refresh_token` of non-rotating providers when a proactive health-check refresh fails with `invalid_grant`. The destructive `refreshToken: null` write in `tokenHealthCheck` was only meant for rotating one-time-use tokens (Codex/OpenAI), but it also fired for Google-family providers (gemini-cli / antigravity / gemini) whose refresh tokens are non-rotating. Once nulled, the connection reported "No valid refresh token available" and could never recover even after re-activation. The token is now preserved (gated on `isRotatingProvider`) so it stays as the recovery artifact. ([#3679](https://github.com/diegosouzapw/OmniRoute/issues/3679) — thanks @3xa228148) + +- fix(dashboard): self-host the Material Symbols icon font instead of loading it from the Google Fonts CDN. On networks where `fonts.googleapis.com` is unreachable (e.g. mainland China), the icon ligature font never loaded, so every icon rendered as its literal text name (`smart_toy`, `visibility`, …) and the layout broke — especially after importing many models. The font is now bundled locally via the `material-symbols` package (`@import "material-symbols/outlined.css"` in `globals.css`), removing the runtime CDN dependency. ([#3695](https://github.com/diegosouzapw/OmniRoute/issues/3695) — thanks @lqyiwwx) + +- fix(antigravity): skip Google One AI credits retry on `full_quota_exhausted` verdict — antigravity executor now calls `decide429()` before attempting the credits retry so that a quota-exhausted account (24h cooldown) bypasses the extra upstream HTTP call instead of hanging for up to ~41s. Also persists the cooldown in the DB via `setConnectionRateLimitUntil` so post-restart routing skips exhausted connections without re-learning the hard way. Bonus: `antigravity429Engine` now recognises the real Antigravity "Individual quota reached. Contact your administrator to enable overages." error message as `quota_exhausted`. ([#3707](https://github.com/diegosouzapw/OmniRoute/issues/3707) — thanks @andrea-kingautomation) + +- fix(cli): `ServerSupervisor.handleExit` now coerces the exit code to a number before calling `process.exit()` — Node.js v24 throws `TypeError [ERR_INVALID_ARG_TYPE]` when `process.exit()` receives a string (e.g. `'ENOENT'` from a spawn `error` event's `err.code`). The `error` callback also now passes `-1` instead of the raw `err.code`, which is an OS error string rather than a meaningful exit code. ([#3748](https://github.com/diegosouzapw/OmniRoute/issues/3748)) + +### 📝 維護 + +- **feat(intelligence): enable Arena ELO sync by default + wire it into the live startup path** — the Arena AI leaderboard ELO sync (`ARENA_ELO_SYNC_ENABLED`) that powers the new Free Provider Rankings page ([#3799](https://github.com/diegosouzapw/OmniRoute/pull/3799)) is now **on by default** (was opt-in). It was also only initialized from `server-init.ts`, which the Next standalone runtime never executes (it boots through `instrumentation-node.ts`), so the sync never actually ran in production — it's now initialized from the live instrumentation path. Fetches from `api.wulong.dev` on startup (non-blocking, never fatal) and refreshes daily; set `ARENA_ELO_SYNC_ENABLED=false` to opt out of the outbound sync. (thanks @diegosouzapw) +- **chore(quality): Quality Gates → 100%** — completes Fase 6A (systemic hardening: a `stale-allowlist` helper applied across ~10 gates, `docs/architecture/QUALITY_GATES.md`, and ratchet engine v2 with `--require-tighten` + per-metric `eps`) and the entire Fase 7 (20 security/dead-code/mutation/tooling gates), with the Fase 8 plan documented. ([#3757](https://github.com/diegosouzapw/OmniRoute/pull/3757)) +- **docs: close the remaining documentation gaps** for proxy operations, skills internals, the memory engine, RTK customization, and compression extensibility (post-#3438 audit, 5 areas in one PR). ([#3453](https://github.com/diegosouzapw/OmniRoute/pull/3453) — thanks @oyi77) +- **chore(quality): re-baseline `providerRegistry.ts` file-size** (4692→4703) after #3768's Ollama Cloud `kimi-k2.7-code` capability fix grew the file past the frozen baseline, turning the release's own Fast Quality Gates red. No source file is touched. ([#3770](https://github.com/diegosouzapw/OmniRoute/pull/3770)) +- **fix(publish): clean the `@omniroute/opencode-plugin` `node_modules` after the tsup build** — the hard links npm creates on Linux ended up in the published tarball as LINK entries, which the npm registry rejects with `E415 "Hard link is not allowed"`. The dependencies are only needed for the build, never shipped. (thanks @diegosouzapw) +- **chore(docs): prune internal planning/spec artifacts and sync the i18n CHANGELOG to 3.8.24.** (thanks @diegosouzapw) +- **test: align two unit tests left stale by this cycle's behavior changes** — `executor-codex` now asserts that GPT 5.4 Mini's `xhigh` reasoning effort passes through unchanged (the intended #3756 default; the model ships an `xhigh` catalog variant and carries no `supportsXHighEffort:false` flag) instead of the old downgrade-to-`high`, and `plugins-route-error-sanitization` now covers the new `/api/plugins/marketplace` route from #3656 (verified compliant with Hard Rule #12: imports + uses `buildErrorBody`). No production behavior change. (thanks @diegosouzapw) +- **docs: refresh root + `docs/` documentation to the current architecture** — a full codebase audit corrected stale counts/facts (MCP 43→87 tools / ~13→30 scopes, DB 45+→83 modules / 55→97 migrations, routing 14→15 strategies, A2A 5→6 skills, Node `>=22.0.0 <23 || >=24.0.0 <27`, TypeScript 6.0) across `CLAUDE.md`, `AGENTS.md`, `README.md`, and the `docs/` index, and documented the plugin marketplace, Notion/Obsidian, and embedded-services subsystems. (thanks @diegosouzapw) --- -## [3.8.22] — TBD +## [3.8.23] — 2026-06-12 -_See English CHANGELOG for v3.8.22 details._ +### ✨ 新功能 + +- **Emergency budget fallback: opt-out env switch `OMNIROUTE_EMERGENCY_FALLBACK`** ([#3741](https://github.com/diegosouzapw/OmniRoute/pull/3741) — thanks @zoispag): adds an `OMNIROUTE_EMERGENCY_FALLBACK` environment variable that disables the budget-exhaustion emergency reroute to `nvidia/openai/gpt-oss-120b` entirely when set to `false` or `0`. Default behavior (enabled) is unchanged. + +- **Auto-Combo: live model intelligence scoring via Arena ELO + models.dev** ([#3660](https://github.com/diegosouzapw/OmniRoute/pull/3660) — thanks @pizzav-xyz): replaces the static fitness lookup with a 5-layer resolution chain (user override → Arena ELO → models.dev tiers → hardcoded map → neutral fallback). A sync pipeline auto-fetches Arena AI leaderboard ELO scores and derives intelligence tiers from models.dev capabilities; combo picks now update as leaderboard rankings change without any manual configuration. + +- **Vertex AI: dynamic model discovery** ([#3712](https://github.com/diegosouzapw/OmniRoute/pull/3712) — thanks @artickc): the `vertex` provider now queries the Generative Language models API at runtime to surface the full account catalog — including image-generation models (Imagen, `gemini-*-image`), embeddings, and partner models — instead of returning only the small hardcoded registry list. + +- **Vertex AI: self-tracked USD spend on the Limits page** ([#3724](https://github.com/diegosouzapw/OmniRoute/pull/3724) — thanks @artickc): since the Google Cloud Billing API is inaccessible via the proxy credential, Vertex connections now track their own cumulative USD spend locally (based on token-cost accounting) and display it on the Limits page as "$ used since account added." + +- **Gemini: rate-limit metadata for known per-model RPM/RPD caps** ([#3686](https://github.com/diegosouzapw/OmniRoute/pull/3686) — thanks @hartmark): injects known rate-limit headers (RPM/RPD) for Gemini models that carry per-model limits (e.g. Gemma 4's 15 RPM / generous RPD), so the cooldown engine applies them correctly instead of locking out the whole account on daily-limit hits. + +- **Model Lockout: full settings UI with success-decay recovery** ([#3629](https://github.com/diegosouzapw/OmniRoute/pull/3629) — thanks @Chewji9875): end-to-end wiring of the per-model lockout feature — settings UI (enable/disable, configure thresholds), backend integration, structured error classification, and a success-decay mechanism that gradually recovers a locked model's fitness as successful calls accumulate. Lockout now applies to all providers when enabled, not just per-model-quota providers. + +- **Provider display modes — All / Configured / Compact** ([#3743](https://github.com/diegosouzapw/OmniRoute/pull/3743) — thanks @rdself): adds a three-state display mode control to the Providers page. "All" shows every registered provider; "Configured" shows only providers with at least one connection; "Compact" shows configured providers in a condensed card layout for denser views. + +- **API key cost drilldown + quota % used** ([#3742](https://github.com/diegosouzapw/OmniRoute/pull/3742) — thanks @Witroch4): the API Keys page now shows a per-key cost breakdown and the percentage of quota consumed for each key. + +### 🔧 錯誤修正 + +- **`@omniroute/opencode-plugin` bundled in the npm tarball + `omniroute setup opencode` CLI command** ([#3726](https://github.com/diegosouzapw/OmniRoute/pull/3726) — thanks @herjarsa): the plugin was never compiled as part of the publish pipeline, requiring manual extraction. Now ships pre-built inside the `omniroute` package and installed via `omniroute setup opencode` (copies plugin into `~/.config/opencode/plugins/omniroute/`, updates `opencode.json` idempotently). Also fixes `provider.models` baseURL resolution — checks `_provider.options.baseURL` as a third fallback so partner/tiered providers no longer return zero models. ([#3711](https://github.com/diegosouzapw/OmniRoute/issues/3711)) + +- **MiMoCode 403 "Illegal access" fixed** ([#3728](https://github.com/diegosouzapw/OmniRoute/pull/3728) — thanks @felipesartori): the Xiaomi free endpoint gates requests on a recognized MiMoCode system-prompt signature; OmniRoute forwarded raw requests without the marker, causing 403 on every call. The executor now injects the required anti-abuse signature. + +- **"Test all models" flow: i18n crash, status icons, auto-hide** ([#3729](https://github.com/diegosouzapw/OmniRoute/pull/3729) — thanks @felipesartori): three bugs in the provider-detail test-all-models flow — `providerText()` crash because the `testAllResults` template requires `{ok, total}` but callers passed `{ok, error}`; missing `online`/`offline` status icons on model rows; results panel not auto-hiding after run completes. + +- **OAuth token-refresh invalidation loop fixed** ([#3692](https://github.com/diegosouzapw/OmniRoute/pull/3692) — thanks @diegosouzapw): `refreshClaudeOAuthToken` returned `null` instead of the error sentinel on non-canonical 400 bodies, causing the caller to retry every 60 seconds — observed as 1,352 consecutive refresh attempts on one Claude account. Fixed alongside hardening of `safeResolveProxy` (proxy resolution errors now warn instead of silently falling back to DIRECT) and adding egress-IP visibility to `safeLogEvents`. + +- **`safeLogEvents` async hotfix** (thanks @diegosouzapw): PR #3692 introduced a `lazy await import(proxyEgress)` inside a sync `safeLogEvents` — an ES syntax error that broke every consumer loading `chatHelpers` via `tsx` and caused 14 tests to fail at module load. Made `safeLogEvents` async; `void`-ed the single `chat.ts` call site. + +- **Kiro: quota tracking for IAM Identity Center accounts** ([#3722](https://github.com/diegosouzapw/OmniRoute/pull/3722) — thanks @artickc): `getKiroUsage` returned "0 used" for IAM Identity Center accounts (and `kiro-cli` imports) because those connections frequently lack a persisted `profileArn`. Now falls back to a name-based profile lookup so quota displays correctly. + +- **Empty Claude SSE stream now surfaces a real error** ([#3689](https://github.com/diegosouzapw/OmniRoute/pull/3689) — thanks @TechNickAI): when a Claude stream completed with lifecycle events but no content block, the proxy returned a synthetic `"[Proxy Error] The upstream API returned an empty response"` as a _successful_ assistant message. Now emits a proper SSE error event; the missing-finalizer synthetic path is preserved for streams that already produced content. + +- **Vertex AI Express-mode API keys** ([#3690](https://github.com/diegosouzapw/OmniRoute/pull/3690) — thanks @artickc): the Vertex executor rejected every non-JSON credential with "Vertex AI requires a valid Service Account JSON." Now accepts Express-mode API key strings (`AIza*`) alongside Service Account JSON, routing them through the correct token endpoint. + +- **Anthropic: strip `top_p` when `temperature` is set** ([#3691](https://github.com/diegosouzapw/OmniRoute/pull/3691) — thanks @zhiru): Anthropic API rejects requests containing both `temperature` and `top_p`; VS Code's Claude extension sends both in every request, causing 400s on all routed calls. The OpenAI→Claude translator now drops `top_p` when `temperature` is present. + +- **Combo reasoning token buffer: conservative application + feature flag** ([#3700](https://github.com/diegosouzapw/OmniRoute/pull/3700) — thanks @rdself): tightens the #3588 buffer (only applies when the model is explicitly thinking-capable, has a non-default known output cap, and the full buffered value fits inside that cap) and adds a `reasoningTokenBufferEnabled` feature flag in combo defaults so users can fully disable it from Settings. + +- **Emergency budget fallback: cross-provider credential leak fixed** ([#3699](https://github.com/diegosouzapw/OmniRoute/pull/3699) — thanks @diegosouzapw): the executor-level emergency hop re-sent the failing provider's API key to the emergency provider's endpoint (e.g. the OpenAI `Authorization` header going to `integrate.api.nvidia.com`). Now orchestrated exclusively by the routing layer, which resolves credentials for the emergency provider via account selection and no longer fires inside combo targets. + +- **`/v1/messages/count_tokens` now honors the connection's proxy assignment** ([#3699](https://github.com/diegosouzapw/OmniRoute/pull/3699) — thanks @diegosouzapw): token count calls went DIRECT regardless of configured proxies, leaking the host IP for proxy-isolated setups. Now wraps execution in `runWithProxyContext`, exactly like chat execution. + +- **Gemini: context-mode fallback for signatureless tool calls** ([#3688](https://github.com/diegosouzapw/OmniRoute/pull/3688) — thanks @diegosouzapw): fixes HTTP 400 on multi-turn thinking-model tool calls when `thought_signature` is unavailable — standard Gemini provider now falls back to context mode instead of sending the unsigned call. + +- **Antigravity: preserve `gemini-3.1-pro` High/Low budget tiers** ([#3696](https://github.com/diegosouzapw/OmniRoute/pull/3696) — thanks @diegosouzapw): upstream accepts the suffixed ids; stop collapsing to bare `gemini-3.1-pro`. + +- **Stream combo: fail over on empty/content-filtered response** ([#3685](https://github.com/diegosouzapw/OmniRoute/pull/3685) — thanks @diegosouzapw): streaming combos now route to the next target instead of surfacing a blank reply. + +- **Qwen Web: migrated to v2 chat API** ([#3723](https://github.com/diegosouzapw/OmniRoute/pull/3723) — thanks @diegosouzapw): the legacy `/api/chat/completions` endpoint was retired upstream returning `504` HTML from Alibaba's gateway for all requests. The executor now uses the two-step v2 flow (`/api/v2/chats/new` → `/api/v2/chat/completions?chat_id=`), replays the full browser cookie jar (cna + ssxmod_itna/itna2 + token) required by Alibaba's WAF instead of only a Bearer token, parses phase-based SSE (think→reasoning, answer→content), and refreshes the model catalog to current ids (`qwen3.7-max`, `qwen3.7-plus`, `qwen3.6-plus`; legacy ids kept as aliases). 17 unit tests. (Closes [#3288](https://github.com/diegosouzapw/OmniRoute/issues/3288)) + +- **Responses API: `stream` defaults to `false` when omitted (spec compliance)** ([#3708](https://github.com/diegosouzapw/OmniRoute/pull/3708) — thanks @diegosouzapw): `/v1/responses` requests that omit `stream` no longer 502 (`STREAM_EARLY_EOF`) when the upstream returns a valid JSON response. `resolveStreamFlag` now applies the OpenAI Responses API spec default (stream=false) in addition to the existing Anthropic Messages API default — previously only `sourceFormat=claude` triggered this path, leaving `sourceFormat=openai-responses` to fall through to the wildcard-Accept heuristic (`Accept: */*` → streaming intent), which caused spec-compliant upstreams that return JSON to appear as a dead stream. Codex CLI (always sends `stream: true`) and explicit SSE clients (`Accept: text/event-stream`) are unaffected. + +- **Semantic cache: scope to requesting API key** ([#3740](https://github.com/diegosouzapw/OmniRoute/pull/3740) — thanks @diegosouzapw): two callers with different API keys sending the same prompt and model no longer receive each other's cached responses. `generateSignature` now includes the `api_key_id` dimension in the SHA-256 hash; unauthenticated requests (no API key) remain isolated from keyed requests. Existing cache entries (generated without the key dimension) are cleared by migration `098`. + +- **Model-family fallback: dot-notation model IDs now resolve correctly** (thanks @diegosouzapw): `getNextFamilyFallback` normalizes dots to hyphens for the initial lookup but also falls back to the bare model name, supporting IDs like `gemini-3.1-pro-high` whose dots are part of the literal name. Previously, `gemini-3.1-pro-high` silently returned null and bypassed the entire family. + +### ♻️ Code Quality + +- **Dashboard god-component (#3501): Phases 1g → 1t complete — ≤800 LOC target reached** ([#3717](https://github.com/diegosouzapw/OmniRoute/pull/3717), [#3721](https://github.com/diegosouzapw/OmniRoute/pull/3721), [#3725](https://github.com/diegosouzapw/OmniRoute/pull/3725), [#3727](https://github.com/diegosouzapw/OmniRoute/pull/3727) — thanks @diegosouzapw): four extraction phases bring `ProviderDetailPageClient.tsx` from 4,062 to 781 LOC — the ≤800 target set at the start of the refactor. Extracted OAuth flow helpers, quota display, traffic-inspector panel, logs viewer, combo-target editor, and remaining inline UI into standalone components under `providers/[id]/components/`. + +### 🌍 Internationalization + +- **zh-CN: comprehensive Simplified Chinese translation improvements** ([#3736](https://github.com/diegosouzapw/OmniRoute/pull/3736) — thanks @sdfsdfw2): broad pass on Simplified Chinese UI strings for accuracy and consistency. + +### 📝 維護 + +- **CI: bump GitHub Actions artifacts/cache actions to latest** (thanks @diegosouzapw): `actions/download-artifact` 4→8 ([#3733](https://github.com/diegosouzapw/OmniRoute/pull/3733)), `actions/cache` 4→5 ([#3734](https://github.com/diegosouzapw/OmniRoute/pull/3734)), `actions/upload-artifact` 4→7 ([#3735](https://github.com/diegosouzapw/OmniRoute/pull/3735)). + +- **File-size ratchet baseline reconciled** ([#3705](https://github.com/diegosouzapw/OmniRoute/pull/3705) — thanks @diegosouzapw): freezes 27 inherited/previously-grown files at their current LOC and registers `providerLimits.ts` in the gate; ongoing shrink tracked via #3501. + +- **docs: add FUNDING.yml and README Support section** ([#3698](https://github.com/diegosouzapw/OmniRoute/pull/3698) — thanks @diegosouzapw) + +- **docs(changelog): restore `#3590` bullet lost on the v3.8.20 release branch** (thanks @diegosouzapw): the fix reached `main` pre-tag via cherry-pick `#3591`, but its changelog bullet only existed on `release/v3.8.20` after the squash-merge; restored per the 2026-06-12 release-branch leftover audit. + +### ✅ Tests + +- **Combo strategy fallback coverage** (`tests/unit/combo-strategy-fallbacks.test.ts`, 11 tests): fill-first / p2c / random / cost-optimized / strict-random fallback paths (previously happy-path only), price-tie stability, stale strict-random deck degradation, unknown-strategy normalization to priority, and circuit-breaker HALF_OPEN recovery inside the combo loop + `preScreenTargets` (lazy-recovery contract). +- **`#1731` fast-skip suite restored** (`tests/integration/combo-provider-exhaustion.test.ts`): the five skipped tests were rewritten against the current routing policy (quota-exhausted 429 marks the provider for the request; transient 429 retries other connections; connection errors skip per-connection; nothing persists across requests) and re-enabled — 8/8 green. +- **Proxy context passthrough** (`tests/integration/proxy-context-passthrough.test.ts`): combo targets each execute under their own connection's proxy; `count_tokens` runs inside the connection's proxy context. + +--- + +## [3.8.22] — 2026-06-11 + +### ✨ Added + +- **MiMoCode free-tier provider** ([#3659] — thanks @pizzav-xyz): new no-auth provider `mimocode` (alias `mcode`) exposing Xiaomi's `mimo-auto` model (1M context) via device-fingerprint bootstrap-JWT auth (`/api/free-ai/bootstrap` → Bearer JWT → `/api/free-ai/openai/chat`). Supports multiple accounts (N fingerprints → round-robin with exponential cooldown), re-bootstrap on 401/403, and cooldown on 429. Reuses a new generic `NoAuthAccountCard` dashboard component (also wired for `opencode`). 22 unit tests; upstream validated live during review. (Maintainer follow-up: added the required `authHeader: "none"` field to the registry entry.) Co-authored with @pizzav-xyz. +- **Prefer Claude Code for unprefixed `claude-*` model IDs** ([#3540] — thanks @Witroch4): opt-in setting (default off) that routes bare `claude-*` model IDs from Claude Code clients through the Claude Code OAuth account instead of requiring a provider prefix. Configurable via the `OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS` env flag or a dashboard toggle on the Claude provider page; explicit provider prefixes still win. Full layer coverage (resolver + DB setting + zod schemas + types + UI) with 6 tests. Co-authored with @Witroch4. +- **Codex Responses-WebSocket call history** ([#3616] — thanks @kkkayye): Codex `/v1/responses` WebSocket calls are now persisted to request history — success completions plus prepare-failures, upstream WS errors and premature closes — with `sanitizeErrorMessage` applied to the stored error. Two proxy-side integration tests cover the success and failure paths. +- **Obsidian/WebDAV**: add the `/api/v1/webdav` file server (PROPFIND/GET/PUT/DELETE/MKCOL/MOVE, Basic-Auth, path-traversal hardened) so Obsidian mobile can sync the vault (#3485, part 2). Implemented in the custom server layer (`scripts/dev/webdav-handler.mjs`) — intercepted before Next.js to support non-standard HTTP methods (`PROPFIND`, `MKCOL`, `MOVE`, `LOCK`). Reads vault path and credentials (with enc:v1: AES-256-GCM decryption) directly from the SQLite `key_value` table; credentials configured via PR1's `/api/settings/obsidian/webdav` endpoint. 36 TDD unit tests covering traversal guard, constant-time auth, decrypt round-trip, XML generation, and full CRUD cycle. +- **Quota overview**: deactivate/activate an account directly from the quota card header (toggle button) so users can park a near-zero-quota account without navigating to the provider detail page. ([#3675](https://github.com/diegosouzapw/OmniRoute/pull/3675) — thanks @leninejunior) + +### ♻️ Code Quality + +- **providers/[id]**: extract `useProviderConnections`, `useProviderSettings`, `useProviderModels` hooks from the god-component — #3501 Phase 1f. `ProviderDetailPageClient.tsx`: 4,948 → 4,063 LOC (−885 lines). New hooks in `hooks/`: `useProviderConnections.ts` (954 LOC — all connection management, batch ops, proxy/CLIProxyAPI state, batch-test runner with MAX_BULK_IDS chunking), `useProviderSettings.ts` (264 LOC — Codex global service mode + Claude routing preference), `useProviderModels.ts` (155 LOC — model metadata, aliases). Frozen baselines updated. 10 Phase-1f smoke tests; typecheck/cycles/lint green. Co-authored with @oyi77. +- **providers/[id]**: extract `useModelCompatState` hook + model sections (`ModelRow`, `PassthroughModelRow`, `PassthroughModelsSection`, `CustomModelsSection`, `CompatibleModelsSection`) from the god-component — #3501 Phase 1e. `ProviderDetailPageClient.tsx`: 6,838 → 4,922 LOC (−1,916 lines). New leaf `hooks/useModelCompatState.ts` (101 LOC); compat helpers moved to `providerPageHelpers.ts`. Frozen baselines: `providerPageHelpers.ts: 822`. 12 Phase-1e smoke tests; typecheck/cycles/lint green; #3610 auto-hide fix preserved. +- **providers/[id]**: extract `ConnectionRow` (+ `CooldownTimer`/`inferErrorType`/`getStatusPresentation`), `ModelCompatPopover` (+ `recordToHeaderRows`), and `SiliconFlowEndpointModal` from the god-component into `components/` — #3501 Phase 1d. `ProviderDetailPageClient.tsx`: 8,092 → 6,838 LOC (−1,254 lines). Frozen baselines: `ConnectionRow.tsx: 941`. 7 new Phase-1d smoke tests; typecheck/cycles/lint green. +- **providers/[id]: extract AddApiKeyModal + EditConnectionModal (+ WebSessionCredentialGuide) from the god-component into components/** ([#3501] Phase 1c): extracted the two heaviest inline modals — `AddApiKeyModal` (~787-LOC body) and `EditConnectionModal` (~1091-LOC body) — plus shared `WebSessionCredentialGuide` (~103 LOC) into standalone files under `providers/[id]/components/modals/` and `providers/[id]/components/` respectively. Added `ERROR_TYPE_LABELS` and `formatTimeAgo` to `providerPageHelpers.ts` (leaf) so `EditConnectionModal` and `ConnectionRow` share them without cycles. Pruned 14 now-unused imports from the god-component. `ProviderDetailPageClient.tsx`: 9,981 → 8,092 LOC (−1,889 lines). Frozen baselines: `AddApiKeyModal.tsx: 842`, `EditConnectionModal.tsx: 1170`. 6 new Phase-1c smoke tests; all 21 vitest modal tests pass; typecheck/cycles/lint green. +- **refactor: small db/utils cleanup** ([#3523] — thanks @androw): table-driven `compression_analytics` column migration (replaces 17 repeated `ALTER TABLE` calls), a single merged `serializeJsonField` helper in `db/providers.ts` (folded two byte-identical serializers), and removal of the dead no-op `syncProviderDataToCloud`/`getProvidersNeedingRefresh` stubs from `shared/utils/machine.ts` (no remaining callers). Pure refactor; behavior unchanged. +- **Provider-detail god-component decomposition — Phase 2b (remaining shared helpers→leaf)** ([#3501]): extended `providers/[id]/providerPageHelpers.ts` with all remaining pure helpers needed by the heavy modals (`AddApiKeyModal`/`EditConnectionModal`) before they can be extracted. Moved 22 symbols: web-session credential label/hint/check/title helpers; upstream-headers helpers (`upstreamHeadersRecordsEqual`, `headerRowsToRecord`, `effectiveUpstreamHeadersForProtocol`, `anyUpstreamHeadersBadge`, `getProtoSlice`) plus their `HeaderDraftRow`/`CompatModelRow`/`CompatModelMap`/`CompatByProtocolMap` types; Codex consts and helpers (`CODEX_REASONING_STRENGTH_OPTIONS`, `CODEX_ACCOUNT_SERVICE_TIER_VALUES`, `CODEX_GLOBAL_SERVICE_MODE_VALUES`, `getCodexServiceTierLabel`, `normalizeCodexLimitPolicy`, `getCodexRequestDefaults`, `getClaudeCodeCompatibleRequestDefaults`); misc helpers (`compatProtocolLabelKey`, `extractCommandCodeCredentialInput`, `normalizeAndValidateHttpBaseUrl`, `SILICONFLOW_ENDPOINTS`, `CommandCodeAuthFlowState`). New transitive imports wired into the leaf: `MODEL_COMPAT_PROTOCOL_KEYS` (`@/shared/constants/modelCompat`), `CodexServiceTier`/`getCodexRequestDefaults`/`getClaudeCodeCompatibleRequestDefaults` (`@/lib/providers/requestDefaults`), `CodexGlobalServiceMode` (`@/lib/providers/codexFastTier`), `WebSessionCredentialRequirement` (`./webSessionCredentials`). `ProviderDetailPageClient.tsx`: 10,288 → 9,980 LOC. Leaf module: 589 LOC (acyclic). 25-assertion unit test suite passes; smoke test 3/3; no import cycles. Co-authored with @oyi77. +- **Provider-detail god-component decomposition — Phase 2 (helpers→lib)** ([#3501]): extracted the pure shared helpers — `ProviderMessageTranslator`/`LocalProviderMetadata` types, `providerText`/`providerCountText`/`readBooleanToggle`, and the provider base-URL + routing-tag/excluded-model parse/format block — into a new leaf `providers/[id]/providerPageHelpers.ts` (imports only `@/shared`, so the client and modals share them with no import cycle). `ProviderDetailPageClient.tsx`: 10,435 → 10,288 LOC. Unblocks extracting the heavier `AddApiKeyModal`/`EditConnectionModal` (which depend on these helpers) without cycling. The Phase 0 smoke test caught a missing transitive import (`isSelfHostedChatProvider`) at mount — now wired + locked by a new helpers unit test (12 assertions). Co-authored with @oyi77. + +- **#3500 fully resolved** — Hard Rule #5 (no raw SQL in route handlers): all 13 internal offenders migrated to `src/lib/db/` modules across slices (call*logs, usage_history/daily_usage_summary, community_servers, usage_logs, semantic_cache, proxy_logs, skills UPDATE, db-backups). The gate's `KNOWN_RAW_SQL` set is renamed to `EXTERNAL_DB_ALLOWED` (with a back-compat alias) and now holds only the **2 external-DB reads** (`oauth/cursor/auto-import`, `oauth/kiro/auto-import`) — these open \_another app's* SQLite to import credentials, so by design they cannot live in OmniRoute's `db/` domain. The gate still blocks any NEW raw SQL against OmniRoute's DB. +- **#3500 fully resolved** — Hard Rule #5 (no raw SQL in route handlers): all 13 internal offenders migrated to `src/lib/db/` modules across slices (call*logs, usage_history/daily_usage_summary, community_servers, usage_logs, semantic_cache, proxy_logs, skills UPDATE, db-backups). The gate's `KNOWN_RAW_SQL` set is renamed to `EXTERNAL_DB_ALLOWED` (with a back-compat alias) and now holds only the **2 external-DB reads** (`oauth/cursor/auto-import`, `oauth/kiro/auto-import`) — these open \_another app's* SQLite to import credentials, so by design they cannot live in OmniRoute's `db/` domain. The gate still blocks any NEW raw SQL against OmniRoute's DB. +- **chore(db-gate):** reclassify `KNOWN_UNEXPORTED` → `INTENTIONALLY_INTERNAL` in `scripts/check/check-db-rules.mjs` ([#3499]): a full audit of all 25 db modules confirmed each is consumed via direct/dynamic import per Hard Rule #2 ("Never barrel-import from localDb.ts"). The old framing labelled them as "debt", which was misleading — they are the correct pattern. The gate's blocking behaviour is unchanged (a NEW unexported module still fails); only the name, comments, and per-module justifications were updated to reflect audited truth. Four modules flagged `DEAD?` (`compressionScheduler`, `discovery`, `pluginMetrics`, `prompts`) have zero production importers and are documented as schema-reserved. A new regression-guard test (`tests/unit/check-db-rules-classification.test.ts`) asserts every non-dead module in the set has ≥1 real importer, so a future consumer removal surfaces as a test failure requiring explicit reclassification. +- **refactor(db): move `call_logs` aggregations into `callLogStats` db module** ([#3500]): extracted raw SQL from three route handlers (`/api/provider-metrics`, `/api/search/stats`, `/api/v1/search/analytics`) into a new `src/lib/db/callLogStats.ts` domain module (`getProviderMetrics`, `getSearchProviderStats`, `getRecentSearchLogs`, `getSearchAggregateStats`, `getSearchProviderCounts`). First slice of #3500 (call_logs cluster). Behavior unchanged; the three routes are removed from `KNOWN_RAW_SQL` in the gate. Validated with TDD unit tests (6 assertions seeding an in-memory SQLite fixture). +- **refactor(db): move `usage_history`/`daily_usage_summary` SQL into `usageAnalytics` db module** ([#3500]): extracted all inline `db.prepare(...)` calls from two route handlers (`/api/usage/analytics`, `/api/settings/export-json`) into a new `src/lib/db/usageAnalytics.ts` module and extended `src/lib/db/callLogStats.ts` with `getFallbackStats`. New exports: `buildUnifiedSource`, `buildPresetUnifiedSource` (UNION CTE builders), plus 12 typed query functions covering summary, daily, daily-cost, heatmap, model, provider, account, api-key, service-tier, weekly-pattern, and preset-cost aggregations, plus `getAllUsageHistory`/`getAllDomainCostHistory`/`getAllDomainBudgets` for backup export. Second slice of #3500. `KNOWN_RAW_SQL` drops from 12 → 10. Validated with 21 TDD unit tests (`tests/unit/db-usage-analytics-3500.test.ts`) seeding a temp SQLite fixture. + +- **refactor(db): move `community_servers` auth look-up into `gamification` db module** ([#3500]): extracted raw SQL from two federation route handlers (`/api/gamification/federation/leaderboard`, `/api/gamification/federation/score`) into a new `getConnectedServerByKeyHash(apiKeyHash)` function in `src/lib/db/gamification.ts`. Third slice of #3500 (gamification federation cluster). Behavior unchanged; the two routes are removed from `KNOWN_RAW_SQL` in the gate. Validated with TDD unit tests (3 assertions seeding a temp SQLite fixture). + +- **refactor(db): move `skills UPDATE` + `db-backups` SQL into db modules** ([#3500]): fifth slice of #3500. Extracted the dynamic `UPDATE skills SET …` from `src/app/api/skills/[id]/route.ts` into a new `src/lib/db/skills.ts` module (`updateSkill(id, patch)`). The dynamic SET clause is injection-safe: column names are validated against a hard-coded allowlist of known writable columns before being interpolated; unknown keys are silently ignored. Extended `src/lib/db/backup.ts` with three new functions: `exportAllSummaryRows()` (multi-table SELECT for key_value / combos / provider_connections / api_keys, used by exportAll), `getTableNamesFromAdapter()` (sqlite_master introspection via an adapter arg, used by import validation), and `countImportedRows()` (post-import COUNT(\*) per table). The backup domain module is the correct home for sqlite_master introspection — it is not "raw SQL in a route" once moved there. `KNOWN_RAW_SQL` drops by 3 (from 8 → 5). Validated with 11 TDD unit tests (`tests/unit/db-backups-skills-3500.test.ts`). +- **refactor(db): move `skills UPDATE` + `db-backups` SQL into db modules** ([#3500]): fifth slice of #3500. Extracted the dynamic `UPDATE skills SET …` from `src/app/api/skills/[id]/route.ts` into a new `src/lib/db/skills.ts` module (`updateSkill(id, patch)`). The dynamic SET clause is injection-safe: column names are validated against a hard-coded allowlist of known writable columns before being interpolated; unknown keys are silently ignored. Extended `src/lib/db/backup.ts` with three new functions: `exportAllSummaryRows()` (multi-table SELECT for key_value / combos / provider_connections / api_keys, used by exportAll), `getTableNamesFromAdapter()` (sqlite_master introspection via an adapter arg, used by import validation), and `countImportedRows()` (post-import COUNT(\*) per table). The backup domain module is the correct home for sqlite_master introspection — it is not "raw SQL in a route" once moved there. `KNOWN_RAW_SQL` drops by 3 (from 8 → 5). Validated with 11 TDD unit tests (`tests/unit/db-backups-skills-3500.test.ts`). +- **refactor(db): move `usage_logs`/`semantic_cache`/`proxy_logs` SQL into db modules** ([#3500]): extracted raw `db.prepare(...)` SQL from three route handlers (`/api/analytics/auto-routing` → `usageLogs.ts`; `/api/cache/entries` → `semanticCache.ts`; `/api/logs/export` → `proxyLogs.ts`) into new `src/lib/db/` domain modules. New exports: `getAutoRoutingTotalCount`, `getAutoRoutingVariantBreakdown`, `getAutoRoutingTopProviders` (usage_logs), `listSemanticCacheEntries`, `deleteSemanticCacheBySignature`, `deleteSemanticCacheByModel` (semantic_cache), and `exportProxyLogsSince` (proxy_logs). Fourth slice of #3500. `KNOWN_RAW_SQL` drops from 8 → 5. Validated with 13 TDD unit tests (`tests/unit/db-logs-cache-3500.test.ts`) seeding temp SQLite fixtures. + +- **Provider-detail god-component decomposition — Phase 0** ([#3501]): introduced `ProviderDetailPageClient.tsx` and reduced `providers/[id]/page.tsx` to a thin 9-line route wrapper (was 12,882 LOC), following the repo's `*PageClient` convention. Added the first-ever smoke render test for the page (Hard Rule #8) as the safety net every later extraction phase is diffed against. Behavior unchanged; the `check-file-size` ratchet now tracks the extracted client. Foundation for Phases 1–6 (strangler-fig). Thanks @oyi77 for the parallel modularization effort in #3627. +- **Provider-detail god-component decomposition — Phase 1a** ([#3501]): extracted the three self-contained auth-import modal clusters (Codex/Claude/Gemini `Import*AuthModal` + `Apply*AuthModal` + their co-located helpers, ~2,160 LOC) into `providers/[id]/components/modals/`. `ProviderDetailPageClient.tsx` drops 12,882 → 10,719 LOC. Behavior unchanged (smoke test green; clusters had clean `{ onClose, onSuccess }` / inline-prop interfaces). Co-authored with @oyi77. +- **Provider-detail god-component decomposition — Phase 1b** ([#3501]): extracted `EditCompatibleNodeModal` (+ its node/props types) into `providers/[id]/components/modals/`, and moved the shared `CC_COMPATIBLE_DEFAULT_CHAT_PATH` constant into a leaf `providerDetailConstants.ts` so the page client and the modal can both import it without a circular dependency. Also removed two dangling section comments left by Phase 1a. `ProviderDetailPageClient.tsx`: 10,719 → 10,435 LOC. Behavior unchanged (smoke test + a new standalone modal render test green; `check:cycles` clean). Co-authored with @oyi77. + +### 🔧 錯誤修正 + +- **Combos / Auto-Combo: premature context compaction ("agent keeps forgetting things")** ([#3680]): two related context-window bugs fixed. (1) `GET /api/combos/auto` now advertises `context_length` / `max_output_tokens` (MAX across the candidate pool — safe because the auto-combo context pre-filter routes oversized requests to large-window candidates), and the opencode plugin consumes them instead of hardcoding `limit: { context: 0 }` — a zero context silently disables opencode's smart auto-compaction, letting sessions grow until the gateway's destructive history purge kicks in. (2) chatCore's proactive compression for DB combos (incl. quota-shared pools) no longer compresses at `min(...allTargets)`: it now uses the EXECUTING target's own window (`resolveComboContextLimit`), keeping min-of-targets only as a defensive fallback when the current provider/model resolves no specific limit. TDD: 8 server tests (`tests/unit/auto-combo-context-advertising.test.ts`) + 3 plugin tests (`tests/auto-combo-context.test.ts`). +- **Obsidian/WebDAV**: add the `/api/settings/obsidian/webdav` config route (enable/disable vault sync), encrypt WebDAV credentials at rest, and remove the duplicate UI block (#3485, part 1). + +- **OpenCode Free / passthrough**: "Test all models" now respects "Auto-hide failed models" and switches the list to the visible filter so hidden models actually disappear (#3610). Three related bugs fixed: `autoHideFailed` is now threaded from the outer component into `PassthroughModelsSection` via a prop (single shared checkbox); the `/api/models/test-all` request body now includes `autoHideFailed: true` so the server persists the hide; and after the loop, `visibilityFilter` is switched to `"visible"` when ≥1 model was hidden. Two pure-function helpers (`buildPassthroughTestBody`, `shouldSwitchToVisibleFilter`) extracted to `providerPageHelpers.ts` with 7 unit tests. +- **Resilience**: clear stale transient connection cooldowns on startup so a prior unclean crash no longer makes every request time out at 120s after restart (#3625) + +- **fix(home topology): restore live in-flight request pulse** ([#3507]): the animated "pulse" edges in the home Provider Topology panel went dead after PR #3401 unified request visibility, because `activeRequests` was hardcoded to `[]`. Re-wired to `useLiveRequests()` (the existing WebSocket hook on port 20129) so that every pending/running request drives the animation in real time. A pure `selectActiveRequests` mapping helper was extracted to `home/topologyUtils.ts` with 5 unit tests. +- **Electron desktop**: launch the peer-stamping `server-ws.mjs` entrypoint so local-only routes (AgentBridge, MCP, services) no longer return 403 LOCAL_ONLY (#3386) +- **Provider Topology**: stop flagging healthy providers as errored based on stale historical failures; use current request status (#3619) +- **OpenCode Free**: fetch the live model catalog from the provider's `modelsUrl` for the no-auth model picker instead of serving a stale hardcoded list (#3611) +- **Hermes Agent**: honour the `HERMES_HOME` env var when writing/reading the agent config instead of always using `~/.hermes` (#3628). Introduced `getHermesHome()` / `getHermesConfigPath()` helpers (read at call-time) and routed all four hardcoded callsites through them so OmniRoute's config lands in the same directory that the Hermes PowerShell installer configures on Windows. +- **MITM/cert**: remove the duplicated "Command failed:" prefix in system-command error messages ([#3641](https://github.com/diegosouzapw/OmniRoute/issues/3641)): `execFileText` was prepending its own `"Command failed: "` prefix on top of Node's `execFile` error message, which already begins with `"Command failed: "` for non-zero exits. The error message now surfaces Node's message directly (no double prefix), with stderr appended only when non-empty. + +- **fix(reasoning): replay `reasoning_content` on plain DeepSeek turns** ([#3632] — thanks @adivekar-utexas): the reasoning-replay gate previously only fired when an assistant message already carried `reasoning_content`. Plain (non-tool-call) turns whose `reasoning_content` was stripped by the client (e.g. Cursor) were forwarded without it, so DeepSeek V4+ rejected the request with 400 "the reasoning_content in the thinking mode must be passed back". The gate now also covers missing/empty `reasoning_content` on DeepSeek replay targets, injecting the cached reasoning (or the non-Anthropic placeholder) so multi-turn text conversations no longer 400. Fixes #1682. 2 regression tests. +- **fix(kiro): route enterprise IAM Identity Center accounts to their regional endpoint** ([#3631] — thanks @artickc): Kiro/CodeWhisperer access tokens and Q Developer profile ARNs are region-bound, so enterprise IAM Identity Center accounts outside `us-east-1` (e.g. `eu-central-1`) were rejected by the default host. Adds `resolveKiroRegion` (stored region → profileArn region → `us-east-1`) and `kiroRuntimeHost` (regional `q.{region}.amazonaws.com`, legacy `codewhisperer.us-east-1` for the default), routes chat + usage to the regional endpoint, and discovers the region-matched `profileArn` via `ListAvailableProfiles` in a best-effort `postExchange` hook. 9 tests. +- **fix(combo): skip same-provider/connection targets on connection-level errors** ([#3637] — thanks @herjarsa): on connection-level upstream errors (408/500/502/503/504/524), remaining same-`provider:connection` targets in a combo request are now skipped to avoid hammering a known-bad connection, in both the priority and round-robin paths. Adjusted in review to **exclude OmniRoute circuit-breaker-open responses** (503 + `X-OmniRoute-Provider-Breaker` / `provider_circuit_open`) from this skip, preserving the invariant that a breaker-open is an ordinary target failure (the next same-provider target is still tried). Co-authored with @herjarsa. +- **/v1/responses**: detect stream readiness for tool-call-only and `object`-less chunks so Codex-shaped (reasoning + tools) requests no longer fail with "Stream ended before producing useful content" (#3612) +- **RTL locales (ar/fa/he/ur)**: use logical CSS direction utilities for the sidebar and key overlays so the layout mirrors correctly under `dir=rtl` (#3541, partial — core layout) +- **Kiro/AWS auto-import**: set a descriptive account name and dedupe by `profileArn` so imports no longer create nameless duplicate "OAuth Account" rows (#3615) +- **fix(guardrails):** the `/api/guardrails/test` route now validates its body through `validateBody()` (Zod) instead of parsing raw JSON directly, aligning it with the repo-wide input-validation pattern (Hard Rule #7). ([#3621](https://github.com/diegosouzapw/OmniRoute/pull/3621) — thanks @diegosouzapw) +- **fix(dashboard): bulk provider connection actions** — close audit, API, and UX gaps in the batch activate/deactivate flow: register the `provider.credentials.batch_updated` event in `HIGH_LEVEL_ACTIONS` and `ACTIVITY_ICONS` (was silently dropped from the Activity feed); fix `/api/providers` PATCH to return `warn` status when `notFound` is non-empty instead of always `success`; `/api/providers/test-batch` empty-result early-return now includes a `summary` so stale-ID mode reports to the user; bulk activate/deactivate chunks selection by 100 to avoid the Zod 400 cap on large provider accounts. ([#3673](https://github.com/diegosouzapw/OmniRoute/pull/3673) — thanks @leninejunior) + +### 📝 維護 + +- **fix(ci):** increase the `execFileSync` `maxBuffer` in `validate-pack-artifact` so the npm-pack inventory no longer overflows on large tarballs during release validation — follow-up to the v3.8.21 pack-artifact hotfix. ([#3622](https://github.com/diegosouzapw/OmniRoute/pull/3622) — thanks @diegosouzapw) --- ## [3.8.21] — 2026-06-11 -_See [English CHANGELOG](/CHANGELOG.md) for v3.8.21 details._ +### ✨ Added + +- **feat(cli):** `omniroute autostart` now accepts the shorthand the headless / `omniroute serve` path was missing — `omniroute autostart on` / `... true` (aliases of `enable`), `... off` / `... false` (aliases of `disable`), a new `... toggle`, and a default `... status` (bare `omniroute autostart` is a safe read-only). Previously autostart could only be toggled from the tray (`serve --tray`) or the Electron Appearance tab, so a plain `omniroute serve` user had no way to enable it. (The cross-platform launchd/systemd/registry logic is unchanged — this only wires the ergonomic CLI surface.) ([#3331](https://github.com/diegosouzapw/OmniRoute/issues/3331) — thanks @uniQta) + +### ♻️ Code Quality + +- **refactor(chatCore):** extract the chatCore request phases — idempotency check, semantic cache check, common request sanitization, and memory/skills injection — into dedicated `open-sse/handlers/chatCore/` modules (`idempotency.ts`, `semanticCache.ts`, `sanitization.ts`, `memorySkillsInjection.ts`), slimming the monolithic handler with no behavior change. (Maintainer follow-up: re-derive `idempotencyKey` at the Phase 9.2 save site after the check moved into the module, fixing a `ReferenceError` on successful non-cached responses.) ([#3598](https://github.com/diegosouzapw/OmniRoute/pull/3598) — thanks @oyi77) +- **docs(opencode-provider):** soft-deprecate `@omniroute/opencode-provider` in favour of `@omniroute/opencode-plugin`. The provider package writes a **static** model list to `opencode.json` that drifts behind the live OmniRoute catalog, whereas the plugin fetches `/v1/models` at OpenCode startup. The package keeps working (no code/behavior change), but its npm description and README now carry a deprecation banner with the one-line migration, and a guard test pins the notice. ([#3419](https://github.com/diegosouzapw/OmniRoute/issues/3419) — thanks @herjarsa) +- **chore(review):** pre-release hardening from a multi-reviewer `/review-reviews` battery over the v3.8.21 diff (7 Opus reviewers; zero blocker/high). Resolved findings: npm tarball no longer ships co-located test files (`files[]` negations + reconciled `.npmignore`; the #3578 closure gate now asserts the real `npm pack` output in both directions); `getSanitizedCachedProviderLimitsMap` scopes its connection scan to antigravity/agy instead of decrypting every active connection on each dashboard poll; the Antigravity quota-tier remap (`toClientAntigravityQuotaModelId`) is centralized in `antigravityModelAliases.ts` (was an inline if-ladder in `usage.ts`); the chatCore idempotency check returns its resolved key so the save site reuses a single derivation; and new tests pin the chatCore extracted modules, the Antigravity `usage_history` fallback contract, the reasoning-wrapper prefix-preservation heuristic, the Antigravity SSE `markdown` branch, and the upstream-ca/test no-persist guarantee. (Live-verified that agy consumer tokens are accepted by the non-daily `cloudcode-pa` host used by `retrieveUserQuota`, so #3604 is not agy-host-limited.) + +### 🔧 錯誤修正 + +- **fix(routing):** reasoning models (deepseek-v4-flash, nemotron, etc.) no longer return empty content in combo routing when they spend all of `max_tokens` on reasoning — `validateResponseQuality` now rejects an empty-content-but-`reasoning_content` response when reasoning consumed ≥90% of completion tokens (so the combo loop retries/falls back), and reasoning models receive a `max_tokens` buffer (+50%, +1000 floor) so reasoning and content both fit. (Maintainer follow-up: the round-robin buffer is applied to a per-attempt copy so it does not compound across models/retries — `4096 → 6144 → 9216 → …`.) ([#3588](https://github.com/diegosouzapw/OmniRoute/pull/3588) — thanks @herjarsa) +- **fix(routing):** a valid `max_tokens`-truncated upstream response is no longer misclassified as empty content and rewritten into a fake 502 — `isEmptyContentResponse()` flagged any Claude `content:[]` / OpenAI empty-choice payload regardless of `stop_reason`/`finish_reason`, so a Claude Code `max_tokens: 1` connectivity ping (HTTP 200, `stop_reason:"max_tokens"`, empty content) became a synthetic `502 "Provider returned empty content"` and triggered a needless family fallback. The guard now treats a terminal truncation/tool signal (Claude `stop_reason` `max_tokens`/`tool_use`, OpenAI `finish_reason` `length`/`tool_calls`) as a legitimate completion; genuinely empty responses (no terminal reason, or `stop`/`end_turn` with empty content) are still caught. ([#3572](https://github.com/diegosouzapw/OmniRoute/issues/3572)) +- **fix(api):** `/v1/completions` now returns the legacy OpenAI Completions shape (`object:"text_completion"`, `choices[].text`) instead of chat payloads (`choices[].message|delta.content`) — the endpoint routes internally through the chat pipeline, so legacy Completion clients like TabbyML's `openai/completion` backend crashed with `missing field "text"`. The response (both non-streaming JSON and the SSE stream) is now translated back to the text-completion shape; `[DONE]` and error bodies pass through unchanged. ([#3571](https://github.com/diegosouzapw/OmniRoute/issues/3571)) +- **fix(usage):** the z.ai/GLM coding-plan quota card no longer shows "Monthly 0%" — coding plans have no monthly cap (only 5-hour windows), so the quota API reports the `TIME_LIMIT` ("Monthly") entry with `total=0`, and the `total>0 ? … : 0` fallback rendered a misleading 0% remaining (which can skew downstream model-choice). With no absolute cap the remaining percentage now falls back to the percentage-derived value (full/100% when 0% used). ([#3580](https://github.com/diegosouzapw/OmniRoute/issues/3580)) +- **docs(discovery):** mark `DISCOVERY_TOOL_DESIGN.md`'s API Endpoints table with an explicit "⚠️ Not yet implemented — Phase 2" banner — the discovery routes are a design proposal (Phase-1 stub only), and the banner makes clear the `KNOWN_STALE_DOC_REFS` gate suppression is intentional, not stale drift. ([#3498](https://github.com/diegosouzapw/OmniRoute/issues/3498)) +- **fix(agent-bridge):** add the missing `POST /api/tools/agent-bridge/upstream-ca/test` route — the UpstreamCaField "Test" button POSTed to it but it didn't exist (404). The new validate-only route checks the CA file exists and is a parseable PEM certificate (returns the subject/expiry) **without** persisting the path or activating it; it inherits the `/api/tools/agent-bridge/` LOCAL_ONLY classification. ([#3488](https://github.com/diegosouzapw/OmniRoute/issues/3488)) +- **fix(gamification):** the dashboard Profile page no longer hits three 404s — added the missing `GET /api/gamification/{level,badges,badges/earned}` routes (management-scoped). The page is operator-wide (no `apiKeyId`), so `level`/`badges/earned` aggregate across all keys (with an optional `?apiKeyId` for a single key), and `badges` seeds the built-in catalog first (idempotent) so the grid is populated even on installs that never seeded it (see #3472). ([#3484](https://github.com/diegosouzapw/OmniRoute/issues/3484)) +- **security(oauth):** migrate the five public OAuth client_ids (Claude, Codex, Qwen, Kimi, GitHub Copilot — 9 server-side call-sites in `providerRegistry.ts` + `oauth.ts`) from string literals to `resolvePublicCred()` (Hard Rule #11), matching the existing Gemini/Antigravity pattern. The values decode byte-for-byte to the same public client_ids (env overrides still win), so OAuth flows are unchanged; the `check-public-creds` allowlist is now empty. The browser-bundled `codexDeviceFlow.ts` copy stays a literal by necessity (it cannot import `open-sse`). ([#3493](https://github.com/diegosouzapw/OmniRoute/issues/3493)) +- **fix(mcp):** `omniroute --mcp` no longer crashes on npm installs with `ERR_MODULE_NOT_FOUND` (e.g. `src/lib/combos/steps.ts`) — the MCP server runs from raw TypeScript and imports across `src/` + `open-sse/`, but the published `files` allowlist only shipped a handful of cherry-picked paths, so the transitive closure (~400 files) was absent from the tarball. `files` now ships the backend source the MCP server needs (`open-sse/` + `src/{domain,lib,mitm,server,shared,sse,types}/`, excluding the `src/app` UI), and a new regression test computes the MCP import closure and fails if any reachable source file is not covered by `files`. ([#3578](https://github.com/diegosouzapw/OmniRoute/issues/3578)) +- **fix(api):** `API_REFERENCE.md` no longer documents a non-existent `/api/guardrails*` / `/api/shadow*` surface (doc-fiction flagged by `check-docs-symbols`, frozen in `KNOWN_STALE_DOC_REFS`). The guardrail pipeline is real (`src/lib/guardrails`), so the two routes that map to actual behavior are now implemented — `GET /api/guardrails` (list the registered guardrails + status) and `POST /api/guardrails/test` (dry-run the pre-call pipeline over a sample input), both management-scoped — while the fictional `enable`/`disable`/`logs` rows and the entire `/api/shadow*` table (shadow A-B comparison is combo-config + `/api/combos/metrics`) were removed from the doc and dropped from the allowlist. ([#3496](https://github.com/diegosouzapw/OmniRoute/issues/3496)) +- **fix(agent-bridge):** the MITM "Start" button no longer reports a misleading "port 443 may be in use" for every failure cause — `startMitm()` only matched the EADDRINUSE stderr line and always threw the port-443 message, so a missing `ROUTER_API_KEY` or an `EACCES` permission error sent users debugging the wrong thing. The startup watcher now buffers the MITM child's stderr and `interpretMitmStartupError()` maps the real `server.cjs` `❌` cause (port-in-use / permission-denied / missing API key / any other diagnostic line) into the surfaced error; with no captured output it stays generic instead of guessing port 443. ([#3606](https://github.com/diegosouzapw/OmniRoute/issues/3606)) +- **fix(oauth):** Kiro "Import Token" no longer reports a bare `Internal server error` that hides the real cause — the import validates/refreshes the pasted refresh token against AWS, and the catch returned a generic 500 string, so an `invalid_grant`, an expired token, or a region mismatch all surfaced identically in the dashboard. The import error now carries the sanitized upstream cause via `sanitizeErrorMessage()` (Hard Rule #12 — no stack, no secrets), keeping the same `{ error: }` response shape, and still falls back to the generic message when there is nothing to report. ([#3589](https://github.com/diegosouzapw/OmniRoute/issues/3589)) +- **fix(antigravity):** the Antigravity/agy Gemini 3.5 Flash catalog now exposes clean public tier IDs (`gemini-3.5-flash-low`/`-medium`/`-high`, matching Antigravity 2.0.4's Low/Medium/High selector) and maps them to the live upstream IDs at the executor boundary, instead of the old confusing `-preview`/`-agent` names. Antigravity model-id normalization moved out of the global model resolver into the executor so client-visible IDs are no longer rewritten before account/credential routing and logging. (Maintainer follow-up: kept `gemini-3.5-flash-preview` as a hidden backward-compat alias routing to the High tier so saved combos/configs keep working; live-validated the tier set via the `agy` CLI catalog.) ([#3603](https://github.com/diegosouzapw/OmniRoute/pull/3603) — thanks @dhaern) +- **fix(usage):** Antigravity/agy Provider Limits now report accurate consumption — `retrieveUserQuota` (live usage) is preferred over the `fetchAvailableModels` catalog view (which keeps reporting full buckets after real usage), with a local `usage_history` fallback for buckets that are only catalog-visible; cached entries are sanitized so retired upstream IDs are not re-exposed, and a deduplicated post-usage refresh keeps the dashboard fresh after each request. (Maintainer follow-up: the post-usage refresh is decoupled through a lightweight `usageEvents` bus so `usageHistory` no longer imports `providerLimits`/the executors graph, keeping the `typecheck:core` surface stable.) ([#3604](https://github.com/diegosouzapw/OmniRoute/pull/3604) — thanks @dhaern) +- **fix(gemini):** textual reasoning wrappers emitted as assistant prose (``/``/``/``, including malformed/open tags like ` 0`, so saving with one field filled 400'd and clearing all limits was impossible. Limits now accept `0` (= "no limit for this period"; enforcement only kicks in above 0) and the cross-field minimum was removed; negatives are still rejected. ([#3537](https://github.com/diegosouzapw/OmniRoute/issues/3537)) +- **fix(gamification):** badge-unlock events no longer re-fire on every request — the "already unlocked?" guard used `getBadges()`, which INNER-JOINs `badge_definitions` (empty until seeded), so it always reported "not earned" and re-emitted `events.badge_unlocked` per request. Added a `hasBadge()` helper that reads `user_badges` directly, so dedup is correct regardless of whether definitions are seeded. ([#3472](https://github.com/diegosouzapw/OmniRoute/issues/3472)) +- **fix(routing):** the `auto` model keyword now works on the Codex `/v1/responses` path — `resolveResponsesApiModel` rewrote the bare `auto` keyword to `codex/auto`, which ChatGPT rejects (`The 'auto' model is not supported when using Codex with a ChatGPT account`). `auto` (OmniRoute's zero-config auto-routing keyword) now passes through untouched so combo routing handles it. ([#3509](https://github.com/diegosouzapw/OmniRoute/issues/3509)) +- **fix(cli-tools):** saving the OpenCode/CLI tool config no longer 400s in cloud mode — every CLI tool card posts `apiKey: null` (the real key is resolved server-side from `keyId`), but `guideSettingsSaveSchema` used `z.string().optional()`, which rejects `null`. The schema now normalizes `null` → `undefined`, so the save succeeds and the `keyId`/default path is used. ([#3552](https://github.com/diegosouzapw/OmniRoute/issues/3552)) +- **fix(catalog):** PublicAI is no longer miscatalogued as keyless/free — it requires an API key (registry `authType:"apikey"`; signup grants a one-time credit, then it bills). The three PublicAI models moved from `freeType:"keyless"` (which could pick them into the no-auth pool and dispatch with no `Authorization` header) to `"one-time-initial"`, and the provider's `hasFree` flag is now `false` — matching `freeTierCatalog.ts`, which already excluded publicai. ([#3558](https://github.com/diegosouzapw/OmniRoute/issues/3558)) +- **fix(gemini-web):** a missing Playwright Chromium browser no longer loops and trips the provider breaker — when the browser binary is not installed, `chromium.launch()` threw an error surfaced as a retryable **500**, so accountFallback marked the account unavailable and retry-looped. It is now classified as a host/config problem and returns **503** with an actionable message (`npx playwright install chromium`) and the `X-Omni-Fallback-Hint: connection_cooldown` header, which skips the provider circuit breaker and applies a short non-exponential cooldown. ([#3516](https://github.com/diegosouzapw/OmniRoute/issues/3516)) +- **fix(proxy):** the SOCKS5 proxy option now follows the runtime `ENABLE_SOCKS5_PROXY` env instead of the build-time `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` — Next.js inlines `NEXT_PUBLIC_*` at build time, so a prebuilt Docker image ignored a runtime setting and the SOCKS5 type stayed hidden. The proxy modal now reads `socks5Enabled` from `GET /api/settings/proxies` (server-side `ENABLE_SOCKS5_PROXY`), with the build-time value kept only as a static-deploy fallback. ([#3508](https://github.com/diegosouzapw/OmniRoute/issues/3508)) +- **fix(playground):** the playground model selector now lists models from custom-endpoint (OpenAI/Anthropic-compatible) providers — it filtered `/v1/models` by the provider's connection id, but the catalog emits compatible-provider models under the node's custom prefix (`prefix/model`), so the list came up empty ("None"/"-"). The selector now filters by the node prefix (exposed additively as `modelPrefix` on provider options; the connection id is unchanged, so translator send/translate and connection lookups are unaffected). ([#3505](https://github.com/diegosouzapw/OmniRoute/issues/3505)) +- **fix(usage):** the Kiro quota card no longer renders a blank when the account returns no usage breakdown — `getKiroUsage` returned `quotas:{}` for a successful GetUsageLimits response without a `usageBreakdownList` (observed with some AWS IAM / Builder ID accounts), which the dashboard showed as an unexplained empty card. It now returns an informative message (surfaced via the card's connection-message path). ([#3506](https://github.com/diegosouzapw/OmniRoute/issues/3506)) +- **fix(security):** route raw `err.message` through `sanitizeErrorMessage()` in five web executors (`adapta-web`, `deepseek-web`, `perplexity-web`, `qoder`, `veoaifree-web`) and the embeddings + search handlers (Hard Rule #12) — these built error response bodies from the raw upstream/exception message, which could leak internal detail. ([#3494](https://github.com/diegosouzapw/OmniRoute/issues/3494), [#3495](https://github.com/diegosouzapw/OmniRoute/issues/3495)) +- **fix(dashboard):** correct two dashboard fetches that hit non-existent routes (404) — `CustomHostsManager` called `/api/tools/traffic-inspector/custom-hosts` (the real route is `/hosts`), and `FeatureFlagsGrid`'s post-restart liveness probe called `/api/health` (the real lightweight endpoint is `/api/health/ping`). ([#3486](https://github.com/diegosouzapw/OmniRoute/issues/3486), [#3487](https://github.com/diegosouzapw/OmniRoute/issues/3487)) +- **chore(providers):** remove the dead `krutrim` registry entry — it was half-registered (present in `providerRegistry.ts` with a baseUrl + one model, but absent from `providers.ts`, with no executor/translator/OAuth), so it was never selectable. Dropped its `ProviderIcon` entry and the `KNOWN_REGISTRY_ONLY` exception. ([#3483](https://github.com/diegosouzapw/OmniRoute/issues/3483)) +- **docs(api):** fix the agent-bridge per-agent state route in `openapi.yaml` and `AGENTBRIDGE.md` — both documented `/api/tools/agent-bridge/agents/{id}/state`, which has no route; corrected to the real per-agent `/api/tools/agent-bridge/agents/{id}` (global state remains `/api/tools/agent-bridge/state`). ([#3489](https://github.com/diegosouzapw/OmniRoute/issues/3489)) +- **docs(api):** correct `API_REFERENCE.md` endpoints that documented non-existent routes — skills (`PUT /api/skills/[id]`, `POST`/`GET /api/skills/executions`), plugins (`[id]`→`[name]`, `activate`/`deactivate`), ACP (`DELETE`/`POST /api/acp/agents` via `?id`/`{action:"refresh"}`), cache (`DELETE /api/cache/reasoning`, `/api/cache/entries`), and removed the fabricated `/api/admin/circuit-breaker`, `/api/admin/rate-limits`, and `/api/system-info` (admin only exposes `/concurrency`). ([#3497](https://github.com/diegosouzapw/OmniRoute/issues/3497)) +- **fix(executor):** strip provider prefix from versioned built-in tool model field — Anthropic rejects `tools[N].model: "cc/claude-opus-4-8"` from Claude Code's `advisor_20260301` and similar versioned built-in tools; the native Claude OAuth execute path now strips any provider prefix from `model` on tools whose name matches `name_YYYYMMDD`. ([#3532](https://github.com/diegosouzapw/OmniRoute/pull/3532) — thanks @ggiak) +- **fix(dashboard):** handle DEGRADED and unknown provider breaker states on the Runtime page — an unrecognised breaker state (e.g. DEGRADED) caused a crash because the styling map had no entry for it; now falls back to a neutral style so the page never throws on unknown states. ([#3533](https://github.com/diegosouzapw/OmniRoute/pull/3533) — thanks @rdself) +- **fix(usage):** make opencode-go quota fetcher fail-open instead of throwing 500 — the quota API rejects chat API keys with a JSON-401 body even though the same key works for chat; previously this threw and crashed the dashboard with a red error banner. It now returns an informative message and keeps rendering like other connection-message cases. ([#3522](https://github.com/diegosouzapw/OmniRoute/pull/3522) — thanks @wilsonicdev) +- **fix(translator):** map the Codex `local_shell` tool type — `local_shell` was absent from the translator's tool-type map, causing it to fall through as an unknown type; it is now forwarded correctly to the upstream. ([#3534](https://github.com/diegosouzapw/OmniRoute/pull/3534) — thanks @kamaka) +- **fix(images):** prefer bare combo names over built-in image model aliases — a user combo named `gpt-image-2` can now shadow the native OpenAI alias so image requests route through the combo; provider-qualified IDs like `openai/gpt-image-2` still resolve via the built-in path. ([#3527](https://github.com/diegosouzapw/OmniRoute/pull/3527) — thanks @AveryanAlex) +- **fix(translator):** fix OpenAI→Gemini translation of historical tool calls — tool results from earlier turns were being converted to text, causing Gemini to pattern-match the response as prose rather than structured content; they now use the native Gemini `functionResponse` part format. ([#3569](https://github.com/diegosouzapw/OmniRoute/pull/3569) — thanks @hartmark) +- **fix(plugins):** forward plugin lifecycle hooks (`onInstall`, `onActivate`, `onDeactivate`, `onUninstall`) via IPC and wrap `onDeactivate`/`onUninstall` in try/catch so a buggy plugin handler can no longer brick teardown; also removes redundant `RegExp()` wrappers in `accountFallback.ts` and fixes indentation in `requestLogger.ts`. ([#3562](https://github.com/diegosouzapw/OmniRoute/pull/3562) — thanks @oyi77) +- **fix(auto-update):** use a stable PROJECT_ROOT walker instead of frozen `process.cwd()` — `resolveProjectRoot` now walks up from `__dirname` to find the nearest directory containing `package.json` or `.git` (bounded at 16 levels), preventing ENOENT errors when the working directory is not the project root. ([#3561](https://github.com/diegosouzapw/OmniRoute/pull/3561) — thanks @oyi77 / @ViFigueiredo via [#3423](https://github.com/diegosouzapw/OmniRoute/pull/3423)) +- **fix(resilience):** expose `providerCooldown` in `GET /api/resilience` and accept it in `PATCH` — PR #3556 added the global provider cooldown tracker to the settings model and `ResilienceTab` UI, but the API route never returned the field (causing a crash when the tab loaded) and `updateResilienceSchema` (`.strict()`) rejected PATCH bodies containing it with 400. `providerCooldownSettingsSchema` is now wired into the Zod schema, returned in the GET response, and merged in the PATCH handler. Caught during v3.8.20 VPS homologation (Hard Rule #18 — 5 TDD tests); shipped to main via [#3591](https://github.com/diegosouzapw/OmniRoute/pull/3591). ([#3590](https://github.com/diegosouzapw/OmniRoute/pull/3590) — thanks @diegosouzapw) --- -## [3.8.19] — Unreleased +## [3.8.19] — 2026-06-09 -_Development cycle in progress._ +> Focused quality-infrastructure release: the complete **quality-gate ratchet + anti-hallucination guardrail system** (Phases 0–6 + fast-tracked 6A.1/6A.2). No external PRs were taken this cycle by design — community PRs carry over to the next cycle. + +### ✨ 新功能 + +- **feat(quality):** quality-gate ratchet + anti-hallucination/rule-enforcement guardrails (Phases 0–6) — generic multi-metric ratchet engine (`quality-baseline.json` + collector + comparator, regression-only) and ~18 deterministic gates wired into CI: provider-consistency, dashboard `fetch()`→route and OpenAPI/docs→route resolution (anti-hallucination), dependency allowlist (anti-slopsquatting), file-size/duplication/complexity ratchets (frozen debt only shrinks), anti test-masking (assert-removal/tautology detection on PR diffs), error-helper (Hard Rule #12), public-creds (Rule #11), route-guard membership (Rules #15/#17), db-rules (Rules #2/#5), known-symbols (executors/strategies/translators), migration numbering. Re-enabled the cheap pre-commit hook, tiered `npm audit`, reconciled the CI coverage gate (40→60) and wired 3 orphaned contract gates. ([#3471](https://github.com/diegosouzapw/OmniRoute/pull/3471) — thanks @diegosouzapw) +- **feat(quality):** test-discovery gate + 135 orphan tests re-wired + vitest in CI (fast-tracked Phase 6A.1/6A.2) — new `check:test-discovery` proves every `*.test.ts|tsx` is collected by a runner that actually executes (15 collectors with textual drift-check; orphans frozen in a shrink-only baseline). Found **195 orphan test files** (incl. `authz/routeGuard.test.ts` guarding Rules #15/#17 — already rotten); 135 re-wired into the node runner via explicit-braces recursive globs across all scripts + 4 CI call sites; the remaining 60 are categorized debt. New `test-vitest` CI job: `test:vitest` blocking (146/146), `test:vitest:ui` informational (14 pre-existing UI-drift fails, triage 2026-06-16). ([#3536](https://github.com/diegosouzapw/OmniRoute/pull/3536) — thanks @diegosouzapw) + +### 🔧 錯誤修正 + +- **fix(authz):** restored the missing `BYPASS_PREFIX_NOT_ALLOWED` schema guard (Hard Rules #15/#17) — the zod refine documented as layer-1 in `routeGuard.ts` was absent from the live `settingsSchemas.ts`, so `PATCH /api/settings` accepted spawn-capable prefixes (e.g. `/api/cli-tools/runtime/`) into the manage-scope bypass list (the layer-2 runtime predicate still refused to honour them). Surfaced by re-wired orphan tests AC-8/AC-10c, which now stand as the permanent regression guard. ([#3536](https://github.com/diegosouzapw/OmniRoute/pull/3536) — thanks @diegosouzapw) +- **fix(db):** `closeDbInstance()`/`resetDbInstance()` now fire the `stateReset.ts` module-state resetters (previously only backup-restore did) — `apiKeys.ts` kept a process-level schema memo across a recreated DB, so the stale re-prepare exploded with `no such column: is_active` and clients received **503 instead of 403** for an invalid bearer; the same path hit production when restoring an older backup snapshot. Includes a dedicated regression test; a test that had accommodated the buggy 503 now asserts the deterministic 403. ([#3536](https://github.com/diegosouzapw/OmniRoute/pull/3536) — thanks @diegosouzapw) + +### 🔒 Security + +- **fix(security):** block the cloud-metadata SSRF pivot in the cli-tools catalog fetch (CodeQL `js/request-forgery`, **critical**) — `fetchOmniRouteCatalog()` built its `/v1/models` URL from a user-controlled `baseUrl` and fetched it. Since the legitimate target is the user's own OmniRoute (loopback), the public-only guard can't apply; `assertSafeCatalogUrl()` now blocks the cloud-metadata/link-local pivot (`169.254.169.254`, `metadata.google.internal`, …) unconditionally, plus non-http(s) protocols and embedded credentials, and the request fetches the re-parsed (taint-severed) URL. Loopback and public OmniRoute Cloud targets stay allowed. ([#3544](https://github.com/diegosouzapw/OmniRoute/pull/3544) — thanks @diegosouzapw) + +### 📝 維護 + +- **docs(quality):** Phase 6A critical-audit plan + Phase 7 community-tooling additions, both stored with an activation gate of **2026-06-16** — 6A: stale-allowlist enforcement, ratchet `--require-tighten`, gate scope expansions, remaining orphan/UI-suite triage; Phase 7 additions: gitleaks (Betterleaks noted), actionlint + zizmor, SPDX license compliance. ([#3530](https://github.com/diegosouzapw/OmniRoute/pull/3530) — thanks @diegosouzapw) +- **chore(quality):** conscious, documented re-baselines so the quality-gate debuts holding the REAL published line — file-size frozen at current sizes for 9 files that grew in the v3.8.18 era (RequestLoggerV2 +281, stream +101, combo +73, chatCore +45, …) and `eslintWarnings` 3482→3501 (the published v3.8.18 tag already measured 3501; this cycle is neutral). Driving both down is Phase 6A work. ([#3538](https://github.com/diegosouzapw/OmniRoute/pull/3538) — thanks @diegosouzapw) +- **chore(release):** open the v3.8.19 development cycle (version bump + electron lockfile sync) and ignore generated yt-downloader artifacts. (thanks @diegosouzapw) +- **test:** release-gate stabilization — the re-wired suites + the debuting CI gates surfaced and fixed 6 latent test defects: 2 suites depended on the dev machine's configured password (now hermetic), the breaker reset-timeout test ran on a 5ms margin, the bypass-prefix schema test consecrated the pre-#3536 bug, the chatcore upstream-timeout test had a structurally-broken pending-detail predicate (tested `.providerRequest` on an array — never passed isolated, even at the published v3.8.18 tag), and internal planning docs were excluded from the docs-symbols gate. Coverage floors re-baselined to the honest post-re-wire denominator (78.4% measured: previously-never-imported modules now count). (thanks @diegosouzapw) --- -## [3.8.18] — Unreleased +## [3.8.18] — 2026-06-09 -_Development cycle in progress._ +### ✨ 新功能 + +- **feat(ui):** unified Active + Finished requests into a single view — the dashboard now shows in-flight and completed requests in one list with deep-linking, live streaming detail, and a dedicated `/api/logs/[id]` detail route; pending requests are tracked per connection and finalized as they complete. ([#3401](https://github.com/diegosouzapw/OmniRoute/pull/3401) — thanks @hartmark / @diegosouzapw) +- **feat(plugins):** plugin lifecycle hooks + theme-manager example — adds `onInstall`/`onActivate`/`onDeactivate`/`onUninstall` lifecycle events dispatched by the plugin manager, thins `index.ts` to a backward-compatible re-export shim over `hooks.ts`, and ships theme-manager + request-logger example plugins. ([#3473](https://github.com/diegosouzapw/OmniRoute/pull/3473) — thanks @oyi77 / @diegosouzapw) +- **feat(browserPool):** Playwright proxy resolved from the proxy registry — browser-backed providers (claude-web/gemini-web) now route through the configured per-provider/global proxy instead of connecting directly, matching how OAuth/token-refresh already honor `resolveProxyForProvider` (closes the VPS IP-rate-limit gap for the browser path). Fully additive with graceful degradation. ([#3492](https://github.com/diegosouzapw/OmniRoute/pull/3492) — thanks @borodulin) + +### 🔧 錯誤修正 + +- **fix(executor):** Llama / OpenAI-compat base URL normalization — a `baseURL` without a path (e.g. `llama.example.foo`) or with a non-`/v1` path (e.g. `bar.example.com/foo`) now correctly gets `/v1/chat/completions` appended, fixing the 404 on message sends while `GET /model` still worked. ([#3519](https://github.com/diegosouzapw/OmniRoute/pull/3519) — thanks @hartmark) +- **fix(sse):** empty-choices chunks without usage are dropped instead of injecting retry text — a streamed chunk carrying an empty `choices` array and no `usage` is now silently skipped rather than emitting placeholder retry text into the stream, eliminating spurious content for clients that send such keepalive-style frames. ([#3513](https://github.com/diegosouzapw/OmniRoute/pull/3513) — thanks @diegosouzapw) +- **fix(types):** restored a clean `typecheck:core` — typed `getPendingRequests()` to its real shape (`Record>`) so the unified-requests view (#3401) no longer treats pending counts as `unknown`, cast the `streamChunks` log payload to its declared type, and aligned `preScreenTargets` (#3169) to the canonical `IsModelAvailable` signature (sync-or-async, normalized via `Promise.resolve`). (thanks @diegosouzapw) +- **fix(opencode-plugin):** repaired the corrupted `index.ts` that broke the npm `publish-opencode-plugin` build (introduced by the #3435 branch) — removed two duplicated code blocks (apiFormat + debug-logging), dropped the local `normaliseFreeLabel` superseded by the `naming.ts` extraction, fixed an undefined `sdkBaseURL` reference, declared the missing `startupDebug` / `logLevel` feature-schema fields, and fixed `shortProviderLabel` dropping the prefix on a long displayName with no alias. Plugin now builds (DTS clean) with all 254 tests green. ([#3435](https://github.com/diegosouzapw/OmniRoute/pull/3435) — thanks @diegosouzapw) +- **fix(catalog):** Codex CLI model-catalog refresh no longer errors — `GET /v1/models` now returns a top-level `models: []` array for Codex clients (detected via the `originator` / `user-agent` = `codex_*` headers it sends on `GET /v1/models?client_version=...`), so `codex_models_manager` stops failing to decode the OpenAI-standard response and no longer logs `failed to refresh available models` on every startup. The array is intentionally empty: Codex replaces its built-in per-model agent prompt (`base_instructions`, ~21k chars) with whatever a populated entry carries for the selected model, so emitting our catalog would break Codex's agent behaviour — an empty list keeps Codex on its built-in model info (same inference as before, minus the error). Non-Codex OpenAI clients receive the unchanged `{object,data}` response. ([#3481](https://github.com/diegosouzapw/OmniRoute/pull/3481) — thanks @diegosouzapw) +- **fix(provider):** Cursor's Responses-API-shaped bodies on `/chat/completions` are detected and handled — a body with `input` but no `messages` is now classified as `openai-responses` (instead of forcing `openai` and building from undefined `messages` → upstream 400); standard OpenAI clients are unaffected by the `messages===undefined` guard. ([#3490](https://github.com/diegosouzapw/OmniRoute/pull/3490) — thanks @borodulin) +- **fix(sse):** numeric provider IDs normalized to strings across 4 more surfaces — extends #3427 to the Responses-API SSE passthrough (`response_id`/`item_id`/`call_id`), the buffered/flush path in `stream.ts`, the dedup-key builders, and `sseParser.ts`, preventing `undefined` lookups when IDs arrive as numbers. ([#3451](https://github.com/diegosouzapw/OmniRoute/pull/3451) — thanks @disafronov) +- **fix(theoldllm):** `X-Request-Token` generated server-side, dropping the Playwright dependency — replicates the site's client `rie()` token (djb2 hash + `oldllm-client-2026` seed + UA prefix + 8-hex `crypto.randomUUID` suffix) directly, so The Old LLM no longer needs a headless browser to mint tokens. ([#3491](https://github.com/diegosouzapw/OmniRoute/pull/3491) — thanks @borodulin / @diegosouzapw) +- **fix(combo):** parallel pre-screen + circuit-breaker fast-exit for priority combos — provider profiles and model availability for all targets are pre-screened concurrently (max 5), and targets whose circuit breaker is OPEN are skipped immediately, reducing first-token latency on multi-target priority combos. ([#3169](https://github.com/diegosouzapw/OmniRoute/pull/3169) — thanks @pizzav-xyz) +- **fix(authz):** URL-tokenized client endpoints (`/api/v1/vscode//...`) authenticate again when the caller sends its own non-OmniRoute `Authorization` header — a non-`Bearer ` header (e.g. VS Code Copilot's own, or an empty `Bearer `) no longer short-circuits auth; it falls through to the path-scoped URL token (still validated downstream), instead of 401'ing under `REQUIRE_API_KEY=true`. ([#3504](https://github.com/diegosouzapw/OmniRoute/pull/3504) — thanks @zhiru / @diegosouzapw) +- **fix(playground):** the dashboard provider Test playground works under `REQUIRE_API_KEY=true` — it previously sent the **masked** key (`sk-xxxx****yyyy`) as a bearer (always invalid → 401). It now authenticates via the dashboard session and sends only the key **id** (`x-omniroute-playground-key-id`); the gateway resolves the secret server-side, honored **only** for an authenticated session and never putting the key secret on the wire. ([#3503](https://github.com/diegosouzapw/OmniRoute/pull/3503) — thanks @zhiru / @diegosouzapw) + +### 📝 維護 + +- **feat(docs):** doc-accuracy gate — new `npm run check:fabricated-docs` (`scripts/check/check-fabricated-docs.mjs`) indexes the codebase (api routes, env vars, CLI commands) and flags API-path/env-var/CLI/hook/file-ref claims in `docs/**` + `AGENTS.md` that don't exist in source (soft-fail by default, `--strict` for CI; wired into `check:docs-all`). Also refreshes the AGENTS.md live counts against source. ([#3510](https://github.com/diegosouzapw/OmniRoute/pull/3510) — thanks @oyi77) +- **chore:** ignore local quality reports and prompt artifacts (`quality-metrics.json`, `PLANO-/RELATORIO-QUALITY-GATES.md`, stray prompt `.txt` files) so they no longer surface in `git status`. (thanks @diegosouzapw) + +### 🔒 Security + +- **fix(opencode-plugin):** bounded the regex quantifiers in `normaliseFreeLabel` to close a polynomial-ReDoS (CodeQL `js/polynomial-redos`) — an unbounded `\s*` before an anchored `\s*$` allowed O(n²) backtracking on attacker-influenced provider/model display names; bounded to `{0,8}`/`{1,8}`. (thanks @diegosouzapw) --- -## [3.8.17] — Unreleased +## [3.8.17] — 2026-06-09 -_Development cycle in progress._ +### ✨ 新功能 + +- **feat(providers):** LMArena provider — routes requests to the LMArena battle platform via the new `lmarena` executor; supports streaming chat completions. ([#3421](https://github.com/diegosouzapw/OmniRoute/pull/3421) — thanks @oyi77) +- **feat(providers):** ZenMux provider — adds the `zenmux` executor for ZenMux's OpenAI-compatible endpoint with streaming support. ([#3429](https://github.com/diegosouzapw/OmniRoute/pull/3429) — thanks @oyi77) +- **feat(providers):** Gemini Business provider — adds the `gemini-business` executor (Phase 2C of the Google provider expansion), enabling Gemini models via Google Workspace accounts. ([#3436](https://github.com/diegosouzapw/OmniRoute/pull/3436) — thanks @oyi77) +- **feat(plugin+api):** auto-combos API + free model quota display — new `GET /api/combos/auto` endpoint lists dynamically scored combos; provider pages now surface free-tier quotas inline; MCP-plugin surface extended to match. ([#3435](https://github.com/diegosouzapw/OmniRoute/pull/3435) — thanks @mrmm) +- **feat(opencode-plugin):** per-prefix API format selection, debug logging, and free-label normaliser — three backports from the mrmm fork: each route prefix can specify its own wire format (OpenAI / Anthropic / Gemini), structured debug output is toggled via env var, and free-tier labels are normalized across providers. ([#3420](https://github.com/diegosouzapw/OmniRoute/pull/3420) — thanks @herjarsa) +- **feat(connections):** connection pagination, health filter, batch-delete confirmation, and custom banned keywords — the provider connections table is now paginated; a health-state filter lets operators show only healthy/degraded/failed connections; multi-select + confirm dialog for bulk deletes; per-connection keyword denylist for content safety. ([#3454](https://github.com/diegosouzapw/OmniRoute/pull/3454) — thanks @sdfsdfw2) +- **feat(settings):** Endpoint Token Saver visibility toggle — operators can now show or hide the Token Saver widget on the endpoint page from Settings → Appearance. ([#3461](https://github.com/diegosouzapw/OmniRoute/pull/3461) — thanks @rdself) +- **feat(catalog):** model catalog name feature flag — a new feature flag controls whether the catalog exposes provider-prefixed model names, letting deployments opt into the legacy bare-name format for downstream tooling compatibility. ([#3464](https://github.com/diegosouzapw/OmniRoute/pull/3464) — thanks @rdself) + +### 🔧 錯誤修正 + +- **fix(translator):** Vertex AI tool calls no longer fail with `400 Unknown name "id"` — the OpenAI-style `id` field is stripped from `functionCall`/`functionResponse` parts for `vertex`/`vertex-partner`; the public Gemini API still receives `id` as required for Gemini 3+ signature matching. ([#3457](https://github.com/diegosouzapw/OmniRoute/pull/3457) — thanks @nullbytef0x / @diegosouzapw) +- **fix(claude):** Claude Code `claude-opus-4-8` tool calls no longer break with `tool call could not be parsed` — OmniRoute no longer force-injects `interleaved-thinking` / `advanced-tool-use` / `effort` beta flags the client never negotiated; clients sending their own `anthropic-beta` header control those betas themselves. ([#3458](https://github.com/diegosouzapw/OmniRoute/pull/3458) — thanks @Forcerecon / @diegosouzapw) +- **fix(catalog):** imported/custom models on no-auth providers (e.g. The Old LLM) now appear in `GET /api/v1/models` and the Playground model selector — the eligibility gate required a DB connection row which no-auth providers never have, silently dropping every imported model for them. ([#3463](https://github.com/diegosouzapw/OmniRoute/pull/3463) — thanks @tjengbudi / @diegosouzapw) +- **fix(browser):** optional `cloakbrowser` import no longer causes bundle errors when the package is absent — the import is now wrapped in a dynamic require so the build succeeds on environments that don't install the optional dep. ([#3460](https://github.com/diegosouzapw/OmniRoute/pull/3460) — thanks @rdself) +- **fix(claude-web):** claude-web session handling cleanup — corrects an edge case where session cookies were not properly refreshed after a Turnstile challenge, and removes stale wrapper code left over from the provider split. ([#3449](https://github.com/diegosouzapw/OmniRoute/pull/3449) — thanks @androw) +- **fix(analytics):** SQL named params are now scoped per query context — a shared params object was being mutated across concurrent analytics queries, causing `SQLITE_MISUSE: named parameter not found` errors under load. ([#3447](https://github.com/diegosouzapw/OmniRoute/pull/3447) — thanks @ReqX) +- **fix(command-code):** chat endpoint reverted to `/alpha/generate` and model-sync discovery fixed — a prior refactor incorrectly targeted the wrong path, causing Command Code completions to silently 404; model listing now also resolves from the correct discovery endpoint. ([#3432](https://github.com/diegosouzapw/OmniRoute/pull/3432) — thanks @TapZe) +- **fix(command-code):** CLI version header aligned to current Command Code release — the `X-Command-Code-Version` header value was pinned to a stale version string, causing upstream version-gated features to be rejected. ([#3462](https://github.com/diegosouzapw/OmniRoute/pull/3462) — thanks @hevener10) +- **fix(sse):** provider IDs are normalized to strings before lookup — numeric provider IDs (e.g. from legacy DB rows) caused `undefined` lookups in the executor registry; all IDs are now coerced to string at the SSE entry point. ([#3427](https://github.com/diegosouzapw/OmniRoute/pull/3427) — thanks @disafronov) +- **fix(stream):** textual tool-call slicing index mismatch resolved and `containsTextualToolCallMarker` deduplicated — two related bugs in the rolling-buffer parser caused partial tool-call chunks to be emitted twice or sliced from the wrong offset, producing garbled JSON in streamed tool responses. ([#3413](https://github.com/diegosouzapw/OmniRoute/pull/3413) — thanks @Ardem2025) +- **fix(stream):** OpenAI usage-only chunks (empty `choices: []`) are now passed through instead of being dropped — some providers emit a trailing stats-only chunk after the last content delta; discarding it caused usage counters to be missing in logged responses. ([#3422](https://github.com/diegosouzapw/OmniRoute/pull/3422) — thanks @xz-dev) +- **fix(translator):** empty-string `reasoning_content` replaced with placeholder on cache miss — `injectEmptyReasoningContentForToolCalls` pre-sets `reasoning_content=""` before the cache lookup; the old guard checked for `undefined`, never firing on miss and leaving `""` in place, which DeepSeek V4+ rejects with a 400. ([#3433](https://github.com/diegosouzapw/OmniRoute/pull/3433) — thanks @ViFigueiredo) +- **fix(catalog):** combos auto-compute `context_length` for any provider-ID form — the context-length resolution only matched exact-string provider IDs, missing combos declared with a numeric or aliased ID; the lookup now normalizes before matching. ([#3417](https://github.com/diegosouzapw/OmniRoute/pull/3417) — thanks @herjarsa) +- **fix(healthcheck):** container bridge network IP probed correctly — the healthcheck script was hard-coded to `localhost` which resolves to IPv6 `::1` inside some container runtimes; it now queries the bridge gateway IP so the probe succeeds on both bridge and host networking modes. ([#3434](https://github.com/diegosouzapw/OmniRoute/pull/3434) — thanks @naimo84) +- **fix(publish):** onnxruntime CUDA binary removed from npm tarball — the native `.node` binary exceeded npm's 413 payload limit and was never needed at runtime (OmniRoute uses the CPU build); the pack policy now excludes the CUDA artifact. ([#3437](https://github.com/diegosouzapw/OmniRoute/pull/3437) — thanks @herjarsa) + +### 📝 維護 + +- **docs:** critical documentation gaps closed — new guides for ACP protocol, router strategies, compression, REST API reference, and updated AUTO-COMBO deep-dive; getting-started section added with Quick Start, Providers, Free Tiers, Auto-Combo, and Troubleshooting pages. ([#3438](https://github.com/diegosouzapw/OmniRoute/pull/3438) — thanks @oyi77) +- **docs(opencode-plugin):** plugin README rewritten to lead with the why — positions the plugin as the recommended integration path over the legacy `@omniroute/opencode-provider` package, with migration guidance. ([#3418](https://github.com/diegosouzapw/OmniRoute/pull/3418) — thanks @herjarsa) +- **docs(env):** `COMMAND_CODE_VERSION` override documented — environment variable added to `.env.example` and reference docs so operators can pin the CLI version header without a code change. ([#3462](https://github.com/diegosouzapw/OmniRoute/pull/3462) — thanks @hevener10) +- **test(auto-combo):** same-provider connection identity assertion added — regression test covering the case where two connections for the same provider share an account ID, verifying the combo engine selects the correct one. ([#3378](https://github.com/diegosouzapw/OmniRoute/pull/3378) — thanks @oyi77) +- **deps:** electron upgraded to 42.3.3; electron-builder to 26.15.2; electron-updater to 6.8.9; 4 development-group and 10 production-group packages bumped via Dependabot. ([#3441](https://github.com/diegosouzapw/OmniRoute/pull/3441) / [#3442](https://github.com/diegosouzapw/OmniRoute/pull/3442) / [#3443](https://github.com/diegosouzapw/OmniRoute/pull/3443) / [#3444](https://github.com/diegosouzapw/OmniRoute/pull/3444) / [#3445](https://github.com/diegosouzapw/OmniRoute/pull/3445) — thanks @diegosouzapw) +- **chore(release):** v3.8.17 development cycle opened from `main`. (thanks @diegosouzapw) --- -## [3.8.16] — Unreleased +## [3.8.16] — 2026-06-08 -_Development cycle in progress._ +### ✨ 新功能 + +- **feat(vision-bridge):** auto-routing to the fastest available vision model — when a request carries image content and the selected model does not support vision, OmniRoute now transparently delegates to the best-match vision-capable model instead of returning an error. ([#3377](https://github.com/diegosouzapw/OmniRoute/pull/3377) — thanks @herjarsa) +- **feat(web-session):** web-session pool observability — new MCP tool `get_web_session_pool_health` and a health-matrix REST response (`GET /api/web-session-pool/health`) expose per-provider slot counts, lease ages, and error budgets so operators can diagnose pool exhaustion without digging through logs. ([#3395](https://github.com/diegosouzapw/OmniRoute/pull/3395) — thanks @oyi77) +- **feat(web-session):** adaptive keepalive threshold — the keepalive heartbeat interval now self-adjusts based on observed provider idle-disconnect behaviour instead of using a fixed constant, reducing both unnecessary pings and unexpected session drops. ([#3397](https://github.com/diegosouzapw/OmniRoute/pull/3397) — thanks @oyi77) +- **feat(web-session):** bulk credential import endpoint (`POST /api/web-session/import`) — import a JSON array of session credentials in one call; each entry is validated and inserted atomically, with per-entry success/failure reported in the response. ([#3403](https://github.com/diegosouzapw/OmniRoute/pull/3403) — thanks @oyi77) +- **feat(api):** REST API for session pool health (`GET /api/session-pool/health`) — a dashboard-facing endpoint that aggregates live slot usage, wait-queue depth, and error rates across all active session pools; wired to a new dashboard widget. ([#3404](https://github.com/diegosouzapw/OmniRoute/pull/3404) — thanks @oyi77) + +### 🔧 錯誤修正 + +- **fix(sse):** eliminate race window in `usageTokenBuffer` settings update — a concurrent save + stream-start could race to apply stale settings, causing token counts to roll back by up to 2 000 tokens after a restart; the update now uses an atomic read-modify-write on the shared settings ref. ([#3405](https://github.com/diegosouzapw/OmniRoute/pull/3405) — thanks @diegosouzapw) +- **fix(context-cache):** server-side context-cache pinning now correctly persists across restarts; proxy message content no longer leaks into the upstream prompt; and the `context_cache_protection` toggle is properly saved to the DB on change. ([#3399](https://github.com/diegosouzapw/OmniRoute/pull/3399) — thanks @k0valik) +- **fix(providers):** the provider settings page now refreshes its model list after a successful `sync-models` call — previously the stale list remained until a full page reload. ([#3402](https://github.com/diegosouzapw/OmniRoute/pull/3402) — thanks @0xtbug) +- **fix(stream):** empty-choices chunks (choices array present but empty, no `finish_reason`) are now silently dropped rather than emitted as a `retry:` SSE event — removes spurious retry lines from streaming responses for providers that emit heartbeat keep-alive chunks. ([#3400](https://github.com/diegosouzapw/OmniRoute/pull/3400) — thanks @0xtbug) +- **fix(account-fallback):** the connection cooldown deduplication state is now preserved across the fallback retry chain — previously a second concurrent failure on the same account could clear the dedupe flag set by the first, allowing the cooldown window to be extended twice. ([#3381](https://github.com/diegosouzapw/OmniRoute/pull/3381) — thanks @oyi77) +- **fix(stream):** false-positive textual tool-call marker truncation — `containsTextualToolCallMarker` now tracks how much of the accumulated streamed content has already been emitted, so it only withholds the unemitted tail rather than re-scanning from the start on every new chunk. ([#3382](https://github.com/diegosouzapw/OmniRoute/pull/3382) — thanks @Ardem2025) +- **fix(sanitizer):** `containsTextualToolCallContent()` now requires the complete `[Tool call: name]\nArguments:` header pattern instead of a bare `.includes("[Tool call:")` check — prevents the non-streaming response sanitizer from nulling out model responses that merely quote `[Tool call:]` in prose or code examples. ([#3355](https://github.com/diegosouzapw/OmniRoute/pull/3410) — thanks @diegosouzapw) +- **fix(stream):** the streaming textual tool-call guard now flushes any remaining buffered content as plain text when the stream ends, regardless of whether the buffer contains `"Arguments:"` — previously, a partial/incomplete tool-call header that arrived at end-of-stream was silently dropped. ([#3355](https://github.com/diegosouzapw/OmniRoute/pull/3410) — thanks @diegosouzapw) +- **fix(executor):** Mistral (and any provider in `PROVIDERS_REQUIRING_USER_LAST_MESSAGE`) no longer receives a trailing `assistant` message with plain text content — `stripTrailingAssistantForProvider` drops it on the upstream-send path, fixing the `400: Expected last role User or Tool … but got assistant` rejection. ([#3396](https://github.com/diegosouzapw/OmniRoute/pull/3409) — thanks @diegosouzapw) +- **fix(mitm):** `getMitmStatus()` in the build-time stub (Docker image) now returns a graceful `{ running: false }` status instead of throwing, so the Agent Bridge UI shows a clean "stopped" state rather than an error banner in containerised deployments. ([#3390](https://github.com/diegosouzapw/OmniRoute/pull/3408) — thanks @diegosouzapw) +- **fix(env):** corrected casing of `OMNIROUTE_TRACE` in `.env.example` and all related documentation files — was previously mixed-case in some places, causing the variable to be silently ignored on case-sensitive file systems. ([#3393](https://github.com/diegosouzapw/OmniRoute/pull/3393) — thanks @androw) +- **fix(featureFlags):** `PRICING_SYNC_ENABLED` description now clearly states that the feature requires the corresponding environment variable to be set — removes the ambiguity that led operators to enable it via the UI only and wonder why sync never ran. ([#3394](https://github.com/diegosouzapw/OmniRoute/pull/3394) — thanks @androw) + +### 📝 維護 + +- **ci(docker):** the CI pipeline now builds and publishes the `-web` image variant in the same Docker publish workflow, so both the standard and browser-backed images stay in sync on every release. ([#3389](https://github.com/diegosouzapw/OmniRoute/pull/3389) — thanks @zhiru) +- **ci(e2e):** E2E shard suite hardened — timeout raised to 45 min for the heaviest shard; build artifact now uses an explicit `tar` bundle to avoid `upload-artifact@v4` LCA path ambiguity; `node_modules` copied into standalone after download; browser cache added to cut cold-shard time; `sync-models` endpoint mocked in `providers-management.spec.ts` so the import modal reaches "done" immediately. (thanks @diegosouzapw) +- **docs:** Codex CLI configuration guide added to the dashboard (`/dashboard/codex-config`) — covers profile naming, model selection, and the `CODEX_*` environment variables accepted by OmniRoute. (thanks @diegosouzapw) +- **chore(agentSkills):** catalog expanded to 43 entries — `config-codex-cli` added as a new `CONFIG_SKILL_IDS` category; all skill-count assertions updated across unit and integration test suites; `next-fetch` opts cast to satisfy the TypeScript overload signature in the skill runner. (thanks @diegosouzapw) --- -## [3.8.15] — Unreleased +## [3.8.15] — 2026-06-07 -_Development cycle in progress._ +### ✨ 新功能 + +- **feat(error-rules):** provider-specific error classification with scope — a declarative rules layer lets providers map upstream error shapes to the right resilience action (provider circuit-breaker vs connection cooldown vs model lockout) at the correct scope, instead of relying on generic status-code heuristics. ([#3370](https://github.com/diegosouzapw/OmniRoute/pull/3370) — thanks @herjarsa) + +### 🔧 錯誤修正 + +- **fix(combo):** add `429` to `PROVIDER_FAILURE_ERROR_CODES` so a rate-limited target no longer drives an infinite retry loop — the combo now cools the target down and moves on. ([#3366](https://github.com/diegosouzapw/OmniRoute/pull/3366) — thanks @herjarsa) +- **fix(catalog):** add a `getTokenLimit` fallback for combo targets with an unknown context window, so a target whose context can't be resolved no longer breaks token-limit computation for the combo. ([#3369](https://github.com/diegosouzapw/OmniRoute/pull/3369) — thanks @herjarsa) +- **fix(auto-combo):** include no-auth providers in Auto-Combo declaratively (driven by provider metadata rather than a hard-coded list), so keyless providers are eligible candidates. ([#3365](https://github.com/diegosouzapw/OmniRoute/pull/3365) — thanks @oyi77) +- **fix(auto-combo):** validate web-session credentials before selecting a web-cookie provider as an Auto-Combo target, so an expired/empty session doesn't get picked. ([#3371](https://github.com/diegosouzapw/OmniRoute/pull/3371) — thanks @oyi77) +- **fix(command-code):** update the Command Code base URL from `/alpha/` to `/provider/v1/` (upstream moved the endpoint). ([#3372](https://github.com/diegosouzapw/OmniRoute/pull/3372) — thanks @TapZe) +- **fix(kiro):** probe `%APPDATA%\kiro\storage.db` on Windows during Kiro auto-import, so the import finds the credential store where Kiro actually writes it on Windows. ([#3375](https://github.com/diegosouzapw/OmniRoute/pull/3375), fixes #3363 — thanks @diegosouzapw; reported by @Gerashka2) + +### 📝 維護 + +- **fix(migrations):** restore `095_provider_node_custom_headers.sql` — it was twice deleted from the release branch by a contributor branch's `git rm` of a duplicate getting folded into the squash merge; restored and guarded. (thanks @diegosouzapw) + +### 🙌 貢獻者 + +Thanks to everyone whose work landed in v3.8.15: + +| Contributor | PRs / Issues | +| ------------------------------------------------ | -------------------------------------------------- | +| [@herjarsa](https://github.com/herjarsa) | #3366, #3369, #3370 | +| [@oyi77](https://github.com/oyi77) | #3365, #3371 | +| [@TapZe](https://github.com/TapZe) | #3372 | +| [@Gerashka2](https://github.com/Gerashka2) | reported #3363 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer — #3375 shepherding, migration restores | --- -## [3.8.14] — Unreleased +## [3.8.14] — 2026-06-07 -_Development cycle in progress._ +### ✨ 新功能 + +- **feat(api):** per-provider **custom headers** for OpenAI/Anthropic-compatible provider nodes — attach operator-defined headers (e.g. tenant/routing headers) to upstream requests via a new `customHeaders` field on provider nodes (`custom_headers_json` column, migration 095). Hardened on merge: values/names validated through the canonical `upstreamHeadersRecordSchema` (CRLF/control-char/length/16-max) with a single shared `isForbiddenCustomHeaderName()` denylist (hop-by-hop + auth), applied case-insensitively, and honored for `anthropic-compatible-cc-*` nodes too. ([#3338](https://github.com/diegosouzapw/OmniRoute/pull/3338) — thanks @pizzav-xyz / @diegosouzapw) + +### 🔒 Security + +- **fix(security):** provider auto-sync self-fetch now uses a trusted loopback/env-pinned origin (`getModelSyncInternalBaseUrl()`) instead of `new URL(request.url).origin`, so a management-authenticated caller can no longer redirect the credential-bearing internal request to an arbitrary host via the `Host` header (CodeQL `js/request-forgery`, critical). Shipped to Docker/Electron in v3.8.13; reaches npm here (npm `3.8.13` was immutable). ([#3336](https://github.com/diegosouzapw/OmniRoute/pull/3336), CodeQL #323 — thanks @diegosouzapw) + +### 🔧 錯誤修正 + +- **fix(translator):** every Gemini/Vertex `functionDeclaration.parameters` is now coerced to an OBJECT-typed schema before cleaning. Clients like GitHub Copilot send some tools (e.g. `terminal_last_command`) whose `parameters` is present but lacks a top-level `type: "object"` (just `{ properties }`, a scalar type, or `{}`); these slipped through `buildGeminiTools`' `params || default` guard and Vertex rejected them with `[400] ... functionDeclaration parameters schema should be of type OBJECT`. Hardens every OpenAI→Gemini tool request (Vertex / antigravity / agy / gemini). (#3357 — thanks @nullbytef0x) +- **fix(gemini):** normalize Gemini/Antigravity textual `[Tool call: ...]` markers — stop suppressing **false positives** (legitimate assistant prose that merely mentions `[Tool call: terminal]`, e.g. in backticks, is preserved instead of being swallowed) and correctly buffer markers **split across streaming chunks** (`[Tool` + ` call: terminal]` + `Arguments: {...}`), flushing the text when it turns out not to be a tool call. Dedups the parsing/validation into a shared `open-sse/utils/textualToolCall.ts` (new `isValidToolCallHeaderPrefix`) and adds `gemini-2.5-flash`/`gemini-3.5-flash-low` model specs. ([#3358](https://github.com/diegosouzapw/OmniRoute/pull/3358) — thanks @Ardem2025 / @diegosouzapw) +- **fix(electron):** clicking "Exit" (or applying an update) now terminates the **whole** server process tree, not just the direct child. The embedded server runs as `omniroute.exe`-as-node (`ELECTRON_RUN_AS_NODE`) and spawns grandchildren (embedded services, MITM proxy, tunnels); on Windows `ChildProcess.kill()` only terminates the direct child, so survivors kept `omniroute.exe` locked — the process "hung in memory" after Exit and updates failed with "file in use". New `killProcessTree()` helper uses `taskkill /PID /T /F` on Windows (signal-based on POSIX); wired into `stopNextServer`, the `waitForServerExit` force-kill, and `installUpdate`. (#3347 — thanks @Flexible78) +- **fix(proxy):** proxy auto-selection is now **opt-in** (new `PROXY_AUTO_SELECT_ENABLED` flag, default off). Previously a single proxy in the registry silently became a global fallback for **all** provider connections (the Step-11 fallback listed every registry proxy, ignoring assignments and per-connection `proxy_enabled`). It now no-ops unless the operator enables the flag. (#3332 — thanks @hertznsk) +- **fix(cli):** write the OpenCode config to `~/.config/opencode/opencode.json` on **all** platforms — on Windows OmniRoute wrote to `%APPDATA%\opencode\` but OpenCode reads from `%USERPROFILE%\.config\opencode\` (XDG), so dashboard-saved config silently had no effect. (#3330 — thanks @abdulkadirozyurt) +- **fix(catalog):** remove `minimaxai/minimax-m3` from the **NVIDIA NIM** tier — NVIDIA does not host it yet, so every request 404'd (`404 page not found`), while sibling `minimax-m2.7` on the same provider works. MiniMax M3 stays available on the tiers that actually serve it. (#3329 — thanks @mikmaneggahommie) +- **fix(sse):** treat **MiniMax M3** as multimodal so the compression layer no longer strips image parts from vision requests — `lite.ts modelSupportsVision` now keeps images for `minimax-m3*` (see also the registry `supportsVision` alignment in Maintenance). ([#3328](https://github.com/diegosouzapw/OmniRoute/pull/3328) — thanks @diegosouzapw) +- **fix(oauth):** Kiro **Builder ID** token import no longer fails with "Bad credentials" — `validateImportToken` only ever tried the social-auth refresh; it now uses the cached AWS SSO `clientId`/`clientSecret` (`~/.aws/sso/cache/*.json`) and the OIDC refresh path (`authMethod: "builder-id"`), with a TDD harness. ([#3333](https://github.com/diegosouzapw/OmniRoute/pull/3333) — thanks @quanturbo / @diegosouzapw) +- **fix(provider-proxy):** honor per-account proxy toggles — a connection with `proxy_enabled = false` is no longer forced through an assigned/registry proxy. ([#3349](https://github.com/diegosouzapw/OmniRoute/pull/3349) — thanks @rdself) +- **fix(providers):** reduce proxy label noise on the provider page (clearer proxy assignment/state display). ([#3346](https://github.com/diegosouzapw/OmniRoute/pull/3346) — thanks @wilsonicdev) +- **fix(noauth):** expose only **usable** model aliases for no-auth providers, so the catalog no longer advertises aliases that can't actually be called. ([#3345](https://github.com/diegosouzapw/OmniRoute/pull/3345) — thanks @oyi77) +- **fix(duckduckgo):** restore the bare `Response` contract for the DuckDuckGo/browser-backed executor (rebased onto the cycle), fixing a wrapping-contract regression. ([#3323](https://github.com/diegosouzapw/OmniRoute/pull/3323) — thanks @oyi77 / @diegosouzapw) +- **fix(dashboard):** drop the duplicate "Distribute Proxies" button on the provider page — it rendered twice at once (provider toolbar + accounts-list header) whenever connections existed and none were selected. The toolbar button (global) and the per-tag-group buttons remain. ([#3352](https://github.com/diegosouzapw/OmniRoute/pull/3352) — thanks @diegosouzapw) +- **fix(electron):** ship `loginManager.js` in the packaged app — #3292 added it (and a `require("./loginManager")` in `main.js`) without adding it to electron-builder's `build.files`, so the packaged app crashed at startup with "Cannot find module" on the Linux/macOS smoke tests. Plus a regression test asserting every local `require("./x")` in the Electron entry points is shipped. ([#3334](https://github.com/diegosouzapw/OmniRoute/pull/3334) — thanks @diegosouzapw) +- **fix(startup):** correct the #3292 auto-refresh daemon import (`@/open-sse/...` → `@omniroute/open-sse/services/autoRefreshDaemon`); the `@/` alias maps to `src/`, so the daemon silently never ran in the built standalone (non-fatal "Cannot find module", caught at runtime). Adds a regression test banning `@/open-sse/*` imports in `src/`. ([#3335](https://github.com/diegosouzapw/OmniRoute/pull/3335) — thanks @diegosouzapw) +- **fix(electron):** wrap `autoUpdater.checkForUpdates()` so a 404/offline/rate-limited update check can no longer surface as an unhandled rejection (the `error` event still notifies the user); fixes the macOS-intel packaged-app smoke failure. ([#3339](https://github.com/diegosouzapw/OmniRoute/pull/3339) — thanks @diegosouzapw) +- **fix(dashboard):** stop the infinite render loop on `/dashboard/cli-agents/hermes-agent` — `HermesAgentToolCard` listed `currentRoles` in the config-load effect's deps while `loadCurrentConfig()` set `currentRoles` to a fresh object on every fetch, so the effect re-fired → refetched → re-set forever (the page spun and spammed `GET /api/cli-tools/hermes-agent-settings` in the console; it manifested only on the always-expanded detail page). `loadCurrentConfig` is now memoized and the batch-seed reads `currentRoles` via a functional update, so the effect runs once. Adds a jsdom regression test asserting the settings endpoint is fetched a bounded number of times. ([#3353](https://github.com/diegosouzapw/OmniRoute/pull/3353) — thanks @diegosouzapw) +- **fix(dashboard):** the Usage Analytics card now surfaces the **real** backend error (status + message) instead of a generic placeholder when `/api/usage/analytics` fails — a new shared `fetchError.ts` helper extracts a useful message. ([#3356](https://github.com/diegosouzapw/OmniRoute/pull/3356) — thanks @diegosouzapw) + +### 📝 維護 + +- **fix(review):** harden the per-provider custom-headers feature surfaced by the `/review-reviews` battery — `updateProviderNode` no longer wipes stored `custom_headers_json` on a partial update that omits the field; `customHeadersSchema` reuses the canonical `upstreamHeadersRecordSchema` guards (CRLF/control-char/length/16-max) and rejects auth header names via a single shared `isForbiddenCustomHeaderName()` denylist (executor + schema no longer keep divergent copies); custom headers now reach the wire for `anthropic-compatible-cc-*` nodes and override the executor's own `Content-Type`/`Accept` case-insensitively instead of duplicating them; and `rowToCamel` normalizes a NULL `_json` column to `baseKey: null`. ([#3350](https://github.com/diegosouzapw/OmniRoute/pull/3350) — thanks @diegosouzapw) +- **fix(catalog):** flag every `minimax-m3` registry entry `supportsVision` (not just the opencode free tier) so the vision-bridge guardrail and the compression layer agree the model is multimodal on all tiers (completes #3328). (thanks @diegosouzapw) +- **fix(oauth):** Kiro Builder ID import forwards the requested `region` to the OIDC validation refresh (no longer pinned to `us-east-1`), prefers the region-matching cached SSO client registration over the first file found, and falls `expiresIn` back to 3600 on the OIDC path. (thanks @diegosouzapw) +- **fix(db):** migration `095` gains an `isSchemaAlreadyApplied` guard so a fresh DB (where `SCHEMA_SQL` already creates `custom_headers_json`) skips it cleanly instead of throwing-then-catching a duplicate-column error. (thanks @diegosouzapw) +- **test:** align stale cycle tests with shipped behavior — NVIDIA `minimaxai/minimax-m3` removal (#3329), the 29th feature flag (`PROXY_AUTO_SELECT_ENABLED`, #3332), and the OpenCode `~/.config` path on Windows (#3330). (thanks @diegosouzapw) +- **docs:** add a documentation comment to the exported `GET` handler in the context-analytics route. ([#3337](https://github.com/diegosouzapw/OmniRoute/pull/3337) — thanks @Lang-Qiu) +- **docs(i18n):** translate 25 core documentation files to Indonesian. ([#3348](https://github.com/diegosouzapw/OmniRoute/pull/3348) — thanks @KrisnaSantosa15) + +### 🙌 貢獻者 + +Thanks to everyone whose work landed in v3.8.14: + +| Contributor | PRs / Issues | +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| [@pizzav-xyz](https://github.com/pizzav-xyz) | #3338 | +| [@quanturbo](https://github.com/quanturbo) | #3333 | +| [@oyi77](https://github.com/oyi77) | #3323, #3345 | +| [@rdself](https://github.com/rdself) | #3349 | +| [@wilsonicdev](https://github.com/wilsonicdev) | #3346 | +| [@hertznsk](https://github.com/hertznsk) | #3332 | +| [@abdulkadirozyurt](https://github.com/abdulkadirozyurt) | #3330 | +| [@mikmaneggahommie](https://github.com/mikmaneggahommie) | #3329 | +| [@Flexible78](https://github.com/Flexible78) | #3347 | +| [@Lang-Qiu](https://github.com/Lang-Qiu) | #3337 | +| [@KrisnaSantosa15](https://github.com/KrisnaSantosa15) | #3348 | +| [@nullbytef0x](https://github.com/nullbytef0x) | #3357 | +| [@Ardem2025](https://github.com/Ardem2025) | #3358 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer — #3334, #3335, #3336, #3339, #3350, #3352, #3353, #3356; review/hardening across the cycle | --- -## [3.8.13] — Unreleased +## [3.8.13] — 2026-06-06 -_Development cycle in progress._ +### ✨ 新功能 + +- **feat(web-cookie):** self-service login infrastructure for 21 web-cookie providers — three login pathways (Electron BrowserWindow, Playwright dashboard fallback, `POST /api/providers/{id}/login`), token-extraction configs, and a 15-min cookie-validity auto-refresh daemon. Hardened on merge: error bodies sanitized (Hard Rule #12), the spawn-capable login route classified LOCAL_ONLY (Hard Rules #15/#17), and the Electron status listener de-duplicated. ([#3292](https://github.com/diegosouzapw/OmniRoute/pull/3292), closes #3070 — thanks @oyi77 / @diegosouzapw) +- **feat(api):** accept path-scoped API keys on client API routes — keys may arrive via `/api/v1/vscode//…` path aliases (incl. `raw`/`combos`); explicit `Authorization`/`x-api-key` headers still take precedence. Split out of #3073. ([#3300](https://github.com/diegosouzapw/OmniRoute/pull/3300) — thanks @zhiru) +- **feat(api):** model-catalog enrichment + MCP `model-catalog` tools — richer per-model metadata (context window, capabilities) surfaced through `/v1/models` and new MCP tools, plus `readHeaderValue` header-record support. Split out of #3073; reconciled on merge with the #3309 URL-token hardening (kept the security gate — no query-string credential fallback, management auth stays header-only). ([#3306](https://github.com/diegosouzapw/OmniRoute/pull/3306) — thanks @zhiru / @diegosouzapw) +- **feat(dashboard):** internationalize the proxy settings UI — `ProxyTab` + the proxy `DocumentationTab`/`FreePoolTab`/`VercelRelayModal` now render via `t(...)`, with matching `en`/`pt-BR` message keys. Split out of #3073. ([#3307](https://github.com/diegosouzapw/OmniRoute/pull/3307), [#3310](https://github.com/diegosouzapw/OmniRoute/pull/3310) — thanks @zhiru) +- **feat(provider):** provider test-all endpoint + per-connection rate-limit overrides + model visibility — `POST /api/models/test-all` runs parallel model tests (chunked, timeout-skip) atop a shared `runSingleModelTest` runner; per-connection rate-limit overrides land via `PATCH /api/providers/:id` (new `rate_limit_overrides_json` column + Zod schema); a dashboard model-visibility toolbar (All / Visible / Hidden) drives a `/v1/models` catalog that excludes user-hidden models; models auto-fetch on every connection add; and passthrough (OpenRouter) models gain test buttons. Folds in dashboard fixes on merge (missing alias/delete handlers, duplicate-model-ID React keys, "Hide all" restored) and a build fix so empty `.env` values no longer override real config. ([#3267](https://github.com/diegosouzapw/OmniRoute/pull/3267) — thanks @Vinayrnani) +- **feat(api):** VS Code Copilot Ollama-compatible BYOK endpoint — exposes an Ollama-shaped surface so VS Code Copilot's "bring your own key" Ollama provider can target OmniRoute directly, with a `VscodeTokenAliasCard` in the dashboard endpoint tab to generate the path-scoped token alias. ([#3316](https://github.com/diegosouzapw/OmniRoute/pull/3316) — thanks @zhiru) +- **feat(combo):** Auto-Combo candidate-expansion optimization + playground model dropdown + "only configured" model toggle — reworks the `auto` strategy's candidate selection in `combo.ts` and surfaces a model picker in the playground `StudioConfigPane` / `useAvailableModels`. ([#3322](https://github.com/diegosouzapw/OmniRoute/pull/3322) — thanks @oyi77) + +### 🔒 Security + +- **fix(auth):** follow-up hardening of the client-API key extractor (#3300) — removed the generic query-string token fallbacks (`?token=`/`?key=`/`?apiKey=`/`?api_key=`), which leak credentials into access logs / Referer headers, and gated URL-borne tokens to client routes only (management auth is now header-only) so a credential in the URL can never authenticate a management route. The path-scoped `/vscode//…` form the VS Code integration needs is unchanged. (security review follow-up to [#3300](https://github.com/diegosouzapw/OmniRoute/pull/3300) — thanks @zhiru / @diegosouzapw) + +### 🔧 錯誤修正 + +- **fix(dashboard):** Agent Bridge page (`/dashboard/tools/agent-bridge`) no longer crashes with "Internal Server Error" — the page replaced its well-shaped state with the raw `/api/tools/agent-bridge/state` response (`{ server, agents }`), leaving `serverState` undefined and throwing `Cannot read properties of undefined (reading 'running')`. A shared `normalizeAgentBridgeState()` now maps the route shape into the page contract (incl. `server.certExists → certTrusted`) and always returns safe defaults, used by both the SSR loader and the polling hook. (#3318 — thanks @tycronk20) +- **fix(codex):** strip client-only params (`prompt_cache_retention`, `safety_identifier`, `user`) on the native `codex/` `/v1/responses` passthrough — Codex upstream rejects them with `400 Unsupported parameter`, which broke Factory Droid and any client injecting those fields. The chat-completions path already stripped them; the responses→responses passthrough now does too. (#3317 — thanks @tycronk20) +- **fix(theoldllm):** stop the `[502]: Body is unusable: Body has already been read` error on the cached-token path — the executor read the same upstream `Response` body with `.text()` twice; it now reads it once and only re-reads after a token-rejection refetch. (#3296 — thanks @onizukashonan14-png) +- **fix(dashboard):** keep no-auth providers (opencode, duckduckgo-web, theoldllm, veoaifree-web) visible under the "Show configured only" filter — they never create a connection row (`stats.total === 0`) but are always usable and already appear in `/v1/models`, so the filter now treats `displayAuthType === "no-auth"` as configured. (#3290 — thanks @uniQta) +- **fix(dashboard):** refresh the connection list after a Codex/Claude/Gemini auth import — the import modals called `fetchData()` (which only reloads provider metadata), so a freshly-imported connection stayed invisible until a manual reload; they now call `fetchConnections()`. ([#3320](https://github.com/diegosouzapw/OmniRoute/pull/3320) — thanks @zhiru) +- **fix(cli):** `omniroute update` no longer always fails on a global install — `getCurrentVersion()` and `createBackup()` now resolve `package.json`/`bin` relative to the script (`import.meta.url`) instead of `process.cwd()` (the user's working dir on a global npm/brew install → _"Could not determine current version"_), and the backup copies the `cli` directory with `cpSync({recursive:true})` instead of `copyFileSync`, which threw a swallowed `EISDIR` → _"Failed to create backup. Aborting"_. (#3295 — thanks @uniQta) +- **fix(sse):** harden the passthrough stream against empty upstream responses — emit a synthetic retry chunk on an empty `choices: []` (fixes a Copilot Chat crash) and log empty post-`tool_calls` completions; also registers **MiniMax M3** (1M context) across 8 provider tiers. ([#3297](https://github.com/diegosouzapw/OmniRoute/pull/3297), #3110 — thanks @wilsonicdev) +- **fix(opencode-provider):** extract `contextLength` from the live `/v1/models` catalog (live > `modelContextLengths` > static map) so passthrough models outside the legacy 8-model map no longer silently truncate to OpenCode's 128K default. ([#3298](https://github.com/diegosouzapw/OmniRoute/pull/3298) — thanks @herjarsa / @diegosouzapw) +- **fix(dev):** auto-rebuild `better-sqlite3` on a Node ABI mismatch at `npm run dev` startup (nvm 22↔24) — dev-only, no-op on the healthy path, unrelated errors not swallowed. ([#3301](https://github.com/diegosouzapw/OmniRoute/pull/3301) — thanks @zhiru) +- **fix(api):** remove the bundled **Completions.me** provider preset — empirically verified to return Rick Astley lyrics instead of real completions for every model/prompt. ([#3302](https://github.com/diegosouzapw/OmniRoute/pull/3302), discussion #3293 — thanks @diegosouzapw; reported by @mikmaneggahommie) +- **fix(ci):** skip the auto-deploy step when the VPS SSH port is unreachable from the GitHub runner (private LAN / firewall) instead of red-failing every release pipeline; genuine deploy/boot failures still fail honestly. ([#3299](https://github.com/diegosouzapw/OmniRoute/pull/3299) — thanks @diegosouzapw) +- **fix(sse):** strip leaked internal tool-call envelopes (`to=functions.*` / `multi_tool_use.parallel { … }`) from visible assistant text and sanitize Responses-API streaming (drop `commentary`-phase output items) so harness syntax never reaches the client. ([#3311](https://github.com/diegosouzapw/OmniRoute/pull/3311) — thanks @zhiru) +- **fix(sse):** expose the Claude (`claude-opus-4-6-thinking`, `claude-sonnet-4-6`) and Gemini budget tiers (`gemini-3.1-pro-{high,low}`, `gemini-3.5-flash-{low,extra-low}`) in the Antigravity catalog — they are user-callable on the Antigravity OAuth backend (agy parity), correcting an earlier assumption that Claude had been removed. ([#3303](https://github.com/diegosouzapw/OmniRoute/pull/3303), discussion #3184 — thanks @diegosouzapw) +- **fix(catalog):** compute a combo's `context_length` from the known targets only — a single target with unknown context no longer collapses the whole combo to `undefined`; also accepts live `{id, contextLength}` model entries in the opencode-provider helper (follow-up to #3298). ([#3304](https://github.com/diegosouzapw/OmniRoute/pull/3304) — thanks @herjarsa / @diegosouzapw) + +### 📝 維護 + +- **test(catalog):** align the Antigravity preview-alias catalog test with the #3303 budget tiers — asserts the restored Claude/Gemini tiers are surfaced, locking in the behavior so a future tier change can't silently drop them again (thanks @diegosouzapw) +- **docs:** rename the `resolve-issues` skill references to `review-issues` across the docs/skill surfaces, matching the renamed governance skill (thanks @diegosouzapw) +- **docs:** document the VS Code / Ollama endpoints (API reference + new `docs/reference/CLI-TOOLS.md`) and improve the env-bootstrap + i18n key-coverage tooling. ([#3319](https://github.com/diegosouzapw/OmniRoute/pull/3319) — thanks @zhiru) +- **chore(release):** open the v3.8.13 development cycle (version bump + cycle bookkeeping) and finalize this changelog (thanks @diegosouzapw) + +### 🙌 貢獻者 + +Thanks to everyone whose work landed in v3.8.13: + +| Contributor | PRs / Issues | +| -------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| [@zhiru](https://github.com/zhiru) | #3300, #3306, #3307 / #3310, #3309, #3301, #3311, #3320, #3319, #3316 | +| [@tycronk20](https://github.com/tycronk20) | #3317, #3318 | +| [@Vinayrnani](https://github.com/Vinayrnani) | #3267 | +| [@oyi77](https://github.com/oyi77) | #3292 (closes #3070), #3322 | +| [@onizukashonan14-png](https://github.com/onizukashonan14-png) | #3296 | +| [@uniQta](https://github.com/uniQta) | #3290, #3295 | +| [@wilsonicdev](https://github.com/wilsonicdev) | #3297 | +| [@herjarsa](https://github.com/herjarsa) | #3298, #3304 | +| [@mikmaneggahommie](https://github.com/mikmaneggahommie) | reported the Completions.me rickroll (discussion #3293) | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer — #3299, #3302, #3303; co-author on #3292 / #3306 / #3298 / #3304 / #3309 | --- -## [3.8.12] — Unreleased +## [3.8.12] — 2026-06-06 -_Development cycle in progress._ +### ✨ 新功能 + +- **chipotle:** add Chipotle Pepper AI — a free provider implemented via the reverse-engineered Amelia protocol, with its executor error body routed through `sanitizeErrorMessage()` (Hard Rule #12) ([#3250](https://github.com/diegosouzapw/OmniRoute/pull/3250) — thanks @oyi77) +- **web-cookie:** add tool-call translation to 8 web-cookie executors via shared `webTools` helpers, so cookie-backed providers can participate in tool/function calling through a single serialize/parse path ([#3259](https://github.com/diegosouzapw/OmniRoute/pull/3259) — thanks @oyi77) +- **free-tiers:** per-model free-token budget catalog + a Monthly Budget dashboard card surfacing each provider's monthly free allowance (joins the honest free-token catalog/API/headline work from #3257) ([#3263](https://github.com/diegosouzapw/OmniRoute/pull/3263), [#3257](https://github.com/diegosouzapw/OmniRoute/pull/3257) — thanks @diegosouzapw) +- **dashboard:** bulk activate / deactivate / retest for selected provider connections — multi-select with batch lifecycle actions on the providers page ([#3271](https://github.com/diegosouzapw/OmniRoute/pull/3271) — thanks @leninejunior) +- **models:** register MiniMax-M3 (frontier coding/agentic model, 1M context, Anthropic-compatible) across 8 provider tiers — `minimax`, `minimax-cn`, `opencode` (free), `opencode-go`, `opencode-zen`, `trae`, `ollama-cloud`, `nvidia` ([#3287](https://github.com/diegosouzapw/OmniRoute/pull/3287), #3110 — thanks @wilsonicdev) + +### 🔧 錯誤修正 + +- **api/responses:** combo names without a slash (e.g. `paid-premium`, `n8n-text`) are no longer force-rewritten to `codex/` on `/v1/responses` — `resolveResponsesApiModel` now returns the request unchanged when the model resolves to a combo (regression from the v3.8.9 Codex WS→HTTP fallback) ([#3268](https://github.com/diegosouzapw/OmniRoute/pull/3268), fixes #3227 / #3233 — thanks @wilsonicdev; supersedes the earlier closed #3242) +- **sse:** strip **every** `` tag before forwarding to the provider, not just the first — a global-regex variant prevents stray routing tags from leaking into the upstream prompt ([#3248](https://github.com/diegosouzapw/OmniRoute/pull/3248), fixes #454 — thanks @MikeTuev) +- **grok-web:** add TLS fingerprint impersonation to bypass Cloudflare anti-bot on the Grok web endpoint, with the executor's error bodies routed through `sanitizeErrorMessage()` (Hard Rule #12) ([#3249](https://github.com/diegosouzapw/OmniRoute/pull/3249), fixes #3180 — thanks @wilsonicdev) +- **providers:** improve provider refresh/validation and the model-catalog UI — including the OpenRouter catalog and the proxy UI, plus the NVIDIA NIM `/models`-suffix probe path (real-VPS validated) ([#3261](https://github.com/diegosouzapw/OmniRoute/pull/3261) — thanks @strangersp) +- **embeddings:** block cross-dimension failover inside embedding combos so a fallback target with a different vector dimension can no longer corrupt results ([#3256](https://github.com/diegosouzapw/OmniRoute/pull/3256) — thanks @diegosouzapw) +- **sse/web-tools:** web-cookie providers (e.g. `ds-web`/`deepseek-v4-pro`) that wrap tool calls as `{json}` are now parsed correctly — the real tool name is read from the JSON body instead of the tag attribute, and the call is no longer silently dropped when `arguments` is absent ([#3275](https://github.com/diegosouzapw/OmniRoute/pull/3275), fixes #3260 — thanks @diegosouzapw) +- **sse/groq:** non-reasoning Groq models (`llama-3.3-70b-versatile`, `llama-4-scout`) are now flagged `supportsReasoning: false`, so `reasoning_effort` / `output_config.effort` / `thinking` are stripped before dispatch instead of being forwarded and rejected with HTTP 400 — fixes the Claude Code → Groq regression of #764 ([#3277](https://github.com/diegosouzapw/OmniRoute/pull/3277), fixes #3258 — thanks @diegosouzapw) +- **api/images:** `POST /v1/images/edits` to a custom OpenAI-compatible provider no longer forwards an empty `model`. The multipart body is now built as a `Buffer` with an explicit boundary instead of a global `FormData` — the patched undici `fetch` serialized a native `FormData` as the literal string `[object FormData]` (text/plain), dropping every field including `model` ([#3278](https://github.com/diegosouzapw/OmniRoute/pull/3278), fixes #3273 — thanks @diegosouzapw) +- **db:** detect SQLite driver-unavailable errors to avoid a destructive DB rename + an optional FTS5 migration guard, so a transient driver-load failure no longer triggers the backup-and-recreate path on a healthy database (split from #3073) ([#3274](https://github.com/diegosouzapw/OmniRoute/pull/3274) — thanks @zhiru) +- **quota:** repair the Quota Sharing Engine — `poolUsageWithDimensions()` promoted onto the `QuotaStore` interface (kills the dynamic type-narrowing hack), single-snapshot burn rate via `computeBurnRateFromWindow()` (the dashboard previously always showed 0), zero-weight allocations normalized to equal distribution, Anthropic `anthropic-ratelimit-*` saturation signals, a `quota.exceeded` webhook fired on block, and quota enforcement extended to the embeddings handler ([#3280](https://github.com/diegosouzapw/OmniRoute/pull/3280) — thanks @oyi77) +- **plugins:** `emitHookBlocking` now chains the payload between handlers — each blocking handler receives the body/metadata as mutated by previous handlers, so a later plugin can observe an earlier plugin's changes (previously every handler got the original static payload) ([#3286](https://github.com/diegosouzapw/OmniRoute/pull/3286) — thanks @oyi77) +- **api/webhooks:** webhook URLs may now target a private/internal address (e.g. `192.168.x`, a docker-internal host) when `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` — the webhook guard reuses the same explicit opt-in as private provider URLs (default OFF; protocol and embedded-credential checks stay unconditional). Cloud-metadata / link-local endpoints (`169.254.169.254`, `metadata.google.internal`, `100.100.100.200`, `169.254.0.0/16`) are blocked **unconditionally** even with the opt-in on, and the webhook test endpoint redacts the upstream response body for private targets (no SSRF→IAM-credential pivot, no content exfiltration) ([#3279](https://github.com/diegosouzapw/OmniRoute/pull/3279), [#3281](https://github.com/diegosouzapw/OmniRoute/pull/3281), fixes #3269 — thanks @diegosouzapw) +- **sse/qoder:** a valid Qoder Personal Access Token is no longer wrongly reported as "expired" when the Cosy validation endpoint returns a generic `Internal Server Error` (HTTP 500). A Cosy 500 only marks the PAT invalid when its body carries an explicit auth signal; a generic server fault now falls back to the #1391 valid-bypass rule ([#3283](https://github.com/diegosouzapw/OmniRoute/pull/3283), fixes #3247 — thanks @wilsonicdev, who independently diagnosed the same root cause and filed [#3282](https://github.com/diegosouzapw/OmniRoute/pull/3282); refined here to keep rejecting on an explicit-auth-signal 500) + +### 📝 維護 + +- **ci:** `deploy-vps` recreates the PM2 process via the `omniroute` bin (instead of a bare `pm2 restart` pinned to the removed `app/server-ws.mjs` path) and gates the deploy on `/api/monitoring/health` reporting `"status":"healthy"`, failing the job (with recent PM2 logs) when the box never becomes healthy — supersedes #3262 ([#3270](https://github.com/diegosouzapw/OmniRoute/pull/3270) — thanks @diegosouzapw) +- **security:** harden the Chipotle executor against CodeQL findings — `Math.random()` → `crypto.randomInt()`/`crypto.randomUUID()` (imported from `node:crypto`) for session/server IDs, and a strict `new URL().hostname` check (replacing a substring match) in its test ([#3285](https://github.com/diegosouzapw/OmniRoute/pull/3285) — thanks @oyi77) +- **governance:** raise the coverage gate from 40% to 60% (statements/lines/functions/branches) now that real coverage sits at ~80% — brings the threshold in line with Hard Rule #9 (thanks @diegosouzapw) +- **docs:** consolidate the community links (Discord + Telegram + WhatsApp) at the top of the README and promote the Free-Token Budget section ([#3289](https://github.com/diegosouzapw/OmniRoute/pull/3289) — thanks @diegosouzapw) +- **docs:** richer free-tier budget-card image (28 models + first-month strip) and softer ToS framing (caution rather than warning) ([#3284](https://github.com/diegosouzapw/OmniRoute/pull/3284) — thanks @diegosouzapw) + +### 🙌 貢獻者 + +Thanks to everyone whose work landed in v3.8.12: + +| Contributor | PRs / Issues | +| ------------------------------------------------ | --------------------------------------------------------------------------------- | +| [@oyi77](https://github.com/oyi77) | #3250, #3259, #3280, #3285, #3286 | +| [@wilsonicdev](https://github.com/wilsonicdev) | #3249, #3268, #3282 / #3283 (co-author, #3247 diagnosis), #3287 | +| [@strangersp](https://github.com/strangersp) | #3261 | +| [@MikeTuev](https://github.com/MikeTuev) | #3248 | +| [@leninejunior](https://github.com/leninejunior) | #3271 | +| [@zhiru](https://github.com/zhiru) | #3274 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer — #3256, #3263, #3270, #3275, #3277, #3278, #3279, #3281, #3284, #3289 | --- -## [3.8.11] — Unreleased +## [3.8.11] — 2026-06-05 -_Development cycle in progress._ +### ✨ 新功能 + +- **theoldllm:** add The Old LLM — a free, Playwright-backed provider with dual-mode operation (cached browser token + direct fetch) bridged through a Vercel relay (#3217 — thanks @oyi77) +- **codex:** add Codex login via OpenAI's browser-driven device authorization flow, exposed as a shareable "Adicionar Externo" public link (`/connect/codex/{token}`) so a third party can complete the OpenAI device login without dashboard access (#3195 — thanks @zhiru) +- **proxy:** per-connection proxy distribution — `proxy_enabled` DB schema + Zod-validated resolution backend, automatic proxy-fallback selection when provider validation hits a network error, and a dashboard UI with per-connection toggles and a tag-filtered "Distribute Proxies" button (#3170, #3171, #3172 — thanks @pizzav-xyz) +- **api:** `/v1/images/generations` and `/v1/images/edits` now resolve a bare combo/alias model name (e.g. `image`) to its single image target, and `/v1/images/edits` forwards multipart edits to custom OpenAI-compatible providers' `{base_url}/images/edits` (also accepting JSON/data-URL edit input) instead of rejecting everything but chatgpt-web (#3214, #3215 — thanks @ngocquynh85) + +### 🔧 錯誤修正 + +- **api:** combo names sent to `/v1/responses` are no longer force-rewritten to `codex/` — the Codex CLI WS→HTTP fallback rewrite now skips bare names that are combos, so combos (e.g. `n8n-text`, `paid-premium`) route correctly again instead of failing with "No credentials for provider: codex" (regression since v3.8.9) (#3227, #3233 — thanks @Marcus1Pierce, @Dima-Kal) +- **antigravity:** the `agy` `gemini-3.1-pro-high`/`-low` models now alias to the plain `gemini-3.1-pro` upstream id (the `-high`/`-low` suffix is rejected for gemini-3.x), and non-streaming upstream 4xx/5xx errors surface as real error bodies instead of being masked as an empty `chat.completion` envelope (#3229) +- **auth:** honor the effective `REQUIRE_API_KEY` feature flag (DB override > env > default) in client API auth instead of reading `process.env` directly, and align the route-local optional-auth checks (`/v1/embeddings`, `/v1/web/fetch`, `/v1/combos`, playground) with it (#3188 — thanks @xz-dev) +- **oauth:** use `api.anthropic.com` for the Claude OAuth token exchange so self-hosted VPS deployments are no longer blocked by Cloudflare Bot Management on `console.anthropic.com` (#3203, fixes #3192 — thanks @wilsonicdev; the same root cause was independently diagnosed by @ibanunmangun in [#3193](https://github.com/diegosouzapw/OmniRoute/pull/3193), credited here as co-author) +- **oauth:** validate OAuth client IDs against `resolvePublicCred` so adding an Antigravity / Gemini CLI / AGY connection with the built-in public client no longer fails with a Google `redirect_uri_mismatch` (#3206 — thanks @juandisay) +- **auto-combo:** include zero-config OpenCode Free in `auto/*` virtual combos even with no `provider_connections` row, reusing the synthetic `noauth` connection id and routing through the `oc/` prefix (#3189, fixes #3155 — thanks @wilsonicdev) +- **sse:** refine Kimi thinking-block handling and add regression tests for assistant tool-call replay (#3191 — thanks @bypanghu) +- **openrouter:** report the true upstream `context_length` for passthrough models instead of the 128K default — `normalizeDiscoveredModels` now reads `context_length`/`top_provider.context_length` (and `max_completion_tokens` for output) when `inputTokenLimit` is absent (#3202 — thanks @pulyankote) +- **images:** custom image-generation providers now use the provider node's base URL (`providerSpecificData.baseUrl`) and resolve the `prefix/model` form, instead of silently falling back to the Gemini endpoint (#3205 — thanks @ngocquynh85) +- **docker:** the container healthcheck now probes `127.0.0.1`/`localhost`/`::1` and prints the failure to stderr instead of swallowing it, fixing false "unhealthy" status when the server binds to a non-loopback address (#3151 — thanks @naimo84) +- **docker:** copy `scripts/dev/healthcheck.mjs` into the runner-base image — the Next.js standalone output doesn't trace it, so the `HEALTHCHECK CMD ["node", "healthcheck.mjs"]` probe silently exited 1 (#3201 — thanks @wilsonicdev) +- **llama-cpp:** fall back to the provider's local default base URL (`127.0.0.1:8080/v1`) when a local connection has no base URL set, instead of silently routing to OpenAI (residual of #3136) (#3197 — thanks @tjengbudi) +- **provider-models:** allow deleting synced/fetched models (e.g. llama-cpp) via `DELETE /api/provider-models` — the handler now clears the `syncedAvailableModels` namespace, not just `customModels` (#3204, fixes #3199 — thanks @wilsonicdev); and a deleted synced model now stays deleted across an auto-fetch re-import (the DELETE marks it hidden and the re-import skips hidden ids) (#3199 — thanks @tjengbudi) +- **db/electron:** fix `Cannot find module 'better-sqlite3'` crash when importing a database backup in the packaged Electron app (Windows installer) — the `db-backups/import` route now opens its integrity-check DB through the resilient driver factory (better-sqlite3 → node:sqlite → sql.js) instead of a static native import that is stripped from the standalone server bundle; a guard test prevents any API route from reintroducing a direct native import (#3025 — thanks @yeardie) +- **dashboard:** the home provider-topology graph now shows the friendly provider name instead of the internal UUID for custom providers — the label precedence let `getProviderConfig`'s `{ name: providerId }` fallback shadow the pre-resolved name (#3198 — thanks @tjengbudi) +- **providers:** NVIDIA key validation now probes the universally-available `meta/llama-3.1-8b-instruct` instead of the catalog's first model (`z-ai/glm-5.1`), which requires the "Public API Endpoints" account permission and could hang/be DEGRADED — making a valid key fail with a misleading "Upstream Error" (#3116 — thanks @miracuves) +- **providers:** NVIDIA NIM key validation no longer times out (504) — the probe bypasses the global undici `fetch` proxy patch (`open-sse/utils/proxyFetch.ts`) that is incompatible with NVIDIA's endpoint and made the request hang silently (#3226 — thanks @miracuves) +- **dashboard:** corrected two misleading provider credential hints — Grok Web now states both `sso` and `sso-rw` cookies are required (was just `sso`), and the Vertex AI Service Account field shows real instructional placeholder text instead of an untranslated stub across 40 locales (#3180, #3091 — thanks @YoursSweetDom, @Guru01100101) +- **i18n:** normalize dotted `compliance.eventTypes` keys into nested objects at load time so next-intl no longer throws `INVALID_KEY: Namespace keys cannot contain "."` (the same PR also corrects the Codex import-auth provider hint) ([#3185](https://github.com/diegosouzapw/OmniRoute/pull/3185) — thanks @zhiru; the same i18n bug was independently fixed by @androw in [#3167](https://github.com/diegosouzapw/OmniRoute/pull/3167), credited here as co-author) +- **usage:** route the `agy` provider's quota through the existing Antigravity usage implementation (register `agy` in `USAGE_FETCHER_PROVIDERS`, all four `getUsageForProvider` call sites + `parseQuotaData` + `syncAntigravitySubscriptionIfNeeded`) so it no longer falls through to "Usage API not implemented" (#3232, fixes #3230 — thanks @wilsonicdev) +- **cli:** show OpenCode Free in the Hermes Agent model picker even with no active connection — new optional `alwaysIncludeProviders` prop on `ModelSelectModal` (defaults to `[]`, so other callers are unaffected) lets zero-config providers like `opencode` surface in the grouped list (#3240 — thanks @wilsonicdev) +- **gemini:** refresh the Gemini (AI Studio) static fallback so the provider tab exposes current 3.x / 2.5 models on first run, preserving the `gemini-2.0-flash` default ordering; the full catalog still comes from API sync once a key is added (#3241, fixes #3231 — thanks @wilsonicdev) + +### 📝 維護 + +- **build:** finish the build-output-isolation cleanup — `assembleStandalone.mjs` now derives both its async (`syncStandalone*`) and sync copy paths from a single `NATIVE_ASSET_ENTRIES`/`EXTRA_MODULE_ENTRIES` source of truth (previously two hand-maintained lists that could silently drift), guarded by a new parity test; and the `Dockerfile` drops 5 redundant per-module `COPY` overrides (`@swc/helpers`, `pino-abstract-transport`, `pino-pretty`, `split2`, `migrations`) now that `assembleStandalone` bundles them into the standalone regardless of NFT/Turbopack tracing (validated with a real Turbopack `docker build` + boot → `/api/monitoring/health` 200; `better-sqlite3` stays explicit since only its native `build/` is synced) (#3187 — thanks @diegosouzapw) +- **combo:** add a regression guard asserting the same-provider cascade is short-circuited by the connection-cooldown layer (#3200 — thanks @diegosouzapw) +- **repo:** housekeeping — ignore the generated `coverage/` output dir and prune deprecated `.agents/skills/*` SKILL definitions superseded by the current workflow skills (thanks @diegosouzapw) + +### 🙌 貢獻者 + +Thanks to everyone whose work landed in v3.8.11: + +| Contributor | PRs / Issues | +| -------------------------------------------------- | ----------------------------------------------- | +| [@wilsonicdev](https://github.com/wilsonicdev) | #3189, #3201, #3203, #3204, #3232, #3240, #3241 | +| [@pizzav-xyz](https://github.com/pizzav-xyz) | #3170, #3171, #3172 | +| [@zhiru](https://github.com/zhiru) | #3185, #3195 | +| [@oyi77](https://github.com/oyi77) | #3217 | +| [@miracuves](https://github.com/miracuves) | #3116, #3226 | +| [@ngocquynh85](https://github.com/ngocquynh85) | #3205, #3214, #3215 | +| [@xz-dev](https://github.com/xz-dev) | #3188 | +| [@bypanghu](https://github.com/bypanghu) | #3191 | +| [@juandisay](https://github.com/juandisay) | #3206 | +| [@tjengbudi](https://github.com/tjengbudi) | #3197, #3198, #3199 | +| [@naimo84](https://github.com/naimo84) | #3151 | +| [@yeardie](https://github.com/yeardie) | #3025 | +| [@pulyankote](https://github.com/pulyankote) | #3202 | +| [@YoursSweetDom](https://github.com/YoursSweetDom) | #3180 | +| [@Guru01100101](https://github.com/Guru01100101) | #3091 | +| [@androw](https://github.com/androw) | #3167 (co-author) | +| [@ibanunmangun](https://github.com/ibanunmangun) | #3193 (co-author) | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer — #3187, #3200, issue-fix batches | --- -## [3.8.10] — Unreleased +## [3.8.10] — 2026-06-04 + +OAuth resilience & observability release: spaced/sequential quota sync for OAuth accounts, a per-provider proactive-refresh skip list to keep short-TTL providers (Kimi) alive without re-exposing the Codex Auth0 cascade, token-expiry visibility on the provider cards, a new provider-stats dashboard, plus a wide batch of provider fixes (DeepSeek-web tool calls, Antigravity, Qoder, MiniMax, GitHub Copilot, Fireworks, llama.cpp, t3.chat-web, Kiro, Kilocode) and Podman deployment support. + +### ✨ 新功能 + +- **dashboard:** new Provider Stats page + `/api/provider-stats` endpoint — per-provider and per-model aggregates from `call_logs` plus live combo/telemetry/tool-latency overlays. (#3175 — thanks @pizzav-xyz / @diegosouzapw) +- **metrics:** cross-request TTFT and gap-after-tool-call latency tracking, aggregated per provider. (#3173 — thanks @pizzav-xyz / @diegosouzapw) +- **quota:** show the OAuth token expiry on provider cards (small, blue, informative — "Token expires in …" / "Token expired"). (#3178 — thanks @diegosouzapw) +- **responses:** strip `previous_response_id` for stateless Responses upstreams, with an auto/strip/preserve setting + UI so stateless clients (e.g. VS Code Custom Endpoint) keep context. (#3143 — thanks @JxnLexn) +- **deploy:** Podman/rootless deployment support (contrib units + `CONTAINER_HOST` hint) and larger upload body-size limits for `/v1/files`. (#3128 — thanks @hartmark) + +### 🔧 錯誤修正 + +- **usage:** sequential + spaced OAuth quota sync (`PROVIDER_LIMITS_SYNC_SPACING_MS`) so a host no longer bursts simultaneous usage/refresh requests; reactive forced re-mint after a 401 on the per-card refresh (recovers imported accounts); a genuine 401 now surfaces a re-authenticate hint. (#3156 — thanks @diegosouzapw) +- **healthcheck:** per-provider proactive-refresh skip list (`OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS`) — keep rotating-cascade providers (Codex/OpenAI) reactive-only while short-TTL providers (Kimi-coding) keep refreshing proactively. (#3159 — thanks @diegosouzapw) +- **providers:** on `?refresh=true` with no remote models, don't resurface the just-cleared synced cache into the local-catalog fallback. (#3181 — thanks @diegosouzapw) +- **providers:** use synced models as the authoritative local catalog across all providers (even on connections that didn't run the sync). (#3148 — thanks @herjarsa) +- **web-tools:** parse bare-JSON tool calls for DeepSeek-web with fuzzy tool-name matching scoped to the requested tools. (#3157 — thanks @wilsonicdev) +- **responses:** normalize `image_url` parts across every Responses input path (message content, replayed output items, `function_call_output`) to avoid upstream 400s. (#3150 — thanks @wilsonicdev) +- **antigravity:** dynamic upstream model resolution via the MITM alias table (server-only executor), with a guard against corrupted alias values. (#3144 — thanks @herjarsa) +- **qoder:** bifurcate validation by token type — PAT (`pt-`) → Cosy, regular API key → dashscope — matching the executor's routing. (#3149 — thanks @herjarsa) +- **api-manager:** preserve API key expiration in local time (the `datetime-local` input no longer silently shifts to UTC) + a clear button. (#3146 — thanks @xz-dev) +- **opencode-plugin:** map `caps.thinking → ModelV2.capabilities.interleaved` for single models and combos. (#3138 — thanks @mrmm) +- **kiro:** optional `targetProvider` on the social-OAuth exchange so Kiro-based providers can reuse the social login flow. (#3176 — thanks @pizzav-xyz) +- **misc:** broaden the DeepSeek reasoning-replay regex (`-free` / `zen/deepseek-v4`), export `ProviderProfile`, and guard a non-string directory entry in the binary manager. (#3177 — thanks @pizzav-xyz) +- **providerRegistry:** point kilocode at the OpenAI format + default executor (matching its sibling `kilo-gateway`). (#3166 — thanks @androw) +- **fireworks:** preserve fully-qualified router/model IDs so Fire Pass router IDs (`accounts/fireworks/routers/...`) are no longer double-prefixed into an upstream 404. (#3133 — thanks @KooshaPari) +- **llama-cpp:** route requests to the configured local baseUrl instead of OpenAI's API (which returned an OpenAI-worded 401). (#3136 — thanks @tjengbudi) +- **t3-chat-web:** parse cookies + convexSessionId from the single stored credential so t3.chat web connections work (the executor previously read fields the credential pipeline never produced). (#3007 — thanks @minhtran162) +- **minimax:** stop capping MiniMax-M3 / MiniMax-M2.7 `max_tokens` at the 8192 default — add the M3 model spec (512K output) and make model-spec lookups case-insensitive. (#3141 — thanks @totaltube) +- **github-copilot:** discover the model catalog live from `api.githubcopilot.com/models` so Import Models refreshes and only entitled models are listed (with fallback to the static catalog). (#3120, #3121 — thanks @gabrielmoreira) +- **combo:** invalidate the nested-combo cache on combo edits so removed targets/models stop being served within the 10s window; log the resolved DATA_DIR at startup to diagnose multi-replica volume mismatches. (#3147 — thanks @ViFigueiredo) +- **providers:** resolve web-provider alias collisions. (thanks @diegosouzapw) + +### 📝 維護 + +- **deps:** bump hono from 4.12.18 to 4.12.23. (#3179 — thanks @dependabot) +- **ci(electron):** make the macOS-arm64 smoke step best-effort (headless GPU crash). (#3137 — thanks @diegosouzapw) +- **chore(release):** open the v3.8.10 development cycle. (thanks @diegosouzapw) --- -## [3.8.9] — Unreleased +## [3.8.9] — 2026-06-03 + +### ✨ 新功能 + +- **Obsidian context source — 24 MCP tools** (`read:obsidian` / `write:obsidian`) — search, read, write, and bidirectional sync against a local Obsidian vault via the [Local REST API community plugin](https://github.com/obsidianmd/obsidian-local-rest-api). Dashboard "Context Sources" tab, settings API, DB config. (#3077 — thanks @branben) +- **cursor:** vision (`image_url`) input for the Cursor provider — OpenAI image parts are encoded as `SelectedContext.selected_images[]` in the `agent.v1` protobuf, plus a tool-commit directive (lifts composer-2.5's tool-call rate), `tool_choice` none/required/specific handling, and `response_format`/`max_tokens`/`stop` output constraints surfaced to the agent. Hardened with SSRF + DNS-rebinding guards, a 1 MiB pre-decode cap, and a protobuf length-overrun check. (#3104 — thanks @payne0420) +- **deepseek-web:** opt-in persistent session + rolling-window conversation memory (`persistSession`, `historyWindow` per-connection settings) and bidirectional tool-call translation — tool schemas are injected as a system prompt and `{…}` blocks in the reply are parsed back into OpenAI `tool_calls` (replacing the old hard `400`). ([#2942](https://github.com/diegosouzapw/OmniRoute/issues/2942), [#2820](https://github.com/diegosouzapw/OmniRoute/issues/2820)) +- **i18n:** Turkish locale-aware search & sorting — a `turkishText` helper (`normalizeForSearch`, `matchesSearch`, `compareTr`) folds the dotted/dotless İ/ı correctly and uses `Intl.Collator("tr")`, wired across dashboard search/sort call-sites with an ESLint guard (warn) against raw `toLowerCase().includes()`. (#3115 — thanks @osrt91) +- **kiro:** add Claude Opus 4.8 to the Kiro (AWS CodeWhisperer) model catalog — Kiro previously topped out at Opus 4.7 even though Opus 4.8 was already defined and served by the `claude` provider. (#3131 — thanks @artickc) + +### 🔧 錯誤修正 + +- **sse:** stop 502'ing streaming requests when a "reasoning" openai-compatible upstream ignores `stream:true` and returns a complete `application/json` body — the streaming readiness check only recognized SSE `data:` frames, so such a JSON body (even with valid `content`/`reasoning_content`) produced a spurious `STREAM_EARLY_EOF`. OmniRoute now detects a non-SSE JSON upstream body on the streaming path and synthesizes an equivalent OpenAI SSE stream (`synthesizeOpenAiSseFromJson`), preserving content + reasoning_content. ([#3089](https://github.com/diegosouzapw/OmniRoute/issues/3089)) +- **cache:** serve semantic-cache hits as SSE for streaming clients — a cache hit returned `application/json` regardless of the `stream` flag, so OpenAI-compatible streaming clients lost `reasoning_content` (and got a non-stream body) on cached responses. Stream requests now SSE-wrap the cached completion. ([#2952](https://github.com/diegosouzapw/OmniRoute/issues/2952)) +- **i18n:** fill the missing Chinese (zh-CN) and Russian (ru) UI translations — both locales were missing 9 entire sections (`quotaPlans`, `activity`, `agentBridge`, `trafficInspector`, `cliCommon`, `cliCode`, `cliAgents`, `acpAgents`, `agentSkills`, ~823 keys each) added after the last translation sweep, so those buttons/labels rendered in English. Both catalogs are now at full key parity with `en.json` (8025 keys). ([#3026](https://github.com/diegosouzapw/OmniRoute/issues/3026), [#3067](https://github.com/diegosouzapw/OmniRoute/issues/3067)) +- **dashboard:** fix "Ambiguous model" error in the provider Playground for vendor-namespaced models — the Playground only prefixed models without a `/`, so ids like `moonshotai/kimi-k2.6` or `nvidia/zyphra/zamba2-7b-instruct` (NVIDIA NIM) were sent bare and rejected when the same id exists under multiple providers. The Playground now always qualifies the selected model with its `providerId/` prefix (without double-prefixing). ([#3050](https://github.com/diegosouzapw/OmniRoute/issues/3050)) +- **db:** stop accepting duplicate API keys for the same provider — `createProviderConnection` now dedups by the decrypted key value (not just by name), so re-adding the same key under a different/blank name updates the existing connection instead of inserting a second row. Whitespace-only differences also dedup. ([#3023](https://github.com/diegosouzapw/OmniRoute/issues/3023)) +- **dashboard:** "Import from /models" now works for no-auth providers (e.g. OpenCode Free) — the button used to silently no-op because no-auth providers have no connection row, so `handleImportModels` returned early and the models route 404'd. The route now serves the provider's model catalog when called with a no-auth provider id, and the dashboard falls back to the provider id when there is no connection. ([#3047](https://github.com/diegosouzapw/OmniRoute/issues/3047)) +- **providers:** forward Grok's paired `sso-rw` cookie for grok-web — both the executor and the connection validator now send `sso=…; sso-rw=…` (via the new `buildGrokCookieHeader` helper) when the pasted blob carries `sso-rw`, fixing the `403` _"Request rejected by anti-bot rules"_ that Grok returns for `sso` alone. The add-account hint now asks for the full cookie line. ([#3063](https://github.com/diegosouzapw/OmniRoute/issues/3063)) +- **providers:** fix claude-web persistent 403 — `execute()` was calling the synchronous `normalizeClaudeSessionCookie()` which never injects `cf_clearance`; changed to async `normalizeClaudeSessionCookieWithAutoRefresh()` with `allowAutoSolve:true`. Also removes dead executor `claude-web-auto-refresh.ts` and correctly reclassifies `duckduckgo-web` and `veoaifree-web` as `NOAUTH_PROVIDERS`. (#3090 — thanks @oyi77) +- **autoCombo:** rotate across all provider connections, never waste capacity — `buildAutoCandidates` now expands each provider into one candidate per active connection (e.g. 43 Cerebras keys → 43 candidates). Adds `ScoreTierRotator` with per-combo round-robin state, combo-name-aware tier preferences (smart/fast/cheap/coding), `connectionDensity` factor (weight 0.05), and budget-cap degradation using the rotator. (#3078 — thanks @oyi77) +- **providers:** fix SiliconFlow model sync from configured endpoint — routes model discovery through `providerSpecificData.baseUrl` so CN (`api.siliconflow.cn`) vs Global endpoint selection is respected, and prevents `/sync-models` from treating `source: "local_catalog"` fallback responses as successful remote syncs. (#3094 — thanks @xz-dev) +- **resilience:** a per-model subscription/permission `403` from a passthrough provider (e.g. Ollama Cloud `deepseek-v4-pro` → _"this model requires a subscription"_) now locks out **only that model** instead of cooling down the whole connection — the free models on the same key keep serving, and repeated paid-model 403s no longer escalate a connection-wide backoff. Generalizes the grok-web 403 precedent to all `hasPerModelQuota` providers; terminal/credential 403s (banned/deactivated key) still deactivate the connection. ([#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027)) +- **cache:** preserve client-side `cache_control` breakpoints for Xiaomi MiMo — added `xiaomi-mimo` to the prompt-caching provider allowlist so Claude Code (via cc-switch) cache hints are no longer stripped by the OpenAI-format translator, restoring cache hits. ([#3088](https://github.com/diegosouzapw/OmniRoute/issues/3088)) +- **tools:** keep opaque object schemas open — empty object schemas (and the `web_search` passthrough shim) now get `additionalProperties: true` so GPT-5.5/Codex stop pruning untyped nested payloads (e.g. `SPLOX_EXECUTE_TOOL.args`). (#3097 — thanks @nmime) +- **codex:** preserve native Responses passthrough tools and history — `tool_search` and `custom` tools (e.g. `apply_patch`) survive `normalizeCodexTools`, and `phase:"commentary"` history items are kept, only on the native passthrough path (`_nativeCodexPassthrough`). (#3107 — thanks @yinaoxiong) +- **responses:** resolve bare ChatGPT model ids (e.g. `gpt-5.5`) to `codex/…` on the `/v1/responses` HTTP fallback path, fixing the Codex CLI WS→HTTP fallback that was routing to a credential-less provider (#3113). +- **sse:** bound the Antigravity 429 short-retry loop (per-URL `MAX_AUTO_RETRIES` guard — no more infinite loop on a persistent 429) and lock quota-exhausted accounts for the full "Resets in XhYmZs" window via model lockout. (#3122 — thanks @ahmet-cetinkaya) +- **image-gen:** add an AbortController timeout to `fetchImageEndpoint` so a stuck image provider surfaces a `504` instead of hanging until the server timeout. (#3105 — thanks @mgarmash) +- **logs (perf):** fix browser freeze and network saturation on `/dashboard/logs` — smaller page size, 15s polling, pause polling on a hidden tab / past the first page, and memoized derived lists. (#3109 — thanks @0xtbug) +- **cli:** handle Windows `.exe` healthchecks with spaces in the path — direct executables skip the shell (so `cmd.exe` doesn't split `C:\…\Name With Spaces\…\claude.exe`) while `.cmd`/`.bat` wrappers still run through it. (#3111 — thanks @EmpRider) +- **cli:** don't write `STORAGE_ENCRYPTION_KEY` to `.env` on informational commands — `omniroute --version`/`--help` no longer generate a key or create `~/.omniroute/.env`; provisioning is scoped to commands that actually touch encrypted storage (#3129). +- **tests:** remove a stale lowercase `db-apikeys-crud.test.ts` duplicate that collided with the canonical `db-apiKeys-crud.test.ts` on case-insensitive filesystems (no coverage lost). (#3125 — thanks @juandisay) +- **kimi:** add a dedicated `KimiExecutor` so Kimi thinking-mode responses no longer drop `reasoning_content` — the reasoning stream is now surfaced instead of being lost. (#3132 — thanks @bypanghu) +- **handler:** provide a `connectionId` fallback when it is undefined, fixing kilo (kilocode) calls that were silently not being written to `call_logs`. (#3130 — thanks @androw) + +### 🔧 Build + +- **build-output-isolation:** unified standalone assembly into one shared `assembleStandalone` module; isolated build output into `.build/` (intermediates, gitignored) and `dist/` (shippable bundle, gitignored), replacing the old repo-root `app/` and `.next/` directories; dropped the duplicate `next build` that prepublish previously ran; added `build:release` script for a clean rebuild with a `dist/BUILD_SHA` HEAD sentinel that guards against deploying stale bundles. **Operators using custom `app/` paths:** the published bundle directory on the VPS image (`/usr/lib/node_modules/omniroute/app/`) is unchanged — only the in-repo build output path moved. Update any local scripts that reference the repo-local `app/` build output to `dist/` instead. +- **build:** re-apply the build-reorg follow-ups that landed after the main refactor merged — the `serve` CLI now falls back from `dist/` to the legacy `app/` location for upgrade safety, and the deploy skills `pm2 stop` before `rsync --delete` to avoid a transient `Cannot find module ./chunks/…` race (#3127). +- **build:** fix the standalone static-asset path so the dashboard renders after the build-output reorg — `assembleStandalone` was copying `static/` into `/.next/static`, but the standalone server (built with `distDir=.build/next`) serves `/_next/static` from `/.build/next/static`, so every JS/CSS chunk 404'd and the login UI rendered as a blank page. The static (and `required-server-files.json` / Turbopack chunk) destinations are now derived from the configured `distDir` instead of a hard-coded `.next`. + +### 📦 Dependencies + +- **electron:** bump to 42.3.2 (crash fix desktopCapturer, Chromium 148.0.7778.218, ThinLTO perf) (#3083) +- **electron-updater:** bump to 6.8.8 (security: harden auto-update flow against path traversal and env var intercepts) (#3084) +- **electron-builder:** bump to 26.14.0 (security hardening, pure-JS blockmap/icon migration) (#3082) +- **dev deps:** bump eslint-config-next 16.2.7, lint-staged 17.0.7, typescript-eslint 8.60.1, vitest 4.1.8 (#3086) +- **prod deps:** bump next 16.2.7, react/react-dom 19.2.7, tsx 4.22.4, ws 8.21.0, parse5 8.0.1, commander 15.0.0, and 15 other packages (#3085) + +### 🙌 貢獻者 + +Huge thanks to everyone whose work shipped in v3.8.9: + +@branben (Obsidian context source), @oyi77 (claude-web 403 fix, autoCombo connection rotation), @xz-dev (SiliconFlow model sync), @nmime (open opaque tool schemas), @payne0420 (Cursor vision input), @mgarmash (image-gen fetch timeout), @yinaoxiong (Codex native passthrough tools/history), @0xtbug (logs page perf), @EmpRider (Windows CLI healthcheck paths), @ahmet-cetinkaya (Antigravity 429 retry bound + quota lockout), @juandisay (duplicate test cleanup), @osrt91 (Turkish locale-aware search & sorting), @artickc (Kiro Opus 4.8 catalog), @bypanghu (Kimi thinking-mode reasoning_content fix), and @androw (connectionId fallback + kilo call logging). + +And thank you to the OmniRoute community for the bug reports, reproductions, and testing that drove these fixes. 🎉 --- -## [3.8.8] — 2026-06-01 +## [3.8.8] — 2026-06-03 ### Added @@ -2270,7 +3848,7 @@ _Development cycle in progress._ `tests/e2e/traffic-inspector.spec.ts`, `tests/e2e/agent-bridge-traffic-cross.spec.ts` (skip-gated on CI by `RUN_AGENT_BRIDGE_E2E` / `RUN_TRAFFIC_INSPECTOR_E2E` / `RUN_CROSS_E2E`). - **Documentation** — `docs/frameworks/AGENTBRIDGE.md` and `docs/frameworks/TRAFFIC_INSPECTOR.md`; - `docs/architecture/REPOSITORY_MAP.md` updated; `docs/reference/openapi.yaml` updated with + `docs/architecture/REPOSITORY_MAP.md` updated; `docs/openapi.yaml` updated with ~28 new routes and 20+ new schemas. - **i18n:** translate Ukrainian (uk-UA) menu and UI strings, plus complete uk-UA UI coverage (#2981 / #2988 — thanks @Lion-killer) - **providers:** add SiliconFlow endpoint selector (#2975 — thanks @xz-dev) @@ -2278,7 +3856,11 @@ _Development cycle in progress._ - **providers:** add Qwen Web (chat.qwen.ai) web-cookie provider (#2947 — thanks @oyi77) - **Quota Share Engine — multi-provider quota pools** — Monitoring/Costs reorg plus a Quota Share Engine: group selector, grouped pool cards, exclusive-quota API keys (`allowedQuotas`), `quotaShared-*` routing models via combos, a 3-step pool wizard (legacy Plans page retired), endpoint + key preview, and full pool editing. Adds quota-pool DB migrations. (#2859 / #3022 / #3032 — thanks @diegosouzapw) - **Dashboard page redesigns (Nav Restructure)** — agent-skills + omni-skills with a dynamic 42-skill catalog and MCP/A2A discovery (#2827); CLI Code's + CLI Agents + ACP Agents pages (#2839); translator friendly redesign, 5 tabs → 2 (#2847); functional `/batch` + `/batch/files` redesign (#2849); Playground Studio + Search Tools Studio (#2869); memory engine redesign — sqlite-vec + hybrid RRF + Studio UI (#2873). (thanks @diegosouzapw) -- **notion:** add Notion as an MCP context source — 6 tools (`notion_search`, `notion_list_databases`, `notion_get_database`, `notion_query_database`, `notion_read`, `notion_append_blocks`) scoped under `read:notion` / `write:notion`, with dashboard "Context Sources" tab, settings API, and token persistence in `key_value` table (#2959) +- **notion:** add Notion as an MCP context source — 6 tools (`notion_search`, `notion_list_databases`, `notion_get_database`, `notion_query_database`, `notion_read`, `notion_append_blocks`) scoped under `read:notion` / `write:notion`, with dashboard "Context Sources" tab, settings API, and token persistence in `key_value` table (#2959 — thanks @branben) +- **Per-API-key stream default mode** — a per-key setting that forces JSON or SSE as the default response shape (migration `077_api_key_stream_default_mode`), so integrations that expect non-streaming JSON work without client changes. (thanks @JxnLexn) +- **Codex Responses-over-WebSocket** — opt-out flag `OMNIROUTE_CODEX_WS_ENABLED` (default ON) upgrading Codex Responses traffic to a WebSocket bridge with a clean handshake and bridge-secret auth; the Quota Share endpoints card now surfaces the Responses + codex-WS endpoints. (thanks @diegosouzapw) +- **Xiaomi MiMo usage tracking** — self-reported usage accounting for Xiaomi MiMo plus a monthly cap preset; DeepSeek USD preset and a Claude plan preset (percent 5h + weekly) seeded into the plan registry. (thanks @diegosouzapw) +- **API Manager: Normal vs Quota key sections** — the API keys screen now splits keys into Normal and Quota sections in a compact 2-table layout, and the Quota Share screen gains a beta banner, live per-account upstream quota, and a real-time Codex quota view backed by the cascade-safe serialized refresh. (thanks @diegosouzapw) ### Changed @@ -2292,6 +3874,27 @@ _Development cycle in progress._ ### Fixed +- **usage:** use xAI's exact provider-reported cost when present instead of always estimating from token counts. (thanks @ryanngit) +- **memory:** the `recent` retrieval strategy no longer drops recent memories whose + text doesn't overlap the current prompt. It was internally mapped to the `exact` + path, which relevance-filtered by the forwarded prompt (`score > 0`), so + recency-based injection silently returned nothing for unrelated prompts. The + prompt is no longer forwarded for `recent` (semantic/hybrid still use it for + vector search). +- **combo:** custom-provider credential lookup now expands `provider_nodes` prefixes + (e.g. `78code/gpt-5.4`) to the generated internal connection ids during account + selection, so combos targeting compatible/custom providers resolve their live + credentials instead of failing to find a connection. (#3058) +- **build:** Docker image build (`docker compose --profile cli build`, which runs + `next build` with Turbopack) no longer errors. Two Turbopack-only failures were + fixed: `sqlite-vec` is now externalized so Turbopack stops trying to bundle its + native `vec0.so` ("Unknown module type"), and `manager.stub.ts` now exports + `getAllAgentsStatus` (statically imported by `/api/tools/agent-bridge/state` — the + missing export aborted the build). The webpack-based VM build was unaffected, which + is why the deploy validated while the Docker build errored. The sqlite-vec native + binary is also now bundled into the standalone output, so vector/semantic memory keeps + working in the container instead of silently degrading to FTS5 keyword search. + (#3066 — thanks @freefrank) - **codex/providers:** `POST /api/providers/[id]/refresh` (the manual/auto "refresh token" endpoint) no longer rotates rotating-refresh providers (Codex/OpenAI share one Auth0 `client_id`). This was the last unguarded proactive-refresh entry point: @@ -2444,20 +4047,70 @@ must be a response to a preceding message with 'tool_calls'` when a Codex - **dashboard:** use a lightweight ping endpoint for the MaintenanceBanner (fixes #3040) (#3043 — thanks @herjarsa) - **test:** resolve pre-existing test failures — env sync, PII, quota, sidebar (#3039 — thanks @oyi77) - **docs/mcp:** regenerate the mcp-tools diagram for 43 tools and fix the tool count (#3028 — thanks @diegosouzapw) -- **mcp:** move `enforceScopes` guard before `MCP_TOOL_MAP` lookup, add inline `scopes` parameter to `withScopeEnforcement()`, and declare scopes on all 24 dynamic tool definitions (memory, skills, plugins, gamification, compression) to fix scope enforcement for dynamic MCP tool groups (#2958) +- **mcp:** move `enforceScopes` guard before `MCP_TOOL_MAP` lookup, add inline `scopes` parameter to `withScopeEnforcement()`, and declare scopes on all 24 dynamic tool definitions (memory, skills, plugins, gamification, compression) to fix scope enforcement for dynamic MCP tool groups (#2958 — thanks @branben) +- **sse/chatCore:** the heap-pressure guard now auto-calibrates its threshold to 85% + of the live V8 heap ceiling (floor 400 MB) instead of a fixed 200 MB that sat below + the app's ~260 MB baseline and returned `503 Service temporarily unavailable due to +resource pressure` for every request once the heap warmed up. It now tracks + `--max-old-space-size` across 1 GB / 2 GB / large VPS; `HEAP_PRESSURE_THRESHOLD_MB` + still overrides. (#3052) +- **proxy:** fail closed for OAuth usage-account proxies (#3051 — thanks @terence71-glitch) +- **proxy:** resolve registry proxy assignments for combo and key levels (#3048 — thanks @terence71-glitch) +- **providers/web:** wire the session pool for fingerprint rotation on Pollinations / DuckDuckGo (#3049 — thanks @oyi77) +- **providers/claude-web:** add `cf_clearance` cookie support and session-pool fingerprint rotation for Pollinations / DuckDuckGo (#3046 — thanks @oyi77) +- **usage:** handle MiniMax coding-plan percent quotas (`general`/percent dimension) so MiniMax coding plans report remaining quota correctly. (thanks @diegosouzapw) +- **home:** pass `providerId` to the quota widget icons so provider brand icons resolve on the home dashboard (#3064 — thanks @xz-dev) +- **quota:** block `qtSd/*` models for keys with no quota-pool allocation (enforcement Check 2.9), and never flag rotating-refresh providers (Codex/OpenAI) as expired during the quota sync (#3030). (thanks @diegosouzapw) + +### 🏆 Contributors + +A special thanks to everyone who contributed to this release — 746 commits since `v3.8.7`: + +| Contributor | PRs / Contribution | +| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer — AgentBridge, Traffic Inspector, Quota Share Engine, Nav Restructure, Plugins integration, releases & upstream ports | +| [@oyi77](https://github.com/oyi77) | #2913, #2947, #2954, #2978, #3015, #3018, #3039, #3041, #3045, #3046, #3049 | +| [@terence71-glitch](https://github.com/terence71-glitch) | #2956, #2960, #2963, #2984, #3000, #3006, #3012, #3048, #3051 | +| [@soyelmismo](https://github.com/soyelmismo) | #2951, #2965, #2973 | +| [@branben](https://github.com/branben) | #2958, #2959 | +| [@makcimbx](https://github.com/makcimbx) | #2937, #2938 | +| [@guanbear](https://github.com/guanbear) | #2931, #3031 | +| [@Lion-killer](https://github.com/Lion-killer) | #2981, #2988 | +| [@JxnLexn](https://github.com/JxnLexn) | per-API-key stream default mode | +| [@androw](https://github.com/androw) | #3017 | +| [@xz-dev](https://github.com/xz-dev) | #2975, #3064 | +| [@S0yora](https://github.com/S0yora) | #2964 | +| [@NekoMonci12](https://github.com/NekoMonci12) | #3008 | +| [@Tentoxa](https://github.com/Tentoxa) | #3010 | +| [@ReqX](https://github.com/ReqX) | #2957 | +| [@NomenAK](https://github.com/NomenAK) | #2943 | +| [@charithharshana](https://github.com/charithharshana) | #2940 | +| [@dhaern](https://github.com/dhaern) | #2927 | +| [@dangeReis](https://github.com/dangeReis) | #3021 | +| [@bobbyunknown](https://github.com/bobbyunknown) | #3029 | +| [@CitrusIce](https://github.com/CitrusIce) | #3035, #3058 | +| [@wussh](https://github.com/wussh) | #3036 | +| [@Chewji9875](https://github.com/Chewji9875) | #3037 | +| [@herjarsa](https://github.com/herjarsa) | #3043 | +| [@freefrank](https://github.com/freefrank) | #3066 (reported the Docker build failure) | + +A special thanks to everyone who contributed code, reviews, and tests for this release: +@androw, @bobbyunknown, @branben, @charithharshana, @Chewji9875, @CitrusIce, @dangeReis, @dhaern, @diegosouzapw, @freefrank, @guanbear, @herjarsa, @JxnLexn, @Lion-killer, @makcimbx, @NekoMonci12, @NomenAK, @oyi77, @ReqX, @S0yora, @soyelmismo, @Tentoxa, @terence71-glitch, @wussh, @xz-dev --- ## [3.8.7] — 2026-05-29 -### ✨ New Features +### ✨ 新功能 - **api (self-service):** add `GET /api/v1/me/status` so a delegated API key can view its own usage (USD used, budget percent, token totals) and optional shared Codex account quota, backed by migration `075_api_key_self_service_usage_scopes` (#2908 — thanks @guanbear). - **analytics:** roll up usage logs to `daily_usage_summary` before raw log cleanup, and query a SQL `UNION` of raw and rolled-up data to prevent analytics history data loss (#2904 — thanks @unitythemaker). - **perf (RAM):** reduce server memory footprint by capping 11 in-memory caches, limiting SQLite page cache, lazy-loading provider registries via Proxy, and optimizing Next.js startup database probes (#2903 — thanks @soyelmismo). -### 🔧 Bug Fixes +### 🔧 錯誤修正 +- **sse:** guard against numeric or non-string upstream error codes and malformed model strings to prevent runtime string-method crashes in `proxyFetch`, `parseModel`, and combo routing (#2463) +- **docker:** add dedicated `runner-web` Docker stage with Playwright + Chromium + system libs so web-cookie providers (Gemini Web, Claude Turnstile) work in container deployments without bloating the base image (#2832) - **token-accounting:** prefer `prompt_tokens` over compatibility `input_tokens` for Anthropic Claude streams to avoid double-counting cached tokens (#2904 — thanks @unitythemaker). - **agy:** add the **Antigravity CLI (`agy`)** as a standalone OAuth provider next to `gemini-cli`/`antigravity`. It reuses the antigravity inference backend (identical Google client, `daily-cloudcode-pa.googleapis.com`) but ships its own model catalog — notably the Claude models the backend exposes (`claude-opus-4-6-thinking`, `claude-sonnet-4-6`) — its own account pool, and connection methods: import the `agy` CLI token file (paste/upload), auto-detect a local CLI login (`~/.gemini/antigravity-cli/antigravity-oauth-token`), browser OAuth, and bulk/ZIP import. New routes: `POST /api/providers/agy-auth/{import,import-bulk,zip-extract,apply-local}`. @@ -2470,13 +4123,13 @@ must be a response to a preceding message with 'tool_calls'` when a Codex - **log-export:** `GET /api/logs/export?type=proxy-logs` returns raw SQLite rows whose IP field is still named `public_ip` (the historical column name). This differs from the `clientIp` field exposed by `GET /api/usage/proxy-logs`. The two endpoints are intentionally inconsistent for now and will be aligned in a future migration (#2880). -### ✨ New Features +### ✨ 新功能 - **usage:** add per-API-key token limits scoped to model/provider/global with two-tier inline enforcement and in-memory cache accelerator (#2888 — thanks @mugnimaestra). - **providers:** audit web cookie providers, fix 4 missing registry entries, and add DuckDuckGo AI Chat provider (#2862 — thanks @oyi77). - **compression:** expand pt-BR pack with 34 new rules inspired by the troglodita project (#2818 — thanks @leninejunior). -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **oauth:** hotfix Windsurf login — drop dead PKCE flow, promote import-token, and resolve SQLite bind type errors (#2884 — thanks @yunaamelia). - **models:** prune stale synced available models for inactive connections and dynamically map Antigravity MITM aliases loop-safely (#2886 — thanks @herjarsa). @@ -2493,61 +4146,258 @@ must be a response to a preceding message with 'tool_calls'` when a Codex --- -## [3.8.6] — 2026-05-27 +## [3.8.6] — 2026-05-29 + +### ✨ 新功能 + +- **providers (Unlimited LLM Access):** add 7 new web-cookie providers plus a research catalog and discovery tool, expanding free/session-based model access ([#2887](https://github.com/diegosouzapw/OmniRoute/pull/2887) — thanks @oyi77) +- **combo (Zero-Latency Combos):** add Hedging, Proactive Compression, and Predictive TTFT strategies for lower tail latency on combo routing ([#2868](https://github.com/diegosouzapw/OmniRoute/pull/2868) — thanks @herjarsa) +- **api,oauth (agy):** add the `agy` (Antigravity CLI) standalone provider with CLI token import ([#2899](https://github.com/diegosouzapw/OmniRoute/pull/2899) — thanks @diegosouzapw) +- **usage:** per-API-key token limits scoped to model / provider / global, backed by migration `073_per_model_token_limits` ([#2888](https://github.com/diegosouzapw/OmniRoute/pull/2888) — thanks @mugnimaestra) +- **providers (web-cookie audit):** fix 4 missing registry entries and add DuckDuckGo ([#2862](https://github.com/diegosouzapw/OmniRoute/pull/2862) — thanks @oyi77) +- **logs:** add clean log history action button to Logs page dashboard (#2799 — thanks @apoapostolov) +- **settings:** restore settings-driven home page layout toggles and auto-refresh limits widget (#2800 — thanks @apoapostolov) +- **modelSpecs:** register explicit model specifications and context/output caps for Moonshot, Qwen, Hunyuan, DeepSeek, MiniMax, GLM on the `opencode-go` provider (#2802 — thanks @jeferssonlemes) +- **claude:** default `xhigh` reasoning-effort support for newer Opus models ([#2874](https://github.com/diegosouzapw/OmniRoute/pull/2874) — thanks @rdself) +- **compression (RTK):** add RTK command filters for `kubectl`, `docker-build`, `composer`, and `gh` ([#2824](https://github.com/diegosouzapw/OmniRoute/pull/2824) — thanks @leninejunior) +- **compression:** expand the pt-BR "troglodita" compression pack from 15 to 49 rules ([#2818](https://github.com/diegosouzapw/OmniRoute/pull/2818) — thanks @leninejunior) +- **opencode-go:** register 4 missing models from the upstream catalog ([#2790](https://github.com/diegosouzapw/OmniRoute/pull/2790) — thanks @jeferssonlemes) +- **build:** nix multi-OS package-manager install (`flake.nix` / `flake.lock`) ([#2806](https://github.com/diegosouzapw/OmniRoute/pull/2806) — thanks @levonk) + +### 🛡️ Security + +- **mitm:** refactor `runElevatedPowerShell` to write the elevated payload to a per-call temp `.ps1` file (mode 0o600) and reference it via `-File` instead of `-EncodedCommand `, removing the textbook fingerprint flagged by Socket.dev (#2863 — thanks @a-dmx) +- **cloud-sync:** require HMAC verification of the Cloud response (`X-Cloud-Sig`) when `OMNIROUTE_CLOUD_SYNC_SECRET` is set; default-off opt-in `OMNIROUTE_CLOUD_SYNC_SECRETS` flag now required to overwrite `accessToken` / `refreshToken` / `providerSpecificData` from the Cloud payload. Closes silent-credential-swap surface (#2863) +- **providers/zed-import:** split into 2-step `discover` + `import` flow. `/import` now requires `confirmedAccounts: [{ service, account, fingerprint }]` and re-reads the keychain server-side to filter by fingerprint, so a tampered discover response cannot trick the endpoint into saving an unrelated token. `OMNIROUTE_ZED_IMPORT_LEGACY_ONE_STEP=true` preserves v3.8.5 behaviour (deprecated, removed in v3.9) (#2863) +- **build:** add `OMNIROUTE_BUILD_PROFILE=minimal` (`npm run build:secure`) that physically removes the four sensitive modules (MITM cert install, Zed keychain reader, Cloud Sync, 9router installer) from the standalone bundle via webpack `NormalModuleReplacementPlugin` aliases. Stubs return HTTP 503 `feature-disabled` at runtime. Intended for the `omniroute-secure` artifact (#2863) +- **docs:** add `docs/security/SOCKET_DEV_FINDINGS.md` per-finding maintainer attestation + `socket.yml` v2 config + in-source `SECURITY-AUDITOR-NOTE:` blocks at every flagged call site (#2863) +- **windsurf:** redact the public Firebase Web key from the Windsurf provider spec (secret-scanning #7) and document the SHA-256 cache-key rationale (code-scanning #261) ([#2894](https://github.com/diegosouzapw/OmniRoute/pull/2894), [#2896](https://github.com/diegosouzapw/OmniRoute/pull/2896) — thanks @diegosouzapw) + +### 🔧 錯誤修正 + +- **antigravity:** harden signature-less tool history handling to prevent malformed tool-call replays ([#2878](https://github.com/diegosouzapw/OmniRoute/pull/2878) — thanks @dhaern) +- **providers:** provider model-sync pruning and dynamic Antigravity MITM proxy mappings ([#2886](https://github.com/diegosouzapw/OmniRoute/pull/2886) — thanks @herjarsa) +- **audio:** build the multipart body manually to preserve `Content-Type` on transcription requests ([#2842](https://github.com/diegosouzapw/OmniRoute/pull/2842) — thanks @soyelmismo) +- **opencode-go:** add a provider-limits quota fetcher so quota state is reported correctly ([#2861](https://github.com/diegosouzapw/OmniRoute/pull/2861) — thanks @RajvardhanPatil07) +- **validation:** add specialty validators for connection test, bypassing the `/models` probe for providers that don't expose it ([#2837](https://github.com/diegosouzapw/OmniRoute/pull/2837) — thanks @oyi77) +- **cli:** restore `omniroute logs` command — create missing `/api/cli-tools/logs` route that `log-streamer.ts` was calling, returning filtered pino log entries with `follow` and `filter` query-param support (#2756) +- **cli:** replace `cli-table3` dependency with a ~50-line hand-rolled ASCII formatter to resolve Node 24 / ESM interop breakage and remove tourniquet `package.json` overrides pinning `ansi-regex@^5`, `strip-ansi@^6`, `string-width@^4` (#2752) +- **fix(opencode-go,opencode-zen):** mark qwen3.7-max / 3.6-plus / 3.5-plus as supportsVision:false to stop forwarding image blocks to vision-incapable upstream models ([#2822]) +- **nous-research:** append /chat/completions to provider baseUrl so DefaultExecutor's default URL builder hits the correct endpoint instead of returning 404 ([#2826]) +- **fix(quota):** honor explicit per-connection `quotaPreflightEnabled: false` even when the provider has global window defaults — adds early-return guard before the AND-of-negations gate in auth.ts ([#2831]) +- **api:** include noAuth providers (opencode, etc.) in `/v1/models` active aliases so their models surface without a DB connection row (#2798) +- **opencode-go:** route Qwen3.x via Claude messages format and repair `fixMissingToolResponses` helper for Claude-shape upstreams (#2791 — thanks @jeferssonlemes) +- **validation:** register missing validation helper checks for web-cookie providers (`claude-web`, `gemini-web`, `copilot-web`, `t3-web`) (#2793 — thanks @oyi77) +- **docker:** check and warn if `/app/data` is not writable in the Docker entrypoint script to fail fast with helpful host instructions (#2795 — thanks @hartmark) +- **oauth:** repair native Google loopback callback flow and support remote callbacks via state matching on 127.0.0.1 (#2796 — thanks @akarray) +- **combo:** resolve custom `openai-compatible-responses-*` provider targets correctly when called via combo name — combo steps storing the internal UUID-prefixed provider id now match the provider node by id as well as by prefix, fixing 503 errors for users with custom providers used inside combos (#2778) +- **combos:** fix combo handling so transient 429 rate limit errors do not poison or persist the rate limited state for the same-provider connection (#2800 — thanks @apoapostolov) +- **gemini:** translate signature-less Gemini thinking model tool calls to text parts to prevent `400 "missing thought_signature"` errors (#2801 — thanks @herjarsa) +- **translator:** strip `safety_identifier` from `/v1/responses` body before forwarding to Chat Completions upstream; fixes LobeHub-originated `400` errors (#2770) +- **warning-cleanup:** relax node engine constraint to `>=22.0.0` and clean dependencies (keeping `marked-terminal` to prevent TUI REPL crash) (#2792 — thanks @oyi77) +- **combo:** normalize upstream Headers into a plain object before classification to avoid Node 24 / undici cross-instance `Cannot read private member #headers` crash on combo failover (#2751) +- **translator:** silently drop `tool_search` built-in tool type instead of returning 400 — newer Codex clients send `tool_search` as a Responses API built-in with no Chat Completions equivalent (#2766) +- **usage:** un-invert GitHub Copilot Free / limited plan quota — `limited_user_quotas` is the _remaining_ count, not used, so the dashboard now shows 100% when the quota is untouched and 0% when fully exhausted (#2876 — thanks @androw) +- **fix(cli):** register openclaw in the CLI tool-detector so it appears in `omniroute status` alongside its existing API and config support ([#2833](https://github.com/diegosouzapw/OmniRoute/issues/2833)) +- **oauth (windsurf):** hotfix Windsurf login — drop the dead PKCE flow and promote the import-token flow as the default ([#2884](https://github.com/diegosouzapw/OmniRoute/pull/2884) — thanks @yunaamelia) +- **antigravity:** normalize textual SSE tool calls and classify Gemini Antigravity resource exhaustion as a model lockout instead of a connection failure ([#2828](https://github.com/diegosouzapw/OmniRoute/pull/2828) — thanks @Ardem2025) +- **reasoning:** gate reasoning replay by the `interleaved` capability field and guard the interleaved capability lookup ([#2843](https://github.com/diegosouzapw/OmniRoute/pull/2843) — thanks @nickwizard) +- **gemini-cli:** prefer real project IDs over `default-project` during discovery ([#2841](https://github.com/diegosouzapw/OmniRoute/pull/2841) — thanks @nickwizard) +- **geminiHelper:** support the `rec.image` content shape and warn on dropped remote image URLs ([#2855](https://github.com/diegosouzapw/OmniRoute/pull/2855) — thanks @Tushar49) +- **deepseek-web:** return `400` when the client sends `tools[]` — `chat.deepseek.com` has no tool support ([#2854](https://github.com/diegosouzapw/OmniRoute/pull/2854) — thanks @Tushar49) +- **claude:** preserve max reasoning effort for supported models ([#2875](https://github.com/diegosouzapw/OmniRoute/pull/2875) — thanks @rdself) +- **github:** route `claude-opus-4.6` via the chat-completions path ([#2821](https://github.com/diegosouzapw/OmniRoute/pull/2821) — thanks @marchlhw) +- **logs:** rename proxy-log "Public IP" to "Client IP" ([#2880](https://github.com/diegosouzapw/OmniRoute/pull/2880) — thanks @rdself) +- **qoder:** reject invalid/expired PATs that surface as a Cosy `500` error ([#2860](https://github.com/diegosouzapw/OmniRoute/pull/2860) — thanks @herjarsa) +- **combo:** preserve system messages during context-handoff summary generation ([#2865](https://github.com/diegosouzapw/OmniRoute/pull/2865) — thanks @herjarsa) +- **cli:** allow nullable/optional `apiKey` in `cliMitmStartSchema` ([#2857](https://github.com/diegosouzapw/OmniRoute/pull/2857) — thanks @herjarsa) +- **chatCore:** wire CLIProxyAPI fallback settings into the chatCore routing engine ([#2866](https://github.com/diegosouzapw/OmniRoute/pull/2866) — thanks @oyi77) +- **skills:** skip interception for unregistered client-native tools ([#2817](https://github.com/diegosouzapw/OmniRoute/pull/2817) — thanks @jeferssonlemes) +- **mcp:** redirect `console.log`/`console.warn` to stderr in `--mcp` stdio mode so they don't corrupt the JSON-RPC stream ([#2840](https://github.com/diegosouzapw/OmniRoute/pull/2840) — thanks @disonjer) +- **cli:** respect the `PORT` env var in the `serve` command ([#2845](https://github.com/diegosouzapw/OmniRoute/pull/2845) — thanks @gogones) +- **sse (RTK):** repair RTK engine defaults so dedup and direct calls work ([#2825](https://github.com/diegosouzapw/OmniRoute/pull/2825) — thanks @leninejunior) +- **i18n:** translate 144 new `__MISSING__` pt-BR strings ([#2816](https://github.com/diegosouzapw/OmniRoute/pull/2816) — thanks @leninejunior); complete and sync remaining pt-BR strings with `en.json` ([#2870](https://github.com/diegosouzapw/OmniRoute/pull/2870) — thanks @alltomatos); translate 162 missing zh-CN UI strings ([#2789](https://github.com/diegosouzapw/OmniRoute/pull/2789) — thanks @InkshadeWoods) ### 🧹 Chores +- **ci:** resolve `release/v3.8.6` gate failures — docs-sync, any-budget, and pack-artifact ([#2895](https://github.com/diegosouzapw/OmniRoute/pull/2895) — thanks @diegosouzapw) +- **security (re-land):** re-integrate the Socket.dev supply-chain mitigations, secrets opt-in, and minimal build profile onto the release branch ([#2871](https://github.com/diegosouzapw/OmniRoute/pull/2871) — thanks @diegosouzapw) +- **skills:** implement automated skill workflows and update system configuration + validation schemas (thanks @diegosouzapw) +- **tests:** stabilize unit suites (blackbox-web, schema-coercion, translator-helper-branches, usage-service-hardening, audio-transcription) and isolate `services-branch-hardening` DB directory to avoid concurrency flakes (thanks @diegosouzapw) +- **chore:** remove stale agent skill documentation files and streamline maintenance workflows (thanks @diegosouzapw) - **gitignore:** ignore `.claude/settings.local.json` so per-user Claude Code permissions never get committed by accident - **release:** version bump and metadata sync (package.json, package-lock.json, electron, open-sse, openapi.yaml) -> v3.8.6 is a maintenance/scaffolding patch. All feature and bug-fix work from the post-v3.8.5 cycle (44 commits — community PRs #2777, #2782–#2787, #2789, #2790, plus internal hotfixes) was already integrated into v3.8.5 and is documented under that section. +### 🏆 Contributors + +A special thanks to everyone who contributed to this release. Ranked by commits since `v3.8.6` (105 commits total): + +| Contributor | Commits | PRs | +| ---------------------------------------------------------- | ------: | ----------------------------------------------- | +| [@diegosouzapw](https://github.com/diegosouzapw) | 38 | maintainer — releases, upstream ports & fixes | +| [@oyi77](https://github.com/oyi77) | 10 | #2887, #2862, #2866, #2837, #2885, #2792, #2793 | +| [@yunaamelia](https://github.com/yunaamelia) | 7 | #2884 | +| [@herjarsa](https://github.com/herjarsa) | 6 | #2868, #2886, #2865, #2860, #2857, #2801 | +| [@leninejunior](https://github.com/leninejunior) | 4 | #2818, #2824, #2825, #2816 | +| [@jeferssonlemes](https://github.com/jeferssonlemes) | 3 | #2791, #2802, #2815, #2817 | +| [@rdself](https://github.com/rdself) | 3 | #2874, #2875, #2880 | +| Dmitry Kuznetsov | 3 | textual tool-call & lockout hardening | +| [@apoapostolov](https://github.com/apoapostolov) | 2 | #2799, #2800 | +| [@unitythemaker](https://github.com/unitythemaker) | 2 | #2904 | +| Nikolay Alafuzov | 2 | reasoning interleaved gating | +| [@Tushar49](https://github.com/Tushar49) | 2 | #2854, #2855, #2807 | +| [@guanbear](https://github.com/guanbear) | 2 | #2908 | +| [@soyelmismo](https://github.com/soyelmismo) | 2 | #2903, #2842 | +| [@RajvardhanPatil07](https://github.com/RajvardhanPatil07) | 1 | #2861 | +| [@mugnimaestra](https://github.com/mugnimaestra) | 1 | #2888 | +| [@dhaern](https://github.com/dhaern) | 1 | #2878 | +| [@hartmark](https://github.com/hartmark) | 1 | #2795, #2771 | +| [@marchlhw](https://github.com/marchlhw) | 1 | #2821 | +| [@alltomatos](https://github.com/alltomatos) | 1 | i18n pt-BR | +| [@akarray](https://github.com/akarray) | 1 | #2796 | +| [@gogones](https://github.com/gogones) | 1 | #2845 | +| [@disonjer](https://github.com/disonjer) | 1 | #2840 | +| [@nickwizard](https://github.com/nickwizard) | 1 | #2841 | +| [@levonk](https://github.com/levonk) | 1 | #2806 | + +_Reviews & additional contributions: @androw, @Ardem2025, @InkshadeWoods._ +A special thanks to everyone who contributed code, reviews, and tests for this release: +@akarray, @alltomatos, @androw, @apoapostolov, @Ardem2025, @dhaern, @disonjer, @gogones, @hartmark, @herjarsa, @InkshadeWoods, @jeferssonlemes, @leninejunior, @levonk, @marchlhw, @mugnimaestra, @nickwizard, @oyi77, @RajvardhanPatil07, @rdself, @soyelmismo, @Tushar49, @yunaamelia, Dmitry Kuznetsov, Nikolay Alafuzov --- -## [3.8.5] — 2026-05-26 +## [3.8.5] — 2026-05-27 + +### ✨ 新功能 + +- **auth:** support restricting API keys to specific endpoint categories (e.g., chat only, search only, embeddings only) with full dashboard configuration and centralized policy enforcement (#2777 — thanks @hijak) +- **batch:** recover stale `in_progress` and `finalizing` batches back to `validating` state on startup, reset counters, and apply a configurable concurrency limit (`BATCH_MAX_CONCURRENT`) (#2755 — thanks @hartmark) + +### 🔧 錯誤修正 + +- **docker:** rebuild `better-sqlite3` native bindings after hardened install to resolve container startup crash (#2772 — thanks @thanet-s) +- **combos:** make combo target timeout configurable, inheriting resolved request timeout by default and clamping values so they only shorten fallback latency (#2775 — thanks @rdself) +- **oauth:** use public callbacks for remote Google OAuth with custom creds (#2787 — thanks @akarray) +- **combos:** allow rate-limited provider connections after transient 429s (#2786 — thanks @JxnLexn) +- **logs:** keep database log settings in sync with the pipeline toggle (#2785 — thanks @JxnLexn) +- **docker:** speedup docker creation by reducing steps and bunch up copy operations (#2784 — thanks @hartmark) +- **codex:** apply global service tiers to combo request bodies (#2783 — thanks @JxnLexn) + +### ⚡ 效能 / CI + +- **ci:** build Docker platforms on native runners (linux/amd64 on ubuntu-24.04 and linux/arm64 on ubuntu-24.04-arm) instead of emulated QEMU, reducing build times significantly (#2774 — thanks @thanet-s) + +### 📝 Documentation + +- **docs:** fix broken documentation links in README after Fumadocs migration (#2782 — thanks @kjhq) + +### 🏆 Hall of Contributors + +A special thanks to everyone who contributed code, reviews, and tests for this release: +@akarray, @hartmark, @hijak, @JxnLexn, @kjhq, @rdself, @thanet-s + +--- + +## [3.8.4] — 2026-05-26 ### 🔒 Security -- **authz:** redirect `/home` and `/home/:path*` to `/login` when unauthenticated (#2712) +- **authz:** redirect `/home` and `/home/:path*` to `/login` when unauthenticated — Next.js middleware matcher omitted `/home`, so any visit reached the page directly on `REQUIRE_LOGIN` deployments (#2712 — thanks @diegosouzapw) +- **review:** resolve v3.8.4 important + minor findings from consolidated review including SSRF guards (#2749 — thanks @diegosouzapw) -### 🔧 Bug Fixes +### ✨ 新功能 -- **mcp:** break callLogs ↔ compliance ESM cycle that deadlocks the bundled MCP server on Node.js 24 (#2650) -- **deepseek:** guard PoW solver Web Worker handler under Node strict mode (#2724) -- **combos:** include no-auth providers in the combo builder picker (#2737) -- **translator:** allow the `web_search` server-tool family in the Responses API translator (#2695) -- **oauth:** register the missing `trae` provider with `import_token` flow (#2658) -- **model:** merge settings-based aliases with the legacy DB alias namespace (#2618, #2208) -- **kiro:** clipboard fallback for HTTP / non-secure contexts (#2689) -- **cli:** raise `omniroute serve` ready timeout to 60s with TCP fallback for Windows cold start (#2460) +- **feat(credential-health):** fail-fast credential health check with TTL cache and background scheduler — validates API key + OAuth connections before combo dispatch, skips failed targets in <1ms instead of 10-30s timeout +- **feat(middleware):** pre-request middleware pipeline with global, combo-specific, and per-request scopes — hooks can mutate body/headers/model, short-circuit, or skip remaining hooks +- **feat(websocket):** live dashboard WebSocket server on port 20129 with EventBus integration — real-time request started/combo target attempt/succeeded/failed and credential health events +- **feat(circuit-breaker):** three-state circuit breaker (CLOSED→DEGRADED→OPEN) with adaptive backoff per failure kind (rate-limit/auth/timeout), escalation count, and historical state tracking +- **feat(key-groups):** API key groups with migration 066 — key_groups, group_model_permissions, key_group_members tables and CRUD, REST endpoints, group auth integration +- **feat(copilot):** OmniRoute Copilot with CodeGraph knowledge base and CLI harness — LLM-guided configurator at POST /api/copilot/chat +- **feat(combo-playground):** combo routing simulation API and dashboard UI under /dashboard/combos/playground/ +- **feat(pwa):** improved PWA manifest with icons, categories, and service worker with push notification support +- **feat(relay):** serverless relay proxies with migration 067 — relay_tokens, relay_rate_limits, relay_logs, public endpoint at /api/v1/relay/chat/completions, management API, dashboard UI +- **feat(cost):** cost optimization engine with alerts (budget/spike/trend thresholds), 6 REST endpoints, dashboard alerts UI +- **feat(backup):** backup and restore system with export/import API and dashboard UI +- **feat(config-templates):** config templates with migration 070, seed data, CRUD + apply API, dashboard UI +- **feat(custom-models):** custom model registry with migration 069, CRUD API, dashboard UI +- **feat(webhooks-cicd):** webhook CI/CD actions with migration 071 — ActionEngine supporting deploy/restart/sync actions, REST API +- **feat(multitenant):** multi-tenant dashboard with per-API-key usage aggregation and provider/model breakdown +- **feat(sla):** SLA dashboard with uptime/latency/error rate queries, summary/trend APIs, uptime badges and sparklines +- **feat(routing-analytics):** AI-powered usage pattern analysis and routing recommendations — combo_metrics queries, hourly failure heatmap, provider breakdown, cost-vs-latency scatter chart +- **feat(teams):** fixed team execution with 13 git worktrees and project-level team configs +- **feat(providers):** add Inner.ai provider support with native executor, translation support, and model catalog definitions (#2704 — thanks @df4p) +- **feat(proxy):** unified free proxy pool, Vercel Relay serverless endpoints, and a redesigned 4-tab proxy dashboard interface (#2705 — thanks @diegosouzapw) +- **feat(webhooks):** 3-step configuration wizard for Slack, Telegram, Discord, and Custom webhook destinations, with reorganized React components (#2703 — thanks @diegosouzapw) +- **feat(openapi):** comprehensive API endpoints content audit with 100% schema coverage, authz security tiers, and full i18n localization support (#2701 — thanks @diegosouzapw) +- **feat(providers):** add BluesMinds, FreeModel.dev, and FreeAIAPIKey to the provider catalog (#2709 — thanks @oyi77) +- **feat(routing/providers):** broaden routing, provider capabilities, and dashboard views — adds AWS Bedrock provider executor, combo scoring inspector, route explainability, reset-aware combo routing, and improves UI views for quota and resilience (#2750 — thanks @JxnLexn) +- **feat(batch-fixes):** clean batch UI, Docker compose base profile, and support for parallel testing execution (#2761 — thanks @diegosouzapw) +- **chore(deps):** added ws + @types/ws for WebSocket support, recharts ^3.8.1 for analytics charts ---- +### 🔧 錯誤修正 -## [Unreleased] +- **validation:** add Poolside specialty validator (direct `/chat/completions` probe — Poolside has no `/v1/models` endpoint and returns 401 for unknown routes, which the generic `/models` flow misread as "invalid API key") (#2723) +- **validation:** add NVIDIA NIM specialty validator and harden `normalizeBaseUrl` against non-string `providerSpecificData.baseUrl` — fixes the `e.startsWith is not a function` TypeError that surfaced after minification (#2463) +- **cli:** `omniroute compression *` falls back to direct REST endpoints (`/api/settings/compression`, `/api/context/combos`, `/api/context/analytics`) when `/api/mcp/tools/call` returns 404; normalize `none → off` / `hybrid → stacked` engine aliases (#2688) +- **cli:** import `cli-helper/tool-detector` and `cli-helper/doctor/checks` with the explicit `.ts` extension that tsx resolves directly, so the published npm package (which ships only the `.ts` source) no longer crashes with `Cannot find module '…tool-detector.js'` (#2509) +- **authz:** make the DB feature-flag override authoritative over `process.env` for `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`, so toggling "Allow Private Provider URLs" in the Electron dashboard takes effect without restarting the spawned server (#2575) +- **fix(antigravity):** stabilize model detection, OAuth handling, and token refresh logic (#2757 — thanks @oyi77) +- **fix(batch):** recover and resume stale batch jobs on server restart instead of failing them, and add configurable concurrency limit (#2755 — thanks @hartmark) +- **fix(harness):** resolve Headers private slot errors and type check compiler issues, and stabilize cooldown retry test flakiness (#2763 — thanks @diegosouzapw) +- Fix combo cascade skipping on credential check timeout +- Fix team sessions going idle (worktree initialization) +- **feat(providers):** enhance Google Gemini, CLI, and Antigravity resilience and features — introduces explicit TypeScript typing to translation layers, adds new Gemini 2.0 models, implements backoff and retry logic in the Gemini CLI executor, extracts Google Search grounding metadata into standard `citations`, and adds backend definitions for the `vertex-partner` provider. ([#2676](https://github.com/diegosouzapw/OmniRoute/pull/2676) — thanks @alltomatos) +- **fix(proxy):** atomically save and assign custom dashboard proxies in a single SQLite transaction, preventing orphan configuration rows (#2697 — thanks @terence71-glitch) +- **fix(reasoning):** inject thinking blocks into Claude-format messages for Kimi K2 to prevent infinite tool-calling loops (#2699 — thanks @herjarsa) +- **fix(antigravity):** default exhausted quota status display to 0% instead of 100% (#2700 — thanks @ahmet-cetinkaya) +- **fix(electron):** add Caps Lock indicator, custom reset warnings, and suppress shell window spawning on startup (#2714 — thanks @benzntech) +- **fix(combos):** resolve context handoff tags ordering issue and enforce a 60-second request timeout limit per combo target to prevent capacity leaks (#2717 — thanks @herjarsa) +- **fix(oauth):** resolve parallel token refresh race conditions in Codex and implement comprehensive error checking across OAuth providers (#2718 — thanks @diegosouzapw) +- **fix(docker):** install `python3`, `make`, and `g++` in the Docker builder stage to support native Node.js addon compilation (#2713 — thanks @mrmm) +- **fix(i18n):** restore real hint and placeholder translation strings for web-cookie providers in `en.json` (#2694 — thanks @diegosouzapw) +- **fix(db):** resolve migration version prefix collision between services and webhook metadata tables (#2727 — thanks @diegosouzapw) +- **fix(vision-bridge):** ensure images are processed when a vision-capable model is matched through a combo routing mapping (#2706 — thanks @herjarsa) +- **mcp:** break callLogs ↔ compliance ESM cycle that deadlocks the bundled MCP server on Node.js 24 — extract no-log state to `compliance/noLog.ts`, switch callers to the leaf module, keep `compliance/index.ts` re-exports for backwards compat (#2650 — thanks @disonjer) +- **deepseek:** guard PoW solver Web Worker handler so `require()` no longer throws `ReferenceError: onmessage is not defined` under Node strict mode (#2724 — thanks @thanet-s) +- **combos:** include no-auth providers (FreeAIAPIKey, BluesMinds, FreeModel.dev, opencode, …) in the combo builder picker — they were invisible because they never get rows in `provider_connections` (#2737 — thanks @herjarsa) +- **translator:** allow the `web_search` server-tool family (`web_search_20250305`, `web_search_20250101`, plain `web_search`) in the Responses API translator and preserve the original versioned name on output (#2695 — thanks @diegosouzapw) +- **oauth:** register the missing `trae` provider with `import_token` flow so the Trae IDE no longer 500s during token import (#2658 — thanks @diegosouzapw) +- **model:** merge settings-based aliases with the legacy DB alias namespace so aliases set via the Settings UI (e.g. `gpt-5.4 → cx/gpt-5.4`) are honored instead of being overridden by provider inference (#2618, #2208 — thanks @diegosouzapw) +- **kiro:** fall back to `document.execCommand("copy")` when the Clipboard API is unavailable (HTTP/non-secure contexts), so the "Copy authorization link" button works on LAN deployments (#2689 — thanks @disonjer) +- **cli:** raise `omniroute serve` ready timeout from 20s to 60s and add a TCP-listening fallback so Windows users no longer get phantom timeouts during slow Next.js cold start (#2460 — thanks @benzntech) +- **mcp:** break circular await deadlock in compliance→callLogs + Kiro refresh resilience (#2747 — thanks @disonjer) +- **ui:** claude-web provider shows 'API Key' label instead of 'Session Cookie' (#2744 — thanks @oyi77) +- **deepseek-web:** lazy start session refresh (#2742 — thanks @thanet-s) +- **docker:** keep fumadocs doc assets in Docker build context (#2741 — thanks @janeza2) +- **vision-bridge:** force bridge for opencode-go/zen models that overstate vision support (#2740 — thanks @herjarsa) +- **combos:** enable universal handoff by default to preserve cross-model conversation context (#2736 — thanks @herjarsa) ---- +### 🚀 Embedded Services -## [3.8.23] — TBD +- **feat(services):** embedded service manager for 9Router and CLIProxyAPI — introduces a full lifecycle management system for locally-run AI proxy daemons accessible on loopback only: + - **ServiceSupervisor** (`src/lib/services/supervisor.ts`) — EventEmitter-based child process manager with state machine (`not_installed → stopped → starting → running → stopping → error`), ring-buffer log capture (5 MB/service), health polling, and configurable stop timeout. + - **ServiceRegistry** (`src/lib/services/registry.ts`) — process-scoped map of active `ServiceSupervisor` instances; integrates with `bootstrap.ts` for auto-start on app launch. + - **9Router lifecycle** — npm-installer (`src/lib/services/installers/ninerouter.ts`), 8 REST endpoints under `/api/services/9router/` (install, start, stop, restart, update, status, auto-start, rotate-key), NineRouterExecutor at `open-sse/executors/ninerouter.ts`, model-sync job, and provider registration. + - **CLIProxyAPI lifecycle** — GitHub-release installer (`src/lib/services/installers/cliproxy.ts`), 7 REST endpoints under `/api/services/cliproxy/` (install, start, stop, restart, update, status, auto-start), health probe at `/v1/models` (CPA 6.x has no `/health` endpoint). + - **SSE log streaming** — `/api/services/{name}/logs` with `tail` and `filter` query params, `snapshot` + `log` SSE events, 30-second heartbeat. + - **WebSocket proxy** — `/api/services/{name}/ws` reverse-proxies WebSocket connections to the embedded service UI port (port 20131); `isLocalOnlyPath()` guard in `routeGuard.ts` (Hard Rule #17). + - **HTTP UI proxy** — `/api/services/9router/proxy/[...path]` for iframe asset loading. + - **Dashboard page** `/dashboard/providers/services` — URL-based tab navigation (`?tab=cliproxy` default / `?tab=9router`), shared components (`ServiceStatusCard`, `ServiceLifecycleButtons`, `ServiceLogsPanel`), sidebar item under Omni Proxy (hideable, `material-symbols-outlined: deployed_code`). + - **CliproxyServiceTab** — auto-start toggle, fallback routing card (enable/disable, URL, status codes); fallback settings remain mirrored in Settings → CLIProxyAPI for backward compatibility. + - **NinerouterServiceTab** — auto-start toggle, API key display + rotation, collapsible embedded Web UI iframe (`sandbox="allow-scripts allow-same-origin allow-forms"`, loopback-only). + - **DB migration 071** (originally 068, renumbered post-merge to avoid collision with `068_free_proxies` and `068_webhooks_kind_metadata`) — extends `version_manager` table with `autoStart`, `autoUpdate`, `providerExpose`, `apiKey`, and `port` columns. `migrationRunner.ts` now throws at boot if two `.sql` files share the same numeric prefix. + - All service routes classified as `LOCAL_ONLY` in `routeGuard.ts`; loopback enforcement is unconditional before any auth check (leaked JWT via tunnel cannot trigger process spawning). ---- +### 🏆 Hall de Contribuidores -### ✨ New Features - -### 🔧 Bug Fixes - ---- - -## [3.8.4] — 2026-05-25 - -### Added - -- Embedded services (work in progress — 9Router, CLIProxyAPI; see T-15 for full entry). +Um agradecimento especial a todos que contribuíram com código, revisões e testes para este release: +@ahmet-cetinkaya, @alltomatos, @benzntech, @Chewji9875, @df4p, @diegosouzapw, @disonjer, @hartmark, @herjarsa, @janeza2, @JxnLexn, @mrmm, @oyi77, @thanet-s, @terence71-glitch --- ## [3.8.3] — 2026-05-24 -### ✨ New Features +### ✨ 新功能 - **feat(combos):** universal context handoff for cross-model conversation continuity — structured XML summary system (``) that preserves conversation continuity and handles state transfer when combo routing switches models. ([#2653](https://github.com/diegosouzapw/OmniRoute/pull/2653) — thanks @herjarsa) - **feat(docs):** migrate `/docs` to Fumadocs MDX with nested routes — replaces the custom docs engine with Fumadocs, adding `[...slug]` catch-all routing, search API at `/docs/api/search`, `source.config.ts` content configuration, and `meta.json` navigation files across 8 doc sections (`architecture/`, `compression/`, `frameworks/`, `guides/`, `ops/`, `reference/`, `routing/`, `security/`). Includes 50+ URL redirects for backward compatibility via `next.config.mjs`. ([#2614](https://github.com/diegosouzapw/OmniRoute/pull/2614) — thanks @ovehbe) @@ -2559,8 +4409,21 @@ must be a response to a preceding message with 'tool_calls'` when a Codex - **feat(webfetch):** category support with dedicated media providers page and executors for Firecrawl, Jina Reader, and Tavily. ([#2645](https://github.com/diegosouzapw/OmniRoute/pull/2645) — thanks @diegosouzapw) - **feat(adapta):** integrate Adapta Org (`adapta-web`) provider with automatic Clerk authentication refresh and custom onboarding tutorial modal. ([#2643](https://github.com/diegosouzapw/OmniRoute/pull/2643) — thanks @df4p) - **feat(i18n):** complete translations for Simplified Chinese — translates 1220 missing keys bringing UI coverage to 98.8% with 0 placeholders. ([#2655](https://github.com/diegosouzapw/OmniRoute/pull/2655) — thanks @L-aros) +- **feat(dashboard):** add Cmd+K / Ctrl+K command palette for sidebar navigation and a slideover panel for LLM provider testing UI. ([#2656](https://github.com/diegosouzapw/OmniRoute/pull/2656) — thanks @mrmm) +- **feat(i18n):** finish Simplified Chinese (zh-CN) UI coverage with 377 translated entries. ([#2659](https://github.com/diegosouzapw/OmniRoute/pull/2659) — thanks @L-aros) +- **feat(dashboard):** chat-first test slide-over layout — consolidates header controls (Model/Key selects, Clear button) into a unified toolbar, maximizes vertical conversation space, integrates live tailing of provider logs in Logs tab, and locks composer focus for keyboard-only convenience. ([#2660](https://github.com/diegosouzapw/OmniRoute/pull/2660) — thanks @mrmm) +- **feat(cli):** desktop updates, autostart, and headless CLI modes — integrates native auto-updater checks, login autostart (Linux .desktop, macOS/Windows login items), and a background headless server CLI daemon mode (`--headless` or `OMNIROUTE_HEADLESS=true`) into the Electron app wrapper. ([#2662](https://github.com/diegosouzapw/OmniRoute/pull/2662) — thanks @benzntech) +- **feat(quota):** card-grid layout and provider group headers under quota management — replaces monolithic table with a beautiful 4-column card grid in limits. ([#2667](https://github.com/diegosouzapw/OmniRoute/pull/2667) — thanks @Gi99lin) +- **feat(dashboard):** real-time WebSocket live monitoring daemon — runs a Node.js WebSocket daemon sidecar on port `20129` to emit real-time events for request starts/completes/fails, combo attempts, and credential status in the dashboard logs. ([#2668](https://github.com/diegosouzapw/OmniRoute/pull/2668) — thanks @herjarsa) +- **feat(copilot):** AI assistant with CodeGraph + CLI + knowledge base — integrates a dashboard assistant with CodeGraph knowledge base access and CLI capabilities for app exploration. ([#2669](https://github.com/diegosouzapw/OmniRoute/pull/2669) — thanks @ovehbe / @herjarsa) +- **feat(pipeline):** pre-request middleware hooks — pipeline executing custom JS hooks before routing/combo logic to mutate headers/body or short-circuit requests. ([#2670](https://github.com/diegosouzapw/OmniRoute/pull/2670) — thanks @herjarsa) +- **feat(resilience):** credential health check + adaptive circuit breaker v2 — background connection health check scheduler with progressive circuit breaker adding DEGRADED state and HALF-OPEN recovery validation to avoid latency spikes. ([#2671](https://github.com/diegosouzapw/OmniRoute/pull/2671) — thanks @herjarsa) +- **feat(playground):** combo routing visual simulator — interactive route simulation page at `/dashboard/combos/playground` to showcase cascade hops, latency, and cost estimates. ([#2672](https://github.com/diegosouzapw/OmniRoute/pull/2672) — thanks @herjarsa) +- **feat(auth):** API key groups with model-level permissions — group definitions with model-level wildcards/denies where API keys inherit group-scoped restrictions. ([#2673](https://github.com/diegosouzapw/OmniRoute/pull/2673) — thanks @herjarsa) +- **feat(pwa):** enhanced manifest + push notification support — polishes offline shortcuts, screenshots, display metadata, and push service workers. ([#2674](https://github.com/diegosouzapw/OmniRoute/pull/2674) — thanks @herjarsa) +- **feat(proxy):** serverless relay proxy endpoints with rate limiting — public relay proxy endpoints with cost caps and rate limits, CRUD API, and dashboard usage tracking. ([#2675](https://github.com/diegosouzapw/OmniRoute/pull/2675) — thanks @herjarsa) -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **fix(settings):** Require Login modal Cancel button text and dismissal — modal now renders localized cancel label via the `common` namespace and closes correctly without modifying settings when cancelled. ([#2649](https://github.com/diegosouzapw/OmniRoute/pull/2649) — thanks @Chewji9875) - **fix(deepseek-web):** re-apply SSE parser, prompt format, and error handling fixes — handles all 3 DeepSeek SSE stream formats (initial fragments, APPEND operations, bare string tokens), uses non-greedy regex for markdown image stripping, simplifies prompt to single-turn, checks `json.code` before token extraction, and uses `accessToken` fallback for session cache eviction on auth errors. ([#2616](https://github.com/diegosouzapw/OmniRoute/pull/2616) — thanks @ovehbe) @@ -2579,17 +4442,27 @@ must be a response to a preceding message with 'tool_calls'` when a Codex - **fix(rtk):** prevent false positives in RTK compression by skipping content-based filter matching for non-shell tool results (e.g. read_file, grep_search). ([#2642](https://github.com/diegosouzapw/OmniRoute/pull/2642) — thanks @HALDRO) - **fix(translator):** enable Claude extended thinking for Copilot Responses-API requests — handles reasoning budget and translations for Copilot. ([#2647](https://github.com/diegosouzapw/OmniRoute/pull/2647) — thanks @ivan-mezentsev) - **fix(tests):** remove duplicate assertion in schema coercion & fix(cli): ignore system vars in env check. (thanks @diegosouzapw) +- **fix(combo):** resolve pending request leaks on unresponsive combo targets — implements a default 60-second per-target timeout during combo routing loops to abort hanging upstream requests and release capacity limits. ([#2663](https://github.com/diegosouzapw/OmniRoute/pull/2663) — thanks @Chewji9875) +- **fix(proxy):** save custom dashboard proxies directly in SQLite registry — writes new provider/account/global/combo custom proxies directly to the modern `proxy_registry` database and assigns them via `proxy_assignments` instead of creating duplicate configurations. ([#2661](https://github.com/diegosouzapw/OmniRoute/pull/2661) — thanks @terence71-glitch) +- **fix(settings):** expand effortLevel enum to support xhigh and max reasoning efforts — adds `xhigh` and `max` levels to the updateThinkingBudgetSchema to resolve validation failures that silently discarded top-effort request payloads. ([#2666](https://github.com/diegosouzapw/OmniRoute/pull/2666) — thanks @mrmm) +- **fix(codex):** Codex OAuth refresh token reuse race condition under parallel requests. ([#2667](https://github.com/diegosouzapw/OmniRoute/pull/2667) — thanks @diegosouzapw) -### 📝 Maintenance +### 📝 維護 - **chore(config):** ignore additional agent workflow command files (`.agents/commands/`). (thanks @diegosouzapw) - **chore(config):** ignore `memory-bank` and Cursor agent rules from tracking. (thanks @ovehbe) +- **chore(ci):** publish @omniroute/opencode-plugin to npm — adds a parallel build, test, and publish job to the npm release workflow for automated package deployment. ([#2666](https://github.com/diegosouzapw/OmniRoute/pull/2666) — thanks @mrmm) + +### 🏆 Hall de Contribuidores + +Um agradecimento especial a todos que contribuíram com código, revisões e testes para este release: +@amogus22877769, @benzntech, @Chewji9875, @df4p, @diegosouzapw, @Gi99lin, @HALDRO, @herjarsa, @ivan-mezentsev, @janeza2, @L-aros, @mrmm, @ovehbe, @oyi77, @Tentoxa, @terence71-glitch, @unitythemaker --- ## [3.8.2] — 2026-05-22 -### ✨ New Features +### ✨ 新功能 - **feat(@omniroute/opencode-plugin):** upstream-provider suffix in model display name — appends provider label to enriched names (e.g. `Claude Opus 4.7 · Claude` vs `Claude Opus 4.7 · Kiro`) so the OC TUI model picker can differentiate same-id models routed through different upstream connections. Default-on, opt-out via `features.providerTag: false`. ([#2602](https://github.com/diegosouzapw/OmniRoute/pull/2602) — thanks @mrmm) - **feat(@omniroute/opencode-plugin):** provider-tag becomes a prefix + traffic-light compression emoji — provider label now prepends (`Claude - Claude Opus 4.7`) for better TUI column grouping, with smart abbreviation for long labels (`GitHub Models` → `GHM`). Compression pipelines render intensity as emoji (🟢🟡🟠🔴). ([#2604](https://github.com/diegosouzapw/OmniRoute/pull/2604) — thanks @mrmm) @@ -2613,7 +4486,7 @@ must be a response to a preceding message with 'tool_calls'` when a Codex - **feat(providers):** add api-airforce free provider with 55 models. ([#2587](https://github.com/diegosouzapw/OmniRoute/pull/2587) — thanks @oyi77) - **feat(dashboard):** configurable sidebar — presets, drag-and-drop ordering, smart-grouping, and new Settings → Sidebar page. ([#2581](https://github.com/diegosouzapw/OmniRoute/pull/2581) — thanks @Gi99lin) -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **fix(validation):** stop appending a second `/models` when the Gemini base URL already ends in `/models` — Google AI Studio connections using the default base URL were validating against `.../v1beta/models/models` and failing with `404` for every connection. ([#2545](https://github.com/diegosouzapw/OmniRoute/issues/2545)) - **fix(cloudflare-ai):** flatten OpenAI content-part arrays to plain strings for the Workers AI (`cf/`) executor — Workers AI's `/ai/v1/chat/completions` rejects `content: [{type:"text",...}]` with HTTP 400, so requests with array content now have their text parts joined into a string. ([#2539](https://github.com/diegosouzapw/OmniRoute/issues/2539)) @@ -2698,7 +4571,7 @@ must be a response to a preceding message with 'tool_calls'` when a Codex - **i18n(all):** translate freeTier provider strings across 41 locales — replaces `__MISSING__:Free Tier Providers` placeholders with proper translations in both `common` and `providers` namespaces. ([#2609](https://github.com/diegosouzapw/OmniRoute/pull/2609) — thanks @leninejunior) - **i18n(pt-BR):** eliminate all 1270 remaining `__MISSING__` markers — completes pt-BR translation across 41 namespaces to true 100% coverage. ([#2610](https://github.com/diegosouzapw/OmniRoute/pull/2610) — thanks @leninejunior) -### 📝 Maintenance +### 📝 維護 - **chore:** remove Akamai VPS deploy from release workflow and skills. - **chore(deps):** bump `actions/setup-node` from v4 to v6 + `randomBytes` security fix for cloud agent task IDs. ([#2589](https://github.com/diegosouzapw/OmniRoute/pull/2589)) @@ -2711,7 +4584,7 @@ must be a response to a preceding message with 'tool_calls'` when a Codex ## [3.8.1] — 2026-05-21 -### ✨ New Features +### ✨ 新功能 - **feat(settings):** Feature Flags Settings Page (Card Grid + DB overrides) — fully implements the feature flags UI dashboard using Variant A (Card Grid) with Glassmorphism, complete with global `GET/PUT/DELETE` API routes, Zod validation, debounced search, category filters, and full 30+ locale i18n support. Resolves priority hierarchy to DB > ENV > Defaults. ([#2457](https://github.com/diegosouzapw/OmniRoute/pull/2457)) - **feat(db):** multi-driver SQLite abstraction layer — new `SqliteAdapter` interface with 3 concrete adapters (`betterSqliteAdapter`, `nodeSqliteAdapter`, `sqljsAdapter`) and a `driverFactory` that cascades `better-sqlite3` → `node:sqlite` → `sql.js (WASM)`. Enables OmniRoute to run on any JavaScript runtime (Node.js, Bun, Deno, Cloudflare Workers) without native binary dependencies. `better-sqlite3` moved to `optionalDependencies`. ([#2447](https://github.com/diegosouzapw/OmniRoute/pull/2447)) @@ -2721,7 +4594,7 @@ must be a response to a preceding message with 'tool_calls'` when a Codex - **feat:** enhance `extractBearer` to support `x-api-key` for Anthropic API style auth. ([#2436](https://github.com/diegosouzapw/OmniRoute/pull/2436) — thanks @thedtvn) - **feat(memory):** wire `createMemory` to `upsertSemanticMemoryPoint` (Qdrant). ([#2439](https://github.com/diegosouzapw/OmniRoute/pull/2439) — thanks @NomenAK) -### 🔧 Bug Fixes & Refactors +### 🔧 錯誤修正 & Refactors - **fix(deepseek-web):** rewrite auth to userToken Bearer + WASM PoW solver. ([#2452](https://github.com/diegosouzapw/OmniRoute/pull/2452) — thanks @ovehbe) - **chore:** update node dependencies and runtime support. ([#2453](https://github.com/diegosouzapw/OmniRoute/pull/2453) — thanks @backryun) @@ -2971,6 +4844,8 @@ must be a response to a preceding message with 'tool_calls'` when a Codex ### Changed - **CLI**: Refactored architecture to use Commander.js as framework. Monolith `bin/cli-commands.mjs` (2853 lines) removed — commands now live individually in `bin/cli/commands/`. No breaking changes in normal usage; all previously listed subcommands continue working. +- **API keys**: keys without explicit rate-limit rules continue to receive the legacy default safety net (`1000/day`, `5000/week`, `20000/month`). Operators that need the previous uncapped behavior can set `DEFAULT_RATE_LIMIT_PER_DAY=0`; positive values scale the daily/weekly/monthly defaults from that daily limit. +- **Cloud features**: fresh installations now start with Cloud disabled by default. Existing deployments with a persisted `cloudEnabled` setting are unchanged; operators can enable Cloud again from Dashboard settings. ### Removed @@ -3130,7 +5005,7 @@ Thanks also to **@app/dependabot** for keeping our dependency tree current via # ### Detalhes completos — features e fixes do lançamento (2026-05-06 a 2026-05-14) -#### ✨ New Features +#### ✨ 新功能 - **feat(providers):** add Command Code provider (#2199 — thanks @ddarkr) - **feat(providers):** add ModelScope provider-specific 429 handling and retry logic (#2202 — thanks @InkshadeWoods) @@ -3177,7 +5052,7 @@ Thanks also to **@app/dependabot** for keeping our dependency tree current via # - **feat(antigravity):** support custom Google Cloud project ID for Antigravity provider (#2227 — thanks @nickwizard) - **feat(cli):** CLI Integration Suite — 5 new management commands (`config`, `status`, `logs`, `update`, `provider`), 3 API endpoints, config generators for 6 tools (Claude, Cline, Codex, Continue, KiloCode, OpenCode), zero-config `auto/` routing, and `@omniroute/opencode-provider` npm package (#2240 — thanks @oyi77) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(pricing):** make `getPricingForModel` fully case-insensitive to ensure custom prices correctly reflect in new incoming requests cost calculations - **fix(gemini):** prevent `functionDeclarations` from being dropped by the sanitizer when `googleSearch` tool is present (#2077) @@ -3369,7 +5244,7 @@ Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉 ## [3.7.9] — 2026-05-03 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -3396,7 +5271,7 @@ Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉 - **feat(usage):** DeepSeek V4 native cache token extraction (#1930 — thanks @smartenok-ops) - **feat(cost):** enhance cost formatting and add Codex GPT-5.5 pricing support (#1944 — thanks @JxnLexn) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(auth):** implement session affinity sticky routing logic - **fix(dashboard):** derive display base URL from origin instead of hardcoding localhost (#1960 — thanks @jeanfbrito) @@ -3447,7 +5322,7 @@ Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉 ## [3.7.8] — 2026-05-01 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -3461,7 +5336,7 @@ Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉 - **feat(providers):** add muse-spark-web provider with multiple models and reasoning support (#1843) - **feat(1proxy):** integrate 1proxy free proxy marketplace with dashboard management and new MCP tools (closes #1788) (#1847) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(codex):** sanitize Responses replay state to prevent internal assistant commentary from leaking (#1868 — thanks @dhaern) - **fix(cli):** add capture-backed Gemini CLI fingerprint (#1866) @@ -3486,7 +5361,7 @@ Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉 ## [3.7.7] — 2026-04-30 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -3500,7 +5375,7 @@ Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉 - **Compression Caching & MCP:** Added caching-aware strategy adjustments to the compression pipeline, alongside new MCP tools for status and configuration (#1758) - **Analytics Custom Filters:** Added custom date range selection, API key filtering, and NULL key analytics backfilling to the Costs Dashboard (#1830) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **Combo Routing:** Fixed an issue where Gemini `-preview` models were incorrectly normalized to their canonical names, causing 404 errors during combo routing (#1834) - **Codex Native Passthrough:** Added support for Cursor 5.5 sending `messages` arrays to the `responses/compact` endpoint, preventing upstream rejections with empty requests (#1832) @@ -3518,7 +5393,7 @@ Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉 ## [3.7.6] — 2026-04-30 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -3538,7 +5413,7 @@ Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉 - **fix(security):** resolve ReDoS vulnerability in Codex executor regex patterns (#1797, #1789) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(stability):** resolve codex input validation, enable combo circuit breaker, and fix broken unit tests (#1804, #1805) - **fix(stability):** safely cast inputs to strings before calling `.trim()` to avoid crashes on numeric fields in proxy modal (#1825) @@ -3552,7 +5427,7 @@ Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉 ### 🧹 Chores -- **chore(workflow):** mandate implementation plan generation in `/resolve-issues` workflow before coding +- **chore(workflow):** mandate implementation plan generation in `/review-issues` workflow before coding - **chore(release):** expand contributor credits to 155 PRs across full project history ### 🏆 Community Contributors Acknowledgment @@ -3621,7 +5496,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.7.5] — 2026-04-29 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -3632,7 +5507,7 @@ We identified that **155 community PRs** across the entire project history (from - **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(dashboard):** add manual 'Clear All' button to terminate stalled long-running requests in Active Requests panel (#1799) - **fix(schema):** remove empty string values from optional tool parameters to prevent upstream validation errors (#1674) @@ -3673,7 +5548,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.7.4] — 2026-04-28 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -3691,7 +5566,7 @@ We identified that **155 community PRs** across the entire project history (from - **security:** replace insecure `Math.random` with `crypto.getRandomValues` for fallback UUID generation to resolve CodeQL CWE-338 finding (#182) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(cc-compatible):** fix CC-compatible relay format and UI copy (#1742) - **fix(codex):** normalize max reasoning effort for Codex routing (#1744) @@ -3716,7 +5591,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.7.3] — 2026-04-28 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(claude):** strip existing billing headers from system array before injecting to prevent Anthropic prompt cache misses — stacked `x-anthropic-billing-header` blocks invalidated prefix matching, causing ~100% cache_create instead of cache_read (#1712) - **fix(claude):** strip `output_config.format` for non-Anthropic Claude-compatible providers during passthrough — third-party Claude endpoints (MiniMax, DeepSeek via aggregators) reject structured output fields with 400 errors (#1719) @@ -3736,7 +5611,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.7.2] — 2026-04-28 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -3751,7 +5626,7 @@ We identified that **155 community PRs** across the entire project history (from - **feat(codex):** enable native Codex websocket responses on beta-gated models (#1658) - **feat(muse-spark-web):** continue the same meta.ai conversation across turns (#1673) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(responses):** sanitize empty string placeholders from tool-call optional arguments in stream delta accumulation to avoid breaking strict clients (#1674) - **fix(codex):** prevent unexpected protocol leakage and fabricated instructions on bare chat completion requests without tools (#1686) @@ -3812,7 +5687,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.7.1] — 2026-04-26 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -3825,7 +5700,7 @@ We identified that **155 community PRs** across the entire project history (from - **feat(cli):** Add `omniroute reset-encrypted-columns` recovery command — nulls encrypted credential columns (`api_key`, `access_token`, `refresh_token`, `id_token`) in `provider_connections` while preserving provider metadata, giving users affected by #1622 a clean recovery path without losing configurations. - **feat(i18n):** Expand locale coverage with nine new language packs (Bengali, Farsi, Gujarati, Indonesian, Marathi, Swahili, Tamil, Telugu, Urdu), bringing total language support from 32 to 41 locales. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(rate-limit):** Add per-model rate limiting for GitHub Copilot provider — a 429 on one model (e.g. `gpt-5.1-codex-max`) no longer locks the entire connection, matching the existing Gemini per-model quota pattern (#1624 — thanks @slewis3600). - **fix(cli-tools):** Preserve existing OpenCode configuration (MCP servers, custom providers, comments) when saving OmniRoute settings — uses `jsonc-parser` for tree-preserving edits instead of destructive JSON roundtrip. Fix API key clipboard copy to use raw keys instead of masked placeholders. Add theme-aware OpenCode light/dark SVG logos (#1626 — thanks @JasonLandbridge). @@ -3860,7 +5735,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.7.0] — 2026-04-26 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -3909,7 +5784,7 @@ We identified that **155 community PRs** across the entire project history (from - **feat(providers):** Register Codex auto review and expand icon coverage. - **feat(tunnels):** Add Tailscale tunnel management routes and runtime helpers for install, login, daemon start, enable/disable, and health checks. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4025,7 +5900,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.6.9] — 2026-04-19 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4045,7 +5920,7 @@ We identified that **155 community PRs** across the entire project history (from - **feat(providers):** Derive Claude CLI model defaults dynamically from provider registry to stay current with upstream API changes (#1393) - **feat(core):** Implement persistent API key, backup pruning, and GPU optimization (#1350, #1367, #1369) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4114,7 +5989,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.6.8] — 2026-04-17 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4158,7 +6033,7 @@ We identified that **155 community PRs** across the entire project history (from - **ci:** Bump GitHub Actions CI node-version to Node.js 24 natively - **fix(types):** Resolve TypeScript compilation errors in `claudeCodeCompatible.ts` (type predicates, `cache_control` index access) and `proxyFetch.ts` (`signal` nullability) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4201,7 +6076,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.6.6] — 2026-04-15 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4232,7 +6107,7 @@ We identified that **155 community PRs** across the entire project history (from - **feat(oauth):** Supports `cursor-agent` CLI as a native Cursor credential source alongside the standard configuration (#1258). - **feat(models):** Custom and imported models now merge correctly into filter lists for all available global providers (#1191). -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4284,7 +6159,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.6.5] — 2026-04-13 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4310,7 +6185,7 @@ We identified that **155 community PRs** across the entire project history (from - **Stabilization Settings:** Added persistence support for `lkgpEnabled` and `backgroundDegradation` settings, integrated into `instrumentation-node.ts` for improved lifecycle awareness (#1212) - **xxhash-wasm dependency:** Added `xxhash-wasm@^1.1.0` for CCH signing (xxHash64 with seed `0x6E52736AC806831E`) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4355,7 +6230,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.6.4] — 2026-04-12 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4384,12 +6259,12 @@ We identified that **155 community PRs** across the entire project history (from - **LKGP Standalone Strategy:** Implemented `lkgp` (Last Known Good Provider) as a fully functional standalone combo strategy. Previously, `lkgp` as a combo strategy silently fell through to `priority` ordering — the LKGP lookup only ran inside the `auto` engine. Now `strategy: "lkgp"` correctly queries the LKGP state, moves the last successful provider to the top of the target list, and saves the LKGP state after each successful request. Falls back to priority ordering when no LKGP state exists - **Unified Routing Rules & Model Aliases:** Consolidated the routing rules and model alias management controls into the Settings page, reducing fragmentation across the dashboard -### ⚡ Performance +### ⚡ 效能 - **Middleware Lazy Loading:** Refactored `src/proxy.ts` to lazy-import `apiAuth`, `db/settings`, and `modelSyncScheduler` modules, reducing middleware cold-start overhead. Added inline `isPublicApiRoute()` to avoid loading the full auth module for public routes - **E2E Auth Bypass:** Added `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` environment flag to bypass authentication gates for dashboard and management API routes during Playwright E2E test runs -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4462,7 +6337,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.6.3] — 2026-04-11 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4480,7 +6355,7 @@ We identified that **155 community PRs** across the entire project history (from - **OpenAI-Compatible Loose Validation:** Empty API keys can now be naturally submitted and saved for any `openai-compatible-*` providers (e.g. Pollinations, localized routes) directly in the UI instead of blocking save actions (#1152) - **Cloudflare Configuration:** Updated the provider schema and UI integration for Cloudflare AI to officially expose and support the backend `accountId` field securely without overrides (#1150) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4507,7 +6382,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.6.2] — 2026-04-11 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4527,7 +6402,7 @@ We identified that **155 community PRs** across the entire project history (from - **Documentation Refresh:** Updated README, ARCHITECTURE, FEATURES, AGENTS.md, and API_REFERENCE for v3.6.2 with accurate provider counts (100+), new executor list, and system API documentation - **Uninstall Guide:** Created comprehensive `docs/guides/UNINSTALL.md` covering clean uninstallation for all deployment methods (npm, Docker, Electron, source) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4546,7 +6421,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.6.1] — 2026-04-10 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4563,7 +6438,7 @@ We identified that **155 community PRs** across the entire project history (from - **OAuth Env Repair Action:** Added a "Repair env" button to the OAuth Providers dashboard that detects and restores missing OAuth client IDs from `.env.example` — with timestamped backup and append-only safety. Includes full 33-language i18n support and sanitized API responses (#1116, by @yart) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4582,7 +6457,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.6.0] — 2026-04-10 -### ✨ New Features & Analytics +### ✨ 新功能與分析 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4599,7 +6474,7 @@ We identified that **155 community PRs** across the entire project history (from - **Combo Smoke Test:** Raised the default token budget to 2048 to prevent truncation of thinking models during preflight checks, and fully randomized the arithmetic probe prompt to bypass deterministic caching from upstream relays (#1105) -### 🐛 Bug Fixes & Compliance +### 🔧 錯誤修正 & Compliance - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4619,7 +6494,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.5.9] — 2026-04-09 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4637,7 +6512,7 @@ We identified that **155 community PRs** across the entire project history (from - **Persistent Combo Ordering:** Drag combo cards by handle to reorder them in the dashboard; order is persisted to SQLite via a new `sort_order` column and `POST /api/combos/reorder` endpoint. Includes DB migration `020_combo_sort_order.sql` and JSON import preservation (#1095) - **Sidebar Group Reorder:** Moved "Logs" before "Health" in the System section and "Limits & Quotas" after "Cache" in the Primary section for a more logical navigation flow (#1095) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4659,7 +6534,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.5.8] — 2026-04-09 -### ✨ New Features & Analytics +### ✨ 新功能與分析 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4676,7 +6551,7 @@ We identified that **155 community PRs** across the entire project history (from - **Analytics Layout Redesign:** Replaced flat metrics with a responsive `CompactStatGrid`, grouping data visually across sections (#1089) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4700,7 +6575,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.5.7] — 2026-04-09 -### 🐛 Bug Fixes & Security +### 🔧 錯誤修正 & Security - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4718,7 +6593,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.5.6] — 2026-04-09 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4740,7 +6615,7 @@ We identified that **155 community PRs** across the entire project history (from - **Environment Auto-Sync:** Added `sync-env.mjs` to auto-generate and append `.env` from `.env.example` during installation, automatically generating cryptographic secrets on first run. - **Source Mode Dashboard Update:** Fixed real-time Source (git-checkout) updating in the dashboard, enabling secure, real-time update pipelines for non-NPM installations. -### 🐛 Bug Fixes & Security +### 🔧 錯誤修正 & Security - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4771,7 +6646,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.5.5] — 2026-04-08 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4790,7 +6665,7 @@ We identified that **155 community PRs** across the entire project history (from - **Context Relay Combo Strategy:** Added the new `context-relay` combo strategy with priority-style routing, structured handoff summary generation once quota usage reaches the warning threshold, and handoff injection after the next real account switch. - **Global Context Relay Defaults:** Added global Settings defaults plus combo-level configuration for `handoffThreshold`, `handoffModel`, and `handoffProviders`, so new or unconfigured combos can inherit the feature consistently. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4825,7 +6700,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.5.4] — 2026-04-07 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4845,7 +6720,7 @@ We identified that **155 community PRs** across the entire project history (from - **Non-Stream Aliases:** Added API support for explicit non-streaming aliases (`non_stream`, `disable_stream`, `disable_streaming`, `streaming=false`), normalized at the boundary before provider translation (#1036 — thanks @wlfonseca). - **Russian Dashboard Localization:** Comprehensive Russian translation for the dashboard UI, including fixes for 2 Ukrainian locale keys (#1003 — thanks @mercs2910). -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4865,7 +6740,7 @@ We identified that **155 community PRs** across the entire project history (from - **macOS Desktop App Startup:** Stabilized packaged macOS app launch by excluding desktop artifacts from the standalone bundle and improving launch path detection (#1004 — thanks @mercs2910). - **macOS Sidebar Layout:** Fixed macOS traffic light overlap, sidebar spacing, and button overflow in the Electron desktop app (#1001 — thanks @mercs2910). -### ⚡ Performance +### ⚡ 效能 - **Analytics Page Load:** Dramatically reduced analytics page load times (30s→1-2s for 50K entries) via date-filtered DB queries, parallel `Promise.all()` cost calculations, and merged 6 COUNT queries into a single CASE WHEN aggregate (#1038 — thanks @oyi77). @@ -4920,7 +6795,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.5.2] — 2026-04-05 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4939,7 +6814,7 @@ We identified that **155 community PRs** across the entire project history (from - **Resilience Engine Overhaul:** Integrated context overflow graceful fallbacks, proactive OAuth token detection, and empty-content emission prevention (#990). - **Context-Optimized Routing Strategy:** Added new intelligent routing capability to natively maximize context windows in automated combo deployments (#990). -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4959,7 +6834,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.5.1] — 2026-04-04 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -4978,7 +6853,7 @@ We identified that **155 community PRs** across the entire project history (from - **Provider Native Capabilities:** Added support for declaring and checking native API features (e.g. `systemInstructions_supported`) preventing failures by sanitizing invalid roles. Currently configured for Gemini Base and Antigravity OAuth providers. - **API Provider Advanced Settings:** Added per-connection custom `User-Agent` overrides for API-key provider connections. The override is stored in `providerSpecificData.customUserAgent` and now applies to validation probes and upstream execution requests. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -4994,7 +6869,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.5.0] — 2026-04-03 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5016,7 +6891,7 @@ We identified that **155 community PRs** across the entire project history (from - **MCP Extensibility:** Added and successfully registered the new `omniroute_web_search` MCP framework tool out of beta into production schemas (#951). - **Tokens Buffer Logic:** Added runtime configuration limits extending configurable input/output token buffers for precise Usage Tracking metrics (#959). -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5105,7 +6980,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.4.6] - 2026-04-02 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5124,7 +6999,7 @@ We identified that **155 community PRs** across the entire project history (from - **Dashboard UI:** Added standalone sidebar navigation for the new Memory and Skills modules (#926). - **i18n:** Added translation strings and layout mappings across 30 languages for the Memory and Skills namespaces. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5146,7 +7021,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.4.5] - 2026-04-02 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5165,7 +7040,7 @@ We identified that **155 community PRs** across the entire project history (from - **Qoder PAT Support:** Integrated Personal Access Tokens (PAT) support directly via the local `qodercli` transport instead of legacy remote `.cn` browser configurations (#913). - **Gemini 3.1 Pro Preview (GitHub):** Added `gemini-3.1-pro-preview` canonical explicit model support natively into the GitHub Copilot provider while preserving older routing aliases (#924). -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5188,7 +7063,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.4.4] - 2026-04-02 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5215,7 +7090,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.4.3] - 2026-04-02 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5239,7 +7114,7 @@ We identified that **155 community PRs** across the entire project history (from - **Model Enhancements:** Added explicit `contextLength` for all opencode-zen models. - **i18n & translations:** Integrated 33 language translations natively, including placeholder CI validations and Chinese documentation updates (#873, #869). -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5282,7 +7157,7 @@ We identified that **155 community PRs** across the entire project history (from > **BREAKING CHANGE: request logging, retention, and logging environment variables have been redesigned.** > On the first startup after upgrading, OmniRoute archives legacy request logs from `DATA_DIR/logs/`, legacy `DATA_DIR/call_logs/`, and `DATA_DIR/log.txt` into `DATA_DIR/log_archives/*.zip`, then removes the deprecated layout and switches to the new unified artifact format under `DATA_DIR/call_logs/`. -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5305,7 +7180,7 @@ We identified that **155 community PRs** across the entire project history (from - **Opencode-Zen Models:** Added 4 free models to opencode-zen registry (#854) - **Tests:** Added unit and E2E tests for settings toggles and bug fixes (#850) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5351,7 +7226,7 @@ We identified that **155 community PRs** across the entire project history (from - **Model Registry Update:** Injected `gpt-5.4-mini` into the Codex provider's array of models (#756) - **Provider Limit Tracking:** Track and display when provider rate limits were last refreshed per account (#843) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5378,7 +7253,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.3.9] - 2026-03-31 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5402,7 +7277,7 @@ We identified that **155 community PRs** across the entire project history (from - **Qoder Integration:** Native integration for Qoder AI natively replacing the legacy iFlow platform mappings (#660) - **Prompt Cache Tracking:** Added tracking capabilities and frontend visualization (Stats card) for semantic and prompt caching in the Dashboard UI -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5424,7 +7299,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.3.7] - 2026-03-30 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5443,7 +7318,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.3.6] - 2026-03-30 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5463,7 +7338,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.3.5] - 2026-03-30 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5481,7 +7356,7 @@ We identified that **155 community PRs** across the entire project history (from - **Gemini Quota Tracking:** Added real-time Gemini CLI quota tracking via the `retrieveUserQuota` API (PR #825) - **Cache Dashboard:** Enhanced the Cache Dashboard to display prompt cache metrics, 24h trends, and estimated cost savings (PR #824) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5500,7 +7375,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.3.4] - 2026-03-30 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5523,7 +7398,7 @@ We identified that **155 community PRs** across the entire project history (from - **Provider Diversity:** Implemented provider diversity scoring via Shannon entropy to improve load distribution. - **Auto-Disable Bounds:** Added an Auto-Disable Banned Accounts setting toggle to the Resilience dashboard. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5547,7 +7422,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.3.3] - 2026-03-29 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5568,7 +7443,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.3.2] - 2026-03-29 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5586,7 +7461,7 @@ We identified that **155 community PRs** across the entire project history (from - **Cloudflare Tunnels:** Cloudflare Quick Tunnel integration with dashboard controls (PR #772). - **Diagnostics:** Semantic cache bypass for combo live tests (PR #773). -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5606,7 +7481,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.3.1] - 2026-03-29 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5631,7 +7506,7 @@ We identified that **155 community PRs** across the entire project history (from - **Release Stabilization** — Finalized v3.2.9 release (combo diagnostics, quality gates, Gemini tool fix) and created missing git tag. Consolidated all staged changes into a single atomic release commit. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5655,7 +7530,7 @@ We identified that **155 community PRs** across the entire project history (from - **Combo Diagnostics** — Introduced a live test bypass flag (`forceLiveComboTest`) allowing administrators to execute real upstream health checks that bypass all local circuit-breaker and cooldown state mechanisms, enabling precise diagnostics during rolling outages (PR #759) - **Quality Gates** — Added automated response quality validation for combos and officially integrated `claude-4.6` model support into the core routing schemas (PR #762) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5678,7 +7553,7 @@ We identified that **155 community PRs** across the entire project history (from - **Docker Auto-Update UI** — Integrated a detached background update process for Docker Compose deployments. The Dashboard UI now seamlessly tracks update lifecycle events combining JSON REST responses with SSE streaming progress overlays for robust cross-environment reliability. - **Cache Analytics** — Repaired zero-metrics visualization mapping by migrating Semantic Cache telemetry logs directly into the centralized tracking SQLite module. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5715,7 +7590,7 @@ We identified that **155 community PRs** across the entire project history (from - **Strict Combo Testing (#735)** — Hardened the combo health check endpoint to require live text responses from models instead of just soft reachability signals. - **Streamed Detailed Logs (#734)** — Switched detailed request logging for SSE streams to reconstruct the final payload, saving immense amounts of SQLite database size and significantly cleaning up the UI. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5743,7 +7618,7 @@ We identified that **155 community PRs** across the entire project history (from - **Qoder AI Migration (#660)** — Completely migrated the legacy `iFlow` core provider onto `Qoder AI` maintaining stable API routing capabilities. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5765,7 +7640,7 @@ We identified that **155 community PRs** across the entire project history (from - **Provider Limits Quota UI (#728)** — Normalized quota limit logic and data labeling inside the Limits interface. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5785,7 +7660,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.2.2] — 2026-03-29 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5802,7 +7677,7 @@ We identified that **155 community PRs** across the entire project history (from - **Four-Stage Request Log Pipeline (#705)** — Refactored log persistence to save comprehensive payloads at four distinct pipeline stages: Client Request, Translated Provider Request, Provider Response, and Translated Client Response. Introduced `streamPayloadCollector` for robust SSE stream truncation and payload serialization. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5820,7 +7695,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.2.1] — 2026-03-29 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5837,7 +7712,7 @@ We identified that **155 community PRs** across the entire project history (from - **Global Fallback Provider (#689)** — When all combo models are exhausted (502/503), OmniRoute now attempts a configurable global fallback model before returning the error. Set `globalFallbackModel` in settings to enable. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5859,7 +7734,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.2.0] — 2026-03-28 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5878,7 +7753,7 @@ We identified that **155 community PRs** across the entire project history (from - **GLM Quota Tracking** — Added real-time usage and session quota tracking for the GLM Coding (Z.AI) provider (PR #698 by @christopher-s) - **Detailed Log Payloads** — Wired full four-stage pipeline payload capturing (original, translated, provider-response, streamed-deltas) directly into the UI (PR #705 by @rdself) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5901,7 +7776,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.1.10] — 2026-03-28 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5920,7 +7795,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.1.9] — 2026-03-28 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -5941,7 +7816,7 @@ We identified that **155 community PRs** across the entire project history (from - **Codex Auth Export** — Added Codex `auth.json` export and apply-local buttons for seamless CLI integration. - **Windsurf BYOK Notes** — Added official limitation warnings to the Windsurf CLI tool card documenting BYOK constraints. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5975,7 +7850,7 @@ We identified that **155 community PRs** across the entire project history (from | `tests/unit/t40-opencode-cli-tools-integration.test.mjs` | CLI tool integration tests | | `COVERAGE_PLAN.md` | Test coverage planning document | -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -5991,7 +7866,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.1.8] - 2026-03-27 -### 🐛 Bug Fixes & Features +### 🔧 錯誤修正 & Features - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6007,7 +7882,7 @@ We identified that **155 community PRs** across the entire project history (from - **Stability:** Patched streaming crashes related to the native Antigravity provider integration failing due to unhandled undefined state arrays (#684). - **Localization Sync:** Deployed a fully overhauled `i18n` synchronizer detecting missing nested JSON properties and retro-fitting 30 locales sequentially (#685).## [3.1.7] - 2026-03-27 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6026,7 +7901,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.1.6] — 2026-03-27 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6045,7 +7920,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.1.5] — 2026-03-27 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6067,7 +7942,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.1.4] — 2026-03-27 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6099,7 +7974,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.1.2] — 2026-03-26 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6122,7 +7997,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.1.1] — 2026-03-26 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -6140,7 +8015,7 @@ We identified that **155 community PRs** across the entire project history (from - **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 +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6163,7 +8038,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.1.0] — 2026-03-26 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -6181,7 +8056,7 @@ We identified that **155 community PRs** across the entire project history (from - **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 +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6207,7 +8082,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.0.9] — 2026-03-26 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6237,7 +8112,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.0.8] — 2026-03-25 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6260,7 +8135,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.0.7] — 2026-03-25 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6278,7 +8153,7 @@ We identified that **155 community PRs** across the entire project history (from - **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 +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -6305,7 +8180,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.0.6] — 2026-03-25 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6321,7 +8196,7 @@ We identified that **155 community PRs** across the entire project history (from - **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 +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -6346,7 +8221,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.0.5] — 2026-03-25 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -6367,7 +8242,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.0.4] — 2026-03-25 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6388,7 +8263,7 @@ We identified that **155 community PRs** across the entire project history (from ## [3.0.3] — 2026-03-25 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -6405,7 +8280,7 @@ We identified that **155 community PRs** across the entire project history (from - **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 +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6437,7 +8312,7 @@ We identified that **155 community PRs** across the entire project history (from - 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(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6625,7 +8500,7 @@ Full media generation playground at `/dashboard/media`: Image Generation, Video, --- -### 🐛 Bug Fixes (40+) +### 🔧 錯誤修正 (40+) - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6813,7 +8688,7 @@ docker pull diegosouzapw/omniroute:3.0.0 - `model-combo-mappings` (POST, PUT), `webhooks` (POST, PUT), `openapi/try` (POST) - CI `check:route-validation:t06` now passes: **176/176 routes validated** -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -6840,7 +8715,7 @@ docker pull diegosouzapw/omniroute:3.0.0 ## [3.0.0-rc.16] — 2026-03-24 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -6862,7 +8737,7 @@ docker pull diegosouzapw/omniroute:3.0.0 ## [3.0.0-rc.15] — 2026-03-24 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -6926,7 +8801,7 @@ docker pull diegosouzapw/omniroute:3.0.0 ## [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) @@ -6943,7 +8818,7 @@ docker pull diegosouzapw/omniroute:3.0.0 | **#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. @@ -6956,7 +8831,7 @@ docker pull diegosouzapw/omniroute:3.0.0 ## [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. @@ -6966,7 +8841,7 @@ docker pull diegosouzapw/omniroute:3.0.0 ## [3.0.0-rc.9] — 2026-03-23 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -6993,7 +8868,7 @@ docker pull diegosouzapw/omniroute:3.0.0 ## [3.0.0-rc.8] — 2026-03-23 -### 🔧 Bug Fixes & Improvements (Fallback, Quota & Budget) +### 🔧 錯誤修正 & 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. @@ -7020,7 +8895,7 @@ Both providers use the new `OpencodeExecutor` with multi-format routing (`/chat/ --- -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -7074,7 +8949,7 @@ OmniRoute now automatically refreshes model lists for connected providers every --- -### 🔧 Bug Fixes +### 🔧 錯誤修正 #### OAuth & Auth @@ -7141,7 +9016,7 @@ OmniRoute now automatically refreshes model lists for connected providers every ## [3.0.0-rc.6] - 2026-03-23 -### 🔧 Bug Fixes & Improvements (sub2api Gap Analysis — T01–T15) +### 🔧 錯誤修正 & 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. @@ -7162,7 +9037,7 @@ OmniRoute now automatically refreshes model lists for connected providers every ## [3.0.0-rc.5] - 2026-03-22 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -7192,7 +9067,7 @@ OmniRoute now automatically refreshes model lists for connected providers every ## [3.0.0-rc.4] - 2026-03-22 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -7215,7 +9090,7 @@ OmniRoute now automatically refreshes model lists for connected providers every ## [3.0.0-rc.3] - 2026-03-22 -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -7233,7 +9108,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **#529** — Provider icons now use [@lobehub/icons](https://github.com/lobehub/lobe-icons) with graceful PNG fallback and a `ProviderIcon` component (130+ providers supported) - **#488** — Auto-update model lists every 24h via `modelSyncScheduler` (configurable via `MODEL_SYNC_INTERVAL_HOURS`) -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **#537** — Gemini CLI OAuth: now shows clear actionable error when `GEMINI_OAUTH_CLIENT_SECRET` is missing in Docker/self-hosted deployments @@ -7241,7 +9116,7 @@ OmniRoute now automatically refreshes model lists for connected providers every ## [3.0.0-rc.2] - 2026-03-22 -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **#536** — LongCat AI key validation: fixed baseUrl (`api.longcat.chat/openai`) and authHeader (`Authorization: Bearer`) - **#535** — Pinned model override: `body.model` is now set to `pinnedModel` when context-cache protection detects a pinned model @@ -7251,7 +9126,7 @@ OmniRoute now automatically refreshes model lists for connected providers every ## [3.0.0-rc.1] - 2026-03-22 -### 🔧 Bug Fixes +### 🔧 錯誤修正 - **#521** — Login no longer gets stuck after skipping password setup (redirects to onboarding) - **#522** — API Manager: Removed misleading "Copy masked key" button (replaced with lock icon tooltip) @@ -7276,7 +9151,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: New OpenCode providers, embedding credentials fix, CLI masked key bug, CACHE_TAG_PATTERN fix. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7306,7 +9181,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Bug fixes — preserve Codex prompt cache key, fix tagContent JSON escaping, sync expired token status to DB. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7376,7 +9251,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Fix media transcription (Deepgram/HuggingFace Content-Type, language detection) and TTS error display. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7431,7 +9306,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Cross-platform machineId fix, per-API-key rate limits, streaming context cache, Alibaba DashScope, search analytics, ZWS v5, and 8 issues closed. -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -7451,7 +9326,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **feat(api)**: Per-API-key request-count limits — `max_requests_per_day` and `max_requests_per_minute` columns with in-memory sliding-window enforcement returning HTTP 429 (#452) - **feat(dev)**: ZWS v5 — HMR leak fix (485 DB connections → 1), memory 2.4GB → 195MB, `globalThis` singletons, Edge Runtime warning fix (@zhangqiang8vip) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7662,7 +9537,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Five community PRs — streaming call log fixes, Kiro compatibility, cache token analytics, Chinese translation, and configurable tool call IDs. -### ✨ Features +#### ✨ 新功能 - **feat(logs)**: Call log response content now correctly accumulated from raw provider chunks (OpenAI/Claude/Gemini) before translation, fixing empty response payloads in streaming mode (#470, @zhangqiang8vip) - **feat(providers)**: Per-model configurable 9-char tool call ID normalization (Mistral-style) — only models with the option enabled get truncated IDs (#470) @@ -7670,7 +9545,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **feat(dashboard)**: Response-first layout in request log detail UI (#470) - **feat(i18n)**: Improved Chinese (zh-CN) translation — complete retranslation (#475, @only4copilot) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7692,7 +9567,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Bailian Coding Plan provider with editable base URLs, plus community contributions for Alibaba Cloud and Kimi Coding. -### ✨ Features +#### ✨ 新功能 - **feat(providers)**: Added Bailian Coding Plan (`bailian-coding-plan`) — Alibaba Model Studio with Anthropic-compatible API. Static catalog of 8 models including Qwen3.5 Plus, Qwen3 Coder, MiniMax M2.5, GLM 5, and Kimi K2.5. Includes custom auth validation (400=valid, 401/403=invalid) (#467, @Mind-Dragon) - **feat(admin)**: Editable default URL in Provider Admin create/edit flows — users can configure custom base URLs per connection. Persisted in `providerSpecificData.baseUrl` with Zod schema validation rejecting non-http(s) schemes (#467) @@ -7707,12 +9582,12 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Two new community-contributed providers (Alibaba Cloud Coding, Kimi Coding API-key) and Docker pino fix. -### ✨ Features +#### ✨ 新功能 - **feat(providers)**: Added Alibaba Cloud Coding Plan support with two OpenAI-compatible endpoints — `alicode` (China) and `alicode-intl` (International), each with 8 models (#465, @dtk1985) - **feat(providers)**: Added dedicated `kimi-coding-apikey` provider path — API-key-based Kimi Coding access is no longer forced through OAuth-only `kimi-coding` route. Includes registry, constants, models API, config, and validation test (#463, @Mind-Dragon) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7732,11 +9607,11 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Codex responses subpath passthrough natively supported, Windows MITM crash fixed, and Combos agent schemas adjusted. -### ✨ Features +#### ✨ 新功能 - **feat(codex)**: Native responses subpath passthrough for Codex — natively routes `POST /v1/responses/compact` to Codex upstream, maintaining Claude Code compatibility without stripping the `/compact` suffix (#457) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7757,7 +9632,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Budget save bug + combo agent features UI + omniModel tag security fix. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7772,7 +9647,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **fix(budget)**: "Save Limits" no longer returns 422 — `warningThreshold` is now correctly sent as fraction (0–1) instead of percentage (0–100) (#451) - **fix(combos)**: `` internal cache tag is now stripped before forwarding requests to providers, preventing cache session breaks (#454) -### ✨ Features +#### ✨ 新功能 - **feat(combos)**: Agent Features section added to combo create/edit modal — expose `system_message` override, `tool_filter_regex`, and `context_cache_protection` directly from the dashboard (#454) @@ -7782,7 +9657,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Docker pino crash, Codex CLI responses worker fix, package-lock sync. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7807,7 +9682,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: UX improvements and Windows CLI healthcheck fix. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7837,7 +9712,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - Local provider_nodes routing for `/v1/rerank` - 30+ i18n keys in search namespace -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7860,7 +9735,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Codex direct API quota fallback fix. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7883,7 +9758,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Light mode UI contrast fixes. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -7905,7 +9780,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: Unified web search routing (POST /v1/search) with 5 providers + Next.js 16.1.7 security fixes (6 CVEs). -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -7988,7 +9863,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **feat(migrations)**: New column `latency_p50` in `combo_metrics` table — zero-breaking, safe for existing users -### 🐛 Bug Fixes / Closures +### 🔧 錯誤修正 / Closures - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8008,7 +9883,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Windows fix: better-sqlite3 prebuilt download without node-gyp/Python/MSVC (#426). -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8028,7 +9903,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > CI fixes (t11 any-budget), bug fix #409 (file attachments via Copilot+Claude), release workflow correction. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8058,7 +9933,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **005_combo_agent_fields.sql**: `ALTER TABLE combos ADD COLUMN system_message TEXT DEFAULT NULL`, `tool_filter_regex TEXT DEFAULT NULL`, `context_cache_protection INTEGER DEFAULT 0` - **006_detailed_request_logs.sql**: New `request_detail_logs` table with 500-entry ring-buffer trigger, opt-in via settings toggle -### ✨ Features +#### ✨ 新功能 - **feat(combo)**: System Message Override per Combo (#399 — `system_message` field replaces or injects system prompt before forwarding to provider) - **feat(combo)**: Tool Filter Regex per Combo (#399 — `tool_filter_regex` keeps only tools matching pattern; supports OpenAI + Anthropic formats) @@ -8073,14 +9948,14 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: SSE improvements, local provider_nodes extensions, proxy registry, Claude passthrough fixes. -### ✨ Features +#### ✨ 新功能 - **feat(health)**: Background health check for local `provider_nodes` with exponential backoff (30s→300s) and `Promise.allSettled` to avoid blocking (#423, @Regis-RCR) - **feat(embeddings)**: Route `/v1/embeddings` to local `provider_nodes` — `buildDynamicEmbeddingProvider()` with hostname validation (#422, @Regis-RCR) - **feat(audio)**: Route TTS/STT to local `provider_nodes` — `buildDynamicAudioProvider()` with SSRF protection (#416, @Regis-RCR) - **feat(proxy)**: Proxy registry, management APIs, and quota-limit generalization (#429, @Regis-RCR) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8111,7 +9986,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Hotfix: Turbopack/Docker compatibility — remove `node:` protocol from all `src/` imports. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8132,7 +10007,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: reasoning model param filtering, local provider 404 fix, Kilo Gateway provider, dependency bumps. -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -8149,7 +10024,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **feat(api)**: Added **Kilo Gateway** (`api.kilo.ai`) as a new API Key provider (alias `kg`) — 335+ models, 6 free models, 3 auto-routing models (`kilo-auto/frontier`, `kilo-auto/balanced`, `kilo-auto/free`). Passthrough models supported via `/api/gateway/models` endpoint. (PR #408 by @Regis-RCR) -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8175,7 +10050,7 @@ OmniRoute now automatically refreshes model lists for connected providers every ## [2.6.4] — 2026-03-17 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8200,7 +10075,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: zod/pino hash-strip baked into build pipeline, Synthetic provider added, VPS PM2 path corrected. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8215,7 +10090,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **fix(build)**: Turbopack hash-strip now runs at **compile time** for ALL packages — not just `better-sqlite3`. Step 5.6 in `prepublish.mjs` walks every `.js` in `app/.next/server/` and strips the 16-char hex suffix from any hashed `require()`. Fixes `zod-dcb22c...`, `pino-...`, etc. MODULE_NOT_FOUND on global npm installs. Closes #398 - **fix(deploy)**: PM2 on both VPS was pointing to stale git-clone directories. Reconfigured to `app/server.js` in the npm global package. Updated `/deploy-vps` workflow to use `npm pack + scp` (npm registry rejects 299MB packages). -### ✨ Features +#### ✨ 新功能 - **feat(provider)**: Synthetic ([synthetic.new](https://synthetic.new)) — privacy-focused OpenAI-compatible inference. `passthroughModels: true` for dynamic HuggingFace model catalog. Initial models: Kimi K2.5, MiniMax M2.5, GLM 4.7, DeepSeek V3.2. (PR #404 by @Regis-RCR) @@ -8230,7 +10105,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Sprint: module hashing fully fixed, 2 PRs merged (Anthropic tools filter + custom endpoint paths), Alibaba Cloud DashScope provider added, 3 stale issues closed. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8245,7 +10120,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **fix(build)**: Extended webpack `externals` hash-strip to cover ALL `serverExternalPackages`, not just `better-sqlite3`. Next.js 16 Turbopack hashes `zod`, `pino`, and every other server-external package into names like `zod-dcb22c6336e0bc69` that don't exist in `node_modules` at runtime. A HASH_PATTERN regex catch-all now strips the 16-char suffix and falls back to the base package name. Also added `NEXT_PRIVATE_BUILD_WORKER=0` in `prepublish.mjs` to reinforce webpack mode, plus a post-build scan that reports any remaining hashed refs. (#396, #398, PR #403) - **fix(chat)**: Anthropic-format tool names (`tool.name` without `.function` wrapper) were silently dropped by the empty-name filter introduced in #346. LiteLLM proxies requests with `anthropic/` prefix in Anthropic Messages API format, causing all tools to be filtered and Anthropic to return `400: tool_choice.any may only be specified while providing tools`. Fixed by falling back to `tool.name` when `tool.function.name` is absent. Added 8 regression unit tests. (PR #397) -### ✨ Features +#### ✨ 新功能 - **feat(api)**: Custom endpoint paths for OpenAI-compatible provider nodes — configure `chatPath` and `modelsPath` per node (e.g. `/v4/chat/completions`) in the provider connection UI. Includes a DB migration (`003_provider_node_custom_paths.sql`) and URL path sanitization (no `..` traversal, must start with `/`). (PR #400) - **feat(provider)**: Alibaba Cloud DashScope added as OpenAI-compatible provider. International endpoint: `dashscope-intl.aliyuncs.com/compatible-mode/v1`. 12 models: `qwen-max`, `qwen-plus`, `qwen-turbo`, `qwen3-coder-plus/flash`, `qwq-plus`, `qwq-32b`, `qwen3-32b`, `qwen3-235b-a22b`. Auth: Bearer API key. @@ -8262,7 +10137,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Critical startup fix: v2.6.0 global npm installs crashed with a 500 error due to a Turbopack/webpack module-name hashing bug in the Next.js 16 instrumentation hook. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8287,7 +10162,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Issue resolution sprint: 4 bugs fixed, logs UX improved, Kiro credit tracking added. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8304,7 +10179,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **fix(oauth)**: Qoder (and other providers that redirect to their own UI) no longer leave the OAuth modal stuck at "Waiting for Authorization" — popup-closed detector auto-transitions to manual URL input mode (#344) - **fix(logs)**: Request log table is now readable in light mode — status badges, token counts, and combo tags use adaptive `dark:` color classes (#378) -### ✨ Features +#### ✨ 新功能 - **feat(kiro)**: Kiro credit tracking added to usage fetcher — queries `getUserCredits` from AWS CodeWhisperer endpoint (#337) @@ -8318,7 +10193,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Codex native passthrough fix + route body validation hardening. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8340,7 +10215,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Build fix: restore VPS connectivity broken by v2.5.7 incomplete publish. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8360,7 +10235,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Media playground error handling fixes. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8382,7 +10257,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Critical security/auth fixes: Antigravity OAuth broken + JWT sessions lost after restart. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8403,7 +10278,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Model list dedup fix, Electron standalone build hardening, and Kiro credit tracking. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8418,7 +10293,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **fix(models) #380**: `GET /api/models` now includes provider aliases when building the active-provider filter — models for `claude` (alias `cc`) and `github` (alias `gh`) were always shown regardless of whether a connection was configured, because `PROVIDER_MODELS` keys are aliases but DB connections are stored under provider IDs. Fixed by expanding each active provider ID to also include its alias via `PROVIDER_ID_TO_ALIAS`. Closes #353. - **fix(electron) #379**: New `scripts/prepare-electron-standalone.mjs` stages a dedicated `/.next/electron-standalone` bundle before Electron packaging. Aborts with a clear error if `node_modules` is a symlink (electron-builder would ship a runtime dependency on the build machine). Cross-platform path sanitization via `path.basename`. By @kfiramar. -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -8439,7 +10314,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Logger startup fix, login bootstrap security fix, and dev HMR reliability improvement. CI infrastructure hardened. -### 🐛 Bug Fixes (PRs #374, #375, #376 by @kfiramar) +### 🔧 錯誤修正 (PRs #374, #375, #376 by @kfiramar) - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8465,7 +10340,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Critical bugfixes: DB schema migration, startup env loading, provider error state clearing, and i18n tooltip fix. Code quality improvements on top of each PR. -### 🐛 Bug Fixes (PRs #369, #371, #372, #373 by @kfiramar) +### 🔧 錯誤修正 (PRs #369, #371, #372, #373 by @kfiramar) - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8490,7 +10365,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Codex account quota policy with auto-rotation, fast tier toggle, gpt-5.4 model, and analytics label fix. -### ✨ New Features (PRs #366, #367, #368) +### ✨ 新功能 (PRs #366, #367, #368) - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -8509,7 +10384,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **Codex Fast Tier Toggle (PR #367)**: Dashboard → Settings → Codex Service Tier. Default-off toggle injects `service_tier: "flex"` only for Codex requests, reducing cost ~80%. Full stack: UI tab + API endpoint + executor + translator + startup restore. - **gpt-5.4 Model (PR #368)**: Adds `cx/gpt-5.4` and `codex/gpt-5.4` to the Codex model registry. Regression test included. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8525,7 +10400,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Major release: strict-random routing strategy, API key access controls, connection groups, external pricing sync, and critical bug fixes for thinking models, combo testing, and tool name validation. -### ✨ New Features (PRs #363 & #365) +### ✨ 新功能 (PRs #363 & #365) - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -8546,7 +10421,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **External Pricing Sync (LiteLLM)**: 3-tier pricing resolution (user overrides → synced → defaults). Opt-in via `PRICING_SYNC_ENABLED=true`. MCP tool `omniroute_sync_pricing`. 23 new tests. - **i18n**: 30 languages updated with strict-random strategy, API key management strings. pt-BR fully translated. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8568,7 +10443,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > API Key Round-Robin support for multi-key provider setups, and confirmation of wildcard routing and quota window rolling already in place. -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -8592,7 +10467,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > UI polish, routing strategy additions, and graceful error handling for usage limits. -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -8611,7 +10486,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **Free Stack Preset Models**: Creating a combo with the Free Stack template now auto-fills 7 best-in-class free provider models (Gemini CLI, Kiro, Qoder×2, Qwen, NVIDIA NIM, Groq). Users just activate the providers and get a $0/month combo out-of-the-box. - **Wider Combo Modal**: Create/Edit combo modal now uses `max-w-4xl` for comfortable editing of large combos. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8629,7 +10504,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > Multiple improvements from community issue analysis, new provider support, bug fixes for token tracking, model routing, and streaming reliability. -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -8653,7 +10528,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **Round-Robin State Management (PR #349)**: Enhanced round-robin logic to handle excluded accounts and maintain rotation state correctly. - **Clipboard UX (PR #360)**: Hardened clipboard operations with fallback for non-secure contexts; Claude tool normalization improvements. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8705,7 +10580,7 @@ OmniRoute now automatically refreshes model lists for connected providers every > **Major release** — Free Stack ecosystem, transcription playground overhaul, 44+ providers, comprehensive free tier documentation, and UI improvements across the board. -### ✨ Features +#### ✨ 新功能 - **Combos: Free Stack template** — New 4th template "Free Stack ($0)" using round-robin across Kiro + Qoder + Qwen + Gemini CLI. Suggests the pre-built zero-cost combo on first use. - **Media/Transcription: Deepgram as default** — Deepgram (Nova 3, $200 free) is now the default transcription provider. AssemblyAI ($50 free) and Groq Whisper (free forever) shown with free credit badges. @@ -8726,14 +10601,14 @@ OmniRoute now automatically refreshes model lists for connected providers every ## [2.3.15] - 2026-03-13 -### ✨ Features +#### ✨ 新功能 - **Auto-Combo Dashboard (Tier Priority)**: Added `🏷️ Tier` as the 7th scoring factor label in the `/dashboard/auto-combo` factor breakdown display — all 7 Auto-Combo scoring factors are now visible. - **i18n — autoCombo section**: Added 20 new translation keys for the Auto-Combo dashboard (`title`, `status`, `modePack`, `providerScores`, `factorTierPriority`, etc.) to all 30 language files. ## [2.3.14] - 2026-03-13 -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. @@ -8759,7 +10634,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **Tier Scoring (API + Validation)**: Added `tierPriority` (weight `0.05`) to the `ScoringWeights` Zod schema and the `combos/auto` API route — the 7th scoring factor is now fully accepted by the REST API and validated on input. `stability` weight adjusted from `0.10` to `0.05` to keep total sum = `1.0`. -### ✨ New Features +### ✨ 新功能 - **feat(docs):** integrate multi-page documentation into OmniRoute dashboard (#1969) - **feat(settings):** add request body limit setting (#1968) @@ -8779,7 +10654,7 @@ OmniRoute now automatically refreshes model lists for connected providers every - **Configurable API Bridge Timeout**: `API_BRIDGE_PROXY_TIMEOUT_MS` env var lets operators tune the proxy timeout (default 30s). Fixes 504 errors on slow upstream responses. (#332) - **Star History**: Replaced star-history.com widget with starchart.cc (`?variant=adaptive`) in all 30 READMEs — adapts to light/dark theme, real-time updates. -### 🐛 Bug Fixes +### 🔧 錯誤修正 - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. - **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. diff --git a/docs/i18n/zh-TW/CODE_OF_CONDUCT.md b/docs/i18n/zh-TW/CODE_OF_CONDUCT.md index 67bd4ea223..49d5b30f48 100644 --- a/docs/i18n/zh-TW/CODE_OF_CONDUCT.md +++ b/docs/i18n/zh-TW/CODE_OF_CONDUCT.md @@ -1,132 +1,88 @@ -# Contributor Covenant Code of Conduct (中文 (繁體)) +# Contributor Covenant 行為準則 (中文 (繁體)) -🌐 **Languages:** 🇺🇸 [English](../../../CODE_OF_CONDUCT.md) · 🇸🇦 [ar](../ar/CODE_OF_CONDUCT.md) · 🇧🇬 [bg](../bg/CODE_OF_CONDUCT.md) · 🇧🇩 [bn](../bn/CODE_OF_CONDUCT.md) · 🇨🇿 [cs](../cs/CODE_OF_CONDUCT.md) · 🇩🇰 [da](../da/CODE_OF_CONDUCT.md) · 🇩🇪 [de](../de/CODE_OF_CONDUCT.md) · 🇪🇸 [es](../es/CODE_OF_CONDUCT.md) · 🇮🇷 [fa](../fa/CODE_OF_CONDUCT.md) · 🇫🇮 [fi](../fi/CODE_OF_CONDUCT.md) · 🇫🇷 [fr](../fr/CODE_OF_CONDUCT.md) · 🇮🇳 [gu](../gu/CODE_OF_CONDUCT.md) · 🇮🇱 [he](../he/CODE_OF_CONDUCT.md) · 🇮🇳 [hi](../hi/CODE_OF_CONDUCT.md) · 🇭🇺 [hu](../hu/CODE_OF_CONDUCT.md) · 🇮🇩 [id](../id/CODE_OF_CONDUCT.md) · 🇮🇹 [it](../it/CODE_OF_CONDUCT.md) · 🇯🇵 [ja](../ja/CODE_OF_CONDUCT.md) · 🇰🇷 [ko](../ko/CODE_OF_CONDUCT.md) · 🇮🇳 [mr](../mr/CODE_OF_CONDUCT.md) · 🇲🇾 [ms](../ms/CODE_OF_CONDUCT.md) · 🇳🇱 [nl](../nl/CODE_OF_CONDUCT.md) · 🇳🇴 [no](../no/CODE_OF_CONDUCT.md) · 🇵🇭 [phi](../phi/CODE_OF_CONDUCT.md) · 🇵🇱 [pl](../pl/CODE_OF_CONDUCT.md) · 🇵🇹 [pt](../pt/CODE_OF_CONDUCT.md) · 🇧🇷 [pt-BR](../pt-BR/CODE_OF_CONDUCT.md) · 🇷🇴 [ro](../ro/CODE_OF_CONDUCT.md) · 🇷🇺 [ru](../ru/CODE_OF_CONDUCT.md) · 🇸🇰 [sk](../sk/CODE_OF_CONDUCT.md) · 🇸🇪 [sv](../sv/CODE_OF_CONDUCT.md) · 🇰🇪 [sw](../sw/CODE_OF_CONDUCT.md) · 🇮🇳 [ta](../ta/CODE_OF_CONDUCT.md) · 🇮🇳 [te](../te/CODE_OF_CONDUCT.md) · 🇹🇭 [th](../th/CODE_OF_CONDUCT.md) · 🇹🇷 [tr](../tr/CODE_OF_CONDUCT.md) · 🇺🇦 [uk-UA](../uk-UA/CODE_OF_CONDUCT.md) · 🇵🇰 [ur](../ur/CODE_OF_CONDUCT.md) · 🇻🇳 [vi](../vi/CODE_OF_CONDUCT.md) · 🇨🇳 [zh-CN](../zh-CN/CODE_OF_CONDUCT.md) +🌐 **語言:** 🇺🇸 [English](../../../CODE_OF_CONDUCT.md) · 🇸🇦 [ar](../ar/CODE_OF_CONDUCT.md) · 🇧🇬 [bg](../bg/CODE_OF_CONDUCT.md) · 🇧🇩 [bn](../bn/CODE_OF_CONDUCT.md) · 🇨🇿 [cs](../cs/CODE_OF_CONDUCT.md) · 🇩🇰 [da](../da/CODE_OF_CONDUCT.md) · 🇩🇪 [de](../de/CODE_OF_CONDUCT.md) · 🇪🇸 [es](../es/CODE_OF_CONDUCT.md) · 🇮🇷 [fa](../fa/CODE_OF_CONDUCT.md) · 🇫🇮 [fi](../fi/CODE_OF_CONDUCT.md) · 🇫🇷 [fr](../fr/CODE_OF_CONDUCT.md) · 🇮🇳 [gu](../gu/CODE_OF_CONDUCT.md) · 🇮🇱 [he](../he/CODE_OF_CONDUCT.md) · 🇮🇳 [hi](../hi/CODE_OF_CONDUCT.md) · 🇭🇺 [hu](../hu/CODE_OF_CONDUCT.md) · 🇮🇩 [id](../id/CODE_OF_CONDUCT.md) · 🇮🇹 [it](../it/CODE_OF_CONDUCT.md) · 🇯🇵 [ja](../ja/CODE_OF_CONDUCT.md) · 🇰🇷 [ko](../ko/CODE_OF_CONDUCT.md) · 🇮🇳 [mr](../mr/CODE_OF_CONDUCT.md) · 🇲🇾 [ms](../ms/CODE_OF_CONDUCT.md) · 🇳🇱 [nl](../nl/CODE_OF_CONDUCT.md) · 🇳🇴 [no](../no/CODE_OF_CONDUCT.md) · 🇵🇭 [phi](../phi/CODE_OF_CONDUCT.md) · 🇵🇱 [pl](../pl/CODE_OF_CONDUCT.md) · 🇵🇹 [pt](../pt/CODE_OF_CONDUCT.md) · 🇧🇷 [pt-BR](../pt-BR/CODE_OF_CONDUCT.md) · 🇷🇴 [ro](../ro/CODE_OF_CONDUCT.md) · 🇷🇺 [ru](../ru/CODE_OF_CONDUCT.md) · 🇸🇰 [sk](../sk/CODE_OF_CONDUCT.md) · 🇸🇪 [sv](../sv/CODE_OF_CONDUCT.md) · 🇰🇪 [sw](../sw/CODE_OF_CONDUCT.md) · 🇮🇳 [ta](../ta/CODE_OF_CONDUCT.md) · 🇮🇳 [te](../te/CODE_OF_CONDUCT.md) · 🇹🇭 [th](../th/CODE_OF_CONDUCT.md) · 🇹🇷 [tr](../tr/CODE_OF_CONDUCT.md) · 🇺🇦 [uk-UA](../uk-UA/CODE_OF_CONDUCT.md) · 🇵🇰 [ur](../ur/CODE_OF_CONDUCT.md) · 🇻🇳 [vi](../vi/CODE_OF_CONDUCT.md) · 🇨🇳 [zh-CN](../zh-CN/CODE_OF_CONDUCT.md) --- -## Our Pledge +## 我們的承諾 -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. +我們身為成員、貢獻者與領導者,承諾讓參與社群的體驗不受騷擾,無論年齡、體型、明顯或不明顯的障礙、種族、性別特徵、性別認同與表現、經驗程度、教育程度、社經地位、國籍、外貌、種族、宗教或性取向。 -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. +我們承諾以行動與互動,促進一個開放、友善、多元、包容且健康的社群。 -## Our Standards +## 我們的標準 -Examples of behavior that contributes to a positive environment for our -community include: +有助於為社群營造正面環境的行為範例包括: -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community +- 對他人展現同理心與善意 +- 尊重不同的意見、觀點與經驗 +- 提供並優雅地接受建設性反饋 +- 承擔責任,向受我們錯誤影響的人道歉,並從經驗中學習 +- 關注的不僅是我們個人,而是整個社群的最佳利益 -Examples of unacceptable behavior include: +不可接受的行為範例包括: -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting +- 使用帶有性暗示的語言或圖像,以及任何形式的性關注或性誘惑 +- 挑釁、侮辱或貶損的評論,以及人身或政治攻擊 +- 公開或私下騷擾 +- 未經明確許可,發布他人的私人資訊(如地址或電子郵件) +- 其他在專業環境中可被合理視為不恰當的行為 -## Enforcement Responsibilities +## 執行責任 -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. +社群領導者負責明確並執行我們對可接受行為的標準,並對他們認為不恰當、具威脅性、冒犯性或有害的任何行為採取適當且公正的糾正措施。 -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. +社群領導者有權移除、編輯或拒絕不符合本行為準則的評論、提交、程式碼、Wiki 編輯、問題及其他貢獻,並將在適當時說明管理決策的理由。 -## Scope +## 適用範圍 -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. +本行為準則適用於所有社群空間,也適用於個人在公共場合正式代表社群的情況。代表社群的範例包括使用官方電子郵件地址、通過官方社交媒體帳號發文,或在線上或線下活動中擔任指定代表。 -## Enforcement +## 執行 -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -. -All complaints will be reviewed and investigated promptly and fairly. +可向負責執行的社群領導者舉報濫用、騷擾或其他不可接受的行為,請透過以下方式聯絡: +- 開啟私人安全性公告: +- 或發送郵件給維護者:diegosouza.pw@outlook.com +- 針對安全敏感性事件,請參閱 [`SECURITY.md`](SECURITY.md) -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. +所有投訴都將被及時且公正地審查與調查。 -## Enforcement Guidelines +所有社群領導者都有義務尊重事件舉報人的隱私與安全。 -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: +## 執行指引 -### 1. Correction +社群領導者將遵循以下社群影響指引,確定對他們認為違反本行為準則的行為所應採取的後果: -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. +### 1. 更正 -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. +**社群影響:** 使用不當語言或其他被視為不專業或不受社群歡迎的行為。 -### 2. Warning +**後果:** 社群領導者發出私下的書面警告,說明違規的性質,並解釋為何該行為不當。可能要求公開道歉。 -**Community Impact**: A violation through a single incident or series -of actions. +### 2. 警告 -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. +**社群影響:** 透過單一事件或一系列行為造成的違規。 -### 3. Temporary Ban +**後果:** 發出警告,說明持續行為的後果。在特定期間內,不得與相關人員互動,包括未經要求的互動以及與行為準則執行者的互動。這包括避免在社群空間以及社交媒體等外部管道進行互動。違反這些條款可能導致暫時或永久禁止。 -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. +### 3. 暫時禁止 -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. +**社群影響:** 嚴重違反社群標準,包括持續的不當行為。 -### 4. Permanent Ban +**後果:** 在特定期間內,暫時禁止與社群進行任何形式的互動或公開交流。在此期間,不得與相關人員進行公開或私下的互動,包括未經要求的互動以及與行為準則執行者的互動。違反這些條款可能導致永久禁止。 -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. +### 4. 永久禁止 -**Consequence**: A permanent ban from any sort of public interaction within -the community. +**社群影響:** 表現出違反社群標準的模式,包括持續的不當行為、騷擾個人,或對某類個人進行攻擊或貶低。 -## Attribution +**後果:** 永久禁止在社群內進行任何形式的公開互動。 -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +## 歸屬 -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). +本行為準則改編自 [Contributor Covenant][homepage] 2.1 版,可於 https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 查閱。 + +社群影響指引參考了 [Mozilla 的行為準則執行階梯](https://github.com/mozilla/diversity)。 [homepage]: https://www.contributor-covenant.org -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +有關本行為準則常見問題的解答,請參閱 https://www.contributor-covenant.org/faq。翻譯版本可於 https://www.contributor-covenant.org/translations 查閱。 diff --git a/docs/i18n/zh-TW/CONTRIBUTING.md b/docs/i18n/zh-TW/CONTRIBUTING.md index 77a21a2b77..5046ff3752 100644 --- a/docs/i18n/zh-TW/CONTRIBUTING.md +++ b/docs/i18n/zh-TW/CONTRIBUTING.md @@ -1,22 +1,20 @@ -# Contributing to OmniRoute (中文 (繁體)) +# 貢獻 OmniRoute (中文 (繁體)) -🌐 **Languages:** 🇺🇸 [English](../../../CONTRIBUTING.md) · 🇸🇦 [ar](../ar/CONTRIBUTING.md) · 🇧🇬 [bg](../bg/CONTRIBUTING.md) · 🇧🇩 [bn](../bn/CONTRIBUTING.md) · 🇨🇿 [cs](../cs/CONTRIBUTING.md) · 🇩🇰 [da](../da/CONTRIBUTING.md) · 🇩🇪 [de](../de/CONTRIBUTING.md) · 🇪🇸 [es](../es/CONTRIBUTING.md) · 🇮🇷 [fa](../fa/CONTRIBUTING.md) · 🇫🇮 [fi](../fi/CONTRIBUTING.md) · 🇫🇷 [fr](../fr/CONTRIBUTING.md) · 🇮🇳 [gu](../gu/CONTRIBUTING.md) · 🇮🇱 [he](../he/CONTRIBUTING.md) · 🇮🇳 [hi](../hi/CONTRIBUTING.md) · 🇭🇺 [hu](../hu/CONTRIBUTING.md) · 🇮🇩 [id](../id/CONTRIBUTING.md) · 🇮🇹 [it](../it/CONTRIBUTING.md) · 🇯🇵 [ja](../ja/CONTRIBUTING.md) · 🇰🇷 [ko](../ko/CONTRIBUTING.md) · 🇮🇳 [mr](../mr/CONTRIBUTING.md) · 🇲🇾 [ms](../ms/CONTRIBUTING.md) · 🇳🇱 [nl](../nl/CONTRIBUTING.md) · 🇳🇴 [no](../no/CONTRIBUTING.md) · 🇵🇭 [phi](../phi/CONTRIBUTING.md) · 🇵🇱 [pl](../pl/CONTRIBUTING.md) · 🇵🇹 [pt](../pt/CONTRIBUTING.md) · 🇧🇷 [pt-BR](../pt-BR/CONTRIBUTING.md) · 🇷🇴 [ro](../ro/CONTRIBUTING.md) · 🇷🇺 [ru](../ru/CONTRIBUTING.md) · 🇸🇰 [sk](../sk/CONTRIBUTING.md) · 🇸🇪 [sv](../sv/CONTRIBUTING.md) · 🇰🇪 [sw](../sw/CONTRIBUTING.md) · 🇮🇳 [ta](../ta/CONTRIBUTING.md) · 🇮🇳 [te](../te/CONTRIBUTING.md) · 🇹🇭 [th](../th/CONTRIBUTING.md) · 🇹🇷 [tr](../tr/CONTRIBUTING.md) · 🇺🇦 [uk-UA](../uk-UA/CONTRIBUTING.md) · 🇵🇰 [ur](../ur/CONTRIBUTING.md) · 🇻🇳 [vi](../vi/CONTRIBUTING.md) · 🇨🇳 [zh-CN](../zh-CN/CONTRIBUTING.md) +🌐 **語言:** 🇺🇸 [English](../../../CONTRIBUTING.md) · 🇸🇦 [ar](../ar/CONTRIBUTING.md) · 🇧🇬 [bg](../bg/CONTRIBUTING.md) · 🇧🇩 [bn](../bn/CONTRIBUTING.md) · 🇨🇿 [cs](../cs/CONTRIBUTING.md) · 🇩🇰 [da](../da/CONTRIBUTING.md) · 🇩🇪 [de](../de/CONTRIBUTING.md) · 🇪🇸 [es](../es/CONTRIBUTING.md) · 🇮🇷 [fa](../fa/CONTRIBUTING.md) · 🇫🇮 [fi](../fi/CONTRIBUTING.md) · 🇫🇷 [fr](../fr/CONTRIBUTING.md) · 🇮🇳 [gu](../gu/CONTRIBUTING.md) · 🇮🇱 [he](../he/CONTRIBUTING.md) · 🇮🇳 [hi](../hi/CONTRIBUTING.md) · 🇭🇺 [hu](../hu/CONTRIBUTING.md) · 🇮🇩 [id](../id/CONTRIBUTING.md) · 🇮🇹 [it](../it/CONTRIBUTING.md) · 🇯🇵 [ja](../ja/CONTRIBUTING.md) · 🇰🇷 [ko](../ko/CONTRIBUTING.md) · 🇮🇳 [mr](../mr/CONTRIBUTING.md) · 🇲🇾 [ms](../ms/CONTRIBUTING.md) · 🇳🇱 [nl](../nl/CONTRIBUTING.md) · 🇳🇴 [no](../no/CONTRIBUTING.md) · 🇵🇭 [phi](../phi/CONTRIBUTING.md) · 🇵🇱 [pl](../pl/CONTRIBUTING.md) · 🇵🇹 [pt](../pt/CONTRIBUTING.md) · 🇧🇷 [pt-BR](../pt-BR/CONTRIBUTING.md) · 🇷🇴 [ro](../ro/CONTRIBUTING.md) · 🇷🇺 [ru](../ru/CONTRIBUTING.md) · 🇸🇰 [sk](../sk/CONTRIBUTING.md) · 🇸🇪 [sv](../sv/CONTRIBUTING.md) · 🇰🇪 [sw](../sw/CONTRIBUTING.md) · 🇮🇳 [ta](../ta/CONTRIBUTING.md) · 🇮🇳 [te](../te/CONTRIBUTING.md) · 🇹🇭 [th](../th/CONTRIBUTING.md) · 🇹🇷 [tr](../tr/CONTRIBUTING.md) · 🇺🇦 [uk-UA](../uk-UA/CONTRIBUTING.md) · 🇵🇰 [ur](../ur/CONTRIBUTING.md) · 🇻🇳 [vi](../vi/CONTRIBUTING.md) · 🇨🇳 [zh-CN](../zh-CN/CONTRIBUTING.md) + +感謝您有興趣貢獻!本指南包含您入門所需的一切。 --- -Thank you for your interest in contributing! This guide covers everything you need to get started. +## 開發環境設定 ---- +### 前置需求 -## Development Setup - -### Prerequisites - -- **Node.js** >= 18 < 24 (recommended: 22 LTS) +- **Node.js** `>=22.22.3 <23`,或 `>=24.0.0 <27`(建議:24 LTS) - **npm** 10+ - **Git** -### Clone & Install +### 複製與安裝 ```bash git clone https://github.com/diegosouzapw/OmniRoute.git @@ -24,85 +22,109 @@ cd OmniRoute npm install ``` -### Environment Variables +### 環境變數 ```bash -# Create your .env from the template +# 從範本建立您的 .env cp .env.example .env -# Generate required secrets +# 產生所需的密鑰 echo "JWT_SECRET=$(openssl rand -base64 48)" >> .env echo "API_KEY_SECRET=$(openssl rand -hex 32)" >> .env ``` -Key variables for development: +開發用的關鍵變數: -| Variable | Development Default | Description | -| ---------------------- | ------------------------ | --------------------- | -| `PORT` | `20128` | Server port | -| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | Base URL for frontend | -| `JWT_SECRET` | (generate above) | JWT signing secret | -| `INITIAL_PASSWORD` | `CHANGEME` | First login password | -| `APP_LOG_LEVEL` | `info` | Log verbosity level | +| 變數 | 開發環境預設值 | 說明 | +| ---------------------- | ----------------------- | ------------------ | +| `PORT` | `20128` | 伺服器埠號 | +| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | 前端的基礎 URL | +| `JWT_SECRET` | (上方產生) | JWT 簽章密鑰 | +| `INITIAL_PASSWORD` | `CHANGEME` | 首次登入密碼 | +| `APP_LOG_LEVEL` | `info` | 日誌詳細程度 | -### Dashboard Settings +### 儀表板設定 -The dashboard provides UI toggles for features that can also be configured via environment variables: +儀表板提供 UI 開關,可設定也能透過環境變數配置的功能: -| Setting Location | Toggle | Description | -| ------------------- | ------------------ | ------------------------------ | -| Settings → Advanced | Debug Mode | Enable debug request logs (UI) | -| Settings → General | Sidebar Visibility | Show/hide sidebar sections | +| 設定位置 | 開關 | 說明 | +| ------------------ | -------------- | ---------------------------- | +| 設定 → 進階 | 除錯模式 | 啟用除錯請求日誌(UI) | +| 設定 → 一般 | 側邊欄可見性 | 顯示/隱藏側邊欄區塊 | -These settings are stored in the database and persist across restarts, overriding env var defaults when set. +這些設定儲存在資料庫中,重新啟動後仍會保留,設定後會覆蓋環境變數的預設值。 -### Running Locally +### 在本機執行 ```bash -# Development mode (hot reload) +# 開發模式(熱載入) npm run dev -# Production build -npm run build +# 生產建置 +npm run build # next build → .build/next/ 然後 assembleStandalone → dist/ npm run start -# Common port configuration +# 發布建置(清除重建 + HEAD 哨兵 — 部署必用) +npm run build:release # rm -rf .build dist && 建置 + 寫入 dist/BUILD_SHA + +# 常見埠號配置 PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev ``` -Default URLs: +### 建置輸出結構 -- **Dashboard**: `http://localhost:20128/dashboard` -- **API**: `http://localhost:20128/v1` +| 目錄 | 內容 | 版本追蹤 | +| ---------- | -------------------------------------------------- | -------- | +| `src/` | 應用程式原始碼(TypeScript / TSX) | 是 | +| `.build/` | 中間產物 — `next build` 輸出(gitignored,`distDir = .build/next`) | 否 | +| `dist/` | 可發佈套件 — 由 `assembleStandalone` 組裝(gitignored) | 否 | + +建置管線為單次傳遞: + +``` +npm run build + └─ next build → .build/next/standalone (Next.js 輸出) + └─ assembleStandalone() (複製 standalone + static + public + 原生資源) + └─ 輸出: dist/ (server.js, .next/static/, public/, node_modules/) +``` + +`npm run build:release` 會額外先清除兩個目錄,然後寫入 `dist/BUILD_SHA`(= `git rev-parse --short HEAD`)作為部署完整性哨兵。 + +> **VPS 部署注意:** 遠端映像檔目錄 `/usr/lib/node_modules/omniroute/app/` 維持不變。部署技能會將 `dist/` 的內容 rsync 到其中。只有儲存庫內的建置輸出路徑改變了(`app/` → `dist/`)。 + +預設 URL: + +- **儀表板**:`http://localhost:20128/dashboard` +- **API**:`http://localhost:20128/v1` --- -## Git Workflow +## Git 工作流程 -> ⚠️ **NEVER commit directly to `main`.** Always use feature branches. +> ⚠️ **絕對不要直接提交到 `main`。** 一律使用功能分支。 ```bash git checkout -b feat/your-feature-name -# ... make changes ... +# ... 進行修改 ... git commit -m "feat: describe your change" git push -u origin feat/your-feature-name -# Open a Pull Request on GitHub +# 在 GitHub 上開啟 Pull Request ``` -### Branch Naming +### 分支命名 -| Prefix | Purpose | -| ----------- | ------------------------- | -| `feat/` | New features | -| `fix/` | Bug fixes | -| `refactor/` | Code restructuring | -| `docs/` | Documentation changes | -| `test/` | Test additions/fixes | -| `chore/` | Tooling, CI, dependencies | +| 前綴 | 用途 | +| ------------ | ---------------------- | +| `feat/` | 新功能 | +| `fix/` | 錯誤修正 | +| `refactor/` | 程式碼重構 | +| `docs/` | 文件變更 | +| `test/` | 測試新增/修正 | +| `chore/` | 工具、CI、依賴項目 | -### Commit Messages +### 提交訊息 -Follow [Conventional Commits](https://www.conventionalcommits.org/): +遵循 [Conventional Commits](https://www.conventionalcommits.org/): ``` feat: add circuit breaker for provider calls @@ -112,200 +134,233 @@ test: add observability unit tests refactor(db): consolidate rate limit tables ``` -Scopes: `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, `a2a`, `memory`, `skills`. +範圍(v3.8):`db`、`sse`、`oauth`、`dashboard`、`api`、`cli`、`docker`、`ci`、`mcp`、`a2a`、`memory`、`skills`、`cloud-agent`、`guardrails`、`compression`、`auto-combo`、`resilience`、`providers`、`executors`、`translator`、`domain`、`authz`。 --- -## Running Tests +## 執行測試 ```bash -# All tests (unit + vitest + ecosystem + e2e) +# 所有測試(單元 + vitest + 生態系 + e2e) npm run test:all -# Single test file (Node.js native test runner — most tests use this) +# 單一測試檔案(Node.js 原生測試執行器 — 大部分測試使用此方式) node --import tsx/esm --test tests/unit/your-file.test.ts -# Vitest (MCP server, autoCombo, cache) +# Vitest(MCP 伺服器、autoCombo、快取) npm run test:vitest -# E2E tests (requires Playwright) +# E2E 測試(需要 Playwright) npm run test:e2e -# Protocol clients E2E (MCP transports, A2A) +# 協定客戶端 E2E(MCP 傳輸、A2A) npm run test:protocols:e2e -# Ecosystem compatibility tests +# 生態系相容性測試 npm run test:ecosystem -# Coverage (60% min statements/lines/functions/branches) +# 覆蓋率閘道:60% statements/lines/functions/branches npm run test:coverage npm run coverage:report -# Lint + format check +# Lint + 格式檢查 npm run lint npm run check + +# 實際上游 combo 冒煙測試(需要 VPS 存取 + 實際提供商額度) +# 會打到真實提供商 — 會花一點錢。絕對不會在 CI 中執行。沒有閘道時會乾淨地跳過。 +# 需要:ssh root@192.168.0.15 存取(從 VPS 讀取唯讀資料庫快照)。 +RUN_COMBO_LIVE=1 npm run test:combo:live + +# Phase-3 VPS 實戰冒煙測試 — 純 Node ESM 腳本,直接打到 .15 伺服器。 +# 需要:ssh root@192.168.0.15 存取(combo 透過 SSH sqlite 建立/刪除)。 +# 會打到真實提供商(少量費用)。只會建立/刪除 __live_test__* combo。絕對不會在 CI 中執行。 +# REQUIRE_API_KEY=false on .15 所以不需要 API 金鑰,但如果設定了 COMBO_LIVE_BASE_URL / COMBO_LIVE_API_KEY 則會遵循。 +npm run test:combo:live:vps # 7 個 HTTP 情境(priority/round-robin/weighted/cost/fusion/auto + health) +npm run test:combo:live:vps:failover # 增加實際跨提供商容錯情境(共 8 個) ``` -Coverage notes: +覆蓋率注意事項: -- `npm run test:coverage` measures source coverage for the main unit test suite, excludes `tests/**`, and includes `open-sse/**` -- Pull requests must keep the overall coverage gate at **60% or higher** for statements, lines, functions, and branches -- If a PR changes production code in `src/`, `open-sse/`, `electron/`, or `bin/`, it must add or update automated tests in the same PR -- `npm run coverage:report` prints the detailed file-by-file report from the latest coverage run -- `npm run test:coverage:legacy` preserves the older metric for historical comparison -- See `docs/ops/COVERAGE_PLAN.md` for the phased coverage improvement roadmap +- `npm run test:coverage` 測量主要單元測試套件的原始碼覆蓋率,排除 `tests/**`,包含 `open-sse/**` +- Pull Request 必須維持覆蓋率閘道在 **60%+** statements/lines/functions/branches +- 如果 PR 變更了 `src/`、`open-sse/`、`electron/` 或 `bin/` 中的生產程式碼,必須在同一 PR 中新增或更新自動化測試 +- `npm run coverage:report` 會列印最近一次覆蓋率執行的詳細逐檔案報告 +- `npm run test:coverage:legacy` 保留舊版指標以供歷史比較 +- 請參閱 `docs/ops/COVERAGE_PLAN.md` 了解階段性覆蓋率改善藍圖 -### Pull Request Requirements +### Pull Request 需求 -Before opening or merging a PR: +在開啟或合併 PR 之前: -- Run `npm run test:unit` -- Run `npm run test:coverage` -- Ensure the coverage gate stays at **60%+** for all metrics -- Include the changed or added test files in the PR description when production code changed -- Check the SonarQube result on the PR when the project secrets are configured in CI +- 執行 `npm run test:unit` +- 執行 `npm run test:coverage` +- 確保覆蓋率閘道維持在 **60%+** statements/lines/functions/branches +- 當生產程式碼變更時,在 PR 說明中包含已變更或新增的測試檔案 +- 當 CI 中配置了專案密鑰時,檢查 PR 上的 SonarQube 結果 -Current test status: **122 unit test files** covering: +目前測試狀態:**122 個單元測試檔案** 涵蓋: -- Provider translators and format conversion -- Rate limiting, circuit breaker, and resilience -- Semantic cache, idempotency, progress tracking -- Database operations and schema (21 DB modules) -- OAuth flows and authentication -- API endpoint validation (Zod v4) -- MCP server tools and scope enforcement -- Memory and Skills systems +- 提供商轉換器與格式轉換 +- 速率限制、斷路器與彈性 +- 語意快取、冪等性、進度追蹤 +- 資料庫操作與結構(21 個 DB 模組) +- OAuth 流程與認證 +- API 端點驗證(Zod v4) +- MCP 伺服器工具與範圍強制 +- 記憶體與技能系統 --- -## Code Style +## 程式碼風格 -- **ESLint** — Run `npm run lint` before committing -- **Prettier** — Auto-formatted via `lint-staged` on commit (2 spaces, semicolons, double quotes, 100 char width, es5 trailing commas) -- **TypeScript** — All `src/` code uses `.ts`/`.tsx`; `open-sse/` uses `.ts`/`.js`; document with TSDoc (`@param`, `@returns`, `@throws`) -- **No `eval()`** — ESLint enforces `no-eval`, `no-implied-eval`, `no-new-func` -- **Zod validation** — Use Zod v4 schemas for all API input validation -- **Naming**: Files = camelCase/kebab-case, components = PascalCase, constants = UPPER_SNAKE +- **ESLint** — 提交前執行 `npm run lint` +- **Prettier** — 透過 `lint-staged` 在提交時自動格式化(2 空格、分號、雙引號、100 字元寬度、es5 尾逗號) +- **TypeScript** — 所有 `src/` 程式碼使用 `.ts`/`.tsx`;`open-sse/` 使用 `.ts`/`.js`;使用 TSDoc 撰寫文件(`@param`、`@returns`、`@throws`) +- **不使用 `eval()`** — ESLint 強制禁止 `no-eval`、`no-implied-eval`、`no-new-func` +- **Zod 驗證** — 所有 API 輸入驗證使用 Zod v4 結構 +- **命名**:檔案 = camelCase/kebab-case,元件 = PascalCase,常數 = UPPER_SNAKE --- -## Project Structure +## 專案結構 ``` src/ # TypeScript (.ts / .tsx) ├── app/ # Next.js 16 App Router -│ ├── (dashboard)/ # Dashboard pages (23 sections) -│ ├── api/ # API routes (51 directories) -│ └── login/ # Auth pages (.tsx) -├── domain/ # Policy engine (policyEngine, comboResolver, costRules, etc.) -├── lib/ # Core business logic (.ts) -│ ├── a2a/ # Agent-to-Agent v0.3 protocol server -│ ├── acp/ # Agent Communication Protocol registry -│ ├── compliance/ # Compliance policy engine -│ ├── db/ # SQLite database layer (21 modules + 16 migrations) -│ ├── memory/ # Persistent conversational memory -│ ├── oauth/ # OAuth providers, services, and utilities -│ ├── skills/ # Extensible skill framework -│ ├── usage/ # Usage tracking and cost calculation -│ └── localDb.ts # Re-export layer only — never add logic here -├── middleware/ # Request middleware (promptInjectionGuard) -├── mitm/ # MITM proxy (cert, DNS, target routing) +│ ├── (dashboard)/ # 儀表板頁面(23 個區塊) +│ ├── api/ # API 路由(51 個目錄) +│ └── login/ # 認證頁面 (.tsx) +├── domain/ # 政策引擎(policyEngine、comboResolver、costRules 等) +├── lib/ # 核心業務邏輯 (.ts) +│ ├── a2a/ # Agent-to-Agent v0.3 協定伺服器 +│ ├── acp/ # Agent 通訊協定註冊表 +│ ├── compliance/ # 合規政策引擎 +│ ├── db/ # SQLite 資料庫層(21 個模組 + 16 個遷移) +│ ├── memory/ # 持久對話記憶 +│ ├── oauth/ # OAuth 提供商、服務與工具 +│ ├── skills/ # 可擴展技能框架 +│ ├── usage/ # 用量追蹤與成本計算 +│ └── localDb.ts # 僅作為重新匯出層 — 永遠不要在此新增邏輯 +├── middleware/ # 請求中介層(promptInjectionGuard) +├── mitm/ # MITM 代理(憑證、DNS、目標路由) ├── shared/ -│ ├── components/ # React components (.tsx) -│ ├── constants/ # Provider definitions (60+), MCP scopes, routing strategies -│ ├── utils/ # Circuit breaker, sanitizer, auth helpers -│ └── validation/ # Zod v4 schemas -└── sse/ # SSE proxy pipeline +│ ├── components/ # React 元件 (.tsx) +│ ├── constants/ # 提供商定義(177)、MCP 範圍、14 種路由策略 +│ ├── utils/ # 斷路器、清理工具、認證輔助 +│ └── validation/ # Zod v4 結構 +└── sse/ # SSE 代理管線 -open-sse/ # @omniroute/open-sse workspace -├── executors/ # 14 provider-specific request executors -├── handlers/ # 11 request handlers (chat, responses, embeddings, images, etc.) -├── mcp-server/ # MCP server (25 tools, 3 transports, 10 scopes) -├── services/ # 36+ services (combo, autoCombo, rateLimitManager, etc.) -├── translator/ # Format translators (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama) -├── transformer/ # Responses API transformer -└── utils/ # 22 utility modules (stream, TLS, proxy, logging) +open-sse/ # @omniroute/open-sse 工作區 +├── executors/ # 14 個提供商專用請求執行器 +├── handlers/ # 11 個請求處理器(聊天、回應、嵌入、圖片等) +├── mcp-server/ # MCP 伺服器(25 個工具、3 種傳輸、10 個範圍) +├── services/ # 36+ 服務(combo、autoCombo、rateLimitManager 等) +├── translator/ # 格式轉換器(OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama) +├── transformer/ # Responses API 轉換器 +└── utils/ # 22 個工具模組(串流、TLS、代理、日誌) -electron/ # Electron desktop app (cross-platform) +electron/ # Electron 桌面應用程式(跨平台) tests/ -├── unit/ # Node.js test runner (122 test files) -├── integration/ # Integration tests -├── e2e/ # Playwright tests -├── security/ # Security tests -├── translator/ # Translator-specific tests -└── load/ # Load tests +├── unit/ # Node.js 測試執行器(1,574 個測試檔案) +├── integration/ # 整合測試 +├── e2e/ # Playwright 測試 +├── security/ # 安全性測試 +├── translator/ # 轉換器專用測試 +└── load/ # 負載測試 -docs/ # Documentation -├── ARCHITECTURE.md # System architecture -├── API_REFERENCE.md # All endpoints -├── USER_GUIDE.md # Provider setup, CLI integration -├── TROUBLESHOOTING.md # Common issues -├── MCP-SERVER.md # MCP server (25 tools) -├── A2A-SERVER.md # A2A agent protocol -├── AUTO-COMBO.md # Auto-combo engine -├── CLI-TOOLS.md # CLI tools integration -├── COVERAGE_PLAN.md # Test coverage improvement plan -├── openapi.yaml # OpenAPI specification -└── adr/ # Architecture Decision Records +docs/ +├── adr/ # 架構決策記錄 +├── architecture/ # 系統架構與彈性 +├── comparison/ # OmniRoute 與替代方案比較 +├── compression/ # 壓縮指南與規則 +├── dev/ # 開發指南 +├── diagrams/ # 架構圖 +├── frameworks/ # MCP、A2A、OpenCode、記憶體、技能 +├── guides/ # 使用者指南、Docker、設定、疑難排解 +├── i18n/ # 國際化 README 翻譯 +├── marketing/ # 行銷素材 +├── ops/ # 部署、代理、覆蓋率、發布 +├── providers/ # 提供商專用文件 +├── reference/ # API 參考、環境變數、CLI 工具、免費方案 +├── releases/ # 版本說明 +├── routing/ # Auto-combo 引擎、推理重播 +├── screenshots/ # 儀表板截圖 +├── security/ # 護欄、合規、隱蔽、代幣 +└── specs/ # 設計規格 ``` --- -## Adding a New Provider +## 新增提供商 -### Step 1: Register Provider Constants +### 步驟 1:註冊提供商常數 -Add to `src/shared/constants/providers.ts` — Zod-validated at module load. +新增至 `src/shared/constants/providers.ts` — 在模組載入時以 Zod 驗證。 -### Step 2: Add Executor (if custom logic needed) +### 步驟 2:新增執行器(如果需要自訂邏輯) -Create executor in `open-sse/executors/your-provider.ts` extending the base executor. +在 `open-sse/executors/your-provider.ts` 中建立執行器,擴展基礎執行器。 -### Step 3: Add Translator (if non-OpenAI format) +### 步驟 3:新增轉換器(若非 OpenAI 格式) -Create request/response translators in `open-sse/translator/`. +在 `open-sse/translator/` 中建立請求/回應轉換器。 -### Step 4: Add OAuth Config (if OAuth-based) +### 步驟 4:新增 OAuth 設定(若基於 OAuth) -Add OAuth credentials in `src/lib/oauth/constants/oauth.ts` and service in `src/lib/oauth/services/`. +在 `src/lib/oauth/constants/oauth.ts` 中新增 OAuth 憑證,並在 `src/lib/oauth/services/` 中新增服務。 -### Step 5: Register Models +如果上游提供商在其公開 CLI / 瀏覽器套件中分發了公開的 OAuth client_id/secret 或 Firebase Web API 金鑰,**請勿**將其嵌入為字串字面值。請使用 `open-sse/utils/publicCreds.ts` 中的 `resolvePublicCred()`,並在 `EMBEDDED_DEFAULTS` 中新增一個遮罩位元組條目。完整的強制性工作流程記錄於 [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)。 -Add model definitions in `open-sse/config/providerRegistry.ts`. +在處理器/執行器內部,傳送到客戶端的錯誤訊息必須通過 `open-sse/utils/error.ts` 的 `buildErrorBody()` / `sanitizeErrorMessage()` — 絕對不要將原始 `err.stack` 或 `err.message` 放入回應主體。請參閱 [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)。 -### Step 6: Add Tests +### 步驟 5:註冊模型 -Write unit tests in `tests/unit/` covering at minimum: +在 `open-sse/config/providerRegistry.ts` 中新增模型定義。 -- Provider registration -- Request/response translation -- Error handling +### 步驟 6:新增測試 + +在 `tests/unit/` 中撰寫單元測試,至少涵蓋: + +- 提供商註冊 +- 請求/回應轉換 +- 錯誤處理 --- -## Pull Request Checklist +## Pull Request 檢查清單 -- [ ] Tests pass (`npm test`) -- [ ] Linting passes (`npm run lint`) -- [ ] Build succeeds (`npm run build`) -- [ ] TypeScript types added for new public functions and interfaces -- [ ] No hardcoded secrets or fallback values -- [ ] All inputs validated with Zod schemas -- [ ] CHANGELOG updated (if user-facing change) -- [ ] Documentation updated (if applicable) +- [ ] 測試通過(`npm test`) +- [ ] Linting 通過(`npm run lint`) +- [ ] 建置成功(`npm run build`) +- [ ] 為新的公開函數和介面新增 TypeScript 型別 +- [ ] 無硬編碼密鑰或後備值 +- [ ] 公開上游憑證透過 `resolvePublicCred()` 嵌入(參見 [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)),而非以字面值嵌入 +- [ ] 錯誤回應通過 `buildErrorBody()` / `sanitizeErrorMessage()` 路由 — 回應主體中無原始堆疊追蹤(參見 [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shell 命令(`exec` / `spawn`)透過 `env` 傳遞執行時期值,而非透過字串插值 +- [ ] 所有輸入以 Zod 結構驗證 +- [ ] 針對使用者可見的變更,在 `changelog.d/{features|fixes|maintenance}/-.md` 下新增 **Changelog 片段**(參見 [`changelog.d/README.md`](./changelog.d/README.md))— 請**勿**直接編輯 `CHANGELOG.md`;片段會在發布時彙總,且絕不會在 PR 之間衝突 +- [ ] 文件已更新(如適用) +- [ ] 無新增的 CodeQL / 密碼掃描警報,或每個警報已附上技術理由並參考相關 `docs/security/` 文件 +- [ ] 啟動子處理程序的路由(`/api/mcp/`、`/api/cli-tools/runtime/`)在 `src/server/authz/routeGuard.ts` 中分類為 `isLocalOnlyPath()` — 參見 [硬規則第 15 條](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] 提交訊息中無 `Co-Authored-By` 尾綴 — 提交必須僅以儲存庫擁有者的 Git 身分出現(硬規則第 16 條) --- -## Releasing +## 發布 -Releases are managed via the `/generate-release` workflow. When a new GitHub Release is created, the package is **automatically published to npm** via GitHub Actions. +發布透過 `/generate-release` 工作流程管理。當建立新的 GitHub Release 時,套件會透過 GitHub Actions **自動發布到 npm**。 + +對於 VPS 部署,請使用 `npm run build:release`(而非 `npm run build`)— 它會執行清除重建,將套件組裝到 `dist/`,並寫入 `dist/BUILD_SHA` 哨兵。然後使用 `/deploy-vps-*-cc` 技能,這些技能會將 `dist/` rsync 到遠端 `app/` 目錄。 --- -## Getting Help +## 取得協助 -- **Architecture**: See [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) -- **API Reference**: See [`docs/reference/API_REFERENCE.md`](docs/reference/API_REFERENCE.md) -- **Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) -- **ADRs**: See `docs/adr/` for architectural decision records +- **架構**:參見 [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) +- **API 參考**:參見 [`docs/reference/API_REFERENCE.md`](docs/reference/API_REFERENCE.md) +- **安全文件**:[`docs/security/CLI_TOKEN.md`](docs/security/CLI_TOKEN.md)、[`docs/security/ROUTE_GUARD_TIERS.md`](docs/security/ROUTE_GUARD_TIERS.md)、[`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md)、[`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md) +- **運維文件**:[`docs/ops/SQLITE_RUNTIME.md`](docs/ops/SQLITE_RUNTIME.md) +- **問題回報**:[github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) +- **ADR**:參見 `docs/adr/` 了解架構決策記錄 diff --git a/docs/i18n/zh-TW/GEMINI.md b/docs/i18n/zh-TW/GEMINI.md index 37f9efac44..4ba9a2ab4a 100644 --- a/docs/i18n/zh-TW/GEMINI.md +++ b/docs/i18n/zh-TW/GEMINI.md @@ -1,25 +1,50 @@ -# Security and Cleanliness Rules for AI Assistants (中文 (繁體)) +# AI 助手的安全與整潔規則 -🌐 **Languages:** 🇺🇸 [English](../../../GEMINI.md) · 🇸🇦 [ar](../ar/GEMINI.md) · 🇧🇬 [bg](../bg/GEMINI.md) · 🇧🇩 [bn](../bn/GEMINI.md) · 🇨🇿 [cs](../cs/GEMINI.md) · 🇩🇰 [da](../da/GEMINI.md) · 🇩🇪 [de](../de/GEMINI.md) · 🇪🇸 [es](../es/GEMINI.md) · 🇮🇷 [fa](../fa/GEMINI.md) · 🇫🇮 [fi](../fi/GEMINI.md) · 🇫🇷 [fr](../fr/GEMINI.md) · 🇮🇳 [gu](../gu/GEMINI.md) · 🇮🇱 [he](../he/GEMINI.md) · 🇮🇳 [hi](../hi/GEMINI.md) · 🇭🇺 [hu](../hu/GEMINI.md) · 🇮🇩 [id](../id/GEMINI.md) · 🇮🇹 [it](../it/GEMINI.md) · 🇯🇵 [ja](../ja/GEMINI.md) · 🇰🇷 [ko](../ko/GEMINI.md) · 🇮🇳 [mr](../mr/GEMINI.md) · 🇲🇾 [ms](../ms/GEMINI.md) · 🇳🇱 [nl](../nl/GEMINI.md) · 🇳🇴 [no](../no/GEMINI.md) · 🇵🇭 [phi](../phi/GEMINI.md) · 🇵🇱 [pl](../pl/GEMINI.md) · 🇵🇹 [pt](../pt/GEMINI.md) · 🇧🇷 [pt-BR](../pt-BR/GEMINI.md) · 🇷🇴 [ro](../ro/GEMINI.md) · 🇷🇺 [ru](../ru/GEMINI.md) · 🇸🇰 [sk](../sk/GEMINI.md) · 🇸🇪 [sv](../sv/GEMINI.md) · 🇰🇪 [sw](../sw/GEMINI.md) · 🇮🇳 [ta](../ta/GEMINI.md) · 🇮🇳 [te](../te/GEMINI.md) · 🇹🇭 [th](../th/GEMINI.md) · 🇹🇷 [tr](../tr/GEMINI.md) · 🇺🇦 [uk-UA](../uk-UA/GEMINI.md) · 🇵🇰 [ur](../ur/GEMINI.md) · 🇻🇳 [vi](../vi/GEMINI.md) · 🇨🇳 [zh-CN](../zh-CN/GEMINI.md) +> **適用範圍:** 基於 Gemini 的代理規則。若為 Claude Code,請見 `CLAUDE.md`。若為其他 AI 助手,請見 `AGENTS.md`。 ---- +## 1. 檔案放置與組織 -## 1. File Placement & Organization +- **測試檔案**:所有單元測試、整合測試、生態系測試或 Vitest 檔案,**必須**嚴格放置在 `tests/` 目錄內(例如 `tests/unit/`、`tests/integration/`)。**嚴禁**在專案根目錄(`/`)建立測試檔案。 +- **腳本與工具**:所有維護、除錯、產生或實驗性腳本(`.cjs`、`.mjs`、`.js`、`.ts`)**必須**嚴格放置在 `scripts/` 子資料夾之一(`build/`、`dev/`、`check/`、`docs/`、`i18n/`、`ad-hoc/`)。一次性或實驗性程式碼請置於 `scripts/ad-hoc/` 下。**嚴禁**將腳本任意散落在專案根目錄(`/`)或 `scripts/` 頂層資料夾。 -- **Test Files**: ALL unit tests, integration tests, ecosystem tests, or Vitest files MUST strictly be placed within the `tests/` directory (e.g., `tests/unit/`, `tests/integration/`). NEVER create test files in the project root (`/`). -- **Scripts and Utilities**: ALL maintenance, debugging, generation, or experimental scripts (`.cjs`, `.mjs`, `.js`, `.ts`) MUST be placed strictly inside the `scripts/` directory or `scripts/scratch/` for temporary one-offs. NEVER dump loose scripts in the project root (`/`). +**專案根目錄僅能包含:** -**The Project Root MUST ONLY CONTAIN:** +- 設定檔(`vitest.config.ts`、`next.config.mjs`、`eslint.config.mjs`、`tsconfig*.json`、`playwright.config.ts`、`prettier.config.mjs`、`postcss.config.mjs`、`sonar-project.properties`、`fly.toml`、`docker-compose*.yml`、`Dockerfile`) +- 相依性檔案(`package.json`、`package-lock.json`) +- 文件檔案(`README.md`、`CHANGELOG.md`、`LICENSE`、`AGENTS.md`、`CLAUDE.md`、`GEMINI.md`、`CONTRIBUTING.md`、`SECURITY.md`、`CODE_OF_CONDUCT.md`、`llm.txt`、`Tuto_Qdrant.md`) +- CI/CD 檔案與忽略定義(`.gitignore`、`.dockerignore`、`.npmignore`、`.npmrc`、`.node-version`、`.nvmrc`、`.env.example`) -- Configuration files (`vitest.config.ts`, `next.config.mjs`, `eslint.config.mjs`, etc.) -- Dependency files (`package.json`, `package-lock.json`) -- Documentation files (`README.md`, `CHANGELOG.md`, `AGENTS.md`) -- CI/CD files and ignore definitions (`.gitignore`, `.dockerignore`) +當建立**任何**驗證測試或一次性邏輯腳本時,請根據您的目標預設使用 `scripts/ad-hoc/` 或 `tests/unit/` 目錄。請勿汙染 `/` 根目錄上下文。 -When creating _any_ validation tests or one-off logic scripts, default to using `scripts/scratch/` or the `tests/unit/` directories according to your goals. Do not pollute the `/` root context. +## 2. 嚴格規則(與 `CLAUDE.md` 對應) -## 2. VPS Dashboard Credentials +1. **絕不提交機密或憑證。** 使用 `.env`(從 `.env.example` 自動產生)或密碼保管庫。密碼、OAuth 密鑰、API 金鑰和 Cookie 值**不得**出現在已提交的檔案中。 +2. **絕不向 `src/lib/localDb.ts` 添加邏輯。** 該檔案僅作為重新匯出的統合點(barrel)。 +3. **絕不使用 `eval()`、`new Function()` 或任何隱含的 eval。** ESLint 已強制執行此規則。 +4. **絕不直接提交至 `main`。** 請使用 `feat/`、`fix/`、`refactor/`、`docs/`、`test/` 或 `chore/` 分支。 +5. **絕不在路由中撰寫原始 SQL** — 一律透過 `src/lib/db/` 領域模組操作。 +6. **絕不靜默吞沒 SSE 串流中的錯誤** — 應傳遞錯誤或乾淨地中止串流。 +7. **絕不繞過 Husky 鉤子**(`--no-verify`、`--no-gpg-sign`),除非獲得操作人員明確許可。 +8. **一律使用 `src/shared/validation/schemas.ts` 中的 Zod 綱要驗證輸入。** +9. **修改生產程式碼(`src/`、`open-sse/`、`electron/`、`bin/`)時,一律同時添加測試。** +10. **覆蓋率必須維持** ≥ 75% 陳述式 / 75% 行 / 75% 函式 / 70% 分支(實際測量值約 82%)。 -| Environment | URL | Password | -| ----------- | ------------------------- | -------- | -| Local VPS | http://192.168.0.15:20128 | 123456 | +## 3. 程式碼庫導航 + +| 任務 | 請先閱讀此文件 | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 理解程式碼庫 | `docs/architecture/REPOSITORY_MAP.md` | +| 架構概覽 | `docs/architecture/ARCHITECTURE.md` | +| 工程參考 | `docs/architecture/CODEBASE_DOCUMENTATION.md` | +| 添加功能 | `CONTRIBUTING.md` + 對應的 `docs/<領域>.md` | +| 各領域深入探索 | `docs/frameworks/SKILLS.md`、`docs/frameworks/MEMORY.md`、`docs/frameworks/EVALS.md`、`docs/security/GUARDRAILS.md`、`docs/security/COMPLIANCE.md`、`docs/frameworks/CLOUD_AGENT.md`、`docs/frameworks/MCP-SERVER.md`、`docs/frameworks/A2A-SERVER.md`、`docs/architecture/AUTHZ_GUIDE.md`、`docs/architecture/RESILIENCE_GUIDE.md`、`docs/routing/AUTO-COMBO.md`、`docs/frameworks/WEBHOOKS.md`、`docs/routing/REASONING_REPLAY.md`、`docs/security/STEALTH_GUIDE.md`、`docs/ops/TUNNELS_GUIDE.md`、`docs/guides/ELECTRON_GUIDE.md`、`docs/reference/PROVIDER_REFERENCE.md` | +| 發布流程 | `docs/ops/RELEASE_CHECKLIST.md` | + +## 4. 本地開發環境存取 + +儀表板可透過操作人員選擇的 URL/連接埠存取(預設 `http://localhost:20128`)。憑證為操作人員專屬: + +- **初始管理員密碼**取自首次安裝時的 `INITIAL_PASSWORD` 環境變數(`.env.example` 中預設為 `CHANGEME`;請在首次登入後立即更換)。 +- **本地 VPS/共享開發環境**:請向操作人員索取 URL 與當前憑證——這些資訊保存在操作人員的個人密碼保管庫中,**不在**此儲存庫內。 + +> 若本檔案舊版中曾出現任何憑證,均為非正式環境的示範值;請將其視為已洩漏,切勿重複使用。 diff --git a/docs/i18n/zh-TW/SECURITY.md b/docs/i18n/zh-TW/SECURITY.md index 6df6491b73..e05040e52b 100644 --- a/docs/i18n/zh-TW/SECURITY.md +++ b/docs/i18n/zh-TW/SECURITY.md @@ -1,159 +1,172 @@ -# Security Policy (中文 (繁體)) +# 安全性政策 -🌐 **Languages:** 🇺🇸 [English](../../../SECURITY.md) · 🇸🇦 [ar](../ar/SECURITY.md) · 🇧🇬 [bg](../bg/SECURITY.md) · 🇧🇩 [bn](../bn/SECURITY.md) · 🇨🇿 [cs](../cs/SECURITY.md) · 🇩🇰 [da](../da/SECURITY.md) · 🇩🇪 [de](../de/SECURITY.md) · 🇪🇸 [es](../es/SECURITY.md) · 🇮🇷 [fa](../fa/SECURITY.md) · 🇫🇮 [fi](../fi/SECURITY.md) · 🇫🇷 [fr](../fr/SECURITY.md) · 🇮🇳 [gu](../gu/SECURITY.md) · 🇮🇱 [he](../he/SECURITY.md) · 🇮🇳 [hi](../hi/SECURITY.md) · 🇭🇺 [hu](../hu/SECURITY.md) · 🇮🇩 [id](../id/SECURITY.md) · 🇮🇹 [it](../it/SECURITY.md) · 🇯🇵 [ja](../ja/SECURITY.md) · 🇰🇷 [ko](../ko/SECURITY.md) · 🇮🇳 [mr](../mr/SECURITY.md) · 🇲🇾 [ms](../ms/SECURITY.md) · 🇳🇱 [nl](../nl/SECURITY.md) · 🇳🇴 [no](../no/SECURITY.md) · 🇵🇭 [phi](../phi/SECURITY.md) · 🇵🇱 [pl](../pl/SECURITY.md) · 🇵🇹 [pt](../pt/SECURITY.md) · 🇧🇷 [pt-BR](../pt-BR/SECURITY.md) · 🇷🇴 [ro](../ro/SECURITY.md) · 🇷🇺 [ru](../ru/SECURITY.md) · 🇸🇰 [sk](../sk/SECURITY.md) · 🇸🇪 [sv](../sv/SECURITY.md) · 🇰🇪 [sw](../sw/SECURITY.md) · 🇮🇳 [ta](../ta/SECURITY.md) · 🇮🇳 [te](../te/SECURITY.md) · 🇹🇭 [th](../th/SECURITY.md) · 🇹🇷 [tr](../tr/SECURITY.md) · 🇺🇦 [uk-UA](../uk-UA/SECURITY.md) · 🇵🇰 [ur](../ur/SECURITY.md) · 🇻🇳 [vi](../vi/SECURITY.md) · 🇨🇳 [zh-CN](../zh-CN/SECURITY.md) +## 回報漏洞 ---- +如果您在 OmniRoute 中發現安全漏洞,請以負責任的方式回報: -## Reporting Vulnerabilities +1. **請勿**在 GitHub 上建立公開 Issue +2. 請使用 [GitHub 安全性公告](https://github.com/diegosouzapw/OmniRoute/security/advisories/new) +3. 內容需包含:說明、重現步驟及潛在影響 -If you discover a security vulnerability in OmniRoute, please report it responsibly: +## 回應時程 -1. **DO NOT** open a public GitHub issue -2. Use [GitHub Security Advisories](https://github.com/diegosouzapw/OmniRoute/security/advisories/new) -3. Include: description, reproduction steps, and potential impact - -## Response Timeline - -| Stage | Target | +| 階段 | 目標 | | ------------------- | --------------------------- | -| Acknowledgment | 48 hours | -| Triage & Assessment | 5 business days | -| Patch Release | 14 business days (critical) | +| 確認收件 | 48 小時 | +| 分類與評估 | 5 個工作日 | +| 修補程式發布 | 14 個工作日(重大漏洞) | -## Supported Versions +## 支援版本 -| Version | Support Status | +| 版本 | 支援狀態 | | ------- | -------------- | -| 3.6.x | ✅ Active | -| 3.5.x | ✅ Security | -| < 3.5.0 | ❌ Unsupported | +| 3.8.x | ✅ 積極維護中 | +| 3.7.x | ✅ 安全性更新 | +| < 3.7.0 | ❌ 不再支援 | --- -## Security Architecture +## 安全架構 -OmniRoute implements a multi-layered security model: +OmniRoute 採用多層式安全模型: ``` -Request → CORS → API Key Auth → Prompt Injection Guard → Input Sanitizer → Rate Limiter → Circuit Breaker → Provider +請求 → CORS → 授權管道(分類 → 政策 → 強制執行) + → 防護欄(PII 遮罩、提示注入、視覺橋接) + → 速率限制器 → 斷路器 → 冷卻 → 模型鎖定 → 提供者 ``` -### 🔐 Authentication & Authorization +### 🔐 身分驗證與授權 -| Feature | Implementation | -| -------------------- | ---------------------------------------------------------- | -| **Dashboard Login** | Password-based auth with JWT tokens (HttpOnly cookies) | -| **API Key Auth** | HMAC-signed keys with CRC validation | -| **OAuth 2.0 + PKCE** | Secure provider auth (Claude, Codex, Gemini, Cursor, etc.) | -| **Token Refresh** | Automatic OAuth token refresh before expiry | -| **Secure Cookies** | `AUTH_COOKIE_SECURE=true` for HTTPS environments | -| **MCP Scopes** | 10 granular scopes for MCP tool access control | +| 功能 | 實作方式 | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| **儀表板登入** | 基於密碼的身分驗證,搭配 JWT Token(HttpOnly Cookie) | +| **API 金鑰驗證** | HMAC 簽署金鑰搭配 CRC 驗證 | +| **OAuth 2.0 + PKCE** | 13 個提供者(Claude、Codex、GitHub、Cursor、Antigravity、Gemini、Kimi Coding、Kilo Code、Cline、Kiro、Qoder、Windsurf、GitLab Duo) | +| **Token 更新** | 自動在 OAuth Token 到期前進行更新 | +| **安全 Cookie** | 在 HTTPS 環境下設定 `AUTH_COOKIE_SECURE=true` | +| **授權管道** | 路由分類(PUBLIC / CLIENT_API / MANAGEMENT)— 請參閱 `docs/architecture/AUTHZ_GUIDE.md` | +| **路由防護層級** | 管理路由採三層模型(LOCAL_ONLY / ALWAYS_PROTECTED / MANAGEMENT)— 請參閱 `docs/security/ROUTE_GUARD_TIERS.md` | +| **管理範圍 MCP** | 遠端 `/api/mcp/*` 存取需透過具備 `manage` 範圍的 API 金鑰控管;`/api/cli-tools/runtime/*` 維持嚴格的迴路限制。詳見 ROUTE_GUARD_TIERS | +| **MCP 範圍** | 約 13 個細粒度範圍(read:health、write:combos、execute:completions 等)— 請參閱 `docs/frameworks/MCP-SERVER.md` | -### 🛡️ Encryption at Rest +### 🛡️ 靜態資料加密 -All sensitive data stored in SQLite is encrypted using **AES-256-GCM** with scrypt key derivation: +所有儲存於 SQLite 的敏感資料皆使用 **AES-256-GCM** 搭配 scrypt 金鑰推導進行加密: -- API keys, access tokens, refresh tokens, and ID tokens -- Versioned format: `enc:v1:::` -- Passthrough mode (plaintext) when `STORAGE_ENCRYPTION_KEY` is not set +- API 金鑰、存取 Token、更新 Token 及身分 Token +- 版本化格式:`enc:v1:::` +- 若未設定 `STORAGE_ENCRYPTION_KEY`,則以純文字模式(passthrough)運作 ```bash -# Generate encryption key: +# 產生加密金鑰: STORAGE_ENCRYPTION_KEY=$(openssl rand -hex 32) ``` -### 🧠 Prompt Injection Guard +### 🛡️ 防護欄框架 -Middleware that detects and blocks prompt injection attacks in LLM requests: +OmniRoute 提供可熱重載的**防護欄註冊表**(`src/lib/guardrails/`),內建 3 道依優先順序排列的防護欄: -| Pattern Type | Severity | Example | +| 防護欄 | 優先順序 | 目的 | +| ------------------ | -------- | -------------------------------------------------------------------------------------- | +| `vision-bridge` | 5 | 為非視覺模型橋接具備影像識別的描述;防範圖片 URL 的 SSRF 攻擊 | +| `pii-masker` | 10 | 呼叫前後進行 PII 遮罩(電子郵件、電話、CPF、CNPJ、信用卡、SSN) | +| `prompt-injection` | 20 | 偵測覆寫/角色劫持/越獄/洩漏模式 | + +自訂防護欄可透過 `registerGuardrail(new MyGuardrail())` 註冊。此模型為故障開放(fail-open)設計(異常不會阻斷流量)。可透過 `x-omniroute-disabled-guardrails` 標頭在單次請求中選擇停用。→ 詳見 [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md)。 + +### 🧠 提示注入防護 + +偵測並阻擋 LLM 請求中的提示注入攻擊的中介軟體: + +| 模式類型 | 嚴重性 | 範例 | | ------------------- | -------- | ---------------------------------------------- | -| System Override | High | "ignore all previous instructions" | -| Role Hijack | High | "you are now DAN, you can do anything" | -| Delimiter Injection | Medium | Encoded separators to break context boundaries | -| DAN/Jailbreak | High | Known jailbreak prompt patterns | -| Instruction Leak | Medium | "show me your system prompt" | +| 系統指令覆寫 | 高 | 「忽略所有先前的指令」 | +| 角色劫持 | 高 | 「你現在是 DAN,你可以做任何事」 | +| 分隔符號注入 | 中 | 編碼後的分隔符,用以破壞上下文邊界 | +| DAN/越獄 | 高 | 已知的越獄提示模式 | +| 指令洩漏 | 中 | 「顯示你的系統提示詞」 | -Configure via dashboard (Settings → Security) or `.env`: +可透過儀表板(設定 → 安全性)或 `.env` 進行設定: ```env INPUT_SANITIZER_ENABLED=true INPUT_SANITIZER_MODE=block # warn | block | redact ``` -### 🔒 PII Redaction +### 🔒 PII 遮罩處理 -Automatic detection and optional redaction of personally identifiable information: +自動偵測並選擇性遮蔽個人識別資訊: -| PII Type | Pattern | Replacement | -| ------------- | --------------------- | ------------------ | -| Email | `user@domain.com` | `[EMAIL_REDACTED]` | -| CPF (Brazil) | `123.456.789-00` | `[CPF_REDACTED]` | -| CNPJ (Brazil) | `12.345.678/0001-00` | `[CNPJ_REDACTED]` | -| Credit Card | `4111-1111-1111-1111` | `[CC_REDACTED]` | -| Phone | `+55 11 99999-9999` | `[PHONE_REDACTED]` | -| SSN (US) | `123-45-6789` | `[SSN_REDACTED]` | +| PII 類型 | 模式 | 取代內容 | +| ------------- | --------------------- | ------------------ | +| 電子郵件 | `user@domain.com` | `[EMAIL_REDACTED]` | +| CPF(巴西) | `123.456.789-00` | `[CPF_REDACTED]` | +| CNPJ(巴西) | `12.345.678/0001-00` | `[CNPJ_REDACTED]` | +| 信用卡 | `4111-1111-1111-1111` | `[CC_REDACTED]` | +| 電話 | `+55 11 99999-9999` | `[PHONE_REDACTED]` | +| SSN(美國) | `123-45-6789` | `[SSN_REDACTED]` | ```env PII_REDACTION_ENABLED=true ``` -### 🌐 Network Security +### 🌐 網路安全 -| Feature | Description | -| ------------------------ | ---------------------------------------------------------------- | -| **CORS** | Configurable origin control (`CORS_ORIGIN` env var, default `*`) | -| **IP Filtering** | Allowlist/blocklist IP ranges in dashboard | -| **Rate Limiting** | Per-provider rate limits with automatic backoff | -| **Anti-Thundering Herd** | Mutex + per-connection locking prevents cascading 502s | -| **TLS Fingerprint** | Browser-like TLS fingerprint spoofing to reduce bot detection | -| **CLI Fingerprint** | Per-provider header/body ordering to match native CLI signatures | +| 功能 | 說明 | +| ------------------------ | ------------------------------------------------------------------------------ | +| **CORS** | 明確的跨域白名單(`CORS_ALLOWED_ORIGINS`;舊版為 `CORS_ORIGIN`) | +| **IP 過濾** | 在儀表板中設定允許/封鎖的 IP 範圍 | +| **速率限制** | 依提供者設定的速率限制,搭配自動退避機制 | +| **防驚群效應** | 互斥鎖+連線層級鎖定,防止連鎖 502 錯誤 | +| **TLS 指紋** | 模擬瀏覽器風格的 TLS 指紋,降低機器人偵測率 | +| **CLI 指紋** | 依提供者調整標頭/主體順序,以符合原生 CLI 簽章 | -### 🔌 Resilience & Availability +### 🔌 韌性與可用性 -| Feature | Description | -| ----------------------- | ------------------------------------------------------------------ | -| **Circuit Breaker** | 3-state (Closed → Open → Half-Open) per provider, SQLite-persisted | -| **Request Idempotency** | 5-second dedup window for duplicate requests | -| **Exponential Backoff** | Automatic retry with increasing delays | -| **Health Dashboard** | Real-time provider health monitoring | +| 功能 | 說明 | +| ----------------------- | ------------------------------------------------------------- | +| **斷路器** | 三種狀態(關閉 → 開啟 → 半開),依提供者設定,持久化至 SQLite | +| **請求冪等性** | 5 秒內重複請求去重視窗 | +| **指數退避** | 自動重試,延遲時間逐步增加 | +| **健康狀態儀表板** | 即時提供者健康狀態監控 | -### 📋 Compliance +### 📋 法規遵循 -| Feature | Description | -| ------------------ | ----------------------------------------------------------- | -| **Log Retention** | Automatic cleanup after `CALL_LOG_RETENTION_DAYS` | -| **No-Log Opt-out** | Per API key `noLog` flag disables request logging | -| **Audit Log** | Administrative actions tracked in `audit_log` table | -| **MCP Audit** | SQLite-backed audit logging for all MCP tool calls | -| **Zod Validation** | All API inputs validated with Zod v4 schemas at module load | +| 功能 | 說明 | +| ------------------ | --------------------------------------------------- | +| **日誌保留** | 依 `CALL_LOG_RETENTION_DAYS` 設定自動清理 | +| **不紀錄選擇退出** | 可為每個 API 金鑰設定 `noLog` 標記以停用請求記錄 | +| **稽核日誌** | 管理操作記錄在 `audit_log` 資料表中 | +| **MCP 稽核** | 以 SQLite 為基礎的稽核記錄,涵蓋所有 MCP 工具呼叫 | +| **Zod 驗證** | 所有 API 輸入皆在模組載入時以 Zod v4 綱要進行驗證 | --- -## Required Environment Variables +## 必要的環境變數 -All secrets must be set before starting the server. The server will **fail fast** if they are missing or weak. +所有機密資訊必須在啟動伺服器前設定完成。若缺少或強度不足,伺服器將**快速失敗(fail fast)**。 ```bash -# REQUIRED — server will not start without these: -JWT_SECRET=$(openssl rand -base64 48) # min 32 chars -API_KEY_SECRET=$(openssl rand -hex 32) # min 16 chars +# 必要項目 — 未設定則無法啟動伺服器: +JWT_SECRET=$(openssl rand -base64 48) # 最少 32 個字元 +API_KEY_SECRET=$(openssl rand -hex 32) # 最少 16 個字元 -# RECOMMENDED — enables encryption at rest: +# 建議設定 — 啟用靜態資料加密: STORAGE_ENCRYPTION_KEY=$(openssl rand -hex 32) ``` -The server actively rejects known-weak values like `changeme`, `secret`, or `password`. +伺服器會主動拒絕已知的弱值,例如 `changeme`、`secret` 或 `password`。 --- -## Docker Security +## Docker 安全 -- Use non-root user in production -- Mount secrets as read-only volumes -- Never copy `.env` files into Docker images -- Use `.dockerignore` to exclude sensitive files -- Set `AUTH_COOKIE_SECURE=true` when behind HTTPS +- 在正式環境中使用非 root 使用者 +- 將機密檔案以唯讀磁區掛載 +- 切勿將 `.env` 檔案複製到 Docker 映像檔中 +- 使用 `.dockerignore` 排除敏感檔案 +- 在 HTTPS 環境下設定 `AUTH_COOKIE_SECURE=true` ```bash docker run -d \ @@ -170,10 +183,52 @@ docker run -d \ --- -## Dependencies +## 相依套件 -- Run `npm audit` regularly -- Keep dependencies updated -- The project uses `husky` + `lint-staged` for pre-commit checks -- CI pipeline runs ESLint security rules on every push -- Provider constants validated at module load via Zod (`src/shared/validation/providerSchema.ts`) +- 定期執行 `npm audit`(`npm run audit:deps` 涵蓋主程式 + Electron) +- 保持相依套件更新 +- 本專案使用 `husky` + `lint-staged` 進行提交前檢查(lint-staged + check-docs-sync + check:any-budget:t11) +- CI 管線每次推送時皆執行 ESLint 安全規則(`no-eval`、`no-implied-eval`、`no-new-func` 設為 error) +- 提供者常數在模組載入時經由 Zod 進行驗證(`src/shared/validation/schemas.ts`) +- 使用預設安全的程式庫:`dompurify` / `isomorphic-dompurify`(XSS 防護)、`jose`(JWT)、`better-sqlite3`(透過參數化查詢消除 SQLi 風險)、`bcryptjs`(密碼雜湊) + +## 嚴格安全規則 + +以下規則由工具與審查人員強制執行: + +1. **絕不提交機密資訊** — `.env` 已加入 .gitignore;`.env.example` 為範本(不含實際值,僅含註解 — 詳見下方的 PUBLIC_CREDS.md) +2. **絕不使用 `eval()`、`new Function()` 或隱含 eval** — ESLint 強制執行 +3. **絕不繞過 Husky 掛鉤**(`--no-verify`、`--no-gpg-sign`),除非取得操作人員明確核准 +4. **絕不在路由中撰寫原始 SQL** — 一律透過 `src/lib/db/`(參數化查詢) +5. **一律使用 Zod 驗證輸入** — `src/shared/validation/schemas.ts` +6. **一律淨化上游標頭** — 黑名單定義於 `src/shared/constants/upstreamHeaders.ts` +7. **靜態加密憑證** — 透過 `src/lib/db/encryption.ts` 使用 AES-256-GCM +8. **透過 `resolvePublicCred()` 公開上游 OAuth 識別碼** — 切勿在原始碼中寫入 `AIza…` / `GOCSPX-…` / `…apps.googleusercontent.com` 字面值。詳見 [`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md) +9. **透過 `buildErrorBody()` / `sanitizeErrorMessage()` 回傳錯誤回應** — 切勿將原始的 `err.stack` / `err.message` 放入 HTTP / SSE / executor / MCP 回應主體。詳見 [`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md) +10. **`exec()` / `spawn()` 的執行期值應透過 `env` 選項傳遞** — 切勿將外部路徑或不可信賴的值以字串插值方式嵌入 shell 傳遞的腳本中。參考:`src/mitm/cert/install.ts::updateNssDatabases` +11. **優先選用預設安全的程式庫** — 請參閱 [tldrsec/awesome-secure-defaults](https://github.com/tldrsec/awesome-secure-defaults)(Helmet.js、DOMPurify、ssrf-req-filter、safe-regex、Google Tink)。在自行實作前優先考慮使用這些套件。 + +## 供應鏈掃描器發現(Socket.dev / Snyk 等) + +已發布的 `omniroute` npm 成品(artifact)採用了 Next.js `output: "standalone"` 建置方式,這表示所有的路由處理器 — 包括已記載的特權功能(MITM、Zed 匯入、Cloud Sync、嵌入式服務監督程式)— 最終都會以壓縮後的 chunk 形式存在於 `.next/server/*.js` 中。啟發式供應鏈掃描器經常會將這些 chunk 比對為惡意軟體特徵。 + +針對每一項發現類別,我們都保留了一份每項發現對應的維護者證明文件: + +- **[`docs/security/SOCKET_DEV_FINDINGS.md`](docs/security/SOCKET_DEV_FINDINGS.md)** — + 逐項發現對照表:原始檔 ↔ 被標記的 chunk ↔ 行為 ↔ 在 v3.8.6 中採取的緩解措施 +- 每個被標記的函式點皆以原始碼內的 `SECURITY-AUDITOR-NOTE:` 區塊連結回同一份文件。 + +對於管線無法放寬此警示的使用者,可以使用 `OMNIROUTE_BUILD_PROFILE=minimal npm run build` 進行建置。該方式會將四個敏感模組替換為執行期回傳 HTTP 503 `feature-disabled` 的樁程式(stub),使特權程式碼路徑從套件中完全移除。發布方式請參閱 [`docs/security/SOCKET_DEV_FINDINGS.md`](docs/security/SOCKET_DEV_FINDINGS.md)。 + +## 參考資料 + +- [`docs/architecture/AUTHZ_GUIDE.md`](docs/architecture/AUTHZ_GUIDE.md) — 授權管線 +- [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md) — 防護欄框架 +- [`docs/security/COMPLIANCE.md`](docs/security/COMPLIANCE.md) — 稽核日誌與保留政策 +- [`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md) — **必要**:公開上游憑證模式 +- [`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md) — **必要**:錯誤回應處理模式 +- [`docs/security/SOCKET_DEV_FINDINGS.md`](docs/security/SOCKET_DEV_FINDINGS.md) — 供應鏈掃描器發現的維護者證明文件 +- [`docs/architecture/RESILIENCE_GUIDE.md`](docs/architecture/RESILIENCE_GUIDE.md) — 斷路器 + 冷卻 + 鎖定 +- [`docs/security/STEALTH_GUIDE.md`](docs/security/STEALTH_GUIDE.md) — TLS 指紋辨識(法律/倫理聲明) +- [`CLAUDE.md`](CLAUDE.md) — AI Agent 的嚴格規則 +- [tldrsec/awesome-secure-defaults](https://github.com/tldrsec/awesome-secure-defaults) — 精選預設安全程式庫清單 diff --git a/docs/i18n/zh-TW/docs/architecture/ARCHITECTURE.md b/docs/i18n/zh-TW/docs/architecture/ARCHITECTURE.md index 3f64817837..5372a756f2 100644 --- a/docs/i18n/zh-TW/docs/architecture/ARCHITECTURE.md +++ b/docs/i18n/zh-TW/docs/architecture/ARCHITECTURE.md @@ -1,149 +1,187 @@ -# OmniRoute Architecture (中文 (簡體)) - -🌐 **Languages:** 🇺🇸 [English](../../../../docs/ARCHITECTURE.md) · 🇸🇦 [ar](../../ar/docs/ARCHITECTURE.md) · 🇧🇬 [bg](../../bg/docs/ARCHITECTURE.md) · 🇧🇩 [bn](../../bn/docs/ARCHITECTURE.md) · 🇨🇿 [cs](../../cs/docs/ARCHITECTURE.md) · 🇩🇰 [da](../../da/docs/ARCHITECTURE.md) · 🇩🇪 [de](../../de/docs/ARCHITECTURE.md) · 🇪🇸 [es](../../es/docs/ARCHITECTURE.md) · 🇮🇷 [fa](../../fa/docs/ARCHITECTURE.md) · 🇫🇮 [fi](../../fi/docs/ARCHITECTURE.md) · 🇫🇷 [fr](../../fr/docs/ARCHITECTURE.md) · 🇮🇳 [gu](../../gu/docs/ARCHITECTURE.md) · 🇮🇱 [he](../../he/docs/ARCHITECTURE.md) · 🇮🇳 [hi](../../hi/docs/ARCHITECTURE.md) · 🇭🇺 [hu](../../hu/docs/ARCHITECTURE.md) · 🇮🇩 [id](../../id/docs/ARCHITECTURE.md) · 🇮🇹 [it](../../it/docs/ARCHITECTURE.md) · 🇯🇵 [ja](../../ja/docs/ARCHITECTURE.md) · 🇰🇷 [ko](../../ko/docs/ARCHITECTURE.md) · 🇮🇳 [mr](../../mr/docs/ARCHITECTURE.md) · 🇲🇾 [ms](../../ms/docs/ARCHITECTURE.md) · 🇳🇱 [nl](../../nl/docs/ARCHITECTURE.md) · 🇳🇴 [no](../../no/docs/ARCHITECTURE.md) · 🇵🇭 [phi](../../phi/docs/ARCHITECTURE.md) · 🇵🇱 [pl](../../pl/docs/ARCHITECTURE.md) · 🇵🇹 [pt](../../pt/docs/ARCHITECTURE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/ARCHITECTURE.md) · 🇷🇴 [ro](../../ro/docs/ARCHITECTURE.md) · 🇷🇺 [ru](../../ru/docs/ARCHITECTURE.md) · 🇸🇰 [sk](../../sk/docs/ARCHITECTURE.md) · 🇸🇪 [sv](../../sv/docs/ARCHITECTURE.md) · 🇰🇪 [sw](../../sw/docs/ARCHITECTURE.md) · 🇮🇳 [ta](../../ta/docs/ARCHITECTURE.md) · 🇮🇳 [te](../../te/docs/ARCHITECTURE.md) · 🇹🇭 [th](../../th/docs/ARCHITECTURE.md) · 🇹🇷 [tr](../../tr/docs/ARCHITECTURE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/ARCHITECTURE.md) · 🇵🇰 [ur](../../ur/docs/ARCHITECTURE.md) · 🇻🇳 [vi](../../vi/docs/ARCHITECTURE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/ARCHITECTURE.md) - +--- +title: "OmniRoute 架構" +version: 3.8.40 +lastUpdated: 2026-06-28 --- -_Last updated: 2026-04-15_ +# OmniRoute 架構 -## Executive Summary +🌐 **語言:** 🇺🇸 [English](./ARCHITECTURE.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/architecture/ARCHITECTURE.md) | 🇪🇸 [Español](../i18n/es/docs/architecture/ARCHITECTURE.md) | 🇫🇷 [Français](../i18n/fr/docs/architecture/ARCHITECTURE.md) | 🇮🇹 [Italiano](../i18n/it/docs/architecture/ARCHITECTURE.md) | 🇷🇺 [Русский](../i18n/ru/docs/architecture/ARCHITECTURE.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/docs/architecture/ARCHITECTURE.md) | 🇩🇪 [Deutsch](../i18n/de/docs/architecture/ARCHITECTURE.md) | 🇮🇳 [हिन्दी](../i18n/in/docs/architecture/ARCHITECTURE.md) | 🇹🇭 [ไทย](../i18n/th/docs/architecture/ARCHITECTURE.md) | 🇺🇦 [Українська](../i18n/uk-UA/docs/architecture/ARCHITECTURE.md) | 🇸🇦 [العربية](../i18n/ar/docs/architecture/ARCHITECTURE.md) | 🇯🇵 [日本語](../i18n/ja/docs/architecture/ARCHITECTURE.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/docs/architecture/ARCHITECTURE.md) | 🇧🇬 [Български](../i18n/bg/docs/architecture/ARCHITECTURE.md) | 🇩🇰 [Dansk](../i18n/da/docs/architecture/ARCHITECTURE.md) | 🇫🇮 [Suomi](../i18n/fi/docs/architecture/ARCHITECTURE.md) | 🇮🇱 [עברית](../i18n/he/docs/architecture/ARCHITECTURE.md) | 🇭🇺 [Magyar](../i18n/hu/docs/architecture/ARCHITECTURE.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/docs/architecture/ARCHITECTURE.md) | 🇰🇷 [한국어](../i18n/ko/docs/architecture/ARCHITECTURE.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/docs/architecture/ARCHITECTURE.md) | 🇳🇱 [Nederlands](../i18n/nl/docs/architecture/ARCHITECTURE.md) | 🇳🇴 [Norsk](../i18n/no/docs/architecture/ARCHITECTURE.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/docs/architecture/ARCHITECTURE.md) | 🇷🇴 [Română](../i18n/ro/docs/architecture/ARCHITECTURE.md) | 🇵🇱 [Polski](../i18n/pl/docs/architecture/ARCHITECTURE.md) | 🇸🇰 [Slovenčina](../i18n/sk/docs/architecture/ARCHITECTURE.md) | 🇸🇪 [Svenska](../i18n/sv/docs/architecture/ARCHITECTURE.md) | 🇵🇭 [Filipino](../i18n/phi/docs/architecture/ARCHITECTURE.md) | 🇨🇿 [Čeština](../i18n/cs/docs/architecture/ARCHITECTURE.md) | 🇭🇰 [中文 (繁體)](../i18n/zh-TW/docs/architecture/ARCHITECTURE.md) -OmniRoute is a local AI routing gateway and dashboard built on Next.js. -It provides a single OpenAI-compatible endpoint (`/v1/*`) and routes traffic across multiple upstream providers with translation, fallback, token refresh, and usage tracking. +_最後更新:2026-06-28_ -Core capabilities: +## 執行摘要 -- OpenAI-compatible API surface for CLI/tools (100+ providers, 16 executors) -- Request/response translation across provider formats -- Model combo fallback (multi-model sequence) -- Structured combo steps (`provider + model + connection`) with runtime ordering by `compositeTiers` -- Account-level fallback (multi-account per provider) -- Quota preflight and quota-aware P2C account selection in the main chat path -- OAuth + API-key provider connection management (13 OAuth modules) -- Embedding generation via `/v1/embeddings` (6 providers, 9 models) -- Image generation via `/v1/images/generations` (10+ providers, 20+ models) -- Audio transcription via `/v1/audio/transcriptions` (7 providers) -- Text-to-speech via `/v1/audio/speech` (10 providers) -- Video generation via `/v1/videos/generations` (ComfyUI + SD WebUI) -- Music generation via `/v1/music/generations` (ComfyUI) -- Web search via `/v1/search` (5 providers) -- Moderations via `/v1/moderations` -- Reranking via `/v1/rerank` -- Think tag parsing (`...`) for reasoning models -- Response sanitization for strict OpenAI SDK compatibility -- Role normalization (developer→system, system→user) for cross-provider compatibility -- Structured output conversion (json_schema → Gemini responseSchema) -- Local persistence for providers, keys, aliases, combos, settings, pricing (26 DB modules) -- Usage/cost tracking and request logging -- Optional cloud sync for multi-device/state sync -- IP allowlist/blocklist for API access control -- Thinking budget management (passthrough/auto/custom/adaptive) -- Global system prompt injection -- Session tracking and fingerprinting -- Per-account enhanced rate limiting with provider-specific profiles -- Circuit breaker pattern for provider resilience -- Anti-thundering herd protection with mutex locking -- Signature-based request deduplication cache -- Domain layer: cost rules, fallback policy, lockout policy -- Context Relay: session handoff summaries for account rotation continuity -- Domain state persistence (SQLite write-through cache for fallbacks, budgets, lockouts, circuit breakers) -- Policy engine for centralized request evaluation (lockout → budget → fallback) -- Request telemetry with p50/p95/p99 latency aggregation -- Combo target telemetry and historical combo target health via `combo_execution_key` / `combo_step_id` -- Correlation ID (X-Request-Id) for end-to-end tracing -- Compliance audit logging with opt-out per API key -- Eval framework for LLM quality assurance -- Health dashboard with real-time provider circuit breaker status -- MCP Server (25 tools) with 3 transports (stdio/SSE/Streamable HTTP) -- A2A Server (JSON-RPC 2.0 + SSE) with skills and task lifecycle -- Memory system (extraction, injection, retrieval, summarization) -- Skills system (registry, executor, sandbox, built-in skills) -- MITM proxy with certificate management and DNS handling -- Prompt injection guard middleware -- ACP (Agent Communication Protocol) registry -- Modular OAuth providers (13 individual modules under `src/lib/oauth/providers/`) -- Uninstall/full-uninstall scripts -- OAuth environment repair action -- WebSocket bridge for OpenAI-compatible WS clients (`/v1/ws`) -- Sync token management (issue/revoke, ETag-versioned config bundle download) -- GLM Thinking (`glmt`) first-class provider preset -- Hybrid token counting (provider-side `/messages/count_tokens` with estimation fallback) -- Model alias auto-seeding (30+ cross-proxy dialect normalizations at startup) -- Safe outbound fetch with SSRF guard, private URL blocking, and configurable retry -- Cooldown-aware chat retries with configurable `requestRetry` and `maxRetryIntervalSec` -- Runtime environment validation with Zod at startup -- Compliance audit v2 with pagination, provider CRUD events, and SSRF-blocked validation logging +OmniRoute 是一個建構於 Next.js 上的本地 AI 路由閘道與儀表板。 +它提供單一的 OpenAI 相容端點(`/v1/*`),並透過轉換、備援、令牌刷新與用量追蹤,將流量路由至多個上游供應商。 -Primary runtime model: +核心能力: -- Next.js app routes under `src/app/api/*` implement both dashboard APIs and compatibility APIs -- A shared SSE/routing core in `src/sse/*` + `open-sse/*` handles provider execution, translation, streaming, fallback, and usage +- OpenAI 相容的 API 表面,適用於 CLI/工具(268 個供應商、84 個執行器) +- 跨供應商格式的請求/回應轉換 +- 模型組合備援(多模型序列) +- 結構化組合步驟(`供應商 + 模型 + 連線`),支援執行期依 `compositeTiers` 排序 +- 帳戶層級備援(每個供應商多帳戶) +- 主要聊天路徑中的配額預檢與配額感知 P2C 帳戶選擇 +- OAuth + API 金鑰供應商連線管理(19 個 OAuth 供應商模組) +- 透過 `/v1/embeddings` 生成嵌入向量(6 個供應商、9 個模型) +- 透過 `/v1/images/generations` 生成圖片(10+ 個供應商、20+ 個模型) +- 透過 `/v1/audio/transcriptions` 進行語音轉錄(7 個供應商) +- 透過 `/v1/audio/speech` 進行文字轉語音(10 個供應商) +- 透過 `/v1/videos/generations` 生成影片(ComfyUI + SD WebUI) +- 透過 `/v1/music/generations` 生成音樂(ComfyUI) +- 透過 `/v1/search` 進行網路搜尋(5 個供應商) +- 透過 `/v1/moderations` 進行內容審核 +- 透過 `/v1/rerank` 進行重新排序 +- Think 標籤解析(`...`)用於推理模型 +- 回應淨化處理,確保嚴格的 OpenAI SDK 相容性 +- 角色正規化(developer→system, system→user)以實現跨供應商相容性 +- 結構化輸出轉換(json_schema → Gemini responseSchema) +- 供應商、金鑰、別名、組合、設定、定價的本地持久化(26 個 DB 模組) +- 用量/成本追蹤與請求記錄 +- 選用雲端同步,支援多裝置/狀態同步 +- 用於 API 存取控制的 IP 允許清單/封鎖清單 +- 思考預算管理(透傳/自動/自訂/自適應) +- 全域系統提示注入 +- 工作階段追蹤與指紋辨識 +- 每個帳戶的增強速率限制,附供應商特定設定檔 +- 用於供應商韌性的斷路器模式 +- 使用互斥鎖防止驚群效應 +- 基於簽章的請求去重快取 +- 領域層:成本規則、備援政策、鎖定政策 +- 上下文轉接:帳戶輪換連續性的工作階段交接摘要 +- 領域狀態持久化(SQLite 寫穿透快取,用於備援、預算、鎖定、斷路器) +- 用於集中化請求評估的政策引擎(鎖定 → 預算 → 備援) +- 請求遙測,含 p50/p95/p99 延遲彙總 +- 組合目標遙測及歷史組合目標健康狀態(透過 `combo_execution_key` / `combo_step_id`) +- 關聯 ID(X-Request-Id)實現端到端追蹤 +- 合規稽核記錄,可依 API 金鑰選擇退出 +- 用於 LLM 品質保證的評估框架 +- 健康狀態儀表板,即時顯示供應商斷路器狀態 +- MCP 伺服器(87 個工具)支援 3 種傳輸方式(stdio/SSE/Streamable HTTP) +- A2A 伺服器(JSON-RPC 2.0 + SSE)含技能與任務生命週期 +- 記憶系統(提取、注入、檢索、摘要) +- 技能系統(註冊表、執行器、沙箱、內建技能) +- MITM 代理,含憑證管理與 DNS 處理 +- 提示注入防護中介軟體 +- 提示壓縮管線,含 Caveman、RTK、堆疊管線、壓縮組合、語言套件與分析功能 +- ACP(代理通訊協定)註冊表 +- 模組化 OAuth 供應商(19 個獨立模組,位於 `src/lib/oauth/providers/`) +- 解除安裝/完整解除安裝指令碼 +- OAuth 環境修復動作 +- WebSocket 橋接,供 OpenAI 相容的 WS 客戶端使用(`/v1/ws`) +- 同步令牌管理(簽發/撤銷,ETag 版本化設定套件下載) +- GLM Thinking(`glmt`)第一級供應商預設 +- 混合令牌計數(供應商端 `/messages/count_tokens` 搭配估算備援) +- 模型別名自動播種(啟動時 30+ 跨代理方言正規化) +- 安全的外送請求,含 SSRF 防護、私人 URL 封鎖與可設定的重試 +- 具冷卻感知的聊天重試,含可設定的 `requestRetry` 與 `maxRetryIntervalSec` +- 啟動時使用 Zod 進行執行環境驗證 +- 合規稽核 v2,含分頁、供應商 CRUD 事件與 SSRF 封鎖驗證記錄 -## Scope and Boundaries +主要執行模型: -### In Scope +- `src/app/api/*` 下的 Next.js 應用路由同時實作儀表板 API 與相容性 API +- `src/sse/*` + `open-sse/*` 中的共用 SSE/路由核心負責供應商執行、轉換、串流、備援與用量 -- Local gateway runtime -- Dashboard management APIs -- Provider authentication and token refresh -- Request translation and SSE streaming -- Local state + usage persistence -- Optional cloud sync orchestration +## 參考圖表 -### Out of Scope +v3.8.0 平台的標準版本控制 Mermaid 原始檔位於 +[`docs/diagrams/`](../diagrams/README.md)。以下複製兩張作為參考; +其餘圖表請參閱各領域專屬指南。 -- Cloud service implementation behind `NEXT_PUBLIC_CLOUD_URL` -- Provider SLA/control plane outside local process -- External CLI binaries themselves (Claude CLI, Codex CLI, etc.) +![請求管線 (/v1/chat/completions)](../diagrams/exported/request-pipeline.svg) -## Dashboard Surface (Current) +> 原始檔:[diagrams/request-pipeline.mmd](../diagrams/request-pipeline.mmd) -Main pages under `src/app/(dashboard)/dashboard/`: +![三層韌性模型](../diagrams/exported/resilience-3layers.svg) -- `/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, step-based builder, model routing rules, manual persisted ordering -- `/dashboard/costs` — cost aggregation and pricing visibility -- `/dashboard/analytics` — usage analytics, evaluations, combo target health -- `/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, quota-monitored sessions -- `/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 +> 原始檔:[diagrams/resilience-3layers.mmd](../diagrams/resilience-3layers.mmd) — 亦可參閱 +> [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) 與 `CLAUDE.md` 韌性參考。 -## High-Level System Context +## 範圍與邊界 + +### 涵蓋範圍 + +- 本地閘道執行環境 +- 儀表板管理 API +- 供應商驗證與令牌刷新 +- 請求轉換與 SSE 串流 +- 本地狀態 + 用量持久化 +- 選用雲端同步協調 + +### 不涵蓋範圍 + +- `NEXT_PUBLIC_CLOUD_URL` 背後的雲端服務實作 +- 本地程序外的供應商 SLA/控制平面 +- 外部 CLI 二進位檔案本身(Claude CLI、Codex CLI 等) + +## 儀表板表面(目前版本) + +`src/app/(dashboard)/dashboard/` 下的主要頁面: + +- `/dashboard` — 快速入門 + 供應商概覽 +- `/dashboard/endpoint` — 端點代理 + MCP + A2A + API 端點分頁 +- `/dashboard/providers` — 供應商連線與憑證 +- `/dashboard/combos` — 組合策略、範本、逐步建置器、模型路由規則、手動持久化排序 +- `/dashboard/auto-combo` — 自動組合引擎:評分權重、模式套件、虛擬工廠預設、遙測 +- `/dashboard/costs` — 成本彙總與定價檢視 +- `/dashboard/analytics` — 用量分析、評估、組合目標健康狀態 +- `/dashboard/limits` — 配額/速率控制 +- `/dashboard/cli-tools` — CLI 入門、執行環境偵測、設定產生 +- `/dashboard/agents` — 偵測到的 ACP 代理 + 自訂代理註冊 +- `/dashboard/cloud-agents` — 雲端託管代理任務(Codex Cloud、Devin、Jules)與任務生命週期 +- `/dashboard/skills` — A2A 技能註冊表、沙箱執行、內建技能目錄 +- `/dashboard/memory` — 持久對話記憶體檢視與檢索 +- `/dashboard/webhooks` — 外送 Webhook 訂閱、金鑰輪換、重試統計 +- `/dashboard/batch` — 批次任務提交與進度 +- `/dashboard/cache` — 讀取快取與推理快取統計、清除控制 +- `/dashboard/playground` — 針對任何已設定組合/模型的互動式聊天測試區 +- `/dashboard/changelog` — 應用內更新日誌檢視器(渲染 `CHANGELOG.md`) +- `/dashboard/system` — 執行時期診斷、版本資訊、環境驗證表面 +- `/dashboard/onboarding` — 新安裝的首次執行設定精靈 +- `/dashboard/media` — 圖片/影片/音樂測試區 +- `/dashboard/search-tools` — 搜尋供應商測試與歷史記錄 +- `/dashboard/health` — 運行時間、斷路器、速率限制、配額監控工作階段 +- `/dashboard/logs` — 請求/代理/稽核/主控台記錄 +- `/dashboard/settings` — 系統設定分頁(一般、路由、組合預設等) +- `/dashboard/context/caveman` — Caveman 壓縮規則、語言套件、預覽與輸出模式 +- `/dashboard/context/rtk` — RTK 命令輸出過濾器、預覽與執行時期安全設定 +- `/dashboard/context/combos` — 指派給路由組合的命名壓縮管線 +- `/dashboard/translator` — 轉換器檢視與請求格式轉換預覽 +- `/dashboard/audit` — 合規稽核記錄瀏覽器,含分頁與結構化中繼資料 +- `/dashboard/usage` — 與 `usage_history` 關聯的每次請求用量瀏覽器 +- `/dashboard/compression` — 壓縮分析、統計資料與管線指派 +- `/dashboard/api-manager` — API 金鑰生命週期與模型權限 + +## 高層級系統上下文 ```mermaid flowchart LR - subgraph Clients[Developer Clients] + subgraph Clients[開發者客戶端] C1[Claude Code] C2[Codex CLI] C3[OpenClaw / Droid / Cline / Continue / Roo] - C4[Custom OpenAI-compatible clients] - BROWSER[Browser Dashboard] + C4[自訂 OpenAI 相容客戶端] + BROWSER[瀏覽器儀表板] end - subgraph Router[OmniRoute Local Process] - API[V1 Compatibility API\n/v1/*] - DASH[Dashboard + Management API\n/api/*] - CORE[SSE + Translation Core\nopen-sse + src/sse] + subgraph Router[OmniRoute 本地程序] + API[V1 相容性 API\n/v1/*] + DASH[儀表板 + 管理 API\n/api/*] + CORE[SSE + 轉換核心\nopen-sse + src/sse] DB[(storage.sqlite)] - UDB[(usage tables + log artifacts)] + UDB[(用量表格 + 記錄工件)] end - subgraph Upstreams[Upstream Providers] - P1[OAuth Providers\nClaude/Codex/Gemini/Qwen/Qoder/GitHub/Kiro/Cursor/Antigravity] - P2[API Key Providers\nOpenAI/Anthropic/OpenRouter/GLM/Kimi/MiniMax\nDeepSeek/Groq/xAI/Mistral/Perplexity\nTogether/Fireworks/Cerebras/Cohere/NVIDIA] - P3[Compatible Nodes\nOpenAI-compatible / Anthropic-compatible] + subgraph Upstreams[上游供應商] + P1[OAuth 供應商\nClaude/Codex/Gemini/Qoder/GitHub/Kiro/Cursor/Antigravity] + P2[API 金鑰供應商\nOpenAI/Anthropic/OpenRouter/GLM/Kimi/MiniMax\nDeepSeek/Groq/xAI/Mistral/Perplexity\nTogether/Fireworks/Cerebras/Cohere/NVIDIA] + P3[相容節點\nOpenAI 相容 / Anthropic 相容] end - subgraph Cloud[Optional Cloud Sync] - CLOUD[Cloud Sync Endpoint\nNEXT_PUBLIC_CLOUD_URL] + subgraph Cloud[選用雲端同步] + CLOUD[雲端同步端點\nNEXT_PUBLIC_CLOUD_URL] end C1 --> API @@ -164,313 +202,477 @@ flowchart LR DASH --> CLOUD ``` -## Core Runtime Components +## 核心執行時期元件 -## 1) API and Routing Layer (Next.js App Routes) +## 1) API 與路由層(Next.js 應用路由) -Main directories: +主要目錄: -- `src/app/api/v1/*` and `src/app/api/v1beta/*` for compatibility APIs -- `src/app/api/*` for management/configuration APIs -- Next rewrites in `next.config.mjs` map `/v1/*` to `/api/v1/*` +- `src/app/api/v1/*` 與 `src/app/api/v1beta/*` 用於相容性 API +- `src/app/api/*` 用於管理/設定 API +- `next.config.mjs` 中的 Next 重寫規則將 `/v1/*` 對應到 `/api/v1/*` -Important compatibility routes: +重要的相容性路由: - `src/app/api/v1/chat/completions/route.ts` - `src/app/api/v1/messages/route.ts` - `src/app/api/v1/responses/route.ts` -- `src/app/api/v1/models/route.ts` — includes custom models with `custom: true` -- `src/app/api/v1/embeddings/route.ts` — embedding generation (6 providers) -- `src/app/api/v1/images/generations/route.ts` — image generation (4+ providers incl. Antigravity/Nebius) +- `src/app/api/v1/models/route.ts` — 包含 `custom: true` 的自訂模型 +- `src/app/api/v1/embeddings/route.ts` — 嵌入向量生成(6 個供應商) +- `src/app/api/v1/images/generations/route.ts` — 圖片生成(4+ 個供應商,含 Antigravity/Nebius) - `src/app/api/v1/messages/count_tokens/route.ts` -- `src/app/api/v1/providers/[provider]/chat/completions/route.ts` — dedicated per-provider chat -- `src/app/api/v1/providers/[provider]/embeddings/route.ts` — dedicated per-provider embeddings -- `src/app/api/v1/providers/[provider]/images/generations/route.ts` — dedicated per-provider images +- `src/app/api/v1/providers/[provider]/chat/completions/route.ts` — 專屬的每個供應商聊天 +- `src/app/api/v1/providers/[provider]/embeddings/route.ts` — 專屬的每個供應商嵌入 +- `src/app/api/v1/providers/[provider]/images/generations/route.ts` — 專屬的每個供應商圖片 - `src/app/api/v1beta/models/route.ts` - `src/app/api/v1beta/models/[...path]/route.ts` -Management domains: +管理領域: -- Auth/settings: `src/app/api/auth/*`, `src/app/api/settings/*` -- Providers/connections: `src/app/api/providers*` -- Provider nodes: `src/app/api/provider-nodes*` -- Custom models: `src/app/api/provider-models` (GET/POST/DELETE) -- Model catalog: `src/app/api/models/route.ts` (GET) -- Proxy config: `src/app/api/settings/proxy` (GET/PUT/DELETE) + `src/app/api/settings/proxy/test` (POST) -- OAuth: `src/app/api/oauth/*` -- Keys/aliases/combos/pricing: `src/app/api/keys*`, `src/app/api/models/alias`, `src/app/api/combos*`, `src/app/api/pricing` -- Usage: `src/app/api/usage/*` -- Sync/cloud: `src/app/api/sync/*`, `src/app/api/cloud/*` -- CLI tooling helpers: `src/app/api/cli-tools/*` -- IP filter: `src/app/api/settings/ip-filter` (GET/PUT) -- Thinking budget: `src/app/api/settings/thinking-budget` (GET/PUT) -- System prompt: `src/app/api/settings/system-prompt` (GET/PUT) -- Sessions: `src/app/api/sessions` (GET) -- Rate limits: `src/app/api/rate-limits` (GET) -- Resilience: `src/app/api/resilience` (GET/PATCH) — request queue, connection cooldown, provider breaker, wait-for-cooldown config -- Resilience reset: `src/app/api/resilience/reset` (POST) — reset provider breakers -- Cache stats: `src/app/api/cache/stats` (GET/DELETE) -- Telemetry: `src/app/api/telemetry/summary` (GET) -- Budget: `src/app/api/usage/budget` (GET/POST) -- Fallback chains: `src/app/api/fallback/chains` (GET/POST/DELETE) -- Compliance audit: `src/app/api/compliance/audit-log` (GET, with pagination + structured metadata) -- Evals: `src/app/api/evals` (GET/POST), `src/app/api/evals/[suiteId]` (GET) -- Policies: `src/app/api/policies` (GET/POST) -- Sync tokens: `src/app/api/sync/tokens` (GET/POST), `src/app/api/sync/tokens/[id]` (GET/DELETE) -- Config bundle: `src/app/api/sync/bundle` (GET, ETag-versioned snapshot of settings/providers/combos/keys) -- WebSocket: `src/app/api/v1/ws/route.ts` — Upgrade handler for OpenAI-compatible WS clients +- 驗證/設定:`src/app/api/auth/*`、`src/app/api/settings/*` +- 供應商/連線:`src/app/api/providers*` +- 供應商節點:`src/app/api/provider-nodes*` +- 自訂模型:`src/app/api/provider-models`(GET/POST/DELETE) +- 模型目錄:`src/app/api/models/route.ts`(GET) +- 代理設定:`src/app/api/settings/proxy`(GET/PUT/DELETE)+ `src/app/api/settings/proxy/test`(POST) +- OAuth:`src/app/api/oauth/*` +- 金鑰/別名/組合/定價:`src/app/api/keys*`、`src/app/api/models/alias`、`src/app/api/combos*`、`src/app/api/pricing` +- 用量:`src/app/api/usage/*` +- 同步/雲端:`src/app/api/sync/*`、`src/app/api/cloud/*` +- CLI 工具輔助:`src/app/api/cli-tools/*` +- IP 過濾器:`src/app/api/settings/ip-filter`(GET/PUT) +- 思考預算:`src/app/api/settings/thinking-budget`(GET/PUT) +- 系統提示:`src/app/api/settings/system-prompt`(GET/PUT) +- 壓縮:`src/app/api/settings/compression`、`src/app/api/compression/*` 與 `src/app/api/context/*` +- 工作階段:`src/app/api/sessions`(GET) +- 速率限制:`src/app/api/rate-limits`(GET) +- 韌性:`src/app/api/resilience`(GET/PATCH)— 請求佇列、連線冷卻、供應商斷路器、等待冷卻設定 +- 韌性重置:`src/app/api/resilience/reset`(POST)— 重置供應商斷路器 +- 快取統計:`src/app/api/cache/stats`(GET/DELETE) +- 遙測:`src/app/api/telemetry/summary`(GET) +- 預算:`src/app/api/usage/budget`(GET/POST) +- 備援鏈:`src/app/api/fallback/chains`(GET/POST/DELETE) +- 合規稽核:`src/app/api/compliance/audit-log`(GET,含分頁 + 結構化中繼資料) +- 評估:`src/app/api/evals`(GET/POST)、`src/app/api/evals/[suiteId]`(GET) +- 政策:`src/app/api/policies`(GET/POST) +- 同步令牌:`src/app/api/sync/tokens`(GET/POST)、`src/app/api/sync/tokens/[id]`(GET/DELETE) +- 設定套件:`src/app/api/sync/bundle`(GET,設定/供應商/組合/金鑰的 ETag 版本化快照) +- WebSocket:`src/app/api/v1/ws/route.ts` — OpenAI 相容 WS 客戶端的升級處理器 -## 2) SSE + Translation Core +## 2) SSE + 轉換核心 -Main flow modules: +主要流程模組: -- Entry: `src/sse/handlers/chat.ts` -- Core orchestration: `open-sse/handlers/chatCore.ts` -- Provider execution adapters: `open-sse/executors/*` -- Format detection/provider config: `open-sse/services/provider.ts` -- Model parse/resolve: `src/sse/services/model.ts`, `open-sse/services/model.ts` -- Account fallback logic: `open-sse/services/accountFallback.ts` -- Translation registry: `open-sse/translator/index.ts` -- Stream transformations: `open-sse/utils/stream.ts`, `open-sse/utils/streamHandler.ts` -- Usage extraction/normalization: `open-sse/utils/usageTracking.ts` -- Think tag parser: `open-sse/utils/thinkTagParser.ts` -- Embedding handler: `open-sse/handlers/embeddings.ts` -- Embedding provider registry: `open-sse/config/embeddingRegistry.ts` -- Image generation handler: `open-sse/handlers/imageGeneration.ts` -- Image provider registry: `open-sse/config/imageRegistry.ts` -- Response sanitization: `open-sse/handlers/responseSanitizer.ts` -- Role normalization: `open-sse/services/roleNormalizer.ts` +- 入口:`src/sse/handlers/chat.ts` +- 核心協調:`open-sse/handlers/chatCore.ts` +- 供應商執行轉接器:`open-sse/executors/*` +- 格式偵測/供應商設定:`open-sse/services/provider.ts` +- 模型解析/解析:`src/sse/services/model.ts`、`open-sse/services/model.ts` +- 帳戶備援邏輯:`open-sse/services/accountFallback.ts` +- 轉換註冊表:`open-sse/translator/index.ts` +- 串流轉換:`open-sse/utils/stream.ts`、`open-sse/utils/streamHandler.ts` +- 用量提取/正規化:`open-sse/utils/usageTracking.ts` +- Think 標籤解析器:`open-sse/utils/thinkTagParser.ts` +- 嵌入處理器:`open-sse/handlers/embeddings.ts` +- 嵌入供應商註冊表:`open-sse/config/embeddingRegistry.ts` +- 圖片生成處理器:`open-sse/handlers/imageGeneration.ts` +- 圖片供應商註冊表:`open-sse/config/imageRegistry.ts` +- 回應淨化:`open-sse/handlers/responseSanitizer.ts` +- 角色正規化:`open-sse/services/roleNormalizer.ts` -Services (business logic): +服務(商業邏輯): -- Account selection/scoring: `open-sse/services/accountSelector.ts` -- Context lifecycle management: `open-sse/services/contextManager.ts` -- IP filter enforcement: `open-sse/services/ipFilter.ts` -- Session tracking: `open-sse/services/sessionManager.ts` -- Request deduplication: `open-sse/services/signatureCache.ts` -- System prompt injection: `open-sse/services/systemPrompt.ts` -- Thinking budget management: `open-sse/services/thinkingBudget.ts` -- Wildcard model routing: `open-sse/services/wildcardRouter.ts` -- Rate limit management: `open-sse/services/rateLimitManager.ts` -- Circuit breaker: `open-sse/services/circuitBreaker.ts` -- Context handoff: `open-sse/services/contextHandoff.ts` — handoff summary generation and injection for context-relay strategy -- Codex quota fetcher: `open-sse/services/codexQuotaFetcher.ts` — fetches Codex quota for context-relay handoff decisions -- Cooldown-aware retry: `src/sse/services/cooldownAwareRetry.ts` — per-model cooldown retries with configurable `requestRetry` / `maxRetryIntervalSec` -- Safe outbound fetch: `src/shared/network/safeOutboundFetch.ts` — guarded provider/model fetch with SSRF guard, private-URL blocking, retry, and timeout -- Outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — validates provider URLs against private/localhost CIDR ranges -- Provider request defaults: `open-sse/services/providerRequestDefaults.ts` — provider-level `maxTokens`, `temperature`, `thinkingBudgetTokens` defaults -- GLM provider constants: `open-sse/config/glmProvider.ts` — shared GLM models, quota URLs, GLMT timeout/defaults -- Antigravity upstream: `open-sse/config/antigravityUpstream.ts` — base URL and discovery path constants -- Codex client constants: `open-sse/config/codexClient.ts` — versioned user-agent and client-version values -- Model alias seed: `src/lib/modelAliasSeed.ts` — seeds 30+ cross-proxy dialect aliases at startup +- 帳戶選擇/評分:`open-sse/services/accountSelector.ts` +- 上下文生命週期管理:`open-sse/services/contextManager.ts` +- IP 過濾器執行:`open-sse/services/ipFilter.ts` +- 工作階段追蹤:`open-sse/services/sessionManager.ts` +- 請求去重:`open-sse/services/signatureCache.ts` +- 系統提示注入:`open-sse/services/systemPrompt.ts` +- 思考預算管理:`open-sse/services/thinkingBudget.ts` +- 萬用字元模型路由:`open-sse/services/wildcardRouter.ts` +- 速率限制管理:`open-sse/services/rateLimitManager.ts` +- 斷路器:`src/shared/utils/circuitBreaker.ts` +- 上下文交接:`open-sse/services/contextHandoff.ts` — 用於上下文轉接策略的交接摘要產生與注入 +- 壓縮:`open-sse/services/compression/*` — 供應商轉換前的主動壓縮;包含 Caveman 規則、RTK 過濾器、堆疊管線、壓縮組合、統計資料與驗證 +- Codex 配額擷取器:`open-sse/services/codexQuotaFetcher.ts` — 擷取 Codex 配額用於上下文轉接交接決策 +- 具冷卻感知的重試:`src/sse/services/cooldownAwareRetry.ts` — 每個模型的冷卻重試,具可設定的 `requestRetry` / `maxRetryIntervalSec` +- 安全外送請求:`src/shared/network/safeOutboundFetch.ts` — 受防護的供應商/模型請求,含 SSRF 防護、私人 URL 封鎖、重試與逾時 +- 外送 URL 防護:`src/shared/network/outboundUrlGuard.ts` — 驗證供應商 URL 是否位於私人/本地 CIDR 範圍 +- 供應商請求預設值:`open-sse/services/providerRequestDefaults.ts` — 供應商層級的 `maxTokens`、`temperature`、`thinkingBudgetTokens` 預設值 +- GLM 供應商常數:`open-sse/config/glmProvider.ts` — 共用的 GLM 模型、配額 URL、GLMT 逾時/預設值 +- Antigravity 上游:`open-sse/config/antigravityUpstream.ts` — 基礎 URL 與探索路徑常數 +- Codex 客戶端常數:`open-sse/config/codexClient.ts` — 版本化的使用者代理與客戶端版本值 +- 模型別名播種:`src/lib/modelAliasSeed.ts` — 啟動時播種 30+ 跨代理方言別名 -Domain layer modules: +領域層模組: -- Cost rules/budgets: `src/lib/domain/costRules.ts` -- Fallback policy: `src/lib/domain/fallbackPolicy.ts` -- Combo resolver: `src/lib/domain/comboResolver.ts` -- Lockout policy: `src/lib/domain/lockoutPolicy.ts` -- Policy engine: `src/domain/policyEngine.ts` — centralized lockout → budget → fallback evaluation -- Error codes catalog: `src/lib/domain/errorCodes.ts` -- Request ID: `src/lib/domain/requestId.ts` -- Fetch timeout: `src/lib/domain/fetchTimeout.ts` -- Request telemetry: `src/lib/domain/requestTelemetry.ts` -- Compliance/audit: `src/lib/domain/compliance/index.ts` -- Eval runner: `src/lib/domain/evalRunner.ts` -- Domain state persistence: `src/lib/db/domainState.ts` — SQLite CRUD for fallback chains, budgets, cost history, lockout state, circuit breakers +- 成本規則/預算:`src/domain/costRules.ts` +- 備援政策:`src/domain/fallbackPolicy.ts` +- 組合解析器:`src/domain/comboResolver.ts` +- 鎖定政策:`src/domain/lockoutPolicy.ts` +- 政策引擎:`src/domain/policyEngine.ts` — 集中化的鎖定 → 預算 → 備援評估 +- 錯誤碼目錄:`src/shared/constants/errorCodes.ts` +- 請求 ID:`src/shared/utils/requestId.ts` +- 擷取逾時:`src/shared/utils/fetchTimeout.ts` +- 請求遙測:`src/shared/utils/requestTelemetry.ts` +- 合規/稽核:`src/lib/compliance/index.ts` +- 評估執行器:`src/lib/evals/evalRunner.ts` +- 領域狀態持久化:`src/lib/db/domainState.ts` — 備援鏈、預算、成本歷史、鎖定狀態、斷路器的 SQLite CRUD -OAuth provider modules (13 individual files under `src/lib/oauth/providers/`): +OAuth 供應商模組(`src/lib/oauth/providers/` 下的 16 個個別檔案): -- Registry index: `src/lib/oauth/providers/index.ts` -- Individual providers: `claude.ts`, `codex.ts`, `gemini.ts`, `antigravity.ts`, `qoder.ts`, `qwen.ts`, `kimi-coding.ts`, `github.ts`, `kiro.ts`, `cursor.ts`, `kilocode.ts`, `cline.ts` -- Thin wrapper: `src/lib/oauth/providers.ts` — re-exports from individual modules +- 註冊表索引:`src/lib/oauth/providers/index.ts` +- 個別供應商:`claude.ts`、`codex.ts`、`gemini.ts`、`antigravity.ts`、`agy.ts`、`qoder.ts`、`qwen.ts`、`kimi-coding.ts`、`github.ts`、`kiro.ts`、`cursor.ts`、`kilocode.ts`、`cline.ts`、`windsurf.ts`、`gitlab-duo.ts`、`trae.ts` +- 薄包裝層:`src/lib/oauth/providers.ts` — 從個別模組重新匯出 -## 3) Persistence Layer +## 5) 嵌入式服務(v3.8.4) -Primary state DB (SQLite): +OmniRoute 可以安裝、監督並路由至本地運行的 AI 工具程序, +稱為**嵌入式服務**。v3.8.4 中提供了兩項:9Router 和 CLIProxyAPI。 -- Core infra: `src/lib/db/core.ts` (better-sqlite3, migrations, WAL) -- Re-export facade: `src/lib/localDb.ts` (thin compatibility layer for callers) -- file: `${DATA_DIR}/storage.sqlite` (or `$XDG_CONFIG_HOME/omniroute/storage.sqlite` when set, else `~/.omniroute/storage.sqlite`) -- entities (tables + KV namespaces): providerConnections, providerNodes, modelAliases, combos, apiKeys, settings, pricing, **customModels**, **proxyConfig**, **ipFilter**, **thinkingBudget**, **systemPrompt** +架構層: -Usage persistence: +- **UI**(`/dashboard/providers/services`)— 雙分頁頁面,含生命週期控制、 + 即時記錄串流、API 金鑰管理,以及(針對 9Router)透過內部反向代理提供的嵌入式原生 UI。 +- **API**(`/api/services/{name}/*`)— 9Router 有 8 個端點,CLIProxyAPI 有 7 個端點, + 全部標記為 **LOCAL_ONLY**(硬性規則 #17)。共用的 `GET /api/services/[name]/logs` + SSE 端點同時服務兩項服務。 +- **監督器**(`src/lib/services/`)— 泛用的 `ServiceSupervisor` 類別包裝 + `child_process.spawn`,持有 5 MB 環形緩衝區用於 SSE 記錄串流、健康狀態 + 探測迴圈、原子操作鎖與 SIGTERM→SIGKILL 優雅關機。 + `bootstrap.ts` 在程序啟動時連接所有已設定的服務。 +- **供應商/執行器**(`open-sse/executors/ninerouter.ts`)— 9Router 以真實供應商型態 + 暴露。模型前綴為 `9router/{sub}/{model}`,每 5 分鐘從 9Router 的 `/v1/models` 端點同步一次。 -- facade: `src/lib/usageDb.ts` (decomposed modules in `src/lib/usage/*`) -- SQLite tables in `storage.sqlite`: `usage_history`, `call_logs`, `proxy_logs` -- optional file artifacts remain for compatibility/debug (`${DATA_DIR}/log.txt`, `${DATA_DIR}/call_logs/`, `/logs/...`) -- legacy JSON files are migrated to SQLite by startup migrations when present +深入探討:`docs/frameworks/EMBEDDED-SERVICES.md` -Domain State DB (SQLite): +## 主要子系統(v3.8.0) -- `src/lib/db/domainState.ts` — CRUD operations for domain state -- Tables (created in `src/lib/db/core.ts`): `domain_fallback_chains`, `domain_budgets`, `domain_cost_history`, `domain_lockout_state`, `domain_circuit_breakers` -- Write-through cache pattern: in-memory Maps are authoritative at runtime; mutations are written synchronously to SQLite; state is restored from DB on cold start +### A. 自動組合引擎 -## 4) Auth + Security Surfaces +自動組合在請求時動態評分並選取路由目標,而非依賴靜態組合定義。它驅動 `auto/*` 模型前綴系列。 -- Dashboard cookie auth: `src/proxy.ts`, `src/app/api/auth/login/route.ts` -- API key generation/verification: `src/shared/utils/apiKey.ts` -- Provider secrets persisted in `providerConnections` entries -- Outbound proxy support via `open-sse/utils/proxyFetch.ts` (env vars) and `open-sse/utils/networkProxy.ts` (configurable per-provider or global) -- SSRF / outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — blocks private/loopback/link-local ranges for all provider calls -- Runtime env validation: `src/lib/env/runtimeEnv.ts` — Zod schema for all environment variables, surfaced as startup errors/warnings -- Sync tokens: `src/lib/db/syncTokens.ts` — scoped tokens for config bundle download endpoints; backed by `sync_tokens` SQLite table (migration `024_create_sync_tokens.sql`) -- WebSocket handshake auth: `src/lib/ws/handshake.ts` — validates WS upgrade requests via API key or session cookie +- 引擎入口:`open-sse/services/autoCombo/`(`autoComboEngine.ts`、`scoringEngine.ts`、`virtualFactory.ts`、`modePacks.ts`) +- 解析器:`src/domain/comboResolver.ts`(`auto/` 前綴的自動偵測) +- 儀表板:`/dashboard/auto-combo` +- 遙測:`auto_combo_decisions` SQLite 表格 -## 5) Cloud Sync +關鍵能力: -- Scheduler init: `src/lib/initCloudSync.ts`, `src/shared/services/initializeCloudSync.ts`, `src/shared/services/modelSyncScheduler.ts` -- Periodic task: `src/shared/services/cloudSyncScheduler.ts` -- Periodic task: `src/shared/services/modelSyncScheduler.ts` -- Control route: `src/app/api/sync/cloud/route.ts` +- **17 種路由策略**(優先級、加權、先填滿、輪詢、P2C、隨機、 + 最少使用、成本最佳化、重置感知、重置視窗、餘裕空間、嚴格隨機、 + **自動**、lkpg、上下文最佳化、上下文轉接、**融合**,加上備援路徑)— + auto 是 v3.8.0 的主要新增功能;`fusion`(面板扇出 + 判斷綜合, + `open-sse/services/fusion.ts`)是 v3.8.36 的新功能。 +- **9 因子評分**:成本、p95 延遲、成功率、配額餘裕、鎖定 + 接近度、斷路器狀態、近期失敗、模型可用性與標籤親和性。 +- **虛擬工廠**在沒有相符的命名組合時實例化暫時組合, + 從健康活躍的供應商連線中篩選候選者。 +- **自動前綴**:`auto/coding`、`auto/cheap`、`auto/fast`、`auto/offline`、 + `auto/smart`、`auto/lkgp` — 每個都有調整過的權重設定檔。 +- **4 種模式套件**:coding、fast、cheap、smart — 以預設權重 + 設定出貨,可從儀表板呼叫。 -## Request Lifecycle (`/v1/chat/completions`) +完整的演算法細節(因子公式、權重調整)請參閱 +[`docs/routing/AUTO-COMBO.md`](../routing/AUTO-COMBO.md)。 + +### B. 雲端代理 + +雲端代理將第三方託管的程式碼代理平台(Codex Cloud、Devin、 +Jules)包裝在統一的 DB 支援任務生命週期後方。所有任務建立/檢視 +端點都需要管理驗證。 + +- 模組根目錄:`src/lib/cloudAgent/`(`baseAgent.ts`、`registry.ts`、`api.ts`、 + `types.ts`、`db.ts`,加上 `agents/` 下的每個代理子目錄) +- 每個代理的實作:`agents/codex/`、`agents/devin/`、`agents/jules/` +- 公開端點:`/api/v1/agents/tasks/*`(列表/建立/取得/取消) +- 管理端點:`/api/cloud/*`(佈建、狀態、批次) +- 儀表板:`/dashboard/cloud-agents` +- 儲存:`cloud_agent_tasks` 表格 + +每個代理的佈建與 OAuth 細節,請參閱 +[`docs/frameworks/CLOUD_AGENT.md`](../frameworks/CLOUD_AGENT.md)。 + +### C. 護欄 + +護欄模組是一個可熱重載的中介軟體層,檢查請求 +與回應中的 PII、提示注入與不安全的視覺內容。違規 +會以 HTTP **503** 加上結構化錯誤碼短路請求,讓 +下游呼叫者可以重試或分支。 + +- 模組根目錄:`src/lib/guardrails/`(`base.ts`、`registry.ts`、`piiMasker.ts`、 + `promptInjection.ts`、`visionBridge.ts`、`visionBridgeHelpers.ts`) +- 熱重載:註冊表監聽設定變更並原地重建鏈 +- 接入點:聊天處理器入口、圖片生成處理器、回應淨化器 +- HTTP 合約:違規以 `503` 狀態碼搭配 `error.code = "GUARDRAIL_VIOLATION"` 呈現 + +規則集撰寫與閾值調整,請參閱 +[`docs/security/GUARDRAILS.md`](../security/GUARDRAILS.md)。 + +### D. 領域層 + +`src/domain/` 命名空間集中化政策決策,讓路由處理器無需 +自行組合鎖定/預算/備援邏輯。 + +- 政策引擎:`src/domain/policyEngine.ts` — 執行前評估的單一入口點(鎖定 → 預算 → 備援順序) +- 成本規則:`src/domain/costRules.ts` +- 備援政策:`src/domain/fallbackPolicy.ts` +- 鎖定政策:`src/domain/lockoutPolicy.ts` +- 基於標籤的路由:`src/domain/tagRouter.ts` +- 組合解析器:`src/domain/comboResolver.ts` — 將組合名稱、`auto/*` 前綴與萬用字元模型目標解析為具體執行計畫 +- 連線/模型規則連接器:`src/domain/connectionModelRules.ts` +- 模型可用性快照:`src/domain/modelAvailability.ts` +- 供應商到期追蹤:`src/domain/providerExpiration.ts` +- 配額快取:`src/domain/quotaCache.ts` +- 降級狀態:`src/domain/degradation.ts` +- 設定稽核:`src/domain/configAudit.ts` +- OmniRoute 回應中繼資料建置器:`src/domain/omnirouteResponseMeta.ts` +- 評估子系統:`src/domain/assessment/` — 定期評估任務 + +### E. 授權管線 + +授權管線對每個傳入請求進行分類,並在調度前套用適當的政策鏈。 + +- 管線入口:`src/server/authz/pipeline.ts` +- 請求分類器:`src/server/authz/classify.ts` — 區分公開相容性路由與管理路由 +- 公開路由清單:`src/shared/constants/publicApiRoutes.ts` +- 政策:`src/server/authz/policies/` — 可組合的斷言(`requireApiKey`、`requireManagement`、`requireFreshAuth` 等) +- 標頭工具:`src/server/authz/headers.ts` +- 斷言輔助:`src/server/authz/assertAuth.ts` +- 請求上下文:`src/server/authz/context.ts` + +公開路由與管理路由之間有嚴格邊界:代理/冷卻 API 與供應商變更需要管理驗證(缺少時回傳 HTTP 401)。 + +完整的路由分類規則,請參閱 +[`docs/architecture/AUTHZ_GUIDE.md`](./AUTHZ_GUIDE.md)。 + +### F. 工作流程 FSM 與任務感知路由器 + +一個有限狀態機驅動的路由器,疊加在組合選擇之上,根據偵測到的工作流程階段(規劃、執行、審查)與背景任務親和性來引導流量。 + +- 工作流程 FSM:`open-sse/services/workflowFSM.ts` +- 任務感知路由器:`open-sse/services/taskAwareRouter.ts` +- 背景任務偵測器:`open-sse/services/backgroundTaskDetector.ts` +- 意圖分類器:`open-sse/services/intentClassifier.ts` + +FSM 轉換結果饋入自動組合的評分,對背景/自動化任務偏向較便宜的模型,對互動式規劃/審查輪次偏向較強的模型。 + +### G. 供應商專屬韌性 + +數個供應商配備了專用的韌性與隱匿模組,建構在全域斷路器 / 連線冷卻 / 模型鎖定層之上: + +- Antigravity 429 引擎:`open-sse/services/antigravity429Engine.ts`(輪換身分、清除回應標頭、透過 `antigravityCredits.ts`、`antigravityHeaderScrub.ts`、`antigravityHeaders.ts`、`antigravityIdentity.ts`、`antigravityObfuscation.ts`、`antigravityVersion.ts` 驅動點數/版本追蹤) +- ModelScope 配額政策:`open-sse/services/modelscopePolicy.ts` +- Claude Code CCH(相容性通道握手):`open-sse/services/claudeCodeCCH.ts`,加上 `claudeCodeCompatible.ts`、`claudeCodeConstraints.ts`、`claudeCodeExtraRemap.ts`、`claudeCodeToolRemapper.ts` +- Claude Code 指紋塑造:`open-sse/services/claudeCodeFingerprint.ts` +- Claude Code 混淆:`open-sse/services/claudeCodeObfuscation.ts` +- ChatGPT TLS 客戶端:`open-sse/services/chatgptTlsClient.ts`(curl-impersonate 風格的 ChatGPT-Web 工作階段) +- ChatGPT 圖片快取:`open-sse/services/chatgptImageCache.ts` + +完整的隱匿手冊與操作指南,請參閱 +[`docs/security/STEALTH_GUIDE.md`](../security/STEALTH_GUIDE.md)。 + +### H. Webhook、推理快取、讀取快取 + +- **Webhook** — 用於供應商/帳戶/任務事件的外送調度。 + - 調度器:`src/lib/webhookDispatcher.ts` + - 儲存:`webhooks` SQLite 表格(透過 `src/lib/db/webhooks.ts`) + - 儀表板:`/dashboard/webhooks`(訂閱、秘密、重試歷史) + - 事件分類與重試語意,請參閱 [`docs/frameworks/WEBHOOKS.md`](../frameworks/WEBHOOKS.md)。 +- **推理快取** — 為會發出思考令牌的供應商(Claude、GLMT 等)提供可重播的推理區塊,讓連續輪次可以跳過重新思考。 + - DB 層:`src/lib/db/reasoningCache.ts` + - 服務層:`open-sse/services/reasoningCache.ts` + - 重播語意,請參閱 [`docs/routing/REASONING_REPLAY.md`](../routing/REASONING_REPLAY.md)。 +- **讀取快取** — 以簽章為鍵的短生命週期回應快取,用於收斂來自損壞上游 SDK 的相同重試。 + - DB 層:`src/lib/db/readCache.ts` + - 統計端點:`GET /api/cache/stats`,儀表板位於 `/dashboard/cache` + +## 3) 持久化層 + +主要狀態 DB(SQLite): + +- 核心基礎設施:`src/lib/db/core.ts`(better-sqlite3、遷移、WAL) +- 重新匯出外觀:`src/lib/localDb.ts`(供呼叫者使用的薄相容層) +- 檔案:`${DATA_DIR}/storage.sqlite`(或設定了 `$XDG_CONFIG_HOME/omniroute/storage.sqlite` 時使用該路徑,否則為 `~/.omniroute/storage.sqlite`) +- 實體(表格 + KV 命名空間):providerConnections、providerNodes、modelAliases、combos、apiKeys、settings、pricing、**customModels**、**proxyConfig**、**ipFilter**、**thinkingBudget**、**systemPrompt** + +用量持久化: + +- 外觀:`src/lib/usageDb.ts`(`src/lib/usage/*` 中的分解模組) +- `storage.sqlite` 中的 SQLite 表格:`usage_history`、`call_logs`、`proxy_logs` +- 為相容性/除錯目的保留的選用檔案工件(`${DATA_DIR}/log.txt`、`${DATA_DIR}/call_logs/`、`/logs/...`) +- 舊版 JSON 檔案在啟動遷移時會移轉至 SQLite(若存在) + +領域狀態 DB(SQLite): + +- `src/lib/db/domainState.ts` — 領域狀態的 CRUD 操作 +- 表格(在 `src/lib/db/core.ts` 中建立):`domain_fallback_chains`、`domain_budgets`、`domain_cost_history`、`domain_lockout_state`、`domain_circuit_breakers` +- 寫穿透快取模式:記憶體中的 Maps 在執行時期具有權威性;變更同步寫入 SQLite;冷啟動時從 DB 恢復狀態 + +## 4) 驗證 + 安全表面 + +- 儀表板 Cookie 驗證:`src/proxy.ts`、`src/app/api/auth/login/route.ts` +- API 金鑰生成/驗證:`src/shared/utils/apiKey.ts` +- 供應商秘密儲存在 `providerConnections` 項目中 +- 透過 `open-sse/utils/proxyFetch.ts`(環境變數)與 `open-sse/utils/networkProxy.ts`(可針對每個供應商或全域設定)支援外送代理 +- SSRF / 外送 URL 防護:`src/shared/network/outboundUrlGuard.ts` — 封鎖所有供應商呼叫的私人/迴路/鏈結本地範圍 +- 執行時期環境驗證:`src/lib/env/runtimeEnv.ts` — 所有環境變數的 Zod 架構,以啟動錯誤/警告形式呈現 +- 同步令牌:`src/lib/db/syncTokens.ts` — 用於設定套件下載端點的範圍限定令牌;由 `sync_tokens` SQLite 表格支援(遷移 `024_create_sync_tokens.sql`) +- WebSocket 握手驗證:`src/lib/ws/handshake.ts` — 透過 API 金鑰或工作階段 Cookie 驗證 WS 升級請求 + +## 5) 雲端同步 + +- 排程器初始化:`src/lib/initCloudSync.ts`、`src/shared/services/initializeCloudSync.ts`、`src/shared/services/modelSyncScheduler.ts` +- 定期任務:`src/shared/services/cloudSyncScheduler.ts` +- 定期任務:`src/shared/services/modelSyncScheduler.ts` +- 控制路由:`src/app/api/sync/cloud/route.ts` + +## 請求生命週期(`/v1/chat/completions`) ```mermaid sequenceDiagram autonumber - participant Client as CLI/SDK Client + participant Client as CLI/SDK 客戶端 participant Route as /api/v1/chat/completions participant Chat as src/sse/handlers/chat participant Core as open-sse/handlers/chatCore - participant Model as Model Resolver - participant Auth as Credential Selector - participant Exec as Provider Executor - participant Prov as Upstream Provider - participant Stream as Stream Translator + participant Model as 模型解析器 + participant Auth as 憑證選擇器 + participant Exec as 供應商執行器 + participant Prov as 上游供應商 + participant Stream as 串流轉換器 participant Usage as usageDb Client->>Route: POST /v1/chat/completions Route->>Chat: handleChat(request) - Chat->>Model: parse/resolve model or combo + Chat->>Model: 解析/解析模型或組合 - alt Combo model - Chat->>Chat: iterate combo models (handleComboChat) + alt Combo 模型 + Chat->>Chat: 迭代組合模型 (handleComboChat) end Chat->>Auth: getProviderCredentials(provider) - Auth-->>Chat: active account + tokens/api key + Auth-->>Chat: 活躍帳戶 + 令牌/API 金鑰 Chat->>Core: handleChatCore(body, modelInfo, credentials) - Core->>Core: detect source format - Core->>Core: translate request to target format + Core->>Core: 偵測來源格式 + Core->>Core: 將請求轉換為目標格式 Core->>Exec: execute(provider, transformedBody) - Exec->>Prov: upstream API call - Prov-->>Exec: SSE/JSON response - Exec-->>Core: response + metadata + Exec->>Prov: 上游 API 呼叫 + Prov-->>Exec: SSE/JSON 回應 + Exec-->>Core: 回應 + 中繼資料 alt 401/403 Core->>Exec: refreshCredentials() - Exec-->>Core: updated tokens - Core->>Exec: retry request + Exec-->>Core: 更新的令牌 + Core->>Exec: 重試請求 end - Core->>Stream: translate/normalize stream to client format - Stream-->>Client: SSE chunks / JSON response + Core->>Stream: 將串流轉換/正規化為客戶端格式 + Stream-->>Client: SSE 區塊 / JSON 回應 - Stream->>Usage: extract usage + persist history/log + Stream->>Usage: 提取用量 + 持久化歷史/記錄 ``` -## Combo + Account Fallback Flow +## 組合 + 帳戶備援流程 ```mermaid flowchart TD - A[Incoming model string] --> B{Is combo name?} - B -- Yes --> C[Load combo models sequence] - B -- No --> D[Single model path] + A[傳入模型字串] --> B{是組合名稱?} + B -- 是 --> C[載入組合模型序列] + B -- 否 --> D[單一模型路徑] - C --> E[Try model N] - E --> F[Resolve provider/model] + C --> E[嘗試模型 N] + E --> F[解析供應商/模型] D --> F - F --> G[Select account credentials] - G --> H{Credentials available?} - H -- No --> I[Return provider unavailable] - H -- Yes --> J[Execute request] + F --> G[選擇帳戶憑證] + G --> H{憑證可用?} + H -- 否 --> I[回傳供應商不可用] + H -- 是 --> J[執行請求] - J --> K{Success?} - K -- Yes --> L[Return response] - K -- No --> M{Fallback-eligible error?} + J --> K{成功?} + K -- 是 --> L[回傳回應] + K -- 否 --> M{符合備援條件的錯誤?} - M -- No --> N[Return error] - M -- Yes --> O[Mark account unavailable cooldown] - O --> P{Another account for provider?} - P -- Yes --> G - P -- No --> Q{In combo with next model?} - Q -- Yes --> E - Q -- No --> R[Return all unavailable] + M -- 否 --> N[回傳錯誤] + M -- 是 --> O[標記帳戶不可用並冷卻] + O --> P{供應商還有其他帳戶?} + P -- 是 --> G + P -- 否 --> Q{在組合中有下一個模型?} + Q -- 是 --> E + Q -- 否 --> R[回傳全部不可用] ``` -Fallback decisions are driven by `open-sse/services/accountFallback.ts` using status codes and error-message heuristics. Combo routing adds one extra guard: provider-scoped 400s such as upstream content-block and role-validation failures are treated as model-local failures so later combo targets can still run. +備援決策由 `open-sse/services/accountFallback.ts` 根據狀態碼與錯誤訊息啟發式驅動。組合路由增加了一層額外防護:供應商範圍的 400 錯誤(如上游內容封鎖與角色驗證失敗)被視為模型本地錯誤,以便後續組合目標仍可執行。 -## OAuth Onboarding and Token Refresh Lifecycle +## OAuth 入門與令牌刷新生命週期 ```mermaid sequenceDiagram autonumber - participant UI as Dashboard UI + participant UI as 儀表板 UI participant OAuth as /api/oauth/[provider]/[action] - participant ProvAuth as Provider Auth Server + participant ProvAuth as 供應商驗證伺服器 participant DB as localDb participant Test as /api/providers/[id]/test - participant Exec as Provider Executor + participant Exec as 供應商執行器 - UI->>OAuth: GET authorize or device-code - OAuth->>ProvAuth: create auth/device flow - ProvAuth-->>OAuth: auth URL or device code payload - OAuth-->>UI: flow data + UI->>OAuth: GET authorize 或 device-code + OAuth->>ProvAuth: 建立驗證/裝置流程 + ProvAuth-->>OAuth: 驗證 URL 或裝置碼載荷 + OAuth-->>UI: 流程資料 - UI->>OAuth: POST exchange or poll - OAuth->>ProvAuth: token exchange/poll - ProvAuth-->>OAuth: access/refresh tokens + UI->>OAuth: POST exchange 或 poll + OAuth->>ProvAuth: 令牌交換/輪詢 + ProvAuth-->>OAuth: 存取/刷新令牌 OAuth->>DB: createProviderConnection(oauth data) - OAuth-->>UI: success + connection id + OAuth-->>UI: 成功 + 連線 ID UI->>Test: POST /api/providers/[id]/test - Test->>Exec: validate credentials / optional refresh - Exec-->>Test: valid or refreshed token info - Test->>DB: update status/tokens/errors - Test-->>UI: validation result + Test->>Exec: 驗證憑證 / 選用刷新 + Exec-->>Test: 有效或已刷新的令牌資訊 + Test->>DB: 更新狀態/令牌/錯誤 + Test-->>UI: 驗證結果 ``` -Refresh during live traffic is executed inside `open-sse/handlers/chatCore.ts` via executor `refreshCredentials()`. +即時流量中的刷新在 `open-sse/handlers/chatCore.ts` 內透過執行器 `refreshCredentials()` 執行。 -## Cloud Sync Lifecycle (Enable / Sync / Disable) +## 雲端同步生命週期(啟用 / 同步 / 停用) ```mermaid sequenceDiagram autonumber - participant UI as Endpoint Page UI + participant UI as 端點頁面 UI participant Sync as /api/sync/cloud participant DB as localDb - participant Cloud as External Cloud Sync + participant Cloud as 外部雲端同步 participant Claude as ~/.claude/settings.json UI->>Sync: POST action=enable - Sync->>DB: set cloudEnabled=true - Sync->>DB: ensure API key exists + Sync->>DB: 設定 cloudEnabled=true + Sync->>DB: 確保 API 金鑰存在 Sync->>Cloud: POST /sync/{machineId} (providers/aliases/combos/keys) - Cloud-->>Sync: sync result + Cloud-->>Sync: 同步結果 Sync->>Cloud: GET /{machineId}/v1/verify - Sync-->>UI: enabled + verification status + Sync-->>UI: 已啟用 + 驗證狀態 UI->>Sync: POST action=sync Sync->>Cloud: POST /sync/{machineId} - Cloud-->>Sync: remote data - Sync->>DB: update newer local tokens/status - Sync-->>UI: synced + Cloud-->>Sync: 遠端資料 + Sync->>DB: 更新較新的本地令牌/狀態 + Sync-->>UI: 已同步 UI->>Sync: POST action=disable - Sync->>DB: set cloudEnabled=false + Sync->>DB: 設定 cloudEnabled=false Sync->>Cloud: DELETE /sync/{machineId} - Sync->>Claude: switch ANTHROPIC_BASE_URL back to local (if needed) - Sync-->>UI: disabled + Sync->>Claude: 將 ANTHROPIC_BASE_URL 切回本地(如需要) + Sync-->>UI: 已停用 ``` -Periodic sync is triggered by `CloudSyncScheduler` when cloud is enabled. +定期同步由 `CloudSyncScheduler` 在雲端啟用時觸發。 -## Data Model and Storage Map +## 資料模型與儲存對映 ```mermaid erDiagram @@ -571,32 +773,32 @@ erDiagram } ``` -Physical storage files: +實體儲存檔案: -- primary runtime DB: `${DATA_DIR}/storage.sqlite` -- request log lines: `${DATA_DIR}/log.txt` (compat/debug artifact) -- structured call payload archives: `${DATA_DIR}/call_logs/` -- optional translator/request debug sessions: `/logs/...` +- 主要執行時期 DB:`${DATA_DIR}/storage.sqlite` +- 請求記錄行:`${DATA_DIR}/log.txt`(相容/除錯工件) +- 結構化呼叫負載歸檔:`${DATA_DIR}/call_logs/` +- 選用轉換器/請求除錯工作階段:`/logs/...` -## Deployment Topology +## 部署拓撲 ```mermaid flowchart LR - subgraph LocalHost[Developer Host] - CLI[CLI Tools] - Browser[Dashboard Browser] + subgraph LocalHost[開發者主機] + CLI[CLI 工具] + Browser[儀表板瀏覽器] end - subgraph ContainerOrProcess[OmniRoute Runtime] - Next[Next.js Server\nPORT=20128] - Core[SSE Core + Executors] + subgraph ContainerOrProcess[OmniRoute 執行時期] + Next[Next.js 伺服器\nPORT=20128] + Core[SSE 核心 + 執行器] MainDB[(storage.sqlite)] - UsageDB[(usage tables + log artifacts)] + UsageDB[(用量表格 + 記錄工件)] end - subgraph External[External Services] - Providers[AI Providers] - SyncCloud[Cloud Sync Service] + subgraph External[外部服務] + Providers[AI 供應商] + SyncCloud[雲端同步服務] end CLI --> Next @@ -609,281 +811,331 @@ flowchart LR Next --> SyncCloud ``` -## Module Mapping (Decision-Critical) +## 模組對映(決策關鍵) -### Route and API Modules +### 路由與 API 模組 -- `src/app/api/v1/*`, `src/app/api/v1beta/*`: compatibility APIs -- `src/app/api/v1/providers/[provider]/*`: dedicated per-provider routes (chat, embeddings, images) -- `src/app/api/providers*`: provider CRUD, validation, testing -- `src/app/api/provider-nodes*`: custom compatible node management -- `src/app/api/provider-models`: custom model management (CRUD) -- `src/app/api/models/route.ts`: model catalog API (aliases + custom models) -- `src/app/api/oauth/*`: OAuth/device-code flows -- `src/app/api/keys*`: local API key lifecycle -- `src/app/api/models/alias`: alias management -- `src/app/api/combos*`: fallback combo management -- `src/app/api/pricing`: pricing overrides for cost calculation -- `src/app/api/settings/proxy`: proxy configuration (GET/PUT/DELETE) -- `src/app/api/settings/proxy/test`: outbound proxy connectivity test (POST) -- `src/app/api/usage/*`: usage and logs APIs -- `src/app/api/sync/*` + `src/app/api/cloud/*`: cloud sync and cloud-facing helpers -- `src/app/api/cli-tools/*`: local CLI config writers/checkers -- `src/app/api/settings/ip-filter`: IP allowlist/blocklist (GET/PUT) -- `src/app/api/settings/thinking-budget`: thinking token budget config (GET/PUT) -- `src/app/api/settings/system-prompt`: global system prompt (GET/PUT) -- `src/app/api/sessions`: active session listing (GET) -- `src/app/api/rate-limits`: per-account rate limit status (GET) -- `src/app/api/sync/tokens`: sync token CRUD (GET/POST) -- `src/app/api/sync/tokens/[id]`: sync token get/delete (GET/DELETE) -- `src/app/api/sync/bundle`: config bundle download (GET, ETag versioning) -- `src/app/api/v1/ws`: WebSocket upgrade handler for OpenAI-compatible WS clients +- `src/app/api/v1/*`、`src/app/api/v1beta/*`:相容性 API +- `src/app/api/v1/providers/[provider]/*`:專屬的每個供應商路由(聊天、嵌入、圖片) +- `src/app/api/providers*`:供應商 CRUD、驗證、測試 +- `src/app/api/provider-nodes*`:自訂相容節點管理 +- `src/app/api/provider-models`:自訂模型管理(CRUD) +- `src/app/api/models/route.ts`:模型目錄 API(別名 + 自訂模型) +- `src/app/api/oauth/*`:OAuth/裝置碼流程 +- `src/app/api/keys*`:本地 API 金鑰生命週期 +- `src/app/api/models/alias`:別名管理 +- `src/app/api/combos*`:備援組合管理 +- `src/app/api/pricing`:用於成本計算的定價覆蓋 +- `src/app/api/settings/proxy`:代理設定(GET/PUT/DELETE) +- `src/app/api/settings/proxy/test`:外送代理連線測試(POST) +- `src/app/api/usage/*`:用量與記錄 API +- `src/app/api/sync/*` + `src/app/api/cloud/*`:雲端同步與雲端面向輔助 +- `src/app/api/cli-tools/*`:本地 CLI 設定寫入器/檢查器 +- `src/app/api/settings/ip-filter`:IP 允許清單/封鎖清單(GET/PUT) +- `src/app/api/settings/thinking-budget`:思考令牌預算設定(GET/PUT) +- `src/app/api/settings/system-prompt`:全域系統提示(GET/PUT) +- `src/app/api/settings/compression`:全域壓縮設定(GET/PUT) +- `src/app/api/compression/*`:壓縮預覽、規則中繼資料與語言套件 +- `src/app/api/context/caveman/config`:Caveman 設定別名(GET/PUT) +- `src/app/api/context/rtk/*`:RTK 設定、過濾器目錄、測試端點與原始輸出恢復 +- `src/app/api/context/combos*`:壓縮組合 CRUD 與路由組合指派 +- `src/app/api/context/analytics`:壓縮分析別名 +- `src/app/api/sessions`:活躍工作階段列表(GET) +- `src/app/api/rate-limits`:每個帳戶的速率限制狀態(GET) +- `src/app/api/sync/tokens`:同步令牌 CRUD(GET/POST) +- `src/app/api/sync/tokens/[id]`:同步令牌取得/刪除(GET/DELETE) +- `src/app/api/sync/bundle`:設定套件下載(GET,ETag 版本化) +- `src/app/api/v1/ws`:OpenAI 相容 WS 客戶端的 WebSocket 升級處理器 -### Routing and Execution Core +### 路由與執行核心 -- `src/sse/handlers/chat.ts`: request parse, combo handling, account selection loop -- `open-sse/handlers/chatCore.ts`: translation, executor dispatch, retry/refresh handling, stream setup -- `open-sse/executors/*`: provider-specific network and format behavior +- `src/sse/handlers/chat.ts`:請求解析、組合處理、帳戶選擇迴圈 +- `open-sse/handlers/chatCore.ts`:轉換、執行器調度、重試/刷新處理、串流設定 +- `open-sse/executors/*`:供應商特定的網路與格式行為 -### Translation Registry and Format Converters +### 轉換註冊表與格式轉換器 -- `open-sse/translator/index.ts`: translator registry and orchestration -- Request translators: `open-sse/translator/request/*` -- Response translators: `open-sse/translator/response/*` -- Format constants: `open-sse/translator/formats.ts` +- `open-sse/translator/index.ts`:轉換器註冊表與協調 +- 請求轉換器:`open-sse/translator/request/*`(9 個模組 — `antigravity-to-openai`、`claude-to-gemini`、`claude-to-openai`、`gemini-to-openai`、`openai-responses`、`openai-to-claude`、`openai-to-cursor`、`openai-to-gemini`、`openai-to-kiro`) +- 回應轉換器:`open-sse/translator/response/*`(8 個模組 — `claude-to-openai`、`cursor-to-openai`、`gemini-to-claude`、`gemini-to-openai`、`kiro-to-openai`、`openai-responses`、`openai-to-antigravity`、`openai-to-claude`) +- 輔助工具:`open-sse/translator/helpers/*`(8 個模組 — `claudeHelper`、`geminiHelper`、`geminiToolsSanitizer`、`maxTokensHelper`、`openaiHelper`、`responsesApiHelper`、`schemaCoercion`、`toolCallHelper`) +- 格式常數:`open-sse/translator/formats.ts` +- 啟動與註冊表:`open-sse/translator/bootstrap.ts`、`open-sse/translator/registry.ts` +- 圖片格式輔助:`open-sse/translator/image/` -### Persistence +### 持久化 -- `src/lib/db/*`: persistent config/state and domain persistence on SQLite -- `src/lib/localDb.ts`: compatibility re-export for DB modules -- `src/lib/usageDb.ts`: usage history/call logs facade on top of SQLite tables +- `src/lib/db/*`:SQLite 上的持久設定/狀態與領域持久化 +- `src/lib/localDb.ts`:DB 模組的相容性重新匯出 +- `src/lib/usageDb.ts`:基於 SQLite 表格的用量歷史/呼叫記錄外觀 -## Provider Executor Coverage (Strategy Pattern) +## 供應商執行器覆蓋範圍(策略模式) -Each provider has a specialized executor extending `BaseExecutor` (in `open-sse/executors/base.ts`), which provides URL building, header construction, retry with exponential backoff, credential refresh hooks, and the `execute()` orchestration method. +每個供應商都有一個專門的執行器,繼承自 `BaseExecutor`(位於 `open-sse/executors/base.ts`),提供 URL 建置、標頭建構、指數退避重試、憑證刷新鉤子與 `execute()` 協調方法。 -| Executor | Provider(s) | Special Handling | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `DefaultExecutor` | OpenAI, Claude, Gemini, Qwen, OpenRouter, GLM, Kimi, MiniMax, DeepSeek, Groq, xAI, Mistral, Perplexity, Together, Fireworks, Cerebras, Cohere, NVIDIA, etc. | Dynamic URL/header config per provider | -| `AntigravityExecutor` | Google Antigravity | Custom project/session IDs, Retry-After parsing | -| `CliProxyApiExecutor` | CLIProxyAPI-compatible providers | Custom auth and protocol handling | -| `CloudflareAiExecutor` | Cloudflare Workers AI | Account ID injection, Neurons-based usage tracking | -| `CodexExecutor` | OpenAI Codex | Injects system instructions, forces reasoning effort | -| `CursorExecutor` | Cursor IDE | ConnectRPC protocol, Protobuf encoding, request signing via checksum | -| `GithubExecutor` | GitHub Copilot | Copilot token refresh, VSCode-mimicking headers | -| `KiroExecutor` | AWS CodeWhisperer/Kiro | AWS EventStream binary format → SSE conversion | -| `OpenCodeExecutor` | OpenCode | AI SDK compatible provider setup | -| `PollinationsExecutor` | Pollinations AI | No API key required, rate-limited requests | -| `PuterExecutor` | Puter | Browser-based provider integration | -| `QoderExecutor` | Qoder AI | PAT and OAuth support, multi-model free tier | -| `VertexExecutor` | Google Vertex AI | Service account auth, region-based endpoints | +| 執行器 | 供應商 | 特殊處理 | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `DefaultExecutor` | OpenAI、Claude、Gemini、Qwen、OpenRouter、GLM、Kimi、MiniMax、DeepSeek、Groq、xAI、Mistral、Perplexity、Together、Fireworks、Cerebras、Cohere、NVIDIA 等 | 每個供應商的動態 URL/標頭設定 | +| `AntigravityExecutor` | Google Antigravity | 自訂專案/工作階段 ID、Retry-After 解析、429 混淆 | +| `AzureOpenAIExecutor` | Azure OpenAI | 基於部署的路由、api-version 查詢強制 | +| `BlackboxWebExecutor` | Blackbox AI(網頁模式) | 含 TLS 指紋模擬的網頁工作階段反向 | +| `ChatGPTWebExecutor` | ChatGPT 網頁 | TLS 客戶端 + 工作階段 Cookie 管理(`chatgptTlsClient.ts`) | +| `ClaudeIdentityExecutor` | Claude.ai(CCH 路徑) | 約束 + 工具重新對應管線、指紋塑造 | +| `CliProxyApiExecutor` | CLIProxyAPI 相容供應商 | 自訂驗證與協定處理 | +| `CloudflareAiExecutor` | Cloudflare Workers AI | 帳戶 ID 注入、基於 Neurons 的用量追蹤 | +| `CodexExecutor` | OpenAI Codex | 注入系統指令、強制推理努力 | +| `CommandCodeExecutor` | Command Code | OAuth + 每個工作階段的標頭輪換 | +| `CursorExecutor` | Cursor IDE | ConnectRPC 協定、Protobuf 編碼、透過 checksum 的請求簽署 | +| `DevinCliExecutor` | Devin CLI | 透過雲端代理模組的 Devin 任務生命週期橋接 | +| `GithubExecutor` | GitHub Copilot | Copilot 令牌刷新、模擬 VSCode 標頭 | +| `GitlabExecutor` | GitLab Duo | GitLab OAuth + 專案範圍路由 | +| `GlmExecutor` | Z.AI GLM(含 `glmt` 預設) | 思考預算感知、GLMT 預設常數 | +| `GrokWebExecutor` | xAI Grok 網頁 | 網頁工作階段反向、模式選擇(think/standard) | +| `KieExecutor` | KIE | 自訂令牌簽發,含輪換的工作階段錨點 | +| `KiroExecutor` | AWS CodeWhisperer/Kiro | AWS EventStream 二進位格式 → SSE 轉換 | +| `MuseSparkWebExecutor` | Muse Spark(網頁) | 含圖片訊息橋接的網頁工作階段反向 | +| `NlpCloudExecutor` | NLP Cloud | 供應商特定的請求主體形式 | +| `OpenCodeExecutor` | OpenCode | AI SDK 相容供應商設定 | +| `PerplexityWebExecutor` | Perplexity 網頁 | 用於聊天延續的網頁工作階段反向 | +| `PetalsExecutor` | Petals 分散式推理 | 去中心化群組路由 | +| `PollinationsExecutor` | Pollinations AI | 無需 API 金鑰、速率限制請求 | +| `PuterExecutor` | Puter | 基於瀏覽器的供應商整合 | +| `QoderExecutor` | Qoder AI | PAT 與 OAuth 支援、多模型免費方案 | +| `VertexExecutor` | Google Vertex AI | 服務帳戶驗證、基於區域的端點 | +| `WindsurfExecutor` | Windsurf(Codeium) | Codeium OAuth + 工作階段令牌刷新 | -All other providers (including custom compatible nodes) use the `DefaultExecutor`. +所有其他供應商(包括自訂相容節點)使用 `DefaultExecutor`。 -## Provider Compatibility Matrix +## 供應商相容性矩陣 -| Provider | Format | Auth | Stream | Non-Stream | Token Refresh | Usage API | -| ---------------- | ---------------- | --------------------- | ---------------- | ---------- | ------------- | ------------------ | -| Claude | claude | API Key / OAuth | ✅ | ✅ | ✅ | ⚠️ Admin only | -| Gemini | gemini | API Key / OAuth | ✅ | ✅ | ✅ | ⚠️ Cloud Console | -| Antigravity | antigravity | OAuth | ✅ | ✅ | ✅ | ✅ Full quota API | -| OpenAI | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Codex | openai-responses | OAuth | ✅ forced | ❌ | ✅ | ✅ Rate limits | -| GitHub Copilot | openai | OAuth + Copilot Token | ✅ | ✅ | ✅ | ✅ Quota snapshots | -| Cursor | cursor | Custom checksum | ✅ | ✅ | ❌ | ❌ | -| Kiro | kiro | AWS SSO OIDC | ✅ (EventStream) | ❌ | ✅ | ✅ Usage limits | -| Qwen | openai | OAuth | ✅ | ✅ | ✅ | ⚠️ Per request | -| Qoder | openai | OAuth / PAT | ✅ | ✅ | ✅ | ⚠️ Per request | -| Kilo Code | openai | OAuth | ✅ | ✅ | ✅ | ❌ | -| Cline | openai | OAuth | ✅ | ✅ | ✅ | ❌ | -| Kimi Coding | openai | OAuth | ✅ | ✅ | ✅ | ❌ | -| OpenRouter | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| GLM/Kimi/MiniMax | claude | API Key | ✅ | ✅ | ❌ | ❌ | -| DeepSeek | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Groq | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| xAI (Grok) | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Mistral | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Perplexity | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Together AI | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Fireworks AI | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Cerebras | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Cohere | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| NVIDIA NIM | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Cloudflare AI | openai | API Token + Acct ID | ✅ | ✅ | ❌ | ❌ | -| Pollinations | openai | None (no key) | ✅ | ✅ | ❌ | ❌ | -| Scaleway AI | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| LongCat | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Ollama Cloud | openai | API Key (optional) | ✅ | ✅ | ❌ | ❌ | -| HuggingFace | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Nebius | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| SiliconFlow | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Hyperbolic | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Vertex AI | gemini | Service Account | ✅ | ✅ | ✅ | ⚠️ Cloud Console | -| Puter | openai | API Key | ✅ | ✅ | ❌ | ❌ | +> **注意:** 以下矩陣為 OmniRoute v3.8.0 中 237 個已註冊供應商的代表性樣本。 +> 完整且持續更新的清單,請參閱 +> [`docs/reference/PROVIDER_REFERENCE.md`](../reference/PROVIDER_REFERENCE.md)(自動產生)或 +> `src/shared/constants/providers.ts`(載入時經 Zod 驗證)中的權威來源。 -## Format Translation Coverage +| 供應商 | 格式 | 驗證 | 串流 | 非串流 | 令牌刷新 | 用量 API | +| ----------------- | ---------------- | --------------------- | ---------------- | ---------- | ------------- | ------------------ | +| Claude | claude | API 金鑰 / OAuth | ✅ | ✅ | ✅ | ⚠️ 僅管理員 | +| Gemini | gemini | API 金鑰 / OAuth | ✅ | ✅ | ✅ | ⚠️ Cloud Console | +| Antigravity | antigravity | OAuth | ✅ | ✅ | ✅ | ✅ 完整配額 API | +| OpenAI | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Codex | openai-responses | OAuth | ✅ 強制 | ❌ | ✅ | ✅ 速率限制 | +| GitHub Copilot | openai | OAuth + Copilot 令牌 | ✅ | ✅ | ✅ | ✅ 配額快照 | +| Cursor | cursor | 自訂 checksum | ✅ | ✅ | ❌ | ❌ | +| Kiro | kiro | AWS SSO OIDC | ✅ (EventStream) | ❌ | ✅ | ✅ 用量限制 | +| Qoder | openai | OAuth / PAT | ✅ | ✅ | ✅ | ⚠️ 每次請求 | +| Kilo Code | openai | OAuth | ✅ | ✅ | ✅ | ❌ | +| Cline | openai | OAuth | ✅ | ✅ | ✅ | ❌ | +| Kimi Coding | openai | OAuth | ✅ | ✅ | ✅ | ❌ | +| OpenRouter | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| GLM/Kimi/MiniMax | claude | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| DeepSeek | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Groq | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| xAI(Grok) | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Mistral | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Perplexity | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Together AI | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Fireworks AI | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Cerebras | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Cohere | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| NVIDIA NIM | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Cloudflare AI | openai | API 令牌 + 帳戶 ID | ✅ | ✅ | ❌ | ❌ | +| Pollinations | openai | 無(無需金鑰) | ✅ | ✅ | ❌ | ❌ | +| Scaleway AI | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| LongCat | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Ollama Cloud | openai | API 金鑰(選用) | ✅ | ✅ | ❌ | ❌ | +| HuggingFace | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Nebius | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| SiliconFlow | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Hyperbolic | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Vertex AI | gemini | 服務帳戶 | ✅ | ✅ | ✅ | ⚠️ Cloud Console | +| Puter | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Command Code | openai | OAuth | ✅ | ✅ | ✅ | ⚠️ 每次請求 | +| Z.AI / GLM | openai | API 金鑰 / OAuth | ✅ | ✅ | ❌ | ❌ | +| GLMT(預設) | claude | API 金鑰 | ✅ | ✅ | ❌ | ⚠️ 每次請求 | +| Kimi Coding | openai | OAuth / API 金鑰 | ✅ | ✅ | ✅ | ❌ | +| KIE | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Windsurf | openai | OAuth(Codeium) | ✅ | ✅ | ✅ | ⚠️ 每次請求 | +| GitLab Duo | openai | OAuth(GitLab) | ✅ | ✅ | ✅ | ❌ | +| Devin CLI | openai | OAuth | ✅ | ✅ | ✅ | ✅ 任務 API | +| Codex Cloud | openai-responses | OAuth | ✅ | ❌ | ✅ | ✅ 速率限制 | +| Jules | openai | OAuth | ✅ | ✅ | ✅ | ✅ 任務 API | +| AgentRouter | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| ChatGPT-Web | openai | 工作階段 Cookie + TLS | ✅ | ✅ | ❌ | ❌ | +| Grok-Web | openai | 工作階段 Cookie | ✅ | ✅ | ❌ | ❌ | +| Perplexity-Web | openai | 工作階段 Cookie | ✅ | ✅ | ❌ | ❌ | +| BlackBox-Web | openai | 工作階段 Cookie + TLS | ✅ | ✅ | ❌ | ❌ | +| Muse-Spark-Web | openai | 工作階段 Cookie | ✅ | ✅ | ❌ | ❌ | +| ModelScope | openai | API 金鑰 | ✅ | ✅ | ❌ | ⚠️ 配額政策 | +| BazaarLink | openai | API 金鑰 | ✅ | ✅ | ❌ | ❌ | +| Petals | openai | 無 | ✅ | ✅ | ❌ | ❌ | +| Qoder | openai | OAuth / PAT | ✅ | ✅ | ✅ | ⚠️ 每次請求 | +| OpenCode(Go/Zen) | openai | OAuth | ✅ | ✅ | ✅ | ❌ | +| CLIProxyAPI | openai | 自訂 | ✅ | ✅ | ❌ | ❌ | -Detected source formats include: +## 格式轉換覆蓋範圍 + +偵測到的來源格式包括: - `openai` - `openai-responses` - `claude` - `gemini` -Target formats include: +目標格式包括: - OpenAI chat/Responses - Claude -- Gemini/Antigravity envelope +- Gemini/Antigravity 信封 - Kiro - Cursor -Translations use **OpenAI as the hub format** — all conversions go through OpenAI as intermediate: +轉換使用 **OpenAI 作為樞紐格式** — 所有轉換都透過 OpenAI 作為中間格式: ``` -Source Format → OpenAI (hub) → Target Format +來源格式 → OpenAI(樞紐)→ 目標格式 ``` -Translations are selected dynamically based on source payload shape and provider target format. +轉換根據來源負載形狀與供應商目標格式動態選擇。 -Additional processing layers in the translation pipeline: +轉換管線中的其他處理層: -- **Response sanitization** — Strips non-standard fields from OpenAI-format responses (both streaming and non-streaming) to ensure strict SDK compliance -- **Role normalization** — Converts `developer` → `system` for non-OpenAI targets; merges `system` → `user` for models that reject the system role (GLM, ERNIE) -- **Think tag extraction** — Parses `...` blocks from content into `reasoning_content` field -- **Structured output** — Converts OpenAI `response_format.json_schema` to Gemini's `responseMimeType` + `responseSchema` +- **回應淨化** — 從 OpenAI 格式回應(串流與非串流)中移除非標準欄位,以確保嚴格的 SDK 相容性 +- **角色正規化** — 將 `developer` → `system` 轉換為非 OpenAI 目標;將 `system` → `user` 合併到拒絕系統角色的模型(GLM、ERNIE) +- **Think 標籤提取** — 將內容中的 `...` 區塊解析為 `reasoning_content` 欄位 +- **結構化輸出** — 將 OpenAI `response_format.json_schema` 轉換為 Gemini 的 `responseMimeType` + `responseSchema` -## Supported API Endpoints +## 支援的 API 端點 -| Endpoint | Format | Handler | +| 端點 | 格式 | 處理器 | | -------------------------------------------------- | ------------------ | ------------------------------------------------------------------- | -| `POST /v1/chat/completions` | OpenAI Chat | `src/sse/handlers/chat.ts` | -| `POST /v1/messages` | Claude Messages | Same handler (auto-detected) | -| `POST /v1/responses` | OpenAI Responses | `open-sse/handlers/responsesHandler.ts` | -| `POST /v1/embeddings` | OpenAI Embeddings | `open-sse/handlers/embeddings.ts` | -| `GET /v1/embeddings` | Model listing | API route | -| `POST /v1/images/generations` | OpenAI Images | `open-sse/handlers/imageGeneration.ts` | -| `GET /v1/images/generations` | Model listing | API route | -| `POST /v1/providers/{provider}/chat/completions` | OpenAI Chat | Dedicated per-provider with model validation | -| `POST /v1/providers/{provider}/embeddings` | OpenAI Embeddings | Dedicated per-provider with model validation | -| `POST /v1/providers/{provider}/images/generations` | OpenAI Images | Dedicated per-provider with model validation | -| `POST /v1/messages/count_tokens` | Claude Token Count | API route | -| `GET /v1/models` | OpenAI Models list | API route (chat + embedding + image + custom models) | -| `GET /api/models/catalog` | Catalog | All models grouped by provider + type | -| `POST /v1beta/models/*:streamGenerateContent` | Gemini native | API route | -| `GET/PUT/DELETE /api/settings/proxy` | Proxy Config | Network proxy configuration | -| `POST /api/settings/proxy/test` | Proxy Connectivity | Proxy health/connectivity test endpoint | -| `GET/POST/DELETE /api/provider-models` | Provider Models | Provider model metadata backing custom and managed available models | +| `POST /v1/chat/completions` | OpenAI Chat | `src/sse/handlers/chat.ts` | +| `POST /v1/messages` | Claude Messages | 相同處理器(自動偵測) | +| `POST /v1/responses` | OpenAI Responses | `open-sse/handlers/responsesHandler.ts` | +| `POST /v1/embeddings` | OpenAI Embeddings | `open-sse/handlers/embeddings.ts` | +| `GET /v1/embeddings` | 模型列表 | API 路由 | +| `POST /v1/images/generations` | OpenAI Images | `open-sse/handlers/imageGeneration.ts` | +| `GET /v1/images/generations` | 模型列表 | API 路由 | +| `POST /v1/providers/{provider}/chat/completions` | OpenAI Chat | 專屬每個供應商,含模型驗證 | +| `POST /v1/providers/{provider}/embeddings` | OpenAI Embeddings | 專屬每個供應商,含模型驗證 | +| `POST /v1/providers/{provider}/images/generations` | OpenAI Images | 專屬每個供應商,含模型驗證 | +| `POST /v1/messages/count_tokens` | Claude Token Count | API 路由 | +| `GET /v1/models` | OpenAI Models list | API 路由(聊天 + 嵌入 + 圖片 + 自訂模型) | +| `GET /api/models/catalog` | 目錄 | 所有模型按供應商 + 類型分組 | +| `POST /v1beta/models/*:streamGenerateContent` | Gemini 原生 | API 路由 | +| `GET/PUT/DELETE /api/settings/proxy` | 代理設定 | 網路代理設定 | +| `POST /api/settings/proxy/test` | 代理連線 | 代理健康/連線測試端點 | +| `GET/POST/DELETE /api/provider-models` | Provider Models | 支援自訂與受管可用模型的供應商模型中繼資料 | -## Bypass Handler +## 繞過處理器 -The bypass handler (`open-sse/utils/bypassHandler.ts`) intercepts known "throwaway" requests from Claude CLI — warmup pings, title extractions, and token counts — and returns a **fake response** without consuming upstream provider tokens. This is triggered only when `User-Agent` contains `claude-cli`. +繞過處理器(`open-sse/utils/bypassHandler.ts`)攔截來自 Claude CLI 的已知「一次性」請求 — 暖機 ping、標題提取與令牌計數 — 並在不消耗上游供應商令牌的情況下回傳**偽造回應**。僅在 `User-Agent` 包含 `claude-cli` 時觸發。 -## Request Logging and Artifacts +## 請求記錄與工件 -The older file-based request logger (`open-sse/utils/requestLogger.ts`) is retained only for -legacy compatibility. The current runtime contract uses: +較舊的基於檔案的請求記錄器(`open-sse/utils/requestLogger.ts`)僅為舊版相容性保留。 +目前的執行時期合約使用: -- `APP_LOG_TO_FILE=true` for application and audit logs written under `/logs/` -- SQLite-backed call log records in `call_logs` -- `${DATA_DIR}/call_logs/YYYY-MM-DD/...` artifacts when the call log pipeline is enabled +- `APP_LOG_TO_FILE=true` 用於寫入 `/logs/` 的應用程式與稽核記錄 +- SQLite 支援的 `call_logs` 中的呼叫記錄 +- 啟用呼叫記錄管線時的 `${DATA_DIR}/call_logs/YYYY-MM-DD/...` 工件 -## Failure Modes and Resilience +## 故障模式與韌性 -## 1) Account/Provider Availability +## 1) 帳戶/供應商可用性 -- connection cooldown on retryable upstream failures -- account fallback before failing request -- combo model fallback when current model/provider path is exhausted +- 可重試的上游故障時進行連線冷卻 +- 在請求失敗前進行帳戶備援 +- 當前模型/供應商路徑耗盡時的組合模型備援 -## 2) Token Expiry +## 2) 令牌到期 -- pre-check and refresh with retry for refreshable providers -- 401/403 retry after refresh attempt in core path +- 對可刷新的供應商進行預檢查與重試刷新 +- 核心路徑中刷新嘗試後的 401/403 重試 -## 3) Stream Safety +## 3) 串流安全 -- disconnect-aware stream controller -- translation stream with end-of-stream flush and `[DONE]` handling -- usage estimation fallback when provider usage metadata is missing +- 具斷線感知的串流控制器 +- 具串流結束 flush 與 `[DONE]` 處理的轉換串流 +- 供應商用量中繼資料遺失時的用量估算備援 -## 4) Cloud Sync Degradation +## 4) 雲端同步降級 -- sync errors are surfaced but local runtime continues -- scheduler has retry-capable logic, but periodic execution currently calls single-attempt sync by default +- 同步錯誤會呈現但本地執行時期繼續運行 +- 排程器具備重試邏輯,但定期執行預設目前呼叫單次嘗試同步 -## 5) Data Integrity +## 5) 資料完整性 -- SQLite schema migrations and auto-upgrade hooks at startup -- legacy JSON → SQLite migration compatibility path +- SQLite 架構遷移與啟動時的自動升級鉤子 +- 舊版 JSON → SQLite 遷移相容性路徑 -## 6) SSRF / Outbound URL Guard +## 6) SSRF / 外送 URL 防護 -- `src/shared/network/outboundUrlGuard.ts` blocks all private/loopback/link-local target URLs before they reach provider executors -- Provider model discovery and validation routes use `src/shared/network/safeOutboundFetch.ts` which applies the guard before every outbound request -- Guard errors surface as `URL_GUARD_BLOCKED` with HTTP 422 and are logged to the compliance audit trail via `providerAudit.ts` +- `src/shared/network/outboundUrlGuard.ts` 在請求到達供應商執行器前封鎖所有私人/迴路/鏈結本地目標 URL +- 供應商模型探索與驗證路由使用 `src/shared/network/safeOutboundFetch.ts`,該函數在每個外送請求前應用防護 +- 防護錯誤以 `URL_GUARD_BLOCKED` 呈現,附 HTTP 422,並透過 `providerAudit.ts` 記錄到合規稽核軌跡 -## Observability and Operational Signals +## 可觀測性與操作訊號 -Runtime visibility sources: +執行時期可視性來源: -- console logs from `src/sse/utils/logger.ts` -- per-request usage aggregates in SQLite (`usage_history`, `call_logs`, `proxy_logs`) -- four-stage detailed payload captures in SQLite (`request_detail_logs`) when `settings.detailed_logs_enabled=true` -- textual request status log in `log.txt` (optional/compat) -- optional application log files under `logs/` when `APP_LOG_TO_FILE=true` -- optional request artifacts under `${DATA_DIR}/call_logs/` when the call log pipeline is enabled -- dashboard usage endpoints (`/api/usage/*`) for UI consumption +- `src/sse/utils/logger.ts` 的主控台記錄 +- SQLite 中的每次請求用量彙總(`usage_history`、`call_logs`、`proxy_logs`) +- 當 `settings.detailed_logs_enabled=true` 時,SQLite 中的四階段詳細負載捕獲(`request_detail_logs`) +- `log.txt` 中的文字請求狀態記錄(選用/相容) +- `APP_LOG_TO_FILE=true` 時 `logs/` 下的選用應用程式記錄檔 +- 啟用呼叫記錄管線時 `${DATA_DIR}/call_logs/` 下的選用請求工件 +- 儀表板用量端點(`/api/usage/*`)供 UI 使用 -Detailed request payload capture stores up to four JSON payload stages per routed call: +詳細請求負載捕獲儲存每個路由呼叫最多四個 JSON 負載階段: -- raw request received from the client -- translated request actually sent upstream -- provider response reconstructed as JSON; streamed responses are compacted to the final summary plus stream metadata -- final client response returned by OmniRoute; streamed responses are stored in the same compact summary form +- 從客戶端接收到的原始請求 +- 實際發送給上游的轉換後請求 +- 重建為 JSON 的供應商回應;串流回應壓縮為最終摘要加上串流中繼資料 +- OmniRoute 回傳的最終客戶端回應;串流回應以相同壓縮摘要形式儲存 -## Security-Sensitive Boundaries +## 安全敏感邊界 -- JWT secret (`JWT_SECRET`) secures dashboard session cookie verification/signing -- Initial password bootstrap (`INITIAL_PASSWORD`) should be explicitly configured for first-run provisioning -- API key HMAC secret (`API_KEY_SECRET`) secures generated local API key format -- Provider secrets (API keys/tokens) are persisted in local DB and should be protected at filesystem level -- Cloud sync endpoints rely on API key auth + machine id semantics +- JWT 秘密(`JWT_SECRET`)保護儀表板工作階段 Cookie 驗證/簽署 +- 初始密碼(`INITIAL_PASSWORD`)應明確設定,用於首次執行佈建 +- API 金鑰 HMAC 秘密(`API_KEY_SECRET`)保護產生的本地 API 金鑰格式 +- 供應商秘密(API 金鑰/令牌)持久化在本地 DB 中,應在檔案系統層級保護 +- 雲端同步端點依賴 API 金鑰驗證 + 機器 ID 語意 -## Environment and Runtime Matrix +## 環境與執行時期矩陣 -Environment variables actively used by code: +程式碼中實際使用的環境變數: -- App/auth: `JWT_SECRET`, `INITIAL_PASSWORD` -- Storage: `DATA_DIR` -- Compatible node behavior: `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` -- Optional storage base override (Linux/macOS when `DATA_DIR` unset): `XDG_CONFIG_HOME` -- Security hashing: `API_KEY_SECRET`, `MACHINE_ID_SALT` -- Logging: `APP_LOG_TO_FILE`, `APP_LOG_RETENTION_DAYS`, `CALL_LOG_RETENTION_DAYS` -- Sync/cloud URLing: `NEXT_PUBLIC_BASE_URL`, `NEXT_PUBLIC_CLOUD_URL` -- Outbound proxy: `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY` and lowercase variants -- SOCKS5 feature flags: `ENABLE_SOCKS5_PROXY`, `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` -- Platform/runtime helpers (not app-specific config): `APPDATA`, `NODE_ENV`, `PORT`, `HOSTNAME` +- 應用程式/驗證:`JWT_SECRET`、`INITIAL_PASSWORD` +- 儲存:`DATA_DIR` +- 相容節點行為:`ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` +- 選用儲存基底覆蓋(Linux/macOS 上未設定 `DATA_DIR` 時):`XDG_CONFIG_HOME` +- 安全雜湊:`API_KEY_SECRET`、`MACHINE_ID_SALT` +- 記錄:`APP_LOG_TO_FILE`、`APP_LOG_RETENTION_DAYS`、`CALL_LOG_RETENTION_DAYS` +- 同步/雲端 URL:`NEXT_PUBLIC_BASE_URL`、`NEXT_PUBLIC_CLOUD_URL` +- 外送代理:`HTTP_PROXY`、`HTTPS_PROXY`、`ALL_PROXY`、`NO_PROXY` 及小寫變體 +- SOCKS5 功能標誌:`ENABLE_SOCKS5_PROXY`、`NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` +- 平台/執行時期輔助(非應用程式特定設定):`APPDATA`、`NODE_ENV`、`PORT`、`HOSTNAME` -## Known Architectural Notes +## 已知架構說明 -1. `usageDb` and `localDb` share the same base directory policy (`DATA_DIR` -> `XDG_CONFIG_HOME/omniroute` -> `~/.omniroute`) with legacy file migration. -2. `/api/v1/route.ts` delegates to the same unified catalog builder used by `/api/v1/models` (`src/app/api/v1/models/catalog.ts`) to avoid semantic drift. -3. Request logger writes full headers/body when enabled; treat log directory as sensitive. -4. Cloud behavior depends on correct `NEXT_PUBLIC_BASE_URL` and cloud endpoint reachability. -5. The `open-sse/` directory is published as the `@omniroute/open-sse` **npm workspace package**. Source code imports it via `@omniroute/open-sse/...` (resolved by Next.js `transpilePackages`). File paths in this document still use the directory name `open-sse/` for consistency. -6. Charts in the dashboard use **Recharts** (SVG-based) for accessible, interactive analytics visualizations (model usage bar charts, provider breakdown tables with success rates). -7. E2E tests use **Playwright** (`tests/e2e/`), run via `npm run test:e2e`. Unit tests use **Node.js test runner** (`tests/unit/`), run via `npm run test:unit`. Source code under `src/` is **TypeScript** (`.ts`/`.tsx`); the `open-sse/` workspace remains JavaScript (`.js`). -8. Settings page is organized into 7 tabs: General, Appearance, AI, Security, Routing, Resilience, Advanced. The Resilience page only configures request queue, connection cooldown, provider breaker, and wait-for-cooldown behavior; live breaker runtime state is shown on the Health page. -9. **Context Relay** strategy (`context-relay`) is split across two layers: `combo.ts` decides if a handoff should be generated, `chat.ts` injects the handoff after account resolution. Handoff data lives in `context_handoffs` SQLite table. This split is intentional because only `chat.ts` knows whether the actual account changed. -10. **Proxy enforcement** is now comprehensive: `tokenHealthCheck.ts` resolves proxy per connection, `/api/providers/validate` uses `runWithProxyContext`, and `proxyFetch.ts` uses `undici.fetch()` to maintain dispatcher compatibility on Node 22. -11. **Node.js runtime policy detection**: `/api/settings/require-login` returns `nodeVersion` and `nodeCompatible` fields. The login page renders a warning banner when the runtime falls outside the supported secure Node.js lines. +1. `usageDb` 與 `localDb` 共用相同的基礎目錄政策(`DATA_DIR` → `XDG_CONFIG_HOME/omniroute` → `~/.omniroute`),附舊版檔案遷移。 +2. `/api/v1/route.ts` 委派給與 `/api/v1/models` 相同的統一目錄建置器(`src/app/api/v1/models/catalog.ts`),以避免語意漂移。 +3. 啟用時請求記錄器寫入完整標頭/主體;請將記錄目錄視為敏感資訊。 +4. 雲端行為取決於正確的 `NEXT_PUBLIC_BASE_URL` 與雲端端點可達性。 +5. `open-sse/` 目錄以 `@omniroute/open-sse` **npm workspace 套件**形式發布。原始碼透過 `@omniroute/open-sse/...` 匯入(由 Next.js `transpilePackages` 解析)。本文件中的檔案路徑為求一致仍使用目錄名稱 `open-sse/`。 +6. 儀表板中的圖表使用 **Recharts**(基於 SVG)實現可存取、互動式的分析視覺化(模型用量長條圖、含成功率的供應商 breakdown 表格)。 +7. E2E 測試使用 **Playwright**(`tests/e2e/`),透過 `npm run test:e2e` 執行。單元測試使用 **Node.js test runner**(`tests/unit/`),透過 `npm run test:unit` 執行。`src/` 下的原始碼為 **TypeScript**(`.ts`/`.tsx`);`open-sse/` workspace 保持 JavaScript(`.js`)。 +8. 設定頁面分為 7 個分頁:一般、外觀、AI、安全性、路由、韌性、進階。韌性頁面僅設定請求佇列、連線冷卻、供應商斷路器與等待冷卻行為;斷路器執行時期狀態顯示在健康狀態頁面上。 +9. **上下文轉接**策略(`context-relay`)分跨兩層:`combo.ts` 決定是否應產生交接,`chat.ts` 在帳戶解析後注入交接。交接資料存在 `context_handoffs` SQLite 表格中。這種拆分是有意為之,因為只有 `chat.ts` 知道實際帳戶是否已變更。 +10. **代理強制**現在是全方位的:`tokenHealthCheck.ts` 根據連線解析代理,`/api/providers/validate` 使用 `runWithProxyContext`,而 `proxyFetch.ts` 使用 `undici.fetch()` 以在 Node 22 上維持調度器相容性。 +11. **Node.js 執行時期政策偵測**:`/api/settings/require-login` 回傳 `nodeVersion` 與 `nodeCompatible` 欄位。當執行時期超出支援的安全 Node.js 版本範圍時,登入頁面會顯示警告橫幅。 -## Operational Verification Checklist +## 操作驗證檢查清單 -- Build from source: `npm run build` -- Build Docker image: `docker build -t omniroute .` -- Start service and verify: +- 從原始碼建置:`npm run build` +- 建置 Docker 映像:`docker build -t omniroute .` +- 啟動服務並驗證: - `GET /api/settings` - `GET /api/v1/models` -- CLI target base URL should be `http://:20128/v1` when `PORT=20128` +- CLI 目標基礎 URL 應為 `http://:20128/v1`(當 `PORT=20128` 時) diff --git a/docs/i18n/zh-TW/docs/architecture/CODEBASE_DOCUMENTATION.md b/docs/i18n/zh-TW/docs/architecture/CODEBASE_DOCUMENTATION.md index 9b78a2b3a6..1c7cb8c8f0 100644 --- a/docs/i18n/zh-TW/docs/architecture/CODEBASE_DOCUMENTATION.md +++ b/docs/i18n/zh-TW/docs/architecture/CODEBASE_DOCUMENTATION.md @@ -1,587 +1,816 @@ -# omniroute — Codebase Documentation (中文 (簡體)) +--- +title: "OmniRoute 程式碼基礎文件" +version: 3.8.40 +lastUpdated: 2026-06-28 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/CODEBASE_DOCUMENTATION.md) · 🇸🇦 [ar](../../ar/docs/CODEBASE_DOCUMENTATION.md) · 🇧🇬 [bg](../../bg/docs/CODEBASE_DOCUMENTATION.md) · 🇧🇩 [bn](../../bn/docs/CODEBASE_DOCUMENTATION.md) · 🇨🇿 [cs](../../cs/docs/CODEBASE_DOCUMENTATION.md) · 🇩🇰 [da](../../da/docs/CODEBASE_DOCUMENTATION.md) · 🇩🇪 [de](../../de/docs/CODEBASE_DOCUMENTATION.md) · 🇪🇸 [es](../../es/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇷 [fa](../../fa/docs/CODEBASE_DOCUMENTATION.md) · 🇫🇮 [fi](../../fi/docs/CODEBASE_DOCUMENTATION.md) · 🇫🇷 [fr](../../fr/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [gu](../../gu/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇱 [he](../../he/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [hi](../../hi/docs/CODEBASE_DOCUMENTATION.md) · 🇭🇺 [hu](../../hu/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇩 [id](../../id/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇹 [it](../../it/docs/CODEBASE_DOCUMENTATION.md) · 🇯🇵 [ja](../../ja/docs/CODEBASE_DOCUMENTATION.md) · 🇰🇷 [ko](../../ko/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [mr](../../mr/docs/CODEBASE_DOCUMENTATION.md) · 🇲🇾 [ms](../../ms/docs/CODEBASE_DOCUMENTATION.md) · 🇳🇱 [nl](../../nl/docs/CODEBASE_DOCUMENTATION.md) · 🇳🇴 [no](../../no/docs/CODEBASE_DOCUMENTATION.md) · 🇵🇭 [phi](../../phi/docs/CODEBASE_DOCUMENTATION.md) · 🇵🇱 [pl](../../pl/docs/CODEBASE_DOCUMENTATION.md) · 🇵🇹 [pt](../../pt/docs/CODEBASE_DOCUMENTATION.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/CODEBASE_DOCUMENTATION.md) · 🇷🇴 [ro](../../ro/docs/CODEBASE_DOCUMENTATION.md) · 🇷🇺 [ru](../../ru/docs/CODEBASE_DOCUMENTATION.md) · 🇸🇰 [sk](../../sk/docs/CODEBASE_DOCUMENTATION.md) · 🇸🇪 [sv](../../sv/docs/CODEBASE_DOCUMENTATION.md) · 🇰🇪 [sw](../../sw/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [ta](../../ta/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [te](../../te/docs/CODEBASE_DOCUMENTATION.md) · 🇹🇭 [th](../../th/docs/CODEBASE_DOCUMENTATION.md) · 🇹🇷 [tr](../../tr/docs/CODEBASE_DOCUMENTATION.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/CODEBASE_DOCUMENTATION.md) · 🇵🇰 [ur](../../ur/docs/CODEBASE_DOCUMENTATION.md) · 🇻🇳 [vi](../../vi/docs/CODEBASE_DOCUMENTATION.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/CODEBASE_DOCUMENTATION.md) +# OmniRoute 程式碼基礎文件 + +> **版本:** v3.8.0 +> **最後更新:** 2026-06-28 +> **讀者對象:** 為 OmniRoute 貢獻程式碼或在其之上建立整合的工程師。 +> +> 如需高階架構圖表及各子系統背後的設計理念,請參閱 [ARCHITECTURE.md](./ARCHITECTURE.md)。如需深入了解各子系統(Auto Combo、MCP 伺服器、A2A 伺服器、技能、記憶體、雲端代理、韌性、壓縮等),請參閱 `docs/` 目錄中的對應文件。 + +本文說明 **當前儲存庫中的內容**,使新進工程師能夠瀏覽目錄結構、理解執行時期分層,並知道在何處新增程式碼,而不需要自行發明新模組。 --- -> A comprehensive, beginner-friendly guide to the **omniroute** multi-provider AI proxy router. +## 1. 技術棧 + +| 面向 | 選擇 | +| ------------ | -------------------------------------------------------------------------------------------------------------------------- | +| Web 框架 | **Next.js 16**(App Router,standalone 輸出,無全域中介軟體) | +| 語言 | **TypeScript 6.0+** — 目標 `ES2022`,`module: esnext`,`moduleResolution: bundler`,`strict: false` | +| 執行時期 | **Node.js** `>=22.22.2 <23` 或 `>=24.0.0 <27`(透過 `engines` + `SUPPORTED_NODE_RANGE` 強制) | +| 資料庫 | **SQLite** 透過 `better-sqlite3`(單例,WAL 日誌模式) | +| 桌面應用 | **Electron 41** + `electron-builder` 26.10(獨立 workspace 位於 `electron/`) | +| 測試 | **Node 原生測試執行器**(單元/整合測試),**Vitest**(MCP、autoCombo、快取),**Playwright**(e2e + protocols-e2e) | +| 建置 | Next.js standalone 透過 `scripts/build/build-next-isolated.mjs` | +| 程式碼風格 | ESLint flat config + Prettier(`lint-staged` 透過 Husky pre-commit) | +| 模組系統 | 全面 ESM(`"type": "module"`) | +| Workspaces | npm workspace — `open-sse` 是唯一的子 workspace | + +路徑別名(`tsconfig.json`): + +- `@/*` → `src/*` +- `@omniroute/open-sse` → `open-sse/index.ts` +- `@omniroute/open-sse/*` → `open-sse/*` + +預設 HTTP 埠:**`20128`**(API 與儀表板共用同一個程序。資料 +目錄由 `DATA_DIR` 環境變數指定,預設為 `~/.omniroute/`)。 --- -## 1. What Is omniroute? - -omniroute is a **proxy router** that sits between AI clients (Claude CLI, Codex, Cursor IDE, etc.) and AI providers (Anthropic, Google, OpenAI, AWS, GitHub, etc.). It solves one big problem: - -> **Different AI clients speak different "languages" (API formats), and different AI providers expect different "languages" too.** omniroute translates between them automatically. - -Think of it like a universal translator at the United Nations — any delegate can speak any language, and the translator converts it for any other delegate. - ---- - -## 2. Architecture Overview - -```mermaid -graph LR - subgraph Clients - A[Claude CLI] - B[Codex] - C[Cursor IDE] - D[OpenAI-compatible] - end - - subgraph omniroute - E[Handler Layer] - F[Translator Layer] - G[Executor Layer] - H[Services Layer] - end - - subgraph Providers - I[Anthropic Claude] - J[Google Gemini] - K[OpenAI / Codex] - L[GitHub Copilot] - M[AWS Kiro] - N[Antigravity] - O[Cursor API] - end - - A --> E - B --> E - C --> E - D --> E - E --> F - F --> G - G --> I - G --> J - G --> K - G --> L - G --> M - G --> N - G --> O - H -.-> E - H -.-> G -``` - -### Core Principle: Hub-and-Spoke Translation - -All format translation passes through **OpenAI format as the hub**: +## 2. 儲存庫佈局 ``` -Client Format → [OpenAI Hub] → Provider Format (request) -Provider Format → [OpenAI Hub] → Client Format (response) -``` - -This means you only need **N translators** (one per format) instead of **N²** (every pair). - ---- - -## 3. Project Structure - -``` -omniroute/ -├── open-sse/ ← Core proxy library (portable, framework-agnostic) -│ ├── index.js ← Main entry point, exports everything -│ ├── config/ ← Configuration & constants -│ ├── executors/ ← Provider-specific request execution -│ ├── handlers/ ← Request handling orchestration -│ ├── services/ ← Business logic (auth, models, fallback, usage) -│ ├── translator/ ← Format translation engine -│ │ ├── request/ ← Request translators (8 files) -│ │ ├── response/ ← Response translators (7 files) -│ │ └── helpers/ ← Shared translation utilities (6 files) -│ └── utils/ ← Utility functions -├── src/ ← Application layer (Express/Worker runtime) -│ ├── app/ ← Web UI, API routes, middleware -│ ├── lib/ ← Database, auth, and shared library code -│ ├── mitm/ ← Man-in-the-middle proxy utilities -│ ├── models/ ← Database models -│ ├── shared/ ← Shared utilities (wrappers around open-sse) -│ ├── sse/ ← SSE endpoint handlers -│ └── store/ ← State management -├── data/ ← Runtime data (credentials, logs) -│ └── provider-credentials.json (external credentials override, gitignored) -└── tester/ ← Test utilities +OmniRoute/ +├── src/ Next.js 應用程式(App Router、函式庫、領域、伺服器、共用) +├── open-sse/ 串流引擎 workspace(@omniroute/open-sse) +├── electron/ 桌面包裝程式(Electron 41 main + preload) +├── bin/ CLI 進入點(omniroute、reset-password) +├── tests/ 單元、整合、e2e、protocols-e2e、翻譯器、安全性、測試用 fixture +├── scripts/ 建置、同步、檢查、遷移及執行時期輔助腳本 +├── docs/ 公開文件(此目錄) +├── public/ 靜態資源、PWA manifest、service worker +├── config/ 執行時期設定範例 +├── images/ 行銷/截圖資源 +├── _ideia/、_references/、_mono_repo/、_tasks/ 內部草稿/規劃(不隨產品出貨) +├── CLAUDE.md 給 Claude Code 的儲存庫規則 +├── AGENTS.md 給代理程式的進階架構參考 +├── package.json v3.8.0,workspace 根目錄 +└── tsconfig.json 路徑別名 + 核心編譯器選項 ``` --- -## 4. Module-by-Module Breakdown +## 3. `src/` — Next.js 應用程式 -### 4.1 Config (`open-sse/config/`) - -The **single source of truth** for all provider configuration. - -| File | Purpose | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `constants.ts` | `PROVIDERS` object with base URLs, OAuth credentials (defaults), headers, and default system prompts for every provider. Also defines `HTTP_STATUS`, `ERROR_TYPES`, `COOLDOWN_MS`, `BACKOFF_CONFIG`, and `SKIP_PATTERNS`. | -| `credentialLoader.ts` | Loads external credentials from `data/provider-credentials.json` and merges them over the hardcoded defaults in `PROVIDERS`. Keeps secrets out of source control while maintaining backwards compatibility. | -| `providerModels.ts` | Central model registry: maps provider aliases → model IDs. Functions like `getModels()`, `getProviderByAlias()`. | -| `codexInstructions.ts` | System instructions injected into Codex requests (editing constraints, sandbox rules, approval policies). | -| `defaultThinkingSignature.ts` | Default "thinking" signatures for Claude and Gemini models. | -| `ollamaModels.ts` | Schema definition for local Ollama models (name, size, family, quantization). | - -#### Credential Loading Flow - -```mermaid -flowchart TD - A["App starts"] --> B["constants.ts defines PROVIDERS\nwith hardcoded defaults"] - B --> C{"data/provider-credentials.json\nexists?"} - C -->|Yes| D["credentialLoader reads JSON"] - C -->|No| E["Use hardcoded defaults"] - D --> F{"For each provider in JSON"} - F --> G{"Provider exists\nin PROVIDERS?"} - G -->|No| H["Log warning, skip"] - G -->|Yes| I{"Value is object?"} - I -->|No| J["Log warning, skip"] - I -->|Yes| K["Merge clientId, clientSecret,\ntokenUrl, authUrl, refreshUrl"] - K --> F - H --> F - J --> F - F -->|Done| L["PROVIDERS ready with\nmerged credentials"] - E --> L ``` +src/ +├── app/ App Router 頁面 + API 路由 +├── lib/ 核心函式庫(資料庫、認證、OAuth、技能、記憶體……) +├── domain/ 純領域層(政策、備援、成本、鎖定……) +├── server/ 僅伺服器模組(授權、CORS、認證) +├── shared/ 型別、常數、驗證、合約、工具(跨邊界安全) +├── mitm/ CLI 整合的中間人代理輔助程式 +├── models/ 本地模型元資料 / 別名 +├── sse/ 仍位於 src/ 下的舊版 SSE 處理器(非 open-sse/) +├── store/ 客戶端狀態儲存 +├── middleware/ 路由層級中介軟體工具(非 Next.js 全域中介軟體) +├── scripts/ 應用程式程式碼可匯入的內部腳本 +├── types/ 環境與共用 TS 型別 +├── i18n/ 語系包 +├── instrumentation.ts Next.js 儀器鉤子 +├── instrumentation-node.ts +├── server-init.ts 程序層級啟動(環境變數、資料庫、工作、同步) +└── proxy.ts 頂層代理啟動輔助程式 +``` + +### 3.1 `src/app/` — App Router + +App Router 同時提供儀表板 UI 和公開/管理 HTTP API。 +**沒有全域中介軟體** — 攔截是在每個路由層級進行的。 + +`src/app/` 下的頂層區段: + +| 路徑 | 用途 | +| --------------------------------------------------------------------------------- | ------------------------------------------ | +| `api/` | 所有 HTTP API 路由(詳見下方細分) | +| `a2a/` | A2A JSON-RPC 2.0 端點(`POST /a2a`) | +| `.well-known/agent.json/` | A2A Agent Card 探索文件 | +| `(dashboard)/` | 儀表板 UI(路由群組,無 URL 前綴) | +| `auth/`、`login/`、`forgot-password/`、`callback/` | 認證流程 | +| `landing/` | 行銷/登陸頁面 | +| `docs/` | 嵌入式 API 文件檢視器 | +| `status/`、`maintenance/`、`offline/` | 運作狀態頁面 | +| `privacy/`、`terms/` | 法律頁面 | +| `400/`、`401/`、`403/`、`408/`、`429/`、`500/`、`502/`、`503/` | 靜態錯誤頁面 | +| `error.tsx`、`global-error.tsx`、`not-found.tsx`、`forbidden/`、`loading.tsx` | 框架錯誤/載入邊界 | +| `layout.tsx`、`page.tsx`、`globals.css`、`manifest.ts` | 根殼層 | + +#### 3.1.1 `src/app/(dashboard)/dashboard/` — UI 頁面 + +`agents`、`analytics`、`api-manager`、`audit`、`auto-combo`、`batch`、`cache`、 +`changelog`、`cli-tools`、`cloud-agents`、`combos`、`compression`、`context`、 +`costs`、`endpoint`、`health`、`limits`、`logs`、`memory`、`onboarding`、 +`playground`、`providers`、`search-tools`、`settings`、`skills`、`system`、 +`translator`、`usage`、`webhooks`,加上根目錄 `page.tsx`、`HomePageClient.tsx`、 +`BootstrapBanner.tsx`。 + +#### 3.1.2 `src/app/api/` — 頂層 API 群組 + +``` +src/app/api/ +├── a2a/{status, tasks} +├── acp/ +├── admin/ +├── analytics/ +├── assess/ +├── auth/ +├── batches/ +├── cache/ +├── cli-tools/ +├── cloud/{codex-responses-ws} +├── combos/ +├── compliance/ +├── compression/ +├── context/ +├── db/、db-backups/ +├── evals/ +├── fallback/ +├── files/ +├── health/ +├── init/ +├── internal/{concurrency} +├── keys/ +├── logs/ +├── mcp/{audit, sse, status, stream, tools} +├── memory/{health, [id]/, route.ts} +├── model-combo-mappings/ +├── models/ +├── monitoring/ +├── oauth/ +├── openapi/ +├── policies/ +├── pricing/ +├── provider-metrics/、provider-models/、provider-nodes/ +├── providers/ +├── rate-limit/、rate-limits/ +├── resilience/ +├── restart/、shutdown/ +├── search/ +├── sessions/ +├── settings/ +├── skills/{executions, [id], install, marketplace, route.ts, skillssh} +├── storage/ +├── sync/、synced-available-models/ +├── system/ +├── tags/ +├── telemetry/ +├── token-health/ +├── translator/ +├── tunnels/ +├── services/ 嵌入式服務管理(9router、cliproxy)— LOCAL_ONLY +├── upstream-proxy/ +├── usage/ +├── v1/ 與 OpenAI 相容的公開 API +├── v1beta/ Gemini 風格的相容層 +├── version-manager/ +└── webhooks/ +``` + +#### 3.1.2a `src/app/api/services/` — 嵌入式服務管理 + +用於安裝、啟動、停止和監控 9Router 及 CLIProxyAPI 的路由。 +所有路徑均標記為 **LOCAL_ONLY**(僅限迴路位址,硬性規則 #17),因為它們 +可能執行 `npm install` 並產生子程序。 + +``` +src/app/api/services/ +├── 9router/ +│ ├── _lib.ts getOrInitSupervisor() 輔助程式 +│ ├── install/route.ts POST — 透過 execFile 執行 npm install +│ ├── start/route.ts POST — supervisor.start() +│ ├── stop/route.ts POST — supervisor.stop() +│ ├── restart/route.ts POST — supervisor.restart() +│ ├── update/route.ts POST — npm install 更新版本 +│ ├── rotate-key/route.ts POST — 產生新 API 金鑰並重新啟動 +│ ├── status/route.ts GET — 即時狀態 + 資料庫狀態 + 版本元資料 +│ └── auto-start/route.ts POST — 切換 auto_start 旗標 +├── cliproxy/ +│ ├── _lib.ts getOrInitSupervisor() 輔助程式 +│ ├── install/route.ts POST — npm install +│ ├── start/route.ts POST — supervisor.start() +│ ├── stop/route.ts POST — supervisor.stop() +│ ├── restart/route.ts POST — supervisor.restart() +│ ├── update/route.ts POST — npm install 更新版本 +│ ├── status/route.ts GET — 即時狀態 + 資料庫狀態 + 版本元資料 +│ └── auto-start/route.ts POST — 切換 auto_start 旗標 +└── [name]/ + └── logs/route.ts GET — SSE 日誌尾部(所有服務共用) +``` + +對應的儀表板 UI: +`src/app/(dashboard)/dashboard/providers/services/` — 雙標籤頁面(CLIProxyAPI + 9Router)。 +9Router 嵌入式 UI 的反向代理: +`src/app/(dashboard)/dashboard/providers/services/[name]/embed/[...path]/route.ts` + +深入探討:`docs/frameworks/EMBEDDED-SERVICES.md` + +#### 3.1.3 `src/app/api/v1/` — 與 OpenAI 相容的公開 API + +``` +v1/ +├── accounts/[id]/ 帳戶查詢 +├── agents/tasks/[id]/、agents/tasks/ A2A 風格任務端點 +├── api/ 在 v1/api 下公開的內部 API 輔助程式 +├── audio/{speech, transcriptions}/ TTS + STT +├── batches/[id]/{cancel}、batches/ OpenAI Batches API +├── chat/completions/ 聊天補全(主要端點) +├── chatgpt-web/ ChatGPT-Web 相容層 +├── completions/ 舊版文字補全 +├── embeddings/ 嵌入向量 +├── files/[id]/、files/ 檔案 API +├── _helpers/ 共用路由輔助程式(無公開 URL) +├── images/{edits, generations}/ 圖片生成 + 編輯 +├── issues/ 分流輔助端點 +├── management/{proxies}/ 管理範圍路由(位於 v1 內) +├── messages/{count_tokens}/ Anthropic 風格訊息相容層 +├── models/ 模型列表(`route.ts`、`catalog.ts`) +├── moderations/ 內容審查 +├── music/ 音樂生成 +├── providers/[provider]/ 各供應商操作 +├── quotas/{check} 配額查詢 +├── registered-keys/ 已註冊金鑰管理 +├── rerank/ 重新排序 +├── responses/[...path]/ OpenAI Responses API(catch-all) +├── search/ 網路搜尋 +├── videos/ 影片生成 +├── ws/ WebSocket 橋接 +└── route.ts 索引處理器 +``` + +每個路由檔案遵循相同的模式: + +``` +路由 → CORS 預檢 → Zod 請求體驗證 → 選擇性認證 + → API 金鑰政策執行 → 處理器委派(open-sse) +``` + +`v1beta/` 是 Gemini 風格的相容層(一個薄包裝層,將請求轉換成 +相同的 `open-sse/handlers/` 管線)。 + +### 3.2 `src/lib/` — 核心函式庫 + +務必透過這些模組匯入資料、同步、OAuth、技能、記憶體等。以下 +表格列出實際目錄及值得注意的頂層檔案。 + +| 模組 | 用途 | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `a2a/` | A2A 協定伺服器:`taskManager.ts`、`streaming.ts`、`taskExecution.ts`、`routingLogger.ts`、`skills/`(6 個技能:成本分析、健康報告、供應商探索、配額管理、智慧路由、列出能力) | +| `acp/` | 代理控制協定:`index.ts`、`manager.ts`、`registry.ts` | +| `api/` | 內部 API 輔助程式:`requireManagementAuth.ts`、`requireCliToolsAuth.ts`、`errorResponse.ts` | +| `auth/` | `managementPassword.ts`(密碼重設/雜湊) | +| `batches/` | OpenAI Batches API 服務(`service.ts`) | +| `catalog/` | OpenRouter 目錄同步(`openrouterCatalog.ts`) | +| `cloudAgent/` | 雲端代理註冊表:`api.ts`、`baseAgent.ts`、`db.ts`、`index.ts`、`registry.ts`、`types.ts`、`agents/{codex, devin, jules}.ts` | +| `combos/` | Combo 解析輔助程式 | +| `compliance/` | 稽核 + 供應商稽核:`index.ts`、`providerAudit.ts` | +| `config/` | 執行時期設定黏合層 | +| `db/` | SQLite 領域模組(參見 §3.2.1) | +| `display/` | API 回應使用的 UI/顯示輔助程式 | +| `embeddings/` | 嵌入服務註冊表 | +| `env/` | 環境變數載入 + 內省 | +| `evals/` | 評估執行時期 | +| `guardrails/` | `piiMasker.ts`、`promptInjection.ts`、`visionBridge.ts`、`visionBridgeHelpers.ts`、`registry.ts`、`base.ts` | +| `jobs/` | 背景工作(`autoUpdate.ts`……) | +| `memory/` | 持久化記憶體:`store.ts`、`cache.ts`、`retrieval.ts`、`summarization.ts`、`extraction.ts`、`injection.ts`、`qdrant.ts`、`settings.ts`、`verify.ts`、`schemas.ts`、`types.ts` | +| `monitoring/` | `observability.ts` | +| `oauth/` | OAuth 供應商(13 個):`antigravity`、`claude`、`cline`、`codex`、`cursor`、`gemini`、`github`、`gitlab-duo`、`kilocode`、`kimi-coding`、`kiro`、`qoder`、`windsurf` 加上 `services/`、`utils/{pkce, server, banner, codexAuthFile, ui}`、`constants/oauth.ts` | +| `plugins/` | 外掛載入器(`index.ts`) | +| `promptCache/` | `prefixAnalyzer.ts`、`index.ts` | +| `providerModels/` | 受管模型生命週期:`modelDiscovery.ts`、`managedModelImport.ts`、`managedAvailableModels.ts`、`cursorAgent.ts` | +| `providers/` | 供應商輔助程式:`catalog.ts`、`validation.ts`、`imageValidation.ts`、`claudeExtraUsage.ts`、`codexConnectionDefaults.ts`、`codexFastTier.ts`、`webCookieAuth.ts`、`managedAvailableModels.ts`、`requestDefaults.ts` | +| `resilience/` | `settings.ts` — 斷路器、冷卻、鎖定設定 | +| `runtime/` | 執行時期功能檢測 | +| `search/` | `executeWebSearch.ts` | +| `services/` | 嵌入式服務框架:`ServiceSupervisor.ts`(通用子程序監控器,具備操作鎖、環形緩衝區、健康檢查)、`bootstrap.ts`(程序層級註冊與自動啟動)、`registry.ts`(工具 → 監控器對應)、`apiKey.ts`(AES-256-GCM 金鑰儲存)、`modelSync.ts`(定期模型同步)、`ringBuffer.ts`(5 MB 循環日誌緩衝區)、`healthCheck.ts`(HTTP 健康探測)、`types.ts`、`embedWsProxy.ts`(WebSocket 代理)、`installers/{ninerouter,cliproxy}.ts`。參見 `docs/frameworks/EMBEDDED-SERVICES.md` | +| `agentSkills/` | 代理技能目錄 + 產生器:`catalog.ts`(getCatalog/getSkillById/filterCatalog/computeCoverage)、`generator.ts`(generateAgentSkills → 寫入 `skills/{id}/SKILL.md`)、`openapiParser.ts`(從 OpenAPI 規格提取 REST 端點)、`cliRegistryParser.ts`(從 bin/cli-registry 提取 CLI 子命令)、`schemas.ts`(Zod:AgentSkillSchema、SkillCoverageSchema、ListQuerySchema、GenerateBodySchema)、`types.ts`(AgentSkill、SkillCoverage、SkillMarkdown、GeneratorReport)。由 REST 路由(`/api/agent-skills/*`)、MCP 工具(`omniroute_agent_skills_*`)和 A2A 技能 `list-capabilities` 使用。參見 [AGENT-SKILLS.md](../frameworks/AGENT-SKILLS.md)。 | +| `skills/` | 技能框架:`registry.ts`、`executor.ts`、`interception.ts`、`injection.ts`、`sandbox.ts`、`custom.ts`、`hybrid.ts`、`builtins.ts`、`a2a.ts`、`providerSettings.ts`、`schemas.ts`、`skillssh.ts`、`types.ts`,加上 `builtin/browser.ts` | +| `spend/` | `batchWriter.ts`(寫入緩衝區) | +| `sync/` | `bundle.ts`、`tokens.ts`(雲端同步) | +| `system/` | 系統層級輔助程式 | +| `translator/` | 頂層翻譯器黏合層(委派給 `open-sse/translator/`) | +| `usage/` | 用量會計:`costCalculator.ts`、`tokenAccounting.ts`、`usageHistory.ts`、`aggregateHistory.ts`、`usageStats.ts`、`callLogs.ts`、`callLogArtifacts.ts`、`fetcher.ts`、`providerLimits.ts`、`migrations.ts` | +| `versionManager/` | 自動更新 + 版本清單 | +| `ws/` | WebSocket 橋接 | +| `zed-oauth/` | Zed 編輯器 OAuth 流程 | + +`src/lib/` 中的頂層檔案: + +- `localDb.ts` — 僅重新匯出層。**切勿**在此新增邏輯。 +- `proxyHealth.ts`、`proxyLogger.ts`、`tokenHealthCheck.ts`、`localHealthCheck.ts` +- `oneproxyRotator.ts`、`oneproxySync.ts` +- `apiBridgeServer.ts`、`cacheLayer.ts`、`semanticCache.ts`、`settingsCache.ts` +- `cloudSync.ts`、`initCloudSync.ts` +- `cloudflaredTunnel.ts`、`ngrokTunnel.ts`、`tailscaleTunnel.ts` +- `consoleInterceptor.ts`、`container.ts`、`gracefulShutdown.ts`、`idempotencyLayer.ts` +- `ipUtils.ts`、`logEnv.ts`、`logPayloads.ts`、`logRotation.ts` +- `modelAliasSeed.ts`、`modelCapabilities.ts`、`modelMetadataRegistry.ts`、`modelsDevSync.ts` +- `piiSanitizer.ts`、`pricingSync.ts` +- `apiKeyExposure.ts`、`cacheControlSettings.ts`、`dataPaths.ts`、`toolPolicy.ts` +- `translatorEvents.ts`、`usageDb.ts`、`usageAnalytics.ts`、`webhookDispatcher.ts` + +#### 3.2.1 `src/lib/db/` + +單例 SQLite 資料庫(`core.ts` 中的 `getDbInstance()`,WAL 日誌模式)。 +**切勿在路由或處理器中撰寫原始 SQL** — 請透過這些模組操作。 + +![資料庫架構概覽(選取的核心資料表)](../diagrams/exported/db-schema-overview.svg) + +> 來源:[diagrams/db-schema-overview.mmd](../diagrams/db-schema-overview.mmd) + +領域模組(每個掌管一個或多個資料表):`apiKeys.ts`、`backup.ts`、 +`batches.ts`、`cleanup.ts`、`cliToolState.ts`、`combos.ts`、 +`commandCodeAuth.ts`、`compression.ts`、`compressionAnalytics.ts`、 +`compressionCacheStats.ts`、`compressionCombos.ts`、`compressionScheduler.ts`、 +`contextHandoffs.ts`、`core.ts`、`creditBalance.ts`、`databaseSettings.ts`、 +`detailedLogs.ts`、`domainState.ts`、`encryption.ts`、`evals.ts`、`files.ts`、 +`healthCheck.ts`、`jsonMigration.ts`、`migrationRunner.ts`、 +`modelComboMappings.ts`、`models.ts`、`oneproxy.ts`、`prompts.ts`、 +`providers.ts`、`providerLimits.ts`、`proxies.ts`、`quotaSnapshots.ts`、 +`readCache.ts`、`reasoningCache.ts`、`registeredKeys.ts`、`secrets.ts`、 +`sessionAccountAffinity.ts`、`settings.ts`、`stateReset.ts`、`stats.ts`、 +`syncTokens.ts`、`tierConfig.ts`、`upstreamProxy.ts`、`versionManager.ts`、 +`webhooks.ts`。 + +`migrations/` 包含 55 個版本化的 `.sql` 檔案(冪等、事務性),並在 +啟動時由 `migrationRunner.ts` 執行。 + +所有遷移建立的資料表(共 52 個): + +`a`、`account_key_limits`、`api_keys`、`batches`、`call_logs`、 +`combo_adaptation_state`、`combos`、`command_code_auth_sessions`、 +`compression_analytics`、`compression_cache_stats`、 +`compression_combo_assignments`、`compression_combos`、`context_handoffs`、 +`daily_usage_summary`、`db_meta`、`domain_budgets`、`domain_circuit_breakers`、 +`domain_cost_history`、`domain_fallback_chains`、`domain_lockout_state`、 +`eval_cases`、`eval_runs`、`eval_suites`、`files`、`hourly_usage_summary`、 +`key_value`、`mcp_tool_audit`、`memories`、`model_combo_mappings`、 +`provider_connections`、`provider_key_limits`、`provider_nodes`、 +`proxy_assignments`、`proxy_logs`、`proxy_registry`、`quota_snapshots`、 +`reasoning_cache`、`registered_keys`、`request_detail_logs`、 +`routing_decisions`、`semantic_cache`、`session_account_affinity`、 +`skill_executions`、`skills`、`sync_tokens`、`tier_assignments`、 +`tier_config`、`upstream_proxy_config`、`usage_history`、`version_manager`、 +`webhooks`(加上用於記憶體搜尋的 FTS5 虛擬資料表)。 + +### 3.3 `src/domain/` — 領域層 + +純商業邏輯,無 I/O。由路由和處理器匯入。 + +| 檔案 | 用途 | +| ---------------------------------------------- | -------------------------------------------------- | +| `policyEngine.ts` | 頂層政策解析器 | +| `fallbackPolicy.ts` | 備援決策樹 | +| `costRules.ts` | 成本計算規則 | +| `lockoutPolicy.ts` | 模型鎖定決策 | +| `tagRouter.ts` | 基於標籤的路由 | +| `comboResolver.ts` | 從請求解析 combo → 目標清單 | +| `connectionModelRules.ts` | 各連線的模型過濾器 | +| `modelAvailability.ts` | 模型可用性檢查 | +| `degradation.ts` | 降級模式轉換 | +| `providerExpiration.ts` | 過期帳戶/金鑰偵測 | +| `quotaCache.ts` | 快取配額決策 | +| `responses.ts`、`omnirouteResponseMeta.ts` | 回應形狀輔助程式 | +| `configAudit.ts` | 設定變更稽核 | +| `assessment/` | 模型評估(依 RFC,部分實作) | +| `types.ts` | 共用領域型別 | + +### 3.4 `src/server/` — 僅伺服器端 + +無法從客戶端元件匯入。 + +``` +server/ +├── auth/loginGuard.ts +├── authz/ +│ ├── classify.ts 將路由分類為公開或管理 +│ ├── assertAuth.ts 斷言輔助程式 +│ ├── context.ts 每個請求的授權上下文 +│ ├── headers.ts +│ ├── pipeline.ts 授權管線 +│ ├── policies/ 具體政策 +│ └── types.ts +└── cors/origins.ts CORS 來源白名單 +``` + +### 3.5 `src/shared/` — 安全共用 + +分為聚焦的子目錄: + +- `constants/` — `providers.ts`(Zod 驗證的供應商目錄)、`models.ts`、 + `modelSpecs.ts`、`modelCompat.ts`、`pricing.ts`、`cliTools.ts`、 + `cliCompatProviders.ts`、`routingStrategies.ts`、`comboConfigMode.ts`、 + `headers.ts`、`upstreamHeaders.ts`(封鎖清單)、`mcpScopes.ts`、 + `errorCodes.ts`、`publicApiRoutes.ts`、`batch.ts`、`batchEndpoints.ts`、 + `bodySize.ts`、`colors.ts`、`appConfig.ts`、`config.ts`、 + `sidebarVisibility.ts`、`visionBridgeDefaults.ts`。 +- `validation/` — `schemas.ts`(約 80 個 Zod 架構)、`compressionConfigSchemas.ts`、 + `oneproxySchemas.ts`、`providerSchema.ts`、`settingsSchemas.ts`、`helpers.ts`。 +- `contracts/` — 發布到 npm 的公開 API 合約。 +- `types/` — 共用 TS 型別。 +- `utils/` — `circuitBreaker.ts`、`apiAuth.ts`、`apiKey.ts`、`apiKeyPolicy.ts`、 + `apiResponse.ts`、`api.ts`、`classify429.ts`、`cliCompat.ts`、`clipboard.ts`、 + `cloud.ts`、`cn.ts`、`cors.ts`、`costEstimator.ts`、`featureFlags.ts`、 + `fetchTimeout.ts`、`formatting.ts`、`inputSanitizer.ts`、`logger.ts`、 + `machine.ts`、`machineId.ts`、`maskEmail.ts`、`modelCatalogSearch.ts`、 + `nodeRuntimeSupport.ts`、`parseApiKeys.ts`、`providerHints.ts`、 + `providerModelAliases.ts`、`rateLimiter.ts`、`releaseNotes.ts`、 + `a11yAudit.ts`,加上 `services/`、`network/`、`middleware/`、`schemas/`、`hooks/`、`components/` 下的儀表板鉤子/元件。 --- -### 4.2 Executors (`open-sse/executors/`) +## 4. `open-sse/` — 串流引擎 workspace -Executors encapsulate **provider-specific logic** using the **Strategy Pattern**. Each executor overrides base methods as needed. +獨立的 npm workspace,發布為 `@omniroute/open-sse`。負責請求 +處理、執行器、翻譯器、服務、轉換器及 MCP 伺服器。 -```mermaid -classDiagram - class BaseExecutor { - +buildUrl(model, stream, options) - +buildHeaders(credentials, stream, body) - +transformRequest(body, model, stream, credentials) - +execute(url, options) - +shouldRetry(status, error) - +refreshCredentials(credentials, log) - } - - class DefaultExecutor { - +refreshCredentials() - } - - class AntigravityExecutor { - +buildUrl() - +buildHeaders() - +transformRequest() - +shouldRetry() - +refreshCredentials() - } - - class CursorExecutor { - +buildUrl() - +buildHeaders() - +transformRequest() - +parseResponse() - +generateChecksum() - } - - class KiroExecutor { - +buildUrl() - +buildHeaders() - +transformRequest() - +parseEventStream() - +refreshCredentials() - } - - BaseExecutor <|-- DefaultExecutor - BaseExecutor <|-- AntigravityExecutor - BaseExecutor <|-- CursorExecutor - BaseExecutor <|-- KiroExecutor - BaseExecutor <|-- CodexExecutor - BaseExecutor <|-- GithubExecutor +``` +open-sse/ +├── index.ts 公開匯出 +├── package.json Workspace 清單 +├── tsconfig.json +├── types.d.ts +├── config/ 供應商註冊表、標頭設定檔、身分識別…… +├── handlers/ 請求處理器(聊天、嵌入、音訊、圖片……) +├── executors/ 84 個供應商專屬的 HTTP 執行器 +├── translator/ 格式轉換(OpenAI ↔ Claude ↔ Gemini ↔ Cursor ↔ Kiro) +├── transformer/ Responses API ↔ Chat Completions 串流轉換器 +├── services/ 80+ 個服務模組(combo、備援、配額、身分識別……) +├── utils/ 串流輔助程式、TLS 客戶端、AWS SigV4、代理請求…… +└── mcp-server/ MCP 伺服器(3 種傳輸方式、30 個範圍、94 個工具) ``` -| Executor | Provider | Key Specializations | -| ---------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | -| `base.ts` | — | Abstract base: URL building, headers, retry logic, credential refresh | -| `default.ts` | Claude, Gemini, OpenAI, GLM, Kimi, MiniMax | Generic OAuth token refresh for standard providers | -| `antigravity.ts` | Google Cloud Code | Project/session ID generation, multi-URL fallback, custom retry parsing from error messages ("reset after 2h7m23s") | -| `cursor.ts` | Cursor IDE | **Most complex**: SHA-256 checksum auth, Protobuf request encoding, binary EventStream → SSE response parsing | -| `codex.ts` | OpenAI Codex | Injects system instructions, manages thinking levels, removes unsupported parameters | -| `github.ts` | GitHub Copilot | Dual token system (GitHub OAuth + Copilot token), VSCode header mimicking | -| `kiro.ts` | AWS CodeWhisperer | AWS EventStream binary parsing, AMZN event frames, token estimation | -| `index.ts` | — | Factory: maps provider name → executor class, with default fallback | +### 4.1 `open-sse/handlers/` + +| 處理器 | 用途 | +| -------------------------- | ------------------------------------------------------------------------- | +| `chatCore.ts` | 主要聊天管線(快取、速率限制、combo 路由、執行器分派) | +| `responsesHandler.ts` | OpenAI Responses API 進入點 | +| `embeddings.ts` | 嵌入向量 | +| `imageGeneration.ts` | 圖片生成 | +| `audioSpeech.ts` | 文字轉語音 | +| `audioTranscription.ts` | 語音轉文字 | +| `videoGeneration.ts` | 影片生成 | +| `musicGeneration.ts` | 音樂生成 | +| `rerank.ts` | 重新排序 | +| `moderations.ts` | 內容審查 | +| `search.ts` | 網路搜尋 | +| `sseParser.ts` | SSE 事件解析器 | +| `usageExtractor.ts` | 從上游串流提取 Token 計數 | +| `responseSanitizer.ts` | 移除供應商特定的雜訊 | +| `responseTranslator.ts` | 供應商回應與翻譯層之間的黏合層 | + +### 4.2 `open-sse/executors/` + +84 個供應商執行器,每個都繼承 `BaseExecutor`(`base.ts`): + +`antigravity`、`azure-openai`、`blackbox-web`、`chatgpt-web`、`cliproxyapi`、 +`cloudflare-ai`、`codex`、`commandCode`、`cursor`、`default`、`devin-cli`、 +`muse-spark-web`、`nlpcloud`、`opencode`、`perplexity-web`、`petals`、 +`pollinations`、`puter`、`qoder`、`vertex`、`windsurf`,加上 `claudeIdentity.ts` +(共用身分識別輔助程式)和 `index.ts`(註冊表)。 + +> 注意:未列在此處的供應商由 `default.ts` 使用通用的 +> 與 OpenAI 相容的執行器處理。完整供應商目錄(268 個條目)位於 +> `src/shared/constants/providers.ts`。 + +### 4.3 `open-sse/translator/` + +軸輻式翻譯(OpenAI 為軸心)。 + +- **9 個請求翻譯器**(`translator/request/`): + `antigravity-to-openai`、`claude-to-gemini`、`claude-to-openai`、 + `gemini-to-openai`、`openai-responses`、`openai-to-claude`、 + `openai-to-cursor`、`openai-to-gemini`、`openai-to-kiro`。 +- **9 個回應翻譯器**(`translator/response/`): + `claude-to-openai`、`cursor-to-openai`、`gemini-to-claude`、`gemini-to-openai`、 + `kiro-to-openai`、`openai-responses`、`openai-to-antigravity`、 + `openai-to-claude`。 +- **9 個輔助程式**(`translator/helpers/`): + `claudeHelper`、`geminiHelper`、`geminiToolsSanitizer`、`maxTokensHelper`、 + `openaiHelper`、`responsesApiHelper`、`schemaCoercion`、`toolCallHelper`,加上 + 輔助程式測試。 +- **圖片輔助程式**(`translator/image/sizeMapper.ts`)。 +- 頂層:`bootstrap.ts`、`formats.ts`、`registry.ts`、`index.ts`。 + +### 4.4 `open-sse/transformer/` + +- `responsesTransformer.ts` — 基於 `TransformStream` 的 Responses API ↔ Chat + Completions 轉換器(由 `responses/` 路由的 catch-all 使用)。 + +### 4.5 `open-sse/services/` + +重點項目(完整列表位於 `open-sse/services/` 下): + +| 面向 | 檔案 | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Combo 路由 | `combo.ts`(17 種策略)、`comboConfig.ts`、`comboMetrics.ts`、`comboManifestMetrics.ts`、`comboAgentMiddleware.ts` | +| Auto Combo 引擎 | `autoCombo/` — `engine.ts`、`scoring.ts`、`taskFitness.ts`、`virtualFactory.ts`、`modePacks.ts`、`autoPrefix.ts`、`persistence.ts`、`providerDiversity.ts`、`providerRegistryAccessor.ts`、`routerStrategy.ts`、`selfHealing.ts`、`index.ts` | +| 韌性 | `accountFallback.ts`(冷卻 + 鎖定)、`errorClassifier.ts`、`emergencyFallback.ts`、`rateLimitManager.ts`、`rateLimitSemaphore.ts`、`accountSemaphore.ts`、`accountSelector.ts` | +| 配額 | `quotaMonitor.ts`、`quotaPreflight.ts`、`bailianQuotaFetcher.ts`、`codexQuotaFetcher.ts`、`deepseekQuotaFetcher.ts`、`openrouterQuotaFetcher.ts`、`openrouterFreeWindow.ts`、`crofUsageFetcher.ts`、`antigravityCredits.ts` | +| 快取 | `reasoningCache.ts`、`searchCache.ts`、`signatureCache.ts`、`requestDedup.ts` | +| 路由智慧 | `intentClassifier.ts`、`taskAwareRouter.ts`、`backgroundTaskDetector.ts`、`volumeDetector.ts`、`wildcardRouter.ts`、`workflowFSM.ts`、`specificityDetector.ts`、`specificityRules.ts`、`specificityTypes.ts` | +| 模型處理 | `modelCapabilities.ts`、`modelDeprecation.ts`、`modelFamilyFallback.ts`、`modelStrip.ts`、`model.ts`、`provider.ts`、`providerRequestDefaults.ts`、`providerCostData.ts`、`payloadRules.ts` | +| 壓縮 | `compression/` — 完整壓縮引擎接線 | +| Token + 工作階段 | `tokenRefresh.ts`、`sessionManager.ts`、`apiKeyRotator.ts`、`contextManager.ts`、`contextHandoff.ts`、`systemPrompt.ts`、`roleNormalizer.ts`、`responsesInputSanitizer.ts`、`toolSchemaSanitizer.ts`、`toolLimitDetector.ts`、`thinkingBudget.ts` | +| 層級 / 清單 | `tierResolver.ts`、`tierConfig.ts`、`tierDefaults.json`、`tierTypes.ts`、`manifestAdapter.ts` | +| IP / 網路 | `ipFilter.ts`、`webSearchFallback.ts` | +| 批次 | `batchProcessor.ts` | +| 用量 | `usage.ts` | + +### 4.6 `open-sse/mcp-server/` + +- **31 個已註冊工具**,在 `server.ts` 中接線(12 個定義於 `schemas/tools.ts` 範圍下, + 5 個壓縮工具、3 個記憶體工具、4 個技能工具,加上透過 `advancedTools.ts` 新增的進階工具)。 +- **3 種傳輸方式**:stdio、HTTP Streamable、SSE。 +- **13 個範圍**,宣告於 `src/shared/constants/mcpScopes.ts`。 +- 稽核資料表:`mcp_tool_audit`(由 `audit.ts` 填充)。 +- 檔案:`server.ts`、`index.ts`、`httpTransport.ts`、`audit.ts`、`scopeEnforcement.ts`、 + `runtimeHeartbeat.ts`、`descriptionCompressor.ts`、`schemas/{tools, a2a, audit, index}.ts`、 + `tools/{advancedTools, compressionTools, memoryTools, skillTools}.ts`, + 加上 `__tests__/` 下的測試。 +- 參見 [MCP-SERVER.md](../frameworks/MCP-SERVER.md) 以取得完整工具目錄。 + +### 4.7 `open-sse/config/` + +供應商註冊表(`providerRegistry.ts`、`providerModels.ts`、 +`providerHeaderProfiles.ts`)、各格式模型註冊表(`audioRegistry.ts`、 +`embeddingRegistry.ts`、`imageRegistry.ts`、`moderationRegistry.ts`、 +`musicRegistry.ts`、`rerankRegistry.ts`、`searchRegistry.ts`、`videoRegistry.ts`)、 +身分識別輔助程式(`codexIdentity.ts`、`codexInstructions.ts`、 +`anthropicHeaders.ts`、`antigravityUpstream.ts`、`antigravityModelAliases.ts`、 +`cliFingerprints.ts`、`toolCloaking.ts`、`defaultThinkingSignature.ts`)、 +憑證輔助程式(`credentialLoader.ts`、`codexClient.ts`),以及雲端 +配接器(`azureAi.ts`、`bedrock.ts`、`datarobot.ts`、`glmProvider.ts`、 +`maritalk.ts`、`oci.ts`、`petals.ts`、`runway.ts`、`sap.ts`、`watsonx.ts`、 +`ollamaModels.ts`、`errorConfig.ts`、`constants.ts`、`registryUtils.ts`)。 + +### 4.8 `open-sse/utils/` + +串流基礎元件與供應商輔助程式:`stream.ts`、`streamHandler.ts`、 +`streamHelpers.ts`、`streamPayloadCollector.ts`、`streamReadiness.ts`、 +`sseHeartbeat.ts`、`proxyFetch.ts`、`proxyDispatcher.ts`、`tlsClient.ts`、 +`networkProxy.ts`、`awsSigV4.ts`、`cacheControlPolicy.ts`、 +`cursorChecksum.ts`、`cursorAgentProtobuf.ts`、`cursorVersionDetector.ts`、 +`comfyuiClient.ts`、`kieTask.ts`、`bypassHandler.ts`、`aiSdkCompat.ts`、 +`thinkTagParser.ts`、`urlSanitize.ts`、`usageTracking.ts`、`requestLogger.ts`、 +`progressTracker.ts`、`cors.ts`、`error.ts`、`logger.ts`、`sleep.ts`、 +`ollamaTransform.ts`。 --- -### 4.3 Handlers (`open-sse/handlers/`) +## 5. `electron/` — 桌面包裝程式 -The **orchestration layer** — coordinates translation, execution, streaming, and error handling. - -| File | Purpose | -| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `chatCore.ts` | **Central orchestrator** (~600 lines). Handles the complete request lifecycle: format detection → translation → executor dispatch → streaming/non-streaming response → token refresh → error handling → usage logging. | -| `responsesHandler.ts` | Adapter for OpenAI's Responses API: converts Responses format → Chat Completions → sends to `chatCore` → converts SSE back to Responses format. | -| `embeddings.ts` | Embedding generation handler: resolves embedding model → provider, dispatches to provider API, returns OpenAI-compatible embedding response. Supports 6+ providers. | -| `imageGeneration.ts` | Image generation handler: resolves image model → provider, supports OpenAI-compatible, Gemini-image (Antigravity), and fallback (Nebius) modes. Returns base64 or URL images. | - -#### Request Lifecycle (chatCore.ts) - -```mermaid -sequenceDiagram - participant Client - participant chatCore - participant Translator - participant Executor - participant Provider - - Client->>chatCore: Request (any format) - chatCore->>chatCore: Detect source format - chatCore->>chatCore: Check bypass patterns - chatCore->>chatCore: Resolve model & provider - chatCore->>Translator: Translate request (source → OpenAI → target) - chatCore->>Executor: Get executor for provider - Executor->>Executor: Build URL, headers, transform request - Executor->>Executor: Refresh credentials if needed - Executor->>Provider: HTTP fetch (streaming or non-streaming) - - alt Streaming - Provider-->>chatCore: SSE stream - chatCore->>chatCore: Pipe through SSE transform stream - Note over chatCore: Transform stream translates
each chunk: target → OpenAI → source - chatCore-->>Client: Translated SSE stream - else Non-streaming - Provider-->>chatCore: JSON response - chatCore->>Translator: Translate response - chatCore-->>Client: Translated JSON - end - - alt Error (401, 429, 500...) - chatCore->>Executor: Retry with credential refresh - chatCore->>chatCore: Account fallback logic - end ``` +electron/ +├── main.js Electron 主程序 +├── preload.js 預載橋接(啟用 contextIsolation) +├── types.d.ts +├── package.json electron-builder 設定,版本 3.8.0 +├── README.md +├── assets/ 建置資源(圖示、授權檔案……) +├── node_modules/ 專屬 node_modules(better-sqlite3、electron-updater) +└── dist-electron/ 建置輸出(不提交) +``` + +五個 npm 腳本位於 workspace 根目錄:`electron:dev`、`electron:build`、 +`electron:build:{win,mac,linux}`、`electron:smoke:packaged`。自動更新透過 +`electron-updater` 指向 GitHub 發布 feed。 --- -### 4.4 Services (`open-sse/services/`) +## 6. `bin/` — CLI -Business logic that supports the handlers and executors. - -| File | Purpose | -| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `provider.ts` | **Format detection** (`detectFormat`): analyzes request body structure to identify Claude/OpenAI/Gemini/Antigravity/Responses formats (includes `max_tokens` heuristic for Claude). Also: URL building, header building, thinking config normalization. Supports `openai-compatible-*` and `anthropic-compatible-*` dynamic providers. | -| `model.ts` | Model string parsing (`claude/model-name` → `{provider: "claude", model: "model-name"}`), alias resolution with collision detection, input sanitization (rejects path traversal/control chars), and model info resolution with async alias getter support. | -| `accountFallback.ts` | Rate-limit handling: exponential backoff (1s → 2s → 4s → max 2min), account cooldown management, error classification (which errors trigger fallback vs. not). | -| `tokenRefresh.ts` | OAuth token refresh for **every provider**: Google (Gemini, Antigravity), Claude, Codex, Qwen, Qoder, GitHub (OAuth + Copilot dual-token), Kiro (AWS SSO OIDC + Social Auth). Includes in-flight promise deduplication cache and retry with exponential backoff. | -| `combo.ts` | **Combo models**: chains of fallback models. If model A fails with a fallback-eligible error, try model B, then C, etc. Returns actual upstream status codes. | -| `usage.ts` | Fetches quota/usage data from provider APIs (GitHub Copilot quotas, Antigravity model quotas, Codex rate limits, Kiro usage breakdowns, Claude settings). | -| `accountSelector.ts` | Smart account selection with scoring algorithm: considers priority, health status, round-robin position, and cooldown state to pick the optimal account for each request. | -| `contextManager.ts` | Request context lifecycle management: creates and tracks per-request context objects with metadata (request ID, timestamps, provider info) for debugging and logging. | -| `ipFilter.ts` | IP-based access control: supports allowlist and blocklist modes. Validates client IP against configured rules before processing API requests. | -| `sessionManager.ts` | Session tracking with client fingerprinting: tracks active sessions using hashed client identifiers, monitors request counts, and provides session metrics. | -| `signatureCache.ts` | Request signature-based deduplication cache: prevents duplicate requests by caching recent request signatures and returning cached responses for identical requests within a time window. | -| `systemPrompt.ts` | Global system prompt injection: prepends or appends a configurable system prompt to all requests, with per-provider compatibility handling. | -| `thinkingBudget.ts` | Reasoning token budget management: supports passthrough, auto (strip thinking config), custom (fixed budget), and adaptive (complexity-scaled) modes for controlling thinking/reasoning tokens. | -| `wildcardRouter.ts` | Wildcard model pattern routing: resolves wildcard patterns (e.g., `*/claude-*`) to concrete provider/model pairs based on availability and priority. | - -#### Token Refresh Deduplication - -```mermaid -sequenceDiagram - participant R1 as Request 1 - participant R2 as Request 2 - participant Cache as refreshPromiseCache - participant OAuth as OAuth Provider - - R1->>Cache: getAccessToken("gemini", token) - Cache->>Cache: No in-flight promise - Cache->>OAuth: Start refresh - R2->>Cache: getAccessToken("gemini", token) - Cache->>Cache: Found in-flight promise - Cache-->>R2: Return existing promise - OAuth-->>Cache: New access token - Cache-->>R1: New access token - Cache-->>R2: Same access token (shared) - Cache->>Cache: Delete cache entry +``` +bin/ +├── omniroute.mjs 主要 CLI 進入點(Node ESM) +├── reset-password.mjs 從 CLI 重設管理密碼 +├── mcp-server.mjs MCP 伺服器啟動器(stdio) +├── nodeRuntimeSupport.mjs Node 版本守衛 +└── cli/ + ├── program.mjs Commander 程式建置器 + ├── runtime.mjs withRuntime 輔助程式(server-first/db-fallback) + ├── output.mjs 輸出格式化器(json/jsonl/table/csv) + ├── i18n.mjs t() 輔助程式及語系包 + ├── api.mjs API 請求輔助程式 + ├── data-dir.mjs + ├── encryption.mjs + ├── sqlite.mjs + └── commands/ + ├── registry.mjs 命令註冊 + ├── setup.mjs + ├── doctor.mjs + ├── providers.mjs + └── ... (每個命令/群組一個檔案) ``` -#### Account Fallback State Machine +`package.json` → `bin` 中公開了兩個二進位檔: -```mermaid -stateDiagram-v2 - [*] --> Active - Active --> Error: Request fails (401/429/500) - Error --> Cooldown: Apply backoff - Cooldown --> Active: Cooldown expires - Active --> Active: Request succeeds (reset backoff) - - state Error { - [*] --> ClassifyError - ClassifyError --> ShouldFallback: Rate limit / Auth / Transient - ClassifyError --> NoFallback: 400 Bad Request - } - - state Cooldown { - [*] --> ExponentialBackoff - ExponentialBackoff: Level 0 = 1s - ExponentialBackoff: Level 1 = 2s - ExponentialBackoff: Level 2 = 4s - ExponentialBackoff: Max = 2min - } -``` - -#### Combo Model Chain - -```mermaid -flowchart LR - A["Request with\ncombo model"] --> B["Model A"] - B -->|"2xx Success"| C["Return response"] - B -->|"429/401/500"| D{"Fallback\neligible?"} - D -->|Yes| E["Model B"] - D -->|No| F["Return error"] - E -->|"2xx Success"| C - E -->|"429/401/500"| G{"Fallback\neligible?"} - G -->|Yes| H["Model C"] - G -->|No| F - H -->|"2xx Success"| C - H -->|"Fail"| I["All failed →\nReturn last status"] -``` +- `omniroute` → `bin/omniroute.mjs` +- `omniroute-reset-password` → `bin/reset-password.mjs` --- -### 4.5 Translator (`open-sse/translator/`) +## 7. `tests/` -The **format translation engine** using a self-registering plugin system. +| 目錄 | 類型 | +| --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `tests/unit/` | 透過 Node 原生測試執行器的單元測試(1821 個檔案,加上 `api/`、`auth/`、`authz/` 子目錄) | +| `tests/integration/` | 跨模組 + 資料庫狀態測試 | +| `tests/e2e/` | Playwright UI 測試 | +| `tests/protocols-e2e/` | MCP/A2A 協定 e2e 測試 | +| `tests/translator/` | 翻譯器專用測試 | +| `tests/security/` | 安全性回歸測試 | +| `tests/load/` | 負載/壓力測試 | +| `tests/golden-set/` | 翻譯器回歸測試的參考輸出 | +| `tests/helpers/`、`tests/fixtures/`、`tests/manual/`、`tests/scratch_test.mjs` | 支援 | -#### 架構 +常用命令: -```mermaid -graph TD - subgraph "Request Translation" - A["Claude → OpenAI"] - B["Gemini → OpenAI"] - C["Antigravity → OpenAI"] - D["OpenAI Responses → OpenAI"] - E["OpenAI → Claude"] - F["OpenAI → Gemini"] - G["OpenAI → Kiro"] - H["OpenAI → Cursor"] - end - - subgraph "Response Translation" - I["Claude → OpenAI"] - J["Gemini → OpenAI"] - K["Kiro → OpenAI"] - L["Cursor → OpenAI"] - M["OpenAI → Claude"] - N["OpenAI → Antigravity"] - O["OpenAI → Responses"] - end -``` - -| Directory | Files | Description | -| ------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `request/` | 8 translators | Convert request bodies between formats. Each file self-registers via `register(from, to, fn)` on import. | -| `response/` | 7 translators | Convert streaming response chunks between formats. Handles SSE event types, thinking blocks, tool calls. | -| `helpers/` | 6 helpers | Shared utilities: `claudeHelper` (system prompt extraction, thinking config), `geminiHelper` (parts/contents mapping), `openaiHelper` (format filtering), `toolCallHelper` (ID generation, missing response injection), `maxTokensHelper`, `responsesApiHelper`. | -| `index.ts` | — | Translation engine: `translateRequest()`, `translateResponse()`, state management, registry. | -| `formats.ts` | — | Format constants: `OPENAI`, `CLAUDE`, `GEMINI`, `ANTIGRAVITY`, `KIRO`, `CURSOR`, `OPENAI_RESPONSES`. | - -#### Key Design: Self-Registering Plugins - -```javascript -// Each translator file calls register() on import: -import { register } from "../index.js"; -register("claude", "openai", translateClaudeToOpenAI); - -// The index.js imports all translator files, triggering registration: -import "./request/claude-to-openai.js"; // ← self-registers -``` +| 命令 | 執行內容 | +| ----------------------------------------------------------- | ----------------------------------------------------------------- | +| `npm run test:unit` | 所有 `tests/unit/*.test.ts` 透過 Node 測試執行器(並發數 10) | +| `npm run test:vitest` | Vitest 套件(MCP、autoCombo、快取) | +| `npm run test:e2e` | Playwright UI 套件 | +| `npm run test:protocols:e2e` | MCP + A2A 協定 e2e | +| `npm run test:coverage` | 覆蓋率門檻(≥60% 行/陳述式/函式/分支) | +| `node --import tsx/esm --test tests/unit/.test.ts` | 單一檔案執行 | --- -### 4.6 Utils (`open-sse/utils/`) +## 8. `scripts/` -| File | Purpose | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `error.ts` | Error response building (OpenAI-compatible format), upstream error parsing, Antigravity retry-time extraction from error messages, SSE error streaming. | -| `stream.ts` | **SSE Transform Stream** — the core streaming pipeline. Two modes: `TRANSLATE` (full format translation) and `PASSTHROUGH` (normalize + extract usage). Handles chunk buffering, usage estimation, content length tracking. Per-stream encoder/decoder instances avoid shared state. | -| `streamHelpers.ts` | Low-level SSE utilities: `parseSSELine` (whitespace-tolerant), `hasValuableContent` (filters empty chunks for OpenAI/Claude/Gemini), `fixInvalidId`, `formatSSE` (format-aware SSE serialization with `perf_metrics` cleanup). | -| `usageTracking.ts` | Token usage extraction from any format (Claude/OpenAI/Gemini/Responses), estimation with separate tool/message char-per-token ratios, buffer addition (2000 tokens safety margin), format-specific field filtering, console logging with ANSI colors. | -| `requestLogger.ts` | Legacy file-based request logging helper kept for compatibility. Current deployments should prefer `APP_LOG_TO_FILE` for application logs and the call log pipeline for persisted request artifacts. | -| `bypassHandler.ts` | Intercepts specific patterns from Claude CLI (title extraction, warmup, count) and returns fake responses without calling any provider. Supports both streaming and non-streaming. Intentionally limited to Claude CLI scope. | -| `networkProxy.ts` | Resolves outbound proxy URL for a given provider with precedence: provider-specific config → global config → environment variables (`HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY`). Supports `NO_PROXY` exclusions. Caches config for 30s. | +按用途分為 6 個子資料夾。 -#### SSE Streaming Pipeline - -```mermaid -flowchart TD - A["Provider SSE stream"] --> B["TextDecoder\n(per-stream instance)"] - B --> C["Buffer lines\n(split on newline)"] - C --> D["parseSSELine()\n(trim whitespace, parse JSON)"] - D --> E{"Mode?"} - E -->|TRANSLATE| F["translateResponse()\ntarget → OpenAI → source"] - E -->|PASSTHROUGH| G["fixInvalidId()\nnormalize chunk"] - F --> H["hasValuableContent()\nfilter empty chunks"] - G --> H - H -->|"Has content"| I["extractUsage()\ntrack token counts"] - H -->|"Empty"| J["Skip chunk"] - I --> K["formatSSE()\nserialize + clean perf_metrics"] - K --> L["TextEncoder\n(per-stream instance)"] - L --> M["Enqueue to\nclient stream"] - - style A fill:#f9f,stroke:#333 - style M fill:#9f9,stroke:#333 -``` - -#### Request Logger Session Structure - -``` -logs/ -└── claude_gemini_claude-sonnet_20260208_143045/ - ├── 1_req_client.json ← Raw client request - ├── 2_req_source.json ← After initial conversion - ├── 3_req_openai.json ← OpenAI intermediate format - ├── 4_req_target.json ← Final target format - ├── 5_res_provider.txt ← Provider SSE chunks (streaming) - ├── 5_res_provider.json ← Provider response (non-streaming) - ├── 6_res_openai.txt ← OpenAI intermediate chunks - ├── 7_res_client.txt ← Client-facing SSE chunks - └── 6_error.json ← Error details (if any) -``` +- **`scripts/build/`** — `build-next-isolated.mjs`、`prepublish.ts`、 + `prepare-electron-standalone.mjs`、`pack-artifact-policy.ts`、 + `validate-pack-artifact.ts`、`postinstall.mjs`、`postinstallSupport.mjs`、 + `uninstall.mjs`、`bootstrap-env.mjs`、`runtime-env.mjs`、 + `native-binary-compat.mjs`。 +- **`scripts/dev/`** — `run-next.mjs`、`run-next-playwright.mjs`、 + `run-standalone.mjs`、`standalone-server-ws.mjs`、`responses-ws-proxy.mjs`、 + `v1-ws-bridge.mjs`、`smoke-electron-packaged.mjs`、 + `run-playwright-tests.mjs`、`run-ecosystem-tests.mjs`、 + `run-protocol-clients-tests.mjs`、`sync-env.mjs`、`healthcheck.mjs`、 + `system-info.mjs`。 +- **`scripts/check/`** — `check-cycles.mjs`、`check-docs-sync.mjs`、 + `check-docs-counts-sync.mjs`、`check-env-doc-sync.mjs`、 + `check-deprecated-versions.mjs`、`check-route-validation.mjs`、 + `check-t11-any-budget.mjs`、`check-pr-test-policy.mjs`、 + `check-supported-node-runtime.ts`、`test-report-summary.mjs`。 +- **`scripts/docs/`** — `generate-docs-index.mjs`、`gen-provider-reference.ts`。 +- **`scripts/i18n/`** — `generate-multilang.mjs`、`run-visual-qa.mjs`、 + `generate-qa-checklist.mjs`、`apply-priority-overrides.mjs`、 + `validate_translation.py`、`check_translations.py`、`i18n_autotranslate.py`、 + `untranslatable-keys.json`。 +- **`scripts/ad-hoc/`** — `cursor-tap.cjs`、`sync-cursor-models.mjs`、 + `migrate-env.mjs`、`dbsetup.js`。 --- -### 4.7 Application Layer (`src/`) +## 9. 請求管線(摘要) -| Directory | Purpose | -| ------------- | ---------------------------------------------------------------------- | -| `src/app/` | Web UI, API routes, Express middleware, OAuth callback handlers | -| `src/lib/` | Database access (`localDb.ts`, `usageDb.ts`), authentication, shared | -| `src/mitm/` | Man-in-the-middle proxy utilities for intercepting provider traffic | -| `src/models/` | Database model definitions | -| `src/shared/` | Wrappers around open-sse functions (provider, stream, error, etc.) | -| `src/sse/` | SSE endpoint handlers that wire the open-sse library to Express routes | -| `src/store/` | Application state management | +![請求管線(/v1/chat/completions)](../diagrams/exported/request-pipeline.svg) -#### Notable API Routes +> 來源:[diagrams/request-pipeline.mmd](../diagrams/request-pipeline.mmd) -| Route | Methods | Purpose | -| --------------------------------------------- | --------------- | ------------------------------------------------------------------------------------- | -| `/api/provider-models` | GET/POST/DELETE | CRUD for custom models per provider | -| `/api/models/catalog` | GET | Aggregated catalog of all models (chat, embedding, image, custom) grouped by provider | -| `/api/settings/proxy` | GET/PUT/DELETE | Hierarchical outbound proxy configuration (`global/providers/combos/keys`) | -| `/api/settings/proxy/test` | POST | Validates proxy connectivity and returns public IP/latency | -| `/v1/providers/[provider]/chat/completions` | POST | Dedicated per-provider chat completions with model validation | -| `/v1/providers/[provider]/embeddings` | POST | Dedicated per-provider embeddings with model validation | -| `/v1/providers/[provider]/images/generations` | POST | Dedicated per-provider image generation with model validation | -| `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist management | -| `/api/settings/thinking-budget` | GET/PUT | Reasoning token budget configuration (passthrough/auto/custom/adaptive) | -| `/api/settings/system-prompt` | GET/PUT | Global system prompt injection for all requests | -| `/api/sessions` | GET | Active session tracking and metrics | -| `/api/rate-limits` | GET | Per-account rate limit status | - ---- - -## 5. Key Design Patterns - -### 5.1 Hub-and-Spoke Translation - -All formats translate through **OpenAI format as the hub**. Adding a new provider only requires writing **one pair** of translators (to/from OpenAI), not N pairs. - -### 5.2 Executor Strategy Pattern - -Each provider has a dedicated executor class inheriting from `BaseExecutor`. The factory in `executors/index.ts` selects the right one at runtime. - -### 5.3 Self-Registering Plugin System - -Translator modules register themselves on import via `register()`. Adding a new translator is just creating a file and importing it. - -### 5.4 Account Fallback with Exponential Backoff - -When a provider returns 429/401/500, the system can switch to the next account, applying exponential cooldowns (1s → 2s → 4s → max 2min). - -### 5.5 Combo Model Chains - -A "combo" groups multiple `provider/model` strings. If the first fails, fallback to the next automatically. - -### 5.6 Stateful Streaming Translation - -Response translation maintains state across SSE chunks (thinking block tracking, tool call accumulation, content block indexing) via the `initState()` mechanism. - -### 5.7 Usage Safety Buffer - -A 2000-token buffer is added to reported usage to prevent clients from hitting context window limits due to overhead from system prompts and format translation. - ---- - -## 6. Supported Formats - -| Format | Direction | Identifier | -| ----------------------- | --------------- | ------------------ | -| OpenAI Chat Completions | source + target | `openai` | -| OpenAI Responses API | source + target | `openai-responses` | -| Anthropic Claude | source + target | `claude` | -| Google Gemini | source + target | `gemini` | -| Antigravity | source + target | `antigravity` | -| AWS Kiro | target only | `kiro` | -| Cursor | target only | `cursor` | - ---- - -## 7. Supported Providers - -| Provider | Auth Method | Executor | Key Notes | -| ------------------------ | ---------------------- | ----------- | --------------------------------------------- | -| Anthropic Claude | API key or OAuth | Default | Uses `x-api-key` header | -| Google Gemini | API key or OAuth | Default | Uses `x-goog-api-key` header | -| Antigravity | OAuth | Antigravity | Multi-URL fallback, custom retry parsing | -| OpenAI | API key | Default | Standard Bearer auth | -| Codex | OAuth | Codex | Injects system instructions, manages thinking | -| GitHub Copilot | OAuth + Copilot token | Github | Dual token, VSCode header mimicking | -| Kiro (AWS) | AWS SSO OIDC or Social | Kiro | Binary EventStream parsing | -| Cursor IDE | Checksum auth | Cursor | Protobuf encoding, SHA-256 checksums | -| Qwen | OAuth | Default | Standard auth | -| Qoder | OAuth (Basic + Bearer) | Default | Dual auth header | -| OpenRouter | API key | Default | Standard Bearer auth | -| GLM, Kimi, MiniMax | API key | Default | Claude-compatible, use `x-api-key` | -| `openai-compatible-*` | API key | Default | Dynamic: any OpenAI-compatible endpoint | -| `anthropic-compatible-*` | API key | Default | Dynamic: any Claude-compatible endpoint | - ---- - -## 8. Data Flow Summary - -### Streaming Request - -```mermaid -flowchart LR - A["Client"] --> B["detectFormat()"] - B --> C["translateRequest()\nsource → OpenAI → target"] - C --> D["Executor\nbuildUrl + buildHeaders"] - D --> E["fetch(providerURL)"] - E --> F["createSSEStream()\nTRANSLATE mode"] - F --> G["parseSSELine()"] - G --> H["translateResponse()\ntarget → OpenAI → source"] - H --> I["extractUsage()\n+ addBuffer"] - I --> J["formatSSE()"] - J --> K["Client receives\ntranslated SSE"] - K --> L["logUsage()\nsaveRequestUsage()"] +``` +客戶端請求 + → /v1/chat/completions (route.ts) + CORS 預檢檢查 + Zod 驗證(shared/validation/schemas.ts 中的 chatCompletionsSchema) + 認證(extractApiKey + isValidApiKey 或 requireManagementAuth) + 政策引擎(src/server/authz/pipeline.ts) + 防護措施(PII 遮罩、提示注入防護、視覺橋接) + → handleChatCore() (open-sse/handlers/chatCore.ts) + 快取檢查(語意 + 讀取快取) + 速率限制(rateLimitManager、accountSemaphore) + Combo 路由(若模型解析為 combo) + comboResolver → 每個目標迴圈 → handleSingleModel() + translateRequest() (open-sse/translator/request/*) + getExecutor(providerId).execute() (open-sse/executors/*) + 向上游發起請求 → 透過 accountFallback 重試/退避 + translateResponse() (open-sse/translator/response/*) + SSE 串流或 JSON 回應 + 若為 Responses API:透過 open-sse/transformer/responsesTransformer.ts 的 TransformStream + → 合規稽核(src/lib/compliance/) + → 回應傳回客戶端 ``` -### Non-Streaming Request +### 韌性執行時期狀態(三種機制) -```mermaid -flowchart LR - A["Client"] --> B["detectFormat()"] - B --> C["translateRequest()\nsource → OpenAI → target"] - C --> D["Executor.execute()"] - D --> E["translateResponse()\ntarget → OpenAI → source"] - E --> F["Return JSON\nresponse"] -``` +| 機制 | 範圍 | 位置 | +| -------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| 供應商斷路器 | 整個供應商 | `src/shared/utils/circuitBreaker.ts`,持久化於 `domain_circuit_breakers` | +| 連線冷卻 | 單一帳戶/金鑰 | `markAccountUnavailable()` 位於 `src/sse/services/auth.ts`;由 `accountFallback.checkFallbackError()` 使用 | +| 模型鎖定 | 供應商 + 連線 + 模型 | `open-sse/services/accountFallback.ts`,持久化於 `domain_lockout_state` | -### Bypass Flow (Claude CLI) +參見 [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) 及 +[CLAUDE.md](../../CLAUDE.md) 中的專屬章節。 -```mermaid -flowchart LR - A["Claude CLI request"] --> B{"Match bypass\npattern?"} - B -->|"Title/Warmup/Count"| C["Generate fake\nOpenAI response"] - B -->|"No match"| D["Normal flow"] - C --> E["Translate to\nsource format"] - E --> F["Return without\ncalling provider"] -``` +--- + +## 10. 如何貢獻 + +### 新增供應商 + +1. 在 `src/shared/constants/providers.ts` 中註冊(載入時以 Zod 驗證)。 +2. 若需要自訂邏輯,在 `open-sse/executors/` 中新增執行器(繼承 `BaseExecutor`)。 +3. 若該供應商不支援 OpenAI 格式,在 `open-sse/translator/` 中新增翻譯器。 +4. 若為 OAuth 基礎,在 `src/lib/oauth/providers/` 和 `src/lib/oauth/services/` 下新增設定。 +5. 在 `open-sse/config/providerRegistry.ts`(或 `open-sse/config/` 下格式專屬的註冊表)中註冊模型。 +6. 在 `tests/unit/` 下撰寫測試。 + +### 新增 API 路由 + +1. 建立 `src/app/api/your-route/route.ts`。 +2. 遵循模式:CORS → Zod 請求體驗證 → 認證 → 處理器委派。 +3. 若為新的請求形狀:在 `src/shared/validation/schemas.ts` 中新增 Zod 架構。 +4. 若僅限管理:將路徑加入 `src/shared/constants/publicApiRoutes.ts`(公開 API 表面的封鎖清單)。 +5. 在 `tests/unit/` 下新增測試。 +6. 更新 `docs/reference/API_REFERENCE.md` 和 `docs/openapi.yaml`。 + +### 新增資料庫模組 + +1. 建立 `src/lib/db/yourModule.ts` 並從 `./core.ts` 匯入 `getDbInstance()`。 +2. 匯出您領域的 CRUD 函式。 +3. 若需新建資料表:在 `src/lib/db/migrations/` 下新增遷移檔案,依序編號、冪等、事務性。 +4. 從 `src/lib/localDb.ts` 重新匯出(僅重新匯出 — **不含邏輯**)。 +5. 在 `tests/unit/` 下新增測試。 + +### 新增 MCP 工具 + +1. 在 `open-sse/mcp-server/tools/` 下新增工具定義(或擴充 `open-sse/mcp-server/schemas/tools.ts`)。 +2. 在 `src/shared/constants/mcpScopes.ts` 中指派適當的範圍。 +3. 在 `open-sse/mcp-server/server.ts` 中註冊該工具。 +4. 在 `open-sse/mcp-server/__tests__/` 下新增測試。 +5. 更新 [MCP-SERVER.md](../frameworks/MCP-SERVER.md)。 + +### 新增 A2A 技能 + +參見 [A2A-SERVER.md § 新增技能](../frameworks/A2A-SERVER.md)。技能位於 +`src/lib/a2a/skills/`,並透過 A2A 任務管理器註冊。 + +--- + +## 11. 慣例 + +- **程式碼風格**:2 空格縮排、雙引號、100 字元寬度、分號、`es5` 結尾逗號 — 由 Prettier 透過 `lint-staged` 強制執行。 +- **匯入順序**:外部 → 內部(`@/`、`@omniroute/open-sse`)→ 相對。 +- **命名**:檔案 `camelCase` 或 `kebab-case`,元件 `PascalCase`,常數 `UPPER_SNAKE`。 +- **ESLint**:`no-eval`、`no-implied-eval`、`no-new-func` = `error` 適用於所有地方;`no-explicit-any` = `warn` 在 `open-sse/` 和 `tests/` 中,其他位置為 error。 +- **TypeScript**:`strict: false`(舊有設定)。在跨模組邊界處優先使用明確型別而非推斷。 +- **資料庫**:切勿在路由或處理器中撰寫原始 SQL — 務必透過 `src/lib/db/` 模組操作。切勿在 `src/lib/localDb.ts` 中新增邏輯。 +- **資料庫實體型別(#3512)**:一個寫入或讀取資料表列形狀的函式,應接收/回傳一個與該資料表欄位 1:1 對應的命名 TS 介面,而非 `any` 或呼叫處的內聯匿名型別。將該介面置於函式旁邊(例如將 `export interface UsageEntry` 放在 `src/lib/usage/usageHistory.ts` 中 `saveRequestUsage` 之上),在不同寫入者逐步填充該列時,將個別欄位保持為可選/可為 null,並對在不同呼叫者間形狀各異的欄位優先使用 `unknown` 而非 `any`(在欄位上註明,例如 `UsageEntry.tokens` 接受原始供應商形狀的用量和正規化後的形狀)。一旦某個檔案的 `any` 計數以此方式歸零,將其加入 `check:any-budget:t11` 白名單(`scripts/check/check-t11-any-budget.mjs`、`maxAny: 0`),使其不會回歸。這是首批適用的慣例 — 更廣泛的「無匿名 `any`」清理將在其餘程式碼庫中迭代進行。 +- **錯誤處理**:使用特定錯誤型別的 try/catch,以 pino 上下文記錄日誌。切勿在 SSE 串流中默默吞嚥錯誤;使用中止信號進行清理。 +- **安全性**:切勿使用 `eval()` / `new Function()` / 隱含 eval。使用 Zod 驗證所有輸入。加密靜態憑證(AES-256-GCM)。保持 `src/shared/constants/upstreamHeaders.ts` 封鎖清單與清理/驗證層一致。 +- **提交訊息**:約定式提交 — `feat(scope): subject`。允許的範圍:`db`、`sse`、`oauth`、`dashboard`、`api`、`cli`、`docker`、`ci`、`mcp`、`a2a`、`memory`、`skills`。 +- **分支**:前綴 `feat/`、`fix/`、`refactor/`、`docs/`、`test/`、`chore/`。切勿直接提交到 `main`。 +- **Husky**:pre-commit 執行 `lint-staged` + `check:docs-sync` + `check:any-budget:t11`;pre-push 執行 `check:any-budget:t11` + `check:tracked-artifacts`(快速閘道;不包含 `test:unit`)。 + +--- + +## 12. 硬性規則(來自 CLAUDE.md) + +1. 切勿提交機密或憑證。 +2. 切勿在 `src/lib/localDb.ts` 中新增邏輯。 +3. 切勿使用 `eval()` / `new Function()` / 隱含 eval。 +4. 切勿直接提交到 `main`。 +5. 切勿在路由中撰寫原始 SQL — 務必透過 `src/lib/db/` 模組操作。 +6. 切勿在 SSE 串流中默默吞嚥錯誤。 +7. 務必使用 Zod 架構驗證輸入。 +8. 變更正式環境程式碼時務必包含測試。 +9. 覆蓋率必須保持 ≥ 60%(陳述式、行、函式、分支)。 + +--- + +## 13. 參見 + +- [ARCHITECTURE.md](./ARCHITECTURE.md) — 高階架構與模組職責。 +- [API_REFERENCE.md](../reference/API_REFERENCE.md) — 公開 + 管理 API 參考。 +- [FEATURES.md](../guides/FEATURES.md) — 功能矩陣與版本亮點。 +- [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) — 斷路器、冷卻、鎖定深入探討。 +- [AUTO-COMBO.md](../routing/AUTO-COMBO.md) — Auto Combo 評分與策略。 +- [MCP-SERVER.md](../frameworks/MCP-SERVER.md) — 完整 MCP 工具目錄 + 傳輸方式。 +- [A2A-SERVER.md](../frameworks/A2A-SERVER.md) — A2A 協定技能與探索。 +- [COMPRESSION_GUIDE.md](../compression/COMPRESSION_GUIDE.md) — RTK + Caveman 壓縮。 +- [CLI-TOOLS.md](../reference/CLI-TOOLS.md) — CLI 整合。 +- [ELECTRON_GUIDE.md](../guides/ELECTRON_GUIDE.md)(若存在)、[DOCKER_GUIDE.md](../guides/DOCKER_GUIDE.md)、[FLY_IO_DEPLOYMENT_GUIDE.md](../ops/FLY_IO_DEPLOYMENT_GUIDE.md)、[VM_DEPLOYMENT_GUIDE.md](../ops/VM_DEPLOYMENT_GUIDE.md)、[TERMUX_GUIDE.md](../guides/TERMUX_GUIDE.md)、[PWA_GUIDE.md](../guides/PWA_GUIDE.md) — 部署目標。 +- [TROUBLESHOOTING.md](../guides/TROUBLESHOOTING.md) — 常見運維問題。 +- [CONTRIBUTING.md](../../CONTRIBUTING.md) — 貢獻者工作流程。 +- [CLAUDE.md](../../CLAUDE.md) — 給 Claude Code 的儲存庫規則(上述許多慣例的真實來源)。 +- [AGENTS.md](../../AGENTS.md) — 代理程式使用的進階架構參考。 diff --git a/docs/i18n/zh-TW/docs/cloudflare-zero-trust-guide.md b/docs/i18n/zh-TW/docs/cloudflare-zero-trust-guide.md index 16662f09de..bcbdb78364 100644 --- a/docs/i18n/zh-TW/docs/cloudflare-zero-trust-guide.md +++ b/docs/i18n/zh-TW/docs/cloudflare-zero-trust-guide.md @@ -1,106 +1,106 @@ -# Guia Completo: Cloudflare Tunnel & Zero Trust (Split-Port) (中文(簡體)) +# 完整指南:Cloudflare Tunnel 與 Zero Trust (Split-Port) (中文(繁體)) -🌐 **Languages:** 🇺🇸 [English](../../../../docs/cloudflare-zero-trust-guide.md) · 🇪🇸 [es](../../es/docs/cloudflare-zero-trust-guide.md) · 🇫🇷 [fr](../../fr/docs/cloudflare-zero-trust-guide.md) · 🇩🇪 [de](../../de/docs/cloudflare-zero-trust-guide.md) · 🇮🇹 [it](../../it/docs/cloudflare-zero-trust-guide.md) · 🇷🇺 [ru](../../ru/docs/cloudflare-zero-trust-guide.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/cloudflare-zero-trust-guide.md) · 🇯🇵 [ja](../../ja/docs/cloudflare-zero-trust-guide.md) · 🇰🇷 [ko](../../ko/docs/cloudflare-zero-trust-guide.md) · 🇸🇦 [ar](../../ar/docs/cloudflare-zero-trust-guide.md) · 🇮🇳 [hi](../../hi/docs/cloudflare-zero-trust-guide.md) · 🇮🇳 [in](../../in/docs/cloudflare-zero-trust-guide.md) · 🇹🇭 [th](../../th/docs/cloudflare-zero-trust-guide.md) · 🇻🇳 [vi](../../vi/docs/cloudflare-zero-trust-guide.md) · 🇮🇩 [id](../../id/docs/cloudflare-zero-trust-guide.md) · 🇲🇾 [ms](../../ms/docs/cloudflare-zero-trust-guide.md) · 🇳🇱 [nl](../../nl/docs/cloudflare-zero-trust-guide.md) · 🇵🇱 [pl](../../pl/docs/cloudflare-zero-trust-guide.md) · 🇸🇪 [sv](../../sv/docs/cloudflare-zero-trust-guide.md) · 🇳🇴 [no](../../no/docs/cloudflare-zero-trust-guide.md) · 🇩🇰 [da](../../da/docs/cloudflare-zero-trust-guide.md) · 🇫🇮 [fi](../../fi/docs/cloudflare-zero-trust-guide.md) · 🇵🇹 [pt](../../pt/docs/cloudflare-zero-trust-guide.md) · 🇷🇴 [ro](../../ro/docs/cloudflare-zero-trust-guide.md) · 🇭🇺 [hu](../../hu/docs/cloudflare-zero-trust-guide.md) · 🇧🇬 [bg](../../bg/docs/cloudflare-zero-trust-guide.md) · 🇸🇰 [sk](../../sk/docs/cloudflare-zero-trust-guide.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/cloudflare-zero-trust-guide.md) · 🇮🇱 [he](../../he/docs/cloudflare-zero-trust-guide.md) · 🇵🇭 [phi](../../phi/docs/cloudflare-zero-trust-guide.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/cloudflare-zero-trust-guide.md) · 🇨🇿 [cs](../../cs/docs/cloudflare-zero-trust-guide.md) · 🇹🇷 [tr](../../tr/docs/cloudflare-zero-trust-guide.md) +🌐 **Languages:** 🇺🇸 [English](../../../../docs/cloudflare-zero-trust-guide.md) · 🇪🇸 [es](../../es/docs/cloudflare-zero-trust-guide.md) · 🇫🇷 [fr](../../fr/docs/cloudflare-zero-trust-guide.md) · 🇩🇪 [de](../../de/docs/cloudflare-zero-trust-guide.md) · 🇮🇹 [it](../../it/docs/cloudflare-zero-trust-guide.md) · 🇷🇺 [ru](../../ru/docs/cloudflare-zero-trust-guide.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/cloudflare-zero-trust-guide.md) · 🇹🇼 [zh-TW](../../zh-TW/docs/cloudflare-zero-trust-guide.md) · 🇯🇵 [ja](../../ja/docs/cloudflare-zero-trust-guide.md) · 🇰🇷 [ko](../../ko/docs/cloudflare-zero-trust-guide.md) · 🇸🇦 [ar](../../ar/docs/cloudflare-zero-trust-guide.md) · 🇮🇳 [hi](../../hi/docs/cloudflare-zero-trust-guide.md) · 🇮🇳 [in](../../in/docs/cloudflare-zero-trust-guide.md) · 🇹🇭 [th](../../th/docs/cloudflare-zero-trust-guide.md) · 🇻🇳 [vi](../../vi/docs/cloudflare-zero-trust-guide.md) · 🇮🇩 [id](../../id/docs/cloudflare-zero-trust-guide.md) · 🇲🇾 [ms](../../ms/docs/cloudflare-zero-trust-guide.md) · 🇳🇱 [nl](../../nl/docs/cloudflare-zero-trust-guide.md) · 🇵🇱 [pl](../../pl/docs/cloudflare-zero-trust-guide.md) · 🇸🇪 [sv](../../sv/docs/cloudflare-zero-trust-guide.md) · 🇳🇴 [no](../../no/docs/cloudflare-zero-trust-guide.md) · 🇩🇰 [da](../../da/docs/cloudflare-zero-trust-guide.md) · 🇫🇮 [fi](../../fi/docs/cloudflare-zero-trust-guide.md) · 🇵🇹 [pt](../../pt/docs/cloudflare-zero-trust-guide.md) · 🇷🇴 [ro](../../ro/docs/cloudflare-zero-trust-guide.md) · 🇭🇺 [hu](../../hu/docs/cloudflare-zero-trust-guide.md) · 🇧🇬 [bg](../../bg/docs/cloudflare-zero-trust-guide.md) · 🇸🇰 [sk](../../sk/docs/cloudflare-zero-trust-guide.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/cloudflare-zero-trust-guide.md) · 🇮🇱 [he](../../he/docs/cloudflare-zero-trust-guide.md) · 🇵🇭 [phi](../../phi/docs/cloudflare-zero-trust-guide.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/cloudflare-zero-trust-guide.md) · 🇨🇿 [cs](../../cs/docs/cloudflare-zero-trust-guide.md) · 🇹🇷 [tr](../../tr/docs/cloudflare-zero-trust-guide.md) --- -Este guia documenta o padrão ouro de infraestrutura de rede para proteger o **OmniRoute** e expor sua aplicação de forma segura para a internet, **sem abrir nenhuma porta (Zero Inbound)**. +本指南記錄了保護 **OmniRoute** 並將應用程式安全地暴露到網際網路的網路基礎設施黃金標準,**無需開放任何連接埠(Zero Inbound)**。 -## O que foi feito na sua VM? +## 您的虛擬機器上做了什麼? -Nós ativamos o OmniRoute em modo **Split-Port** através do PM2: +我們透過 PM2 以 **Split-Port** 模式啟動了 OmniRoute: -- **Porta \`20128\`:** Roda **apenas a API** `/v1`. -- **Porta \`20129\`:** Roda **apenas o Dashboard** Administrativo visual. +- **連接埠 `20128`:** 僅執行 **API** `/v1`。 +- **連接埠 `20129`:** 僅執行可視化管理 **Dashboard**。 -Além disso, o serviço interno exige \`REQUIRE_API_KEY=true\`, o que significa que nenhum agente pode consumir os endpoints da API sem enviar um "Bearer Token" legítimo gerado na aba API Keys do Painel. +此外,內部服務要求 `REQUIRE_API_KEY=true`,這表示任何代理程式都必須傳送在管理面板 API Keys 標籤頁中產生的有效 "Bearer Token" 才能存取 API 端點。 -Isso nos permite criar duas regras completamente independentes na rede. É aqui que entra o **Cloudflare Tunnel (cloudflared)**. +這使我們能夠在網路中建立兩條完全獨立的規則。這就是 **Cloudflare Tunnel(cloudflared)** 發揮作用的地方。 --- -## 1. Como Criar o Túnel na Cloudflare +## 1. 如何在 Cloudflare 上建立隧道 -O utilitário \`cloudflared\` já está instalado na sua máquina. Siga os passos na nuvem: +`cloudflared` 工具已安裝在您的機器上。請按以下雲端步驟操作: -1. Acesse seu painel **Cloudflare Zero Trust** (One.dash.cloudflare.com). -2. No menu à esquerda, vá em **Networks > Tunnels**. -3. Clique em **Add a Tunnel**, escolha **Cloudflared** e dê o nome \`OmniRoute-VM\`. -4. Ele vai gerar um comando na tela chamado "Install and run a connector". **Você só precisa copiar o Token (a string longa após `--token`)**. -5. Logue via SSH na sua máquina virtual (ou Terminal do Proxmox) e execute: - \`\`\`bash - # Inicia e amarra o túnel permanentemente à sua conta - cloudflared service install SEU_TOKEN_GIGANTE_AQUI - \`\`\` +1. 前往您的 **Cloudflare Zero Trust** 面板(One.dash.cloudflare.com)。 +2. 在左側選單中,前往 **Networks > Tunnels**。 +3. 點選 **Add a Tunnel**,選擇 **Cloudflared**,命名為 `OmniRoute-VM`。 +4. 畫面會產生一個名為 "Install and run a connector" 的指令。**您只需複製 Token(`--token` 後面的長字串)**。 +5. 透過 SSH 登入您的虛擬機器(或 Proxmox 終端機),執行: + ```bash + # 啟動並永久綁定隧道到您的帳戶 + cloudflared service install YOUR_HUGE_TOKEN_HERE + ``` --- -## 2. Configurando o Roteamento (Public Hostnames) +## 2. 設定路由(Public Hostnames) -Ainda na tela do Tunnel recém-criado, vá para a aba **Public Hostnames** e adicione as **duas** rotas, aproveitando a separação que fizemos: +在新建立隧道的介面中,進入 **Public Hostnames** 標籤頁,利用我們做的連接埠分離,新增 **兩條** 路由: -### Rota 1: API Segura (Limitada) +### 路由 1:安全 API(受限) -- **Subdomain:** \`api\` -- **Domain:** \`seuglobal.com.br\` (escolha seu domínio real) -- **Service Type:** \`HTTP\` -- **URL:** \`127.0.0.1:20128\` _(Porta interna da API)_ +- **Subdomain:** `api` +- **Domain:** `yourdomain.com`(選擇您的實際網域) +- **Service Type:** `HTTP` +- **URL:** `127.0.0.1:20128` _(API 內部連接埠)_ -### Rota 2: Painel Zero Trust (Fechado) +### 路由 2:Zero Trust 管理面板(封閉) -- **Subdomain:** \`omniroute\` ou \`painel\` -- **Domain:** \`seuglobal.com.br\` -- **Service Type:** \`HTTP\` -- **URL:** \`127.0.0.1:20129\` _(Porta interna do App/Visual)_ +- **Subdomain:** `omniroute` 或 `panel` +- **Domain:** `yourdomain.com` +- **Service Type:** `HTTP` +- **URL:** `127.0.0.1:20129` _(App/可視化內部連接埠)_ -Neste momento, a conectividade "Física" está resolvida. Agora vamos blindar de verdade. +此時,"實體"連線已經解決。現在我們要真正加固它。 --- -## 3. Blindando o Painel com Zero Trust (Access) +## 3. 使用 Zero Trust(Access)加固管理面板 -Nenhuma senha local protege melhor o seu painel do que remover totalmente o acesso a ele da internet aberta. +比起在本機設定密碼,更好的保護管理面板方式是將它完全從開放網際網路中移除。 -1. No painel Zero Trust, vá em **Access > Applications > Add an application**. -2. Selecione **Self-hosted**. -3. Em **Application name**, coloque \`Painel OmniRoute\`. -4. Em **Application domain**, coloque \`omniroute.seuglobal.com.br\` (O mesmo que você fez na "Rota 2"). -5. Clique em **Next**. -6. Em **Rule action**, escolha \`Allow\`. Em nome da Rule coloque \`Admin Apenas\`. -7. Em **Include**, no seletor de "Selector" escolha \`Emails\` e digite o seu email, por exemplo \`admin@spgeo.com.br\`. -8. Salve (`Add application`). +1. 在 Zero Trust 面板中,前往 **Access > Applications > Add an application**。 +2. 選擇 **Self-hosted**。 +3. 在 **Application name** 中,填入 `OmniRoute Panel`。 +4. 在 **Application domain** 中,填入 `omniroute.yourdomain.com`(與"路由 2"中設定的一致)。 +5. 點選 **Next**。 +6. 在 **Rule action** 中選擇 `Allow`。在 Rule 名稱中填入 `Admin Only`。 +7. 在 **Include** 中,"Selector" 選擇 `Emails`,輸入您的電子郵件,例如 `admin@example.com`。 +8. 儲存(`Add application`)。 -> **O que isso fez:** Se você tentar abrir \`omniroute.seuglobal.com.br\`, não cai mais na sua aplicação OmniRoute! Cai numa tela elegante da Cloudflare pedindo para digitar seu email. Somente se você (ou o email que você botou) for digitado lá, ele recebe no Outlook/Gmail um código de 6 dígitos temporário que libera o túnel até a porta \`20129\`. +> **效果:** 如果您嘗試開啟 `omniroute.yourdomain.com`,將不再直接進入您的 OmniRoute 應用程式!而是跳轉到一個精美的 Cloudflare 頁面,要求輸入電子郵件地址。只有您(或您填寫的電子郵件)輸入後,Outlook/Gmail 會收到一個 6 位數臨時驗證碼,驗證通過後才會解除隧道限制,允許存取 `20129` 連接埠。 --- -## 4. Limitando e Protegendo a API com Rate Limit (WAF) +## 4. 使用速率限制(WAF)限制並保護 API -O Dashboard do Zero Trust não se aplica à rota da API (\`api.seuglobal.com.br\`), porque é um acesso programático via ferramentas automatizadas (agentes) sem navegador. Para ele, usaremos o Firewall principal (WAF) da Cloudflare. +Zero Trust Dashboard 不適用於 API 路由(`api.yourdomain.com`),因為這是透過自動化工具(代理程式)進行的程式化存取,無需瀏覽器。對於這種情況,我們將使用 Cloudflare 的主要防火牆(WAF)。 -1. Acesse o **Painel Normal** da Cloudflare (dash.cloudflare.com) e entre no seu Domínio. -2. No menu esquerdo, vá em **Security > WAF > Rate limiting rules**. -3. Clique em **Create rule**. -4. **Name:** \`Anti-Abuso OmniRoute API\` +1. 前往 Cloudflare **一般面板**(dash.cloudflare.com),進入您的網域。 +2. 在左側選單中,前往 **Security > WAF > Rate limiting rules**。 +3. 點選 **Create rule**。 +4. **Name:** `Anti-Abuse OmniRoute API` 5. **If incoming requests match...** - - Escolha em Field: \`Hostname\` - - Operator: \`equals\` - - Value: \`api.seuglobal.com.br\` -6. Em **With the same characteristics:** Mantenha \`IP\`. -7. Nos limites (Limit): - - **When requests exceed:** \`50\` - - **Period:** \`1 minute\` -8. No final, em **Action**: \`Block\` (Bloquear) e decida se o bloqueio dura por 1 minuto ou 1 hora. -9. **Deploy**. + - Field 選擇:`Hostname` + - Operator:`equals` + - Value:`api.yourdomain.com` +6. **With the same characteristics:** 保持 `IP`。 +7. 限制條件(Limit): + - **When requests exceed:** `50` + - **Period:** `1 minute` +8. 最後,在 **Action** 中選擇 `Block`,並決定封鎖持續 1 分鐘還是 1 小時。 +9. **Deploy**。 -> **O que isso fez:** Ninguém pode mandar mais de 50 requisições num período de 60 segundos na sua URL de API. Como você roda vários agentes e os consumos por trás já batem rate limit e já rastreiam tokens, isso é apenas uma medida na Borda da Internet (Edge Layer) que protege sua Instância On-Premises de cair por estresse térmico antes mesmo do tráfego descer pelo túnel. +> **效果:** 在 60 秒內,任何人都不能向您的 API URL 發送超過 50 次請求。由於您執行著多個代理程式,其背後的消耗已經受到速率限制和 Token 追蹤,這只是網際網路邊緣層(Edge Layer)的一項措施,在流量進入隧道之前就保護您的本機部署執行個體免受壓力超載。 --- -## Finalização +## 完成 -1. A sua VM **não possui nenhuma porta exposta** em `/etc/ufw`. -2. O OmniRoute só conversa HTTPS saindo (\`cloudflared\`) e não recebendo TCP direto do mundo. -3. Seus requets pro OpenAI são ofuscados porque configuramos eles globalmente pra passar em um Proxy SOCKS5 (A nuvem não liga pro SOCKS5 porque ela vem Inbound). -4. Seu painel web tem 2-Factor com Email. -5. Sua API está ratelimitada na borda pela Cloudflare e só trafega Bearer Tokens. +1. 您的虛擬機器 **沒有任何連接埠暴露** 在 `/etc/ufw` 中。 +2. OmniRoute 僅透過 `cloudflared` 進行 HTTPS 對外通訊,不直接接收來自外部的 TCP 連線。 +3. 您的 OpenAI 請求已混淆處理,因為我們已全域設定透過 SOCKS5 代理傳送(雲端不關心 SOCKS5,因為流量是入站的)。 +4. 您的 Web 管理面板具有電子郵件兩步驟驗證。 +5. 您的 API 在邊緣層受 Cloudflare 速率限制,且僅傳輸 Bearer Token。 diff --git a/docs/i18n/zh-TW/docs/features/context-relay.md b/docs/i18n/zh-TW/docs/features/context-relay.md index 60945b2dac..027656ef24 100644 --- a/docs/i18n/zh-TW/docs/features/context-relay.md +++ b/docs/i18n/zh-TW/docs/features/context-relay.md @@ -1,65 +1,57 @@ -# Context Relay (中文(簡體)) +# Context Relay(繁體中文) -🌐 **Languages:** 🇺🇸 [English](../../../../../docs/features/context-relay.md) · 🇪🇸 [es](../../../es/docs/features/context-relay.md) · 🇫🇷 [fr](../../../fr/docs/features/context-relay.md) · 🇩🇪 [de](../../../de/docs/features/context-relay.md) · 🇮🇹 [it](../../../it/docs/features/context-relay.md) · 🇷🇺 [ru](../../../ru/docs/features/context-relay.md) · 🇨🇳 [zh-CN](../../../zh-CN/docs/features/context-relay.md) · 🇯🇵 [ja](../../../ja/docs/features/context-relay.md) · 🇰🇷 [ko](../../../ko/docs/features/context-relay.md) · 🇸🇦 [ar](../../../ar/docs/features/context-relay.md) · 🇮🇳 [hi](../../../hi/docs/features/context-relay.md) · 🇮🇳 [in](../../../in/docs/features/context-relay.md) · 🇹🇭 [th](../../../th/docs/features/context-relay.md) · 🇻🇳 [vi](../../../vi/docs/features/context-relay.md) · 🇮🇩 [id](../../../id/docs/features/context-relay.md) · 🇲🇾 [ms](../../../ms/docs/features/context-relay.md) · 🇳🇱 [nl](../../../nl/docs/features/context-relay.md) · 🇵🇱 [pl](../../../pl/docs/features/context-relay.md) · 🇸🇪 [sv](../../../sv/docs/features/context-relay.md) · 🇳🇴 [no](../../../no/docs/features/context-relay.md) · 🇩🇰 [da](../../../da/docs/features/context-relay.md) · 🇫🇮 [fi](../../../fi/docs/features/context-relay.md) · 🇵🇹 [pt](../../../pt/docs/features/context-relay.md) · 🇷🇴 [ro](../../../ro/docs/features/context-relay.md) · 🇭🇺 [hu](../../../hu/docs/features/context-relay.md) · 🇧🇬 [bg](../../../bg/docs/features/context-relay.md) · 🇸🇰 [sk](../../../sk/docs/features/context-relay.md) · 🇺🇦 [uk-UA](../../../uk-UA/docs/features/context-relay.md) · 🇮🇱 [he](../../../he/docs/features/context-relay.md) · 🇵🇭 [phi](../../../phi/docs/features/context-relay.md) · 🇧🇷 [pt-BR](../../../pt-BR/docs/features/context-relay.md) · 🇨🇿 [cs](../../../cs/docs/features/context-relay.md) · 🇹🇷 [tr](../../../tr/docs/features/context-relay.md) +🌐 **語言:** 🇺🇸 [English](../../../../../docs/features/context-relay.md) · 🇪🇸 [es](../../../es/docs/features/context-relay.md) · 🇫🇷 [fr](../../../fr/docs/features/context-relay.md) · 🇩🇪 [de](../../../de/docs/features/context-relay.md) · 🇮🇹 [it](../../../it/docs/features/context-relay.md) · 🇷🇺 [ru](../../../ru/docs/features/context-relay.md) · 🇨🇳 [zh-CN](../../../zh-CN/docs/features/context-relay.md) · 🇯🇵 [ja](../../../ja/docs/features/context-relay.md) · 🇰🇷 [ko](../../../ko/docs/features/context-relay.md) · 🇸🇦 [ar](../../../ar/docs/features/context-relay.md) · 🇮🇳 [hi](../../../hi/docs/features/context-relay.md) · 🇮🇳 [in](../../../in/docs/features/context-relay.md) · 🇹🇭 [th](../../../th/docs/features/context-relay.md) · 🇻🇳 [vi](../../../vi/docs/features/context-relay.md) · 🇮🇩 [id](../../../id/docs/features/context-relay.md) · 🇲🇾 [ms](../../../ms/docs/features/context-relay.md) · 🇳🇱 [nl](../../../nl/docs/features/context-relay.md) · 🇵🇱 [pl](../../../pl/docs/features/context-relay.md) · 🇸🇪 [sv](../../../sv/docs/features/context-relay.md) · 🇳🇴 [no](../../../no/docs/features/context-relay.md) · 🇩🇰 [da](../../../da/docs/features/context-relay.md) · 🇫🇮 [fi](../../../fi/docs/features/context-relay.md) · 🇵🇹 [pt](../../../pt/docs/features/context-relay.md) · 🇷🇴 [ro](../../../ro/docs/features/context-relay.md) · 🇭🇺 [hu](../../../hu/docs/features/context-relay.md) · 🇧🇬 [bg](../../../bg/docs/features/context-relay.md) · 🇸🇰 [sk](../../../sk/docs/features/context-relay.md) · 🇺🇦 [uk-UA](../../../uk-UA/docs/features/context-relay.md) · 🇮🇱 [he](../../../he/docs/features/context-relay.md) · 🇵🇭 [phi](../../../phi/docs/features/context-relay.md) · 🇧🇷 [pt-BR](../../../pt-BR/docs/features/context-relay.md) · 🇨🇿 [cs](../../../cs/docs/features/context-relay.md) · 🇹🇷 [tr](../../../tr/docs/features/context-relay.md) --- -`context-relay` is a combo strategy that keeps session continuity when the active account -rotates before the conversation is finished. +`context-relay` 是一種 combo 策略,可在活躍帳戶於對話完成前輪換時,保持工作階段的連續性。 -The current runtime behaves like priority routing for model selection, then adds a -handoff layer on top: +目前的執行時期行為類似於模型選擇的優先路由,然後在上方加入一層交接層: -- before the active account is exhausted, OmniRoute generates a compact structured summary -- after authentication selects a different account for the same session, OmniRoute injects - that summary as a system message into the next request -- once the handoff is consumed successfully, it is removed from storage +- 在活躍帳戶耗盡之前,OmniRoute 會產生一個精簡的結構化摘要 +- 在認證為同一個工作階段選取不同帳戶後,OmniRoute 會將該摘要作為系統訊息注入到下一個請求中 +- 交接成功消耗後,會從儲存中移除 -## When To Use It +## 何時使用 -Use `context-relay` when all of the following are true: +當以下所有條件成立時,請使用 `context-relay`: -- the combo is expected to rotate between multiple accounts of the same provider -- losing short-term conversational continuity would hurt task quality -- the provider exposes enough quota information to predict an approaching account limit +- combo 預計會在同一個提供商的多個帳戶之間輪換 +- 失去短期對話連續性會影響任務品質 +- 提供商暴露了足夠的配額資訊,可以預測即將到來的帳戶限制 -This is most useful for long-running coding or research sessions that may outlive a single -account window. +這對於可能超過單一帳戶視窗的長時間編碼或研究會話最為有用。 -## Runtime Flow +## 執行時期流程 -The current behavior is intentionally split across two runtime layers. +目前的行為有意分散在兩個執行時期層中。 -### 0% to 84% quota used +### 已使用 0% 至 84% 的配額 -No handoff is generated. Requests behave like normal priority routing. +不會產生交接。請求的行為就像一般的優先路由。 -### 85% to 94% quota used +### 已使用 85% 至 94% 的配額 -If the active provider is enabled in `handoffProviders`, OmniRoute generates a structured -handoff summary in the background before the account is fully exhausted. +如果活躍提供商在 `handoffProviders` 中啟用,OmniRoute 會在帳戶完全耗盡之前在背景產生結構化的交接摘要。 -Important details: +重要細節: -- the default warning threshold is `0.85` -- the hard stop for generation is `0.95` -- only one in-flight handoff generation is allowed per `sessionId + comboName` -- if an active handoff already exists for that session/combo, no duplicate summary is generated +- 預設警告閾值為 `0.85` +- 產生作業的硬性停止點為 `0.95` +- 每個 `sessionId + comboName` 只允許一個進行中的交接產生作業 +- 如果該工作階段/combo 已有活躍的交接,則不會產生重複的摘要 -### 95% or more quota used +### 已使用 95% 或更多的配額 -No new handoff is generated. At this point the system is already in or near exhaustion and -the runtime avoids scheduling another summary request. +不會產生新的交接。此時系統已處於或接近耗盡狀態,執行時期會避免排程另一個摘要請求。 -### After account rotation +### 帳戶輪換後 -When the next request for the same session resolves to a different authenticated account, -OmniRoute prepends the stored handoff as a system message. Injection happens only after the -real account switch is known. +當同一個工作階段的下一個請求解析到不同的已認證帳戶時,OmniRoute 會將儲存的交接作為系統訊息預先加入。只有在實際帳戶切換已知後才會進行注入。 -## Handoff Payload +## 交接酬載 -The persisted handoff payload is stored in `context_handoffs` and includes: +持久化的交接酬載儲存在 `context_handoffs` 中,包括: - `sessionId` - `comboName` @@ -74,57 +66,51 @@ The persisted handoff payload is stored in `context_handoffs` and includes: - `generatedAt` - `expiresAt` -The summary model is instructed to return a JSON object with this structure: +摘要模型被指示回傳具有此結構的 JSON 物件: ```json { - "summary": "Dense summary of what matters for continuity", - "keyDecisions": ["Decision 1", "Decision 2"], - "taskProgress": "What is done, what is pending, and the next step", - "activeEntities": ["fileA.ts", "feature X", "provider Y"] + "summary": "關於哪些內容對連續性重要的精簡摘要", + "keyDecisions": ["決策 1", "決策 2"], + "taskProgress": "已完成的事項、待辦事項以及下一步", + "activeEntities": ["fileA.ts", "功能 X", "提供商 Y"] } ``` -At injection time, OmniRoute converts that payload into a `` system -message so the next account can continue with the correct local context. +在注入時,OmniRoute 會將該酬載轉換為 `` 系統訊息,以便下一個帳戶能以正確的本地上下文繼續。 ## 設定 -`context-relay` supports these config fields: +`context-relay` 支援以下配置欄位: -- `handoffThreshold`: warning threshold for summary generation, default `0.85` -- `handoffModel`: optional model override used only for summary generation -- `handoffProviders`: allowlist of providers allowed to trigger handoff generation +- `handoffThreshold`:摘要產生的警告閾值,預設 `0.85` +- `handoffModel`:可選的模型覆寫,僅用於摘要產生 +- `handoffProviders`:允許觸發交接產生的提供商允許清單 -Global defaults can be configured in Settings, and combo-specific values can override them -in the Combos page. +全域預設值可在設定中配置,combo 專用值可在 Combos 頁面中覆寫。 -## Architectural Note +## 架構說明 -The current implementation does not use a standalone `handleContextRelayCombo` handler. +目前的實作未使用獨立的 `handleContextRelayCombo` 處理器。 -Instead: +而是: -- `open-sse/services/combo.ts` decides whether a successful turn should generate a handoff -- `src/sse/handlers/chat.ts` injects the handoff only after authentication resolves the - actual account used for the request +- `open-sse/services/combo.ts` 決定成功的回合是否應產生交接 +- `src/sse/handlers/chat.ts` 僅在認證解析出請求使用的實際帳戶後才注入交接 -This split is intentional in the current codebase because the combo loop alone does not know -whether the request stayed on the same account or actually switched accounts. +這種分離在目前的程式碼庫中是有意的,因為 combo 迴圈本身無法知道請求是停留在同一個帳戶還是實際切換了帳戶。 -## Limitations +## 限制 -- Effective runtime support is currently centered on `codex` quota rotation. -- `handoffProviders` is already modeled as a config surface, but real handoff generation - still depends on provider-specific quota plumbing. -- The summary is intentionally compact and recent-history based; it is not a full transcript - replay mechanism. -- Handoffs are scoped by `sessionId + comboName` and expire automatically. -- If the session does not switch accounts, the stored handoff is not injected. +- 目前的執行時期支援主要集中在 `codex` 配額輪換上 +- `handoffProviders` 已建模為配置表面,但實際的交接產生仍依賴於提供商特定的配額管線 +- 摘要刻意保持精簡並基於近期歷史;它不是完整的對話記錄重播機制 +- 交接以 `sessionId + comboName` 為範圍,並會自動過期 +- 如果工作階段未切換帳戶,則不會注入儲存的交接 -## Recommended Usage Pattern +## 建議使用模式 -- use multiple accounts from the same provider -- keep stable `sessionId` values across the session -- set `handoffThreshold` early enough to leave room for the background summary request -- treat the feature as continuity assistance, not as a replacement for persistent memory +- 使用同一個提供商的多個帳戶 +- 在整個工作階段中保持穩定的 `sessionId` 值 +- 儘早設定 `handoffThreshold`,為背景摘要請求預留空間 +- 將此功能視為連續性輔助,而非持久化記憶體的替代方案 diff --git a/docs/i18n/zh-TW/docs/frameworks/A2A-SERVER.md b/docs/i18n/zh-TW/docs/frameworks/A2A-SERVER.md index 61663e8d15..8ca44d0c1b 100644 --- a/docs/i18n/zh-TW/docs/frameworks/A2A-SERVER.md +++ b/docs/i18n/zh-TW/docs/frameworks/A2A-SERVER.md @@ -1,43 +1,60 @@ -# OmniRoute A2A Server Documentation (中文 (簡體)) - -🌐 **Languages:** 🇺🇸 [English](../../../../docs/A2A-SERVER.md) · 🇸🇦 [ar](../../ar/docs/A2A-SERVER.md) · 🇧🇬 [bg](../../bg/docs/A2A-SERVER.md) · 🇧🇩 [bn](../../bn/docs/A2A-SERVER.md) · 🇨🇿 [cs](../../cs/docs/A2A-SERVER.md) · 🇩🇰 [da](../../da/docs/A2A-SERVER.md) · 🇩🇪 [de](../../de/docs/A2A-SERVER.md) · 🇪🇸 [es](../../es/docs/A2A-SERVER.md) · 🇮🇷 [fa](../../fa/docs/A2A-SERVER.md) · 🇫🇮 [fi](../../fi/docs/A2A-SERVER.md) · 🇫🇷 [fr](../../fr/docs/A2A-SERVER.md) · 🇮🇳 [gu](../../gu/docs/A2A-SERVER.md) · 🇮🇱 [he](../../he/docs/A2A-SERVER.md) · 🇮🇳 [hi](../../hi/docs/A2A-SERVER.md) · 🇭🇺 [hu](../../hu/docs/A2A-SERVER.md) · 🇮🇩 [id](../../id/docs/A2A-SERVER.md) · 🇮🇹 [it](../../it/docs/A2A-SERVER.md) · 🇯🇵 [ja](../../ja/docs/A2A-SERVER.md) · 🇰🇷 [ko](../../ko/docs/A2A-SERVER.md) · 🇮🇳 [mr](../../mr/docs/A2A-SERVER.md) · 🇲🇾 [ms](../../ms/docs/A2A-SERVER.md) · 🇳🇱 [nl](../../nl/docs/A2A-SERVER.md) · 🇳🇴 [no](../../no/docs/A2A-SERVER.md) · 🇵🇭 [phi](../../phi/docs/A2A-SERVER.md) · 🇵🇱 [pl](../../pl/docs/A2A-SERVER.md) · 🇵🇹 [pt](../../pt/docs/A2A-SERVER.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/A2A-SERVER.md) · 🇷🇴 [ro](../../ro/docs/A2A-SERVER.md) · 🇷🇺 [ru](../../ru/docs/A2A-SERVER.md) · 🇸🇰 [sk](../../sk/docs/A2A-SERVER.md) · 🇸🇪 [sv](../../sv/docs/A2A-SERVER.md) · 🇰🇪 [sw](../../sw/docs/A2A-SERVER.md) · 🇮🇳 [ta](../../ta/docs/A2A-SERVER.md) · 🇮🇳 [te](../../te/docs/A2A-SERVER.md) · 🇹🇭 [th](../../th/docs/A2A-SERVER.md) · 🇹🇷 [tr](../../tr/docs/A2A-SERVER.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/A2A-SERVER.md) · 🇵🇰 [ur](../../ur/docs/A2A-SERVER.md) · 🇻🇳 [vi](../../vi/docs/A2A-SERVER.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/A2A-SERVER.md) - +--- +title: "OmniRoute A2A 伺服器文件" +version: 3.8.40 +lastUpdated: 2026-06-28 --- -> Agent-to-Agent Protocol v0.3 — OmniRoute as an intelligent routing agent +# OmniRoute A2A 伺服器文件 -## Agent Discovery +> Agent-to-Agent 協定 v0.3 — OmniRoute 作為智慧路由代理 + +A2A 介面包含兩個面向: + +- **JSON-RPC 2.0** 位於 `POST /a2a`(標準進入點,定義於 `src/app/a2a/route.ts`)。 +- **REST** 位於 `/api/a2a/*`,供儀表板和工具使用(狀態、任務清單、取消)。 + +任務由 `A2ATaskManager`(`src/lib/a2a/taskManager.ts`,預設 5 分鐘 TTL)追蹤。技能透過 `A2A_SKILL_HANDLERS`(位於 `src/lib/a2a/taskExecution.ts`)調度。 + +## 代理探索 ```bash curl http://localhost:20128/.well-known/agent.json ``` -Returns the Agent Card describing OmniRoute's capabilities, skills, and authentication requirements. +回傳描述 OmniRoute 能力、技能及驗證需求的 Agent Card。 + +Agent Card 中的 `version` 欄位源自 `process.env.npm_package_version`(參見 `src/app/.well-known/agent.json/route.ts:13`),因此每次發版都會與 `package.json` 自動同步。 --- -## Authentication +## 驗證 -All `/a2a` requests require an API key via the `Authorization` header: +所有 `/a2a` 請求都需要透過 `Authorization` 標頭提供 API 金鑰: ``` -Authorization: Bearer YOUR_OMNIROUTE_API_KEY +Authorization: Bearer YOUR_O..._KEY ``` -If no API key is configured on the server, authentication is bypassed. +若伺服器未設定 API 金鑰,則跳過驗證。 + +## 啟用 + +A2A 由 **Endpoints → A2A** 開關控制,預設為停用。停用時, +`GET /api/a2a/status` 回報 `status: "disabled"` 及 `online: false`;對 +`POST /a2a` 的 JSON-RPC 呼叫則回傳 HTTP 503 及 JSON-RPC 錯誤碼 `-32000`。 --- -## JSON-RPC 2.0 Methods +## JSON-RPC 2.0 方法 -### `message/send` — Synchronous Execution +### `message/send` — 同步執行 -Sends a message to a skill and waits for the complete response. +發送訊息給技能並等待完整回應。 ```bash curl -X POST http://localhost:20128/a2a \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_KEY" \ + -H "Authorization: Bearer ***" \ -d '{ "jsonrpc": "2.0", "id": "1", @@ -50,7 +67,7 @@ curl -X POST http://localhost:20128/a2a \ }' ``` -**Response:** +**回應:** ```json { @@ -71,14 +88,14 @@ curl -X POST http://localhost:20128/a2a \ } ``` -### `message/stream` — SSE Streaming +### `message/stream` — SSE 串流 -Same as `message/send` but returns Server-Sent Events for real-time streaming. +與 `message/send` 相同,但回傳 Server-Sent Events 以實現即時串流。 ```bash curl -N -X POST http://localhost:20128/a2a \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_KEY" \ + -H "Authorization: Bearer ***" \ -d '{ "jsonrpc": "2.0", "id": "1", @@ -90,7 +107,7 @@ curl -N -X POST http://localhost:20128/a2a \ }' ``` -**SSE Events:** +**SSE 事件:** ``` data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"working"},"chunk":{"type":"text","content":"..."}}} @@ -100,36 +117,113 @@ data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","s data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"completed"},"metadata":{...}}} ``` -### `tasks/get` — Query Task Status +### `tasks/get` — 查詢任務狀態 ```bash curl -X POST http://localhost:20128/a2a \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_KEY" \ + -H "Authorization: Bearer ***" \ -d '{"jsonrpc":"2.0","id":"2","method":"tasks/get","params":{"taskId":"TASK_UUID"}}' ``` -### `tasks/cancel` — Cancel a Task +### `tasks/cancel` — 取消任務 ```bash curl -X POST http://localhost:20128/a2a \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_KEY" \ + -H "Authorization: Bearer ***" \ -d '{"jsonrpc":"2.0","id":"3","method":"tasks/cancel","params":{"taskId":"TASK_UUID"}}' ``` --- -## Available Skills +## 可用技能 -| Skill | Description | -| :----------------- | :------------------------------------------------------------------------------------------------------------------------------ | -| `smart-routing` | Routes prompts through OmniRoute's intelligent pipeline. Returns response with routing explanation, cost, and resilience trace. | -| `quota-management` | Answers natural-language queries about provider quotas, suggests free combos, and provides quota rankings. | +OmniRoute 提供 6 個 A2A 技能,配置於 `src/lib/a2a/taskExecution.ts::A2A_SKILL_HANDLERS`。每個技能模組位於 `src/lib/a2a/skills/`。 + +| 技能 | ID | 描述 | 標籤 | 範例 | +| :----------------- | :-------------------- | :--------------------------------------------------------------------------------- | :-------------------------- | :------------------------------------- | +| Smart Routing | `smart-routing` | 透過 OmniRoute 的 combo 引擎 + 評分,將提示路由至最佳提供者/組合 | routing, providers | "Route this prompt via the best model" | +| Quota Management | `quota-management` | 回報各提供者的配額狀態,協助呼叫端決定何時限流或切換 | quota, providers | "Check quota for anthropic" | +| Provider Discovery | `provider-discovery` | 列出已安裝的提供者及其能力、免費方案標記、OAuth 狀態 | providers, discovery | "What providers are available?" | +| Cost Analysis | `cost-analysis` | 根據目錄及近期使用量,估算請求/對話的成本 | cost, usage | "Estimate cost for this conversation" | +| Health Report | `health-report` | 彙總各提供者的斷路器、冷卻、鎖定狀態 | health, resilience | "Show health status of all providers" | +| List Capabilities | `list-capabilities` | 回傳完整的 42 項代理技能目錄,以 Markdown 表格呈現,附原始 SKILL.md 網址供注入上下文 | catalog, discovery, skills | "List all OmniRoute capabilities" | + +> 注意:Agent Card 描述目前標示「36+ 個提供者」(`src/app/.well-known/agent.json/route.ts:26` 及 `:55`)。實際目錄已成長至 180+ 個提供者——該字串應在後續變更中更新(另開文件/程式碼待辦事項追蹤;此處不修改)。 + +### `list-capabilities` 技能詳情 + +`list-capabilities` 技能對於需要在發送 API 呼叫前探索 OmniRoute 能力的外部代理尤其有用。它回傳結構化的 Markdown 表格產出: + +``` +| ID | Name | Category | Area | Endpoints/Commands | Raw URL | +| --- | --- | --- | --- | --- | --- | +| omni-auth | Auth & Sessions | api | auth | POST /api/auth/login, ... | https://raw.githubusercontent.com/... | +... +``` + +每一列都包含 `rawUrl` 欄位,讓代理可直接擷取完整的 SKILL.md。`metadata.totalSkills` 欄位固定為 `42`。實作位於:`src/lib/a2a/skills/listCapabilities.ts`。另請參閱 [AGENT-SKILLS.md](./AGENT-SKILLS.md)。 --- -## Task Lifecycle +## REST API(輔助) + +JSON-RPC 端點 `/a2a` 是標準的 A2A 進入點。以下 REST 端點為儀表板和外部工具提供輔助存取: + +| 端點 | 方法 | 描述 | 驗證方式 | +| :---------------------------- | :----- | :------------------------ | :--------------------- | +| `/api/a2a/status` | GET | 伺服器狀態、已註冊技能 | (公開) | +| `/api/a2a/tasks` | GET | 列出任務(可篩選) | management | +| `/api/a2a/tasks/[id]` | GET | 依 ID 取得任務 | management | +| `/api/a2a/tasks/[id]/cancel` | POST | 取消執行中的任務 | management | +| `/.well-known/agent.json` | GET | Agent Card(A2A 探索) | (公開,快取 3600 秒) | + +--- + +## 新增技能 + +1. **建立技能檔案:** `src/lib/a2a/skills/.ts` + + 匯出一個非同步函式 `(task: A2ATask) => Promise<{ artifacts, metadata }>`。請遵循現有技能(如 `smartRouting.ts`)的結構。 + +2. **註冊處理器:** 在 `src/lib/a2a/taskExecution.ts` 中,將項目加入 `A2A_SKILL_HANDLERS`: + + ```typescript + export const A2A_SKILL_HANDLERS = { + // ...existing skills + "your-skill": async (task) => { + const skillModule = await import("./skills/yourSkill"); + return skillModule.executeYourSkill(task); + }, + }; + ``` + +3. **在 Agent Card 中揭露:** 在 `src/app/.well-known/agent.json/route.ts` 中,附加至 `skills` 陣列: + + ```json + { + "id": "your-skill", + "name": "Your Skill", + "description": "簡短、聚焦意圖的描述", + "tags": ["routing", "quota"], + "examples": ["Sample natural-language invocation"] + } + ``` + +4. **撰寫測試:** `tests/unit/a2a-.test.ts`。涵蓋正常路徑及錯誤路徑。 + +5. **撰寫文件:** 將新技能加入本文件的「可用技能」表格。 + +--- + +## 任務 TTL + +任務在 `ttlMinutes`(預設 5 分鐘)後過期——此參數配置於 `src/lib/a2a/taskManager.ts:82` 的 `A2ATaskManager` 建構子中。若要自訂,請複製 `A2ATaskManager` 實體化並傳入不同的值(例如 `new A2ATaskManager(15)` 代表 15 分鐘 TTL)。背景排程器每 60 秒清除一次過期任務。 + +--- + +## 任務生命週期 ``` submitted → working → completed @@ -137,27 +231,28 @@ submitted → working → completed → cancelled ``` -- Tasks expire after 5 minutes (configurable) -- Terminal states: `completed`, `failed`, `cancelled` -- Event log tracks every state transition +- 任務預設在 5 分鐘後過期(參見[任務 TTL](#任務-ttl)) +- 終止狀態:`completed`、`failed`、`cancelled` +- 事件日誌追蹤每一次狀態轉換 --- -## Error Codes +## 錯誤碼 -| Code | Meaning | -| :----- | :----------------------------- | -| -32700 | Parse error (invalid JSON) | -| -32600 | Invalid request / Unauthorized | -| -32601 | Method or skill not found | -| -32602 | Invalid params | -| -32603 | Internal error | +| 代碼 | 含義 | +| :----- | :-------------------------- | +| -32700 | 解析錯誤(JSON 格式無效) | +| -32600 | 無效請求 / 未授權 | +| -32601 | 方法或技能不存在 | +| -32602 | 參數無效 | +| -32603 | 內部錯誤 | +| -32000 | A2A 端點已停用 | --- -## Integration Examples +## 整合範例 -### Python (requests) +### Python(requests) ```python import requests @@ -176,7 +271,7 @@ print(result["artifacts"][0]["content"]) print(result["metadata"]["routing_explanation"]) ``` -### TypeScript (fetch) +### TypeScript(fetch) ```typescript const resp = await fetch("http://localhost:20128/a2a", { diff --git a/docs/i18n/zh-TW/docs/frameworks/MCP-SERVER.md b/docs/i18n/zh-TW/docs/frameworks/MCP-SERVER.md index 4ac7921d2e..933de9023a 100644 --- a/docs/i18n/zh-TW/docs/frameworks/MCP-SERVER.md +++ b/docs/i18n/zh-TW/docs/frameworks/MCP-SERVER.md @@ -1,87 +1,409 @@ -# OmniRoute MCP Server Documentation (中文 (簡體)) - -🌐 **Languages:** 🇺🇸 [English](../../../../docs/MCP-SERVER.md) · 🇸🇦 [ar](../../ar/docs/MCP-SERVER.md) · 🇧🇬 [bg](../../bg/docs/MCP-SERVER.md) · 🇧🇩 [bn](../../bn/docs/MCP-SERVER.md) · 🇨🇿 [cs](../../cs/docs/MCP-SERVER.md) · 🇩🇰 [da](../../da/docs/MCP-SERVER.md) · 🇩🇪 [de](../../de/docs/MCP-SERVER.md) · 🇪🇸 [es](../../es/docs/MCP-SERVER.md) · 🇮🇷 [fa](../../fa/docs/MCP-SERVER.md) · 🇫🇮 [fi](../../fi/docs/MCP-SERVER.md) · 🇫🇷 [fr](../../fr/docs/MCP-SERVER.md) · 🇮🇳 [gu](../../gu/docs/MCP-SERVER.md) · 🇮🇱 [he](../../he/docs/MCP-SERVER.md) · 🇮🇳 [hi](../../hi/docs/MCP-SERVER.md) · 🇭🇺 [hu](../../hu/docs/MCP-SERVER.md) · 🇮🇩 [id](../../id/docs/MCP-SERVER.md) · 🇮🇹 [it](../../it/docs/MCP-SERVER.md) · 🇯🇵 [ja](../../ja/docs/MCP-SERVER.md) · 🇰🇷 [ko](../../ko/docs/MCP-SERVER.md) · 🇮🇳 [mr](../../mr/docs/MCP-SERVER.md) · 🇲🇾 [ms](../../ms/docs/MCP-SERVER.md) · 🇳🇱 [nl](../../nl/docs/MCP-SERVER.md) · 🇳🇴 [no](../../no/docs/MCP-SERVER.md) · 🇵🇭 [phi](../../phi/docs/MCP-SERVER.md) · 🇵🇱 [pl](../../pl/docs/MCP-SERVER.md) · 🇵🇹 [pt](../../pt/docs/MCP-SERVER.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/MCP-SERVER.md) · 🇷🇴 [ro](../../ro/docs/MCP-SERVER.md) · 🇷🇺 [ru](../../ru/docs/MCP-SERVER.md) · 🇸🇰 [sk](../../sk/docs/MCP-SERVER.md) · 🇸🇪 [sv](../../sv/docs/MCP-SERVER.md) · 🇰🇪 [sw](../../sw/docs/MCP-SERVER.md) · 🇮🇳 [ta](../../ta/docs/MCP-SERVER.md) · 🇮🇳 [te](../../te/docs/MCP-SERVER.md) · 🇹🇭 [th](../../th/docs/MCP-SERVER.md) · 🇹🇷 [tr](../../tr/docs/MCP-SERVER.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/MCP-SERVER.md) · 🇵🇰 [ur](../../ur/docs/MCP-SERVER.md) · 🇻🇳 [vi](../../vi/docs/MCP-SERVER.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/MCP-SERVER.md) - +--- +title: "OmniRoute MCP Server 文件" +version: 3.8.40 +lastUpdated: 2026-06-28 --- -> Model Context Protocol server with 16 intelligent tools +# OmniRoute MCP Server 文件 + +> 模型上下文協定(Model Context Protocol)伺服器,提供 104 個工具,涵蓋路由、快取、壓縮、記憶、技能、代理、池與上下文來源操作。 +> +> 真相來源:`open-sse/mcp-server/server.ts` 透過 `countUniqueMcpTools()` 計算出 **104 個唯一工具**:42 個標準定義(包括六個 CCR 生命週期工具與 agent-skills 三件組),加上記憶體(3 個)、技能(4 個)、GitHub 技能(3 個)、池(6 個)、遊戲化(8 個)、外掛(8 個)、Notion(6 個)、Obsidian(22 個)與兩個僅限 RTK 的壓縮工具。 ## 安裝 -OmniRoute MCP is built-in. Start it with: +OmniRoute MCP 為內建功能。透過以下指令啟動: ```bash omniroute --mcp ``` -Or via the open-sse transport: +或透過 open-sse 傳輸層: ```bash -# HTTP streamable transport (port 20130) -omniroute --dev # MCP auto-starts on /mcp endpoint +# HTTP 可串流傳輸(連接埠 20130) +omniroute --dev # MCP 會自動在 /mcp 端點啟動 ``` -## IDE Configuration +## 傳輸層 -See [IDE Configs](integrations/ide-configs.md) for Antigravity, Cursor, Copilot, and Claude Desktop setup. +MCP 伺服器提供三種傳輸層,皆由同一個 `createMcpServer()` 工廠函式驅動: + +| 傳輸層 | 位置 | 使用時機 | +| :----------------- | :----------------------------------------- | :------------------------------------------------ | +| `stdio` | `open-sse/mcp-server/server.ts` | IDE 整合(Claude Desktop、Cursor 等) | +| `sse` | `POST/GET /api/mcp/sse` 經由 `httpTransport` | 需要事件串流的瀏覽器/代理客戶端 | +| `streamable-http` | `POST/GET/DELETE /api/mcp/stream` | 多工作階段 HTTP 客戶端(`mcp-session-id` 標頭) | + +作用中的 HTTP 傳輸層(`sse` 或 `streamable-http`)由 `mcpTransport` 設定值選取。切換傳輸層會關閉另一傳輸層上的現有工作階段。 + +### 遠端存取(manage 範圍繞過) + +`/api/mcp/*` 屬於 LOCAL_ONLY 層級(`src/server/authz/routeGuard.ts`)— 預設僅允許回環主機(`localhost`、`127.0.0.1`、`::1`)存取。自 v3.8.2 起,非回環客戶端若提供攜帶 `manage` 範圍的 `Authorization: Bearer `,則可連線。這是透過隧道、反向代理或公開主機名稱到達遠端 MCP 伺服器的唯一方式。 + +```bash +# 授予 manage 範圍:開啟儀表板 API Keys 頁面,在該金鑰上切換 +# 「管理存取」(Management Access),或在建立時 POST scopes:["manage"] + +# 然後從遠端 MCP 客戶端連線: +curl -i \ + -H "Host: your-public-host.example" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"my-client","version":"0"}}}' \ + https://your-public-host.example/api/mcp/stream +``` + +沒有 manage 範圍的金鑰(或未提供 Bearer)會回傳 `403 LOCAL_ONLY`。兄弟前綴 `/api/cli-tools/runtime/*` 故意**不可繞過** — 請參閱[路由守衛層級 — Manage 範圍例外](../security/ROUTE_GUARD_TIERS.md#manage-scope-carve-out)。 + +## IDE 設定 + +請參閱 [MCP 客戶端設定](../guides/SETUP_GUIDE.md#mcp-client-configuration) 了解 Claude Desktop、Cursor、Cline 及相容 MCP 客戶端的設定方式。 --- -## Essential Tools (8) +## 基礎工具(8 個)— 第一階段 -| Tool | Description | -| :------------------------------ | :--------------------------------------- | -| `omniroute_get_health` | Gateway health, circuit breakers, uptime | -| `omniroute_list_combos` | All configured combos with models | -| `omniroute_get_combo_metrics` | Performance metrics for a specific combo | -| `omniroute_switch_combo` | Switch active combo by ID/name | -| `omniroute_check_quota` | Quota status per provider or all | -| `omniroute_route_request` | Send a chat completion through OmniRoute | -| `omniroute_cost_report` | Cost analytics for a time period | -| `omniroute_list_models_catalog` | Full model catalog with capabilities | +| 工具 | 範圍 | 說明 | +| :-------------------------------- | :--------------------- | :---------------------------------------------------------------- | +| `omniroute_get_health` | `read:health` | 運作時間、記憶體、斷路器、速率限制、快取統計 | +| `omniroute_list_combos` | `read:combos` | 所有已設定的組合及策略(可選指標) | +| `omniroute_get_combo_metrics` | `read:combos` | 特定組合的效能指標 | +| `omniroute_switch_combo` | `write:combos` | 啟用或停用組合 | +| `omniroute_check_quota` | `read:quota` | 已用配額/總配額、剩餘百分比、重置時間、代幣健康狀態 | +| `omniroute_route_request` | `execute:completions` | 透過 OmniRoute 路由發送聊天完成請求 | +| `omniroute_cost_report` | `read:usage` | 按期間(工作階段/日/週/月)的成本報告 | +| `omniroute_list_models_catalog` | `read:models` | 完整模型目錄,包含功能、狀態、定價 | -## Advanced Tools (8) +## 第一階段 — 搜尋 -| Tool | Description | -| :--------------------------------- | :---------------------------------------------------------- | -| `omniroute_simulate_route` | Dry-run routing simulation with fallback tree | -| `omniroute_set_budget_guard` | Session budget with degrade/block/alert actions | -| `omniroute_set_resilience_profile` | Apply conservative/balanced/aggressive preset | -| `omniroute_test_combo` | Live-test all models in a combo via a real upstream request | -| `omniroute_get_provider_metrics` | Detailed metrics for one provider | -| `omniroute_best_combo_for_task` | Task-fitness recommendation with alternatives | -| `omniroute_explain_route` | Explain a past routing decision | -| `omniroute_get_session_snapshot` | Full session state: costs, tokens, errors | +| 工具 | 範圍 | 說明 | +| :----------------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------- | +| `omniroute_web_search` | `execute:search` | 透過 OmniRoute 搜尋閘道(Serper/Brave/Perplexity/Exa/Tavily/Google PSE/Linkup/SearchAPI/SearXNG)進行網路搜尋,支援容錯轉移 | -## Authentication +## 進階工具(11 個)— 第二階段 -MCP tools are authenticated via API key scopes. Each tool requires specific scopes: +| 工具 | 範圍 | 說明 | +| :----------------------------------- | :------------------------------------ | :------------------------------------------------------------------------------------ | +| `omniroute_simulate_route` | `read:health`、`read:combos` | 乾執行路由模擬,含備援樹 | +| `omniroute_set_budget_guard` | `write:budget` | 工作階段預算,可設為降級/封鎖/警示動作 | +| `omniroute_set_routing_strategy` | `write:combos` | 於執行階段更新組合策略(優先/加權/自動等) | +| `omniroute_set_resilience_profile` | `write:resilience` | 套用 `aggressive`/`balanced`/`conservative` 復原能力預設 | +| `omniroute_test_combo` | `execute:completions`、`read:combos` | 使用真實上游呼叫,對組合中的每個提供者進行即時測試 | +| `omniroute_get_provider_metrics` | `read:health` | 各提供者指標,含 p50/p95/p99 延遲與斷路器狀態 | +| `omniroute_best_combo_for_task` | `read:combos`、`read:health` | 依任務類型推薦組合,考量預算與延遲限制 | +| `omniroute_explain_route` | `read:health`、`read:usage` | 解釋為何請求被路由至某提供者(評分因素+備援) | +| `omniroute_get_session_snapshot` | `read:usage` | 完整工作階段快照:成本、代幣、熱門模型/提供者、錯誤、預算守衛 | +| `omniroute_db_health_check` | `read:health`、`write:resilience` | 診斷(並可選自動修復)資料庫漂移,如中斷的組合參考/孤立資料列 | +| `omniroute_sync_pricing` | `pricing:write` | 從外部來源(LiteLLM)同步定價資料;支援 `dryRun` | -| Scope | Tools | -| :------------- | :----------------------------------------------- | -| `read:health` | get_health, get_provider_metrics | -| `read:combos` | list_combos, get_combo_metrics | -| `write:combos` | switch_combo | -| `read:quota` | check_quota | -| `write:route` | route_request, simulate_route, test_combo | -| `read:usage` | cost_report, get_session_snapshot, explain_route | -| `write:config` | set_budget_guard, set_resilience_profile | -| `read:models` | list_models_catalog, best_combo_for_task | +## 快取工具(2 個) -## Audit Logging +| 工具 | 範圍 | 說明 | +| :------------------------ | :------------- | :------------------------------------------------ | +| `omniroute_cache_stats` | `read:cache` | 語意快取、提示快取與冪等性統計 | +| `omniroute_cache_flush` | `write:cache` | 全域或依簽章/模型清除快取 | -Every tool call is logged to `mcp_tool_audit` with: +## 壓縮工具(13 個) -- Tool name, arguments, result -- Duration (ms), success/failure -- API key hash, timestamp +| 工具 | 範圍 | 說明 | +| :------------------------------------ | :------------------ | :------------------------------------------------------------------------------------------------------------------------- | +| `omniroute_compression_status` | `read:compression` | 壓縮設定、分析摘要與快取感知統計(包含 `analytics.mcpDescriptionCompression` 元資料) | +| `omniroute_compression_configure` | `write:compression` | 設定壓縮模式、閾值、目標比率、系統提示保留、MCP 描述壓縮開關 | +| `omniroute_set_compression_engine` | `write:compression` | 選取作用中引擎(off/caveman/rtk/stacked)與 Caveman/RTK 強度 | +| `omniroute_list_compression_combos` | `read:compression` | 列出已命名的壓縮組合及其引擎管線 | +| `omniroute_compression_combo_stats` | `read:compression` | 依壓縮組合與引擎分組的分析資料 | +| `omniroute_ccr_store` | `write:compression` | 將呼叫者隔離的內容儲存至有界限的記憶體內 CCR 存放區,並回傳標記與 `ccr://` 參考 | +| `omniroute_ccr_retrieve` | `read:compression` | 以完整、開頭、結尾、行數、grep 及統計模式擷取 CCR 內容 | +| `omniroute_ccr_inspect` | `read:compression` | 檢查呼叫者擁有的 CCR 元資料,不回傳內容 | +| `omniroute_ccr_list` | `read:compression` | 列出呼叫者擁有的 CCR 區塊之分頁元資料 | +| `omniroute_ccr_delete` | `write:compression` | 刪除呼叫者擁有的 CCR 區塊 | +| `omniroute_ccr_stats` | `read:compression` | 回報呼叫者範圍的記憶體使用量、生命週期計數器與存放區限制 | +| `omniroute_rtk_discover` | `read:compression` | 在選擇性加入的 RTK 輸出樣本中發現重複出現的雜訊 | +| `omniroute_rtk_learn` | `read:compression` | 從選擇性加入的樣本產生可供審查的 RTK 過濾器草稿 | -## Files +CCR 條目僅存在於記憶體中,重新啟動後即消失。每個區塊限制為 2 MiB,每個主體限制為 16 MiB,全域存放區限制為 64 MiB。條目預設 TTL 為 24 小時(最長七天)。完整的 MCP 擷取限制為 256 KiB;較大的區塊仍可透過範圍與 grep 模式使用。儲存、擷取、列出、檢查、刪除與統計皆以通過驗證的 API 金鑰主體進行隔離。稽核記錄包含雜湊與大小元資料,絕不包含內容。 -| File | Purpose | -| :------------------------------------------- | :------------------------------------------ | -| `open-sse/mcp-server/server.ts` | MCP server creation + 16 tool registrations | -| `open-sse/mcp-server/transport.ts` | Stdio + HTTP transport | -| `open-sse/mcp-server/auth.ts` | API key + scope validation | -| `open-sse/mcp-server/audit.ts` | Tool call audit logging | -| `open-sse/mcp-server/tools/advancedTools.ts` | 8 advanced tool handlers | +`omniroute_compression_status` 會將 MCP 描述壓縮分別回報於 `analytics.mcpDescriptionCompression` 之下。這些數值是對 MCP 可列出描述(`tools`、`prompts`、`resources` 與 `resourceTemplates`)的元資料大小估計值,並非提供者使用收據,並標記有 `source: "mcp_metadata_estimate"`。 + +### MCP 無障礙樹過濾器(v3.8.0) + +與上述壓縮工具不同,OmniRoute 包含一個執行後過濾器,可在 MCP 瀏覽器/無障礙工具的**工具結果**回傳給代理之前對其進行壓縮。此過濾器本身不是一個工具 — 它會透明地作用於任何包含冗長無障礙樹或瀏覽器快照文字(≥2000 字元)的工具結果。 + +關鍵行為: + +- 將 ≥30 行連續重複的同層兄弟行摺疊為開頭+結尾摘要 +- 保留 Playwright/電腦使用所需的 `[ref=eXX]` 錨點 +- 對過大的文字(>50,000 字元)進行強制截斷,並附上導航提示 +- 預期節省:瀏覽器快照承載的 **60–80%** + +設定:全域設定中的 `compression.mcpAccessibility`(遷移 056)。 +實作:`open-sse/services/compression/engines/mcpAccessibility/`。 +完整文件:[壓縮引擎 — MCP 無障礙樹過濾器](../compression/COMPRESSION_ENGINES.md#mcp-accessibility-tree-filter)。 + +請參閱[壓縮引擎](../compression/COMPRESSION_ENGINES.md)與 [RTK 壓縮](../compression/RTK_COMPRESSION.md)了解這些工具背後的執行時期壓縮模型。 + +## 1Proxy 工具(3 個) + +| 工具 | 範圍 | 說明 | +| :---------------------------- | :-------------- | :----------------------------------------------------------------------------------- | +| `omniroute_oneproxy_fetch` | `read:proxies` | 從 1proxy 市集取得免費代理(協定/國家/品質/數量過濾器) | +| `omniroute_oneproxy_rotate` | `read:proxies` | 依策略(`random`/`quality`/`sequential`)取得下一個可用代理 | +| `omniroute_oneproxy_stats` | `read:proxies` | 池統計、同步狀態、依協定與國家的分佈 | + +## 記憶工具(3 個) + +定義於 `open-sse/mcp-server/tools/memoryTools.ts`。認證/範圍透過標準 MCP 範圍管線強制執行。 + +| 工具 | 範圍 | 說明 | +| :-------------------------- | :-------------- | :----------------------------------------------------------------------------- | +| `omniroute_memory_search` | `read:memory` | 依查詢/類型/API 金鑰搜尋記憶,並執行代幣預算限制 | +| `omniroute_memory_add` | `write:memory` | 新增記憶條目(`factual`/`episodic`/`procedural`/`semantic`) | +| `omniroute_memory_clear` | `write:memory` | 清除某 API 金鑰的記憶,可選擇依類型或 `olderThan` 時間戳過濾 | + +## 技能工具(4 個) + +定義於 `open-sse/mcp-server/tools/skillTools.ts`。由 `src/lib/skills/registry` 與 `src/lib/skills/executor` 支援。 + +| 工具 | 範圍 | 說明 | +| :------------------------------ | :---------------- | :----------------------------------------------------------------------------- | +| `omniroute_skills_list` | `read:skills` | 列出已註冊的技能,可依 API 金鑰、名稱或啟用狀態過濾 | +| `omniroute_skills_enable` | `write:skills` | 依 ID 啟用或停用特定技能 | +| `omniroute_skills_execute` | `execute:skills` | 以提供的輸入執行技能,並回傳執行記錄 | +| `omniroute_skills_executions` | `read:skills` | 列出近期技能執行歷史 | + +## Notion 上下文來源(6 個) + +定義於 `open-sse/mcp-server/tools/notionTools.ts`。代幣儲存於 `key_value` 表中,透過 `src/lib/db/notion.ts` 操作。REST 客戶端位於 `src/lib/notion/api.ts`。設定 API 位於 `src/app/api/settings/notion/route.ts`。儀表板 UI 位於 `src/app/(dashboard)/dashboard/endpoint/components/NotionSourceCard.tsx`。 + +在端點儀表板的**上下文來源**頁籤中設定你的 Notion 整合代碼,或透過 REST API 設定: + +```bash +# 設定代碼 +curl -X POST http://localhost:20128/api/settings/notion \ + -H "Content-Type: application/json" \ + -d '{"token": "ntn_..."}' + +# 檢查狀態 +curl http://localhost:20128/api/settings/notion + +# 斷開連線 +curl -X DELETE http://localhost:20128/api/settings/notion +``` + +| 工具 | 範圍 | 說明 | +| :----------------------------- | :-------------- | :------------------------------------------------------------- | +| `notion_search` | `read:notion` | 在所有頁面與資料庫中進行全文搜尋 | +| `notion_get_page` | `read:notion` | 依 ID 取得頁面及其屬性 | +| `notion_list_block_children` | `read:notion` | 列出頁面或區塊的子區塊 | +| `notion_query_database` | `read:notion` | 以過濾器、排序與分頁查詢資料庫 | +| `notion_get_database` | `read:notion` | 依 ID 取得資料庫架構 | +| `notion_append_blocks` | `write:notion` | 將子區塊附加至父區塊(每次請求最多 100 個) | + +## Agent 技能目錄工具(3 個) + +定義於 `open-sse/mcp-server/tools/agentSkillTools.ts`。由 `src/lib/agentSkills/catalog` 支援。這些工具將 42 個項目的 Agent 技能文件目錄暴露給 MCP 客戶端與外部代理。範圍:`read:catalog`。 + +| 工具 | 範圍 | 說明 | +| :---------------------------------- | :-------------- | :--------------------------------------------------------------------------------------------------------------- | +| `omniroute_agent_skills_list` | `read:catalog` | 列出全部 42 個 agent 技能,可選 `category`(api\|cli)與 `area` 過濾器;回傳元資料+覆蓋率 | +| `omniroute_agent_skills_get` | `read:catalog` | 依標準 `id` 取得單一技能的完整元資料+SKILL.md 內容 | +| `omniroute_agent_skills_coverage` | `read:catalog` | 覆蓋率統計:22 個 API 與 20 個 CLI 技能中有多少個在檔案系統上擁有 SKILL.md 檔案,與目錄總數比較 | + +請參閱 [AGENT-SKILLS.md](./AGENT-SKILLS.md) 了解完整目錄及外部代理如何使用。 + +## 相關框架(v3.8.0) + +上述 MCP 工具清單(104 個唯一工具,由 `countUniqueMcpTools()` 計算)的範圍故意限定於執行時期路由/快取/壓縮/記憶/技能/代理/上下文來源操作。兩個相鄰框架與 MCP 伺服器一同於 v3.8.0 提供,並分別記錄: + +### Cloud Agents + +Cloud Agents 是行程外的 AI 編碼代理(codex-cloud、devin、jules),透過與 LLM 提供者相同的連線模型接入 OmniRoute。它們透過自己的 REST 介面(`/api/v1/agents/*`)暴露,且**不屬於** MCP 工具目錄的一部分 — 呼叫 Cloud Agent 不會消耗 MCP 範圍。 + +- 實作:`src/lib/cloudAgent/`(`registry.ts`、`agents/codex-cloud.ts`、`agents/devin.ts`、`agents/jules.ts`)。 +- 生命週期:`createTask`、`getStatus`、`approvePlan`、`sendMessage`、`listSources`。 +- 文件:[docs/frameworks/CLOUD_AGENT.md](./CLOUD_AGENT.md)。 + +### Guardrails + +Guardrails 是在聊天管線中套用的執行前/執行後過濾器(vision-bridge、pii-masker、prompt-injection)。它們在抵達 MCP 工具/路由層之前執行,並將結構化違規記錄發送至稽核管線;它們不是以 MCP 工具的形式被呼叫。 + +- 實作:`src/lib/guardrails/`。 +- 文件:[docs/security/GUARDRAILS.md](../security/GUARDRAILS.md)。 + +當偵錯一個看似被封鎖的 MCP 呼叫時,請同時檢查 MCP 稽核日誌(`scope_denied:*` 條目)與 guardrails 稽核軌跡 — 請求可能在抵達 MCP 範圍強制執行層**之前**就被 guardrail 拒絕。 + +--- + +## REST API 端點 + +| 端點 | 方法 | 說明 | 認證 | +| :----------------------- | :--------------------- | :--------------------------------------------------------------------------------------------- | :-------------------------- | +| `/api/mcp/status` | `GET` | 伺服器狀態:心跳、HTTP 傳輸狀態、稽核活動摘要 | 管理(工作階段/管理員) | +| `/api/mcp/tools` | `GET` | 工具目錄(名稱、說明、範圍、階段、來源端點) | 管理 | +| `/api/mcp/sse` | `GET` / `POST` | SSE 傳輸端點(由 `mcpEnabled` + `mcpTransport === "sse"` 閘控) | API 金鑰 + 範圍 | +| `/api/mcp/stream` | `POST`/`GET`/`DELETE` | 可串流 HTTP 傳輸(使用 `mcp-session-id` 標頭;`DELETE` 結束工作階段) | API 金鑰 + 範圍 | +| `/api/mcp/audit` | `GET` | 來自 `mcp_tool_audit` 的稽核日誌條目(過濾器:`limit`、`offset`、`tool`、`success`、`apiKeyId`) | 管理 | +| `/api/mcp/audit/stats` | `GET` | 彙總稽核統計(`totalCalls`、`successRate`、`avgDurationMs`、熱門工具) | 管理 | + +原始檔案:`src/app/api/mcp/{status,tools,sse,stream,audit,audit/stats}/route.ts`。 + +SSE 與 Streamable HTTP 兩種傳輸層在設定中啟用 MCP 伺服器(`mcpEnabled`)並選取適當的 `mcpTransport` 之前,皆處於封鎖狀態。若設定了錯誤的傳輸層,路由會回傳 HTTP 400 並提示切換設定。 + +--- + +## 認證與範圍 + +MCP 工具透過 API 金鑰範圍進行認證。範圍強制執行集中於 `open-sse/mcp-server/scopeEnforcement.ts`。每個工具需要特定的範圍: + +| 範圍 | 工具 | +| :-------------------- | :------------------------------------------------------------------------------------------------------------------ | +| `read:health` | `get_health`、`get_provider_metrics`、`simulate_route`、`explain_route`、`best_combo_for_task`、`db_health_check` | +| `read:combos` | `list_combos`、`get_combo_metrics`、`simulate_route`、`best_combo_for_task`、`test_combo` | +| `write:combos` | `switch_combo`、`set_routing_strategy` | +| `read:quota` | `check_quota` | +| `read:usage` | `cost_report`、`get_session_snapshot`、`explain_route` | +| `read:models` | `list_models_catalog` | +| `execute:completions` | `route_request`、`test_combo` | +| `execute:search` | `web_search` | +| `write:budget` | `set_budget_guard` | +| `write:resilience` | `set_resilience_profile`、`db_health_check` | +| `pricing:write` | `sync_pricing` | +| `read:cache` | `cache_stats` | +| `write:cache` | `cache_flush` | +| `read:compression` | `compression_status`、`list_compression_combos`、`compression_combo_stats` | +| `write:compression` | `compression_configure`、`set_compression_engine` | +| `read:proxies` | `oneproxy_fetch`、`oneproxy_rotate`、`oneproxy_stats` | +| `read:notion` | `notion_search`、`notion_list_databases`、`notion_get_database`、`notion_query_database`、`notion_read` | +| `write:notion` | `notion_append_blocks` | +| `read:memory` | `memory_search` | +| `write:memory` | `memory_add`、`memory_clear` | +| `read:skills` | `skills_list`、`skills_executions` | +| `write:skills` | `skills_enable` | +| `execute:skills` | `skills_execute` | +| `read:catalog` | `agent_skills_list`、`agent_skills_get`、`agent_skills_coverage` | + +支援萬用字元範圍:`read:*` 授予所有讀取範圍,`*` 授予完整存取權限。 + +--- + +## 環境變數 + +| 變數 | 預設值 | 用途 | +| :---------------------------------------- | :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | +| `OMNIROUTE_BASE_URL` | `http://localhost:20128` | MCP 伺服器在呼叫 OmniRoute 內部 API 時使用的基礎 URL | +| `OMNIROUTE_API_KEY` | (空) | 轉發為 `Authorization: ` 至內部 API 呼叫的 API 金鑰 | +| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `false`(僅 `"true"` 啟用) | 啟用時,缺少範圍會拒絕工具呼叫,並在稽核日誌中記錄 `scope_denied:<原因>` | +| `OMNIROUTE_MCP_SCOPES` | (空) | 逗號分隔的範圍允許清單,視為預設「可用」(用於呼叫者未提供自身範圍時) | +| `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | (未設定=開啟) | 設為 `0/false/off/no` 時,在註冊時停用 MCP 描述壓縮 | +| `OMNIROUTE_MCP_DESCRIPTION_COMPRESSION` | (未設定=開啟) | 上述相同開關的別名 | +| `MCP_TOOL_DENY` | (未設定=無過濾) | 逗號分隔的工具名稱,從 `tools/list` 中移除(工具基數減少 — 請參閱下方) | +| `MCP_TOOL_ALLOW` | (未設定=無過濾) | 逗號分隔的工具名稱,僅保留這些工具(允許清單模式 — 請參閱下方) | +| `DATA_DIR` | `~/.omniroute` | 心跳檔案寫入至 `${DATA_DIR}/runtime/mcp-heartbeat.json` | + +--- + +## 描述壓縮 + +MCP 工具、提示與資源註冊表可在註冊/列出時壓縮描述,以減少暴露給客戶端的元資料大小(進而降低提示上下文成本)。實作位於 `open-sse/mcp-server/descriptionCompressor.ts`,並透過 `createMcpServer()` 中的 `compressMcpRegistryMetadata` 接入 MCP 伺服器。 + +- 壓縮使用 Caveman 規則集(`getRulesForContext("all", "full")`)對描述文字進行壓縮,並保留區塊提取(程式碼跨度、圍欄區塊等),以確保結構性內容不被改變。 +- 可透過 `key_value` 設定表中的 `compression.mcpDescriptionCompressionEnabled` 值(預設:啟用)依部署切換 — 在 UI 中顯示為**分析 → MCP 描述壓縮**。 +- 可透過 `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS=false` 或 `OMNIROUTE_MCP_DESCRIPTION_COMPRESSION=false` 全域切換。 +- 即時統計資料透過 `omniroute_compression_status` 在 `analytics.mcpDescriptionCompression` 下呈現,並標記為 `source: "mcp_metadata_estimate"`,以與真實的提供者使用收據區分。 + +--- + +## 工具基數減少(F4.3) + +描述壓縮會縮小每個工具的元資料;**工具基數減少**則更進一步,減少**宣告的工具總數**。在 `tools/list` 清單中廣告較少的工具,可降低客戶端模型為工具目錄所支付的每次請求代幣成本(「第 5 層」壓縮)。實作為一個純粹、無狀態的過濾器,位於 `open-sse/mcp-server/toolCardinality.ts`(`reduceToolManifest`),接入 `createMcpServer()`(`open-sse/mcp-server/server.ts`)的註冊迴圈。 + +**選擇性加入,預設關閉。** 過濾器僅在至少設定兩個環境變數之一時才執行;若兩者皆未設定,則所有 104 個工具保持不變地被宣告。 + +| 變數 | 模式 | +| :---------------- | :----------------------------------------------------------------------------------------- | +| `MCP_TOOL_DENY` | 黑名單 — 逗號分隔的工具名稱,始終從 `tools/list` 中移除 | +| `MCP_TOOL_ALLOW` | 允許清單 — 逗號分隔的工具名稱;僅這些工具保留,其他全部移除 | + +`deny` 優先於 `allow`。名稱以逗號分隔,前後空白被去除,空條目被忽略。範例: + +```bash +# 從目錄中移除兩個工具 +MCP_TOOL_DENY="omniroute_get_health,omniroute_list_combos" omniroute --mcp + +# 僅宣告路由+配額工具(允許清單模式) +MCP_TOOL_ALLOW="omniroute_route_request,omniroute_check_quota" omniroute --mcp +``` + +**被過濾的工具如何移除:** 註冊始終成功;設定檔拒絕的工具隨後會在 MCP SDK 控制代碼上被 `.disable()`,因此它永遠不會出現在 `tools/list` 中,但接線保持完整(乾淨的啟用/停用,無需重新註冊)。設定檔解析器為 `readMcpToolProfileFromEnv(process.env)`,當兩個變數皆為空時回傳 `null`(不過濾)。 + +`reduceToolManifest` 背後更豐富的 `ToolProfile` 型別也支援範圍交集過濾(`allowScopes`,含 `read:*` 風格的萬用字元比對)與確定性的 `maxTools` 上限,但這兩個控制項需要在註冊時取得完整清單,且**目前尚未透過環境變數暴露**(`tools/list` 層級的鉤子為已追蹤的後續功能)。`estimateManifestTokens()` 可用於比較減少前後的工具清單代幣成本。 + +--- + +## 執行時期心跳 + +stdio 傳輸層每 5 秒將存活狀態持續寫入 `${DATA_DIR}/runtime/mcp-heartbeat.json`。儀表板(`/api/mcp/status`)讀取此檔案加上 PID 存活狀態以推斷 `online` 狀態。HTTP 傳輸層則從行程內的 `getMcpHttpStatus()` 回報狀態(不寫入檔案)。 + +心跳快照包含以下內容: + +```json +{ + "pid": 12345, + "startedAt": "2026-05-13T12:34:56.000Z", + "lastHeartbeatAt": "2026-05-13T12:35:01.000Z", + "version": "1.8.1", + "transport": "stdio", + "scopesEnforced": false, + "allowedScopes": [], + "toolCount": 43 +} +``` + +--- + +## 稽核日誌 + +每個工具呼叫均由 `open-sse/mcp-server/audit.ts` 記錄至 SQLite `mcp_tool_audit` 表: + +- 工具名稱、引數(依各工具的 `auditLevel` 進行雜湊/截斷)、結果 +- 持續時間(毫秒)、成功/失敗標記、錯誤訊息(適用時) +- API 金鑰雜湊、時間戳 +- 範圍拒絕記錄為 `scope_denied:<原因>`,附帶缺少的範圍清單 + +使用儀表板或 `/api/mcp/audit` 與 `/api/mcp/audit/stats` REST 端點來檢查近期呼叫。 + +--- + +## 檔案 + +| 檔案 | 用途 | +| :---------------------------------------------------------------------- | :--------------------------------------------------------------- | +| `open-sse/mcp-server/server.ts` | MCP 伺服器工廠、stdio 入口點、範圍化工具註冊 | +| `open-sse/mcp-server/httpTransport.ts` | SSE + Streamable HTTP 傳輸層(工作階段管理) | +| `open-sse/mcp-server/scopeEnforcement.ts` | 工具範圍評估與呼叫者解析 | +| `open-sse/mcp-server/audit.ts` | 工具呼叫稽核日誌(`mcp_tool_audit`) | +| `open-sse/mcp-server/runtimeHeartbeat.ts` | stdio 心跳寫入器(`mcp-heartbeat.json`) | +| `open-sse/mcp-server/descriptionCompressor.ts` | 工具/提示/資源註冊表的描述壓縮 | +| `open-sse/mcp-server/schemas/tools.ts` | Zod 架構+工具註冊表(`MCP_TOOLS`,34 個條目) | +| `open-sse/mcp-server/tools/advancedTools.ts` | 第二階段+快取+1proxy 工具處理器 | +| `open-sse/mcp-server/tools/compressionTools.ts` | 壓縮工具處理器 | +| `open-sse/mcp-server/tools/memoryTools.ts` | 記憶工具定義(3 個工具) | +| `open-sse/mcp-server/tools/skillTools.ts` | 技能工具定義(4 個工具) | +| `open-sse/mcp-server/tools/notionTools.ts` | Notion 上下文來源工具定義(6 個工具) | +| `open-sse/mcp-server/tools/gamificationTools.ts` | 遊戲化工具定義(8 個工具) | +| `open-sse/mcp-server/tools/pluginTools.ts` | 外掛註冊與管理工具(8 個工具) | +| `src/app/api/mcp/status/route.ts` | `/api/mcp/status` 端點 | +| `src/app/api/mcp/tools/route.ts` | `/api/mcp/tools` 端點 | +| `src/app/api/mcp/sse/route.ts` | `/api/mcp/sse` SSE 傳輸路由 | +| `src/app/api/mcp/stream/route.ts` | `/api/mcp/stream` Streamable HTTP 傳輸路由 | +| `src/app/api/mcp/audit/route.ts` | `/api/mcp/audit` 稽核日誌查詢 | +| `src/app/api/mcp/audit/stats/route.ts` | `/api/mcp/audit/stats` 彙總稽核指標 | +| `src/lib/notion/api.ts` | Notion REST API 客戶端(重試、逾時、錯誤分類) | +| `src/lib/db/notion.ts` | Notion 代碼持久化(`key_value` 表) | +| `src/app/api/settings/notion/route.ts` | Notion 設定 API(GET/POST/DELETE) | +| `src/app/(dashboard)/dashboard/endpoint/components/NotionSourceCard.tsx` | Notion 代碼管理 UI | +| `tests/unit/notion-api.test.ts` | Notion API 客戶端測試(7 個) | +| `tests/unit/notion-tools.test.ts` | Notion 工具範圍強制執行測試(10 個) | +| `tests/unit/db/notion.test.mjs` | Notion DB 模組測試(3 個) | diff --git a/docs/i18n/zh-TW/docs/guides/FEATURES.md b/docs/i18n/zh-TW/docs/guides/FEATURES.md index 415cdb866a..abd311d969 100644 --- a/docs/i18n/zh-TW/docs/guides/FEATURES.md +++ b/docs/i18n/zh-TW/docs/guides/FEATURES.md @@ -1,269 +1,328 @@ -# OmniRoute — Dashboard Features Gallery (中文 (簡體)) +--- +title: "OmniRoute — 儀表板功能總覽" +version: 3.8.40 +lastUpdated: 2026-06-28 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/FEATURES.md) · 🇸🇦 [ar](../../ar/docs/FEATURES.md) · 🇧🇬 [bg](../../bg/docs/FEATURES.md) · 🇧🇩 [bn](../../bn/docs/FEATURES.md) · 🇨🇿 [cs](../../cs/docs/FEATURES.md) · 🇩🇰 [da](../../da/docs/FEATURES.md) · 🇩🇪 [de](../../de/docs/FEATURES.md) · 🇪🇸 [es](../../es/docs/FEATURES.md) · 🇮🇷 [fa](../../fa/docs/FEATURES.md) · 🇫🇮 [fi](../../fi/docs/FEATURES.md) · 🇫🇷 [fr](../../fr/docs/FEATURES.md) · 🇮🇳 [gu](../../gu/docs/FEATURES.md) · 🇮🇱 [he](../../he/docs/FEATURES.md) · 🇮🇳 [hi](../../hi/docs/FEATURES.md) · 🇭🇺 [hu](../../hu/docs/FEATURES.md) · 🇮🇩 [id](../../id/docs/FEATURES.md) · 🇮🇹 [it](../../it/docs/FEATURES.md) · 🇯🇵 [ja](../../ja/docs/FEATURES.md) · 🇰🇷 [ko](../../ko/docs/FEATURES.md) · 🇮🇳 [mr](../../mr/docs/FEATURES.md) · 🇲🇾 [ms](../../ms/docs/FEATURES.md) · 🇳🇱 [nl](../../nl/docs/FEATURES.md) · 🇳🇴 [no](../../no/docs/FEATURES.md) · 🇵🇭 [phi](../../phi/docs/FEATURES.md) · 🇵🇱 [pl](../../pl/docs/FEATURES.md) · 🇵🇹 [pt](../../pt/docs/FEATURES.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/FEATURES.md) · 🇷🇴 [ro](../../ro/docs/FEATURES.md) · 🇷🇺 [ru](../../ru/docs/FEATURES.md) · 🇸🇰 [sk](../../sk/docs/FEATURES.md) · 🇸🇪 [sv](../../sv/docs/FEATURES.md) · 🇰🇪 [sw](../../sw/docs/FEATURES.md) · 🇮🇳 [ta](../../ta/docs/FEATURES.md) · 🇮🇳 [te](../../te/docs/FEATURES.md) · 🇹🇭 [th](../../th/docs/FEATURES.md) · 🇹🇷 [tr](../../tr/docs/FEATURES.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/FEATURES.md) · 🇵🇰 [ur](../../ur/docs/FEATURES.md) · 🇻🇳 [vi](../../vi/docs/FEATURES.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/FEATURES.md) +# OmniRoute — 儀表板功能總覽 + +🌐 **主要 README 翻譯:** 🇺🇸 [English](../README.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/README.md) | 🇪🇸 [Español](../i18n/es/README.md) | 🇫🇷 [Français](../i18n/fr/README.md) | 🇮🇹 [Italiano](../i18n/it/README.md) | 🇷🇺 [Русский](../i18n/ru/README.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/README.md) | 🇩🇪 [Deutsch](../i18n/de/README.md) | 🇮🇳 [हिन्दी](../i18n/in/README.md) | 🇹🇭 [ไทย](../i18n/th/README.md) | 🇺🇦 [Українська](../i18n/uk-UA/README.md) | 🇸🇦 [العربية](../i18n/ar/README.md) | 🇯🇵 [日本語](../i18n/ja/README.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/README.md) | 🇧🇬 [Български](../i18n/bg/README.md) | 🇩🇰 [Dansk](../i18n/da/README.md) | 🇫🇮 [Suomi](../i18n/fi/README.md) | 🇮🇱 [עברית](../i18n/he/README.md) | 🇭🇺 [Magyar](../i18n/hu/README.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/README.md) | 🇰🇷 [한국어](../i18n/ko/README.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/README.md) | 🇳🇱 [Nederlands](../i18n/nl/README.md) | 🇳🇴 [Norsk](../i18n/no/README.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/README.md) | 🇷🇴 [Română](../i18n/ro/README.md) | 🇵🇱 [Polski](../i18n/pl/README.md) | 🇸🇰 [Slovenčina](../i18n/sk/README.md) | 🇸🇪 [Svenska](../i18n/sv/README.md) | 🇵🇭 [Filipino](../i18n/phi/README.md) | 🇨🇿 [Čeština](../i18n/cs/README.md) + +OmniRoute 儀表板各區塊的視覺化導覽。 + +> 📅 **最後更新:** 2026-06-28 — **v3.8.40** --- -Visual guide to every section of the OmniRoute dashboard. +## ✨ v3.8.0 重點功能 + +v3.7.x → v3.8.0 版本週期新增了零設定自動路由、新供應商、OAuth 流程、更深的抗災能力以及更豐富的 CLI 體驗。以下為重點功能——完整細節請參閱稍後章節及連結的規格文件。 + +- 🤖 **Auto Combo / 零設定自動路由** — 使用前綴 `auto/coding`、`auto/fast`、`auto/cheap`、`auto/offline`、`auto/smart`、`auto/lkgp`。由 9 因子評分引擎和 4 個精選**模式包**(快速出貨、節省成本、品質優先、離線友善)驅動 +- 🆕 **Command Code 供應商**(#2199)— 一級支援,含模型目錄及配額追蹤 +- 🆕 **Z.AI 供應商** — 新增免費方案供應商,附配額標籤 +- 🎬 **KIE 媒體擴展** — 擴充目錄,納入影片生成模型 +- 🔐 **Windsurf + Devin CLI OAuth 流程**(#2168)— 端到端瀏覽器登入 +- 🆓 **8 個新的免費供應商** — LLM7、Lepton、UncloseAI、BazaarLink、Completions、Enally、FreeTheAi、Command Code +- 🎯 **清單感知分層路由 W1–W4** — 供應商清單驅動加權層級選擇 +- 🎨 **Cursor 完整 OpenAI 相容性** — 工具呼叫、串流、階段管理端到端 +- 📊 **Cursor Pro 方案用量** — 在供應商限制儀表板中顯示配額與週期數據 +- ⚡ **服務層級 breakdown / Codex 快速層分析** — 各層級用量可視化 +- 📌 **每階段黏性路由** — Codex 階段在輪次間固定使用相同帳戶 +- 🔊 **Inworld TTS 增強** — 語音目錄、串流及延遲改善 +- 🔑 **Kiro 無頭驗證** — 透過本機 `kiro-cli` SQLite 儲存庫登入,無需瀏覽器 +- 📉 **DeepSeek 配額與限制監控** — 在儀表板顯示每日/每月用量 +- 🔄 **重設感知路由策略** — Combo 現在優先選用配額視窗最早重置的帳戶 +- ⏱️ **`fallbackDelayMs`** 與**動態工具限制偵測** — 更精細的備援時機 + 各供應商工具數量限制 +- 🔧 **背景模式降級(Responses API)** — 當上游缺乏背景輪詢能力時,降級為同步模式並附上結構化警告 +- 🚦 **各供應商 429 分類** + `useUpstream429BreakerHints` 開關 — 利用上游速率限制提示來微調斷路器行為 +- 🩺 **模型冷卻儀表板** — 觀察各模型的鎖定狀態,並可從 UI 手動重新啟用 +- 🔒 **MITM 動態 Linux 憑證偵測** — 適用於 Debian/Ubuntu、Fedora/RHEL、Arch 及其他發行版 +- 💻 **CLI 增強套件** — 20 多個指令,包含 `omniroute providers`、`omniroute combos`、`omniroute doctor`、`omniroute setup` +- 🔍 **Qdrant 嵌入模型探索** — 自動向量儲存模型探測 +- 🔑 **API 金鑰 / Bearer 金鑰搭配 `manage` 範圍** — 透過 API 以程式方式執行管理操作 +- 🏥 **Combo 目標健康度分析** + **結構化 Combo 建構器** — 各目標健康度及 UI 建構器,用於組合 `(供應商, 模型, 連線)` 步驟 +- 🤝 **GitLab Duo OAuth 供應商** — 使用 GitLab 憑證登入 +- 🧠 **推理重播快取** — 混合記憶體 + SQLite 持久化推理軌跡 + +📚 **相關文件:** [技能框架](../frameworks/SKILLS.md) · [記憶系統](../frameworks/MEMORY.md) · [雲端代理](../frameworks/CLOUD_AGENT.md) · [Webhook](../frameworks/WEBHOOKS.md) · [推理重播快取](../routing/REASONING_REPLAY.md) --- -## 🔌 Providers +## 🔌 供應商 +管理 AI 供應商連線:OAuth 供應商(Claude Code、Codex)、API 金鑰供應商(Groq、DeepSeek、OpenRouter)以及免費供應商(Qoder、Kiro)。Kiro 帳戶包含額度餘額追蹤——剩餘額度、總配額及續約日期,均可在「儀表板 → 用量」中檢視。 -![Providers Dashboard](screenshots/01-providers.png) +OpenRouter 連線可在「進階設定」中儲存各連線的 `preset`。設定後,OmniRoute 會將其作為 OpenRouter 頂層請求欄位發送,例如 `"preset": "email-copywriter"`,除非客戶端請求已提供自己的 `preset`。 + +![供應商儀表板](../screenshots/01-providers.png) --- -## 🎨 Combos +## 🎨 Combo -Create model routing combos with 13 strategies: priority, weighted, round-robin, random, least-used, cost-optimized, strict-random, auto, fill-first, p2c, lkgp, context-optimized, and **context-relay**. Each combo chains multiple models with automatic fallback and includes quick templates and readiness checks. +使用 17 種策略建立模型路由組合:優先、加權、先填滿、輪詢、p2c(二選一)、隨機、最少使用、成本最佳化、重設感知、重設視窗、餘裕空間、嚴格隨機、自動、lkgp(最後已知良好供應商)、情境最佳化、情境轉接,以及**融合**(並行分發給多個模型,再由評判模型合成一個答案)。每個組合可串聯多個模型並自動備援,內含快速範本與就緒檢查。 -Recent combo improvements: +近期 Combo 改善: -- **Structured combo builder** — create each step by selecting provider, model, and exact account/connection -- **Repeated provider support** — reuse the same provider many times in one combo as long as the `(provider, model, connection)` tuple is unique -- **Combo target health** — analytics and health surfaces now distinguish individual combo targets/steps instead of collapsing everything into model strings -- **Composite tier ordering** — `defaultTier -> fallbackTier` now influences runtime execution/fallback order for top-level combo steps +- **結構化 Combo 建構器** — 透過選擇供應商、模型及精確帳戶/連線來建立每個步驟 +- **重複供應商支援** — 只要 `(供應商, 模型, 連線)` 組合唯一,即可在同一組合中多次重複使用相同供應商 +- **Combo 目標健康度** — 分析與健康度面板現在可區分個別 Combo 目標/步驟,而非全部收攏為模型字串 +- **複合層級排序** — `defaultTier -> fallbackTier` 現在會影響頂層 Combo 步驟的執行/備援順序 -![Combos Dashboard](screenshots/02-combos.png) +![Combo 儀表板](../screenshots/02-combos.png) --- -## 📊 Analytics +## 📊 分析 -Comprehensive usage analytics with token consumption, cost estimates, activity heatmaps, weekly distribution charts, and per-provider breakdowns. +全面的用量分析,包含 Token 消耗、成本估算、活動熱圖、每週分佈圖表及各供應商 breakdown。 -![Analytics Dashboard](screenshots/03-analytics.png) +![分析儀表板](../screenshots/03-analytics.png) --- -## 🏥 System Health +## 🏥 系統健康度 -Real-time monitoring: uptime, memory, version, latency percentiles (p50/p95/p99), cache statistics, provider circuit breaker states, active quota-monitored sessions, and combo target health. +即時監控:運作時間、記憶體、版本、延遲百分位數(p50/p95/p99)、快取統計、供應商斷路器狀態、活躍配額監控階段及 Combo 目標健康度。 -![Health Dashboard](screenshots/04-health.png) +![健康度儀表板](../screenshots/04-health.png) --- -## 🔧 Translator Playground +## 🔧 轉換器測試區 -Four modes for debugging API translations: **Playground** (format converter), **Chat Tester** (live requests), **Test Bench** (batch tests), and **Live Monitor** (real-time stream). +四種模式用於除錯 API 轉換:**測試區**(格式轉換器)、**聊天測試器**(即時請求)、**測試平台**(批次測試)及**即時監控**(即時串流)。 -![Translator Playground](screenshots/05-translator.png) +![轉換器測試區](../screenshots/05-translator.png) --- -## 🎮 Model Playground _(v2.0.9+)_ +## 🎮 模型測試區 _(v2.0.9+)_ -Test any model directly from the dashboard. Select provider, model, and endpoint, write prompts with Monaco Editor, stream responses in real-time, abort mid-stream, and view timing metrics. +直接從儀表板測試任何模型。選擇供應商、模型及端點,使用 Monaco Editor 編寫提示詞,即時串流接收回應,可中途中止並檢視時間指標。 --- -## 🎨 Themes _(v2.0.5+)_ +## 🎨 主題 _(v2.0.5+)_ -Customizable color themes for the entire dashboard. Choose from 7 preset colors (Coral, Blue, Red, Green, Violet, Orange, Cyan) or create a custom theme by picking any hex color. Supports light, dark, and system mode. +可自訂的儀表板色彩主題。從 7 個預設顏色(珊瑚、藍、紅、綠、紫罗兰、橙、青)中選擇,或透過挑選任意十六進位色碼建立自訂主題。支援淺色、深色及系統模式。 --- -## ⚙️ Settings +## ⚙️ 設定 -Comprehensive settings panel with tabs: +全面的設定面板,包含 **7 個分頁**: -- **General** — System storage, backup management (export/import database) -- **Appearance** — Theme selector (dark/light/system), color theme presets and custom colors, health log visibility, sidebar item visibility controls -- **Security** — API endpoint protection, custom provider blocking, IP filtering, session info -- **Routing** — Model aliases, background task degradation -- **Resilience** — Rate limit persistence, circuit breaker tuning, auto-disable banned accounts, provider expiration monitoring, **Context Relay** handoff threshold and summary model configuration -- **Advanced** — Configuration overrides, configuration audit trail, fallback degradation mode +- **一般** — 系統儲存、備份管理(匯出/匯入資料庫) +- **外觀** — 主題選擇器(深色/淺色/系統)、色彩主題預設與自訂顏色、健康度記錄可見度、側邊欄項目與群組分隔線可見度控制、端點通道可見度控制 +- **AI** — AI 助手功能、預設路由預設(Auto Combo `auto/coding`、`auto/fast`、`auto/cheap`、`auto/smart`)、推理重播快取及技能/記憶開關 +- **安全性** — API 端點保護、自訂供應商封鎖、IP 過濾、階段資訊 +- **路由** — 模型別名、背景任務降級、清單感知分層路由(W1–W4)、`fallbackDelayMs`、每階段黏性路由 +- **抗災能力** — 速率限制持久化、斷路器調校、自動停用被封帳戶、供應商到期監控、**Context Relay** 交接門檻與摘要模型配置、各供應商 429 分類及 `useUpstream429BreakerHints` 開關、模型冷卻 +- **進階** — 配置覆寫、配置審計軌跡、備援降級模式、Responses API 背景模式降級 -![Settings Dashboard](screenshots/06-settings.png) +![設定儀表板](../screenshots/06-settings.png) --- -## 🔧 CLI Tools +## 🔧 CLI 工具 -One-click configuration for AI coding tools: Claude Code, Codex CLI, OpenClaw, Kilo Code, Antigravity, Cline, Continue, Cursor, and Factory Droid. Features automated config apply/reset, connection profiles, and model mapping. +一鍵配置 AI 程式碼工具:Claude Code、Codex CLI、OpenClaw、Kilo Code、Antigravity、Cline、Continue、Cursor 及 Factory Droid。功能包含自動化配置套用/重置、連線設定檔及模型對應。 -![CLI Tools Dashboard](screenshots/07-cli-tools.png) +![CLI 工具儀表板](../screenshots/07-cli-tools.png) --- -## 🤖 CLI Agents _(v2.0.11+)_ +## 🤖 CLI 代理 _(v2.0.11+)_ -Dashboard for discovering and managing CLI agents. Shows a grid of 17 built-in agents (Codex, Claude, Goose, OpenClaw, Aider, OpenCode, Cline, Qwen Code, ForgeCode, Amazon Q, Open Interpreter, Cursor CLI, Warp, **Windsurf**, **Devin CLI**, **Kimi Coding**, **Command Code**) with: +用於探索與管理 CLI 代理的儀表板。顯示 16 個內建代理的網格(Codex、Claude、Goose、OpenClaw、Aider、OpenCode、Cline、ForgeCode、Amazon Q、Open Interpreter、Cursor CLI、Warp、**Windsurf**、**Devin CLI**、**Kimi Coding**、**Command Code**),包含: -- **Installation status** — Installed / Not Found with version detection -- **Protocol badges** — stdio, HTTP, etc. -- **Custom agents** — Register any CLI tool via form (name, binary, version command, spawn args) -- **CLI Fingerprint Matching** — Per-provider toggle to match native CLI request signatures, reducing ban risk while preserving proxy IP +- **安裝狀態** — 已安裝 / 未找到,含版本偵測 +- **協定徽章** — stdio、HTTP 等 +- **自訂代理** — 透過表單註冊任何 CLI 工具(名稱、二進位檔、版本指令、啟動參數) +- **CLI 指紋比對** — 各供應商開關,用於比對原生 CLI 請求特徵,降低被封風險同時保留代理 IP +- **OAuth 支援代理** — Windsurf 與 Devin CLI 現使用瀏覽器 OAuth 流程進行驗證(v3.8.0+) --- -## 🔗 Context Relay _(v3.5.5+)_ +## 🔗 Context Relay _(v3.5.5+)_ -A combo strategy that preserves session continuity when account rotation happens mid-conversation. Before the active account is exhausted, OmniRoute generates a structured handoff summary in the background. After the next request resolves to a different account, the summary is injected as a system message so the new account continues with full context. +一種 Combo 策略,可在對話中途發生帳戶輪換時保持階段連續性。在目前帳戶額度耗盡前,OmniRoute 會在背景產生結構化的交接摘要。當下一次請求解析到不同帳戶時,該摘要會作為系統訊息注入,使新帳戶能銜接完整上下文。 -Configurable via combo-level or global settings: +可透過 Combo 層級或全域設定調整: -- **Handoff Threshold** — Quota usage percentage that triggers summary generation (default 85%) -- **Max Messages For Summary** — How much recent history to condense -- **Summary Model** — Optional override model for generating the handoff summary +- **交接門檻** — 觸發摘要產生的配額使用百分比(預設 85%) +- **摘要最大訊息數** — 濃縮多少近期對話歷史 +- **摘要模型** — 可選的覆寫模型,用於產生交接摘要 -Currently supports Codex account rotation. See [Context Relay documentation](features/context-relay.md). +目前支援 Codex 帳戶輪換。請參閱 [Context Relay 文件](../architecture/ARCHITECTURE.md)。 --- -## 🛡️ Proxy Hardening _(v3.5.5+)_ +## 🗜️ 提示詞壓縮 _(v3.7.9+)_ -Comprehensive proxy configuration enforcement across the entire request pipeline: +「上下文與快取」現在有專屬頁面顯示 Caveman、RTK 及壓縮組合: -- **Token Health Check** — Background OAuth refresh now resolves proxy config per connection, preventing failures in proxy-required environments -- **API Key Validation** — Provider key validation (`POST /api/providers/validate`) routes through `runWithProxyContext`, honoring provider-level and global proxy settings -- **undici Dispatcher Fix** — Proxy dispatchers use undici's own fetch implementation instead of Node's built-in fetch, resolving `invalid onRequestStart method` errors on Node.js 22 -- **Node.js Version Detection** — Login page proactively detects incompatible Node.js versions (24+) and displays a warning banner with instructions to use Node 22 LTS +- **Caveman** — 語言感知規則包、預覽、輸出模式控制及分析 +- **RTK** — 指令感知壓縮,適用於 shell、git、測試、建置、套件、Docker、基礎設施、JSON 及堆疊追蹤輸出 +- **壓縮組合** — 命名管線(如 `rtk -> caveman`)可指派給路由組合;預設疊加數學平均達到約 **89%**,當兩套引擎都啟用時,可節省 **78–95%** 的合格上下文 +- **原始輸出復原** — 可選的 RTK 脫敏原始輸出指標,用於除錯壓縮失敗 + +請參閱 [壓縮指南](../compression/COMPRESSION_GUIDE.md)、[RTK 壓縮](../compression/RTK_COMPRESSION.md) 及 [壓縮引擎](../compression/COMPRESSION_ENGINES.md)。 --- -## 📧 Email Privacy Masking _(v3.5.6+)_ +## 🛡️ 代理強化 _(v3.5.5+)_ -OAuth account emails are now masked in the provider dashboard (e.g. `di*****@g****.com`) to prevent accidental exposure when sharing screenshots or recording demos. The full email address remains accessible via hover tooltip (`title` attribute). +全面代理設定強制執行,涵蓋整個請求管線: + +- **Token 健康檢查** — 背景 OAuth 重新整理現在會依連線解析代理設定,防止在需要代理的環境中發生失敗 +- **API 金鑰驗證** — 供應商金鑰驗證(`POST /api/providers/validate`)會經由 `runWithProxyContext` 路由,遵循供應商層級與全域代理設定 +- **undici Dispatcher 修正** — 代理 dispatcher 使用 undici 自身的 fetch 實作而非 Node 內建 fetch,解決 Node.js 22 上的 `invalid onRequestStart method` 錯誤 +- **Node.js 版本偵測** — 登入頁面主動偵測不相容的 Node.js 版本(24+),並顯示警告橫幅,提示使用 Node 22 LTS --- -## 👁️ Model Visibility Toggle _(v3.5.6+)_ +## 📧 電子郵件隱私遮罩 _(v3.5.6+)_ -The provider page model list now includes: - -- **Real-time search/filter bar** — Quickly find specific models -- **Per-model visibility toggle** (👁 icon) — Hidden models are grayed out and excluded from the `/v1/models` catalog -- **Active-count badge** (`N/M active`) — Shows at a glance how many models are enabled vs total +OAuth 帳戶電子郵件預設會遮罩(例如 `di*****@g****.com`),防止在分享螢幕截圖或錄製示範時意外暴露。使用「設定 → 外觀 → 帳戶電子郵件可見度」可在供應商、Combo、記錄、配額及測試區等畫面中,全域顯示或隱藏完整帳戶郵件。 --- -## 🔧 OAuth Env Repair _(v3.6.1+)_ +## 👁️ 模型可見度開關 _(v3.5.6+)_ -One-click "Repair env" action for OAuth providers that restores missing environment variables and fixes broken auth state. Accessible from `Dashboard → Providers → [OAuth Provider] → Repair env`. Automatically detects and repairs: +供應商頁面的模型列表現在包含: -- Missing OAuth client credentials -- Corrupted env file entries -- Backup path sanitization +- **即時搜尋/篩選列** — 快速尋找特定模型 +- **各模型可見度開關**(👁 圖示)— 隱藏的模型會變灰,並從 `/v1/models` 目錄中排除 +- **活躍數量徽章**(`N/M 活躍`)— 一目了然顯示啟用模型數量 vs 總數 --- -## 🗑️ Uninstall / Full Uninstall _(v3.6.2+)_ +## 🔧 OAuth 環境修復 _(v3.6.1+)_ -Clean removal scripts for all installation methods: +OAuth 供應商的一鍵「修復環境」功能,可恢復遺失的環境變數並修復受損的驗證狀態。可從「儀表板 → 供應商 → [OAuth 供應商] → 修復環境」進入。自動偵測並修復: -| Command | Action | -| ------------------------ | ----------------------------------------------------------------------------------- | -| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. | -| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. | +- 遺失的 OAuth 客戶端憑證 +- 損毀的 env 檔案條目 +- 備份路徑清理 --- -## 🖼️ Media _(v2.0.3+)_ +## 🗑️ 解除安裝 / 完整解除安裝 _(v3.6.2+)_ -Generate images, videos, and music from the dashboard. Supports OpenAI, xAI, Together, Hyperbolic, SD WebUI, ComfyUI, AnimateDiff, Stable Audio Open, and MusicGen. +所有安裝方式的乾淨移除腳本: + +| 指令 | 動作 | +| ------------------------ | -------------------------------------------------------------------------------- | +| `npm run uninstall` | 移除系統應用程式,但**保留您的資料庫與配置**於 `~/.omniroute` 中。 | +| `npm run uninstall:full` | 移除應用程式,並**永久清除所有配置、金鑰與資料庫**。 | --- -## 📝 Request Logs +## 🖼️ 媒體 _(v2.0.3+)_ -Real-time request logging with filtering by provider, model, account, and API key. Shows status codes, token usage, latency, and response details. - -![Usage Logs](screenshots/08-usage.png) +從儀表板產生圖片、影片及音樂。支援 OpenAI、xAI、Together、Hyperbolic、SD WebUI、ComfyUI、AnimateDiff、Stable Audio Open 及 MusicGen。 --- -## 🌐 API Endpoint +## 📝 請求記錄 -Your unified API endpoint with capability breakdown: Chat Completions, Responses API, Embeddings, Image Generation, Reranking, Audio Transcription, Text-to-Speech, Moderations, and registered API keys. Cloudflare Quick Tunnel integration and cloud proxy support for remote access. +即時請求記錄,可依供應商、模型、帳戶及 API 金鑰篩選。顯示狀態碼、Token 用量、延遲及回應詳細資料。 -![Endpoint Dashboard](screenshots/09-endpoint.png) +![用量記錄](../screenshots/08-usage.png) --- -## 🔑 API Key Management +## 🌐 API 端點 -Create, scope, and revoke API keys. Each key can be restricted to specific models/providers with full access or read-only permissions. Visual key management with usage tracking. +統一的 API 端點,包含功能 breakdown:Chat Completions、Responses API、Embeddings、圖片生成、Reranking、音訊轉錄、文字轉語音、Moderations 及已註冊的 API 金鑰。支援 Cloudflare Quick Tunnel、Tailscale Funnel、ngrok Tunnel 及雲端代理,便於遠端存取。 + +![端點儀表板](../screenshots/09-endpoint.png) --- -## 📋 Audit Log +## 🔑 API 金鑰管理 -Administrative action tracking with filtering by action type, actor, target, IP address, and timestamp. Full security event history. +建立、設定範圍及撤銷 API 金鑰。每個金鑰可限制為特定模型/供應商,並可設定完整存取或唯讀權限。視覺化金鑰管理,附用量追蹤。 --- -## 🖥️ Desktop Application +## 📋 稽核記錄 -Native Electron desktop app for Windows, macOS, and Linux. Run OmniRoute as a standalone application with system tray integration, offline support, auto-update, and one-click install. +管理操作追蹤,可依操作類型、執行者、目標、IP 位址及時間戳篩選。完整安全事件歷史記錄。 -Key features: +--- -- Server readiness polling (no blank screen on cold start) -- System tray with port management +## 🖥️ 桌面應用程式 + +原生 Electron 桌面應用程式,支援 Windows、macOS 及 Linux。將 OmniRoute 作為獨立應用程式執行,包含系統列整合、離線支援、自動更新及一鍵安裝。 + +主要功能: + +- 伺服器就緒輪詢(冷啟動時無白畫面) +- 系統列及埠號管理 - Content Security Policy -- Single-instance lock -- Auto-update on restart -- Platform-conditional UI (macOS traffic lights, Windows/Linux default titlebar) -- Hardened Electron build packaging — symlinked `node_modules` in the standalone bundle is detected and rejected before packaging, preventing runtime dependency on the build machine (v2.5.5+) -- **Graceful shutdown** — Electron `before-quit` shuts down Next.js cleanly, preventing SQLite WAL database locks (v3.6.2+) +- 單實例鎖定 +- 重啟時自動更新 +- 平台條件式 UI(macOS 交通號誌燈、Windows/Linux 預設標題列) +- 強化 Electron 建置打包 — 獨立套件中的符號連結 `node_modules` 會在打包前被偵測並拒絕,防止執行時期依賴建置機器(v2.5.5+) +- **優雅關機** — Electron `before-quit` 會乾淨地關閉 Next.js,防止 SQLite WAL 資料庫鎖定(v3.6.2+) -📖 See [`electron/README.md`](../electron/README.md) for full documentation. +📖 完整文件請參閱 [`electron/README.md`](../../electron/README.md)。 --- -## 🌐 V1 WebSocket Bridge _(v3.6.6+)_ +## 🌐 V1 WebSocket 橋接器 _(v3.6.6+)_ -OmniRoute now supports **OpenAI-compatible WebSocket clients** via the `/v1/ws` upgrade endpoint. The custom `scripts/v1-ws-bridge.mjs` server wraps Next.js and upgrades WS connections to full bidirectional streaming sessions. Authentication uses the same API key or session cookie as HTTP requests. +OmniRoute 現在透過 `/v1/ws` 升級端點支援 **OpenAI 相容的 WebSocket 客戶端**。自訂的 `scripts/dev/v1-ws-bridge.mjs` 伺服器包裝 Next.js,並將 WS 連線升級為完整的雙向串流階段。驗證使用與 HTTP 請求相同的 API 金鑰或階段 Cookie。 -Key behaviours: +主要行為: -- WS upgrade validated by `src/lib/ws/handshake.ts` before the connection is established -- Streams terminated cleanly on session close or upstream error -- Works alongside the existing HTTP+SSE streaming path simultaneously +- WS 升級在連線建立前由 `src/lib/ws/handshake.ts` 驗證 +- 階段關閉或上游錯誤時,串流會乾淨終止 +- 可與現有的 HTTP+SSE 串流路徑同時運作 --- -## 🔑 Sync Tokens & Config Bundle _(v3.6.6+)_ +## 🔑 同步 Token 與配置套件 _(v3.6.6+)_ -Multi-device and external operator access is now possible via **scoped sync tokens**: +現在可透過**限域同步 token** 進行多裝置及外部操作者存取: -- **`POST /api/sync/tokens`** — Issue a new sync token (scoped, with optional expiry) -- **`DELETE /api/sync/tokens/:id`** — Revoke a token -- **`GET /api/sync/bundle`** — Download a versioned, ETag-keyed JSON snapshot of all non-sensitive settings (passwords redacted) +- **`POST /api/sync/tokens`** — 簽發新的同步 token(限域,可選到期時間) +- **`DELETE /api/sync/tokens/:id`** — 撤銷 token +- **`GET /api/sync/bundle`** — 下載所有非敏感設定的版本化、ETag 鍵控 JSON 快照(密碼已脫敏) -The config bundle is built by `src/lib/sync/bundle.ts`. Consumers compare the `ETag` response header to detect changes without re-downloading the full payload. +配置套件由 `src/lib/sync/bundle.ts` 建構。消費者可比對 `ETag` 回應標頭來偵測變更,無需重新下載完整內容。 --- -## 🧠 GLM Thinking Preset _(v3.6.6+)_ +## 🧠 GLM Thinking 預設 _(v3.6.6+)_ -**GLM Thinking (`glmt`)** is now a registered first-class provider: 65 536 max output tokens, 24 576 thinking budget, 900 s default timeout, Claude-compatible API format, and shared usage sync with the GLM family. +**GLM Thinking(`glmt`)** 現已註冊為一級供應商:65,536 最大輸出 token、24,576 思考預算、900 秒預設逾時、Claude 相容 API 格式,及與 GLM 系列的共用用量同步。 -**Hybrid token counting** also lands in v3.6.6: when a Claude-compatible provider exposes `/messages/count_tokens`, OmniRoute calls it before large requests with graceful estimation fallback. +**混合 Token 計數** 也在 v3.6.6 中登場:當 Claude 相容供應商暴露 `/messages/count_tokens` 端點時,OmniRoute 會在大請求前呼叫它,並附帶優雅的估算備援。 --- -## 🛡️ Safe Outbound Fetch & SSRF Guard _(v3.6.6+)_ +## 🛡️ 安全外出擷取與 SSRF 防護 _(v3.6.6+)_ -All provider validation and model discovery calls now go through a two-layer outbound guard: +所有供應商驗證及模型探索呼叫現在都會通過兩層外出防護: -1. **URL guard** (`src/shared/network/outboundUrlGuard.ts`) — Blocks private/loopback/link-local IP ranges before the socket is opened. -2. **Safe fetch wrapper** (`src/shared/network/safeOutboundFetch.ts`) — Applies the URL guard, normalises timeouts, and retries transient errors with exponential backoff. +1. **URL 防護**(`src/shared/network/outboundUrlGuard.ts`)— 在 socket 開啟前封鎖私有/迴路/連結本地 IP 範圍 +2. **安全擷取包裝**(`src/shared/network/safeOutboundFetch.ts`)— 套用 URL 防護、標準化逾時,並以指數退避重試暫時性錯誤 -Guard violations surface as HTTP 422 (`URL_GUARD_BLOCKED`) and are written to the compliance audit log via `providerAudit.ts`. +防護違規會以 HTTP 422(`URL_GUARD_BLOCKED`)呈現,並透過 `providerAudit.ts` 寫入合規稽核記錄。 --- -## 🔄 Cooldown-Aware Retries _(v3.6.6+)_ +## 🔄 冷卻感知重試 _(v3.6.6+)_ -Chat requests now **automatically retry** when an upstream provider returns a model-scoped cooldown. Configurable via `REQUEST_RETRY` (default: 2) and `MAX_RETRY_INTERVAL_SEC` (default: 30 s). Rate-limit header learning improved across `x-ratelimit-reset-requests`, `x-ratelimit-reset-tokens`, and `Retry-After` — per-model cooldown state is visible in the Resilience dashboard. +當上游供應商回傳模型層級冷卻時,聊天請求現在會**自動重試**。可透過 `REQUEST_RETRY`(預設:2)及 `MAX_RETRY_INTERVAL_SEC`(預設:30 秒)設定。速率限制標頭學習已改進,涵蓋 `x-ratelimit-reset-requests`、`x-ratelimit-reset-tokens` 及 `Retry-After`——各模型冷卻狀態可在「抗災能力」儀表板中檢視。 --- -## 📋 Compliance Audit v2 _(v3.6.6+)_ +## 📋 合規稽核 v2 _(v3.6.6+)_ -The audit log has been expanded with cursor-based pagination, request context enrichment (request ID, user agent, IP), structured auth events, provider CRUD events with diff context, and SSRF-blocked validation logging. New events emitted by `src/lib/compliance/providerAudit.ts`. +稽核記錄已擴充,包含游標分頁、請求上下文豐富化(請求 ID、使用者代理、IP)、結構化驗證事件、含差異上下文的供應商 CRUD 事件,以及 SSRF 封鎖驗證記錄。新事件由 `src/lib/compliance/providerAudit.ts` 發送。 diff --git a/docs/i18n/zh-TW/docs/guides/I18N.md b/docs/i18n/zh-TW/docs/guides/I18N.md index 445ddf1e7e..29bd29be3b 100644 --- a/docs/i18n/zh-TW/docs/guides/I18N.md +++ b/docs/i18n/zh-TW/docs/guides/I18N.md @@ -1,90 +1,144 @@ -# i18n — Internationalization Guide (中文 (簡體)) - -🌐 **Languages:** 🇺🇸 [English](../../../../docs/I18N.md) · 🇸🇦 [ar](../../ar/docs/I18N.md) · 🇧🇬 [bg](../../bg/docs/I18N.md) · 🇧🇩 [bn](../../bn/docs/I18N.md) · 🇨🇿 [cs](../../cs/docs/I18N.md) · 🇩🇰 [da](../../da/docs/I18N.md) · 🇩🇪 [de](../../de/docs/I18N.md) · 🇪🇸 [es](../../es/docs/I18N.md) · 🇮🇷 [fa](../../fa/docs/I18N.md) · 🇫🇮 [fi](../../fi/docs/I18N.md) · 🇫🇷 [fr](../../fr/docs/I18N.md) · 🇮🇳 [gu](../../gu/docs/I18N.md) · 🇮🇱 [he](../../he/docs/I18N.md) · 🇮🇳 [hi](../../hi/docs/I18N.md) · 🇭🇺 [hu](../../hu/docs/I18N.md) · 🇮🇩 [id](../../id/docs/I18N.md) · 🇮🇹 [it](../../it/docs/I18N.md) · 🇯🇵 [ja](../../ja/docs/I18N.md) · 🇰🇷 [ko](../../ko/docs/I18N.md) · 🇮🇳 [mr](../../mr/docs/I18N.md) · 🇲🇾 [ms](../../ms/docs/I18N.md) · 🇳🇱 [nl](../../nl/docs/I18N.md) · 🇳🇴 [no](../../no/docs/I18N.md) · 🇵🇭 [phi](../../phi/docs/I18N.md) · 🇵🇱 [pl](../../pl/docs/I18N.md) · 🇵🇹 [pt](../../pt/docs/I18N.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/I18N.md) · 🇷🇴 [ro](../../ro/docs/I18N.md) · 🇷🇺 [ru](../../ru/docs/I18N.md) · 🇸🇰 [sk](../../sk/docs/I18N.md) · 🇸🇪 [sv](../../sv/docs/I18N.md) · 🇰🇪 [sw](../../sw/docs/I18N.md) · 🇮🇳 [ta](../../ta/docs/I18N.md) · 🇮🇳 [te](../../te/docs/I18N.md) · 🇹🇭 [th](../../th/docs/I18N.md) · 🇹🇷 [tr](../../tr/docs/I18N.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/I18N.md) · 🇵🇰 [ur](../../ur/docs/I18N.md) · 🇻🇳 [vi](../../vi/docs/I18N.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/I18N.md) - +--- +title: "i18n — 國際化指南" +version: 3.8.40 +lastUpdated: 2026-06-28 --- -OmniRoute supports **30 languages** with full dashboard UI translation, translated documentation, and RTL support for Arabic and Hebrew. +# i18n — 國際化指南 -## Quick Reference +OmniRoute 支援 **43 種語言**,包含完整的儀表板 UI 翻譯、翻譯文件,以及阿拉伯語和希伯來語的 RTL(從右至左)支援。 -| Task | Command | -| ---------------------- | --------------------------------------------------------------------------------------- | -| Generate translations | `node scripts/i18n/generate-multilang.mjs messages` | -| Translate docs (LLM) | `python3 scripts/i18n_autotranslate.py --api-url --api-key --model ` | -| Validate a locale | `python3 scripts/validate_translation.py quick -l cs` | -| Check code keys | `python3 scripts/check_translations.py` | -| Generate QA report | `node scripts/i18n/generate-qa-checklist.mjs` | -| Visual QA (Playwright) | `node scripts/i18n/run-visual-qa.mjs` | +🌐 **語言:** 🇺🇸 [English](./I18N.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/guides/I18N.md) | 🇪🇸 [Español](../i18n/es/docs/guides/I18N.md) | 🇫🇷 [Français](../i18n/fr/docs/guides/I18N.md) | 🇩🇪 [Deutsch](../i18n/de/docs/guides/I18N.md) | 🇮🇹 [Italiano](../i18n/it/docs/guides/I18N.md) | 🇷🇺 [Русский](../i18n/ru/docs/guides/I18N.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/docs/guides/I18N.md) | 🇯🇵 [日本語](../i18n/ja/docs/guides/I18N.md) | 🇰🇷 [한국어](../i18n/ko/docs/guides/I18N.md) | 🇸🇦 [العربية](../i18n/ar/docs/guides/I18N.md) | 🇮🇳 [हिन्दी](../i18n/hi/docs/guides/I18N.md) | 🇹🇭 [ไทย](../i18n/th/docs/guides/I18N.md) | 🇹🇷 [Türkçe](../i18n/tr/docs/guides/I18N.md) | 🇺🇦 [Українська](../i18n/uk-UA/docs/guides/I18N.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/docs/guides/I18N.md) | 🇧🇬 [Български](../i18n/bg/docs/guides/I18N.md) | 🇩🇰 [Dansk](../i18n/da/docs/guides/I18N.md) | 🇫🇮 [Suomi](../i18n/fi/docs/guides/I18N.md) | 🇮🇱 [עברית](../i18n/he/docs/guides/I18N.md) | 🇭🇺 [Magyar](../i18n/hu/docs/guides/I18N.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/docs/guides/I18N.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/docs/guides/I18N.md) | 🇳🇱 [Nederlands](../i18n/nl/docs/guides/I18N.md) | 🇳🇴 [Norsk](../i18n/no/docs/guides/I18N.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/docs/guides/I18N.md) | 🇷🇴 [Română](../i18n/ro/docs/guides/I18N.md) | 🇵🇱 [Polski](../i18n/pl/docs/guides/I18N.md) | 🇸🇰 [Slovenčina](../i18n/sk/docs/guides/I18N.md) | 🇸🇪 [Svenska](../i18n/sv/docs/guides/I18N.md) | 🇵🇭 [Filipino](../i18n/phi/docs/guides/I18N.md) | 🇨🇿 [Čeština](../i18n/cs/docs/guides/I18N.md) + +## 翻譯管線(建議使用 — v3.8.0) + +OmniRoute 使用基於雜湊的增量翻譯器來處理說明文件,後端採用與 OpenAI 相容的 LLM 端點(通常是透過 OmniRoute Cloud 的 `cx/gpt-5.4-mini`): + +```bash +# 執行翻譯(增量模式 — 僅處理有變更的來源) +npm run i18n:run + +# 限制僅翻譯一個語系 +npm run i18n:run -- --locale=pt-BR + +# 指定檔案(以逗號分隔,使用相對於儲存庫的路徑) +npm run i18n:run -- --files=CLAUDE.md,docs/architecture/ARCHITECTURE.md + +# 強制重新翻譯所有內容(成本較高) +npm run i18n:run -- --force + +# 預覽即將進行的操作(不呼叫 API,不寫入檔案) +npm run i18n:run:dry + +# CI 閘道 — 若狀態偏移則結束碼非零 +npm run i18n:check +``` + +**唯一真相來源。** `config/i18n.json` 列出了每個語系(UI + 文件),以及 RTL 集合和 `docsExcluded` 代碼。執行時期設定檔 `src/i18n/config.ts` 是該 JSON 的輕量轉接層。 + +**後端。** 透過環境變數設定(在 `.env` 中設定,切勿提交): + +| 變數 | 用途 | +| ---------------------------------- | ------------------------------------- | +| `OMNIROUTE_TRANSLATION_API_URL` | 與 OpenAI 相容的基礎 URL,例如 `…/v1` | +| `OMNIROUTE_TRANSLATION_API_KEY` | Bearer 令牌(不會記錄在日誌中) | +| `OMNIROUTE_TRANSLATION_MODEL` | 模型 ID,例如 `cx/gpt-5.4-mini` | +| `OMNIROUTE_TRANSLATION_TIMEOUT_MS` | 選用,預設 `60000` | +| `OMNIROUTE_TRANSLATION_CONCURRENCY`| 選用,預設 `4` | + +**狀態追蹤。** `.i18n-state.json`(已提交)會為每個來源 + 每個語系保留 SHA-256 雜湊值。漂移偵測是自動且確定性的 — `i18n:check` 不需要 API 呼叫。 + +**輸出格式。** 每個翻譯後的檔案會在最上方加入 `# <標題>(<語言>)` 行、`🌐 語言:…` 列、`---` 分隔線,以及翻譯後的正文。此佈局與 `scripts/check/check-docs-sync.mjs` 已對 `llm.txt` 和 `CHANGELOG.md` 鏡像強制執行的格式一致。 + +### 舊版腳本(已棄用) + +較舊的 Python 腳本(`scripts/i18n/i18n_autotranslate.py`)和基於 Google 翻譯的產生器(`scripts/i18n/generate-multilang.mjs`)仍然存在,但附有棄用橫幅。它們將在 v3.10 中移除。`generate-multilang.mjs` 的 `messages` 和 `readme` 模式(UI 字串 + 根目錄 README 變體)尚未由新管線處理,目前仍在使用中。 + +## 快速參考 + +| 任務 | 指令 | +| ------------------------ | ------------------------------------------------------------- | +| 翻譯文件(LLM) | `npm run i18n:run`(建議使用 — 增量、基於雜湊) | +| 翻譯 UI 字串 | `node scripts/i18n/generate-multilang.mjs messages` | +| 檢查翻譯漂移 | `npm run i18n:check` | +| 驗證語系 | `python3 scripts/i18n/validate_translation.py quick -l cs` | +| 檢查程式碼按鍵 | `python3 scripts/i18n/check_translations.py` | +| 產生 QA 報告 | `node scripts/i18n/generate-qa-checklist.mjs` | +| 視覺 QA(Playwright) | `node scripts/i18n/run-visual-qa.mjs` | ## 架構 -### Source of Truth +![增量式基於雜湊的 i18n 管線](../diagrams/exported/i18n-flow.svg) -- **UI strings**: `src/i18n/messages/en.json` (English source, ~2800 keys) -- **Locale files**: `src/i18n/messages/{locale}.json` (30 translations) -- **Framework**: `next-intl` with cookie-based locale resolution -- **Config**: `src/i18n/config.ts` — defines all 30 locales, language names, flags +> 來源:[diagrams/i18n-flow.mmd](../diagrams/i18n-flow.mmd) -### Runtime Flow +### 唯一真相來源 -1. User selects language → `NEXT_LOCALE` cookie set -2. `src/i18n/request.ts` resolves locale: cookie → `Accept-Language` header → fallback `en` -3. Dynamic import loads `messages/{locale}.json` -4. Components use `useTranslations("namespace")` and `t("key")` +- **UI 字串**:`src/i18n/messages/en.json`(英文來源,約 2800 個按鍵) +- **語系檔案**:`src/i18n/messages/{locale}.json`(30 種翻譯) +- **框架**:`next-intl`,搭配基於 Cookie 的語系解析 +- **設定**:`src/i18n/config.ts` — 定義全部 30 個語系、語言名稱、旗標 -### Supported Locales +### 執行時期流程 -| Code | Language | RTL | Google Translate Code | -| ------- | -------------------- | --- | --------------------- | -| `ar` | العربية | Yes | `ar` | -| `bg` | Български | No | `bg` | -| `cs` | Čeština | No | `cs` | -| `da` | Dansk | No | `da` | -| `de` | Deutsch | No | `de` | -| `es` | Español | No | `es` | -| `fi` | Suomi | No | `fi` | -| `fr` | Français | No | `fr` | -| `he` | עברית | Yes | `iw` | -| `hi` | हिन्दी | No | `hi` | -| `hu` | Magyar | No | `hu` | -| `id` | Bahasa Indonesia | No | `id` | -| `it` | Italiano | No | `it` | -| `ja` | 日本語 | No | `ja` | -| `ko` | 한국어 | No | `ko` | -| `ms` | Bahasa Melayu | No | `ms` | -| `nl` | Nederlands | No | `nl` | -| `no` | Norsk | No | `no` | -| `phi` | Filipino | No | `tl` | -| `pl` | Polski | No | `pl` | -| `pt` | Português (Portugal) | No | `pt` | -| `pt-BR` | Português (Brasil) | No | `pt` | -| `ro` | Română | No | `ro` | -| `ru` | Русский | No | `ru` | -| `sk` | Slovenčina | No | `sk` | -| `sv` | Svenska | No | `sv` | -| `th` | ไทย | No | `th` | -| `tr` | Türkçe | No | `tr` | -| `uk-UA` | Українська | No | `uk` | -| `vi` | Tiếng Việt | No | `vi` | -| `zh-CN` | 中文 (簡體) | No | `zh-CN` | +1. 使用者選擇語言 → 設定 `NEXT_LOCALE` Cookie +2. `src/i18n/request.ts` 解析語系:Cookie → `Accept-Language` 標頭 → 備用 `en` +3. 動態匯入載入 `messages/{locale}.json` +4. 元件使用 `useTranslations("namespace")` 和 `t("key")` -## Adding a New Language +### 支援的語系 -### 1. Register the Locale +| 代碼 | 語言 | RTL | Google 翻譯代碼 | +| -------- | ---------------------- | --- | --------------- | +| `ar` | العربية | 是 | `ar` | +| `bg` | Български | 否 | `bg` | +| `cs` | Čeština | 否 | `cs` | +| `da` | Dansk | 否 | `da` | +| `de` | Deutsch | 否 | `de` | +| `es` | Español | 否 | `es` | +| `fi` | Suomi | 否 | `fi` | +| `fr` | Français | 否 | `fr` | +| `he` | עברית | 是 | `iw` | +| `hi` | हिन्दी | 否 | `hi` | +| `hu` | Magyar | 否 | `hu` | +| `id` | Bahasa Indonesia | 否 | `id` | +| `it` | Italiano | 否 | `it` | +| `ja` | 日本語 | 否 | `ja` | +| `ko` | 한국어 | 否 | `ko` | +| `ms` | Bahasa Melayu | 否 | `ms` | +| `nl` | Nederlands | 否 | `nl` | +| `no` | Norsk | 否 | `no` | +| `phi` | Filipino | 否 | `tl` | +| `pl` | Polski | 否 | `pl` | +| `pt` | Português (Portugal) | 否 | `pt` | +| `pt-BR` | Português (Brasil) | 否 | `pt` | +| `ro` | Română | 否 | `ro` | +| `ru` | Русский | 否 | `ru` | +| `sk` | Slovenčina | 否 | `sk` | +| `sv` | Svenska | 否 | `sv` | +| `th` | ไทย | 否 | `th` | +| `tr` | Türkçe | 否 | `tr` | +| `uk-UA` | Українська | 否 | `uk` | +| `vi` | Tiếng Việt | 否 | `vi` | +| `zh-CN` | 中文 (简体) | 否 | `zh-CN` | +| `zh-TW` | 中文 (繁體) | 否 | `zh-TW` | -Edit `src/i18n/config.ts`: +## 新增語言 + +### 1. 註冊語系 + +編輯 `src/i18n/config.ts`: ```ts -// Add to LOCALES array +// 新增至 LOCALES 陣列 "xx", -// Add to LANGUAGES array +// 新增至 LANGUAGES 陣列 { code: "xx", label: "XX", name: "Language Name", flag: "🏳️" }, ``` -### 2. Add to Generator +### 2. 新增至產生器 -Edit `scripts/i18n/generate-multilang.mjs` — add entry to `LOCALE_SPECS`: +編輯 `scripts/i18n/generate-multilang.mjs` — 在 `LOCALE_SPECS` 中新增項目: ```js { @@ -98,344 +152,414 @@ Edit `scripts/i18n/generate-multilang.mjs` — add entry to `LOCALE_SPECS`: }, ``` -### 3. Generate Initial Translation +### 3. 產生初始翻譯 ```bash node scripts/i18n/generate-multilang.mjs messages ``` -This creates `src/i18n/messages/xx.json` auto-translated from `en.json` via Google Translate. +這會從 `en.json` 透過 Google 翻譯自動翻譯,建立 `src/i18n/messages/xx.json`。 -### 4. Review & Fix Auto-Translations +### 4. 審查與修正自動翻譯 -Auto-translations are a starting point. Review manually for: +自動翻譯僅是起點。請手動檢查以下項目: -- Technical accuracy -- Context-appropriate terminology -- Proper handling of placeholders (`{count}`, `{value}`, etc.) +- 技術準確性 +- 符合語境的術語用法 +- 佔位符號(`{count}`、`{value}` 等)的正確處理 -### 5. Validate +### 5. 驗證 ```bash -python3 scripts/validate_translation.py quick -l xx -python3 scripts/validate_translation.py diff common -l xx +python3 scripts/i18n/validate_translation.py quick -l xx +python3 scripts/i18n/validate_translation.py diff common -l xx ``` -### 6. Generate Translated Documentation +### 6. 產生翻譯文件 ```bash node scripts/i18n/generate-multilang.mjs docs ``` -## Auto-Translation Pipeline +## 自動翻譯管線 -### generate-multilang.mjs (Google Translate) +### generate-multilang.mjs(Google 翻譯) -**Primary auto-translation engine** — uses Google Translate free API to generate translations for UI strings, READMEs, and documentation. +**主要自動翻譯引擎** — 使用 Google 翻譯免費 API 為 UI 字串、README 和說明文件產生翻譯。 ```bash node scripts/i18n/generate-multilang.mjs [messages|readme|docs|all] ``` -| Mode | What it does | -| ---------- | ----------------------------------------------------------------------------- | -| `messages` | Translates missing keys in `src/i18n/messages/{locale}.json` from `en.json` | -| `readme` | Translates `README.md` into all locales as `README.{code}.md` in project root | -| `docs` | Translates `DOC_SOURCE_FILES` into `docs/i18n/{locale}/{docName}` | -| `all` | Runs all three modes | +| 模式 | 功能說明 | +| ---------- | ------------------------------------------------------------------------------- | +| `messages` | 從 `en.json` 翻譯 `src/i18n/messages/{locale}.json` 中遺漏的按鍵 | +| `readme` | 將 `README.md` 翻譯為專案根目錄下的 `README.{code}.md` 所有語系版本 | +| `docs` | 將 `DOC_SOURCE_FILES` 翻譯為 `docs/i18n/{locale}/{docName}` | +| `all` | 執行上述三種模式 | -**Features:** +**功能特色:** -- **Text protection**: Masks code blocks (` ``` `), inline code (`` ` ``), markdown links/images (`[text](url)`), HTML tags, tables, and ICU placeholders (`{count}`, `{value}`, `{total}`, etc.) before translation, then restores them -- **Chunked batching**: Joins multiple strings with `__OMNIROUTE_I18N_SEPARATOR__` delimiters to minimize API calls (max 1800 chars per request) -- **In-memory cache**: Avoids redundant API calls for repeated strings within a session -- **Retry logic**: Exponential backoff (up to 5 attempts with 300ms × attempt delay) for 429/5xx errors -- **Timeout**: 20 seconds per request -- **Skip existing**: If target file already exists, it is NOT overwritten +- **文字保護**:在翻譯前遮罩程式碼區塊(` ``` `)、行內程式碼(`` ` ``)、Markdown 連結/圖片(`[text](url)`)、HTML 標籤、表格和 ICU 佔位符號(`{count}`、`{value}`、`{total}` 等),翻譯後再還原 +- **分塊批次處理**:使用 `__OMNIROUTE_I18N_SEPARATOR__` 分隔符號將多個字串連接起來,以減少 API 呼叫次數(每個請求最多 1800 字元) +- **記憶體快取**:避免在單一工作階段內對重複字串進行多餘的 API 呼叫 +- **重試邏輯**:針對 429/5xx 錯誤採用指數退避(最多 5 次嘗試,延遲時間為 300ms × 嘗試次數) +- **超時**:每個請求 20 秒 +- **略過現有檔案**:若目標檔案已存在,則不會覆寫 -**Important behaviors:** +**重要行為:** -- `docs/i18n/README.md` is **regenerated** each run — it's an auto-generated index of all docs -- Root `README.{code}.md` files are only created if they don't exist (skips locales in `EXISTING_README_CODES`) -- Language bars (`🌐 **Languages:** ...`) are automatically inserted/updated in all translated docs +- `docs/i18n/README.md` 每次執行時都會**重新產生** — 這是所有說明的自動產生索引 +- 根目錄的 `README.{code}.md` 僅在不存在時才會建立(略過 `EXISTING_README_CODES` 中的語系) +- 語言列(`🌐 **語言:** ...`)會自動插入/更新至所有翻譯文件中 -### i18n_autotranslate.py (LLM-based) +### i18n_autotranslate.py(LLM 基礎) -**Secondary translator** — uses any OpenAI-compatible LLM API (including OmniRoute itself) to translate existing `docs/i18n/` markdown files. Best for polishing or re-translating docs with better quality than Google Translate. +**次要翻譯器** — 使用任何與 OpenAI 相容的 LLM API(包括 OmniRoute 本身)來翻譯現有的 `docs/i18n/` Markdown 檔案。最適合用於潤飾或重新翻譯文件,品質優於 Google 翻譯。 ```bash -python3 scripts/i18n_autotranslate.py \ +python3 scripts/i18n/i18n_autotranslate.py \ --api-url http://localhost:20128/v1 \ --api-key sk-your-key \ --model gpt-4o ``` -**Features:** +**功能特色:** -- Scans `docs/i18n/` markdown files for English paragraphs -- Skips code blocks, tables, and already-translated content -- Sends paragraphs to LLM with technical translation system prompt -- Supports all 30 languages +- 掃描 `docs/i18n/` 中的 Markdown 檔案以取得英文段落 +- 略過程式碼區塊、表格和已翻譯的內容 +- 將段落發送給 LLM,並附帶技術翻譯系統提示 +- 支援全部 43 種語言 -## Validation & QA +## CLI i18n + +`omniroute` CLI 有獨立於 Next.js 儀表板之外的 i18n 層。 + +### 運作方式 + +- CLI 指令中所有使用者可見的字串均透過 `t("module.key", vars)`(來自 `bin/cli/i18n.mjs`)處理 +- 語系目錄是 `bin/cli/locales/` 中的 JSON 檔案 — 內建 43 種語言 +- 任何遺漏的按鍵都會自動回退到 `en`,因此部分翻譯仍然有效 +- 可用語系的唯一真相來源是 `config/i18n.json`(與儀表板共用) + +### 語系選擇 + +偵測順序(第一個符合者優先): + +| 優先順序 | 來源 | 範例 | +| -------- | ------------------------ | ------------------------------------------ | +| 1 | `--lang` 旗標 | `omniroute --lang de status` | +| 2 | `OMNIROUTE_LANG` 環境變數| `OMNIROUTE_LANG=ja omniroute providers` | +| 3 | `LC_ALL` 系統環境變數 | 自動從終端機語系偵測 | +| 4 | `LC_MESSAGES` 系統環境變數| 自動從終端機語系偵測 | +| 5 | `LANG` 系統環境變數 | 自動從終端機語系偵測 | +| 6 | 備用 | `en` | + +底線形式的語系代碼(`pt_BR`)會標準化為連字號形式(`pt-BR`)。 +語系代碼會經由 `/^[a-zA-Z0-9-]+$/` 驗證 — 拒絕路徑遍歷攻擊。 + +### 儲存語言偏好 + +```bash +# 設定語言並儲存至 ~/.omniroute/.env(跨工作階段持續有效) +omniroute config lang set pt-BR + +# 檢視目前語言 +omniroute config lang get + +# 列出全部 42 種可用語言 +omniroute config lang list + +# JSON 輸出 +omniroute config lang list --output json +``` + +儲存的偏好設定會以原子方式寫入 `~/.omniroute/.env`,並在 CLI 啟動時、任何指令執行前載入。 + +### 單次覆寫 + +```bash +# 僅為單一指令覆寫(不持續保留) +omniroute --lang de providers list +``` + +注意:`--lang` 旗標**不會**寫入環境變數檔案 — 它僅影響當次呼叫。請使用 `config lang set` 來持續保留設定。 + +### 可用語系 + +`bin/cli/locales/` 中內建 43 個語系檔案。完整翻譯:`en`、`pt-BR`。 +僅有骨架(所有按鍵回退至 `en`):`bn`、`gu`、`he`、`in`、`mr`、`ms`、`phi`、`sw`、`ta`、`te`、`ur`。 +其他 29 個語系均包含 `common` + `program` 按鍵的翻譯。 + +### 新增 CLI 語系 + +1. 在 `config/i18n.json` 中新增語系項目。 +2. 執行 `node bin/cli/scripts/generate-locales.mjs` — 建立語系檔案。 +3. 翻譯按鍵(或保留為 `{}` 以使用 en 回退骨架)。 +4. PR 必須將字串新增至 `en.json` 和 `pt-BR.json`;其他檔案則盡力而為。 + +## 驗證與 QA ### validate_translation.py -**Translation validator** — compares any locale JSON against `en.json` and reports issues. +**翻譯驗證器** — 將任何語系 JSON 與 `en.json` 進行比較,並回報問題。 ```bash -# Quick check (counts only) -python3 scripts/validate_translation.py quick -l cs -# Output: +# 快速檢查(僅計數) +python3 scripts/i18n/validate_translation.py quick -l cs +# 輸出: # Missing: 0 # Untranslated: 0 # Ignored (UNTRANSLATABLE_KEYS): 236 -# Detailed diff by category -python3 scripts/validate_translation.py diff common -l cs -python3 scripts/validate_translation.py diff settings -l cs +# 依分類詳細差異 +python3 scripts/i18n/validate_translation.py diff common -l cs +python3 scripts/i18n/validate_translation.py diff settings -l cs -# Export to CSV -python3 scripts/validate_translation.py csv -l cs > report.csv +# 匯出至 CSV +python3 scripts/i18n/validate_translation.py csv -l cs > report.csv -# Export to Markdown -python3 scripts/validate_translation.py md -l cs > report.md +# 匯出至 Markdown +python3 scripts/i18n/validate_translation.py md -l cs > report.md -# Full report (default) -python3 scripts/validate_translation.py -l cs +# 完整報告(預設) +python3 scripts/i18n/validate_translation.py -l cs ``` -**Detects:** +**可偵測的項目:** -- **Missing keys** — keys in `en.json` but not in locale file -- **Extra keys** — keys in locale file but not in `en.json` -- **Untranslated keys** — keys where locale value equals English source (excluding allowlist) -- **Placeholder mismatches** — ICU placeholders that don't match between source and translation +- **遺漏的按鍵** — `en.json` 中存在,但語系檔案中沒有的按鍵 +- **多餘的按鍵** — 語系檔案中存在,但 `en.json` 中沒有的按鍵 +- **未翻譯的按鍵** — 語系值與英文來源相同的按鍵(排除允許清單) +- **佔位符號不匹配** — 來源與翻譯之間 ICU 佔位符號不一致 -**Exit codes:** -| Code | Meaning | -|------|---------| -| 0 | OK | -| 1 | Generic error | -| 2 | Missing strings (hard error) | -| 3 | Untranslated warning (soft) | +**結束代碼:** -**Environment:** Set `TRANSLATION_LANG=cs` or use `-l cs` flag. +| 代碼 | 含義 | +| ---- | ----------------------------- | +| 0 | 正常 | +| 1 | 一般錯誤 | +| 2 | 遺漏字串(嚴重錯誤) | +| 3 | 未翻譯警告(軟性錯誤) | + +**環境:** 設定 `TRANSLATION_LANG=cs` 或使用 `-l cs` 旗標。 ### check_translations.py -**Code-to-JSON key checker** — scans `src/**/*.tsx` and `src/**/*.ts` for `useTranslations()` calls and verifies all referenced keys exist in `en.json`. +**程式碼對 JSON 按鍵檢查器** — 掃描 `src/**/*.tsx` 和 `src/**/*.ts` 中的 `useTranslations()` 呼叫,並驗證所有被參考的按鍵都存在於 `en.json` 中。 ```bash -# Basic check -python3 scripts/check_translations.py +# 基本檢查 +python3 scripts/i18n/check_translations.py -# Verbose output -python3 scripts/check_translations.py --verbose +# 詳細輸出 +python3 scripts/i18n/check_translations.py --verbose -# Auto-fix (adds missing keys to en.json) -python3 scripts/check_translations.py --fix +# 自動修正(將遺漏的按鍵新增至 en.json) +python3 scripts/i18n/check_translations.py --fix ``` ### generate-qa-checklist.mjs -**Static analysis QA** — scans Next.js page files for i18n risk metrics and generates a Markdown report. +**靜態分析 QA** — 掃描 Next.js 頁面檔案中與 i18n 相關的風險指標,並產生 Markdown 報告。 ```bash node scripts/i18n/generate-qa-checklist.mjs ``` -**Checks:** +**檢查項目:** -- Fixed-width class usage (overflow risk) -- Directional left/right classes (RTL risk) -- Clipping-prone patterns -- Locale parity (missing/extra keys vs `en.json`) -- README language selector bars in priority locales (`es`, `fr`, `de`, `ja`, `ar`) +- 固定寬度類別的使用(溢位風險) +- 方向性 left/right 類別(RTL 風險) +- 易裁剪的模式 +- 語系一致性(與 `en.json` 相比遺漏/多餘的按鍵) +- 優先語系(`es`、`fr`、`de`、`ja`、`ar`)的 README 語言選擇器列 -**Output:** `docs/reports/i18n-qa-checklist-{date}.md` +**輸出:** `docs/reports/i18n-qa-checklist-{date}.md` ### run-visual-qa.mjs -**Visual QA via Playwright** — takes screenshots of all dashboard routes in multiple locales and viewports, then evaluates page health. +**透過 Playwright 進行視覺 QA** — 在多個語系和檢視區間對所有儀表板路由進行螢幕截圖,然後評估頁面健康狀況。 ```bash -# Default: es, fr, de, ja, ar on localhost:20128 +# 預設:在 localhost:20128 上使用 es、fr、de、ja、ar node scripts/i18n/run-visual-qa.mjs -# Custom base URL and locales +# 自訂基礎 URL 和語系 QA_BASE_URL=http://staging.example.com QA_LOCALES=de,fr node scripts/i18n/run-visual-qa.mjs -# Custom routes +# 自訂路由 QA_ROUTES=/dashboard/settings,/dashboard/providers node scripts/i18n/run-visual-qa.mjs ``` -**Detects:** +**可偵測的項目:** -- Text overflow -- Element clipping -- RTL layout mismatches +- 文字溢位 +- 元素裁剪 +- RTL 佈局不一致 -**Output:** `docs/reports/i18n-visual-qa-{date}.md` + JSON report +**輸出:** `docs/reports/i18n-visual-qa-{date}.md` + JSON 報告 -## Managing Untranslatable Keys +## 管理不可翻譯的按鍵 ### untranslatable-keys.json -**File:** `scripts/i18n/untranslatable-keys.json` +**檔案:** `scripts/i18n/untranslatable-keys.json` -Allowlist of keys that should remain identical to English source. Used by `validate_translation.py` to avoid false-positive "untranslated" warnings. +允許清單,列出應與英文來源保持一致的按鍵。由 `validate_translation.py` 用來避免「未翻譯」的誤報警告。 ```json { - "description": "Keys that should remain untranslated...", + "description": "應保持不翻譯的按鍵…", "keys": [ "common.model", "common.oauth", "health.cpu", - ... + … ] } ``` -**What belongs here:** +**應歸類於此的項目:** -- Brand/product names: `landing.brandName`, `common.social-github` -- Technical terms/acronyms: `health.cpu`, `mcpDashboard.pid`, `settings.ai` -- ICU/format strings: `apiManager.modelsCount`, `health.millisecondsShort` -- Placeholder values: `providers.openaiBaseUrlPlaceholder`, `cliTools.baseUrlPlaceholder` -- Protocol names: `common.http`, `common.oauth`, `providers.oauth2Label` -- Navigation sections: `sidebar.primarySection`, `sidebar.cliSection` +- 品牌/產品名稱:`landing.brandName`、`common.social-github` +- 技術術語/縮寫:`health.cpu`、`mcpDashboard.pid`、`settings.ai` +- ICU/格式字串:`apiManager.modelsCount`、`health.millisecondsShort` +- 佔位符號值:`providers.openaiBaseUrlPlaceholder`、`cliTools.baseUrlPlaceholder` +- 協定名稱:`common.http`、`common.oauth`、`providers.oauth2Label` +- 導航區段:`sidebar.primarySection`、`sidebar.cliSection` -**To add a key:** Edit the `keys` array in `scripts/i18n/untranslatable-keys.json` and re-run validation. +**若要新增按鍵:** 編輯 `scripts/i18n/untranslatable-keys.json` 中的 `keys` 陣列,然後重新執行驗證。 -## CI Integration +## CI 整合 -### GitHub Actions (`.github/workflows/ci.yml`) +### GitHub Actions(`.github/workflows/ci.yml`) -The CI pipeline validates all locales on every push and PR: +CI 管線會在每次推送和 PR 時驗證所有語系: -1. **`i18n-matrix` job** — dynamically discovers all locale files (excluding `en.json`) -2. **`i18n` job** — runs `validate_translation.py quick -l ''` for each locale in parallel -3. **`ci-summary` job** — aggregates results into a dashboard summary +1. **`i18n-matrix` 任務** — 動態發現所有語系檔案(排除 `en.json`) +2. **`i18n` 任務** — 對每個語系平行執行 `validate_translation.py quick -l ''` +3. **`ci-summary` 任務** — 將結果彙整為儀表板摘要 ```yaml -# i18n-matrix: discovers languages +# i18n-matrix:發現語言 LANGS=$(ls src/i18n/messages/*.json | xargs -n1 basename | sed 's/.json$//' | grep -v '^en$') -# i18n: validates each language -python3 scripts/validate_translation.py quick -l '${{ matrix.lang }}' +# i18n:驗證每種語言 +python3 scripts/i18n/validate_translation.py quick -l '${{ matrix.lang }}' ``` -**Dashboard output:** +**儀表板輸出:** ``` -## 🌍 Translations -| Metric | Value | +## 🌍 翻譯 +| 指標 | 數值 | |--------|------| -| Languages checked | 30 | -| Total untranslated | 0 | +| 已檢查語言數 | 30 | +| 未翻譯總數 | 0 | -✅ All translations complete +✅ 所有翻譯皆完整 ``` -## File Structure +## 檔案結構 ``` src/i18n/ -├── config.ts # Locale definitions (30 locales, RTL config) -├── request.ts # Runtime locale resolution +├── config.ts # 語系定義(30 個語系、RTL 設定) +├── request.ts # 執行時期語系解析 └── messages/ - ├── en.json # Source of truth (~2800 keys) - ├── cs.json # Czech translation - ├── de.json # German translation - └── ... # 30 locale files total + ├── en.json # 唯一真相來源(約 2800 個按鍵) + ├── cs.json # 捷克文翻譯 + ├── de.json # 德文翻譯 + └── ... # 共 30 個語系檔案 scripts/ ├── i18n/ -│ ├── generate-multilang.mjs # Auto-translation engine (Google Translate, 888 lines) -│ ├── generate-qa-checklist.mjs # Static analysis QA -│ ├── run-visual-qa.mjs # Playwright visual QA -│ └── untranslatable-keys.json # Allowlist for validation (236 keys) -├── validate_translation.py # Translation validator -├── check_translations.py # Code-to-JSON key checker -└── i18n_autotranslate.py # LLM-based doc translator +│ ├── generate-multilang.mjs # 自動翻譯引擎(Google 翻譯,888 行) +│ ├── generate-qa-checklist.mjs # 靜態分析 QA +│ ├── run-visual-qa.mjs # Playwright 視覺 QA +│ └── untranslatable-keys.json # 驗證允許清單(236 個按鍵) +├── validate_translation.py # 翻譯驗證器 +├── check_translations.py # 程式碼對 JSON 按鍵檢查器 +└── i18n_autotranslate.py # 基於 LLM 的文件翻譯器 .github/workflows/ -└── ci.yml # i18n validation in CI matrix +└── ci.yml # CI 矩陣中的 i18n 驗證 docs/ -├── I18N.md # This file — i18n toolchain documentation +├── I18N.md # 本檔案 — i18n 工具鏈文件 ├── i18n/ -│ ├── README.md # Auto-generated language index -│ ├── cs/ # Czech docs +│ ├── README.md # 自動產生的語言索引 +│ ├── cs/ # 捷克文文件 │ │ └── docs/ -│ │ ├── I18N.md # Czech translation of this file +│ │ ├── I18N.md # 本檔案的捷克文翻譯 │ │ └── ... -│ ├── de/ # German docs -│ └── ... # 30 locale directories +│ ├── de/ # 德文文件 +│ └── ... # 30 個語系目錄 └── reports/ - ├── i18n-qa-checklist-*.md # Static analysis reports - └── i18n-visual-qa-*.md # Visual QA reports + ├── i18n-qa-checklist-*.md # 靜態分析報告 + └── i18n-visual-qa-*.md # 視覺 QA 報告 ``` -## Best Practices +## 最佳實踐 -### When Editing Translations +### 編輯翻譯時 -1. **Always edit `en.json` first** — it's the source of truth -2. **Run `generate-multilang.mjs messages`** to propagate new keys to all locales -3. **Review auto-translations** — Google Translate is a starting point, not final -4. **Validate before committing** — `python3 scripts/validate_translation.py quick -l ` -5. **Update `untranslatable-keys.json`** if a key should remain in English +1. **始終先編輯 `en.json`** — 這是唯一真相來源 +2. **執行 `generate-multilang.mjs messages`** 以將新按鍵傳播至所有語系 +3. **審查自動翻譯** — Google 翻譯是起點,而非終點 +4. **提交前先驗證** — `python3 scripts/i18n/validate_translation.py quick -l ` +5. **更新 `untranslatable-keys.json`** — 若某個按鍵應保持為英文 -### Placeholder Safety +### 佔位符號安全性 -- ICU placeholders (`{count}`, `{value}`, `{total}`, `{seconds}`) must be preserved exactly -- Plural formats (`{count, plural, one {# model} other {# models}}`) must maintain structure -- The validator detects placeholder mismatches automatically +- ICU 佔位符號(`{count}`、`{value}`、`{total}`、`{seconds}`)必須保持原樣 +- 複數格式(`{count, plural, one {# model} other {# models}}`)必須維持結構 +- 驗證器會自動偵測佔位符號不匹配 -### Adding New Translation Keys in Code +### 在程式碼中新增翻譯按鍵 ```tsx -// Use namespaced keys +// 使用命名空間按鍵 const t = useTranslations("settings"); -t("cacheSettings"); // maps to settings.cacheSettings in JSON +t("cacheSettings"); // 對應 JSON 中的 settings.cacheSettings -// Run check_translations.py to verify keys exist -python3 scripts/check_translations.py --verbose +// 執行 check_translations.py 以驗證按鍵是否存在 +python3 scripts/i18n/check_translations.py --verbose ``` -### RTL Considerations +### RTL 考量 -- Arabic (`ar`) and Hebrew (`he`) are RTL locales -- Avoid hardcoded `left`/`right` CSS — use `start`/`end` logical properties -- Visual QA catches RTL layout mismatches via `run-visual-qa.mjs` +- 阿拉伯語(`ar`)和希伯來語(`he`)是 RTL 語系 +- 避免硬編碼 `left`/`right` CSS — 使用 `start`/`end` 邏輯屬性 +- 視覺 QA 透過 `run-visual-qa.mjs` 捕捉 RTL 佈局不一致 -## Known Issues & History +## 已知問題與歷史記錄 -### `in.json` → `hi.json` Fix +### `in.json` → `hi.json` 修正 -The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This created an orphaned `in.json` duplicate of `hi.json`. Fixed by changing `code: "in"` to `code: "hi"` in `generate-multilang.mjs` and removing the orphaned file. +產生器原本使用 `code: "in"`(已棄用的 Google 翻譯代碼)來表示印度語,而不是正確的 ISO 639-1 `hi`。這建立了一個孤立的 `in.json` 檔案作為 `hi.json` 的重複。修正方式為將 `generate-multilang.mjs` 中的 `code: "in"` 改為 `code: "hi"`,並移除孤立的檔案。 -### `docs/i18n/README.md` Is Auto-Generated +> ⚠️ **稽核備註(2026-05-13):** `docs/i18n/in/` 目錄仍然存在於磁碟上(是 `hi/` 的完整重複)。翻譯產生器不再寫入該目錄,但歷史留存目錄未被清理。確認沒有外部連結參考舊路徑後,可使用 `rm -rf docs/i18n/in/` 安全刪除。 -The `docs/i18n/README.md` file is completely regenerated by `generate-multilang.mjs docs`. Any manual edits will be lost. Use `docs/guides/I18N.md` (this file) for hand-written documentation that should persist. +### `docs/i18n/README.md` 為自動產生 -### External Untranslatable Keys List +`docs/i18n/README.md` 檔案會由 `generate-multilang.mjs docs` 完全重新產生。任何手動編輯都會遺失。請使用 `docs/guides/I18N.md`(本檔案)來存放應保留的手寫文件。 -The `untranslatable-keys.json` allowlist was moved from an inline Python set in `validate_translation.py` to an external JSON file for easier maintenance. The validator loads it at runtime. +### 外部不可翻譯按鍵清單 -### `generate-multilang.mjs` Hindi Code Fix +`untranslatable-keys.json` 允許清單已從 `validate_translation.py` 中的行內 Python 集合移至外部 JSON 檔案,以便於維護。驗證器會在執行時期載入該檔案。 -The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This was introduced in upstream commit `952b0b22c` by `diegosouzapw`. Fixed by changing `code: "in"` to `code: "hi"` in the `LOCALE_SPECS` array and removing the orphaned `in.json` file. +### `generate-multilang.mjs` 印度語代碼修正 -### `validate_translation.py` Ignored Count Output +產生器原本使用 `code: "in"`(已棄用的 Google 翻譯代碼)來表示印度語,而不是正確的 ISO 639-1 `hi`。此問題由 `diegosouzapw` 在上游提交 `952b0b22c` 中引入。修正方式為將 `LOCALE_SPECS` 陣列中的 `code: "in"` 改為 `code: "hi"`,並移除孤立的 `in.json` 檔案。 -The `quick` check now displays the count of ignored keys from `untranslatable-keys.json`: +### `validate_translation.py` 忽略計數輸出 + +`quick` 檢查現在會顯示來自 `untranslatable-keys.json` 的忽略按鍵計數: ``` Missing: 0 Untranslated: 0 -Ignored (UNTRANSLATABLE_KEYS): 236 +Ignored (UNTRANSLATABLE_KEYS): <隨版本而異> ``` diff --git a/docs/i18n/zh-TW/docs/guides/TROUBLESHOOTING.md b/docs/i18n/zh-TW/docs/guides/TROUBLESHOOTING.md index 2a1d9dd720..3b9ccbdcf8 100644 --- a/docs/i18n/zh-TW/docs/guides/TROUBLESHOOTING.md +++ b/docs/i18n/zh-TW/docs/guides/TROUBLESHOOTING.md @@ -1,72 +1,150 @@ -# Troubleshooting (中文 (簡體)) +--- +title: "疑難排解" +version: 3.8.49 +lastUpdated: 2026-07-15 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/TROUBLESHOOTING.md) · 🇸🇦 [ar](../../ar/docs/TROUBLESHOOTING.md) · 🇧🇬 [bg](../../bg/docs/TROUBLESHOOTING.md) · 🇧🇩 [bn](../../bn/docs/TROUBLESHOOTING.md) · 🇨🇿 [cs](../../cs/docs/TROUBLESHOOTING.md) · 🇩🇰 [da](../../da/docs/TROUBLESHOOTING.md) · 🇩🇪 [de](../../de/docs/TROUBLESHOOTING.md) · 🇪🇸 [es](../../es/docs/TROUBLESHOOTING.md) · 🇮🇷 [fa](../../fa/docs/TROUBLESHOOTING.md) · 🇫🇮 [fi](../../fi/docs/TROUBLESHOOTING.md) · 🇫🇷 [fr](../../fr/docs/TROUBLESHOOTING.md) · 🇮🇳 [gu](../../gu/docs/TROUBLESHOOTING.md) · 🇮🇱 [he](../../he/docs/TROUBLESHOOTING.md) · 🇮🇳 [hi](../../hi/docs/TROUBLESHOOTING.md) · 🇭🇺 [hu](../../hu/docs/TROUBLESHOOTING.md) · 🇮🇩 [id](../../id/docs/TROUBLESHOOTING.md) · 🇮🇹 [it](../../it/docs/TROUBLESHOOTING.md) · 🇯🇵 [ja](../../ja/docs/TROUBLESHOOTING.md) · 🇰🇷 [ko](../../ko/docs/TROUBLESHOOTING.md) · 🇮🇳 [mr](../../mr/docs/TROUBLESHOOTING.md) · 🇲🇾 [ms](../../ms/docs/TROUBLESHOOTING.md) · 🇳🇱 [nl](../../nl/docs/TROUBLESHOOTING.md) · 🇳🇴 [no](../../no/docs/TROUBLESHOOTING.md) · 🇵🇭 [phi](../../phi/docs/TROUBLESHOOTING.md) · 🇵🇱 [pl](../../pl/docs/TROUBLESHOOTING.md) · 🇵🇹 [pt](../../pt/docs/TROUBLESHOOTING.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/TROUBLESHOOTING.md) · 🇷🇴 [ro](../../ro/docs/TROUBLESHOOTING.md) · 🇷🇺 [ru](../../ru/docs/TROUBLESHOOTING.md) · 🇸🇰 [sk](../../sk/docs/TROUBLESHOOTING.md) · 🇸🇪 [sv](../../sv/docs/TROUBLESHOOTING.md) · 🇰🇪 [sw](../../sw/docs/TROUBLESHOOTING.md) · 🇮🇳 [ta](../../ta/docs/TROUBLESHOOTING.md) · 🇮🇳 [te](../../te/docs/TROUBLESHOOTING.md) · 🇹🇭 [th](../../th/docs/TROUBLESHOOTING.md) · 🇹🇷 [tr](../../tr/docs/TROUBLESHOOTING.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/TROUBLESHOOTING.md) · 🇵🇰 [ur](../../ur/docs/TROUBLESHOOTING.md) · 🇻🇳 [vi](../../vi/docs/TROUBLESHOOTING.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/TROUBLESHOOTING.md) +# 疑難排解 + +> **給使用者**:想要快速解決問題?請參考下方的[快速參考](#快速參考)。 + +🌐 **語言:** 🇺🇸 [English](../../../../guides/TROUBLESHOOTING.md) | 🇧🇷 [Português (Brasil)](../../pt-BR/docs/guides/TROUBLESHOOTING.md) | 🇪🇸 [Español](../../es/docs/guides/TROUBLESHOOTING.md) | 🇫🇷 [Français](../../fr/docs/guides/TROUBLESHOOTING.md) | 🇮🇹 [Italiano](../../it/docs/guides/TROUBLESHOOTING.md) | 🇷🇺 [Русский](../../ru/docs/guides/TROUBLESHOOTING.md) | 🇨🇳 [中文 (简体)](../../zh-CN/docs/guides/TROUBLESHOOTING.md) | 🇩🇪 [Deutsch](../../de/docs/guides/TROUBLESHOOTING.md) | 🇮🇳 [हिन्दी](../../in/docs/guides/TROUBLESHOOTING.md) | 🇹🇭 [ไทย](../../th/docs/guides/TROUBLESHOOTING.md) | 🇺🇦 [Українська](../../uk-UA/docs/guides/TROUBLESHOOTING.md) | 🇸🇦 [العربية](../../ar/docs/guides/TROUBLESHOOTING.md) | 🇯🇵 [日本語](../../ja/docs/guides/TROUBLESHOOTING.md) | 🇻🇳 [Tiếng Việt](../../vi/docs/guides/TROUBLESHOOTING.md) | 🇧🇬 [Български](../../bg/docs/guides/TROUBLESHOOTING.md) | 🇩🇰 [Dansk](../../da/docs/guides/TROUBLESHOOTING.md) | 🇫🇮 [Suomi](../../fi/docs/guides/TROUBLESHOOTING.md) | 🇮🇱 [עברית](../../he/docs/guides/TROUBLESHOOTING.md) | 🇭🇺 [Magyar](../../hu/docs/guides/TROUBLESHOOTING.md) | 🇮🇩 [Bahasa Indonesia](../../id/docs/guides/TROUBLESHOOTING.md) | 🇰🇷 [한국어](../../ko/docs/guides/TROUBLESHOOTING.md) | 🇲🇾 [Bahasa Melayu](../../ms/docs/guides/TROUBLESHOOTING.md) | 🇳🇱 [Nederlands](../../nl/docs/guides/TROUBLESHOOTING.md) | 🇳🇴 [Norsk](../../no/docs/guides/TROUBLESHOOTING.md) | 🇵🇹 [Português (Portugal)](../../pt/docs/guides/TROUBLESHOOTING.md) | 🇷🇴 [Română](../../ro/docs/guides/TROUBLESHOOTING.md) | 🇵🇱 [Polski](../../pl/docs/guides/TROUBLESHOOTING.md) | 🇸🇰 [Slovenčina](../../sk/docs/guides/TROUBLESHOOTING.md) | 🇸🇪 [Svenska](../../sv/docs/guides/TROUBLESHOOTING.md) | 🇵🇭 [Filipino](../../phi/docs/guides/TROUBLESHOOTING.md) | 🇨🇿 [Čeština](../../cs/docs/guides/TROUBLESHOOTING.md) + +OmniRoute 的常見問題與解決方案。 --- -Common problems and solutions for OmniRoute. +## 快速參考 + +**剛接觸 OmniRoute?** 從這裡開始 — 這些能解決 90% 的問題: + +| 我看見這個 | 代表什麼 | 該怎麼做 | +| ----------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | +| 「無法連線」 | OmniRoute 未在執行 | 執行 `omniroute` 或 `docker restart omniroute` | +| 「API 金鑰無效」 | 金鑰錯誤或已過期 | 從供應商網站重新複製金鑰 | +| 「超出速率限制」 | 請求傳送過於頻繁 | 等待 1 分鐘,或使用 `model: "auto"` 自動切換 | +| 「超出配額」 | 免費/付費配額已用完 | 連接更多供應商,或使用免費供應商(Kiro, Pollinations) | +| 「回應緩慢」 | 供應商忙碌或距離較遠 | 使用 `model: "auto/fast"` 或連接較快的供應商(Groq, Cerebras) | +| 「使用了錯誤的供應商」 | `auto` 選了不同的供應商 | 這是正常的!`auto` 會選最好的。使用 `model: "openai/gpt-4o"` 來強制指定供應商 | +| 「502 Bad Gateway」 | 供應商故障 | 等待後重試,或使用 `model: "auto"` 切換供應商 | +| 「401 Unauthorized」 | 憑證錯誤 | 檢查 API 金鑰或重新透過 OAuth 認證 | +| 「429 Too Many Requests」| 已達速率限制 | 等待 1 分鐘,或連接更多供應商 | + +**還是卡住了?** 請參考下方的[詳細疑難排解](#詳細疑難排解),或在 [Discord](https://discord.gg/U47eFqAXCn) 上提問。 --- -## Quick Fixes - -| Problem | Solution | -| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| First login not working | Set `INITIAL_PASSWORD` in `.env` (no hardcoded default) | -| Dashboard opens on wrong port | Set `PORT=20128` and `NEXT_PUBLIC_BASE_URL=http://localhost:20128` | -| No logs written to disk | Set `APP_LOG_TO_FILE=true` and verify call log capture is enabled | -| EACCES: permission denied | Set `DATA_DIR=/path/to/writable/dir` to override `~/.omniroute` | -| Routing strategy not saving | Update to v1.4.11+ (Zod schema fix for settings persistence) | -| Login crash / blank page | Check Node.js version — see [Node.js Compatibility](#nodejs-compatibility) below | -| `dlopen` / `slice is not valid mach-o file` (macOS) | Run `cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute` — see [macOS native module rebuild](#macos-native-module-rebuild) below | -| Proxy "fetch failed" | Ensure proxy config is set at the correct level — see [Proxy Issues](#proxy-issues) below | +## 詳細疑難排解 --- -## Node.js Compatibility +## 快速修復 - +| 問題 | 解決方案 | +| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 首次登入無法運作 | 在 `.env` 中設定 `INITIAL_PASSWORD`(無硬編碼預設值) | +| 儀表板開啟在錯誤的連接埠 | 設定 `PORT=20128` 和 `NEXT_PUBLIC_BASE_URL=http://localhost:20128` | +| 沒有日誌寫入磁碟 | 設定 `APP_LOG_TO_FILE=true`,並確認呼叫記錄捕捉功能已啟用 | +| EACCES:權限被拒 | 設定 `DATA_DIR=/path/to/writable/dir` 以覆蓋 `~/.omniroute` | +| 路由策略未儲存 | 更新至最新的 v3.x 版本(早期版本已修復 Zod schema 以確保設定持續性) | +| 登入崩潰/空白頁面 | 檢查 Node.js 版本 — 請參閱下方的 [Node.js 相容性](#nodejs-相容性) | +| `dlopen` / `slice is not valid mach-o file`(macOS)| 執行 `cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute` — 請參閱下方的 [macOS 原生模組重建](#macos-原生模組重建) | +| Proxy「fetch 失敗」 | 確保 Proxy 設定在正確的層級 — 請參閱下方的 [Proxy 問題](#proxy-問題) | +| 防毒軟體隔離 `README.md` | 誤判 — 請參閱下方的[防毒軟體誤判](#防毒軟體誤判) | +| Kaspersky 將桌面應用程式標記為木馬 | 未簽署安裝程式的行為分析誤判 — 請參閱下方的[防毒軟體誤判](#防毒軟體誤判) | -### Login page crashes or shows "Module self-registration" error +--- -**Cause:** You are running a Node.js version outside OmniRoute's approved secure runtime floor. The most common case is running an older Node 20, 22, or 24 patch level that falls below the patched security floor OmniRoute requires. +## 防毒軟體誤判 -**Symptoms:** + -- Login page shows a blank screen or a server error -- Console shows `Error: Module did not self-register` or similar native binding errors -- The login page shows an **orange warning banner** with your Node version if the runtime is outside the supported secure policy +### Avast/AVG 將 `README.md` 隔離並標記為 `MD:HttpRequest-inf[Susp]` -**Fix:** +**這是誤判。沒有任何檔案受感染,也無需任何操作。** -1. Install a supported Node.js LTS release (recommended: Node.js 24.x): +Avast 和 AVG 執行啟發式掃描,會將包含大量類似 HTTP 請求連結的純文字/Markdown 檔案標記為可疑。OmniRoute 的 `README.md` 隨 npm 套件一起發布(它列在 `package.json` → `files` 中),因此在全域安裝時會出現在 `node_modules/omniroute/README.md` — 而其中包含約 15 個 `http://localhost:20128/...` 的範例(MCP HTTP/SSE 端點、A2A `.well-known` URL 以及 `curl` 程式碼片段)。這樣的連結密度足以觸發啟發式掃描。 + +如果這個問題是最近才發生的:檔案的本質並未改變。README 增加了端點表格(新增了 MCP HTTP + SSE + A2A)和更多 `curl` 範例,使其超過了閾值。 + +該檔案是沒有可執行內容的靜態文件。您可以安全地將其從隔離區還原。 + +**該怎麼做:** + +1. **停止通知** — 在防毒軟體中排除安裝目錄(Avast:設定 → 例外),加入您的全域 `node_modules` 路徑和/或 OmniRoute 資料目錄(`~/.omniroute/`)。 +2. **回報誤判** — ,附上被隔離的 `README.md`。這能幫助所有人,因為這是供應商的啟發式掃描對文字檔案的過度反應。 + +**為什麼我們不在這邊「修復」這個問題:** 範例全都是 `http://localhost`,而 localhost 若要使用 `https` 會需要自簽憑證,增加使用摩擦。為了避開某家廠商的啟發式掃描而修改文件,會損害所有讀者的閱讀體驗,只為了一個掃描器的錯誤。 + +### Kaspersky 將桌面應用程式標記為 `PDM:Trojan.Win32.Generic` + +**這是行為啟發式掃描的誤判。沒有任何檔案受感染。** Kaspersky 的 `PDM:` 前綴表示判定來自其主動防禦模組(系統監控器),該模組根據安裝程式的*行為*來判斷,而非比對已知惡意軟體。當觸發時,Kaspersky 會「回滾」整個安裝過程 — 刪除它已經寫入的檔案 — 因此應用程式最終會損壞或遺失。 + +被標記的檔案是桌面應用程式所捆綁的、已聲明的開源依賴的標準組件,例如: + +- `resources/app/.build/next/node_modules/playwright-/lib/…/agentParser.js` 和 `workerProcessEntry.js` — [Playwright](https://playwright.dev),用於應用程式內供應商登入和瀏覽器支援聊天的瀏覽器自動化函式庫。 +- `resources/app/.build/next/node_modules/tls-client-node-/bin/tls-client-windows-64-.dll` — 來自 `tls-client-node` 的原生二進位檔案,用於某些網路供應商的 Cloudflare 相容 HTTP。 + +**為什麼會觸發:** Windows 安裝程式**尚未進行程式碼簽署**,因此未簽署的 NSIS 安裝程式沒有信譽,行為啟發式掃描會以最大強度執行。加上捆綁的原生 DLL 和數百個寫入 `%LOCALAPPDATA%\Programs\OmniRoute` 的 `.js` 檔案(包括 Next.js 獨立建置的雜湊後綴套件目錄),這就足以觸發啟發式掃描。程式碼簽署已規劃中;在完成之前,新版本可能會重複觸發此問題。 + +**該怎麼做:** + +1. **先驗證您的下載**(排除檔案被竄改的可能性)。每個版本都會發布 `latest.yml`,其 `sha512` 欄位(base64)涵蓋 `OmniRoute.Setup..exe` 安裝程式。在 PowerShell 中,從包含安裝程式的目錄執行: + ```powershell + $b = [System.Security.Cryptography.SHA512]::Create().ComputeHash( + [System.IO.File]::ReadAllBytes("$PWD\OmniRoute.Setup..exe")) + [Convert]::ToBase64String($b) + ``` + 輸出必須與 `latest.yml` → `sha512` 相符。如果不符,請刪除檔案並僅從 [GitHub 發布頁面](https://github.com/diegosouzapw/OmniRoute/releases) 重新下載。 +2. **還原 + 排除** — 從隔離區還原被回滾的項目,並為 `%LOCALAPPDATA%\Programs\OmniRoute` 加入排除規則(Kaspersky → 設定 → 威脅與排除),然後重新安裝。 +3. **回報誤判** — 。使用者提交的誤判報告確實能加速允許清單的建立。 + +--- + +## Node.js 相容性 + + + +### 登入頁面崩潰或顯示「Module self-registration」錯誤 + +**原因:** 您執行的 Node.js 版本低於 OmniRoute 核准的安全執行環境最低版本。最常見的情況是執行較舊的 Node 22 或 24 修補版本,低於 OmniRoute 所需的修補安全門檻。 + +**症狀:** + +- 登入頁面顯示空白畫面或伺服器錯誤 +- 主控台顯示 `Error: Module did not self-register` 或類似的原生綁定錯誤 +- 如果執行環境超出支援的安全政策範圍,登入頁面會顯示**橘色警告橫幅**,上面有您的 Node 版本 + +**修復方式:** + +1. 安裝支援的 Node.js LTS 版本(建議:Node.js 24.x): ```bash nvm install 24 nvm use 24 ``` -2. Verify your version: `node --version` should show `v24.0.0` or newer on the 24.x LTS line -3. Reinstall OmniRoute: `npm install -g omniroute` -4. Restart: `omniroute` +2. 驗證版本:`node --version` 應顯示 `v24.0.0` 或更高的 24.x LTS 版本 +3. 重新安裝 OmniRoute:`npm install -g omniroute` +4. 重新啟動:`omniroute` -> **Supported secure versions:** `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`. Node.js 24.x LTS (Krypton) is fully supported. +> **支援的安全版本:** `>=22.22.2 <23` 或 `>=24.0.0 <27`。Node.js 24.x LTS(Krypton)和 Node.js 26 皆受完整支援。 -### macOS: `dlopen` / "slice is not valid mach-o file" +### macOS:`dlopen` /「slice is not valid mach-o file」 - + -**Cause:** After a global `npm install -g omniroute`, the `better-sqlite3` native binary inside the package may have been compiled for a different architecture or Node.js ABI than what is running locally. This is common on macOS (both Apple Silicon and Intel) when the pre-built binary does not match your environment. +**原因:** 在全域 `npm install -g omniroute` 之後,套件內的 `better-sqlite3` 原生二進位檔案可能是為與本地執行環境不同的架構或 Node.js ABI 所編譯。這在 macOS(Apple Silicon 和 Intel 皆適用)上很常見,當預先編譯的二進位檔案與您的環境不符時就會發生。 -**Symptoms:** +**症狀:** -- Server fails immediately on startup with a `dlopen` error -- Error contains `slice is not valid mach-o file` -- Full example: +- 伺服器在啟動時立即失敗,出現 `dlopen` 錯誤 +- 錯誤訊息包含 `slice is not valid mach-o file` +- 完整範例: ``` dlopen(/Users//.nvm/versions/node/v24.14.1/lib/node_modules/omniroute/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node, 0x0001): tried: '...' (slice is not valid mach-o file) ``` -**Fix — rebuild for your local environment (no Node.js downgrade required):** +**修復方式 — 為您的本地環境重建(無需降級 Node.js):** ```bash cd $(npm root -g)/omniroute/app @@ -74,98 +152,114 @@ npm rebuild better-sqlite3 omniroute ``` -> **Note:** This recompiles the native binding against your local Node.js version and CPU architecture, resolving the binary mismatch. The officially supported range is **`>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`** (`engines` field in `package.json`). Node.js 24.x LTS (Krypton) is fully supported with `better-sqlite3` v12.x. +> **注意:** 這會針對您當地的 Node.js 版本和 CPU 架構重新編譯原生綁定,解決二進位檔案不匹配的問題。官方支援的執行環境範圍為 **`>=22.22.2 <23` 或 `>=24.0.0 <27`**(`src/shared/utils/nodeRuntimeSupport.ts` 中的 `SUPPORTED_NODE_RANGE`,與 `package.json` 的 `engines` 欄位一致)。Node.js 24.x LTS(Krypton)和 Node.js 26 搭配 `better-sqlite3` v12.x 皆受完整支援。 --- -## Proxy Issues +## Proxy 問題 - + -### Provider validation shows "fetch failed" +### 供應商驗證顯示「fetch 失敗」 -**Cause:** The API key validation endpoint (`POST /api/providers/validate`) was previously bypassing proxy configuration, causing failures in environments that require proxy routing. +**原因:** API 金鑰驗證端點(`POST /api/providers/validate`)先前會繞過 Proxy 設定,導致在需要 Proxy 路由的環境中失敗。 -**Fix (v3.5.5+):** This is now fixed. Provider validation routes through `runWithProxyContext`, honoring provider-level and global proxy settings automatically. +**修復方式(v3.5.5+):** 此問題現已修復。供應商驗證會透過 `runWithProxyContext` 路由,自動遵循供應商層級和全域的 Proxy 設定。 -### Token health check fails with "fetch failed" +### Token 健康狀態檢查失敗,顯示「fetch 失敗」 -**Cause:** Background OAuth token refresh was not resolving proxy configuration per connection. +**原因:** 背景 OAuth Token 刷新未針對每個連線解析 Proxy 設定。 -**Fix (v3.5.5+):** The token health check scheduler now resolves proxy config per connection before attempting refresh. Update to v3.5.5+. +**修復方式(v3.5.5+):** Token 健康狀態檢查排程器現在會在嘗試刷新前,先為每個連線解析 Proxy 設定。請更新至 v3.5.5+。 -### SOCKS5 proxy returns "invalid onRequestStart method" +### SOCKS5 Proxy 回傳「invalid onRequestStart method」 -**Cause:** On Node.js 22, the undici@8 dispatcher is incompatible with Node's built-in `fetch()` implementation. +**原因:** 在 Node.js 22 上,undici@8 的分派器與 Node 內建的 `fetch()` 實作不相容。 -**Fix (v3.5.5+):** OmniRoute now uses undici's own `fetch()` function when a proxy dispatcher is active, ensuring consistent behavior. Update to v3.5.5+. +**修復方式(v3.5.5+):** OmniRoute 現在在啟用 Proxy 分派器時使用 undici 自己的 `fetch()` 函式,確保行為一致。請更新至 v3.5.5+。 + +### WSL 下的 MITM Proxy:Windows 主機上的桌面應用程式未被攔截 + +**原因:** MITM Proxy 及其 CA 憑證會安裝在 OmniRoute 執行的環境中。在 WSL 下,該環境是 Linux 客體,而 AI 桌面應用程式(Kiro、Trae、Copilot、Zed 等)則在 Windows 主機上執行。主機應用程式不信任客體的憑證儲存區,也不會透過客體的系統 Proxy 路由,因此桌面攔截無法在該處生效。 + +**建議:** 在與您要攔截的桌面應用程式相同的作業系統上原生執行 OmniRoute(Windows 應用程式用 Windows;macOS/Linux 同理)。在 WSL 內執行 OmniRoute 同時鎖定主機應用程式,需要手動在 Windows 主機上信任所產生的 CA 憑證,並將每個主機應用程式的網路/Proxy 設定指向 WSL Proxy 端點 — 這是不受支援且脆弱的設定。 --- -## Provider Issues +## 供應商問題 -### "Language model did not provide messages" +###「Language model did not provide messages」 -**Cause:** Provider quota exhausted. +**原因:** 供應商配額已用完。 -**Fix:** +**修復方式:** -1. Check dashboard quota tracker -2. Use a combo with fallback tiers -3. Switch to cheaper/free tier +1. 檢查儀表板配額追蹤器 +2. 使用包含備援層級的組合 +3. 切換至較便宜/免費的方案 -### Rate Limiting +### 速率限制 -**Cause:** Subscription quota exhausted. +**原因:** 訂閱配額已用完。 -**Fix:** +**修復方式:** -- Add fallback: `cc/claude-opus-4-6 → glm/glm-4.7 → if/kimi-k2-thinking` -- Use GLM/MiniMax as cheap backup +- 加入備援:`cc/claude-opus-4-6 → glm/glm-4.7 → if/qwen3.8-max-preview` +- 使用 GLM/MiniMax 作為便宜的備援方案 -### OAuth Token Expired +### OAuth Token 已過期 -OmniRoute auto-refreshes tokens. If issues persist: +OmniRoute 會自動刷新 Token。如果問題持續存在: -1. Dashboard → Provider → Reconnect -2. Delete and re-add the provider connection +1. 儀表板 → 供應商 → 重新連線 +2. 刪除並重新加入供應商連線 + +### Kiro 多帳號:第二個帳號使第一個帳號失效 + +**原因:** Kiro 的後端對每個 OIDC 用戶端註冊強制執行單一活躍階段。當兩個帳號共用相同的已註冊用戶端(v3.8.0 之前匯入的連線)時,刷新一個帳號的 Token 會使另一個帳號的刷新 Token 失效。 + +**修復方式(v3.8.0+):** 重新匯入受影響的連線。從 v3.8.0 開始,每個透過**匯入 Token**、**Google/GitHub 社群登入**或**自動匯入**建立的新 Kiro 連線,都會自動註冊其專屬的 OIDC 用戶端。因此該連線完全隔離,刷新一個帳號不會影響任何其他帳號。 + +在 v3.8.0 *之前*匯入的連線不帶有每個連線的用戶端註冊。這些連線會繼續使用共用的社群登入刷新端點。若要獲得隔離,請從儀表板 → 供應商刪除舊連線,並透過三種匯入流程之一重新加入。 + +如需完整詳細資訊和逐步新增兩個 Kiro 帳號的說明,請參閱 [`docs/guides/KIRO_SETUP.md`](./KIRO_SETUP.md)。 --- -## Cloud Issues +## 雲端問題 -### Cloud Sync Errors +### 雲端同步錯誤 -1. Verify `BASE_URL` points to your running instance (e.g., `http://localhost:20128`) -2. Verify `CLOUD_URL` points to your cloud endpoint (e.g., `https://omniroute.dev`) -3. Keep `NEXT_PUBLIC_*` values aligned with server-side values +1. 確認 `BASE_URL` 指向您執行的實例(例如 `http://localhost:20128`) +2. 確認 `CLOUD_URL` 指向您的雲端端點(例如 `https://omniroute.dev`) +3. 保持 `NEXT_PUBLIC_*` 的值與伺服器端的值一致 -### Cloud `stream=false` Returns 500 +### 雲端 `stream=false` 回傳 500 -**Symptom:** `Unexpected token 'd'...` on cloud endpoint for non-streaming calls. +**症狀:** 雲端端點在非串流呼叫時出現 `Unexpected token 'd'...`。 -**Cause:** Upstream returns SSE payload while client expects JSON. +**原因:** 上游回傳 SSE 負載,但用戶端預期的是 JSON。 -**Workaround:** Use `stream=true` for cloud direct calls. Local runtime includes SSE→JSON fallback. +**解決方法:** 對雲端直接呼叫使用 `stream=true`。本地執行環境包含 SSE→JSON 備援機制。 -### Cloud Says Connected but "Invalid API key" +### 雲端顯示已連線但出現「API 金鑰無效」 -1. Create a fresh key from local dashboard (`/api/keys`) -2. Run cloud sync: Enable Cloud → Sync Now -3. Old/non-synced keys can still return `401` on cloud +1. 從本地儀表板(`/api/keys`)建立新的金鑰 +2. 執行雲端同步:啟用雲端 → 立即同步 +3. 舊的/未同步的金鑰仍可能在雲端回傳 `401` --- -## Docker Issues +## Docker 問題 -### CLI Tool Shows Not Installed +### CLI 工具顯示未安裝 -1. Check runtime fields: `curl http://localhost:20128/api/cli-tools/runtime/codex | jq` -2. For portable mode: use image target `runner-cli` (bundled CLIs) -3. For host mount mode: set `CLI_EXTRA_PATHS` and mount host bin directory as read-only -4. If `installed=true` and `runnable=false`: binary was found but failed healthcheck +1. 檢查執行環境欄位:`curl http://localhost:20128/api/cli-tools/runtime/codex | jq` +2. 對於可攜帶模式:使用映像檔目標 `runner-cli`(內建 CLI) +3. 對於主機掛載模式:設定 `CLI_EXTRA_PATHS` 並將主機 bin 目錄以唯讀方式掛載 +4. 如果 `installed=true` 且 `runnable=false`:二進位檔案已找到但健康檢查失敗 -### Quick Runtime Validation +### 快速執行環境驗證 ```bash curl -s http://localhost:20128/api/cli-tools/codex-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}' @@ -175,166 +269,281 @@ curl -s http://localhost:20128/api/cli-tools/openclaw-settings | jq '{installed, --- -## Cost Issues +## 成本問題 -### High Costs +### 成本過高 -1. Check usage stats in Dashboard → Usage -2. Switch primary model to GLM/MiniMax -3. Set cost budgets per API key: Dashboard → API Keys → Budget +1. 在儀表板 → 使用量中檢查用量統計 +2. 將主要模型切換至 GLM/MiniMax +3. 對非關鍵任務使用免費方案(Qoder、Kiro) +4. 為每個 API 金鑰設定成本預算:儀表板 → API 金鑰 → 預算 --- -## Debugging +## 除錯 -### Enable Log Files +### 啟用日誌檔案 -Set `APP_LOG_TO_FILE=true` in your `.env` file. Application logs are written under `logs/`. -Request artifacts are stored under `${DATA_DIR}/call_logs/` when the call log pipeline is -enabled in settings. +在 `.env` 檔案中設定 `APP_LOG_TO_FILE=true`。應用程式日誌會寫入 `logs/` 目錄下。 +請求工件會在啟用呼叫記錄管線時儲存在 `${DATA_DIR}/call_logs/` 目錄下。 +啟用管線捕捉時,設定 `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS=false` 可省略串流區塊負載,或調整 `CALL_LOG_PIPELINE_MAX_SIZE_KB` 來變更工件大小上限(KB)。 -### Check Provider Health +### 檢查供應商健康狀態 ```bash -# Health dashboard +# 健康狀態儀表板 http://localhost:20128/dashboard/health -# API health check +# API 健康狀態檢查 curl http://localhost:20128/api/monitoring/health ``` -### Runtime Storage +### 執行環境儲存 -- Main state: `${DATA_DIR}/storage.sqlite` (providers, combos, aliases, keys, settings) -- Usage: SQLite tables in `storage.sqlite` (`usage_history`, `call_logs`, `proxy_logs`) + optional `${DATA_DIR}/call_logs/` -- Application logs: `/logs/...` (when `APP_LOG_TO_FILE=true`) -- Call log artifacts: `${DATA_DIR}/call_logs/YYYY-MM-DD/...` when the call log pipeline is enabled +- 主要狀態:`${DATA_DIR}/storage.sqlite`(供應商、組合、別名、金鑰、設定) +- 使用量:`storage.sqlite` 中的 SQLite 表格(`usage_history`、`call_logs`、`proxy_logs`)+ 選用的 `${DATA_DIR}/call_logs/` +- 應用程式日誌:`/logs/...`(當 `APP_LOG_TO_FILE=true` 時) +- 呼叫記錄工件:啟用呼叫記錄管線時在 `${DATA_DIR}/call_logs/YYYY-MM-DD/...` 下 + +請求記錄頁面的**清理歷史記錄**動作會清除 `call_logs`、舊的 `request_detail_logs` 以及本地的 `${DATA_DIR}/call_logs/` 工件目錄。 --- -## Circuit Breaker Issues +## 斷路器問題 -### Provider stuck in OPEN state +### 供應商卡在 OPEN 狀態 -When a provider's circuit breaker is OPEN, requests are blocked until the cooldown expires. +當供應商的斷路器處於 OPEN 狀態時,請求將被阻擋直到冷卻時間結束。 -**Fix:** +**修復方式:** -1. Go to **Dashboard → Settings → Resilience** -2. Check the circuit breaker card for the affected provider -3. Click **Reset All** to clear all breakers, or wait for the cooldown to expire -4. Verify the provider is actually available before resetting +1. 前往**儀表板 → 設定 → 備援** +2. 檢查受影響供應商的斷路器卡片 +3. 點擊**全部重設**以清除所有斷路器,或等待冷卻時間結束 +4. 在重設前確認供應商確實可用 -### Provider keeps tripping the circuit breaker +### 供應商持續觸發斷路器 -If a provider repeatedly enters OPEN state: +如果供應商反覆進入 OPEN 狀態: -1. Check **Dashboard → Health → Provider Health** for the failure pattern -2. Go to **Settings → Resilience → Provider Profiles** and increase the failure threshold -3. Check if the provider has changed API limits or requires re-authentication -4. Review latency telemetry — high latency may cause timeout-based failures +1. 檢查**儀表板 → 健康狀態 → 供應商健康狀態**以了解失敗模式 +2. 前往**設定 → 備援 → 供應商設定檔**並提高失敗閾值 +3. 檢查供應商是否變更了 API 限制或需要重新認證 +4. 檢閱延遲遙測資料 — 高延遲可能導致基於超時的失敗 --- -## Audio Transcription Issues +## 語音轉文字問題 -### "Unsupported model" error +###「Unsupported model」錯誤 -- Ensure you're using the correct prefix: `deepgram/nova-3` or `assemblyai/best` -- Verify the provider is connected in **Dashboard → Providers** +- 確保使用正確的前綴:`deepgram/nova-3` 或 `assemblyai/best` +- 確認該供應商已在**儀表板 → 供應商**中連線 -### Transcription returns empty or fails +### 轉錄回傳空值或失敗 -- Check supported audio formats: `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm` -- Verify file size is within provider limits (typically < 25MB) -- Check provider API key validity in the provider card +- 檢查支援的音訊格式:`mp3`、`wav`、`m4a`、`flac`、`ogg`、`webm` +- 確認檔案大小在供應商限制內(通常 < 25MB) +- 在供應商卡片中檢查供應商 API 金鑰的有效性 --- -## Translator Debugging +## 翻譯器除錯 -Use **Dashboard → Translator** to debug format translation issues: +使用**儀表板 → 翻譯器**來除錯格式翻譯問題: -| Mode | When to Use | -| ---------------- | -------------------------------------------------------------------------------------------- | -| **Playground** | Compare input/output formats side by side — paste a failing request to see how it translates | -| **Chat Tester** | Send live messages and inspect the full request/response payload including headers | -| **Test Bench** | Run batch tests across format combinations to find which translations are broken | -| **Live Monitor** | Watch real-time request flow to catch intermittent translation issues | +| 模式 | 使用時機 | +| ---------------- | ----------------------------------------------------------------------------------------------- | +| **遊樂場** | 並排比較輸入/輸出格式 — 貼上失敗的請求以查看翻譯結果 | +| **聊天測試器** | 發送即時訊息並檢查完整的請求/回應負載,包括標頭 | +| **測試平台** | 跨格式組合執行批次測試,找出哪些翻譯有問題 | +| **即時監控器** | 監控即時請求流程,捕捉間歇性的翻譯問題 | -### Common format issues +### 常見格式問題 -- **Thinking tags not appearing** — Check if the target provider supports thinking and the thinking budget setting -- **Tool calls dropping** — Some format translations may strip unsupported fields; verify in Playground mode -- **System prompt missing** — Claude and Gemini handle system prompts differently; check translation output -- **SDK returns raw string instead of object** — Fixed in v1.1.0: response sanitizer now strips non-standard fields (`x_groq`, `usage_breakdown`, etc.) that cause OpenAI SDK Pydantic validation failures -- **GLM/ERNIE rejects `system` role** — Fixed in v1.1.0: role normalizer automatically merges system messages into user messages for incompatible models -- **`developer` role not recognized** — Fixed in v1.1.0: automatically converted to `system` for non-OpenAI providers -- **`json_schema` not working with Gemini** — Fixed in v1.1.0: `response_format` is now converted to Gemini's `responseMimeType` + `responseSchema` +- **思考標籤未顯示** — 檢查目標供應商是否支援思考功能以及思考預算設定 +- **工具呼叫被遺漏** — 某些格式翻譯可能會移除不支援的欄位;請在遊樂場模式中驗證 +- **系統提示詞遺失** — Claude 和 Gemini 處理系統提示詞的方式不同;請檢查翻譯輸出 +- **SDK 回傳原始字串而非物件** — 已在 v1.x 中解決;回應清理器會移除導致 OpenAI SDK Pydantic 驗證失敗的非標準欄位(`x_groq`、`usage_breakdown` 等)。如果您在 v3.x+ 仍看到此問題,請提交 issue。 +- **GLM/ERNIE 拒絕 `system` 角色** — 已在 v1.x 中解決;角色正規化器會自動將系統訊息合併到使用者訊息中,以相容不相容的模型。如果您在 v3.x+ 仍看到此問題,請提交 issue。 +- **`developer` 角色不被辨識** — 已在 v1.x 中解決;對非 OpenAI 供應商會自動轉換為 `system`。如果您在 v3.x+ 仍看到此問題,請提交 issue。 +- **`json_schema` 在 Gemini 上無法使用** — 已在 v1.x 中解決;`response_format` 現在會轉換為 Gemini 的 `responseMimeType` + `responseSchema`。如果您在 v3.x+ 仍看到此問題,請提交 issue。 --- -## Resilience Settings +## 備援設定 -### Auto rate-limit not triggering +### 自動速率限制未觸發 -- Auto rate-limit only applies to API key providers (not OAuth/subscription) -- Verify **Settings → Resilience → Provider Profiles** has auto-rate-limit enabled -- Check if the provider returns `429` status codes or `Retry-After` headers +- 自動速率限制僅適用於 API 金鑰供應商(不適用於 OAuth/訂閱) +- 確認**設定 → 備援 → 供應商設定檔**已啟用自動速率限制 +- 檢查供應商是否回傳 `429` 狀態碼或 `Retry-After` 標頭 -### Tuning exponential backoff +### 調整指數退避 -Provider profiles support these settings: +供應商設定檔支援以下設定: -- **Base delay** — Initial wait time after first failure (default: 1s) -- **Max delay** — Maximum wait time cap (default: 30s) -- **Multiplier** — How much to increase delay per consecutive failure (default: 2x) +- **基本延遲** — 首次失敗後的初始等待時間(預設:1 秒) +- **最大延遲** — 等待時間上限(預設:30 秒) +- **乘數** — 每次連續失敗延遲的增加倍率(預設:2 倍) -### Anti-thundering herd +### 防止驚群效應 -When many concurrent requests hit a rate-limited provider, OmniRoute uses mutex + auto rate-limiting to serialize requests and prevent cascading failures. This is automatic for API key providers. +當大量並發請求湧入一個已達速率限制的供應商時,OmniRoute 會使用互斥鎖 + 自動速率限制來序列化請求,防止連鎖失敗。這對 API 金鑰供應商是自動生效的。 --- -## Optional RAG / LLM failure taxonomy (16 problems) +## 選用:RAG / LLM 失敗分類(16 種問題) -Some OmniRoute users place the gateway in front of RAG or agent stacks. In those setups it is common to see a strange pattern: OmniRoute looks healthy (providers up, routing profiles ok, no rate limit alerts) but the final answer is still wrong. +部分 OmniRoute 使用者將閘道器部署在 RAG 或 Agent 堆疊之前。在這些設定中,常會看到一種奇怪的現象:OmniRoute 看起來正常(供應商正常、路由設定檔無誤、無速率限制警示),但最終答案仍然錯誤。 -In practice these incidents usually come from the downstream RAG pipeline, not from the gateway itself. +實際上,這些問題通常來自下游的 RAG 管線,而非閘道器本身。 -If you want a shared vocabulary to describe those failures you can use the WFGY ProblemMap, an external MIT license text resource that defines sixteen recurring RAG / LLM failure patterns. At a high level it covers: +如果您想要一個共享的詞彙來描述這些失敗,可以使用 WFGY ProblemMap,這是一個外部的 MIT 授權文字資源,定義了十六種常見的 RAG / LLM 失敗模式。高層次涵蓋: -- retrieval drift and broken context boundaries -- empty or stale indexes and vector stores -- embedding versus semantic mismatch -- prompt assembly and context window issues -- logic collapse and overconfident answers -- long chain and agent coordination failures -- multi agent memory and role drift -- deployment and bootstrap ordering problems +- 檢索偏移與中斷的上下文邊界 +- 空索引或過時索引以及向量儲存庫 +- 嵌入與語義不匹配 +- 提示詞組裝與上下文視窗問題 +- 邏輯崩潰與過度自信的答案 +- 長鏈與 Agent 協調失敗 +- 多 Agent 記憶與角色偏移 +- 部署與啟動順序問題 -The idea is simple: +概念很簡單: -1. When you investigate a bad response, capture: - - user task and request - - route or provider combo in OmniRoute - - any RAG context used downstream (retrieved documents, tool calls, etc) -2. Map the incident to one or two WFGY ProblemMap numbers (`No.1` … `No.16`). -3. Store the number in your own dashboard, runbook, or incident tracker next to the OmniRoute logs. -4. Use the corresponding WFGY page to decide whether you need to change your RAG stack, retriever, or routing strategy. +1. 當您調查一個錯誤回應時,記錄: + - 使用者的任務與請求 + - OmniRoute 中的路由或供應商組合 + - 下游使用的任何 RAG 上下文(檢索的文件、工具呼叫等) +2. 將事件對應到一或兩個 WFGY ProblemMap 編號(`No.1` … `No.16`)。 +3. 將編號儲存在您自己的儀表板、Runbook 或事件追蹤器中,放在 OmniRoute 日誌旁邊。 +4. 使用對應的 WFGY 頁面來決定是否需要變更您的 RAG 堆疊、檢索器或路由策略。 -Full text and concrete recipes live here (MIT license, text only): +完整文字與具體做法在此(MIT 授權,僅文字): [WFGY ProblemMap README](https://github.com/onestardao/WFGY/blob/main/ProblemMap/README.md) -You can ignore this section if you do not run RAG or agent pipelines behind OmniRoute. +如果您沒有在 OmniRoute 後方執行 RAG 或 Agent 管線,可以忽略本節。 --- -## Still Stuck? +## v3.8.0 已知問題 -- **GitHub Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) -- **Architecture**: See [`docs/architecture/ARCHITECTURE.md`](ARCHITECTURE.md) for internal details -- **API Reference**: See [`docs/reference/API_REFERENCE.md`](API_REFERENCE.md) for all endpoints -- **Health Dashboard**: Check **Dashboard → Health** for real-time system status -- **Translator**: Use **Dashboard → Translator** to debug format issues +v3.8.0 版本特有的問題及其目前的解決方法。如果後續修補版本中提供了修復,本條目將會更新或移除。 + +### Windsurf OAuth 流程失敗,顯示 401 + +**症狀:** + +- 從儀表板完成 Windsurf OAuth 流程時出現「401 unauthorized」 +- 回呼後 Windsurf 供應商卡片仍停留在「需要重新連線」狀態 + +**原因:** + +- `WINDSURF_FIREBASE_API_KEY` 環境變數遺失或為空 +- `WINDSURF_API_KEY` 設定錯誤或指向過期的 Token +- 本地防火牆/Proxy 阻擋了 OAuth 回呼 + +**修復方式:** + +1. 確認 `.env` 中已設定 `WINDSURF_FIREBASE_API_KEY` 和 `WINDSURF_API_KEY` +2. 重新啟動 OmniRoute 以載入新的環境變數值 +3. 從**儀表板 → 供應商 → Windsurf → 重新連線**重新執行 OAuth 流程 + +### Devin CLI 認證失敗 + +**症狀:** + +- 呼叫 Devin 相關工具時出現「Devin CLI not found」或「auth failed」 +- CLI 執行環境檢查回報 `installed=false` + +**原因:** + +- `CLI_DEVIN_BIN` 指向不存在的路徑 +- 主機上未安裝 Devin CLI + +**修復方式:** + +1. 為您的平台安裝 Devin CLI +2. 在 `.env` 中設定 `CLI_DEVIN_BIN=/usr/local/bin/devin`(或實際路徑) +3. 重新啟動 OmniRoute 並從**儀表板 → CLI 工具**重新測試 + +### 模型冷卻卡住(手動重設) + +**症狀:** + +- 模型在冷卻時間過後仍列在冷卻清單中 +- 儘管時間戳記已在過去,請求在組合路由中仍跳過該模型 + +**手動重設:** + +- **儀表板:** **設定 → 模型冷卻** → 點擊受影響卡片上的**重新啟用** +- **API:** 使用管理認證標頭呼叫 `DELETE /api/resilience/model-cooldowns` + +### Command Code 供應商連線失敗,顯示 403 + +**症狀:** + +- 測試 Command Code 供應商連線時出現 403 +- 剛新增後供應商卡片顯示「unauthorized」 + +**原因:** OAuth 流程未完成(回呼未收到或 Token 未持久化)。 + +**修復方式:** + +- 從 CLI 執行 `omniroute providers` 以重新觸發 OAuth 流程,或 +- 從**儀表板 → 供應商 → Command Code → 重新連線**重新執行 OAuth + +### ModelScope 回傳積極的 429 冷卻 + +**症狀:** + +- 在 ModelScope 上,少量請求突發後出現非常短或立即的冷卻 +- 組合路由比預期更早跳過 ModelScope + +**原因:** ModelScope 會發出供應商特定的 `Retry-After` 標頭。v3.8.0 提供了專門處理這些標頭的功能,因此較舊的版本會將其誤讀為一般的速率限制提示。 + +**修復方式:** + +- 確保您使用 v3.8.0 或更新版本 +- 確認**設定 → 備援**下的 `useUpstream429BreakerHints` 切換已啟用 + +### OMNIROUTE_WS_BRIDGE_SECRET 在生產環境中遺失 + +**症狀:** + +- 在遠端生產主機上執行時,每個 Codex/Responses WebSocket 橋接請求都出現 401 +- WebSocket 橋接握手在連線後立即關閉 + +**原因:** 生產環境缺少 `OMNIROUTE_WS_BRIDGE_SECRET` 環境變數。 + +**修復方式:** + +1. 產生隨機密鑰:`openssl rand -hex 32` +2. 在生產伺服器環境中設定 `OMNIROUTE_WS_BRIDGE_SECRET=<隨機密鑰>`(以及任何與橋接通訊的用戶端) +3. 重新啟動 OmniRoute + +### Responses API:背景模式降級為同步 + +**症狀:** + +- 記錄警告:`background mode degraded to synchronous` +- `background: true` 請求回傳正常的同步回應,而非背景工作控制代碼 + +**原因:** v3.8.0 會故意將 Responses API 上的 `background: true` 降級為同步執行,同時發出警告。完整的非同步背景執行是未來的交付項目。 + +**修復方式:** + +- 調整用戶端在不使用 `background` 的情況下呼叫,或 +- 等待後續發布完整非同步背景模式的版本(請關注更新日誌) + +--- + +## 還是卡住了? + +- **GitHub Issues**:[github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) +- **架構**:請參閱 [`docs/architecture/ARCHITECTURE.md`](../architecture/ARCHITECTURE.md) 了解內部細節 +- **API 參考**:請參閱 [`docs/reference/API_REFERENCE.md`](../reference/API_REFERENCE.md) 了解所有端點 +- **健康狀態儀表板**:查看**儀表板 → 健康狀態**以取得即時系統狀態 +- **翻譯器**:使用**儀表板 → 翻譯器**來除錯格式問題 diff --git a/docs/i18n/zh-TW/docs/guides/UNINSTALL.md b/docs/i18n/zh-TW/docs/guides/UNINSTALL.md index 7a21b47eab..4667d438fc 100644 --- a/docs/i18n/zh-TW/docs/guides/UNINSTALL.md +++ b/docs/i18n/zh-TW/docs/guides/UNINSTALL.md @@ -1,56 +1,60 @@ -# OmniRoute — Uninstall Guide (中文 (簡體)) +--- +title: "OmniRoute — 解除安裝指南" +version: 3.8.40 +lastUpdated: 2026-06-28 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/UNINSTALL.md) · 🇸🇦 [ar](../../ar/docs/UNINSTALL.md) · 🇧🇬 [bg](../../bg/docs/UNINSTALL.md) · 🇧🇩 [bn](../../bn/docs/UNINSTALL.md) · 🇨🇿 [cs](../../cs/docs/UNINSTALL.md) · 🇩🇰 [da](../../da/docs/UNINSTALL.md) · 🇩🇪 [de](../../de/docs/UNINSTALL.md) · 🇪🇸 [es](../../es/docs/UNINSTALL.md) · 🇮🇷 [fa](../../fa/docs/UNINSTALL.md) · 🇫🇮 [fi](../../fi/docs/UNINSTALL.md) · 🇫🇷 [fr](../../fr/docs/UNINSTALL.md) · 🇮🇳 [gu](../../gu/docs/UNINSTALL.md) · 🇮🇱 [he](../../he/docs/UNINSTALL.md) · 🇮🇳 [hi](../../hi/docs/UNINSTALL.md) · 🇭🇺 [hu](../../hu/docs/UNINSTALL.md) · 🇮🇩 [id](../../id/docs/UNINSTALL.md) · 🇮🇹 [it](../../it/docs/UNINSTALL.md) · 🇯🇵 [ja](../../ja/docs/UNINSTALL.md) · 🇰🇷 [ko](../../ko/docs/UNINSTALL.md) · 🇮🇳 [mr](../../mr/docs/UNINSTALL.md) · 🇲🇾 [ms](../../ms/docs/UNINSTALL.md) · 🇳🇱 [nl](../../nl/docs/UNINSTALL.md) · 🇳🇴 [no](../../no/docs/UNINSTALL.md) · 🇵🇭 [phi](../../phi/docs/UNINSTALL.md) · 🇵🇱 [pl](../../pl/docs/UNINSTALL.md) · 🇵🇹 [pt](../../pt/docs/UNINSTALL.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/UNINSTALL.md) · 🇷🇴 [ro](../../ro/docs/UNINSTALL.md) · 🇷🇺 [ru](../../ru/docs/UNINSTALL.md) · 🇸🇰 [sk](../../sk/docs/UNINSTALL.md) · 🇸🇪 [sv](../../sv/docs/UNINSTALL.md) · 🇰🇪 [sw](../../sw/docs/UNINSTALL.md) · 🇮🇳 [ta](../../ta/docs/UNINSTALL.md) · 🇮🇳 [te](../../te/docs/UNINSTALL.md) · 🇹🇭 [th](../../th/docs/UNINSTALL.md) · 🇹🇷 [tr](../../tr/docs/UNINSTALL.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/UNINSTALL.md) · 🇵🇰 [ur](../../ur/docs/UNINSTALL.md) · 🇻🇳 [vi](../../vi/docs/UNINSTALL.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/UNINSTALL.md) +# OmniRoute — 解除安裝指南 + +🌐 **語言:** 🇺🇸 [English](./UNINSTALL.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/guides/UNINSTALL.md) | 🇪🇸 [Español](../i18n/es/docs/guides/UNINSTALL.md) | 🇫🇷 [Français](../i18n/fr/docs/guides/UNINSTALL.md) | 🇮🇹 [Italiano](../i18n/it/docs/guides/UNINSTALL.md) | 🇷🇺 [Русский](../i18n/ru/docs/guides/UNINSTALL.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/docs/guides/UNINSTALL.md) | 🇩🇪 [Deutsch](../i18n/de/docs/guides/UNINSTALL.md) | 🇮🇳 [हिन्दी](../i18n/in/docs/guides/UNINSTALL.md) | 🇹🇭 [ไทย](../i18n/th/docs/guides/UNINSTALL.md) | 🇺🇦 [Українська](../i18n/uk-UA/docs/guides/UNINSTALL.md) | 🇸🇦 [العربية](../i18n/ar/docs/guides/UNINSTALL.md) | 🇯🇵 [日本語](../i18n/ja/docs/guides/UNINSTALL.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/docs/guides/UNINSTALL.md) | 🇧🇬 [Български](../i18n/bg/docs/guides/UNINSTALL.md) | 🇩🇰 [Dansk](../i18n/da/docs/guides/UNINSTALL.md) | 🇫🇮 [Suomi](../i18n/fi/docs/guides/UNINSTALL.md) | 🇮🇱 [עברית](../i18n/he/docs/guides/UNINSTALL.md) | 🇭🇺 [Magyar](../i18n/hu/docs/guides/UNINSTALL.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/docs/guides/UNINSTALL.md) | 🇰🇷 [한국어](../i18n/ko/docs/guides/UNINSTALL.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/docs/guides/UNINSTALL.md) | 🇳🇱 [Nederlands](../i18n/nl/docs/guides/UNINSTALL.md) | 🇳🇴 [Norsk](../i18n/no/docs/guides/UNINSTALL.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/docs/guides/UNINSTALL.md) | 🇷🇴 [Română](../i18n/ro/docs/guides/UNINSTALL.md) | 🇵🇱 [Polski](../i18n/pl/docs/guides/UNINSTALL.md) | 🇸🇰 [Slovenčina](../i18n/sk/docs/guides/UNINSTALL.md) | 🇸🇪 [Svenska](../i18n/sv/docs/guides/UNINSTALL.md) | 🇵🇭 [Filipino](../i18n/phi/docs/guides/UNINSTALL.md) | 🇨🇿 [Čeština](../i18n/cs/docs/guides/UNINSTALL.md) | 🇹🇼 [繁體中文 (臺灣)](../i18n/zh-TW/docs/guides/UNINSTALL.md) + +本指南說明如何從系統中徹底移除 OmniRoute。 --- -This guide covers how to cleanly remove OmniRoute from your system. +## 快速解除安裝(v3.6.2+) ---- +OmniRoute 提供兩個內建指令碼來進行乾淨的移除: -## Quick Uninstall (v3.6.2+) - -OmniRoute provides two built-in scripts for clean removal: - -### Keep Your Data +### 保留資料 ```bash npm run uninstall ``` -This removes the OmniRoute application but **preserves** your database, configurations, API keys, and provider settings in `~/.omniroute/`. Use this if you plan to reinstall later and want to keep your setup. +此指令會移除 OmniRoute 應用程式,但**保留**您的資料庫、設定檔、API 金鑰及供應商設定於 `~/.omniroute/`。若您日後打算重新安裝並保留既有設定,請使用此方式。 -### Full Removal +### 完整移除 ```bash npm run uninstall:full ``` -This removes the application **and permanently erases** all data: +此指令會移除應用程式,**並永久刪除**所有資料: -- Database (`storage.sqlite`) -- Provider configurations and API keys -- Backup files -- Log files -- All files in the `~/.omniroute/` directory +- 資料庫(`storage.sqlite`) +- 供應商設定與 API 金鑰 +- 備份檔案 +- 日誌檔案 +- `~/.omniroute/` 目錄中的所有檔案 -> ⚠️ **Warning:** `npm run uninstall:full` is irreversible. All your provider connections, combos, API keys, and usage history will be permanently deleted. +> ⚠️ **警告:** `npm run uninstall:full` 為不可逆操作。所有供應商連線、組合設定、API 金鑰及使用記錄都將永久刪除。 --- -## Manual Uninstall +## 手動解除安裝 -### NPM Global Install +### NPM 全域安裝 ```bash -# Remove the global package +# 移除全域套件 npm uninstall -g omniroute -# (Optional) Remove data directory +# (選擇性)移除資料目錄 rm -rf ~/.omniroute ``` -### pnpm Global Install +### pnpm 全域安裝 ```bash pnpm uninstall -g omniroute @@ -60,97 +64,97 @@ rm -rf ~/.omniroute ### Docker ```bash -# Stop and remove the container +# 停止並移除容器 docker stop omniroute docker rm omniroute -# Remove the volume (deletes all data) +# 移除資料卷(刪除所有資料) docker volume rm omniroute-data -# (Optional) Remove the image +# (選擇性)移除映像檔 docker rmi diegosouzapw/omniroute:latest ``` ### Docker Compose ```bash -# Stop and remove containers +# 停止並移除容器 docker compose down -# Also remove volumes (deletes all data) +# 一併移除資料卷(刪除所有資料) docker compose down -v ``` -### Electron Desktop App +### Electron 桌面應用程式 -**Windows:** +**Windows:** -- Open `Settings → Apps → OmniRoute → Uninstall` -- Or run the NSIS uninstaller from the install directory +- 開啟 `設定 → 應用程式 → OmniRoute → 解除安裝` +- 或從安裝目錄執行 NSIS 解除安裝程式 -**macOS:** +**macOS:** -- Drag `OmniRoute.app` from `/Applications` to Trash -- Remove data: `rm -rf ~/Library/Application Support/omniroute` +- 將 `/Applications` 中的 `OmniRoute.app` 拖入垃圾桶 +- 移除資料:`rm -rf ~/Library/Application Support/omniroute` -**Linux:** +**Linux:** -- Remove the AppImage file -- Remove data: `rm -rf ~/.omniroute` +- 刪除 AppImage 檔案 +- 移除資料:`rm -rf ~/.omniroute` -### Source Install (git clone) +### 原始碼安裝(git clone) ```bash -# Remove the cloned directory +# 移除複製的目錄 rm -rf /path/to/omniroute -# (Optional) Remove data directory +# (選擇性)移除資料目錄 rm -rf ~/.omniroute ``` --- -## Data Directories +## 資料目錄 -OmniRoute stores data in the following locations by default: +OmniRoute 預設將資料存放於以下位置: -| Platform | Default Path | Override | -| ------------- | ----------------------------- | ------------------------- | -| Linux | `~/.omniroute/` | `DATA_DIR` env var | -| macOS | `~/.omniroute/` | `DATA_DIR` env var | -| Windows | `%APPDATA%/omniroute/` | `DATA_DIR` env var | -| Docker | `/app/data/` (mounted volume) | `DATA_DIR` env var | -| XDG-compliant | `$XDG_CONFIG_HOME/omniroute/` | `XDG_CONFIG_HOME` env var | +| 平台 | 預設路徑 | 覆蓋方式 | +| -------------- | ------------------------------ | ----------------------- | +| Linux | `~/.omniroute/` | `DATA_DIR` 環境變數 | +| macOS | `~/.omniroute/` | `DATA_DIR` 環境變數 | +| Windows | `%APPDATA%/omniroute/` | `DATA_DIR` 環境變數 | +| Docker | `/app/data/`(掛載資料卷) | `DATA_DIR` 環境變數 | +| XDG 相容模式 | `$XDG_CONFIG_HOME/omniroute/` | `XDG_CONFIG_HOME` 環境變數 | -### Files in the data directory +### 資料目錄中的檔案 -| File/Directory | Description | -| -------------------- | ------------------------------------------------- | -| `storage.sqlite` | Main database (providers, combos, settings, keys) | -| `storage.sqlite-wal` | SQLite write-ahead log (temporary) | -| `storage.sqlite-shm` | SQLite shared memory (temporary) | -| `call_logs/` | Request payload archives | -| `backups/` | Automatic database backups | -| `log.txt` | Legacy request log (optional) | +| 檔案/目錄 | 說明 | +| --------------------- | --------------------------------------- | +| `storage.sqlite` | 主要資料庫(供應商、組合、設定、金鑰) | +| `storage.sqlite-wal` | SQLite 預寫式日誌(暫存) | +| `storage.sqlite-shm` | SQLite 共享記憶體(暫存) | +| `call_logs/` | 請求承載記錄封存 | +| `backups/` | 自動資料庫備份 | +| `log.txt` | 舊版請求日誌(選用) | --- -## Verify Complete Removal +## 驗證是否完整移除 -After uninstalling, verify there are no remaining files: +解除安裝後,請確認無殘留檔案: ```bash -# Check for global npm package +# 檢查全域 npm 套件 npm list -g omniroute 2>/dev/null -# Check for data directory +# 檢查資料目錄 ls -la ~/.omniroute/ 2>/dev/null -# Check for running processes +# 檢查正在執行的程序 pgrep -f omniroute ``` -If any process is still running, stop it: +若仍有程序在執行,請將其停止: ```bash pkill -f omniroute diff --git a/docs/i18n/zh-TW/docs/guides/USER_GUIDE.md b/docs/i18n/zh-TW/docs/guides/USER_GUIDE.md index 72510c0ed6..f245c5194d 100644 --- a/docs/i18n/zh-TW/docs/guides/USER_GUIDE.md +++ b/docs/i18n/zh-TW/docs/guides/USER_GUIDE.md @@ -1,264 +1,290 @@ -# User Guide (中文 (簡體)) +--- +title: "使用者指南" +version: 3.8.40 +lastUpdated: 2026-06-28 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/USER_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/USER_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/USER_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/USER_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/USER_GUIDE.md) · 🇩🇰 [da](../../da/docs/USER_GUIDE.md) · 🇩🇪 [de](../../de/docs/USER_GUIDE.md) · 🇪🇸 [es](../../es/docs/USER_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/USER_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/USER_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/USER_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/USER_GUIDE.md) · 🇮🇱 [he](../../he/docs/USER_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/USER_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/USER_GUIDE.md) · 🇮🇩 [id](../../id/docs/USER_GUIDE.md) · 🇮🇹 [it](../../it/docs/USER_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/USER_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/USER_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/USER_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/USER_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/USER_GUIDE.md) · 🇳🇴 [no](../../no/docs/USER_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/USER_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/USER_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/USER_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/USER_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/USER_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/USER_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/USER_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/USER_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/USER_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/USER_GUIDE.md) · 🇮🇳 [te](../../te/docs/USER_GUIDE.md) · 🇹🇭 [th](../../th/docs/USER_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/USER_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/USER_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/USER_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/USER_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/USER_GUIDE.md) +# 使用者指南 + +🌐 **語言:** 🇺🇸 [English](./USER_GUIDE.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/guides/USER_GUIDE.md) | 🇪🇸 [Español](../i18n/es/docs/guides/USER_GUIDE.md) | 🇫🇷 [Français](../i18n/fr/docs/guides/USER_GUIDE.md) | 🇮🇹 [Italiano](../i18n/it/docs/guides/USER_GUIDE.md) | 🇷🇺 [Русский](../i18n/ru/docs/guides/USER_GUIDE.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/docs/guides/USER_GUIDE.md) | 🇩🇪 [Deutsch](../i18n/de/docs/guides/USER_GUIDE.md) | 🇮🇳 [हिन्दी](../i18n/in/docs/guides/USER_GUIDE.md) | 🇹🇭 [ไทย](../i18n/th/docs/guides/USER_GUIDE.md) | 🇺🇦 [Українська](../i18n/uk-UA/docs/guides/USER_GUIDE.md) | 🇸🇦 [العربية](../i18n/ar/docs/guides/USER_GUIDE.md) | 🇯🇵 [日本語](../i18n/ja/docs/guides/USER_GUIDE.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/docs/guides/USER_GUIDE.md) | 🇧🇬 [Български](../i18n/bg/docs/guides/USER_GUIDE.md) | 🇩🇰 [Dansk](../i18n/da/docs/guides/USER_GUIDE.md) | 🇫🇮 [Suomi](../i18n/fi/docs/guides/USER_GUIDE.md) | 🇮🇱 [עברית](../i18n/he/docs/guides/USER_GUIDE.md) | 🇭🇺 [Magyar](../i18n/hu/docs/guides/USER_GUIDE.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/docs/guides/USER_GUIDE.md) | 🇰🇷 [한국어](../i18n/ko/docs/guides/USER_GUIDE.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/docs/guides/USER_GUIDE.md) | 🇳🇱 [Nederlands](../i18n/nl/docs/guides/USER_GUIDE.md) | 🇳🇴 [Norsk](../i18n/no/docs/guides/USER_GUIDE.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/docs/guides/USER_GUIDE.md) | 🇷🇴 [Română](../i18n/ro/docs/guides/USER_GUIDE.md) | 🇵🇱 [Polski](../i18n/pl/docs/guides/USER_GUIDE.md) | 🇸🇰 [Slovenčina](../i18n/sk/docs/guides/USER_GUIDE.md) | 🇸🇪 [Svenska](../i18n/sv/docs/guides/USER_GUIDE.md) | 🇵🇭 [Filipino](../i18n/phi/docs/guides/USER_GUIDE.md) | 🇨🇿 [Čeština](../i18n/cs/docs/guides/USER_GUIDE.md) + +設定提供者、建立組合、整合 CLI 工具及部署 OmniRoute 的完整指南。 --- -Complete guide for configuring providers, creating combos, integrating CLI tools, and deploying OmniRoute. +## 目錄 + +- [價錢一覽](#-價錢一覽) +- [使用情境](#-使用情境) +- [提供者設定](#-提供者設定) +- [CLI 整合](#-cli-整合) +- [部署](#-部署) +- [可用模型](#-可用模型) +- [進階功能](#-進階功能) +- [自動路由(零配置)](#-自動路由零配置) +- [MCP 與 A2A 整合](#-mcp-與-a2a-整合) +- [技能系統](#-技能系統) +- [記憶系統](#-記憶系統) +- [Webhooks](#-webhooks) +- [雲端代理](#-雲端代理) +- [程式化管理](#-程式化管理) +- [內部 CLI](#-內部-cli) +- [桌面應用程式(Electron)](#-桌面應用程式electron) --- -## Table of Contents +## 💰 價錢一覽 -- [Pricing at a Glance](#-pricing-at-a-glance) -- [Use Cases](#-use-cases) -- [Provider Setup](#-provider-setup) -- [CLI Integration](#-cli-integration) -- [Deployment](#-deployment) -- [Available Models](#-available-models) -- [Advanced Features](#-advanced-features) +| 方案 | 提供者 | 費用 | 額度重置 | 最適合 | +| ------------------- | ----------------- | ----------- | -------------- | -------------------- | +| **💳 訂閱制** | Claude Code (Pro) | $20/月 | 5 小時 + 每週 | 已訂閱使用者 | +| | Codex (Plus/Pro) | $20-200/月 | 5 小時 + 每週 | OpenAI 使用者 | +| | GitHub Copilot | $10-19/月 | 每月 | GitHub 使用者 | +| **🔑 API 金鑰** | DeepSeek | 按用量計費 | 無 | 便宜的推理模型 | +| | Groq | 按用量計費 | 無 | 超快速推論 | +| | xAI (Grok) | 按用量計費 | 無 | Grok 4 推理 | +| | Mistral | 按用量計費 | 無 | 歐盟託管模型 | +| | Perplexity | 按用量計費 | 無 | 結合搜尋功能 | +| | Together AI | 按用量計費 | 無 | 開源模型 | +| | Fireworks AI | 按用量計費 | 無 | 快速 FLUX 圖片生成 | +| | Cerebras | 按用量計費 | 無 | 晶圓級速度 | +| | Cohere | 按用量計費 | 無 | Command R+ RAG | +| | NVIDIA NIM | 按用量計費 | 無 | 企業級模型 | +| | Baidu Qianfan | 按用量計費 | 無 | ERNIE 模型 | +| **💰 便宜方案** | GLM-4.7 | $0.6/百萬 | 每日上午 10 點 | 預算備用 | +| | MiniMax M2.1 | $0.2/百萬 | 5 小時滾動 | 最便宜的選擇 | +| | Kimi K2 | $9/月固定 | 每月 1,000 萬 | 可預測成本 | +| **🆓 免費方案** | Qoder | $0 | 無限制 | 8 個模型免費 | +| | Qwen | $0 | 無限制 | 3 個模型免費 | +| | Kiro | $0 | 約 50 點/月 | Claude 免費使用 | --- -## 💰 Pricing at a Glance +## 🎯 使用情境 -| Tier | Provider | Cost | Quota Reset | Best For | -| ------------------- | ----------------- | ----------- | ---------------- | -------------------- | -| **💳 SUBSCRIPTION** | Claude Code (Pro) | $20/mo | 5h + weekly | Already subscribed | -| | Codex (Plus/Pro) | $20-200/mo | 5h + weekly | OpenAI users | -| | GitHub Copilot | $10-19/mo | Monthly | GitHub users | -| **🔑 API KEY** | DeepSeek | Pay per use | None | Cheap reasoning | -| | Groq | Pay per use | None | Ultra-fast inference | -| | xAI (Grok) | Pay per use | None | Grok 4 reasoning | -| | Mistral | Pay per use | None | EU-hosted models | -| | Perplexity | Pay per use | None | Search-augmented | -| | Together AI | Pay per use | None | Open-source models | -| | Fireworks AI | Pay per use | None | Fast FLUX images | -| | Cerebras | Pay per use | None | Wafer-scale speed | -| | Cohere | Pay per use | None | Command R+ RAG | -| | NVIDIA NIM | Pay per use | None | Enterprise models | -| **💰 CHEAP** | GLM-4.7 | $0.6/1M | Daily 10AM | Budget backup | -| | MiniMax M2.1 | $0.2/1M | 5-hour rolling | Cheapest option | -| | Kimi K2 | $9/mo flat | 10M tokens/mo | Predictable cost | -| **🆓 FREE** | Qoder | $0 | Unlimited | 8 models free | -| | Qwen | $0 | Unlimited | 3 models free | -| | Kiro | $0 | Unlimited | Claude free | +### 情境 1:「我有 Claude Pro 訂閱」 - ---- - -## 🎯 Use Cases - -### Case 1: "I have Claude Pro subscription" - -**Problem:** Quota expires unused, rate limits during heavy coding +**問題:** 額度用不完、密集編碼時遇到速率限制 ``` -Combo: "maximize-claude" - 1. cc/claude-opus-4-7 (use subscription fully) - 2. glm/glm-4.7 (cheap backup when quota out) - 3. if/kimi-k2-thinking (free emergency fallback) +Combo:「maximize-claude」 + 1. cc/claude-opus-4-7 (充分利用訂閱) + 2. glm/glm-4.7 (額度用盡時的便宜備援) + 3. if/qwen3.8-max-preview (免費緊急備援) -Monthly cost: $20 (subscription) + ~$5 (backup) = $25 total -vs. $20 + hitting limits = frustration +每月費用:$20(訂閱)+ ~$5(備援)= $25 總計 +vs. $20 + 碰到限制 = 挫折感 ``` -### Case 2: "I want zero cost" +### 情境 2:「我想要零成本」 -**Problem:** Can't afford subscriptions, need reliable AI coding +**問題:** 負擔不起訂閱,需要可靠的 AI 編碼 ``` -Combo: "free-forever" - 1. if/kimi-k2-thinking (unlimited free) - 2. qw/qwen3-coder-plus (unlimited free) +Combo:「free-forever」 + 1. if/kimi-k2.7-code (無限制免費) + 2. kr/qwen3-coder-next (Kiro 免費備援) -Monthly cost: $0 -Quality: Production-ready models +每月費用:$0 +品質:可用於生產的模型 ``` -### Case 3: "I need 24/7 coding, no interruptions" +### 情境 3:「我需要 24/7 不中斷編碼」 -**Problem:** Deadlines, can't afford downtime +**問題:** 截止日期逼近,無法承受停機 ``` -Combo: "always-on" - 1. cc/claude-opus-4-7 (best quality) - 2. cx/gpt-5.2-codex (second subscription) - 3. glm/glm-4.7 (cheap, resets daily) - 4. minimax/MiniMax-M2.1 (cheapest, 5h reset) - 5. if/kimi-k2-thinking (free unlimited) +Combo:「always-on」 + 1. cc/claude-opus-4-7 (最佳品質) + 2. cx/gpt-5.5 (第二訂閱) + 3. glm/glm-4.7 (便宜,每日重置) + 4. minimax/MiniMax-M2.1 (最便宜,5 小時重置) + 5. if/deepseek-v4-flash (免費無限制) -Result: 5 layers of fallback = zero downtime -Monthly cost: $20-200 (subscriptions) + $10-20 (backup) +結果:5 層備援 = 零停機 +每月費用:$20-200(訂閱)+ $10-20(備援) ``` -### Case 4: "I want FREE AI in OpenClaw" +### 情境 4:「我想要 OpenClaw 中的免費 AI」 -**Problem:** Need AI assistant in messaging apps, completely free +**問題:** 需要在即時通訊應用程式中使用 AI 助手,完全免費 ``` -Combo: "openclaw-free" - 1. if/glm-4.7 (unlimited free) - 2. if/minimax-m2.1 (unlimited free) - 3. if/kimi-k2-thinking (unlimited free) +Combo:「openclaw-free」 + 1. if/qwen3.8-max-preview (無限制免費) + 2. if/deepseek-v4-flash (無限制免費) + 3. if/kimi-k2.7-code (無限制免費) -Monthly cost: $0 -Access via: WhatsApp, Telegram, Slack, Discord, iMessage, Signal... +每月費用:$0 +可透過:WhatsApp、Telegram、Slack、Discord、iMessage、Signal... ``` --- -## 📖 Provider Setup +## 📖 提供者設定 -### 🔐 Subscription Providers +### 🔐 訂閱制提供者 -#### Claude Code (Pro/Max) +#### Claude Code(Pro/Max) ```bash -Dashboard → Providers → Connect Claude Code -→ OAuth login → Auto token refresh -→ 5-hour + weekly quota tracking +控制台 → 提供者 → 連接 Claude Code +→ OAuth 登入 → 自動權杖重新整理 +→ 5 小時 + 每週額度追蹤 -Models: +模型: cc/claude-opus-4-7 - cc/claude-sonnet-4-5-20250929 + cc/claude-sonnet-4-6 cc/claude-haiku-4-5-20251001 ``` -**Pro Tip:** Use Opus for complex tasks, Sonnet for speed. OmniRoute tracks quota per model! +**小撇步:** 複雜任務使用 Opus,追求速度使用 Sonnet。OmniRoute 會追蹤每個模型的額度! -#### OpenAI Codex (Plus/Pro) +Claude 及與 Claude Code 相容的路由會保留 Opus 和 Sonnet 模型的 `max` 思考強度設定。 +Haiku 模型不接受 `max` 思考強度層級,因此 OmniRoute 會在將請求發送給上游之前, +將該設定降級為較高的思考預算。 + +#### OpenAI Codex(Plus/Pro) ```bash -Dashboard → Providers → Connect Codex -→ OAuth login (port 1455) -→ 5-hour + weekly reset +控制台 → 提供者 → 連接 Codex +→ OAuth 登入(連接埠 1455) +→ 5 小時 + 每週重置 -Models: - cx/gpt-5.2-codex - cx/gpt-5.1-codex-max +模型: + cx/gpt-5.5 + cx/gpt-5.4 + cx/gpt-5.3-codex + cx/gpt-5.3-codex-spark ``` - - #### GitHub Copilot ```bash -Dashboard → Providers → Connect GitHub -→ OAuth via GitHub -→ Monthly reset (1st of month) +控制台 → 提供者 → 連接 GitHub +→ 透過 GitHub OAuth +→ 每月重置(每月 1 日) -Models: - gh/gpt-5 - gh/claude-4.5-sonnet +模型: + gh/gpt-5.5 + gh/gpt-5.4 + gh/claude-sonnet-4.6 + gh/claude-opus-4.7 gh/gemini-3.1-pro-preview ``` -### 💰 Cheap Providers +### 💰 便宜提供者 -#### GLM-4.7 (Daily reset, $0.6/1M) +#### GLM-4.7(每日重置,$0.6/百萬) -1. Sign up: [Zhipu AI](https://open.bigmodel.cn/) -2. Get API key from Coding Plan -3. Dashboard → Add API Key: Provider: `glm`, API Key: `your-key` +1. 註冊:[智譜 AI](https://open.bigmodel.cn) +2. 從 Coding Plan 取得 API 金鑰 +3. 控制台 → 新增 API 金鑰:提供者:`glm`,API 金鑰:`your-key` -**Use:** `glm/glm-4.7` — **Pro Tip:** Coding Plan offers 3× quota at 1/7 cost! Reset daily 10:00 AM. +**使用:** `glm/glm-4.7` — **小撇步:** Coding Plan 提供 3 倍額度,僅需 1/7 費用!每日上午 10:00 重置。 -#### MiniMax M2.1 (5h reset, $0.20/1M) +#### MiniMax M2.1(5 小時重置,$0.20/百萬) -1. Sign up: [MiniMax](https://www.minimax.io/) -2. Get API key → Dashboard → Add API Key +1. 註冊:[MiniMax](https://www.minimax.io) +2. 取得 API 金鑰 → 控制台 → 新增 API 金鑰 -**Use:** `minimax/MiniMax-M2.1` — **Pro Tip:** Cheapest option for long context (1M tokens)! +**使用:** `minimax/MiniMax-M2.1` — **小撇步:** 長上下文(100 萬權杖)的最便宜選擇! -#### Kimi K2 ($9/month flat) +#### Kimi K2(每月 $9 固定) -1. Subscribe: [Moonshot AI](https://platform.moonshot.ai/) -2. Get API key → Dashboard → Add API Key +1. 訂閱:[Moonshot AI](https://platform.moonshot.ai) +2. 取得 API 金鑰 → 控制台 → 新增 API 金鑰 -**Use:** `kimi/kimi-latest` — **Pro Tip:** Fixed $9/month for 10M tokens = $0.90/1M effective cost! +**使用:** `kimi/kimi-k2.5` — **小撇步:** 每月 $9 固定費用,1,000 萬權杖 = 每百萬權杖 $0.90 有效成本! -### 🆓 FREE Providers +#### 百度千帆 / ERNIE -#### Qoder (8 FREE models) +1. 註冊:[百度 AI 雲千帆](https://cloud.baidu.com/product/wenxinworkshop) +2. 建立千帆 API 金鑰 → 控制台 → 新增 API 金鑰:提供者:`qianfan` + +**使用:** `qianfan/ernie-5.1`、`qianfan/ernie-x1.1`,或其他千帆 OpenAI 相容模型 ID。 + +### 🆓 免費提供者 + +無需驗證的免費提供者在其提供者頁面上有一個**無需身份驗證**的開關。 +關閉該開關會停用該提供者,將其從「提供者配置」/「精簡檢視」中移除, +並從 `/v1/models` 中移除其模型。 + +#### Qoder(9 個免費模型) ```bash -Dashboard → Connect Qoder → OAuth login → Unlimited usage +控制台 → 連接 Qoder → OAuth 登入 → 無限制使用 -Models: if/kimi-k2-thinking, if/qwen3-coder-plus, if/glm-4.7, if/minimax-m2, if/deepseek-r1 +模型:if/qwen3.8-max-preview, if/qwen3.7-max, if/qwen3.7-plus, if/kimi-k3, if/kimi-k2.7-code, if/glm-5.2, if/deepseek-v4-pro, if/deepseek-v4-flash, if/minimax-m3 ``` -#### Qwen (3 FREE models) +#### Kiro(Claude 免費使用) ```bash -Dashboard → Connect Qwen → Device code auth → Unlimited usage +控制台 → 連接 Kiro → AWS Builder ID 或 Google/GitHub → 約 50 點/月 -Models: qw/qwen3-coder-plus, qw/qwen3-coder-flash -``` - -#### Kiro (Claude FREE) - -```bash -Dashboard → Connect Kiro → AWS Builder ID or Google/GitHub → Unlimited - -Models: kr/claude-sonnet-4.5, kr/claude-haiku-4.5 +模型:kr/claude-sonnet-4.5, kr/claude-haiku-4.5 ``` --- -## 🎨 Combos +## 🎨 組合 -You can reorder combo cards directly in **Dashboard → Combos** by dragging the handle on each card. The order is stored in SQLite and restored on reload. +您可以直接在**控制台 → 組合**中透過拖曳卡片上的手柄來重新排序組合卡片。順序會儲存在 SQLite 中,並在重新載入時恢復。 -### Example 1: Maximize Subscription → Cheap Backup +### 範例 1:最大化訂閱 → 便宜備援 ``` -Dashboard → Combos → Create New +控制台 → 組合 → 建立新組合 -Name: premium-coding -Models: - 1. cc/claude-opus-4-7 (Subscription primary) - 2. glm/glm-4.7 (Cheap backup, $0.6/1M) - 3. minimax/MiniMax-M2.1 (Cheapest fallback, $0.20/1M) +名稱:premium-coding +模型: + 1. cc/claude-opus-4-7 (訂閱主要) + 2. glm/glm-4.7 (便宜備援,$0.6/百萬) + 3. minimax/MiniMax-M2.7 (最便宜備援,$0.3/百萬) -Use in CLI: premium-coding +在 CLI 中使用:premium-coding ``` -### Example 2: Free-Only (Zero Cost) +### 範例 2:僅免費(零成本) ``` -Name: free-combo -Models: - 1. if/kimi-k2-thinking (unlimited) - 2. qw/qwen3-coder-plus (unlimited) +名稱:free-combo +模型: + 1. if/kimi-k2.7-code(無限制) + 2. kr/qwen3-coder-next(Kiro 免費備援) -Cost: $0 forever! +費用:永遠 $0! ``` --- -## 🔧 CLI Integration +## 🔧 CLI 整合 ### Cursor IDE ``` -Settings → Models → Advanced: - OpenAI API Base URL: http://localhost:20128/v1 - OpenAI API Key: [from omniroute dashboard] - Model: cc/claude-opus-4-7 +設定 → 模型 → 進階: + OpenAI API 基礎網址:http://localhost:20128/v1 + OpenAI API 金鑰:[取自 omniroute 控制台] + 模型:cc/claude-opus-4-7 ``` ### Claude Code -Edit `~/.claude/config.json`: +編輯 `~/.claude/settings.json`: ```json { - "anthropic_api_base": "http://localhost:20128/v1", - "anthropic_api_key": "your-omniroute-api-key" + "env": { + "ANTHROPIC_BASE_URL": "http://localhost:20128", + "ANTHROPIC_AUTH_TOKEN": "your-omniroute-api-key" + } } ``` +在此處使用與 Claude 相容的根端點。請勿在 `ANTHROPIC_BASE_URL` 後附加 `/v1`。 + ### Codex CLI ```bash @@ -269,13 +295,13 @@ codex "your prompt" ### OpenClaw -Edit `~/.openclaw/openclaw.json`: +編輯 `~/.openclaw/openclaw.json`: ```json { "agents": { "defaults": { - "model": { "primary": "omniroute/if/glm-4.7" } + "model": { "primary": "omniroute/if/kimi-k2.7-code" } } }, "models": { @@ -284,59 +310,59 @@ Edit `~/.openclaw/openclaw.json`: "baseUrl": "http://localhost:20128/v1", "apiKey": "your-omniroute-api-key", "api": "openai-completions", - "models": [{ "id": "if/glm-4.7", "name": "glm-4.7" }] + "models": [{ "id": "if/kimi-k2.7-code", "name": "Kimi K2.7 Code" }] } } } } ``` -**Or use Dashboard:** CLI Tools → OpenClaw → Auto-config +**或使用控制台:** CLI 工具 → OpenClaw → 自動配置 ### Cline / Continue / RooCode ``` -Provider: OpenAI Compatible -Base URL: http://localhost:20128/v1 -API Key: [from dashboard] -Model: cc/claude-opus-4-7 +提供者:OpenAI 相容 +基礎網址:http://localhost:20128/v1 +API 金鑰:[取自控制台] +模型:cc/claude-opus-4-7 ``` --- -## 部署 +## 🚀 部署 -### Global npm install (Recommended) +### 全域 npm 安裝(建議) ```bash npm install -g omniroute -# Create config directory +# 建立配置目錄 mkdir -p ~/.omniroute -# Create .env file (see .env.example) +# 建立 .env 檔案(參閱 .env.example) cp .env.example ~/.omniroute/.env -# Start server +# 啟動伺服器 omniroute -# Or with custom port: +# 或使用自訂連接埠: omniroute --port 3000 ``` -The CLI automatically loads `.env` from `~/.omniroute/.env` or `./.env`. +CLI 會自動從 `~/.omniroute/.env` 或 `./.env` 載入 `.env`。 -### Uninstalling +### 解除安裝 -When you no longer need OmniRoute, we provide two quick scripts for a clean removal: +當您不再需要 OmniRoute 時,我們提供兩個快速腳本進行乾淨移除: -| Command | Action | -| ------------------------ | ----------------------------------------------------------------------------------- | -| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. | -| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. | +| 指令 | 動作 | +| ------------------------ | ------------------------------------------------------------------------------------ | +| `npm run uninstall` | 移除系統應用程式,但**保留您的資料庫和配置**於 `~/.omniroute`。 | +| `npm run uninstall:full` | 移除應用程式並永久**清除所有配置、金鑰和資料庫**。 | -> Note: To run these commands, navigate to the OmniRoute project folder (if you cloned it) and run them. Alternatively, if globally installed, you can simply run `npm uninstall -g omniroute`. +> 注意:若要執行這些指令,請導航至 OmniRoute 專案資料夾(如果您是透過複製倉庫的方式)並執行。或者,若是全域安裝,您可以直接執行 `npm uninstall -g omniroute`。 -### VPS Deployment +### VPS 部署 ```bash git clone https://github.com/diegosouzapw/OmniRoute.git @@ -352,25 +378,25 @@ export NEXT_PUBLIC_BASE_URL="http://localhost:20128" export API_KEY_SECRET="endpoint-proxy-api-key-secret" npm run start -# Or: pm2 start npm --name omniroute -- start +# 或:pm2 start npm --name omniroute -- start ``` -### PM2 Deployment (Low Memory) +### PM2 部署(低記憶體) -For servers with limited RAM, use the memory limit option: +對於記憶體有限的伺服器,可使用記憶體限制選項: ```bash -# With 512MB limit (default) +# 使用 512MB 限制(預設) pm2 start npm --name omniroute -- start -# Or with custom memory limit +# 或使用自訂記憶體限制 OMNIROUTE_MEMORY_MB=512 pm2 start npm --name omniroute -- start -# Or using ecosystem.config.js +# 或使用 ecosystem.config.js pm2 start ecosystem.config.js ``` -Create `ecosystem.config.js`: +建立 `ecosystem.config.js`: ```javascript module.exports = { @@ -395,26 +421,26 @@ module.exports = { ### Docker ```bash -# Build image (default = runner-cli with codex/claude/droid preinstalled) +# 建立映像檔(預設 = runner-cli 搭配內建的 codex/claude/droid) docker build -t omniroute:cli . -# Portable mode (recommended) +# 可攜式模式(建議) docker run -d --name omniroute -p 20128:20128 --env-file ./.env -v omniroute-data:/app/data omniroute:cli ``` -For host-integrated mode with CLI binaries, see the Docker section in the main docs. +如需整合主機模式搭配 CLI 二進位檔,請參閱主要文件中的 Docker 章節。 -### Void Linux (xbps-src) +### Void Linux(xbps-src) -Void Linux users can package and install OmniRoute natively using the `xbps-src` cross-compilation framework. This automates the Node.js standalone build along with the required `better-sqlite3` native bindings. +Void Linux 使用者可以使用 `xbps-src` 跨編譯框架,以原生方式封裝並安裝 OmniRoute。這會自動化 Node.js 獨立建置以及所需的 `better-sqlite3` 原生繫結。

-View xbps-src template +檢視 xbps-src 模板 ```bash # Template file for 'omniroute' pkgname=omniroute -version=3.2.4 +version=3.8.0 revision=1 hostmakedepends="nodejs python3 make" depends="openssl" @@ -504,107 +530,148 @@ post_install() {
-### Environment Variables +### 環境變數 -| Variable | Default | Description | -| --------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | `omniroute-default-secret-change-me` | JWT signing secret (**change in production**) | -| `INITIAL_PASSWORD` | `123456` | First login password | -| `DATA_DIR` | `~/.omniroute` | Data directory (db, usage, logs) | -| `PORT` | framework default | Service port (`20128` in examples) | -| `HOSTNAME` | framework default | Bind host (Docker defaults to `0.0.0.0`) | -| `NODE_ENV` | runtime default | Set `production` for deploy | -| `BASE_URL` | `http://localhost:20128` | Server-side internal base URL | -| `CLOUD_URL` | `https://omniroute.dev` | Cloud sync endpoint base URL | -| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | HMAC secret for generated API keys | -| `REQUIRE_API_KEY` | `false` | Enforce Bearer API key on `/v1/*` | -| `ALLOW_API_KEY_REVEAL` | `false` | Allow Api Manager to copy full API keys on demand | -| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | Server-side refresh cadence for cached Provider Limits data; UI refresh buttons still trigger manual sync | -| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | Disable automatic SQLite snapshots before writes/import/restore; manual backups still work | -| `APP_LOG_TO_FILE` | `true` | Enables application and audit log output to disk | -| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) | -| `CLOUDFLARED_BIN` | unset | Use an existing `cloudflared` binary instead of managed download | -| `CLOUDFLARED_PROTOCOL` | `http2` | Transport for managed Quick Tunnels (`http2`, `quic`, or `auto`) | -| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit in MB | -| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries | -| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries | +| 變數 | 預設值 | 說明 | +| --------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | `omniroute-default-secret-change-me` | JWT 簽署密鑰(**生產環境務必修改**) | +| `INITIAL_PASSWORD` | `CHANGEME` | 首次登入密碼 | +| `DATA_DIR` | `~/.omniroute` | 資料目錄(資料庫、用量、日誌) | +| `PORT` | framework 預設 | 服務連接埠(範例中為 `20128`) | +| `HOSTNAME` | framework 預設 | 繫結主機(Docker 預設為 `0.0.0.0`) | +| `NODE_ENV` | runtime 預設 | 部署時設定為 `production` | +| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | 公開的基礎網址,顯示於控制台並暴露給伺服器(取代舊的 `BASE_URL`) | +| `NEXT_PUBLIC_CLOUD_URL` | `https://omniroute.dev` | 雲端同步端點基礎網址(取代舊的 `CLOUD_URL`) | +| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | 用於產生 API 金鑰的 HMAC 密鑰 | +| `REQUIRE_API_KEY` | `false` | 對 `/v1/*` 強制要求 Bearer API 金鑰 | +| `ALLOW_API_KEY_REVEAL` | `false` | 允許已驗證的控制台使用者按需顯示完整儲存的 API 金鑰值 | +| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | 提供者限制快取資料的伺服器端重新整理頻率;UI 重新整理按鈕仍會觸發手動同步 | +| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | 停用在寫入/匯入/還原前自動建立 SQLite 快照;手動備份仍可正常使用 | +| `APP_LOG_TO_FILE` | `true` | 啟用將應用程式和稽核日誌輸出至磁碟 | +| `AUTH_COOKIE_SECURE` | `false` | 強制使用安全 `Secure` 認證 Cookie(在 HTTPS 反向代理後方) | +| `CLOUDFLARED_BIN` | 未設定 | 使用既有的 `cloudflared` 二進位檔而非受管理的下載 | +| `CLOUDFLARED_PROTOCOL` | `http2` | 受管理快速隧道的傳輸協定(`http2`、`quic` 或 `auto`) | +| `OMNIROUTE_MEMORY_MB` | `512` | Node.js 堆積限制(MB) | +| `PROMPT_CACHE_MAX_SIZE` | `50` | 提示快取最大條目數 | +| `SEMANTIC_CACHE_MAX_SIZE` | `100` | 語意快取最大條目數 | -For the full environment variable reference, see the [README](../README.md). +如需完整的環境變數參考,請參閱 [README](../README.md)。 --- -## 📊 Available Models +## 📊 可用模型
-View all available models +檢視所有可用模型 -**Claude Code (`cc/`)** — Pro/Max: `cc/claude-opus-4-7`, `cc/claude-sonnet-4-5-20250929`, `cc/claude-haiku-4-5-20251001` +> 以下列表取自 v3.8.0 的 `open-sse/config/providerRegistry.ts`。雲端目錄(Gemini、OpenRouter 等)會動態同步 — 如需完整的即時目錄,請開啟**控制台 → 提供者 → [提供者] → 可用模型**或呼叫 `GET /api/models/catalog`。 -**Codex (`cx/`)** — Plus/Pro: `cx/gpt-5.2-codex`, `cx/gpt-5.1-codex-max` +**Claude Code(`cc/`)** — Pro/Max OAuth:`cc/claude-opus-4-8`, `cc/claude-opus-4-7`, `cc/claude-opus-4-6`, `cc/claude-opus-4-5-20251101`, `cc/claude-sonnet-4-6`, `cc/claude-sonnet-4-5-20250929`, `cc/claude-haiku-4-5-20251001` +**Codex(`cx/`)** — Plus/Pro OAuth:`cx/gpt-5.5`(+ 強度層級:`gpt-5.5-xhigh`, `gpt-5.5-high`, `gpt-5.5-medium`, `gpt-5.5-low`), `cx/gpt-5.4`, `cx/gpt-5.4-mini`, `cx/gpt-5.3-codex`, `cx/gpt-5.3-codex-spark` -**GitHub Copilot (`gh/`)**: `gh/gpt-5`, `gh/claude-4.5-sonnet` +**GitHub Copilot(`gh/`)** — OAuth:`gh/gpt-5.5`, `gh/gpt-5.4`, `gh/gpt-5.4-mini`, `gh/gpt-5-mini`, `gh/gpt-5.3-codex`, `gh/claude-opus-4.7`, `gh/claude-opus-4.6`, `gh/claude-opus-4-5-20251101`, `gh/claude-sonnet-4.6`, `gh/claude-sonnet-4.5`, `gh/claude-haiku-4.5`, `gh/gemini-3.1-pro-preview`, `gh/gemini-3-flash-preview`, `gh/oswe-vscode-prime` -**GLM (`glm/`)** — $0.6/1M: `glm/glm-4.7` +**Kiro(`kr/`)** — 免費 OAuth:`kr/auto-kiro`, `kr/claude-opus-4.7`, `kr/claude-opus-4.6`, `kr/claude-sonnet-4.6`, `kr/claude-sonnet-4.5`, `kr/claude-haiku-4.5`, `kr/deepseek-3.2`, `kr/minimax-m2.5`, `kr/minimax-m2.1`, `kr/glm-5`, `kr/qwen3-coder-next` -**MiniMax (`minimax/`)** — $0.2/1M: `minimax/MiniMax-M2.1` +**Qoder(`if/`)** — 免費 OAuth:`if/qwen3.8-max-preview`, `if/qwen3.7-max`, `if/qwen3.7-plus`, `if/kimi-k3`, `if/kimi-k2.7-code`, `if/glm-5.2`, `if/deepseek-v4-pro`, `if/deepseek-v4-flash`, `if/minimax-m3` -**Qoder (`if/`)** — FREE: `if/kimi-k2-thinking`, `if/qwen3-coder-plus`, `if/deepseek-r1` +**GLM(`glm/`, `glm-cn/`, `zai/`, `glmt/`)** — $0.2–0.6/百萬:`glm/glm-5.1`, `glm/glm-5`, `glm/glm-5-turbo`, `glm/glm-4.7`, `glm/glm-4.7-flash`, `glm/glm-4.6`, `glm/glm-4.6v`, `glm/glm-4.5`, `glm/glm-4.5v`, `glm/glm-4.5-air` -**Qwen (`qw/`)** — FREE: `qw/qwen3-coder-plus`, `qw/qwen3-coder-flash` +**MiniMax(`minimax/`, `minimax-cn/`)** — $0.2/百萬:`minimax/MiniMax-M2.7`, `minimax/MiniMax-M2.7-highspeed`, `minimax/MiniMax-M2.5`, `minimax/MiniMax-M2.5-highspeed` -**Kiro (`kr/`)** — FREE: `kr/claude-sonnet-4.5`, `kr/claude-haiku-4.5` +**Kimi(`kimi/`, `kimi-coding/`, `kimi-coding-apikey/`)** — 每月 $9 固定或按用量計費:`kimi/kimi-k2.6`, `kimi/kimi-k2.5` -**DeepSeek (`ds/`)**: `ds/deepseek-chat`, `ds/deepseek-reasoner` +**DeepSeek(`ds/`)** — API 金鑰:`ds/deepseek-v4-pro`, `ds/deepseek-v4-flash` -**Groq (`groq/`)**: `groq/llama-3.3-70b-versatile`, `groq/llama-4-maverick-17b-128e-instruct` +**Groq(`groq/`)** — 超快速:`groq/llama-3.3-70b-versatile`, `groq/meta-llama/llama-4-maverick-17b-128e-instruct`, `groq/qwen/qwen3-32b`, `groq/openai/gpt-oss-120b` -**xAI (`xai/`)**: `xai/grok-4`, `xai/grok-4-0709-fast-reasoning`, `xai/grok-code-mini` +**xAI(`xai/`)** — Grok 原生:`xai/grok-4.3`, `xai/grok-4.20-multi-agent-0309`, `xai/grok-4.20-0309-reasoning`, `xai/grok-4.20-0309-non-reasoning` -**Mistral (`mistral/`)**: `mistral/mistral-large-2501`, `mistral/codestral-2501` +**Mistral(`mistral/`)** — 歐盟託管:`mistral/mistral-large-latest`, `mistral/mistral-medium-3-5`, `mistral/mistral-small-latest`, `mistral/devstral-latest`, `mistral/codestral-latest` -**Perplexity (`pplx/`)**: `pplx/sonar-pro`, `pplx/sonar` +**Perplexity(`pplx/`)** — 結合搜尋功能:`pplx/sonar-deep-research`, `pplx/sonar-reasoning-pro`, `pplx/sonar-pro`, `pplx/sonar` -**Together AI (`together/`)**: `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` +**Together AI(`together/`)** — 開源:`together/meta-llama/Llama-3.3-70B-Instruct-Turbo-Free`(免費), `together/meta-llama/Llama-Vision-Free`, `together/deepseek-ai/DeepSeek-R1-Distill-Llama-70B-Free`, `together/deepseek-ai/DeepSeek-R1`, `together/Qwen/Qwen3-235B-A22B`, `together/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` -**Fireworks AI (`fireworks/`)**: `fireworks/accounts/fireworks/models/deepseek-v3p1` +**Fireworks AI(`fireworks/`)** — 快速推論:`fireworks/accounts/fireworks/models/kimi-k2p6`, `fireworks/accounts/fireworks/models/minimax-m2p7`, `fireworks/accounts/fireworks/models/qwen3p6-plus`, `fireworks/accounts/fireworks/models/glm-5p1`, `fireworks/accounts/fireworks/models/deepseek-v4-pro` -**Cerebras (`cerebras/`)**: `cerebras/llama-3.3-70b` +**Cerebras(`cerebras/`)** — 晶圓級:`cerebras/zai-glm-4.7`, `cerebras/gpt-oss-120b` -**Cohere (`cohere/`)**: `cohere/command-r-plus-08-2024` +**Cohere(`cohere/`)** — 專注 RAG:`cohere/command-a-reasoning-08-2025`, `cohere/command-a-vision-07-2025`, `cohere/command-a-03-2025`, `cohere/command-r-08-2024` -**NVIDIA NIM (`nvidia/`)**: `nvidia/nvidia/llama-3.3-70b-instruct` +**NVIDIA NIM(`nvidia/`)** — 企業級:`nvidia/z-ai/glm-5.1`, `nvidia/minimaxai/minimax-m2.7`, `nvidia/google/gemma-4-31b-it`, `nvidia/mistralai/mistral-small-4-119b-2603`, `nvidia/mistralai/mistral-large-3-675b-instruct-2512`, `nvidia/qwen/qwen3.5-397b-a17b`, `nvidia/deepseek-ai/deepseek-v4-pro`, `nvidia/openai/gpt-oss-120b`, `nvidia/nvidia/nemotron-3-super-120b-a12b` + +**百度千帆(`qianfan/`)** — ERNIE:`qianfan/ernie-5.1`, `qianfan/ernie-5.0-thinking-latest`, `qianfan/ernie-x1.1` + +**Ollama Cloud(`ollama-cloud/`)**:`ollama-cloud/deepseek-v4-pro`, `ollama-cloud/deepseek-v4-flash`, `ollama-cloud/kimi-k2.6`, `ollama-cloud/glm-5.1`, `ollama-cloud/minimax-m2.7`, `ollama-cloud/gemma4:31b`, `ollama-cloud/qwen3.5:397b` + +**Gemini(Google Cloud `gemini/`)**:依 API 金鑰從 Google 即時同步 — 無靜態清單。在**控制台 → 提供者**中連接金鑰,然後使用**可用模型**匯入當前目錄(例如 `gemini/gemini-3-pro`, `gemini/gemini-3-flash`)。 + +**其他相容提供者**(部分):`cohere`, `databricks`, `snowflake`, `together`, `vertex`, `alibaba`, `alibaba-cn`, `bedrock`(透過 `aws-bedrock`), `azure-ai`, `openrouter`(直通目錄), `siliconflow`, `hyperbolic`, `huggingface`, `featherless-ai`, `cloudflare-ai`, `scaleway`, `deepinfra`, `vercel-ai-gateway`, `bazaarlink`, `friendliai`, `nous-research`, `reka`, `volcengine`, `ai21`, `gigachat`。每個都在 `providerRegistry.ts` 中維護自己的模型清單,並可在提供者公開 `/models` 端點時自動同步。 + +**關於模型 ID 的說明:** OmniRoute 使用提供者原生 ID(`claude-opus-4-8`, `gpt-5.5`, `glm-5.1`, `MiniMax-M2.7`, `kimi-k2.5`, `grok-4.20-0309-reasoning`)。部分 ID 包含帶點號的版本號,因為上游 API 期望如此。如果以上未列出某個模型,請執行 `omniroute models --search ` 或存取 `GET /api/models/catalog` 以確認可用性。
--- -## 🧩 Advanced Features +## 🧩 進階功能 -### Custom Models +### 自訂模型 -Add any model ID to any provider without waiting for an app update: +無需等待應用程式更新,即可將任何模型 ID 新增至任何提供者: ```bash -# Via API +# 透過 API curl -X POST http://localhost:20128/api/provider-models \ -H "Content-Type: application/json" \ - -d '{"provider": "openai", "modelId": "gpt-4.5-preview", "modelName": "GPT-4.5 Preview"}' + -d '{"provider": "openai", "modelId": "gpt-5.2", "modelName": "GPT-5.2"}' -# List: curl http://localhost:20128/api/provider-models?provider=openai -# Remove: curl -X DELETE "http://localhost:20128/api/provider-models?provider=openai&model=gpt-4.5-preview" +# 列出:curl http://localhost:20128/api/provider-models?provider=openai +# 移除:curl -X DELETE "http://localhost:20128/api/provider-models?provider=openai&model=gpt-5.2" ``` -Or use Dashboard: **Providers → [Provider] → Custom Models**. +或使用控制台:**提供者 → [提供者] → 自訂模型**。 -Notes: +注意: -- OpenRouter and OpenAI/Anthropic-compatible providers are managed from **Available Models** only. Manual add, import, and auto-sync all land in the same available-model list, so there is no separate Custom Models section for those providers. -- The **Custom Models** section is intended for providers that do not expose managed available-model imports. +- OpenRouter 和 OpenAI/Anthropic 相容提供者僅透過**可用模型**管理。手動新增、匯入和自動同步都會匯入同一個可用模型清單,因此這些提供者沒有獨立的自訂模型區塊。 +- **自訂模型**區塊專為不提供受管可用模型匯入的提供者而設計。 -### Dedicated Provider Routes +### 串聯 OmniRoute 對等節點 -Route requests directly to a specific provider with model validation: +另一個 OmniRoute 閘道可以作為**自訂 OpenAI 相容**提供者加入。使用 +對等節點的 `/v1` 基礎網址以及由該對等節點簽發的專用最小權限 API 金鑰。 + +對於雙向或多跳鍊,請在每個閘道上啟用選擇性迴圈防護: + +```bash +# gateway-a +OMNIROUTE_INSTANCE_ID=gateway-a +OMNIROUTE_PEER_URLS=http://gateway-b:20128/v1 +OMNIROUTE_PEER_MAX_HOPS=4 +``` + +```bash +# gateway-b +OMNIROUTE_INSTANCE_ID=gateway-b +OMNIROUTE_PEER_URLS=http://gateway-a:20128/v1 +OMNIROUTE_PEER_MAX_HOPS=4 +``` + +只有發送到明確允許清單中的對等節點網址的請求,才會收到 +`X-OmniRoute-Peer-Trace` 標頭。閘道在遇到重複的執行個體 ID 或用盡跳數 +預算時,會回應 `508 Loop Detected`;一般上游提供者不會收到任何對等節點中繼資料。 + +對等串聯並非資料庫複寫或主機容錯移轉。每個閘道維護獨立的 +SQLite 狀態、快取、速率計數器和工作階段。請使用健康檢查反向代理或用戶端 +容錯移轉來實現主動/被動或主動/主動的高可用性,且絕不要將一個 SQLite 資料庫 +掛載到多個正在執行的 OmniRoute 執行個體中。 + +### 專用提供者路由 + +將請求直接路由到特定提供者,並附帶模型驗證: ```bash POST http://localhost:20128/v1/providers/openai/chat/completions @@ -612,338 +679,579 @@ POST http://localhost:20128/v1/providers/openai/embeddings POST http://localhost:20128/v1/providers/fireworks/images/generations ``` -The provider prefix is auto-added if missing. Mismatched models return `400`. +若缺少提供者前綴,會自動補上。模型不符時會回傳 `400` 錯誤。 -### Network Proxy Configuration +### 網路代理配置 ```bash -# Set global proxy +# 設定全域代理 curl -X PUT http://localhost:20128/api/settings/proxy \ -d '{"global": {"type":"http","host":"proxy.example.com","port":"8080"}}' -# Per-provider proxy +# 各提供者獨立代理 curl -X PUT http://localhost:20128/api/settings/proxy \ -d '{"providers": {"openai": {"type":"socks5","host":"proxy.example.com","port":"1080"}}}' -# Test proxy +# 測試代理 curl -X POST http://localhost:20128/api/settings/proxy/test \ -d '{"proxy":{"type":"socks5","host":"proxy.example.com","port":"1080"}}' ``` -**Precedence:** Key-specific → Combo-specific → Provider-specific → Global → Environment. +**優先順序:** 金鑰特定 → 組合特定 → 提供者特定 → 全域 → 環境變數。 -### Model Catalog API +### 模型目錄 API ```bash curl http://localhost:20128/api/models/catalog ``` -Returns models grouped by provider with types (`chat`, `embedding`, `image`). +回傳按提供者分組的模型,包含類型(`chat`、`embedding`、`image`)。 -### Cloud Sync +### 雲端同步 -- Sync providers, combos, and settings across devices -- Automatic background sync with timeout + fail-fast -- Prefer server-side `BASE_URL`/`CLOUD_URL` in production +- 跨裝置同步提供者、組合和設定 +- 自動背景同步,附帶超時和快速失敗機制 +- 生產環境中建議使用伺服器端的 `NEXT_PUBLIC_BASE_URL` / `NEXT_PUBLIC_CLOUD_URL` -### Cloudflare Quick Tunnel +### Cloudflare 快速隧道 -- Available in **Dashboard → Endpoints** for Docker and other self-hosted deployments -- Creates a temporary `https://*.trycloudflare.com` URL that forwards to your current OpenAI-compatible `/v1` endpoint -- First enable installs `cloudflared` only when needed; later restarts reuse the same managed binary -- Quick Tunnels are not auto-restored after an OmniRoute or container restart; re-enable them from the dashboard when needed -- Tunnel URLs are ephemeral and change every time you stop/start the tunnel -- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained containers -- Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want to override the managed transport choice -- Set `CLOUDFLARED_BIN` if you prefer using a preinstalled `cloudflared` binary instead of the managed download +- 在**控制台 → 端點**中可用,適用於 Docker 和其他自架部署 +- 建立一個暫時的 `https://*.trycloudflare.com` 網址,轉發到您目前的 OpenAI 相容 `/v1` 端點 +- 首次啟用時僅在需要時安裝 `cloudflared`;後續重啟會重複使用相同的受管理二進位檔 +- 快速隧道在 OmniRoute 或容器重啟後不會自動恢復;請在需要時從控制台重新啟用 +- 隧道網址是暫時的,每次停止/啟動隧道時都會變更 +- 受管理的快速隧道預設使用 HTTP/2 傳輸,以避免在受限容器中產生大量的 QUIC UDP 緩衝區警告 +- 如果您想覆蓋受管理的傳輸選擇,請設定 `CLOUDFLARED_PROTOCOL=quic` 或 `auto` +- 如果您偏好使用預先安裝的 `cloudflared` 二進位檔而非受管理的下載,請設定 `CLOUDFLARED_BIN` +- Cloudflare 快速隧道、Tailscale Funnel 和 ngrok 隧道面板可在**設定 → 外觀**中顯示或隱藏。隱藏面板不會停止正在執行的隧道。 -### LLM Gateway Intelligence (Phase 9) +### LLM 閘道智慧功能(第 9 階段) -- **Semantic Cache** — Auto-caches non-streaming, temperature=0 responses (bypass with `X-OmniRoute-No-Cache: true`) -- **Request Idempotency** — Deduplicates requests within 5s via `Idempotency-Key` or `X-Request-Id` header -- **Progress Tracking** — Opt-in SSE `event: progress` events via `X-OmniRoute-Progress: true` header +- **語意快取** — 自動快取非串流、temperature=0 的回應(可透過 `X-OmniRoute-No-Cache: true` 繞過) +- **請求冪等性** — 透過 `Idempotency-Key` 或 `X-Request-Id` 標頭在 5 秒內去重複請求 +- **進度追蹤** — 選擇性 SSE `event: progress` 事件,透過 `X-OmniRoute-Progress: true` 標頭啟用 --- -### Translator Playground +### 翻譯器測試平台 -Access via **Dashboard → Translator**. Debug and visualize how OmniRoute translates API requests between providers. +透過**控制台 → 翻譯器**存取。偵錯並視覺化 OmniRoute 如何在提供者之間轉換 API 請求。 -| Mode | Purpose | -| ---------------- | -------------------------------------------------------------------------------------- | -| **Playground** | Select source/target formats, paste a request, and see the translated output instantly | -| **Chat Tester** | Send live chat messages through the proxy and inspect the full request/response cycle | -| **Test Bench** | Run batch tests across multiple format combinations to verify translation correctness | -| **Live Monitor** | Watch real-time translations as requests flow through the proxy | +| 模式 | 用途 | +| ----------------- | -------------------------------------------------------------------------------------- | +| **測試平台** | 選擇來源/目標格式,貼上請求,即可即時查看轉換後的輸出 | +| **聊天測試器** | 透過代理發送即時聊天訊息,並檢查完整的請求/回應週期 | +| **測試台** | 跨多種格式組合執行批次測試,驗證轉換正確性 | +| **即時監控** | 即時觀察請求流經代理時的轉換過程 | -**Use cases:** +**使用情境:** -- Debug why a specific client/provider combination fails -- Verify that thinking tags, tool calls, and system prompts translate correctly -- Compare format differences between OpenAI, Claude, Gemini, and Responses API formats +- 偵錯特定用戶端/提供者組合為何失敗 +- 驗證思考標籤、工具呼叫和系統提示是否正確轉換 +- 比較 OpenAI、Claude、Gemini 和 Responses API 格式之間的差異 --- -### Routing Strategies +### 路由策略 -Configure via **Dashboard → Settings → Routing**. +透過**控制台 → 設定 → 路由**配置。控制台顯示六種最常用的策略;組合和自動路由器內部支援更廣泛的範圍。 -| Strategy | Description | -| ------------------------------ | ------------------------------------------------------------------------------------------------ | -| **Fill First** | Uses accounts in priority order — primary account handles all requests until unavailable | -| **Round Robin** | Cycles through all accounts with a configurable sticky limit (default: 3 calls per account) | -| **P2C (Power of Two Choices)** | Picks 2 random accounts and routes to the healthier one — balances load with awareness of health | -| **Random** | Randomly selects an account for each request using Fisher-Yates shuffle | -| **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 +| 策略 | 說明 | +| ------------------------------- | ---------------------------------------------------------------------------------------- | +| **填滿優先** | 按優先順序使用帳戶 — 主要帳戶處理所有請求,直到無法使用為止 | +| **循環輪詢** | 在所有帳戶之間循環,附帶可配置的黏性限制(預設每個帳戶 3 次呼叫) | +| **P2C(雙隨機選擇)** | 選取 2 個隨機帳戶,路由到健康狀態較佳的那個 — 在負載與健康感知間取得平衡 | +| **隨機** | 使用 Fisher-Yates 洗牌法隨機為每個請求選取帳戶 | +| **最少使用** | 路由到 `lastUsedAt` 時間戳記最舊的帳戶,平均分配流量 | +| **成本最佳化** | 路由到優先級值最低的帳戶,以最低成本提供者為最佳化目標 | -For external session affinity (for example, Claude Code/Codex agents behind reverse proxies), send: +**進階組合和自動策略**(可依組合配置,或透過 `auto/*` 前綴使用 — 參閱 [AUTO-COMBO.md](../routing/AUTO-COMBO.md)): + +- `priority` — 嚴格順序,不進行循環輪詢 +- `weighted` — 依各模型權重按比例分配流量 +- `fill-first` — 耗盡第一個模型直到達到限制 +- `round-robin` / `strict-random` / `random` +- `p2c`(雙隨機選擇) +- `least-used` 和 `cost-optimized` +- `auto` — 跨所有候選者的評分驅動路由 +- `lkgp`(Last Known Good Provider)— 鎖定每個工作階段最後成功的模型 +- `context-optimized` — 選取可用上下文視窗最大的模型 +- `context-relay` — 串聯長上下文模型以處理後續回合 + +#### 外部黏性工作階段標頭 + +用於外部工作階段親和性(例如,反向代理後方的 Claude Code/Codex 代理),請發送: ```http X-Session-Id: your-session-key ``` -OmniRoute also accepts `x_session_id` and returns the effective session key in `X-OmniRoute-Session-Id`. +OmniRoute 也接受 `x_session_id`,並在 `X-OmniRoute-Session-Id` 中回傳有效的會話金鑰。 -If you use Nginx and send underscore-form headers, enable: +如果您使用 Nginx 並發送底線格式的標頭,請啟用: ```nginx underscores_in_headers on; ``` -#### Wildcard Model Aliases +#### 萬用字元模型別名 -Create wildcard patterns to remap model names: +建立萬用字元模式來重新對應模型名稱: ``` -Pattern: claude-sonnet-* → Target: cc/claude-sonnet-4-5-20250929 -Pattern: gpt-* → Target: gh/gpt-5.1-codex +模式:claude-sonnet-* → 目標:cc/claude-sonnet-4-6 +模式:gpt-* → 目標:gh/gpt-5.3-codex ``` -Wildcards support `*` (any characters) and `?` (single character). +萬用字元支援 `*`(任意字元)和 `?`(單一字元)。 -#### Fallback Chains +#### 備援鏈 -Define global fallback chains that apply across all requests: +定義套用至所有請求的全域備援鏈: ``` -Chain: production-fallback +鏈:production-fallback 1. cc/claude-opus-4-7 - 2. gh/gpt-5.1-codex + 2. gh/gpt-5.3-codex 3. glm/glm-4.7 ``` --- -### Resilience & Circuit Breakers +### 韌性與斷路器 -Configure via **Dashboard → Settings → Resilience**. +透過**控制台 → 設定 → 韌性**配置。 -OmniRoute implements provider-level resilience with five components: +OmniRoute 提供五個元件的提供者層級韌性: -1. **Request Queue & Pacing** — System-level request shaping: - - **Requests Per Minute (RPM)** — Maximum requests per minute per account - - **Min Time Between Requests** — Minimum gap in milliseconds between requests - - **Max Concurrent Requests** — Maximum simultaneous requests per account +1. **請求佇列與節流** — 系統層級的請求塑形: + - **每分鐘請求數(RPM)** — 每個帳戶每分鐘的最大請求數 + - **請求間最小間隔** — 請求之間的最小間隔(毫秒) + - **最大並發請求數** — 每個帳戶的最大同時請求數 -2. **Connection Cooldown** — Per-auth-type configuration for a single connection after retryable failures: - - **Base Cooldown** — Default cooldown window for retryable upstream failures - - **Use Upstream Retry Hints** — Honors authoritative `Retry-After` or reset hints when provided - - **Max Backoff Steps** — Maximum exponential backoff level for repeated failures +2. **連線冷卻** — 在可重試失敗後,依驗證類型為單一連線配置: + - **基礎冷卻** — 可重試上游失敗的預設冷卻視窗 + - **使用上游重試提示** — 在有提供時,遵循權威性的 `Retry-After` 或重置提示 + - **最大退避步數** — 重複失敗的最大指數退避層級 -3. **Provider Circuit Breaker** — Tracks end-to-end provider failures and automatically opens the breaker when the configured threshold is reached: - - **Failure Threshold** — Consecutive provider failures before opening the breaker - - **Reset Timeout** — Time window before the provider is tested again - - **CLOSED** (Healthy) — Requests flow normally - - **OPEN** — Provider is temporarily blocked after repeated failures - - **HALF_OPEN** — Testing if provider has recovered +3. **提供者斷路器** — 追蹤端到端的提供者失敗,在達到配置的警告門檻時將提供者標記為降級,並在達到配置的失敗門檻時開啟斷路器: + - **降級門檻** — 進入 `DEGRADED` 狀態前的連續提供者失敗次數 + - **失敗門檻** — 進入 `OPEN` 狀態前的連續提供者失敗次數 + - **重置超時** — 再次測試提供者前的時間視窗 + - **CLOSED(健康)** — 請求正常流動 + - **DEGRADED(降級)** — 請求仍可流動,但同時追蹤升高的失敗率 + - **OPEN(開啟)** — 重複失敗後暫時阻擋提供者 + - **HALF_OPEN(半開)** — 測試提供者是否已恢復 - Connection-scoped `429` rate limits stay in **Connection Cooldown** and do not count toward the provider breaker. + 連線層級的 `429` 速率限制會留在**連線冷卻**中,不計入提供者斷路器。 - The provider breaker runtime state is shown on **Dashboard → Health** only. + 提供者斷路器的執行時期狀態僅顯示於**控制台 → 健康狀態**。 -4. **Wait For Cooldown** — If every candidate connection is already cooling down, OmniRoute can wait for the earliest cooldown and retry the same client request automatically. +4. **等待冷卻** — 如果所有候選連線都處於冷卻狀態,OmniRoute 可以等待最早的冷卻結束,並自動重試相同的用戶端請求。 -5. **Rate Limit Auto-Detection** — When upstream providers return explicit wait windows, those hints override the local connection cooldown when the setting is enabled. +5. **速率限制自動偵測** — 當上游提供者回傳明確的等待視窗時,這些提示會在啟用設定時覆蓋本機連線冷卻。 -**Pro Tip:** Use the **Health** page to inspect and reset live provider breakers after an outage. The Resilience page only changes configuration. +**小撇步:** 使用**健康狀態**頁面來檢查並在故障後重設即時提供者斷路器。韌性頁面僅變更配置。 --- -### Database Export / Import +### 資料庫匯出 / 匯入 -Manage database backups in **Dashboard → Settings → System & Storage**. +在**控制台 → 設定 → 系統與儲存**中管理資料庫備份。 -| Action | Description | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| **Export Database** | Downloads the current SQLite database as a `.sqlite` file | -| **Export All (.tar.gz)** | Downloads a full backup archive including: database, settings, combos, provider connections (no credentials), API key metadata | -| **Import Database** | Upload a `.sqlite` file to replace the current database. A pre-import backup is automatically created unless `DISABLE_SQLITE_AUTO_BACKUP=true` | +| 動作 | 說明 | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| **匯出資料庫** | 將目前的 SQLite 資料庫下載為 `.sqlite` 檔案 | +| **全部匯出(.tar.gz)** | 下載完整的備份封存檔,包含:資料庫、設定、組合、提供者連線(不含憑證)、API 金鑰中繼資料 | +| **匯入資料庫** | 上傳 `.sqlite` 檔案以取代目前的資料庫。除非 `DISABLE_SQLITE_AUTO_BACKUP=true`,否則會自動建立匯入前備份 | ```bash -# API: Export database +# API:匯出資料庫 curl -o backup.sqlite http://localhost:20128/api/db-backups/export -# API: Export all (full archive) +# API:全部匯出(完整封存檔) curl -o backup.tar.gz http://localhost:20128/api/db-backups/exportAll -# API: Import database +# API:匯入資料庫 curl -X POST http://localhost:20128/api/db-backups/import \ -F "file=@backup.sqlite" ``` -**Import Validation:** The imported file is validated for integrity (SQLite pragma check), required tables (`provider_connections`, `provider_nodes`, `combos`, `api_keys`), and size (max 100MB). +**匯入驗證:** 匯入的檔案會進行完整性檢查(SQLite pragma 檢查)、必要表格檢查(`provider_connections`, `provider_nodes`, `combos`, `api_keys`)以及大小檢查(最大 100MB)。 -**Use Cases:** +**使用情境:** -- Migrate OmniRoute between machines -- Create external backups for disaster recovery -- Share configurations between team members (export all → share archive) +- 在機器間遷移 OmniRoute +- 建立外部備份以進行災難復原 +- 在團隊成員之間分享配置(全部匯出 → 分享封存檔) --- -### Settings Dashboard +### 設定控制台 -The settings page is organized into 6 tabs for easy navigation: +設定頁面分為 **7 個標籤**,方便導覽: -| Tab | Contents | -| -------------- | -------------------------------------------------------------------------------------------- | -| **General** | System storage tools, appearance settings, theme controls, and per-item sidebar visibility | -| **Security** | Login/Password settings, IP Access Control, API auth for `/models`, and Provider Blocking | -| **Routing** | Global routing strategy (6 options), wildcard model aliases, fallback chains, combo defaults | -| **Resilience** | Request queue, connection cooldown, provider breaker config, and wait-for-cooldown behavior | -| **AI** | Thinking budget configuration, global system prompt injection, prompt cache stats | -| **Advanced** | Global proxy configuration (HTTP/SOCKS5) | +| 標籤 | 內容 | +| ----------------- | ------------------------------------------------------------------------------------------------------------------ | +| **一般** | 系統儲存工具、預設行為、端點隧道可見性 | +| **外觀** | 主題控制(淺色/深色/系統)、側邊欄可見性、Cloudflare/Tailscale/ngrok 隧道卡片的開關 | +| **AI** | 思考預算配置、全域系統提示注入、提示快取統計 | +| **安全性** | 登入/密碼設定、IP 存取控制、`/models` 的 API 驗證、提供者封鎖、提示注入防護 | +| **路由** | 全域路由策略(填滿優先 / 循環輪詢 / P2C / 隨機 / 最少使用 / 成本最佳化)、萬用字元模型別名、備援鏈、組合預設 | +| **韌性** | 請求佇列、連線冷卻、提供者斷路器配置、以及等待冷卻行為 | +| **進階** | 全域代理配置(HTTP/SOCKS5)、各提供者代理覆蓋設定 | + +一般標籤不再重複顯示唯讀的日誌和快取說明。資料庫保留和 +最佳化設定透過 `/api/settings/database` 持續保存;手動清除快取使用 +`DELETE /api/cache`。請求和代理日誌行數上限由 +`CALL_LOGS_TABLE_MAX_ROWS` 和 `PROXY_LOGS_TABLE_MAX_ROWS` 控制。 --- -### Costs & Budget Management +### 費用與預算管理 -Access via **Dashboard → Costs**. +透過**控制台 → 費用**存取。 -| Tab | Purpose | -| ----------- | ---------------------------------------------------------------------------------------- | -| **Budget** | Set spending limits per API key with daily/weekly/monthly budgets and real-time tracking | -| **Pricing** | View and edit model pricing entries — cost per 1K input/output tokens per provider | +| 標籤 | 用途 | +| ------------ | ------------------------------------------------------------------------------------------- | +| **預算** | 為每個 API 金鑰設定每日/每週/每月的支出限制,並提供即時追蹤 | +| **定價** | 檢視和編輯模型定價條目 — 各提供者的每千輸入/輸出權杖費用 | ```bash -# API: Set a budget +# API:設定預算 curl -X POST http://localhost:20128/api/usage/budget \ -H "Content-Type: application/json" \ -d '{"keyId": "key-123", "limit": 50.00, "period": "monthly"}' -# API: Get current budget status +# API:取得目前預算狀態 curl http://localhost:20128/api/usage/budget ``` -**Cost Tracking:** Every request logs token usage and calculates cost using the pricing table. View breakdowns in **Dashboard → Usage** by provider, model, and API key. +**費用追蹤:** 每個請求都會記錄權杖使用量,並使用定價表計算費用。在**控制台 → 用量**中依提供者、模型和 API 金鑰檢視明細。 --- -### Audio Transcription +### 音訊轉錄 -OmniRoute supports audio transcription via the OpenAI-compatible endpoint: +OmniRoute 透過 OpenAI 相容端點支援音訊轉錄: ```bash POST /v1/audio/transcriptions -Authorization: Bearer your-api-key +Authorization: Bearer *** Content-Type: multipart/form-data -# Example with curl +# 使用 curl 的範例 curl -X POST http://localhost:20128/v1/audio/transcriptions \ - -H "Authorization: Bearer your-api-key" \ + -H "Authorization: Bearer ***" \ -F "file=@audio.mp3" \ -F "model=deepgram/nova-3" ``` -Available providers: **Deepgram** (`deepgram/`), **AssemblyAI** (`assemblyai/`). +**語音轉文字(轉錄)** 提供者: -Supported audio formats: `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm`. +- `openai/`(whisper 相容) +- `groq/`(Groq Whisper Turbo) +- `deepgram/`(Nova 系列) +- `assemblyai/` +- `nvidia/`(Parakeet, Canary) +- `huggingface/`(whisper 變體) +- `qwen/` + +**文字轉語音(`POST /v1/audio/speech`)** 提供者: + +- `openai/`(tts-1, tts-1-hd) +- `hyperbolic/` +- `deepgram/`(Aura) +- `nvidia/`(Magpie TTS) +- `elevenlabs/` +- `huggingface/` +- `inworld/` +- `cartesia/` +- `playht/` +- `kie/` +- `aws-polly/` +- `xiaomi-mimo/` +- `edgetts/`(Microsoft Edge「朗讀功能」— 免費,無需 API 金鑰;非官方/逆向工程端點) +- `coqui/`, `tortoise/` +- `qwen/` + +支援的轉錄音訊格式:`mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm`。TTS 輸出格式取決於提供者(mp3, wav, opus, pcm, mulaw)。 --- -### Combo Balancing Strategies +### 組合平衡策略 -Configure per-combo balancing in **Dashboard → Combos → Create/Edit → Strategy**. +在**控制台 → 組合 → 建立/編輯 → 策略**中配置每個組合的平衡策略。 -| Strategy | Description | -| ------------------ | ------------------------------------------------------------------------ | -| **Round-Robin** | Rotates through models sequentially | -| **Priority** | Always tries the first model; falls back only on error | -| **Random** | Picks a random model from the combo for each request | -| **Weighted** | Routes proportionally based on assigned weights per model | -| **Least-Used** | Routes to the model with the fewest recent requests (uses combo metrics) | -| **Cost-Optimized** | Routes to the cheapest available model (uses pricing table) | +| 策略 | 說明 | +| ----------------- | ------------------------------------------------------------------ | +| **循環輪詢** | 依序輪換模型 | +| **優先級** | 始終嘗試第一個模型;僅在出錯時才進行備援 | +| **隨機** | 為每個請求從組合中隨機選取一個模型 | +| **加權** | 根據每個模型的指定權重按比例路由 | +| **最少使用** | 路由到近期請求最少的模型(使用組合指標) | +| **成本最佳化** | 路由到最便宜的可用模型(使用定價表) | -Global combo defaults can be set in **Dashboard → Settings → Routing → Combo Defaults**. +全域組合預設可在**控制台 → 設定 → 路由 → 組合預設**中設定。 +組合目標超時預設會繼承當前請求的超時設定。僅在需要更短的 +單目標限制以觸發更快的備援時,才使用組合預設或個別組合上的**目標超時(秒)**。 + +零延遲組合最佳化為選擇性功能。請保持**零延遲最佳化**停用,以 +防止這些延遲功能搶跑備援目標、根據 TTFT 歷史記錄跳過目標,或壓縮備援請求;啟用此選項會允許配置的避險策略、預測性 TTFT +跳過,以及主動式備援壓縮,以路由/請求正確性換取更低的尾端 +延遲。 + +當上游提供者要求嚴格的 `max_tokens` / `maxOutputTokens` 限制時,請停用**推理權杖緩衝區**。啟用時,組合路由僅在模型有已知輸出上限時為推理模型增加 +緩衝空間,若安全的緩衝值超過該上限,則保留用戶端權杖限制不變。如果用戶端限制已高於已知上限, +OmniRoute 會在發送上游請求前將其限制在該上限內。 --- -### Health Dashboard +### 健康狀態控制台 -Access via **Dashboard → Health**. Real-time system health overview with 6 cards: +透過**控制台 → 健康狀態**存取。即時系統健康狀態總覽,包含 6 張卡片: -| Card | What It Shows | -| --------------------- | ----------------------------------------------------------- | -| **System Status** | Uptime, version, memory usage, data directory | -| **Provider Health** | Global provider circuit breaker runtime state | -| **Rate Limits** | Active connection cooldowns per account with remaining time | -| **Active Lockouts** | Active model-scoped lockouts and temporary exclusions | -| **Signature Cache** | Deduplication cache stats (active keys, hit rate) | -| **Latency Telemetry** | p50/p95/p99 latency aggregation per provider | +| 卡片 | 顯示內容 | +| ----------------------- | ------------------------------------------------- | +| **系統狀態** | 運作時間、版本、記憶體使用量、資料目錄 | +| **提供者健康狀態** | 全域提供者斷路器執行時期狀態 | +| **速率限制** | 各帳戶的活躍連線冷卻狀態及剩餘時間 | +| **活躍鎖定** | 活躍的模型層級鎖定和暫時排除 | +| **簽章快取** | 去重複快取統計(活躍金鑰數、命中率) | +| **延遲遙測** | 各提供者的 p50/p95/p99 延遲匯總 | -**Pro Tip:** The Health page auto-refreshes every 10 seconds. Use the circuit breaker card to identify which providers are experiencing issues. +**小撇步:** 健康狀態頁面每 10 秒自動重新整理。使用斷路器卡片來識別哪些提供者正在發生問題。 --- -## 🖥️ Desktop Application (Electron) +## 🤖 自動路由(零配置) -OmniRoute is available as a native desktop application for Windows, macOS, and Linux. +OmniRoute 內建**評分驅動的自動路由器**,會為每次請求在已連線的所有提供者中選取最佳模型 — 無需維護組合。只需使用 `auto/*` 前綴發送請求,OmniRoute 就會即時組裝一個虛擬組合,依據延遲、成本、成功率、上下文適應性、模型對任務的適合度、近期失敗、額度和斷路器狀態對候選者進行評分。 + +| 前綴 | 最佳化目標 | +| ---------------- | ---------------------------------------------------------------------------- | +| `auto` | 平衡預設(延遲 × 成本 × 成功率) | +| `auto/coding` | 編碼任務:偏好 Claude、GPT-5、GLM、Kimi、Qwen Coder、DeepSeek 程式模型 | +| `auto/cheap` | 最低 $/權杖,可接受較高延遲 | +| `auto/fast` | 最低延遲,忽略成本 | +| `auto/offline` | 僅限本地提供者(Ollama, vLLM, llama.cpp)— 適用於隔離環境 | +| `auto/smart` | 推理品質優先(Opus, GPT-5 xhigh, R1, GLM 5.1 推理) | +| `auto/lkgp` | 「Last Known Good Provider」— 鎖定最近一次成功的目標 | + +範例: + +```bash +curl -X POST http://localhost:20128/v1/chat/completions \ + -H "Authorization: Bearer ***" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "auto/coding", + "messages": [{ "role": "user", "content": "重構這個 Python 函式" }], + "stream": true + }' +``` + +自動路由器的完整說明請見 [AUTO-COMBO.md](../routing/AUTO-COMBO.md) — 包括如何調整評分權重、封鎖特定提供者,以及在**控制台 → 自動組合**中檢查路由決策。 + +--- + +## 🔌 MCP 與 A2A 整合 + +OmniRoute 既是 **MCP 伺服器**(模型上下文協定),也是 **A2A 伺服器**(代理間 JSON-RPC 2.0)。任何相容 MCP 的 IDE 或代理主機都可以直接呼叫 OmniRoute 工具 — 無需額外的包裝器。 + +### MCP 傳輸方式 + +- **SSE**:`http://localhost:20128/api/mcp/sse` +- **可串流 HTTP**:`http://localhost:20128/api/mcp/stream` +- **stdio**:`omniroute --mcp`(適用於偏好 stdio 的 IDE 外掛程式) + +### 連接 Claude Desktop + +編輯 `~/Library/Application Support/Claude/claude_desktop_config.json`(macOS)或 Windows/Linux 的對應路徑: + +```json +{ + "mcpServers": { + "omniroute": { + "command": "omniroute", + "args": ["--mcp"] + } + } +} +``` + +### 連接 Cursor / Continue / VS Code MCP + +使用 SSE 網址 `http://localhost:20128/api/mcp/sse` 和在**控制台 → API 金鑰**中產生的 Bearer API 金鑰。 + +### 範圍 + +MCP 工具分為 10 個範圍:`analytics`、`auth`、`billing`、`combos`、`health`、`keys`、`memory`、`models`、`providers`、`system`。每個 Bearer 金鑰可以限制在特定範圍內 — 完整工具目錄請參閱 [MCP-SERVER.md](../frameworks/MCP-SERVER.md),JSON-RPC 架構請參閱 [A2A-SERVER.md](../frameworks/A2A-SERVER.md)。 + +--- + +## 🧠 技能系統 + +OmniRoute 提供可擴充的**技能框架**(`src/lib/skills/`),讓代理和 A2A 端點可以執行領域特定的常式(例如 `code-review`、`summarize`、`extract-facts`、`web-research`)。 + +- **市集 UI** — 從**控制台 → 技能**瀏覽和安裝技能 +- **各金鑰範圍** — 限制哪些 API 金鑰可以呼叫哪些技能 +- **自訂技能** — 將 TypeScript 檔案放入 `src/lib/a2a/skills/`,註冊後即可透過 A2A 立即呼叫 + +完整參考:[SKILLS.md](../frameworks/SKILLS.md)。 + +--- + +## 💾 記憶系統 + +OmniRoute 使用混合檢索來持久保存**長期對話記憶**: + +- **SQLite FTS5** 用於跨回合的關鍵字搜尋 +- **Qdrant 向量儲存**(選擇性)用於語意回憶 +- **自動事實提取** — 每次工作階段後會總結實體、偏好和決策,儲存在 `memory_facts` 表中 +- 記憶按 API 金鑰和工作階段範圍劃分 + +在**控制台 → 記憶**中管理記憶(搜尋、編輯、匯出、清除)。HTTP 介面(`/api/memory/*`)讓代理可以程式化地推送和查詢事實 — 請參閱 [MEMORY.md](../frameworks/MEMORY.md)。 + +--- + +## 🔔 Webhooks + +訂閱 OmniRoute 事件以進行即時監控和自動化。 + +- 在**控制台 → Webhooks** 中建立 webhook,包含目標網址和 HMAC 簽署密鑰 +- 可用事件:`request.completed`, `request.failed`, `provider.unavailable`, `budget.exceeded`, `combo.switched`, `circuit_breaker.opened`, `circuit_breaker.closed` +- 每個承載都包含 `X-OmniRoute-Signature`(HMAC-SHA256)以供驗證 +- 重試:3 次嘗試,使用指數退避,之後進入死信佇列 + +完整架構請見 [WEBHOOKS.md](../frameworks/WEBHOOKS.md)。 + +--- + +## ☁️ 雲端代理 + +OmniRoute 與雲端編碼代理(**OpenAI Codex Cloud**, **Devin**, **Jules**, **Antigravity**)整合,讓您可以從處理本地路由的同一個控制台分派長時間執行的任務。 + +- 在**控制台 → 雲端代理**或透過 `POST /api/v1/agents/tasks` 建立任務 +- 追蹤每個任務的狀態、日誌和產出物 +- 自備每個提供者的 API 金鑰 — 憑證絕不會離開 OmniRoute 實例 + +完整參考:[CLOUD_AGENT.md](../frameworks/CLOUD_AGENT.md)。 + +--- + +## 🛠️ 程式化管理 + +您可以使用具有 `manage` 範圍的 **Bearer 金鑰**,透過 HTTP 管理所有 OmniRoute 資源(提供者、組合、金鑰、設定)。 + +在**控制台 → API 金鑰 → 新增金鑰 → 範圍:manage** 中產生金鑰,然後: + +```bash +# 列出提供者 +curl http://localhost:20128/api/providers \ + -H "Authorization: Bearer $OMNIR..._KEY" + +# 新增提供者連線 +curl -X POST http://localhost:20128/api/providers \ + -H "Authorization: Bearer $OMNIR..._KEY" \ + -H "Content-Type: application/json" \ + -d '{ "provider": "openai", "apiKey": "sk-...", "name": "main" }' + +# 建立組合 +curl -X POST http://localhost:20128/api/combos \ + -H "Authorization: Bearer $OMNIR..._KEY" \ + -H "Content-Type: application/json" \ + -d '{ "name": "premium", "strategy": "priority", "models": [{ "model": "cc/claude-opus-4-7" }, { "model": "glm/glm-5.1" }] }' + +# 列出/建立 API 金鑰 +curl http://localhost:20128/api/keys -H "Authorization: Bearer $OMNIR..._KEY" +curl -X POST http://localhost:20128/api/keys -H "Authorization: Bearer $OMNIR..._KEY" \ + -d '{ "name": "ci-bot", "scopes": ["chat"] }' +``` + +完整的端點目錄和請求/回應架構請參閱 [API_REFERENCE.md](../reference/API_REFERENCE.md)。 + +--- + +## 💻 內部 CLI + +OmniRoute 內建一個內部 CLI(`omniroute …`),用於設定、診斷和執行時期控制。這**與控制台中的「CLI 工具」頁面不同**,後者用於配置第三方 CLI(Claude Code, Cursor, Codex, Cline, …)使其能夠與 OmniRoute 通訊。 + +```bash +omniroute setup # 互動式精靈(密碼、提供者、組合) +omniroute setup --non-interactive # CI 友善模式 +omniroute doctor # 健康診斷(資料目錄、資料庫、提供者、連接埠) +omniroute providers available # 列出支援的提供者 +omniroute providers list # 列出已配置的連線 +omniroute providers test # 即時測試提供者連線 +omniroute combos list # 列出組合 +omniroute combos switch # 設定預設組合 +omniroute models # 列出可用模型(--json, --search) +omniroute keys add | list | remove # 從終端機管理 API 金鑰 +omniroute backup # 快照配置 + 資料庫 +omniroute restore [] # 從快照還原 +omniroute health # 詳細健康狀態(斷路器、快取、記憶體) +omniroute quota # 提供者額度使用情況 +omniroute mcp status # MCP 伺服器狀態 +omniroute a2a status # A2A 伺服器狀態 +omniroute tunnel list|create|stop # Cloudflare/Tailscale/ngrok 隧道 +omniroute reset-password # 重設管理員密碼 +omniroute --mcp # 透過 stdio 啟動 MCP 伺服器 +omniroute --port 3000 # 在自訂連接埠上啟動伺服器 +``` + +提示:將 `omniroute doctor --json` 與您的監控工具搭配使用,可在提供者連線異常時發出警報。 + +--- + +## 🖥️ 桌面應用程式(Electron) + +OmniRoute 也可作為 Windows、macOS 和 Linux 的原生桌面應用程式使用。 ### 安裝 ```bash -# From the electron directory: +# 從 electron 目錄: cd electron npm install -# Development mode (connect to running Next.js dev server): +# 開發模式(連接到執行中的 Next.js 開發伺服器): npm run dev -# Production mode (uses standalone build): +# 生產模式(使用獨立建置): npm start ``` -### Building Installers +### 建置安裝程式 ```bash cd electron -npm run build # Current platform -npm run build:win # Windows (.exe NSIS) -npm run build:mac # macOS (.dmg universal) -npm run build:linux # Linux (.AppImage) +npm run build # 當前平台 +npm run build:win # Windows(.exe NSIS) +npm run build:mac # macOS(.dmg 通用版) +npm run build:linux # Linux(.AppImage) ``` -Output → `electron/dist-electron/` +輸出 → `electron/dist-electron/` -### Key Features +### 主要功能 -| Feature | Description | -| --------------------------- | ---------------------------------------------------- | -| **Server Readiness** | Polls server before showing window (no blank screen) | -| **System Tray** | Minimize to tray, change port, quit from tray menu | -| **Port Management** | Change server port from tray (auto-restarts server) | -| **Content Security Policy** | Restrictive CSP via session headers | -| **Single Instance** | Only one app instance can run at a time | -| **Offline Mode** | Bundled Next.js server works without internet | +| 功能 | 說明 | +| --------------------------- | ------------------------------------------------- | +| **伺服器就緒檢查** | 在顯示視窗前輪詢伺服器(無空白畫面) | +| **系統托盤** | 最小化至托盤、變更連接埠、從托盤選單退出 | +| **連接埠管理** | 從托盤變更伺服器連接埠(自動重新啟動伺服器) | +| **內容安全策略** | 透過工作階段標頭實施嚴格的 CSP | +| **單一實例** | 一次只能執行一個應用程式實例 | +| **離線模式** | 內建 Next.js 伺服器,無需網路即可運作 | -### Environment Variables +### 環境變數 -| Variable | Default | Description | -| --------------------- | ------- | -------------------------------- | -| `OMNIROUTE_PORT` | `20128` | Server port | -| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit (64–16384 MB) | +| 變數 | 預設值 | 說明 | +| ---------------------- | ------- | --------------------------------- | +| `OMNIROUTE_PORT` | `20128` | 伺服器連接埠 | +| `OMNIROUTE_MEMORY_MB` | `512` | Node.js 堆積限制(64–16384 MB) | -📖 Full documentation: [`electron/README.md`](../electron/README.md) +📖 完整文件:[`electron/README.md`](../../electron/README.md) diff --git a/docs/i18n/zh-TW/docs/ops/COVERAGE_PLAN.md b/docs/i18n/zh-TW/docs/ops/COVERAGE_PLAN.md index 044d58fae7..b76fc02122 100644 --- a/docs/i18n/zh-TW/docs/ops/COVERAGE_PLAN.md +++ b/docs/i18n/zh-TW/docs/ops/COVERAGE_PLAN.md @@ -1,170 +1,183 @@ -# Test Coverage Plan (中文 (簡體)) - -🌐 **Languages:** 🇺🇸 [English](../../../../docs/COVERAGE_PLAN.md) · 🇸🇦 [ar](../../ar/docs/COVERAGE_PLAN.md) · 🇧🇬 [bg](../../bg/docs/COVERAGE_PLAN.md) · 🇧🇩 [bn](../../bn/docs/COVERAGE_PLAN.md) · 🇨🇿 [cs](../../cs/docs/COVERAGE_PLAN.md) · 🇩🇰 [da](../../da/docs/COVERAGE_PLAN.md) · 🇩🇪 [de](../../de/docs/COVERAGE_PLAN.md) · 🇪🇸 [es](../../es/docs/COVERAGE_PLAN.md) · 🇮🇷 [fa](../../fa/docs/COVERAGE_PLAN.md) · 🇫🇮 [fi](../../fi/docs/COVERAGE_PLAN.md) · 🇫🇷 [fr](../../fr/docs/COVERAGE_PLAN.md) · 🇮🇳 [gu](../../gu/docs/COVERAGE_PLAN.md) · 🇮🇱 [he](../../he/docs/COVERAGE_PLAN.md) · 🇮🇳 [hi](../../hi/docs/COVERAGE_PLAN.md) · 🇭🇺 [hu](../../hu/docs/COVERAGE_PLAN.md) · 🇮🇩 [id](../../id/docs/COVERAGE_PLAN.md) · 🇮🇹 [it](../../it/docs/COVERAGE_PLAN.md) · 🇯🇵 [ja](../../ja/docs/COVERAGE_PLAN.md) · 🇰🇷 [ko](../../ko/docs/COVERAGE_PLAN.md) · 🇮🇳 [mr](../../mr/docs/COVERAGE_PLAN.md) · 🇲🇾 [ms](../../ms/docs/COVERAGE_PLAN.md) · 🇳🇱 [nl](../../nl/docs/COVERAGE_PLAN.md) · 🇳🇴 [no](../../no/docs/COVERAGE_PLAN.md) · 🇵🇭 [phi](../../phi/docs/COVERAGE_PLAN.md) · 🇵🇱 [pl](../../pl/docs/COVERAGE_PLAN.md) · 🇵🇹 [pt](../../pt/docs/COVERAGE_PLAN.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/COVERAGE_PLAN.md) · 🇷🇴 [ro](../../ro/docs/COVERAGE_PLAN.md) · 🇷🇺 [ru](../../ru/docs/COVERAGE_PLAN.md) · 🇸🇰 [sk](../../sk/docs/COVERAGE_PLAN.md) · 🇸🇪 [sv](../../sv/docs/COVERAGE_PLAN.md) · 🇰🇪 [sw](../../sw/docs/COVERAGE_PLAN.md) · 🇮🇳 [ta](../../ta/docs/COVERAGE_PLAN.md) · 🇮🇳 [te](../../te/docs/COVERAGE_PLAN.md) · 🇹🇭 [th](../../th/docs/COVERAGE_PLAN.md) · 🇹🇷 [tr](../../tr/docs/COVERAGE_PLAN.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/COVERAGE_PLAN.md) · 🇵🇰 [ur](../../ur/docs/COVERAGE_PLAN.md) · 🇻🇳 [vi](../../vi/docs/COVERAGE_PLAN.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/COVERAGE_PLAN.md) - +--- +title: "測試覆蓋率計畫" +version: 3.8.40 +lastUpdated: 2026-06-28 --- -Last updated: 2026-03-28 +# 測試覆蓋率計畫 -## Baseline +最後更新:2026-06-28 -There are multiple coverage numbers depending on how the report is computed. For planning, only one of them is useful. +> 狀態測量於 2026-05-13:行 82.58%、陳述式 82.58%、函式 84.23%、條件分支 75.22%。第 1–5 階段已完成。當前重點為第 6 階段(>=85%)與第 7 階段(>=90%)。 -| Metric | Scope | Statements / Lines | Branches | Functions | Notes | -| -------------------- | ----------------------------------------------------- | -----------------: | -------: | --------: | --------------------------------------------------- | -| Legacy | Old `npm run test:coverage` | 79.42% | 75.15% | 67.94% | Inflated: counts test files and excludes `open-sse` | -| Diagnostic | Source-only, excluding tests and excluding `open-sse` | 68.16% | 63.55% | 64.06% | Useful only to isolate `src/**` | -| Recommended baseline | Source-only, excluding tests and including `open-sse` | 56.95% | 66.05% | 57.80% | This is the project-wide baseline to improve | +## 基準 -The recommended baseline is the number to optimize against. +根據報告計算方式的不同,會有多組覆蓋率數字。在規劃上,只有其中一組具有參考價值。 -## Rules +| 指標 | 範圍 | 陳述式/行 | 條件分支 | 函式 | 備註 | +| ---------------------- | ----------------------------------------------------- | ---------: | -------: | --------: | ------------------------------------------------- | +| 舊版 | 舊有 `npm run test:coverage` | 79.42% | 75.15% | 67.94% | 失真:計入了測試檔且排除 `open-sse` | +| 診斷用 | 僅原始碼,排除測試檔且排除 `open-sse` | 68.16% | 63.55% | 64.06% | 僅用於隔離 `src/**` 分析 | +| 建議基準 | 僅原始碼,排除測試檔且納入 `open-sse` | 82.58% | 75.22% | 84.23% | 這是應改善的專案級基準 | -- Coverage targets apply to source files, not to `tests/**`. -- `open-sse/**` is part of the product and must remain in scope. -- New code should not reduce coverage in touched areas. -- Prefer testing behavior and branch outcomes over implementation details. -- Prefer temp SQLite databases and small fixtures over broad mocks for `src/lib/db/**`. +建議基準是應優化的目標數字。 -## Current command set +## 規則 + +- 覆蓋率目標適用於原始碼,不包含 `tests/**`。 +- `open-sse/**` 屬於產品的一部分,必須納入範圍。 +- 新程式碼不應降低受影響區域的覆蓋率。 +- 優先測試行為與條件分支結果,而非實作細節。 +- 對於 `src/lib/db/**`,優先使用暫存 SQLite 資料庫與小型測試資料,而非廣泛的 Mock。 + +## 當前指令集 - `npm run test:coverage` - - Main source coverage gate for the unit test suite - - Generates `text-summary`, `html`, `json-summary`, and `lcov` + - 單元測試套件的主要原始碼覆蓋率閘門 + - 產生 `text-summary`、`html`、`json-summary` 與 `lcov` - `npm run coverage:report` - - Detailed file-by-file report from the latest run + - 來自最近一次執行的逐檔詳細報告 - `npm run test:coverage:legacy` - - Historical comparison only + - 僅供歷史比較 -## Milestones +## 里程碑 -| Phase | Target | Focus | -| ------- | ---------------------: | ------------------------------------------------- | -| Phase 1 | 60% statements / lines | Quick wins and low-risk utility coverage | -| Phase 2 | 65% statements / lines | DB and route foundations | -| Phase 3 | 70% statements / lines | Provider validation and usage analytics | -| Phase 4 | 75% statements / lines | `open-sse` translators and helpers | -| Phase 5 | 80% statements / lines | `open-sse` handlers and executor branches | -| Phase 6 | 85% statements / lines | Harder edge cases, branch debt, regression suites | -| Phase 7 | 90% statements / lines | Final sweep, gap closure, strict ratchet | +| 階段 | 目標 | 重點 | 狀態 | +| -------- | -------------------: | ------------------------------------------------ | ------------- | +| 第 1 階段 | 60% 陳述式/行 | 快速取勝與低風險通用工具覆蓋率 | ✅ 已完成 | +| 第 2 階段 | 65% 陳述式/行 | 資料庫與路由基礎 | ✅ 已完成 | +| 第 3 階段 | 70% 陳述式/行 | Provider 驗證與使用分析 | ✅ 已完成 | +| 第 4 階段 | 75% 陳述式/行 | `open-sse` 轉換器與輔助工具 | ✅ 已完成 | +| 第 5 階段 | 80% 陳述式/行 | `open-sse` 處理器與執行器分支 | ✅ 已完成 | +| 第 6 階段 | 85% 陳述式/行 | 較難的邊界案例、分支負債、回歸測試套件 | 進行中 | +| 第 7 階段 | 90% 陳述式/行 | 最終掃蕩、缺口補齊、嚴格棘輪機制 | 待處理 | -Branches and functions should ratchet upward with each phase, but the primary hard target is statements / lines. +條件分支與函式應隨各階段逐步提升,但主要硬目標仍為陳述式/行。 -## Priority hotspots +## 優先熱點 -These files or areas offer the best return for the next phases: +這些檔案目前行覆蓋率最低(< 60%),在第 6–7 階段中能帶來最佳改善效益。資料來源為 `coverage/coverage-summary.json`(2026-05-13): -1. `open-sse/handlers` - - `chatCore.ts` at 7.57% - - Overall directory at 29.07% -2. `open-sse/translator/request` - - Overall directory at 36.39% - - Many translators are still near single-digit coverage -3. `open-sse/translator/response` - - Overall directory at 8.07% -4. `open-sse/executors` - - Overall directory at 36.62% -5. `src/lib/db` - - `models.ts` at 20.66% - - `registeredKeys.ts` at 34.46% - - `modelComboMappings.ts` at 36.25% - - `settings.ts` at 46.40% - - `webhooks.ts` at 33.33% -6. `src/lib/usage` - - `usageHistory.ts` at 21.12% - - `usageStats.ts` at 9.56% - - `costCalculator.ts` at 30.00% -7. `src/lib/providers` - - `validation.ts` at 41.16% -8. Low-risk utility and API files for early gains - - `src/shared/utils/upstreamError.ts` - - `src/shared/utils/apiAuth.ts` - - `src/lib/api/errorResponse.ts` - - `src/app/api/settings/require-login/route.ts` - - `src/app/api/providers/[id]/models/route.ts` +| # | 檔案 | 行覆蓋率 % | +| --- | ------------------------------------------------------------- | --------: | +| 1 | `open-sse/services/compression/validation.ts` | 7.87% | +| 2 | `src/app/api/v1/batches/route.ts` | 9.67% | +| 3 | `src/app/docs/components/FeedbackWidget.tsx` | 9.80% | +| 4 | `open-sse/services/compression/toolResultCompressor.ts` | 10.00% | +| 5 | `src/app/docs/components/DocCodeBlocks.tsx` | 10.63% | +| 6 | `open-sse/services/compression/engines/rtk/lineFilter.ts` | 10.96% | +| 7 | `open-sse/services/specificityRules.ts` | 11.28% | +| 8 | `src/mitm/systemCommands.ts` | 12.19% | +| 9 | `open-sse/services/compression/aggressive.ts` | 12.77% | +| 10 | `src/app/api/v1/batches/[id]/cancel/route.ts` | 12.98% | +| 11 | `open-sse/services/compression/progressiveAging.ts` | 13.26% | +| 12 | `open-sse/services/compression/engines/rtk/smartTruncate.ts` | 13.43% | +| 13 | `open-sse/services/compression/engines/rtk/deduplicator.ts` | 13.51% | +| 14 | `src/lib/cloudAgent/agents/jules.ts` | 13.52% | +| 15 | `open-sse/services/compression/lite.ts` | 14.46% | +| 16 | `src/app/api/v1/rerank/route.ts` | 14.94% | +| 17 | `open-sse/services/compression/preservation.ts` | 15.07% | +| 18 | `src/lib/cloudAgent/agents/codex.ts` | 15.54% | +| 19 | `open-sse/services/tierResolver.ts` | 16.66% | +| 20 | `src/app/docs/components/DocsLazyWrapper.tsx` | 16.66% | -## Execution checklist +第 6–7 階段的主題: -### Phase 1: 56.95% -> 60% +- `open-sse/services/compression/**` 是低覆蓋率最密集的區塊,主導了剩餘差距。 +- 批次與重排序 API 路由(`src/app/api/v1/batches/**`、`src/app/api/v1/rerank/route.ts`)需要處理器層級的測試。 +- Cloud Agent 轉接器(`src/lib/cloudAgent/agents/jules.ts`、`codex.ts`)與 `tierResolver.ts` 需要情境測試。 +- Docs UI 元件與 `src/mitm/systemCommands.ts` 優先級較低,但屬於低成本的分支改善機會。 -- [x] Fix coverage metric so it reflects source code instead of test files -- [x] Keep a legacy coverage script for comparison -- [x] Record the baseline and hotspots in-repo -- [ ] Add focused tests for low-risk utilities: +## 執行檢查清單 + +### 第 1 階段:56.95% -> 60% + +- [x] 修正覆蓋率指標,使其反映原始碼而非測試檔 +- [x] 保留舊版覆蓋率腳本以供比較 +- [x] 在儲存庫中記錄基準與熱點 +- [ ] 為低風險通用工具新增聚焦測試: - `src/shared/utils/upstreamError.ts` - `src/shared/utils/fetchTimeout.ts` - `src/lib/api/errorResponse.ts` - `src/shared/utils/apiAuth.ts` - `src/lib/display/names.ts` -- [ ] Add route tests for: +- [ ] 為以下路由新增測試: - `src/app/api/settings/require-login/route.ts` - `src/app/api/providers/[id]/models/route.ts` -### Phase 2: 60% -> 65% +### 第 2 階段:60% -> 65% -- [ ] Add DB-backed tests for: +- [ ] 新增資料庫驅動測試: - `src/lib/db/modelComboMappings.ts` - `src/lib/db/settings.ts` - `src/lib/db/registeredKeys.ts` -- [ ] Cover branch behavior in: +- [ ] 涵蓋以下項目的條件分支行為: - `src/lib/providers/validation.ts` - `src/app/api/v1/embeddings/route.ts` - `src/app/api/v1/moderations/route.ts` -### Phase 3: 65% -> 70% +### 第 3 階段:65% -> 70% -- [ ] Add usage analytics tests for: +- [ ] 新增使用分析測試: - `src/lib/usage/usageHistory.ts` - `src/lib/usage/usageStats.ts` - `src/lib/usage/costCalculator.ts` -- [ ] Expand route coverage for proxy management and settings branches +- [ ] 擴充 Proxy 管理與設定分支的路由覆蓋率 -### Phase 4: 70% -> 75% +### 第 4 階段:70% -> 75% -- [ ] Cover translator helpers and central translation paths: +- [ ] 涵蓋轉換器輔助工具與中央翻譯路徑: - `open-sse/translator/index.ts` - `open-sse/translator/helpers/*` - `open-sse/translator/request/*` - `open-sse/translator/response/*` -### Phase 5: 75% -> 80% +### 第 5 階段:75% -> 80% -- [ ] Add handler-level tests for: +- [ ] 新增處理器層級測試: - `open-sse/handlers/chatCore.ts` - `open-sse/handlers/responsesHandler.js` - `open-sse/handlers/imageGeneration.js` - `open-sse/handlers/embeddings.js` -- [ ] Add executor branch coverage for provider-specific auth, retries, and endpoint overrides +- [ ] 針對 Provider 特定的驗證、重試與端點覆寫,新增執行器分支覆蓋率 -### Phase 6: 80% -> 85% +### 第 6 階段:80% -> 85% -- [ ] Merge more edge-case suites into the main coverage path -- [ ] Increase function coverage for DB modules with weak constructor/helper coverage -- [ ] Close branch gaps in `settings.ts`, `registeredKeys.ts`, `validation.ts`, and translator helpers +- [ ] 將更多邊界案例測試套件納入主要覆蓋率路徑 +- [ ] 提升建構子/輔助工具覆蓋率薄弱的資料庫模組的函式覆蓋率 +- [ ] 補齊 `settings.ts`、`registeredKeys.ts`、`validation.ts` 與轉換器輔助工具中的條件分支缺口 -### Phase 7: 85% -> 90% +### 第 7 階段:85% -> 90% -- [ ] Treat the remaining low-coverage files as blockers -- [ ] Add regression tests for every uncovered production bug fixed during the push to 90% -- [ ] Raise the coverage gate in CI only after the local baseline is stable for at least two consecutive runs +- [ ] 將剩餘低覆蓋率檔案視為阻擋事項 +- [ ] 為在推進至 90% 過程中所修正的每一個未覆蓋的正式環境錯誤,新增回歸測試 +- [ ] 僅在本地基準連續兩次執行穩定後,才在 CI 中調高覆蓋率閘門 -## Ratchet policy +## 棘輪機制 -Update `npm run test:coverage` thresholds only after the project actually exceeds the next milestone with a comfortable buffer. +僅在專案實際以充裕緩衝超越下一里程碑後,才更新 `npm run test:coverage` 的閾值。 -Recommended ratchet sequence: +**當前閘門:**`npm run test:coverage` 強制執行 **60 陳述式 / 60 行 / 60 函式 / 60 條件分支**(該指標已在 Quality-Gates 第 6A.1 階段重新基準化——先前的 82.58% 基準因計入測試檔且排除 `open-sse` 而有失真)。`test:coverage:legacy` 指令保留舊有的 50/50/50 指標以供歷史比較。 + +如需針對最新報告進行臨時閾值檢查,請使用: + +```bash +node scripts/check/test-report-summary.mjs --threshold 75 +``` + +建議的棘輪序列(順序為 `陳述式-行 / 條件分支 / 函式`): 1. 55/60/55 2. 60/62/58 3. 65/64/62 4. 70/66/66 -5. 75/70/72 +5. 75/70/72 <-- 當前閘門(75/70/75) 6. 80/75/78 7. 85/80/84 8. 90/85/88 -Order is `statements-lines / branches / functions`. +下一個棘輪目標為 `80/75/78`,當條件分支覆蓋率連續兩次執行維持在 78% 以上時即生效。 -## Known gap +## 已知缺口 -The current coverage command measures the main Node unit suite and includes source reached from it, including `open-sse`. It does not yet merge Vitest coverage into a single unified report. That merge is worth doing later, but it is not a blocker for starting the 60% -> 80% climb. +目前的覆蓋率指令測量的是主要的 Node 單元測試套件,並包含從中可達的原始碼(包括 `open-sse`)。它尚未將 Vitest 覆蓋率合併為單一統一報告。這項合併工作值得日後進行,但不會阻礙從 60% 邁向 80% 的爬升。 diff --git a/docs/i18n/zh-TW/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md b/docs/i18n/zh-TW/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md index a24c02db6a..1f6afe0526 100644 --- a/docs/i18n/zh-TW/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md +++ b/docs/i18n/zh-TW/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md @@ -1,32 +1,36 @@ -# OmniRoute Fly.io 部署指南 (中文 (簡體)) - -🌐 **Languages:** 🇺🇸 [English](../../../../docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇩🇰 [da](../../da/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇩🇪 [de](../../de/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇪🇸 [es](../../es/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇱 [he](../../he/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇩 [id](../../id/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇹 [it](../../it/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇳🇴 [no](../../no/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [te](../../te/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇹🇭 [th](../../th/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/FLY_IO_DEPLOYMENT_GUIDE.md) - +--- +title: "OmniRoute Fly.io 部署指南(繁體中文)" +version: 3.8.40 +lastUpdated: 2026-06-28 --- -本文檔記錄 OmniRoute 在 Fly.io 上的實際部署方法,適用於兩類場景: +# OmniRoute Fly.io 部署指南(繁體中文) -- 首次把當前項目部署到 Fly.io -- 後續代碼更新後繼續發布 -- 新項目參考同樣流程部署 +🌐 **語言:** 🇺🇸 [English](../../../../docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇩🇰 [da](../../da/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇩🇪 [de](../../de/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇪🇸 [es](../../es/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇱 [he](../../he/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇩 [id](../../id/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇹 [it](../../it/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇳🇴 [no](../../no/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [te](../../te/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇹🇭 [th](../../th/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/FLY_IO_DEPLOYMENT_GUIDE.md) -本文基於當前項目已經驗證通過的設定整理,應用名為 `omniroute`。 +本文件說明 OmniRoute 在 Fly.io 上的實際部署流程,涵蓋兩種情境: + +- 首次將目前專案部署到 Fly.io +- 發布後續程式碼更新 +- 新專案遵循相同的部署工作流程 + +本指南基於目前專案經過驗證的有效配置。應用程式名稱為 `omniroute`。 --- ## 1. 部署目標 -- 平臺:Fly.io -- 部署方式:本地 `flyctl` 直接發布 -- 運行方式:使用倉庫內現有 `Dockerfile` 和 `fly.toml` -- 數據持久化:Fly Volume 掛載到 `/data` -- 訪問地址:`https://omniroute.fly.dev/` +- 平台:Fly.io +- 部署方式:本機 `flyctl` 直接發布 +- 執行環境:使用儲存庫中現有的 `Dockerfile` 和 `fly.toml` +- 資料持久化:Fly Volume 掛載至 `/data` +- 存取 URL:`https://omniroute.fly.dev/` --- -## 2. 當前項目關鍵設定 +## 2. 目前專案關鍵配置 -當前倉庫中的 `fly.toml` 已確認包含以下關鍵項: +目前儲存庫中的 `fly.toml` 已確認包含以下關鍵項目: ```toml app = 'omniroute' @@ -49,15 +53,15 @@ primary_region = 'sin' BIND = "0.0.0.0" ``` -說明: +注意事項: -- `app = 'omniroute'` 決定實際部署到哪個 Fly 應用 -- `destination = '/data'` 決定持久卷掛載目錄 -- 本項目必須讓 `DATA_DIR=/data`,否則資料庫和密鑰會寫到容器臨時目錄 +- `app = 'omniroute'` 決定部署目標是哪個 Fly 應用程式 +- `destination = '/data'` 決定持久化磁碟區的掛載目錄 +- 此專案必須設定 `DATA_DIR=/data`,否則資料庫和金鑰會寫入容器的暫存目錄 --- -## 3. 必備工具 +## 3. 前置需求 ### 3.1 安裝 Fly CLI @@ -67,15 +71,15 @@ Windows PowerShell: pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex" ``` -如果安裝腳本在當前環境失敗,也可以手動下載 `flyctl` 二進位並放到 `PATH` 中。 +如果安裝腳本在您的環境中失敗,您也可以手動下載 `flyctl` 二進位檔並將其加入 `PATH`。 -### 3.2 登錄 Fly 帳號 +### 3.2 登入您的 Fly 帳戶 ```powershell flyctl auth login ``` -### 3.3 檢查登錄狀態 +### 3.3 驗證登入狀態 ```powershell flyctl auth whoami @@ -84,43 +88,43 @@ flyctl version --- -## 4. 首次部署當前項目 +## 4. 首次部署目前專案 -### 4.1 獲取代碼並進入目錄 +### 4.1 複製程式碼並進入目錄 ```powershell git clone https://github.com/diegosouzapw/OmniRoute.git cd OmniRoute ``` -### 4.2 確認應用名 +### 4.2 確認應用程式名稱 -打開 `fly.toml`,重點看這一行: +開啟 `fly.toml` 並確認以下行: ```toml app = 'omniroute' ``` -如果你準備部署到自己的新應用,可改成全局唯一名稱,例如: +如果您要部署到自己的新應用程式,可以將其變更為全域唯一的名稱,例如: ```toml app = 'omniroute-yourname' ``` -注意: +注意事項: -- 控制臺裡要看的是與 `fly.toml` 裡 `app` 一致的應用 -- 以前如果用過別的名字,例如 `oroute`,不要和 `omniroute` 混淆 +- 請確保您在控制台中看到的應用程式與 `fly.toml` 中的 `app` 值相符 +- 如果您之前使用過不同的名稱(例如 `oroute`),請不要與 `omniroute` 混淆 -### 4.3 創建應用 +### 4.3 建立應用程式 -如果該應用尚不存在: +如果應用程式尚未存在: ```powershell flyctl apps create omniroute ``` -如果你已經改成別的應用名,把 `omniroute` 替換成你的名字。 +如果您變更了應用程式名稱,請將 `omniroute` 替換為您選擇的名稱。 ### 4.4 首次部署 @@ -130,163 +134,204 @@ flyctl deploy --- -## 5. 必配參數 +## 5. 必要參數 -本項目在 Fly.io 上建議至少設定以下參數。 +本專案建議至少在 Fly.io 上配置以下參數。 -### 5.1 已驗證使用的參數 +### 5.1 已驗證的參數 -這些參數已經在當前 `omniroute` 應用上實際部署: +以下參數已在目前 `omniroute` 應用程式的實際部署中使用: - `API_KEY_SECRET` - `DATA_DIR` - `JWT_SECRET` - `MACHINE_ID_SALT` - `NEXT_PUBLIC_BASE_URL` +- `OMNIROUTE_WS_BRIDGE_SECRET`(生產環境必填 — 用於 WebSocket 橋接認證) - `STORAGE_ENCRYPTION_KEY` ### 5.2 關於 `INITIAL_PASSWORD` -當前項目沒有設置 `INITIAL_PASSWORD`,因為本次部署按需求不使用它。 +目前專案未設定 `INITIAL_PASSWORD`,因為此部署不需要。 -如果不設置: +如果未設定: -- 啟動日誌會提示默認密碼是 `CHANGEME` -- 部署後應儘快在系統設置中修改登錄密碼 +- 啟動日誌會顯示預設密碼為 `CHANGEME` +- 您應在部署後盡快在系統設定中變更登入密碼 -如果你希望無人值守初始化後臺密碼,也可以後續補: +如果您想在無人值守的情況下初始化後端密碼,可以之後再新增: - `INITIAL_PASSWORD` --- -## 6. 推薦參數說明 +## 6. 建議參數 -### 6.1 Secrets 中設置 +### 6.1 機密配置 -建議放入 Fly Secrets: +以下變數建議用於 Fly Secrets: -| 變量名 | 是否推薦 | 說明 | -| ------------------------ | -------- | ------------------------------ | -| `API_KEY_SECRET` | 必需 | API Key 生成與校驗使用 | -| `JWT_SECRET` | 必需 | 登錄態和 JWT 籤名使用 | -| `STORAGE_ENCRYPTION_KEY` | 強烈推薦 | 加密存儲敏感連接資訊 | -| `MACHINE_ID_SALT` | 推薦 | 生成穩定機器標識 | -| `INITIAL_PASSWORD` | 可選 | 首次部署時直接指定後臺初始密碼 | -| OAuth/API 私密憑證 | 按需 | 各類外部平臺鑑權設定 | +| 變數 | 建議 | 說明 | +| ----------------------------- | -------------- | ----------------------------------- | +| `API_KEY_SECRET` | 必填 | 用於 API 金鑰產生與驗證 | +| `JWT_SECRET` | 必填 | 用於登入工作階段和 JWT 簽章 | +| `OMNIROUTE_WS_BRIDGE_SECRET` | 生產環境必填 | WebSocket 橋接認證密鑰 | +| `STORAGE_ENCRYPTION_KEY` | 強烈建議 | 靜態加密敏感連線資訊 | +| `MACHINE_ID_SALT` | 建議 | 產生穩定的機器識別碼 | +| `INITIAL_PASSWORD` | 可選 | 首次部署時設定初始後端密碼 | +| OAuth/API 私有憑證 | 視需要而定 | 外部平台認證配置 | -### 6.2 當前項目推薦值 +### 6.2 目前專案的建議值 -| 變量名 | 推薦值 | +| 變數 | 建議值 | | ---------------------- | --------------------------- | | `DATA_DIR` | `/data` | | `NEXT_PUBLIC_BASE_URL` | `https://omniroute.fly.dev` | -說明: +注意事項: -- `DATA_DIR=/data` 非常關鍵,必須與 Fly Volume 掛載點一致 -- `NEXT_PUBLIC_BASE_URL` 用於調度器和前端回調等場景 +- `DATA_DIR=/data` 至關重要,必須與 Fly Volume 掛載點相符 +- `NEXT_PUBLIC_BASE_URL` 由排程器、前端回呼等使用 + +### 6.3 OAuth 回呼 URL 配置 + +如果您需要在 Fly.io 部署上啟用基於 OAuth 的提供商(例如 Antigravity、Gemini、Cursor),請確保以下兩點: + +1. **將 `NEXT_PUBLIC_BASE_URL` 設定為您的公開 HTTPS 網域** + + ```powershell + flyctl secrets set NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev -a omniroute + ``` + + 如果您使用自訂網域,請替換為對應的網域(例如 `https://omniroute.yourdomain.com`)。 + +2. **在提供商控制台中配置回呼 URL** + + 所有 OAuth 提供商共用單一回呼路徑 `/callback` — 沒有每個提供商的獨立回呼路由: + + ```text + /callback + ``` + + 例如,不論是 Gemini、Antigravity、Cursor 或 GitLab Duo: + - `https://omniroute.fly.dev/callback` + + 如果 `NEXT_PUBLIC_BASE_URL` 與註冊在提供商的回呼 URL 不符,OAuth 流程將在瀏覽器重新導向步驟失敗。 --- -## 7. 一鍵設置參數 +## 7. 單一命令密鑰設定 -下面命令會生成安全隨機值,並把當前項目需要的參數一次性寫入 Fly Secrets。 +以下命令會產生安全隨機值,並一步將目前專案的所有必要參數寫入 Fly Secrets。 -說明: +注意事項: - 不包含 `INITIAL_PASSWORD` -- 適用於當前項目 `omniroute` +- 適用於目前專案 `omniroute` ```powershell $apiKeySecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() $jwtSecret = [Convert]::ToHexString((1..64 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() $machineIdSalt = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() $storageKey = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() +$wsBridgeSecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() flyctl secrets set ` API_KEY_SECRET=$apiKeySecret ` JWT_SECRET=$jwtSecret ` MACHINE_ID_SALT=$machineIdSalt ` STORAGE_ENCRYPTION_KEY=$storageKey ` + OMNIROUTE_WS_BRIDGE_SECRET=$wsBridgeSecret ` DATA_DIR=/data ` NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev ` -a omniroute ``` -如果你還要加初始密碼: +在 Linux / macOS 上,也可以使用 `openssl rand -hex 32`: + +```bash +flyctl secrets set OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -hex 32) -a omniroute +``` + +注意事項: + +- `OMNIROUTE_WS_BRIDGE_SECRET` 在生產環境中為必填;缺少此項將導致 WebSocket 橋接通話失敗 + +如果您也想設定初始密碼: ```powershell -flyctl secrets set INITIAL_PASSWORD=你的強密碼 -a omniroute +flyctl secrets set INITIAL_PASSWORD=your-strong-password -a omniroute ``` --- -## 8. 查看當前參數 +## 8. 檢視目前參數 ```powershell flyctl secrets list -a omniroute ``` -如果控制臺 `Secrets` 頁面沒有顯示你期待的變量,先檢查: +如果控制台中的「Secrets」頁面未顯示預期的變數,請檢查: -- 看的應用是不是 `omniroute` -- `fly.toml` 的 `app` 是否和控制臺應用一致 +- 您正在檢視的是 `omniroute` 應用程式 +- `fly.toml` 中的 `app` 值與控制台中的應用程式相符 --- -## 9. 後續更新發布 +## 9. 後續更新與發布 -代碼有更新後,發布步驟很簡單: +程式碼更新後,發布流程很簡單: ```powershell git pull flyctl deploy ``` -如果只更新參數,不改代碼: +如果您只需要更新參數而不變更程式碼: ```powershell flyctl secrets set KEY=value -a omniroute ``` -Fly 會自動滾動更新機器。 +Fly 會自動執行機器的滾動更新。 -### 9.1 跟蹤原倉庫更新並保留 fork 的 `fly.toml` +### 9.1 追蹤上游儲存庫更新同時保留 Fork 的 `fly.toml` -如果當前倉庫是 fork,並且你要同步上遊 `https://github.com/diegosouzapw/OmniRoute` 的更新,推薦按下面流程執行。 +如果目前儲存庫是 fork,且您想同步上游 `https://github.com/diegosouzapw/OmniRoute` 的更新,請遵循以下工作流程。 -先確認遠程: +首先,確認您的遠端倉庫: ```powershell git remote -v ``` -應至少包含: +您應該會看到: -- `origin` 指向你自己的 fork -- `upstream` 指向原倉庫 +- `origin` 指向您自己的 fork +- `upstream` 指向原始儲存庫 -如果沒有 `upstream`,先添加: +如果未配置 `upstream`,請新增: ```powershell git remote add upstream https://github.com/diegosouzapw/OmniRoute.git ``` -同步上遊前,先抓取最新提交和標籤: +在與上游同步之前,請先擷取最新的提交和標籤: ```powershell git fetch upstream --tags ``` -查看當前版本和上遊標籤: +檢查目前版本和上游標籤: ```powershell git describe --tags --always git show --no-patch --oneline v3.4.7 ``` -如果你想合併上遊最新 `main`,並強制保留 fork 當前的 `fly.toml`,可按下面流程執行: +> 注意:目前專案版本為 `v3.8.0`。以下 `v3.4.7` 的引用僅作為歷史範例保留。實際發布時,請使用 `:latest` 或目前版本標籤(例如 `:v3.8.0`)。 + +如果您想合併最新的上游 `main`,同時強制保留您 fork 的 `fly.toml`,請遵循以下工作流程: ```powershell git merge upstream/main @@ -296,52 +341,52 @@ git commit -m "chore(deploy): keep fork fly.toml" git push origin main ``` -說明: +注意事項: -- `git merge upstream/main` 用於同步原倉庫最新代碼 -- `git checkout HEAD~1 -- fly.toml` 用於恢復合併前你 fork 自己的 `fly.toml` -- 如果上遊沒有改 `fly.toml`,這一步不會帶來額外差異 -- 如果上遊改了 `fly.toml`,這一步能確保 Fly 應用名、掛載卷、區域等 fork 自定義部署設定不被覆蓋 +- `git merge upstream/main` 會同步原始儲存庫的最新程式碼 +- `git checkout HEAD~1 -- fly.toml` 會從合併前還原您 fork 自己的 `fly.toml` +- 如果上游未修改 `fly.toml`,此步驟不會引入任何差異 +- 如果上游修改了 `fly.toml`,此步驟可確保您的 Fly 應用程式名稱、磁碟區掛載、地區和其他 fork 特定的部署配置不會被覆寫 -如果你明確只想對齊某個發布標籤,例如 `v3.4.7`,也可以先確認標籤是否已經包含在 `upstream/main`: +如果您想對齊特定的發布標籤(例如 `v3.4.7`),請先確認該標籤已包含在 `upstream/main` 中: ```powershell git merge-base --is-ancestor v3.4.7 upstream/main ``` -返回成功表示 `upstream/main` 已經包含該版本,直接合併 `upstream/main` 即可。 +成功回傳表示 `upstream/main` 已包含該版本;您可以直接合併 `upstream/main`。 -### 9.2 同步上遊後的標準發布順序 +### 9.2 同步上游後的標準發布順序 -同步原倉庫完成後,推薦按下面順序發布: +與原始儲存庫同步後,請遵循此建議的發布順序: 1. `git fetch upstream --tags` 2. `git merge upstream/main` -3. 恢復 fork 的 `fly.toml` +3. 還原 fork 的 `fly.toml` 4. `git push origin main` 5. `flyctl deploy` 6. `flyctl status -a omniroute` 7. `flyctl logs --no-tail -a omniroute` -這就是當前項目升級到 `v3.4.7` 時使用的實際流程。 +這是升級目前專案至 `v3.4.7` 時使用的實際工作流程(範例引用的是舊版本;目前實際版本為 `v3.8.0`)。 --- -## 10. 發布後檢查 +## 10. 部署後檢查 -### 10.1 查看應用狀態 +### 10.1 檢查應用程式狀態 ```powershell flyctl status -a omniroute ``` -### 10.2 查看啟動日誌 +### 10.2 檢視啟動日誌 ```powershell flyctl logs --no-tail -a omniroute ``` -### 10.3 檢查網站可訪問 +### 10.3 驗證網站可存取性 ```powershell try { @@ -355,40 +400,40 @@ try { } ``` -返回 `200` 說明站點已正常響應。 +回傳值為 `200` 表示網站正常回應。 --- -## 11. 成功標誌 +## 11. 成功指標 -部署成功後,日誌裡應看到類似內容: +成功部署後,日誌應顯示類似以下內容: ```text [bootstrap] Secrets persisted to: /data/server.env [DB] SQLite database ready: /data/storage.sqlite ``` -這兩個點很關鍵: +這兩點至關重要: -- `/data/server.env` 說明運行時密鑰落到了持久卷 -- `/data/storage.sqlite` 說明資料庫寫入持久卷 +- `/data/server.env` 確認執行時期密鑰已寫入持久化磁碟區 +- `/data/storage.sqlite` 確認資料庫已寫入持久化磁碟區 -如果你看到的是 `/app/data/...`,說明 `DATA_DIR` 沒配對,需要立即修正。 +如果您看到 `/app/data/...`,表示 `DATA_DIR` 配置錯誤,必須立即修正。 --- ## 12. 常見問題 -### 12.1 `Secrets` 頁面是空的 +### 12.1 「Secrets」頁面為空 -通常有兩種原因: +通常有兩個原因: -- 你還沒執行 `flyctl secrets set` -- 你打開的是另一個應用,例如 `oroute`,不是 `omniroute` +- 您尚未執行 `flyctl secrets set` +- 您正在檢視不同的應用程式(例如 `oroute` 而非 `omniroute`) -### 12.2 `flyctl deploy` 報 `app not found` +### 12.2 `flyctl deploy` 回報 `app not found` -先創建應用: +請先建立應用程式: ```powershell flyctl apps create omniroute @@ -396,41 +441,41 @@ flyctl apps create omniroute ### 12.3 `fly.toml` 解析失敗 -重點檢查: +請檢查以下項目: -- 注釋裡是否有亂碼字符 -- TOML 引號和縮進是否正確 +- 註解中是否有亂碼字元 +- TOML 的引號和縮排是否正確 -### 12.4 數據沒有持久化 +### 12.4 資料未持久化 -檢查以下兩點: +請同時驗證以下兩項: -- `fly.toml` 中是否存在 `destination = '/data'` -- `DATA_DIR` 是否設置為 `/data` +- `fly.toml` 包含 `destination = '/data'` +- `DATA_DIR` 設定為 `/data` -### 12.5 不設置 `INITIAL_PASSWORD` 是否能跑 +### 12.5 沒有 `INITIAL_PASSWORD` 可以執行嗎? -可以運行,但會回退到默認 `CHANGEME`。生產環境建議儘快修改後臺密碼。 +可以執行。它會回退到預設密碼 `CHANGEME`。建議在生產環境中盡快變更後端密碼。 --- -## 13. 新項目復用建議 +## 13. 新專案重複使用 -如果以後是新項目照著這份文檔部署,最少改這幾項: +如果您要依照本文件部署新專案,只需變更以下項目: -1. 修改 `fly.toml` 裡的 `app` -2. 修改 `NEXT_PUBLIC_BASE_URL` -3. 保持 `DATA_DIR=/data` -4. 重新生成 `API_KEY_SECRET`、`JWT_SECRET`、`MACHINE_ID_SALT`、`STORAGE_ENCRYPTION_KEY` -5. 首次部署後檢查日誌是否寫入 `/data` +1. 變更 `fly.toml` 中的 `app` 值 +2. 變更 `NEXT_PUBLIC_BASE_URL` +3. 保留 `DATA_DIR=/data` +4. 重新產生 `API_KEY_SECRET`、`JWT_SECRET`、`MACHINE_ID_SALT` 和 `STORAGE_ENCRYPTION_KEY` +5. 首次部署後,驗證日誌是否寫入 `/data` -不要直接復用舊項目的密鑰。 +請勿重複使用先前專案的密鑰。 --- -## 14. 當前項目的最小發布清單 +## 14. 目前專案的極簡發布檢查清單 -當前項目後續最常用的命令如下: +後續發布最常用的命令: ```powershell flyctl auth whoami @@ -440,13 +485,13 @@ flyctl deploy flyctl logs --no-tail -a omniroute ``` -如果只是正常發版,核心就是: +對於一般發布,核心命令很簡單: ```powershell flyctl deploy ``` -如果是新環境首次部署,核心就是: +對於新環境的首次部署,核心步驟為: 1. `flyctl auth login` 2. `flyctl apps create omniroute` diff --git a/docs/i18n/zh-TW/docs/ops/RELEASE_CHECKLIST.md b/docs/i18n/zh-TW/docs/ops/RELEASE_CHECKLIST.md index d9b151cc8d..b4eb765944 100644 --- a/docs/i18n/zh-TW/docs/ops/RELEASE_CHECKLIST.md +++ b/docs/i18n/zh-TW/docs/ops/RELEASE_CHECKLIST.md @@ -1,44 +1,373 @@ -# Release Checklist (中文 (簡體)) +--- +title: "發行檢查清單" +version: 3.8.40 +lastUpdated: 2026-06-28 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/RELEASE_CHECKLIST.md) · 🇸🇦 [ar](../../ar/docs/RELEASE_CHECKLIST.md) · 🇧🇬 [bg](../../bg/docs/RELEASE_CHECKLIST.md) · 🇧🇩 [bn](../../bn/docs/RELEASE_CHECKLIST.md) · 🇨🇿 [cs](../../cs/docs/RELEASE_CHECKLIST.md) · 🇩🇰 [da](../../da/docs/RELEASE_CHECKLIST.md) · 🇩🇪 [de](../../de/docs/RELEASE_CHECKLIST.md) · 🇪🇸 [es](../../es/docs/RELEASE_CHECKLIST.md) · 🇮🇷 [fa](../../fa/docs/RELEASE_CHECKLIST.md) · 🇫🇮 [fi](../../fi/docs/RELEASE_CHECKLIST.md) · 🇫🇷 [fr](../../fr/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [gu](../../gu/docs/RELEASE_CHECKLIST.md) · 🇮🇱 [he](../../he/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [hi](../../hi/docs/RELEASE_CHECKLIST.md) · 🇭🇺 [hu](../../hu/docs/RELEASE_CHECKLIST.md) · 🇮🇩 [id](../../id/docs/RELEASE_CHECKLIST.md) · 🇮🇹 [it](../../it/docs/RELEASE_CHECKLIST.md) · 🇯🇵 [ja](../../ja/docs/RELEASE_CHECKLIST.md) · 🇰🇷 [ko](../../ko/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [mr](../../mr/docs/RELEASE_CHECKLIST.md) · 🇲🇾 [ms](../../ms/docs/RELEASE_CHECKLIST.md) · 🇳🇱 [nl](../../nl/docs/RELEASE_CHECKLIST.md) · 🇳🇴 [no](../../no/docs/RELEASE_CHECKLIST.md) · 🇵🇭 [phi](../../phi/docs/RELEASE_CHECKLIST.md) · 🇵🇱 [pl](../../pl/docs/RELEASE_CHECKLIST.md) · 🇵🇹 [pt](../../pt/docs/RELEASE_CHECKLIST.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/RELEASE_CHECKLIST.md) · 🇷🇴 [ro](../../ro/docs/RELEASE_CHECKLIST.md) · 🇷🇺 [ru](../../ru/docs/RELEASE_CHECKLIST.md) · 🇸🇰 [sk](../../sk/docs/RELEASE_CHECKLIST.md) · 🇸🇪 [sv](../../sv/docs/RELEASE_CHECKLIST.md) · 🇰🇪 [sw](../../sw/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [ta](../../ta/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [te](../../te/docs/RELEASE_CHECKLIST.md) · 🇹🇭 [th](../../th/docs/RELEASE_CHECKLIST.md) · 🇹🇷 [tr](../../tr/docs/RELEASE_CHECKLIST.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/RELEASE_CHECKLIST.md) · 🇵🇰 [ur](../../ur/docs/RELEASE_CHECKLIST.md) · 🇻🇳 [vi](../../vi/docs/RELEASE_CHECKLIST.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/RELEASE_CHECKLIST.md) +# 發行檢查清單 + +> **最後更新:** 2026-06-28 — v3.8.40 +> 精簡化發行流程,運用 Claude Code Skills 實現自動化。 +> +> **在發行之間保持佇列/分支為綠色:** 請參閱 [RELEASE_GREEN.md](./RELEASE_GREEN.md) +>(`/green-prs` 系列指令 + `npm run check:release-green` + `/babysit` + 夜間排程)。定期執行此流程—— +> 尤其是在執行本檢查清單**之前**——可讓發行 PR 一開始就處於綠色狀態。 + +## TL;DR + +```bash +# 1. 更新版本號 + 產生 CHANGELOG(skill) +/version-bump-cc patch # 或 minor / major + +# 2. 在本機執行品質門檻檢查 +npm run check # lint + 測試 +npm run test:coverage # 完整覆蓋率門檻(60/60/60/60) + +# 3. 建置與冒煙測試 +npm run build +npm run test:e2e # 選擇性但建議執行 + +# 4. 產生存放版(skill) +/generate-release-cc + +# 5. 部署(skill) +/deploy-vps-both-cc # 或 akamai-cc / local-cc + +# 6. 擷取發行佐證(skill) +/capture-release-evidences-cc +``` + +## npm 階段式發布(自 v3.8.49 起預設 — WS1.3/D2) + +npm 發布工作流程不再直接發布:它會啟動打包後的 tarball +(`check:pack-boot`),然後執行 `npm stage publish`——確切的位元組會暫存在 +登錄檔中,在擁有者核准前**不可安裝**。人類 2FA 關卡移至 +驗證**之後**,而非之前。 + +**工作流程轉為綠色後的擁有者流程:** + +1. `npm stage list omniroute` — 找到 stage id(也會列印在工作流程摘要中)。 +2. 驗證暫存位元組(建議執行):`npm stage download `,然後將下載的 tarball + 安裝到臨時字首目錄並啟動它(`npm run check:pack-boot` 會在 CI 中自動執行 + 相同的 pack→install→boot 驗證流程)。 +3. `npm stage approve ` — 2FA 提示即為發布操作。`npm stage reject ` 則會捨棄。 +4. 發布後網路檢查:發布後驗證器(v3.8.49 方案的 WS1.4)會從公開登錄檔安裝 + 已發布版本到一個乾淨的容器中並啟動它。 + +**緊急備援:** 使用 `workflow_dispatch` 搭配 `publish_mode=direct` 可恢復 +傳統的立即 `npm publish`(僅在階段式發布本身出問題時使用;請記錄原因)。 + +**一次性強化措施(擁有者,npmjs.com):**為 `omniroute` 設定僅限階段式發布的 +Trusted Publisher,這樣即使長期權杖外洩,也無法從任何地方直接 `npm publish`—— +CI 只能暫存;只有擁有者的 2FA 才能真正發布。 + +**成品損壞應對手冊(未變更):** `npm deprecate omniroute@ " — use "` +為預設反射動作(幾分鐘內完成,可逆轉);`npm unpublish` 僅在 72 小時/無依賴套件 +的時間窗口內使用,且絕不作為第一步。Docker:絕不重寫版本標籤——回滾是將 +`latest` 重新指向最後一個正常的摘要。 + +## Hotfix 快速通道(標籤 `hotfix`) + +標記為 `hotfix` 的 PR 會跳過繁重的 CI 矩陣(9 分片 E2E、覆蓋率棘輪、 +品質門檻、品質延伸檢查),僅保留快速且高訊號的關卡:建置、 +單元測試分片、整合測試、vitest、lint/型別檢查、docs-sync、`check:pack-artifact` +以及 tarball 啟動冒煙測試(`check:pack-boot`)。目標:在 ≤15 分鐘內變為綠色,而非 ~33 分鐘。 + +**進入條件——必須全部符合(以 Chromium/VS Code/Node 緊急通道為藍本):** + +1. **嚴重性**:正式環境已中斷——已發布的成品啟動時崩潰/安全性修正/該版本的所有使用者都受影響。「重要」不等於「已中斷」。 +2. **授權**:只有倉儲擁有者可以套用 `hotfix` 標籤。此標籤即為核准——專案 PR 不得自行使用。 +3. **佐證**:PR 主體需連結先前完整通過的繁重執行結果(被跳過的工作本來會重新驗證的套件),以及修正本身從失敗到通過的測試記錄。 +4. **範圍**:僅限 cherry-pick——最小修正,不得重構,不得夾帶其他變更。 + +被跳過的覆蓋率/棘輪檢查範圍將由發行分支上的下一個完整執行重新驗證(持續 release-green)——快速通道是跳過**等待**,而非跳過**驗證**。 +僅限測試的 diff(所有檔案都在 `tests/` 下,且不在 `tests/e2e/` 下)會自動跳過 E2E +矩陣,無需任何標籤。 + +## 詳細檢查清單 + +### 發行前 + +- [ ] 所有目標為此版本的 PR 都已合併到 `release/vX.Y.0` +- [ ] 所有與此版本相關的 Linear/問題項目都已關閉或推遲至下個里程碑 +- [ ] CI 在 `release/vX.Y.0` 分支上為綠色 +- [ ] 程式碼中沒有 `TODO(release)` 標記:`grep -r "TODO(release)" src/ open-sse/` +- [ ] Docker 基礎映像為最新版本(目前為 `node:24.15.0-trixie-slim`) + +### 版本號與變更日誌 + +- [ ] 執行 `/version-bump-cc `(Claude Code skill) + - 更新 `package.json`、`electron/package.json` + - 從上次標籤以來的 git 提交重新產生 `CHANGELOG.md` + - 更新 README.md 徽章 +- [ ] 手動檢視 CHANGELOG.md,必要時清理提交訊息 +- [ ] 確認 `CHANGELOG.md` 中最新的 semver 區段與 `package.json` 版本一致 +- [ ] 保留 `## [Unreleased]` 作為變更日誌的第一個區段,供後續工作使用 +- [ ] 更新 `docs/openapi.yaml` → `info.version` 必須等於 `package.json` 版本 + +### 程式碼品質 + +- [ ] `npm run lint` — 0 個錯誤(警告為預先存在的) +- [ ] `npm run typecheck:core` — 乾淨通過 +- [ ] `npm run typecheck:noimplicit:core` — 乾淨通過(嚴格模式) +- [ ] `npm run check:cycles` — 沒有循環依賴 +- [ ] `npm run check:any-budget:t11` — 在預算內 +- [ ] `npm run check:route-validation:t06` — 乾淨通過 +- [ ] `npm run check:node-runtime` — 符合支援的執行時期最低版本(`>=22.22.2 <23`、`>=24.0.0 <27`,詳見 `src/shared/utils/nodeRuntimeSupport.ts` 中的 `SUPPORTED_NODE_RANGE`;需與 `package.json` 的 `engines` 一致) + +### 測試 + +- [ ] `npm run test:unit` — 通過 +- [ ] `npm run test:vitest` — 通過(MCP 伺服器、autoCombo、快取) +- [ ] `npm run test:coverage` — 門檻 60/60/60/60 已達成(statements/lines/functions/branches) +- [ ] `npm run test:integration` — 通過(若變更涉及 DB/處理器) +- [ ] `npm run test:combo:matrix` — 通過(combo 策略矩陣:證明所有 17 種路由策略的選擇決策是確定性的;在更動 combo 路由、策略解析或備援邏輯時執行) +- [ ] `RUN_COMBO_LIVE=1 npm run test:combo:live` — **選擇性/手動**(受閘控的真實上游冒煙測試;從 VPS `root@192.168.0.15` 讀取唯讀 DB 快照;會命中真實提供者,消耗額度;不在 CI 中執行;若無閘控變數則乾淨跳過) +- [ ] `npm run test:combo:live:vps` — **選擇性/手動**(Phase-3 VPS 即時冒煙測試:透過純 Node ESM 對 `.15` 伺服器執行 7 個 HTTP 情境;需要 `ssh root@192.168.0.15`;只會建立/刪除 `__live_test__*` 類型的 combo;會命中真實提供者;不在 CI 中執行) +- [ ] `npm run test:e2e` — 通過(UI 變更) +- [ ] `npm run test:protocols:e2e` — 通過(MCP/A2A 變更) +- [ ] `npm run test:ecosystem` — 通過 + +### Hooks(Husky 驗證) + +Husky hooks 位於 `.husky/` 目錄,會在 git 操作時自動執行。 + +- **pre-commit:** `npx lint-staged + node scripts/check/check-docs-sync.mjs + npm run check:any-budget:t11` +- **pre-push:** 快速確定性關卡 — `npm run check:any-budget:t11 && npm run check:tracked-artifacts`(於 2026-06-13 啟用)。故意排除 `test:unit`(速度慢;由 CI 的 `test-unit` 工作負責)。 + - 在推送發行分支前,請手動執行 `npm run test:unit`。 + +若 hook 失敗:請修正根本問題,不要使用 `--no-verify` 繞過。 + +### Conventional Commits + +所有與發行相關的提交都必須遵循 `type(scope): subject` 格式。 + +**有效類型:** `feat`、`fix`、`refactor`、`docs`、`test`、`chore`、`perf`、`style`、`ci` + +**有效範圍:** `db`、`sse`、`oauth`、`dashboard`、`api`、`cli`、`docker`、`ci`、`mcp`、`a2a`、`memory`、`skills`、`cloud-agent`、`guardrails`、`compression`、`auto-combo`、`resilience`、`providers`、`executors`、`translator`、`domain`、`authz` + +重大變更:在結尾加上 `BREAKING CHANGE:` 或在範圍後加上 `!`(例如 `feat(api)!: drop /v0`)。 + +### 文件 + +- [ ] `npm run check:docs-sync` 通過(pre-commit 會自動執行) +- [ ] `npm run check:docs-all` 通過(總括:docs-sync + docs-counts + env-doc-sync + deprecated-versions + doc-links) +- [ ] `npm run check:env-doc-sync` 退出碼為 0——程式碼 ↔ `.env.example` ↔ `docs/reference/ENVIRONMENT.md` 的環境變數合約完整無缺 +- [ ] `npm run check:doc-links` 退出碼為 0——重構後沒有損毀的內部 markdown 參照 +- [ ] 已檢視 `docs/architecture/ARCHITECTURE.md`,確認無儲存/執行時期偏差 +- [ ] 已檢視 `docs/guides/TROUBLESHOOTING.md`,確認無環境變數與操作偏差 +- [ ] 若 `.env.example` 有變更:已更新 `docs/reference/ENVIRONMENT.md` +- [ ] 若新功能有 UI:`docs/guides/USER_GUIDE.md` 中有提及 +- [ ] 若新功能有 API:已更新 `docs/reference/API_REFERENCE.md` + `docs/openapi.yaml` +- [ ] 若新功能為模組:存在專屬的 `docs/.md` +- [ ] 若有重大變更:`docs/guides/TROUBLESHOOTING.md` 中有遷移說明 + +### i18n + +- [ ] `npm run i18n:check` 退出碼為 0——翻譯狀態(`.i18n-state.json`)與來源文件同步(嚴格模式下無偏差來源;警告模式對最後一刻的文件修飾可接受,但應在打標籤前歸零) +- [ ] `npm run i18n:check-ui-coverage` 退出碼為 0——每個 UI 語系都達到或超過 80% 的覆蓋率門檻 +- [ ] `npm run i18n:sync-ui:dry` 回報 0 個缺失鍵,遍及全部 42 個語系 +- [ ] 若英文來源文件有變更,請在打標籤前執行 `npm run i18n:run`(需要在 `.env` 中有 `OMNIROUTE_TRANSLATION_API_KEY`) +- [ ] 若翻譯貢獻不大,可延遲至下一版本(在 CHANGELOG 中追蹤) + +### 資料庫遷移 + +- [ ] 若 `src/lib/db/migrations/` 中有新檔案: + - [ ] 每個遷移都是等冪的(`CREATE TABLE IF NOT EXISTS` 等) + - [ ] 遷移包含在交易中 + - [ ] 編號正確(序列中無間隙) +- [ ] 在全新安裝上測試:刪除 `~/.omniroute/omniroute.db` 並執行 `npm run dev` +- [ ] 在既有安裝上測試:備份資料庫、執行遷移、驗證結構 +- [ ] 若遷移會改寫表格,需正確處理 WAL 檔案(`-wal`、`-shm`) + +### 提供者目錄(Zod 驗證) + +- [ ] `src/shared/constants/providers.ts` 的 Zod 結構在載入時有效 + - [ ] 所有提供者都有必要欄位(`id`、`label`、`kind` 等) + - [ ] 新的免費提供者已提供 `freeNote` + - [ ] OAuth 提供者已在 `src/lib/oauth/constants/oauth.ts` 中註冊 `oauthConfig` +- [ ] 若新增提供者:`open-sse/executors/` 中有對應的執行器 +- [ ] 若非 OpenAI 格式:`open-sse/translator/` 中有轉譯器 +- [ ] 模型已在 `open-sse/config/providerRegistry.ts` 中註冊 +- [ ] `tests/unit/` 中的單元測試涵蓋提供者分類與路由 + +### 桌面版(Electron) + +若 `electron/` 有變更: + +- [ ] `npm run electron:smoke:packaged` 通過 +- [ ] 至少在 `:win`、`:mac`、`:linux` 其中之一測試過建置 +- [ ] 程式碼簽署憑證未過期(若有簽署) +- [ ] `electron/package.json` 版本與根目錄 `package.json` 一致 +- [ ] 若發布至 `stable` 頻道,已更新自動更新頻道指標 + +### 建置目錄結構 + +倉儲使用三個不同的輸出目錄——切勿混淆: + +| 目錄 | 用途 | 是否追蹤? | +| --------- | --------------------------------------------------- | ------------- | +| `src/` | 應用程式原始碼(TypeScript/TSX) | 是 | +| `.build/` | 建置中間產物 — `next build` 輸出(`distDir`) | 否(gitignored)| +| `dist/` | 可發行的 npm 套件 — 由 `assembleStandalone` 組合而成 | 否(gitignored)| + +> **操作注意:** 遠端 VPS 映像目錄仍為 `/usr/lib/node_modules/omniroute/app/`。 +> 只有**倉儲內**的建置輸出目錄變更了(`app/` → `dist/`)。部署 skills 會將 +> `dist/` 內容 rsync 到遠端的 `app/` 目錄——無需變更 VPS 路徑。 + +**單一建置流程:** + +``` +npm run build:release + └─ rm -rf .build dist (清理) + └─ next build → .build/next/ (中間產物) + └─ assembleStandalone (複製 standalone + static + public + natives → dist/) + └─ 寫入 dist/BUILD_SHA (HEAD 標記) +``` + +請勿為了部署而先執行 `npm run build` 再執行 `npm run build:cli`—— +請使用 `npm run build:release`,它會在單一命令中完成乾淨重建 + 標記。 + +### 成品驗證 + +- [ ] `npm run build:release` 成功且 `dist/BUILD_SHA` == `git rev-parse --short HEAD` +- [ ] `npm run check:pack-artifact` 乾淨通過——無 `app.__qa_backup`、`scripts/scratch`、`package-lock.json` 或其他本地殘留檔案 +- [ ] 建置後存在 `dist/server.js` + +### 標籤與發行 + +- [ ] 執行 `/generate-release-cc`(Claude Code skill): + - 建立標籤 `vX.Y.Z` + - 推送標籤與分支 + - 以變更日誌內容開啟 GitHub Release + - 附加 Electron 安裝程式(若有建置) +- [ ] 或手動操作: + ```bash + git tag -a vX.Y.Z -m "Release vX.Y.Z" + git push origin vX.Y.Z + gh release create vX.Y.Z --notes-from-tag + ``` + +### 部署 + +部署 skills 使用輕量 rsync 流程——無需 `npm pack`,無需 `npm i -g`: + +- [ ] 使用符合目標的部署 skill: + - `/deploy-vps-local-cc` — 本地 VPS(192.168.0.15) + - `/deploy-vps-akamai-cc` — Akamai VPS(69.164.221.35) + - `/deploy-vps-both-cc` — 兩者同時 +- [ ] 部署前,確認 `dist/BUILD_SHA` == `git rev-parse --short HEAD` +- [ ] 建置必須在 `node_modules` 是真實目錄的環境中執行(主檢出目錄或已執行 `npm ci` 的工作目錄——**不是符號連結的工作目錄**) +- [ ] 冒煙測試已部署的實例: + - 開啟 `/dashboard/health` → 檢查版本字串與發行版本一致 + - 對已知提供者發送 `/v1/chat/completions` 請求 + - 確認 `/api/monitoring/health` 回傳 `CLOSED` 的斷路器狀態 + - 確認 MCP 傳輸協定正常回應(`/mcp` HTTP、`/mcp-sse` SSE) + +### 發行後 + +- [ ] 執行 `/capture-release-evidences-cc`(Claude Code skill) + - 擷取新功能的 WebP 螢幕截圖/錄影 + - 附加到版本說明/部落格文章 +- [ ] 在 GitHub Discussions/Discord 上發布發行公告 +- [ ] 開啟下一版本的里程碑 +- [ ] 若為重大更新:置頂討論或在 `news.json` 中新增應用程式內橫幅 + +## 內嵌服務冒煙測試(v3.8.4+) + +在發布任何包含內嵌服務變更的版本前,請確認: + +### 全新資料庫啟動(可發現遷移衝突——於 v3.8.4 hotfix 後新增) + +- [ ] `DATA_DIR=$(mktemp -d) npm start &` — 等待 10 秒啟動 +- [ ] `curl -s http://127.0.0.1:20128/api/services/9router/status | jq '.tool'` 回傳 `"9router"`(不是 404,不是 500)。確認遷移 `071_services.sql` 已套用且已寫入種子資料列。 +- [ ] `sqlite3 $DATA_DIR/storage.sqlite "PRAGMA table_info(version_manager);" | grep -E "provider_expose|logs_buffer_path|last_sync_at"` 回傳 3 列。 +- [ ] `sqlite3 $DATA_DIR/storage.sqlite "PRAGMA table_info(webhooks);" | grep -E "kind|metadata_encrypted"` 回傳 2 列(驗證 `070_webhooks_kind_metadata.sql` 已套用)。 +- [ ] `node --import tsx/esm --test tests/unit/db/no-migration-collisions.test.ts` 通過——防止未來的衝突。 + +### 9Router + +- [ ] `POST /api/services/9router/install` 在 2 分鐘內回傳 200 並包含 `installedVersion` +- [ ] `POST /api/services/9router/start` 在 30 秒內回傳 200 且 `state: "running"` +- [ ] `GET /api/services/9router/status` 回報 `health: "healthy"` +- [ ] `POST /v1/chat/completions` 搭配 `"model": "9router/auto/..."` 回傳 200(端到端透過 9Router 路由) +- [ ] `GET /dashboard/providers/services/9router/embed/dashboard` 在代理程式內渲染 9Router 原生 UI(非直接 `127.0.0.1:port` iframe) +- [ ] `POST /api/services/9router/rotate-key` 回傳 `{ keyRotated: true }` 且服務正常重啟 +- [ ] `POST /api/services/9router/stop` 回傳 200 且 `state: "stopped"` +- [ ] `GET /api/services/9router/logs?tail=50` 回傳 SSE 串流,包含 `snapshot` 事件與最近的行 +- [ ] 在 PATH 中沒有 `npm` 的環境中安裝,回傳 500 並顯示友善(非堆疊追蹤)的錯誤訊息 + +### CLIProxyAPI + +- [ ] `POST /api/services/cliproxy/install` 在 2 分鐘內回傳 200 +- [ ] `POST /api/services/cliproxy/start` 在 30 秒內回傳 200 且 `state: "running"` +- [ ] `GET /api/services/cliproxy/status` 回報 `health: "healthy"` +- [ ] `POST /api/services/cliproxy/stop` 回傳 200 且 `state: "stopped"` +- [ ] `GET /api/services/cliproxy/logs?tail=50` 回傳 SSE 串流 + +### 安全性迴歸測試 + +- [ ] `curl -H "X-Forwarded-For: 1.2.3.4" http://localhost:20128/api/services/9router/start` 回傳 `403 LOCAL_ONLY` +- [ ] `curl -H "X-Forwarded-For: 1.2.3.4" http://localhost:20128/api/services/cliproxy/start` 回傳 `403 LOCAL_ONLY` +- [ ] `/api/services/*` 的錯誤回應不包含 `err.stack` 或絕對路徑 + +## v3.8.0+ 檢查項目 + +在發布任何 v3.8.x 版本前,請確認以下額外項目: + +- [ ] `omniroute --tray` 在 macOS 上啟動(systray2 已安裝到 `~/.omniroute/runtime/`) +- [ ] `omniroute --tray` 在 Linux 上啟動(需要 DISPLAY;若未設定則優雅報錯) +- [ ] `omniroute --tray` 在 Windows 上啟動(PowerShell NotifyIcon,無需額外二進位檔) +- [ ] `omniroute config tray enable` 建立開機自啟項目;disable 則移除 +- [ ] `npm install -g omniroute@<此版本>` 執行 postinstall 而不會致命退出 +- [ ] 更新路徑保留選擇性依賴:`omniroute update --apply` 和自動更新器 + 執行 `npm install -g … --include=optional`,因此 `optionalDependencies`(better-sqlite3、 + keytar、tls-client,以及 llmlingua SLM 堆疊:`@atjsh/llmlingua-2`、 + `@huggingface/transformers@3.5.2`、`@tensorflow/tfjs`、`js-tiktoken`)在更新後仍會保留。 + `@huggingface/transformers` 維持選擇性,因此其 `onnxruntime-node` CUDA 提供者的 postinstall + 不會在 CUDA 11 主機上中斷安裝。Ultra `modelPath` SLM 層還需要 + tinybert 模型,會在首次使用時自動下載到 `${DATA_DIR}/models/llmlingua`。Postinstall + (`scripts/build/colocateOptionals.mjs`)接著將 SLM 選擇性閉包複製到 + `dist/node_modules`,使工作者解析到**單一** `@huggingface/transformers` 3.5.2 + 選擇性實例——獨立追蹤僅捆綁 transformers,而非動態匯入的 + 選擇性套件,因此若無此步驟,工作者會載入 llmlingua-2 並使用根目錄的 transformers, + 導致 SLM 層靜默地失敗但仍保持運作。 +- [ ] `omniroute status` 在無 `.env` 的情況下正常運作(僅限 CLI 權杖路徑,迴環介面) +- [ ] `curl http://localhost:20128/api/shutdown` 回傳 401(始終受保護的路由) +- [ ] `curl -H "host: evil.com" http://localhost:20128/api/mcp/sse` 回傳 401(迴環保護) +- [ ] SQLite 執行時期在首次執行時解析為 `bundled`(捆綁的二進位檔對平台有效) +- [ ] 當 `node_modules/better-sqlite3` 被刪除時,SQLite 執行時期備援至 `runtime` +- [ ] Smart MCP 過濾器壓縮真實的 `playwright-mcp browser_snapshot` 輸出(≥50% 縮減) +- [ ] 所有 10 個 `skills/omniroute*/SKILL.md` 檔案均可透過原始 GitHub URL 公開獲取 +- [ ] 入門精靈在新安裝時顯示「運作方式」分層導覽步驟 +- [ ] 首頁儀表板的分層覆蓋率小工具顯示已設定/啟用中的計數 --- -Use this checklist before tagging or publishing a new OmniRoute release. +## 回滾 -## Version and Changelog +若發行版本有重大問題: -1. Bump `package.json` version (`x.y.z`) in the release branch. -2. Move release notes from `## [Unreleased]` in `CHANGELOG.md` to a dated section: - - `## [x.y.z] — YYYY-MM-DD` -3. Keep `## [Unreleased]` as the first changelog section for upcoming work. -4. Ensure the latest semver section in `CHANGELOG.md` equals `package.json` version. +1. `gh release edit vX.Y.Z --prerelease`(標記為非最新) +2. `git tag -d vX.Y.Z && git push --delete origin vX.Y.Z`(僅在使用者尚未採用時) +3. 或者:在 `release/vX.Y.0` 上進行 hotfix → 修補版本 `vX.Y.(Z+1)` +4. 立即在 GitHub Discussions 和 Discord 中溝通 -## API Docs +## 嚴格規則 -1. Update `docs/reference/openapi.yaml`: - - `info.version` must equal `package.json` version. -2. Validate endpoint examples if API contracts changed. +- 絕不直接提交到 `main` +- 絕不對 `main` 或 `release/*` 分支使用 `git push --force` +- 絕不跳過 Husky hooks(`--no-verify`) +- 絕不提交機密、憑證或 `.env` 檔案 +- 覆蓋率必須維持 ≥60/60/60/60(statements/lines/functions/branches) +- 在變更 `src/`、`open-sse/`、`electron/` 或 `bin/` 中的正式程式碼時,務必包含或更新測試 -## Runtime Docs +## 自動化同步檢查 -1. Review `docs/architecture/ARCHITECTURE.md` for storage/runtime drift. -2. Review `docs/guides/TROUBLESHOOTING.md` for env var and operational drift. -3. Verify the release/runtime Node.js version still satisfies the supported secure floor: - - `>=20.20.2 <21` or `>=22.22.2 <23` - - `npm run check:node-runtime` -4. Validate the npm publish artifact after building the standalone package: - - `npm run build:cli` - - `npm run check:pack-artifact` - - confirm no `app.__qa_backup`, `scripts/scratch`, `package-lock.json`, or other local residue -5. Update localized docs if source docs changed significantly. - -## Automated Check - -Run the sync guard locally before opening PR: +在開啟 PR 前在本機執行文件同步檢查: ```bash npm run check:docs-sync ``` -CI also runs this check in `.github/workflows/ci.yml` (lint job). +CI 也會在 `.github/workflows/ci.yml`(lint 工作)中執行此檢查。 diff --git a/docs/i18n/zh-TW/docs/ops/VM_DEPLOYMENT_GUIDE.md b/docs/i18n/zh-TW/docs/ops/VM_DEPLOYMENT_GUIDE.md index 1f5b355781..f039d3267f 100644 --- a/docs/i18n/zh-TW/docs/ops/VM_DEPLOYMENT_GUIDE.md +++ b/docs/i18n/zh-TW/docs/ops/VM_DEPLOYMENT_GUIDE.md @@ -1,130 +1,138 @@ -# OmniRoute — Deployment Guide on VM with Cloudflare (中文 (簡體)) +--- +title: "OmniRoute — VM 部署指南(搭配 Cloudflare)" +version: 3.8.40 +lastUpdated: 2026-06-28 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/VM_DEPLOYMENT_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/VM_DEPLOYMENT_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/VM_DEPLOYMENT_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/VM_DEPLOYMENT_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/VM_DEPLOYMENT_GUIDE.md) · 🇩🇰 [da](../../da/docs/VM_DEPLOYMENT_GUIDE.md) · 🇩🇪 [de](../../de/docs/VM_DEPLOYMENT_GUIDE.md) · 🇪🇸 [es](../../es/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/VM_DEPLOYMENT_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/VM_DEPLOYMENT_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇱 [he](../../he/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/VM_DEPLOYMENT_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇩 [id](../../id/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇹 [it](../../it/docs/VM_DEPLOYMENT_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/VM_DEPLOYMENT_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/VM_DEPLOYMENT_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/VM_DEPLOYMENT_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/VM_DEPLOYMENT_GUIDE.md) · 🇳🇴 [no](../../no/docs/VM_DEPLOYMENT_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/VM_DEPLOYMENT_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/VM_DEPLOYMENT_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/VM_DEPLOYMENT_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/VM_DEPLOYMENT_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/VM_DEPLOYMENT_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/VM_DEPLOYMENT_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/VM_DEPLOYMENT_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/VM_DEPLOYMENT_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [te](../../te/docs/VM_DEPLOYMENT_GUIDE.md) · 🇹🇭 [th](../../th/docs/VM_DEPLOYMENT_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/VM_DEPLOYMENT_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/VM_DEPLOYMENT_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/VM_DEPLOYMENT_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/VM_DEPLOYMENT_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/VM_DEPLOYMENT_GUIDE.md) +# OmniRoute — VM 部署指南(搭配 Cloudflare) + +🌐 **語言:** 🇺🇸 [English](./VM_DEPLOYMENT_GUIDE.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇪🇸 [Español](../i18n/es/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇫🇷 [Français](../i18n/fr/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇮🇹 [Italiano](../i18n/it/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇷🇺 [Русский](../i18n/ru/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇩🇪 [Deutsch](../i18n/de/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇮🇳 [हिन्दी](../i18n/in/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇹🇭 [ไทย](../i18n/th/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇺🇦 [Українська](../i18n/uk-UA/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇸🇦 [العربية](../i18n/ar/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇯🇵 [日本語](../i18n/ja/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇧🇬 [Български](../i18n/bg/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇩🇰 [Dansk](../i18n/da/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇫🇮 [Suomi](../i18n/fi/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇮🇱 [עברית](../i18n/he/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇭🇺 [Magyar](../i18n/hu/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇰🇷 [한국어](../i18n/ko/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇳🇱 [Nederlands](../i18n/nl/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇳🇴 [Norsk](../i18n/no/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇷🇴 [Română](../i18n/ro/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇵🇱 [Polski](../i18n/pl/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇸🇰 [Slovenčina](../i18n/sk/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇸🇪 [Svenska](../i18n/sv/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇵🇭 [Filipino](../i18n/phi/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇨🇿 [Čeština](../i18n/cs/docs/ops/VM_DEPLOYMENT_GUIDE.md) | 🇹🇼 [繁體中文](./VM_DEPLOYMENT_GUIDE.md) + +在 VM(VPS)上安裝並設定 OmniRoute 的完整指南,搭配經由 Cloudflare 管理的網域。 --- -Complete guide to install and configure OmniRoute on a VM (VPS) with domain managed via Cloudflare. +## 前置需求 + +| 項目 | 最低規格 | 建議規格 | +| ----------- | -------------------------- | ------------------ | +| **CPU** | 1 vCPU | 2 vCPU | +| **RAM** | 1 GB | 2 GB | +| **硬碟** | 10 GB SSD | 25 GB SSD | +| **作業系統**| Ubuntu 22.04 LTS | Ubuntu 24.04 LTS | +| **網域** | 在 Cloudflare 註冊 | — | +| **Docker** | Docker Engine 24+ | Docker 27+ | + +**經測試的提供商**: Akamai (Linode)、DigitalOcean、Vultr、Hetzner、AWS Lightsail。 --- -## Prerequisites +## 1. 設定 VM -| Item | Minimum | Recommended | -| ---------- | ------------------------ | ---------------- | -| **CPU** | 1 vCPU | 2 vCPU | -| **RAM** | 1 GB | 2 GB | -| **Disk** | 10 GB SSD | 25 GB SSD | -| **OS** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS | -| **Domain** | Registered on Cloudflare | — | -| **Docker** | Docker Engine 24+ | Docker 27+ | +### 1.1 建立執行個體 -**Tested providers**: Akamai (Linode), DigitalOcean, Vultr, Hetzner, AWS Lightsail. +在你偏好的 VPS 提供商: ---- +- 選擇 Ubuntu 24.04 LTS +- 選擇最低方案(1 vCPU / 1 GB RAM) +- 設定強效 root 密碼或配置 SSH 金鑰 +- 記下**公開 IP**(例如 `203.0.113.10`) -## 1. Configure the VM - -### 1.1 Create the instance - -On your preferred VPS provider: - -- Choose Ubuntu 24.04 LTS -- Select the minimum plan (1 vCPU / 1 GB RAM) -- Set a strong root password or configure SSH key -- Note the **public IP** (e.g., `203.0.113.10`) - -### 1.2 Connect via SSH +### 1.2 透過 SSH 連線 ```bash ssh root@203.0.113.10 ``` -### 1.3 Update the system +### 1.3 更新系統 ```bash apt update && apt upgrade -y ``` -### 1.4 Install Docker +### 1.4 安裝 Docker ```bash -# Install dependencies +# 安裝相依套件 apt install -y ca-certificates curl gnupg -# Add official Docker repository +# 加入官方 Docker 儲存庫 install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg -echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $ (. /etc/os-release && echo 「$VERSION_CODENAME」) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $ (. /etc/os-release && echo \"$VERSION_CODENAME\") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null apt update apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin ``` -### 1.5 Install nginx +### 1.5 安裝 nginx ```bash apt install -y nginx ``` -### 1.6 Configure Firewall (UFW) +### 1.6 設定防火牆 (UFW) ```bash ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp # SSH -ufw allow 80/tcp # HTTP (redirect) +ufw allow 80/tcp # HTTP(重新導向) ufw allow 443/tcp # HTTPS ufw enable ``` -> **Tip**: For maximum security, restrict ports 80 and 443 to Cloudflare IPs only. See the [Advanced Security](#advanced-security) section. +> **提示**: 為達最高安全性,可將連接埠 80 和 443 限制為僅允許 Cloudflare IP。請參閱[進階安全](#advanced-security)一節。 --- -## 2. Install OmniRoute +## 2. 安裝 OmniRoute -### 2.1 Create configuration directory +### 2.1 建立設定目錄 ```bash mkdir -p /opt/omniroute ``` -### 2.2 Create environment variables file +### 2.2 建立環境變數檔 ```bash -cat > /opt/omniroute/.env << 『EOF』 -# === Security === +cat > /opt/omniroute/.env << 'EOF' +# === 安全性 === JWT_SECRET=CHANGE-TO-A-UNIQUE-64-CHAR-SECRET-KEY INITIAL_PASSWORD=YourSecurePassword123! API_KEY_SECRET=REPLACE-WITH-ANOTHER-SECRET-KEY STORAGE_ENCRYPTION_KEY=REPLACE-WITH-THIRD-SECRET-KEY STORAGE_ENCRYPTION_KEY_VERSION=v1 MACHINE_ID_SALT=CHANGE-TO-A-UNIQUE-SALT +OMNIROUTE_WS_BRIDGE_SECRET=REPLACE-WITH-WS-BRIDGE-SECRET # 生產環境必填:Codex Responses WS bridge 使用 -# === App === +# === 應用程式 === PORT=20128 NODE_ENV=production HOSTNAME=0.0.0.0 DATA_DIR=/app/data -STORAGE_DRIVER=sqlite APP_LOG_TO_FILE=true -AUTH_COOKIE_SECURE=false +AUTH_COOKIE_SECURE=true REQUIRE_API_KEY=false -# === Domain (change to your domain) === -BASE_URL=https://llms.seudominio.com +# === URLs(請改為你的網域)=== +# 內部伺服器對伺服器的基礎 URL,用於排程任務/自我擷取 +BASE_URL=http://127.0.0.1:20128 +# 瀏覽器端使用的 URL,用於 OAuth 回呼、儀表板連結和產生的公開 URL NEXT_PUBLIC_BASE_URL=https://llms.seudominio.com +# 選擇性:產生的公開資源 URL 的明確公開來源覆寫 +# OMNIROUTE_PUBLIC_BASE_URL=https://llms.seudominio.com -# === Cloud Sync (optional) === +# === Cloud 同步(選擇性)=== # CLOUD_URL=https://cloud.omniroute.online # NEXT_PUBLIC_CLOUD_URL=https://cloud.omniroute.online EOF ``` -> ⚠️ **IMPORTANT**: Generate unique secret keys! Use `openssl rand -hex 32` for each key. +> ⚠️ **重要**: 請產生唯一的密鑰!使用 `openssl rand -hex 32` 為每個密鑰產生隨機值。 -### 2.3 Start the container +### 2.3 啟動容器 ```bash docker pull diegosouzapw/omniroute:latest @@ -138,45 +146,45 @@ docker run -d \ diegosouzapw/omniroute:latest ``` -### 2.4 Verify that it is running +### 2.4 確認運作中 ```bash docker ps | grep omniroute docker logs omniroute --tail 20 ``` -It should display: `[DB] SQLite database ready` and `listening on port 20128`. +應顯示:`[DB] SQLite database ready` 和 `listening on port 20128`。 --- -## 3. Configure nginx (Reverse Proxy) +## 3. 設定 nginx(反向代理) -### 3.1 Generate SSL certificate (Cloudflare Origin) +### 3.1 產生 SSL 憑證(Cloudflare Origin) -In the Cloudflare dashboard: +在 Cloudflare 儀表板中: -1. Go to **SSL/TLS → Origin Server** -2. Click **Create Certificate** -3. Keep the defaults (15 years, \*.yourdomain.com) -4. Copy the **Origin Certificate** and the **Private Key** +1. 前往 **SSL/TLS → Origin Server** +2. 點擊 **Create Certificate** +3. 保持預設值(15 年、\\*.yourdomain.com) +4. 複製 **Origin Certificate** 和 **Private Key** ```bash mkdir -p /etc/nginx/ssl -# Paste the certificate +# 貼上憑證 nano /etc/nginx/ssl/origin.crt -# Paste the private key +# 貼上私鑰 nano /etc/nginx/ssl/origin.key chmod 600 /etc/nginx/ssl/origin.key ``` -### 3.2 Nginx Configuration +### 3.2 Nginx 設定 ```bash -cat > /etc/nginx/sites-available/omniroute << 『NGINX』 -# Default server — blocks direct access via IP +cat > /etc/nginx/sites-available/omniroute << 'NGINX' +# 預設伺服器 — 封鎖直接透過 IP 存取 server { listen 80 default_server; listen [::]:80 default_server; @@ -192,7 +200,7 @@ server { server { listen 443 ssl; listen [::]:443 ssl; - server_name llms.yourdomain.com; # Change to your domain + server_name llms.yourdomain.com; # 改為你的網域 ssl_certificate /etc/nginx/ssl/origin.crt; ssl_certificate_key /etc/nginx/ssl/origin.key; @@ -203,16 +211,17 @@ server { location / { proxy_pass http://127.0.0.1:20128; proxy_set_header Host $host; + proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - # WebSocket support + # WebSocket 支援 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 「upgrade」; + proxy_set_header Connection "upgrade"; - # SSE (Server-Sent Events) — streaming AI responses + # SSE(Server-Sent Events)— 串流 AI 回應 proxy_buffering off; proxy_cache off; proxy_read_timeout 600s; @@ -220,7 +229,7 @@ server { } } -# HTTP → HTTPS redirect +# HTTP → HTTPS 重新導向 server { listen 80; listen [::]:80; @@ -230,59 +239,65 @@ server { NGINX ``` -Keep reverse-proxy stream timeouts aligned with your OmniRoute timeout env vars. If you raise -`FETCH_TIMEOUT_MS` / `STREAM_IDLE_TIMEOUT_MS`, raise `proxy_read_timeout` / `proxy_send_timeout` -above the same threshold. +請確保反向代理串流超時時間與你的 OmniRoute 超時環境變數保持一致。如果你調高了 +`FETCH_TIMEOUT_MS`/`STREAM_IDLE_TIMEOUT_MS`,請同步調高 `proxy_read_timeout`/`proxy_send_timeout` +至相同閾值以上。 -### 3.3 Enable and Test +OmniRoute 使用 `NEXT_PUBLIC_BASE_URL` 作為 OAuth 回呼和產生公開連結的標準瀏覽器端來源。 +已驗證的儀表板寫入操作使用同源請求加上綁定 session 的 CSRF 保護,因此不需要靜態公開基礎 URL。 +上述的 `X-Forwarded-*` 標頭依然是實用的路由後設資料,但在 OAuth 或產生的瀏覽器連結需要公開 URL +時,它們不能取代明確設定公開 URL。僅在 OmniRoute 無法被用戶端直接存取且你的代理伺服器 +會移除/重建傳入的轉發標頭時,才啟用 `OMNIROUTE_TRUST_PROXY`。 + +### 3.3 啟用並測試 ```bash -# Remove default configuration +# 移除預設設定 rm -f /etc/nginx/sites-enabled/default -# Enable OmniRoute +# 啟用 OmniRoute ln -sf /etc/nginx/sites-available/omniroute /etc/nginx/sites-enabled/omniroute -# Test and reload +# 測試並重新載入 nginx -t && systemctl reload nginx ``` --- -## 4. Configure Cloudflare DNS +## 4. 設定 Cloudflare DNS -### 4.1 Add DNS record +### 4.1 新增 DNS 記錄 -In the Cloudflare dashboard → DNS: +在 Cloudflare 儀表板 → DNS: -| Type | Name | Content | Proxy | -| ---- | ------ | ---------------------- | ---------- | -| A | `llms` | `203.0.113.10` (VM IP) | ✅ Proxied | +| 類型 | 名稱 | 內容 | Proxy | +| ---- | ------ | ----------------------- | ----------- | +| A | `llms` | `203.0.113.10`(VM IP) | ✅ 已代理 | -### 4.2 Configure SSL +### 4.2 設定 SSL -Under **SSL/TLS → Overview**: +在 **SSL/TLS → Overview**: -- Mode: **Full (Strict)** +- 模式:**Full (Strict)** -Under **SSL/TLS → Edge Certificates**: +在 **SSL/TLS → Edge Certificates**: -- Always Use HTTPS: ✅ On -- Minimum TLS Version: TLS 1.2 -- Automatic HTTPS Rewrites: ✅ On +- Always Use HTTPS:✅ 開啟 +- Minimum TLS Version:TLS 1.2 +- Automatic HTTPS Rewrites:✅ 開啟 -### 4.3 Testing +### 4.3 測試 ```bash curl -sI https://llms.seudominio.com/health -# Should return HTTP/2 200 +# 應回傳 HTTP/2 200 ``` --- -## 5. Operations and Maintenance +## 5. 操作與維護 -### Upgrade to a new version +### 升級至新版本 ```bash docker pull diegosouzapw/omniroute:latest @@ -294,42 +309,42 @@ docker run -d --name omniroute --restart unless-stopped \ diegosouzapw/omniroute:latest ``` -### View logs +### 檢視日誌 ```bash -docker logs -f omniroute # Real-time stream -docker logs omniroute --tail 50 # Last 50 lines +docker logs -f omniroute # 即時串流 +docker logs omniroute --tail 50 # 最後 50 行 ``` -### Manual database backup +### 手動資料庫備份 ```bash -# Copy data from the volume to the host +# 從容器複製資料到主機 docker cp omniroute:/app/data ./backup-$(date +%F) -# Or compress the entire volume +# 或壓縮整個磁碟區 docker run --rm -v omniroute-data:/data -v $(pwd):/backup \ alpine tar czf /backup/omniroute-data-$(date +%F).tar.gz /data ``` -### Restore from backup +### 從備份還原 ```bash docker stop omniroute docker run --rm -v omniroute-data:/data -v $(pwd):/backup \ - alpine sh -c 「rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /」 + alpine sh -c "rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /" docker start omniroute ``` --- -## 6. Advanced Security +## 6. 進階安全 -### Restrict nginx to Cloudflare IPs +### 限制 nginx 僅允許 Cloudflare IP ```bash -cat > /etc/nginx/cloudflare-ips.conf << 『CF』 -# Cloudflare IPv4 ranges — update periodically +cat > /etc/nginx/cloudflare-ips.conf << 'CF' +# Cloudflare IPv4 範圍 — 請定期更新 # https://www.cloudflare.com/ips-v4/ set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; @@ -350,58 +365,58 @@ real_ip_header CF-Connecting-IP; CF ``` -Add the following to `nginx.conf` inside the `http {}` block: +將以下內容加入 `nginx.conf` 中的 `http {}` 區塊: ```nginx include /etc/nginx/cloudflare-ips.conf; ``` -### Install fail2ban +### 安裝 fail2ban ```bash apt install -y fail2ban systemctl enable fail2ban systemctl start fail2ban -# Check status +# 檢查狀態 fail2ban-client status sshd ``` -### Block direct access to the Docker port +### 封鎖對 Docker 連接埠的直接存取 ```bash -# Prevent direct external access to port 20128 +# 防止外部直接存取連接埠 20128 iptables -I DOCKER-USER -p tcp --dport 20128 -j DROP iptables -I DOCKER-USER -i lo -p tcp --dport 20128 -j ACCEPT -# Persist the rules +# 持續保存規則 apt install -y iptables-persistent netfilter-persistent save ``` --- -## 7. Deploy to Cloudflare Workers (Optional) +## 7. 部署至 Cloudflare Workers(選擇性) -For remote access via Cloudflare Workers (without exposing the VM directly): +用於透過 Cloudflare Workers 進行遠端存取(無需直接暴露 VM): ```bash -# In the local repository +# 在本機儲存庫中 cd omnirouteCloud npm install npx wrangler login npx wrangler deploy ``` -See the full documentation at [omnirouteCloud/README.md](../omnirouteCloud/README.md). +另請參閱 [TUNNELS_GUIDE.md](./TUNNELS_GUIDE.md) 以了解儲存庫內的 Cloudflare Tunnel 逐步說明。獨立的 `omnirouteCloud/` worker 位於另一個配套儲存庫中。 --- -## Port Summary +## 連接埠摘要 -| Port | Service | Access | -| ----- | ----------- | -------------------------- | -| 22 | SSH | Public (with fail2ban) | -| 80 | nginx HTTP | Redirect → HTTPS | -| 443 | nginx HTTPS | Via Cloudflare Proxy | -| 20128 | OmniRoute | Localhost only (via nginx) | +| 連接埠 | 服務 | 存取方式 | +| ------ | ------------- | ------------------------------ | +| 22 | SSH | 公開(搭配 fail2ban) | +| 80 | nginx HTTP | 重新導向 → HTTPS | +| 443 | nginx HTTPS | 經由 Cloudflare Proxy | +| 20128 | OmniRoute | 僅限本機(經由 nginx) | diff --git a/docs/i18n/zh-TW/docs/reference/API_REFERENCE.md b/docs/i18n/zh-TW/docs/reference/API_REFERENCE.md index e5182f4016..0fc570e1d8 100644 --- a/docs/i18n/zh-TW/docs/reference/API_REFERENCE.md +++ b/docs/i18n/zh-TW/docs/reference/API_REFERENCE.md @@ -1,134 +1,247 @@ -# API Reference (中文 (簡體)) +--- +title: "API 參考" +version: 3.8.40 +lastUpdated: 2026-06-28 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/API_REFERENCE.md) · 🇸🇦 [ar](../../ar/docs/API_REFERENCE.md) · 🇧🇬 [bg](../../bg/docs/API_REFERENCE.md) · 🇧🇩 [bn](../../bn/docs/API_REFERENCE.md) · 🇨🇿 [cs](../../cs/docs/API_REFERENCE.md) · 🇩🇰 [da](../../da/docs/API_REFERENCE.md) · 🇩🇪 [de](../../de/docs/API_REFERENCE.md) · 🇪🇸 [es](../../es/docs/API_REFERENCE.md) · 🇮🇷 [fa](../../fa/docs/API_REFERENCE.md) · 🇫🇮 [fi](../../fi/docs/API_REFERENCE.md) · 🇫🇷 [fr](../../fr/docs/API_REFERENCE.md) · 🇮🇳 [gu](../../gu/docs/API_REFERENCE.md) · 🇮🇱 [he](../../he/docs/API_REFERENCE.md) · 🇮🇳 [hi](../../hi/docs/API_REFERENCE.md) · 🇭🇺 [hu](../../hu/docs/API_REFERENCE.md) · 🇮🇩 [id](../../id/docs/API_REFERENCE.md) · 🇮🇹 [it](../../it/docs/API_REFERENCE.md) · 🇯🇵 [ja](../../ja/docs/API_REFERENCE.md) · 🇰🇷 [ko](../../ko/docs/API_REFERENCE.md) · 🇮🇳 [mr](../../mr/docs/API_REFERENCE.md) · 🇲🇾 [ms](../../ms/docs/API_REFERENCE.md) · 🇳🇱 [nl](../../nl/docs/API_REFERENCE.md) · 🇳🇴 [no](../../no/docs/API_REFERENCE.md) · 🇵🇭 [phi](../../phi/docs/API_REFERENCE.md) · 🇵🇱 [pl](../../pl/docs/API_REFERENCE.md) · 🇵🇹 [pt](../../pt/docs/API_REFERENCE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/API_REFERENCE.md) · 🇷🇴 [ro](../../ro/docs/API_REFERENCE.md) · 🇷🇺 [ru](../../ru/docs/API_REFERENCE.md) · 🇸🇰 [sk](../../sk/docs/API_REFERENCE.md) · 🇸🇪 [sv](../../sv/docs/API_REFERENCE.md) · 🇰🇪 [sw](../../sw/docs/API_REFERENCE.md) · 🇮🇳 [ta](../../ta/docs/API_REFERENCE.md) · 🇮🇳 [te](../../te/docs/API_REFERENCE.md) · 🇹🇭 [th](../../th/docs/API_REFERENCE.md) · 🇹🇷 [tr](../../tr/docs/API_REFERENCE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/API_REFERENCE.md) · 🇵🇰 [ur](../../ur/docs/API_REFERENCE.md) · 🇻🇳 [vi](../../vi/docs/API_REFERENCE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/API_REFERENCE.md) +# API 參考 + +🌐 **語言:** 🇺🇸 [English](./API_REFERENCE.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/reference/API_REFERENCE.md) | 🇪🇸 [Español](../i18n/es/docs/reference/API_REFERENCE.md) | 🇫🇷 [Français](../i18n/fr/docs/reference/API_REFERENCE.md) | 🇮🇹 [Italiano](../i18n/it/docs/reference/API_REFERENCE.md) | 🇷🇺 [Русский](../i18n/ru/docs/reference/API_REFERENCE.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/docs/reference/API_REFERENCE.md) | 🇩🇪 [Deutsch](../i18n/de/docs/reference/API_REFERENCE.md) | 🇮🇳 [हिन्दी](../i18n/in/docs/reference/API_REFERENCE.md) | 🇹🇭 [ไทย](../i18n/th/docs/reference/API_REFERENCE.md) | 🇺🇦 [Українська](../i18n/uk-UA/docs/reference/API_REFERENCE.md) | 🇸🇦 [العربية](../i18n/ar/docs/reference/API_REFERENCE.md) | 🇯🇵 [日本語](../i18n/ja/docs/reference/API_REFERENCE.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/docs/reference/API_REFERENCE.md) | 🇧🇬 [Български](../i18n/bg/docs/reference/API_REFERENCE.md) | 🇩🇰 [Dansk](../i18n/da/docs/reference/API_REFERENCE.md) | 🇫🇮 [Suomi](../i18n/fi/docs/reference/API_REFERENCE.md) | 🇮🇱 [עברית](../i18n/he/docs/reference/API_REFERENCE.md) | 🇭🇺 [Magyar](../i18n/hu/docs/reference/API_REFERENCE.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/docs/reference/API_REFERENCE.md) | 🇰🇷 [한국어](../i18n/ko/docs/reference/API_REFERENCE.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/docs/reference/API_REFERENCE.md) | 🇳🇱 [Nederlands](../i18n/nl/docs/reference/API_REFERENCE.md) | 🇳🇴 [Norsk](../i18n/no/docs/reference/API_REFERENCE.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/docs/reference/API_REFERENCE.md) | 🇷🇴 [Română](../i18n/ro/docs/reference/API_REFERENCE.md) | 🇵🇱 [Polski](../i18n/pl/docs/reference/API_REFERENCE.md) | 🇸🇰 [Slovenčina](../i18n/sk/docs/reference/API_REFERENCE.md) | 🇸🇪 [Svenska](../i18n/sv/docs/reference/API_REFERENCE.md) | 🇵🇭 [Filipino](../i18n/phi/docs/reference/API_REFERENCE.md) | 🇨🇿 [Čeština](../i18n/cs/docs/reference/API_REFERENCE.md) + +所有 OmniRoute API 端點的完整參考資料。 --- -Complete reference for all OmniRoute API endpoints. +## 目錄 + +- [聊天補全](#聊天補全) +- [嵌入](#嵌入) +- [圖片生成](#圖片生成) +- [列出模型](#列出模型) +- [提供者外掛清單](#提供者外掛清單) +- [相容性端點](#相容性端點) +- [檔案 API](#檔案-api) +- [批次 API](#批次-api) +- [搜尋 API](#搜尋-api) +- [WebSocket 串流](#websocket-串流) +- [配額與問題回報](#配額與問題回報) +- [語義快取](#語義快取) +- [儀表板與管理](#儀表板與管理) +- [組合管理](#組合管理) +- [Webhook](#webhook) +- [註冊金鑰(自動管理)](#註冊金鑰自動管理) +- [代理程式協定](#代理程式協定) +- [管理代理](#管理代理) +- [彈性(進階)](#彈性進階) +- [技能](#技能) +- [記憶](#記憶) +- [MCP 伺服器](#mcp-伺服器) +- [A2A 伺服器](#a2a-伺服器) +- [雲端、評測與評估](#雲端評測與評估) +- [請求處理](#請求處理) +- [身分驗證](#身分驗證) --- -## Table of Contents - -- [Chat Completions](#chat-completions) -- [Embeddings](#embeddings) -- [Image Generation](#image-generation) -- [List Models](#list-models) -- [Compatibility Endpoints](#compatibility-endpoints) -- [Semantic Cache](#semantic-cache) -- [Dashboard & Management](#dashboard--management) -- [Request Processing](#request-processing) -- [Authentication](#authentication) - ---- - -## Chat Completions +## 聊天補全 ```bash POST /v1/chat/completions -Authorization: Bearer your-api-key +Authorization: Bearer *** Content-Type: application/json { "model": "cc/claude-opus-4-6", "messages": [ - {"role": "user", "content": "Write a function to..."} + {"role": "user", "content": "撰寫一個函式..."} ], "stream": true } ``` -### 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 | -| `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 | +| 標頭 | 方向 | 說明 | +| ------------------------------ | -------- | --------------------------------------------------------------------------------------------- | +| `X-OmniRoute-No-Cache` | 請求 | 設為 `true` 以略過快取 | +| `x-omniroute-no-memory` | 請求 | 設為 `true` 以跳過此請求的記憶與技能注入(等同於 no-cache;避免每次呼叫的權杖/成本開銷) | +| `X-OmniRoute-Progress` | 請求 | 設為 `true` 以取得進度事件 | +| `X-Session-Id` | 請求 | 用於外部工作階段親和性的黏性工作階段金鑰 | +| `x_session_id` | 請求 | 也接受底線變體(直接 HTTP) | +| `Idempotency-Key` | 請求 | 去重金鑰(5 秒窗口) | +| `X-Request-Id` | 請求 | 替代去重金鑰 | +| `X-OmniRoute-Cache` | 回應 | `HIT` 或 `MISS`(非串流) | +| `X-OmniRoute-Idempotent` | 回應 | `true` 表示已去重 | +| `X-OmniRoute-Progress` | 回應 | `enabled` 表示進度追蹤已啟用 | +| `X-OmniRoute-Session-Id` | 回應 | OmniRoute 使用的有效工作階段 ID | +| `X-OmniRoute-Request-Id` | 回應 | 請求關聯 ID(已知時) | +| `X-OmniRoute-Version` | 回應 | OmniRoute 建置版本(始終存在) | +| `X-OmniRoute-Cost-Saved` | 回應 | 快取命中避免的美金成本(僅限快取命中) | +| `X-OmniRoute-Decision` | 回應 | 路由追蹤:`strategy=<名稱>; provider=<別名>; latency_ms=`(`<名稱>` 為組合策略,非組合請求則為 `single`)—— 完成回應時始終存在 | -> Nginx note: if you rely on underscore headers (for example `x_session_id`), enable `underscores_in_headers on;`. +> Nginx 注意:如果您依賴底線標頭(例如 `x_session_id`),請啟用 `underscores_in_headers on;`。 + +> **成本遙測標頭:** 非串流成功回應也會攜帶 `X-OmniRoute-*` 成本遙測組 — `X-OmniRoute-Response-Cost`(美金,固定 10 位小數;免費/未定價為 `0.0000000000`)、`X-OmniRoute-Tokens-In` / `X-OmniRoute-Tokens-Out`、`X-OmniRoute-Model`、`X-OmniRoute-Provider`、`X-OmniRoute-Latency-Ms`、`X-OmniRoute-Cache-Hit` 以及 `X-OmniRoute-Fallback-Attempts`(僅大於 0 時),加上 `X-OmniRoute-Request-Id` 和 `X-OmniRoute-Version`。這些由聊天補全、`/v1/responses`、`/v1/messages` **以及媒體端點** — `/v1/embeddings`、`/v1/images/generations`、`/v1/audio/speech`、`/v1/audio/transcriptions`、`/v1/rerank`、`/v1/videos/generations`、`/v1/music/generations` 和 `/v1/moderations`(成本始終為 `0`)發出。媒體成本按模態計算(每張圖片、每秒、每字元、每個搜尋單位),有定價時適用,否則為 `0`(故障開放)。 + +> **快取命中的成本語義:** 當語義快取命中時(`X-OmniRoute-Cache-Hit: true`),不會進行上游呼叫,因此 `X-OmniRoute-Response-Cost` 為 `0.0000000000`(服務命中的**增量**成本)。原始/原本的成本單獨在 `X-OmniRoute-Cost-Saved` 中回報。計費消費者應加總 `X-OmniRoute-Response-Cost`(命中不計費);快取分析可匯總 `X-OmniRoute-Cost-Saved`。 + +### `x-omniroute-compression` + +按請求覆寫壓縮方案。最高優先順序 — 勝過路由組合覆寫、作用中設定檔、自動觸發和面板預設。值: + +| 值 | 效果 | +| --------------- | -------------------------------------------------------- | +| `off` | 此請求不進行壓縮。 | +| `default` | 面板衍生的 Default 設定檔(忽略作用中設定檔)。 | +| `engine:` | 啟用時的單一引擎,例如 `engine:rtk`。 | +| `` | 具名組合,先按名稱(不區分大小寫)比對,再按 ID 比對。 | + +備註: + +- 未知值會被忽略(請求絕不會被拒絕);解析會依正常運算子優先順序進行。 +- 若多個組合共用一個名稱,請傳入組合 **id** 以進行確定性比對。 +- 名稱為 `off` 或 `default` 的組合無法透過名稱選取(這些關鍵字會先被解讀);請使用其 ID 來參考此類組合。 +- 主壓縮開關為硬閘:當壓縮在全域層級停用時,此標頭無法啟用它。 + +套用的方案會透過回應標頭回顯: + +``` +X-OmniRoute-Compression: ; source= +``` + +其中 `` 為 `request-header`、`routing-override`、`active-profile`、`auto-trigger`、`default` 或 `off` 之一。 --- -## Embeddings +## 嵌入 ```bash POST /v1/embeddings -Authorization: Bearer your-api-key +Authorization: Bearer *** Content-Type: application/json { "model": "nebius/Qwen/Qwen3-Embedding-8B", - "input": "The food was delicious" + "input": "食物很美味" } ``` -Available providers: Nebius, OpenAI, Mistral, Together AI, Fireworks, NVIDIA, **OpenRouter**, **GitHub Models**. +可用提供者:Nebius、OpenAI、Mistral、Together AI、Fireworks、NVIDIA、**OpenRouter**、**GitHub Models**。 ```bash -# List all embedding models +# 列出所有嵌入模型 GET /v1/embeddings ``` --- -## Image Generation +## 圖片生成 ```bash POST /v1/images/generations -Authorization: Bearer your-api-key +Authorization: Bearer *** Content-Type: application/json { "model": "openai/gpt-image-2", - "prompt": "A beautiful sunset over mountains", + "prompt": "山巒上美麗的夕陽", "size": "1024x1024" } ``` -Available providers: OpenAI (GPT Image 2), xAI (Grok Image), Together AI (FLUX), Fireworks AI, Nebius (FLUX), Hyperbolic, NanoBanana, **OpenRouter**, SD WebUI (local), ComfyUI (local). +可用提供者:OpenAI(GPT Image 2)、xAI(Grok Image)、Together AI(FLUX)、Fireworks AI、Nebius(FLUX)、Hyperbolic、NanoBanana、**OpenRouter**、SD WebUI(本機)、ComfyUI(本機)。 ```bash -# List all image models +# 列出所有圖片模型 GET /v1/images/generations ``` --- -## List Models +## 列出模型 ```bash GET /v1/models -Authorization: Bearer your-api-key +Authorization: Bearer *** -→ Returns all chat, embedding, and image models + combos in OpenAI format +→ 以 OpenAI 格式回傳所有聊天、嵌入和圖片模型 + 組合 ``` +### 無思考模型變體 + +對於支援思考的 Claude 模型,`/v1/models` 也會提供一個**無思考**變體,其 ID 前綴為 `claude-3-omniroute-no-thinking/`: + +``` +claude-3-omniroute-no-thinking// +``` + +選取此 ID(例如在始終附加 `thinking` 區塊的 Claude Code 配置中)會解析回實際的 `/`,並抑制推理 — 在 `/v1/messages` 路徑上設定 `thinking:{type:"disabled"}`,或在 `/v1/chat/completions` 路徑上刪除 `reasoning`/`reasoning_effort` 欄位。僅針對支援 thinking **且**遵循 `disabled` 的 Claude 系列模型列出此變體(因此例如僅適配且拒絕 `disabled` 的模型會被排除)。運算子可以透過 `ModelSpec.noThinkingAlias` 強制為每個模型啟用或停用此變體。 + --- -## Compatibility Endpoints +## 提供者外掛清單 -| Method | Path | Format | -| ------ | --------------------------- | ---------------------- | -| POST | `/v1/chat/completions` | OpenAI | -| POST | `/v1/messages` | Anthropic | -| POST | `/v1/responses` | OpenAI Responses | -| POST | `/v1/embeddings` | OpenAI | -| POST | `/v1/images/generations` | OpenAI | -| GET | `/v1/models` | OpenAI | -| POST | `/v1/messages/count_tokens` | Anthropic | -| GET | `/v1beta/models` | Gemini | -| POST | `/v1beta/models/{...path}` | Gemini generateContent | -| POST | `/v1/api/chat` | Ollama | +```bash +GET /api/v1/provider-plugin-manifest +``` -### Dedicated Provider Routes +回傳 Bifrost、CLIProxyAPI 和未來 sidecar 路由器使用的 JSON 安全提供者外掛清單。回應由 TypeScript 提供者註冊表產生,且刻意排除 OAuth 用戶端密碼、執行時期環境解析、執行器函式、請求標頭和帳戶資料。 + +當 sidecar 在行程外執行且無法直接匯入 `open-sse/config/providerPluginManifestRegistry.ts` 時,請使用此端點。 + +--- + +## 相容性端點 + +| 方法 | 路徑 | 格式 | +| ------ | ---------------------------------------- | -------------------------------- | +| POST | `/v1/chat/completions` | OpenAI | +| POST | `/v1/messages` | Anthropic | +| POST | `/v1/responses` | OpenAI Responses | +| POST | `/v1/embeddings` | OpenAI | +| POST | `/v1/images/generations` | OpenAI Images | +| POST | `/v1/images/edits` | OpenAI Images(編輯/修補) | +| POST | `/v1/videos/generations` | OpenAI 樣式影片生成 | +| POST | `/v1/music/generations` | OpenAI 樣式音樂生成 | +| POST | `/v1/audio/transcriptions` | OpenAI Audio(STT) | +| POST | `/v1/audio/speech` | OpenAI TTS(回傳音訊主體) | +| POST | `/v1/rerank` | Cohere/Voyage 樣式重新排序 | +| POST | `/v1/moderations` | OpenAI 審核 | +| GET | `/v1/models` | OpenAI | +| POST | `/v1/messages/count_tokens` | Anthropic | +| GET | `/v1beta/models` | Gemini | +| POST | `/v1beta/models/{...path}` | Gemini generateContent | +| POST | `/v1/api/chat` | Ollama | +| GET | `/api/v1/vscode/{token}/` | OpenAI 目錄別名 | +| GET | `/api/v1/vscode/{token}/models` | OpenAI 模型別名 | +| POST | `/api/v1/vscode/{token}/chat/completions`| OpenAI 權杖化別名 | +| POST | `/api/v1/vscode/{token}/responses` | OpenAI Responses 權杖化別名 | +| POST | `/api/v1/vscode/{token}/api/chat` | Ollama 權杖化別名 | +| GET | `/api/v1/vscode/{token}/api/tags` | Ollama 標籤權杖化別名 | + +所有 POST 路由遵循相同格式:`Bearer your-api-key` + Zod 驗證的 JSON 主體(`v1RerankSchema`、`v1ModerationSchema`、`v1AudioSpeechSchema` 等,請參閱 `src/shared/validation/schemas.ts`)。結構描述失敗時會回傳 4xx。 + +對於無法附加 `Authorization: Bearer ***` 的客戶端,OmniRoute 也接受 URL 中的 API 金鑰,可透過查詢字串相容性(`?token=...`、`?apiKey=...`、`?api_key=...`、`?key=...`)或下方記錄的專用 `/api/v1/vscode/{token}/...` 端點。 + +```bash +# 重新排序 +POST /v1/rerank { "model": "cohere/rerank-3", "query": "...", "documents": ["..."] } + +# 審核 +POST /v1/moderations { "model": "omni-moderation-latest", "input": "..." } + +# TTS — 回傳 audio/mpeg(或要求格式)主體 +POST /v1/audio/speech { "model": "openai/tts-1", "input": "你好", "voice": "alloy" } + +# 圖片編輯(multipart) +POST /v1/images/edits -F image=@input.png -F prompt="..." -F mask=@mask.png + +# 影片/音樂生成(提供者前綴模型 ID) +POST /v1/videos/generations { "model": "runway/gen-3", "prompt": "..." } +POST /v1/music/generations { "model": "suno/v3.5", "prompt": "..." } +``` + +### 專用提供者路由 ```bash POST /v1/providers/{provider}/chat/completions @@ -136,21 +249,132 @@ POST /v1/providers/{provider}/embeddings POST /v1/providers/{provider}/images/generations ``` -The provider prefix is auto-added if missing. Mismatched models return `400`. +如果缺少提供者前綴,會自動加上。模型不符時回傳 `400`。 --- -## Semantic Cache +## 檔案 API + +OpenAI 相容的檔案端點,用於批次輸入/輸出和檔案用途上傳。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------ | --------------------------------------------------------------------------------------------- | +| POST | `/v1/files` | 上傳檔案(multipart:`file`、`purpose`、`expires_after[anchor]`、`expires_after[seconds]`)— 最大 512 MiB | +| GET | `/v1/files` | 列出已驗證 API 金鑰的檔案 | +| GET | `/v1/files/[id]` | 取得檔案的中繼資料 | +| DELETE | `/v1/files/[id]` | 刪除檔案 | +| GET | `/v1/files/[id]/content` | 串流回傳原始檔案內容 | + +**驗證:** Bearer API 金鑰 — 檔案透過 `getApiKeyRequestScope` 按 API 金鑰範圍隔離。 + +--- + +## 批次 API + +OpenAI 相容的批次處理。 + +| 方法 | 路徑 | 說明 | +| ------ | -------------------------- | ------------------------------------------------------------------------------------------------ | +| POST | `/v1/batches` | 建立批次 — 主體由 `v1BatchCreateSchema` 驗證(`input_file_id`、`endpoint`、`completion_window`) | +| GET | `/v1/batches` | 列出批次 | +| GET | `/v1/batches/[id]` | 取得批次狀態 + `request_counts` | +| DELETE | `/v1/batches/[id]` | 刪除已完成/失敗的批次 | +| POST | `/v1/batches/[id]/cancel` | 取消進行中的批次 | + +**驗證:** Bearer API 金鑰。批次按 API 金鑰範圍隔離。 + +--- + +## 搜尋 API + +Web/搜尋提供者抽象層(Tavily、Brave、Exa、Serper 等)。 + +| 方法 | 路徑 | 說明 | +| ------ | ----------------------- | ----------------------------------------------------------------- | +| GET | `/v1/search` | 列出已配置的搜尋提供者 + 功能 | +| POST | `/v1/search` | 執行搜尋查詢 — 主體由 `v1SearchSchema` 驗證,支援快取/合併 | +| GET | `/v1/search/analytics` | 各提供者的命中/延遲/快取統計 | + +**驗證:** Bearer API 金鑰(`extractApiKey` + `isValidApiKey`)。搜尋政策透過 `enforceApiKeyPolicy` 強制執行。 + +--- + +## WebSocket 串流 ```bash -# Get cache stats +GET /v1/ws?handshake=1 +``` + +驗證 WebSocket 升級握手並回傳線路協定範例訊息(`request`、`cancel`)。實際 WS 框架由捆綁的 WS 伺服器在 Next.js 路由表外處理。 + +**驗證:** 握手期間使用 Bearer API 金鑰。 + +### 透過 WebSocket 的 Responses API(僅限 codex) + +```bash +# 與 HTTP API 相同的主機:埠(預設 20128);升級連線: +wscat -c "ws://localhost:20128/v1/responses?api_key=" +# (或:-H "Authorization: Bearer ") + +# 第一幀必須是 response.create: +{ "type": "response.create", "model": "gpt-5.5", "input": [ { "role": "user", "content": "嗨" } ] } +``` + +Responses-API-over-WebSocket 代理**僅連線至 `codex`**(ChatGPT 後端)。它與 API/儀表板共用相同埠,路徑為 `/v1/responses`、`/responses` 和 `/api/v1/responses`。在第一個 `response.create` 幀時,它透過內部 `codex-responses-ws` 橋接器進行驗證 + 準備,選取 codex OAuth 連線,並透過 `wreq-js` 傳輸隧道至 `wss://chatgpt.com/backend-api/codex/responses`。**非 codex 模型會被拒絕**(`codex_ws_provider_required`)。對於配額共享路由,請使用 `model: "qtSd//codex/"`。實作於 `app/server-ws.mjs` + `scripts/dev/responses-ws-proxy.mjs` + `src/app/api/internal/codex-responses-ws/route.ts`。 + +**驗證:** 握手期間使用 Bearer API 金鑰。捆綁的 HTTP 伺服器(`server-ws.mjs`)必須為作用中進入點(預設情況下,當 `app/server-ws.mjs` 存在時即為)。 + +#### 模型 ID:使用 bare ChatGPT ID(不含 `codex/` 前綴) + +OpenAI **Codex CLI** 在 `supports_websockets = true` 時會於用戶端驗證模型名稱,並**拒絕帶提供者前綴的 ID**,例如 `codex/gpt-5.5`(`使用 ChatGPT 帳戶使用 Codex 時不支援 'codex/gpt-5.5' 模型`)。請傳送**裸** ID(例如 `gpt-5.5`)。OmniRoute 的橋接器僅限 codex,因此它會將裸 ID 重新解析為 codex 模型(`resolveCodexWsModelInfo`),然後再隧道至上游 — 即使裸 `gpt-5.5` 在其他情況下會透過 HTTP 路由至另一個提供者。 + +#### 配置 OpenAI Codex CLI + +將 Codex CLI 指向 OmniRoute,方法是在 `~/.codex/config.toml` 中新增一個支援 WebSocket 的自訂提供者(使用單獨的 `CODEX_HOME` 以避免觸碰現有配置): + +```toml +model = "gpt-5.5" # 裸 ID — 不是 "codex/gpt-5.5" +model_provider = "omniroute" + +[model_providers.omniroute] +name = "OmniRoute (WS)" +base_url = "http://localhost:20128/v1" # 無尾斜線;WS URL 由此衍生(生產環境使用 https/wss) +wire_api = "responses" # 自 2026 年 2 月以來唯一支援的值 +supports_websockets = true # 啟用 Responses-over-WS 傳輸 +env_key = "OMNIROUTE_API_KEY" # 存放 OmniRoute API 金鑰(Bearer) +``` + +```bash +export OMNIROUTE_API_KEY=sk-... # OmniRoute API 金鑰(若 REQUIRE_API_KEY=false 則任意金鑰) +codex exec "Responda apenas: PONG" +``` + +CLI 將 `base_url + /responses` 升級為 WebSocket,而 OmniRoute 將其隧道至選取的 codex OAuth 連線。已針對本機伺服器進行端到端驗證:ChatGPT 回傳 `codex.rate_limits` + `response.created` 並串流完成內容。 + +--- + +## 配額與問題回報 + +| 方法 | 路徑 | 說明 | +| ------ | -------------------- | ------------------------------------------------------------------- | +| GET | `/v1/quotas/check` | 在發行註冊金鑰前預先驗證 `provider` + `accountId` 的配額 | +| POST | `/v1/issues/report` | 將配額/金鑰發行失敗回報至 GitHub(需要 `GITHUB_ISSUES_REPO` + 權杖) | + +**驗證:** Bearer API 金鑰(`isAuthenticated`)。 + +--- + +## 語義快取 + +```bash +# 取得快取統計 GET /api/cache/stats -# Clear all caches +# 清除所有快取 DELETE /api/cache/stats ``` -Response example: +回應範例: ```json { @@ -169,171 +393,198 @@ Response example: --- -## Dashboard & Management +## 儀表板與管理 -### Authentication +### 身分驗證 -| Endpoint | Method | Description | -| ----------------------------- | ------- | --------------------- | -| `/api/auth/login` | POST | Login | -| `/api/auth/logout` | POST | Logout | -| `/api/settings/require-login` | GET/PUT | Toggle login required | +| 端點 | 方法 | 說明 | +| -------------------------------- | -------- | ----------------- | +| `/api/auth/login` | POST | 登入 | +| `/api/auth/logout` | POST | 登出 | +| `/api/settings/require-login` | GET/PUT | 切換需要登入 | -### Provider Management +### 提供者管理 -| Endpoint | Method | Description | -| ---------------------------- | --------------------- | ---------------------------------------------- | -| `/api/providers` | GET/POST | List / create providers | -| `/api/providers/[id]` | GET/PUT/DELETE | Manage a provider | -| `/api/providers/[id]/test` | POST | Test provider connection | -| `/api/providers/[id]/models` | GET | List provider models | -| `/api/providers/validate` | POST | Validate provider config | -| `/api/provider-nodes*` | Various | Provider node management | -| `/api/provider-models` | GET/POST/PATCH/DELETE | Custom models (add, update, hide/show, delete) | +| 端點 | 方法 | 說明 | +| --------------------------------- | ----------------------- | ----------------------------------- | +| `/api/providers` | GET/POST | 列出/建立提供者 | +| `/api/providers/[id]` | GET/PUT/DELETE | 管理提供者 | +| `/api/providers/[id]/test` | POST | 測試提供者連線 | +| `/api/providers/[id]/models` | GET | 列出提供者模型 | +| `/api/providers/validate` | POST | 驗證提供者配置 | +| `/api/providers/bulk` | POST | 為一個提供者大量新增 API 金鑰 | +| `/api/providers/import` | POST | 從解析的 CSV/JSON 檔案匯入異質提供者清單(#6836);每列部分失敗結果 | +| `/api/provider-nodes*` | 各種 | 提供者節點管理 | +| `/api/provider-models` | GET/POST/PATCH/DELETE | 自訂模型(新增、更新、隱藏/顯示、刪除) | -### OAuth Flows +### OAuth 流程 -| Endpoint | Method | Description | -| -------------------------------- | ------- | ----------------------- | -| `/api/oauth/[provider]/[action]` | Various | Provider-specific OAuth | +| 端點 | 方法 | 說明 | +| --------------------------------- | -------- | ------------------- | +| `/api/oauth/[provider]/[action]` | 各種 | 提供者特定 OAuth | -### Routing & Config +### 路由與配置 -| Endpoint | Method | Description | -| --------------------- | -------- | ----------------------------- | -| `/api/models/alias` | GET/POST | Model aliases | -| `/api/models/catalog` | GET | All models by provider + type | -| `/api/combos*` | Various | Combo management | -| `/api/keys*` | Various | API key management | -| `/api/pricing` | GET | Model pricing | +| 端點 | 方法 | 說明 | +| ---------------------- | --------- | ----------------------------- | +| `/api/models/alias` | GET/POST | 模型別名 | +| `/api/models/catalog` | GET | 按提供者 + 類型分類的所有模型 | +| `/api/combos*` | 各種 | 組合管理 | +| `/api/keys*` | 各種 | API 金鑰管理 | +| `/api/pricing` | GET | 模型定價 | -### Usage & Analytics +### 用量與分析 -| Endpoint | Method | Description | -| --------------------------- | ------ | -------------------- | -| `/api/usage/history` | GET | Usage history | -| `/api/usage/logs` | GET | Usage logs | -| `/api/usage/request-logs` | GET | Request-level logs | -| `/api/usage/[connectionId]` | GET | Per-connection usage | +| 端點 | 方法 | 說明 | +| -------------------------------- | ---------------- | ------------------------- | +| `/api/usage/history` | GET | 用量歷史 | +| `/api/usage/logs` | GET | 用量日誌 | +| `/api/usage/request-logs` | GET | 請求層級日誌 | +| `/api/usage/[connectionId]` | GET | 各連線用量 | +| `/api/usage/token-limits` | GET/POST/DELETE | 各 API 金鑰的權杖限制預算 | -### Settings +### 設定 -| Endpoint | Method | Description | -| ------------------------------- | ------------- | ---------------------- | -| `/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 | -| `/api/settings/thinking-budget` | GET/PUT | Reasoning token budget | -| `/api/settings/system-prompt` | GET/PUT | Global system prompt | +| 端點 | 方法 | 說明 | +| ---------------------------------------- | -------------- | ----------------------------------- | +| `/api/settings` | GET/PUT/PATCH | 一般設定 | +| `/api/settings/proxy` | GET/PUT | 網路代理設定 | +| `/api/settings/proxy/test` | POST | 測試代理連線 | +| `/api/settings/ip-filter` | GET/PUT | IP 允許清單/封鎖清單 | +| `/api/settings/thinking-budget` | GET/PUT | 推理權杖預算 | +| `/api/settings/system-prompt` | GET/PUT | 全域系統提示詞 | +| `/api/settings/compression` | GET/PUT | 全域壓縮設定 | +| `/api/settings/purge-request-history` | POST | 清除請求日誌列與本機呼叫記錄檔案 | -### Monitoring +### 上下文與壓縮 -| Endpoint | Method | Description | -| ------------------------ | ---------- | ---------------------------------------------------------------------------------------------------- | -| `/api/sessions` | GET | Active session tracking | -| `/api/rate-limits` | GET | Per-account rate limits | -| `/api/monitoring/health` | GET | Health check + provider summary (`catalogCount`, `configuredCount`, `activeCount`, `monitoredCount`) | -| `/api/cache/stats` | GET/DELETE | Cache stats / clear | +| 端點 | 方法 | 說明 | +| --------------------------------------- | --------------- | --------------------------------------------------------- | +| `/api/compression/preview` | POST | 預覽 off/lite/standard/aggressive/ultra/RTK/stacked 壓縮 | +| `/api/compression/language-packs` | GET | 列出可用的 Caveman 語言包 | +| `/api/compression/rules` | GET | 列出 Caveman 規則中繼資料 | +| `/api/context/caveman/config` | GET/PUT | Caveman 特定設定別名 | +| `/api/context/rtk/config` | GET/PUT | RTK 特定設定,包含自訂過濾器和原始輸出保留 | +| `/api/context/rtk/filters` | GET | RTK 過濾器目錄和自訂過濾器診斷 | +| `/api/context/rtk/test` | POST | 對文字負載執行 RTK 預覽/測試 | +| `/api/context/rtk/raw-output/[id]` | GET | 透過指標 ID 讀取保留的編輯後原始輸出 | +| `/api/context/combos` | GET/POST | 壓縮組合清單/建立 | +| `/api/context/combos/[id]` | GET/PUT/DELETE | 壓縮組合詳細/更新/刪除 | +| `/api/context/combos/[id]/assignments` | GET/PUT | 將壓縮組合指派給路由組合 | +| `/api/context/analytics` | GET | 壓縮分析別名 | -### Backup & Export/Import +### 監控 -| Endpoint | Method | Description | -| --------------------------- | ------ | --------------------------------------- | -| `/api/db-backups` | GET | List available backups | -| `/api/db-backups` | PUT | Create a manual backup | -| `/api/db-backups` | POST | Restore from a specific backup | -| `/api/db-backups/export` | GET | Download database as .sqlite file | -| `/api/db-backups/import` | POST | Upload .sqlite file to replace database | -| `/api/db-backups/exportAll` | GET | Download full backup as .tar.gz archive | +| 端點 | 方法 | 說明 | +| ------------------------- | ---------- | ----------------------------------------------------------------------------- | +| `/api/sessions` | GET | 作用中工作階段追蹤 | +| `/api/rate-limits` | GET | 各帳戶速率限制 | +| `/api/monitoring/health` | GET | 健康檢查 + 提供者摘要(`catalogCount`、`configuredCount`、`activeCount`、`monitoredCount`) | +| `/api/cache/stats` | GET/DELETE | 快取統計/清除 | -### Cloud Sync +### 備份與匯出/匯入 -| Endpoint | Method | Description | -| ---------------------- | ------- | --------------------- | -| `/api/sync/cloud` | Various | Cloud sync operations | -| `/api/sync/initialize` | POST | Initialize sync | -| `/api/cloud/*` | Various | Cloud management | +| 端點 | 方法 | 說明 | +| ---------------------------- | ------ | --------------------------------------- | +| `/api/db-backups` | GET | 列出可用備份 | +| `/api/db-backups` | PUT | 建立手動備份 | +| `/api/db-backups` | POST | 從特定備份還原 | +| `/api/db-backups/export` | GET | 以 .sqlite 檔案下載資料庫 | +| `/api/db-backups/import` | POST | 上傳 .sqlite 檔案以取代資料庫 | +| `/api/db-backups/exportAll` | GET | 以 .tar.gz 壓縮檔下載完整備份 | -### Tunnels +### 雲端同步 -| Endpoint | Method | Description | -| -------------------------- | ------ | ----------------------------------------------------------------------- | -| `/api/tunnels/cloudflared` | GET | Read Cloudflare Quick Tunnel install/runtime status for the dashboard | -| `/api/tunnels/cloudflared` | POST | Enable or disable the Cloudflare Quick Tunnel (`action=enable/disable`) | +| 端點 | 方法 | 說明 | +| ----------------------- | ------ | ----------------- | +| `/api/sync/cloud` | 各種 | 雲端同步操作 | +| `/api/sync/initialize` | POST | 初始化同步 | +| `/api/cloud/*` | 各種 | 雲端管理 | -### CLI Tools +### 隧道 -| Endpoint | Method | Description | -| ---------------------------------- | ------ | ------------------- | -| `/api/cli-tools/claude-settings` | GET | Claude CLI status | -| `/api/cli-tools/codex-settings` | GET | Codex CLI status | -| `/api/cli-tools/droid-settings` | GET | Droid CLI status | -| `/api/cli-tools/openclaw-settings` | GET | OpenClaw CLI status | -| `/api/cli-tools/runtime/[toolId]` | GET | Generic CLI runtime | +| 端點 | 方法 | 說明 | +| --------------------------- | ------ | ----------------------------------------------------------- | +| `/api/tunnels/cloudflared` | GET | 讀取儀表板的 Cloudflare Quick Tunnel 安裝/執行時期狀態 | +| `/api/tunnels/cloudflared` | POST | 啟用或停用 Cloudflare Quick Tunnel(`action=enable/disable`)| +| `/api/tunnels/ngrok` | GET | 讀取儀表板的 ngrok Tunnel 執行時期狀態 | +| `/api/tunnels/ngrok` | POST | 啟用或停用 ngrok Tunnel(`action=enable/disable`) | -CLI responses include: `installed`, `runnable`, `command`, `commandPath`, `runtimeMode`, `reason`. +### CLI 工具 -### ACP Agents +| 端點 | 方法 | 說明 | +| ----------------------------------- | ------ | --------------- | +| `/api/cli-tools/claude-settings` | GET | Claude CLI 狀態 | +| `/api/cli-tools/codex-settings` | GET | Codex CLI 狀態 | +| `/api/cli-tools/droid-settings` | GET | Droid CLI 狀態 | +| `/api/cli-tools/openclaw-settings` | GET | OpenClaw CLI 狀態 | +| `/api/cli-tools/runtime/[toolId]` | GET | 通用 CLI 執行時期 | -| Endpoint | Method | Description | -| ----------------- | ------ | -------------------------------------------------------- | -| `/api/acp/agents` | GET | List all detected agents (built-in + custom) with status | -| `/api/acp/agents` | POST | Add custom agent or refresh detection cache | -| `/api/acp/agents` | DELETE | Remove a custom agent by `id` query param | +CLI 回應包含:`installed`、`runnable`、`command`、`commandPath`、`runtimeMode`、`reason`。 -GET response includes `agents[]` (id, name, binary, version, installed, protocol, isCustom) and `summary` (total, installed, notFound, builtIn, custom). +### ACP 代理程式 -### Resilience & Rate Limits +| 端點 | 方法 | 說明 | +| ---------------- | ------ | ------------------------------------------------- | +| `/api/acp/agents`| GET | 列出所有檢測到的代理程式(內建 + 自訂)及其狀態 | +| `/api/acp/agents`| POST | 新增自訂代理程式或重新整理偵測快取 | +| `/api/acp/agents`| DELETE | 透過 `id` 查詢參數移除自訂代理程式 | -| Endpoint | Method | Description | -| ----------------------- | --------- | ---------------------------------------------------------------------------------- | -| `/api/resilience` | GET/PATCH | Get/update request queue, connection cooldown, provider breaker, and wait settings | -| `/api/resilience/reset` | POST | Reset provider circuit breakers | -| `/api/rate-limits` | GET | Per-account rate limit status | -| `/api/rate-limit` | GET | Global rate limit configuration | +GET 回應包含 `agents[]`(id、name、binary、version、installed、protocol、isCustom)和 `summary`(total、installed、notFound、builtIn、custom)。 -### Evals +### 彈性與速率限制 -| Endpoint | Method | Description | -| ------------ | -------- | --------------------------------- | -| `/api/evals` | GET/POST | List eval suites / run evaluation | +| 端點 | 方法 | 說明 | +| ---------------------------------- | ---------- | ----------------------------------------------------------------------------- | +| `/api/resilience` | GET/PATCH | 取得/更新請求佇列、連線冷卻、提供者斷路器和等待設定 | +| `/api/resilience/reset` | POST | 重設提供者斷路器 | +| `/api/resilience/model-cooldowns` | GET | 列出作用中的各(提供者、連線、模型)鎖定,按剩餘時間排序 | +| `/api/resilience/model-cooldowns` | DELETE | 清除模型鎖定 — 主體 `{provider, model}` 或 `{all: true}` 以清除全部 | +| `/api/rate-limits` | GET | 各帳戶速率限制狀態 | +| `/api/rate-limit` | GET | 全域速率限制配置 | -### Policies +> 所有四個 `/api/resilience/*` 路由都需要**管理驗證**(`requireManagementAuth`)。請參閱[彈性(進階)](#彈性進階)以取得提供者斷路器 vs 連線冷卻 vs 模型鎖定的完整說明。 -| Endpoint | Method | Description | -| --------------- | --------------- | ----------------------- | -| `/api/policies` | GET/POST/DELETE | Manage routing policies | +### 評測 -### Compliance +| 端點 | 方法 | 說明 | +| ------------- | --------- | ------------------------- | +| `/api/evals` | GET/POST | 列出評測套件 / 執行評測 | -| Endpoint | Method | Description | -| --------------------------- | ------ | ----------------------------- | -| `/api/compliance/audit-log` | GET | Compliance audit log (last N) | +### 政策 -### v1beta (Gemini-Compatible) +| 端點 | 方法 | 說明 | +| ---------------- | ---------------- | ----------------- | +| `/api/policies` | GET/POST/DELETE | 管理路由政策 | -| Endpoint | Method | Description | -| -------------------------- | ------ | --------------------------------- | -| `/v1beta/models` | GET | List models in Gemini format | -| `/v1beta/models/{...path}` | POST | Gemini `generateContent` endpoint | +### 合規 -These endpoints mirror Gemini's API format for clients that expect native Gemini SDK compatibility. +| 端點 | 方法 | 說明 | +| ---------------------------- | ---- | ----------------------- | +| `/api/compliance/audit-log` | GET | 合規稽核日誌(最近 N 筆)| -### Internal / System APIs +### v1beta(Gemini 相容) -| Endpoint | Method | Description | -| ------------------------ | ------ | ---------------------------------------------------- | -| `/api/init` | GET | Application initialization check (used on first run) | -| `/api/tags` | GET | Ollama-compatible model tags (for Ollama clients) | -| `/api/restart` | POST | Trigger graceful server restart | -| `/api/shutdown` | POST | Trigger graceful server shutdown | -| `/api/system/env/repair` | POST | Repair OAuth provider environment variables | -| `/api/system-info` | GET | Generate system diagnostics report | +| 端點 | 方法 | 說明 | +| ------------------------- | ------ | ------------------------------- | +| `/v1beta/models` | GET | 以 Gemini 格式列出模型 | +| `/v1beta/models/{...path}`| POST | Gemini `generateContent` 端點 | -> **Note:** These endpoints are used internally by the system or for Ollama client compatibility. They are not typically called by end users. +這些端點鏡像 Gemini 的 API 格式,供預期原生 Gemini SDK 相容性的用戶端使用。 -### OAuth Environment Repair _(v3.6.1+)_ +### 內部 / 系統 API + +| 端點 | 方法 | 說明 | +| ----------------------- | ------ | ------------------------------------------------- | +| `/api/init` | GET | 應用程式初始化檢查(首次執行時使用) | +| `/api/tags` | GET | Ollama 相容模型標籤(供 Ollama 用戶端使用) | +| `/api/restart` | POST | 觸發優雅伺服器重新啟動 | +| `/api/shutdown` | POST | 觸發優雅伺服器關閉 | +| `/api/system/env/repair`| POST | 修復 OAuth 提供者環境變數 | + +> **注意:** 這些端點供系統內部使用或為 Ollama 用戶端相容性而提供。通常不應由一般使用者呼叫。 + +### OAuth 環境修復 _(v3.6.1+)_ ```bash POST /api/system/env/repair @@ -344,7 +595,7 @@ Content-Type: application/json } ``` -Repairs missing or corrupted OAuth environment variables for a specific provider. Returns: +修復特定提供者缺失或損壞的 OAuth 環境變數。回傳: ```json { @@ -356,66 +607,99 @@ Repairs missing or corrupted OAuth environment variables for a specific provider --- -## Audio Transcription +## 音訊轉寫 ```bash POST /v1/audio/transcriptions -Authorization: Bearer your-api-key +Authorization: Bearer *** Content-Type: multipart/form-data ``` -Transcribe audio files using Deepgram or AssemblyAI. +使用 Deepgram 或 AssemblyAI 轉寫音訊檔案。 -**Request:** +**請求:** ```bash curl -X POST http://localhost:20128/v1/audio/transcriptions \ - -H "Authorization: Bearer your-api-key" \ + -H "Authorization: Bearer ***" \ -F "file=@recording.mp3" \ -F "model=deepgram/nova-3" ``` -**Response:** +**回應:** ```json { - "text": "Hello, this is the transcribed audio content.", + "text": "你好,這是轉寫後的音訊內容。", "task": "transcribe", - "language": "en", + "language": "zh", "duration": 12.5 } ``` -**Supported providers:** `deepgram/nova-3`, `assemblyai/best`. +**支援的提供者:** `deepgram/nova-3`、`assemblyai/best`。 -**Supported formats:** `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm`. +**支援的格式:** `mp3`、`wav`、`m4a`、`flac`、`ogg`、`webm`。 --- -## Ollama Compatibility +## Ollama 相容性 -For clients that use Ollama's API format: +對於使用 Ollama API 格式的用戶端: ```bash -# Chat endpoint (Ollama format) +# 聊天端點(Ollama 格式) POST /v1/api/chat -# Model listing (Ollama format) +# 模型列表(Ollama 格式) GET /api/tags ``` -Requests are automatically translated between Ollama and internal formats. +請求會自動在 Ollama 與內部格式之間轉換。 + +## 權杖化 VS Code / 無標頭別名 + +當整合無法注入 `Authorization` 標頭且需要將 API 金鑰嵌入基本 URL 時,請使用這些別名。 + +```bash +# OpenAI 樣式目錄別名 +GET /api/v1/vscode/{token}/ +GET /api/v1/vscode/{token}/models + +# OpenAI 樣式聊天別名 +POST /api/v1/vscode/{token}/chat/completions +POST /api/v1/vscode/{token}/responses + +# Ollama 樣式別名 +POST /api/v1/vscode/{token}/api/chat +GET /api/v1/vscode/{token}/api/tags +``` + +範例: + +```bash +curl https://your-host.example/api/v1/vscode/YOUR_API_KEY/models +curl -X POST https://your-host.example/api/v1/vscode/YOUR_API_KEY/chat/completions \ + -H "Content-Type: application/json" \ + -d '{"model":"auto","messages":[{"role":"user","content":"你好"}]}' +``` + +備註: + +- 權杖化別名重用與 `/v1/*` 和 `/api/tags` 相同的處理常式;回應格式保持不變。 +- 當用戶端支援自訂標頭時,優先使用 `Authorization: Bearer ***`。 +- URL 中的權杖可能會出現於反向代理日誌、瀏覽器歷史記錄和 OmniRoute 外的遙測資料中。請將其視為相容性選項,而非預設驗證模式。 --- -## Telemetry +## 遙測 ```bash -# Get latency telemetry summary (p50/p95/p99 per provider) +# 取得延遲遙測摘要(各提供者的 p50/p95/p99) GET /api/telemetry/summary ``` -**Response:** +**回應:** ```json { @@ -428,42 +712,642 @@ GET /api/telemetry/summary --- -## Budget +## 預算 ```bash -# Get budget status for all API keys +# 取得所有 API 金鑰的預算狀態 GET /api/usage/budget -# Set or update a budget +# 設定或更新預算 POST /api/usage/budget Content-Type: application/json { - "keyId": "key-123", - "limit": 50.00, - "period": "monthly" + "apiKeyId": "key-123", + "dailyLimitUsd": 5.00, + "weeklyLimitUsd": 30.00, + "monthlyLimitUsd": 100.00, + "warningThreshold": 0.8, + "resetInterval": "monthly" } ``` -## Request Processing +> **結構描述備註**(`setBudgetSchema`):`apiKeyId` 為必填;`dailyLimitUsd`、`weeklyLimitUsd` 或 `monthlyLimitUsd` 至少一個必須大於零。選用欄位:`warningThreshold`(0–1)、`resetInterval`(`daily` | `weekly` | `monthly`)、`resetTime`(`HH:MM`)。舊版 `{keyId, limit, period}` 格式會回傳 `400 Bad Request`。 -1. Client sends request to `/v1/*` -2. Route handler calls `handleChat`, `handleEmbedding`, `handleAudioTranscription`, or `handleImageGeneration` -3. Model is resolved (direct provider/model or alias/combo) -4. Credentials selected from local DB with account availability filtering -5. For chat: `handleChatCore` — format detection, translation, cache check, idempotency check -6. Provider executor sends upstream request -7. Response translated back to client format (chat) or returned as-is (embeddings/images/audio) -8. Usage/logging recorded -9. Fallback applies on errors according to combo rules +## 權杖限制 -Full architecture reference: [`ARCHITECTURE.md`](ARCHITECTURE.md) +各 API 金鑰的**權杖**預算(與上述以美金為基礎的預算不同)。在請求路徑上即時強制執行:當金鑰的當前窗口用量達到限制時,請求會被拒絕,回傳 `429 Too Many Requests`。限制可限定於特定 `model`、`provider`,或套用至金鑰的 `global` 範圍;當多個限制符合請求時,最嚴格的限制會勝出。 + +```bash +# 列出金鑰的權杖限制(包含即時窗口用量) +GET /api/usage/token-limits?apiKeyId=key-123 + +# 建立或更新權杖限制 +POST /api/usage/token-limits +Content-Type: application/json + +{ + "apiKeyId": "key-123", + "scopeType": "model", + "scopeValue": "openai/gpt-4o", + "tokenLimit": 1000000, + "resetInterval": "monthly", + "enabled": true +} + +# 依 ID 刪除權杖限制 +DELETE /api/usage/token-limits?id=tl-abc +``` + +> **結構描述備註**(`setTokenLimitSchema`):`apiKeyId` 和 `scopeType`(`model` | `provider` | `global`)為必填。`scopeValue` 為必填,除非 `scopeType` 為 `global`(例如 `model` 範圍傳入模型 ID,`provider` 範圍傳入提供者 ID)。`tokenLimit` 必須為正整數(可從字串強制轉換)。選用:`id`(省略為建立,提供則為更新)、`resetInterval`(`daily` | `weekly` | `monthly`,預設 `monthly`)、`resetTime`(`HH:MM`)、`enabled`(預設 `true`)。`GET` 回應會為每個限制補充 `tokensUsed`、`remaining`、`windowStart`、`periodStartAt` 和 `nextResetAt`。此為管理層級端點(驗證由 authz 管線集中強制執行)。 + +## 請求處理 + +1. 用戶端傳送請求至 `/v1/*` +2. 路由處理常式呼叫 `handleChat`、`handleEmbedding`、`handleAudioTranscription` 或 `handleImageGeneration` +3. 解析模型(直接提供者/模型或別名/組合) +4. 從本機資料庫選取憑證,並依帳戶可用性過濾 +5. 聊天:`handleChatCore` 檢查語義/簽章快取並解析組合壓縮設定 +6. 啟用時,在提供者轉換前執行主動壓縮(`lite`、Caveman、RTK 或 stacked) +7. 提供者執行器傳送上游請求 +8. 將回應轉換回用戶端格式(聊天)或原樣回傳(嵌入/圖片/音訊) +9. 記錄用量、壓縮分析和請求日誌 +10. 根據組合規則,在發生錯誤時套用後備方案 + +完整架構參考:[`ARCHITECTURE.md`](../architecture/ARCHITECTURE.md) --- -## Authentication +## 組合管理 -- Dashboard routes (`/dashboard/*`) use `auth_token` cookie -- Login uses saved password hash; fallback to `INITIAL_PASSWORD` -- `requireLogin` toggleable via `/api/settings/require-login` -- `/v1/*` routes optionally require Bearer API key when `REQUIRE_API_KEY=true` +更高層級的路由組合(已在 `/api/combos*` 下摘要)也可以從模型 ID 模式進行 1:1 映射,允許將 OpenAI 樣式模型 ID 透明重新導向至組合。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------------------- | ----------------------------------------------------------------- | +| GET | `/api/model-combo-mappings` | 列出所有模型→組合映射 | +| POST | `/api/model-combo-mappings` | 建立映射 — 主體:`{pattern, comboId, priority?, enabled?, description?}` | +| GET | `/api/model-combo-mappings/[id]` | 取得單一映射 | +| PUT | `/api/model-combo-mappings/[id]` | 更新現有映射的欄位 | +| DELETE | `/api/model-combo-mappings/[id]` | 移除映射 | + +**驗證:** 管理工作階段/API 金鑰(`requireManagementAuth`)。 + +--- + +## Webhook + +OmniRoute 事件(請求完成、配額耗盡、金鑰輪換等)的外送 Webhook 訂閱。 + +| 方法 | 路徑 | 說明 | +| ------ | --------------------------- | ----------------------------------------------------------------- | +| GET | `/api/webhooks` | 列出 webhook(密碼以 `...` 遮罩) | +| POST | `/api/webhooks` | 建立 webhook — 主體:`{url, events?: ["*"], secret?, description?}` | +| GET | `/api/webhooks/[id]` | 取得 webhook | +| PUT | `/api/webhooks/[id]` | 更新 url/events/secret/description | +| DELETE | `/api/webhooks/[id]` | 移除 webhook | +| POST | `/api/webhooks/[id]/test` | 傳送測試負載至 webhook URL 並回傳傳遞狀態 | + +**驗證:** 管理工作階段/API 金鑰(`requireManagementAuth`)。 + +--- + +## 註冊金鑰(自動管理) + +由自動金鑰管理子系統使用,針對後端提供者/帳戶發行和輪換 API 金鑰,並設有每日/每小時配額。 + +| 方法 | 路徑 | 說明 | +| ------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| GET | `/api/v1/registered-keys` | 列出註冊金鑰(僅顯示遮罩前綴) | +| POST | `/api/v1/registered-keys` | 發行新的註冊金鑰 — 主體:`{name, provider?, accountId?, idempotencyKey?, expiresAt?, dailyBudget?, hourlyBudget?}`。回傳原始金鑰**一次**。配額不足時回傳 `429`。 | +| GET | `/api/v1/registered-keys/[id]` | 取得註冊金鑰的中繼資料(不含原始材料) | +| DELETE | `/api/v1/registered-keys/[id]` | 撤銷註冊金鑰 | +| POST | `/api/v1/registered-keys/[id]/revoke` | 明確撤銷端點(與 DELETE 效果相同) | + +**驗證:** Bearer API 金鑰(`isAuthenticated`)。另請參閱 `/v1/quotas/check` 和 `/v1/issues/report`。 + +--- + +## 代理程式協定 + +雲端代理程式任務(Claude Code、Codex Cloud、OpenHands 等),代表 OmniRoute 使用者遠端執行。 + +| 方法 | 路徑 | 說明 | +| ------ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| GET | `/api/v1/agents/tasks` | 列出任務 — 選用 `?provider=`、`?status=`、`?limit=`(1–500,預設 50) | +| POST | `/api/v1/agents/tasks` | 建立任務 — 主體由 `CreateCloudAgentTaskSchema` 驗證(`providerId`、`prompt`、`source`、`options?`)。回傳 `201` 及任務封裝 | +| DELETE | `/api/v1/agents/tasks?id=...` | 刪除任務 | +| GET | `/api/v1/agents/tasks/[id]` | 讀取任務 — 當設定 `external_id` 時,會同步從上游雲端代理程式重新整理狀態 | +| POST | `/api/v1/agents/tasks/[id]` | 區分動作:`{action: "approve"}`、`{action: "message", message}` 或 `{action: "cancel"}` | +| DELETE | `/api/v1/agents/tasks/[id]` | 依 ID 刪除特定任務 | + +> **驗證:** 每個方法都需要管理驗證(`requireCloudAgentManagementAuth`)。在 v3.8.0 之前,這些端點未經驗證 — 請參閱提交 `588a0333` 以了解重大變更。 + +```bash +# 建立 Claude Code 雲端任務 +curl -X POST http://localhost:20128/api/v1/agents/tasks \ + -H "Authorization: Bearer your-m...-key" \ + -H "Content-Type: application/json" \ + -d '{"providerId":"claude-code-cloud","prompt":"修復失敗的測試","source":{"repo":"...","branch":"..."}}' +``` + +--- + +## 管理代理 + +可指派給提供者、帳戶或全域的外送 HTTP(S)/SOCKS 代理。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| GET | `/api/v1/management/proxies` | 列出代理(使用 `?id=` 回傳單一代理;使用 `?id=&where_used=1` 回傳指派圖) | +| POST | `/api/v1/management/proxies` | 建立代理 — 主體由 `createProxyRegistrySchema` 驗證 | +| PATCH | `/api/v1/management/proxies` | 更新代理 — 主體由 `updateProxyRegistrySchema` 驗證(需要 `id`) | +| DELETE | `/api/v1/management/proxies?id=...&force=1` | 刪除代理(使用 `force=1` 來分離指派) | +| GET | `/api/v1/management/proxies/assignments` | 列出指派 — 可依 `proxy_id`、`scope`、`scope_id` 過濾;傳入 `resolve_connection_id=` 以解析連線的作用中代理 | +| PUT | `/api/v1/management/proxies/assignments` | 指派 — 主體由 `proxyAssignmentSchema` 驗證(`{scope, scopeId?, proxyId?}`)。清除調度器快取 | +| PUT | `/api/v1/management/proxies/bulk-assign` | 大量指派 — 主體由 `bulkProxyAssignmentSchema` 驗證(`{scope, scopeIds[], proxyId?}`) | +| GET | `/api/v1/management/proxies/health?hours=24` | 匯總窗口內的代理健康狀態(成功/失敗計數、延遲) | + +**驗證:** 每個路由都需要管理工作階段/API 金鑰(`requireManagementAuth`)。 + +> 任務說明中的 `POST /api/v1/management/proxies/[id]/assignments` 和 `POST /api/v1/management/proxies/[id]/health` 由上述平面 `/assignments` 和 `/health` 路由提供服務 — 程式碼庫中不存在每個 ID 的子路由。 + +--- + +## 彈性(進階) + +OmniRoute 公開三種獨立的暫時性故障機制;下面的管理端點讓運算子可以讀取和覆寫它們: + +| 範圍 | 狀態儲存 | 讀取 | 重設/清除 | +| ------------------- | ----------------------------------------- | ----------------------------------------- | --------------------------------------------- | +| 提供者斷路器 | `domain_circuit_breakers` + 記憶體內 | `/api/monitoring/health` | `POST /api/resilience/reset` | +| 連線冷卻 | 提供者連線上的 `rateLimitedUntil` | `/api/rate-limits`、`/api/providers/[id]` | (惰性重新啟用;透過提供者 PUT 清除) | +| 模型鎖定 | 記憶體內模型可用性註冊表 | `GET /api/resilience/model-cooldowns` | `DELETE /api/resilience/model-cooldowns` | + +`PATCH /api/resilience` 接受 `providerBreaker.oauth` 和 `providerBreaker.apikey` 下的提供者斷路器覆寫。每個設定檔支援 `degradationThreshold`、`failureThreshold` 和 `resetTimeoutMs`;相同的欄位也顯示在儀表板 → 設定 → 彈性中。 + +```bash +# 清除單一模型鎖定 +curl -X DELETE http://localhost:20128/api/resilience/model-cooldowns \ + -H "Cookie: auth_token=..." \ + -H "Content-Type: application/json" \ + -d '{"provider":"openai","model":"gpt-4o-mini"}' + +# 清除所有鎖定 +curl -X DELETE http://localhost:20128/api/resilience/model-cooldowns \ + -H "Cookie: auth_token=..." \ + -d '{"all":true}' +``` + +完整概念參考和斷路器預設值:請參閱 [`CLAUDE.md`](../../CLAUDE.md) →「彈性執行時期狀態」。 + +--- + +## 技能 + +用於以自訂可執行處理常式擴充 OmniRoute 的技能框架,以及市集整合。 + +| 方法 | 路徑 | 說明 | +| ------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| GET | `/api/skills` | 列出已安裝技能 — 可依 `?q=`、`?mode=on\|off\|auto`、`?source=skillsmp\|skillssh\|local` 過濾,支援分頁 | +| GET | `/api/skills/[id]` | 取得單一技能 | +| PUT | `/api/skills/[id]` | 更新技能(名稱、說明、模式、結構描述、處理常式、標籤) | +| DELETE | `/api/skills/[id]` | 解除安裝技能 | +| POST | `/api/skills/install` | 從原始資訊清單安裝技能 — 主體:`{name, version, description, schema:{input, output}, handlerCode, apiKeyId?}` | +| GET | `/api/skills/executions` | 列出最近的技能執行記錄(稽核軌跡,含輸入/輸出/持續時間) | +| GET | `/api/skills/marketplace?q=...` | 從 SkillsMP 市集搜尋/熱門清單(需要 `skillsmpApiKey` 設定) | +| POST | `/api/skills/marketplace/install` | 從 SkillsMP 依 ID 安裝技能 | +| GET | `/api/skills/skillssh?q=&limit=` | 搜尋 skills.sh 註冊表 | +| POST | `/api/skills/skillssh/install` | 從 skills.sh 依 ID 安裝技能 | + +**驗證:** 管理工作階段/API 金鑰。市集搜尋路由接受管理驗證或 Bearer API 金鑰(`isAuthenticated`)。 + +--- + +## 記憶 + +持久性的對話/事實記憶儲存,按 API 金鑰/工作階段範圍隔離。 + +| 方法 | 路徑 | 說明 | +| ------ | --------------------- | ----------------------------------------------------------------------------------------------------- | +| GET | `/api/memory` | 列出記憶 — `?apiKeyId=`、`?type=`、`?sessionId=`、`?q=`,支援 `offset/limit` 或 `page/limit` 分頁 | +| POST | `/api/memory` | 建立記憶 — 主體由 Zod 驗證:`{content, key, type?, sessionId?, apiKeyId?, metadata?, expiresAt?}` | +| GET | `/api/memory/[id]` | 取得單一記憶 | +| DELETE | `/api/memory/[id]` | 刪除記憶 | +| GET | `/api/memory/health` | 記憶子系統健康狀態(資料庫連線、嵌入後端、向量索引狀態) | + +**驗證:** 管理工作階段/API 金鑰(`requireManagementAuth`)。`type` 列舉:`FACTUAL`、`EPISODIC`、`SEMANTIC`、`PROCEDURAL`(請參閱 `src/lib/memory/types.ts` 中的 `MemoryType`)。 + +--- + +## MCP 伺服器 + +OmniRoute 內嵌了一個模型上下文協定伺服器,具有 3 種傳輸方式(stdio、SSE、streamable-http)和範圍限定的工具。下方的儀表板端點可讀取狀態/稽核資料並代理 HTTP 傳輸。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------ | ----------------------------------------------------------------------------------------------- | -------------------- | +| GET | `/api/mcp/status` | 心跳、傳輸、線上狀態、最後呼叫、熱門工具、24 小時成功率 | +| GET | `/api/mcp/tools` | MCP 工具清單,包含 `name`、`description`、`scopes`、`phase`、`auditLevel`、`sourceEndpoints` | +| GET | `/api/mcp/sse` | 開啟 SSE 傳輸的 SSE 串流(若 MCP 停用或傳輸不符則回傳 `503`) | +| POST | `/api/mcp/sse` | 在 SSE 傳輸上傳送 JSON-RPC 框架 | +| GET | `/api/mcp/stream` | 開啟 Streamable HTTP 傳輸的 SSE 端(伺服器發起訊息) | +| POST | `/api/mcp/stream` | 在 Streamable HTTP 傳輸上傳送 JSON-RPC 框架 | +| DELETE | `/api/mcp/stream` | 結束 Streamable HTTP 工作階段 | +| GET | `/api/mcp/audit` | 查詢稽核日誌 — `?limit=`、`?offset=`、`?tool=`、`?success=true | false`、`?apiKeyId=` | +| GET | `/api/mcp/audit/stats` | 匯總稽核統計(總數、成功率、平均持續時間、熱門工具) | + +**驗證:** `sse`/`stream` 傳輸遵循 MCP 特定的驗證表面(具有 `mcp` 範圍的 Bearer API 金鑰);`status`/`tools`/`audit*` 路由可從儀表板讀取(除了到達儀表板主機外,不需要額外驗證)。 + +> 兩種 HTTP 傳輸均由 `settings.mcpEnabled` 和 `settings.mcpTransport` 控制 — 傳輸不符回傳 `400`,MCP 停用狀態回傳 `503`。 + +--- + +## A2A 伺服器 + +OmniRoute 公開一個 A2A(代理程式對代理程式)JSON-RPC 2.0 端點,以及用於檢視/儀表板使用的 REST 包裝器。 + +### JSON-RPC + +```bash +POST /a2a +Authorization: Bearer *** # 選用,除非設定了 OMNIROUTE_API_KEY +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "message/send", + "params": { + "skill": "smart-routing", + "messages": [{"role": "user", "content": "路由此編碼任務"}] + } +} +``` + +支援的方法(全部由 `settings.a2aEnabled` 控制): + +| 方法 | 說明 | +| ---------------- | --------------------------------------------------------- | +| `message/send` | 同步技能執行;回傳 `{task, artifacts, metadata}` | +| `message/stream` | 相同技能集的串流 SSE 執行 | +| `tasks/get` | 依 `taskId` 取得任務 | +| `tasks/cancel` | 依 `taskId` 取消任務 | + +內建技能:`smart-routing`、`quota-management`、`provider-discovery`、`cost-analysis`、`health-report`。 + +### Agent Card + +```bash +GET /.well-known/agent.json +``` + +回傳公開的 A2A agent card(名稱、說明、功能、技能目錄、驗證方案)— 公開快取 1 小時。不需要驗證。 + +### REST 輔助工具 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------------ | --------------------------------------------------------------------------------------------- | +| GET | `/api/a2a/status` | A2A 啟用狀態 + 任務統計 + 快取的 agent card 摘要 | +| GET | `/api/a2a/tasks` | 列出任務 — `?state=submitted\|working\|completed\|failed\|cancelled`、`?skill=`、`?limit=`(≤200)、`?offset=` | +| POST | `/api/a2a/tasks` | (未實作為 REST 輔助工具 — 請透過 JSON-RPC `message/send` 建立) | +| GET | `/api/a2a/tasks/[id]` | 取得單一任務 | +| POST | `/api/a2a/tasks/[id]/cancel` | 取消任務 | + +**驗證:** REST 輔助工具在無管理驗證下執行(可從儀表板讀取);JSON-RPC `/a2a` 路由在配置時使用 Bearer `OMNIROUTE_API_KEY`。 + +--- + +## 雲端、評測與評估 + +| 方法 | 路徑 | 說明 | +| ------ | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------------------------- | ----------------------------------- | +| POST | `/api/cloud/auth` | 驗證 Bearer 金鑰並為雲端同步用戶端回傳遮罩的提供者連線 + 模型別名 | +| POST | `/api/cloud/credentials/update` | 更新雲端同步提供者的加密憑證 | +| POST | `/api/cloud/model/resolve` | 使用本機路由表將邏輯模型 ID 解析為具體的提供者/模型 | +| GET | `/api/cloud/models/alias` | 列出向雲端同步公開的模型別名 | +| GET | `/api/assess` | 讀取最新評估分類(各提供者/模型) | +| POST | `/api/assess` | 執行評估 — 主體:`{scope: {type:"all"} | {type:"provider", providerId} | {type:"model", modelId}, trigger?}` | +| GET | `/api/evals` | 列出內建評測套件 + 最近執行 | +| POST | `/api/evals` | 觸發評測執行 | +| POST | `/api/evals/suites` | 建立自訂評測套件 — 主體由 `evalSuiteSaveSchema` 驗證 | +| GET | `/api/evals/suites/[id]` | 取得自訂評測套件 | + +**驗證:** `/api/cloud/auth` 直接驗證 Bearer 金鑰;其他 `/api/cloud/*`、`/api/evals/*` 和 `/api/assess` 路由需要管理工作階段/API 金鑰。`/api/assess` POST 使用 `validateBody` 搭配區分聯集範圍結構描述。 + +--- + +## ACP(代理程式用戶端協定)管理 + +ACP 代理程式以子程序執行。這些端點管理 ACP 代理程式偵測和自訂代理程式註冊。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| GET | `/api/acp/agents` | 列出所有已知的 CLI 代理程式(內建 + 自訂),含安裝狀態、版本、二進位檔 | +| POST | `/api/acp/agents` | 註冊自訂 ACP 代理程式或重新整理快取 — 主體:`{id, name, binary, versionCommand, providerAlias, spawnArgs, protocol}` 或 `{action: "refresh"}` | +| DELETE | `/api/acp/agents` | 移除自訂 ACP 代理程式 — 查詢參數:`?id=` | + +**回應範例**(`GET /api/acp/agents`): + +```json +{ + "agents": [ + { + "id": "claude", + "name": "Claude Code CLI", + "binary": "claude", + "version": "1.0.45", + "installed": true, + "protocol": "stdio", + "providerAlias": "claude", + "isCustom": false + }, + { + "id": "my-custom-cli", + "name": "我的自訂 CLI", + "installed": false, + "protocol": "stdio", + "providerAlias": "my-provider", + "isCustom": true + } + ], + "cacheTtlMs": 60000, + "cacheAge": 1234 +} +``` + +**驗證:** 需要管理工作階段(儀表板 `auth_token` cookie)或管理範圍的 API 金鑰。 + +請參閱 [ACP 框架](../frameworks/ACP.md) 以取得完整詳細資訊。 + +--- + +## 分析與可觀測性 + +用於監控路由、壓縮和提供者多樣性的即時分析端點。這些為 `/dashboard/analytics/*` 頁面提供支援。 + +### 自動路由分析 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------------------- | --------------------------------------------------------------------------------------------- | +| GET | `/api/analytics/auto-routing` | 匯總自動路由統計:總呼叫數、策略分佈、層級分佈、熱門提供者 | +| GET | `/api/analytics/auto-routing?days=7` | 時間窗口統計(預設 24 小時) | + +**回應範例:** + +```json +{ + "window": "24h", + "totalCalls": 1234, + "strategyBreakdown": { + "rules": 800, + "cost": 200, + "latency": 150, + "sla-aware": 50, + "lkgp": 34 + }, + "tierBreakdown": { + "ultra": 100, + "pro": 500, + "standard": 400, + "free": 234 + }, + "topProviders": [ + { "provider": "openai", "calls": 500, "avgLatencyMs": 850 }, + { "provider": "anthropic", "calls": 300, "avgLatencyMs": 1200 } + ] +} +``` + +### 壓縮分析 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------------- | --------------------------------------------------------------------- | +| GET | `/api/analytics/compression` | 匯總壓縮統計:節省的權杖、節省百分比、模式分佈、引擎使用量 | + +**回應範例:** + +```json +{ + "window": "24h", + "totalOriginalTokens": 5000000, + "totalCompressedTokens": 3500000, + "totalSavings": 1500000, + "savingsPct": 30.0, + "modeBreakdown": { + "lite": 400, + "standard": 600, + "aggressive": 100, + "ultra": 50, + "rtk": 84 + }, + "engineBreakdown": { + "caveman": 800, + "rtk": 434 + } +} +``` + +### 提供者多樣性追蹤 + +| 方法 | 路徑 | 說明 | +| ------ | ----------------------------- | ------------------------------------------------------------------------------------------- | +| GET | `/api/analytics/diversity` | 基於 Shannon 熵的多樣性追蹤:透過衡量提供者分佈來防止單點故障 | + +**回應範例:** + +```json +{ + "window": "24h", + "shannonEntropy": 2.45, + "maxEntropy": 3.17, + "diversityRatio": 0.77, + "providerUsage": { + "openai": 0.4, + "anthropic": 0.25, + "google": 0.2, + "kiro": 0.15 + }, + "warnings": ["OpenAI 佔了 40% 的流量 — 建議增加多樣性"] +} +``` + +**驗證:** 需要管理工作階段或管理範圍的 API 金鑰。 + +--- + +## 管理員操作 + +僅限管理員的營運管理端點。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------- | ---------------------------------------------------------------------------------------------- | +| GET | `/api/admin/concurrency` | 讀取當前並行限制(全域 + 各提供者) | +| POST | `/api/admin/concurrency` | 更新並行限制 — 主體:`{global?: number, perProvider?: Record}` | + +**驗證:** 需要具有管理員範圍的管理工作階段。 + +--- + +## CLI 工具管理 + +管理與 OmniRoute 整合的 CLI 工具(antigravity、chipotle、commandCode、devin-cli 等)。請參閱[提供者參考](./PROVIDER_REFERENCE.md)以取得完整清單。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| GET | `/api/cli-tools/all-statuses` | 所有 CLI 工具的狀態(已安裝、版本、最後上線時間) | +| GET | `/api/cli-tools/[id]/status` | 特定 CLI 工具的狀態(ID 可為:antigravity、chipotle、commandCode、devin-cli 等) | +| POST | `/api/cli-tools/apply` | 將 CLI 工具配置套用至提供者連線 | +| GET | `/api/cli-tools/backups` | 列出 CLI 工具配置備份 | +| POST | `/api/cli-tools/backups` | 建立所有 CLI 工具配置的備份 | +| POST | `/api/cli-tools/[id]/restore` | 從備份還原 CLI 工具 | +| GET | `/api/cli-tools/antigravity-mitm` | Antigravity MITM 代理狀態(「antigravity-mitm」CLI 工具) | +| POST | `/api/cli-tools/antigravity-mitm/alias` | 配置 antigravity-mitm 別名 | + +**驗證:** 需要管理工作階段。 + +--- + +## 代理程式技能 + +管理 AI 代理程式技能(類似於 OpenAI 的自訂 GPT,但用於代理程式)。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------------- | ------------------------------------------------------------------------------------ | +| GET | `/api/agent-skills` | 列出所有代理程式技能(內建 + 自訂) | +| GET | `/api/agent-skills/[id]` | 取得特定代理程式技能 | +| POST | `/api/agent-skills` | 建立自訂代理程式技能 — 主體:`{name, description, prompt, model?, temperature?}` | +| PUT | `/api/agent-skills/[id]` | 更新自訂代理程式技能 | +| DELETE | `/api/agent-skills/[id]` | 刪除自訂代理程式技能 | +| GET | `/api/agent-skills/[id]/raw` | 取得原始提示詞 + 中繼資料(不含執行) | +| POST | `/api/agent-skills/generate` | 以自然語言描述 AI 生成新技能 | + +**驗證:** 需要管理工作階段或管理範圍的 API 金鑰。 + +--- + +## 快取管理 + +管理語義快取和推理快取。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------- | ----------------------------------------------------------------------------------------------------- | +| GET | `/api/cache` | 快取概覽:總條目數、命中率、磁碟大小 | +| GET | `/api/cache/entries` | 列出快取條目(支援分頁) | +| DELETE | `/api/cache/entries` | 刪除快取條目(依查詢參數過濾) | +| GET | `/api/cache/stats` | 詳細快取統計(各提供者、各模型) | +| GET | `/api/cache/reasoning` | 推理快取狀態(用於推理重播) | +| DELETE | `/api/cache/reasoning` | 清除推理快取 — 查詢參數:`?toolCallId=`(單一)或 `?provider=

` 或無參數(全部) | + +**驗證:** 需要管理工作階段。 + +--- + +## 記憶系統 + +管理持久性記憶(FTS5 + 向量嵌入)。 + +| 方法 | 路徑 | 說明 | +| ------ | ----------------------- | ----------------------------------------------------------- | +| GET | `/api/memory` | 列出記憶條目(依範圍、類型、搜尋查詢過濾) | +| POST | `/api/memory` | 建立新的記憶條目 — 主體:`{scope, type, content, metadata?}`| +| GET | `/api/memory/[id]` | 取得特定記憶條目 | +| PUT | `/api/memory/[id]` | 更新記憶條目 | +| DELETE | `/api/memory/[id]` | 刪除記憶條目 | +| GET | `/api/memory/search` | 搜尋記憶(FTS5 + 向量) | +| POST | `/api/memory/clear` | 清除記憶條目(含過濾器) | +| GET | `/api/memory/stats` | 記憶統計(總條目數、嵌入覆蓋率等) | + +**驗證:** 需要管理工作階段或管理範圍的 API 金鑰。 + +--- + +## Webhook + +管理事件的 Webhook 訂閱。 + +| 方法 | 路徑 | 說明 | +| ------ | ---------------------------------- | ----------------------------------------------------------------------- | +| GET | `/api/webhooks` | 列出所有 Webhook 訂閱 | +| POST | `/api/webhooks` | 建立 Webhook 訂閱 — 主體:`{url, events[], secret?, active?}` | +| GET | `/api/webhooks/[id]` | 取得特定 Webhook 訂閱 | +| PUT | `/api/webhooks/[id]` | 更新 Webhook 訂閱 | +| DELETE | `/api/webhooks/[id]` | 刪除 Webhook 訂閱 | +| GET | `/api/webhooks/events` | 列出所有可用的 Webhook 事件類型 | +| GET | `/api/webhooks/[id]/deliveries` | 列出 Webhook 的傳遞歷史記錄(成功/失敗日誌) | +| POST | `/api/webhooks/[id]/test` | 向 Webhook 傳送測試事件 | + +**驗證:** 需要管理工作階段。 + +請參閱 [Webhook 框架](../frameworks/WEBHOOKS.md)以取得完整的事件類型。 + +--- + +## 技能框架 + +管理技能(代理程式擴充框架)。 + +| 方法 | 路徑 | 說明 | +| ------ | --------------------------- | ------------------------------------------------------------------------------------ | +| GET | `/api/skills` | 列出所有已安裝的技能(內建 + 自訂) | +| POST | `/api/skills/install` | 從本機路徑或 URL 安裝技能 | +| DELETE | `/api/skills/[id]` | 解除安裝技能 | +| PUT | `/api/skills/[id]` | 啟用或停用技能 — 主體:`{enabled?: boolean, mode?: "on" \| "off" \| "auto"}` | +| POST | `/api/skills/executions` | 執行技能 — 主體:`{skillName, apiKeyId, input?, sessionId?}` | +| GET | `/api/skills/executions` | 列出所有技能的執行歷史(依 `?apiKeyId=` 過濾) | + +**驗證:** 需要管理工作階段或管理範圍的 API 金鑰。 + +請參閱[技能框架](../frameworks/SKILLS.md)以取得完整詳細資訊。 + +--- + +## 外掛程式 + +管理 OmniRoute 外掛程式(第三方擴充套件)。 + +| 方法 | 路徑 | 說明 | +| ------ | ----------------------------------- | --------------------------------- | +| GET | `/api/plugins` | 列出已安裝的外掛程式 | +| POST | `/api/plugins/install` | 從本機路徑或 URL 安裝外掛程式 | +| DELETE | `/api/plugins/[name]` | 解除安裝外掛程式 | +| POST | `/api/plugins/[name]/activate` | 啟用外掛程式 | +| POST | `/api/plugins/[name]/deactivate` | 停用外掛程式 | +| GET | `/api/plugins/[name]/config` | 取得外掛程式設定 | +| PUT | `/api/plugins/[name]/config` | 更新外掛程式設定 | + +**驗證:** 需要管理工作階段。 + +請參閱[外掛程式框架](../frameworks/PLUGIN_SDK.md)以取得完整詳細資訊。 + +--- + +## 影子路由 + +提供者的影子 / A-B 比較**不是一個獨立的 REST 表面** — 它是透過組合路由配置的(請參閱[自動組合](../routing/AUTO-COMBO.md))。各組合的比較指標由 `GET /api/combos/metrics` 提供。 + +--- + +## 防護機制 + +檢查執行時期防護機制(PII 偵測、提示注入偵測、視覺橋接)。防護機制在每個請求上執行;每次呼叫的選擇退出方式是透過 `x-omniroute-disabled-guardrails` 請求標頭 — 沒有持久的啟用/停用表面。 + +| 方法 | 路徑 | 說明 | +| ------ | ------------------------- | ----------------------------------------------------------------------------------- | +| GET | `/api/guardrails` | 列出已註冊的防護機制及其狀態(名稱/已啟用/優先順序) | +| POST | `/api/guardrails/test` | 對範例輸入乾執行呼叫前管線 — 主體:`{input, disabledGuardrails?}` | + +**驗證:** 需要管理工作階段。 + +請參閱[安全性 > 防護機制](../security/GUARDRAILS.md)以取得完整詳細資訊。 + +--- + +--- + +## 身分驗證 + +- 儀表板路由(`/dashboard/*`)使用 `auth_token` cookie +- 登入使用儲存的密碼雜湊;後備為 `INITIAL_PASSWORD` +- `requireLogin` 可透過 `/api/settings/require-login` 切換 +- `/v1/*` 路由在 `REQUIRE_API_KEY=true` 時可選擇要求 Bearer API 金鑰 + +> **重大變更(v3.8.0)** — `/api/v1/agents/tasks/*` 和冷卻管理端點現在需要**管理驗證**(儀表板 `auth_token` cookie 或管理範圍的 API 金鑰)。先前未經驗證即可呼叫這些路由的用戶端將收到 `401 Unauthorized`。請參閱提交 `588a0333`(`fix(auth): require management auth for agent and cooldown APIs`)。 diff --git a/docs/i18n/zh-TW/docs/reference/CLI-TOOLS.md b/docs/i18n/zh-TW/docs/reference/CLI-TOOLS.md index 5486b63cea..78fa7e6f07 100644 --- a/docs/i18n/zh-TW/docs/reference/CLI-TOOLS.md +++ b/docs/i18n/zh-TW/docs/reference/CLI-TOOLS.md @@ -1,89 +1,284 @@ -# CLI Tools Setup Guide — OmniRoute (中文 (簡體)) +--- +title: "CLI 工具 — OmniRoute" +version: 3.8.40 +lastUpdated: 2026-06-28 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/CLI-TOOLS.md) · 🇸🇦 [ar](../../ar/docs/CLI-TOOLS.md) · 🇧🇬 [bg](../../bg/docs/CLI-TOOLS.md) · 🇧🇩 [bn](../../bn/docs/CLI-TOOLS.md) · 🇨🇿 [cs](../../cs/docs/CLI-TOOLS.md) · 🇩🇰 [da](../../da/docs/CLI-TOOLS.md) · 🇩🇪 [de](../../de/docs/CLI-TOOLS.md) · 🇪🇸 [es](../../es/docs/CLI-TOOLS.md) · 🇮🇷 [fa](../../fa/docs/CLI-TOOLS.md) · 🇫🇮 [fi](../../fi/docs/CLI-TOOLS.md) · 🇫🇷 [fr](../../fr/docs/CLI-TOOLS.md) · 🇮🇳 [gu](../../gu/docs/CLI-TOOLS.md) · 🇮🇱 [he](../../he/docs/CLI-TOOLS.md) · 🇮🇳 [hi](../../hi/docs/CLI-TOOLS.md) · 🇭🇺 [hu](../../hu/docs/CLI-TOOLS.md) · 🇮🇩 [id](../../id/docs/CLI-TOOLS.md) · 🇮🇹 [it](../../it/docs/CLI-TOOLS.md) · 🇯🇵 [ja](../../ja/docs/CLI-TOOLS.md) · 🇰🇷 [ko](../../ko/docs/CLI-TOOLS.md) · 🇮🇳 [mr](../../mr/docs/CLI-TOOLS.md) · 🇲🇾 [ms](../../ms/docs/CLI-TOOLS.md) · 🇳🇱 [nl](../../nl/docs/CLI-TOOLS.md) · 🇳🇴 [no](../../no/docs/CLI-TOOLS.md) · 🇵🇭 [phi](../../phi/docs/CLI-TOOLS.md) · 🇵🇱 [pl](../../pl/docs/CLI-TOOLS.md) · 🇵🇹 [pt](../../pt/docs/CLI-TOOLS.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/CLI-TOOLS.md) · 🇷🇴 [ro](../../ro/docs/CLI-TOOLS.md) · 🇷🇺 [ru](../../ru/docs/CLI-TOOLS.md) · 🇸🇰 [sk](../../sk/docs/CLI-TOOLS.md) · 🇸🇪 [sv](../../sv/docs/CLI-TOOLS.md) · 🇰🇪 [sw](../../sw/docs/CLI-TOOLS.md) · 🇮🇳 [ta](../../ta/docs/CLI-TOOLS.md) · 🇮🇳 [te](../../te/docs/CLI-TOOLS.md) · 🇹🇭 [th](../../th/docs/CLI-TOOLS.md) · 🇹🇷 [tr](../../tr/docs/CLI-TOOLS.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/CLI-TOOLS.md) · 🇵🇰 [ur](../../ur/docs/CLI-TOOLS.md) · 🇻🇳 [vi](../../vi/docs/CLI-TOOLS.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/CLI-TOOLS.md) +# CLI 工具 — OmniRoute + +最後更新:2026-06-28 + +OmniRoute 整合了三類 CLI 工具,分別對應三個專屬儀表板頁面: + +| 頁面 | 路由 | 概念 | 數量 | +| ---------------- | -------------------------- | ------------------------------------------------------------ | ---------------- | +| **CLI 程式碼工具** | `/dashboard/cli-code` | 指向 OmniRoute 的程式碼工具(客戶端 → CLI → OmniRoute → 提供商) | 21 | +| **CLI 代理工具** | `/dashboard/cli-agents` | 指向 OmniRoute 的自動代理工具(相同流程,範圍更廣) | 6 | +| **ACP 代理** | `/dashboard/acp-agents` | OmniRoute 透過 stdio/ACP 以反向流程衍生的 CLI | 參見註冊表 | + +舊版路由透過 308 重新導向:`/dashboard/cli-tools` → `/dashboard/cli-code`,`/dashboard/agents` → `/dashboard/acp-agents`。 --- -This guide explains how to install and configure all supported AI coding CLI tools -to use **OmniRoute** as the unified backend, giving you centralized key management, -cost tracking, model switching, and request logging across every tool. - ---- - -## How It Works +## 運作方式 ``` -Claude / Codex / OpenCode / Cline / KiloCode / Continue / Kiro / Cursor / Copilot +CLI 程式碼工具 / CLI 代理工具(消費流程): +Claude / Codex / OpenCode / Cline / KiloCode / Continue / Hermes Agent / Goose / ... │ - ▼ (all point to OmniRoute) + ▼ (全部指向 OmniRoute) http://YOUR_SERVER:20128/v1 │ - ▼ (OmniRoute routes to the right provider) + ▼ (OmniRoute 路由至對應提供商) Anthropic / OpenAI / Gemini / DeepSeek / Groq / Mistral / ... + +ACP 代理(反向衍生流程): + 客戶端請求 → OmniRoute → 透過 stdio/ACP 衍生 CLI → 回應 ``` -**Benefits:** +**優勢:** -- One API key to manage all tools -- Cost tracking across all CLIs in the dashboard -- Model switching without reconfiguring every tool -- Works locally and on remote servers (VPS) +- 只需一個 API 金鑰管理所有工具 +- 在儀表板中追蹤所有 CLI 的費用 +- 切換模型無需重新設定每個工具 +- 可在本機及遠端伺服器上運作(VPS、Docker、Akamai、Cloudflare Tunnel) --- -## Supported Tools (Dashboard Source of Truth) +## 使用 `setup-*` 自動設定 -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 | -| **Qwen Code** | `qwen` | `qwen` | custom | npm | - -### 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`. - ---- - -## Step 1 — Get an OmniRoute API Key - -1. Open the OmniRoute dashboard → **API Manager** (`/dashboard/api-manager`) -2. Click **Create API Key** -3. Give it a name (e.g. `cli-tools`) and select all permissions -4. Copy the key — you'll need it for every CLI below - -> Your key looks like: `sk-xxxxxxxxxxxxxxxx-xxxxxxxxx` - ---- - -## Step 2 — Install CLI Tools - -All npm-based tools require Node.js 18+: +您無需手動編寫每個工具的設定檔。OmniRoute 為每個受支援的 CLI 提供了 `setup-*` 指令,可讀取執行中 OmniRoute(本機或遠端)的**即時**模型目錄,並在您的機器上寫入該工具的設定檔: ```bash -# Claude Code (Anthropic) +omniroute setup-codex omniroute setup-claude omniroute setup-opencode +omniroute setup-cline omniroute setup-kilo omniroute setup-continue +omniroute setup-cursor omniroute setup-roo omniroute setup-crush +omniroute setup-goose omniroute setup-qwen omniroute setup-aider +``` + +每個指令都接受 `--remote --api-key `(針對遠端 OmniRoute 設定本機工具)、`--dry-run`(預覽不寫入)和 `--port`。不支援模型自動探索的工具(Cline、Kilo、Roo、Goose、Aider、Gemini)需要 `--model `(以及用於非互動執行的 `--yes`)。啟動器 `omniroute launch`(Claude Code)和 `omniroute launch-codex`(Codex)會以正確的環境變數注入來衍生 CLI,完全不寫入設定檔。 + +> **完整參考:** 主要表格 — 每個指令寫入的內容、所有旗標、本機 vs 遠端,以及哪些工具需要加上 `/v1` 字尾 — 請參閱 **[CLI 整合指南](../guides/CLI-INTEGRATIONS.md)**。 + +--- + +## 資料來源 + +統一目錄位於 `src/shared/constants/cliTools.ts`,型別為 `CLI_TOOLS: Record`。 + +每個條目包含以下欄位(定義於 `src/shared/schemas/cliCatalog.ts`): + +| 欄位 | 型別 | 說明 | +| ------------------------------------------------ | ------------------------------------------------------------ | ----------------------------------------- | +| `category` | `"code" \| "agent"` | 工具顯示在哪個頁面 | +| `vendor` | `string` | 工具來源("Anthropic"、"OSS (P. Gauthier)") | +| `acpSpawnable` | `boolean` | 也可用作 ACP 代理(顯示徽章) | +| `baseUrlSupport` | `"full" \| "partial" \| "none"` | 自訂端點支援程度。`"none"` = MITM 待辦事項 | +| `configType` | `"env" \| "custom" \| "guide" \| "custom-builder" \| "mitm"` | 設定機制 | +| `id`、`name`、`color`、`description`、`docsUrl` | 標準 | 核心顯示欄位 | + +`baseUrlSupport: "none"` 的條目**不會**顯示在儀表板頁面上 — 它們會註冊在 MITM 待辦事項中,屬於 plan 11 的範疇(參見 `_tasks/features-v3.8.6/refactorpages/_orchestration/_plan11-mitm-backlog.md`)。 + +--- + +## 1. CLI 程式碼工具目錄(25 個工具) + +所有出現在 `/dashboard/cli-code` 的工具。`baseUrlSupport: none` 的工具會透過 MITM 或手動指南而非自訂基礎 URL 來連接: + +| id | 名稱 | 供應商 | baseUrlSupport | configType | acpSpawnable | +| -------------- | ------------------- | ------------------------ | -------------- | --------------- | ------------ | +| claude | Claude Code | Anthropic | full | env | true | +| codex | OpenAI Codex CLI | OpenAI | full | custom | true | +| cline | Cline | OSS(前 Claude Dev) | full | custom | true | +| kilo | Kilo Code | Kilo-Org | full | custom | false | +| roo | Roo Code | Roo(OSS) | full | guide | false | +| continue | Continue | continue.dev | full | guide | false | +| aider | Aider | OSS(P. Gauthier) | full | guide | true | +| forge | ForgeCode | Antinomy HQ | full | custom | true | +| jcode | jcode | 1jehuang(OSS) | full | custom | false | +| deepseek-tui | DeepSeek TUI | Hunter Bown(OSS) | full | custom | false | +| codewhale | CodeWhale | Hmbown(OSS) | full | custom | false | +| opencode | OpenCode | Anomaly(前 SST) | full | guide | true | +| droid | Factory Droid | Factory AI | partial | guide | false | +| copilot | GitHub Copilot CLI | GitHub/MS | full | custom | false | +| cursor-cli | Cursor CLI | Anysphere | partial | guide | true | +| smelt | Smelt | leonardcser(OSS) | full | custom | false | +| pi | Pi(pi-coding-agent) | M. Zechner(OSS) | full | custom | false | +| grok-build | Grok Build | xAI | full | custom | false | +| crush | Crush | OSS(Charm) | full | custom | false | +| qwen | Qwen Code | Alibaba | full | guide | true | +| cursor | Cursor | Anysphere | none | guide | false | +| antigravity | Antigravity | Google | none | mitm | false | +| hermes | Hermes | Nous Research | none | guide | false | +| kiro | Kiro AI | Amazon | none | mitm | false | +| custom | 自訂 CLI | — | full | custom-builder | false | + +`baseUrlSupport: "partial"` 的工具會在儀表板卡片上顯示「⚠ 基礎 URL 部分支援」徽章。 + +--- + +## 2. CLI 代理工具目錄(8 個工具) + +出現在 `/dashboard/cli-agents` 的自動代理工具: + +| id | 名稱 | 供應商 | baseUrlSupport | acpSpawnable | +| ------------ | ------------------- | ------------------------- | -------------- | ------------ | +| hermes-agent | Hermes Agent | Nous Research | full | false | +| openclaw | OpenClaw | OSS(P. Steinberger) | full | true | +| goose | Goose | Block / Linux Foundation | full | true | +| interpreter | Open Interpreter | OSS | full | true | +| warp | Warp AI | Warp Inc. | partial | true | +| agent-deck | Agent Deck | asheshgoplani(OSS) | full | false | +| omp | Oh My Pi | OSS | full | true | +| letta | Letta CLI | Letta | full | false | + +--- + +## 3. ACP 代理(/dashboard/acp-agents) + +此頁面(從 `/dashboard/agents` 重新命名而來)顯示 OmniRoute 可以**衍生**為後端執行引擎(透過 stdio/ACP 協定)的 CLI。目錄獨立維護於 `src/lib/acp/registry.ts`,**不同於** `CLI_TOOLS`。 + +--- + +## 4. MITM 待辦事項(不在儀表板中顯示) + +以下 CLI 原生不支援自訂基礎 URL,**不會列出**在 CLI 程式碼工具或 CLI 代理工具頁面中。它們是 plan 11 中 MITM 攔截的候選對象: + +| CLI | 原因 | +| --------------------- | ------------------------------------------------- | +| windsurf | BYOK 僅限特定 Claude 模型 + 企業 URL/Token | +| amp | 封閉生態系統(Sourcegraph) | +| amazon-q / kiro-cli | AWS SSO 認證,無自訂 URL | +| cowork | Anthropic Desktop,無可設定的端點 | + +完整交叉參考請參閱 `_tasks/features-v3.8.6/refactorpages/_orchestration/_plan11-mitm-backlog.md`。 + +--- + +## 5. 批次偵測 API + +所有工具偵測透過單一端點匯總: + +**`GET /api/cli-tools/all-statuses`** + +- 身份驗證:`requireCliToolsAuth(request)`(與其他 `/api/cli-tools/` 路由相同) +- 回傳:`Record`(型別:`src/shared/types/cliBatchStatus.ts`) +- 策略:對所有工具執行 `Promise.all`,每個工具 5 秒逾時 +- 快取:記憶體中 LRU,以設定檔 `mtime` 作為索引。當 `mtime` 變更時失效。伺服器重新啟動時重設。 + +每個工具的回應結構: + +```ts +interface ToolBatchStatus { + detection: { + installed: boolean; + runnable: boolean; + version?: string; + command?: string; + commandPath?: string; + reason?: string; + }; + config: { + status: "configured" | "not_configured" | "not_installed" | "unknown" | "other"; + endpoint?: string | null; + lastConfiguredAt?: string | null; + }; + error?: string; // 已清理,無堆疊追蹤 +} +``` + +--- + +## 6. 新工具的設定處理器 + +`configType: "custom"` 的新工具擁有專屬的設定 API 路由: + +| 路由 | 工具 | +| ------------------------------------------------- | ----------------------------------- | +| `POST /api/cli-tools/forge-settings` | ForgeCode(.forge.toml) | +| `POST /api/cli-tools/jcode-settings` | jcode(--base-url 旗標) | +| `POST /api/cli-tools/deepseek-tui-settings` | DeepSeek TUI(OPENAI_BASE_URL,舊版) | +| `POST /api/cli-tools/codewhale-settings` | CodeWhale(OPENAI_BASE_URL,主要 + 舊版 `~/.deepseek` 同步) | +| `POST /api/cli-tools/smelt-settings` | Smelt | +| `POST /api/cli-tools/pi-settings` | Pi 程式碼代理 | +| `POST /api/cli-tools/grok-build-settings` | Grok Build(~/.grok/config.toml,`[model.omniroute]`) | +| `POST /api/cli-tools/qwen-settings` | Qwen Code(`~/.qwen/settings.json` + 專用 `.env` 金鑰) | + +所有路由都使用 `sanitizeErrorMessage()` 處理錯誤回應(硬性規則 #12)。 + +--- + +## 7. 儀表板頁面架構 + +### CLI 程式碼工具(`/dashboard/cli-code`) + +- `src/app/(dashboard)/dashboard/cli-code/page.tsx` — 伺服器元件 +- `src/app/(dashboard)/dashboard/cli-code/CliCodePageClient.tsx` — 客戶端網格 +- `src/app/(dashboard)/dashboard/cli-code/[id]/page.tsx` — 工具詳細頁面 +- `src/app/(dashboard)/dashboard/cli-code/components/` — 12 個專用工具卡片 + `ToolDetailClient.tsx` + +### CLI 代理工具(`/dashboard/cli-agents`) + +- `src/app/(dashboard)/dashboard/cli-agents/page.tsx` — 伺服器元件 +- `src/app/(dashboard)/dashboard/cli-agents/CliAgentsPageClient.tsx` — 客戶端網格 +- `src/app/(dashboard)/dashboard/cli-agents/[id]/page.tsx` — 重複使用 `ToolDetailClient` + +### ACP 代理(`/dashboard/acp-agents`) + +- `src/app/(dashboard)/dashboard/acp-agents/page.tsx` — 伺服器元件(從 `agents/` 遷移) + +### 共用 UI 元件(`src/shared/components/cli/`) + +| 檔案 | 用途 | +| ------------------------ | ---------------------------------------------- | +| `CliToolCard.tsx` | 智慧型狀態卡片(偵測 + 設定 + 端點) | +| `CliConceptCard.tsx` | 各頁面概念說明卡片 | +| `CliComparisonCard.tsx` | 三欄 CLI 類型比較卡片 | +| `BaseUrlSelect.tsx` | 端點下拉選單(本機/雲端/自訂) | +| `ApiKeySelect.tsx` | API 金鑰選擇器 | +| `ManualConfigModal.tsx` | 可複製的設定片段模態框 | + +### 共用 Hook(`src/shared/hooks/cli/`) + +| 檔案 | 用途 | +| ---------------------------- | ------------------------------------------- | +| `useToolBatchStatuses.ts` | 擷取 `/api/cli-tools/all-statuses`,管理載入/重新整理狀態 | + +--- + +## 8. 國際化(i18n) + +plan 14 F9 中新增的命名空間: + +| 命名空間 | 用途 | +| ------------- | ------------------------------------------- | +| `cliCommon` | 共用字串(卡片標籤、概念/比較文字、詳細頁面標籤) | +| `cliCode` | CLI 程式碼工具頁面字串 | +| `cliAgents` | CLI 代理工具頁面字串 | +| `acpAgents` | ACP 代理頁面字串 | + +已提供完整的巴西葡萄牙文(PT-BR)和英文(EN)翻譯。其他 39 種語言會透過 `src/i18n/request.ts` 中的命名空間層級合併自動回退為英文。 + +--- + +## 9. 快速入門 + +### 步驟 1 — 取得 OmniRoute API 金鑰 + +1. 開啟 `/dashboard/api-manager` → **建立 API 金鑰** +2. 為金鑰命名(例如 `cli-tools`)並選取所有權限 +3. 複製金鑰 — 下方每個 CLI 都會用到 + +> 您的金鑰格式如:`«redacted:sk-…»` + +--- + +### 步驟 2 — 安裝 CLI 工具 + +所有基於 npm 的工具都需要 Node.js 22.22.2+ 或 24.x: + +```bash +# Claude Code(Anthropic) npm install -g @anthropic-ai/claude-code # OpenAI Codex @@ -98,138 +293,164 @@ npm install -g cline # KiloCode npm install -g kilocode -# Kiro CLI (Amazon — requires curl + unzip) -apt-get install -y unzip # on Debian/Ubuntu -curl -fsSL https://cli.kiro.dev/install | bash -export PATH="$HOME/.local/bin:$PATH" # add to ~/.bashrc -``` +# Qwen Code +npm install -g @qwen-code/qwen-code -**Verify:** +# Aider +pip install aider-chat -```bash -claude --version # 2.x.x -codex --version # 0.x.x -opencode --version # x.x.x -cline --version # 2.x.x -kilocode --version # x.x.x (or: kilo --version) -kiro-cli --version # 1.x.x +# Smelt +cargo install smelt # 基於 Rust + +# Pi 程式碼代理 +# 請參閱 https://github.com/zechnerj/pi-coding-agent 了解安裝方式 + +# jcode +# 請參閱 https://github.com/1jehuang/jcode 了解安裝方式 ``` --- -## Step 3 — Set Global Environment Variables +### 步驟 3 — 透過儀表板設定 -Add to `~/.bashrc` (or `~/.zshrc`), then run `source ~/.bashrc`: +1. 前往 `http://localhost:20128/dashboard/cli-code` +2. 在網格中尋找您的工具 +3. 點選卡片開啟工具詳細頁面 +4. 選取您的 API 金鑰和基礎 URL +5. 點選**套用設定**或複製手動設定片段 + +--- + +### 步驟 4 — 設定全域環境變數 ```bash -# OmniRoute Universal Endpoint +# OmniRoute 通用端點 export OPENAI_BASE_URL="http://localhost:20128/v1" -export OPENAI_API_KEY="sk-your-omniroute-key" -export ANTHROPIC_BASE_URL="http://localhost:20128/v1" -export ANTHROPIC_API_KEY="sk-your-omniroute-key" +export OPENAI_API_KEY="«redacted:sk-…»" +export ANTHROPIC_BASE_URL="http://localhost:20128" +export ANTHROPIC_AUTH_TOKEN="«redacted:sk-…»" export GEMINI_BASE_URL="http://localhost:20128/v1" -export GEMINI_API_KEY="sk-your-omniroute-key" +export GEMINI_API_KEY="«redacted:sk-…»" ``` -> For a **remote server** replace `localhost:20128` with the server IP or domain, -> e.g. `http://192.168.0.15:20128`. +> 若使用**遠端伺服器**,請將 `localhost:20128` 替換為伺服器 IP 或網域名稱, +> 例如 `http://:20128`。 --- -## Step 4 — Configure Each Tool +### 步驟 4 — 設定各個工具 -### Claude Code +#### Claude Code ```bash -# Via CLI: -claude config set --global api-base-url http://localhost:20128/v1 - -# Or create ~/.claude/settings.json: +# 建立 ~/.claude/settings.json: mkdir -p ~/.claude && cat > ~/.claude/settings.json << EOF { - "apiBaseUrl": "http://localhost:20128/v1", - "apiKey": "sk-your-omniroute-key" + "env": { + "ANTHROPIC_BASE_URL": "http://localhost:20128", + "ANTHROPIC_AUTH_TOKEN": "«redacted:sk-…»" + } } EOF ``` -**Test:** `claude "say hello"` +請使用統一的 Anthropic 閘道根路徑來設定 Claude Code。此處不要加上 `/v1`。 + +**測試:** `claude "say hello"` --- -### OpenAI Codex +#### OpenAI Codex ```bash mkdir -p ~/.codex && cat > ~/.codex/config.yaml << EOF model: auto -apiKey: sk-your-omniroute-key +apiKey: *** apiBaseUrl: http://localhost:20128/v1 EOF ``` -**Test:** `codex "what is 2+2?"` +**測試:** `codex "what is 2+2?"` --- -### OpenCode +#### OpenCode ```bash -mkdir -p ~/.config/opencode && cat > ~/.config/opencode/config.toml << EOF -[provider.openai] -base_url = "http://localhost:20128/v1" -api_key = "sk-your-omniroute-key" +mkdir -p ~/.config/opencode && cat > ~/.config/opencode/opencode.json << EOF +{ + "\$schema": "https://opencode.ai/config.json", + "provider": { + "omniroute": { + "npm": "@ai-sdk/openai-compatible", + "name": "OmniRoute", + "options": { + "baseURL": "http://localhost:20128/v1", + "apiKey": "«redacted:sk-…»" + }, + "models": { + "claude-sonnet-4-5": { "name": "claude-sonnet-4-5" }, + "claude-sonnet-4-5-thinking": { "name": "claude-sonnet-4-5-thinking" }, + "gemini-3-flash": { "name": "gemini-3-flash" } + } + } + } +} EOF ``` -**Test:** `opencode` +**測試:** `opencode` + +> 使用 `opencode run "your prompt" --model omniroute/claude-sonnet-4-5-thinking --variant high` +> 來發送思考變體。 --- -### Cline (CLI or VS Code) +#### Cline(CLI 或 VS Code) -**CLI mode:** +**CLI 模式:** ```bash mkdir -p ~/.cline/data && cat > ~/.cline/data/globalState.json << EOF { "apiProvider": "openai", "openAiBaseUrl": "http://localhost:20128/v1", - "openAiApiKey": "sk-your-omniroute-key" + "openAiApiKey": "«redacted:sk-…»" } EOF ``` -**VS Code mode:** -Cline extension settings → API Provider: `OpenAI Compatible` → Base URL: `http://localhost:20128/v1` +**VS Code 模式:** +Cline 擴充功能設定 → API Provider:`OpenAI Compatible` → Base URL:`http://localhost:20128/v1` -Or use the OmniRoute dashboard → **CLI Tools → Cline → Apply Config**. +或使用 OmniRoute 儀表板 → **CLI 工具 → Cline → 套用設定**。 --- -### KiloCode (CLI or VS Code) +#### KiloCode(CLI 或 VS Code) -**CLI mode:** +**CLI 模式:** ```bash -kilocode --api-base http://localhost:20128/v1 --api-key sk-your-omniroute-key +kilocode --api-base http://localhost:20128/v1 --api-key «redacted:sk-…» ``` -**VS Code settings:** +**VS Code 設定:** ```json { "kilo-code.openAiBaseUrl": "http://localhost:20128/v1", - "kilo-code.apiKey": "sk-your-omniroute-key" + "kilo-code.apiKey": "«redacted:sk-…»" } ``` -Or use the OmniRoute dashboard → **CLI Tools → KiloCode → Apply Config**. +或使用 OmniRoute 儀表板 → **CLI 工具 → KiloCode → 套用設定**。 --- -### Continue (VS Code Extension) +#### Continue(VS Code 擴充功能) -Edit `~/.continue/config.yaml`: +編輯 `~/.continue/config.yaml`: ```yaml models: @@ -237,162 +458,251 @@ models: provider: openai model: auto apiBase: http://localhost:20128/v1 - apiKey: sk-your-omniroute-key + apiKey: *** default: true ``` -Restart VS Code after editing. +編輯後重新啟動 VS Code。 --- -### Kiro CLI (Amazon) +#### VS Code Insiders(`chatLanguageModels.json`) + +當 VS Code Insiders 設定為使用自訂端點模型,且您希望 OmniRoute 在無需自訂標頭欄位的情況下運作時使用。 + +**建議位置:** + +- Linux:`~/.config/Code - Insiders/User/chatLanguageModels.json` +- Windows:`%APPDATA%/Code - Insiders/User/chatLanguageModels.json` + +**使用 Token 化 OmniRoute 別名的範例:** + +```json +[ + { + "vendor": "customendpoint", + "id": "auto", + "name": "OmniRoute Auto", + "family": "gpt-4", + "version": "1.0.0", + "url": "http://localhost:20128/api/v1/vscode/«redacted:sk-…»/chat/completions", + "modelsUrl": "http://localhost:20128/api/v1/vscode/«redacted:sk-…»/models", + "requestFormat": "openai-chat-completions", + "contextWindow": 256000, + "maxOutputTokens": 32768, + "auth": { + "type": "none" + } + } +] +``` + +**注意事項:** + +- 將 `«redacted:sk-…»` 替換為在 OmniRoute 中建立的 API 金鑰。 +- `url` 欄位應指向 `/api/v1/vscode/{token}/chat/completions`。 +- `modelsUrl` 欄位應指向 `/api/v1/vscode/{token}/models`。 +- 如果客戶端支援自訂標頭,建議使用標準的 `/v1` + Bearer 標頭流程。 +- 內嵌 URL 的 Token 是相容性備援方案,可能會出現在編輯器日誌或代理歷史記錄中。 + +--- + +#### Kiro CLI(Amazon) ```bash -# Login to your AWS/Kiro account: +# 登入您的 AWS/Kiro 帳戶: kiro-cli login -# The CLI uses its own auth — OmniRoute is not needed as backend for Kiro CLI itself. -# Use kiro-cli alongside OmniRoute for other tools. +# CLI 使用自己的認證機制 — Kiro CLI 本身不需要 OmniRoute 作為後端。 +# 請將 kiro-cli 與 OmniRoute 搭配使用於其他工具。 kiro-cli status ``` +至於 **Kiro IDE** 桌面應用程式,請使用 OmniRoute 在 `/dashboard/cli-tools → Kiro` 提供的 MITM 端點。 + --- -### Qwen Code (Alibaba) +## 10. 內部 OmniRoute CLI -Qwen Code supports OpenAI-compatible API endpoints via environment variables or `settings.json`. - -**Option 1: Environment variables (`~/.qwen/.env`)** +`omniroute` 二進位檔提供用於伺服器生命週期管理、設定、診斷和提供商管理的指令。進入點:`bin/omniroute.mjs`。 ```bash -mkdir -p ~/.qwen && cat > ~/.qwen/.env << EOF -OPENAI_API_KEY="sk-your-omniroute-key" -OPENAI_BASE_URL="http://localhost:20128/v1" -OPENAI_MODEL="auto" -EOF +omniroute # 啟動伺服器(預設通訊埠 20128) +omniroute setup # 互動式設定精靈 +omniroute doctor # 檢查設定、資料庫、通訊埠、執行環境 +omniroute providers list # 已設定的提供商連線 +omniroute providers test-all # 測試每個作用中連線 +omniroute reset-password # 重設管理員密碼 +omniroute logs # 串流要求日誌 +omniroute health # 詳細健康狀態(斷路器、快取、記憶體) +omniroute --version # 顯示版本 +omniroute --help # 顯示所有指令 ``` -**Option 2: `settings.json` with model providers** - -```json -// ~/.qwen/settings.json -{ - "env": { - "OPENAI_API_KEY": "sk-your-omniroute-key", - "OPENAI_BASE_URL": "http://localhost:20128/v1" - }, - "modelProviders": { - "openai": [ - { - "id": "omniroute-default", - "name": "OmniRoute (Auto)", - "envKey": "OPENAI_API_KEY", - "baseUrl": "http://localhost:20128/v1" - } - ] - } -} -``` - -**Option 3: Inline CLI flags** +### 設定與初始化 ```bash -OPENAI_BASE_URL="http://localhost:20128/v1" \ -OPENAI_API_KEY="sk-your-omniroute-key" \ -OPENAI_MODEL="auto" \ -qwen +omniroute setup # 互動式設定精靈 +omniroute setup --non-interactive # CI/自動化模式(讀取環境變數 + 旗標) +omniroute setup --password '' # 直接設定管理員密碼 +omniroute setup --add-provider \ + --provider openai \ + --api-key '' \ + --test-provider # 一氣呵成新增並測試提供商 ``` -> For a **remote server** replace `localhost:20128` with the server IP or domain. +非互動式設定可識別的環境變數: -**Test:** `qwen "say hello"` +| 變數 | 用途 | +| -------------------- | ----------------------------------------- | +| `OMNIROUTE_API_KEY` | 提供商 API 金鑰(透過 Commander `.env()` 繫結至 `--api-key`) | +| `DATA_DIR` | 覆寫 OmniRoute 資料目錄 | -### Cursor (Desktop App) +所有其他非互動式輸入皆以旗標傳遞(非環境變數): +`--password`、`--provider`、`--provider-name`、`--provider-base-url`、`--default-model` +(請參閱上方 `omniroute setup` 選項)。 -> **Note:** Cursor routes requests through its cloud. For OmniRoute integration, -> enable **Cloud Endpoint** in OmniRoute Settings and use your public domain URL. +### 診斷 -Via GUI: **Settings → Models → OpenAI API Key** +```bash +omniroute doctor # 檢查設定、資料庫、通訊埠、執行環境、記憶體、運作狀態 +omniroute doctor --json # 機器可讀的 JSON +omniroute doctor --no-liveness # 跳過 HTTP 健康狀態探測 +omniroute doctor --host 0.0.0.0 # 覆寫運作狀態主機 +omniroute doctor --liveness-url # 完整健康端點 URL 覆寫 +``` -- Base URL: `https://your-domain.com/v1` -- API Key: your OmniRoute key +doctor 會執行以下檢查:`Config`、`Database`、`Storage/encryption`、 +`Port availability`、`Node runtime`、`Native binary`(better-sqlite3)、 +`Memory` 和 `Server liveness`。若有任一檢查結果為 `fail`,則以非零退出碼結束。 + +### 提供商管理 + +```bash +omniroute providers available # OmniRoute 提供商目錄 +omniroute providers available --search openai # 依 ID/名稱/別名/類別過濾目錄 +omniroute providers available --category api-key # 依類別過濾(api-key、oauth、free 等) +omniroute providers available --json # 機器可讀的 JSON + +omniroute providers list # 已設定的提供商連線 +omniroute providers list --json + +omniroute providers test # 測試一個已設定的連線 +omniroute providers test-all # 測試每個作用中連線 +omniroute providers validate # 僅限本機的結構驗證 +``` + +> `providers available` 讀取 OmniRoute 目錄;`providers list/test/test-all/validate` +> 直接讀取本機 SQLite 資料庫,無需伺服器執行中。 + +### 復原與重設 + +```bash +omniroute reset-password # 重設管理員密碼(亦可使用:omniroute-reset-password) +omniroute reset-encrypted-columns # 顯示警告 + 加密憑證重設的試執行 +omniroute reset-encrypted-columns --force # 實際將 SQLite 中的加密憑證設為 null +``` + +### 憑證匯出(⚠ 請謹慎處理) + +```bash +omniroute auth export # 顯示警告 + 確認閘道 — 不會存取資料庫 +omniroute auth export --force # 將所有連線的**解密後**憑證匯出至 stdout 為 JSON +omniroute auth export --force --id # 僅匯出符合條件的連線 +omniroute auth export --force --format env # 輸出為 OMNIROUTE__= 格式 +omniroute auth export --force --out creds.json # 寫入檔案(以 0600 權限建立) +``` + +`auth export` 是**僅限本機**(直接讀取 SQLite,無 HTTP 路由),且故意將 +**明文** `apiKey`/`accessToken`/`refreshToken`/`idToken` 值寫入/輸出 — 這是功能,不是錯誤。 +若未使用 `--force`,則不會從資料庫讀取任何內容,也不會解密任何內容。在輸出任何明文之前, +stderr 上一定會顯示警告橫幅。需要設定 `STORAGE_ENCRYPTION_KEY`。 +如果某個欄位解密失敗(金鑰過期、密文損毀),會回報為 +`DecryptFailed: true`,而非中止整個匯出作業或洩漏底層錯誤。 + +### 其他子指令 + +以下指令假設 OmniRoute 伺服器正在執行中,除非另有說明: + +```bash +omniroute status # 完整的執行時期狀態 +omniroute logs # 串流要求日誌(--json、--search、--follow) +omniroute config show # 顯示目前設定 + +omniroute provider list # 列出可用提供商(providers list 的別名) +omniroute provider add # 將 OmniRoute 註冊為工具上的提供商 +omniroute keys add | list | remove # 管理 API 金鑰 +omniroute models [provider] # 列出模型(--json、--search) +omniroute combo list | switch | create | delete + +omniroute backup # 快照設定 + 資料庫 +omniroute restore # 從先前的快照還原 + +omniroute health # 詳細健康狀態(斷路器、快取、記憶體) +omniroute quota # 提供商配額使用情況 +omniroute cache # 快取狀態 +omniroute cache clear # 清除語意 + 簽章快取 + +omniroute mcp status | restart # MCP 伺服器狀態 / 重新啟動 +omniroute a2a status | card # A2A 伺服器狀態 / 代理卡片 + +omniroute tunnel list | create | stop # 管理通道(cloudflare/tailscale/ngrok) +omniroute env show | get | set # 檢查 / 設定環境變數(暫時性) + +omniroute test # 提供商連線冒煙測試 +omniroute update # 檢查更新 +omniroute completion # 產生 Shell 補全 +``` + +### 常用旗標 + +| 旗標 | 說明 | +| ------------------- | ----------------------------------------- | +| `--no-open` | 啟動時不自動開啟瀏覽器 | +| `--port ` | 覆寫 API 通訊埠(預設 20128) | +| `--mcp` | 以 MCP 伺服器模式透過 stdio 執行(用於 IDE)| +| `--non-interactive` | CI 模式(無提示;從環境變數/旗標讀取) | +| `--json` | 機器可讀的 JSON 輸出(doctor、providers 等)| +| `--help`、`-h` | 顯示指令專屬說明 | +| `--version`、`-v` | 顯示已安裝版本 | --- -## Dashboard Auto-Configuration +## 可用 API 端點 -The OmniRoute dashboard automates configuration for most tools: +| 端點 | 說明 | 用途 | +| --------------------------- | ----------------- | ----------------------- | +| `/v1/chat/completions` | 標準聊天(所有提供商)| 所有現代工具 | +| `/v1/responses` | Responses API(OpenAI 格式)| Codex、代理工作流程 | +| `/v1/completions` | 舊版文字補全 | 使用 `prompt:` 的較舊工具 | +| `/v1/embeddings` | 文字嵌入 | RAG、搜尋 | +| `/v1/images/generations` | 圖片生成 | GPT-Image、Flux 等 | +| `/v1/audio/speech` | 文字轉語音 | ElevenLabs、OpenAI TTS | +| `/v1/audio/transcriptions` | 語音轉文字 | Deepgram、AssemblyAI | -1. Go to `http://localhost:20128/dashboard/cli-tools` -2. Expand any tool card -3. Select your API key from the dropdown -4. Click **Apply Config** (if tool is detected as installed) -5. Or copy the generated config snippet manually +可直接貼上的 Token 化 OmniRoute URL 範例: ---- +```txt +Token 範例:«redacted:sk-…» -## Built-in Agents: Droid & OpenClaw - -**Droid** and **OpenClaw** are AI agents built directly into OmniRoute — no installation needed. -They run as internal routes and use OmniRoute's model routing automatically. - -- Access: `http://localhost:20128/dashboard/agents` -- Configure: same combos and providers as all other tools -- No API key or CLI install required - ---- - -## Available API Endpoints - -| Endpoint | Description | Use For | -| -------------------------- | ----------------------------- | --------------------------- | -| `/v1/chat/completions` | Standard chat (all providers) | All modern tools | -| `/v1/responses` | Responses API (OpenAI format) | Codex, agentic workflows | -| `/v1/completions` | Legacy text completions | Older tools using `prompt:` | -| `/v1/embeddings` | Text embeddings | RAG, search | -| `/v1/images/generations` | Image generation | GPT-Image, Flux, etc. | -| `/v1/audio/speech` | Text-to-speech | ElevenLabs, OpenAI TTS | -| `/v1/audio/transcriptions` | Speech-to-text | Deepgram, AssemblyAI | +標準 OpenAI 基礎:http://localhost:20128/v1 +VS Code 模型:http://localhost:20128/api/v1/vscode/«redacted:sk-…»/models +VS Code 聊天:http://localhost:20128/api/v1/vscode/«redacted:sk-…»/chat/completions +VS Code responses:http://localhost:20128/api/v1/vscode/«redacted:sk-…»/responses +Ollama tags:http://localhost:20128/api/v1/vscode/«redacted:sk-…»/api/tags +Ollama 聊天:http://localhost:20128/api/v1/vscode/«redacted:sk-…»/api/chat +``` --- ## 故障排除 -| Error | Cause | Fix | -| ------------------------- | ----------------------- | ------------------------------------------ | -| `Connection refused` | OmniRoute not running | `pm2 start omniroute` | -| `401 Unauthorized` | Wrong API key | Check in `/dashboard/api-manager` | -| `No combo configured` | No active routing combo | Set up in `/dashboard/combos` | -| `invalid model` | Model not in catalog | Use `auto` or check `/dashboard/providers` | -| CLI shows "not installed" | Binary not in PATH | Check `which ` | -| `kiro-cli: not found` | Not in PATH | `export PATH="$HOME/.local/bin:$PATH"` | - ---- - -## Quick Setup Script (One Command) - -```bash -# Install all CLIs and configure for OmniRoute (replace with your key and server URL) -OMNIROUTE_URL="http://localhost:20128/v1" -OMNIROUTE_KEY="sk-your-omniroute-key" - -npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai cline kilocode @qwen-code/qwen-code - -# Kiro CLI -apt-get install -y unzip 2>/dev/null; curl -fsSL https://cli.kiro.dev/install | bash - -# Write configs -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 >> ~/.bashrc << EOF -export OPENAI_BASE_URL="$OMNIROUTE_URL" -export OPENAI_API_KEY="$OMNIROUTE_KEY" -export ANTHROPIC_BASE_URL="$OMNIROUTE_URL" -export ANTHROPIC_API_KEY="$OMNIROUTE_KEY" -EOF - -source ~/.bashrc -echo "✅ All CLIs installed and configured for OmniRoute" -``` +| 錯誤 | 原因 | 解決方式 | +| ---------------------------------------------- | ------------------------- | ----------------------------------------------- | +| `Connection refused` | OmniRoute 未執行 | `omniroute serve` | +| `401 Unauthorized` | API 金鑰錯誤 | 在 `/dashboard/api-manager` 中檢查 | +| `No combo configured` | 無作用中路由組合 | 在 `/dashboard/combos` 中設定 | +| CLI 顯示「not installed」 | 二進位檔不在 PATH 中 | 檢查 `which ` | +| 儀表板在安裝後顯示「not detected」 | 快取過期 | 點選儀表板中的「⟳ 重新整理偵測」 | +| 舊連結 `/dashboard/cli-tools` | v3.8.6 之前的書籤 | 自動重新導向至 `/dashboard/cli-code`(308) | +| 舊連結 `/dashboard/agents` | v3.8.6 之前的書籤 | 自動重新導向至 `/dashboard/acp-agents`(308) | diff --git a/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md b/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md index ed2140f34b..bad532a3aa 100644 --- a/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md @@ -1,665 +1,461 @@ -# Environment Variables Reference (中文 (簡體)) +--- +title: "環境變數參考" +version: 3.8.40 +lastUpdated: 2026-06-28 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/ENVIRONMENT.md) · 🇸🇦 [ar](../../ar/docs/ENVIRONMENT.md) · 🇧🇬 [bg](../../bg/docs/ENVIRONMENT.md) · 🇧🇩 [bn](../../bn/docs/ENVIRONMENT.md) · 🇨🇿 [cs](../../cs/docs/ENVIRONMENT.md) · 🇩🇰 [da](../../da/docs/ENVIRONMENT.md) · 🇩🇪 [de](../../de/docs/ENVIRONMENT.md) · 🇪🇸 [es](../../es/docs/ENVIRONMENT.md) · 🇮🇷 [fa](../../fa/docs/ENVIRONMENT.md) · 🇫🇮 [fi](../../fi/docs/ENVIRONMENT.md) · 🇫🇷 [fr](../../fr/docs/ENVIRONMENT.md) · 🇮🇳 [gu](../../gu/docs/ENVIRONMENT.md) · 🇮🇱 [he](../../he/docs/ENVIRONMENT.md) · 🇮🇳 [hi](../../hi/docs/ENVIRONMENT.md) · 🇭🇺 [hu](../../hu/docs/ENVIRONMENT.md) · 🇮🇩 [id](../../id/docs/ENVIRONMENT.md) · 🇮🇹 [it](../../it/docs/ENVIRONMENT.md) · 🇯🇵 [ja](../../ja/docs/ENVIRONMENT.md) · 🇰🇷 [ko](../../ko/docs/ENVIRONMENT.md) · 🇮🇳 [mr](../../mr/docs/ENVIRONMENT.md) · 🇲🇾 [ms](../../ms/docs/ENVIRONMENT.md) · 🇳🇱 [nl](../../nl/docs/ENVIRONMENT.md) · 🇳🇴 [no](../../no/docs/ENVIRONMENT.md) · 🇵🇭 [phi](../../phi/docs/ENVIRONMENT.md) · 🇵🇱 [pl](../../pl/docs/ENVIRONMENT.md) · 🇵🇹 [pt](../../pt/docs/ENVIRONMENT.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/ENVIRONMENT.md) · 🇷🇴 [ro](../../ro/docs/ENVIRONMENT.md) · 🇷🇺 [ru](../../ru/docs/ENVIRONMENT.md) · 🇸🇰 [sk](../../sk/docs/ENVIRONMENT.md) · 🇸🇪 [sv](../../sv/docs/ENVIRONMENT.md) · 🇰🇪 [sw](../../sw/docs/ENVIRONMENT.md) · 🇮🇳 [ta](../../ta/docs/ENVIRONMENT.md) · 🇮🇳 [te](../../te/docs/ENVIRONMENT.md) · 🇹🇭 [th](../../th/docs/ENVIRONMENT.md) · 🇹🇷 [tr](../../tr/docs/ENVIRONMENT.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/ENVIRONMENT.md) · 🇵🇰 [ur](../../ur/docs/ENVIRONMENT.md) · 🇻🇳 [vi](../../vi/docs/ENVIRONMENT.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/ENVIRONMENT.md) +# 環境變數參考 + +> OmniRoute 所識別的每個環境變數的完整參考。 +> 快速入門範本請參閱 [`.env.example`](../../.env.example)。 + +> [!IMPORTANT] +> 此處記錄的每個變數都必須同時出現在 `.env.example` 中,而 `.env.example` 中的每個變數都必須出現在此處。`npm run check:env-doc-sync` 會在提交時及 CI 中強制執行此規則。若要刻意省略某個變數,請將其加入 `scripts/check/check-env-doc-sync.mjs` 中的允許清單。 --- -> Complete reference for every environment variable recognized by OmniRoute. -> For a quick-start template, see [`.env.example`](../.env.example). +## 目錄 + +- [1. 必要秘密](#1-必要秘密) +- [2. 儲存與資料庫](#2-儲存與資料庫) +- [3. 網路與連接埠](#3-網路與連接埠) +- [4. 安全與驗證](#4-安全與驗證) +- [5. 輸入消毒與 PII 保護](#5-輸入消毒與-pii-保護) +- [6. 工具與路由政策](#6-工具與路由政策) +- [7. URL 與雲端同步](#7-url-與雲端同步) +- [8. 對外代理](#8-對外代理) +- [9. CLI 工具整合](#9-cli-工具整合) +- [10. 內部代理與 MCP 整合](#10-內部代理與-mcp-整合) +- [11. OAuth 提供者憑證](#11-oauth-提供者憑證) +- [12. 提供者 User-Agent 覆寫](#12-提供者-user-agent-覆寫) +- [13. CLI 指紋相容性](#13-cli-指紋相容性) +- [14. API 金鑰提供者](#14-api-金鑰提供者) +- [15. 逾時設定](#15-逾時設定) +- [16. 紀錄](#16-紀錄) +- [17. 記憶體最佳化](#17-記憶體最佳化) +- [18. 定價同步](#18-定價同步) +- [19. 模型同步(開發)](#19-模型同步開發) +- [20. 提供者特定設定](#20-提供者特定設定) +- [21. 代理健康狀態](#21-代理健康狀態) +- [22. 除錯](#22-除錯) +- [23. GitHub 整合](#23-github-整合) +- [24. Skills 沙箱(v3.8.0+)](#24-skills-沙箱-v380) +- [部署情境](#部署情境) +- [稽核:已移除/失效變數](#稽核已移除失效變數) --- -## Table of Contents +## 1. 必要秘密 -- [1. Required Secrets](#1-required-secrets) -- [2. Storage & Database](#2-storage--database) -- [3. Network & Ports](#3-network--ports) -- [4. Security & Authentication](#4-security--authentication) -- [5. Input Sanitization & PII Protection](#5-input-sanitization--pii-protection) -- [6. Tool & Routing Policies](#6-tool--routing-policies) -- [7. URLs & Cloud Sync](#7-urls--cloud-sync) -- [8. Outbound Proxy](#8-outbound-proxy) -- [9. CLI Tool Integration](#9-cli-tool-integration) -- [10. Internal Agent & MCP Integrations](#10-internal-agent--mcp-integrations) -- [11. OAuth Provider Credentials](#11-oauth-provider-credentials) -- [12. Provider User-Agent Overrides](#12-provider-user-agent-overrides) -- [13. CLI Fingerprint Compatibility](#13-cli-fingerprint-compatibility) -- [14. API Key Providers](#14-api-key-providers) -- [15. Timeout Settings](#15-timeout-settings) -- [16. Logging](#16-logging) -- [17. Memory Optimization](#17-memory-optimization) -- [18. Pricing Sync](#18-pricing-sync) -- [19. Model Sync (Dev)](#19-model-sync-dev) -- [20. Provider-Specific Settings](#20-provider-specific-settings) -- [21. Proxy Health](#21-proxy-health) -- [22. Debugging](#22-debugging) -- [23. GitHub Integration](#23-github-integration) -- [Deployment Scenarios](#deployment-scenarios) -- [Audit: Removed / Dead Variables](#audit-removed--dead-variables) +這些**必須**在首次執行前設定。若未設定,應用程式將拒絕啟動或以不安全的預設值運作。 ---- +| 變數 | 必填 | 預設值 | 原始檔 | 說明 | +| -------------------------------- | --------------------- | ----------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **是** | _(無)_ | `src/lib/auth` | 簽署/驗證所有儀表板工作階段 Cookie(JWT)。使用 `openssl rand -base64 48` 產生。 | +| `API_KEY_SECRET` | **是** | _(無)_ | `src/lib/db/apiKeys.ts` | SQLite 中 API 金鑰靜態加密的 AES 加密金鑰。使用 `openssl rand -hex 32` 產生。 | +| `INITIAL_PASSWORD` | **是** | `CHANGEME` | 啟動腳本 | 設定初始管理員儀表板密碼(與 `.env.example` 預設值一致 — 故意保留為明顯不安全的狀態以強制變更)。**首次使用前請變更。** 登入後,可透過儀表板 → 設定 → 安全性變更。 | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **是**(生產環境) | _(未設定)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 內部 Codex Responses WebSocket 橋接的共用秘密。驗證 Electron/瀏覽器 WS 中繼與 OmniRoute 之間的橋接請求。⚠️ **生產環境中為必填 — 若未設定,所有 WS 橋接請求將被拒絕。** 使用 `openssl rand -base64 32` 產生。 | +| `OMNIROUTE_PEER_STAMP_TOKEN` | 否(自動) | _(每次啟動自動產生)_ | `src/server/authz/policies/management.ts` | 用來證明受信任對等 IP 戳記來自 OmniRoute 自身 HTTP 伺服器的每程序秘密(`scripts/dev/peer-stamp.mjs`)。授權中間件僅在戳記攜帶此權杖時才信任請求位置(LOCAL_ONLY 路由的迴路/LAN 閘控)。每次啟動自動產生 — 保持未設定;僅在多程序設定中需共用戳記時才固定。 | -## 1. Required Secrets - -These **must** be set before the first run. Without them, the application will either refuse to start or operate with insecure defaults. - -| Variable | Required | Default | Source File | Description | -| ------------------ | -------- | -------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Yes** | _(none)_ | `src/lib/auth` | Signs/verifies all dashboard session cookies (JWT). Generate with `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Yes** | `123456` | Bootstrap script | Sets the initial admin dashboard password. **Change before first use.** After login, change via Dashboard → Settings → Security. | - -### Generation Commands +### 產生指令 ```bash -# Generate all three secrets at once: +# 一次產生四個秘密: echo "JWT_SECRET=$(openssl rand -base64 48)" echo "API_KEY_SECRET=$(openssl rand -hex 32)" echo "INITIAL_PASSWORD=$(openssl rand -base64 16)" +echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ``` > [!CAUTION] -> Never commit `.env` files with real secrets to version control. The `.gitignore` already excludes `.env`, but verify before pushing. +> 切勿將含有真實秘密的 `.env` 檔案提交至版本控制。`.gitignore` 已排除 `.env`,但請在推送前再次確認。 --- -## 2. Storage & Database +## 2. 儲存與資料庫 -OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These variables control data location, encryption, and lifecycle. +OmniRoute 使用 **SQLite**(經由 `better-sqlite3`)進行所有持久化儲存。這些變數控制資料位置、加密和生命週期。 -| Variable | Default | Source File | Description | -| -------------------------------- | -------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | Root directory for SQLite DB, backups, and data files. Override for Docker volumes or custom paths. | -| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. | -| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. | -| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | When `true`, skips the automatic database backup that runs before migrations on every startup. | -| `OMNIROUTE_CRYPT_KEY` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** for `STORAGE_ENCRYPTION_KEY`. Accepted as a fallback when the primary variable is absent. | -| `OMNIROUTE_API_KEY_BASE64` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** (Base64-encoded form) accepted as a fallback. Decoded automatically before use. | +| 變數 | 預設值 | 原始檔 | 說明 | +| ---------------------------------- | --------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | SQLite 資料庫、備份和資料檔案的根目錄。可為 Docker 磁碟區或自訂路徑進行覆寫。 | +| `STORAGE_ENCRYPTION_KEY` | _(空值 = 停用)_ | `src/lib/db/encryption.ts` | 完整 SQLite 資料庫靜態加密的 AES 金鑰。使用 `openssl rand -hex 32` 產生。 | +| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/build/bootstrap-env.mjs`, `electron/main.js` | 加密金鑰的版本標籤。執行金鑰輪換時請遞增,以支援舊備份的解密。 | +| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | 設為 `true` 時,跳過每次啟動時在遷移前執行的自動資料庫備份。 | +| `OMNIROUTE_CRYPT_KEY` | _(未設定)_ | `src/lib/db/encryption.ts` | **舊版別名**,對應 `STORAGE_ENCRYPTION_KEY`。在主變數不存在時接受為後備。 | +| `OMNIROUTE_API_KEY_BASE64` | _(未設定)_ | `src/lib/db/encryption.ts` | **舊版別名**(Base64 編碼形式),接受為後備。使用前會自動解碼。 | +| `OMNIROUTE_DB_HEALTHCHECK_INTERVAL_MS` | _(未設定)_ | `src/lib/db/core.ts` | 覆寫定期 SQLite 健康檢查間隔(毫秒)。未設定時,預設值衍生自 `NODE_ENV`。 | +| `OMNIROUTE_SKIP_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts`, `src/lib/db/healthCheck.ts` | 設為 `1` 以在啟動時跳過資料庫健康檢查。適用於短期任務和整合測試。 | +| `OMNIROUTE_FORCE_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts` | 設為 `1` 以強制啟用資料庫健康檢查迴圈,即使通常會被跳過(例如短期任務)。 | +| `OMNIROUTE_SKIP_POSTINSTALL` | `0` | `scripts/postinstall.mjs` | 設為 `1` 以在 `npm install` 期間跳過原生執行序暖機。適用於 sqlite 已建置完成的 CI/無頭安裝。 | +| `OMNIROUTE_MIGRATIONS_DIR` | _(自動偵測)_ | `src/lib/db/migrationRunner.ts` | 覆寫遷移執行器掃描的目錄。在自訂建置中打包遷移時很有用。 | +| `OMNIROUTE_MAX_PENDING_MIGRATIONS` | `50` | `src/lib/db/migrationRunner.ts` | 大量待處理遷移安全閥值(#3416)。若現有資料庫的待處理遷移超過此數目,啟動會中止(防止追蹤表被清除)。提高以還原較舊的備份;設為 `0` 可停用檢查。 | +| `OMNIROUTE_SPEND_FLUSH_INTERVAL_MS` | _(程式碼中預設值)_ | `src/lib/spend/batchWriter.ts` | 批次花費/成本寫入器的寫入間隔(毫秒)。較低的值減少寫入合併;較高的值減少資料庫競爭。 | +| `OMNIROUTE_SPEND_MAX_BUFFER_SIZE` | _(程式碼中預設值)_ | `src/lib/spend/batchWriter.ts` | 強制寫入前最大緩衝的花費條目數。在高 QPS 部署中調高;在記憶體優先時調低。 | +| `OMNIROUTE_PROXY_FETCH_DEBUG` | _(未設定)_ | `open-sse/utils/proxyFetch.ts` | 設為 `"true"` 以在 Vercel 中繼路徑上輸出 `[ProxyFetch]` 除錯紀錄。預設關閉以避免洩漏路由提示。 | +| `OMNIROUTE_DEBUG_COMPLETION` | _(未設定)_ | `bin/cli/commands/completion.mjs` | 設為任何非空值以從 CLI shell 補完快取路徑輸出 `[omniroute completion]` 診斷資訊(讀取/重新整理/寫入)。預設關閉 — 這些快取會靜默失敗,因此遺失或損壞的快取不會破壞 Tab 補完。 | +| `BATCH_RETRY_DURATION_MS` | `86400000`(24 小時) | `open-sse/services/batchProcessor.ts` | 個別批次項目的最長重試窗口(毫秒)。超過此時間的項目標記為失敗。 | +| `BATCH_BACKOFF_BASE_MS` | `5000` | `open-sse/services/batchProcessor.ts` | 批次項目重試指數退避的基礎延遲(毫秒)。 | +| `BATCH_BACKOFF_MAX_MS` | `3600000`(1 小時) | `open-sse/services/batchProcessor.ts` | 批次項目重試間指數退避的上限(毫秒)。 | +| `BATCH_MAX_CONCURRENT` | `1` | `open-sse/services/batchProcessor.ts` | 同時處理的最大批次數量。調高以增加吞吐量;保持較低以避免速率限制風暴。 | -### Scenarios +### 情境 -| Scenario | Configuration | -| --------------------- | -------------------------------------------------------------------------------- | -| **Local development** | Leave all defaults. DB lives at `~/.omniroute/omniroute.db`. | -| **Docker** | `DATA_DIR=/data` + mount a volume at `/data`. | -| **Encrypted at rest** | Set `STORAGE_ENCRYPTION_KEY` + keep backups of the key! Losing it = losing data. | -| **CI/Testing** | `DATA_DIR=/tmp/omniroute-test` — ephemeral, no encryption needed. | +| 情境 | 設定 | +| --------------------- | ---------------------------------------------------------------------------------- | +| **本機開發** | 保留所有預設值。資料庫位於 `~/.omniroute/omniroute.db`。 | +| **Docker** | `DATA_DIR=/data` + 在 `/data` 掛載磁碟區。 | +| **靜態加密** | 設定 `STORAGE_ENCRYPTION_KEY` + 備份金鑰!遺失金鑰 = 遺失資料。 | +| **CI/測試** | `DATA_DIR=/tmp/omniroute-test` — 暫時性,無需加密。 | --- -## 3. Network & Ports +## 3. 網路與連接埠 -| Variable | Default | Source File | Description | -| --------------------- | ------------ | -------------------------- | -------------------------------------------------------------------------------------- | -| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). | -| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. | -| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. | -| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. | -| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. | -| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. | -| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. | -| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. | +| 變數 | 預設值 | 原始檔 | 說明 | +| ----------------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PORT` | `20128` | `src/lib/runtime/ports.ts` | 儀表板 UI 和 API 端點的主要連接埠(單連接埠模式)。 | +| `OMNIROUTE_BASE_PATH` | _(空值 = 根路徑)_ | `next.config.mjs` | 在反向代理後以子路徑提供 OmniRoute 服務的 URL 子路徑(設定 Next.js `basePath`;驗證重新導向會感知 basePath)。例如 `/omniroute`。 | +| `API_PORT` | _(未設定)_ | `src/lib/runtime/ports.ts` | 設定時,在此獨立連接埠上提供 `/v1/*` 代理 API。 | +| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | API 連接埠的綁定位址。 | +| `DASHBOARD_PORT` | _(未設定)_ | `src/lib/runtime/ports.ts` | 設定時,在此獨立連接埠上提供儀表板 UI。 | +| `OMNI_MAX_CONCURRENT_CONNECTIONS` | `0` _(停用)_ | `src/sse/utils/backpressure.ts` | 限制同時進行的聊天連線;超過限制的請求會回傳 `503` 和 `Retry-After`。正整數啟用防護;未設定/`0` 停用。 | +| `OMNIROUTE_INSTANCE_ID` | _(未設定)_ | `src/shared/resilience/peerRouting.ts` | 串聯 OmniRoute 實例時此閘道的穩定唯一 ID。啟用入站對等迴圈檢查。允許字元:字母、數字、`.`, `_`, `:`, `-`;最多 64 個字元。 | +| `OMNIROUTE_PEER_URLS` | _(未設定)_ | `src/shared/resilience/peerRouting.ts`, `open-sse/executors/base.ts` | 以逗號分隔的 OmniRoute 基礎 URL,可能接收 `X-OmniRoute-Peer-Trace`。僅明確允許清單中的上游 URL 會收到對等元資料;所有其他提供者不受影響。 | +| `OMNIROUTE_PEER_MAX_HOPS` | `4` | `src/shared/resilience/peerRouting.ts` | 串聯請求接受的最大之前造訪 OmniRoute 實例數(`1`-`32`)。重複的實例或耗盡的預算回傳 HTTP `508 偵測到迴圈`。 | +| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Docker 生產模式中儀表板的主機端發布連接埠。 | +| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Docker 生產模式中 API 的主機端發布連接埠。 | +| `OMNIROUTE_PORT` | _(未設定)_ | `src/lib/runtime/ports.ts` | 在 Electron 或其他包裝器中執行時優先於 `PORT`。 | +| `LIVE_WS_PORT` | `20129` | `src/server/ws/liveServer.ts` | 即時 WebSocket 即時監控伺服器的連接埠。 | +| `LIVE_WS_HOST` | `127.0.0.1` | `src/server/ws/liveServer.ts` | 即時 WebSocket 伺服器的綁定位址。設為 `0.0.0.0` 以在 LAN 上公開(也請設定 `LIVE_WS_ALLOWED_ORIGINS`)。 | +| `LIVE_WS_ALLOWED_ORIGINS` | _(未設定)_ | `src/server/ws/liveServer.ts` | 允許開啟即時 WebSocket 的額外來源(逗號分隔)。迴路儀表板來源已預設允許。 | +| `LIVE_WS_ALLOWED_HOSTS` | _(未設定)_ | `src/server/ws/liveServerAllowList.ts` | 允許用於即時 WebSocket 來源的額外主機名稱(逗號分隔)。與 `LIVE_WS_ALLOWED_ORIGINS`(完整原始 URL)不同,僅比對主機部分 — 適用於 LAN/Tailscale 設定。 | +| `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` | _(未設定)_ | `src/hooks/useLiveDashboard.ts` | 即時儀表板 WebSocket 的公開 URL(瀏覽器端)。當使用反向代理或 Cloudflare Tunnel 前端 WS 伺服器時設定(例如 `wss://ws.my-ai.com/live-ws`);瀏覽器會連接到此處而不是 `ws://hostname:20132`。路徑名稱部分也用作 WebSocket 升級路徑(預設:`/live-ws`)。 | +| `OMNIROUTE_ENABLE_LIVE_WS` | `true` | `src/server/ws/liveServer.ts` 和 `scripts/start-ws-server.mjs` | 設為 `0` 或 `false` 以停用即時 WebSocket 伺服器(預設啟用,綁定至迴路)。CI/測試工具切換,用於停用獨立的即時 WebSocket 輔助腳本。 | +| `RELAY_IP_PER_MINUTE` | `30` | `src/app/api/v1/relay/chat/completions/route.ts` | 每(權杖、IP)中繼速率限制,請求/分鐘。記憶體內,每個實例。`0` 或負數停用 IP 維度閘控(每權杖資料庫限制仍適用)。 | +| `NODE_ENV` | `production` | Next.js 核心 | 控制紀錄詳細程度、快取、錯誤詳細資訊揭露和 Next.js 最佳化。 | +| `OMNIROUTE_USE_TURBOPACK` | `1`(Turbopack — 程式碼預設值) | `package.json` / Next.js 16 | Turbopack 是 `npm run dev` 和 `npm run build` 的預設打包工具(基準測試顯示建置速度快 2-3 倍)。設為 `0` 以在 Windows 上、遇到原生繫結/打包器相容性問題時,**或在記憶體受限的機器上**回退至 webpack — 此 Next.js 版本線(16.2.x)上的 Turbopack 生產建置已知在大型模組圖上的記憶體峰值遠高於 webpack(Next 16.3 的 Turbopack 記憶體回收修復尚未穩定);webpack 回退的峰值低得多。詳見 #6409。 | +| `OMNIROUTE_SKIP_DB_HEALTHCHECK` | _(未設定)_ | `src/lib/db/core.ts` / `src/lib/db/healthCheck.ts` | 設為 `1` 以在啟動時跳過 SQLite 完整性健康檢查。在大型資料庫上加快啟動速度時很有用。 | +| `CREDENTIAL_HEALTH_CHECK_INTERVAL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/scheduler.ts` | 背景憑證健康檢查排程器的間隔(毫秒)。最小值:10000(10 秒)。 | +| `CREDENTIAL_HEALTH_CACHE_TTL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/cache.ts` | 快取憑證健康狀態的 TTL(毫秒)。 | +| `OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK` | `false` | `src/lib/credentialHealth/scheduler.ts` | 設為 `1` 或 `true` 以停用提供者連線的背景定期測試。 | +| `HOST` | `0.0.0.0` | `scripts/dev/run-next.mjs` | Next.js 開發/啟動伺服器的綁定位址。設定時覆寫預設的 `0.0.0.0`。 | +| `HOSTNAME` | `127.0.0.1` | `scripts/dev/run-next-playwright.mjs` | Playwright 執行器啟動 Next.js 時使用的綁定位址。預設為 `127.0.0.1` 以進行封閉測試。**請勿用於 `omniroute serve`** — 請改用 `OMNIROUTE_SERVER_HOST`(POSIX shell 會自動將 `HOSTNAME` 設為機器名稱;`.env` 無法覆蓋它)。 | +| `OMNIROUTE_SERVER_HOST` | `0.0.0.0` | `bin/cli/commands/serve.mjs` | `omniroute serve` 的綁定位址。避免與 POSIX shell `HOSTNAME` 變數衝突(bash/zsh 總是將其設為機器名稱)。未設定時回退至 `0.0.0.0`。(#6194) | -### Port Modes +### 連接埠模式 ``` -┌─────────────────────────── Single Port (default) ──────────────────────────┐ +┌─────────────────────────── 單一連接埠(預設)──────────────────────────┐ │ PORT=20128 │ -│ → Dashboard: http://localhost:20128 │ -│ → API: http://localhost:20128/v1/chat/completions │ +│ → 儀表板:http://localhost:20128 │ +│ → API: http://localhost:20128/v1/chat/completions │ └─────────────────────────────────────────────────────────────────────────────┘ -┌─────────────────────────── Split Ports ─────────────────────────────────────┐ +┌─────────────────────────── 分離連接埠 ─────────────────────────────────────┐ │ DASHBOARD_PORT=20128 │ │ API_PORT=20129 │ │ API_HOST=0.0.0.0 │ -│ → Dashboard: http://localhost:20128 │ -│ → API: http://0.0.0.0:20129/v1/chat/completions │ -│ Use case: Expose API to LAN while restricting Dashboard to localhost. │ +│ → 儀表板:http://localhost:20128 │ +│ → API: http://0.0.0.0:20129/v1/chat/completions │ +│ 使用案例:將 API 公開至 LAN,同時限制儀表板僅限 localhost。 │ └─────────────────────────────────────────────────────────────────────────────┘ -┌─────────────────────────── Docker Production ──────────────────────────────┐ +┌─────────────────────────── Docker 生產 ──────────────────────────────┐ │ PROD_DASHBOARD_PORT=443 PROD_API_PORT=8443 │ -│ → Maps container ports to host ports in docker-compose.prod.yml. │ +│ → 將容器連接埠對應至 docker-compose.prod.yml 中的主機連接埠。 │ └─────────────────────────────────────────────────────────────────────────────┘ ``` --- -## 4. Security & Authentication +## 4. 安全與驗證 -| Variable | Default | Source File | Description | -| ----------------------------- | --------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combined with hardware identifiers for machine fingerprinting. Change per-deployment for isolation. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sets the `Secure` flag on session cookies. **Must be `true`** when running behind HTTPS. | -| `REQUIRE_API_KEY` | `false` | API middleware | When `true`, all `/v1/*` proxy requests must include a valid API key. | -| `ALLOW_API_KEY_REVEAL` | `false` | Dashboard providers page | Allows revealing full API key values in the Dashboard UI. Security risk on shared instances. | -| `NO_LOG_API_KEY_IDS` | _(empty)_ | `src/lib/compliance/index.ts` | Comma-separated API key IDs that bypass request logging (GDPR compliance). | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximum allowed request body size. Rejects payloads exceeding this limit. | -| `CORS_ORIGIN` | `*` | Next.js middleware | CORS `Access-Control-Allow-Origin` value. Restrict for production. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Block provider calls targeting private/loopback/link-local IP ranges. Disable only in isolated test envs. | +| 變數 | 預設值 | 原始檔 | 說明 | +| --------------------------------------- | ------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | 與硬體識別碼結合用於機器指紋識別的鹽值。請為每個部署變更以實現隔離。 | +| `OMNIROUTE_CLI_SALT` | `omniroute-cli-auth-v1` | `src/lib/machineToken.ts` | 用於衍生本機 CLI 驗證權杖的 HMAC 鹽值。變更此值會輪換機器上所有 CLI 權杖。詳見 `docs/security/CLI_TOKEN.md`。 | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | 在工作階段 Cookie 上設定 `Secure` 標記。在 HTTPS 後方執行時**必須設為 `true`**。 | +| `REQUIRE_API_KEY` | `false` | API 中介軟體 | 設為 `true` 時,所有 `/v1/*` 代理請求必須包含有效的 API 金鑰。 | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 允許在儀表板 UI 中顯示完整的 API 金鑰值。可從儀表板功能標誌設定;在共用實例上存在安全風險。 | +| `NO_LOG_API_KEY_IDS` | _(空值)_ | `src/lib/compliance/index.ts` | 以逗號分隔的 API 金鑰 ID,這些金鑰的請求不會被記錄(GDPR 合規)。 | +| `DEFAULT_RATE_LIMIT_PER_DAY` | `1000` | `src/shared/utils/apiKeyPolicy.ts` | 套用至 `rate_limits` 欄位為空的 API 金鑰的每日請求預算後備值。預設(未設定/空值/格式錯誤)保留舊版 1000/天、5000/週、20000/月 的窗口。明確設為 `0` 可選擇退出(無限制)。任何正整數 N 啟用 N/天、5N/週、20N/月。Zod 驗證;無效值會記錄警告並使用舊版預設值。 | +| `MAX_BODY_SIZE_BYTES` | `10485760`(10 MB) | `src/shared/middleware/bodySizeGuard.ts` | 允許的最大請求主體大小。超過此限制的負載將被拒絕。 | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144`(256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | 實際請求主體達到或超過此閥值時,需要在 JSON 解析前取得原子程序本機重量級許可租約。 | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800`(50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | 聊天路由硬性上限,針對有限攝取期間讀取的位元組數強制執行,包括缺少、無效或不誠實的 `Content-Length` 的請求;超過則回傳 `413`。 | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | `1` | `src/shared/middleware/chatBodyAdmission.ts` | 單一程序中同時允許的最大重量級聊天請求。當容量不可用時,OmniRoute 回傳可重試的 `503` 及 `Retry-After`。 | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864`(64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | 非串流上游回應完全緩衝到記憶體中的硬性上限。超過此值時,上游讀取器會被取消,請求快速失敗,而不是增長無界字串直到堆疊耗盡。 | +| `CORS_ORIGIN` | _(未設定)_ | `src/server/cors/origins.ts` | 舊版單一來源 CORS 允許清單。新部署建議使用 `CORS_ALLOWED_ORIGINS`。CORS 僅適用於跨來源瀏覽器 API 用戶端;已驗證的儀表板寫入使用同源請求加上工作階段綁定的 CSRF 保護。 | +| `CORS_ALLOWED_ORIGINS` | _(未設定)_ | `src/server/cors/origins.ts` | 逗號分隔的 CORS 允許清單。除非明確設定 `CORS_ALLOW_ALL=true`,否則不會發送萬用字元。 | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 僅限開發使用的逃逸艙口,會回應任何瀏覽器 `Origin`。請勿在共用或生產部署中啟用。 | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | 封鎖針對私有/迴路/鏈結本機 IP 範圍的提供者呼叫。僅在隔離測試環境中停用。 | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | 允許指向私有/本機網路(localhost、192.168.x.x、10.x.x.x 等)的提供者 URL。**自託管提供者(LM Studio、Ollama、vLLM、Llamafile、Triton、SearXNG)必須設定為 `true`**。設為 `false` 時,儀表板拒絕驗證本機 URL。 | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | 允許在本機/私有位址(127.0.0.1、localhost、LAN、私有範圍)上新增/驗證提供者 — 限定於提供者驗證路徑。**預設 `true`**(本機優先);設為 `false` 以強制執行嚴格僅限公開的封鎖。雲端元資料端點(169.254.169.254、metadata.google.internal)仍保持封鎖。(#5066) | -### Hardening Checklist +### 強化檢查清單 ```bash -# Production security minimum: -AUTH_COOKIE_SECURE=true # Requires HTTPS -REQUIRE_API_KEY=true # Authenticate all proxy calls -ALLOW_API_KEY_REVEAL=false # Never expose keys in UI -CORS_ORIGIN=https://your.domain.com -MAX_BODY_SIZE_BYTES=5242880 # 5 MB limit +# 生產安全性最低要求: +AUTH_COOKIE_SECURE=true # 需要 HTTPS +REQUIRE_API_KEY=true # 驗證所有代理呼叫 +ALLOW_API_KEY_REVEAL=false # 絕不在 UI 中暴露金鑰 +CORS_ALLOWED_ORIGINS=https://your.domain.com +MAX_BODY_SIZE_BYTES=5242880 # 5 MB 限制 ``` --- -## 5. Input Sanitization & PII Protection +## 5. 輸入消毒與 PII 保護 -OmniRoute provides a two-layer defense: request-side injection scanning and response-side PII stripping. +OmniRoute 提供兩層防禦:請求端的注入掃描和回應端的 PII 去除。 -### Request-Side: Prompt Injection Guard +### 請求端:提示注入防護 -| Variable | Default | Source File | Description | -| ------------------------- | --------- | ---------------------------------------- | ------------------------------------------------------------------------------------------- | -| `INPUT_SANITIZER_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | Enable scanning of incoming messages for prompt injection patterns. | -| `INPUT_SANITIZER_MODE` | `warn` | `src/middleware/promptInjectionGuard.ts` | `warn` = log only, `block` = reject request with 400, `redact` = strip suspicious patterns. | -| `INJECTION_GUARD_MODE` | _(unset)_ | `src/middleware/promptInjectionGuard.ts` | Legacy alias for `INPUT_SANITIZER_MODE` — same behavior. | -| `PII_REDACTION_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | Detect PII (emails, phones, SSNs) in incoming requests. | +| 變數 | 預設值 | 原始檔 | 說明 | +| --------------------------- | --------- | ----------------------------------------- | -------------------------------------------------------------------------------------------- | +| `INPUT_SANITIZER_ENABLED` | `true` | `src/middleware/promptInjectionGuard.ts` | 啟用掃描傳入訊息中的提示注入模式。 | +| `INPUT_SANITIZER_MODE` | `warn` | `src/middleware/promptInjectionGuard.ts` | `warn` = 僅記錄,`block` = 以 400 拒絕請求,`redact` = 去除可疑模式。 | +| `INJECTION_GUARD_MODE` | _(未設定)_ | `src/middleware/promptInjectionGuard.ts` | `INPUT_SANITIZER_MODE` 的舊版別名 — 行為相同。 | +| `PII_REDACTION_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | 偵測傳入請求中的 PII(電子郵件、電話、SSN)。 | +| `CREDENTIAL_REDACTION_ENABLED` | `false` | `src/lib/guardrails/credentialMasker.ts` | 從請求/回應負載中去除眾所周知的 API 金鑰/秘密權杖模式。選擇加入;鏡像 `PII_REDACTION_ENABLED`。 | -### Response-Side: PII Sanitizer +### 回應端:PII 消毒器 -| Variable | Default | Source File | Description | -| -------------------------------- | -------- | ------------------------- | ----------------------------------------------------------------------- | -| `PII_RESPONSE_SANITIZATION` | `false` | `src/lib/piiSanitizer.ts` | Scan LLM responses for leaked PII before returning to client. | -| `PII_RESPONSE_SANITIZATION_MODE` | `redact` | `src/lib/piiSanitizer.ts` | `redact` = mask PII, `warn` = log only, `block` = drop entire response. | +| 變數 | 預設值 | 原始檔 | 說明 | +| -------------------------------- | -------- | --------------------------- | -------------------------------------------------------------------- | +| `PII_RESPONSE_SANITIZATION` | `false` | `src/lib/piiSanitizer.ts` | 在回傳給用戶端前掃描 LLM 回應中洩漏的 PII。 | +| `PII_RESPONSE_SANITIZATION_MODE` | `redact` | `src/lib/piiSanitizer.ts` | `redact` = 遮罩 PII,`warn` = 僅記錄,`block` = 丟棄整個回應。 | -### Scenarios +### VS Code 權杖化路由內容消毒器 -| Scenario | Configuration | +| 變數 | 預設值 | 原始檔 | 說明 | +| ----------------------------------- | ------ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `OMNIROUTE_VSCODE_SANITIZE_CONTEXT` | `1` | `src/app/api/v1/vscode/contextSanitizer.ts` | 從 `/v1/vscode/[token]/*` 請求中去除隱含的活躍編輯器內容(`editorContext`、`activeEditor`、`currentFile`、`selection`、`openTabs`…),並編輯明確附加的敏感檔案內容。預設安全;設為 `0` 以停用。 | + +### 情境 + +| 情境 | 設定 | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| **Enterprise compliance** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=block`, `PII_REDACTION_ENABLED=true`, `PII_RESPONSE_SANITIZATION=true` | -| **Monitoring only** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=warn` — logs but never blocks | -| **Personal use** | Leave all disabled — zero overhead | +| **企業合規** | `INPUT_SANITIZER_ENABLED=true`、`INPUT_SANITIZER_MODE=block`、`PII_REDACTION_ENABLED=true`、`PII_RESPONSE_SANITIZATION=true` | +| **僅監控** | `INPUT_SANITIZER_ENABLED=true`、`INPUT_SANITIZER_MODE=warn` — 記錄但從不封鎖 | +| **個人使用** | 全部停用 — 零開銷 | --- -## 6. Tool & Routing Policies +## 6. 工具與路由政策 -| Variable | Default | Source File | Description | -| ------------------ | ---------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `TOOL_POLICY_MODE` | `disabled` | `src/lib/toolPolicy.ts` | Controls LLM tool/function-calling access. `allowlist` = only listed tools, `denylist` = all except listed, `disabled` = no restrictions. | +| 變數 | 預設值 | 原始檔 | 說明 | +| ------------------------------------------------------------ | ------------------------------ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TOOL_POLICY_MODE` | `disabled` | `src/lib/toolPolicy.ts` | 控制 LLM 工具/函式呼叫存取。`allowlist` = 僅列出工具,`denylist` = 除列出者外全部允許,`disabled` = 無限制。 | +| `OMNIROUTE_PAYLOAD_RULES_PATH` | `./config/payloadRules.json` | `open-sse/services/payloadRules.ts` | 負載操作規則 JSON 檔案的路徑(每個模型/協定的上游調整)。 | +| `OMNIROUTE_PAYLOAD_RULES_RELOAD_MS` | `5000` | `open-sse/services/payloadRules.ts` | 熱重新載入負載規則檔案的間隔(毫秒)。最小值 `1000`。 | +| `OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS` | `false` | `open-sse/services/model.ts` | 選擇加入:將來自 Claude Code 用戶端的裸 `claude-*` 模型 ID 路由至 Claude Code OAuth 帳戶,而不需要提供者前綴。明確的提供者前綴仍然優先。也可透過 Claude 提供者頁面上的儀表板切換進行設定。 | --- -## 7. URLs & Cloud Sync +## 7. URL 與雲端同步 -| Variable | Default | Source File | Description | -| ----------------------- | ------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. | -| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). | -| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. | -| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** | -| `NEXT_PUBLIC_CLOUD_URL` | _(empty)_ | Client-side | Client-side mirror of `CLOUD_URL`. | -| `NEXT_PUBLIC_APP_URL` | _(unset)_ | `src/shared/services/cloudSyncScheduler.ts` | Legacy fallback for `NEXT_PUBLIC_BASE_URL`. | +| 變數 | 預設值 | 原始檔 | 說明 | +| ------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | 伺服器端 URL,供內部同步作業呼叫 `/api/sync/cloud`。即使應用程式透過公開代理提供服務,也請保持為迴路/容器 URL。 | +| `CLOUD_URL` | _(空值)_ | `src/lib/cloudSync.ts` | 雲端中繼端點 URL(進階功能)。 | +| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | 雲端同步請求的 HTTP 逾時。 | +| `OMNIROUTE_BUILD_PROFILE` | `full` | Webpack 建置設定 | 建置時設定檔(設為 `minimal` 以從套件中實際排除特權模組)。 | +| `OMNIROUTE_CLOUD_SYNC_SECRET` | _(空值)_ | `src/lib/cloudSync.ts` | 用於驗證 Cloud Sync 回應的 HMAC-SHA256 簽章的共用秘密。 | +| `OMNIROUTE_CLOUD_SYNC_SECRETS` | `false` | `src/lib/cloudSync.ts` | 設為 `true` 以允許 Cloud Sync 端點覆寫本機憑證。預設為 `false`。 | +| `OMNIROUTE_ZED_IMPORT_LEGACY_ONE_STEP` | `false` | `src/app/api/providers/zed/import/route.ts` | 設為 `true` 以回退至 v3.8.5 的單步「匯入全部」行為,無需使用者確認。 | +| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth、儀表板、同步 | 面向公眾的 URL,用於 OAuth redirect_uri、儀表板連結和產生的公開 URL。當 OAuth 回呼或產生的瀏覽器連結需要使用標準反向代理主機時,請設定此為穩定的公開 URL。 | +| `NEXT_PUBLIC_CLOUD_URL` | _(空值)_ | 用戶端 | `CLOUD_URL` 的用戶端鏡像。 | +| `NEXT_PUBLIC_APP_URL` | _(未設定)_ | `src/shared/services/cloudSyncScheduler.ts` | `NEXT_PUBLIC_BASE_URL` 的舊版後備值。 | +| `OMNIROUTE_PUBLIC_BASE_URL` | _(未設定)_ | 公開原始解析器、圖像 URL | 用於公開 URL 產生和非儀表板瀏覽器原始驗證(例如 `/v1/chatgpt-web/image/`)的最高優先級瀏覽器端 OmniRoute 原始。當 OpenWebUI 或其他中繼透過內部 URL 到達 OmniRoute,但使用者的瀏覽器必須從 LAN、通道或公開原始取得圖像時,請設定此值。**請勿**包含 `/v1`。 | +| `OMNIROUTE_PROVIDER_MANIFEST_URL` | _(未設定)_ | `open-sse/config/providerPluginManifestUrl.ts` | 向 Sidecar 用戶端公告的絕對提供者外掛清單 URL。未設定時,OmniRoute 會從請求原始或 HOST/PORT 衍生 `/api/v1/provider-plugin-manifest`。 | +| `OMNIROUTE_PUBLIC_PROTOCOL` | `http` | `open-sse/config/providerPluginManifestUrl.ts` | 在沒有請求原始的情況下從 HOST/PORT 衍生提供者外掛清單 URL 時使用的協定。當未設定明確的 `OMNIROUTE_PROVIDER_MANIFEST_URL` 時,請在 TLS 終止的公開代理後方設為 `https`。 | +| `OMNIROUTE_TRUST_PROXY` | _(未設定)_ | `src/server/origin/publicOrigin.ts` | 用於轉發的公開原始標頭的可選信任模式。未設定 = 不信任 `Forwarded` / `X-Forwarded-*` 進行安全決策。`true` / `loopback` 僅信任來自權杖戳記迴路代理的轉發主機/協定。`private` / `lan` 也信任私有 LAN 代理對等。生產環境中建議使用明確的 `NEXT_PUBLIC_BASE_URL`。 | +| `OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS` | `180000`(3 分鐘) | `open-sse/executors/chatgpt-web.ts` | 非同步 chatgpt-web 圖像透過 celsius WebSocket 到達的最長等待時間。在上游佇列較深的期間調高。 | +| `OMNIROUTE_CGPT_WEB_IMAGE_CACHE_MAX_MB` | `256` | `open-sse/services/chatgptImageCache.ts` | 服務 `/v1/chatgpt-web/image/` 的 chatgpt-web 圖像快取的記憶體內總位元組預算(MB)。在記憶體受限的主機上調低;如果圖像生成量大且用戶端競爭 30 分鐘 TTL,請調高。 | +| `OMNIROUTE_CGPT_WEB_PRO_TIMEOUT_MS` | `1200000`(20 分鐘) | `open-sse/executors/chatgpt-web.ts` | chatgpt-web GPT-5.5 Pro 背景輪詢交接的總等待預算。Pro 推理執行結束帶外完成,因此 OmniRoute 會持續輪詢直到答案到達或此預算用盡。如果 Pro 請求在完成前逾時,請調高。 | +| `OMNIROUTE_CGPT_WEB_PRO_POLL_INTERVAL_MS` | `4000`(4 秒) | `open-sse/executors/chatgpt-web.ts` | chatgpt-web GPT-5.5 Pro 背景輪詢嘗試之間的間隔。調低可獲得更快的完成速度,但會增加上游輪詢量;調高可減少請求量。 | +| `THEOLDLLM_NAV_TIMEOUT_MS` | `30000`(30 秒) | `open-sse/executors/theoldllm.ts` | The Old LLM(theoldllm)免費提供者使用的瀏覽器導航逾時(毫秒)。如果中繼頁面在慢速網路上穩定速度較慢,請調高。 | +| `KIE_CALLBACK_URL` | _(未設定)_ | `open-sse/utils/kieTask.ts` | 非同步 kie.ai 作業的公開回呼 URL。在 `OMNIROUTE_KIE_CALLBACK_URL` 和 `OMNIROUTE_PUBLIC_URL` 之前的最高優先級覆寫。 | +| `OMNIROUTE_KIE_CALLBACK_URL` | _(未設定)_ | `open-sse/utils/kieTask.ts` | `KIE_CALLBACK_URL` 的替代拼寫。當主變數未設定時回退。 | +| `OMNIROUTE_PUBLIC_URL` | _(未設定)_ | `open-sse/utils/kieTask.ts` | 用於組成非同步回呼 URL 的公開原始。kie.ai 回呼的最低優先級後備值;也用作其他中繼的通用公開 URL。 | +| `OMNIROUTE_CROF_USAGE_URL` | `https://crof.ai/usage_api/` | `open-sse/services/usage.ts` | CrofAI 用量查詢端點,用於用量頁面。可為中繼/測試裝置進行覆寫。 | +| `OMNIROUTE_OPENCODE_QUOTA_URL` | `https://opencode.ai/zen/go/v1/quota` | `open-sse/services/opencodeQuotaFetcher.ts` | OpenCode(zen/go)用量查詢端點,用於用量頁面。可為中繼/測試裝置進行覆寫。 | +| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | _(未設定)_ | `open-sse/services/opencodeOllamaUsage.ts` | OpenCode Go 用量查詢端點,用於用量頁面。OpenCode Go 沒有公開的用量 API,因此在運營者選擇加入自託管/鏡像端點之前,此項無預設值且網路呼叫將被跳過。 | +| `OMNIROUTE_OPENCODE_GO_DASHBOARD_URL` | `https://opencode.ai/workspace` | `open-sse/services/usage.ts` | 設定工作區 ID 和驗證 Cookie 時用於用量抓取的 OpenCode Go 儀表板基礎 URL。可為中繼/測試裝置進行覆寫。 | +| `OPENCODE_GO_WORKSPACE_ID` | _(未設定)_ | `open-sse/services/usage.ts` | 用於儀表板用量抓取的 OpenCode Go 工作區 ID。當設定多個帳戶時,建議使用每個連線的儀表板欄位。 | +| `OMNIROUTE_OPENCODE_GO_WORKSPACE_ID` | _(未設定)_ | `open-sse/services/usage.ts` | 在較短別名之前使用的替代 OpenCode Go 工作區 ID 環境變數。當設定多個帳戶時,建議使用每個連線的儀表板欄位。 | +| `OPENCODE_GO_AUTH_COOKIE` | _(未設定)_ | `open-sse/services/usage.ts` | 用於儀表板用量抓取的 OpenCode Go `auth` Cookie。敏感資訊;當設定多個帳戶時,建議使用每個連線的儀表板欄位。 | +| `OPENCODE_SYNTHESIZE_CLI_HEADERS` | `false` | `open-sse/executors/opencode.ts` | 選擇加入:在用戶端未發送的 opencode-go/zen 上游請求上合成 OpenCode CLI 身分標頭(User-Agent、x-opencode-client/project、請求/工作階段 UUID),以便 VPS 出口上的 Cloudflare 接受它們(#6210/#5997)。預設關閉(僅轉發更安全)。 | +| `OPENCODE_USER_AGENT` | `opencode-cli/1.0.0` | `open-sse/executors/opencode.ts` | 當 `OPENCODE_SYNTHESIZE_CLI_HEADERS` 啟用且未設定每個提供者的 `_USER_AGENT` 覆寫時使用的預設 User-Agent。僅套用至 opencode 執行器。 | +| `OPENCODE_CLIENT` | `cli` | `open-sse/executors/opencode.ts` | 當 `OPENCODE_SYNTHESIZE_CLI_HEADERS` 啟用時,用於合成的 `x-opencode-client` 標頭的值。 | +| `OPENCODE_PROJECT` | `default` | `open-sse/executors/opencode.ts` | 當 `OPENCODE_SYNTHESIZE_CLI_HEADERS` 啟用時,用於合成的 `x-opencode-project` 標頭的值。 | +| `OMNIROUTE_OPENCODE_GO_AUTH_COOKIE` | _(未設定)_ | `open-sse/services/usage.ts` | 在較短別名之前使用的替代 OpenCode Go `auth` Cookie 環境變數。敏感資訊;當設定多個帳戶時,建議使用每個連線的儀表板欄位。 | +| `OMNIROUTE_OLLAMA_CLOUD_USAGE_URL` | `https://ollama.com/settings` | `open-sse/services/usage.ts` | 用於用量抓取的 Ollama Cloud 設定 URL。可為中繼/測試裝置進行覆寫。 | +| `OLLAMA_USAGE_COOKIE` | _(未設定)_ | `open-sse/services/usage.ts` | 用於設定頁面用量抓取的 Ollama Cloud `__Secure-session` Cookie。敏感資訊;當設定多個帳戶時,建議使用每個連線的儀表板欄位。 | +| `OLLAMA_CLOUD_USAGE_COOKIE` | _(未設定)_ | `open-sse/services/usage.ts` | 替代的 Ollama Cloud `__Secure-session` Cookie 環境變數。敏感資訊;當設定多個帳戶時,建議使用每個連線的儀表板欄位。 | +| `OMNIROUTE_OLLAMA_USAGE_COOKIE` | _(未設定)_ | `open-sse/services/usage.ts` | 在較短別名之前使用的替代 Ollama Cloud `__Secure-session` Cookie 環境變數。敏感資訊;當設定多個帳戶時,建議使用每個連線的儀表板欄位。 | +| `OMNIROUTE_CODEWHISPERER_BASE_URL` | `https://codewhisperer.us-east-1.amazonaws.com` | `open-sse/services/usage.ts` | CodeWhisperer(AWS Kiro)用量限制端點。可為中繼/測試裝置進行覆寫。 | > [!IMPORTANT] -> When deploying behind a reverse proxy (nginx, Caddy), `NEXT_PUBLIC_BASE_URL` **must** be set to your public URL (e.g., `https://omniroute.example.com`). Without this, OAuth callbacks will fail because the redirect_uri won't match. +> 當在反向代理(nginx、Caddy)後方部署時,如果 OAuth 回呼或產生的公開連結需要使用該主機名稱,請將 `NEXT_PUBLIC_BASE_URL` 設定為穩定的公開 URL(例如 `https://omniroute.example.com`)。否則 OAuth 回呼可能因 redirect_uri 不匹配而失敗,且產生的公開連結可能指向內部容器原始。 +> +> 對於伺服器對伺服器作業,請將 `BASE_URL` 保留為內部迴路/容器 URL。請勿使用瀏覽器 `Origin` 或公開主機名稱來進行含有憑證的內部自我提取。 +> +> 已驗證的儀表板寫入不需要靜態的公開基礎 URL:儀表板會發送帶有工作階段綁定 CSRF 權杖的同源不安全請求。OmniRoute 仍會為非儀表板瀏覽器整合集中化公開原始驗證:先信任明確的公開 URL 環境變數;除非啟用 `OMNIROUTE_TRUST_PROXY` 且直接代理對等已被權杖戳記為受信任,否則原始的 `Forwarded` / `X-Forwarded-*` 標頭將被忽略。請勿使用 CORS 設定來修復同源儀表板請求;CORS 僅適用於跨來源瀏覽器用戶端。 --- -## 8. Outbound Proxy +## 8. 對外代理 -Route upstream LLM provider calls through an HTTP or SOCKS5 proxy for egress control, geo-routing, or IP masking. +透過 HTTP 或 SOCKS5 代理路由上游 LLM 提供者呼叫,以進行出口控制、地理路由或 IP 遮罩。 -| Variable | Default | Source File | Description | -| --------------------------------- | --------- | -------------------- | ----------------------------------------------------------------------------------- | -| `ENABLE_SOCKS5_PROXY` | `true` | `open-sse/executors` | Enable SOCKS5 proxy agent for upstream calls. | -| `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` | `true` | Client-side | Client-side awareness of SOCKS5 availability. | -| `HTTP_PROXY` | _(unset)_ | Node.js standard | HTTP proxy for upstream calls. | -| `HTTPS_PROXY` | _(unset)_ | Node.js standard | HTTPS proxy for upstream calls. | -| `ALL_PROXY` | _(unset)_ | Node.js standard | Universal proxy (supports `socks5://`). | -| `NO_PROXY` | _(unset)_ | Node.js standard | Comma-separated hostnames/IPs to bypass the proxy. | -| `ENABLE_TLS_FINGERPRINT` | `false` | `open-sse/executors` | Spoof TLS fingerprint using wreq-js (mimics Chrome 124). Counters JA3/JA4 blocking. | +| 變數 | 預設值 | 原始檔 | 說明 | +| ------------------------------------------ | ----------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ENABLE_SOCKS5_PROXY` | `true` | `open-sse/executors` | 啟用上游呼叫的 SOCKS5 代理代理。使用 `false` 選擇退出。 | +| `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` | `true` | 用戶端 | 用戶端對 SOCKS5 可用性的感知。 | +| `HTTP_PROXY` | _(未設定)_ | Node.js 標準 | 上游呼叫的 HTTP 代理。 | +| `HTTPS_PROXY` | _(未設定)_ | Node.js 標準 | 上游呼叫的 HTTPS 代理。 | +| `ALL_PROXY` | _(未設定)_ | Node.js 標準 | 通用代理(支援 `socks5://`)。 | +| `NO_PROXY` | _(未設定)_ | Node.js 標準 | 逗號分隔的主機名稱/IP,以繞過代理。 | +| `OMNIROUTE_PROXY_DISPATCHER_CONNECTIONS` | `32` | `open-sse/utils/proxyDispatcher.ts` | 每個快取 HTTP/SOCKS 代理分配器的最大並行 Socket。當多個請求共用相同帳戶層級代理時,像是 Codex `/v1/responses` 這類長壽命的 SSE 串流需要多個連線。超過 `256` 的值將被限制。 | +| `SOCKS_HANDSHAKE_TIMEOUT_MS` | `10000` | `open-sse/utils/socksConnectorWithFamily.ts` | SOCKS5 握手(連線)逾時(毫秒)。當單一住宅閘道主機受到高並發(例如 100 個同時請求)衝擊時調高 — 在飽和的連線池下,即使代理可達,真實握手時間也可能超過 10 秒,否則會顯示為錯誤的 `[Proxy Fast-Fail] Proxy unreachable`。上限為 `120000`。 | +| `PROXY_FAIL_OPEN` | `false` | `src/sse/handlers/chatHelpers.ts` | 設為 `false`(預設)時,分配代理失敗的請求會被**拒絕(關閉失敗)**,而不是回退至直接連線 — 可防止真實 IP 洩漏。設為 `true` 以恢復舊版的 DIRECT 回退。 | +| `ENABLE_TLS_FINGERPRINT` | `false` | `open-sse/executors` | 使用 wreq-js 偽造 TLS 指紋(模仿 Chrome 124)。對抗 JA3/JA4 封鎖。 | +| `OMNIROUTE_TURNSTILE_IGNORE_TLS_ERRORS` | `false` | `open-sse/services/claudeTurnstileSolver.ts` | 允許 Claude Turnstile Playwright 瀏覽器上下文忽略 HTTPS 憑證錯誤。 | -### Scenarios +### 情境 -| Scenario | Configuration | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| **SOCKS5 through SSH tunnel** | `ALL_PROXY=socks5://127.0.0.1:7890`, `ENABLE_SOCKS5_PROXY=true` | -| **Corporate HTTP proxy** | `HTTP_PROXY=http://proxy.corp.com:3128`, `HTTPS_PROXY=http://proxy.corp.com:3128`, `NO_PROXY=localhost,internal.corp.com` | -| **Anti-fingerprint** | `ENABLE_TLS_FINGERPRINT=true` — requires `wreq-js` (included) | +| 情境 | 設定 | +| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| **透過 SSH 隧道的 SOCKS5** | `ALL_PROXY=socks5://127.0.0.1:7890`、`ENABLE_SOCKS5_PROXY=true` | +| **企業 HTTP 代理** | `HTTP_PROXY=http://proxy.corp.com:3128`、`HTTPS_PROXY=http://proxy.corp.com:3128`、`NO_PROXY=localhost,internal.corp.com` | +| **反指紋** | `ENABLE_TLS_FINGERPRINT=true` — 需要 `wreq-js`(已包含) | +| **出口受控/無直接存取** | 保持 `PROXY_FAIL_OPEN=false`(預設)。代理不可用時請求嚴格失敗,不會透過直接連線洩漏。 | +| **舊版/開發 — 允許直接回退** | `PROXY_FAIL_OPEN=true`。恢復強化前行為:代理解析失敗時使用直接連線。 | + +> **注意(NVIDIA 驗證繞過 — #3226):** NVIDIA 的 API 金鑰驗證端點 +> 在透過全域代理/TLS 修補的 fetch(undici 分配器 → 504)路由時會停滯。 +> `src/lib/providers/validation.ts::directHttpsRequest()` 有意使用 +> `safeOutboundFetch({ bypassProxyPatch: true })` 繞過該單一驗證呼叫的代理修補。 +> 這是文件記載的範圍限定例外 — 它**不**影響聊天/用量出口。 +> 此繞過由 `tests/unit/proxy-bypass-scope-guard-3226.test.ts` 進行範圍固定。 --- -## 9. CLI Tool Integration +## 9. CLI 工具整合 -Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex, etc.). +控制 OmniRoute 如何發現和啟動 CLI 附屬程式(Claude Code、Codex 等)。 -| Variable | Default | Source File | Description | -| ------------------------- | ---------- | ----------------------------------- | -------------------------------------------------------------------------- | -| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = search system PATH; `manual` = use explicit paths only. | -| `CLI_EXTRA_PATHS` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Additional PATH entries for CLI binary discovery (colon-separated). | -| `CLI_CONFIG_HOME` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Override home directory for reading CLI configs (`~/.claude`, `~/.codex`). | -| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | Allow OmniRoute to write CLI config files (token refresh, session data). | -| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Custom path to Claude CLI binary. | -| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Custom path to Codex CLI binary. | -| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Custom path to Droid CLI binary. | -| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | Custom path to OpenClaw CLI binary. | -| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Custom path to Cursor agent binary. | -| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Custom path to Cline CLI binary. | -| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Custom path to Continue CLI binary. | -| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. | +| 變數 | 預設值 | 原始檔 | 說明 | +| --------------------------- | ------------ | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = 搜尋系統 PATH;`manual` = 僅使用明確路徑。 | +| `CLI_EXTRA_PATHS` | _(未設定)_ | `src/shared/services/cliRuntime.ts` | 用於 CLI 二進位檔發現的額外 PATH 條目(冒號分隔)。 | +| `CLI_CONFIG_HOME` | _(未設定)_ | `src/shared/services/cliRuntime.ts` | 覆寫用於讀取 CLI 設定檔(`~/.claude`、`~/.codex`)的家目錄。 | +| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | 允許 OmniRoute 寫入 CLI 設定檔(權杖重新整理、工作階段資料)。 | +| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Claude CLI 二進位檔的自訂路徑。 | +| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Codex CLI 二進位檔的自訂路徑。 | +| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Droid CLI 二進位檔的自訂路徑。 | +| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | OpenClaw CLI 二進位檔的自訂路徑。 | +| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Cursor 代理二進位檔的自訂路徑。 | +| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Cline CLI 二進位檔的自訂路徑。 | +| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Continue CLI 二進位檔的自訂路徑。 | +| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Qoder CLI 二進位檔的自訂路徑。 | +| `CLI_QWEN_BIN` | `qwen` | `src/shared/services/cliRuntime.ts` | Qwen Code CLI 二進位檔的自訂路徑。 | +| `CLI_DEVIN_BIN` | `devin` | `open-sse/executors/devin-cli.ts` | Devin CLI 二進位檔的自訂路徑(v3.8.0)。由 Windsurf/Devin 執行器使用。 | +| `AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | 本機 `auggie` 提供者使用的 Augment(Auggie)CLI 二進位檔的絕對路徑覆寫。回退至 `CLI_AUGGIE_BIN`,然後是 PATH 查詢。 | +| `CLI_AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Augment(Auggie)CLI 二進位檔路徑的別名覆寫(在 `AUGGIE_BIN` 之後檢查)。 | +| `HERMES_HOME` | `~/.hermes` | `src/lib/cli-helper/config-generator/hermesHome.ts` | Hermes Agent 家目錄,OmniRoute 在此讀取/寫入 Hermes CLI 設定。與 Hermes PowerShell 安裝程式在 Windows 上設定的環境變數(`%LOCALAPPDATA%\\hermes`)相符。 | -### Docker Example +### CLI 設定檔自動同步 + +這些功能標誌為選擇加入,預設關閉。也可從 CLI Code 儀表板切換。 + +| 變數 | 預設值 | 原始檔 | 說明 | +| --------------------------------------- | ------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 在提供者模型同步後,自動根據即時目錄重新寫入 `~/.codex/*.config.toml` 設定檔。需要 `CLI_ALLOW_CONFIG_WRITES`;絕不變更作用中/預設的 Codex 設定、驗證、Codex-lb 設定或提供者選擇。 | +| `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 在提供者模型同步後,自動根據即時目錄重新寫入 `~/.claude/profiles//settings.json` Claude Code 設定檔。需要 `CLI_ALLOW_CONFIG_WRITES`;絕不變更作用中/預設的 Claude 設定、驗證或提供者選擇。 | + +### Docker 範例 ```bash -# Mount host binaries into the container and tell OmniRoute where they are: +# 將主機二進位檔掛載至容器並告訴 OmniRoute 它們的位置: CLI_EXTRA_PATHS=/host-cli/bin CLI_CONFIG_HOME=/root CLI_ALLOW_CONFIG_WRITES=true CLI_CLAUDE_BIN=/host-cli/bin/claude ``` ---- +### CLI 二進位檔(`omniroute`)輔助工具 -## 10. Internal Agent & MCP Integrations +這些變數調整 `omniroute` CLI 二進位檔自身的行為(非上述的附屬程式偵測)。 -| Variable | Default | Source File | Description | -| --------------------------------------- | ----------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `OMNIROUTE_BASE_URL` | auto-detect | `open-sse/mcp-server/server.ts` | Explicit URL for MCP/A2A tools to reach OmniRoute. Overrides localhost auto-detection. | -| `OMNIROUTE_API_KEY` | _(unset)_ | MCP/A2A modules | API key for internal MCP tool and A2A skill calls. | -| `OMNIROUTE_API_KEY_ID` | _(unset)_ | `open-sse/mcp-server/audit.ts` | Key ID for MCP audit log attribution. | -| `ROUTER_API_KEY` | _(unset)_ | Legacy | Legacy alias for `OMNIROUTE_API_KEY`. | -| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `false` | `open-sse/mcp-server/server.ts` | Enforce scope-based access control on MCP tool calls. | -| `OMNIROUTE_MCP_SCOPES` | _(all)_ | `open-sse/mcp-server/server.ts` | Comma-separated scopes: `admin`, `combos`, `health`, `models`, `routing`, `budget`, `metrics`, `pricing`, `memory`, `skills`. | -| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | Model catalog sync interval in hours. | -| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/server-init.ts` | Provider rate-limit and quota polling interval. | -| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | Disable all background services (sync, pricing, model refresh). Useful for CI/test. | -| `OMNIROUTE_BOOTSTRAPPED` | `false` | `src/app/(dashboard)/dashboard/page.tsx` | Set `true` by bootstrap script after initial setup. Controls setup wizard visibility. | -| `OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE` | `0` | `open-sse/executors/antigravity.ts` | Escape hatch: allow request body to override the Antigravity project field. | - -### OAuth CLI Bridge (Internal) - -| Variable | Default | Source File | Description | -| ------------------- | ----------- | ------------------------------- | ----------------------------------------- | -| `OMNIROUTE_SERVER` | auto-detect | `src/lib/oauth/config/index.ts` | Server URL for CLI↔OmniRoute auth bridge. | -| `OMNIROUTE_TOKEN` | _(unset)_ | `src/lib/oauth/config/index.ts` | Auth token for CLI bridge. | -| `OMNIROUTE_USER_ID` | `cli` | `src/lib/oauth/config/index.ts` | User ID for CLI bridge sessions. | -| `SERVER_URL` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_SERVER`. | -| `CLI_TOKEN` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_TOKEN`. | -| `CLI_USER_ID` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_USER_ID`. | +| 變數 | 預設值 | 原始檔 | 說明 | +| -------------------------------- | ------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `OMNIROUTE_LANG` | _(系統)_ | `bin/cli/i18n.mjs` | 強制 CLI 輸出語言。BCP-47 地區設定(例如 `en`、`pt-BR`)。覆寫系統地區設定環境變數(LC_ALL、LC_MESSAGES)。 | +| `OMNIROUTE_SHOW_LOG` | _(未設定)_ | `bin/cli/runtime/processSupervisor.mjs` | 設為 `1` 以在監督模式下將伺服器 stdout/stderr 轉發至終端機。等同於 `omniroute serve` 的 `--log` 標記。 | +| `OMNIROUTE_CLI_TOKEN` | _(未設定)_ | `bin/cli/api.mjs` | 作為 `x-omniroute-cli-token` 標頭注入的機器驗證權杖。在工作 8.12 中自動產生。 | +| `OMNIROUTE_HTTP_TIMEOUT_MS` | `30000` | `bin/cli/api.mjs` | CLI → 伺服器請求的每次嘗試 HTTP 逾時(毫秒)。 | +| `OMNIROUTE_VERBOSE` | `0` | `bin/cli/api.mjs` | 設為 `1` 以在 CLI 指令期間將重試/退避診斷資訊列印至 stderr。 | +| `OMNIROUTE_PLUGIN_PATH` | _(未設定)_ | `bin/cli/plugins.mjs` | CLI 外掛發現的自訂目錄(`omniroute-cmd-*` 套件)。未設定時預設為 `~/.omniroute/plugins/`。 | +| `OMNIROUTE_PLUGINS_ALLOW_EXEC` | `0` | `src/lib/plugins/pluginWorker.ts` | 設為 `1` 以允許外掛請求 `exec` 權限(從工作者沙箱產生子程序)。僅限本機運營者。 | --- -## 11. OAuth Provider Credentials +## 10. 內部代理與 MCP 整合 -Built-in credentials for **localhost development**. For remote deployments, register your own at each provider's developer console. +| 變數 | 預設值 | 原始檔 | 說明 | +| ------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `OMNIROUTE_BASE_URL` | 自動偵測 | `open-sse/mcp-server/server.ts` | MCP/A2A 工具用來連線 OmniRoute 的明確 URL。覆寫 localhost 自動偵測。 | +| `OMNIROUTE_API_KEY` | _(未設定)_ | MCP/A2A 模組 | 內部 MCP 工具和 A2A 技能呼叫的 API 金鑰。 | +| `OMNIROUTE_API_KEY_ID` | _(未設定)_ | `open-sse/mcp-server/audit.ts` | 用於 MCP 稽核紀錄歸因的金鑰 ID。 | +| `ROUTER_API_KEY` | _(未設定)_ | 舊版 | `OMNIROUTE_API_KEY` 的舊版別名。 | +| `OMNIROUTE_ISSUE_AGENT_ENABLED` | `false` | `src/app/api/issue-agent/runs/route.ts` | 啟用離線/本機 Issue Agent 記錄式分類。除非明確執行本機記錄式分類工作流程,否則請保持停用。 | +| `OMNIROUTE_ISSUE_AGENT_TIMEOUT_MS` | _(未設定)_ | `src/lib/issueAgent/execution.ts` | 單次 Issue Agent 記錄式分類執行的逾時(毫秒)。限制在內部最大值內;未設定或無效時回退至內建預設值。 | +| `OMNIROUTE_CONTEXT` | _(活躍上下文)_ | `bin/cli/program.mjs`、`bin/cli/api.mjs` | CLI 遠端模式的 `omniroute` 指令上下文/設定檔;覆寫本機上下文儲存中的活躍上下文。等同於 `--context `。 | +| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `true` | `open-sse/mcp-server/server.ts` | 對 MCP 工具呼叫強制執行基於範圍的存取控制。 | +| `OMNIROUTE_MCP_SCOPES` | _(全部)_ | `open-sse/mcp-server/server.ts` | 逗號分隔的範圍:`admin`、`combos`、`health`、`models`、`routing`、`budget`、`metrics`、`pricing`、`memory`、`skills`。 | +| `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | `false` | `open-sse/mcp-server/descriptionCompressor.ts` | 在序列化清單前壓縮 MCP 工具描述。啟用值:`1`、`true`、`on`。 | +| `OMNIROUTE_MCP_DESCRIPTION_COMPRESSION` | `rtk` | `open-sse/mcp-server/descriptionCompressor.ts` | 壓縮演算法/設定檔。停用值:`0`、`false`、`off`。 | +| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | 模型目錄同步間隔(小時)。 | +| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/server-init.ts` | 提供者速率限制和配額輪詢間隔。 | +| `PROVIDER_LIMITS_SYNC_SPACING_MS` | `1500` | `src/lib/usage/providerLimits.ts` | 大量同步中連續 OAuth 配額擷取之間的間隔(毫秒);OAuth 連線一次擷取一個,以避免突發上游。`0` 表示選擇退出(並發)。 | +| `OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS` | `250` | `open-sse/services/quotaFetchThrottle.ts` | 每個請求預檢/監控路徑上連續上游配額擷取之間的最小間隔(毫秒);分散並發網路呼叫,使同一 IP 上的多個帳戶不會突發上游。連接到 Codex(`/wham/usage`)、DeepSeek、Bailian(兩個擷取站點)、OpenCode 和 Crof 配額擷取器(#6009、#6911)。通用的 `usage.ts::getUsageForProvider` 分發路徑(github/glm/minimax/nanogpt/xai/等)尚未涵蓋 — 另行追蹤。快取命中不受影響。`0` 停用;限制在 `0..5000`。 | +| `PROVIDER_LIMITS_POST_USAGE_REFRESH_DELAY_MS` | `5000` | `src/lib/usage/providerLimits.ts` | 在實際用量事件後重新整理提供者限制前的延遲(毫秒),給上游配額 API 時間來記錄消耗。 | +| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | 停用所有背景服務(同步、定價、模型重新整理)。適用於 CI/測試。 | +| `OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS` | _(未設定)_ | `src/lib/config/runtimeSettings.ts` | 在自動化測試偵測下強制啟用背景任務。設為 `1` 以覆寫測試啟發式。 | +| `OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS` | `600000` | `src/lib/jobs/budgetResetJob.ts` | 預算重設檢查頻率(毫秒)。下限 `10000`。 | +| `OMNIROUTE_CONNECTION_RECOVERY_INTERVAL_MS` | `60000` | `src/lib/quota/connectionRecovery.ts` | 主動連線冷卻恢復頻率(毫秒):重新驗證其短暫 `rate_limited_until` 已過期的連線,在請求熱路徑之外。下限 `5000`。 | +| `OMNIROUTE_DISABLE_CONNECTION_RECOVERY` | `false` | `src/lib/quota/connectionRecovery.ts` | 停用主動連線冷卻恢復排程器(`getProviderCredentials` 中的懶式恢復仍然適用)。 | +| `OMNIROUTE_REASONING_CACHE_CLEANUP_INTERVAL_MS` | `1800000` | `src/lib/jobs/reasoningCacheCleanupJob.ts` | 推理快取清理頻率(毫秒)。下限 `60000`。 | +| `OMNIROUTE_CONFIG_HOT_RELOAD_MS` | `5000` | `src/lib/config/hotReload.ts` | 設定熱重新載入的輪詢間隔(毫秒)。低於 `1000` 將被拒絕。 | +| `OMNIROUTE_DISABLE_REDIS_AUTH_CACHE` | _(啟用中)_ | `src/lib/db/apiKeys.ts` | 設為 `1` 以繞過 Redis 支援的 API 金鑰驗證快取(強制資料庫讀取)。 | +| `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | `0` | `open-sse/services/compression/engines/rtk/filterLoader.ts` | 信任使用者管理的 RTK 專案過濾規則,無需嚴格的簽章檢查。 | +| `COMPRESSION_PIPELINE_BREAKER_ENABLED` | `false` | `open-sse/services/compression/pipelineEngineBreaker.ts` | T02 堆疊管線每個引擎的斷路器主開關。**選擇加入(預設關閉)** — 啟用時,跨請求重複拋出的引擎會跳過(開放失敗)進行冷卻;關閉時 = 位元組相同的舊版行為。 | +| `COMPRESSION_PIPELINE_BREAKER_THRESHOLD` | `3` | `open-sse/services/compression/pipelineEngineBreaker.ts` | 在引擎的斷路器開啟前連續跨請求失敗次數。 | +| `COMPRESSION_PIPELINE_BREAKER_COOLDOWN_MS` | `30000` | `open-sse/services/compression/pipelineEngineBreaker.ts` | 已開啟引擎在進行半開探測前保持跳過的毫秒數。 | +| `COMPRESSION_CCR_RETRIEVAL_RAMP_FACTOR` | `2` | `open-sse/services/compression/engines/ccr/index.ts` | T08/H8 CCR 檢索回饋斜坡:儲存區塊的每次先前檢索都會線性提高其有效 `minChars`(經常檢索的內容壓縮較少;`>=3` 次檢索 = 永不壓縮)。`1` 停用斜坡(僅在閥值處進行二進位跳過)。 | +| `COMPRESSION_PREFIX_FREEZE_ENABLED` | `false` | `open-sse/services/compression/prefixFreeze.ts` | T08/H5 用量觀察前綴凍結主開關。**選擇加入(預設關閉)** — 啟用時,觀察到 `>=` 閥值的系統提示被視為穩定的可快取前綴,並從壓縮中保留,即使靜態快取啟發式遺漏(凍結僅*保留*,絕不變異)。 | +| `COMPRESSION_PREFIX_FREEZE_THRESHOLD` | `3` | `open-sse/services/compression/prefixFreeze.ts` | 系統提示在被視為凍結的穩定前綴之前的觀察次數。 | +| `OMNIROUTE_BOOTSTRAPPED` | `false` | `src/app/(dashboard)/dashboard/page.tsx` | 由啟動腳本在初始設定後設為 `true`。控制設定精靈的可見性。 | +| `OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE` | `0` | `open-sse/executors/antigravity.ts` | 逃逸艙口:允許請求主體覆寫 Antigravity 專案欄位。 | +| `ANTIGRAVITY_CREDITS` | _(未設定)_ | `open-sse/services/antigravityCredits.ts` | 覆寫 Antigravity 公告的剩餘點數(測試/強制值)。 | +| `AGY_TOKEN_FILE` | `~/.gemini/antigravity-cli/antigravity-oauth-token` | `src/app/api/providers/agy-auth/apply-local/route.ts` | 覆寫自動偵測本機登入匯入的 Antigravity CLI(agy)權杖檔案路徑。 | -| Variable | Provider | Notes | -| --------------------------------- | ----------------------- | --------------------------------------------------------------------------------- | -| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | Public client — no secret needed. | -| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | Override redirect URI. Default: `https://platform.claude.com/oauth/code/callback` | -| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | Public client. | -| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | Requires matching `_SECRET`. | -| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — | -| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | Public client. | -| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | Public client. | -| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | Requires matching `_SECRET`. | -| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — | -| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | Public client. | -| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — | -| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | Set to enable Qoder OAuth. | -| `QODER_OAUTH_TOKEN_URL` | Qoder | — | -| `QODER_OAUTH_USERINFO_URL` | Qoder | — | -| `QODER_OAUTH_CLIENT_ID` | Qoder | — | -| `QODER_PERSONAL_ACCESS_TOKEN` | Qoder | Direct API key fallback (bypasses OAuth). | -| `QODER_CLI_WORKSPACE` | Qoder | Workspace ID for Qoder CLI. | -| `OMNIROUTE_QODER_WORKSPACE` | Qoder | Alias for `QODER_CLI_WORKSPACE`. | +### OAuth CLI 橋接(內部) -> [!WARNING] -> -> 1. Go to [Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials) -> 2. Create an OAuth 2.0 Client ID (type: "Web application") -> 3. Add your server URL as Authorized redirect URI -> 4. Replace the credential values in `.env`. +| 變數 | 預設值 | 原始檔 | 說明 | +| --------------------- | ------------ | --------------------------------- | --------------------------------------- | +| `OMNIROUTE_SERVER` | 自動偵測 | `src/lib/oauth/config/index.ts` | CLI↔OmniRoute 驗證橋接的伺服器 URL。 | +| `OMNIROUTE_TOKEN` | _(未設定)_ | `src/lib/oauth/config/index.ts` | CLI 橋接的驗證權杖。 | +| `OMNIROUTE_USER_ID` | `cli` | `src/lib/oauth/config/index.ts` | CLI 橋接工作階段的使用者 ID。 | +| `SERVER_URL` | _(未設定)_ | `src/lib/oauth/config/index.ts` | `OMNIROUTE_SERVER` 的舊版別名。 | +| `CLI_TOKEN` | _(未設定)_ | `src/lib/oauth/config/index.ts` | `OMNIROUTE_TOKEN` 的舊版別名。 | +| `CLI_USER_ID` | _(未設定)_ | `src/lib/oauth/config/index.ts` | `OMNIROUTE_USER_ID` 的舊版別名。 | --- -## 12. Provider User-Agent Overrides +## 11. OAuth 提供者憑證 -Override the `User-Agent` header sent to each upstream provider. This is dynamically resolved at runtime by the executor base class: - -``` -process.env[`${PROVIDER_ID}_USER_AGENT`] -``` - -> **Source:** `open-sse/executors/base.ts` → `buildHeaders()` - -| Variable | Default Value | When to Update | -| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | -| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | -| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | -| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | -| `ANTIGRAVITY_USER_AGENT` | `antigravity/2.0.1 darwin/arm64` | When Antigravity IDE updates | -| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | When Kiro IDE updates | -| `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates | -| `QWEN_USER_AGENT` | `QwenCode/0.15.11 (linux; x64)` | When Qwen Code updates | -| `CURSOR_USER_AGENT` | `connect-es/1.6.1` | When Cursor updates | - -> [!TIP] -> You can add User-Agent overrides for **any** provider using the pattern `{PROVIDER_ID}_USER_AGENT`. The executor dynamically constructs the env var name. - ---- - -## 13. CLI Fingerprint Compatibility - -When enabled, OmniRoute reorders HTTP headers and JSON body fields to match the exact signature of official CLI tools. This reduces the risk of account flagging while preserving your proxy IP. - -**Source:** `open-sse/config/cliFingerprints.ts`, `open-sse/executors/base.ts` - -### Per-Provider - -| Variable | Effect | -| -------------------------- | --------------------------------------- | -| `CLI_COMPAT_CODEX=1` | Mimics Codex CLI request signature | -| `CLI_COMPAT_CLAUDE=1` | Mimics Claude Code request signature | -| `CLI_COMPAT_GITHUB=1` | Mimics GitHub Copilot request signature | -| `CLI_COMPAT_ANTIGRAVITY=1` | Mimics Antigravity request signature | -| `CLI_COMPAT_KIRO=1` | Mimics Kiro IDE request signature | -| `CLI_COMPAT_CURSOR=1` | Mimics Cursor request signature | -| `CLI_COMPAT_KIMI_CODING=1` | Mimics Kimi Coding request signature | -| `CLI_COMPAT_KILOCODE=1` | Mimics Kilo Code request signature | -| `CLI_COMPAT_CLINE=1` | Mimics Cline request signature | -| `CLI_COMPAT_QWEN=1` | Mimics Qwen Code request signature | - -### Global - -| Variable | Effect | -| ------------------ | --------------------------------------------------------------- | -| `CLI_COMPAT_ALL=1` | Enable fingerprint compatibility for **all** providers at once. | - -> [!NOTE] -> This feature works alongside the User-Agent overrides (§12). The fingerprint system handles header ordering and body field ordering, while User-Agent overrides handle the specific UA string. Both can be enabled independently. - ---- - -## 14. API Key Providers - -API keys for providers that use direct authentication. **Preferred setup:** Dashboard → Providers → Add API Key. - -Setting via environment variables is an alternative for Docker or headless deployments. - -Recognized pattern: `{PROVIDER_ID}_API_KEY` - -| Variable | Provider | -| -------------------- | ------------------- | -| `DEEPSEEK_API_KEY` | DeepSeek | -| `GROQ_API_KEY` | Groq | -| `XAI_API_KEY` | xAI (Grok) | -| `MISTRAL_API_KEY` | Mistral AI | -| `PERPLEXITY_API_KEY` | Perplexity | -| `TOGETHER_API_KEY` | Together AI | -| `FIREWORKS_API_KEY` | Fireworks AI | -| `CEREBRAS_API_KEY` | Cerebras | -| `COHERE_API_KEY` | Cohere | -| `NVIDIA_API_KEY` | NVIDIA NIM | -| `NEBIUS_API_KEY` | Nebius (embeddings) | - -> [!TIP] -> Keys set via the Dashboard are stored encrypted in SQLite and take precedence over environment variables. - ---- - -## 15. Timeout Settings - -All values are in **milliseconds**. Centralized resolution in `src/shared/utils/runtimeTimeouts.ts`. - -### Timeout Hierarchy - -``` -REQUEST_TIMEOUT_MS (global override) -├─→ FETCH_TIMEOUT_MS (upstream provider calls, default: 600000) -│ ├─→ FETCH_HEADERS_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS) -│ ├─→ FETCH_BODY_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS) -│ ├─→ TLS_CLIENT_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS) -│ ├── FETCH_CONNECT_TIMEOUT_MS (independent, default: 30000) -│ └── FETCH_KEEPALIVE_TIMEOUT_MS (independent, default: 4000) -├─→ STREAM_IDLE_TIMEOUT_MS (inherits from REQUEST_TIMEOUT_MS, default: 600000) -└─→ API_BRIDGE_PROXY_TIMEOUT_MS (inherits from REQUEST_TIMEOUT_MS, default: 600000) - ├─→ API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS (derived, default: 600000) - ├── API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS (default: 60000) - ├── API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS (default: 5000) - └── API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS (default: 0 = disabled) -``` - -| Variable | Default | Description | -| ---------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------- | -| `REQUEST_TIMEOUT_MS` | _(unset)_ | Global shortcut — overrides both `FETCH_TIMEOUT_MS` and `STREAM_IDLE_TIMEOUT_MS` defaults. | -| `FETCH_TIMEOUT_MS` | `600000` | Total HTTP request timeout for upstream provider calls. | -| `STREAM_IDLE_TIMEOUT_MS` | `600000` | Max silence between SSE chunks before aborting. Extended-thinking models rarely pause >90s. | -| `FETCH_HEADERS_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive response headers. | -| `FETCH_BODY_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive the full response body. | -| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | TCP connection establishment timeout. | -| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Keep-alive socket idle timeout. | -| `TLS_CLIENT_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | TLS fingerprint proxy (wreq-js) timeout. | -| `API_BRIDGE_PROXY_TIMEOUT_MS` | `600000` | Proxy hop timeout for `/v1` bridge requests. | -| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `600000` | Overall server request timeout for the bridge. | -| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | Time to send response headers via the bridge. | -| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Bridge keep-alive idle timeout. | -| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | Raw socket timeout (0 = disabled). | -| `SHUTDOWN_TIMEOUT_MS` | `30000` | Grace period on SIGTERM/SIGINT before force-exit. | - -### Scenarios - -| Scenario | Configuration | -| -------------------------------- | ------------------------------------------------------ | -| **Long-running code generation** | `REQUEST_TIMEOUT_MS=900000` (15 min) | -| **Fast-fail for production API** | `API_BRIDGE_PROXY_TIMEOUT_MS=10000` | -| **Extended thinking models** | `STREAM_IDLE_TIMEOUT_MS=300000` (5 min between chunks) | - ---- - -## 16. Logging - -The logging system writes to both stdout and rotated log files. All configuration is read by `src/lib/logEnv.ts`. - -| Variable | Default | Description | -| --------------------------- | -------------------------- | ---------------------------------------------------------------------------- | -| `APP_LOG_LEVEL` | `info` | Minimum log level: `debug`, `info`, `warn`, `error`. | -| `APP_LOG_FORMAT` | `text` | Output format: `text` (human-readable) or `json` (structured). | -| `APP_LOG_TO_FILE` | `true` | Write logs to file alongside stdout. | -| `APP_LOG_FILE_PATH` | `logs/application/app.log` | Log file path (relative to project root or `DATA_DIR`). | -| `APP_LOG_MAX_FILE_SIZE` | `50M` | Max file size before rotation. Accepts: `50M`, `1G`, `512K`, or plain bytes. | -| `APP_LOG_RETENTION_DAYS` | `7` | Days to keep rotated application log files. | -| `APP_LOG_MAX_FILES` | `20` | Maximum rotated log file backups. | -| `CALL_LOG_RETENTION_DAYS` | `7` | Days to keep request/call log entries in the database. | -| `CALL_LOG_MAX_ENTRIES` | `10000` | Max call log entries in the in-memory buffer. | -| `CALL_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `call_logs` SQLite table before pruning. | -| `PROXY_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `proxy_logs` SQLite table before pruning. | - ---- - -## 17. Memory Optimization - -| Variable | Default | Description | -| -------------------------- | ------------------------------- | ---------------------------------------------------------------------- | -| `OMNIROUTE_MEMORY_MB` | `512` | Runtime V8 heap limit. Docker standalone and `omniroute serve` use it to set `--max-old-space-size`. | -| `PROMPT_CACHE_MAX_SIZE` | `50` | Max cached system prompt entries. | -| `PROMPT_CACHE_MAX_BYTES` | `2097152` (2 MB) | Max total prompt cache size. | -| `PROMPT_CACHE_TTL_MS` | `300000` (5 min) | Prompt cache entry TTL. | -| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max cached temperature=0 responses. | -| `SEMANTIC_CACHE_MAX_BYTES` | `4194304` (4 MB) | Max total semantic cache size. | -| `SEMANTIC_CACHE_TTL_MS` | `1800000` (30 min) | Semantic cache entry TTL. | -| `STREAM_HISTORY_MAX` | `50` | Max recent stream events in the Dashboard live view buffer. | -| `CONTEXT_LENGTH_DEFAULT` | `128000` | Global fallback max context length for models without explicit config. | -| `USAGE_TOKEN_BUFFER` | `100` | Extra token headroom reserved when tracking usage quotas. | - -### Low-RAM Docker Example - -```bash -OMNIROUTE_MEMORY_MB=128 -PROMPT_CACHE_MAX_SIZE=20 -PROMPT_CACHE_MAX_BYTES=524288 # 512 KB -SEMANTIC_CACHE_MAX_SIZE=25 -SEMANTIC_CACHE_MAX_BYTES=1048576 # 1 MB -STREAM_HISTORY_MAX=10 -``` - ---- - -## 18. Pricing Sync - -Automatic model pricing data synchronization from external sources. - -| Variable | Default | Source File | Description | -| ----------------------- | ------------- | ------------------------ | ----------------------------- | -| `PRICING_SYNC_ENABLED` | `false` | `src/lib/pricingSync.ts` | Opt-in periodic pricing sync. | -| `PRICING_SYNC_INTERVAL` | `86400` (24h) | `src/lib/pricingSync.ts` | Sync interval in seconds. | -| `PRICING_SYNC_SOURCES` | `litellm` | `src/lib/pricingSync.ts` | Comma-separated data sources. | - ---- - -## 19. Model Sync (Dev) - -| Variable | Default | Source File | Description | -| -------------------------- | ------------- | -------------------------- | -------------------------------------------------------- | -| `MODELS_DEV_SYNC_INTERVAL` | `86400` (24h) | `src/lib/modelsDevSync.ts` | Development-time model catalog sync interval in seconds. | - ---- - -## 20. Provider-Specific Settings - -| Variable | Default | Source File | Description | -| ----------------------------------------- | ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------- | -| `OPENROUTER_CATALOG_TTL_MS` | `86400000` (24h) | `src/lib/catalog/openrouterCatalog.ts` | OpenRouter model catalog cache TTL. | -| `NANOBANANA_POLL_TIMEOUT_MS` | `120000` | `open-sse/handlers/imageGeneration.ts` | Max wait for NanoBanana image generation jobs. | -| `NANOBANANA_POLL_INTERVAL_MS` | `2500` | `open-sse/handlers/imageGeneration.ts` | NanoBanana job polling frequency. | -| `CLOUDFLARE_ACCOUNT_ID` | _(unset)_ | `open-sse/executors/cloudflare-ai.ts` | Account ID for Cloudflare Workers AI. | -| `CLOUDFLARED_BIN` | auto-detect | `src/lib/cloudflaredTunnel.ts` | Custom path to `cloudflared` binary. | -| `SEARCH_CACHE_TTL_MS` | `300000` (5 min) | `open-sse/services/searchCache.ts` | TTL for search API (Perplexity, Brave, etc.) response caching. | -| `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` | `false` | `src/app/api/providers/route.ts` | Allow multiple simultaneous connections per OpenAI-compatible provider. | -| `ENABLE_CC_COMPATIBLE_PROVIDER` | `false` | `src/shared/utils/featureFlags.ts` | Enable experimental Claude Code compatible provider endpoint. | -| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge host (legacy integration). | -| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge port. | -| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI config directory. | -| `LOCAL_HOSTNAMES` | _(empty)_ | `open-sse/config/providerRegistry.ts` | Comma-separated additional hostnames treated as "local" (Docker service names, etc.). | - ---- - -## 21. Proxy Health - -| Variable | Default | Source File | Description | -| ---------------------------- | ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. | -| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. | -| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. | -| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. | -| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. | - ---- - -## 22. Debugging - -> [!CAUTION] -> These variables produce **verbose output** and may leak sensitive data. **Never enable in production.** - -| Variable | Default | Source File | Description | -| -------------------------------- | --------- | ----------------------------------------- | -------------------------------------------------------------- | -| `CURSOR_PROTOBUF_DEBUG` | _(unset)_ | `open-sse/utils/cursorProtobuf.ts` | Set `1` to dump Cursor protobuf decode/encode details. | -| `CURSOR_STREAM_DEBUG` | _(unset)_ | `open-sse/executors/cursor.ts` | Set `1` to dump raw Cursor SSE stream data. | -| `DEBUG_RESPONSES_SSE_TO_JSON` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to log Responses API SSE→JSON translation details. | -| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(unset)_ | E2E test harness | Set `true` to enable E2E test mode (relaxed auth, test hooks). | - ---- - -## 23. GitHub Integration - -Allow users to report issues directly from the Dashboard. - -| Variable | Default | Source File | Description | -| --------------------- | --------- | --------------------------------------- | ------------------------------------------------------- | -| `GITHUB_ISSUES_REPO` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | Repository in `owner/repo` format. | -| `GITHUB_ISSUES_TOKEN` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | GitHub Personal Access Token with `issues:write` scope. | - ---- - -## Deployment Scenarios - -### Minimal Local Development - -```bash -JWT_SECRET=$(openssl rand -base64 48) -API_KEY_SECRET=$(openssl rand -hex 32) -INITIAL_PASSWORD=dev123 -PORT=20128 -NODE_ENV=development -``` - -### Docker Production - -```bash -JWT_SECRET= -API_KEY_SECRET= -INITIAL_PASSWORD= -STORAGE_ENCRYPTION_KEY= -DATA_DIR=/data -PORT=20128 -API_PORT=20129 -NODE_ENV=production -AUTH_COOKIE_SECURE=true -REQUIRE_API_KEY=true -NEXT_PUBLIC_BASE_URL=https://omniroute.example.com -BASE_URL=http://localhost:20128 -OMNIROUTE_MEMORY_MB=512 -CORS_ORIGIN=https://your-frontend.example.com -``` - -### Air-Gapped / CI - -```bash -JWT_SECRET=test-jwt-secret-for-ci -API_KEY_SECRET=test-api-key-secret-for-ci -INITIAL_PASSWORD=testpass -NODE_ENV=production -OMNIROUTE_DISABLE_BACKGROUND_SERVICES=true -APP_LOG_TO_FILE=false -``` - -### VPS with Reverse Proxy (nginx + Cloudflare) - -```bash -JWT_SECRET= -API_KEY_SECRET= -STORAGE_ENCRYPTION_KEY= -PORT=20128 -AUTH_COOKIE_SECURE=true -REQUIRE_API_KEY=true -NEXT_PUBLIC_BASE_URL=https://omniroute.example.com -BASE_URL=http://127.0.0.1:20128 -CORS_ORIGIN=https://omniroute.example.com -ENABLE_TLS_FINGERPRINT=true -CLI_COMPAT_ALL=1 -``` - ---- - -## Audit: Removed / Dead Variables - -The following variables appeared in previous versions of `.env.example` but have **no runtime references** in the current codebase. They have been removed: - -| Variable | Reason | -| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| `STORAGE_DRIVER=sqlite` | Never read by any source file. SQLite is the only supported driver — no selection needed. | -| `INSTANCE_NAME=omniroute` | Present in old docs/env templates but unused at runtime. May return in a future multi-instance feature. | -| `SQLITE_MAX_SIZE_MB=2048` | Not referenced in source code. Database size is not artificially limited. | -| `SQLITE_CLEAN_LEGACY_FILES=true` | Not referenced in source code. Legacy cleanup was likely removed. | -| `CLI_ROO_BIN` | Not registered in `src/shared/services/cliRuntime.ts`. | -| `CLI_KIMI_CODING_BIN` | Not registered in `src/shared/services/cliRuntime.ts` (Kimi Coding uses OAuth, not a CLI binary). | -| `IFLOW_OAUTH_CLIENT_ID` / `IFLOW_OAUTH_CLIENT_SECRET` | Not referenced anywhere in source code. | - -### Default Value Corrections - -| Variable | Old `.env.example` Value | Actual Code Default | Fixed | -| ------------------------- | ------------------------ | ------------------- | ------------------------------------------------------ | -| `APP_LOG_RETENTION_DAYS` | `90` | `7` | ✅ Removed misleading value; documented `7` as default | -| `CALL_LOG_RETENTION_DAYS` | `90` | `7` | ✅ Removed misleading value; documented `7` as default | +**本機開發**的內建憑證。對於遠端部署,請在各提供者的開發者主控台自行註冊。 + diff --git a/docs/i18n/zh-TW/docs/routing/AUTO-COMBO.md b/docs/i18n/zh-TW/docs/routing/AUTO-COMBO.md index f0ca4d48d2..81d92cdf38 100644 --- a/docs/i18n/zh-TW/docs/routing/AUTO-COMBO.md +++ b/docs/i18n/zh-TW/docs/routing/AUTO-COMBO.md @@ -1,67 +1,617 @@ -# OmniRoute Auto-Combo Engine (中文 (簡體)) - -🌐 **Languages:** 🇺🇸 [English](../../../../docs/AUTO-COMBO.md) · 🇸🇦 [ar](../../ar/docs/AUTO-COMBO.md) · 🇧🇬 [bg](../../bg/docs/AUTO-COMBO.md) · 🇧🇩 [bn](../../bn/docs/AUTO-COMBO.md) · 🇨🇿 [cs](../../cs/docs/AUTO-COMBO.md) · 🇩🇰 [da](../../da/docs/AUTO-COMBO.md) · 🇩🇪 [de](../../de/docs/AUTO-COMBO.md) · 🇪🇸 [es](../../es/docs/AUTO-COMBO.md) · 🇮🇷 [fa](../../fa/docs/AUTO-COMBO.md) · 🇫🇮 [fi](../../fi/docs/AUTO-COMBO.md) · 🇫🇷 [fr](../../fr/docs/AUTO-COMBO.md) · 🇮🇳 [gu](../../gu/docs/AUTO-COMBO.md) · 🇮🇱 [he](../../he/docs/AUTO-COMBO.md) · 🇮🇳 [hi](../../hi/docs/AUTO-COMBO.md) · 🇭🇺 [hu](../../hu/docs/AUTO-COMBO.md) · 🇮🇩 [id](../../id/docs/AUTO-COMBO.md) · 🇮🇹 [it](../../it/docs/AUTO-COMBO.md) · 🇯🇵 [ja](../../ja/docs/AUTO-COMBO.md) · 🇰🇷 [ko](../../ko/docs/AUTO-COMBO.md) · 🇮🇳 [mr](../../mr/docs/AUTO-COMBO.md) · 🇲🇾 [ms](../../ms/docs/AUTO-COMBO.md) · 🇳🇱 [nl](../../nl/docs/AUTO-COMBO.md) · 🇳🇴 [no](../../no/docs/AUTO-COMBO.md) · 🇵🇭 [phi](../../phi/docs/AUTO-COMBO.md) · 🇵🇱 [pl](../../pl/docs/AUTO-COMBO.md) · 🇵🇹 [pt](../../pt/docs/AUTO-COMBO.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/AUTO-COMBO.md) · 🇷🇴 [ro](../../ro/docs/AUTO-COMBO.md) · 🇷🇺 [ru](../../ru/docs/AUTO-COMBO.md) · 🇸🇰 [sk](../../sk/docs/AUTO-COMBO.md) · 🇸🇪 [sv](../../sv/docs/AUTO-COMBO.md) · 🇰🇪 [sw](../../sw/docs/AUTO-COMBO.md) · 🇮🇳 [ta](../../ta/docs/AUTO-COMBO.md) · 🇮🇳 [te](../../te/docs/AUTO-COMBO.md) · 🇹🇭 [th](../../th/docs/AUTO-COMBO.md) · 🇹🇷 [tr](../../tr/docs/AUTO-COMBO.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/AUTO-COMBO.md) · 🇵🇰 [ur](../../ur/docs/AUTO-COMBO.md) · 🇻🇳 [vi](../../vi/docs/AUTO-COMBO.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/AUTO-COMBO.md) - +--- +title: "OmniRoute 自動組合引擎" +version: 3.8.40 +lastUpdated: 2026-06-28 --- -> Self-managing model chains with adaptive scoring +# OmniRoute 自動組合引擎 -## How It Works +> **給一般使用者**:想要快速入門?請參閱[自動組合使用者指南](../getting-started/AUTO-COMBO-GUIDE.md)取得簡單的說明與範例。 -The Auto-Combo Engine dynamically selects the best provider/model for each request using a **6-factor scoring function**: +> 自我管理模型鏈,具備自適應評分 + 零設定自動路由 -| Factor | Weight | Description | -| :--------- | :----- | :---------------------------------------------- | -| Quota | 0.20 | Remaining capacity [0..1] | -| Health | 0.25 | Circuit breaker: CLOSED=1.0, HALF=0.5, OPEN=0.0 | -| CostInv | 0.20 | Inverse cost (cheaper = higher score) | -| LatencyInv | 0.15 | Inverse p95 latency (faster = higher) | -| TaskFit | 0.10 | Model × task type fitness score | -| Stability | 0.10 | Low variance in latency/errors | +## 零設定自動路由(`auto/` 前綴) -## Mode Packs +> **新功能:** 無需建立組合。直接在任何客戶端中使用 `auto/` 前綴。 -| Pack | Focus | Key Weight | -| :---------------------- | :----------- | :--------------- | -| 🚀 **Ship Fast** | Speed | latencyInv: 0.35 | -| 💰 **Cost Saver** | Economy | costInv: 0.40 | -| 🎯 **Quality First** | Best model | taskFit: 0.40 | -| 📡 **Offline Friendly** | Availability | quota: 0.40 | +### 快速範例 -## Self-Healing +| 模型 ID | 變體 | 行為 | +| --------------- | --------- | --------------------------------------------------------------- | +| `auto` | 預設 | 所有已連線提供商,LKGP 策略,平衡權重 | +| `auto/coding` | coding | 品質優先權重,適合程式碼生成 | +| `auto/fast` | fast | 低延遲加權選擇 | +| `auto/cheap` | cheap | 成本最佳化路由(最低成本優先) | +| `auto/offline` | offline | 偏好額度可用性最高的提供商 | +| `auto/smart` | smart | 品質優先 + 較高探索率(10%),以發掘更佳模型 | +| `auto/lkgp` | lkgp | 明確 LKGP(與預設 `auto` 相同) | -- **Temporary exclusion**: Score < 0.2 → excluded for 5 min (progressive backoff, max 30 min) -- **Circuit breaker awareness**: OPEN → auto-excluded; HALF_OPEN → probe requests -- **Incident mode**: >50% OPEN → disable exploration, maximize stability -- **Cooldown recovery**: After exclusion, first request is a "probe" with reduced timeout +### 類別 × 層級組合(`auto/<類別>:<層級>`) -## Bandit Exploration +OpenRouter 風格的後綴將**路由種類**(類別)與**最佳化方式**(層級)分離,讓您可以自由組合(#4235 Phase B, `open-sse/services/autoCombo/suffixComposition.ts`): -5% of requests (configurable) are routed to random providers for exploration. Disabled in incident mode. +- **類別**(依能力過濾候選池):`coding`(程式)· `reasoning`(推理)· `vision`(視覺)· `chat`(對話)· `multimodal`(多模態)。`vision`/`multimodal` 保留具視覺能力的模型;`reasoning` 保留推理/思考模型。 +- **層級**(選擇評分權重 / 過濾池):`fast`(快速出貨)· `cheap`(別名 `floor`,節省成本)· `reliable`(斷路器健康度 + 延遲穩定性)· `free` / `pro`(透過 `classifyTier` 依模型層級過濾池 — 免費層 vs. 高級層)。 + +| 範例 | 解析結果 | +| --------------------------- | ----------------------------------------------- | +| `auto/coding:fast` | coding 池,低延遲權重 | +| `auto/coding:cheap` | coding 池,成本最佳化(別名 `auto/coding:floor`)| +| `auto/reasoning:pro` | 僅限推理/思考模型,高級層 | +| `auto/vision` | 具視覺能力的模型(無層級 → 平衡權重) | +| `auto/multimodal:free` | 多模態能力模型,僅限免費層 | + +任何有效的 `auto/<類別>[:<層級>]` 皆可按需解析;精選子集會在 `/v1/models` 與儀表板中顯示(`AUTO_SUFFIX_VARIANTS` 定義於 `open-sse/services/autoCombo/builtinCatalog.ts`)。過濾採用**容錯開放**機制—若條件未匹配到任何已連線模型,則使用完整候選池,確保路由永不中斷。核心評分器(`combo.ts`)維持不變;類別/層級過濾則在 `buildAutoCandidates` 中執行。 + +> **即時模型智慧:** 若 `ARENA_ELO_SYNC_ENABLED` 旗標開啟,自動路由的適應性會參考即時 **Arena ELO** 排名 + **models.dev** 層級資料;否則回退至靜態適應性評分表。 + +**使用方式:** + +```bash +# 任何支援 OpenAI 格式的 IDE 或 CLI 工具 +Base URL: http://localhost:20128/v1 +API Key: + +# 在程式碼/設定中,將 model 設為: +model: "auto" # 平衡預設 +model: "auto/coding" # 最適合程式任務 +model: "auto/fast" # 最快的可用選項 +model: "auto/cheap" # 每 token 最便宜 +``` + +**運作流程:** + +1. OmniRoute 在 `src/sse/handlers/chat.ts` 中偵測到 `auto/` 前綴 +2. 查詢資料庫中所有**活躍的提供商連線** +3. 過濾出具有有效憑證(API 金鑰或 OAuth token)的連線 +4. 為每個連線決定模型(`connection.defaultModel` 或提供商的第一個模型) +5. 在記憶體中建立**虛擬組合**(不存入資料庫) +6. 使用所選變體的權重設定檔 + LKGP 策略進行路由 + +**主要特性:** + +- ✅ **永遠開啟:** 無需開關、無需建立組合、無需設定 +- ✅ **動態:** 自動反映當前連線的提供商 +- ✅ **工作階段黏著性:** LKGP 確保上次成功的提供商獲得優先權 +- ✅ **多帳號感知:** 每個提供商連線成為獨立的候選項目 +- ✅ **無資料庫寫入:** 虛擬組合僅存在於請求期間,零持久化開銷 + +### 依金鑰候選控制(#7819, Level 1+2) + +`GET /v1/auto-combo/{channel}/candidates`(`{channel}` = `auto/` 後的後綴,或基礎頻道使用 `auto` 字面值)是一個**唯讀**端點,列出某個 `auto/*` 頻道當前的候選池,並裝飾有即時可達性資訊,重複使用現有的彈性讀取機制(絕不直接使用原始的斷路器 `state`): + +- 提供商斷路器 — `getCircuitBreaker(provider).getStatus()` / `.canExecute()` +- 連線冷卻 — `rateLimitedUntil` / `testStatus`(來自已解析的 `provider_connections` 資料列) +- 模型鎖定 — `isModelLocked(provider, connectionId, model)` + +每個候選項也攜帶此 API 金鑰的 `excluded`(排除)旗標。排除設定依各 API 金鑰儲存(`auto_candidate_overrides` 資料表,遷移 `128`)— OmniRoute 為單租戶架構,無 `users` 資料表,因此 `apiKeyId` 是最接近的實際呼叫者識別身份—並在候選池的瓶頸點透過純粹且經單元測試的 `filterExcludedCandidates()` 強制執行(`open-sse/services/autoCombo/virtualFactory.ts` → `open-sse/services/autoCombo/candidateOverrides.ts`)。此過濾採用**容錯開放**機制:若 `apiKeyId`/channel 未設定或資料庫查詢失敗,則不進行過濾,使未設定任何覆寫的管理員所看到的路由行為與此功能推出前完全一致。 + +**延至後續議題:** 依候選權重 + 明確排序(Level 3 — 饋入現有的加權/優先策略路徑),以及為每個 `auto/*` 頻道鎖定特定的 `combo.ts` 策略(Level 4)。請參閱 #7819 計畫中有關覆寫設定應維持依 API 金鑰或改為全域的開放問題(考量單租戶模型)。 + +**幕後流程:** + +```txt +Request: { model: "auto/coding" } + ↓ +src/sse/handlers/chat.ts 偵測前綴 + ↓ +createVirtualAutoCombo('coding') → 來自活躍連線的候選池 + ↓ +handleComboChat(與持久化組合使用相同引擎) + ↓ +自動評分為每個請求選擇最佳提供商/模型 +``` + +**實作檔案:** + +| 檔案 | 用途 | +| ------------------------------------------------------------ | --------------------------------------- | +| `open-sse/services/autoCombo/autoPrefix.ts` | 前綴解析器(`parseAutoPrefix`) | +| `open-sse/services/autoCombo/virtualFactory.ts` | 建立虛擬 `AutoComboConfig` 物件 | +| `open-sse/services/autoCombo/providerRegistryAccessor.ts` | 用於 mock 提供商註冊表的測試鉤子 | +| `src/sse/handlers/chat.ts` | 整合點:自動前綴短路處理 | +| `src/shared/constants/providers.ts` | `SYSTEM_PROVIDERS.auto` 系統條目 | + +## 運作原理(持久化自動組合) + +自動組合引擎使用**12 因子評分函數**(定義於 `open-sse/services/autoCombo/scoring.ts` → `DEFAULT_WEIGHTS`)為每個請求動態選擇最佳的提供商/模型。所有權重合計為 **1.0**。 + +![自動組合 12 因子評分](../diagrams/exported/auto-combo-12factor.svg) + +> 來源:[diagrams/auto-combo-12factor.mmd](../diagrams/auto-combo-12factor.mmd)(可透過 `npm run docs:render-diagrams` 重新生成)。 + +| 因子 | 預設權重 | 說明 | +| :--------------------- | :------- | :---------------------------------------------------------------- | +| `health`(健康度) | 0.20 | 斷路器健康分數(CLOSED=1.0, HALF_OPEN=0.5, OPEN=0.0) | +| `quota`(額度) | 0.15 | 剩餘額度 / 速率限制餘裕 [0..1] | +| `costInv`(成本倒數) | 0.15 | 倒數**混合**成本(60% 輸入 + 40% 輸出 token 價格,經正規化)— 越便宜分數越高 | +| `latencyInv`(延遲倒數)| 0.12 | 倒數 p95 延遲經池正規化 — 越快分數越高 | +| `taskFit`(任務適應性)| 0.08 | 任務類型適應性(程式、審查、規劃、分析、除錯、文件) | +| `stability`(穩定性) | 0.05 | 基於變異數的穩定性(低延遲 stdDev / 錯誤率) | +| `tierPriority`(層級優先)| 0.05 | 帳戶層級優先級 — Ultra=1.0, Pro=0.67, Standard=0.33, Free=0.0 | +| `tierAffinity`(層級親和性)| 0.05 | 候選層級與清單建議層級之間的親和性 | +| `specificityMatch`(特異性匹配)| 0.05 | 請求特異性(清單提示)與模型層級之間的匹配度 | +| `contextAffinity`(上下文親和性)| 0.05 | 請求的上下文視窗需求與模型上下文視窗之間的親和性 | +| `connectionDensity`(連線密度)| 0.05 | 在同一個提供商的不同連線之間分散負載(反集中化) | +| `resetWindowAffinity`(重置視窗親和性)| 0.00 | 傾向於額度重置視窗有利的連線(預設停用) | + +**合計:** `0.20 + 0.15 + 0.15 + 0.12 + 0.08 + 0.05 + 0.05 + 0.05 + 0.05 + 0.05 + 0.05 + 0.00 = 1.0`(經 `validateWeights()` 驗證)。 + +## 模式套件 + +四個預定義的加權設定檔位於 `open-sse/services/autoCombo/modePacks.ts`。每個套件會覆寫預設權重,使選擇偏向特定目標。以下是**每個套件的完整權重表**(每個設定檔行合計為 1.0)。 + +| 因子 | ship-fast | cost-saver | quality-first | offline-friendly | +| :----------- | :-------- | :--------- | :------------ | :--------------- | +| quota | 0.14 | 0.14 | 0.10 | **0.37** | +| health | 0.28 | 0.19 | 0.18 | 0.28 | +| costInv | 0.05 | **0.37** | 0.05 | 0.10 | +| latencyInv | **0.32** | 0.05 | 0.05 | 0.05 | +| taskFit | 0.10 | 0.10 | **0.37** | 0.00 | +| stability | 0.00 | 0.05 | 0.15 | 0.10 | +| tierPriority | 0.05 | 0.05 | 0.05 | 0.05 | + +備註: + +- `tierAffinity` 和 `specificityMatch` 未在模式套件中設定 — `calculateScore()` 在缺少時以 `?? 0` 處理。 +- 各套件重點一覽: + - **ship-fast(快速出貨)** → latencyInv 0.32 + health 0.28(低延遲、健康連線) + - **cost-saver(節省成本)** → costInv 0.37(最便宜的 token 勝出) + - **quality-first(品質優先)** → taskFit 0.37 + stability 0.15(最適合任務的模型,一致穩定) + - **offline-friendly(離線友善)** → quota 0.37 + health 0.28(最大餘裕,不計速度或成本) + +### 每次請求控制(標頭)— #6023 / #6024 / #6025 / #3470 + +`auto` 組合可透過三個標頭**針對每次請求**進行調整,無需修改組合的儲存設定。這些僅適用於 `auto` 策略,且僅對攜帶這些標頭的請求生效;當標頭不存在時,則使用組合儲存的 `modePack`/`budgetCap`/`budgetFallback`。 + +| 標頭 | 接受值 | 效果 | +| :-------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `X-OmniRoute-Mode` | 預設別名(`fast`、`balanced`、`quality`、`cheap`、`reliable`、`offline`)或原始套件名稱(`ship-fast`、`cost-saver`、`quality-first`、`offline-friendly`、`reliability-first`) | 覆寫本次請求的評分權重。`balanced`/`default` 強制使用預設權重(無套件)。未知值則忽略(保留原有設定)。 | +| `X-OmniRoute-Budget` | 正數(每次請求的最大美元金額) | 硬性成本上限:估計成本超過此值的候選項在選擇前即被過濾。當**每個**候選項都超過上限時,行為由下方的 `X-OmniRoute-Budget-Fallback` 控制。 | +| `X-OmniRoute-Budget-Fallback` | `cheapest`(預設,別名:`cheapest-viable`、`soft`)或 `strict`(別名:`block`、`hard`) | `cheapest`:回退至全域最便宜的候選項(即使仍超過上限,為舊版行為)。`strict`:拒絕選擇—請求快速失敗,回傳 `HTTP 402`,而非默默超支。未知值則忽略。 | + +```bash +# 強制使用最快設定檔,將此請求上限設為 $0.05,超支時直接封鎖而非降級 +curl -sS http://localhost:20128/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "X-OmniRoute-Mode: fast" \ + -H "X-OmniRoute-Budget: 0.05" \ + -H "X-OmniRoute-Budget-Fallback: strict" \ + -d '{"model":"auto","messages":[{"role":"user","content":"hi"}]}' +``` + +解析是一個純函數(`open-sse/services/autoCombo/requestControls.ts`);解析後的值饋入引擎現有的 `config.modePack` / `config.budgetCap` / `config.budgetFallback` 輸入。組合儲存的 `config.budgetFallback`(`"strict"` | `"cheapest"`)設定持久化策略;標頭則為單次請求覆寫此設定。 + +## 所有路由策略 + +OmniRoute 的組合引擎支援 **18 種路由策略**(宣告於 `src/shared/constants/routingStrategies.ts` → `ROUTING_STRATEGY_VALUES`)。自動組合引擎本身以 `auto` 策略對外提供;其他策略可用於持久化組合。 + +| 策略 | 說明 | +| :----------------- | :--------------------------------------------------------------------------------- | +| `priority` | 依明確優先級排列的第一目標有序列表 | +| `weighted` | 依各目標權重的加權隨機選擇 | +| `round-robin` | 依序循環切換目標 | +| `context-relay` | 跨目標交接上下文(長對話) | +| `fill-first` | 先填滿每個目標的額度,再移至下一個 | +| `p2c` | Power-of-2-choices 隨機負載平衡 | +| `random` | 均勻隨機選擇 | +| `least-used` | 挑選當前負載最低的目標 | +| `cost-optimized` | 依目錄定價最小化每次請求成本 | +| `reset-aware` ⭐ | 依額度重置時間排序 — 重置視窗短者優先 | +| `reset-window` | 偏好額度視窗最快重置的目標 | +| `headroom` | 挑選剩餘額度空間最大的目標 | +| `strict-random` | 純隨機,不排除重複 | +| `auto` | 使用自動組合評分(9 因子)— **推薦** | +| `lkgp` | 上次已知良好路徑(黏著路由至上次成功的目標) | +| `context-optimized`| 挑選最適合當前上下文大小的目標 | +| `fusion` 🧬 | 平行分發給多個模型面板,再由評判模型合成一個答案(詳見下方) | +| `pipeline` | 依序執行目標,將每個步驟的輸出串接至下一步驟的輸入;僅回傳最終答案(#6396) | + +⭐ = v3.8.0 新增 · 🧬 = v3.8.36 新增 + +## Fusion 策略 + +`fusion` 是唯一一種**不選擇單一目標**的策略。它將提示**平行分發給每個面板模型**,然後由可設定的**評判模型**從所有面板回應中合成一個最終答案。移植自上游 `decolua/9router`(OpenRouter 的 Fusion 設計);實作位於 `open-sse/services/fusion.ts`。 + +運作方式: + +0. **含有工具的請求繞過** — 若請求攜帶非空的 `tools` 陣列且 `tool_choice` 非明確設為 `"none"`,則跳過面板:直接路由至單一模型(設定的評判模型,或 `panel[0]`),`tools`/`tool_choice` 原樣傳遞。面板成員無法存取工具,且評判模型的合成指示會抑制工具呼叫輸出,因此代理/工具呼叫客戶端會獲得真正的工具呼叫決策,而非合成的散文(#6771)。 +1. **平行分發**(僅限不含工具的請求)— 提示同時發送給所有面板模型,強制非串流並移除工具(評判模型需要完整散文才能合成)。 +2. **法定人數寬限期收集** — 一旦收到 `minPanel` 個答案,隨即啟動一個短暫的寬限期計時器等待落後者,然後以已收集到的所有答案繼續進行融合。這限制了最慢模型對實際時間的懲罰,並設有硬性超時上限。 +3. **評判合成** — 面板答案經匿名化處理(`Source 1`、`Source 2`……— 使評判模型衡量內容實質而非品牌)後交給評判模型,由其分析共識/矛盾/部分覆蓋/獨特見解/盲點,然後撰寫**一個**權威答案。評判呼叫保留客戶端的原始 `stream` 旗標 + 工具,因此串流和下游工具使用仍可運作。 +4. **優雅降級** — 0 個面板答案 → `503`;恰好 1 個答案存活 → 直接回傳該答案(無需融合);單一模型面板則直接回傳答案。 + +面板成員也可以是 `combo-ref` 步驟(`{kind: "combo-ref", comboName: "..."}`),引用另一個組合—它被解析為**一個黑箱面板聲音**(完整遞迴分發至被引用的組合,而非將該組合自身的目標展開),並具有與其他所有使用 combo-ref 的策略相同的深度/循環保護機制(#6764)。 + +### 設定 + +設定於組合的 `config` blob(無需結構描述遷移—它重複使用現有的 `combos` 資料表): + +| 欄位 | 類型 | 預設值 | 用途 | +| :------------------------------------------ | :------- | :---------------- | :---------------------------------------------------------- | +| `config.judgeModel` | `string` | 第一個面板模型 | 負責合成最終答案的模型 | +| `config.fusionTuning.minPanel` | `number` | `2` | 寬限期計時器啟動前所需的成功答案數(限制在 `[2, panelSize]` 之間)| +| `config.fusionTuning.stragglerGraceMs` | `number` | `8000` | 達到法定人數後等待落後者的時間 | +| `config.fusionTuning.panelHardTimeoutMs` | `number` | `90000` | 絕對超時上限,防止單一掛起的模型拖垮整個請求 | + +預設值位於 `FUSION_DEFAULTS`(`open-sse/services/fusion.ts`)。 + +### 範例 + +```bash +curl -X POST http://localhost:20128/api/combos \ + -H "Authorization: Bearer ***" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "fusion-panel", + "strategy": "fusion", + "targets": [ + { "model": "cc/claude-opus-4-7" }, + { "model": "cx/gpt-5.5" }, + { "model": "glm/glm-5.1" } + ], + "config": { + "judgeModel": "cc/claude-opus-4-7", + "fusionTuning": { "minPanel": 2, "stragglerGraceMs": 8000, "panelHardTimeoutMs": 90000 } + } + }' +``` + +然後像任何組合一樣呼叫:`{"model":"fusion-panel","messages":[...]}`。 + +## 虛擬自動組合工廠 + +自動組合引擎無需預先定義的組合。相反地,`open-sse/services/autoCombo/virtualFactory.ts` 會即時建立候選項: + +1. 取得 `getProviderConnections({ isActive: true })`(所有啟用的連線) +2. 過濾出具備有效憑證的連線(API 金鑰或未過期的 OAuth token,透過 `hasUsableOAuthToken()`) +3. 與 `getProviderRegistry()` 交叉參考以取得模型可用性 + 定價 +4. 對每個元組 `(provider, model, connection)` 建立 `VirtualAutoComboCandidate` +5. 選取 `connection.defaultModel`(或註冊表中的第一個模型)作為分發目標 +6. 使用 9 因子 `scorePool()` 和變體的權重套件為每個候選項評分 +7. 回傳結果的記憶體中 `AutoComboConfig` 供 `handleComboChat()` 使用 — 永不持久化至資料庫 + +這表示**新增一個啟用 `auto/*` 的提供商會自動擴展候選池**—無需手動編輯組合。虛擬組合在每次請求時重新建立,因此新新增或剛恢復健康的連線會立即被納入。 + +## 自我修復 + +- **暫時排除**:分數 < 0.2 → 排除 5 分鐘(漸進式退避,最長 30 分鐘) +- **斷路器感知**:OPEN → 自動排除;HALF_OPEN → 探測請求 +- **事故模式**:>50% OPEN → 停用探索,最大化穩定性 +- **冷卻恢復**:排除後,第一個請求為「探測」請求,使用縮短的超時時間 + +## Bandit 探索 + +5% 的請求(可設定)會路由至隨機提供商進行探索。事故模式下停用。 ## API -```bash -# Create auto-combo -curl -X POST http://localhost:20128/api/combos/auto \ - -H "Content-Type: application/json" \ - -d '{"id":"my-auto","name":"Auto Coder","candidatePool":["anthropic","google","openai"],"modePack":"ship-fast"}' +**沒有專用的 `POST /api/combos/auto` 端點**—自動組合透過兩種方式使用: -# List auto-combos -curl http://localhost:20128/api/combos/auto +1. **零設定(推薦):** 發送任何聊天完成請求,設定 `model: "auto"` 或 `model: "auto/<變體>"`。虛擬工廠為每次請求建立組合 — 無需持久化,無需 API 呼叫。 + +2. **使用 `strategy: "auto"` 的持久化組合:** 透過 `POST /api/combos` 建立一般組合,設定 `strategy: "auto"` 以及 `config.auto.weights` / `config.auto.candidatePool`。使用相同的評分引擎;組合儲存於 `combos` 表中,可透過 ID 重複使用。 + +用於探索時,`GET /api/combos/auto` 列出每個變體及其已解析的候選池,以及 `context_length` / `max_output_tokens` — 即候選池視窗中的**最大值**。客戶端(例如 opencode 外掛)必須公告這些值而非 `0`:零上下文會完全停用 opencode 的自動壓縮功能,讓工作階段持續增長直到閘道的歷史清除破壞上下文。公告最大值是安全的,因為自動組合上下文預先過濾會將超大型請求路由至大視窗的候選項。 + +```bash +# 零設定使用(無需建立組合) +curl -X POST http://localhost:20128/v1/chat/completions \ + -H "Authorization: Bearer ***" \ + -H "Content-Type: application/json" \ + -d '{"model":"auto/coding","messages":[{"role":"user","content":"Hello"}]}' + +# 透過一般組合端點建立持久化自動組合 +curl -X POST http://localhost:20128/api/combos \ + -H "Content-Type: application/json" \ + -d '{"id":"my-auto","name":"Auto Coder","strategy":"auto","config":{"auto":{"candidatePool":["anthropic","google","openai"],"weights":{"quota":0.15,"health":0.3,"costInv":0.05,"latencyInv":0.35,"taskFit":0.1,"stability":0,"tierPriority":0.05}}}}' ``` -## Task Fitness +### 自動路由器策略 -30+ models scored across 6 task types (`coding`, `review`, `planning`, `analysis`, `debugging`, `documentation`). Supports wildcard patterns (e.g., `*-coder` → high coding score). +持久化的 `strategy: "auto"` 組合可以設定 `config.routerStrategy`(或舊版 `config.auto.routerStrategy`)為以下之一: -## Files +- `rules` — 預設加權評分 +- `cost` / `eco` — 最便宜的健全提供商 +- `latency` / `fast` — 最低 p95 延遲,附可靠性懲罰 +- `sla-aware` / `sla` — 偏好滿足 p95 延遲、錯誤率與可選成本 SLA 的候選項 +- `lkgp` — 上次已知良好的提供商優先 -| File | Purpose | -| :------------------------------------------- | :------------------------------------ | -| `open-sse/services/autoCombo/scoring.ts` | Scoring function & pool normalization | -| `open-sse/services/autoCombo/taskFitness.ts` | Model × task fitness lookup | -| `open-sse/services/autoCombo/engine.ts` | Selection logic, bandit, budget cap | -| `open-sse/services/autoCombo/selfHealing.ts` | Exclusion, probes, incident mode | -| `open-sse/services/autoCombo/modePacks.ts` | 4 weight profiles | -| `src/app/api/combos/auto/route.ts` | REST API | +### 路由器策略詳細說明 + +自動組合引擎提供 5 個可插拔的 **RouterStrategy** 實作,可透過 `config.routerStrategy`(或舊版 `config.auto.routerStrategy`)切換。每種策略根據給定的 `RoutingContext`(任務類型、工具/視覺提示、token 估算、可選 SLA 策略、可選的上次已知良好提供商)從候選池中選擇一個提供商。 + +#### 1. `rules`(預設)— 6 因子加權評分 + +包裝現有的評分引擎。過濾掉 `OPEN` 斷路器狀態的候選項,然後使用當前任務類型和 `getTaskFitness()` 執行 `scorePool()`,選取得分最高的提供商。 + +```ts +class RulesStrategyImpl implements RouterStrategy { + readonly name = "rules"; + readonly description = + "6 因子加權評分:額度、健康度、成本、延遲、任務適應性、穩定性"; + + select(pool, context) { + const eligible = pool.filter((c) => c.circuitBreakerState !== "OPEN"); + const ranked = scorePool( + eligible.length > 0 ? eligible : pool, + context.taskType, + undefined, + getTaskFitness + ); + return { provider: ranked[0].provider /* ... */ }; + } +} +``` + +**使用時機**:預設值。當您希望在所有訊號之間取得平衡時使用。 + +**別名**:`rules`(無別名) + +--- + +#### 2. `cost` / `eco` — 最便宜的健全提供商 + +將候選池按 `costPer1MTokens`(升序)排序,選取最便宜的。首先過濾掉 `OPEN` 狀態的候選項。 + +```ts +class CostStrategyImpl implements RouterStrategy { + readonly name = "cost"; + readonly description = "始終選擇最便宜的可用提供商"; + + select(pool, context) { + const healthy = pool.filter((c) => c.circuitBreakerState !== "OPEN"); + const sorted = [...healthy].sort((a, b) => a.costPer1MTokens - b.costPer1MTokens); + return { provider: sorted[0].provider /* ... */ }; + } +} +``` + +**使用時機**:成本敏感的工作負載、批次處理或背景任務。 + +**別名**:`cost`, `eco` + +--- + +#### 3. `latency` / `fast` — 最低 p95 延遲附可靠性懲罰 + +按 `p95LatencyMs + (errorRate * 1000)` 排序。錯誤率懲罰確保不可靠的提供商即使名義延遲較低也會被排在較低位置。 + +```ts +class LatencyStrategyImpl implements RouterStrategy { + readonly name = "latency"; + readonly description = "優先考慮最低 p95 延遲,加權可靠性"; + + select(pool, context) { + const healthy = pool.filter((c) => c.circuitBreakerState !== "OPEN"); + const sorted = [...healthy].sort( + (a, b) => a.p95LatencyMs + a.errorRate * 1000 - (b.p95LatencyMs + b.errorRate * 1000) + ); + return { provider: sorted[0].provider /* ... */ }; + } +} +``` + +**使用時機**:延遲敏感的工作負載,如即時對話、自動完成或互動式程式碼輔助工具。 + +**別名**:`latency`, `fast` + +--- + +#### 4. `sla-aware` / `sla` — 延遲/錯誤/成本 SLA 合規 + +根據每個候選項滿足設定的 SLA 策略的程度進行評分: + +| 因子 | 權重 | 公式 | +| ------------ | ---- | --------------------------------- | +| 延遲分數 | 35% | `threshold / max(value, ε)` | +| 錯誤分數 | 35% | `threshold / max(value, ε)` | +| 健康分數 | 15% | `1.0`(CLOSED) / `0.5`(HALF_OPEN) / `0.0`(OPEN) | +| 成本分數 | 10% | `threshold / max(value, ε)` 或反向正規化 | +| 穩定性分數 | 5% | 反向正規化延遲標準差 | + +當 `hardConstraints: true` 時,候選項主要按**違規分數**(超出任何 SLA 的程度)排序,其次再按綜合分數。否則僅使用綜合分數。 + +```ts +class SLAStrategyImpl implements RouterStrategy { + readonly name = "sla-aware"; + readonly description = + "選擇最可能滿足延遲、錯誤率和成本 SLA 的提供商"; + + select(pool, context) { + // ... 根據策略對每個候選項評分:{ targetP95Ms, maxErrorRate, maxCostPer1MTokens, hardConstraints } + } +} +``` + +**SLA 欄位**(設定於組合設定中): + +```json +{ + "strategy": "auto", + "config": { + "routerStrategy": "sla-aware", + "slaTargetP95Ms": 1500, + "slaMaxErrorRate": 0.05, + "slaMaxCostPer1MTokens": 5, + "slaHardConstraints": true + } +} +``` + +**使用時機**:具有嚴格延遲、錯誤率或成本預算的正式環境工作負載。 + +**別名**:`sla-aware`, `sla` + +--- + +#### 5. `lkgp` — 上次已知良好的提供商優先 + +先嘗試**上次已知良好的提供商**(若有設定),若失敗則回退至 `rules` 策略。適用於工作階段的黏著性—同一提供商處理對話中的後續請求。 + +```ts +class LKGPStrategyImpl implements RouterStrategy { + readonly name = "lkgp"; + readonly description = "先嘗試上次已知良好的提供商,若失敗則回退至 rules"; + + select(pool, context) { + if (context.lkgpEnabled === false) { + return getStrategy("rules").select(pool, context); + } + + if (context.lastKnownGoodProvider) { + const candidates = pool.filter( + (c) => c.provider === context.lastKnownGoodProvider && c.circuitBreakerState !== "OPEN" + ); + if (candidates.length > 0) { + return { provider: candidates[0].provider /* ... */ }; + } + } + + // 回退至 rules 策略 + return getStrategy("rules").select(pool, context); + } +} +``` + +**使用時機**:多輪對話中,希望同一提供商處理後續請求(例如為了快取、上下文連續性或定價一致性)。 + +**別名**:`lkgp`(無別名) + +--- + +### 自訂路由器策略 + +您可以透過公開 API 註冊自己的 `RouterStrategy` 實作: + +```ts +import { + registerStrategy, + type RouterStrategy, +} from "@omniroute/open-sse/services/autoCombo/routerStrategy"; + +class MyCustomStrategy implements RouterStrategy { + readonly name = "my-custom"; + readonly description = "我的自訂路由策略"; + + select(pool, context) { + // 您的路由邏輯在此 + return { + provider: pool[0].provider, + model: pool[0].model, + strategy: this.name, + reason: "MyCustomStrategy: ...", + candidatesConsidered: pool.length, + finalScore: 1.0, + }; + } +} + +registerStrategy("my-custom", new MyCustomStrategy()); +``` + +然後使用: + +```json +{ + "strategy": "auto", + "config": { + "routerStrategy": "my-custom" + } +} +``` + +--- + +### 路由器策略選擇指南 + +| 使用案例 | 策略 | 原因 | +| -------------------- | ------------ | --------------------------------- | +| 平衡工作負載 | `rules` | 預設 — 考慮所有因素 | +| 最小化成本 | `cost` | 始終選取最便宜的 | +| 最小化延遲 | `latency` | 選取最快的可靠提供商 | +| 嚴格 SLA | `sla-aware` | 按 p95/錯誤率/成本門檻過濾 | +| 多輪對話 | `lkgp` | 工作階段黏著性 | + +SLA-aware 欄位: + +```json +{ + "strategy": "auto", + "config": { + "routerStrategy": "sla-aware", + "slaTargetP95Ms": 1500, + "slaMaxErrorRate": 0.05, + "slaMaxCostPer1MTokens": 5, + "slaHardConstraints": true + } +} +``` + +## 任務適應性 + +30+ 個模型在 6 種任務類型(`coding`(程式)、`review`(審查)、`planning`(規劃)、`analysis`(分析)、`debugging`(除錯)、`documentation`(文件))上進行評分。支援萬用字元模式(例如 `*-coder` → 高程式設計分數)。 + +## 自動變體回顧 + +包含裸 `auto`(預設)加上 `autoPrefix.ts` 中宣告的 6 個 `AutoVariant` 值,共有 **7 個可呼叫的模型 ID**: + +`auto`, `auto/coding`, `auto/fast`, `auto/cheap`, `auto/offline`, `auto/smart`, `auto/lkgp` + +(`AutoVariant` 本身列舉 6 個值;第 7 個選項為「無變體」— 裸 `auto` — 由 `parseAutoPrefix()` 以 `variant: undefined` 處理。) + +## 層級如何融入自動組合 + +12 因子評分函數(`open-sse/services/autoCombo/scoring.ts`)將層級歸屬視為兩個訊號:`tierPriority`(0.05)和 `tierAffinity`(0.05)。請參閱上方標準的[評分因子表](#運作原理持久化自動組合)以取得完整的 `DEFAULT_WEIGHTS` 集合 — 各套件覆寫值(ship-fast/cost-saver/quality-first/offline-friendly)列於「各套件權重設定檔」表中。 + +層級本身**不會**強制 Tier 1 優先 — 如果 Tier 1 延遲不佳或成本 vs. 品質次佳,則 Tier 2 勝出。若要強制層級排序,請使用組合策略 `priority` 並按層級排列提供商。 + +若要強烈偏好 Tier 1(訂閱制),請增加 `tierPriority` 權重: + +```json +{ + "strategy": "auto", + "config": { "auto": { "weights": { "tierPriority": 0.3, "costInv": 0.05 } } } +} +``` + +請參閱 `docs/marketing/TIERS.md` 了解層級定義與提供商分類。 + +## 測試與覆蓋範圍 + +### 確定性路由決策矩陣(`npm run test:combo:matrix`) + +`tests/integration/combo-matrix/*.test.ts` 證明所有 18 個公開策略的路由**決策**端到端地通過真實的組合管線,上游以 mock 方式模擬。覆蓋範圍包含: + +- 全部 18 個 `ROUTING_STRATEGY_VALUES` 策略(有序、加權、成本、上下文、fusion……)。 +- `quota-share`(內部)端到端:透過真實的 `selectQuotaShareTarget` 接縫進行 DRR 公平性 + 飽和降級處理(`registerQuotaFetcher` / `setLKGP` / `__setHeadroomSaturationFetcherForTests`)。 +- `context-relay` 在所有目標數量下的通用交接覆蓋。 + +此測試套件在 CI 中執行(`test:integration` 任務),使用 `--test-concurrency=1` 和 `--test-force-exit`,確保確定性且不需要真實憑證。 + +### 閘控即時煙霧測試(不在 CI 中—需要真實提供商) + +| 指令 | 功能說明 | +| :---------------------------------------- | :-------------------------------------------------------------------- | +| `npm run test:combo:live` | 處理中真實路由(`RUN_COMBO_LIVE=1`);快照即時 OmniRoute 資料庫 | +| `npm run test:combo:live:vps` | 對即時 OmniRoute 伺服器的 HTTP 呼叫(設定 `COMBO_LIVE_BASE_URL`) | +| `npm run test:combo:live:vps:failover` | 同上,但加入刻意觸發的容錯轉移情境 | + +這些煙霧測試實際演練真實線路(組合 → 提供商 → 完成)。刻意排除在 CI 之外,因為它們需要真實憑證和 VPS 存取權限。 + +--- + +## 相關檔案 + +| 檔案 | 用途 | +| :---------------------------------------------------------- | :--------------------------------------------- | +| `open-sse/services/autoCombo/scoring.ts` | 9 因子評分函數、`DEFAULT_WEIGHTS`、池正規化 | +| `open-sse/services/autoCombo/taskFitness.ts` | 模型 × 任務適應性查詢表 | +| `open-sse/services/autoCombo/engine.ts` | 選擇邏輯、bandit、預算上限 | +| `open-sse/services/autoCombo/selfHealing.ts` | 排除、探測、事故模式 | +| `open-sse/services/autoCombo/modePacks.ts` | 4 個權重設定檔(ship-fast, cost-saver, quality-first, offline-friendly) | +| `open-sse/services/autoCombo/autoPrefix.ts` | `auto/` 前綴解析器 + 6 個變體 | +| `open-sse/services/autoCombo/virtualFactory.ts` | 從即時連線建立記憶體中 `AutoComboConfig` | +| `open-sse/services/autoCombo/providerRegistryAccessor.ts` | 用於 mock 提供商註冊表的測試鉤子 | +| `src/shared/constants/routingStrategies.ts` | `ROUTING_STRATEGY_VALUES`(18 種策略) | +| `src/sse/handlers/chat.ts` | 整合點:自動前綴短路處理 | diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 66b0b10434..397fe5931d 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -24,8 +24,8 @@ "inactive": "不活躍", "noData": "無可用資料", "nothingHere": "暫無內容", - "configure": "配置", - "manualConfig": "__MISSING__:Manual config", + "configure": "設定", + "manualConfig": "手動設定", "manage": "管理", "name": "名稱", "actions": "操作", @@ -33,8 +33,8 @@ "type": "型別", "model": "模型", "models": "模型", - "provider": "提供商", - "unknownProvider": "__MISSING__:Unknown provider", + "provider": "提供者", + "unknownProvider": "未知的提供者", "account": "帳戶", "time": "時間", "details": "詳情", @@ -112,7 +112,7 @@ "compatible": "相容", "chat-completions": "對話補全", "oauth": "OAuth", - "auth_token": "認證令牌", + "auth_token": "認證權杖", "crypto": "加密", "hours": "小時", "selfsigned": "自簽名", @@ -162,7 +162,7 @@ "noLockouts": "無鎖定", "webSearchDesc": "Web Search 功能說明", "audioProvidersHeading": "音訊 Provider", - "cloudAgentProviders": "雲代理提供商", + "cloudAgentProviders": "雲代理提供者", "minutesAgo": "分鐘前", "a": "A", "liveAutoRefreshing": "即時自動重新整理中", @@ -186,19 +186,19 @@ "usageTracking": "使用量跟蹤", "disableCloudTitle": "停用雲端", "noConnections": "無連線", - "providerHealth": "提供商健康狀態", + "providerHealth": "提供者健康狀態", "confirmDbImport": "確認DB匯入", "notAvailableSymbol": "—", "backupsAvailable": "可用備份", - "providerAuto": "自動提供商", - "newProviderNamePlaceholder": "新的提供商名稱", + "providerAuto": "自動提供者", + "newProviderNamePlaceholder": "新的提供者名稱", "a2aQuickStartStep2": "A2A快速開始步驟2", "ok": "確定", "available": "可用", "noBackupYet": "無備份暫無", "more": "更多", "noCompatibleYet": "無相容暫無", - "multiProvider": "多提供商", + "multiProvider": "多提供者", "repairEnvHint": "修復環境提示", "disablingCloud": "正在停用 Cloud", "testBench": "測試臺", @@ -220,7 +220,7 @@ "formatConverter": "格式轉換器", "zedImportNetworkError": "Zed 匯入網路錯誤", "apiKeyLabel": "API key 標籤", - "noModelsForProvider": "此提供商沒有可用模型", + "noModelsForProvider": "此提供者沒有可用模型", "mcpCardDescription": "通過 MCP 工具連線 Agent 和自動化流程。", "apiTypeLabel": "API 型別標籤", "configuredProvidersLabel": "已配置 Provider 標籤", @@ -248,7 +248,7 @@ "restore": "恢復", "disableWarning": "停用警告", "moderationsDesc": "稽核說明", - "providerHealthStatusAria": "提供商健康狀態", + "providerHealthStatusAria": "提供者健康狀態", "modelNamePlaceholder": "模型名稱", "mcpQuickStartStep2": "MCP 快速開始步驟 2", "quickStart": "快速開始", @@ -256,7 +256,7 @@ "loadingBackups": "正在載入備份", "anthropicBaseUrlPlaceholder": "Anthropic Base URL", "description": "描述", - "noProviderFound": "未找到提供商", + "noProviderFound": "未找到提供者", "auto": "自動", "protocolsDescription": "配置並測試支援的協議端點。", "cloudSessionNote": "Cloud Session 提示", @@ -264,7 +264,7 @@ "defaultStrategy": "預設策略", "purgeExpiredLogs": "清理已過期日誌", "justNow": "剛剛現在", - "providerTestFailed": "提供商測試失敗", + "providerTestFailed": "提供者測試失敗", "openaiBaseUrlPlaceholder": "OpenAI Base URL", "image": "影像", "failedCreateChain": "建立鏈失敗", @@ -284,7 +284,7 @@ "listModelsDesc": "列出可用 Model。", "databaseSize": "資料庫大小", "chainCreated": "鏈已建立", - "providerTestTimeout": "提供商測試超時", + "providerTestTimeout": "提供者測試超時", "routingStrategy": "路由策略", "translateAction": "翻譯操作", "exportFailed": "匯出失敗", @@ -300,12 +300,12 @@ "localProviders": "本地 Provider", "comboNamePlaceholder": "Combo 名稱", "memoryRss": "記憶體 RSS", - "disableProvider": "停用提供商", + "disableProvider": "停用提供者", "welcomeDesc": "歡迎使用 OmniRoute。", "nameLabel": "名稱標籤", "allOperational": "全部執行正常", "backupNow": "備份現在", - "providerMaxRetriesAria": "提供商最大重試次數", + "providerMaxRetriesAria": "提供者最大重試次數", "textToSpeechDesc": "將文本轉換為語音音訊。", "machineId": "機器 ID", "globalProxy": "全域性代理", @@ -317,7 +317,7 @@ "a2aQuickStartStep1": "A2A快速開始步驟1", "importFailed": "匯入失敗", "inputPlaceholder": "輸入內容...", - "providerModelsTitle": "提供商模型", + "providerModelsTitle": "提供者模型", "lastBackup": "最近備份", "yourEndpoint": "你的 Endpoint", "autoDisableThresholdDesc": "觸發自動停用前允許的連續失敗次數。", @@ -331,14 +331,14 @@ "paused": "已暫停", "llmProviders": "LLM Provider", "enableCombo": "啟用組合", - "removeProviderOverrideAria": "移除提供商覆蓋", + "removeProviderOverrideAria": "移除提供者覆蓋", "nodeVersion": "Node 版本", "openai": "OpenAI", "exportFailedWithError": "匯出失敗:{error}", "proxyConfigured": "代理已配置", "concurrencyPerModel": "每個模型併發數", "protocolTasksLabel": "協議任務標籤", - "oauthProviders": "OAuth 提供商", + "oauthProviders": "OAuth 提供者", "lockedCount": "已鎖定數量", "deleteChainConfirm": "刪除此後備鏈?", "recentTranslations": "最近翻譯", @@ -370,8 +370,8 @@ "cloudRequestTimeout": "雲端請求超時", "showConfiguredOnly": "顯示已配置僅", "showFreeOnly": "僅免費", - "addFirstProvider": "新增您的第一個提供商", - "addFirstProviderDesc": "連線 AI 提供商以開始通過 OmniRoute 路由請求。您可以使用免費提供商、API 金鑰或 OAuth 帳戶。", + "addFirstProvider": "新增您的第一個提供者", + "addFirstProviderDesc": "連線 AI 提供者以開始通過 OmniRoute 路由請求。您可以使用免費提供者、API 金鑰或 OAuth 帳戶。", "learnMore": "瞭解更多", "includeDomains": "包含域名", "promptCache": "提示快取", @@ -395,8 +395,8 @@ "cloudBenefitAccess": "訪問 Cloud 能力", "maxRetries": "最大重試次數", "queueTimeout": "佇列超時", - "queueDepth": "__MISSING__:Queue Depth", - "addProvider": "新增提供商", + "queueDepth": "佇列深度", + "addProvider": "新增提供者", "realtime": "即時", "totalTranslations": "總計翻譯", "protocolActiveStreamsLabel": "協議活躍 Stream", @@ -438,14 +438,14 @@ "openaiPrefixPlaceholder": "OpenAI 字首", "enterPassword": "輸入密碼", "openA2aDashboard": "開啟 A2A Dashboard", - "enableProvider": "啟用提供商", + "enableProvider": "啟用提供者", "modeTest": "測試模式", "failedDeleteChain": "刪除鏈失敗", - "providerDesc": "提供商描述", + "providerDesc": "提供者描述", "templateLoadHint": "選擇模板以快速填充配置。", "lastFailure": "最近失敗", "moveDown": "移動下移", - "providerLabel": "提供商標籤", + "providerLabel": "提供者標籤", "clearCacheFailed": "清除快取失敗", "imagesGenerations": "影像生成", "repairEnvWorking": "修復環境處理中", @@ -465,7 +465,7 @@ "input": "輸入", "testingConnection": "正在測試連線", "excludeDomains": "排除域名", - "webCookieProviders": "Web Cookie 提供商", + "webCookieProviders": "Web Cookie 提供者", "allTestsPassed": "全部測試通過", "openaiCompatibleName": "OpenAI 相容名稱", "warningCostOptimizedPartialPricing": "部分模型缺少定價,成本最佳化結果可能不完整。", @@ -496,7 +496,7 @@ "exampleTemplatesHint": "示例模板提示", "retryDelayLabel": "重試延遲標籤", "audioTranscription": "音訊轉寫", - "fallbackChainsDesc": "定義每個模型的提供商後備順序。", + "fallbackChainsDesc": "定義每個模型的提供者後備順序。", "exampleTemplates": "示例模板", "connectionsCount": "連線數量", "modelsPathLabel": "Model 路徑", @@ -522,7 +522,7 @@ "weighted": "加權", "providers": "Provider", "ccCompatibleLabel": "CC 相容", - "noFallbackChainsDesc": "建立一條鏈路,用於定義某個模型的提供商回退順序。", + "noFallbackChainsDesc": "建立一條鏈路,用於定義某個模型的提供者回退順序。", "yesImport": "確認匯入", "lockoutsAutoRefreshHint": "鎖定自動重新整理提示", "tabApis": "API 標籤頁", @@ -549,7 +549,7 @@ "noModelsYet": "暫無 Model", "signatureFamily": "簽名族", "externalApiCalls": "外部 API 呼叫", - "providerOverridesDesc": "覆蓋每個提供商的超時和重試設定。", + "providerOverridesDesc": "覆蓋每個提供者的超時和重試設定。", "signatureTool": "簽名工具", "connectionFailed": "連線失敗", "activeLimitersPlural": "活躍限制器複數", @@ -592,7 +592,7 @@ "errorDuringImport": "錯誤期間匯入", "comboDefaultsGuideHint2": "這些預設值會在新建 Combo 時使用。", "connecting": "正在連線", - "fillModelAndProviders": "請填寫模型和提供商", + "fillModelAndProviders": "請填寫模型和提供者", "syncing": "正在同步", "resetConfirm": "重置確認", "trackMetrics": "跟蹤指標", @@ -603,9 +603,9 @@ "syncingData": "正在同步資料", "cloudBenefitPorts": "通過 Cloud 暴露埠", "compatibleProviders": "相容 Provider", - "freeTierProviders": "免費層級提供商", + "freeTierProviders": "免費層級提供者", "freeTierLabel": "提供免費套餐", - "freeTierProvidersDesc": "提供免費套餐的提供商 — 有些需要 API 金鑰註冊,有些則根本不需要憑據。", + "freeTierProvidersDesc": "提供免費套餐的提供者 — 有些需要 API 金鑰註冊,有些則根本不需要憑據。", "clearCache": "清除快取", "reqs": "請求數", "addAnthropicCompatible": "新增 Anthropic 相容 Provider", @@ -615,12 +615,12 @@ "millisecondsAbbr": "毫秒縮寫", "backupReasonPreRestore": "恢復前備份", "disableCloud": "停用雲端", - "newProviderNameAria": "新的提供商名稱", + "newProviderNameAria": "新的提供者名稱", "passwordsMismatch": "密碼不匹配", "a2aQuickStartStep3": "A2A快速開始步驟3", "liveMonitorDescriptionSuffix": "或外部 API 呼叫來生成事件。", - "failedAddProvider": "新增提供商失敗", - "addAtLeastOneProvider": "至少新增一個提供商", + "failedAddProvider": "新增提供者失敗", + "addAtLeastOneProvider": "至少新增一個提供者", "reasonSeparator": "原因分隔符", "zedImporting": "正在從 Zed 匯入", "confirmPasswordPlaceholder": "確認密碼佔位符", @@ -657,7 +657,7 @@ "modelLockouts": "模型鎖定", "usingCloudProxy": "正在使用 Cloud 代理", "loadingHealth": "正在載入健康狀態", - "providerOverrides": "提供商覆蓋", + "providerOverrides": "提供者覆蓋", "audioTranscriptionDesc": "音訊轉寫說明", "learnedFromHeaders": "從響應頭學習", "totalRequests": "總計請求", @@ -693,7 +693,7 @@ "batchDetailModel": "型號", "batchDetailWindow": "窗戶", "batchDetailCreated": "已建立", - "providerTopologyEmpty": "尚未連線提供商", + "providerTopologyEmpty": "尚未連線提供者", "badgeToastUnlocked": "徽章已解鎖!", "batchListSearchPlaceholder": "按 ID、端點、型號搜尋...", "batchListDeleteAllCompletedTitle": "刪除所有已完成的批次", @@ -716,7 +716,7 @@ "batchConceptTitle": "批處理", "batchConceptSubtitle": "以約 50% 的成本非同步處理數千個請求(24 小時間隔)。非常適合評估、批次分類和嵌入。", "batchConceptHowItWorks": "工作原理", - "batchConceptBenefit50pct": "輸入 + 輸出令牌享受 50% 折扣", + "batchConceptBenefit50pct": "輸入 + 輸出權杖享受 50% 折扣", "batchConceptAsync24h": "非同步處理,24 小時完成視窗", "batchConceptUseCases": "最適合批次分類、評估和嵌入", "filesConceptTitle": "批處理檔案", @@ -736,7 +736,7 @@ "wizardStep2Input": "輸入", "wizardStep3Validate": "驗證", "wizardStep4Cost": "成本與建立", - "wizardProviderLabel": "提供商", + "wizardProviderLabel": "提供者", "wizardEndpointLabel": "端點", "wizardModelLabel": "模型", "wizardInputKindJsonl": "JSONL", @@ -754,7 +754,7 @@ "wizardCostEstimatedNotice": "估算成本 — 實際計費可能有所不同。", "wizardErrorUpload": "上傳檔案失敗。請重試。", "wizardErrorCreate": "建立批處理失敗。請重試。", - "wizardEmptyProviders": "先連線支援批處理的提供商(OpenAI、Anthropic 或 Gemini)以建立批處理。", + "wizardEmptyProviders": "先連線支援批處理的提供者(OpenAI、Anthropic 或 Gemini)以建立批處理。", "uploadModalTitle": "上傳批處理檔案", "uploadModalDropOrPick": "拖放 .jsonl 檔案或點選選擇", "uploadModalUpload": "上傳", @@ -769,7 +769,7 @@ "batchActionDownloadOutput": "下載輸出", "batchActionDownloadErrors": "下載錯誤", "batchActionRetry": "重試失敗的", - "batchActionRetryConfirm": "重試 {n} 個失敗請求?最終成本取決於實際消耗的令牌。", + "batchActionRetryConfirm": "重試 {n} 個失敗請求?最終成本取決於實際消耗的權杖。", "expirationBadgeCritical": "緊急", "expirationBadgeWarning": "即將", "expirationBadgeNormal": "待處理", @@ -782,7 +782,7 @@ "filesListUsedByRoleError": "錯誤", "filesListSizeColumn": "大小", "batchListProgressPartial": "(部分)", - "batchListProviderColumn": "提供商", + "batchListProviderColumn": "提供者", "batchListProviderUnknown": "—", "batchListProviderOther": "其他", "batchListTitle": "批處理", @@ -818,9 +818,9 @@ "wizardCostInputTok": "輸入 tok", "wizardCostOutputTok": "輸出 tok", "wizardCostWindow": "視窗", - "wizardDestinationSelectProvider": "選擇提供商…", + "wizardDestinationSelectProvider": "選擇提供者…", "wizardDestinationSelectModel": "選擇模型…", - "wizardDestinationConnectProvider": "連線提供商", + "wizardDestinationConnectProvider": "連線提供者", "batchListBatchCreated": "批處理 {id} 已建立 — 重新整理列表中…", "batchListBatchCreatedDismiss": "關閉", "batchListRefreshing": "重新整理中…", @@ -843,15 +843,100 @@ "batchDetailCancelConfirm": "取消此批處理?進行中的請求將停止。", "batchActionCancelError": "取消批處理失敗。請重試。", "batchActionRetryError": "重試失敗請求失敗。請重試。", - "batchConceptRetentionNote": "結果和錯誤檔案保留 30 天(Anthropic:29 天)" + "batchConceptRetentionNote": "結果和錯誤檔案保留 30 天(Anthropic:29 天)", + "addModel": "新增模型", + "batchFileDetailLoadError": "載入檔案內容時發生錯誤", + "batchFileInUseByActiveBatch": "檔案正由作用中的批次使用中", + "batchFilePurpose": { + "assistants": "助手", + "batch": "批次輸入", + "batch-output": "批次輸出", + "fine-tune": "微調" + }, + "batchFilesAllPurposes": "所有用途", + "batchFilesCount": "{count, plural, one {# 個檔案} other {# 個檔案}}", + "batchFilesExpires": "到期日", + "batchFilesFilename": "檔案名稱", + "batchFilesNeverExpires": "永不", + "batchFilesNoneFound": "找不到任何檔案", + "batchFilesPurpose": "用途", + "capsLockOn": "Caps Lock 已開啟", + "confirmAction": "確認", + "confirmTitle": "確認", + "dismissNotification": "關閉通知", + "effortExtraHigh": "極高", + "effortHigh": "高", + "effortLow": "低", + "effortMax": "最大", + "effortMedium": "中", + "effortNone": "無", + "effortUltra": "極致", + "modelAliases": "模型別名", + "nextPage": "下一頁", + "previousPage": "上一頁", + "reasoningEffort": "推理程度", + "selectOption": "選取一個選項", + "toggleColumns": "切換欄位", + "tokensAmount": "數量", + "tokensConnectServer": "連線伺服器", + "tokensConnectServerFailed": "連線伺服器失敗", + "tokensConnecting": "正在連線...", + "tokensCreateInvite": "建立邀請", + "tokensCreateInviteFailed": "建立邀請失敗", + "tokensCreatingInvite": "正在建立...", + "tokensDate": "日期", + "tokensDisconnect": "中斷連線", + "tokensDisconnectServerFailed": "中斷伺服器連線失敗", + "tokensFrom": "來自", + "tokensInviteCreated": "邀請碼已建立", + "tokensInviteUses": "{used}/{max} 次使用", + "tokensLastSync": "上次同步:{date}", + "tokensNoServersConnected": "尚未連線任何伺服器。連線至社群伺服器以分享排行榜。", + "tokensReason": "原因", + "tokensReceived": "已接收", + "tokensRedeem": "兌換", + "tokensRedeemFailed": "兌換邀請失敗", + "tokensRedeemSuccess": "邀請已兌換。伺服器:{server}", + "tokensRedeeming": "正在兌換...", + "tokensRevoke": "撤銷", + "tokensRevokeInviteFailed": "撤銷邀請失敗", + "tokensRevoked": "已撤銷", + "tokensSending": "正在傳送...", + "tokensSent": "已傳送", + "tokensServerStatus": { + "connected": "已連線", + "disconnected": "已中斷連線", + "error": "錯誤", + "pending": "處理中", + "syncing": "正在同步" + }, + "tokensTo": "至", + "tokensTransferFailed": "轉移失敗", + "tokensTransferSuccess": "轉移成功({idempotencyKey})", + "wireApi": "Wire API", + "wizardCsvApplyMapping": "套用對應", + "wizardCsvContentMapped": "內容欄位已對應(messages、input 或 prompt)", + "wizardCsvCustomIdMapped": "custom_id 已對應", + "wizardCsvIgnoreColumn": "— 忽略 —", + "wizardCsvNoColumns": "CSV 標題中未偵測到任何欄位。", + "wizardCsvRowError": "第 {row} 行:{reason}", + "wizardCsvRowsParsed": "已解析 {count} 行", + "wizardCsvRowsSkipped": "已跳過 {count} 行", + "wizardValidating": "正在驗證…", + "wizardValidationDuplicateIds": "偵測到重複的 custom_id:", + "wizardValidationErrorCount": "發現 {count, plural, one {# 個錯誤} other {# 個錯誤}}", + "wizardValidationFirstErrors": "錯誤(前 {count} 個):", + "wizardValidationLine": "第 {line} 行", + "wizardValidationParseFailed": "驗證失敗 — 無法解析內容。", + "wizardValidationSummary": "{lines} 行 · {ids} 個不重複 custom_id" }, - "disabled": "__MISSING__:Disabled", - "featureFlagOmnirouteEmergencyFallbackDescription": "__MISSING__:Route budget-exhausted requests to the emergency free fallback provider/model.", - "featureFlagArenaEloSyncEnabledDescription": "__MISSING__:Enable periodic Arena AI leaderboard ELO sync for model intelligence rankings.", + "disabled": "已停用", + "featureFlagOmnirouteEmergencyFallbackDescription": "將預算耗盡的請求路由到緊急免費備用提供者/模型。", + "featureFlagArenaEloSyncEnabledDescription": "啟用定期 Arena AI 排行榜 ELO 同步,用於模型智慧排名。", "sidebar": { "home": "首頁", "dashboard": "儀表板", - "providers": "提供商", + "providers": "提供者", "combos": "組合", "usage": "用量", "analytics": "分析", @@ -971,11 +1056,11 @@ "monitoringSection": "監控", "auditSecuritySection": "審計與安全", "devtoolsSection": "開發工具", - "configurationSection": "配置", + "configurationSection": "設定", "aiFeaturesSection": "AI 功能", "mcp": "MCP", "a2a": "A2A", - "plugins": "__MISSING__:Plugins", + "plugins": "外掛程式", "apiEndpoints": "API 端點", "batchFiles": "檔案", "analyticsEvals": "評估", @@ -985,9 +1070,9 @@ "analyticsCompression": "壓縮", "costsBudget": "預算", "costsFreeTiers": "免費層預算", - "costsFreeTiersSubtitle": "每月免費令牌配額", - "freeProviderRankings": "__MISSING__:Free Provider Rankings", - "freeProviderRankingsSubtitle": "__MISSING__:Best free providers ranked by model ELO scores", + "costsFreeTiersSubtitle": "每月免費權杖配額", + "freeProviderRankings": "免費提供者排名", + "freeProviderRankingsSubtitle": "根據模型 ELO 分數排名的最佳免費提供者", "costsQuotaShare": "配額共享", "costsPricing": "定價", "logsProxy": "代理日誌", @@ -999,16 +1084,16 @@ "settingsAppearance": "外觀", "settingsAi": "AI 設定", "settingsSecurity": "安全", - "settingsAccessTokens": "__MISSING__:Access Tokens", + "settingsAccessTokens": "存取令牌", "settingsFeatureFlags": "功能標誌", "settingsAuthz": "授權", "settingsRouting": "路由", "settingsResilience": "彈性", - "modelLockout": "__MISSING__:Model Lockout", + "modelLockout": "模型鎖定", "settingsAdvanced": "高階", "omniProxySection": "OmniProxy", - "quotaTracker": "提供商配額", - "providerQuota": "提供商配額", + "quotaTracker": "提供者配額", + "providerQuota": "提供者配額", "runtime": "執行時", "consoleLogs": "控制台日誌", "globalRouting": "全域性路由", @@ -1025,9 +1110,9 @@ "auditGroup": "審計", "batchGroup": "批處理", "homeSubtitle": "儀表盤概覽", - "providersSubtitle": "管理 AI 提供商", + "providersSubtitle": "管理 AI 提供者", "quotaTrackerSubtitle": "跟蹤使用限制", - "providerQuotaSubtitle": "跟蹤提供商使用限制", + "providerQuotaSubtitle": "跟蹤提供者使用限制", "runtimeSubtitle": "即時彈性與會話", "contextCombosSubtitle": "組合壓縮引擎", "cliToolsSubtitle": "配置 CLI 執行時", @@ -1039,19 +1124,19 @@ "oneProxySubtitle": "公共代理閘道器", "leaderboard": "排行榜", "profile": "個人資料", - "tokens": "令牌", + "tokens": "權杖", "leaderboardSubtitle": "排名與成就", "profileSubtitle": "帳戶與偏好", - "tokensSubtitle": "令牌使用和預算", + "tokensSubtitle": "權杖使用和預算", "usageSubtitle": "流量和使用統計", "analyticsComboHealthSubtitle": "組合目標可靠性", - "analyticsUtilizationSubtitle": "提供商利用率", + "analyticsUtilizationSubtitle": "提供者利用率", "costsSubtitle": "支出明細", "cacheSubtitle": "快取命中率", - "analyticsCompressionSubtitle": "令牌節省統計", + "analyticsCompressionSubtitle": "權杖節省統計", "analyticsSearchSubtitle": "搜尋工具分析", "analyticsEvalsSubtitle": "評估套件結果", - "providerStats": "提供商統計", + "providerStats": "提供者統計", "providerStatsSubtitle": "延遲和效能指標", "logsSubtitle": "應用日誌", "logsProxySubtitle": "代理流量日誌", @@ -1060,7 +1145,7 @@ "healthSubtitle": "系統健康檢查", "costsPricingSubtitle": "按模型定價規則", "costsBudgetSubtitle": "預算限制", - "costsQuotaShareSubtitle": "跨金鑰共享提供商配額", + "costsQuotaShareSubtitle": "跨金鑰共享提供者配額", "auditLogSubtitle": "授權審計", "auditMcpSubtitle": "MCP 伺服器審計", "auditA2aSubtitle": "A2A 協議審計", @@ -1072,7 +1157,7 @@ "agentSkillsSubtitle": "A2A 技能登錄檔", "mcpSubtitle": "MCP 伺服器控制", "a2aSubtitle": "A2A 協議伺服器", - "pluginsSubtitle": "__MISSING__:Plugin marketplace & installs", + "pluginsSubtitle": "外掛程式市集與安裝", "mediaSubtitle": "快取的媒體檔案", "batchFilesSubtitle": "批處理輸入/輸出檔案", "settingsSubtitle": "所有設定", @@ -1083,7 +1168,7 @@ "settingsResilienceSubtitle": "重試與斷路器", "settingsAdvancedSubtitle": "高階使用者選項", "settingsSecuritySubtitle": "認證與加密", - "settingsAccessTokensSubtitle": "__MISSING__:Scoped CLI tokens for remote mode", + "settingsAccessTokensSubtitle": "遠端模式的範圍 CLI 令牌", "settingsFeatureFlagsSubtitle": "切換系統功能", "settingsSidebar": "側邊欄", "settingsSidebarSubtitle": "自定義側邊欄佈局", @@ -1092,7 +1177,7 @@ "issuesSubtitle": "報告錯誤", "changelogSubtitle": "釋出說明", "costsQuotaPlans": "計劃與配額", - "costsQuotaPlansSubtitle": "按提供商配置計劃", + "costsQuotaPlansSubtitle": "按提供者配置計劃", "activity": "活動", "activitySubtitle": "近期事件的友好動態", "logsGroup": "日誌", @@ -1118,8 +1203,23 @@ "cannotHide": "此項無法隱藏", "alwaysVisible": "始終可見", "groupSeparatorLabel": "隔斷", - "discovery": "__MISSING__:Discovery", - "discoverySubtitle": "__MISSING__:Scan providers for free access" + "discovery": "探索", + "discoverySubtitle": "掃描提供者以尋找免費存取", + "chaosConfig": "混沌模式", + "chaosConfigSubtitle": "多模型並行執行", + "combosLiveSubtitle": "即時路由串聯", + "compressionStudioSubtitle": "即時引擎串聯", + "contextAggressiveSubtitle": "摘要與時效管理", + "contextCcrSubtitle": "檢索標記", + "contextHeadroomSubtitle": "表格壓縮", + "contextLiteSubtitle": "快速空白清理", + "contextLlmlinguaSubtitle": "語義修剪", + "contextOmniglyph": "OmniGlyph", + "contextOmniglyphSubtitle": "以圖像呈現上下文", + "contextSessionDedupSubtitle": "跨輪次去重", + "contextSettingsSubtitle": "全域預設值", + "contextUltraSubtitle": "啟發式修剪", + "mainNavigation": "主導覽" }, "webhooks": { "title": "Webhook", @@ -1177,7 +1277,7 @@ "step1Desc": "選擇此 Webhook 要對接的目標整合系統。" }, "howItWorks": { - "step1": "選擇一個整合提供商(如 Slack、Discord、自定義 Webhook)並配置連線詳情。", + "step1": "選擇一個整合提供者(如 Slack、Discord、自定義 Webhook)並配置連線詳情。", "step2": "配置要訂閱的系統事件(如補全錯誤、模型回退或用量限制)。", "step3": "傳送測試負載以驗證端點是否能正常接收資料。", "step4": "使用 Webhook 金鑰對 X-Webhook-Signature HMAC-SHA256 頭進行驗證,以確保端點安全。", @@ -1245,9 +1345,9 @@ "tutorial": "如何建立 Slack Webhook:" }, "telegram": { - "botToken": "Telegram 機器人令牌", + "botToken": "Telegram 機器人權杖", "botTokenPlaceholder": "123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ", - "botTokenHint": "建立機器人時從 @BotFather 獲取的 HTTP API 令牌。", + "botTokenHint": "建立機器人時從 @BotFather 獲取的 HTTP API 權杖。", "chatId": "Telegram 聊天 ID / 頻道", "chatIdPlaceholder": "-100123456789 或 @channelname", "chatIdHint": "聊天/頻道的唯一數字識別符號或公開使用者名稱。", @@ -1260,7 +1360,7 @@ "complianceTab": "合規", "mcpTab": "MCP 審計", "title": "合規審計", - "description": "合規審計日誌記錄的策略、訪問、提供商和安全事件。", + "description": "合規審計日誌記錄的策略、訪問、提供者和安全事件。", "eventType": "事件型別", "eventTypePlaceholder": "按操作或事件型別篩選", "severity": "嚴重級別", @@ -1358,8 +1458,8 @@ "service.reveal_api_key": "服務 API 金鑰已洩露", "settings.update": "設定已更新", "settings.update_failed": "設定更新失敗", - "sync.token.created": "同步令牌已建立", - "sync.token.revoked": "同步令牌已撤銷" + "sync.token.created": "同步權杖已建立", + "sync.token.revoked": "同步權杖已撤銷" } }, "themesPage": { @@ -1374,8 +1474,8 @@ "header": { "logout": "退出登入", "language": "語言", - "providers": "提供商", - "providerDescription": "管理 AI 提供商連線", + "providers": "提供者", + "providerDescription": "管理 AI 提供者連線", "combos": "組合", "comboDescription": "支援故障回退的模型組合", "usage": "用量與分析", @@ -1400,9 +1500,9 @@ "mediaDescription": "生成影像、影片和音樂", "themes": "主題", "themesDescription": "為整個儀表板選擇顏色主題", - "costsDescription": "跟蹤支出,分析趨勢,管理所有 AI 提供商的預算", - "cacheDescription": "監控提供商提示快取效率和本地語義響應複用。", - "limitsDescription": "為每個 API 金鑰和提供商配置速率限制和配額", + "costsDescription": "跟蹤支出,分析趨勢,管理所有 AI 提供者的預算", + "cacheDescription": "監控提供者提示快取效率和本地語義響應複用。", + "limitsDescription": "為每個 API 金鑰和提供者配置速率限制和配額", "runtimeDescription": "即時執行時可觀測性 — 斷路器、冷卻、模型鎖定、會話和配額告警", "apiManagerDescription": "管理 OmniRoute 例項的 API 金鑰和訪問控制", "batchDescription": "通過批次 API 呼叫非同步處理大量請求", @@ -1416,22 +1516,22 @@ "skillsDescription": "安裝和管理沙箱技能,實現自動提示和執行", "agentSkillsDescription": "代理就緒技能目錄,一鍵複製 URL 以整合 AI 客戶端", "translatorDescription": "跨 API 格式翻譯和測試提示:OpenAI ↔ Claude ↔ Gemini", - "playgroundDescription": "互動式測試提示,即時檢視提供商響應和格式檢查", - "searchToolsDescription": "搜尋分析、提供商細分、快取命中率和成本跟蹤", + "playgroundDescription": "互動式測試提示,即時檢視提供者響應和格式檢查", + "searchToolsDescription": "搜尋分析、提供者細分、快取命中率和成本跟蹤", "logsDescription": "即時請求日誌、錯誤追蹤和流式事件檢查器", "auditDescription": "API 金鑰使用、MCP 工具呼叫和策略事件的合規審計追蹤", "webhooksDescription": "配置 Webhook 端點以接收即時事件通知", - "healthDescription": "系統健康概覽:提供商、斷路器、速率限制和資料庫", - "proxyDescription": "為出站提供商連線配置上游代理設定", + "healthDescription": "系統健康概覽:提供者、斷路器、速率限制和資料庫", + "proxyDescription": "為出站提供者連線配置上游代理設定", "apiEndpointsDescription": "管理自定義 API 端點配置和路由覆蓋", "batchFilesDescription": "瀏覽和管理批處理作業輸出檔案和結果", "analyticsEvalsDescription": "模型評估結果和效能基準", "analyticsSearchDescription": "搜尋查詢分析、快取命中率和成本跟蹤", - "analyticsUtilizationDescription": "提供商利用率指標和容量規劃", + "analyticsUtilizationDescription": "提供者利用率指標和容量規劃", "analyticsComboHealthDescription": "組合路由配置的即時健康與效能", - "analyticsCompressionDescription": "上下文壓縮分析和令牌節省", - "costsBudgetDescription": "每個 API 金鑰和提供商的預算限制和支出告警", - "costsPricingDescription": "用於令牌成本計算的自定義定價配置", + "analyticsCompressionDescription": "上下文壓縮分析和權杖節省", + "costsBudgetDescription": "每個 API 金鑰和提供者的預算限制和支出告警", + "costsPricingDescription": "用於權杖成本計算的自定義定價配置", "logsProxyDescription": "上游代理請求日誌和流量檢查", "logsConsoleDescription": "應用控制台輸出和除錯日誌", "logsActivityDescription": "使用者操作和系統事件的審計追蹤", @@ -1470,27 +1570,32 @@ "settingsAdvancedDescription": "高階負載規則、請求限制和代理 API 設定", "mitmProxyDescription": "配置 MITM 代理設定以進行流量檢查和除錯", "oneProxyDescription": "配置 1Proxy 設定以實現高階代理鏈", - "omniSkillsDescription": "安裝和管理用於自動提示和工具執行的沙箱技能" + "omniSkillsDescription": "安裝和管理用於自動提示和工具執行的沙箱技能", + "openQuickNavigation": "開啟快速導覽", + "quickNavigation": "快速導覽", + "quickNavigationTitle": "快速導覽(⌘K / Ctrl+K)", + "switchToDarkMode": "切換至深色模式", + "switchToLightMode": "切換至淺色模式" }, "home": { "quickStart": "快速入門", - "quickStartDesc": "4 個步驟快速上手:連線提供商、路由模型並監控全域性執行情況。", + "quickStartDesc": "4 個步驟快速上手:連線提供者、路由模型並監控全域性執行情況。", "fullDocs": "完整文件", "step1Title": "1. 建立 API 金鑰", "step1Desc": "前往 端點 -> 已註冊金鑰。為每個環境生成一個獨立金鑰。", - "step2Title": "2. 連線提供商", - "step2Desc": "在 提供商 中新增帳戶。支援 OAuth、API Key 和免費套餐。", + "step2Title": "2. 連線提供者", + "step2Desc": "在 提供者 中新增帳戶。支援 OAuth、API Key 和免費套餐。", "step3Title": "3. 配置客戶端", "step3Desc": "在 IDE 或 API 客戶端中將基本 URL 設定為 {url}。", "step4Title": "4. 監控與最佳化", "step4Desc": "在 請求日誌分析 中跟蹤 Token、成本與錯誤。", - "providersOverview": "提供商概覽", - "configuredOf": "{total} 個可用提供商中已配置 {configured} 個", - "noModelsAvailable": "該提供商當前沒有可用模型。", - "noProvidersConfigured": "尚未配置提供商", - "addProvider": "新增提供商", + "providersOverview": "提供者概覽", + "configuredOf": "{total} 個可用提供者中已配置 {configured} 個", + "noModelsAvailable": "該提供者當前沒有可用模型。", + "noProvidersConfigured": "尚未配置提供者", + "addProvider": "新增提供者", "configureFirst": "首先在 {providers} 中配置連線", - "configureProvider": "配置提供商", + "configureProvider": "配置提供者", "modelAvailable": "{count} 模型可用", "modelsAvailable": "{count} 個模型可用", "connectionsActive": "{count} 連線處於活動狀態", @@ -1511,20 +1616,20 @@ "updateAvailableDesc": "有新版本可用。點選更新。", "updateStarted": "更新已開始...", "reloadingPageAutomatically": "自動重新載入頁面...", - "providerTopology": "提供商拓撲" + "providerTopology": "提供者拓撲" }, "analytics": { "title": "分析", "usageAnalyticsTitle": "用量分析", "diversityScoreTitle": "供應商多元化", - "diversityScoreDesc": "近期流量視窗內提供商集中度的快照。", + "diversityScoreDesc": "近期流量視窗內提供者集中度的快照。", "diversityShannonEntropy": "夏農熵", "diversityWindow": "視窗:{count} 次請求 · 最近 {mins} 分鐘", "diversityHealthy": "分佈健康", "diversityRiskHigh": "供應商鎖定風險高", "diversityRiskModerate": "分佈一般", "diversityScoreLabel": "得分", - "diversityHigherExplanation": "數值越高,表示流量分散到的提供商越多。", + "diversityHigherExplanation": "數值越高,表示流量分散到的提供者越多。", "diversityNoData": "暫無近期用量資料。", "chartRequests": "請求數", "chartInput": "輸入", @@ -1537,15 +1642,15 @@ "chartCostPct": "佔成本 {pct}%", "chartUsageDetail": "用量明細", "chartCacheRead": "快取讀取", - "chartCostByProvider": "按提供商劃分的成本", + "chartCostByProvider": "按提供者劃分的成本", "chartNoCostData": "無成本資料", "chartModelUsageOverTime": "模型用量趨勢", "chartNoData": "無資料", "chartWeekly": "每週", "chartModelBreakdown": "按模型拆分", "chartModel": "模型", - "chartProvider": "提供商", - "chartProviderBreakdown": "按提供商拆分", + "chartProvider": "提供者", + "chartProviderBreakdown": "按提供者拆分", "filterAllKeys": "全部金鑰", "filterSearchKeys": "搜尋金鑰…", "filterNoKeysMatch": "沒有匹配的金鑰", @@ -1575,7 +1680,7 @@ "estCost": "預估成本", "infraTitle": "基礎設施", "infraAccounts": "帳號", - "infraProviders": "提供商", + "infraProviders": "提供者", "infraApiKeys": "API 金鑰", "infraModels": "模型", "perfTitle": "效能", @@ -1585,17 +1690,17 @@ "perfFastReq": "Fast 請求", "highlightsTitle": "亮點", "highlightsTopModel": "熱門模型", - "highlightsTopProvider": "熱門提供商", + "highlightsTopProvider": "熱門提供者", "highlightsBusiestDay": "最繁忙的一天", "highlightsDiversity": "多樣性", "highlightsFallbackRate": "回退率", "customRange": "自定義", - "overviewDescription": "監控所有提供商和模型的 API 使用模式、令牌消耗、成本和活動趨勢。", + "overviewDescription": "監控所有提供者和模型的 API 使用模式、權杖消耗、成本和活動趨勢。", "evalsDescription": "執行評估套件來測試和驗證您的 LLM 端點。比較模型質量、檢測迴歸和基準延遲。", "overview": "概述", "evals": "評測", "utilization": "利用率", - "utilizationDescription": "提供商配額使用趨勢和速率限制跟蹤", + "utilizationDescription": "提供者配額使用趨勢和速率限制跟蹤", "modelStatus": "模型狀態", "modelStatusCooldown": "冷卻中", "modelStatusUnavailable": "不可用", @@ -1603,13 +1708,13 @@ "comboHealth": "組合健康狀況", "comboHealthDescription": "組合級別配額、使用分佈和效能指標", "compressionAnalyticsTitle": "壓縮分析", - "compressionAnalyticsDescription": "壓縮分析 — token 節省、模式分佈和提供商統計。", + "compressionAnalyticsDescription": "壓縮分析 — token 節省、模式分佈和提供者統計。", "autoRoutingTotalAutoRequests": "自動請求總數", "autoRoutingAvgSelectionScore": "平均選擇分數", "autoRoutingExplorationRate": "探索率", "autoRoutingLkgpHitRate": "LKGP命中率", "autoRoutingRequestsByVariant": "變體請求", - "autoRoutingTopRoutedProviders": "頂級路由提供商", + "autoRoutingTopRoutedProviders": "頂級路由提供者", "comboHealthWorstQuotaLeft": "剩餘最差名額", "comboHealthUsageSkew": "使用偏差", "comboHealthSuccessRate": "成功率", @@ -1634,19 +1739,207 @@ "compressionAnalyticsPromptTokens": "提示標記", "compressionAnalyticsCompletionTokens": "完成標記", "compressionAnalyticsTotalTokens": "代幣總數", - "compressionAnalyticsCacheTokens": "快取令牌", + "compressionAnalyticsCacheTokens": "快取權杖", "compressionAnalyticsNoDataYet": "還沒有壓縮資料", "searchAnalyticsTotalSearches": "總搜尋次數", "searchAnalyticsCacheHitRate": "快取命中率", "searchAnalyticsTotalCost": "總成本", "searchAnalyticsAvgResponse": "平均響應", "searchAnalyticsNoSearchesYet": "還沒有搜尋", - "providerUtilizationTitle": "提供商利用率", + "providerUtilizationTitle": "提供者利用率", "providerUtilizationFailedToLoad": "無法載入利用率資料", "providerUtilizationNoData": "沒有可用的利用率資料", "providerUtilizationGettingStarted": "開始使用", "providerUtilizationLatestSnapshot": "最新配額快照", - "providerUtilizationRemainingCapacity": "剩餘容量" + "providerUtilizationRemainingCapacity": "剩餘容量", + "account": "帳戶", + "activityCellTitle": "{date}:{tokens} 個 Token", + "activityLess": "較少", + "activityMore": "較多", + "activitySummary": "{active} 個活躍天數 · {tokens} 個 Token · {days} 天", + "chartApiKey": "API 金鑰", + "chartApiKeyBreakdown": "API 金鑰明細", + "chartByAccount": "依帳戶", + "chartByApiKey": "依 API 金鑰", + "chartDate": "日期", + "chartRequestsByProviderDate": "依供應商與日期的請求", + "combo": "組合", + "comboHealthActionable": "{count} 個可操作", + "comboHealthAutopilotDescription": "來自組合健康狀態、預測、配額和供應商健康狀態的優先建議。", + "comboHealthAutopilotState": { + "degraded": "需要注意", + "down": "已中斷", + "healthy": "健康" + }, + "comboHealthAutopilotTitle": "組合健康自動駕駛儀", + "comboHealthConfidence": "{level} 信心水準", + "comboHealthCostHistory": "歷史 {total} · {daily}/天", + "comboHealthDaysToExhaust": "{days} 天後耗盡", + "comboHealthDegraded": "降級", + "comboHealthDown": "已中斷", + "comboHealthExecutionTargetsDescription": "結構化組合目標的步驟層級執行時期指標和配額可見性。", + "comboHealthFactor": { + "contextAffinity": "上下文", + "costInv": "成本", + "health": "健康狀態", + "latencyInv": "延遲", + "quota": "配額", + "resetWindowAffinity": "重設視窗", + "specificityMatch": "特定性", + "stability": "穩定性", + "taskFit": "任務契合度", + "tierAffinity": "層級契合度", + "tierPriority": "層級" + }, + "comboHealthFetchFailed": "擷取組合健康資料失敗", + "comboHealthForecastDescription": "從歷史組合流量和配額快照進行的線性預測。", + "comboHealthForecastHorizon": "{value} 預測", + "comboHealthForecastTitle": "成本與配額預測", + "comboHealthGiniCoefficient": "吉尼係數", + "comboHealthHealthy": "健康", + "comboHealthIntro": "依組合監控配額壓力、模型使用傾斜度和傳遞效能。", + "comboHealthIssueCount": "問題 {count} 項", + "comboHealthIssues": "問題", + "comboHealthLatencyValue": "延遲 {value} 毫秒", + "comboHealthModelProviderCount": "{models} 個模型橫跨 {providers} 個供應商", + "comboHealthNoActiveIssues": "在所選範圍內未偵測到作用中的組合健康問題。", + "comboHealthNoData": "無可用的組合健康資料", + "comboHealthNoDataDescription": "組合配額快照和路由請求將在流量開始流動後顯示於此。", + "comboHealthNoDepletionEstimate": "無耗盡預估", + "comboHealthNoInspectableTargets": "此組合無可檢查的目標。", + "comboHealthPerformance": "效能", + "comboHealthPerformanceDescription": "路由組合流量的可靠性和輸送量。", + "comboHealthPricingCoverage": "定價涵蓋範圍", + "comboHealthProjectedCost": "預估成本", + "comboHealthProjectedQuota": "預估配額", + "comboHealthProjectedRequests": "預估請求數", + "comboHealthQuotaHealthDescription": "各供應商中最低的剩餘配額,附帶短期趨勢訊號。", + "comboHealthQuotaRisk": "{level} 配額風險", + "comboHealthQuotaScope": "配額範圍:{scope}", + "comboHealthQuotaValue": "配額 {value}", + "comboHealthReadOnlyRecompute": "唯讀重新計算", + "comboHealthRemainingQuota": "剩餘配額 {value}", + "comboHealthRequestCount": "{count} 次請求", + "comboHealthRequestShort": "{count} 次", + "comboHealthRequestsInRange": "所選範圍內 {count} 次", + "comboHealthScoringDescription": "使用目前健康狀態、預測和路由啟發式對目標排名進行的因素層級說明。", + "comboHealthScoringInspector": "智慧評分檢查器", + "comboHealthSelectedRank": "已選取排名第 1", + "comboHealthShareSummary": "請求佔比 {requests} · Token 佔比 {tokens}", + "comboHealthStepAutomatic": "健康指標將在請求路由時自動顯示", + "comboHealthStepCreate": "在組合中使用多個供應商建立組合", + "comboHealthStepSend": "傳送請求至組合端點以產生流量資料", + "comboHealthTask": "任務:{task}", + "comboHealthTracking": "正在追蹤 {range} 內的 {count} 個組合", + "comboHealthTraffic": "流量", + "comboHealthTrend": { + "declining": "下降中", + "improving": "改善中", + "stable": "穩定" + }, + "comboHealthTrendValue": "趨勢:{trend}", + "comboHealthUsageSkewDescription": "此組合內的模型請求佔比與 Token 佔比。", + "comboHealthWorstProjectedQuota": "最差預估配額", + "compressionAnalyticsChartPoint": "{hour}:{count} 個請求,省下 {tokens} 個 Token", + "compressionAnalyticsInfo": "壓縮分析:按模式(關閉、精簡、標準、積極、極致、RTK、堆疊)、引擎、壓縮組合與供應商追蹤 Token 節省量。將滑鼠懸停在圖表上查看詳細資訊。使用時間選擇器檢視不同時間範圍。", + "compressionAnalyticsLast24HoursActivity": "過去 24 小時(活動)", + "compressionAnalyticsLoading": "正在載入壓縮分析…", + "compressionAnalyticsMaxRequests": "每小時最大請求數:{count}", + "compressionAnalyticsMaxTokens": "每小時最大 Token 數:{count}", + "compressionAnalyticsModeBreakdown": "模式分佈", + "compressionAnalyticsModeStats": "{count} 個請求 · 省下 {tokens} 個 Token", + "compressionAnalyticsNoDataDescription": "啟用壓縮後,透過 /v1/chat/completions 發出第一個請求後,壓縮請求資料便會顯示在此處。", + "compressionAnalyticsProviderBreakdown": "供應商分佈", + "compressionAnalyticsRealTokens": "{count} 個實際 Token", + "compressionAnalyticsRealUsageReceipts": "實際用量收據", + "compressionAnalyticsSkipped": " · {count} 個已跳過(無操作)", + "compressionAnalyticsSources": "來源", + "compressionAnalyticsStartTracking": "使用 POST /v1/chat/completions 並搭配壓縮設定,即可開始追蹤壓縮分析。", + "compressionAnalyticsValidationRestores": "驗證還原", + "connection": "連線", + "datedTokenCount": "{date} · {tokens} 個 Token", + "direct": "直接", + "latency": "延遲", + "model": "模型", + "mostActiveDay": "最活躍日", + "noDataLast7Days": "過去 7 天內無資料", + "notAvailable": "不適用", + "provider": "供應商", + "providerUtilizationAccountSplit": "帳戶拆分", + "providerUtilizationGlobalView": "全域檢視", + "providerUtilizationLoading": "正在載入使用率資料…", + "providerUtilizationNoDataDescription": "收集使用率資料後,供應商配額快照便會顯示在此處。", + "providerUtilizationStepAutomatic": "資料將在收集配額快照時自動顯示", + "providerUtilizationStepConnect": "透過 OAuth 或 API 金鑰在供應商中連線供應商", + "providerUtilizationStepEnable": "在組合或直接請求中使用該供應商,以啟用配額追蹤", + "rangeAllTime": "全部時間", + "rangeLast24h": "過去 24 小時", + "rangeLast30d": "過去 30 天", + "rangeLast7d": "過去 7 天", + "remainingQuota": "剩餘配額", + "requestTokenSummary": "{requests} 個請求 · {tokens} 個 Token", + "retry": "重試", + "retrying": "正在重試…", + "routeAvgTargetLatency": "平均目標延遲", + "routeCallLogsExact": "精確呼叫日誌", + "routeConfidence": "信心度 {confidence}", + "routeDecisionSummary": "決策摘要", + "routeDiffersFromTop": "與目前首選不同", + "routeEvidence": "證據", + "routeExactRuntimeLog": "精確執行日誌", + "routeExplainFailed": "無法解釋路由", + "routeFactors": "路由因素", + "routeFactorsSubtitle": "用於此說明的加權訊號", + "routeFallbackTimeline": "備援與目標時間軸", + "routeFallbackTimelineSubtitle": "根據此請求附近保存的呼叫日誌推斷", + "routeFetchLogsFailed": "無法取得請求日誌", + "routeLimitations": "限制", + "routeMatchesTopTarget": "符合目前首選目標", + "routeNoKnownLimitations": "此說明無已知限制。", + "routeNoRecomputeCandidates": "無法為此請求重新計算組合候選排名。", + "routeNoRelatedEvidence": "尚未保存相關目標證據。", + "routeNoRequestLogs": "無可用請求日誌", + "routeNoRequestLogsDescription": "請先透過 OmniRoute 傳送流量。路由說明是根據保存的結構化呼叫日誌所產生。", + "routeNoStep": "無步驟", + "routeNoStepId": "無步驟 ID", + "routeNotComboRouted": "未使用組合路由", + "routeReadOnlyRecompute": "唯讀重新計算", + "routeRecentSuccess": "近期成功", + "routeRecommendations": "建議", + "routeRuntimeRankNow": "目前執行排名", + "routeRuntimeScoreNow": "目前執行評分", + "routeScore": "路由評分", + "routeSelectedTarget": "已選目標", + "routeStep": "步驟", + "routeTargetMissingNow": "目前目標遺失", + "routeTokenCounts": "輸入 {input} · 輸出 {output}", + "routeTopNow": "目前首選", + "routeTrace": "路由追蹤", + "routeTraceDescription": "檢視保存的請求追蹤記錄:已選目標、路由因素、備援證據、目前評分重播、延遲、Token 與目標健康狀態。", + "routeTraceRequestLog": "請求日誌", + "routeTraceTitle": "路由追蹤檢視", + "routeUnableToLoad": "無法載入路由說明", + "routeWeight": "權重 {weight}%", + "routeWhyTarget": "為何選此目標?", + "routeWhyTargetSubtitle": "精確執行中繼資料及唯讀評分重播", + "routeWouldSelectNow": "目前會選擇", + "runtime": "執行時間", + "search": "搜尋", + "sectionsAria": "分析區塊", + "selected": "已選取", + "statusExhausted": "已耗盡", + "statusHealthy": "健康", + "statusLow": "偏低", + "tokens": "Token", + "unknown": "未知", + "unknownApiKey": "未知的 API 金鑰", + "unknownError": "未知錯誤", + "utilizationRange": { + "1h": "過去 1 小時", + "24h": "過去 24 小時", + "30d": "過去 30 天", + "7d": "過去 7 天" + } }, "apiManager": { "title": "API 金鑰", @@ -1701,9 +1994,9 @@ "ownUsageVisibilityDesc": "允許此金鑰呼叫狀態端點,檢視自身美元用量、預算佔比和 Token 總量。", "sharedAccountQuotaVisibility": "共享帳號配額", "sharedAccountQuotaVisibilityDesc": "當配置了一個明確連線時,允許此金鑰檢視共享上游帳號配額。", - "localUsageCommand": "__MISSING__:Allow local usage command", - "localUsageCommandDesc": "__MISSING__:Allows this API key to use @@om-usage to retrieve cached usage and quota information without calling an upstream provider.", - "localUsageCommandBadge": "__MISSING__:Usage cmd", + "localUsageCommand": "允許本機使用量指令", + "localUsageCommandDesc": "允許此 API 金鑰使用 @@om-usage 來擷取快取的使用量和配額資訊,而無需呼叫上游提供者。", + "localUsageCommandBadge": "使用量命令", "keyCreated": "API 金鑰已建立", "keyCreatedSuccess": "金鑰建立成功!", "keyCreatedNote": "請立即複製並儲存此金鑰,它不會再次顯示。", @@ -1713,7 +2006,7 @@ "allEndpointsAllowed": "此金鑰可以訪問所有 API 端點。", "endpointsRestricted": "僅限 {count} 個端點。", "autoResolve": "自動解析", - "autoResolveDesc": "為這個 API 金鑰自動將有歧義的模型名解析到原生提供商。", + "autoResolveDesc": "為這個 API 金鑰自動將有歧義的模型名解析到原生提供者。", "streamDefaultMode": "流預設相容性", "streamDefaultModeDesc": "此鍵省略了 `stream` 標誌。JSON 模式返回非流式響應,除非客戶端明確請求 SSE。", "streamDefaultLegacy": "遺留", @@ -1744,7 +2037,7 @@ "selected": "已選擇 {count} 個", "all": "全部", "clear": "清空", - "searchModels": "按名稱或提供商搜尋模型...", + "searchModels": "按名稱或提供者搜尋模型...", "noModelsFound": "未找到模型", "keyNameRequired": "金鑰名稱不能為空", "keyNameTooLong": "金鑰名稱長度不能超過 {max} 個字元", @@ -1763,8 +2056,8 @@ "copyMaskedKey": "複製遮罩後的金鑰", "keyOnlyAvailableAtCreation": "完整金鑰僅會在建立時顯示一次,請在首次建立時立即複製儲存", "modelsCount": "{count, plural, one {# 個模型} other {# 個模型}}", - "devicesCount": "__MISSING__:{count, plural, one {# device} other {# devices}}", - "devicesTooltip": "__MISSING__:{count, plural, one {# distinct IP/User-Agent device seen with this key (last 30 min)} other {# distinct IP/User-Agent devices seen with this key (last 30 min)}}", + "devicesCount": "{count, plural, one {# 個裝置} other {# 個裝置}}", + "devicesTooltip": "{count, plural, one {此金鑰看到 # 個不同 IP/User-Agent 裝置(最近 30 分鐘)} other {此金鑰看到 # 個不同 IP/User-Agent 裝置(最近 30 分鐘)}}", "lastUsedOn": "最近使用:{date}", "viewCostsFor": "檢視 {name} 的費用", "editPermissions": "編輯許可權", @@ -1813,7 +2106,19 @@ "normalKeysSection": "普通鍵", "quotaKeysSection": "配額金鑰", "quotaPill": "配額", - "quotaModeOnly": "僅配額" + "quotaModeOnly": "僅配額", + "allCombosAllowed": "此金鑰可使用任何組合。", + "allowedCombos": "允許的組合", + "bypassProviderQuota": "繞過供應商配額限制", + "bypassProviderQuotaDescription": "允許此金鑰在路由期間忽略上游供應商/帳戶的限制政策。API 金鑰的美元配額仍然適用。", + "expandClaudeCodeFamilies": "展開 Claude Code 系列", + "maxActiveSessionsDescription": "0 = 無限制。當此金鑰超過並行黏著工作階段時,回傳 429。", + "removeClaudeCodeDefault": "移除 Claude Code 預設", + "restrictCatalogUnavailable": "模型目錄不可用;此金鑰有 {selectedCount} 個選定的模型限制。", + "restrictLoading": "正在載入模型目錄…", + "restrictedComboCount": "限制為 {count} 個組合。", + "throttleDelay": "節流延遲", + "throttleDelayDescription": "在此金鑰的請求被路由之前,加入固定延遲。0 = 不減速。" }, "auditLog": { "title": "稽核日誌", @@ -1845,7 +2150,7 @@ "generate": "生成", "generating": "生成中...", "loadingModels": "正在載入可用模型...", - "noModels": "暫無可用模型。請先配置支援媒體能力的提供商。", + "noModels": "暫無可用模型。請先配置支援媒體能力的提供者。", "error": "生成失敗", "result": "結果", "imageDescription": "使用 OpenAI、xAI、Together、Hyperbolic、SD WebUI、ComfyUI 等根據文本提示生成影像。", @@ -1861,15 +2166,66 @@ "webFetch": "網頁抓取", "video": "影片", "music": "音樂", - "ocr": "__MISSING__:OCR" + "ocr": "OCR" }, - "noProviders": "尚未為此型別配置任何提供商。", + "noProviders": "尚未為此型別配置任何提供者。", "addConnection": "新增連線", - "backToProviders": "返回提供商列表", + "backToProviders": "返回提供者列表", "connections": "{count} 個連線", - "noConnections": "暫無連線 —— 請從提供商頁面新增。", + "noConnections": "暫無連線 —— 請從提供者頁面新增。", "loading": "正在載入...", - "suggestedModels": "__MISSING__:Suggested models from provider" + "suggestedModels": "供應商建議的模型", + "apiKeyRequired": "需要 API 金鑰", + "audioVideoFile": "音訊/視訊檔案", + "audioVideoFileHint": "支援最高 4 GB 的音訊和視訊檔案", + "configureApiKeys": "在提供者中設定 API 金鑰", + "credentialsRequired": "需要在提供者中的 API 金鑰", + "disabled": "已停用", + "downloadFormat": "下載 {format}", + "emptyTranscription": "轉錄回傳空文字。音訊可能不包含可辨識的語音,或「{provider}」API 金鑰可能無效。請檢查儀表板 → 日誌 → 代理以了解詳情。", + "enhanceThisImage": "增強此圖片", + "enhancementInstructionsPlaceholder": "選擇性增強說明...", + "enterPrompt": "請輸入提示詞。", + "enterTextToSynthesize": "請輸入要合成的文字。", + "failedToReadFile": "讀取檔案失敗", + "fileTooLarge": "檔案過大({size})。允許的最大值:{max}。", + "format": "格式", + "generateModality": "生成 {modality}", + "generatedImageAlt": "已生成的圖片 {index}", + "generationFailed": "生成失敗", + "imageGeneration": "圖片生成", + "imagePromptPlaceholder": "日落時分的寧靜山景...", + "imageToText": "圖片轉文字", + "imageToTextComingSoon": "當 /api/v1/images/understanding 實作後,即可使用內嵌的圖片轉文字遊樂場。", + "maskImage": "遮罩圖片", + "maskImageHint": "可選。由支援遮罩的 inpaint 類型模型使用。", + "musicGeneration": "音樂生成", + "musicPromptPlaceholder": "活潑的電子音樂,搭配合成器音墊...", + "noImagesReturned": "未回傳任何圖片。提供者可能已接受請求,但回傳了空的資料。", + "noSpeechDetected": "在音訊檔案中未偵測到語音。如果您上傳的是音樂或無聲檔案,請嘗試上傳包含說話內容的音訊檔案。提供者:\"{provider}\"。", + "noTextReturned": "未回傳任何文字", + "promptOptional": "提示詞(選填)", + "provider": "提供者", + "providerCount": "{count} 個提供者", + "requestFailed": "請求失敗({status})", + "save": "儲存", + "selectAudioToTranscribe": "請選擇要轉錄的音訊檔案。", + "sourceImage": "來源圖片", + "sourceImageHint": "適用於以圖生圖、編輯和放大工作流程(選填)。", + "speechTextPlaceholder": "您好!歡迎使用 OmniRoute,您的智慧 AI 閘道...", + "synthesizeSpeech": "合成語音", + "synthesizing": "正在合成...", + "text": "文字", + "textToSpeech": "文字轉語音", + "topazRequiresImage": "Topaz 需要輸入圖片。", + "transcribeAudio": "轉錄音訊", + "transcribing": "正在轉錄...", + "transcription": "轉錄", + "transcriptionPlaceholder": "上傳要轉錄的音訊檔案...", + "videoGeneration": "影片生成", + "videoPromptPlaceholder": "花朵綻放的縮時攝影...", + "voice": "語音", + "wordTimestamps": "逐詞時間戳記({count} 詞)" }, "search": { "searchQuery": "搜尋詞", @@ -1877,15 +2233,15 @@ "cachedResult": "已快取", "searchCost": "成本", "searchTools": "搜尋工具", - "searchToolsDesc": "支援提供商對比的高階搜尋測試", - "compareProviders": "對比提供商", + "searchToolsDesc": "支援提供者對比的高階搜尋測試", + "compareProviders": "對比提供者", "rerankResults": "結果重排", "searchHistory": "搜尋歷史", "urlOverlap": "URL 重疊度", - "noSearchProviders": "尚未配置搜尋提供商。請前往設定新增。", + "noSearchProviders": "尚未配置搜尋提供者。請前往設定新增。", "noRerankModels": "沒有可用的重排模型", "webSearch": "網頁搜尋", - "provider": "提供商", + "provider": "提供者", "searchType": "搜尋型別", "maxResults": "最大結果數", "filters": "篩選條件", @@ -1932,26 +2288,26 @@ "scrapeConceptTitle": "抓取", "scrapeConceptDesc": "抓取 = 提取 URL 的完整內容(Markdown、文本或 HTML)", "compareConceptTitle": "比較", - "compareConceptDesc": "比較 = 跨 N 個提供商並排執行相同查詢,比較延遲、成本和結果重疊", + "compareConceptDesc": "比較 = 跨 N 個提供者並排執行相同查詢,比較延遲、成本和結果重疊", "rerankConceptTitle": "重新排序", "rerankConceptDesc": "重新排序 = 通過 LLM 重新排序結果以基於查詢提高相關性", "autoConceptTitle": "自動(最便宜)", - "autoConceptDesc": "自動(最便宜)= 自動選擇具有已配置憑據的最便宜可用提供商", - "providerCatalogTitle": "提供商目錄", + "autoConceptDesc": "自動(最便宜)= 自動選擇具有已配置憑據的最便宜可用提供者", + "providerCatalogTitle": "提供者目錄", "kindSearch": "搜尋", "kindFetch": "獲取/抓取", "statusConfigured": "已配置", "statusMissing": "無憑據", "statusRateLimited": "速率受限", - "configureProvider": "在提供商中配置", - "loadingProviders": "載入提供商…", - "failedToLoadProviders": "載入提供商失敗", + "configureProvider": "在提供者中配置", + "loadingProviders": "載入提供者…", + "failedToLoadProviders": "載入提供者失敗", "costPerQuery": "成本/查詢", "freeQuota": "免費配額/月", "scrapeUrl": "要抓取的 URL", "scrapeUrlPlaceholder": "https://example.com", - "scrapeUrlRequired": "__MISSING__:URL is required", - "scrapeUrlInvalid": "__MISSING__:Invalid URL — it must start with http:// or https://", + "scrapeUrlRequired": "需要 URL", + "scrapeUrlInvalid": "無效的 URL — 必須以 http:// 或 https:// 開頭", "scrapeExtract": "提取", "scrapeExtracting": "提取中…", "scrapeFullPage": "整頁", @@ -1963,10 +2319,10 @@ "scrapeRaw": "原始", "scrapeContentTruncated": "(已截斷,檢視原始)", "scrapeMetadata": "後設資料", - "scrapeProvider": "提供商", + "scrapeProvider": "提供者", "scrapeSize": "大小", - "scrapeEmptyState": "__MISSING__:Enter a URL to extract its content", - "scrapeProvidersAvailable": "__MISSING__:Available providers: Firecrawl, Jina Reader, Tavily.", + "scrapeEmptyState": "輸入 URL 以擷取其內容", + "scrapeProvidersAvailable": "可用提供者:Firecrawl、Jina Reader、Tavily、TinyFish。", "compareRun": "比較", "compareRunning": "比較中…", "autoProvider": "自動(最便宜)", @@ -1976,12 +2332,64 @@ "searchTypeLabel": "型別", "rerankModelLabel": "重新排序模型", "noneOption": "無", - "size": "大小" + "size": "大小", + "audioFormats25Mb": "mp3、wav、m4a、ogg、flac — 最大 25 MB", + "chooseFile": "選擇檔案…", + "clear": "清除", + "closeRawModal": "關閉原始內容模態", + "compareEmptyDescription": "結果將並排顯示,包含延遲、成本與 URL 重疊程度", + "compareEmptyTitle": "選擇供應商並輸入查詢以進行比較", + "compareProviderHint": "在比較標籤頁中選擇最多 4 個供應商,以進行並排比較。", + "compareQuery": "要比較的查詢", + "compareQueryPlaceholder": "2026 年人工智慧趨勢", + "compareResults": "結果 —「{query}」", + "configuration": "設定", + "configurationPane": "設定面板", + "configureMoreProviders": "設定更多供應商", + "configureProviders": "設定供應商", + "contentTruncated": "內容已截斷至 256 KB(原始大小:{size})", + "documentUrl": "文件 URL", + "embedding": "嵌入", + "embeddingSample": "哈囉,世界!", + "failed": "失敗", + "fileTooLarge25Mb": "檔案過大 — 最大 25 MB", + "history": "歷史記錄", + "historyHint": "歷史記錄可在搜尋標籤頁中使用。", + "httpError": "錯誤 {status}", + "image": "圖片", + "imageSample": "日落時分的寧靜山景", + "links": "連結", + "maxCompareProviders": "一次最多可比較 {count} 個供應商。", + "music": "音樂", + "musicSample": "輕快爵士鋼琴搭配輕量打擊樂", + "noActiveProvider": "無作用中的搜尋供應商", + "noActiveProviderDescription": "無作用中的搜尋供應商。請在供應商中設定一個。", + "noAudioUrl": "回應中無音訊 URL:{response}", + "noResults": "無結果", + "overlapSummary": "{first} vs {second}:{overlap} 個共通項目", + "rawContent": "原始內容 — {size}", + "rawScrapedContent": "原始爬取內容", + "requestFailed": "請求失敗", + "resultCount": "{count} 筆結果", + "selectAll": "全選", + "selectAudioFirst": "請先選擇音訊檔案。", + "selectedProviders": "供應商(已選 {count} 個):", + "sharedResult": "共通", + "sharedResultTitle": "與其他供應商共通", + "speechToText": "語音轉文字", + "status": "狀態", + "textToSpeech": "文字轉語音", + "ttsSample": "哈囉,這是一段文字轉語音測試。", + "video": "影片", + "videoSample": "山脈上空雲層的延時攝影", + "viewFullRaw": "檢視完整原始內容", + "webFetch": "網頁擷取", + "webSearchSample": "什麼是 OmniRoute AI 閘道?" }, "cliTools": { "title": "CLI 工具", - "noActiveProviders": "當前沒有活躍的提供商", - "noActiveProvidersDesc": "請先新增並連線提供商以配置 CLI 工具。", + "noActiveProviders": "當前沒有活躍的提供者", + "noActiveProvidersDesc": "請先新增並連線提供者以配置 CLI 工具。", "mapModels": "對映模型", "testConnection": "測試連線", "connectionStatus": "連線狀態", @@ -2023,7 +2431,7 @@ "cliRuntimeNotDetected": "未檢測到 CLI 執行時", "binary": "二進位制", "configPath": "配置路徑", - "configPathShort": "配置", + "configPathShort": "設定", "failedCheckRuntimeStatus": "無法檢查執行時狀態。", "copy": "複製", "copied": "已複製", @@ -2050,7 +2458,7 @@ "antigravityStep2Prefix": "2. 新增", "antigravityStep2Suffix": "新增到您的主機檔案中作為 127.0.0.1。", "antigravityStep3": "3. 開啟 Antigravity,請求就會被代理。", - "mitmHowWorksDesc": "{toolName} 會先向原始提供商端點發起請求,隨後由 MITM 攔截並重定向到 OmniRoute。", + "mitmHowWorksDesc": "{toolName} 會先向原始提供者端點發起請求,隨後由 MITM 攔截並重定向到 OmniRoute。", "mitmStep1": "1. 啟動 MITM,讓請求經由 OmniRoute 路由。", "mitmStep2Prefix": "2. 將", "mitmStep2Suffix": "新增到你的 hosts 檔案,並指向 127.0.0.1。", @@ -2084,7 +2492,7 @@ "current": "當前", "baseUrlPlaceholder": "https://.../v1", "resetToDefault": "重置為預設值", - "providerModelPlaceholder": "提供商/模型 ID", + "providerModelPlaceholder": "提供者/模型 ID", "apply": "應用", "reset": "重置", "manualConfig": "手動配置", @@ -2097,10 +2505,10 @@ "applied": "已應用!", "failed": "失敗", "resetDone": "重置!", - "omnirouteConfiguredOpenAiCompatible": "OmniRoute 已配置為 OpenAI 相容提供商", - "provider": "提供商", + "omnirouteConfiguredOpenAiCompatible": "OmniRoute 已配置為 OpenAI 相容提供者", + "provider": "提供者", "model": "模型", - "providers": "提供商", + "providers": "提供者", "auth": "授權", "noApiKeysAvailable": "沒有可用的 API 金鑰", "usingDefaultOmniroute": "使用預設值:sk_omniroute", @@ -2165,7 +2573,25 @@ "amp": "Sourcegraph Amp 程式設計助手 CLI", "hermes": "Hermes AI 終端助手", "hermes-agent": "Hermes Agent (by Nousresearch) — 支援多模型(委派、視覺、壓縮等)的高階終端 AI。", - "custom": "通用 OpenAI 相容 CLI 或 SDK 配置生成器" + "custom": "通用 OpenAI 相容 CLI 或 SDK 配置生成器", + "agent-deck": "Agent Deck 多代理協調器", + "aider": "Aider AI 結對程式設計 CLI,支援 OpenAI 相容的基礎 URL", + "codewhale": "CodeWhale 程式代理,DeepSeek TUI 的後繼者", + "crush": "Crush 終端機程式代理,由 Charm 開發", + "cursor-cli": "Cursor Agent CLI 的無頭代理模式", + "deepseek-tui": "以 Rust 撰寫的 DeepSeek TUI 程式代理", + "forge": "ForgeCode 程式代理 CLI,支援自訂提供者", + "goose": "Goose 自主代理 CLI", + "grok-build": "xAI Grok Build TUI 程式代理,支援自訂提供者", + "interpreter": "Open Interpreter 自主程式代理 CLI", + "jcode": "jcode 終端機程式代理", + "letta": "Letta CLI 代理,具備持久記憶與工具使用能力", + "omp": "Oh My Pi 終端機程式代理", + "pi": "輕量級 Pi 終端機程式代理", + "qwen": "阿里巴巴 Qwen Code CLI", + "roo": "Roo Code AI 助手,適用於 VS Code", + "smelt": "Smelt 程式代理 CLI", + "warp": "Warp AI 終端機,支援自訂提供者" }, "guides": { "cursor": { @@ -2272,7 +2698,7 @@ "desc": "點選 Windsurf 中的 AI Settings 圖示,或前往 Settings" }, "2": { - "title": "新增自定義提供商", + "title": "新增自定義提供者", "desc": "選擇 \"Add custom provider\" (OpenAI 相容)" }, "3": { @@ -2319,29 +2745,100 @@ "copilotConfigGenerator": "GitHub Copilot 配置生成器", "copilotApiKey": "API金鑰", "copilotFilterModelsPlaceholder": "過濾器型號...", - "copilotMaxInputTokens": "最大輸入令牌數", + "copilotMaxInputTokens": "最大輸入權杖數", "copilotMaxOutputTokens": "最大輸出代幣", "copilotToolCalling": "工具呼叫", "copilotPasteInto": "貼上到:", "wireApiChatCompletions": "聊天完成 (/chat/completions)", "wireApiResponses": "響應 API (/responses)", - "reasoningEffort": "__MISSING__:Reasoning effort for {model}", - "reasoningEffortDefault": "__MISSING__:Default", - "reasoningEffortHint": "__MISSING__:Default preserves the reasoning effort sent by the agent", + "reasoningEffort": "{model} 的推理努力", + "reasoningEffortDefault": "預設", + "reasoningEffortHint": "預設保留代理傳送的推理努力", "reasoningEffortTier": { - "none": "__MISSING__:None", - "low": "__MISSING__:Low", - "medium": "__MISSING__:Medium", - "high": "__MISSING__:High", - "xhigh": "__MISSING__:XHigh" - } + "none": "無", + "low": "低", + "medium": "中等", + "high": "高", + "xhigh": "極高" + }, + "addModel": "新增模型", + "advancedOptions": "進階選項", + "classifierCompatCycle": "循環:關閉 → 自動 → 始終", + "classifierCompatDescription": "以合成允許回應繞過 Claude Code 的 --permission-mode auto 安全分類器,使備援路由不會因關閉而失敗。預設為關閉。", + "classifierCompatLoadFailed": "無法載入設定", + "classifierCompatMode": { + "always": "始終", + "auto": "自動", + "off": "關閉" + }, + "classifierCompatTitle": "自動權限分類器相容性", + "claudeProfiles": "Claude Code 設定檔", + "claudeProfilesDescription": "在模型探索後重新產生每個 ~/.claude/profiles/…/settings.json。", + "codexProfiles": "Codex 設定檔", + "codexProfilesDescription": "在模型探索後重新產生 ~/.codex/*.config.toml。", + "copilotCompatibilityWarning": "此設定使用 Azure 廠商解決方案來處理自訂模型清單。已在 VS Code ≥ 1.109GitHub Copilot Chat ≥ v0.37 上測試。未來的擴充功能更新可能會改變此行為。", + "copilotCopyConfigForModels": "複製設定({count} 個模型)", + "copilotGeneratorDescriptionPrefix": "產生", + "copilotGeneratorDescriptionSuffix": "VS Code GitHub Copilot 的區塊,使用 Azure 廠商模式。選取您想要的模型,然後將 JSON 複製到您的設定檔中。", + "copilotReloadInstruction": "然後重新載入 VS Code 並在輸入提示中設定 API 金鑰。", + "copilotSelectModels": "選取模型({selected}/{total})", + "effortExtraHigh": "極高", + "effortHigh": "高", + "effortLow": "低", + "effortMax": "最高", + "effortMedium": "中", + "effortNone": "無", + "effortUltra": "極致", + "failedSave": "無法儲存", + "hermesApply": "套用至 Hermes Agent", + "hermesApplyModelToAll": "將 {model} 套用至所有角色", + "hermesConfiguredRoles": "{configured}/{total} 個角色", + "hermesFirstSetupTitle": "首次透過 OmniRoute 設定於 {date}", + "hermesNotOmniRoute": "{provider}(非 OmniRoute)", + "hermesPreviewFailed": "無法產生預覽", + "hermesPreviewPath": "預覽——將寫入至 ~/.hermes/config.yaml", + "hermesQuickApply": "快速將相同模型套用至所有角色:", + "hermesRemovePendingRole": "從待定變更中移除此角色", + "hermesRoleApproval": "核准", + "hermesRoleApprovalDesc": "安全與核准決策", + "hermesRoleCompression": "壓縮", + "hermesRoleCompressionDesc": "提示壓縮與摘要", + "hermesRoleDefault": "預設(主要)", + "hermesRoleDefaultDesc": "主要對話模型", + "hermesRoleDelegation": "委派(子代理)", + "hermesRoleDelegationDesc": "協調器與子代理模型", + "hermesRoleSkillsHub": "技能中心", + "hermesRoleSkillsHubDesc": "技能與工具使用推理", + "hermesRoleVision": "視覺", + "hermesRoleVisionDesc": "圖片與螢幕截圖理解", + "hermesRoleWebExtract": "網頁擷取", + "hermesRoleWebExtractDesc": "網頁內容擷取", + "hermesRolesWillUpdate": "{count, plural, one {# 個角色將被更新} other {# 個角色將被更新}}", + "hermesSaveDescription": "將選取的模型為每個角色儲存至", + "hermesSavedTo": "已儲存至 {path}", + "hermesSelectBeforePreview": "在預覽前,請先為角色選取模型,或確認角色已載入。", + "hermesSinceSetup": "設置後 {time}", + "hermesViaOmniRoute": "{provider} (經由 OmniRoute)", + "loadingModels": "正在載入模型...", + "modelAliases": "模型別名", + "networkError": "網路錯誤", + "other": "其他", + "preview": "預覽", + "profileSyncDescription": "提供者模型同步後,自動從即時目錄重新產生 CLI 工具設定檔。預設為關閉——僅寫入設定檔;使用中的/預設設定絕不會被變更。", + "profileSyncLoadFailed": "載入設定失敗", + "profileSyncTitle": "CLI 設定檔自動同步", + "refreshAll": "全部重新整理", + "routeModelPlaceholder": "將 {model} 路由至...", + "selectAll": "全選", + "vision": "視覺", + "wireApi": "Wire API" }, "combos": { "title": "組合", "description": "建立支援權重路由與故障回退的模型組合", "autoCatalogTitle": "自動路由目錄", "autoCatalogTemplateCount": "{count} 個模板", - "autoCatalogDescription": "內建 auto/* 組合,從已連線的提供商動態解析。直接將這些 ID 作為 model 欄位使用——無需設定。", + "autoCatalogDescription": "內建 auto/* 組合,從已連線的提供者動態解析。直接將這些 ID 作為 model 欄位使用——無需設定。", "autoCatalogExpand": "展開自動路由目錄", "autoCatalogCollapse": "收起自動路由目錄", "createCombo": "建立組合", @@ -2362,8 +2859,8 @@ "leastLatency": "最低延遲", "comboName": "組合名稱", "comboNamePlaceholder": "my-combo", - "comboDescription": "__MISSING__:Description", - "comboDescriptionPlaceholder": "__MISSING__:Optional note describing this combo", + "comboDescription": "說明", + "comboDescriptionPlaceholder": "描述此組合的選用備註", "deleteConfirm": "刪除這個組合?", "noCombosYet": "還沒有組合", "comboCreated": "組合建立成功", @@ -2403,16 +2900,16 @@ "resetAwareDesc": "平衡剩餘配額與 5 小時和每週重置,然後對相似分數進行迴圈賽", "strictRandom": "嚴格隨機", "strictRandomDesc": "洗牌池模式:每個模型使用一次後再重新洗牌", - "fusion": "__MISSING__:Fusion", - "fusionDesc": "__MISSING__:Fans the prompt to every panel model in parallel, then a judge model synthesizes one final answer", - "fusionJudgeModel": "__MISSING__:Judge model", - "fusionJudgeModelHelp": "__MISSING__:Model that synthesizes the panel answers into one final response. Leave empty to use the first panel model.", - "fusionMinPanel": "__MISSING__:Min panel", - "fusionMinPanelHelp": "__MISSING__:Successful panel answers required before stragglers get a grace window (default 2).", - "fusionStragglerGraceMs": "__MISSING__:Straggler grace (ms)", - "fusionStragglerGraceMsHelp": "__MISSING__:How long to wait for slow panel models once quorum is reached (default 8000).", - "fusionPanelHardTimeoutMs": "__MISSING__:Panel hard timeout (ms)", - "fusionPanelHardTimeoutMsHelp": "__MISSING__:Absolute cap so one hung model can't stall the whole panel (default 90000).", + "fusion": "融合", + "fusionDesc": "將提示並行發送給面板中的每個模型,然後由評判模型綜整出一個最終答案", + "fusionJudgeModel": "評判模型", + "fusionJudgeModelHelp": "將小組回答整合為最終回應的模型。留空則使用第一個小組模型。", + "fusionMinPanel": "最少小組", + "fusionMinPanelHelp": "在落後者獲得寬限期之前所需的最少成功小組回答數(預設為 2)。", + "fusionStragglerGraceMs": "落後者寬限(毫秒)", + "fusionStragglerGraceMsHelp": "達到法定人數後等待緩慢小組模型的時間(預設 8000)。", + "fusionPanelHardTimeoutMs": "面板硬性逾時(毫秒)", + "fusionPanelHardTimeoutMsHelp": "絕對上限,防止單一卡住的模型拖慢整個面板(預設值 90000)。", "models": "模型", "autoBalance": "自動平衡", "advancedSettings": "高階設定", @@ -2425,8 +2922,8 @@ "contextRelayMaxMessagesHelp": "限制壓縮排接力摘要中的最近歷史訊息數量。", "contextRelaySummaryModel": "摘要模型", "contextRelaySummaryModelHelp": "僅用於生成交接摘要的可選覆蓋模型。留空則複用當前活躍的 combo 模型。", - "contextRelayProviderNote": "Context Relay 當前主要為 Codex 帳戶輪換生成交接摘要。與同一提供商的多個帳戶配合使用時,連續性效果最佳。", - "advancedHint": "留空則使用全域性預設值。這些設定會覆蓋每個提供商的配置。", + "contextRelayProviderNote": "Context Relay 當前主要為 Codex 帳戶輪換生成交接摘要。與同一提供者的多個帳戶配合使用時,連續性效果最佳。", + "advancedHint": "留空則使用全域性預設值。這些設定會覆蓋每個提供者的配置。", "failoverBeforeRetry": "重試之前進行故障轉移", "maxSetRetries": "最大設定重試次數", "setRetryDelayMs": "設定重試延遲(毫秒)", @@ -2493,8 +2990,8 @@ "example": "示例:在接近配額耗盡時輪換多個帳戶的 Codex 會話。" }, "fill-first": { - "when": "當您希望在轉移到下一個提供商之前完全耗盡一個提供商的配額時使用。", - "avoid": "當您需要在提供商之間進行請求級負載均衡時避免使用。", + "when": "當您希望在轉移到下一個提供者之前完全耗盡一個提供者的配額時使用。", + "avoid": "當您需要在提供者之間進行請求級負載均衡時避免使用。", "example": "示例:在使用完所有 200 美元的 Deepgram 額度後再回退到 Groq。" }, "auto": { @@ -2517,13 +3014,13 @@ "maxRetries": "請求失敗前最多會嘗試多少次重試。", "retryDelay": "兩次重試之間的初始等待時間。值越大越能降低突發壓力。", "timeout": "請求被中止前允許的最長持續時間。", - "healthcheck": "路由決策時跳過不健康的模型或提供商。", + "healthcheck": "路由決策時跳過不健康的模型或提供者。", "concurrencyPerModel": "輪詢模式下每個模型允許的最大併發請求數。", "queueTimeout": "請求在佇列中等待超時前允許停留的最長時間。", "failoverBeforeRetry": "啟用後,任何上游錯誤都會觸發立即故障轉移到下一個組合目標,跳過所有重試和回退 URL。", "maxSetRetries": "每個目標失敗時重試完整目標集的次數。 0 = 沒有設定級別重試。", "setRetryDelayMs": "設定級別重試嘗試之間的延遲,為暫時性問題提供解決時間。", - "disableSessionStickiness": "__MISSING__:Rotate to a different connection on every request instead of pinning a whole conversation to one connection by the first-message hash. Overrides the global default. Leave on Inherit to preserve prompt-cache hits for multi-turn chats." + "disableSessionStickiness": "在每次請求時輪換到不同的連線,而不是根據首則訊息雜湊將整個對話固定到單一連線。覆寫全域預設值。保留為「繼承」以保留多輪對話的提示快取命中效果。" }, "templatesTitle": "快捷模板", "templatesDescription": "先套用一個初始模板,再按需調整模型和配置。", @@ -2579,10 +3076,10 @@ "routingInputs": "路由輸入", "routingInputsHint": "模式包與權重保留在此處;熔斷器執行狀態保留在健康頁面。", "emailVisibilityHint": "此處帳號郵箱遵循全域性隱私開關。", - "emailVisibilityTooltip": "使用眼睛圖示可在組合、提供商與配額頁面間全域性切換帳號郵箱的顯示與隱藏。", + "emailVisibilityTooltip": "使用眼睛圖示可在組合、提供者與配額頁面間全域性切換帳號郵箱的顯示與隱藏。", "manualModel": "手動模型", "manualModelInvalid": "請按 provider/model 格式填寫。", - "manualModelUnknownProvider": "未知的提供商字首。", + "manualModelUnknownProvider": "未知的提供者字首。", "builderDynamicAccountShort": "動態帳號", "builderNeedValidName": "請先填寫有效的組合名稱再繼續。", "statusOverview": "狀態概覽", @@ -2627,7 +3124,7 @@ "description": "使用一個主模型,並保持回退鏈路簡短且可靠。", "tip1": "把最可靠的模型放在第一位。", "tip2": "保留 1 到 2 個質量相近的備用模型。", - "tip3": "開啟安全重試,吸收臨時性的提供商故障。" + "tip3": "開啟安全重試,吸收臨時性的提供者故障。" }, "weighted": { "title": "可控流量分配", @@ -2666,8 +3163,8 @@ }, "reset-aware": { "title": "重置感知帳戶輪換", - "description": "根據重置時間平衡剩餘的提供商配額。", - "tip1": "對具有配額遙測的提供商使用顯式帳戶步驟或帳戶標籤路由。", + "description": "根據重置時間平衡剩餘的提供者配額。", + "tip1": "對具有配額遙測的提供者使用顯式帳戶步驟或帳戶標籤路由。", "tip2": "當短期疲勞風險更大時,調整訓練次數與每週重量。", "tip3": "保持領帶較小,這樣等值帳戶仍然可以公平地輪換。" }, @@ -2679,9 +3176,9 @@ "tip3": "非常適合在多個 API 帳戶之間做負載均衡。" }, "fill-first": { - "description": "在切換到鏈中的下一個提供商之前,先耗盡一個提供商的配額。", + "description": "在切換到鏈中的下一個提供者之前,先耗盡一個提供者的配額。", "tip1": "按免費配額大小排列模型 — 最大的放在第一位。", - "tip2": "啟用健康檢查以跳過已耗盡的提供商。", + "tip2": "啟用健康檢查以跳過已耗盡的提供者。", "tip3": "非常適合免費層級堆疊(Deepgram → Groq → NIM)。", "title": "配額耗盡策略" }, @@ -2700,15 +3197,15 @@ "title": "歷史路由" }, "context-optimized": { - "description": "基於上下文視窗使用情況和令牌效率最佳化路由。", + "description": "基於上下文視窗使用情況和權杖效率最佳化路由。", "tip1": "將長對話路由到具有更大上下文視窗的模型。", - "tip2": "監控上下文利用率以避免令牌浪費。", + "tip2": "監控上下文利用率以避免權杖浪費。", "tip3": "最適合需要大量上下文保留的對話式 AI。", "title": "上下文最佳化" }, "context-relay": { "description": "當預期帳戶輪換且下一個帳戶必須繼承簡化的任務摘要時效果最佳。", - "tip1": "與為同一模型系列輪換帳戶的提供商配合使用。", + "tip1": "與為同一模型系列輪換帳戶的提供者配合使用。", "tip2": "將交接閾值設定在硬配額截止值以下,以便有時間生成摘要。", "tip3": "僅在主模型太貴或不穩定時,才設定專用摘要模型。", "title": "會話連續性優先" @@ -2722,11 +3219,11 @@ } }, "templateFreeStack": "免費棧($0)", - "templateFreeStackDesc": "在所有免費提供商之間進行輪詢:Kiro(Claude)、Qoder(5 個模型)、Qwen(4 個模型)。零成本,編碼不中斷。", + "templateFreeStackDesc": "在所有免費提供者之間進行輪詢:Kiro(Claude)、Qoder(5 個模型)、Qwen(4 個模型)。零成本,編碼不中斷。", "auto": "自動組合", "autoDesc": "自愈型智慧路由池(效能最佳化)", "lkgp": "LKGP 模式", - "lkgpDesc": "最後已知良好提供商(可預測的彈性)", + "lkgpDesc": "最後已知良好提供者(可預測的彈性)", "wizardGuideTitle": "組合入門指南", "wizardGuideDesc": "建立模型組合以智慧路由 AI 流量", "wizardGuideHint": "或點選上方「+ 建立組合」", @@ -2738,7 +3235,7 @@ "wizardStep3Title": "選擇策略", "wizardStep3Desc": "選擇請求在模型之間的分發方式 — 提供 13 種策略", "wizardStep4Title": "審查並儲存", - "wizardStep4Desc": "審查您的配置並激活組合", + "wizardStep4Desc": "審查您的配置並啟用組合", "emailVisibilityStateOn": "開啟", "emailVisibilityStateOff": "關閉", "reorderHandle": "拖拽排序", @@ -2751,7 +3248,7 @@ }, "steps": { "label": "步驟", - "description": "提供商、模型和帳戶選擇" + "description": "提供者、模型和帳戶選擇" }, "strategy": { "label": "策略", @@ -2772,16 +3269,16 @@ "builderStageLocked": "已鎖定 — 請先完成上一步", "builderTitle": "構建組合", "builderBrowseCatalog": "瀏覽目錄", - "builderProvider": "提供商", - "builderLoadingProviders": "載入提供商中...", - "builderSelectProvider": "選擇提供商", + "builderProvider": "提供者", + "builderLoadingProviders": "載入提供者中...", + "builderSelectProvider": "選擇提供者", "builderModel": "模型", "builderSelectModel": "選擇模型", - "builderProviderFirst": "請先選擇提供商", + "builderProviderFirst": "請先選擇提供者", "builderAccount": "帳戶", "builderPreview": "預覽", "builderAddStep": "新增步驟", - "builderDuplicateExact": "該提供商/模型/帳戶步驟已存在於組合中。", + "builderDuplicateExact": "該提供者/模型/帳戶步驟已存在於組合中。", "builderComboRef": "組合引用", "builderAddComboRef": "新增組合引用", "builderComboRefStep": "新增組合引用步驟", @@ -2791,24 +3288,24 @@ "reviewStrategy": "策略", "reviewSteps": "步驟", "reviewAccounts": "帳戶", - "reviewProviders": "提供商", + "reviewProviders": "提供者", "reviewComboRefs": "組合引用", "reviewAdvanced": "高階設定", "reviewAgentFlags": "Agent 標誌", "reviewSequence": "模型序列", "reviewNoSteps": "未配置任何步驟", "builderStagesDescription": "按順序完成各個階段以定義組合、構建步驟、選擇路由策略並審查結果。", - "builderStepsDescription": "按順序構建每個組合步驟:提供商、模型,然後是帳戶。這允許在不同帳戶上重複使用相同的提供商和模型。", - "selectProvider": "選擇提供商", - "selectProviderPlaceholder": "選擇提供商", + "builderStepsDescription": "按順序構建每個組合步驟:提供者、模型,然後是帳戶。這允許在不同帳戶上重複使用相同的提供者和模型。", + "selectProvider": "選擇提供者", + "selectProviderPlaceholder": "選擇提供者", "selectModel": "選擇模型", - "selectModelPlaceholder": "請先選擇提供商", + "selectModelPlaceholder": "請先選擇提供者", "selectAccount": "選擇帳戶", "selectComboToReference": "選擇要引用的現有組合", "comboReference": "組合引用", "addComboReference": "新增組合引用", "addStepBeforeContinue": "在繼續到下一階段之前,請至少新增一個步驟。", - "previewNextStep": "選擇提供商和模型以預覽下一步。", + "previewNextStep": "選擇提供者和模型以預覽下一步。", "autoSelectAccount": "執行時自動選擇帳戶", "modePackBalanced": "均衡", "modePackBudget": "預算優先", @@ -2817,13 +3314,13 @@ "browseLegacyCatalog": "瀏覽舊版模型目錄", "agentFeaturesTitle": "智慧代理功能", "agentFeaturesDescription": "— 可選,用於智慧體/工具工作流", - "responseValidationTitle": "__MISSING__:Response validation", - "responseValidationHelp": "__MISSING__:Fail over to the next target when a 200 OK body fails these checks (assistant content).", - "responseValidationForbidden": "__MISSING__:Forbidden substrings (one per line)", - "responseValidationRequired": "__MISSING__:Required substrings (one per line)", - "responseValidationMinLength": "__MISSING__:Minimum content length (chars)", - "responseValidationJsonPaths": "__MISSING__:JSON-path checks", - "responseValidationAddCheck": "__MISSING__:+ Add check", + "responseValidationTitle": "回應驗證", + "responseValidationHelp": "當 200 OK 的回應主體未通過這些檢查(助理內容)時,容錯移轉至下一個目標。", + "responseValidationForbidden": "禁止的子字串(每行一個)", + "responseValidationRequired": "必要子字串(每行一個)", + "responseValidationMinLength": "最小內容長度(字元)", + "responseValidationJsonPaths": "JSON 路徑檢查", + "responseValidationAddCheck": "+ 新增檢查", "agentFeaturesSystemMessageOverride": "系統訊息覆蓋", "agentFeaturesSystemMessagePlaceholder": "覆蓋通過此組合路由的所有請求的系統提示…", "agentFeaturesSystemMessageHint": "替換客戶端傳送的任何系統訊息。留空則透傳客戶端系統訊息。", @@ -2838,23 +3335,65 @@ "agentFeaturesContextLengthErrorRange": "上下文長度必須介於 1000 到 2000000 之間", "compressionOverride": "壓縮覆蓋", "modePack": "模式包", - "disableSessionStickiness": "__MISSING__:Disable session stickiness", - "sessionStickinessEnabled": "__MISSING__:Stickiness on", - "sessionStickinessDisabled": "__MISSING__:Stickiness off", - "kimiPresetTitle": "Kimi Coding preset", - "kimiPresetDescription": "Kimi K3 as the primary model (Moonshot API), with automatic fallback to your Kimi Code connections (kimi-coding, kimi-web) when configured.", - "kimiPresetCta": "Add preset" + "disableSessionStickiness": "停用連線黏性", + "sessionStickinessEnabled": "黏性已啟用", + "sessionStickinessDisabled": "黏性關閉", + "active": "啟用中", + "effectiveRoutingShare": "有效路由佔比(權重 ÷ 總和)", + "inactive": "未啟用", + "liveActiveCount": "啟用中 ({count})", + "liveDataHint": "即時資料經由 WebSocket combo 頻道送達。", + "liveDisconnected": "即時已停用 — WebSocket 已中斷連線。顯示最後已知狀態。", + "liveErrorCount": "錯誤 ({count})", + "liveFleet": "艦隊", + "liveFleetDataHint": "艦隊資料經由即時 combo 事件送達。", + "liveInactiveCount": "未啟用 ({count})", + "liveNoProviders": "尚未觀察到任何提供者。", + "liveNoRun": "無可用的 combo 執行。", + "liveSelectCombo": "選取 combo", + "liveSelectComboPlaceholder": "— 選取 combo —", + "liveSingle": "單一", + "liveTargetCount": "{count, plural, one {# 個目標} other {# 個目標}}", + "nestedComboExecute": "將巢狀 combo 作為目標執行", + "nestedComboFlatten": "扁平化巢狀 combo", + "playgroundConfiguration": "設定", + "playgroundCreateFirst": "請先建立一個", + "playgroundDescription": "模擬請求如何透過您的 combos 進行路由", + "playgroundEmptyHint": "選取一個 combo 並點選模擬路由以查看路由路徑。", + "playgroundErrorCount": "錯誤 ({count})", + "playgroundEstimatedCost": "預估成本", + "playgroundEstimatedLatency": "預估延遲", + "playgroundEstimatedPromptTokens": "預估提示詞 Token", + "playgroundFallback": "降級回退", + "playgroundNetworkError": "模擬期間發生網路錯誤", + "playgroundNoCombosConfigured": "未設定任何 combos", + "playgroundNoCombosYet": "尚未設定任何 combos。", + "playgroundRoutingPath": "路由路徑", + "playgroundSimulateRoute": "模擬路由", + "playgroundSimulating": "正在模擬...", + "playgroundStatusAvailable": "可用", + "playgroundStatusDegraded": "降級", + "playgroundStatusError": "錯誤", + "playgroundStatusNoQuota": "無配額", + "playgroundStatusUnknown": "未知", + "playgroundStrategy": "策略", + "playgroundTitle": "Combo Playground", + "playgroundWarningCount": "警告 ({count})", + "playgroundWeight": "權重 {value}", + "kimiPresetTitle": "Kimi Coding 預設組合", + "kimiPresetDescription": "以 Kimi K3 作為主要模型(Moonshot API),設定完成後會自動備援至您的 Kimi Code 連線(kimi-coding、kimi-web)。", + "kimiPresetCta": "新增預設組合" }, "costs": { "title": "成本", - "pageDescription": "跟蹤支出、分析趨勢,並管理所有提供商的 AI 預算", + "pageDescription": "跟蹤支出、分析趨勢,並管理所有提供者的 AI 預算", "overview": "概覽", "budget": "預算", "totalCost": "總成本", "breakdown": "成本明細", "noData": "無成本資料", "byModel": "按模型", - "byProvider": "按提供商", + "byProvider": "按提供者", "range7d": "7 天", "range30d": "30 天", "range90d": "90 天", @@ -2871,8 +3410,8 @@ "noCostDataDescription": "發起請求後,成本資料會顯示在這裡。", "spendToday": "今日支出", "overviewLoadFailed": "載入成本概覽失敗", - "overviewDescription": "按提供商、模型和時間段彙總成本。", - "providerShare": "提供商佔比", + "overviewDescription": "按提供者、模型和時間段彙總成本。", + "providerShare": "提供者佔比", "topProviders": "熱門 Provider", "costTrend": "成本趨勢", "noCostDataTitle": "暫無成本資料", @@ -2912,8 +3451,8 @@ "exportJSON": "匯出為 JSON", "legacyFreeLabel": "舊版 / 免費", "costExplorerTitle": "成本探索器", - "costExplorerDescription": "按提供商、模型、API 金鑰、帳戶或服務層級探索支出。", - "groupProvider": "提供商", + "costExplorerDescription": "按提供者、模型、API 金鑰、帳戶或服務層級探索支出。", + "groupProvider": "提供者", "groupModel": "模型", "groupApiKey": "API 金鑰", "groupAccount": "帳戶", @@ -2954,7 +3493,7 @@ "enableCloud": "啟用雲端", "modelsAcrossEndpoints": "{endpoints} 個端點共提供 {models} 個模型", "loadingModels": "正在載入可用模型...", - "chatDesc": "支援所有提供商的流式與非流式聊天", + "chatDesc": "支援所有提供者的流式與非流式聊天", "embeddings": "嵌入", "embeddingsDesc": "用於搜尋和 RAG 流程的文本向量", "imageGeneration": "影像生成", @@ -2970,7 +3509,7 @@ "moderations": "內容稽核", "moderationsDesc": "內容安全稽核與分類", "responsesDesc": "適用於 Codex 和高階智慧體工作流的 OpenAI Responses API", - "listModelsDesc": "列出所有已連線提供商下的可用模型", + "listModelsDesc": "列出所有已連線提供者下的可用模型", "settingsApiDesc": "通過 API 讀取和修改 OmniRoute 配置", "settingsApi": "設定 API", "categoryCore": "核心 API", @@ -2978,9 +3517,9 @@ "categorySearch": "搜尋與發現", "categoryUtility": "工具與管理", "webSearch": "網頁搜尋", - "webSearchDesc": "統一接入多個提供商的網頁搜尋,支援自動故障轉移與快取", - "searchProvider": "搜尋提供商", - "searchProviderDesc": "該提供商會用於 `POST /v1/search` 的網頁搜尋。無需配置模型,只要連線 API 金鑰即可使用。", + "webSearchDesc": "統一接入多個提供者的網頁搜尋,支援自動故障轉移與快取", + "searchProvider": "搜尋提供者", + "searchProviderDesc": "該提供者會用於 `POST /v1/search` 的網頁搜尋。無需配置模型,只要連線 API 金鑰即可使用。", "enableCloudTitle": "啟用雲代理", "whatYouGet": "啟用後可獲得", "cloudBenefitAccess": "從世界任何地方訪問你的 API", @@ -3033,7 +3572,7 @@ "cloudflaredIdleNote": "為當前端點建立一個臨時的 Cloudflare Quick Tunnel。", "cloudflaredLastError": "最近錯誤:{error}", "providerModelsTitle": "{provider} — 模型", - "noModelsForProvider": "該提供商當前沒有可用模型。", + "noModelsForProvider": "該提供者當前沒有可用模型。", "chat": "聊天", "embedding": "向量", "image": "影像", @@ -3131,7 +3670,7 @@ "apiEndpointsSearchPlaceholder": "搜尋端點...", "apiEndpointsRequiresAuth": "需要授權", "apiEndpointsNoMatch": "沒有端點與您的過濾器匹配", - "vscodeAliasTitle": "VS Code 令牌別名", + "vscodeAliasTitle": "VS Code 權杖別名", "vscodeAliasDescriptionReady": "使用 /api/v1/vscode/TOKEN/... 介面的可貼上相容性 URL。", "vscodeAliasDescriptionError": "由於當前會話無法載入 CLI 金鑰,正在顯示佔位符 URL。", "vscodeAliasDescriptionLoading": "正在載入 CLI 金鑰。在金鑰可用之前將顯示佔位符 URL。", @@ -3155,7 +3694,84 @@ "showInternal": "顯示內部端點", "customSystemPromptTitle": "Custom System Prompt", "customSystemPromptDescription": "Inject a custom system prompt into every model request", - "customSystemPromptPlaceholder": "e.g. Always respond in pirate speak..." + "customSystemPromptPlaceholder": "e.g. Always respond in pirate speak...", + "activeCloud": "雲端", + "activeEndpoints": "啟用中的端點", + "activeLocal": "本機", + "activeTunnelCount": "{active} / {total} 個啟用中", + "all": "全部", + "apiKey": "API 金鑰", + "apiKeyRequired": "此端點需要 API 金鑰。", + "apiKeyRevealDisabled": "API 金鑰顯示功能已停用(ALLOW_API_KEY_REVEAL)。請在「功能旗標」頁面上變更,或手動貼上 API 金鑰。", + "apiKeyRevealFailed": "顯示 API 金鑰失敗({status})", + "apiKeyRevealInvalid": "API 金鑰顯示回傳無效的回應", + "apiKeysLoadFailed": "載入 API 金鑰失敗({status})", + "apiKeysLoadFailedGeneric": "載入 API 金鑰失敗", + "badgeInternal": "內部", + "badgeLocal": "本機", + "badgeProtected": "受保護", + "bearerAuth": "Bearer 驗證", + "catalogLoadFailed": "API 目錄請求失敗(HTTP {status})", + "catalogLoadFailedGeneric": "載入 API 目錄失敗", + "catalogStats": "共 {categories} 個類別中的 {endpoints} 個端點", + "catalogUnavailableDescription": "無法載入 OpenAPI 規格。", + "close": "關閉", + "copyUrlTitle": "複製 {url}", + "dataSchemas": "資料結構", + "endpointSections": "端點區段", + "enterManually": "手動輸入", + "errorStatus": "錯誤", + "example": "範例", + "more": "還有 {count} 個", + "noActiveApiKeys": "找不到啟用中的 API 金鑰。切換至手動輸入以貼上金鑰。", + "obsidianApiKeyPlaceholder": "Obsidian API 金鑰", + "obsidianBaseUrlOptional": "基礎 URL(選填)", + "obsidianConfigureMobile": "設定 Obsidian 行動版", + "obsidianConnect": "連線", + "obsidianConnectFailed": "連線失敗", + "obsidianConnected": "已連線", + "obsidianConnectionFailed": "連線失敗", + "obsidianDescription": "透過路由 AI 模型搜尋、讀取、寫入和管理 Obsidian 筆記", + "obsidianDisable": "停用", + "obsidianDisableWebdavFailed": "停用 WebDAV 失敗", + "obsidianDisconnect": "中斷連線", + "obsidianDisconnectFailed": "中斷連線失敗", + "obsidianEnable": "啟用", + "obsidianEnableWebdavFailed": "啟用 WebDAV 失敗", + "obsidianEnterToken": "請輸入 Obsidian API Token", + "obsidianEnterVaultPath": "請輸入保險庫目錄路徑", + "obsidianMobileInstructions": "在 Obsidian 行動版中:設定 → 同步 → WebDAV → 輸入以下內容:", + "obsidianNotConnected": "未連線", + "obsidianPassword": "密碼", + "obsidianPortWarning": "連接埠 27124 是 MCP 端點(HTTPS,自我簽署憑證)。REST API 使用 HTTP 連接埠 27123。", + "obsidianRemoteVaultHint": "預設:{defaultUrl}。對於遠端保險庫,請輸入 Tailscale IP + 連接埠(例如 http://100.x.x.x:27123)。請在執行 Obsidian 的機器上啟用 Local REST API 外掛程式。", + "obsidianRestToken": "Obsidian Local REST API Token", + "obsidianTailscaleHint": "如果從行動裝置連線,請使用 Tailscale IP 而非 localhost。兩台裝置必須位於相同的 Tailscale 網路中。", + "obsidianTokenConfigured": "Token 已設定。Obsidian 工具可透過 MCP 使用。", + "obsidianUsername": "使用者名稱", + "obsidianVaultDirectoryPath": "保險庫目錄路徑", + "obsidianVaultSync": "保險庫同步(WebDAV)", + "obsidianVaultSyncDescription": "使用 WebDAV 透過 Tailscale 將保險庫同步至 Obsidian 行動版。Obsidian 行動版內建 WebDAV 支援 — 無需外掛程式。", + "obsidianWebdavDisabledMessage": "WebDAV 同步已停用", + "obsidianWebdavEnabled": "WebDAV 同步已啟用", + "obsidianWebdavEnabledMessage": "WebDAV 同步已啟用。請在下方設定您的行動裝置。", + "obsidianWebdavSync": "WebDAV 同步", + "obsidianWebdavUrl": "WebDAV URL", + "openJsonResponse": "開啟 JSON 回應", + "pasteApiKey": "在此貼上您的 API 金鑰", + "requestBody": "請求主體", + "requestBodyJson": "請求主體(JSON)", + "requestFailed": "請求失敗({status})", + "sendRequest": "傳送請求", + "sending": "傳送中...", + "showInternalTooltip": "顯示或隱藏內部路由(預設隱藏)", + "statusRunning": "執行中", + "switchToSelection": "切換至選取", + "tabA2a": "A2A", + "tabContextSources": "上下文來源", + "tabMcp": "MCP", + "tryIt": "試試看", + "tunnels": "隧道" }, "endpoints": { "tabProxy": "端點代理", @@ -3227,7 +3843,7 @@ "profileConservative": "保守", "applyProfile": "應用配置", "resetCircuitBreakers": "重置斷路器", - "resetCircuitBreakersHelp": "清除當前斷路器狀態及提供商失敗計數。", + "resetCircuitBreakersHelp": "清除當前斷路器狀態及提供者失敗計數。", "resetAllBreakers": "重置全部斷路器", "toolsAndScopes": "工具與作用域", "tableTool": "工具", @@ -3398,7 +4014,7 @@ "qdrantOk": "健康 ({latencyMs}ms)", "qdrantError": "連線錯誤", "needsReindex": "{count} 個記憶體需要重新索引", - "configureCta": "配置", + "configureCta": "設定", "vectorStoreInstallHint": "要啟用向量搜尋:npm install sqlite-vec(需要本地 Node.js,而不是 WASM),然後重啟。" }, "episodic": "情景型", @@ -3437,7 +4053,7 @@ "rerankApplied": "重新排序已應用", "fallback": "後備", "noResults": "未找到與此查詢相關的記憶。", - "tokensUsed": "已使用的令牌", + "tokensUsed": "已使用的權杖", "none": "無", "errorFetch": "獲取預覽失敗" }, @@ -3448,10 +4064,10 @@ "description": "可選的 Qdrant 整合用於可擴充套件的語義搜尋", "enableLabel": "啟用 Qdrant", "enableDesc": "啟用後,Qdrant 將作為主要向量儲存使用", - "banner": "__MISSING__:Tier 2 vector store — an external, scalable alternative to the built-in sqlite-vec (Tier 1). Enable it only if you have a very large memory set or want shared memory across instances; most users are fine on sqlite-vec. When enabled it becomes the primary store and automatically falls back to sqlite-vec if unreachable.", - "hostHelp": "__MISSING__:Local Docker: http://localhost:6333 · Qdrant Cloud: your cluster URL", - "collectionHelp": "__MISSING__:Any name — OmniRoute creates it on first use", - "embeddingModelHelp": "__MISSING__:Sets the vector dimension automatically on first use. Existing memories are not back-filled, and changing the model after data exists needs a fresh collection.", + "banner": "Tier 2 向量儲存 — 內建 sqlite-vec(Tier 1)的外部可擴展替代方案。僅在您有非常大的記憶體集合或想要跨執行個體共享記憶體時才啟用;大多數使用者使用 sqlite-vec 就很夠用。啟用後它會成為主要儲存,並在無法連線時自動回退到 sqlite-vec。", + "hostHelp": "本地 Docker:http://localhost:6333 · Qdrant Cloud:您的叢集 URL", + "collectionHelp": "任意名稱 — OmniRoute 會在首次使用時建立", + "embeddingModelHelp": "首次使用時自動設定向量維度。現有記憶不會回溯填充,資料存在後變更模型需要建立新集合。", "testConnection": "測試連線", "testing": "測試中...", "statusActive": "活動", @@ -3506,7 +4122,7 @@ "tokensUsed": "已用 Tokens", "tooltip": { "totalEntries": "此 API 金鑰儲存的總記憶數", - "tokensUsed": "活動記憶佔用的估計總令牌數", + "tokensUsed": "活動記憶佔用的估計總權杖數", "hitRate": "按 ID 讀取的快取命中率(不是語義回憶準確性)", "factual": "使用者上下文中的客觀、永久性事實", "episodic": "對話歷史中的事件和經歷", @@ -3528,7 +4144,7 @@ "noExecutions": "未找到執行記錄", "enabled": "已啟用", "disabled": "已停用", - "delete": "__MISSING__:Delete", + "delete": "刪除", "version": "版本", "tableDescription": "說明", "skill": "技能", @@ -3554,7 +4170,7 @@ "successRate": "成功率", "marketplaceTab": "市場", "applyFilters": "應用篩選", - "popularDefaultsLabel": "所選提供商的預設熱門技能:", + "popularDefaultsLabel": "所選提供者的預設熱門技能:", "onMode": "開", "offMode": "關", "autoMode": "自動", @@ -3576,7 +4192,7 @@ "pageInfo": "第 {page} / {totalPages} 頁(共 {total} 條)", "previous": "上一頁", "next": "下一頁", - "activeProvider": "當前提供商:", + "activeProvider": "當前提供者:", "changeInSettings": "可在 設定 → 記憶與技能 中修改。", "installs": "安裝", "marketplaceSkillsMpHint": "請在設定中配置 SkillsMP API 金鑰以瀏覽市場。", @@ -3584,7 +4200,17 @@ "installSkillModalDesc": "貼上技能清單 JSON 或上傳 .json 檔案。", "uploadJson": "上傳 JSON", "cancel": "取消", - "installSkill": "安裝技巧" + "installSkill": "安裝技巧", + "handlerCode": "處理器程式碼", + "handlerTab": "處理器", + "handlerUnavailable": "處理器不可用", + "inputSchema": "輸入架構", + "outputSchema": "輸出架構", + "runTestPlaceholder": "執行測試(佔位符)", + "schemaTab": "架構", + "selectSkillToInspect": "請在左側選取一個技能進行檢視。", + "setModeAria": "設定模式 {mode}", + "uninstallSkill": "解除安裝技能" }, "health": { "title": "系統健康狀況", @@ -3600,9 +4226,9 @@ "database": "資料庫", "version": "版本", "lastCheck": "最後檢查", - "providerHealth": "提供商健康", + "providerHealth": "提供者健康", "systemMetrics": "系統指標", - "tokenHealth": "令牌健康", + "tokenHealth": "權杖健康", "refreshAll": "全部重新整理", "checkNow": "立即檢視", "loadingHealth": "正在載入健康資料...", @@ -3630,16 +4256,16 @@ "signatureSession": "會話", "recovering": "正在恢復中", "noCBData": "當前沒有可用的斷路器資料,請先發起一些請求。", - "providerHealthStatusAria": "提供商健康狀況", + "providerHealthStatusAria": "提供者健康狀況", "issuesLabel": "檢測到的問題", "operational": "執行中", - "providers": "提供商", + "providers": "提供者", "configuredProvidersLabel": "在儀表板中配置", - "configuredProvidersHint": "指已在 /dashboard/providers 中配置憑證的提供商,無論當前執行時狀態如何。", + "configuredProvidersHint": "指已在 /dashboard/providers 中配置憑證的提供者,無論當前執行時狀態如何。", "activeProviders": "{count} 個活躍", - "activeProvidersHint": "指當前已啟用並可參與請求路由的已配置提供商。", + "activeProvidersHint": "指當前已啟用並可參與請求路由的已配置提供者。", "monitoredProviders": "{count} 個監控中", - "monitoredProvidersHint": "指當前由斷路器健康監控器持續跟蹤的提供商。", + "monitoredProvidersHint": "指當前由斷路器健康監控器持續跟蹤的提供者。", "healthyCount": "{count} 健康", "nodeVersion": "節點 {version}", "failures": "{count} 失敗", @@ -3654,7 +4280,7 @@ "runningCount": "{count} 正在執行", "ok": "正常", "activeLockouts": "主動鎖定", - "resetConfirm": "將所有斷路器重置為正常狀態?這將清除所有故障計數並將所有提供商恢復到執行狀態。", + "resetConfirm": "將所有斷路器重置為正常狀態?這將清除所有故障計數並將所有提供者恢復到執行狀態。", "resetAllTitle": "將所有斷路器重置為正常狀態", "resetting": "正在重置...", "resetAll": "全部重置", @@ -3671,18 +4297,18 @@ "noSessionQuotaMonitorsActive": "沒有活動的會話配額監視器。", "gracefulDegradationStatus": "優雅降級狀態", "additionalModels": "+{count} 更多型號", - "providerHealthMatrixTitle": "提供商健康矩陣", - "providerHealthMatrixDescription": "來自熔斷器、冷卻、鎖定和日誌的提供商 × 帳戶 × 模型狀態。", + "providerHealthMatrixTitle": "提供者健康矩陣", + "providerHealthMatrixDescription": "來自熔斷器、冷卻、鎖定和日誌的提供者 × 帳戶 × 模型狀態。", "healthMatrixRange": "健康矩陣時間範圍", - "providerFilter": "提供商篩選", + "providerFilter": "提供者篩選", "onlyIssues": "僅顯示問題", "refresh": "重新整理", "accounts": "帳戶", "models": "模型", "issues": "問題", - "loadingProviderHealthMatrix": "正在載入提供商健康矩陣...", - "failedProviderHealthMatrix": "載入提供商健康矩陣失敗:{error}", - "noProvidersMatchedFilters": "沒有提供商匹配當前篩選條件。", + "loadingProviderHealthMatrix": "正在載入提供者健康矩陣...", + "failedProviderHealthMatrix": "載入提供者健康矩陣失敗:{error}", + "noProvidersMatchedFilters": "沒有提供者匹配當前篩選條件。", "modelPillSummary": "{requests} 請求 · {successRate} 成功率 · {latency} 平均延遲", "modelLockoutSummary": "{reason} · 剩餘 {duration}", "locked": "已鎖定", @@ -3699,7 +4325,28 @@ "issueCount": "{count} 個問題", "score": "評分", "lastRequest": "最近請求", - "lastError": "最近錯誤" + "lastError": "最近錯誤", + "activeCount": "{count} 個作用中", + "ageSeconds": "已存在 {count} 秒", + "alerting": "警示中", + "attentionNeeded": "需要注意", + "databaseHealthDescription": "診斷並修復過時的配額/領域行和損毀的組合參考。", + "degradationDefault": "預設", + "degradationFull": "完整", + "degradationMinimal": "最小", + "degradationReduced": "減少", + "errors": "錯誤", + "idleSeconds": "閒置 {count} 秒", + "quotaMonitors": "配額監控", + "repairBackupCreated": "在變更前已建立修復備份。", + "repairing": "修復中...", + "repairs": "修復", + "requestCount": "{count} 個請求", + "retryIn": "在 {duration} 後重試", + "runAutoRepair": "執行自動修復", + "sessionActivity": "工作階段活動", + "sinceTime": "自 {time} 以來", + "status": "狀態" }, "telemetry": { "title": "系統遙測", @@ -3804,12 +4451,12 @@ "noEntries": "未找到稽核日誌條目", "previous": "上一頁", "next": "下一頁", - "providerWarningTitle": "提供商警告", + "providerWarningTitle": "提供者警告", "viewDetails": "檢視詳情", "eventMetadata": "事件後設資料", "eventPayload": "事件負載", "requestId": "請求 ID", - "providerWarningDesc": "上游提供商返回了警告。請檢視詳情以瞭解更多資訊。", + "providerWarningDesc": "上游提供者返回了警告。請檢視詳情以瞭解更多資訊。", "a": "A", "offset": "偏移量", "limit": "限制", @@ -3824,7 +4471,7 @@ "clearAll": "全部清除", "confirmClearActiveRequests": "清除所有活躍請求?", "model": "模型", - "provider": "提供商", + "provider": "提供者", "account": "帳戶", "elapsed": "已耗時", "activeStage": "階段", @@ -3841,40 +4488,53 @@ "viewPayloads": "檢視", "activeCount": "{count} 個活躍", "clientPayload": "客戶端請求載荷", - "upstreamPayload": "上游提供商載荷", + "upstreamPayload": "上游提供者載荷", "upstreamNotSentYet": "尚未傳送到上游", "runningRequestDetailMeta": "Account:{account} — 已耗時:{elapsed}", "export": "匯出", "exporting": "正在匯出...", "exportFailed": "匯出失敗", - "cleanHistoryButton": "__MISSING__:Clean history", - "cleanHistoryTitle": "__MISSING__:Clean log history?", - "cleanHistoryMessage": "__MISSING__:This permanently clears all request log rows, legacy detail rows, and local artifact files under DATA_DIR/call_logs. The live page will refresh after cleanup.", - "cleanHistoryConfirm": "__MISSING__:Clean history", - "cleanHistoryCancel": "__MISSING__:Cancel", - "cleanHistorySuccess": "__MISSING__:Cleaned {deleted} log entries, {deletedArtifacts} artifacts, and {deletedDetailedLogs} legacy detail rows.", - "cleanHistoryEmpty": "__MISSING__:No request log history was found.", - "cleanHistoryFailed": "__MISSING__:Failed to clean log history.", + "cleanHistoryButton": "清理歷史記錄", + "cleanHistoryTitle": "清理記錄歷史?", + "cleanHistoryMessage": "這會永久清除 DATA_DIR/call_logs 下的所有請求記錄行、舊版詳細資料行和本地工件檔案。清理後即時頁面將重新整理。", + "cleanHistoryConfirm": "清理歷史記錄", + "cleanHistoryCancel": "取消", + "cleanHistorySuccess": "已清理 {deleted} 筆記錄、{deletedArtifacts} 個工件和 {deletedDetailedLogs} 列舊版詳細資料。", + "cleanHistoryEmpty": "找不到請求紀錄歷史。", + "cleanHistoryFailed": "清除紀錄歷史失敗。", "timeRange": "時間範圍", "lastNHours": "最近 {hours} 小時", "defaultRange": "預設", "consoleViewer": { "fetchFailed": "獲取日誌失敗", "copyFailed": "複製日誌條目失敗", - "copyLogEntry": "複製日誌條目" + "copyLogEntry": "複製日誌條目", + "applicationConsole": "應用程式主控台", + "autoScroll": "自動捲動", + "consoleAria": "應用程式主控台日誌", + "disableAutoScroll": "停用自動捲動", + "emptyFileLoggingHint": "確保在您的 .env 檔案中設定 APP_LOG_TO_FILE=true", + "enableAutoScroll": "啟用自動捲動", + "entryCount": "{count} 個條目", + "fileLoggingRequired": "確保應用程式正在將日誌寫入檔案(APP_LOG_TO_FILE=true)", + "filterByLevel": "依日誌層級篩選", + "lastHour": "過去 1 小時", + "searchAria": "搜尋日誌條目", + "searchPlaceholder": "搜尋日誌…", + "updatedAt": "更新於 {time}" }, "compressionLogTitle": "壓縮日誌", "compressionLogEmpty": "尚未有壓縮請求。啟用壓縮處理請求時,壓縮統計資訊將出現在此處。", - "tokens": "令牌" + "tokens": "權杖" }, "onboarding": { "welcome": "歡迎", - "tiers": "__MISSING__:Tiers", + "tiers": "方案分級", "security": "安全", "test": "測試", "ready": "準備好了!", "setPassword": "設定密碼", - "addProvider": "新增您的第一個提供商", + "addProvider": "新增您的第一個提供者", "getStarted": "開始使用", "skip": "跳過", "skipWizard": "完全跳過嚮導", @@ -3887,18 +4547,18 @@ "passwordsMismatch": "密碼不匹配", "setupComplete": "設定完成!", "goToDashboard": "轉到儀表板→", - "welcomeDesc": "OmniRoute 是您的本地 AI API 代理。它通過負載均衡、故障轉移和使用情況跟蹤將請求路由到多個 AI 提供商。", - "multiProvider": "多提供商", + "welcomeDesc": "OmniRoute 是您的本地 AI API 代理。它通過負載均衡、故障轉移和使用情況跟蹤將請求路由到多個 AI 提供者。", + "multiProvider": "多提供者", "usageTracking": "使用情況追蹤", "securityDesc": "設定密碼以保護您的儀表板,或暫時跳過。", - "providerDesc": "連線你的第一個 AI 提供商。你之後還可以繼續新增。", + "providerDesc": "連線你的第一個 AI 提供者。你之後還可以繼續新增。", "apiKeyRequired": "API 金鑰(必填)", "customUrlOptional": "自定義 URL(可選)", - "testDesc": "讓我們驗證您的提供商連線是否有效。", + "testDesc": "讓我們驗證您的提供者連線是否有效。", "runTest": "執行連線測試", "testingConnection": "測試連線...", - "connectionSuccessful": "連線成功!您的提供商已準備就緒。", - "noProviderFound": "未找到提供商。您可以稍後從儀表板新增一個。", + "connectionSuccessful": "連線成功!您的提供者已準備就緒。", + "noProviderFound": "未找到提供者。您可以稍後從儀表板新增一個。", "testFailed": "測試失敗,但您可以稍後配置。", "couldNotTest": "現在無法測試。您可以從儀表板進行測試。", "doneDesc": "你都準備好了!您的 OmniRoute 例項已配置並準備好代理 AI 請求。", @@ -3906,73 +4566,75 @@ "continue": "繼續", "retry": "重試", "failedSetPassword": "設定密碼失敗。再試一次。", - "failedAddProvider": "新增提供商失敗。再試一次。", + "failedAddProvider": "新增提供者失敗。再試一次。", "connectionError": "連線錯誤。請再試一次。", - "provider": "提供商", - "apiKeyHelp": "API金鑰是AI服務的密碼。從提供商的網站(例如 platform.openai.com、console.anthropic.com)獲取一個。", + "provider": "提供者", + "apiKeyHelp": "API金鑰是AI服務的密碼。從提供者的網站(例如 platform.openai.com、console.anthropic.com)獲取一個。", "tier": { - "subtitle": "OmniRoute 將提供商組織為三層,因此路由首先優先選擇最可靠、成本最低的路徑。", + "subtitle": "OmniRoute 將提供者組織為三層,因此路由首先優先選擇最可靠、成本最低的路徑。", "tier1": { "label": "優質客戶", "description": "具有本機身份驗證流程和推理模型的一流 CLI。" }, "tier2": { "label": "成本最佳化", - "description": "用於日常流量的廉價、高吞吐量提供商。" + "description": "用於日常流量的廉價、高吞吐量提供者。" }, "tier3": { "label": "後備和專業", "description": "本地託管或專用端點用作後備。" }, - "configure": "配置提供商" + "configure": "配置提供者", + "afterSetup": "設定完成後。", + "flowCaption": "請求會依序流經您的訂閱配額、按量計費的便宜提供者,再到免費層級提供者 — 全自動,無需設定。" }, "tierFlowDiagramAlt": "OmniRoute 3 層回退圖", "apiKeyMgmt": "API金鑰管理" }, "providers": { - "title": "提供商", + "title": "提供者", "Account Deactivated": "帳戶已停用", - "allProviders": "所有提供商", - "audioProviders": "音訊提供商", + "allProviders": "所有提供者", + "audioProviders": "音訊提供者", "showFreeOnly": "僅顯示免費", - "addProvider": "新增提供商", - "addFirstProvider": "新增您的第一個提供商", - "addFirstProviderDesc": "連線 AI 提供商以開始通過 OmniRoute 路由請求。您可以使用免費提供商、API 金鑰或 OAuth 帳戶。", + "addProvider": "新增提供者", + "addFirstProvider": "新增您的第一個提供者", + "addFirstProviderDesc": "連線 AI 提供者以開始通過 OmniRoute 路由請求。您可以使用免費提供者、API 金鑰或 OAuth 帳戶。", "learnMore": "瞭解更多", "adaptaTutorial": { - "title": "__MISSING__:How to connect Adapta Web", - "introPrefix": "__MISSING__:Adapta authenticates through Clerk. The token", - "introSuffix": "__MISSING__:is a long-lived JWT that lets OmniRoute refresh sessions automatically.", - "or": "__MISSING__:or", - "step1Title": "__MISSING__:Open Adapta chat", - "step1DescPrefix": "__MISSING__:Open", - "step1DescSuffix": "__MISSING__:and sign in with your Gold or Business account.", - "step2Title": "__MISSING__:Open DevTools", - "step2DescPrefix": "__MISSING__:Press", - "step2DescSuffix": "__MISSING__:to open Developer Tools.", - "step3Title": "__MISSING__:Go to Application → Cookies", - "step3DescPrefix": "__MISSING__:In the", - "step3DescMiddle": "__MISSING__:expand", - "step3DescSuffix": "__MISSING__:and click", - "step4Title": "__MISSING__:Copy the cookie value for", - "step4DescPrefix": "__MISSING__:Find the cookie named", - "step4DescMiddle": "__MISSING__:in the list. Click it and copy the content from the", - "step4DescSuffix": "__MISSING__:column. It starts with", - "step5Title": "__MISSING__:Paste it here and save", - "step5DescPrefix": "__MISSING__:Click", - "step5DescMiddle": "__MISSING__:paste the", - "step5DescSuffix": "__MISSING__:value into the API Key field, then save. OmniRoute will refresh the session automatically.", - "tipLabel": "__MISSING__:Tip:", - "tipPrefix": "__MISSING__:The", - "tipSuffix": "__MISSING__:cookie is long-lived, usually for months. You only need to renew it if you sign out or Adapta invalidates the session." + "title": "如何連線 Adapta Web", + "introPrefix": "Adapta 透過 Clerk 進行驗證。該令牌", + "introSuffix": "是一個長效 JWT,可讓 OmniRoute 自動重新整理工作階段。", + "or": "或", + "step1Title": "開啟 Adapta 聊天", + "step1DescPrefix": "開啟", + "step1DescSuffix": "並使用您的 Gold 或 Business 帳戶登入。", + "step2Title": "開啟 DevTools", + "step2DescPrefix": "按下", + "step2DescSuffix": "以開啟開發人員工具。", + "step3Title": "前往 Application → Cookies", + "step3DescPrefix": "在", + "step3DescMiddle": "展開", + "step3DescSuffix": "並點擊", + "step4Title": "複製以下項目的 Cookie 值:", + "step4DescPrefix": "尋找名為", + "step4DescMiddle": "在清單中。點擊它並從中複製內容", + "step4DescSuffix": "欄位。其開頭為", + "step5Title": "貼到此處並儲存", + "step5DescPrefix": "點擊", + "step5DescMiddle": "貼上", + "step5DescSuffix": "值貼到 API 金鑰欄位,然後儲存。OmniRoute 會自動重新整理工作階段。", + "tipLabel": "提示:", + "tipPrefix": "該", + "tipSuffix": "Cookie 的有效期很長,通常為數個月。只有在您登出或 Adapta 使工作階段失效時才需要更新。" }, - "editProvider": "編輯提供商", - "deleteProvider": "刪除提供商", - "noProviders": "沒有配置提供商", + "editProvider": "編輯提供者", + "deleteProvider": "刪除提供者", + "noProviders": "沒有配置提供者", "modelAvailability": "模型可用性", "accounts": "帳戶", "newAccount": "新帳戶", - "deleteConfirm": "您確定要刪除該提供商嗎?", + "deleteConfirm": "您確定要刪除該提供者嗎?", "testing": "測試...", "testConnection": "測試連線", "testSuccess": "連線成功", @@ -3986,10 +4648,10 @@ "chat": "聊天", "responses": "回應", "messages": "留言", - "oauthProviders": "OAuth 提供商", - "freeProviders": "免費提供商", - "apiKeyProviders": "API 金鑰提供商", - "compatibleProviders": "API 金鑰相容提供商", + "oauthProviders": "OAuth 提供者", + "freeProviders": "免費提供者", + "apiKeyProviders": "API 金鑰提供者", + "compatibleProviders": "API 金鑰相容提供者", "testAll": "測試全部", "reorderByAvailability": "重新排序", "reorderByAvailabilityTitle": "依可用性重新排序連線", @@ -4012,7 +4674,7 @@ "freeFilterAll": "全部", "freeFilterFreeOnly": "僅免費", "freeFilterPaidOnly": "僅付費", - "sortFreeFirst": "__MISSING__:Free first", + "sortFreeFirst": "免費優先", "showHiddenOnly": "僅隱藏", "filterByVisibility": "按可見性篩選", "autoHideFailed": "自動隱藏失敗模型", @@ -4034,39 +4696,39 @@ "freeTierAvailable": "有免費額度", "hasFreeTooltip": "提供免費層", "noAuthLabel": "免鑑權", - "noAuthProviders": "免鑑權提供商", + "noAuthProviders": "免鑑權提供者", "upstreamProxyLabel": "上游代理", - "freeAggregated": "所有提供免費層的提供商(同時也會顯示在各自的原生分類中)", + "freeAggregated": "所有提供免費層的提供者(同時也會顯示在各自的原生分類中)", "deprecated": "已棄用", - "deprecatedProvider": "此提供商已棄用", + "deprecatedProvider": "此提供者已棄用", "riskNotice": { "title": "繼續之前", - "tooltip": "該提供商有使用注意事項 —— 點選檢視詳情", - "oauth": "此提供商使用你官方產品的會話 / OAuth,這並未被授權用於代理或路由用途。 不建議進行高強度的自主代理使用(OpenCloud 風格、長鏈路多步流程、大批次請求)—— 上游可能因此限制甚至封禁帳號。 使用風險自負。", - "webCookie": "此提供商通過你的網頁會話 Cookie 進行鑑權。上游服務可能隨時讓會話失效,屆時你需要重新登入。不建議用於長時間無人值守的操作。 使用風險自負。", - "deprecated": "此提供商已被上游下線,可能隨時停止工作。已存在的連線可能在上游完全關閉訪問前繼續可用。建議考慮替代方案。", - "dontShowAgain": "對此提供商不再顯示此提示", + "tooltip": "該提供者有使用注意事項 —— 點選檢視詳情", + "oauth": "此提供者使用你官方產品的會話 / OAuth,這並未被授權用於代理或路由用途。 不建議進行高強度的自主代理使用(OpenCloud 風格、長鏈路多步流程、大批次請求)—— 上游可能因此限制甚至封禁帳號。 使用風險自負。", + "webCookie": "此提供者通過你的網頁會話 Cookie 進行鑑權。上游服務可能隨時讓會話失效,屆時你需要重新登入。不建議用於長時間無人值守的操作。 使用風險自負。", + "deprecated": "此提供者已被上游下線,可能隨時停止工作。已存在的連線可能在上游完全關閉訪問前繼續可用。建議考慮替代方案。", + "dontShowAgain": "對此提供者不再顯示此提示", "understand": "我已瞭解,繼續", "cancel": "取消" }, "disabled": "已停用", - "enableProvider": "啟用提供商", - "disableProvider": "停用提供商", + "enableProvider": "啟用提供者", + "disableProvider": "停用提供者", "testResults": "測試結果", - "noCompatibleYet": "尚未新增相容的提供商", + "noCompatibleYet": "尚未新增相容的提供者", "compatibleHint": "使用上面的按鈕新增 OpenAI 或 Anthropic 相容端點", "addOpenAICompatible": "新增 OpenAI 相容", "addAnthropicCompatible": "新增 Anthropic 相容端點", - "addNewProvider": "新增新提供商", - "backToProviders": "返回提供商", - "configureNewProvider": "配置新的 AI 提供商以與您的應用程式一起使用。", - "providerLabel": "提供商", - "selectProvider": "選擇提供商", - "selectedProvider": "選定的提供商", + "addNewProvider": "新增新提供者", + "backToProviders": "返回提供者", + "configureNewProvider": "配置新的 AI 提供者以與您的應用程式一起使用。", + "providerLabel": "提供者", + "selectProvider": "選擇提供者", + "selectedProvider": "選定的提供者", "authMethod": "認證方式", "apiKeyLabel": "API key", "apiKeyRequired": "需要 API 金鑰", - "selectProviderRequired": "請選擇提供商", + "selectProviderRequired": "請選擇提供者", "enterApiKey": "輸入您的 API 金鑰", "apiKeySecure": "您的 API 金鑰將被加密並安全儲存。", "oauth2Connect": "使用 OAuth2 連線", @@ -4076,10 +4738,10 @@ "displayNamePlaceholder": "例如,生產 API、開發環境", "displayNameHint": "可選。用於標識此配置的友好名稱。", "active": "活躍", - "activeDescription": "啟用此提供商以在您的應用程式中使用", + "activeDescription": "啟用此提供者以在您的應用程式中使用", "cancel": "取消", - "createProvider": "建立提供商", - "failedCreate": "建立提供商失敗", + "createProvider": "建立提供者", + "failedCreate": "建立提供者失敗", "errorOccurred": "發生錯誤。請再試一次。", "modelStatus": "模型狀態", "showConfiguredOnly": "僅顯示已配置", @@ -4092,8 +4754,8 @@ "clearCooldown": "清除", "clearing": "清算...", "until": "直到 {time}", - "providerTestFailed": "提供商測試失敗", - "providerTestTimeout": "提供商測試超時,可能是同時測試的連線過多", + "providerTestFailed": "提供者測試失敗", + "providerTestTimeout": "提供者測試超時,可能是同時測試的連線過多", "modeTest": "{mode} 測試", "passedCount": "{count} 通過", "failedCount": "{count} 失敗", @@ -4110,9 +4772,9 @@ "apiTypeLabel": "API 型別", "prefixHint": "必填。模型名稱使用的唯一字首。", "nameHint": "必填。該節點的友好標籤。", - "baseUrlHint": "必填。 提供商 API 基本 URL。", - "iconUrlLabel": "__MISSING__:Icon URL", - "iconUrlHint": "__MISSING__:Optional. Image URL shown as this provider's icon.", + "baseUrlHint": "必填。 提供者 API 基本 URL。", + "iconUrlLabel": "圖示網址", + "iconUrlHint": "選用。顯示為此供應商圖示的圖片網址。", "anthropicPrefixPlaceholder": "ac-prod", "openaiPrefixPlaceholder": "oc-prod", "anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1", @@ -4123,7 +4785,7 @@ "connectionFailed": "連線失敗。檢查 URL 和金鑰。", "testKeyLabel": "測試 API 金鑰", "testKeyPlaceholder": "sk-...(僅用於驗證)", - "providerNotFound": "未找到提供商", + "providerNotFound": "未找到提供者", "deleteConnectionConfirm": "刪除這個連線嗎?", "deleteConnectionConfirmNamed": "確定要刪除{name}嗎?此操作無法復原。", "batchDeleteSelected": "刪除選中({count})", @@ -4134,10 +4796,10 @@ "batchRetestSelected": "批次測試", "batchActivateSuccess": "已啟用 {count} 個連線", "batchDeactivateSuccess": "已停用 {count} 個連線", - "batchUpdatePartial": "__MISSING__:{count} updated, {skipped} skipped (no longer exist)", - "batchUpdateNone": "__MISSING__:No connections were updated (they may no longer exist)", - "batchRetestLimit": "__MISSING__:Retest up to {max} connections at a time", - "noConnectionsToTest": "__MISSING__:No matching connections to test", + "batchUpdatePartial": "{count} 個已更新,{skipped} 個已跳過(不再存在)", + "batchUpdateNone": "沒有連線被更新(可能已不再存在)", + "batchRetestLimit": "一次最多重新測試 {max} 條連線", + "noConnectionsToTest": "沒有符合條件的連線可測試", "batchDeleteConfirmTitle": "刪除連線", "batchDeleteConfirmButton": "刪除", "filterActive": "健康", @@ -4173,7 +4835,7 @@ "autoSyncDisabled": "自動同步已停用", "autoSyncToggleFailed": "切換自動同步失敗", "clearAllModels": "清除所有模型", - "clearAllModelsConfirm": "您確定要刪除此提供商的所有模型嗎?", + "clearAllModelsConfirm": "您確定要刪除此提供者的所有模型嗎?", "clearAllModelsSuccess": "所有模型已清除", "clearAllModelsFailed": "清除模型失敗", "addConnectionToImport": "新增連線以啟用匯入。", @@ -4181,7 +4843,7 @@ "connectionCount": "{count} 連線", "fetchingModels": "正在獲取可用模型...", "failedFetchModels": "獲取模型失敗", - "noFreeModelsFound": "__MISSING__:No free models found for this provider — nothing imported.", + "noFreeModelsFound": "此供應商未找到免費模型 — 未匯入任何內容。", "fetchModelsSuccess": "找到 {count} 個新模型", "fetchModelsFailed": "無法自動獲取模型(可從設定中重試)", "noModelsFound": "未找到模型", @@ -4225,8 +4887,8 @@ "optional": "可選", "anthropicCompatibleName": "Anthropic 相容", "openaiCompatibleName": "OpenAI 相容", - "compatibleDefaultModelLabel": "__MISSING__:Default Model", - "compatibleDefaultModelHint": "__MISSING__:Enter the model ID exactly as your compatible endpoint expects it. This model will be saved as the connection default.", + "compatibleDefaultModelLabel": "預設模型", + "compatibleDefaultModelHint": "請按照相容端點所期望的格式輸入模型 ID。此模型將儲存為連線預設值。", "failedImportModels": "匯入模型失敗", "noModelsReturnedFromEndpoint": "/models 端點沒有返回任何模型。", "importingModelsProgress": "正在匯入 {current} 個模型(共 {total} 個)...", @@ -4247,10 +4909,10 @@ "openai": "OpenAI", "singleConnectionPerCompatible": "每個相容節點僅允許一個連線。如果需要更多連線,請新增另一個節點。", "connections": "連線", - "providerProxyTitleConfigured": "提供商代理:{host}", + "providerProxyTitleConfigured": "提供者代理:{host}", "configured": "已配置", - "providerProxyConfigureHint": "為該提供商的所有連線配置代理", - "providerProxy": "提供商代理", + "providerProxyConfigureHint": "為該提供者的所有連線配置代理", + "providerProxy": "提供者代理", "repairEnv": "修復 .env", "repairEnvWorking": "修復中...", "repairEnvHint": "將缺失的 OAuth 預設值補充到 .env 中,不會覆蓋現有值。", @@ -4261,7 +4923,7 @@ "addConnection": "新增連線", "availableModels": "可用模型", "builtInModels": "內建模型", - "builtInModelsHint": "該提供商的登錄檔模型。點選鉛筆可設定相容選項。", + "builtInModelsHint": "該提供者的登錄檔模型。點選鉛筆可設定相容選項。", "pageAutoRefresh": "頁面會自動重新整理...", "statusDisabled": "已停用", "statusConnected": "已連線", @@ -4278,7 +4940,7 @@ "errorTypeUpstreamAuth": "上游授權", "errorTypeMissingCredential": "缺少憑證", "errorTypeRefreshFailed": "重新整理失敗", - "errorTypeTokenExpired": "令牌已過期", + "errorTypeTokenExpired": "權杖已過期", "errorTypeRateLimited": "速率有限", "errorTypeUpstreamUnavailable": "上游不可用", "errorTypeNetworkError": "網路錯誤", @@ -4287,7 +4949,7 @@ "errorTypeCreditsExhausted": "額度已用完", "errorTypeBanned": "403 禁止訪問", "proxySourceGlobal": "全域性", - "proxySourceProvider": "提供商", + "proxySourceProvider": "提供者", "proxySourceKey": "金鑰", "proxyConfiguredBySource": "代理 ({source}):{host}", "proxyOn": "代理開啟", @@ -4296,8 +4958,8 @@ "proxyDisabledTitle": "此連線已停用代理", "perKeyProxyOn": "按金鑰", "perKeyProxyOff": "連線", - "perKeyProxyEnabledTitle": "已為此提供商啟用按金鑰代理分配", - "perKeyProxyDisabledTitle": "已為此提供商停用按金鑰代理分配", + "perKeyProxyEnabledTitle": "已為此提供者啟用按金鑰代理分配", + "perKeyProxyDisabledTitle": "已為此提供者停用按金鑰代理分配", "autoPriority": "自動:{priority}", "proxy": "代理", "retestAuthentication": "重新驗證身份", @@ -4328,13 +4990,13 @@ "compatProtocolOpenAI": "OpenAI Chat Completions", "compatProtocolOpenAIResponses": "OpenAI Responses API", "compatProtocolClaude": "Anthropic Messages", - "targetFormatLabel": "__MISSING__:Target Format", - "targetFormatHint": "__MISSING__:Override the upstream wire format. Use 'Anthropic Messages' to route OpenAI-compatible providers (e.g. opencode-go) through the /v1/messages endpoint.", - "targetFormatAuto": "__MISSING__:Default (auto)", - "targetFormatGemini": "__MISSING__:Gemini", - "targetFormatAntigravity": "__MISSING__:Antigravity", + "targetFormatLabel": "目標格式", + "targetFormatHint": "覆寫上游線路格式。使用「Anthropic Messages」將 OpenAI 相容的提供者(例如 opencode-go)路由到 /v1/messages 端點。", + "targetFormatAuto": "預設(自動)", + "targetFormatGemini": "Gemini", + "targetFormatAntigravity": "Antigravity", "compatUpstreamHeadersLabel": "上游額外請求頭", - "compatUpstreamHeadersHint": "與修改廠商連線/API 配置同屬高許可權能力,僅可信管理員應使用。這些頭會在 OmniRoute 按廠商 API Key 自動加好鑑權頭之後再合併。若「名稱」與系統已加的頭相同(例如都叫 Authorization),則以你填的值為準,會整段替換自動那條(含 Bearer 令牌),上游請求裡不再使用面板裡儲存的金鑰來生成 Authorization。填錯可能導致 401,請謹慎。每個請求頭單獨一行;部分閘道器需要額外 Authentication 等可在此加。滑鼠移入或聚焦「值」可暫時看明文。點空白處、關閉本面板或切走焦點即儲存。", + "compatUpstreamHeadersHint": "與修改廠商連線/API 配置同屬高許可權能力,僅可信管理員應使用。這些頭會在 OmniRoute 按廠商 API Key 自動加好鑑權頭之後再合併。若「名稱」與系統已加的頭相同(例如都叫 Authorization),則以你填的值為準,會整段替換自動那條(含 Bearer 權杖),上游請求裡不再使用面板裡儲存的金鑰來生成 Authorization。填錯可能導致 401,請謹慎。每個請求頭單獨一行;部分閘道器需要額外 Authentication 等可在此加。滑鼠移入或聚焦「值」可暫時看明文。點空白處、關閉本面板或切走焦點即儲存。", "compatUpstreamHeaderName": "請求頭名稱", "compatUpstreamHeaderValue": "值", "compatUpstreamAddRow": "新增請求頭", @@ -4342,8 +5004,8 @@ "compatBadgeUpstreamHeaders": "請求頭", "perModelQuotaLabel": "按模型配額", "perModelQuotaDescription": "啟用後,429/404 錯誤只會鎖定特定 Model,而不是整個連線。適用於具有按 Model 速率限制的 Provider(例如 ModelScope)。", - "importFreeModelsOnlyLabel": "__MISSING__:Import only free models", - "importFreeModelsOnlyHint": "__MISSING__:When enabled, only this provider's free models are imported. Paid models are skipped.", + "importFreeModelsOnlyLabel": "僅匯入免費模型", + "importFreeModelsOnlyHint": "啟用後,僅匯入此提供者的免費模型。付費模型將被跳過。", "perModelQuotaToggle": "按模型配額開關", "modelId": "模型 ID", "customModelPlaceholder": "例如:gpt-4.5-turbo", @@ -4378,7 +5040,7 @@ "accountName": "帳戶名稱", "email": "電子郵件", "healthCheckMinutes": "健康檢查(分鐘)", - "healthCheckHint": "主動令牌重新整理間隔。 0 = 停用。", + "healthCheckHint": "主動權杖重新整理間隔。 0 = 停用。", "deselectAllModels": "取消全選", "modelsActiveCount": "{active}/{total} 已啟用", "noModelsMatch": "沒有匹配 \"{filter}\" 的模型", @@ -4390,9 +5052,9 @@ "editCompatibleTitle": "編輯 {type} 相容", "compatibleBaseUrlHint": "{type} 相容 API 的根 URL。若端點路徑是自定義的,請在高階設定中配置。", "apiKeyForCheck": "API 金鑰(用於檢查)", - "testModelIdLabel": "__MISSING__:Model ID (optional)", - "testModelIdPlaceholder": "__MISSING__:e.g. my-model-id", - "testModelIdHint": "__MISSING__:If the provider lacks a /models endpoint, enter a model ID to validate via /chat/completions instead.", + "testModelIdLabel": "模型 ID(選用)", + "testModelIdPlaceholder": "例如:my-model-id", + "testModelIdHint": "如果提供者缺少 /models 端點,請輸入模型 ID 以透過 /chat/completions 驗證。", "compatibleProdPlaceholder": "{type} 相容(產品)", "tokenRefreshed": "Token 重新整理成功", "tokenRefreshFailed": "Token 重新整理失敗", @@ -4476,19 +5138,19 @@ "codexAuthApplyFailed": "在本地應用 Codex auth.json 失敗", "codexAuthExported": "Codex auth.json 已匯出", "codexAuthExportFailed": "匯出 Codex auth.json 失敗", - "codexCliGuideButton": "__MISSING__:Codex CLI Guide", - "codexCliGuideTitle": "__MISSING__:Codex CLI Guide", - "codexCliGuideLoading": "__MISSING__:Loading guide...", - "codexCliGuideLoadFailed": "__MISSING__:Could not load the guide.", - "codexExternalLinkButton": "__MISSING__:External Codex link", - "codexExternalLinkModalTitle": "__MISSING__:External Codex link", - "codexExternalLinkModalDescription": "__MISSING__:Share this single-use link with the person who will authenticate the Codex account. They open it in their own browser, complete the OpenAI login, and the connection is registered here. The link expires in 15 minutes.", - "codexExternalLinkGenerating": "__MISSING__:Generating link...", - "codexExternalLinkWaiting": "__MISSING__:Waiting for browser authentication. This window refreshes automatically.", - "codexExternalLinkCreateFailed": "__MISSING__:Failed to generate the link.", - "codexExternalLinkNetworkError": "__MISSING__:Could not contact the server.", - "codexExternalLinkConnected": "__MISSING__:Codex account connected through the external link.", - "codexExternalLinkExpired": "__MISSING__:The link expired before completion.", + "codexCliGuideButton": "Codex CLI 指南", + "codexCliGuideTitle": "Codex CLI 指南", + "codexCliGuideLoading": "正在載入指南…", + "codexCliGuideLoadFailed": "無法載入指南。", + "codexExternalLinkButton": "外部 Codex 連結", + "codexExternalLinkModalTitle": "外部 Codex 連結", + "codexExternalLinkModalDescription": "將此一次性連結分享給將要驗證 Codex 帳戶的人。他們在自己的瀏覽器中開啟連結,完成 OpenAI 登入,連線即會在此註冊。連結將在 15 分鐘後過期。", + "codexExternalLinkGenerating": "正在產生連結⋯", + "codexExternalLinkWaiting": "等待瀏覽器驗證。此視窗會自動重新整理。", + "codexExternalLinkCreateFailed": "產生連結失敗。", + "codexExternalLinkNetworkError": "無法聯繫伺服器。", + "codexExternalLinkConnected": "已透過外部連結連線 Codex 帳戶。", + "codexExternalLinkExpired": "連結在完成前已過期。", "importCodexAuth": "匯入授權", "codexImportModalTitle": "匯入法典認證", "codexImportTabSingle": "單人", @@ -4525,12 +5187,12 @@ "advancedSettings": "高階設定", "chatPathLabel": "聊天端點路徑", "chatPathPlaceholder": "/chat/completions", - "chatPathHint": "為非標準 API 的提供商自定義聊天路徑(例如:/v4/chat/completions)", + "chatPathHint": "為非標準 API 的提供者自定義聊天路徑(例如:/v4/chat/completions)", "modelsPathLabel": "模型端點路徑", "modelsPathPlaceholder": "/models", "modelsPathHint": "為驗證流程自定義模型路徑(例如:/v4/models)", - "clientIdentityLabel": "__MISSING__:Client Identity", - "clientIdentityHint": "__MISSING__:Optional. Adds client fingerprint headers (e.g. User-Agent) matching a known CLI for compatible gateways that expect one.", + "clientIdentityLabel": "用戶端身分", + "clientIdentityHint": "選用。為需要此資訊的相容閘道新增與已知 CLI 相符的客戶端指紋標頭(例如 User-Agent)。", "statusDeactivated": "已停用(手動)", "statusBanned": "已封禁 / 沙箱違規", "statusCreditsExhausted": "餘額不足 / 配額已耗盡", @@ -4556,7 +5218,7 @@ "apikey": "API key", "audio": "音訊", "audioProvidersHeading": "音訊 Provider", - "cloudAgentProviders": "雲代理提供商", + "cloudAgentProviders": "雲代理提供者", "audioShortLabel": "音訊", "azureOpenAiBaseUrlHint": "Azure OpenAI 資源的 Base URL。", "bailianBaseUrlHint": "阿里雲百鍊服務的 Base URL。", @@ -4568,8 +5230,8 @@ "t3ChatWebCookiePlaceholder": "凸會話 ID=abc123...", "blockClaudeExtraUsageDescription": "啟用後,一旦用量 API 回報排隊中的額外用量,OmniRoute 會將此 Claude 連線標記為不可用,讓容錯移轉切換到其他連線,而非繼續以按量計費的額外用量。", "blockClaudeExtraUsageLabel": "封鎖額外 Claude 用量", - "disableCoolingDescription": "__MISSING__:Skip the transient cooldown so this connection stays eligible even after recoverable errors (terminal states like banned/expired still apply).", - "disableCoolingLabel": "__MISSING__:Disable cooldown for this connection", + "disableCoolingDescription": "跳過暫時冷卻,使此連線在可恢復錯誤後仍保持資格(已停用/已過期等終止狀態仍適用)。", + "disableCoolingLabel": "對此連線停用冷卻", "bulkPasteAdded": "{count, plural, one {已新增 1 個 key} other {已新增 # 個 key}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {已跳過 1 個重複項} other {已跳過 # 個重複項}}", "bulkPasteHint": "每行貼上一個 API 金鑰。空行會被忽略,重複金鑰會被跳過。", @@ -4622,7 +5284,7 @@ "expirationBannerExpiringSoon": "憑據即將過期", "expirationBannerExpiringSoonDesc": "此 Provider 的憑據即將過期,請提前更新。", "extraApiKeyMasked": "金鑰 {index}:{prefix}••••{suffix}", - "extraApiKeysHint": "為同一提供商新增額外 API 金鑰,以便輪換和回退使用。", + "extraApiKeysHint": "為同一提供者新增額外 API 金鑰,以便輪換和回退使用。", "extraApiKeysLabel": "額外 API 金鑰", "apiKeyHealthLabel": "API 金鑰健康狀態", "apiKeyStatusActive": "正常", @@ -4652,8 +5314,8 @@ "embeddingRerankProviders": "嵌入與重排序", "imagesShortLabel": "影像", "llmProviders": "LLM Provider", - "localProviderApiKeyOptionalHint": "本地提供商的 API 金鑰通常是可選的。", - "localProviderBaseUrlHint": "輸入本地提供商的 Base URL。", + "localProviderApiKeyOptionalHint": "本地提供者的 API 金鑰通常是可選的。", + "localProviderBaseUrlHint": "輸入本地提供者的 Base URL。", "localProviders": "本地 Provider", "maxConcurrentWholeNumberError": "最大併發必須是整數", "museSparkWebCookieHint": "從 Muse Spark Web 會話複製 Cookie。", @@ -4666,7 +5328,7 @@ "perplexitySearchSharedKeyInfo": "Perplexity Search 可使用共享 key 配置。", "perplexityWebCookieHint": "從 Perplexity Web 會話複製 Cookie。", "perplexityWebCookiePlaceholder": "Perplexity Web Cookie", - "personalAccessTokenLabel": "個人訪問令牌", + "personalAccessTokenLabel": "個人訪問權杖", "qoderPatHint": "輸入 Qoder Personal Access Token。", "qoderPatPlaceholder": "Qoder PAT", "rateLimitOverridesSection": "速率限制覆蓋", @@ -4674,12 +5336,12 @@ "rateLimitOverridesMaxConcurrentLabel": "最大併發(速率限制)", "rateLimitOverridesMinTimeHint": "請求之間的最小時間(毫秒)。覆蓋預設速率限制器延遲。", "rateLimitOverridesMinTimeLabel": "最小間隔(毫秒)", - "rateLimitOverridesRpmHint": "此連線的每分鐘最大請求數。覆蓋提供商預設值。", + "rateLimitOverridesRpmHint": "此連線的每分鐘最大請求數。覆蓋提供者預設值。", "rateLimitOverridesRpmLabel": "RPM(請求/分鐘)", - "rateLimitOverridesTpdHint": "此連線的每日最大令牌數。覆蓋提供商預設值。", - "rateLimitOverridesTpdLabel": "TPD(令牌/天)", - "rateLimitOverridesTpmHint": "此連線的每分鐘最大令牌數。覆蓋提供商預設值。", - "rateLimitOverridesTpmLabel": "TPM(令牌/分鐘)", + "rateLimitOverridesTpdHint": "此連線的每日最大權杖數。覆蓋提供者預設值。", + "rateLimitOverridesTpdLabel": "TPD(權杖/天)", + "rateLimitOverridesTpmHint": "此連線的每分鐘最大權杖數。覆蓋提供者預設值。", + "rateLimitOverridesTpmLabel": "TPM(權杖/分鐘)", "refreshOauthTokenTitle": "重新整理 OAuth Token", "regionHint": "選擇此 Provider 使用的區域。", "regionLabel": "區域", @@ -4691,10 +5353,10 @@ "searchEngineIdHint": "Google Programmable Search Engine 的 ID。", "searchEngineIdLabel": "Search Engine ID", "searchEngineIdRequired": "Search Engine ID 為必填項", - "searchProvider": "搜尋提供商", - "searchProviderDesc": "按名稱、能力或類別查詢提供商。", + "searchProvider": "搜尋提供者", + "searchProviderDesc": "按名稱、能力或類別查詢提供者。", "searchProviders": "搜尋 Provider", - "searchByModel": "__MISSING__:Search by model…", + "searchByModel": "依模型搜尋…", "searchProvidersHeading": "搜尋 Provider", "searxngBaseUrlHint": "SearXNG 例項的 Base URL。", "searxngInfo": "配置 SearXNG 以啟用自託管 Web Search。", @@ -4720,7 +5382,7 @@ "totalKeysRotating": "{count, plural, one {1 個 key 正在輪換} other {# 個 key 正在輪換}}", "unhideModel": "取消隱藏模型", "upstreamProxyProviders": "上游代理 Provider", - "validationModelIdHint": "用於驗證此提供商連線的模型 ID。", + "validationModelIdHint": "用於驗證此提供者連線的模型 ID。", "validationModelIdLabel": "驗證模型 ID", "validationModelIdPlaceholder": "輸入用於測試的模型 ID", "vertexServiceAccountPlaceholder": "貼上 Service Account JSON({\"type\":\"service_account\"...})或 OAuth access_token", @@ -4734,13 +5396,13 @@ "zedImportNone": "沒有可從 Zed 匯入的內容", "zedImportSuccess": "Zed 匯入成功", "zedImporting": "正在從 Zed 匯入", - "freeTierProviders": "免費層提供商", + "freeTierProviders": "免費層提供者", "freeTierLabel": "有免費額度", - "freeTierProvidersDesc": "提供免費層的提供商——有些需要註冊 API 金鑰,有些無需任何憑證。", + "freeTierProvidersDesc": "提供免費層的提供者——有些需要註冊 API 金鑰,有些無需任何憑證。", "providerSummaryAll": "全部", - "ideProviders": "IDE 提供商", - "ideProvidersDesc": "內建 AI 訂閱的編輯器。使用提供商頁面直接從 IDE 金鑰鏈匯入憑據。", - "noIdeProviders": "沒有符合當前篩選條件的 IDE 提供商。", + "ideProviders": "IDE 提供者", + "ideProvidersDesc": "內建 AI 訂閱的編輯器。使用提供者頁面直接從 IDE 金鑰鏈匯入憑據。", + "noIdeProviders": "沒有符合當前篩選條件的 IDE 提供者。", "providerDetailFastTierTooltip": "預設對所有 Codex 連線應用 Codex Fast 層級", "providerDetailFastDefaultLabel": "快速預設", "providerDetailBrowserManualConnect": "瀏覽器/手動連線", @@ -4751,61 +5413,61 @@ "providerDetailMyClaudeAccountPlaceholder": "我的克勞德帳戶", "providerDetailPathAutoDetected": "根據作業系統 (Linux/Mac) 自動檢測路徑。", "webFetch": "網頁抓取", - "webFetchTooltip": "從網頁 URL 抽取內容的提供商(HTML → Markdown、抓取、截圖)", - "webFetchProvidersHeading": "網頁抓取提供商", + "webFetchTooltip": "從網頁 URL 抽取內容的提供者(HTML → Markdown、抓取、截圖)", + "webFetchProvidersHeading": "網頁抓取提供者", "compatibleProvidersDesc": "您託管或配置的 OpenAI 相容和 Anthropic 相容端點。將任意 OpenAI SDK 指向您的 URL 並在此處路由請求。", - "oauthProvidersDesc": "通過 OAuth 認證的提供商——登入一次,OmniRoute 自動處理令牌輪換。", - "webCookieProvidersDesc": "這些提供商使用瀏覽器網路會話、cookie 或網路令牌而不是 API 金鑰。開啟提供程式以新增所需的會話憑據。", - "apiKeyProvidersDesc": "標準 API 金鑰提供商。新增金鑰後,OmniRoute 代為路由、重試和限流。", + "oauthProvidersDesc": "通過 OAuth 認證的提供者——登入一次,OmniRoute 自動處理權杖輪換。", + "webCookieProvidersDesc": "這些提供者使用瀏覽器網路會話、cookie 或網路權杖而不是 API 金鑰。開啟提供程式以新增所需的會話憑據。", + "apiKeyProvidersDesc": "標準 API 金鑰提供者。新增金鑰後,OmniRoute 代為路由、重試和限流。", "noAuthProvidersDesc": "無需憑證的開放端點——無需註冊即可立即使用。", "upstreamProxyProvidersDesc": "通過上游代理路由出站流量。適用於企業網路或流量審計場景。", - "webFetchProvidersDesc": "從網頁 URL 抓取和提取內容的提供商。用於將即時網頁資料注入提示詞。", - "aggregatorsGatewaysDesc": "多提供商聚合器和 AI 閘道器,通過單一統一 API 對接數十個底層模型。", + "webFetchProvidersDesc": "從網頁 URL 抓取和提取內容的提供者。用於將即時網頁資料注入提示詞。", + "aggregatorsGatewaysDesc": "多提供者聚合器和 AI 閘道器,通過單一統一 API 對接數十個底層模型。", "enterpriseCloudDesc": "企業級和雲託管模型,提供增強 SLA、合規認證和專用容量。", "cloudAgentProvidersDesc": "自主雲代理,可執行長時間執行的任務,支援計劃審批和即時狀態跟蹤。", "localProvidersDesc": "在您自己的硬體上執行的自託管模型。資料不會離開您的基礎設施。", - "searchProvidersDesc": "網頁和文件搜尋提供商。可附加到 LLM 呼叫以實現檢索增強生成(RAG)。", - "audioProvidersDesc": "文本轉語音和語音轉文本提供商,用於語音輸入輸出和音訊轉錄管道。", - "embeddingRerankProvidersDesc": "向量嵌入和重排序提供商,用於語義搜尋、RAG 管道和相似度評分。", - "imageProvidersDesc": "影像生成和視覺提供商——從文本建立影像或分析現有影像。", - "videoProvidersDesc": "影片生成提供商。從文本提示或影像建立短影片片段。", - "onboardingWizard": "提供商入職嚮導", + "searchProvidersDesc": "網頁和文件搜尋提供者。可附加到 LLM 呼叫以實現檢索增強生成(RAG)。", + "audioProvidersDesc": "文本轉語音和語音轉文本提供者,用於語音輸入輸出和音訊轉錄管道。", + "embeddingRerankProvidersDesc": "向量嵌入和重排序提供者,用於語義搜尋、RAG 管道和相似度評分。", + "imageProvidersDesc": "影像生成和視覺提供者——從文本建立影像或分析現有影像。", + "videoProvidersDesc": "影片生成提供者。從文本提示或影像建立短影片片段。", + "onboardingWizard": "提供者入職嚮導", "onboardingWizardShort": "入職嚮導", - "onboardingWizardDescription": "通過驗證、永續性和即時連線測試連線 API 金鑰、自定義相容和 OAuth 提供商。", + "onboardingWizardDescription": "通過驗證、永續性和即時連線測試連線 API 金鑰、自定義相容和 OAuth 提供者。", "onboardingStepType": "型別", - "onboardingStepProvider": "提供商", + "onboardingStepProvider": "提供者", "onboardingStepCredentials": "憑證", "onboardingStepResult": "結果", - "onboardingTypeApiKeyTitle": "API 金鑰提供商", + "onboardingTypeApiKeyTitle": "API 金鑰提供者", "onboardingTypeApiKeyText": "使用內建提供程式,例如 OpenAI、Anthropic、Gemini、Groq、Azure 等。", - "onboardingTypeCustomTitle": "自定義相容提供商", + "onboardingTypeCustomTitle": "自定義相容提供者", "onboardingTypeCustomText": "建立與 OpenAI、Anthropic 或 Claude Code 相容的端點並新增其金鑰。", - "onboardingTypeOAuthTitle": "OAuth 提供商", - "onboardingTypeOAuthText": "為編碼提供商重用現有的 OAuth、裝置程式碼或本地匯入流程。", - "onboardingChooseOAuthProvider": "選擇 OAuth 提供商", - "onboardingChooseApiKeyProvider": "選擇 API 金鑰提供商", - "onboardingChooseProviderDescription": "選擇一個提供商,然後嚮導將指導您完成憑據和測試。", + "onboardingTypeOAuthTitle": "OAuth 提供者", + "onboardingTypeOAuthText": "為編碼提供者重用現有的 OAuth、裝置程式碼或本地匯入流程。", + "onboardingChooseOAuthProvider": "選擇 OAuth 提供者", + "onboardingChooseApiKeyProvider": "選擇 API 金鑰提供者", + "onboardingChooseProviderDescription": "選擇一個提供者,然後嚮導將指導您完成憑據和測試。", "onboardingChangeType": "變更型別", - "onboardingChangeProvider": "更換提供商", - "onboardingSearchProviders": "搜尋提供商...", + "onboardingChangeProvider": "更換提供者", + "onboardingSearchProviders": "搜尋提供者...", "onboardingApiKeyOptional": "API 金鑰可選", - "onboardingProviderConnected": "提供商已連線", + "onboardingProviderConnected": "提供者已連線", "onboardingProviderSavedWithWarnings": "提供者儲存時帶有警告", - "onboardingProviderFinished": "提供商入職完成", - "onboardingYourProviderConnection": "您的提供商連線", + "onboardingProviderFinished": "提供者入職完成", + "onboardingYourProviderConnection": "您的提供者連線", "onboardingTestPassed": "測試通過", "onboardingTestFailed": "測試失敗", - "onboardingOpenProviderDetails": "開放提供商詳細資訊", + "onboardingOpenProviderDetails": "開放提供者詳細資訊", "onboardingTryInPlayground": "去遊樂場試試", "onboardingDefaultConnectionName": "{provider} 主要", - "onboardingTestingConnection": "測試提供商連線...", + "onboardingTestingConnection": "測試提供者連線...", "onboardingValidatingCredentials": "正在驗證憑據...", - "onboardingSavingConnection": "正在儲存提供商連線...", - "onboardingProviderFailed": "提供商加入失敗", + "onboardingSavingConnection": "正在儲存提供者連線...", + "onboardingProviderFailed": "提供者加入失敗", "onboardingCreatingCompatibleProvider": "建立相容的提供者...", - "onboardingSavingCompatibleConnection": "正在儲存相容的提供商連線...", - "onboardingCustomProviderFallbackName": "定製提供商", - "onboardingCustomProviderFailed": "自定義提供商加入失敗", + "onboardingSavingCompatibleConnection": "正在儲存相容的提供者連線...", + "onboardingCustomProviderFallbackName": "定製提供者", + "onboardingCustomProviderFailed": "自定義提供者加入失敗", "onboardingLoadingOAuthConnection": "正在載入 OAuth 連線...", "onboardingOAuthNoConnectionFound": "OAuth 已完成,但未找到提供者連線。", "onboardingOAuthFailed": "OAuth 登入失敗", @@ -4816,51 +5478,51 @@ "onboardingBaseUrlOverrideHint": "可選。儲存為providerSpecificData.baseUrl。", "onboardingRegion": "地區", "onboardingSearchCx": "搜尋 CX/引擎 ID", - "onboardingProviderSpecificIdPlaceholder": "可選的提供商特定 ID", + "onboardingProviderSpecificIdPlaceholder": "可選的提供者特定 ID", "onboardingCustomUserAgent": "自定義使用者代理", "onboardingWorking": "工作…", "onboardingValidateSaveTest": "驗證、儲存和測試", "onboardingBack": "返回", - "onboardingCreateCustomCompatibleProvider": "建立自定義相容提供商", + "onboardingCreateCustomCompatibleProvider": "建立自定義相容提供者", "onboardingCreateCustomCompatibleDescription": "該向導首先建立一個提供程式節點,然後儲存並測試其 API 金鑰連線。", "onboardingProtocol": "協議", "onboardingOpenAiCompatible": "相容 OpenAI", "onboardingAnthropicCompatible": "人類相容", "onboardingClaudeCodeCompatible": "克勞德程式碼相容", - "onboardingProviderPrefix": "提供商字首", - "onboardingProviderPrefixHint": "用於生成託管提供商 ID。", + "onboardingProviderPrefix": "提供者字首", + "onboardingProviderPrefixHint": "用於生成託管提供者 ID。", "onboardingChatPath": "聊天路徑", "onboardingModelsPath": "模型路徑", "onboardingCreateSaveTest": "建立、儲存和測試", "onboardingConnectProvider": "連線 {provider}", - "onboardingOAuthFlowDescription": "OmniRoute 將開啟該提供商的現有 OAuth 流程。登入後,嚮導將重新載入已儲存的連線並執行與提供程式頁面相同的連線測試。", + "onboardingOAuthFlowDescription": "OmniRoute 將開啟該提供者的現有 OAuth 流程。登入後,嚮導將重新載入已儲存的連線並執行與提供程式頁面相同的連線測試。", "onboardingStartOAuthFlow": "啟動 OAuth 流程", "passthroughModelsDescription": "{provider} 接受提供者本機模型 ID。從 /models 匯入或新增用於路由的自定義 ID。", "bedrockModelsDescription": "Amazon Bedrock 模型的範圍按 AWS 區域劃分。從 /models 匯入或新增在所選區域中啟用的基岩模型 ID。", "bedrockModelPlaceholder": "人類.克勞德十四行詩-4-6", "addProviderSessionCookieTitle": "新增 {provider} 會話 cookie", - "addProviderWebTokenTitle": "新增 {provider} 網路令牌", + "addProviderWebTokenTitle": "新增 {provider} 網路權杖", "addProviderConnectionTitle": "新增 {provider} 連線", - "webTokenCredentialLabel": "網路會話令牌", + "webTokenCredentialLabel": "網路會話權杖", "webNoAuthCredentialLabel": "無需任何憑證", "webCookieCredentialHint": "所需 cookie:{credential}。貼上您自己登入的 {provider} Web 會話中的 Cookie 標頭值。請勿包含 Cookie: 字首。", - "webTokenCredentialHint": "憑證:{credential}。貼上您自己登入的 {provider} Web 會話中的令牌值,或者貼上 DevTools HAR 匯出(如果提供商支援)的令牌值。", + "webTokenCredentialHint": "憑證:{credential}。貼上您自己登入的 {provider} Web 會話中的權杖值,或者貼上 DevTools HAR 匯出(如果提供者支援)的權杖值。", "webCookieEditHint": "留空以保留當前會話 cookie。所需 cookie:{credential}。", - "webTokenEditHint": "留空以保留當前的 ​​Web 會話令牌。憑證:{credential}。", + "webTokenEditHint": "留空以保留當前的 ​​Web 會話權杖。憑證:{credential}。", "webSessionGuideTitle": "如何獲取會話憑證", "webSessionGuideIntro": "{provider} 使用瀏覽器網路會話而不是 API 金鑰。", "webCookieRequiredCredential": "所需 cookie:{credential}", - "webTokenRequiredCredential": "所需令牌:{credential}", + "webTokenRequiredCredential": "所需權杖:{credential}", "webSessionGuideStep1": "在瀏覽器中登入 {provider}。", "webSessionGuideStep2": "開啟瀏覽器開發人員工具並檢查 Web 應用程式發出的請求。", - "webSessionGuideStep3": "從提供商自己的域複製所需的憑據。對於 cookie,僅複製 Cookie 標頭值並省略 Cookie:。", + "webSessionGuideStep3": "從提供者自己的域複製所需的憑據。對於 cookie,僅複製 Cookie 標頭值並省略 Cookie:。", "webSessionGuideStep4": "將其貼上到此處並檢查連線。如果它停止工作,請重新登入並將其替換為新值。", "webSessionSecurityHint": "將其視為密碼:它可以訪問您登入的網路帳戶,直到其過期或被撤銷。", "webNoAuthGuideTitle": "無需任何憑證", "webNoAuthGuideBody": "{provider} 不需要 API 金鑰或 cookie。儲存連線以使用其免費 Web 端點。", "webSessionCredentialValidationFailed": "會話憑據驗證失敗。重新登入,複製新的憑據,然後重試。", "checkCookie": "檢查cookie", - "checkWebToken": "檢查令牌", + "checkWebToken": "檢查權杖", "huggingchatLabel": "HuggingChat(免費)", "huggingchatDesc": "通過 huggingface.co/chat 免費使用 LLM 聊天", "poeWebLabel": "Poe 網頁", @@ -4873,14 +5535,315 @@ "kimiWebDesc": "通過 www.kimi.com 訪問 Moonshot AI 聊天(國際版,Connect-RPC API)", "doubaoWebLabel": "Dola Web", "doubaoWebDesc": "通過 dola.com 訪問字節跳動 AI 聊天", - "overrideBaseUrlAdvanced": "__MISSING__:Advanced: override base URL", - "overrideBaseUrlHint": "__MISSING__:Advanced: point this built-in provider at a custom endpoint. Leave blank to use the default.", - "bulkAddFormatHintCloudflare": "__MISSING__:One key per line. Format: name|accountId|apiKey (Cloudflare account ID + API token).", - "visionCapableLabel": "__MISSING__:Vision capable", - "visionCapableHint": "__MISSING__:Manually flag this model as vision-capable when the provider's discovery metadata doesn't report an image input modality (common for self-hosted/local backends).", + "overrideBaseUrlAdvanced": "進階:覆寫基礎 URL", + "overrideBaseUrlHint": "進階:將此內建提供者指向自訂端點。留空以使用預設值。", + "bulkAddFormatHintCloudflare": "每行一個金鑰。格式:name|accountId|apiKey(Cloudflare 帳戶 ID + API 令牌)。", + "visionCapableLabel": "支援視覺", + "visionCapableHint": "當供應商的探索中繼資料未回報圖片輸入模式時(常見於自託管/本地後端),手動將此模型標記為具備視覺能力。", "kimiOfficialSupporterBadge": "Official Supporter", "kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) is an official OmniRoute launch partner", - "kimiPartnerLinkNote": "Partner link — supports OmniRoute at no extra cost to you" + "addPat": "新增 PAT", + "claudeImportEntryName": "項目 {number}", + "claudeImportNoValidEntries": "無有效項目可匯入", + "claudeImportParseError": "解析錯誤", + "compatAllowedParamsHint": "允許的參數(拒絕後重新加入)", + "compatAllowedParamsPlaceholder": "reasoning, …(逗號分隔)", + "compatBlockedParamsHint": "封鎖的參數(從請求中移除)", + "compatBlockedParamsPlaceholder": "thinking, …(逗號分隔)", + "compatParamFiltersLabel": "參數過濾器", + "compatSaving": "儲存中...", + "connect": "連線", + "contextWindowOverrideHint": "當提供者回報不正確時,手動設定此模型的實際上下文視窗(token)。此設定優先於自動偵測/目錄值,並可防止組合路由丟棄該模型。", + "contextWindowOverrideInvalid": "上下文視窗覆寫值必須為正整數 token 數", + "contextWindowOverrideLabel": "上下文視窗覆寫", + "contextWindowOverridePlaceholder": "例如 131072", + "customGatewayNamePlaceholder": "我的閘道", + "experimentalOauth": "實驗性 OAuth", + "globalCodexServiceMode": "全域 Codex 服務模式", + "grokConnectionName": "連線名稱(選填)", + "grokImportDescription": "匯入您的 Grok Build ~/.grok/auth.json 檔案。您可以在終端機中執行 grok login 來取得此檔案。", + "grokImportFailed": "匯入 Grok Build 驗證失敗", + "grokImportSuccess": "Grok Build 連線匯入成功", + "grokImportTitle": "匯入 Grok Build 驗證", + "grokInvalidAuth": "這不是有效的 Grok Build auth.json 檔案。必須是包含 JWT 金鑰的物件。", + "grokParseFailed": "無法解析 JSON", + "grokPasteJson": "貼上 JSON", + "grokRefreshIncluded": "已包含更新令牌 — 自動令牌更新已啟用", + "grokRefreshMissing": "找不到更新令牌 — 請在令牌過期後重新匯入", + "grokSaveConnection": "儲存連線", + "grokSaving": "正在儲存...", + "grokUploadFile": "上傳檔案", + "grokValidToken": "偵測到有效的 Grok Build 令牌", + "hideConnectionFromProviderQuota": "在提供者配額中隱藏此帳號", + "importAuth": "匯入驗證", + "importErrorInvalidPriority": "無效的優先權(必須為 1-100)", + "importErrorMalformedRow": "格式錯誤的行", + "importErrorMissingApiKey": "缺少 API 金鑰", + "importErrorMissingName": "缺少名稱", + "importErrorMissingProvider": "缺少提供者", + "importErrorNotArray": "檔案必須包含 JSON 陣列", + "importFromFile": "從檔案匯入", + "importFromFileChoose": "選擇檔案", + "importFromFileColBaseUrl": "基本 URL", + "importFromFileColName": "名稱", + "importFromFileColProvider": "提供者", + "importFromFileDescription": "上傳列出多個提供者的 CSV 或 JSON 檔案(每行可以是不同的提供者類型)。在下方檢視解析後的行,並選取要匯入的項目。", + "importFromFileErrorLine": "第 {line} 行:{reason}", + "importFromFileImport": "匯入 {count} 個提供者", + "importFromFileImporting": "正在匯入…", + "importFromFileResult": "已匯入 {success} 個提供者({failed} 個失敗)", + "importFromFileSelectHint": "已選取 {count}/{total} 個", + "importFromFileTitle": "從檔案匯入提供者", + "importGrokAuth": "匯入 Grok Build 驗證", + "inherit": "繼承", + "interceptFetchHint": "將原生的 web_fetch 工具呼叫重新導向至 OmniRoute 的 /v1/web/fetch。", + "interceptFetchLabel": "攔截 web_fetch", + "interceptSearchHint": "將原生的 web_search 工具呼叫重新導向至 OmniRoute 的 /v1/search。", + "interceptSearchLabel": "攔截 web_search", + "interceptionLoadError": "載入攔截設定失敗:{error}", + "interceptionSaveError": "儲存攔截設定失敗:{error}", + "interceptionSectionHint": "將此提供者的原生 web_search / web_fetch 工具呼叫透過 OmniRoute 自己的搜尋和擷取端點路由,而非讓提供者原生執行。預設為關閉 — 現有行為保持不變。", + "interceptionSectionTitle": "Web 工具攔截", + "lmarenaWebCookieHint": "開啟 arena.ai,登入後從網路請求中複製完整的 Cookie 標頭。請包含 arena-auth-prod-v1.0 和 arena-auth-prod-v1.1(以及後續區塊,如有),最好附帶 cf_clearance。請勿僅貼上空白的 arena-auth-prod-v1 cookie。可選:若 create-evaluation 仍然回傳 403,可提供 providerSpecificData.recaptchaV3Token。", + "m365TierEduOption": "教育版", + "m365TierEnterpriseOption": "企業/工作版", + "m365TierHint": "選擇此連線使用哪個 Microsoft 365 Copilot 介面。Individual 是預設的消費者 BizChat;教育版和企業(工作)版則使用各自的租戶介面。", + "m365TierIndividualOption": "個人版(預設)", + "m365TierLabel": "Copilot 層級", + "manualApiKey": "手動 API 金鑰", + "onboardingProviderDescriptions": { + "360ai": "在 ai.360.cn 取得 API 金鑰", + "agentrouter": "在 https://agentrouter.org/register 取得 $200 美元免費額度 — 無需信用卡。", + "agnes": "在 agnes-ai.com 取得 API 金鑰", + "agy": "匯入你的 Antigravity CLI(`agy`)登入資訊(貼上/上傳其 token 檔案)、自動偵測本地 CLI 登入,或使用 Google 登入。共用 Antigravity 後端(包含 Claude 模型)。", + "ai21": "註冊即贈 $10 美元試用額度(有效期 3 個月),無需信用卡", + "aimlapi": "免費方案已暫停(2026 年)— AI/ML API 現僅提供隨用隨付方案(最低儲值 $20 美元);無定期免費額度。", + "alibaba": "使用 API 金鑰連線阿里雲。", + "alibaba-cn": "使用 API 金鑰連線阿里雲(中國)。", + "amazon-q": "使用與 Kiro 相同的 AWS Builder ID 或匯入的 refresh-token 流程,但將 Amazon Q 連線分開管理。", + "anthropic": "使用 API 金鑰連線 Anthropic。", + "antigravity": "使用現有的 OAuth 流程連線 Antigravity。", + "api-airforce": "從 https://panel.api.airforce 取得 API 金鑰 — OpenAI 相容端點為 https://api.airforce/v1", + "arcee-ai": "在 arcee.ai 取得 API 金鑰", + "azure-ai": "Foundry 使用 OpenAI v1 介面,以部署名稱作為模型。OmniRoute 將根資源 URL 正規化為 v1 chat 和 /models 端點。", + "azure-openai": "使用你的 Azure OpenAI API 金鑰。基本 URL 應為你的資源端點,例如 https://my-resource.openai.azure.com。", + "bai": "b.ai OpenAI 相容 LLM 閘道的 Bearer API 金鑰(不同於 TheB.AI)。在 https://docs.b.ai 建立金鑰,然後使用 https://api.b.ai/v1 作為 OpenAI 相容的基本 URL。", + "baichuan": "在 platform.baichuan-ai.com 取得 API 金鑰", + "baidu": "在 console.bce.baidu.com 取得 API 金鑰", + "bailian-coding-plan": "使用 API 金鑰連線阿里雲通義靈碼。", + "baseten": "$30 美元 GPU 推論免費試用額度", + "bazaarlink": "在 https://bazaarlink.ai 建立免費 API 金鑰 — 模型 'auto:free' 路由至零成本推論。所有模型使用 提供者/模型名稱 格式,例如 xiaomi/mimo-v2.5-pro。", + "bedrock": "原生 Bedrock 整合:模型探索使用 Bedrock 基礎模型和推論設定檔,聊天則使用區域性 Bedrock Runtime Converse/ConverseStream API。", + "black-forest-labs": "使用 API 金鑰連線 Black Forest Labs。", + "blackbox": "免費方案:無限基本聊天加上 Minimax-M2.5,無需信用卡", + "bluesminds": "在 https://www.bluesminds.com 取得你的 API 金鑰 — OpenAI 相容端點為 https://api.bluesminds.com/v1,附每日免費額度。VIP 模型(Claude Opus 4.5、Gemini 2.5 Pro)會消耗 pi 額度。", + "byteplus": "使用 API 金鑰連線 BytePlus ModelArk。", + "bytez": "$1 美元免費額度,每 4 週更新", + "cerebras": "免費試用:每天 100 萬 tokens、每分鐘 3 萬 TPM、每分鐘 5 次請求 — 無需信用卡。", + "charm-hyper": "在 https://hyper.charm.land 建立 API 金鑰,然後在此貼上作為 Bearer token。", + "chenzk": "OpenAI 相容閘道,在 chenzk.top 提供即時模型目錄。", + "chutes": "Chutes OpenAI 相容閘道的 Bearer API 金鑰。", + "clarifai": "Clarifai 在 /v2/ext/openai/v1 提供 OpenAI 相容的聊天、回應和 /models 端點。公開/社群模型通常需要 PAT;應用程式範圍的金鑰僅適用於該應用程式內的資源。", + "claude": "使用現有的 OAuth 流程連線 Claude Code。", + "cline": "使用現有的 OAuth 流程連線 Cline。", + "cloudflare-ai": "需要 API Token 和 Account ID(可在 dash.cloudflare.com 找到)", + "codestral": "使用 API 金鑰連線 Codestral。", + "codex": "使用現有的 OAuth 流程連線 OpenAI Codex。", + "cohere": "免費試用:每月 1,000 次 API 呼叫供測試使用,無需信用卡", + "command-code": "從 Command Code 建立或複製 API 金鑰,然後在此貼上作為 Bearer token。", + "coze": "在 coze.com/open/api 取得 API 金鑰", + "crof": "使用 API 金鑰連線 CrofAI。", + "cursor": "使用現有的 OAuth 流程連線 Cursor IDE。", + "databricks": "使用 API 金鑰連線 Databricks。", + "datarobot": "預設閘道從 /genai/llmgw/catalog/ 目錄中取得活躍模型。也支援使用部署 URL 進行直接的 OpenAI 相容聊天請求。", + "deepinfra": "註冊即贈免費額度,供 API 測試和模型探索使用", + "deepseek": "註冊即贈 500 萬免費 tokens — 無需信用卡", + "dgrid": "在 https://dgrid.ai 建立 DGrid API 金鑰,然後使用 https://api.dgrid.ai/v1 作為 OpenAI 相容的基本 URL。", + "dify": "從你的 Dify 實例取得 API 金鑰。", + "digitalocean": "使用 API 金鑰連線 DigitalOcean。", + "dit": "dit.ai(Distributed Intelligence Trade)是一個 OpenAI 相容的路由器/閘道,採用動態按請求定價,在 https://api.dit.ai/v1 提供 /v1/chat/completions 端點。OmniRoute 使用 OpenAI 協定;支出/節省分析可在 dit.ai 儀表板中查看。", + "doubao": "在 console.volcengine.com 取得 API 金鑰", + "empower": "Empower 在 https://app.empower.dev/api/v1 提供 OpenAI 相容聊天功能,並在 empower-functions 上支援工具呼叫。", + "factory": "在 https://app.factory.ai/settings/api-keys 取得你的 Factory API 金鑰,然後以 Bearer token 形式貼上。OpenAI 相容端點為 https://api.factory.ai/v1。", + "fal-ai": "使用 API 金鑰連線 Fal.ai。", + "featherless-ai": "提供免費方案 — 無需信用卡", + "fenayai": "FenayAI OpenAI 相容閘道的 Bearer API 金鑰。", + "firecrawl": "使用 API 金鑰連線 Firecrawl。", + "fireworks": "註冊即贈 $1 美元入門免費額度,供 API 測試使用", + "freeaiapikey": "40+ 種模型的折扣 API 代理,包括 GPT-5、Claude Opus 4.6、Claude Sonnet 4.6、Qwen 3.5。在 https://freeaiapikey.com/dashboard 取得 API 金鑰。基本 URL:https://freeaiapikey.com/v1。", + "freemodel-dev": "在 https://freemodel.dev 取得 $300 美元免費 API 額度 — 無需付款資訊。OpenAI 相容端點。提供 GPT-5.4 和 GPT-5.5 模型。", + "freepik": "使用 Freepik 的 Mystic API 生成圖片。", + "freetheai": "免費的 OpenAI 相容閘道,支援透傳模型。", + "friendliai": "無伺服器推論的免費方案 — 無需信用卡", + "g4f-gemini": "免費無需金鑰的 g4f.space Gemini 反向代理,每分鐘限制 5 次請求。", + "g4f-groq": "免費無需金鑰的 g4f.space Groq 反向代理,每分鐘限制 5 次請求。", + "g4f-nvidia": "免費無需金鑰的 g4f.space NVIDIA NIM 反向代理,每分鐘限制 5 次請求。", + "g4f-ollama": "來自 g4f.space 的免費無需金鑰託管 Ollama 閘道,每分鐘限制 5 次請求。", + "g4f-pollinations": "免費無需金鑰的 g4f.space Pollinations 反向代理,每分鐘限制 5 次請求。", + "galadriel": "使用 API 金鑰連線 Galadriel。", + "gemini": "永久免費:Gemini 2.5 Flash 每天 1,500 次請求 — 無需信用卡,在 aistudio.google.com 取得金鑰", + "getgoapi": "使用 API 金鑰連線 GoAPI。", + "gigachat": "使用 API 金鑰連線 GigaChat(Sber)。", + "github": "使用現有的 OAuth 流程連線 GitHub Copilot。", + "github-models": "在 github.com/settings/tokens 建立具有 'models: read' 範圍的 GitHub PAT", + "gitlab": "用於公開 Code Suggestions API 的 GitLab 個人存取權杖。不使用 gitlab.com 時,請設定自託管的基本 URL。", + "gitlab-duo": "具有 ai_features + read_user 範圍的 OAuth 應用程式。在此 OmniRoute 實例上設定 GITLAB_DUO_OAUTH_CLIENT_ID 和可選的 GITLAB_DUO_OAUTH_CLIENT_SECRET。", + "gitlawb": "從 Gitlawb Opengateway 儀表板取得 API 金鑰。", + "gitlawb-gmi": "從 Gitlawb Opengateway 儀表板取得 API 金鑰。", + "glm": "使用 API 金鑰連線 GLM Coding。", + "glm-cn": "使用 API 金鑰連線 GLM Coding(中國)。", + "glmt": "預設 GLM 設定檔,具有較高的 token 預算、啟用思考功能,以及更長的超時時間。", + "groq": "免費方案:每分鐘 30 次 / 每天 14,400 次請求 — 無需信用卡", + "hackclub": "在 ai.hackclub.com 使用你的 Hack Club 帳號登入。", + "haiper": "在 haiper.ai/haiper-api 取得 API 金鑰", + "hcnsec": "在 api.hcnsec.cn 取得 API 金鑰", + "heroku": "使用 API 金鑰連線 Heroku AI。", + "huggingface": "數千種模型的免費 Inference API(Whisper、VITS、SDXL…)", + "hyperbolic": "註冊即贈 $1-5 美元試用額度,供無伺服器推論使用", + "ideogram": "在 ideogram.ai/docs/api 取得 API 金鑰", + "iflytek": "在 console.xfyun.cn 取得 API 金鑰", + "inference-net": "註冊即贈 $25 美元免費額度,另提供研究補助金", + "jina-ai": "Jina AI 重新排序 API 的 Bearer API 金鑰。", + "jina-reader": "使用 API 金鑰連線 Jina Reader。", + "kenari": "Kenari 在 https://kenari.id/v1/chat/completions 提供 OpenAI 相容的聊天完成端點,以及涵蓋 Claude、GPT、DeepSeek、GLM、Kimi 等模型的即時 /v1/models 目錄。OmniRoute 使用 OpenAI 協定並透過透傳方式列出模型。", + "kie": "使用 API 金鑰連線 KIE.AI。", + "kilo-gateway": "使用 API 金鑰連線 Kilo Gateway。", + "kilocode": "使用現有的 OAuth 流程連線 Kilo Code。", + "kimi": "使用 API 金鑰連線 Kimi。", + "kimi-coding": "使用現有的 OAuth 流程連線 Kimi Coding。", + "kimi-coding-apikey": "使用 API 金鑰連線 Kimi Coding(API 金鑰)。", + "kiro": "免費方案:每月 50 額度(約 2.5 萬至 10 萬 tokens)。⚠️ Kiro 服務條款禁止第三方代理/轉接使用。", + "lambda-ai": "使用 API 金鑰連線 Lambda AI。", + "laozhang": "使用 API 金鑰連線 LaoZhang AI。", + "leonardo": "在 leonardo.ai/developer 取得 API 金鑰", + "liquid": "在 liquid.ai 取得 API 金鑰", + "llamagate": "使用 API 金鑰連線 LlamaGate。", + "llm7": "無需 API 金鑰即可使用(使用 'unused' 作為金鑰)。在 token.llm7.io 取得免費 token 以獲得更高限制。", + "longcat": "免費:帳號註冊 + KYC 驗證後一次性贈送 1,000 萬 tokens(LongCat-2.0)。僅限一次性 — 非每日/每月定期配額。", + "maritalk": "使用 API 金鑰連線 Maritalk。", + "meta-llama": "使用 API 金鑰連線 Meta Llama API。", + "minimax": "使用 API 金鑰連線 Minimax Coding。", + "minimax-cn": "使用 API 金鑰連線 Minimax(中國)。", + "mistral": "免費實驗方案:所有模型速率限制存取,無需信用卡", + "mixedbread": "使用 Mixedbread API 建立嵌入向量。", + "modal": "Modal 通常在 /v1 提供用戶自託管的 OpenAI 相容應用程式。OmniRoute 將探測 /v1/models 並將聊天流量路由至 /v1/chat/completions。", + "modelscope": "透過 ModelScope API-Inference 的免費方案 — 需要阿里雲帳號。", + "monsterapi": "在 monsterapi.ai 取得 API 金鑰", + "moonshot": "使用 API 金鑰連線 Moonshot AI。", + "morph": "免費方案:每月 25 萬額度,$0 美元", + "nanogpt": "使用 API 金鑰連線 NanoGPT。", + "nebius": "註冊即贈約 $1 美元試用額度,供 API 測試使用", + "nlpcloud": "NLP Cloud 使用專有的聊天機器人 API,而非 OpenAI chat/completions。OmniRoute 將 OpenAI 訊息轉換為 input/context/history,並提供支援的聊天機器人模型本地目錄。", + "nomic": "在 atlas.nomic.ai 取得 API 金鑰", + "nous-research": "Nous 提供 OpenAI 相容的 /v1 介面,附有大型遠端 /models 目錄。/chat/completions 端點需要有效的 API 金鑰才能進行程式化推論。", + "novita": "註冊即贈 $0.50 美元試用額度(有效期約 1 年)", + "nscale": "註冊即贈 $5 美元免費額度,供推論測試使用", + "nube": "使用 API 金鑰連線 Nube.sh。", + "nvidia": "免費開發者存取:每分鐘約 40 次請求,70+ 種模型(Kimi K2.5、GLM 4.7、DeepSeek V3.2...)", + "oci": "OCI 提供 OpenAI 相容的聊天和回應端點。Project ID 在 OmniRoute 中為選填,但 Responses 和代理工作流程可能需要。", + "ollama-cloud": "使用 API 金鑰連線 Ollama Cloud。", + "openadapter": "OpenAdapter 在 https://api.openadapter.in/v1/chat/completions 提供 OpenAI 相容的聊天完成端點,匯集 70+ 種開源模型(DeepSeek、Qwen、Kimi、MiniMax、GLM、Llama、Mistral…)。OmniRoute 使用 OpenAI 協定。", + "openai": "使用 API 金鑰連線 OpenAI。", + "opencode-go": "使用 API 金鑰連線 OpenCode Go。", + "opencode-zen": "使用 API 金鑰連線 OpenCode Zen。", + "openrouter": "使用 :free 後綴的 $0/token 免費模型 — 每分鐘 20 次 / 每天 200 次請求", + "openvecta": "註冊即贈免費額度,可用於 LLM、嵌入向量和推理模型的 OpenAI 相容推論", + "orcarouter": "在 https://www.orcarouter.ai 建立 API 金鑰(以 sk-orca- 開頭),然後以 Bearer token 形式貼上。OpenAI 相容端點為 https://api.orcarouter.ai/v1。", + "ovhcloud": "使用 API 金鑰連線 OVHcloud AI。", + "perplexity": "使用 API 金鑰連線 Perplexity。", + "piapi": "使用 API 金鑰連線 PiAPI。", + "pioneer": "$75 美元免費使用額度 — 無需信用卡", + "poe": "Poe 在 https://api.poe.com/v1 提供 OpenAI 相容的聊天和回應功能,並在 /usage/current_balance 進行認證餘額檢查。", + "pollinations": "免費無需金鑰方案:openai、openai-fast、openai-large、qwen-coder、mistral、deepseek、grok、gemini-flash-lite-3.1、perplexity-fast、perplexity-reasoning。進階模型(claude、gemini、midijourney)需要從 enter.pollinations.ai 取得 Pollinations API 金鑰。", + "predibase": "$25 美元免費試用額度(30 天有效期)", + "publicai": "需要 API 金鑰 — 一次性註冊額度,之後付費", + "puter": "在 puter.com/dashboard 取得 token → 複製 Auth Token", + "qianfan": "使用百度 AI 雲的千帆 API 金鑰。預設端點為 OpenAI 相容的 v2。", + "qiniu": "在 https://portal.qiniu.com/ai-inference/api-key 建立七牛 AI 推論 API 金鑰,然後以 Bearer token 形式貼上。OpenAI 相容端點為 https://api.qnaigc.com/v1,使用單一金鑰代理 DeepSeek、Claude、Kimi 等模型。", + "qwen": "使用現有的 OAuth 流程連線 Qwen Code。", + "recraft": "使用 API 金鑰連線 Recraft。", + "reka": "Reka Chat 在 /v1 提供 OpenAI 相容介面。OmniRoute 探測 /v1/models 並將聊天流量路由至 /v1/chat/completions。", + "requesty": "在 https://app.requesty.ai 建立 API 金鑰,然後以 Bearer token 形式貼上。OpenAI 相容端點為 https://router.requesty.ai/v1,附即時 /v1/models 目錄。", + "runwayml": "Runway 影片生成為任務導向。OmniRoute 提交文字轉影片或圖片轉影片作業,輪詢 /v1/tasks/[id],並將完成的影片輸出正規化為類似 OpenAI 的 /v1/videos/generations 回應。", + "sambanova": "註冊即贈 $5 美元免費額度(30 天有效期),無需信用卡", + "sap": "模型探索使用 AI_API_URL 上的 /v2/lm/scenarios/foundation-models/models。聊天請求使用 deploymentUrl/chat/completions,並需要 AI-Resource-Group。", + "scaleway": "新帳戶贈送 100 萬免費 tokens — 符合歐盟/GDPR 規範(巴黎),Qwen3 235B 和 Llama 70B", + "segmind": "使用 Segmind 的託管模型生成圖片和影片。", + "sensenova": "在 platform.sensenova.cn 取得 API 金鑰", + "siliconflow": "$1 美元免費額度,身分驗證後另有永久免費模型", + "snowflake": "使用 API 金鑰連線 Snowflake Cortex。", + "sparkdesk": "在 console.xfyun.cn 取得 API 金鑰", + "stability-ai": "使用 API 金鑰連線 Stability AI。", + "stepfun": "在 platform.stepfun.com 取得 API 金鑰", + "sumopod": "SumoPod 在 https://ai.sumopod.com/v1/chat/completions 提供 OpenAI 相容的聊天完成端點,以及即時 /v1/models 目錄。OmniRoute 使用 OpenAI 協定並透過透傳方式列出模型。", + "suno": "貼上來自 suno.ai 的 session cookie(Clerk 驗證)", + "synthetic": "使用 API 金鑰連線 Synthetic。", + "tencent": "在 console.cloud.tencent.com 取得 API 金鑰", + "thebai": "TheB.AI OpenAI 相容閘道的 Bearer API 金鑰。", + "tinyfish": "來自 agent.tinyfish.ai/api-keys 的 X-API-Key", + "together": "使用 API 金鑰連線 Together AI。", + "tokenrouter": "TokenRouter 在 https://api.tokenrouter.com/v1/chat/completions 提供 OpenAI 相容的聊天完成端點,以及可用的 /v1/models 目錄。OmniRoute 使用 OpenAI 協定。", + "topaz": "使用 API 金鑰連線 Topaz。", + "udio": "貼上來自 udio.com 的 session cookie(Supabase 驗證)", + "uncloseai": "無需驗證。API 接受任何非空字串作為識別金鑰。", + "upstage": "使用 API 金鑰連線 Upstage。", + "v0-vercel": "使用 API 金鑰連線 v0(Vercel)。", + "venice": "使用 API 金鑰連線 Venice.ai。", + "vercel-ai-gateway": "使用 API 金鑰連線 Vercel AI Gateway。", + "vertex": "提供 Service Account JSON 或 OAuth access_token", + "vertex-partner": "提供用於 Vertex AI 合作夥伴模型的相同 Service Account JSON。", + "volcengine": "使用 API 金鑰連線火山引擎。", + "voyage-ai": "Voyage AI 嵌入向量和重新排序 API 的 Bearer API 金鑰。", + "wafer": "來自 https://wafer.ai 的 API 金鑰", + "wandb": "使用 API 金鑰連線 Weights & Biases Inference。", + "watsonx": "watsonx 模型閘道在 /ml/gateway/v1 下提供 OpenAI 相容的 /chat/completions 和 /models 端點。", + "x5lab": "X5Lab 在 https://api.x5lab.dev/v1/chat/completions 提供 OpenAI 相容的聊天完成端點,以及即時 /v1/models 目錄。OmniRoute 使用 OpenAI 協定並透過透傳方式列出模型。", + "xai": "使用 API 金鑰連線 xAI(Grok)。", + "xiaomi-mimo": "使用 API 金鑰連線 Xiaomi MiMo。", + "yi": "在 platform.lingyiwanwu.com 取得 API 金鑰", + "zai": "來自 https://open.bigmodel.cn/usercenter/apikeys 的 API 金鑰", + "zenmux": "ZenMux 在 /api/v1/chat/completions 提供 OpenAI 相容的聊天完成端點,以及 Anthropic Messages(/api/anthropic/v1/messages)和 Google Gemini(/api/vertex-ai)協定介面。OmniRoute 使用 OpenAI 協定。" + }, + "openWebProviderSite": "開啟 {host}", + "paramFiltersAllowedHint": "這些參數會在從拒絕清單中移除後被重新加入(僅限用戶端有發送的情況)。", + "paramFiltersAllowedLabel": "允許的參數", + "paramFiltersAllowedPlaceholder": "reasoning, …(以逗號分隔)", + "paramFiltersAutoLearnHint": "啟用後,若上游回傳 400 錯誤並顯示「不支援的參數:X」,該參數將自動加入封鎖清單並重新發送請求。", + "paramFiltersAutoLearnLabel": "從 400 錯誤自動學習", + "paramFiltersBlockedHint": "這些參數會從對外請求中移除(拒絕清單)。", + "paramFiltersBlockedLabel": "被封鎖的參數", + "paramFiltersBlockedPlaceholder": "thinking, reasoning_budget, …(以逗號分隔)", + "paramFiltersLoadError": "載入參數過濾器設定失敗:{error}", + "paramFiltersResetError": "重設參數過濾器設定失敗:{error}", + "paramFiltersResetSuccess": "參數過濾器設定已重設為預設值", + "paramFiltersResetToDefault": "重設為預設值", + "paramFiltersSaveChanges": "儲存變更", + "paramFiltersSaveError": "儲存參數過濾器設定失敗:{error}", + "paramFiltersSaveSuccess": "參數過濾器設定已儲存", + "paramFiltersSaving": "正在儲存…", + "paramFiltersSectionHint": "在發送至此提供者前移除或重新加入請求參數。用於避免因提供者拒絕特定參數而產生的 400 錯誤(例如 NVIDIA NIM 拒絕 thinking)。", + "paramFiltersSectionTitle": "參數過濾器", + "providerQuotaShort": "配額", + "quotaVisibilityUpdateFailed": "更新提供者配額可見性失敗", + "showConnectionInProviderQuota": "在提供者配額中顯示此帳號", + "tierOverrideAuto": "自動(由路由決定)", + "tierOverrideCheap": "便宜", + "tierOverrideFree": "免費", + "tierOverrideHelpText": "將此提供者固定到路由層級,而非讓 OmniRoute 從模型定價推斷。", + "tierOverrideLabel": "層級覆寫", + "tierOverridePremium": "高級", + "zedImportAction": "匯入", + "zedImportDescription": "探索 Zed IDE 存放在 OS 鑰匙圈中的 AI 提供者憑證(OpenAI、Anthropic、Google、Mistral、xAI),並將其匯入為連線。此機器上必須已安裝 Zed IDE。", + "zedImportTitle": "從 Zed 鑰匙圈匯入", + "zedManualDescription": "當 OmniRoute 在 Docker 中執行或鑰匙圈無法使用時使用此選項。貼上 Zed 儲存在 ~/.config/zed/settings.json 下的 API 金鑰,或從 Zed AI 設定面板複製。", + "zedManualImportFailed": "手動匯入失敗", + "zedManualImportSuccess": "已從 Zed 匯入 {provider} token", + "zedManualTitle": "手動 Token 匯入", + "zedNoCredentials": "在鑰匙圈中找不到 Zed 憑證", + "zedPasteApiKey": "貼上 API 金鑰…", + "zedSaving": "正在儲存…", + "zedUnsupportedCredentials": "找到 {count} 個鑰匙圈憑證,但均未匹配支援的提供者", + "kimiPartnerLinkNote": "合作夥伴連結 — 支援 OmniRoute,您無需額外付費" }, "settings": { "title": "設定", @@ -4890,11 +5853,11 @@ "routing": "路由", "cache": "快取", "resilience": "韌性", - "description": "__MISSING__:Description", - "enable": "__MISSING__:Enable", - "disable": "__MISSING__:Disable", - "update": "__MISSING__:Update", - "routingSettingsIntro": "控制您的請求如何路由、轉換和傳送給 AI 提供商。", + "description": "說明", + "enable": "啟用", + "disable": "停用", + "update": "更新", + "routingSettingsIntro": "控制您的請求如何路由、轉換和傳送給 AI 提供者。", "routingOpDropParagraphContainsLabel": "丟棄段落(包含)", "routingOpDropParagraphStartsWithLabel": "丟棄段落(開頭匹配)", "routingOpReplaceTextLabel": "替換文本", @@ -4911,7 +5874,7 @@ "routingOpDropBlockContainsDesc": "移除整個系統塊(而非僅段落),只要其文本包含任一指定子串。當一整塊帶指紋時使用,例如注入的 MCP 伺服器描述。", "routingOpPrependSystemBlockDesc": "在系統陣列的開頭插入一個新的文本塊。用於新增 Anthropic 分類器期望的 SDK 身份宣告 'You are a Claude agent, built on Anthropic's Claude Agent SDK.'。", "routingOpAppendSystemBlockDesc": "在系統陣列末尾追加一個新文本塊。用於不必置於 [0] 位置的修飾性內容。", - "routingOpInjectBillingHeaderDesc": "在前置位置插入特殊的 'x-anthropic-billing-header: cc_version=...; cc_entrypoint=...; cch=...;' 文本塊,用於通過 Anthropic 分類器校驗。CC 橋接轉發端點必須使用;原生 claude 提供商已自帶計費行,在那裡通常無需此操作。", + "routingOpInjectBillingHeaderDesc": "在前置位置插入特殊的 'x-anthropic-billing-header: cc_version=...; cc_entrypoint=...; cch=...;' 文本塊,用於通過 Anthropic 分類器校驗。CC 橋接轉發端點必須使用;原生 claude 提供者已自帶計費行,在那裡通常無需此操作。", "routingOpObfuscateWordsDesc": "在每個指定詞的首字母后插入零寬連字元,例如 'opencode' 變為 'o‍pencode'。視覺上對人類完全相同,但能繞過分類器的詞匹配。作用於系統塊、使用者/助手訊息以及工具描述。", "routingNeedlesHint": "子串列表。段落只要包含其中任一項即匹配。通過「新增條目」逐行新增。", "routingPrefixesHint": "字串列表。段落以其中任一項開頭即匹配(匹配前會去除前導空白)。", @@ -4925,7 +5888,7 @@ "routingIdempotencyKeyHint": "可選。設定後,如果已有以該鍵開頭的塊存在,則跳過此操作,避免重試時重複插入。", "routingBillingEntrypointHint": "作為 'cc_entrypoint=' 注入的值。Anthropic 接受 'sdk-cli'(Agent SDK)、'cli'(Claude Code CLI)或其他文件化的值。", "routingBillingVersionFormatHint": "cc_version= 後 3 字元構建雜湊的計算方式。'ex-machina' = sha256(CCH_SALT + 第一條使用者訊息字元 + 版本)(每條訊息獨立)。'omniroute-daystamp' = sha256(YYYY-MM-DD + 版本)(按天穩定)。", - "routingBillingCchAlgoHint": "5 字元 cch= 令牌的計算方式。'sha256-first-user' = 第一條使用者訊息文本的 sha256;'xxhash64-body' = 由請求體級簽名稍後填充;'static-zero' = 字面佔位符 '00000'。", + "routingBillingCchAlgoHint": "5 字元 cch= 權杖的計算方式。'sha256-first-user' = 第一條使用者訊息文本的 sha256;'xxhash64-body' = 由請求體級簽名稍後填充;'static-zero' = 字面佔位符 '00000'。", "routingObfuscateWordsHint": "要混淆的小寫詞語。ZWJ 插入對大小寫不敏感,所以 'opencode' 也會匹配 'OpenCode' 與 'OPENCODE'。", "routingObfuscateTargetsHint": "掃描詞語的請求體範圍:系統塊、使用者/助手訊息以及工具描述。", "routingObfuscateTargetsLabel": "目標", @@ -4938,8 +5901,8 @@ "routingSummarizeAppendSystemBlock": "追加塊:\"{text}\"", "routingSummarizeInjectBillingHeader": "注入計費頭(entrypoint={entrypoint},version={versionFormat},cch={cchAlgo})", "routingSummarizeObfuscateWords": "通過 ZWJ 在 {targets} 中混淆 {count} 個詞", - "routingDefaultAutoVariantLKGP": "上次正常的提供商", - "routingDefaultAutoVariantLKGPDesc": "上次正常的提供商", + "routingDefaultAutoVariantLKGP": "上次正常的提供者", + "routingDefaultAutoVariantLKGPDesc": "上次正常的提供者", "routingDefaultAutoVariantCoding": "程式碼場景質量優先", "routingDefaultAutoVariantCodingDesc": "程式碼場景質量優先", "routingDefaultAutoVariantFast": "低延遲路由", @@ -4990,7 +5953,7 @@ "memoryDesc": "跨會話持久化對話記憶", "memoryEnabled": "啟用記憶", "memoryEnabledDesc": "啟用後,OmniRoute 會注入相關的歷史上下文。", - "memoryTokenCostWarning": "__MISSING__:Heads up: with memory enabled, OmniRoute injects up to {tokens} tokens of retrieved context into every chat request — this increases token usage and cost. To skip injection for a specific request, send the \"x-omniroute-no-memory: true\" header.", + "memoryTokenCostWarning": "提醒:啟用記憶功能後,OmniRoute 會將最多 {tokens} 個 token 的檢索上下文注入到每個聊天請求中 — 這會增加 token 使用量和成本。若要跳過特定請求的注入,請傳送 \"x-omniroute-no-memory: true\" 標頭。", "maxTokens": "最大 Tokens", "retentionDays": "保留時長", "recent": "最近", @@ -5018,28 +5981,28 @@ "justNow": "剛才", "modelsDevStats": "同步統計", "modelsDevStatsDesc": "當前 models.dev 資料覆蓋情況", - "providers": "提供商", + "providers": "提供者", "modelsWithPricing": "已有定價的模型", "capabilities": "能力", "lastSyncCount": "上次同步數量", "lastSyncFull": "上次完整同步", "modelsDevInfo": "工作原理", - "modelsDevInfoDesc": "models.dev 是由 SST/OpenCode 團隊維護的開源 AI 模型規格資料庫,提供 100+ 提供商、4,000+ 模型的定價、能力、上下文限制和模態資訊。", + "modelsDevInfoDesc": "models.dev 是由 SST/OpenCode 團隊維護的開源 AI 模型規格資料庫,提供 100+ 提供者、4,000+ 模型的定價、能力、上下文限制和模態資訊。", "modelsDevInfoResolution": "定價解析順序(優先順序從高到低):", "modelsDevInfoOrder": "使用者覆蓋 → models.dev → LiteLLM → 硬編碼預設值", "systemTheme": "系統主題", "debugToggle": "啟用除錯模式", - "logToolSourcesToggle": "__MISSING__:Log Tool Sources", - "logToolSourcesDescription": "__MISSING__:Emit a diagnostic log line per request summarizing tool count and MCP/hosted/client source breakdown.", + "logToolSourcesToggle": "記錄工具來源", + "logToolSourcesDescription": "每個請求發出一行診斷記錄,總結工具數量及 MCP/託管/客戶端來源分布。", "homePinProviderQuotaToHome": "將資訊固定到首頁", - "homeProviderQuotaLimits": "提供商配額限制", - "homeProviderQuotaLimitsDesc": "將提供商配額狀態容器(含全部重新整理按鈕)固定到首頁頂部。", + "homeProviderQuotaLimits": "提供者配額限制", + "homeProviderQuotaLimitsDesc": "將提供者配額狀態容器(含全部重新整理按鈕)固定到首頁頂部。", "homeQuickStart": "快速入門", "homeQuickStartDesc": "在首頁上顯示快速入門面板。", - "homeProviderTopology": "提供商拓撲", - "homeProviderTopologyDesc": "在首頁上顯示提供商拓撲。", + "homeProviderTopology": "提供者拓撲", + "homeProviderTopologyDesc": "在首頁上顯示提供者拓撲。", "accountEmailVisibility": "帳號郵箱可見性", - "accountEmailVisibilityDesc": "在提供商、組合、日誌、配額和 Playground 頁面顯示完整帳號郵箱。關閉後預設打碼顯示。", + "accountEmailVisibilityDesc": "在提供者、組合、日誌、配額和 Playground 頁面顯示完整帳號郵箱。關閉後預設打碼顯示。", "sidebarVisibilityToggle": "顯示側邊欄專案", "enableCache": "啟用快取", "cacheTTL": "快取生存時間", @@ -5070,20 +6033,20 @@ "enableSystemPrompt": "啟用系統提示", "systemPromptText": "系統提示文字", "autoDisableBannedAccounts": "自動停用被封禁帳戶", - "autoDisableDescription": "若提供商連線返回特定的永久封禁訊號(如 HTTP 403\"請驗證您的帳戶\"),則將其永久標記為停用。這會將其從組合輪換中移除。", + "autoDisableDescription": "若提供者連線返回特定的永久封禁訊號(如 HTTP 403\"請驗證您的帳戶\"),則將其永久標記為停用。這會將其從組合輪換中移除。", "autoDisableThreshold": "封禁閾值", "autoDisableThresholdDesc": "觸發永久停用所需的連續封禁訊號次數。", - "customBannedSignals": "__MISSING__:Banned Keywords", - "customBannedSignalsDesc": "__MISSING__:Additional keywords that trigger permanent account ban detection. Built-in keywords always apply.", - "customBannedSignalsPlaceholder": "__MISSING__:e.g. api key revoked", - "noCustomBannedSignals": "__MISSING__:No custom keywords. Only built-in keywords are active.", + "customBannedSignals": "禁止關鍵字", + "customBannedSignalsDesc": "觸發永久帳戶封鎖偵測的額外關鍵字。內建關鍵字始終適用。", + "customBannedSignalsPlaceholder": "例如:api key revoked", + "noCustomBannedSignals": "無自訂關鍵字。僅內建關鍵字生效。", "resilienceStructureTitle": "彈性結構", "resilienceStructureDesc": "此頁面僅配置行為。即時斷路器狀態顯示在執行狀況頁面上。組合特定的重試和迴圈槽控制保留在組合設定上。", "enableThinking": "激發思考", - "maxThinkingTokens": "最大思考令牌", + "maxThinkingTokens": "最大思考權杖", "enableProxy": "啟用代理", "perKeyProxyEnabled": "啟用按金鑰代理分配", - "perKeyProxyEnabledDesc": "啟用後,每個提供商連線可以使用自己的代理分配", + "perKeyProxyEnabledDesc": "啟用後,每個提供者連線可以使用自己的代理分配", "proxyUrl": "代理網址", "pricingRates": "定價費率格式", "currentPricing": "當前定價概述", @@ -5161,7 +6124,7 @@ "evictions": "驅逐", "loadingCacheStats": "正在載入快取統計資訊...", "globalProxy": "全域性代理", - "globalProxyDesc": "為所有 API 呼叫配置全域性出站代理。單獨的提供商、組合和鍵可以覆蓋此設定。", + "globalProxyDesc": "為所有 API 呼叫配置全域性出站代理。單獨的提供者、組合和鍵可以覆蓋此設定。", "noGlobalProxy": "沒有配置全域性代理", "proxyPool": "代理池", "freePool": "免費池", @@ -5184,7 +6147,7 @@ "healthcheckLatency": "延遲", "proxyDocumentationScopeTitle": "代理作用域解析", "proxyDocumentationScopeDescBefore": "OmniRoute 按優先順序順序解析出站代理:", - "proxyDocumentationScopeOrder": "組合 → 帳戶 → 提供商 → 全域性", + "proxyDocumentationScopeOrder": "組合 → 帳戶 → 提供者 → 全域性", "proxyDocumentationScopeDescAfter": "。最具體的作用域優先。", "proxyDocumentationAddTitle": "新增自定義代理", "proxyDocumentationAddDescBefore": "轉到", @@ -5196,15 +6159,15 @@ "proxyDocumentationSocks5DescBefore": "SOCKS5 代理預設停用。設定", "proxyDocumentationFreePoolDesc": "免費池選項卡聚合來自 1proxy、Proxifly 和 IPLocate 的代理。使用 ⊕ 測試並將代理提升到您的登錄檔中。只有通過連通性測試的代理才會被新增。", "proxyDocumentationVercelRelayDescBefore": "Vercel Relay 是一個出站邊緣中繼,而不是入站隧道。部署後,LLM API 呼叫將通過 Vercel 的動態 IP 傳送,繞過資料中心地理封鎖和速率限制。中繼由生成的金鑰頭保護", - "proxyDocumentationVercelRelayDescAfter": "您的 Vercel 令牌僅在部署期間使用,不會儲存。", - "vercelRelayTokenRequired": "需要令牌", + "proxyDocumentationVercelRelayDescAfter": "您的 Vercel 權杖僅在部署期間使用,不會儲存。", + "vercelRelayTokenRequired": "需要權杖", "vercelRelayDeployFailed": "部署失敗", - "vercelRelayTokenHint": "令牌僅在部署期間使用——從不儲存。", - "denoRelayTokenRequired": "__MISSING__:Deno Deploy token is required", - "denoRelayOrgDomainRequired": "__MISSING__:Organization domain is required", - "denoRelayDeployFailed": "__MISSING__:Deno Deploy failed", - "denoRelayTokenHint": "__MISSING__:Organization token (prefix ddo_) from console.deno.com → Organization → Settings → Organization Tokens. Used once for deploy and never stored.", - "denoRelayOrgDomainHint": "__MISSING__:Your Deno Deploy organization's default domain (e.g. acme.deno.net). The relay will be reachable at https://..deno.net.", + "vercelRelayTokenHint": "權杖僅在部署期間使用——從不儲存。", + "denoRelayTokenRequired": "需要 Deno Deploy 令牌", + "denoRelayOrgDomainRequired": "需要組織網域", + "denoRelayDeployFailed": "Deno Deploy 失敗", + "denoRelayTokenHint": "來自 console.deno.com → Organization → Settings → Organization Tokens 的組織令牌(前綴 ddo_)。僅在部署時使用一次,永不儲存。", + "denoRelayOrgDomainHint": "您的 Deno Deploy 組織的預設網域(例如 acme.deno.net)。轉發站位於 https://..deno.net。", "proxyFreePoolFilterProtocol": "按協議篩選", "proxyFreePoolProtocol": "協議", "proxyFreePoolCountryPlaceholder": "國家(例如 US)", @@ -5228,22 +6191,22 @@ "close": "關閉", "cancel": "取消", "globalLabel": "全域性", - "configure": "配置", + "configure": "設定", "globalSystemPrompt": "全域性系統提示", "saved": "已儲存", "beforePromptLabel": "提示詞前置", - "beforePromptDesc": "注入到代理/提供商系統指令之前", - "beforePromptPlaceholder": "插入到代理/提供商提示詞之前的指令...", + "beforePromptDesc": "注入到代理/提供者系統指令之前", + "beforePromptPlaceholder": "插入到代理/提供者提示詞之前的指令...", "afterPromptLabel": "提示詞後置", - "afterPromptDesc": "注入到代理/提供商系統指令之後", - "afterPromptPlaceholder": "插入到代理/提供商提示詞之後的指令...", + "afterPromptDesc": "注入到代理/提供者系統指令之後", + "afterPromptPlaceholder": "插入到代理/提供者提示詞之後的指令...", "chars": "{count} 字元", "thinkingBudgetTitle": "思考預算", - "thinkingBudgetDesc": "控制所有請求中 AI 推理令牌的使用", + "thinkingBudgetDesc": "控制所有請求中 AI 推理權杖的使用", "passthrough": "直通", "passthroughDesc": "沒有變化——客戶控制思維預算", "auto": "自動", - "autoDesc": "丟棄所有思考配置,由提供商自行決定", + "autoDesc": "丟棄所有思考配置,由提供者自行決定", "custom": "定製", "customDesc": "為所有請求設定固定的 Token 預算", "adaptive": "自適應", @@ -5281,30 +6244,30 @@ "bruteForceProtectionDesc": "在同一 IP 多次失敗後,對 /api/auth/login 進行限流和鎖定。", "corsAllowedOrigins": "CORS 允許的源", "corsAllowedOriginsDesc": "允許呼叫此伺服器的瀏覽器來源列表,以逗號分隔。空列表 = 不允許瀏覽器 CORS 訪問(伺服器到伺服器仍可用)。僅在開發環境使用 CORS_ALLOW_ALL=true 環境變數。", - "blockedProviders": "被阻止的提供商", - "blockedProvidersDesc": "在 `/v1/models` 響應中隱藏指定提供商。被隱藏的提供商不會出現在模型列表中。", - "providersBlocked": "{count} 個提供商/模型已遮蔽", + "blockedProviders": "被阻止的提供者", + "blockedProvidersDesc": "在 `/v1/models` 響應中隱藏指定提供者。被隱藏的提供者不會出現在模型列表中。", + "providersBlocked": "{count} 個提供者/模型已遮蔽", "blockProviderTitle": "遮蔽 {provider}", "unblockProviderTitle": "取消遮蔽 {provider}", "cliFingerprint": "CLI 指紋匹配", "cliFingerprintDesc": "在代理請求時模擬原生 CLI 二進位制的請求特徵。會重新排列請求頭和請求體欄位,使其與官方 CLI 工具更一致,同時保留你的代理 IP。", - "cliFingerprintEnabled": "已有 {count} 個提供商啟用了 CLI 指紋", + "cliFingerprintEnabled": "已有 {count} 個提供者啟用了 CLI 指紋", "enableFingerprintTitle": "為 {provider} 啟用指紋", "disableFingerprintTitle": "為 {provider} 停用指紋", "systemTransforms": "系統塊轉換管道", - "systemTransformsDesc": "在轉發之前,每個提供者訂購的轉換管道應用於請求正文。支援任何提供商 ID。", - "systemTransformsAddProvider": "新增提供商", - "systemTransformsAddProviderPlaceholder": "選擇提供商...", - "systemTransformsAddProviderAllConfigured": "所有提供商均已配置", - "systemTransformsRemoveProvider": "刪除提供商", - "systemTransformsNoProviders": "沒有配置提供者。新增提供商即可開始。", + "systemTransformsDesc": "在轉發之前,每個提供者訂購的轉換管道應用於請求正文。支援任何提供者 ID。", + "systemTransformsAddProvider": "新增提供者", + "systemTransformsAddProviderPlaceholder": "選擇提供者...", + "systemTransformsAddProviderAllConfigured": "所有提供者均已配置", + "systemTransformsRemoveProvider": "刪除提供者", + "systemTransformsNoProviders": "沒有配置提供者。新增提供者即可開始。", "systemTransformsOpMoveUp": "向上移動", "systemTransformsOpMoveDown": "下移", "systemTransformsOpDelete": "刪除操作", "routingStrategy": "路由策略", "routingAdvancedGuideTitle": "高階路由指南", "routingAdvancedGuideHint1": "需要可預測優先順序時使用 Fill First,需要公平分配時使用 Round Robin,需要延遲彈性時使用 P2C。", - "routingAdvancedGuideHint2": "如果各提供商在質量或成本上差異明顯,後臺任務可優先考慮“成本最佳化”,需要均衡消耗時可從“最少使用”開始。", + "routingAdvancedGuideHint2": "如果各提供者在質量或成本上差異明顯,後臺任務可優先考慮“成本最佳化”,需要均衡消耗時可從“最少使用”開始。", "fillFirst": "優先填滿", "fillFirstDesc": "按優先順序順序使用帳戶", "roundRobin": "輪詢", @@ -5382,15 +6345,15 @@ "details": "詳情", "time": "時間", "fallbackChainsTitle": "後備鏈", - "fallbackChainsDesc": "定義每個模型的提供商後備順序", + "fallbackChainsDesc": "定義每個模型的提供者後備順序", "addChain": "+ 新增鏈", "modelName": "模型名稱", "modelNamePlaceholder": "claude-sonnet-4-20250514", - "providersCommaSeparated": "提供商(用逗號分隔,按優先順序排序)", + "providersCommaSeparated": "提供者(用逗號分隔,按優先順序排序)", "providersCommaSeparatedPlaceholder": "anthropic, openai, gemini", "createChain": "建立鏈", "noFallbackChains": "無後備鏈", - "noFallbackChainsDesc": "建立一條鏈路,用於定義某個模型的提供商回退順序。", + "noFallbackChainsDesc": "建立一條鏈路,用於定義某個模型的提供者回退順序。", "loadingFallbackChains": "正在載入後備鏈...", "deleteChainConfirm": "刪除“{model}”的後備鏈?", "chainCreated": "為 {model} 建立的鏈", @@ -5398,8 +6361,8 @@ "failedCreateChain": "建立鏈失敗", "failedDeleteChain": "刪除鏈失敗", "deleteChain": "刪除鏈", - "fillModelAndProviders": "請填寫模型名稱和提供商", - "addAtLeastOneProvider": "新增至少一個提供商", + "fillModelAndProviders": "請填寫模型名稱和提供者", + "addAtLeastOneProvider": "新增至少一個提供者", "comboDefaultsTitle": "組合預設值", "comboDefaultsGuideTitle": "如何調整組合預設值", "comboDefaultsGuideHint1": "在低延遲流中保持較低的重試次數;僅增加長生成任務的超時。", @@ -5416,48 +6379,48 @@ "retryDelayLabel": "重試延遲(毫秒)", "timeoutLabel": "超時(毫秒)", "healthCheck": "健康檢查", - "healthCheckDesc": "預先檢查提供商的可用性", + "healthCheckDesc": "預先檢查提供者的可用性", "trackMetrics": "跟蹤指標", "trackMetricsDesc": "記錄每個組合請求指標", - "providerOverrides": "提供商覆蓋", - "providerOverridesDesc": "覆蓋每個提供商的超時和重試。提供商設定會覆蓋全域性預設設定。", + "providerOverrides": "提供者覆蓋", + "providerOverridesDesc": "覆蓋每個提供者的超時和重試。提供者設定會覆蓋全域性預設設定。", "providerMaxRetriesAria": "{provider} 最大重試次數", "providerTimeoutAria": "{provider} 超時毫秒", "removeProviderOverrideAria": "刪除 {provider} 覆蓋", - "selectProviderPlaceholder": "__MISSING__:Select provider...", - "searchProviderPlaceholder": "__MISSING__:Search providers...", - "searchProviderAria": "__MISSING__:Search providers", + "selectProviderPlaceholder": "選擇提供者…", + "searchProviderPlaceholder": "搜尋供應商⋯", + "searchProviderAria": "搜尋供應商", "newProviderNamePlaceholder": "例如 google、openai...", - "newProviderNameAria": "新的提供商名稱", + "newProviderNameAria": "新的提供者名稱", "retries": "重試", "ms": "毫秒", "saveComboDefaults": "儲存組合預設值", "maxNestingDepth": "最大巢狀深度", "concurrencyPerModel": "併發/模型", "queueTimeout": "佇列超時(毫秒)", - "queueDepth": "__MISSING__:Queue Depth", + "queueDepth": "佇列深度", "contextRelayHandoffThreshold": "交接閾值", "contextRelayMaxMessages": "摘要最大訊息數", "contextRelaySummaryModel": "摘要模型", "contextRelayProviderNote": "Context Relay 當前會為 Codex 帳戶生成交接摘要,並將這些值作為新的或未配置 combo 的全域性預設值。", - "providerProfiles": "提供商策略配置", - "providerProfilesDesc": "為 OAuth(基於會話)和 API Key(計費型)提供商分別設定彈性策略。由於速率限制更低,OAuth 提供商通常採用更嚴格的閾值。", - "oauthProviders": "OAuth 提供商", - "apiKeyProviders": "API 金鑰提供商", + "providerProfiles": "提供者策略配置", + "providerProfilesDesc": "為 OAuth(基於會話)和 API Key(計費型)提供者分別設定彈性策略。由於速率限制更低,OAuth 提供者通常採用更嚴格的閾值。", + "oauthProviders": "OAuth 提供者", + "apiKeyProviders": "API 金鑰提供者", "transientCooldown": "瞬時故障冷卻", "rateLimitCooldown": "限流冷卻", "maxBackoffLevel": "最大退避級別", "cbThreshold": "熔斷閾值", "cbResetTime": "熔斷重置時間", "rateLimiting": "速率限制", - "rateLimitingDesc": "API 金鑰提供商會自動受到安全預設值的速率限制。限制是從響應標頭中學習的,並隨著時間的推移進行調整。", + "rateLimitingDesc": "API 金鑰提供者會自動受到安全預設值的速率限制。限制是從響應標頭中學習的,並隨著時間的推移進行調整。", "defaultSafetyNet": "預設安全網", "rpm": "RPM", "minGap": "最小間隔", "maxConcurrent": "最大併發數", "activeLimiters": "活動限流器", "noActiveLimiters": "尚無活動的速率限制器。", - "reservoir": "令牌池", + "reservoir": "權杖池", "running": "執行中", "queued": "排隊中", "circuitBreakers": "斷路器", @@ -5532,23 +6495,23 @@ "modelPricingDesc": "配置每個模型的成本費率 • 所有費率均以美元/100 萬 Tokens 為單位", "registry": "登記處", "priced": "定價", - "searchProvidersModels": "搜尋提供商或模型...", + "searchProvidersModels": "搜尋提供者或模型...", "showAll": "顯示全部", - "noProvidersMatch": "沒有與您的搜尋匹配的提供商。", + "noProvidersMatch": "沒有與您的搜尋匹配的提供者。", "howPricingWorks": "定價如何運作", "cacheWrite": "快取寫入", "unsaved": "未儲存", "resetDefaults": "重置預設值", - "saveProvider": "儲存提供商", + "saveProvider": "儲存提供者", "model": "模型", "models": "模型", - "moreProviders": "{count} 更多提供商", + "moreProviders": "{count} 更多提供者", "withPricing": "已配置定價", "policiesCircuitBreakers": "策略與斷路器", "activeIssuesDetected": "檢測到活躍問題", "off": "關閉", "resetPricingConfirm": "將 {provider} 的所有定價重置為預設值?", - "pricingDescInput": "輸入:傳送到模型的令牌", + "pricingDescInput": "輸入:傳送到模型的權杖", "pricingDescOutput": "輸出:生成的 Tokens", "pricingDescCached": "快取:重用輸入(約輸入率的 50%)", "pricingDescReasoning": "推理:思考 Tokens(預設回退到輸出費率)", @@ -5558,7 +6521,7 @@ "totalModels": "模型總數", "active": "活躍", "costCalculation": "成本計算", - "costCalculationDesc": "成本是根據為每個模型配置的令牌使用情況和定價費率計算的。", + "costCalculationDesc": "成本是根據為每個模型配置的權杖使用情況和定價費率計算的。", "pricingFormat": "定價格式", "pricingFormatDesc": "所有費率均以美元/100 萬 Tokens 為單位(每百萬 Tokens 美元)。", "tokenTypes": "Token 型別", @@ -5572,43 +6535,43 @@ "viewFullDetails": "檢視完整詳情", "themeCoral": "珊瑚色", "adaptiveVolumeRouting": "自適應流量路由", - "adaptiveVolumeRoutingDesc": "根據即時負載量和吞吐壓力,動態調整各提供商連線承載的流量。", - "lkgpToggleTitle": "最後已知良好提供商(LKGP)", - "lkgpToggleDesc": "啟用後,路由器會記住上一次成功返回響應的提供商,並在後續請求中優先嚐試它。", - "echoRequestedModelTitle": "__MISSING__:Echo requested model name in responses", - "echoRequestedModelDesc": "__MISSING__:When enabled, the response `model` field echoes the alias or combo name the client requested instead of the upstream model name. Fixes strict clients (e.g. Claude Desktop) that reject a response whose model does not match the request.", - "webSearchRouteTitle": "__MISSING__:Web search routing", - "webSearchRouteDesc": "__MISSING__:When a request includes a native web_search tool, route the whole request to this model instead of the default — useful for providers that don't implement Anthropic's web_search server tool. Leave blank to disable.", - "webSearchRoutePlaceholder": "__MISSING__:e.g. openrouter,anthropic/claude-3.5-sonnet", + "adaptiveVolumeRoutingDesc": "根據即時負載量和吞吐壓力,動態調整各提供者連線承載的流量。", + "lkgpToggleTitle": "最後已知良好提供者(LKGP)", + "lkgpToggleDesc": "啟用後,路由器會記住上一次成功返回響應的提供者,並在後續請求中優先嚐試它。", + "echoRequestedModelTitle": "在回應中反映請求的模型名稱", + "echoRequestedModelDesc": "啟用後,回應的 `model` 欄位會反映客戶端請求的別名或組合名稱,而非上游模型名稱。修正嚴格客戶端(例如 Claude Desktop)因回應的 model 與請求不符而拒絕回應的問題。", + "webSearchRouteTitle": "網路搜尋路由", + "webSearchRouteDesc": "當請求包含原生的 web_search 工具時,將整個請求路由到此模型而非預設模型 — 適用於未實作 Anthropic web_search 伺服器工具的提供者。留空以停用。", + "webSearchRoutePlaceholder": "搜尋或選取模型⋯", "clearLkgpCache": "清除 LKGP 快取", "lkgpCacheCleared": "LKGP 快取已成功清除", "lkgpCacheClearFailed": "清除 LKGP 快取失敗", "days": "天", "lkgp": "LKGP 模式", - "lkgpDesc": "最後已知良好提供商(可預測的彈性)", + "lkgpDesc": "最後已知良好提供者(可預測的彈性)", "maintenance": "維護", "purgeExpiredLogs": "清理過期日誌", "purgeLogsFailed": "清理日誌失敗", - "logsDeleted": "__MISSING__:{count, plural, =0 {No expired logs purged} one {Purged # expired log} other {Purged # expired logs}}", - "resetUsageData": "__MISSING__:Reset Usage Data", - "resetUsageDataDesc": "__MISSING__:Select how far back you want to delete usage data. This action cannot be undone.", - "resetUsagePeriod_5m": "__MISSING__:5 minutes", - "resetUsagePeriod_1h": "__MISSING__:1 hour", - "resetUsagePeriod_3h": "__MISSING__:3 hours", - "resetUsagePeriod_6h": "__MISSING__:6 hours", - "resetUsagePeriod_12h": "__MISSING__:12 hours", - "resetUsagePeriod_1d": "__MISSING__:1 day", - "resetUsagePeriod_7d": "__MISSING__:7 days", - "resetUsagePeriod_30d": "__MISSING__:30 days", - "resetUsagePeriod_all": "__MISSING__:All time", - "resetUsageSuccess": "__MISSING__:{count, plural, =0 {No usage data rows deleted} one {Reset usage data (# row deleted)} other {Reset usage data (# rows deleted)}}", - "resetUsageFailed": "__MISSING__:Failed to reset usage data", - "reset": "__MISSING__:Reset", - "resetting": "__MISSING__:Resetting...", + "logsDeleted": "{count, plural, =0 {無過期紀錄被清除} one {已清除 # 筆過期紀錄} other {已清除 # 筆過期紀錄}}", + "resetUsageData": "重置使用量資料", + "resetUsageDataDesc": "選擇要刪除多遠以前的使用量、請求紀錄和分析資料。供應商設定、連線、API 金鑰、組合和設定將會保留。此動作無法復原。", + "resetUsagePeriod_5m": "5 分鐘", + "resetUsagePeriod_1h": "1 小時", + "resetUsagePeriod_3h": "3 小時", + "resetUsagePeriod_6h": "6 小時", + "resetUsagePeriod_12h": "12 小時", + "resetUsagePeriod_1d": "1 天", + "resetUsagePeriod_7d": "7 天", + "resetUsagePeriod_30d": "30 天", + "resetUsagePeriod_all": "全部時間", + "resetUsageSuccess": "{count, plural, =0 {未刪除任何使用量資料列} one {已重設使用量資料(已刪除 # 列)} other {已重設使用量資料(已刪除 # 列)}}", + "resetUsageFailed": "重設使用量資料失敗", + "reset": "重置", + "resetting": "正在重設⋯", "contextOpt": "上下文最佳化", "contextOptDesc": "根據上下文視窗需求和對話長度進行路由", - "priorityDesc": "順序回退——先嚐試提供商 1,再嘗試提供商 2,依此類推", - "weightedDesc": "按百分比權重在各提供商之間分配流量", + "priorityDesc": "順序回退——先嚐試提供者 1,再嘗試提供者 2,依此類推", + "weightedDesc": "按百分比權重在各提供者之間分配流量", "modelRoutingTitle": "模型路由規則", "modelRoutingDesc": "使用 glob 模式自動將模型路由到指定組合", "addRule": "新增規則", @@ -5659,13 +6622,13 @@ "pricingSyncFailedWithReason": "價格同步失敗:{reason}", "clearSyncedPricing": "清除同步定價", "compressionTitle": "提示詞壓縮", - "compressionDesc": "在傳送給提供商之前壓縮提示詞,以減少 token 使用量", + "compressionDesc": "在傳送給提供者之前壓縮提示詞,以減少 token 使用量", "tokenSaverTitle": "Token Saver", "tokenSaverSubtitle": "讓每次請求消耗更少 token。", "tokenSaverToolOutput": "工具輸出", - "tokenSaverToolOutputDesc": "在傳送給提供商前清理 git、grep、ls、tree 和日誌輸出。", + "tokenSaverToolOutputDesc": "在傳送給提供者前清理 git、grep、ls、tree 和日誌輸出。", "tokenSaverLlmOutput": "LLM 輸出", - "tokenSaverLlmOutputDesc": "注入簡潔回覆指令,不改寫提供商輸出。", + "tokenSaverLlmOutputDesc": "注入簡潔回覆指令,不改寫提供者輸出。", "tokenSaverInputCompression": "輸入壓縮", "tokenSaverInputCompressionDesc": "在保留程式碼、URL 和意圖的前提下改寫可壓縮的對話歷史。", "tokenSaverFineTunePrefix": "可在以下頁面微調各引擎:", @@ -5689,11 +6652,11 @@ "compressionUltraMinScore": "最低分數閾值", "compressionUltraSlmFallback": "回退到 Aggressive", "compressionUltraModelPath": "SLM 模型路徑", - "compressionUltraEngine": "__MISSING__:Ultra tier", - "compressionUltraEngineHeuristic": "__MISSING__:Heuristic (Tier-A, default)", - "compressionUltraEngineSlm": "__MISSING__:SLM (LLMLingua-2, opt-in)", - "compressionUltraSlmHint": "__MISSING__:SLM downloads a small ONNX model on first use (cold-start) and transparently falls back to the heuristic on timeout or if unavailable.", - "compressionUltraSlmPrewarm": "__MISSING__:Pre-warm SLM model on enable", + "compressionUltraEngine": "Ultra 層級", + "compressionUltraEngineHeuristic": "啟發式(Tier-A,預設)", + "compressionUltraEngineSlm": "SLM(LLMLingua-2,自選啟用)", + "compressionUltraSlmHint": "SLM 在首次使用(冷啟動)時下載一個小型 ONNX 模型,並在超時或不可用時透明地回退到啟發式。", + "compressionUltraSlmPrewarm": "啟用時預熱 SLM 模型", "compressionSummarizerEnabled": "啟用摘要器", "compressionMaxTokensPerMessage": "每條訊息最大 Token 數", "compressionMinSavings": "最低節省閾值", @@ -5705,12 +6668,12 @@ "compressionAutoTrigger": "自動觸發閾值", "compressionCacheTTL": "快取 TTL", "compressionPreserveSystem": "保留系統提示", - "compressionPreserveSystemAlways": "__MISSING__:Always", - "compressionPreserveSystemWhenNoCache": "__MISSING__:When no cache", - "compressionPreserveSystemNever": "__MISSING__:Never", + "compressionPreserveSystemAlways": "永遠", + "compressionPreserveSystemWhenNoCache": "無快取時", + "compressionPreserveSystemNever": "永不", "compressionCavemanConfig": "Caveman 引擎配置", "compressionCavemanConfigDesc": "微調基於規則的壓縮引擎", - "compressionCavemanPanelHint": "__MISSING__:Its on/off and level are set in the panel:", + "compressionCavemanPanelHint": "其開關與等級在面板中設定:", "compressionRoles": "壓縮訊息角色", "compressionRoleUser": "使用者", "compressionRoleAssistant": "助手", @@ -5751,7 +6714,7 @@ "qdrantHelpStep4": "4. 儲存,測試連線,然後測試搜尋。", "qdrantEmbeddingQuickSelect": "從發現的模型中快速選擇...", "qdrantEmbeddingInputPlaceholder": "openai/text-embedding-3-small", - "qdrantEmbeddingHint": "格式:提供商/模型。必須配置提供者憑證。", + "qdrantEmbeddingHint": "格式:提供者/模型。必須配置提供者憑證。", "qdrantApiKeyPlaceholderKeep": "(留空以保留當前金鑰)", "qdrantApiKeyPlaceholderOptional": "(如不用則留空)", "qdrantSaveHint": "提示:編輯主機/埠/集合/模型,然後單擊“儲存”。 API 金鑰是可選的。", @@ -5775,14 +6738,14 @@ "resilienceTrigger": "觸發:", "resilienceEffect": "效果:", "resilienceRequestQueueTitle": "請求佇列與速率", - "resilienceAutoEnableApiKeyProviders": "為 API 金鑰提供商自動啟用", + "resilienceAutoEnableApiKeyProviders": "為 API 金鑰提供者自動啟用", "resilienceRequestsPerMinute": "每分鐘請求數", "resilienceMinTimeBetweenRequests": "請求之間的最小時間", "resilienceConcurrentRequests": "併發請求數", "resilienceMaxQueueWaitTime": "最大佇列等待時間", "resilienceBaseCooldown": "基礎冷卻時間", "resilienceUseUpstreamRetryHints": "使用上游重試提示", - "resilienceDefaultPerProvider": "預設(按提供商)", + "resilienceDefaultPerProvider": "預設(按提供者)", "resilienceAlwaysOn": "始終開啟", "resilienceAlwaysOff": "始終關閉", "routingRemoveEntry": "刪除條目", @@ -5845,18 +6808,18 @@ "routingAntigravitySignatureBypassDesc": "經過輕量校驗後接受客戶端提交的簽名,無效時回退到已儲存的簽名。", "routingAntigravitySignatureBypassStrictLabel": "嚴格繞過", "routingAntigravitySignatureBypassStrictDesc": "在接受客戶端提交的簽名前要求完整的 protobuf 校驗。", - "routingHeaderFingerprintTitle": "標頭指紋(每個提供商)", + "routingHeaderFingerprintTitle": "標頭指紋(每個提供者)", "routingServerRejectedSave": "⚠ 伺服器拒絕儲存:", "routingAddTransformOp": "新增變換操作", "routingClientCacheControlTitle": "客戶端快取控制", "routingClientCacheControlDesc": "配置 OmniRoute 是否保留客戶端提交的 cache_control 標記", "routingClientCacheControlAutoDesc": "對於確定性的 Claude 相容流程,按原樣保留客戶端提交的 cache_control。如果請求未攜帶 cache_control,OmniRoute 不會注入任何由橋接器擁有的標記,以相容 CC 相容的第三方代理。", "routingClientCacheControlAlwaysLabel": "始終保留", - "routingClientCacheControlAlwaysDesc": "始終按原樣將客戶端提交的 cache_control 請求頭轉發給上游提供商。", + "routingClientCacheControlAlwaysDesc": "始終按原樣將客戶端提交的 cache_control 請求頭轉發給上游提供者。", "routingClientCacheControlNeverLabel": "從不保留", - "routingClientCacheControlNeverDesc": "始終移除客戶端的 cache_control 請求頭,在原生提供商流程支援時由 OmniRoute 管理快取。", + "routingClientCacheControlNeverDesc": "始終移除客戶端的 cache_control 請求頭,在原生提供者流程支援時由 OmniRoute 管理快取。", "routingZeroConfigTitle": "零配置自動路由", - "routingZeroConfigDesc": "啟用使用 auto/ 字首的自動提供商選擇。啟用後,發往 auto、auto/coding、auto/fast 等的請求將在所有已連線提供商之間動態路由。", + "routingZeroConfigDesc": "啟用使用 auto/ 字首的自動提供者選擇。啟用後,發往 auto、auto/coding、auto/fast 等的請求將在所有已連線提供者之間動態路由。", "routingDefaultAutoVariant": "預設自動變體", "visionBridge": "願景橋", "visionBridgeDesc": "在將影像請求路由到純文本模型之前,自動執行一次視覺到文本的回退。", @@ -5871,7 +6834,7 @@ "visionBridgeTimeoutMs": "超時(毫秒)", "visionBridgeMaxImagesPerRequest": "每個請求的最大影像數", "resilienceMaxBackoffSteps": "最大退避步數", - "resilienceProviderBreakerTitle": "按提供商的斷路器", + "resilienceProviderBreakerTitle": "按提供者的斷路器", "resilienceFailureThreshold": "失敗閾值", "resilienceDegradationThreshold": "Degradation threshold", "resilienceResetTimeout": "重置超時", @@ -5885,22 +6848,22 @@ "resilienceNo": "否", "resilienceDefault": "預設", "storageDatabaseBackupRetention": "資料庫備份保留", - "storageDatabaseBackups": "__MISSING__:Database backups", + "storageDatabaseBackups": "資料庫備份", "storagePurgeData": "清除資料", - "storagePurgeDataDesc": "__MISSING__:Immediately delete all records without applying retention checks. Use with caution.", - "storageRetentionCleanup": "__MISSING__:Retention Settings", - "storageRetentionCleanupDesc": "__MISSING__:Configure operational record retention and database backup cleanup.", - "retentionCallDays": "__MISSING__:Call {count}d", - "retentionAppDays": "__MISSING__:App {count}d", - "retentionRows": "__MISSING__:{count} rows", + "storagePurgeDataDesc": "立即刪除所有記錄,不套用保留檢查。請謹慎使用。", + "storageRetentionCleanup": "保留設定", + "storageRetentionCleanupDesc": "設定操作記錄保留和資料庫備份清理。", + "retentionCallDays": "呼叫 {count} 天", + "retentionAppDays": "應用程式 {count} 天", + "retentionRows": "{count} 列", "retentionQuotaSnapshots": "配額快照(天)", - "retentionCompressionAnalytics": "__MISSING__:Compression Analytics (days)", + "retentionCompressionAnalytics": "壓縮分析(天)", "retentionMcpAudit": "MCP 稽核(天)", "retentionA2aEvents": "A2A 活動(天)", "retentionCallLogs": "通話記錄(天)", "retentionUsageHistory": "使用歷史(天)", "retentionMemoryEntries": "記憶體條目(天)", - "saveRetentionSettings": "__MISSING__:Save retention settings", + "saveRetentionSettings": "儲存保留設定", "storageAutoVacuumMode": "自動真空模式", "storageScheduledVacuum": "預定真空", "storageVacuumHour": "真空時間(0-23)", @@ -5913,28 +6876,28 @@ "storageIntegrityCheck": "完整性檢查", "storageIntegrityOk": "✓ 確定", "storageIntegrityError": "✗ 錯誤", - "storageUsageTokenBuffer": "使用令牌緩衝區", - "storageUsageTokenBufferDesc": "__MISSING__:Extra tokens added to reported usage to account for system prompt overhead.", - "storageUsageTokenBufferHint": "__MISSING__:Set to 0 to report raw provider token counts. Default: 2000.", - "storageUsageTokenBufferCurrent": "__MISSING__:Current: {value}", - "redisLauncherTitle": "__MISSING__:Local Redis", - "redisLauncherDesc": "__MISSING__:One-click launch a Redis 7 container (Podman or Docker) for response cache, quota tracking, and rate limiting.", - "redisLauncherRefresh": "__MISSING__:Refresh", - "redisLauncherStop": "__MISSING__:Stop", - "redisLauncherLaunching": "__MISSING__:Launching...", - "redisLauncherLaunch": "__MISSING__:Launch Redis", - "redisLauncherContainer": "__MISSING__:Container", - "redisLauncherRunning": "__MISSING__:Running", - "redisLauncherReachable": "__MISSING__:Reachable", - "redisLauncherError": "__MISSING__:Error: {message}", - "redisLauncherHint": "__MISSING__:Equivalent to running `omniroute redis up`. The container is named `omniroute-redis` and listens on 127.0.0.1:6379.", + "storageUsageTokenBuffer": "使用權杖緩衝區", + "storageUsageTokenBufferDesc": "為計入系統提示開銷而加到回報使用量的額外 token。", + "storageUsageTokenBufferHint": "設為 0 以回報原始提供者 token 計數。預設值:2000。", + "storageUsageTokenBufferCurrent": "目前:{value}", + "redisLauncherTitle": "本地 Redis", + "redisLauncherDesc": "一鍵啟動 Redis 7 容器(Podman 或 Docker),用於回應快取、配額追蹤和速率限制。", + "redisLauncherRefresh": "重新整理", + "redisLauncherStop": "停止", + "redisLauncherLaunching": "正在啟動⋯", + "redisLauncherLaunch": "啟動 Redis", + "redisLauncherContainer": "容器", + "redisLauncherRunning": "執行中", + "redisLauncherReachable": "可連線", + "redisLauncherError": "錯誤:{message}", + "redisLauncherHint": "等同於執行 `omniroute redis up`。容器名稱為 `omniroute-redis`,監聽於 127.0.0.1:6379。", "compressionSettingsAutoTriggerMode": "自動觸發模式", "compressionSettingsMcpDescriptionCompression": "MCP描述壓縮", - "mcpAccessibilityTitle": "__MISSING__:MCP accessibility output", + "mcpAccessibilityTitle": "MCP 無障礙輸出", "compressionSettingsCavemanIntensity": "穴居人強度", "compressionSettingsCavemanOutputMode": "穴居人輸出模式", - "compressionSettingsOutputStyles": "__MISSING__:Output styles", - "compressionStylesTileTitle": "__MISSING__:Output styles", + "compressionSettingsOutputStyles": "輸出樣式", + "compressionStylesTileTitle": "輸出樣式", "compressionSettingsOutputIntensity": "輸出強度", "compressionSettingsAutoClarityBypass": "自動清晰度旁路", "resilienceWaitForCooldown": "等待冷卻", @@ -5950,9 +6913,9 @@ "cliproxyapiUrl": "CLIProxyAPI URL", "cliproxyapiStatus": "CLIProxyAPI 狀態", "cliproxyapiNotDetected": "未檢測到", - "cliproxyapiImportAuthTitle": "__MISSING__:Import accounts from CLIProxyAPI", - "cliproxyapiImportAuthDesc": "__MISSING__:Import the OAuth accounts CLIProxyAPI already saved in ~/.cli-proxy-api/ as OmniRoute connections, so you don't have to log in to each account again. Supported account types (Gemini, Codex, Claude, Antigravity, Qwen, Kimi) are imported; others are skipped.", - "cliproxyapiImportAuthButton": "__MISSING__:Import accounts", + "cliproxyapiImportAuthTitle": "從 CLIProxyAPI 匯入帳戶", + "cliproxyapiImportAuthDesc": "將 CLIProxyAPI 已儲存在 ~/.cli-proxy-api/ 中的 OAuth 帳戶匯入為 OmniRoute 連線,這樣您就不必再次登入每個帳戶。支援的帳戶類型(Gemini、Codex、Claude、Antigravity、Qwen、Kimi)會被匯入;其他則會跳過。", + "cliproxyapiImportAuthButton": "匯入帳戶", "payloadRulesTitle": "負載規則", "payloadRulesDesc": "按模型與協議配置請求 payload 的變更。修改會持久化到設定中,並在儲存後立即熱載入到執行時。", "payloadRuleDefaultTitle": "default", @@ -6013,8 +6976,8 @@ "authz": { "cors": { "wildcard": { - "title": "__MISSING__:CORS is open to every origin (CORS_ALLOW_ALL=true)", - "desc": "__MISSING__:Any website can call this server's API from a visitor's browser. Use only on trusted networks — set explicit origins in ALLOWED_ORIGINS and disable CORS_ALLOW_ALL in production." + "title": "CORS 對所有來源開放(CORS_ALLOW_ALL=true)", + "desc": "任何網站都可以從訪客的瀏覽器呼叫此伺服器的 API。請僅在受信任的網路上使用 — 在 ALLOWED_ORIGINS 中設定明確的來源,並在正式環境中停用 CORS_ALLOW_ALL。" } }, "title": "授權清單", @@ -6087,14 +7050,14 @@ "resilienceConnectionCooldownTrigger": "當連線返回暫時性上游故障時", "resilienceConnectionCooldownEffect": "暫時跳過該連線並增加重複失敗的退避時間", "resilienceConnectionCooldownDesc": "基礎冷卻時間涵蓋短暫的連線故障。當啟用上游重試提示時,提供程式的顯式視窗將覆蓋本地冷卻時間。", - "resilienceUseUpstreamRetryHintsDesc": "使用來自上游提供商的重試/重置值(如果可用)。", + "resilienceUseUpstreamRetryHintsDesc": "使用來自上游提供者的重試/重置值(如果可用)。", "resilienceUseUpstream429BreakerHints": "使用上游 429 提示進行斷路器冷卻", "resilienceUseUpstream429BreakerHintsShort": "使用上游 429 提示", - "resilienceUseUpstream429BreakerHintsDesc": "將 429 響應中的重試/配額耗盡訊號應用於斷路器冷卻持續時間。預設使用每個提供商的策略:直接雲提供商預設開啟;反向代理、自託管和 CLI 支援的提供商預設關閉。獨立於“使用上游重試提示”。", - "resilienceProviderBreakerScope": "整個提供商", + "resilienceUseUpstream429BreakerHintsDesc": "將 429 響應中的重試/配額耗盡訊號應用於斷路器冷卻持續時間。預設使用每個提供者的策略:直接雲提供者預設開啟;反向代理、自託管和 CLI 支援的提供者預設關閉。獨立於“使用上游重試提示”。", + "resilienceProviderBreakerScope": "整個提供者", "resilienceProviderBreakerTrigger": "連接回退耗盡後最終傳輸/伺服器失敗", - "resilienceProviderBreakerEffect": "暫時阻止該提供商,直到重置時間到期", - "resilienceProviderBreakerDesc": "即時斷路器狀態僅顯示在執行狀況頁面上。連線範圍的 429 速率限制保留在連線冷卻中,並且不會觸發提供商斷路器。", + "resilienceProviderBreakerEffect": "暫時阻止該提供者,直到重置時間到期", + "resilienceProviderBreakerDesc": "即時斷路器狀態僅顯示在執行狀況頁面上。連線範圍的 429 速率限制保留在連線冷卻中,並且不會觸發提供者斷路器。", "resilienceResetTime": "復位時間", "resilienceWaitForCooldownTitle": "等待冷卻", "resilienceWaitForCooldownScope": "當前客戶請求", @@ -6105,23 +7068,23 @@ "resilienceEnableServerWaitDesc": "啟用後,OmniRoute 會等待第一次冷卻時間到期並自動重試。", "resilienceMaxAttempts": "最大嘗試次數", "resilienceMaxWaitPerAttempt": "每次嘗試的最長等待時間", - "resilienceComboCooldownWaitTitle": "__MISSING__:Quota-share combo cooldown wait", - "resilienceComboCooldownWaitDesc": "__MISSING__:For quota-share combos only: wait out a short transient cooldown and re-dispatch instead of returning a 429 immediately. Never waits on quota_exhausted.", - "resilienceComboCooldownWaitToggleDesc": "__MISSING__:Quota-share combos only; never waits on quota_exhausted.", - "resilienceComboCooldownMaxWaitMs": "__MISSING__:Maximum wait per attempt", - "resilienceComboCooldownBudgetMs": "__MISSING__:Total wait budget", - "resilienceQuotaShareConcurrencyTitle": "__MISSING__:Quota-share per-connection concurrency", - "resilienceQuotaShareConcurrencyDesc": "__MISSING__:For quota-share combos only: when a connection sets a Max Concurrent cap, serialize concurrent requests to that subscription account so it is never flooded past its ceiling. Excess requests wait in the queue instead of getting a 429. The cap comes from each connection's Max Concurrent field; this switch only enables or disables honoring it.", - "resilienceQuotaShareConcurrencyToggleDesc": "__MISSING__:Quota-share combos only; honors each connection's Max Concurrent cap.", - "resilienceProviderCooldownTitle": "__MISSING__:Provider Cooldown", - "resilienceProviderCooldownScope": "__MISSING__:All combo requests", - "resilienceProviderCooldownTrigger": "__MISSING__:When a provider/connection fails", - "resilienceProviderCooldownEffect": "__MISSING__:Skips the failed provider for a cooldown period before retrying", - "resilienceProviderCooldownDesc": "__MISSING__:Prevents subsequent requests from re-walking the same failing providers. Cooldown scales exponentially with consecutive failures.", - "resilienceProviderCooldownEnabled": "__MISSING__:Enable global provider cooldown", - "resilienceProviderCooldownEnabledDesc": "__MISSING__:When enabled, failed providers are tracked globally and skipped for a cooldown period.", - "resilienceProviderCooldownMin": "__MISSING__:Minimum cooldown", - "resilienceProviderCooldownMax": "__MISSING__:Maximum cooldown", + "resilienceComboCooldownWaitTitle": "配額共享組合冷卻等待", + "resilienceComboCooldownWaitDesc": "僅適用於配額共享組合:等待短暫的暫時冷卻後重新派發,而非立即回傳 429。絕不等待 quota_exhausted。", + "resilienceComboCooldownWaitToggleDesc": "僅限配額共享組合;絕不等待 quota_exhausted。", + "resilienceComboCooldownMaxWaitMs": "每次嘗試的最大等待時間", + "resilienceComboCooldownBudgetMs": "總等待預算", + "resilienceQuotaShareConcurrencyTitle": "配額共享的每連線並行數", + "resilienceQuotaShareConcurrencyDesc": "僅適用於配額共享組合:當連線設定了最大並行上限時,將對該訂閱帳戶的並行請求序列化,避免超過上限。超額請求會在佇列中等待,而不會收到 429 錯誤。上限來自每個連線的「最大並行」欄位;此開關僅啟用或停用是否遵循該上限。", + "resilienceQuotaShareConcurrencyToggleDesc": "僅限配額共享組合;遵循各連線的最大並行上限。", + "resilienceProviderCooldownTitle": "提供者冷卻", + "resilienceProviderCooldownScope": "所有組合請求", + "resilienceProviderCooldownTrigger": "當提供者/連線失敗時", + "resilienceProviderCooldownEffect": "在重試前跳過失敗的提供者一段冷卻時間", + "resilienceProviderCooldownDesc": "防止後續請求重複嘗試同一組失敗的供應商。冷卻時間會隨著連續失敗次數呈指數級增長。", + "resilienceProviderCooldownEnabled": "啟用全域提供者冷卻", + "resilienceProviderCooldownEnabledDesc": "啟用後,失敗的提供者會在全域範圍內被追蹤並在冷卻期間內被跳過。", + "resilienceProviderCooldownMin": "最小冷卻", + "resilienceProviderCooldownMax": "最大冷卻", "forcedFingerprintTitle": "{provider} 始終啟用 — OAuth 帳戶安全所必需;無法關閉。", "forcedFingerprintBadge": "必需", "codexSessionAffinityTitle": "法典會話關聯性", @@ -6137,62 +7100,316 @@ "vercelRelaySuccess": "Vercel Relay 部署成功", "vercelRelayButton": "部署 Vercel Relay", "vercelRelayModalTitle": "部署 Vercel Relay", - "vercelRelayWarning": "警告:需要 Vercel 部署令牌。此令牌僅用於建立無伺服器中繼,OmniRoute 不會儲存該令牌。", - "vercelRelayTokenLabel": "Vercel 訪問令牌", + "vercelRelayWarning": "警告:需要 Vercel 部署權杖。此權杖僅用於建立無伺服器中繼,OmniRoute 不會儲存該權杖。", + "vercelRelayTokenLabel": "Vercel 訪問權杖", "vercelRelayProjectNameLabel": "Vercel 專案名稱", "vercelRelayFreeTierNote": "中繼是在 Vercel 免費層上部署的輕量級代理端點,用於繞過本地網路/區域限制。", "vercelRelayDeploying": "正在部署...", "vercelRelayDeploy": "部署", - "deployRelayButton": "__MISSING__:Deploy Relay", - "denoRelayButton": "__MISSING__:Deploy Deno Relay", - "denoRelayModalTitle": "__MISSING__:Deploy Deno Relay", - "denoRelayWarning": "__MISSING__:Warning: A Deno Deploy organization token is required. This token will only be used to create the relay app and will never be stored by OmniRoute.", - "denoRelayTokenLabel": "__MISSING__:Deno Deploy Organization Token", - "denoRelayOrgDomainLabel": "__MISSING__:Organization Domain", - "denoRelayProjectNameLabel": "__MISSING__:Deno App Name", - "denoRelayFreeTierNote": "__MISSING__:Deno Deploy v2 runs on a global edge network. Free tier: 1M requests & 100GiB outbound traffic per month, no per-request CPU limits, up to 20 active apps & 50 custom domains.", - "denoRelayDeploying": "__MISSING__:Deploying...", - "denoRelayDeploy": "__MISSING__:Deploy", - "cloudflareRelaySuccess": "__MISSING__:Cloudflare Relay deployed successfully", - "cloudflareRelayButton": "__MISSING__:Deploy Cloudflare Relay", - "cloudflareRelayModalTitle": "__MISSING__:Deploy Cloudflare Worker Relay", - "cloudflareRelayWarning": "__MISSING__:Deploys a Cloudflare Worker that proxies LLM requests through Cloudflare's edge network — masking the host IP behind dynamic Cloudflare addresses. The Worker enforces a one-time auth secret so the public workers.dev URL cannot be reused as an open relay.", - "cloudflareRelayTokenHowto": "__MISSING__:Create the API token under My Profile -> API Tokens -> Create Token -> Custom Token -> Account / Workers Scripts / Edit.", - "cloudflareRelayAccountIdLabel": "__MISSING__:Cloudflare Account ID", - "cloudflareRelayAccountIdHint": "__MISSING__:Found on the right side of the Cloudflare dashboard overview page.", - "cloudflareRelayApiTokenLabel": "__MISSING__:Cloudflare API Token", - "cloudflareRelayApiTokenHint": "__MISSING__:Requires 'Workers Scripts: Edit' permission. The token is used only at deploy time and is never stored.", - "cloudflareRelayProjectNameLabel": "__MISSING__:Worker Name", - "cloudflareRelayFreeTierNote": "__MISSING__:Free tier: 100,000 requests/day per Cloudflare account.", - "cloudflareRelayDeploying": "__MISSING__:Deploying...", - "cloudflareRelayDeploy": "__MISSING__:Deploy", - "cloudflareRelayCredsRequired": "__MISSING__:Account ID and API Token are required", - "cloudflareRelayDeployFailed": "__MISSING__:Deploy failed", - "modelLockout": "__MISSING__:Model Lockout", - "modelLockoutPageDescription": "__MISSING__:Configure which HTTP error codes trigger per-model lockout and control the cooldown behavior.", - "modelLockoutEnabled": "__MISSING__:Enable Model Lockout", - "modelLockoutEnabledDescription": "__MISSING__:When enabled, models that fail with configured error codes are temporarily locked to prevent retry loops.", - "modelLockoutErrorCodes": "__MISSING__:Error Codes", - "modelLockoutErrorCodesDescription": "__MISSING__:HTTP status codes that trigger model lockout. Type a code and click Add, or select from suggestions.", - "modelLockoutBaseCooldown": "__MISSING__:Base Cooldown (ms)", - "modelLockoutBaseCooldownDescription": "__MISSING__:Initial cooldown duration in milliseconds before a model can be retried.", - "modelLockoutMaxCooldown": "__MISSING__:Max Cooldown (ms)", - "modelLockoutMaxCooldownDescription": "__MISSING__:Maximum cooldown duration in milliseconds. Prevents excessively long lockouts.", - "modelLockoutExponentialBackoff": "__MISSING__:Exponential Backoff", - "modelLockoutExponentialBackoffDescription": "__MISSING__:When enabled, each consecutive failure increases the cooldown duration exponentially.", - "modelLockoutMaxBackoffSteps": "__MISSING__:Max Backoff Steps", - "modelLockoutMaxBackoffStepsDescription": "__MISSING__:Maximum number of backoff steps before the cooldown stops growing. The Max Cooldown cap is reached first in most configurations, making this a safety ceiling for when Max Cooldown is raised.", - "disableSessionStickiness": "__MISSING__:Disable session stickiness", - "disableSessionStickinessDesc": "__MISSING__:Round-robin and random combos rotate to a different connection on every request instead of pinning a whole conversation to one connection by the first-message hash. Leave off to preserve prompt-cache hits for multi-turn chats. Per-combo overrides take precedence.", + "deployRelayButton": "部署 Relay", + "denoRelayButton": "部署 Deno 轉發站", + "denoRelayModalTitle": "部署 Deno Relay", + "denoRelayWarning": "警告:需要 Deno Deploy 組織 Token。此 Token 僅用於建立中繼應用程式,OmniRoute 絕不會儲存它。", + "denoRelayTokenLabel": "Deno Deploy 組織 Token", + "denoRelayOrgDomainLabel": "組織網域", + "denoRelayProjectNameLabel": "Deno 應用程式名稱", + "denoRelayFreeTierNote": "Deno Deploy v2 運行於全球邊緣網路。免費方案:每月 100 萬次請求與 100GiB 對外流量,無每次請求 CPU 限制,最多 20 個活躍應用程式與 50 個自訂網域。", + "denoRelayDeploying": "正在部署…", + "denoRelayDeploy": "部署", + "cloudflareRelaySuccess": "Cloudflare 轉發站部署成功", + "cloudflareRelayButton": "部署 Cloudflare 轉發站", + "cloudflareRelayModalTitle": "部署 Cloudflare Worker 轉發站", + "cloudflareRelayWarning": "部署一個 Cloudflare Worker,透過 Cloudflare 的邊緣網路代理 LLM 請求 — 將主機 IP 隱藏在動態 Cloudflare 位址之後。該 Worker 強制使用一次性驗證密鑰,因此公開的 workers.dev URL 無法被重複使用作為開放轉發站。", + "cloudflareRelayTokenHowto": "在「我的個人檔案」->「API Token」->「建立 Token」->「自訂 Token」->「Account / Workers Scripts / Edit」下方建立 API Token。", + "cloudflareRelayAccountIdLabel": "Cloudflare 帳戶 ID", + "cloudflareRelayAccountIdHint": "位於 Cloudflare 儀表板概覽頁面的右側。", + "cloudflareRelayApiTokenLabel": "Cloudflare API 令牌", + "cloudflareRelayApiTokenHint": "需要「Workers Scripts: Edit」權限。該令牌僅在部署時使用,且永不儲存。", + "cloudflareRelayProjectNameLabel": "Worker 名稱", + "cloudflareRelayFreeTierNote": "免費方案:每個 Cloudflare 帳戶每天 100,000 次請求。", + "cloudflareRelayDeploying": "正在部署…", + "cloudflareRelayDeploy": "部署", + "cloudflareRelayCredsRequired": "需要帳戶 ID 和 API 令牌", + "cloudflareRelayDeployFailed": "部署失敗", + "modelLockout": "模型鎖定", + "modelLockoutPageDescription": "設定哪些 HTTP 錯誤碼會觸發各模型鎖定,並控制冷卻行為。", + "modelLockoutEnabled": "啟用模型鎖定", + "modelLockoutEnabledDescription": "啟用後,因設定的錯誤碼而失敗的模型會被暫時鎖定,以防止重試迴圈。", + "modelLockoutErrorCodes": "錯誤碼", + "modelLockoutErrorCodesDescription": "觸發模型鎖定的 HTTP 狀態碼。輸入代碼並點擊新增,或從建議中選取。", + "modelLockoutBaseCooldown": "基本冷卻(毫秒)", + "modelLockoutBaseCooldownDescription": "模型可重試前的初始冷卻時間(毫秒)。", + "modelLockoutMaxCooldown": "最大冷卻(毫秒)", + "modelLockoutMaxCooldownDescription": "最大冷卻時間(毫秒)。防止過長的鎖定。", + "modelLockoutExponentialBackoff": "指數回退", + "modelLockoutExponentialBackoffDescription": "啟用後,每次連續失敗都會以指數方式增加冷卻時間。", + "modelLockoutMaxBackoffSteps": "最大退避步數", + "modelLockoutMaxBackoffStepsDescription": "冷卻時間停止增長前的最大回退步數。在大多數配置中,最大冷卻上限會先達到,這使得此設定成為提高最大冷卻時的安全上限。", + "disableSessionStickiness": "停用工作階段黏性", + "disableSessionStickinessDesc": "輪詢和隨機組合在每個請求輪換到不同的連線,而不是根據第一則訊息的雜湊將整個對話固定到一個連線。關閉以保留多輪聊天的提示快取命中。各組合的覆寫設定優先。", "codexAutoPingTitle": "Codex Quota Auto-Ping", "codexAutoPingDesc": "Opt-in per connection: sends a tiny request right after a Codex session window resets so it isn't cold when you need it.", "codexAutoPingWarning": "Consumes a small amount of real Codex quota on every ping. Off by default — enable only for connections you actively rely on.", "codexAutoPingSaveError": "Failed to update Codex Auto-Ping setting", "codexAutoPingToggleAria": "Toggle Codex quota auto-ping for {connection}", - "credentialRedaction": "__MISSING__:Credential Redaction", - "credentialRedactionDesc": "__MISSING__:Redact API keys, tokens, and secrets from context sent to providers and from responses.", - "enableCredentialRedaction": "__MISSING__:Enable credential redaction", - "enableCredentialRedactionDesc": "__MISSING__:Scrubs API keys, tokens, private keys, and JWTs from messages, tool calls, and responses." + "credentialRedaction": "憑證編輯", + "credentialRedactionDesc": "從傳送給提供者的上下文和回應中編輯 API 金鑰、令牌和機密。", + "enableCredentialRedaction": "啟用憑證遮蔽", + "enableCredentialRedactionDesc": "從訊息、工具呼叫和回應中清除 API 金鑰、令牌、私鑰和 JWT。", + "accountRoundRobin": "循環輪詢", + "accountRoundRobinDesc": "輪換帳號以分散負載", + "addErrorCode": "新增錯誤代碼...", + "addKeyValue": "新增鍵值", + "allProvidersAdded": "所有供應商已新增", + "backupCleanupFailed": "清理資料庫備份失敗", + "backupCleanupSuccess": "已刪除 {backups} 個備份集與 {files} 個檔案。", + "backupRetentionSaveFailed": "儲存備份保留設定失敗", + "backupRetentionSaved": "備份保留設定已儲存。", + "comboConfigMode": "Combo 設定模式", + "comboConfigModeDesc": "選擇 Combo 建立與編輯對話框的組織方式。", + "comboConfigModeExpert": "專家", + "comboConfigModeExpertDesc": "在單一頁面顯示所有 Combo 選項,並啟用直接輸入模型。", + "comboConfigModeGuided": "引導", + "comboConfigModeGuidedDesc": "使用當前逐步 Combo 建置器。", + "comboRoundRobin": "Combo 循環輪詢", + "comboRoundRobinDesc": "輪換 Combo 目標,而非總是從第一個開始", + "comboStickyLimit": "Combo 黏著限制", + "comboStickyLimitDesc": "切換前每個 Combo 目標的呼叫次數", + "compressionAdaptiveOff": "自適應上下文預算:關閉(傳統自動觸發)", + "compressionAdaptiveTarget": "自適應({mode},策略:{policy})— 目標約 {target, number} tokens(針對 {contextLimit, number} token 的視窗)", + "compressionDerivedMode": "模式:{mode}", + "compressionDerivedOff": "關閉", + "compressionDerivedRuns": "執行:{pipeline}", + "compressionEffectivePipeline": "有效管線:", + "compressionEngine": { + "aggressive": { + "description": "摘要整理並淘汰舊對話輪次。", + "label": "積極" + }, + "caveman": { + "description": "基於規則的文字壓縮。", + "label": "原始" + }, + "ccr": { + "description": "內容定址檢索標記。", + "label": "CCR(檢索)" + }, + "headroom": { + "description": "表格 JSON 壓縮。", + "label": "精簡" + }, + "lite": { + "description": "空白與格式清理。", + "label": "輕量" + }, + "llmlingua": { + "description": "語義修剪(ONNX)。", + "label": "LLMLingua(SLM)" + }, + "omniglyph": { + "description": "將上下文作為圖片(Claude Fable 5,直接路由)。", + "label": "OmniGlyph" + }, + "relevance": { + "description": "針對最新使用者查詢進行抽取式句子評分。", + "label": "相關性" + }, + "rtk": { + "description": "命令輸出過濾。", + "label": "RTK" + }, + "session-dedup": { + "description": "跨輪次區塊去重。", + "label": "Session 去重" + }, + "ultra": { + "description": "啟發式 Token 修剪,可選用 SLM。", + "label": "極致" + } + }, + "compressionGuidanceCacheImpact": "快取影響", + "compressionGuidanceFullGuideLink": "完整壓縮指南", + "compressionGuidanceHide": "隱藏詳細資訊", + "compressionGuidanceSafeDefault": "安全預設", + "compressionGuidanceShow": "詳細資訊", + "compressionLevel": { + "aggressive": "積極", + "full": "完整", + "lite": "輕量", + "minimal": "最小", + "standard": "標準", + "ultra": "極致" + }, + "compressionLiveZoneDesc": "保持壓縮對話前綴穩定,僅處理新附加的項目。", + "compressionLiveZoneTitle": "快取對齊即時區域", + "compressionOutputStyle": { + "less-code": { + "description": "YAGNI 階梯:最小可行變更,不建立未要求的抽象層。", + "label": "更少程式碼" + }, + "terse-cjk": { + "description": "文言文極簡風格(僅適用於中文)。", + "label": "精簡 CJK(文言)" + }, + "terse-prose": { + "description": "刪除填充詞、冠詞與模糊用語,同時保持技術內容精確。", + "label": "精簡文體" + } + }, + "compressionOutputStylesDescription": "注入回應塑形指令,無需改寫供應商輸出。可自由組合。", + "compressionStylesTileEmpty": "尚無已套用風格的執行。", + "compressionStylesTileSummary": "已節省 {tokens, number} tokens · {runs} 次風格執行", + "configured": "已設定", + "exportJson": "匯出 JSON", + "failedToReadFile": "讀取檔案失敗", + "faviconFileTooLarge": "Favicon 檔案必須小於 50KB", + "homePinnedSectionsDesc": "選擇要固定在首頁頂部的區塊。", + "importJson": "匯入 JSON", + "invalidFaviconFileType": "無效的檔案類型。請上傳 PNG、ICO、SVG、GIF 或 WebP。", + "invalidJsonFileType": "無效的檔案類型。僅允許 .json 檔案。", + "invalidLogoFileType": "無效的檔案類型。請上傳 PNG、JPG、SVG、GIF 或 WebP。", + "jsonExportFailed": "JSON 匯出失敗", + "jsonImportError": "JSON 匯入時發生錯誤", + "jsonImportFailed": "匯入 JSON 失敗", + "legacyJsonImportSuccess": "舊版 JSON 匯入成功!", + "logoFileTooLarge": "Logo 檔案必須小於 500KB", + "manualVacuum": "手動 VACUUM", + "mcpAccessibilityDescription": "限定 MCP 工具輸出範圍(獨立儲存區)。", + "modelCooldownClearFailed": "清除冷卻失敗", + "modelCooldownReactivated": "模型已重新啟用:{model}", + "modelCooldownsAllReactivated": "所有處於冷卻狀態的模型已重新啟用。", + "modelCooldownsClearFailed": "清除冷卻狀態失敗", + "modelCooldownsDescription": "模型在故障後暫時隔離。冷卻期滿後會自動恢復。", + "modelCooldownsLoadFailed": "載入冷卻狀態失敗", + "modelCooldownsReactivate": "重新啟用", + "modelCooldownsReactivateAll": "全部重新啟用", + "modelCooldownsReasonRemaining": "原因:{reason} • 剩餘:{remaining}", + "modelLockoutBaseRangeError": "基礎冷卻時間必須介於 5,000 毫秒至 600,000 毫秒之間", + "modelLockoutLoadFailed": "載入模型鎖定設定失敗", + "modelLockoutLoading": "正在載入模型鎖定設定...", + "modelLockoutMaxCooldownHint": "≥ 基礎冷卻時間 — 3,600,000 毫秒", + "modelLockoutMaxRangeError": "最大冷卻時間必須介於 5,000 毫秒至 3,600,000 毫秒之間", + "modelLockoutOrderError": "最大冷卻時間必須大於或等於基礎冷卻時間", + "modelLockoutSaveFailed": "儲存模型鎖定設定失敗", + "modelLockoutStepsRangeError": "最大退避步驟必須介於 0 至 20 之間", + "modelOverrideLoadFailed": "載入模型覆寫設定失敗", + "modelOverrideRemoveFailed": "移除模型覆寫設定失敗", + "modelOverrideRemoved": "模型覆寫設定已移除", + "modelOverrideSaveFailed": "儲存模型覆寫設定失敗", + "modelOverrideSaved": "模型覆寫設定已儲存", + "modelOverrideValuePlaceholder": "數值", + "modelOverridesDesc": "覆寫路由與請求塑形所使用的供應商/模型能力。目標使用與 Combo 相同的供應商/模型格式。", + "modelOverridesTitle": "模型覆寫", + "moveDown": "向下移動", + "moveUp": "向上移動", + "noModelOverrides": "此模型未設定任何覆寫。", + "noProvidersFound": "找不到供應商", + "none": "無", + "paidModelPatternWarning": "此模式僅比對付費模型 — 請啟用付費模型或調整模式。", + "pricingAll": "全部", + "pricingAuth": "驗證", + "pricingAuthUnknown": "未知", + "pricingClearFilters": "清除篩選", + "pricingCoverage": "覆蓋率", + "pricingCoverageGaps": "覆蓋缺口", + "pricingFilteredFrom": "(從 {count} 中篩選)", + "pricingHighestCoverage": "最高覆蓋率", + "pricingLowestCoverage": "最低覆蓋率", + "pricingMostModels": "最多模型", + "pricingNameAscending": "名稱(A–Z)", + "pricingShowMoreProviders": "顯示另外 {count} 個(剩餘 {remaining} 個)", + "pricingShowingProviders": "顯示第 {visible} 個,共 {total} 個", + "pricingSort": "排序", + "providerAccountRoutingDesc": "覆寫此供應商的全域帳號策略(9router 對等)。", + "providerAccountRoutingTitle": "多帳號路由", + "providerQuotaAutoRefresh": "供應商配額自動重新整理", + "providerQuotaAutoRefreshDesc": "在供應商限制檢視開啟時自動重新整理。", + "providerQuotaAutoRefreshInterval": "重新整理間隔", + "providerQuotaAutoRefreshIntervalDesc": "配額檢視應多久重新整理一次(以秒為單位)。", + "providerQuotaAutoRefreshToggle": "自動重新整理", + "providerQuotaAutoRefreshToggleDesc": "在頁面可見時每隔幾分鐘重新整理配額檢視。", + "providerRoutingInheritGlobal": "繼承全域預設", + "providerRoutingStrategy": "帳號策略", + "proxyFreePoolBulkResult": "已新增 {succeeded} 個,{failed} 個失敗", + "proxyFreePoolListTotal": "已列出", + "proxyFreePoolLoadMore": "載入更多", + "proxyFreePoolPageSummary": "第 {page} 頁,共 {totalPages} 頁(共 {total} 個代理)", + "proxyFreePoolSearchLabel": "依主機搜尋代理", + "proxyFreePoolSearchPlaceholder": "搜尋主機…", + "proxyFreePoolSortLabel": "排序代理", + "proxyFreePoolSortLatency": "延遲", + "proxyFreePoolSortQuality": "品質", + "proxyFreePoolSortRecent": "最近驗證", + "proxyFreePoolTesting": "正在測試代理...", + "proxyFreePoolTotalSummary": "共 {total} 個代理", + "proxySubscription": { + "error": { + "LOCAL_CORE_ENDPOINT_INVALID": "本機 proxy-core 端點無效;已忽略 SS/VMess/Trojan/VLESS 節點。", + "NEEDS_CORE_NOT_CONFIGURED": "此訂閱包含需要本機 proxy core 的節點(SS/VMess/Trojan/VLESS);在設定本機 core SOCKS5 端點之前,這些節點不會被路由。", + "NO_USABLE_NODES": "訂閱未產生可用節點(http/https/socks5 或具有本機 core 端點的節點)。" + } + }, + "proxySubscriptionsTab": "訂閱", + "proxyToggleSources": "切換代理來源", + "purgeCallLogs": "清除通話記錄", + "purgeCallLogsFailed": "清除通話記錄失敗", + "purgeCallLogsSuccess": "已清除 {count} 筆通話記錄", + "purgeDetailedLogs": "清除詳細記錄", + "purgeDetailedLogsFailed": "清除詳細記錄失敗", + "purgeDetailedLogsSuccess": "已清除 {count} 筆詳細記錄", + "purgeQuotaSnapshots": "清除配額快照", + "purgeQuotaSnapshotsFailed": "清除配額快照失敗", + "purgeQuotaSnapshotsSuccess": "已清除 {count} 筆配額快照", + "removeErrorCode": "移除 {code}", + "retentionXpAuditLog": "XP 稽核記錄(天數)", + "routingStrategyAccountSummary": "跨帳號分配請求,每個帳號 {limit} 次呼叫。", + "routingStrategyComboFallbackSummary": " Combo 使用各自設定的策略(預設優先/備用)。", + "routingStrategyComboSummary": " Combo 在每個目標 {limit} 次呼叫後輪換。", + "routingStrategyFillFirstSummary": "依優先順序使用帳號(優先填滿)。", + "routingStrategySubtitle": "比照 9router:帳號循環輪詢、黏著限制與 Combo 輪換", + "routingStrategyTitle": "路由策略", + "searchModelOverrideTargets": "搜尋供應商/模型...", + "seconds": "秒", + "selectedModel": "已選模型", + "sessionAffinityDesc": "將同一對話保持在同一個帳號上達此秒數(適用於任何供應商)。設為 0 以停用。", + "sessionAffinityTitle": "Session 親和性", + "sessionAffinityTtl": "親和性 TTL(秒)", + "startOnLogin": "登入時啟動", + "startOnLoginDesc": "在系統啟動時自動啟動 OmniRoute,並在背景系統匣中靜默執行。", + "storageBackupAgeRetention": "保留 {count} 天", + "storageBackupAgeRetentionOff": "天數保留關閉", + "storageBackupCleanOld": "清理舊備份", + "storageBackupCount": "{count} 個備份", + "storageBackupDeleteOlderThan": "刪除超過天數", + "storageBackupKeepLatest": "保留最新備份", + "storageBackupMaximum": "最多 {count} 個", + "storageBackupRetentionDescription": "自動 SQLite 備份儲存於", + "storageBackupRetentionHelp": "設定要保留的快照數量,並可選擇刪除超過指定天數的備份。", + "storageBackupSaveRetention": "儲存保留設定", + "storageCacheSizeKb": "快取大小(KB)", + "storageCompressionAggregation": "壓縮與彙總設定", + "storageDaily": "每日", + "storageDatabaseStatistics": "資料庫統計", + "storageEnableAggregation": "啟用資料彙總", + "storageGranularity": "粒度", + "storageHourly": "每小時", + "storageIntegrityNotChecked": "尚未檢查", + "storageJournalModeFull": "完整", + "storageJournalModeIncremental": "增量", + "storageJournalModeNone": "無", + "storageOptimizationSettings": "最佳化設定", + "storageOptimizeOnStartup": "啟動時最佳化", + "storageRawDataRetention": "原始資料保留(天數)", + "storageSaveAggregation": "儲存彙總設定", + "storageSaveOptimization": "儲存最佳化設定", + "storageVacuumDaily": "每日", + "storageVacuumMonthly": "每月", + "storageVacuumNever": "從不", + "storageVacuumWeekly": "每週", + "storageWeekly": "每週", + "suggestions": "建議:", + "vacuumCompleted": "VACUUM 已完成", + "vacuumFailed": "VACUUM 失敗" }, "contextRtk": { "title": "RTK 引擎", @@ -6207,7 +7424,7 @@ "codeBlocks": "程式碼塊", "filterCatalog": "過濾器目錄", "filterCatalogDesc": "可用的輸出過濾器(按類別)", - "guidedConfig": "配置", + "guidedConfig": "設定", "guidedConfigDesc": "調整 RTK 過濾終端輸出的方式", "maxLines": "最大行數", "maxChars": "最大字元數", @@ -6244,20 +7461,39 @@ "tooltipDedup": "刪除重複行的積極程度。", "tooltipMaxChars": "每個輸出塊的最大字元數。", "tooltipMaxLines": "命令輸出中保留的最大行數。多餘的部分被截斷。", - "learnDiscoverTitle": "__MISSING__:Learn & Discover filters", - "learnDiscoverDesc": "__MISSING__:Mine your captured command output (Raw Output retention must be on) to suggest new RTK filters. Suggestions only — you review and save them.", - "discoverHeading": "__MISSING__:Discover repeated noise", - "discoverButton": "__MISSING__:Scan samples", - "discoverScanning": "__MISSING__:Scanning…", - "discoverEmpty": "__MISSING__:No repeated noise found. Enable Raw Output retention and run a few commands first.", - "discoverSamples": "__MISSING__:{count} sample(s) scanned", - "discoverHits": "__MISSING__:{hits}× across samples", - "learnHeading": "__MISSING__:Learn a filter for a command", - "learnCommandPlaceholder": "__MISSING__:e.g. npm install", - "learnButton": "__MISSING__:Suggest filter", - "learnEmpty": "__MISSING__:No suggestion yet. Type a command you have run and scan.", - "learnSamplesUsed": "__MISSING__:Learned from {count} matching sample(s)", - "suggestionError": "__MISSING__:Could not load suggestions. Check management auth and try again." + "learnDiscoverTitle": "學習與探索篩選器", + "learnDiscoverDesc": "挖掘您捕捉的命令輸出(必須啟用原始輸出保留)以建議新的 RTK 篩選器。僅供建議 — 由您審閱並儲存。", + "discoverHeading": "發現重複噪音", + "discoverButton": "掃描樣本", + "discoverScanning": "正在掃描…", + "discoverEmpty": "未發現重複雜訊。請先啟用原始輸出保留功能並執行一些指令。", + "discoverSamples": "已掃描 {count} 個樣本", + "discoverHits": "跨樣本 {hits} 次命中", + "learnHeading": "為命令學習篩選器", + "learnCommandPlaceholder": "例如:npm install", + "learnButton": "建議篩選器", + "learnEmpty": "尚無建議。請輸入您已執行的指令並掃描。", + "learnSamplesUsed": "從 {count} 個匹配範例中學習", + "suggestionError": "無法載入建議。請檢查管理員驗證後重試。", + "tomlBackupCreated": "已建立先前檔案的備份。", + "tomlChooseFile": "選擇 TOML 檔案", + "tomlConfirmOverwrite": "取代現有的全域檔案並建立備份", + "tomlFileReadError": "無法讀取選取的 TOML 檔案。", + "tomlImportDesc": "驗證 RTK TOML 結構描述 v1 篩選器並全域安裝。安裝會寫入 DATA_DIR/rtk/filters.toml;僅在明確確認後才會取代現有檔案。", + "tomlImportError": "無法處理 RTK TOML 檔案。", + "tomlImportPlaceholder": "在此貼上 RTK filters.toml 內容...", + "tomlImportTitle": "匯入 RTK TOML 篩選器", + "tomlInstall": "全域安裝", + "tomlInstalled": "已安裝於 {path}", + "tomlInstalling": "安裝中…", + "tomlTestCount": "{count} 個測試", + "tomlTestFailed": "失敗", + "tomlTestPassed": "通過", + "tomlValidate": "驗證", + "tomlValidating": "驗證中…", + "tomlValidationFailed": "驗證完成,但內聯測試失敗", + "tomlValidationPassed": "驗證通過", + "tomlValidationSummary": "{filters} 個篩選器,{tests} 個內聯測試" }, "contextCombos": { "title": "壓縮組合", @@ -6279,7 +7515,45 @@ "setAsDefault": "設為預設", "save": "儲存", "cancel": "取消", - "enabled": "已啟用" + "enabled": "已啟用", + "active": "啟用", + "activeProfile": "使用中的設定檔", + "activeProfileDescription": "選擇哪個壓縮設定檔全域執行 — 面板衍生的「預設」或已儲存的命名組合。", + "comboNamePlaceholder": "組合名稱", + "compressionSettings": "壓縮設定", + "contextEditingAria": "上下文編輯", + "contextEditingClaude": "上下文編輯(Claude)", + "contextEditingDescription": "讓提供者清除伺服器端舊的工具使用區塊,無需重寫訊息。", + "contextEditingNote": "目前僅適用於 Claude(Anthropic)。這是一種委託模式:提供者會在伺服器端清除舊的工具使用區塊 — 我們不會重寫訊息。不影響其他提供者。", + "defaultConfiguredPrefix": "預設 — 於下列位置設定:", + "defaultFromPanel": "預設(來自面板)", + "deleteNamedConfirm": "刪除組合「{name}」?", + "descriptionPlaceholder": "說明", + "dragToReorder": "拖曳以重新排序步驟", + "engine": "引擎", + "explanationActiveProfile": "使用中的設定檔:選擇哪個壓縮設定檔全域執行 — 面板衍生的「預設」或您已儲存的命名組合。", + "explanationDefault": "預設(來自面板):衍生自主開關和您在「壓縮設定」中設定的各引擎切換。", + "explanationIntro": "壓縮透過在發送給提供者之前重寫歷史記錄來減少 Token 和成本,同時保留語意。", + "explanationNamedCombos": "命名組合:您在命名組合編輯器中建立的已儲存管道。選取其中一個即可使其成為每個請求的使用中設定檔。", + "explanationPreview": "預覽:顯示使用中設定檔依序執行哪些引擎。", + "hideExplanation": "隱藏說明", + "howItWorks": "運作方式", + "hubDescription": "選擇哪個壓縮設定檔全域執行。", + "hubTitle": "壓縮中心", + "intensity": "強度", + "intensityFull": "完整", + "intensityLite": "精簡", + "intensityUltra": "極致", + "languagePacksList": "語言包:{packs}", + "loading": "載入中…", + "nameRequired": "請在儲存前輸入組合名稱。", + "namedCombos": "命名組合", + "namedCombosDescription": "儲存不同的管道並將其指派給特定的路由組合。", + "pipelineRequired": "請在儲存前新增至少一個管道步驟。", + "providerDelegated": "提供者委託的壓縮", + "runs": "執行次數:", + "saveComboFailed": "組合儲存失敗(HTTP {status})。", + "saveSettingsFailed": "設定儲存失敗。" }, "contextCaveman": { "title": "Caveman 引擎", @@ -6296,7 +7570,7 @@ "languagePacksDesc": "為特定語言啟用壓縮規則。", "labelAutoTrigger": "當上下文超出時自動壓縮", "labelCompressionRate": "抗壓強度", - "labelMaxTokens": "每條訊息的目標令牌", + "labelMaxTokens": "每條訊息的目標權杖", "labelMinLength": "最小訊息長度", "labelMinSavings": "儲存的最低代幣數量", "enabled": "已啟用", @@ -6318,32 +7592,33 @@ "advancedMode": "高階", "tooltipAutoTrigger": "當上下文超過此大小時自動壓縮。", "tooltipCompressionRate": "0.0 = 無,1.0 = 最大值。 0.5 = 平衡。", - "tooltipMaxTokens": "壓縮後的目標令牌計數。更低=更具攻擊性。", + "tooltipMaxTokens": "壓縮後的目標權杖計數。更低=更具攻擊性。", "tooltipMinLength": "比這短的訊息不會被壓縮。更低=更多壓縮。", - "tooltipMinSavings": "僅當至少儲存這麼多令牌時才進行壓縮。", + "tooltipMinSavings": "僅當至少儲存這麼多權杖時才進行壓縮。", "ultraSettings": "超級設定", "ultraSettingsDesc": "SLM 支援的語義壓縮", "preview": { "lite": "回覆要簡潔。保留技術術語、程式碼、錯誤、URL 和識別符號。", "full": "回覆要簡短緊湊。保留所有技術實質。", "ultra": "用常見技術縮寫進行極簡回覆。保留精確符號。" - } + }, + "masterDisabledWarning": "Token Saver 主開關已關閉 — 這些設定在您從「壓縮設定」開啟或在此處變更之前,不會影響請求。" }, "translator": { "title": "翻譯器", "metaTitle": "翻譯器遊樂場 | OmniRoute", - "metaDescription": "除錯、測試和視覺化提供商之間的 API 格式轉換", + "metaDescription": "除錯、測試和視覺化提供者之間的 API 格式轉換", "playgroundTitle": "翻譯器遊樂場", "playground": "遊樂場", "realtime": "即時翻譯活動", "chatTester": "聊天測試儀", "testBench": "測試臺", "liveMonitor": "即時監控", - "modeDescriptionPlayground": "貼上任意 API 請求體,檢視 OmniRoute 如何在不同提供商格式之間進行轉換(OpenAI ↔ Claude ↔ Gemini ↔ Responses API)。", - "modeDescriptionChatTester": "通過 OmniRoute 傳送真實聊天請求,並檢查完整往返流程:輸入、轉換後的請求、提供商響應以及轉換後的輸出。", - "modeDescriptionTestBench": "執行預定義的場景並比較提供商和模型之間的相容性。", + "modeDescriptionPlayground": "貼上任意 API 請求體,檢視 OmniRoute 如何在不同提供者格式之間進行轉換(OpenAI ↔ Claude ↔ Gemini ↔ Responses API)。", + "modeDescriptionChatTester": "通過 OmniRoute 傳送真實聊天請求,並檢查完整往返流程:輸入、轉換後的請求、提供者響應以及轉換後的輸出。", + "modeDescriptionTestBench": "執行預定義的場景並比較提供者和模型之間的相容性。", "modeDescriptionLiveMonitor": "即時檢視請求流經 OmniRoute 時產生的翻譯事件。", - "modeDescriptionFallback": "除錯、測試並可視化 OmniRoute 如何在提供商之間轉換 API 請求。", + "modeDescriptionFallback": "除錯、測試並可視化 OmniRoute 如何在提供者之間轉換 API 請求。", "recentTranslations": "最近的翻譯", "noTranslations": "還沒有翻譯", "source": "來源", @@ -6383,7 +7658,7 @@ "featureRoleNormalization": "角色規範化", "featureRoleNormalizationDesc": "針對非 OpenAI 目標將 developer→system。針對不支援 system 角色的模型將 system→user。", "featureToolCallIds": "工具呼叫 ID 規範化", - "featureToolCallIdsDesc": "在缺失時生成唯一的 tool_call ID。針對 Mistral 等提供商規範化為 9 字元格式。", + "featureToolCallIdsDesc": "在缺失時生成唯一的 tool_call ID。針對 Mistral 等提供者規範化為 9 字元格式。", "featureMissingToolResponse": "工具響應注入", "featureMissingToolResponseDesc": "當客戶端傳送 tool_calls 但沒有對應響應時,注入空的 tool_result 訊息。", "featureThinkingBudget": "思考預算", @@ -6404,8 +7679,8 @@ "templateLoadHint": "模板以 {format} 格式載入請求。更改源格式以以不同的格式載入。", "compatibilityTester": "相容性測試儀", "compatibilityReport": "相容性報告", - "testBenchDescription": "執行預定義的場景(簡單聊天、工具呼叫等)以驗證翻譯和提供商相容性。選擇源格式和目標提供商,然後執行所有測試以檢視相容性百分比。使用它來查詢哪些功能可以跨提供商使用。", - "targetProvider": "目標提供商", + "testBenchDescription": "執行預定義的場景(簡單聊天、工具呼叫等)以驗證翻譯和提供者相容性。選擇源格式和目標提供者,然後執行所有測試以檢視相容性百分比。使用它來查詢哪些功能可以跨提供者使用。", + "targetProvider": "目標提供者", "runAllTests": "執行所有測試", "runTest": "執行測試", "reRun": "重新執行", @@ -6484,16 +7759,16 @@ "pipelineDebugger": "管道偵錯程式", "translationPipeline": "翻譯管道", "pipelineVisualization": "管道視覺化", - "pipelineVisualizationHint": "傳送訊息以檢視您的請求如何經過檢測 → 翻譯 → 提供商呼叫。", + "pipelineVisualizationHint": "傳送訊息以檢視您的請求如何經過檢測 → 翻譯 → 提供者呼叫。", "chatTesterDescription": "以特定客戶端格式傳送訊息並檢查翻譯管道的每個步驟。", - "chatTesterFlow": "客戶端請求 → 格式檢測 → OpenAI 中間格式 → 提供商格式 → 響應", + "chatTesterFlow": "客戶端請求 → 格式檢測 → OpenAI 中間格式 → 提供者格式 → 響應", "clickStepToInspect": "單擊任意步驟即可檢查該階段的資料。", "clientFormat": "客戶端格式", - "provider": "提供商", + "provider": "提供者", "modelPlaceholder": "選擇或輸入模型名稱...", "sendMessageToSeePipeline": "傳送訊息檢視翻譯管道", "chatMessageHintPrefix": "您的訊息將被格式化為", - "chatMessageHintSuffix": "請求,通過管道進行翻譯,然後傳送給選定的提供商。", + "chatMessageHintSuffix": "請求,通過管道進行翻譯,然後傳送給選定的提供者。", "youWithFormat": "您 ({format})", "assistant": "助理", "typeMessage": "輸入訊息...", @@ -6504,11 +7779,11 @@ "formatDetectedDescription": "OmniRoute 會根據請求結構自動識別 API 格式", "openaiIntermediate": "OpenAI 中間格式", "openaiIntermediateDescription": "所有格式都會先歸一化為 OpenAI 格式(通用橋接層)", - "providerFormat": "提供商格式", - "providerFormatDescription": "隨後再把 OpenAI 格式轉換為提供商原生格式", - "providerResponse": "提供商回應", - "providerResponseRawDescription": "來自提供商 API 的原始響應", - "providerResponseSseDescription": "來自提供商 API 的原始 SSE 流", + "providerFormat": "提供者格式", + "providerFormatDescription": "隨後再把 OpenAI 格式轉換為提供者原生格式", + "providerResponse": "提供者回應", + "providerResponseRawDescription": "來自提供者 API 的原始響應", + "providerResponseSseDescription": "來自提供者 API 的原始 SSE 流", "unexpectedError": "發生意外錯誤", "error": "錯誤", "errorMessage": "錯誤:{message}", @@ -6549,7 +7824,7 @@ "scenarioSchemaCoercion": "模式強制(結構化輸出)", "techniques": "技巧:", "friendlyTitle": "翻譯器", - "friendlySubtitle": "使用您現有的應用程式與任何提供商 — 無需重寫程式碼。", + "friendlySubtitle": "使用您現有的應用程式與任何提供者 — 無需重寫程式碼。", "conceptHeadline": "您的應用程式使用一個 API 的“語言”。翻譯器將其轉換為使用另一個提供者。", "conceptDiagramAppLabel": "您的應用程式", "conceptDiagramSourceLabel": "源格式", @@ -6559,7 +7834,7 @@ "conceptDiagramExampleSource": "Claude", "conceptDiagramExampleTarget": "雙子座", "conceptHowItWorksToggle": "它是如何工作的", - "conceptHowItWorksBody": "您的應用以其自己的格式傳送請求。翻譯器檢測該格式,通過 OpenAI 作為中介中心進行轉換(或在可用的情況下直接進行轉換),將其傳送到所選提供商,並將響應轉換回您應用的格式。", + "conceptHowItWorksBody": "您的應用以其自己的格式傳送請求。翻譯器檢測該格式,通過 OpenAI 作為中介中心進行轉換(或在可用的情況下直接進行轉換),將其傳送到所選提供者,並將響應轉換回您應用的格式。", "tabTranslate": "翻譯", "tabMonitor": "監視器", "tabTranslateAriaLabel": "前往翻譯選項卡", @@ -6596,7 +7871,7 @@ "advancedTestBenchTitle": "測試平臺 (8 個場景)", "advancedTestBenchSubtitle": "執行所有場景並報告通過/失敗 + 相容性 %。", "advancedCompressionTitle": "壓縮預覽", - "advancedCompressionSubtitle": "估算不同壓縮模式下的令牌節省。", + "advancedCompressionSubtitle": "估算不同壓縮模式下的權杖節省。", "monitorOriginHint": "由 Translate 或主管道生成的事件會即時顯示在這裡。", "monitorEmptyCta": "轉到翻譯選項卡併發送請求 — 它將出現在這裡。", "monitorOpenTranslateButton": "前往翻譯", @@ -6617,10 +7892,26 @@ "conceptDiagramHubTooltip": "翻譯器用於在沒有直接對映的格式之間轉換的中間樞紐。", "conceptDiagramSourceTooltip": "您的應用程式使用的 API 格式(例如,Anthropic SDK = claude)。", "conceptDiagramTargetTooltip": "請求將實際傳送到的提供者。", - "compressionEmptyHint": "__MISSING__:Fill in the input field on the Translate tab (Simple Controls or Raw JSON) to enable the preview.", - "compressionModeLabel": "__MISSING__:Compression mode", - "compressionPreviewButton": "__MISSING__:Preview Compression", - "compressionPreviewing": "__MISSING__:Previewing…" + "compressionEmptyHint": "在「翻譯」標籤頁(簡易控制或原始 JSON)中填寫輸入欄位以啟用預覽。", + "compressionModeLabel": "壓縮模式", + "compressionPreviewButton": "預覽壓縮", + "compressionPreviewing": "正在預覽…", + "compressionCompressed": "已壓縮", + "compressionDuration": "持續時間", + "compressionOriginal": "原始", + "compressionPreviewFailed": "壓縮預覽失敗", + "compressionSaved": "已儲存", + "copyInput": "複製輸入", + "copyIntermediateJson": "複製中繼 JSON", + "copyOutput": "複製輸出", + "copyOutputJson": "複製輸出 JSON", + "live": "即時", + "loading": "正在載入…", + "pauseAutoRefresh": "暫停自動重新整理", + "pipelineStepsAria": "管線步驟", + "resumeAutoRefresh": "繼續自動重新整理", + "streamTransformFailed": "轉換串流失敗", + "tokens": "tokens" }, "usage": { "title": "用量", @@ -6736,7 +8027,7 @@ "notifyEvalRunFailed": "評估執行失敗", "notifyEvalTitle": "評估:{name}", "modelEvals": "模型評估", - "evalsHeroDescription": "通過執行預定義評測套件來測試和驗證你的 LLM 端點。每個套件都包含多個測試用例,會經由 OmniRoute 傳送真實提示,並將響應與預期標準進行比較,幫助你發現迴歸、比較模型,並確保跨提供商的響應質量。", + "evalsHeroDescription": "通過執行預定義評測套件來測試和驗證你的 LLM 端點。每個套件都包含多個測試用例,會經由 OmniRoute 傳送真實提示,並將響應與預期標準進行比較,幫助你發現迴歸、比較模型,並確保跨提供者的響應質量。", "qualityValidation": "質量驗證", "modelComparison": "模型對比", "regressionDetection": "迴歸檢測", @@ -6757,16 +8048,16 @@ "durationHoursShort": "{value} 時", "reasonSeparator": "-", "notAvailableSymbol": "-", - "providerLimits": "提供商限制", - "noProviders": "沒有連線提供商", - "connectProvidersForQuota": "通過 OAuth 連線提供商,以跟蹤 API 配額限制和使用情況。", + "providerLimits": "提供者限制", + "noProviders": "沒有連線提供者", + "connectProvidersForQuota": "通過 OAuth 連線提供者,以跟蹤 API 配額限制和使用情況。", "accountsCount": "{count, plural, one {# 個帳戶} other {# 個帳戶}}", "filteredFromCount": "(從 {count} 過濾)", "autoRefresh": "自動重新整理", "refreshAll": "全部重新整理", "loadingQuotas": "正在載入...", - "showMoreQuotas": "__MISSING__:Show {count} more", - "showLessQuotas": "__MISSING__:Show less", + "showMoreQuotas": "顯示另外 {count} 個", + "showLessQuotas": "顯示較少", "account": "帳戶", "modelQuotas": "模型配額", "lastUsed": "最近重新整理", @@ -6778,9 +8069,9 @@ "inDuration": "在 {duration} 中", "notApplicable": "不適用", "rawPlanWithValue": "原始計劃:{plan}", - "noPlanFromProvider": "提供商沒有計劃", - "tokenExpiresIn": "令牌在 {time} 後過期", - "tokenExpired": "令牌已過期", + "noPlanFromProvider": "提供者沒有計劃", + "tokenExpiresIn": "權杖在 {time} 後過期", + "tokenExpired": "權杖已過期", "noQuotaData": "無配額資料", "cardExpand": "展開", "cardCollapse": "收起", @@ -6930,11 +8221,11 @@ "quotaCutoffsButtonDefault": "預設", "quotaCutoffsButtonHelp": "編輯此帳戶的最小剩餘配額截止值。", "quotaCutoffsButtonDisabled": "此帳戶尚無可用的配額視窗。", - "deactivateAccount": "__MISSING__:Deactivate account (stop routing)", - "activateAccount": "__MISSING__:Activate account (resume routing)", - "accountActivated": "__MISSING__:Account activated", - "accountDeactivated": "__MISSING__:Account deactivated", - "toggleActiveFailed": "__MISSING__:Failed to update account status", + "deactivateAccount": "停用帳戶(停止路由)", + "activateAccount": "啟用帳戶(恢復路由)", + "accountActivated": "帳戶已啟用", + "accountDeactivated": "帳戶已停用", + "toggleActiveFailed": "更新帳戶狀態失敗", "quotaCutoffsTitle": "{name} ({provider}) 的配額截止", "quotaCutoffsExplainer": "覆蓋停止為每個配額視窗選擇此帳戶的最小剩餘配額百分比。留空以繼承提供程式預設值。", "quotaCutoffsDefaultHint": "預設最小剩餘量:{default}%", @@ -6961,7 +8252,7 @@ "budgetLinearExtrapolation": "線性外推法", "budgetThisMonthSoFar": "這個月到目前為止", "budgetProjectedEndOfMonth": "預計月底", - "budgetByProvider": "按提供商", + "budgetByProvider": "按提供者", "budgetDailyDollar": "每日 $", "budgetWeeklyDollar": "每週 $", "budgetMonthlyDollar": "每月 $", @@ -6971,12 +8262,74 @@ "noSpendLast30Days": "過去 30 天內沒有消費", "updatedShort": "更新於", "lastRefreshed": "上次重新整理", - "providerQuota": "提供商配額", + "providerQuota": "提供者配額", "providerQuotaHomeHint": "已連線帳戶的即時狀態", - "hideQuotaRow": "__MISSING__:Hide this quota row", - "showQuotaRow": "__MISSING__:Show this quota row", - "hiddenQuotaRowsLabel": "__MISSING__:Hidden:", - "quotaVisibilityUpdateFailed": "__MISSING__:Failed to update quota visibility" + "hideQuotaRow": "隱藏此配額列", + "showQuotaRow": "顯示此配額列", + "hiddenQuotaRowsLabel": "已隱藏:", + "quotaVisibilityUpdateFailed": "更新配額可見性失敗", + "budgetAboveLimitShort": "超出限制 ⚠", + "budgetAboveMonthlyLimit": "⚠ 超出 {limit}/月", + "budgetApplyToSelected": "套用到 {count} 個選取金鑰", + "budgetAtOrAboveWarning": "≥ 警告閾值", + "budgetColumnKey": "金鑰", + "budgetColumnMonth": "月份", + "budgetColumnStatus": "狀態", + "budgetColumnToday": "今日", + "budgetCostBreakdown30d": "成本明細(30 天)", + "budgetHardCapComingSoon": "上限政策和電子郵件警示即將推出", + "budgetLimits": "限制", + "budgetNextReset": "下次重置", + "budgetNoKeysSelected": "未選取任何金鑰", + "budgetOnTrackShort": "正常範圍", + "budgetPageDescription": "為每個 API 金鑰設定每日、每週和每月的支出限制。", + "budgetPageTitle": "預算", + "budgetProjection": "預測", + "budgetResetDaily": "每日", + "budgetResetMonthly": "每月", + "budgetResetWeekly": "每週", + "budgetSelectKeysFirst": "請先選取金鑰再套用", + "budgetSelectedTemplateHint": "已選取 {count} 個 · 點擊模板以套用", + "budgetStatus": { + "alerting": "警示中", + "all": "全部", + "blocked": "已封鎖", + "no-limit": "無限制", + "safe": "安全", + "warning": "警告" + }, + "budgetTemplateApplied": "已將「{template}」套用到 {count} 個金鑰", + "budgetTemplateApplyFailed": "套用模板失敗", + "budgetTemplateDailyAmount": "${amount}/天", + "budgetTemplateMonthlyAmount": "${amount}/月", + "budgetTemplateNames": { + "tpl-ci": "CI", + "tpl-dev": "開發", + "tpl-prod": "生產" + }, + "budgetTemplateStorageHint": "共 {count} 個模板 · 透過 localStorage 編輯:", + "budgetTemplates": "模板", + "confirmRedeemResetCredit": "兌換後將立即重置符合資格的 Codex 用量區間,並永久消耗此額度。", + "confirmRedeemResetCreditButton": "兌換額度", + "confirmRedeemResetCreditTitle": "要兌換此重置額度嗎?", + "manageResetCredits": "檢視額度", + "noResetCreditsAvailable": "目前沒有可用的重置額度。", + "redeemResetCredit": "兌換重置", + "redeemThisResetCredit": "兌換", + "resetCreditDefaultTitle": "完整重置", + "resetCreditExpiresAt": "到期日 {date}", + "resetCreditExpiresFirst": "優先到期", + "resetCreditNoExpiry": "無到期日", + "resetCreditRedeemFailed": "兌換重置額度失敗", + "resetCreditRedeemed": "重置已兌換", + "resetCreditsDetailsUnavailable": "額度詳細資料目前無法使用。請重新整理後再試一次。", + "resetCreditsLabel": "重置額度", + "resetCreditsLoadFailed": "載入重置額度失敗", + "resetCreditsModalExplainer": "額度依到期日排序。自動兌換一律使用最先到期的額度。", + "resetCreditsModalTitle": "Codex 重置額度", + "unknownProvider": "未知的提供者", + "usdCost": "USD 成本", + "viewResetCredits": "檢視重置額度" }, "modals": { "waitingAuth": "等待授權", @@ -6990,10 +8343,10 @@ "awsIamIdentity": "AWS IAM 身份中心", "googleAccount": "Google 帳戶", "githubAccount": "GitHub 帳戶", - "importToken": "匯入令牌", - "pasteToken": "從 Kiro IDE 貼上重新整理令牌。", + "importToken": "匯入權杖", + "pasteToken": "從 Kiro IDE 貼上重新整理權杖。", "awsRegion": "AWS 區域", - "autoDetecting": "自動檢測令牌...", + "autoDetecting": "自動檢測權杖...", "readingFromCache": "從 AWS SSO 快取中讀取", "readingFromCursor": "從 Cursor IDE 資料庫讀取", "initializing": "正在初始化...", @@ -7004,7 +8357,7 @@ "noModelsFound": "沒有找到模型" }, "loggers": { - "allProviders": "所有提供商", + "allProviders": "所有提供者", "allModels": "所有模型", "allAccounts": "所有帳戶", "allApiKeys": "所有 API 金鑰", @@ -7031,7 +8384,7 @@ "usageByModel": "按模型使用", "usageByAccount": "按帳戶使用情況", "failedToLoad": "無法載入使用情況統計資訊。", - "tokenHealth": "令牌健康", + "tokenHealth": "權杖健康", "totalOAuth": "總 OAuth", "healthy": "健康", "warning": "警告", @@ -7051,13 +8404,13 @@ "password": "密碼", "unifiedProxy": "統一 AI API 代理", "unifiedAiApiProxy": "統一 AI API 代理", - "unifiedAiApiProxyDesc": "通過單個端點將請求路由到多個 AI 提供商。內建負載均衡、故障轉移和使用情況跟蹤。", + "unifiedAiApiProxyDesc": "通過單個端點將請求路由到多個 AI 提供者。內建負載均衡、故障轉移和使用情況跟蹤。", "passwordNotEnabled": "未啟用密碼保護", "loading": "正在載入...", "invalidPassword": "密碼無效", "errorOccurredRetry": "發生錯誤。請再試一次。", "configureInstance": "開始配置你的 OmniRoute 例項", - "runOnboardingWizard": "執行入門嚮導來設定您的密碼並連線您的第一個 AI 提供商。", + "runOnboardingWizard": "執行入門嚮導來設定您的密碼並連線您的第一個 AI 提供者。", "startOnboarding": "開始引導", "secureYourInstance": "保護您的例項", "setPasswordDescription": "設定密碼以保護您的儀表板並保護您的 API 端點免遭未經授權的訪問。", @@ -7068,7 +8421,7 @@ "copyUrlManual": "請複製位址列中的 URL 並將其貼上到應用程式中。", "accessDeniedDescription": "您無權訪問此資源。檢查您的 API 金鑰或聯絡管理員。", "goToDashboard": "轉到儀表板", - "featureMultiProviderTitle": "多提供商", + "featureMultiProviderTitle": "多提供者", "featureMultiProviderDesc": "OpenAI、Anthropic、Google 等", "featureLoadBalancingTitle": "負載均衡", "featureLoadBalancingDesc": "智慧分配請求", @@ -7107,7 +8460,8 @@ "nodeIncompatibleTitle": "不相容的 Node.js 版本", "nodeIncompatibleDesc": "你當前執行的是 Node.js {version},它不在 OmniRoute 支援的安全執行時策略內。請使用已打補丁的 Node.js 20.x 或 22.x LTS 版本。", "nodeIncompatibleFixLabel": "修復:安裝已打補丁的 Node.js 22 LTS 版本", - "nodeIncompatibleHint": "OmniRoute 要求 Node.js 22.22.2+(22.x LTS)或 24.0.0+(24.x LTS)。為保證穩定性,建議使用 Node 24 LTS。" + "nodeIncompatibleHint": "OmniRoute 要求 Node.js 22.22.2+(22.x LTS)或 24.0.0+(24.x LTS)。為保證穩定性,建議使用 Node 24 LTS。", + "continueWithOidc": "使用 OIDC 繼續" }, "landing": { "brandName": "OmniRoute", @@ -7118,18 +8472,18 @@ "github": "GitHub", "versionLive": "v1.0 現已上線", "oneEndpoint": "一個端點", - "allProviders": "所有 AI 提供商", + "allProviders": "所有 AI 提供者", "heroDescription": "帶 Web 儀表板的 AI 端點代理,是 CLIProxyAPI 的 JavaScript 版本。可與 Claude Code、OpenAI Codex、Cline、RooCode 等 CLI 工具無縫配合。", "getStarted": "開始使用", "viewOnGithub": "在 GitHub 上檢視", "powerfulFeatures": "強大的功能", "featuresSubtitle": "在一個地方管理 AI 基礎設施所需的一切,專為規模化而構建。", "featureUnifiedEndpointTitle": "統一端點", - "featureUnifiedEndpointDesc": "通過單一標準 API URL 訪問所有提供商。", + "featureUnifiedEndpointDesc": "通過單一標準 API URL 訪問所有提供者。", "featureEasySetupTitle": "輕鬆設定", "featureEasySetupDesc": "使用 npx 命令在幾分鐘內啟動並執行。", "featureModelFallbackTitle": "模型回退", - "featureModelFallbackDesc": "發生故障或高延遲時自動切換提供商。", + "featureModelFallbackDesc": "發生故障或高延遲時自動切換提供者。", "featureUsageTrackingTitle": "使用情況追蹤", "featureUsageTrackingDesc": "所有模型的詳細分析和成本監控。", "featureOAuthApiKeysTitle": "OAuth 與 API 金鑰", @@ -7141,19 +8495,19 @@ "featureDashboardTitle": "儀表板", "featureDashboardDesc": "用於即時流量分析的視覺化儀表板。", "howItWorks": "OmniRoute 工作原理", - "howItWorksDescription": "資料從您的應用程式通過我們的智慧路由層無縫流向最適合該工作的提供商。", + "howItWorksDescription": "資料從您的應用程式通過我們的智慧路由層無縫流向最適合該工作的提供者。", "howItWorksStep1Title": "1. CLI 和 SDK", "howItWorksStep1Description": "您的請求從您最喜歡的工具或我們的統一 SDK 開始。只需更改基本 URL 即可。", "howItWorksStep2Title": "2. OmniRoute 中樞", - "howItWorksStep2Description": "我們的引擎分析提示、檢查提供商的執行狀況以及最低延遲或成本的路線。", - "howItWorksStep3Title": "3. AI 提供商", - "howItWorksStep3Description": "請求會被立即轉發給 OpenAI、Anthropic、Gemini 或其他提供商完成處理。", + "howItWorksStep2Description": "我們的引擎分析提示、檢查提供者的執行狀況以及最低延遲或成本的路線。", + "howItWorksStep3Title": "3. AI 提供者", + "howItWorksStep3Description": "請求會被立即轉發給 OpenAI、Anthropic、Gemini 或其他提供者完成處理。", "getStartedIn30Seconds": "30 秒內開始", - "getStartedDescription": "安裝 OmniRoute,通過 Web 儀表板配置你的提供商,然後開始路由 AI 請求。", + "getStartedDescription": "安裝 OmniRoute,通過 Web 儀表板配置你的提供者,然後開始路由 AI 請求。", "installOmniRoute": "安裝 OmniRoute", "installStepDescription": "執行 npx 命令立即啟動伺服器", "openDashboard": "開啟儀表板", - "openDashboardStepDescription": "通過 Web 介面配置提供商和 API 金鑰", + "openDashboardStepDescription": "通過 Web 介面配置提供者和 API 金鑰", "routeRequests": "路由請求", "routeRequestsStepDescription": "將您的 CLI 工具指向 {endpoint}", "terminal": "終端", @@ -7163,7 +8517,7 @@ "serverRunningOnLabel": "伺服器運行於", "dashboardLabel": "儀表板", "readyToRoute": "已準備好開始路由! ✓", - "configureProvidersNote": "📝 在儀表板中配置提供商或使用環境變數", + "configureProvidersNote": "📝 在儀表板中配置提供者或使用環境變數", "dataLocation": "資料位置:", "dataLocationMacLinux": "macOS/Linux:", "dataLocationWindows": "Windows:", @@ -7175,7 +8529,7 @@ "npm": "npm", "legal": "法律", "mitLicense": "MIT 許可證", - "footerTagline": "AI 生成的統一端點。輕鬆連線、路由和管理您的 AI 提供商。", + "footerTagline": "AI 生成的統一端點。輕鬆連線、路由和管理您的 AI 提供者。", "copyright": "© {year} OmniRoute。保留所有權利。", "flowToolClaudeCode": "Claude Code", "flowToolOpenAICodex": "OpenAI Codex", @@ -7196,8 +8550,8 @@ "quickStart": "快速入門", "deploymentGuides": "部署指南", "features": "特點", - "supportedProviders": "支援的提供商", - "supportedProvidersToc": "提供商", + "supportedProviders": "支援的提供者", + "supportedProvidersToc": "提供者", "commonUseCases": "常見用例", "clientCompatibility": "客戶端相容性", "protocolsToc": "協議", @@ -7214,7 +8568,7 @@ "oauthAutoRefresh": "支援自動重新整理 Token 的 OAuth 連線。", "fullStreaming": "所有模型都支援完整流式輸出。", "docsLabel": "文件", - "docsHeroDescription": "面向多提供商 LLM 的 AI 閘道器。一個端點即可統一接入 OpenAI、Anthropic、Gemini、GitHub Copilot、Claude Code、Cursor 等 20+ 提供商。", + "docsHeroDescription": "面向多提供者 LLM 的 AI 閘道器。一個端點即可統一接入 OpenAI、Anthropic、Gemini、GitHub Copilot、Claude Code、Cursor 等 20+ 提供者。", "openDashboard": "開啟儀表板", "endpointPage": "端點頁面", "github": "GitHub", @@ -7226,11 +8580,11 @@ "quickStartStep1Middle": "或者從 GitHub 克隆並執行", "quickStartStep2Title": "2. 建立API金鑰", "quickStartStep2Text": "轉至端點 -> 註冊金鑰。每個環境生成一個金鑰。", - "quickStartStep3Title": "3. 連線提供商", - "quickStartStep3Text": "通過 OAuth 登入、API 金鑰或免費套餐自動接入來新增提供商帳戶。", + "quickStartStep3Title": "3. 連線提供者", + "quickStartStep3Text": "通過 OAuth 登入、API 金鑰或免費套餐自動接入來新增提供者帳戶。", "quickStartStep4Title": "4. 設定客戶端基本 URL", "quickStartStep4Prefix": "將您的 IDE 或 API 客戶端指向", - "quickStartStep4Suffix": "例如使用提供商字首", + "quickStartStep4Suffix": "例如使用提供者字首", "deploySetupTitle": "設定指南", "deploySetupText": "OmniRoute 的分步安裝、環境配置和首次執行演練。", "deployElectronTitle": "電子桌面", @@ -7245,34 +8599,34 @@ "deployPwaText": "在 Android、iOS 和桌面瀏覽器上將 OmniRoute 作為漸進式 Web 應用程式安裝。", "deployTermuxTitle": "Termux(Android)", "deployTermuxText": "通過 Termux 在 Android 上以無介面模式執行 OmniRoute,並從移動瀏覽器訪問儀表盤。", - "featureRoutingTitle": "多提供商路由", - "featureRoutingText": "通過單一 OpenAI 相容端點將請求路由到 30+ AI 提供商,支援聊天、Responses、音訊和影像 API。", + "featureRoutingTitle": "多提供者路由", + "featureRoutingText": "通過單一 OpenAI 相容端點將請求路由到 30+ AI 提供者,支援聊天、Responses、音訊和影像 API。", "featureCombosTitle": "組合和平衡", "featureCombosText": "使用後備鏈和平衡策略建立模型組合:迴圈、優先順序、隨機、最少使用和成本最佳化。", "featureUsageTitle": "使用情況和成本跟蹤", - "featureUsageText": "即時令牌計數、每個提供商/模型的成本計算以及按 API 金鑰和帳戶劃分的詳細使用情況細分。", + "featureUsageText": "即時權杖計數、每個提供者/模型的成本計算以及按 API 金鑰和帳戶劃分的詳細使用情況細分。", "featureAnalyticsTitle": "分析儀表板", - "featureAnalyticsText": "視覺化分析,包含隨時間變化的請求、令牌、錯誤、延遲、成本和模型受歡迎程度的圖表。", + "featureAnalyticsText": "視覺化分析,包含隨時間變化的請求、權杖、錯誤、延遲、成本和模型受歡迎程度的圖表。", "featureHealthTitle": "健康監測", - "featureHealthText": "即時健康檢查、提供商狀態、斷路器狀態以及具有指數退避功能的自動速率限制檢測。", + "featureHealthText": "即時健康檢查、提供者狀態、斷路器狀態以及具有指數退避功能的自動速率限制檢測。", "featureCliTitle": "CLI工具", "featureCliText": "可在儀表板中管理 IDE 配置、匯出/匯入備份、發現 Codex 配置檔案並修改設定。", "featureSecurityTitle": "安全與策略", "featureSecurityText": "API 金鑰身份驗證、IP 過濾、提示注入防護、域策略、會話管理和稽核日誌記錄。", "featureCloudSyncTitle": "雲同步", "featureCloudSyncText": "將配置同步到 Cloudflare Workers,以便通過加密憑據和自動故障轉移實現遠端訪問。", - "providersAcrossConnectionTypes": "跨三種連線型別的 {count} 提供商。", - "manageProviders": "管理提供商", - "providersCount": "{count} 提供商", + "providersAcrossConnectionTypes": "跨三種連線型別的 {count} 提供者。", + "manageProviders": "管理提供者", + "providersCount": "{count} 提供者", "providerTypeFree": "免費套餐", "providerTypeOAuth": "OAuth", "providerTypeApiKey": "API金鑰", - "useCaseSingleEndpointTitle": "許多提供商的單一端點", + "useCaseSingleEndpointTitle": "許多提供者的單一端點", "useCaseSingleEndpointText": "將客戶端統一指向一個 Base URL,再通過模型字首進行路由(例如:gh/、cc/、kr/、openai/)。", "useCaseFallbackTitle": "使用組合進行回退和模型切換", - "useCaseFallbackText": "在儀表板中建立組合模型,並在提供商內部輪換時保持客戶端配置穩定。", + "useCaseFallbackText": "在儀表板中建立組合模型,並在提供者內部輪換時保持客戶端配置穩定。", "useCaseUsageVisibilityTitle": "使用情況、成本和除錯可見性", - "useCaseUsageVisibilityText": "在“使用情況”和“分析”選項卡中按提供商、帳戶和 API 金鑰跟蹤令牌和成本。", + "useCaseUsageVisibilityText": "在“使用情況”和“分析”選項卡中按提供者、帳戶和 API 金鑰跟蹤權杖和成本。", "clientCherryStudioTitle": "櫻桃工作室", "baseUrlLabel": "基礎 URL", "chatEndpointLabel": "聊天端點", @@ -7290,15 +8644,15 @@ "clientClaudeBullet1Middle": "(Claude)或", "clientClaudeBullet1Suffix": "(Antigravity)字首。", "clientWindsurfTitle": "Windsurf", - "clientWindsurfBullet1": "將 OmniRoute 用作 OpenAI 相容的 base URL,並保留顯式提供商字首,以實現確定性路由。", + "clientWindsurfBullet1": "將 OmniRoute 用作 OpenAI 相容的 base URL,並保留顯式提供者字首,以實現確定性路由。", "clientWindsurfBullet2": "常規流量將模型指向 `/v1/chat/completions`,併為 Codex 風格流程保留 `/v1/responses`。", "clientWindsurfBullet3": "使用儀表盤 -> CLI 工具獲取現成的 Windsurf 配置指南。", "clientClineTitle": "Cline", - "clientClineBullet1": "Cline 最適合使用顯式的提供商/模型字首,這樣路由器無需猜測後端。", + "clientClineBullet1": "Cline 最適合使用顯式的提供者/模型字首,這樣路由器無需猜測後端。", "clientClineBullet2": "常規模型使用 `/v1/chat/completions`,並在不同帳戶間複用同一個 OmniRoute base URL。", - "clientClineBullet3": "除錯 Cline 執行時問題前,請使用提供商儀表盤驗證 OAuth/API 金鑰。", + "clientClineBullet3": "除錯 Cline 執行時問題前,請使用提供者儀表盤驗證 OAuth/API 金鑰。", "clientKimiTitle": "Kimi Coding", - "clientKimiBullet1": "輪換底層帳戶或提供商組合時,將 OmniRoute 用作穩定的 base URL。", + "clientKimiBullet1": "輪換底層帳戶或提供者組合時,將 OmniRoute 用作穩定的 base URL。", "clientKimiBullet2": "在編碼流程中優先使用帶字首的模型,讓回退和審計軌跡保持明確。", "clientKimiBullet3": "當你希望為使用工具的客戶端啟用原生 Responses 風格路由時,請使用 `/v1/responses`。", "protocolsTitle": "協議:MCP 與 A2A", @@ -7319,7 +8673,7 @@ "protocolTroubleshooting3": "可使用 `/dashboard/mcp` 和 `/dashboard/a2a` 進行執行控制並檢視審計資訊。", "endpointChatNote": "OpenAI 相容聊天端點(預設)。", "endpointResponsesNote": "Responses API 端點(Codex、o 系列)。", - "endpointModelsNote": "所有連線的提供商的模型目錄。", + "endpointModelsNote": "所有連線的提供者的模型目錄。", "endpointAudioNote": "音訊轉錄(Deepgram、AssemblyAI)。", "endpointSpeechNote": "語音生成(ElevenLabs、OpenAI TTS)。", "endpointEmbeddingsNote": "文本嵌入生成(OpenAI、Cohere、Voyage)。", @@ -7334,20 +8688,20 @@ "mgmtAssignmentsListNote": "按 scope、scope_id 或 proxy_id 列出代理繫結關係。", "mgmtAssignmentsUpdateNote": "為 global/provider/account/combo 作用域分配或清除代理。", "mgmtLegacyMigrationNote": "將舊版 proxyConfig 對映匯入為登錄檔繫結關係。", - "modelPrefixesDescriptionStart": "在模型名稱之前使用提供商字首可路由到特定提供商。示例:", + "modelPrefixesDescriptionStart": "在模型名稱之前使用提供者字首可路由到特定提供者。示例:", "modelPrefixesDescriptionEnd": "會被路由到 GitHub Copilot。", - "provider": "提供商", + "provider": "提供者", "type": "型別", "troubleshootingModelRouting": "如果客戶端在模型路由上失敗,請使用顯式 provider/model(例如:gh/gpt-5.1-codex)。", - "troubleshootingAmbiguousModels": "如果您收到不明確的模型錯誤,請選擇提供商字首而不是裸模型 ID。", + "troubleshootingAmbiguousModels": "如果您收到不明確的模型錯誤,請選擇提供者字首而不是裸模型 ID。", "troubleshootingCodexFamily": "對於 GitHub Codex 系列模型,請保持模型名為 `gh/codex-model`;路由器會自動選擇 `/responses`。", - "troubleshootingTestConnection": "在從 IDE 或外部客戶端進行測試之前,請使用儀表板 > 提供商 > 測試連線。", - "troubleshootingCircuitBreaker": "如果提供商顯示斷路器已開啟,請等待冷卻或檢視執行狀況頁面以瞭解詳細資訊。", - "troubleshootingOAuth": "對於 OAuth 提供商,如果 Token 過期,請重新認證,並檢查提供商卡片上的狀態指示器。", + "troubleshootingTestConnection": "在從 IDE 或外部客戶端進行測試之前,請使用儀表板 > 提供者 > 測試連線。", + "troubleshootingCircuitBreaker": "如果提供者顯示斷路器已開啟,請等待冷卻或檢視執行狀況頁面以瞭解詳細資訊。", + "troubleshootingOAuth": "對於 OAuth 提供者,如果 Token 過期,請重新認證,並檢查提供者卡片上的狀態指示器。", "endpointCompletionsNote": "用於文本生成的舊版 completions 端點。", "endpointModerationsNote": "內容稽核和安全分類。", "endpointRerankNote": "用於檢索增強生成的文件重排序(Cohere、Jina)。", - "endpointSearchNote": "通過 5 個提供商進行網頁搜尋(Serper、Brave、Exa、Tavily、Perplexity)。", + "endpointSearchNote": "通過 5 個提供者進行網頁搜尋(Serper、Brave、Exa、Tavily、Perplexity)。", "endpointSearchAnalyticsNote": "搜尋請求的分析和指標。", "endpointVideoNote": "影片生成(ComfyUI、SD WebUI 工作流)。", "endpointMusicNote": "通過 ComfyUI 工作流生成音樂。", @@ -7356,12 +8710,12 @@ "endpointFilesNote": "用於多模態輸入的檔案上傳。", "endpointBatchesNote": "用於批次 API 請求的 Batch 處理。", "endpointWsNote": "用於即時流式傳輸的 WebSocket 端點。", - "mgmtProvidersListNote": "列出所有已註冊的提供商連線。", - "mgmtProvidersCreateNote": "建立新的提供商連線。", - "mgmtProvidersUpdateNote": "更新現有提供商連線。", - "mgmtProvidersDeleteNote": "刪除提供商連線。", - "mgmtProvidersTestNote": "測試提供商的連線和認證。", - "mgmtProvidersModelsNote": "列出特定提供商的可用模型。", + "mgmtProvidersListNote": "列出所有已註冊的提供者連線。", + "mgmtProvidersCreateNote": "建立新的提供者連線。", + "mgmtProvidersUpdateNote": "更新現有提供者連線。", + "mgmtProvidersDeleteNote": "刪除提供者連線。", + "mgmtProvidersTestNote": "測試提供者的連線和認證。", + "mgmtProvidersModelsNote": "列出特定提供者的可用模型。", "mgmtSettingsGetNote": "獲取當前應用設定。", "mgmtSettingsUpdateNote": "更新應用設定。", "mgmtPayloadRulesGetNote": "獲取載荷轉換規則。", @@ -7373,7 +8727,7 @@ "mcpToolsRoutingTitle": "路由與發現", "mcpToolsRoutingDesc": "健康檢查、組合管理、配額監控、成本報告和模型目錄訪問。", "mcpToolsOperationsTitle": "運維與策略", - "mcpToolsOperationsDesc": "路由模擬、預算保護、策略切換、韌性配置和提供商指標。", + "mcpToolsOperationsDesc": "路由模擬、預算保護、策略切換、韌性配置和提供者指標。", "mcpToolsCacheTitle": "快取管理", "mcpToolsCacheDesc": "檢視快取統計,並清空語義快取或簽名快取。", "mcpToolsCompressionTitle": "壓縮發動機", @@ -7385,9 +8739,9 @@ "mcpToolsSkillsTitle": "技能", "mcpToolsSkillsDesc": "列出、啟用、執行和監控自定義技能執行。", "featureAutoComboTitle": "Auto-Combo", - "featureAutoComboText": "根據你連線的提供商、使用模式和模型能力,自動建立最佳化組合。", + "featureAutoComboText": "根據你連線的提供者、使用模式和模型能力,自動建立最佳化組合。", "featureSearchTitle": "網頁搜尋", - "featureSearchText": "整合 5 個提供商的網頁搜尋(Serper、Brave、Exa、Tavily、Perplexity),幷包含分析和成本跟蹤。", + "featureSearchText": "整合 5 個提供者的網頁搜尋(Serper、Brave、Exa、Tavily、Perplexity),幷包含分析和成本跟蹤。", "featureMemoryTitle": "記憶系統", "featureMemoryText": "跨會話提供提取、注入、檢索和摘要能力的持久化對話記憶。", "featureSkillsTitle": "技能框架", @@ -7403,7 +8757,7 @@ "legal": { "privacyPolicy": "隱私政策", "termsOfService": "服務條款", - "providerConfigurations": "提供商配置", + "providerConfigurations": "提供者配置", "apiKeys": "API 金鑰", "usageLogs": "使用日誌", "applicationSettings": "應用程式設定", @@ -7425,32 +8779,32 @@ "privacySection1Text": "OmniRoute 是一款本地優先的應用。所有資料處理和儲存都只發生在你的裝置上,不存在集中式伺服器收集你的資訊。", "privacySection2Title": "2. 我們儲存的資料", "privacyDataStoredIn": "以下資料儲存在本地", - "privacyDataProviderConfigurationsDesc": "連線 URL、提供商型別和優先順序設定", - "privacyDataApiKeysDesc": "已加密並存儲在本地,用於與 AI 提供商進行身份驗證", - "privacyDataUsageLogsDesc": "請求計數、令牌使用情況、模型名稱、時間戳和響應時間", + "privacyDataProviderConfigurationsDesc": "連線 URL、提供者型別和優先順序設定", + "privacyDataApiKeysDesc": "已加密並存儲在本地,用於與 AI 提供者進行身份驗證", + "privacyDataUsageLogsDesc": "請求計數、權杖使用情況、模型名稱、時間戳和響應時間", "privacyDataApplicationSettingsDesc": "主題偏好、路由策略和組合配置", "privacySection3Title": "3. 無遙測", "privacySection3Text": "OmniRoute 不收集遙測、分析或崩潰報告。不會向我們或任何第三方傳送資料。你的使用模式、API 呼叫和配置都會保持私密。", - "privacySection4Title": "4. 第三方 AI 提供商", - "privacySection4Text": "當你通過 OmniRoute 發起 API 呼叫時,請求會被轉發到你配置的 AI 提供商(例如:OpenAI、Anthropic、Google)。這些提供商有各自的隱私政策,請查閱:", + "privacySection4Title": "4. 第三方 AI 提供者", + "privacySection4Text": "當你通過 OmniRoute 發起 API 呼叫時,請求會被轉發到你配置的 AI 提供者(例如:OpenAI、Anthropic、Google)。這些提供者有各自的隱私政策,請查閱:", "privacyOpenAiPolicy": "OpenAI 隱私政策", "privacyAnthropicPolicy": "Anthropic 隱私政策", "privacyGooglePolicy": "Google 隱私政策", "privacySection5Title": "5. 雲同步(可選)", - "privacySection5Text": "如果您啟用可選的雲同步功能,提供商配置和 API 金鑰可能會傳輸到配置的雲端點。此功能預設處於停用狀態,需要明確選擇加入。", + "privacySection5Text": "如果您啟用可選的雲同步功能,提供者配置和 API 金鑰可能會傳輸到配置的雲端點。此功能預設處於停用狀態,需要明確選擇加入。", "privacySection6Title": "6. 日誌記錄", "privacyLoggingIntro": "可以通過儀表板設定配置請求日誌。您可以:", "privacySection7Title": "7. 您的權利", "privacySection7TextStart": "由於所有資料都儲存在本地,因此您擁有完全的控制權。您可以隨時刪除您的資料,方法是刪除", "privacySection7TextEnd": "目錄或使用儀表板中的資料庫備份和恢復功能。", "termsSection1Title": "1. 概述", - "termsSection1Text": "OmniRoute 是一款本地優先的 AI API 代理路由器,完全執行在你的裝置上。它通過負載均衡、故障回退和用量跟蹤,將請求路由到多個 AI 提供商。", + "termsSection1Text": "OmniRoute 是一款本地優先的 AI API 代理路由器,完全執行在你的裝置上。它通過負載均衡、故障回退和用量跟蹤,將請求路由到多個 AI 提供者。", "termsSection2Title": "2. 使用者的責任", - "termsResponsibilityApiKeys": "你需要自行負責管理自己的 API 金鑰,以及第三方 AI 提供商(OpenAI、Anthropic、Google 等)的憑證。", - "termsResponsibilityCompliance": "你必須遵守通過 OmniRoute 訪問的每個 AI 提供商的服務條款。", + "termsResponsibilityApiKeys": "你需要自行負責管理自己的 API 金鑰,以及第三方 AI 提供者(OpenAI、Anthropic、Google 等)的憑證。", + "termsResponsibilityCompliance": "你必須遵守通過 OmniRoute 訪問的每個 AI 提供者的服務條款。", "termsResponsibilitySecurity": "你需要負責本地 OmniRoute 安裝的安全,包括設定密碼和限制網路訪問。", "termsSection3Title": "3. 工作原理", - "termsSection3Text": "OmniRoute 充當中間代理。傳送到 OmniRoute 的 API 呼叫會被轉換後轉發到你配置的 AI 提供商。除必要的協議轉換外,OmniRoute 不會修改你的請求或響應內容。", + "termsSection3Text": "OmniRoute 充當中間代理。傳送到 OmniRoute 的 API 呼叫會被轉換後轉發到你配置的 AI 提供者。除必要的協議轉換外,OmniRoute 不會修改你的請求或響應內容。", "termsSection4Title": "4. 資料處理", "termsDataStoredLocally": "所有資料都儲存在你本機上的 SQLite 資料庫中。", "termsNoTransmission": "除非你明確啟用雲同步功能,否則 OmniRoute 不會將任何資料傳輸到外部伺服器。", @@ -7506,7 +8860,7 @@ "comparisonTitle": "CLI 工具與 Agent 目標有什麼區別?", "comparisonCliToolsLabel": "CLI 工具頁面", "comparisonCliToolsTitle": "你的 IDE 通過 OmniRoute 傳送請求", - "comparisonCliToolsDesc": "配置 Claude Code、Codex、Cursor 和其他 IDE,將 OmniRoute 用作它們的 API base URL。OmniRoute 作為代理,將請求路由到你配置的提供商。", + "comparisonCliToolsDesc": "配置 Claude Code、Codex、Cursor 和其他 IDE,將 OmniRoute 用作它們的 API base URL。OmniRoute 作為代理,將請求路由到你配置的提供者。", "comparisonAgentsLabel": "當前頁面(Agent 目標)", "comparisonAgentsTitle": "OmniRoute 將請求傳送到本地 CLI 工具", "comparisonAgentsDesc": "OmniRoute 可以啟動本地 CLI 二進位制檔案(claude、codex、goose)作為執行後端。CLI 工具使用自己的認證處理請求並返回結果。", @@ -7564,13 +8918,13 @@ "settingsTab": "設定", "agentsEnabled": "已啟用", "agentsDisabled": "已停用", - "filterAllProviders": "所有提供商", + "filterAllProviders": "所有提供者", "filterAll": "全部", "autoRefreshing": "自動重新整理中", "viewPR": "檢視 Pull Request", "connected": "已連線", "notConnected": "未連線", - "configure": "配置", + "configure": "設定", "settingsTitle": "雲代理設定", "settingsDesc": "為雲代理配置本地偏好。", "settingEnableAgents": "啟用雲代理", @@ -7599,7 +8953,21 @@ "statusCancelled": "取消", "repositoryName": "儲存庫名稱", "repositoryUrl": "儲存庫網址", - "branch": "分公司" + "branch": "分公司", + "activityTypes": { + "code_change": "程式碼變更", + "command": "指令", + "completion": "完成", + "error": "錯誤", + "message": "訊息", + "plan": "計畫" + }, + "agentDescriptions": { + "codexCloud": "OpenAI 的雲端程式代理", + "cursorCloud": "Cursor 的背景/雲端代理(官方 API 金鑰)", + "devin": "Cognition 的 AI 軟體工程師", + "jules": "Google 的自主程式代理" + } }, "templateNames": { "simple-chat": "簡單對話", @@ -7650,7 +9018,7 @@ }, "cache": { "title": "快取管理", - "description": "監控提供商側 Prompt Cache 的效率,以及本地 Semantic Cache 的響應複用情況。", + "description": "監控提供者側 Prompt Cache 的效率,以及本地 Semantic Cache 的響應複用情況。", "refresh": "重新整理", "clearAll": "清空語義快取", "memoryEntries": "記憶體條目", @@ -7680,9 +9048,9 @@ "unavailable": "快取不可用", "unavailableDesc": "無法獲取快取統計資訊。請確保伺服器正在執行。", "loadingCacheAria": "正在載入快取", - "promptCache": "Prompt 快取(提供商側)", + "promptCache": "Prompt 快取(提供者側)", "semanticCache": "語義快取", - "promptCacheSectionDesc": "基於 usage history 展示提供商側 prompt cache 的活躍度,讓你區分哪些請求真的啟用了 cache control,以及實際複用了多少輸入。", + "promptCacheSectionDesc": "基於 usage history 展示提供者側 prompt cache 的活躍度,讓你區分哪些請求真的啟用了 cache control,以及實際複用了多少輸入。", "promptTrendDesc": "按小時展示最近 24 小時的請求量、快取覆蓋率,以及 cache read token 的變化。", "cachedRequests": "快取請求數", "cachedRequests24h": "24 小時快取請求數", @@ -7710,9 +9078,9 @@ "writeShort": "寫入", "resetting": "正在重置...", "resetMetrics": "重置指標", - "byProvider": "按提供商分類", - "providerCacheRateDesc": "每個提供商都會直接展示總輸入 token、cache read token 和 cache write token,方便你對照原始資料判斷比率是否可靠。", - "provider": "提供商", + "byProvider": "按提供者分類", + "providerCacheRateDesc": "每個提供者都會直接展示總輸入 token、cache read token 和 cache write token,方便你對照原始資料判斷比率是否可靠。", + "provider": "提供者", "requests": "請求數", "inputTokens": "輸入 Tokens 總計", "cachedTokensCol": "快取讀取", @@ -7721,7 +9089,7 @@ "peakCached": "峰值快取量", "cached": "已快取", "overview": "概覽", - "tableProvider": "提供商", + "tableProvider": "提供者", "tableModel": "模型", "performanceTitle": "效能", "semanticCacheSectionDesc": "OmniRoute 自己維護的確定性響應快取。開啟後,重複的非流式、temperature=0 請求可以直接在本地命中,不再訪問上游 provider。", @@ -7732,7 +9100,7 @@ "loading": "載入中...", "entriesLoadError": "載入語義快取條目失敗。", "noEntries": "未找到快取條目", - "noPromptCacheData": "暫時還沒有記錄到提供商側 prompt cache 活動。", + "noPromptCacheData": "暫時還沒有記錄到提供者側 prompt cache 活動。", "noTrendData": "最近 24 小時還沒有記錄到 prompt cache 活動。", "signature": "簽名", "model": "模型", @@ -7751,7 +9119,7 @@ "reasoningReplays": "總回放次數", "reasoningCharsCached": "已快取字元數", "reasoningMisses": "快取未命中", - "reasoningByProvider": "按提供商", + "reasoningByProvider": "按提供者", "reasoningByModel": "按模型", "reasoningRecentEntries": "最近條目", "reasoningToolCallId": "工具呼叫 ID", @@ -7846,29 +9214,29 @@ }, "cursorAuthModal": { "title": "連線 Cursor IDE", - "autoDetecting": "自動檢測令牌中...", + "autoDetecting": "自動檢測權杖中...", "readingFromCursor": "正在從 Cursor IDE 或 cursor-agent 讀取", - "tokensAutoDetected": "已成功從 Cursor IDE 自動檢測到令牌!", - "cursorNotDetected": "未檢測到 Cursor IDE。請手動貼上您的令牌。", - "accessToken": "訪問令牌", + "tokensAutoDetected": "已成功從 Cursor IDE 自動檢測到權杖!", + "cursorNotDetected": "未檢測到 Cursor IDE。請手動貼上您的權杖。", + "accessToken": "訪問權杖", "required": "*", - "accessTokenPlaceholder": "訪問令牌將自動填充...", + "accessTokenPlaceholder": "訪問權杖將自動填充...", "machineId": "機器 ID", "optional": "(可選)", "machineIdPlaceholder": "機器 ID 將自動填充...", "importing": "正在匯入...", - "importToken": "匯入令牌", + "importToken": "匯入權杖", "cancel": "取消", - "errorAutoDetect": "無法自動檢測令牌", - "errorAutoDetectFailed": "自動檢測令牌失敗", - "errorEnterToken": "請輸入訪問令牌", + "errorAutoDetect": "無法自動檢測權杖", + "errorAutoDetectFailed": "自動檢測權杖失敗", + "errorEnterToken": "請輸入訪問權杖", "errorImportFailed": "匯入失敗" }, "pricingModal": { "title": "定價配置", "loading": "正在載入定價資料...", "pricingRatesFormat": "定價費率格式", - "ratesDescription": "所有費率均為 每百萬令牌美元($/1M 令牌)。示例:輸入費率為 2.50 表示每 1,000,000 個輸入令牌收費 2.50 美元。", + "ratesDescription": "所有費率均為 每百萬權杖美元($/1M 權杖)。示例:輸入費率為 2.50 表示每 1,000,000 個輸入權杖收費 2.50 美元。", "model": "模型", "input": "輸入", "output": "輸出", @@ -7905,11 +9273,11 @@ "modalEditTitle": "編輯代理", "labelName": "名稱", "labelType": "型別", - "labelFamily": "__MISSING__:IP family", - "familyAuto": "__MISSING__:Auto (dual-stack)", - "familyIpv4": "__MISSING__:IPv4 only", - "familyIpv6": "__MISSING__:IPv6 only", - "familyHint": "__MISSING__:Egress address family. Auto keeps dual-stack; IPv4/IPv6 pin this proxy to that family (prevents a v4 leak under an IPv6-only proxy).", + "labelFamily": "IP 家族", + "familyAuto": "自動(雙協定棧)", + "familyIpv4": "僅 IPv4", + "familyIpv6": "僅 IPv6", + "familyHint": "出口位址家族。自動保留雙協定棧;IPv4/IPv6 將此代理固定到該家族(防止在僅 IPv6 代理下洩漏 v4)。", "labelHost": "主機", "labelPort": "埠", "labelUsername": "使用者名稱", @@ -7933,7 +9301,7 @@ "labelScope": "作用域", "labelProxy": "代理", "scopeGlobal": "全域性", - "scopeProvider": "按提供商", + "scopeProvider": "按提供者", "scopeAccount": "按帳號", "scopeCombo": "按組合", "bulkImportErrorMissingName": "缺少 NAME", @@ -7974,18 +9342,52 @@ "bulkImportPreview": "預覽", "clearAssignment": "(明確分配)", "bulkProxyAssignment": "批次代理分配", - "testAll": "__MISSING__:Test All", - "errorTestFailed": "__MISSING__:Failed to test proxies", - "batchSelectedCount": "__MISSING__:{count} selected", - "batchDeleteSelected": "__MISSING__:Delete {count} selected", - "batchActivateSelected": "__MISSING__:Enable {count} selected", - "testPassed": "__MISSING__:✓ OK" + "testAll": "全部測試", + "errorTestFailed": "測試代理失敗", + "batchSelectedCount": "已選取 {count} 個", + "batchDeleteSelected": "刪除選取的 {count} 個", + "batchActivateSelected": "啟用選取的 {count} 個", + "testPassed": "✓ 正常", + "close": "關閉", + "managePool": "管理池", + "poolAddFailed": "將代理新增至池失敗", + "poolAddLabel": "新增代理", + "poolAddMember": "新增", + "poolDescription": "將多個代理附加到一個範圍,並在其中輪換出口 IP。只有單一代理的範圍行為與一般指派完全相同。", + "poolLoad": "載入池", + "poolLoadFailed": "載入代理池失敗", + "poolMembersLabel": "池成員({count})", + "poolNoMembers": "此池中尚無代理。", + "poolRemove": "移除", + "poolRemoveFailed": "從池中移除代理失敗", + "poolSaveFailed": "儲存池指派失敗", + "poolScopeIdLabel": "範圍 ID", + "poolScopeIdPlaceholder": "提供者 ID / 連線 ID / 組合 ID", + "poolScopeIdRequired": "提供者、帳號或組合範圍需要範圍 ID。", + "poolSelectProxy": "選取代理…", + "poolStrategyFailed": "更新輪換策略失敗", + "poolStrategyHint": "round-robin 依序輪換成員;random 均勻隨機選取;sticky 在時間視窗內固定使用一個成員;latency-optimized 根據日誌選取最快的成員。", + "poolStrategyLabel": "輪換策略", + "poolTitle": "代理池與輪換", + "relayAuthMissing": "缺少驗證", + "relayProbeSummary": "中繼探測:{alive}/{tested} 存活", + "relayRepairError": "修復失敗", + "relayRepairFailed": "中繼修復失敗", + "relayRepairRedeployRequired": "中繼驗證無法恢復 — 請重新部署中繼以寫入新的 token。", + "relayRepairTooltip": "原地恢復中繼驗證。如果 token 無法恢復(例如在 STORAGE_ENCRYPTION_KEY 輪換後),請改為重新部署中繼。", + "repair": "修復", + "selectAllProxies": "選取所有代理", + "selectProxy": "選取 {name}", + "strategyLatency": "延遲最佳化", + "strategyRandom": "隨機", + "strategyRoundRobin": "輪詢", + "strategySticky": "固定" }, "playground": { "title": "模型演練場", - "description": "直接從儀表板測試任何模型。選擇提供商、模型和端點型別,然後傳送請求以檢視原始響應。", + "description": "直接從儀表板測試任何模型。選擇提供者、模型和端點型別,然後傳送請求以檢視原始響應。", "endpoint": "端點", - "provider": "提供商", + "provider": "提供者", "model": "模型", "accountKey": "帳戶 / 金鑰", "autoAccounts": "自動({count} 個帳戶)", @@ -8019,7 +9421,10 @@ "video": "影片生成", "music": "音樂生成", "rerank": "Rerank", - "search": "網頁搜尋" + "search": "網頁搜尋", + "completions": "補全", + "moderations": "審核", + "webFetch": "網頁擷取" }, "conversationalChat": "對話式聊天", "clearChat": "清除聊天內容", @@ -8028,7 +9433,7 @@ "tabCompare": "比較", "tabApi": "API", "tabBuild": "構建", - "configPane": "配置", + "configPane": "設定", "systemPrompt": "系統提示", "systemPromptPlaceholder": "你是一個樂於助人的助手。", "modelPlaceholder": "例如 openai/gpt-4o", @@ -8037,7 +9442,7 @@ "collapseConfig": "摺疊配置面板", "expandConfig": "展開配置面板", "temperature": "溫度", - "maxTokens": "最大令牌數", + "maxTokens": "最大權杖數", "topP": "Top-p", "presencePenalty": "存在懲罰", "frequencyPenalty": "頻率懲罰", @@ -8078,7 +9483,7 @@ "ttft": "TTFT", "tps": "TPS", "metricsDisclaimer": "客戶端估算", - "tokensLabel": "令牌", + "tokensLabel": "權杖", "costLabel": "成本", "costEstimated": "(估算)", "toolsLabel": "工具", @@ -8095,11 +9500,11 @@ "enterToolResult": "輸入工具結果…", "build": { "step1Label": "測試什麼?", - "step2Label": "配置", + "step2Label": "設定", "step3Label": "執行", "step1Title": "你想測試什麼?", "step1Subtitle": "選擇您想在本次會議中探索的功能。", - "step2Title": "配置", + "step2Title": "設定", "step2Subtitle": "設定請求中將使用的工具或 JSON 架構。", "step3Title": "執行", "modeToolsTitle": "工具", @@ -8112,7 +9517,77 @@ "nextButton": "下一步", "runButton": "執行", "promptPlaceholder": "輸入您的訊息…(按 Enter 傳送,Shift+Enter 換行)" - } + }, + "addColumn": "新增欄位", + "addModelColumn": "新增模型欄", + "addModelToCompare": "新增模型欄以進行比較", + "autoProvider": "自動", + "cancelAllStreams": "取消所有串流", + "cancelStream": "取消串流", + "columnCount": "{count}/{max} 欄", + "comparePromptPlaceholder": "在此輸入提示詞...", + "confirmImprovePrompt": "確認改善提示詞", + "editTool": "編輯工具 {name}", + "effort": "投入程度", + "effortDefault": "預設", + "errorLabel": "錯誤", + "estimatedCostTitle": "預估費用(不保證準確)", + "generating": "正在生成...", + "httpError": "錯誤 {status}", + "improvePromptAria": "使用 AI 改善提示詞", + "improvePromptDescription": "這將會把您目前的系統提示詞傳送至 以生成改善版本。", + "improvePromptFailed": "改善提示詞失敗。", + "jsonMode": "JSON 模式", + "jsonModeDescription": "強制使用 response_format: json_schema", + "jsonSchema": "JSON 結構", + "jsonSchemaEditor": "JSON 結構編輯器", + "modelsSimultaneously": "最多同時使用 {max} 個模型", + "networkError": "網路錯誤", + "newColumnModelName": "新欄位的模型名稱", + "noModel": "無模型", + "noResponseBody": "無回應內容", + "readyToRun": "準備就緒,可執行。", + "reasoningLabel": "推理", + "regenerate": "重新生成", + "regenerateLastResponse": "重新生成最後回應", + "removeColumn": "移除欄位", + "removeModelColumn": "移除 {model} 的欄位", + "removeTool": "移除工具 {name}", + "requestCancelled": "請求已取消", + "role": { + "assistant": "assistant", + "system": "system", + "user": "user" + }, + "runAllColumns": "執行所有欄位", + "schemaName": "結構名稱", + "schemaValidated": "結構驗證通過", + "seed": "隨機種子", + "setModelInConfigFirst": "請先在設定面板中設定模型。", + "startConversation": "開始對話 — 請在下方輸入訊息", + "status": { + "done": "已完成", + "error": "錯誤", + "idle": "閒置", + "streaming": "串流中" + }, + "statusLabel": "狀態:{status}", + "stop": "停止", + "stopSequences": "停止序列", + "stopSequencesPlaceholder": "例如 \"\\n\\n\" 或 \"END\"", + "thinking": "思考中", + "tokenCountsTitle": "提示 token ↑ / 補全 token ↓", + "toolNameRequiredPlaceholder": "函式名稱 *", + "toolParamsJsonSchema": "參數的 JSON 結構", + "toolParamsLabel": "參數(JSON 結構)", + "toolsCount": "工具({count})", + "tpsTitle": "每秒 token 數(客戶端估算)", + "ttftTitle": "首次 token 延遲(客戶端估算)", + "typeMessageWithShortcut": "輸入訊息...(Enter 傳送,Shift+Enter 換行)", + "unknownError": "發生未知錯誤。", + "userPrompt": "使用者提示詞", + "validateSchema": "驗證結構", + "waitingForResponse": "正在等待回應..." }, "miniPlayground": { "endpoint": "端點", @@ -8149,7 +9624,39 @@ "noKeysFound": "未找到 API 金鑰。請到 Keys 區域新增一個。", "exampleLabel": "示例", "latency": "{ms} 毫秒", - "statsLine": "{ms}ms · {tokensIn} 輸入 / {tokensOut} 輸出 tokens" + "statsLine": "{ms}ms · {tokensIn} 輸入 / {tokensOut} 輸出 tokens", + "assistant": "助理", + "audioFormats25Mb": "MP3、WAV、M4A、OGG 或 FLAC · 最大 25 MB", + "browserAudioUnsupported": "您的瀏覽器不支援音訊播放。", + "browserVideoUnsupported": "您的瀏覽器不支援影片播放。", + "chooseFile": "選擇檔案", + "clear": "清除", + "defaultKey": "(預設)", + "documentUrl": "文件網址", + "embedding": "嵌入", + "embeddingSample": "OmniRoute 能將 AI 請求路由至多個提供者。", + "emptyConversation": "傳送訊息開始對話", + "errorLabel": "錯誤", + "fileTooLarge25Mb": "檔案過大。最大大小:25 MB。", + "image": "圖片", + "imageSample": "日落時分的未來城市,電影級燈光", + "music": "音樂", + "musicSample": "平靜的環境音樂,搭配溫暖的鋼琴和柔和的弦樂", + "noAudioUrl": "回應中未包含音訊網址:{response}", + "requestFailed": "請求失敗", + "searchResultFallback": "結果 {number}", + "selectAudioFirst": "請先選擇音訊檔案。", + "sendHint": "Shift+Enter 換行 · Enter 傳送", + "speechToText": "語音轉文字", + "stop": "停止", + "textToSpeech": "文字轉語音", + "ttsSample": "來自 OmniRoute 的問候。這是文字轉語音測試。", + "video": "影片", + "videoSample": "紙飛機飛越未來城市", + "webFetch": "網頁擷取", + "webSearch": "網路搜尋", + "webSearchSample": "什麼是 OmniRoute?", + "you": "你" }, "requestLogger": { "recording": "正在錄製", @@ -8159,8 +9666,8 @@ "updatingPipelineLogs": "正在更新管道日誌...", "updatePipelineFailed": "更新流水線日誌失敗", "capturePipeline": "為新請求捕獲流水線載荷", - "searchPlaceholder": "搜尋模型、提供商、帳戶、API金鑰、組合...", - "allProviders": "所有提供商", + "searchPlaceholder": "搜尋模型、提供者、帳戶、API金鑰、組合...", + "allProviders": "所有提供者", "allModels": "所有模型", "allAccounts": "所有帳戶", "allApiKeys": "所有API金鑰", @@ -8172,8 +9679,8 @@ "shown": "顯示", "sortNewest": "最新", "sortOldest": "最早", - "sortTokensDesc": "令牌 ↓", - "sortTokensAsc": "令牌 ↑", + "sortTokensDesc": "權杖 ↓", + "sortTokensAsc": "權杖 ↑", "sortDurationDesc": "時長 ↓", "sortDurationAsc": "時長 ↑", "sortStatusDesc": "狀態 ↓", @@ -8188,7 +9695,7 @@ "semantic": "Semantic Cache", "upstream": "上游", "semanticCacheHit": "語義快取命中(由 OmniRoute 提供)", - "upstreamResponse": "上游提供商響應", + "upstreamResponse": "上游提供者響應", "noApiKey": "無 API key", "requestedRoutedTitle": "請求 {requested},路由為 {routed}", "statusFilters": { @@ -8202,7 +9709,7 @@ "cacheSource": "快取來源", "model": "模型", "requested": "請求", - "provider": "提供商", + "provider": "提供者", "protocol": "請求協議", "account": "帳戶", "apiKey": "API金鑰", @@ -8217,8 +9724,8 @@ "noLogs": "暫無日誌記錄。進行一些API呼叫以在此處檢視它們。", "noMatchingLogs": "沒有匹配當前篩選器的日誌。", "callLogsInfo": "呼叫日誌也儲存為JSON檔案到{dataDir},並根據{retentionDays}和{maxEntries}進行輪換。", - "loadMore": "__MISSING__:Load More", - "loadingMore": "__MISSING__:Loading more..." + "loadMore": "載入更多", + "loadingMore": "載入更多…" }, "proxyLogger": { "filterAll": "全部", @@ -8230,14 +9737,14 @@ "colTls": "TLS", "colType": "型別", "colLevel": "級別", - "colProvider": "提供商", + "colProvider": "提供者", "colTarget": "目標", "colLatency": "延遲", "colClientIp": "客戶端 IP", "colTime": "時間", "recording": "記錄中", "paused": "已暫停", - "searchPlaceholder": "搜尋主機、提供商、目標或 IP...", + "searchPlaceholder": "搜尋主機、提供者、目標或 IP...", "allTypes": "全部型別", "allLevels": "全部級別", "allProviders": "全部 Provider", @@ -8296,10 +9803,10 @@ "hintModelsBlocked": "模型已阻止", "resilienceTitle": "3 層彈性", "resilienceSubtitle": "反映記錄的彈性模型", - "providersHealthy": "{percent}% 提供商健康", + "providersHealthy": "{percent}% 提供者健康", "layer": "第 {n} 層", - "layer1Title": "提供商斷路器", - "layer1Desc": "阻止到上游級失敗的提供商的流量", + "layer1Title": "提供者斷路器", + "layer1Desc": "阻止到上游級失敗的提供者的流量", "layer2Title": "連線冷卻", "layer2Desc": "跳過一個壞帳戶/金鑰;其他連線繼續服務", "layer3Title": "模型鎖定", @@ -8345,16 +9852,16 @@ }, "quotaShare": { "title": "配額共享", - "description": "通過百分比限制跨 API 金鑰共享提供商配額", + "description": "通過百分比限制跨 API 金鑰共享提供者配額", "newPool": "新建池", "betaTitle": "Beta — UI preview.", "betaDescription": "配置儲存在 localStorage 中(尚未持久化到伺服器)。每次請求的上限執行將在未來更新中實現。", "kpiActivePools": "活躍池", "kpiKeysAllocated": "已分配金鑰", "kpiAvgUnallocated": "平均未分配", - "kpiProvidersWithQuota": "有配額的提供商", + "kpiProvidersWithQuota": "有配額的提供者", "emptyTitle": "未配置池", - "emptyDescription": "建立池以分配 API 金鑰,通過百分比分配共享提供商的配額視窗。", + "emptyDescription": "建立池以分配 API 金鑰,通過百分比分配共享提供者的配額視窗。", "loading": "載入中…", "removePool": "移除池", "removeConfirm": "移除此池?", @@ -8374,7 +9881,7 @@ "policyBurstHint": "允許突發進入空閒池", "editAllocations": "編輯分配", "newPoolTitle": "新建配額池", - "providerConnection": "提供商連線(帳戶)", + "providerConnection": "提供者連線(帳戶)", "selectConnection": "選擇連線…", "noEligibleConnections": "沒有帶配額資料的連線。首先從 /dashboard/quota 重新整理。", "quotaWindow": "配額視窗", @@ -8427,7 +9934,7 @@ "wizardStep1Label": "帳戶", "wizardStep2Label": "限制", "wizardStep3Label": "金鑰", - "wizardStep1Title": "選擇提供商連線", + "wizardStep1Title": "選擇提供者連線", "wizardStep1Subtitle": "選擇此池將共享配額的提供者帳戶,設定名稱和預設策略。", "wizardStep2Title": "配置配額維度", "wizardStep2Subtitle": "為所選連線定義配額計劃維度(單位、視窗、限制)。保持不變以保持當前設定。", @@ -8475,7 +9982,11 @@ "deleteGroupConfirm": "要刪除此組嗎?必須先重新分配或移除其池。", "deleteGroupHasPools": "該組仍然有池 — 請先重新分配或刪除它們。", "ungroupedTitle": "未分組", - "ungroupedHint": "這些池未分配給已知組。編輯一個池以將其移動到真實組中。" + "ungroupedHint": "這些池未分配給已知組。編輯一個池以將其移動到真實組中。", + "apiKeyColumn": "API 金鑰", + "fairShareShort": "公平", + "weightColumn": "權重", + "weightPercent": "權重 %" }, "plugins": { "title": "外掛", @@ -8498,7 +10009,7 @@ "uninstalled": "{name} 已解除安裝", "uninstallFailed": "解除安裝 {name} 失敗", "configure": "配置:{name}", - "configurePlugin": "配置", + "configurePlugin": "設定", "noConfigSettings": "此外掛無可配置的設定。", "saving": "儲存中…", "saveConfiguration": "儲存配置", @@ -8511,22 +10022,22 @@ "status": "狀態", "enabled": "已啟用", "disabled": "已停用", - "installedTab": "__MISSING__:Installed", - "marketplaceTab": "__MISSING__:Marketplace", - "marketplaceUrlLabel": "__MISSING__:Custom Marketplace URL", - "marketplaceUrlPlaceholder": "__MISSING__:Leave empty for official Omniroute registry", - "saveMarketplaceUrl": "__MISSING__:Save & Reload", - "marketplaceUrlSaved": "__MISSING__:Marketplace URL updated", - "marketplaceEmpty": "__MISSING__:No plugins found in marketplace.", - "marketplaceInstallComingSoon": "__MISSING__:Marketplace installs are coming soon.", - "verified": "__MISSING__:Verified", - "install": "__MISSING__:Install", - "installedFromMarketplace": "__MISSING__:Plugin {name} installed!", + "installedTab": "已安裝", + "marketplaceTab": "市集", + "marketplaceUrlLabel": "自訂市集 URL", + "marketplaceUrlPlaceholder": "留空以使用官方 Omniroute 註冊表", + "saveMarketplaceUrl": "儲存並重新載入", + "marketplaceUrlSaved": "市集 URL 已更新", + "marketplaceEmpty": "在市集中找不到外掛程式。", + "marketplaceInstallComingSoon": "市集安裝即將推出。", + "verified": "已验证", + "install": "安裝", + "installedFromMarketplace": "外掛 {name} 已安裝!", "hooks": "鉤子" }, "quotaPlans": { "title": "計劃與配額", - "description": "為每個提供商配置配額計劃 — 維度(%、請求、令牌、$)和時間視窗", + "description": "為每個提供者配置配額計劃 — 維度(%、請求、權杖、$)和時間視窗", "providerLabel": "提供者 / 連線", "detectedPlanLabel": "檢測到的計劃", "manualPlanLabel": "手動覆蓋", @@ -8538,7 +10049,7 @@ "unitOptions": { "percent": "%", "requests": "請求", - "tokens": "令牌", + "tokens": "權杖", "usd": "美元 ($)" }, "windowOptions": { @@ -8551,9 +10062,9 @@ "useCatalogButton": "使用目錄", "saveOverrideButton": "儲存覆蓋", "revertToCatalogButton": "恢復到目錄", - "unknownProviderNotice": "在左側選擇一個提供商以配置其配額計劃。", + "unknownProviderNotice": "在左側選擇一個提供者以配置其配額計劃。", "catalogTitle": "已知目錄", - "catalogDescription": "自動檢測到以下提供商的計劃:" + "catalogDescription": "自動檢測到以下提供者的計劃:" }, "activity": { "title": "活動", @@ -8621,13 +10132,19 @@ "authLoginMisconfigured": "身份驗證配置無效", "authLoginSetupRequired": "需要進行身份驗證設定", "authLogoutSuccess": "{actor} 已登出", - "syncTokenCreated": "{actor} 建立了同步令牌", - "syncTokenRevoked": "{actor} 撤銷了同步令牌", + "syncTokenCreated": "{actor} 建立了同步權杖", + "syncTokenRevoked": "{actor} 撤銷了同步權杖", "settingsUpdate": "{actor} 更新了設定", "settingsUpdateFailed": "設定更新失敗", "serviceRevealApiKey": "{actor} 揭露了 {target} 的 API 金鑰", "genericEvent": "{actor} {target}" - } + }, + "fetchFailed": "擷取活動失敗", + "filterAria": "依事件類型篩選", + "loading": "載入中", + "loadingActivity": "正在載入活動⋯", + "refresh": "重新整理", + "refreshAria": "重新整理活動動態" }, "agentBridge": { "title": "AgentBridge", @@ -8651,7 +10168,7 @@ "restartServer": "重啟", "trustCert": "信任證書", "downloadCert": "下載證書", - "certManualTitle": "__MISSING__:Certificate couldn't be installed automatically (e.g. inside a container). The bridge can still run — trust the CA manually:", + "certManualTitle": "無法自動安裝憑證(例如在容器內)。橋接器仍可執行 — 請手動信任 CA:", "regenerateCert": "重新生成證書", "starting": "正在啟動…", "stopping": "停止中…", @@ -8718,28 +10235,28 @@ "quickLinks": "快速連結", "quickLinkProviders": "配置提供程式", "quickLinkInspector": "在流量檢查器中檢視流量", - "maintenanceTitle": "__MISSING__:Maintenance & Diagnostics", - "maintenanceSubtitle": "__MISSING__:Self-test the capture pipeline, undo leftover system state, and move your setup between machines.", - "orphanedStateWarning": "__MISSING__:A previous session left system state behind (DNS spoof, CA, or system proxy). Run Repair to clean it up.", - "diagnose": "__MISSING__:Diagnose", - "diagnosing": "__MISSING__:Diagnosing…", - "diagnoseHealthy": "__MISSING__:Capture pipeline is healthy.", - "diagnoseUnhealthy": "__MISSING__:Capture pipeline has problems:", - "repair": "__MISSING__:Repair", - "repairing": "__MISSING__:Repairing…", - "repairDone": "__MISSING__:Repaired: {items}", - "repairNothing": "__MISSING__:Nothing to repair — system state is clean.", - "removeCa": "__MISSING__:Remove CA", - "removeCaConfirm": "__MISSING__:Remove CA?", - "removeCaDone": "__MISSING__:MITM root CA removed from the OS trust store.", - "removing": "__MISSING__:Removing…", - "confirm": "__MISSING__:Confirm", - "exportConfig": "__MISSING__:Export config", - "exporting": "__MISSING__:Exporting…", - "importConfig": "__MISSING__:Import config", - "importing": "__MISSING__:Importing…", - "importInvalidJson": "__MISSING__:The selected file is not valid JSON.", - "importDone": "__MISSING__:Imported {bypass} bypass · {hosts} hosts · {agents} agents", + "maintenanceTitle": "維護與診斷", + "maintenanceSubtitle": "自我測試捕捉管道、復原遺留的系統狀態,以及在機器之間移轉您的設定。", + "orphanedStateWarning": "先前的工作階段留下了系統狀態(DNS 偽造、CA 或系統代理)。執行修復以清理。", + "diagnose": "診斷", + "diagnosing": "正在診斷…", + "diagnoseHealthy": "擷取管線運作正常。", + "diagnoseUnhealthy": "捕捉管道存在問題:", + "repair": "修復", + "repairing": "正在修復…", + "repairDone": "已修復:{items}", + "repairNothing": "無需修復 — 系統狀態乾淨。", + "removeCa": "移除 CA", + "removeCaConfirm": "移除 CA?", + "removeCaDone": "已從作業系統信任儲存庫中移除 MITM 根憑證。", + "removing": "正在移除⋯", + "confirm": "確認", + "exportConfig": "匯出設定", + "exporting": "正在匯出…", + "importConfig": "匯入設定", + "importing": "正在匯入…", + "importInvalidJson": "選取的檔案不是有效的 JSON。", + "importDone": "已匯入 {bypass} 個繞過 · {hosts} 個主機 · {agents} 個代理程式", "save": "儲存", "saving": "儲存中…", "cancel": "取消", @@ -8753,7 +10270,8 @@ "goNow": "現在去", "message": "MITM Proxy 現在歸 AgentBridge 所有。", "title": "此頁面已移動" - } + }, + "unknownError": "未知錯誤" }, "trafficInspector": { "title": "流量檢查員", @@ -8767,8 +10285,8 @@ "httpProxyModeDesc": "使用 HTTP_PROXY 環境變數", "systemWideMode": "系統範圍內", "systemWideModeDesc": "攔截所有系統流量(高階)", - "tproxyMode": "__MISSING__:TPROXY Decrypt", - "tproxyModeUnavailable": "__MISSING__:TPROXY decrypt requires Linux + root + the native addon", + "tproxyMode": "TPROXY 解密", + "tproxyModeUnavailable": "TPROXY 解密需要 Linux + root 權限 + 原生附加元件", "filterBarTitle": "過濾器", "profileLlmOnly": "僅限 LLM", "profileCustom": "自定義", @@ -8825,7 +10343,7 @@ "httpProxyTitle": "HTTP 代理程式碼片段 — 埠 {port}", "notRecording": "未錄製", "anyStatus": "任何狀態", - "liveOnly": "__MISSING__:Live", + "liveOnly": "即時", "viewingRecordedSession": "檢視錄製的會話", "backToLive": "返回直播", "untitledSession": "未命名會話", @@ -8850,7 +10368,40 @@ "timingRequestSize": "請求大小", "timingResponseSize": "響應大小", "pausedNewBadge": "{count} 新的", - "clearContextFilter": "清除" + "clearContextFilter": "清除", + "addHostFailed": "新增主機失敗", + "clear": "清除", + "close": "關閉", + "collapse": "折疊", + "deleteSession": "刪除工作階段", + "expand": "展開", + "filterByContext": "以此上下文篩選", + "filteringContext": "正在篩選:上下文 {context}", + "formatted": "格式化", + "hide": "隱藏", + "invalidHost": "無效的主機", + "invalidHostname": "無效的主機名稱", + "name": "名稱", + "networkError": "網路錯誤", + "noRequestBody": "無請求主體。", + "noResponseBody": "無回應主體。", + "noSseEvents": "無 SSE 事件", + "raw": "原始", + "removeHost": "移除 {host}", + "requestCountShort": "{count} 個請求", + "requestDetails": "請求詳細資訊", + "roleAssistant": "助手", + "roleSystem": "系統", + "roleTool": "工具", + "roleUser": "使用者", + "saving": "正在儲存…", + "sensitiveHeaders": "敏感標頭", + "sessionName": "工作階段 {id}", + "sessions": "工作階段", + "show": "顯示", + "systemPromptHidden": "系統提示已隱藏 — 點擊展開", + "trafficProfile": "流量設定檔", + "value": "值" }, "cliCommon": { "concept": { @@ -8876,7 +10427,7 @@ "comparison": { "title": "瞭解 OmniRoute 中的 3 種 CLI 型別", "thisPage": "[此頁面 ✓]", - "open": "__MISSING__:Open →", + "open": "開啟 →", "code": { "title": "程式碼工具", "desc": "指向 Omni", @@ -8906,11 +10457,12 @@ "manualConfig": "手動配置", "installGuide": "安裝指南", "endpointLabel": "端點", - "baseUrlFull": "__MISSING__:Full Base URL", + "baseUrlFull": "完整基礎 URL", "baseUrlPartial": "部分基礎 URL", "refreshDetection": "重新整理檢測", "alsoAcp": "也 ACP", - "connectProviderHint": "__MISSING__:Connect a provider in Providers" + "connectProviderHint": "在提供者中連線一個提供者", + "versionNotFound": "找不到" }, "detail": { "back": "返回", @@ -8920,7 +10472,7 @@ "vendor": "供應商", "category": "型別", "detectionStatus": "檢測", - "configStatus": "配置", + "configStatus": "設定", "baseUrlLabel": "基礎 URL", "apiKeyLabel": "API 金鑰", "modelMappingLabel": "模型對映", @@ -9038,7 +10590,7 @@ }, "categoryApi": "API", "categoryCli": "命令列介面", - "categoryConfig": "配置", + "categoryConfig": "設定", "filterAll": "所有", "coverageLabel": "覆蓋率", "mcpUrl": "MCP URL", @@ -9057,67 +10609,252 @@ "regenerateConfirm": "這將重新生成所有缺失的 SKILL.md 檔案。繼續嗎?", "regenerateRunning": "正在重新生成技能…", "regenerateSuccess": "技能成功重生。", - "regenerateError": "無法重新生成技能。" + "regenerateError": "無法重新生成技能。", + "a2aPrompt": "將此 Agent Card 註冊到您的協調器中,以啟用 A2A 任務委派。", + "catalog": { + "cli-a2a": { + "description": "從 CLI 與 OmniRoute A2A 伺服器互動。傳送任務、檢查技能執行歷史,並以互動方式測試 JSON-RPC 2.0 代理間協定。", + "name": "CLI:A2A 協定" + }, + "cli-backup-sync": { + "description": "從 CLI 備份和還原 OmniRoute 資料。觸發增量快照、同步至雲端儲存、管理備份排程,以及從封存檔案還原。", + "name": "CLI:備份與同步" + }, + "cli-batches": { + "description": "從 CLI 提交與監控批次推論任務。上傳和管理批次處理檔案、擷取結果,並將批次管線整合至 CI/CD 工作流程。", + "name": "CLI:批次與檔案" + }, + "cli-chat": { + "description": "從 CLI 傳送聊天完成請求、串流回應,以及啟動互動式 REPL 工作階段。支援所有 OmniRoute 供應商、組合路由和系統提示設定。", + "name": "CLI:聊天" + }, + "cli-compression": { + "description": "從 CLI 設定與測試提示壓縮。管理 RTK 篩選器、Caveman 規則、堆疊壓縮模式,並使用真實提示預覽壓縮輸出。", + "name": "CLI:壓縮" + }, + "cli-contexts": { + "description": "從 CLI 管理上下文工程設定、RTK 篩選器集合和對話工作階段。套用上下文轉送設定並檢查啟用中的上下文管線。", + "name": "CLI:上下文與工作階段" + }, + "cli-cost-usage": { + "description": "從 CLI 檢視成本明細、Token 使用量和呼叫紀錄。依供應商、模型或日期範圍篩選。匯出使用量報告並檢查各連線的支出。", + "name": "CLI:成本與使用量" + }, + "cli-eval": { + "description": "從 CLI 建立與執行評估套件、觀看即時基準測試進度、檢視評分卡、比較模型效能,並將評估執行整合至 CI 工作流程。", + "name": "CLI:評估" + }, + "cli-health": { + "description": "從 CLI 檢查伺服器健康狀態、元件狀態和即時指標。執行 `health`、`health components` 和 `health watch` 以取得斷路器和供應商狀態的即時儀表板。", + "name": "CLI:健康狀態" + }, + "cli-keys": { + "description": "從 CLI 建立、列出、輪換和撤銷 OmniRoute API 金鑰。管理供應商驗證的 OAuth 流程,並檢查金鑰範圍和到期日。", + "name": "CLI:API 金鑰" + }, + "cli-mcp": { + "description": "從 CLI 檢查 MCP 伺服器狀態、列出已註冊的工具和範圍、執行工具呼叫,以及管理 MCP 稽核記錄。", + "name": "CLI:MCP" + }, + "cli-models": { + "description": "從 CLI 查詢可用的 AI 模型、列出模型別名,以及瀏覽完整模型目錄。依供應商篩選、依能力搜尋,並解析模型名稱變體。", + "name": "CLI:模型" + }, + "cli-plugins-skills": { + "description": "從 CLI 管理 Omni Skills(列出、安裝、測試、移除)、外掛(建立、設定)和持續性記憶體(搜尋、新增、清除)。", + "name": "CLI:外掛、技能與記憶體" + }, + "cli-policy-audit": { + "description": "從 CLI 檢查稽核記錄、管理存取政策、檢視遙測資料,以及檢閱請求歷史。依事件類型、使用者或時間範圍篩選,以符合合規工作流程。", + "name": "CLI:政策與稽核" + }, + "cli-providers": { + "description": "從 CLI 管理供應商連線:列出可用/已設定的供應商、新增、測試、全部測試、驗證、輪換 API 金鑰,以及檢視各供應商指標。", + "name": "CLI:供應商" + }, + "cli-resilience": { + "description": "從 CLI 檢查和管理斷路器狀態、連線冷卻時間、配額限制和退避等級。重設卡住的供應商並設定韌性臨界值。", + "name": "CLI:韌性與配額" + }, + "cli-routing": { + "description": "從 CLI 建立、列出、更新和刪除路由組合。測試路由策略、檢查組合指標,並以互動方式設定容錯移轉鏈。", + "name": "CLI:路由與組合" + }, + "cli-serve": { + "description": "從 CLI 啟動、停止和重新啟動 OmniRoute 伺服器。管理守護程式模式、連接埠設定、自動復原、系統匣整合和儀表板開啟捷徑。", + "name": "CLI:伺服器" + }, + "cli-setup": { + "description": "透過 CLI setup 和 config 指令執行初始設定、設定全域 CLI 設定、管理環境變數、檢查更新,以及設定自動啟動。", + "name": "CLI:設定與配置" + }, + "cli-skill-collector": { + "description": "偵測已安裝的 CLI 程式設計工具(Claude Code、Codex、Cursor、Copilot、Cline 等),在 GitHub 上搜尋相符的代理技能,並透過 OmniRoute 的內建 API 將它們安裝至偵測到的工具。", + "name": "CLI:代理技能收集器" + }, + "cli-tunnel": { + "description": "從 CLI 啟動和停止隧道連線(ngrok、Cloudflare、自訂)。檢查啟用中的隧道 URL、設定驗證,以及測試外部可連線性。", + "name": "CLI:隧道" + }, + "config-codex-cli": { + "description": "逐步代理工作流程,在任何機器(Linux、macOS、Windows)上設定 OpenAI Codex CLI 以使用 OmniRoute 作為 OpenAI 相容後端。偵測作業系統和 Shell,寫入 config.toml 和 7 個命名設定檔,設定環境變數,並驗證設定。", + "name": "設定:Codex CLI" + }, + "omni-agents-a2a": { + "description": "透過 JSON-RPC 2.0 代理間協定與 OmniRoute 互動。6 個內建 A2A 技能:智慧路由、配額管理、供應商探索、成本分析、健康報告、列出能力。", + "name": "代理程式與 A2A 協定" + }, + "omni-api-keys": { + "description": "建立、列出、輪換和撤銷 OmniRoute API 金鑰。控制每個金鑰的範圍、支出限制和到期日。金鑰可控制所有代理和管理端點的存取。", + "name": "API 金鑰" + }, + "omni-auth": { + "description": "管理 API 金鑰驗證和工作階段 Token。從這裡開始,透過 Bearer Token 驗證請求、取得工作階段 Cookie,以及設定 OmniRoute API 的登入需求。", + "name": "驗證" + }, + "omni-budget": { + "description": "設定每個 API 金鑰或全域的支出限制、Token 配額和速率限制政策。檢查目前消耗量,並跨供應商執行成本控制。", + "name": "預算與速率限制" + }, + "omni-cache": { + "description": "管理 LLM 回應快取。檢視快取統計資料、清除項目、設定 TTL 策略,以及控制語義相似性快取臨界值。", + "name": "快取" + }, + "omni-cli-tools": { + "description": "管理透過 API 公開的 CLI 工具整合。列出、設定和呼叫可延伸 OmniRoute 自動化範圍的 CLI 工具外掛。", + "name": "CLI 工具" + }, + "omni-combos-routing": { + "description": "使用 14 種策略(優先級、加權、循環、自動組合等)建立和管理路由組合。設定容錯移轉鏈、測試路由結果,以及擷取組合指標。", + "name": "組合與路由" + }, + "omni-compression": { + "description": "設定 RTK(指令輸出)、Caveman(散文)和堆疊壓縮模式。管理語言套件、自訂規則,以及測試可將 Token 減少 60–90% 的提示壓縮功能。", + "name": "壓縮" + }, + "omni-context-rtk": { + "description": "設定 RTK 篩選器、上下文工程規則和上下文轉送設定。使用真實提示樣本測試壓縮,並管理上下文轉換管線。", + "name": "上下文與 RTK" + }, + "omni-db-backups": { + "description": "觸發系統備份、從備份檔案還原,以及管理 SQLite 資料庫生命週期。支援匯出、匯入和增量快照策略。", + "name": "資料庫與備份" + }, + "omni-github-skills": { + "description": "從包含 SKILL.md、CLAUDE.md、.cursorrules 及類似代理技能檔案的 GitHub 儲存庫中搜尋、評分、掃描和匯入代理技能。在 160+ 個供應商類別中探索社群技能,使用啟發式評分評估相關性,檢查惡意程式碼或硬編碼的機密,並安裝至 Hermes、Claude Code、Gemini CLI 或 OpenCode 代理目錄。", + "name": "GitHub 技能探索" + }, + "omni-inference": { + "description": "核心 OpenAI 相容推論端點:聊天完成、嵌入、圖片、音訊(TTS/STT)、審核、重新排序和 Responses API。AI 代理的主要整合面。", + "name": "推論(OpenAI 相容)" + }, + "omni-mcp": { + "description": "連線至 OmniRoute MCP 伺服器(37 個工具、3 種傳輸方式:SSE/stdio/HTTP)。涵蓋 16 個權限範圍內的路由、快取、壓縮、記憶體、技能、供應商和稽核工具。", + "name": "MCP 伺服器" + }, + "omni-models": { + "description": "查詢所有已設定供應商中可用的 AI 模型。列出模型、解析模型別名,以及瀏覽包含供應商特定變體的完整模型目錄。", + "name": "模型" + }, + "omni-providers": { + "description": "透過 REST API 管理供應商連線、API 金鑰、OAuth 流程和連線測試。列出、新增、更新、移除和測試 AI 供應商整合(OpenAI、Anthropic、Gemini 及 160 多個)。", + "name": "供應商" + }, + "omni-proxies": { + "description": "設定上游供應商請求的 HTTP/HTTPS/SOCKS 代理。設定每供應商或全域代理規則、測試連線能力,以及管理代理輪換。", + "name": "代理設定" + }, + "omni-resilience": { + "description": "監控供應商健康狀態、斷路器狀態、p50/p95/p99 延遲指標和預算守衛警示。即時檢查連線冷卻時間和模型鎖定。", + "name": "韌性與監控" + }, + "omni-settings": { + "description": "讀取和更新全域應用程式設定:系統提示、思考預算、IP 篩選器、酬載規則、組合預設值和需要登入設定。", + "name": "設定" + }, + "omni-sync-cloud": { + "description": "將 OmniRoute 設定、供應商連線和設定同步至雲端儲存或從中同步。管理雲端工作者驗證和遠端備份目標。", + "name": "雲端同步" + }, + "omni-tunnels": { + "description": "建立和管理安全隧道(ngrok、Cloudflare Tunnel、自訂),以將 OmniRoute 公開至網際網路,或與遠端代理和 CI 管線分享存取權。", + "name": "隧道" + }, + "omni-usage-logs": { + "description": "存取詳細的呼叫紀錄和使用量分析。依供應商、模型、時間範圍、狀態和成本篩選。匯出紀錄並彙總所有連線的 Token 使用量。", + "name": "使用量與紀錄" + }, + "omni-version-manager": { + "description": "安裝、啟動、停止、重新啟動和更新內嵌服務(9Router、CLIProxyAPI)。監控服務狀態、擷取紀錄,以及為僅限本機的服務端點設定自動啟動。", + "name": "版本管理員" + }, + "omni-webhooks": { + "description": "註冊、列出、測試和移除 Webhook 端點。設定事件訂閱(request.completed、provider.error、budget.exceeded 等)並管理傳遞重試。", + "name": "Webhook" + } + }, + "mcpPrompt": "將此 MCP 端點新增至您的代理,為其提供 37 個 OmniRoute 工具。", + "refresh": "重新整理" }, "freeProviderRankingsPage": { - "title": "__MISSING__:Free Provider Rankings", - "subtitle": "__MISSING__:Best free providers ranked by model ELO scores from Arena AI leaderboards", - "loading": "__MISSING__:Loading rankings...", - "errorLoading": "__MISSING__:Failed to load rankings", - "emptyState": "__MISSING__:No rankings available yet. Arena ELO data syncs on startup (daily) — check back shortly, or trigger a manual sync. Disable Arena ELO Sync in Feature Flags or set ARENA_ELO_SYNC_ENABLED=false to opt out.", - "bestModel": "__MISSING__:Best", - "allCategories": "__MISSING__:All Categories", - "categoryDefault": "__MISSING__:Default", - "categoryCoding": "__MISSING__:Coding", - "categoryReview": "__MISSING__:Review", - "categoryDocumentation": "__MISSING__:Documentation", - "categoryDebugging": "__MISSING__:Debugging", - "colRank": "__MISSING__:Rank", - "colProvider": "__MISSING__:Provider", - "colTopModel": "__MISSING__:Top Model", - "colScore": "__MISSING__:Score", - "colAvgScore": "__MISSING__:Avg Score", - "colModels": "__MISSING__:Models", - "colType": "__MISSING__:Type", - "filterConfiguredOnly": "__MISSING__:Configured only", - "filterAvailableOnly": "__MISSING__:Available only", - "filterAvailableOnlyHelp": "__MISSING__:Hide providers whose connections are all rate-limited or out of quota.", - "configuredOnly": "__MISSING__:Configured Only", - "configuredOnlyHint": "__MISSING__:Show only providers with active connections", - "noConfiguredProviders": "__MISSING__:No configured providers found. Add a provider connection first.", - "colConfigured": "__MISSING__:Status", - "typeAll": "__MISSING__:All Types", - "typeNoauth": "__MISSING__:No Signup", - "typeOauth": "__MISSING__:OAuth Login", - "typeApikey": "__MISSING__:API Key", - "sortTypeFirst": "__MISSING__:Easiest first", - "sortTypeFirstHelp": "__MISSING__:Group by signup effort (No Signup → OAuth Login → API Key), keeping quality order within each group", - "typeLegend": "__MISSING__:No Signup = zero setup · OAuth Login = sign in with your own account · API Key = bring your own key or that provider's free tier" + "title": "免費提供者排名", + "subtitle": "根據 Arena AI 排行榜的模型 ELO 分數排名的最佳免費提供者", + "loading": "正在載入排名…", + "errorLoading": "載入排名失敗", + "emptyState": "尚無排名資料。Arena ELO 資料會在啟動時同步(每日)— 請稍後再查看,或觸發手動同步。在功能標記中停用 Arena ELO 同步,或設定 ARENA_ELO_SYNC_ENABLED=false 以選擇退出。", + "bestModel": "最佳", + "allCategories": "全部分類", + "categoryDefault": "預設", + "categoryCoding": "程式設計", + "categoryReview": "審閱", + "categoryDocumentation": "文件", + "categoryDebugging": "除錯", + "colRank": "排名", + "colProvider": "提供者", + "colTopModel": "頂尖模型", + "colScore": "分數", + "colAvgScore": "平均分數", + "colModels": "模型", + "colType": "類型", + "filterConfiguredOnly": "僅顯示已設定", + "filterAvailableOnly": "僅可用", + "filterAvailableOnlyHelp": "隱藏所有連線皆被限速或配額用盡的供應商。", + "configuredOnly": "僅已設定", + "configuredOnlyHint": "僅顯示有活躍連線的提供者", + "noConfiguredProviders": "找不到已設定的提供者。請先新增提供者連線。", + "colConfigured": "狀態", + "typeAll": "所有類型", + "typeNoauth": "無需註冊", + "typeOauth": "OAuth 登入", + "typeApikey": "API 金鑰", + "sortTypeFirst": "最簡單優先", + "sortTypeFirstHelp": "按註冊難度分組(無需註冊 → OAuth 登入 → API 金鑰),在各組內保持品質順序", + "typeLegend": "無需註冊 = 零設定 · OAuth 登入 = 使用自己的帳戶登入 · API 金鑰 = 自帶金鑰或使用該供應商的免費方案" }, "discovery": { - "title": "__MISSING__:Provider Discovery", - "subtitle": "__MISSING__:Scan providers for free/unlimited access methods and review findings. Opt-in, local-only.", - "scanLabel": "__MISSING__:Provider to scan", - "scanPlaceholder": "__MISSING__:e.g. huggingchat", - "scan": "__MISSING__:Scan", - "scanning": "__MISSING__:Scanning…", - "scanQueued": "__MISSING__:Scan complete for {provider}.", - "scanFailed": "__MISSING__:Scan failed.", - "loadFailed": "__MISSING__:Failed to load discovery results.", - "localOnlyNote": "__MISSING__:This tool is local-only (loopback). Scans run from this machine and are never reachable remotely.", - "verify": "__MISSING__:Verify", - "verifyFailed": "__MISSING__:Failed to verify finding.", - "delete": "__MISSING__:Delete", - "deleteFailed": "__MISSING__:Failed to delete finding.", - "deleteTitle": "__MISSING__:Delete discovery result", - "deleteConfirm": "__MISSING__:Delete the discovery finding for {provider}? This cannot be undone.", - "emptyTitle": "__MISSING__:No discovery results yet", - "emptyDescription": "__MISSING__:Run a scan above to look for free access methods on a provider.", - "risk": "__MISSING__:Risk", - "method": "__MISSING__:Method", - "auth": "__MISSING__:Auth", - "feasibility": "__MISSING__:Feasibility", - "models": "__MISSING__:Models" + "title": "提供者探索", + "subtitle": "掃描提供者以尋找免費/無限制的存取方式並審查結果。自選啟用,僅限本地。", + "scanLabel": "要掃描的提供者", + "scanPlaceholder": "例如 huggingchat", + "scan": "掃描", + "scanning": "正在掃描…", + "scanQueued": "{provider} 掃描完成。", + "scanFailed": "掃描失敗。", + "loadFailed": "無法載入探索結果。", + "localOnlyNote": "此工具僅限本地(回送)。掃描從本機執行,無法遠端存取。", + "verify": "驗證", + "verifyFailed": "驗證結果失敗。", + "delete": "刪除", + "deleteFailed": "刪除結果失敗。", + "deleteTitle": "刪除探索結果", + "deleteConfirm": "刪除 {provider} 的探索結果?此操作無法復原。", + "emptyTitle": "尚無探索結果", + "emptyDescription": "在上方執行掃描以尋找提供者的免費存取方式。", + "risk": "風險", + "method": "方法", + "auth": "驗證", + "feasibility": "可行性", + "models": "模型" }, "reasoningRouting": { "title": "Reasoning routing policies", @@ -9196,11 +10933,1159 @@ "set": "Set fixed value" } }, + "breadcrumbs": { + "a2a": "A2A", + "accessTokens": "存取權杖", + "acpAgents": "ACP 代理", + "activity": "活動", + "add": "新增", + "advanced": "進階", + "agentBridge": "代理橋接", + "agentSkills": "代理技能", + "aggressive": "積極", + "ai": "AI 設定", + "analytics": "分析", + "apiEndpoints": "API 端點", + "apiKeys": "API 金鑰", + "apiManager": "API 管理員", + "appearance": "外觀", + "ariaLabel": "麵包屑", + "audit": "稽核", + "auditLog": "稽核日誌", + "batch": "批次", + "budget": "預算", + "cache": "快取", + "caveman": "Caveman", + "ccr": "CCR", + "changelog": "變更日誌", + "chaos": "混沌", + "cliAgents": "CLI 代理", + "cliCode": "CLI Code", + "cloudAgents": "雲端代理", + "comboHealth": "組合健康狀態", + "combos": "組合", + "compression": "壓縮", + "console": "主控台", + "context": "上下文", + "costs": "成本", + "dashboard": "儀表板", + "discovery": "探索", + "edit": "編輯", + "endpoint": "端點", + "evals": "評估", + "featureFlags": "功能開關", + "files": "檔案", + "freeProviderRankings": "免費供應商排名", + "freeTiers": "免費方案", + "gamification": "遊戲化", + "general": "一般", + "headroom": "餘量", + "health": "健康狀態", + "home": "首頁", + "leaderboard": "排行榜", + "limits": "限制", + "lite": "精簡", + "live": "即時", + "llmlingua": "LLMLingua", + "logger": "記錄器", + "logs": "日誌", + "mcp": "MCP", + "media": "媒體", + "models": "模型", + "new": "新增", + "omniglyph": "OmniGlyph", + "playground": "遊樂場", + "plugins": "外掛程式", + "pricing": "定價", + "profile": "個人檔案", + "providerStats": "供應商統計", + "providers": "供應商", + "quota": "配額", + "quotaShare": "配額分享", + "relay": "轉發", + "resilience": "韌性", + "routing": "路由", + "rtk": "RTK", + "runtime": "執行時期", + "search": "搜尋", + "searchTools": "搜尋工具", + "security": "安全性", + "services": "服務", + "sessionDedup": "工作階段去重", + "settings": "設定", + "sidebar": "側邊欄", + "studio": "工作室", + "tokens": "Token", + "tools": "工具", + "trafficInspector": "流量檢查器", + "translator": "翻譯器", + "ultra": "極致", + "usage": "用量", + "utilization": "使用率", + "webhooks": "Webhook" + }, + "changelogPage": { + "announcementsLoadFailed": "無法載入公告。請稍後再試。", + "changelogLoadFailed": "無法載入變更日誌。請稍後再試。", + "changelogTab": "變更日誌", + "learnMore": "了解更多", + "loading": "正在載入變更日誌…", + "newsTab": "新聞", + "noAnnouncements": "目前暫無新公告。", + "retry": "重試", + "viewFullHistory": "在 GitHub 上檢視完整歷史" + }, + "chaosConfig": { + "addProvider": "新增供應商", + "availableProviders": "可用供應商({count})", + "configError": "無法儲存混沌設定", + "configReset": "重設為預設值", + "configSaved": "混沌設定已成功儲存", + "disabled": "已停用", + "enableChaos": "啟用混沌模式", + "enableChaosDesc": "允許已啟用混沌模式的 API 金鑰使用此功能", + "enabled": "已啟用", + "keyPermission": "混沌模式存取", + "keyPermissionDesc": "允許此 API 金鑰使用混沌模式(多模型並行執行)", + "loadingProviderModels": "正在載入供應商…", + "maxTokens": "最大 Token 數", + "maxTokensDesc": "每個模型回應的最大 Token 數。數值越高,成本越高且耗時越久。", + "mode": "預設模式", + "modeCollaborative": "協作", + "modeCollaborativeDesc": "模型串聯輸出——每個模型會看到前一個結果", + "modeParallel": "並行", + "modeParallelDesc": "所有模型同時執行——最快結果", + "modelId": "模型", + "modelIdPlaceholder": "模型 ID(選填)", + "noProviderOverrides": "無覆寫——所有啟用的供應商將使用其預設模型參與", + "off": "關閉", + "on": "開啟", + "pageSubtitle": "在同一任務上以並行或協作方式執行多個 AI 模型", + "pageTitle": "混沌模式", + "providerId": "供應商", + "providerIdPlaceholder": "供應商 ID(輸入或選取)", + "providerOverrides": "供應商覆寫", + "providerOverridesDesc": "為混沌模式選取每個供應商的特定模型", + "removeProvider": "移除", + "saveConfig": "儲存設定", + "systemPrompt": "系統提示(選填)", + "systemPromptDesc": "針對所有混沌模式模型實例的自訂指示", + "systemPromptPlaceholder": "選填:覆寫預設混沌模式系統提示…", + "testButton": "測試混沌模式", + "testTask": "寫一首關於人工智慧的短詩", + "timeout": "逾時(毫秒)", + "timeoutDesc": "每次模型呼叫的最長時間(5000-600000 毫秒)" + }, + "cloudSyncStatus": { + "connected": "已連線", + "disabled": "已停用", + "disconnected": "已中斷連線", + "error": "同步錯誤", + "lastSync": "遠端設定同步 {status} — 上次同步:{time}", + "off": "同步關閉", + "statusLabel": "遠端設定同步狀態:{status}", + "synced": "已同步", + "syncing": "正在同步..." + }, + "comboControl": { + "accountShort": "帳戶 {id}", + "active": "啟用中", + "averageResponseTime": "平均回應時間", + "backToCombos": "返回 Combos", + "callLogs": "呼叫記錄", + "comboHealth": "Combo 健康狀態", + "comboReference": "Combo 參考", + "configuredTargets": "已設定的目標", + "configuredTargetsDescription": "已儲存的 combo 步驟,並在可用時補充對應的健康資料。", + "costs": "成本", + "description": " 的路由行為、健康狀態、配額、執行時期指標與近期決策的集中唯讀檢視。", + "disabled": "已停用", + "dynamic": "動態", + "editInCombos": "在 Combos 中編輯", + "healthReason": { + "comboHealthy": "Combo 看起來健康", + "elevatedFallbackRate": "降級回退率偏高", + "highFallbackRate": "降級回退率高", + "lowSuccessRate": "成功率偏低", + "noRecentTraffic": "近期無 combo 流量", + "quotaExhausted": "至少一個配額已耗盡", + "quotaGettingLow": "配額即將不足", + "quotaNearlyExhausted": "配額幾乎耗盡", + "successBelowTarget": "成功率低於目標", + "trafficHighlySkewed": "流量分布極度不均衡" + }, + "healthReasons": "健康原因", + "keyShort": "金鑰 {id}", + "latency": "延遲", + "loadFailed": "載入 Combo Control Center 失敗", + "modelTarget": "模型目標", + "nestedCombo": "巢狀 combo", + "noConfiguredTargets": "尚未設定任何目標。", + "noQuotaSnapshots": "此 combo 時間視窗內無配額快照。", + "noRecentLogs": "找不到近期的 combo 呼叫記錄。", + "noResolvedTargetHealth": "尚無已解析的目標健康資料。", + "noRuntimeConfig": "無自訂執行時期設定。", + "overview": "總覽", + "overviewDescription": "此 combo 的策略、執行時期狀態與控制連結。", + "playground": "遊樂場", + "providerAccountTelemetry": "提供者/帳戶遙測", + "providers": "提供者", + "quickLinks": "快速連結", + "quota": "配額", + "quotaDistribution": "配額與分布", + "rangeWindow": "{range} 視窗", + "recentDecisions": "近期路由決策", + "recentDecisionsDescription": "依此 combo 名稱篩選的近期呼叫記錄。開啟 Analytics 以取得完整解釋。", + "refresh": "重新整理", + "requests": "請求", + "resolvedTargetMetrics": "{requests} 請求 · {success} 成功 · {latency} · 配額 {quota}", + "resolvedTargets": "已解析的執行時期目標", + "resolvedTargetsDescription": "巢狀 combo 解析後扁平化的目標及目標層級指標。", + "runtimeConfig": "執行時期設定", + "runtimeConfigDescription": "為此 combo 選取進階設定。", + "runtimeHealthBlend": "執行時期/健康混合", + "state": { + "critical": "嚴重", + "healthy": "健康", + "idle": "閒置", + "warning": "需要留意" + }, + "stepShort": "步驟 {id}", + "strategy": "策略", + "success": "成功", + "targetCounts": "{configured} 已設定 · {resolved} 已解析", + "targets": "目標", + "title": "Combo Control Center", + "unavailable": "Combo Control Center 無法使用", + "unknown": "未知", + "unknownModel": "未知模型", + "unknownProvider": "未知提供者", + "usageSkew": "使用偏差", + "weight": "權重 {value}%", + "worstQuota": "最差配額" + }, + "compressionEngineConfig": { + "averageSavings": "平均節省", + "compressed": "已壓縮", + "compressedTokens": "已壓縮 Token", + "compressionSettings": "壓縮設定", + "configuration": "設定", + "diff": "差異", + "diffLabels": { + "added": "新增", + "change": "變更", + "modified": "已修改", + "removed": "已移除" + }, + "engineFields": { + "llmlingua": { + "compressionRate": { + "label": "壓縮率(保留比例)" + }, + "modelPath": { + "label": "模型路徑(離線覆寫)" + } + } + }, + "engineNotFound": "找不到引擎「{engine}」。", + "engines": { + "aggressive": { + "description": "摘要、工具結果壓縮與漸進式老化。", + "name": "積極" + }, + "ccr": { + "description": "針對重複內容區塊的內容定址檢索標記。", + "name": "CCR" + }, + "headroom": { + "description": "同質 JSON 陣列的無損表格壓縮,具備明確的行數標記。", + "name": "Headroom SmartCrusher" + }, + "lite": { + "description": "快速空白字元縮減、工具結果與圖片 URL 縮減。", + "name": "精簡" + }, + "llmlingua": { + "description": "基於 ONNX 的語意 Token 分類。僅壓縮散文;程式碼區塊與保留結構不受影響。在模型或工作者錯誤時會以開放失敗模式處理。", + "name": "LLMLingua-2(語意修剪)" + }, + "session-dedup": { + "description": "跨輪次的重複區塊去重,具備可復原的內容標記。", + "name": "Session Dedup" + }, + "ultra": { + "description": "啟發式 Token 修剪,具備選用的本地 SLM 降級回退。", + "name": "極致" + } + }, + "fields": { + "compressionRate": { + "label": "壓縮率" + }, + "fuzzy": { + "description": "選擇將與較早訊息至少有約 85% 相似度的完整訊息,替換為可復原的 CCR 標記。", + "label": "模糊近似重複去重" + }, + "intensity": { + "label": "強度" + }, + "maxTokensPerMessage": { + "label": "每則訊息的最大 Token 數" + }, + "minBlockChars": { + "description": "字尾區塊做為去重候選的最小字元數。", + "label": "最小區塊字元數" + }, + "minChars": { + "description": "區塊做為 CCR 候選的最小字元數。", + "label": "最小區塊字元數" + }, + "minMessageLength": { + "label": "最小訊息長度" + }, + "minRows": { + "description": "觸發表格壓縮所需的同質 JSON 陣列最小行數。預設值:8。", + "label": "最小壓縮行數" + }, + "minSavingsThreshold": { + "label": "最小節省門檻" + }, + "minScoreThreshold": { + "label": "最小分數門檻" + }, + "minTokens": { + "label": "最小 Token 數(下限)" + }, + "model": { + "label": "模型" + }, + "modelPath": { + "label": "模型路徑" + }, + "preserveSystemPrompt": { + "label": "保留系統提示詞" + }, + "retrievalRampFactor": { + "description": "控制頻繁檢索的區塊抵抗壓縮的強度。每次先前的檢索會線性提高有效最小區塊大小;設為 1 則停用此機制。", + "label": "檢索斜坡因子 (H8)" + }, + "slmFallbackToAggressive": { + "label": "降級回退至積極模式" + }, + "summarizerEnabled": { + "label": "啟用摘要器" + } + }, + "globalSettingsOnly": "此圖層由全域設定管理;此處尚無個別引擎設定可儲存。", + "last7Days": "過去 7 天", + "loadFailed": "無法載入引擎資訊。", + "loading": "載入中…", + "noAdditionalConfiguration": "無其他設定。", + "noDataYet": "尚無資料", + "options": { + "intensity": { + "full": "完整", + "lite": "精簡", + "ultra": "極致" + }, + "model": { + "bert-base": "BERT-base(710 MB,準確度較高)", + "tinybert": "TinyBERT(57 MB,快速 — 預設)" + } + }, + "original": "原始", + "originalTokens": "原始 Token", + "panelPointerPrefix": "在「壓縮設定」中開啟或關閉此圖層並設定其等級", + "panelPointerSuffix": "。此頁面僅編輯其詳細設定。", + "preview": "預覽", + "previewFailed": "預覽失敗。", + "previewInput": "預覽輸入", + "previewSample": "敏捷的棕色狐狸跳過懶惰的狗。這是一則用於預覽壓縮的範例訊息。它包含足夠的文字來顯示有意義的 Token 節省效果。", + "processing": "處理中…", + "runs": "執行次數", + "save": "儲存", + "saveFailed": "設定儲存失敗。", + "saving": "儲存中…", + "savings": "節省", + "tokensSaved": "已節省 Token" + }, + "compressionStudio": { + "activeCombined": "在組合流程中啟用", + "bytes": "位元組", + "canvas": "畫布", + "capReached": "已達上限", + "cockpitView": "駕駛艙檢視", + "combined": "組合", + "combinedFlow": "組合流程", + "compareTab": "比較", + "diff": "差異", + "eachLayer": "各圖層分開", + "encoder": "編碼器", + "encoderComparison": "編碼器 A/B — {count} 個陣列", + "encoderWinner": "勝出:{winner}", + "engine": "引擎", + "enterJudgeModel": "輸入評判模型", + "error": "錯誤", + "fidelity": "保真度", + "fuzzyDedup": "模糊去重(近似重複 → CCR)", + "inlineView": "行內", + "input": "輸入", + "inputPlaceholder": "貼上提示詞、工具輸出或上下文…", + "judgeModel": "評判模型", + "judgeModelPlaceholder": "例如 claude-haiku", + "laneRejected": "已拒絕:{reason}", + "liveDataHint": "即時資料會透過 WebSocket 壓縮通道送達。", + "loadAb": "載入 A/B", + "maxCostUsd": "美元上限", + "noRun": "無可用的壓縮執行記錄。", + "output": "輸出", + "outputTokensShort": "輸出 Token", + "pause": "暫停", + "pauseReplay": "暫停重播", + "playReplay": "播放重播", + "playTab": "播放", + "protectSensitive": "保護敏感內容(風險閘道)", + "provider": "提供者", + "quantumLock": "QuantumLock(穩定快取前綴)", + "replay": "重播", + "replayDone": "重播完成", + "requiresOnnx": "需要 ONNX 模型", + "resetReplay": "重設重播", + "retention": "保留", + "run": "執行", + "running": "執行中…", + "saliencyHeatmap": "顯著性熱圖", + "savings": "節省", + "skipped": "跳過", + "spent": "已花費 ${spent} / ${cap}", + "splitComingSoon": "分割檢視(即將推出)", + "stepProgress": "步驟 {current}/{total}", + "tokenCount": "{count, number} 個 Token", + "tokenShort": "tok", + "tokensCl100k": "Token(cl100k)", + "verifyAll": "全部驗證", + "verifyFidelity": "驗證保真度(拒絕任何破壞內容的圖層)", + "verifying": "驗證中…", + "waterfall": "瀑布圖" + }, + "embeddedServices": { + "apiKey": "API 金鑰", + "apiKeyDescription": "OmniRoute 用來驗證 {name} 的金鑰", + "autoStart": "自動啟動", + "autoStartDescription": "OmniRoute 啟動時自動啟動 {name}", + "availableModels": "可用模型", + "cancel": "取消", + "clear": "清除", + "cliproxyUrl": "CLIProxyAPI URL", + "description": "隨需管理的本機引擎 — CLIProxyAPI、9Router、Mux 和 Bifrost。僅可透過迴路位址存取。", + "download": "下載", + "enableFallback": "啟用備援", + "externalBadge": "外部管理", + "externalDescription": "OmniRoute 在 {host}:{port} 找到 9Router。其生命週期、日誌、更新和服務金鑰仍由外部安裝管理。", + "externalTitle": "偵測到外部 9Router", + "fallbackCodes": "備援狀態碼(逗號分隔)", + "fallbackRouting": "備援路由", + "fallbackRoutingDescription": "透過 CLIProxyAPI 重試失敗的提供者請求", + "filterLogs": "篩選日誌…", + "hide": "隱藏", + "install": "安裝", + "install9Router": "安裝 9Router", + "install9RouterDescription": "透過 npm 下載並安裝 9Router。需要約 500 MB 磁碟空間。", + "installationFailed": "安裝失敗(HTTP {status})", + "installationSucceeded": "9Router 安裝成功。正在啟動…", + "installing": "安裝中…", + "invalidUrl": "無效的 URL — 必須以 http:// 或 https:// 開頭", + "keyAutoHide": "金鑰將在 30 秒後自動隱藏。", + "keyRevealEmpty": "顯示失敗:未傳回金鑰", + "keyRevealFailed": "顯示金鑰失敗", + "keyRotateFailed": "金鑰輪換失敗", + "keyRotated": "金鑰已輪換 — {name} 已重新啟動以套用新金鑰", + "logStreamFailed": "無法串流 {name} 的日誌。請檢查本機存取權限和服務狀態。", + "mappingInvalidJson": "無效的 JSON。", + "mappingMustBeObject": "對應必須是 JSON 物件,而非陣列或基本型別值。", + "mappingSaveFailed": "對應儲存失敗", + "mappingSaved": "對應已儲存", + "mappingValueMustBeString": "金鑰「{key}」的值必須是字串。", + "modelMapping": "模型對應", + "modelMappingDescription": "將 OmniRoute 模型 ID 對應至 CLIProxyAPI 模型 ID(例如,\"gpt-4o\": \"openai-gpt-4o\")", + "modelMappingEditor": "模型對應 JSON 編輯器", + "modelsDiscovered": "已發現 {count} 個模型", + "modelsLoadFailed": "載入模型失敗", + "modelsLoading": "載入中…", + "networkInstallFailed": "網路錯誤 — 無法連線至安裝端點", + "next": "下一步", + "noLogs": "尚無日誌輸出。", + "noModels": "找不到模型。選擇「立即重新整理」以從執行中的服務同步。", + "openNewTab": "在新分頁中開啟", + "pageOf": "第 {page} 頁,共 {total} 頁", + "pause": "暫停", + "port": "連接埠 {port}", + "previous": "上一頁", + "providerExposure": "提供者曝光", + "providerExposureDescription": "將 9Router 模型以 9router/ 前綴形式公開為路由目標。", + "providerExposureHint": "啟用後,發現的模型會出現在 OmniRoute 各處的提供者選擇器中。", + "providerExposureLabel": "以 9router/… 形式公開", + "providerExposureNetworkFailed": "網路錯誤 — 無法更新提供者曝光設定", + "providerExposureUpdateFailed": "更新失敗(HTTP {status})", + "refreshNow": "立即重新整理", + "refreshing": "重新整理中…", + "restart": "重新啟動", + "restarting": "重新啟動中…", + "resume": "繼續", + "reveal": "顯示", + "revealConfirm": "顯示 API 金鑰將記錄在稽核軌跡中。是否繼續?", + "revealTitle": "顯示 API 金鑰", + "revealing": "顯示中…", + "rotateKey": "輪換金鑰", + "rotating": "輪換中…", + "save": "儲存", + "saveFailed": "設定儲存失敗", + "saved": "已儲存", + "servicePort": "連接埠", + "servicePortHint": "OmniRoute 和 9Router 必須使用不同的連接埠。9Router 的預設連接埠為 20130。", + "start": "啟動", + "starting": "啟動中…", + "stateError": "錯誤", + "stateNotInstalled": "未安裝", + "stateRunning": "執行中", + "stateStarting": "啟動中", + "stateStopped": "已停止", + "stateStopping": "停止中", + "stateUnknown": "未知", + "stop": "停止", + "stopping": "停止中…", + "title": "內嵌服務", + "unavailable": "無法使用", + "update": "更新", + "updating": "更新中…", + "version": "版本", + "versionHint": "輸入「latest」或特定版本(例如 1.2.3)。", + "webUi": "9Router Web UI" + }, + "featureFlags": { + "activeCount": "{count} 個啟用中", + "cancel": "取消", + "categories": { + "all": "全部", + "cli": "CLI", + "health": "健康狀態", + "network": "網路", + "policies": "政策", + "requiresRestart": "需要重新啟動", + "runtime": "執行時期", + "security": "安全性" + }, + "categoryLabel": "類別:{category}", + "caution": "注意", + "confirmReset": "確認重設", + "confirmResetOverrides": "重設所有 {count} 個資料庫覆寫?", + "confirmRestart": "確認重新啟動", + "danger": "危險", + "dbOverrideCount": "{count} 個資料庫覆寫", + "definitions": { + "ALLOW_API_KEY_REVEAL": { + "description": "允許已驗證的儀表板使用者顯示儲存的 API 金鑰,而不僅是看到遮罩值。" + }, + "ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE": { + "description": "允許每個相容性節點的多重連線。" + }, + "ARENA_ELO_SYNC_ENABLED": { + "description": "定期同步 Arena AI 排行榜的 ELO 資料以進行模型智慧排名。" + }, + "CLI_COMPAT_ALL": { + "description": "啟用所有 CLI 用戶端的相容模式。" + }, + "ENABLE_CC_COMPATIBLE_PROVIDER": { + "description": "啟用 Claude Code 相容的提供者模式。" + }, + "ENABLE_TLS_FINGERPRINT": { + "description": "啟用 TLS 指紋隱匿模式。" + }, + "INJECTION_GUARD_MODE": { + "description": "設定提示注入防護模式。" + }, + "INPUT_SANITIZER_ENABLED": { + "description": "啟用所有請求的輸入清理功能。" + }, + "MITM_DISABLE_TLS_VERIFY": { + "description": "停用 MITM 代理的 TLS 憑證驗證。" + }, + "MODELS_CATALOG_PREFIX_MODE": { + "description": "控制 /v1/models 中的模型 ID 前綴:dual 同時發出別名與標準前綴,alias 僅發出簡短前綴,canonical 僅發出完整的提供者 ID。" + }, + "MODEL_ALIAS_COMPAT_ENABLED": { + "description": "啟用模型別名相容層。" + }, + "MODEL_CATALOG_INCLUDE_NAMES": { + "description": "在 /v1/models 回應中包含顯示友善的名稱欄位。對於僅接受模型 ID 的用戶端請停用此項。" + }, + "OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS": { + "description": "允許 localhost、LAN 和私有 IP 範圍的提供者。這是本機 OpenAI 相容模型所需的設定,且預設為啟用。雲端中繼資料端點(如 169.254.169.254)仍維持封鎖。" + }, + "OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS": { + "description": "允許指向私有或內部網路的提供者 URL。" + }, + "OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES": { + "description": "在提供者-模型同步後,從即時目錄重新產生 ~/.claude/profiles//settings.json 設定檔。這絕不會更改作用中或預設的 Claude 配置,且預設為關閉。" + }, + "OMNIROUTE_AUTO_SYNC_CODEX_PROFILES": { + "description": "在提供者-模型同步後,從即時目錄重新產生 ~/.codex/*.config.toml 設定檔。這絕不會更改作用中或預設的 Codex 配置,且預設為關閉。" + }, + "OMNIROUTE_CODEX_WS_ENABLED": { + "description": "允許 Codex 透過 WebSocket 使用 Responses。停用時,Codex 會回退至 HTTP Responses。" + }, + "OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK": { + "description": "當代理可達性檢查失敗時,允許 OAuth 和提供者驗證流程繞過固定代理。預設為關閉,因為這可能變更帳戶的出口 IP。" + }, + "OMNIROUTE_DISABLE_BACKGROUND_SERVICES": { + "description": "停用所有背景服務,包括配額重新整理和同步。" + }, + "OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK": { + "description": "停用本機執行個體的健康檢查端點。" + }, + "OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK": { + "description": "停用權杖驗證的健康檢查。" + }, + "OMNIROUTE_EMERGENCY_FALLBACK": { + "description": "將預算耗盡的請求路由至緊急免費備用提供者和模型。" + }, + "OMNIROUTE_ENABLE_LIVE_WS": { + "description": "在匯入時於回送埠 20132 啟動即時儀表板 WebSocket 伺服器。將旗標設為 0 或 false 以停用。LAN 暴露還需要 LIVE_WS_HOST=0.0.0.0 和 LIVE_WS_ALLOWED_ORIGINS。" + }, + "OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS": { + "description": "在執行時期啟用背景任務處理。" + }, + "OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS": { + "description": "壓縮 MCP 工具描述以減少 token 用量。" + }, + "OMNIROUTE_MCP_ENFORCE_SCOPES": { + "description": "強制執行 MCP 工具存取的作用域限制。" + }, + "OMNIROUTE_RTK_TRUST_PROJECT_FILTERS": { + "description": "信任專案層級的 RTK 篩選器而不進行驗證。" + }, + "ONEPROXY_ENABLED": { + "description": "啟用透過 1proxy 的請求代理。" + }, + "OUTBOUND_SSRF_GUARD_ENABLED": { + "description": "封鎖對私有或內部 IP 範圍的對外請求。" + }, + "PII_REDACTION_ENABLED": { + "description": "從請求中編輯個人識別資訊(PII)。" + }, + "PII_RESPONSE_SANITIZATION": { + "description": "清理提供者回應中的個人識別資訊(PII)。" + }, + "PII_RESPONSE_SANITIZATION_MODE": { + "description": "選擇如何處理回應中的 PII:redact 將其取代,warn 僅記錄它,block 拒絕回應,off 則停用清理。" + }, + "PRICING_SYNC_ENABLED": { + "description": "自動同步定價資料。PRICING_SYNC_ENABLED 環境變數也必須為 true。" + }, + "PROXY_AUTO_SELECT_ENABLED": { + "description": "當連線未指定代理時,自動選取第一個可用的註冊表代理。預設為關閉,因為否則一個註冊表代理會成為所有流量的全域備援(#3332)。" + }, + "RATE_LIMIT_AUTO_ENABLE": { + "description": "根據使用模式自動啟用速率限制。" + }, + "REQUIRE_API_KEY": { + "description": "要求所有傳入請求都需要 API 金鑰。" + }, + "RESPONSES_PASSTHROUGH_DROP_COMMENTARY": { + "description": "在將 Responses API 穿透串流轉發至用戶端之前,移除內部註解階段的輸出項目。停用此旗標以接收原始上游註解。" + }, + "SKILLS_SANDBOX_NETWORK_ENABLED": { + "description": "在技能沙盒中啟用網路存取。" + }, + "STREAM_RECOVERY_ENABLED": { + "description": "在任何回應位元組到達用戶端之前,透明地重試被截斷的上游 SSE 串流。" + }, + "STREAM_RECOVERY_MIDSTREAM_ENABLED": { + "description": "允許串流復原在位元組已到達用戶端後再次請求回應並將其拼接。" + }, + "TOOL_POLICY_MODE": { + "description": "設定工具使用政策的強制執行模式。" + } + }, + "enumValues": { + "alias": "別名", + "block": "封鎖", + "canonical": "標準", + "disabled": "已停用", + "dual": "雙重", + "off": "關閉", + "redact": "編輯", + "warn": "警告" + }, + "inactiveCount": "{count} 個未啟用", + "loadFailed": "無法載入功能旗標", + "noSearchResults": "沒有符合搜尋條件的旗標", + "requiresRestart": "需要重新啟動", + "reset": "重設", + "resetAllOverrides": "重設所有覆寫", + "resetFlag": "將 {label} 重設為預設值", + "resetOverridesFailed": "重設覆寫失敗", + "resetOverridesFailedHttp": "重設覆寫失敗:HTTP {status}", + "resetting": "重設中...", + "restartFailed": "重新啟動失敗", + "restartFailedHttp": "重新啟動失敗:HTTP {status}", + "restartRequiredCount": "{count} 項變更需要重新啟動伺服器才能生效。", + "restartRequiredDescription": "這些旗標僅在程序重新載入後才生效。立即重新啟動或繼續編輯——待處理的旗標會保留在佇列中,直到您確認。", + "restartServer": "重新啟動伺服器", + "restartViewDescription": "這些旗標僅在伺服器重新啟動後才生效。您可以像其他旗標一樣切換它們——變更會立即持久化,但新值僅在程序啟動時讀取。使用上方的重新啟動伺服器橫幅來套用。", + "restarting": "正在重新啟動…", + "retry": "重試", + "searchPlaceholder": "搜尋旗標...", + "source": "來源", + "title": "功能旗標", + "updateFailed": "更新旗標失敗", + "updateFailedHttp": "更新旗標失敗:HTTP {status}" + }, + "freeBudget": { + "boost": "一次性 $10 OpenRouter 充值即可解鎖約 {tokens}/月(50 → 1000 請求/天)", + "credit": "{tokens} 額度", + "firstMonth": "第一個月(+ 額度)", + "freeType": { + "creditMonthly": "額度/月", + "daily": "每日", + "discontinued": "已停產", + "keyless": "無需金鑰", + "monthly": "每月", + "signupCredit": "註冊額度", + "uncapped": "無上限" + }, + "hideTosRestricted": "隱藏受 ToS 限制的", + "model": "模型", + "modelName": "模型名稱", + "provider": "提供者", + "remaining": "剩餘 {remaining} · 佔 {total} 的 {percent}%", + "segmentHint": "每個區段 = 一個免費池 · 池間去重、誠實計數(無膨脹的速率限制上限)。", + "sort": "排序", + "steadyMonth": "穩定/月", + "title": "免費代幣預算", + "tokensMonth": "代幣/月", + "tosRestricted": "{count} 個模型被標記為受 ToS 限制——由您決定", + "tosTitle": { + "avoid": "受 ToS 限制——請審閱條款", + "caution": "注意——個人使用/代理條款", + "ok": "通常較為寬鬆" + }, + "type": "類型", + "uncapped": "永久免費,無公佈上限(有速率限制)——真實存取,不計入標題數字:", + "usedThisMonth": "本月已使用" + }, + "gamification": { + "badges": { + "architect": { + "criteria": "建立 3 條組合路由。", + "description": "已建立 3 條組合路由", + "name": "架構師" + }, + "bug-hunter": { + "criteria": "回報 5 個有效的問題。", + "description": "已回報 5 個問題", + "name": "錯誤獵人" + }, + "community-hero": { + "criteria": "與其他使用者分享總共 1,000,000 個代幣。", + "description": "已與他人分享 1,000,000 個代幣", + "name": "社群英雄" + }, + "community-leader": { + "criteria": "在任何排行榜上進入前 10 名。", + "description": "已在任何排行榜上進入前 10 名", + "name": "社群領袖" + }, + "contributor": { + "criteria": "有 1 個 pull request 被合併至 OmniRoute。", + "description": "已合併 1 個 pull request", + "name": "貢獻者" + }, + "daily-user": { + "criteria": "連續使用 OmniRoute 3 天。", + "description": "連續活躍 3 天", + "name": "每日用戶" + }, + "early-adopter": { + "criteria": "在遊戲化功能上線後的第一個月內加入。", + "description": "在遊戲化功能上線的第一個月內加入", + "name": "早期採用者" + }, + "explorer": { + "criteria": "使用至少 5 個不同的 AI 提供者。", + "description": "已使用 5 個不同的提供者", + "name": "探索者" + }, + "first-token": { + "criteria": "完成您透過 OmniRoute 的第一個 API 請求。", + "description": "已發出您的第一個 API 請求", + "name": "第一個代幣" + }, + "generous": { + "criteria": "與其他使用者分享總共 1,000 個代幣。", + "description": "已與他人分享 1,000 個代幣", + "name": "慷慨" + }, + "monthly-master": { + "criteria": "連續使用 OmniRoute 30 天。", + "description": "連續活躍 30 天", + "name": "月度大師" + }, + "philanthropist": { + "criteria": "與其他使用者分享總共 10,000 個代幣。", + "description": "已與他人分享 10,000 個代幣", + "name": "慈善家" + }, + "polyglot": { + "criteria": "使用至少 10 個不同的 AI 模型。", + "description": "已使用 10 個不同的模型", + "name": "多語言高手" + }, + "resilient": { + "criteria": "連續 7 天維持 100% 正常運行時間。", + "description": "已維持 100% 正常運行時間 7 天", + "name": "韌性" + }, + "secret-badge": { + "criteria": "完成隱藏成就以揭曉此徽章。", + "description": "一項隱藏成就等待著您...", + "name": "???" + }, + "speedster": { + "criteria": "在 100 個請求中保持平均延遲低於 500 毫秒。", + "description": "在 100 個請求中保持平均延遲低於 500 毫秒", + "name": "速度之星" + }, + "token-consumer": { + "criteria": "透過 OmniRoute 完成 1,000 個 API 請求。", + "description": "已發出 1,000 個 API 請求", + "name": "代幣消費者" + }, + "token-machine": { + "criteria": "透過 OmniRoute 完成 10,000 個 API 請求。", + "description": "已發出 10,000 個 API 請求", + "name": "代幣機器" + }, + "token-santa": { + "criteria": "與其他使用者分享總共 100,000 個代幣。", + "description": "已與他人分享 100,000 個代幣", + "name": "代幣聖誕老人" + }, + "token-whale": { + "criteria": "透過 OmniRoute 完成 100,000 個 API 請求。", + "description": "已發出 100,000 個 API 請求", + "name": "代幣巨鯨" + }, + "unstoppable": { + "criteria": "連續使用 OmniRoute 365 天。", + "description": "連續活躍 365 天", + "name": "勢不可擋" + }, + "weekly-warrior": { + "criteria": "連續使用 OmniRoute 7 天。", + "description": "連續活躍 7 天", + "name": "每週戰士" + } + }, + "badgesTitle": "徽章({earned}/{total})", + "categories": { + "contribution": "貢獻", + "rare": "稀有成就", + "sharing": "分享", + "streak": "連續記錄", + "usage": "使用量" + }, + "category": "類別", + "dayStreak": "連續 {count} 天", + "earnedDate": "獲得於 {date}", + "earnedOn": "於 {date} 獲得", + "hiddenBadge": "隱藏成就", + "leaderboardEmpty": "此範圍尚無任何條目。開始使用 OmniRoute 即可出現在排行榜上!", + "leaderboardLoadFailed": "無法載入排行榜(HTTP {status})", + "leaderboardScopes": { + "allTime": "全部時間", + "monthly": "每月", + "tokensShared": "已分享代幣", + "weekly": "每週" + }, + "levelProgress": "等級 {current} → {next}", + "levelTitles": { + "beginner": "初學者", + "expert": "專家", + "explorer": "探索者", + "legend": "傳奇", + "master": "大師" + }, + "maintainStreak": "每天持續使用 OmniRoute 以維持您的連續記錄!", + "name": "名稱", + "noBadges": "尚無可用的徽章。", + "points": "點數", + "profileLoadFailed": "無法載入個人檔案資料", + "rank": "排名", + "rarities": { + "common": "普通", + "epic": "史詩", + "legendary": "傳說", + "rare": "稀有", + "uncommon": "不凡" + }, + "scope": "範圍", + "score": "分數", + "tierLabel": "階層:{tier}", + "tiers": { + "bronze": "青銅", + "diamond": "鑽石", + "gold": "黃金", + "platinum": "白金", + "silver": "白銀" + }, + "tokensShared": "已分享代幣", + "totalXpEarned": "總共獲得 {count} 經驗值" + }, + "noAuthProvider": { + "accountDescription": "即可使用 — 無需註冊。可新增帳號進行速率限制輪換。", + "accountName": "{provider} 帳號 {number}", + "accounts": "帳號({count})", + "addAccount": "新增帳號", + "adding": "正在新增...", + "autoGeneratedAccount": "使用自動產生的帳號。請選取「{addLabel}」以進行速率限制輪換。", + "cancel": "取消", + "configureProxy": "設定代理", + "createConnectionFailed": "建立連線失敗", + "custom": "自訂", + "description": "此提供者立即可用 — 無需註冊或 API 金鑰。", + "directConnection": "直接連線(無代理)", + "fetchProxiesFailed": "取得代理清單失敗", + "host": "主機", + "noSavedProxies": "無已儲存的代理 — 請在設定 → 代理中新增", + "noSavedProxiesError": "找不到已儲存的代理。請先在設定 → 代理中新增代理。", + "passwordOptional": "密碼(選填)", + "port": "連接埠", + "providerDisabled": "{provider} 已停用", + "providerEnabled": "{provider} 已啟用", + "proxyConfigured": "代理已設定:{host}", + "proxyForAccount": "帳號 {number} 的代理", + "removeAccount": "移除帳號", + "save": "儲存", + "saved": "已儲存", + "saving": "正在儲存...", + "title": "無需驗證", + "updateConnectionFailed": "更新連線失敗", + "updateProviderFailed": "更新提供者失敗", + "usernameOptional": "使用者名稱(選填)" + }, + "omniglyph": { + "beforeAfterTitle": "轉換前 → 轉換後", + "blockSavings": "此區塊節省 −{percent}% token", + "description": "將內容壓縮為圖片格式。將系統提示詞、工具說明文件和密集的歷史記錄渲染為精簡的 PNG 頁面,讓 Claude Fable 5 以圖片而非文字方式讀取。圖片 token 按尺寸而非字元計費,因此轉換後的區塊成本約降低 10 倍。僅限直接 Anthropic 路由。", + "economics": { + "accuracy": "Fable 5 上的閱讀準確度(n=30)", + "fewerTokens": "轉換後區塊的 token 減少", + "imageTokens": "1568×728 頁面的圖片 token(約 28k 字元)", + "savings": "端到端節省(實測)" + }, + "economicsTitle": "經濟效益", + "enableAria": "啟用 OmniGlyph 引擎", + "enableDescription": "在堆疊中最後執行(RTK/Caveman 清理文字後,OmniGlyph 將剩餘部分轉換為圖片),也可透過 omniglyph 模式獨立執行。此為預覽功能,預設為關閉,待端到端驗證完成後才會預設開啟。", + "enableTitle": "啟用引擎", + "gates": { + "format": { + "label": "格式", + "pass": "原生 Claude", + "why": "主體必須使用 Claude 格式,且絕對不能在訊息中包含 system 角色。" + }, + "model": { + "label": "模型", + "pass": "claude-fable-5", + "why": "只有 Fable 5 能以 100% 準確度讀取密集頁面(實測,n=30)。GPT-5.5 和 Gemini 2.5 Flash 已封鎖。" + }, + "profitable": { + "label": "效益評估", + "pass": "夠密集", + "why": "精確的 28px 區塊成本閘門會依請求決定;小型或稀疏的文字將直接通過,不經轉換。" + }, + "transport": { + "label": "傳輸方式", + "pass": "直接 Anthropic", + "why": "聚合器會重新取樣圖片並破壞可讀性 — 僅直接路由具有權威性。" + } + }, + "gatesDescription": "封閉式失敗:每個閘門都必須通過,否則請求將未經轉換直接通過(每次跳過記錄為 skip:<reason>)。", + "gatesTitle": "觸發時機", + "preview": "預覽", + "realRender": "{characters, number} 字元密集工具說明文件的實際渲染結果 — 非模擬圖。", + "renderedImageAlt": "渲染後的密集頁面,{width}×{height}px", + "renderedTokens": "已渲染頁面 · ≈ {tokens, number} 個 token", + "saveFailed": "無法儲存。", + "saved": "已儲存。", + "textTokens": "文字 · ≈ {tokens, number} 個 token" + }, + "providerHealthAutopilot": { + "action": { + "clearConnectionCooldown": "清除連線冷卻", + "clearModelLockout": "清除模型鎖定", + "clearStaleError": "清除過時錯誤狀態", + "disableConnection": "停用此連線", + "reactivateConnection": "重新啟用連線", + "resetProviderBreaker": "重設提供者斷路器" + }, + "actionApplied": "已套用 {action}。", + "actionFailed": "自動駕駛操作失敗", + "actions": "操作", + "applying": "正在套用...", + "connections": "連線", + "description": "找出不穩定的提供者、帳號冷卻、過時錯誤及可安全手動修復的問題。", + "errorCode": "代碼 {code}", + "issue": { + "circuitOpenRecommendation": "確認上游已恢復,然後重設提供者斷路器或等待重試視窗。", + "circuitOpenTitle": "提供者斷路器已開啟", + "circuitRecoveryRecommendation": "讓下次探測完成,或手動驗證後重設斷路器。", + "circuitRecoveryTitle": "提供者正在探測恢復", + "cooldownRecommendation": "等待上游重試視窗,或確認恢復後清除冷卻。", + "cooldownTitle": "{label} 處於暫時冷卻狀態", + "inactiveRecommendation": "僅在非刻意停用的情況下重新啟用。", + "inactiveTitle": "{label} 已停用", + "modelLockoutRecommendation": "請先解決連線狀態或確認模型配額/可用性已恢復,再清除鎖定。", + "modelLockoutTitle": "{model} 在某條連線中被鎖定", + "quotaRecommendation": "請檢視配額使用情況,必要時將流量切換至其他健康的連線。", + "quotaTitle": "配額監控回報 {status}", + "staleErrorRecommendation": "清除過時錯誤欄位,使此連線恢復正常路由資格。", + "staleErrorTitle": "{label} 有過時錯誤狀態", + "terminalRecommendation": "檢查帳單、重新驗證或更換憑證後再重新啟用。", + "terminalTitle": "{label} 處於終止帳號狀態" + }, + "issues": "問題", + "loadFailed": "載入自動駕駛報告失敗", + "loadingRecommendations": "正在載入提供者建議...", + "noRecommendations": "目前沒有提供者健康狀態建議。", + "providerMetrics": "分數 {score}% · 啟用 {active}/{total} · 冷卻 {cooldown} · 模型鎖定 {lockouts}", + "providerState": { + "degraded": "降級", + "down": "宕機", + "healthy": "健康" + }, + "refresh": "重新整理", + "remainingSeconds": "剩餘 {seconds} 秒", + "severity": { + "critical": "嚴重", + "info": "資訊", + "warning": "警告" + }, + "state": { + "critical": "嚴重", + "healthy": "健康", + "loading": "載入中", + "warning": "警告" + }, + "status": "狀態", + "title": "提供者健康自動駕駛" + }, + "providerStats": { + "activeProviders": "活躍提供者", + "avgLatency": "平均延遲", + "avgTotal": "平均總計", + "avgTtft": "平均 TTFT", + "comboMetrics": "組合指標", + "comboMetricsDescription": "各組合的延遲與串流通量", + "gapAfterTool": "工具後間隔", + "loadFailed": "載入提供者統計資料失敗:{error}", + "loading": "正在載入提供者統計資料...", + "model": "模型", + "noProviderData": "尚未記錄提供者資料。", + "provider": "提供者", + "providerBreakdown": "提供者細項", + "providerCount": "{count} 個提供者", + "rate": "速率", + "refresh": "重新整理", + "requestTelemetry": "請求遙測", + "requestTelemetryDescription": "7 階段管線細項(過去 5 分鐘)", + "requests": "請求", + "retry": "重試", + "success": "成功", + "successRate": "成功率", + "tokensIn": "輸入 Token", + "tokensOut": "輸出 Token", + "totalRequests": "請求總數", + "ttftAfterTool": "工具後 TTFT", + "unknownError": "未知錯誤", + "updated": "更新時間 {time}" + }, + "relay": { + "cancel": "取消", + "createButton": "建立 Token", + "createFailed": "建立 Token 失敗", + "createTitle": "建立 Relay Token", + "created": "Relay Token 已建立", + "createdTitle": "Token 已建立 — 立即複製!", + "delete": "刪除", + "deleteConfirm": "刪除此 Relay Token?此操作無法復原。", + "deleteFailed": "刪除 Token 失敗", + "deleted": "Token 已刪除", + "description": "建立可代理至 OmniRoute 的公開 API 端點,具備速率限制與存取控制", + "descriptionPlaceholder": "用於我的無伺服器函式", + "disable": "停用", + "dismiss": "關閉", + "empty": "尚未設定任何 Relay Token。建立一個以開始使用。", + "enable": "啟用", + "loading": "載入中...", + "maxPerDay": "每日最大請求數", + "maxPerMinute": "每分鐘最大請求數", + "nameRequired": "名稱 *", + "newToken": "新增 Relay Token", + "shownOnce": "此 Token 將不會再次顯示,請妥善保存。", + "title": "無伺服器 Relay 代理", + "toggleFailed": "切換 Token 狀態失敗", + "tokenCount": "Relay Token ({count})", + "tokenDescription": "描述", + "tokenFor": "{name} 的 Token:", + "usage": "使用方式", + "usageDescription": "將請求傳送至您的 Relay 端點:" + }, + "usageLimits": { + "apiKeyUsdQuota": "API 金鑰 USD 配額", + "apiKeyUsdQuotaDescription": "啟用後,@@om-usage 會傳回每日配額、每週配額、每日支出和每週支出(以 USD 計)。每週配額在可用時會遵循快取的 Claude 重置時間。", + "close": "關閉", + "dailyQuota": "每日配額", + "dailyQuotaUsd": "每日配額(USD)", + "dailySpend": "每日支出", + "disabled": "已停用", + "enabled": "已啟用", + "estimatedFullQuota": "估計 100%", + "fallbackRollingDays": "備用:滾動 {days} 天", + "fallbackRollingDaysShort": "備用滾動 {days}天", + "fromObservedReset": "根據觀測到的 {quota} 重置", + "fromRecordedReset": "根據記錄的 {quota} 重置", + "fromReset": "根據 {quota} 重置", + "loadUsdCostsFailed": "載入 USD 成本失敗", + "loadingUsdCosts": "正在載入 USD 成本", + "noApiKeyUsage": "此提供者區間內無 API 金鑰用量。", + "noUsdLimit": "無 USD 限制", + "quotaEstimator": "配額估算器", + "quotaUsed": "已用配額", + "quotaWindowDescription": "每週配額在可用時會遵循快取的 Claude 每週重置時間,否則會備用滾動 7 天區間。每日配額使用 Fortaleza 日曆日。", + "requestTokenCounts": "{requests} 個請求 · {tokens} 個 token", + "resetDueNow": "即將重置", + "resetsInDays": "{count} 天後重置", + "resetsInHours": "{count} 小時後重置", + "rows": "行", + "saveFailed": "儲存用量限制失敗", + "saveQuota": "儲存配額", + "saving": "正在儲存...", + "unknown": "未知", + "usdCost": "USD 成本", + "usdUsageQuota": "USD 用量配額", + "usdUsageQuotaDescription": "當此金鑰的本地 USD 支出達到設定的每日或每週配額後,會以 400 API 錯誤封鎖該金鑰。", + "used": "已使用", + "weeklyQuota": "每週配額", + "weeklyQuotaUsd": "每週配額(USD)", + "weeklySpend": "每週支出", + "window": "區間" + }, "kimiSponsorBanner": { - "title": "Kimi (Moonshot AI) is now an official sponsor of OmniRoute", - "description": "Kimi K3 brings a 1M-token context window and frontier coding performance to OmniRoute at a fraction of the cost.", - "cta": "Get Kimi Code", - "partnerLinkNote": "Partner link", - "dismissAriaLabel": "Dismiss" + "title": "Kimi(Moonshot AI)現已成為 OmniRoute 官方贊助商", + "description": "Kimi K3 提供 100 萬 Token 的上下文視窗與頂尖程式碼效能,但只需同類產品的一小部分成本。", + "cta": "取得 Kimi Code", + "partnerLinkNote": "合作夥伴連結", + "dismissAriaLabel": "關閉" } }