@blockrun/clawrouter 0.12.95 → 0.12.97

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 (282) hide show
  1. package/dist/auth.d.ts +82 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +388 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/balance.d.ts +97 -0
  6. package/dist/balance.d.ts.map +1 -0
  7. package/dist/balance.js +157 -0
  8. package/dist/balance.js.map +1 -0
  9. package/dist/cli.d.ts +15 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +256 -54994
  12. package/dist/cli.js.map +1 -1
  13. package/dist/compression/codebook.d.ts +24 -0
  14. package/dist/compression/codebook.d.ts.map +1 -0
  15. package/dist/compression/codebook.js +119 -0
  16. package/dist/compression/codebook.js.map +1 -0
  17. package/dist/compression/index.d.ts +33 -0
  18. package/dist/compression/index.d.ts.map +1 -0
  19. package/dist/compression/index.js +246 -0
  20. package/dist/compression/index.js.map +1 -0
  21. package/dist/compression/layers/deduplication.d.ts +28 -0
  22. package/dist/compression/layers/deduplication.d.ts.map +1 -0
  23. package/dist/compression/layers/deduplication.js +101 -0
  24. package/dist/compression/layers/deduplication.js.map +1 -0
  25. package/dist/compression/layers/dictionary.d.ts +21 -0
  26. package/dist/compression/layers/dictionary.d.ts.map +1 -0
  27. package/dist/compression/layers/dictionary.js +73 -0
  28. package/dist/compression/layers/dictionary.js.map +1 -0
  29. package/dist/compression/layers/dynamic-codebook.d.ts +26 -0
  30. package/dist/compression/layers/dynamic-codebook.d.ts.map +1 -0
  31. package/dist/compression/layers/dynamic-codebook.js +149 -0
  32. package/dist/compression/layers/dynamic-codebook.js.map +1 -0
  33. package/dist/compression/layers/json-compact.d.ts +23 -0
  34. package/dist/compression/layers/json-compact.d.ts.map +1 -0
  35. package/dist/compression/layers/json-compact.js +79 -0
  36. package/dist/compression/layers/json-compact.js.map +1 -0
  37. package/dist/compression/layers/observation.d.ts +21 -0
  38. package/dist/compression/layers/observation.d.ts.map +1 -0
  39. package/dist/compression/layers/observation.js +130 -0
  40. package/dist/compression/layers/observation.js.map +1 -0
  41. package/dist/compression/layers/paths.d.ts +24 -0
  42. package/dist/compression/layers/paths.d.ts.map +1 -0
  43. package/dist/compression/layers/paths.js +110 -0
  44. package/dist/compression/layers/paths.js.map +1 -0
  45. package/dist/compression/layers/whitespace.d.ts +27 -0
  46. package/dist/compression/layers/whitespace.d.ts.map +1 -0
  47. package/dist/compression/layers/whitespace.js +60 -0
  48. package/dist/compression/layers/whitespace.js.map +1 -0
  49. package/dist/compression/types.d.ts +84 -0
  50. package/dist/compression/types.d.ts.map +1 -0
  51. package/dist/compression/types.js +27 -0
  52. package/dist/compression/types.js.map +1 -0
  53. package/dist/config.d.ts +12 -0
  54. package/dist/config.d.ts.map +1 -0
  55. package/dist/config.js +22 -0
  56. package/dist/config.js.map +1 -0
  57. package/dist/dedup.d.ts +34 -0
  58. package/dist/dedup.d.ts.map +1 -0
  59. package/dist/dedup.js +150 -0
  60. package/dist/dedup.js.map +1 -0
  61. package/dist/doctor.d.ts +8 -0
  62. package/dist/doctor.d.ts.map +1 -0
  63. package/dist/doctor.js +375 -0
  64. package/dist/doctor.js.map +1 -0
  65. package/dist/error-classification.test.d.ts +2 -0
  66. package/dist/error-classification.test.d.ts.map +1 -0
  67. package/dist/error-classification.test.js +59 -0
  68. package/dist/error-classification.test.js.map +1 -0
  69. package/dist/errors.d.ts +54 -0
  70. package/dist/errors.d.ts.map +1 -0
  71. package/dist/errors.js +85 -0
  72. package/dist/errors.js.map +1 -0
  73. package/dist/exclude-models.d.ts +28 -0
  74. package/dist/exclude-models.d.ts.map +1 -0
  75. package/dist/exclude-models.integration.test.d.ts +2 -0
  76. package/dist/exclude-models.integration.test.d.ts.map +1 -0
  77. package/dist/exclude-models.integration.test.js +46 -0
  78. package/dist/exclude-models.integration.test.js.map +1 -0
  79. package/dist/exclude-models.js +70 -0
  80. package/dist/exclude-models.js.map +1 -0
  81. package/dist/exclude-models.test.d.ts +5 -0
  82. package/dist/exclude-models.test.d.ts.map +1 -0
  83. package/dist/exclude-models.test.js +95 -0
  84. package/dist/exclude-models.test.js.map +1 -0
  85. package/dist/fs-read.d.ts +11 -0
  86. package/dist/fs-read.d.ts.map +1 -0
  87. package/dist/fs-read.js +47 -0
  88. package/dist/fs-read.js.map +1 -0
  89. package/dist/index.d.ts +34 -1368
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +1071 -56194
  92. package/dist/index.js.map +1 -1
  93. package/dist/journal.d.ts +67 -0
  94. package/dist/journal.d.ts.map +1 -0
  95. package/dist/journal.js +184 -0
  96. package/dist/journal.js.map +1 -0
  97. package/dist/journal.test.d.ts +2 -0
  98. package/dist/journal.test.d.ts.map +1 -0
  99. package/dist/journal.test.js +201 -0
  100. package/dist/journal.test.js.map +1 -0
  101. package/dist/logger.d.ts +33 -0
  102. package/dist/logger.d.ts.map +1 -0
  103. package/dist/logger.js +35 -0
  104. package/dist/logger.js.map +1 -0
  105. package/dist/models.d.ts +107 -0
  106. package/dist/models.d.ts.map +1 -0
  107. package/dist/models.js +941 -0
  108. package/dist/models.js.map +1 -0
  109. package/dist/models.test.d.ts +2 -0
  110. package/dist/models.test.d.ts.map +1 -0
  111. package/dist/models.test.js +38 -0
  112. package/dist/models.test.js.map +1 -0
  113. package/dist/partners/index.d.ts +8 -0
  114. package/dist/partners/index.d.ts.map +1 -0
  115. package/dist/partners/index.js +6 -0
  116. package/dist/partners/index.js.map +1 -0
  117. package/dist/partners/registry.d.ts +51 -0
  118. package/dist/partners/registry.d.ts.map +1 -0
  119. package/dist/partners/registry.js +256 -0
  120. package/dist/partners/registry.js.map +1 -0
  121. package/dist/partners/tools.d.ts +24 -0
  122. package/dist/partners/tools.d.ts.map +1 -0
  123. package/dist/partners/tools.js +97 -0
  124. package/dist/partners/tools.js.map +1 -0
  125. package/dist/payment-preauth.d.ts +17 -0
  126. package/dist/payment-preauth.d.ts.map +1 -0
  127. package/dist/payment-preauth.js +102 -0
  128. package/dist/payment-preauth.js.map +1 -0
  129. package/dist/provider.d.ts +19 -0
  130. package/dist/provider.d.ts.map +1 -0
  131. package/dist/provider.js +45 -0
  132. package/dist/provider.js.map +1 -0
  133. package/dist/proxy.d.ts +210 -0
  134. package/dist/proxy.d.ts.map +1 -0
  135. package/dist/proxy.debrand.test.d.ts +2 -0
  136. package/dist/proxy.debrand.test.d.ts.map +1 -0
  137. package/dist/proxy.debrand.test.js +101 -0
  138. package/dist/proxy.debrand.test.js.map +1 -0
  139. package/dist/proxy.degraded-response.test.d.ts +2 -0
  140. package/dist/proxy.degraded-response.test.d.ts.map +1 -0
  141. package/dist/proxy.degraded-response.test.js +105 -0
  142. package/dist/proxy.degraded-response.test.js.map +1 -0
  143. package/dist/proxy.image-download.test.d.ts +2 -0
  144. package/dist/proxy.image-download.test.d.ts.map +1 -0
  145. package/dist/proxy.image-download.test.js +92 -0
  146. package/dist/proxy.image-download.test.js.map +1 -0
  147. package/dist/proxy.js +3810 -0
  148. package/dist/proxy.js.map +1 -0
  149. package/dist/proxy.models-endpoint.test.d.ts +2 -0
  150. package/dist/proxy.models-endpoint.test.d.ts.map +1 -0
  151. package/dist/proxy.models-endpoint.test.js +19 -0
  152. package/dist/proxy.models-endpoint.test.js.map +1 -0
  153. package/dist/proxy.multiturn-thinking.test.d.ts +2 -0
  154. package/dist/proxy.multiturn-thinking.test.d.ts.map +1 -0
  155. package/dist/proxy.multiturn-thinking.test.js +82 -0
  156. package/dist/proxy.multiturn-thinking.test.js.map +1 -0
  157. package/dist/proxy.payment-chain-reuse.test.d.ts +2 -0
  158. package/dist/proxy.payment-chain-reuse.test.d.ts.map +1 -0
  159. package/dist/proxy.payment-chain-reuse.test.js +30 -0
  160. package/dist/proxy.payment-chain-reuse.test.js.map +1 -0
  161. package/dist/proxy.solana-resilience.test.d.ts +12 -0
  162. package/dist/proxy.solana-resilience.test.d.ts.map +1 -0
  163. package/dist/proxy.solana-resilience.test.js +39 -0
  164. package/dist/proxy.solana-resilience.test.js.map +1 -0
  165. package/dist/report.d.ts +6 -0
  166. package/dist/report.d.ts.map +1 -0
  167. package/dist/report.js +44 -0
  168. package/dist/report.js.map +1 -0
  169. package/dist/response-cache.advanced.test.d.ts +2 -0
  170. package/dist/response-cache.advanced.test.d.ts.map +1 -0
  171. package/dist/response-cache.advanced.test.js +566 -0
  172. package/dist/response-cache.advanced.test.js.map +1 -0
  173. package/dist/response-cache.d.ts +85 -0
  174. package/dist/response-cache.d.ts.map +1 -0
  175. package/dist/response-cache.edge-cases.test.d.ts +2 -0
  176. package/dist/response-cache.edge-cases.test.d.ts.map +1 -0
  177. package/dist/response-cache.edge-cases.test.js +488 -0
  178. package/dist/response-cache.edge-cases.test.js.map +1 -0
  179. package/dist/response-cache.extreme.test.d.ts +2 -0
  180. package/dist/response-cache.extreme.test.d.ts.map +1 -0
  181. package/dist/response-cache.extreme.test.js +561 -0
  182. package/dist/response-cache.extreme.test.js.map +1 -0
  183. package/dist/response-cache.js +240 -0
  184. package/dist/response-cache.js.map +1 -0
  185. package/dist/response-cache.test.d.ts +2 -0
  186. package/dist/response-cache.test.d.ts.map +1 -0
  187. package/dist/response-cache.test.js +242 -0
  188. package/dist/response-cache.test.js.map +1 -0
  189. package/dist/retry.d.ts +42 -0
  190. package/dist/retry.d.ts.map +1 -0
  191. package/dist/retry.js +99 -0
  192. package/dist/retry.js.map +1 -0
  193. package/dist/router/config.d.ts +11 -0
  194. package/dist/router/config.d.ts.map +1 -0
  195. package/dist/router/config.js +1215 -0
  196. package/dist/router/config.js.map +1 -0
  197. package/dist/router/index.d.ts +18 -0
  198. package/dist/router/index.d.ts.map +1 -0
  199. package/dist/router/index.js +19 -0
  200. package/dist/router/index.js.map +1 -0
  201. package/dist/router/llm-classifier.d.ts +29 -0
  202. package/dist/router/llm-classifier.d.ts.map +1 -0
  203. package/dist/router/llm-classifier.js +99 -0
  204. package/dist/router/llm-classifier.js.map +1 -0
  205. package/dist/router/rules.d.ts +12 -0
  206. package/dist/router/rules.d.ts.map +1 -0
  207. package/dist/router/rules.js +202 -0
  208. package/dist/router/rules.js.map +1 -0
  209. package/dist/router/selector.d.ts +58 -0
  210. package/dist/router/selector.d.ts.map +1 -0
  211. package/dist/router/selector.js +165 -0
  212. package/dist/router/selector.js.map +1 -0
  213. package/dist/router/selector.test.d.ts +2 -0
  214. package/dist/router/selector.test.d.ts.map +1 -0
  215. package/dist/router/selector.test.js +67 -0
  216. package/dist/router/selector.test.js.map +1 -0
  217. package/dist/router/strategy.d.ts +19 -0
  218. package/dist/router/strategy.d.ts.map +1 -0
  219. package/dist/router/strategy.js +138 -0
  220. package/dist/router/strategy.js.map +1 -0
  221. package/dist/router/strategy.test.d.ts +2 -0
  222. package/dist/router/strategy.test.d.ts.map +1 -0
  223. package/dist/router/strategy.test.js +131 -0
  224. package/dist/router/strategy.test.js.map +1 -0
  225. package/dist/router/types.d.ts +127 -0
  226. package/dist/router/types.d.ts.map +1 -0
  227. package/dist/router/types.js +11 -0
  228. package/dist/router/types.js.map +1 -0
  229. package/dist/session.d.ts +124 -0
  230. package/dist/session.d.ts.map +1 -0
  231. package/dist/session.js +256 -0
  232. package/dist/session.js.map +1 -0
  233. package/dist/session.test.d.ts +2 -0
  234. package/dist/session.test.d.ts.map +1 -0
  235. package/dist/session.test.js +163 -0
  236. package/dist/session.test.js.map +1 -0
  237. package/dist/solana-balance.d.ts +48 -0
  238. package/dist/solana-balance.d.ts.map +1 -0
  239. package/dist/solana-balance.js +142 -0
  240. package/dist/solana-balance.js.map +1 -0
  241. package/dist/spend-control.d.ts +112 -0
  242. package/dist/spend-control.d.ts.map +1 -0
  243. package/dist/spend-control.js +278 -0
  244. package/dist/spend-control.js.map +1 -0
  245. package/dist/spend-control.test.d.ts +5 -0
  246. package/dist/spend-control.test.d.ts.map +1 -0
  247. package/dist/spend-control.test.js +210 -0
  248. package/dist/spend-control.test.js.map +1 -0
  249. package/dist/stats.d.ts +64 -0
  250. package/dist/stats.d.ts.map +1 -0
  251. package/dist/stats.js +295 -0
  252. package/dist/stats.js.map +1 -0
  253. package/dist/types.d.ts +150 -0
  254. package/dist/types.d.ts.map +1 -0
  255. package/dist/types.js +9 -0
  256. package/dist/types.js.map +1 -0
  257. package/dist/update-hint.test.d.ts +9 -0
  258. package/dist/update-hint.test.d.ts.map +1 -0
  259. package/dist/update-hint.test.js +156 -0
  260. package/dist/update-hint.test.js.map +1 -0
  261. package/dist/updater.d.ts +10 -0
  262. package/dist/updater.d.ts.map +1 -0
  263. package/dist/updater.js +55 -0
  264. package/dist/updater.js.map +1 -0
  265. package/dist/version.d.ts +3 -0
  266. package/dist/version.d.ts.map +1 -0
  267. package/dist/version.js +16 -0
  268. package/dist/version.js.map +1 -0
  269. package/dist/wallet.d.ts +52 -0
  270. package/dist/wallet.d.ts.map +1 -0
  271. package/dist/wallet.js +93 -0
  272. package/dist/wallet.js.map +1 -0
  273. package/dist/wallet.test.d.ts +6 -0
  274. package/dist/wallet.test.d.ts.map +1 -0
  275. package/dist/wallet.test.js +117 -0
  276. package/dist/wallet.test.js.map +1 -0
  277. package/dist/x402-sdk.test.d.ts +6 -0
  278. package/dist/x402-sdk.test.d.ts.map +1 -0
  279. package/dist/x402-sdk.test.js +161 -0
  280. package/dist/x402-sdk.test.js.map +1 -0
  281. package/package.json +1 -1
  282. package/scripts/e2e-multiturn.mjs +4 -1
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Spend Control - Time-windowed spending limits
3
+ *
4
+ * Absorbed from @blockrun/clawwallet. Chain-agnostic (works for both EVM and Solana).
5
+ *
6
+ * Features:
7
+ * - Per-request limits (e.g., max $0.10 per call)
8
+ * - Hourly limits (e.g., max $3.00 per hour)
9
+ * - Daily limits (e.g., max $20.00 per day)
10
+ * - Session limits (e.g., max $5.00 per session)
11
+ * - Rolling windows (last 1h, last 24h)
12
+ * - Persistent storage (~/.openclaw/blockrun/spending.json)
13
+ */
14
+ import * as fs from "node:fs";
15
+ import * as path from "node:path";
16
+ import { homedir } from "node:os";
17
+ import { readTextFileSync } from "./fs-read.js";
18
+ const WALLET_DIR = path.join(homedir(), ".openclaw", "blockrun");
19
+ const HOUR_MS = 60 * 60 * 1000;
20
+ const DAY_MS = 24 * HOUR_MS;
21
+ export class FileSpendControlStorage {
22
+ spendingFile;
23
+ constructor() {
24
+ this.spendingFile = path.join(WALLET_DIR, "spending.json");
25
+ }
26
+ load() {
27
+ try {
28
+ if (fs.existsSync(this.spendingFile)) {
29
+ const data = JSON.parse(readTextFileSync(this.spendingFile));
30
+ const rawLimits = data.limits ?? {};
31
+ const rawHistory = data.history ?? [];
32
+ const limits = {};
33
+ for (const key of ["perRequest", "hourly", "daily", "session"]) {
34
+ const val = rawLimits[key];
35
+ if (typeof val === "number" && val > 0 && Number.isFinite(val)) {
36
+ limits[key] = val;
37
+ }
38
+ }
39
+ const history = [];
40
+ if (Array.isArray(rawHistory)) {
41
+ for (const r of rawHistory) {
42
+ if (typeof r?.timestamp === "number" &&
43
+ typeof r?.amount === "number" &&
44
+ Number.isFinite(r.timestamp) &&
45
+ Number.isFinite(r.amount) &&
46
+ r.amount >= 0) {
47
+ history.push({
48
+ timestamp: r.timestamp,
49
+ amount: r.amount,
50
+ model: typeof r.model === "string" ? r.model : undefined,
51
+ action: typeof r.action === "string" ? r.action : undefined,
52
+ });
53
+ }
54
+ }
55
+ }
56
+ return { limits, history };
57
+ }
58
+ }
59
+ catch (err) {
60
+ console.error(`[ClawRouter] Failed to load spending data, starting fresh: ${err}`);
61
+ }
62
+ return null;
63
+ }
64
+ save(data) {
65
+ try {
66
+ if (!fs.existsSync(WALLET_DIR)) {
67
+ fs.mkdirSync(WALLET_DIR, { recursive: true, mode: 0o700 });
68
+ }
69
+ fs.writeFileSync(this.spendingFile, JSON.stringify(data, null, 2), {
70
+ mode: 0o600,
71
+ });
72
+ }
73
+ catch (err) {
74
+ console.error(`[ClawRouter] Failed to save spending data: ${err}`);
75
+ }
76
+ }
77
+ }
78
+ export class InMemorySpendControlStorage {
79
+ data = null;
80
+ load() {
81
+ return this.data
82
+ ? {
83
+ limits: { ...this.data.limits },
84
+ history: this.data.history.map((r) => ({ ...r })),
85
+ }
86
+ : null;
87
+ }
88
+ save(data) {
89
+ this.data = {
90
+ limits: { ...data.limits },
91
+ history: data.history.map((r) => ({ ...r })),
92
+ };
93
+ }
94
+ }
95
+ export class SpendControl {
96
+ limits = {};
97
+ history = [];
98
+ sessionSpent = 0;
99
+ sessionCalls = 0;
100
+ storage;
101
+ now;
102
+ constructor(options) {
103
+ this.storage = options?.storage ?? new FileSpendControlStorage();
104
+ this.now = options?.now ?? (() => Date.now());
105
+ this.load();
106
+ }
107
+ setLimit(window, amount) {
108
+ if (!Number.isFinite(amount) || amount <= 0) {
109
+ throw new Error("Limit must be a finite positive number");
110
+ }
111
+ this.limits[window] = amount;
112
+ this.save();
113
+ }
114
+ clearLimit(window) {
115
+ delete this.limits[window];
116
+ this.save();
117
+ }
118
+ getLimits() {
119
+ return { ...this.limits };
120
+ }
121
+ check(estimatedCost) {
122
+ const now = this.now();
123
+ if (this.limits.perRequest !== undefined) {
124
+ if (estimatedCost > this.limits.perRequest) {
125
+ return {
126
+ allowed: false,
127
+ blockedBy: "perRequest",
128
+ remaining: this.limits.perRequest,
129
+ reason: `Per-request limit exceeded: $${estimatedCost.toFixed(4)} > $${this.limits.perRequest.toFixed(2)} max`,
130
+ };
131
+ }
132
+ }
133
+ if (this.limits.hourly !== undefined) {
134
+ const hourlySpent = this.getSpendingInWindow(now - HOUR_MS, now);
135
+ const remaining = this.limits.hourly - hourlySpent;
136
+ if (estimatedCost > remaining) {
137
+ const oldestInWindow = this.history.find((r) => r.timestamp >= now - HOUR_MS);
138
+ const resetIn = oldestInWindow
139
+ ? Math.ceil((oldestInWindow.timestamp + HOUR_MS - now) / 1000)
140
+ : 0;
141
+ return {
142
+ allowed: false,
143
+ blockedBy: "hourly",
144
+ remaining,
145
+ reason: `Hourly limit exceeded: $${(hourlySpent + estimatedCost).toFixed(2)} > $${this.limits.hourly.toFixed(2)} max`,
146
+ resetIn,
147
+ };
148
+ }
149
+ }
150
+ if (this.limits.daily !== undefined) {
151
+ const dailySpent = this.getSpendingInWindow(now - DAY_MS, now);
152
+ const remaining = this.limits.daily - dailySpent;
153
+ if (estimatedCost > remaining) {
154
+ const oldestInWindow = this.history.find((r) => r.timestamp >= now - DAY_MS);
155
+ const resetIn = oldestInWindow
156
+ ? Math.ceil((oldestInWindow.timestamp + DAY_MS - now) / 1000)
157
+ : 0;
158
+ return {
159
+ allowed: false,
160
+ blockedBy: "daily",
161
+ remaining,
162
+ reason: `Daily limit exceeded: $${(dailySpent + estimatedCost).toFixed(2)} > $${this.limits.daily.toFixed(2)} max`,
163
+ resetIn,
164
+ };
165
+ }
166
+ }
167
+ if (this.limits.session !== undefined) {
168
+ const remaining = this.limits.session - this.sessionSpent;
169
+ if (estimatedCost > remaining) {
170
+ return {
171
+ allowed: false,
172
+ blockedBy: "session",
173
+ remaining,
174
+ reason: `Session limit exceeded: $${(this.sessionSpent + estimatedCost).toFixed(2)} > $${this.limits.session.toFixed(2)} max`,
175
+ };
176
+ }
177
+ }
178
+ return { allowed: true };
179
+ }
180
+ record(amount, metadata) {
181
+ if (!Number.isFinite(amount) || amount < 0) {
182
+ throw new Error("Record amount must be a non-negative finite number");
183
+ }
184
+ const record = {
185
+ timestamp: this.now(),
186
+ amount,
187
+ model: metadata?.model,
188
+ action: metadata?.action,
189
+ };
190
+ this.history.push(record);
191
+ this.sessionSpent += amount;
192
+ this.sessionCalls += 1;
193
+ this.cleanup();
194
+ this.save();
195
+ }
196
+ getSpendingInWindow(from, to) {
197
+ return this.history
198
+ .filter((r) => r.timestamp >= from && r.timestamp <= to)
199
+ .reduce((sum, r) => sum + r.amount, 0);
200
+ }
201
+ getSpending(window) {
202
+ const now = this.now();
203
+ switch (window) {
204
+ case "hourly":
205
+ return this.getSpendingInWindow(now - HOUR_MS, now);
206
+ case "daily":
207
+ return this.getSpendingInWindow(now - DAY_MS, now);
208
+ case "session":
209
+ return this.sessionSpent;
210
+ }
211
+ }
212
+ getRemaining(window) {
213
+ const limit = this.limits[window];
214
+ if (limit === undefined)
215
+ return null;
216
+ return Math.max(0, limit - this.getSpending(window));
217
+ }
218
+ getStatus() {
219
+ const now = this.now();
220
+ const hourlySpent = this.getSpendingInWindow(now - HOUR_MS, now);
221
+ const dailySpent = this.getSpendingInWindow(now - DAY_MS, now);
222
+ return {
223
+ limits: { ...this.limits },
224
+ spending: {
225
+ hourly: hourlySpent,
226
+ daily: dailySpent,
227
+ session: this.sessionSpent,
228
+ },
229
+ remaining: {
230
+ hourly: this.limits.hourly !== undefined ? this.limits.hourly - hourlySpent : null,
231
+ daily: this.limits.daily !== undefined ? this.limits.daily - dailySpent : null,
232
+ session: this.limits.session !== undefined ? this.limits.session - this.sessionSpent : null,
233
+ },
234
+ calls: this.sessionCalls,
235
+ };
236
+ }
237
+ getHistory(limit) {
238
+ const records = [...this.history].reverse();
239
+ return limit ? records.slice(0, limit) : records;
240
+ }
241
+ resetSession() {
242
+ this.sessionSpent = 0;
243
+ this.sessionCalls = 0;
244
+ }
245
+ cleanup() {
246
+ const cutoff = this.now() - DAY_MS;
247
+ this.history = this.history.filter((r) => r.timestamp >= cutoff);
248
+ }
249
+ save() {
250
+ this.storage.save({
251
+ limits: { ...this.limits },
252
+ history: [...this.history],
253
+ });
254
+ }
255
+ load() {
256
+ const data = this.storage.load();
257
+ if (data) {
258
+ this.limits = data.limits;
259
+ this.history = data.history;
260
+ this.cleanup();
261
+ }
262
+ }
263
+ }
264
+ export function formatDuration(seconds) {
265
+ if (seconds < 60) {
266
+ return `${seconds}s`;
267
+ }
268
+ else if (seconds < 3600) {
269
+ const mins = Math.ceil(seconds / 60);
270
+ return `${mins} min`;
271
+ }
272
+ else {
273
+ const hours = Math.floor(seconds / 3600);
274
+ const mins = Math.ceil((seconds % 3600) / 60);
275
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
276
+ }
277
+ }
278
+ //# sourceMappingURL=spend-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-control.js","sourceRoot":"","sources":["../src/spend-control.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * SpendControl tests — limits, recording, window expiry, persistence.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=spend-control.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-control.test.d.ts","sourceRoot":"","sources":["../src/spend-control.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * SpendControl tests — limits, recording, window expiry, persistence.
3
+ */
4
+ import { describe, it, expect } from "vitest";
5
+ import { SpendControl, InMemorySpendControlStorage, formatDuration } from "./spend-control.js";
6
+ function createControl(nowMs = Date.now()) {
7
+ let clock = nowMs;
8
+ const storage = new InMemorySpendControlStorage();
9
+ const control = new SpendControl({ storage, now: () => clock });
10
+ const advance = (ms) => {
11
+ clock += ms;
12
+ };
13
+ return { control, storage, advance };
14
+ }
15
+ describe("SpendControl", () => {
16
+ describe("per-request limit", () => {
17
+ it("allows requests under the limit", () => {
18
+ const { control } = createControl();
19
+ control.setLimit("perRequest", 0.1);
20
+ expect(control.check(0.05).allowed).toBe(true);
21
+ });
22
+ it("blocks requests over the limit", () => {
23
+ const { control } = createControl();
24
+ control.setLimit("perRequest", 0.1);
25
+ const result = control.check(0.15);
26
+ expect(result.allowed).toBe(false);
27
+ expect(result.blockedBy).toBe("perRequest");
28
+ });
29
+ it("blocks requests exactly at the limit boundary", () => {
30
+ const { control } = createControl();
31
+ control.setLimit("perRequest", 0.1);
32
+ // Exactly equal should pass
33
+ expect(control.check(0.1).allowed).toBe(true);
34
+ // Just over should fail
35
+ expect(control.check(0.100001).allowed).toBe(false);
36
+ });
37
+ });
38
+ describe("hourly limit", () => {
39
+ it("accumulates spending within the hour", () => {
40
+ const { control } = createControl();
41
+ control.setLimit("hourly", 1.0);
42
+ control.record(0.4);
43
+ control.record(0.4);
44
+ expect(control.check(0.25).allowed).toBe(false);
45
+ expect(control.check(0.15).allowed).toBe(true);
46
+ });
47
+ it("resets after the hour window passes", () => {
48
+ const { control, advance } = createControl();
49
+ control.setLimit("hourly", 1.0);
50
+ control.record(0.9);
51
+ expect(control.check(0.2).allowed).toBe(false);
52
+ // Advance past the 1-hour window
53
+ advance(61 * 60 * 1000);
54
+ expect(control.check(0.2).allowed).toBe(true);
55
+ });
56
+ it("provides resetIn seconds", () => {
57
+ const { control } = createControl();
58
+ control.setLimit("hourly", 0.5);
59
+ control.record(0.5);
60
+ const result = control.check(0.01);
61
+ expect(result.allowed).toBe(false);
62
+ expect(result.resetIn).toBeGreaterThan(0);
63
+ expect(result.resetIn).toBeLessThanOrEqual(3600);
64
+ });
65
+ });
66
+ describe("daily limit", () => {
67
+ it("accumulates across hours within the day", () => {
68
+ const { control, advance } = createControl();
69
+ control.setLimit("daily", 5.0);
70
+ control.record(2.0);
71
+ advance(2 * 60 * 60 * 1000); // 2 hours later
72
+ control.record(2.0);
73
+ expect(control.check(1.5).allowed).toBe(false);
74
+ expect(control.check(0.9).allowed).toBe(true);
75
+ });
76
+ it("resets after the day window passes", () => {
77
+ const { control, advance } = createControl();
78
+ control.setLimit("daily", 5.0);
79
+ control.record(4.9);
80
+ expect(control.check(0.2).allowed).toBe(false);
81
+ advance(25 * 60 * 60 * 1000); // 25 hours
82
+ expect(control.check(0.2).allowed).toBe(true);
83
+ });
84
+ });
85
+ describe("session limit", () => {
86
+ it("tracks spending within the session", () => {
87
+ const { control } = createControl();
88
+ control.setLimit("session", 2.0);
89
+ control.record(1.5);
90
+ expect(control.check(0.6).allowed).toBe(false);
91
+ expect(control.check(0.4).allowed).toBe(true);
92
+ });
93
+ it("resetSession clears session spending", () => {
94
+ const { control } = createControl();
95
+ control.setLimit("session", 2.0);
96
+ control.record(1.9);
97
+ expect(control.check(0.2).allowed).toBe(false);
98
+ control.resetSession();
99
+ expect(control.check(0.2).allowed).toBe(true);
100
+ });
101
+ });
102
+ describe("multiple limits", () => {
103
+ it("checks all limits and reports the first violation", () => {
104
+ const { control } = createControl();
105
+ control.setLimit("perRequest", 0.5);
106
+ control.setLimit("hourly", 2.0);
107
+ // Over per-request limit
108
+ const result = control.check(0.6);
109
+ expect(result.allowed).toBe(false);
110
+ expect(result.blockedBy).toBe("perRequest");
111
+ });
112
+ it("checks hourly after per-request passes", () => {
113
+ const { control } = createControl();
114
+ control.setLimit("perRequest", 1.0);
115
+ control.setLimit("hourly", 2.0);
116
+ control.record(1.8);
117
+ const result = control.check(0.3);
118
+ expect(result.allowed).toBe(false);
119
+ expect(result.blockedBy).toBe("hourly");
120
+ });
121
+ });
122
+ describe("getStatus", () => {
123
+ it("returns current spending and remaining amounts", () => {
124
+ const { control } = createControl();
125
+ control.setLimit("hourly", 3.0);
126
+ control.setLimit("daily", 10.0);
127
+ control.record(1.0);
128
+ control.record(0.5);
129
+ const status = control.getStatus();
130
+ expect(status.spending.hourly).toBeCloseTo(1.5);
131
+ expect(status.spending.session).toBeCloseTo(1.5);
132
+ expect(status.remaining.hourly).toBeCloseTo(1.5);
133
+ expect(status.remaining.daily).toBeCloseTo(8.5);
134
+ expect(status.calls).toBe(2);
135
+ });
136
+ });
137
+ describe("getHistory", () => {
138
+ it("returns records in reverse chronological order", () => {
139
+ const { control, advance } = createControl();
140
+ control.record(0.1, { model: "first" });
141
+ advance(1000);
142
+ control.record(0.2, { model: "second" });
143
+ const history = control.getHistory();
144
+ expect(history).toHaveLength(2);
145
+ expect(history[0].model).toBe("second");
146
+ expect(history[1].model).toBe("first");
147
+ });
148
+ it("respects limit parameter", () => {
149
+ const { control, advance } = createControl();
150
+ control.record(0.1);
151
+ advance(100);
152
+ control.record(0.2);
153
+ advance(100);
154
+ control.record(0.3);
155
+ expect(control.getHistory(2)).toHaveLength(2);
156
+ });
157
+ });
158
+ describe("clearLimit", () => {
159
+ it("removes a specific limit", () => {
160
+ const { control } = createControl();
161
+ control.setLimit("perRequest", 0.01);
162
+ expect(control.check(0.05).allowed).toBe(false);
163
+ control.clearLimit("perRequest");
164
+ expect(control.check(0.05).allowed).toBe(true);
165
+ });
166
+ });
167
+ describe("persistence", () => {
168
+ it("persists limits and history across instances via shared storage", () => {
169
+ const storage = new InMemorySpendControlStorage();
170
+ const clock = Date.now();
171
+ const c1 = new SpendControl({ storage, now: () => clock });
172
+ c1.setLimit("hourly", 5.0);
173
+ c1.record(2.0);
174
+ // New instance, same storage
175
+ const c2 = new SpendControl({ storage, now: () => clock });
176
+ expect(c2.getLimits().hourly).toBe(5.0);
177
+ expect(c2.getSpending("hourly")).toBeCloseTo(2.0);
178
+ });
179
+ });
180
+ describe("validation", () => {
181
+ it("rejects non-positive limits", () => {
182
+ const { control } = createControl();
183
+ expect(() => control.setLimit("hourly", 0)).toThrow();
184
+ expect(() => control.setLimit("hourly", -1)).toThrow();
185
+ });
186
+ it("rejects negative record amounts", () => {
187
+ const { control } = createControl();
188
+ expect(() => control.record(-0.5)).toThrow();
189
+ });
190
+ it("rejects non-finite values", () => {
191
+ const { control } = createControl();
192
+ expect(() => control.setLimit("hourly", Infinity)).toThrow();
193
+ expect(() => control.setLimit("hourly", NaN)).toThrow();
194
+ });
195
+ });
196
+ });
197
+ describe("formatDuration", () => {
198
+ it("formats seconds", () => {
199
+ expect(formatDuration(30)).toBe("30s");
200
+ });
201
+ it("formats minutes", () => {
202
+ expect(formatDuration(120)).toBe("2 min");
203
+ expect(formatDuration(90)).toBe("2 min");
204
+ });
205
+ it("formats hours and minutes", () => {
206
+ expect(formatDuration(3660)).toBe("1h 1m");
207
+ expect(formatDuration(7200)).toBe("2h");
208
+ });
209
+ });
210
+ //# sourceMappingURL=spend-control.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-control.test.js","sourceRoot":"","sources":["../src/spend-control.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Usage Statistics Aggregator
3
+ *
4
+ * Reads usage log files and aggregates statistics for terminal display.
5
+ * Supports filtering by date range and provides multiple aggregation views.
6
+ */
7
+ export type DailyStats = {
8
+ date: string;
9
+ totalRequests: number;
10
+ totalCost: number;
11
+ totalBaselineCost: number;
12
+ totalSavings: number;
13
+ avgLatencyMs: number;
14
+ byTier: Record<string, {
15
+ count: number;
16
+ cost: number;
17
+ }>;
18
+ byModel: Record<string, {
19
+ count: number;
20
+ cost: number;
21
+ }>;
22
+ };
23
+ export type AggregatedStats = {
24
+ period: string;
25
+ totalRequests: number;
26
+ totalCost: number;
27
+ totalBaselineCost: number;
28
+ totalSavings: number;
29
+ savingsPercentage: number;
30
+ avgLatencyMs: number;
31
+ avgCostPerRequest: number;
32
+ byTier: Record<string, {
33
+ count: number;
34
+ cost: number;
35
+ percentage: number;
36
+ }>;
37
+ byModel: Record<string, {
38
+ count: number;
39
+ cost: number;
40
+ percentage: number;
41
+ }>;
42
+ dailyBreakdown: DailyStats[];
43
+ entriesWithBaseline: number;
44
+ };
45
+ /**
46
+ * Get aggregated statistics for the last N days.
47
+ */
48
+ export declare function getStats(days?: number): Promise<AggregatedStats>;
49
+ /**
50
+ * Format stats as ASCII table for terminal display.
51
+ */
52
+ export declare function formatStatsAscii(stats: AggregatedStats): string;
53
+ /**
54
+ * Format per-request log entries as an ASCII table for terminal display.
55
+ * Reads the last N days of log files and shows each request individually.
56
+ */
57
+ export declare function formatRecentLogs(days?: number): Promise<string>;
58
+ /**
59
+ * Delete all usage log files, resetting stats to zero.
60
+ */
61
+ export declare function clearStats(): Promise<{
62
+ deletedFiles: number;
63
+ }>;
64
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7E,cAAc,EAAE,UAAU,EAAE,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AAqFF;;GAEG;AACH,wBAAsB,QAAQ,CAAC,IAAI,GAAE,MAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CAsFzE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CA4E/D;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,GAAE,MAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAsDxE;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CAWpE"}