Compare commits

..

6 Commits

Author SHA1 Message Date
diegosouzapw ac68022233 feat(release): v2.2.3 — bug fixes from community PRs
Build Electron Desktop App / Validate version (push) Failing after 41s
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
fix(google-cli): remove fake projectId fallback causing permission/verification errors (#285)
- antigravity.ts, openai-to-gemini.ts, geminiHelper.ts
- Throws clear error instead of silently sending with random project IDs

fix(claude): extend empty tool name filter to all message roles (#288)
- Pass 1.4 now covers all roles, not just assistant
- Filters tool_result with missing tool_use_id
- Filters top-level body.tools with empty names
- Explicit @swc/helpers COPY in Dockerfile runner-base stage
2026-03-10 12:53:47 -03:00
Nina Gleichner c2b31f6b20 Fix empty tool name 400 errors from Claude API and missing @swc/helpers in Docker (#288)
* Initial plan

* fix: filter empty tool names and missing tool_use_id; add @swc/helpers to Docker

Co-authored-by: ngleichner1 <263653359+ngleichner1@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ngleichner1 <263653359+ngleichner1@users.noreply.github.com>
2026-03-10 12:46:17 -03:00
Jack 54b1d8c8de fix(google-cli): stop random project fallback and require real OAuth projectId (#285)
Co-authored-by: jack <jack@plutus-32g.local>
2026-03-10 12:46:15 -03:00
diegosouzapw cd1ab696b2 docs: add npm run system-info to Support and troubleshooting sections
Users are now directed to run 'npm run system-info' when reporting bugs.
Added to:
- ## Support → '🐛 Reporting a Bug?' subsection
- Pain point #10 '🐛 I can't diagnose errors' bullet list
2026-03-10 12:45:20 -03:00
diegosouzapw d9d0640f6e feat(release): v2.2.2 — system-info script, Kimi/Mistral/Llama aliases
Build Electron Desktop App / Validate version (push) Failing after 28s
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
- New npm run system-info command (#280)
- Kimi K2/K2.5 Fireworks path aliases (#265)
- Mistral/Llama short aliases (#278)
- Custom alias instructions via Settings → Model Aliases
2026-03-10 11:00:20 -03:00
Diego Rodrigues de Sa e Souza e19046116a feat: system-info.mjs script + Kimi/Mistral/Llama model aliases (#280 #265 #278) (#284)
System Info Script (#280):
- New scripts/system-info.mjs: collects Node.js version, OmniRoute version,
  OS info, Agent CLI tool versions (iflow, gemini, claude, codex, antigravity,
  droid, openclaw, kilo, cursor, aider), Docker/PM2 status, system packages
- Outputs system-info.txt for easy attachment to bug reports
- Added npm script: 'npm run system-info'

Model Aliases (#265 #278):
- Added Kimi K2/K2.5 Fireworks path aliases to built-in alias map
  (fireworks/accounts/fireworks/models/kimi-k2p5 → moonshotai/Kimi-K2.5)
- Added short aliases: kimi-k2p5, kimi-k2, mistral-large, mistral-small,
  codestral, llama-3.3, llama-3-70b, llama-3-8b
- Custom aliases can be added via Settings → Model Aliases tab in dashboard

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-03-10 10:59:27 -03:00
12 changed files with 264 additions and 41 deletions
+29
View File
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
## [2.2.3] — 2026-03-10
> ### 🐛 Bug Fixes · 🔧 Reliability
### Bug Fixes
- **Antigravity/Gemini CLI: remove fake projectId fallback (#285)** — OmniRoute was generating random fallback project IDs (e.g. `useful-fuze-a04c5`) when OAuth credentials lacked a real GCP `projectId`. This caused confusing `Permission denied on resource project` and `Verify your account` errors from Google. Now throws a clear actionable error: _reconnect OAuth so OmniRoute can load your real Cloud Code project_. Affects `antigravity.ts`, `openai-to-gemini.ts`, `geminiHelper.ts`.
- **Claude Code: filter empty-named tool_use blocks across all message roles (#288)** — Pass 1.4 only filtered empty tool names from `assistant` messages. Extended to all roles (user, system). Also filters `tool_result` blocks missing `tool_use_id`, and top-level `body.tools` declarations with empty names. Prevents `Invalid input[x].name: empty string` 400 errors from Claude API.
- **Docker: explicit @swc/helpers copy (#288)** — Added `COPY --from=builder /app/node_modules/@swc/helpers` to Dockerfile `runner-base` stage. The standalone tracer doesn't always include this package, causing runtime `MODULE_NOT_FOUND` crashloops.
---
## [2.2.2] — 2026-03-10
> ### ✨ New Features · 🔀 Model Aliases
### New Features
- **system-info.mjs (#280)** — New `npm run system-info` command that collects Node.js version, OmniRoute version, OS info, CLI tool versions (iflow, gemini, claude, codex, antigravity, droid, openclaw, kilo, cursor, aider), Docker/PM2 status, and system packages. Outputs `system-info.txt` for easy attachment to bug reports.
### Model Aliases
- **Kimi K2/K2.5 Fireworks aliases (#265)** — Built-in aliases added: `fireworks/accounts/fireworks/models/kimi-k2p5` and `kimi-k2p5``moonshotai/Kimi-K2.5`; same for `kimi-k2``moonshotai/Kimi-K2`. Fireworks long path model names now auto-resolve.
- **Mistral short aliases (#278)** — `mistral-large``mistral-large-latest`, `mistral-small``mistral-small-latest`, `codestral``codestral-latest`.
- **Llama short aliases** — `llama-3.3``llama-3.3-70b-versatile`, `llama-3-70b``llama-3.3-70b-versatile`, `llama-3-8b``llama3-8b-8192`.
- **Custom aliases** — Users can define their own aliases in **Settings → Model Aliases** tab. Example: `gpt-5.4``cx/gpt-5.4`.
---
## [2.2.1] — 2026-03-10
> ### 🐛 Bug Fixes · 🔐 Security · 🔧 CI
+2
View File
@@ -29,6 +29,8 @@ RUN mkdir -p /app/data
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/.next/standalone ./
# Explicitly copy @swc/helpers — not always traced by standalone output but needed at runtime
COPY --from=builder /app/node_modules/@swc/helpers ./node_modules/@swc/helpers
COPY --from=builder /app/scripts/run-standalone.mjs ./run-standalone.mjs
COPY --from=builder /app/scripts/runtime-env.mjs ./runtime-env.mjs
COPY --from=builder /app/scripts/healthcheck.mjs ./healthcheck.mjs
+11
View File
@@ -167,6 +167,16 @@ _Connect any AI-powered IDE or CLI tool through OmniRoute — free API gateway f
- **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md), open a PR, or pick a `good first issue`
- **Original Project**: [9router by decolua](https://github.com/decolua/9router)
### 🐛 Reporting a Bug?
When opening an issue, please run the system-info command and attach the generated file:
```bash
npm run system-info
```
This generates a `system-info.txt` with your Node.js version, OmniRoute version, OS details, installed CLI tools (iflow, gemini, claude, codex, antigravity, droid, etc.), Docker/PM2 status, and system packages — everything we need to reproduce your issue quickly. Attach the file directly to your GitHub issue.
---
## 🔄 How It Works
@@ -358,6 +368,7 @@ When a call fails, the dev doesn't know if it was a rate limit, expired token, w
- **Translator Playground** — 4 debugging modes: Playground (format translation), Chat Tester (round-trip), Test Bench (batch), Live Monitor (real-time)
- **Request Telemetry** — p50/p95/p99 latency + X-Request-Id tracing
- **File-Based Logging with Rotation** — Console interceptor captures everything to JSON log with size-based rotation
- **System Info Report**`npm run system-info` generates `system-info.txt` with your full environment (Node version, OmniRoute version, OS, CLI tools, Docker/PM2 status). Attach it when reporting issues for instant triage.
</details>
+4 -13
View File
@@ -38,14 +38,11 @@ export class AntigravityExecutor extends BaseExecutor {
transformRequest(model, body, stream, credentials) {
const bodyProjectId = body?.project;
const credentialsProjectId = credentials?.projectId;
const hasExplicitProject = !!(bodyProjectId || credentialsProjectId);
const projectId = bodyProjectId || credentialsProjectId || this.generateProjectId();
const projectId = bodyProjectId || credentialsProjectId;
if (!hasExplicitProject) {
console.warn(
`[Antigravity] ⚠️ No projectId provided via body or credentials — using generated fallback "${projectId}". ` +
`This may cause 404 errors if the account has no active GCP project. ` +
`Ensure the OAuth token includes a valid project or the request includes a project field.`
if (!projectId) {
throw new Error(
"Missing Google projectId for Antigravity account. Please reconnect OAuth so OmniRoute can fetch your real Cloud Code project (loadCodeAssist)."
);
}
@@ -128,12 +125,6 @@ export class AntigravityExecutor extends BaseExecutor {
}
}
generateProjectId() {
const adj = ["useful", "bright", "swift", "calm", "bold"][Math.floor(Math.random() * 5)];
const noun = ["fuze", "wave", "spark", "flow", "core"][Math.floor(Math.random() * 5)];
return `${adj}-${noun}-${crypto.randomUUID().slice(0, 5)}`;
}
generateSessionId() {
return `-${Math.floor(Math.random() * 9_000_000_000_000_000_000)}`;
}
+18
View File
@@ -31,6 +31,24 @@ const BUILT_IN_ALIASES: Record<string, string> = {
"gpt-4-0125-preview": "gpt-4-turbo",
"gpt-4-1106-preview": "gpt-4-turbo",
"gpt-3.5-turbo-0125": "gpt-3.5-turbo",
// Kimi/Moonshot — Fireworks long-path aliases (#265)
"accounts/fireworks/models/kimi-k2p5": "moonshotai/Kimi-K2.5",
"fireworks/accounts/fireworks/models/kimi-k2p5": "moonshotai/Kimi-K2.5",
"kimi-k2p5": "moonshotai/Kimi-K2.5",
"accounts/fireworks/models/kimi-k2": "moonshotai/Kimi-K2",
"fireworks/accounts/fireworks/models/kimi-k2": "moonshotai/Kimi-K2",
"kimi-k2": "moonshotai/Kimi-K2",
// Mistral short aliases
"mistral-large": "mistral-large-latest",
"mistral-small": "mistral-small-latest",
codestral: "codestral-latest",
// Llama short aliases
"llama-3.3": "llama-3.3-70b-versatile",
"llama-3-70b": "llama-3.3-70b-versatile",
"llama-3-8b": "llama3-8b-8192",
};
// ── Custom Aliases (persisted via Settings API) ─────────────────────────────
+12 -2
View File
@@ -127,14 +127,24 @@ export function prepareClaudeRequest(body, provider = null) {
}
// Pass 1.4: Filter out tool_use blocks with empty names (causes Claude 400 error)
// Apply to ALL roles (assistant tool_use + any user messages that may carry tool_use)
// Also filter tool_result blocks with missing tool_use_id
for (const msg of filtered) {
if (msg.role === "assistant" && Array.isArray(msg.content)) {
if (Array.isArray(msg.content)) {
msg.content = msg.content.filter(
(block) => block.type !== "tool_use" || (block.name && block.name.trim())
(block) => block.type !== "tool_use" || (block.name && block.name?.trim())
);
msg.content = msg.content.filter(
(block) => block.type !== "tool_result" || block.tool_use_id
);
}
}
// Also filter top-level tool declarations with empty names
if (body.tools && Array.isArray(body.tools)) {
body.tools = body.tools.filter((tool) => tool.name && tool.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);
@@ -126,15 +126,6 @@ export function generateSessionId() {
return `-${Math.floor(Math.random() * 9000000000000000000)}`;
}
// Generate project ID
export function generateProjectId() {
const adjectives = ["useful", "bright", "swift", "calm", "bold"];
const nouns = ["fuze", "wave", "spark", "flow", "core"];
const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
const noun = nouns[Math.floor(Math.random() * nouns.length)];
return `${adj}-${noun}-${crypto.randomUUID().slice(0, 5)}`;
}
// Helper: Remove unsupported keywords recursively from object/array
function removeUnsupportedKeywords(obj, keywords) {
if (!obj || typeof obj !== "object") return;
@@ -175,6 +175,9 @@ export function openaiToClaudeRequest(model, body, stream) {
};
});
// Filter out tools with empty names (would cause Claude 400 error)
result.tools = result.tools.filter((tool) => tool.name && tool.name?.trim());
// Add cache_control to last tool that doesn't have defer_loading
// Tools with defer_loading=true cannot have cache_control (API rejects it)
for (let i = result.tools.length - 1; i >= 0; i--) {
@@ -227,6 +230,8 @@ function getContentBlocksFromMessage(msg, toolNameMap = new Map(), disableToolPr
if (part.type === "text" && part.text) {
blocks.push({ type: "text", text: part.text });
} else if (part.type === "tool_result") {
// Skip tool_result with no tool_use_id (would be useless and may cause errors)
if (!part.tool_use_id) continue;
blocks.push({
type: "tool_result",
tool_use_id: part.tool_use_id,
@@ -15,7 +15,6 @@ import {
tryParseJSON,
generateRequestId,
generateSessionId,
generateProjectId,
cleanJSONSchemaForAntigravity,
} from "../helpers/geminiHelper.ts";
@@ -321,13 +320,11 @@ export function openaiToGeminiCLIRequest(model, body, stream) {
// Wrap Gemini CLI format in Cloud Code wrapper
function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigravity = false) {
const hasRealProject = !!credentials?.projectId;
const projectId = credentials?.projectId || generateProjectId();
const projectId = credentials?.projectId;
if (!hasRealProject) {
console.warn(
`[${isAntigravity ? "Antigravity" : "GeminiCLI"}] ⚠️ No projectId in credentials — using generated fallback "${projectId}". ` +
`This may cause 404 errors. Ensure the OAuth token includes a valid GCP project.`
if (!projectId) {
throw new Error(
`${isAntigravity ? "Antigravity" : "GeminiCLI"} account is missing projectId. Reconnect OAuth to load your real Cloud Code project before sending requests.`
);
}
@@ -374,13 +371,11 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
}
function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = null) {
const hasRealProject = !!credentials?.projectId;
const projectId = credentials?.projectId || generateProjectId();
const projectId = credentials?.projectId;
if (!hasRealProject) {
console.warn(
`[Antigravity/Claude] ⚠️ No projectId in credentials — using generated fallback "${projectId}". ` +
`This may cause 404 errors. Ensure the OAuth token includes a valid GCP project.`
if (!projectId) {
throw new Error(
"Antigravity/Claude account is missing projectId. Reconnect OAuth to load your real Cloud Code project before sending requests."
);
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "2.2.1",
"version": "2.2.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "2.2.1",
"version": "2.2.3",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "2.2.1",
"version": "2.2.3",
"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": {
@@ -76,7 +76,8 @@
"check": "npm run lint && npm run test",
"prepublishOnly": "npm run build:cli",
"postinstall": "node scripts/postinstall.mjs",
"prepare": "husky"
"prepare": "husky",
"system-info": "node scripts/system-info.mjs"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.27.1",
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env node
/**
* system-info.mjs OmniRoute System Information Reporter (#280)
*
* Collects system/environment info for bug reports.
* Usage: node scripts/system-info.mjs [--output system-info.txt]
*
* Output includes:
* - Node.js version
* - OmniRoute version
* - OS info
* - Relevant system packages (if apt available)
* - Agent CLI tools (iflow, gemini, claude, codex, antigravity, droid, etc.)
* - Docker / PM2 status
*/
import { execSync } from "child_process";
import { readFileSync, writeFileSync, existsSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import os from "os";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, "..");
// ── Helpers ────────────────────────────────────────────────────────────────
function run(cmd, fallback = "N/A") {
try {
return execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
} catch {
return fallback;
}
}
function toolVersion(cmd, args = "--version") {
const version = run(`${cmd} ${args}`, null);
if (version === null) return "not installed";
// Trim to first line, remove prefixes like "v", "Version: "
return version
.split("\n")[0]
.replace(/^(version\s*:?\s*|v)/i, "")
.trim();
}
function section(title) {
const line = "─".repeat(60);
return `\n${line}\n ${title}\n${line}\n`;
}
// ── Collect Info ──────────────────────────────────────────────────────────
const lines = [];
lines.push("OmniRoute System Information Report");
lines.push(`Generated: ${new Date().toISOString()}`);
// ── Node.js & Runtime ────────────────────────────────────────────────────
lines.push(section("Node.js & Runtime"));
lines.push(`Node.js: ${process.version}`);
lines.push(`npm: v${run("npm --version")}`);
lines.push(`Platform: ${process.platform} (${process.arch})`);
lines.push(`OS: ${os.type()} ${os.release()} (${os.arch()})`);
lines.push(`Hostname: ${os.hostname()}`);
lines.push(`CPUs: ${os.cpus().length}x ${os.cpus()[0]?.model || "unknown"}`);
lines.push(`Total RAM: ${Math.round(os.totalmem() / 1024 / 1024)} MB`);
lines.push(`Free RAM: ${Math.round(os.freemem() / 1024 / 1024)} MB`);
// ── OmniRoute Version ────────────────────────────────────────────────────
lines.push(section("OmniRoute"));
try {
const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8"));
lines.push(`Version: ${pkg.version}`);
lines.push(`Name: ${pkg.name}`);
} catch {
lines.push("Version: unable to read package.json");
}
const installedGlobal = run("npm list -g omniroute --depth=0 2>/dev/null | grep omniroute");
lines.push(`Global npm: ${installedGlobal || "not installed globally"}`);
const pm2Status = run("pm2 list 2>/dev/null | grep omniroute | awk '{print $4, $10, $12}'");
lines.push(`PM2 status: ${pm2Status || "not running via PM2"}`);
// ── Agent CLI Tools ──────────────────────────────────────────────────────
lines.push(section("Agent CLI Tools"));
const cliTools = [
{ name: "iflow-cli", cmd: "iflow", args: "--version" },
{ name: "gemini-cli", cmd: "gemini", args: "--version" },
{ name: "claude-code", cmd: "claude", args: "--version" },
{ name: "openai-codex", cmd: "codex", args: "--version" },
{ name: "antigravity", cmd: "antigravity", args: "--version" },
{ name: "droid", cmd: "droid", args: "--version" },
{ name: "openclaw", cmd: "openclaw", args: "--version" },
{ name: "kilo", cmd: "kilo", args: "--version" },
{ name: "cursor", cmd: "cursor", args: "--version" },
{ name: "aider", cmd: "aider", args: "--version" },
];
for (const { name, cmd, args } of cliTools) {
const v = toolVersion(cmd, args);
lines.push(`${name.padEnd(20)} ${v}`);
}
// ── Docker ───────────────────────────────────────────────────────────────
lines.push(section("Docker"));
lines.push(`Docker: ${run("docker --version", "not installed")}`);
const dockerContainers = run(
"docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' 2>/dev/null",
"N/A"
);
lines.push(`Containers:\n${dockerContainers}`);
// ── System Packages ──────────────────────────────────────────────────────
lines.push(section("System Packages (relevant)"));
const relevantPkgs = ["build-essential", "libssl-dev", "openssl", "libsqlite3-dev", "python3"];
for (const pkg of relevantPkgs) {
const ver = run(`dpkg -l ${pkg} 2>/dev/null | grep '^ii' | awk '{print $3}'`, "not found");
lines.push(`${pkg.padEnd(24)} ${ver}`);
}
// ── Environment Variables (safe subset) ─────────────────────────────────
lines.push(section("Environment Variables (non-sensitive)"));
const safeEnvKeys = [
"NODE_ENV",
"PORT",
"DATA_DIR",
"DB_BACKUPS_DIR",
"LOG_LEVEL",
"NEXT_PUBLIC_APP_URL",
"ROUTER_API_KEY_HINT",
];
for (const key of safeEnvKeys) {
const val = process.env[key];
if (val !== undefined) {
// Mask if looks like a secret
const masked = val.length > 8 ? val.substring(0, 4) + "****" : "****";
lines.push(`${key.padEnd(28)} ${masked}`);
}
}
// ── Output ───────────────────────────────────────────────────────────────
const report = lines.join("\n") + "\n";
// Write to file
const outArg = process.argv.find((a) => a.startsWith("--output="));
const outFile = outArg
? outArg.replace("--output=", "")
: process.argv[process.argv.indexOf("--output") + 1] || "system-info.txt";
const outPath = join(ROOT, outFile);
writeFileSync(outPath, report);
console.log(report);
console.log(`\n✅ Report saved to: ${outPath}`);
console.log(
`📎 Attach this file when reporting issues at: https://github.com/diegosouzapw/OmniRoute/issues`
);