@routstr/sdk 0.3.9 → 0.3.11
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 +6413 -0
- package/dist/browser.js.map +1 -0
- package/dist/browser.mjs +6361 -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 +6791 -0
- package/dist/bun.js.map +1 -0
- package/dist/bun.mjs +6733 -0
- package/dist/bun.mjs.map +1 -0
- package/dist/bunSqlite-BmXWNc25.d.ts +18 -0
- package/dist/bunSqlite-Bro9efsl.d.mts +18 -0
- package/dist/client/index.d.mts +85 -42
- package/dist/client/index.d.ts +85 -42
- package/dist/client/index.js +1243 -1584
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +1239 -1585
- 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 +30 -31
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +30 -31
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +9 -7
- package/dist/index.d.ts +9 -7
- package/dist/index.js +1264 -1648
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1260 -1645
- 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 +6857 -0
- package/dist/node.js.map +1 -0
- package/dist/node.mjs +6801 -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 +1970 -0
- package/dist/storage/bun.js.map +1 -0
- package/dist/storage/bun.mjs +1946 -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 +238 -650
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +239 -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 +2034 -0
- package/dist/storage/node.js.map +1 -0
- package/dist/storage/node.mjs +2012 -0
- package/dist/storage/node.mjs.map +1 -0
- package/dist/{store-58VcEUoA.d.ts → store-CAQLSbEj.d.ts} +52 -1
- package/dist/{store-C6dfj1cc.d.mts → store-CuXwe5Rg.d.mts} +52 -1
- package/dist/wallet/index.js +38 -24
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +38 -24
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +26 -1
package/dist/index.js
CHANGED
|
@@ -139,9 +139,11 @@ var MintDiscoveryError = class extends Error {
|
|
|
139
139
|
}
|
|
140
140
|
baseUrl;
|
|
141
141
|
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
var DEFAULT_NOSTR_RELAYS = [
|
|
143
|
+
"wss://relay.damus.io",
|
|
144
|
+
"wss://nos.lol",
|
|
145
|
+
"wss://relay.routstr.com"
|
|
146
|
+
];
|
|
145
147
|
var ModelManager = class _ModelManager {
|
|
146
148
|
constructor(adapter, config = {}) {
|
|
147
149
|
this.adapter = adapter;
|
|
@@ -150,8 +152,10 @@ var ModelManager = class _ModelManager {
|
|
|
150
152
|
this.includeProviderUrls = config.includeProviderUrls || [];
|
|
151
153
|
this.excludeProviderUrls = config.excludeProviderUrls || [];
|
|
152
154
|
this.routstrPubkey = config.routstrPubkey || "4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8";
|
|
155
|
+
this.nostrRelays = config.nostrRelays;
|
|
153
156
|
this.logger = (config.logger ?? consoleLogger).child("ModelManager");
|
|
154
157
|
this.eventStoreDbPath = config.eventStoreDbPath;
|
|
158
|
+
this.persistentEventDatabaseFactory = config.persistentEventDatabaseFactory;
|
|
155
159
|
}
|
|
156
160
|
adapter;
|
|
157
161
|
cacheTTL;
|
|
@@ -159,6 +163,7 @@ var ModelManager = class _ModelManager {
|
|
|
159
163
|
includeProviderUrls;
|
|
160
164
|
excludeProviderUrls;
|
|
161
165
|
routstrPubkey;
|
|
166
|
+
nostrRelays;
|
|
162
167
|
logger;
|
|
163
168
|
providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
|
|
164
169
|
/** Persistent event store for relay-fetched events (null if not configured/initialized) */
|
|
@@ -166,6 +171,7 @@ var ModelManager = class _ModelManager {
|
|
|
166
171
|
eventStoreDb = null;
|
|
167
172
|
eventStoreInitPromise = null;
|
|
168
173
|
eventStoreDbPath;
|
|
174
|
+
persistentEventDatabaseFactory;
|
|
169
175
|
/**
|
|
170
176
|
* Get the list of bootstrapped provider base URLs
|
|
171
177
|
* @returns Array of provider base URLs
|
|
@@ -194,7 +200,7 @@ var ModelManager = class _ModelManager {
|
|
|
194
200
|
} catch (error) {
|
|
195
201
|
this.eventStoreInitPromise = null;
|
|
196
202
|
throw new Error(
|
|
197
|
-
`
|
|
203
|
+
`Persistent Nostr event storage requires a runtime-specific database factory. Use @routstr/sdk/node, @routstr/sdk/bun, inject persistentEventDatabaseFactory, or omit eventStoreDbPath. (${error})`
|
|
198
204
|
);
|
|
199
205
|
}
|
|
200
206
|
})();
|
|
@@ -209,16 +215,15 @@ var ModelManager = class _ModelManager {
|
|
|
209
215
|
return this.ensureEventStore();
|
|
210
216
|
}
|
|
211
217
|
async createPersistentEventDatabase() {
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
if (!this.eventStoreDbPath) {
|
|
219
|
+
throw new Error("eventStoreDbPath is required");
|
|
220
|
+
}
|
|
221
|
+
if (!this.persistentEventDatabaseFactory) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
"persistentEventDatabaseFactory is required. Import ModelManager from @routstr/sdk/node or @routstr/sdk/bun for SQLite-backed persistent event storage."
|
|
216
224
|
);
|
|
217
225
|
}
|
|
218
|
-
|
|
219
|
-
return new BetterSqlite3EventDatabase(
|
|
220
|
-
this.eventStoreDbPath
|
|
221
|
-
);
|
|
226
|
+
return this.persistentEventDatabaseFactory(this.eventStoreDbPath);
|
|
222
227
|
}
|
|
223
228
|
/** Close the persistent event store database handle, if configured. */
|
|
224
229
|
closeEventStore() {
|
|
@@ -331,6 +336,13 @@ var ModelManager = class _ModelManager {
|
|
|
331
336
|
}
|
|
332
337
|
return this.bootstrapFromHttp(torMode, forceRefresh);
|
|
333
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Resolve Nostr relay URLs.
|
|
341
|
+
* Returns user-configured relays if set, otherwise the shared defaults.
|
|
342
|
+
*/
|
|
343
|
+
getNostrRelays() {
|
|
344
|
+
return this.nostrRelays && this.nostrRelays.length > 0 ? this.nostrRelays : DEFAULT_NOSTR_RELAYS;
|
|
345
|
+
}
|
|
334
346
|
/**
|
|
335
347
|
* Bootstrap providers from Nostr network (kind 38421)
|
|
336
348
|
* @param kind The Nostr kind to fetch
|
|
@@ -338,11 +350,7 @@ var ModelManager = class _ModelManager {
|
|
|
338
350
|
* @returns Array of provider base URLs
|
|
339
351
|
*/
|
|
340
352
|
async bootstrapFromNostr(kind, torMode, forceRefresh = false) {
|
|
341
|
-
const
|
|
342
|
-
"wss://relay.primal.net",
|
|
343
|
-
"wss://nos.lol",
|
|
344
|
-
"wss://relay.damus.io"
|
|
345
|
-
];
|
|
353
|
+
const relays = this.getNostrRelays();
|
|
346
354
|
const cached = await this.getCachedNostrEvents(
|
|
347
355
|
{ kinds: [kind] },
|
|
348
356
|
this.cacheTTL,
|
|
@@ -353,7 +361,7 @@ var ModelManager = class _ModelManager {
|
|
|
353
361
|
const pool = new applesauceRelay.RelayPool();
|
|
354
362
|
const timeoutMs = 5e3;
|
|
355
363
|
await new Promise((resolve) => {
|
|
356
|
-
pool.req(
|
|
364
|
+
pool.req(relays, {
|
|
357
365
|
kinds: [kind],
|
|
358
366
|
limit: 100
|
|
359
367
|
}).pipe(
|
|
@@ -525,16 +533,11 @@ var ModelManager = class _ModelManager {
|
|
|
525
533
|
);
|
|
526
534
|
let sessionEvents = cached;
|
|
527
535
|
if (cached.length === 0) {
|
|
528
|
-
const
|
|
529
|
-
"wss://relay.primal.net",
|
|
530
|
-
"wss://nos.lol",
|
|
531
|
-
"wss://relay.damus.io",
|
|
532
|
-
"wss://relay.routstr.com"
|
|
533
|
-
];
|
|
536
|
+
const lgtmRelays = this.getNostrRelays();
|
|
534
537
|
const pool = new applesauceRelay.RelayPool();
|
|
535
538
|
const timeoutMs = 5e3;
|
|
536
539
|
await new Promise((resolve) => {
|
|
537
|
-
pool.req(
|
|
540
|
+
pool.req(lgtmRelays, {
|
|
538
541
|
kinds: [38425],
|
|
539
542
|
"#t": ["lgtm"],
|
|
540
543
|
limit: 500,
|
|
@@ -776,11 +779,7 @@ var ModelManager = class _ModelManager {
|
|
|
776
779
|
return cachedModels;
|
|
777
780
|
}
|
|
778
781
|
}
|
|
779
|
-
const
|
|
780
|
-
"wss://relay.damus.io",
|
|
781
|
-
"wss://nos.lol",
|
|
782
|
-
"wss://relay.routstr.com"
|
|
783
|
-
];
|
|
782
|
+
const relays = this.getNostrRelays();
|
|
784
783
|
const cached = await this.getCachedNostrEvents(
|
|
785
784
|
{ kinds: [38423], "#d": ["routstr-21-models"], authors: [this.routstrPubkey] },
|
|
786
785
|
this.cacheTTL,
|
|
@@ -791,7 +790,7 @@ var ModelManager = class _ModelManager {
|
|
|
791
790
|
const pool = new applesauceRelay.RelayPool();
|
|
792
791
|
const timeoutMs = 5e3;
|
|
793
792
|
await new Promise((resolve) => {
|
|
794
|
-
pool.req(
|
|
793
|
+
pool.req(relays, {
|
|
795
794
|
kinds: [38423],
|
|
796
795
|
"#d": ["routstr-21-models"],
|
|
797
796
|
limit: 1,
|
|
@@ -1992,8 +1991,8 @@ var BalanceManager = class _BalanceManager {
|
|
|
1992
1991
|
const refundableProviderBalance = Object.entries(
|
|
1993
1992
|
balanceState.providerBalances
|
|
1994
1993
|
).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
|
|
1995
|
-
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount <
|
|
1996
|
-
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount);
|
|
1994
|
+
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 3) {
|
|
1995
|
+
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount, adjustedAmount);
|
|
1997
1996
|
return this.createProviderToken({
|
|
1998
1997
|
...options,
|
|
1999
1998
|
retryCount: retryCount + 1
|
|
@@ -2132,33 +2131,47 @@ var BalanceManager = class _BalanceManager {
|
|
|
2132
2131
|
}
|
|
2133
2132
|
return candidates;
|
|
2134
2133
|
}
|
|
2135
|
-
async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
|
|
2134
|
+
async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount, requiredAmount) {
|
|
2136
2135
|
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
2137
2136
|
const forceRefund = retryCount >= 2;
|
|
2138
|
-
const
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2137
|
+
const candidates = apiKeyDistribution.filter((apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0).map((apiKey) => {
|
|
2138
|
+
const full = this.storageAdapter.getApiKey(apiKey.baseUrl);
|
|
2139
|
+
return {
|
|
2140
|
+
baseUrl: apiKey.baseUrl,
|
|
2141
|
+
amount: apiKey.amount,
|
|
2142
|
+
lastUsed: full?.lastUsed ?? 0,
|
|
2143
|
+
key: full?.key
|
|
2144
|
+
};
|
|
2145
|
+
}).filter((c) => c.key != null).sort((a, b) => a.lastUsed - b.lastUsed);
|
|
2146
|
+
if (candidates.length === 0) return;
|
|
2147
|
+
if (forceRefund) {
|
|
2148
|
+
for (const candidate of candidates) {
|
|
2149
|
+
await this.refundApiKey({
|
|
2150
2150
|
mintUrl,
|
|
2151
|
-
baseUrl:
|
|
2152
|
-
apiKey:
|
|
2153
|
-
forceRefund
|
|
2151
|
+
baseUrl: candidate.baseUrl,
|
|
2152
|
+
apiKey: candidate.key,
|
|
2153
|
+
forceRefund: true
|
|
2154
2154
|
});
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2155
|
+
const newState = await this.getBalanceState();
|
|
2156
|
+
const newAvailable = (newState.mintBalances[mintUrl] || 0) + (newState.providerBalances[baseUrl] || 0);
|
|
2157
|
+
if (newAvailable >= requiredAmount) {
|
|
2158
|
+
this.logger.log(
|
|
2159
|
+
`_refundOtherProvidersForTopUp: freed enough balance (${newAvailable} >= ${requiredAmount}), stopping early`
|
|
2160
|
+
);
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2161
2163
|
}
|
|
2164
|
+
} else {
|
|
2165
|
+
await Promise.allSettled(
|
|
2166
|
+
candidates.map(
|
|
2167
|
+
(candidate) => this.refundApiKey({
|
|
2168
|
+
mintUrl,
|
|
2169
|
+
baseUrl: candidate.baseUrl,
|
|
2170
|
+
apiKey: candidate.key,
|
|
2171
|
+
forceRefund: false
|
|
2172
|
+
})
|
|
2173
|
+
)
|
|
2174
|
+
);
|
|
2162
2175
|
}
|
|
2163
2176
|
}
|
|
2164
2177
|
/**
|
|
@@ -2337,561 +2350,242 @@ var BalanceManager = class _BalanceManager {
|
|
|
2337
2350
|
}
|
|
2338
2351
|
};
|
|
2339
2352
|
|
|
2340
|
-
//
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
const totalMsats = costObj.total_msats;
|
|
2357
|
-
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
2358
|
-
if (typeof totalMsats === "number") {
|
|
2359
|
-
satsCost = totalMsats / 1e3;
|
|
2360
|
-
}
|
|
2353
|
+
// utils/torUtils.ts
|
|
2354
|
+
var TOR_ONION_SUFFIX = ".onion";
|
|
2355
|
+
var isTorContext = () => {
|
|
2356
|
+
if (typeof window === "undefined") return false;
|
|
2357
|
+
const hostname = window.location.hostname.toLowerCase();
|
|
2358
|
+
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2359
|
+
};
|
|
2360
|
+
var isOnionUrl = (url) => {
|
|
2361
|
+
if (!url) return false;
|
|
2362
|
+
const trimmed = url.trim().toLowerCase();
|
|
2363
|
+
if (!trimmed) return false;
|
|
2364
|
+
try {
|
|
2365
|
+
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
2366
|
+
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2367
|
+
} catch {
|
|
2368
|
+
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
2361
2369
|
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2370
|
+
};
|
|
2371
|
+
var shouldAllowHttp = (url, torMode) => {
|
|
2372
|
+
if (!url.startsWith("http://")) return true;
|
|
2373
|
+
if (url.includes("localhost") || url.includes("127.0.0.1")) return true;
|
|
2374
|
+
return torMode && isOnionUrl(url);
|
|
2375
|
+
};
|
|
2376
|
+
var normalizeProviderUrl = (url, torMode = false) => {
|
|
2377
|
+
if (!url || typeof url !== "string") return null;
|
|
2378
|
+
const trimmed = url.trim();
|
|
2379
|
+
if (!trimmed) return null;
|
|
2380
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
2381
|
+
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
2364
2382
|
}
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
const trimmed = id.trim();
|
|
2378
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
2379
|
-
}
|
|
2380
|
-
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
2381
|
-
if (!parsed || typeof parsed !== "object") {
|
|
2382
|
-
return null;
|
|
2383
|
+
const useHttpForOnion = torMode && isOnionUrl(trimmed);
|
|
2384
|
+
const withProto = `${useHttpForOnion ? "http" : "https"}://${trimmed}`;
|
|
2385
|
+
return withProto.endsWith("/") ? withProto : `${withProto}/`;
|
|
2386
|
+
};
|
|
2387
|
+
var dedupePreserveOrder = (urls) => {
|
|
2388
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2389
|
+
const out = [];
|
|
2390
|
+
for (const url of urls) {
|
|
2391
|
+
if (!seen.has(url)) {
|
|
2392
|
+
seen.add(url);
|
|
2393
|
+
out.push(url);
|
|
2394
|
+
}
|
|
2383
2395
|
}
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
+
return out;
|
|
2397
|
+
};
|
|
2398
|
+
var getProviderEndpoints = (provider, torMode) => {
|
|
2399
|
+
const rawUrls = [
|
|
2400
|
+
provider.endpoint_url,
|
|
2401
|
+
...Array.isArray(provider.endpoint_urls) ? provider.endpoint_urls : [],
|
|
2402
|
+
provider.onion_url,
|
|
2403
|
+
...Array.isArray(provider.onion_urls) ? provider.onion_urls : []
|
|
2404
|
+
];
|
|
2405
|
+
const normalized = rawUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2406
|
+
const unique = dedupePreserveOrder(normalized).filter(
|
|
2407
|
+
(value) => shouldAllowHttp(value, torMode)
|
|
2408
|
+
);
|
|
2409
|
+
if (unique.length === 0) return [];
|
|
2410
|
+
const onion = unique.filter((value) => isOnionUrl(value));
|
|
2411
|
+
const clearnet = unique.filter((value) => !isOnionUrl(value));
|
|
2412
|
+
if (torMode) {
|
|
2413
|
+
return onion.length > 0 ? onion : clearnet;
|
|
2396
2414
|
}
|
|
2397
|
-
|
|
2415
|
+
return clearnet;
|
|
2416
|
+
};
|
|
2417
|
+
var filterBaseUrlsForTor = (baseUrls, torMode) => {
|
|
2418
|
+
if (!Array.isArray(baseUrls)) return [];
|
|
2419
|
+
const normalized = baseUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2420
|
+
const filtered = normalized.filter(
|
|
2421
|
+
(value) => torMode ? true : !isOnionUrl(value)
|
|
2422
|
+
);
|
|
2423
|
+
return dedupePreserveOrder(
|
|
2424
|
+
filtered.filter((value) => shouldAllowHttp(value, torMode))
|
|
2425
|
+
);
|
|
2426
|
+
};
|
|
2427
|
+
|
|
2428
|
+
// client/ProviderManager.ts
|
|
2429
|
+
function getImageResolutionFromDataUrl(dataUrl) {
|
|
2430
|
+
try {
|
|
2431
|
+
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:"))
|
|
2432
|
+
return null;
|
|
2433
|
+
const commaIdx = dataUrl.indexOf(",");
|
|
2434
|
+
if (commaIdx === -1) return null;
|
|
2435
|
+
const meta = dataUrl.slice(5, commaIdx);
|
|
2436
|
+
const base64 = dataUrl.slice(commaIdx + 1);
|
|
2437
|
+
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
2438
|
+
const len = binary.length;
|
|
2439
|
+
const bytes = new Uint8Array(len);
|
|
2440
|
+
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
2441
|
+
const isPNG = meta.includes("image/png");
|
|
2442
|
+
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
2443
|
+
if (isPNG) {
|
|
2444
|
+
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
2445
|
+
for (let i = 0; i < sig.length; i++) {
|
|
2446
|
+
if (bytes[i] !== sig[i]) return null;
|
|
2447
|
+
}
|
|
2448
|
+
const view = new DataView(
|
|
2449
|
+
bytes.buffer,
|
|
2450
|
+
bytes.byteOffset,
|
|
2451
|
+
bytes.byteLength
|
|
2452
|
+
);
|
|
2453
|
+
const width = view.getUint32(16, false);
|
|
2454
|
+
const height = view.getUint32(20, false);
|
|
2455
|
+
if (width > 0 && height > 0) return { width, height };
|
|
2456
|
+
return null;
|
|
2457
|
+
}
|
|
2458
|
+
if (isJPEG) {
|
|
2459
|
+
let offset = 0;
|
|
2460
|
+
if (bytes[offset++] !== 255 || bytes[offset++] !== 216) return null;
|
|
2461
|
+
while (offset < bytes.length) {
|
|
2462
|
+
while (offset < bytes.length && bytes[offset] !== 255) offset++;
|
|
2463
|
+
if (offset + 1 >= bytes.length) break;
|
|
2464
|
+
while (bytes[offset] === 255) offset++;
|
|
2465
|
+
const marker = bytes[offset++];
|
|
2466
|
+
if (marker === 216 || marker === 217) continue;
|
|
2467
|
+
if (offset + 1 >= bytes.length) break;
|
|
2468
|
+
const length = bytes[offset] << 8 | bytes[offset + 1];
|
|
2469
|
+
offset += 2;
|
|
2470
|
+
if (marker === 192 || marker === 194) {
|
|
2471
|
+
if (length < 7 || offset + length - 2 > bytes.length) return null;
|
|
2472
|
+
const precision = bytes[offset];
|
|
2473
|
+
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
2474
|
+
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
2475
|
+
if (precision > 0 && width > 0 && height > 0)
|
|
2476
|
+
return { width, height };
|
|
2477
|
+
return null;
|
|
2478
|
+
} else {
|
|
2479
|
+
offset += length - 2;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
return null;
|
|
2483
|
+
}
|
|
2398
2484
|
return null;
|
|
2399
|
-
}
|
|
2400
|
-
const usage = parsed.usage;
|
|
2401
|
-
const usageCost = usage.cost;
|
|
2402
|
-
let cost = 0;
|
|
2403
|
-
let msats = 0;
|
|
2404
|
-
if (typeof usageCost === "number") {
|
|
2405
|
-
cost = usageCost;
|
|
2406
|
-
} else if (usageCost && typeof usageCost === "object") {
|
|
2407
|
-
cost = usageCost.total_usd ?? 0;
|
|
2408
|
-
msats = usageCost.total_msats ?? 0;
|
|
2409
|
-
}
|
|
2410
|
-
if (cost === 0) {
|
|
2411
|
-
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
2412
|
-
}
|
|
2413
|
-
if (msats === 0) {
|
|
2414
|
-
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
2415
|
-
}
|
|
2416
|
-
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
2417
|
-
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
2418
|
-
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
2419
|
-
const result = {
|
|
2420
|
-
promptTokens,
|
|
2421
|
-
completionTokens,
|
|
2422
|
-
totalTokens,
|
|
2423
|
-
cost: Number(cost ?? 0),
|
|
2424
|
-
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
|
|
2425
|
-
};
|
|
2426
|
-
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
2485
|
+
} catch {
|
|
2427
2486
|
return null;
|
|
2428
2487
|
}
|
|
2429
|
-
return result;
|
|
2430
|
-
}
|
|
2431
|
-
function toUsageStats(usage) {
|
|
2432
|
-
if (!usage) return void 0;
|
|
2433
|
-
return {
|
|
2434
|
-
total_tokens: usage.totalTokens,
|
|
2435
|
-
prompt_tokens: usage.promptTokens,
|
|
2436
|
-
completion_tokens: usage.completionTokens,
|
|
2437
|
-
cost: usage.cost,
|
|
2438
|
-
sats_cost: usage.satsCost
|
|
2439
|
-
};
|
|
2440
2488
|
}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
if (!response.body) {
|
|
2454
|
-
throw new Error("Response body is not available");
|
|
2455
|
-
}
|
|
2456
|
-
const reader = response.body.getReader();
|
|
2457
|
-
const decoder = new TextDecoder("utf-8");
|
|
2458
|
-
let buffer = "";
|
|
2459
|
-
this.accumulatedContent = "";
|
|
2460
|
-
this.accumulatedThinking = "";
|
|
2461
|
-
this.accumulatedImages = [];
|
|
2462
|
-
this.isInThinking = false;
|
|
2463
|
-
this.isInContent = false;
|
|
2464
|
-
let usage;
|
|
2465
|
-
let model;
|
|
2466
|
-
let finish_reason;
|
|
2467
|
-
let citations;
|
|
2468
|
-
let annotations;
|
|
2469
|
-
let responseId;
|
|
2470
|
-
try {
|
|
2471
|
-
while (true) {
|
|
2472
|
-
const { done, value } = await reader.read();
|
|
2473
|
-
if (done) {
|
|
2474
|
-
break;
|
|
2475
|
-
}
|
|
2476
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
2477
|
-
buffer += chunk;
|
|
2478
|
-
const lines = buffer.split("\n");
|
|
2479
|
-
buffer = lines.pop() || "";
|
|
2480
|
-
for (const line of lines) {
|
|
2481
|
-
const parsed = this._parseLine(line);
|
|
2482
|
-
if (!parsed) continue;
|
|
2483
|
-
if (parsed.content) {
|
|
2484
|
-
this._handleContent(parsed.content, callbacks, modelId);
|
|
2485
|
-
}
|
|
2486
|
-
if (parsed.reasoning) {
|
|
2487
|
-
this._handleThinking(parsed.reasoning, callbacks);
|
|
2488
|
-
}
|
|
2489
|
-
if (parsed.usage) {
|
|
2490
|
-
usage = parsed.usage;
|
|
2491
|
-
}
|
|
2492
|
-
if (parsed.model) {
|
|
2493
|
-
model = parsed.model;
|
|
2494
|
-
}
|
|
2495
|
-
if (parsed.finish_reason) {
|
|
2496
|
-
finish_reason = parsed.finish_reason;
|
|
2497
|
-
}
|
|
2498
|
-
if (parsed.responseId) {
|
|
2499
|
-
responseId = parsed.responseId;
|
|
2500
|
-
}
|
|
2501
|
-
if (parsed.citations) {
|
|
2502
|
-
citations = parsed.citations;
|
|
2503
|
-
}
|
|
2504
|
-
if (parsed.annotations) {
|
|
2505
|
-
annotations = parsed.annotations;
|
|
2506
|
-
}
|
|
2507
|
-
if (parsed.images) {
|
|
2508
|
-
this._mergeImages(parsed.images);
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
} finally {
|
|
2513
|
-
reader.releaseLock();
|
|
2489
|
+
function calculateImageTokens(width, height, detail = "auto") {
|
|
2490
|
+
if (detail === "low") return 85;
|
|
2491
|
+
let w = width;
|
|
2492
|
+
let h = height;
|
|
2493
|
+
if (w > 2048 || h > 2048) {
|
|
2494
|
+
const aspectRatio = w / h;
|
|
2495
|
+
if (w > h) {
|
|
2496
|
+
w = 2048;
|
|
2497
|
+
h = Math.floor(w / aspectRatio);
|
|
2498
|
+
} else {
|
|
2499
|
+
h = 2048;
|
|
2500
|
+
w = Math.floor(h * aspectRatio);
|
|
2514
2501
|
}
|
|
2515
|
-
return {
|
|
2516
|
-
content: this.accumulatedContent,
|
|
2517
|
-
thinking: this.accumulatedThinking || void 0,
|
|
2518
|
-
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
2519
|
-
usage,
|
|
2520
|
-
model,
|
|
2521
|
-
responseId,
|
|
2522
|
-
finish_reason,
|
|
2523
|
-
citations,
|
|
2524
|
-
annotations
|
|
2525
|
-
};
|
|
2526
2502
|
}
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
const jsonData = line.slice(6);
|
|
2536
|
-
if (jsonData === "[DONE]") {
|
|
2537
|
-
return null;
|
|
2503
|
+
if (w > 768 || h > 768) {
|
|
2504
|
+
const aspectRatio = w / h;
|
|
2505
|
+
if (w > h) {
|
|
2506
|
+
w = 768;
|
|
2507
|
+
h = Math.floor(w / aspectRatio);
|
|
2508
|
+
} else {
|
|
2509
|
+
h = 768;
|
|
2510
|
+
w = Math.floor(h * aspectRatio);
|
|
2538
2511
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
2554
|
-
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
2555
|
-
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
2556
|
-
};
|
|
2557
|
-
}
|
|
2558
|
-
if (parsed.id) {
|
|
2559
|
-
result.responseId = parsed.id;
|
|
2560
|
-
}
|
|
2561
|
-
if (parsed.model) {
|
|
2562
|
-
result.model = parsed.model;
|
|
2563
|
-
}
|
|
2564
|
-
if (parsed.citations) {
|
|
2565
|
-
result.citations = parsed.citations;
|
|
2566
|
-
}
|
|
2567
|
-
if (parsed.annotations) {
|
|
2568
|
-
result.annotations = parsed.annotations;
|
|
2569
|
-
}
|
|
2570
|
-
if (parsed.choices?.[0]?.finish_reason) {
|
|
2571
|
-
result.finish_reason = parsed.choices[0].finish_reason;
|
|
2572
|
-
}
|
|
2573
|
-
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
2574
|
-
if (images && Array.isArray(images)) {
|
|
2575
|
-
result.images = images;
|
|
2576
|
-
}
|
|
2577
|
-
return result;
|
|
2578
|
-
} catch {
|
|
2579
|
-
return null;
|
|
2512
|
+
}
|
|
2513
|
+
const tilesWidth = Math.floor((w + 511) / 512);
|
|
2514
|
+
const tilesHeight = Math.floor((h + 511) / 512);
|
|
2515
|
+
const numTiles = tilesWidth * tilesHeight;
|
|
2516
|
+
return 85 + 170 * numTiles;
|
|
2517
|
+
}
|
|
2518
|
+
var ProviderManager = class _ProviderManager {
|
|
2519
|
+
constructor(providerRegistry, store, logger) {
|
|
2520
|
+
this.providerRegistry = providerRegistry;
|
|
2521
|
+
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2522
|
+
this.logger = (logger ?? consoleLogger).child(`ProviderManager:${this.instanceId}`);
|
|
2523
|
+
if (store) {
|
|
2524
|
+
this.store = store;
|
|
2525
|
+
this.hydrateFromStore();
|
|
2580
2526
|
}
|
|
2581
2527
|
}
|
|
2528
|
+
providerRegistry;
|
|
2529
|
+
failedProviders = /* @__PURE__ */ new Set();
|
|
2530
|
+
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
2531
|
+
lastFailed = /* @__PURE__ */ new Map();
|
|
2532
|
+
/** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
|
|
2533
|
+
providersOnCoolDown = [];
|
|
2534
|
+
/** Cooldown duration in milliseconds (42 seconds) */
|
|
2535
|
+
static COOLDOWN_DURATION_MS = 42 * 1e3;
|
|
2536
|
+
/** Optional persistent store for failure tracking */
|
|
2537
|
+
store = null;
|
|
2538
|
+
/** Instance ID for debugging */
|
|
2539
|
+
instanceId;
|
|
2540
|
+
logger;
|
|
2582
2541
|
/**
|
|
2583
|
-
*
|
|
2542
|
+
* Hydrate in-memory state from persistent store
|
|
2584
2543
|
*/
|
|
2585
|
-
|
|
2586
|
-
if (
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
}
|
|
2595
|
-
this.accumulatedContent += content;
|
|
2596
|
-
}
|
|
2597
|
-
callbacks.onContent(this.accumulatedContent);
|
|
2544
|
+
hydrateFromStore() {
|
|
2545
|
+
if (!this.store) return;
|
|
2546
|
+
const state = this.store.getState();
|
|
2547
|
+
this.failedProviders = new Set(state.failedProviders);
|
|
2548
|
+
this.lastFailed = new Map(Object.entries(state.lastFailed));
|
|
2549
|
+
const now = Date.now();
|
|
2550
|
+
this.providersOnCoolDown = state.providersOnCooldown.filter(
|
|
2551
|
+
(entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
|
|
2552
|
+
).map((entry) => [entry.baseUrl, entry.timestamp]);
|
|
2553
|
+
this.logger.log(`Hydrated from store: failedProviders=${this.failedProviders.size} lastFailed=${this.lastFailed.size} providersOnCooldown=${this.providersOnCoolDown.length}`);
|
|
2598
2554
|
}
|
|
2599
2555
|
/**
|
|
2600
|
-
*
|
|
2556
|
+
* Get instance ID for debugging
|
|
2601
2557
|
*/
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
this.accumulatedThinking += "<thinking> ";
|
|
2605
|
-
this.isInThinking = true;
|
|
2606
|
-
}
|
|
2607
|
-
this.accumulatedThinking += reasoning;
|
|
2608
|
-
callbacks.onThinking(this.accumulatedThinking);
|
|
2558
|
+
getInstanceId() {
|
|
2559
|
+
return this.instanceId;
|
|
2609
2560
|
}
|
|
2610
2561
|
/**
|
|
2611
|
-
*
|
|
2562
|
+
* Clean up expired cooldown entries
|
|
2563
|
+
* Also removes the provider from failedProviders so it can be retried
|
|
2612
2564
|
*/
|
|
2613
|
-
|
|
2614
|
-
const
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2565
|
+
cleanupExpiredCooldowns() {
|
|
2566
|
+
const now = Date.now();
|
|
2567
|
+
const before = this.providersOnCoolDown.length;
|
|
2568
|
+
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
2569
|
+
([url, timestamp]) => {
|
|
2570
|
+
const age = now - timestamp;
|
|
2571
|
+
const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
|
|
2572
|
+
if (isExpired) {
|
|
2573
|
+
this.logger.log(`Removing expired cooldown for ${url} (age: ${age}ms)`);
|
|
2574
|
+
this.failedProviders.delete(url);
|
|
2575
|
+
if (this.store) {
|
|
2576
|
+
this.store.getState().removeFailedProvider(url);
|
|
2577
|
+
}
|
|
2620
2578
|
}
|
|
2621
|
-
|
|
2622
|
-
this.isInThinking = false;
|
|
2623
|
-
this.accumulatedThinking += "</thinking>";
|
|
2624
|
-
} else if (this.isInThinking) {
|
|
2625
|
-
this.accumulatedThinking += part;
|
|
2626
|
-
} else {
|
|
2627
|
-
this.accumulatedContent += part;
|
|
2579
|
+
return !isExpired;
|
|
2628
2580
|
}
|
|
2581
|
+
);
|
|
2582
|
+
const after = this.providersOnCoolDown.length;
|
|
2583
|
+
if (before !== after) {
|
|
2584
|
+
this.logger.log(`Cleaned up ${before - after} expired cooldown(s), ${after} remaining`);
|
|
2629
2585
|
}
|
|
2630
2586
|
}
|
|
2631
2587
|
/**
|
|
2632
|
-
*
|
|
2633
|
-
*/
|
|
2634
|
-
_mergeImages(newImages) {
|
|
2635
|
-
for (const img of newImages) {
|
|
2636
|
-
const newUrl = img.image_url?.url;
|
|
2637
|
-
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
2638
|
-
const existingUrl = existing.image_url?.url;
|
|
2639
|
-
if (newUrl && existingUrl) {
|
|
2640
|
-
return existingUrl === newUrl;
|
|
2641
|
-
}
|
|
2642
|
-
if (img.index !== void 0 && existing.index !== void 0) {
|
|
2643
|
-
return existing.index === img.index;
|
|
2644
|
-
}
|
|
2645
|
-
return false;
|
|
2646
|
-
});
|
|
2647
|
-
if (existingIndex === -1) {
|
|
2648
|
-
this.accumulatedImages.push(img);
|
|
2649
|
-
} else {
|
|
2650
|
-
this.accumulatedImages[existingIndex] = img;
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
};
|
|
2655
|
-
|
|
2656
|
-
// utils/torUtils.ts
|
|
2657
|
-
var TOR_ONION_SUFFIX = ".onion";
|
|
2658
|
-
var isTorContext = () => {
|
|
2659
|
-
if (typeof window === "undefined") return false;
|
|
2660
|
-
const hostname = window.location.hostname.toLowerCase();
|
|
2661
|
-
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2662
|
-
};
|
|
2663
|
-
var isOnionUrl = (url) => {
|
|
2664
|
-
if (!url) return false;
|
|
2665
|
-
const trimmed = url.trim().toLowerCase();
|
|
2666
|
-
if (!trimmed) return false;
|
|
2667
|
-
try {
|
|
2668
|
-
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
2669
|
-
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
2670
|
-
} catch {
|
|
2671
|
-
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
2672
|
-
}
|
|
2673
|
-
};
|
|
2674
|
-
var shouldAllowHttp = (url, torMode) => {
|
|
2675
|
-
if (!url.startsWith("http://")) return true;
|
|
2676
|
-
if (url.includes("localhost") || url.includes("127.0.0.1")) return true;
|
|
2677
|
-
return torMode && isOnionUrl(url);
|
|
2678
|
-
};
|
|
2679
|
-
var normalizeProviderUrl = (url, torMode = false) => {
|
|
2680
|
-
if (!url || typeof url !== "string") return null;
|
|
2681
|
-
const trimmed = url.trim();
|
|
2682
|
-
if (!trimmed) return null;
|
|
2683
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
2684
|
-
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
2685
|
-
}
|
|
2686
|
-
const useHttpForOnion = torMode && isOnionUrl(trimmed);
|
|
2687
|
-
const withProto = `${useHttpForOnion ? "http" : "https"}://${trimmed}`;
|
|
2688
|
-
return withProto.endsWith("/") ? withProto : `${withProto}/`;
|
|
2689
|
-
};
|
|
2690
|
-
var dedupePreserveOrder = (urls) => {
|
|
2691
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2692
|
-
const out = [];
|
|
2693
|
-
for (const url of urls) {
|
|
2694
|
-
if (!seen.has(url)) {
|
|
2695
|
-
seen.add(url);
|
|
2696
|
-
out.push(url);
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
return out;
|
|
2700
|
-
};
|
|
2701
|
-
var getProviderEndpoints = (provider, torMode) => {
|
|
2702
|
-
const rawUrls = [
|
|
2703
|
-
provider.endpoint_url,
|
|
2704
|
-
...Array.isArray(provider.endpoint_urls) ? provider.endpoint_urls : [],
|
|
2705
|
-
provider.onion_url,
|
|
2706
|
-
...Array.isArray(provider.onion_urls) ? provider.onion_urls : []
|
|
2707
|
-
];
|
|
2708
|
-
const normalized = rawUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2709
|
-
const unique = dedupePreserveOrder(normalized).filter(
|
|
2710
|
-
(value) => shouldAllowHttp(value, torMode)
|
|
2711
|
-
);
|
|
2712
|
-
if (unique.length === 0) return [];
|
|
2713
|
-
const onion = unique.filter((value) => isOnionUrl(value));
|
|
2714
|
-
const clearnet = unique.filter((value) => !isOnionUrl(value));
|
|
2715
|
-
if (torMode) {
|
|
2716
|
-
return onion.length > 0 ? onion : clearnet;
|
|
2717
|
-
}
|
|
2718
|
-
return clearnet;
|
|
2719
|
-
};
|
|
2720
|
-
var filterBaseUrlsForTor = (baseUrls, torMode) => {
|
|
2721
|
-
if (!Array.isArray(baseUrls)) return [];
|
|
2722
|
-
const normalized = baseUrls.map((value) => normalizeProviderUrl(value, torMode)).filter((value) => Boolean(value));
|
|
2723
|
-
const filtered = normalized.filter(
|
|
2724
|
-
(value) => torMode ? true : !isOnionUrl(value)
|
|
2725
|
-
);
|
|
2726
|
-
return dedupePreserveOrder(
|
|
2727
|
-
filtered.filter((value) => shouldAllowHttp(value, torMode))
|
|
2728
|
-
);
|
|
2729
|
-
};
|
|
2730
|
-
|
|
2731
|
-
// client/ProviderManager.ts
|
|
2732
|
-
function getImageResolutionFromDataUrl(dataUrl) {
|
|
2733
|
-
try {
|
|
2734
|
-
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:"))
|
|
2735
|
-
return null;
|
|
2736
|
-
const commaIdx = dataUrl.indexOf(",");
|
|
2737
|
-
if (commaIdx === -1) return null;
|
|
2738
|
-
const meta = dataUrl.slice(5, commaIdx);
|
|
2739
|
-
const base64 = dataUrl.slice(commaIdx + 1);
|
|
2740
|
-
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
2741
|
-
const len = binary.length;
|
|
2742
|
-
const bytes = new Uint8Array(len);
|
|
2743
|
-
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
2744
|
-
const isPNG = meta.includes("image/png");
|
|
2745
|
-
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
2746
|
-
if (isPNG) {
|
|
2747
|
-
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
2748
|
-
for (let i = 0; i < sig.length; i++) {
|
|
2749
|
-
if (bytes[i] !== sig[i]) return null;
|
|
2750
|
-
}
|
|
2751
|
-
const view = new DataView(
|
|
2752
|
-
bytes.buffer,
|
|
2753
|
-
bytes.byteOffset,
|
|
2754
|
-
bytes.byteLength
|
|
2755
|
-
);
|
|
2756
|
-
const width = view.getUint32(16, false);
|
|
2757
|
-
const height = view.getUint32(20, false);
|
|
2758
|
-
if (width > 0 && height > 0) return { width, height };
|
|
2759
|
-
return null;
|
|
2760
|
-
}
|
|
2761
|
-
if (isJPEG) {
|
|
2762
|
-
let offset = 0;
|
|
2763
|
-
if (bytes[offset++] !== 255 || bytes[offset++] !== 216) return null;
|
|
2764
|
-
while (offset < bytes.length) {
|
|
2765
|
-
while (offset < bytes.length && bytes[offset] !== 255) offset++;
|
|
2766
|
-
if (offset + 1 >= bytes.length) break;
|
|
2767
|
-
while (bytes[offset] === 255) offset++;
|
|
2768
|
-
const marker = bytes[offset++];
|
|
2769
|
-
if (marker === 216 || marker === 217) continue;
|
|
2770
|
-
if (offset + 1 >= bytes.length) break;
|
|
2771
|
-
const length = bytes[offset] << 8 | bytes[offset + 1];
|
|
2772
|
-
offset += 2;
|
|
2773
|
-
if (marker === 192 || marker === 194) {
|
|
2774
|
-
if (length < 7 || offset + length - 2 > bytes.length) return null;
|
|
2775
|
-
const precision = bytes[offset];
|
|
2776
|
-
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
2777
|
-
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
2778
|
-
if (precision > 0 && width > 0 && height > 0)
|
|
2779
|
-
return { width, height };
|
|
2780
|
-
return null;
|
|
2781
|
-
} else {
|
|
2782
|
-
offset += length - 2;
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
return null;
|
|
2786
|
-
}
|
|
2787
|
-
return null;
|
|
2788
|
-
} catch {
|
|
2789
|
-
return null;
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
function calculateImageTokens(width, height, detail = "auto") {
|
|
2793
|
-
if (detail === "low") return 85;
|
|
2794
|
-
let w = width;
|
|
2795
|
-
let h = height;
|
|
2796
|
-
if (w > 2048 || h > 2048) {
|
|
2797
|
-
const aspectRatio = w / h;
|
|
2798
|
-
if (w > h) {
|
|
2799
|
-
w = 2048;
|
|
2800
|
-
h = Math.floor(w / aspectRatio);
|
|
2801
|
-
} else {
|
|
2802
|
-
h = 2048;
|
|
2803
|
-
w = Math.floor(h * aspectRatio);
|
|
2804
|
-
}
|
|
2805
|
-
}
|
|
2806
|
-
if (w > 768 || h > 768) {
|
|
2807
|
-
const aspectRatio = w / h;
|
|
2808
|
-
if (w > h) {
|
|
2809
|
-
w = 768;
|
|
2810
|
-
h = Math.floor(w / aspectRatio);
|
|
2811
|
-
} else {
|
|
2812
|
-
h = 768;
|
|
2813
|
-
w = Math.floor(h * aspectRatio);
|
|
2814
|
-
}
|
|
2815
|
-
}
|
|
2816
|
-
const tilesWidth = Math.floor((w + 511) / 512);
|
|
2817
|
-
const tilesHeight = Math.floor((h + 511) / 512);
|
|
2818
|
-
const numTiles = tilesWidth * tilesHeight;
|
|
2819
|
-
return 85 + 170 * numTiles;
|
|
2820
|
-
}
|
|
2821
|
-
function isInsecureHttpUrl(url) {
|
|
2822
|
-
return url.startsWith("http://");
|
|
2823
|
-
}
|
|
2824
|
-
var ProviderManager = class _ProviderManager {
|
|
2825
|
-
constructor(providerRegistry, store, logger) {
|
|
2826
|
-
this.providerRegistry = providerRegistry;
|
|
2827
|
-
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2828
|
-
this.logger = (logger ?? consoleLogger).child(`ProviderManager:${this.instanceId}`);
|
|
2829
|
-
if (store) {
|
|
2830
|
-
this.store = store;
|
|
2831
|
-
this.hydrateFromStore();
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
providerRegistry;
|
|
2835
|
-
failedProviders = /* @__PURE__ */ new Set();
|
|
2836
|
-
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
2837
|
-
lastFailed = /* @__PURE__ */ new Map();
|
|
2838
|
-
/** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
|
|
2839
|
-
providersOnCoolDown = [];
|
|
2840
|
-
/** Cooldown duration in milliseconds (42 seconds) */
|
|
2841
|
-
static COOLDOWN_DURATION_MS = 42 * 1e3;
|
|
2842
|
-
/** Optional persistent store for failure tracking */
|
|
2843
|
-
store = null;
|
|
2844
|
-
/** Instance ID for debugging */
|
|
2845
|
-
instanceId;
|
|
2846
|
-
logger;
|
|
2847
|
-
/**
|
|
2848
|
-
* Hydrate in-memory state from persistent store
|
|
2849
|
-
*/
|
|
2850
|
-
hydrateFromStore() {
|
|
2851
|
-
if (!this.store) return;
|
|
2852
|
-
const state = this.store.getState();
|
|
2853
|
-
this.failedProviders = new Set(state.failedProviders);
|
|
2854
|
-
this.lastFailed = new Map(Object.entries(state.lastFailed));
|
|
2855
|
-
const now = Date.now();
|
|
2856
|
-
this.providersOnCoolDown = state.providersOnCooldown.filter(
|
|
2857
|
-
(entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
|
|
2858
|
-
).map((entry) => [entry.baseUrl, entry.timestamp]);
|
|
2859
|
-
this.logger.log(`Hydrated from store: failedProviders=${this.failedProviders.size} lastFailed=${this.lastFailed.size} providersOnCooldown=${this.providersOnCoolDown.length}`);
|
|
2860
|
-
}
|
|
2861
|
-
/**
|
|
2862
|
-
* Get instance ID for debugging
|
|
2863
|
-
*/
|
|
2864
|
-
getInstanceId() {
|
|
2865
|
-
return this.instanceId;
|
|
2866
|
-
}
|
|
2867
|
-
/**
|
|
2868
|
-
* Clean up expired cooldown entries
|
|
2869
|
-
* Also removes the provider from failedProviders so it can be retried
|
|
2870
|
-
*/
|
|
2871
|
-
cleanupExpiredCooldowns() {
|
|
2872
|
-
const now = Date.now();
|
|
2873
|
-
const before = this.providersOnCoolDown.length;
|
|
2874
|
-
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
2875
|
-
([url, timestamp]) => {
|
|
2876
|
-
const age = now - timestamp;
|
|
2877
|
-
const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
|
|
2878
|
-
if (isExpired) {
|
|
2879
|
-
this.logger.log(`Removing expired cooldown for ${url} (age: ${age}ms)`);
|
|
2880
|
-
this.failedProviders.delete(url);
|
|
2881
|
-
if (this.store) {
|
|
2882
|
-
this.store.getState().removeFailedProvider(url);
|
|
2883
|
-
}
|
|
2884
|
-
}
|
|
2885
|
-
return !isExpired;
|
|
2886
|
-
}
|
|
2887
|
-
);
|
|
2888
|
-
const after = this.providersOnCoolDown.length;
|
|
2889
|
-
if (before !== after) {
|
|
2890
|
-
this.logger.log(`Cleaned up ${before - after} expired cooldown(s), ${after} remaining`);
|
|
2891
|
-
}
|
|
2892
|
-
}
|
|
2893
|
-
/**
|
|
2894
|
-
* Get the cooldown duration in milliseconds
|
|
2588
|
+
* Get the cooldown duration in milliseconds
|
|
2895
2589
|
*/
|
|
2896
2590
|
getCooldownDurationMs() {
|
|
2897
2591
|
return _ProviderManager.COOLDOWN_DURATION_MS;
|
|
@@ -3037,7 +2731,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
3037
2731
|
if (this.isOnCooldown(baseUrl)) {
|
|
3038
2732
|
continue;
|
|
3039
2733
|
}
|
|
3040
|
-
if (!torMode &&
|
|
2734
|
+
if (!torMode && isOnionUrl(baseUrl)) {
|
|
3041
2735
|
continue;
|
|
3042
2736
|
}
|
|
3043
2737
|
const model = models.find((m) => m.id === modelId);
|
|
@@ -3088,7 +2782,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
3088
2782
|
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
3089
2783
|
if (disabledProviders.has(baseUrl)) continue;
|
|
3090
2784
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
3091
|
-
if (!torMode &&
|
|
2785
|
+
if (!torMode && isOnionUrl(baseUrl))
|
|
3092
2786
|
continue;
|
|
3093
2787
|
const model = models.find((m) => m.id === modelId);
|
|
3094
2788
|
if (!model) continue;
|
|
@@ -3103,16 +2797,18 @@ var ProviderManager = class _ProviderManager {
|
|
|
3103
2797
|
getProviderPriceRankingForModel(modelId, options = {}) {
|
|
3104
2798
|
const includeDisabled = options.includeDisabled ?? false;
|
|
3105
2799
|
const torMode = options.torMode ?? false;
|
|
3106
|
-
const
|
|
3107
|
-
|
|
3108
|
-
)
|
|
2800
|
+
const disabledProviderList = this.providerRegistry.getDisabledProviders();
|
|
2801
|
+
const disabledProviders = new Set(disabledProviderList);
|
|
2802
|
+
if (disabledProviderList.length > 0) {
|
|
2803
|
+
this.logger.log(`getProviderPriceRankingForModel: disabled providers (${disabledProviderList.length}): ${disabledProviderList.join(", ")}`);
|
|
2804
|
+
}
|
|
3109
2805
|
const allModels = this.providerRegistry.getAllProvidersModels();
|
|
3110
2806
|
const results = [];
|
|
3111
2807
|
for (const [baseUrl, models] of Object.entries(allModels)) {
|
|
3112
2808
|
if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
|
|
3113
2809
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
3114
2810
|
if (torMode && !baseUrl.includes(".onion")) continue;
|
|
3115
|
-
if (!torMode &&
|
|
2811
|
+
if (!torMode && baseUrl.includes(".onion"))
|
|
3116
2812
|
continue;
|
|
3117
2813
|
const match = models.find((model) => model.id === modelId);
|
|
3118
2814
|
if (!match?.sats_pricing) continue;
|
|
@@ -3132,12 +2828,20 @@ var ProviderManager = class _ProviderManager {
|
|
|
3132
2828
|
totalPerMillion
|
|
3133
2829
|
});
|
|
3134
2830
|
}
|
|
3135
|
-
|
|
2831
|
+
results.sort((a, b) => {
|
|
3136
2832
|
if (a.totalPerMillion !== b.totalPerMillion) {
|
|
3137
2833
|
return a.totalPerMillion - b.totalPerMillion;
|
|
3138
2834
|
}
|
|
3139
2835
|
return a.baseUrl.localeCompare(b.baseUrl);
|
|
3140
2836
|
});
|
|
2837
|
+
if (results.length > 0) {
|
|
2838
|
+
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");
|
|
2839
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} ranking (${results.length} providers):
|
|
2840
|
+
${ranking}`);
|
|
2841
|
+
} else {
|
|
2842
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} no providers found`);
|
|
2843
|
+
}
|
|
2844
|
+
return results;
|
|
3141
2845
|
}
|
|
3142
2846
|
/**
|
|
3143
2847
|
* Get best-priced provider for a specific model
|
|
@@ -3330,141 +3034,6 @@ var createMemoryDriver = (seed) => {
|
|
|
3330
3034
|
};
|
|
3331
3035
|
};
|
|
3332
3036
|
|
|
3333
|
-
// storage/drivers/sqlite.ts
|
|
3334
|
-
var isBun = () => {
|
|
3335
|
-
return typeof process.versions.bun !== "undefined";
|
|
3336
|
-
};
|
|
3337
|
-
var cachedDbModule = null;
|
|
3338
|
-
var loadDatabase = async (dbPath) => {
|
|
3339
|
-
if (isBun()) {
|
|
3340
|
-
throw new Error(
|
|
3341
|
-
"SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
|
|
3342
|
-
);
|
|
3343
|
-
}
|
|
3344
|
-
try {
|
|
3345
|
-
if (!cachedDbModule) {
|
|
3346
|
-
cachedDbModule = (await import('better-sqlite3')).default;
|
|
3347
|
-
}
|
|
3348
|
-
return new cachedDbModule(dbPath);
|
|
3349
|
-
} catch (error) {
|
|
3350
|
-
throw new Error(
|
|
3351
|
-
`better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
|
|
3352
|
-
);
|
|
3353
|
-
}
|
|
3354
|
-
};
|
|
3355
|
-
var createSqliteDriver = (options = {}) => {
|
|
3356
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
3357
|
-
const tableName = options.tableName || "sdk_storage";
|
|
3358
|
-
let db;
|
|
3359
|
-
let selectStmt;
|
|
3360
|
-
let upsertStmt;
|
|
3361
|
-
let deleteStmt;
|
|
3362
|
-
const initDb = async () => {
|
|
3363
|
-
if (!db) {
|
|
3364
|
-
db = await loadDatabase(dbPath);
|
|
3365
|
-
db.exec(
|
|
3366
|
-
`CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
3367
|
-
);
|
|
3368
|
-
selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
|
|
3369
|
-
upsertStmt = db.prepare(
|
|
3370
|
-
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)
|
|
3371
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
3372
|
-
);
|
|
3373
|
-
deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
|
|
3374
|
-
}
|
|
3375
|
-
};
|
|
3376
|
-
const ensureInit = async () => {
|
|
3377
|
-
if (!db) {
|
|
3378
|
-
await initDb();
|
|
3379
|
-
}
|
|
3380
|
-
};
|
|
3381
|
-
return {
|
|
3382
|
-
async getItem(key, defaultValue) {
|
|
3383
|
-
try {
|
|
3384
|
-
await ensureInit();
|
|
3385
|
-
const row = selectStmt.get(key);
|
|
3386
|
-
if (!row || typeof row.value !== "string") return defaultValue;
|
|
3387
|
-
try {
|
|
3388
|
-
return JSON.parse(row.value);
|
|
3389
|
-
} catch (parseError) {
|
|
3390
|
-
if (typeof defaultValue === "string") {
|
|
3391
|
-
return row.value;
|
|
3392
|
-
}
|
|
3393
|
-
throw parseError;
|
|
3394
|
-
}
|
|
3395
|
-
} catch (error) {
|
|
3396
|
-
console.error(`SQLite getItem failed for key "${key}":`, error);
|
|
3397
|
-
return defaultValue;
|
|
3398
|
-
}
|
|
3399
|
-
},
|
|
3400
|
-
async setItem(key, value) {
|
|
3401
|
-
try {
|
|
3402
|
-
await ensureInit();
|
|
3403
|
-
upsertStmt.run(key, JSON.stringify(value));
|
|
3404
|
-
} catch (error) {
|
|
3405
|
-
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
3406
|
-
}
|
|
3407
|
-
},
|
|
3408
|
-
async removeItem(key) {
|
|
3409
|
-
try {
|
|
3410
|
-
await ensureInit();
|
|
3411
|
-
deleteStmt.run(key);
|
|
3412
|
-
} catch (error) {
|
|
3413
|
-
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
3414
|
-
}
|
|
3415
|
-
}
|
|
3416
|
-
};
|
|
3417
|
-
};
|
|
3418
|
-
async function createBunSqliteDriver(dbPath, options) {
|
|
3419
|
-
const logger = (options?.logger ?? consoleLogger).child("BunSqliteDriver");
|
|
3420
|
-
const SQLite = (await import(
|
|
3421
|
-
/* webpackIgnore: true */
|
|
3422
|
-
'bun:sqlite'
|
|
3423
|
-
)).default;
|
|
3424
|
-
const db = new SQLite(dbPath);
|
|
3425
|
-
db.run(`
|
|
3426
|
-
CREATE TABLE IF NOT EXISTS sdk_storage (
|
|
3427
|
-
key TEXT PRIMARY KEY,
|
|
3428
|
-
value TEXT NOT NULL
|
|
3429
|
-
)
|
|
3430
|
-
`);
|
|
3431
|
-
return {
|
|
3432
|
-
async getItem(key, defaultValue) {
|
|
3433
|
-
try {
|
|
3434
|
-
const row = db.query("SELECT value FROM sdk_storage WHERE key = ?").get(key);
|
|
3435
|
-
if (!row || typeof row.value !== "string") return defaultValue;
|
|
3436
|
-
try {
|
|
3437
|
-
return JSON.parse(row.value);
|
|
3438
|
-
} catch (parseError) {
|
|
3439
|
-
if (typeof defaultValue === "string") {
|
|
3440
|
-
return row.value;
|
|
3441
|
-
}
|
|
3442
|
-
throw parseError;
|
|
3443
|
-
}
|
|
3444
|
-
} catch (error) {
|
|
3445
|
-
logger.error(`getItem failed for key "${key}":`, error);
|
|
3446
|
-
return defaultValue;
|
|
3447
|
-
}
|
|
3448
|
-
},
|
|
3449
|
-
async setItem(key, value) {
|
|
3450
|
-
try {
|
|
3451
|
-
db.query(
|
|
3452
|
-
"INSERT INTO sdk_storage (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
3453
|
-
).run(key, JSON.stringify(value));
|
|
3454
|
-
} catch (error) {
|
|
3455
|
-
logger.error(`setItem failed for key "${key}":`, error);
|
|
3456
|
-
}
|
|
3457
|
-
},
|
|
3458
|
-
async removeItem(key) {
|
|
3459
|
-
try {
|
|
3460
|
-
db.query("DELETE FROM sdk_storage WHERE key = ?").run(key);
|
|
3461
|
-
} catch (error) {
|
|
3462
|
-
logger.error(`removeItem failed for key "${key}":`, error);
|
|
3463
|
-
}
|
|
3464
|
-
}
|
|
3465
|
-
};
|
|
3466
|
-
}
|
|
3467
|
-
|
|
3468
3037
|
// storage/drivers/indexedDB.ts
|
|
3469
3038
|
var isBrowser = typeof indexedDB !== "undefined";
|
|
3470
3039
|
var openDatabase = (dbName, storeName) => {
|
|
@@ -3472,15 +3041,32 @@ var openDatabase = (dbName, storeName) => {
|
|
|
3472
3041
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
3473
3042
|
}
|
|
3474
3043
|
return new Promise((resolve, reject) => {
|
|
3475
|
-
const request = indexedDB.open(dbName,
|
|
3044
|
+
const request = indexedDB.open(dbName, 2);
|
|
3476
3045
|
request.onupgradeneeded = () => {
|
|
3477
3046
|
const db = request.result;
|
|
3478
3047
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
3479
3048
|
db.createObjectStore(storeName);
|
|
3480
3049
|
}
|
|
3050
|
+
if (storeName !== "usage_tracking" && !db.objectStoreNames.contains("usage_tracking")) {
|
|
3051
|
+
const utStore = db.createObjectStore("usage_tracking", { keyPath: "id" });
|
|
3052
|
+
utStore.createIndex("timestamp", "timestamp", { unique: false });
|
|
3053
|
+
utStore.createIndex("modelId", "modelId", { unique: false });
|
|
3054
|
+
utStore.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
3055
|
+
utStore.createIndex("sessionId", "sessionId", { unique: false });
|
|
3056
|
+
utStore.createIndex("client", "client", { unique: false });
|
|
3057
|
+
}
|
|
3058
|
+
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
3059
|
+
db.createObjectStore("sdk_storage");
|
|
3060
|
+
}
|
|
3481
3061
|
};
|
|
3482
3062
|
request.onsuccess = () => resolve(request.result);
|
|
3483
3063
|
request.onerror = () => reject(request.error);
|
|
3064
|
+
request.onblocked = () => {
|
|
3065
|
+
console.warn(
|
|
3066
|
+
`[IndexedDB driver] open blocked for "${dbName}" (store: "${storeName}") \u2014 close other tabs using this DB`
|
|
3067
|
+
);
|
|
3068
|
+
reject(new Error(`IndexedDB "${dbName}" blocked by another connection`));
|
|
3069
|
+
};
|
|
3484
3070
|
});
|
|
3485
3071
|
};
|
|
3486
3072
|
var createIndexedDBDriver = (options = {}) => {
|
|
@@ -3582,6 +3168,91 @@ var SDK_STORAGE_KEYS = {
|
|
|
3582
3168
|
PROVIDERS_ON_COOLDOWN: "providers_on_cooldown"
|
|
3583
3169
|
};
|
|
3584
3170
|
|
|
3171
|
+
// storage/usageTracking/aggregate.ts
|
|
3172
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
3173
|
+
var jsGroupKey = (entry, groupBy, tzOffsetMinutes) => {
|
|
3174
|
+
switch (groupBy) {
|
|
3175
|
+
case "modelId":
|
|
3176
|
+
return entry.modelId ?? null;
|
|
3177
|
+
case "baseUrl":
|
|
3178
|
+
return entry.baseUrl ?? null;
|
|
3179
|
+
case "client":
|
|
3180
|
+
return entry.client ?? null;
|
|
3181
|
+
case "sessionId":
|
|
3182
|
+
return entry.sessionId ?? null;
|
|
3183
|
+
case "provider":
|
|
3184
|
+
return entry.provider ?? null;
|
|
3185
|
+
case "day": {
|
|
3186
|
+
const d = new Date(entry.timestamp - tzOffsetMinutes * 6e4);
|
|
3187
|
+
return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
|
|
3188
|
+
}
|
|
3189
|
+
case "hour": {
|
|
3190
|
+
const d = new Date(entry.timestamp - tzOffsetMinutes * 6e4);
|
|
3191
|
+
return pad2(d.getUTCHours());
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
};
|
|
3195
|
+
var reduceAggregate = (entries, options = {}) => {
|
|
3196
|
+
const emptyRow = (group) => ({
|
|
3197
|
+
group,
|
|
3198
|
+
requests: 0,
|
|
3199
|
+
promptTokens: 0,
|
|
3200
|
+
completionTokens: 0,
|
|
3201
|
+
totalTokens: 0,
|
|
3202
|
+
cost: 0,
|
|
3203
|
+
satsCost: 0,
|
|
3204
|
+
baseMsats: 0,
|
|
3205
|
+
inputMsats: 0,
|
|
3206
|
+
outputMsats: 0,
|
|
3207
|
+
totalMsats: 0,
|
|
3208
|
+
totalUsd: 0,
|
|
3209
|
+
cacheReadInputTokens: 0,
|
|
3210
|
+
cacheCreationInputTokens: 0,
|
|
3211
|
+
cacheReadMsats: 0,
|
|
3212
|
+
cacheCreationMsats: 0
|
|
3213
|
+
});
|
|
3214
|
+
const accumulate = (row, entry) => {
|
|
3215
|
+
row.requests += 1;
|
|
3216
|
+
row.promptTokens += entry.promptTokens;
|
|
3217
|
+
row.completionTokens += entry.completionTokens;
|
|
3218
|
+
row.totalTokens += entry.totalTokens;
|
|
3219
|
+
row.cost += entry.cost;
|
|
3220
|
+
row.satsCost += entry.satsCost;
|
|
3221
|
+
row.baseMsats += entry.baseMsats ?? 0;
|
|
3222
|
+
row.inputMsats += entry.inputMsats ?? 0;
|
|
3223
|
+
row.outputMsats += entry.outputMsats ?? 0;
|
|
3224
|
+
row.totalMsats += entry.totalMsats ?? 0;
|
|
3225
|
+
row.totalUsd += entry.totalUsd ?? 0;
|
|
3226
|
+
row.cacheReadInputTokens += entry.cacheReadInputTokens ?? 0;
|
|
3227
|
+
row.cacheCreationInputTokens += entry.cacheCreationInputTokens ?? 0;
|
|
3228
|
+
row.cacheReadMsats += entry.cacheReadMsats ?? 0;
|
|
3229
|
+
row.cacheCreationMsats += entry.cacheCreationMsats ?? 0;
|
|
3230
|
+
};
|
|
3231
|
+
if (!options.groupBy) {
|
|
3232
|
+
const total = emptyRow(null);
|
|
3233
|
+
for (const entry of entries) accumulate(total, entry);
|
|
3234
|
+
return [total];
|
|
3235
|
+
}
|
|
3236
|
+
const tz = options.tzOffsetMinutes ?? 0;
|
|
3237
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3238
|
+
for (const entry of entries) {
|
|
3239
|
+
const key = jsGroupKey(entry, options.groupBy, tz);
|
|
3240
|
+
let row = groups.get(key);
|
|
3241
|
+
if (!row) {
|
|
3242
|
+
row = emptyRow(key);
|
|
3243
|
+
groups.set(key, row);
|
|
3244
|
+
}
|
|
3245
|
+
accumulate(row, entry);
|
|
3246
|
+
}
|
|
3247
|
+
const rows = [...groups.values()];
|
|
3248
|
+
if (options.groupBy === "day" || options.groupBy === "hour") {
|
|
3249
|
+
rows.sort((a, b) => (a.group ?? "").localeCompare(b.group ?? ""));
|
|
3250
|
+
} else {
|
|
3251
|
+
rows.sort((a, b) => b.satsCost - a.satsCost);
|
|
3252
|
+
}
|
|
3253
|
+
return rows;
|
|
3254
|
+
};
|
|
3255
|
+
|
|
3585
3256
|
// storage/usageTracking/indexedDB.ts
|
|
3586
3257
|
var DEFAULT_DB_NAME = "routstr-sdk";
|
|
3587
3258
|
var DEFAULT_STORE_NAME = "usage_tracking";
|
|
@@ -3593,9 +3264,10 @@ var openDatabase2 = (dbName, storeName) => {
|
|
|
3593
3264
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
3594
3265
|
}
|
|
3595
3266
|
return new Promise((resolve, reject) => {
|
|
3596
|
-
const request = indexedDB.open(dbName,
|
|
3267
|
+
const request = indexedDB.open(dbName, 3);
|
|
3597
3268
|
request.onupgradeneeded = () => {
|
|
3598
3269
|
const db = request.result;
|
|
3270
|
+
const tx = request.transaction;
|
|
3599
3271
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
3600
3272
|
const store = db.createObjectStore(storeName, { keyPath: "id" });
|
|
3601
3273
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
@@ -3603,10 +3275,25 @@ var openDatabase2 = (dbName, storeName) => {
|
|
|
3603
3275
|
store.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
3604
3276
|
store.createIndex("sessionId", "sessionId", { unique: false });
|
|
3605
3277
|
store.createIndex("client", "client", { unique: false });
|
|
3278
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
3279
|
+
} else if (tx) {
|
|
3280
|
+
const store = tx.objectStore(storeName);
|
|
3281
|
+
if (!store.indexNames.contains("provider")) {
|
|
3282
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
3286
|
+
db.createObjectStore("sdk_storage");
|
|
3606
3287
|
}
|
|
3607
3288
|
};
|
|
3608
3289
|
request.onsuccess = () => resolve(request.result);
|
|
3609
3290
|
request.onerror = () => reject(request.error);
|
|
3291
|
+
request.onblocked = () => {
|
|
3292
|
+
console.warn(
|
|
3293
|
+
`[usageTracking IndexedDB] open blocked for "${dbName}" \u2014 close other tabs using this DB`
|
|
3294
|
+
);
|
|
3295
|
+
reject(new Error(`IndexedDB "${dbName}" blocked by another connection`));
|
|
3296
|
+
};
|
|
3610
3297
|
});
|
|
3611
3298
|
};
|
|
3612
3299
|
var matchesFilters = (entry, options = {}) => {
|
|
@@ -3628,6 +3315,12 @@ var matchesFilters = (entry, options = {}) => {
|
|
|
3628
3315
|
if (options.client && entry.client !== options.client) {
|
|
3629
3316
|
return false;
|
|
3630
3317
|
}
|
|
3318
|
+
if (options.clients && options.clients.length > 0 && (entry.client == null || !options.clients.includes(entry.client))) {
|
|
3319
|
+
return false;
|
|
3320
|
+
}
|
|
3321
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
3322
|
+
return false;
|
|
3323
|
+
}
|
|
3631
3324
|
return true;
|
|
3632
3325
|
};
|
|
3633
3326
|
var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
@@ -3723,6 +3416,10 @@ var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
|
3723
3416
|
const results = await this.list(options2);
|
|
3724
3417
|
return results.length;
|
|
3725
3418
|
},
|
|
3419
|
+
async aggregate(options2 = {}) {
|
|
3420
|
+
const entries = await this.list(options2);
|
|
3421
|
+
return reduceAggregate(entries, options2);
|
|
3422
|
+
},
|
|
3726
3423
|
async deleteOlderThan(timestamp) {
|
|
3727
3424
|
await ensureMigrated();
|
|
3728
3425
|
const db = await getDb();
|
|
@@ -3759,393 +3456,8 @@ var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
|
3759
3456
|
};
|
|
3760
3457
|
};
|
|
3761
3458
|
|
|
3762
|
-
// storage/usageTracking/sqlite.ts
|
|
3763
|
-
var MIGRATION_MARKER_KEY2 = "usage_tracking_migration_v1";
|
|
3764
|
-
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3765
|
-
var isBun2 = () => {
|
|
3766
|
-
return typeof process.versions.bun !== "undefined";
|
|
3767
|
-
};
|
|
3768
|
-
var cachedDbModule2 = null;
|
|
3769
|
-
var loadDatabase2 = async (dbPath) => {
|
|
3770
|
-
if (isBun2()) {
|
|
3771
|
-
throw new Error(
|
|
3772
|
-
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
3773
|
-
);
|
|
3774
|
-
}
|
|
3775
|
-
try {
|
|
3776
|
-
if (!cachedDbModule2) {
|
|
3777
|
-
cachedDbModule2 = (await import('better-sqlite3')).default;
|
|
3778
|
-
}
|
|
3779
|
-
return new cachedDbModule2(dbPath);
|
|
3780
|
-
} catch (error) {
|
|
3781
|
-
throw new Error(
|
|
3782
|
-
`better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
|
|
3783
|
-
);
|
|
3784
|
-
}
|
|
3785
|
-
};
|
|
3786
|
-
var buildWhereClause = (options = {}) => {
|
|
3787
|
-
const clauses = [];
|
|
3788
|
-
const params = [];
|
|
3789
|
-
if (typeof options.before === "number") {
|
|
3790
|
-
clauses.push("timestamp < ?");
|
|
3791
|
-
params.push(options.before);
|
|
3792
|
-
}
|
|
3793
|
-
if (typeof options.after === "number") {
|
|
3794
|
-
clauses.push("timestamp > ?");
|
|
3795
|
-
params.push(options.after);
|
|
3796
|
-
}
|
|
3797
|
-
if (options.modelId) {
|
|
3798
|
-
clauses.push("model_id = ?");
|
|
3799
|
-
params.push(options.modelId);
|
|
3800
|
-
}
|
|
3801
|
-
if (options.baseUrl) {
|
|
3802
|
-
clauses.push("base_url = ?");
|
|
3803
|
-
params.push(normalizeBaseUrl2(options.baseUrl));
|
|
3804
|
-
}
|
|
3805
|
-
if (options.sessionId) {
|
|
3806
|
-
clauses.push("session_id = ?");
|
|
3807
|
-
params.push(options.sessionId);
|
|
3808
|
-
}
|
|
3809
|
-
if (options.client) {
|
|
3810
|
-
clauses.push("client = ?");
|
|
3811
|
-
params.push(options.client);
|
|
3812
|
-
}
|
|
3813
|
-
return {
|
|
3814
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
3815
|
-
params
|
|
3816
|
-
};
|
|
3817
|
-
};
|
|
3818
|
-
var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
3819
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
3820
|
-
const tableName = options.tableName || "usage_tracking";
|
|
3821
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
3822
|
-
let db;
|
|
3823
|
-
let insertStmt;
|
|
3824
|
-
let migrationComplete = false;
|
|
3825
|
-
const initDb = async () => {
|
|
3826
|
-
if (!db) {
|
|
3827
|
-
db = await loadDatabase2(dbPath);
|
|
3828
|
-
db.exec(`
|
|
3829
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
3830
|
-
id TEXT PRIMARY KEY,
|
|
3831
|
-
timestamp INTEGER NOT NULL,
|
|
3832
|
-
model_id TEXT NOT NULL,
|
|
3833
|
-
base_url TEXT NOT NULL,
|
|
3834
|
-
request_id TEXT NOT NULL,
|
|
3835
|
-
cost REAL NOT NULL,
|
|
3836
|
-
sats_cost REAL NOT NULL,
|
|
3837
|
-
prompt_tokens INTEGER NOT NULL,
|
|
3838
|
-
completion_tokens INTEGER NOT NULL,
|
|
3839
|
-
total_tokens INTEGER NOT NULL,
|
|
3840
|
-
client TEXT,
|
|
3841
|
-
session_id TEXT,
|
|
3842
|
-
tags TEXT
|
|
3843
|
-
);
|
|
3844
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
3845
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
3846
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
3847
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
3848
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
3849
|
-
`);
|
|
3850
|
-
insertStmt = db.prepare(`
|
|
3851
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
3852
|
-
id, timestamp, model_id, base_url, request_id,
|
|
3853
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
3854
|
-
client, session_id, tags
|
|
3855
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3856
|
-
`);
|
|
3857
|
-
}
|
|
3858
|
-
};
|
|
3859
|
-
const ensureInit = async () => {
|
|
3860
|
-
if (!db) {
|
|
3861
|
-
await initDb();
|
|
3862
|
-
}
|
|
3863
|
-
};
|
|
3864
|
-
const appendOne = (entry) => {
|
|
3865
|
-
insertStmt.run(
|
|
3866
|
-
entry.id,
|
|
3867
|
-
entry.timestamp,
|
|
3868
|
-
entry.modelId,
|
|
3869
|
-
normalizeBaseUrl2(entry.baseUrl),
|
|
3870
|
-
entry.requestId,
|
|
3871
|
-
entry.cost,
|
|
3872
|
-
entry.satsCost,
|
|
3873
|
-
entry.promptTokens,
|
|
3874
|
-
entry.completionTokens,
|
|
3875
|
-
entry.totalTokens,
|
|
3876
|
-
entry.client ?? null,
|
|
3877
|
-
entry.sessionId ?? null,
|
|
3878
|
-
JSON.stringify(entry.tags ?? [])
|
|
3879
|
-
);
|
|
3880
|
-
};
|
|
3881
|
-
const ensureMigrated = async () => {
|
|
3882
|
-
if (!legacyStorageDriver || migrationComplete) return;
|
|
3883
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
3884
|
-
MIGRATION_MARKER_KEY2,
|
|
3885
|
-
false
|
|
3886
|
-
);
|
|
3887
|
-
if (migrated) {
|
|
3888
|
-
migrationComplete = true;
|
|
3889
|
-
return;
|
|
3890
|
-
}
|
|
3891
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
3892
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
3893
|
-
[]
|
|
3894
|
-
);
|
|
3895
|
-
for (const entry of legacyEntries) {
|
|
3896
|
-
appendOne(entry);
|
|
3897
|
-
}
|
|
3898
|
-
if (legacyEntries.length > 0) {
|
|
3899
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
3900
|
-
}
|
|
3901
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
3902
|
-
migrationComplete = true;
|
|
3903
|
-
};
|
|
3904
|
-
const mapRow = (row) => ({
|
|
3905
|
-
id: row.id,
|
|
3906
|
-
timestamp: row.timestamp,
|
|
3907
|
-
modelId: row.model_id,
|
|
3908
|
-
baseUrl: row.base_url,
|
|
3909
|
-
requestId: row.request_id,
|
|
3910
|
-
cost: row.cost,
|
|
3911
|
-
satsCost: row.sats_cost,
|
|
3912
|
-
promptTokens: row.prompt_tokens,
|
|
3913
|
-
completionTokens: row.completion_tokens,
|
|
3914
|
-
totalTokens: row.total_tokens,
|
|
3915
|
-
client: row.client ?? void 0,
|
|
3916
|
-
sessionId: row.session_id ?? void 0,
|
|
3917
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
3918
|
-
});
|
|
3919
|
-
return {
|
|
3920
|
-
async migrate() {
|
|
3921
|
-
await ensureInit();
|
|
3922
|
-
await ensureMigrated();
|
|
3923
|
-
},
|
|
3924
|
-
async append(entry) {
|
|
3925
|
-
await ensureInit();
|
|
3926
|
-
await ensureMigrated();
|
|
3927
|
-
appendOne(entry);
|
|
3928
|
-
},
|
|
3929
|
-
async appendMany(entries) {
|
|
3930
|
-
await ensureInit();
|
|
3931
|
-
await ensureMigrated();
|
|
3932
|
-
for (const entry of entries) {
|
|
3933
|
-
appendOne(entry);
|
|
3934
|
-
}
|
|
3935
|
-
},
|
|
3936
|
-
async list(options2 = {}) {
|
|
3937
|
-
await ensureInit();
|
|
3938
|
-
await ensureMigrated();
|
|
3939
|
-
const { sql, params } = buildWhereClause(options2);
|
|
3940
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
3941
|
-
const stmt = db.prepare(
|
|
3942
|
-
`SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`
|
|
3943
|
-
);
|
|
3944
|
-
const rows = stmt.all(
|
|
3945
|
-
...typeof options2.limit === "number" ? [...params, options2.limit] : params
|
|
3946
|
-
);
|
|
3947
|
-
return rows.map(mapRow);
|
|
3948
|
-
},
|
|
3949
|
-
async count(options2 = {}) {
|
|
3950
|
-
await ensureInit();
|
|
3951
|
-
await ensureMigrated();
|
|
3952
|
-
const { sql, params } = buildWhereClause(options2);
|
|
3953
|
-
const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
|
|
3954
|
-
const row = stmt.get(...params);
|
|
3955
|
-
return Number(row?.count ?? 0);
|
|
3956
|
-
},
|
|
3957
|
-
async deleteOlderThan(timestamp) {
|
|
3958
|
-
await ensureInit();
|
|
3959
|
-
await ensureMigrated();
|
|
3960
|
-
const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
|
|
3961
|
-
const result = stmt.run(timestamp);
|
|
3962
|
-
return result.changes;
|
|
3963
|
-
},
|
|
3964
|
-
async clear() {
|
|
3965
|
-
await ensureInit();
|
|
3966
|
-
await ensureMigrated();
|
|
3967
|
-
db.prepare(`DELETE FROM ${tableName}`).run();
|
|
3968
|
-
}
|
|
3969
|
-
};
|
|
3970
|
-
};
|
|
3971
|
-
|
|
3972
|
-
// storage/usageTracking/bunSqlite.ts
|
|
3973
|
-
var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
|
|
3974
|
-
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3975
|
-
var buildWhereClause2 = (options = {}) => {
|
|
3976
|
-
const clauses = [];
|
|
3977
|
-
const params = [];
|
|
3978
|
-
if (typeof options.before === "number") {
|
|
3979
|
-
clauses.push("timestamp < ?");
|
|
3980
|
-
params.push(options.before);
|
|
3981
|
-
}
|
|
3982
|
-
if (typeof options.after === "number") {
|
|
3983
|
-
clauses.push("timestamp > ?");
|
|
3984
|
-
params.push(options.after);
|
|
3985
|
-
}
|
|
3986
|
-
if (options.modelId) {
|
|
3987
|
-
clauses.push("model_id = ?");
|
|
3988
|
-
params.push(options.modelId);
|
|
3989
|
-
}
|
|
3990
|
-
if (options.baseUrl) {
|
|
3991
|
-
clauses.push("base_url = ?");
|
|
3992
|
-
params.push(normalizeBaseUrl3(options.baseUrl));
|
|
3993
|
-
}
|
|
3994
|
-
if (options.sessionId) {
|
|
3995
|
-
clauses.push("session_id = ?");
|
|
3996
|
-
params.push(options.sessionId);
|
|
3997
|
-
}
|
|
3998
|
-
if (options.client) {
|
|
3999
|
-
clauses.push("client = ?");
|
|
4000
|
-
params.push(options.client);
|
|
4001
|
-
}
|
|
4002
|
-
return {
|
|
4003
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
4004
|
-
params
|
|
4005
|
-
};
|
|
4006
|
-
};
|
|
4007
|
-
var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
4008
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
4009
|
-
const tableName = options.tableName || "usage_tracking";
|
|
4010
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
4011
|
-
const SQLiteDatabase = options.sqlite?.Database;
|
|
4012
|
-
let migrationPromise = null;
|
|
4013
|
-
if (!SQLiteDatabase) {
|
|
4014
|
-
throw new Error(
|
|
4015
|
-
"Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
|
|
4016
|
-
);
|
|
4017
|
-
}
|
|
4018
|
-
const db = new SQLiteDatabase(dbPath);
|
|
4019
|
-
db.run(`
|
|
4020
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
4021
|
-
id TEXT PRIMARY KEY,
|
|
4022
|
-
timestamp INTEGER NOT NULL,
|
|
4023
|
-
model_id TEXT NOT NULL,
|
|
4024
|
-
base_url TEXT NOT NULL,
|
|
4025
|
-
request_id TEXT NOT NULL,
|
|
4026
|
-
cost REAL NOT NULL,
|
|
4027
|
-
sats_cost REAL NOT NULL,
|
|
4028
|
-
prompt_tokens INTEGER NOT NULL,
|
|
4029
|
-
completion_tokens INTEGER NOT NULL,
|
|
4030
|
-
total_tokens INTEGER NOT NULL,
|
|
4031
|
-
client TEXT,
|
|
4032
|
-
session_id TEXT,
|
|
4033
|
-
tags TEXT
|
|
4034
|
-
)
|
|
4035
|
-
`);
|
|
4036
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
|
|
4037
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
|
|
4038
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
|
|
4039
|
-
const appendOne = (entry) => {
|
|
4040
|
-
db.query(`
|
|
4041
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
4042
|
-
id, timestamp, model_id, base_url, request_id,
|
|
4043
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
4044
|
-
client, session_id, tags
|
|
4045
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4046
|
-
`).run(
|
|
4047
|
-
entry.id,
|
|
4048
|
-
entry.timestamp,
|
|
4049
|
-
entry.modelId,
|
|
4050
|
-
normalizeBaseUrl3(entry.baseUrl),
|
|
4051
|
-
entry.requestId,
|
|
4052
|
-
entry.cost,
|
|
4053
|
-
entry.satsCost,
|
|
4054
|
-
entry.promptTokens,
|
|
4055
|
-
entry.completionTokens,
|
|
4056
|
-
entry.totalTokens,
|
|
4057
|
-
entry.client ?? null,
|
|
4058
|
-
entry.sessionId ?? null,
|
|
4059
|
-
JSON.stringify(entry.tags ?? [])
|
|
4060
|
-
);
|
|
4061
|
-
};
|
|
4062
|
-
const mapRow = (row) => ({
|
|
4063
|
-
id: row.id,
|
|
4064
|
-
timestamp: row.timestamp,
|
|
4065
|
-
modelId: row.model_id,
|
|
4066
|
-
baseUrl: row.base_url,
|
|
4067
|
-
requestId: row.request_id,
|
|
4068
|
-
cost: row.cost,
|
|
4069
|
-
satsCost: row.sats_cost,
|
|
4070
|
-
promptTokens: row.prompt_tokens,
|
|
4071
|
-
completionTokens: row.completion_tokens,
|
|
4072
|
-
totalTokens: row.total_tokens,
|
|
4073
|
-
client: row.client ?? void 0,
|
|
4074
|
-
sessionId: row.session_id ?? void 0,
|
|
4075
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
4076
|
-
});
|
|
4077
|
-
const ensureMigrated = async () => {
|
|
4078
|
-
if (!legacyStorageDriver) return;
|
|
4079
|
-
if (!migrationPromise) {
|
|
4080
|
-
migrationPromise = (async () => {
|
|
4081
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
4082
|
-
MIGRATION_MARKER_KEY3,
|
|
4083
|
-
false
|
|
4084
|
-
);
|
|
4085
|
-
if (migrated) return;
|
|
4086
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
4087
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
4088
|
-
[]
|
|
4089
|
-
);
|
|
4090
|
-
if (legacyEntries.length > 0) {
|
|
4091
|
-
for (const entry of legacyEntries) {
|
|
4092
|
-
appendOne(entry);
|
|
4093
|
-
}
|
|
4094
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
4095
|
-
}
|
|
4096
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
|
|
4097
|
-
})();
|
|
4098
|
-
}
|
|
4099
|
-
await migrationPromise;
|
|
4100
|
-
};
|
|
4101
|
-
return {
|
|
4102
|
-
async migrate() {
|
|
4103
|
-
await ensureMigrated();
|
|
4104
|
-
},
|
|
4105
|
-
async append(entry) {
|
|
4106
|
-
await ensureMigrated();
|
|
4107
|
-
appendOne(entry);
|
|
4108
|
-
},
|
|
4109
|
-
async appendMany(entries) {
|
|
4110
|
-
await ensureMigrated();
|
|
4111
|
-
for (const entry of entries) {
|
|
4112
|
-
appendOne(entry);
|
|
4113
|
-
}
|
|
4114
|
-
},
|
|
4115
|
-
async list(options2 = {}) {
|
|
4116
|
-
await ensureMigrated();
|
|
4117
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
4118
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
4119
|
-
const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
|
|
4120
|
-
let rows;
|
|
4121
|
-
if (typeof options2.limit === "number") {
|
|
4122
|
-
rows = db.query(query).all(...params, options2.limit);
|
|
4123
|
-
} else {
|
|
4124
|
-
rows = db.query(query).all(...params);
|
|
4125
|
-
}
|
|
4126
|
-
return rows.map(mapRow);
|
|
4127
|
-
},
|
|
4128
|
-
async count(options2 = {}) {
|
|
4129
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
4130
|
-
const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
|
|
4131
|
-
const row = db.query(query).get(...params);
|
|
4132
|
-
return Number(row?.count ?? 0);
|
|
4133
|
-
},
|
|
4134
|
-
async deleteOlderThan(timestamp) {
|
|
4135
|
-
await ensureMigrated();
|
|
4136
|
-
const before = timestamp;
|
|
4137
|
-
const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
|
|
4138
|
-
return result.changes ?? 0;
|
|
4139
|
-
},
|
|
4140
|
-
async clear() {
|
|
4141
|
-
await ensureMigrated();
|
|
4142
|
-
db.query(`DELETE FROM ${tableName}`).run();
|
|
4143
|
-
}
|
|
4144
|
-
};
|
|
4145
|
-
};
|
|
4146
|
-
|
|
4147
3459
|
// storage/usageTracking/memory.ts
|
|
4148
|
-
var
|
|
3460
|
+
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
4149
3461
|
var matchesFilters2 = (entry, options = {}) => {
|
|
4150
3462
|
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
4151
3463
|
return false;
|
|
@@ -4156,7 +3468,7 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
4156
3468
|
if (options.modelId && entry.modelId !== options.modelId) {
|
|
4157
3469
|
return false;
|
|
4158
3470
|
}
|
|
4159
|
-
if (options.baseUrl &&
|
|
3471
|
+
if (options.baseUrl && normalizeBaseUrl2(entry.baseUrl) !== normalizeBaseUrl2(options.baseUrl)) {
|
|
4160
3472
|
return false;
|
|
4161
3473
|
}
|
|
4162
3474
|
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
@@ -4165,23 +3477,29 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
4165
3477
|
if (options.client && entry.client !== options.client) {
|
|
4166
3478
|
return false;
|
|
4167
3479
|
}
|
|
3480
|
+
if (options.clients && options.clients.length > 0 && (entry.client == null || !options.clients.includes(entry.client))) {
|
|
3481
|
+
return false;
|
|
3482
|
+
}
|
|
3483
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
3484
|
+
return false;
|
|
3485
|
+
}
|
|
4168
3486
|
return true;
|
|
4169
3487
|
};
|
|
4170
3488
|
var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
4171
3489
|
const store = /* @__PURE__ */ new Map();
|
|
4172
3490
|
for (const entry of seed) {
|
|
4173
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
3491
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
4174
3492
|
}
|
|
4175
3493
|
return {
|
|
4176
3494
|
async migrate() {
|
|
4177
3495
|
return;
|
|
4178
3496
|
},
|
|
4179
3497
|
async append(entry) {
|
|
4180
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
3498
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
4181
3499
|
},
|
|
4182
3500
|
async appendMany(entries) {
|
|
4183
3501
|
for (const entry of entries) {
|
|
4184
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
3502
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
4185
3503
|
}
|
|
4186
3504
|
},
|
|
4187
3505
|
async list(options = {}) {
|
|
@@ -4194,6 +3512,10 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
4194
3512
|
async count(options = {}) {
|
|
4195
3513
|
return (await this.list(options)).length;
|
|
4196
3514
|
},
|
|
3515
|
+
async aggregate(options = {}) {
|
|
3516
|
+
const entries = [...store.values()].filter((entry) => matchesFilters2(entry, options));
|
|
3517
|
+
return reduceAggregate(entries, options);
|
|
3518
|
+
},
|
|
4197
3519
|
async deleteOlderThan(timestamp) {
|
|
4198
3520
|
let deleted = 0;
|
|
4199
3521
|
for (const [id, entry] of store.entries()) {
|
|
@@ -4209,7 +3531,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
4209
3531
|
}
|
|
4210
3532
|
};
|
|
4211
3533
|
};
|
|
4212
|
-
var
|
|
3534
|
+
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
4213
3535
|
var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
4214
3536
|
modelsFromAllProviders: {},
|
|
4215
3537
|
lastUsedModel: null,
|
|
@@ -4232,7 +3554,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4232
3554
|
setModelsFromAllProviders: (value) => {
|
|
4233
3555
|
const normalized = {};
|
|
4234
3556
|
for (const [baseUrl, models] of Object.entries(value)) {
|
|
4235
|
-
normalized[
|
|
3557
|
+
normalized[normalizeBaseUrl3(baseUrl)] = models;
|
|
4236
3558
|
}
|
|
4237
3559
|
void driver.setItem(
|
|
4238
3560
|
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
@@ -4245,7 +3567,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4245
3567
|
set({ lastUsedModel: value });
|
|
4246
3568
|
},
|
|
4247
3569
|
setBaseUrlsList: (value) => {
|
|
4248
|
-
const normalized = value.map((url) =>
|
|
3570
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
4249
3571
|
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
4250
3572
|
set({ baseUrlsList: normalized });
|
|
4251
3573
|
},
|
|
@@ -4254,14 +3576,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4254
3576
|
set({ lastBaseUrlsUpdate: value });
|
|
4255
3577
|
},
|
|
4256
3578
|
setDisabledProviders: (value) => {
|
|
4257
|
-
const normalized = value.map((url) =>
|
|
3579
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
4258
3580
|
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
4259
3581
|
set({ disabledProviders: normalized });
|
|
4260
3582
|
},
|
|
4261
3583
|
setMintsFromAllProviders: (value) => {
|
|
4262
3584
|
const normalized = {};
|
|
4263
3585
|
for (const [baseUrl, mints] of Object.entries(value)) {
|
|
4264
|
-
normalized[
|
|
3586
|
+
normalized[normalizeBaseUrl3(baseUrl)] = mints.map(
|
|
4265
3587
|
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
4266
3588
|
);
|
|
4267
3589
|
}
|
|
@@ -4274,7 +3596,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4274
3596
|
setInfoFromAllProviders: (value) => {
|
|
4275
3597
|
const normalized = {};
|
|
4276
3598
|
for (const [baseUrl, info] of Object.entries(value)) {
|
|
4277
|
-
normalized[
|
|
3599
|
+
normalized[normalizeBaseUrl3(baseUrl)] = info;
|
|
4278
3600
|
}
|
|
4279
3601
|
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
4280
3602
|
set({ infoFromAllProviders: normalized });
|
|
@@ -4282,7 +3604,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4282
3604
|
setLastModelsUpdate: (value) => {
|
|
4283
3605
|
const normalized = {};
|
|
4284
3606
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
4285
|
-
normalized[
|
|
3607
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
4286
3608
|
}
|
|
4287
3609
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
4288
3610
|
set({ lastModelsUpdate: normalized });
|
|
@@ -4292,7 +3614,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4292
3614
|
const updates = typeof value === "function" ? value(state.apiKeys) : value;
|
|
4293
3615
|
const normalized = updates.map((entry) => ({
|
|
4294
3616
|
...entry,
|
|
4295
|
-
baseUrl:
|
|
3617
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4296
3618
|
balance: entry.balance ?? 0,
|
|
4297
3619
|
lastUsed: entry.lastUsed ?? null
|
|
4298
3620
|
}));
|
|
@@ -4304,7 +3626,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4304
3626
|
set((state) => {
|
|
4305
3627
|
const updates = typeof value === "function" ? value(state.childKeys) : value;
|
|
4306
3628
|
const normalized = updates.map((entry) => ({
|
|
4307
|
-
parentBaseUrl:
|
|
3629
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
4308
3630
|
childKey: entry.childKey,
|
|
4309
3631
|
balance: entry.balance ?? 0,
|
|
4310
3632
|
balanceLimit: entry.balanceLimit,
|
|
@@ -4318,9 +3640,9 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4318
3640
|
setXcashuTokens: (value) => {
|
|
4319
3641
|
const normalized = {};
|
|
4320
3642
|
for (const [baseUrl, tokens] of Object.entries(value)) {
|
|
4321
|
-
normalized[
|
|
3643
|
+
normalized[normalizeBaseUrl3(baseUrl)] = tokens.map((entry) => ({
|
|
4322
3644
|
...entry,
|
|
4323
|
-
baseUrl:
|
|
3645
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4324
3646
|
createdAt: entry.createdAt ?? Date.now(),
|
|
4325
3647
|
tryCount: entry.tryCount ?? 0
|
|
4326
3648
|
}));
|
|
@@ -4371,12 +3693,12 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4371
3693
|
},
|
|
4372
3694
|
// ========== Failure Tracking ==========
|
|
4373
3695
|
setFailedProviders: (value) => {
|
|
4374
|
-
const normalized = value.map((url) =>
|
|
3696
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
4375
3697
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
|
|
4376
3698
|
set({ failedProviders: normalized });
|
|
4377
3699
|
},
|
|
4378
3700
|
addFailedProvider: (baseUrl) => {
|
|
4379
|
-
const normalized =
|
|
3701
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4380
3702
|
const current = get().failedProviders;
|
|
4381
3703
|
if (!current.includes(normalized)) {
|
|
4382
3704
|
const updated = [...current, normalized];
|
|
@@ -4385,7 +3707,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4385
3707
|
}
|
|
4386
3708
|
},
|
|
4387
3709
|
removeFailedProvider: (baseUrl) => {
|
|
4388
|
-
const normalized =
|
|
3710
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4389
3711
|
const current = get().failedProviders;
|
|
4390
3712
|
const updated = current.filter((url) => url !== normalized);
|
|
4391
3713
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
@@ -4394,13 +3716,13 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4394
3716
|
setLastFailed: (value) => {
|
|
4395
3717
|
const normalized = {};
|
|
4396
3718
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
4397
|
-
normalized[
|
|
3719
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
4398
3720
|
}
|
|
4399
3721
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
|
|
4400
3722
|
set({ lastFailed: normalized });
|
|
4401
3723
|
},
|
|
4402
3724
|
setLastFailedTimestamp: (baseUrl, timestamp) => {
|
|
4403
|
-
const normalized =
|
|
3725
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4404
3726
|
const current = get().lastFailed;
|
|
4405
3727
|
const updated = { ...current, [normalized]: timestamp };
|
|
4406
3728
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
|
|
@@ -4408,14 +3730,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4408
3730
|
},
|
|
4409
3731
|
setProvidersOnCooldown: (value) => {
|
|
4410
3732
|
const normalized = value.map((entry) => ({
|
|
4411
|
-
baseUrl:
|
|
3733
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4412
3734
|
timestamp: entry.timestamp
|
|
4413
3735
|
}));
|
|
4414
3736
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
|
|
4415
3737
|
set({ providersOnCooldown: normalized });
|
|
4416
3738
|
},
|
|
4417
3739
|
addProviderOnCooldown: (baseUrl, timestamp) => {
|
|
4418
|
-
const normalized =
|
|
3740
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4419
3741
|
const current = get().providersOnCooldown;
|
|
4420
3742
|
if (!current.some((entry) => entry.baseUrl === normalized)) {
|
|
4421
3743
|
const updated = [...current, { baseUrl: normalized, timestamp }];
|
|
@@ -4424,7 +3746,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
4424
3746
|
}
|
|
4425
3747
|
},
|
|
4426
3748
|
removeProviderFromCooldown: (baseUrl) => {
|
|
4427
|
-
const normalized =
|
|
3749
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4428
3750
|
const current = get().providersOnCooldown;
|
|
4429
3751
|
const updated = current.filter((entry) => entry.baseUrl !== normalized);
|
|
4430
3752
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
@@ -4495,40 +3817,40 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
4495
3817
|
]);
|
|
4496
3818
|
const modelsFromAllProviders = Object.fromEntries(
|
|
4497
3819
|
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
4498
|
-
|
|
3820
|
+
normalizeBaseUrl3(baseUrl),
|
|
4499
3821
|
models
|
|
4500
3822
|
])
|
|
4501
3823
|
);
|
|
4502
|
-
const baseUrlsList = rawBaseUrls.map((url) =>
|
|
3824
|
+
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl3(url));
|
|
4503
3825
|
const disabledProviders = rawDisabledProviders.map(
|
|
4504
|
-
(url) =>
|
|
3826
|
+
(url) => normalizeBaseUrl3(url)
|
|
4505
3827
|
);
|
|
4506
3828
|
const mintsFromAllProviders = Object.fromEntries(
|
|
4507
3829
|
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
4508
|
-
|
|
3830
|
+
normalizeBaseUrl3(baseUrl),
|
|
4509
3831
|
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
4510
3832
|
])
|
|
4511
3833
|
);
|
|
4512
3834
|
const infoFromAllProviders = Object.fromEntries(
|
|
4513
3835
|
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
4514
|
-
|
|
3836
|
+
normalizeBaseUrl3(baseUrl),
|
|
4515
3837
|
info
|
|
4516
3838
|
])
|
|
4517
3839
|
);
|
|
4518
3840
|
const lastModelsUpdate = Object.fromEntries(
|
|
4519
3841
|
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
4520
|
-
|
|
3842
|
+
normalizeBaseUrl3(baseUrl),
|
|
4521
3843
|
timestamp
|
|
4522
3844
|
])
|
|
4523
3845
|
);
|
|
4524
3846
|
const apiKeys = rawApiKeys.map((entry) => ({
|
|
4525
3847
|
...entry,
|
|
4526
|
-
baseUrl:
|
|
3848
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4527
3849
|
balance: entry.balance ?? 0,
|
|
4528
3850
|
lastUsed: entry.lastUsed ?? null
|
|
4529
3851
|
}));
|
|
4530
3852
|
const childKeys = rawChildKeys.map((entry) => ({
|
|
4531
|
-
parentBaseUrl:
|
|
3853
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
4532
3854
|
childKey: entry.childKey,
|
|
4533
3855
|
balance: entry.balance ?? 0,
|
|
4534
3856
|
balanceLimit: entry.balanceLimit,
|
|
@@ -4537,9 +3859,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
4537
3859
|
}));
|
|
4538
3860
|
const xcashuTokens = Object.fromEntries(
|
|
4539
3861
|
Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
|
|
4540
|
-
|
|
3862
|
+
normalizeBaseUrl3(baseUrl),
|
|
4541
3863
|
tokens.map((entry) => ({
|
|
4542
|
-
baseUrl:
|
|
3864
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4543
3865
|
token: entry.token,
|
|
4544
3866
|
createdAt: entry.createdAt ?? Date.now(),
|
|
4545
3867
|
tryCount: entry.tryCount ?? 0
|
|
@@ -4560,16 +3882,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
4560
3882
|
lastUsed: entry.lastUsed ?? null
|
|
4561
3883
|
}));
|
|
4562
3884
|
const failedProviders = rawFailedProviders.map(
|
|
4563
|
-
(url) =>
|
|
3885
|
+
(url) => normalizeBaseUrl3(url)
|
|
4564
3886
|
);
|
|
4565
3887
|
const lastFailed = Object.fromEntries(
|
|
4566
3888
|
Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
|
|
4567
|
-
|
|
3889
|
+
normalizeBaseUrl3(baseUrl),
|
|
4568
3890
|
timestamp
|
|
4569
3891
|
])
|
|
4570
3892
|
);
|
|
4571
3893
|
const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
|
|
4572
|
-
baseUrl:
|
|
3894
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
4573
3895
|
timestamp: entry.timestamp
|
|
4574
3896
|
}));
|
|
4575
3897
|
store.setState({
|
|
@@ -4610,12 +3932,12 @@ var createDiscoveryAdapterFromStore = (store) => ({
|
|
|
4610
3932
|
getCachedProviderInfo: () => store.getState().infoFromAllProviders,
|
|
4611
3933
|
setCachedProviderInfo: (info) => store.getState().setInfoFromAllProviders(info),
|
|
4612
3934
|
getProviderLastUpdate: (baseUrl) => {
|
|
4613
|
-
const normalized =
|
|
3935
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4614
3936
|
const timestamps = store.getState().lastModelsUpdate;
|
|
4615
3937
|
return timestamps[normalized] || null;
|
|
4616
3938
|
},
|
|
4617
3939
|
setProviderLastUpdate: (baseUrl, timestamp) => {
|
|
4618
|
-
const normalized =
|
|
3940
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4619
3941
|
const timestamps = { ...store.getState().lastModelsUpdate };
|
|
4620
3942
|
timestamps[normalized] = timestamp;
|
|
4621
3943
|
store.getState().setLastModelsUpdate(timestamps);
|
|
@@ -4644,24 +3966,24 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4644
3966
|
return Object.entries(distributionMap).map(([baseUrl, amt]) => ({ baseUrl, amount: amt })).sort((a, b) => b.amount - a.amount);
|
|
4645
3967
|
},
|
|
4646
3968
|
saveProviderInfo: (baseUrl, info) => {
|
|
4647
|
-
const normalized =
|
|
3969
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4648
3970
|
const next = { ...store.getState().infoFromAllProviders };
|
|
4649
3971
|
next[normalized] = info;
|
|
4650
3972
|
store.getState().setInfoFromAllProviders(next);
|
|
4651
3973
|
},
|
|
4652
3974
|
getProviderInfo: (baseUrl) => {
|
|
4653
|
-
const normalized =
|
|
3975
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4654
3976
|
return store.getState().infoFromAllProviders[normalized] || null;
|
|
4655
3977
|
},
|
|
4656
3978
|
// ========== API Keys (for apikeys mode) ==========
|
|
4657
3979
|
getApiKey: (baseUrl) => {
|
|
4658
|
-
const normalized =
|
|
3980
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4659
3981
|
const entry = store.getState().apiKeys.find((key) => key.baseUrl === normalized);
|
|
4660
3982
|
if (!entry) return null;
|
|
4661
3983
|
return entry;
|
|
4662
3984
|
},
|
|
4663
3985
|
setApiKey: (baseUrl, key) => {
|
|
4664
|
-
const normalized =
|
|
3986
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4665
3987
|
const keys = store.getState().apiKeys;
|
|
4666
3988
|
const existingIndex = keys.findIndex(
|
|
4667
3989
|
(entry) => entry.baseUrl === normalized
|
|
@@ -4679,7 +4001,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4679
4001
|
store.getState().setApiKeys(next);
|
|
4680
4002
|
},
|
|
4681
4003
|
updateApiKeyBalance: (baseUrl, balance) => {
|
|
4682
|
-
const normalized =
|
|
4004
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4683
4005
|
const keys = store.getState().apiKeys;
|
|
4684
4006
|
const next = keys.map(
|
|
4685
4007
|
(entry) => entry.baseUrl === normalized ? { ...entry, balance, lastUsed: Date.now() } : entry
|
|
@@ -4687,7 +4009,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4687
4009
|
store.getState().setApiKeys(next);
|
|
4688
4010
|
},
|
|
4689
4011
|
removeApiKey: (baseUrl) => {
|
|
4690
|
-
const normalized =
|
|
4012
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4691
4013
|
const next = store.getState().apiKeys.filter((entry) => entry.baseUrl !== normalized);
|
|
4692
4014
|
store.getState().setApiKeys(next);
|
|
4693
4015
|
},
|
|
@@ -4701,7 +4023,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4701
4023
|
},
|
|
4702
4024
|
// ========== Child Keys ==========
|
|
4703
4025
|
getChildKey: (parentBaseUrl) => {
|
|
4704
|
-
const normalized =
|
|
4026
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4705
4027
|
const entry = store.getState().childKeys.find((key) => key.parentBaseUrl === normalized);
|
|
4706
4028
|
if (!entry) return null;
|
|
4707
4029
|
return {
|
|
@@ -4714,7 +4036,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4714
4036
|
};
|
|
4715
4037
|
},
|
|
4716
4038
|
setChildKey: (parentBaseUrl, childKey, balance, validityDate, balanceLimit) => {
|
|
4717
|
-
const normalized =
|
|
4039
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4718
4040
|
const keys = store.getState().childKeys;
|
|
4719
4041
|
const existingIndex = keys.findIndex(
|
|
4720
4042
|
(entry) => entry.parentBaseUrl === normalized
|
|
@@ -4745,7 +4067,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4745
4067
|
}
|
|
4746
4068
|
},
|
|
4747
4069
|
updateChildKeyBalance: (parentBaseUrl, balance) => {
|
|
4748
|
-
const normalized =
|
|
4070
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4749
4071
|
const keys = store.getState().childKeys;
|
|
4750
4072
|
const next = keys.map(
|
|
4751
4073
|
(entry) => entry.parentBaseUrl === normalized ? { ...entry, balance } : entry
|
|
@@ -4753,7 +4075,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4753
4075
|
store.getState().setChildKeys(next);
|
|
4754
4076
|
},
|
|
4755
4077
|
removeChildKey: (parentBaseUrl) => {
|
|
4756
|
-
const normalized =
|
|
4078
|
+
const normalized = normalizeBaseUrl3(parentBaseUrl);
|
|
4757
4079
|
const next = store.getState().childKeys.filter((entry) => entry.parentBaseUrl !== normalized);
|
|
4758
4080
|
store.getState().setChildKeys(next);
|
|
4759
4081
|
},
|
|
@@ -4778,11 +4100,11 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4778
4100
|
return store.getState().xcashuTokens;
|
|
4779
4101
|
},
|
|
4780
4102
|
getXcashuTokensForBaseUrl: (baseUrl) => {
|
|
4781
|
-
const normalized =
|
|
4103
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4782
4104
|
return store.getState().xcashuTokens[normalized] || [];
|
|
4783
4105
|
},
|
|
4784
4106
|
addXcashuToken: (baseUrl, token) => {
|
|
4785
|
-
const normalized =
|
|
4107
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4786
4108
|
const tokens = store.getState().xcashuTokens;
|
|
4787
4109
|
const existing = tokens[normalized] || [];
|
|
4788
4110
|
const next = { ...tokens };
|
|
@@ -4793,7 +4115,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4793
4115
|
store.getState().setXcashuTokens(next);
|
|
4794
4116
|
},
|
|
4795
4117
|
removeXcashuToken: (baseUrl, token) => {
|
|
4796
|
-
const normalized =
|
|
4118
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4797
4119
|
const tokens = store.getState().xcashuTokens;
|
|
4798
4120
|
const existing = tokens[normalized] || [];
|
|
4799
4121
|
const next = { ...tokens };
|
|
@@ -4804,7 +4126,7 @@ var createStorageAdapterFromStore = (store) => ({
|
|
|
4804
4126
|
store.getState().setXcashuTokens(next);
|
|
4805
4127
|
},
|
|
4806
4128
|
clearXcashuTokensForBaseUrl: (baseUrl) => {
|
|
4807
|
-
const normalized =
|
|
4129
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4808
4130
|
const tokens = store.getState().xcashuTokens;
|
|
4809
4131
|
const next = { ...tokens };
|
|
4810
4132
|
delete next[normalized];
|
|
@@ -4818,16 +4140,16 @@ var createProviderRegistryFromStore = (store, logger) => {
|
|
|
4818
4140
|
const log = (logger ?? consoleLogger).child("ProviderRegistry");
|
|
4819
4141
|
return {
|
|
4820
4142
|
getModelsForProvider: (baseUrl) => {
|
|
4821
|
-
const normalized =
|
|
4143
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4822
4144
|
return store.getState().modelsFromAllProviders[normalized] || [];
|
|
4823
4145
|
},
|
|
4824
4146
|
getDisabledProviders: () => store.getState().disabledProviders,
|
|
4825
4147
|
getProviderMints: (baseUrl) => {
|
|
4826
|
-
const normalized =
|
|
4148
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4827
4149
|
return store.getState().mintsFromAllProviders[normalized] || [];
|
|
4828
4150
|
},
|
|
4829
4151
|
getProviderInfo: async (baseUrl) => {
|
|
4830
|
-
const normalized =
|
|
4152
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
4831
4153
|
const cached = store.getState().infoFromAllProviders[normalized];
|
|
4832
4154
|
if (cached) return cached;
|
|
4833
4155
|
try {
|
|
@@ -4853,11 +4175,11 @@ var createProviderRegistryFromStore = (store, logger) => {
|
|
|
4853
4175
|
var MODEL_KEY_PREFIX = "models:provider:";
|
|
4854
4176
|
var MODEL_TS_KEY_PREFIX = "models:provider_timestamp:";
|
|
4855
4177
|
var PROVIDER_INDEX_KEY = "models:provider_index";
|
|
4856
|
-
var
|
|
4178
|
+
var MIGRATION_MARKER_KEY2 = "models_sharded_migration_v1";
|
|
4857
4179
|
var encodeBaseUrl = (baseUrl) => encodeURIComponent(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
4858
4180
|
var modelKey = (baseUrl) => `${MODEL_KEY_PREFIX}${encodeBaseUrl(baseUrl)}`;
|
|
4859
4181
|
var modelTsKey = (baseUrl) => `${MODEL_TS_KEY_PREFIX}${encodeBaseUrl(baseUrl)}`;
|
|
4860
|
-
var
|
|
4182
|
+
var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
4861
4183
|
var createShardedDiscoveryAdapter = async (options) => {
|
|
4862
4184
|
const { driver } = options;
|
|
4863
4185
|
const legacyModels = await driver.getItem(SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS, {});
|
|
@@ -4865,7 +4187,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4865
4187
|
if (Object.keys(legacyModels).length > 0) {
|
|
4866
4188
|
const migratedProviders = [];
|
|
4867
4189
|
for (const [baseUrl, models] of Object.entries(legacyModels)) {
|
|
4868
|
-
const normalized =
|
|
4190
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4869
4191
|
await driver.setItem(modelKey(normalized), models);
|
|
4870
4192
|
const ts = legacyTimestamps[normalized] ?? Date.now();
|
|
4871
4193
|
await driver.setItem(modelTsKey(normalized), ts);
|
|
@@ -4880,7 +4202,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4880
4202
|
await driver.removeItem(SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS);
|
|
4881
4203
|
await driver.removeItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE);
|
|
4882
4204
|
}
|
|
4883
|
-
await driver.setItem(
|
|
4205
|
+
await driver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
4884
4206
|
const [
|
|
4885
4207
|
rawMints,
|
|
4886
4208
|
rawInfo,
|
|
@@ -4914,31 +4236,31 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4914
4236
|
const providerIndex = /* @__PURE__ */ new Set();
|
|
4915
4237
|
const knownProviders = /* @__PURE__ */ new Set();
|
|
4916
4238
|
for (const baseUrl of Object.keys(rawInfo)) {
|
|
4917
|
-
knownProviders.add(
|
|
4239
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4918
4240
|
}
|
|
4919
4241
|
for (const baseUrl of Object.keys(rawMints)) {
|
|
4920
|
-
knownProviders.add(
|
|
4242
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4921
4243
|
}
|
|
4922
4244
|
for (const baseUrl of rawBaseUrls) {
|
|
4923
|
-
knownProviders.add(
|
|
4245
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4924
4246
|
}
|
|
4925
4247
|
for (const baseUrl of rawDisabled) {
|
|
4926
|
-
knownProviders.add(
|
|
4248
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4927
4249
|
}
|
|
4928
4250
|
for (const baseUrl of Object.keys(legacyModels)) {
|
|
4929
|
-
knownProviders.add(
|
|
4251
|
+
knownProviders.add(normalizeBaseUrl4(baseUrl));
|
|
4930
4252
|
}
|
|
4931
4253
|
const indexProviders = await driver.getItem(
|
|
4932
4254
|
PROVIDER_INDEX_KEY,
|
|
4933
4255
|
[]
|
|
4934
4256
|
);
|
|
4935
4257
|
for (const baseUrl of indexProviders) {
|
|
4936
|
-
const normalized =
|
|
4258
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4937
4259
|
providerIndex.add(normalized);
|
|
4938
4260
|
knownProviders.add(normalized);
|
|
4939
4261
|
}
|
|
4940
4262
|
for (const baseUrl of knownProviders) {
|
|
4941
|
-
const normalized =
|
|
4263
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4942
4264
|
const models = await driver.getItem(
|
|
4943
4265
|
modelKey(normalized),
|
|
4944
4266
|
null
|
|
@@ -4959,19 +4281,19 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4959
4281
|
}
|
|
4960
4282
|
let mints = Object.fromEntries(
|
|
4961
4283
|
Object.entries(rawMints).map(([baseUrl, mintList]) => [
|
|
4962
|
-
|
|
4284
|
+
normalizeBaseUrl4(baseUrl),
|
|
4963
4285
|
mintList.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
4964
4286
|
])
|
|
4965
4287
|
);
|
|
4966
4288
|
let info = Object.fromEntries(
|
|
4967
4289
|
Object.entries(rawInfo).map(([baseUrl, entry]) => [
|
|
4968
|
-
|
|
4290
|
+
normalizeBaseUrl4(baseUrl),
|
|
4969
4291
|
entry
|
|
4970
4292
|
])
|
|
4971
4293
|
);
|
|
4972
4294
|
let _lastUsedModel = lastUsedModel;
|
|
4973
|
-
let _disabledProviders = rawDisabled.map(
|
|
4974
|
-
let _baseUrlsList = rawBaseUrls.map(
|
|
4295
|
+
let _disabledProviders = rawDisabled.map(normalizeBaseUrl4);
|
|
4296
|
+
let _baseUrlsList = rawBaseUrls.map(normalizeBaseUrl4);
|
|
4975
4297
|
let _lastBaseUrlsUpdate = lastBaseUrlsUpdate;
|
|
4976
4298
|
let _routstr21Models = rawRoutstr21Models;
|
|
4977
4299
|
let _lastRoutstr21ModelsUpdate = lastRoutstr21ModelsUpdate;
|
|
@@ -4989,10 +4311,10 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
4989
4311
|
},
|
|
4990
4312
|
setCachedModels: (models) => {
|
|
4991
4313
|
const nextKeys = new Set(
|
|
4992
|
-
Object.keys(models).map((baseUrl) =>
|
|
4314
|
+
Object.keys(models).map((baseUrl) => normalizeBaseUrl4(baseUrl))
|
|
4993
4315
|
);
|
|
4994
4316
|
for (const baseUrl of [...modelsByBaseUrl.keys()]) {
|
|
4995
|
-
if (!nextKeys.has(
|
|
4317
|
+
if (!nextKeys.has(normalizeBaseUrl4(baseUrl))) {
|
|
4996
4318
|
providerIndex.delete(baseUrl);
|
|
4997
4319
|
modelsByBaseUrl.delete(baseUrl);
|
|
4998
4320
|
timestampsByBaseUrl.delete(baseUrl);
|
|
@@ -5001,7 +4323,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5001
4323
|
}
|
|
5002
4324
|
}
|
|
5003
4325
|
for (const [baseUrl, modelList] of Object.entries(models)) {
|
|
5004
|
-
const normalized =
|
|
4326
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5005
4327
|
providerIndex.add(normalized);
|
|
5006
4328
|
modelsByBaseUrl.set(normalized, modelList);
|
|
5007
4329
|
const ts = timestampsByBaseUrl.get(normalized) ?? Date.now();
|
|
@@ -5012,10 +4334,10 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5012
4334
|
persistProviderIndex();
|
|
5013
4335
|
},
|
|
5014
4336
|
getProviderLastUpdate: (baseUrl) => {
|
|
5015
|
-
return timestampsByBaseUrl.get(
|
|
4337
|
+
return timestampsByBaseUrl.get(normalizeBaseUrl4(baseUrl)) ?? null;
|
|
5016
4338
|
},
|
|
5017
4339
|
setProviderLastUpdate: (baseUrl, timestamp) => {
|
|
5018
|
-
const normalized =
|
|
4340
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5019
4341
|
providerIndex.add(normalized);
|
|
5020
4342
|
timestampsByBaseUrl.set(normalized, timestamp);
|
|
5021
4343
|
void driver.setItem(modelTsKey(normalized), timestamp);
|
|
@@ -5026,7 +4348,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5026
4348
|
setCachedMints: (value) => {
|
|
5027
4349
|
const normalized = {};
|
|
5028
4350
|
for (const [baseUrl, mintList] of Object.entries(value)) {
|
|
5029
|
-
normalized[
|
|
4351
|
+
normalized[normalizeBaseUrl4(baseUrl)] = mintList.map(
|
|
5030
4352
|
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
5031
4353
|
);
|
|
5032
4354
|
}
|
|
@@ -5038,7 +4360,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5038
4360
|
setCachedProviderInfo: (value) => {
|
|
5039
4361
|
const normalized = {};
|
|
5040
4362
|
for (const [baseUrl, entry] of Object.entries(value)) {
|
|
5041
|
-
normalized[
|
|
4363
|
+
normalized[normalizeBaseUrl4(baseUrl)] = entry;
|
|
5042
4364
|
}
|
|
5043
4365
|
info = normalized;
|
|
5044
4366
|
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
@@ -5052,7 +4374,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5052
4374
|
// -- Disabled providers (kv) --
|
|
5053
4375
|
getDisabledProviders: () => _disabledProviders,
|
|
5054
4376
|
setDisabledProviders: (urls) => {
|
|
5055
|
-
const normalized = urls.map(
|
|
4377
|
+
const normalized = urls.map(normalizeBaseUrl4);
|
|
5056
4378
|
_disabledProviders = normalized;
|
|
5057
4379
|
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
5058
4380
|
},
|
|
@@ -5060,7 +4382,7 @@ var createShardedDiscoveryAdapter = async (options) => {
|
|
|
5060
4382
|
getBaseUrlsList: () => _baseUrlsList,
|
|
5061
4383
|
getBaseUrlsLastUpdate: () => _lastBaseUrlsUpdate,
|
|
5062
4384
|
setBaseUrlsList: (urls) => {
|
|
5063
|
-
const normalized = urls.map(
|
|
4385
|
+
const normalized = urls.map(normalizeBaseUrl4);
|
|
5064
4386
|
_baseUrlsList = normalized;
|
|
5065
4387
|
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
5066
4388
|
},
|
|
@@ -5088,16 +4410,16 @@ var createProviderRegistryFromDiscoveryAdapter = (adapter, logger) => {
|
|
|
5088
4410
|
const log = (logger ?? consoleLogger).child("ProviderRegistry");
|
|
5089
4411
|
return {
|
|
5090
4412
|
getModelsForProvider: (baseUrl) => {
|
|
5091
|
-
const normalized =
|
|
4413
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5092
4414
|
return adapter.getCachedModels()[normalized] || [];
|
|
5093
4415
|
},
|
|
5094
4416
|
getDisabledProviders: () => adapter.getDisabledProviders(),
|
|
5095
4417
|
getProviderMints: (baseUrl) => {
|
|
5096
|
-
const normalized =
|
|
4418
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5097
4419
|
return adapter.getCachedMints()[normalized] || [];
|
|
5098
4420
|
},
|
|
5099
4421
|
getProviderInfo: async (baseUrl) => {
|
|
5100
|
-
const normalized =
|
|
4422
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
5101
4423
|
const cached = adapter.getCachedProviderInfo()[normalized];
|
|
5102
4424
|
if (cached) return cached;
|
|
5103
4425
|
try {
|
|
@@ -5128,31 +4450,13 @@ var isBrowser3 = () => {
|
|
|
5128
4450
|
return false;
|
|
5129
4451
|
}
|
|
5130
4452
|
};
|
|
5131
|
-
var isNode = () => {
|
|
5132
|
-
try {
|
|
5133
|
-
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
5134
|
-
} catch {
|
|
5135
|
-
return false;
|
|
5136
|
-
}
|
|
5137
|
-
};
|
|
5138
4453
|
var defaultDriver = null;
|
|
5139
|
-
var isBun3 = () => {
|
|
5140
|
-
return typeof process.versions.bun !== "undefined";
|
|
5141
|
-
};
|
|
5142
4454
|
var getDefaultSdkDriver = () => {
|
|
5143
4455
|
if (defaultDriver) return defaultDriver;
|
|
5144
4456
|
if (isBrowser3()) {
|
|
5145
4457
|
defaultDriver = localStorageDriver;
|
|
5146
4458
|
return defaultDriver;
|
|
5147
4459
|
}
|
|
5148
|
-
if (isBun3()) {
|
|
5149
|
-
defaultDriver = createMemoryDriver();
|
|
5150
|
-
return defaultDriver;
|
|
5151
|
-
}
|
|
5152
|
-
if (isNode()) {
|
|
5153
|
-
defaultDriver = createSqliteDriver();
|
|
5154
|
-
return defaultDriver;
|
|
5155
|
-
}
|
|
5156
4460
|
defaultDriver = createMemoryDriver();
|
|
5157
4461
|
return defaultDriver;
|
|
5158
4462
|
};
|
|
@@ -5173,16 +4477,6 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
5173
4477
|
});
|
|
5174
4478
|
return defaultUsageTrackingDriver;
|
|
5175
4479
|
}
|
|
5176
|
-
if (isBun3()) {
|
|
5177
|
-
defaultUsageTrackingDriver = createBunSqliteUsageTrackingDriver();
|
|
5178
|
-
return defaultUsageTrackingDriver;
|
|
5179
|
-
}
|
|
5180
|
-
if (isNode()) {
|
|
5181
|
-
defaultUsageTrackingDriver = createSqliteUsageTrackingDriver({
|
|
5182
|
-
legacyStorageDriver: storageDriver
|
|
5183
|
-
});
|
|
5184
|
-
return defaultUsageTrackingDriver;
|
|
5185
|
-
}
|
|
5186
4480
|
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
5187
4481
|
return defaultUsageTrackingDriver;
|
|
5188
4482
|
};
|
|
@@ -5198,36 +4492,206 @@ var getDefaultDiscoveryAdapter = async () => {
|
|
|
5198
4492
|
};
|
|
5199
4493
|
var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
|
|
5200
4494
|
var getDefaultProviderRegistry = async () => createProviderRegistryFromDiscoveryAdapter(await getDefaultDiscoveryAdapter());
|
|
5201
|
-
|
|
5202
|
-
|
|
4495
|
+
|
|
4496
|
+
// client/usage.ts
|
|
4497
|
+
var numOrUndef = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4498
|
+
function extractCostBreakdown(costObj) {
|
|
4499
|
+
if (!costObj || typeof costObj !== "object") return {};
|
|
5203
4500
|
return {
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
4501
|
+
baseMsats: numOrUndef(costObj.base_msats),
|
|
4502
|
+
inputMsats: numOrUndef(costObj.input_msats),
|
|
4503
|
+
outputMsats: numOrUndef(costObj.output_msats),
|
|
4504
|
+
totalMsats: numOrUndef(costObj.total_msats),
|
|
4505
|
+
totalUsd: numOrUndef(costObj.total_usd),
|
|
4506
|
+
cacheReadInputTokens: numOrUndef(costObj.cache_read_input_tokens),
|
|
4507
|
+
cacheCreationInputTokens: numOrUndef(costObj.cache_creation_input_tokens),
|
|
4508
|
+
cacheReadMsats: numOrUndef(costObj.cache_read_msats),
|
|
4509
|
+
cacheCreationMsats: numOrUndef(costObj.cache_creation_msats),
|
|
4510
|
+
remainingBalanceMsats: numOrUndef(costObj.remaining_balance_msats)
|
|
5209
4511
|
};
|
|
5210
4512
|
}
|
|
5211
|
-
function
|
|
5212
|
-
if (!
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
const
|
|
5217
|
-
const
|
|
5218
|
-
|
|
5219
|
-
let
|
|
5220
|
-
let
|
|
4513
|
+
function extractUsageFromResponseBody(body, fallbackSatsCost = 0) {
|
|
4514
|
+
if (!body || typeof body !== "object") return null;
|
|
4515
|
+
const usage = body.usage;
|
|
4516
|
+
if (!usage || typeof usage !== "object") return null;
|
|
4517
|
+
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
4518
|
+
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
4519
|
+
const totalTokens = Number(usage.total_tokens ?? 0);
|
|
4520
|
+
const costValue = usage.cost;
|
|
4521
|
+
let cost = 0;
|
|
4522
|
+
let satsCost = fallbackSatsCost;
|
|
4523
|
+
let breakdown = {};
|
|
4524
|
+
if (typeof costValue === "number") {
|
|
4525
|
+
cost = costValue;
|
|
4526
|
+
} else if (costValue && typeof costValue === "object") {
|
|
4527
|
+
const costObj = costValue;
|
|
4528
|
+
const totalUsd = costObj.total_usd;
|
|
4529
|
+
const totalMsats = costObj.total_msats;
|
|
4530
|
+
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
4531
|
+
if (typeof totalMsats === "number") {
|
|
4532
|
+
satsCost = totalMsats / 1e3;
|
|
4533
|
+
}
|
|
4534
|
+
breakdown = extractCostBreakdown(costObj);
|
|
4535
|
+
}
|
|
4536
|
+
const provider = typeof body.provider === "string" ? body.provider : void 0;
|
|
4537
|
+
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
4538
|
+
return null;
|
|
4539
|
+
}
|
|
4540
|
+
return {
|
|
4541
|
+
promptTokens,
|
|
4542
|
+
completionTokens,
|
|
4543
|
+
totalTokens,
|
|
4544
|
+
cost,
|
|
4545
|
+
satsCost,
|
|
4546
|
+
provider,
|
|
4547
|
+
...breakdown
|
|
4548
|
+
};
|
|
4549
|
+
}
|
|
4550
|
+
function extractResponseId(body) {
|
|
4551
|
+
if (!body || typeof body !== "object") return void 0;
|
|
4552
|
+
const id = body.id;
|
|
4553
|
+
if (typeof id !== "string") return void 0;
|
|
4554
|
+
const trimmed = id.trim();
|
|
4555
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
4556
|
+
}
|
|
4557
|
+
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
4558
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4559
|
+
return null;
|
|
4560
|
+
}
|
|
4561
|
+
const provider = typeof parsed.provider === "string" ? parsed.provider : void 0;
|
|
4562
|
+
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
4563
|
+
const costObj = parsed.cost;
|
|
4564
|
+
const msats2 = costObj.total_msats ?? 0;
|
|
4565
|
+
const cost2 = costObj.total_usd ?? 0;
|
|
4566
|
+
if (msats2 === 0 && cost2 === 0) return null;
|
|
4567
|
+
return {
|
|
4568
|
+
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
4569
|
+
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
4570
|
+
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
4571
|
+
cost: Number(cost2),
|
|
4572
|
+
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost,
|
|
4573
|
+
provider,
|
|
4574
|
+
...extractCostBreakdown(costObj)
|
|
4575
|
+
};
|
|
4576
|
+
}
|
|
4577
|
+
if (!parsed.usage) {
|
|
4578
|
+
return null;
|
|
4579
|
+
}
|
|
4580
|
+
const usage = parsed.usage;
|
|
4581
|
+
const usageCost = usage.cost;
|
|
4582
|
+
let cost = 0;
|
|
4583
|
+
let msats = 0;
|
|
4584
|
+
let breakdown = {};
|
|
4585
|
+
if (typeof usageCost === "number") {
|
|
4586
|
+
cost = usageCost;
|
|
4587
|
+
} else if (usageCost && typeof usageCost === "object") {
|
|
4588
|
+
cost = usageCost.total_usd ?? 0;
|
|
4589
|
+
msats = usageCost.total_msats ?? 0;
|
|
4590
|
+
breakdown = extractCostBreakdown(usageCost);
|
|
4591
|
+
}
|
|
4592
|
+
const routstrCost = parsed.metadata?.routstr?.cost;
|
|
4593
|
+
if (routstrCost && typeof routstrCost === "object") {
|
|
4594
|
+
breakdown = { ...extractCostBreakdown(routstrCost), ...breakdown };
|
|
4595
|
+
}
|
|
4596
|
+
if (cost === 0) {
|
|
4597
|
+
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
4598
|
+
}
|
|
4599
|
+
if (msats === 0) {
|
|
4600
|
+
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
4601
|
+
}
|
|
4602
|
+
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
4603
|
+
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
4604
|
+
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
4605
|
+
const result = {
|
|
4606
|
+
promptTokens,
|
|
4607
|
+
completionTokens,
|
|
4608
|
+
totalTokens,
|
|
4609
|
+
cost: Number(cost ?? 0),
|
|
4610
|
+
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost,
|
|
4611
|
+
provider,
|
|
4612
|
+
...breakdown
|
|
4613
|
+
};
|
|
4614
|
+
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
4615
|
+
return null;
|
|
4616
|
+
}
|
|
4617
|
+
return result;
|
|
4618
|
+
}
|
|
4619
|
+
function toUsageStats(usage) {
|
|
4620
|
+
if (!usage) return void 0;
|
|
4621
|
+
return {
|
|
4622
|
+
total_tokens: usage.totalTokens,
|
|
4623
|
+
prompt_tokens: usage.promptTokens,
|
|
4624
|
+
completion_tokens: usage.completionTokens,
|
|
4625
|
+
cost: usage.cost,
|
|
4626
|
+
sats_cost: usage.satsCost
|
|
4627
|
+
};
|
|
4628
|
+
}
|
|
4629
|
+
function mergeUsage(previous, next) {
|
|
4630
|
+
if (!previous) return next;
|
|
4631
|
+
const pickNum = (n, p) => typeof n === "number" && n > 0 ? n : p ?? n;
|
|
4632
|
+
return {
|
|
4633
|
+
promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
|
|
4634
|
+
completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
|
|
4635
|
+
totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
|
|
4636
|
+
cost: next.cost > 0 ? next.cost : previous.cost,
|
|
4637
|
+
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost,
|
|
4638
|
+
provider: next.provider ?? previous.provider,
|
|
4639
|
+
baseMsats: pickNum(next.baseMsats, previous.baseMsats),
|
|
4640
|
+
inputMsats: pickNum(next.inputMsats, previous.inputMsats),
|
|
4641
|
+
outputMsats: pickNum(next.outputMsats, previous.outputMsats),
|
|
4642
|
+
totalMsats: pickNum(next.totalMsats, previous.totalMsats),
|
|
4643
|
+
totalUsd: pickNum(next.totalUsd, previous.totalUsd),
|
|
4644
|
+
cacheReadInputTokens: pickNum(
|
|
4645
|
+
next.cacheReadInputTokens,
|
|
4646
|
+
previous.cacheReadInputTokens
|
|
4647
|
+
),
|
|
4648
|
+
cacheCreationInputTokens: pickNum(
|
|
4649
|
+
next.cacheCreationInputTokens,
|
|
4650
|
+
previous.cacheCreationInputTokens
|
|
4651
|
+
),
|
|
4652
|
+
cacheReadMsats: pickNum(next.cacheReadMsats, previous.cacheReadMsats),
|
|
4653
|
+
cacheCreationMsats: pickNum(
|
|
4654
|
+
next.cacheCreationMsats,
|
|
4655
|
+
previous.cacheCreationMsats
|
|
4656
|
+
),
|
|
4657
|
+
remainingBalanceMsats: pickNum(
|
|
4658
|
+
next.remainingBalanceMsats,
|
|
4659
|
+
previous.remainingBalanceMsats
|
|
4660
|
+
)
|
|
4661
|
+
};
|
|
4662
|
+
}
|
|
4663
|
+
function hasUsageChanged(previous, next) {
|
|
4664
|
+
if (!previous) return true;
|
|
4665
|
+
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;
|
|
4666
|
+
}
|
|
4667
|
+
function isInspectionComplete(responseIdCaptured, usage) {
|
|
4668
|
+
return responseIdCaptured && !!usage && usage.totalTokens > 0 && typeof usage.totalMsats === "number" && !!usage.provider;
|
|
4669
|
+
}
|
|
4670
|
+
async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
|
|
4671
|
+
const reader = stream.getReader();
|
|
4672
|
+
const decoder = new TextDecoder("utf-8");
|
|
4673
|
+
let buffer = "";
|
|
4674
|
+
let capturedUsage = null;
|
|
4675
|
+
let capturedResponseId;
|
|
5221
4676
|
let responseIdCaptured = false;
|
|
4677
|
+
let rawChunkSequence = 0;
|
|
5222
4678
|
const inspectDataPayload = (jsonText) => {
|
|
5223
|
-
|
|
4679
|
+
const trimmed = jsonText.trim();
|
|
4680
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
4681
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
4682
|
+
return;
|
|
4683
|
+
}
|
|
4684
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
4685
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
5224
4686
|
return;
|
|
5225
4687
|
}
|
|
5226
|
-
const trimmed = jsonText.trim();
|
|
5227
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
5228
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
5229
4688
|
try {
|
|
5230
4689
|
const data = JSON.parse(trimmed);
|
|
4690
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
4691
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
4692
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
4693
|
+
return;
|
|
4694
|
+
}
|
|
5231
4695
|
if (!responseIdCaptured) {
|
|
5232
4696
|
const responseId = data?.id;
|
|
5233
4697
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -5238,19 +4702,21 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
5238
4702
|
}
|
|
5239
4703
|
const usage = extractUsageFromSSEJson(data);
|
|
5240
4704
|
if (usage) {
|
|
4705
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
5241
4706
|
const merged = mergeUsage(capturedUsage, usage);
|
|
5242
4707
|
if (hasUsageChanged(capturedUsage, merged)) {
|
|
5243
4708
|
capturedUsage = merged;
|
|
4709
|
+
console.log("[routstr:sse] \u2192 merged (changed):", merged);
|
|
5244
4710
|
onUsage(merged);
|
|
4711
|
+
} else {
|
|
4712
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
5245
4713
|
}
|
|
5246
4714
|
}
|
|
5247
4715
|
} catch {
|
|
4716
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
5248
4717
|
}
|
|
5249
4718
|
};
|
|
5250
4719
|
const inspectEventBlock = (eventBlock) => {
|
|
5251
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
5252
|
-
return;
|
|
5253
|
-
}
|
|
5254
4720
|
const lines = eventBlock.split(/\r?\n/);
|
|
5255
4721
|
const dataParts = [];
|
|
5256
4722
|
for (const line of lines) {
|
|
@@ -5279,7 +4745,9 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
5279
4745
|
const { value, done } = await reader.read();
|
|
5280
4746
|
if (done) break;
|
|
5281
4747
|
if (value && value.byteLength > 0) {
|
|
5282
|
-
|
|
4748
|
+
const text = decoder.decode(value, { stream: true });
|
|
4749
|
+
void options?.onRawChunk?.(value, rawChunkSequence++, text);
|
|
4750
|
+
buffer += text;
|
|
5283
4751
|
drainBufferedEvents();
|
|
5284
4752
|
}
|
|
5285
4753
|
}
|
|
@@ -5308,14 +4776,22 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
5308
4776
|
let capturedUsage = null;
|
|
5309
4777
|
let responseIdCaptured = false;
|
|
5310
4778
|
const inspectDataPayload = (jsonText) => {
|
|
5311
|
-
|
|
4779
|
+
const trimmed = jsonText.trim();
|
|
4780
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
4781
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
4782
|
+
return;
|
|
4783
|
+
}
|
|
4784
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
4785
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
5312
4786
|
return;
|
|
5313
4787
|
}
|
|
5314
|
-
const trimmed = jsonText.trim();
|
|
5315
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
5316
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
5317
4788
|
try {
|
|
5318
4789
|
const data = JSON.parse(trimmed);
|
|
4790
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
4791
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
4792
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
4793
|
+
return;
|
|
4794
|
+
}
|
|
5319
4795
|
if (!responseIdCaptured) {
|
|
5320
4796
|
const responseId = data?.id;
|
|
5321
4797
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -5325,19 +4801,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
5325
4801
|
}
|
|
5326
4802
|
const usage = extractUsageFromSSEJson(data);
|
|
5327
4803
|
if (usage) {
|
|
4804
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
5328
4805
|
const mergedUsage = mergeUsage(capturedUsage, usage);
|
|
5329
4806
|
if (hasUsageChanged(capturedUsage, mergedUsage)) {
|
|
5330
4807
|
capturedUsage = mergedUsage;
|
|
4808
|
+
console.log("[routstr:sse] \u2192 merged (changed):", mergedUsage);
|
|
5331
4809
|
onUsage(mergedUsage);
|
|
4810
|
+
} else {
|
|
4811
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
5332
4812
|
}
|
|
5333
4813
|
}
|
|
5334
4814
|
} catch {
|
|
4815
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
5335
4816
|
}
|
|
5336
4817
|
};
|
|
5337
4818
|
const inspectEventBlock = (eventBlock) => {
|
|
5338
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
5339
|
-
return;
|
|
5340
|
-
}
|
|
5341
4819
|
const lines = eventBlock.split(/\r?\n/);
|
|
5342
4820
|
const dataParts = [];
|
|
5343
4821
|
for (const line of lines) {
|
|
@@ -5410,11 +4888,11 @@ var RoutstrClient = class {
|
|
|
5410
4888
|
this.balanceManager,
|
|
5411
4889
|
this.logger
|
|
5412
4890
|
);
|
|
5413
|
-
this.streamProcessor = new StreamProcessor();
|
|
5414
4891
|
this.alertLevel = alertLevel;
|
|
5415
4892
|
this.mode = mode;
|
|
5416
4893
|
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
5417
4894
|
this.sdkStore = options.sdkStore;
|
|
4895
|
+
this.requestResponseLogSink = options.requestResponseLogSink;
|
|
5418
4896
|
this.providerManager = options.providerManager ?? new ProviderManager(providerRegistry, this.sdkStore, this.logger);
|
|
5419
4897
|
}
|
|
5420
4898
|
walletAdapter;
|
|
@@ -5422,7 +4900,6 @@ var RoutstrClient = class {
|
|
|
5422
4900
|
providerRegistry;
|
|
5423
4901
|
cashuSpender;
|
|
5424
4902
|
balanceManager;
|
|
5425
|
-
streamProcessor;
|
|
5426
4903
|
providerManager;
|
|
5427
4904
|
alertLevel;
|
|
5428
4905
|
mode;
|
|
@@ -5430,6 +4907,7 @@ var RoutstrClient = class {
|
|
|
5430
4907
|
usageTrackingDriver;
|
|
5431
4908
|
sdkStore;
|
|
5432
4909
|
logger;
|
|
4910
|
+
requestResponseLogSink;
|
|
5433
4911
|
/**
|
|
5434
4912
|
* Get the current client mode
|
|
5435
4913
|
*/
|
|
@@ -5620,6 +5098,7 @@ var RoutstrClient = class {
|
|
|
5620
5098
|
let usagePromise = Promise.resolve({});
|
|
5621
5099
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
5622
5100
|
const [clientStream, inspectStream] = response.body.tee();
|
|
5101
|
+
const requestResponseLogId = response.requestResponseLogId;
|
|
5623
5102
|
processedResponse = new Response(clientStream, {
|
|
5624
5103
|
status: response.status,
|
|
5625
5104
|
statusText: response.statusText,
|
|
@@ -5627,6 +5106,7 @@ var RoutstrClient = class {
|
|
|
5627
5106
|
});
|
|
5628
5107
|
processedResponse.baseUrl = response.baseUrl;
|
|
5629
5108
|
processedResponse.token = response.token;
|
|
5109
|
+
processedResponse.requestResponseLogId = requestResponseLogId;
|
|
5630
5110
|
usagePromise = inspectSSEWebStream(
|
|
5631
5111
|
inspectStream,
|
|
5632
5112
|
(usage) => {
|
|
@@ -5636,8 +5116,23 @@ var RoutstrClient = class {
|
|
|
5636
5116
|
(responseId) => {
|
|
5637
5117
|
capturedResponseId = responseId;
|
|
5638
5118
|
processedResponse.requestId = responseId;
|
|
5119
|
+
},
|
|
5120
|
+
{
|
|
5121
|
+
onRawChunk: (_chunk, sequence, text) => {
|
|
5122
|
+
void this.requestResponseLogSink?.logResponseChunk?.(
|
|
5123
|
+
requestResponseLogId,
|
|
5124
|
+
sequence,
|
|
5125
|
+
text
|
|
5126
|
+
);
|
|
5127
|
+
}
|
|
5639
5128
|
}
|
|
5640
|
-
)
|
|
5129
|
+
).then(async (result) => {
|
|
5130
|
+
await this.requestResponseLogSink?.logResponseEnd?.(requestResponseLogId);
|
|
5131
|
+
return result;
|
|
5132
|
+
}).catch(async (error) => {
|
|
5133
|
+
await this.requestResponseLogSink?.logResponseError?.(requestResponseLogId, error);
|
|
5134
|
+
throw error;
|
|
5135
|
+
});
|
|
5641
5136
|
processedResponse.usagePromise = usagePromise;
|
|
5642
5137
|
}
|
|
5643
5138
|
return {
|
|
@@ -5664,153 +5159,6 @@ var RoutstrClient = class {
|
|
|
5664
5159
|
}
|
|
5665
5160
|
return void 0;
|
|
5666
5161
|
}
|
|
5667
|
-
/**
|
|
5668
|
-
* Fetch AI response with streaming
|
|
5669
|
-
*/
|
|
5670
|
-
async fetchAIResponse(options, callbacks) {
|
|
5671
|
-
const {
|
|
5672
|
-
messageHistory,
|
|
5673
|
-
selectedModel,
|
|
5674
|
-
baseUrl,
|
|
5675
|
-
mintUrl,
|
|
5676
|
-
balance,
|
|
5677
|
-
transactionHistory,
|
|
5678
|
-
maxTokens,
|
|
5679
|
-
headers
|
|
5680
|
-
} = options;
|
|
5681
|
-
const apiMessages = await this._convertMessages(messageHistory);
|
|
5682
|
-
const requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
5683
|
-
selectedModel,
|
|
5684
|
-
apiMessages,
|
|
5685
|
-
maxTokens
|
|
5686
|
-
);
|
|
5687
|
-
try {
|
|
5688
|
-
await this._checkBalance();
|
|
5689
|
-
callbacks.onPaymentProcessing?.(true);
|
|
5690
|
-
const spendResult = await this._spendToken({
|
|
5691
|
-
mintUrl,
|
|
5692
|
-
amount: requiredSats,
|
|
5693
|
-
baseUrl
|
|
5694
|
-
});
|
|
5695
|
-
let token = spendResult.token;
|
|
5696
|
-
let tokenBalance = spendResult.tokenBalance;
|
|
5697
|
-
let tokenBalanceUnit = spendResult.tokenBalanceUnit;
|
|
5698
|
-
let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
5699
|
-
let initialTokenBalanceUnknown = spendResult.tokenBalanceUnknown;
|
|
5700
|
-
callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
|
|
5701
|
-
const baseHeaders = this._buildBaseHeaders(headers);
|
|
5702
|
-
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
5703
|
-
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
5704
|
-
const providerVersion = providerInfo?.version ?? "";
|
|
5705
|
-
let modelIdForRequest = selectedModel.id;
|
|
5706
|
-
if (/^0\.1\./.test(providerVersion)) {
|
|
5707
|
-
const newModel = await this.providerManager.getModelForProvider(
|
|
5708
|
-
baseUrl,
|
|
5709
|
-
selectedModel.id
|
|
5710
|
-
);
|
|
5711
|
-
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
5712
|
-
}
|
|
5713
|
-
const body = {
|
|
5714
|
-
model: modelIdForRequest,
|
|
5715
|
-
messages: apiMessages,
|
|
5716
|
-
stream: true
|
|
5717
|
-
};
|
|
5718
|
-
if (maxTokens !== void 0) {
|
|
5719
|
-
body.max_tokens = maxTokens;
|
|
5720
|
-
}
|
|
5721
|
-
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
5722
|
-
body.tools = [{ type: "web_search" }];
|
|
5723
|
-
}
|
|
5724
|
-
const response = await this._makeRequest({
|
|
5725
|
-
path: "/v1/chat/completions",
|
|
5726
|
-
method: "POST",
|
|
5727
|
-
body,
|
|
5728
|
-
selectedModel,
|
|
5729
|
-
baseUrl,
|
|
5730
|
-
mintUrl,
|
|
5731
|
-
token,
|
|
5732
|
-
requiredSats,
|
|
5733
|
-
maxTokens,
|
|
5734
|
-
headers: requestHeaders,
|
|
5735
|
-
baseHeaders
|
|
5736
|
-
});
|
|
5737
|
-
if (!response.body) {
|
|
5738
|
-
throw new Error("Response body is not available");
|
|
5739
|
-
}
|
|
5740
|
-
if (response.status === 200) {
|
|
5741
|
-
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
5742
|
-
const responseToken = response.token || token;
|
|
5743
|
-
if (baseUrlUsed !== baseUrl || responseToken !== token) {
|
|
5744
|
-
token = responseToken;
|
|
5745
|
-
if (typeof response.initialTokenBalanceInSats === "number") {
|
|
5746
|
-
tokenBalanceInSats = response.initialTokenBalanceInSats;
|
|
5747
|
-
initialTokenBalanceUnknown = Boolean(
|
|
5748
|
-
response.initialTokenBalanceUnknown
|
|
5749
|
-
);
|
|
5750
|
-
} else {
|
|
5751
|
-
initialTokenBalanceUnknown = true;
|
|
5752
|
-
}
|
|
5753
|
-
}
|
|
5754
|
-
const streamingResult = await this.streamProcessor.process(
|
|
5755
|
-
response,
|
|
5756
|
-
{
|
|
5757
|
-
onContent: callbacks.onStreamingUpdate,
|
|
5758
|
-
onThinking: callbacks.onThinkingUpdate
|
|
5759
|
-
},
|
|
5760
|
-
selectedModel.id
|
|
5761
|
-
);
|
|
5762
|
-
if (streamingResult.finish_reason === "content_filter") {
|
|
5763
|
-
callbacks.onMessageAppend({
|
|
5764
|
-
role: "assistant",
|
|
5765
|
-
content: "Your request was denied due to content filtering."
|
|
5766
|
-
});
|
|
5767
|
-
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
5768
|
-
const message = await this._createAssistantMessage(streamingResult);
|
|
5769
|
-
callbacks.onMessageAppend(message);
|
|
5770
|
-
} else {
|
|
5771
|
-
callbacks.onMessageAppend({
|
|
5772
|
-
role: "system",
|
|
5773
|
-
content: "The provider did not respond to this request."
|
|
5774
|
-
});
|
|
5775
|
-
}
|
|
5776
|
-
callbacks.onStreamingUpdate("");
|
|
5777
|
-
callbacks.onThinkingUpdate("");
|
|
5778
|
-
const isApikeysEstimate = this.mode === "apikeys";
|
|
5779
|
-
let satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
5780
|
-
token,
|
|
5781
|
-
baseUrl: baseUrlUsed,
|
|
5782
|
-
mintUrl,
|
|
5783
|
-
initialTokenBalance: tokenBalanceInSats,
|
|
5784
|
-
initialTokenBalanceUnknown,
|
|
5785
|
-
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
5786
|
-
response,
|
|
5787
|
-
modelId: selectedModel.id,
|
|
5788
|
-
usage: streamingResult.usage ? {
|
|
5789
|
-
promptTokens: Number(streamingResult.usage.prompt_tokens ?? 0),
|
|
5790
|
-
completionTokens: Number(
|
|
5791
|
-
streamingResult.usage.completion_tokens ?? 0
|
|
5792
|
-
),
|
|
5793
|
-
totalTokens: Number(streamingResult.usage.total_tokens ?? 0),
|
|
5794
|
-
cost: Number(streamingResult.usage.cost ?? 0),
|
|
5795
|
-
satsCost: Number(streamingResult.usage.sats_cost ?? 0)
|
|
5796
|
-
} : void 0,
|
|
5797
|
-
requestId: streamingResult.responseId
|
|
5798
|
-
});
|
|
5799
|
-
const estimatedCosts = this._getEstimatedCosts(
|
|
5800
|
-
selectedModel,
|
|
5801
|
-
streamingResult
|
|
5802
|
-
);
|
|
5803
|
-
const onLastMessageSatsUpdate = callbacks.onLastMessageSatsUpdate;
|
|
5804
|
-
onLastMessageSatsUpdate?.(satsSpent, estimatedCosts);
|
|
5805
|
-
} else {
|
|
5806
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
5807
|
-
}
|
|
5808
|
-
} catch (error) {
|
|
5809
|
-
this._handleError(error, callbacks);
|
|
5810
|
-
} finally {
|
|
5811
|
-
callbacks.onPaymentProcessing?.(false);
|
|
5812
|
-
}
|
|
5813
|
-
}
|
|
5814
5162
|
/**
|
|
5815
5163
|
* Make the API request with failover support
|
|
5816
5164
|
*/
|
|
@@ -5818,16 +5166,30 @@ var RoutstrClient = class {
|
|
|
5818
5166
|
const { path, method, body, baseUrl, token, headers } = params;
|
|
5819
5167
|
try {
|
|
5820
5168
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
5169
|
+
const requestBodyText = body === void 0 || method === "GET" ? void 0 : JSON.stringify(body);
|
|
5170
|
+
const requestLogId = await this.requestResponseLogSink?.logRequest?.({
|
|
5171
|
+
method,
|
|
5172
|
+
url,
|
|
5173
|
+
path,
|
|
5174
|
+
baseUrl,
|
|
5175
|
+
headers,
|
|
5176
|
+
body,
|
|
5177
|
+
rawBody: requestBodyText
|
|
5178
|
+
});
|
|
5821
5179
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
5822
5180
|
const response = await fetch(url, {
|
|
5823
5181
|
method,
|
|
5824
5182
|
headers,
|
|
5825
|
-
body:
|
|
5183
|
+
body: requestBodyText
|
|
5826
5184
|
});
|
|
5827
5185
|
if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
|
|
5828
5186
|
response.baseUrl = baseUrl;
|
|
5829
5187
|
response.token = token;
|
|
5188
|
+
response.requestResponseLogId = requestLogId;
|
|
5189
|
+
await this.requestResponseLogSink?.logResponseStart?.(requestLogId, response);
|
|
5190
|
+
const contentType = response.headers.get("content-type") || "";
|
|
5830
5191
|
if (!response.ok) {
|
|
5192
|
+
void this.requestResponseLogSink?.logResponseBody?.(requestLogId, response.clone());
|
|
5831
5193
|
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
5832
5194
|
let bodyText;
|
|
5833
5195
|
try {
|
|
@@ -5845,6 +5207,9 @@ var RoutstrClient = class {
|
|
|
5845
5207
|
params.retryCount ?? 0
|
|
5846
5208
|
);
|
|
5847
5209
|
}
|
|
5210
|
+
if (!contentType.includes("text/event-stream")) {
|
|
5211
|
+
void this.requestResponseLogSink?.logResponseBody?.(requestLogId, response.clone());
|
|
5212
|
+
}
|
|
5848
5213
|
return response;
|
|
5849
5214
|
} catch (error) {
|
|
5850
5215
|
if (isNetworkErrorMessage(error?.message || "")) {
|
|
@@ -6335,263 +5700,519 @@ var RoutstrClient = class {
|
|
|
6335
5700
|
}
|
|
6336
5701
|
}
|
|
6337
5702
|
/**
|
|
6338
|
-
*
|
|
5703
|
+
* Check wallet balance and throw if insufficient
|
|
6339
5704
|
*/
|
|
6340
|
-
async
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
);
|
|
5705
|
+
async _checkBalance() {
|
|
5706
|
+
const balances = await this.walletAdapter.getBalances();
|
|
5707
|
+
const totalBalance = Object.values(balances).reduce((sum, v) => sum + v, 0);
|
|
5708
|
+
if (totalBalance <= 0) {
|
|
5709
|
+
throw new InsufficientBalanceError(1, 0);
|
|
5710
|
+
}
|
|
6347
5711
|
}
|
|
6348
5712
|
/**
|
|
6349
|
-
*
|
|
5713
|
+
* Spend a token using CashuSpender with standardized error handling
|
|
6350
5714
|
*/
|
|
6351
|
-
async
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
5715
|
+
async _spendToken(params) {
|
|
5716
|
+
const { mintUrl, amount, baseUrl } = params;
|
|
5717
|
+
this._log(
|
|
5718
|
+
"DEBUG",
|
|
5719
|
+
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
5720
|
+
);
|
|
5721
|
+
if (this.mode === "apikeys") {
|
|
5722
|
+
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
5723
|
+
if (!parentApiKey) {
|
|
5724
|
+
this._log(
|
|
5725
|
+
"DEBUG",
|
|
5726
|
+
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
5727
|
+
);
|
|
5728
|
+
const spendResult2 = await this.cashuSpender.spend({
|
|
5729
|
+
mintUrl,
|
|
5730
|
+
amount: amount * TOPUP_MARGIN,
|
|
5731
|
+
baseUrl: "",
|
|
5732
|
+
reuseToken: false
|
|
6369
5733
|
});
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
5734
|
+
if (!spendResult2.token) {
|
|
5735
|
+
this._log(
|
|
5736
|
+
"ERROR",
|
|
5737
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
5738
|
+
spendResult2.error
|
|
5739
|
+
);
|
|
5740
|
+
throw new Error(
|
|
5741
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
5742
|
+
);
|
|
5743
|
+
} else {
|
|
5744
|
+
this._log(
|
|
5745
|
+
"DEBUG",
|
|
5746
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
5747
|
+
);
|
|
5748
|
+
}
|
|
5749
|
+
this._log(
|
|
5750
|
+
"DEBUG",
|
|
5751
|
+
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
5752
|
+
);
|
|
5753
|
+
try {
|
|
5754
|
+
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
5755
|
+
} catch (error) {
|
|
5756
|
+
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
5757
|
+
const receiveResult = await this.cashuSpender.receiveToken(
|
|
5758
|
+
spendResult2.token
|
|
5759
|
+
);
|
|
5760
|
+
if (receiveResult.success) {
|
|
5761
|
+
this._log(
|
|
5762
|
+
"DEBUG",
|
|
5763
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
5764
|
+
);
|
|
5765
|
+
} else {
|
|
5766
|
+
this._log(
|
|
5767
|
+
"DEBUG",
|
|
5768
|
+
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
5769
|
+
);
|
|
5770
|
+
}
|
|
5771
|
+
this._log(
|
|
5772
|
+
"DEBUG",
|
|
5773
|
+
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
5774
|
+
);
|
|
5775
|
+
} else {
|
|
5776
|
+
throw error;
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
5780
|
+
} else {
|
|
5781
|
+
this._log(
|
|
5782
|
+
"DEBUG",
|
|
5783
|
+
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
5784
|
+
);
|
|
5785
|
+
}
|
|
5786
|
+
let tokenBalance = 0;
|
|
5787
|
+
let tokenBalanceUnit = "sat";
|
|
5788
|
+
let tokenBalanceUnknown = false;
|
|
5789
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
5790
|
+
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
5791
|
+
(d) => d.baseUrl === baseUrl
|
|
5792
|
+
);
|
|
5793
|
+
if (distributionForBaseUrl) {
|
|
5794
|
+
tokenBalance = distributionForBaseUrl.amount;
|
|
5795
|
+
}
|
|
5796
|
+
if (tokenBalance === 0 && parentApiKey) {
|
|
5797
|
+
try {
|
|
5798
|
+
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
5799
|
+
parentApiKey.key,
|
|
5800
|
+
baseUrl
|
|
5801
|
+
);
|
|
5802
|
+
tokenBalance = balanceInfo.amount;
|
|
5803
|
+
tokenBalanceUnit = balanceInfo.unit;
|
|
5804
|
+
tokenBalanceUnknown = Boolean(balanceInfo.balanceUnknown);
|
|
5805
|
+
} catch (e) {
|
|
5806
|
+
this._log("WARN", "Could not get initial API key balance:", e);
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
this._log(
|
|
5810
|
+
"DEBUG",
|
|
5811
|
+
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
5812
|
+
);
|
|
5813
|
+
return {
|
|
5814
|
+
token: parentApiKey?.key ?? "",
|
|
5815
|
+
tokenBalance,
|
|
5816
|
+
tokenBalanceUnit,
|
|
5817
|
+
tokenBalanceUnknown
|
|
5818
|
+
};
|
|
5819
|
+
}
|
|
5820
|
+
this._log(
|
|
5821
|
+
"DEBUG",
|
|
5822
|
+
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
5823
|
+
);
|
|
5824
|
+
const spendResult = await this.cashuSpender.spend({
|
|
5825
|
+
mintUrl,
|
|
5826
|
+
amount,
|
|
5827
|
+
baseUrl: "",
|
|
5828
|
+
reuseToken: false
|
|
5829
|
+
});
|
|
5830
|
+
if (!spendResult.token) {
|
|
5831
|
+
this._log(
|
|
5832
|
+
"ERROR",
|
|
5833
|
+
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
5834
|
+
spendResult.error
|
|
5835
|
+
);
|
|
5836
|
+
} else {
|
|
5837
|
+
this._log(
|
|
5838
|
+
"DEBUG",
|
|
5839
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
5840
|
+
);
|
|
5841
|
+
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
5842
|
+
}
|
|
5843
|
+
return {
|
|
5844
|
+
token: spendResult.token,
|
|
5845
|
+
tokenBalance: spendResult.balance,
|
|
5846
|
+
tokenBalanceUnit: spendResult.unit ?? "sat",
|
|
5847
|
+
tokenBalanceUnknown: false
|
|
5848
|
+
};
|
|
5849
|
+
}
|
|
5850
|
+
/**
|
|
5851
|
+
* Build request headers with common defaults and dev mock controls
|
|
5852
|
+
*/
|
|
5853
|
+
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
5854
|
+
const headers = {
|
|
5855
|
+
...additionalHeaders,
|
|
5856
|
+
"Content-Type": "application/json"
|
|
5857
|
+
};
|
|
5858
|
+
return headers;
|
|
5859
|
+
}
|
|
5860
|
+
/**
|
|
5861
|
+
* Attach auth headers using the active client mode
|
|
5862
|
+
*/
|
|
5863
|
+
_withAuthHeader(headers, token) {
|
|
5864
|
+
const nextHeaders = { ...headers };
|
|
5865
|
+
if (this.mode === "xcashu") {
|
|
5866
|
+
nextHeaders["X-Cashu"] = token;
|
|
5867
|
+
} else {
|
|
5868
|
+
nextHeaders["Authorization"] = `Bearer ${token}`;
|
|
5869
|
+
}
|
|
5870
|
+
return nextHeaders;
|
|
5871
|
+
}
|
|
5872
|
+
};
|
|
5873
|
+
|
|
5874
|
+
// client/StreamProcessor.ts
|
|
5875
|
+
var StreamProcessor = class {
|
|
5876
|
+
accumulatedContent = "";
|
|
5877
|
+
accumulatedThinking = "";
|
|
5878
|
+
accumulatedImages = [];
|
|
5879
|
+
isInThinking = false;
|
|
5880
|
+
isInContent = false;
|
|
5881
|
+
/**
|
|
5882
|
+
* Process a streaming response
|
|
5883
|
+
*/
|
|
5884
|
+
async process(response, callbacks, modelId) {
|
|
5885
|
+
if (!response.body) {
|
|
5886
|
+
throw new Error("Response body is not available");
|
|
5887
|
+
}
|
|
5888
|
+
const reader = response.body.getReader();
|
|
5889
|
+
const decoder = new TextDecoder("utf-8");
|
|
5890
|
+
let buffer = "";
|
|
5891
|
+
this.accumulatedContent = "";
|
|
5892
|
+
this.accumulatedThinking = "";
|
|
5893
|
+
this.accumulatedImages = [];
|
|
5894
|
+
this.isInThinking = false;
|
|
5895
|
+
this.isInContent = false;
|
|
5896
|
+
let usage;
|
|
5897
|
+
let model;
|
|
5898
|
+
let finish_reason;
|
|
5899
|
+
let citations;
|
|
5900
|
+
let annotations;
|
|
5901
|
+
let responseId;
|
|
5902
|
+
try {
|
|
5903
|
+
while (true) {
|
|
5904
|
+
const { done, value } = await reader.read();
|
|
5905
|
+
if (done) {
|
|
5906
|
+
break;
|
|
5907
|
+
}
|
|
5908
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
5909
|
+
buffer += chunk;
|
|
5910
|
+
const lines = buffer.split("\n");
|
|
5911
|
+
buffer = lines.pop() || "";
|
|
5912
|
+
for (const line of lines) {
|
|
5913
|
+
const parsed = this._parseLine(line);
|
|
5914
|
+
if (!parsed) continue;
|
|
5915
|
+
if (parsed.content) {
|
|
5916
|
+
this._handleContent(parsed.content, callbacks, modelId);
|
|
5917
|
+
}
|
|
5918
|
+
if (parsed.reasoning) {
|
|
5919
|
+
this._handleThinking(parsed.reasoning, callbacks);
|
|
5920
|
+
}
|
|
5921
|
+
if (parsed.usage) {
|
|
5922
|
+
usage = parsed.usage;
|
|
5923
|
+
}
|
|
5924
|
+
if (parsed.model) {
|
|
5925
|
+
model = parsed.model;
|
|
5926
|
+
}
|
|
5927
|
+
if (parsed.finish_reason) {
|
|
5928
|
+
finish_reason = parsed.finish_reason;
|
|
5929
|
+
}
|
|
5930
|
+
if (parsed.responseId) {
|
|
5931
|
+
responseId = parsed.responseId;
|
|
5932
|
+
}
|
|
5933
|
+
if (parsed.citations) {
|
|
5934
|
+
citations = parsed.citations;
|
|
5935
|
+
}
|
|
5936
|
+
if (parsed.annotations) {
|
|
5937
|
+
annotations = parsed.annotations;
|
|
5938
|
+
}
|
|
5939
|
+
if (parsed.images) {
|
|
5940
|
+
this._mergeImages(parsed.images);
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5944
|
+
} finally {
|
|
5945
|
+
reader.releaseLock();
|
|
5946
|
+
}
|
|
5947
|
+
return {
|
|
5948
|
+
content: this.accumulatedContent,
|
|
5949
|
+
thinking: this.accumulatedThinking || void 0,
|
|
5950
|
+
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
5951
|
+
usage,
|
|
5952
|
+
model,
|
|
5953
|
+
responseId,
|
|
5954
|
+
finish_reason,
|
|
5955
|
+
citations,
|
|
5956
|
+
annotations
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
/**
|
|
5960
|
+
* Parse a single SSE line
|
|
5961
|
+
*/
|
|
5962
|
+
_parseLine(line) {
|
|
5963
|
+
if (!line.trim()) return null;
|
|
5964
|
+
if (!line.startsWith("data: ")) {
|
|
5965
|
+
return null;
|
|
5966
|
+
}
|
|
5967
|
+
const jsonData = line.slice(6);
|
|
5968
|
+
if (jsonData === "[DONE]") {
|
|
5969
|
+
return null;
|
|
5970
|
+
}
|
|
5971
|
+
try {
|
|
5972
|
+
const parsed = JSON.parse(jsonData);
|
|
5973
|
+
const result = {};
|
|
5974
|
+
if (parsed.choices?.[0]?.delta?.content) {
|
|
5975
|
+
result.content = parsed.choices[0].delta.content;
|
|
5976
|
+
}
|
|
5977
|
+
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
5978
|
+
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
5979
|
+
}
|
|
5980
|
+
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
5981
|
+
if (extractedUsage) {
|
|
5982
|
+
result.usage = toUsageStats(extractedUsage);
|
|
5983
|
+
} else if (parsed.usage) {
|
|
5984
|
+
result.usage = {
|
|
5985
|
+
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
5986
|
+
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
5987
|
+
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
5988
|
+
};
|
|
5989
|
+
}
|
|
5990
|
+
if (parsed.id) {
|
|
5991
|
+
result.responseId = parsed.id;
|
|
5992
|
+
}
|
|
5993
|
+
if (parsed.model) {
|
|
5994
|
+
result.model = parsed.model;
|
|
5995
|
+
}
|
|
5996
|
+
if (parsed.citations) {
|
|
5997
|
+
result.citations = parsed.citations;
|
|
5998
|
+
}
|
|
5999
|
+
if (parsed.annotations) {
|
|
6000
|
+
result.annotations = parsed.annotations;
|
|
6001
|
+
}
|
|
6002
|
+
if (parsed.choices?.[0]?.finish_reason) {
|
|
6003
|
+
result.finish_reason = parsed.choices[0].finish_reason;
|
|
6004
|
+
}
|
|
6005
|
+
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
6006
|
+
if (images && Array.isArray(images)) {
|
|
6007
|
+
result.images = images;
|
|
6008
|
+
}
|
|
6009
|
+
return result;
|
|
6010
|
+
} catch {
|
|
6011
|
+
return null;
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
/**
|
|
6015
|
+
* Handle content delta with thinking support
|
|
6016
|
+
*/
|
|
6017
|
+
_handleContent(content, callbacks, modelId) {
|
|
6018
|
+
if (this.isInThinking && !this.isInContent) {
|
|
6019
|
+
this.accumulatedThinking += "</thinking>";
|
|
6020
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
6021
|
+
this.isInThinking = false;
|
|
6022
|
+
this.isInContent = true;
|
|
6023
|
+
}
|
|
6024
|
+
if (modelId) {
|
|
6025
|
+
this._extractThinkingFromContent(content, callbacks);
|
|
6026
|
+
} else {
|
|
6027
|
+
this.accumulatedContent += content;
|
|
6028
|
+
}
|
|
6029
|
+
callbacks.onContent(this.accumulatedContent);
|
|
6030
|
+
}
|
|
6031
|
+
/**
|
|
6032
|
+
* Handle thinking/reasoning content
|
|
6033
|
+
*/
|
|
6034
|
+
_handleThinking(reasoning, callbacks) {
|
|
6035
|
+
if (!this.isInThinking) {
|
|
6036
|
+
this.accumulatedThinking += "<thinking> ";
|
|
6037
|
+
this.isInThinking = true;
|
|
6375
6038
|
}
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
content: result.content || ""
|
|
6379
|
-
};
|
|
6039
|
+
this.accumulatedThinking += reasoning;
|
|
6040
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
6380
6041
|
}
|
|
6381
6042
|
/**
|
|
6382
|
-
*
|
|
6043
|
+
* Extract thinking blocks from content (for models with inline thinking)
|
|
6383
6044
|
*/
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6045
|
+
_extractThinkingFromContent(content, callbacks) {
|
|
6046
|
+
const parts = content.split(/(<thinking>|<\/thinking>)/);
|
|
6047
|
+
for (const part of parts) {
|
|
6048
|
+
if (part === "<thinking>") {
|
|
6049
|
+
this.isInThinking = true;
|
|
6050
|
+
if (!this.accumulatedThinking.includes("<thinking>")) {
|
|
6051
|
+
this.accumulatedThinking += "<thinking> ";
|
|
6052
|
+
}
|
|
6053
|
+
} else if (part === "</thinking>") {
|
|
6054
|
+
this.isInThinking = false;
|
|
6055
|
+
this.accumulatedThinking += "</thinking>";
|
|
6056
|
+
} else if (this.isInThinking) {
|
|
6057
|
+
this.accumulatedThinking += part;
|
|
6058
|
+
} else {
|
|
6059
|
+
this.accumulatedContent += part;
|
|
6390
6060
|
}
|
|
6391
6061
|
}
|
|
6392
|
-
return estimatedCosts;
|
|
6393
6062
|
}
|
|
6394
6063
|
/**
|
|
6395
|
-
*
|
|
6064
|
+
* Merge images into accumulated array, avoiding duplicates
|
|
6396
6065
|
*/
|
|
6397
|
-
|
|
6398
|
-
const
|
|
6399
|
-
|
|
6066
|
+
_mergeImages(newImages) {
|
|
6067
|
+
for (const img of newImages) {
|
|
6068
|
+
const newUrl = img.image_url?.url;
|
|
6069
|
+
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
6070
|
+
const existingUrl = existing.image_url?.url;
|
|
6071
|
+
if (newUrl && existingUrl) {
|
|
6072
|
+
return existingUrl === newUrl;
|
|
6073
|
+
}
|
|
6074
|
+
if (img.index !== void 0 && existing.index !== void 0) {
|
|
6075
|
+
return existing.index === img.index;
|
|
6076
|
+
}
|
|
6077
|
+
return false;
|
|
6078
|
+
});
|
|
6079
|
+
if (existingIndex === -1) {
|
|
6080
|
+
this.accumulatedImages.push(img);
|
|
6081
|
+
} else {
|
|
6082
|
+
this.accumulatedImages[existingIndex] = img;
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6400
6085
|
}
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6086
|
+
};
|
|
6087
|
+
|
|
6088
|
+
// client/fetchAIResponse.ts
|
|
6089
|
+
async function fetchAIResponse(options, callbacks, deps) {
|
|
6090
|
+
const {
|
|
6091
|
+
messageHistory,
|
|
6092
|
+
selectedModel,
|
|
6093
|
+
baseUrl,
|
|
6094
|
+
mintUrl,
|
|
6095
|
+
maxTokens,
|
|
6096
|
+
headers
|
|
6097
|
+
} = options;
|
|
6098
|
+
try {
|
|
6099
|
+
const apiMessages = await convertMessages(messageHistory);
|
|
6100
|
+
callbacks.onPaymentProcessing?.(true);
|
|
6101
|
+
callbacks.onTokenCreated?.(deps.getPendingCashuTokenAmount?.() ?? 0);
|
|
6102
|
+
const body = {
|
|
6103
|
+
model: selectedModel.id,
|
|
6104
|
+
messages: apiMessages,
|
|
6105
|
+
stream: true
|
|
6106
|
+
};
|
|
6107
|
+
if (maxTokens !== void 0) {
|
|
6108
|
+
body.max_tokens = maxTokens;
|
|
6109
|
+
}
|
|
6110
|
+
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
6111
|
+
body.tools = [{ type: "web_search" }];
|
|
6112
|
+
}
|
|
6113
|
+
const response = await deps.client.routeRequest({
|
|
6114
|
+
path: "/v1/chat/completions",
|
|
6115
|
+
method: "POST",
|
|
6116
|
+
body,
|
|
6117
|
+
headers,
|
|
6118
|
+
baseUrl,
|
|
6119
|
+
mintUrl,
|
|
6120
|
+
modelId: selectedModel.id
|
|
6121
|
+
});
|
|
6122
|
+
if (!response.body) {
|
|
6123
|
+
throw new Error("Response body is not available");
|
|
6124
|
+
}
|
|
6125
|
+
if (response.status !== 200) {
|
|
6126
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
6127
|
+
}
|
|
6128
|
+
const streamProcessor = new StreamProcessor();
|
|
6129
|
+
const streamingResult = await streamProcessor.process(
|
|
6130
|
+
response,
|
|
6131
|
+
{
|
|
6132
|
+
onContent: callbacks.onStreamingUpdate,
|
|
6133
|
+
onThinking: callbacks.onThinkingUpdate
|
|
6134
|
+
},
|
|
6135
|
+
selectedModel.id
|
|
6136
|
+
);
|
|
6137
|
+
if (streamingResult.finish_reason === "content_filter") {
|
|
6413
6138
|
callbacks.onMessageAppend({
|
|
6414
|
-
role: "
|
|
6415
|
-
content: "
|
|
6139
|
+
role: "assistant",
|
|
6140
|
+
content: "Your request was denied due to content filtering."
|
|
6416
6141
|
});
|
|
6142
|
+
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
6143
|
+
const message = await createAssistantMessage(streamingResult);
|
|
6144
|
+
callbacks.onMessageAppend(message);
|
|
6417
6145
|
} else {
|
|
6418
6146
|
callbacks.onMessageAppend({
|
|
6419
6147
|
role: "system",
|
|
6420
|
-
content: "
|
|
6148
|
+
content: "The provider did not respond to this request."
|
|
6421
6149
|
});
|
|
6422
6150
|
}
|
|
6151
|
+
callbacks.onStreamingUpdate("");
|
|
6152
|
+
callbacks.onThinkingUpdate("");
|
|
6153
|
+
} catch (error) {
|
|
6154
|
+
handleError(error, callbacks, deps.alertLevel, deps.logger);
|
|
6155
|
+
} finally {
|
|
6156
|
+
callbacks.onPaymentProcessing?.(false);
|
|
6423
6157
|
}
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6158
|
+
}
|
|
6159
|
+
async function convertMessages(messages) {
|
|
6160
|
+
return Promise.all(
|
|
6161
|
+
messages.filter((m) => m.role !== "system").map(async (m) => ({
|
|
6162
|
+
role: m.role,
|
|
6163
|
+
content: typeof m.content === "string" ? m.content : m.content
|
|
6164
|
+
}))
|
|
6165
|
+
);
|
|
6166
|
+
}
|
|
6167
|
+
async function createAssistantMessage(result) {
|
|
6168
|
+
if (result.images && result.images.length > 0) {
|
|
6169
|
+
const content = [];
|
|
6170
|
+
if (result.content) {
|
|
6171
|
+
content.push({
|
|
6172
|
+
type: "text",
|
|
6173
|
+
text: result.content,
|
|
6174
|
+
thinking: result.thinking,
|
|
6175
|
+
citations: result.citations,
|
|
6176
|
+
annotations: result.annotations
|
|
6177
|
+
});
|
|
6432
6178
|
}
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
const { mintUrl, amount, baseUrl } = params;
|
|
6439
|
-
this._log(
|
|
6440
|
-
"DEBUG",
|
|
6441
|
-
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
6442
|
-
);
|
|
6443
|
-
if (this.mode === "apikeys") {
|
|
6444
|
-
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
6445
|
-
if (!parentApiKey) {
|
|
6446
|
-
this._log(
|
|
6447
|
-
"DEBUG",
|
|
6448
|
-
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
6449
|
-
);
|
|
6450
|
-
const spendResult2 = await this.cashuSpender.spend({
|
|
6451
|
-
mintUrl,
|
|
6452
|
-
amount: amount * TOPUP_MARGIN,
|
|
6453
|
-
baseUrl: "",
|
|
6454
|
-
reuseToken: false
|
|
6455
|
-
});
|
|
6456
|
-
if (!spendResult2.token) {
|
|
6457
|
-
this._log(
|
|
6458
|
-
"ERROR",
|
|
6459
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
6460
|
-
spendResult2.error
|
|
6461
|
-
);
|
|
6462
|
-
throw new Error(
|
|
6463
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
6464
|
-
);
|
|
6465
|
-
} else {
|
|
6466
|
-
this._log(
|
|
6467
|
-
"DEBUG",
|
|
6468
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
6469
|
-
);
|
|
6470
|
-
}
|
|
6471
|
-
this._log(
|
|
6472
|
-
"DEBUG",
|
|
6473
|
-
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
6474
|
-
);
|
|
6475
|
-
try {
|
|
6476
|
-
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
6477
|
-
} catch (error) {
|
|
6478
|
-
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
6479
|
-
const receiveResult = await this.cashuSpender.receiveToken(
|
|
6480
|
-
spendResult2.token
|
|
6481
|
-
);
|
|
6482
|
-
if (receiveResult.success) {
|
|
6483
|
-
this._log(
|
|
6484
|
-
"DEBUG",
|
|
6485
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
6486
|
-
);
|
|
6487
|
-
} else {
|
|
6488
|
-
this._log(
|
|
6489
|
-
"DEBUG",
|
|
6490
|
-
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
6491
|
-
);
|
|
6492
|
-
}
|
|
6493
|
-
this._log(
|
|
6494
|
-
"DEBUG",
|
|
6495
|
-
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
6496
|
-
);
|
|
6497
|
-
} else {
|
|
6498
|
-
throw error;
|
|
6499
|
-
}
|
|
6500
|
-
}
|
|
6501
|
-
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
6502
|
-
} else {
|
|
6503
|
-
this._log(
|
|
6504
|
-
"DEBUG",
|
|
6505
|
-
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
6506
|
-
);
|
|
6507
|
-
}
|
|
6508
|
-
let tokenBalance = 0;
|
|
6509
|
-
let tokenBalanceUnit = "sat";
|
|
6510
|
-
let tokenBalanceUnknown = false;
|
|
6511
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
6512
|
-
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
6513
|
-
(d) => d.baseUrl === baseUrl
|
|
6514
|
-
);
|
|
6515
|
-
if (distributionForBaseUrl) {
|
|
6516
|
-
tokenBalance = distributionForBaseUrl.amount;
|
|
6517
|
-
}
|
|
6518
|
-
if (tokenBalance === 0 && parentApiKey) {
|
|
6519
|
-
try {
|
|
6520
|
-
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
6521
|
-
parentApiKey.key,
|
|
6522
|
-
baseUrl
|
|
6523
|
-
);
|
|
6524
|
-
tokenBalance = balanceInfo.amount;
|
|
6525
|
-
tokenBalanceUnit = balanceInfo.unit;
|
|
6526
|
-
tokenBalanceUnknown = Boolean(balanceInfo.balanceUnknown);
|
|
6527
|
-
} catch (e) {
|
|
6528
|
-
this._log("WARN", "Could not get initial API key balance:", e);
|
|
6179
|
+
for (const img of result.images) {
|
|
6180
|
+
content.push({
|
|
6181
|
+
type: "image_url",
|
|
6182
|
+
image_url: {
|
|
6183
|
+
url: img.image_url.url
|
|
6529
6184
|
}
|
|
6530
|
-
}
|
|
6531
|
-
this._log(
|
|
6532
|
-
"DEBUG",
|
|
6533
|
-
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
6534
|
-
);
|
|
6535
|
-
return {
|
|
6536
|
-
token: parentApiKey?.key ?? "",
|
|
6537
|
-
tokenBalance,
|
|
6538
|
-
tokenBalanceUnit,
|
|
6539
|
-
tokenBalanceUnknown
|
|
6540
|
-
};
|
|
6541
|
-
}
|
|
6542
|
-
this._log(
|
|
6543
|
-
"DEBUG",
|
|
6544
|
-
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
6545
|
-
);
|
|
6546
|
-
const spendResult = await this.cashuSpender.spend({
|
|
6547
|
-
mintUrl,
|
|
6548
|
-
amount,
|
|
6549
|
-
baseUrl: "",
|
|
6550
|
-
reuseToken: false
|
|
6551
|
-
});
|
|
6552
|
-
if (!spendResult.token) {
|
|
6553
|
-
this._log(
|
|
6554
|
-
"ERROR",
|
|
6555
|
-
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
6556
|
-
spendResult.error
|
|
6557
|
-
);
|
|
6558
|
-
} else {
|
|
6559
|
-
this._log(
|
|
6560
|
-
"DEBUG",
|
|
6561
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
6562
|
-
);
|
|
6563
|
-
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
6185
|
+
});
|
|
6564
6186
|
}
|
|
6565
6187
|
return {
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
tokenBalanceUnit: spendResult.unit ?? "sat",
|
|
6569
|
-
tokenBalanceUnknown: false
|
|
6570
|
-
};
|
|
6571
|
-
}
|
|
6572
|
-
/**
|
|
6573
|
-
* Build request headers with common defaults and dev mock controls
|
|
6574
|
-
*/
|
|
6575
|
-
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
6576
|
-
const headers = {
|
|
6577
|
-
...additionalHeaders,
|
|
6578
|
-
"Content-Type": "application/json"
|
|
6188
|
+
role: "assistant",
|
|
6189
|
+
content
|
|
6579
6190
|
};
|
|
6580
|
-
return headers;
|
|
6581
6191
|
}
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6192
|
+
return {
|
|
6193
|
+
role: "assistant",
|
|
6194
|
+
content: result.content || ""
|
|
6195
|
+
};
|
|
6196
|
+
}
|
|
6197
|
+
function handleError(error, callbacks, alertLevel, logger) {
|
|
6198
|
+
logger.error("[fetchAIResponse] Error occurred", error);
|
|
6199
|
+
if (error instanceof Error) {
|
|
6200
|
+
const isStreamError = error.message.includes("Error in input stream") || error.message.includes("Load failed");
|
|
6201
|
+
const modifiedErrorMsg = isStreamError ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
|
|
6202
|
+
logger.error(
|
|
6203
|
+
`[fetchAIResponse] Error type=${error.constructor.name}, message=${modifiedErrorMsg}, isStreamError=${isStreamError}`
|
|
6204
|
+
);
|
|
6205
|
+
callbacks.onMessageAppend({
|
|
6206
|
+
role: "system",
|
|
6207
|
+
content: "Uncaught Error: " + modifiedErrorMsg + (alertLevel === "max" ? " | " + error.stack : "")
|
|
6208
|
+
});
|
|
6209
|
+
} else {
|
|
6210
|
+
callbacks.onMessageAppend({
|
|
6211
|
+
role: "system",
|
|
6212
|
+
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
6213
|
+
});
|
|
6593
6214
|
}
|
|
6594
|
-
}
|
|
6215
|
+
}
|
|
6595
6216
|
|
|
6596
6217
|
// routeRequests.ts
|
|
6597
6218
|
async function resolveRouteRequestContext(options) {
|
|
@@ -6614,7 +6235,8 @@ async function resolveRouteRequestContext(options) {
|
|
|
6614
6235
|
usageTrackingDriver,
|
|
6615
6236
|
sdkStore,
|
|
6616
6237
|
providerManager: providedProviderManager,
|
|
6617
|
-
logger
|
|
6238
|
+
logger,
|
|
6239
|
+
requestResponseLogSink
|
|
6618
6240
|
} = options;
|
|
6619
6241
|
let modelManager;
|
|
6620
6242
|
let providers;
|
|
@@ -6663,15 +6285,8 @@ async function resolveRouteRequestContext(options) {
|
|
|
6663
6285
|
baseUrl = cheapest.baseUrl;
|
|
6664
6286
|
selectedModel = cheapest.model;
|
|
6665
6287
|
}
|
|
6666
|
-
const balances = await walletAdapter.getBalances();
|
|
6667
|
-
const totalBalance = Object.values(balances).reduce((sum, v) => sum + v, 0);
|
|
6668
|
-
if (totalBalance <= 0) {
|
|
6669
|
-
throw new Error(
|
|
6670
|
-
"Wallet balance is empty. Add a mint and fund it before making requests."
|
|
6671
|
-
);
|
|
6672
|
-
}
|
|
6673
6288
|
const providerMints = providerRegistry.getProviderMints(baseUrl);
|
|
6674
|
-
const mintUrl = walletAdapter.getActiveMintUrl() || providerMints[0] || Object.keys(
|
|
6289
|
+
const mintUrl = walletAdapter.getActiveMintUrl() || providerMints[0] || Object.keys(await walletAdapter.getBalances())[0];
|
|
6675
6290
|
if (!mintUrl) {
|
|
6676
6291
|
throw new Error("No mint configured in wallet");
|
|
6677
6292
|
}
|
|
@@ -6681,7 +6296,7 @@ async function resolveRouteRequestContext(options) {
|
|
|
6681
6296
|
providerRegistry,
|
|
6682
6297
|
"min",
|
|
6683
6298
|
mode,
|
|
6684
|
-
{ usageTrackingDriver, sdkStore, providerManager, logger }
|
|
6299
|
+
{ usageTrackingDriver, sdkStore, providerManager, logger, requestResponseLogSink }
|
|
6685
6300
|
);
|
|
6686
6301
|
const maxTokens = extractMaxTokens(requestBody);
|
|
6687
6302
|
const stream = extractStream(requestBody);
|
|
@@ -6762,8 +6377,6 @@ exports.StreamProcessor = StreamProcessor;
|
|
|
6762
6377
|
exports.StreamingError = StreamingError;
|
|
6763
6378
|
exports.TokenOperationError = TokenOperationError;
|
|
6764
6379
|
exports.consoleLogger = consoleLogger;
|
|
6765
|
-
exports.createBunSqliteDriver = createBunSqliteDriver;
|
|
6766
|
-
exports.createBunSqliteUsageTrackingDriver = createBunSqliteUsageTrackingDriver;
|
|
6767
6380
|
exports.createDiscoveryAdapterFromStore = createDiscoveryAdapterFromStore;
|
|
6768
6381
|
exports.createIndexedDBDriver = createIndexedDBDriver;
|
|
6769
6382
|
exports.createIndexedDBUsageTrackingDriver = createIndexedDBUsageTrackingDriver;
|
|
@@ -6774,9 +6387,11 @@ exports.createProviderRegistryFromStore = createProviderRegistryFromStore;
|
|
|
6774
6387
|
exports.createSSEParserTransform = createSSEParserTransform;
|
|
6775
6388
|
exports.createSdkStore = createSdkStore;
|
|
6776
6389
|
exports.createShardedDiscoveryAdapter = createShardedDiscoveryAdapter;
|
|
6777
|
-
exports.createSqliteDriver = createSqliteDriver;
|
|
6778
|
-
exports.createSqliteUsageTrackingDriver = createSqliteUsageTrackingDriver;
|
|
6779
6390
|
exports.createStorageAdapterFromStore = createStorageAdapterFromStore;
|
|
6391
|
+
exports.extractResponseId = extractResponseId;
|
|
6392
|
+
exports.extractUsageFromResponseBody = extractUsageFromResponseBody;
|
|
6393
|
+
exports.extractUsageFromSSEJson = extractUsageFromSSEJson;
|
|
6394
|
+
exports.fetchAIResponse = fetchAIResponse;
|
|
6780
6395
|
exports.filterBaseUrlsForTor = filterBaseUrlsForTor;
|
|
6781
6396
|
exports.getDefaultDiscoveryAdapter = getDefaultDiscoveryAdapter;
|
|
6782
6397
|
exports.getDefaultProviderRegistry = getDefaultProviderRegistry;
|
|
@@ -6793,5 +6408,6 @@ exports.noopLogger = noopLogger;
|
|
|
6793
6408
|
exports.normalizeProviderUrl = normalizeProviderUrl;
|
|
6794
6409
|
exports.routeRequests = routeRequests;
|
|
6795
6410
|
exports.setDefaultUsageTrackingDriver = setDefaultUsageTrackingDriver;
|
|
6411
|
+
exports.toUsageStats = toUsageStats;
|
|
6796
6412
|
//# sourceMappingURL=index.js.map
|
|
6797
6413
|
//# sourceMappingURL=index.js.map
|