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