@tokenbuddy/tokenbuddy 1.0.36 → 1.0.37

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 (143) hide show
  1. package/dist/src/buyer-store.d.ts +6 -1
  2. package/dist/src/buyer-store.js +43 -4
  3. package/dist/src/cli.js +2 -2
  4. package/dist/src/daemon.d.ts +12 -0
  5. package/dist/src/daemon.js +791 -61
  6. package/dist/src/doctor-diagnostics.js +1 -6
  7. package/dist/src/provider-install.d.ts +2 -2
  8. package/dist/src/provider-install.js +248 -2
  9. package/dist/src/seller-catalog.d.ts +21 -0
  10. package/dist/src/seller-catalog.js +17 -0
  11. package/dist/src/seller-route-planner.d.ts +4 -1
  12. package/dist/src/seller-route-planner.js +3 -0
  13. package/dist/src/seller-routing-strategy.d.ts +3 -0
  14. package/dist/src/terminal-detect.d.ts +1 -1
  15. package/dist/src/terminal-detect.js +3 -2
  16. package/package.json +15 -2
  17. package/static/ui/assets/index-Djfl9tw5.js +271 -0
  18. package/static/ui/assets/index-DkfztCkn.css +1 -0
  19. package/static/ui/index.html +2 -2
  20. package/dist/src/buyer-store.d.ts.map +0 -1
  21. package/dist/src/buyer-store.js.map +0 -1
  22. package/dist/src/clawtip-bootstrap.d.ts.map +0 -1
  23. package/dist/src/clawtip-bootstrap.js.map +0 -1
  24. package/dist/src/cli.d.ts.map +0 -1
  25. package/dist/src/cli.js.map +0 -1
  26. package/dist/src/credit-tracker.d.ts.map +0 -1
  27. package/dist/src/credit-tracker.js.map +0 -1
  28. package/dist/src/daemon.d.ts.map +0 -1
  29. package/dist/src/daemon.js.map +0 -1
  30. package/dist/src/doctor-clawtip-wallet.d.ts.map +0 -1
  31. package/dist/src/doctor-clawtip-wallet.js.map +0 -1
  32. package/dist/src/doctor-diagnostics.d.ts.map +0 -1
  33. package/dist/src/doctor-diagnostics.js.map +0 -1
  34. package/dist/src/index.d.ts.map +0 -1
  35. package/dist/src/index.js.map +0 -1
  36. package/dist/src/init-clawtip-activation.d.ts.map +0 -1
  37. package/dist/src/init-clawtip-activation.js.map +0 -1
  38. package/dist/src/init-payment-options.d.ts.map +0 -1
  39. package/dist/src/init-payment-options.js.map +0 -1
  40. package/dist/src/init-setup.d.ts.map +0 -1
  41. package/dist/src/init-setup.js.map +0 -1
  42. package/dist/src/model-index.d.ts.map +0 -1
  43. package/dist/src/model-index.js.map +0 -1
  44. package/dist/src/package-update.d.ts.map +0 -1
  45. package/dist/src/package-update.js.map +0 -1
  46. package/dist/src/prewarm-cache.d.ts.map +0 -1
  47. package/dist/src/prewarm-cache.js.map +0 -1
  48. package/dist/src/prewarm-scheduler.d.ts.map +0 -1
  49. package/dist/src/prewarm-scheduler.js.map +0 -1
  50. package/dist/src/provider-install.d.ts.map +0 -1
  51. package/dist/src/provider-install.js.map +0 -1
  52. package/dist/src/provider-routing-config.d.ts.map +0 -1
  53. package/dist/src/provider-routing-config.js.map +0 -1
  54. package/dist/src/registry-trust.d.ts.map +0 -1
  55. package/dist/src/registry-trust.js.map +0 -1
  56. package/dist/src/route-failover.d.ts.map +0 -1
  57. package/dist/src/route-failover.js.map +0 -1
  58. package/dist/src/seller-catalog.d.ts.map +0 -1
  59. package/dist/src/seller-catalog.js.map +0 -1
  60. package/dist/src/seller-concurrency-limiter.d.ts.map +0 -1
  61. package/dist/src/seller-concurrency-limiter.js.map +0 -1
  62. package/dist/src/seller-metadata-cache.d.ts.map +0 -1
  63. package/dist/src/seller-metadata-cache.js.map +0 -1
  64. package/dist/src/seller-pool.d.ts.map +0 -1
  65. package/dist/src/seller-pool.js.map +0 -1
  66. package/dist/src/seller-route-planner.d.ts.map +0 -1
  67. package/dist/src/seller-route-planner.js.map +0 -1
  68. package/dist/src/seller-routing-config.d.ts.map +0 -1
  69. package/dist/src/seller-routing-config.js.map +0 -1
  70. package/dist/src/seller-routing-strategy.d.ts.map +0 -1
  71. package/dist/src/seller-routing-strategy.js.map +0 -1
  72. package/dist/src/stream-failover.d.ts.map +0 -1
  73. package/dist/src/stream-failover.js.map +0 -1
  74. package/dist/src/tb-clawtip-proof.d.ts.map +0 -1
  75. package/dist/src/tb-clawtip-proof.js.map +0 -1
  76. package/dist/src/tb-proxyd.d.ts.map +0 -1
  77. package/dist/src/tb-proxyd.js.map +0 -1
  78. package/dist/src/terminal-detect.d.ts.map +0 -1
  79. package/dist/src/terminal-detect.js.map +0 -1
  80. package/dist/src/terminal-image.d.ts.map +0 -1
  81. package/dist/src/terminal-image.js.map +0 -1
  82. package/src/buyer-store.ts +0 -1090
  83. package/src/clawtip-bootstrap.ts +0 -65
  84. package/src/cli.ts +0 -2243
  85. package/src/credit-tracker.ts +0 -295
  86. package/src/daemon.ts +0 -5475
  87. package/src/doctor-clawtip-wallet.ts +0 -95
  88. package/src/doctor-diagnostics.ts +0 -1026
  89. package/src/index.ts +0 -16
  90. package/src/init-clawtip-activation.ts +0 -695
  91. package/src/init-payment-options.ts +0 -373
  92. package/src/init-setup.ts +0 -165
  93. package/src/model-index.ts +0 -278
  94. package/src/package-update.ts +0 -311
  95. package/src/prewarm-cache.ts +0 -485
  96. package/src/prewarm-scheduler.ts +0 -675
  97. package/src/provider-install.ts +0 -1006
  98. package/src/provider-routing-config.ts +0 -410
  99. package/src/registry-trust.ts +0 -51
  100. package/src/route-failover.ts +0 -304
  101. package/src/seller-catalog.ts +0 -505
  102. package/src/seller-concurrency-limiter.ts +0 -161
  103. package/src/seller-metadata-cache.ts +0 -91
  104. package/src/seller-pool.ts +0 -557
  105. package/src/seller-route-planner.ts +0 -513
  106. package/src/seller-routing-config.ts +0 -211
  107. package/src/seller-routing-strategy.ts +0 -362
  108. package/src/stream-failover.ts +0 -152
  109. package/src/tb-clawtip-proof.ts +0 -28
  110. package/src/tb-proxyd.ts +0 -101
  111. package/src/terminal-detect.ts +0 -333
  112. package/src/terminal-image.ts +0 -228
  113. package/static/ui/assets/index-0MVXD7bH.css +0 -1
  114. package/static/ui/assets/index-BVbeDEwq.js +0 -271
  115. package/static/ui/assets/index-BVbeDEwq.js.map +0 -1
  116. package/tests/cli-routing.test.ts +0 -363
  117. package/tests/control-plane-ui-endpoints.test.ts +0 -1630
  118. package/tests/credit-tracker.test.ts +0 -165
  119. package/tests/daemon-413-fallback.test.ts +0 -92
  120. package/tests/daemon-classify.test.ts +0 -452
  121. package/tests/daemon-roles.test.ts +0 -92
  122. package/tests/daemon-trusted-registry-cache.test.ts +0 -132
  123. package/tests/e2e.test.ts +0 -366
  124. package/tests/image-generation-e2e.test.ts +0 -230
  125. package/tests/model-index.test.ts +0 -198
  126. package/tests/package-update.test.ts +0 -147
  127. package/tests/prewarm-cache.test.ts +0 -296
  128. package/tests/prewarm-scheduler.test.ts +0 -367
  129. package/tests/provider-routing-config.test.ts +0 -150
  130. package/tests/registry-trust.test.ts +0 -28
  131. package/tests/route-failover.test.ts +0 -222
  132. package/tests/seller-catalog-413.test.ts +0 -120
  133. package/tests/seller-catalog-utilities.test.ts +0 -124
  134. package/tests/seller-concurrency-limiter.test.ts +0 -83
  135. package/tests/seller-metadata-cache.test.ts +0 -89
  136. package/tests/seller-pool.test.ts +0 -365
  137. package/tests/seller-route-planner.test.ts +0 -312
  138. package/tests/seller-routing-config.test.ts +0 -124
  139. package/tests/seller-routing-strategy.test.ts +0 -167
  140. package/tests/stream-failover.test.ts +0 -52
  141. package/tests/thousand-seller.test.ts +0 -151
  142. package/tests/tokenbuddy.test.ts +0 -4043
  143. package/tsconfig.json +0 -8
@@ -1,1090 +0,0 @@
1
- // @ts-ignore node:sqlite types are not present in the current @types/node release.
2
- import { DatabaseSync } from "node:sqlite";
3
- import * as crypto from "crypto";
4
- import * as fs from "fs";
5
- import * as os from "os";
6
- import * as path from "path";
7
- import { createModuleLogger } from "@tokenbuddy/logging";
8
-
9
- const logger = createModuleLogger("tb-proxyd");
10
-
11
- /**
12
- * buyer 端缓存的 seller access token + 余额快照。
13
- * v1.2 PR-fix:每次取出都校验 `expiresAt`,避免把过期 token 发给 seller 导致 401。
14
- */
15
- export interface CachedToken {
16
- token: string;
17
- balanceMicros: number;
18
- reservedMicros: number;
19
- spentMicros: number;
20
- balanceSource?: string;
21
- /**
22
- * ISO-8601 expiry timestamp sourced from the seller's
23
- * `/purchase/complete` response. v1.2 PR-fix: the buyer now
24
- * checks this on every cached-token lookup so we never serve a
25
- * stale access token to the upstream.
26
- */
27
- expiresAt?: string;
28
- }
29
-
30
- /**
31
- * 持久化的支付方式配置。
32
- * `isDefault` 用于 `tb init` / 路由层在没有显式 method 时选默认。
33
- */
34
- export interface PaymentConfig {
35
- method: string;
36
- enabled: boolean;
37
- isDefault: boolean;
38
- config?: Record<string, unknown>;
39
- updatedAt: string;
40
- }
41
-
42
- /**
43
- * 待结算的 purchase 快照(buyer 端在 `/purchase/create` 之后、`/purchase/complete` 之前的临时状态)。
44
- */
45
- export interface PendingPurchaseInput {
46
- purchaseId: string;
47
- sellerKey: string;
48
- modelId: string;
49
- paymentMethod: string;
50
- amountUsdMicros: number;
51
- status: string;
52
- paymentReference?: string;
53
- expiresAt?: string;
54
- }
55
-
56
- /**
57
- * 完成态 purchase 的账本输入。`paymentReference` 会被 hash 之后落库(不存明文)。
58
- */
59
- export interface PurchaseLedgerInput {
60
- purchaseId: string;
61
- sellerKey: string;
62
- modelId: string;
63
- paymentMethod: string;
64
- status: string;
65
- creditMicros: number;
66
- currency: string;
67
- paymentAmount?: string;
68
- paymentAmountMinor?: number;
69
- paymentCurrency?: string;
70
- paymentReference?: string;
71
- completedAt?: string;
72
- }
73
-
74
- /**
75
- * `tb doctor` 与审计日志消费的"安全版" purchase 账本条目。
76
- * 关键差异:`paymentReference` 已 hash 化为 `paymentReferenceHash`,永不暴露明文。
77
- */
78
- export interface SafePurchaseLedgerEntry {
79
- purchaseId: string;
80
- sellerKey: string;
81
- modelId: string;
82
- paymentMethod: string;
83
- status: string;
84
- creditMicros: number;
85
- currency: string;
86
- paymentAmount?: string;
87
- paymentAmountMinor?: number;
88
- paymentCurrency?: string;
89
- paymentReferenceHash?: string;
90
- createdAt: string;
91
- completedAt?: string;
92
- }
93
-
94
- /**
95
- * buyer 端单次推理请求的账本输入。
96
- * `prompt` / `response` 在落库前会 hash,原始内容永不落盘。
97
- *
98
- * v1.2 tb-ui v1 (Iter 4):补 7 字段 — 排查 / 性能 / 路由决策溯源必备。
99
- * - `ttftMs`:首字延迟(从请求发起到首 byte),`forwardProxyRequest` 计算
100
- * - `fallbackCount`:本请求实际走过的 seller 数(> 1 即发生 failover)
101
- * - `routeReason`:`planSellerRouteSet().reason`(如 `fullAuto:balanced:routes_3`)
102
- * - `falloverChain`:failover 路径上 seller id 数组(空 = 主路径成功)
103
- * - `upstreamStatus`:seller 透传的上游健康状态(`healthy|degraded|unhealthy|unknown`)
104
- * - `durationMs`:总耗时(req start → response end 或失败)
105
- * - `paymentMethod`:本请求使用的支付方式(`mock|clawtip`)
106
- */
107
- export interface InferenceLedgerInput {
108
- requestId: string;
109
- sellerKey: string;
110
- modelId: string;
111
- endpoint: string;
112
- status: string;
113
- promptTokens: number;
114
- completionTokens: number;
115
- cacheReadTokens?: number;
116
- billedMicros: number;
117
- estimatedMicros?: number;
118
- settledMicros?: number;
119
- settledUsdMicros?: number;
120
- priceVersion?: string;
121
- inputPriceMicrosPer1m?: number;
122
- outputPriceMicrosPer1m?: number;
123
- cacheReadPriceMicrosPer1m?: number;
124
- inputCostMicros?: number;
125
- outputCostMicros?: number;
126
- cacheReadCostMicros?: number;
127
- originalUsdMicros?: number;
128
- billingMultiplier?: number;
129
- serviceTier?: string;
130
- billingUnit?: "tokens" | "images";
131
- imageCount?: number;
132
- imageSize?: string;
133
- imageQuality?: string;
134
- imageOutputFormat?: string;
135
- imageOutputTokens?: number;
136
- imageOutputCostMicros?: number;
137
- imageCostMicrosPerImage?: number;
138
- balanceSnapshotMicros?: number;
139
- balanceSource?: string;
140
- prompt?: string;
141
- response?: string;
142
- ttftMs?: number;
143
- fallbackCount?: number;
144
- routeReason?: string;
145
- falloverChain?: string[];
146
- upstreamStatus?: string;
147
- durationMs?: number;
148
- paymentMethod?: string;
149
- }
150
-
151
- /**
152
- * `tb doctor` 与审计日志消费的"安全版"推理账本条目。
153
- * 关键差异:`prompt` / `response` 已 hash 化为 `promptHash` / `responseHash`。
154
- */
155
- export interface SafeInferenceLedgerEntry {
156
- requestId: string;
157
- sellerKey: string;
158
- modelId: string;
159
- endpoint: string;
160
- status: string;
161
- promptTokens: number;
162
- completionTokens: number;
163
- cacheReadTokens: number;
164
- billedMicros: number;
165
- estimatedMicros?: number;
166
- settledMicros?: number;
167
- settledUsdMicros?: number;
168
- priceVersion?: string;
169
- inputPriceMicrosPer1m?: number;
170
- outputPriceMicrosPer1m?: number;
171
- cacheReadPriceMicrosPer1m?: number;
172
- inputCostMicros?: number;
173
- outputCostMicros?: number;
174
- cacheReadCostMicros?: number;
175
- originalUsdMicros?: number;
176
- billingMultiplier?: number;
177
- serviceTier?: string;
178
- billingUnit?: "tokens" | "images";
179
- imageCount?: number;
180
- imageSize?: string;
181
- imageQuality?: string;
182
- imageOutputFormat?: string;
183
- imageOutputTokens?: number;
184
- imageOutputCostMicros?: number;
185
- imageCostMicrosPerImage?: number;
186
- balanceSnapshotMicros?: number;
187
- balanceSource?: string;
188
- promptHash?: string;
189
- responseHash?: string;
190
- createdAt: string;
191
- // v1.2 tb-ui v1 (Iter 4):新增 7 字段
192
- ttftMs?: number;
193
- fallbackCount?: number;
194
- routeReason?: string;
195
- falloverChain?: string[];
196
- upstreamStatus?: string;
197
- durationMs?: number;
198
- paymentMethod?: string;
199
- }
200
-
201
- /**
202
- * seller token 余额快照的入参。
203
- * 每次推理后由 `BuyerStore.recordTokenBalance` 调用,作为路由决策的"最近一次真实余额"。
204
- */
205
- export interface TokenBalanceSnapshotInput {
206
- sellerKey: string;
207
- token?: string;
208
- tokenClass?: string;
209
- balanceMicros: number;
210
- reservedMicros?: number;
211
- spentMicros?: number;
212
- balanceSource: string;
213
- expiresAt?: string;
214
- }
215
-
216
- /**
217
- * `BuyerStore.summary()` 的返回结构,用于 `tb doctor` 展示 DB 健康度。
218
- */
219
- export interface BuyerStoreSummary {
220
- journalMode: string;
221
- paymentsCount: number;
222
- pendingPurchasesCount: number;
223
- purchaseLedgerCount: number;
224
- inferenceLedgerCount: number;
225
- providerRuntimeConfigCount: number;
226
- daemonRuntimeConfigCount: number;
227
- }
228
-
229
- /**
230
- * `tb init` 时记录的 provider 安装快照(用于后续 `tb rollback`)。
231
- * `existed` 标记安装前文件是否已存在,避免回滚覆盖用户原始内容。
232
- */
233
- export interface ProviderInstallSnapshot {
234
- providerId: string;
235
- files: Array<{
236
- path: string;
237
- existed: boolean;
238
- content?: string;
239
- }>;
240
- }
241
-
242
- /**
243
- * provider 维度运行时配置(由 `tb init` / `tb config` 写入)。
244
- * 存的是各 provider(如 opencode / cline)需要的 model mapping 等。
245
- */
246
- export interface ProviderRuntimeConfigRecord<T = unknown> {
247
- providerId: string;
248
- config: T;
249
- createdAt: string;
250
- updatedAt: string;
251
- }
252
-
253
- /**
254
- * 守护进程维度运行时配置(按 `configKey` 命名空间)。
255
- * 比如 `seller_routing` / `prewarm` / `inference` 等 daemon 子系统的配置。
256
- */
257
- export interface DaemonRuntimeConfigRecord<T = unknown> {
258
- configKey: string;
259
- config: T;
260
- createdAt: string;
261
- updatedAt: string;
262
- }
263
-
264
- /**
265
- * `BuyerStore` 构造选项。
266
- * 优先用 `dbPath`(绝对路径),否则用 `root`(目录)+ 默认 `buyer-store.db`。
267
- */
268
- export interface BuyerStoreOptions {
269
- root?: string;
270
- dbPath?: string;
271
- }
272
-
273
- function nowIso(): string {
274
- return new Date().toISOString();
275
- }
276
-
277
- function safeHash(value?: string): string | undefined {
278
- if (!value) {
279
- return undefined;
280
- }
281
- return crypto.createHash("sha256").update(value).digest("hex");
282
- }
283
-
284
- function boolFromSql(value: unknown): boolean {
285
- return value === 1 || value === true;
286
- }
287
-
288
- function ensureDirForFile(filePath: string): void {
289
- const dir = path.dirname(filePath);
290
- if (dir !== "." && !fs.existsSync(dir)) {
291
- fs.mkdirSync(dir, { recursive: true });
292
- }
293
- }
294
-
295
- /**
296
- * 解析 buyer store 的 DB 路径。
297
- * 优先级:`dbPath` → `TOKENBUDDY_BUYER_STORE` env → `~/.tokenbuddy-store/buyer-store.db`。
298
- *
299
- * @param options 解析选项
300
- * @returns DB 文件绝对路径
301
- */
302
- export function resolveBuyerStorePath(options: BuyerStoreOptions = {}): string {
303
- if (options.dbPath) {
304
- return options.dbPath;
305
- }
306
- const root = options.root || process.env.TOKENBUDDY_BUYER_STORE || path.join(os.homedir(), ".tokenbuddy-store");
307
- return path.join(root, "buyer-store.db");
308
- }
309
-
310
- /**
311
- * buyer 端 SQLite 持久化层。
312
- * 维护:支付配置、pending purchase、purchase 账本、inference 账本、provider / daemon runtime config、token 余额快照。
313
- * 内部用 SHA256 摘要替代明文敏感字段(prompt / response / paymentReference / token)。
314
- */
315
- export class BuyerStore {
316
- private db: DatabaseSync;
317
-
318
- constructor(options: BuyerStoreOptions | string = {}) {
319
- const dbPath = typeof options === "string" ? options : resolveBuyerStorePath(options);
320
- ensureDirForFile(dbPath);
321
- this.db = new DatabaseSync(dbPath);
322
- this.db.exec("PRAGMA journal_mode = WAL;");
323
- this.initSchema();
324
- }
325
-
326
- public journalMode(): string {
327
- const row = this.db.prepare("PRAGMA journal_mode;").get() as { journal_mode: string };
328
- return row.journal_mode;
329
- }
330
-
331
- public summary(): BuyerStoreSummary {
332
- return {
333
- journalMode: this.journalMode(),
334
- paymentsCount: this.countRows("payment_config"),
335
- pendingPurchasesCount: this.countRows("pending_purchases"),
336
- purchaseLedgerCount: this.countRows("purchase_ledger"),
337
- inferenceLedgerCount: this.countRows("inference_ledger"),
338
- providerRuntimeConfigCount: this.countRows("provider_runtime_config"),
339
- daemonRuntimeConfigCount: this.countRows("daemon_runtime_config")
340
- };
341
- }
342
-
343
- public saveProviderInstallSnapshot(snapshot: ProviderInstallSnapshot): void {
344
- const updatedAt = nowIso();
345
- this.db.prepare(
346
- `INSERT OR REPLACE INTO provider_install_state (
347
- provider_id, snapshot_json, created_at, updated_at
348
- ) VALUES (
349
- ?, ?,
350
- COALESCE((SELECT created_at FROM provider_install_state WHERE provider_id = ?), ?),
351
- ?
352
- )`
353
- ).run(
354
- snapshot.providerId,
355
- JSON.stringify(snapshot),
356
- snapshot.providerId,
357
- updatedAt,
358
- updatedAt
359
- );
360
- }
361
-
362
- public getProviderInstallSnapshot(providerId: string): ProviderInstallSnapshot | undefined {
363
- const row = this.db.prepare(
364
- "SELECT snapshot_json FROM provider_install_state WHERE provider_id = ?"
365
- ).get(providerId) as { snapshot_json: string } | undefined;
366
- if (!row) {
367
- return undefined;
368
- }
369
- return JSON.parse(row.snapshot_json) as ProviderInstallSnapshot;
370
- }
371
-
372
- public removeProviderInstallSnapshot(providerId: string): boolean {
373
- const result = this.db.prepare("DELETE FROM provider_install_state WHERE provider_id = ?").run(providerId) as { changes: number };
374
- return result.changes > 0;
375
- }
376
-
377
- public saveProviderRuntimeConfig(providerId: string, config: unknown): void {
378
- const updatedAt = nowIso();
379
- this.db.prepare(
380
- `INSERT OR REPLACE INTO provider_runtime_config (
381
- provider_id, runtime_json, created_at, updated_at
382
- ) VALUES (
383
- ?, ?,
384
- COALESCE((SELECT created_at FROM provider_runtime_config WHERE provider_id = ?), ?),
385
- ?
386
- )`
387
- ).run(providerId, JSON.stringify(config), providerId, updatedAt, updatedAt);
388
- }
389
-
390
- public getProviderRuntimeConfig<T = unknown>(providerId: string): ProviderRuntimeConfigRecord<T> | undefined {
391
- const row = this.db.prepare(
392
- `SELECT provider_id, runtime_json, created_at, updated_at
393
- FROM provider_runtime_config
394
- WHERE provider_id = ?`
395
- ).get(providerId) as {
396
- provider_id: string;
397
- runtime_json: string;
398
- created_at: string;
399
- updated_at: string;
400
- } | undefined;
401
- if (!row) {
402
- return undefined;
403
- }
404
- return {
405
- providerId: row.provider_id,
406
- config: JSON.parse(row.runtime_json) as T,
407
- createdAt: row.created_at,
408
- updatedAt: row.updated_at
409
- };
410
- }
411
-
412
- public removeProviderRuntimeConfig(providerId: string): boolean {
413
- const result = this.db.prepare("DELETE FROM provider_runtime_config WHERE provider_id = ?").run(providerId) as { changes: number };
414
- return result.changes > 0;
415
- }
416
-
417
- public saveDaemonRuntimeConfig(configKey: string, config: unknown): void {
418
- const updatedAt = nowIso();
419
- this.db.prepare(
420
- `INSERT OR REPLACE INTO daemon_runtime_config (
421
- config_key, config_json, created_at, updated_at
422
- ) VALUES (
423
- ?, ?,
424
- COALESCE((SELECT created_at FROM daemon_runtime_config WHERE config_key = ?), ?),
425
- ?
426
- )`
427
- ).run(configKey, JSON.stringify(config), configKey, updatedAt, updatedAt);
428
- }
429
-
430
- public getDaemonRuntimeConfig<T = unknown>(configKey: string): DaemonRuntimeConfigRecord<T> | undefined {
431
- const row = this.db.prepare(
432
- `SELECT config_key, config_json, created_at, updated_at
433
- FROM daemon_runtime_config
434
- WHERE config_key = ?`
435
- ).get(configKey) as {
436
- config_key: string;
437
- config_json: string;
438
- created_at: string;
439
- updated_at: string;
440
- } | undefined;
441
- if (!row) {
442
- return undefined;
443
- }
444
- return {
445
- configKey: row.config_key,
446
- config: JSON.parse(row.config_json) as T,
447
- createdAt: row.created_at,
448
- updatedAt: row.updated_at
449
- };
450
- }
451
-
452
- public removeDaemonRuntimeConfig(configKey: string): boolean {
453
- const result = this.db.prepare("DELETE FROM daemon_runtime_config WHERE config_key = ?").run(configKey) as { changes: number };
454
- return result.changes > 0;
455
- }
456
-
457
- public getToken(sellerKey: string): CachedToken | undefined {
458
- const stmt = this.db.prepare(
459
- "SELECT token, balance_micros, reserved_micros, spent_micros, balance_source, expires_at FROM token_cache WHERE seller_key = ?"
460
- );
461
- const row = stmt.get(sellerKey) as {
462
- token: string;
463
- balance_micros: number;
464
- reserved_micros: number;
465
- spent_micros: number;
466
- balance_source: string | null;
467
- expires_at: string | null;
468
- } | undefined;
469
- if (!row) {
470
- return undefined;
471
- }
472
- return {
473
- token: row.token,
474
- balanceMicros: row.balance_micros,
475
- reservedMicros: row.reserved_micros,
476
- spentMicros: row.spent_micros,
477
- balanceSource: row.balance_source || undefined,
478
- expiresAt: row.expires_at || undefined
479
- };
480
- }
481
-
482
- public saveToken(sellerKey: string, token: string, tokenClass: string, balanceMicros: number, expiresAt: string): void {
483
- const stmt = this.db.prepare(
484
- `INSERT OR REPLACE INTO token_cache (
485
- seller_key, token, token_class, balance_micros, reserved_micros,
486
- spent_micros, balance_source, expires_at, updated_at
487
- ) VALUES (?, ?, ?, ?, 0, 0, 'purchase_complete', ?, ?)`
488
- );
489
- stmt.run(sellerKey, token, tokenClass, balanceMicros, expiresAt, nowIso());
490
- }
491
-
492
- public reconcileTokenBalance(input: TokenBalanceSnapshotInput): void {
493
- const current = this.db.prepare(
494
- "SELECT token, token_class, expires_at FROM token_cache WHERE seller_key = ?"
495
- ).get(input.sellerKey) as {
496
- token: string;
497
- token_class: string;
498
- expires_at: string;
499
- } | undefined;
500
- const token = input.token || current?.token;
501
- const tokenClass = input.tokenClass || current?.token_class || "seller";
502
- const expiresAt = input.expiresAt || current?.expires_at || new Date(Date.now() + 86400 * 1000).toISOString();
503
- if (!token) {
504
- return;
505
- }
506
- this.db.prepare(
507
- `INSERT OR REPLACE INTO token_cache (
508
- seller_key, token, token_class, balance_micros, reserved_micros,
509
- spent_micros, balance_source, expires_at, updated_at
510
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
511
- ).run(
512
- input.sellerKey,
513
- token,
514
- tokenClass,
515
- input.balanceMicros,
516
- input.reservedMicros ?? 0,
517
- input.spentMicros ?? 0,
518
- input.balanceSource,
519
- expiresAt,
520
- nowIso()
521
- );
522
- logger.info("token.cache.reconciled", "token cache reconciled from seller balance snapshot", {
523
- sellerKey: input.sellerKey,
524
- balanceMicros: input.balanceMicros,
525
- reservedMicros: input.reservedMicros ?? 0,
526
- spentMicros: input.spentMicros ?? 0,
527
- balanceSource: input.balanceSource
528
- });
529
- }
530
-
531
- public markTokenStale(sellerKey: string): void {
532
- this.db.prepare(
533
- "UPDATE token_cache SET balance_micros = 0, balance_source = 'stale', updated_at = ? WHERE seller_key = ?"
534
- ).run(nowIso(), sellerKey);
535
- }
536
-
537
- public listPayments(): PaymentConfig[] {
538
- const rows = this.db.prepare(
539
- `SELECT method, enabled, is_default, config_json, updated_at
540
- FROM payment_config
541
- ORDER BY method ASC`
542
- ).all() as Array<{
543
- method: string;
544
- enabled: number;
545
- is_default: number;
546
- config_json: string | null;
547
- updated_at: string;
548
- }>;
549
-
550
- return rows.map(row => ({
551
- method: row.method,
552
- enabled: boolFromSql(row.enabled),
553
- isDefault: boolFromSql(row.is_default),
554
- config: row.config_json ? JSON.parse(row.config_json) as Record<string, unknown> : undefined,
555
- updatedAt: row.updated_at
556
- }));
557
- }
558
-
559
- public savePayment(config: Omit<PaymentConfig, "updatedAt">): void {
560
- const updatedAt = nowIso();
561
- if (config.isDefault) {
562
- this.db.prepare("UPDATE payment_config SET is_default = 0").run();
563
- }
564
- this.db.prepare(
565
- `INSERT OR REPLACE INTO payment_config (
566
- method, enabled, is_default, config_json, created_at, updated_at
567
- ) VALUES (
568
- ?, ?, ?, ?,
569
- COALESCE((SELECT created_at FROM payment_config WHERE method = ?), ?),
570
- ?
571
- )`
572
- ).run(
573
- config.method,
574
- config.enabled ? 1 : 0,
575
- config.isDefault ? 1 : 0,
576
- config.config ? JSON.stringify(config.config) : null,
577
- config.method,
578
- updatedAt,
579
- updatedAt
580
- );
581
- }
582
-
583
- public getPayment(method: string): PaymentConfig | undefined {
584
- return this.listPayments().find((payment) => payment.method === method);
585
- }
586
-
587
- public removePayment(method: string): boolean {
588
- const result = this.db.prepare("DELETE FROM payment_config WHERE method = ?").run(method) as { changes: number };
589
- return result.changes > 0;
590
- }
591
-
592
- public upsertPendingPurchase(input: PendingPurchaseInput): void {
593
- const updatedAt = nowIso();
594
- this.db.prepare(
595
- `INSERT OR REPLACE INTO pending_purchases (
596
- purchase_id, seller_key, model_id, payment_method, amount_usd_micros,
597
- status, payment_reference_hash, created_at, updated_at, expires_at
598
- ) VALUES (
599
- ?, ?, ?, ?, ?, ?, ?,
600
- COALESCE((SELECT created_at FROM pending_purchases WHERE purchase_id = ?), ?),
601
- ?, ?
602
- )`
603
- ).run(
604
- input.purchaseId,
605
- input.sellerKey,
606
- input.modelId,
607
- input.paymentMethod,
608
- input.amountUsdMicros,
609
- input.status,
610
- safeHash(input.paymentReference) || null,
611
- input.purchaseId,
612
- updatedAt,
613
- updatedAt,
614
- input.expiresAt || null
615
- );
616
- }
617
-
618
- public listPendingPurchases(): Array<PendingPurchaseInput & { paymentReferenceHash?: string; updatedAt: string }> {
619
- const rows = this.db.prepare(
620
- `SELECT purchase_id, seller_key, model_id, payment_method, amount_usd_micros,
621
- status, payment_reference_hash, updated_at, expires_at
622
- FROM pending_purchases
623
- ORDER BY updated_at DESC, purchase_id ASC`
624
- ).all() as Array<{
625
- purchase_id: string;
626
- seller_key: string;
627
- model_id: string;
628
- payment_method: string;
629
- amount_usd_micros: number;
630
- status: string;
631
- payment_reference_hash: string | null;
632
- updated_at: string;
633
- expires_at: string | null;
634
- }>;
635
-
636
- return rows.map(row => ({
637
- purchaseId: row.purchase_id,
638
- sellerKey: row.seller_key,
639
- modelId: row.model_id,
640
- paymentMethod: row.payment_method,
641
- amountUsdMicros: row.amount_usd_micros,
642
- status: row.status,
643
- paymentReferenceHash: row.payment_reference_hash || undefined,
644
- updatedAt: row.updated_at,
645
- expiresAt: row.expires_at || undefined
646
- }));
647
- }
648
-
649
- public recordPurchaseLedger(input: PurchaseLedgerInput): void {
650
- const createdAt = nowIso();
651
- this.db.prepare(
652
- `INSERT INTO purchase_ledger (
653
- purchase_id, seller_key, model_id, payment_method, status, credit_micros,
654
- currency, payment_amount, payment_amount_minor, payment_currency,
655
- payment_reference_hash, created_at, completed_at
656
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
657
- ).run(
658
- input.purchaseId,
659
- input.sellerKey,
660
- input.modelId,
661
- input.paymentMethod,
662
- input.status,
663
- input.creditMicros,
664
- input.currency,
665
- input.paymentAmount ?? null,
666
- input.paymentAmountMinor ?? null,
667
- input.paymentCurrency ?? null,
668
- safeHash(input.paymentReference) || null,
669
- createdAt,
670
- input.completedAt || null
671
- );
672
- }
673
-
674
- public listPurchaseLedger(): SafePurchaseLedgerEntry[] {
675
- const rows = this.db.prepare(
676
- `SELECT purchase_id, seller_key, model_id, payment_method, status, credit_micros,
677
- currency, payment_amount, payment_amount_minor, payment_currency,
678
- payment_reference_hash, created_at, completed_at
679
- FROM purchase_ledger
680
- ORDER BY id ASC`
681
- ).all() as Array<{
682
- purchase_id: string;
683
- seller_key: string;
684
- model_id: string;
685
- payment_method: string;
686
- status: string;
687
- credit_micros: number;
688
- currency: string;
689
- payment_amount: string | null;
690
- payment_amount_minor: number | null;
691
- payment_currency: string | null;
692
- payment_reference_hash: string | null;
693
- created_at: string;
694
- completed_at: string | null;
695
- }>;
696
-
697
- return rows.map(row => ({
698
- purchaseId: row.purchase_id,
699
- sellerKey: row.seller_key,
700
- modelId: row.model_id,
701
- paymentMethod: row.payment_method,
702
- status: row.status,
703
- creditMicros: row.credit_micros,
704
- currency: row.currency,
705
- paymentAmount: row.payment_amount || undefined,
706
- paymentAmountMinor: row.payment_amount_minor ?? undefined,
707
- paymentCurrency: row.payment_currency || undefined,
708
- paymentReferenceHash: row.payment_reference_hash || undefined,
709
- createdAt: row.created_at,
710
- completedAt: row.completed_at || undefined
711
- }));
712
- }
713
-
714
- public recordInferenceLedger(input: InferenceLedgerInput): void {
715
- this.db.prepare(
716
- `INSERT INTO inference_ledger (
717
- request_id, seller_key, model_id, endpoint, status, prompt_tokens,
718
- completion_tokens, cache_read_tokens, billed_micros, estimated_micros, settled_micros,
719
- settled_usd_micros, price_version,
720
- input_price_micros_per_1m, output_price_micros_per_1m, cache_read_price_micros_per_1m,
721
- input_cost_micros, output_cost_micros, cache_read_cost_micros,
722
- original_usd_micros, billing_multiplier, service_tier,
723
- billing_unit, image_count, image_size, image_quality, image_output_format,
724
- image_output_tokens, image_output_cost_micros, image_cost_micros_per_image,
725
- balance_snapshot_micros, balance_source,
726
- prompt_hash, response_hash, created_at,
727
- ttft_ms, fallback_count, route_reason, fallover_chain_json, upstream_status,
728
- duration_ms, payment_method
729
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
730
- ).run(
731
- input.requestId,
732
- input.sellerKey,
733
- input.modelId,
734
- input.endpoint,
735
- input.status,
736
- input.promptTokens,
737
- input.completionTokens,
738
- input.cacheReadTokens ?? 0,
739
- input.billedMicros,
740
- input.estimatedMicros ?? input.billedMicros,
741
- input.settledMicros ?? null,
742
- input.settledUsdMicros ?? null,
743
- input.priceVersion || null,
744
- input.inputPriceMicrosPer1m ?? null,
745
- input.outputPriceMicrosPer1m ?? null,
746
- input.cacheReadPriceMicrosPer1m ?? null,
747
- input.inputCostMicros ?? null,
748
- input.outputCostMicros ?? null,
749
- input.cacheReadCostMicros ?? null,
750
- input.originalUsdMicros ?? null,
751
- input.billingMultiplier ?? null,
752
- input.serviceTier ?? null,
753
- input.billingUnit ?? (input.endpoint === "/v1/images/generations" ? "images" : "tokens"),
754
- input.imageCount ?? null,
755
- input.imageSize ?? null,
756
- input.imageQuality ?? null,
757
- input.imageOutputFormat ?? null,
758
- input.imageOutputTokens ?? null,
759
- input.imageOutputCostMicros ?? null,
760
- input.imageCostMicrosPerImage ?? null,
761
- input.balanceSnapshotMicros ?? null,
762
- input.balanceSource || "unknown",
763
- safeHash(input.prompt) || null,
764
- safeHash(input.response) || null,
765
- nowIso(),
766
- input.ttftMs ?? null,
767
- input.fallbackCount ?? null,
768
- input.routeReason ?? null,
769
- input.falloverChain ? JSON.stringify(input.falloverChain) : null,
770
- input.upstreamStatus ?? null,
771
- input.durationMs ?? null,
772
- input.paymentMethod ?? null
773
- );
774
- }
775
-
776
- public listInferenceLedger(): SafeInferenceLedgerEntry[] {
777
- const rows = this.db.prepare(
778
- `SELECT request_id, seller_key, model_id, endpoint, status, prompt_tokens,
779
- completion_tokens, cache_read_tokens, billed_micros, estimated_micros, settled_micros,
780
- settled_usd_micros, price_version,
781
- input_price_micros_per_1m, output_price_micros_per_1m, cache_read_price_micros_per_1m,
782
- input_cost_micros, output_cost_micros, cache_read_cost_micros,
783
- original_usd_micros, billing_multiplier, service_tier,
784
- billing_unit, image_count, image_size, image_quality, image_output_format,
785
- image_output_tokens, image_output_cost_micros, image_cost_micros_per_image,
786
- balance_snapshot_micros, balance_source,
787
- prompt_hash, response_hash, created_at,
788
- ttft_ms, fallback_count, route_reason, fallover_chain_json, upstream_status,
789
- duration_ms, payment_method
790
- FROM inference_ledger
791
- ORDER BY id ASC`
792
- ).all() as Array<{
793
- request_id: string;
794
- seller_key: string;
795
- model_id: string;
796
- endpoint: string;
797
- status: string;
798
- prompt_tokens: number;
799
- completion_tokens: number;
800
- cache_read_tokens: number;
801
- billed_micros: number;
802
- estimated_micros: number | null;
803
- settled_micros: number | null;
804
- settled_usd_micros: number | null;
805
- price_version: string | null;
806
- input_price_micros_per_1m: number | null;
807
- output_price_micros_per_1m: number | null;
808
- cache_read_price_micros_per_1m: number | null;
809
- input_cost_micros: number | null;
810
- output_cost_micros: number | null;
811
- cache_read_cost_micros: number | null;
812
- original_usd_micros: number | null;
813
- billing_multiplier: number | null;
814
- service_tier: string | null;
815
- billing_unit: string | null;
816
- image_count: number | null;
817
- image_size: string | null;
818
- image_quality: string | null;
819
- image_output_format: string | null;
820
- image_output_tokens: number | null;
821
- image_output_cost_micros: number | null;
822
- image_cost_micros_per_image: number | null;
823
- balance_snapshot_micros: number | null;
824
- balance_source: string | null;
825
- prompt_hash: string | null;
826
- response_hash: string | null;
827
- created_at: string;
828
- ttft_ms: number | null;
829
- fallback_count: number | null;
830
- route_reason: string | null;
831
- fallover_chain_json: string | null;
832
- upstream_status: string | null;
833
- duration_ms: number | null;
834
- payment_method: string | null;
835
- }>;
836
-
837
- return rows.map(row => ({
838
- requestId: row.request_id,
839
- sellerKey: row.seller_key,
840
- modelId: row.model_id,
841
- endpoint: row.endpoint,
842
- status: row.status,
843
- promptTokens: row.prompt_tokens,
844
- completionTokens: row.completion_tokens,
845
- cacheReadTokens: row.cache_read_tokens,
846
- billedMicros: row.billed_micros,
847
- estimatedMicros: row.estimated_micros ?? undefined,
848
- settledMicros: row.settled_micros ?? undefined,
849
- settledUsdMicros: row.settled_usd_micros ?? undefined,
850
- priceVersion: row.price_version || undefined,
851
- inputPriceMicrosPer1m: row.input_price_micros_per_1m ?? undefined,
852
- outputPriceMicrosPer1m: row.output_price_micros_per_1m ?? undefined,
853
- cacheReadPriceMicrosPer1m: row.cache_read_price_micros_per_1m ?? undefined,
854
- inputCostMicros: row.input_cost_micros ?? undefined,
855
- outputCostMicros: row.output_cost_micros ?? undefined,
856
- cacheReadCostMicros: row.cache_read_cost_micros ?? undefined,
857
- originalUsdMicros: row.original_usd_micros ?? undefined,
858
- billingMultiplier: row.billing_multiplier ?? undefined,
859
- serviceTier: row.service_tier || undefined,
860
- billingUnit: row.billing_unit === "images" ? "images" : row.billing_unit === "tokens" ? "tokens" : undefined,
861
- imageCount: row.image_count ?? undefined,
862
- imageSize: row.image_size || undefined,
863
- imageQuality: row.image_quality || undefined,
864
- imageOutputFormat: row.image_output_format || undefined,
865
- imageOutputTokens: row.image_output_tokens ?? undefined,
866
- imageOutputCostMicros: row.image_output_cost_micros ?? undefined,
867
- imageCostMicrosPerImage: row.image_cost_micros_per_image ?? undefined,
868
- balanceSnapshotMicros: row.balance_snapshot_micros ?? undefined,
869
- balanceSource: row.balance_source || undefined,
870
- promptHash: row.prompt_hash || undefined,
871
- responseHash: row.response_hash || undefined,
872
- createdAt: row.created_at,
873
- ttftMs: row.ttft_ms ?? undefined,
874
- fallbackCount: row.fallback_count ?? undefined,
875
- routeReason: row.route_reason ?? undefined,
876
- falloverChain: row.fallover_chain_json ? JSON.parse(row.fallover_chain_json) as string[] : undefined,
877
- upstreamStatus: row.upstream_status ?? undefined,
878
- durationMs: row.duration_ms ?? undefined,
879
- paymentMethod: row.payment_method ?? undefined
880
- }));
881
- }
882
-
883
- public close(): void {
884
- this.db.close();
885
- }
886
-
887
- private initSchema(): void {
888
- this.db.exec(`
889
- CREATE TABLE IF NOT EXISTS token_cache (
890
- seller_key TEXT PRIMARY KEY,
891
- token TEXT NOT NULL,
892
- token_class TEXT NOT NULL,
893
- balance_micros INTEGER NOT NULL,
894
- reserved_micros INTEGER NOT NULL DEFAULT 0,
895
- spent_micros INTEGER NOT NULL DEFAULT 0,
896
- balance_source TEXT,
897
- expires_at TEXT NOT NULL,
898
- updated_at TEXT NOT NULL
899
- );
900
-
901
- CREATE TABLE IF NOT EXISTS payment_config (
902
- method TEXT PRIMARY KEY,
903
- enabled INTEGER NOT NULL,
904
- is_default INTEGER NOT NULL DEFAULT 0,
905
- config_json TEXT,
906
- created_at TEXT NOT NULL,
907
- updated_at TEXT NOT NULL
908
- );
909
-
910
- CREATE TABLE IF NOT EXISTS pending_purchases (
911
- purchase_id TEXT PRIMARY KEY,
912
- seller_key TEXT NOT NULL,
913
- model_id TEXT NOT NULL,
914
- payment_method TEXT NOT NULL,
915
- amount_usd_micros INTEGER NOT NULL,
916
- status TEXT NOT NULL,
917
- payment_reference_hash TEXT,
918
- created_at TEXT NOT NULL,
919
- updated_at TEXT NOT NULL,
920
- expires_at TEXT
921
- );
922
-
923
- CREATE TABLE IF NOT EXISTS purchase_ledger (
924
- id INTEGER PRIMARY KEY AUTOINCREMENT,
925
- purchase_id TEXT NOT NULL,
926
- seller_key TEXT NOT NULL,
927
- model_id TEXT NOT NULL,
928
- payment_method TEXT NOT NULL,
929
- status TEXT NOT NULL,
930
- credit_micros INTEGER NOT NULL,
931
- currency TEXT NOT NULL,
932
- payment_amount TEXT,
933
- payment_amount_minor INTEGER,
934
- payment_currency TEXT,
935
- payment_reference_hash TEXT,
936
- created_at TEXT NOT NULL,
937
- completed_at TEXT
938
- );
939
-
940
- CREATE TABLE IF NOT EXISTS inference_ledger (
941
- id INTEGER PRIMARY KEY AUTOINCREMENT,
942
- request_id TEXT NOT NULL,
943
- seller_key TEXT NOT NULL,
944
- model_id TEXT NOT NULL,
945
- endpoint TEXT NOT NULL,
946
- status TEXT NOT NULL,
947
- prompt_tokens INTEGER NOT NULL,
948
- completion_tokens INTEGER NOT NULL,
949
- cache_read_tokens INTEGER NOT NULL DEFAULT 0,
950
- billed_micros INTEGER NOT NULL,
951
- estimated_micros INTEGER,
952
- settled_micros INTEGER,
953
- settled_usd_micros INTEGER,
954
- price_version TEXT,
955
- input_price_micros_per_1m INTEGER,
956
- output_price_micros_per_1m INTEGER,
957
- cache_read_price_micros_per_1m INTEGER,
958
- input_cost_micros INTEGER,
959
- output_cost_micros INTEGER,
960
- cache_read_cost_micros INTEGER,
961
- original_usd_micros INTEGER,
962
- billing_multiplier REAL,
963
- service_tier TEXT,
964
- billing_unit TEXT,
965
- image_count INTEGER,
966
- image_size TEXT,
967
- image_quality TEXT,
968
- image_output_format TEXT,
969
- image_output_tokens INTEGER,
970
- image_output_cost_micros INTEGER,
971
- image_cost_micros_per_image INTEGER,
972
- balance_snapshot_micros INTEGER,
973
- balance_source TEXT,
974
- prompt_hash TEXT,
975
- response_hash TEXT,
976
- created_at TEXT NOT NULL,
977
- ttft_ms INTEGER,
978
- fallback_count INTEGER,
979
- route_reason TEXT,
980
- fallover_chain_json TEXT,
981
- upstream_status TEXT,
982
- duration_ms INTEGER,
983
- payment_method TEXT
984
- );
985
-
986
- CREATE TABLE IF NOT EXISTS provider_install_state (
987
- provider_id TEXT PRIMARY KEY,
988
- snapshot_json TEXT NOT NULL,
989
- created_at TEXT NOT NULL,
990
- updated_at TEXT NOT NULL
991
- );
992
-
993
- CREATE TABLE IF NOT EXISTS provider_runtime_config (
994
- provider_id TEXT PRIMARY KEY,
995
- runtime_json TEXT NOT NULL,
996
- created_at TEXT NOT NULL,
997
- updated_at TEXT NOT NULL
998
- );
999
-
1000
- CREATE TABLE IF NOT EXISTS daemon_runtime_config (
1001
- config_key TEXT PRIMARY KEY,
1002
- config_json TEXT NOT NULL,
1003
- created_at TEXT NOT NULL,
1004
- updated_at TEXT NOT NULL
1005
- );
1006
- `);
1007
- for (const [column, definition] of [
1008
- ["reserved_micros", "INTEGER NOT NULL DEFAULT 0"],
1009
- ["spent_micros", "INTEGER NOT NULL DEFAULT 0"],
1010
- ["balance_source", "TEXT"]
1011
- ]) {
1012
- this.ensureColumn("token_cache", column, definition);
1013
- }
1014
- for (const [column, definition] of [
1015
- ["payment_amount", "TEXT"],
1016
- ["payment_amount_minor", "INTEGER"],
1017
- ["payment_currency", "TEXT"]
1018
- ]) {
1019
- this.ensureColumn("purchase_ledger", column, definition);
1020
- }
1021
- for (const [column, definition] of [
1022
- ["estimated_micros", "INTEGER"],
1023
- ["cache_read_tokens", "INTEGER NOT NULL DEFAULT 0"],
1024
- ["settled_micros", "INTEGER"],
1025
- ["settled_usd_micros", "INTEGER"],
1026
- ["price_version", "TEXT"],
1027
- ["input_price_micros_per_1m", "INTEGER"],
1028
- ["output_price_micros_per_1m", "INTEGER"],
1029
- ["cache_read_price_micros_per_1m", "INTEGER"],
1030
- ["input_cost_micros", "INTEGER"],
1031
- ["output_cost_micros", "INTEGER"],
1032
- ["cache_read_cost_micros", "INTEGER"],
1033
- ["original_usd_micros", "INTEGER"],
1034
- ["billing_multiplier", "REAL"],
1035
- ["service_tier", "TEXT"],
1036
- ["billing_unit", "TEXT"],
1037
- ["image_count", "INTEGER"],
1038
- ["image_size", "TEXT"],
1039
- ["image_quality", "TEXT"],
1040
- ["image_output_format", "TEXT"],
1041
- ["image_output_tokens", "INTEGER"],
1042
- ["image_output_cost_micros", "INTEGER"],
1043
- ["image_cost_micros_per_image", "INTEGER"],
1044
- ["balance_snapshot_micros", "INTEGER"],
1045
- ["balance_source", "TEXT"],
1046
- ["ttft_ms", "INTEGER"],
1047
- ["fallback_count", "INTEGER"],
1048
- ["route_reason", "TEXT"],
1049
- ["fallover_chain_json", "TEXT"],
1050
- ["upstream_status", "TEXT"],
1051
- ["duration_ms", "INTEGER"],
1052
- ["payment_method", "TEXT"]
1053
- ]) {
1054
- this.ensureColumn("inference_ledger", column, definition);
1055
- }
1056
- }
1057
-
1058
- private ensureColumn(table: string, column: string, definition: string): void {
1059
- const rows = this.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
1060
- if (!rows.some((row) => row.name === column)) {
1061
- this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
1062
- }
1063
- }
1064
-
1065
- private countRows(tableName: string): number {
1066
- const row = this.db.prepare(`SELECT COUNT(*) AS count FROM ${tableName}`).get() as { count: number };
1067
- return row.count;
1068
- }
1069
-
1070
- /**
1071
- * v1.2 §18.4: aggregate inference-ledger rows from the last `days`
1072
- * window and return the top `limit` most-used model ids. The focus-set
1073
- * builder uses this when no explicit warmup configuration is provided.
1074
- */
1075
- public recentModels(days: number, limit: number): string[] {
1076
- if (days <= 0 || limit <= 0) {
1077
- return [];
1078
- }
1079
- const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
1080
- const rows = this.db.prepare(
1081
- `SELECT model_id, COUNT(*) AS uses
1082
- FROM inference_ledger
1083
- WHERE created_at >= ? AND model_id IS NOT NULL AND model_id != ''
1084
- GROUP BY model_id
1085
- ORDER BY uses DESC, model_id ASC
1086
- LIMIT ?`
1087
- ).all(cutoff, limit) as Array<{ model_id: string; uses: number }>;
1088
- return rows.map((row) => row.model_id);
1089
- }
1090
- }