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