@routstr/sdk 0.3.9 → 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 +801 -1291
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +801 -1292
- package/dist/client/index.mjs.map +1 -1
- package/dist/discovery/index.d.mts +33 -3
- package/dist/discovery/index.d.ts +33 -3
- package/dist/discovery/index.js +28 -21
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +28 -21
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1045 -1564
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1045 -1561
- 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 +4 -30
- package/dist/storage/index.d.ts +4 -30
- package/dist/storage/index.js +139 -650
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +140 -647
- 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/package.json +26 -1
package/dist/index.js
CHANGED
|
@@ -139,9 +139,6 @@ var MintDiscoveryError = class extends Error {
|
|
|
139
139
|
}
|
|
140
140
|
baseUrl;
|
|
141
141
|
};
|
|
142
|
-
function isBunRuntime() {
|
|
143
|
-
return typeof Bun !== "undefined";
|
|
144
|
-
}
|
|
145
142
|
var ModelManager = class _ModelManager {
|
|
146
143
|
constructor(adapter, config = {}) {
|
|
147
144
|
this.adapter = adapter;
|
|
@@ -150,8 +147,10 @@ var ModelManager = class _ModelManager {
|
|
|
150
147
|
this.includeProviderUrls = config.includeProviderUrls || [];
|
|
151
148
|
this.excludeProviderUrls = config.excludeProviderUrls || [];
|
|
152
149
|
this.routstrPubkey = config.routstrPubkey || "4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8";
|
|
150
|
+
this.nostrRelays = config.nostrRelays;
|
|
153
151
|
this.logger = (config.logger ?? consoleLogger).child("ModelManager");
|
|
154
152
|
this.eventStoreDbPath = config.eventStoreDbPath;
|
|
153
|
+
this.persistentEventDatabaseFactory = config.persistentEventDatabaseFactory;
|
|
155
154
|
}
|
|
156
155
|
adapter;
|
|
157
156
|
cacheTTL;
|
|
@@ -159,6 +158,7 @@ var ModelManager = class _ModelManager {
|
|
|
159
158
|
includeProviderUrls;
|
|
160
159
|
excludeProviderUrls;
|
|
161
160
|
routstrPubkey;
|
|
161
|
+
nostrRelays;
|
|
162
162
|
logger;
|
|
163
163
|
providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
|
|
164
164
|
/** Persistent event store for relay-fetched events (null if not configured/initialized) */
|
|
@@ -166,6 +166,7 @@ var ModelManager = class _ModelManager {
|
|
|
166
166
|
eventStoreDb = null;
|
|
167
167
|
eventStoreInitPromise = null;
|
|
168
168
|
eventStoreDbPath;
|
|
169
|
+
persistentEventDatabaseFactory;
|
|
169
170
|
/**
|
|
170
171
|
* Get the list of bootstrapped provider base URLs
|
|
171
172
|
* @returns Array of provider base URLs
|
|
@@ -194,7 +195,7 @@ var ModelManager = class _ModelManager {
|
|
|
194
195
|
} catch (error) {
|
|
195
196
|
this.eventStoreInitPromise = null;
|
|
196
197
|
throw new Error(
|
|
197
|
-
`
|
|
198
|
+
`Persistent Nostr event storage requires a runtime-specific database factory. Use @routstr/sdk/node, @routstr/sdk/bun, inject persistentEventDatabaseFactory, or omit eventStoreDbPath. (${error})`
|
|
198
199
|
);
|
|
199
200
|
}
|
|
200
201
|
})();
|
|
@@ -209,16 +210,15 @@ var ModelManager = class _ModelManager {
|
|
|
209
210
|
return this.ensureEventStore();
|
|
210
211
|
}
|
|
211
212
|
async createPersistentEventDatabase() {
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
if (!this.eventStoreDbPath) {
|
|
214
|
+
throw new Error("eventStoreDbPath is required");
|
|
215
|
+
}
|
|
216
|
+
if (!this.persistentEventDatabaseFactory) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
"persistentEventDatabaseFactory is required. Import ModelManager from @routstr/sdk/node or @routstr/sdk/bun for SQLite-backed persistent event storage."
|
|
216
219
|
);
|
|
217
220
|
}
|
|
218
|
-
|
|
219
|
-
return new BetterSqlite3EventDatabase(
|
|
220
|
-
this.eventStoreDbPath
|
|
221
|
-
);
|
|
221
|
+
return this.persistentEventDatabaseFactory(this.eventStoreDbPath);
|
|
222
222
|
}
|
|
223
223
|
/** Close the persistent event store database handle, if configured. */
|
|
224
224
|
closeEventStore() {
|
|
@@ -331,6 +331,13 @@ var ModelManager = class _ModelManager {
|
|
|
331
331
|
}
|
|
332
332
|
return this.bootstrapFromHttp(torMode, forceRefresh);
|
|
333
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Resolve Nostr relay URLs for a given use case.
|
|
336
|
+
* Returns user-configured relays if set, otherwise the provided defaults.
|
|
337
|
+
*/
|
|
338
|
+
getNostrRelays(defaults) {
|
|
339
|
+
return this.nostrRelays && this.nostrRelays.length > 0 ? this.nostrRelays : defaults;
|
|
340
|
+
}
|
|
334
341
|
/**
|
|
335
342
|
* Bootstrap providers from Nostr network (kind 38421)
|
|
336
343
|
* @param kind The Nostr kind to fetch
|
|
@@ -338,11 +345,11 @@ var ModelManager = class _ModelManager {
|
|
|
338
345
|
* @returns Array of provider base URLs
|
|
339
346
|
*/
|
|
340
347
|
async bootstrapFromNostr(kind, torMode, forceRefresh = false) {
|
|
341
|
-
const
|
|
348
|
+
const relays = this.getNostrRelays([
|
|
342
349
|
"wss://relay.primal.net",
|
|
343
350
|
"wss://nos.lol",
|
|
344
351
|
"wss://relay.damus.io"
|
|
345
|
-
];
|
|
352
|
+
]);
|
|
346
353
|
const cached = await this.getCachedNostrEvents(
|
|
347
354
|
{ kinds: [kind] },
|
|
348
355
|
this.cacheTTL,
|
|
@@ -353,7 +360,7 @@ var ModelManager = class _ModelManager {
|
|
|
353
360
|
const pool = new applesauceRelay.RelayPool();
|
|
354
361
|
const timeoutMs = 5e3;
|
|
355
362
|
await new Promise((resolve) => {
|
|
356
|
-
pool.req(
|
|
363
|
+
pool.req(relays, {
|
|
357
364
|
kinds: [kind],
|
|
358
365
|
limit: 100
|
|
359
366
|
}).pipe(
|
|
@@ -525,16 +532,16 @@ var ModelManager = class _ModelManager {
|
|
|
525
532
|
);
|
|
526
533
|
let sessionEvents = cached;
|
|
527
534
|
if (cached.length === 0) {
|
|
528
|
-
const
|
|
535
|
+
const lgtmRelays = this.getNostrRelays([
|
|
529
536
|
"wss://relay.primal.net",
|
|
530
537
|
"wss://nos.lol",
|
|
531
538
|
"wss://relay.damus.io",
|
|
532
539
|
"wss://relay.routstr.com"
|
|
533
|
-
];
|
|
540
|
+
]);
|
|
534
541
|
const pool = new applesauceRelay.RelayPool();
|
|
535
542
|
const timeoutMs = 5e3;
|
|
536
543
|
await new Promise((resolve) => {
|
|
537
|
-
pool.req(
|
|
544
|
+
pool.req(lgtmRelays, {
|
|
538
545
|
kinds: [38425],
|
|
539
546
|
"#t": ["lgtm"],
|
|
540
547
|
limit: 500,
|
|
@@ -776,11 +783,11 @@ var ModelManager = class _ModelManager {
|
|
|
776
783
|
return cachedModels;
|
|
777
784
|
}
|
|
778
785
|
}
|
|
779
|
-
const
|
|
786
|
+
const relays = this.getNostrRelays([
|
|
780
787
|
"wss://relay.damus.io",
|
|
781
788
|
"wss://nos.lol",
|
|
782
789
|
"wss://relay.routstr.com"
|
|
783
|
-
];
|
|
790
|
+
]);
|
|
784
791
|
const cached = await this.getCachedNostrEvents(
|
|
785
792
|
{ kinds: [38423], "#d": ["routstr-21-models"], authors: [this.routstrPubkey] },
|
|
786
793
|
this.cacheTTL,
|
|
@@ -791,7 +798,7 @@ var ModelManager = class _ModelManager {
|
|
|
791
798
|
const pool = new applesauceRelay.RelayPool();
|
|
792
799
|
const timeoutMs = 5e3;
|
|
793
800
|
await new Promise((resolve) => {
|
|
794
|
-
pool.req(
|
|
801
|
+
pool.req(relays, {
|
|
795
802
|
kinds: [38423],
|
|
796
803
|
"#d": ["routstr-21-models"],
|
|
797
804
|
limit: 1,
|
|
@@ -2337,470 +2344,154 @@ var BalanceManager = class _BalanceManager {
|
|
|
2337
2344
|
}
|
|
2338
2345
|
};
|
|
2339
2346
|
|
|
2340
|
-
//
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
const totalMsats = costObj.total_msats;
|
|
2357
|
-
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
2358
|
-
if (typeof totalMsats === "number") {
|
|
2359
|
-
satsCost = totalMsats / 1e3;
|
|
2360
|
-
}
|
|
2347
|
+
// utils/torUtils.ts
|
|
2348
|
+
var TOR_ONION_SUFFIX = ".onion";
|
|
2349
|
+
var isTorContext = () => {
|
|
2350
|
+
if (typeof window === "undefined") return false;
|
|
2351
|
+
const hostname = window.location.hostname.toLowerCase();
|
|
2352
|
+
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2353
|
+
};
|
|
2354
|
+
var isOnionUrl = (url) => {
|
|
2355
|
+
if (!url) return false;
|
|
2356
|
+
const trimmed = url.trim().toLowerCase();
|
|
2357
|
+
if (!trimmed) return false;
|
|
2358
|
+
try {
|
|
2359
|
+
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
2360
|
+
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2361
|
+
} catch {
|
|
2362
|
+
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
2361
2363
|
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
+
};
|
|
2365
|
+
var shouldAllowHttp = (url, torMode) => {
|
|
2366
|
+
if (!url.startsWith("http://")) return true;
|
|
2367
|
+
if (url.includes("localhost") || url.includes("127.0.0.1")) return true;
|
|
2368
|
+
return torMode && isOnionUrl(url);
|
|
2369
|
+
};
|
|
2370
|
+
var normalizeProviderUrl = (url, torMode = false) => {
|
|
2371
|
+
if (!url || typeof url !== "string") return null;
|
|
2372
|
+
const trimmed = url.trim();
|
|
2373
|
+
if (!trimmed) return null;
|
|
2374
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
2375
|
+
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
2364
2376
|
}
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
const trimmed = id.trim();
|
|
2378
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
2379
|
-
}
|
|
2380
|
-
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
2381
|
-
if (!parsed || typeof parsed !== "object") {
|
|
2382
|
-
return null;
|
|
2377
|
+
const useHttpForOnion = torMode && isOnionUrl(trimmed);
|
|
2378
|
+
const withProto = `${useHttpForOnion ? "http" : "https"}://${trimmed}`;
|
|
2379
|
+
return withProto.endsWith("/") ? withProto : `${withProto}/`;
|
|
2380
|
+
};
|
|
2381
|
+
var dedupePreserveOrder = (urls) => {
|
|
2382
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2383
|
+
const out = [];
|
|
2384
|
+
for (const url of urls) {
|
|
2385
|
+
if (!seen.has(url)) {
|
|
2386
|
+
seen.add(url);
|
|
2387
|
+
out.push(url);
|
|
2388
|
+
}
|
|
2383
2389
|
}
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2390
|
+
return out;
|
|
2391
|
+
};
|
|
2392
|
+
var getProviderEndpoints = (provider, torMode) => {
|
|
2393
|
+
const rawUrls = [
|
|
2394
|
+
provider.endpoint_url,
|
|
2395
|
+
...Array.isArray(provider.endpoint_urls) ? provider.endpoint_urls : [],
|
|
2396
|
+
provider.onion_url,
|
|
2397
|
+
...Array.isArray(provider.onion_urls) ? provider.onion_urls : []
|
|
2398
|
+
];
|
|
2399
|
+
const normalized = rawUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2400
|
+
const unique = dedupePreserveOrder(normalized).filter(
|
|
2401
|
+
(value) => shouldAllowHttp(value, torMode)
|
|
2402
|
+
);
|
|
2403
|
+
if (unique.length === 0) return [];
|
|
2404
|
+
const onion = unique.filter((value) => isOnionUrl(value));
|
|
2405
|
+
const clearnet = unique.filter((value) => !isOnionUrl(value));
|
|
2406
|
+
if (torMode) {
|
|
2407
|
+
return onion.length > 0 ? onion : clearnet;
|
|
2396
2408
|
}
|
|
2397
|
-
|
|
2409
|
+
return clearnet;
|
|
2410
|
+
};
|
|
2411
|
+
var filterBaseUrlsForTor = (baseUrls, torMode) => {
|
|
2412
|
+
if (!Array.isArray(baseUrls)) return [];
|
|
2413
|
+
const normalized = baseUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2414
|
+
const filtered = normalized.filter(
|
|
2415
|
+
(value) => torMode ? true : !isOnionUrl(value)
|
|
2416
|
+
);
|
|
2417
|
+
return dedupePreserveOrder(
|
|
2418
|
+
filtered.filter((value) => shouldAllowHttp(value, torMode))
|
|
2419
|
+
);
|
|
2420
|
+
};
|
|
2421
|
+
|
|
2422
|
+
// client/ProviderManager.ts
|
|
2423
|
+
function getImageResolutionFromDataUrl(dataUrl) {
|
|
2424
|
+
try {
|
|
2425
|
+
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:"))
|
|
2426
|
+
return null;
|
|
2427
|
+
const commaIdx = dataUrl.indexOf(",");
|
|
2428
|
+
if (commaIdx === -1) return null;
|
|
2429
|
+
const meta = dataUrl.slice(5, commaIdx);
|
|
2430
|
+
const base64 = dataUrl.slice(commaIdx + 1);
|
|
2431
|
+
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
2432
|
+
const len = binary.length;
|
|
2433
|
+
const bytes = new Uint8Array(len);
|
|
2434
|
+
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
2435
|
+
const isPNG = meta.includes("image/png");
|
|
2436
|
+
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
2437
|
+
if (isPNG) {
|
|
2438
|
+
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
2439
|
+
for (let i = 0; i < sig.length; i++) {
|
|
2440
|
+
if (bytes[i] !== sig[i]) return null;
|
|
2441
|
+
}
|
|
2442
|
+
const view = new DataView(
|
|
2443
|
+
bytes.buffer,
|
|
2444
|
+
bytes.byteOffset,
|
|
2445
|
+
bytes.byteLength
|
|
2446
|
+
);
|
|
2447
|
+
const width = view.getUint32(16, false);
|
|
2448
|
+
const height = view.getUint32(20, false);
|
|
2449
|
+
if (width > 0 && height > 0) return { width, height };
|
|
2450
|
+
return null;
|
|
2451
|
+
}
|
|
2452
|
+
if (isJPEG) {
|
|
2453
|
+
let offset = 0;
|
|
2454
|
+
if (bytes[offset++] !== 255 || bytes[offset++] !== 216) return null;
|
|
2455
|
+
while (offset < bytes.length) {
|
|
2456
|
+
while (offset < bytes.length && bytes[offset] !== 255) offset++;
|
|
2457
|
+
if (offset + 1 >= bytes.length) break;
|
|
2458
|
+
while (bytes[offset] === 255) offset++;
|
|
2459
|
+
const marker = bytes[offset++];
|
|
2460
|
+
if (marker === 216 || marker === 217) continue;
|
|
2461
|
+
if (offset + 1 >= bytes.length) break;
|
|
2462
|
+
const length = bytes[offset] << 8 | bytes[offset + 1];
|
|
2463
|
+
offset += 2;
|
|
2464
|
+
if (marker === 192 || marker === 194) {
|
|
2465
|
+
if (length < 7 || offset + length - 2 > bytes.length) return null;
|
|
2466
|
+
const precision = bytes[offset];
|
|
2467
|
+
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
2468
|
+
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
2469
|
+
if (precision > 0 && width > 0 && height > 0)
|
|
2470
|
+
return { width, height };
|
|
2471
|
+
return null;
|
|
2472
|
+
} else {
|
|
2473
|
+
offset += length - 2;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
return null;
|
|
2477
|
+
}
|
|
2398
2478
|
return null;
|
|
2399
|
-
}
|
|
2400
|
-
const usage = parsed.usage;
|
|
2401
|
-
const usageCost = usage.cost;
|
|
2402
|
-
let cost = 0;
|
|
2403
|
-
let msats = 0;
|
|
2404
|
-
if (typeof usageCost === "number") {
|
|
2405
|
-
cost = usageCost;
|
|
2406
|
-
} else if (usageCost && typeof usageCost === "object") {
|
|
2407
|
-
cost = usageCost.total_usd ?? 0;
|
|
2408
|
-
msats = usageCost.total_msats ?? 0;
|
|
2409
|
-
}
|
|
2410
|
-
if (cost === 0) {
|
|
2411
|
-
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
2412
|
-
}
|
|
2413
|
-
if (msats === 0) {
|
|
2414
|
-
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
2415
|
-
}
|
|
2416
|
-
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
2417
|
-
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
2418
|
-
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
2419
|
-
const result = {
|
|
2420
|
-
promptTokens,
|
|
2421
|
-
completionTokens,
|
|
2422
|
-
totalTokens,
|
|
2423
|
-
cost: Number(cost ?? 0),
|
|
2424
|
-
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
|
|
2425
|
-
};
|
|
2426
|
-
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
2479
|
+
} catch {
|
|
2427
2480
|
return null;
|
|
2428
2481
|
}
|
|
2429
|
-
return result;
|
|
2430
|
-
}
|
|
2431
|
-
function toUsageStats(usage) {
|
|
2432
|
-
if (!usage) return void 0;
|
|
2433
|
-
return {
|
|
2434
|
-
total_tokens: usage.totalTokens,
|
|
2435
|
-
prompt_tokens: usage.promptTokens,
|
|
2436
|
-
completion_tokens: usage.completionTokens,
|
|
2437
|
-
cost: usage.cost,
|
|
2438
|
-
sats_cost: usage.satsCost
|
|
2439
|
-
};
|
|
2440
2482
|
}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
if (!response.body) {
|
|
2454
|
-
throw new Error("Response body is not available");
|
|
2455
|
-
}
|
|
2456
|
-
const reader = response.body.getReader();
|
|
2457
|
-
const decoder = new TextDecoder("utf-8");
|
|
2458
|
-
let buffer = "";
|
|
2459
|
-
this.accumulatedContent = "";
|
|
2460
|
-
this.accumulatedThinking = "";
|
|
2461
|
-
this.accumulatedImages = [];
|
|
2462
|
-
this.isInThinking = false;
|
|
2463
|
-
this.isInContent = false;
|
|
2464
|
-
let usage;
|
|
2465
|
-
let model;
|
|
2466
|
-
let finish_reason;
|
|
2467
|
-
let citations;
|
|
2468
|
-
let annotations;
|
|
2469
|
-
let responseId;
|
|
2470
|
-
try {
|
|
2471
|
-
while (true) {
|
|
2472
|
-
const { done, value } = await reader.read();
|
|
2473
|
-
if (done) {
|
|
2474
|
-
break;
|
|
2475
|
-
}
|
|
2476
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
2477
|
-
buffer += chunk;
|
|
2478
|
-
const lines = buffer.split("\n");
|
|
2479
|
-
buffer = lines.pop() || "";
|
|
2480
|
-
for (const line of lines) {
|
|
2481
|
-
const parsed = this._parseLine(line);
|
|
2482
|
-
if (!parsed) continue;
|
|
2483
|
-
if (parsed.content) {
|
|
2484
|
-
this._handleContent(parsed.content, callbacks, modelId);
|
|
2485
|
-
}
|
|
2486
|
-
if (parsed.reasoning) {
|
|
2487
|
-
this._handleThinking(parsed.reasoning, callbacks);
|
|
2488
|
-
}
|
|
2489
|
-
if (parsed.usage) {
|
|
2490
|
-
usage = parsed.usage;
|
|
2491
|
-
}
|
|
2492
|
-
if (parsed.model) {
|
|
2493
|
-
model = parsed.model;
|
|
2494
|
-
}
|
|
2495
|
-
if (parsed.finish_reason) {
|
|
2496
|
-
finish_reason = parsed.finish_reason;
|
|
2497
|
-
}
|
|
2498
|
-
if (parsed.responseId) {
|
|
2499
|
-
responseId = parsed.responseId;
|
|
2500
|
-
}
|
|
2501
|
-
if (parsed.citations) {
|
|
2502
|
-
citations = parsed.citations;
|
|
2503
|
-
}
|
|
2504
|
-
if (parsed.annotations) {
|
|
2505
|
-
annotations = parsed.annotations;
|
|
2506
|
-
}
|
|
2507
|
-
if (parsed.images) {
|
|
2508
|
-
this._mergeImages(parsed.images);
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
} finally {
|
|
2513
|
-
reader.releaseLock();
|
|
2514
|
-
}
|
|
2515
|
-
return {
|
|
2516
|
-
content: this.accumulatedContent,
|
|
2517
|
-
thinking: this.accumulatedThinking || void 0,
|
|
2518
|
-
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
2519
|
-
usage,
|
|
2520
|
-
model,
|
|
2521
|
-
responseId,
|
|
2522
|
-
finish_reason,
|
|
2523
|
-
citations,
|
|
2524
|
-
annotations
|
|
2525
|
-
};
|
|
2526
|
-
}
|
|
2527
|
-
/**
|
|
2528
|
-
* Parse a single SSE line
|
|
2529
|
-
*/
|
|
2530
|
-
_parseLine(line) {
|
|
2531
|
-
if (!line.trim()) return null;
|
|
2532
|
-
if (!line.startsWith("data: ")) {
|
|
2533
|
-
return null;
|
|
2534
|
-
}
|
|
2535
|
-
const jsonData = line.slice(6);
|
|
2536
|
-
if (jsonData === "[DONE]") {
|
|
2537
|
-
return null;
|
|
2538
|
-
}
|
|
2539
|
-
try {
|
|
2540
|
-
const parsed = JSON.parse(jsonData);
|
|
2541
|
-
const result = {};
|
|
2542
|
-
if (parsed.choices?.[0]?.delta?.content) {
|
|
2543
|
-
result.content = parsed.choices[0].delta.content;
|
|
2544
|
-
}
|
|
2545
|
-
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
2546
|
-
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
2547
|
-
}
|
|
2548
|
-
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
2549
|
-
if (extractedUsage) {
|
|
2550
|
-
result.usage = toUsageStats(extractedUsage);
|
|
2551
|
-
} else if (parsed.usage) {
|
|
2552
|
-
result.usage = {
|
|
2553
|
-
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
2554
|
-
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
2555
|
-
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
2556
|
-
};
|
|
2557
|
-
}
|
|
2558
|
-
if (parsed.id) {
|
|
2559
|
-
result.responseId = parsed.id;
|
|
2560
|
-
}
|
|
2561
|
-
if (parsed.model) {
|
|
2562
|
-
result.model = parsed.model;
|
|
2563
|
-
}
|
|
2564
|
-
if (parsed.citations) {
|
|
2565
|
-
result.citations = parsed.citations;
|
|
2566
|
-
}
|
|
2567
|
-
if (parsed.annotations) {
|
|
2568
|
-
result.annotations = parsed.annotations;
|
|
2569
|
-
}
|
|
2570
|
-
if (parsed.choices?.[0]?.finish_reason) {
|
|
2571
|
-
result.finish_reason = parsed.choices[0].finish_reason;
|
|
2572
|
-
}
|
|
2573
|
-
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
2574
|
-
if (images && Array.isArray(images)) {
|
|
2575
|
-
result.images = images;
|
|
2576
|
-
}
|
|
2577
|
-
return result;
|
|
2578
|
-
} catch {
|
|
2579
|
-
return null;
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
/**
|
|
2583
|
-
* Handle content delta with thinking support
|
|
2584
|
-
*/
|
|
2585
|
-
_handleContent(content, callbacks, modelId) {
|
|
2586
|
-
if (this.isInThinking && !this.isInContent) {
|
|
2587
|
-
this.accumulatedThinking += "</thinking>";
|
|
2588
|
-
callbacks.onThinking(this.accumulatedThinking);
|
|
2589
|
-
this.isInThinking = false;
|
|
2590
|
-
this.isInContent = true;
|
|
2591
|
-
}
|
|
2592
|
-
if (modelId) {
|
|
2593
|
-
this._extractThinkingFromContent(content, callbacks);
|
|
2594
|
-
} else {
|
|
2595
|
-
this.accumulatedContent += content;
|
|
2596
|
-
}
|
|
2597
|
-
callbacks.onContent(this.accumulatedContent);
|
|
2598
|
-
}
|
|
2599
|
-
/**
|
|
2600
|
-
* Handle thinking/reasoning content
|
|
2601
|
-
*/
|
|
2602
|
-
_handleThinking(reasoning, callbacks) {
|
|
2603
|
-
if (!this.isInThinking) {
|
|
2604
|
-
this.accumulatedThinking += "<thinking> ";
|
|
2605
|
-
this.isInThinking = true;
|
|
2606
|
-
}
|
|
2607
|
-
this.accumulatedThinking += reasoning;
|
|
2608
|
-
callbacks.onThinking(this.accumulatedThinking);
|
|
2609
|
-
}
|
|
2610
|
-
/**
|
|
2611
|
-
* Extract thinking blocks from content (for models with inline thinking)
|
|
2612
|
-
*/
|
|
2613
|
-
_extractThinkingFromContent(content, callbacks) {
|
|
2614
|
-
const parts = content.split(/(<thinking>|<\/thinking>)/);
|
|
2615
|
-
for (const part of parts) {
|
|
2616
|
-
if (part === "<thinking>") {
|
|
2617
|
-
this.isInThinking = true;
|
|
2618
|
-
if (!this.accumulatedThinking.includes("<thinking>")) {
|
|
2619
|
-
this.accumulatedThinking += "<thinking> ";
|
|
2620
|
-
}
|
|
2621
|
-
} else if (part === "</thinking>") {
|
|
2622
|
-
this.isInThinking = false;
|
|
2623
|
-
this.accumulatedThinking += "</thinking>";
|
|
2624
|
-
} else if (this.isInThinking) {
|
|
2625
|
-
this.accumulatedThinking += part;
|
|
2626
|
-
} else {
|
|
2627
|
-
this.accumulatedContent += part;
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
/**
|
|
2632
|
-
* Merge images into accumulated array, avoiding duplicates
|
|
2633
|
-
*/
|
|
2634
|
-
_mergeImages(newImages) {
|
|
2635
|
-
for (const img of newImages) {
|
|
2636
|
-
const newUrl = img.image_url?.url;
|
|
2637
|
-
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
2638
|
-
const existingUrl = existing.image_url?.url;
|
|
2639
|
-
if (newUrl && existingUrl) {
|
|
2640
|
-
return existingUrl === newUrl;
|
|
2641
|
-
}
|
|
2642
|
-
if (img.index !== void 0 && existing.index !== void 0) {
|
|
2643
|
-
return existing.index === img.index;
|
|
2644
|
-
}
|
|
2645
|
-
return false;
|
|
2646
|
-
});
|
|
2647
|
-
if (existingIndex === -1) {
|
|
2648
|
-
this.accumulatedImages.push(img);
|
|
2649
|
-
} else {
|
|
2650
|
-
this.accumulatedImages[existingIndex] = img;
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
};
|
|
2655
|
-
|
|
2656
|
-
// utils/torUtils.ts
|
|
2657
|
-
var TOR_ONION_SUFFIX = ".onion";
|
|
2658
|
-
var isTorContext = () => {
|
|
2659
|
-
if (typeof window === "undefined") return false;
|
|
2660
|
-
const hostname = window.location.hostname.toLowerCase();
|
|
2661
|
-
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2662
|
-
};
|
|
2663
|
-
var isOnionUrl = (url) => {
|
|
2664
|
-
if (!url) return false;
|
|
2665
|
-
const trimmed = url.trim().toLowerCase();
|
|
2666
|
-
if (!trimmed) return false;
|
|
2667
|
-
try {
|
|
2668
|
-
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
2669
|
-
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2670
|
-
} catch {
|
|
2671
|
-
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
2672
|
-
}
|
|
2673
|
-
};
|
|
2674
|
-
var shouldAllowHttp = (url, torMode) => {
|
|
2675
|
-
if (!url.startsWith("http://")) return true;
|
|
2676
|
-
if (url.includes("localhost") || url.includes("127.0.0.1")) return true;
|
|
2677
|
-
return torMode && isOnionUrl(url);
|
|
2678
|
-
};
|
|
2679
|
-
var normalizeProviderUrl = (url, torMode = false) => {
|
|
2680
|
-
if (!url || typeof url !== "string") return null;
|
|
2681
|
-
const trimmed = url.trim();
|
|
2682
|
-
if (!trimmed) return null;
|
|
2683
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
2684
|
-
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
2685
|
-
}
|
|
2686
|
-
const useHttpForOnion = torMode && isOnionUrl(trimmed);
|
|
2687
|
-
const withProto = `${useHttpForOnion ? "http" : "https"}://${trimmed}`;
|
|
2688
|
-
return withProto.endsWith("/") ? withProto : `${withProto}/`;
|
|
2689
|
-
};
|
|
2690
|
-
var dedupePreserveOrder = (urls) => {
|
|
2691
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2692
|
-
const out = [];
|
|
2693
|
-
for (const url of urls) {
|
|
2694
|
-
if (!seen.has(url)) {
|
|
2695
|
-
seen.add(url);
|
|
2696
|
-
out.push(url);
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
return out;
|
|
2700
|
-
};
|
|
2701
|
-
var getProviderEndpoints = (provider, torMode) => {
|
|
2702
|
-
const rawUrls = [
|
|
2703
|
-
provider.endpoint_url,
|
|
2704
|
-
...Array.isArray(provider.endpoint_urls) ? provider.endpoint_urls : [],
|
|
2705
|
-
provider.onion_url,
|
|
2706
|
-
...Array.isArray(provider.onion_urls) ? provider.onion_urls : []
|
|
2707
|
-
];
|
|
2708
|
-
const normalized = rawUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2709
|
-
const unique = dedupePreserveOrder(normalized).filter(
|
|
2710
|
-
(value) => shouldAllowHttp(value, torMode)
|
|
2711
|
-
);
|
|
2712
|
-
if (unique.length === 0) return [];
|
|
2713
|
-
const onion = unique.filter((value) => isOnionUrl(value));
|
|
2714
|
-
const clearnet = unique.filter((value) => !isOnionUrl(value));
|
|
2715
|
-
if (torMode) {
|
|
2716
|
-
return onion.length > 0 ? onion : clearnet;
|
|
2717
|
-
}
|
|
2718
|
-
return clearnet;
|
|
2719
|
-
};
|
|
2720
|
-
var filterBaseUrlsForTor = (baseUrls, torMode) => {
|
|
2721
|
-
if (!Array.isArray(baseUrls)) return [];
|
|
2722
|
-
const normalized = baseUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2723
|
-
const filtered = normalized.filter(
|
|
2724
|
-
(value) => torMode ? true : !isOnionUrl(value)
|
|
2725
|
-
);
|
|
2726
|
-
return dedupePreserveOrder(
|
|
2727
|
-
filtered.filter((value) => shouldAllowHttp(value, torMode))
|
|
2728
|
-
);
|
|
2729
|
-
};
|
|
2730
|
-
|
|
2731
|
-
// client/ProviderManager.ts
|
|
2732
|
-
function getImageResolutionFromDataUrl(dataUrl) {
|
|
2733
|
-
try {
|
|
2734
|
-
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:"))
|
|
2735
|
-
return null;
|
|
2736
|
-
const commaIdx = dataUrl.indexOf(",");
|
|
2737
|
-
if (commaIdx === -1) return null;
|
|
2738
|
-
const meta = dataUrl.slice(5, commaIdx);
|
|
2739
|
-
const base64 = dataUrl.slice(commaIdx + 1);
|
|
2740
|
-
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
2741
|
-
const len = binary.length;
|
|
2742
|
-
const bytes = new Uint8Array(len);
|
|
2743
|
-
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
2744
|
-
const isPNG = meta.includes("image/png");
|
|
2745
|
-
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
2746
|
-
if (isPNG) {
|
|
2747
|
-
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
2748
|
-
for (let i = 0; i < sig.length; i++) {
|
|
2749
|
-
if (bytes[i] !== sig[i]) return null;
|
|
2750
|
-
}
|
|
2751
|
-
const view = new DataView(
|
|
2752
|
-
bytes.buffer,
|
|
2753
|
-
bytes.byteOffset,
|
|
2754
|
-
bytes.byteLength
|
|
2755
|
-
);
|
|
2756
|
-
const width = view.getUint32(16, false);
|
|
2757
|
-
const height = view.getUint32(20, false);
|
|
2758
|
-
if (width > 0 && height > 0) return { width, height };
|
|
2759
|
-
return null;
|
|
2760
|
-
}
|
|
2761
|
-
if (isJPEG) {
|
|
2762
|
-
let offset = 0;
|
|
2763
|
-
if (bytes[offset++] !== 255 || bytes[offset++] !== 216) return null;
|
|
2764
|
-
while (offset < bytes.length) {
|
|
2765
|
-
while (offset < bytes.length && bytes[offset] !== 255) offset++;
|
|
2766
|
-
if (offset + 1 >= bytes.length) break;
|
|
2767
|
-
while (bytes[offset] === 255) offset++;
|
|
2768
|
-
const marker = bytes[offset++];
|
|
2769
|
-
if (marker === 216 || marker === 217) continue;
|
|
2770
|
-
if (offset + 1 >= bytes.length) break;
|
|
2771
|
-
const length = bytes[offset] << 8 | bytes[offset + 1];
|
|
2772
|
-
offset += 2;
|
|
2773
|
-
if (marker === 192 || marker === 194) {
|
|
2774
|
-
if (length < 7 || offset + length - 2 > bytes.length) return null;
|
|
2775
|
-
const precision = bytes[offset];
|
|
2776
|
-
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
2777
|
-
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
2778
|
-
if (precision > 0 && width > 0 && height > 0)
|
|
2779
|
-
return { width, height };
|
|
2780
|
-
return null;
|
|
2781
|
-
} else {
|
|
2782
|
-
offset += length - 2;
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
return null;
|
|
2786
|
-
}
|
|
2787
|
-
return null;
|
|
2788
|
-
} catch {
|
|
2789
|
-
return null;
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
function calculateImageTokens(width, height, detail = "auto") {
|
|
2793
|
-
if (detail === "low") return 85;
|
|
2794
|
-
let w = width;
|
|
2795
|
-
let h = height;
|
|
2796
|
-
if (w > 2048 || h > 2048) {
|
|
2797
|
-
const aspectRatio = w / h;
|
|
2798
|
-
if (w > h) {
|
|
2799
|
-
w = 2048;
|
|
2800
|
-
h = Math.floor(w / aspectRatio);
|
|
2801
|
-
} else {
|
|
2802
|
-
h = 2048;
|
|
2803
|
-
w = Math.floor(h * aspectRatio);
|
|
2483
|
+
function calculateImageTokens(width, height, detail = "auto") {
|
|
2484
|
+
if (detail === "low") return 85;
|
|
2485
|
+
let w = width;
|
|
2486
|
+
let h = height;
|
|
2487
|
+
if (w > 2048 || h > 2048) {
|
|
2488
|
+
const aspectRatio = w / h;
|
|
2489
|
+
if (w > h) {
|
|
2490
|
+
w = 2048;
|
|
2491
|
+
h = Math.floor(w / aspectRatio);
|
|
2492
|
+
} else {
|
|
2493
|
+
h = 2048;
|
|
2494
|
+
w = Math.floor(h * aspectRatio);
|
|
2804
2495
|
}
|
|
2805
2496
|
}
|
|
2806
2497
|
if (w > 768 || h > 768) {
|
|
@@ -2818,9 +2509,6 @@ function calculateImageTokens(width, height, detail = "auto") {
|
|
|
2818
2509
|
const numTiles = tilesWidth * tilesHeight;
|
|
2819
2510
|
return 85 + 170 * numTiles;
|
|
2820
2511
|
}
|
|
2821
|
-
function isInsecureHttpUrl(url) {
|
|
2822
|
-
return url.startsWith("http://");
|
|
2823
|
-
}
|
|
2824
2512
|
var ProviderManager = class _ProviderManager {
|
|
2825
2513
|
constructor(providerRegistry, store, logger) {
|
|
2826
2514
|
this.providerRegistry = providerRegistry;
|
|
@@ -3037,7 +2725,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
3037
2725
|
if (this.isOnCooldown(baseUrl)) {
|
|
3038
2726
|
continue;
|
|
3039
2727
|
}
|
|
3040
|
-
if (!torMode &&
|
|
2728
|
+
if (!torMode && isOnionUrl(baseUrl)) {
|
|
3041
2729
|
continue;
|
|
3042
2730
|
}
|
|
3043
2731
|
const model = models.find((m) => m.id === modelId);
|
|
@@ -3088,7 +2776,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
3088
2776
|
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
3089
2777
|
if (disabledProviders.has(baseUrl)) continue;
|
|
3090
2778
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
3091
|
-
if (!torMode &&
|
|
2779
|
+
if (!torMode && isOnionUrl(baseUrl))
|
|
3092
2780
|
continue;
|
|
3093
2781
|
const model = models.find((m) => m.id === modelId);
|
|
3094
2782
|
if (!model) continue;
|
|
@@ -3103,16 +2791,18 @@ var ProviderManager = class _ProviderManager {
|
|
|
3103
2791
|
getProviderPriceRankingForModel(modelId, options = {}) {
|
|
3104
2792
|
const includeDisabled = options.includeDisabled ?? false;
|
|
3105
2793
|
const torMode = options.torMode ?? false;
|
|
3106
|
-
const
|
|
3107
|
-
|
|
3108
|
-
)
|
|
2794
|
+
const disabledProviderList = this.providerRegistry.getDisabledProviders();
|
|
2795
|
+
const disabledProviders = new Set(disabledProviderList);
|
|
2796
|
+
if (disabledProviderList.length > 0) {
|
|
2797
|
+
this.logger.log(`getProviderPriceRankingForModel: disabled providers (${disabledProviderList.length}): ${disabledProviderList.join(", ")}`);
|
|
2798
|
+
}
|
|
3109
2799
|
const allModels = this.providerRegistry.getAllProvidersModels();
|
|
3110
2800
|
const results = [];
|
|
3111
2801
|
for (const [baseUrl, models] of Object.entries(allModels)) {
|
|
3112
2802
|
if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
|
|
3113
2803
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
3114
2804
|
if (torMode && !baseUrl.includes(".onion")) continue;
|
|
3115
|
-
if (!torMode &&
|
|
2805
|
+
if (!torMode && baseUrl.includes(".onion"))
|
|
3116
2806
|
continue;
|
|
3117
2807
|
const match = models.find((model) => model.id === modelId);
|
|
3118
2808
|
if (!match?.sats_pricing) continue;
|
|
@@ -3132,12 +2822,20 @@ var ProviderManager = class _ProviderManager {
|
|
|
3132
2822
|
totalPerMillion
|
|
3133
2823
|
});
|
|
3134
2824
|
}
|
|
3135
|
-
|
|
2825
|
+
results.sort((a, b) => {
|
|
3136
2826
|
if (a.totalPerMillion !== b.totalPerMillion) {
|
|
3137
2827
|
return a.totalPerMillion - b.totalPerMillion;
|
|
3138
2828
|
}
|
|
3139
2829
|
return a.baseUrl.localeCompare(b.baseUrl);
|
|
3140
2830
|
});
|
|
2831
|
+
if (results.length > 0) {
|
|
2832
|
+
const ranking = results.map((r, i) => ` ${i + 1}. ${r.baseUrl} total=${r.totalPerMillion.toFixed(2)} sats/M (prompt=${r.promptPerMillion.toFixed(2)} completion=${r.completionPerMillion.toFixed(2)})`).join("\n");
|
|
2833
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} ranking (${results.length} providers):
|
|
2834
|
+
${ranking}`);
|
|
2835
|
+
} else {
|
|
2836
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} no providers found`);
|
|
2837
|
+
}
|
|
2838
|
+
return results;
|
|
3141
2839
|
}
|
|
3142
2840
|
/**
|
|
3143
2841
|
* Get best-priced provider for a specific model
|
|
@@ -3323,147 +3021,12 @@ var createMemoryDriver = (seed) => {
|
|
|
3323
3021
|
},
|
|
3324
3022
|
async setItem(key, value) {
|
|
3325
3023
|
store.set(key, JSON.stringify(value));
|
|
3326
|
-
},
|
|
3327
|
-
async removeItem(key) {
|
|
3328
|
-
store.delete(key);
|
|
3329
|
-
}
|
|
3330
|
-
};
|
|
3331
|
-
};
|
|
3332
|
-
|
|
3333
|
-
// storage/drivers/sqlite.ts
|
|
3334
|
-
var isBun = () => {
|
|
3335
|
-
return typeof process.versions.bun !== "undefined";
|
|
3336
|
-
};
|
|
3337
|
-
var cachedDbModule = null;
|
|
3338
|
-
var loadDatabase = async (dbPath) => {
|
|
3339
|
-
if (isBun()) {
|
|
3340
|
-
throw new Error(
|
|
3341
|
-
"SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
|
|
3342
|
-
);
|
|
3343
|
-
}
|
|
3344
|
-
try {
|
|
3345
|
-
if (!cachedDbModule) {
|
|
3346
|
-
cachedDbModule = (await import('better-sqlite3')).default;
|
|
3347
|
-
}
|
|
3348
|
-
return new cachedDbModule(dbPath);
|
|
3349
|
-
} catch (error) {
|
|
3350
|
-
throw new Error(
|
|
3351
|
-
`better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
|
|
3352
|
-
);
|
|
3353
|
-
}
|
|
3354
|
-
};
|
|
3355
|
-
var createSqliteDriver = (options = {}) => {
|
|
3356
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
3357
|
-
const tableName = options.tableName || "sdk_storage";
|
|
3358
|
-
let db;
|
|
3359
|
-
let selectStmt;
|
|
3360
|
-
let upsertStmt;
|
|
3361
|
-
let deleteStmt;
|
|
3362
|
-
const initDb = async () => {
|
|
3363
|
-
if (!db) {
|
|
3364
|
-
db = await loadDatabase(dbPath);
|
|
3365
|
-
db.exec(
|
|
3366
|
-
`CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
3367
|
-
);
|
|
3368
|
-
selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
|
|
3369
|
-
upsertStmt = db.prepare(
|
|
3370
|
-
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)
|
|
3371
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
3372
|
-
);
|
|
3373
|
-
deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
|
|
3374
|
-
}
|
|
3375
|
-
};
|
|
3376
|
-
const ensureInit = async () => {
|
|
3377
|
-
if (!db) {
|
|
3378
|
-
await initDb();
|
|
3379
|
-
}
|
|
3380
|
-
};
|
|
3381
|
-
return {
|
|
3382
|
-
async getItem(key, defaultValue) {
|
|
3383
|
-
try {
|
|
3384
|
-
await ensureInit();
|
|
3385
|
-
const row = selectStmt.get(key);
|
|
3386
|
-
if (!row || typeof row.value !== "string") return defaultValue;
|
|
3387
|
-
try {
|
|
3388
|
-
return JSON.parse(row.value);
|
|
3389
|
-
} catch (parseError) {
|
|
3390
|
-
if (typeof defaultValue === "string") {
|
|
3391
|
-
return row.value;
|
|
3392
|
-
}
|
|
3393
|
-
throw parseError;
|
|
3394
|
-
}
|
|
3395
|
-
} catch (error) {
|
|
3396
|
-
console.error(`SQLite getItem failed for key "${key}":`, error);
|
|
3397
|
-
return defaultValue;
|
|
3398
|
-
}
|
|
3399
|
-
},
|
|
3400
|
-
async setItem(key, value) {
|
|
3401
|
-
try {
|
|
3402
|
-
await ensureInit();
|
|
3403
|
-
upsertStmt.run(key, JSON.stringify(value));
|
|
3404
|
-
} catch (error) {
|
|
3405
|
-
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
3406
|
-
}
|
|
3407
|
-
},
|
|
3408
|
-
async removeItem(key) {
|
|
3409
|
-
try {
|
|
3410
|
-
await ensureInit();
|
|
3411
|
-
deleteStmt.run(key);
|
|
3412
|
-
} catch (error) {
|
|
3413
|
-
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
3414
|
-
}
|
|
3415
|
-
}
|
|
3416
|
-
};
|
|
3417
|
-
};
|
|
3418
|
-
async function createBunSqliteDriver(dbPath, options) {
|
|
3419
|
-
const logger = (options?.logger ?? consoleLogger).child("BunSqliteDriver");
|
|
3420
|
-
const SQLite = (await import(
|
|
3421
|
-
/* webpackIgnore: true */
|
|
3422
|
-
'bun:sqlite'
|
|
3423
|
-
)).default;
|
|
3424
|
-
const db = new SQLite(dbPath);
|
|
3425
|
-
db.run(`
|
|
3426
|
-
CREATE TABLE IF NOT EXISTS sdk_storage (
|
|
3427
|
-
key TEXT PRIMARY KEY,
|
|
3428
|
-
value TEXT NOT NULL
|
|
3429
|
-
)
|
|
3430
|
-
`);
|
|
3431
|
-
return {
|
|
3432
|
-
async getItem(key, defaultValue) {
|
|
3433
|
-
try {
|
|
3434
|
-
const row = db.query("SELECT value FROM sdk_storage WHERE key = ?").get(key);
|
|
3435
|
-
if (!row || typeof row.value !== "string") return defaultValue;
|
|
3436
|
-
try {
|
|
3437
|
-
return JSON.parse(row.value);
|
|
3438
|
-
} catch (parseError) {
|
|
3439
|
-
if (typeof defaultValue === "string") {
|
|
3440
|
-
return row.value;
|
|
3441
|
-
}
|
|
3442
|
-
throw parseError;
|
|
3443
|
-
}
|
|
3444
|
-
} catch (error) {
|
|
3445
|
-
logger.error(`getItem failed for key "${key}":`, error);
|
|
3446
|
-
return defaultValue;
|
|
3447
|
-
}
|
|
3448
|
-
},
|
|
3449
|
-
async setItem(key, value) {
|
|
3450
|
-
try {
|
|
3451
|
-
db.query(
|
|
3452
|
-
"INSERT INTO sdk_storage (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
3453
|
-
).run(key, JSON.stringify(value));
|
|
3454
|
-
} catch (error) {
|
|
3455
|
-
logger.error(`setItem failed for key "${key}":`, error);
|
|
3456
|
-
}
|
|
3457
|
-
},
|
|
3458
|
-
async removeItem(key) {
|
|
3459
|
-
try {
|
|
3460
|
-
db.query("DELETE FROM sdk_storage WHERE key = ?").run(key);
|
|
3461
|
-
} catch (error) {
|
|
3462
|
-
logger.error(`removeItem failed for key "${key}":`, error);
|
|
3463
|
-
}
|
|
3024
|
+
},
|
|
3025
|
+
async removeItem(key) {
|
|
3026
|
+
store.delete(key);
|
|
3464
3027
|
}
|
|
3465
3028
|
};
|
|
3466
|
-
}
|
|
3029
|
+
};
|
|
3467
3030
|
|
|
3468
3031
|
// storage/drivers/indexedDB.ts
|
|
3469
3032
|
var isBrowser = typeof indexedDB !== "undefined";
|
|
@@ -3472,15 +3035,32 @@ var openDatabase = (dbName, storeName) => {
|
|
|
3472
3035
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
3473
3036
|
}
|
|
3474
3037
|
return new Promise((resolve, reject) => {
|
|
3475
|
-
const request = indexedDB.open(dbName,
|
|
3038
|
+
const request = indexedDB.open(dbName, 2);
|
|
3476
3039
|
request.onupgradeneeded = () => {
|
|
3477
3040
|
const db = request.result;
|
|
3478
3041
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
3479
3042
|
db.createObjectStore(storeName);
|
|
3480
3043
|
}
|
|
3044
|
+
if (storeName !== "usage_tracking" && !db.objectStoreNames.contains("usage_tracking")) {
|
|
3045
|
+
const utStore = db.createObjectStore("usage_tracking", { keyPath: "id" });
|
|
3046
|
+
utStore.createIndex("timestamp", "timestamp", { unique: false });
|
|
3047
|
+
utStore.createIndex("modelId", "modelId", { unique: false });
|
|
3048
|
+
utStore.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
3049
|
+
utStore.createIndex("sessionId", "sessionId", { unique: false });
|
|
3050
|
+
utStore.createIndex("client", "client", { unique: false });
|
|
3051
|
+
}
|
|
3052
|
+
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
3053
|
+
db.createObjectStore("sdk_storage");
|
|
3054
|
+
}
|
|
3481
3055
|
};
|
|
3482
3056
|
request.onsuccess = () => resolve(request.result);
|
|
3483
3057
|
request.onerror = () => reject(request.error);
|
|
3058
|
+
request.onblocked = () => {
|
|
3059
|
+
console.warn(
|
|
3060
|
+
`[IndexedDB driver] open blocked for "${dbName}" (store: "${storeName}") \u2014 close other tabs using this DB`
|
|
3061
|
+
);
|
|
3062
|
+
reject(new Error(`IndexedDB "${dbName}" blocked by another connection`));
|
|
3063
|
+
};
|
|
3484
3064
|
});
|
|
3485
3065
|
};
|
|
3486
3066
|
var createIndexedDBDriver = (options = {}) => {
|
|
@@ -3593,9 +3173,10 @@ var openDatabase2 = (dbName, storeName) => {
|
|
|
3593
3173
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
3594
3174
|
}
|
|
3595
3175
|
return new Promise((resolve, reject) => {
|
|
3596
|
-
const request = indexedDB.open(dbName,
|
|
3176
|
+
const request = indexedDB.open(dbName, 3);
|
|
3597
3177
|
request.onupgradeneeded = () => {
|
|
3598
3178
|
const db = request.result;
|
|
3179
|
+
const tx = request.transaction;
|
|
3599
3180
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
3600
3181
|
const store = db.createObjectStore(storeName, { keyPath: "id" });
|
|
3601
3182
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
@@ -3603,10 +3184,25 @@ var openDatabase2 = (dbName, storeName) => {
|
|
|
3603
3184
|
store.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
3604
3185
|
store.createIndex("sessionId", "sessionId", { unique: false });
|
|
3605
3186
|
store.createIndex("client", "client", { unique: false });
|
|
3187
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
3188
|
+
} else if (tx) {
|
|
3189
|
+
const store = tx.objectStore(storeName);
|
|
3190
|
+
if (!store.indexNames.contains("provider")) {
|
|
3191
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
3195
|
+
db.createObjectStore("sdk_storage");
|
|
3606
3196
|
}
|
|
3607
3197
|
};
|
|
3608
3198
|
request.onsuccess = () => resolve(request.result);
|
|
3609
3199
|
request.onerror = () => reject(request.error);
|
|
3200
|
+
request.onblocked = () => {
|
|
3201
|
+
console.warn(
|
|
3202
|
+
`[usageTracking IndexedDB] open blocked for "${dbName}" \u2014 close other tabs using this DB`
|
|
3203
|
+
);
|
|
3204
|
+
reject(new Error(`IndexedDB "${dbName}" blocked by another connection`));
|
|
3205
|
+
};
|
|
3610
3206
|
});
|
|
3611
3207
|
};
|
|
3612
3208
|
var matchesFilters = (entry, options = {}) => {
|
|
@@ -3628,6 +3224,9 @@ var matchesFilters = (entry, options = {}) => {
|
|
|
3628
3224
|
if (options.client && entry.client !== options.client) {
|
|
3629
3225
|
return false;
|
|
3630
3226
|
}
|
|
3227
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
3228
|
+
return false;
|
|
3229
|
+
}
|
|
3631
3230
|
return true;
|
|
3632
3231
|
};
|
|
3633
3232
|
var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
@@ -3759,393 +3358,8 @@ var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
|
3759
3358
|
};
|
|
3760
3359
|
};
|
|
3761
3360
|
|
|
3762
|
-
// storage/usageTracking/sqlite.ts
|
|
3763
|
-
var MIGRATION_MARKER_KEY2 = "usage_tracking_migration_v1";
|
|
3764
|
-
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3765
|
-
var isBun2 = () => {
|
|
3766
|
-
return typeof process.versions.bun !== "undefined";
|
|
3767
|
-
};
|
|
3768
|
-
var cachedDbModule2 = null;
|
|
3769
|
-
var loadDatabase2 = async (dbPath) => {
|
|
3770
|
-
if (isBun2()) {
|
|
3771
|
-
throw new Error(
|
|
3772
|
-
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
3773
|
-
);
|
|
3774
|
-
}
|
|
3775
|
-
try {
|
|
3776
|
-
if (!cachedDbModule2) {
|
|
3777
|
-
cachedDbModule2 = (await import('better-sqlite3')).default;
|
|
3778
|
-
}
|
|
3779
|
-
return new cachedDbModule2(dbPath);
|
|
3780
|
-
} catch (error) {
|
|
3781
|
-
throw new Error(
|
|
3782
|
-
`better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
|
|
3783
|
-
);
|
|
3784
|
-
}
|
|
3785
|
-
};
|
|
3786
|
-
var buildWhereClause = (options = {}) => {
|
|
3787
|
-
const clauses = [];
|
|
3788
|
-
const params = [];
|
|
3789
|
-
if (typeof options.before === "number") {
|
|
3790
|
-
clauses.push("timestamp < ?");
|
|
3791
|
-
params.push(options.before);
|
|
3792
|
-
}
|
|
3793
|
-
if (typeof options.after === "number") {
|
|
3794
|
-
clauses.push("timestamp > ?");
|
|
3795
|
-
params.push(options.after);
|
|
3796
|
-
}
|
|
3797
|
-
if (options.modelId) {
|
|
3798
|
-
clauses.push("model_id = ?");
|
|
3799
|
-
params.push(options.modelId);
|
|
3800
|
-
}
|
|
3801
|
-
if (options.baseUrl) {
|
|
3802
|
-
clauses.push("base_url = ?");
|
|
3803
|
-
params.push(normalizeBaseUrl2(options.baseUrl));
|
|
3804
|
-
}
|
|
3805
|
-
if (options.sessionId) {
|
|
3806
|
-
clauses.push("session_id = ?");
|
|
3807
|
-
params.push(options.sessionId);
|
|
3808
|
-
}
|
|
3809
|
-
if (options.client) {
|
|
3810
|
-
clauses.push("client = ?");
|
|
3811
|
-
params.push(options.client);
|
|
3812
|
-
}
|
|
3813
|
-
return {
|
|
3814
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
3815
|
-
params
|
|
3816
|
-
};
|
|
3817
|
-
};
|
|
3818
|
-
var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
3819
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
3820
|
-
const tableName = options.tableName || "usage_tracking";
|
|
3821
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
3822
|
-
let db;
|
|
3823
|
-
let insertStmt;
|
|
3824
|
-
let migrationComplete = false;
|
|
3825
|
-
const initDb = async () => {
|
|
3826
|
-
if (!db) {
|
|
3827
|
-
db = await loadDatabase2(dbPath);
|
|
3828
|
-
db.exec(`
|
|
3829
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
3830
|
-
id TEXT PRIMARY KEY,
|
|
3831
|
-
timestamp INTEGER NOT NULL,
|
|
3832
|
-
model_id TEXT NOT NULL,
|
|
3833
|
-
base_url TEXT NOT NULL,
|
|
3834
|
-
request_id TEXT NOT NULL,
|
|
3835
|
-
cost REAL NOT NULL,
|
|
3836
|
-
sats_cost REAL NOT NULL,
|
|
3837
|
-
prompt_tokens INTEGER NOT NULL,
|
|
3838
|
-
completion_tokens INTEGER NOT NULL,
|
|
3839
|
-
total_tokens INTEGER NOT NULL,
|
|
3840
|
-
client TEXT,
|
|
3841
|
-
session_id TEXT,
|
|
3842
|
-
tags TEXT
|
|
3843
|
-
);
|
|
3844
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
3845
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
3846
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
3847
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
3848
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
3849
|
-
`);
|
|
3850
|
-
insertStmt = db.prepare(`
|
|
3851
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
3852
|
-
id, timestamp, model_id, base_url, request_id,
|
|
3853
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
3854
|
-
client, session_id, tags
|
|
3855
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3856
|
-
`);
|
|
3857
|
-
}
|
|
3858
|
-
};
|
|
3859
|
-
const ensureInit = async () => {
|
|
3860
|
-
if (!db) {
|
|
3861
|
-
await initDb();
|
|
3862
|
-
}
|
|
3863
|
-
};
|
|
3864
|
-
const appendOne = (entry) => {
|
|
3865
|
-
insertStmt.run(
|
|
3866
|
-
entry.id,
|
|
3867
|
-
entry.timestamp,
|
|
3868
|
-
entry.modelId,
|
|
3869
|
-
normalizeBaseUrl2(entry.baseUrl),
|
|
3870
|
-
entry.requestId,
|
|
3871
|
-
entry.cost,
|
|
3872
|
-
entry.satsCost,
|
|
3873
|
-
entry.promptTokens,
|
|
3874
|
-
entry.completionTokens,
|
|
3875
|
-
entry.totalTokens,
|
|
3876
|
-
entry.client ?? null,
|
|
3877
|
-
entry.sessionId ?? null,
|
|
3878
|
-
JSON.stringify(entry.tags ?? [])
|
|
3879
|
-
);
|
|
3880
|
-
};
|
|
3881
|
-
const ensureMigrated = async () => {
|
|
3882
|
-
if (!legacyStorageDriver || migrationComplete) return;
|
|
3883
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
3884
|
-
MIGRATION_MARKER_KEY2,
|
|
3885
|
-
false
|
|
3886
|
-
);
|
|
3887
|
-
if (migrated) {
|
|
3888
|
-
migrationComplete = true;
|
|
3889
|
-
return;
|
|
3890
|
-
}
|
|
3891
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
3892
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
3893
|
-
[]
|
|
3894
|
-
);
|
|
3895
|
-
for (const entry of legacyEntries) {
|
|
3896
|
-
appendOne(entry);
|
|
3897
|
-
}
|
|
3898
|
-
if (legacyEntries.length > 0) {
|
|
3899
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
3900
|
-
}
|
|
3901
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
3902
|
-
migrationComplete = true;
|
|
3903
|
-
};
|
|
3904
|
-
const mapRow = (row) => ({
|
|
3905
|
-
id: row.id,
|
|
3906
|
-
timestamp: row.timestamp,
|
|
3907
|
-
modelId: row.model_id,
|
|
3908
|
-
baseUrl: row.base_url,
|
|
3909
|
-
requestId: row.request_id,
|
|
3910
|
-
cost: row.cost,
|
|
3911
|
-
satsCost: row.sats_cost,
|
|
3912
|
-
promptTokens: row.prompt_tokens,
|
|
3913
|
-
completionTokens: row.completion_tokens,
|
|
3914
|
-
totalTokens: row.total_tokens,
|
|
3915
|
-
client: row.client ?? void 0,
|
|
3916
|
-
sessionId: row.session_id ?? void 0,
|
|
3917
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
3918
|
-
});
|
|
3919
|
-
return {
|
|
3920
|
-
async migrate() {
|
|
3921
|
-
await ensureInit();
|
|
3922
|
-
await ensureMigrated();
|
|
3923
|
-
},
|
|
3924
|
-
async append(entry) {
|
|
3925
|
-
await ensureInit();
|
|
3926
|
-
await ensureMigrated();
|
|
3927
|
-
appendOne(entry);
|
|
3928
|
-
},
|
|
3929
|
-
async appendMany(entries) {
|
|
3930
|
-
await ensureInit();
|
|
3931
|
-
await ensureMigrated();
|
|
3932
|
-
for (const entry of entries) {
|
|
3933
|
-
appendOne(entry);
|
|
3934
|
-
}
|
|
3935
|
-
},
|
|
3936
|
-
async list(options2 = {}) {
|
|
3937
|
-
await ensureInit();
|
|
3938
|
-
await ensureMigrated();
|
|
3939
|
-
const { sql, params } = buildWhereClause(options2);
|
|
3940
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
3941
|
-
const stmt = db.prepare(
|
|
3942
|
-
`SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`
|
|
3943
|
-
);
|
|
3944
|
-
const rows = stmt.all(
|
|
3945
|
-
...typeof options2.limit === "number" ? [...params, options2.limit] : params
|
|
3946
|
-
);
|
|
3947
|
-
return rows.map(mapRow);
|
|
3948
|
-
},
|
|
3949
|
-
async count(options2 = {}) {
|
|
3950
|
-
await ensureInit();
|
|
3951
|
-
await ensureMigrated();
|
|
3952
|
-
const { sql, params } = buildWhereClause(options2);
|
|
3953
|
-
const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
|
|
3954
|
-
const row = stmt.get(...params);
|
|
3955
|
-
return Number(row?.count ?? 0);
|
|
3956
|
-
},
|
|
3957
|
-
async deleteOlderThan(timestamp) {
|
|
3958
|
-
await ensureInit();
|
|
3959
|
-
await ensureMigrated();
|
|
3960
|
-
const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
|
|
3961
|
-
const result = stmt.run(timestamp);
|
|
3962
|
-
return result.changes;
|
|
3963
|
-
},
|
|
3964
|
-
async clear() {
|
|
3965
|
-
await ensureInit();
|
|
3966
|
-
await ensureMigrated();
|
|
3967
|
-
db.prepare(`DELETE FROM ${tableName}`).run();
|
|
3968
|
-
}
|
|
3969
|
-
};
|
|
3970
|
-
};
|
|
3971
|
-
|
|
3972
|
-
// storage/usageTracking/bunSqlite.ts
|
|
3973
|
-
var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
|
|
3974
|
-
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3975
|
-
var buildWhereClause2 = (options = {}) => {
|
|
3976
|
-
const clauses = [];
|
|
3977
|
-
const params = [];
|
|
3978
|
-
if (typeof options.before === "number") {
|
|
3979
|
-
clauses.push("timestamp < ?");
|
|
3980
|
-
params.push(options.before);
|
|
3981
|
-
}
|
|
3982
|
-
if (typeof options.after === "number") {
|
|
3983
|
-
clauses.push("timestamp > ?");
|
|
3984
|
-
params.push(options.after);
|
|
3985
|
-
}
|
|
3986
|
-
if (options.modelId) {
|
|
3987
|
-
clauses.push("model_id = ?");
|
|
3988
|
-
params.push(options.modelId);
|
|
3989
|
-
}
|
|
3990
|
-
if (options.baseUrl) {
|
|
3991
|
-
clauses.push("base_url = ?");
|
|
3992
|
-
params.push(normalizeBaseUrl3(options.baseUrl));
|
|
3993
|
-
}
|
|
3994
|
-
if (options.sessionId) {
|
|
3995
|
-
clauses.push("session_id = ?");
|
|
3996
|
-
params.push(options.sessionId);
|
|
3997
|
-
}
|
|
3998
|
-
if (options.client) {
|
|
3999
|
-
clauses.push("client = ?");
|
|
4000
|
-
params.push(options.client);
|
|
4001
|
-
}
|
|
4002
|
-
return {
|
|
4003
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
4004
|
-
params
|
|
4005
|
-
};
|
|
4006
|
-
};
|
|
4007
|
-
var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
4008
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
4009
|
-
const tableName = options.tableName || "usage_tracking";
|
|
4010
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
4011
|
-
const SQLiteDatabase = options.sqlite?.Database;
|
|
4012
|
-
let migrationPromise = null;
|
|
4013
|
-
if (!SQLiteDatabase) {
|
|
4014
|
-
throw new Error(
|
|
4015
|
-
"Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
|
|
4016
|
-
);
|
|
4017
|
-
}
|
|
4018
|
-
const db = new SQLiteDatabase(dbPath);
|
|
4019
|
-
db.run(`
|
|
4020
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
4021
|
-
id TEXT PRIMARY KEY,
|
|
4022
|
-
timestamp INTEGER NOT NULL,
|
|
4023
|
-
model_id TEXT NOT NULL,
|
|
4024
|
-
base_url TEXT NOT NULL,
|
|
4025
|
-
request_id TEXT NOT NULL,
|
|
4026
|
-
cost REAL NOT NULL,
|
|
4027
|
-
sats_cost REAL NOT NULL,
|
|
4028
|
-
prompt_tokens INTEGER NOT NULL,
|
|
4029
|
-
completion_tokens INTEGER NOT NULL,
|
|
4030
|
-
total_tokens INTEGER NOT NULL,
|
|
4031
|
-
client TEXT,
|
|
4032
|
-
session_id TEXT,
|
|
4033
|
-
tags TEXT
|
|
4034
|
-
)
|
|
4035
|
-
`);
|
|
4036
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
|
|
4037
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
|
|
4038
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
|
|
4039
|
-
const appendOne = (entry) => {
|
|
4040
|
-
db.query(`
|
|
4041
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
4042
|
-
id, timestamp, model_id, base_url, request_id,
|
|
4043
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
4044
|
-
client, session_id, tags
|
|
4045
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4046
|
-
`).run(
|
|
4047
|
-
entry.id,
|
|
4048
|
-
entry.timestamp,
|
|
4049
|
-
entry.modelId,
|
|
4050
|
-
normalizeBaseUrl3(entry.baseUrl),
|
|
4051
|
-
entry.requestId,
|
|
4052
|
-
entry.cost,
|
|
4053
|
-
entry.satsCost,
|
|
4054
|
-
entry.promptTokens,
|
|
4055
|
-
entry.completionTokens,
|
|
4056
|
-
entry.totalTokens,
|
|
4057
|
-
entry.client ?? null,
|
|
4058
|
-
entry.sessionId ?? null,
|
|
4059
|
-
JSON.stringify(entry.tags ?? [])
|
|
4060
|
-
);
|
|
4061
|
-
};
|
|
4062
|
-
const mapRow = (row) => ({
|
|
4063
|
-
id: row.id,
|
|
4064
|
-
timestamp: row.timestamp,
|
|
4065
|
-
modelId: row.model_id,
|
|
4066
|
-
baseUrl: row.base_url,
|
|
4067
|
-
requestId: row.request_id,
|
|
4068
|
-
cost: row.cost,
|
|
4069
|
-
satsCost: row.sats_cost,
|
|
4070
|
-
promptTokens: row.prompt_tokens,
|
|
4071
|
-
completionTokens: row.completion_tokens,
|
|
4072
|
-
totalTokens: row.total_tokens,
|
|
4073
|
-
client: row.client ?? void 0,
|
|
4074
|
-
sessionId: row.session_id ?? void 0,
|
|
4075
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
4076
|
-
});
|
|
4077
|
-
const ensureMigrated = async () => {
|
|
4078
|
-
if (!legacyStorageDriver) return;
|
|
4079
|
-
if (!migrationPromise) {
|
|
4080
|
-
migrationPromise = (async () => {
|
|
4081
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
4082
|
-
MIGRATION_MARKER_KEY3,
|
|
4083
|
-
false
|
|
4084
|
-
);
|
|
4085
|
-
if (migrated) return;
|
|
4086
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
4087
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
4088
|
-
[]
|
|
4089
|
-
);
|
|
4090
|
-
if (legacyEntries.length > 0) {
|
|
4091
|
-
for (const entry of legacyEntries) {
|
|
4092
|
-
appendOne(entry);
|
|
4093
|
-
}
|
|
4094
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
4095
|
-
}
|
|
4096
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
|
|
4097
|
-
})();
|
|
4098
|
-
}
|
|
4099
|
-
await migrationPromise;
|
|
4100
|
-
};
|
|
4101
|
-
return {
|
|
4102
|
-
async migrate() {
|
|
4103
|
-
await ensureMigrated();
|
|
4104
|
-
},
|
|
4105
|
-
async append(entry) {
|
|
4106
|
-
await ensureMigrated();
|
|
4107
|
-
appendOne(entry);
|
|
4108
|
-
},
|
|
4109
|
-
async appendMany(entries) {
|
|
4110
|
-
await ensureMigrated();
|
|
4111
|
-
for (const entry of entries) {
|
|
4112
|
-
appendOne(entry);
|
|
4113
|
-
}
|
|
4114
|
-
},
|
|
4115
|
-
async list(options2 = {}) {
|
|
4116
|
-
await ensureMigrated();
|
|
4117
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
4118
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
4119
|
-
const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
|
|
4120
|
-
let rows;
|
|
4121
|
-
if (typeof options2.limit === "number") {
|
|
4122
|
-
rows = db.query(query).all(...params, options2.limit);
|
|
4123
|
-
} else {
|
|
4124
|
-
rows = db.query(query).all(...params);
|
|
4125
|
-
}
|
|
4126
|
-
return rows.map(mapRow);
|
|
4127
|
-
},
|
|
4128
|
-
async count(options2 = {}) {
|
|
4129
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
4130
|
-
const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
|
|
4131
|
-
const row = db.query(query).get(...params);
|
|
4132
|
-
return Number(row?.count ?? 0);
|
|
4133
|
-
},
|
|
4134
|
-
async deleteOlderThan(timestamp) {
|
|
4135
|
-
await ensureMigrated();
|
|
4136
|
-
const before = timestamp;
|
|
4137
|
-
const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
|
|
4138
|
-
return result.changes ?? 0;
|
|
4139
|
-
},
|
|
4140
|
-
async clear() {
|
|
4141
|
-
await ensureMigrated();
|
|
4142
|
-
db.query(`DELETE FROM ${tableName}`).run();
|
|
4143
|
-
}
|
|
4144
|
-
};
|
|
4145
|
-
};
|
|
4146
|
-
|
|
4147
3361
|
// storage/usageTracking/memory.ts
|
|
4148
|
-
var
|
|
3362
|
+
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
4149
3363
|
var matchesFilters2 = (entry, options = {}) => {
|
|
4150
3364
|
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
4151
3365
|
return false;
|
|
@@ -4156,7 +3370,7 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
4156
3370
|
if (options.modelId && entry.modelId !== options.modelId) {
|
|
4157
3371
|
return false;
|
|
4158
3372
|
}
|
|
4159
|
-
if (options.baseUrl &&
|
|
3373
|
+
if (options.baseUrl && normalizeBaseUrl2(entry.baseUrl) !== normalizeBaseUrl2(options.baseUrl)) {
|
|
4160
3374
|
return false;
|
|
4161
3375
|
}
|
|
4162
3376
|
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
@@ -4165,23 +3379,26 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
4165
3379
|
if (options.client && entry.client !== options.client) {
|
|
4166
3380
|
return false;
|
|
4167
3381
|
}
|
|
3382
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
3383
|
+
return false;
|
|
3384
|
+
}
|
|
4168
3385
|
return true;
|
|
4169
3386
|
};
|
|
4170
3387
|
var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
4171
3388
|
const store = /* @__PURE__ */ new Map();
|
|
4172
3389
|
for (const entry of seed) {
|
|
4173
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
3390
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
4174
3391
|
}
|
|
4175
3392
|
return {
|
|
4176
3393
|
async migrate() {
|
|
4177
3394
|
return;
|
|
4178
3395
|
},
|
|
4179
3396
|
async append(entry) {
|
|
4180
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
3397
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
4181
3398
|
},
|
|
4182
3399
|
async appendMany(entries) {
|
|
4183
3400
|
for (const entry of entries) {
|
|
4184
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
3401
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
4185
3402
|
}
|
|
4186
3403
|
},
|
|
4187
3404
|
async list(options = {}) {
|
|
@@ -4209,7 +3426,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
4209
3426
|
}
|
|
4210
3427
|
};
|
|
4211
3428
|
};
|
|
4212
|
-
var
|
|
3429
|
+
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
4213
3430
|
var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
4214
3431
|
modelsFromAllProviders: {},
|
|
4215
3432
|
lastUsedModel: null,
|
|
@@ -4232,7 +3449,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4232
3449
|
setModelsFromAllProviders: (value) => {
|
|
4233
3450
|
const normalized = {};
|
|
4234
3451
|
for (const [baseUrl, models] of Object.entries(value)) {
|
|
4235
|
-
normalized[
|
|
3452
|
+
normalized[normalizeBaseUrl3(baseUrl)] = models;
|
|
4236
3453
|
}
|
|
4237
3454
|
void driver.setItem(
|
|
4238
3455
|
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
@@ -4245,7 +3462,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4245
3462
|
set({ lastUsedModel: value });
|
|
4246
3463
|
},
|
|
4247
3464
|
setBaseUrlsList: (value) => {
|
|
4248
|
-
const normalized = value.map((url) =>
|
|
3465
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
4249
3466
|
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
4250
3467
|
set({ baseUrlsList: normalized });
|
|
4251
3468
|
},
|
|
@@ -4254,14 +3471,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4254
3471
|
set({ lastBaseUrlsUpdate: value });
|
|
4255
3472
|
},
|
|
4256
3473
|
setDisabledProviders: (value) => {
|
|
4257
|
-
const normalized = value.map((url) =>
|
|
3474
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
4258
3475
|
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
4259
3476
|
set({ disabledProviders: normalized });
|
|
4260
3477
|
},
|
|
4261
3478
|
setMintsFromAllProviders: (value) => {
|
|
4262
3479
|
const normalized = {};
|
|
4263
3480
|
for (const [baseUrl, mints] of Object.entries(value)) {
|
|
4264
|
-
normalized[
|
|
3481
|
+
normalized[normalizeBaseUrl3(baseUrl)] = mints.map(
|
|
4265
3482
|
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
4266
3483
|
);
|
|
4267
3484
|
}
|
|
@@ -4274,7 +3491,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4274
3491
|
setInfoFromAllProviders: (value) => {
|
|
4275
3492
|
const normalized = {};
|
|
4276
3493
|
for (const [baseUrl, info] of Object.entries(value)) {
|
|
4277
|
-
normalized[
|
|
3494
|
+
normalized[normalizeBaseUrl3(baseUrl)] = info;
|
|
4278
3495
|
}
|
|
4279
3496
|
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
4280
3497
|
set({ infoFromAllProviders: normalized });
|
|
@@ -4282,7 +3499,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4282
3499
|
setLastModelsUpdate: (value) => {
|
|
4283
3500
|
const normalized = {};
|
|
4284
3501
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
4285
|
-
normalized[
|
|
3502
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
4286
3503
|
}
|
|
4287
3504
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
4288
3505
|
set({ lastModelsUpdate: normalized });
|
|
@@ -4292,7 +3509,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4292
3509
|
const updates = typeof value === "function" ? value(state.apiKeys) : value;
|
|
4293
3510
|
const normalized = updates.map((entry) => ({
|
|
4294
3511
|
...entry,
|
|
4295
|
-
baseUrl:
|
|
3512
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4296
3513
|
balance: entry.balance ?? 0,
|
|
4297
3514
|
lastUsed: entry.lastUsed ?? null
|
|
4298
3515
|
}));
|
|
@@ -4304,7 +3521,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4304
3521
|
set((state) => {
|
|
4305
3522
|
const updates = typeof value === "function" ? value(state.childKeys) : value;
|
|
4306
3523
|
const normalized = updates.map((entry) => ({
|
|
4307
|
-
parentBaseUrl:
|
|
3524
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
4308
3525
|
childKey: entry.childKey,
|
|
4309
3526
|
balance: entry.balance ?? 0,
|
|
4310
3527
|
balanceLimit: entry.balanceLimit,
|
|
@@ -4318,9 +3535,9 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4318
3535
|
setXcashuTokens: (value) => {
|
|
4319
3536
|
const normalized = {};
|
|
4320
3537
|
for (const [baseUrl, tokens] of Object.entries(value)) {
|
|
4321
|
-
normalized[
|
|
3538
|
+
normalized[normalizeBaseUrl3(baseUrl)] = tokens.map((entry) => ({
|
|
4322
3539
|
...entry,
|
|
4323
|
-
baseUrl:
|
|
3540
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4324
3541
|
createdAt: entry.createdAt ?? Date.now(),
|
|
4325
3542
|
tryCount: entry.tryCount ?? 0
|
|
4326
3543
|
}));
|
|
@@ -4371,12 +3588,12 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4371
3588
|
},
|
|
4372
3589
|
// ========== Failure Tracking ==========
|
|
4373
3590
|
setFailedProviders: (value) => {
|
|
4374
|
-
const normalized = value.map((url) =>
|
|
3591
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
4375
3592
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
|
|
4376
3593
|
set({ failedProviders: normalized });
|
|
4377
3594
|
},
|
|
4378
3595
|
addFailedProvider: (baseUrl) => {
|
|
4379
|
-
const normalized =
|
|
3596
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4380
3597
|
const current = get().failedProviders;
|
|
4381
3598
|
if (!current.includes(normalized)) {
|
|
4382
3599
|
const updated = [...current, normalized];
|
|
@@ -4385,7 +3602,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4385
3602
|
}
|
|
4386
3603
|
},
|
|
4387
3604
|
removeFailedProvider: (baseUrl) => {
|
|
4388
|
-
const normalized =
|
|
3605
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4389
3606
|
const current = get().failedProviders;
|
|
4390
3607
|
const updated = current.filter((url) => url !== normalized);
|
|
4391
3608
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
@@ -4394,13 +3611,13 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4394
3611
|
setLastFailed: (value) => {
|
|
4395
3612
|
const normalized = {};
|
|
4396
3613
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
4397
|
-
normalized[
|
|
3614
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
4398
3615
|
}
|
|
4399
3616
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
|
|
4400
3617
|
set({ lastFailed: normalized });
|
|
4401
3618
|
},
|
|
4402
3619
|
setLastFailedTimestamp: (baseUrl, timestamp) => {
|
|
4403
|
-
const normalized =
|
|
3620
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4404
3621
|
const current = get().lastFailed;
|
|
4405
3622
|
const updated = { ...current, [normalized]: timestamp };
|
|
4406
3623
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
|
|
@@ -4408,14 +3625,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4408
3625
|
},
|
|
4409
3626
|
setProvidersOnCooldown: (value) => {
|
|
4410
3627
|
const normalized = value.map((entry) => ({
|
|
4411
|
-
baseUrl:
|
|
3628
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4412
3629
|
timestamp: entry.timestamp
|
|
4413
3630
|
}));
|
|
4414
3631
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
|
|
4415
3632
|
set({ providersOnCooldown: normalized });
|
|
4416
3633
|
},
|
|
4417
3634
|
addProviderOnCooldown: (baseUrl, timestamp) => {
|
|
4418
|
-
const normalized =
|
|
3635
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4419
3636
|
const current = get().providersOnCooldown;
|
|
4420
3637
|
if (!current.some((entry) => entry.baseUrl === normalized)) {
|
|
4421
3638
|
const updated = [...current, { baseUrl: normalized, timestamp }];
|
|
@@ -4424,7 +3641,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4424
3641
|
}
|
|
4425
3642
|
},
|
|
4426
3643
|
removeProviderFromCooldown: (baseUrl) => {
|
|
4427
|
-
const normalized =
|
|
3644
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4428
3645
|
const current = get().providersOnCooldown;
|
|
4429
3646
|
const updated = current.filter((entry) => entry.baseUrl !== normalized);
|
|
4430
3647
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
@@ -4495,40 +3712,40 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
4495
3712
|
]);
|
|
4496
3713
|
const modelsFromAllProviders = Object.fromEntries(
|
|
4497
3714
|
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
4498
|
-
|
|
3715
|
+
normalizeBaseUrl3(baseUrl),
|
|
4499
3716
|
models
|
|
4500
3717
|
])
|
|
4501
3718
|
);
|
|
4502
|
-
const baseUrlsList = rawBaseUrls.map((url) =>
|
|
3719
|
+
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl3(url));
|
|
4503
3720
|
const disabledProviders = rawDisabledProviders.map(
|
|
4504
|
-
(url) =>
|
|
3721
|
+
(url) => normalizeBaseUrl3(url)
|
|
4505
3722
|
);
|
|
4506
3723
|
const mintsFromAllProviders = Object.fromEntries(
|
|
4507
3724
|
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
4508
|
-
|
|
3725
|
+
normalizeBaseUrl3(baseUrl),
|
|
4509
3726
|
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
4510
3727
|
])
|
|
4511
3728
|
);
|
|
4512
3729
|
const infoFromAllProviders = Object.fromEntries(
|
|
4513
3730
|
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
4514
|
-
|
|
3731
|
+
normalizeBaseUrl3(baseUrl),
|
|
4515
3732
|
info
|
|
4516
3733
|
])
|
|
4517
3734
|
);
|
|
4518
3735
|
const lastModelsUpdate = Object.fromEntries(
|
|
4519
3736
|
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
4520
|
-
|
|
3737
|
+
normalizeBaseUrl3(baseUrl),
|
|
4521
3738
|
timestamp
|
|
4522
3739
|
])
|
|
4523
3740
|
);
|
|
4524
3741
|
const apiKeys = rawApiKeys.map((entry) => ({
|
|
4525
3742
|
...entry,
|
|
4526
|
-
baseUrl:
|
|
3743
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4527
3744
|
balance: entry.balance ?? 0,
|
|
4528
3745
|
lastUsed: entry.lastUsed ?? null
|
|
4529
3746
|
}));
|
|
4530
3747
|
const childKeys = rawChildKeys.map((entry) => ({
|
|
4531
|
-
parentBaseUrl:
|
|
3748
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
4532
3749
|
childKey: entry.childKey,
|
|
4533
3750
|
balance: entry.balance ?? 0,
|
|
4534
3751
|
balanceLimit: entry.balanceLimit,
|
|
@@ -4537,9 +3754,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
4537
3754
|
}));
|
|
4538
3755
|
const xcashuTokens = Object.fromEntries(
|
|
4539
3756
|
Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
|
|
4540
|
-
|
|
3757
|
+
normalizeBaseUrl3(baseUrl),
|
|
4541
3758
|
tokens.map((entry) => ({
|
|
4542
|
-
baseUrl:
|
|
3759
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4543
3760
|
token: entry.token,
|
|
4544
3761
|
createdAt: entry.createdAt ?? Date.now(),
|
|
4545
3762
|
tryCount: entry.tryCount ?? 0
|
|
@@ -4560,16 +3777,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
4560
3777
|
lastUsed: entry.lastUsed ?? null
|
|
4561
3778
|
}));
|
|
4562
3779
|
const failedProviders = rawFailedProviders.map(
|
|
4563
|
-
(url) =>
|
|
3780
|
+
(url) => normalizeBaseUrl3(url)
|
|
4564
3781
|
);
|
|
4565
3782
|
const lastFailed = Object.fromEntries(
|
|
4566
3783
|
Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
|
|
4567
|
-
|
|
3784
|
+
normalizeBaseUrl3(baseUrl),
|
|
4568
3785
|
timestamp
|
|
4569
3786
|
])
|
|
4570
3787
|
);
|
|
4571
3788
|
const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
|
|
4572
|
-
baseUrl:
|
|
3789
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4573
3790
|
timestamp: entry.timestamp
|
|
4574
3791
|
}));
|
|
4575
3792
|
store.setState({
|
|
@@ -4610,12 +3827,12 @@ var createDiscoveryAdapterFromStore = (store) => ({
|
|
|
4610
3827
|
getCachedProviderInfo: () => store.getState().infoFromAllProviders,
|
|
4611
3828
|
setCachedProviderInfo: (info) => store.getState().setInfoFromAllProviders(info),
|
|
4612
3829
|
getProviderLastUpdate: (baseUrl) => {
|
|
4613
|
-
const normalized =
|
|
3830
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4614
3831
|
const timestamps = store.getState().lastModelsUpdate;
|
|
4615
3832
|
return timestamps[normalized] || null;
|
|
4616
3833
|
},
|
|
4617
3834
|
setProviderLastUpdate: (baseUrl, timestamp) => {
|
|
4618
|
-
const normalized =
|
|
3835
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4619
3836
|
const timestamps = { ...store.getState().lastModelsUpdate };
|
|
4620
3837
|
timestamps[normalized] = timestamp;
|
|
4621
3838
|
store.getState().setLastModelsUpdate(timestamps);
|
|
@@ -4644,24 +3861,24 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4644
3861
|
return Object.entries(distributionMap).map(([baseUrl, amt]) => ({ baseUrl, amount: amt })).sort((a, b) => b.amount - a.amount);
|
|
4645
3862
|
},
|
|
4646
3863
|
saveProviderInfo: (baseUrl, info) => {
|
|
4647
|
-
const normalized =
|
|
3864
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4648
3865
|
const next = { ...store.getState().infoFromAllProviders };
|
|
4649
3866
|
next[normalized] = info;
|
|
4650
3867
|
store.getState().setInfoFromAllProviders(next);
|
|
4651
3868
|
},
|
|
4652
3869
|
getProviderInfo: (baseUrl) => {
|
|
4653
|
-
const normalized =
|
|
3870
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4654
3871
|
return store.getState().infoFromAllProviders[normalized] || null;
|
|
4655
3872
|
},
|
|
4656
3873
|
// ========== API Keys (for apikeys mode) ==========
|
|
4657
3874
|
getApiKey: (baseUrl) => {
|
|
4658
|
-
const normalized =
|
|
3875
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4659
3876
|
const entry = store.getState().apiKeys.find((key) => key.baseUrl === normalized);
|
|
4660
3877
|
if (!entry) return null;
|
|
4661
3878
|
return entry;
|
|
4662
3879
|
},
|
|
4663
3880
|
setApiKey: (baseUrl, key) => {
|
|
4664
|
-
const normalized =
|
|
3881
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4665
3882
|
const keys = store.getState().apiKeys;
|
|
4666
3883
|
const existingIndex = keys.findIndex(
|
|
4667
3884
|
(entry) => entry.baseUrl === normalized
|
|
@@ -4679,7 +3896,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4679
3896
|
store.getState().setApiKeys(next);
|
|
4680
3897
|
},
|
|
4681
3898
|
updateApiKeyBalance: (baseUrl, balance) => {
|
|
4682
|
-
const normalized =
|
|
3899
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4683
3900
|
const keys = store.getState().apiKeys;
|
|
4684
3901
|
const next = keys.map(
|
|
4685
3902
|
(entry) => entry.baseUrl === normalized ? { ...entry, balance, lastUsed: Date.now() } : entry
|
|
@@ -4687,7 +3904,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4687
3904
|
store.getState().setApiKeys(next);
|
|
4688
3905
|
},
|
|
4689
3906
|
removeApiKey: (baseUrl) => {
|
|
4690
|
-
const normalized =
|
|
3907
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4691
3908
|
const next = store.getState().apiKeys.filter((entry) => entry.baseUrl !== normalized);
|
|
4692
3909
|
store.getState().setApiKeys(next);
|
|
4693
3910
|
},
|
|
@@ -4701,7 +3918,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4701
3918
|
},
|
|
4702
3919
|
// ========== Child Keys ==========
|
|
4703
3920
|
getChildKey: (parentBaseUrl) => {
|
|
4704
|
-
const normalized =
|
|
3921
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4705
3922
|
const entry = store.getState().childKeys.find((key) => key.parentBaseUrl === normalized);
|
|
4706
3923
|
if (!entry) return null;
|
|
4707
3924
|
return {
|
|
@@ -4714,7 +3931,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4714
3931
|
};
|
|
4715
3932
|
},
|
|
4716
3933
|
setChildKey: (parentBaseUrl, childKey, balance, validityDate, balanceLimit) => {
|
|
4717
|
-
const normalized =
|
|
3934
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4718
3935
|
const keys = store.getState().childKeys;
|
|
4719
3936
|
const existingIndex = keys.findIndex(
|
|
4720
3937
|
(entry) => entry.parentBaseUrl === normalized
|
|
@@ -4745,7 +3962,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4745
3962
|
}
|
|
4746
3963
|
},
|
|
4747
3964
|
updateChildKeyBalance: (parentBaseUrl, balance) => {
|
|
4748
|
-
const normalized =
|
|
3965
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4749
3966
|
const keys = store.getState().childKeys;
|
|
4750
3967
|
const next = keys.map(
|
|
4751
3968
|
(entry) => entry.parentBaseUrl === normalized ? { ...entry, balance } : entry
|
|
@@ -4753,7 +3970,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4753
3970
|
store.getState().setChildKeys(next);
|
|
4754
3971
|
},
|
|
4755
3972
|
removeChildKey: (parentBaseUrl) => {
|
|
4756
|
-
const normalized =
|
|
3973
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4757
3974
|
const next = store.getState().childKeys.filter((entry) => entry.parentBaseUrl !== normalized);
|
|
4758
3975
|
store.getState().setChildKeys(next);
|
|
4759
3976
|
},
|
|
@@ -4778,11 +3995,11 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4778
3995
|
return store.getState().xcashuTokens;
|
|
4779
3996
|
},
|
|
4780
3997
|
getXcashuTokensForBaseUrl: (baseUrl) => {
|
|
4781
|
-
const normalized =
|
|
3998
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4782
3999
|
return store.getState().xcashuTokens[normalized] || [];
|
|
4783
4000
|
},
|
|
4784
4001
|
addXcashuToken: (baseUrl, token) => {
|
|
4785
|
-
const normalized =
|
|
4002
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4786
4003
|
const tokens = store.getState().xcashuTokens;
|
|
4787
4004
|
const existing = tokens[normalized] || [];
|
|
4788
4005
|
const next = { ...tokens };
|
|
@@ -4793,7 +4010,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4793
4010
|
store.getState().setXcashuTokens(next);
|
|
4794
4011
|
},
|
|
4795
4012
|
removeXcashuToken: (baseUrl, token) => {
|
|
4796
|
-
const normalized =
|
|
4013
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4797
4014
|
const tokens = store.getState().xcashuTokens;
|
|
4798
4015
|
const existing = tokens[normalized] || [];
|
|
4799
4016
|
const next = { ...tokens };
|
|
@@ -4804,7 +4021,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4804
4021
|
store.getState().setXcashuTokens(next);
|
|
4805
4022
|
},
|
|
4806
4023
|
clearXcashuTokensForBaseUrl: (baseUrl) => {
|
|
4807
|
-
const normalized =
|
|
4024
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4808
4025
|
const tokens = store.getState().xcashuTokens;
|
|
4809
4026
|
const next = { ...tokens };
|
|
4810
4027
|
delete next[normalized];
|
|
@@ -4818,16 +4035,16 @@ var createProviderRegistryFromStore = (store, logger) => {
|
|
|
4818
4035
|
const log = (logger ?? consoleLogger).child("ProviderRegistry");
|
|
4819
4036
|
return {
|
|
4820
4037
|
getModelsForProvider: (baseUrl) => {
|
|
4821
|
-
const normalized =
|
|
4038
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4822
4039
|
return store.getState().modelsFromAllProviders[normalized] || [];
|
|
4823
4040
|
},
|
|
4824
4041
|
getDisabledProviders: () => store.getState().disabledProviders,
|
|
4825
4042
|
getProviderMints: (baseUrl) => {
|
|
4826
|
-
const normalized =
|
|
4043
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4827
4044
|
return store.getState().mintsFromAllProviders[normalized] || [];
|
|
4828
4045
|
},
|
|
4829
4046
|
getProviderInfo: async (baseUrl) => {
|
|
4830
|
-
const normalized =
|
|
4047
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4831
4048
|
const cached = store.getState().infoFromAllProviders[normalized];
|
|
4832
4049
|
if (cached) return cached;
|
|
4833
4050
|
try {
|
|
@@ -4853,11 +4070,11 @@ var createProviderRegistryFromStore = (store, logger) => {
|
|
|
4853
4070
|
var MODEL_KEY_PREFIX = "models:provider:";
|
|
4854
4071
|
var MODEL_TS_KEY_PREFIX = "models:provider_timestamp:";
|
|
4855
4072
|
var PROVIDER_INDEX_KEY = "models:provider_index";
|
|
4856
|
-
var
|
|
4073
|
+
var MIGRATION_MARKER_KEY2 = "models_sharded_migration_v1";
|
|
4857
4074
|
var encodeBaseUrl = (baseUrl) => encodeURIComponent(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
4858
4075
|
var modelKey = (baseUrl) => `${MODEL_KEY_PREFIX}${encodeBaseUrl(baseUrl)}`;
|
|
4859
4076
|
var modelTsKey = (baseUrl) => `${MODEL_TS_KEY_PREFIX}${encodeBaseUrl(baseUrl)}`;
|
|
4860
|
-
var
|
|
4077
|
+
var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
4861
4078
|
var createShardedDiscoveryAdapter = async (options) => {
|
|
4862
4079
|
const { driver } = options;
|
|
4863
4080
|
const legacyModels = await driver.getItem(SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS, {});
|
|
@@ -4865,7 +4082,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4865
4082
|
if (Object.keys(legacyModels).length > 0) {
|
|
4866
4083
|
const migratedProviders = [];
|
|
4867
4084
|
for (const [baseUrl, models] of Object.entries(legacyModels)) {
|
|
4868
|
-
const normalized =
|
|
4085
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4869
4086
|
await driver.setItem(modelKey(normalized), models);
|
|
4870
4087
|
const ts = legacyTimestamps[normalized] ?? Date.now();
|
|
4871
4088
|
await driver.setItem(modelTsKey(normalized), ts);
|
|
@@ -4880,7 +4097,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4880
4097
|
await driver.removeItem(SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS);
|
|
4881
4098
|
await driver.removeItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE);
|
|
4882
4099
|
}
|
|
4883
|
-
await driver.setItem(
|
|
4100
|
+
await driver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
4884
4101
|
const [
|
|
4885
4102
|
rawMints,
|
|
4886
4103
|
rawInfo,
|
|
@@ -4914,31 +4131,31 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4914
4131
|
const providerIndex = /* @__PURE__ */ new Set();
|
|
4915
4132
|
const knownProviders = /* @__PURE__ */ new Set();
|
|
4916
4133
|
for (const baseUrl of Object.keys(rawInfo)) {
|
|
4917
|
-
knownProviders.add(
|
|
4134
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4918
4135
|
}
|
|
4919
4136
|
for (const baseUrl of Object.keys(rawMints)) {
|
|
4920
|
-
knownProviders.add(
|
|
4137
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4921
4138
|
}
|
|
4922
4139
|
for (const baseUrl of rawBaseUrls) {
|
|
4923
|
-
knownProviders.add(
|
|
4140
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4924
4141
|
}
|
|
4925
4142
|
for (const baseUrl of rawDisabled) {
|
|
4926
|
-
knownProviders.add(
|
|
4143
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4927
4144
|
}
|
|
4928
4145
|
for (const baseUrl of Object.keys(legacyModels)) {
|
|
4929
|
-
knownProviders.add(
|
|
4146
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4930
4147
|
}
|
|
4931
4148
|
const indexProviders = await driver.getItem(
|
|
4932
4149
|
PROVIDER_INDEX_KEY,
|
|
4933
4150
|
[]
|
|
4934
4151
|
);
|
|
4935
4152
|
for (const baseUrl of indexProviders) {
|
|
4936
|
-
const normalized =
|
|
4153
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4937
4154
|
providerIndex.add(normalized);
|
|
4938
4155
|
knownProviders.add(normalized);
|
|
4939
4156
|
}
|
|
4940
4157
|
for (const baseUrl of knownProviders) {
|
|
4941
|
-
const normalized =
|
|
4158
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4942
4159
|
const models = await driver.getItem(
|
|
4943
4160
|
modelKey(normalized),
|
|
4944
4161
|
null
|
|
@@ -4959,19 +4176,19 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4959
4176
|
}
|
|
4960
4177
|
let mints = Object.fromEntries(
|
|
4961
4178
|
Object.entries(rawMints).map(([baseUrl, mintList]) => [
|
|
4962
|
-
|
|
4179
|
+
normalizeBaseUrl4(baseUrl),
|
|
4963
4180
|
mintList.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
4964
4181
|
])
|
|
4965
4182
|
);
|
|
4966
4183
|
let info = Object.fromEntries(
|
|
4967
4184
|
Object.entries(rawInfo).map(([baseUrl, entry]) => [
|
|
4968
|
-
|
|
4185
|
+
normalizeBaseUrl4(baseUrl),
|
|
4969
4186
|
entry
|
|
4970
4187
|
])
|
|
4971
4188
|
);
|
|
4972
4189
|
let _lastUsedModel = lastUsedModel;
|
|
4973
|
-
let _disabledProviders = rawDisabled.map(
|
|
4974
|
-
let _baseUrlsList = rawBaseUrls.map(
|
|
4190
|
+
let _disabledProviders = rawDisabled.map(normalizeBaseUrl4);
|
|
4191
|
+
let _baseUrlsList = rawBaseUrls.map(normalizeBaseUrl4);
|
|
4975
4192
|
let _lastBaseUrlsUpdate = lastBaseUrlsUpdate;
|
|
4976
4193
|
let _routstr21Models = rawRoutstr21Models;
|
|
4977
4194
|
let _lastRoutstr21ModelsUpdate = lastRoutstr21ModelsUpdate;
|
|
@@ -4989,10 +4206,10 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4989
4206
|
},
|
|
4990
4207
|
setCachedModels: (models) => {
|
|
4991
4208
|
const nextKeys = new Set(
|
|
4992
|
-
Object.keys(models).map((baseUrl) =>
|
|
4209
|
+
Object.keys(models).map((baseUrl) => normalizeBaseUrl4(baseUrl))
|
|
4993
4210
|
);
|
|
4994
4211
|
for (const baseUrl of [...modelsByBaseUrl.keys()]) {
|
|
4995
|
-
if (!nextKeys.has(
|
|
4212
|
+
if (!nextKeys.has(normalizeBaseUrl4(baseUrl))) {
|
|
4996
4213
|
providerIndex.delete(baseUrl);
|
|
4997
4214
|
modelsByBaseUrl.delete(baseUrl);
|
|
4998
4215
|
timestampsByBaseUrl.delete(baseUrl);
|
|
@@ -5001,7 +4218,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5001
4218
|
}
|
|
5002
4219
|
}
|
|
5003
4220
|
for (const [baseUrl, modelList] of Object.entries(models)) {
|
|
5004
|
-
const normalized =
|
|
4221
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5005
4222
|
providerIndex.add(normalized);
|
|
5006
4223
|
modelsByBaseUrl.set(normalized, modelList);
|
|
5007
4224
|
const ts = timestampsByBaseUrl.get(normalized) ?? Date.now();
|
|
@@ -5012,10 +4229,10 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5012
4229
|
persistProviderIndex();
|
|
5013
4230
|
},
|
|
5014
4231
|
getProviderLastUpdate: (baseUrl) => {
|
|
5015
|
-
return timestampsByBaseUrl.get(
|
|
4232
|
+
return timestampsByBaseUrl.get(normalizeBaseUrl4(baseUrl)) ?? null;
|
|
5016
4233
|
},
|
|
5017
4234
|
setProviderLastUpdate: (baseUrl, timestamp) => {
|
|
5018
|
-
const normalized =
|
|
4235
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5019
4236
|
providerIndex.add(normalized);
|
|
5020
4237
|
timestampsByBaseUrl.set(normalized, timestamp);
|
|
5021
4238
|
void driver.setItem(modelTsKey(normalized), timestamp);
|
|
@@ -5026,7 +4243,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5026
4243
|
setCachedMints: (value) => {
|
|
5027
4244
|
const normalized = {};
|
|
5028
4245
|
for (const [baseUrl, mintList] of Object.entries(value)) {
|
|
5029
|
-
normalized[
|
|
4246
|
+
normalized[normalizeBaseUrl4(baseUrl)] = mintList.map(
|
|
5030
4247
|
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
5031
4248
|
);
|
|
5032
4249
|
}
|
|
@@ -5038,7 +4255,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5038
4255
|
setCachedProviderInfo: (value) => {
|
|
5039
4256
|
const normalized = {};
|
|
5040
4257
|
for (const [baseUrl, entry] of Object.entries(value)) {
|
|
5041
|
-
normalized[
|
|
4258
|
+
normalized[normalizeBaseUrl4(baseUrl)] = entry;
|
|
5042
4259
|
}
|
|
5043
4260
|
info = normalized;
|
|
5044
4261
|
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
@@ -5052,7 +4269,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5052
4269
|
// -- Disabled providers (kv) --
|
|
5053
4270
|
getDisabledProviders: () => _disabledProviders,
|
|
5054
4271
|
setDisabledProviders: (urls) => {
|
|
5055
|
-
const normalized = urls.map(
|
|
4272
|
+
const normalized = urls.map(normalizeBaseUrl4);
|
|
5056
4273
|
_disabledProviders = normalized;
|
|
5057
4274
|
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
5058
4275
|
},
|
|
@@ -5060,7 +4277,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5060
4277
|
getBaseUrlsList: () => _baseUrlsList,
|
|
5061
4278
|
getBaseUrlsLastUpdate: () => _lastBaseUrlsUpdate,
|
|
5062
4279
|
setBaseUrlsList: (urls) => {
|
|
5063
|
-
const normalized = urls.map(
|
|
4280
|
+
const normalized = urls.map(normalizeBaseUrl4);
|
|
5064
4281
|
_baseUrlsList = normalized;
|
|
5065
4282
|
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
5066
4283
|
},
|
|
@@ -5088,16 +4305,16 @@ var createProviderRegistryFromDiscoveryAdapter = (adapter, logger) => {
|
|
|
5088
4305
|
const log = (logger ?? consoleLogger).child("ProviderRegistry");
|
|
5089
4306
|
return {
|
|
5090
4307
|
getModelsForProvider: (baseUrl) => {
|
|
5091
|
-
const normalized =
|
|
4308
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5092
4309
|
return adapter.getCachedModels()[normalized] || [];
|
|
5093
4310
|
},
|
|
5094
4311
|
getDisabledProviders: () => adapter.getDisabledProviders(),
|
|
5095
4312
|
getProviderMints: (baseUrl) => {
|
|
5096
|
-
const normalized =
|
|
4313
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5097
4314
|
return adapter.getCachedMints()[normalized] || [];
|
|
5098
4315
|
},
|
|
5099
4316
|
getProviderInfo: async (baseUrl) => {
|
|
5100
|
-
const normalized =
|
|
4317
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5101
4318
|
const cached = adapter.getCachedProviderInfo()[normalized];
|
|
5102
4319
|
if (cached) return cached;
|
|
5103
4320
|
try {
|
|
@@ -5128,89 +4345,222 @@ var isBrowser3 = () => {
|
|
|
5128
4345
|
return false;
|
|
5129
4346
|
}
|
|
5130
4347
|
};
|
|
5131
|
-
var isNode = () => {
|
|
5132
|
-
try {
|
|
5133
|
-
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
5134
|
-
} catch {
|
|
5135
|
-
return false;
|
|
5136
|
-
}
|
|
5137
|
-
};
|
|
5138
4348
|
var defaultDriver = null;
|
|
5139
|
-
var isBun3 = () => {
|
|
5140
|
-
return typeof process.versions.bun !== "undefined";
|
|
5141
|
-
};
|
|
5142
4349
|
var getDefaultSdkDriver = () => {
|
|
5143
4350
|
if (defaultDriver) return defaultDriver;
|
|
5144
4351
|
if (isBrowser3()) {
|
|
5145
4352
|
defaultDriver = localStorageDriver;
|
|
5146
4353
|
return defaultDriver;
|
|
5147
4354
|
}
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
4355
|
+
defaultDriver = createMemoryDriver();
|
|
4356
|
+
return defaultDriver;
|
|
4357
|
+
};
|
|
4358
|
+
var defaultStore = null;
|
|
4359
|
+
var defaultUsageTrackingDriver = null;
|
|
4360
|
+
var getDefaultSdkStore = () => {
|
|
4361
|
+
if (!defaultStore) {
|
|
4362
|
+
defaultStore = createSdkStore({ driver: getDefaultSdkDriver() });
|
|
4363
|
+
}
|
|
4364
|
+
return defaultStore.hydrate.then(() => defaultStore.store);
|
|
4365
|
+
};
|
|
4366
|
+
var getDefaultUsageTrackingDriver = () => {
|
|
4367
|
+
if (defaultUsageTrackingDriver) return defaultUsageTrackingDriver;
|
|
4368
|
+
const storageDriver = getDefaultSdkDriver();
|
|
4369
|
+
if (isBrowser3()) {
|
|
4370
|
+
defaultUsageTrackingDriver = createIndexedDBUsageTrackingDriver({
|
|
4371
|
+
legacyStorageDriver: storageDriver
|
|
4372
|
+
});
|
|
4373
|
+
return defaultUsageTrackingDriver;
|
|
4374
|
+
}
|
|
4375
|
+
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
4376
|
+
return defaultUsageTrackingDriver;
|
|
4377
|
+
};
|
|
4378
|
+
var setDefaultUsageTrackingDriver = (driver) => {
|
|
4379
|
+
defaultUsageTrackingDriver = driver;
|
|
4380
|
+
};
|
|
4381
|
+
var defaultDiscoveryAdapter = null;
|
|
4382
|
+
var getDefaultDiscoveryAdapter = async () => {
|
|
4383
|
+
if (defaultDiscoveryAdapter) return defaultDiscoveryAdapter;
|
|
4384
|
+
const driver = getDefaultSdkDriver();
|
|
4385
|
+
defaultDiscoveryAdapter = await createShardedDiscoveryAdapter({ driver });
|
|
4386
|
+
return defaultDiscoveryAdapter;
|
|
4387
|
+
};
|
|
4388
|
+
var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
|
|
4389
|
+
var getDefaultProviderRegistry = async () => createProviderRegistryFromDiscoveryAdapter(await getDefaultDiscoveryAdapter());
|
|
4390
|
+
|
|
4391
|
+
// client/usage.ts
|
|
4392
|
+
var numOrUndef = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4393
|
+
function extractCostBreakdown(costObj) {
|
|
4394
|
+
if (!costObj || typeof costObj !== "object") return {};
|
|
4395
|
+
return {
|
|
4396
|
+
baseMsats: numOrUndef(costObj.base_msats),
|
|
4397
|
+
inputMsats: numOrUndef(costObj.input_msats),
|
|
4398
|
+
outputMsats: numOrUndef(costObj.output_msats),
|
|
4399
|
+
totalMsats: numOrUndef(costObj.total_msats),
|
|
4400
|
+
totalUsd: numOrUndef(costObj.total_usd),
|
|
4401
|
+
cacheReadInputTokens: numOrUndef(costObj.cache_read_input_tokens),
|
|
4402
|
+
cacheCreationInputTokens: numOrUndef(costObj.cache_creation_input_tokens),
|
|
4403
|
+
cacheReadMsats: numOrUndef(costObj.cache_read_msats),
|
|
4404
|
+
cacheCreationMsats: numOrUndef(costObj.cache_creation_msats),
|
|
4405
|
+
remainingBalanceMsats: numOrUndef(costObj.remaining_balance_msats)
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
function extractUsageFromResponseBody(body, fallbackSatsCost = 0) {
|
|
4409
|
+
if (!body || typeof body !== "object") return null;
|
|
4410
|
+
const usage = body.usage;
|
|
4411
|
+
if (!usage || typeof usage !== "object") return null;
|
|
4412
|
+
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
4413
|
+
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
4414
|
+
const totalTokens = Number(usage.total_tokens ?? 0);
|
|
4415
|
+
const costValue = usage.cost;
|
|
4416
|
+
let cost = 0;
|
|
4417
|
+
let satsCost = fallbackSatsCost;
|
|
4418
|
+
let breakdown = {};
|
|
4419
|
+
if (typeof costValue === "number") {
|
|
4420
|
+
cost = costValue;
|
|
4421
|
+
} else if (costValue && typeof costValue === "object") {
|
|
4422
|
+
const costObj = costValue;
|
|
4423
|
+
const totalUsd = costObj.total_usd;
|
|
4424
|
+
const totalMsats = costObj.total_msats;
|
|
4425
|
+
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
4426
|
+
if (typeof totalMsats === "number") {
|
|
4427
|
+
satsCost = totalMsats / 1e3;
|
|
4428
|
+
}
|
|
4429
|
+
breakdown = extractCostBreakdown(costObj);
|
|
4430
|
+
}
|
|
4431
|
+
const provider = typeof body.provider === "string" ? body.provider : void 0;
|
|
4432
|
+
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
4433
|
+
return null;
|
|
4434
|
+
}
|
|
4435
|
+
return {
|
|
4436
|
+
promptTokens,
|
|
4437
|
+
completionTokens,
|
|
4438
|
+
totalTokens,
|
|
4439
|
+
cost,
|
|
4440
|
+
satsCost,
|
|
4441
|
+
provider,
|
|
4442
|
+
...breakdown
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4445
|
+
function extractResponseId(body) {
|
|
4446
|
+
if (!body || typeof body !== "object") return void 0;
|
|
4447
|
+
const id = body.id;
|
|
4448
|
+
if (typeof id !== "string") return void 0;
|
|
4449
|
+
const trimmed = id.trim();
|
|
4450
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
4451
|
+
}
|
|
4452
|
+
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
4453
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4454
|
+
return null;
|
|
4455
|
+
}
|
|
4456
|
+
const provider = typeof parsed.provider === "string" ? parsed.provider : void 0;
|
|
4457
|
+
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
4458
|
+
const costObj = parsed.cost;
|
|
4459
|
+
const msats2 = costObj.total_msats ?? 0;
|
|
4460
|
+
const cost2 = costObj.total_usd ?? 0;
|
|
4461
|
+
if (msats2 === 0 && cost2 === 0) return null;
|
|
4462
|
+
return {
|
|
4463
|
+
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
4464
|
+
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
4465
|
+
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
4466
|
+
cost: Number(cost2),
|
|
4467
|
+
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost,
|
|
4468
|
+
provider,
|
|
4469
|
+
...extractCostBreakdown(costObj)
|
|
4470
|
+
};
|
|
4471
|
+
}
|
|
4472
|
+
if (!parsed.usage) {
|
|
4473
|
+
return null;
|
|
5151
4474
|
}
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
4475
|
+
const usage = parsed.usage;
|
|
4476
|
+
const usageCost = usage.cost;
|
|
4477
|
+
let cost = 0;
|
|
4478
|
+
let msats = 0;
|
|
4479
|
+
let breakdown = {};
|
|
4480
|
+
if (typeof usageCost === "number") {
|
|
4481
|
+
cost = usageCost;
|
|
4482
|
+
} else if (usageCost && typeof usageCost === "object") {
|
|
4483
|
+
cost = usageCost.total_usd ?? 0;
|
|
4484
|
+
msats = usageCost.total_msats ?? 0;
|
|
4485
|
+
breakdown = extractCostBreakdown(usageCost);
|
|
5155
4486
|
}
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
};
|
|
5159
|
-
var defaultStore = null;
|
|
5160
|
-
var defaultUsageTrackingDriver = null;
|
|
5161
|
-
var getDefaultSdkStore = () => {
|
|
5162
|
-
if (!defaultStore) {
|
|
5163
|
-
defaultStore = createSdkStore({ driver: getDefaultSdkDriver() });
|
|
4487
|
+
const routstrCost = parsed.metadata?.routstr?.cost;
|
|
4488
|
+
if (routstrCost && typeof routstrCost === "object") {
|
|
4489
|
+
breakdown = { ...extractCostBreakdown(routstrCost), ...breakdown };
|
|
5164
4490
|
}
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
var getDefaultUsageTrackingDriver = () => {
|
|
5168
|
-
if (defaultUsageTrackingDriver) return defaultUsageTrackingDriver;
|
|
5169
|
-
const storageDriver = getDefaultSdkDriver();
|
|
5170
|
-
if (isBrowser3()) {
|
|
5171
|
-
defaultUsageTrackingDriver = createIndexedDBUsageTrackingDriver({
|
|
5172
|
-
legacyStorageDriver: storageDriver
|
|
5173
|
-
});
|
|
5174
|
-
return defaultUsageTrackingDriver;
|
|
4491
|
+
if (cost === 0) {
|
|
4492
|
+
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
5175
4493
|
}
|
|
5176
|
-
if (
|
|
5177
|
-
|
|
5178
|
-
return defaultUsageTrackingDriver;
|
|
4494
|
+
if (msats === 0) {
|
|
4495
|
+
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
5179
4496
|
}
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
4497
|
+
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
4498
|
+
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
4499
|
+
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
4500
|
+
const result = {
|
|
4501
|
+
promptTokens,
|
|
4502
|
+
completionTokens,
|
|
4503
|
+
totalTokens,
|
|
4504
|
+
cost: Number(cost ?? 0),
|
|
4505
|
+
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost,
|
|
4506
|
+
provider,
|
|
4507
|
+
...breakdown
|
|
4508
|
+
};
|
|
4509
|
+
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
4510
|
+
return null;
|
|
5185
4511
|
}
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
};
|
|
5199
|
-
var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
|
|
5200
|
-
var getDefaultProviderRegistry = async () => createProviderRegistryFromDiscoveryAdapter(await getDefaultDiscoveryAdapter());
|
|
4512
|
+
return result;
|
|
4513
|
+
}
|
|
4514
|
+
function toUsageStats(usage) {
|
|
4515
|
+
if (!usage) return void 0;
|
|
4516
|
+
return {
|
|
4517
|
+
total_tokens: usage.totalTokens,
|
|
4518
|
+
prompt_tokens: usage.promptTokens,
|
|
4519
|
+
completion_tokens: usage.completionTokens,
|
|
4520
|
+
cost: usage.cost,
|
|
4521
|
+
sats_cost: usage.satsCost
|
|
4522
|
+
};
|
|
4523
|
+
}
|
|
5201
4524
|
function mergeUsage(previous, next) {
|
|
5202
4525
|
if (!previous) return next;
|
|
4526
|
+
const pickNum = (n, p) => typeof n === "number" && n > 0 ? n : p ?? n;
|
|
5203
4527
|
return {
|
|
5204
4528
|
promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
|
|
5205
4529
|
completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
|
|
5206
4530
|
totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
|
|
5207
4531
|
cost: next.cost > 0 ? next.cost : previous.cost,
|
|
5208
|
-
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
|
|
4532
|
+
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost,
|
|
4533
|
+
provider: next.provider ?? previous.provider,
|
|
4534
|
+
baseMsats: pickNum(next.baseMsats, previous.baseMsats),
|
|
4535
|
+
inputMsats: pickNum(next.inputMsats, previous.inputMsats),
|
|
4536
|
+
outputMsats: pickNum(next.outputMsats, previous.outputMsats),
|
|
4537
|
+
totalMsats: pickNum(next.totalMsats, previous.totalMsats),
|
|
4538
|
+
totalUsd: pickNum(next.totalUsd, previous.totalUsd),
|
|
4539
|
+
cacheReadInputTokens: pickNum(
|
|
4540
|
+
next.cacheReadInputTokens,
|
|
4541
|
+
previous.cacheReadInputTokens
|
|
4542
|
+
),
|
|
4543
|
+
cacheCreationInputTokens: pickNum(
|
|
4544
|
+
next.cacheCreationInputTokens,
|
|
4545
|
+
previous.cacheCreationInputTokens
|
|
4546
|
+
),
|
|
4547
|
+
cacheReadMsats: pickNum(next.cacheReadMsats, previous.cacheReadMsats),
|
|
4548
|
+
cacheCreationMsats: pickNum(
|
|
4549
|
+
next.cacheCreationMsats,
|
|
4550
|
+
previous.cacheCreationMsats
|
|
4551
|
+
),
|
|
4552
|
+
remainingBalanceMsats: pickNum(
|
|
4553
|
+
next.remainingBalanceMsats,
|
|
4554
|
+
previous.remainingBalanceMsats
|
|
4555
|
+
)
|
|
5209
4556
|
};
|
|
5210
4557
|
}
|
|
5211
4558
|
function hasUsageChanged(previous, next) {
|
|
5212
4559
|
if (!previous) return true;
|
|
5213
|
-
return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
|
|
4560
|
+
return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost || previous.provider !== next.provider || previous.totalMsats !== next.totalMsats || previous.remainingBalanceMsats !== next.remainingBalanceMsats;
|
|
4561
|
+
}
|
|
4562
|
+
function isInspectionComplete(responseIdCaptured, usage) {
|
|
4563
|
+
return responseIdCaptured && !!usage && usage.totalTokens > 0 && typeof usage.totalMsats === "number" && !!usage.provider;
|
|
5214
4564
|
}
|
|
5215
4565
|
async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
5216
4566
|
const reader = stream.getReader();
|
|
@@ -5220,14 +4570,22 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
5220
4570
|
let capturedResponseId;
|
|
5221
4571
|
let responseIdCaptured = false;
|
|
5222
4572
|
const inspectDataPayload = (jsonText) => {
|
|
5223
|
-
|
|
4573
|
+
const trimmed = jsonText.trim();
|
|
4574
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
4575
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
4576
|
+
return;
|
|
4577
|
+
}
|
|
4578
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
4579
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
5224
4580
|
return;
|
|
5225
4581
|
}
|
|
5226
|
-
const trimmed = jsonText.trim();
|
|
5227
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
5228
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
5229
4582
|
try {
|
|
5230
4583
|
const data = JSON.parse(trimmed);
|
|
4584
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
4585
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
4586
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
4587
|
+
return;
|
|
4588
|
+
}
|
|
5231
4589
|
if (!responseIdCaptured) {
|
|
5232
4590
|
const responseId = data?.id;
|
|
5233
4591
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -5238,19 +4596,21 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
5238
4596
|
}
|
|
5239
4597
|
const usage = extractUsageFromSSEJson(data);
|
|
5240
4598
|
if (usage) {
|
|
4599
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
5241
4600
|
const merged = mergeUsage(capturedUsage, usage);
|
|
5242
4601
|
if (hasUsageChanged(capturedUsage, merged)) {
|
|
5243
4602
|
capturedUsage = merged;
|
|
4603
|
+
console.log("[routstr:sse] \u2192 merged (changed):", merged);
|
|
5244
4604
|
onUsage(merged);
|
|
4605
|
+
} else {
|
|
4606
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
5245
4607
|
}
|
|
5246
4608
|
}
|
|
5247
4609
|
} catch {
|
|
4610
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
5248
4611
|
}
|
|
5249
4612
|
};
|
|
5250
4613
|
const inspectEventBlock = (eventBlock) => {
|
|
5251
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
5252
|
-
return;
|
|
5253
|
-
}
|
|
5254
4614
|
const lines = eventBlock.split(/\r?\n/);
|
|
5255
4615
|
const dataParts = [];
|
|
5256
4616
|
for (const line of lines) {
|
|
@@ -5308,14 +4668,22 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
5308
4668
|
let capturedUsage = null;
|
|
5309
4669
|
let responseIdCaptured = false;
|
|
5310
4670
|
const inspectDataPayload = (jsonText) => {
|
|
5311
|
-
|
|
4671
|
+
const trimmed = jsonText.trim();
|
|
4672
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
4673
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
4674
|
+
return;
|
|
4675
|
+
}
|
|
4676
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
4677
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
5312
4678
|
return;
|
|
5313
4679
|
}
|
|
5314
|
-
const trimmed = jsonText.trim();
|
|
5315
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
5316
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
5317
4680
|
try {
|
|
5318
4681
|
const data = JSON.parse(trimmed);
|
|
4682
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
4683
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
4684
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
4685
|
+
return;
|
|
4686
|
+
}
|
|
5319
4687
|
if (!responseIdCaptured) {
|
|
5320
4688
|
const responseId = data?.id;
|
|
5321
4689
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -5325,19 +4693,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
5325
4693
|
}
|
|
5326
4694
|
const usage = extractUsageFromSSEJson(data);
|
|
5327
4695
|
if (usage) {
|
|
4696
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
5328
4697
|
const mergedUsage = mergeUsage(capturedUsage, usage);
|
|
5329
4698
|
if (hasUsageChanged(capturedUsage, mergedUsage)) {
|
|
5330
4699
|
capturedUsage = mergedUsage;
|
|
4700
|
+
console.log("[routstr:sse] \u2192 merged (changed):", mergedUsage);
|
|
5331
4701
|
onUsage(mergedUsage);
|
|
4702
|
+
} else {
|
|
4703
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
5332
4704
|
}
|
|
5333
4705
|
}
|
|
5334
4706
|
} catch {
|
|
4707
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
5335
4708
|
}
|
|
5336
4709
|
};
|
|
5337
4710
|
const inspectEventBlock = (eventBlock) => {
|
|
5338
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
5339
|
-
return;
|
|
5340
|
-
}
|
|
5341
4711
|
const lines = eventBlock.split(/\r?\n/);
|
|
5342
4712
|
const dataParts = [];
|
|
5343
4713
|
for (const line of lines) {
|
|
@@ -5410,7 +4780,6 @@ var RoutstrClient = class {
|
|
|
5410
4780
|
this.balanceManager,
|
|
5411
4781
|
this.logger
|
|
5412
4782
|
);
|
|
5413
|
-
this.streamProcessor = new StreamProcessor();
|
|
5414
4783
|
this.alertLevel = alertLevel;
|
|
5415
4784
|
this.mode = mode;
|
|
5416
4785
|
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
@@ -5422,7 +4791,6 @@ var RoutstrClient = class {
|
|
|
5422
4791
|
providerRegistry;
|
|
5423
4792
|
cashuSpender;
|
|
5424
4793
|
balanceManager;
|
|
5425
|
-
streamProcessor;
|
|
5426
4794
|
providerManager;
|
|
5427
4795
|
alertLevel;
|
|
5428
4796
|
mode;
|
|
@@ -5664,153 +5032,6 @@ var RoutstrClient = class {
|
|
|
5664
5032
|
}
|
|
5665
5033
|
return void 0;
|
|
5666
5034
|
}
|
|
5667
|
-
/**
|
|
5668
|
-
* Fetch AI response with streaming
|
|
5669
|
-
*/
|
|
5670
|
-
async fetchAIResponse(options, callbacks) {
|
|
5671
|
-
const {
|
|
5672
|
-
messageHistory,
|
|
5673
|
-
selectedModel,
|
|
5674
|
-
baseUrl,
|
|
5675
|
-
mintUrl,
|
|
5676
|
-
balance,
|
|
5677
|
-
transactionHistory,
|
|
5678
|
-
maxTokens,
|
|
5679
|
-
headers
|
|
5680
|
-
} = options;
|
|
5681
|
-
const apiMessages = await this._convertMessages(messageHistory);
|
|
5682
|
-
const requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
5683
|
-
selectedModel,
|
|
5684
|
-
apiMessages,
|
|
5685
|
-
maxTokens
|
|
5686
|
-
);
|
|
5687
|
-
try {
|
|
5688
|
-
await this._checkBalance();
|
|
5689
|
-
callbacks.onPaymentProcessing?.(true);
|
|
5690
|
-
const spendResult = await this._spendToken({
|
|
5691
|
-
mintUrl,
|
|
5692
|
-
amount: requiredSats,
|
|
5693
|
-
baseUrl
|
|
5694
|
-
});
|
|
5695
|
-
let token = spendResult.token;
|
|
5696
|
-
let tokenBalance = spendResult.tokenBalance;
|
|
5697
|
-
let tokenBalanceUnit = spendResult.tokenBalanceUnit;
|
|
5698
|
-
let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
5699
|
-
let initialTokenBalanceUnknown = spendResult.tokenBalanceUnknown;
|
|
5700
|
-
callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
|
|
5701
|
-
const baseHeaders = this._buildBaseHeaders(headers);
|
|
5702
|
-
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
5703
|
-
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
5704
|
-
const providerVersion = providerInfo?.version ?? "";
|
|
5705
|
-
let modelIdForRequest = selectedModel.id;
|
|
5706
|
-
if (/^0\.1\./.test(providerVersion)) {
|
|
5707
|
-
const newModel = await this.providerManager.getModelForProvider(
|
|
5708
|
-
baseUrl,
|
|
5709
|
-
selectedModel.id
|
|
5710
|
-
);
|
|
5711
|
-
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
5712
|
-
}
|
|
5713
|
-
const body = {
|
|
5714
|
-
model: modelIdForRequest,
|
|
5715
|
-
messages: apiMessages,
|
|
5716
|
-
stream: true
|
|
5717
|
-
};
|
|
5718
|
-
if (maxTokens !== void 0) {
|
|
5719
|
-
body.max_tokens = maxTokens;
|
|
5720
|
-
}
|
|
5721
|
-
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
5722
|
-
body.tools = [{ type: "web_search" }];
|
|
5723
|
-
}
|
|
5724
|
-
const response = await this._makeRequest({
|
|
5725
|
-
path: "/v1/chat/completions",
|
|
5726
|
-
method: "POST",
|
|
5727
|
-
body,
|
|
5728
|
-
selectedModel,
|
|
5729
|
-
baseUrl,
|
|
5730
|
-
mintUrl,
|
|
5731
|
-
token,
|
|
5732
|
-
requiredSats,
|
|
5733
|
-
maxTokens,
|
|
5734
|
-
headers: requestHeaders,
|
|
5735
|
-
baseHeaders
|
|
5736
|
-
});
|
|
5737
|
-
if (!response.body) {
|
|
5738
|
-
throw new Error("Response body is not available");
|
|
5739
|
-
}
|
|
5740
|
-
if (response.status === 200) {
|
|
5741
|
-
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
5742
|
-
const responseToken = response.token || token;
|
|
5743
|
-
if (baseUrlUsed !== baseUrl || responseToken !== token) {
|
|
5744
|
-
token = responseToken;
|
|
5745
|
-
if (typeof response.initialTokenBalanceInSats === "number") {
|
|
5746
|
-
tokenBalanceInSats = response.initialTokenBalanceInSats;
|
|
5747
|
-
initialTokenBalanceUnknown = Boolean(
|
|
5748
|
-
response.initialTokenBalanceUnknown
|
|
5749
|
-
);
|
|
5750
|
-
} else {
|
|
5751
|
-
initialTokenBalanceUnknown = true;
|
|
5752
|
-
}
|
|
5753
|
-
}
|
|
5754
|
-
const streamingResult = await this.streamProcessor.process(
|
|
5755
|
-
response,
|
|
5756
|
-
{
|
|
5757
|
-
onContent: callbacks.onStreamingUpdate,
|
|
5758
|
-
onThinking: callbacks.onThinkingUpdate
|
|
5759
|
-
},
|
|
5760
|
-
selectedModel.id
|
|
5761
|
-
);
|
|
5762
|
-
if (streamingResult.finish_reason === "content_filter") {
|
|
5763
|
-
callbacks.onMessageAppend({
|
|
5764
|
-
role: "assistant",
|
|
5765
|
-
content: "Your request was denied due to content filtering."
|
|
5766
|
-
});
|
|
5767
|
-
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
5768
|
-
const message = await this._createAssistantMessage(streamingResult);
|
|
5769
|
-
callbacks.onMessageAppend(message);
|
|
5770
|
-
} else {
|
|
5771
|
-
callbacks.onMessageAppend({
|
|
5772
|
-
role: "system",
|
|
5773
|
-
content: "The provider did not respond to this request."
|
|
5774
|
-
});
|
|
5775
|
-
}
|
|
5776
|
-
callbacks.onStreamingUpdate("");
|
|
5777
|
-
callbacks.onThinkingUpdate("");
|
|
5778
|
-
const isApikeysEstimate = this.mode === "apikeys";
|
|
5779
|
-
let satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
5780
|
-
token,
|
|
5781
|
-
baseUrl: baseUrlUsed,
|
|
5782
|
-
mintUrl,
|
|
5783
|
-
initialTokenBalance: tokenBalanceInSats,
|
|
5784
|
-
initialTokenBalanceUnknown,
|
|
5785
|
-
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
5786
|
-
response,
|
|
5787
|
-
modelId: selectedModel.id,
|
|
5788
|
-
usage: streamingResult.usage ? {
|
|
5789
|
-
promptTokens: Number(streamingResult.usage.prompt_tokens ?? 0),
|
|
5790
|
-
completionTokens: Number(
|
|
5791
|
-
streamingResult.usage.completion_tokens ?? 0
|
|
5792
|
-
),
|
|
5793
|
-
totalTokens: Number(streamingResult.usage.total_tokens ?? 0),
|
|
5794
|
-
cost: Number(streamingResult.usage.cost ?? 0),
|
|
5795
|
-
satsCost: Number(streamingResult.usage.sats_cost ?? 0)
|
|
5796
|
-
} : void 0,
|
|
5797
|
-
requestId: streamingResult.responseId
|
|
5798
|
-
});
|
|
5799
|
-
const estimatedCosts = this._getEstimatedCosts(
|
|
5800
|
-
selectedModel,
|
|
5801
|
-
streamingResult
|
|
5802
|
-
);
|
|
5803
|
-
const onLastMessageSatsUpdate = callbacks.onLastMessageSatsUpdate;
|
|
5804
|
-
onLastMessageSatsUpdate?.(satsSpent, estimatedCosts);
|
|
5805
|
-
} else {
|
|
5806
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
5807
|
-
}
|
|
5808
|
-
} catch (error) {
|
|
5809
|
-
this._handleError(error, callbacks);
|
|
5810
|
-
} finally {
|
|
5811
|
-
callbacks.onPaymentProcessing?.(false);
|
|
5812
|
-
}
|
|
5813
|
-
}
|
|
5814
5035
|
/**
|
|
5815
5036
|
* Make the API request with failover support
|
|
5816
5037
|
*/
|
|
@@ -6306,292 +5527,555 @@ var RoutstrClient = class {
|
|
|
6306
5527
|
requestId = requestId ?? extractResponseId(responseBody) ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
6307
5528
|
}
|
|
6308
5529
|
}
|
|
6309
|
-
if (!usage) {
|
|
6310
|
-
return;
|
|
5530
|
+
if (!usage) {
|
|
5531
|
+
return;
|
|
5532
|
+
}
|
|
5533
|
+
const finalRequestId = requestId || "unknown";
|
|
5534
|
+
const store = this.sdkStore ?? await getDefaultSdkStore();
|
|
5535
|
+
const state = store.getState();
|
|
5536
|
+
const matchKey = clientApiKey ?? token;
|
|
5537
|
+
const matchingClient = state.clientIds.find(
|
|
5538
|
+
(client) => client.apiKey === matchKey
|
|
5539
|
+
);
|
|
5540
|
+
const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
|
|
5541
|
+
const usageTracking = this.usageTrackingDriver ?? getDefaultUsageTrackingDriver();
|
|
5542
|
+
const entry = {
|
|
5543
|
+
id: entryId,
|
|
5544
|
+
timestamp: Date.now(),
|
|
5545
|
+
modelId,
|
|
5546
|
+
baseUrl,
|
|
5547
|
+
requestId: finalRequestId,
|
|
5548
|
+
client: matchingClient?.clientId,
|
|
5549
|
+
...usage
|
|
5550
|
+
};
|
|
5551
|
+
if (this.mode === "xcashu") {
|
|
5552
|
+
entry.satsCost = satsSpent;
|
|
5553
|
+
}
|
|
5554
|
+
await usageTracking.append(entry);
|
|
5555
|
+
} catch (error) {
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
/**
|
|
5559
|
+
* Check wallet balance and throw if insufficient
|
|
5560
|
+
*/
|
|
5561
|
+
async _checkBalance() {
|
|
5562
|
+
const balances = await this.walletAdapter.getBalances();
|
|
5563
|
+
const totalBalance = Object.values(balances).reduce((sum, v) => sum + v, 0);
|
|
5564
|
+
if (totalBalance <= 0) {
|
|
5565
|
+
throw new InsufficientBalanceError(1, 0);
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
/**
|
|
5569
|
+
* Spend a token using CashuSpender with standardized error handling
|
|
5570
|
+
*/
|
|
5571
|
+
async _spendToken(params) {
|
|
5572
|
+
const { mintUrl, amount, baseUrl } = params;
|
|
5573
|
+
this._log(
|
|
5574
|
+
"DEBUG",
|
|
5575
|
+
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
5576
|
+
);
|
|
5577
|
+
if (this.mode === "apikeys") {
|
|
5578
|
+
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
5579
|
+
if (!parentApiKey) {
|
|
5580
|
+
this._log(
|
|
5581
|
+
"DEBUG",
|
|
5582
|
+
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
5583
|
+
);
|
|
5584
|
+
const spendResult2 = await this.cashuSpender.spend({
|
|
5585
|
+
mintUrl,
|
|
5586
|
+
amount: amount * TOPUP_MARGIN,
|
|
5587
|
+
baseUrl: "",
|
|
5588
|
+
reuseToken: false
|
|
5589
|
+
});
|
|
5590
|
+
if (!spendResult2.token) {
|
|
5591
|
+
this._log(
|
|
5592
|
+
"ERROR",
|
|
5593
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
5594
|
+
spendResult2.error
|
|
5595
|
+
);
|
|
5596
|
+
throw new Error(
|
|
5597
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
5598
|
+
);
|
|
5599
|
+
} else {
|
|
5600
|
+
this._log(
|
|
5601
|
+
"DEBUG",
|
|
5602
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
5603
|
+
);
|
|
5604
|
+
}
|
|
5605
|
+
this._log(
|
|
5606
|
+
"DEBUG",
|
|
5607
|
+
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
5608
|
+
);
|
|
5609
|
+
try {
|
|
5610
|
+
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
5611
|
+
} catch (error) {
|
|
5612
|
+
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
5613
|
+
const receiveResult = await this.cashuSpender.receiveToken(
|
|
5614
|
+
spendResult2.token
|
|
5615
|
+
);
|
|
5616
|
+
if (receiveResult.success) {
|
|
5617
|
+
this._log(
|
|
5618
|
+
"DEBUG",
|
|
5619
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
5620
|
+
);
|
|
5621
|
+
} else {
|
|
5622
|
+
this._log(
|
|
5623
|
+
"DEBUG",
|
|
5624
|
+
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
5625
|
+
);
|
|
5626
|
+
}
|
|
5627
|
+
this._log(
|
|
5628
|
+
"DEBUG",
|
|
5629
|
+
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
5630
|
+
);
|
|
5631
|
+
} else {
|
|
5632
|
+
throw error;
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
5636
|
+
} else {
|
|
5637
|
+
this._log(
|
|
5638
|
+
"DEBUG",
|
|
5639
|
+
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
5640
|
+
);
|
|
5641
|
+
}
|
|
5642
|
+
let tokenBalance = 0;
|
|
5643
|
+
let tokenBalanceUnit = "sat";
|
|
5644
|
+
let tokenBalanceUnknown = false;
|
|
5645
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
5646
|
+
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
5647
|
+
(d) => d.baseUrl === baseUrl
|
|
5648
|
+
);
|
|
5649
|
+
if (distributionForBaseUrl) {
|
|
5650
|
+
tokenBalance = distributionForBaseUrl.amount;
|
|
6311
5651
|
}
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
5652
|
+
if (tokenBalance === 0 && parentApiKey) {
|
|
5653
|
+
try {
|
|
5654
|
+
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
5655
|
+
parentApiKey.key,
|
|
5656
|
+
baseUrl
|
|
5657
|
+
);
|
|
5658
|
+
tokenBalance = balanceInfo.amount;
|
|
5659
|
+
tokenBalanceUnit = balanceInfo.unit;
|
|
5660
|
+
tokenBalanceUnknown = Boolean(balanceInfo.balanceUnknown);
|
|
5661
|
+
} catch (e) {
|
|
5662
|
+
this._log("WARN", "Could not get initial API key balance:", e);
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
this._log(
|
|
5666
|
+
"DEBUG",
|
|
5667
|
+
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
6318
5668
|
);
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
modelId,
|
|
6325
|
-
baseUrl,
|
|
6326
|
-
requestId: finalRequestId,
|
|
6327
|
-
client: matchingClient?.clientId,
|
|
6328
|
-
...usage
|
|
5669
|
+
return {
|
|
5670
|
+
token: parentApiKey?.key ?? "",
|
|
5671
|
+
tokenBalance,
|
|
5672
|
+
tokenBalanceUnit,
|
|
5673
|
+
tokenBalanceUnknown
|
|
6329
5674
|
};
|
|
6330
|
-
if (this.mode === "xcashu") {
|
|
6331
|
-
entry.satsCost = satsSpent;
|
|
6332
|
-
}
|
|
6333
|
-
await usageTracking.append(entry);
|
|
6334
|
-
} catch (error) {
|
|
6335
5675
|
}
|
|
5676
|
+
this._log(
|
|
5677
|
+
"DEBUG",
|
|
5678
|
+
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
5679
|
+
);
|
|
5680
|
+
const spendResult = await this.cashuSpender.spend({
|
|
5681
|
+
mintUrl,
|
|
5682
|
+
amount,
|
|
5683
|
+
baseUrl: "",
|
|
5684
|
+
reuseToken: false
|
|
5685
|
+
});
|
|
5686
|
+
if (!spendResult.token) {
|
|
5687
|
+
this._log(
|
|
5688
|
+
"ERROR",
|
|
5689
|
+
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
5690
|
+
spendResult.error
|
|
5691
|
+
);
|
|
5692
|
+
} else {
|
|
5693
|
+
this._log(
|
|
5694
|
+
"DEBUG",
|
|
5695
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
5696
|
+
);
|
|
5697
|
+
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
5698
|
+
}
|
|
5699
|
+
return {
|
|
5700
|
+
token: spendResult.token,
|
|
5701
|
+
tokenBalance: spendResult.balance,
|
|
5702
|
+
tokenBalanceUnit: spendResult.unit ?? "sat",
|
|
5703
|
+
tokenBalanceUnknown: false
|
|
5704
|
+
};
|
|
6336
5705
|
}
|
|
6337
5706
|
/**
|
|
6338
|
-
*
|
|
5707
|
+
* Build request headers with common defaults and dev mock controls
|
|
6339
5708
|
*/
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
);
|
|
5709
|
+
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
5710
|
+
const headers = {
|
|
5711
|
+
...additionalHeaders,
|
|
5712
|
+
"Content-Type": "application/json"
|
|
5713
|
+
};
|
|
5714
|
+
return headers;
|
|
6347
5715
|
}
|
|
6348
5716
|
/**
|
|
6349
|
-
*
|
|
5717
|
+
* Attach auth headers using the active client mode
|
|
6350
5718
|
*/
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
5719
|
+
_withAuthHeader(headers, token) {
|
|
5720
|
+
const nextHeaders = { ...headers };
|
|
5721
|
+
if (this.mode === "xcashu") {
|
|
5722
|
+
nextHeaders["X-Cashu"] = token;
|
|
5723
|
+
} else {
|
|
5724
|
+
nextHeaders["Authorization"] = `Bearer ${token}`;
|
|
5725
|
+
}
|
|
5726
|
+
return nextHeaders;
|
|
5727
|
+
}
|
|
5728
|
+
};
|
|
5729
|
+
|
|
5730
|
+
// client/StreamProcessor.ts
|
|
5731
|
+
var StreamProcessor = class {
|
|
5732
|
+
accumulatedContent = "";
|
|
5733
|
+
accumulatedThinking = "";
|
|
5734
|
+
accumulatedImages = [];
|
|
5735
|
+
isInThinking = false;
|
|
5736
|
+
isInContent = false;
|
|
5737
|
+
/**
|
|
5738
|
+
* Process a streaming response
|
|
5739
|
+
*/
|
|
5740
|
+
async process(response, callbacks, modelId) {
|
|
5741
|
+
if (!response.body) {
|
|
5742
|
+
throw new Error("Response body is not available");
|
|
5743
|
+
}
|
|
5744
|
+
const reader = response.body.getReader();
|
|
5745
|
+
const decoder = new TextDecoder("utf-8");
|
|
5746
|
+
let buffer = "";
|
|
5747
|
+
this.accumulatedContent = "";
|
|
5748
|
+
this.accumulatedThinking = "";
|
|
5749
|
+
this.accumulatedImages = [];
|
|
5750
|
+
this.isInThinking = false;
|
|
5751
|
+
this.isInContent = false;
|
|
5752
|
+
let usage;
|
|
5753
|
+
let model;
|
|
5754
|
+
let finish_reason;
|
|
5755
|
+
let citations;
|
|
5756
|
+
let annotations;
|
|
5757
|
+
let responseId;
|
|
5758
|
+
try {
|
|
5759
|
+
while (true) {
|
|
5760
|
+
const { done, value } = await reader.read();
|
|
5761
|
+
if (done) {
|
|
5762
|
+
break;
|
|
5763
|
+
}
|
|
5764
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
5765
|
+
buffer += chunk;
|
|
5766
|
+
const lines = buffer.split("\n");
|
|
5767
|
+
buffer = lines.pop() || "";
|
|
5768
|
+
for (const line of lines) {
|
|
5769
|
+
const parsed = this._parseLine(line);
|
|
5770
|
+
if (!parsed) continue;
|
|
5771
|
+
if (parsed.content) {
|
|
5772
|
+
this._handleContent(parsed.content, callbacks, modelId);
|
|
6368
5773
|
}
|
|
6369
|
-
|
|
5774
|
+
if (parsed.reasoning) {
|
|
5775
|
+
this._handleThinking(parsed.reasoning, callbacks);
|
|
5776
|
+
}
|
|
5777
|
+
if (parsed.usage) {
|
|
5778
|
+
usage = parsed.usage;
|
|
5779
|
+
}
|
|
5780
|
+
if (parsed.model) {
|
|
5781
|
+
model = parsed.model;
|
|
5782
|
+
}
|
|
5783
|
+
if (parsed.finish_reason) {
|
|
5784
|
+
finish_reason = parsed.finish_reason;
|
|
5785
|
+
}
|
|
5786
|
+
if (parsed.responseId) {
|
|
5787
|
+
responseId = parsed.responseId;
|
|
5788
|
+
}
|
|
5789
|
+
if (parsed.citations) {
|
|
5790
|
+
citations = parsed.citations;
|
|
5791
|
+
}
|
|
5792
|
+
if (parsed.annotations) {
|
|
5793
|
+
annotations = parsed.annotations;
|
|
5794
|
+
}
|
|
5795
|
+
if (parsed.images) {
|
|
5796
|
+
this._mergeImages(parsed.images);
|
|
5797
|
+
}
|
|
5798
|
+
}
|
|
6370
5799
|
}
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
content
|
|
6374
|
-
};
|
|
5800
|
+
} finally {
|
|
5801
|
+
reader.releaseLock();
|
|
6375
5802
|
}
|
|
6376
5803
|
return {
|
|
6377
|
-
|
|
6378
|
-
|
|
5804
|
+
content: this.accumulatedContent,
|
|
5805
|
+
thinking: this.accumulatedThinking || void 0,
|
|
5806
|
+
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
5807
|
+
usage,
|
|
5808
|
+
model,
|
|
5809
|
+
responseId,
|
|
5810
|
+
finish_reason,
|
|
5811
|
+
citations,
|
|
5812
|
+
annotations
|
|
6379
5813
|
};
|
|
6380
5814
|
}
|
|
6381
5815
|
/**
|
|
6382
|
-
*
|
|
5816
|
+
* Parse a single SSE line
|
|
6383
5817
|
*/
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
if (
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
5818
|
+
_parseLine(line) {
|
|
5819
|
+
if (!line.trim()) return null;
|
|
5820
|
+
if (!line.startsWith("data: ")) {
|
|
5821
|
+
return null;
|
|
5822
|
+
}
|
|
5823
|
+
const jsonData = line.slice(6);
|
|
5824
|
+
if (jsonData === "[DONE]") {
|
|
5825
|
+
return null;
|
|
5826
|
+
}
|
|
5827
|
+
try {
|
|
5828
|
+
const parsed = JSON.parse(jsonData);
|
|
5829
|
+
const result = {};
|
|
5830
|
+
if (parsed.choices?.[0]?.delta?.content) {
|
|
5831
|
+
result.content = parsed.choices[0].delta.content;
|
|
5832
|
+
}
|
|
5833
|
+
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
5834
|
+
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
5835
|
+
}
|
|
5836
|
+
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
5837
|
+
if (extractedUsage) {
|
|
5838
|
+
result.usage = toUsageStats(extractedUsage);
|
|
5839
|
+
} else if (parsed.usage) {
|
|
5840
|
+
result.usage = {
|
|
5841
|
+
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
5842
|
+
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
5843
|
+
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
5844
|
+
};
|
|
5845
|
+
}
|
|
5846
|
+
if (parsed.id) {
|
|
5847
|
+
result.responseId = parsed.id;
|
|
5848
|
+
}
|
|
5849
|
+
if (parsed.model) {
|
|
5850
|
+
result.model = parsed.model;
|
|
5851
|
+
}
|
|
5852
|
+
if (parsed.citations) {
|
|
5853
|
+
result.citations = parsed.citations;
|
|
5854
|
+
}
|
|
5855
|
+
if (parsed.annotations) {
|
|
5856
|
+
result.annotations = parsed.annotations;
|
|
5857
|
+
}
|
|
5858
|
+
if (parsed.choices?.[0]?.finish_reason) {
|
|
5859
|
+
result.finish_reason = parsed.choices[0].finish_reason;
|
|
5860
|
+
}
|
|
5861
|
+
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
5862
|
+
if (images && Array.isArray(images)) {
|
|
5863
|
+
result.images = images;
|
|
6390
5864
|
}
|
|
5865
|
+
return result;
|
|
5866
|
+
} catch {
|
|
5867
|
+
return null;
|
|
5868
|
+
}
|
|
5869
|
+
}
|
|
5870
|
+
/**
|
|
5871
|
+
* Handle content delta with thinking support
|
|
5872
|
+
*/
|
|
5873
|
+
_handleContent(content, callbacks, modelId) {
|
|
5874
|
+
if (this.isInThinking && !this.isInContent) {
|
|
5875
|
+
this.accumulatedThinking += "</thinking>";
|
|
5876
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
5877
|
+
this.isInThinking = false;
|
|
5878
|
+
this.isInContent = true;
|
|
5879
|
+
}
|
|
5880
|
+
if (modelId) {
|
|
5881
|
+
this._extractThinkingFromContent(content, callbacks);
|
|
5882
|
+
} else {
|
|
5883
|
+
this.accumulatedContent += content;
|
|
6391
5884
|
}
|
|
6392
|
-
|
|
6393
|
-
}
|
|
6394
|
-
/**
|
|
6395
|
-
* Get pending API key amount
|
|
6396
|
-
*/
|
|
6397
|
-
_getPendingCashuTokenAmount() {
|
|
6398
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
6399
|
-
return apiKeyDistribution.reduce((total, item) => total + item.amount, 0);
|
|
5885
|
+
callbacks.onContent(this.accumulatedContent);
|
|
6400
5886
|
}
|
|
6401
5887
|
/**
|
|
6402
|
-
* Handle
|
|
5888
|
+
* Handle thinking/reasoning content
|
|
6403
5889
|
*/
|
|
6404
|
-
|
|
6405
|
-
this.
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
const modifiedErrorMsg = isStreamError ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
|
|
6409
|
-
this._log(
|
|
6410
|
-
"ERROR",
|
|
6411
|
-
`[RoutstrClient] _handleError: Error type=${error.constructor.name}, message=${modifiedErrorMsg}, isStreamError=${isStreamError}`
|
|
6412
|
-
);
|
|
6413
|
-
callbacks.onMessageAppend({
|
|
6414
|
-
role: "system",
|
|
6415
|
-
content: "Uncaught Error: " + modifiedErrorMsg + (this.alertLevel === "max" ? " | " + error.stack : "")
|
|
6416
|
-
});
|
|
6417
|
-
} else {
|
|
6418
|
-
callbacks.onMessageAppend({
|
|
6419
|
-
role: "system",
|
|
6420
|
-
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
6421
|
-
});
|
|
5890
|
+
_handleThinking(reasoning, callbacks) {
|
|
5891
|
+
if (!this.isInThinking) {
|
|
5892
|
+
this.accumulatedThinking += "<thinking> ";
|
|
5893
|
+
this.isInThinking = true;
|
|
6422
5894
|
}
|
|
5895
|
+
this.accumulatedThinking += reasoning;
|
|
5896
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
6423
5897
|
}
|
|
6424
5898
|
/**
|
|
6425
|
-
*
|
|
5899
|
+
* Extract thinking blocks from content (for models with inline thinking)
|
|
6426
5900
|
*/
|
|
6427
|
-
|
|
6428
|
-
const
|
|
6429
|
-
const
|
|
6430
|
-
|
|
6431
|
-
|
|
5901
|
+
_extractThinkingFromContent(content, callbacks) {
|
|
5902
|
+
const parts = content.split(/(<thinking>|<\/thinking>)/);
|
|
5903
|
+
for (const part of parts) {
|
|
5904
|
+
if (part === "<thinking>") {
|
|
5905
|
+
this.isInThinking = true;
|
|
5906
|
+
if (!this.accumulatedThinking.includes("<thinking>")) {
|
|
5907
|
+
this.accumulatedThinking += "<thinking> ";
|
|
5908
|
+
}
|
|
5909
|
+
} else if (part === "</thinking>") {
|
|
5910
|
+
this.isInThinking = false;
|
|
5911
|
+
this.accumulatedThinking += "</thinking>";
|
|
5912
|
+
} else if (this.isInThinking) {
|
|
5913
|
+
this.accumulatedThinking += part;
|
|
5914
|
+
} else {
|
|
5915
|
+
this.accumulatedContent += part;
|
|
5916
|
+
}
|
|
6432
5917
|
}
|
|
6433
5918
|
}
|
|
6434
5919
|
/**
|
|
6435
|
-
*
|
|
5920
|
+
* Merge images into accumulated array, avoiding duplicates
|
|
6436
5921
|
*/
|
|
6437
|
-
|
|
6438
|
-
const
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
6445
|
-
if (!parentApiKey) {
|
|
6446
|
-
this._log(
|
|
6447
|
-
"DEBUG",
|
|
6448
|
-
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
6449
|
-
);
|
|
6450
|
-
const spendResult2 = await this.cashuSpender.spend({
|
|
6451
|
-
mintUrl,
|
|
6452
|
-
amount: amount * TOPUP_MARGIN,
|
|
6453
|
-
baseUrl: "",
|
|
6454
|
-
reuseToken: false
|
|
6455
|
-
});
|
|
6456
|
-
if (!spendResult2.token) {
|
|
6457
|
-
this._log(
|
|
6458
|
-
"ERROR",
|
|
6459
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
6460
|
-
spendResult2.error
|
|
6461
|
-
);
|
|
6462
|
-
throw new Error(
|
|
6463
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
6464
|
-
);
|
|
6465
|
-
} else {
|
|
6466
|
-
this._log(
|
|
6467
|
-
"DEBUG",
|
|
6468
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
6469
|
-
);
|
|
5922
|
+
_mergeImages(newImages) {
|
|
5923
|
+
for (const img of newImages) {
|
|
5924
|
+
const newUrl = img.image_url?.url;
|
|
5925
|
+
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
5926
|
+
const existingUrl = existing.image_url?.url;
|
|
5927
|
+
if (newUrl && existingUrl) {
|
|
5928
|
+
return existingUrl === newUrl;
|
|
6470
5929
|
}
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
6474
|
-
);
|
|
6475
|
-
try {
|
|
6476
|
-
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
6477
|
-
} catch (error) {
|
|
6478
|
-
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
6479
|
-
const receiveResult = await this.cashuSpender.receiveToken(
|
|
6480
|
-
spendResult2.token
|
|
6481
|
-
);
|
|
6482
|
-
if (receiveResult.success) {
|
|
6483
|
-
this._log(
|
|
6484
|
-
"DEBUG",
|
|
6485
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
6486
|
-
);
|
|
6487
|
-
} else {
|
|
6488
|
-
this._log(
|
|
6489
|
-
"DEBUG",
|
|
6490
|
-
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
6491
|
-
);
|
|
6492
|
-
}
|
|
6493
|
-
this._log(
|
|
6494
|
-
"DEBUG",
|
|
6495
|
-
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
6496
|
-
);
|
|
6497
|
-
} else {
|
|
6498
|
-
throw error;
|
|
6499
|
-
}
|
|
5930
|
+
if (img.index !== void 0 && existing.index !== void 0) {
|
|
5931
|
+
return existing.index === img.index;
|
|
6500
5932
|
}
|
|
6501
|
-
|
|
5933
|
+
return false;
|
|
5934
|
+
});
|
|
5935
|
+
if (existingIndex === -1) {
|
|
5936
|
+
this.accumulatedImages.push(img);
|
|
6502
5937
|
} else {
|
|
6503
|
-
this.
|
|
6504
|
-
"DEBUG",
|
|
6505
|
-
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
6506
|
-
);
|
|
6507
|
-
}
|
|
6508
|
-
let tokenBalance = 0;
|
|
6509
|
-
let tokenBalanceUnit = "sat";
|
|
6510
|
-
let tokenBalanceUnknown = false;
|
|
6511
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
6512
|
-
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
6513
|
-
(d) => d.baseUrl === baseUrl
|
|
6514
|
-
);
|
|
6515
|
-
if (distributionForBaseUrl) {
|
|
6516
|
-
tokenBalance = distributionForBaseUrl.amount;
|
|
6517
|
-
}
|
|
6518
|
-
if (tokenBalance === 0 && parentApiKey) {
|
|
6519
|
-
try {
|
|
6520
|
-
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
6521
|
-
parentApiKey.key,
|
|
6522
|
-
baseUrl
|
|
6523
|
-
);
|
|
6524
|
-
tokenBalance = balanceInfo.amount;
|
|
6525
|
-
tokenBalanceUnit = balanceInfo.unit;
|
|
6526
|
-
tokenBalanceUnknown = Boolean(balanceInfo.balanceUnknown);
|
|
6527
|
-
} catch (e) {
|
|
6528
|
-
this._log("WARN", "Could not get initial API key balance:", e);
|
|
6529
|
-
}
|
|
5938
|
+
this.accumulatedImages[existingIndex] = img;
|
|
6530
5939
|
}
|
|
6531
|
-
this._log(
|
|
6532
|
-
"DEBUG",
|
|
6533
|
-
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
6534
|
-
);
|
|
6535
|
-
return {
|
|
6536
|
-
token: parentApiKey?.key ?? "",
|
|
6537
|
-
tokenBalance,
|
|
6538
|
-
tokenBalanceUnit,
|
|
6539
|
-
tokenBalanceUnknown
|
|
6540
|
-
};
|
|
6541
5940
|
}
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
5941
|
+
}
|
|
5942
|
+
};
|
|
5943
|
+
|
|
5944
|
+
// client/fetchAIResponse.ts
|
|
5945
|
+
async function fetchAIResponse(options, callbacks, deps) {
|
|
5946
|
+
const {
|
|
5947
|
+
messageHistory,
|
|
5948
|
+
selectedModel,
|
|
5949
|
+
baseUrl,
|
|
5950
|
+
mintUrl,
|
|
5951
|
+
maxTokens,
|
|
5952
|
+
headers
|
|
5953
|
+
} = options;
|
|
5954
|
+
try {
|
|
5955
|
+
const apiMessages = await convertMessages(messageHistory);
|
|
5956
|
+
callbacks.onPaymentProcessing?.(true);
|
|
5957
|
+
callbacks.onTokenCreated?.(deps.getPendingCashuTokenAmount?.() ?? 0);
|
|
5958
|
+
const providerInfo = await deps.providerRegistry.getProviderInfo(baseUrl);
|
|
5959
|
+
const providerVersion = providerInfo?.version ?? "";
|
|
5960
|
+
let modelIdForRequest = selectedModel.id;
|
|
5961
|
+
if (/^0\.1\./.test(providerVersion)) {
|
|
5962
|
+
const newModel = await deps.client.getProviderManager().getModelForProvider(baseUrl, selectedModel.id);
|
|
5963
|
+
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
5964
|
+
}
|
|
5965
|
+
const body = {
|
|
5966
|
+
model: modelIdForRequest,
|
|
5967
|
+
messages: apiMessages,
|
|
5968
|
+
stream: true
|
|
5969
|
+
};
|
|
5970
|
+
if (maxTokens !== void 0) {
|
|
5971
|
+
body.max_tokens = maxTokens;
|
|
5972
|
+
}
|
|
5973
|
+
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
5974
|
+
body.tools = [{ type: "web_search" }];
|
|
5975
|
+
}
|
|
5976
|
+
const response = await deps.client.routeRequest({
|
|
5977
|
+
path: "/v1/chat/completions",
|
|
5978
|
+
method: "POST",
|
|
5979
|
+
body,
|
|
5980
|
+
headers,
|
|
5981
|
+
baseUrl,
|
|
6547
5982
|
mintUrl,
|
|
6548
|
-
|
|
6549
|
-
baseUrl: "",
|
|
6550
|
-
reuseToken: false
|
|
5983
|
+
modelId: selectedModel.id
|
|
6551
5984
|
});
|
|
6552
|
-
if (!
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
5985
|
+
if (!response.body) {
|
|
5986
|
+
throw new Error("Response body is not available");
|
|
5987
|
+
}
|
|
5988
|
+
if (response.status !== 200) {
|
|
5989
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
5990
|
+
}
|
|
5991
|
+
const streamProcessor = new StreamProcessor();
|
|
5992
|
+
const streamingResult = await streamProcessor.process(
|
|
5993
|
+
response,
|
|
5994
|
+
{
|
|
5995
|
+
onContent: callbacks.onStreamingUpdate,
|
|
5996
|
+
onThinking: callbacks.onThinkingUpdate
|
|
5997
|
+
},
|
|
5998
|
+
selectedModel.id
|
|
5999
|
+
);
|
|
6000
|
+
if (streamingResult.finish_reason === "content_filter") {
|
|
6001
|
+
callbacks.onMessageAppend({
|
|
6002
|
+
role: "assistant",
|
|
6003
|
+
content: "Your request was denied due to content filtering."
|
|
6004
|
+
});
|
|
6005
|
+
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
6006
|
+
const message = await createAssistantMessage(streamingResult);
|
|
6007
|
+
callbacks.onMessageAppend(message);
|
|
6558
6008
|
} else {
|
|
6559
|
-
|
|
6560
|
-
"
|
|
6561
|
-
|
|
6562
|
-
);
|
|
6563
|
-
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
6009
|
+
callbacks.onMessageAppend({
|
|
6010
|
+
role: "system",
|
|
6011
|
+
content: "The provider did not respond to this request."
|
|
6012
|
+
});
|
|
6564
6013
|
}
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6014
|
+
callbacks.onStreamingUpdate("");
|
|
6015
|
+
callbacks.onThinkingUpdate("");
|
|
6016
|
+
} catch (error) {
|
|
6017
|
+
handleError(error, callbacks, deps.alertLevel, deps.logger);
|
|
6018
|
+
} finally {
|
|
6019
|
+
callbacks.onPaymentProcessing?.(false);
|
|
6571
6020
|
}
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6021
|
+
}
|
|
6022
|
+
async function convertMessages(messages) {
|
|
6023
|
+
return Promise.all(
|
|
6024
|
+
messages.filter((m) => m.role !== "system").map(async (m) => ({
|
|
6025
|
+
role: m.role,
|
|
6026
|
+
content: typeof m.content === "string" ? m.content : m.content
|
|
6027
|
+
}))
|
|
6028
|
+
);
|
|
6029
|
+
}
|
|
6030
|
+
async function createAssistantMessage(result) {
|
|
6031
|
+
if (result.images && result.images.length > 0) {
|
|
6032
|
+
const content = [];
|
|
6033
|
+
if (result.content) {
|
|
6034
|
+
content.push({
|
|
6035
|
+
type: "text",
|
|
6036
|
+
text: result.content,
|
|
6037
|
+
thinking: result.thinking,
|
|
6038
|
+
citations: result.citations,
|
|
6039
|
+
annotations: result.annotations
|
|
6040
|
+
});
|
|
6041
|
+
}
|
|
6042
|
+
for (const img of result.images) {
|
|
6043
|
+
content.push({
|
|
6044
|
+
type: "image_url",
|
|
6045
|
+
image_url: {
|
|
6046
|
+
url: img.image_url.url
|
|
6047
|
+
}
|
|
6048
|
+
});
|
|
6049
|
+
}
|
|
6050
|
+
return {
|
|
6051
|
+
role: "assistant",
|
|
6052
|
+
content
|
|
6579
6053
|
};
|
|
6580
|
-
return headers;
|
|
6581
6054
|
}
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6055
|
+
return {
|
|
6056
|
+
role: "assistant",
|
|
6057
|
+
content: result.content || ""
|
|
6058
|
+
};
|
|
6059
|
+
}
|
|
6060
|
+
function handleError(error, callbacks, alertLevel, logger) {
|
|
6061
|
+
logger.error("[fetchAIResponse] Error occurred", error);
|
|
6062
|
+
if (error instanceof Error) {
|
|
6063
|
+
const isStreamError = error.message.includes("Error in input stream") || error.message.includes("Load failed");
|
|
6064
|
+
const modifiedErrorMsg = isStreamError ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
|
|
6065
|
+
logger.error(
|
|
6066
|
+
`[fetchAIResponse] Error type=${error.constructor.name}, message=${modifiedErrorMsg}, isStreamError=${isStreamError}`
|
|
6067
|
+
);
|
|
6068
|
+
callbacks.onMessageAppend({
|
|
6069
|
+
role: "system",
|
|
6070
|
+
content: "Uncaught Error: " + modifiedErrorMsg + (alertLevel === "max" ? " | " + error.stack : "")
|
|
6071
|
+
});
|
|
6072
|
+
} else {
|
|
6073
|
+
callbacks.onMessageAppend({
|
|
6074
|
+
role: "system",
|
|
6075
|
+
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
6076
|
+
});
|
|
6593
6077
|
}
|
|
6594
|
-
}
|
|
6078
|
+
}
|
|
6595
6079
|
|
|
6596
6080
|
// routeRequests.ts
|
|
6597
6081
|
async function resolveRouteRequestContext(options) {
|
|
@@ -6762,8 +6246,6 @@ exports.StreamProcessor = StreamProcessor;
|
|
|
6762
6246
|
exports.StreamingError = StreamingError;
|
|
6763
6247
|
exports.TokenOperationError = TokenOperationError;
|
|
6764
6248
|
exports.consoleLogger = consoleLogger;
|
|
6765
|
-
exports.createBunSqliteDriver = createBunSqliteDriver;
|
|
6766
|
-
exports.createBunSqliteUsageTrackingDriver = createBunSqliteUsageTrackingDriver;
|
|
6767
6249
|
exports.createDiscoveryAdapterFromStore = createDiscoveryAdapterFromStore;
|
|
6768
6250
|
exports.createIndexedDBDriver = createIndexedDBDriver;
|
|
6769
6251
|
exports.createIndexedDBUsageTrackingDriver = createIndexedDBUsageTrackingDriver;
|
|
@@ -6774,9 +6256,8 @@ exports.createProviderRegistryFromStore = createProviderRegistryFromStore;
|
|
|
6774
6256
|
exports.createSSEParserTransform = createSSEParserTransform;
|
|
6775
6257
|
exports.createSdkStore = createSdkStore;
|
|
6776
6258
|
exports.createShardedDiscoveryAdapter = createShardedDiscoveryAdapter;
|
|
6777
|
-
exports.createSqliteDriver = createSqliteDriver;
|
|
6778
|
-
exports.createSqliteUsageTrackingDriver = createSqliteUsageTrackingDriver;
|
|
6779
6259
|
exports.createStorageAdapterFromStore = createStorageAdapterFromStore;
|
|
6260
|
+
exports.fetchAIResponse = fetchAIResponse;
|
|
6780
6261
|
exports.filterBaseUrlsForTor = filterBaseUrlsForTor;
|
|
6781
6262
|
exports.getDefaultDiscoveryAdapter = getDefaultDiscoveryAdapter;
|
|
6782
6263
|
exports.getDefaultProviderRegistry = getDefaultProviderRegistry;
|