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.
Files changed (102) hide show
  1. package/app/.next/BUILD_ID +1 -1
  2. package/app/.next/build-manifest.json +2 -2
  3. package/app/.next/prerender-manifest.json +3 -3
  4. package/app/.next/server/app/(dashboard)/dashboard/a2a/page_client-reference-manifest.js +1 -1
  5. package/app/.next/server/app/(dashboard)/dashboard/agents/page_client-reference-manifest.js +1 -1
  6. package/app/.next/server/app/(dashboard)/dashboard/analytics/page_client-reference-manifest.js +1 -1
  7. package/app/.next/server/app/(dashboard)/dashboard/api-manager/page_client-reference-manifest.js +1 -1
  8. package/app/.next/server/app/(dashboard)/dashboard/audit-log/page_client-reference-manifest.js +1 -1
  9. package/app/.next/server/app/(dashboard)/dashboard/auto-combo/page_client-reference-manifest.js +1 -1
  10. package/app/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
  11. package/app/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
  12. package/app/.next/server/app/(dashboard)/dashboard/costs/page_client-reference-manifest.js +1 -1
  13. package/app/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
  14. package/app/.next/server/app/(dashboard)/dashboard/health/page_client-reference-manifest.js +1 -1
  15. package/app/.next/server/app/(dashboard)/dashboard/limits/page_client-reference-manifest.js +1 -1
  16. package/app/.next/server/app/(dashboard)/dashboard/logs/page_client-reference-manifest.js +1 -1
  17. package/app/.next/server/app/(dashboard)/dashboard/mcp/page_client-reference-manifest.js +1 -1
  18. package/app/.next/server/app/(dashboard)/dashboard/media/page_client-reference-manifest.js +1 -1
  19. package/app/.next/server/app/(dashboard)/dashboard/onboarding/page_client-reference-manifest.js +1 -1
  20. package/app/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
  21. package/app/.next/server/app/(dashboard)/dashboard/playground/page_client-reference-manifest.js +1 -1
  22. package/app/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
  23. package/app/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
  24. package/app/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
  25. package/app/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
  26. package/app/.next/server/app/(dashboard)/dashboard/search-tools/page_client-reference-manifest.js +1 -1
  27. package/app/.next/server/app/(dashboard)/dashboard/settings/page_client-reference-manifest.js +1 -1
  28. package/app/.next/server/app/(dashboard)/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
  29. package/app/.next/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
  30. package/app/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
  31. package/app/.next/server/app/400/page_client-reference-manifest.js +1 -1
  32. package/app/.next/server/app/401/page_client-reference-manifest.js +1 -1
  33. package/app/.next/server/app/403/page_client-reference-manifest.js +1 -1
  34. package/app/.next/server/app/408/page_client-reference-manifest.js +1 -1
  35. package/app/.next/server/app/429/page_client-reference-manifest.js +1 -1
  36. package/app/.next/server/app/500/page_client-reference-manifest.js +1 -1
  37. package/app/.next/server/app/502/page_client-reference-manifest.js +1 -1
  38. package/app/.next/server/app/503/page_client-reference-manifest.js +1 -1
  39. package/app/.next/server/app/_global-error.html +2 -2
  40. package/app/.next/server/app/_global-error.rsc +1 -1
  41. package/app/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  42. package/app/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  43. package/app/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  44. package/app/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  45. package/app/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  46. package/app/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  47. package/app/.next/server/app/callback/page_client-reference-manifest.js +1 -1
  48. package/app/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  49. package/app/.next/server/app/forbidden/page_client-reference-manifest.js +1 -1
  50. package/app/.next/server/app/forgot-password/page_client-reference-manifest.js +1 -1
  51. package/app/.next/server/app/landing/page_client-reference-manifest.js +1 -1
  52. package/app/.next/server/app/login/page_client-reference-manifest.js +1 -1
  53. package/app/.next/server/app/maintenance/page_client-reference-manifest.js +1 -1
  54. package/app/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  55. package/app/.next/server/app/page_client-reference-manifest.js +1 -1
  56. package/app/.next/server/app/privacy/page_client-reference-manifest.js +1 -1
  57. package/app/.next/server/app/status/page_client-reference-manifest.js +1 -1
  58. package/app/.next/server/app/terms/page_client-reference-manifest.js +1 -1
  59. package/app/.next/server/chunks/[root-of-the-server]__051203a6._.js +2 -2
  60. package/app/.next/server/chunks/[root-of-the-server]__0891af92._.js +1 -1
  61. package/app/.next/server/chunks/[root-of-the-server]__14f70e13._.js +1 -1
  62. package/app/.next/server/chunks/[root-of-the-server]__1f2b0d89._.js +1 -1
  63. package/app/.next/server/chunks/[root-of-the-server]__46e00e59._.js +1 -1
  64. package/app/.next/server/chunks/[root-of-the-server]__6e52619e._.js +1 -1
  65. package/app/.next/server/chunks/[root-of-the-server]__7ace0fcd._.js +1 -1
  66. package/app/.next/server/chunks/[root-of-the-server]__7fa4d14e._.js +1 -1
  67. package/app/.next/server/chunks/[root-of-the-server]__8466a619._.js +1 -1
  68. package/app/.next/server/chunks/[root-of-the-server]__df1c79db._.js +1 -1
  69. package/app/.next/server/chunks/[root-of-the-server]__e22609bd._.js +1 -1
  70. package/app/.next/server/chunks/_05c48915._.js +1 -1
  71. package/app/.next/server/chunks/_06515a8a._.js +1 -1
  72. package/app/.next/server/chunks/_2115d8de._.js +1 -1
  73. package/app/.next/server/chunks/_3ac953eb._.js +1 -1
  74. package/app/.next/server/chunks/_4b8fd853._.js +1 -1
  75. package/app/.next/server/chunks/_68683848._.js +1 -1
  76. package/app/.next/server/chunks/_ee9b677b._.js +1 -1
  77. package/app/.next/server/chunks/_efd5ede2._.js +1 -1
  78. package/app/.next/server/chunks/open-sse_config_providerModels_ts_04541468._.js +1 -1
  79. package/app/.next/server/chunks/open-sse_config_providerRegistry_ts_2f74ec2a._.js +1 -1
  80. package/app/.next/server/chunks/open-sse_config_providerRegistry_ts_dec0f840._.js +1 -1
  81. package/app/.next/server/chunks/ssr/[root-of-the-server]__9ef96d20._.js +1 -1
  82. package/app/.next/server/chunks/ssr/[root-of-the-server]__a6942102._.js +1 -1
  83. package/app/.next/server/chunks/ssr/_19b3d5b1._.js +1 -1
  84. package/app/.next/server/chunks/ssr/open-sse_config_providerModels_ts_4cac55e2._.js +1 -1
  85. package/app/.next/server/chunks/ssr/src_lib_initCloudSync_ts_982b9d4d._.js +1 -1
  86. package/app/.next/server/pages/500.html +2 -2
  87. package/app/.next/server/server-reference-manifest.js +1 -1
  88. package/app/.next/server/server-reference-manifest.json +1 -1
  89. package/app/.next/static/chunks/{58d319dd9faa8901.js → d1f1359e38f6b40a.js} +1 -1
  90. package/app/.next/static/chunks/{66870aa02a097795.js → fa0cdd9c7cf7222b.js} +1 -1
  91. package/app/CHANGELOG.md +31 -0
  92. package/app/docs/openapi.yaml +1 -1
  93. package/app/open-sse/config/providerRegistry.ts +4 -2
  94. package/app/open-sse/handlers/chatCore.ts +6 -4
  95. package/app/package-lock.json +2 -2
  96. package/app/package.json +1 -1
  97. package/app/src/app/api/v1/models/catalog.ts +60 -1
  98. package/app/src/lib/tokenHealthCheck.ts +24 -6
  99. package/package.json +1 -1
  100. /package/app/.next/static/{kq8q3NRT_-3D_qpyiBgIN → IflDsSCpfe7S_9YDKUH5D}/_buildManifest.js +0 -0
  101. /package/app/.next/static/{kq8q3NRT_-3D_qpyiBgIN → IflDsSCpfe7S_9YDKUH5D}/_clientMiddlewareManifest.json +0 -0
  102. /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.0",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])}]);
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
@@ -1,7 +1,7 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: OmniRoute API
4
- version: 3.1.0
4
+ version: 3.1.2
5
5
  description: |
6
6
  OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
7
7
  endpoint that routes requests to multiple AI providers with load balancing,
@@ -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://api.ollama.com/v1/chat/completions",
1045
- modelsUrl: "https://api.ollama.com/v1/models",
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: Disable tool name prefix when routing Claude-format requests
430
- // to non-Claude backends (prefix causes tool name mismatches)
431
- const claudeProviders = ["claude", "anthropic"];
432
- if (targetFormat === FORMATS.CLAUDE && !claudeProviders.includes(provider?.toLowerCase?.())) {
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
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omniroute",
3
- "version": "3.1.0",
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.0",
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.0",
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: `${canonicalProviderId}/${model.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
- // Skip connections already marked as expired (need re-auth, not retry)
176
- if (conn.testStatus === "expired") return;
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
- // Refresh failed record but don't disable the connection
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.0",
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": {