@tokenbuddy/tokenbuddy 1.0.5 → 1.0.7
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/src/buyer-store.d.ts +48 -1
- package/dist/src/buyer-store.d.ts.map +1 -1
- package/dist/src/buyer-store.js +144 -17
- package/dist/src/buyer-store.js.map +1 -1
- package/dist/src/cli.d.ts +17 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +560 -63
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +11 -5
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +574 -161
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/doctor-clawtip-wallet.d.ts +14 -0
- package/dist/src/doctor-clawtip-wallet.d.ts.map +1 -0
- package/dist/src/doctor-clawtip-wallet.js +54 -0
- package/dist/src/doctor-clawtip-wallet.js.map +1 -0
- package/dist/src/doctor-diagnostics.d.ts +99 -0
- package/dist/src/doctor-diagnostics.d.ts.map +1 -0
- package/dist/src/doctor-diagnostics.js +552 -0
- package/dist/src/doctor-diagnostics.js.map +1 -0
- package/dist/src/init-clawtip-activation.d.ts +48 -0
- package/dist/src/init-clawtip-activation.d.ts.map +1 -0
- package/dist/src/init-clawtip-activation.js +395 -0
- package/dist/src/init-clawtip-activation.js.map +1 -0
- package/dist/src/init-payment-options.d.ts +56 -0
- package/dist/src/init-payment-options.d.ts.map +1 -0
- package/dist/src/init-payment-options.js +165 -0
- package/dist/src/init-payment-options.js.map +1 -0
- package/dist/src/provider-install.d.ts +37 -2
- package/dist/src/provider-install.d.ts.map +1 -1
- package/dist/src/provider-install.js +317 -67
- package/dist/src/provider-install.js.map +1 -1
- package/dist/src/seller-catalog.d.ts +79 -0
- package/dist/src/seller-catalog.d.ts.map +1 -0
- package/dist/src/seller-catalog.js +126 -0
- package/dist/src/seller-catalog.js.map +1 -0
- package/dist/src/tb-proxyd.js +13 -2
- package/dist/src/tb-proxyd.js.map +1 -1
- package/dist/src/terminal-image.d.ts +22 -0
- package/dist/src/terminal-image.d.ts.map +1 -0
- package/dist/src/terminal-image.js +135 -0
- package/dist/src/terminal-image.js.map +1 -0
- package/package.json +1 -1
- package/src/buyer-store.ts +253 -18
- package/src/cli.ts +709 -68
- package/src/daemon.ts +651 -167
- package/src/doctor-clawtip-wallet.ts +70 -0
- package/src/doctor-diagnostics.ts +861 -0
- package/src/init-clawtip-activation.ts +487 -0
- package/src/init-payment-options.ts +249 -0
- package/src/provider-install.ts +426 -76
- package/src/seller-catalog.ts +222 -0
- package/src/tb-proxyd.ts +14 -2
- package/src/terminal-image.ts +187 -0
- package/tests/e2e.test.ts +88 -5
- package/tests/tokenbuddy.test.ts +1362 -27
package/src/buyer-store.ts
CHANGED
|
@@ -11,6 +11,9 @@ const logger = createModuleLogger("tb-proxyd");
|
|
|
11
11
|
export interface CachedToken {
|
|
12
12
|
token: string;
|
|
13
13
|
balanceMicros: number;
|
|
14
|
+
reservedMicros: number;
|
|
15
|
+
spentMicros: number;
|
|
16
|
+
balanceSource?: string;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export interface PaymentConfig {
|
|
@@ -66,6 +69,12 @@ export interface InferenceLedgerInput {
|
|
|
66
69
|
promptTokens: number;
|
|
67
70
|
completionTokens: number;
|
|
68
71
|
billedMicros: number;
|
|
72
|
+
estimatedMicros?: number;
|
|
73
|
+
settledMicros?: number;
|
|
74
|
+
settledUsdMicros?: number;
|
|
75
|
+
priceVersion?: string;
|
|
76
|
+
balanceSnapshotMicros?: number;
|
|
77
|
+
balanceSource?: string;
|
|
69
78
|
prompt?: string;
|
|
70
79
|
response?: string;
|
|
71
80
|
}
|
|
@@ -79,17 +88,36 @@ export interface SafeInferenceLedgerEntry {
|
|
|
79
88
|
promptTokens: number;
|
|
80
89
|
completionTokens: number;
|
|
81
90
|
billedMicros: number;
|
|
91
|
+
estimatedMicros?: number;
|
|
92
|
+
settledMicros?: number;
|
|
93
|
+
settledUsdMicros?: number;
|
|
94
|
+
priceVersion?: string;
|
|
95
|
+
balanceSnapshotMicros?: number;
|
|
96
|
+
balanceSource?: string;
|
|
82
97
|
promptHash?: string;
|
|
83
98
|
responseHash?: string;
|
|
84
99
|
createdAt: string;
|
|
85
100
|
}
|
|
86
101
|
|
|
102
|
+
export interface TokenBalanceSnapshotInput {
|
|
103
|
+
sellerKey: string;
|
|
104
|
+
token?: string;
|
|
105
|
+
tokenClass?: string;
|
|
106
|
+
balanceMicros: number;
|
|
107
|
+
reservedMicros?: number;
|
|
108
|
+
spentMicros?: number;
|
|
109
|
+
balanceSource: string;
|
|
110
|
+
expiresAt?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
87
113
|
export interface BuyerStoreSummary {
|
|
88
114
|
journalMode: string;
|
|
89
115
|
paymentsCount: number;
|
|
90
116
|
pendingPurchasesCount: number;
|
|
91
117
|
purchaseLedgerCount: number;
|
|
92
118
|
inferenceLedgerCount: number;
|
|
119
|
+
providerRuntimeConfigCount: number;
|
|
120
|
+
daemonRuntimeConfigCount: number;
|
|
93
121
|
}
|
|
94
122
|
|
|
95
123
|
export interface ProviderInstallSnapshot {
|
|
@@ -101,6 +129,20 @@ export interface ProviderInstallSnapshot {
|
|
|
101
129
|
}>;
|
|
102
130
|
}
|
|
103
131
|
|
|
132
|
+
export interface ProviderRuntimeConfigRecord<T = unknown> {
|
|
133
|
+
providerId: string;
|
|
134
|
+
config: T;
|
|
135
|
+
createdAt: string;
|
|
136
|
+
updatedAt: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface DaemonRuntimeConfigRecord<T = unknown> {
|
|
140
|
+
configKey: string;
|
|
141
|
+
config: T;
|
|
142
|
+
createdAt: string;
|
|
143
|
+
updatedAt: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
104
146
|
export interface BuyerStoreOptions {
|
|
105
147
|
root?: string;
|
|
106
148
|
dbPath?: string;
|
|
@@ -158,7 +200,9 @@ export class BuyerStore {
|
|
|
158
200
|
paymentsCount: this.countRows("payment_config"),
|
|
159
201
|
pendingPurchasesCount: this.countRows("pending_purchases"),
|
|
160
202
|
purchaseLedgerCount: this.countRows("purchase_ledger"),
|
|
161
|
-
inferenceLedgerCount: this.countRows("inference_ledger")
|
|
203
|
+
inferenceLedgerCount: this.countRows("inference_ledger"),
|
|
204
|
+
providerRuntimeConfigCount: this.countRows("provider_runtime_config"),
|
|
205
|
+
daemonRuntimeConfigCount: this.countRows("daemon_runtime_config")
|
|
162
206
|
};
|
|
163
207
|
}
|
|
164
208
|
|
|
@@ -196,42 +240,164 @@ export class BuyerStore {
|
|
|
196
240
|
return result.changes > 0;
|
|
197
241
|
}
|
|
198
242
|
|
|
243
|
+
public saveProviderRuntimeConfig(providerId: string, config: unknown): void {
|
|
244
|
+
const updatedAt = nowIso();
|
|
245
|
+
this.db.prepare(
|
|
246
|
+
`INSERT OR REPLACE INTO provider_runtime_config (
|
|
247
|
+
provider_id, runtime_json, created_at, updated_at
|
|
248
|
+
) VALUES (
|
|
249
|
+
?, ?,
|
|
250
|
+
COALESCE((SELECT created_at FROM provider_runtime_config WHERE provider_id = ?), ?),
|
|
251
|
+
?
|
|
252
|
+
)`
|
|
253
|
+
).run(providerId, JSON.stringify(config), providerId, updatedAt, updatedAt);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public getProviderRuntimeConfig<T = unknown>(providerId: string): ProviderRuntimeConfigRecord<T> | undefined {
|
|
257
|
+
const row = this.db.prepare(
|
|
258
|
+
`SELECT provider_id, runtime_json, created_at, updated_at
|
|
259
|
+
FROM provider_runtime_config
|
|
260
|
+
WHERE provider_id = ?`
|
|
261
|
+
).get(providerId) as {
|
|
262
|
+
provider_id: string;
|
|
263
|
+
runtime_json: string;
|
|
264
|
+
created_at: string;
|
|
265
|
+
updated_at: string;
|
|
266
|
+
} | undefined;
|
|
267
|
+
if (!row) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
providerId: row.provider_id,
|
|
272
|
+
config: JSON.parse(row.runtime_json) as T,
|
|
273
|
+
createdAt: row.created_at,
|
|
274
|
+
updatedAt: row.updated_at
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public removeProviderRuntimeConfig(providerId: string): boolean {
|
|
279
|
+
const result = this.db.prepare("DELETE FROM provider_runtime_config WHERE provider_id = ?").run(providerId) as { changes: number };
|
|
280
|
+
return result.changes > 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
public saveDaemonRuntimeConfig(configKey: string, config: unknown): void {
|
|
284
|
+
const updatedAt = nowIso();
|
|
285
|
+
this.db.prepare(
|
|
286
|
+
`INSERT OR REPLACE INTO daemon_runtime_config (
|
|
287
|
+
config_key, config_json, created_at, updated_at
|
|
288
|
+
) VALUES (
|
|
289
|
+
?, ?,
|
|
290
|
+
COALESCE((SELECT created_at FROM daemon_runtime_config WHERE config_key = ?), ?),
|
|
291
|
+
?
|
|
292
|
+
)`
|
|
293
|
+
).run(configKey, JSON.stringify(config), configKey, updatedAt, updatedAt);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
public getDaemonRuntimeConfig<T = unknown>(configKey: string): DaemonRuntimeConfigRecord<T> | undefined {
|
|
297
|
+
const row = this.db.prepare(
|
|
298
|
+
`SELECT config_key, config_json, created_at, updated_at
|
|
299
|
+
FROM daemon_runtime_config
|
|
300
|
+
WHERE config_key = ?`
|
|
301
|
+
).get(configKey) as {
|
|
302
|
+
config_key: string;
|
|
303
|
+
config_json: string;
|
|
304
|
+
created_at: string;
|
|
305
|
+
updated_at: string;
|
|
306
|
+
} | undefined;
|
|
307
|
+
if (!row) {
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
configKey: row.config_key,
|
|
312
|
+
config: JSON.parse(row.config_json) as T,
|
|
313
|
+
createdAt: row.created_at,
|
|
314
|
+
updatedAt: row.updated_at
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
public removeDaemonRuntimeConfig(configKey: string): boolean {
|
|
319
|
+
const result = this.db.prepare("DELETE FROM daemon_runtime_config WHERE config_key = ?").run(configKey) as { changes: number };
|
|
320
|
+
return result.changes > 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
199
323
|
public getToken(sellerKey: string): CachedToken | undefined {
|
|
200
|
-
const stmt = this.db.prepare(
|
|
201
|
-
|
|
324
|
+
const stmt = this.db.prepare(
|
|
325
|
+
"SELECT token, balance_micros, reserved_micros, spent_micros, balance_source FROM token_cache WHERE seller_key = ?"
|
|
326
|
+
);
|
|
327
|
+
const row = stmt.get(sellerKey) as {
|
|
328
|
+
token: string;
|
|
329
|
+
balance_micros: number;
|
|
330
|
+
reserved_micros: number;
|
|
331
|
+
spent_micros: number;
|
|
332
|
+
balance_source: string | null;
|
|
333
|
+
} | undefined;
|
|
202
334
|
if (!row) {
|
|
203
335
|
return undefined;
|
|
204
336
|
}
|
|
205
337
|
return {
|
|
206
338
|
token: row.token,
|
|
207
|
-
balanceMicros: row.balance_micros
|
|
339
|
+
balanceMicros: row.balance_micros,
|
|
340
|
+
reservedMicros: row.reserved_micros,
|
|
341
|
+
spentMicros: row.spent_micros,
|
|
342
|
+
balanceSource: row.balance_source || undefined
|
|
208
343
|
};
|
|
209
344
|
}
|
|
210
345
|
|
|
211
346
|
public saveToken(sellerKey: string, token: string, tokenClass: string, balanceMicros: number, expiresAt: string): void {
|
|
212
347
|
const stmt = this.db.prepare(
|
|
213
348
|
`INSERT OR REPLACE INTO token_cache (
|
|
214
|
-
seller_key, token, token_class, balance_micros,
|
|
215
|
-
|
|
349
|
+
seller_key, token, token_class, balance_micros, reserved_micros,
|
|
350
|
+
spent_micros, balance_source, expires_at, updated_at
|
|
351
|
+
) VALUES (?, ?, ?, ?, 0, 0, 'purchase_complete', ?, ?)`
|
|
216
352
|
);
|
|
217
353
|
stmt.run(sellerKey, token, tokenClass, balanceMicros, expiresAt, nowIso());
|
|
218
354
|
}
|
|
219
355
|
|
|
220
|
-
public
|
|
221
|
-
const
|
|
356
|
+
public reconcileTokenBalance(input: TokenBalanceSnapshotInput): void {
|
|
357
|
+
const current = this.db.prepare(
|
|
358
|
+
"SELECT token, token_class, expires_at FROM token_cache WHERE seller_key = ?"
|
|
359
|
+
).get(input.sellerKey) as {
|
|
360
|
+
token: string;
|
|
361
|
+
token_class: string;
|
|
362
|
+
expires_at: string;
|
|
363
|
+
} | undefined;
|
|
364
|
+
const token = input.token || current?.token;
|
|
365
|
+
const tokenClass = input.tokenClass || current?.token_class || "seller";
|
|
366
|
+
const expiresAt = input.expiresAt || current?.expires_at || new Date(Date.now() + 86400 * 1000).toISOString();
|
|
222
367
|
if (!token) {
|
|
223
368
|
return;
|
|
224
369
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
370
|
+
this.db.prepare(
|
|
371
|
+
`INSERT OR REPLACE INTO token_cache (
|
|
372
|
+
seller_key, token, token_class, balance_micros, reserved_micros,
|
|
373
|
+
spent_micros, balance_source, expires_at, updated_at
|
|
374
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
375
|
+
).run(
|
|
376
|
+
input.sellerKey,
|
|
377
|
+
token,
|
|
378
|
+
tokenClass,
|
|
379
|
+
input.balanceMicros,
|
|
380
|
+
input.reservedMicros ?? 0,
|
|
381
|
+
input.spentMicros ?? 0,
|
|
382
|
+
input.balanceSource,
|
|
383
|
+
expiresAt,
|
|
384
|
+
nowIso()
|
|
385
|
+
);
|
|
386
|
+
logger.info("token.cache.reconciled", "token cache reconciled from seller balance snapshot", {
|
|
387
|
+
sellerKey: input.sellerKey,
|
|
388
|
+
balanceMicros: input.balanceMicros,
|
|
389
|
+
reservedMicros: input.reservedMicros ?? 0,
|
|
390
|
+
spentMicros: input.spentMicros ?? 0,
|
|
391
|
+
balanceSource: input.balanceSource
|
|
232
392
|
});
|
|
233
393
|
}
|
|
234
394
|
|
|
395
|
+
public markTokenStale(sellerKey: string): void {
|
|
396
|
+
this.db.prepare(
|
|
397
|
+
"UPDATE token_cache SET balance_micros = 0, balance_source = 'stale', updated_at = ? WHERE seller_key = ?"
|
|
398
|
+
).run(nowIso(), sellerKey);
|
|
399
|
+
}
|
|
400
|
+
|
|
235
401
|
public listPayments(): PaymentConfig[] {
|
|
236
402
|
const rows = this.db.prepare(
|
|
237
403
|
`SELECT method, enabled, is_default, config_json, updated_at
|
|
@@ -402,8 +568,10 @@ export class BuyerStore {
|
|
|
402
568
|
this.db.prepare(
|
|
403
569
|
`INSERT INTO inference_ledger (
|
|
404
570
|
request_id, seller_key, model_id, endpoint, status, prompt_tokens,
|
|
405
|
-
completion_tokens, billed_micros,
|
|
406
|
-
|
|
571
|
+
completion_tokens, billed_micros, estimated_micros, settled_micros,
|
|
572
|
+
settled_usd_micros, price_version, balance_snapshot_micros, balance_source,
|
|
573
|
+
prompt_hash, response_hash, created_at
|
|
574
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
407
575
|
).run(
|
|
408
576
|
input.requestId,
|
|
409
577
|
input.sellerKey,
|
|
@@ -413,6 +581,12 @@ export class BuyerStore {
|
|
|
413
581
|
input.promptTokens,
|
|
414
582
|
input.completionTokens,
|
|
415
583
|
input.billedMicros,
|
|
584
|
+
input.estimatedMicros ?? input.billedMicros,
|
|
585
|
+
input.settledMicros ?? null,
|
|
586
|
+
input.settledUsdMicros ?? null,
|
|
587
|
+
input.priceVersion || null,
|
|
588
|
+
input.balanceSnapshotMicros ?? null,
|
|
589
|
+
input.balanceSource || "unknown",
|
|
416
590
|
safeHash(input.prompt) || null,
|
|
417
591
|
safeHash(input.response) || null,
|
|
418
592
|
nowIso()
|
|
@@ -422,7 +596,9 @@ export class BuyerStore {
|
|
|
422
596
|
public listInferenceLedger(): SafeInferenceLedgerEntry[] {
|
|
423
597
|
const rows = this.db.prepare(
|
|
424
598
|
`SELECT request_id, seller_key, model_id, endpoint, status, prompt_tokens,
|
|
425
|
-
completion_tokens, billed_micros,
|
|
599
|
+
completion_tokens, billed_micros, estimated_micros, settled_micros,
|
|
600
|
+
settled_usd_micros, price_version, balance_snapshot_micros, balance_source,
|
|
601
|
+
prompt_hash, response_hash, created_at
|
|
426
602
|
FROM inference_ledger
|
|
427
603
|
ORDER BY id ASC`
|
|
428
604
|
).all() as Array<{
|
|
@@ -434,6 +610,12 @@ export class BuyerStore {
|
|
|
434
610
|
prompt_tokens: number;
|
|
435
611
|
completion_tokens: number;
|
|
436
612
|
billed_micros: number;
|
|
613
|
+
estimated_micros: number | null;
|
|
614
|
+
settled_micros: number | null;
|
|
615
|
+
settled_usd_micros: number | null;
|
|
616
|
+
price_version: string | null;
|
|
617
|
+
balance_snapshot_micros: number | null;
|
|
618
|
+
balance_source: string | null;
|
|
437
619
|
prompt_hash: string | null;
|
|
438
620
|
response_hash: string | null;
|
|
439
621
|
created_at: string;
|
|
@@ -448,6 +630,12 @@ export class BuyerStore {
|
|
|
448
630
|
promptTokens: row.prompt_tokens,
|
|
449
631
|
completionTokens: row.completion_tokens,
|
|
450
632
|
billedMicros: row.billed_micros,
|
|
633
|
+
estimatedMicros: row.estimated_micros ?? undefined,
|
|
634
|
+
settledMicros: row.settled_micros ?? undefined,
|
|
635
|
+
settledUsdMicros: row.settled_usd_micros ?? undefined,
|
|
636
|
+
priceVersion: row.price_version || undefined,
|
|
637
|
+
balanceSnapshotMicros: row.balance_snapshot_micros ?? undefined,
|
|
638
|
+
balanceSource: row.balance_source || undefined,
|
|
451
639
|
promptHash: row.prompt_hash || undefined,
|
|
452
640
|
responseHash: row.response_hash || undefined,
|
|
453
641
|
createdAt: row.created_at
|
|
@@ -465,6 +653,9 @@ export class BuyerStore {
|
|
|
465
653
|
token TEXT NOT NULL,
|
|
466
654
|
token_class TEXT NOT NULL,
|
|
467
655
|
balance_micros INTEGER NOT NULL,
|
|
656
|
+
reserved_micros INTEGER NOT NULL DEFAULT 0,
|
|
657
|
+
spent_micros INTEGER NOT NULL DEFAULT 0,
|
|
658
|
+
balance_source TEXT,
|
|
468
659
|
expires_at TEXT NOT NULL,
|
|
469
660
|
updated_at TEXT NOT NULL
|
|
470
661
|
);
|
|
@@ -515,6 +706,12 @@ export class BuyerStore {
|
|
|
515
706
|
prompt_tokens INTEGER NOT NULL,
|
|
516
707
|
completion_tokens INTEGER NOT NULL,
|
|
517
708
|
billed_micros INTEGER NOT NULL,
|
|
709
|
+
estimated_micros INTEGER,
|
|
710
|
+
settled_micros INTEGER,
|
|
711
|
+
settled_usd_micros INTEGER,
|
|
712
|
+
price_version TEXT,
|
|
713
|
+
balance_snapshot_micros INTEGER,
|
|
714
|
+
balance_source TEXT,
|
|
518
715
|
prompt_hash TEXT,
|
|
519
716
|
response_hash TEXT,
|
|
520
717
|
created_at TEXT NOT NULL
|
|
@@ -526,7 +723,45 @@ export class BuyerStore {
|
|
|
526
723
|
created_at TEXT NOT NULL,
|
|
527
724
|
updated_at TEXT NOT NULL
|
|
528
725
|
);
|
|
726
|
+
|
|
727
|
+
CREATE TABLE IF NOT EXISTS provider_runtime_config (
|
|
728
|
+
provider_id TEXT PRIMARY KEY,
|
|
729
|
+
runtime_json TEXT NOT NULL,
|
|
730
|
+
created_at TEXT NOT NULL,
|
|
731
|
+
updated_at TEXT NOT NULL
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
CREATE TABLE IF NOT EXISTS daemon_runtime_config (
|
|
735
|
+
config_key TEXT PRIMARY KEY,
|
|
736
|
+
config_json TEXT NOT NULL,
|
|
737
|
+
created_at TEXT NOT NULL,
|
|
738
|
+
updated_at TEXT NOT NULL
|
|
739
|
+
);
|
|
529
740
|
`);
|
|
741
|
+
for (const [column, definition] of [
|
|
742
|
+
["reserved_micros", "INTEGER NOT NULL DEFAULT 0"],
|
|
743
|
+
["spent_micros", "INTEGER NOT NULL DEFAULT 0"],
|
|
744
|
+
["balance_source", "TEXT"]
|
|
745
|
+
]) {
|
|
746
|
+
this.ensureColumn("token_cache", column, definition);
|
|
747
|
+
}
|
|
748
|
+
for (const [column, definition] of [
|
|
749
|
+
["estimated_micros", "INTEGER"],
|
|
750
|
+
["settled_micros", "INTEGER"],
|
|
751
|
+
["settled_usd_micros", "INTEGER"],
|
|
752
|
+
["price_version", "TEXT"],
|
|
753
|
+
["balance_snapshot_micros", "INTEGER"],
|
|
754
|
+
["balance_source", "TEXT"]
|
|
755
|
+
]) {
|
|
756
|
+
this.ensureColumn("inference_ledger", column, definition);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
private ensureColumn(table: string, column: string, definition: string): void {
|
|
761
|
+
const rows = this.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
|
762
|
+
if (!rows.some((row) => row.name === column)) {
|
|
763
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
764
|
+
}
|
|
530
765
|
}
|
|
531
766
|
|
|
532
767
|
private countRows(tableName: string): number {
|