@blockrun/clawrouter 0.12.262 → 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: {
@@ -39920,6 +39933,13 @@ var init_version4 = __esm({
39920
39933
  import { readdir, unlink } from "fs/promises";
39921
39934
  import { join as join5 } from "path";
39922
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
+ }
39923
39943
  async function parseLogFile(filePath) {
39924
39944
  try {
39925
39945
  const content = await readTextFile(filePath);
@@ -40105,7 +40125,8 @@ function formatStatsAscii(stats) {
40105
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");
40106
40126
  return lines.join("\n");
40107
40127
  }
40108
- async function formatRecentLogs(days = 1) {
40128
+ async function formatRecentLogs(requestedDays = 1) {
40129
+ const days = Number.isFinite(requestedDays) && requestedDays > 0 ? Math.floor(requestedDays) : 1;
40109
40130
  const logFiles = await getLogFiles();
40110
40131
  const filesToRead = logFiles.slice(0, days);
40111
40132
  const allEntries = [];
@@ -40161,13 +40182,15 @@ async function clearStats() {
40161
40182
  return { deletedFiles: 0 };
40162
40183
  }
40163
40184
  }
40164
- var LOG_DIR2;
40185
+ var LOG_DIR2, DEFAULT_STATS_DAYS, MAX_STATS_DAYS;
40165
40186
  var init_stats = __esm({
40166
40187
  "src/stats.ts"() {
40167
40188
  "use strict";
40168
40189
  init_fs_read();
40169
40190
  init_version4();
40170
40191
  LOG_DIR2 = join5(homedir3(), ".openclaw", "blockrun", "logs");
40192
+ DEFAULT_STATS_DAYS = 7;
40193
+ MAX_STATS_DAYS = 30;
40171
40194
  }
40172
40195
  });
40173
40196
 
@@ -91086,8 +91109,8 @@ async function startProxy(options) {
91086
91109
  if (req.url === "/stats" || req.url?.startsWith("/stats?")) {
91087
91110
  try {
91088
91111
  const url2 = new URL(req.url, "http://localhost");
91089
- const days = parseInt(url2.searchParams.get("days") || "7", 10);
91090
- const stats = await getStats(Math.min(days, 30));
91112
+ const days = resolveStatsDays(url2.searchParams.get("days"));
91113
+ const stats = await getStats(days);
91091
91114
  res.writeHead(200, {
91092
91115
  "Content-Type": "application/json",
91093
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
@@ -34768,7 +34768,7 @@ var init_spend_control = __esm({
34768
34768
  }
34769
34769
  }
34770
34770
  if (this.limits.hourly !== void 0) {
34771
- const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2);
34771
+ const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2, now2);
34772
34772
  const remaining = this.limits.hourly - hourlySpent;
34773
34773
  if (estimatedCost2 > remaining) {
34774
34774
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - HOUR_MS);
@@ -34783,7 +34783,7 @@ var init_spend_control = __esm({
34783
34783
  }
34784
34784
  }
34785
34785
  if (this.limits.daily !== void 0) {
34786
- const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2);
34786
+ const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2, now2);
34787
34787
  const remaining = this.limits.daily - dailySpent;
34788
34788
  if (estimatedCost2 > remaining) {
34789
34789
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - DAY_MS);
@@ -34880,17 +34880,30 @@ var init_spend_control = __esm({
34880
34880
  }
34881
34881
  }
34882
34882
  }
34883
- 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) {
34884
34897
  const recorded = this.history.filter((r2) => r2.timestamp >= from15 && r2.timestamp <= to).reduce((sum, r2) => sum + r2.amount, 0);
34885
- return recorded + (to >= this.now() ? this.pendingTotal() : 0);
34898
+ return recorded + (to >= now2 ? this.pendingTotal() : 0);
34886
34899
  }
34887
34900
  getSpending(window2) {
34888
34901
  const now2 = this.now();
34889
34902
  switch (window2) {
34890
34903
  case "hourly":
34891
- return this.getSpendingInWindow(now2 - HOUR_MS, now2);
34904
+ return this.getSpendingInWindow(now2 - HOUR_MS, now2, now2);
34892
34905
  case "daily":
34893
- return this.getSpendingInWindow(now2 - DAY_MS, now2);
34906
+ return this.getSpendingInWindow(now2 - DAY_MS, now2, now2);
34894
34907
  case "session":
34895
34908
  return this.sessionSpent + this.pendingTotal();
34896
34909
  }
@@ -34902,8 +34915,8 @@ var init_spend_control = __esm({
34902
34915
  }
34903
34916
  getStatus() {
34904
34917
  const now2 = this.now();
34905
- const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2);
34906
- 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);
34907
34920
  return {
34908
34921
  limits: cloneLimits(this.limits),
34909
34922
  spending: {
@@ -39835,6 +39848,13 @@ var init_version4 = __esm({
39835
39848
  import { readdir, unlink } from "fs/promises";
39836
39849
  import { join as join5 } from "path";
39837
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
+ }
39838
39858
  async function parseLogFile(filePath) {
39839
39859
  try {
39840
39860
  const content = await readTextFile(filePath);
@@ -40030,13 +40050,15 @@ async function clearStats() {
40030
40050
  return { deletedFiles: 0 };
40031
40051
  }
40032
40052
  }
40033
- var LOG_DIR2;
40053
+ var LOG_DIR2, DEFAULT_STATS_DAYS, MAX_STATS_DAYS;
40034
40054
  var init_stats = __esm({
40035
40055
  "src/stats.ts"() {
40036
40056
  "use strict";
40037
40057
  init_fs_read();
40038
40058
  init_version4();
40039
40059
  LOG_DIR2 = join5(homedir3(), ".openclaw", "blockrun", "logs");
40060
+ DEFAULT_STATS_DAYS = 7;
40061
+ MAX_STATS_DAYS = 30;
40040
40062
  }
40041
40063
  });
40042
40064
 
@@ -90906,8 +90928,8 @@ async function startProxy(options) {
90906
90928
  if (req.url === "/stats" || req.url?.startsWith("/stats?")) {
90907
90929
  try {
90908
90930
  const url2 = new URL(req.url, "http://localhost");
90909
- const days = parseInt(url2.searchParams.get("days") || "7", 10);
90910
- const stats = await getStats(Math.min(days, 30));
90931
+ const days = resolveStatsDays(url2.searchParams.get("days"));
90932
+ const stats = await getStats(days);
90911
90933
  res.writeHead(200, {
90912
90934
  "Content-Type": "application/json",
90913
90935
  "Cache-Control": "no-cache"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blockrun/clawrouter",
3
- "version": "0.12.262",
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",