@blockrun/clawrouter 0.12.261 → 0.12.263

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/dist/cli.js CHANGED
@@ -32632,7 +32632,7 @@ var init_spend_control = __esm({
32632
32632
  }
32633
32633
  }
32634
32634
  if (this.limits.hourly !== void 0) {
32635
- const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2);
32635
+ const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2, now2);
32636
32636
  const remaining = this.limits.hourly - hourlySpent;
32637
32637
  if (estimatedCost2 > remaining) {
32638
32638
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - HOUR_MS);
@@ -32647,7 +32647,7 @@ var init_spend_control = __esm({
32647
32647
  }
32648
32648
  }
32649
32649
  if (this.limits.daily !== void 0) {
32650
- const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2);
32650
+ const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2, now2);
32651
32651
  const remaining = this.limits.daily - dailySpent;
32652
32652
  if (estimatedCost2 > remaining) {
32653
32653
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - DAY_MS);
@@ -32744,17 +32744,30 @@ var init_spend_control = __esm({
32744
32744
  }
32745
32745
  }
32746
32746
  }
32747
- getSpendingInWindow(from15, to) {
32747
+ // `now` is the single clock reading the caller already took to build the
32748
+ // window; it must be passed in, not re-read here. In-flight reservations are
32749
+ // "now" holds, so they count only against a window that reaches the present
32750
+ // (`to >= now`). The bug this guards against: reading the clock a SECOND time
32751
+ // inside this method (the old `to >= this.now()`) could land a millisecond
32752
+ // after the caller's `now`, flip the guard false, and silently drop the
32753
+ // pending total from the hourly/daily check — letting two concurrent payments
32754
+ // both clear the same remaining budget. Threading the caller's `now` keeps the
32755
+ // guard meaningful (a genuinely historical window with `to < now` still
32756
+ // excludes live holds) without a second, racing read. Every existing test
32757
+ // injects a frozen clock (`now: () => clock`), so the two reads always matched
32758
+ // and the sub-ms window went uncaught; see the live-clock test in
32759
+ // spend-control.test.ts.
32760
+ getSpendingInWindow(from15, to, now2) {
32748
32761
  const recorded = this.history.filter((r2) => r2.timestamp >= from15 && r2.timestamp <= to).reduce((sum, r2) => sum + r2.amount, 0);
32749
- return recorded + (to >= this.now() ? this.pendingTotal() : 0);
32762
+ return recorded + (to >= now2 ? this.pendingTotal() : 0);
32750
32763
  }
32751
32764
  getSpending(window2) {
32752
32765
  const now2 = this.now();
32753
32766
  switch (window2) {
32754
32767
  case "hourly":
32755
- return this.getSpendingInWindow(now2 - HOUR_MS, now2);
32768
+ return this.getSpendingInWindow(now2 - HOUR_MS, now2, now2);
32756
32769
  case "daily":
32757
- return this.getSpendingInWindow(now2 - DAY_MS, now2);
32770
+ return this.getSpendingInWindow(now2 - DAY_MS, now2, now2);
32758
32771
  case "session":
32759
32772
  return this.sessionSpent + this.pendingTotal();
32760
32773
  }
@@ -32766,8 +32779,8 @@ var init_spend_control = __esm({
32766
32779
  }
32767
32780
  getStatus() {
32768
32781
  const now2 = this.now();
32769
- const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2);
32770
- const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2);
32782
+ const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2, now2);
32783
+ const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2, now2);
32771
32784
  return {
32772
32785
  limits: cloneLimits(this.limits),
32773
32786
  spending: {
@@ -37672,7 +37685,9 @@ var init_top_models = __esm({
37672
37685
  "openai/gpt-5.4-nano",
37673
37686
  "openai/gpt-5.3-codex",
37674
37687
  "google/gemini-3.1-pro",
37688
+ "google/gemini-3.6-flash",
37675
37689
  "google/gemini-3.5-flash",
37690
+ "google/gemini-3.5-flash-lite",
37676
37691
  "google/gemini-3.1-flash-lite",
37677
37692
  "google/gemini-3-flash-preview",
37678
37693
  "xai/grok-4.5",
@@ -37685,11 +37700,13 @@ var init_top_models = __esm({
37685
37700
  "zai/glm-5-turbo",
37686
37701
  "zai/glm-5",
37687
37702
  "xiaomi/mimo-v2.5",
37703
+ "xiaomi/mimo-v2.5-pro",
37688
37704
  "minimax/minimax-m3",
37689
37705
  "minimax/minimax-m2.7",
37690
37706
  "moonshot/kimi-k3",
37691
37707
  "qwen/qwen3.7-max",
37692
37708
  "qwen/qwen3.8-flash",
37709
+ "tencent/hy3",
37693
37710
  "deepseek/deepseek-v4-flash-vision-exp",
37694
37711
  "deepseek/deepseek-v4-pro",
37695
37712
  "deepseek/deepseek-chat",
@@ -39916,6 +39933,13 @@ var init_version4 = __esm({
39916
39933
  import { readdir, unlink } from "fs/promises";
39917
39934
  import { join as join5 } from "path";
39918
39935
  import { homedir as homedir3 } from "os";
39936
+ function resolveStatsDays(raw) {
39937
+ const parsed = parseInt(raw ?? "", 10);
39938
+ if (!Number.isFinite(parsed) || parsed <= 0) {
39939
+ return DEFAULT_STATS_DAYS;
39940
+ }
39941
+ return Math.min(parsed, MAX_STATS_DAYS);
39942
+ }
39919
39943
  async function parseLogFile(filePath) {
39920
39944
  try {
39921
39945
  const content = await readTextFile(filePath);
@@ -40101,7 +40125,8 @@ function formatStatsAscii(stats) {
40101
40125
  lines.push("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
40102
40126
  return lines.join("\n");
40103
40127
  }
40104
- async function formatRecentLogs(days = 1) {
40128
+ async function formatRecentLogs(requestedDays = 1) {
40129
+ const days = Number.isFinite(requestedDays) && requestedDays > 0 ? Math.floor(requestedDays) : 1;
40105
40130
  const logFiles = await getLogFiles();
40106
40131
  const filesToRead = logFiles.slice(0, days);
40107
40132
  const allEntries = [];
@@ -40157,13 +40182,15 @@ async function clearStats() {
40157
40182
  return { deletedFiles: 0 };
40158
40183
  }
40159
40184
  }
40160
- var LOG_DIR2;
40185
+ var LOG_DIR2, DEFAULT_STATS_DAYS, MAX_STATS_DAYS;
40161
40186
  var init_stats = __esm({
40162
40187
  "src/stats.ts"() {
40163
40188
  "use strict";
40164
40189
  init_fs_read();
40165
40190
  init_version4();
40166
40191
  LOG_DIR2 = join5(homedir3(), ".openclaw", "blockrun", "logs");
40192
+ DEFAULT_STATS_DAYS = 7;
40193
+ MAX_STATS_DAYS = 30;
40167
40194
  }
40168
40195
  });
40169
40196
 
@@ -91082,8 +91109,8 @@ async function startProxy(options) {
91082
91109
  if (req.url === "/stats" || req.url?.startsWith("/stats?")) {
91083
91110
  try {
91084
91111
  const url2 = new URL(req.url, "http://localhost");
91085
- const days = parseInt(url2.searchParams.get("days") || "7", 10);
91086
- const stats = await getStats(Math.min(days, 30));
91112
+ const days = resolveStatsDays(url2.searchParams.get("days"));
91113
+ const stats = await getStats(days);
91087
91114
  res.writeHead(200, {
91088
91115
  "Content-Type": "application/json",
91089
91116
  "Cache-Control": "no-cache"
package/dist/index.d.ts CHANGED
@@ -1467,12 +1467,6 @@ declare function fetchWithRetry(fetchFn: (url: string, init?: RequestInit) => Pr
1467
1467
  */
1468
1468
  declare function isRetryable(errorOrResponse: Error | Response, config?: Partial<RetryConfig>): boolean;
1469
1469
 
1470
- /**
1471
- * Usage Statistics Aggregator
1472
- *
1473
- * Reads usage log files and aggregates statistics for terminal display.
1474
- * Supports filtering by date range and provides multiple aggregation views.
1475
- */
1476
1470
  type DailyStats = {
1477
1471
  date: string;
1478
1472
  totalRequests: number;
package/dist/index.js CHANGED
@@ -75,7 +75,9 @@ var init_top_models = __esm({
75
75
  "openai/gpt-5.4-nano",
76
76
  "openai/gpt-5.3-codex",
77
77
  "google/gemini-3.1-pro",
78
+ "google/gemini-3.6-flash",
78
79
  "google/gemini-3.5-flash",
80
+ "google/gemini-3.5-flash-lite",
79
81
  "google/gemini-3.1-flash-lite",
80
82
  "google/gemini-3-flash-preview",
81
83
  "xai/grok-4.5",
@@ -88,11 +90,13 @@ var init_top_models = __esm({
88
90
  "zai/glm-5-turbo",
89
91
  "zai/glm-5",
90
92
  "xiaomi/mimo-v2.5",
93
+ "xiaomi/mimo-v2.5-pro",
91
94
  "minimax/minimax-m3",
92
95
  "minimax/minimax-m2.7",
93
96
  "moonshot/kimi-k3",
94
97
  "qwen/qwen3.7-max",
95
98
  "qwen/qwen3.8-flash",
99
+ "tencent/hy3",
96
100
  "deepseek/deepseek-v4-flash-vision-exp",
97
101
  "deepseek/deepseek-v4-pro",
98
102
  "deepseek/deepseek-chat",
@@ -34764,7 +34768,7 @@ var init_spend_control = __esm({
34764
34768
  }
34765
34769
  }
34766
34770
  if (this.limits.hourly !== void 0) {
34767
- const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2);
34771
+ const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2, now2);
34768
34772
  const remaining = this.limits.hourly - hourlySpent;
34769
34773
  if (estimatedCost2 > remaining) {
34770
34774
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - HOUR_MS);
@@ -34779,7 +34783,7 @@ var init_spend_control = __esm({
34779
34783
  }
34780
34784
  }
34781
34785
  if (this.limits.daily !== void 0) {
34782
- const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2);
34786
+ const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2, now2);
34783
34787
  const remaining = this.limits.daily - dailySpent;
34784
34788
  if (estimatedCost2 > remaining) {
34785
34789
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - DAY_MS);
@@ -34876,17 +34880,30 @@ var init_spend_control = __esm({
34876
34880
  }
34877
34881
  }
34878
34882
  }
34879
- getSpendingInWindow(from15, to) {
34883
+ // `now` is the single clock reading the caller already took to build the
34884
+ // window; it must be passed in, not re-read here. In-flight reservations are
34885
+ // "now" holds, so they count only against a window that reaches the present
34886
+ // (`to >= now`). The bug this guards against: reading the clock a SECOND time
34887
+ // inside this method (the old `to >= this.now()`) could land a millisecond
34888
+ // after the caller's `now`, flip the guard false, and silently drop the
34889
+ // pending total from the hourly/daily check — letting two concurrent payments
34890
+ // both clear the same remaining budget. Threading the caller's `now` keeps the
34891
+ // guard meaningful (a genuinely historical window with `to < now` still
34892
+ // excludes live holds) without a second, racing read. Every existing test
34893
+ // injects a frozen clock (`now: () => clock`), so the two reads always matched
34894
+ // and the sub-ms window went uncaught; see the live-clock test in
34895
+ // spend-control.test.ts.
34896
+ getSpendingInWindow(from15, to, now2) {
34880
34897
  const recorded = this.history.filter((r2) => r2.timestamp >= from15 && r2.timestamp <= to).reduce((sum, r2) => sum + r2.amount, 0);
34881
- return recorded + (to >= this.now() ? this.pendingTotal() : 0);
34898
+ return recorded + (to >= now2 ? this.pendingTotal() : 0);
34882
34899
  }
34883
34900
  getSpending(window2) {
34884
34901
  const now2 = this.now();
34885
34902
  switch (window2) {
34886
34903
  case "hourly":
34887
- return this.getSpendingInWindow(now2 - HOUR_MS, now2);
34904
+ return this.getSpendingInWindow(now2 - HOUR_MS, now2, now2);
34888
34905
  case "daily":
34889
- return this.getSpendingInWindow(now2 - DAY_MS, now2);
34906
+ return this.getSpendingInWindow(now2 - DAY_MS, now2, now2);
34890
34907
  case "session":
34891
34908
  return this.sessionSpent + this.pendingTotal();
34892
34909
  }
@@ -34898,8 +34915,8 @@ var init_spend_control = __esm({
34898
34915
  }
34899
34916
  getStatus() {
34900
34917
  const now2 = this.now();
34901
- const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2);
34902
- const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2);
34918
+ const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2, now2);
34919
+ const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2, now2);
34903
34920
  return {
34904
34921
  limits: cloneLimits(this.limits),
34905
34922
  spending: {
@@ -39831,6 +39848,13 @@ var init_version4 = __esm({
39831
39848
  import { readdir, unlink } from "fs/promises";
39832
39849
  import { join as join5 } from "path";
39833
39850
  import { homedir as homedir3 } from "os";
39851
+ function resolveStatsDays(raw) {
39852
+ const parsed = parseInt(raw ?? "", 10);
39853
+ if (!Number.isFinite(parsed) || parsed <= 0) {
39854
+ return DEFAULT_STATS_DAYS;
39855
+ }
39856
+ return Math.min(parsed, MAX_STATS_DAYS);
39857
+ }
39834
39858
  async function parseLogFile(filePath) {
39835
39859
  try {
39836
39860
  const content = await readTextFile(filePath);
@@ -40026,13 +40050,15 @@ async function clearStats() {
40026
40050
  return { deletedFiles: 0 };
40027
40051
  }
40028
40052
  }
40029
- var LOG_DIR2;
40053
+ var LOG_DIR2, DEFAULT_STATS_DAYS, MAX_STATS_DAYS;
40030
40054
  var init_stats = __esm({
40031
40055
  "src/stats.ts"() {
40032
40056
  "use strict";
40033
40057
  init_fs_read();
40034
40058
  init_version4();
40035
40059
  LOG_DIR2 = join5(homedir3(), ".openclaw", "blockrun", "logs");
40060
+ DEFAULT_STATS_DAYS = 7;
40061
+ MAX_STATS_DAYS = 30;
40036
40062
  }
40037
40063
  });
40038
40064
 
@@ -90902,8 +90928,8 @@ async function startProxy(options) {
90902
90928
  if (req.url === "/stats" || req.url?.startsWith("/stats?")) {
90903
90929
  try {
90904
90930
  const url2 = new URL(req.url, "http://localhost");
90905
- const days = parseInt(url2.searchParams.get("days") || "7", 10);
90906
- const stats = await getStats(Math.min(days, 30));
90931
+ const days = resolveStatsDays(url2.searchParams.get("days"));
90932
+ const stats = await getStats(days);
90907
90933
  res.writeHead(200, {
90908
90934
  "Content-Type": "application/json",
90909
90935
  "Cache-Control": "no-cache"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blockrun/clawrouter",
3
- "version": "0.12.261",
4
- "description": "Smart LLM router — save 84% on inference costs. 76 models (7 free), one wallet, x402 micropayments.",
3
+ "version": "0.12.263",
4
+ "description": "Smart LLM router \u2014 save 84% on inference costs. 76 models (7 free), one wallet, x402 micropayments.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -21,7 +21,9 @@
21
21
  "openai/gpt-5.4-nano",
22
22
  "openai/gpt-5.3-codex",
23
23
  "google/gemini-3.1-pro",
24
+ "google/gemini-3.6-flash",
24
25
  "google/gemini-3.5-flash",
26
+ "google/gemini-3.5-flash-lite",
25
27
  "google/gemini-3.1-flash-lite",
26
28
  "google/gemini-3-flash-preview",
27
29
  "xai/grok-4.5",
@@ -34,11 +36,13 @@
34
36
  "zai/glm-5-turbo",
35
37
  "zai/glm-5",
36
38
  "xiaomi/mimo-v2.5",
39
+ "xiaomi/mimo-v2.5-pro",
37
40
  "minimax/minimax-m3",
38
41
  "minimax/minimax-m2.7",
39
42
  "moonshot/kimi-k3",
40
43
  "qwen/qwen3.7-max",
41
44
  "qwen/qwen3.8-flash",
45
+ "tencent/hy3",
42
46
  "deepseek/deepseek-v4-flash-vision-exp",
43
47
  "deepseek/deepseek-v4-pro",
44
48
  "deepseek/deepseek-chat",