omniroute 3.0.7 → 3.0.8

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 (95) 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]__1f2b0d89._.js +1 -1
  62. package/app/.next/server/chunks/[root-of-the-server]__46e00e59._.js +1 -1
  63. package/app/.next/server/chunks/[root-of-the-server]__6e52619e._.js +1 -1
  64. package/app/.next/server/chunks/[root-of-the-server]__70a3877b._.js +5 -5
  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]__b013abab._.js +6 -6
  68. package/app/.next/server/chunks/_05c48915._.js +1 -1
  69. package/app/.next/server/chunks/_06515a8a._.js +1 -1
  70. package/app/.next/server/chunks/_2115d8de._.js +1 -1
  71. package/app/.next/server/chunks/_3ac953eb._.js +1 -1
  72. package/app/.next/server/chunks/_4b8fd853._.js +1 -1
  73. package/app/.next/server/chunks/_68683848._.js +1 -1
  74. package/app/.next/server/chunks/_ee9b677b._.js +1 -1
  75. package/app/.next/server/chunks/_efd5ede2._.js +1 -1
  76. package/app/.next/server/chunks/open-sse_translator_index_ts_f5fd0821._.js +1 -1
  77. package/app/.next/server/chunks/ssr/[root-of-the-server]__9ef96d20._.js +1 -1
  78. package/app/.next/server/chunks/ssr/[root-of-the-server]__a6942102._.js +1 -1
  79. package/app/.next/server/pages/500.html +2 -2
  80. package/app/.next/server/server-reference-manifest.js +1 -1
  81. package/app/.next/server/server-reference-manifest.json +1 -1
  82. package/app/.next/static/chunks/{84065c05df1a3a1a.js → 6d9bfb06374cbab3.js} +1 -1
  83. package/app/CHANGELOG.md +13 -0
  84. package/app/docs/openapi.yaml +1 -1
  85. package/app/open-sse/handlers/responseSanitizer.ts +58 -0
  86. package/app/open-sse/translator/response/openai-to-claude.ts +12 -1
  87. package/app/open-sse/utils/usageTracking.ts +44 -5
  88. package/app/package-lock.json +20 -20
  89. package/app/package.json +1 -1
  90. package/app/src/lib/db/combos.ts +5 -5
  91. package/app/src/lib/db/models.ts +9 -9
  92. package/package.json +1 -1
  93. /package/app/.next/static/{kmbT_1tKg7aSbEcFkhAUg → VtLeHcThjUtEgx1Z2skPg}/_buildManifest.js +0 -0
  94. /package/app/.next/static/{kmbT_1tKg7aSbEcFkhAUg → VtLeHcThjUtEgx1Z2skPg}/_clientMiddlewareManifest.json +0 -0
  95. /package/app/.next/static/{kmbT_1tKg7aSbEcFkhAUg → VtLeHcThjUtEgx1Z2skPg}/_ssgManifest.js +0 -0
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "node": {},
3
3
  "edge": {},
4
- "encryptionKey": "GT2OGLcmUlhJAJa0YmX+L2I1G2XUUapLrPFxQursgOc="
4
+ "encryptionKey": "hJD64YPa1YK2oyI5QJMnIdW40VKpZRkXJLr03XUQxSo="
5
5
  }
@@ -1 +1 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,804730,e=>{e.v({name:"omniroute",version:"3.0.7",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.0.8",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,19 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## [3.0.8] — 2026-03-25
8
+
9
+ ### 🐛 Bug Fixes
10
+
11
+ - **Translation Failures for OpenAI-format Providers in Claude CLI (#632):**
12
+ - Handle `reasoning_details[]` array format from StepFun/OpenRouter — converts to `reasoning_content`
13
+ - Handle `reasoning` field alias from some providers → normalized to `reasoning_content`
14
+ - Cross-map usage field names: `input_tokens`↔`prompt_tokens`, `output_tokens`↔`completion_tokens` in `filterUsageForFormat`
15
+ - Fix `extractUsage` to accept both `input_tokens`/`output_tokens` and `prompt_tokens`/`completion_tokens` as valid usage fields
16
+ - Applied to both streaming (`sanitizeStreamingChunk`, `openai-to-claude.ts` translator) and non-streaming (`sanitizeMessage`) paths
17
+
18
+ ---
19
+
7
20
  ## [3.0.7] — 2026-03-25
8
21
 
9
22
  ### 🐛 Bug Fixes
@@ -1,7 +1,7 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: OmniRoute API
4
- version: 3.0.7
4
+ version: 3.0.8
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,
@@ -172,6 +172,44 @@ function sanitizeMessage(msg: unknown): unknown {
172
172
  sanitized.reasoning_content = msgRecord.reasoning_content;
173
173
  }
174
174
 
175
+ // Handle 'reasoning' field alias (some providers use this instead of reasoning_content)
176
+ if (
177
+ msgRecord.reasoning &&
178
+ typeof msgRecord.reasoning === "string" &&
179
+ !sanitized.reasoning_content
180
+ ) {
181
+ sanitized.reasoning_content = msgRecord.reasoning;
182
+ }
183
+
184
+ // Handle reasoning_details[] array (StepFun/OpenRouter format)
185
+ // Structure: [{ type: "reasoning.text", text: "...", format: "unknown", index: 0 }]
186
+ if (Array.isArray(msgRecord.reasoning_details) && !sanitized.reasoning_content) {
187
+ const reasoningParts: string[] = [];
188
+ for (const detail of msgRecord.reasoning_details) {
189
+ const detailObj = detail && typeof detail === "object" ? (detail as JsonRecord) : null;
190
+ if (!detailObj) continue;
191
+ const detailType = typeof detailObj.type === "string" ? detailObj.type : "";
192
+ const detailText =
193
+ typeof detailObj.text === "string"
194
+ ? detailObj.text
195
+ : typeof detailObj.content === "string"
196
+ ? detailObj.content
197
+ : "";
198
+ if (
199
+ detailText &&
200
+ (detailType === "reasoning" ||
201
+ detailType === "reasoning.text" ||
202
+ detailType === "thinking" ||
203
+ detailType === "")
204
+ ) {
205
+ reasoningParts.push(detailText);
206
+ }
207
+ }
208
+ if (reasoningParts.length > 0) {
209
+ sanitized.reasoning_content = reasoningParts.join("");
210
+ }
211
+ }
212
+
175
213
  // Preserve tool_calls
176
214
  if (msgRecord.tool_calls) {
177
215
  sanitized.tool_calls = msgRecord.tool_calls;
@@ -258,6 +296,26 @@ export function sanitizeStreamingChunk(parsed: unknown): unknown {
258
296
  if (deltaRecord.content !== undefined) delta.content = deltaRecord.content;
259
297
  if (deltaRecord.reasoning_content !== undefined) {
260
298
  delta.reasoning_content = deltaRecord.reasoning_content;
299
+ } else if (typeof deltaRecord.reasoning === "string" && deltaRecord.reasoning) {
300
+ // Alias: some providers use 'reasoning' instead of 'reasoning_content'
301
+ delta.reasoning_content = deltaRecord.reasoning;
302
+ } else if (Array.isArray(deltaRecord.reasoning_details)) {
303
+ // StepFun/OpenRouter: reasoning_details[{type:"reasoning.text", text:"..."}]
304
+ const parts: string[] = [];
305
+ for (const detail of deltaRecord.reasoning_details) {
306
+ const d = detail && typeof detail === "object" ? (detail as JsonRecord) : null;
307
+ if (!d) continue;
308
+ const text =
309
+ typeof d.text === "string"
310
+ ? d.text
311
+ : typeof d.content === "string"
312
+ ? d.content
313
+ : "";
314
+ if (text) parts.push(text);
315
+ }
316
+ if (parts.length > 0) {
317
+ delta.reasoning_content = parts.join("");
318
+ }
261
319
  }
262
320
  if (deltaRecord.tool_calls !== undefined) delta.tool_calls = deltaRecord.tool_calls;
263
321
  if (deltaRecord.function_call !== undefined)
@@ -93,7 +93,18 @@ export function openaiToClaudeResponse(chunk, state) {
93
93
  }
94
94
 
95
95
  // Handle reasoning_content (thinking) - GLM, DeepSeek, etc.
96
- const reasoningContent = delta?.reasoning_content || delta?.reasoning;
96
+ // Also supports 'reasoning' field alias and reasoning_details[] (StepFun/OpenRouter)
97
+ let reasoningContent = delta?.reasoning_content || delta?.reasoning;
98
+ if (!reasoningContent && Array.isArray(delta?.reasoning_details)) {
99
+ const parts: string[] = [];
100
+ for (const detail of delta.reasoning_details) {
101
+ if (detail && typeof detail === "object") {
102
+ const text = detail.text || detail.content;
103
+ if (typeof text === "string" && text) parts.push(text);
104
+ }
105
+ }
106
+ if (parts.length > 0) reasoningContent = parts.join("");
107
+ }
97
108
  if (reasoningContent) {
98
109
  stopTextBlock(state, results);
99
110
 
@@ -66,12 +66,47 @@ export function addBufferToUsage(usage) {
66
66
  export function filterUsageForFormat(usage, targetFormat) {
67
67
  if (!usage || typeof usage !== "object") return usage;
68
68
 
69
+ // Cross-map between Claude-style and OpenAI-style field names before filtering.
70
+ // Some providers return input_tokens/output_tokens even when using OpenAI format.
71
+ const convertedUsage = { ...usage };
72
+ if (targetFormat === FORMATS.CLAUDE || targetFormat === FORMATS.OPENAI_RESPONSES) {
73
+ // OpenAI → Claude: prompt_tokens → input_tokens
74
+ if (convertedUsage.prompt_tokens !== undefined && convertedUsage.input_tokens === undefined) {
75
+ convertedUsage.input_tokens = convertedUsage.prompt_tokens;
76
+ }
77
+ if (
78
+ convertedUsage.completion_tokens !== undefined &&
79
+ convertedUsage.output_tokens === undefined
80
+ ) {
81
+ convertedUsage.output_tokens = convertedUsage.completion_tokens;
82
+ }
83
+ } else {
84
+ // Claude → OpenAI: input_tokens → prompt_tokens
85
+ if (convertedUsage.input_tokens !== undefined && convertedUsage.prompt_tokens === undefined) {
86
+ convertedUsage.prompt_tokens = convertedUsage.input_tokens;
87
+ }
88
+ if (
89
+ convertedUsage.output_tokens !== undefined &&
90
+ convertedUsage.completion_tokens === undefined
91
+ ) {
92
+ convertedUsage.completion_tokens = convertedUsage.output_tokens;
93
+ }
94
+ // Ensure total_tokens is set
95
+ if (
96
+ convertedUsage.total_tokens === undefined &&
97
+ convertedUsage.prompt_tokens !== undefined &&
98
+ convertedUsage.completion_tokens !== undefined
99
+ ) {
100
+ convertedUsage.total_tokens = convertedUsage.prompt_tokens + convertedUsage.completion_tokens;
101
+ }
102
+ }
103
+
69
104
  // Helper to pick only defined fields from usage
70
105
  const pickFields = (fields) => {
71
106
  const filtered = {};
72
107
  for (const field of fields) {
73
- if (usage[field] !== undefined) {
74
- filtered[field] = usage[field];
108
+ if (convertedUsage[field] !== undefined) {
109
+ filtered[field] = convertedUsage[field];
75
110
  }
76
111
  }
77
112
  return filtered;
@@ -230,10 +265,14 @@ export function extractUsage(chunk) {
230
265
  }
231
266
 
232
267
  // OpenAI format
233
- if (chunk.usage && typeof chunk.usage === "object" && chunk.usage.prompt_tokens !== undefined) {
268
+ if (
269
+ chunk.usage &&
270
+ typeof chunk.usage === "object" &&
271
+ (chunk.usage.prompt_tokens !== undefined || chunk.usage.input_tokens !== undefined)
272
+ ) {
234
273
  return normalizeUsage({
235
- prompt_tokens: chunk.usage.prompt_tokens,
236
- completion_tokens: chunk.usage.completion_tokens || 0,
274
+ prompt_tokens: chunk.usage.prompt_tokens ?? chunk.usage.input_tokens ?? 0,
275
+ completion_tokens: chunk.usage.completion_tokens ?? chunk.usage.output_tokens ?? 0,
237
276
  cached_tokens: chunk.usage.prompt_tokens_details?.cached_tokens,
238
277
  reasoning_tokens: chunk.usage.completion_tokens_details?.reasoning_tokens,
239
278
  });
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omniroute",
3
- "version": "3.0.7",
3
+ "version": "3.0.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omniroute",
9
- "version": "3.0.7",
9
+ "version": "3.0.8",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "workspaces": [
@@ -3096,9 +3096,9 @@
3096
3096
  }
3097
3097
  },
3098
3098
  "node_modules/@parcel/watcher/node_modules/picomatch": {
3099
- "version": "4.0.3",
3100
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
3101
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
3099
+ "version": "4.0.4",
3100
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
3101
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
3102
3102
  "license": "MIT",
3103
3103
  "engines": {
3104
3104
  "node": ">=12"
@@ -12842,9 +12842,9 @@
12842
12842
  }
12843
12843
  },
12844
12844
  "node_modules/lint-staged/node_modules/picomatch": {
12845
- "version": "4.0.3",
12846
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
12847
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
12845
+ "version": "4.0.4",
12846
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
12847
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
12848
12848
  "dev": true,
12849
12849
  "license": "MIT",
12850
12850
  "engines": {
@@ -15311,9 +15311,9 @@
15311
15311
  "license": "ISC"
15312
15312
  },
15313
15313
  "node_modules/picomatch": {
15314
- "version": "2.3.1",
15315
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
15316
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
15314
+ "version": "2.3.2",
15315
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
15316
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
15317
15317
  "license": "MIT",
15318
15318
  "engines": {
15319
15319
  "node": ">=8.6"
@@ -18225,9 +18225,9 @@
18225
18225
  }
18226
18226
  },
18227
18227
  "node_modules/tinyglobby/node_modules/picomatch": {
18228
- "version": "4.0.3",
18229
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
18230
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
18228
+ "version": "4.0.4",
18229
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
18230
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
18231
18231
  "dev": true,
18232
18232
  "license": "MIT",
18233
18233
  "engines": {
@@ -19373,9 +19373,9 @@
19373
19373
  }
19374
19374
  },
19375
19375
  "node_modules/vite/node_modules/picomatch": {
19376
- "version": "4.0.3",
19377
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
19378
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
19376
+ "version": "4.0.4",
19377
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
19378
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
19379
19379
  "dev": true,
19380
19380
  "license": "MIT",
19381
19381
  "engines": {
@@ -19468,9 +19468,9 @@
19468
19468
  }
19469
19469
  },
19470
19470
  "node_modules/vitest/node_modules/picomatch": {
19471
- "version": "4.0.3",
19472
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
19473
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
19471
+ "version": "4.0.4",
19472
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
19473
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
19474
19474
  "dev": true,
19475
19475
  "license": "MIT",
19476
19476
  "engines": {
package/app/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omniroute",
3
- "version": "3.0.7",
3
+ "version": "3.0.8",
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": {
@@ -27,21 +27,21 @@ export async function getCombos() {
27
27
  .map((row) => JSON.parse(row));
28
28
  }
29
29
 
30
- export async function getComboById(id) {
30
+ export async function getComboById(id: string) {
31
31
  const db = getDbInstance();
32
32
  const row = db.prepare("SELECT data FROM combos WHERE id = ?").get(id);
33
33
  const payload = getSerializedData(row);
34
34
  return payload ? JSON.parse(payload) : null;
35
35
  }
36
36
 
37
- export async function getComboByName(name) {
37
+ export async function getComboByName(name: string) {
38
38
  const db = getDbInstance();
39
39
  const row = db.prepare("SELECT data FROM combos WHERE name = ?").get(name);
40
40
  const payload = getSerializedData(row);
41
41
  return payload ? JSON.parse(payload) : null;
42
42
  }
43
43
 
44
- export async function createCombo(data) {
44
+ export async function createCombo(data: JsonRecord) {
45
45
  const db = getDbInstance();
46
46
  const now = new Date().toISOString();
47
47
 
@@ -63,7 +63,7 @@ export async function createCombo(data) {
63
63
  return combo;
64
64
  }
65
65
 
66
- export async function updateCombo(id, data) {
66
+ export async function updateCombo(id: string, data: JsonRecord) {
67
67
  const db = getDbInstance();
68
68
  const existing = db.prepare("SELECT data FROM combos WHERE id = ?").get(id);
69
69
  if (!existing) return null;
@@ -84,7 +84,7 @@ export async function updateCombo(id, data) {
84
84
  return merged;
85
85
  }
86
86
 
87
- export async function deleteCombo(id) {
87
+ export async function deleteCombo(id: string) {
88
88
  const db = getDbInstance();
89
89
  const result = db.prepare("DELETE FROM combos WHERE id = ?").run(id);
90
90
  if (result.changes === 0) return false;
@@ -247,7 +247,7 @@ export async function getModelAliases() {
247
247
  return result;
248
248
  }
249
249
 
250
- export async function setModelAlias(alias, model) {
250
+ export async function setModelAlias(alias: string, model: unknown) {
251
251
  const db = getDbInstance();
252
252
  db.prepare(
253
253
  "INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('modelAliases', ?, ?)"
@@ -255,7 +255,7 @@ export async function setModelAlias(alias, model) {
255
255
  backupDbFile("pre-write");
256
256
  }
257
257
 
258
- export async function deleteModelAlias(alias) {
258
+ export async function deleteModelAlias(alias: string) {
259
259
  const db = getDbInstance();
260
260
  db.prepare("DELETE FROM key_value WHERE namespace = 'modelAliases' AND key = ?").run(alias);
261
261
  backupDbFile("pre-write");
@@ -263,7 +263,7 @@ export async function deleteModelAlias(alias) {
263
263
 
264
264
  // ──────────────── MITM Alias ────────────────
265
265
 
266
- export async function getMitmAlias(toolName) {
266
+ export async function getMitmAlias(toolName?: string) {
267
267
  const db = getDbInstance();
268
268
  if (toolName) {
269
269
  const row = db
@@ -282,7 +282,7 @@ export async function getMitmAlias(toolName) {
282
282
  return result;
283
283
  }
284
284
 
285
- export async function setMitmAliasAll(toolName, mappings) {
285
+ export async function setMitmAliasAll(toolName: string, mappings: unknown) {
286
286
  const db = getDbInstance();
287
287
  db.prepare(
288
288
  "INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('mitmAlias', ?, ?)"
@@ -292,7 +292,7 @@ export async function setMitmAliasAll(toolName, mappings) {
292
292
 
293
293
  // ──────────────── Custom Models ────────────────
294
294
 
295
- export async function getCustomModels(providerId) {
295
+ export async function getCustomModels(providerId?: string) {
296
296
  const db = getDbInstance();
297
297
  if (providerId) {
298
298
  const row = db
@@ -342,7 +342,7 @@ export async function addCustomModel(
342
342
  const value = getKeyValue(row).value;
343
343
  const models = value ? JSON.parse(value) : [];
344
344
 
345
- const exists = models.find((m) => m.id === modelId);
345
+ const exists = models.find((m: JsonRecord) => m.id === modelId);
346
346
  if (exists) return exists;
347
347
 
348
348
  const model = {
@@ -430,7 +430,7 @@ export async function replaceCustomModels(
430
430
  return merged;
431
431
  }
432
432
 
433
- export async function removeCustomModel(providerId, modelId) {
433
+ export async function removeCustomModel(providerId: string, modelId: string) {
434
434
  const db = getDbInstance();
435
435
  const row = db
436
436
  .prepare("SELECT value FROM key_value WHERE namespace = 'customModels' AND key = ?")
@@ -441,7 +441,7 @@ export async function removeCustomModel(providerId, modelId) {
441
441
  if (!value) return false;
442
442
  const models = JSON.parse(value);
443
443
  const before = models.length;
444
- const filtered = models.filter((m) => m.id !== modelId);
444
+ const filtered = models.filter((m: JsonRecord) => m.id !== modelId);
445
445
 
446
446
  if (filtered.length === before) return false;
447
447
 
@@ -476,7 +476,7 @@ export async function updateCustomModel(
476
476
  if (!value) return null;
477
477
 
478
478
  const models = JSON.parse(value);
479
- const index = models.findIndex((m) => m.id === modelId);
479
+ const index = models.findIndex((m: JsonRecord) => m.id === modelId);
480
480
  if (index === -1) return null;
481
481
 
482
482
  const current = models[index];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omniroute",
3
- "version": "3.0.7",
3
+ "version": "3.0.8",
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": {