@routstr/sdk 0.3.8 → 0.3.10
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/browser.d.mts +12 -0
- package/dist/browser.d.ts +12 -0
- package/dist/browser.js +6278 -0
- package/dist/browser.js.map +1 -0
- package/dist/browser.mjs +6230 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/bun.d.mts +29 -0
- package/dist/bun.d.ts +29 -0
- package/dist/bun.js +6586 -0
- package/dist/bun.js.map +1 -0
- package/dist/bun.mjs +6532 -0
- package/dist/bun.mjs.map +1 -0
- package/dist/bunSqlite-BMTseLIz.d.ts +18 -0
- package/dist/bunSqlite-D6AreVE2.d.mts +18 -0
- package/dist/client/index.d.mts +63 -41
- package/dist/client/index.d.ts +63 -41
- package/dist/client/index.js +1223 -1658
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +1223 -1659
- package/dist/client/index.mjs.map +1 -1
- package/dist/discovery/index.d.mts +67 -3
- package/dist/discovery/index.d.ts +67 -3
- package/dist/discovery/index.js +242 -79
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +242 -79
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1975 -2004
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1973 -2001
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +22 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +6651 -0
- package/dist/node.js.map +1 -0
- package/dist/node.mjs +6599 -0
- package/dist/node.mjs.map +1 -0
- package/dist/storage/bun.d.mts +16 -0
- package/dist/storage/bun.d.ts +16 -0
- package/dist/storage/bun.js +1801 -0
- package/dist/storage/bun.js.map +1 -0
- package/dist/storage/bun.mjs +1777 -0
- package/dist/storage/bun.mjs.map +1 -0
- package/dist/storage/index.d.mts +30 -30
- package/dist/storage/index.d.ts +30 -30
- package/dist/storage/index.js +393 -625
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +392 -622
- package/dist/storage/index.mjs.map +1 -1
- package/dist/storage/node.d.mts +22 -0
- package/dist/storage/node.d.ts +22 -0
- package/dist/storage/node.js +1864 -0
- package/dist/storage/node.js.map +1 -0
- package/dist/storage/node.mjs +1842 -0
- package/dist/storage/node.mjs.map +1 -0
- package/dist/{store-C6dfj1cc.d.mts → store-BiuM2V9N.d.mts} +14 -0
- package/dist/{store-58VcEUoA.d.ts → store-C8MZlfuz.d.ts} +14 -0
- package/dist/wallet/index.d.mts +4 -0
- package/dist/wallet/index.d.ts +4 -0
- package/dist/wallet/index.js +11 -4
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +11 -4
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +28 -2
package/dist/index.js
CHANGED
|
@@ -147,7 +147,10 @@ var ModelManager = class _ModelManager {
|
|
|
147
147
|
this.includeProviderUrls = config.includeProviderUrls || [];
|
|
148
148
|
this.excludeProviderUrls = config.excludeProviderUrls || [];
|
|
149
149
|
this.routstrPubkey = config.routstrPubkey || "4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8";
|
|
150
|
+
this.nostrRelays = config.nostrRelays;
|
|
150
151
|
this.logger = (config.logger ?? consoleLogger).child("ModelManager");
|
|
152
|
+
this.eventStoreDbPath = config.eventStoreDbPath;
|
|
153
|
+
this.persistentEventDatabaseFactory = config.persistentEventDatabaseFactory;
|
|
151
154
|
}
|
|
152
155
|
adapter;
|
|
153
156
|
cacheTTL;
|
|
@@ -155,8 +158,15 @@ var ModelManager = class _ModelManager {
|
|
|
155
158
|
includeProviderUrls;
|
|
156
159
|
excludeProviderUrls;
|
|
157
160
|
routstrPubkey;
|
|
161
|
+
nostrRelays;
|
|
158
162
|
logger;
|
|
159
163
|
providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
|
|
164
|
+
/** Persistent event store for relay-fetched events (null if not configured/initialized) */
|
|
165
|
+
eventStore = null;
|
|
166
|
+
eventStoreDb = null;
|
|
167
|
+
eventStoreInitPromise = null;
|
|
168
|
+
eventStoreDbPath;
|
|
169
|
+
persistentEventDatabaseFactory;
|
|
160
170
|
/**
|
|
161
171
|
* Get the list of bootstrapped provider base URLs
|
|
162
172
|
* @returns Array of provider base URLs
|
|
@@ -164,6 +174,103 @@ var ModelManager = class _ModelManager {
|
|
|
164
174
|
getBaseUrls() {
|
|
165
175
|
return this.adapter.getBaseUrlsList();
|
|
166
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Lazily initialize the persistent event store.
|
|
179
|
+
* Returns null if no eventStoreDbPath was provided.
|
|
180
|
+
*/
|
|
181
|
+
async ensureEventStore() {
|
|
182
|
+
if (!this.eventStoreDbPath) return null;
|
|
183
|
+
if (this.eventStore) return this.eventStore;
|
|
184
|
+
if (!this.eventStoreInitPromise) {
|
|
185
|
+
this.eventStoreInitPromise = (async () => {
|
|
186
|
+
try {
|
|
187
|
+
const db = await this.createPersistentEventDatabase();
|
|
188
|
+
this.eventStoreDb = db;
|
|
189
|
+
this.eventStore = new applesauceCore.EventStore({ database: db });
|
|
190
|
+
this.initializeEventStoreMetadata();
|
|
191
|
+
this.logger.log(
|
|
192
|
+
`Persistent event store initialized at ${this.eventStoreDbPath}`
|
|
193
|
+
);
|
|
194
|
+
return this.eventStore;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.eventStoreInitPromise = null;
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Persistent Nostr event storage requires a runtime-specific database factory. Use @routstr/sdk/node, @routstr/sdk/bun, inject persistentEventDatabaseFactory, or omit eventStoreDbPath. (${error})`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
})();
|
|
202
|
+
}
|
|
203
|
+
return this.eventStoreInitPromise;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get the persistent event store, initializing it if configured.
|
|
207
|
+
* Returns null if no eventStoreDbPath was provided.
|
|
208
|
+
*/
|
|
209
|
+
async getEventStore() {
|
|
210
|
+
return this.ensureEventStore();
|
|
211
|
+
}
|
|
212
|
+
async createPersistentEventDatabase() {
|
|
213
|
+
if (!this.eventStoreDbPath) {
|
|
214
|
+
throw new Error("eventStoreDbPath is required");
|
|
215
|
+
}
|
|
216
|
+
if (!this.persistentEventDatabaseFactory) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
"persistentEventDatabaseFactory is required. Import ModelManager from @routstr/sdk/node or @routstr/sdk/bun for SQLite-backed persistent event storage."
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return this.persistentEventDatabaseFactory(this.eventStoreDbPath);
|
|
222
|
+
}
|
|
223
|
+
/** Close the persistent event store database handle, if configured. */
|
|
224
|
+
closeEventStore() {
|
|
225
|
+
this.eventStoreDb?.close?.();
|
|
226
|
+
this.eventStore = null;
|
|
227
|
+
this.eventStoreDb = null;
|
|
228
|
+
this.eventStoreInitPromise = null;
|
|
229
|
+
}
|
|
230
|
+
initializeEventStoreMetadata() {
|
|
231
|
+
this.eventStoreDb?.db?.exec(
|
|
232
|
+
`CREATE TABLE IF NOT EXISTS routstr_event_cache_metadata (
|
|
233
|
+
event_id TEXT PRIMARY KEY,
|
|
234
|
+
fetched_at INTEGER NOT NULL
|
|
235
|
+
)`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
markEventFetched(event, fetchedAt = Date.now()) {
|
|
239
|
+
const db = this.eventStoreDb?.db;
|
|
240
|
+
if (!db) return;
|
|
241
|
+
db.prepare(
|
|
242
|
+
`INSERT INTO routstr_event_cache_metadata (event_id, fetched_at)
|
|
243
|
+
VALUES (?, ?)
|
|
244
|
+
ON CONFLICT(event_id) DO UPDATE SET fetched_at = excluded.fetched_at`
|
|
245
|
+
).run?.(event.id, fetchedAt);
|
|
246
|
+
}
|
|
247
|
+
getEventFetchedAt(event) {
|
|
248
|
+
const db = this.eventStoreDb?.db;
|
|
249
|
+
if (!db) return void 0;
|
|
250
|
+
const row = db.prepare(
|
|
251
|
+
`SELECT fetched_at FROM routstr_event_cache_metadata WHERE event_id = ?`
|
|
252
|
+
).get?.(event.id);
|
|
253
|
+
return typeof row?.fetched_at === "number" ? row.fetched_at : void 0;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Check the persistent event store for fresh cached events.
|
|
257
|
+
* Returns events from SQLite if they were fetched within `maxAge`, otherwise
|
|
258
|
+
* returns empty array (caller should hit relays). Events without local fetch
|
|
259
|
+
* metadata fall back to Nostr created_at for backwards compatibility.
|
|
260
|
+
*/
|
|
261
|
+
async getCachedNostrEvents(filter, maxAge, forceRefresh = false) {
|
|
262
|
+
const eventStore = await this.ensureEventStore();
|
|
263
|
+
if (forceRefresh) return [];
|
|
264
|
+
if (!eventStore) return [];
|
|
265
|
+
const timeline = eventStore.getTimeline(filter);
|
|
266
|
+
if (timeline.length === 0) return [];
|
|
267
|
+
const cutoff = Date.now() - maxAge;
|
|
268
|
+
const freshest = Math.max(
|
|
269
|
+
...timeline.map((e) => this.getEventFetchedAt(e) ?? e.created_at * 1e3)
|
|
270
|
+
);
|
|
271
|
+
if (freshest < cutoff) return [];
|
|
272
|
+
return timeline;
|
|
273
|
+
}
|
|
167
274
|
static async init(adapter, config = {}, options = {}) {
|
|
168
275
|
const manager = new _ModelManager(adapter, config);
|
|
169
276
|
const torMode = options.torMode ?? false;
|
|
@@ -192,19 +299,31 @@ var ModelManager = class _ModelManager {
|
|
|
192
299
|
torMode
|
|
193
300
|
);
|
|
194
301
|
await this.fetchRoutstr21Models(forceRefresh);
|
|
195
|
-
await this.syncReviewedProvidersFromNostr(
|
|
302
|
+
await this.syncReviewedProvidersFromNostr(
|
|
303
|
+
filteredCachedUrls,
|
|
304
|
+
this.providerNodePubkeysByUrl,
|
|
305
|
+
forceRefresh
|
|
306
|
+
);
|
|
196
307
|
return filteredCachedUrls;
|
|
197
308
|
}
|
|
198
309
|
}
|
|
199
310
|
}
|
|
200
311
|
try {
|
|
201
|
-
const nostrProviders = await this.bootstrapFromNostr(
|
|
312
|
+
const nostrProviders = await this.bootstrapFromNostr(
|
|
313
|
+
38421,
|
|
314
|
+
torMode,
|
|
315
|
+
forceRefresh
|
|
316
|
+
);
|
|
202
317
|
if (nostrProviders.length > 0) {
|
|
203
318
|
const filtered = this.filterBaseUrlsForTor(nostrProviders, torMode);
|
|
204
319
|
this.adapter.setBaseUrlsList(filtered);
|
|
205
320
|
this.adapter.setBaseUrlsLastUpdate(Date.now());
|
|
206
321
|
await this.fetchRoutstr21Models(forceRefresh);
|
|
207
|
-
await this.syncReviewedProvidersFromNostr(
|
|
322
|
+
await this.syncReviewedProvidersFromNostr(
|
|
323
|
+
filtered,
|
|
324
|
+
this.providerNodePubkeysByUrl,
|
|
325
|
+
forceRefresh
|
|
326
|
+
);
|
|
208
327
|
return filtered;
|
|
209
328
|
}
|
|
210
329
|
} catch (e) {
|
|
@@ -213,42 +332,59 @@ var ModelManager = class _ModelManager {
|
|
|
213
332
|
return this.bootstrapFromHttp(torMode, forceRefresh);
|
|
214
333
|
}
|
|
215
334
|
/**
|
|
216
|
-
*
|
|
335
|
+
* Resolve Nostr relay URLs for a given use case.
|
|
336
|
+
* Returns user-configured relays if set, otherwise the provided defaults.
|
|
337
|
+
*/
|
|
338
|
+
getNostrRelays(defaults) {
|
|
339
|
+
return this.nostrRelays && this.nostrRelays.length > 0 ? this.nostrRelays : defaults;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Bootstrap providers from Nostr network (kind 38421)
|
|
217
343
|
* @param kind The Nostr kind to fetch
|
|
218
344
|
* @param torMode Whether running in Tor context
|
|
219
345
|
* @returns Array of provider base URLs
|
|
220
346
|
*/
|
|
221
|
-
async bootstrapFromNostr(kind, torMode) {
|
|
222
|
-
const
|
|
347
|
+
async bootstrapFromNostr(kind, torMode, forceRefresh = false) {
|
|
348
|
+
const relays = this.getNostrRelays([
|
|
223
349
|
"wss://relay.primal.net",
|
|
224
350
|
"wss://nos.lol",
|
|
225
351
|
"wss://relay.damus.io"
|
|
226
|
-
];
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
352
|
+
]);
|
|
353
|
+
const cached = await this.getCachedNostrEvents(
|
|
354
|
+
{ kinds: [kind] },
|
|
355
|
+
this.cacheTTL,
|
|
356
|
+
forceRefresh
|
|
357
|
+
);
|
|
358
|
+
let sessionEvents = cached;
|
|
359
|
+
if (cached.length === 0) {
|
|
360
|
+
const pool = new applesauceRelay.RelayPool();
|
|
361
|
+
const timeoutMs = 5e3;
|
|
362
|
+
await new Promise((resolve) => {
|
|
363
|
+
pool.req(relays, {
|
|
364
|
+
kinds: [kind],
|
|
365
|
+
limit: 100
|
|
366
|
+
}).pipe(
|
|
367
|
+
applesauceRelay.onlyEvents(),
|
|
368
|
+
rxjs.tap((event) => {
|
|
369
|
+
sessionEvents.push(event);
|
|
370
|
+
this.eventStore?.add(event);
|
|
371
|
+
this.markEventFetched(event);
|
|
372
|
+
})
|
|
373
|
+
).subscribe({
|
|
374
|
+
complete: () => {
|
|
375
|
+
resolve();
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
setTimeout(() => {
|
|
241
379
|
resolve();
|
|
242
|
-
}
|
|
380
|
+
}, timeoutMs);
|
|
243
381
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
248
|
-
const timeline = localEventStore.getTimeline({ kinds: [kind] });
|
|
382
|
+
} else {
|
|
383
|
+
this.logger.log(`Using ${cached.length} cached kind ${kind} events from persistent store`);
|
|
384
|
+
}
|
|
249
385
|
const bases = /* @__PURE__ */ new Set();
|
|
250
386
|
this.providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
|
|
251
|
-
for (const event of
|
|
387
|
+
for (const event of sessionEvents) {
|
|
252
388
|
const eventUrls = [];
|
|
253
389
|
for (const tag of event.tags) {
|
|
254
390
|
if (tag[0] === "u" && typeof tag[1] === "string") {
|
|
@@ -356,7 +492,11 @@ var ModelManager = class _ModelManager {
|
|
|
356
492
|
this.adapter.setBaseUrlsList(list);
|
|
357
493
|
this.adapter.setBaseUrlsLastUpdate(Date.now());
|
|
358
494
|
await this.fetchRoutstr21Models(forceRefresh);
|
|
359
|
-
await this.syncReviewedProvidersFromNostr(
|
|
495
|
+
await this.syncReviewedProvidersFromNostr(
|
|
496
|
+
list,
|
|
497
|
+
this.providerNodePubkeysByUrl,
|
|
498
|
+
forceRefresh
|
|
499
|
+
);
|
|
360
500
|
}
|
|
361
501
|
return list;
|
|
362
502
|
} catch (e) {
|
|
@@ -375,7 +515,7 @@ var ModelManager = class _ModelManager {
|
|
|
375
515
|
* @param baseUrls Current provider base URLs to evaluate
|
|
376
516
|
* @returns Array of provider base URLs disabled by the review set
|
|
377
517
|
*/
|
|
378
|
-
async syncReviewedProvidersFromNostr(baseUrls = this.adapter.getBaseUrlsList(), providerNodes = this.providerNodePubkeysByUrl) {
|
|
518
|
+
async syncReviewedProvidersFromNostr(baseUrls = this.adapter.getBaseUrlsList(), providerNodes = this.providerNodePubkeysByUrl, forceRefresh = false) {
|
|
379
519
|
if (baseUrls.length === 0) return [];
|
|
380
520
|
if (!this.adapter.setDisabledProviders) {
|
|
381
521
|
this.logger.warn(
|
|
@@ -383,30 +523,43 @@ var ModelManager = class _ModelManager {
|
|
|
383
523
|
);
|
|
384
524
|
return [];
|
|
385
525
|
}
|
|
386
|
-
const LGTM_RELAYS = [
|
|
387
|
-
"wss://relay.primal.net",
|
|
388
|
-
"wss://nos.lol",
|
|
389
|
-
"wss://relay.damus.io",
|
|
390
|
-
"wss://relay.routstr.com"
|
|
391
|
-
];
|
|
392
526
|
const reviewedNodePubkeys = /* @__PURE__ */ new Set();
|
|
393
527
|
{
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
528
|
+
const cached = await this.getCachedNostrEvents(
|
|
529
|
+
{ kinds: [38425], "#t": ["lgtm"], authors: [this.routstrPubkey] },
|
|
530
|
+
this.cacheTTL,
|
|
531
|
+
forceRefresh
|
|
532
|
+
);
|
|
533
|
+
let sessionEvents = cached;
|
|
534
|
+
if (cached.length === 0) {
|
|
535
|
+
const lgtmRelays = this.getNostrRelays([
|
|
536
|
+
"wss://relay.primal.net",
|
|
537
|
+
"wss://nos.lol",
|
|
538
|
+
"wss://relay.damus.io",
|
|
539
|
+
"wss://relay.routstr.com"
|
|
540
|
+
]);
|
|
541
|
+
const pool = new applesauceRelay.RelayPool();
|
|
542
|
+
const timeoutMs = 5e3;
|
|
543
|
+
await new Promise((resolve) => {
|
|
544
|
+
pool.req(lgtmRelays, {
|
|
545
|
+
kinds: [38425],
|
|
546
|
+
"#t": ["lgtm"],
|
|
547
|
+
limit: 500,
|
|
548
|
+
authors: [this.routstrPubkey]
|
|
549
|
+
}).pipe(
|
|
550
|
+
applesauceRelay.onlyEvents(),
|
|
551
|
+
rxjs.tap((event) => {
|
|
552
|
+
sessionEvents.push(event);
|
|
553
|
+
this.eventStore?.add(event);
|
|
554
|
+
this.markEventFetched(event);
|
|
555
|
+
})
|
|
556
|
+
).subscribe({ complete: () => resolve() });
|
|
557
|
+
setTimeout(() => resolve(), timeoutMs);
|
|
558
|
+
});
|
|
559
|
+
} else {
|
|
560
|
+
this.logger.log(`Using ${cached.length} cached kind 38425 events from persistent store`);
|
|
561
|
+
}
|
|
562
|
+
for (const event of sessionEvents) {
|
|
410
563
|
const hasLgtmTag = event.tags.some(
|
|
411
564
|
(tag) => tag[0] === "t" && tag[1]?.toLowerCase() === "lgtm"
|
|
412
565
|
);
|
|
@@ -515,7 +668,7 @@ var ModelManager = class _ModelManager {
|
|
|
515
668
|
if (this.isProviderDownError(error)) {
|
|
516
669
|
this.logger.warn(`Provider ${base} is down right now.`);
|
|
517
670
|
} else {
|
|
518
|
-
this.logger.warn(`
|
|
671
|
+
this.logger.warn(`Provider ${base} unreachable: ${error.message}`);
|
|
519
672
|
}
|
|
520
673
|
this.adapter.setProviderLastUpdate(base, Date.now());
|
|
521
674
|
return { success: false, base };
|
|
@@ -630,39 +783,49 @@ var ModelManager = class _ModelManager {
|
|
|
630
783
|
return cachedModels;
|
|
631
784
|
}
|
|
632
785
|
}
|
|
633
|
-
const
|
|
786
|
+
const relays = this.getNostrRelays([
|
|
634
787
|
"wss://relay.damus.io",
|
|
635
788
|
"wss://nos.lol",
|
|
636
789
|
"wss://relay.routstr.com"
|
|
637
|
-
];
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
790
|
+
]);
|
|
791
|
+
const cached = await this.getCachedNostrEvents(
|
|
792
|
+
{ kinds: [38423], "#d": ["routstr-21-models"], authors: [this.routstrPubkey] },
|
|
793
|
+
this.cacheTTL,
|
|
794
|
+
forceRefresh
|
|
795
|
+
);
|
|
796
|
+
let sessionEvents = cached;
|
|
797
|
+
if (cached.length === 0) {
|
|
798
|
+
const pool = new applesauceRelay.RelayPool();
|
|
799
|
+
const timeoutMs = 5e3;
|
|
800
|
+
await new Promise((resolve) => {
|
|
801
|
+
pool.req(relays, {
|
|
802
|
+
kinds: [38423],
|
|
803
|
+
"#d": ["routstr-21-models"],
|
|
804
|
+
limit: 1,
|
|
805
|
+
authors: [this.routstrPubkey]
|
|
806
|
+
}).pipe(
|
|
807
|
+
applesauceRelay.onlyEvents(),
|
|
808
|
+
rxjs.tap((event2) => {
|
|
809
|
+
sessionEvents.push(event2);
|
|
810
|
+
this.eventStore?.add(event2);
|
|
811
|
+
this.markEventFetched(event2);
|
|
812
|
+
})
|
|
813
|
+
).subscribe({
|
|
814
|
+
complete: () => {
|
|
815
|
+
resolve();
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
setTimeout(() => {
|
|
654
819
|
resolve();
|
|
655
|
-
}
|
|
820
|
+
}, timeoutMs);
|
|
656
821
|
});
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const timeline = localEventStore.getTimeline({ kinds: [38423] });
|
|
662
|
-
if (timeline.length === 0) {
|
|
822
|
+
} else {
|
|
823
|
+
this.logger.log(`Using ${cached.length} cached kind 38423 events from persistent store`);
|
|
824
|
+
}
|
|
825
|
+
if (sessionEvents.length === 0) {
|
|
663
826
|
return cachedModels.length > 0 ? cachedModels : [];
|
|
664
827
|
}
|
|
665
|
-
const event =
|
|
828
|
+
const event = sessionEvents[0];
|
|
666
829
|
try {
|
|
667
830
|
const content = JSON.parse(event.content);
|
|
668
831
|
const models = Array.isArray(content?.models) ? content.models : [];
|
|
@@ -1380,7 +1543,7 @@ var CashuSpender = class {
|
|
|
1380
1543
|
});
|
|
1381
1544
|
continue;
|
|
1382
1545
|
}
|
|
1383
|
-
if (balanceResult.amount >= 0) {
|
|
1546
|
+
if (balanceResult.amount >= 0 && !balanceResult.balanceUnknown) {
|
|
1384
1547
|
const balanceSat = balanceResult.unit === "msat" ? Math.floor(balanceResult.amount / 1e3) : balanceResult.amount;
|
|
1385
1548
|
this.storageAdapter.updateApiKeyBalance(
|
|
1386
1549
|
apiKeyEntry.baseUrl,
|
|
@@ -2129,17 +2292,24 @@ var BalanceManager = class _BalanceManager {
|
|
|
2129
2292
|
this.logger.warn("getTokenBalance: FAILED", data);
|
|
2130
2293
|
const isInvalidApiKey = response.status === 401 && data?.detail?.error?.code === "invalid_api_key" && data?.detail?.error?.message?.includes("proofs already spent");
|
|
2131
2294
|
return {
|
|
2132
|
-
amount:
|
|
2295
|
+
amount: 0,
|
|
2133
2296
|
reserved: data.reserved ?? 0,
|
|
2134
2297
|
unit: "msat",
|
|
2135
2298
|
apiKey: data.api_key,
|
|
2136
|
-
isInvalidApiKey
|
|
2299
|
+
isInvalidApiKey,
|
|
2300
|
+
balanceUnknown: true
|
|
2137
2301
|
};
|
|
2138
2302
|
}
|
|
2139
2303
|
} catch (error) {
|
|
2140
2304
|
this.logger.error("getTokenBalance error", error);
|
|
2141
2305
|
}
|
|
2142
|
-
return {
|
|
2306
|
+
return {
|
|
2307
|
+
amount: 0,
|
|
2308
|
+
reserved: 0,
|
|
2309
|
+
unit: "sat",
|
|
2310
|
+
apiKey: "",
|
|
2311
|
+
balanceUnknown: true
|
|
2312
|
+
};
|
|
2143
2313
|
}
|
|
2144
2314
|
/**
|
|
2145
2315
|
* Handle topup errors with specific error types
|
|
@@ -2174,529 +2344,210 @@ var BalanceManager = class _BalanceManager {
|
|
|
2174
2344
|
}
|
|
2175
2345
|
};
|
|
2176
2346
|
|
|
2177
|
-
//
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
const totalMsats = costObj.total_msats;
|
|
2194
|
-
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
2195
|
-
if (typeof totalMsats === "number") {
|
|
2196
|
-
satsCost = totalMsats / 1e3;
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
2200
|
-
return null;
|
|
2201
|
-
}
|
|
2202
|
-
return {
|
|
2203
|
-
promptTokens,
|
|
2204
|
-
completionTokens,
|
|
2205
|
-
totalTokens,
|
|
2206
|
-
cost,
|
|
2207
|
-
satsCost
|
|
2208
|
-
};
|
|
2209
|
-
}
|
|
2210
|
-
function extractResponseId(body) {
|
|
2211
|
-
if (!body || typeof body !== "object") return void 0;
|
|
2212
|
-
const id = body.id;
|
|
2213
|
-
if (typeof id !== "string") return void 0;
|
|
2214
|
-
const trimmed = id.trim();
|
|
2215
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
2216
|
-
}
|
|
2217
|
-
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
2218
|
-
if (!parsed || typeof parsed !== "object") {
|
|
2219
|
-
return null;
|
|
2220
|
-
}
|
|
2221
|
-
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
2222
|
-
const costObj = parsed.cost;
|
|
2223
|
-
const msats2 = costObj.total_msats ?? 0;
|
|
2224
|
-
const cost2 = costObj.total_usd ?? 0;
|
|
2225
|
-
if (msats2 === 0 && cost2 === 0) return null;
|
|
2226
|
-
return {
|
|
2227
|
-
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
2228
|
-
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
2229
|
-
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
2230
|
-
cost: Number(cost2),
|
|
2231
|
-
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
if (!parsed.usage) {
|
|
2235
|
-
return null;
|
|
2236
|
-
}
|
|
2237
|
-
const usage = parsed.usage;
|
|
2238
|
-
const usageCost = usage.cost;
|
|
2239
|
-
let cost = 0;
|
|
2240
|
-
let msats = 0;
|
|
2241
|
-
if (typeof usageCost === "number") {
|
|
2242
|
-
cost = usageCost;
|
|
2243
|
-
} else if (usageCost && typeof usageCost === "object") {
|
|
2244
|
-
cost = usageCost.total_usd ?? 0;
|
|
2245
|
-
msats = usageCost.total_msats ?? 0;
|
|
2347
|
+
// utils/torUtils.ts
|
|
2348
|
+
var TOR_ONION_SUFFIX = ".onion";
|
|
2349
|
+
var isTorContext = () => {
|
|
2350
|
+
if (typeof window === "undefined") return false;
|
|
2351
|
+
const hostname = window.location.hostname.toLowerCase();
|
|
2352
|
+
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2353
|
+
};
|
|
2354
|
+
var isOnionUrl = (url) => {
|
|
2355
|
+
if (!url) return false;
|
|
2356
|
+
const trimmed = url.trim().toLowerCase();
|
|
2357
|
+
if (!trimmed) return false;
|
|
2358
|
+
try {
|
|
2359
|
+
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
2360
|
+
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2361
|
+
} catch {
|
|
2362
|
+
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
2246
2363
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2364
|
+
};
|
|
2365
|
+
var shouldAllowHttp = (url, torMode) => {
|
|
2366
|
+
if (!url.startsWith("http://")) return true;
|
|
2367
|
+
if (url.includes("localhost") || url.includes("127.0.0.1")) return true;
|
|
2368
|
+
return torMode && isOnionUrl(url);
|
|
2369
|
+
};
|
|
2370
|
+
var normalizeProviderUrl = (url, torMode = false) => {
|
|
2371
|
+
if (!url || typeof url !== "string") return null;
|
|
2372
|
+
const trimmed = url.trim();
|
|
2373
|
+
if (!trimmed) return null;
|
|
2374
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
2375
|
+
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
2249
2376
|
}
|
|
2250
|
-
|
|
2251
|
-
|
|
2377
|
+
const useHttpForOnion = torMode && isOnionUrl(trimmed);
|
|
2378
|
+
const withProto = `${useHttpForOnion ? "http" : "https"}://${trimmed}`;
|
|
2379
|
+
return withProto.endsWith("/") ? withProto : `${withProto}/`;
|
|
2380
|
+
};
|
|
2381
|
+
var dedupePreserveOrder = (urls) => {
|
|
2382
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2383
|
+
const out = [];
|
|
2384
|
+
for (const url of urls) {
|
|
2385
|
+
if (!seen.has(url)) {
|
|
2386
|
+
seen.add(url);
|
|
2387
|
+
out.push(url);
|
|
2388
|
+
}
|
|
2252
2389
|
}
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
const
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2390
|
+
return out;
|
|
2391
|
+
};
|
|
2392
|
+
var getProviderEndpoints = (provider, torMode) => {
|
|
2393
|
+
const rawUrls = [
|
|
2394
|
+
provider.endpoint_url,
|
|
2395
|
+
...Array.isArray(provider.endpoint_urls) ? provider.endpoint_urls : [],
|
|
2396
|
+
provider.onion_url,
|
|
2397
|
+
...Array.isArray(provider.onion_urls) ? provider.onion_urls : []
|
|
2398
|
+
];
|
|
2399
|
+
const normalized = rawUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2400
|
+
const unique = dedupePreserveOrder(normalized).filter(
|
|
2401
|
+
(value) => shouldAllowHttp(value, torMode)
|
|
2402
|
+
);
|
|
2403
|
+
if (unique.length === 0) return [];
|
|
2404
|
+
const onion = unique.filter((value) => isOnionUrl(value));
|
|
2405
|
+
const clearnet = unique.filter((value) => !isOnionUrl(value));
|
|
2406
|
+
if (torMode) {
|
|
2407
|
+
return onion.length > 0 ? onion : clearnet;
|
|
2265
2408
|
}
|
|
2266
|
-
return
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
if (!
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
}
|
|
2409
|
+
return clearnet;
|
|
2410
|
+
};
|
|
2411
|
+
var filterBaseUrlsForTor = (baseUrls, torMode) => {
|
|
2412
|
+
if (!Array.isArray(baseUrls)) return [];
|
|
2413
|
+
const normalized = baseUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2414
|
+
const filtered = normalized.filter(
|
|
2415
|
+
(value) => torMode ? true : !isOnionUrl(value)
|
|
2416
|
+
);
|
|
2417
|
+
return dedupePreserveOrder(
|
|
2418
|
+
filtered.filter((value) => shouldAllowHttp(value, torMode))
|
|
2419
|
+
);
|
|
2420
|
+
};
|
|
2278
2421
|
|
|
2279
|
-
// client/
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
accumulatedImages = [];
|
|
2284
|
-
isInThinking = false;
|
|
2285
|
-
isInContent = false;
|
|
2286
|
-
/**
|
|
2287
|
-
* Process a streaming response
|
|
2288
|
-
*/
|
|
2289
|
-
async process(response, callbacks, modelId) {
|
|
2290
|
-
if (!response.body) {
|
|
2291
|
-
throw new Error("Response body is not available");
|
|
2292
|
-
}
|
|
2293
|
-
const reader = response.body.getReader();
|
|
2294
|
-
const decoder = new TextDecoder("utf-8");
|
|
2295
|
-
let buffer = "";
|
|
2296
|
-
this.accumulatedContent = "";
|
|
2297
|
-
this.accumulatedThinking = "";
|
|
2298
|
-
this.accumulatedImages = [];
|
|
2299
|
-
this.isInThinking = false;
|
|
2300
|
-
this.isInContent = false;
|
|
2301
|
-
let usage;
|
|
2302
|
-
let model;
|
|
2303
|
-
let finish_reason;
|
|
2304
|
-
let citations;
|
|
2305
|
-
let annotations;
|
|
2306
|
-
let responseId;
|
|
2307
|
-
try {
|
|
2308
|
-
while (true) {
|
|
2309
|
-
const { done, value } = await reader.read();
|
|
2310
|
-
if (done) {
|
|
2311
|
-
break;
|
|
2312
|
-
}
|
|
2313
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
2314
|
-
buffer += chunk;
|
|
2315
|
-
const lines = buffer.split("\n");
|
|
2316
|
-
buffer = lines.pop() || "";
|
|
2317
|
-
for (const line of lines) {
|
|
2318
|
-
const parsed = this._parseLine(line);
|
|
2319
|
-
if (!parsed) continue;
|
|
2320
|
-
if (parsed.content) {
|
|
2321
|
-
this._handleContent(parsed.content, callbacks, modelId);
|
|
2322
|
-
}
|
|
2323
|
-
if (parsed.reasoning) {
|
|
2324
|
-
this._handleThinking(parsed.reasoning, callbacks);
|
|
2325
|
-
}
|
|
2326
|
-
if (parsed.usage) {
|
|
2327
|
-
usage = parsed.usage;
|
|
2328
|
-
}
|
|
2329
|
-
if (parsed.model) {
|
|
2330
|
-
model = parsed.model;
|
|
2331
|
-
}
|
|
2332
|
-
if (parsed.finish_reason) {
|
|
2333
|
-
finish_reason = parsed.finish_reason;
|
|
2334
|
-
}
|
|
2335
|
-
if (parsed.responseId) {
|
|
2336
|
-
responseId = parsed.responseId;
|
|
2337
|
-
}
|
|
2338
|
-
if (parsed.citations) {
|
|
2339
|
-
citations = parsed.citations;
|
|
2340
|
-
}
|
|
2341
|
-
if (parsed.annotations) {
|
|
2342
|
-
annotations = parsed.annotations;
|
|
2343
|
-
}
|
|
2344
|
-
if (parsed.images) {
|
|
2345
|
-
this._mergeImages(parsed.images);
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
} finally {
|
|
2350
|
-
reader.releaseLock();
|
|
2351
|
-
}
|
|
2352
|
-
return {
|
|
2353
|
-
content: this.accumulatedContent,
|
|
2354
|
-
thinking: this.accumulatedThinking || void 0,
|
|
2355
|
-
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
2356
|
-
usage,
|
|
2357
|
-
model,
|
|
2358
|
-
responseId,
|
|
2359
|
-
finish_reason,
|
|
2360
|
-
citations,
|
|
2361
|
-
annotations
|
|
2362
|
-
};
|
|
2363
|
-
}
|
|
2364
|
-
/**
|
|
2365
|
-
* Parse a single SSE line
|
|
2366
|
-
*/
|
|
2367
|
-
_parseLine(line) {
|
|
2368
|
-
if (!line.trim()) return null;
|
|
2369
|
-
if (!line.startsWith("data: ")) {
|
|
2422
|
+
// client/ProviderManager.ts
|
|
2423
|
+
function getImageResolutionFromDataUrl(dataUrl) {
|
|
2424
|
+
try {
|
|
2425
|
+
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:"))
|
|
2370
2426
|
return null;
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2427
|
+
const commaIdx = dataUrl.indexOf(",");
|
|
2428
|
+
if (commaIdx === -1) return null;
|
|
2429
|
+
const meta = dataUrl.slice(5, commaIdx);
|
|
2430
|
+
const base64 = dataUrl.slice(commaIdx + 1);
|
|
2431
|
+
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
2432
|
+
const len = binary.length;
|
|
2433
|
+
const bytes = new Uint8Array(len);
|
|
2434
|
+
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
2435
|
+
const isPNG = meta.includes("image/png");
|
|
2436
|
+
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
2437
|
+
if (isPNG) {
|
|
2438
|
+
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
2439
|
+
for (let i = 0; i < sig.length; i++) {
|
|
2440
|
+
if (bytes[i] !== sig[i]) return null;
|
|
2441
|
+
}
|
|
2442
|
+
const view = new DataView(
|
|
2443
|
+
bytes.buffer,
|
|
2444
|
+
bytes.byteOffset,
|
|
2445
|
+
bytes.byteLength
|
|
2446
|
+
);
|
|
2447
|
+
const width = view.getUint32(16, false);
|
|
2448
|
+
const height = view.getUint32(20, false);
|
|
2449
|
+
if (width > 0 && height > 0) return { width, height };
|
|
2374
2450
|
return null;
|
|
2375
2451
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
result.model = parsed.model;
|
|
2400
|
-
}
|
|
2401
|
-
if (parsed.citations) {
|
|
2402
|
-
result.citations = parsed.citations;
|
|
2403
|
-
}
|
|
2404
|
-
if (parsed.annotations) {
|
|
2405
|
-
result.annotations = parsed.annotations;
|
|
2406
|
-
}
|
|
2407
|
-
if (parsed.choices?.[0]?.finish_reason) {
|
|
2408
|
-
result.finish_reason = parsed.choices[0].finish_reason;
|
|
2409
|
-
}
|
|
2410
|
-
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
2411
|
-
if (images && Array.isArray(images)) {
|
|
2412
|
-
result.images = images;
|
|
2452
|
+
if (isJPEG) {
|
|
2453
|
+
let offset = 0;
|
|
2454
|
+
if (bytes[offset++] !== 255 || bytes[offset++] !== 216) return null;
|
|
2455
|
+
while (offset < bytes.length) {
|
|
2456
|
+
while (offset < bytes.length && bytes[offset] !== 255) offset++;
|
|
2457
|
+
if (offset + 1 >= bytes.length) break;
|
|
2458
|
+
while (bytes[offset] === 255) offset++;
|
|
2459
|
+
const marker = bytes[offset++];
|
|
2460
|
+
if (marker === 216 || marker === 217) continue;
|
|
2461
|
+
if (offset + 1 >= bytes.length) break;
|
|
2462
|
+
const length = bytes[offset] << 8 | bytes[offset + 1];
|
|
2463
|
+
offset += 2;
|
|
2464
|
+
if (marker === 192 || marker === 194) {
|
|
2465
|
+
if (length < 7 || offset + length - 2 > bytes.length) return null;
|
|
2466
|
+
const precision = bytes[offset];
|
|
2467
|
+
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
2468
|
+
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
2469
|
+
if (precision > 0 && width > 0 && height > 0)
|
|
2470
|
+
return { width, height };
|
|
2471
|
+
return null;
|
|
2472
|
+
} else {
|
|
2473
|
+
offset += length - 2;
|
|
2474
|
+
}
|
|
2413
2475
|
}
|
|
2414
|
-
return result;
|
|
2415
|
-
} catch {
|
|
2416
2476
|
return null;
|
|
2417
2477
|
}
|
|
2478
|
+
return null;
|
|
2479
|
+
} catch {
|
|
2480
|
+
return null;
|
|
2418
2481
|
}
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2482
|
+
}
|
|
2483
|
+
function calculateImageTokens(width, height, detail = "auto") {
|
|
2484
|
+
if (detail === "low") return 85;
|
|
2485
|
+
let w = width;
|
|
2486
|
+
let h = height;
|
|
2487
|
+
if (w > 2048 || h > 2048) {
|
|
2488
|
+
const aspectRatio = w / h;
|
|
2489
|
+
if (w > h) {
|
|
2490
|
+
w = 2048;
|
|
2491
|
+
h = Math.floor(w / aspectRatio);
|
|
2492
|
+
} else {
|
|
2493
|
+
h = 2048;
|
|
2494
|
+
w = Math.floor(h * aspectRatio);
|
|
2428
2495
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2496
|
+
}
|
|
2497
|
+
if (w > 768 || h > 768) {
|
|
2498
|
+
const aspectRatio = w / h;
|
|
2499
|
+
if (w > h) {
|
|
2500
|
+
w = 768;
|
|
2501
|
+
h = Math.floor(w / aspectRatio);
|
|
2431
2502
|
} else {
|
|
2432
|
-
|
|
2503
|
+
h = 768;
|
|
2504
|
+
w = Math.floor(h * aspectRatio);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
const tilesWidth = Math.floor((w + 511) / 512);
|
|
2508
|
+
const tilesHeight = Math.floor((h + 511) / 512);
|
|
2509
|
+
const numTiles = tilesWidth * tilesHeight;
|
|
2510
|
+
return 85 + 170 * numTiles;
|
|
2511
|
+
}
|
|
2512
|
+
var ProviderManager = class _ProviderManager {
|
|
2513
|
+
constructor(providerRegistry, store, logger) {
|
|
2514
|
+
this.providerRegistry = providerRegistry;
|
|
2515
|
+
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2516
|
+
this.logger = (logger ?? consoleLogger).child(`ProviderManager:${this.instanceId}`);
|
|
2517
|
+
if (store) {
|
|
2518
|
+
this.store = store;
|
|
2519
|
+
this.hydrateFromStore();
|
|
2433
2520
|
}
|
|
2434
|
-
callbacks.onContent(this.accumulatedContent);
|
|
2435
2521
|
}
|
|
2522
|
+
providerRegistry;
|
|
2523
|
+
failedProviders = /* @__PURE__ */ new Set();
|
|
2524
|
+
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
2525
|
+
lastFailed = /* @__PURE__ */ new Map();
|
|
2526
|
+
/** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
|
|
2527
|
+
providersOnCoolDown = [];
|
|
2528
|
+
/** Cooldown duration in milliseconds (42 seconds) */
|
|
2529
|
+
static COOLDOWN_DURATION_MS = 42 * 1e3;
|
|
2530
|
+
/** Optional persistent store for failure tracking */
|
|
2531
|
+
store = null;
|
|
2532
|
+
/** Instance ID for debugging */
|
|
2533
|
+
instanceId;
|
|
2534
|
+
logger;
|
|
2436
2535
|
/**
|
|
2437
|
-
*
|
|
2536
|
+
* Hydrate in-memory state from persistent store
|
|
2438
2537
|
*/
|
|
2439
|
-
|
|
2440
|
-
if (!this.
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2538
|
+
hydrateFromStore() {
|
|
2539
|
+
if (!this.store) return;
|
|
2540
|
+
const state = this.store.getState();
|
|
2541
|
+
this.failedProviders = new Set(state.failedProviders);
|
|
2542
|
+
this.lastFailed = new Map(Object.entries(state.lastFailed));
|
|
2543
|
+
const now = Date.now();
|
|
2544
|
+
this.providersOnCoolDown = state.providersOnCooldown.filter(
|
|
2545
|
+
(entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
|
|
2546
|
+
).map((entry) => [entry.baseUrl, entry.timestamp]);
|
|
2547
|
+
this.logger.log(`Hydrated from store: failedProviders=${this.failedProviders.size} lastFailed=${this.lastFailed.size} providersOnCooldown=${this.providersOnCoolDown.length}`);
|
|
2446
2548
|
}
|
|
2447
2549
|
/**
|
|
2448
|
-
*
|
|
2449
|
-
*/
|
|
2450
|
-
_extractThinkingFromContent(content, callbacks) {
|
|
2451
|
-
const parts = content.split(/(<thinking>|<\/thinking>)/);
|
|
2452
|
-
for (const part of parts) {
|
|
2453
|
-
if (part === "<thinking>") {
|
|
2454
|
-
this.isInThinking = true;
|
|
2455
|
-
if (!this.accumulatedThinking.includes("<thinking>")) {
|
|
2456
|
-
this.accumulatedThinking += "<thinking> ";
|
|
2457
|
-
}
|
|
2458
|
-
} else if (part === "</thinking>") {
|
|
2459
|
-
this.isInThinking = false;
|
|
2460
|
-
this.accumulatedThinking += "</thinking>";
|
|
2461
|
-
} else if (this.isInThinking) {
|
|
2462
|
-
this.accumulatedThinking += part;
|
|
2463
|
-
} else {
|
|
2464
|
-
this.accumulatedContent += part;
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
/**
|
|
2469
|
-
* Merge images into accumulated array, avoiding duplicates
|
|
2470
|
-
*/
|
|
2471
|
-
_mergeImages(newImages) {
|
|
2472
|
-
for (const img of newImages) {
|
|
2473
|
-
const newUrl = img.image_url?.url;
|
|
2474
|
-
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
2475
|
-
const existingUrl = existing.image_url?.url;
|
|
2476
|
-
if (newUrl && existingUrl) {
|
|
2477
|
-
return existingUrl === newUrl;
|
|
2478
|
-
}
|
|
2479
|
-
if (img.index !== void 0 && existing.index !== void 0) {
|
|
2480
|
-
return existing.index === img.index;
|
|
2481
|
-
}
|
|
2482
|
-
return false;
|
|
2483
|
-
});
|
|
2484
|
-
if (existingIndex === -1) {
|
|
2485
|
-
this.accumulatedImages.push(img);
|
|
2486
|
-
} else {
|
|
2487
|
-
this.accumulatedImages[existingIndex] = img;
|
|
2488
|
-
}
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
};
|
|
2492
|
-
|
|
2493
|
-
// utils/torUtils.ts
|
|
2494
|
-
var TOR_ONION_SUFFIX = ".onion";
|
|
2495
|
-
var isTorContext = () => {
|
|
2496
|
-
if (typeof window === "undefined") return false;
|
|
2497
|
-
const hostname = window.location.hostname.toLowerCase();
|
|
2498
|
-
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2499
|
-
};
|
|
2500
|
-
var isOnionUrl = (url) => {
|
|
2501
|
-
if (!url) return false;
|
|
2502
|
-
const trimmed = url.trim().toLowerCase();
|
|
2503
|
-
if (!trimmed) return false;
|
|
2504
|
-
try {
|
|
2505
|
-
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
2506
|
-
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2507
|
-
} catch {
|
|
2508
|
-
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
2509
|
-
}
|
|
2510
|
-
};
|
|
2511
|
-
var shouldAllowHttp = (url, torMode) => {
|
|
2512
|
-
if (!url.startsWith("http://")) return true;
|
|
2513
|
-
if (url.includes("localhost") || url.includes("127.0.0.1")) return true;
|
|
2514
|
-
return torMode && isOnionUrl(url);
|
|
2515
|
-
};
|
|
2516
|
-
var normalizeProviderUrl = (url, torMode = false) => {
|
|
2517
|
-
if (!url || typeof url !== "string") return null;
|
|
2518
|
-
const trimmed = url.trim();
|
|
2519
|
-
if (!trimmed) return null;
|
|
2520
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
2521
|
-
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
2522
|
-
}
|
|
2523
|
-
const useHttpForOnion = torMode && isOnionUrl(trimmed);
|
|
2524
|
-
const withProto = `${useHttpForOnion ? "http" : "https"}://${trimmed}`;
|
|
2525
|
-
return withProto.endsWith("/") ? withProto : `${withProto}/`;
|
|
2526
|
-
};
|
|
2527
|
-
var dedupePreserveOrder = (urls) => {
|
|
2528
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2529
|
-
const out = [];
|
|
2530
|
-
for (const url of urls) {
|
|
2531
|
-
if (!seen.has(url)) {
|
|
2532
|
-
seen.add(url);
|
|
2533
|
-
out.push(url);
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
return out;
|
|
2537
|
-
};
|
|
2538
|
-
var getProviderEndpoints = (provider, torMode) => {
|
|
2539
|
-
const rawUrls = [
|
|
2540
|
-
provider.endpoint_url,
|
|
2541
|
-
...Array.isArray(provider.endpoint_urls) ? provider.endpoint_urls : [],
|
|
2542
|
-
provider.onion_url,
|
|
2543
|
-
...Array.isArray(provider.onion_urls) ? provider.onion_urls : []
|
|
2544
|
-
];
|
|
2545
|
-
const normalized = rawUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2546
|
-
const unique = dedupePreserveOrder(normalized).filter(
|
|
2547
|
-
(value) => shouldAllowHttp(value, torMode)
|
|
2548
|
-
);
|
|
2549
|
-
if (unique.length === 0) return [];
|
|
2550
|
-
const onion = unique.filter((value) => isOnionUrl(value));
|
|
2551
|
-
const clearnet = unique.filter((value) => !isOnionUrl(value));
|
|
2552
|
-
if (torMode) {
|
|
2553
|
-
return onion.length > 0 ? onion : clearnet;
|
|
2554
|
-
}
|
|
2555
|
-
return clearnet;
|
|
2556
|
-
};
|
|
2557
|
-
var filterBaseUrlsForTor = (baseUrls, torMode) => {
|
|
2558
|
-
if (!Array.isArray(baseUrls)) return [];
|
|
2559
|
-
const normalized = baseUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2560
|
-
const filtered = normalized.filter(
|
|
2561
|
-
(value) => torMode ? true : !isOnionUrl(value)
|
|
2562
|
-
);
|
|
2563
|
-
return dedupePreserveOrder(
|
|
2564
|
-
filtered.filter((value) => shouldAllowHttp(value, torMode))
|
|
2565
|
-
);
|
|
2566
|
-
};
|
|
2567
|
-
|
|
2568
|
-
// client/ProviderManager.ts
|
|
2569
|
-
function getImageResolutionFromDataUrl(dataUrl) {
|
|
2570
|
-
try {
|
|
2571
|
-
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:"))
|
|
2572
|
-
return null;
|
|
2573
|
-
const commaIdx = dataUrl.indexOf(",");
|
|
2574
|
-
if (commaIdx === -1) return null;
|
|
2575
|
-
const meta = dataUrl.slice(5, commaIdx);
|
|
2576
|
-
const base64 = dataUrl.slice(commaIdx + 1);
|
|
2577
|
-
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
2578
|
-
const len = binary.length;
|
|
2579
|
-
const bytes = new Uint8Array(len);
|
|
2580
|
-
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
2581
|
-
const isPNG = meta.includes("image/png");
|
|
2582
|
-
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
2583
|
-
if (isPNG) {
|
|
2584
|
-
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
2585
|
-
for (let i = 0; i < sig.length; i++) {
|
|
2586
|
-
if (bytes[i] !== sig[i]) return null;
|
|
2587
|
-
}
|
|
2588
|
-
const view = new DataView(
|
|
2589
|
-
bytes.buffer,
|
|
2590
|
-
bytes.byteOffset,
|
|
2591
|
-
bytes.byteLength
|
|
2592
|
-
);
|
|
2593
|
-
const width = view.getUint32(16, false);
|
|
2594
|
-
const height = view.getUint32(20, false);
|
|
2595
|
-
if (width > 0 && height > 0) return { width, height };
|
|
2596
|
-
return null;
|
|
2597
|
-
}
|
|
2598
|
-
if (isJPEG) {
|
|
2599
|
-
let offset = 0;
|
|
2600
|
-
if (bytes[offset++] !== 255 || bytes[offset++] !== 216) return null;
|
|
2601
|
-
while (offset < bytes.length) {
|
|
2602
|
-
while (offset < bytes.length && bytes[offset] !== 255) offset++;
|
|
2603
|
-
if (offset + 1 >= bytes.length) break;
|
|
2604
|
-
while (bytes[offset] === 255) offset++;
|
|
2605
|
-
const marker = bytes[offset++];
|
|
2606
|
-
if (marker === 216 || marker === 217) continue;
|
|
2607
|
-
if (offset + 1 >= bytes.length) break;
|
|
2608
|
-
const length = bytes[offset] << 8 | bytes[offset + 1];
|
|
2609
|
-
offset += 2;
|
|
2610
|
-
if (marker === 192 || marker === 194) {
|
|
2611
|
-
if (length < 7 || offset + length - 2 > bytes.length) return null;
|
|
2612
|
-
const precision = bytes[offset];
|
|
2613
|
-
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
2614
|
-
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
2615
|
-
if (precision > 0 && width > 0 && height > 0)
|
|
2616
|
-
return { width, height };
|
|
2617
|
-
return null;
|
|
2618
|
-
} else {
|
|
2619
|
-
offset += length - 2;
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
return null;
|
|
2623
|
-
}
|
|
2624
|
-
return null;
|
|
2625
|
-
} catch {
|
|
2626
|
-
return null;
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
function calculateImageTokens(width, height, detail = "auto") {
|
|
2630
|
-
if (detail === "low") return 85;
|
|
2631
|
-
let w = width;
|
|
2632
|
-
let h = height;
|
|
2633
|
-
if (w > 2048 || h > 2048) {
|
|
2634
|
-
const aspectRatio = w / h;
|
|
2635
|
-
if (w > h) {
|
|
2636
|
-
w = 2048;
|
|
2637
|
-
h = Math.floor(w / aspectRatio);
|
|
2638
|
-
} else {
|
|
2639
|
-
h = 2048;
|
|
2640
|
-
w = Math.floor(h * aspectRatio);
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2643
|
-
if (w > 768 || h > 768) {
|
|
2644
|
-
const aspectRatio = w / h;
|
|
2645
|
-
if (w > h) {
|
|
2646
|
-
w = 768;
|
|
2647
|
-
h = Math.floor(w / aspectRatio);
|
|
2648
|
-
} else {
|
|
2649
|
-
h = 768;
|
|
2650
|
-
w = Math.floor(h * aspectRatio);
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
const tilesWidth = Math.floor((w + 511) / 512);
|
|
2654
|
-
const tilesHeight = Math.floor((h + 511) / 512);
|
|
2655
|
-
const numTiles = tilesWidth * tilesHeight;
|
|
2656
|
-
return 85 + 170 * numTiles;
|
|
2657
|
-
}
|
|
2658
|
-
function isInsecureHttpUrl(url) {
|
|
2659
|
-
return url.startsWith("http://");
|
|
2660
|
-
}
|
|
2661
|
-
var ProviderManager = class _ProviderManager {
|
|
2662
|
-
constructor(providerRegistry, store, logger) {
|
|
2663
|
-
this.providerRegistry = providerRegistry;
|
|
2664
|
-
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2665
|
-
this.logger = (logger ?? consoleLogger).child(`ProviderManager:${this.instanceId}`);
|
|
2666
|
-
if (store) {
|
|
2667
|
-
this.store = store;
|
|
2668
|
-
this.hydrateFromStore();
|
|
2669
|
-
}
|
|
2670
|
-
}
|
|
2671
|
-
providerRegistry;
|
|
2672
|
-
failedProviders = /* @__PURE__ */ new Set();
|
|
2673
|
-
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
2674
|
-
lastFailed = /* @__PURE__ */ new Map();
|
|
2675
|
-
/** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
|
|
2676
|
-
providersOnCoolDown = [];
|
|
2677
|
-
/** Cooldown duration in milliseconds (42 seconds) */
|
|
2678
|
-
static COOLDOWN_DURATION_MS = 42 * 1e3;
|
|
2679
|
-
/** Optional persistent store for failure tracking */
|
|
2680
|
-
store = null;
|
|
2681
|
-
/** Instance ID for debugging */
|
|
2682
|
-
instanceId;
|
|
2683
|
-
logger;
|
|
2684
|
-
/**
|
|
2685
|
-
* Hydrate in-memory state from persistent store
|
|
2686
|
-
*/
|
|
2687
|
-
hydrateFromStore() {
|
|
2688
|
-
if (!this.store) return;
|
|
2689
|
-
const state = this.store.getState();
|
|
2690
|
-
this.failedProviders = new Set(state.failedProviders);
|
|
2691
|
-
this.lastFailed = new Map(Object.entries(state.lastFailed));
|
|
2692
|
-
const now = Date.now();
|
|
2693
|
-
this.providersOnCoolDown = state.providersOnCooldown.filter(
|
|
2694
|
-
(entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
|
|
2695
|
-
).map((entry) => [entry.baseUrl, entry.timestamp]);
|
|
2696
|
-
this.logger.log(`Hydrated from store: failedProviders=${this.failedProviders.size} lastFailed=${this.lastFailed.size} providersOnCooldown=${this.providersOnCoolDown.length}`);
|
|
2697
|
-
}
|
|
2698
|
-
/**
|
|
2699
|
-
* Get instance ID for debugging
|
|
2550
|
+
* Get instance ID for debugging
|
|
2700
2551
|
*/
|
|
2701
2552
|
getInstanceId() {
|
|
2702
2553
|
return this.instanceId;
|
|
@@ -2874,7 +2725,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
2874
2725
|
if (this.isOnCooldown(baseUrl)) {
|
|
2875
2726
|
continue;
|
|
2876
2727
|
}
|
|
2877
|
-
if (!torMode &&
|
|
2728
|
+
if (!torMode && isOnionUrl(baseUrl)) {
|
|
2878
2729
|
continue;
|
|
2879
2730
|
}
|
|
2880
2731
|
const model = models.find((m) => m.id === modelId);
|
|
@@ -2925,7 +2776,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
2925
2776
|
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
2926
2777
|
if (disabledProviders.has(baseUrl)) continue;
|
|
2927
2778
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
2928
|
-
if (!torMode &&
|
|
2779
|
+
if (!torMode && isOnionUrl(baseUrl))
|
|
2929
2780
|
continue;
|
|
2930
2781
|
const model = models.find((m) => m.id === modelId);
|
|
2931
2782
|
if (!model) continue;
|
|
@@ -2940,16 +2791,18 @@ var ProviderManager = class _ProviderManager {
|
|
|
2940
2791
|
getProviderPriceRankingForModel(modelId, options = {}) {
|
|
2941
2792
|
const includeDisabled = options.includeDisabled ?? false;
|
|
2942
2793
|
const torMode = options.torMode ?? false;
|
|
2943
|
-
const
|
|
2944
|
-
|
|
2945
|
-
)
|
|
2794
|
+
const disabledProviderList = this.providerRegistry.getDisabledProviders();
|
|
2795
|
+
const disabledProviders = new Set(disabledProviderList);
|
|
2796
|
+
if (disabledProviderList.length > 0) {
|
|
2797
|
+
this.logger.log(`getProviderPriceRankingForModel: disabled providers (${disabledProviderList.length}): ${disabledProviderList.join(", ")}`);
|
|
2798
|
+
}
|
|
2946
2799
|
const allModels = this.providerRegistry.getAllProvidersModels();
|
|
2947
2800
|
const results = [];
|
|
2948
2801
|
for (const [baseUrl, models] of Object.entries(allModels)) {
|
|
2949
2802
|
if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
|
|
2950
2803
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
2951
2804
|
if (torMode && !baseUrl.includes(".onion")) continue;
|
|
2952
|
-
if (!torMode &&
|
|
2805
|
+
if (!torMode && baseUrl.includes(".onion"))
|
|
2953
2806
|
continue;
|
|
2954
2807
|
const match = models.find((model) => model.id === modelId);
|
|
2955
2808
|
if (!match?.sats_pricing) continue;
|
|
@@ -2969,12 +2822,20 @@ var ProviderManager = class _ProviderManager {
|
|
|
2969
2822
|
totalPerMillion
|
|
2970
2823
|
});
|
|
2971
2824
|
}
|
|
2972
|
-
|
|
2825
|
+
results.sort((a, b) => {
|
|
2973
2826
|
if (a.totalPerMillion !== b.totalPerMillion) {
|
|
2974
2827
|
return a.totalPerMillion - b.totalPerMillion;
|
|
2975
2828
|
}
|
|
2976
2829
|
return a.baseUrl.localeCompare(b.baseUrl);
|
|
2977
2830
|
});
|
|
2831
|
+
if (results.length > 0) {
|
|
2832
|
+
const ranking = results.map((r, i) => ` ${i + 1}. ${r.baseUrl} total=${r.totalPerMillion.toFixed(2)} sats/M (prompt=${r.promptPerMillion.toFixed(2)} completion=${r.completionPerMillion.toFixed(2)})`).join("\n");
|
|
2833
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} ranking (${results.length} providers):
|
|
2834
|
+
${ranking}`);
|
|
2835
|
+
} else {
|
|
2836
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} no providers found`);
|
|
2837
|
+
}
|
|
2838
|
+
return results;
|
|
2978
2839
|
}
|
|
2979
2840
|
/**
|
|
2980
2841
|
* Get best-priced provider for a specific model
|
|
@@ -3167,141 +3028,6 @@ var createMemoryDriver = (seed) => {
|
|
|
3167
3028
|
};
|
|
3168
3029
|
};
|
|
3169
3030
|
|
|
3170
|
-
// storage/drivers/sqlite.ts
|
|
3171
|
-
var isBun = () => {
|
|
3172
|
-
return typeof process.versions.bun !== "undefined";
|
|
3173
|
-
};
|
|
3174
|
-
var cachedDbModule = null;
|
|
3175
|
-
var loadDatabase = async (dbPath) => {
|
|
3176
|
-
if (isBun()) {
|
|
3177
|
-
throw new Error(
|
|
3178
|
-
"SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
|
|
3179
|
-
);
|
|
3180
|
-
}
|
|
3181
|
-
try {
|
|
3182
|
-
if (!cachedDbModule) {
|
|
3183
|
-
cachedDbModule = (await import('better-sqlite3')).default;
|
|
3184
|
-
}
|
|
3185
|
-
return new cachedDbModule(dbPath);
|
|
3186
|
-
} catch (error) {
|
|
3187
|
-
throw new Error(
|
|
3188
|
-
`better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
|
|
3189
|
-
);
|
|
3190
|
-
}
|
|
3191
|
-
};
|
|
3192
|
-
var createSqliteDriver = (options = {}) => {
|
|
3193
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
3194
|
-
const tableName = options.tableName || "sdk_storage";
|
|
3195
|
-
let db;
|
|
3196
|
-
let selectStmt;
|
|
3197
|
-
let upsertStmt;
|
|
3198
|
-
let deleteStmt;
|
|
3199
|
-
const initDb = async () => {
|
|
3200
|
-
if (!db) {
|
|
3201
|
-
db = await loadDatabase(dbPath);
|
|
3202
|
-
db.exec(
|
|
3203
|
-
`CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
3204
|
-
);
|
|
3205
|
-
selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
|
|
3206
|
-
upsertStmt = db.prepare(
|
|
3207
|
-
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)
|
|
3208
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
3209
|
-
);
|
|
3210
|
-
deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
|
|
3211
|
-
}
|
|
3212
|
-
};
|
|
3213
|
-
const ensureInit = async () => {
|
|
3214
|
-
if (!db) {
|
|
3215
|
-
await initDb();
|
|
3216
|
-
}
|
|
3217
|
-
};
|
|
3218
|
-
return {
|
|
3219
|
-
async getItem(key, defaultValue) {
|
|
3220
|
-
try {
|
|
3221
|
-
await ensureInit();
|
|
3222
|
-
const row = selectStmt.get(key);
|
|
3223
|
-
if (!row || typeof row.value !== "string") return defaultValue;
|
|
3224
|
-
try {
|
|
3225
|
-
return JSON.parse(row.value);
|
|
3226
|
-
} catch (parseError) {
|
|
3227
|
-
if (typeof defaultValue === "string") {
|
|
3228
|
-
return row.value;
|
|
3229
|
-
}
|
|
3230
|
-
throw parseError;
|
|
3231
|
-
}
|
|
3232
|
-
} catch (error) {
|
|
3233
|
-
console.error(`SQLite getItem failed for key "${key}":`, error);
|
|
3234
|
-
return defaultValue;
|
|
3235
|
-
}
|
|
3236
|
-
},
|
|
3237
|
-
async setItem(key, value) {
|
|
3238
|
-
try {
|
|
3239
|
-
await ensureInit();
|
|
3240
|
-
upsertStmt.run(key, JSON.stringify(value));
|
|
3241
|
-
} catch (error) {
|
|
3242
|
-
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
3243
|
-
}
|
|
3244
|
-
},
|
|
3245
|
-
async removeItem(key) {
|
|
3246
|
-
try {
|
|
3247
|
-
await ensureInit();
|
|
3248
|
-
deleteStmt.run(key);
|
|
3249
|
-
} catch (error) {
|
|
3250
|
-
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
};
|
|
3254
|
-
};
|
|
3255
|
-
async function createBunSqliteDriver(dbPath, options) {
|
|
3256
|
-
const logger = (options?.logger ?? consoleLogger).child("BunSqliteDriver");
|
|
3257
|
-
const SQLite = (await import(
|
|
3258
|
-
/* webpackIgnore: true */
|
|
3259
|
-
'bun:sqlite'
|
|
3260
|
-
)).default;
|
|
3261
|
-
const db = new SQLite(dbPath);
|
|
3262
|
-
db.run(`
|
|
3263
|
-
CREATE TABLE IF NOT EXISTS sdk_storage (
|
|
3264
|
-
key TEXT PRIMARY KEY,
|
|
3265
|
-
value TEXT NOT NULL
|
|
3266
|
-
)
|
|
3267
|
-
`);
|
|
3268
|
-
return {
|
|
3269
|
-
async getItem(key, defaultValue) {
|
|
3270
|
-
try {
|
|
3271
|
-
const row = db.query("SELECT value FROM sdk_storage WHERE key = ?").get(key);
|
|
3272
|
-
if (!row || typeof row.value !== "string") return defaultValue;
|
|
3273
|
-
try {
|
|
3274
|
-
return JSON.parse(row.value);
|
|
3275
|
-
} catch (parseError) {
|
|
3276
|
-
if (typeof defaultValue === "string") {
|
|
3277
|
-
return row.value;
|
|
3278
|
-
}
|
|
3279
|
-
throw parseError;
|
|
3280
|
-
}
|
|
3281
|
-
} catch (error) {
|
|
3282
|
-
logger.error(`getItem failed for key "${key}":`, error);
|
|
3283
|
-
return defaultValue;
|
|
3284
|
-
}
|
|
3285
|
-
},
|
|
3286
|
-
async setItem(key, value) {
|
|
3287
|
-
try {
|
|
3288
|
-
db.query(
|
|
3289
|
-
"INSERT INTO sdk_storage (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
3290
|
-
).run(key, JSON.stringify(value));
|
|
3291
|
-
} catch (error) {
|
|
3292
|
-
logger.error(`setItem failed for key "${key}":`, error);
|
|
3293
|
-
}
|
|
3294
|
-
},
|
|
3295
|
-
async removeItem(key) {
|
|
3296
|
-
try {
|
|
3297
|
-
db.query("DELETE FROM sdk_storage WHERE key = ?").run(key);
|
|
3298
|
-
} catch (error) {
|
|
3299
|
-
logger.error(`removeItem failed for key "${key}":`, error);
|
|
3300
|
-
}
|
|
3301
|
-
}
|
|
3302
|
-
};
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
3031
|
// storage/drivers/indexedDB.ts
|
|
3306
3032
|
var isBrowser = typeof indexedDB !== "undefined";
|
|
3307
3033
|
var openDatabase = (dbName, storeName) => {
|
|
@@ -3309,15 +3035,32 @@ var openDatabase = (dbName, storeName) => {
|
|
|
3309
3035
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
3310
3036
|
}
|
|
3311
3037
|
return new Promise((resolve, reject) => {
|
|
3312
|
-
const request = indexedDB.open(dbName,
|
|
3038
|
+
const request = indexedDB.open(dbName, 2);
|
|
3313
3039
|
request.onupgradeneeded = () => {
|
|
3314
3040
|
const db = request.result;
|
|
3315
3041
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
3316
3042
|
db.createObjectStore(storeName);
|
|
3317
3043
|
}
|
|
3044
|
+
if (storeName !== "usage_tracking" && !db.objectStoreNames.contains("usage_tracking")) {
|
|
3045
|
+
const utStore = db.createObjectStore("usage_tracking", { keyPath: "id" });
|
|
3046
|
+
utStore.createIndex("timestamp", "timestamp", { unique: false });
|
|
3047
|
+
utStore.createIndex("modelId", "modelId", { unique: false });
|
|
3048
|
+
utStore.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
3049
|
+
utStore.createIndex("sessionId", "sessionId", { unique: false });
|
|
3050
|
+
utStore.createIndex("client", "client", { unique: false });
|
|
3051
|
+
}
|
|
3052
|
+
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
3053
|
+
db.createObjectStore("sdk_storage");
|
|
3054
|
+
}
|
|
3318
3055
|
};
|
|
3319
3056
|
request.onsuccess = () => resolve(request.result);
|
|
3320
3057
|
request.onerror = () => reject(request.error);
|
|
3058
|
+
request.onblocked = () => {
|
|
3059
|
+
console.warn(
|
|
3060
|
+
`[IndexedDB driver] open blocked for "${dbName}" (store: "${storeName}") \u2014 close other tabs using this DB`
|
|
3061
|
+
);
|
|
3062
|
+
reject(new Error(`IndexedDB "${dbName}" blocked by another connection`));
|
|
3063
|
+
};
|
|
3321
3064
|
});
|
|
3322
3065
|
};
|
|
3323
3066
|
var createIndexedDBDriver = (options = {}) => {
|
|
@@ -3430,9 +3173,10 @@ var openDatabase2 = (dbName, storeName) => {
|
|
|
3430
3173
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
3431
3174
|
}
|
|
3432
3175
|
return new Promise((resolve, reject) => {
|
|
3433
|
-
const request = indexedDB.open(dbName,
|
|
3176
|
+
const request = indexedDB.open(dbName, 3);
|
|
3434
3177
|
request.onupgradeneeded = () => {
|
|
3435
3178
|
const db = request.result;
|
|
3179
|
+
const tx = request.transaction;
|
|
3436
3180
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
3437
3181
|
const store = db.createObjectStore(storeName, { keyPath: "id" });
|
|
3438
3182
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
@@ -3440,10 +3184,25 @@ var openDatabase2 = (dbName, storeName) => {
|
|
|
3440
3184
|
store.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
3441
3185
|
store.createIndex("sessionId", "sessionId", { unique: false });
|
|
3442
3186
|
store.createIndex("client", "client", { unique: false });
|
|
3187
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
3188
|
+
} else if (tx) {
|
|
3189
|
+
const store = tx.objectStore(storeName);
|
|
3190
|
+
if (!store.indexNames.contains("provider")) {
|
|
3191
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
3195
|
+
db.createObjectStore("sdk_storage");
|
|
3443
3196
|
}
|
|
3444
3197
|
};
|
|
3445
3198
|
request.onsuccess = () => resolve(request.result);
|
|
3446
3199
|
request.onerror = () => reject(request.error);
|
|
3200
|
+
request.onblocked = () => {
|
|
3201
|
+
console.warn(
|
|
3202
|
+
`[usageTracking IndexedDB] open blocked for "${dbName}" \u2014 close other tabs using this DB`
|
|
3203
|
+
);
|
|
3204
|
+
reject(new Error(`IndexedDB "${dbName}" blocked by another connection`));
|
|
3205
|
+
};
|
|
3447
3206
|
});
|
|
3448
3207
|
};
|
|
3449
3208
|
var matchesFilters = (entry, options = {}) => {
|
|
@@ -3465,6 +3224,9 @@ var matchesFilters = (entry, options = {}) => {
|
|
|
3465
3224
|
if (options.client && entry.client !== options.client) {
|
|
3466
3225
|
return false;
|
|
3467
3226
|
}
|
|
3227
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
3228
|
+
return false;
|
|
3229
|
+
}
|
|
3468
3230
|
return true;
|
|
3469
3231
|
};
|
|
3470
3232
|
var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
@@ -3596,393 +3358,8 @@ var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
|
3596
3358
|
};
|
|
3597
3359
|
};
|
|
3598
3360
|
|
|
3599
|
-
// storage/usageTracking/sqlite.ts
|
|
3600
|
-
var MIGRATION_MARKER_KEY2 = "usage_tracking_migration_v1";
|
|
3601
|
-
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3602
|
-
var isBun2 = () => {
|
|
3603
|
-
return typeof process.versions.bun !== "undefined";
|
|
3604
|
-
};
|
|
3605
|
-
var cachedDbModule2 = null;
|
|
3606
|
-
var loadDatabase2 = async (dbPath) => {
|
|
3607
|
-
if (isBun2()) {
|
|
3608
|
-
throw new Error(
|
|
3609
|
-
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
3610
|
-
);
|
|
3611
|
-
}
|
|
3612
|
-
try {
|
|
3613
|
-
if (!cachedDbModule2) {
|
|
3614
|
-
cachedDbModule2 = (await import('better-sqlite3')).default;
|
|
3615
|
-
}
|
|
3616
|
-
return new cachedDbModule2(dbPath);
|
|
3617
|
-
} catch (error) {
|
|
3618
|
-
throw new Error(
|
|
3619
|
-
`better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
|
|
3620
|
-
);
|
|
3621
|
-
}
|
|
3622
|
-
};
|
|
3623
|
-
var buildWhereClause = (options = {}) => {
|
|
3624
|
-
const clauses = [];
|
|
3625
|
-
const params = [];
|
|
3626
|
-
if (typeof options.before === "number") {
|
|
3627
|
-
clauses.push("timestamp < ?");
|
|
3628
|
-
params.push(options.before);
|
|
3629
|
-
}
|
|
3630
|
-
if (typeof options.after === "number") {
|
|
3631
|
-
clauses.push("timestamp > ?");
|
|
3632
|
-
params.push(options.after);
|
|
3633
|
-
}
|
|
3634
|
-
if (options.modelId) {
|
|
3635
|
-
clauses.push("model_id = ?");
|
|
3636
|
-
params.push(options.modelId);
|
|
3637
|
-
}
|
|
3638
|
-
if (options.baseUrl) {
|
|
3639
|
-
clauses.push("base_url = ?");
|
|
3640
|
-
params.push(normalizeBaseUrl2(options.baseUrl));
|
|
3641
|
-
}
|
|
3642
|
-
if (options.sessionId) {
|
|
3643
|
-
clauses.push("session_id = ?");
|
|
3644
|
-
params.push(options.sessionId);
|
|
3645
|
-
}
|
|
3646
|
-
if (options.client) {
|
|
3647
|
-
clauses.push("client = ?");
|
|
3648
|
-
params.push(options.client);
|
|
3649
|
-
}
|
|
3650
|
-
return {
|
|
3651
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
3652
|
-
params
|
|
3653
|
-
};
|
|
3654
|
-
};
|
|
3655
|
-
var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
3656
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
3657
|
-
const tableName = options.tableName || "usage_tracking";
|
|
3658
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
3659
|
-
let db;
|
|
3660
|
-
let insertStmt;
|
|
3661
|
-
let migrationComplete = false;
|
|
3662
|
-
const initDb = async () => {
|
|
3663
|
-
if (!db) {
|
|
3664
|
-
db = await loadDatabase2(dbPath);
|
|
3665
|
-
db.exec(`
|
|
3666
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
3667
|
-
id TEXT PRIMARY KEY,
|
|
3668
|
-
timestamp INTEGER NOT NULL,
|
|
3669
|
-
model_id TEXT NOT NULL,
|
|
3670
|
-
base_url TEXT NOT NULL,
|
|
3671
|
-
request_id TEXT NOT NULL,
|
|
3672
|
-
cost REAL NOT NULL,
|
|
3673
|
-
sats_cost REAL NOT NULL,
|
|
3674
|
-
prompt_tokens INTEGER NOT NULL,
|
|
3675
|
-
completion_tokens INTEGER NOT NULL,
|
|
3676
|
-
total_tokens INTEGER NOT NULL,
|
|
3677
|
-
client TEXT,
|
|
3678
|
-
session_id TEXT,
|
|
3679
|
-
tags TEXT
|
|
3680
|
-
);
|
|
3681
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
3682
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
3683
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
3684
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
3685
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
3686
|
-
`);
|
|
3687
|
-
insertStmt = db.prepare(`
|
|
3688
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
3689
|
-
id, timestamp, model_id, base_url, request_id,
|
|
3690
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
3691
|
-
client, session_id, tags
|
|
3692
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3693
|
-
`);
|
|
3694
|
-
}
|
|
3695
|
-
};
|
|
3696
|
-
const ensureInit = async () => {
|
|
3697
|
-
if (!db) {
|
|
3698
|
-
await initDb();
|
|
3699
|
-
}
|
|
3700
|
-
};
|
|
3701
|
-
const appendOne = (entry) => {
|
|
3702
|
-
insertStmt.run(
|
|
3703
|
-
entry.id,
|
|
3704
|
-
entry.timestamp,
|
|
3705
|
-
entry.modelId,
|
|
3706
|
-
normalizeBaseUrl2(entry.baseUrl),
|
|
3707
|
-
entry.requestId,
|
|
3708
|
-
entry.cost,
|
|
3709
|
-
entry.satsCost,
|
|
3710
|
-
entry.promptTokens,
|
|
3711
|
-
entry.completionTokens,
|
|
3712
|
-
entry.totalTokens,
|
|
3713
|
-
entry.client ?? null,
|
|
3714
|
-
entry.sessionId ?? null,
|
|
3715
|
-
JSON.stringify(entry.tags ?? [])
|
|
3716
|
-
);
|
|
3717
|
-
};
|
|
3718
|
-
const ensureMigrated = async () => {
|
|
3719
|
-
if (!legacyStorageDriver || migrationComplete) return;
|
|
3720
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
3721
|
-
MIGRATION_MARKER_KEY2,
|
|
3722
|
-
false
|
|
3723
|
-
);
|
|
3724
|
-
if (migrated) {
|
|
3725
|
-
migrationComplete = true;
|
|
3726
|
-
return;
|
|
3727
|
-
}
|
|
3728
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
3729
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
3730
|
-
[]
|
|
3731
|
-
);
|
|
3732
|
-
for (const entry of legacyEntries) {
|
|
3733
|
-
appendOne(entry);
|
|
3734
|
-
}
|
|
3735
|
-
if (legacyEntries.length > 0) {
|
|
3736
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
3737
|
-
}
|
|
3738
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
3739
|
-
migrationComplete = true;
|
|
3740
|
-
};
|
|
3741
|
-
const mapRow = (row) => ({
|
|
3742
|
-
id: row.id,
|
|
3743
|
-
timestamp: row.timestamp,
|
|
3744
|
-
modelId: row.model_id,
|
|
3745
|
-
baseUrl: row.base_url,
|
|
3746
|
-
requestId: row.request_id,
|
|
3747
|
-
cost: row.cost,
|
|
3748
|
-
satsCost: row.sats_cost,
|
|
3749
|
-
promptTokens: row.prompt_tokens,
|
|
3750
|
-
completionTokens: row.completion_tokens,
|
|
3751
|
-
totalTokens: row.total_tokens,
|
|
3752
|
-
client: row.client ?? void 0,
|
|
3753
|
-
sessionId: row.session_id ?? void 0,
|
|
3754
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
3755
|
-
});
|
|
3756
|
-
return {
|
|
3757
|
-
async migrate() {
|
|
3758
|
-
await ensureInit();
|
|
3759
|
-
await ensureMigrated();
|
|
3760
|
-
},
|
|
3761
|
-
async append(entry) {
|
|
3762
|
-
await ensureInit();
|
|
3763
|
-
await ensureMigrated();
|
|
3764
|
-
appendOne(entry);
|
|
3765
|
-
},
|
|
3766
|
-
async appendMany(entries) {
|
|
3767
|
-
await ensureInit();
|
|
3768
|
-
await ensureMigrated();
|
|
3769
|
-
for (const entry of entries) {
|
|
3770
|
-
appendOne(entry);
|
|
3771
|
-
}
|
|
3772
|
-
},
|
|
3773
|
-
async list(options2 = {}) {
|
|
3774
|
-
await ensureInit();
|
|
3775
|
-
await ensureMigrated();
|
|
3776
|
-
const { sql, params } = buildWhereClause(options2);
|
|
3777
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
3778
|
-
const stmt = db.prepare(
|
|
3779
|
-
`SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`
|
|
3780
|
-
);
|
|
3781
|
-
const rows = stmt.all(
|
|
3782
|
-
...typeof options2.limit === "number" ? [...params, options2.limit] : params
|
|
3783
|
-
);
|
|
3784
|
-
return rows.map(mapRow);
|
|
3785
|
-
},
|
|
3786
|
-
async count(options2 = {}) {
|
|
3787
|
-
await ensureInit();
|
|
3788
|
-
await ensureMigrated();
|
|
3789
|
-
const { sql, params } = buildWhereClause(options2);
|
|
3790
|
-
const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
|
|
3791
|
-
const row = stmt.get(...params);
|
|
3792
|
-
return Number(row?.count ?? 0);
|
|
3793
|
-
},
|
|
3794
|
-
async deleteOlderThan(timestamp) {
|
|
3795
|
-
await ensureInit();
|
|
3796
|
-
await ensureMigrated();
|
|
3797
|
-
const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
|
|
3798
|
-
const result = stmt.run(timestamp);
|
|
3799
|
-
return result.changes;
|
|
3800
|
-
},
|
|
3801
|
-
async clear() {
|
|
3802
|
-
await ensureInit();
|
|
3803
|
-
await ensureMigrated();
|
|
3804
|
-
db.prepare(`DELETE FROM ${tableName}`).run();
|
|
3805
|
-
}
|
|
3806
|
-
};
|
|
3807
|
-
};
|
|
3808
|
-
|
|
3809
|
-
// storage/usageTracking/bunSqlite.ts
|
|
3810
|
-
var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
|
|
3811
|
-
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3812
|
-
var buildWhereClause2 = (options = {}) => {
|
|
3813
|
-
const clauses = [];
|
|
3814
|
-
const params = [];
|
|
3815
|
-
if (typeof options.before === "number") {
|
|
3816
|
-
clauses.push("timestamp < ?");
|
|
3817
|
-
params.push(options.before);
|
|
3818
|
-
}
|
|
3819
|
-
if (typeof options.after === "number") {
|
|
3820
|
-
clauses.push("timestamp > ?");
|
|
3821
|
-
params.push(options.after);
|
|
3822
|
-
}
|
|
3823
|
-
if (options.modelId) {
|
|
3824
|
-
clauses.push("model_id = ?");
|
|
3825
|
-
params.push(options.modelId);
|
|
3826
|
-
}
|
|
3827
|
-
if (options.baseUrl) {
|
|
3828
|
-
clauses.push("base_url = ?");
|
|
3829
|
-
params.push(normalizeBaseUrl3(options.baseUrl));
|
|
3830
|
-
}
|
|
3831
|
-
if (options.sessionId) {
|
|
3832
|
-
clauses.push("session_id = ?");
|
|
3833
|
-
params.push(options.sessionId);
|
|
3834
|
-
}
|
|
3835
|
-
if (options.client) {
|
|
3836
|
-
clauses.push("client = ?");
|
|
3837
|
-
params.push(options.client);
|
|
3838
|
-
}
|
|
3839
|
-
return {
|
|
3840
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
3841
|
-
params
|
|
3842
|
-
};
|
|
3843
|
-
};
|
|
3844
|
-
var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
3845
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
3846
|
-
const tableName = options.tableName || "usage_tracking";
|
|
3847
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
3848
|
-
const SQLiteDatabase = options.sqlite?.Database;
|
|
3849
|
-
let migrationPromise = null;
|
|
3850
|
-
if (!SQLiteDatabase) {
|
|
3851
|
-
throw new Error(
|
|
3852
|
-
"Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
|
|
3853
|
-
);
|
|
3854
|
-
}
|
|
3855
|
-
const db = new SQLiteDatabase(dbPath);
|
|
3856
|
-
db.run(`
|
|
3857
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
3858
|
-
id TEXT PRIMARY KEY,
|
|
3859
|
-
timestamp INTEGER NOT NULL,
|
|
3860
|
-
model_id TEXT NOT NULL,
|
|
3861
|
-
base_url TEXT NOT NULL,
|
|
3862
|
-
request_id TEXT NOT NULL,
|
|
3863
|
-
cost REAL NOT NULL,
|
|
3864
|
-
sats_cost REAL NOT NULL,
|
|
3865
|
-
prompt_tokens INTEGER NOT NULL,
|
|
3866
|
-
completion_tokens INTEGER NOT NULL,
|
|
3867
|
-
total_tokens INTEGER NOT NULL,
|
|
3868
|
-
client TEXT,
|
|
3869
|
-
session_id TEXT,
|
|
3870
|
-
tags TEXT
|
|
3871
|
-
)
|
|
3872
|
-
`);
|
|
3873
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
|
|
3874
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
|
|
3875
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
|
|
3876
|
-
const appendOne = (entry) => {
|
|
3877
|
-
db.query(`
|
|
3878
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
3879
|
-
id, timestamp, model_id, base_url, request_id,
|
|
3880
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
3881
|
-
client, session_id, tags
|
|
3882
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3883
|
-
`).run(
|
|
3884
|
-
entry.id,
|
|
3885
|
-
entry.timestamp,
|
|
3886
|
-
entry.modelId,
|
|
3887
|
-
normalizeBaseUrl3(entry.baseUrl),
|
|
3888
|
-
entry.requestId,
|
|
3889
|
-
entry.cost,
|
|
3890
|
-
entry.satsCost,
|
|
3891
|
-
entry.promptTokens,
|
|
3892
|
-
entry.completionTokens,
|
|
3893
|
-
entry.totalTokens,
|
|
3894
|
-
entry.client ?? null,
|
|
3895
|
-
entry.sessionId ?? null,
|
|
3896
|
-
JSON.stringify(entry.tags ?? [])
|
|
3897
|
-
);
|
|
3898
|
-
};
|
|
3899
|
-
const mapRow = (row) => ({
|
|
3900
|
-
id: row.id,
|
|
3901
|
-
timestamp: row.timestamp,
|
|
3902
|
-
modelId: row.model_id,
|
|
3903
|
-
baseUrl: row.base_url,
|
|
3904
|
-
requestId: row.request_id,
|
|
3905
|
-
cost: row.cost,
|
|
3906
|
-
satsCost: row.sats_cost,
|
|
3907
|
-
promptTokens: row.prompt_tokens,
|
|
3908
|
-
completionTokens: row.completion_tokens,
|
|
3909
|
-
totalTokens: row.total_tokens,
|
|
3910
|
-
client: row.client ?? void 0,
|
|
3911
|
-
sessionId: row.session_id ?? void 0,
|
|
3912
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
3913
|
-
});
|
|
3914
|
-
const ensureMigrated = async () => {
|
|
3915
|
-
if (!legacyStorageDriver) return;
|
|
3916
|
-
if (!migrationPromise) {
|
|
3917
|
-
migrationPromise = (async () => {
|
|
3918
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
3919
|
-
MIGRATION_MARKER_KEY3,
|
|
3920
|
-
false
|
|
3921
|
-
);
|
|
3922
|
-
if (migrated) return;
|
|
3923
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
3924
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
3925
|
-
[]
|
|
3926
|
-
);
|
|
3927
|
-
if (legacyEntries.length > 0) {
|
|
3928
|
-
for (const entry of legacyEntries) {
|
|
3929
|
-
appendOne(entry);
|
|
3930
|
-
}
|
|
3931
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
3932
|
-
}
|
|
3933
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
|
|
3934
|
-
})();
|
|
3935
|
-
}
|
|
3936
|
-
await migrationPromise;
|
|
3937
|
-
};
|
|
3938
|
-
return {
|
|
3939
|
-
async migrate() {
|
|
3940
|
-
await ensureMigrated();
|
|
3941
|
-
},
|
|
3942
|
-
async append(entry) {
|
|
3943
|
-
await ensureMigrated();
|
|
3944
|
-
appendOne(entry);
|
|
3945
|
-
},
|
|
3946
|
-
async appendMany(entries) {
|
|
3947
|
-
await ensureMigrated();
|
|
3948
|
-
for (const entry of entries) {
|
|
3949
|
-
appendOne(entry);
|
|
3950
|
-
}
|
|
3951
|
-
},
|
|
3952
|
-
async list(options2 = {}) {
|
|
3953
|
-
await ensureMigrated();
|
|
3954
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
3955
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
3956
|
-
const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
|
|
3957
|
-
let rows;
|
|
3958
|
-
if (typeof options2.limit === "number") {
|
|
3959
|
-
rows = db.query(query).all(...params, options2.limit);
|
|
3960
|
-
} else {
|
|
3961
|
-
rows = db.query(query).all(...params);
|
|
3962
|
-
}
|
|
3963
|
-
return rows.map(mapRow);
|
|
3964
|
-
},
|
|
3965
|
-
async count(options2 = {}) {
|
|
3966
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
3967
|
-
const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
|
|
3968
|
-
const row = db.query(query).get(...params);
|
|
3969
|
-
return Number(row?.count ?? 0);
|
|
3970
|
-
},
|
|
3971
|
-
async deleteOlderThan(timestamp) {
|
|
3972
|
-
await ensureMigrated();
|
|
3973
|
-
const before = timestamp;
|
|
3974
|
-
const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
|
|
3975
|
-
return result.changes ?? 0;
|
|
3976
|
-
},
|
|
3977
|
-
async clear() {
|
|
3978
|
-
await ensureMigrated();
|
|
3979
|
-
db.query(`DELETE FROM ${tableName}`).run();
|
|
3980
|
-
}
|
|
3981
|
-
};
|
|
3982
|
-
};
|
|
3983
|
-
|
|
3984
3361
|
// storage/usageTracking/memory.ts
|
|
3985
|
-
var
|
|
3362
|
+
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3986
3363
|
var matchesFilters2 = (entry, options = {}) => {
|
|
3987
3364
|
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
3988
3365
|
return false;
|
|
@@ -3993,7 +3370,7 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
3993
3370
|
if (options.modelId && entry.modelId !== options.modelId) {
|
|
3994
3371
|
return false;
|
|
3995
3372
|
}
|
|
3996
|
-
if (options.baseUrl &&
|
|
3373
|
+
if (options.baseUrl && normalizeBaseUrl2(entry.baseUrl) !== normalizeBaseUrl2(options.baseUrl)) {
|
|
3997
3374
|
return false;
|
|
3998
3375
|
}
|
|
3999
3376
|
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
@@ -4002,23 +3379,26 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
4002
3379
|
if (options.client && entry.client !== options.client) {
|
|
4003
3380
|
return false;
|
|
4004
3381
|
}
|
|
3382
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
3383
|
+
return false;
|
|
3384
|
+
}
|
|
4005
3385
|
return true;
|
|
4006
3386
|
};
|
|
4007
3387
|
var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
4008
3388
|
const store = /* @__PURE__ */ new Map();
|
|
4009
3389
|
for (const entry of seed) {
|
|
4010
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
3390
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
4011
3391
|
}
|
|
4012
3392
|
return {
|
|
4013
3393
|
async migrate() {
|
|
4014
3394
|
return;
|
|
4015
3395
|
},
|
|
4016
3396
|
async append(entry) {
|
|
4017
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
3397
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
4018
3398
|
},
|
|
4019
3399
|
async appendMany(entries) {
|
|
4020
3400
|
for (const entry of entries) {
|
|
4021
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
3401
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
4022
3402
|
}
|
|
4023
3403
|
},
|
|
4024
3404
|
async list(options = {}) {
|
|
@@ -4046,7 +3426,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
4046
3426
|
}
|
|
4047
3427
|
};
|
|
4048
3428
|
};
|
|
4049
|
-
var
|
|
3429
|
+
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
4050
3430
|
var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
4051
3431
|
modelsFromAllProviders: {},
|
|
4052
3432
|
lastUsedModel: null,
|
|
@@ -4069,7 +3449,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4069
3449
|
setModelsFromAllProviders: (value) => {
|
|
4070
3450
|
const normalized = {};
|
|
4071
3451
|
for (const [baseUrl, models] of Object.entries(value)) {
|
|
4072
|
-
normalized[
|
|
3452
|
+
normalized[normalizeBaseUrl3(baseUrl)] = models;
|
|
4073
3453
|
}
|
|
4074
3454
|
void driver.setItem(
|
|
4075
3455
|
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
@@ -4082,7 +3462,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4082
3462
|
set({ lastUsedModel: value });
|
|
4083
3463
|
},
|
|
4084
3464
|
setBaseUrlsList: (value) => {
|
|
4085
|
-
const normalized = value.map((url) =>
|
|
3465
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
4086
3466
|
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
4087
3467
|
set({ baseUrlsList: normalized });
|
|
4088
3468
|
},
|
|
@@ -4091,14 +3471,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4091
3471
|
set({ lastBaseUrlsUpdate: value });
|
|
4092
3472
|
},
|
|
4093
3473
|
setDisabledProviders: (value) => {
|
|
4094
|
-
const normalized = value.map((url) =>
|
|
3474
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
4095
3475
|
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
4096
3476
|
set({ disabledProviders: normalized });
|
|
4097
3477
|
},
|
|
4098
3478
|
setMintsFromAllProviders: (value) => {
|
|
4099
3479
|
const normalized = {};
|
|
4100
3480
|
for (const [baseUrl, mints] of Object.entries(value)) {
|
|
4101
|
-
normalized[
|
|
3481
|
+
normalized[normalizeBaseUrl3(baseUrl)] = mints.map(
|
|
4102
3482
|
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
4103
3483
|
);
|
|
4104
3484
|
}
|
|
@@ -4111,7 +3491,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4111
3491
|
setInfoFromAllProviders: (value) => {
|
|
4112
3492
|
const normalized = {};
|
|
4113
3493
|
for (const [baseUrl, info] of Object.entries(value)) {
|
|
4114
|
-
normalized[
|
|
3494
|
+
normalized[normalizeBaseUrl3(baseUrl)] = info;
|
|
4115
3495
|
}
|
|
4116
3496
|
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
4117
3497
|
set({ infoFromAllProviders: normalized });
|
|
@@ -4119,7 +3499,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4119
3499
|
setLastModelsUpdate: (value) => {
|
|
4120
3500
|
const normalized = {};
|
|
4121
3501
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
4122
|
-
normalized[
|
|
3502
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
4123
3503
|
}
|
|
4124
3504
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
4125
3505
|
set({ lastModelsUpdate: normalized });
|
|
@@ -4129,7 +3509,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4129
3509
|
const updates = typeof value === "function" ? value(state.apiKeys) : value;
|
|
4130
3510
|
const normalized = updates.map((entry) => ({
|
|
4131
3511
|
...entry,
|
|
4132
|
-
baseUrl:
|
|
3512
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4133
3513
|
balance: entry.balance ?? 0,
|
|
4134
3514
|
lastUsed: entry.lastUsed ?? null
|
|
4135
3515
|
}));
|
|
@@ -4141,7 +3521,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4141
3521
|
set((state) => {
|
|
4142
3522
|
const updates = typeof value === "function" ? value(state.childKeys) : value;
|
|
4143
3523
|
const normalized = updates.map((entry) => ({
|
|
4144
|
-
parentBaseUrl:
|
|
3524
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
4145
3525
|
childKey: entry.childKey,
|
|
4146
3526
|
balance: entry.balance ?? 0,
|
|
4147
3527
|
balanceLimit: entry.balanceLimit,
|
|
@@ -4155,9 +3535,9 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4155
3535
|
setXcashuTokens: (value) => {
|
|
4156
3536
|
const normalized = {};
|
|
4157
3537
|
for (const [baseUrl, tokens] of Object.entries(value)) {
|
|
4158
|
-
normalized[
|
|
3538
|
+
normalized[normalizeBaseUrl3(baseUrl)] = tokens.map((entry) => ({
|
|
4159
3539
|
...entry,
|
|
4160
|
-
baseUrl:
|
|
3540
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4161
3541
|
createdAt: entry.createdAt ?? Date.now(),
|
|
4162
3542
|
tryCount: entry.tryCount ?? 0
|
|
4163
3543
|
}));
|
|
@@ -4208,12 +3588,12 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4208
3588
|
},
|
|
4209
3589
|
// ========== Failure Tracking ==========
|
|
4210
3590
|
setFailedProviders: (value) => {
|
|
4211
|
-
const normalized = value.map((url) =>
|
|
3591
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
4212
3592
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
|
|
4213
3593
|
set({ failedProviders: normalized });
|
|
4214
3594
|
},
|
|
4215
3595
|
addFailedProvider: (baseUrl) => {
|
|
4216
|
-
const normalized =
|
|
3596
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4217
3597
|
const current = get().failedProviders;
|
|
4218
3598
|
if (!current.includes(normalized)) {
|
|
4219
3599
|
const updated = [...current, normalized];
|
|
@@ -4222,7 +3602,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4222
3602
|
}
|
|
4223
3603
|
},
|
|
4224
3604
|
removeFailedProvider: (baseUrl) => {
|
|
4225
|
-
const normalized =
|
|
3605
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4226
3606
|
const current = get().failedProviders;
|
|
4227
3607
|
const updated = current.filter((url) => url !== normalized);
|
|
4228
3608
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
@@ -4231,13 +3611,13 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4231
3611
|
setLastFailed: (value) => {
|
|
4232
3612
|
const normalized = {};
|
|
4233
3613
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
4234
|
-
normalized[
|
|
3614
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
4235
3615
|
}
|
|
4236
3616
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
|
|
4237
3617
|
set({ lastFailed: normalized });
|
|
4238
3618
|
},
|
|
4239
3619
|
setLastFailedTimestamp: (baseUrl, timestamp) => {
|
|
4240
|
-
const normalized =
|
|
3620
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4241
3621
|
const current = get().lastFailed;
|
|
4242
3622
|
const updated = { ...current, [normalized]: timestamp };
|
|
4243
3623
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
|
|
@@ -4245,14 +3625,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4245
3625
|
},
|
|
4246
3626
|
setProvidersOnCooldown: (value) => {
|
|
4247
3627
|
const normalized = value.map((entry) => ({
|
|
4248
|
-
baseUrl:
|
|
3628
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4249
3629
|
timestamp: entry.timestamp
|
|
4250
3630
|
}));
|
|
4251
3631
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
|
|
4252
3632
|
set({ providersOnCooldown: normalized });
|
|
4253
3633
|
},
|
|
4254
3634
|
addProviderOnCooldown: (baseUrl, timestamp) => {
|
|
4255
|
-
const normalized =
|
|
3635
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4256
3636
|
const current = get().providersOnCooldown;
|
|
4257
3637
|
if (!current.some((entry) => entry.baseUrl === normalized)) {
|
|
4258
3638
|
const updated = [...current, { baseUrl: normalized, timestamp }];
|
|
@@ -4261,7 +3641,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4261
3641
|
}
|
|
4262
3642
|
},
|
|
4263
3643
|
removeProviderFromCooldown: (baseUrl) => {
|
|
4264
|
-
const normalized =
|
|
3644
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4265
3645
|
const current = get().providersOnCooldown;
|
|
4266
3646
|
const updated = current.filter((entry) => entry.baseUrl !== normalized);
|
|
4267
3647
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
@@ -4332,40 +3712,40 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
4332
3712
|
]);
|
|
4333
3713
|
const modelsFromAllProviders = Object.fromEntries(
|
|
4334
3714
|
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
4335
|
-
|
|
3715
|
+
normalizeBaseUrl3(baseUrl),
|
|
4336
3716
|
models
|
|
4337
3717
|
])
|
|
4338
3718
|
);
|
|
4339
|
-
const baseUrlsList = rawBaseUrls.map((url) =>
|
|
3719
|
+
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl3(url));
|
|
4340
3720
|
const disabledProviders = rawDisabledProviders.map(
|
|
4341
|
-
(url) =>
|
|
3721
|
+
(url) => normalizeBaseUrl3(url)
|
|
4342
3722
|
);
|
|
4343
3723
|
const mintsFromAllProviders = Object.fromEntries(
|
|
4344
3724
|
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
4345
|
-
|
|
3725
|
+
normalizeBaseUrl3(baseUrl),
|
|
4346
3726
|
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
4347
3727
|
])
|
|
4348
3728
|
);
|
|
4349
3729
|
const infoFromAllProviders = Object.fromEntries(
|
|
4350
3730
|
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
4351
|
-
|
|
3731
|
+
normalizeBaseUrl3(baseUrl),
|
|
4352
3732
|
info
|
|
4353
3733
|
])
|
|
4354
3734
|
);
|
|
4355
3735
|
const lastModelsUpdate = Object.fromEntries(
|
|
4356
3736
|
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
4357
|
-
|
|
3737
|
+
normalizeBaseUrl3(baseUrl),
|
|
4358
3738
|
timestamp
|
|
4359
3739
|
])
|
|
4360
3740
|
);
|
|
4361
3741
|
const apiKeys = rawApiKeys.map((entry) => ({
|
|
4362
3742
|
...entry,
|
|
4363
|
-
baseUrl:
|
|
3743
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4364
3744
|
balance: entry.balance ?? 0,
|
|
4365
3745
|
lastUsed: entry.lastUsed ?? null
|
|
4366
3746
|
}));
|
|
4367
3747
|
const childKeys = rawChildKeys.map((entry) => ({
|
|
4368
|
-
parentBaseUrl:
|
|
3748
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
4369
3749
|
childKey: entry.childKey,
|
|
4370
3750
|
balance: entry.balance ?? 0,
|
|
4371
3751
|
balanceLimit: entry.balanceLimit,
|
|
@@ -4374,9 +3754,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
4374
3754
|
}));
|
|
4375
3755
|
const xcashuTokens = Object.fromEntries(
|
|
4376
3756
|
Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
|
|
4377
|
-
|
|
3757
|
+
normalizeBaseUrl3(baseUrl),
|
|
4378
3758
|
tokens.map((entry) => ({
|
|
4379
|
-
baseUrl:
|
|
3759
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4380
3760
|
token: entry.token,
|
|
4381
3761
|
createdAt: entry.createdAt ?? Date.now(),
|
|
4382
3762
|
tryCount: entry.tryCount ?? 0
|
|
@@ -4397,16 +3777,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
4397
3777
|
lastUsed: entry.lastUsed ?? null
|
|
4398
3778
|
}));
|
|
4399
3779
|
const failedProviders = rawFailedProviders.map(
|
|
4400
|
-
(url) =>
|
|
3780
|
+
(url) => normalizeBaseUrl3(url)
|
|
4401
3781
|
);
|
|
4402
3782
|
const lastFailed = Object.fromEntries(
|
|
4403
3783
|
Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
|
|
4404
|
-
|
|
3784
|
+
normalizeBaseUrl3(baseUrl),
|
|
4405
3785
|
timestamp
|
|
4406
3786
|
])
|
|
4407
3787
|
);
|
|
4408
3788
|
const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
|
|
4409
|
-
baseUrl:
|
|
3789
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4410
3790
|
timestamp: entry.timestamp
|
|
4411
3791
|
}));
|
|
4412
3792
|
store.setState({
|
|
@@ -4447,12 +3827,12 @@ var createDiscoveryAdapterFromStore = (store) => ({
|
|
|
4447
3827
|
getCachedProviderInfo: () => store.getState().infoFromAllProviders,
|
|
4448
3828
|
setCachedProviderInfo: (info) => store.getState().setInfoFromAllProviders(info),
|
|
4449
3829
|
getProviderLastUpdate: (baseUrl) => {
|
|
4450
|
-
const normalized =
|
|
3830
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4451
3831
|
const timestamps = store.getState().lastModelsUpdate;
|
|
4452
3832
|
return timestamps[normalized] || null;
|
|
4453
3833
|
},
|
|
4454
3834
|
setProviderLastUpdate: (baseUrl, timestamp) => {
|
|
4455
|
-
const normalized =
|
|
3835
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4456
3836
|
const timestamps = { ...store.getState().lastModelsUpdate };
|
|
4457
3837
|
timestamps[normalized] = timestamp;
|
|
4458
3838
|
store.getState().setLastModelsUpdate(timestamps);
|
|
@@ -4481,24 +3861,24 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4481
3861
|
return Object.entries(distributionMap).map(([baseUrl, amt]) => ({ baseUrl, amount: amt })).sort((a, b) => b.amount - a.amount);
|
|
4482
3862
|
},
|
|
4483
3863
|
saveProviderInfo: (baseUrl, info) => {
|
|
4484
|
-
const normalized =
|
|
3864
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4485
3865
|
const next = { ...store.getState().infoFromAllProviders };
|
|
4486
3866
|
next[normalized] = info;
|
|
4487
3867
|
store.getState().setInfoFromAllProviders(next);
|
|
4488
3868
|
},
|
|
4489
3869
|
getProviderInfo: (baseUrl) => {
|
|
4490
|
-
const normalized =
|
|
3870
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4491
3871
|
return store.getState().infoFromAllProviders[normalized] || null;
|
|
4492
3872
|
},
|
|
4493
3873
|
// ========== API Keys (for apikeys mode) ==========
|
|
4494
3874
|
getApiKey: (baseUrl) => {
|
|
4495
|
-
const normalized =
|
|
3875
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4496
3876
|
const entry = store.getState().apiKeys.find((key) => key.baseUrl === normalized);
|
|
4497
3877
|
if (!entry) return null;
|
|
4498
3878
|
return entry;
|
|
4499
3879
|
},
|
|
4500
3880
|
setApiKey: (baseUrl, key) => {
|
|
4501
|
-
const normalized =
|
|
3881
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4502
3882
|
const keys = store.getState().apiKeys;
|
|
4503
3883
|
const existingIndex = keys.findIndex(
|
|
4504
3884
|
(entry) => entry.baseUrl === normalized
|
|
@@ -4516,7 +3896,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4516
3896
|
store.getState().setApiKeys(next);
|
|
4517
3897
|
},
|
|
4518
3898
|
updateApiKeyBalance: (baseUrl, balance) => {
|
|
4519
|
-
const normalized =
|
|
3899
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4520
3900
|
const keys = store.getState().apiKeys;
|
|
4521
3901
|
const next = keys.map(
|
|
4522
3902
|
(entry) => entry.baseUrl === normalized ? { ...entry, balance, lastUsed: Date.now() } : entry
|
|
@@ -4524,7 +3904,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4524
3904
|
store.getState().setApiKeys(next);
|
|
4525
3905
|
},
|
|
4526
3906
|
removeApiKey: (baseUrl) => {
|
|
4527
|
-
const normalized =
|
|
3907
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4528
3908
|
const next = store.getState().apiKeys.filter((entry) => entry.baseUrl !== normalized);
|
|
4529
3909
|
store.getState().setApiKeys(next);
|
|
4530
3910
|
},
|
|
@@ -4538,7 +3918,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4538
3918
|
},
|
|
4539
3919
|
// ========== Child Keys ==========
|
|
4540
3920
|
getChildKey: (parentBaseUrl) => {
|
|
4541
|
-
const normalized =
|
|
3921
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4542
3922
|
const entry = store.getState().childKeys.find((key) => key.parentBaseUrl === normalized);
|
|
4543
3923
|
if (!entry) return null;
|
|
4544
3924
|
return {
|
|
@@ -4551,7 +3931,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4551
3931
|
};
|
|
4552
3932
|
},
|
|
4553
3933
|
setChildKey: (parentBaseUrl, childKey, balance, validityDate, balanceLimit) => {
|
|
4554
|
-
const normalized =
|
|
3934
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4555
3935
|
const keys = store.getState().childKeys;
|
|
4556
3936
|
const existingIndex = keys.findIndex(
|
|
4557
3937
|
(entry) => entry.parentBaseUrl === normalized
|
|
@@ -4582,7 +3962,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4582
3962
|
}
|
|
4583
3963
|
},
|
|
4584
3964
|
updateChildKeyBalance: (parentBaseUrl, balance) => {
|
|
4585
|
-
const normalized =
|
|
3965
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4586
3966
|
const keys = store.getState().childKeys;
|
|
4587
3967
|
const next = keys.map(
|
|
4588
3968
|
(entry) => entry.parentBaseUrl === normalized ? { ...entry, balance } : entry
|
|
@@ -4590,7 +3970,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4590
3970
|
store.getState().setChildKeys(next);
|
|
4591
3971
|
},
|
|
4592
3972
|
removeChildKey: (parentBaseUrl) => {
|
|
4593
|
-
const normalized =
|
|
3973
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4594
3974
|
const next = store.getState().childKeys.filter((entry) => entry.parentBaseUrl !== normalized);
|
|
4595
3975
|
store.getState().setChildKeys(next);
|
|
4596
3976
|
},
|
|
@@ -4615,11 +3995,11 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4615
3995
|
return store.getState().xcashuTokens;
|
|
4616
3996
|
},
|
|
4617
3997
|
getXcashuTokensForBaseUrl: (baseUrl) => {
|
|
4618
|
-
const normalized =
|
|
3998
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4619
3999
|
return store.getState().xcashuTokens[normalized] || [];
|
|
4620
4000
|
},
|
|
4621
4001
|
addXcashuToken: (baseUrl, token) => {
|
|
4622
|
-
const normalized =
|
|
4002
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4623
4003
|
const tokens = store.getState().xcashuTokens;
|
|
4624
4004
|
const existing = tokens[normalized] || [];
|
|
4625
4005
|
const next = { ...tokens };
|
|
@@ -4630,7 +4010,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4630
4010
|
store.getState().setXcashuTokens(next);
|
|
4631
4011
|
},
|
|
4632
4012
|
removeXcashuToken: (baseUrl, token) => {
|
|
4633
|
-
const normalized =
|
|
4013
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4634
4014
|
const tokens = store.getState().xcashuTokens;
|
|
4635
4015
|
const existing = tokens[normalized] || [];
|
|
4636
4016
|
const next = { ...tokens };
|
|
@@ -4641,7 +4021,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4641
4021
|
store.getState().setXcashuTokens(next);
|
|
4642
4022
|
},
|
|
4643
4023
|
clearXcashuTokensForBaseUrl: (baseUrl) => {
|
|
4644
|
-
const normalized =
|
|
4024
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4645
4025
|
const tokens = store.getState().xcashuTokens;
|
|
4646
4026
|
const next = { ...tokens };
|
|
4647
4027
|
delete next[normalized];
|
|
@@ -4655,16 +4035,16 @@ var createProviderRegistryFromStore = (store, logger) => {
|
|
|
4655
4035
|
const log = (logger ?? consoleLogger).child("ProviderRegistry");
|
|
4656
4036
|
return {
|
|
4657
4037
|
getModelsForProvider: (baseUrl) => {
|
|
4658
|
-
const normalized =
|
|
4038
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4659
4039
|
return store.getState().modelsFromAllProviders[normalized] || [];
|
|
4660
4040
|
},
|
|
4661
4041
|
getDisabledProviders: () => store.getState().disabledProviders,
|
|
4662
4042
|
getProviderMints: (baseUrl) => {
|
|
4663
|
-
const normalized =
|
|
4043
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4664
4044
|
return store.getState().mintsFromAllProviders[normalized] || [];
|
|
4665
4045
|
},
|
|
4666
4046
|
getProviderInfo: async (baseUrl) => {
|
|
4667
|
-
const normalized =
|
|
4047
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4668
4048
|
const cached = store.getState().infoFromAllProviders[normalized];
|
|
4669
4049
|
if (cached) return cached;
|
|
4670
4050
|
try {
|
|
@@ -4686,39 +4066,292 @@ var createProviderRegistryFromStore = (store, logger) => {
|
|
|
4686
4066
|
};
|
|
4687
4067
|
};
|
|
4688
4068
|
|
|
4689
|
-
// storage/
|
|
4690
|
-
var
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4069
|
+
// storage/shardedDiscoveryAdapter.ts
|
|
4070
|
+
var MODEL_KEY_PREFIX = "models:provider:";
|
|
4071
|
+
var MODEL_TS_KEY_PREFIX = "models:provider_timestamp:";
|
|
4072
|
+
var PROVIDER_INDEX_KEY = "models:provider_index";
|
|
4073
|
+
var MIGRATION_MARKER_KEY2 = "models_sharded_migration_v1";
|
|
4074
|
+
var encodeBaseUrl = (baseUrl) => encodeURIComponent(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
4075
|
+
var modelKey = (baseUrl) => `${MODEL_KEY_PREFIX}${encodeBaseUrl(baseUrl)}`;
|
|
4076
|
+
var modelTsKey = (baseUrl) => `${MODEL_TS_KEY_PREFIX}${encodeBaseUrl(baseUrl)}`;
|
|
4077
|
+
var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
4078
|
+
var createShardedDiscoveryAdapter = async (options) => {
|
|
4079
|
+
const { driver } = options;
|
|
4080
|
+
const legacyModels = await driver.getItem(SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS, {});
|
|
4081
|
+
const legacyTimestamps = await driver.getItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, {});
|
|
4082
|
+
if (Object.keys(legacyModels).length > 0) {
|
|
4083
|
+
const migratedProviders = [];
|
|
4084
|
+
for (const [baseUrl, models] of Object.entries(legacyModels)) {
|
|
4085
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4086
|
+
await driver.setItem(modelKey(normalized), models);
|
|
4087
|
+
const ts = legacyTimestamps[normalized] ?? Date.now();
|
|
4088
|
+
await driver.setItem(modelTsKey(normalized), ts);
|
|
4089
|
+
migratedProviders.push(normalized);
|
|
4090
|
+
}
|
|
4091
|
+
const existingIndex = await driver.getItem(
|
|
4092
|
+
PROVIDER_INDEX_KEY,
|
|
4093
|
+
[]
|
|
4094
|
+
);
|
|
4095
|
+
const merged = [.../* @__PURE__ */ new Set([...existingIndex, ...migratedProviders])];
|
|
4096
|
+
await driver.setItem(PROVIDER_INDEX_KEY, merged);
|
|
4097
|
+
await driver.removeItem(SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS);
|
|
4098
|
+
await driver.removeItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE);
|
|
4695
4099
|
}
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4100
|
+
await driver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
4101
|
+
const [
|
|
4102
|
+
rawMints,
|
|
4103
|
+
rawInfo,
|
|
4104
|
+
lastUsedModel,
|
|
4105
|
+
rawDisabled,
|
|
4106
|
+
rawBaseUrls,
|
|
4107
|
+
lastBaseUrlsUpdate,
|
|
4108
|
+
rawRoutstr21Models,
|
|
4109
|
+
lastRoutstr21ModelsUpdate
|
|
4110
|
+
] = await Promise.all([
|
|
4111
|
+
driver.getItem(
|
|
4112
|
+
SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
|
|
4113
|
+
{}
|
|
4114
|
+
),
|
|
4115
|
+
driver.getItem(
|
|
4116
|
+
SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS,
|
|
4117
|
+
{}
|
|
4118
|
+
),
|
|
4119
|
+
driver.getItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, null),
|
|
4120
|
+
driver.getItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, []),
|
|
4121
|
+
driver.getItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, []),
|
|
4122
|
+
driver.getItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, null),
|
|
4123
|
+
driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
|
|
4124
|
+
driver.getItem(
|
|
4125
|
+
SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
|
|
4126
|
+
null
|
|
4127
|
+
)
|
|
4128
|
+
]);
|
|
4129
|
+
const modelsByBaseUrl = /* @__PURE__ */ new Map();
|
|
4130
|
+
const timestampsByBaseUrl = /* @__PURE__ */ new Map();
|
|
4131
|
+
const providerIndex = /* @__PURE__ */ new Set();
|
|
4132
|
+
const knownProviders = /* @__PURE__ */ new Set();
|
|
4133
|
+
for (const baseUrl of Object.keys(rawInfo)) {
|
|
4134
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4702
4135
|
}
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4136
|
+
for (const baseUrl of Object.keys(rawMints)) {
|
|
4137
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4138
|
+
}
|
|
4139
|
+
for (const baseUrl of rawBaseUrls) {
|
|
4140
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4141
|
+
}
|
|
4142
|
+
for (const baseUrl of rawDisabled) {
|
|
4143
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4144
|
+
}
|
|
4145
|
+
for (const baseUrl of Object.keys(legacyModels)) {
|
|
4146
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4147
|
+
}
|
|
4148
|
+
const indexProviders = await driver.getItem(
|
|
4149
|
+
PROVIDER_INDEX_KEY,
|
|
4150
|
+
[]
|
|
4151
|
+
);
|
|
4152
|
+
for (const baseUrl of indexProviders) {
|
|
4153
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4154
|
+
providerIndex.add(normalized);
|
|
4155
|
+
knownProviders.add(normalized);
|
|
4156
|
+
}
|
|
4157
|
+
for (const baseUrl of knownProviders) {
|
|
4158
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4159
|
+
const models = await driver.getItem(
|
|
4160
|
+
modelKey(normalized),
|
|
4161
|
+
null
|
|
4162
|
+
);
|
|
4163
|
+
const ts = await driver.getItem(
|
|
4164
|
+
modelTsKey(normalized),
|
|
4165
|
+
null
|
|
4166
|
+
);
|
|
4167
|
+
if (models !== null) {
|
|
4168
|
+
modelsByBaseUrl.set(normalized, models);
|
|
4169
|
+
}
|
|
4170
|
+
if (ts !== null) {
|
|
4171
|
+
timestampsByBaseUrl.set(normalized, ts);
|
|
4172
|
+
}
|
|
4173
|
+
if (models !== null || ts !== null) {
|
|
4174
|
+
providerIndex.add(normalized);
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
let mints = Object.fromEntries(
|
|
4178
|
+
Object.entries(rawMints).map(([baseUrl, mintList]) => [
|
|
4179
|
+
normalizeBaseUrl4(baseUrl),
|
|
4180
|
+
mintList.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
4181
|
+
])
|
|
4182
|
+
);
|
|
4183
|
+
let info = Object.fromEntries(
|
|
4184
|
+
Object.entries(rawInfo).map(([baseUrl, entry]) => [
|
|
4185
|
+
normalizeBaseUrl4(baseUrl),
|
|
4186
|
+
entry
|
|
4187
|
+
])
|
|
4188
|
+
);
|
|
4189
|
+
let _lastUsedModel = lastUsedModel;
|
|
4190
|
+
let _disabledProviders = rawDisabled.map(normalizeBaseUrl4);
|
|
4191
|
+
let _baseUrlsList = rawBaseUrls.map(normalizeBaseUrl4);
|
|
4192
|
+
let _lastBaseUrlsUpdate = lastBaseUrlsUpdate;
|
|
4193
|
+
let _routstr21Models = rawRoutstr21Models;
|
|
4194
|
+
let _lastRoutstr21ModelsUpdate = lastRoutstr21ModelsUpdate;
|
|
4195
|
+
const persistProviderIndex = () => {
|
|
4196
|
+
void driver.setItem(PROVIDER_INDEX_KEY, [...providerIndex]);
|
|
4197
|
+
};
|
|
4198
|
+
return {
|
|
4199
|
+
// -- Models (sharded kv) --
|
|
4200
|
+
getCachedModels: () => {
|
|
4201
|
+
const result = {};
|
|
4202
|
+
for (const [baseUrl, models] of modelsByBaseUrl.entries()) {
|
|
4203
|
+
result[baseUrl] = models;
|
|
4204
|
+
}
|
|
4205
|
+
return result;
|
|
4206
|
+
},
|
|
4207
|
+
setCachedModels: (models) => {
|
|
4208
|
+
const nextKeys = new Set(
|
|
4209
|
+
Object.keys(models).map((baseUrl) => normalizeBaseUrl4(baseUrl))
|
|
4210
|
+
);
|
|
4211
|
+
for (const baseUrl of [...modelsByBaseUrl.keys()]) {
|
|
4212
|
+
if (!nextKeys.has(normalizeBaseUrl4(baseUrl))) {
|
|
4213
|
+
providerIndex.delete(baseUrl);
|
|
4214
|
+
modelsByBaseUrl.delete(baseUrl);
|
|
4215
|
+
timestampsByBaseUrl.delete(baseUrl);
|
|
4216
|
+
void driver.removeItem(modelKey(baseUrl));
|
|
4217
|
+
void driver.removeItem(modelTsKey(baseUrl));
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
for (const [baseUrl, modelList] of Object.entries(models)) {
|
|
4221
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4222
|
+
providerIndex.add(normalized);
|
|
4223
|
+
modelsByBaseUrl.set(normalized, modelList);
|
|
4224
|
+
const ts = timestampsByBaseUrl.get(normalized) ?? Date.now();
|
|
4225
|
+
timestampsByBaseUrl.set(normalized, ts);
|
|
4226
|
+
void driver.setItem(modelKey(normalized), modelList);
|
|
4227
|
+
void driver.setItem(modelTsKey(normalized), ts);
|
|
4228
|
+
}
|
|
4229
|
+
persistProviderIndex();
|
|
4230
|
+
},
|
|
4231
|
+
getProviderLastUpdate: (baseUrl) => {
|
|
4232
|
+
return timestampsByBaseUrl.get(normalizeBaseUrl4(baseUrl)) ?? null;
|
|
4233
|
+
},
|
|
4234
|
+
setProviderLastUpdate: (baseUrl, timestamp) => {
|
|
4235
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4236
|
+
providerIndex.add(normalized);
|
|
4237
|
+
timestampsByBaseUrl.set(normalized, timestamp);
|
|
4238
|
+
void driver.setItem(modelTsKey(normalized), timestamp);
|
|
4239
|
+
persistProviderIndex();
|
|
4240
|
+
},
|
|
4241
|
+
// -- Mints (kv) --
|
|
4242
|
+
getCachedMints: () => mints,
|
|
4243
|
+
setCachedMints: (value) => {
|
|
4244
|
+
const normalized = {};
|
|
4245
|
+
for (const [baseUrl, mintList] of Object.entries(value)) {
|
|
4246
|
+
normalized[normalizeBaseUrl4(baseUrl)] = mintList.map(
|
|
4247
|
+
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
4248
|
+
);
|
|
4249
|
+
}
|
|
4250
|
+
mints = normalized;
|
|
4251
|
+
void driver.setItem(SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS, normalized);
|
|
4252
|
+
},
|
|
4253
|
+
// -- Provider info (kv) --
|
|
4254
|
+
getCachedProviderInfo: () => info,
|
|
4255
|
+
setCachedProviderInfo: (value) => {
|
|
4256
|
+
const normalized = {};
|
|
4257
|
+
for (const [baseUrl, entry] of Object.entries(value)) {
|
|
4258
|
+
normalized[normalizeBaseUrl4(baseUrl)] = entry;
|
|
4259
|
+
}
|
|
4260
|
+
info = normalized;
|
|
4261
|
+
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
4262
|
+
},
|
|
4263
|
+
// -- Last used model (kv) --
|
|
4264
|
+
getLastUsedModel: () => _lastUsedModel,
|
|
4265
|
+
setLastUsedModel: (modelId) => {
|
|
4266
|
+
_lastUsedModel = modelId;
|
|
4267
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, modelId);
|
|
4268
|
+
},
|
|
4269
|
+
// -- Disabled providers (kv) --
|
|
4270
|
+
getDisabledProviders: () => _disabledProviders,
|
|
4271
|
+
setDisabledProviders: (urls) => {
|
|
4272
|
+
const normalized = urls.map(normalizeBaseUrl4);
|
|
4273
|
+
_disabledProviders = normalized;
|
|
4274
|
+
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
4275
|
+
},
|
|
4276
|
+
// -- Base URLs (kv) --
|
|
4277
|
+
getBaseUrlsList: () => _baseUrlsList,
|
|
4278
|
+
getBaseUrlsLastUpdate: () => _lastBaseUrlsUpdate,
|
|
4279
|
+
setBaseUrlsList: (urls) => {
|
|
4280
|
+
const normalized = urls.map(normalizeBaseUrl4);
|
|
4281
|
+
_baseUrlsList = normalized;
|
|
4282
|
+
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
4283
|
+
},
|
|
4284
|
+
setBaseUrlsLastUpdate: (timestamp) => {
|
|
4285
|
+
_lastBaseUrlsUpdate = timestamp;
|
|
4286
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, timestamp);
|
|
4287
|
+
},
|
|
4288
|
+
// -- Routstr21 models (kv) --
|
|
4289
|
+
getRoutstr21Models: () => _routstr21Models,
|
|
4290
|
+
setRoutstr21Models: (models) => {
|
|
4291
|
+
_routstr21Models = models;
|
|
4292
|
+
void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, models);
|
|
4293
|
+
},
|
|
4294
|
+
getRoutstr21ModelsLastUpdate: () => _lastRoutstr21ModelsUpdate,
|
|
4295
|
+
setRoutstr21ModelsLastUpdate: (timestamp) => {
|
|
4296
|
+
_lastRoutstr21ModelsUpdate = timestamp;
|
|
4297
|
+
void driver.setItem(
|
|
4298
|
+
SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
|
|
4299
|
+
timestamp
|
|
4300
|
+
);
|
|
4301
|
+
}
|
|
4302
|
+
};
|
|
4303
|
+
};
|
|
4304
|
+
var createProviderRegistryFromDiscoveryAdapter = (adapter, logger) => {
|
|
4305
|
+
const log = (logger ?? consoleLogger).child("ProviderRegistry");
|
|
4306
|
+
return {
|
|
4307
|
+
getModelsForProvider: (baseUrl) => {
|
|
4308
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4309
|
+
return adapter.getCachedModels()[normalized] || [];
|
|
4310
|
+
},
|
|
4311
|
+
getDisabledProviders: () => adapter.getDisabledProviders(),
|
|
4312
|
+
getProviderMints: (baseUrl) => {
|
|
4313
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4314
|
+
return adapter.getCachedMints()[normalized] || [];
|
|
4315
|
+
},
|
|
4316
|
+
getProviderInfo: async (baseUrl) => {
|
|
4317
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4318
|
+
const cached = adapter.getCachedProviderInfo()[normalized];
|
|
4319
|
+
if (cached) return cached;
|
|
4320
|
+
try {
|
|
4321
|
+
const response = await fetch(`${normalized}v1/info`);
|
|
4322
|
+
if (!response.ok) {
|
|
4323
|
+
throw new Error(`Failed ${response.status}`);
|
|
4324
|
+
}
|
|
4325
|
+
const info = await response.json();
|
|
4326
|
+
adapter.setCachedProviderInfo({
|
|
4327
|
+
...adapter.getCachedProviderInfo(),
|
|
4328
|
+
[normalized]: info
|
|
4329
|
+
});
|
|
4330
|
+
return info;
|
|
4331
|
+
} catch (error) {
|
|
4332
|
+
log.warn(`Failed to fetch provider info from ${normalized}:`, error);
|
|
4333
|
+
return null;
|
|
4334
|
+
}
|
|
4335
|
+
},
|
|
4336
|
+
getAllProvidersModels: () => adapter.getCachedModels()
|
|
4337
|
+
};
|
|
4338
|
+
};
|
|
4339
|
+
|
|
4340
|
+
// storage/index.ts
|
|
4341
|
+
var isBrowser3 = () => {
|
|
4342
|
+
try {
|
|
4343
|
+
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
4344
|
+
} catch {
|
|
4345
|
+
return false;
|
|
4346
|
+
}
|
|
4347
|
+
};
|
|
4348
|
+
var defaultDriver = null;
|
|
4349
|
+
var getDefaultSdkDriver = () => {
|
|
4709
4350
|
if (defaultDriver) return defaultDriver;
|
|
4710
4351
|
if (isBrowser3()) {
|
|
4711
4352
|
defaultDriver = localStorageDriver;
|
|
4712
4353
|
return defaultDriver;
|
|
4713
4354
|
}
|
|
4714
|
-
if (isBun3()) {
|
|
4715
|
-
defaultDriver = createMemoryDriver();
|
|
4716
|
-
return defaultDriver;
|
|
4717
|
-
}
|
|
4718
|
-
if (isNode()) {
|
|
4719
|
-
defaultDriver = createSqliteDriver();
|
|
4720
|
-
return defaultDriver;
|
|
4721
|
-
}
|
|
4722
4355
|
defaultDriver = createMemoryDriver();
|
|
4723
4356
|
return defaultDriver;
|
|
4724
4357
|
};
|
|
@@ -4739,38 +4372,195 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
4739
4372
|
});
|
|
4740
4373
|
return defaultUsageTrackingDriver;
|
|
4741
4374
|
}
|
|
4742
|
-
if (isBun3()) {
|
|
4743
|
-
defaultUsageTrackingDriver = createBunSqliteUsageTrackingDriver();
|
|
4744
|
-
return defaultUsageTrackingDriver;
|
|
4745
|
-
}
|
|
4746
|
-
if (isNode()) {
|
|
4747
|
-
defaultUsageTrackingDriver = createSqliteUsageTrackingDriver({
|
|
4748
|
-
legacyStorageDriver: storageDriver
|
|
4749
|
-
});
|
|
4750
|
-
return defaultUsageTrackingDriver;
|
|
4751
|
-
}
|
|
4752
4375
|
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
4753
4376
|
return defaultUsageTrackingDriver;
|
|
4754
4377
|
};
|
|
4755
4378
|
var setDefaultUsageTrackingDriver = (driver) => {
|
|
4756
4379
|
defaultUsageTrackingDriver = driver;
|
|
4757
4380
|
};
|
|
4758
|
-
var
|
|
4381
|
+
var defaultDiscoveryAdapter = null;
|
|
4382
|
+
var getDefaultDiscoveryAdapter = async () => {
|
|
4383
|
+
if (defaultDiscoveryAdapter) return defaultDiscoveryAdapter;
|
|
4384
|
+
const driver = getDefaultSdkDriver();
|
|
4385
|
+
defaultDiscoveryAdapter = await createShardedDiscoveryAdapter({ driver });
|
|
4386
|
+
return defaultDiscoveryAdapter;
|
|
4387
|
+
};
|
|
4759
4388
|
var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
|
|
4760
|
-
var getDefaultProviderRegistry = async () =>
|
|
4389
|
+
var getDefaultProviderRegistry = async () => createProviderRegistryFromDiscoveryAdapter(await getDefaultDiscoveryAdapter());
|
|
4390
|
+
|
|
4391
|
+
// client/usage.ts
|
|
4392
|
+
var numOrUndef = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4393
|
+
function extractCostBreakdown(costObj) {
|
|
4394
|
+
if (!costObj || typeof costObj !== "object") return {};
|
|
4395
|
+
return {
|
|
4396
|
+
baseMsats: numOrUndef(costObj.base_msats),
|
|
4397
|
+
inputMsats: numOrUndef(costObj.input_msats),
|
|
4398
|
+
outputMsats: numOrUndef(costObj.output_msats),
|
|
4399
|
+
totalMsats: numOrUndef(costObj.total_msats),
|
|
4400
|
+
totalUsd: numOrUndef(costObj.total_usd),
|
|
4401
|
+
cacheReadInputTokens: numOrUndef(costObj.cache_read_input_tokens),
|
|
4402
|
+
cacheCreationInputTokens: numOrUndef(costObj.cache_creation_input_tokens),
|
|
4403
|
+
cacheReadMsats: numOrUndef(costObj.cache_read_msats),
|
|
4404
|
+
cacheCreationMsats: numOrUndef(costObj.cache_creation_msats),
|
|
4405
|
+
remainingBalanceMsats: numOrUndef(costObj.remaining_balance_msats)
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
function extractUsageFromResponseBody(body, fallbackSatsCost = 0) {
|
|
4409
|
+
if (!body || typeof body !== "object") return null;
|
|
4410
|
+
const usage = body.usage;
|
|
4411
|
+
if (!usage || typeof usage !== "object") return null;
|
|
4412
|
+
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
4413
|
+
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
4414
|
+
const totalTokens = Number(usage.total_tokens ?? 0);
|
|
4415
|
+
const costValue = usage.cost;
|
|
4416
|
+
let cost = 0;
|
|
4417
|
+
let satsCost = fallbackSatsCost;
|
|
4418
|
+
let breakdown = {};
|
|
4419
|
+
if (typeof costValue === "number") {
|
|
4420
|
+
cost = costValue;
|
|
4421
|
+
} else if (costValue && typeof costValue === "object") {
|
|
4422
|
+
const costObj = costValue;
|
|
4423
|
+
const totalUsd = costObj.total_usd;
|
|
4424
|
+
const totalMsats = costObj.total_msats;
|
|
4425
|
+
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
4426
|
+
if (typeof totalMsats === "number") {
|
|
4427
|
+
satsCost = totalMsats / 1e3;
|
|
4428
|
+
}
|
|
4429
|
+
breakdown = extractCostBreakdown(costObj);
|
|
4430
|
+
}
|
|
4431
|
+
const provider = typeof body.provider === "string" ? body.provider : void 0;
|
|
4432
|
+
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
4433
|
+
return null;
|
|
4434
|
+
}
|
|
4435
|
+
return {
|
|
4436
|
+
promptTokens,
|
|
4437
|
+
completionTokens,
|
|
4438
|
+
totalTokens,
|
|
4439
|
+
cost,
|
|
4440
|
+
satsCost,
|
|
4441
|
+
provider,
|
|
4442
|
+
...breakdown
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4445
|
+
function extractResponseId(body) {
|
|
4446
|
+
if (!body || typeof body !== "object") return void 0;
|
|
4447
|
+
const id = body.id;
|
|
4448
|
+
if (typeof id !== "string") return void 0;
|
|
4449
|
+
const trimmed = id.trim();
|
|
4450
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
4451
|
+
}
|
|
4452
|
+
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
4453
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4454
|
+
return null;
|
|
4455
|
+
}
|
|
4456
|
+
const provider = typeof parsed.provider === "string" ? parsed.provider : void 0;
|
|
4457
|
+
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
4458
|
+
const costObj = parsed.cost;
|
|
4459
|
+
const msats2 = costObj.total_msats ?? 0;
|
|
4460
|
+
const cost2 = costObj.total_usd ?? 0;
|
|
4461
|
+
if (msats2 === 0 && cost2 === 0) return null;
|
|
4462
|
+
return {
|
|
4463
|
+
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
4464
|
+
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
4465
|
+
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
4466
|
+
cost: Number(cost2),
|
|
4467
|
+
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost,
|
|
4468
|
+
provider,
|
|
4469
|
+
...extractCostBreakdown(costObj)
|
|
4470
|
+
};
|
|
4471
|
+
}
|
|
4472
|
+
if (!parsed.usage) {
|
|
4473
|
+
return null;
|
|
4474
|
+
}
|
|
4475
|
+
const usage = parsed.usage;
|
|
4476
|
+
const usageCost = usage.cost;
|
|
4477
|
+
let cost = 0;
|
|
4478
|
+
let msats = 0;
|
|
4479
|
+
let breakdown = {};
|
|
4480
|
+
if (typeof usageCost === "number") {
|
|
4481
|
+
cost = usageCost;
|
|
4482
|
+
} else if (usageCost && typeof usageCost === "object") {
|
|
4483
|
+
cost = usageCost.total_usd ?? 0;
|
|
4484
|
+
msats = usageCost.total_msats ?? 0;
|
|
4485
|
+
breakdown = extractCostBreakdown(usageCost);
|
|
4486
|
+
}
|
|
4487
|
+
const routstrCost = parsed.metadata?.routstr?.cost;
|
|
4488
|
+
if (routstrCost && typeof routstrCost === "object") {
|
|
4489
|
+
breakdown = { ...extractCostBreakdown(routstrCost), ...breakdown };
|
|
4490
|
+
}
|
|
4491
|
+
if (cost === 0) {
|
|
4492
|
+
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
4493
|
+
}
|
|
4494
|
+
if (msats === 0) {
|
|
4495
|
+
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
4496
|
+
}
|
|
4497
|
+
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
4498
|
+
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
4499
|
+
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
4500
|
+
const result = {
|
|
4501
|
+
promptTokens,
|
|
4502
|
+
completionTokens,
|
|
4503
|
+
totalTokens,
|
|
4504
|
+
cost: Number(cost ?? 0),
|
|
4505
|
+
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost,
|
|
4506
|
+
provider,
|
|
4507
|
+
...breakdown
|
|
4508
|
+
};
|
|
4509
|
+
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
4510
|
+
return null;
|
|
4511
|
+
}
|
|
4512
|
+
return result;
|
|
4513
|
+
}
|
|
4514
|
+
function toUsageStats(usage) {
|
|
4515
|
+
if (!usage) return void 0;
|
|
4516
|
+
return {
|
|
4517
|
+
total_tokens: usage.totalTokens,
|
|
4518
|
+
prompt_tokens: usage.promptTokens,
|
|
4519
|
+
completion_tokens: usage.completionTokens,
|
|
4520
|
+
cost: usage.cost,
|
|
4521
|
+
sats_cost: usage.satsCost
|
|
4522
|
+
};
|
|
4523
|
+
}
|
|
4761
4524
|
function mergeUsage(previous, next) {
|
|
4762
4525
|
if (!previous) return next;
|
|
4526
|
+
const pickNum = (n, p) => typeof n === "number" && n > 0 ? n : p ?? n;
|
|
4763
4527
|
return {
|
|
4764
4528
|
promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
|
|
4765
4529
|
completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
|
|
4766
4530
|
totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
|
|
4767
4531
|
cost: next.cost > 0 ? next.cost : previous.cost,
|
|
4768
|
-
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
|
|
4532
|
+
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost,
|
|
4533
|
+
provider: next.provider ?? previous.provider,
|
|
4534
|
+
baseMsats: pickNum(next.baseMsats, previous.baseMsats),
|
|
4535
|
+
inputMsats: pickNum(next.inputMsats, previous.inputMsats),
|
|
4536
|
+
outputMsats: pickNum(next.outputMsats, previous.outputMsats),
|
|
4537
|
+
totalMsats: pickNum(next.totalMsats, previous.totalMsats),
|
|
4538
|
+
totalUsd: pickNum(next.totalUsd, previous.totalUsd),
|
|
4539
|
+
cacheReadInputTokens: pickNum(
|
|
4540
|
+
next.cacheReadInputTokens,
|
|
4541
|
+
previous.cacheReadInputTokens
|
|
4542
|
+
),
|
|
4543
|
+
cacheCreationInputTokens: pickNum(
|
|
4544
|
+
next.cacheCreationInputTokens,
|
|
4545
|
+
previous.cacheCreationInputTokens
|
|
4546
|
+
),
|
|
4547
|
+
cacheReadMsats: pickNum(next.cacheReadMsats, previous.cacheReadMsats),
|
|
4548
|
+
cacheCreationMsats: pickNum(
|
|
4549
|
+
next.cacheCreationMsats,
|
|
4550
|
+
previous.cacheCreationMsats
|
|
4551
|
+
),
|
|
4552
|
+
remainingBalanceMsats: pickNum(
|
|
4553
|
+
next.remainingBalanceMsats,
|
|
4554
|
+
previous.remainingBalanceMsats
|
|
4555
|
+
)
|
|
4769
4556
|
};
|
|
4770
4557
|
}
|
|
4771
4558
|
function hasUsageChanged(previous, next) {
|
|
4772
4559
|
if (!previous) return true;
|
|
4773
|
-
return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
|
|
4560
|
+
return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost || previous.provider !== next.provider || previous.totalMsats !== next.totalMsats || previous.remainingBalanceMsats !== next.remainingBalanceMsats;
|
|
4561
|
+
}
|
|
4562
|
+
function isInspectionComplete(responseIdCaptured, usage) {
|
|
4563
|
+
return responseIdCaptured && !!usage && usage.totalTokens > 0 && typeof usage.totalMsats === "number" && !!usage.provider;
|
|
4774
4564
|
}
|
|
4775
4565
|
async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
4776
4566
|
const reader = stream.getReader();
|
|
@@ -4780,14 +4570,22 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
4780
4570
|
let capturedResponseId;
|
|
4781
4571
|
let responseIdCaptured = false;
|
|
4782
4572
|
const inspectDataPayload = (jsonText) => {
|
|
4783
|
-
|
|
4573
|
+
const trimmed = jsonText.trim();
|
|
4574
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
4575
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
4576
|
+
return;
|
|
4577
|
+
}
|
|
4578
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
4579
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
4784
4580
|
return;
|
|
4785
4581
|
}
|
|
4786
|
-
const trimmed = jsonText.trim();
|
|
4787
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
4788
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
4789
4582
|
try {
|
|
4790
4583
|
const data = JSON.parse(trimmed);
|
|
4584
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
4585
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
4586
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
4587
|
+
return;
|
|
4588
|
+
}
|
|
4791
4589
|
if (!responseIdCaptured) {
|
|
4792
4590
|
const responseId = data?.id;
|
|
4793
4591
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -4798,19 +4596,21 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
4798
4596
|
}
|
|
4799
4597
|
const usage = extractUsageFromSSEJson(data);
|
|
4800
4598
|
if (usage) {
|
|
4599
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
4801
4600
|
const merged = mergeUsage(capturedUsage, usage);
|
|
4802
4601
|
if (hasUsageChanged(capturedUsage, merged)) {
|
|
4803
4602
|
capturedUsage = merged;
|
|
4603
|
+
console.log("[routstr:sse] \u2192 merged (changed):", merged);
|
|
4804
4604
|
onUsage(merged);
|
|
4605
|
+
} else {
|
|
4606
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
4805
4607
|
}
|
|
4806
4608
|
}
|
|
4807
4609
|
} catch {
|
|
4610
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
4808
4611
|
}
|
|
4809
4612
|
};
|
|
4810
4613
|
const inspectEventBlock = (eventBlock) => {
|
|
4811
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
4812
|
-
return;
|
|
4813
|
-
}
|
|
4814
4614
|
const lines = eventBlock.split(/\r?\n/);
|
|
4815
4615
|
const dataParts = [];
|
|
4816
4616
|
for (const line of lines) {
|
|
@@ -4868,14 +4668,22 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
4868
4668
|
let capturedUsage = null;
|
|
4869
4669
|
let responseIdCaptured = false;
|
|
4870
4670
|
const inspectDataPayload = (jsonText) => {
|
|
4871
|
-
|
|
4671
|
+
const trimmed = jsonText.trim();
|
|
4672
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
4673
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
4674
|
+
return;
|
|
4675
|
+
}
|
|
4676
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
4677
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
4872
4678
|
return;
|
|
4873
4679
|
}
|
|
4874
|
-
const trimmed = jsonText.trim();
|
|
4875
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
4876
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
4877
4680
|
try {
|
|
4878
4681
|
const data = JSON.parse(trimmed);
|
|
4682
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
4683
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
4684
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
4685
|
+
return;
|
|
4686
|
+
}
|
|
4879
4687
|
if (!responseIdCaptured) {
|
|
4880
4688
|
const responseId = data?.id;
|
|
4881
4689
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -4885,19 +4693,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
4885
4693
|
}
|
|
4886
4694
|
const usage = extractUsageFromSSEJson(data);
|
|
4887
4695
|
if (usage) {
|
|
4696
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
4888
4697
|
const mergedUsage = mergeUsage(capturedUsage, usage);
|
|
4889
4698
|
if (hasUsageChanged(capturedUsage, mergedUsage)) {
|
|
4890
4699
|
capturedUsage = mergedUsage;
|
|
4700
|
+
console.log("[routstr:sse] \u2192 merged (changed):", mergedUsage);
|
|
4891
4701
|
onUsage(mergedUsage);
|
|
4702
|
+
} else {
|
|
4703
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
4892
4704
|
}
|
|
4893
4705
|
}
|
|
4894
4706
|
} catch {
|
|
4707
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
4895
4708
|
}
|
|
4896
4709
|
};
|
|
4897
4710
|
const inspectEventBlock = (eventBlock) => {
|
|
4898
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
4899
|
-
return;
|
|
4900
|
-
}
|
|
4901
4711
|
const lines = eventBlock.split(/\r?\n/);
|
|
4902
4712
|
const dataParts = [];
|
|
4903
4713
|
for (const line of lines) {
|
|
@@ -4970,7 +4780,6 @@ var RoutstrClient = class {
|
|
|
4970
4780
|
this.balanceManager,
|
|
4971
4781
|
this.logger
|
|
4972
4782
|
);
|
|
4973
|
-
this.streamProcessor = new StreamProcessor();
|
|
4974
4783
|
this.alertLevel = alertLevel;
|
|
4975
4784
|
this.mode = mode;
|
|
4976
4785
|
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
@@ -4982,7 +4791,6 @@ var RoutstrClient = class {
|
|
|
4982
4791
|
providerRegistry;
|
|
4983
4792
|
cashuSpender;
|
|
4984
4793
|
balanceManager;
|
|
4985
|
-
streamProcessor;
|
|
4986
4794
|
providerManager;
|
|
4987
4795
|
alertLevel;
|
|
4988
4796
|
mode;
|
|
@@ -5067,6 +4875,8 @@ var RoutstrClient = class {
|
|
|
5067
4875
|
baseUrl: prepared.baseUrlUsed,
|
|
5068
4876
|
mintUrl: params.mintUrl,
|
|
5069
4877
|
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
4878
|
+
initialTokenBalanceUnknown: prepared.tokenBalanceUnknown,
|
|
4879
|
+
fallbackSatsSpent: usage?.satsCost,
|
|
5070
4880
|
response: prepared.response,
|
|
5071
4881
|
modelId: prepared.modelId,
|
|
5072
4882
|
usage,
|
|
@@ -5131,7 +4941,7 @@ var RoutstrClient = class {
|
|
|
5131
4941
|
);
|
|
5132
4942
|
}
|
|
5133
4943
|
}
|
|
5134
|
-
const { token, tokenBalance, tokenBalanceUnit } = await this._spendToken({
|
|
4944
|
+
const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = await this._spendToken({
|
|
5135
4945
|
mintUrl,
|
|
5136
4946
|
amount: requiredSats,
|
|
5137
4947
|
baseUrl
|
|
@@ -5157,9 +4967,20 @@ var RoutstrClient = class {
|
|
|
5157
4967
|
baseHeaders,
|
|
5158
4968
|
selectedModel
|
|
5159
4969
|
});
|
|
5160
|
-
|
|
4970
|
+
let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
4971
|
+
let initialTokenBalanceUnknown = tokenBalanceUnknown;
|
|
5161
4972
|
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
5162
4973
|
const tokenUsed = response.token || token;
|
|
4974
|
+
if (baseUrlUsed !== baseUrl || tokenUsed !== token) {
|
|
4975
|
+
if (typeof response.initialTokenBalanceInSats === "number") {
|
|
4976
|
+
tokenBalanceInSats = response.initialTokenBalanceInSats;
|
|
4977
|
+
initialTokenBalanceUnknown = Boolean(
|
|
4978
|
+
response.initialTokenBalanceUnknown
|
|
4979
|
+
);
|
|
4980
|
+
} else {
|
|
4981
|
+
initialTokenBalanceUnknown = true;
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
5163
4984
|
const contentType = response.headers.get("content-type") || "";
|
|
5164
4985
|
let processedResponse = response;
|
|
5165
4986
|
let capturedUsage;
|
|
@@ -5192,6 +5013,7 @@ var RoutstrClient = class {
|
|
|
5192
5013
|
tokenUsed,
|
|
5193
5014
|
baseUrlUsed,
|
|
5194
5015
|
tokenBalanceInSats,
|
|
5016
|
+
tokenBalanceUnknown: initialTokenBalanceUnknown,
|
|
5195
5017
|
modelId,
|
|
5196
5018
|
capturedUsage,
|
|
5197
5019
|
capturedResponseId,
|
|
@@ -5211,899 +5033,1049 @@ var RoutstrClient = class {
|
|
|
5211
5033
|
return void 0;
|
|
5212
5034
|
}
|
|
5213
5035
|
/**
|
|
5214
|
-
*
|
|
5036
|
+
* Make the API request with failover support
|
|
5215
5037
|
*/
|
|
5216
|
-
async
|
|
5217
|
-
const {
|
|
5218
|
-
messageHistory,
|
|
5219
|
-
selectedModel,
|
|
5220
|
-
baseUrl,
|
|
5221
|
-
mintUrl,
|
|
5222
|
-
balance,
|
|
5223
|
-
transactionHistory,
|
|
5224
|
-
maxTokens,
|
|
5225
|
-
headers
|
|
5226
|
-
} = options;
|
|
5227
|
-
const apiMessages = await this._convertMessages(messageHistory);
|
|
5228
|
-
const requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
5229
|
-
selectedModel,
|
|
5230
|
-
apiMessages,
|
|
5231
|
-
maxTokens
|
|
5232
|
-
);
|
|
5038
|
+
async _makeRequest(params) {
|
|
5039
|
+
const { path, method, body, baseUrl, token, headers } = params;
|
|
5233
5040
|
try {
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
const
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5041
|
+
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
5042
|
+
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
5043
|
+
const response = await fetch(url, {
|
|
5044
|
+
method,
|
|
5045
|
+
headers,
|
|
5046
|
+
body: body === void 0 || method === "GET" ? void 0 : JSON.stringify(body)
|
|
5240
5047
|
});
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5048
|
+
if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
|
|
5049
|
+
response.baseUrl = baseUrl;
|
|
5050
|
+
response.token = token;
|
|
5051
|
+
if (!response.ok) {
|
|
5052
|
+
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
5053
|
+
let bodyText;
|
|
5054
|
+
try {
|
|
5055
|
+
bodyText = await response.text();
|
|
5056
|
+
} catch (e) {
|
|
5057
|
+
bodyText = void 0;
|
|
5058
|
+
}
|
|
5059
|
+
return await this._handleErrorResponse(
|
|
5060
|
+
params,
|
|
5061
|
+
token,
|
|
5062
|
+
response.status,
|
|
5063
|
+
requestId,
|
|
5064
|
+
this.mode === "xcashu" ? response.headers.get("x-cashu") ?? void 0 : void 0,
|
|
5065
|
+
bodyText,
|
|
5066
|
+
params.retryCount ?? 0
|
|
5255
5067
|
);
|
|
5256
|
-
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
5257
5068
|
}
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5069
|
+
return response;
|
|
5070
|
+
} catch (error) {
|
|
5071
|
+
if (isNetworkErrorMessage(error?.message || "")) {
|
|
5072
|
+
return await this._handleErrorResponse(
|
|
5073
|
+
params,
|
|
5074
|
+
token,
|
|
5075
|
+
-1,
|
|
5076
|
+
// just for Network Error to skip all statuses
|
|
5077
|
+
void 0,
|
|
5078
|
+
void 0,
|
|
5079
|
+
void 0,
|
|
5080
|
+
params.retryCount ?? 0
|
|
5081
|
+
);
|
|
5265
5082
|
}
|
|
5266
|
-
|
|
5267
|
-
|
|
5083
|
+
throw error;
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
/**
|
|
5087
|
+
* Handle error responses with failover
|
|
5088
|
+
*/
|
|
5089
|
+
async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
|
|
5090
|
+
const MAX_RETRIES_PER_PROVIDER = 2;
|
|
5091
|
+
const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
|
|
5092
|
+
let tryNextProvider = false;
|
|
5093
|
+
const errorMessage = responseBody;
|
|
5094
|
+
this._log(
|
|
5095
|
+
"DEBUG",
|
|
5096
|
+
`[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}, errorMessage=${errorMessage}`
|
|
5097
|
+
);
|
|
5098
|
+
this._log(
|
|
5099
|
+
"DEBUG",
|
|
5100
|
+
`[RoutstrClient] _handleErrorResponse: Attempting to receive/restore token for ${baseUrl}`
|
|
5101
|
+
);
|
|
5102
|
+
if (params.token.startsWith("cashu")) {
|
|
5103
|
+
const receiveResult = await this.cashuSpender.receiveToken(
|
|
5104
|
+
params.token
|
|
5105
|
+
);
|
|
5106
|
+
if (receiveResult.success) {
|
|
5107
|
+
this._log(
|
|
5108
|
+
"DEBUG",
|
|
5109
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
5110
|
+
);
|
|
5111
|
+
tryNextProvider = true;
|
|
5112
|
+
} else {
|
|
5113
|
+
this._log(
|
|
5114
|
+
"DEBUG",
|
|
5115
|
+
`[RoutstrClient] _handleErrorResponse: Failed to receive token: ${receiveResult.message}`
|
|
5116
|
+
);
|
|
5268
5117
|
}
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5118
|
+
}
|
|
5119
|
+
if (this.mode === "xcashu") {
|
|
5120
|
+
if (xCashuRefundToken) {
|
|
5121
|
+
this._log(
|
|
5122
|
+
"DEBUG",
|
|
5123
|
+
`[RoutstrClient] _handleErrorResponse: Attempting to receive xcashu refund token, preview=${xCashuRefundToken.substring(0, 20)}...`
|
|
5124
|
+
);
|
|
5125
|
+
const receiveResult = await this.cashuSpender.receiveToken(xCashuRefundToken);
|
|
5126
|
+
if (receiveResult.success) {
|
|
5127
|
+
this._log(
|
|
5128
|
+
"DEBUG",
|
|
5129
|
+
`[RoutstrClient] _handleErrorResponse: xcashu refund received, amount=${receiveResult.amount}`
|
|
5130
|
+
);
|
|
5131
|
+
tryNextProvider = true;
|
|
5132
|
+
} else {
|
|
5133
|
+
this._log(
|
|
5134
|
+
"ERROR",
|
|
5135
|
+
`[xcashu] Failed to receive refund token: ${receiveResult.message}`
|
|
5136
|
+
);
|
|
5137
|
+
throw new ProviderError(
|
|
5138
|
+
baseUrl,
|
|
5139
|
+
status,
|
|
5140
|
+
"[xcashu] Failed to receive refund token",
|
|
5141
|
+
requestId
|
|
5142
|
+
);
|
|
5143
|
+
}
|
|
5144
|
+
} else {
|
|
5145
|
+
if (!tryNextProvider)
|
|
5146
|
+
throw new ProviderError(
|
|
5147
|
+
baseUrl,
|
|
5148
|
+
status,
|
|
5149
|
+
"[xcashu] Failed to receive refund token",
|
|
5150
|
+
requestId
|
|
5151
|
+
);
|
|
5284
5152
|
}
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
selectedModel.id
|
|
5153
|
+
}
|
|
5154
|
+
if (status === 402 && !tryNextProvider && this.mode === "apikeys") {
|
|
5155
|
+
this.storageAdapter.getApiKey(baseUrl);
|
|
5156
|
+
let topupAmount = params.requiredSats;
|
|
5157
|
+
try {
|
|
5158
|
+
const currentBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5159
|
+
params.token,
|
|
5160
|
+
baseUrl
|
|
5294
5161
|
);
|
|
5295
|
-
if (
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
5301
|
-
const message = await this._createAssistantMessage(streamingResult);
|
|
5302
|
-
callbacks.onMessageAppend(message);
|
|
5162
|
+
if (currentBalanceInfo.balanceUnknown) {
|
|
5163
|
+
this._log(
|
|
5164
|
+
"DEBUG",
|
|
5165
|
+
`[RoutstrClient] _handleErrorResponse: Current balance unknown for ${baseUrl}; using default topup amount=${topupAmount}`
|
|
5166
|
+
);
|
|
5303
5167
|
} else {
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5168
|
+
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
5169
|
+
const reservedBalance = currentBalanceInfo.unit === "msat" ? (currentBalanceInfo.reserved ?? 0) / 1e3 : currentBalanceInfo.reserved ?? 0;
|
|
5170
|
+
const shortfall = Math.max(
|
|
5171
|
+
0,
|
|
5172
|
+
params.requiredSats - currentBalance + reservedBalance
|
|
5173
|
+
);
|
|
5174
|
+
topupAmount = shortfall > 0.21 * params.requiredSats ? shortfall : 0.21 * params.requiredSats;
|
|
5175
|
+
this._log(
|
|
5176
|
+
"DEBUG",
|
|
5177
|
+
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance}. Reserved Balance: ${reservedBalance}. Available Balance: ${currentBalance - reservedBalance}`
|
|
5178
|
+
);
|
|
5308
5179
|
}
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
baseUrl: baseUrlUsed,
|
|
5315
|
-
mintUrl,
|
|
5316
|
-
initialTokenBalance: tokenBalanceInSats,
|
|
5317
|
-
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
5318
|
-
response,
|
|
5319
|
-
modelId: selectedModel.id,
|
|
5320
|
-
usage: streamingResult.usage ? {
|
|
5321
|
-
promptTokens: Number(streamingResult.usage.prompt_tokens ?? 0),
|
|
5322
|
-
completionTokens: Number(
|
|
5323
|
-
streamingResult.usage.completion_tokens ?? 0
|
|
5324
|
-
),
|
|
5325
|
-
totalTokens: Number(streamingResult.usage.total_tokens ?? 0),
|
|
5326
|
-
cost: Number(streamingResult.usage.cost ?? 0),
|
|
5327
|
-
satsCost: Number(streamingResult.usage.sats_cost ?? 0)
|
|
5328
|
-
} : void 0,
|
|
5329
|
-
requestId: streamingResult.responseId
|
|
5330
|
-
});
|
|
5331
|
-
const estimatedCosts = this._getEstimatedCosts(
|
|
5332
|
-
selectedModel,
|
|
5333
|
-
streamingResult
|
|
5180
|
+
} catch (e) {
|
|
5181
|
+
this._log(
|
|
5182
|
+
"WARN",
|
|
5183
|
+
"Could not get current token balance for topup calculation:",
|
|
5184
|
+
e
|
|
5334
5185
|
);
|
|
5335
|
-
|
|
5336
|
-
|
|
5186
|
+
}
|
|
5187
|
+
const topupResult = await this.balanceManager.topUp({
|
|
5188
|
+
mintUrl,
|
|
5189
|
+
baseUrl,
|
|
5190
|
+
amount: topupAmount * TOPUP_MARGIN,
|
|
5191
|
+
token: params.token
|
|
5192
|
+
});
|
|
5193
|
+
this._log(
|
|
5194
|
+
"DEBUG",
|
|
5195
|
+
`[RoutstrClient] _handleErrorResponse: Topup result for ${baseUrl}: success=${topupResult.success}, message=${topupResult.message}`
|
|
5196
|
+
);
|
|
5197
|
+
if (!topupResult.success) {
|
|
5198
|
+
const message = topupResult.message || "";
|
|
5199
|
+
if (message.includes("Insufficient balance")) {
|
|
5200
|
+
const needMatch = message.match(/need (\d+)/);
|
|
5201
|
+
const haveMatch = message.match(/have (\d+)/);
|
|
5202
|
+
const required = needMatch ? parseInt(needMatch[1], 10) : params.requiredSats;
|
|
5203
|
+
const available = haveMatch ? parseInt(haveMatch[1], 10) : 0;
|
|
5204
|
+
this._log(
|
|
5205
|
+
"DEBUG",
|
|
5206
|
+
`[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
|
|
5207
|
+
);
|
|
5208
|
+
throw new InsufficientBalanceError(
|
|
5209
|
+
required,
|
|
5210
|
+
available,
|
|
5211
|
+
0,
|
|
5212
|
+
"",
|
|
5213
|
+
message
|
|
5214
|
+
);
|
|
5215
|
+
} else {
|
|
5216
|
+
this._log(
|
|
5217
|
+
"DEBUG",
|
|
5218
|
+
`[RoutstrClient] _handleErrorResponse: Topup failed with non-insufficient-balance error, will try next provider`
|
|
5219
|
+
);
|
|
5220
|
+
tryNextProvider = true;
|
|
5221
|
+
}
|
|
5337
5222
|
} else {
|
|
5338
|
-
|
|
5223
|
+
this._log(
|
|
5224
|
+
"DEBUG",
|
|
5225
|
+
`[RoutstrClient] _handleErrorResponse: Topup successful, will retry with new token`
|
|
5226
|
+
);
|
|
5227
|
+
}
|
|
5228
|
+
if (!tryNextProvider) {
|
|
5229
|
+
if (retryCount < MAX_RETRIES_PER_PROVIDER) {
|
|
5230
|
+
this._log(
|
|
5231
|
+
"DEBUG",
|
|
5232
|
+
`[RoutstrClient] _handleErrorResponse: Retrying 402 (attempt ${retryCount + 1}/${MAX_RETRIES_PER_PROVIDER})`
|
|
5233
|
+
);
|
|
5234
|
+
return this._makeRequest({
|
|
5235
|
+
...params,
|
|
5236
|
+
token: params.token,
|
|
5237
|
+
headers: this._withAuthHeader(params.baseHeaders, params.token),
|
|
5238
|
+
retryCount: retryCount + 1
|
|
5239
|
+
});
|
|
5240
|
+
} else {
|
|
5241
|
+
this._log(
|
|
5242
|
+
"DEBUG",
|
|
5243
|
+
`[RoutstrClient] _handleErrorResponse: 402 retry limit reached (${retryCount}/${MAX_RETRIES_PER_PROVIDER}), failing over to next provider`
|
|
5244
|
+
);
|
|
5245
|
+
tryNextProvider = true;
|
|
5246
|
+
}
|
|
5339
5247
|
}
|
|
5340
|
-
} catch (error) {
|
|
5341
|
-
this._handleError(error, callbacks);
|
|
5342
|
-
} finally {
|
|
5343
|
-
callbacks.onPaymentProcessing?.(false);
|
|
5344
5248
|
}
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5249
|
+
const isInsufficientBalance413 = status === 413 && responseBody?.includes("Insufficient balance");
|
|
5250
|
+
if (isInsufficientBalance413 && !tryNextProvider && this.mode === "apikeys") {
|
|
5251
|
+
let retryToken = params.token;
|
|
5252
|
+
try {
|
|
5253
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5254
|
+
params.token,
|
|
5255
|
+
baseUrl
|
|
5256
|
+
);
|
|
5257
|
+
if (latestBalanceInfo.isInvalidApiKey) {
|
|
5258
|
+
this._log(
|
|
5259
|
+
"DEBUG",
|
|
5260
|
+
`[RoutstrClient] _handleErrorResponse: Invalid API key (proofs already spent), removing for ${baseUrl}`
|
|
5261
|
+
);
|
|
5262
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
5263
|
+
tryNextProvider = true;
|
|
5264
|
+
} else {
|
|
5265
|
+
const latestTokenBalance = latestBalanceInfo.balanceUnknown ? void 0 : latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
5266
|
+
if (latestBalanceInfo.apiKey) {
|
|
5267
|
+
const storedApiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
5268
|
+
if (storedApiKeyEntry?.key !== latestBalanceInfo.apiKey) {
|
|
5269
|
+
if (storedApiKeyEntry) {
|
|
5270
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
5271
|
+
}
|
|
5272
|
+
this.storageAdapter.setApiKey(baseUrl, latestBalanceInfo.apiKey);
|
|
5273
|
+
}
|
|
5274
|
+
retryToken = latestBalanceInfo.apiKey;
|
|
5275
|
+
}
|
|
5276
|
+
if (latestTokenBalance !== void 0 && latestTokenBalance >= 0) {
|
|
5277
|
+
this.storageAdapter.updateApiKeyBalance(
|
|
5278
|
+
baseUrl,
|
|
5279
|
+
latestTokenBalance
|
|
5280
|
+
);
|
|
5281
|
+
}
|
|
5369
5282
|
}
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
this.mode === "xcashu" ? response.headers.get("x-cashu") ?? void 0 : void 0,
|
|
5376
|
-
bodyText,
|
|
5377
|
-
params.retryCount ?? 0
|
|
5283
|
+
} catch (error) {
|
|
5284
|
+
this._log(
|
|
5285
|
+
"WARN",
|
|
5286
|
+
`[RoutstrClient] _handleErrorResponse: Failed to refresh API key after 413 insufficient balance for ${baseUrl}`,
|
|
5287
|
+
error
|
|
5378
5288
|
);
|
|
5379
5289
|
}
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5290
|
+
if (retryCount < MAX_RETRIES_PER_PROVIDER) {
|
|
5291
|
+
this._log(
|
|
5292
|
+
"DEBUG",
|
|
5293
|
+
`[RoutstrClient] _handleErrorResponse: Retrying 413 (attempt ${retryCount + 1}/${MAX_RETRIES_PER_PROVIDER})`
|
|
5294
|
+
);
|
|
5295
|
+
return this._makeRequest({
|
|
5296
|
+
...params,
|
|
5297
|
+
token: retryToken,
|
|
5298
|
+
headers: this._withAuthHeader(params.baseHeaders, retryToken),
|
|
5299
|
+
retryCount: retryCount + 1
|
|
5300
|
+
});
|
|
5301
|
+
} else {
|
|
5302
|
+
this._log(
|
|
5303
|
+
"DEBUG",
|
|
5304
|
+
`[RoutstrClient] _handleErrorResponse: 413 retry limit reached (${retryCount}/${MAX_RETRIES_PER_PROVIDER}), failing over to next provider`
|
|
5305
|
+
);
|
|
5306
|
+
tryNextProvider = true;
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
5309
|
+
if (status === 401 && this.mode === "apikeys") {
|
|
5310
|
+
this._log(
|
|
5311
|
+
"DEBUG",
|
|
5312
|
+
`[RoutstrClient] _handleErrorResponse: Checking balance for ${baseUrl}, key preview=${token}`
|
|
5313
|
+
);
|
|
5314
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5315
|
+
token,
|
|
5316
|
+
baseUrl
|
|
5317
|
+
);
|
|
5318
|
+
if (latestBalanceInfo.isInvalidApiKey) {
|
|
5319
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
5320
|
+
tryNextProvider = true;
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
if ((status === 401 || status === 403 || status === 404 || status === 413 || status === 400 || status === 429 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
|
|
5324
|
+
this._log(
|
|
5325
|
+
"DEBUG",
|
|
5326
|
+
`[RoutstrClient] _handleErrorResponse: Status ${status} (${status === 429 ? "rate limited" : "auth/server error"}), attempting refund for ${baseUrl}, mode=${this.mode}`
|
|
5327
|
+
);
|
|
5328
|
+
if (this.mode === "apikeys") {
|
|
5329
|
+
this._log(
|
|
5330
|
+
"DEBUG",
|
|
5331
|
+
`[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
|
|
5332
|
+
);
|
|
5333
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5385
5334
|
token,
|
|
5386
|
-
|
|
5387
|
-
// just for Network Error to skip all statuses
|
|
5388
|
-
void 0,
|
|
5389
|
-
void 0,
|
|
5390
|
-
void 0,
|
|
5391
|
-
params.retryCount ?? 0
|
|
5335
|
+
baseUrl
|
|
5392
5336
|
);
|
|
5337
|
+
this._log(
|
|
5338
|
+
"DEBUG",
|
|
5339
|
+
`[RoutstrClient] _handleErrorResponse: Initial API key balance: ${latestBalanceInfo.amount}`
|
|
5340
|
+
);
|
|
5341
|
+
const refundResult = await this.balanceManager.refundApiKey({
|
|
5342
|
+
mintUrl,
|
|
5343
|
+
baseUrl,
|
|
5344
|
+
apiKey: token,
|
|
5345
|
+
forceRefund: true
|
|
5346
|
+
});
|
|
5347
|
+
this._log(
|
|
5348
|
+
"DEBUG",
|
|
5349
|
+
`[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
|
|
5350
|
+
);
|
|
5351
|
+
if (!refundResult.success && latestBalanceInfo.amount > 0 && !latestBalanceInfo.balanceUnknown) {
|
|
5352
|
+
throw new ProviderError(
|
|
5353
|
+
baseUrl,
|
|
5354
|
+
status,
|
|
5355
|
+
refundResult.message ?? "Unknown error"
|
|
5356
|
+
);
|
|
5357
|
+
}
|
|
5393
5358
|
}
|
|
5394
|
-
throw error;
|
|
5395
5359
|
}
|
|
5360
|
+
this.providerManager.markFailed(baseUrl);
|
|
5361
|
+
this._log(
|
|
5362
|
+
"DEBUG",
|
|
5363
|
+
`[RoutstrClient] _handleErrorResponse: Marked provider ${baseUrl} as failed`
|
|
5364
|
+
);
|
|
5365
|
+
if (!selectedModel) {
|
|
5366
|
+
throw new ProviderError(
|
|
5367
|
+
baseUrl,
|
|
5368
|
+
status,
|
|
5369
|
+
"Funny, no selected model. HMM. "
|
|
5370
|
+
);
|
|
5371
|
+
}
|
|
5372
|
+
const nextProvider = this.providerManager.findNextBestProvider(
|
|
5373
|
+
selectedModel.id,
|
|
5374
|
+
baseUrl
|
|
5375
|
+
);
|
|
5376
|
+
if (nextProvider) {
|
|
5377
|
+
this._log(
|
|
5378
|
+
"DEBUG",
|
|
5379
|
+
`[RoutstrClient] _handleErrorResponse: Failing over to next provider: ${nextProvider}, model: ${selectedModel.id}`
|
|
5380
|
+
);
|
|
5381
|
+
const newModel = await this.providerManager.getModelForProvider(
|
|
5382
|
+
nextProvider,
|
|
5383
|
+
selectedModel.id
|
|
5384
|
+
) ?? selectedModel;
|
|
5385
|
+
const messagesForPricing = Array.isArray(
|
|
5386
|
+
body?.messages
|
|
5387
|
+
) ? body.messages : [];
|
|
5388
|
+
const newRequiredSats = this.providerManager.getRequiredSatsForModel(
|
|
5389
|
+
newModel,
|
|
5390
|
+
messagesForPricing,
|
|
5391
|
+
params.maxTokens
|
|
5392
|
+
);
|
|
5393
|
+
this._log(
|
|
5394
|
+
"DEBUG",
|
|
5395
|
+
`[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
|
|
5396
|
+
);
|
|
5397
|
+
const spendResult = await this._spendToken({
|
|
5398
|
+
mintUrl,
|
|
5399
|
+
amount: newRequiredSats,
|
|
5400
|
+
baseUrl: nextProvider
|
|
5401
|
+
});
|
|
5402
|
+
const retryResponse = await this._makeRequest({
|
|
5403
|
+
...params,
|
|
5404
|
+
path,
|
|
5405
|
+
method,
|
|
5406
|
+
body,
|
|
5407
|
+
baseUrl: nextProvider,
|
|
5408
|
+
selectedModel: newModel,
|
|
5409
|
+
token: spendResult.token,
|
|
5410
|
+
requiredSats: newRequiredSats,
|
|
5411
|
+
headers: this._withAuthHeader(params.baseHeaders, spendResult.token),
|
|
5412
|
+
retryCount: 0
|
|
5413
|
+
});
|
|
5414
|
+
retryResponse.initialTokenBalanceInSats = spendResult.tokenBalanceUnit === "msat" ? spendResult.tokenBalance / 1e3 : spendResult.tokenBalance;
|
|
5415
|
+
retryResponse.initialTokenBalanceUnknown = spendResult.tokenBalanceUnknown;
|
|
5416
|
+
return retryResponse;
|
|
5417
|
+
}
|
|
5418
|
+
throw new FailoverError(
|
|
5419
|
+
baseUrl,
|
|
5420
|
+
Array.from(this.providerManager.getFailedProviders())
|
|
5421
|
+
);
|
|
5396
5422
|
}
|
|
5397
5423
|
/**
|
|
5398
|
-
* Handle
|
|
5424
|
+
* Handle post-response balance update for all modes
|
|
5399
5425
|
*/
|
|
5400
|
-
async
|
|
5401
|
-
const
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
);
|
|
5417
|
-
if (
|
|
5418
|
-
this.
|
|
5419
|
-
"DEBUG",
|
|
5420
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
5421
|
-
);
|
|
5422
|
-
tryNextProvider = true;
|
|
5423
|
-
} else {
|
|
5424
|
-
this._log(
|
|
5425
|
-
"DEBUG",
|
|
5426
|
-
`[RoutstrClient] _handleErrorResponse: Failed to receive token: ${receiveResult.message}`
|
|
5427
|
-
);
|
|
5428
|
-
}
|
|
5429
|
-
}
|
|
5430
|
-
if (this.mode === "xcashu") {
|
|
5431
|
-
if (xCashuRefundToken) {
|
|
5432
|
-
this._log(
|
|
5433
|
-
"DEBUG",
|
|
5434
|
-
`[RoutstrClient] _handleErrorResponse: Attempting to receive xcashu refund token, preview=${xCashuRefundToken.substring(0, 20)}...`
|
|
5435
|
-
);
|
|
5436
|
-
const receiveResult = await this.cashuSpender.receiveToken(xCashuRefundToken);
|
|
5426
|
+
async _handlePostResponseBalanceUpdate(params) {
|
|
5427
|
+
const {
|
|
5428
|
+
token,
|
|
5429
|
+
baseUrl,
|
|
5430
|
+
mintUrl,
|
|
5431
|
+
initialTokenBalance,
|
|
5432
|
+
initialTokenBalanceUnknown,
|
|
5433
|
+
fallbackSatsSpent,
|
|
5434
|
+
response,
|
|
5435
|
+
modelId,
|
|
5436
|
+
usage,
|
|
5437
|
+
requestId,
|
|
5438
|
+
clientApiKey
|
|
5439
|
+
} = params;
|
|
5440
|
+
let satsSpent = initialTokenBalance;
|
|
5441
|
+
if (this.mode === "xcashu" && response) {
|
|
5442
|
+
const refundToken = response.headers.get("x-cashu") ?? void 0;
|
|
5443
|
+
if (refundToken) {
|
|
5444
|
+
const receiveResult = await this.cashuSpender.receiveToken(refundToken);
|
|
5437
5445
|
if (receiveResult.success) {
|
|
5438
|
-
this.
|
|
5439
|
-
|
|
5440
|
-
`[RoutstrClient] _handleErrorResponse: xcashu refund received, amount=${receiveResult.amount}`
|
|
5441
|
-
);
|
|
5442
|
-
tryNextProvider = true;
|
|
5446
|
+
this.storageAdapter.removeXcashuToken(baseUrl, token);
|
|
5447
|
+
satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
|
|
5443
5448
|
} else {
|
|
5444
5449
|
this._log(
|
|
5445
5450
|
"ERROR",
|
|
5446
5451
|
`[xcashu] Failed to receive refund token: ${receiveResult.message}`
|
|
5447
5452
|
);
|
|
5448
|
-
throw new ProviderError(
|
|
5449
|
-
baseUrl,
|
|
5450
|
-
status,
|
|
5451
|
-
"[xcashu] Failed to receive refund token",
|
|
5452
|
-
requestId
|
|
5453
|
-
);
|
|
5454
5453
|
}
|
|
5455
|
-
} else {
|
|
5456
|
-
if (!tryNextProvider)
|
|
5457
|
-
throw new ProviderError(
|
|
5458
|
-
baseUrl,
|
|
5459
|
-
status,
|
|
5460
|
-
"[xcashu] Failed to receive refund token",
|
|
5461
|
-
requestId
|
|
5462
|
-
);
|
|
5463
5454
|
}
|
|
5464
|
-
}
|
|
5465
|
-
if (status === 402 && !tryNextProvider && this.mode === "apikeys") {
|
|
5466
|
-
this.storageAdapter.getApiKey(baseUrl);
|
|
5467
|
-
let topupAmount = params.requiredSats;
|
|
5455
|
+
} else if (this.mode === "apikeys") {
|
|
5468
5456
|
try {
|
|
5469
|
-
const
|
|
5470
|
-
|
|
5457
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5458
|
+
token,
|
|
5471
5459
|
baseUrl
|
|
5472
5460
|
);
|
|
5473
|
-
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
5474
|
-
const reservedBalance = currentBalanceInfo.unit === "msat" ? (currentBalanceInfo.reserved ?? 0) / 1e3 : currentBalanceInfo.reserved ?? 0;
|
|
5475
|
-
const shortfall = Math.max(0, params.requiredSats - currentBalance + reservedBalance);
|
|
5476
|
-
topupAmount = shortfall > 0.21 * params.requiredSats ? shortfall : 0.21 * params.requiredSats;
|
|
5477
5461
|
this._log(
|
|
5478
5462
|
"DEBUG",
|
|
5479
|
-
|
|
5463
|
+
"LATEST Balance",
|
|
5464
|
+
latestBalanceInfo.amount,
|
|
5465
|
+
latestBalanceInfo.reserved,
|
|
5466
|
+
latestBalanceInfo.apiKey,
|
|
5467
|
+
baseUrl
|
|
5480
5468
|
);
|
|
5469
|
+
const latestTokenBalance = latestBalanceInfo.balanceUnknown ? void 0 : latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
5470
|
+
const storedApiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
5471
|
+
if (storedApiKeyEntry?.key.startsWith("cashu") && latestBalanceInfo.apiKey) {
|
|
5472
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
5473
|
+
this.storageAdapter.setApiKey(baseUrl, latestBalanceInfo.apiKey);
|
|
5474
|
+
}
|
|
5475
|
+
if (latestTokenBalance !== void 0) {
|
|
5476
|
+
this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
|
|
5477
|
+
}
|
|
5478
|
+
satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
5481
5479
|
} catch (e) {
|
|
5482
|
-
this._log(
|
|
5483
|
-
|
|
5484
|
-
"Could not get current token balance for topup calculation:",
|
|
5485
|
-
e
|
|
5486
|
-
);
|
|
5480
|
+
this._log("WARN", "Could not get updated API key balance:", e);
|
|
5481
|
+
satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
5487
5482
|
}
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5483
|
+
}
|
|
5484
|
+
await this._trackResponseUsage({
|
|
5485
|
+
token,
|
|
5486
|
+
baseUrl,
|
|
5487
|
+
response,
|
|
5488
|
+
modelId,
|
|
5489
|
+
satsSpent,
|
|
5490
|
+
usage,
|
|
5491
|
+
requestId,
|
|
5492
|
+
clientApiKey
|
|
5493
|
+
});
|
|
5494
|
+
(async () => {
|
|
5495
|
+
})();
|
|
5496
|
+
return satsSpent;
|
|
5497
|
+
}
|
|
5498
|
+
async _trackResponseUsage(params) {
|
|
5499
|
+
const {
|
|
5500
|
+
token,
|
|
5501
|
+
baseUrl,
|
|
5502
|
+
response,
|
|
5503
|
+
modelId,
|
|
5504
|
+
satsSpent,
|
|
5505
|
+
usage: providedUsage,
|
|
5506
|
+
requestId: providedRequestId,
|
|
5507
|
+
clientApiKey
|
|
5508
|
+
} = params;
|
|
5509
|
+
if (!response || !modelId) {
|
|
5510
|
+
return;
|
|
5511
|
+
}
|
|
5512
|
+
try {
|
|
5513
|
+
let usage = providedUsage;
|
|
5514
|
+
let requestId = providedRequestId;
|
|
5515
|
+
if (!usage || !requestId) {
|
|
5516
|
+
const contentType = response.headers.get("content-type") || "";
|
|
5517
|
+
if (contentType.includes("text/event-stream")) {
|
|
5518
|
+
usage = usage ?? response.usage;
|
|
5519
|
+
requestId = requestId ?? response.requestId ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
5520
|
+
if (!usage) {
|
|
5521
|
+
return;
|
|
5522
|
+
}
|
|
5516
5523
|
} else {
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
);
|
|
5521
|
-
tryNextProvider = true;
|
|
5524
|
+
const cloned = response.clone();
|
|
5525
|
+
const responseBody = await cloned.json();
|
|
5526
|
+
usage = usage ?? extractUsageFromResponseBody(responseBody, satsSpent) ?? void 0;
|
|
5527
|
+
requestId = requestId ?? extractResponseId(responseBody) ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
5522
5528
|
}
|
|
5523
|
-
}
|
|
5529
|
+
}
|
|
5530
|
+
if (!usage) {
|
|
5531
|
+
return;
|
|
5532
|
+
}
|
|
5533
|
+
const finalRequestId = requestId || "unknown";
|
|
5534
|
+
const store = this.sdkStore ?? await getDefaultSdkStore();
|
|
5535
|
+
const state = store.getState();
|
|
5536
|
+
const matchKey = clientApiKey ?? token;
|
|
5537
|
+
const matchingClient = state.clientIds.find(
|
|
5538
|
+
(client) => client.apiKey === matchKey
|
|
5539
|
+
);
|
|
5540
|
+
const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
|
|
5541
|
+
const usageTracking = this.usageTrackingDriver ?? getDefaultUsageTrackingDriver();
|
|
5542
|
+
const entry = {
|
|
5543
|
+
id: entryId,
|
|
5544
|
+
timestamp: Date.now(),
|
|
5545
|
+
modelId,
|
|
5546
|
+
baseUrl,
|
|
5547
|
+
requestId: finalRequestId,
|
|
5548
|
+
client: matchingClient?.clientId,
|
|
5549
|
+
...usage
|
|
5550
|
+
};
|
|
5551
|
+
if (this.mode === "xcashu") {
|
|
5552
|
+
entry.satsCost = satsSpent;
|
|
5553
|
+
}
|
|
5554
|
+
await usageTracking.append(entry);
|
|
5555
|
+
} catch (error) {
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
/**
|
|
5559
|
+
* Check wallet balance and throw if insufficient
|
|
5560
|
+
*/
|
|
5561
|
+
async _checkBalance() {
|
|
5562
|
+
const balances = await this.walletAdapter.getBalances();
|
|
5563
|
+
const totalBalance = Object.values(balances).reduce((sum, v) => sum + v, 0);
|
|
5564
|
+
if (totalBalance <= 0) {
|
|
5565
|
+
throw new InsufficientBalanceError(1, 0);
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
/**
|
|
5569
|
+
* Spend a token using CashuSpender with standardized error handling
|
|
5570
|
+
*/
|
|
5571
|
+
async _spendToken(params) {
|
|
5572
|
+
const { mintUrl, amount, baseUrl } = params;
|
|
5573
|
+
this._log(
|
|
5574
|
+
"DEBUG",
|
|
5575
|
+
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
5576
|
+
);
|
|
5577
|
+
if (this.mode === "apikeys") {
|
|
5578
|
+
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
5579
|
+
if (!parentApiKey) {
|
|
5524
5580
|
this._log(
|
|
5525
5581
|
"DEBUG",
|
|
5526
|
-
`[RoutstrClient]
|
|
5582
|
+
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
5527
5583
|
);
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5584
|
+
const spendResult2 = await this.cashuSpender.spend({
|
|
5585
|
+
mintUrl,
|
|
5586
|
+
amount: amount * TOPUP_MARGIN,
|
|
5587
|
+
baseUrl: "",
|
|
5588
|
+
reuseToken: false
|
|
5589
|
+
});
|
|
5590
|
+
if (!spendResult2.token) {
|
|
5531
5591
|
this._log(
|
|
5532
|
-
"
|
|
5533
|
-
`[RoutstrClient]
|
|
5592
|
+
"ERROR",
|
|
5593
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
5594
|
+
spendResult2.error
|
|
5595
|
+
);
|
|
5596
|
+
throw new Error(
|
|
5597
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
5534
5598
|
);
|
|
5535
|
-
return this._makeRequest({
|
|
5536
|
-
...params,
|
|
5537
|
-
token: params.token,
|
|
5538
|
-
headers: this._withAuthHeader(params.baseHeaders, params.token),
|
|
5539
|
-
retryCount: retryCount + 1
|
|
5540
|
-
});
|
|
5541
5599
|
} else {
|
|
5542
5600
|
this._log(
|
|
5543
5601
|
"DEBUG",
|
|
5544
|
-
`[RoutstrClient]
|
|
5602
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
5545
5603
|
);
|
|
5546
|
-
tryNextProvider = true;
|
|
5547
5604
|
}
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
if (isInsufficientBalance413 && !tryNextProvider && this.mode === "apikeys") {
|
|
5552
|
-
let retryToken = params.token;
|
|
5553
|
-
try {
|
|
5554
|
-
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5555
|
-
params.token,
|
|
5556
|
-
baseUrl
|
|
5605
|
+
this._log(
|
|
5606
|
+
"DEBUG",
|
|
5607
|
+
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
5557
5608
|
);
|
|
5558
|
-
|
|
5559
|
-
this.
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5609
|
+
try {
|
|
5610
|
+
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
5611
|
+
} catch (error) {
|
|
5612
|
+
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
5613
|
+
const receiveResult = await this.cashuSpender.receiveToken(
|
|
5614
|
+
spendResult2.token
|
|
5615
|
+
);
|
|
5616
|
+
if (receiveResult.success) {
|
|
5617
|
+
this._log(
|
|
5618
|
+
"DEBUG",
|
|
5619
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
5620
|
+
);
|
|
5621
|
+
} else {
|
|
5622
|
+
this._log(
|
|
5623
|
+
"DEBUG",
|
|
5624
|
+
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
5625
|
+
);
|
|
5574
5626
|
}
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
this.storageAdapter.updateApiKeyBalance(
|
|
5579
|
-
baseUrl,
|
|
5580
|
-
latestTokenBalance
|
|
5627
|
+
this._log(
|
|
5628
|
+
"DEBUG",
|
|
5629
|
+
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
5581
5630
|
);
|
|
5631
|
+
} else {
|
|
5632
|
+
throw error;
|
|
5582
5633
|
}
|
|
5583
5634
|
}
|
|
5584
|
-
|
|
5585
|
-
this._log(
|
|
5586
|
-
"WARN",
|
|
5587
|
-
`[RoutstrClient] _handleErrorResponse: Failed to refresh API key after 413 insufficient balance for ${baseUrl}`,
|
|
5588
|
-
error
|
|
5589
|
-
);
|
|
5590
|
-
}
|
|
5591
|
-
if (retryCount < MAX_RETRIES_PER_PROVIDER) {
|
|
5592
|
-
this._log(
|
|
5593
|
-
"DEBUG",
|
|
5594
|
-
`[RoutstrClient] _handleErrorResponse: Retrying 413 (attempt ${retryCount + 1}/${MAX_RETRIES_PER_PROVIDER})`
|
|
5595
|
-
);
|
|
5596
|
-
return this._makeRequest({
|
|
5597
|
-
...params,
|
|
5598
|
-
token: retryToken,
|
|
5599
|
-
headers: this._withAuthHeader(params.baseHeaders, retryToken),
|
|
5600
|
-
retryCount: retryCount + 1
|
|
5601
|
-
});
|
|
5635
|
+
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
5602
5636
|
} else {
|
|
5603
5637
|
this._log(
|
|
5604
5638
|
"DEBUG",
|
|
5605
|
-
`[RoutstrClient]
|
|
5639
|
+
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
5606
5640
|
);
|
|
5607
|
-
tryNextProvider = true;
|
|
5608
5641
|
}
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5616
|
-
token,
|
|
5617
|
-
baseUrl
|
|
5642
|
+
let tokenBalance = 0;
|
|
5643
|
+
let tokenBalanceUnit = "sat";
|
|
5644
|
+
let tokenBalanceUnknown = false;
|
|
5645
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
5646
|
+
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
5647
|
+
(d) => d.baseUrl === baseUrl
|
|
5618
5648
|
);
|
|
5619
|
-
if (
|
|
5620
|
-
|
|
5621
|
-
tryNextProvider = true;
|
|
5649
|
+
if (distributionForBaseUrl) {
|
|
5650
|
+
tokenBalance = distributionForBaseUrl.amount;
|
|
5622
5651
|
}
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
);
|
|
5629
|
-
if (this.mode === "apikeys") {
|
|
5630
|
-
this._log(
|
|
5631
|
-
"DEBUG",
|
|
5632
|
-
`[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
|
|
5633
|
-
);
|
|
5634
|
-
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5635
|
-
token,
|
|
5636
|
-
baseUrl
|
|
5637
|
-
);
|
|
5638
|
-
this._log(
|
|
5639
|
-
"DEBUG",
|
|
5640
|
-
`[RoutstrClient] _handleErrorResponse: Initial API key balance: ${latestBalanceInfo.amount}`
|
|
5641
|
-
);
|
|
5642
|
-
const refundResult = await this.balanceManager.refundApiKey({
|
|
5643
|
-
mintUrl,
|
|
5644
|
-
baseUrl,
|
|
5645
|
-
apiKey: token,
|
|
5646
|
-
forceRefund: true
|
|
5647
|
-
});
|
|
5648
|
-
this._log(
|
|
5649
|
-
"DEBUG",
|
|
5650
|
-
`[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
|
|
5651
|
-
);
|
|
5652
|
-
if (!refundResult.success && latestBalanceInfo.amount > 0) {
|
|
5653
|
-
throw new ProviderError(
|
|
5654
|
-
baseUrl,
|
|
5655
|
-
status,
|
|
5656
|
-
refundResult.message ?? "Unknown error"
|
|
5652
|
+
if (tokenBalance === 0 && parentApiKey) {
|
|
5653
|
+
try {
|
|
5654
|
+
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
5655
|
+
parentApiKey.key,
|
|
5656
|
+
baseUrl
|
|
5657
5657
|
);
|
|
5658
|
+
tokenBalance = balanceInfo.amount;
|
|
5659
|
+
tokenBalanceUnit = balanceInfo.unit;
|
|
5660
|
+
tokenBalanceUnknown = Boolean(balanceInfo.balanceUnknown);
|
|
5661
|
+
} catch (e) {
|
|
5662
|
+
this._log("WARN", "Could not get initial API key balance:", e);
|
|
5658
5663
|
}
|
|
5659
5664
|
}
|
|
5665
|
+
this._log(
|
|
5666
|
+
"DEBUG",
|
|
5667
|
+
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
5668
|
+
);
|
|
5669
|
+
return {
|
|
5670
|
+
token: parentApiKey?.key ?? "",
|
|
5671
|
+
tokenBalance,
|
|
5672
|
+
tokenBalanceUnit,
|
|
5673
|
+
tokenBalanceUnknown
|
|
5674
|
+
};
|
|
5660
5675
|
}
|
|
5661
|
-
this.providerManager.markFailed(baseUrl);
|
|
5662
5676
|
this._log(
|
|
5663
5677
|
"DEBUG",
|
|
5664
|
-
`[RoutstrClient]
|
|
5665
|
-
);
|
|
5666
|
-
if (!selectedModel) {
|
|
5667
|
-
throw new ProviderError(
|
|
5668
|
-
baseUrl,
|
|
5669
|
-
status,
|
|
5670
|
-
"Funny, no selected model. HMM. "
|
|
5671
|
-
);
|
|
5672
|
-
}
|
|
5673
|
-
const nextProvider = this.providerManager.findNextBestProvider(
|
|
5674
|
-
selectedModel.id,
|
|
5675
|
-
baseUrl
|
|
5678
|
+
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
5676
5679
|
);
|
|
5677
|
-
|
|
5680
|
+
const spendResult = await this.cashuSpender.spend({
|
|
5681
|
+
mintUrl,
|
|
5682
|
+
amount,
|
|
5683
|
+
baseUrl: "",
|
|
5684
|
+
reuseToken: false
|
|
5685
|
+
});
|
|
5686
|
+
if (!spendResult.token) {
|
|
5678
5687
|
this._log(
|
|
5679
|
-
"
|
|
5680
|
-
`[RoutstrClient]
|
|
5681
|
-
|
|
5682
|
-
const newModel = await this.providerManager.getModelForProvider(
|
|
5683
|
-
nextProvider,
|
|
5684
|
-
selectedModel.id
|
|
5685
|
-
) ?? selectedModel;
|
|
5686
|
-
const messagesForPricing = Array.isArray(
|
|
5687
|
-
body?.messages
|
|
5688
|
-
) ? body.messages : [];
|
|
5689
|
-
const newRequiredSats = this.providerManager.getRequiredSatsForModel(
|
|
5690
|
-
newModel,
|
|
5691
|
-
messagesForPricing,
|
|
5692
|
-
params.maxTokens
|
|
5688
|
+
"ERROR",
|
|
5689
|
+
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
5690
|
+
spendResult.error
|
|
5693
5691
|
);
|
|
5692
|
+
} else {
|
|
5694
5693
|
this._log(
|
|
5695
5694
|
"DEBUG",
|
|
5696
|
-
`[RoutstrClient]
|
|
5695
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
5697
5696
|
);
|
|
5698
|
-
|
|
5699
|
-
mintUrl,
|
|
5700
|
-
amount: newRequiredSats,
|
|
5701
|
-
baseUrl: nextProvider
|
|
5702
|
-
});
|
|
5703
|
-
return this._makeRequest({
|
|
5704
|
-
...params,
|
|
5705
|
-
path,
|
|
5706
|
-
method,
|
|
5707
|
-
body,
|
|
5708
|
-
baseUrl: nextProvider,
|
|
5709
|
-
selectedModel: newModel,
|
|
5710
|
-
token: spendResult.token,
|
|
5711
|
-
requiredSats: newRequiredSats,
|
|
5712
|
-
headers: this._withAuthHeader(params.baseHeaders, spendResult.token),
|
|
5713
|
-
retryCount: 0
|
|
5714
|
-
});
|
|
5697
|
+
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
5715
5698
|
}
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5699
|
+
return {
|
|
5700
|
+
token: spendResult.token,
|
|
5701
|
+
tokenBalance: spendResult.balance,
|
|
5702
|
+
tokenBalanceUnit: spendResult.unit ?? "sat",
|
|
5703
|
+
tokenBalanceUnknown: false
|
|
5704
|
+
};
|
|
5705
|
+
}
|
|
5706
|
+
/**
|
|
5707
|
+
* Build request headers with common defaults and dev mock controls
|
|
5708
|
+
*/
|
|
5709
|
+
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
5710
|
+
const headers = {
|
|
5711
|
+
...additionalHeaders,
|
|
5712
|
+
"Content-Type": "application/json"
|
|
5713
|
+
};
|
|
5714
|
+
return headers;
|
|
5715
|
+
}
|
|
5716
|
+
/**
|
|
5717
|
+
* Attach auth headers using the active client mode
|
|
5718
|
+
*/
|
|
5719
|
+
_withAuthHeader(headers, token) {
|
|
5720
|
+
const nextHeaders = { ...headers };
|
|
5721
|
+
if (this.mode === "xcashu") {
|
|
5722
|
+
nextHeaders["X-Cashu"] = token;
|
|
5723
|
+
} else {
|
|
5724
|
+
nextHeaders["Authorization"] = `Bearer ${token}`;
|
|
5725
|
+
}
|
|
5726
|
+
return nextHeaders;
|
|
5720
5727
|
}
|
|
5728
|
+
};
|
|
5729
|
+
|
|
5730
|
+
// client/StreamProcessor.ts
|
|
5731
|
+
var StreamProcessor = class {
|
|
5732
|
+
accumulatedContent = "";
|
|
5733
|
+
accumulatedThinking = "";
|
|
5734
|
+
accumulatedImages = [];
|
|
5735
|
+
isInThinking = false;
|
|
5736
|
+
isInContent = false;
|
|
5721
5737
|
/**
|
|
5722
|
-
*
|
|
5738
|
+
* Process a streaming response
|
|
5723
5739
|
*/
|
|
5724
|
-
async
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
let
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
"ERROR",
|
|
5748
|
-
`[xcashu] Failed to receive refund token: ${receiveResult.message}`
|
|
5749
|
-
);
|
|
5740
|
+
async process(response, callbacks, modelId) {
|
|
5741
|
+
if (!response.body) {
|
|
5742
|
+
throw new Error("Response body is not available");
|
|
5743
|
+
}
|
|
5744
|
+
const reader = response.body.getReader();
|
|
5745
|
+
const decoder = new TextDecoder("utf-8");
|
|
5746
|
+
let buffer = "";
|
|
5747
|
+
this.accumulatedContent = "";
|
|
5748
|
+
this.accumulatedThinking = "";
|
|
5749
|
+
this.accumulatedImages = [];
|
|
5750
|
+
this.isInThinking = false;
|
|
5751
|
+
this.isInContent = false;
|
|
5752
|
+
let usage;
|
|
5753
|
+
let model;
|
|
5754
|
+
let finish_reason;
|
|
5755
|
+
let citations;
|
|
5756
|
+
let annotations;
|
|
5757
|
+
let responseId;
|
|
5758
|
+
try {
|
|
5759
|
+
while (true) {
|
|
5760
|
+
const { done, value } = await reader.read();
|
|
5761
|
+
if (done) {
|
|
5762
|
+
break;
|
|
5750
5763
|
}
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5764
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
5765
|
+
buffer += chunk;
|
|
5766
|
+
const lines = buffer.split("\n");
|
|
5767
|
+
buffer = lines.pop() || "";
|
|
5768
|
+
for (const line of lines) {
|
|
5769
|
+
const parsed = this._parseLine(line);
|
|
5770
|
+
if (!parsed) continue;
|
|
5771
|
+
if (parsed.content) {
|
|
5772
|
+
this._handleContent(parsed.content, callbacks, modelId);
|
|
5773
|
+
}
|
|
5774
|
+
if (parsed.reasoning) {
|
|
5775
|
+
this._handleThinking(parsed.reasoning, callbacks);
|
|
5776
|
+
}
|
|
5777
|
+
if (parsed.usage) {
|
|
5778
|
+
usage = parsed.usage;
|
|
5779
|
+
}
|
|
5780
|
+
if (parsed.model) {
|
|
5781
|
+
model = parsed.model;
|
|
5782
|
+
}
|
|
5783
|
+
if (parsed.finish_reason) {
|
|
5784
|
+
finish_reason = parsed.finish_reason;
|
|
5785
|
+
}
|
|
5786
|
+
if (parsed.responseId) {
|
|
5787
|
+
responseId = parsed.responseId;
|
|
5788
|
+
}
|
|
5789
|
+
if (parsed.citations) {
|
|
5790
|
+
citations = parsed.citations;
|
|
5791
|
+
}
|
|
5792
|
+
if (parsed.annotations) {
|
|
5793
|
+
annotations = parsed.annotations;
|
|
5794
|
+
}
|
|
5795
|
+
if (parsed.images) {
|
|
5796
|
+
this._mergeImages(parsed.images);
|
|
5797
|
+
}
|
|
5771
5798
|
}
|
|
5772
|
-
this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
|
|
5773
|
-
satsSpent = initialTokenBalance - latestTokenBalance;
|
|
5774
|
-
} catch (e) {
|
|
5775
|
-
this._log("WARN", "Could not get updated API key balance:", e);
|
|
5776
|
-
satsSpent = fallbackSatsSpent ?? initialTokenBalance;
|
|
5777
5799
|
}
|
|
5800
|
+
} finally {
|
|
5801
|
+
reader.releaseLock();
|
|
5778
5802
|
}
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
modelId,
|
|
5784
|
-
satsSpent,
|
|
5803
|
+
return {
|
|
5804
|
+
content: this.accumulatedContent,
|
|
5805
|
+
thinking: this.accumulatedThinking || void 0,
|
|
5806
|
+
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
5785
5807
|
usage,
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5808
|
+
model,
|
|
5809
|
+
responseId,
|
|
5810
|
+
finish_reason,
|
|
5811
|
+
citations,
|
|
5812
|
+
annotations
|
|
5813
|
+
};
|
|
5792
5814
|
}
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
if (!response || !modelId) {
|
|
5805
|
-
return;
|
|
5815
|
+
/**
|
|
5816
|
+
* Parse a single SSE line
|
|
5817
|
+
*/
|
|
5818
|
+
_parseLine(line) {
|
|
5819
|
+
if (!line.trim()) return null;
|
|
5820
|
+
if (!line.startsWith("data: ")) {
|
|
5821
|
+
return null;
|
|
5822
|
+
}
|
|
5823
|
+
const jsonData = line.slice(6);
|
|
5824
|
+
if (jsonData === "[DONE]") {
|
|
5825
|
+
return null;
|
|
5806
5826
|
}
|
|
5807
5827
|
try {
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
if (
|
|
5811
|
-
|
|
5812
|
-
if (contentType.includes("text/event-stream")) {
|
|
5813
|
-
usage = usage ?? response.usage;
|
|
5814
|
-
requestId = requestId ?? response.requestId ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
5815
|
-
if (!usage) {
|
|
5816
|
-
return;
|
|
5817
|
-
}
|
|
5818
|
-
} else {
|
|
5819
|
-
const cloned = response.clone();
|
|
5820
|
-
const responseBody = await cloned.json();
|
|
5821
|
-
usage = usage ?? extractUsageFromResponseBody(responseBody, satsSpent) ?? void 0;
|
|
5822
|
-
requestId = requestId ?? extractResponseId(responseBody) ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
5823
|
-
}
|
|
5828
|
+
const parsed = JSON.parse(jsonData);
|
|
5829
|
+
const result = {};
|
|
5830
|
+
if (parsed.choices?.[0]?.delta?.content) {
|
|
5831
|
+
result.content = parsed.choices[0].delta.content;
|
|
5824
5832
|
}
|
|
5825
|
-
if (
|
|
5826
|
-
|
|
5833
|
+
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
5834
|
+
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
5827
5835
|
}
|
|
5828
|
-
const
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
const entry = {
|
|
5838
|
-
id: entryId,
|
|
5839
|
-
timestamp: Date.now(),
|
|
5840
|
-
modelId,
|
|
5841
|
-
baseUrl,
|
|
5842
|
-
requestId: finalRequestId,
|
|
5843
|
-
client: matchingClient?.clientId,
|
|
5844
|
-
...usage
|
|
5845
|
-
};
|
|
5846
|
-
if (this.mode === "xcashu") {
|
|
5847
|
-
entry.satsCost = satsSpent;
|
|
5836
|
+
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
5837
|
+
if (extractedUsage) {
|
|
5838
|
+
result.usage = toUsageStats(extractedUsage);
|
|
5839
|
+
} else if (parsed.usage) {
|
|
5840
|
+
result.usage = {
|
|
5841
|
+
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
5842
|
+
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
5843
|
+
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
5844
|
+
};
|
|
5848
5845
|
}
|
|
5849
|
-
|
|
5850
|
-
|
|
5846
|
+
if (parsed.id) {
|
|
5847
|
+
result.responseId = parsed.id;
|
|
5848
|
+
}
|
|
5849
|
+
if (parsed.model) {
|
|
5850
|
+
result.model = parsed.model;
|
|
5851
|
+
}
|
|
5852
|
+
if (parsed.citations) {
|
|
5853
|
+
result.citations = parsed.citations;
|
|
5854
|
+
}
|
|
5855
|
+
if (parsed.annotations) {
|
|
5856
|
+
result.annotations = parsed.annotations;
|
|
5857
|
+
}
|
|
5858
|
+
if (parsed.choices?.[0]?.finish_reason) {
|
|
5859
|
+
result.finish_reason = parsed.choices[0].finish_reason;
|
|
5860
|
+
}
|
|
5861
|
+
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
5862
|
+
if (images && Array.isArray(images)) {
|
|
5863
|
+
result.images = images;
|
|
5864
|
+
}
|
|
5865
|
+
return result;
|
|
5866
|
+
} catch {
|
|
5867
|
+
return null;
|
|
5851
5868
|
}
|
|
5852
5869
|
}
|
|
5853
5870
|
/**
|
|
5854
|
-
*
|
|
5855
|
-
*/
|
|
5856
|
-
async _convertMessages(messages) {
|
|
5857
|
-
return Promise.all(
|
|
5858
|
-
messages.filter((m) => m.role !== "system").map(async (m) => ({
|
|
5859
|
-
role: m.role,
|
|
5860
|
-
content: typeof m.content === "string" ? m.content : m.content
|
|
5861
|
-
}))
|
|
5862
|
-
);
|
|
5863
|
-
}
|
|
5864
|
-
/**
|
|
5865
|
-
* Create assistant message from streaming result
|
|
5871
|
+
* Handle content delta with thinking support
|
|
5866
5872
|
*/
|
|
5867
|
-
|
|
5868
|
-
if (
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
}
|
|
5879
|
-
for (const img of result.images) {
|
|
5880
|
-
content.push({
|
|
5881
|
-
type: "image_url",
|
|
5882
|
-
image_url: {
|
|
5883
|
-
url: img.image_url.url
|
|
5884
|
-
}
|
|
5885
|
-
});
|
|
5886
|
-
}
|
|
5887
|
-
return {
|
|
5888
|
-
role: "assistant",
|
|
5889
|
-
content
|
|
5890
|
-
};
|
|
5873
|
+
_handleContent(content, callbacks, modelId) {
|
|
5874
|
+
if (this.isInThinking && !this.isInContent) {
|
|
5875
|
+
this.accumulatedThinking += "</thinking>";
|
|
5876
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
5877
|
+
this.isInThinking = false;
|
|
5878
|
+
this.isInContent = true;
|
|
5879
|
+
}
|
|
5880
|
+
if (modelId) {
|
|
5881
|
+
this._extractThinkingFromContent(content, callbacks);
|
|
5882
|
+
} else {
|
|
5883
|
+
this.accumulatedContent += content;
|
|
5891
5884
|
}
|
|
5892
|
-
|
|
5893
|
-
role: "assistant",
|
|
5894
|
-
content: result.content || ""
|
|
5895
|
-
};
|
|
5885
|
+
callbacks.onContent(this.accumulatedContent);
|
|
5896
5886
|
}
|
|
5897
5887
|
/**
|
|
5898
|
-
*
|
|
5888
|
+
* Handle thinking/reasoning content
|
|
5899
5889
|
*/
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
if (completion_tokens !== void 0 && prompt_tokens !== void 0) {
|
|
5905
|
-
estimatedCosts = (selectedModel.sats_pricing?.completion ?? 0) * completion_tokens + (selectedModel.sats_pricing?.prompt ?? 0) * prompt_tokens;
|
|
5906
|
-
}
|
|
5890
|
+
_handleThinking(reasoning, callbacks) {
|
|
5891
|
+
if (!this.isInThinking) {
|
|
5892
|
+
this.accumulatedThinking += "<thinking> ";
|
|
5893
|
+
this.isInThinking = true;
|
|
5907
5894
|
}
|
|
5908
|
-
|
|
5895
|
+
this.accumulatedThinking += reasoning;
|
|
5896
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
5909
5897
|
}
|
|
5910
5898
|
/**
|
|
5911
|
-
*
|
|
5899
|
+
* Extract thinking blocks from content (for models with inline thinking)
|
|
5912
5900
|
*/
|
|
5913
|
-
|
|
5914
|
-
const
|
|
5915
|
-
|
|
5901
|
+
_extractThinkingFromContent(content, callbacks) {
|
|
5902
|
+
const parts = content.split(/(<thinking>|<\/thinking>)/);
|
|
5903
|
+
for (const part of parts) {
|
|
5904
|
+
if (part === "<thinking>") {
|
|
5905
|
+
this.isInThinking = true;
|
|
5906
|
+
if (!this.accumulatedThinking.includes("<thinking>")) {
|
|
5907
|
+
this.accumulatedThinking += "<thinking> ";
|
|
5908
|
+
}
|
|
5909
|
+
} else if (part === "</thinking>") {
|
|
5910
|
+
this.isInThinking = false;
|
|
5911
|
+
this.accumulatedThinking += "</thinking>";
|
|
5912
|
+
} else if (this.isInThinking) {
|
|
5913
|
+
this.accumulatedThinking += part;
|
|
5914
|
+
} else {
|
|
5915
|
+
this.accumulatedContent += part;
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5916
5918
|
}
|
|
5917
5919
|
/**
|
|
5918
|
-
*
|
|
5920
|
+
* Merge images into accumulated array, avoiding duplicates
|
|
5919
5921
|
*/
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
const
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5922
|
+
_mergeImages(newImages) {
|
|
5923
|
+
for (const img of newImages) {
|
|
5924
|
+
const newUrl = img.image_url?.url;
|
|
5925
|
+
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
5926
|
+
const existingUrl = existing.image_url?.url;
|
|
5927
|
+
if (newUrl && existingUrl) {
|
|
5928
|
+
return existingUrl === newUrl;
|
|
5929
|
+
}
|
|
5930
|
+
if (img.index !== void 0 && existing.index !== void 0) {
|
|
5931
|
+
return existing.index === img.index;
|
|
5932
|
+
}
|
|
5933
|
+
return false;
|
|
5934
|
+
});
|
|
5935
|
+
if (existingIndex === -1) {
|
|
5936
|
+
this.accumulatedImages.push(img);
|
|
5937
|
+
} else {
|
|
5938
|
+
this.accumulatedImages[existingIndex] = img;
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
};
|
|
5943
|
+
|
|
5944
|
+
// client/fetchAIResponse.ts
|
|
5945
|
+
async function fetchAIResponse(options, callbacks, deps) {
|
|
5946
|
+
const {
|
|
5947
|
+
messageHistory,
|
|
5948
|
+
selectedModel,
|
|
5949
|
+
baseUrl,
|
|
5950
|
+
mintUrl,
|
|
5951
|
+
maxTokens,
|
|
5952
|
+
headers
|
|
5953
|
+
} = options;
|
|
5954
|
+
try {
|
|
5955
|
+
const apiMessages = await convertMessages(messageHistory);
|
|
5956
|
+
callbacks.onPaymentProcessing?.(true);
|
|
5957
|
+
callbacks.onTokenCreated?.(deps.getPendingCashuTokenAmount?.() ?? 0);
|
|
5958
|
+
const providerInfo = await deps.providerRegistry.getProviderInfo(baseUrl);
|
|
5959
|
+
const providerVersion = providerInfo?.version ?? "";
|
|
5960
|
+
let modelIdForRequest = selectedModel.id;
|
|
5961
|
+
if (/^0\.1\./.test(providerVersion)) {
|
|
5962
|
+
const newModel = await deps.client.getProviderManager().getModelForProvider(baseUrl, selectedModel.id);
|
|
5963
|
+
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
5964
|
+
}
|
|
5965
|
+
const body = {
|
|
5966
|
+
model: modelIdForRequest,
|
|
5967
|
+
messages: apiMessages,
|
|
5968
|
+
stream: true
|
|
5969
|
+
};
|
|
5970
|
+
if (maxTokens !== void 0) {
|
|
5971
|
+
body.max_tokens = maxTokens;
|
|
5972
|
+
}
|
|
5973
|
+
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
5974
|
+
body.tools = [{ type: "web_search" }];
|
|
5975
|
+
}
|
|
5976
|
+
const response = await deps.client.routeRequest({
|
|
5977
|
+
path: "/v1/chat/completions",
|
|
5978
|
+
method: "POST",
|
|
5979
|
+
body,
|
|
5980
|
+
headers,
|
|
5981
|
+
baseUrl,
|
|
5982
|
+
mintUrl,
|
|
5983
|
+
modelId: selectedModel.id
|
|
5984
|
+
});
|
|
5985
|
+
if (!response.body) {
|
|
5986
|
+
throw new Error("Response body is not available");
|
|
5987
|
+
}
|
|
5988
|
+
if (response.status !== 200) {
|
|
5989
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
5990
|
+
}
|
|
5991
|
+
const streamProcessor = new StreamProcessor();
|
|
5992
|
+
const streamingResult = await streamProcessor.process(
|
|
5993
|
+
response,
|
|
5994
|
+
{
|
|
5995
|
+
onContent: callbacks.onStreamingUpdate,
|
|
5996
|
+
onThinking: callbacks.onThinkingUpdate
|
|
5997
|
+
},
|
|
5998
|
+
selectedModel.id
|
|
5999
|
+
);
|
|
6000
|
+
if (streamingResult.finish_reason === "content_filter") {
|
|
5929
6001
|
callbacks.onMessageAppend({
|
|
5930
|
-
role: "
|
|
5931
|
-
content: "
|
|
6002
|
+
role: "assistant",
|
|
6003
|
+
content: "Your request was denied due to content filtering."
|
|
5932
6004
|
});
|
|
6005
|
+
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
6006
|
+
const message = await createAssistantMessage(streamingResult);
|
|
6007
|
+
callbacks.onMessageAppend(message);
|
|
5933
6008
|
} else {
|
|
5934
6009
|
callbacks.onMessageAppend({
|
|
5935
6010
|
role: "system",
|
|
5936
|
-
content: "
|
|
6011
|
+
content: "The provider did not respond to this request."
|
|
5937
6012
|
});
|
|
5938
6013
|
}
|
|
6014
|
+
callbacks.onStreamingUpdate("");
|
|
6015
|
+
callbacks.onThinkingUpdate("");
|
|
6016
|
+
} catch (error) {
|
|
6017
|
+
handleError(error, callbacks, deps.alertLevel, deps.logger);
|
|
6018
|
+
} finally {
|
|
6019
|
+
callbacks.onPaymentProcessing?.(false);
|
|
5939
6020
|
}
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
6021
|
+
}
|
|
6022
|
+
async function convertMessages(messages) {
|
|
6023
|
+
return Promise.all(
|
|
6024
|
+
messages.filter((m) => m.role !== "system").map(async (m) => ({
|
|
6025
|
+
role: m.role,
|
|
6026
|
+
content: typeof m.content === "string" ? m.content : m.content
|
|
6027
|
+
}))
|
|
6028
|
+
);
|
|
6029
|
+
}
|
|
6030
|
+
async function createAssistantMessage(result) {
|
|
6031
|
+
if (result.images && result.images.length > 0) {
|
|
6032
|
+
const content = [];
|
|
6033
|
+
if (result.content) {
|
|
6034
|
+
content.push({
|
|
6035
|
+
type: "text",
|
|
6036
|
+
text: result.content,
|
|
6037
|
+
thinking: result.thinking,
|
|
6038
|
+
citations: result.citations,
|
|
6039
|
+
annotations: result.annotations
|
|
6040
|
+
});
|
|
5948
6041
|
}
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
const { mintUrl, amount, baseUrl } = params;
|
|
5955
|
-
this._log(
|
|
5956
|
-
"DEBUG",
|
|
5957
|
-
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
5958
|
-
);
|
|
5959
|
-
if (this.mode === "apikeys") {
|
|
5960
|
-
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
5961
|
-
if (!parentApiKey) {
|
|
5962
|
-
this._log(
|
|
5963
|
-
"DEBUG",
|
|
5964
|
-
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
5965
|
-
);
|
|
5966
|
-
const spendResult2 = await this.cashuSpender.spend({
|
|
5967
|
-
mintUrl,
|
|
5968
|
-
amount: amount * TOPUP_MARGIN,
|
|
5969
|
-
baseUrl: "",
|
|
5970
|
-
reuseToken: false
|
|
5971
|
-
});
|
|
5972
|
-
if (!spendResult2.token) {
|
|
5973
|
-
this._log(
|
|
5974
|
-
"ERROR",
|
|
5975
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
5976
|
-
spendResult2.error
|
|
5977
|
-
);
|
|
5978
|
-
throw new Error(
|
|
5979
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
5980
|
-
);
|
|
5981
|
-
} else {
|
|
5982
|
-
this._log(
|
|
5983
|
-
"DEBUG",
|
|
5984
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
5985
|
-
);
|
|
5986
|
-
}
|
|
5987
|
-
this._log(
|
|
5988
|
-
"DEBUG",
|
|
5989
|
-
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
5990
|
-
);
|
|
5991
|
-
try {
|
|
5992
|
-
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
5993
|
-
} catch (error) {
|
|
5994
|
-
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
5995
|
-
const receiveResult = await this.cashuSpender.receiveToken(
|
|
5996
|
-
spendResult2.token
|
|
5997
|
-
);
|
|
5998
|
-
if (receiveResult.success) {
|
|
5999
|
-
this._log(
|
|
6000
|
-
"DEBUG",
|
|
6001
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
6002
|
-
);
|
|
6003
|
-
} else {
|
|
6004
|
-
this._log(
|
|
6005
|
-
"DEBUG",
|
|
6006
|
-
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
6007
|
-
);
|
|
6008
|
-
}
|
|
6009
|
-
this._log(
|
|
6010
|
-
"DEBUG",
|
|
6011
|
-
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
6012
|
-
);
|
|
6013
|
-
} else {
|
|
6014
|
-
throw error;
|
|
6015
|
-
}
|
|
6016
|
-
}
|
|
6017
|
-
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
6018
|
-
} else {
|
|
6019
|
-
this._log(
|
|
6020
|
-
"DEBUG",
|
|
6021
|
-
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
6022
|
-
);
|
|
6023
|
-
}
|
|
6024
|
-
let tokenBalance = 0;
|
|
6025
|
-
let tokenBalanceUnit = "sat";
|
|
6026
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
6027
|
-
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
6028
|
-
(d) => d.baseUrl === baseUrl
|
|
6029
|
-
);
|
|
6030
|
-
if (distributionForBaseUrl) {
|
|
6031
|
-
tokenBalance = distributionForBaseUrl.amount;
|
|
6032
|
-
}
|
|
6033
|
-
if (tokenBalance === 0 && parentApiKey) {
|
|
6034
|
-
try {
|
|
6035
|
-
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
6036
|
-
parentApiKey.key,
|
|
6037
|
-
baseUrl
|
|
6038
|
-
);
|
|
6039
|
-
tokenBalance = balanceInfo.amount;
|
|
6040
|
-
tokenBalanceUnit = balanceInfo.unit;
|
|
6041
|
-
} catch (e) {
|
|
6042
|
-
this._log("WARN", "Could not get initial API key balance:", e);
|
|
6042
|
+
for (const img of result.images) {
|
|
6043
|
+
content.push({
|
|
6044
|
+
type: "image_url",
|
|
6045
|
+
image_url: {
|
|
6046
|
+
url: img.image_url.url
|
|
6043
6047
|
}
|
|
6044
|
-
}
|
|
6045
|
-
this._log(
|
|
6046
|
-
"DEBUG",
|
|
6047
|
-
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
6048
|
-
);
|
|
6049
|
-
return {
|
|
6050
|
-
token: parentApiKey?.key ?? "",
|
|
6051
|
-
tokenBalance,
|
|
6052
|
-
tokenBalanceUnit
|
|
6053
|
-
};
|
|
6054
|
-
}
|
|
6055
|
-
this._log(
|
|
6056
|
-
"DEBUG",
|
|
6057
|
-
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
6058
|
-
);
|
|
6059
|
-
const spendResult = await this.cashuSpender.spend({
|
|
6060
|
-
mintUrl,
|
|
6061
|
-
amount,
|
|
6062
|
-
baseUrl: "",
|
|
6063
|
-
reuseToken: false
|
|
6064
|
-
});
|
|
6065
|
-
if (!spendResult.token) {
|
|
6066
|
-
this._log(
|
|
6067
|
-
"ERROR",
|
|
6068
|
-
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
6069
|
-
spendResult.error
|
|
6070
|
-
);
|
|
6071
|
-
} else {
|
|
6072
|
-
this._log(
|
|
6073
|
-
"DEBUG",
|
|
6074
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
6075
|
-
);
|
|
6076
|
-
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
6048
|
+
});
|
|
6077
6049
|
}
|
|
6078
6050
|
return {
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
tokenBalanceUnit: spendResult.unit ?? "sat"
|
|
6082
|
-
};
|
|
6083
|
-
}
|
|
6084
|
-
/**
|
|
6085
|
-
* Build request headers with common defaults and dev mock controls
|
|
6086
|
-
*/
|
|
6087
|
-
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
6088
|
-
const headers = {
|
|
6089
|
-
...additionalHeaders,
|
|
6090
|
-
"Content-Type": "application/json"
|
|
6051
|
+
role: "assistant",
|
|
6052
|
+
content
|
|
6091
6053
|
};
|
|
6092
|
-
return headers;
|
|
6093
6054
|
}
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6055
|
+
return {
|
|
6056
|
+
role: "assistant",
|
|
6057
|
+
content: result.content || ""
|
|
6058
|
+
};
|
|
6059
|
+
}
|
|
6060
|
+
function handleError(error, callbacks, alertLevel, logger) {
|
|
6061
|
+
logger.error("[fetchAIResponse] Error occurred", error);
|
|
6062
|
+
if (error instanceof Error) {
|
|
6063
|
+
const isStreamError = error.message.includes("Error in input stream") || error.message.includes("Load failed");
|
|
6064
|
+
const modifiedErrorMsg = isStreamError ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
|
|
6065
|
+
logger.error(
|
|
6066
|
+
`[fetchAIResponse] Error type=${error.constructor.name}, message=${modifiedErrorMsg}, isStreamError=${isStreamError}`
|
|
6067
|
+
);
|
|
6068
|
+
callbacks.onMessageAppend({
|
|
6069
|
+
role: "system",
|
|
6070
|
+
content: "Uncaught Error: " + modifiedErrorMsg + (alertLevel === "max" ? " | " + error.stack : "")
|
|
6071
|
+
});
|
|
6072
|
+
} else {
|
|
6073
|
+
callbacks.onMessageAppend({
|
|
6074
|
+
role: "system",
|
|
6075
|
+
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
6076
|
+
});
|
|
6105
6077
|
}
|
|
6106
|
-
}
|
|
6078
|
+
}
|
|
6107
6079
|
|
|
6108
6080
|
// routeRequests.ts
|
|
6109
6081
|
async function resolveRouteRequestContext(options) {
|
|
@@ -6274,19 +6246,18 @@ exports.StreamProcessor = StreamProcessor;
|
|
|
6274
6246
|
exports.StreamingError = StreamingError;
|
|
6275
6247
|
exports.TokenOperationError = TokenOperationError;
|
|
6276
6248
|
exports.consoleLogger = consoleLogger;
|
|
6277
|
-
exports.createBunSqliteDriver = createBunSqliteDriver;
|
|
6278
|
-
exports.createBunSqliteUsageTrackingDriver = createBunSqliteUsageTrackingDriver;
|
|
6279
6249
|
exports.createDiscoveryAdapterFromStore = createDiscoveryAdapterFromStore;
|
|
6280
6250
|
exports.createIndexedDBDriver = createIndexedDBDriver;
|
|
6281
6251
|
exports.createIndexedDBUsageTrackingDriver = createIndexedDBUsageTrackingDriver;
|
|
6282
6252
|
exports.createMemoryDriver = createMemoryDriver;
|
|
6283
6253
|
exports.createMemoryUsageTrackingDriver = createMemoryUsageTrackingDriver;
|
|
6254
|
+
exports.createProviderRegistryFromDiscoveryAdapter = createProviderRegistryFromDiscoveryAdapter;
|
|
6284
6255
|
exports.createProviderRegistryFromStore = createProviderRegistryFromStore;
|
|
6285
6256
|
exports.createSSEParserTransform = createSSEParserTransform;
|
|
6286
6257
|
exports.createSdkStore = createSdkStore;
|
|
6287
|
-
exports.
|
|
6288
|
-
exports.createSqliteUsageTrackingDriver = createSqliteUsageTrackingDriver;
|
|
6258
|
+
exports.createShardedDiscoveryAdapter = createShardedDiscoveryAdapter;
|
|
6289
6259
|
exports.createStorageAdapterFromStore = createStorageAdapterFromStore;
|
|
6260
|
+
exports.fetchAIResponse = fetchAIResponse;
|
|
6290
6261
|
exports.filterBaseUrlsForTor = filterBaseUrlsForTor;
|
|
6291
6262
|
exports.getDefaultDiscoveryAdapter = getDefaultDiscoveryAdapter;
|
|
6292
6263
|
exports.getDefaultProviderRegistry = getDefaultProviderRegistry;
|