Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5763609008 | |||
| 6d672ab09a | |||
| ac68022233 | |||
| c2b31f6b20 | |||
| 54b1d8c8de | |||
| cd1ab696b2 |
@@ -49,6 +49,9 @@ jobs:
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
no-cache: false
|
||||
env:
|
||||
DOCKER_BUILDKIT_INLINE_CACHE: 1
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
|
||||
@@ -107,6 +107,10 @@ jobs:
|
||||
"
|
||||
echo "✓ electron/package.json version set to $VERSION_NO_V"
|
||||
|
||||
- name: Install fpm (Linux .deb packaging tool)
|
||||
if: matrix.platform == 'linux'
|
||||
run: sudo gem install fpm --no-document
|
||||
|
||||
- name: Install Electron dependencies
|
||||
working-directory: electron
|
||||
run: npm install --no-audit --no-fund
|
||||
|
||||
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [2.2.4] — 2026-03-10
|
||||
|
||||
> ### 🔧 CI Fixes
|
||||
|
||||
### CI
|
||||
|
||||
- **docs-sync fix** — Updated `docs/openapi.yaml` version from `2.2.0` to `2.2.3` (was out of sync with `package.json`, causing CI lint failure)
|
||||
- **CHANGELOG format** — Added required `## [Unreleased]` section at top of `CHANGELOG.md` (required by `check:docs-sync` script)
|
||||
- **Electron Linux** — Added `gem install fpm` step to `electron-release.yml` Linux build job; `fpm` is required by `electron-builder` to package `.deb` installers but was not pre-installed on `ubuntu-latest` runners
|
||||
- **Docker publish** — Added `DOCKER_BUILDKIT_INLINE_CACHE` env; previous `502 error writing layer blob` was a transient Docker Hub network error
|
||||
|
||||
---
|
||||
|
||||
## [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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: OmniRoute API
|
||||
version: 2.2.0
|
||||
version: 2.2.3
|
||||
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,
|
||||
|
||||
@@ -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)}`;
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.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": {
|
||||
|
||||
Reference in New Issue
Block a user