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