@tokenbuddy/tokenbuddy 1.0.9 → 1.0.11

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 (71) hide show
  1. package/dist/src/buyer-store.d.ts +13 -0
  2. package/dist/src/buyer-store.d.ts.map +1 -1
  3. package/dist/src/buyer-store.js +21 -2
  4. package/dist/src/buyer-store.js.map +1 -1
  5. package/dist/src/cli.d.ts.map +1 -1
  6. package/dist/src/cli.js +54 -0
  7. package/dist/src/cli.js.map +1 -1
  8. package/dist/src/credit-tracker.d.ts +118 -0
  9. package/dist/src/credit-tracker.d.ts.map +1 -0
  10. package/dist/src/credit-tracker.js +220 -0
  11. package/dist/src/credit-tracker.js.map +1 -0
  12. package/dist/src/daemon.d.ts +49 -4
  13. package/dist/src/daemon.d.ts.map +1 -1
  14. package/dist/src/daemon.js +541 -405
  15. package/dist/src/daemon.js.map +1 -1
  16. package/dist/src/model-index.d.ts +86 -0
  17. package/dist/src/model-index.d.ts.map +1 -0
  18. package/dist/src/model-index.js +214 -0
  19. package/dist/src/model-index.js.map +1 -0
  20. package/dist/src/prewarm-cache.d.ts +149 -0
  21. package/dist/src/prewarm-cache.d.ts.map +1 -0
  22. package/dist/src/prewarm-cache.js +288 -0
  23. package/dist/src/prewarm-cache.js.map +1 -0
  24. package/dist/src/prewarm-scheduler.d.ts +150 -0
  25. package/dist/src/prewarm-scheduler.d.ts.map +1 -0
  26. package/dist/src/prewarm-scheduler.js +484 -0
  27. package/dist/src/prewarm-scheduler.js.map +1 -0
  28. package/dist/src/provider-install.d.ts.map +1 -1
  29. package/dist/src/provider-install.js +9 -1
  30. package/dist/src/provider-install.js.map +1 -1
  31. package/dist/src/route-failover.d.ts +96 -0
  32. package/dist/src/route-failover.d.ts.map +1 -0
  33. package/dist/src/route-failover.js +177 -0
  34. package/dist/src/route-failover.js.map +1 -0
  35. package/dist/src/seller-catalog.d.ts +26 -0
  36. package/dist/src/seller-catalog.d.ts.map +1 -1
  37. package/dist/src/seller-catalog.js +40 -0
  38. package/dist/src/seller-catalog.js.map +1 -1
  39. package/dist/src/seller-pool.d.ts +127 -0
  40. package/dist/src/seller-pool.d.ts.map +1 -0
  41. package/dist/src/seller-pool.js +243 -0
  42. package/dist/src/seller-pool.js.map +1 -0
  43. package/dist/src/stream-failover.d.ts +78 -0
  44. package/dist/src/stream-failover.d.ts.map +1 -0
  45. package/dist/src/stream-failover.js +93 -0
  46. package/dist/src/stream-failover.js.map +1 -0
  47. package/package.json +1 -1
  48. package/src/buyer-store.ts +32 -2
  49. package/src/cli.ts +61 -0
  50. package/src/credit-tracker.test.ts +165 -0
  51. package/src/credit-tracker.ts +269 -0
  52. package/src/daemon.ts +569 -445
  53. package/src/model-index.test.ts +184 -0
  54. package/src/model-index.ts +266 -0
  55. package/src/prewarm-cache.test.ts +281 -0
  56. package/src/prewarm-cache.ts +373 -0
  57. package/src/prewarm-scheduler.test.ts +367 -0
  58. package/src/prewarm-scheduler.ts +581 -0
  59. package/src/provider-install.ts +9 -1
  60. package/src/route-failover.test.ts +193 -0
  61. package/src/route-failover.ts +233 -0
  62. package/src/seller-catalog-413.test.ts +61 -0
  63. package/src/seller-catalog.ts +47 -0
  64. package/src/seller-pool.test.ts +231 -0
  65. package/src/seller-pool.ts +333 -0
  66. package/src/stream-failover.test.ts +52 -0
  67. package/src/stream-failover.ts +129 -0
  68. package/src/thousand-seller.test.ts +151 -0
  69. package/tests/daemon-413-fallback.test.ts +92 -0
  70. package/tests/e2e.test.ts +3 -2
  71. package/tests/tokenbuddy.test.ts +68 -11
@@ -0,0 +1,220 @@
1
+ import { createModuleLogger } from "@tokenbuddy/logging";
2
+ const logger = createModuleLogger("tb-proxyd:credit-tracker");
3
+ /**
4
+ * Default window after a successful purchase during which failover is
5
+ * softened (see buyer-driven-fallback-design.md §17.3). 30 seconds is the
6
+ * v1.1 starting point; v1.2 keeps the same value.
7
+ */
8
+ export const DEFAULT_FRESH_PURCHASE_WINDOW_MS = 30_000;
9
+ /**
10
+ * Default threshold of "still considered fresh": the current balance must
11
+ * still cover this fraction of the last purchase amount for the entry to
12
+ * be treated as "刚买". 0.5 means "at least half of the purchased credit is
13
+ * still untouched".
14
+ */
15
+ export const DEFAULT_FRESH_PURCHASE_THRESHOLD = 0.5;
16
+ /**
17
+ * Maximum number of automatic purchases a single session may trigger per
18
+ * minute. When the budget is exhausted, route-failover must `切` rather than
19
+ * `重买` (see §17.5).
20
+ */
21
+ export const DEFAULT_PURCHASE_BUDGET_PER_MINUTE = 3;
22
+ /**
23
+ * Tracks per-seller credit state for the buyer-driven-fallback design's
24
+ * "balance protection" rules. The tracker is intentionally agnostic of the
25
+ * actual seller HTTP layer: callers feed in `recordPurchase`,
26
+ * `recordSpend`, and `transferLeftover` events as they observe them, and
27
+ * the tracker answers "is this seller still inside its fresh-purchase
28
+ * window?" / "may I auto-purchase again this minute?".
29
+ *
30
+ * The tracker is process-local: a buyer restart resets the session
31
+ * counters. See §17.11.4 for the rationale.
32
+ */
33
+ export class CreditTracker {
34
+ freshPurchaseWindowMs;
35
+ freshPurchaseThreshold;
36
+ purchaseBudgetPerMinute;
37
+ now;
38
+ entries = new Map();
39
+ purchaseTimestamps = [];
40
+ totalWastedMicros = 0;
41
+ wastedSinceLastDoctorRun = 0;
42
+ constructor(options = {}) {
43
+ this.freshPurchaseWindowMs = options.freshPurchaseWindowMs ?? DEFAULT_FRESH_PURCHASE_WINDOW_MS;
44
+ this.freshPurchaseThreshold = options.freshPurchaseThreshold ?? DEFAULT_FRESH_PURCHASE_THRESHOLD;
45
+ this.purchaseBudgetPerMinute = options.purchaseBudgetPerMinute ?? DEFAULT_PURCHASE_BUDGET_PER_MINUTE;
46
+ this.now = options.now ?? Date.now;
47
+ }
48
+ /**
49
+ * Register a successful purchase. Updates the seller's balance, the
50
+ * session-wide budget window, and the "fresh purchase" timestamps. If
51
+ * the seller is unknown, an entry is created.
52
+ */
53
+ recordPurchase(sellerId, amountMicros, balanceMicros) {
54
+ if (!Number.isFinite(amountMicros) || amountMicros <= 0) {
55
+ throw new Error("recordPurchase requires a positive amountMicros");
56
+ }
57
+ const ts = this.now();
58
+ const previous = this.entries.get(sellerId);
59
+ const entry = {
60
+ sellerId,
61
+ lastPurchaseAt: ts,
62
+ lastPurchaseAmountMicros: amountMicros,
63
+ currentBalanceMicros: Math.max(0, balanceMicros),
64
+ leftoverCreditMicros: previous?.leftoverCreditMicros ?? 0,
65
+ lastUpdatedAt: ts
66
+ };
67
+ this.entries.set(sellerId, entry);
68
+ this.purchaseTimestamps.push(ts);
69
+ this.prunePurchaseTimestamps(ts);
70
+ logger.info("credit.purchase.recorded", "seller credit purchase recorded", {
71
+ sellerId,
72
+ amountMicros,
73
+ balanceMicros: entry.currentBalanceMicros,
74
+ purchasesInLastMinute: this.purchaseTimestamps.length
75
+ });
76
+ return entry;
77
+ }
78
+ /**
79
+ * Update the seller's current balance after a successful inference. The
80
+ * amount spent is implicit (`previous - next`) so the caller does not
81
+ * have to track it. A non-positive `balanceMicros` is treated as 0.
82
+ */
83
+ recordSpend(sellerId, balanceMicros) {
84
+ const previous = this.entries.get(sellerId);
85
+ if (!previous) {
86
+ return undefined;
87
+ }
88
+ const next = {
89
+ ...previous,
90
+ currentBalanceMicros: Math.max(0, balanceMicros),
91
+ lastUpdatedAt: this.now()
92
+ };
93
+ this.entries.set(sellerId, next);
94
+ return next;
95
+ }
96
+ /**
97
+ * Move the seller's remaining balance to the `leftoverCreditMicros`
98
+ * bucket. Called when failover triggers while a balance is still
99
+ * available. The pool's pre-warm / probe logic can later attempt to
100
+ * consume the leftover credit (see PR-3.1). The wasted amount is also
101
+ * added to the session-level counters.
102
+ */
103
+ transferLeftoverToWasted(sellerId, reason) {
104
+ const previous = this.entries.get(sellerId);
105
+ if (!previous) {
106
+ return 0;
107
+ }
108
+ const wasted = previous.currentBalanceMicros;
109
+ if (wasted <= 0) {
110
+ return 0;
111
+ }
112
+ const next = {
113
+ ...previous,
114
+ currentBalanceMicros: 0,
115
+ leftoverCreditMicros: previous.leftoverCreditMicros + wasted,
116
+ lastUpdatedAt: this.now()
117
+ };
118
+ this.entries.set(sellerId, next);
119
+ this.totalWastedMicros += wasted;
120
+ this.wastedSinceLastDoctorRun += wasted;
121
+ logger.warn("credit.leftover.wasted", "leftover credit logged as wasted on failover", {
122
+ sellerId,
123
+ wastedMicros: wasted,
124
+ reason,
125
+ totalWastedMicros: this.totalWastedMicros
126
+ });
127
+ return wasted;
128
+ }
129
+ /**
130
+ * Consume leftover credit when the seller recovers and a probe
131
+ * successfully burns it down. Returns the amount actually consumed.
132
+ */
133
+ consumeLeftover(sellerId, amountMicros) {
134
+ const previous = this.entries.get(sellerId);
135
+ if (!previous || previous.leftoverCreditMicros <= 0) {
136
+ return 0;
137
+ }
138
+ const consume = Math.max(0, Math.min(amountMicros, previous.leftoverCreditMicros));
139
+ if (consume <= 0) {
140
+ return 0;
141
+ }
142
+ const next = {
143
+ ...previous,
144
+ leftoverCreditMicros: previous.leftoverCreditMicros - consume,
145
+ lastUpdatedAt: this.now()
146
+ };
147
+ this.entries.set(sellerId, next);
148
+ logger.info("credit.leftover.consumed", "leftover credit consumed by recovery probe", {
149
+ sellerId,
150
+ consumedMicros: consume,
151
+ remainingMicros: next.leftoverCreditMicros
152
+ });
153
+ return consume;
154
+ }
155
+ /**
156
+ * Returns `true` when the seller is still inside the fresh-purchase
157
+ * window: at least `freshPurchaseThreshold` of the most recent
158
+ * purchase is still unused, and the window has not elapsed. Sellers
159
+ * with no recorded purchase are never "fresh".
160
+ */
161
+ isInFreshPurchaseWindow(sellerId, now = this.now()) {
162
+ const entry = this.entries.get(sellerId);
163
+ if (!entry || entry.lastPurchaseAmountMicros <= 0) {
164
+ return false;
165
+ }
166
+ if (now - entry.lastPurchaseAt > this.freshPurchaseWindowMs) {
167
+ return false;
168
+ }
169
+ const ratio = entry.currentBalanceMicros / entry.lastPurchaseAmountMicros;
170
+ return ratio >= this.freshPurchaseThreshold;
171
+ }
172
+ /**
173
+ * Returns `true` when the per-minute auto-purchase budget is still
174
+ * available. Used by `route-failover` to refuse auto-repurchase and
175
+ * force a clean `切` once the session is at risk of over-spending.
176
+ */
177
+ canAutoPurchase(now = this.now()) {
178
+ this.prunePurchaseTimestamps(now);
179
+ return this.purchaseTimestamps.length < this.purchaseBudgetPerMinute;
180
+ }
181
+ /**
182
+ * Snapshot for `tb doctor`. Mirrors the design's "Credit Usage" block.
183
+ */
184
+ summary() {
185
+ return {
186
+ totalWastedMicros: this.totalWastedMicros,
187
+ wastedSinceLastDoctorRun: this.wastedSinceLastDoctorRun,
188
+ purchasesInLastMinute: this.purchaseTimestamps.length,
189
+ purchaseBudgetPerMinute: this.purchaseBudgetPerMinute,
190
+ freshPurchaseWindowMs: this.freshPurchaseWindowMs,
191
+ freshPurchaseThreshold: this.freshPurchaseThreshold,
192
+ perSeller: Array.from(this.entries.values()).map((entry) => ({
193
+ sellerId: entry.sellerId,
194
+ currentBalanceMicros: entry.currentBalanceMicros,
195
+ lastPurchaseAmountMicros: entry.lastPurchaseAmountMicros,
196
+ lastPurchaseAt: entry.lastPurchaseAt,
197
+ leftoverCreditMicros: entry.leftoverCreditMicros
198
+ }))
199
+ };
200
+ }
201
+ getEntry(sellerId) {
202
+ return this.entries.get(sellerId);
203
+ }
204
+ resetDoctorRunCounter() {
205
+ this.wastedSinceLastDoctorRun = 0;
206
+ }
207
+ clear() {
208
+ this.entries.clear();
209
+ this.purchaseTimestamps.length = 0;
210
+ this.totalWastedMicros = 0;
211
+ this.wastedSinceLastDoctorRun = 0;
212
+ }
213
+ prunePurchaseTimestamps(now) {
214
+ const cutoff = now - 60_000;
215
+ while (this.purchaseTimestamps.length > 0 && this.purchaseTimestamps[0] < cutoff) {
216
+ this.purchaseTimestamps.shift();
217
+ }
218
+ }
219
+ }
220
+ //# sourceMappingURL=credit-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credit-tracker.js","sourceRoot":"","sources":["../../src/credit-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,MAAM,GAAG,kBAAkB,CAAC,0BAA0B,CAAC,CAAC;AAE9D;;;;GAIG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,GAAG,CAAC;AAEpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,CAAC,CAAC;AAkBpD;;;;;;;;;;GAUG;AACH,MAAM,OAAO,aAAa;IACP,qBAAqB,CAAS;IAC9B,sBAAsB,CAAS;IAC/B,uBAAuB,CAAS;IAChC,GAAG,CAAe;IAElB,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC1C,kBAAkB,GAAa,EAAE,CAAC;IAC3C,iBAAiB,GAAG,CAAC,CAAC;IACtB,wBAAwB,GAAG,CAAC,CAAC;IAErC,YAAY,UAAgC,EAAE;QAC5C,IAAI,CAAC,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,IAAI,gCAAgC,CAAC;QAC/F,IAAI,CAAC,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,IAAI,gCAAgC,CAAC;QACjG,IAAI,CAAC,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,IAAI,kCAAkC,CAAC;QACrG,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,QAAgB,EAAE,YAAoB,EAAE,aAAqB;QAC1E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAiB;YAC1B,QAAQ;YACR,cAAc,EAAE,EAAE;YAClB,wBAAwB,EAAE,YAAY;YACtC,oBAAoB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC;YAChD,oBAAoB,EAAE,QAAQ,EAAE,oBAAoB,IAAI,CAAC;YACzD,aAAa,EAAE,EAAE;SAClB,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAClC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,uBAAuB,CAAC,EAAE,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE,iCAAiC,EAAE;YACzE,QAAQ;YACR,YAAY;YACZ,aAAa,EAAE,KAAK,CAAC,oBAAoB;YACzC,qBAAqB,EAAE,IAAI,CAAC,kBAAkB,CAAC,MAAM;SACtD,CAAC,CAAC;QACH,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,QAAgB,EAAE,aAAqB;QACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,IAAI,GAAiB;YACzB,GAAG,QAAQ;YACX,oBAAoB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC;YAChD,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;SAC1B,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,wBAAwB,CAAC,QAAgB,EAAE,MAAc;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,oBAAoB,CAAC;QAC7C,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;YAChB,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,IAAI,GAAiB;YACzB,GAAG,QAAQ;YACX,oBAAoB,EAAE,CAAC;YACvB,oBAAoB,EAAE,QAAQ,CAAC,oBAAoB,GAAG,MAAM;YAC5D,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;SAC1B,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,iBAAiB,IAAI,MAAM,CAAC;QACjC,IAAI,CAAC,wBAAwB,IAAI,MAAM,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,8CAA8C,EAAE;YACpF,QAAQ;YACR,YAAY,EAAE,MAAM;YACpB,MAAM;YACN,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;SAC1C,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,eAAe,CAAC,QAAgB,EAAE,YAAoB;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,oBAAoB,IAAI,CAAC,EAAE,CAAC;YACpD,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC;QACnF,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YACjB,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,IAAI,GAAiB;YACzB,GAAG,QAAQ;YACX,oBAAoB,EAAE,QAAQ,CAAC,oBAAoB,GAAG,OAAO;YAC7D,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;SAC1B,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE,4CAA4C,EAAE;YACpF,QAAQ;YACR,cAAc,EAAE,OAAO;YACvB,eAAe,EAAE,IAAI,CAAC,oBAAoB;SAC3C,CAAC,CAAC;QACH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;OAKG;IACH,uBAAuB,CAAC,QAAgB,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;QAChE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,EAAE,CAAC;YAClD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,GAAG,GAAG,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC5D,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,oBAAoB,GAAG,KAAK,CAAC,wBAAwB,CAAC;QAC1E,OAAO,KAAK,IAAI,IAAI,CAAC,sBAAsB,CAAC;IAC9C,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;QACtC,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,IAAI,CAAC,uBAAuB,CAAC;IACvE,CAAC;IAED;;OAEG;IACH,OAAO;QACL,OAAO;YACL,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;YACzC,wBAAwB,EAAE,IAAI,CAAC,wBAAwB;YACvD,qBAAqB,EAAE,IAAI,CAAC,kBAAkB,CAAC,MAAM;YACrD,uBAAuB,EAAE,IAAI,CAAC,uBAAuB;YACrD,qBAAqB,EAAE,IAAI,CAAC,qBAAqB;YACjD,sBAAsB,EAAE,IAAI,CAAC,sBAAsB;YACnD,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC3D,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,oBAAoB,EAAE,KAAK,CAAC,oBAAoB;gBAChD,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;gBACxD,cAAc,EAAE,KAAK,CAAC,cAAc;gBACpC,oBAAoB,EAAE,KAAK,CAAC,oBAAoB;aACjD,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;IAED,QAAQ,CAAC,QAAgB;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,qBAAqB;QACnB,IAAI,CAAC,wBAAwB,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;QAC3B,IAAI,CAAC,wBAAwB,GAAG,CAAC,CAAC;IACpC,CAAC;IAEO,uBAAuB,CAAC,GAAW;QACzC,MAAM,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC;QAC5B,OAAO,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC;YACjF,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAC;QAClC,CAAC;IACH,CAAC;CACF"}
@@ -5,6 +5,9 @@ export interface DaemonConfig {
5
5
  sellerRegistryUrl: string;
6
6
  selectionMode?: "auto" | "manual";
7
7
  selectedSellerId?: string;
8
+ warmupModels?: string[];
9
+ warmupRefreshIntervalSecs?: number;
10
+ warmupProbeTimeoutMs?: number;
8
11
  }
9
12
  export declare class TokenbuddyDaemon {
10
13
  private config;
@@ -14,9 +17,17 @@ export declare class TokenbuddyDaemon {
14
17
  private selectionMode;
15
18
  private selectedSellerId?;
16
19
  private activePurchases;
20
+ private readonly modelIndex;
21
+ private readonly prewarmCache;
22
+ private readonly creditTracker;
23
+ private readonly sellerPool;
24
+ private readonly routeFailover;
25
+ private readonly prewarmScheduler;
17
26
  constructor(config: DaemonConfig);
27
+ private buildHealthProber;
18
28
  private activeControlPort;
19
29
  private activeProxyPort;
30
+ private lastRegistrySnapshot;
20
31
  private fetchRegistry;
21
32
  private runtimeSummary;
22
33
  private endpointProtocol;
@@ -27,11 +38,23 @@ export declare class TokenbuddyDaemon {
27
38
  private applyResolvedModelToBody;
28
39
  private defaultPaymentMethod;
29
40
  private selectSellerRoutes;
30
- private logRouteSelected;
31
- private shouldFailoverStatus;
32
- private logFailover;
33
41
  private failoverErrorMessage;
34
- private selectSeller;
42
+ /**
43
+ * Map an HTTP status from a failed seller call to a `FailureKind` that
44
+ * the route-failover controller understands. Hard 4xx (other than
45
+ * auth/insufficient) means the seller is wrong for the request; 5xx
46
+ * and 429 are treated as transient and eligible for the soft-failure
47
+ * retry budget. The v1.1 "insufficient funds" check stays on the
48
+ * caller side because it short-circuits the failure path with a
49
+ * re-purchase.
50
+ */
51
+ private classifyFailureStatus;
52
+ /**
53
+ * Emit the structured failover log line. The decision itself is
54
+ * produced by `RouteFailover.decide`; this helper exists only to keep
55
+ * the controller loop readable.
56
+ */
57
+ private handleFailoverDecision;
35
58
  private listSellerBackedModels;
36
59
  private readUsage;
37
60
  private parseSellerSettlementSummary;
@@ -42,6 +65,21 @@ export declare class TokenbuddyDaemon {
42
65
  private inferPromptForHash;
43
66
  private autoPurchaseAmountUsdMicros;
44
67
  private tokenRebuyMinBalanceMicros;
68
+ /**
69
+ * v1.2 §8: hard per-request deadline. The buyer refuses to wait longer
70
+ * than this for a single seller; on expiry the request is aborted and
71
+ * the route-failover controller can either retry the same seller with
72
+ * a smaller body or fail over. Configurable via
73
+ * `TB_PROXYD_REQUEST_DEADLINE_MS` (default 30s).
74
+ */
75
+ private requestDeadlineMs;
76
+ /**
77
+ * Safety margin subtracted from the cached token's `expiresAt` before
78
+ * deciding to reuse it. Buying a new token 60s before expiry gives the
79
+ * upstream enough headroom to reject any in-flight calls under the old
80
+ * token before the buyer assumes the new one is valid.
81
+ */
82
+ private tokenExpirySafetyMarginMs;
45
83
  private getOrPurchaseToken;
46
84
  private resolvePaymentProof;
47
85
  private runClawtipProofCommand;
@@ -49,6 +87,13 @@ export declare class TokenbuddyDaemon {
49
87
  private copyUpstreamHeaders;
50
88
  private forwardProxyRequest;
51
89
  start(): void;
90
+ /**
91
+ * v1.2 §18.4: build the focus set from the explicit config, the env
92
+ * override, and the historical usage in the buyer store. The order of
93
+ * precedence: explicit config > env > historical > empty.
94
+ */
95
+ private resolveFocusSet;
96
+ private runStartupPrewarmSweep;
52
97
  stop(): void;
53
98
  }
54
99
  //# sourceMappingURL=daemon.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/daemon.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AA4WD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,aAAa,CAAC,CAAM;IAC5B,OAAO,CAAC,WAAW,CAAC,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,OAAO,CAAC,eAAe,CAAsC;gBAEjD,MAAM,EAAE,YAAY;IAchC,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,eAAe;YAKT,aAAa;IAI3B,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,sBAAsB;IAoB9B,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,wBAAwB;IAehC,OAAO,CAAC,oBAAoB;YAKd,kBAAkB;IAiEhC,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,oBAAoB;YAId,YAAY;YAOZ,sBAAsB;IAWpC,OAAO,CAAC,SAAS;IA8BjB,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,yBAAyB;YAkDnB,oBAAoB;IA0ClC,OAAO,CAAC,2BAA2B;YAcrB,4BAA4B;IAkB1C,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,2BAA2B;IASnC,OAAO,CAAC,0BAA0B;YASpB,kBAAkB;YA8JlB,mBAAmB;IA2BjC,OAAO,CAAC,sBAAsB;IAkF9B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,mBAAmB;YAUb,mBAAmB;IAmO1B,KAAK;IA8QL,IAAI;CAKZ"}
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/daemon.ts"],"names":[],"mappings":"AAsCA,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAI1B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAkJD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,aAAa,CAAC,CAAM;IAC5B,OAAO,CAAC,WAAW,CAAC,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,OAAO,CAAC,eAAe,CAAsC;IAK7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAuB;IACrD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAIxB;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAG3B;IAIH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoB;gBAEzC,MAAM,EAAE,YAAY;IAwBhC,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,oBAAoB,CAAuC;YAErD,aAAa;IAgC3B,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,sBAAsB;IAoB9B,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,wBAAwB;IAehC,OAAO,CAAC,oBAAoB;YAKd,kBAAkB;IAyDhC,OAAO,CAAC,oBAAoB;IAI5B;;;;;;;;OAQG;IACH,OAAO,CAAC,qBAAqB;IAa7B;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;YAqBhB,sBAAsB;IAWpC,OAAO,CAAC,SAAS;IA8BjB,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,yBAAyB;YAkDnB,oBAAoB;IA0ClC,OAAO,CAAC,2BAA2B;YAcrB,4BAA4B;IAkB1C,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,2BAA2B;IASnC,OAAO,CAAC,0BAA0B;IASlC;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;;;;OAKG;IACH,OAAO,CAAC,yBAAyB;YAYnB,kBAAkB;YA4KlB,mBAAmB;IA2BjC,OAAO,CAAC,sBAAsB;IAkF9B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,mBAAmB;YAUb,mBAAmB;IAwU1B,KAAK;IA8TZ;;;;OAIG;IACH,OAAO,CAAC,eAAe;YAaT,sBAAsB;IAmB7B,IAAI;CAMZ"}