Compare commits

...

5 Commits

Author SHA1 Message Date
diegosouzapw
c5213d4415 chore(workflow): update generate-release with Phase 2 validation step 2026-04-28 09:58:44 -03:00
diegosouzapw
d2da66c339 chore(release): bump to v3.7.3 — changelog, docs, version sync 2026-04-28 08:56:16 -03:00
diegosouzapw
11a0bcf65e docs: update CHANGELOG.md with merged PR entries for v3.7.3 2026-04-28 08:36:05 -03:00
diegosouzapw
b0979d78aa Merge branch 'release/v3.7.3' into backryun/main
# Conflicts:
#	tests/integration/chatcore-compression-integration.test.ts
#	tests/integration/resilience-http-e2e.test.ts
2026-04-28 08:32:32 -03:00
backryun
76c04a8783 Update image-model list 2026-04-28 20:31:56 +09:00
17 changed files with 227 additions and 192 deletions

View File

@@ -213,11 +213,43 @@ Inform the user:
---
## Phase 2: Post-Merge (only after user confirms)
## Phase 2: Post-Merge Validation (Local VPS)
> Run these steps only AFTER the user has merged the PR.
> Run these steps only AFTER the user has merged the PR into `main` and all CI jobs have passed.
### 11. Create Git Tag and GitHub Release (MANDATORY)
### 11. Deploy to Local VPS for Final Validation (MANDATORY)
Before cutting the official git tag and publishing to the world, deploy the `main` branch to the Local VPS for a final homologation test.
```bash
git checkout main
git pull origin main
# Build and pack locally
cd /home/diegosouzapw/dev/proxys/9router && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
# Deploy to LOCAL VPS (192.168.0.15)
scp omniroute-*.tgz root@192.168.0.15:/tmp/
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
# Verify
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
```
### 12. 🛑 STOP — Notify User & Await Final OK
**This is a mandatory stop point.**
Inform the user that the `main` branch is now running on the Local VPS.
Wait for the user to manually test and give the **OK**.
**DO NOT proceed to Phase 3 until the user confirms the local deploy is stable.**
---
## Phase 3: Official Launch
> Run these steps only AFTER the user gives the final OK from the Phase 2 local validation.
### 13. Create Git Tag and GitHub Release (MANDATORY)
// turbo
@@ -239,7 +271,7 @@ gh release create "v$VERSION" --title "v$VERSION" --notes "$NOTES" --target main
> **CRITICAL**: Docker Hub and npm MUST always publish the same version.
> The Docker image is built automatically via GitHub Actions when a new tag is pushed.
> After pushing the tag in step 11-12, **verify the workflow runs**:
> After pushing the tag in step 13, **verify the workflow runs**:
```bash
# Verify the Docker workflow triggered
@@ -247,40 +279,30 @@ gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit
# Wait for the Docker build to complete (usually 510 min)
gh run watch --repo diegosouzapw/OmniRoute
# After completion, verify on Docker Hub:
# https://hub.docker.com/r/diegosouzapw/omniroute/tags
```
If the Docker build was not triggered automatically, trigger it manually:
### 15. Publish to NPM (Optional/Automated)
Normally handled by CI, but if manual publish is required:
```bash
gh workflow run docker-publish.yml --repo diegosouzapw/OmniRoute --ref v2.x.y
npm publish
```
### 15. Deploy to BOTH VPS environments (MANDATORY)
### 16. Deploy to AKAMAI VPS (Production)
> Always deploy to **both** environments after every release.
> See `/deploy-vps` workflow for detailed steps.
Now that the release is officially cut, deploy it to the Akamai VPS.
```bash
# Build and pack locally
cd /home/diegosouzapw/dev/proxys/9router && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
# Deploy to LOCAL VPS (192.168.0.15)
scp omniroute-*.tgz root@192.168.0.15:/tmp/
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
# Deploy to AKAMAI VPS (69.164.221.35)
scp omniroute-*.tgz root@69.164.221.35:/tmp/
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
# Verify both
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
# Verify
curl -s -o /dev/null -w "AKAMAI: HTTP %{http_code}\n" http://69.164.221.35:20128/
```
### 16. Preserve release branch
### 17. Preserve release branch
```bash
# Branch is kept for historical purposes. Do not delete.

View File

@@ -10,9 +10,17 @@
- **fix(claude):** strip existing billing headers from system array before injecting to prevent Anthropic prompt cache misses — stacked `x-anthropic-billing-header` blocks invalidated prefix matching, causing ~100% cache_create instead of cache_read (#1712)
- **fix(claude):** strip `output_config.format` for non-Anthropic Claude-compatible providers during passthrough — third-party Claude endpoints (MiniMax, DeepSeek via aggregators) reject structured output fields with 400 errors (#1719)
- **fix(combo):** set terminal error state on response quality validation failure — prevents misleading `ALL_ACCOUNTS_INACTIVE` 503 when the real issue is response quality validation (#1707)
- **fix(combo):** set terminal error state on response quality validation failure — prevents misleading `ALL_ACCOUNTS_INACTIVE` 503 when the real issue is response quality validation (#1707, #1710)
- **fix(combo):** treat combo fallback as target-level orchestration — all non-ok responses (including generic 400s) now fall through to the next target instead of being terminal; removes complex bad-request allowlist regex (#1713)
- **fix(codex):** restore namespace MCP tools and hosted-tool whitelist — regression from #1581 that silently dropped all MCP tool groups and Responses-API hosted tools (#1715)
- **fix(codex):** add neutral instructions for bare chat requests — Codex Responses backend rejects requests without `instructions`, making Codex unusable for normal chat (#1709)
- **fix(proxy):** wrap proxy assignment queries in try-catch for missing `proxy_assignments` table — Electron installs where migration 004 hasn't run no longer crash with `no such table` error (#1706)
- **fix(migration):** improve Windows file URL path resolution in migration runner — adds direct URL path extraction and `process.cwd()` fallback for CI-built bundles with leaked build-time paths (#1704)
- **fix(ui):** fix light mode active request payload modal — add missing `--color-card` theme token, use opaque `bg-surface` instead of translucent `bg-card/70`, add backdrop blur (#1714)
### 🔄 Updates
- **chore(image-models):** refresh image generation model registry — replace stale FLUX aliases with FLUX Kontext / FLUX.2 mappings, remove deprecated FLUX Redux/Depth/Canny variants (#1722)
---

View File

@@ -1,6 +1,6 @@
{
"name": "omniroute-desktop",
"version": "3.7.2",
"version": "3.7.3",
"description": "OmniRoute Desktop Application",
"main": "main.js",
"author": {

View File

@@ -8,7 +8,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
**Key value:** One endpoint (`http://localhost:20128/v1`), unlimited models, zero downtime, minimal cost.
**Current version:** 3.7.2
**Current version:** 3.7.3
## Tech Stack
@@ -279,7 +279,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.7.2)
## Key Features (v3.7.3)
### Core Proxy
- **160+ AI providers** with automatic format translation

View File

@@ -37,57 +37,60 @@ interface ImageModelAliasEntry {
const IMAGE_MODEL_ALIASES: Record<string, ImageModelAliasEntry> = {
"flux-kontext": {
provider: "pollinations",
model: "flux-kontext",
name: "FLUX Kontext",
provider: "black-forest-labs",
model: "flux-kontext-pro",
name: "FLUX Kontext Pro",
listInCatalog: true,
inputModalities: ["text", "image"],
},
"flux-kontext-max": {
provider: "together",
model: "black-forest-labs/FLUX.1.1-pro",
provider: "black-forest-labs",
model: "flux-kontext-max",
name: "FLUX Kontext Max",
listInCatalog: true,
inputModalities: ["text", "image"],
},
"flux-redux": {
provider: "together",
model: "black-forest-labs/FLUX.1-redux",
name: "FLUX Redux",
"flux-2-max": {
provider: "black-forest-labs",
model: "flux-2-max",
name: "FLUX.2 Max",
listInCatalog: true,
inputModalities: ["text", "image"],
},
"flux-depth": {
provider: "together",
model: "black-forest-labs/FLUX.1-depth",
name: "FLUX Depth",
"flux-2-pro": {
provider: "black-forest-labs",
model: "flux-2-pro",
name: "FLUX.2 Pro",
listInCatalog: true,
inputModalities: ["text", "image"],
},
"flux-canny": {
provider: "together",
model: "black-forest-labs/FLUX.1-canny",
name: "FLUX Canny",
"flux-2-flex": {
provider: "black-forest-labs",
model: "flux-2-flex",
name: "FLUX.2 Flex",
listInCatalog: true,
inputModalities: ["text", "image"],
},
"flux-dev-lora": {
"flux-2-dev": {
provider: "together",
model: "black-forest-labs/FLUX.1-dev-lora",
name: "FLUX Dev LoRA",
model: "black-forest-labs/FLUX.2-dev",
name: "FLUX.2 Dev",
listInCatalog: true,
inputModalities: ["text", "image"],
},
kontext: {
provider: "pollinations",
model: "flux-kontext",
name: "FLUX Kontext",
provider: "black-forest-labs",
model: "flux-kontext-pro",
name: "FLUX Kontext Pro",
listInCatalog: false,
inputModalities: ["text", "image"],
},
"pollinations/kontext": {
provider: "pollinations",
model: "flux-kontext",
name: "FLUX Kontext",
provider: "black-forest-labs",
model: "flux-kontext-pro",
name: "FLUX Kontext Pro",
listInCatalog: false,
inputModalities: ["text", "image"],
},
};
@@ -110,11 +113,11 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authHeader: "bearer",
format: "openai", // native OpenAI format
models: [
{ id: "gpt-image-1", name: "GPT Image 1" },
{ id: "dall-e-3", name: "DALL-E 3" },
{ id: "dall-e-2", name: "DALL-E 2" },
{ id: "gpt-image-2", name: "GPT Image 2" },
{ id: "gpt-image-1.5", name: "GPT Image 1.5" },
{ id: "gpt-image-1-mini", name: "GPT Image 1 Mini" },
],
supportedSizes: ["1024x1024", "1024x1792", "1792x1024", "256x256", "512x512"],
supportedSizes: ["1024x1024", "1024x1792", "1792x1024"],
},
// Codex exposes image generation only as a Responses-API hosted tool under
@@ -129,11 +132,11 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authHeader: "bearer",
format: "codex-responses",
models: [
{ id: "gpt-5.5", name: "GPT 5.5 (Codex Image)" },
{ id: "gpt-5.4", name: "GPT 5.4 (Codex Image)" },
{ id: "gpt-5.3-codex", name: "GPT 5.3 Codex (Image)" },
{ id: "gpt-5.1-codex-max", name: "GPT 5.1 Codex Max (Image)" },
],
supportedSizes: ["512x512", "1024x1024", "1024x1536", "1536x1024"],
supportedSizes: ["1024x1024", "1024x1536", "1536x1024"],
},
"chatgpt-web": {
@@ -153,7 +156,7 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authType: "apikey",
authHeader: "bearer",
format: "openai",
models: [{ id: "grok-2-image-1212", name: "Grok 2 Image" }],
models: [{ id: "grok-imagine-image", name: "Grok Imagine Image" }],
supportedSizes: ["1024x1024"],
},
@@ -165,32 +168,39 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
format: "openai",
models: [
{
id: "black-forest-labs/FLUX.1.1-pro",
name: "FLUX 1.1 Pro",
id: "black-forest-labs/FLUX.2-max",
name: "FLUX.2 Max",
inputModalities: ["text", "image"],
description: "Advanced contextual image editing and image-to-image generation",
},
{ id: "black-forest-labs/FLUX.1-schnell-Free", name: "FLUX 1 Schnell (Free)" },
{ id: "stabilityai/stable-diffusion-xl-base-1.0", name: "SDXL Base 1.0" },
{
id: "black-forest-labs/FLUX.1-redux",
name: "FLUX.1 Redux",
inputModalities: ["text", "image"],
description: "Generate prompt-guided variations from an input image",
},
{
id: "black-forest-labs/FLUX.1-depth",
name: "FLUX.1 Depth",
id: "black-forest-labs/FLUX.2-pro",
name: "FLUX.2 Pro",
inputModalities: ["text", "image"],
description: "Depth-conditioned image generation from an input image",
},
{
id: "black-forest-labs/FLUX.1-canny",
name: "FLUX.1 Canny",
id: "black-forest-labs/FLUX.2-flex",
name: "FLUX.2 Flex",
inputModalities: ["text", "image"],
},
{
id: "black-forest-labs/FLUX.2-dev",
name: "FLUX.2 Dev",
inputModalities: ["text", "image"],
},
{ id: "openai/gpt-image-1.5", name: "GPT Image 1.5", inputModalities: ["text", "image"] },
{ id: "Wan-AI/Wan2.6-image", name: "Wan 2.6 Image", inputModalities: ["text", "image"] },
{
id: "Qwen/Qwen-Image-2.0-Pro",
name: "Qwen Image 2.0 Pro",
inputModalities: ["text", "image"],
},
{ id: "Qwen/Qwen-Image-2.0", name: "Qwen Image 2.0", inputModalities: ["text", "image"] },
{ id: "google/flash-image-3.1", name: "NanoBanana 2", inputModalities: ["text", "image"] },
{
id: "google/gemini-3-pro-image",
name: "NanoBanana Pro",
inputModalities: ["text", "image"],
description: "Canny edge guided image generation from an input image",
},
{ id: "black-forest-labs/FLUX.1-dev-lora", name: "FLUX.1 Dev LoRA" },
],
supportedSizes: ["1024x1024", "512x512"],
},
@@ -202,6 +212,9 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authHeader: "bearer",
format: "openai",
models: [
{ id: "accounts/fireworks/flux-kontext-max", name: "FLUX Kontext Max" },
{ id: "accounts/fireworks/flux-kontext-pro", name: "FLUX Kontext Pro" },
{ id: "accounts/fireworks/flux-1-schnell-fp8", name: "FLUX.1 schnell" },
{ id: "accounts/fireworks/models/flux-1-dev-fp8", name: "FLUX 1 Dev FP8" },
{ id: "accounts/fireworks/models/stable-diffusion-xl-1024-v1-0", name: "SDXL 1024 v1.0" },
],
@@ -218,6 +231,7 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
supportedSizes: ["1024x1024"],
},
//Curruntly no models serving
nebius: {
id: "nebius",
baseUrl: "https://api.tokenfactory.nebius.com/v1/images/generations",
@@ -225,11 +239,8 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authType: "apikey",
authHeader: "bearer",
format: "openai",
models: [
{ id: "black-forest-labs/flux-schnell", name: "FLUX.1 Schnell" },
{ id: "black-forest-labs/flux-dev", name: "FLUX.1 Dev" },
],
supportedSizes: ["1024x1024", "512x512"],
models: [{ id: "black-forest-labs/flux-schnell", name: "No Model yet" }],
supportedSizes: ["1024x1024"],
},
hyperbolic: {
@@ -238,13 +249,10 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authType: "apikey",
authHeader: "bearer",
format: "hyperbolic", // custom: uses model_name, returns base64 images
models: [
{ id: "SDXL1.0-base", name: "SDXL 1.0 Base" },
{ id: "SD2", name: "Stable Diffusion 2" },
{ id: "FLUX.1-dev", name: "FLUX.1 Dev" },
],
supportedSizes: ["1024x1024", "512x512"],
models: [{ id: "SDXL1.0-base", name: "No Model yet" }],
supportedSizes: ["1024x1024"],
},
//Curruntly no models serving
nanobanana: {
id: "nanobanana",
@@ -294,12 +302,15 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authHeader: "bearer",
format: "openai",
models: [
{ id: "openai/dall-e-3", name: "DALL-E 3 (via OpenRouter)" },
{ id: "openai/gpt-image-1", name: "GPT Image 1 (via OpenRouter)" },
{ id: "google/imagen-3.0-generate-002", name: "Imagen 3 (via OpenRouter)" },
{ id: "black-forest-labs/FLUX.1-schnell", name: "FLUX.1 Schnell (via OpenRouter)" },
{ id: "openai/gpt-5.4-image-2", name: "GPT Image 2 (via OpenRouter)" },
{ id: "openai/gpt-5-image-mini", name: "GPT Image 1 Mini (via OpenRouter)" },
{ id: "google/gemini-3.1-flash-image-preview", name: "Nano Banana 2 (via OpenRouter)" },
{ id: "google/gemini-3-pro-image-preview", name: "Nano Banana Pro (via OpenRouter)" },
{ id: "black-forest-labs/flux.2-max", name: "FLUX.2 Max (via OpenRouter)" },
{ id: "black-forest-labs/flux.2-pro", name: "FLUX.2 Pro (via OpenRouter)" },
{ id: "black-forest-labs/flux.2-flex", name: "FLUX.2 Flex (via OpenRouter)" },
],
supportedSizes: ["1024x1024", "1024x1792", "1792x1024", "256x256", "512x512"],
supportedSizes: ["1024x1024", "1024x1792", "1792x1024"],
},
pollinations: {
@@ -310,25 +321,14 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authHeader: "bearer",
format: "openai",
models: [
{ id: "klein", name: "FLUX.2 Klein 4B" },
{ id: "flux", name: "Flux Schnell" },
{ id: "zimage", name: "Z-Image Turbo" },
{ id: "klein", name: "FLUX.2 Klein 4B" },
{ id: "gptimage", name: "GPT Image 1 Mini" },
{ id: "qwen-image", name: "Qwen Image Plus" },
{ id: "wan-image", name: "Wan 2.7 Image" },
{
id: "flux-kontext",
name: "FLUX.1 Kontext",
inputModalities: ["text", "image"],
description: "Context-aware image editing with optional source image",
},
{
id: "flux-kontext-max",
name: "FLUX.1 Kontext Max",
inputModalities: ["text", "image"],
description: "Higher quality Kontext editing with optional source image",
},
{ id: "gpt-image-2", name: "GPT Image 2" },
{ id: "gptimage-large", name: "GPT Image 1.5" },
{ id: "gptimage", name: "GPT Image 1 Mini" },
],
supportedSizes: ["1024x1024", "512x512"],
},
@@ -340,17 +340,17 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authHeader: "key",
format: "fal-ai",
models: [
{ id: "fal-ai/flux-pro/v1.1", name: "FLUX.1 Pro v1.1" },
{ id: "fal-ai/flux-pro/v1.1-ultra", name: "FLUX.1 Pro v1.1 Ultra" },
{ id: "fal-ai/flux/schnell", name: "FLUX.1 Schnell" },
{ id: "fal-ai/flux-2-max", name: "FLUX.2 Max" },
{ id: "fal-ai/flux-2-pro", name: "FLUX.2 Pro" },
{ id: "fal-ai/flux-2-flex", name: "FLUX.2 Flex" },
{ id: "bria/text-to-image/3.2", name: "Bria 3.2" },
{ id: "fal-ai/bytedance/seedream/v3/text-to-image", name: "SeeDream V3" },
{ id: "fal-ai/bytedance/seedream/v4.5/text-to-image", name: "SeeDream V4.5" },
{ id: "fal-ai/bytedance/dreamina/v3.1/text-to-image", name: "Dreamina V3.1" },
{ id: "fal-ai/ideogram/v3", name: "Ideogram V3" },
{ id: "fal-ai/imagen4/preview", name: "Imagen 4 Preview" },
{ id: "fal-ai/imagen4/preview/fast", name: "Imagen 4 Preview Fast" },
{ id: "fal-ai/imagen4/preview/ultra", name: "Imagen 4 Preview Ultra" },
{ id: "fal-ai/recraft/v3/text-to-image", name: "Recraft V3 via Fal" },
{ id: "fal-ai/nano-banana-pro", name: "Nano Banana Pro" },
{ id: "fal-ai/nano-banana-2", name: "Nano Banana 2" },
{ id: "fal-ai/recraft/v4/pro/text-to-image", name: "Recraft V4 Pro via Fal" },
{ id: "fal-ai/recraft/v4/text-to-image", name: "Recraft V4 via Fal" },
{ id: "fal-ai/stable-diffusion-v35-medium", name: "Stable Diffusion v3.5 Medium" },
],
supportedSizes: ["1024x1024", "1024x1280", "1280x1024"],
@@ -363,29 +363,26 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authHeader: "bearer",
format: "stability-ai",
models: [
{ id: "sd3", name: "sd3" },
{ id: "sd3-large", name: "sd3-large" },
{ id: "sd3-large-turbo", name: "sd3-large-turbo" },
{ id: "sd3-medium", name: "sd3-medium" },
{ id: "sd3.5-large", name: "sd3.5-large" },
{ id: "sd3.5-large-turbo", name: "sd3.5-large-turbo" },
{ id: "sd3.5-medium", name: "sd3.5-medium" },
{ id: "stable-image-ultra", name: "Stable Image Ultra" },
{ id: "stable-image-core", name: "Stable Image Core" },
{ id: "sd3.5-large-turbo", name: "sd3.5-large-turbo" },
{ id: "sd3.5-large", name: "sd3.5-large" },
{ id: "sd3.5-medium", name: "sd3.5-medium" },
{ id: "sd3.5-flash", name: "sd3.5-flash" },
{ id: "erase", name: "Erase", inputModalities: ["image"] },
{ id: "inpaint", name: "Inpaint", inputModalities: ["text", "image"] },
{ id: "outpaint", name: "Outpaint", inputModalities: ["text", "image"] },
{ id: "erase", name: "Erase", inputModalities: ["image"] },
{ id: "remove-background", name: "Remove Background", inputModalities: ["image"] },
{ id: "search-and-replace", name: "Search and Replace", inputModalities: ["text", "image"] },
{ id: "search-and-recolor", name: "Search and Recolor", inputModalities: ["text", "image"] },
{ id: "remove-background", name: "Remove Background", inputModalities: ["image"] },
{
id: "replace-background-and-relight",
name: "Replace Background and Relight",
inputModalities: ["text", "image"],
},
{ id: "creative", name: "Creative Upscale", inputModalities: ["text", "image"] },
{ id: "fast", name: "Fast Upscale", inputModalities: ["image"] },
{ id: "conservative", name: "Conservative Upscale", inputModalities: ["image"] },
{ id: "creative", name: "Creative Upscale", inputModalities: ["text", "image"] },
{ id: "sketch", name: "Sketch Control", inputModalities: ["text", "image"] },
{ id: "structure", name: "Structure Control", inputModalities: ["text", "image"] },
{ id: "style", name: "Style Control", inputModalities: ["text", "image"] },
@@ -401,16 +398,15 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authHeader: "x-key",
format: "black-forest-labs",
models: [
{ id: "flux-kontext-pro", name: "flux-kontext-pro", inputModalities: ["text", "image"] },
{ id: "flux-kontext-max", name: "flux-kontext-max", inputModalities: ["text", "image"] },
{ id: "flux-pro-1.0-fill", name: "flux-pro-1.0-fill", inputModalities: ["text", "image"] },
{
id: "flux-pro-1.0-expand",
name: "flux-pro-1.0-expand",
inputModalities: ["text", "image"],
},
{ id: "flux-pro-1.1", name: "flux-pro-1.1" },
{ id: "flux-2-max", name: "FLUX.2 Max" },
{ id: "flux-2-pro", name: "FLUX.2 Pro" },
{ id: "flux-2-flex", name: "FLUX.2 Flex" },
{ id: "flux-pro-1.1-ultra", name: "flux-pro-1.1-ultra" },
{ id: "flux-pro-1.1", name: "flux-pro-1.1" },
{ id: "flux-2-klein-9b", name: "flux 2 Klein 9B" },
{ id: "flux-2-klein-4b", name: "flux 2 Klein 4B" },
{ id: "flux-kontext-max", name: "flux-kontext-max", inputModalities: ["text", "image"] },
{ id: "flux-kontext-pro", name: "flux-kontext-pro", inputModalities: ["text", "image"] },
{ id: "flux-dev", name: "flux-dev" },
{ id: "flux-pro", name: "flux-pro" },
],
@@ -424,8 +420,10 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
authHeader: "bearer",
format: "recraft",
models: [
{ id: "recraftv2", name: "recraftv2" },
{ id: "recraftv3", name: "recraftv3" },
{ id: "recraftv4_pro", name: "Recraft V4 Pro" },
{ id: "recraftv4", name: "Recraft V4" },
{ id: "recraftv3", name: "Recraft V3" },
{ id: "recraftv2", name: "Recraft V2" },
],
supportedSizes: ["1024x1024", "1024x1280", "1280x1024"],
},

View File

@@ -32,24 +32,29 @@ import {
import { fetchRemoteImage } from "@/shared/network/remoteImageFetch";
const OPENAI_IMAGE_TO_IMAGE_MODELS = new Set([
"black-forest-labs/FLUX.1-redux",
"black-forest-labs/FLUX.1-depth",
"black-forest-labs/FLUX.1-canny",
"black-forest-labs/FLUX.1.1-pro",
"FLUX.1-redux",
"FLUX.1-depth",
"FLUX.1-canny",
"FLUX.1.1-pro",
"black-forest-labs/FLUX.2-max",
"black-forest-labs/FLUX.2-pro",
"black-forest-labs/FLUX.2-flex",
"black-forest-labs/FLUX.2-dev",
"openai/gpt-image-1.5",
"Wan-AI/Wan2.6-image",
"Qwen/Qwen-Image-2.0-Pro",
"Qwen/Qwen-Image-2.0",
"google/flash-image-3.1",
"google/gemini-3-pro-image",
"flux-kontext-max",
"flux-kontext",
"flux-kontext-pro",
]);
const BFL_MODEL_ENDPOINTS = {
"flux-2-max": "/v1/flux-2-max",
"flux-2-pro": "/v1/flux-2-pro",
"flux-2-flex": "/v1/flux-2-flex",
"flux-2-klein-9b": "/v1/flux-2-klein-9b",
"flux-2-klein-4b": "/v1/flux-2-klein-4b",
"flux-kontext-pro": "/v1/flux-kontext-pro",
"flux-kontext-max": "/v1/flux-kontext-max",
"flux-pro-1.0-fill": "/v1/flux-pro-1.0-fill",
"flux-pro-1.0-expand": "/v1/flux-pro-1.0-expand",
"flux-pro-1.1": "/v1/flux-pro-1.1",
"flux-pro-1.1-ultra": "/v1/flux-pro-1.1-ultra",
"flux-dev": "/v1/flux-dev",
@@ -57,22 +62,20 @@ const BFL_MODEL_ENDPOINTS = {
};
const BFL_EDIT_MODELS = new Set([
"flux-2-max",
"flux-2-pro",
"flux-2-flex",
"flux-kontext-pro",
"flux-kontext-max",
"flux-pro-1.0-fill",
"flux-pro-1.0-expand",
]);
const BFL_FAILURE_STATUSES = new Set(["Error", "Failed", "Content Moderated", "Request Moderated"]);
const STABILITY_GENERATION_ENDPOINTS = {
sd3: "/v2beta/stable-image/generate/sd3",
"sd3-large": "/v2beta/stable-image/generate/sd3",
"sd3-large-turbo": "/v2beta/stable-image/generate/sd3",
"sd3-medium": "/v2beta/stable-image/generate/sd3",
"sd3.5-large": "/v2beta/stable-image/generate/sd3",
"sd3.5-large-turbo": "/v2beta/stable-image/generate/sd3",
"sd3.5-medium": "/v2beta/stable-image/generate/sd3",
"sd3.5-flash": "/v2beta/stable-image/generate/sd3",
"stable-image-ultra": "/v2beta/stable-image/generate/ultra",
"stable-image-core": "/v2beta/stable-image/generate/core",
};
@@ -1058,7 +1061,7 @@ async function handleStabilityAIImageGeneration({
try {
if (STABILITY_GENERATION_ENDPOINTS[model]) {
if (model.startsWith("sd3") && model !== "sd3") {
if (model.startsWith("sd3.5")) {
upstreamBody.model = model;
}
@@ -1070,7 +1073,7 @@ async function handleStabilityAIImageGeneration({
upstreamBody.mode = "text-to-image";
}
if (!model.startsWith("sd3") || !imageUrl) {
if (!model.startsWith("sd3.5") || !imageUrl) {
upstreamBody.aspect_ratio = body.aspect_ratio || mapImageSize(body.size);
}

View File

@@ -1,6 +1,6 @@
{
"name": "@omniroute/open-sse",
"version": "3.7.2",
"version": "3.7.3",
"description": "Express SSE sidecar for OmniRoute — handles streaming, protocol translation, and provider orchestration",
"type": "module",
"main": "index.js",

6
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "3.7.2",
"version": "3.7.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "3.7.2",
"version": "3.7.3",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
@@ -13808,7 +13808,7 @@
},
"open-sse": {
"name": "@omniroute/open-sse",
"version": "3.7.2"
"version": "3.7.3"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "3.7.2",
"version": "3.7.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": {

View File

@@ -274,7 +274,7 @@ test("getStaticModelsForProvider returns local image catalogs for image-only pro
assert.ok(models, "xAI should expose local image models");
assert.deepEqual(
models.map((model) => model.id),
["grok-2-image-1212"]
["grok-imagine-image"]
);
});

View File

@@ -115,7 +115,7 @@ test("getTransientBuildPaths leaves _tasks in place by default", () => {
["legacy app snapshot", "local Wine prefix"]
);
assert.equal(
paths.some((entry) => entry.sourcePath === "/repo/_tasks"),
paths.some((entry) => path.basename(entry.sourcePath) === "_tasks"),
false
);
});
@@ -124,7 +124,7 @@ test("getTransientBuildPaths only moves _tasks when explicitly enabled", () => {
const paths = getTransientBuildPaths("/repo", { OMNIROUTE_BUILD_MOVE_TASKS: "1" });
assert.equal(
paths.some((entry) => entry.sourcePath === "/repo/_tasks"),
paths.some((entry) => path.basename(entry.sourcePath) === "_tasks"),
true
);
});
@@ -170,6 +170,6 @@ test("syncStandaloneNativeAssets copies wreq-js native runtime into standalone o
assert.equal(changed, true);
assert.equal(await fs.readFile(destinationNativeFile, "utf8"), "native module bytes");
assert.match(logs[0] ?? "", /wreq-js\/rust/);
assert.match((logs[0] ?? "").replaceAll("\\", "/"), /wreq-js\/rust/);
});
});

View File

@@ -13,6 +13,7 @@ const QWEN_CONFIG_PATH = path.join(DUMMY_HOME, ".qwen", "settings.json");
const QWEN_ENV_PATH = path.join(DUMMY_HOME, ".qwen", ".env");
const OPENCODE_CONFIG_PATH = path.join(DUMMY_HOME, ".config", "opencode", "opencode.json");
const originalXDG = process.env.XDG_CONFIG_HOME;
const originalAppData = process.env.APPDATA;
const originalJwtSecret = process.env.JWT_SECRET;
async function createAuthCookie() {
@@ -51,6 +52,7 @@ test.beforeEach(async () => {
// Force XDG_CONFIG_HOME so resolveOpencodeConfigPath resolves to our dummy dir
// (CI runners often have XDG_CONFIG_HOME set, causing path mismatch)
process.env.XDG_CONFIG_HOME = path.join(DUMMY_HOME, ".config");
process.env.APPDATA = path.join(DUMMY_HOME, ".config");
process.env.API_KEY_SECRET = "test-secret";
await fs.mkdir(path.dirname(QWEN_CONFIG_PATH), { recursive: true }).catch(() => {});
});
@@ -59,6 +61,8 @@ test.afterEach(async () => {
await fs.rm(DUMMY_HOME, { recursive: true, force: true }).catch(() => {});
if (originalXDG === undefined) delete process.env.XDG_CONFIG_HOME;
else process.env.XDG_CONFIG_HOME = originalXDG;
if (originalAppData === undefined) delete process.env.APPDATA;
else process.env.APPDATA = originalAppData;
if (originalJwtSecret === undefined) delete process.env.JWT_SECRET;
else process.env.JWT_SECRET = originalJwtSecret;
});

View File

@@ -54,10 +54,10 @@ test("handleImageGeneration routes OpenAI-compatible providers and forwards imag
try {
const result = await handleImageGeneration({
body: {
model: "openai/dall-e-3",
model: "openai/gpt-image-2",
prompt: "city skyline",
n: 2,
size: "1024x1792",
size: "1024x1536",
quality: "hd",
response_format: "url",
style: "vivid",
@@ -70,10 +70,10 @@ test("handleImageGeneration routes OpenAI-compatible providers and forwards imag
assert.equal(captured.url, "https://api.openai.com/v1/images/generations");
assert.equal(captured.headers.Authorization, "Bearer image-key");
assert.deepEqual(captured.body, {
model: "dall-e-3",
model: "gpt-image-2",
prompt: "city skyline",
n: 2,
size: "1024x1792",
size: "1024x1536",
quality: "hd",
response_format: "url",
style: "vivid",
@@ -250,32 +250,32 @@ test("handleImageGeneration treats unknown provider prefixes as invalid image mo
test("image registry resolves flux aliases and exposes planned catalog aliases", () => {
assert.deepEqual(parseImageModel("flux-kontext"), {
provider: "pollinations",
model: "flux-kontext",
provider: "black-forest-labs",
model: "flux-kontext-pro",
});
assert.deepEqual(parseImageModel("pollinations/kontext"), {
provider: "pollinations",
model: "flux-kontext",
provider: "black-forest-labs",
model: "flux-kontext-pro",
});
assert.deepEqual(parseImageModel("flux-redux"), {
assert.deepEqual(parseImageModel("flux-2-dev"), {
provider: "together",
model: "black-forest-labs/FLUX.1-redux",
model: "black-forest-labs/FLUX.2-dev",
});
const modelIds = new Set(getAllImageModels().map((model) => model.id));
const fluxRedux = getAllImageModels().find((model) => model.id === "flux-redux");
const flux2Dev = getAllImageModels().find((model) => model.id === "flux-2-dev");
const fluxKontext = getAllImageModels().find((model) => model.id === "flux-kontext");
for (const alias of [
"flux-kontext",
"flux-kontext-max",
"flux-redux",
"flux-depth",
"flux-canny",
"flux-dev-lora",
"flux-2-max",
"flux-2-pro",
"flux-2-flex",
"flux-2-dev",
]) {
assert.equal(modelIds.has(alias), true, `Expected alias ${alias} in image catalog`);
}
assert.deepEqual(fluxRedux?.inputModalities, ["text", "image"]);
assert.deepEqual(flux2Dev?.inputModalities, ["text", "image"]);
assert.deepEqual(fluxKontext?.inputModalities, ["text", "image"]);
});
@@ -1130,7 +1130,7 @@ test("handleImageGeneration logs OpenAI-compatible upstream failures and transpo
try {
const failed = await handleImageGeneration({
body: {
model: "openai/dall-e-3",
model: "openai/gpt-image-2",
prompt: "broken upstream",
},
credentials: { apiKey: "image-key" },
@@ -1151,7 +1151,7 @@ test("handleImageGeneration logs OpenAI-compatible upstream failures and transpo
try {
const errored = await handleImageGeneration({
body: {
model: "openai/dall-e-3",
model: "openai/gpt-image-2",
prompt: "transport issue",
},
credentials: { apiKey: "image-key" },

View File

@@ -101,7 +101,7 @@ test("v1 image generation POST still requires prompts for text-input models", as
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "openai/dall-e-3",
model: "openai/gpt-image-2",
image_url: "https://example.com/source.png",
}),
})
@@ -109,5 +109,5 @@ test("v1 image generation POST still requires prompts for text-input models", as
const body = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(body.error.message, /Prompt is required for image model: openai\/dall-e-3/);
assert.match(body.error.message, /Prompt is required for image model: openai\/gpt-image-2/);
});

View File

@@ -464,7 +464,7 @@ test("v1 models catalog includes media, moderation, rerank, video, and music mod
const byId = new Map(body.data.map((item) => [item.id, item]));
assert.equal(response.status, 200);
assert.equal((byId.get("openai/gpt-image-1") as any).type, "image");
assert.equal((byId.get("openai/gpt-image-2") as any).type, "image");
assert.equal((byId.get("openai/whisper-1") as any).type, "audio");
assert.equal((byId.get("openai/whisper-1") as any).subtype, "transcription");
assert.equal((byId.get("openai/omni-moderation-latest") as any).type, "moderation");
@@ -484,10 +484,10 @@ test("v1 models catalog exposes image model input and output modalities for adva
const byId = new Map(body.data.map((item) => [item.id, item]));
assert.equal(response.status, 200);
assert.deepEqual((byId as any).get("flux-redux")?.input_modalities, ["text", "image"]);
(assert as any).deepEqual((byId.get("flux-redux") as any).output_modalities, ["image"]);
(assert as any).equal((byId.get("flux-redux") as any).type, "image");
assert.ok((byId.get("flux-redux") as any).supported_sizes?.includes("1024x1024"));
assert.deepEqual((byId as any).get("flux-2-dev")?.input_modalities, ["text", "image"]);
(assert as any).deepEqual((byId.get("flux-2-dev") as any).output_modalities, ["image"]);
(assert as any).equal((byId.get("flux-2-dev") as any).type, "image");
assert.ok((byId.get("flux-2-dev") as any).supported_sizes?.includes("1024x1024"));
(assert as any).deepEqual((byId.get("topaz/topaz-enhance") as any).input_modalities, ["image"]);
assert.deepEqual((byId.get("topaz/topaz-enhance") as any).output_modalities, ["image"]);
});

View File

@@ -71,10 +71,10 @@ describe("OpenRouter & GitHub registry entries (#960)", () => {
assert.ok(p.models.length >= 2, `Expected ≥2 models, got ${p.models.length}`);
});
it("parses openrouter/openai/dall-e-3 correctly", () => {
const result = parseImageModel("openrouter/openai/dall-e-3");
it("parses openrouter/openai/gpt-5.4-image-2 correctly", () => {
const result = parseImageModel("openrouter/openai/gpt-5.4-image-2");
assert.equal(result.provider, "openrouter");
assert.equal(result.model, "openai/dall-e-3");
assert.equal(result.model, "openai/gpt-5.4-image-2");
});
});
});

View File

@@ -67,7 +67,7 @@ describe("transformToOmniRoute", () => {
litellm_provider: "openai",
mode: "embedding",
},
"openai/dall-e-3": {
"openai/gpt-image-2": {
input_cost_per_token: 0,
output_cost_per_token: 0,
litellm_provider: "openai",