diff --git a/.agents/workflows/resolve-issues.md b/.agents/workflows/resolve-issues.md index f0130fb53e..aa2aa7177a 100644 --- a/.agents/workflows/resolve-issues.md +++ b/.agents/workflows/resolve-issues.md @@ -10,6 +10,8 @@ This workflow fetches all open issues from the project's GitHub repository, clas > **BRANCH RULE**: All work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `fix/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5. +> **⛔ PR PROHIBITION**: If a fix is associated with a contributor's PR, you MUST merge their PR — NEVER close it and re-implement the fix yourself. See `/review-prs` workflow for the full policy. The `gh pr close` command is FORBIDDEN unless the repository owner explicitly requests it. + ## Steps ### 1. Identify the GitHub Repository diff --git a/.agents/workflows/review-prs.md b/.agents/workflows/review-prs.md index 6cb91e08ca..58a5018646 100644 --- a/.agents/workflows/review-prs.md +++ b/.agents/workflows/review-prs.md @@ -4,6 +4,25 @@ description: Analyze open Pull Requests from the project's GitHub repository, ge # /review-prs — PR Review & Analysis Workflow +## ⛔ ABSOLUTE PROHIBITION — Read Before Anything Else + +> **NEVER close a contributor's PR if you intend to use ANY of their code, ideas, or fixes.** +> +> **NEVER manually integrate contributor code into a release branch and then close their PR.** +> +> These actions are **STRICTLY FORBIDDEN** under all circumstances: +> +> 1. ❌ Closing a PR and cherry-picking/copying its code into a release branch +> 2. ❌ Closing a PR "because of conflicts" and re-implementing the same fix yourself +> 3. ❌ Closing a PR and committing a "similar" solution inspired by it +> 4. ❌ Using `gh pr close` on any PR whose content was or will be used +> +> **Why**: Closing a PR after taking the contributor's work means they get ZERO credit on GitHub — no "Merged" badge, no contribution graph entry, no public record. This is effectively stealing their contribution. An audit found this happened to **37 PRs** in the past. +> +> **The ONLY acceptable flow**: Resolve conflicts IN the contributor's branch, push fixes TO their branch, then merge THEIR PR via `gh pr merge`. See Step 7 and Step 8 for the exact procedure. +> +> **When to close a PR**: ONLY when the user (repository owner) explicitly requests it, OR when the PR is clearly spam/malicious, OR when the author themselves asks to close it. In ALL other cases, leave it open. + ## Overview This workflow fetches all open PRs from the project's GitHub repository, performs a critical analysis of each one, generates a detailed report, and waits for user approval before proceeding with implementation. **All improvements are committed on the current release branch** (`release/vX.Y.Z`). diff --git a/.omc/project-memory.json b/.omc/project-memory.json deleted file mode 100644 index 7018fa00e1..0000000000 --- a/.omc/project-memory.json +++ /dev/null @@ -1,250 +0,0 @@ -{ - "version": "1.0.0", - "lastScanned": 1775016362438, - "projectRoot": "/home/openclaw/omniroute-src", - "techStack": { - "languages": [ - { - "name": "JavaScript/TypeScript", - "version": ">=18.0.0 <24.0.0", - "confidence": "high", - "markers": ["package.json"] - }, - { - "name": "TypeScript", - "version": null, - "confidence": "high", - "markers": ["tsconfig.json"] - } - ], - "frameworks": [ - { - "name": "express", - "version": "5.2.1", - "category": "backend" - }, - { - "name": "next", - "version": "16.0.10", - "category": "fullstack" - }, - { - "name": "react", - "version": "19.2.4", - "category": "frontend" - }, - { - "name": "react-dom", - "version": "19.2.4", - "category": "frontend" - }, - { - "name": "@playwright/test", - "version": "1.58.2", - "category": "testing" - }, - { - "name": "vitest", - "version": "4.0.18", - "category": "testing" - } - ], - "packageManager": "npm", - "runtime": "Node.js 18.0.024.0.0" - }, - "build": { - "buildCommand": "npm run build", - "testCommand": "npm test", - "lintCommand": "npm run lint", - "devCommand": "npm run dev", - "scripts": { - "dev": "node scripts/run-next.mjs dev", - "build": "node scripts/build-next-isolated.mjs", - "build:cli": "node scripts/prepublish.mjs", - "start": "node scripts/run-next.mjs start", - "lint": "eslint .", - "electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"", - "electron:build": "npm run build && cd electron && npm run build", - "electron:build:win": "npm run build && cd electron && npm run build:win", - "electron:build:mac": "npm run build && cd electron && npm run build:mac", - "electron:build:linux": "npm run build && cd electron && npm run build:linux", - "test": "node --import tsx/esm --test tests/unit/*.test.mjs", - "test:unit": "node --import tsx/esm --test tests/unit/*.test.mjs", - "test:plan3": "node --import tsx/esm --test tests/unit/plan3-p0.test.mjs", - "test:fixes": "node --import tsx/esm --test tests/unit/fixes-p1.test.mjs", - "test:security": "node --import tsx/esm --test tests/unit/security-fase01.test.mjs", - "check:cycles": "node scripts/check-cycles.mjs", - "check:route-validation:t06": "node scripts/check-route-validation.mjs", - "check:any-budget:t11": "node scripts/check-t11-any-budget.mjs", - "check:docs-sync": "node scripts/check-docs-sync.mjs", - "typecheck:core": "tsc --pretty false -p tsconfig.typecheck-core.json", - "typecheck:noimplicit:core": "tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json", - "test:integration": "node --import tsx/esm --test tests/integration/*.test.mjs", - "test:e2e": "node scripts/run-playwright-tests.mjs test tests/e2e/*.spec.ts", - "test:protocols:e2e": "node scripts/run-protocol-clients-tests.mjs", - "test:vitest": "vitest run open-sse/mcp-server/__tests__/*.test.ts open-sse/services/autoCombo/__tests__/*.test.ts", - "test:ecosystem": "node scripts/run-ecosystem-tests.mjs", - "test:coverage": "c8 --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 55 --lines 55 --functions 55 --branches 60 node --import tsx/esm --test tests/unit/*.test.mjs", - "test:coverage:legacy": "c8 --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.mjs", - "coverage:report": "c8 report --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov", - "coverage:report:legacy": "c8 report --exclude=open-sse --reporter=text --reporter=text-summary", - "test:all": "npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e", - "check": "npm run lint && npm run test", - "prepublishOnly": "npm run build:cli", - "postinstall": "node scripts/postinstall.mjs", - "prepare": "husky", - "system-info": "node scripts/system-info.mjs" - } - }, - "conventions": { - "namingStyle": "camelCase", - "importStyle": null, - "testPattern": null, - "fileOrganization": null - }, - "structure": { - "isMonorepo": true, - "workspaces": ["open-sse"], - "mainDirectories": ["bin", "docs", "public", "scripts", "src", "tests"], - "gitBranches": { - "defaultBranch": "main", - "branchingStrategy": null - } - }, - "customNotes": [], - "directoryMap": { - "bin": { - "path": "bin", - "purpose": "Executable scripts", - "fileCount": 3, - "lastAccessed": 1775016362426, - "keyFiles": ["mcp-server.mjs", "omniroute.mjs", "reset-password.mjs"] - }, - "docs": { - "path": "docs", - "purpose": "Documentation", - "fileCount": 14, - "lastAccessed": 1775016362426, - "keyFiles": [ - "A2A-SERVER.md", - "API_REFERENCE.md", - "ARCHITECTURE.md", - "AUTO-COMBO.md", - "CLI-TOOLS.md" - ] - }, - "electron": { - "path": "electron", - "purpose": null, - "fileCount": 5, - "lastAccessed": 1775016362431, - "keyFiles": ["README.md", "main.js", "package.json", "preload.js", "types.d.ts"] - }, - "images": { - "path": "images", - "purpose": null, - "fileCount": 1, - "lastAccessed": 1775016362434, - "keyFiles": ["omniroute.png"] - }, - "logs": { - "path": "logs", - "purpose": null, - "fileCount": 3, - "lastAccessed": 1775016362434, - "keyFiles": ["build_clean_tools.log", "build_debug.log", "build_force_clean.log"] - }, - "open-sse": { - "path": "open-sse", - "purpose": null, - "fileCount": 5, - "lastAccessed": 1775016362434, - "keyFiles": ["index.ts", "package.json", "tsconfig.json", "types.d.ts"] - }, - "public": { - "path": "public", - "purpose": "Public files", - "fileCount": 3, - "lastAccessed": 1775016362435, - "keyFiles": ["apple-touch-icon.svg", "favicon.svg", "icon-192.svg"] - }, - "scripts": { - "path": "scripts", - "purpose": "Build/utility scripts", - "fileCount": 23, - "lastAccessed": 1775016362435, - "keyFiles": [ - "bootstrap-env.mjs", - "build-next-isolated.mjs", - "check-cycles.mjs", - "check-docs-sync.mjs", - "check-route-validation.mjs" - ] - }, - "src": { - "path": "src", - "purpose": "Source code", - "fileCount": 4, - "lastAccessed": 1775016362435, - "keyFiles": ["instrumentation-node.ts", "instrumentation.ts", "proxy.ts", "server-init.ts"] - }, - "tests": { - "path": "tests", - "purpose": "Test files", - "fileCount": 0, - "lastAccessed": 1775016362435, - "keyFiles": [] - }, - "electron/assets": { - "path": "electron/assets", - "purpose": "Static assets", - "fileCount": 4, - "lastAccessed": 1775016362436, - "keyFiles": ["icon.icns", "icon.ico", "icon.png"] - }, - "open-sse/config": { - "path": "open-sse/config", - "purpose": "Configuration files", - "fileCount": 17, - "lastAccessed": 1775016362436, - "keyFiles": ["audioRegistry.ts", "cliFingerprints.ts", "codexInstructions.ts"] - }, - "open-sse/services": { - "path": "open-sse/services", - "purpose": "Business logic services", - "fileCount": 35, - "lastAccessed": 1775016362437, - "keyFiles": ["accountFallback.ts", "accountSelector.ts", "apiKeyRotator.ts"] - }, - "src/app": { - "path": "src/app", - "purpose": "Application code", - "fileCount": 7, - "lastAccessed": 1775016362438, - "keyFiles": ["error.tsx", "global-error.tsx", "globals.css"] - }, - "src/lib": { - "path": "src/lib", - "purpose": "Library code", - "fileCount": 30, - "lastAccessed": 1775016362438, - "keyFiles": ["apiBridgeServer.ts", "apiKeyExposure.ts", "cacheControlSettings.ts"] - }, - "src/middleware": { - "path": "src/middleware", - "purpose": "Middleware", - "fileCount": 1, - "lastAccessed": 1775016362438, - "keyFiles": ["promptInjectionGuard.ts"] - }, - "src/models": { - "path": "src/models", - "purpose": "Data models", - "fileCount": 1, - "lastAccessed": 1775016362438, - "keyFiles": ["index.ts"] - } - }, - "hotPaths": [], - "userDirectives": [] -} diff --git a/.omc/sessions/53c002c3-36a6-47c3-a52d-a8f756c264eb.json b/.omc/sessions/53c002c3-36a6-47c3-a52d-a8f756c264eb.json deleted file mode 100644 index f3dfd0f299..0000000000 --- a/.omc/sessions/53c002c3-36a6-47c3-a52d-a8f756c264eb.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "session_id": "53c002c3-36a6-47c3-a52d-a8f756c264eb", - "ended_at": "2026-04-01T04:06:04.924Z", - "reason": "prompt_input_exit", - "agents_spawned": 0, - "agents_completed": 0, - "modes_used": [] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index fae48f28c7..442a2bedab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,5 +17,33 @@ "level": "off" } }, - "git.ignoreLimitWarning": true + "git.ignoreLimitWarning": true, + // "files.exclude": { + // "**/_references": true, + // "**/_mono_repo": true, + // "**/electron": true, + // "**/node_modules": true, + // "**/.next": true, + // "**/coverage": true, + // "**/omniroute-*.tgz": true, + // "**/_tasks": true + // }, + "files.watcherExclude": { + "**/_references/**": true, + "**/_mono_repo/**": true, + "**/electron/**": true, + "**/node_modules/**": true, + "**/.next/**": true, + "**/coverage/**": true, + "**/_tasks/**": true + }, + "search.exclude": { + "**/_references": true, + "**/_mono_repo": true, + "**/electron": true, + "**/node_modules": true, + "**/.next": true, + "**/coverage": true, + "**/_tasks": true + } } diff --git a/AGENTS.md b/AGENTS.md index 77c697f3b3..6fa58c42f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -418,3 +418,4 @@ Request middleware including `promptInjectionGuard.ts`. - **Provider constants** validated at module load via Zod (`src/shared/validation/providerSchema.ts`) - **Pricing data** syncs from LiteLLM via `src/lib/pricingSync.ts` - **Memory/Skills** are cross-cutting: affect MCP tools, request pipeline, and A2A skills +- **⛔ NEVER close a contributor's PR** after using their code — always merge via GitHub so they get credit. See `.agents/workflows/review-prs.md` for full policy. diff --git a/CHANGELOG.md b/CHANGELOG.md index d829a062b0..16411f6e72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,94 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 diff --git a/README.md b/README.md index f619d64816..a26c25f47b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/diegosouzapw-omniroute-badge.png)](https://mseep.ai/app/diegosouzapw-omniroute) + # 🚀 OmniRoute — The Free AI Gateway ### Never stop coding. Smart routing to **FREE & low-cost AI models** with automatic fallback. diff --git a/docs/i18n/README.md b/docs/i18n/README.md index 30da8b0077..7e8a95e592 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -1,4 +1,4 @@ -# 🌐 Multilingual Documentation — 9router +# 🌐 Multilingual Documentation — OmniRoute Translations of documentation into 39 languages. Code blocks remain in English. diff --git a/docs/i18n/ar/CHANGELOG.md b/docs/i18n/ar/CHANGELOG.md index 2f84624023..b44a7d0b90 100644 --- a/docs/i18n/ar/CHANGELOG.md +++ b/docs/i18n/ar/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### الأمان + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/bg/CHANGELOG.md b/docs/i18n/bg/CHANGELOG.md index 532214bb70..60afd17be3 100644 --- a/docs/i18n/bg/CHANGELOG.md +++ b/docs/i18n/bg/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Сигурност + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/bn/CHANGELOG.md b/docs/i18n/bn/CHANGELOG.md index 2a1f4023d1..19b1510b9a 100644 --- a/docs/i18n/bn/CHANGELOG.md +++ b/docs/i18n/bn/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/cs/CHANGELOG.md b/docs/i18n/cs/CHANGELOG.md index 7b59dcdd81..3e9a40f561 100644 --- a/docs/i18n/cs/CHANGELOG.md +++ b/docs/i18n/cs/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Bezpečnost + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/da/CHANGELOG.md b/docs/i18n/da/CHANGELOG.md index 5ac55da9b4..cfeb78306b 100644 --- a/docs/i18n/da/CHANGELOG.md +++ b/docs/i18n/da/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Sikkerhed + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/de/CHANGELOG.md b/docs/i18n/de/CHANGELOG.md index f5814bcd58..6ec201bb96 100644 --- a/docs/i18n/de/CHANGELOG.md +++ b/docs/i18n/de/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Sicherheit + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/es/CHANGELOG.md b/docs/i18n/es/CHANGELOG.md index ae27b1a6cc..777e606501 100644 --- a/docs/i18n/es/CHANGELOG.md +++ b/docs/i18n/es/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/fa/CHANGELOG.md b/docs/i18n/fa/CHANGELOG.md index a3a52dc001..73421e1abf 100644 --- a/docs/i18n/fa/CHANGELOG.md +++ b/docs/i18n/fa/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/fi/CHANGELOG.md b/docs/i18n/fi/CHANGELOG.md index d4f07a7e00..f7e6fafca4 100644 --- a/docs/i18n/fi/CHANGELOG.md +++ b/docs/i18n/fi/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Turvallisuus + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/fr/CHANGELOG.md b/docs/i18n/fr/CHANGELOG.md index 81e86bbc05..dbd9b620a5 100644 --- a/docs/i18n/fr/CHANGELOG.md +++ b/docs/i18n/fr/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Sécurité + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/gu/CHANGELOG.md b/docs/i18n/gu/CHANGELOG.md index 0bbee5d7f8..484de2b0d6 100644 --- a/docs/i18n/gu/CHANGELOG.md +++ b/docs/i18n/gu/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/he/CHANGELOG.md b/docs/i18n/he/CHANGELOG.md index 1651d8eb2d..c73723829a 100644 --- a/docs/i18n/he/CHANGELOG.md +++ b/docs/i18n/he/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### אבטחה + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/hi/CHANGELOG.md b/docs/i18n/hi/CHANGELOG.md index 9ea77a070e..07ed812121 100644 --- a/docs/i18n/hi/CHANGELOG.md +++ b/docs/i18n/hi/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### सुरक्षा + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/hu/CHANGELOG.md b/docs/i18n/hu/CHANGELOG.md index f11a2c90b6..4ccbe4a816 100644 --- a/docs/i18n/hu/CHANGELOG.md +++ b/docs/i18n/hu/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Biztonság + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/id/CHANGELOG.md b/docs/i18n/id/CHANGELOG.md index 0f0fb403f5..5e47c1e5ca 100644 --- a/docs/i18n/id/CHANGELOG.md +++ b/docs/i18n/id/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Keamanan + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/it/CHANGELOG.md b/docs/i18n/it/CHANGELOG.md index 4db3a1f2f2..9f5034983c 100644 --- a/docs/i18n/it/CHANGELOG.md +++ b/docs/i18n/it/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Sicurezza + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/ja/CHANGELOG.md b/docs/i18n/ja/CHANGELOG.md index 9447f5cf1b..5d0efc0400 100644 --- a/docs/i18n/ja/CHANGELOG.md +++ b/docs/i18n/ja/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### セキュリティ + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/ko/CHANGELOG.md b/docs/i18n/ko/CHANGELOG.md index b85ba72008..a9fe9603a6 100644 --- a/docs/i18n/ko/CHANGELOG.md +++ b/docs/i18n/ko/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### 보안 + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/mr/CHANGELOG.md b/docs/i18n/mr/CHANGELOG.md index f59c86f893..87e7e231d7 100644 --- a/docs/i18n/mr/CHANGELOG.md +++ b/docs/i18n/mr/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/ms/CHANGELOG.md b/docs/i18n/ms/CHANGELOG.md index 76d7f52c87..56b6a5cb46 100644 --- a/docs/i18n/ms/CHANGELOG.md +++ b/docs/i18n/ms/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Keselamatan + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/nl/CHANGELOG.md b/docs/i18n/nl/CHANGELOG.md index 2edeab924f..2d944cc176 100644 --- a/docs/i18n/nl/CHANGELOG.md +++ b/docs/i18n/nl/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Beveiliging + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/no/CHANGELOG.md b/docs/i18n/no/CHANGELOG.md index 5508badcfd..304b3264d9 100644 --- a/docs/i18n/no/CHANGELOG.md +++ b/docs/i18n/no/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Sikkerhet + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/phi/CHANGELOG.md b/docs/i18n/phi/CHANGELOG.md index 79df01ab9c..e311d2b3d0 100644 --- a/docs/i18n/phi/CHANGELOG.md +++ b/docs/i18n/phi/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/pl/CHANGELOG.md b/docs/i18n/pl/CHANGELOG.md index 449d906a62..812ad924f5 100644 --- a/docs/i18n/pl/CHANGELOG.md +++ b/docs/i18n/pl/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Bezpieczeństwo + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/pt-BR/CHANGELOG.md b/docs/i18n/pt-BR/CHANGELOG.md index c2a65d5855..7c4677c303 100644 --- a/docs/i18n/pt-BR/CHANGELOG.md +++ b/docs/i18n/pt-BR/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Segurança + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/pt/CHANGELOG.md b/docs/i18n/pt/CHANGELOG.md index f57dc3fd71..aad7de42fa 100644 --- a/docs/i18n/pt/CHANGELOG.md +++ b/docs/i18n/pt/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Segurança + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/ro/CHANGELOG.md b/docs/i18n/ro/CHANGELOG.md index 64ecea4f27..f67a9e5311 100644 --- a/docs/i18n/ro/CHANGELOG.md +++ b/docs/i18n/ro/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Securitate + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/ru/CHANGELOG.md b/docs/i18n/ru/CHANGELOG.md index 17252b3e41..5e4f6529a5 100644 --- a/docs/i18n/ru/CHANGELOG.md +++ b/docs/i18n/ru/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Безопасность + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/sk/CHANGELOG.md b/docs/i18n/sk/CHANGELOG.md index 9ba25a9c88..197a64c53a 100644 --- a/docs/i18n/sk/CHANGELOG.md +++ b/docs/i18n/sk/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Bezpečnosť + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/sv/CHANGELOG.md b/docs/i18n/sv/CHANGELOG.md index e5a2cf37c5..a1aee6bb9f 100644 --- a/docs/i18n/sv/CHANGELOG.md +++ b/docs/i18n/sv/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Säkerhet + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/sw/CHANGELOG.md b/docs/i18n/sw/CHANGELOG.md index a9f6058daf..e16b69e0e3 100644 --- a/docs/i18n/sw/CHANGELOG.md +++ b/docs/i18n/sw/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/ta/CHANGELOG.md b/docs/i18n/ta/CHANGELOG.md index acb416710e..0511159a0e 100644 --- a/docs/i18n/ta/CHANGELOG.md +++ b/docs/i18n/ta/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/te/CHANGELOG.md b/docs/i18n/te/CHANGELOG.md index 2b31e03d3e..6a67141fda 100644 --- a/docs/i18n/te/CHANGELOG.md +++ b/docs/i18n/te/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/th/CHANGELOG.md b/docs/i18n/th/CHANGELOG.md index 671a5c9493..8efffb5eff 100644 --- a/docs/i18n/th/CHANGELOG.md +++ b/docs/i18n/th/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### ความปลอดภัย + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/tr/CHANGELOG.md b/docs/i18n/tr/CHANGELOG.md index 728d8d3411..93d809bef2 100644 --- a/docs/i18n/tr/CHANGELOG.md +++ b/docs/i18n/tr/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Güvenlik + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/uk-UA/CHANGELOG.md b/docs/i18n/uk-UA/CHANGELOG.md index da8f950918..9a0494d033 100644 --- a/docs/i18n/uk-UA/CHANGELOG.md +++ b/docs/i18n/uk-UA/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Безпека + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/ur/CHANGELOG.md b/docs/i18n/ur/CHANGELOG.md index 198881b9b8..ca28771f70 100644 --- a/docs/i18n/ur/CHANGELOG.md +++ b/docs/i18n/ur/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Seguridad + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/vi/CHANGELOG.md b/docs/i18n/vi/CHANGELOG.md index 09c3dbec2d..d801138946 100644 --- a/docs/i18n/vi/CHANGELOG.md +++ b/docs/i18n/vi/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### Bảo mật + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/i18n/zh-CN/CHANGELOG.md b/docs/i18n/zh-CN/CHANGELOG.md index b98fb404b2..ce0394c2c7 100644 --- a/docs/i18n/zh-CN/CHANGELOG.md +++ b/docs/i18n/zh-CN/CHANGELOG.md @@ -6,14 +6,130 @@ ## [Unreleased] +### ✨ New Features + +- **feat:** ongoing development + +--- + +## [3.7.5] — 2026-04-29 + +### ✨ New Features + +- **feat(tunnels):** integrate native ngrok tunnel support with dashboard UI parity (#1753) +- **feat(api-keys):** add rename support in the permissions modal — editable key name field with validation (#1796) + +### 🐛 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) +- **fix(providers):** ensure proper streaming cleanup and semaphore release to prevent stalls with nanoGPT (#1781) +- **fix(db):** wrap quota_snapshots access in try/catch to gracefully handle pending database migrations (#1784) +- **feat(providers):** add support for glm-cn (BigModel) provider (#1770) +- **fix(grok-web):** fix Grok validator and cookie parsing (#1793) +- **fix(antigravity):** scrub internal OmniRoute headers (#1794) +- **fix(chatgpt-web):** restore validator + expand model catalog to ChatGPT Plus tier (#1792) +- **fix(codex):** stabilize Copilot responses replay state (#1791) +- **fix(antigravity):** cap Claude bridge output tokens (#1785) +- **fix(schema):** strip `default` properties from tool-call JSON schemas during egress to prevent injection errors (#1782) +- **fix(db):** add `quota_snapshots` table to core DB schema initialization to prevent startup failures on fresh installs +- **fix(models):** apply blocked providers filter to non-chat catalog models (image, embedding, audio, etc.) (#1752) +- **fix(antigravity):** stabilize streaming payload parsing and deduplicate usage/model metadata refreshes (#1748) +- **fix(antigravity):** normalize Gemini bridge payloads — sanitize tool names, cap output tokens, and fix thinking budget (#1769) +- **fix(sse):** propagate AbortSignal to pre-fetch semaphore and rate-limit awaits to prevent memory leaks (#1771) +- **fix(models):** fix model sync import handling — separate synced models from custom models to prevent data loss (#1755) +- **fix(codex):** improve VS Code Copilot /responses reasoning and tool follow-ups (#1750) +- **fix(memory):** resolve build issues and implement memory UPSERT logic to prevent duplicate entries (#1763) +- **fix(kiro):** support organization IDC OAuth with regional endpoints and refresh (#1754) +- **fix(combo):** include 429 in provider circuit breaker to stop infinite retry loops on exhausted quotas (#1767) +- **fix(claude):** respect client-set thinking/effort params — only inject adaptive thinking and high effort when the client hasn't explicitly set them, preventing forced quota drain on Claude Max accounts (#1761) +- **fix(blackbox-web):** correct cookie name and populate session/subscription fields (#1776) +- **fix(codex):** align client identity metadata (#1778) +- **fix(claude):** fix support for claude-cli using Gemini provider (#1779) +- **test(reasoning-cache):** isolate DB state using mkdtempSync to prevent 401 middleware errors + +### 🛠️ Maintenance + +- **chore(docs):** add MseeP.ai security assessment badge to README (#1727) +- **chore(xiaomi):** update Xiaomi provider model list (#1759) +- **chore(db):** move DB health endpoint to management API (#1757) +- **chore(ui):** speed up endpoint initial render with background task loading (#1760) +- **chore(workflows):** add strict PR contributor credit policy to prevent future merge credit loss + +### 🏆 Community Contributors Acknowledgment + +We identified that **37 community PRs** across past releases (v3.4.0 → v3.7.4) were manually integrated into release branches but closed instead of properly merged through GitHub, preventing contributors from receiving merge credit on their profiles. We sincerely apologize for this oversight and have since updated our workflows to ensure this never happens again. + +**The following contributors had their code and ideas integrated across multiple releases without proper merge credit. Thank you for your invaluable contributions to OmniRoute:** + +| Contributor | Contributions (PRs) | +| :----------------------------------------------------- | :----------------------------------------------------------------------- | +| [@rdself](https://github.com/rdself) | #1742, #1357, #1356, #1089, #1069, #904, #880, #875, #853, #851, #974 | +| [@oyi77](https://github.com/oyi77) | #1411, #1021, #990, #926, #908, #883, #881, #868, #862, #859, #850, #983 | +| [@benzntech](https://github.com/benzntech) | #1677, #1444, #1440, #1437, #1435 | +| [@clousky2020](https://github.com/clousky2020) | #1644, #1408 | +| [@christopher-s](https://github.com/christopher-s) | #885, #868, #992 | +| [@kang-heewon](https://github.com/kang-heewon) | #1235, #884 | +| [@backryun](https://github.com/backryun) | #1627, #1358, #1722 | +| [@tombii](https://github.com/tombii) | #900, #856 | +| [@slewis3600](https://github.com/slewis3600) | #1624 | +| [@dhaern](https://github.com/dhaern) | #1647 | +| [@JasonLandbridge](https://github.com/JasonLandbridge) | #1626 | +| [@hartmark](https://github.com/hartmark) | #1500 | +| [@herjarsa](https://github.com/herjarsa) | #1480 | +| [@andruwa13](https://github.com/andruwa13) | #1457 | +| [@i1hwan](https://github.com/i1hwan) | #1386 | +| [@xandr0s](https://github.com/xandr0s) | #1376 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #1188 | +| [@wlfonseca](https://github.com/wlfonseca) | #1016 | +| [@only4copilot](https://github.com/only4copilot) | #1039, #855 | +| [@AndrewDragonIV](https://github.com/AndrewDragonIV) | #898 | +| [@dt418](https://github.com/dt418) | #896 | +| [@willbnu](https://github.com/willbnu) | #882 | +| [@defhouse](https://github.com/defhouse) | #906 | +| [@mercs2910](https://github.com/mercs2910) | #1001 | +| [@zen0bit](https://github.com/zen0bit) | #912 | +| [@razllivan](https://github.com/razllivan) | #987 | +| [@foxy1402](https://github.com/foxy1402) | #934 | +| [@knopki](https://github.com/knopki) | #1434 | +| [@dail45](https://github.com/dail45) | #1413 | + --- ## [3.7.4] — 2026-04-28 +### ✨ New Features + +- **feat(ui):** add endpoint tunnel visibility settings (#1743) +- **feat(cli):** refresh CLI fingerprint provider profiles (#1746) +- **feat(proxy):** implement bulk proxy import via pipe-delimited parser with update-or-create (upsert) logic and real-time preview table +- **feat(pwa):** add fullscreen installable PWA with manifest, service worker, and cross-platform app icons (#1728) + +### 安全 + +- **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) +- **fix(claude-code):** fix Claude Code gateway config helper (#1745) +- **fix(db):** reconcile legacy `create_reasoning_cache` migration tracking to prevent version shadowing on `032` and resolve startup warnings (#1734) +- **fix(db):** intercept `007` migration to use idempotent `IF NOT EXISTS` logic via `PRAGMA table_info`, preventing syntax crashes on fresh installs (#1733) +- **fix(cc-compatible):** preserve Claude Code system skeleton to prevent request rejection by strict compatible upstream providers (#1740) + +- **fix(providers):** add API key validation for image-only providers and fix Stability AI requests to use `multipart/form-data` instead of JSON (#1726) +- **fix(codex):** preserve `previous_response_id` and `conversation_id` fields when input array is empty to prevent schema validation errors (#1729) +- **fix(searxng):** bypass UI validation block when `apiKeyOptional` is true and fix typing errors in provider dashboard to allow saving search providers without credentials (#1721) +- **fix(proxy):** disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent "Socket hang up" rotation failures - **stream:** correctly identify `thought` and `error` blocks in Antigravity/Gemini SSE streams to prevent premature 502 timeouts (#1725, #1705) +### 🛠️ Maintenance + +- **workflow:** add phase 4 release monitoring instructions to `/generate-release` workflow +- **test:** fix typescript compilation errors in unit tests to keep CI typecheck pipeline fully green +- **test:** update responses store expectations for empty input arrays + --- ## [3.7.3] — 2026-04-28 diff --git a/docs/openapi.yaml b/docs/openapi.yaml index ccf1626744..40a309b584 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: OmniRoute API - version: 3.7.4 + version: 3.7.5 description: | OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible endpoint that routes requests to multiple AI providers with load balancing, diff --git a/electron/package.json b/electron/package.json index 7a023263f8..cb06672579 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "omniroute-desktop", - "version": "3.7.4", + "version": "3.7.5", "description": "OmniRoute Desktop Application", "main": "main.js", "author": { diff --git a/llm.txt b/llm.txt index 99ba91f792..5903d37c52 100644 --- a/llm.txt +++ b/llm.txt @@ -8,7 +8,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo **Key value:** One endpoint (`http://localhost:20128/v1`), unlimited models, zero downtime, minimal cost. -**Current version:** 3.7.4 +**Current version:** 3.7.5 ## Tech Stack @@ -279,7 +279,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo └── .env.example # Environment variable template ``` -## Key Features (v3.7.4) +## Key Features (v3.7.5) ### Core Proxy - **160+ AI providers** with automatic format translation diff --git a/next.config.mjs b/next.config.mjs index b1e835a4ae..79348ec773 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -67,6 +67,7 @@ const nextConfig = { "tls-client-node", "koffi", "tough-cookie", + "@ngrok/ngrok", "child_process", "fs", "path", @@ -100,6 +101,12 @@ const nextConfig = { contextRegExp: /thread-stream/, }) ); + + // Mark @ngrok/ngrok as external to prevent webpack from trying to bundle its .node binaries + config.externals = config.externals || []; + config.externals.push({ + "@ngrok/ngrok": "commonjs @ngrok/ngrok", + }); // ── Turbopack / Next.js 16 module-hash patch (#394, #396, #398) ──────── // // Next.js 16 (with or without Turbopack) compiles the instrumentation hook diff --git a/open-sse/config/codexClient.ts b/open-sse/config/codexClient.ts index 383a9dbbf2..af8dbce4a9 100644 --- a/open-sse/config/codexClient.ts +++ b/open-sse/config/codexClient.ts @@ -1,10 +1,11 @@ const DEFAULT_CODEX_CLIENT_VERSION = "0.125.0"; -const DEFAULT_CODEX_USER_AGENT_PLATFORM = "Windows 10.0.26100"; +const DEFAULT_CODEX_USER_AGENT_PLATFORM = "Windows 10.0.26200"; const DEFAULT_CODEX_USER_AGENT_ARCH = "x64"; const CODEX_VERSION_OVERRIDE_ENV = "CODEX_CLIENT_VERSION"; const CODEX_USER_AGENT_OVERRIDE_ENV = "CODEX_USER_AGENT"; const SAFE_HEADER_TOKEN_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/; const SAFE_HEADER_VALUE_PATTERN = /^[\x20-\x7E]{1,200}$/; +const SAFE_CODEX_SESSION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,200}$/; function getSafeEnvValue(name: string, pattern: RegExp): string | null { const raw = process.env[name]; @@ -40,3 +41,9 @@ export function getCodexDefaultHeaders(): Record { "User-Agent": getCodexUserAgent(), }; } + +export function normalizeCodexSessionId(value: unknown): string | null { + if (typeof value !== "string") return null; + const normalized = value.trim(); + return SAFE_CODEX_SESSION_ID_PATTERN.test(normalized) ? normalized : null; +} diff --git a/open-sse/config/codexIdentity.ts b/open-sse/config/codexIdentity.ts new file mode 100644 index 0000000000..2d336046f6 --- /dev/null +++ b/open-sse/config/codexIdentity.ts @@ -0,0 +1,87 @@ +import { createHash, randomUUID } from "node:crypto"; + +import { normalizeCodexSessionId } from "./codexClient.ts"; + +const CODEX_INSTALLATION_SALT = "omniroute-codex-installation"; +const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + +export type CodexClientIdentity = { + sessionId: string; + turnId: string; + windowId: string; + installationId: string; +}; + +function normalizeUuid(value: unknown): string | null { + return typeof value === "string" && UUID_PATTERN.test(value.trim()) ? value.trim() : null; +} + +function uuidFromStableValue(value: string): string { + const hash = createHash("sha256").update(value).digest("hex"); + return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-a${hash.slice(17, 20)}-${hash.slice(20, 32)}`; +} + +export function getCodexInstallationId( + providerSpecificData?: Record | null +): string { + const explicit = normalizeUuid(providerSpecificData?.codexInstallationId); + if (explicit) return explicit; + + const stableSource = + typeof providerSpecificData?.workspaceId === "string" && providerSpecificData.workspaceId.trim() + ? providerSpecificData.workspaceId.trim() + : typeof providerSpecificData?.accountId === "string" && providerSpecificData.accountId.trim() + ? providerSpecificData.accountId.trim() + : typeof providerSpecificData?.email === "string" && providerSpecificData.email.trim() + ? providerSpecificData.email.trim() + : "default"; + + return uuidFromStableValue(`${CODEX_INSTALLATION_SALT}:${stableSource}`); +} + +export function createCodexClientIdentity( + sessionId: string | null, + providerSpecificData?: Record | null +): CodexClientIdentity | null { + const normalizedSessionId = normalizeCodexSessionId(sessionId); + if (!normalizedSessionId) return null; + return { + sessionId: normalizedSessionId, + turnId: randomUUID(), + windowId: `${normalizedSessionId}:0`, + installationId: getCodexInstallationId(providerSpecificData), + }; +} + +export function applyCodexClientIdentityHeaders( + headers: Record, + identity?: CodexClientIdentity | null +): void { + if (!identity) return; + headers["session_id"] = identity.sessionId; + headers["x-client-request-id"] = identity.sessionId; + headers["x-codex-window-id"] = identity.windowId; + headers["x-codex-turn-metadata"] = JSON.stringify({ + session_id: identity.sessionId, + thread_source: "user", + turn_id: identity.turnId, + sandbox: "none", + }); +} + +export function applyCodexClientMetadata( + body: Record, + identity?: CodexClientIdentity | null +): void { + if (!identity) return; + const existing = + body.client_metadata && + typeof body.client_metadata === "object" && + !Array.isArray(body.client_metadata) + ? (body.client_metadata as Record) + : {}; + body.client_metadata = { + ...existing, + "x-codex-installation-id": identity.installationId, + }; +} diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index d68401e0c1..be8a496855 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -228,7 +228,7 @@ const CHAT_OPENAI_COMPAT_MODELS: Record = { codestral: buildModels(["codestral-2405", "codestral-latest"]), upstage: buildModels(["solar-pro", "solar-mini", "solar-docvision", "solar-embedding-1-large"]), maritalk: buildModels(["sabia-3", "sabia-3-small"]), - "xiaomi-mimo": buildModels(["mimo-v2-pro", "mimo-v2-omni", "mimo-v2-tts"]), + "xiaomi-mimo": buildModels(["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-omni", "mimo-v2-flash"]), "inference-net": buildModels([ "meta-llama/Llama-3.3-70B-Instruct", "deepseek-ai/DeepSeek-R1", @@ -735,6 +735,27 @@ export const REGISTRY: Record = { models: [...GLM_SHARED_MODELS], }, + "glm-cn": { + id: "glm-cn", + alias: "glm-cn", + format: "openai", + executor: "default", + baseUrl: "https://open.bigmodel.cn/api/paas/v4/chat/completions", + authType: "apikey", + authHeader: "bearer", + defaultContextLength: 128000, + models: [ + { id: "glm-4-plus", name: "GLM-4 Plus" }, + { id: "glm-4-0520", name: "GLM-4 0520" }, + { id: "glm-4-air", name: "GLM-4 Air" }, + { id: "glm-4-airx", name: "GLM-4 AirX" }, + { id: "glm-4-long", name: "GLM-4 Long", contextLength: 1000000 }, + { id: "glm-4-flashx", name: "GLM-4 FlashX" }, + { id: "glm-4-flash", name: "GLM-4 Flash" }, + ], + passthroughModels: true, + }, + glmt: { id: "glmt", alias: "glmt", @@ -1087,7 +1108,21 @@ export const REGISTRY: Record = { baseUrl: "https://chatgpt.com/backend-api/conversation", authType: "apikey", authHeader: "cookie", - models: [{ id: "gpt-5.3-instant", name: "GPT-5.3 Instant (via ChatGPT Web)" }], + models: [ + { id: "gpt-5.3-instant", name: "GPT-5.3 Instant" }, + { id: "gpt-5.3", name: "GPT-5.3" }, + { id: "gpt-5.3-mini", name: "GPT-5.3 Mini" }, + { id: "gpt-5.5-thinking", name: "GPT-5.5 Thinking" }, + { id: "gpt-5.4-thinking", name: "GPT-5.4 Thinking" }, + { id: "gpt-5.4-thinking-mini", name: "GPT-5.4 Thinking Mini" }, + { id: "gpt-5.2-instant", name: "GPT-5.2 Instant" }, + { id: "gpt-5.2", name: "GPT-5.2" }, + { id: "gpt-5.2-thinking", name: "GPT-5.2 Thinking" }, + { id: "gpt-5.1", name: "GPT-5.1" }, + { id: "gpt-5", name: "GPT-5" }, + { id: "gpt-5-mini", name: "GPT-5 Mini" }, + { id: "o3", name: "o3" }, + ], }, "grok-web": { @@ -1100,7 +1135,6 @@ export const REGISTRY: Record = { authHeader: "cookie", passthroughModels: true, models: [ - { id: "auto", name: "Grok Auto" }, { id: "fast", name: "Grok Fast" }, { id: "expert", name: "Grok 4.20 Thinking" }, { id: "heavy", name: "Grok 4.20 Multi Agent" }, diff --git a/open-sse/config/toolCloaking.ts b/open-sse/config/toolCloaking.ts index 7fa5cd3b35..429a6ca28b 100644 --- a/open-sse/config/toolCloaking.ts +++ b/open-sse/config/toolCloaking.ts @@ -27,7 +27,7 @@ const AG_DEFAULT_TOOL_NAMES = [ const AG_DECOY_TOOL_NAMES = [ ...AG_DEFAULT_TOOL_NAMES, - "mcp_sequential-thinking_sequentialthinking", + "mcp_sequential_thinking_sequentialthinking", ] as const; export const AG_DEFAULT_TOOLS = new Set(AG_DEFAULT_TOOL_NAMES); diff --git a/open-sse/executors/antigravity.ts b/open-sse/executors/antigravity.ts index 9cf0cb989d..9a6f23b479 100644 --- a/open-sse/executors/antigravity.ts +++ b/open-sse/executors/antigravity.ts @@ -28,15 +28,29 @@ const CREDITS_EXHAUSTED_TTL_MS = 5 * 60 * 60 * 1000; // 5 hours const BARE_PRO_IDS = new Set(["gemini-3.1-pro"]); +function cloneAntigravityRequestBody(body: unknown): unknown { + if (!body || typeof body !== "object") { + return body; + } + + try { + return structuredClone(body); + } catch { + return JSON.parse(JSON.stringify(body)); + } +} + function serializeAntigravityRequest( provider: string, headers: Record, body: unknown ): { headers: Record; bodyString: string } { + const serializedBody = cloneAntigravityRequestBody(body); + if (!isCliCompatEnabled(provider)) { - return { headers, bodyString: JSON.stringify(body) }; + return { headers, bodyString: JSON.stringify(serializedBody) }; } - return applyFingerprint(provider, { ...headers }, body); + return applyFingerprint(provider, { ...headers }, serializedBody); } type AntigravityCollectedStream = { @@ -585,7 +599,11 @@ export class AntigravityExecutor extends BaseExecutor { } try { - const serializedRequest = serializeAntigravityRequest(this.provider, headers, transformedBody); + const serializedRequest = serializeAntigravityRequest( + this.provider, + headers, + transformedBody + ); const finalHeaders = serializedRequest.headers; const response = await fetch(url, { diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index ada44b7279..a1137eec01 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -267,6 +267,32 @@ export class BaseExecutor { void model; void stream; void credentials; + + // Fix #1674: Remove empty string values from optional parameters + // like tool descriptions to avoid upstream validation failures. + if (body && typeof body === "object" && !Array.isArray(body)) { + const cloned = { ...body } as Record; + + if (Array.isArray(cloned.tools)) { + cloned.tools = cloned.tools.map((tool: any) => { + if (tool?.function && typeof tool.function === "object") { + const func = { ...tool.function }; + if (func.description === "") delete func.description; + return { ...tool, function: func }; + } + return tool; + }); + } + + // Also clean up top level optional fields that commonly cause issues when empty + const optionalKeys = ["user", "stop", "seed", "response_format"]; + for (const key of optionalKeys) { + if (cloned[key] === "") delete cloned[key]; + } + + return cloned; + } + return body; } @@ -527,17 +553,25 @@ export class BaseExecutor { const supportsAdaptiveThinking = supportsXHighEffort("claude", model); - if (supportsAdaptiveThinking && !tb.thinking) { + // Fix #1761: Only inject adaptive thinking/high effort if the client didn't + // explicitly set these fields. This allows users to opt-out by sending + // `thinking: null` or `output_config: { effort: "low" }` to prevent forced + // quota drain on Claude Max accounts. + const originalBody = body as Record; + const clientExplicitThinking = originalBody?.thinking !== undefined; + const clientExplicitEffort = originalBody?.output_config !== undefined; + + if (supportsAdaptiveThinking && !tb.thinking && !clientExplicitThinking) { tb.thinking = { type: "adaptive" }; } - if (supportsAdaptiveThinking && !tb.context_management) { + if (supportsAdaptiveThinking && !tb.context_management && !clientExplicitThinking) { tb.context_management = { edits: [{ type: "clear_thinking_20251015", keep: "all" }], }; } - if (supportsAdaptiveThinking && !tb.output_config) { + if (supportsAdaptiveThinking && !tb.output_config && !clientExplicitEffort) { tb.output_config = { effort: "high" }; } diff --git a/open-sse/executors/blackbox-web.ts b/open-sse/executors/blackbox-web.ts index 1a95ecb079..35ec86d45a 100644 --- a/open-sse/executors/blackbox-web.ts +++ b/open-sse/executors/blackbox-web.ts @@ -8,10 +8,21 @@ import { FETCH_TIMEOUT_MS } from "../config/constants.ts"; import { normalizeSessionCookieHeader } from "@/lib/providers/webCookieAuth"; const BLACKBOX_CHAT_API = "https://app.blackbox.ai/api/chat"; -const BLACKBOX_DEFAULT_COOKIE = "__Secure-authjs.session-token"; +const BLACKBOX_DEFAULT_COOKIE = "next-auth.session-token"; const BLACKBOX_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36"; +const SESSION_CACHE_TTL_MS = 5 * 60_000; // 5 minutes + +type CachedSession = { + sessionData: Record | null; + subscriptionCache: Record | null; + teamAccount: string; + fetchedAt: number; +}; + +const sessionCache = new Map(); + type BlackboxMessage = { id: string; role: "user" | "assistant"; @@ -290,14 +301,87 @@ export class BlackboxWebExecutor extends BaseExecutor { } const cookieHeader = normalizeBlackboxCookieHeader(credentials.apiKey || ""); - const headers: Record = { - Accept: "text/plain, */*", - "Content-Type": "application/json", + const baseHeaders: Record = { + Accept: "application/json", Cookie: cookieHeader, Origin: "https://app.blackbox.ai", - Referer: `https://app.blackbox.ai/chat/${chatId}`, "User-Agent": BLACKBOX_USER_AGENT, }; + + // Fetch session + subscription — Blackbox requires these in the request body. + // Cached per cookie to avoid redundant round-trips on every request. + let sessionData: Record | null = null; + let subscriptionCache: Record | null = null; + let teamAccount = ""; + + const cacheKey = cookieHeader; + const cached = sessionCache.get(cacheKey); + if (cached && Date.now() - cached.fetchedAt < SESSION_CACHE_TTL_MS) { + sessionData = cached.sessionData; + subscriptionCache = cached.subscriptionCache; + teamAccount = cached.teamAccount; + log?.debug?.("BLACKBOX-WEB", `Session cache hit (${teamAccount || "no email"})`); + } else { + const sideSignal = signal + ? mergeAbortSignals(signal, AbortSignal.timeout(10_000)) + : AbortSignal.timeout(10_000); + + try { + const sessionRes = await fetch("https://app.blackbox.ai/api/auth/session", { + method: "GET", + headers: { ...baseHeaders, Accept: "application/json" }, + signal: sideSignal, + }); + sessionData = sessionRes.ok ? ((await sessionRes.json()) as Record) : null; + const email = (sessionData as any)?.user?.email as string | undefined; + teamAccount = email || ""; + log?.debug?.("BLACKBOX-WEB", `Session email: ${email ?? "none"}`); + + if (email) { + const subRes = await fetch("https://app.blackbox.ai/api/check-subscription", { + method: "POST", + headers: { ...baseHeaders, "Content-Type": "application/json" }, + body: JSON.stringify({ email }), + signal: sideSignal, + }); + const rawSub = subRes.ok ? ((await subRes.json()) as Record) : null; + if (rawSub) { + subscriptionCache = { + status: rawSub.hasActiveSubscription ? "PREMIUM" : "FREE", + customerId: rawSub.customerId ?? null, + expiryTimestamp: rawSub.expiryTimestamp ?? null, + lastChecked: Date.now(), + isTrialSubscription: rawSub.isTrialSubscription ?? false, + hasPaymentVerificationFailure: false, + verificationFailureTimestamp: null, + requiresAuthentication: false, + isTeam: rawSub.isTeam ?? false, + numSeats: rawSub.numSeats ?? 1, + provider: rawSub.provider ?? null, + previouslySubscribed: rawSub.previouslySubscribed ?? false, + activeInsuffientCredits: rawSub.activeInsuffientCredits ?? false, + }; + log?.debug?.("BLACKBOX-WEB", `Subscription: ${subscriptionCache.status}`); + } + } + + sessionCache.set(cacheKey, { + sessionData, + subscriptionCache, + teamAccount, + fetchedAt: Date.now(), + }); + } catch (diagErr) { + log?.debug?.("BLACKBOX-WEB", `Session/subscription fetch failed (non-fatal): ${diagErr}`); + } + } + + const headers: Record = { + ...baseHeaders, + Accept: "text/plain, */*", + "Content-Type": "application/json", + Referer: `https://app.blackbox.ai/chat/${chatId}`, + }; mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders); const transformedBody = { @@ -343,10 +427,12 @@ export class BlackboxWebExecutor extends BaseExecutor { webMode: false, offlineMode: false, }, - session: null, - isPremium: credentials.providerSpecificData?.isPremium ?? true, - teamAccount: "", - subscriptionCache: null, + session: sessionData, + isPremium: subscriptionCache + ? subscriptionCache.status === "PREMIUM" + : (credentials.providerSpecificData?.isPremium ?? true), + teamAccount, + subscriptionCache, beastMode: false, reasoningMode: false, designerMode: false, @@ -434,6 +520,70 @@ export class BlackboxWebExecutor extends BaseExecutor { } const responseText = (await readTextResponse(upstreamResponse.body, signal)).trim(); + + log?.debug?.("BLACKBOX-WEB", `Response (first 300 chars): ${responseText.slice(0, 300)}`); + log?.debug?.("BLACKBOX-WEB", `userSelectedModel sent: ${transformedBody.userSelectedModel}`); + log?.debug?.("BLACKBOX-WEB", `isPremium sent: ${transformedBody.isPremium}`); + + // Blackbox sometimes returns HTTP 200 with in-band error messages instead of proper HTTP status codes. + // Detect known error patterns and surface them as real errors. + const lowerText = responseText.toLowerCase(); + const isSubscriptionError = + /not upgraded|upgrade to a premium plan|upgrade.required/i.test(responseText) || + lowerText.includes("please upgrade"); + const isAuthError = + /please login|login required|authentication required/i.test(responseText) && + !isSubscriptionError; + const isRateLimit = /rate limit|too many requests/i.test(responseText) && !isSubscriptionError; + + if (isSubscriptionError) { + log?.warn?.("BLACKBOX-WEB", "Blackbox returned subscription error in response body"); + const errorResponse = new Response( + JSON.stringify({ + error: { + message: + "Blackbox reports your account lacks a premium subscription. " + + "If you have a paid plan, re-paste your session cookie from app.blackbox.ai.", + type: "upstream_error", + code: "BLACKBOX_SUBSCRIPTION_REQUIRED", + }, + }), + { status: 402, headers: { "Content-Type": "application/json" } } + ); + return { response: errorResponse, url: BLACKBOX_CHAT_API, headers, transformedBody }; + } + + if (isAuthError) { + log?.warn?.("BLACKBOX-WEB", "Blackbox returned auth error in response body"); + const errorResponse = new Response( + JSON.stringify({ + error: { + message: + "Blackbox session is not authenticated — re-paste next-auth.session-token from app.blackbox.ai", + type: "upstream_error", + code: "BLACKBOX_AUTH_REQUIRED", + }, + }), + { status: 401, headers: { "Content-Type": "application/json" } } + ); + return { response: errorResponse, url: BLACKBOX_CHAT_API, headers, transformedBody }; + } + + if (isRateLimit) { + log?.warn?.("BLACKBOX-WEB", "Blackbox returned rate-limit error in response body"); + const errorResponse = new Response( + JSON.stringify({ + error: { + message: "Blackbox Web rate limited the session. Wait a moment and retry.", + type: "upstream_error", + code: "BLACKBOX_RATE_LIMIT", + }, + }), + { status: 429, headers: { "Content-Type": "application/json" } } + ); + return { response: errorResponse, url: BLACKBOX_CHAT_API, headers, transformedBody }; + } + const id = `chatcmpl-blackbox-${crypto.randomUUID().slice(0, 12)}`; const created = Math.floor(Date.now() / 1000); diff --git a/open-sse/executors/chatgpt-web.ts b/open-sse/executors/chatgpt-web.ts index dca26a18e5..3acc626d8c 100644 --- a/open-sse/executors/chatgpt-web.ts +++ b/open-sse/executors/chatgpt-web.ts @@ -71,11 +71,25 @@ function deviceIdFor(cookie: string): string { return id; } -// OmniRoute model ID → ChatGPT internal slug. ChatGPT's web routes use -// dash-separated IDs (e.g. "gpt-5-3" not "gpt-5.3-instant"). +// OmniRoute model ID → ChatGPT internal slug. OmniRoute uses dot-form IDs +// (e.g. "gpt-5.3-instant"), ChatGPT's web routes use dash-form +// (e.g. "gpt-5-3-instant"). The slug catalog comes from +// /backend-api/models on a logged-in account; "gpt-5-4-t-mini" is ChatGPT's +// abbreviated slug for "GPT-5.4 Thinking Mini". const MODEL_MAP: Record = { - "gpt-5.3-instant": "gpt-5-3", - "gpt-5-3": "gpt-5-3", + "gpt-5.3-instant": "gpt-5-3-instant", + "gpt-5.3": "gpt-5-3", + "gpt-5.3-mini": "gpt-5-3-mini", + "gpt-5.5-thinking": "gpt-5-5-thinking", + "gpt-5.4-thinking": "gpt-5-4-thinking", + "gpt-5.4-thinking-mini": "gpt-5-4-t-mini", + "gpt-5.2-instant": "gpt-5-2-instant", + "gpt-5.2": "gpt-5-2", + "gpt-5.2-thinking": "gpt-5-2-thinking", + "gpt-5.1": "gpt-5-1", + "gpt-5": "gpt-5", + "gpt-5-mini": "gpt-5-mini", + o3: "o3", }; // ─── Browser-like default headers ────────────────────────────────────────── diff --git a/open-sse/executors/codex.ts b/open-sse/executors/codex.ts index 42b3049a7f..49c200c8fb 100644 --- a/open-sse/executors/codex.ts +++ b/open-sse/executors/codex.ts @@ -10,8 +10,22 @@ import { CODEX_DEFAULT_INSTRUCTIONS, } from "../config/codexInstructions.ts"; import { PROVIDERS } from "../config/constants.ts"; -import { getCodexClientVersion, getCodexUserAgent } from "../config/codexClient.ts"; +import { + getCodexClientVersion, + getCodexUserAgent, + normalizeCodexSessionId, +} from "../config/codexClient.ts"; +import { + applyCodexClientIdentityHeaders, + applyCodexClientMetadata, + createCodexClientIdentity, +} from "../config/codexIdentity.ts"; import { getAccessToken } from "../services/tokenRefresh.ts"; +import { + getRememberedFunctionCallsByIds, + getRememberedResponseConversationItems, + getRememberedResponseFunctionCalls, +} from "../services/responsesToolCallState.ts"; import { getThinkingBudgetConfig, ThinkingMode } from "../services/thinkingBudget.ts"; import { CORS_HEADERS } from "../utils/cors.ts"; import { createRequire } from "module"; @@ -253,68 +267,11 @@ export function getCodexUpstreamModel(model: unknown): string { return splitCodexReasoningSuffix(model).baseModel; } -function stringifyCodexInstructionContent(content: unknown): string { - if (typeof content === "string") { - return content.trim(); - } - - if (Array.isArray(content)) { - return content - .map((part) => { - if (typeof part === "string") return part.trim(); - if (!part || typeof part !== "object") return ""; - const record = part as Record; - if (typeof record.text === "string") return record.text.trim(); - if (typeof record.content === "string") return record.content.trim(); - return ""; - }) - .filter(Boolean) - .join("\n") - .trim(); - } - - return ""; -} - -function hoistSystemMessagesToInstructions(body: Record): void { - if (!Array.isArray(body.input)) return; - - const systemChunks: string[] = []; - const filteredInput = body.input.filter((itemValue) => { - if (!itemValue || typeof itemValue !== "object" || Array.isArray(itemValue)) { - return true; - } - - const item = itemValue as Record; - const role = typeof item.role === "string" ? item.role : ""; - const type = typeof item.type === "string" ? item.type : ""; - const isSystemMessage = role === "system" && (!type || type === "message"); - if (!isSystemMessage) { - return true; - } - - const text = stringifyCodexInstructionContent(item.content); - if (text) { - systemChunks.push(text); - } - return false; - }); - - if (systemChunks.length === 0) return; - - const existingInstructions = - typeof body.instructions === "string" ? body.instructions.trim() : ""; - body.instructions = existingInstructions - ? `${systemChunks.join("\n\n")}\n\n${existingInstructions}` - : systemChunks.join("\n\n"); - body.input = filteredInput; -} - /** * Convert role=system messages in `input` to role=developer. * * GPT-5 models support the `developer` role in input, but reject `system`. - * Unlike hoistSystemMessagesToInstructions(), this keeps the content inside + * This keeps the content inside * the `input` array where it benefits from OpenAI's automatic prompt caching. * * OpenAI's prompt caching matches on the serialized prefix of the `input` array @@ -343,6 +300,23 @@ function convertSystemToDeveloperRole(body: Record): void { } } +function buildRecoveredToolContextMessage( + droppedItems: Array> +): Record { + return { + type: "message", + role: "user", + content: [ + { + type: "input_text", + text: + "Recovered tool context from the previous turn. Continue using this context instead of calling the same tools again unless you must.\n" + + JSON.stringify(droppedItems), + }, + ], + }; +} + /** * Strip server-generated item IDs from the input array. * @@ -359,18 +333,155 @@ function convertSystemToDeveloperRole(body: Record): void { * 3. Strips the "id" field from any object in input whose id matches a * server-generated prefix (rs_, fc_, resp_, msg_) — so the content is * preserved but the backend won't try to look it up - * 4. Always deletes previous_response_id (endpoint doesn't persist responses) + * 4. Expands locally remembered conversation snapshots for stateful follow-ups + * when the upstream backend rejects previous_response_id + * 5. Falls back to rehydrating missing function_call items if only the older + * tool-call state is available + * 6. Filters orphaned function_call/function_call_output items when one side + * of the tool exchange is still missing after local replay/fallback repair */ function stripStoredItemReferences(body: Record): void { const hasInput = Array.isArray(body.input) && body.input.length > 0; + const inputItems = Array.isArray(body.input) ? body.input : []; + const previousResponseId = + typeof body.previous_response_id === "string" ? body.previous_response_id : ""; + const rememberedConversationItems = + hasInput && previousResponseId + ? getRememberedResponseConversationItems(previousResponseId) + : []; - // Always strip previous_response_id IF we have input. - // The /codex/responses endpoint does not persist responses, so any reference - // to a previous response would cause a 404. However, if input is missing (e.g. Cursor - // trying to continue generation), stripping it leaves the payload empty causing a 400 Schema error. - // We leave it intact so Codex returns 404, which correctly triggers Cursor's fallback to resend history. - if (hasInput) { - delete body.previous_response_id; + if (rememberedConversationItems.length > 0) { + body.input = [...rememberedConversationItems, ...inputItems]; + } + const inputFunctionCallIds = new Set(); + const inputFunctionCallOutputIds = new Set(); + + for (const item of Array.isArray(body.input) ? body.input : []) { + if (!item || typeof item !== "object" || Array.isArray(item)) continue; + const record = item as Record; + const type = typeof record.type === "string" ? record.type : ""; + const callId = typeof record.call_id === "string" ? record.call_id : ""; + if (!callId) continue; + if (type === "function_call") { + inputFunctionCallIds.add(callId); + continue; + } + if (type === "function_call_output") { + inputFunctionCallOutputIds.add(callId); + } + } + + const missingFunctionCallIds = [...inputFunctionCallOutputIds].filter( + (callId) => !inputFunctionCallIds.has(callId) + ); + + if (hasInput && previousResponseId && missingFunctionCallIds.length > 0) { + const rememberedFunctionCalls = getRememberedResponseFunctionCalls(previousResponseId); + const globallyRememberedFunctionCalls = getRememberedFunctionCallsByIds(missingFunctionCallIds); + const injectedFunctionCalls = [...rememberedFunctionCalls, ...globallyRememberedFunctionCalls] + .filter((functionCall) => missingFunctionCallIds.includes(functionCall.call_id)) + .filter((functionCall) => !inputFunctionCallIds.has(functionCall.call_id)) + .filter( + (functionCall, index, allFunctionCalls) => + allFunctionCalls.findIndex((candidate) => candidate.call_id === functionCall.call_id) === + index + ) + .map((functionCall) => ({ + type: "function_call", + call_id: functionCall.call_id, + name: functionCall.name, + arguments: functionCall.arguments, + })); + + if (injectedFunctionCalls.length > 0) { + body.input = [...injectedFunctionCalls, ...inputItems]; + for (const functionCall of injectedFunctionCalls) { + inputFunctionCallIds.add(functionCall.call_id); + } + } + } + + const finalFunctionCallIds = new Set(); + const finalFunctionCallOutputIds = new Set(); + if (Array.isArray(body.input)) { + for (const item of body.input) { + if (!item || typeof item !== "object" || Array.isArray(item)) continue; + const record = item as Record; + const type = typeof record.type === "string" ? record.type : ""; + const callId = typeof record.call_id === "string" ? record.call_id : ""; + if (!callId) continue; + if (type === "function_call") { + finalFunctionCallIds.add(callId); + continue; + } + if (type === "function_call_output") { + finalFunctionCallOutputIds.add(callId); + } + } + } + + const droppedOrphanFunctionCallIds: string[] = []; + const droppedOrphanFunctionCallOutputIds: string[] = []; + const droppedOrphanItems: Array> = []; + if (Array.isArray(body.input)) { + body.input = body.input.filter((item) => { + if (!item || typeof item !== "object" || Array.isArray(item)) { + return true; + } + + const record = item as Record; + const callId = typeof record.call_id === "string" ? record.call_id : ""; + if (!callId) { + return true; + } + + if (record.type === "function_call") { + if (finalFunctionCallOutputIds.has(callId)) { + return true; + } + + droppedOrphanFunctionCallIds.push(callId); + droppedOrphanItems.push({ ...record }); + return false; + } + + if (record.type === "function_call_output") { + if (finalFunctionCallIds.has(callId)) { + return true; + } + + droppedOrphanFunctionCallOutputIds.push(callId); + droppedOrphanItems.push({ ...record }); + return false; + } + + return true; + }); + } + + if (droppedOrphanFunctionCallIds.length > 0) { + console.warn( + `[Codex] stripStoredItemReferences: dropped ${droppedOrphanFunctionCallIds.length} orphan function_call item(s): ${droppedOrphanFunctionCallIds.join(", ")}` + ); + } + + if (droppedOrphanFunctionCallOutputIds.length > 0) { + console.warn( + `[Codex] stripStoredItemReferences: dropped ${droppedOrphanFunctionCallOutputIds.length} orphan function_call_output item(s): ${droppedOrphanFunctionCallOutputIds.join(", ")}` + ); + } + + if (Array.isArray(body.input) && body.input.length === 0 && droppedOrphanItems.length > 0) { + body.input = [buildRecoveredToolContextMessage(droppedOrphanItems)]; + console.warn( + `[Codex] stripStoredItemReferences: synthesized recovery message from ${droppedOrphanItems.length} dropped orphan tool item(s)` + ); + } + + // Codex rejects previous_response_id for passthrough requests. + delete body.previous_response_id; + if (Array.isArray(body.input) && body.input.length === 0) { + delete body.input; } if (!Array.isArray(body.input)) return; @@ -514,7 +625,7 @@ function getResponsesSubpath(endpointPath: unknown): string | null { return match[1] || ""; } -function isCompactResponsesEndpoint(endpointPath: unknown): boolean { +export function isCompactResponsesEndpoint(endpointPath: unknown): boolean { return getResponsesSubpath(endpointPath)?.toLowerCase() === "/compact"; } @@ -711,21 +822,40 @@ export class CodexExecutor extends BaseExecutor { } async execute(input: ExecuteInput) { - if (!isCodexResponsesWebSocketRequired(input.model, input.credentials)) { - return super.execute(input); + const sessionId = this.getPromptCacheSessionId( + input.credentials, + input.body as Record | null + ); + const identity = createCodexClientIdentity( + sessionId, + input.credentials?.providerSpecificData ?? null + ); + const credentials = identity + ? { + ...input.credentials, + providerSpecificData: { + ...(input.credentials?.providerSpecificData || {}), + codexClientIdentity: identity, + }, + } + : input.credentials; + const nextInput = { ...input, credentials }; + + if (!isCodexResponsesWebSocketRequired(nextInput.model, nextInput.credentials)) { + return super.execute(nextInput); } const url = CODEX_RESPONSES_WS_URL; - const headers = normalizeCodexWsHeaders(this.buildHeaders(input.credentials, true)); - mergeUpstreamExtraHeaders(headers, input.upstreamExtraHeaders); + const headers = normalizeCodexWsHeaders(this.buildHeaders(nextInput.credentials, true)); + mergeUpstreamExtraHeaders(headers, nextInput.upstreamExtraHeaders); const transformedBody = (await this.transformRequest( - input.model, - input.body, + nextInput.model, + nextInput.body, true, - input.credentials + nextInput.credentials )) as Record; - transformedBody.model = getCodexUpstreamModel(transformedBody.model || input.model); + transformedBody.model = getCodexUpstreamModel(transformedBody.model || nextInput.model); delete transformedBody.stream; delete transformedBody.stream_options; @@ -760,7 +890,7 @@ export class CodexExecutor extends BaseExecutor { let abortHandler: (() => void) | null = null; const removeAbortListener = () => { if (!abortHandler) return; - input.signal?.removeEventListener("abort", abortHandler); + nextInput.signal?.removeEventListener("abort", abortHandler); abortHandler = null; }; @@ -821,7 +951,7 @@ export class CodexExecutor extends BaseExecutor { abortHandler = () => { finishStream({ reason: "client_aborted" }); }; - input.signal?.addEventListener("abort", abortHandler, { once: true }); + nextInput.signal?.addEventListener("abort", abortHandler, { once: true }); try { ws = await websocketFn(toWebSocketUrl(url), { @@ -830,7 +960,7 @@ export class CodexExecutor extends BaseExecutor { headers, }); if (closed) return; - if (input.signal?.aborted) { + if (nextInput.signal?.aborted) { finishStream({ reason: "client_aborted" }); return; } @@ -928,6 +1058,7 @@ export class CodexExecutor extends BaseExecutor { if (workspaceId) { headers["chatgpt-account-id"] = workspaceId; } + const clientIdentity = credentials?.providerSpecificData?.codexClientIdentity; // Originator header — identifies the client type to the Codex backend. // Ref: openai/codex login/src/auth/default_client.rs DEFAULT_ORIGINATOR = "codex_cli_rs" @@ -940,6 +1071,7 @@ export class CodexExecutor extends BaseExecutor { if (cacheSessionId) { headers["session_id"] = cacheSessionId; } + applyCodexClientIdentityHeaders(headers, clientIdentity); return headers; } @@ -956,13 +1088,17 @@ export class CodexExecutor extends BaseExecutor { credentials, body: Record | null ): string | null { + const promptCacheKey = normalizeCodexSessionId(body?.prompt_cache_key); + if (promptCacheKey) return promptCacheKey; + // Prefer per-session identifiers from the client request body const sessionId = body?.session_id ?? body?.conversation_id; - if (typeof sessionId === "string" && sessionId.length > 0) { - return sessionId; + const normalizedSessionId = normalizeCodexSessionId(sessionId); + if (normalizedSessionId) { + return normalizedSessionId; } // Fall back to workspaceId (account-wide) — better than nothing - return credentials?.providerSpecificData?.workspaceId || null; + return normalizeCodexSessionId(credentials?.providerSpecificData?.workspaceId) || null; } /** @@ -1061,9 +1197,10 @@ export class CodexExecutor extends BaseExecutor { } } - // Store: The Codex OAuth backend rejects store=true with - // "Store must be set to false". Default to false unless the provider - // explicitly opts in (e.g. API-key accounts that support persistence). + // Store: regular Codex Responses rejects store=true with + // "Store must be set to false", while /responses/compact rejects the + // store field entirely. Default regular requests to false unless the + // provider explicitly opts in (e.g. API-key accounts that support persistence). // Ref: sub2api openai_codex_transform.go line 75-80 const explicitStoreSetting = credentials?.providerSpecificData && @@ -1071,7 +1208,9 @@ export class CodexExecutor extends BaseExecutor { !Array.isArray(credentials.providerSpecificData) ? credentials.providerSpecificData.openaiStoreEnabled : undefined; - if (explicitStoreSetting === true) { + if (isCompactRequest) { + delete body.store; + } else if (explicitStoreSetting === true) { body.store = true; } else { // backend rejects store=true ("Store must be set to false"), so default to false. @@ -1123,16 +1262,19 @@ export class CodexExecutor extends BaseExecutor { } delete body.reasoning_effort; - // previous_response_id: always stripped by stripStoredItemReferences(). - // The /codex/responses endpoint does not persist responses, so any reference - // to a previous response ID would cause a 404. This matches the behavior of - // both the official Codex CLI (sets None) and CLIProxyAPI (deletes the field). + // previous_response_id is expanded into a self-contained local replay when + // input is present because Codex rejects that parameter upstream. // Remove unsupported token limit parameters BEFORE the passthrough return. // Codex API rejects both max_tokens and max_output_tokens regardless of // whether the request came via native passthrough or translation. delete body.max_tokens; delete body.max_output_tokens; + // VS Code Copilot BYOK Responses requests include `truncation` (for example + // "auto" or "disabled"). The Codex /responses backend currently rejects this + // field entirely with 400 Unsupported parameter: truncation, so strip it for + // both native passthrough and translated requests. + delete body.truncation; delete body.background; // Droid CLI sends this but Codex Responses API rejects it // Inject prompt_cache_key for Codex prompt caching. @@ -1146,16 +1288,13 @@ export class CodexExecutor extends BaseExecutor { body.prompt_cache_key = cacheSessionId; } } + applyCodexClientMetadata(body, credentials?.providerSpecificData?.codexClientIdentity); // Delete session_id and conversation_id from the body. // These are often injected by OmniRoute's fallback logic for store=true, - // but the upstream Codex API strictly rejects them as unsupported parameters - // UNLESS the request lacks input entirely (where they are required to avoid a 400 Schema Error). + // but the upstream Codex API strictly rejects them as unsupported parameters. delete body.session_id; - const hasInput = Array.isArray(body.input) && body.input.length > 0; - if (hasInput) { - delete body.conversation_id; - } + delete body.conversation_id; if (nativeCodexPassthrough) { return body; diff --git a/open-sse/executors/default.ts b/open-sse/executors/default.ts index 56696c1687..2f568f7630 100644 --- a/open-sse/executors/default.ts +++ b/open-sse/executors/default.ts @@ -347,7 +347,8 @@ export class DefaultExecutor extends BaseExecutor { */ transformRequest(model, body, stream, credentials) { void model; - let withDefaults = applyProviderRequestDefaults(body, this.config.requestDefaults); + const cleanedBody = super.transformRequest(model, body, stream, credentials); + let withDefaults = applyProviderRequestDefaults(cleanedBody, this.config.requestDefaults); if (typeof withDefaults === "object" && withDefaults !== null && !Array.isArray(withDefaults)) { if (this.provider?.startsWith?.("anthropic-compatible-")) { diff --git a/open-sse/executors/grok-web.ts b/open-sse/executors/grok-web.ts index 0ab6cbf04b..fc97f94799 100644 --- a/open-sse/executors/grok-web.ts +++ b/open-sse/executors/grok-web.ts @@ -19,6 +19,7 @@ import { type ExecuteInput, } from "./base.ts"; import { FETCH_TIMEOUT_MS } from "../config/constants.ts"; +import { extractCookieValue } from "@/lib/providers/webCookieAuth"; // ─── Constants ────────────────────────────────────────────────────────────── @@ -36,14 +37,13 @@ interface GrokModelInfo { } const MODEL_MAP: Record = { - auto: { modeId: "auto", isThinking: false }, fast: { modeId: "fast", isThinking: false }, expert: { modeId: "expert", isThinking: true }, heavy: { modeId: "heavy", isThinking: true }, "grok-420-computer-use-sa": { modeId: "grok-420-computer-use-sa", isThinking: true }, // Legacy aliases retained for manually-entered model IDs. - "grok-4": { modeId: "auto", isThinking: false }, + "grok-4": { modeId: "fast", isThinking: false }, "grok-4.1-fast": { modeId: "fast", isThinking: false }, "grok-4.1-expert": { modeId: "expert", isThinking: true }, "grok-4-heavy": { modeId: "heavy", isThinking: true }, @@ -516,9 +516,9 @@ export class GrokWebExecutor extends BaseExecutor { // Resolve model → Grok Web mode const modelInfo = MODEL_MAP[model]; if (!modelInfo) { - log?.info?.("GROK-WEB", `Unmapped model ${model}, defaulting to auto mode`); + log?.info?.("GROK-WEB", `Unmapped model ${model}, defaulting to fast mode`); } - const { modeId, isThinking } = modelInfo || MODEL_MAP.auto; + const { modeId, isThinking } = modelInfo || MODEL_MAP.fast; // Parse OpenAI messages → single Grok message string const message = parseOpenAIMessages(messages); @@ -592,11 +592,11 @@ export class GrokWebExecutor extends BaseExecutor { traceparent: `00-${traceId}-${spanId}-00`, }; - // Cookie auth — strip "sso=" prefix if user included it + // Cookie auth — accepts a bare value, "sso=", or a full + // DevTools cookie blob; we extract the sso pair and ignore the rest. if (credentials.apiKey) { - let token = credentials.apiKey; - if (token.startsWith("sso=")) token = token.slice(4); - headers["Cookie"] = `sso=${token}`; + const token = extractCookieValue(credentials.apiKey, "sso"); + if (token) headers["Cookie"] = `sso=${token}`; } // Apply upstream extra headers diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 09740e64be..a131a7cbc6 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -85,6 +85,7 @@ import { parseCodexQuotaHeaders, getCodexModelScope, getCodexDualWindowCooldownMs, + isCompactResponsesEndpoint, } from "../executors/codex.ts"; import { invalidateCodexQuotaCache } from "../services/codexQuotaFetcher.ts"; import { translateNonStreamingResponse } from "./responseTranslator.ts"; @@ -621,10 +622,11 @@ function wrapReadableStreamWithFinalize( }, async cancel(reason) { + runFinalize(); try { await reader.cancel(reason); - } finally { - runFinalize(); + } catch (error) { + // Ignored } }, }); @@ -1339,7 +1341,12 @@ export async function handleChatCore({ delete b.streaming; } - const stream = resolveStreamFlag(body?.stream, acceptHeader); + // Codex /responses/compact is JSON-only: Codex CLI does not send stream=false, + // so route shape must override the usual Accept/header fallback. + const stream = + nativeCodexPassthrough && isCompactResponsesEndpoint(endpointPath) + ? false + : resolveStreamFlag(body?.stream, acceptHeader); const settings = await getCachedSettings(); setGeminiThoughtSignatureMode(settings.antigravitySignatureCacheMode); const semanticCacheEnabled = settings.semanticCacheEnabled !== false; @@ -2162,67 +2169,78 @@ export async function handleChatCore({ accountSemaphoreKey && accountSemaphoreMaxConcurrency != null ? await acquireAccountSemaphore(accountSemaphoreKey, { maxConcurrency: accountSemaphoreMaxConcurrency, + signal: streamController.signal, }) : () => {}; try { - const rawResult = await withRateLimit(provider, connectionId, modelToCall, async () => { - let attempts = 0; - const maxAttempts = provider === "qwen" ? 3 : 1; + const rawResult = await withRateLimit( + provider, + connectionId, + modelToCall, + async () => { + let attempts = 0; + const maxAttempts = provider === "qwen" ? 3 : 1; - while (attempts < maxAttempts) { - const res = await executor.execute({ - model: modelToCall, - body: bodyToSend, - stream: upstreamStream, - credentials: executionCredentials, - signal: streamController.signal, - log, - extendedContext, - upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall), - clientHeaders: buildExecutorClientHeaders(clientRawRequest?.headers, userAgent), - onCredentialsRefreshed, - }); + while (attempts < maxAttempts) { + const res = await executor.execute({ + model: modelToCall, + body: bodyToSend, + stream: upstreamStream, + credentials: executionCredentials, + signal: streamController.signal, + log, + extendedContext, + upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall), + clientHeaders: buildExecutorClientHeaders(clientRawRequest?.headers, userAgent), + onCredentialsRefreshed, + }); - // Qwen 429 strict quota backoff (wait 1.5s, 3s and retry) - if (provider === "qwen" && res.response.status === 429 && attempts < maxAttempts - 1) { - const bodyPeek = await res.response - .clone() - .text() - .catch(() => ""); - if (bodyPeek.toLowerCase().includes("exceeded your current quota")) { - const delay = 1500 * (attempts + 1); - log?.warn?.("QWEN_RETRY", `Quota 429 hit. Retrying in ${delay}ms...`); - await new Promise((r) => setTimeout(r, delay)); - attempts++; - continue; - } - } - - // For streaming: release the semaphore when the client drains or cancels the stream. - if (stream) { - const originalBody = res.response.body; - if (!originalBody) { - acquireAccountSemaphoreRelease(); - return res; + // Qwen 429 strict quota backoff (wait 1.5s, 3s and retry) + if ( + provider === "qwen" && + res.response.status === 429 && + attempts < maxAttempts - 1 + ) { + const bodyPeek = await res.response + .clone() + .text() + .catch(() => ""); + if (bodyPeek.toLowerCase().includes("exceeded your current quota")) { + const delay = 1500 * (attempts + 1); + log?.warn?.("QWEN_RETRY", `Quota 429 hit. Retrying in ${delay}ms...`); + await new Promise((r) => setTimeout(r, delay)); + attempts++; + continue; + } } - return { - ...res, - response: new Response( - wrapReadableStreamWithFinalize(originalBody, acquireAccountSemaphoreRelease), - { - status: res.response.status, - statusText: res.response.statusText, - headers: res.response.headers, - } - ), - }; - } + // For streaming: release the semaphore when the client drains or cancels the stream. + if (stream) { + const originalBody = res.response.body; + if (!originalBody) { + acquireAccountSemaphoreRelease(); + return res; + } - return res; - } - }); + return { + ...res, + response: new Response( + wrapReadableStreamWithFinalize(originalBody, acquireAccountSemaphoreRelease), + { + status: res.response.status, + statusText: res.response.statusText, + headers: res.response.headers, + } + ), + }; + } + + return res; + } + }, + streamController.signal + ); if (stream) { return rawResult; @@ -2877,17 +2895,18 @@ export async function handleChatCore({ } else { try { responseBody = rawBody ? JSON.parse(rawBody) : {}; - } catch { + } catch (err) { appendRequestLog({ model, provider, connectionId, status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`, }).catch(() => {}); + const detailedError = `Invalid JSON response from provider (error: ${err instanceof Error ? err.message : String(err)}): ${rawBody.substring(0, 1000)}`; const invalidJsonMessage = "Invalid JSON response from provider"; persistAttemptLogs({ status: HTTP_STATUS.BAD_GATEWAY, - error: invalidJsonMessage, + error: detailedError, providerRequest: finalBody || translatedBody, providerResponse: normalizedProviderPayload, clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, invalidJsonMessage), @@ -3487,6 +3506,7 @@ export async function handleChatCore({ clientResponseFormat === FORMATS.OPENAI && !isResponsesEndpoint && !isDroidCLI; + const streamStateBody = finalBody || body; if (needsResponsesTranslation) { // Provider returns openai-responses, translate to openai (Chat Completions) that clients expect @@ -3499,7 +3519,7 @@ export async function handleChatCore({ responseToolNameMap, model, connectionId, - body, + streamStateBody, onStreamComplete, apiKeyInfo, handleStreamFailure @@ -3515,7 +3535,7 @@ export async function handleChatCore({ responseToolNameMap, model, connectionId, - body, + streamStateBody, onStreamComplete, apiKeyInfo, handleStreamFailure @@ -3528,10 +3548,11 @@ export async function handleChatCore({ responseToolNameMap, model, connectionId, - body, + streamStateBody, onStreamComplete, apiKeyInfo, - handleStreamFailure + handleStreamFailure, + clientResponseFormat ); } diff --git a/open-sse/mcp-server/__tests__/dbHealthTool.test.ts b/open-sse/mcp-server/__tests__/dbHealthTool.test.ts index 9aa7565ca4..850074a7c5 100644 --- a/open-sse/mcp-server/__tests__/dbHealthTool.test.ts +++ b/open-sse/mcp-server/__tests__/dbHealthTool.test.ts @@ -7,15 +7,30 @@ import { MCP_TOOL_MAP, dbHealthCheckInput } from "../schemas/tools.ts"; const mockFetch = vi.fn(); vi.stubGlobal("fetch", mockFetch); +const mockRunManagedDbHealthCheck = vi.hoisted(() => vi.fn()); + vi.mock("../audit.ts", () => ({ logToolCall: vi.fn().mockResolvedValue(undefined), })); +vi.mock("../../../src/lib/db/core.ts", () => ({ + runManagedDbHealthCheck: mockRunManagedDbHealthCheck, +})); + describe("omniroute_db_health_check MCP tool", () => { let client: Client; beforeEach(async () => { mockFetch.mockReset(); + mockRunManagedDbHealthCheck.mockReset(); + mockRunManagedDbHealthCheck.mockReturnValue({ + isHealthy: false, + issues: [{ type: "broken_reference", table: "combos", description: "broken", count: 1 }], + repairedCount: 1, + backupCreated: true, + autoRepair: true, + checkedAt: new Date().toISOString(), + }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const server = createMcpServer(); await server.connect(serverTransport); @@ -38,32 +53,19 @@ describe("omniroute_db_health_check MCP tool", () => { expect(dbHealthCheckInput.safeParse({ autoRepair: "yes" }).success).toBe(false); }); - it("dispatches to /api/v1/db/health using POST when autoRepair=true", async () => { - mockFetch.mockResolvedValueOnce({ - ok: true, - json: async () => ({ - isHealthy: false, - issues: [{ type: "broken_reference", table: "combos", description: "broken", count: 1 }], - repairedCount: 1, - backupCreated: true, - autoRepair: true, - checkedAt: new Date().toISOString(), - }), - }); - + it("runs the database repair flow directly when autoRepair=true", async () => { const result = await client.callTool({ name: "omniroute_db_health_check", arguments: { autoRepair: true }, }); expect(result.isError).toBeFalsy(); - expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining("/api/v1/db/health"), - expect.objectContaining({ method: "POST" }) - ); + expect(mockFetch).not.toHaveBeenCalled(); + expect(mockRunManagedDbHealthCheck).toHaveBeenCalledWith({ autoRepair: true }); const content = result.content[0] as { type: string; text: string }; const payload = JSON.parse(content.text); + expect(payload.autoRepair).toBe(true); expect(payload.repairedCount).toBe(1); expect(payload.backupCreated).toBe(true); }); diff --git a/open-sse/mcp-server/schemas/tools.ts b/open-sse/mcp-server/schemas/tools.ts index 85492cde97..047d877f79 100644 --- a/open-sse/mcp-server/schemas/tools.ts +++ b/open-sse/mcp-server/schemas/tools.ts @@ -882,7 +882,7 @@ export const dbHealthCheckTool: McpToolDefinition< scopes: ["read:health", "write:resilience"], auditLevel: "full", phase: 2, - sourceEndpoints: ["/api/v1/db/health"], + sourceEndpoints: ["/api/db/health"], }; // --- Tool 19: omniroute_sync_pricing --- diff --git a/open-sse/mcp-server/tools/advancedTools.ts b/open-sse/mcp-server/tools/advancedTools.ts index e9918f491b..9769244f88 100644 --- a/open-sse/mcp-server/tools/advancedTools.ts +++ b/open-sse/mcp-server/tools/advancedTools.ts @@ -893,11 +893,8 @@ export async function handleDbHealthCheck(args: { autoRepair?: boolean }) { const autoRepair = args.autoRepair === true; try { - const result = toRecord( - await apiFetch("/api/v1/db/health", { - method: autoRepair ? "POST" : "GET", - }) - ); + const { runManagedDbHealthCheck } = await import("../../../src/lib/db/core.ts"); + const result = runManagedDbHealthCheck({ autoRepair }); await logToolCall( "omniroute_db_health_check", diff --git a/open-sse/package.json b/open-sse/package.json index 4d7bf34fe6..b57e8dec92 100644 --- a/open-sse/package.json +++ b/open-sse/package.json @@ -1,6 +1,6 @@ { "name": "@omniroute/open-sse", - "version": "3.7.4", + "version": "3.7.5", "description": "Express SSE sidecar for OmniRoute — handles streaming, protocol translation, and provider orchestration", "type": "module", "main": "index.js", diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index c1b02de5d9..eec3afcadb 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -56,7 +56,7 @@ type ModelFailureState = { // Provider-level failure tracking for circuit breaker behavior // Error codes that count toward provider-level failure threshold -const PROVIDER_FAILURE_ERROR_CODES = new Set([408, 500, 502, 503, 504]); +const PROVIDER_FAILURE_ERROR_CODES = new Set([408, 429, 500, 502, 503, 504]); // T06 (sub2api PR #1037): Signals that indicate permanent account deactivation. // When a 401 body contains these strings, the account is permanently dead diff --git a/open-sse/services/accountSemaphore.ts b/open-sse/services/accountSemaphore.ts index 0f96dec799..d210dedf06 100644 --- a/open-sse/services/accountSemaphore.ts +++ b/open-sse/services/accountSemaphore.ts @@ -28,6 +28,7 @@ interface AccountGate { export interface AcquireAccountSemaphoreOptions { maxConcurrency?: number | null; timeoutMs?: number; + signal?: AbortSignal | null; } export interface AccountSemaphoreStatsEntry { @@ -168,18 +169,34 @@ function createSemaphoreTimeoutError( return error; } +function makeAbortError(signal: AbortSignal): Error { + const reason = signal.reason; + if (reason instanceof Error) return reason; + const err = new Error(typeof reason === "string" ? reason : "The operation was aborted"); + err.name = "AbortError"; + return err; +} + /** * Acquire a slot for a provider/model/account tuple. * Returns an idempotent release function that is safe to call in finally blocks. */ export function acquire( semaphoreKey: string, - { maxConcurrency = null, timeoutMs = DEFAULT_TIMEOUT_MS }: AcquireAccountSemaphoreOptions = {} + { + maxConcurrency = null, + timeoutMs = DEFAULT_TIMEOUT_MS, + signal = null, + }: AcquireAccountSemaphoreOptions = {} ): Promise<() => void> { if (isBypassed(maxConcurrency)) { return Promise.resolve(createNoopReleaseFn()); } + if (signal?.aborted) { + return Promise.reject(makeAbortError(signal)); + } + const gate = ensureGate(semaphoreKey, maxConcurrency); clearCleanupTimer(gate); @@ -189,7 +206,16 @@ export function acquire( } return new Promise((resolve, reject) => { + let abortListener: (() => void) | null = null; + + const cleanup = () => { + if (abortListener && signal) { + signal.removeEventListener("abort", abortListener); + } + }; + const timer = setTimeout(() => { + cleanup(); const nextGate = gates.get(semaphoreKey); if (!nextGate) { reject(createSemaphoreTimeoutError(semaphoreKey, timeoutMs)); @@ -209,7 +235,49 @@ export function acquire( }, timeoutMs); timer.unref?.(); - gate.queue.push({ resolve, reject, timer }); + + const queueItem: QueuedAcquire = { + resolve: (release) => { + cleanup(); + resolve(release); + }, + reject: (error) => { + cleanup(); + reject(error); + }, + timer, + }; + + gate.queue.push(queueItem); + + if (signal) { + abortListener = () => { + cleanup(); + clearTimeout(timer); + + const nextGate = gates.get(semaphoreKey); + if (!nextGate) { + reject(makeAbortError(signal)); + return; + } + + const queueIndex = nextGate.queue.findIndex((item) => item.timer === timer); + if (queueIndex !== -1) { + nextGate.queue.splice(queueIndex, 1); + } + + if (nextGate.running === 0 && nextGate.queue.length === 0) { + scheduleCleanup(semaphoreKey); + } + + reject(makeAbortError(signal)); + }; + if (signal.aborted) { + abortListener(); + } else { + signal.addEventListener("abort", abortListener); + } + } }); } diff --git a/open-sse/services/antigravityHeaderScrub.ts b/open-sse/services/antigravityHeaderScrub.ts index 5ecdd91627..200e324957 100644 --- a/open-sse/services/antigravityHeaderScrub.ts +++ b/open-sse/services/antigravityHeaderScrub.ts @@ -52,7 +52,8 @@ export function scrubProxyAndFingerprintHeaders( ): Record { const cleaned: Record = {}; for (const [key, value] of Object.entries(headers)) { - if (!HEADERS_TO_REMOVE.includes(key.toLowerCase())) { + const lowerKey = key.toLowerCase(); + if (!lowerKey.startsWith("x-omniroute-") && !HEADERS_TO_REMOVE.includes(lowerKey)) { cleaned[key] = value; } } diff --git a/open-sse/services/antigravityHeaders.ts b/open-sse/services/antigravityHeaders.ts index 3e6b632337..4b38e90cf2 100644 --- a/open-sse/services/antigravityHeaders.ts +++ b/open-sse/services/antigravityHeaders.ts @@ -20,7 +20,8 @@ export const GEMINI_CLI_VERSION = "0.39.1"; export const GEMINI_SDK_VERSION = "1.30.0"; export const NODE_VERSION = "v22.21.1"; export const ANTIGRAVITY_LOAD_CODE_ASSIST_USER_AGENT = "google-api-nodejs-client/10.3.0"; -export const ANTIGRAVITY_LOAD_CODE_ASSIST_API_CLIENT = "google-cloud-sdk vscode_cloudshelleditor/0.1"; +export const ANTIGRAVITY_LOAD_CODE_ASSIST_API_CLIENT = + "google-cloud-sdk vscode_cloudshelleditor/0.1"; const LOAD_CODE_ASSIST_METADATA = Object.freeze({ ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", @@ -133,6 +134,4 @@ export function googApiClientHeader(): string { return `google-genai-sdk/${GEMINI_SDK_VERSION} gl-node/${NODE_VERSION}`; } -export { - ANTIGRAVITY_VERSION, -}; +export { ANTIGRAVITY_VERSION }; diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 127fcbb6ae..0988c224fe 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -127,7 +127,7 @@ function toTrimmedString(value): string | null { * 1. Body is valid JSON * 2. Has at least one choice with non-empty content or tool_calls */ -async function validateResponseQuality( +export async function validateResponseQuality( response: Response, isStreaming: boolean, log: { warn?: (...args: unknown[]) => void } @@ -161,7 +161,7 @@ async function validateResponseQuality( try { json = JSON.parse(text); } catch { - if (text.startsWith("data:")) return { valid: true }; + if (text.startsWith("data:") || text.startsWith("event:")) return { valid: true }; return { valid: false, reason: "response is not valid JSON" }; } diff --git a/open-sse/services/rateLimitManager.ts b/open-sse/services/rateLimitManager.ts index 4af4f86306..d0acd2410f 100644 --- a/open-sse/services/rateLimitManager.ts +++ b/open-sse/services/rateLimitManager.ts @@ -274,18 +274,57 @@ function getLimiter(provider, connectionId, model = null) { * @param {string} connectionId - Connection ID * @param {string} model - Model name (optional, for per-model limits) * @param {Function} fn - The async function to execute (e.g., executor.execute) + * @param {AbortSignal} signal - Optional abort signal to cancel waiting * @returns {Promise} Result of fn() */ -export async function withRateLimit(provider, connectionId, model, fn) { +export async function withRateLimit(provider, connectionId, model, fn, signal = null) { if (!enabledConnections.has(connectionId)) { return fn(); } + if (signal?.aborted) { + const reason = signal.reason; + if (reason instanceof Error) throw reason; + const err = new Error(typeof reason === "string" ? reason : "The operation was aborted"); + err.name = "AbortError"; + throw err; + } + const limiter = getLimiter(provider, connectionId, model); const maxWaitMs = currentRequestQueueSettings.maxWaitMs; const scheduleOpts = maxWaitMs && maxWaitMs > 0 ? { expiration: maxWaitMs } : {}; + try { - return await limiter.schedule(scheduleOpts, fn); + if (signal) { + let abortListener: (() => void) | undefined; + const abortPromise = new Promise((_, reject) => { + const onAbort = () => { + const reason = signal.reason; + const err = + reason instanceof Error + ? reason + : new Error(typeof reason === "string" ? reason : "The operation was aborted"); + err.name = "AbortError"; + reject(err); + }; + if (signal.aborted) { + onAbort(); + return; + } + abortListener = onAbort; + signal.addEventListener("abort", abortListener, { once: true }); + }); + + try { + return await Promise.race([limiter.schedule(scheduleOpts, fn), abortPromise]); + } finally { + if (abortListener) { + signal.removeEventListener("abort", abortListener); + } + } + } else { + return await limiter.schedule(scheduleOpts, fn); + } } catch (err) { // Bottleneck throws when a job exceeds its expiration timeout. // Surface as a clear rate-limit timeout so callers can fallback. diff --git a/open-sse/services/responsesToolCallState.ts b/open-sse/services/responsesToolCallState.ts new file mode 100644 index 0000000000..9f1af5f5bb --- /dev/null +++ b/open-sse/services/responsesToolCallState.ts @@ -0,0 +1,219 @@ +type JsonRecord = Record; + +type RememberedFunctionCall = { + call_id: string; + name: string; + arguments: string; +}; + +type RememberedResponseToolState = { + functionCalls: RememberedFunctionCall[]; + conversationItems: unknown[]; + expiresAt: number; + updatedAt: number; +}; + +type RememberedFunctionCallByIdState = RememberedFunctionCall & { + expiresAt: number; + updatedAt: number; +}; + +const RESPONSE_TOOL_CALL_TTL_MS = 30 * 60 * 1000; +const RESPONSE_TOOL_CALL_CACHE_MAX_ENTRIES = 512; + +const rememberedResponseToolCalls = new Map(); +const rememberedFunctionCallsById = new Map(); + +function toRecord(value: unknown): JsonRecord | null { + return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null; +} + +function cleanupRememberedResponseToolCalls(now: number = Date.now()) { + for (const [responseId, entry] of rememberedResponseToolCalls.entries()) { + if (entry.expiresAt <= now) { + rememberedResponseToolCalls.delete(responseId); + } + } + + for (const [callId, entry] of rememberedFunctionCallsById.entries()) { + if (entry.expiresAt <= now) { + rememberedFunctionCallsById.delete(callId); + } + } + + if (rememberedResponseToolCalls.size <= RESPONSE_TOOL_CALL_CACHE_MAX_ENTRIES) { + if (rememberedFunctionCallsById.size <= RESPONSE_TOOL_CALL_CACHE_MAX_ENTRIES) { + return; + } + } + + if (rememberedResponseToolCalls.size > RESPONSE_TOOL_CALL_CACHE_MAX_ENTRIES) { + const oldestEntries = [...rememberedResponseToolCalls.entries()].sort( + (a, b) => a[1].updatedAt - b[1].updatedAt + ); + + while (rememberedResponseToolCalls.size > RESPONSE_TOOL_CALL_CACHE_MAX_ENTRIES) { + const oldest = oldestEntries.shift(); + if (!oldest) break; + rememberedResponseToolCalls.delete(oldest[0]); + } + } + + if (rememberedFunctionCallsById.size > RESPONSE_TOOL_CALL_CACHE_MAX_ENTRIES) { + const oldestCallEntries = [...rememberedFunctionCallsById.entries()].sort( + (a, b) => a[1].updatedAt - b[1].updatedAt + ); + + while (rememberedFunctionCallsById.size > RESPONSE_TOOL_CALL_CACHE_MAX_ENTRIES) { + const oldest = oldestCallEntries.shift(); + if (!oldest) break; + rememberedFunctionCallsById.delete(oldest[0]); + } + } +} + +export function rememberResponseFunctionCalls( + responseId: unknown, + outputItems: readonly unknown[] +) { + const normalizedResponseId = typeof responseId === "string" ? responseId.trim() : ""; + if (!normalizedResponseId || !Array.isArray(outputItems) || outputItems.length === 0) { + return; + } + + const existingEntry = rememberedResponseToolCalls.get(normalizedResponseId); + + const functionCalls: RememberedFunctionCall[] = []; + + for (const item of outputItems) { + const record = toRecord(item); + if (!record || record.type !== "function_call") continue; + + const callId = typeof record.call_id === "string" ? record.call_id.trim() : ""; + const name = typeof record.name === "string" ? record.name.trim() : ""; + const argumentsValue = + typeof record.arguments === "string" + ? record.arguments + : JSON.stringify(record.arguments ?? {}); + + if (!callId || !name) continue; + + functionCalls.push({ + call_id: callId, + name, + arguments: argumentsValue, + }); + } + + if (functionCalls.length === 0) { + return; + } + + cleanupRememberedResponseToolCalls(); + + const now = Date.now(); + for (const functionCall of functionCalls) { + rememberedFunctionCallsById.set(functionCall.call_id, { + ...functionCall, + updatedAt: now, + expiresAt: now + RESPONSE_TOOL_CALL_TTL_MS, + }); + } + + rememberedResponseToolCalls.set(normalizedResponseId, { + functionCalls, + conversationItems: existingEntry?.conversationItems?.map((item) => structuredClone(item)) || [], + updatedAt: now, + expiresAt: now + RESPONSE_TOOL_CALL_TTL_MS, + }); +} + +export function rememberResponseConversationState( + responseId: unknown, + requestInput: readonly unknown[], + outputItems: readonly unknown[] +) { + const normalizedResponseId = typeof responseId === "string" ? responseId.trim() : ""; + if (!normalizedResponseId) { + return; + } + + const normalizedRequestInput = Array.isArray(requestInput) ? requestInput : []; + const normalizedOutputItems = Array.isArray(outputItems) ? outputItems : []; + const conversationItems = [...normalizedRequestInput, ...normalizedOutputItems]; + if (conversationItems.length === 0) { + return; + } + + cleanupRememberedResponseToolCalls(); + + const existingEntry = rememberedResponseToolCalls.get(normalizedResponseId); + rememberedResponseToolCalls.set(normalizedResponseId, { + functionCalls: existingEntry?.functionCalls?.map((functionCall) => ({ ...functionCall })) || [], + conversationItems: conversationItems.map((item) => structuredClone(item)), + updatedAt: Date.now(), + expiresAt: Date.now() + RESPONSE_TOOL_CALL_TTL_MS, + }); +} + +export function getRememberedResponseFunctionCalls(responseId: unknown): RememberedFunctionCall[] { + cleanupRememberedResponseToolCalls(); + + const normalizedResponseId = typeof responseId === "string" ? responseId.trim() : ""; + if (!normalizedResponseId) { + return []; + } + + const entry = rememberedResponseToolCalls.get(normalizedResponseId); + if (!entry) { + return []; + } + + return entry.functionCalls.map((functionCall) => ({ ...functionCall })); +} + +export function getRememberedResponseConversationItems(responseId: unknown): unknown[] { + cleanupRememberedResponseToolCalls(); + + const normalizedResponseId = typeof responseId === "string" ? responseId.trim() : ""; + if (!normalizedResponseId) { + return []; + } + + const entry = rememberedResponseToolCalls.get(normalizedResponseId); + if (!entry) { + return []; + } + + return entry.conversationItems.map((item) => structuredClone(item)); +} + +export function getRememberedFunctionCallsByIds( + callIds: readonly string[] +): RememberedFunctionCall[] { + cleanupRememberedResponseToolCalls(); + + if (!Array.isArray(callIds) || callIds.length === 0) { + return []; + } + + const remembered: RememberedFunctionCall[] = []; + for (const rawCallId of callIds) { + const callId = typeof rawCallId === "string" ? rawCallId.trim() : ""; + if (!callId) continue; + const entry = rememberedFunctionCallsById.get(callId); + if (!entry) continue; + remembered.push({ + call_id: entry.call_id, + name: entry.name, + arguments: entry.arguments, + }); + } + + return remembered; +} + +export function clearRememberedResponseFunctionCallsForTesting() { + rememberedResponseToolCalls.clear(); + rememberedFunctionCallsById.clear(); +} diff --git a/open-sse/services/tokenRefresh.ts b/open-sse/services/tokenRefresh.ts index 5147c036dc..e1324acced 100755 --- a/open-sse/services/tokenRefresh.ts +++ b/open-sse/services/tokenRefresh.ts @@ -513,11 +513,7 @@ export async function refreshKiroToken( // AWS SSO OIDC (Builder ID or IDC) // If clientId and clientSecret exist, assume AWS SSO OIDC (default to builder-id if authMethod not specified) if (clientId && clientSecret) { - const isIDC = authMethod === "idc"; - const endpoint = - isIDC && region - ? `https://oidc.${region}.amazonaws.com/token` - : "https://oidc.us-east-1.amazonaws.com/token"; + const endpoint = `https://oidc.${region || "us-east-1"}.amazonaws.com/token`; const response = await runWithProxyContext(proxyConfig, () => fetch(endpoint, { diff --git a/open-sse/translator/helpers/geminiToolsSanitizer.ts b/open-sse/translator/helpers/geminiToolsSanitizer.ts index e35a9df7b1..d76f23cf7c 100644 --- a/open-sse/translator/helpers/geminiToolsSanitizer.ts +++ b/open-sse/translator/helpers/geminiToolsSanitizer.ts @@ -30,12 +30,17 @@ function normalizeGeminiToolName( options: GeminiToolSanitizationOptions = {} ): string { const trimmed = name.trim(); - if (!options.stripNamespace) { - return trimmed; - } + const namespaceStripped = !options.stripNamespace + ? trimmed + : (() => { + const namespaceIndex = trimmed.indexOf(":"); + return namespaceIndex >= 0 ? trimmed.slice(namespaceIndex + 1) : trimmed; + })(); - const namespaceIndex = trimmed.indexOf(":"); - return namespaceIndex >= 0 ? trimmed.slice(namespaceIndex + 1) : trimmed; + return namespaceStripped + .replace(/[^a-zA-Z0-9_]/g, "_") + .replace(/_+/g, "_") + .replace(/^_+|_+$/g, ""); } function buildHashedGeminiToolName( diff --git a/open-sse/translator/helpers/schemaCoercion.ts b/open-sse/translator/helpers/schemaCoercion.ts index ecce28a0a7..17030c1703 100644 --- a/open-sse/translator/helpers/schemaCoercion.ts +++ b/open-sse/translator/helpers/schemaCoercion.ts @@ -52,6 +52,11 @@ export function coerceSchemaNumericFields(schema: unknown): unknown { const result: JsonRecord = { ...schema }; + // Fix #1782: Strip 'default' property to prevent upstream models from eagerly injecting optional fields + if ("default" in result) { + delete result.default; + } + for (const field of NUMERIC_SCHEMA_FIELDS) { if (field in result) { result[field] = coerceNumericString(result[field]); diff --git a/open-sse/translator/request/claude-to-gemini.ts b/open-sse/translator/request/claude-to-gemini.ts index ff5e2a2803..fb3665c790 100644 --- a/open-sse/translator/request/claude-to-gemini.ts +++ b/open-sse/translator/request/claude-to-gemini.ts @@ -7,6 +7,7 @@ import { } from "../helpers/geminiHelper.ts"; import { DEFAULT_THINKING_GEMINI_SIGNATURE } from "../../config/defaultThinkingSignature.ts"; import { buildGeminiTools, sanitizeGeminiToolName } from "../helpers/geminiToolsSanitizer.ts"; +import { capMaxOutputTokens } from "../../../src/lib/modelCapabilities.ts"; /** * Direct Claude → Gemini request translator. @@ -49,7 +50,7 @@ export function claudeToGeminiRequest(model, body, stream) { result.generationConfig.topK = body.top_k; } if (body.max_tokens !== undefined) { - result.generationConfig.maxOutputTokens = body.max_tokens; + result.generationConfig.maxOutputTokens = capMaxOutputTokens(model, body.max_tokens); } // ── System instruction ───────────────────────────────────────── @@ -62,7 +63,7 @@ export function claudeToGeminiRequest(model, body, stream) { } if (systemText) { result.systemInstruction = { - role: "user", + role: "system", parts: [{ text: systemText }], }; } @@ -177,7 +178,9 @@ export function claudeToGeminiRequest(model, body, stream) { // ── Thinking config ──────────────────────────────────────────── // Priority: thinking.budget_tokens (Claude native) > output_config.effort (Claude Code). - if (body.thinking?.type === "enabled" && body.thinking.budget_tokens) { + if (model.startsWith("gemma-4")) { + // gemma-4 models returns - 400: Thinking budget is not supported for this model + } else if (body.thinking?.type === "enabled" && body.thinking.budget_tokens) { result.generationConfig.thinkingConfig = { thinkingBudget: body.thinking.budget_tokens, includeThoughts: true, diff --git a/open-sse/translator/request/openai-to-gemini.ts b/open-sse/translator/request/openai-to-gemini.ts index 73c20e5bc8..e5f1bed467 100644 --- a/open-sse/translator/request/openai-to-gemini.ts +++ b/open-sse/translator/request/openai-to-gemini.ts @@ -27,6 +27,12 @@ import { } from "../helpers/geminiHelper.ts"; import { buildGeminiTools, sanitizeGeminiToolName } from "../helpers/geminiToolsSanitizer.ts"; +// Observed Antigravity wrapper output cap, not an underlying model capability. +// Keep this bridge-local: capMaxOutputTokens() falls back to OmniRoute's generic +// 8192 default for unknown Claude-family IDs, while Antigravity currently caps +// visible output around 16K. See: https://github.com/keisksw/antigravity-output-analysis +const ANTIGRAVITY_CLAUDE_MAX_OUTPUT_TOKENS = 16_384; + type GeminiPart = Record; type GeminiContent = { role: string; parts: GeminiPart[] }; @@ -186,7 +192,7 @@ function openaiToGeminiBase(model, body, stream, toolNameOptions: GeminiToolName if (systemText) { if (!result.systemInstruction) { result.systemInstruction = { - role: "user", + role: "system", parts: [{ text: systemText }], }; } else { @@ -413,7 +419,7 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra if (envelope.request.systemInstruction?.parts) { envelope.request.systemInstruction.parts.unshift(defaultPart); } else { - envelope.request.systemInstruction = { role: "user", parts: [defaultPart] }; + envelope.request.systemInstruction = { role: "system", parts: [defaultPart] }; } // Add toolConfig for Antigravity @@ -430,7 +436,20 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra return envelope; } -function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = null) { +function getAntigravityClaudeOutputTokens(body: Record): number { + const requested = body.max_tokens ?? body.max_completion_tokens; + if (typeof requested === "number" && Number.isFinite(requested) && requested >= 1) { + return Math.min(Math.floor(requested), ANTIGRAVITY_CLAUDE_MAX_OUTPUT_TOKENS); + } + return ANTIGRAVITY_CLAUDE_MAX_OUTPUT_TOKENS; +} + +function wrapInCloudCodeEnvelopeForClaude( + model, + claudeRequest, + credentials = null, + sourceBody = {} +) { const toolNameMap = new Map(); const sanitizeToolName = (name: string) => sanitizeGeminiToolName(name, { @@ -449,6 +468,11 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu const cleanModel = model.includes("/") ? model.split("/").pop()! : model; + const generationConfig: GeminiGenerationConfig = { + temperature: claudeRequest.temperature || 1, + maxOutputTokens: getAntigravityClaudeOutputTokens(sourceBody), + }; + const envelope: CloudCodeEnvelope = { project: projectId, model: cleanModel, @@ -458,10 +482,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu request: { sessionId: generateSessionId(), contents: [], - generationConfig: { - temperature: claudeRequest.temperature || 1, - maxOutputTokens: claudeRequest.max_tokens || 4096, - }, + generationConfig, }, }; @@ -555,7 +576,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu } } - envelope.request.systemInstruction = { role: "user", parts: systemParts }; + envelope.request.systemInstruction = { role: "system", parts: systemParts }; const changedToolNameMap = buildChangedToolNameMap(toolNameMap); if (changedToolNameMap) { @@ -571,7 +592,7 @@ export function openaiToAntigravityRequest(model, body, stream, credentials = nu if (isClaude) { const claudeRequest = openaiToClaudeRequestForAntigravity(model, body, stream); - return wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials); + return wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials, body); } const geminiCLI = openaiToGeminiCLIRequest(model, body, stream); diff --git a/open-sse/utils/stream.ts b/open-sse/utils/stream.ts index 7675ef04e3..cd2b16863e 100644 --- a/open-sse/utils/stream.ts +++ b/open-sse/utils/stream.ts @@ -28,6 +28,10 @@ import { sanitizeStreamingChunk, extractThinkingFromContent, } from "../handlers/responseSanitizer.ts"; +import { + rememberResponseConversationState, + rememberResponseFunctionCalls, +} from "../services/responsesToolCallState.ts"; import { buildErrorBody } from "./error.ts"; /** @@ -54,6 +58,48 @@ export { COLORS, formatSSE }; type JsonRecord = Record; +function buildResponsesOutputItemKey(item: unknown): string | null { + if (!item || typeof item !== "object" || Array.isArray(item)) { + return null; + } + + const record = item as JsonRecord; + const type = typeof record.type === "string" ? record.type : ""; + const id = typeof record.id === "string" ? record.id : ""; + const callId = typeof record.call_id === "string" ? record.call_id : ""; + const outputIndex = typeof record.output_index === "number" ? record.output_index : ""; + const name = typeof record.name === "string" ? record.name : ""; + + if (!type && !id && !callId) { + return null; + } + + return `${type}:${id}:${callId}:${outputIndex}:${name}`; +} + +function pushUniqueResponsesOutputItems(target: unknown[], items: readonly unknown[]) { + const seen = new Set(); + + for (const existingItem of target) { + const key = buildResponsesOutputItemKey(existingItem); + if (key) { + seen.add(key); + } + } + + for (const item of items) { + const key = buildResponsesOutputItemKey(item); + if (key && seen.has(key)) { + continue; + } + + target.push(item); + if (key) { + seen.add(key); + } + } +} + type StreamLogger = { appendProviderChunk?: (value: string) => void; appendConvertedChunk?: (value: string) => void; @@ -80,6 +126,7 @@ type StreamOptions = { mode?: string; targetFormat?: string; sourceFormat?: string; + clientResponseFormat?: string | null; provider?: string | null; reqLogger?: StreamLogger | null; toolNameMap?: unknown; @@ -476,6 +523,7 @@ export function createSSEStream(options: StreamOptions = {}) { mode = STREAM_MODE.TRANSLATE, targetFormat, sourceFormat, + clientResponseFormat = null, provider = null, reqLogger = null, toolNameMap = null, @@ -487,6 +535,11 @@ export function createSSEStream(options: StreamOptions = {}) { onFailure = null, } = options; + const clientExpectsResponsesStream = + (mode === STREAM_MODE.PASSTHROUGH + ? clientResponseFormat === FORMATS.OPENAI_RESPONSES + : sourceFormat === FORMATS.OPENAI_RESPONSES) === true; + let buffer = ""; let usage: UsageTokenRecord | null = null; /** Passthrough (OpenAI CC shape): saw tool_calls in stream before finish_reason */ @@ -516,6 +569,10 @@ export function createSSEStream(options: StreamOptions = {}) { // used to backfill `response.completed.response.output` when upstream returns it // empty (which happens when `store: false` — see backfillResponsesCompletedOutput). const passthroughResponsesOutputItems: unknown[] = []; + const passthroughResponsesPendingFunctionCalls = new Map(); + let passthroughResponsesId: string | null = null; + let passthroughResponsesCurrentFunctionCallKey: string | null = null; + const passthroughResponsesReasoningSummarySeen = new Set(); const streamStartedAt = Date.now(); // Guard against duplicate [DONE] events — ensures exactly one per stream @@ -683,6 +740,101 @@ export function createSSEStream(options: StreamOptions = {}) { controller.enqueue(encoder.encode(comment)); }; + const getResponsesReasoningKey = (payload: Record): string | null => { + if (typeof payload.item_id === "string" && payload.item_id) { + return payload.item_id; + } + + const item = + payload.item && typeof payload.item === "object" && !Array.isArray(payload.item) + ? (payload.item as Record) + : null; + if (item && typeof item.id === "string" && item.id) { + return item.id; + } + + const responseId = + typeof payload.response_id === "string" && payload.response_id + ? payload.response_id + : passthroughResponsesId; + const outputIndex = + typeof payload.output_index === "number" && Number.isInteger(payload.output_index) + ? payload.output_index + : null; + + return responseId !== null && outputIndex !== null ? `${responseId}:${outputIndex}` : null; + }; + + const emitSyntheticResponsesReasoningSummary = ( + controller: TransformStreamDefaultController, + payload: Record + ) => { + const item = + payload.item && typeof payload.item === "object" && !Array.isArray(payload.item) + ? (payload.item as Record) + : null; + if (!item || item.type !== "reasoning" || !Array.isArray(item.summary)) { + return; + } + + const summaryText = item.summary + .map((part) => { + if (!part || typeof part !== "object" || Array.isArray(part)) { + return ""; + } + return typeof (part as Record).text === "string" + ? ((part as Record).text as string) + : ""; + }) + .join(""); + + if (!summaryText) { + return; + } + + const reasoningKey = getResponsesReasoningKey(payload); + if (!reasoningKey || passthroughResponsesReasoningSummarySeen.has(reasoningKey)) { + return; + } + passthroughResponsesReasoningSummarySeen.add(reasoningKey); + + const itemId = typeof item.id === "string" && item.id ? item.id : reasoningKey; + const outputIndex = + typeof payload.output_index === "number" && Number.isInteger(payload.output_index) + ? payload.output_index + : 0; + + const syntheticEvents = [ + { + event: "response.reasoning_summary_text.delta", + body: { + type: "response.reasoning_summary_text.delta", + item_id: itemId, + output_index: outputIndex, + summary_index: 0, + delta: summaryText, + }, + }, + { + event: "response.reasoning_summary_part.done", + body: { + type: "response.reasoning_summary_part.done", + item_id: itemId, + output_index: outputIndex, + summary_index: 0, + part: { type: "summary_text", text: summaryText }, + }, + }, + ]; + + for (const syntheticEvent of syntheticEvents) { + clientPayloadCollector.push(syntheticEvent.body); + const output = `event: ${syntheticEvent.event}\ndata: ${JSON.stringify(syntheticEvent.body)}\n\n`; + reqLogger?.appendConvertedChunk?.(output); + controller.enqueue(encoder.encode(output)); + } + }; + return new TransformStream( { start(controller) { @@ -809,6 +961,15 @@ export function createSSEStream(options: StreamOptions = {}) { parsed.type === "error"); if (isResponsesSSE) { + const responseId = + typeof parsed.response?.id === "string" + ? parsed.response.id + : typeof parsed.response_id === "string" + ? parsed.response_id + : null; + if (responseId) { + passthroughResponsesId = responseId; + } // Responses SSE: only extract usage, forward payload as-is const extracted = extractUsage(parsed); if (extracted) { @@ -825,11 +986,107 @@ export function createSSEStream(options: StreamOptions = {}) { if (parsed.type === "response.failed") { failurePayload = normalizeStreamFailurePayload(parsed); } + if ( + parsed.type === "response.reasoning_summary_text.delta" || + parsed.type === "response.reasoning_summary_text.done" || + parsed.type === "response.reasoning_summary_part.done" + ) { + const reasoningKey = getResponsesReasoningKey(parsed); + if (reasoningKey) { + passthroughResponsesReasoningSummarySeen.add(reasoningKey); + } + } + if ( + parsed.type === "response.output_item.added" && + parsed.item?.type === "function_call" + ) { + const item = + parsed.item && typeof parsed.item === "object" && !Array.isArray(parsed.item) + ? { ...(parsed.item as JsonRecord) } + : null; + const pendingKey = + item && typeof item.id === "string" + ? item.id + : item && typeof item.call_id === "string" + ? item.call_id + : null; + if (item && pendingKey) { + if (typeof item.arguments !== "string") { + item.arguments = ""; + } + passthroughResponsesPendingFunctionCalls.set(pendingKey, item); + passthroughResponsesCurrentFunctionCallKey = pendingKey; + } + } + if (parsed.type === "response.function_call_arguments.delta") { + const pendingKey = + typeof parsed.item_id === "string" + ? parsed.item_id + : passthroughResponsesCurrentFunctionCallKey; + const pending = pendingKey + ? passthroughResponsesPendingFunctionCalls.get(pendingKey) + : undefined; + if (pending && typeof parsed.delta === "string") { + const previousArgs = + typeof pending.arguments === "string" ? pending.arguments : ""; + pending.arguments = previousArgs + parsed.delta; + } + } + if (parsed.type === "response.function_call_arguments.done") { + const pendingKey = + typeof parsed.item_id === "string" + ? parsed.item_id + : passthroughResponsesCurrentFunctionCallKey; + const pending = pendingKey + ? passthroughResponsesPendingFunctionCalls.get(pendingKey) + : undefined; + if (pending) { + if (typeof parsed.arguments === "string") { + pending.arguments = parsed.arguments; + } + pushUniqueResponsesOutputItems(passthroughResponsesOutputItems, [pending]); + } + } // Capture each completed output item so the final // response.completed snapshot can be backfilled when upstream // returns an empty `output` (happens with store: false). if (parsed.type === "response.output_item.done" && parsed.item) { - passthroughResponsesOutputItems.push(parsed.item); + emitSyntheticResponsesReasoningSummary(controller, parsed); + pushUniqueResponsesOutputItems(passthroughResponsesOutputItems, [parsed.item]); + if (parsed.item?.type === "function_call") { + const pendingKey = + typeof parsed.item.id === "string" + ? parsed.item.id + : typeof parsed.item.call_id === "string" + ? parsed.item.call_id + : null; + if (pendingKey) { + passthroughResponsesPendingFunctionCalls.delete(pendingKey); + if (passthroughResponsesCurrentFunctionCallKey === pendingKey) { + passthroughResponsesCurrentFunctionCallKey = null; + } + } + } + } + if ( + parsed.type === "response.completed" && + Array.isArray(parsed.response?.output) && + parsed.response.output.length > 0 + ) { + pushUniqueResponsesOutputItems( + passthroughResponsesOutputItems, + parsed.response.output + ); + } + if ( + parsed.type === "response.completed" && + passthroughResponsesPendingFunctionCalls.size > 0 + ) { + pushUniqueResponsesOutputItems(passthroughResponsesOutputItems, [ + ...passthroughResponsesPendingFunctionCalls.values(), + ]); + passthroughResponsesPendingFunctionCalls.clear(); + passthroughResponsesCurrentFunctionCallKey = null; } // Two transport-level fixes for Responses passthrough: // 1) Strip echoed `instructions` + `tools` from lifecycle @@ -1077,6 +1334,7 @@ export function createSSEStream(options: StreamOptions = {}) { } catch {} } clearIdleTimer(); + trackPendingRequest(model, provider, connectionId, false); controller.error(new Error(failurePayload.message || "Upstream failure")); return; } @@ -1288,6 +1546,25 @@ export function createSSEStream(options: StreamOptions = {}) { } clearPendingPassthroughEvent(); + if (passthroughResponsesId) { + const requestInput = + body && typeof body === "object" && Array.isArray((body as JsonRecord).input) + ? ((body as JsonRecord).input as unknown[]) + : []; + rememberResponseConversationState( + passthroughResponsesId, + requestInput, + passthroughResponsesOutputItems + ); + } + + if (passthroughResponsesId && passthroughResponsesOutputItems.length > 0) { + rememberResponseFunctionCalls( + passthroughResponsesId, + passthroughResponsesOutputItems + ); + } + // Estimate usage if provider didn't return valid usage if (!hasValidUsage(usage) && totalContentLength > 0) { usage = estimateUsage(body, totalContentLength, sourceFormat || FORMATS.OPENAI); @@ -1307,10 +1584,12 @@ export function createSSEStream(options: StreamOptions = {}) { if (!doneSent) { await emitFinalSseMetadata(controller, usage); doneSent = true; - clientPayloadCollector.push({ done: true }); - const doneOutput = "data: [DONE]\n\n"; - reqLogger?.appendConvertedChunk?.(doneOutput); - controller.enqueue(encoder.encode(doneOutput)); + if (!clientExpectsResponsesStream) { + clientPayloadCollector.push({ done: true }); + const doneOutput = "data: [DONE]\n\n"; + reqLogger?.appendConvertedChunk?.(doneOutput); + controller.enqueue(encoder.encode(doneOutput)); + } } // Notify caller for call log persistence (include full response body with accumulated content) if (onComplete) { @@ -1499,10 +1778,12 @@ export function createSSEStream(options: StreamOptions = {}) { if (!doneSent) { await emitFinalSseMetadata(controller, state?.usage as Record | null); doneSent = true; - clientPayloadCollector.push({ done: true }); - const doneOutput = "data: [DONE]\n\n"; - reqLogger?.appendConvertedChunk?.(doneOutput); - controller.enqueue(encoder.encode(doneOutput)); + if (!clientExpectsResponsesStream) { + clientPayloadCollector.push({ done: true }); + const doneOutput = "data: [DONE]\n\n"; + reqLogger?.appendConvertedChunk?.(doneOutput); + controller.enqueue(encoder.encode(doneOutput)); + } } // Estimate usage if provider didn't return valid usage (for translate mode) @@ -1632,7 +1913,8 @@ export function createPassthroughStreamWithLogger( body: unknown = null, onComplete: ((payload: StreamCompletePayload) => void) | null = null, apiKeyInfo: unknown = null, - onFailure: ((payload: StreamFailurePayload) => void | Promise) | null = null + onFailure: ((payload: StreamFailurePayload) => void | Promise) | null = null, + clientResponseFormat: string | null = null ) { return createSSEStream({ mode: STREAM_MODE.PASSTHROUGH, @@ -1645,5 +1927,6 @@ export function createPassthroughStreamWithLogger( body, onComplete, onFailure, + clientResponseFormat, }); } diff --git a/package-lock.json b/package-lock.json index dee96e1e29..17ae67cb5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "omniroute", - "version": "3.7.4", + "version": "3.7.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "omniroute", - "version": "3.7.4", + "version": "3.7.5", "hasInstallScript": true, "license": "MIT", "workspaces": [ @@ -16,6 +16,7 @@ "@lobehub/icons": "^5.5.4", "@modelcontextprotocol/sdk": "^1.27.1", "@monaco-editor/react": "^4.7.0", + "@ngrok/ngrok": "^1.7.0", "@swc/helpers": "0.5.21", "axios": "^1.15.0", "bcryptjs": "^3.0.3", @@ -2322,6 +2323,235 @@ "node": ">= 10" } }, + "node_modules/@ngrok/ngrok": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok/-/ngrok-1.7.0.tgz", + "integrity": "sha512-P06o9TpxrJbiRbHQkiwy/rUrlXRupc+Z8KT4MiJfmcdWxvIdzjCaJOdnNkcOTs6DMyzIOefG5tvk/HLdtjqr0g==", + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@ngrok/ngrok-android-arm64": "1.7.0", + "@ngrok/ngrok-darwin-arm64": "1.7.0", + "@ngrok/ngrok-darwin-universal": "1.7.0", + "@ngrok/ngrok-darwin-x64": "1.7.0", + "@ngrok/ngrok-freebsd-x64": "1.7.0", + "@ngrok/ngrok-linux-arm-gnueabihf": "1.7.0", + "@ngrok/ngrok-linux-arm64-gnu": "1.7.0", + "@ngrok/ngrok-linux-arm64-musl": "1.7.0", + "@ngrok/ngrok-linux-x64-gnu": "1.7.0", + "@ngrok/ngrok-linux-x64-musl": "1.7.0", + "@ngrok/ngrok-win32-arm64-msvc": "1.7.0", + "@ngrok/ngrok-win32-ia32-msvc": "1.7.0", + "@ngrok/ngrok-win32-x64-msvc": "1.7.0" + } + }, + "node_modules/@ngrok/ngrok-android-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-android-arm64/-/ngrok-android-arm64-1.7.0.tgz", + "integrity": "sha512-8tco3ID6noSaNy+CMS7ewqPoIkIM6XO5COCzsUp3Wv3XEbMSyn65RN6cflX2JdqLfUCHcMyD0ahr9IEiHwqmbQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-arm64/-/ngrok-darwin-arm64-1.7.0.tgz", + "integrity": "sha512-+dmJSOzSO+MNDVrPOca2yYDP1W3KfP4qOlAkarIeFRIfqonQwq3QCBmcR7HAlZocLsSqEwyG6KP4RRvAuT0WGQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-universal": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-universal/-/ngrok-darwin-universal-1.7.0.tgz", + "integrity": "sha512-fDEfewyE2pWGFBhOSwQZObeHUkc65U1l+3HIgSOe094TMHsqmyJD0KTCgW9KSn0VP4OvDZbAISi1T3nvqgZYhQ==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-x64/-/ngrok-darwin-x64-1.7.0.tgz", + "integrity": "sha512-+fwMi5uHd9G8BS42MMa9ye6exI5lwTcjUO6Ut497Vu0qgLONdVRenRqnEePV+Q3KtQR7NjqkMnomVfkr9MBjtw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-freebsd-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-freebsd-x64/-/ngrok-freebsd-x64-1.7.0.tgz", + "integrity": "sha512-2OGgbrjy3yLRrqAz5N6hlUKIWIXSpR5RjQa2chtZMsSbszQ6c9dI+uVQfOKAeo05tHMUgrYAZ7FocC+ig0dzdQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm-gnueabihf": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm-gnueabihf/-/ngrok-linux-arm-gnueabihf-1.7.0.tgz", + "integrity": "sha512-SN9YIfEQiR9xN90QVNvdgvAemqMLoFVSeTWZs779145hQMhvF9Qd9rnWi6J+2uNNK10OczdV1oc/nq1es7u/3g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-gnu/-/ngrok-linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-KDMgzPKFU2kbpVSaA2RZBBia5IPdJEe063YlyVFnSMJmPYWCUnMwdybBsucXfV9u1Lw/ZjKTKotIlbTWGn3HGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-musl/-/ngrok-linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-e66vUdVrBlQ0lT9ZdamB4U604zt5Gualt8/WVcUGzbu8s5LajWd6g/mzZCUjK4UepjvMpfgmCp1/+rX7Rk8d5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-gnu/-/ngrok-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-M6gF0DyOEFqXLfWxObfL3bxYZ4+PnKBHuyLVaqNfFN9Y5utY2mdPOn5422Ppbk4XoIK5/YkuhRqPJl/9FivKEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-musl/-/ngrok-linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-4Ijm0dKeoyzZTMaYxR2EiNjtlK81ebflg/WYIO1XtleFrVy4UJEGnxtxEidYoT4BfCqi4uvXiK2Mx216xXKvog==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-arm64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-arm64-msvc/-/ngrok-win32-arm64-msvc-1.7.0.tgz", + "integrity": "sha512-u7qyWIJI2/YG1HTBnHwUR1+Z2tyGfAsUAItJK/+N1G0FeWJhIWQvSIFJHlaPy4oW1Dc8mSDBX9qvVsiQgLaRFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-ia32-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-ia32-msvc/-/ngrok-win32-ia32-msvc-1.7.0.tgz", + "integrity": "sha512-/UdYUsLNv/Q8j9YJsyIfq/jLCoD8WP+NidouucTUzSoDtmOsXBBT3itLrmPiZTEdEgKiFYLuC1Zon8XQQvbVLA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-x64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-x64-msvc/-/ngrok-win32-x64-msvc-1.7.0.tgz", + "integrity": "sha512-UFJg/duEWzZlLkEs61Gz6/5nYhGaKI62I8dvUGdBR3NCtIMagehnFaFxmnXZldyHmCM8U0aCIFNpWRaKcrQkoA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -14972,7 +15202,7 @@ }, "open-sse": { "name": "@omniroute/open-sse", - "version": "3.7.4" + "version": "3.7.5" } } } diff --git a/package.json b/package.json index e17f1e9d4e..0a2a1d4300 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omniroute", - "version": "3.7.4", + "version": "3.7.5", "description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.", "type": "module", "bin": { @@ -110,6 +110,7 @@ "@lobehub/icons": "^5.5.4", "@modelcontextprotocol/sdk": "^1.27.1", "@monaco-editor/react": "^4.7.0", + "@ngrok/ngrok": "^1.7.0", "@swc/helpers": "0.5.21", "axios": "^1.15.0", "bcryptjs": "^3.0.3", diff --git a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx index a71b38a258..fff2b31ec3 100644 --- a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx +++ b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx @@ -6,7 +6,7 @@ import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; import { useTranslations } from "next-intl"; // Constants for validation -const MAX_KEY_NAME_LENGTH = 100; +const MAX_KEY_NAME_LENGTH = 200; const MAX_SELECTED_MODELS = 500; // Debounce hook for search optimization @@ -232,14 +232,13 @@ export default function ApiManagerPageClient() { const clearError = useCallback(() => setError(null), []); const handleCreateKey = async () => { - // Validate and sanitize input - const sanitizedName = sanitizeInput(newKeyName); - const validation = validateKeyName(sanitizedName, t); - + // Validate raw input first, then sanitize + const validation = validateKeyName(newKeyName, t); if (!validation.valid) { setError(validation.error || t("invalidKeyName")); return; } + const sanitizedName = sanitizeInput(newKeyName); setIsSubmitting(true); clearError(); @@ -322,6 +321,7 @@ export default function ApiManagerPageClient() { }; const handleUpdatePermissions = async ( + name: string, allowedModels: string[], noLog: boolean, allowedConnections: string[], @@ -332,6 +332,14 @@ export default function ApiManagerPageClient() { ) => { if (!editingKey || !editingKey.id) return; + // Validate raw input first, then sanitize + const nameValidation = validateKeyName(name, t); + if (!nameValidation.valid) { + setError(nameValidation.error || t("invalidKeyName")); + return; + } + const sanitizedName = sanitizeInput(name); + // Validate models array if (!Array.isArray(allowedModels)) { setError(t("invalidModelsSelection")); @@ -366,6 +374,7 @@ export default function ApiManagerPageClient() { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ + name: sanitizedName, allowedModels: validModels, allowedConnections: validConnections, noLog, @@ -764,6 +773,7 @@ export default function ApiManagerPageClient() { value={newKeyName} onChange={(e) => setNewKeyName(e.target.value)} placeholder={t("keyNamePlaceholder")} + maxLength={MAX_KEY_NAME_LENGTH} autoFocus />

{t("keyNameDesc")}

@@ -862,6 +872,7 @@ const PermissionsModal = memo(function PermissionsModal({ searchModel: string; onSearchChange: (v: string) => void; onSave: ( + name: string, models: string[], noLog: boolean, connections: string[], @@ -879,6 +890,7 @@ const PermissionsModal = memo(function PermissionsModal({ const initialConnections = Array.isArray(apiKey?.allowedConnections) ? apiKey.allowedConnections : []; + const [keyName, setKeyName] = useState(apiKey?.name ?? ""); const [selectedModels, setSelectedModels] = useState(initialModels); const [allowAll, setAllowAll] = useState(initialModels.length === 0); const [noLogEnabled, setNoLogEnabled] = useState(apiKey?.noLog === true); @@ -993,6 +1005,7 @@ const PermissionsModal = memo(function PermissionsModal({ } : null; onSave( + keyName, allowAll ? [] : selectedModels, noLogEnabled, allowAllConnections ? [] : selectedConnections, @@ -1003,6 +1016,7 @@ const PermissionsModal = memo(function PermissionsModal({ ); }, [ onSave, + keyName, allowAll, selectedModels, noLogEnabled, @@ -1028,6 +1042,22 @@ const PermissionsModal = memo(function PermissionsModal({ onClose={onClose} >
+ {/* Key Name */} +
+
+

{t("keyName")}

+

{t("keyNameDesc")}

+
+
+ setKeyName(e.target.value)} + placeholder={t("keyNamePlaceholder")} + maxLength={MAX_KEY_NAME_LENGTH} + /> +
+
+ {/* Access Mode Toggle */}
)} + + {showNgrokTunnel && ( +
+
+
+
+
+

+ {translateOrFallback("ngrokTitle", "ngrok Tunnel")} +

+ + {ngrokPhaseMeta[ngrokPhase].label} + +
+
+ + {ngrokStatus?.supported !== false && ( + + )} +
+ + {ngrokNotice && ( +
+ + {ngrokNotice.type === "success" + ? "check_circle" + : ngrokNotice.type === "info" + ? "info" + : "error"} + + {ngrokNotice.message} + +
+ )} + +

{ngrokUrlNotice}

+ {ngrokStatus?.phase === "needs_auth" && ( +
+ + setNgrokToken(event.target.value)} + placeholder={translateOrFallback( + "ngrokAuthTokenPlaceholder", + "Enter your ngrok authtoken" + )} + disabled={ngrokBusy} + className="font-mono text-sm" + /> +
+ )} +
+ + +
+ {ngrokStatus?.lastError && ( +

+ {translateOrFallback("ngrokLastError", "Last error: {error}", { + error: ngrokStatus.lastError, + })} +

+ )} +
+
+ )} @@ -1258,24 +1582,12 @@ export default function APIPageClient({ machineId }) {

{t("available")}

- {t("modelsAcrossEndpoints", { - models: Object.values(endpointData).reduce( - (acc, models) => acc + models.length, - 0 - ), - endpoints: - [ - endpointData.chat, - endpointData.embeddings, - endpointData.images, - endpointData.video, - endpointData.rerank, - endpointData.audioTranscription, - endpointData.audioSpeech, - endpointData.moderation, - endpointData.music, - ].filter((a) => a.length > 0).length + 2, - })} + {modelsLoading + ? translateOrFallback("loadingModels", "Loading available models...") + : t("modelsAcrossEndpoints", { + models: totalEndpointModelCount, + endpoints: availableEndpointCount, + })}

@@ -1304,6 +1616,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> {/* Responses API */} @@ -1325,6 +1638,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> {/* Legacy Completions */} @@ -1346,6 +1660,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> @@ -1376,6 +1691,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> {/* Image Generation */} @@ -1394,6 +1710,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> {/* Audio Transcription */} @@ -1414,6 +1731,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> {/* Audio Speech (TTS) */} @@ -1432,6 +1750,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> {/* Music Generation */} @@ -1451,6 +1770,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> {/* Video Generation */} @@ -1470,6 +1790,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> @@ -1541,6 +1862,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> {/* Moderations */} @@ -1559,6 +1881,7 @@ export default function APIPageClient({ machineId }) { copy={copy} copied={copied} baseUrl={currentEndpoint} + modelsLoading={modelsLoading} /> {/* List Models */} @@ -2078,6 +2401,7 @@ function EndpointSection({ copy, copied, baseUrl, + modelsLoading = false, }) { const t = useTranslations("endpoint"); const grouped = useMemo(() => { @@ -2111,7 +2435,7 @@ function EndpointSection({
{title} - {t("modelsCount", { count: models.length })} + {modelsLoading ? "..." : t("modelsCount", { count: models.length })}

{description}

@@ -2143,35 +2467,44 @@ function EndpointSection({ {/* Models grouped by provider */} -
- {grouped.map(([providerId, providerModels]) => ( -
-
-
- - {providerName(providerId)} - - - ({(providerModels as any).length}) - -
-
- {(providerModels as any).map((m) => ( - - {m.root || m.id.split("/").pop()} + {modelsLoading ? ( +
+ + progress_activity + + {t("loadingModels")} +
+ ) : ( +
+ {grouped.map(([providerId, providerModels]) => ( +
+
+
+ + {providerName(providerId)} - ))} + + ({(providerModels as any).length}) + +
+
+ {(providerModels as any).map((m) => ( + + {m.root || m.id.split("/").pop()} + + ))} +
-
- ))} -
+ ))} +
+ )}
)}
@@ -2191,4 +2524,5 @@ EndpointSection.propTypes = { copy: PropTypes.func.isRequired, copied: PropTypes.string, baseUrl: PropTypes.string.isRequired, + modelsLoading: PropTypes.bool, }; diff --git a/src/app/(dashboard)/dashboard/endpoint/__tests__/EndpointPageClient.test.tsx b/src/app/(dashboard)/dashboard/endpoint/__tests__/EndpointPageClient.test.tsx new file mode 100644 index 0000000000..6b54fcf62f --- /dev/null +++ b/src/app/(dashboard)/dashboard/endpoint/__tests__/EndpointPageClient.test.tsx @@ -0,0 +1,250 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import EndpointPageClient from "../EndpointPageClient"; + +type Deferred = { + promise: Promise; + resolve: (value: T) => void; + reject: (error: unknown) => void; +}; + +function createDeferred(): Deferred { + let resolve!: (value: T) => void; + let reject!: (error: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} + +function jsonResponse(data: unknown) { + return { + ok: true, + json: async () => data, + } as Response; +} + +function getRequestPath(input: RequestInfo | URL) { + return typeof input === "string" ? input : input instanceof URL ? input.pathname : input.url; +} + +const cleanupCallbacks: Array<() => void> = []; + +async function waitForText(text: string) { + const startedAt = Date.now(); + while (!document.body.textContent?.includes(text)) { + if (Date.now() - startedAt > 1000) { + throw new Error(`Timed out waiting for text: ${text}`); + } + await new Promise((resolve) => setTimeout(resolve, 10)); + } +} + +function renderEndpointPage() { + const container = document.createElement("div"); + document.body.appendChild(container); + const root = createRoot(container); + let mounted = true; + + act(() => { + root.render(); + }); + + const unmount = () => { + if (!mounted) { + return; + } + act(() => { + root.unmount(); + }); + container.remove(); + mounted = false; + }; + + cleanupCallbacks.push(unmount); + + return { + unmount, + }; +} + +vi.mock("next/link", () => ({ + default: ({ href, children, ...props }: React.AnchorHTMLAttributes) => ( + + {children} + + ), +})); + +vi.mock("next-intl", () => ({ + useTranslations: (namespace?: string) => { + const messages: Record = { + "endpoint.title": "Endpoint", + "endpoint.available": "Available endpoints", + "endpoint.loadingModels": "Loading available models...", + "endpoint.modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "endpoint.modelsCount": "{count} models", + "endpoint.chatCompletions": "Chat Completions", + "endpoint.chatDesc": "Chat endpoint", + "endpoint.responses": "Responses API", + "endpoint.responsesDesc": "Responses endpoint", + "endpoint.completionsLegacy": "Completions", + "endpoint.completionsLegacyDesc": "Completions endpoint", + "endpoint.embeddings": "Embeddings", + "endpoint.embeddingsDesc": "Embedding endpoint", + "endpoint.imageGeneration": "Image Generation", + "endpoint.imageDesc": "Image endpoint", + "endpoint.audioTranscription": "Audio Transcription", + "endpoint.audioTranscriptionDesc": "Audio transcription endpoint", + "endpoint.textToSpeech": "Text to Speech", + "endpoint.textToSpeechDesc": "Speech endpoint", + "endpoint.musicGeneration": "Music Generation", + "endpoint.musicDesc": "Music endpoint", + "endpoint.videoGeneration": "Video Generation", + "endpoint.videoDesc": "Video endpoint", + "endpoint.rerank": "Rerank", + "endpoint.rerankDesc": "Rerank endpoint", + "endpoint.moderations": "Moderations", + "endpoint.moderationsDesc": "Moderation endpoint", + "endpoint.listModels": "List Models", + "endpoint.listModelsDesc": "List model endpoint", + "endpoint.overviewTitle": "Endpoint overview", + "endpoint.overviewDescription": "Endpoint overview description", + "endpoint.tabApis": "APIs", + "endpoint.tabProtocols": "Protocols", + "endpoint.categoryCore": "Core APIs", + "endpoint.categoryMedia": "Media APIs", + "endpoint.categoryUtility": "Utility APIs", + "endpoint.machineId": "Machine {id}", + "endpoint.usingLocalServer": "Using local server", + "common.copy": "Copy", + "common.cancel": "Cancel", + }; + + const translate = (key: string, values?: Record) => { + const fullKey = namespace ? `${namespace}.${key}` : key; + let message = messages[fullKey] ?? key; + if (values) { + for (const [name, value] of Object.entries(values)) { + message = message.replace(`{${name}}`, String(value)); + } + } + return message; + }; + + return translate; + }, +})); + +describe("EndpointPageClient", () => { + const fetchMock = vi.fn(); + + beforeEach(() => { + fetchMock.mockReset(); + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; + vi.stubGlobal("fetch", fetchMock); + }); + + afterEach(() => { + while (cleanupCallbacks.length > 0) { + cleanupCallbacks.pop()?.(); + } + document.body.innerHTML = ""; + vi.unstubAllGlobals(); + }); + + it("renders the endpoint shell before models finish and skips hidden tunnel probes", async () => { + const modelsDeferred = createDeferred(); + + fetchMock.mockImplementation((input: RequestInfo | URL) => { + const path = getRequestPath(input); + if (path === "/api/settings") { + return Promise.resolve( + jsonResponse({ + cloudEnabled: false, + cloudConfigured: false, + hideEndpointCloudflaredTunnel: true, + hideEndpointTailscaleFunnel: true, + machineId: "machine-12345678", + }) + ); + } + if (path === "/v1/models") { + return modelsDeferred.promise; + } + if (path === "/api/mcp/status") { + return Promise.resolve(jsonResponse({ online: false })); + } + if (path === "/api/a2a/status") { + return Promise.resolve(jsonResponse({ status: "ok", tasks: { activeStreams: 0 } })); + } + if (path === "/api/search/providers") { + return Promise.resolve(jsonResponse({ providers: [] })); + } + throw new Error(`Unexpected request: ${path}`); + }); + + renderEndpointPage(); + + await waitForText("Endpoint"); + expect(document.body.textContent).toContain("Loading available models..."); + expect(fetchMock).toHaveBeenCalledWith("/v1/models"); + expect(fetchMock).not.toHaveBeenCalledWith("/api/tunnels/cloudflared", expect.anything()); + expect(fetchMock).not.toHaveBeenCalledWith("/api/tunnels/tailscale", expect.anything()); + + modelsDeferred.resolve( + jsonResponse({ + data: [ + { + id: "openai/gpt-4o", + owned_by: "openai", + root: "gpt-4o", + }, + { + id: "openai/text-embedding-3-small", + owned_by: "openai", + root: "text-embedding-3-small", + type: "embedding", + }, + ], + }) + ); + + await waitForText("2 models across 4 endpoints"); + }); + + it("does not start background endpoint requests after unmounting during settings load", async () => { + const settingsDeferred = createDeferred(); + fetchMock.mockImplementation((input: RequestInfo | URL) => { + const path = getRequestPath(input); + if (path === "/api/settings") { + return settingsDeferred.promise; + } + throw new Error(`Unexpected request after unmount: ${path}`); + }); + + const { unmount } = renderEndpointPage(); + unmount(); + + await act(async () => { + settingsDeferred.resolve( + jsonResponse({ + cloudEnabled: false, + cloudConfigured: false, + hideEndpointCloudflaredTunnel: false, + hideEndpointTailscaleFunnel: false, + }) + ); + await settingsDeferred.promise; + }); + + const requestPaths = fetchMock.mock.calls.map(([input]) => getRequestPath(input)); + expect(requestPaths.length).toBeGreaterThan(0); + expect(requestPaths.every((path) => path === "/api/settings")).toBe(true); + }); +}); diff --git a/src/app/(dashboard)/dashboard/health/page.tsx b/src/app/(dashboard)/dashboard/health/page.tsx index 5dad4cd35f..d4af48798e 100644 --- a/src/app/(dashboard)/dashboard/health/page.tsx +++ b/src/app/(dashboard)/dashboard/health/page.tsx @@ -81,7 +81,7 @@ export default function HealthPage() { const fetchDbHealth = useCallback(async () => { try { - const res = await fetch("/api/v1/db/health"); + const res = await fetch("/api/db/health"); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = await res.json(); setDbHealth(json); @@ -138,7 +138,7 @@ export default function HealthPage() { const handleRepairDb = async () => { setRepairingDb(true); try { - const res = await fetch("/api/v1/db/health", { method: "POST" }); + const res = await fetch("/api/db/health", { method: "POST" }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = await res.json(); setDbHealth(json); diff --git a/src/app/(dashboard)/dashboard/memory/page.tsx b/src/app/(dashboard)/dashboard/memory/page.tsx index 526cc9debe..885727721a 100644 --- a/src/app/(dashboard)/dashboard/memory/page.tsx +++ b/src/app/(dashboard)/dashboard/memory/page.tsx @@ -205,17 +205,21 @@ export default function MemoryPage() { {health !== null && ( )} {health === null && !checkingHealth && ( )}
@@ -321,7 +325,7 @@ export default function MemoryPage() {
- Page {page} of {totalPages} ({total} total) + {t("pageInfo", { page, totalPages, total })}
@@ -356,47 +360,47 @@ export default function MemoryPage() { onClick={() => setAddDialogOpen(false)} disabled={isSubmitting} > - Cancel + {t("cancel")} } >
- +
- + setNewMemory({ ...newMemory, key: e.target.value })} - placeholder="e.g., user_preference_theme" + placeholder={t("keyPlaceholder")} className="w-full" />
- + setNewMemory({ ...newMemory, content: e.target.value })} - placeholder="e.g., Prefers dark mode" + placeholder={t("contentPlaceholder")} className="w-full" />
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx index 25ad3c7f7b..5cefea5c4d 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx @@ -460,7 +460,7 @@ interface CooldownTimerProps { function getModelSourceBadgeClass(source?: string): string { switch (normalizeModelCatalogSource(source)) { - case "api-sync": + case "imported": return "border-sky-500/30 bg-sky-500/10 text-sky-300"; case "custom": return "border-emerald-500/30 bg-emerald-500/10 text-emerald-300"; @@ -1041,12 +1041,12 @@ export default function ProviderDetailPage() { const isOAuth = providerSupportsOAuth && !providerSupportsPat; const registryModels = getModelsByProviderId(providerId); // Prefer synced API-discovered models when available, then merge built-ins - // and user-managed custom/imported models without duplicating IDs. + // and user-managed custom models without duplicating IDs. const models = useMemo(() => { if (providerId === "gemini") { return syncedAvailableModels.map((model: any) => ({ ...model, - source: model?.source === "api-sync" ? "api-sync" : "api-sync", + source: "imported", })); } @@ -1061,7 +1061,7 @@ export default function ProviderDetailPage() { .map((model: any) => ({ id: model.id, name: model.name || model.id, - source: "api-sync", + source: "imported", })); const knownIds = new Set([...registryIds, ...syncedExtras.map((model: any) => model.id)]); const customExtras = modelMeta.customModels @@ -1069,7 +1069,7 @@ export default function ProviderDetailPage() { .map((cm: any) => ({ id: cm.id, name: cm.name || cm.id, - source: cm.source === "api-sync" ? "api-sync" : "custom", + source: normalizeModelCatalogSource(cm.source) === "imported" ? "imported" : "custom", })); return [...builtInModels, ...syncedExtras, ...customExtras]; }, [providerId, registryModels, syncedAvailableModels, modelMeta.customModels]); @@ -2097,6 +2097,9 @@ export default function ProviderDetailPage() { typeof data.importedChanges?.total === "number" ? data.importedChanges.total : importedCount; + const totalChangedCount = + changedCount + + (typeof data.customModelChanges?.total === "number" ? data.customModelChanges.total : 0); if (importedModels.length === 0) { setImportProgress((prev) => ({ @@ -2113,7 +2116,7 @@ export default function ProviderDetailPage() { ], importedCount, })); - if (changedCount > 0) { + if (totalChangedCount > 0) { setTimeout(() => { window.location.reload(); }, 2000); @@ -2142,7 +2145,7 @@ export default function ProviderDetailPage() { importedCount, })); - if (changedCount > 0) { + if (totalChangedCount > 0) { setTimeout(() => { window.location.reload(); }, 2000); diff --git a/src/app/api/v1/db/health/route.ts b/src/app/api/db/health/route.ts similarity index 100% rename from src/app/api/v1/db/health/route.ts rename to src/app/api/db/health/route.ts diff --git a/src/app/api/oauth/[provider]/[action]/route.ts b/src/app/api/oauth/[provider]/[action]/route.ts index cf5dd090eb..0b31439e5c 100755 --- a/src/app/api/oauth/[provider]/[action]/route.ts +++ b/src/app/api/oauth/[provider]/[action]/route.ts @@ -81,6 +81,8 @@ export async function GET( } const authData = generateAuthData(provider, null); + const startUrl = searchParams.get("startUrl"); + const region = searchParams.get("region") || "us-east-1"; // Resolve proxy for this provider (provider-level → global → direct) const proxy = await resolveProxyForProvider(provider); @@ -95,7 +97,24 @@ export async function GET( provider === "kilocode" ) { // GitHub, Kiro/Amazon Q, Kimi Coding, and KiloCode don't use PKCE for device code - deviceData = await runWithProxyContext(proxy, () => (requestDeviceCode as any)(provider)); + if ((provider === "kiro" || provider === "amazon-q") && startUrl) { + const providerOverrideConfig = { + ...providerData.config, + startUrl, + region, + skipIssuerUrlForRegistration: true, + registerClientUrl: `https://oidc.${region}.amazonaws.com/client/register`, + deviceAuthUrl: `https://oidc.${region}.amazonaws.com/device_authorization`, + tokenUrl: `https://oidc.${region}.amazonaws.com/token`, + ssoOidcEndpoint: `https://oidc.${region}.amazonaws.com`, + }; + + deviceData = await runWithProxyContext(proxy, () => + (requestDeviceCode as any)(provider, null, providerOverrideConfig) + ); + } else { + deviceData = await runWithProxyContext(proxy, () => (requestDeviceCode as any)(provider)); + } } else { // Qwen and other providers use PKCE deviceData = await runWithProxyContext(proxy, () => diff --git a/src/app/api/pricing/models/route.ts b/src/app/api/pricing/models/route.ts index 98882c4af1..e25ba2fcc1 100644 --- a/src/app/api/pricing/models/route.ts +++ b/src/app/api/pricing/models/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts"; -import { getAllCustomModels, getPricing } from "@/lib/localDb"; +import { getAllCustomModels, getAllSyncedAvailableModels, getPricing } from "@/lib/localDb"; function asRecord(value: unknown): Record { return value && typeof value === "object" && !Array.isArray(value) @@ -20,8 +20,9 @@ function asModelArray(value: unknown): Array<{ id?: string; name?: string }> { * GET /api/pricing/models * Returns the full model catalog merged from three sources: * 1. providerRegistry (hardcoded) - * 2. customModels (DB — user-added or imported via /models) - * 3. pricing data (DB — models with pricing configured but not in sources 1/2) + * 2. syncedAvailableModels (DB — discovered/imported from provider /models) + * 3. customModels (DB — manually added models) + * 4. pricing data (DB — models with pricing configured but not in sources 1/2/3) */ export async function GET() { try { @@ -46,25 +47,14 @@ export async function GET() { }; } - // ── 2. Custom models (DB) ─────────────────────────────────────── - let customModelsMap: Record = {}; - try { - customModelsMap = asRecord(await getAllCustomModels()); - } catch { - /* DB may not be ready */ - } - - for (const [providerId, rawModels] of Object.entries(customModelsMap)) { - const models = asModelArray(rawModels); - // Resolve alias — check if a registry entry maps this providerId - let alias = providerId; + const resolveAlias = (providerId: string) => { for (const entry of Object.values(REGISTRY)) { - if (entry.id === providerId) { - alias = entry.alias || entry.id; - break; - } + if (entry.id === providerId) return entry.alias || entry.id; } + return providerId; + }; + const ensureCatalogProvider = (providerId: string, alias: string) => { if (!catalog[alias]) { catalog[alias] = { id: providerId, @@ -75,25 +65,52 @@ export async function GET() { models: [], }; } + return catalog[alias]; + }; + + const appendDbModels = (providerId: string, rawModels: unknown) => { + const models = asModelArray(rawModels); + const alias = resolveAlias(providerId); + const providerCatalog = ensureCatalogProvider(providerId, alias); + const existingIds = new Set(providerCatalog.models.map((m) => m.id)); - const existingIds = new Set(catalog[alias].models.map((m) => m.id)); for (const model of models) { const modelId = typeof model.id === "string" ? model.id : null; - if (!modelId || existingIds.has(modelId)) { - continue; - } - if (!existingIds.has(modelId)) { - catalog[alias].models.push({ - id: modelId, - name: typeof model.name === "string" && model.name.trim() ? model.name : modelId, - custom: true, - }); - existingIds.add(modelId); - } + if (!modelId || existingIds.has(modelId)) continue; + providerCatalog.models.push({ + id: modelId, + name: typeof model.name === "string" && model.name.trim() ? model.name : modelId, + custom: true, + }); + existingIds.add(modelId); } + }; + + // ── 2. Synced available models (DB) ───────────────────────────── + let syncedModelsMap: Record = {}; + try { + syncedModelsMap = asRecord(await getAllSyncedAvailableModels()); + } catch { + /* DB may not be ready */ } - // ── 3. Pricing-only models (DB) ───────────────────────────────── + for (const [providerId, rawModels] of Object.entries(syncedModelsMap)) { + appendDbModels(providerId, rawModels); + } + + // ── 3. Custom models (DB) ─────────────────────────────────────── + let customModelsMap: Record = {}; + try { + customModelsMap = asRecord(await getAllCustomModels()); + } catch { + /* DB may not be ready */ + } + + for (const [providerId, rawModels] of Object.entries(customModelsMap)) { + appendDbModels(providerId, rawModels); + } + + // ── 4. Pricing-only models (DB) ───────────────────────────────── let pricingData: Record = {}; try { pricingData = await getPricing(); diff --git a/src/app/api/providers/[id]/sync-models/route.ts b/src/app/api/providers/[id]/sync-models/route.ts index 9844deeb4d..a8b807985b 100644 --- a/src/app/api/providers/[id]/sync-models/route.ts +++ b/src/app/api/providers/[id]/sync-models/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; import { getProviderConnectionById } from "@/models"; +import { getSyncedAvailableModelsForConnection } from "@/lib/db/models"; import { importManagedModels, type ManagedModelImportMode, @@ -10,6 +11,7 @@ import { buildModelSyncInternalHeaders, isModelSyncInternalRequest, } from "@/shared/services/modelSyncScheduler"; +import { GET as getProviderModels } from "../models/route"; type JsonRecord = Record; @@ -28,8 +30,8 @@ function normalizeModelForComparison(model: unknown) { const rawSource = toNonEmptyString(record.source)?.toLowerCase(); const source = rawSource === "api-sync" || rawSource === "auto-sync" || rawSource === "imported" - ? "api-sync" - : rawSource || "auto-sync"; + ? "imported" + : rawSource || "manual"; const apiFormat = toNonEmptyString(record.apiFormat) || "chat-completions"; const supportedEndpoints = Array.isArray(record.supportedEndpoints) ? Array.from( @@ -56,6 +58,41 @@ function isManagedSyncedModel(model: unknown) { return source === "api-sync" || source === "auto-sync" || source === "imported"; } +function getErrorMessageFromPayload(payload: JsonRecord): string | null { + const error = payload.error; + if (typeof error === "string" && error.trim().length > 0) { + return error.trim(); + } + + const errorRecord = asRecord(error); + return toNonEmptyString(errorRecord.message) || toNonEmptyString(payload.message); +} + +async function readJsonResponse(response: Response): Promise<{ + data: JsonRecord; + parseError: string | null; +}> { + const body = await response.text(); + if (!body.trim()) { + return { + data: {}, + parseError: "Empty response body from /models", + }; + } + + try { + return { + data: asRecord(JSON.parse(body)), + parseError: null, + }; + } catch { + return { + data: {}, + parseError: "Invalid JSON response from /models", + }; + } +} + function summarizeModelChanges(previousModels: unknown, nextModels: unknown) { const previousList = Array.isArray(previousModels) ? previousModels : []; const nextList = Array.isArray(nextModels) ? nextModels : []; @@ -117,13 +154,54 @@ function getModelSyncChannelLabel(connection: unknown) { ); } +async function fetchProviderModelsForSync(request: Request, connectionId: string) { + // Construct a safe localhost URL from the incoming request's origin. + // The route only accepts authenticated or internal-scheduler requests, + // and the path is hardcoded — no user-controlled URL components reach fetch. + const SAFE_HOSTS = new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1"]); + const incomingUrl = new URL(request.url); + const safeOrigin = SAFE_HOSTS.has(incomingUrl.hostname) + ? incomingUrl.origin + : `http://127.0.0.1:${process.env.PORT || "20128"}`; + const modelsPath = `/api/providers/${encodeURIComponent(connectionId)}/models?refresh=true`; + const headers = { + cookie: request.headers.get("cookie") || "", + ...buildModelSyncInternalHeaders(), + }; + + try { + return await fetch(new URL(modelsPath, safeOrigin).href, { + method: "GET", + cache: "no-store", + headers, + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.warn( + `[ModelSync] Internal /models self-fetch failed for ${connectionId.slice( + 0, + 8 + )}; falling back to in-process route: ${message}` + ); + + return getProviderModels( + new Request(new URL(modelsPath, "http://localhost").href, { + method: "GET", + headers, + }), + { params: { id: connectionId } } + ); + } +} + /** * POST /api/providers/[id]/sync-models * - * Fetches the model list from a provider's /models endpoint and replaces the - * full custom models list for that provider while refreshing the per-connection - * discovery cache. Successful syncs only write a call log when the fetched - * channel actually changes the stored model list. + * Fetches the model list from a provider's /models endpoint, stores discovered + * models in the per-connection available-model cache, and removes matching + * upstream-discovered rows from the provider's custom model list. Successful + * syncs only write a call log when the fetched channel or custom model cleanup + * changes stored model state. * * Used by: * - modelSyncScheduler (auto-sync on interval) @@ -153,30 +231,21 @@ export async function POST(request: Request, { params }: { params: Promise<{ id: logProvider = toNonEmptyString(connection.provider) || "unknown"; channelLabel = getModelSyncChannelLabel(connection); + const previousSyncedAvailableModelsForConnection = await getSyncedAvailableModelsForConnection( + logProvider, + id + ); - // Fetch models from the existing /api/providers/[id]/models endpoint. - // Construct a safe localhost URL from the incoming request's origin. - // The route only accepts authenticated or internal-scheduler requests, - // and the path is hardcoded — no user-controlled URL components reach fetch. - const SAFE_HOSTS = new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1"]); - const incomingUrl = new URL(request.url); - const safeOrigin = SAFE_HOSTS.has(incomingUrl.hostname) - ? incomingUrl.origin - : `http://127.0.0.1:${process.env.PORT || "20128"}`; - const modelsPath = `/api/providers/${encodeURIComponent(id)}/models?refresh=true`; - const modelsRes = await fetch(new URL(modelsPath, safeOrigin).href, { - method: "GET", - cache: "no-store", - headers: { - cookie: request.headers.get("cookie") || "", - ...buildModelSyncInternalHeaders(), - }, - }); + const modelsRes = await fetchProviderModelsForSync(request, id); const duration = Date.now() - start; - const modelsData = await modelsRes.json(); + const { data: modelsData, parseError } = await readJsonResponse(modelsRes); + const payloadError = getErrorMessageFromPayload(modelsData); - if (!modelsRes.ok) { + if (!modelsRes.ok || parseError) { + const responseStatus = modelsRes.ok ? 502 : modelsRes.status; + const logError = payloadError || parseError || `HTTP ${modelsRes.status}`; + const responseError = payloadError || parseError || "Failed to fetch models"; // Log the failed attempt await saveCallLog({ method: "GET", @@ -187,22 +256,35 @@ export async function POST(request: Request, { params }: { params: Promise<{ id: sourceFormat: "-", connectionId: id, duration, - error: modelsData.error || `HTTP ${modelsRes.status}`, + error: logError, requestType: "model-sync", + ...(parseError + ? { + responseBody: { + upstreamStatus: modelsRes.status, + parseError, + }, + } + : {}), }); return NextResponse.json( - { error: modelsData.error || "Failed to fetch models" }, - { status: modelsRes.status } + { + error: responseError, + ...(parseError ? { upstreamStatus: modelsRes.status } : {}), + }, + { status: responseStatus } ); } const fetchedModels = modelsData.models || []; const { previousModels, + previousSyncedAvailableModels, persistedModels, importedModels, discoveredModels, + syncedAvailableModels, syncedAliases, importedChanges, } = await importManagedModels({ @@ -210,22 +292,30 @@ export async function POST(request: Request, { params }: { params: Promise<{ id: connectionId: id, fetchedModels, mode, + previousSyncedAvailableModels: previousSyncedAvailableModelsForConnection, }); - const modelChanges = summarizeModelChanges(previousModels, persistedModels); + const effectiveAvailableModels = + discoveredModels.length > 0 ? discoveredModels : syncedAvailableModels; + const modelChanges = summarizeModelChanges( + previousSyncedAvailableModels, + effectiveAvailableModels + ); + const customModelChanges = summarizeModelChanges(previousModels, persistedModels); const syncedModelsCount = - discoveredModels.length > 0 - ? discoveredModels.length + effectiveAvailableModels.length > 0 + ? effectiveAvailableModels.length : persistedModels.filter((model) => isManagedSyncedModel(model)).length; const availableModelsCount = new Set( - [...persistedModels, ...discoveredModels] + [...persistedModels, ...effectiveAvailableModels] .map((model) => toNonEmptyString(asRecord(model).id)) .filter((modelId): modelId is string => Boolean(modelId)) ).size; const importedCount = importedChanges.added; const updatedCount = importedChanges.updated; + const shouldLog = modelChanges.total > 0 || customModelChanges.total > 0; - if (modelChanges.total > 0) { + if (shouldLog) { await saveCallLog({ method: "GET", path: `/api/providers/${id}/models`, @@ -243,6 +333,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ id: provider: logProvider, channel: channelLabel, modelChanges, + customModelChanges, importedCount, updatedCount, mode, @@ -258,10 +349,11 @@ export async function POST(request: Request, { params }: { params: Promise<{ id: availableModelsCount, syncedAliases, modelChanges, + customModelChanges, importedCount, updatedCount, importedChanges, - logged: modelChanges.total > 0, + logged: shouldLog, models: persistedModels, importedModels, }); diff --git a/src/app/api/tunnels/ngrok/route.ts b/src/app/api/tunnels/ngrok/route.ts new file mode 100644 index 0000000000..5fad841674 --- /dev/null +++ b/src/app/api/tunnels/ngrok/route.ts @@ -0,0 +1,74 @@ +import { NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; +import { isAuthenticated } from "@/shared/utils/apiAuth"; +import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; +import { getNgrokTunnelStatus, startNgrokTunnel, stopNgrokTunnel } from "@/lib/ngrokTunnel"; + +export const dynamic = "force-dynamic"; + +const actionSchema = z.object({ + action: z.enum(["enable", "disable"]), + authToken: z.string().optional(), +}); + +function unauthorized() { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); +} + +export async function GET(request: NextRequest) { + if (!(await isAuthenticated(request))) { + return unauthorized(); + } + + try { + const status = await getNgrokTunnelStatus(); + return NextResponse.json(status); + } catch (error) { + return NextResponse.json( + { + error: error instanceof Error ? error.message : "Failed to load ngrok tunnel status", + }, + { status: 500 } + ); + } +} + +export async function POST(request: NextRequest) { + if (!(await isAuthenticated(request))) { + return unauthorized(); + } + + let rawBody: unknown; + try { + rawBody = await request.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); + } + + const validation = validateBody(actionSchema, rawBody); + if (isValidationFailure(validation)) { + return validation.response; + } + + const parsed = validation.data; + + try { + const status = + parsed.action === "enable" + ? await startNgrokTunnel(parsed.authToken) + : await stopNgrokTunnel(); + + return NextResponse.json({ + success: true, + action: parsed.action, + status, + }); + } catch (error) { + return NextResponse.json( + { + error: error instanceof Error ? error.message : "Failed to update ngrok tunnel", + }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/models/catalog.ts b/src/app/api/v1/models/catalog.ts index e291961e60..962ca76e07 100644 --- a/src/app/api/v1/models/catalog.ts +++ b/src/app/api/v1/models/catalog.ts @@ -478,6 +478,17 @@ export async function getUnifiedModelsResponse( const isProviderActive = (provider: string) => { if (activeAliases.size === 0) return false; // No active connections = show nothing const alias = providerIdToAlias[provider] || provider; + const canonicalProviderId = FALLBACK_ALIAS_TO_PROVIDER[alias] || provider; + + // FIX #1752: Ensure blocked providers are not returned for non-chat models + if ( + blockedProviders.has(alias) || + blockedProviders.has(canonicalProviderId) || + blockedProviders.has(provider) + ) { + return false; + } + return activeAliases.has(alias) || activeAliases.has(provider); }; diff --git a/src/app/api/v1beta/models/route.ts b/src/app/api/v1beta/models/route.ts index 37390e3f18..5d5aec0a8f 100644 --- a/src/app/api/v1beta/models/route.ts +++ b/src/app/api/v1beta/models/route.ts @@ -1,5 +1,9 @@ import { PROVIDER_MODELS } from "@/shared/constants/models"; -import { getAllCustomModels, getSyncedAvailableModels } from "@/lib/db/models"; +import { + getAllCustomModels, + getAllSyncedAvailableModels, + getSyncedAvailableModels, +} from "@/lib/db/models"; import { getResolvedModelCapabilities } from "@/lib/modelCapabilities"; import { getSyncedCapabilities } from "@/lib/modelsDevSync"; @@ -67,6 +71,46 @@ export async function GET() { console.error("[v1beta/models] Error fetching synced Gemini models:", err); } + const existingNames = new Set(models.map((model) => (model as any).name)); + + // Synced/imported models for non-Gemini providers + try { + const syncedModelsMap = await getAllSyncedAvailableModels(); + for (const [providerId, syncedModels] of Object.entries(syncedModelsMap)) { + if (providerId === "gemini") continue; + if (!Array.isArray(syncedModels)) continue; + for (const m of syncedModels) { + if (!m || typeof m.id !== "string") continue; + const name = `models/${providerId}/${m.id}`; + if (existingNames.has(name)) continue; + const resolved = getResolvedModelCapabilities({ + provider: providerId, + model: m.id, + }); + models.push({ + name, + displayName: m.name || m.id, + ...(typeof m.description === "string" ? { description: m.description } : {}), + supportedGenerationMethods: ["generateContent"], + inputTokenLimit: + typeof m.inputTokenLimit === "number" + ? m.inputTokenLimit + : resolved.maxInputTokens || resolved.contextWindow || 128000, + outputTokenLimit: + typeof m.outputTokenLimit === "number" + ? m.outputTokenLimit + : resolved.maxOutputTokens || 8192, + ...(m.supportsThinking === true || resolved.supportsThinking === true + ? { thinking: true } + : {}), + }); + existingNames.add(name); + } + } + } catch { + // Synced models are optional — skip on error + } + // Custom models (use stored metadata from provider APIs) try { const customModelsMap = (await getAllCustomModels()) as Record; @@ -83,8 +127,10 @@ export async function GET() { provider: providerId, model: String(m.id), }); + const name = `models/${providerId}/${m.id}`; + if (existingNames.has(name)) continue; models.push({ - name: `models/${providerId}/${m.id}`, + name, displayName: m.name || m.id, ...(typeof m.description === "string" ? { description: m.description } : {}), supportedGenerationMethods: ["generateContent"], @@ -100,6 +146,7 @@ export async function GET() { ? { thinking: true } : {}), }); + existingNames.add(name); } } } catch { diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 24c73da4e6..60ea81b4be 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -1728,6 +1728,7 @@ "disableCloud": "تعطيل السحابة", "enableCloud": "تمكين السحابة", "modelsAcrossEndpoints": "نماذج {models} عبر نقاط النهاية {endpoints}", + "loadingModels": "Loading available models...", "chatDesc": "الدردشة المتدفقة وغير المتدفقة مع جميع مقدمي الخدمة", "embeddings": "التضمين", "embeddingsDesc": "تضمينات نصية للبحث وخطوط أنابيب RAG", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index 7df37dab8b..0d13117ca7 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -1728,6 +1728,7 @@ "disableCloud": "Деактивиране на облака", "enableCloud": "Активиране на облака", "modelsAcrossEndpoints": "{models} модели в {endpoints} крайни точки", + "loadingModels": "Loading available models...", "chatDesc": "Поточно и нестрийминг чат с всички доставчици", "embeddings": "Вграждания", "embeddingsDesc": "Текстови вграждания за търсене и RAG тръбопроводи", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 4adc83cae0..f0a6007d1e 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -1832,6 +1832,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 07cb1a4522..99e013bf8f 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -1728,6 +1728,7 @@ "disableCloud": "Zakázat cloud", "enableCloud": "Povolit cloud", "modelsAcrossEndpoints": "{models} modelů napříč koncovými body {endpoints}", + "loadingModels": "Načítání dostupných modelů...", "chatDesc": "Streamovací i nestreamovací chat se všemi poskytovateli", "embeddings": "Vkládání", "embeddingsDesc": "Vkládání textu pro vyhledávání a RAG kanály", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 7d21e3ffa4..acd1846e33 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -1728,6 +1728,7 @@ "disableCloud": "Deaktiver Cloud", "enableCloud": "Aktiver Cloud", "modelsAcrossEndpoints": "{models} modeller på tværs af {endpoints} slutpunkter", + "loadingModels": "Loading available models...", "chatDesc": "Streaming og ikke-streaming chat med alle udbydere", "embeddings": "Indlejringer", "embeddingsDesc": "Tekstindlejringer til søge- og RAG-pipelines", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index b392bdb37d..e479fb070b 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -1728,6 +1728,7 @@ "disableCloud": "Cloud deaktivieren", "enableCloud": "Cloud aktivieren", "modelsAcrossEndpoints": "{models} Modelle über {endpoints} Endpunkte hinweg", + "loadingModels": "Loading available models...", "chatDesc": "Streaming- und Nicht-Streaming-Chat mit allen Anbietern", "embeddings": "Einbettungen", "embeddingsDesc": "Texteinbettungen für Such- und RAG-Pipelines", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 0c36d058eb..8f7028f435 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1833,6 +1833,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", @@ -1980,7 +1981,24 @@ "tailscaleInstallPasswordHint": "On macOS and Linux, sudo may be required for the package install and daemon start.", "tailscaleSudoPlaceholder": "Optional sudo password", "tailscaleInstalling": "Installing", - "tailscaleSudoLabel": "Sudo Password (required on macOS/Linux)" + "tailscaleSudoLabel": "Sudo Password (required on macOS/Linux)", + "ngrokTitle": "ngrok Tunnel", + "ngrokRunning": "Running", + "ngrokStarting": "Starting", + "ngrokStoppedState": "Stopped", + "ngrokNeedsAuth": "Needs Auth", + "ngrokNotInstalled": "Not installed", + "ngrokUnsupported": "Unsupported", + "ngrokError": "Error", + "ngrokEnable": "Enable Tunnel", + "ngrokDisable": "Stop Tunnel", + "ngrokUrlNotice": "Creates a public ngrok tunnel.", + "ngrokAuthTokenLabel": "Authtoken (Required if NGROK_AUTHTOKEN not set)", + "ngrokAuthTokenPlaceholder": "Enter your ngrok authtoken", + "ngrokLastError": "Last error: {error}", + "ngrokStarted": "ngrok tunnel started", + "ngrokStopped": "ngrok tunnel stopped", + "ngrokRequestFailed": "Failed to update ngrok tunnel" }, "endpoints": { "tabProxy": "Endpoint Proxy", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 30135c685c..3a013f897b 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -1728,6 +1728,7 @@ "disableCloud": "Deshabilitar la nube", "enableCloud": "Habilitar la nube", "modelsAcrossEndpoints": "{models} modelos en {endpoints} puntos finales", + "loadingModels": "Loading available models...", "chatDesc": "Chat streaming y no streaming con todos los proveedores", "embeddings": "Incrustaciones", "embeddingsDesc": "Inserciones de texto para búsqueda y canales RAG", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 4adc83cae0..f0a6007d1e 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -1832,6 +1832,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index df906fff20..33f9f7fe45 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -1728,6 +1728,7 @@ "disableCloud": "Poista Cloud käytöstä", "enableCloud": "Ota Cloud käyttöön", "modelsAcrossEndpoints": "{models} mallit {endpoints} päätepisteen välillä", + "loadingModels": "Loading available models...", "chatDesc": "Suoratoisto ja ei-striimaus chat kaikkien palveluntarjoajien kanssa", "embeddings": "Upotukset", "embeddingsDesc": "Tekstin upotukset haku- ja RAG-putkistoja varten", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index c81017f2a9..d0bbf6c5ec 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -1728,6 +1728,7 @@ "disableCloud": "Désactiver le cloud", "enableCloud": "Activer le cloud", "modelsAcrossEndpoints": "Modèles {models} sur {endpoints} points de terminaison", + "loadingModels": "Loading available models...", "chatDesc": "Chat en streaming et sans streaming avec tous les fournisseurs", "embeddings": "Intégrations", "embeddingsDesc": "Intégrations de texte pour les pipelines de recherche et RAG", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 4adc83cae0..f0a6007d1e 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -1832,6 +1832,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index cb44fc19b4..7fade3bdfa 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -1728,6 +1728,7 @@ "disableCloud": "השבת את הענן", "enableCloud": "אפשר ענן", "modelsAcrossEndpoints": "מודלים {models} על פני {endpoints} נקודות קצה", + "loadingModels": "Loading available models...", "chatDesc": "צ'אט בסטרימינג ושלא בסטרימינג עם כל הספקים", "embeddings": "הטבעות", "embeddingsDesc": "הטמעת טקסט עבור חיפוש וצינורות RAG", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 3d6e2f5dca..98d8b21ade 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -1728,6 +1728,7 @@ "disableCloud": "बादल अक्षम करें", "enableCloud": "क्लाउड सक्षम करें", "modelsAcrossEndpoints": "{models} मॉडल {endpoints} समापन बिंदु पर", + "loadingModels": "Loading available models...", "chatDesc": "सभी प्रदाताओं के साथ स्ट्रीमिंग और गैर-स्ट्रीमिंग चैट", "embeddings": "एंबेडिंग", "embeddingsDesc": "खोज और RAG पाइपलाइनों के लिए टेक्स्ट एम्बेडिंग", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 9e2acf95c4..733a345323 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -1728,6 +1728,7 @@ "disableCloud": "Felhő letiltása", "enableCloud": "Felhő engedélyezése", "modelsAcrossEndpoints": "{models} modellek {endpoints} végpontokon keresztül", + "loadingModels": "Loading available models...", "chatDesc": "Streaming és nem streaming csevegés minden szolgáltatóval", "embeddings": "Beágyazások", "embeddingsDesc": "Szövegbeágyazások keresési és RAG-folyamatokhoz", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index bbbe0f6280..9261de026b 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -1728,6 +1728,7 @@ "disableCloud": "Nonaktifkan Awan", "enableCloud": "Aktifkan Awan", "modelsAcrossEndpoints": "{models} model di seluruh titik akhir {endpoints}", + "loadingModels": "Loading available models...", "chatDesc": "Obrolan streaming & non-streaming dengan semua penyedia", "embeddings": "Penyematan", "embeddingsDesc": "Penyematan teks untuk saluran pencarian & RAG", diff --git a/src/i18n/messages/in.json b/src/i18n/messages/in.json index 4adc83cae0..f0a6007d1e 100644 --- a/src/i18n/messages/in.json +++ b/src/i18n/messages/in.json @@ -1832,6 +1832,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 71fcecbf68..f50718524d 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -1728,6 +1728,7 @@ "disableCloud": "Disabilita Nuvola", "enableCloud": "Abilita nuvola", "modelsAcrossEndpoints": "Modelli {models} sugli endpoint {endpoints}", + "loadingModels": "Loading available models...", "chatDesc": "Chat in streaming e non in streaming con tutti i provider", "embeddings": "Incorporamenti", "embeddingsDesc": "Incorporamenti di testo per pipeline di ricerca e RAG", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 89252a3f6e..e6c9f1a78e 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -1728,6 +1728,7 @@ "disableCloud": "クラウドを無効にする", "enableCloud": "クラウドを有効にする", "modelsAcrossEndpoints": "{endpoints} エンドポイントにわたる {models} モデル", + "loadingModels": "Loading available models...", "chatDesc": "すべてのプロバイダーとのストリーミングおよび非ストリーミング チャット", "embeddings": "埋め込み", "embeddingsDesc": "検索および RAG パイプライン用のテキスト埋め込み", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 9ae850aba4..c2fd694265 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -1728,6 +1728,7 @@ "disableCloud": "클라우드 비활성화", "enableCloud": "클라우드 활성화", "modelsAcrossEndpoints": "{endpoints} 엔드포인트 전반의 {models} 모델", + "loadingModels": "Loading available models...", "chatDesc": "모든 제공업체와의 스트리밍 및 비스트리밍 채팅", "embeddings": "임베딩", "embeddingsDesc": "검색 및 RAG 파이프라인을 위한 텍스트 임베딩", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 4adc83cae0..f0a6007d1e 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -1832,6 +1832,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index a2755fb439..923c6fecff 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -1728,6 +1728,7 @@ "disableCloud": "Lumpuhkan Cloud", "enableCloud": "Dayakan Cloud", "modelsAcrossEndpoints": "{models} model merentas {endpoints} titik akhir", + "loadingModels": "Loading available models...", "chatDesc": "Sembang penstriman & bukan penstriman dengan semua pembekal", "embeddings": "Embeddings", "embeddingsDesc": "Pembenaman teks untuk carian & saluran paip RAG", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 5cc649acc6..87f0bdc5a3 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -1728,6 +1728,7 @@ "disableCloud": "Schakel Cloud uit", "enableCloud": "Schakel Cloud in", "modelsAcrossEndpoints": "{models} modellen voor {endpoints} eindpunten", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat met alle providers", "embeddings": "Inbedding", "embeddingsDesc": "Tekstinsluitingen voor zoek- en RAG-pijplijnen", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index f324e86394..7f420cd60c 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -1728,6 +1728,7 @@ "disableCloud": "Deaktiver Cloud", "enableCloud": "Aktiver Cloud", "modelsAcrossEndpoints": "{models} modeller på tvers av {endpoints} endepunkter", + "loadingModels": "Loading available models...", "chatDesc": "Streaming og ikke-streaming chat med alle leverandører", "embeddings": "Innstøpinger", "embeddingsDesc": "Tekstinnbygging for søk og RAG-rørledninger", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index dc68fede35..47cb2b0ea0 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -1728,6 +1728,7 @@ "disableCloud": "Huwag paganahin ang Cloud", "enableCloud": "Paganahin ang Cloud", "modelsAcrossEndpoints": "{models} na mga modelo sa {endpoints} na mga endpoint", + "loadingModels": "Loading available models...", "chatDesc": "Streaming at non-streaming na chat sa lahat ng provider", "embeddings": "Mga pag-embed", "embeddingsDesc": "Mga text embed para sa paghahanap at RAG pipeline", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 46fadd3785..fccc303a88 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -1728,6 +1728,7 @@ "disableCloud": "Wyłącz chmurę", "enableCloud": "Włącz chmurę", "modelsAcrossEndpoints": "{models} modele w {endpoints} punktach końcowych", + "loadingModels": "Loading available models...", "chatDesc": "Czat strumieniowy i niestreamingowy ze wszystkimi dostawcami", "embeddings": "Osadzenia", "embeddingsDesc": "Osadzanie tekstu dla potoków wyszukiwania i RAG", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 7699421d57..f796253b88 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -1755,6 +1755,7 @@ "disableCloud": "Desativar Nuvem", "enableCloud": "Ativar Nuvem", "modelsAcrossEndpoints": "{models} modelos em {endpoints} endpoints", + "loadingModels": "Carregando modelos disponíveis...", "chatDesc": "Chat streaming e não-streaming com todos os provedores", "embeddings": "Incorporações", "embeddingsDesc": "Embeddings de texto para busca e pipelines RAG", @@ -1902,7 +1903,24 @@ "tailscaleInstallPasswordHint": "On macOS and Linux, sudo may be required for the package install and daemon start.", "tailscaleSudoPlaceholder": "Optional sudo password", "tailscaleInstalling": "Installing", - "tailscaleSudoLabel": "Senha Sudo (obrigatória em macOS/Linux)" + "tailscaleSudoLabel": "Senha Sudo (obrigatória em macOS/Linux)", + "ngrokTitle": "Túnel ngrok", + "ngrokRunning": "Rodando", + "ngrokStarting": "Iniciando", + "ngrokStoppedState": "Parado", + "ngrokNeedsAuth": "Requer Autenticação", + "ngrokNotInstalled": "Não instalado", + "ngrokUnsupported": "Não suportado", + "ngrokError": "Erro", + "ngrokEnable": "Habilitar Túnel", + "ngrokDisable": "Parar Túnel", + "ngrokUrlNotice": "Cria um túnel público ngrok.", + "ngrokAuthTokenLabel": "Authtoken (Necessário caso NGROK_AUTHTOKEN não esteja definido)", + "ngrokAuthTokenPlaceholder": "Digite seu authtoken do ngrok", + "ngrokLastError": "Último erro: {error}", + "ngrokStarted": "Túnel ngrok iniciado", + "ngrokStopped": "Túnel ngrok parado", + "ngrokRequestFailed": "Falha ao atualizar túnel ngrok" }, "endpoints": { "tabProxy": "Proxy de terminal", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index c7369919f5..e27f865647 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -1728,6 +1728,7 @@ "disableCloud": "Desativar nuvem", "enableCloud": "Habilitar nuvem", "modelsAcrossEndpoints": "Modelos {models} em endpoints {endpoints}", + "loadingModels": "Loading available models...", "chatDesc": "Bate-papo com e sem streaming com todos os provedores", "embeddings": "Incorporações", "embeddingsDesc": "Incorporações de texto para pipelines de pesquisa e RAG", @@ -1874,7 +1875,24 @@ "tailscaleInstallIntro": "Installs Tailscale on this machine and prepares OmniRoute to enable Funnel.", "tailscaleInstallPasswordHint": "On macOS and Linux, sudo may be required for the package install and daemon start.", "tailscaleSudoPlaceholder": "Optional sudo password", - "tailscaleInstalling": "Installing" + "tailscaleInstalling": "Installing", + "ngrokTitle": "Túnel ngrok", + "ngrokRunning": "Rodando", + "ngrokStarting": "Iniciando", + "ngrokStoppedState": "Parado", + "ngrokNeedsAuth": "Requer Autenticação", + "ngrokNotInstalled": "Não instalado", + "ngrokUnsupported": "Não suportado", + "ngrokError": "Erro", + "ngrokEnable": "Habilitar Túnel", + "ngrokDisable": "Parar Túnel", + "ngrokUrlNotice": "Cria um túnel público ngrok.", + "ngrokAuthTokenLabel": "Authtoken (Necessário caso NGROK_AUTHTOKEN não esteja definido)", + "ngrokAuthTokenPlaceholder": "Digite seu authtoken do ngrok", + "ngrokLastError": "Último erro: {error}", + "ngrokStarted": "Túnel ngrok iniciado", + "ngrokStopped": "Túnel ngrok parado", + "ngrokRequestFailed": "Falha ao atualizar túnel ngrok" }, "endpoints": { "tabProxy": "Endpoint Proxy", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 382cef4020..09e5d7c7fc 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -1728,6 +1728,7 @@ "disableCloud": "Dezactivați Cloud", "enableCloud": "Activați Cloud", "modelsAcrossEndpoints": "{models} modele în {endpoints} puncte finale", + "loadingModels": "Loading available models...", "chatDesc": "Chat în flux și non-stream cu toți furnizorii", "embeddings": "Înglobări", "embeddingsDesc": "Încorporare de text pentru conductele de căutare și RAG", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 8d1f489110..f93064b68d 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -1752,6 +1752,7 @@ "disableCloud": "Отключить облако", "enableCloud": "Включить облако", "modelsAcrossEndpoints": "{models} модели на {endpoints} конечных точках", + "loadingModels": "Загрузка доступных моделей...", "chatDesc": "Потоковое и непотоковое чат со всеми провайдерами", "embeddings": "Вложения", "embeddingsDesc": "Встраивание текста для конвейеров поиска и RAG", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 90c90e3537..e6468d0fb2 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -1728,6 +1728,7 @@ "disableCloud": "Zakázať cloud", "enableCloud": "Povoliť cloud", "modelsAcrossEndpoints": "{models} modelov v {endpoints} koncových bodoch", + "loadingModels": "Loading available models...", "chatDesc": "Streaming a non-streaming chat so všetkými poskytovateľmi", "embeddings": "Vloženie", "embeddingsDesc": "Textové vloženie pre vyhľadávanie a RAG potrubia", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index f782de3c2c..a4423b2711 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -1728,6 +1728,7 @@ "disableCloud": "Inaktivera Cloud", "enableCloud": "Aktivera Cloud", "modelsAcrossEndpoints": "{models} modeller över {endpoints} slutpunkter", + "loadingModels": "Loading available models...", "chatDesc": "Strömmande och icke-strömmande chatt med alla leverantörer", "embeddings": "Inbäddningar", "embeddingsDesc": "Textinbäddningar för sök- och RAG-pipelines", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index 4adc83cae0..f0a6007d1e 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -1832,6 +1832,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 4adc83cae0..f0a6007d1e 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -1832,6 +1832,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index 4adc83cae0..f0a6007d1e 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -1832,6 +1832,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 7eed51c7d6..52f0bc361d 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -1728,6 +1728,7 @@ "disableCloud": "ปิดการใช้งานคลาวด์", "enableCloud": "เปิดใช้งานคลาวด์", "modelsAcrossEndpoints": "{models} โมเดลข้ามจุดสิ้นสุด {endpoints}", + "loadingModels": "Loading available models...", "chatDesc": "การแชทแบบสตรีมมิงและไม่สตรีมกับผู้ให้บริการทั้งหมด", "embeddings": "การฝัง", "embeddingsDesc": "การฝังข้อความสำหรับการค้นหาและไปป์ไลน์ RAG", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 79c06bcd15..d282b0617c 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -1728,6 +1728,7 @@ "disableCloud": "Bulutu Devre Dışı Bırak", "enableCloud": "Bulutu Etkinleştir", "modelsAcrossEndpoints": "{endpoints} uç noktada {models} model", + "loadingModels": "Kullanılabilir modeller yükleniyor...", "chatDesc": "Tüm sağlayıcılarla akışlı ve akışsız sohbet", "embeddings": "Embedding'ler", "embeddingsDesc": "Arama ve RAG iş akışları için metin embedding'leri", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 59f17551dd..1918225e9a 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -1728,6 +1728,7 @@ "disableCloud": "Вимкнути хмару", "enableCloud": "Увімкнути хмару", "modelsAcrossEndpoints": "{models} моделей через {endpoints} кінцевих точок", + "loadingModels": "Loading available models...", "chatDesc": "Потоковий і непотоковий чат з усіма провайдерами", "embeddings": "Вбудовування", "embeddingsDesc": "Вбудовування тексту для конвеєрів пошуку та RAG", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 4adc83cae0..f0a6007d1e 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -1832,6 +1832,7 @@ "disableCloud": "Disable Cloud", "enableCloud": "Enable Cloud", "modelsAcrossEndpoints": "{models} models across {endpoints} endpoints", + "loadingModels": "Loading available models...", "chatDesc": "Streaming & non-streaming chat with all providers", "embeddings": "Embeddings", "embeddingsDesc": "Text embeddings for search & RAG pipelines", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index dbf4cb40f4..c02f68f5c2 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -1728,6 +1728,7 @@ "disableCloud": "Tắt đám mây", "enableCloud": "Kích hoạt đám mây", "modelsAcrossEndpoints": "{models} mô hình trên {endpoints} điểm cuối", + "loadingModels": "Loading available models...", "chatDesc": "Trò chuyện trực tuyến và không phát trực tuyến với tất cả các nhà cung cấp", "embeddings": "Nhúng", "embeddingsDesc": "Nhúng văn bản cho đường dẫn tìm kiếm & RAG", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 3f7f512e60..429d83ecac 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -1335,13 +1335,17 @@ } } }, - "autoConfiguredTab": "Auto Configured Tab", - "toolCategoriesDesc": "Tool Categories Desc", - "allToolsTab": "All Tools Tab", - "guidedClientsTab": "Guided Clients Tab", - "mitmClientsTab": "Mitm Clients Tab", - "toolCategories": "Tool Categories", - "visibleToolsCount": "Visible Tools Count" + "autoConfiguredTab": "自动配置", + "toolCategoriesDesc": "配置 AI 编程助手通过 OmniRoute 路由", + "allToolsTab": "所有工具", + "guidedClientsTab": "引导客户端", + "mitmClientsTab": "MITM 客户端", + "customCliTab": "自定义 CLI", + "toolCategories": "工具分类", + "visibleToolsCount": "{count} 个工具可用", + "installationGuide": "安装指南", + "whenToUseLabel": "何时使用", + "openToolDocs": "打开工具文档" }, "combos": { "title": "组合", @@ -1826,6 +1830,7 @@ "disableCloud": "禁用云端", "enableCloud": "启用云端", "modelsAcrossEndpoints": "{endpoints} 个端点共提供 {models} 个模型", + "loadingModels": "正在加载可用模型...", "chatDesc": "支持所有提供商的流式与非流式聊天", "embeddings": "Embeddings", "embeddingsDesc": "用于搜索和 RAG 流程的文本向量", @@ -4324,7 +4329,9 @@ "addMemory": "添加记忆", "type": "类型", "key": "键", + "keyPlaceholder": "例如:user_preference_theme", "content": "内容", + "contentPlaceholder": "例如:偏好深色模式", "created": "创建时间", "actions": "操作", "delete": "删除", @@ -4332,7 +4339,16 @@ "episodic": "情景型", "procedural": "程序型", "semantic": "语义型", - "a": "A" + "previous": "上一页", + "next": "下一页", + "pageInfo": "第 {page} 页,共 {totalPages} 页(共 {total} 条)", + "checkingHealth": "检查中...", + "checkHealth": "检查健康", + "pipelineOk": "Pipeline 正常 ({latencyMs}ms)", + "pipelineError": "Pipeline 错误", + "healthUnknown": "健康状态未知", + "cancel": "取消", + "save": "保存" }, "skills": { "title": "技能", diff --git a/src/lib/combos/builderOptions.ts b/src/lib/combos/builderOptions.ts index 0aea042b83..6bb17a8a71 100644 --- a/src/lib/combos/builderOptions.ts +++ b/src/lib/combos/builderOptions.ts @@ -21,7 +21,7 @@ import type { RegistryModel } from "@omniroute/open-sse/config/providerRegistry. type JsonRecord = Record; -type BuilderModelSource = "api-sync" | "system" | "custom" | "fallback"; +type BuilderModelSource = "imported" | "system" | "custom" | "fallback"; type BuilderConnectionStatus = "active" | "inactive" | "rate-limited" | "error"; type ProviderVisual = { icon: string; color: string; source: "system" | "provider-node" }; @@ -159,7 +159,7 @@ function isChatCapable(supportedEndpoints: string[] | undefined): boolean { function getSourcePriority(source: BuilderModelSource): number { switch (source) { - case "api-sync": + case "imported": return 0; case "system": return 1; @@ -412,7 +412,7 @@ export async function getComboBuilderOptions(): Promise 0 ? value.trim() : null; +} + function getKeyValue(row: unknown): { key: string | null; value: string | null } { const record = asRecord(row); return { @@ -378,7 +382,7 @@ export async function addCustomModel( } /** - * Replace the entire custom models list for a provider (used by auto-sync). + * Replace the entire custom models list for a provider. * Preserves per-model compatibility overrides for models that still exist. */ export async function replaceCustomModels( @@ -397,7 +401,7 @@ export async function replaceCustomModels( { allowEmpty = false }: { allowEmpty?: boolean } = {} ) { // Guard: skip destructive clear when the caller hasn't explicitly opted in. - // This prevents auto-sync from wiping manually-imported models when the + // This prevents callers from wiping manually added models when the // upstream /models endpoint fails, times out, or returns an empty list. if (models.length === 0 && !allowEmpty) { const existing = await getCustomModels(providerId); @@ -520,7 +524,7 @@ export async function removeCustomModel(providerId: string, modelId: string) { export interface SyncedAvailableModel { id: string; name: string; - source: "api-sync"; + source: "imported"; supportedEndpoints?: string[]; inputTokenLimit?: number; outputTokenLimit?: number; @@ -528,6 +532,57 @@ export interface SyncedAvailableModel { supportsThinking?: boolean; } +type SyncedAvailableModelInput = Omit & { + source?: string; +}; + +function normalizeSyncedAvailableModel(model: unknown): SyncedAvailableModel | null { + const record = asRecord(model); + const id = + toNonEmptyString(record.id) || toNonEmptyString(record.name) || toNonEmptyString(record.model); + if (!id) return null; + + const name = + toNonEmptyString(record.name) || + toNonEmptyString(record.displayName) || + toNonEmptyString(record.model) || + id; + const supportedEndpoints = Array.isArray(record.supportedEndpoints) + ? Array.from( + new Set( + record.supportedEndpoints + .map((endpoint) => toNonEmptyString(endpoint)) + .filter((endpoint): endpoint is string => Boolean(endpoint)) + ) + ).sort() + : undefined; + + return { + id, + name, + source: "imported", + ...(supportedEndpoints && supportedEndpoints.length > 0 ? { supportedEndpoints } : {}), + ...(typeof record.inputTokenLimit === "number" + ? { inputTokenLimit: record.inputTokenLimit } + : {}), + ...(typeof record.outputTokenLimit === "number" + ? { outputTokenLimit: record.outputTokenLimit } + : {}), + ...(typeof record.description === "string" ? { description: record.description } : {}), + ...(record.supportsThinking === true ? { supportsThinking: true } : {}), + }; +} + +function normalizeSyncedAvailableModels(models: unknown): SyncedAvailableModel[] { + if (!Array.isArray(models)) return []; + const deduped = new Map(); + for (const model of models) { + const normalized = normalizeSyncedAvailableModel(model); + if (normalized) deduped.set(normalized.id, normalized); + } + return Array.from(deduped.values()); +} + /** * Get synced available models for a specific provider connection. */ @@ -544,7 +599,7 @@ export async function getSyncedAvailableModelsForConnection( if (!value) return []; try { const models = JSON.parse(value); - return Array.isArray(models) ? models : []; + return normalizeSyncedAvailableModels(models); } catch { return []; } @@ -566,7 +621,7 @@ export async function getSyncedAvailableModels( for (const row of rows) { const { key, value } = getKeyValue(row); if (!key || value === null) continue; - const models: SyncedAvailableModel[] = JSON.parse(value); + const models = normalizeSyncedAvailableModels(JSON.parse(value)); for (const m of models) { if (m.id) map.set(m.id, m); } @@ -591,7 +646,7 @@ export async function getAllSyncedAvailableModels(): Promise< if (!key || value === null) continue; const providerId = key.split(":")[0]; if (!byProvider.has(providerId)) byProvider.set(providerId, new Map()); - const models: SyncedAvailableModel[] = JSON.parse(value); + const models = normalizeSyncedAvailableModels(JSON.parse(value)); const map = byProvider.get(providerId)!; for (const m of models) { if (m.id) map.set(m.id, m); @@ -611,18 +666,19 @@ export async function getAllSyncedAvailableModels(): Promise< export async function replaceSyncedAvailableModelsForConnection( providerId: string, connectionId: string, - models: SyncedAvailableModel[] + models: SyncedAvailableModelInput[] ): Promise { const db = getDbInstance(); const key = `${providerId}:${connectionId}`; - if (models.length === 0) { + const normalizedModels = normalizeSyncedAvailableModels(models); + if (normalizedModels.length === 0) { db.prepare("DELETE FROM key_value WHERE namespace = 'syncedAvailableModels' AND key = ?").run( key ); } else { db.prepare( "INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('syncedAvailableModels', ?, ?)" - ).run(key, JSON.stringify(models)); + ).run(key, JSON.stringify(normalizedModels)); } backupDbFile("pre-write"); // Return the full unioned list for the provider diff --git a/src/lib/db/quotaSnapshots.ts b/src/lib/db/quotaSnapshots.ts index c1d9bb6721..7e40070b11 100644 --- a/src/lib/db/quotaSnapshots.ts +++ b/src/lib/db/quotaSnapshots.ts @@ -19,22 +19,32 @@ export function saveQuotaSnapshot(snapshot: Omit rowToCamel(r) as unknown as QuotaSnapshotRow); + try { + const sql = `SELECT * FROM quota_snapshots WHERE ${conditions.join(" AND ")} ORDER BY created_at ASC`; + const rows = db.prepare(sql).all(...params); + return rows.map((r) => rowToCamel(r) as unknown as QuotaSnapshotRow); + } catch (err: any) { + if (err?.message?.includes("no such table")) { + return []; + } + throw err; + } } export function getAggregatedSnapshots(opts: { @@ -100,34 +117,41 @@ export function getAggregatedSnapshots(opts: { const selectKey = opts.aggregateBy === "connection" ? "provider || ':' || connection_id as provider" : "provider"; - const sql = ` - SELECT - datetime((strftime('%s', created_at) / ${bucketSeconds}) * ${bucketSeconds}, 'unixepoch') as bucket, - ${selectKey}, - AVG(remaining_percentage) as remainingPct, - MAX(is_exhausted) as isExhausted, - window_key - FROM quota_snapshots - WHERE ${conditions.join(" AND ")} - GROUP BY ${groupFields} - ORDER BY bucket ASC - `; + try { + const sql = ` + SELECT + datetime((strftime('%s', created_at) / ${bucketSeconds}) * ${bucketSeconds}, 'unixepoch') as bucket, + ${selectKey}, + AVG(remaining_percentage) as remainingPct, + MAX(is_exhausted) as isExhausted, + window_key + FROM quota_snapshots + WHERE ${conditions.join(" AND ")} + GROUP BY ${groupFields} + ORDER BY bucket ASC + `; - const rows = db.prepare(sql).all(...params) as Array<{ - bucket: string; - provider: string; - remainingPct: number | null; - isExhausted: number; - windowKey: string; - }>; + const rows = db.prepare(sql).all(...params) as Array<{ + bucket: string; + provider: string; + remainingPct: number | null; + isExhausted: number; + windowKey: string; + }>; - return rows.map((r) => ({ - timestamp: r.bucket, - provider: r.provider, - remainingPct: r.remainingPct ?? 0, - isExhausted: r.isExhausted === 1, - windowKey: r.windowKey, - })); + return rows.map((r) => ({ + timestamp: r.bucket, + provider: r.provider, + remainingPct: r.remainingPct ?? 0, + isExhausted: r.isExhausted === 1, + windowKey: r.windowKey, + })); + } catch (err: any) { + if (err?.message?.includes("no such table")) { + return []; + } + throw err; + } } export function cleanupOldSnapshots(retentionDays = 90): number { @@ -141,8 +165,14 @@ export function cleanupOldSnapshots(retentionDays = 90): number { const db = getDbInstance() as unknown as DbLike; const cutoffDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString(); - const result = db.prepare("DELETE FROM quota_snapshots WHERE created_at < ?").run(cutoffDate); - lastCleanupAt = now; - - return result.changes; + try { + const result = db.prepare("DELETE FROM quota_snapshots WHERE created_at < ?").run(cutoffDate); + lastCleanupAt = now; + return result.changes; + } catch (err: any) { + if (err?.message?.includes("no such table")) { + return 0; + } + throw err; + } } diff --git a/src/lib/memory/store.ts b/src/lib/memory/store.ts index df9f554f55..e724a0f2cf 100644 --- a/src/lib/memory/store.ts +++ b/src/lib/memory/store.ts @@ -77,15 +77,78 @@ function rowToMemory(row: MemoryRow): Memory { } /** - * Create a new memory entry + * Find existing memory by apiKeyId and key (for UPSERT logic) + */ +function findExistingMemory( + db: ReturnType, + apiKeyId: string, + key: string +): MemoryRow | undefined { + if (!key) return undefined; + const stmt = db.prepare( + "SELECT * FROM memories WHERE api_key_id = ? AND key = ? ORDER BY created_at DESC LIMIT 1" + ); + return stmt.get(apiKeyId, key) as MemoryRow | undefined; +} + +/** + * Create a new memory entry (UPSERT: updates existing if same apiKeyId + key) */ export async function createMemory( memory: Omit ): Promise { const db = getDbInstance(); - const id = crypto.randomUUID(); const now = new Date().toISOString(); + // Check for existing memory with same apiKeyId + key (UPSERT logic) + const existing = memory.key ? findExistingMemory(db, memory.apiKeyId, memory.key) : undefined; + + if (existing) { + // UPDATE existing record + const updatedMetadata = { ...parseJSON(existing.metadata), ...memory.metadata }; + const stmt = db.prepare( + "UPDATE memories SET content = ?, metadata = ?, updated_at = ?, session_id = ?, type = ?, expires_at = ? WHERE id = ?" + ); + stmt.run( + memory.content, + JSON.stringify(updatedMetadata), + now, + memory.sessionId, + memory.type, + memory.expiresAt ?? null, + existing.id + ); + + const updatedMemory: Memory = { + id: String(existing.id), + apiKeyId: memory.apiKeyId, + sessionId: memory.sessionId, + type: memory.type, + key: memory.key, + content: memory.content, + metadata: updatedMetadata, + createdAt: new Date(String(existing.created_at)), + updatedAt: new Date(now), + expiresAt: memory.expiresAt ?? null, + }; + + // Invalidate and update cache + invalidateMemoryCache(existing.id); + evictIfNeeded(_memoryCache); + _memoryCache.set(existing.id, { value: updatedMemory, timestamp: Date.now() }); + + log.info("memory.updated", { + apiKeyId: memory.apiKeyId, + type: memory.type, + id: existing.id, + key: memory.key, + }); + + return updatedMemory; + } + + // INSERT new record if not exists + const id = crypto.randomUUID(); const stmt = db.prepare( "INSERT INTO memories (id, api_key_id, session_id, type, key, content, metadata, created_at, updated_at, expires_at) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" diff --git a/src/lib/ngrokTunnel.ts b/src/lib/ngrokTunnel.ts new file mode 100644 index 0000000000..369c3a8929 --- /dev/null +++ b/src/lib/ngrokTunnel.ts @@ -0,0 +1,142 @@ +import { getRuntimePorts } from "@/lib/runtime/ports"; + +export type TunnelPhase = + | "unsupported" + | "not_installed" + | "stopped" + | "needs_auth" + | "starting" + | "running" + | "error"; + +export type NgrokTunnelStatus = { + supported: boolean; + installed: boolean; + running: boolean; + publicUrl: string | null; + apiUrl: string | null; + targetUrl: string; + phase: TunnelPhase; + lastError: string | null; +}; + +// Next.js hot-reloading safe global storage for the listener +const globalForNgrok = globalThis as unknown as { + __ngrokListener: any; +}; + +let startPromise: Promise | null = null; + +function getLocalTargetUrl() { + const { apiPort } = getRuntimePorts(); + return `http://127.0.0.1:${apiPort}`; +} + +function getTunnelApiUrl(publicUrl: string | null) { + return publicUrl ? `${publicUrl.replace(/\/$/, "")}/v1` : null; +} + +export async function getNgrokTunnelStatus(): Promise { + const targetUrl = getLocalTargetUrl(); + const tokenAvailable = !!( + process.env.NGROK_AUTHTOKEN && process.env.NGROK_AUTHTOKEN.trim() !== "" + ); + const listener = globalForNgrok.__ngrokListener; + let currentUrl = null; + + if (listener) { + try { + currentUrl = typeof listener.url === "function" ? listener.url() : listener.url; + } catch { + // Ignored + } + } + + return { + supported: true, + installed: true, + running: currentUrl !== null, + publicUrl: currentUrl || null, + apiUrl: currentUrl ? getTunnelApiUrl(currentUrl) : null, + targetUrl, + phase: currentUrl === null ? (tokenAvailable ? "stopped" : "needs_auth") : "running", + lastError: null, + }; +} + +export async function startNgrokTunnel(inputAuthToken?: string): Promise { + const current = await getNgrokTunnelStatus(); + if (current.running) return current; + if (startPromise) return startPromise; + + startPromise = (async () => { + try { + const authToken = + inputAuthToken && inputAuthToken.trim() !== "" + ? inputAuthToken.trim() + : process.env.NGROK_AUTHTOKEN; + + if (!authToken) { + return { + ...(await getNgrokTunnelStatus()), + phase: "needs_auth", + lastError: "An ngrok authtoken is required.", + }; + } + + // Dynamically import ngrok so it doesn't break environments where native build fails if not used + const ngrok = await import("@ngrok/ngrok"); + + const targetUrl = getLocalTargetUrl(); + const listenerOptions: any = { addr: targetUrl }; + + if (!inputAuthToken && process.env.NGROK_AUTHTOKEN) { + listenerOptions.authtoken_from_env = true; + } else { + listenerOptions.authtoken = authToken; + } + + const listener = await ngrok.forward(listenerOptions); + globalForNgrok.__ngrokListener = listener; + + let url = null; + try { + url = typeof listener.url === "function" ? listener.url() : listener.url; + } catch { + // Ignored + } + + if (!url) { + await stopNgrokTunnel(); + throw new Error("ngrok did not return a public URL."); + } + + return await getNgrokTunnelStatus(); + } catch (error) { + return { + ...(await getNgrokTunnelStatus()), + phase: "error", + lastError: error instanceof Error ? error.message : String(error), + }; + } finally { + startPromise = null; + } + })(); + + return startPromise; +} + +export async function stopNgrokTunnel(): Promise { + const listener = globalForNgrok.__ngrokListener; + if (listener) { + try { + if (typeof listener.close === "function") { + await listener.close(); + } + } catch (e) { + // Ignore close errors + } + globalForNgrok.__ngrokListener = undefined; + } + return await getNgrokTunnelStatus(); +} diff --git a/src/lib/oauth/providers.ts b/src/lib/oauth/providers.ts index e00cd994ec..5709042630 100644 --- a/src/lib/oauth/providers.ts +++ b/src/lib/oauth/providers.ts @@ -81,12 +81,12 @@ export async function exchangeTokens(providerName, code, redirectUri, codeVerifi /** * Request device code (for device_code flow) */ -export async function requestDeviceCode(providerName, codeChallenge) { +export async function requestDeviceCode(providerName, codeChallenge, configOverride = null) { const provider = getProvider(providerName); if (provider.flowType !== "device_code") { throw new Error(`Provider ${providerName} does not support device code flow`); } - return await provider.requestDeviceCode(provider.config, codeChallenge); + return await provider.requestDeviceCode(configOverride || provider.config, codeChallenge); } /** diff --git a/src/lib/oauth/providers/kiro.ts b/src/lib/oauth/providers/kiro.ts index 945c75cd74..80c03a7d81 100644 --- a/src/lib/oauth/providers/kiro.ts +++ b/src/lib/oauth/providers/kiro.ts @@ -4,6 +4,27 @@ export const kiro = { config: KIRO_CONFIG, flowType: "device_code", requestDeviceCode: async (config) => { + const regionMatch = String(config.tokenUrl || "").match(/oidc\.([a-z0-9-]+)\.amazonaws\.com/i); + const resolvedRegion = regionMatch?.[1] || "us-east-1"; + const registerPayload: { + clientName: string; + clientType: string; + scopes: string[]; + grantTypes: string[]; + issuerUrl?: string; + } = { + clientName: config.clientName, + clientType: config.clientType, + scopes: config.scopes, + grantTypes: config.grantTypes, + }; + + // For enterprise IDC custom startUrl flows, issuerUrl can differ per tenant. + // Sending a fixed issuerUrl often causes invalid_request during device auth. + if (config.issuerUrl && !config.skipIssuerUrlForRegistration) { + registerPayload.issuerUrl = config.issuerUrl; + } + // Step 1: Register client with AWS SSO OIDC const registerRes = await fetch(config.registerClientUrl, { method: "POST", @@ -11,13 +32,7 @@ export const kiro = { "Content-Type": "application/json", Accept: "application/json", }, - body: JSON.stringify({ - clientName: config.clientName, - clientType: config.clientType, - scopes: config.scopes, - grantTypes: config.grantTypes, - issuerUrl: config.issuerUrl, - }), + body: JSON.stringify(registerPayload), }); if (!registerRes.ok) { @@ -57,10 +72,14 @@ export const kiro = { interval: deviceData.interval || 5, _clientId: clientInfo.clientId, _clientSecret: clientInfo.clientSecret, + _region: resolvedRegion, }; }, pollToken: async (config, deviceCode, codeVerifier, extraData) => { - const response = await fetch(config.tokenUrl, { + const tokenRegion = extraData?._region || "us-east-1"; + const tokenUrl = `https://oidc.${tokenRegion}.amazonaws.com/token`; + + const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json", @@ -91,6 +110,7 @@ export const kiro = { expires_in: data.expiresIn, _clientId: extraData?._clientId, _clientSecret: extraData?._clientSecret, + _region: tokenRegion, }, }; } @@ -110,6 +130,7 @@ export const kiro = { providerSpecificData: { clientId: tokens._clientId, clientSecret: tokens._clientSecret, + region: tokens._region, }, }), }; diff --git a/src/lib/providerModels/managedModelImport.ts b/src/lib/providerModels/managedModelImport.ts index 41a2555cec..227d96f9ae 100644 --- a/src/lib/providerModels/managedModelImport.ts +++ b/src/lib/providerModels/managedModelImport.ts @@ -1,7 +1,10 @@ import { getCustomModels, + getSyncedAvailableModelsForConnection, + mergeModelCompatOverride, replaceCustomModels, replaceSyncedAvailableModelsForConnection, + type ModelCompatPatch, type SyncedAvailableModel, } from "@/lib/db/models"; import { @@ -9,7 +12,6 @@ import { usesManagedAvailableModels, } from "@/lib/providerModels/managedAvailableModels"; import { normalizeDiscoveredModels } from "@/lib/providerModels/modelDiscovery"; -import { getModelsByProviderId } from "@/shared/constants/models"; type JsonRecord = Record; @@ -18,7 +20,7 @@ export type ManagedModelImportMode = "merge" | "sync"; export type ManagedImportedModel = { id: string; name: string; - source: "api-sync"; + source: "imported"; apiFormat: "chat-completions"; supportedEndpoints?: string[]; inputTokenLimit?: number; @@ -34,42 +36,39 @@ function toNonEmptyString(value: unknown): string | null { function normalizeManagedSource(source: unknown): string { const normalized = toNonEmptyString(source)?.toLowerCase(); if (normalized === "api-sync" || normalized === "auto-sync" || normalized === "imported") { - return "api-sync"; + return "imported"; } return normalized || "manual"; } -function normalizeImportedModels( - providerId: string, - fetchedModels: unknown -): ManagedImportedModel[] { +function normalizeImportedModels(fetchedModels: unknown): ManagedImportedModel[] { const discovered = normalizeDiscoveredModels(fetchedModels); - const registryIds = new Set(getModelsByProviderId(providerId).map((model: any) => model.id)); - return discovered - .filter((model) => !registryIds.has(model.id)) - .map((model) => ({ - id: model.id, - name: model.name || model.id, - source: "api-sync", - apiFormat: "chat-completions", - ...(Array.isArray(model.supportedEndpoints) && model.supportedEndpoints.length > 0 - ? { supportedEndpoints: model.supportedEndpoints } - : {}), - ...(typeof model.inputTokenLimit === "number" - ? { inputTokenLimit: model.inputTokenLimit } - : {}), - ...(typeof model.outputTokenLimit === "number" - ? { outputTokenLimit: model.outputTokenLimit } - : {}), - ...(typeof model.description === "string" ? { description: model.description } : {}), - ...(model.supportsThinking === true ? { supportsThinking: true } : {}), - })); + return discovered.map((model) => ({ + id: model.id, + name: model.name || model.id, + source: "imported", + apiFormat: "chat-completions", + ...(Array.isArray(model.supportedEndpoints) && model.supportedEndpoints.length > 0 + ? { supportedEndpoints: model.supportedEndpoints } + : {}), + ...(typeof model.inputTokenLimit === "number" + ? { inputTokenLimit: model.inputTokenLimit } + : {}), + ...(typeof model.outputTokenLimit === "number" + ? { outputTokenLimit: model.outputTokenLimit } + : {}), + ...(typeof model.description === "string" ? { description: model.description } : {}), + ...(model.supportsThinking === true ? { supportsThinking: true } : {}), + })); } -function isManagedDiscoveredSource(source: unknown): boolean { - const normalized = toNonEmptyString(source)?.toLowerCase(); - return normalized === "api-sync" || normalized === "auto-sync" || normalized === "imported"; +function isImportedSource(source: unknown): boolean { + return normalizeManagedSource(source) === "imported"; +} + +function getModelId(model: JsonRecord): string | null { + return toNonEmptyString(model.id); } function summarizeImportedChanges( @@ -86,9 +85,30 @@ function summarizeImportedChanges( const toComparable = (model: JsonRecord | undefined) => { if (!model) return null; + const id = toNonEmptyString(model.id) || ""; + const supportedEndpoints = Array.isArray(model.supportedEndpoints) + ? Array.from( + new Set( + model.supportedEndpoints + .map((endpoint) => toNonEmptyString(endpoint)) + .filter((endpoint): endpoint is string => Boolean(endpoint)) + ) + ).sort() + : ["chat"]; return { - ...model, + id, + name: toNonEmptyString(model.name) || id, source: normalizeManagedSource(model.source), + apiFormat: toNonEmptyString(model.apiFormat) || "chat-completions", + supportedEndpoints, + ...(typeof model.inputTokenLimit === "number" + ? { inputTokenLimit: model.inputTokenLimit } + : {}), + ...(typeof model.outputTokenLimit === "number" + ? { outputTokenLimit: model.outputTokenLimit } + : {}), + ...(typeof model.description === "string" ? { description: model.description } : {}), + ...(model.supportsThinking === true ? { supportsThinking: true } : {}), }; }; @@ -125,37 +145,71 @@ function collectAddedImportedModels( return importedModels.filter((model) => !previousIds.has(model.id)); } +function getCompatPatchFromCustomModel(model: JsonRecord): ModelCompatPatch | null { + const patch: ModelCompatPatch = {}; + + if (typeof model.normalizeToolCallId === "boolean") { + patch.normalizeToolCallId = model.normalizeToolCallId; + } + if (typeof model.preserveOpenAIDeveloperRole === "boolean") { + patch.preserveOpenAIDeveloperRole = model.preserveOpenAIDeveloperRole; + } + if (typeof model.isHidden === "boolean") { + patch.isHidden = model.isHidden; + } + if (model.compatByProtocol && typeof model.compatByProtocol === "object") { + patch.compatByProtocol = model.compatByProtocol as ModelCompatPatch["compatByProtocol"]; + } + if (model.upstreamHeaders && typeof model.upstreamHeaders === "object") { + patch.upstreamHeaders = model.upstreamHeaders as Record; + } + + return Object.keys(patch).length > 0 ? patch : null; +} + +function preserveRemovedCustomModelCompat(providerId: string, removedModels: JsonRecord[]) { + for (const model of removedModels) { + const modelId = getModelId(model); + if (!modelId) continue; + const patch = getCompatPatchFromCustomModel(model); + if (!patch) continue; + mergeModelCompatOverride(providerId, modelId, patch); + } +} + export async function importManagedModels({ providerId, connectionId, fetchedModels, mode, + previousSyncedAvailableModels: previousSyncedAvailableModelsInput, }: { providerId: string; connectionId: string; fetchedModels: unknown; mode: ManagedModelImportMode; + previousSyncedAvailableModels?: SyncedAvailableModel[]; }) { const previousModels = (await getCustomModels(providerId)) as JsonRecord[]; - const candidateImportedModels = normalizeImportedModels(providerId, fetchedModels); + const previousSyncedAvailableModels = + previousSyncedAvailableModelsInput ?? + (await getSyncedAvailableModelsForConnection(providerId, connectionId)); + const discoveredModels = normalizeDiscoveredModels(fetchedModels); + const candidateImportedModels = normalizeImportedModels(fetchedModels); const importedIds = new Set(candidateImportedModels.map((model) => model.id)); + const discoveredIds = new Set(discoveredModels.map((model) => model.id)); const nextModelsMap = new Map(); + const removedCustomModels: JsonRecord[] = []; - if (mode === "merge") { - for (const model of previousModels) { - if (model?.id) nextModelsMap.set(String(model.id), model); + for (const model of previousModels) { + const modelId = getModelId(model); + if (!modelId) continue; + if (isImportedSource(model.source) || discoveredIds.has(modelId)) { + removedCustomModels.push(model); + continue; } - } else { - for (const model of previousModels) { - if (!model?.id) continue; - if (isManagedDiscoveredSource(model.source)) continue; - nextModelsMap.set(String(model.id), model); - } - } - - for (const model of candidateImportedModels) { - nextModelsMap.set(model.id, model); + nextModelsMap.set(modelId, model); } const persistedModels = (await replaceCustomModels( @@ -170,11 +224,12 @@ export async function importManagedModels({ outputTokenLimit?: number; description?: string; supportsThinking?: boolean; - }> + }>, + { allowEmpty: true } )) as JsonRecord[]; + preserveRemovedCustomModelCompat(providerId, removedCustomModels); - const discoveredModels = normalizeDiscoveredModels(fetchedModels); - let syncedAvailableModels: SyncedAvailableModel[] = []; + let syncedAvailableModels: SyncedAvailableModel[] = previousSyncedAvailableModels; if (discoveredModels.length > 0) { syncedAvailableModels = await replaceSyncedAvailableModelsForConnection( providerId, @@ -184,24 +239,28 @@ export async function importManagedModels({ } let syncedAliases = 0; - if (usesManagedAvailableModels(providerId)) { + if (usesManagedAvailableModels(providerId) && (mode === "merge" || discoveredModels.length > 0)) { const aliasSync = await syncManagedAvailableModelAliases( providerId, - mode === "sync" - ? persistedModels - .map((model) => toNonEmptyString(model.id)) - .filter((modelId): modelId is string => Boolean(modelId)) - : candidateImportedModels.map((model) => model.id), + discoveredModels.map((model) => model.id), { pruneMissing: mode === "sync" } ); syncedAliases = aliasSync.assignedAliases.length; } - const importedChanges = summarizeImportedChanges(previousModels, persistedModels, importedIds); - const importedModels = collectAddedImportedModels(previousModels, candidateImportedModels); + const importedChanges = summarizeImportedChanges( + previousSyncedAvailableModels as JsonRecord[], + discoveredModels as JsonRecord[], + importedIds + ); + const importedModels = collectAddedImportedModels( + previousSyncedAvailableModels as JsonRecord[], + candidateImportedModels + ); return { previousModels, + previousSyncedAvailableModels, persistedModels, importedModels, discoveredModels, diff --git a/src/lib/providerModels/modelDiscovery.ts b/src/lib/providerModels/modelDiscovery.ts index 0ed9dbcca5..89235b566a 100644 --- a/src/lib/providerModels/modelDiscovery.ts +++ b/src/lib/providerModels/modelDiscovery.ts @@ -48,7 +48,7 @@ export function normalizeDiscoveredModels(models: unknown): SyncedAvailableModel deduped.set(id, { id, name, - source: "api-sync", + source: "imported", ...(supportedEndpoints && supportedEndpoints.length > 0 ? { supportedEndpoints } : {}), ...(typeof record.inputTokenLimit === "number" ? { inputTokenLimit: record.inputTokenLimit } diff --git a/src/lib/providers/validation.ts b/src/lib/providers/validation.ts index dcff7864ec..48e646069d 100644 --- a/src/lib/providers/validation.ts +++ b/src/lib/providers/validation.ts @@ -24,7 +24,7 @@ import { safeOutboundFetch, } from "@/shared/network/safeOutboundFetch"; import { getProviderOutboundGuard } from "@/shared/network/outboundUrlGuard"; -import { normalizeSessionCookieHeader } from "@/lib/providers/webCookieAuth"; +import { extractCookieValue, normalizeSessionCookieHeader } from "@/lib/providers/webCookieAuth"; import { getGigachatAccessToken } from "@omniroute/open-sse/services/gigachatAuth.ts"; import { validateQoderCliPat } from "@omniroute/open-sse/services/qoderCli.ts"; import { @@ -2309,8 +2309,13 @@ function buildMetaAiValidationBody() { async function validateGrokWebProvider({ apiKey, providerSpecificData = {} }: any) { try { - let token = apiKey; - if (token.startsWith("sso=")) token = token.slice(4); + const token = extractCookieValue(apiKey, "sso"); + if (!token) { + return { + valid: false, + error: "Missing sso cookie — paste the value (or the full grok.com cookie line)", + }; + } // Generate the same Cloudflare-bypass headers the GrokWebExecutor uses. const randomHex = (n: number) => { @@ -2353,7 +2358,7 @@ async function validateGrokWebProvider({ apiKey, providerSpecificData = {} }: an ), body: JSON.stringify({ temporary: true, - modeId: "auto", + modeId: "fast", message: "test", fileAttachments: [], imageAttachments: [], @@ -2385,13 +2390,31 @@ async function validateGrokWebProvider({ apiKey, providerSpecificData = {} }: an errorDetail = (await response.text()).slice(0, 240); } catch {} - if (response.status === 401 || response.status === 403) { + if (response.status === 401) { return { valid: false, error: "Invalid SSO cookie — re-paste from grok.com DevTools → Cookies → sso", }; } + if (response.status === 403) { + // Grok uses 403 for auth failures, entitlement issues, geo blocks, and + // resource errors. Default-deny: only the auth-shaped 403 gets the + // re-paste hint; anything else surfaces the upstream body so the user + // (or maintainer, if upstream renames the probe model) sees the real + // cause instead of a misleading "valid" verdict. + if (/invalid-credentials|unauthenticated|unauthorized/i.test(errorDetail)) { + return { + valid: false, + error: "Invalid SSO cookie — re-paste from grok.com DevTools → Cookies → sso", + }; + } + return { + valid: false, + error: `Grok rejected validation (403)${errorDetail ? `: ${errorDetail.slice(0, 160)}` : ""}`, + }; + } + if (response.status === 429) { return { valid: false, error: "Grok rate limited during validation (429)" }; } @@ -2409,6 +2432,114 @@ async function validateGrokWebProvider({ apiKey, providerSpecificData = {} }: an } } +async function validateChatGptWebProvider({ apiKey, providerSpecificData = {} }: any) { + try { + // Accept bare value, unchunked cookie, chunked (.0/.1) cookies, or full + // "Cookie: ..." DevTools line. Pass through verbatim once recognised. + let cookieHeader = String(apiKey || "").trim(); + if (/^cookie\s*:\s*/i.test(cookieHeader)) { + cookieHeader = cookieHeader.replace(/^cookie\s*:\s*/i, ""); + } + if (!/__Secure-next-auth\.session-token(?:\.\d+)?\s*=/.test(cookieHeader)) { + cookieHeader = `__Secure-next-auth.session-token=${cookieHeader}`; + } + + // Use the TLS-impersonating client — Cloudflare on chatgpt.com pins + // cf_clearance to JA3/JA4 + HTTP/2 SETTINGS, so plain Node fetch always + // gets cf-mitigated: challenge regardless of cookies. + const { tlsFetchChatGpt, TlsClientUnavailableError } = + await import("@omniroute/open-sse/services/chatgptTlsClient.ts"); + + let response; + try { + response = await tlsFetchChatGpt("https://chatgpt.com/api/auth/session", { + method: "GET", + headers: applyCustomUserAgent( + { + Accept: "application/json", + "Accept-Language": "en-US,en;q=0.9", + "Cache-Control": "no-cache", + Cookie: cookieHeader, + Origin: "https://chatgpt.com", + Pragma: "no-cache", + Referer: "https://chatgpt.com/", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "User-Agent": + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:150.0) Gecko/20100101 Firefox/150.0", + }, + providerSpecificData + ), + timeoutMs: 30_000, + }); + } catch (err: any) { + if (err instanceof TlsClientUnavailableError) { + return { + valid: false, + error: `${err.message} (chatgpt-web requires this — without it, Cloudflare blocks every request)`, + }; + } + throw err; + } + + const contentType = response.headers.get("content-type") || ""; + const cfRay = response.headers.get("cf-ray"); + const cfMitigated = response.headers.get("cf-mitigated"); + + if (response.status === 401 || response.status === 403) { + const bodyText = response.text || ""; + if (cfMitigated || /just a moment|cloudflare|cf-chl|attention required/i.test(bodyText)) { + return { + valid: false, + error: + "Cloudflare blocked the validator — open chatgpt.com in your browser, then copy the FULL Cookie line from DevTools (Network → request → Cookie) including cf_clearance, __cf_bm, _cfuvid, and the session-token chunks.", + }; + } + return { + valid: false, + error: + "Invalid ChatGPT session cookie — re-paste __Secure-next-auth.session-token from chatgpt.com DevTools → Cookies", + }; + } + + if (response.status >= 500) { + return { valid: false, error: `ChatGPT unavailable (${response.status})` }; + } + + if (response.status >= 400) { + return { valid: false, error: `Validation failed: ${response.status}` }; + } + + if (!contentType.includes("json")) { + return { + valid: false, + error: `ChatGPT returned non-JSON (${contentType || "no content-type"}${cfRay ? `, cf-ray=${cfRay}` : ""}) — paste the FULL Cookie line including cf_clearance, __cf_bm, _cfuvid alongside the session-token chunks.`, + }; + } + + let data: any = {}; + try { + data = JSON.parse(response.text || "{}"); + } catch { + return { + valid: false, + error: + "ChatGPT session response was not JSON — paste the FULL Cookie line including cf_clearance and __cf_bm.", + }; + } + if (!data?.accessToken) { + return { + valid: false, + error: "ChatGPT session expired — log into chatgpt.com and copy a fresh cookie", + }; + } + return { valid: true, error: null }; + } catch (error: any) { + return toValidationErrorResult(error); + } +} + async function validatePerplexityWebProvider({ apiKey, providerSpecificData = {} }: any) { try { let sessionToken = apiKey; @@ -2491,7 +2622,7 @@ async function validatePerplexityWebProvider({ apiKey, providerSpecificData = {} async function validateBlackboxWebProvider({ apiKey, providerSpecificData = {} }: any) { try { - const cookieHeader = normalizeSessionCookieHeader(apiKey, "__Secure-authjs.session-token"); + const cookieHeader = normalizeSessionCookieHeader(apiKey, "next-auth.session-token"); const sessionHeaders = applyCustomUserAgent( { Accept: "application/json", @@ -2747,6 +2878,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi snowflake: validateSnowflakeProvider, gigachat: validateGigachatProvider, "grok-web": validateGrokWebProvider, + "chatgpt-web": validateChatGptWebProvider, "perplexity-web": validatePerplexityWebProvider, "blackbox-web": validateBlackboxWebProvider, "muse-spark-web": validateMuseSparkWebProvider, diff --git a/src/lib/providers/webCookieAuth.ts b/src/lib/providers/webCookieAuth.ts index de95eda53c..35a837de26 100644 --- a/src/lib/providers/webCookieAuth.ts +++ b/src/lib/providers/webCookieAuth.ts @@ -17,6 +17,29 @@ export function normalizeSessionCookieHeader(rawValue: string, defaultCookieName return `${defaultCookieName}=${normalized}`; } +/** + * Extract a single cookie's value from whatever the user pasted. Handles: + * - bare value: "eyJ0eXAi..." → "eyJ0eXAi..." + * - single pair: "sso=eyJ0eXAi..." → "eyJ0eXAi..." + * - full DevTools cookie blob: "foo=1; sso=eyJ...; bar=2" → "eyJ..." + * Returns "" if a blob is given that does not contain the named cookie. + */ +export function extractCookieValue(rawValue: string, cookieName: string): string { + const trimmed = stripCookieInputPrefix(rawValue); + if (!trimmed) return ""; + + if (trimmed.includes(";")) { + const escaped = cookieName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = trimmed.match(new RegExp("(?:^|;\\s*)" + escaped + "=([^;\\s]+)")); + return match ? match[1] : ""; + } + + const prefix = `${cookieName}=`; + if (trimmed.startsWith(prefix)) return trimmed.slice(prefix.length); + + return trimmed; +} + export function normalizeSessionCookieHeaders( rawValues: Array, defaultCookieName: string diff --git a/src/shared/components/ActiveRequestsPanel.tsx b/src/shared/components/ActiveRequestsPanel.tsx index 7de34f721d..988a4ff3d3 100644 --- a/src/shared/components/ActiveRequestsPanel.tsx +++ b/src/shared/components/ActiveRequestsPanel.tsx @@ -62,6 +62,19 @@ export default function ActiveRequestsPanel() { }; }, []); + const handleClearAll = async () => { + if (!window.confirm(t("confirmClearActiveRequests") || "Clear all active requests?")) return; + try { + const res = await fetch("/api/logs/active", { method: "DELETE" }); + if (res.ok) { + setRows([]); + setSelectedRow(null); + } + } catch (error) { + console.error("Failed to clear active requests:", error); + } + }; + if (!loading && rows.length === 0) { return null; } @@ -75,9 +88,19 @@ export default function ActiveRequestsPanel() {

{t("runningRequestsDesc")}

-
- - {loading ? t("loading") : t("activeCount", { count: rows.length })} +
+
+ + {loading ? t("loading") : t("activeCount", { count: rows.length })} +
+ {rows.length > 0 && ( + + )}
diff --git a/src/shared/components/KiroAuthModal.tsx b/src/shared/components/KiroAuthModal.tsx index 5a49d0d5bf..588771f40f 100644 --- a/src/shared/components/KiroAuthModal.tsx +++ b/src/shared/components/KiroAuthModal.tsx @@ -138,17 +138,19 @@ export default function KiroAuthModal({ - {/* AWS IAM Identity Center (IDC) - HIDDEN */} + {/* AWS IAM Identity Center (IDC) */}