omniroute 2.7.1 → 2.7.3

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 (114) 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/settings/page_client-reference-manifest.js +1 -1
  27. package/app/.next/server/app/(dashboard)/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
  28. package/app/.next/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
  29. package/app/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
  30. package/app/.next/server/app/400/page_client-reference-manifest.js +1 -1
  31. package/app/.next/server/app/401/page_client-reference-manifest.js +1 -1
  32. package/app/.next/server/app/403/page_client-reference-manifest.js +1 -1
  33. package/app/.next/server/app/408/page_client-reference-manifest.js +1 -1
  34. package/app/.next/server/app/429/page_client-reference-manifest.js +1 -1
  35. package/app/.next/server/app/500/page_client-reference-manifest.js +1 -1
  36. package/app/.next/server/app/502/page_client-reference-manifest.js +1 -1
  37. package/app/.next/server/app/503/page_client-reference-manifest.js +1 -1
  38. package/app/.next/server/app/_global-error.html +2 -2
  39. package/app/.next/server/app/_global-error.rsc +1 -1
  40. package/app/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  41. package/app/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  42. package/app/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  43. package/app/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  44. package/app/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  45. package/app/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  46. package/app/.next/server/app/api/v1/api/chat/route.js +3 -3
  47. package/app/.next/server/app/api/v1/audio/speech/route.js +3 -3
  48. package/app/.next/server/app/api/v1/audio/transcriptions/route.js +6 -6
  49. package/app/.next/server/app/api/v1/chat/completions/route.js +7 -7
  50. package/app/.next/server/app/api/v1/completions/route.js +7 -7
  51. package/app/.next/server/app/api/v1/embeddings/route.js +5 -5
  52. package/app/.next/server/app/api/v1/images/generations/route.js +7 -7
  53. package/app/.next/server/app/api/v1/messages/route.js +3 -3
  54. package/app/.next/server/app/api/v1/moderations/route.js +3 -3
  55. package/app/.next/server/app/api/v1/music/generations/route.js +4 -4
  56. package/app/.next/server/app/api/v1/providers/[provider]/chat/completions/route.js +8 -8
  57. package/app/.next/server/app/api/v1/providers/[provider]/embeddings/route.js +5 -5
  58. package/app/.next/server/app/api/v1/providers/[provider]/images/generations/route.js +8 -8
  59. package/app/.next/server/app/api/v1/rerank/route.js +6 -6
  60. package/app/.next/server/app/api/v1/responses/route.js +3 -3
  61. package/app/.next/server/app/api/v1/search/route.js +6 -6
  62. package/app/.next/server/app/api/v1/videos/generations/route.js +6 -6
  63. package/app/.next/server/app/api/v1beta/models/[...path]/route.js +8 -8
  64. package/app/.next/server/app/callback/page_client-reference-manifest.js +1 -1
  65. package/app/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  66. package/app/.next/server/app/forbidden/page_client-reference-manifest.js +1 -1
  67. package/app/.next/server/app/forgot-password/page_client-reference-manifest.js +1 -1
  68. package/app/.next/server/app/landing/page_client-reference-manifest.js +1 -1
  69. package/app/.next/server/app/login/page_client-reference-manifest.js +1 -1
  70. package/app/.next/server/app/maintenance/page_client-reference-manifest.js +1 -1
  71. package/app/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  72. package/app/.next/server/app/page_client-reference-manifest.js +1 -1
  73. package/app/.next/server/app/privacy/page_client-reference-manifest.js +1 -1
  74. package/app/.next/server/app/status/page_client-reference-manifest.js +1 -1
  75. package/app/.next/server/app/terms/page_client-reference-manifest.js +1 -1
  76. package/app/.next/server/chunks/[root-of-the-server]__09c944b3._.js +1 -1
  77. package/app/.next/server/chunks/[root-of-the-server]__167585da._.js +1 -1
  78. package/app/.next/server/chunks/[root-of-the-server]__7d9b23e7._.js +1 -1
  79. package/app/.next/server/chunks/[root-of-the-server]__80e3bfc3._.js +1 -1
  80. package/app/.next/server/chunks/[root-of-the-server]__84e445b2._.js +2 -2
  81. package/app/.next/server/chunks/[root-of-the-server]__92cb0def._.js +2 -2
  82. package/app/.next/server/chunks/[root-of-the-server]__db2f9fe0._.js +1 -1
  83. package/app/.next/server/chunks/[root-of-the-server]__dc47ee64._.js +1 -1
  84. package/app/.next/server/chunks/_05c48915._.js +1 -1
  85. package/app/.next/server/chunks/_2115d8de._.js +1 -1
  86. package/app/.next/server/chunks/_3ac953eb._.js +1 -1
  87. package/app/.next/server/chunks/_4b8fd853._.js +1 -1
  88. package/app/.next/server/chunks/_68683848._.js +1 -1
  89. package/app/.next/server/chunks/_6f1b3c3f._.js +1 -1
  90. package/app/.next/server/chunks/_ee9b677b._.js +1 -1
  91. package/app/.next/server/chunks/_ffda39da._.js +10 -10
  92. package/app/.next/server/chunks/src_4787afd3._.js +1 -1
  93. package/app/.next/server/chunks/ssr/[root-of-the-server]__9affb65e._.js +1 -1
  94. package/app/.next/server/chunks/ssr/[root-of-the-server]__a6942102._.js +1 -1
  95. package/app/.next/server/chunks/ssr/_5a9cd299._.js +1 -1
  96. package/app/.next/server/pages/500.html +2 -2
  97. package/app/.next/server/server-reference-manifest.js +1 -1
  98. package/app/.next/server/server-reference-manifest.json +1 -1
  99. package/app/.next/static/chunks/{7db919b3e9b8f72c.js → 1d4f94c2f5112482.js} +1 -1
  100. package/app/.next/static/chunks/201cb349d9cc400f.css +1 -0
  101. package/app/.next/static/chunks/{4b1344f456abe7c3.js → 5609e4cc1aa751bb.js} +1 -1
  102. package/app/CHANGELOG.md +25 -0
  103. package/app/docs/openapi.yaml +1 -1
  104. package/app/package-lock.json +2 -2
  105. package/app/package.json +1 -1
  106. package/app/src/domain/quotaCache.ts +40 -1
  107. package/app/src/shared/components/RequestLoggerV2.tsx +5 -5
  108. package/app/src/sse/services/auth.ts +30 -5
  109. package/app/tests/unit/quota-policy-generalization.test.mjs +40 -0
  110. package/package.json +1 -1
  111. package/app/.next/static/chunks/85b7200badb8af05.css +0 -1
  112. /package/app/.next/static/{kvAWBEqvRUAvBVH0wpZhX → TaE7xCUJ3vzE5oXXL5DhE}/_buildManifest.js +0 -0
  113. /package/app/.next/static/{kvAWBEqvRUAvBVH0wpZhX → TaE7xCUJ3vzE5oXXL5DhE}/_clientMiddlewareManifest.json +0 -0
  114. /package/app/.next/static/{kvAWBEqvRUAvBVH0wpZhX → TaE7xCUJ3vzE5oXXL5DhE}/_ssgManifest.js +0 -0
package/app/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## [2.7.3] — 2026-03-18
8
+
9
+ > Sprint: Codex direct API quota fallback fix.
10
+
11
+ ### 🐛 Bug Fixes
12
+
13
+ - **fix(codex)**: Block weekly-exhausted accounts in direct API fallback (#440)
14
+ - `resolveQuotaWindow()` prefix matching: `"weekly"` now matches `"weekly (7d)"` cache keys
15
+ - `applyCodexWindowPolicy()` enforces `useWeekly`/`use5h` toggles correctly
16
+ - 4 new regression tests (766 total)
17
+
18
+ ---
19
+
20
+ ## [2.7.2] — 2026-03-18
21
+
22
+ > Sprint: Light mode UI contrast fixes.
23
+
24
+ ### 🐛 Bug Fixes
25
+
26
+ - **fix(logs)**: Fix light mode contrast in request logs filter buttons and combo badge (#378)
27
+ - Error/Success/Combo filter buttons now readable in light mode
28
+ - Combo row badge uses stronger violet in light mode
29
+
30
+ ---
31
+
7
32
  ## [2.7.1] — 2026-03-17
8
33
 
9
34
  > Sprint: Unified web search routing (POST /v1/search) with 5 providers + Next.js 16.1.7 security fixes (6 CVEs).
@@ -1,7 +1,7 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: OmniRoute API
4
- version: 2.7.1
4
+ version: 2.7.3
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,
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omniroute",
3
- "version": "2.7.1",
3
+ "version": "2.7.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omniroute",
9
- "version": "2.7.1",
9
+ "version": "2.7.3",
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": "2.7.1",
3
+ "version": "2.7.3",
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": {
@@ -101,6 +101,45 @@ function clampPercent(value: number): number {
101
101
  return Math.max(0, Math.min(100, value));
102
102
  }
103
103
 
104
+ function normalizeWindowKey(value: unknown): string {
105
+ if (typeof value !== "string") return "";
106
+ return value
107
+ .toLowerCase()
108
+ .replace(/[^a-z0-9]+/g, " ")
109
+ .trim();
110
+ }
111
+
112
+ function resolveQuotaWindow(
113
+ quotas: Record<string, QuotaInfo>,
114
+ windowName: string
115
+ ): QuotaInfo | null {
116
+ const direct = quotas[windowName];
117
+ if (direct) return direct;
118
+
119
+ const normalizedTarget = normalizeWindowKey(windowName);
120
+ if (!normalizedTarget) return null;
121
+
122
+ const prefixMatches: Array<{ key: string; quota: QuotaInfo }> = [];
123
+ for (const [key, quota] of Object.entries(quotas)) {
124
+ const normalizedKey = normalizeWindowKey(key);
125
+ if (!normalizedKey) continue;
126
+ if (normalizedKey === normalizedTarget) return quota;
127
+ // Support canonical selection of generic windows from labeled windows,
128
+ // e.g. "weekly" from "weekly (7d)" or "session" from "session (5h)".
129
+ if (normalizedKey.startsWith(`${normalizedTarget} `)) {
130
+ prefixMatches.push({ key, quota });
131
+ }
132
+ }
133
+
134
+ // Deterministic fallback: choose the lexicographically first matching key.
135
+ if (prefixMatches.length > 0) {
136
+ prefixMatches.sort((a, b) => a.key.localeCompare(b.key));
137
+ return prefixMatches[0].quota;
138
+ }
139
+
140
+ return null;
141
+ }
142
+
104
143
  function earliestResetAt(quotas: Record<string, QuotaInfo>): string | null {
105
144
  let earliest: string | null = null;
106
145
  let earliestMs = Infinity;
@@ -201,7 +240,7 @@ export function getQuotaWindowStatus(
201
240
 
202
241
  const now = Date.now();
203
242
 
204
- const window = entry.quotas[windowName];
243
+ const window = resolveQuotaWindow(entry.quotas, windowName);
205
244
  if (!window) return null;
206
245
 
207
246
  const remainingPercentage = clampPercent(window.remainingPercentage);
@@ -258,7 +258,7 @@ export default function RequestLoggerV2() {
258
258
  onClick={() => setRecording(!recording)}
259
259
  className={`flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium border transition-colors ${
260
260
  recording
261
- ? "bg-red-500/10 border-red-500/30 text-red-400"
261
+ ? "bg-red-500/10 border-red-500/30 text-red-700 dark:text-red-400"
262
262
  : "bg-bg-subtle border-border text-text-muted"
263
263
  }`}
264
264
  >
@@ -413,11 +413,11 @@ export default function RequestLoggerV2() {
413
413
  className={`flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium border transition-all ${
414
414
  activeFilter === f.key
415
415
  ? f.key === "error"
416
- ? "bg-red-500/20 text-red-400 border-red-500/40"
416
+ ? "bg-red-500/20 text-red-700 dark:text-red-400 border-red-500/40"
417
417
  : f.key === "ok"
418
- ? "bg-emerald-500/20 text-emerald-400 border-emerald-500/40"
418
+ ? "bg-emerald-500/20 text-emerald-700 dark:text-emerald-400 border-emerald-500/40"
419
419
  : f.key === "combo"
420
- ? "bg-violet-500/20 text-violet-300 border-violet-500/40"
420
+ ? "bg-violet-500/20 text-violet-700 dark:text-violet-300 border-violet-500/40"
421
421
  : "bg-primary text-white border-primary"
422
422
  : "bg-bg-subtle border-border text-text-muted hover:border-text-muted"
423
423
  }`}
@@ -635,7 +635,7 @@ export default function RequestLoggerV2() {
635
635
  {visibleColumns.combo && (
636
636
  <td className="px-3 py-2">
637
637
  {log.comboName ? (
638
- <span className="inline-block px-2 py-0.5 rounded-full text-[9px] font-bold bg-violet-500/20 text-violet-700 dark:text-violet-300 border border-violet-500/30">
638
+ <span className="inline-block px-2 py-0.5 rounded-full text-[9px] font-bold bg-violet-500/20 text-violet-800 dark:text-violet-300 border border-violet-500/40">
639
639
  {log.comboName}
640
640
  </span>
641
641
  ) : (
@@ -132,12 +132,38 @@ function normalizeWindowName(windowName: unknown): string | null {
132
132
  return normalized.length > 0 ? normalized : null;
133
133
  }
134
134
 
135
- function getLegacyCodexWindows(providerSpecificData: JsonRecord): string[] {
135
+ function uniqueWindows(windows: string[]): string[] {
136
+ return [...new Set(windows)];
137
+ }
138
+
139
+ function normalizeCodexWindowName(windowName: unknown): string | null {
140
+ if (typeof windowName !== "string") return null;
141
+ const normalized = windowName.trim().toLowerCase();
142
+ if (normalized === "session (5h)" || normalized === "5h" || normalized === "five_hour") {
143
+ return "session";
144
+ }
145
+ if (normalized === "weekly (7d)" || normalized === "7d" || normalized === "seven_day") {
146
+ return "weekly";
147
+ }
148
+ return normalized;
149
+ }
150
+
151
+ function applyCodexWindowPolicy(rawWindows: string[], providerSpecificData: JsonRecord): string[] {
136
152
  const codexPolicy = getCodexLimitPolicy(providerSpecificData);
137
- const windows: string[] = [];
153
+ const normalizedRaw = rawWindows.map(normalizeCodexWindowName).filter(Boolean) as string[];
154
+
155
+ // Preserve explicitly configured custom windows, but enforce canonical Codex windows
156
+ // from toggles so weekly exhaustion is never skipped when useWeekly=true.
157
+ let windows = [...normalizedRaw];
158
+ windows = windows.filter((windowName) => {
159
+ if (windowName === "session") return codexPolicy.use5h;
160
+ if (windowName === "weekly") return codexPolicy.useWeekly;
161
+ return true;
162
+ });
138
163
  if (codexPolicy.use5h) windows.push("session");
139
164
  if (codexPolicy.useWeekly) windows.push("weekly");
140
- return windows;
165
+
166
+ return uniqueWindows(windows);
141
167
  }
142
168
 
143
169
  export function resolveQuotaLimitPolicy(
@@ -149,8 +175,7 @@ export function resolveQuotaLimitPolicy(
149
175
  const windows = rawWindows.map(normalizeWindowName).filter(Boolean) as string[];
150
176
 
151
177
  if (provider === "codex") {
152
- const fallbackWindows = getLegacyCodexWindows(providerSpecificData);
153
- const defaultWindows = windows.length > 0 ? windows : fallbackWindows;
178
+ const defaultWindows = applyCodexWindowPolicy(windows, providerSpecificData);
154
179
  const enabled = toBooleanOrDefault(rawPolicy.enabled, defaultWindows.length > 0);
155
180
 
156
181
  return {
@@ -21,6 +21,26 @@ test("resolveQuotaLimitPolicy keeps codex legacy defaults when generic policy is
21
21
  assert.equal(policy.thresholdPercent, 90);
22
22
  });
23
23
 
24
+ test("resolveQuotaLimitPolicy enforces codex weekly window when weekly toggle is enabled", () => {
25
+ const policy = auth.resolveQuotaLimitPolicy("codex", {
26
+ codexLimitPolicy: { use5h: true, useWeekly: true },
27
+ limitPolicy: { enabled: true, windows: ["session"] },
28
+ });
29
+
30
+ assert.equal(policy.enabled, true);
31
+ assert.deepEqual(policy.windows.sort(), ["session", "weekly"]);
32
+ });
33
+
34
+ test("resolveQuotaLimitPolicy removes codex weekly window when weekly toggle is disabled", () => {
35
+ const policy = auth.resolveQuotaLimitPolicy("codex", {
36
+ codexLimitPolicy: { use5h: true, useWeekly: false },
37
+ limitPolicy: { enabled: true, windows: ["session", "weekly"] },
38
+ });
39
+
40
+ assert.equal(policy.enabled, true);
41
+ assert.deepEqual(policy.windows, ["session"]);
42
+ });
43
+
24
44
  test("resolveQuotaLimitPolicy disables non-codex policy by default", () => {
25
45
  const policy = auth.resolveQuotaLimitPolicy("openai", {});
26
46
  assert.equal(policy.enabled, false);
@@ -60,6 +80,26 @@ test("evaluateQuotaLimitPolicy blocks when configured window reaches threshold",
60
80
  assert.equal(result.resetAt, resetAt);
61
81
  });
62
82
 
83
+ test("evaluateQuotaLimitPolicy matches canonical weekly window against labeled cache keys", () => {
84
+ const resetAt = new Date(Date.now() + 60_000).toISOString();
85
+ quotaCache.setQuotaCache("conn-policy-weekly-label", "codex", {
86
+ "weekly (7d)": { remainingPercentage: 0, resetAt },
87
+ });
88
+
89
+ const result = auth.evaluateQuotaLimitPolicy(
90
+ "codex",
91
+ buildConnection("conn-policy-weekly-label", {
92
+ codexLimitPolicy: { use5h: true, useWeekly: true },
93
+ limitPolicy: { enabled: true, windows: ["weekly"] },
94
+ })
95
+ );
96
+
97
+ assert.equal(result.blocked, true);
98
+ assert.equal(result.reasons.length, 1);
99
+ assert.match(result.reasons[0], /weekly usage/i);
100
+ assert.equal(result.resetAt, resetAt);
101
+ });
102
+
63
103
  test("evaluateQuotaLimitPolicy does not block when no quota data exists", () => {
64
104
  const result = auth.evaluateQuotaLimitPolicy(
65
105
  "openai",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omniroute",
3
- "version": "2.7.1",
3
+ "version": "2.7.3",
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": {