From 9ba65d3323c7ba2ca8bbc82186eceb09edc5fdc5 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Wed, 25 Mar 2026 13:08:44 -0300 Subject: [PATCH] =?UTF-8?q?fix(release):=20v3.0.6=20=E2=80=94=20proxy=20co?= =?UTF-8?q?ntext,=20playground=20selector,=20CI=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix: Limits usage fetch wraps BOTH token refresh and usage call inside proxy context (fixes SOCKS5 Codex accounts) - Fix: CI integration test v1/models gracefully handles empty models list - Fix: Settings proxy test button results now render with priority over health data - Feat: Playground account selector dropdown for testing specific connections - Merge: PR #623 LongCat API base URL path correction --- CHANGELOG.md | 18 ++++ docs/openapi.yaml | 2 +- package-lock.json | 4 +- package.json | 2 +- .../(dashboard)/dashboard/playground/page.tsx | 83 ++++++++++++++++++- .../components/ProxyRegistryManager.tsx | 14 ++-- src/app/api/usage/[connectionId]/route.ts | 63 +++++++++----- .../v1-contracts-behavior.test.mjs | 14 ++-- 8 files changed, 157 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7636b8e369..7ef1cc28bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ --- +## [3.0.6] — 2026-03-25 + +### 🐛 Bug Fixes + +- **Limits/Proxy:** Fixed Codex limit fetching for accounts behind SOCKS5 proxies — token refresh now runs inside proxy context +- **CI:** Fixed integration test `v1/models` assertion failure in CI environments without provider connections +- **Settings:** Proxy test button now shows success/failure results immediately (previously hidden behind health data) + +### ✨ New Features + +- **Playground:** Added Account selector dropdown — test specific connections individually when a provider has multiple accounts + +### 🔧 Maintenance + +- Merged PR #623 — LongCat API base URL path correction + +--- + ## [3.0.5] — 2026-03-25 ### ✨ New Features diff --git a/docs/openapi.yaml b/docs/openapi.yaml index ebc9a4b796..8ca6e0d91e 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: OmniRoute API - version: 3.0.5 + version: 3.0.6 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 99676f14df..21d3831931 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "omniroute", - "version": "3.0.5", + "version": "3.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "omniroute", - "version": "3.0.5", + "version": "3.0.6", "hasInstallScript": true, "license": "MIT", "workspaces": [ diff --git a/package.json b/package.json index f5c3a32195..5e457eb5c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omniroute", - "version": "3.0.5", + "version": "3.0.6", "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/(dashboard)/dashboard/playground/page.tsx b/src/app/(dashboard)/dashboard/playground/page.tsx index 61c7f272d0..eff6bbbfdd 100644 --- a/src/app/(dashboard)/dashboard/playground/page.tsx +++ b/src/app/(dashboard)/dashboard/playground/page.tsx @@ -20,6 +20,13 @@ interface ProviderOption { label: string; } +interface ConnectionOption { + id: string; + name: string; + provider: string; + authType: string; +} + const ENDPOINT_OPTIONS = [ { value: "chat", label: "Chat Completions" }, { value: "responses", label: "Responses" }, @@ -182,8 +189,10 @@ function ImageResultsInline({ data }: { data: any }) { export default function PlaygroundPage() { const [models, setModels] = useState([]); const [providers, setProviders] = useState([]); + const [connections, setConnections] = useState([]); const [selectedProvider, setSelectedProvider] = useState(""); const [selectedModel, setSelectedModel] = useState(""); + const [selectedConnection, setSelectedConnection] = useState(""); const [selectedEndpoint, setSelectedEndpoint] = useState("chat"); const [requestBody, setRequestBody] = useState(""); const [responseBody, setResponseBody] = useState(""); @@ -205,7 +214,38 @@ export default function PlaygroundPage() { const isImageEndpoint = selectedEndpoint === "images"; const supportsVision = isChatEndpoint && isVisionModel(selectedModel); - // Fetch models + // Load connections for a given provider (called imperatively) + const loadConnections = useCallback((provider: string) => { + if (!provider) { + setConnections([]); + setSelectedConnection(""); + return; + } + fetch("/api/providers/client") + .then((res) => res.json()) + .then((data) => { + const allConns: ConnectionOption[] = []; + for (const p of data?.providers || []) { + if (p.id !== provider) continue; + for (const conn of p.connections || []) { + allConns.push({ + id: conn.id, + name: conn.name || conn.id, + provider: p.id, + authType: conn.authType || "apiKey", + }); + } + } + setConnections(allConns); + setSelectedConnection(""); + }) + .catch(() => { + setConnections([]); + setSelectedConnection(""); + }); + }, []); + + // Fetch models and initialize first provider useEffect(() => { fetch("/v1/models") .then((res) => res.json()) @@ -222,10 +262,13 @@ export default function PlaygroundPage() { .sort() .map((p) => ({ value: p, label: p })); setProviders(providerOpts); - if (providerOpts.length > 0) setSelectedProvider(providerOpts[0].value); + if (providerOpts.length > 0) { + setSelectedProvider(providerOpts[0].value); + loadConnections(providerOpts[0].value); + } }) .catch(() => {}); - }, []); + }, [loadConnections]); const filteredModels = models .filter((m) => !selectedProvider || m.id.startsWith(selectedProvider + "/")) @@ -241,6 +284,8 @@ export default function PlaygroundPage() { const handleProviderChange = (newProvider: string) => { setSelectedProvider(newProvider); + setSelectedConnection(""); + loadConnections(newProvider); const providerModels = models .filter((m) => !newProvider || m.id.startsWith(newProvider + "/")) .map((m) => m.id); @@ -334,8 +379,13 @@ export default function PlaygroundPage() { } catch { /* ignore parse errors */ } + const fetchHeaders: Record = {}; + if (selectedConnection) { + fetchHeaders["X-OmniRoute-Connection"] = selectedConnection; + } res = await fetch(`/api${path}`, { method: "POST", + headers: fetchHeaders, body: form, signal: controller.signal, }); @@ -345,9 +395,13 @@ export default function PlaygroundPage() { if (supportsVision && uploadedImages.length > 0) { parsed = buildChatBodyWithImages(parsed, uploadedImages); } + const fetchHeaders: Record = { "Content-Type": "application/json" }; + if (selectedConnection) { + fetchHeaders["X-OmniRoute-Connection"] = selectedConnection; + } res = await fetch(`/api${path}`, { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: fetchHeaders, body: JSON.stringify(parsed), signal: controller.signal, }); @@ -473,6 +527,27 @@ export default function PlaygroundPage() { )} + {/* Account/Connection — shown when provider has multiple connections */} + {!isSearchEndpoint && connections.length > 1 && ( +
+ +