@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/dist/src/daemon.js
CHANGED
|
@@ -4,20 +4,302 @@ import { spawn } from "child_process";
|
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import { createModuleLogger } from "@tokenbuddy/logging";
|
|
6
6
|
import { BuyerStore } from "./buyer-store.js";
|
|
7
|
-
import { applyProviderInstall, detectProviders, previewProviderInstall, rollbackProviderInstall } from "./provider-install.js";
|
|
7
|
+
import { applyProviderInstall, detectProviders, previewProviderInstall, rollbackProviderInstall, } from "./provider-install.js";
|
|
8
|
+
import { discoverSellerBackedModels, fetchSellerManifest, fetchSellerRegistry, manifestModelIds, manifestPaymentMethods, manifestProtocols, normalizeSellerUrl, } from "./seller-catalog.js";
|
|
8
9
|
const logger = createModuleLogger("tb-proxyd");
|
|
9
10
|
const PROXY_JSON_BODY_LIMIT = "10mb";
|
|
11
|
+
function numericHeaderField(value) {
|
|
12
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
16
|
+
const parsed = Number(value);
|
|
17
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
class ResponsesStreamNormalizer {
|
|
22
|
+
pending = "";
|
|
23
|
+
state = new Map();
|
|
24
|
+
push(chunk) {
|
|
25
|
+
this.pending += chunk;
|
|
26
|
+
const blocks = this.pending.split("\n\n");
|
|
27
|
+
this.pending = blocks.pop() || "";
|
|
28
|
+
return blocks
|
|
29
|
+
.map((block) => this.normalizeBlock(block))
|
|
30
|
+
.filter((block) => block.length > 0)
|
|
31
|
+
.join("\n\n");
|
|
32
|
+
}
|
|
33
|
+
finish() {
|
|
34
|
+
if (!this.pending.trim()) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
const block = this.normalizeBlock(this.pending);
|
|
38
|
+
this.pending = "";
|
|
39
|
+
return block;
|
|
40
|
+
}
|
|
41
|
+
normalizeBlock(block) {
|
|
42
|
+
if (!block.trim()) {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
// Each \n\n separates an event in SSE format
|
|
46
|
+
const subBlocks = block.split("\n\n");
|
|
47
|
+
const output = [];
|
|
48
|
+
for (const sub of subBlocks) {
|
|
49
|
+
if (!sub.trim() || sub.trim() === "data: [DONE]") {
|
|
50
|
+
if (sub.trim())
|
|
51
|
+
output.push(sub);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const lines = sub.split("\n");
|
|
55
|
+
const eventLine = lines.find((l) => l.startsWith("event:"));
|
|
56
|
+
const dataLine = lines.find((l) => l.startsWith("data:"));
|
|
57
|
+
if (!dataLine) {
|
|
58
|
+
output.push(sub);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const rawData = dataLine.replace(/^data:\s?/, "");
|
|
62
|
+
if (rawData === "[DONE]") {
|
|
63
|
+
output.push(sub);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
let payload;
|
|
67
|
+
try {
|
|
68
|
+
payload = JSON.parse(rawData);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
output.push(sub);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const eventName = (eventLine?.replace(/^event:\s?/, "") || payload?.type);
|
|
75
|
+
if (!eventName || !eventName.startsWith("response.")) {
|
|
76
|
+
output.push(sub);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// When upstream already sends content_part.added, record it in state
|
|
80
|
+
if (eventName === "response.content_part.added" &&
|
|
81
|
+
payload?.item_id) {
|
|
82
|
+
const current = this.state.get(payload.item_id);
|
|
83
|
+
if (current)
|
|
84
|
+
current.contentPartStarted = true;
|
|
85
|
+
output.push(sub);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// response.output_item.added: inject content_part.added only if upstream hasn't
|
|
89
|
+
if (eventName === "response.output_item.added" &&
|
|
90
|
+
payload?.item?.type === "message" &&
|
|
91
|
+
payload?.item?.id) {
|
|
92
|
+
const itemId = payload.item.id;
|
|
93
|
+
const current = this.getState(itemId);
|
|
94
|
+
const item = { ...payload.item };
|
|
95
|
+
item.content = [{ type: "output_text", text: "", annotations: [] }];
|
|
96
|
+
output.push(this.serializeEvent(eventName, {
|
|
97
|
+
...payload,
|
|
98
|
+
output_index: payload.output_index ?? 0,
|
|
99
|
+
item
|
|
100
|
+
}));
|
|
101
|
+
if (!current.contentPartStarted) {
|
|
102
|
+
current.contentPartStarted = true;
|
|
103
|
+
output.push(this.serializeEvent("response.content_part.added", {
|
|
104
|
+
type: "response.content_part.added",
|
|
105
|
+
item_id: itemId,
|
|
106
|
+
output_index: payload.output_index ?? 0,
|
|
107
|
+
content_index: 0,
|
|
108
|
+
part: { type: "output_text", text: "", annotations: [] }
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// response.output_text.delta: inject content_part.added if missing
|
|
114
|
+
if (eventName === "response.output_text.delta" && payload?.item_id) {
|
|
115
|
+
const itemId = payload.item_id;
|
|
116
|
+
const current = this.getState(itemId);
|
|
117
|
+
if (!current.contentPartStarted) {
|
|
118
|
+
current.contentPartStarted = true;
|
|
119
|
+
output.push(this.serializeEvent("response.content_part.added", {
|
|
120
|
+
type: "response.content_part.added",
|
|
121
|
+
item_id: itemId,
|
|
122
|
+
output_index: payload.output_index ?? 0,
|
|
123
|
+
content_index: payload.content_index ?? 0,
|
|
124
|
+
part: { type: "output_text", text: "", annotations: [] }
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
const deltaText = typeof payload.delta === "string"
|
|
128
|
+
? payload.delta
|
|
129
|
+
: typeof payload.delta?.text === "string"
|
|
130
|
+
? payload.delta.text
|
|
131
|
+
: "";
|
|
132
|
+
current.text += deltaText;
|
|
133
|
+
output.push(this.serializeEvent(eventName, {
|
|
134
|
+
...payload,
|
|
135
|
+
output_index: payload.output_index ?? 0,
|
|
136
|
+
content_index: payload.content_index ?? 0
|
|
137
|
+
}));
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// response.output_text.done: also emit content_part.done
|
|
141
|
+
if (eventName === "response.output_text.done" && payload?.item_id) {
|
|
142
|
+
const itemId = payload.item_id;
|
|
143
|
+
const current = this.getState(itemId);
|
|
144
|
+
output.push(this.serializeEvent(eventName, {
|
|
145
|
+
...payload,
|
|
146
|
+
output_index: payload.output_index ?? 0,
|
|
147
|
+
content_index: payload.content_index ?? 0
|
|
148
|
+
}));
|
|
149
|
+
output.push(this.serializeEvent("response.content_part.done", {
|
|
150
|
+
type: "response.content_part.done",
|
|
151
|
+
item_id: itemId,
|
|
152
|
+
output_index: payload.output_index ?? 0,
|
|
153
|
+
content_index: payload.content_index ?? 0,
|
|
154
|
+
part: { type: "output_text", text: current.text, annotations: [] }
|
|
155
|
+
}));
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
// response.output_item.done: normalize content to output_text type
|
|
159
|
+
if (eventName === "response.output_item.done" &&
|
|
160
|
+
payload?.item?.type === "message" &&
|
|
161
|
+
payload?.item?.id) {
|
|
162
|
+
const itemId = payload.item.id;
|
|
163
|
+
const current = this.getState(itemId);
|
|
164
|
+
const item = {
|
|
165
|
+
...payload.item,
|
|
166
|
+
content: [{ type: "output_text", text: current.text, annotations: [] }]
|
|
167
|
+
};
|
|
168
|
+
output.push(this.serializeEvent(eventName, {
|
|
169
|
+
...payload,
|
|
170
|
+
output_index: payload.output_index ?? 0,
|
|
171
|
+
item
|
|
172
|
+
}));
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
// response.completed: patch output if empty
|
|
176
|
+
if (eventName === "response.completed" && payload?.response) {
|
|
177
|
+
const response = { ...payload.response };
|
|
178
|
+
if (!Array.isArray(response.output) || response.output.length === 0) {
|
|
179
|
+
const first = this.state.values().next()
|
|
180
|
+
.value;
|
|
181
|
+
if (first) {
|
|
182
|
+
response.output = [{
|
|
183
|
+
id: first.itemId,
|
|
184
|
+
type: "message",
|
|
185
|
+
status: "completed",
|
|
186
|
+
role: "assistant",
|
|
187
|
+
content: [{ type: "output_text", text: first.text, annotations: [] }]
|
|
188
|
+
}];
|
|
189
|
+
response.output_text = first.text;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
output.push(this.serializeEvent(eventName, { ...payload, response }));
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// All other events: pass through unchanged
|
|
196
|
+
output.push(sub);
|
|
197
|
+
}
|
|
198
|
+
return output.join("\n\n");
|
|
199
|
+
}
|
|
200
|
+
getState(itemId) {
|
|
201
|
+
const current = this.state.get(itemId);
|
|
202
|
+
if (current)
|
|
203
|
+
return current;
|
|
204
|
+
const created = { itemId, text: "", contentPartStarted: false };
|
|
205
|
+
this.state.set(itemId, created);
|
|
206
|
+
return created;
|
|
207
|
+
}
|
|
208
|
+
serializeEvent(name, data) {
|
|
209
|
+
return `event: ${name}\ndata: ${JSON.stringify(data)}`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
class SellerSettlementStreamExtractor {
|
|
213
|
+
pending = "";
|
|
214
|
+
settlement;
|
|
215
|
+
push(chunk) {
|
|
216
|
+
this.pending += chunk;
|
|
217
|
+
const blocks = this.pending.split("\n\n");
|
|
218
|
+
this.pending = blocks.pop() || "";
|
|
219
|
+
return blocks
|
|
220
|
+
.map((block) => this.processBlock(block))
|
|
221
|
+
.filter((block) => block.length > 0)
|
|
222
|
+
.join("\n\n");
|
|
223
|
+
}
|
|
224
|
+
finish() {
|
|
225
|
+
const downstream = this.pending.trim() ? this.processBlock(this.pending) : "";
|
|
226
|
+
this.pending = "";
|
|
227
|
+
return { downstream, settlement: this.settlement };
|
|
228
|
+
}
|
|
229
|
+
current() {
|
|
230
|
+
return this.settlement;
|
|
231
|
+
}
|
|
232
|
+
processBlock(block) {
|
|
233
|
+
if (!block.trim()) {
|
|
234
|
+
return "";
|
|
235
|
+
}
|
|
236
|
+
const lines = block.split("\n");
|
|
237
|
+
const eventLine = lines.find((line) => line.startsWith("event:"));
|
|
238
|
+
const eventName = eventLine?.replace(/^event:\s?/, "").trim();
|
|
239
|
+
if (eventName !== "tokenbuddy.settlement") {
|
|
240
|
+
return block;
|
|
241
|
+
}
|
|
242
|
+
const dataLine = lines.find((line) => line.startsWith("data:"));
|
|
243
|
+
if (!dataLine) {
|
|
244
|
+
return "";
|
|
245
|
+
}
|
|
246
|
+
const parsed = parseSellerSettlementObject(dataLine.replace(/^data:\s?/, ""));
|
|
247
|
+
if (parsed) {
|
|
248
|
+
this.settlement = parsed;
|
|
249
|
+
}
|
|
250
|
+
return "";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function parseSellerSettlementObject(raw) {
|
|
254
|
+
try {
|
|
255
|
+
const parsed = JSON.parse(raw);
|
|
256
|
+
const requestId = typeof parsed.requestId === "string"
|
|
257
|
+
? parsed.requestId
|
|
258
|
+
: typeof parsed.request_id === "string"
|
|
259
|
+
? parsed.request_id
|
|
260
|
+
: undefined;
|
|
261
|
+
const settledMicros = numericHeaderField(parsed.settledMicros ?? parsed.settled_micros);
|
|
262
|
+
const remainingCreditMicros = numericHeaderField(parsed.remainingCreditMicros ?? parsed.remaining_credit_micros);
|
|
263
|
+
if (!requestId || settledMicros === undefined || remainingCreditMicros === undefined) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
requestId,
|
|
268
|
+
settledMicros,
|
|
269
|
+
settledUsdMicros: numericHeaderField(parsed.settledUsdMicros ?? parsed.settled_usd_micros),
|
|
270
|
+
remainingCreditMicros,
|
|
271
|
+
reservedBalanceMicros: numericHeaderField(parsed.reservedBalanceMicros ?? parsed.reserved_balance_micros),
|
|
272
|
+
spentMicros: numericHeaderField(parsed.spentMicros ?? parsed.spent_micros),
|
|
273
|
+
priceVersion: typeof parsed.priceVersion === "string"
|
|
274
|
+
? parsed.priceVersion
|
|
275
|
+
: typeof parsed.price_version === "string"
|
|
276
|
+
? parsed.price_version
|
|
277
|
+
: undefined
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
10
284
|
export class TokenbuddyDaemon {
|
|
11
285
|
config;
|
|
12
286
|
tokenStore;
|
|
13
287
|
controlServer;
|
|
14
288
|
proxyServer;
|
|
15
289
|
selectionMode;
|
|
290
|
+
selectedSellerId;
|
|
16
291
|
activePurchases = new Map();
|
|
17
292
|
constructor(config) {
|
|
18
|
-
this.config = config;
|
|
19
|
-
this.selectionMode = config.selectionMode || "auto";
|
|
20
293
|
this.tokenStore = new BuyerStore({ dbPath: config.dbPath });
|
|
294
|
+
const routingPreference = this.tokenStore.getDaemonRuntimeConfig("routing")
|
|
295
|
+
?.config;
|
|
296
|
+
this.config = config;
|
|
297
|
+
this.selectionMode =
|
|
298
|
+
config.selectionMode ||
|
|
299
|
+
(routingPreference?.mode === "fixed" ? "manual" : routingPreference?.mode) ||
|
|
300
|
+
"auto";
|
|
301
|
+
this.selectedSellerId =
|
|
302
|
+
config.selectedSellerId || routingPreference?.sellerId;
|
|
21
303
|
}
|
|
22
304
|
activeControlPort() {
|
|
23
305
|
const address = this.controlServer?.address?.();
|
|
@@ -28,39 +310,23 @@ export class TokenbuddyDaemon {
|
|
|
28
310
|
return typeof address === "object" && address ? address.port : this.config.proxyPort;
|
|
29
311
|
}
|
|
30
312
|
async fetchRegistry() {
|
|
31
|
-
|
|
32
|
-
if (!response.ok) {
|
|
33
|
-
throw new Error(`registry returned ${response.status}`);
|
|
34
|
-
}
|
|
35
|
-
const data = await response.json();
|
|
36
|
-
if (!data || !Array.isArray(data.sellers)) {
|
|
37
|
-
throw new Error("registry response missing sellers");
|
|
38
|
-
}
|
|
39
|
-
return data;
|
|
40
|
-
}
|
|
41
|
-
async fetchSellerManifest(seller) {
|
|
42
|
-
const baseUrl = seller.url.replace(/\/+$/, "");
|
|
43
|
-
const response = await fetch(`${baseUrl}/manifest`);
|
|
44
|
-
if (!response.ok) {
|
|
45
|
-
throw new Error(`manifest returned ${response.status}`);
|
|
46
|
-
}
|
|
47
|
-
return await response.json();
|
|
313
|
+
return await fetchSellerRegistry(this.config.sellerRegistryUrl);
|
|
48
314
|
}
|
|
49
315
|
runtimeSummary() {
|
|
316
|
+
const sellerRoutingMode = this.selectedSellerId ? "fixed" : this.selectionMode;
|
|
50
317
|
return {
|
|
51
318
|
status: "running",
|
|
52
319
|
pid: process.pid,
|
|
53
320
|
controlPort: this.activeControlPort(),
|
|
54
321
|
proxyPort: this.activeProxyPort(),
|
|
55
322
|
selectionMode: this.selectionMode,
|
|
323
|
+
sellerRoutingMode,
|
|
324
|
+
selectedSellerId: this.selectedSellerId,
|
|
56
325
|
dbPath: this.config.dbPath,
|
|
57
326
|
sellerRegistryUrl: this.config.sellerRegistryUrl,
|
|
58
327
|
store: this.tokenStore.summary()
|
|
59
328
|
};
|
|
60
329
|
}
|
|
61
|
-
normalizeSellerUrl(seller) {
|
|
62
|
-
return seller.url.replace(/\/+$/, "");
|
|
63
|
-
}
|
|
64
330
|
endpointProtocol(endpoint) {
|
|
65
331
|
if (endpoint === "/v1/chat/completions") {
|
|
66
332
|
return "chat_completions";
|
|
@@ -84,17 +350,55 @@ export class TokenbuddyDaemon {
|
|
|
84
350
|
}
|
|
85
351
|
return undefined;
|
|
86
352
|
}
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
return
|
|
90
|
-
?
|
|
91
|
-
:
|
|
353
|
+
stripLocalClaudeOneMMarker(modelId) {
|
|
354
|
+
const trimmed = modelId.trimEnd();
|
|
355
|
+
return trimmed.toLowerCase().endsWith("[1m]")
|
|
356
|
+
? trimmed.slice(0, -4).trimEnd()
|
|
357
|
+
: trimmed;
|
|
92
358
|
}
|
|
93
|
-
|
|
94
|
-
|
|
359
|
+
resolveClaudeRoleModel(modelId) {
|
|
360
|
+
const runtimeConfig = this.tokenStore.getProviderRuntimeConfig("claude-code")?.config;
|
|
361
|
+
const stripped = this.stripLocalClaudeOneMMarker(modelId);
|
|
362
|
+
if (!runtimeConfig || runtimeConfig.selectionKind !== "claude-role-mapping") {
|
|
363
|
+
return stripped;
|
|
364
|
+
}
|
|
365
|
+
const lowered = stripped.toLowerCase();
|
|
366
|
+
if (lowered === "haiku" || lowered.startsWith("claude-haiku")) {
|
|
367
|
+
return runtimeConfig.roles.haiku?.upstreamModel || runtimeConfig.fallbackModel || stripped;
|
|
368
|
+
}
|
|
369
|
+
if (lowered === "sonnet" || lowered.startsWith("claude-sonnet")) {
|
|
370
|
+
return runtimeConfig.roles.sonnet?.upstreamModel || runtimeConfig.fallbackModel || stripped;
|
|
371
|
+
}
|
|
372
|
+
if (lowered === "opus" || lowered.startsWith("claude-opus")) {
|
|
373
|
+
return runtimeConfig.roles.opus?.upstreamModel || runtimeConfig.fallbackModel || stripped;
|
|
374
|
+
}
|
|
375
|
+
return runtimeConfig.fallbackModel || stripped;
|
|
95
376
|
}
|
|
96
|
-
|
|
97
|
-
|
|
377
|
+
resolveRouteModelId(endpoint, body) {
|
|
378
|
+
const requestedModelId = this.extractModelId(endpoint, body);
|
|
379
|
+
if (!requestedModelId) {
|
|
380
|
+
return {};
|
|
381
|
+
}
|
|
382
|
+
if (this.endpointProtocol(endpoint) === "messages") {
|
|
383
|
+
return {
|
|
384
|
+
requestedModelId,
|
|
385
|
+
resolvedModelId: this.resolveClaudeRoleModel(requestedModelId)
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
requestedModelId,
|
|
390
|
+
resolvedModelId: requestedModelId
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
applyResolvedModelToBody(endpoint, body, resolvedModelId) {
|
|
394
|
+
const nextBody = { ...body };
|
|
395
|
+
if ("model" in nextBody) {
|
|
396
|
+
nextBody.model = resolvedModelId;
|
|
397
|
+
}
|
|
398
|
+
if (endpoint === "/v1/responses" && "model_id" in nextBody) {
|
|
399
|
+
nextBody.model_id = resolvedModelId;
|
|
400
|
+
}
|
|
401
|
+
return nextBody;
|
|
98
402
|
}
|
|
99
403
|
defaultPaymentMethod() {
|
|
100
404
|
const payments = this.tokenStore.listPayments().filter((payment) => payment.enabled);
|
|
@@ -112,12 +416,15 @@ export class TokenbuddyDaemon {
|
|
|
112
416
|
const registry = await this.fetchRegistry();
|
|
113
417
|
const defaultSellers = registry.sellers.filter((seller) => seller.id === registry.defaultSeller);
|
|
114
418
|
const backupSellers = registry.sellers.filter((seller) => seller.id !== registry.defaultSeller);
|
|
115
|
-
const
|
|
419
|
+
const manualSellers = this.selectedSellerId
|
|
420
|
+
? registry.sellers.filter((seller) => seller.id === this.selectedSellerId)
|
|
421
|
+
: defaultSellers;
|
|
422
|
+
const sellers = this.selectionMode === "manual" ? manualSellers : [...defaultSellers, ...backupSellers];
|
|
116
423
|
const routes = [];
|
|
117
424
|
for (const seller of sellers) {
|
|
118
425
|
let manifest;
|
|
119
426
|
try {
|
|
120
|
-
manifest = await
|
|
427
|
+
manifest = await fetchSellerManifest(seller);
|
|
121
428
|
}
|
|
122
429
|
catch (error) {
|
|
123
430
|
logger.warn("route.manifest.failed", "seller manifest unavailable during route selection", {
|
|
@@ -128,9 +435,9 @@ export class TokenbuddyDaemon {
|
|
|
128
435
|
});
|
|
129
436
|
continue;
|
|
130
437
|
}
|
|
131
|
-
const protocols =
|
|
132
|
-
const paymentMethods =
|
|
133
|
-
const modelIds =
|
|
438
|
+
const protocols = manifestProtocols(manifest, seller);
|
|
439
|
+
const paymentMethods = manifestPaymentMethods(manifest, seller);
|
|
440
|
+
const modelIds = manifestModelIds(manifest);
|
|
134
441
|
if (!protocols.includes(protocol) || !paymentMethods.includes(paymentMethod) || !modelIds.includes(modelId)) {
|
|
135
442
|
continue;
|
|
136
443
|
}
|
|
@@ -190,52 +497,10 @@ export class TokenbuddyDaemon {
|
|
|
190
497
|
return route;
|
|
191
498
|
}
|
|
192
499
|
async listSellerBackedModels() {
|
|
193
|
-
const
|
|
194
|
-
const sellerResults = await Promise.all(registry.sellers.map(async (seller) => {
|
|
195
|
-
try {
|
|
196
|
-
const manifest = await this.fetchSellerManifest(seller);
|
|
197
|
-
const protocols = this.manifestProtocols(manifest, seller);
|
|
198
|
-
const paymentMethods = this.manifestPaymentMethods(manifest, seller);
|
|
199
|
-
const models = (manifest.models || []).map((model) => ({
|
|
200
|
-
id: model.id,
|
|
201
|
-
sellerId: seller.id,
|
|
202
|
-
sellerName: seller.name,
|
|
203
|
-
sellerUrl: seller.url,
|
|
204
|
-
supportedProtocols: protocols,
|
|
205
|
-
paymentMethods
|
|
206
|
-
}));
|
|
207
|
-
return {
|
|
208
|
-
seller: {
|
|
209
|
-
id: seller.id,
|
|
210
|
-
name: seller.name,
|
|
211
|
-
url: seller.url,
|
|
212
|
-
status: "ok",
|
|
213
|
-
manifestSellerId: manifest.sellerId || manifest.seller_id || seller.id
|
|
214
|
-
},
|
|
215
|
-
models
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
catch (error) {
|
|
219
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
220
|
-
logger.warn("models.refresh.seller_failed", "seller manifest refresh failed", {
|
|
221
|
-
sellerId: seller.id,
|
|
222
|
-
errorMessage
|
|
223
|
-
});
|
|
224
|
-
return {
|
|
225
|
-
seller: {
|
|
226
|
-
id: seller.id,
|
|
227
|
-
name: seller.name,
|
|
228
|
-
url: seller.url,
|
|
229
|
-
status: "failed",
|
|
230
|
-
errorMessage
|
|
231
|
-
},
|
|
232
|
-
models: []
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
}));
|
|
500
|
+
const catalog = await discoverSellerBackedModels(this.config.sellerRegistryUrl);
|
|
236
501
|
return {
|
|
237
|
-
models:
|
|
238
|
-
sellers:
|
|
502
|
+
models: catalog.models,
|
|
503
|
+
sellers: catalog.sellers
|
|
239
504
|
};
|
|
240
505
|
}
|
|
241
506
|
readUsage(bodyText) {
|
|
@@ -261,6 +526,125 @@ export class TokenbuddyDaemon {
|
|
|
261
526
|
return fallback;
|
|
262
527
|
}
|
|
263
528
|
}
|
|
529
|
+
parseSellerSettlementSummary(headers) {
|
|
530
|
+
const raw = headers.get("x-tokenbuddy-settlement");
|
|
531
|
+
if (!raw) {
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
return parseSellerSettlementObject(raw);
|
|
535
|
+
}
|
|
536
|
+
recordReconciledInference(route, endpoint, requestId, usage, settlement, prompt, response) {
|
|
537
|
+
if (settlement) {
|
|
538
|
+
this.tokenStore.reconcileTokenBalance({
|
|
539
|
+
sellerKey: route.seller.id,
|
|
540
|
+
balanceMicros: settlement.remainingCreditMicros,
|
|
541
|
+
reservedMicros: settlement.reservedBalanceMicros ?? 0,
|
|
542
|
+
spentMicros: settlement.spentMicros ?? 0,
|
|
543
|
+
balanceSource: "seller_settlement_summary"
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
const settledMicros = settlement?.settledMicros;
|
|
547
|
+
this.tokenStore.recordInferenceLedger({
|
|
548
|
+
requestId: settlement?.requestId || requestId,
|
|
549
|
+
sellerKey: route.seller.id,
|
|
550
|
+
modelId: route.modelId,
|
|
551
|
+
endpoint,
|
|
552
|
+
status: settlement ? "settled" : "estimated",
|
|
553
|
+
promptTokens: usage.promptTokens,
|
|
554
|
+
completionTokens: usage.completionTokens,
|
|
555
|
+
billedMicros: settledMicros ?? usage.billedMicros,
|
|
556
|
+
estimatedMicros: usage.billedMicros,
|
|
557
|
+
settledMicros,
|
|
558
|
+
settledUsdMicros: settlement?.settledUsdMicros,
|
|
559
|
+
priceVersion: settlement?.priceVersion,
|
|
560
|
+
balanceSnapshotMicros: settlement?.remainingCreditMicros,
|
|
561
|
+
balanceSource: settlement ? "seller_authoritative" : "estimated",
|
|
562
|
+
prompt,
|
|
563
|
+
response
|
|
564
|
+
});
|
|
565
|
+
logger.info("inference.ledger.recorded", "safe inference ledger recorded", {
|
|
566
|
+
requestId: settlement?.requestId || requestId,
|
|
567
|
+
sellerKey: route.seller.id,
|
|
568
|
+
model: route.modelId,
|
|
569
|
+
endpoint,
|
|
570
|
+
status: settlement ? "settled" : "estimated",
|
|
571
|
+
estimatedMicros: usage.billedMicros,
|
|
572
|
+
settledMicros,
|
|
573
|
+
balanceSource: settlement ? "seller_authoritative" : "estimated"
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
async refreshSellerBalance(route, token, balanceSource) {
|
|
577
|
+
const sellerKey = route.seller.id;
|
|
578
|
+
const sellerUrl = normalizeSellerUrl(route.seller);
|
|
579
|
+
const response = await fetch(`${sellerUrl}/v1/balance`, {
|
|
580
|
+
headers: { "Authorization": `Bearer ${token}` }
|
|
581
|
+
});
|
|
582
|
+
if (!response.ok) {
|
|
583
|
+
logger.warn("token.balance_refresh.failed", "seller balance refresh failed", {
|
|
584
|
+
sellerKey,
|
|
585
|
+
model: route.modelId,
|
|
586
|
+
status: response.status
|
|
587
|
+
});
|
|
588
|
+
return undefined;
|
|
589
|
+
}
|
|
590
|
+
const data = await response.json();
|
|
591
|
+
const creditMicros = numericHeaderField(data.creditMicros ?? data.credit_micros) ?? 0;
|
|
592
|
+
const reservedMicros = numericHeaderField(data.reservedMicros ?? data.reserved_micros) ?? 0;
|
|
593
|
+
const spentMicros = numericHeaderField(data.spentMicros ?? data.spent_micros) ?? 0;
|
|
594
|
+
const snapshot = {
|
|
595
|
+
creditMicros,
|
|
596
|
+
reservedMicros,
|
|
597
|
+
spentMicros,
|
|
598
|
+
availableMicros: Math.max(0, creditMicros - reservedMicros)
|
|
599
|
+
};
|
|
600
|
+
this.tokenStore.reconcileTokenBalance({
|
|
601
|
+
sellerKey,
|
|
602
|
+
balanceMicros: snapshot.availableMicros,
|
|
603
|
+
reservedMicros,
|
|
604
|
+
spentMicros,
|
|
605
|
+
balanceSource
|
|
606
|
+
});
|
|
607
|
+
logger.info("token.balance_refresh.succeeded", "seller balance refreshed", {
|
|
608
|
+
sellerKey,
|
|
609
|
+
model: route.modelId,
|
|
610
|
+
availableMicros: snapshot.availableMicros,
|
|
611
|
+
reservedMicros,
|
|
612
|
+
spentMicros,
|
|
613
|
+
balanceSource
|
|
614
|
+
});
|
|
615
|
+
return snapshot;
|
|
616
|
+
}
|
|
617
|
+
isInsufficientFundsResponse(status, bodyText) {
|
|
618
|
+
if (status !== 402) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
const parsed = JSON.parse(bodyText);
|
|
623
|
+
const code = parsed.error?.code || "";
|
|
624
|
+
const message = parsed.error?.message || "";
|
|
625
|
+
return code === "insufficient_funds" || /insufficient funds/i.test(message);
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
return /insufficient funds/i.test(bodyText);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async recoverFromInsufficientFunds(route, token) {
|
|
632
|
+
const sellerKey = route.seller.id;
|
|
633
|
+
this.tokenStore.markTokenStale(sellerKey);
|
|
634
|
+
const snapshot = await this.refreshSellerBalance(route, token, "seller_402_refresh");
|
|
635
|
+
const rebuyMinBalanceMicros = this.tokenRebuyMinBalanceMicros();
|
|
636
|
+
if (!snapshot || snapshot.availableMicros <= rebuyMinBalanceMicros) {
|
|
637
|
+
logger.info("purchase.retry_after_402.started", "seller 402 triggered one-shot auto purchase retry", {
|
|
638
|
+
sellerKey,
|
|
639
|
+
model: route.modelId,
|
|
640
|
+
availableMicros: snapshot?.availableMicros ?? 0,
|
|
641
|
+
rebuyMinBalanceMicros
|
|
642
|
+
});
|
|
643
|
+
return await this.getOrPurchaseToken(route);
|
|
644
|
+
}
|
|
645
|
+
const cached = this.tokenStore.getToken(sellerKey);
|
|
646
|
+
return cached?.token || token;
|
|
647
|
+
}
|
|
264
648
|
inferPromptForHash(body) {
|
|
265
649
|
if (!body || typeof body !== "object") {
|
|
266
650
|
return undefined;
|
|
@@ -286,7 +670,7 @@ export class TokenbuddyDaemon {
|
|
|
286
670
|
}
|
|
287
671
|
async getOrPurchaseToken(route) {
|
|
288
672
|
const sellerKey = route.seller.id;
|
|
289
|
-
const sellerUrl =
|
|
673
|
+
const sellerUrl = normalizeSellerUrl(route.seller);
|
|
290
674
|
const { modelId, paymentMethod } = route;
|
|
291
675
|
const cached = this.tokenStore.getToken(sellerKey);
|
|
292
676
|
const rebuyMinBalanceMicros = this.tokenRebuyMinBalanceMicros();
|
|
@@ -461,7 +845,7 @@ export class TokenbuddyDaemon {
|
|
|
461
845
|
const timeoutMs = this.clawtipProofTimeoutMs();
|
|
462
846
|
const payload = JSON.stringify({
|
|
463
847
|
sellerKey: route.seller.id,
|
|
464
|
-
sellerUrl:
|
|
848
|
+
sellerUrl: normalizeSellerUrl(route.seller),
|
|
465
849
|
modelId: route.modelId,
|
|
466
850
|
purchase: createData,
|
|
467
851
|
paymentInstructions: createData.paymentInstructions || createData.payment_instructions,
|
|
@@ -542,7 +926,7 @@ export class TokenbuddyDaemon {
|
|
|
542
926
|
copyUpstreamHeaders(upstreamResponse, res) {
|
|
543
927
|
upstreamResponse.headers.forEach((value, key) => {
|
|
544
928
|
const lowerKey = key.toLowerCase();
|
|
545
|
-
if (["connection", "keep-alive", "transfer-encoding", "upgrade"].includes(lowerKey)) {
|
|
929
|
+
if (["connection", "content-encoding", "content-length", "keep-alive", "transfer-encoding", "upgrade"].includes(lowerKey)) {
|
|
546
930
|
return;
|
|
547
931
|
}
|
|
548
932
|
res.setHeader(key, value);
|
|
@@ -551,7 +935,8 @@ export class TokenbuddyDaemon {
|
|
|
551
935
|
async forwardProxyRequest(endpoint, req, res) {
|
|
552
936
|
const startedAt = Date.now();
|
|
553
937
|
const body = req.body || {};
|
|
554
|
-
const
|
|
938
|
+
const { requestedModelId, resolvedModelId } = this.resolveRouteModelId(endpoint, body);
|
|
939
|
+
const modelId = resolvedModelId;
|
|
555
940
|
const requestId = req.header("x-request-id") || (body && typeof body === "object" ? body.requestId : undefined) || `proxy_req_${crypto.randomBytes(8).toString("hex")}`;
|
|
556
941
|
const idempotencyKey = req.header("idempotency-key") || `idem_${crypto.randomBytes(12).toString("hex")}`;
|
|
557
942
|
if (!modelId) {
|
|
@@ -570,23 +955,16 @@ export class TokenbuddyDaemon {
|
|
|
570
955
|
requestId,
|
|
571
956
|
sellerKey,
|
|
572
957
|
model: modelId,
|
|
958
|
+
requestedModel: requestedModelId,
|
|
573
959
|
endpoint,
|
|
574
960
|
stream: Boolean(body.stream)
|
|
575
961
|
});
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
const upstreamBody = {
|
|
962
|
+
const sellerUrl = normalizeSellerUrl(route.seller);
|
|
963
|
+
const upstreamBody = this.applyResolvedModelToBody(endpoint, {
|
|
579
964
|
...body,
|
|
580
965
|
requestId
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
requestId,
|
|
584
|
-
sellerKey,
|
|
585
|
-
model: modelId,
|
|
586
|
-
endpoint,
|
|
587
|
-
stream: Boolean(body.stream)
|
|
588
|
-
});
|
|
589
|
-
const upstreamResponse = await fetch(`${sellerUrl}${endpoint}`, {
|
|
966
|
+
}, modelId);
|
|
967
|
+
const sendSellerRequest = async (token) => fetch(`${sellerUrl}${endpoint}`, {
|
|
590
968
|
method: "POST",
|
|
591
969
|
headers: {
|
|
592
970
|
"Content-Type": "application/json",
|
|
@@ -596,25 +974,64 @@ export class TokenbuddyDaemon {
|
|
|
596
974
|
},
|
|
597
975
|
body: JSON.stringify(upstreamBody)
|
|
598
976
|
});
|
|
977
|
+
logger.info("proxy.upstream_fetch.started", "proxy upstream fetch started", {
|
|
978
|
+
requestId,
|
|
979
|
+
sellerKey,
|
|
980
|
+
model: modelId,
|
|
981
|
+
endpoint,
|
|
982
|
+
stream: Boolean(body.stream)
|
|
983
|
+
});
|
|
984
|
+
let token = await this.getOrPurchaseToken(route);
|
|
985
|
+
let upstreamResponse = await sendSellerRequest(token);
|
|
599
986
|
if (!upstreamResponse.ok) {
|
|
600
987
|
const errorBody = await upstreamResponse.text();
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
988
|
+
if (this.isInsufficientFundsResponse(upstreamResponse.status, errorBody)) {
|
|
989
|
+
token = await this.recoverFromInsufficientFunds(route, token);
|
|
990
|
+
upstreamResponse = await sendSellerRequest(token);
|
|
991
|
+
if (upstreamResponse.ok) {
|
|
992
|
+
logger.info("proxy.retry_after_402.succeeded", "seller request succeeded after one-shot auto purchase retry", {
|
|
993
|
+
requestId,
|
|
994
|
+
sellerKey,
|
|
995
|
+
model: modelId,
|
|
996
|
+
endpoint,
|
|
997
|
+
durationMs: Date.now() - startedAt
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
const retryErrorBody = await upstreamResponse.text();
|
|
1002
|
+
logger.warn("proxy.retry_after_402.failed", "seller request still failed after one-shot auto purchase retry", {
|
|
1003
|
+
requestId,
|
|
1004
|
+
sellerKey,
|
|
1005
|
+
model: modelId,
|
|
1006
|
+
endpoint,
|
|
1007
|
+
status: upstreamResponse.status,
|
|
1008
|
+
durationMs: Date.now() - startedAt
|
|
1009
|
+
});
|
|
1010
|
+
this.copyUpstreamHeaders(upstreamResponse, res);
|
|
1011
|
+
res.status(upstreamResponse.status);
|
|
1012
|
+
res.send(retryErrorBody);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
logger.warn("proxy.upstream_fetch.failed", "proxy upstream fetch returned non-ok status", {
|
|
1018
|
+
requestId,
|
|
1019
|
+
sellerKey,
|
|
1020
|
+
model: modelId,
|
|
1021
|
+
endpoint,
|
|
1022
|
+
status: upstreamResponse.status,
|
|
1023
|
+
durationMs: Date.now() - startedAt
|
|
1024
|
+
});
|
|
1025
|
+
if (this.shouldFailoverStatus(upstreamResponse.status) && routeIndex < routes.length - 1) {
|
|
1026
|
+
lastError = new Error(`seller ${sellerKey} returned ${upstreamResponse.status}`);
|
|
1027
|
+
this.logFailover(route, endpoint, routeIndex, "upstream_status", upstreamResponse.status);
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
this.copyUpstreamHeaders(upstreamResponse, res);
|
|
1031
|
+
res.status(upstreamResponse.status);
|
|
1032
|
+
res.send(errorBody);
|
|
1033
|
+
return;
|
|
613
1034
|
}
|
|
614
|
-
this.copyUpstreamHeaders(upstreamResponse, res);
|
|
615
|
-
res.status(upstreamResponse.status);
|
|
616
|
-
res.send(errorBody);
|
|
617
|
-
return;
|
|
618
1035
|
}
|
|
619
1036
|
this.copyUpstreamHeaders(upstreamResponse, res);
|
|
620
1037
|
res.status(upstreamResponse.status);
|
|
@@ -634,64 +1051,56 @@ export class TokenbuddyDaemon {
|
|
|
634
1051
|
return;
|
|
635
1052
|
}
|
|
636
1053
|
let bytes = 0;
|
|
1054
|
+
const decoder = new TextDecoder();
|
|
1055
|
+
const responsesStreamNormalizer = new ResponsesStreamNormalizer();
|
|
1056
|
+
const settlementExtractor = new SellerSettlementStreamExtractor();
|
|
637
1057
|
while (true) {
|
|
638
1058
|
const { done, value } = await reader.read();
|
|
639
1059
|
if (done) {
|
|
640
1060
|
break;
|
|
641
1061
|
}
|
|
642
1062
|
bytes += value.byteLength;
|
|
643
|
-
|
|
1063
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1064
|
+
const sellerChunk = settlementExtractor.push(chunk);
|
|
1065
|
+
if (sellerChunk.length === 0) {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if (endpoint === "/v1/responses") {
|
|
1069
|
+
const normalized = responsesStreamNormalizer.push(sellerChunk);
|
|
1070
|
+
if (normalized.length > 0) {
|
|
1071
|
+
res.write(`${normalized}\n\n`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
else {
|
|
1075
|
+
res.write(sellerChunk);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const settlementTrailing = settlementExtractor.finish();
|
|
1079
|
+
if (settlementTrailing.downstream.length > 0) {
|
|
1080
|
+
if (endpoint === "/v1/responses") {
|
|
1081
|
+
const normalized = responsesStreamNormalizer.push(settlementTrailing.downstream);
|
|
1082
|
+
if (normalized.length > 0) {
|
|
1083
|
+
res.write(`${normalized}\n\n`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
res.write(settlementTrailing.downstream);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (endpoint === "/v1/responses") {
|
|
1091
|
+
const trailing = responsesStreamNormalizer.finish();
|
|
1092
|
+
if (trailing.length > 0) {
|
|
1093
|
+
res.write(`${trailing}\n\n`);
|
|
1094
|
+
}
|
|
644
1095
|
}
|
|
645
1096
|
res.end();
|
|
646
|
-
|
|
647
|
-
this.tokenStore.deductBalance(sellerKey, billedMicros);
|
|
648
|
-
this.tokenStore.recordInferenceLedger({
|
|
649
|
-
requestId,
|
|
650
|
-
sellerKey,
|
|
651
|
-
modelId,
|
|
652
|
-
endpoint,
|
|
653
|
-
status: "settled",
|
|
654
|
-
promptTokens: 0,
|
|
655
|
-
completionTokens: 0,
|
|
656
|
-
billedMicros,
|
|
657
|
-
prompt: this.inferPromptForHash(body)
|
|
658
|
-
});
|
|
659
|
-
logger.info("inference.ledger.recorded", "safe inference ledger recorded", {
|
|
660
|
-
requestId,
|
|
661
|
-
sellerKey,
|
|
662
|
-
model: modelId,
|
|
663
|
-
endpoint,
|
|
664
|
-
status: "settled",
|
|
665
|
-
billedMicros
|
|
666
|
-
});
|
|
1097
|
+
this.recordReconciledInference(route, endpoint, requestId, { promptTokens: 0, completionTokens: 0, billedMicros: Math.max(1, bytes) }, this.parseSellerSettlementSummary(upstreamResponse.headers) ?? settlementTrailing.settlement ?? settlementExtractor.current(), this.inferPromptForHash(body));
|
|
667
1098
|
return;
|
|
668
1099
|
}
|
|
669
1100
|
const responseBody = await upstreamResponse.text();
|
|
670
1101
|
res.send(responseBody);
|
|
671
1102
|
const usage = this.readUsage(responseBody);
|
|
672
|
-
this.
|
|
673
|
-
this.tokenStore.recordInferenceLedger({
|
|
674
|
-
requestId,
|
|
675
|
-
sellerKey,
|
|
676
|
-
modelId,
|
|
677
|
-
endpoint,
|
|
678
|
-
status: "settled",
|
|
679
|
-
promptTokens: usage.promptTokens,
|
|
680
|
-
completionTokens: usage.completionTokens,
|
|
681
|
-
billedMicros: usage.billedMicros,
|
|
682
|
-
prompt: this.inferPromptForHash(body),
|
|
683
|
-
response: responseBody
|
|
684
|
-
});
|
|
685
|
-
logger.info("inference.ledger.recorded", "safe inference ledger recorded", {
|
|
686
|
-
requestId,
|
|
687
|
-
sellerKey,
|
|
688
|
-
model: modelId,
|
|
689
|
-
endpoint,
|
|
690
|
-
status: "settled",
|
|
691
|
-
promptTokens: usage.promptTokens,
|
|
692
|
-
completionTokens: usage.completionTokens,
|
|
693
|
-
billedMicros: usage.billedMicros
|
|
694
|
-
});
|
|
1103
|
+
this.recordReconciledInference(route, endpoint, requestId, usage, this.parseSellerSettlementSummary(upstreamResponse.headers), this.inferPromptForHash(body), responseBody);
|
|
695
1104
|
return;
|
|
696
1105
|
}
|
|
697
1106
|
catch (routeError) {
|
|
@@ -867,7 +1276,9 @@ export class TokenbuddyDaemon {
|
|
|
867
1276
|
const changes = previewProviderInstall({
|
|
868
1277
|
providers: Array.isArray(req.body?.providers) ? req.body.providers : [],
|
|
869
1278
|
proxyUrl: String(req.body?.proxyUrl || ""),
|
|
870
|
-
model:
|
|
1279
|
+
model: typeof req.body?.model === "string" ? req.body.model : undefined,
|
|
1280
|
+
providerSelections: req.body?.providerSelections,
|
|
1281
|
+
sellerRouting: req.body?.sellerRouting,
|
|
871
1282
|
home: typeof req.body?.home === "string" ? req.body.home : undefined
|
|
872
1283
|
});
|
|
873
1284
|
logger.info("provider.install.previewed", "provider install previewed", {
|
|
@@ -894,7 +1305,9 @@ export class TokenbuddyDaemon {
|
|
|
894
1305
|
const applied = applyProviderInstall({
|
|
895
1306
|
providers: Array.isArray(req.body?.providers) ? req.body.providers : [],
|
|
896
1307
|
proxyUrl: String(req.body?.proxyUrl || ""),
|
|
897
|
-
model:
|
|
1308
|
+
model: typeof req.body?.model === "string" ? req.body.model : undefined,
|
|
1309
|
+
providerSelections: req.body?.providerSelections,
|
|
1310
|
+
sellerRouting: req.body?.sellerRouting,
|
|
898
1311
|
home: typeof req.body?.home === "string" ? req.body.home : undefined
|
|
899
1312
|
}, this.tokenStore);
|
|
900
1313
|
logger.info("provider.install.applied", "provider install applied", {
|