omniroute 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/.next/BUILD_ID +1 -1
- package/app/.next/build-manifest.json +2 -2
- package/app/.next/prerender-manifest.json +3 -3
- package/app/.next/server/app/(dashboard)/dashboard/a2a/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/agents/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/analytics/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/api-manager/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/audit-log/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/auto-combo/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/costs/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/health/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/limits/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/logs/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/mcp/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/media/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/onboarding/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/playground/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/search-tools/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/settings/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/400/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/401/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/403/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/408/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/429/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/500/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/502/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/503/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/_global-error.html +2 -2
- package/app/.next/server/app/_global-error.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/callback/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/forbidden/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/forgot-password/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/landing/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/maintenance/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/offline/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/privacy/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/status/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/terms/page_client-reference-manifest.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__051203a6._.js +2 -2
- package/app/.next/server/chunks/[root-of-the-server]__0891af92._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__14f70e13._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__1f2b0d89._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__46e00e59._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__6e52619e._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__7ace0fcd._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__7fa4d14e._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__8466a619._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__df1c79db._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__e22609bd._.js +1 -1
- package/app/.next/server/chunks/_05c48915._.js +1 -1
- package/app/.next/server/chunks/_06515a8a._.js +1 -1
- package/app/.next/server/chunks/_2115d8de._.js +1 -1
- package/app/.next/server/chunks/_3ac953eb._.js +1 -1
- package/app/.next/server/chunks/_4b8fd853._.js +1 -1
- package/app/.next/server/chunks/_68683848._.js +1 -1
- package/app/.next/server/chunks/_ee9b677b._.js +1 -1
- package/app/.next/server/chunks/_efd5ede2._.js +1 -1
- package/app/.next/server/chunks/open-sse_config_providerModels_ts_04541468._.js +1 -1
- package/app/.next/server/chunks/open-sse_config_providerRegistry_ts_2f74ec2a._.js +1 -1
- package/app/.next/server/chunks/open-sse_config_providerRegistry_ts_dec0f840._.js +1 -1
- package/app/.next/server/chunks/ssr/[root-of-the-server]__9ef96d20._.js +1 -1
- package/app/.next/server/chunks/ssr/[root-of-the-server]__a6942102._.js +1 -1
- package/app/.next/server/chunks/ssr/_19b3d5b1._.js +1 -1
- package/app/.next/server/chunks/ssr/open-sse_config_providerModels_ts_4cac55e2._.js +1 -1
- package/app/.next/server/chunks/ssr/src_lib_initCloudSync_ts_982b9d4d._.js +1 -1
- package/app/.next/server/pages/500.html +2 -2
- package/app/.next/server/server-reference-manifest.js +1 -1
- package/app/.next/server/server-reference-manifest.json +1 -1
- package/app/.next/static/chunks/{58d319dd9faa8901.js → d1f1359e38f6b40a.js} +1 -1
- package/app/.next/static/chunks/{66870aa02a097795.js → fa0cdd9c7cf7222b.js} +1 -1
- package/app/CHANGELOG.md +31 -0
- package/app/docs/openapi.yaml +1 -1
- package/app/open-sse/config/providerRegistry.ts +4 -2
- package/app/open-sse/handlers/chatCore.ts +6 -4
- package/app/package-lock.json +2 -2
- package/app/package.json +1 -1
- package/app/src/app/api/v1/models/catalog.ts +60 -1
- package/app/src/lib/tokenHealthCheck.ts +24 -6
- package/package.json +1 -1
- /package/app/.next/static/{kq8q3NRT_-3D_qpyiBgIN → IflDsSCpfe7S_9YDKUH5D}/_buildManifest.js +0 -0
- /package/app/.next/static/{kq8q3NRT_-3D_qpyiBgIN → IflDsSCpfe7S_9YDKUH5D}/_clientMiddlewareManifest.json +0 -0
- /package/app/.next/static/{kq8q3NRT_-3D_qpyiBgIN → IflDsSCpfe7S_9YDKUH5D}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,804730,e=>{e.v({name:"omniroute",version:"3.1.
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,804730,e=>{e.v({name:"omniroute",version:"3.1.2",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:{omniroute:"bin/omniroute.mjs","omniroute-reset-password":"bin/reset-password.mjs"},files:["bin/","app/","open-sse/mcp-server/","src/shared/contracts/","scripts/postinstall.mjs","scripts/native-binary-compat.mjs","README.md","LICENSE"],workspaces:["open-sse"],engines:{node:">=18.0.0 <24.0.0"},keywords:["ai","router","proxy","openai","claude","anthropic","gemini","fallback","cursor","cline","codex","llm","auto-fallback"],license:"MIT",author:"diegosouzapw",repository:{type:"git",url:"https://github.com/diegosouzapw/OmniRoute"},homepage:"https://omniroute.online",scripts:{dev:"node scripts/run-next.mjs dev",build:"node scripts/build-next-isolated.mjs","build:cli":"node scripts/prepublish.mjs",start:"node scripts/run-next.mjs start",lint:"eslint .","electron:dev":'concurrently "npm run dev" "wait-on http://localhost:20128 && cd electron && npm run dev"',"electron:build":"npm run build && cd electron && npm run build","electron:build:win":"npm run build && cd electron && npm run build:win","electron:build:mac":"npm run build && cd electron && npm run build:mac","electron:build:linux":"npm run build && cd electron && npm run build:linux",test:"node --import tsx/esm --test tests/unit/*.test.mjs","test:unit":"node --import tsx/esm --test tests/unit/*.test.mjs","test:plan3":"node --import tsx/esm --test tests/unit/plan3-p0.test.mjs","test:fixes":"node --import tsx/esm --test tests/unit/fixes-p1.test.mjs","test:security":"node --import tsx/esm --test tests/unit/security-fase01.test.mjs","check:cycles":"node scripts/check-cycles.mjs","check:route-validation:t06":"node scripts/check-route-validation.mjs","check:any-budget:t11":"node scripts/check-t11-any-budget.mjs","check:docs-sync":"node scripts/check-docs-sync.mjs","typecheck:core":"tsc --pretty false -p tsconfig.typecheck-core.json","typecheck:noimplicit:core":"tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json","test:integration":"node --import tsx/esm --test tests/integration/*.test.mjs","test:e2e":"node scripts/run-playwright-tests.mjs test tests/e2e/*.spec.ts","test:protocols:e2e":"node scripts/run-protocol-clients-tests.mjs","test:vitest":"vitest run open-sse/mcp-server/__tests__/*.test.ts open-sse/services/autoCombo/__tests__/*.test.ts","test:ecosystem":"node scripts/run-ecosystem-tests.mjs","test:coverage":"npx c8 --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.mjs","test:all":"npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e",check:"npm run lint && npm run test",prepublishOnly:"npm run build:cli",postinstall:"node scripts/postinstall.mjs",prepare:"husky","system-info":"node scripts/system-info.mjs"},dependencies:{"@lobehub/icons":"^5.0.1","@modelcontextprotocol/sdk":"^1.27.1","@monaco-editor/react":"^4.7.0","@swc/helpers":"0.5.19",bcryptjs:"^3.0.3","better-sqlite3":"^12.6.2",bottleneck:"^2.19.5",dompurify:"^3.3.2",express:"^5.2.1","fetch-socks":"^1.3.2","http-proxy-middleware":"^3.0.5","https-proxy-agent":"^8.0.0",jose:"^6.1.3",keytar:"^7.9.0",lowdb:"^7.0.1","monaco-editor":"^0.55.1",next:"^16.0.10","next-intl":"^4.8.3","node-machine-id":"^1.1.12",open:"^11.0.0",ora:"^9.1.0",pino:"^10.3.1","pino-pretty":"^13.1.3",react:"19.2.4","react-dom":"19.2.4",recharts:"^3.7.0",selfsigned:"^5.5.0",tsx:"^4.21.0",undici:"^7.19.2",uuid:"^13.0.0","wreq-js":"^2.0.1",zod:"^4.3.6",zustand:"^5.0.10"},devDependencies:{"@playwright/test":"^1.58.2","@tailwindcss/postcss":"^4.1.18","@types/bcryptjs":"^3.0.0","@types/better-sqlite3":"^7.6.13","@types/keytar":"^4.4.0","@types/node":"^25.2.3","@types/react":"^19.2.14","@types/react-dom":"^19.2.3",concurrently:"^9.2.1","cross-env":"^10.1.0",eslint:"^9.39.2","eslint-config-next":"^16.0.10",husky:"^9.1.7","lint-staged":"^16.2.7",prettier:"^3.8.1",tailwindcss:"^4",typescript:"^5.9.3","typescript-eslint":"^8.56.0",vitest:"^4.0.18","wait-on":"^9.0.4"},"lint-staged":{"*.{js,jsx,ts,tsx,mjs}":["prettier --write","eslint --fix --no-error-on-unmatched-pattern"],"*.{json,md,yml,yaml,css}":["prettier --write"]},pnpm:{onlyBuiltDependencies:["@parcel/watcher","@swc/core","better-sqlite3","esbuild","omniroute","sharp"]},overrides:{dompurify:"^3.3.2"}})},175696,e=>{"use strict";var t=e.i(861745),s=e.i(843476);function n({locale:e,...n}){if(!e)throw Error(void 0);return(0,s.jsx)(t.IntlProvider,{locale:e,...n})}e.s(["default",()=>n])}]);
|
package/app/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [3.1.2] — 2026-03-26
|
|
8
|
+
|
|
9
|
+
### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **Critical: Tool Calling Regression** — Fixed `proxy_Bash` errors by disabling the `proxy_` tool name prefix in the Claude passthrough path. Tools like `Bash`, `Read`, `Write` were being renamed to `proxy_Bash`, `proxy_Read`, etc., causing Claude to reject them (#618)
|
|
12
|
+
- **Kiro Account Ban Documentation** — Documented as upstream AWS anti-fraud false positive, not an OmniRoute issue (#649)
|
|
13
|
+
|
|
14
|
+
### 🧪 Tests
|
|
15
|
+
|
|
16
|
+
- **936 tests, 0 failures**
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [3.1.1] — 2026-03-26
|
|
21
|
+
|
|
22
|
+
### ✨ New Features
|
|
23
|
+
|
|
24
|
+
- **Vision Capability Metadata**: Added `capabilities.vision`, `input_modalities`, and `output_modalities` to `/v1/models` entries for vision-capable models (PR #646)
|
|
25
|
+
- **Gemini 3.1 Models**: Added `gemini-3.1-pro-preview` and `gemini-3.1-flash-lite-preview` to the Antigravity provider (#645)
|
|
26
|
+
|
|
27
|
+
### 🐛 Bug Fixes
|
|
28
|
+
|
|
29
|
+
- **Ollama Cloud 401 Error**: Fixed incorrect API base URL — changed from `api.ollama.com` to official `ollama.com/v1/chat/completions` (#643)
|
|
30
|
+
- **Expired Token Retry**: Added bounded retry with exponential backoff (5→10→20 min) for expired OAuth connections instead of permanently skipping them (PR #647)
|
|
31
|
+
|
|
32
|
+
### 🧪 Tests
|
|
33
|
+
|
|
34
|
+
- **936 tests, 0 failures**
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
7
38
|
## [3.1.0] — 2026-03-26
|
|
8
39
|
|
|
9
40
|
### ✨ New Features
|
package/app/docs/openapi.yaml
CHANGED
|
@@ -393,6 +393,8 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
|
|
393
393
|
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
|
|
394
394
|
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" },
|
|
395
395
|
{ id: "claude-sonnet-4", name: "Claude Sonnet 4" },
|
|
396
|
+
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
|
|
397
|
+
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
|
|
396
398
|
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
|
|
397
399
|
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
|
|
398
400
|
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
|
|
@@ -1041,8 +1043,8 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
|
|
1041
1043
|
alias: "ollamacloud",
|
|
1042
1044
|
format: "openai",
|
|
1043
1045
|
executor: "default",
|
|
1044
|
-
baseUrl: "https://
|
|
1045
|
-
modelsUrl: "https://
|
|
1046
|
+
baseUrl: "https://ollama.com/v1/chat/completions",
|
|
1047
|
+
modelsUrl: "https://ollama.com/api/tags",
|
|
1046
1048
|
authType: "apikey",
|
|
1047
1049
|
authHeader: "bearer",
|
|
1048
1050
|
// Note: rate limits vary by plan (free = "Light usage", Pro = more, Max = 5x Pro).
|
|
@@ -426,10 +426,12 @@ export async function handleChatCore({
|
|
|
426
426
|
} else {
|
|
427
427
|
translatedBody = { ...body };
|
|
428
428
|
|
|
429
|
-
// Issue #199:
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
429
|
+
// Issue #199 + #618: Always disable tool name prefix in Claude passthrough.
|
|
430
|
+
// The proxy_ prefix was designed for OpenAI→Claude translation to avoid
|
|
431
|
+
// conflicts with Claude OAuth tools, but in the passthrough path the tools
|
|
432
|
+
// are already in Claude format. Applying the prefix turns "Bash" into
|
|
433
|
+
// "proxy_Bash", which Claude rejects ("No such tool available: proxy_Bash").
|
|
434
|
+
if (targetFormat === FORMATS.CLAUDE) {
|
|
433
435
|
translatedBody._disableToolPrefix = true;
|
|
434
436
|
}
|
|
435
437
|
|
package/app/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omniroute",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omniroute",
|
|
9
|
-
"version": "3.1.
|
|
9
|
+
"version": "3.1.2",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"workspaces": [
|
package/app/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omniroute",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,47 @@ const FALLBACK_ALIAS_TO_PROVIDER = {
|
|
|
33
33
|
qw: "qwen",
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
const VISION_MODEL_KEYWORDS = [
|
|
37
|
+
"gpt-4o",
|
|
38
|
+
"gpt-4.1",
|
|
39
|
+
"gpt-4-vision",
|
|
40
|
+
"gpt-4-turbo",
|
|
41
|
+
"claude-3",
|
|
42
|
+
"claude-3.5",
|
|
43
|
+
"claude-3-5",
|
|
44
|
+
"claude-4",
|
|
45
|
+
"claude-opus",
|
|
46
|
+
"claude-sonnet",
|
|
47
|
+
"claude-haiku",
|
|
48
|
+
"gemini",
|
|
49
|
+
"gemma",
|
|
50
|
+
"llava",
|
|
51
|
+
"bakllava",
|
|
52
|
+
"pixtral",
|
|
53
|
+
"mistral-pixtral",
|
|
54
|
+
"qwen-vl",
|
|
55
|
+
"qvq",
|
|
56
|
+
"glm-4.6v",
|
|
57
|
+
"glm-4.5v",
|
|
58
|
+
"vision",
|
|
59
|
+
"multimodal",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
function isVisionModelId(modelId: string): boolean {
|
|
63
|
+
const normalized = String(modelId || "").toLowerCase();
|
|
64
|
+
if (!normalized) return false;
|
|
65
|
+
return VISION_MODEL_KEYWORDS.some((keyword) => normalized.includes(keyword));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getVisionCapabilityFields(modelId: string) {
|
|
69
|
+
if (!isVisionModelId(modelId)) return null;
|
|
70
|
+
return {
|
|
71
|
+
capabilities: { vision: true },
|
|
72
|
+
input_modalities: ["text", "image"],
|
|
73
|
+
output_modalities: ["text"],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
36
77
|
function buildAliasMaps() {
|
|
37
78
|
const aliasToProviderId: Record<string, string> = {};
|
|
38
79
|
const providerIdToAlias: Record<string, string> = {};
|
|
@@ -214,6 +255,8 @@ export async function getUnifiedModelsResponse(
|
|
|
214
255
|
|
|
215
256
|
for (const model of providerModels) {
|
|
216
257
|
const aliasId = `${alias}/${model.id}`;
|
|
258
|
+
const visionFields =
|
|
259
|
+
getVisionCapabilityFields(aliasId) || getVisionCapabilityFields(model.id);
|
|
217
260
|
// Model-level context length overrides provider default
|
|
218
261
|
const contextLength = model.contextLength || defaultContextLength;
|
|
219
262
|
|
|
@@ -226,13 +269,17 @@ export async function getUnifiedModelsResponse(
|
|
|
226
269
|
root: model.id,
|
|
227
270
|
parent: null,
|
|
228
271
|
...(contextLength ? { context_length: contextLength } : {}),
|
|
272
|
+
...(visionFields || {}),
|
|
229
273
|
});
|
|
230
274
|
|
|
231
275
|
// Add provider-id prefix in addition to short alias (ex: kiro/model + kr/model).
|
|
232
276
|
// This improves compatibility for clients that expect full provider names.
|
|
233
277
|
if (canonicalProviderId !== alias) {
|
|
278
|
+
const providerIdModel = `${canonicalProviderId}/${model.id}`;
|
|
279
|
+
const providerVisionFields =
|
|
280
|
+
getVisionCapabilityFields(providerIdModel) || getVisionCapabilityFields(model.id);
|
|
234
281
|
models.push({
|
|
235
|
-
id:
|
|
282
|
+
id: providerIdModel,
|
|
236
283
|
object: "model",
|
|
237
284
|
created: timestamp,
|
|
238
285
|
owned_by: canonicalProviderId,
|
|
@@ -240,6 +287,7 @@ export async function getUnifiedModelsResponse(
|
|
|
240
287
|
root: model.id,
|
|
241
288
|
parent: aliasId,
|
|
242
289
|
...(contextLength ? { context_length: contextLength } : {}),
|
|
290
|
+
...(providerVisionFields || {}),
|
|
243
291
|
});
|
|
244
292
|
}
|
|
245
293
|
}
|
|
@@ -383,6 +431,10 @@ export async function getUnifiedModelsResponse(
|
|
|
383
431
|
if (endpoints.includes("embeddings")) modelType = "embedding";
|
|
384
432
|
else if (endpoints.includes("images")) modelType = "image";
|
|
385
433
|
else if (endpoints.includes("audio")) modelType = "audio";
|
|
434
|
+
const visionFields =
|
|
435
|
+
modelType === "chat"
|
|
436
|
+
? getVisionCapabilityFields(aliasId) || getVisionCapabilityFields(modelId)
|
|
437
|
+
: null;
|
|
386
438
|
|
|
387
439
|
models.push({
|
|
388
440
|
id: aliasId,
|
|
@@ -398,12 +450,18 @@ export async function getUnifiedModelsResponse(
|
|
|
398
450
|
...(endpoints.length > 1 || !endpoints.includes("chat")
|
|
399
451
|
? { supported_endpoints: endpoints }
|
|
400
452
|
: {}),
|
|
453
|
+
...(visionFields || {}),
|
|
401
454
|
});
|
|
402
455
|
|
|
403
456
|
// Only add provider-prefixed version if different from alias
|
|
404
457
|
if (canonicalProviderId !== alias && !prefix) {
|
|
405
458
|
const providerPrefixedId = `${canonicalProviderId}/${modelId}`;
|
|
406
459
|
if (models.some((m) => m.id === providerPrefixedId)) continue;
|
|
460
|
+
const providerVisionFields =
|
|
461
|
+
modelType === "chat"
|
|
462
|
+
? getVisionCapabilityFields(providerPrefixedId) ||
|
|
463
|
+
getVisionCapabilityFields(modelId)
|
|
464
|
+
: null;
|
|
407
465
|
models.push({
|
|
408
466
|
id: providerPrefixedId,
|
|
409
467
|
object: "model",
|
|
@@ -414,6 +472,7 @@ export async function getUnifiedModelsResponse(
|
|
|
414
472
|
parent: aliasId,
|
|
415
473
|
custom: true,
|
|
416
474
|
...(modelType ? { type: modelType } : {}),
|
|
475
|
+
...(providerVisionFields || {}),
|
|
417
476
|
});
|
|
418
477
|
}
|
|
419
478
|
}
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
21
21
|
const TICK_MS = 60 * 1000; // sweep interval: every 60 seconds
|
|
22
22
|
const DEFAULT_HEALTH_CHECK_INTERVAL_MIN = 60; // default per-connection interval
|
|
23
|
+
const EXPIRED_RETRY_MAX = 3; // max retry attempts for expired connections before giving up
|
|
24
|
+
const EXPIRED_RETRY_BACKOFF_MIN = 5; // backoff between expired retries (minutes)
|
|
23
25
|
const LOG_PREFIX = "[HealthCheck]";
|
|
24
26
|
const TRUE_ENV_VALUES = new Set(["1", "true", "yes", "on"]);
|
|
25
27
|
|
|
@@ -172,8 +174,19 @@ async function checkConnection(conn) {
|
|
|
172
174
|
if (!conn.isActive) return;
|
|
173
175
|
if (!conn.refreshToken || typeof conn.refreshToken !== "string") return;
|
|
174
176
|
|
|
175
|
-
//
|
|
176
|
-
if (conn.testStatus === "expired")
|
|
177
|
+
// Retry expired connections with exponential backoff up to EXPIRED_RETRY_MAX times.
|
|
178
|
+
if (conn.testStatus === "expired") {
|
|
179
|
+
const retryCount = conn.expiredRetryCount ?? 0;
|
|
180
|
+
if (retryCount >= EXPIRED_RETRY_MAX) return;
|
|
181
|
+
|
|
182
|
+
const lastRetry = conn.expiredRetryAt ? new Date(conn.expiredRetryAt).getTime() : 0;
|
|
183
|
+
const backoffMs = EXPIRED_RETRY_BACKOFF_MIN * 60 * 1000 * Math.pow(2, retryCount);
|
|
184
|
+
if (Date.now() - lastRetry < backoffMs) return;
|
|
185
|
+
|
|
186
|
+
log(
|
|
187
|
+
`${LOG_PREFIX} Retrying expired ${conn.provider}/${conn.name || conn.email || conn.id} (attempt ${retryCount + 1}/${EXPIRED_RETRY_MAX})`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
177
190
|
|
|
178
191
|
if (!supportsTokenRefresh(conn.provider)) {
|
|
179
192
|
const now = new Date().toISOString();
|
|
@@ -248,7 +261,6 @@ async function checkConnection(conn) {
|
|
|
248
261
|
}
|
|
249
262
|
|
|
250
263
|
if (result && result.accessToken) {
|
|
251
|
-
// Token refreshed successfully — update DB
|
|
252
264
|
const updateData: any = {
|
|
253
265
|
accessToken: result.accessToken,
|
|
254
266
|
lastHealthCheckAt: now,
|
|
@@ -258,6 +270,8 @@ async function checkConnection(conn) {
|
|
|
258
270
|
lastErrorType: null,
|
|
259
271
|
lastErrorSource: null,
|
|
260
272
|
errorCode: null,
|
|
273
|
+
expiredRetryCount: null,
|
|
274
|
+
expiredRetryAt: null,
|
|
261
275
|
};
|
|
262
276
|
|
|
263
277
|
if (result.refreshToken) {
|
|
@@ -271,18 +285,22 @@ async function checkConnection(conn) {
|
|
|
271
285
|
await updateProviderConnection(conn.id, updateData);
|
|
272
286
|
log(`${LOG_PREFIX} ✓ ${conn.provider}/${conn.name || conn.email || conn.id} refreshed`);
|
|
273
287
|
} else {
|
|
274
|
-
|
|
288
|
+
const wasExpired = conn.testStatus === "expired";
|
|
289
|
+
const retryCount = (conn.expiredRetryCount ?? 0) + (wasExpired ? 1 : 0);
|
|
290
|
+
|
|
275
291
|
await updateProviderConnection(conn.id, {
|
|
276
292
|
lastHealthCheckAt: now,
|
|
277
|
-
testStatus: "error",
|
|
293
|
+
testStatus: wasExpired ? "expired" : "error",
|
|
278
294
|
lastError: "Health check: token refresh failed",
|
|
279
295
|
lastErrorAt: now,
|
|
280
296
|
lastErrorType: "token_refresh_failed",
|
|
281
297
|
lastErrorSource: "oauth",
|
|
282
298
|
errorCode: "refresh_failed",
|
|
299
|
+
...(wasExpired ? { expiredRetryCount: retryCount, expiredRetryAt: now } : {}),
|
|
283
300
|
});
|
|
284
301
|
logWarn(
|
|
285
|
-
`${LOG_PREFIX} ✗ ${conn.provider}/${conn.name || conn.email || conn.id} refresh failed`
|
|
302
|
+
`${LOG_PREFIX} ✗ ${conn.provider}/${conn.name || conn.email || conn.id} refresh failed` +
|
|
303
|
+
(wasExpired ? ` (expired retry ${retryCount}/${EXPIRED_RETRY_MAX})` : "")
|
|
286
304
|
);
|
|
287
305
|
}
|
|
288
306
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omniroute",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|