From 2f2d6b853516f18b876b03ad6cedfd5158864d1b Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sat, 21 Mar 2026 17:37:51 -0300 Subject: [PATCH] =?UTF-8?q?chore(release):=20v2.9.4=20=E2=80=94=20bug=20fi?= =?UTF-8?q?xes=20(#491,=20#515,=20#517)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix(translator): preserve prompt_cache_key in Responses API translation (#517) - fix(combo): escape \n in tagContent for valid JSON injection (#515) - fix(usage): sync expired token status back to DB on live auth failure (#491) - chore: bump version to 2.9.4 in package.json + docs/openapi.yaml - docs: update CHANGELOG.md with v2.9.4 release notes --- CHANGELOG.md | 21 ++++++++++++ docs/openapi.yaml | 2 +- package-lock.json | 4 +-- package.json | 2 +- src/app/api/usage/[connectionId]/route.ts | 40 +++++++++++++++++++++-- 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e4221ac3..0ceeda55f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ --- +## [2.9.4] — 2026-03-21 + +> Sprint: Bug fixes — preserve Codex prompt cache key, fix tagContent JSON escaping, sync expired token status to DB. + +### 🐛 Bug Fixes + +- **fix(translator)**: Preserve `prompt_cache_key` in Responses API → Chat Completions translation (#517) + — The field is a cache-affinity signal used by Codex; stripping it was preventing prompt cache hits. + Fixed in `openai-responses.ts` and `responsesApiHelper.ts`. + +- **fix(combo)**: Escape `\n` in `tagContent` so injected JSON string is valid (#515) + — Template literal newlines (U+000A) are not allowed unescaped inside JSON string values. + Replaced with `\\n` literal sequences in `open-sse/services/combo.ts`. + +- **fix(usage)**: Sync expired token status back to DB on live auth failure (#491) + — When the Limits & Quotas live check returns 401/403, the connection `testStatus` is now updated + to `"expired"` in the database so the Providers page reflects the same degraded state. + Fixed in `src/app/api/usage/[connectionId]/route.ts`. + +--- + ## [2.9.3] — 2026-03-21 > Sprint: Add 5 new free AI providers — LongCat, Pollinations, Cloudflare AI, Scaleway, AI/ML API. diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 31ee7d0af5..ca737fa558 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: OmniRoute API - version: 2.9.3 + version: 2.9.4 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/package-lock.json b/package-lock.json index 04f712c24d..6584e5baea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "omniroute", - "version": "2.9.3", + "version": "2.9.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "omniroute", - "version": "2.9.3", + "version": "2.9.4", "hasInstallScript": true, "license": "MIT", "workspaces": [ diff --git a/package.json b/package.json index 3bdde1fc37..f8a354b830 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omniroute", - "version": "2.9.3", + "version": "2.9.4", "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": { diff --git a/src/app/api/usage/[connectionId]/route.ts b/src/app/api/usage/[connectionId]/route.ts index 749234a896..ecfec888ba 100644 --- a/src/app/api/usage/[connectionId]/route.ts +++ b/src/app/api/usage/[connectionId]/route.ts @@ -1,4 +1,8 @@ -import { getProviderConnectionById, updateProviderConnection, resolveProxyForConnection } from "@/lib/localDb"; +import { + getProviderConnectionById, + updateProviderConnection, + resolveProxyForConnection, +} from "@/lib/localDb"; import { getMachineId } from "@/shared/utils/machine"; import { getUsageForProvider } from "@omniroute/open-sse/services/usage.ts"; import { getExecutor } from "@omniroute/open-sse/executors/index.ts"; @@ -109,7 +113,10 @@ async function refreshAndUpdateCredentials(connection: any) { /** * GET /api/usage/[connectionId] - Get usage data for a specific connection */ -export async function GET(request: Request, { params }: { params: Promise<{ connectionId: string }> }) { +export async function GET( + request: Request, + { params }: { params: Promise<{ connectionId: string }> } +) { try { const { connectionId } = await params; @@ -155,7 +162,34 @@ export async function GET(request: Request, { params }: { params: Promise<{ conn // Populate quota cache for quota-aware account selection if (isRecord(usage?.quotas)) { - setQuotaCache(connectionId, connection.provider, usage.quotas); + setQuotaCache( + connectionId, + connection.provider as string, + usage.quotas as Record + ); + } + + // (#491) If the live usage check returned an auth error, sync the expired status + // back to the DB so the Providers page reflects the same degraded state as + // Limits & Quotas (which performs the live check). + const errorMessage = typeof usage?.message === "string" ? usage.message.toLowerCase() : ""; + const isAuthError = + errorMessage.includes("token expired") || + errorMessage.includes("access denied") || + errorMessage.includes("re-authenticate") || + errorMessage.includes("unauthorized"); + + if (isAuthError && connection.testStatus !== "expired") { + try { + await updateProviderConnection(connection.id as string, { + testStatus: "expired", + lastErrorType: "token_expired", + lastErrorAt: new Date().toISOString(), + }); + } catch (dbErr) { + // Non-critical: log but don't block the response + console.error("[Usage API] Failed to sync expired status to DB:", dbErr); + } } return Response.json(usage);