@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.
Files changed (56) hide show
  1. package/dist/src/buyer-store.d.ts +48 -1
  2. package/dist/src/buyer-store.d.ts.map +1 -1
  3. package/dist/src/buyer-store.js +144 -17
  4. package/dist/src/buyer-store.js.map +1 -1
  5. package/dist/src/cli.d.ts +17 -0
  6. package/dist/src/cli.d.ts.map +1 -1
  7. package/dist/src/cli.js +560 -63
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/daemon.d.ts +11 -5
  10. package/dist/src/daemon.d.ts.map +1 -1
  11. package/dist/src/daemon.js +574 -161
  12. package/dist/src/daemon.js.map +1 -1
  13. package/dist/src/doctor-clawtip-wallet.d.ts +14 -0
  14. package/dist/src/doctor-clawtip-wallet.d.ts.map +1 -0
  15. package/dist/src/doctor-clawtip-wallet.js +54 -0
  16. package/dist/src/doctor-clawtip-wallet.js.map +1 -0
  17. package/dist/src/doctor-diagnostics.d.ts +99 -0
  18. package/dist/src/doctor-diagnostics.d.ts.map +1 -0
  19. package/dist/src/doctor-diagnostics.js +552 -0
  20. package/dist/src/doctor-diagnostics.js.map +1 -0
  21. package/dist/src/init-clawtip-activation.d.ts +48 -0
  22. package/dist/src/init-clawtip-activation.d.ts.map +1 -0
  23. package/dist/src/init-clawtip-activation.js +395 -0
  24. package/dist/src/init-clawtip-activation.js.map +1 -0
  25. package/dist/src/init-payment-options.d.ts +56 -0
  26. package/dist/src/init-payment-options.d.ts.map +1 -0
  27. package/dist/src/init-payment-options.js +165 -0
  28. package/dist/src/init-payment-options.js.map +1 -0
  29. package/dist/src/provider-install.d.ts +37 -2
  30. package/dist/src/provider-install.d.ts.map +1 -1
  31. package/dist/src/provider-install.js +317 -67
  32. package/dist/src/provider-install.js.map +1 -1
  33. package/dist/src/seller-catalog.d.ts +79 -0
  34. package/dist/src/seller-catalog.d.ts.map +1 -0
  35. package/dist/src/seller-catalog.js +126 -0
  36. package/dist/src/seller-catalog.js.map +1 -0
  37. package/dist/src/tb-proxyd.js +13 -2
  38. package/dist/src/tb-proxyd.js.map +1 -1
  39. package/dist/src/terminal-image.d.ts +22 -0
  40. package/dist/src/terminal-image.d.ts.map +1 -0
  41. package/dist/src/terminal-image.js +135 -0
  42. package/dist/src/terminal-image.js.map +1 -0
  43. package/package.json +1 -1
  44. package/src/buyer-store.ts +253 -18
  45. package/src/cli.ts +709 -68
  46. package/src/daemon.ts +651 -167
  47. package/src/doctor-clawtip-wallet.ts +70 -0
  48. package/src/doctor-diagnostics.ts +861 -0
  49. package/src/init-clawtip-activation.ts +487 -0
  50. package/src/init-payment-options.ts +249 -0
  51. package/src/provider-install.ts +426 -76
  52. package/src/seller-catalog.ts +222 -0
  53. package/src/tb-proxyd.ts +14 -2
  54. package/src/terminal-image.ts +187 -0
  55. package/tests/e2e.test.ts +88 -5
  56. package/tests/tokenbuddy.test.ts +1362 -27
@@ -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("SELECT token, balance_micros FROM token_cache WHERE seller_key = ?");
201
- const row = stmt.get(sellerKey) as { token: string; balance_micros: number } | undefined;
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, expires_at, updated_at
215
- ) VALUES (?, ?, ?, ?, ?, ?)`
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 deductBalance(sellerKey: string, amountMicros: number): void {
221
- const token = this.getToken(sellerKey);
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
- const newBalance = Math.max(0, token.balanceMicros - amountMicros);
226
- const stmt = this.db.prepare("UPDATE token_cache SET balance_micros = ?, updated_at = ? WHERE seller_key = ?");
227
- stmt.run(newBalance, nowIso(), sellerKey);
228
- logger.info("token.cache.debited", "token cache balance debited", {
229
- sellerKey,
230
- amountMicros,
231
- balanceMicros: newBalance
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, prompt_hash, response_hash, created_at
406
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
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, prompt_hash, response_hash, created_at
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 {