Compare commits

..

13 Commits

Author SHA1 Message Date
diegosouzapw 70465ada4d feat(release): v1.8.1 — usage API proxy support
Build Electron Desktop App / Validate version (push) Failing after 35s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
2026-03-03 12:06:42 -03:00
Diego Rodrigues de Sa e Souza 8ddea153d3 Merge pull request #195 from diegosouzapw/fix/issue-194-usage-proxy
fix: route usage API quota fetches through configured proxy (#194)
2026-03-03 12:05:55 -03:00
diegosouzapw 8dca8fba6b fix: route usage API quota fetches through configured proxy (#194) 2026-03-03 12:04:59 -03:00
diegosouzapw f21ba7df64 feat(release): v1.8.0 — empty tool_use.name validation, Windows electron fix
Build Electron Desktop App / Validate version (push) Failing after 31s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
2026-03-03 11:21:31 -03:00
Diego Rodrigues de Sa e Souza ef917e42d1 Merge pull request #190 from benzntech/fix/electron-windows-collect-installers
fix: Windows electron release — collect portable exe by pattern
2026-03-03 11:19:53 -03:00
Diego Rodrigues de Sa e Souza 865a1b9b2c Merge pull request #193 from diegosouzapw/fix/issue-191-empty-tool-use-name
fix: validate empty tool_use.name to prevent Claude 400 errors (#191)
2026-03-03 11:19:45 -03:00
diegosouzapw de8a0836a8 fix: validate empty tool_use.name to prevent Claude 400 errors (#191) 2026-03-03 11:18:53 -03:00
benzntech b8272c55d7 fix: address review — break after first portable exe, remove debug ls 2026-03-03 09:27:31 +05:30
benzntech 8d93c13f9a fix: collect portable exe by pattern instead of hardcoded filename
electron-builder produces 'OmniRoute 1.6.9.exe' (with version) as the
portable exe, not 'OmniRoute.exe'. The hardcoded check failed, returning
exit code 1 and breaking every Windows build in the release workflow.

Now finds the portable exe by excluding 'Setup' (NSIS installer) and
blockmap files, then copies it as OmniRoute.exe for the release assets.
2026-03-03 09:20:24 +05:30
diegosouzapw 8152b030bf chore: bump version to 1.7.14 and update CHANGELOG
Build Electron Desktop App / Validate version (push) Failing after 37s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
2026-03-02 19:18:38 -03:00
diegosouzapw 9352ac767f Merge PR #188: fix passthrough stream for Responses SSE (#186) 2026-03-02 19:17:38 -03:00
diegosouzapw 5f20029ff7 fix: make passthrough stream format-aware for Responses SSE (#186)
Passthrough mode now detects Responses SSE payloads (parsed.type starts
with 'response.') and skips Chat Completions-specific sanitization:
- sanitizeStreamingChunk() only runs on Chat Completions payloads
- fixInvalidId() and hasValuableContent() checks skipped for Responses
- Usage extraction still runs for both formats
- Content length tracking adapted for Responses delta format

This prevents potential stream corruption when Responses SSE data
triggers idFixed or other Chat Completions-specific rewrite conditions.
2026-03-02 19:13:34 -03:00
diegosouzapw dbd00117c8 chore: bump to 1.7.13 (npm republish)
Build Electron Desktop App / Validate version (push) Failing after 31s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
2026-03-02 18:51:24 -03:00
8 changed files with 119 additions and 50 deletions
+6 -2
View File
@@ -110,9 +110,13 @@ jobs:
for file in *${{ matrix.ext }}; do
[ -f "$file" ] && cp "$file" ../../release-assets/
done
# Windows: also copy portable standalone exe
# Windows: also copy portable standalone exe as OmniRoute.exe
if [ "${{ matrix.platform }}" = "windows" ]; then
[ -f "OmniRoute.exe" ] && cp OmniRoute.exe ../../release-assets/
for file in *.exe; do
# Skip the NSIS installer (contains "Setup")
case "$file" in *Setup*) continue ;; esac
[ -f "$file" ] && cp "$file" "../../release-assets/OmniRoute.exe" && break
done
fi
- name: Upload artifacts
+23
View File
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
## [1.8.1] — 2026-03-03
### 🐛 Bug Fixes
- **Usage API Proxy Support** — Quota/usage fetch calls (`/api/usage/[connectionId]`) now route through the dashboard-configured proxy (Global → Provider → Key level). Previously, usage fetchers used bare `fetch()` which bypassed the Global Proxy setting, causing "fetch failed" errors in Docker deployments behind a proxy. Fixes #194
## [1.8.0] — 2026-03-03
### 🐛 Bug Fixes
- **Empty `tool_use.name` Validation** — Fixed intermittent HTTP 400 errors when using Claude Code through OmniRoute. Assistant messages with empty `tool_use.name` fields (from interrupted tool calls or malformed history) are now validated and filtered at two layers: the `openai-to-claude` request translator and the `prepareClaudeRequest` sanitizer. Fixes #191
- **Windows Electron Release** — Fixed the "Collect installers" step failing in every Windows build since v1.7.5+. `electron-builder` produces versioned portable exe filenames (e.g., `OmniRoute 1.6.9.exe`), not the hardcoded `OmniRoute.exe` the workflow expected. Now finds the portable exe dynamically by pattern. PR #190 by @benzntech
## [1.7.14] — 2026-03-02
### 🐛 Bug Fixes
- **Responses SSE Passthrough** — Passthrough mode is now format-aware: Responses SSE payloads (`response.*` type) skip Chat Completions-specific sanitization (`sanitizeStreamingChunk`, `fixInvalidId`, `hasValuableContent`), preventing potential stream corruption for Responses-native clients. Usage extraction still works for both formats. Fixes #186
### ✨ Features
- **Blackbox AI Dashboard** — Added blackbox.ai provider to the dashboard frontend (providers page, pricing, models endpoint). Completes #175
## [1.7.11] — 2026-03-02
### ✨ Features
@@ -126,6 +126,15 @@ export function prepareClaudeRequest(body, provider = null) {
}
}
// Pass 1.4: Filter out tool_use blocks with empty names (causes Claude 400 error)
for (const msg of filtered) {
if (msg.role === "assistant" && Array.isArray(msg.content)) {
msg.content = msg.content.filter(
(block) => block.type !== "tool_use" || (block.name && block.name.trim())
);
}
}
// Pass 1.5: Fix tool_use/tool_result ordering
// Each tool_use must have tool_result in the NEXT message (not same message with other content)
filtered = fixToolUseOrdering(filtered);
@@ -228,7 +228,10 @@ function getContentBlocksFromMessage(msg, toolNameMap = new Map()) {
});
} else if (part.type === "tool_use") {
// Tool name already has prefix from tool declarations, keep as-is
blocks.push({ type: "tool_use", id: part.id, name: part.name, input: part.input });
// CRITICAL: Skip tool_use blocks with empty name (causes Claude 400 error)
if (part.name && part.name.trim()) {
blocks.push({ type: "tool_use", id: part.id, name: part.name, input: part.input });
}
}
}
} else if (msg.content) {
@@ -241,8 +244,12 @@ function getContentBlocksFromMessage(msg, toolNameMap = new Map()) {
if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
for (const tc of msg.tool_calls) {
if (tc.type === "function") {
// CRITICAL: Skip tool_calls with empty function name (causes Claude 400 error)
const fnName = tc.function?.name;
if (!fnName || !fnName.trim()) continue;
// Apply prefix to tool name
const toolName = CLAUDE_OAUTH_TOOL_PREFIX + tc.function.name;
const toolName = CLAUDE_OAUTH_TOOL_PREFIX + fnName;
blocks.push({
type: "tool_use",
id: tc.id,
+60 -40
View File
@@ -136,51 +136,71 @@ export function createSSEStream(options: any = {}) {
try {
let parsed = JSON.parse(trimmed.slice(5).trim());
// Sanitize: strip non-standard fields for OpenAI SDK compatibility
parsed = sanitizeStreamingChunk(parsed);
// Detect Responses SSE payloads (have a `type` field like "response.created",
// "response.output_item.added", etc.) and skip Chat Completions-specific
// sanitization to avoid corrupting the stream for Responses-native clients.
const isResponsesSSE =
parsed.type &&
typeof parsed.type === "string" &&
parsed.type.startsWith("response.");
const idFixed = fixInvalidId(parsed);
if (!hasValuableContent(parsed, FORMATS.OPENAI)) {
continue;
}
const delta = parsed.choices?.[0]?.delta;
// Extract <think> tags from streaming content
if (delta?.content && typeof delta.content === "string") {
const { content, thinking } = extractThinkingFromContent(delta.content);
delta.content = content;
if (thinking && !delta.reasoning_content) {
delta.reasoning_content = thinking;
if (isResponsesSSE) {
// Responses SSE: only extract usage, forward payload as-is
const extracted = extractUsage(parsed);
if (extracted) {
usage = extracted;
}
}
// Track content length from Responses format
if (parsed.delta && typeof parsed.delta === "string") {
totalContentLength += parsed.delta.length;
}
} else {
// Chat Completions: full sanitization pipeline
parsed = sanitizeStreamingChunk(parsed);
const content = delta?.content || delta?.reasoning_content;
if (content && typeof content === "string") {
totalContentLength += content.length;
}
const idFixed = fixInvalidId(parsed);
const extracted = extractUsage(parsed);
if (extracted) {
usage = extracted;
}
if (!hasValuableContent(parsed, FORMATS.OPENAI)) {
continue;
}
const isFinishChunk = parsed.choices?.[0]?.finish_reason;
if (isFinishChunk && !hasValidUsage(parsed.usage)) {
const estimated = estimateUsage(body, totalContentLength, FORMATS.OPENAI);
parsed.usage = filterUsageForFormat(estimated, FORMATS.OPENAI);
output = `data: ${JSON.stringify(parsed)}\n`;
usage = estimated;
injectedUsage = true;
} else if (isFinishChunk && usage) {
const buffered = addBufferToUsage(usage);
parsed.usage = filterUsageForFormat(buffered, FORMATS.OPENAI);
output = `data: ${JSON.stringify(parsed)}\n`;
injectedUsage = true;
} else if (idFixed) {
output = `data: ${JSON.stringify(parsed)}\n`;
injectedUsage = true;
const delta = parsed.choices?.[0]?.delta;
// Extract <think> tags from streaming content
if (delta?.content && typeof delta.content === "string") {
const { content, thinking } = extractThinkingFromContent(delta.content);
delta.content = content;
if (thinking && !delta.reasoning_content) {
delta.reasoning_content = thinking;
}
}
const content = delta?.content || delta?.reasoning_content;
if (content && typeof content === "string") {
totalContentLength += content.length;
}
const extracted = extractUsage(parsed);
if (extracted) {
usage = extracted;
}
const isFinishChunk = parsed.choices?.[0]?.finish_reason;
if (isFinishChunk && !hasValidUsage(parsed.usage)) {
const estimated = estimateUsage(body, totalContentLength, FORMATS.OPENAI);
parsed.usage = filterUsageForFormat(estimated, FORMATS.OPENAI);
output = `data: ${JSON.stringify(parsed)}\n`;
usage = estimated;
injectedUsage = true;
} else if (isFinishChunk && usage) {
const buffered = addBufferToUsage(usage);
parsed.usage = filterUsageForFormat(buffered, FORMATS.OPENAI);
output = `data: ${JSON.stringify(parsed)}\n`;
injectedUsage = true;
} else if (idFixed) {
output = `data: ${JSON.stringify(parsed)}\n`;
injectedUsage = true;
}
}
} catch {}
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "1.7.12",
"version": "1.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "1.7.12",
"version": "1.8.0",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "1.7.12",
"version": "1.8.1",
"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": {
+9 -3
View File
@@ -1,8 +1,9 @@
import { getProviderConnectionById, updateProviderConnection } 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";
import { syncToCloud } from "@/lib/cloudSync";
import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts";
/**
* Sync to cloud if enabled
@@ -139,8 +140,13 @@ export async function GET(request: Request, { params }: { params: Promise<{ conn
);
}
// Fetch usage from provider API
const usage = await getUsageForProvider(connection);
// Resolve proxy for this connection (key → combo → provider → global → direct)
const proxyInfo = await resolveProxyForConnection(connectionId);
// Fetch usage from provider API, wrapped in proxy context
const usage = await runWithProxyContext(proxyInfo?.proxy || null, () =>
getUsageForProvider(connection)
);
return Response.json(usage);
} catch (error) {
console.error("[Usage API] Error fetching usage:", error);