@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/client/index.mjs
CHANGED
|
@@ -1059,8 +1059,8 @@ var BalanceManager = class _BalanceManager {
|
|
|
1059
1059
|
const refundableProviderBalance = Object.entries(
|
|
1060
1060
|
balanceState.providerBalances
|
|
1061
1061
|
).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
|
|
1062
|
-
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount <
|
|
1063
|
-
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount);
|
|
1062
|
+
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 3) {
|
|
1063
|
+
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount, adjustedAmount);
|
|
1064
1064
|
return this.createProviderToken({
|
|
1065
1065
|
...options,
|
|
1066
1066
|
retryCount: retryCount + 1
|
|
@@ -1199,33 +1199,47 @@ var BalanceManager = class _BalanceManager {
|
|
|
1199
1199
|
}
|
|
1200
1200
|
return candidates;
|
|
1201
1201
|
}
|
|
1202
|
-
async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
|
|
1202
|
+
async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount, requiredAmount) {
|
|
1203
1203
|
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
1204
1204
|
const forceRefund = retryCount >= 2;
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1205
|
+
const candidates = apiKeyDistribution.filter((apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0).map((apiKey) => {
|
|
1206
|
+
const full = this.storageAdapter.getApiKey(apiKey.baseUrl);
|
|
1207
|
+
return {
|
|
1208
|
+
baseUrl: apiKey.baseUrl,
|
|
1209
|
+
amount: apiKey.amount,
|
|
1210
|
+
lastUsed: full?.lastUsed ?? 0,
|
|
1211
|
+
key: full?.key
|
|
1212
|
+
};
|
|
1213
|
+
}).filter((c) => c.key != null).sort((a, b) => a.lastUsed - b.lastUsed);
|
|
1214
|
+
if (candidates.length === 0) return;
|
|
1215
|
+
if (forceRefund) {
|
|
1216
|
+
for (const candidate of candidates) {
|
|
1217
|
+
await this.refundApiKey({
|
|
1217
1218
|
mintUrl,
|
|
1218
|
-
baseUrl:
|
|
1219
|
-
apiKey:
|
|
1220
|
-
forceRefund
|
|
1219
|
+
baseUrl: candidate.baseUrl,
|
|
1220
|
+
apiKey: candidate.key,
|
|
1221
|
+
forceRefund: true
|
|
1221
1222
|
});
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1223
|
+
const newState = await this.getBalanceState();
|
|
1224
|
+
const newAvailable = (newState.mintBalances[mintUrl] || 0) + (newState.providerBalances[baseUrl] || 0);
|
|
1225
|
+
if (newAvailable >= requiredAmount) {
|
|
1226
|
+
this.logger.log(
|
|
1227
|
+
`_refundOtherProvidersForTopUp: freed enough balance (${newAvailable} >= ${requiredAmount}), stopping early`
|
|
1228
|
+
);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1228
1231
|
}
|
|
1232
|
+
} else {
|
|
1233
|
+
await Promise.allSettled(
|
|
1234
|
+
candidates.map(
|
|
1235
|
+
(candidate) => this.refundApiKey({
|
|
1236
|
+
mintUrl,
|
|
1237
|
+
baseUrl: candidate.baseUrl,
|
|
1238
|
+
apiKey: candidate.key,
|
|
1239
|
+
forceRefund: false
|
|
1240
|
+
})
|
|
1241
|
+
)
|
|
1242
|
+
);
|
|
1229
1243
|
}
|
|
1230
1244
|
}
|
|
1231
1245
|
/**
|
|
@@ -1404,690 +1418,371 @@ var BalanceManager = class _BalanceManager {
|
|
|
1404
1418
|
}
|
|
1405
1419
|
};
|
|
1406
1420
|
|
|
1407
|
-
//
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
const totalMsats = costObj.total_msats;
|
|
1424
|
-
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
1425
|
-
if (typeof totalMsats === "number") {
|
|
1426
|
-
satsCost = totalMsats / 1e3;
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
1430
|
-
return null;
|
|
1421
|
+
// utils/torUtils.ts
|
|
1422
|
+
var TOR_ONION_SUFFIX = ".onion";
|
|
1423
|
+
var isTorContext = () => {
|
|
1424
|
+
if (typeof window === "undefined") return false;
|
|
1425
|
+
const hostname = window.location.hostname.toLowerCase();
|
|
1426
|
+
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
1427
|
+
};
|
|
1428
|
+
var isOnionUrl = (url) => {
|
|
1429
|
+
if (!url) return false;
|
|
1430
|
+
const trimmed = url.trim().toLowerCase();
|
|
1431
|
+
if (!trimmed) return false;
|
|
1432
|
+
try {
|
|
1433
|
+
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
1434
|
+
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
1435
|
+
} catch {
|
|
1436
|
+
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
1431
1437
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1440
|
+
// client/ProviderManager.ts
|
|
1441
|
+
function getImageResolutionFromDataUrl(dataUrl) {
|
|
1442
|
+
try {
|
|
1443
|
+
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:"))
|
|
1444
|
+
return null;
|
|
1445
|
+
const commaIdx = dataUrl.indexOf(",");
|
|
1446
|
+
if (commaIdx === -1) return null;
|
|
1447
|
+
const meta = dataUrl.slice(5, commaIdx);
|
|
1448
|
+
const base64 = dataUrl.slice(commaIdx + 1);
|
|
1449
|
+
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
1450
|
+
const len = binary.length;
|
|
1451
|
+
const bytes = new Uint8Array(len);
|
|
1452
|
+
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
1453
|
+
const isPNG = meta.includes("image/png");
|
|
1454
|
+
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
1455
|
+
if (isPNG) {
|
|
1456
|
+
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
1457
|
+
for (let i = 0; i < sig.length; i++) {
|
|
1458
|
+
if (bytes[i] !== sig[i]) return null;
|
|
1459
|
+
}
|
|
1460
|
+
const view = new DataView(
|
|
1461
|
+
bytes.buffer,
|
|
1462
|
+
bytes.byteOffset,
|
|
1463
|
+
bytes.byteLength
|
|
1464
|
+
);
|
|
1465
|
+
const width = view.getUint32(16, false);
|
|
1466
|
+
const height = view.getUint32(20, false);
|
|
1467
|
+
if (width > 0 && height > 0) return { width, height };
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
if (isJPEG) {
|
|
1471
|
+
let offset = 0;
|
|
1472
|
+
if (bytes[offset++] !== 255 || bytes[offset++] !== 216) return null;
|
|
1473
|
+
while (offset < bytes.length) {
|
|
1474
|
+
while (offset < bytes.length && bytes[offset] !== 255) offset++;
|
|
1475
|
+
if (offset + 1 >= bytes.length) break;
|
|
1476
|
+
while (bytes[offset] === 255) offset++;
|
|
1477
|
+
const marker = bytes[offset++];
|
|
1478
|
+
if (marker === 216 || marker === 217) continue;
|
|
1479
|
+
if (offset + 1 >= bytes.length) break;
|
|
1480
|
+
const length = bytes[offset] << 8 | bytes[offset + 1];
|
|
1481
|
+
offset += 2;
|
|
1482
|
+
if (marker === 192 || marker === 194) {
|
|
1483
|
+
if (length < 7 || offset + length - 2 > bytes.length) return null;
|
|
1484
|
+
const precision = bytes[offset];
|
|
1485
|
+
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
1486
|
+
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
1487
|
+
if (precision > 0 && width > 0 && height > 0)
|
|
1488
|
+
return { width, height };
|
|
1489
|
+
return null;
|
|
1490
|
+
} else {
|
|
1491
|
+
offset += length - 2;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1449
1496
|
return null;
|
|
1450
|
-
}
|
|
1451
|
-
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
1452
|
-
const costObj = parsed.cost;
|
|
1453
|
-
const msats2 = costObj.total_msats ?? 0;
|
|
1454
|
-
const cost2 = costObj.total_usd ?? 0;
|
|
1455
|
-
if (msats2 === 0 && cost2 === 0) return null;
|
|
1456
|
-
return {
|
|
1457
|
-
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
1458
|
-
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
1459
|
-
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
1460
|
-
cost: Number(cost2),
|
|
1461
|
-
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost
|
|
1462
|
-
};
|
|
1463
|
-
}
|
|
1464
|
-
if (!parsed.usage) {
|
|
1497
|
+
} catch {
|
|
1465
1498
|
return null;
|
|
1466
1499
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
let
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1500
|
+
}
|
|
1501
|
+
function calculateImageTokens(width, height, detail = "auto") {
|
|
1502
|
+
if (detail === "low") return 85;
|
|
1503
|
+
let w = width;
|
|
1504
|
+
let h = height;
|
|
1505
|
+
if (w > 2048 || h > 2048) {
|
|
1506
|
+
const aspectRatio = w / h;
|
|
1507
|
+
if (w > h) {
|
|
1508
|
+
w = 2048;
|
|
1509
|
+
h = Math.floor(w / aspectRatio);
|
|
1510
|
+
} else {
|
|
1511
|
+
h = 2048;
|
|
1512
|
+
w = Math.floor(h * aspectRatio);
|
|
1513
|
+
}
|
|
1476
1514
|
}
|
|
1477
|
-
if (
|
|
1478
|
-
|
|
1515
|
+
if (w > 768 || h > 768) {
|
|
1516
|
+
const aspectRatio = w / h;
|
|
1517
|
+
if (w > h) {
|
|
1518
|
+
w = 768;
|
|
1519
|
+
h = Math.floor(w / aspectRatio);
|
|
1520
|
+
} else {
|
|
1521
|
+
h = 768;
|
|
1522
|
+
w = Math.floor(h * aspectRatio);
|
|
1523
|
+
}
|
|
1479
1524
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1525
|
+
const tilesWidth = Math.floor((w + 511) / 512);
|
|
1526
|
+
const tilesHeight = Math.floor((h + 511) / 512);
|
|
1527
|
+
const numTiles = tilesWidth * tilesHeight;
|
|
1528
|
+
return 85 + 170 * numTiles;
|
|
1529
|
+
}
|
|
1530
|
+
var ProviderManager = class _ProviderManager {
|
|
1531
|
+
constructor(providerRegistry, store, logger) {
|
|
1532
|
+
this.providerRegistry = providerRegistry;
|
|
1533
|
+
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1534
|
+
this.logger = (logger ?? consoleLogger).child(`ProviderManager:${this.instanceId}`);
|
|
1535
|
+
if (store) {
|
|
1536
|
+
this.store = store;
|
|
1537
|
+
this.hydrateFromStore();
|
|
1538
|
+
}
|
|
1482
1539
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1540
|
+
providerRegistry;
|
|
1541
|
+
failedProviders = /* @__PURE__ */ new Set();
|
|
1542
|
+
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
1543
|
+
lastFailed = /* @__PURE__ */ new Map();
|
|
1544
|
+
/** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
|
|
1545
|
+
providersOnCoolDown = [];
|
|
1546
|
+
/** Cooldown duration in milliseconds (42 seconds) */
|
|
1547
|
+
static COOLDOWN_DURATION_MS = 42 * 1e3;
|
|
1548
|
+
/** Optional persistent store for failure tracking */
|
|
1549
|
+
store = null;
|
|
1550
|
+
/** Instance ID for debugging */
|
|
1551
|
+
instanceId;
|
|
1552
|
+
logger;
|
|
1553
|
+
/**
|
|
1554
|
+
* Hydrate in-memory state from persistent store
|
|
1555
|
+
*/
|
|
1556
|
+
hydrateFromStore() {
|
|
1557
|
+
if (!this.store) return;
|
|
1558
|
+
const state = this.store.getState();
|
|
1559
|
+
this.failedProviders = new Set(state.failedProviders);
|
|
1560
|
+
this.lastFailed = new Map(Object.entries(state.lastFailed));
|
|
1561
|
+
const now = Date.now();
|
|
1562
|
+
this.providersOnCoolDown = state.providersOnCooldown.filter(
|
|
1563
|
+
(entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
|
|
1564
|
+
).map((entry) => [entry.baseUrl, entry.timestamp]);
|
|
1565
|
+
this.logger.log(`Hydrated from store: failedProviders=${this.failedProviders.size} lastFailed=${this.lastFailed.size} providersOnCooldown=${this.providersOnCoolDown.length}`);
|
|
1495
1566
|
}
|
|
1496
|
-
return result;
|
|
1497
|
-
}
|
|
1498
|
-
function toUsageStats(usage) {
|
|
1499
|
-
if (!usage) return void 0;
|
|
1500
|
-
return {
|
|
1501
|
-
total_tokens: usage.totalTokens,
|
|
1502
|
-
prompt_tokens: usage.promptTokens,
|
|
1503
|
-
completion_tokens: usage.completionTokens,
|
|
1504
|
-
cost: usage.cost,
|
|
1505
|
-
sats_cost: usage.satsCost
|
|
1506
|
-
};
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
// client/StreamProcessor.ts
|
|
1510
|
-
var StreamProcessor = class {
|
|
1511
|
-
accumulatedContent = "";
|
|
1512
|
-
accumulatedThinking = "";
|
|
1513
|
-
accumulatedImages = [];
|
|
1514
|
-
isInThinking = false;
|
|
1515
|
-
isInContent = false;
|
|
1516
1567
|
/**
|
|
1517
|
-
*
|
|
1568
|
+
* Get instance ID for debugging
|
|
1518
1569
|
*/
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
this.
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
while (true) {
|
|
1539
|
-
const { done, value } = await reader.read();
|
|
1540
|
-
if (done) {
|
|
1541
|
-
break;
|
|
1542
|
-
}
|
|
1543
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
1544
|
-
buffer += chunk;
|
|
1545
|
-
const lines = buffer.split("\n");
|
|
1546
|
-
buffer = lines.pop() || "";
|
|
1547
|
-
for (const line of lines) {
|
|
1548
|
-
const parsed = this._parseLine(line);
|
|
1549
|
-
if (!parsed) continue;
|
|
1550
|
-
if (parsed.content) {
|
|
1551
|
-
this._handleContent(parsed.content, callbacks, modelId);
|
|
1552
|
-
}
|
|
1553
|
-
if (parsed.reasoning) {
|
|
1554
|
-
this._handleThinking(parsed.reasoning, callbacks);
|
|
1555
|
-
}
|
|
1556
|
-
if (parsed.usage) {
|
|
1557
|
-
usage = parsed.usage;
|
|
1558
|
-
}
|
|
1559
|
-
if (parsed.model) {
|
|
1560
|
-
model = parsed.model;
|
|
1561
|
-
}
|
|
1562
|
-
if (parsed.finish_reason) {
|
|
1563
|
-
finish_reason = parsed.finish_reason;
|
|
1564
|
-
}
|
|
1565
|
-
if (parsed.responseId) {
|
|
1566
|
-
responseId = parsed.responseId;
|
|
1567
|
-
}
|
|
1568
|
-
if (parsed.citations) {
|
|
1569
|
-
citations = parsed.citations;
|
|
1570
|
-
}
|
|
1571
|
-
if (parsed.annotations) {
|
|
1572
|
-
annotations = parsed.annotations;
|
|
1573
|
-
}
|
|
1574
|
-
if (parsed.images) {
|
|
1575
|
-
this._mergeImages(parsed.images);
|
|
1570
|
+
getInstanceId() {
|
|
1571
|
+
return this.instanceId;
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Clean up expired cooldown entries
|
|
1575
|
+
* Also removes the provider from failedProviders so it can be retried
|
|
1576
|
+
*/
|
|
1577
|
+
cleanupExpiredCooldowns() {
|
|
1578
|
+
const now = Date.now();
|
|
1579
|
+
const before = this.providersOnCoolDown.length;
|
|
1580
|
+
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1581
|
+
([url, timestamp]) => {
|
|
1582
|
+
const age = now - timestamp;
|
|
1583
|
+
const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
|
|
1584
|
+
if (isExpired) {
|
|
1585
|
+
this.logger.log(`Removing expired cooldown for ${url} (age: ${age}ms)`);
|
|
1586
|
+
this.failedProviders.delete(url);
|
|
1587
|
+
if (this.store) {
|
|
1588
|
+
this.store.getState().removeFailedProvider(url);
|
|
1576
1589
|
}
|
|
1577
1590
|
}
|
|
1591
|
+
return !isExpired;
|
|
1578
1592
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1593
|
+
);
|
|
1594
|
+
const after = this.providersOnCoolDown.length;
|
|
1595
|
+
if (before !== after) {
|
|
1596
|
+
this.logger.log(`Cleaned up ${before - after} expired cooldown(s), ${after} remaining`);
|
|
1581
1597
|
}
|
|
1582
|
-
return {
|
|
1583
|
-
content: this.accumulatedContent,
|
|
1584
|
-
thinking: this.accumulatedThinking || void 0,
|
|
1585
|
-
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
1586
|
-
usage,
|
|
1587
|
-
model,
|
|
1588
|
-
responseId,
|
|
1589
|
-
finish_reason,
|
|
1590
|
-
citations,
|
|
1591
|
-
annotations
|
|
1592
|
-
};
|
|
1593
1598
|
}
|
|
1594
1599
|
/**
|
|
1595
|
-
*
|
|
1600
|
+
* Get the cooldown duration in milliseconds
|
|
1596
1601
|
*/
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
if (!line.startsWith("data: ")) {
|
|
1600
|
-
return null;
|
|
1601
|
-
}
|
|
1602
|
-
const jsonData = line.slice(6);
|
|
1603
|
-
if (jsonData === "[DONE]") {
|
|
1604
|
-
return null;
|
|
1605
|
-
}
|
|
1606
|
-
try {
|
|
1607
|
-
const parsed = JSON.parse(jsonData);
|
|
1608
|
-
const result = {};
|
|
1609
|
-
if (parsed.choices?.[0]?.delta?.content) {
|
|
1610
|
-
result.content = parsed.choices[0].delta.content;
|
|
1611
|
-
}
|
|
1612
|
-
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
1613
|
-
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
1614
|
-
}
|
|
1615
|
-
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
1616
|
-
if (extractedUsage) {
|
|
1617
|
-
result.usage = toUsageStats(extractedUsage);
|
|
1618
|
-
} else if (parsed.usage) {
|
|
1619
|
-
result.usage = {
|
|
1620
|
-
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
1621
|
-
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
1622
|
-
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
1623
|
-
};
|
|
1624
|
-
}
|
|
1625
|
-
if (parsed.id) {
|
|
1626
|
-
result.responseId = parsed.id;
|
|
1627
|
-
}
|
|
1628
|
-
if (parsed.model) {
|
|
1629
|
-
result.model = parsed.model;
|
|
1630
|
-
}
|
|
1631
|
-
if (parsed.citations) {
|
|
1632
|
-
result.citations = parsed.citations;
|
|
1633
|
-
}
|
|
1634
|
-
if (parsed.annotations) {
|
|
1635
|
-
result.annotations = parsed.annotations;
|
|
1636
|
-
}
|
|
1637
|
-
if (parsed.choices?.[0]?.finish_reason) {
|
|
1638
|
-
result.finish_reason = parsed.choices[0].finish_reason;
|
|
1639
|
-
}
|
|
1640
|
-
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
1641
|
-
if (images && Array.isArray(images)) {
|
|
1642
|
-
result.images = images;
|
|
1643
|
-
}
|
|
1644
|
-
return result;
|
|
1645
|
-
} catch {
|
|
1646
|
-
return null;
|
|
1647
|
-
}
|
|
1602
|
+
getCooldownDurationMs() {
|
|
1603
|
+
return _ProviderManager.COOLDOWN_DURATION_MS;
|
|
1648
1604
|
}
|
|
1649
1605
|
/**
|
|
1650
|
-
*
|
|
1606
|
+
* Check if a provider is currently on cooldown
|
|
1651
1607
|
*/
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
this.isInThinking = false;
|
|
1657
|
-
this.isInContent = true;
|
|
1658
|
-
}
|
|
1659
|
-
if (modelId) {
|
|
1660
|
-
this._extractThinkingFromContent(content, callbacks);
|
|
1661
|
-
} else {
|
|
1662
|
-
this.accumulatedContent += content;
|
|
1663
|
-
}
|
|
1664
|
-
callbacks.onContent(this.accumulatedContent);
|
|
1608
|
+
isOnCooldown(baseUrl) {
|
|
1609
|
+
this.cleanupExpiredCooldowns();
|
|
1610
|
+
const result = this.providersOnCoolDown.some(([url]) => url === baseUrl);
|
|
1611
|
+
return result;
|
|
1665
1612
|
}
|
|
1666
1613
|
/**
|
|
1667
|
-
*
|
|
1614
|
+
* Get all providers currently on cooldown
|
|
1668
1615
|
*/
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1616
|
+
getProvidersOnCooldown() {
|
|
1617
|
+
this.cleanupExpiredCooldowns();
|
|
1618
|
+
return [...this.providersOnCoolDown];
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Reset the failed providers list
|
|
1622
|
+
*/
|
|
1623
|
+
resetFailedProviders() {
|
|
1624
|
+
this.failedProviders.clear();
|
|
1625
|
+
if (this.store) {
|
|
1626
|
+
this.store.getState().setFailedProviders([]);
|
|
1673
1627
|
}
|
|
1674
|
-
this.accumulatedThinking += reasoning;
|
|
1675
|
-
callbacks.onThinking(this.accumulatedThinking);
|
|
1676
1628
|
}
|
|
1677
1629
|
/**
|
|
1678
|
-
*
|
|
1630
|
+
* Get the last failed timestamp for a provider
|
|
1679
1631
|
*/
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1632
|
+
getLastFailed(baseUrl) {
|
|
1633
|
+
return this.lastFailed.get(baseUrl);
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Get all providers with their last failed timestamps
|
|
1637
|
+
*/
|
|
1638
|
+
getAllLastFailed() {
|
|
1639
|
+
return new Map(this.lastFailed);
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Mark a provider as failed
|
|
1643
|
+
* If a provider fails twice within 5 minutes, it's added to cooldown
|
|
1644
|
+
*/
|
|
1645
|
+
markFailed(baseUrl) {
|
|
1646
|
+
const now = Date.now();
|
|
1647
|
+
const lastFailure = this.lastFailed.get(baseUrl);
|
|
1648
|
+
this.logger.log(`markFailed: ${baseUrl} lastFailure=${lastFailure} now=${now}`);
|
|
1649
|
+
if (lastFailure !== void 0) {
|
|
1650
|
+
const timeSinceLastFailure = now - lastFailure;
|
|
1651
|
+
this.logger.log(`markFailed: timeSinceLastFailure=${timeSinceLastFailure}ms withinCooldown=${timeSinceLastFailure < _ProviderManager.COOLDOWN_DURATION_MS}`);
|
|
1652
|
+
}
|
|
1653
|
+
this.lastFailed.set(baseUrl, now);
|
|
1654
|
+
this.failedProviders.add(baseUrl);
|
|
1655
|
+
if (this.store) {
|
|
1656
|
+
this.store.getState().setLastFailedTimestamp(baseUrl, now);
|
|
1657
|
+
this.store.getState().addFailedProvider(baseUrl);
|
|
1658
|
+
}
|
|
1659
|
+
this.logger.log(`markFailed: updated ${baseUrl} to ${now}, failedProviders=${this.failedProviders.size}`);
|
|
1660
|
+
if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
|
|
1661
|
+
this.logger.log(`markFailed: second failure within cooldown window for ${baseUrl}`);
|
|
1662
|
+
if (!this.isOnCooldown(baseUrl)) {
|
|
1663
|
+
this.providersOnCoolDown.push([baseUrl, now]);
|
|
1664
|
+
if (this.store) {
|
|
1665
|
+
this.store.getState().addProviderOnCooldown(baseUrl, now);
|
|
1687
1666
|
}
|
|
1688
|
-
|
|
1689
|
-
this.isInThinking = false;
|
|
1690
|
-
this.accumulatedThinking += "</thinking>";
|
|
1691
|
-
} else if (this.isInThinking) {
|
|
1692
|
-
this.accumulatedThinking += part;
|
|
1667
|
+
this.logger.log(`markFailed: ${baseUrl} added to cooldown`);
|
|
1693
1668
|
} else {
|
|
1694
|
-
this.
|
|
1669
|
+
this.logger.log(`markFailed: ${baseUrl} already on cooldown`);
|
|
1670
|
+
}
|
|
1671
|
+
} else {
|
|
1672
|
+
if (lastFailure === void 0) {
|
|
1673
|
+
this.logger.log(`markFailed: first failure for ${baseUrl}`);
|
|
1674
|
+
} else {
|
|
1675
|
+
this.logger.log(`markFailed: failure outside cooldown window for ${baseUrl} (${now - lastFailure}ms ago)`);
|
|
1695
1676
|
}
|
|
1696
1677
|
}
|
|
1697
1678
|
}
|
|
1698
1679
|
/**
|
|
1699
|
-
*
|
|
1680
|
+
* Remove a provider from cooldown (e.g., after successful request)
|
|
1700
1681
|
*/
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
return existingUrl === newUrl;
|
|
1708
|
-
}
|
|
1709
|
-
if (img.index !== void 0 && existing.index !== void 0) {
|
|
1710
|
-
return existing.index === img.index;
|
|
1711
|
-
}
|
|
1712
|
-
return false;
|
|
1713
|
-
});
|
|
1714
|
-
if (existingIndex === -1) {
|
|
1715
|
-
this.accumulatedImages.push(img);
|
|
1716
|
-
} else {
|
|
1717
|
-
this.accumulatedImages[existingIndex] = img;
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
};
|
|
1722
|
-
|
|
1723
|
-
// utils/torUtils.ts
|
|
1724
|
-
var TOR_ONION_SUFFIX = ".onion";
|
|
1725
|
-
var isTorContext = () => {
|
|
1726
|
-
if (typeof window === "undefined") return false;
|
|
1727
|
-
const hostname = window.location.hostname.toLowerCase();
|
|
1728
|
-
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
1729
|
-
};
|
|
1730
|
-
var isOnionUrl = (url) => {
|
|
1731
|
-
if (!url) return false;
|
|
1732
|
-
const trimmed = url.trim().toLowerCase();
|
|
1733
|
-
if (!trimmed) return false;
|
|
1734
|
-
try {
|
|
1735
|
-
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
1736
|
-
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
1737
|
-
} catch {
|
|
1738
|
-
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
1739
|
-
}
|
|
1740
|
-
};
|
|
1741
|
-
|
|
1742
|
-
// client/ProviderManager.ts
|
|
1743
|
-
function getImageResolutionFromDataUrl(dataUrl) {
|
|
1744
|
-
try {
|
|
1745
|
-
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:"))
|
|
1746
|
-
return null;
|
|
1747
|
-
const commaIdx = dataUrl.indexOf(",");
|
|
1748
|
-
if (commaIdx === -1) return null;
|
|
1749
|
-
const meta = dataUrl.slice(5, commaIdx);
|
|
1750
|
-
const base64 = dataUrl.slice(commaIdx + 1);
|
|
1751
|
-
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
1752
|
-
const len = binary.length;
|
|
1753
|
-
const bytes = new Uint8Array(len);
|
|
1754
|
-
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
1755
|
-
const isPNG = meta.includes("image/png");
|
|
1756
|
-
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
1757
|
-
if (isPNG) {
|
|
1758
|
-
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
1759
|
-
for (let i = 0; i < sig.length; i++) {
|
|
1760
|
-
if (bytes[i] !== sig[i]) return null;
|
|
1761
|
-
}
|
|
1762
|
-
const view = new DataView(
|
|
1763
|
-
bytes.buffer,
|
|
1764
|
-
bytes.byteOffset,
|
|
1765
|
-
bytes.byteLength
|
|
1766
|
-
);
|
|
1767
|
-
const width = view.getUint32(16, false);
|
|
1768
|
-
const height = view.getUint32(20, false);
|
|
1769
|
-
if (width > 0 && height > 0) return { width, height };
|
|
1770
|
-
return null;
|
|
1771
|
-
}
|
|
1772
|
-
if (isJPEG) {
|
|
1773
|
-
let offset = 0;
|
|
1774
|
-
if (bytes[offset++] !== 255 || bytes[offset++] !== 216) return null;
|
|
1775
|
-
while (offset < bytes.length) {
|
|
1776
|
-
while (offset < bytes.length && bytes[offset] !== 255) offset++;
|
|
1777
|
-
if (offset + 1 >= bytes.length) break;
|
|
1778
|
-
while (bytes[offset] === 255) offset++;
|
|
1779
|
-
const marker = bytes[offset++];
|
|
1780
|
-
if (marker === 216 || marker === 217) continue;
|
|
1781
|
-
if (offset + 1 >= bytes.length) break;
|
|
1782
|
-
const length = bytes[offset] << 8 | bytes[offset + 1];
|
|
1783
|
-
offset += 2;
|
|
1784
|
-
if (marker === 192 || marker === 194) {
|
|
1785
|
-
if (length < 7 || offset + length - 2 > bytes.length) return null;
|
|
1786
|
-
const precision = bytes[offset];
|
|
1787
|
-
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
1788
|
-
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
1789
|
-
if (precision > 0 && width > 0 && height > 0)
|
|
1790
|
-
return { width, height };
|
|
1791
|
-
return null;
|
|
1792
|
-
} else {
|
|
1793
|
-
offset += length - 2;
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
return null;
|
|
1797
|
-
}
|
|
1798
|
-
return null;
|
|
1799
|
-
} catch {
|
|
1800
|
-
return null;
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
function calculateImageTokens(width, height, detail = "auto") {
|
|
1804
|
-
if (detail === "low") return 85;
|
|
1805
|
-
let w = width;
|
|
1806
|
-
let h = height;
|
|
1807
|
-
if (w > 2048 || h > 2048) {
|
|
1808
|
-
const aspectRatio = w / h;
|
|
1809
|
-
if (w > h) {
|
|
1810
|
-
w = 2048;
|
|
1811
|
-
h = Math.floor(w / aspectRatio);
|
|
1812
|
-
} else {
|
|
1813
|
-
h = 2048;
|
|
1814
|
-
w = Math.floor(h * aspectRatio);
|
|
1682
|
+
removeFromCooldown(baseUrl) {
|
|
1683
|
+
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1684
|
+
([url]) => url !== baseUrl
|
|
1685
|
+
);
|
|
1686
|
+
if (this.store) {
|
|
1687
|
+
this.store.getState().removeProviderFromCooldown(baseUrl);
|
|
1815
1688
|
}
|
|
1816
1689
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
w = Math.floor(h * aspectRatio);
|
|
1690
|
+
/**
|
|
1691
|
+
* Clear all cooldown tracking
|
|
1692
|
+
*/
|
|
1693
|
+
clearCooldowns() {
|
|
1694
|
+
this.providersOnCoolDown = [];
|
|
1695
|
+
if (this.store) {
|
|
1696
|
+
this.store.getState().clearProvidersOnCooldown();
|
|
1825
1697
|
}
|
|
1826
1698
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
}
|
|
1835
|
-
var ProviderManager = class _ProviderManager {
|
|
1836
|
-
constructor(providerRegistry, store, logger) {
|
|
1837
|
-
this.providerRegistry = providerRegistry;
|
|
1838
|
-
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1839
|
-
this.logger = (logger ?? consoleLogger).child(`ProviderManager:${this.instanceId}`);
|
|
1840
|
-
if (store) {
|
|
1841
|
-
this.store = store;
|
|
1842
|
-
this.hydrateFromStore();
|
|
1699
|
+
/**
|
|
1700
|
+
* Clear all failure tracking (lastFailed timestamps)
|
|
1701
|
+
*/
|
|
1702
|
+
clearFailureHistory() {
|
|
1703
|
+
this.lastFailed.clear();
|
|
1704
|
+
if (this.store) {
|
|
1705
|
+
this.store.getState().setLastFailed({});
|
|
1843
1706
|
}
|
|
1844
1707
|
}
|
|
1845
|
-
providerRegistry;
|
|
1846
|
-
failedProviders = /* @__PURE__ */ new Set();
|
|
1847
|
-
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
1848
|
-
lastFailed = /* @__PURE__ */ new Map();
|
|
1849
|
-
/** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
|
|
1850
|
-
providersOnCoolDown = [];
|
|
1851
|
-
/** Cooldown duration in milliseconds (42 seconds) */
|
|
1852
|
-
static COOLDOWN_DURATION_MS = 42 * 1e3;
|
|
1853
|
-
/** Optional persistent store for failure tracking */
|
|
1854
|
-
store = null;
|
|
1855
|
-
/** Instance ID for debugging */
|
|
1856
|
-
instanceId;
|
|
1857
|
-
logger;
|
|
1858
1708
|
/**
|
|
1859
|
-
*
|
|
1709
|
+
* Check if a provider has failed
|
|
1860
1710
|
*/
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
const state = this.store.getState();
|
|
1864
|
-
this.failedProviders = new Set(state.failedProviders);
|
|
1865
|
-
this.lastFailed = new Map(Object.entries(state.lastFailed));
|
|
1866
|
-
const now = Date.now();
|
|
1867
|
-
this.providersOnCoolDown = state.providersOnCooldown.filter(
|
|
1868
|
-
(entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
|
|
1869
|
-
).map((entry) => [entry.baseUrl, entry.timestamp]);
|
|
1870
|
-
this.logger.log(`Hydrated from store: failedProviders=${this.failedProviders.size} lastFailed=${this.lastFailed.size} providersOnCooldown=${this.providersOnCoolDown.length}`);
|
|
1711
|
+
hasFailed(baseUrl) {
|
|
1712
|
+
return this.failedProviders.has(baseUrl);
|
|
1871
1713
|
}
|
|
1872
1714
|
/**
|
|
1873
|
-
* Get
|
|
1715
|
+
* Get a copy of the failed providers set
|
|
1874
1716
|
*/
|
|
1875
|
-
|
|
1876
|
-
return this.
|
|
1717
|
+
getFailedProviders() {
|
|
1718
|
+
return new Set(this.failedProviders);
|
|
1877
1719
|
}
|
|
1878
1720
|
/**
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
1721
|
+
* Find the next best provider for a model
|
|
1722
|
+
* @param modelId The model ID to find a provider for
|
|
1723
|
+
* @param currentBaseUrl The current provider to exclude
|
|
1724
|
+
* @returns The best provider URL or null if none available
|
|
1881
1725
|
*/
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1726
|
+
findNextBestProvider(modelId, currentBaseUrl) {
|
|
1727
|
+
try {
|
|
1728
|
+
const torMode = isTorContext();
|
|
1729
|
+
const disabledProviders = new Set(
|
|
1730
|
+
this.providerRegistry.getDisabledProviders()
|
|
1731
|
+
);
|
|
1732
|
+
this.logger.log(`findNextBestProvider: model=${modelId} disabled=${[...disabledProviders].length} onCooldown=${this.providersOnCoolDown.length}`);
|
|
1733
|
+
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
1734
|
+
this.logger.log(`findNextBestProvider: total providers=${Object.keys(allProviders).length}`);
|
|
1735
|
+
const candidates = [];
|
|
1736
|
+
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
1737
|
+
if (baseUrl === currentBaseUrl) {
|
|
1738
|
+
continue;
|
|
1895
1739
|
}
|
|
1896
|
-
|
|
1740
|
+
if (disabledProviders.has(baseUrl)) {
|
|
1741
|
+
continue;
|
|
1742
|
+
}
|
|
1743
|
+
if (this.isOnCooldown(baseUrl)) {
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
if (!torMode && isOnionUrl(baseUrl)) {
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1749
|
+
const model = models.find((m) => m.id === modelId);
|
|
1750
|
+
if (!model) {
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
const cost = model.sats_pricing?.completion ?? 0;
|
|
1754
|
+
candidates.push({ baseUrl, model, cost });
|
|
1897
1755
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1756
|
+
candidates.sort((a, b) => a.cost - b.cost);
|
|
1757
|
+
if (candidates.length > 0) {
|
|
1758
|
+
return candidates[0].baseUrl;
|
|
1759
|
+
} else {
|
|
1760
|
+
return null;
|
|
1761
|
+
}
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
this.logger.error("findNextBestProvider error:", error);
|
|
1764
|
+
return null;
|
|
1902
1765
|
}
|
|
1903
1766
|
}
|
|
1904
1767
|
/**
|
|
1905
|
-
*
|
|
1768
|
+
* Find the best model for a provider
|
|
1769
|
+
* Useful when switching providers and need to find equivalent model
|
|
1906
1770
|
*/
|
|
1907
|
-
|
|
1908
|
-
|
|
1771
|
+
async getModelForProvider(baseUrl, modelId) {
|
|
1772
|
+
const models = this.providerRegistry.getModelsForProvider(baseUrl);
|
|
1773
|
+
const exactMatch = models.find((m) => m.id === modelId);
|
|
1774
|
+
if (exactMatch) return exactMatch;
|
|
1775
|
+
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
1776
|
+
if (providerInfo?.version && /^0\.1\./.test(providerInfo.version)) {
|
|
1777
|
+
const suffix = modelId.split("/").pop();
|
|
1778
|
+
const suffixMatch = models.find((m) => m.id === suffix);
|
|
1779
|
+
if (suffixMatch) return suffixMatch;
|
|
1780
|
+
}
|
|
1781
|
+
return null;
|
|
1909
1782
|
}
|
|
1910
1783
|
/**
|
|
1911
|
-
*
|
|
1912
|
-
|
|
1913
|
-
isOnCooldown(baseUrl) {
|
|
1914
|
-
this.cleanupExpiredCooldowns();
|
|
1915
|
-
const result = this.providersOnCoolDown.some(([url]) => url === baseUrl);
|
|
1916
|
-
return result;
|
|
1917
|
-
}
|
|
1918
|
-
/**
|
|
1919
|
-
* Get all providers currently on cooldown
|
|
1920
|
-
*/
|
|
1921
|
-
getProvidersOnCooldown() {
|
|
1922
|
-
this.cleanupExpiredCooldowns();
|
|
1923
|
-
return [...this.providersOnCoolDown];
|
|
1924
|
-
}
|
|
1925
|
-
/**
|
|
1926
|
-
* Reset the failed providers list
|
|
1927
|
-
*/
|
|
1928
|
-
resetFailedProviders() {
|
|
1929
|
-
this.failedProviders.clear();
|
|
1930
|
-
if (this.store) {
|
|
1931
|
-
this.store.getState().setFailedProviders([]);
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
/**
|
|
1935
|
-
* Get the last failed timestamp for a provider
|
|
1936
|
-
*/
|
|
1937
|
-
getLastFailed(baseUrl) {
|
|
1938
|
-
return this.lastFailed.get(baseUrl);
|
|
1939
|
-
}
|
|
1940
|
-
/**
|
|
1941
|
-
* Get all providers with their last failed timestamps
|
|
1942
|
-
*/
|
|
1943
|
-
getAllLastFailed() {
|
|
1944
|
-
return new Map(this.lastFailed);
|
|
1945
|
-
}
|
|
1946
|
-
/**
|
|
1947
|
-
* Mark a provider as failed
|
|
1948
|
-
* If a provider fails twice within 5 minutes, it's added to cooldown
|
|
1949
|
-
*/
|
|
1950
|
-
markFailed(baseUrl) {
|
|
1951
|
-
const now = Date.now();
|
|
1952
|
-
const lastFailure = this.lastFailed.get(baseUrl);
|
|
1953
|
-
this.logger.log(`markFailed: ${baseUrl} lastFailure=${lastFailure} now=${now}`);
|
|
1954
|
-
if (lastFailure !== void 0) {
|
|
1955
|
-
const timeSinceLastFailure = now - lastFailure;
|
|
1956
|
-
this.logger.log(`markFailed: timeSinceLastFailure=${timeSinceLastFailure}ms withinCooldown=${timeSinceLastFailure < _ProviderManager.COOLDOWN_DURATION_MS}`);
|
|
1957
|
-
}
|
|
1958
|
-
this.lastFailed.set(baseUrl, now);
|
|
1959
|
-
this.failedProviders.add(baseUrl);
|
|
1960
|
-
if (this.store) {
|
|
1961
|
-
this.store.getState().setLastFailedTimestamp(baseUrl, now);
|
|
1962
|
-
this.store.getState().addFailedProvider(baseUrl);
|
|
1963
|
-
}
|
|
1964
|
-
this.logger.log(`markFailed: updated ${baseUrl} to ${now}, failedProviders=${this.failedProviders.size}`);
|
|
1965
|
-
if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
|
|
1966
|
-
this.logger.log(`markFailed: second failure within cooldown window for ${baseUrl}`);
|
|
1967
|
-
if (!this.isOnCooldown(baseUrl)) {
|
|
1968
|
-
this.providersOnCoolDown.push([baseUrl, now]);
|
|
1969
|
-
if (this.store) {
|
|
1970
|
-
this.store.getState().addProviderOnCooldown(baseUrl, now);
|
|
1971
|
-
}
|
|
1972
|
-
this.logger.log(`markFailed: ${baseUrl} added to cooldown`);
|
|
1973
|
-
} else {
|
|
1974
|
-
this.logger.log(`markFailed: ${baseUrl} already on cooldown`);
|
|
1975
|
-
}
|
|
1976
|
-
} else {
|
|
1977
|
-
if (lastFailure === void 0) {
|
|
1978
|
-
this.logger.log(`markFailed: first failure for ${baseUrl}`);
|
|
1979
|
-
} else {
|
|
1980
|
-
this.logger.log(`markFailed: failure outside cooldown window for ${baseUrl} (${now - lastFailure}ms ago)`);
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
/**
|
|
1985
|
-
* Remove a provider from cooldown (e.g., after successful request)
|
|
1986
|
-
*/
|
|
1987
|
-
removeFromCooldown(baseUrl) {
|
|
1988
|
-
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1989
|
-
([url]) => url !== baseUrl
|
|
1990
|
-
);
|
|
1991
|
-
if (this.store) {
|
|
1992
|
-
this.store.getState().removeProviderFromCooldown(baseUrl);
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
/**
|
|
1996
|
-
* Clear all cooldown tracking
|
|
1997
|
-
*/
|
|
1998
|
-
clearCooldowns() {
|
|
1999
|
-
this.providersOnCoolDown = [];
|
|
2000
|
-
if (this.store) {
|
|
2001
|
-
this.store.getState().clearProvidersOnCooldown();
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
/**
|
|
2005
|
-
* Clear all failure tracking (lastFailed timestamps)
|
|
2006
|
-
*/
|
|
2007
|
-
clearFailureHistory() {
|
|
2008
|
-
this.lastFailed.clear();
|
|
2009
|
-
if (this.store) {
|
|
2010
|
-
this.store.getState().setLastFailed({});
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
/**
|
|
2014
|
-
* Check if a provider has failed
|
|
2015
|
-
*/
|
|
2016
|
-
hasFailed(baseUrl) {
|
|
2017
|
-
return this.failedProviders.has(baseUrl);
|
|
2018
|
-
}
|
|
2019
|
-
/**
|
|
2020
|
-
* Get a copy of the failed providers set
|
|
2021
|
-
*/
|
|
2022
|
-
getFailedProviders() {
|
|
2023
|
-
return new Set(this.failedProviders);
|
|
2024
|
-
}
|
|
2025
|
-
/**
|
|
2026
|
-
* Find the next best provider for a model
|
|
2027
|
-
* @param modelId The model ID to find a provider for
|
|
2028
|
-
* @param currentBaseUrl The current provider to exclude
|
|
2029
|
-
* @returns The best provider URL or null if none available
|
|
2030
|
-
*/
|
|
2031
|
-
findNextBestProvider(modelId, currentBaseUrl) {
|
|
2032
|
-
try {
|
|
2033
|
-
const torMode = isTorContext();
|
|
2034
|
-
const disabledProviders = new Set(
|
|
2035
|
-
this.providerRegistry.getDisabledProviders()
|
|
2036
|
-
);
|
|
2037
|
-
this.logger.log(`findNextBestProvider: model=${modelId} disabled=${[...disabledProviders].length} onCooldown=${this.providersOnCoolDown.length}`);
|
|
2038
|
-
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
2039
|
-
this.logger.log(`findNextBestProvider: total providers=${Object.keys(allProviders).length}`);
|
|
2040
|
-
const candidates = [];
|
|
2041
|
-
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
2042
|
-
if (baseUrl === currentBaseUrl) {
|
|
2043
|
-
continue;
|
|
2044
|
-
}
|
|
2045
|
-
if (disabledProviders.has(baseUrl)) {
|
|
2046
|
-
continue;
|
|
2047
|
-
}
|
|
2048
|
-
if (this.isOnCooldown(baseUrl)) {
|
|
2049
|
-
continue;
|
|
2050
|
-
}
|
|
2051
|
-
if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
|
|
2052
|
-
continue;
|
|
2053
|
-
}
|
|
2054
|
-
const model = models.find((m) => m.id === modelId);
|
|
2055
|
-
if (!model) {
|
|
2056
|
-
continue;
|
|
2057
|
-
}
|
|
2058
|
-
const cost = model.sats_pricing?.completion ?? 0;
|
|
2059
|
-
candidates.push({ baseUrl, model, cost });
|
|
2060
|
-
}
|
|
2061
|
-
candidates.sort((a, b) => a.cost - b.cost);
|
|
2062
|
-
if (candidates.length > 0) {
|
|
2063
|
-
return candidates[0].baseUrl;
|
|
2064
|
-
} else {
|
|
2065
|
-
return null;
|
|
2066
|
-
}
|
|
2067
|
-
} catch (error) {
|
|
2068
|
-
this.logger.error("findNextBestProvider error:", error);
|
|
2069
|
-
return null;
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
2072
|
-
/**
|
|
2073
|
-
* Find the best model for a provider
|
|
2074
|
-
* Useful when switching providers and need to find equivalent model
|
|
2075
|
-
*/
|
|
2076
|
-
async getModelForProvider(baseUrl, modelId) {
|
|
2077
|
-
const models = this.providerRegistry.getModelsForProvider(baseUrl);
|
|
2078
|
-
const exactMatch = models.find((m) => m.id === modelId);
|
|
2079
|
-
if (exactMatch) return exactMatch;
|
|
2080
|
-
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
2081
|
-
if (providerInfo?.version && /^0\.1\./.test(providerInfo.version)) {
|
|
2082
|
-
const suffix = modelId.split("/").pop();
|
|
2083
|
-
const suffixMatch = models.find((m) => m.id === suffix);
|
|
2084
|
-
if (suffixMatch) return suffixMatch;
|
|
2085
|
-
}
|
|
2086
|
-
return null;
|
|
2087
|
-
}
|
|
2088
|
-
/**
|
|
2089
|
-
* Get all available providers for a model
|
|
2090
|
-
* Returns sorted list by price
|
|
1784
|
+
* Get all available providers for a model
|
|
1785
|
+
* Returns sorted list by price
|
|
2091
1786
|
*/
|
|
2092
1787
|
getAllProvidersForModel(modelId) {
|
|
2093
1788
|
const candidates = [];
|
|
@@ -2099,7 +1794,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
2099
1794
|
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
2100
1795
|
if (disabledProviders.has(baseUrl)) continue;
|
|
2101
1796
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
2102
|
-
if (!torMode &&
|
|
1797
|
+
if (!torMode && isOnionUrl(baseUrl))
|
|
2103
1798
|
continue;
|
|
2104
1799
|
const model = models.find((m) => m.id === modelId);
|
|
2105
1800
|
if (!model) continue;
|
|
@@ -2114,16 +1809,18 @@ var ProviderManager = class _ProviderManager {
|
|
|
2114
1809
|
getProviderPriceRankingForModel(modelId, options = {}) {
|
|
2115
1810
|
const includeDisabled = options.includeDisabled ?? false;
|
|
2116
1811
|
const torMode = options.torMode ?? false;
|
|
2117
|
-
const
|
|
2118
|
-
|
|
2119
|
-
)
|
|
1812
|
+
const disabledProviderList = this.providerRegistry.getDisabledProviders();
|
|
1813
|
+
const disabledProviders = new Set(disabledProviderList);
|
|
1814
|
+
if (disabledProviderList.length > 0) {
|
|
1815
|
+
this.logger.log(`getProviderPriceRankingForModel: disabled providers (${disabledProviderList.length}): ${disabledProviderList.join(", ")}`);
|
|
1816
|
+
}
|
|
2120
1817
|
const allModels = this.providerRegistry.getAllProvidersModels();
|
|
2121
1818
|
const results = [];
|
|
2122
1819
|
for (const [baseUrl, models] of Object.entries(allModels)) {
|
|
2123
1820
|
if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
|
|
2124
1821
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
2125
1822
|
if (torMode && !baseUrl.includes(".onion")) continue;
|
|
2126
|
-
if (!torMode &&
|
|
1823
|
+
if (!torMode && baseUrl.includes(".onion"))
|
|
2127
1824
|
continue;
|
|
2128
1825
|
const match = models.find((model) => model.id === modelId);
|
|
2129
1826
|
if (!match?.sats_pricing) continue;
|
|
@@ -2143,12 +1840,20 @@ var ProviderManager = class _ProviderManager {
|
|
|
2143
1840
|
totalPerMillion
|
|
2144
1841
|
});
|
|
2145
1842
|
}
|
|
2146
|
-
|
|
1843
|
+
results.sort((a, b) => {
|
|
2147
1844
|
if (a.totalPerMillion !== b.totalPerMillion) {
|
|
2148
1845
|
return a.totalPerMillion - b.totalPerMillion;
|
|
2149
1846
|
}
|
|
2150
1847
|
return a.baseUrl.localeCompare(b.baseUrl);
|
|
2151
1848
|
});
|
|
1849
|
+
if (results.length > 0) {
|
|
1850
|
+
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");
|
|
1851
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} ranking (${results.length} providers):
|
|
1852
|
+
${ranking}`);
|
|
1853
|
+
} else {
|
|
1854
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} no providers found`);
|
|
1855
|
+
}
|
|
1856
|
+
return results;
|
|
2152
1857
|
}
|
|
2153
1858
|
/**
|
|
2154
1859
|
* Get best-priced provider for a specific model
|
|
@@ -2336,92 +2041,6 @@ var createMemoryDriver = (seed) => {
|
|
|
2336
2041
|
};
|
|
2337
2042
|
};
|
|
2338
2043
|
|
|
2339
|
-
// storage/drivers/sqlite.ts
|
|
2340
|
-
var isBun = () => {
|
|
2341
|
-
return typeof process.versions.bun !== "undefined";
|
|
2342
|
-
};
|
|
2343
|
-
var cachedDbModule = null;
|
|
2344
|
-
var loadDatabase = async (dbPath) => {
|
|
2345
|
-
if (isBun()) {
|
|
2346
|
-
throw new Error(
|
|
2347
|
-
"SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
|
|
2348
|
-
);
|
|
2349
|
-
}
|
|
2350
|
-
try {
|
|
2351
|
-
if (!cachedDbModule) {
|
|
2352
|
-
cachedDbModule = (await import('better-sqlite3')).default;
|
|
2353
|
-
}
|
|
2354
|
-
return new cachedDbModule(dbPath);
|
|
2355
|
-
} catch (error) {
|
|
2356
|
-
throw new Error(
|
|
2357
|
-
`better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
|
|
2358
|
-
);
|
|
2359
|
-
}
|
|
2360
|
-
};
|
|
2361
|
-
var createSqliteDriver = (options = {}) => {
|
|
2362
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2363
|
-
const tableName = options.tableName || "sdk_storage";
|
|
2364
|
-
let db;
|
|
2365
|
-
let selectStmt;
|
|
2366
|
-
let upsertStmt;
|
|
2367
|
-
let deleteStmt;
|
|
2368
|
-
const initDb = async () => {
|
|
2369
|
-
if (!db) {
|
|
2370
|
-
db = await loadDatabase(dbPath);
|
|
2371
|
-
db.exec(
|
|
2372
|
-
`CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
2373
|
-
);
|
|
2374
|
-
selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
|
|
2375
|
-
upsertStmt = db.prepare(
|
|
2376
|
-
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)
|
|
2377
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
2378
|
-
);
|
|
2379
|
-
deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
|
|
2380
|
-
}
|
|
2381
|
-
};
|
|
2382
|
-
const ensureInit = async () => {
|
|
2383
|
-
if (!db) {
|
|
2384
|
-
await initDb();
|
|
2385
|
-
}
|
|
2386
|
-
};
|
|
2387
|
-
return {
|
|
2388
|
-
async getItem(key, defaultValue) {
|
|
2389
|
-
try {
|
|
2390
|
-
await ensureInit();
|
|
2391
|
-
const row = selectStmt.get(key);
|
|
2392
|
-
if (!row || typeof row.value !== "string") return defaultValue;
|
|
2393
|
-
try {
|
|
2394
|
-
return JSON.parse(row.value);
|
|
2395
|
-
} catch (parseError) {
|
|
2396
|
-
if (typeof defaultValue === "string") {
|
|
2397
|
-
return row.value;
|
|
2398
|
-
}
|
|
2399
|
-
throw parseError;
|
|
2400
|
-
}
|
|
2401
|
-
} catch (error) {
|
|
2402
|
-
console.error(`SQLite getItem failed for key "${key}":`, error);
|
|
2403
|
-
return defaultValue;
|
|
2404
|
-
}
|
|
2405
|
-
},
|
|
2406
|
-
async setItem(key, value) {
|
|
2407
|
-
try {
|
|
2408
|
-
await ensureInit();
|
|
2409
|
-
upsertStmt.run(key, JSON.stringify(value));
|
|
2410
|
-
} catch (error) {
|
|
2411
|
-
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
2412
|
-
}
|
|
2413
|
-
},
|
|
2414
|
-
async removeItem(key) {
|
|
2415
|
-
try {
|
|
2416
|
-
await ensureInit();
|
|
2417
|
-
deleteStmt.run(key);
|
|
2418
|
-
} catch (error) {
|
|
2419
|
-
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
};
|
|
2423
|
-
};
|
|
2424
|
-
|
|
2425
2044
|
// storage/keys.ts
|
|
2426
2045
|
var SDK_STORAGE_KEYS = {
|
|
2427
2046
|
MODELS_FROM_ALL_PROVIDERS: "modelsFromAllProviders",
|
|
@@ -2445,6 +2064,91 @@ var SDK_STORAGE_KEYS = {
|
|
|
2445
2064
|
PROVIDERS_ON_COOLDOWN: "providers_on_cooldown"
|
|
2446
2065
|
};
|
|
2447
2066
|
|
|
2067
|
+
// storage/usageTracking/aggregate.ts
|
|
2068
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
2069
|
+
var jsGroupKey = (entry, groupBy, tzOffsetMinutes) => {
|
|
2070
|
+
switch (groupBy) {
|
|
2071
|
+
case "modelId":
|
|
2072
|
+
return entry.modelId ?? null;
|
|
2073
|
+
case "baseUrl":
|
|
2074
|
+
return entry.baseUrl ?? null;
|
|
2075
|
+
case "client":
|
|
2076
|
+
return entry.client ?? null;
|
|
2077
|
+
case "sessionId":
|
|
2078
|
+
return entry.sessionId ?? null;
|
|
2079
|
+
case "provider":
|
|
2080
|
+
return entry.provider ?? null;
|
|
2081
|
+
case "day": {
|
|
2082
|
+
const d = new Date(entry.timestamp - tzOffsetMinutes * 6e4);
|
|
2083
|
+
return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
|
|
2084
|
+
}
|
|
2085
|
+
case "hour": {
|
|
2086
|
+
const d = new Date(entry.timestamp - tzOffsetMinutes * 6e4);
|
|
2087
|
+
return pad2(d.getUTCHours());
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
var reduceAggregate = (entries, options = {}) => {
|
|
2092
|
+
const emptyRow = (group) => ({
|
|
2093
|
+
group,
|
|
2094
|
+
requests: 0,
|
|
2095
|
+
promptTokens: 0,
|
|
2096
|
+
completionTokens: 0,
|
|
2097
|
+
totalTokens: 0,
|
|
2098
|
+
cost: 0,
|
|
2099
|
+
satsCost: 0,
|
|
2100
|
+
baseMsats: 0,
|
|
2101
|
+
inputMsats: 0,
|
|
2102
|
+
outputMsats: 0,
|
|
2103
|
+
totalMsats: 0,
|
|
2104
|
+
totalUsd: 0,
|
|
2105
|
+
cacheReadInputTokens: 0,
|
|
2106
|
+
cacheCreationInputTokens: 0,
|
|
2107
|
+
cacheReadMsats: 0,
|
|
2108
|
+
cacheCreationMsats: 0
|
|
2109
|
+
});
|
|
2110
|
+
const accumulate = (row, entry) => {
|
|
2111
|
+
row.requests += 1;
|
|
2112
|
+
row.promptTokens += entry.promptTokens;
|
|
2113
|
+
row.completionTokens += entry.completionTokens;
|
|
2114
|
+
row.totalTokens += entry.totalTokens;
|
|
2115
|
+
row.cost += entry.cost;
|
|
2116
|
+
row.satsCost += entry.satsCost;
|
|
2117
|
+
row.baseMsats += entry.baseMsats ?? 0;
|
|
2118
|
+
row.inputMsats += entry.inputMsats ?? 0;
|
|
2119
|
+
row.outputMsats += entry.outputMsats ?? 0;
|
|
2120
|
+
row.totalMsats += entry.totalMsats ?? 0;
|
|
2121
|
+
row.totalUsd += entry.totalUsd ?? 0;
|
|
2122
|
+
row.cacheReadInputTokens += entry.cacheReadInputTokens ?? 0;
|
|
2123
|
+
row.cacheCreationInputTokens += entry.cacheCreationInputTokens ?? 0;
|
|
2124
|
+
row.cacheReadMsats += entry.cacheReadMsats ?? 0;
|
|
2125
|
+
row.cacheCreationMsats += entry.cacheCreationMsats ?? 0;
|
|
2126
|
+
};
|
|
2127
|
+
if (!options.groupBy) {
|
|
2128
|
+
const total = emptyRow(null);
|
|
2129
|
+
for (const entry of entries) accumulate(total, entry);
|
|
2130
|
+
return [total];
|
|
2131
|
+
}
|
|
2132
|
+
const tz = options.tzOffsetMinutes ?? 0;
|
|
2133
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2134
|
+
for (const entry of entries) {
|
|
2135
|
+
const key = jsGroupKey(entry, options.groupBy, tz);
|
|
2136
|
+
let row = groups.get(key);
|
|
2137
|
+
if (!row) {
|
|
2138
|
+
row = emptyRow(key);
|
|
2139
|
+
groups.set(key, row);
|
|
2140
|
+
}
|
|
2141
|
+
accumulate(row, entry);
|
|
2142
|
+
}
|
|
2143
|
+
const rows = [...groups.values()];
|
|
2144
|
+
if (options.groupBy === "day" || options.groupBy === "hour") {
|
|
2145
|
+
rows.sort((a, b) => (a.group ?? "").localeCompare(b.group ?? ""));
|
|
2146
|
+
} else {
|
|
2147
|
+
rows.sort((a, b) => b.satsCost - a.satsCost);
|
|
2148
|
+
}
|
|
2149
|
+
return rows;
|
|
2150
|
+
};
|
|
2151
|
+
|
|
2448
2152
|
// storage/usageTracking/indexedDB.ts
|
|
2449
2153
|
var DEFAULT_DB_NAME = "routstr-sdk";
|
|
2450
2154
|
var DEFAULT_STORE_NAME = "usage_tracking";
|
|
@@ -2456,9 +2160,10 @@ var openDatabase = (dbName, storeName) => {
|
|
|
2456
2160
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
2457
2161
|
}
|
|
2458
2162
|
return new Promise((resolve, reject) => {
|
|
2459
|
-
const request = indexedDB.open(dbName,
|
|
2163
|
+
const request = indexedDB.open(dbName, 3);
|
|
2460
2164
|
request.onupgradeneeded = () => {
|
|
2461
2165
|
const db = request.result;
|
|
2166
|
+
const tx = request.transaction;
|
|
2462
2167
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
2463
2168
|
const store = db.createObjectStore(storeName, { keyPath: "id" });
|
|
2464
2169
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
@@ -2466,10 +2171,25 @@ var openDatabase = (dbName, storeName) => {
|
|
|
2466
2171
|
store.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
2467
2172
|
store.createIndex("sessionId", "sessionId", { unique: false });
|
|
2468
2173
|
store.createIndex("client", "client", { unique: false });
|
|
2174
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
2175
|
+
} else if (tx) {
|
|
2176
|
+
const store = tx.objectStore(storeName);
|
|
2177
|
+
if (!store.indexNames.contains("provider")) {
|
|
2178
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
2182
|
+
db.createObjectStore("sdk_storage");
|
|
2469
2183
|
}
|
|
2470
2184
|
};
|
|
2471
2185
|
request.onsuccess = () => resolve(request.result);
|
|
2472
2186
|
request.onerror = () => reject(request.error);
|
|
2187
|
+
request.onblocked = () => {
|
|
2188
|
+
console.warn(
|
|
2189
|
+
`[usageTracking IndexedDB] open blocked for "${dbName}" \u2014 close other tabs using this DB`
|
|
2190
|
+
);
|
|
2191
|
+
reject(new Error(`IndexedDB "${dbName}" blocked by another connection`));
|
|
2192
|
+
};
|
|
2473
2193
|
});
|
|
2474
2194
|
};
|
|
2475
2195
|
var matchesFilters = (entry, options = {}) => {
|
|
@@ -2491,6 +2211,12 @@ var matchesFilters = (entry, options = {}) => {
|
|
|
2491
2211
|
if (options.client && entry.client !== options.client) {
|
|
2492
2212
|
return false;
|
|
2493
2213
|
}
|
|
2214
|
+
if (options.clients && options.clients.length > 0 && (entry.client == null || !options.clients.includes(entry.client))) {
|
|
2215
|
+
return false;
|
|
2216
|
+
}
|
|
2217
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
2218
|
+
return false;
|
|
2219
|
+
}
|
|
2494
2220
|
return true;
|
|
2495
2221
|
};
|
|
2496
2222
|
var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
@@ -2586,6 +2312,10 @@ var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
|
2586
2312
|
const results = await this.list(options2);
|
|
2587
2313
|
return results.length;
|
|
2588
2314
|
},
|
|
2315
|
+
async aggregate(options2 = {}) {
|
|
2316
|
+
const entries = await this.list(options2);
|
|
2317
|
+
return reduceAggregate(entries, options2);
|
|
2318
|
+
},
|
|
2589
2319
|
async deleteOlderThan(timestamp) {
|
|
2590
2320
|
await ensureMigrated();
|
|
2591
2321
|
const db = await getDb();
|
|
@@ -2622,410 +2352,31 @@ var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
|
2622
2352
|
};
|
|
2623
2353
|
};
|
|
2624
2354
|
|
|
2625
|
-
// storage/usageTracking/
|
|
2626
|
-
var MIGRATION_MARKER_KEY2 = "usage_tracking_migration_v1";
|
|
2355
|
+
// storage/usageTracking/memory.ts
|
|
2627
2356
|
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2628
|
-
var
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
var cachedDbModule2 = null;
|
|
2632
|
-
var loadDatabase2 = async (dbPath) => {
|
|
2633
|
-
if (isBun2()) {
|
|
2634
|
-
throw new Error(
|
|
2635
|
-
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
2636
|
-
);
|
|
2637
|
-
}
|
|
2638
|
-
try {
|
|
2639
|
-
if (!cachedDbModule2) {
|
|
2640
|
-
cachedDbModule2 = (await import('better-sqlite3')).default;
|
|
2641
|
-
}
|
|
2642
|
-
return new cachedDbModule2(dbPath);
|
|
2643
|
-
} catch (error) {
|
|
2644
|
-
throw new Error(
|
|
2645
|
-
`better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
|
|
2646
|
-
);
|
|
2357
|
+
var matchesFilters2 = (entry, options = {}) => {
|
|
2358
|
+
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
2359
|
+
return false;
|
|
2647
2360
|
}
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
const clauses = [];
|
|
2651
|
-
const params = [];
|
|
2652
|
-
if (typeof options.before === "number") {
|
|
2653
|
-
clauses.push("timestamp < ?");
|
|
2654
|
-
params.push(options.before);
|
|
2361
|
+
if (typeof options.after === "number" && entry.timestamp <= options.after) {
|
|
2362
|
+
return false;
|
|
2655
2363
|
}
|
|
2656
|
-
if (
|
|
2657
|
-
|
|
2658
|
-
params.push(options.after);
|
|
2364
|
+
if (options.modelId && entry.modelId !== options.modelId) {
|
|
2365
|
+
return false;
|
|
2659
2366
|
}
|
|
2660
|
-
if (options.
|
|
2661
|
-
|
|
2662
|
-
params.push(options.modelId);
|
|
2367
|
+
if (options.baseUrl && normalizeBaseUrl2(entry.baseUrl) !== normalizeBaseUrl2(options.baseUrl)) {
|
|
2368
|
+
return false;
|
|
2663
2369
|
}
|
|
2664
|
-
if (options.
|
|
2665
|
-
clauses.push("base_url = ?");
|
|
2666
|
-
params.push(normalizeBaseUrl2(options.baseUrl));
|
|
2667
|
-
}
|
|
2668
|
-
if (options.sessionId) {
|
|
2669
|
-
clauses.push("session_id = ?");
|
|
2670
|
-
params.push(options.sessionId);
|
|
2671
|
-
}
|
|
2672
|
-
if (options.client) {
|
|
2673
|
-
clauses.push("client = ?");
|
|
2674
|
-
params.push(options.client);
|
|
2675
|
-
}
|
|
2676
|
-
return {
|
|
2677
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
2678
|
-
params
|
|
2679
|
-
};
|
|
2680
|
-
};
|
|
2681
|
-
var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
2682
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2683
|
-
const tableName = options.tableName || "usage_tracking";
|
|
2684
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2685
|
-
let db;
|
|
2686
|
-
let insertStmt;
|
|
2687
|
-
let migrationComplete = false;
|
|
2688
|
-
const initDb = async () => {
|
|
2689
|
-
if (!db) {
|
|
2690
|
-
db = await loadDatabase2(dbPath);
|
|
2691
|
-
db.exec(`
|
|
2692
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2693
|
-
id TEXT PRIMARY KEY,
|
|
2694
|
-
timestamp INTEGER NOT NULL,
|
|
2695
|
-
model_id TEXT NOT NULL,
|
|
2696
|
-
base_url TEXT NOT NULL,
|
|
2697
|
-
request_id TEXT NOT NULL,
|
|
2698
|
-
cost REAL NOT NULL,
|
|
2699
|
-
sats_cost REAL NOT NULL,
|
|
2700
|
-
prompt_tokens INTEGER NOT NULL,
|
|
2701
|
-
completion_tokens INTEGER NOT NULL,
|
|
2702
|
-
total_tokens INTEGER NOT NULL,
|
|
2703
|
-
client TEXT,
|
|
2704
|
-
session_id TEXT,
|
|
2705
|
-
tags TEXT
|
|
2706
|
-
);
|
|
2707
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
2708
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
2709
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
2710
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
2711
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
2712
|
-
`);
|
|
2713
|
-
insertStmt = db.prepare(`
|
|
2714
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
2715
|
-
id, timestamp, model_id, base_url, request_id,
|
|
2716
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2717
|
-
client, session_id, tags
|
|
2718
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2719
|
-
`);
|
|
2720
|
-
}
|
|
2721
|
-
};
|
|
2722
|
-
const ensureInit = async () => {
|
|
2723
|
-
if (!db) {
|
|
2724
|
-
await initDb();
|
|
2725
|
-
}
|
|
2726
|
-
};
|
|
2727
|
-
const appendOne = (entry) => {
|
|
2728
|
-
insertStmt.run(
|
|
2729
|
-
entry.id,
|
|
2730
|
-
entry.timestamp,
|
|
2731
|
-
entry.modelId,
|
|
2732
|
-
normalizeBaseUrl2(entry.baseUrl),
|
|
2733
|
-
entry.requestId,
|
|
2734
|
-
entry.cost,
|
|
2735
|
-
entry.satsCost,
|
|
2736
|
-
entry.promptTokens,
|
|
2737
|
-
entry.completionTokens,
|
|
2738
|
-
entry.totalTokens,
|
|
2739
|
-
entry.client ?? null,
|
|
2740
|
-
entry.sessionId ?? null,
|
|
2741
|
-
JSON.stringify(entry.tags ?? [])
|
|
2742
|
-
);
|
|
2743
|
-
};
|
|
2744
|
-
const ensureMigrated = async () => {
|
|
2745
|
-
if (!legacyStorageDriver || migrationComplete) return;
|
|
2746
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
2747
|
-
MIGRATION_MARKER_KEY2,
|
|
2748
|
-
false
|
|
2749
|
-
);
|
|
2750
|
-
if (migrated) {
|
|
2751
|
-
migrationComplete = true;
|
|
2752
|
-
return;
|
|
2753
|
-
}
|
|
2754
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2755
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2756
|
-
[]
|
|
2757
|
-
);
|
|
2758
|
-
for (const entry of legacyEntries) {
|
|
2759
|
-
appendOne(entry);
|
|
2760
|
-
}
|
|
2761
|
-
if (legacyEntries.length > 0) {
|
|
2762
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2763
|
-
}
|
|
2764
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
2765
|
-
migrationComplete = true;
|
|
2766
|
-
};
|
|
2767
|
-
const mapRow = (row) => ({
|
|
2768
|
-
id: row.id,
|
|
2769
|
-
timestamp: row.timestamp,
|
|
2770
|
-
modelId: row.model_id,
|
|
2771
|
-
baseUrl: row.base_url,
|
|
2772
|
-
requestId: row.request_id,
|
|
2773
|
-
cost: row.cost,
|
|
2774
|
-
satsCost: row.sats_cost,
|
|
2775
|
-
promptTokens: row.prompt_tokens,
|
|
2776
|
-
completionTokens: row.completion_tokens,
|
|
2777
|
-
totalTokens: row.total_tokens,
|
|
2778
|
-
client: row.client ?? void 0,
|
|
2779
|
-
sessionId: row.session_id ?? void 0,
|
|
2780
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
2781
|
-
});
|
|
2782
|
-
return {
|
|
2783
|
-
async migrate() {
|
|
2784
|
-
await ensureInit();
|
|
2785
|
-
await ensureMigrated();
|
|
2786
|
-
},
|
|
2787
|
-
async append(entry) {
|
|
2788
|
-
await ensureInit();
|
|
2789
|
-
await ensureMigrated();
|
|
2790
|
-
appendOne(entry);
|
|
2791
|
-
},
|
|
2792
|
-
async appendMany(entries) {
|
|
2793
|
-
await ensureInit();
|
|
2794
|
-
await ensureMigrated();
|
|
2795
|
-
for (const entry of entries) {
|
|
2796
|
-
appendOne(entry);
|
|
2797
|
-
}
|
|
2798
|
-
},
|
|
2799
|
-
async list(options2 = {}) {
|
|
2800
|
-
await ensureInit();
|
|
2801
|
-
await ensureMigrated();
|
|
2802
|
-
const { sql, params } = buildWhereClause(options2);
|
|
2803
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
2804
|
-
const stmt = db.prepare(
|
|
2805
|
-
`SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`
|
|
2806
|
-
);
|
|
2807
|
-
const rows = stmt.all(
|
|
2808
|
-
...typeof options2.limit === "number" ? [...params, options2.limit] : params
|
|
2809
|
-
);
|
|
2810
|
-
return rows.map(mapRow);
|
|
2811
|
-
},
|
|
2812
|
-
async count(options2 = {}) {
|
|
2813
|
-
await ensureInit();
|
|
2814
|
-
await ensureMigrated();
|
|
2815
|
-
const { sql, params } = buildWhereClause(options2);
|
|
2816
|
-
const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
|
|
2817
|
-
const row = stmt.get(...params);
|
|
2818
|
-
return Number(row?.count ?? 0);
|
|
2819
|
-
},
|
|
2820
|
-
async deleteOlderThan(timestamp) {
|
|
2821
|
-
await ensureInit();
|
|
2822
|
-
await ensureMigrated();
|
|
2823
|
-
const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
|
|
2824
|
-
const result = stmt.run(timestamp);
|
|
2825
|
-
return result.changes;
|
|
2826
|
-
},
|
|
2827
|
-
async clear() {
|
|
2828
|
-
await ensureInit();
|
|
2829
|
-
await ensureMigrated();
|
|
2830
|
-
db.prepare(`DELETE FROM ${tableName}`).run();
|
|
2831
|
-
}
|
|
2832
|
-
};
|
|
2833
|
-
};
|
|
2834
|
-
|
|
2835
|
-
// storage/usageTracking/bunSqlite.ts
|
|
2836
|
-
var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
|
|
2837
|
-
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2838
|
-
var buildWhereClause2 = (options = {}) => {
|
|
2839
|
-
const clauses = [];
|
|
2840
|
-
const params = [];
|
|
2841
|
-
if (typeof options.before === "number") {
|
|
2842
|
-
clauses.push("timestamp < ?");
|
|
2843
|
-
params.push(options.before);
|
|
2844
|
-
}
|
|
2845
|
-
if (typeof options.after === "number") {
|
|
2846
|
-
clauses.push("timestamp > ?");
|
|
2847
|
-
params.push(options.after);
|
|
2848
|
-
}
|
|
2849
|
-
if (options.modelId) {
|
|
2850
|
-
clauses.push("model_id = ?");
|
|
2851
|
-
params.push(options.modelId);
|
|
2852
|
-
}
|
|
2853
|
-
if (options.baseUrl) {
|
|
2854
|
-
clauses.push("base_url = ?");
|
|
2855
|
-
params.push(normalizeBaseUrl3(options.baseUrl));
|
|
2856
|
-
}
|
|
2857
|
-
if (options.sessionId) {
|
|
2858
|
-
clauses.push("session_id = ?");
|
|
2859
|
-
params.push(options.sessionId);
|
|
2860
|
-
}
|
|
2861
|
-
if (options.client) {
|
|
2862
|
-
clauses.push("client = ?");
|
|
2863
|
-
params.push(options.client);
|
|
2864
|
-
}
|
|
2865
|
-
return {
|
|
2866
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
2867
|
-
params
|
|
2868
|
-
};
|
|
2869
|
-
};
|
|
2870
|
-
var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
2871
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2872
|
-
const tableName = options.tableName || "usage_tracking";
|
|
2873
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2874
|
-
const SQLiteDatabase = options.sqlite?.Database;
|
|
2875
|
-
let migrationPromise = null;
|
|
2876
|
-
if (!SQLiteDatabase) {
|
|
2877
|
-
throw new Error(
|
|
2878
|
-
"Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
|
|
2879
|
-
);
|
|
2880
|
-
}
|
|
2881
|
-
const db = new SQLiteDatabase(dbPath);
|
|
2882
|
-
db.run(`
|
|
2883
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2884
|
-
id TEXT PRIMARY KEY,
|
|
2885
|
-
timestamp INTEGER NOT NULL,
|
|
2886
|
-
model_id TEXT NOT NULL,
|
|
2887
|
-
base_url TEXT NOT NULL,
|
|
2888
|
-
request_id TEXT NOT NULL,
|
|
2889
|
-
cost REAL NOT NULL,
|
|
2890
|
-
sats_cost REAL NOT NULL,
|
|
2891
|
-
prompt_tokens INTEGER NOT NULL,
|
|
2892
|
-
completion_tokens INTEGER NOT NULL,
|
|
2893
|
-
total_tokens INTEGER NOT NULL,
|
|
2894
|
-
client TEXT,
|
|
2895
|
-
session_id TEXT,
|
|
2896
|
-
tags TEXT
|
|
2897
|
-
)
|
|
2898
|
-
`);
|
|
2899
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
|
|
2900
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
|
|
2901
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
|
|
2902
|
-
const appendOne = (entry) => {
|
|
2903
|
-
db.query(`
|
|
2904
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
2905
|
-
id, timestamp, model_id, base_url, request_id,
|
|
2906
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2907
|
-
client, session_id, tags
|
|
2908
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2909
|
-
`).run(
|
|
2910
|
-
entry.id,
|
|
2911
|
-
entry.timestamp,
|
|
2912
|
-
entry.modelId,
|
|
2913
|
-
normalizeBaseUrl3(entry.baseUrl),
|
|
2914
|
-
entry.requestId,
|
|
2915
|
-
entry.cost,
|
|
2916
|
-
entry.satsCost,
|
|
2917
|
-
entry.promptTokens,
|
|
2918
|
-
entry.completionTokens,
|
|
2919
|
-
entry.totalTokens,
|
|
2920
|
-
entry.client ?? null,
|
|
2921
|
-
entry.sessionId ?? null,
|
|
2922
|
-
JSON.stringify(entry.tags ?? [])
|
|
2923
|
-
);
|
|
2924
|
-
};
|
|
2925
|
-
const mapRow = (row) => ({
|
|
2926
|
-
id: row.id,
|
|
2927
|
-
timestamp: row.timestamp,
|
|
2928
|
-
modelId: row.model_id,
|
|
2929
|
-
baseUrl: row.base_url,
|
|
2930
|
-
requestId: row.request_id,
|
|
2931
|
-
cost: row.cost,
|
|
2932
|
-
satsCost: row.sats_cost,
|
|
2933
|
-
promptTokens: row.prompt_tokens,
|
|
2934
|
-
completionTokens: row.completion_tokens,
|
|
2935
|
-
totalTokens: row.total_tokens,
|
|
2936
|
-
client: row.client ?? void 0,
|
|
2937
|
-
sessionId: row.session_id ?? void 0,
|
|
2938
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
2939
|
-
});
|
|
2940
|
-
const ensureMigrated = async () => {
|
|
2941
|
-
if (!legacyStorageDriver) return;
|
|
2942
|
-
if (!migrationPromise) {
|
|
2943
|
-
migrationPromise = (async () => {
|
|
2944
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
2945
|
-
MIGRATION_MARKER_KEY3,
|
|
2946
|
-
false
|
|
2947
|
-
);
|
|
2948
|
-
if (migrated) return;
|
|
2949
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2950
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2951
|
-
[]
|
|
2952
|
-
);
|
|
2953
|
-
if (legacyEntries.length > 0) {
|
|
2954
|
-
for (const entry of legacyEntries) {
|
|
2955
|
-
appendOne(entry);
|
|
2956
|
-
}
|
|
2957
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2958
|
-
}
|
|
2959
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
|
|
2960
|
-
})();
|
|
2961
|
-
}
|
|
2962
|
-
await migrationPromise;
|
|
2963
|
-
};
|
|
2964
|
-
return {
|
|
2965
|
-
async migrate() {
|
|
2966
|
-
await ensureMigrated();
|
|
2967
|
-
},
|
|
2968
|
-
async append(entry) {
|
|
2969
|
-
await ensureMigrated();
|
|
2970
|
-
appendOne(entry);
|
|
2971
|
-
},
|
|
2972
|
-
async appendMany(entries) {
|
|
2973
|
-
await ensureMigrated();
|
|
2974
|
-
for (const entry of entries) {
|
|
2975
|
-
appendOne(entry);
|
|
2976
|
-
}
|
|
2977
|
-
},
|
|
2978
|
-
async list(options2 = {}) {
|
|
2979
|
-
await ensureMigrated();
|
|
2980
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
2981
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
2982
|
-
const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
|
|
2983
|
-
let rows;
|
|
2984
|
-
if (typeof options2.limit === "number") {
|
|
2985
|
-
rows = db.query(query).all(...params, options2.limit);
|
|
2986
|
-
} else {
|
|
2987
|
-
rows = db.query(query).all(...params);
|
|
2988
|
-
}
|
|
2989
|
-
return rows.map(mapRow);
|
|
2990
|
-
},
|
|
2991
|
-
async count(options2 = {}) {
|
|
2992
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
2993
|
-
const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
|
|
2994
|
-
const row = db.query(query).get(...params);
|
|
2995
|
-
return Number(row?.count ?? 0);
|
|
2996
|
-
},
|
|
2997
|
-
async deleteOlderThan(timestamp) {
|
|
2998
|
-
await ensureMigrated();
|
|
2999
|
-
const before = timestamp;
|
|
3000
|
-
const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
|
|
3001
|
-
return result.changes ?? 0;
|
|
3002
|
-
},
|
|
3003
|
-
async clear() {
|
|
3004
|
-
await ensureMigrated();
|
|
3005
|
-
db.query(`DELETE FROM ${tableName}`).run();
|
|
3006
|
-
}
|
|
3007
|
-
};
|
|
3008
|
-
};
|
|
3009
|
-
|
|
3010
|
-
// storage/usageTracking/memory.ts
|
|
3011
|
-
var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3012
|
-
var matchesFilters2 = (entry, options = {}) => {
|
|
3013
|
-
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
3014
|
-
return false;
|
|
3015
|
-
}
|
|
3016
|
-
if (typeof options.after === "number" && entry.timestamp <= options.after) {
|
|
3017
|
-
return false;
|
|
3018
|
-
}
|
|
3019
|
-
if (options.modelId && entry.modelId !== options.modelId) {
|
|
2370
|
+
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
3020
2371
|
return false;
|
|
3021
2372
|
}
|
|
3022
|
-
if (options.
|
|
2373
|
+
if (options.client && entry.client !== options.client) {
|
|
3023
2374
|
return false;
|
|
3024
2375
|
}
|
|
3025
|
-
if (options.
|
|
2376
|
+
if (options.clients && options.clients.length > 0 && (entry.client == null || !options.clients.includes(entry.client))) {
|
|
3026
2377
|
return false;
|
|
3027
2378
|
}
|
|
3028
|
-
if (options.
|
|
2379
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
3029
2380
|
return false;
|
|
3030
2381
|
}
|
|
3031
2382
|
return true;
|
|
@@ -3033,18 +2384,18 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
3033
2384
|
var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
3034
2385
|
const store = /* @__PURE__ */ new Map();
|
|
3035
2386
|
for (const entry of seed) {
|
|
3036
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2387
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
3037
2388
|
}
|
|
3038
2389
|
return {
|
|
3039
2390
|
async migrate() {
|
|
3040
2391
|
return;
|
|
3041
2392
|
},
|
|
3042
2393
|
async append(entry) {
|
|
3043
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2394
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
3044
2395
|
},
|
|
3045
2396
|
async appendMany(entries) {
|
|
3046
2397
|
for (const entry of entries) {
|
|
3047
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2398
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
3048
2399
|
}
|
|
3049
2400
|
},
|
|
3050
2401
|
async list(options = {}) {
|
|
@@ -3057,6 +2408,10 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
3057
2408
|
async count(options = {}) {
|
|
3058
2409
|
return (await this.list(options)).length;
|
|
3059
2410
|
},
|
|
2411
|
+
async aggregate(options = {}) {
|
|
2412
|
+
const entries = [...store.values()].filter((entry) => matchesFilters2(entry, options));
|
|
2413
|
+
return reduceAggregate(entries, options);
|
|
2414
|
+
},
|
|
3060
2415
|
async deleteOlderThan(timestamp) {
|
|
3061
2416
|
let deleted = 0;
|
|
3062
2417
|
for (const [id, entry] of store.entries()) {
|
|
@@ -3072,7 +2427,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
3072
2427
|
}
|
|
3073
2428
|
};
|
|
3074
2429
|
};
|
|
3075
|
-
var
|
|
2430
|
+
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3076
2431
|
var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
3077
2432
|
modelsFromAllProviders: {},
|
|
3078
2433
|
lastUsedModel: null,
|
|
@@ -3095,7 +2450,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3095
2450
|
setModelsFromAllProviders: (value) => {
|
|
3096
2451
|
const normalized = {};
|
|
3097
2452
|
for (const [baseUrl, models] of Object.entries(value)) {
|
|
3098
|
-
normalized[
|
|
2453
|
+
normalized[normalizeBaseUrl3(baseUrl)] = models;
|
|
3099
2454
|
}
|
|
3100
2455
|
void driver.setItem(
|
|
3101
2456
|
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
@@ -3108,7 +2463,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3108
2463
|
set({ lastUsedModel: value });
|
|
3109
2464
|
},
|
|
3110
2465
|
setBaseUrlsList: (value) => {
|
|
3111
|
-
const normalized = value.map((url) =>
|
|
2466
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
3112
2467
|
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
3113
2468
|
set({ baseUrlsList: normalized });
|
|
3114
2469
|
},
|
|
@@ -3117,14 +2472,14 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3117
2472
|
set({ lastBaseUrlsUpdate: value });
|
|
3118
2473
|
},
|
|
3119
2474
|
setDisabledProviders: (value) => {
|
|
3120
|
-
const normalized = value.map((url) =>
|
|
2475
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
3121
2476
|
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
3122
2477
|
set({ disabledProviders: normalized });
|
|
3123
2478
|
},
|
|
3124
2479
|
setMintsFromAllProviders: (value) => {
|
|
3125
2480
|
const normalized = {};
|
|
3126
2481
|
for (const [baseUrl, mints] of Object.entries(value)) {
|
|
3127
|
-
normalized[
|
|
2482
|
+
normalized[normalizeBaseUrl3(baseUrl)] = mints.map(
|
|
3128
2483
|
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
3129
2484
|
);
|
|
3130
2485
|
}
|
|
@@ -3137,7 +2492,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3137
2492
|
setInfoFromAllProviders: (value) => {
|
|
3138
2493
|
const normalized = {};
|
|
3139
2494
|
for (const [baseUrl, info] of Object.entries(value)) {
|
|
3140
|
-
normalized[
|
|
2495
|
+
normalized[normalizeBaseUrl3(baseUrl)] = info;
|
|
3141
2496
|
}
|
|
3142
2497
|
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
3143
2498
|
set({ infoFromAllProviders: normalized });
|
|
@@ -3145,7 +2500,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3145
2500
|
setLastModelsUpdate: (value) => {
|
|
3146
2501
|
const normalized = {};
|
|
3147
2502
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
3148
|
-
normalized[
|
|
2503
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
3149
2504
|
}
|
|
3150
2505
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
3151
2506
|
set({ lastModelsUpdate: normalized });
|
|
@@ -3155,7 +2510,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3155
2510
|
const updates = typeof value === "function" ? value(state.apiKeys) : value;
|
|
3156
2511
|
const normalized = updates.map((entry) => ({
|
|
3157
2512
|
...entry,
|
|
3158
|
-
baseUrl:
|
|
2513
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3159
2514
|
balance: entry.balance ?? 0,
|
|
3160
2515
|
lastUsed: entry.lastUsed ?? null
|
|
3161
2516
|
}));
|
|
@@ -3167,7 +2522,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3167
2522
|
set((state) => {
|
|
3168
2523
|
const updates = typeof value === "function" ? value(state.childKeys) : value;
|
|
3169
2524
|
const normalized = updates.map((entry) => ({
|
|
3170
|
-
parentBaseUrl:
|
|
2525
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
3171
2526
|
childKey: entry.childKey,
|
|
3172
2527
|
balance: entry.balance ?? 0,
|
|
3173
2528
|
balanceLimit: entry.balanceLimit,
|
|
@@ -3181,9 +2536,9 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3181
2536
|
setXcashuTokens: (value) => {
|
|
3182
2537
|
const normalized = {};
|
|
3183
2538
|
for (const [baseUrl, tokens] of Object.entries(value)) {
|
|
3184
|
-
normalized[
|
|
2539
|
+
normalized[normalizeBaseUrl3(baseUrl)] = tokens.map((entry) => ({
|
|
3185
2540
|
...entry,
|
|
3186
|
-
baseUrl:
|
|
2541
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3187
2542
|
createdAt: entry.createdAt ?? Date.now(),
|
|
3188
2543
|
tryCount: entry.tryCount ?? 0
|
|
3189
2544
|
}));
|
|
@@ -3234,12 +2589,12 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3234
2589
|
},
|
|
3235
2590
|
// ========== Failure Tracking ==========
|
|
3236
2591
|
setFailedProviders: (value) => {
|
|
3237
|
-
const normalized = value.map((url) =>
|
|
2592
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
3238
2593
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
|
|
3239
2594
|
set({ failedProviders: normalized });
|
|
3240
2595
|
},
|
|
3241
2596
|
addFailedProvider: (baseUrl) => {
|
|
3242
|
-
const normalized =
|
|
2597
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3243
2598
|
const current = get().failedProviders;
|
|
3244
2599
|
if (!current.includes(normalized)) {
|
|
3245
2600
|
const updated = [...current, normalized];
|
|
@@ -3248,7 +2603,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3248
2603
|
}
|
|
3249
2604
|
},
|
|
3250
2605
|
removeFailedProvider: (baseUrl) => {
|
|
3251
|
-
const normalized =
|
|
2606
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3252
2607
|
const current = get().failedProviders;
|
|
3253
2608
|
const updated = current.filter((url) => url !== normalized);
|
|
3254
2609
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
@@ -3257,13 +2612,13 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3257
2612
|
setLastFailed: (value) => {
|
|
3258
2613
|
const normalized = {};
|
|
3259
2614
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
3260
|
-
normalized[
|
|
2615
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
3261
2616
|
}
|
|
3262
2617
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
|
|
3263
2618
|
set({ lastFailed: normalized });
|
|
3264
2619
|
},
|
|
3265
2620
|
setLastFailedTimestamp: (baseUrl, timestamp) => {
|
|
3266
|
-
const normalized =
|
|
2621
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3267
2622
|
const current = get().lastFailed;
|
|
3268
2623
|
const updated = { ...current, [normalized]: timestamp };
|
|
3269
2624
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
|
|
@@ -3271,14 +2626,14 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3271
2626
|
},
|
|
3272
2627
|
setProvidersOnCooldown: (value) => {
|
|
3273
2628
|
const normalized = value.map((entry) => ({
|
|
3274
|
-
baseUrl:
|
|
2629
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3275
2630
|
timestamp: entry.timestamp
|
|
3276
2631
|
}));
|
|
3277
2632
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
|
|
3278
2633
|
set({ providersOnCooldown: normalized });
|
|
3279
2634
|
},
|
|
3280
2635
|
addProviderOnCooldown: (baseUrl, timestamp) => {
|
|
3281
|
-
const normalized =
|
|
2636
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3282
2637
|
const current = get().providersOnCooldown;
|
|
3283
2638
|
if (!current.some((entry) => entry.baseUrl === normalized)) {
|
|
3284
2639
|
const updated = [...current, { baseUrl: normalized, timestamp }];
|
|
@@ -3287,7 +2642,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3287
2642
|
}
|
|
3288
2643
|
},
|
|
3289
2644
|
removeProviderFromCooldown: (baseUrl) => {
|
|
3290
|
-
const normalized =
|
|
2645
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3291
2646
|
const current = get().providersOnCooldown;
|
|
3292
2647
|
const updated = current.filter((entry) => entry.baseUrl !== normalized);
|
|
3293
2648
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
@@ -3358,40 +2713,40 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3358
2713
|
]);
|
|
3359
2714
|
const modelsFromAllProviders = Object.fromEntries(
|
|
3360
2715
|
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
3361
|
-
|
|
2716
|
+
normalizeBaseUrl3(baseUrl),
|
|
3362
2717
|
models
|
|
3363
2718
|
])
|
|
3364
2719
|
);
|
|
3365
|
-
const baseUrlsList = rawBaseUrls.map((url) =>
|
|
2720
|
+
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl3(url));
|
|
3366
2721
|
const disabledProviders = rawDisabledProviders.map(
|
|
3367
|
-
(url) =>
|
|
2722
|
+
(url) => normalizeBaseUrl3(url)
|
|
3368
2723
|
);
|
|
3369
2724
|
const mintsFromAllProviders = Object.fromEntries(
|
|
3370
2725
|
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
3371
|
-
|
|
2726
|
+
normalizeBaseUrl3(baseUrl),
|
|
3372
2727
|
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
3373
2728
|
])
|
|
3374
2729
|
);
|
|
3375
2730
|
const infoFromAllProviders = Object.fromEntries(
|
|
3376
2731
|
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
3377
|
-
|
|
2732
|
+
normalizeBaseUrl3(baseUrl),
|
|
3378
2733
|
info
|
|
3379
2734
|
])
|
|
3380
2735
|
);
|
|
3381
2736
|
const lastModelsUpdate = Object.fromEntries(
|
|
3382
2737
|
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
3383
|
-
|
|
2738
|
+
normalizeBaseUrl3(baseUrl),
|
|
3384
2739
|
timestamp
|
|
3385
2740
|
])
|
|
3386
2741
|
);
|
|
3387
2742
|
const apiKeys = rawApiKeys.map((entry) => ({
|
|
3388
2743
|
...entry,
|
|
3389
|
-
baseUrl:
|
|
2744
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3390
2745
|
balance: entry.balance ?? 0,
|
|
3391
2746
|
lastUsed: entry.lastUsed ?? null
|
|
3392
2747
|
}));
|
|
3393
2748
|
const childKeys = rawChildKeys.map((entry) => ({
|
|
3394
|
-
parentBaseUrl:
|
|
2749
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
3395
2750
|
childKey: entry.childKey,
|
|
3396
2751
|
balance: entry.balance ?? 0,
|
|
3397
2752
|
balanceLimit: entry.balanceLimit,
|
|
@@ -3400,9 +2755,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3400
2755
|
}));
|
|
3401
2756
|
const xcashuTokens = Object.fromEntries(
|
|
3402
2757
|
Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
|
|
3403
|
-
|
|
2758
|
+
normalizeBaseUrl3(baseUrl),
|
|
3404
2759
|
tokens.map((entry) => ({
|
|
3405
|
-
baseUrl:
|
|
2760
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3406
2761
|
token: entry.token,
|
|
3407
2762
|
createdAt: entry.createdAt ?? Date.now(),
|
|
3408
2763
|
tryCount: entry.tryCount ?? 0
|
|
@@ -3423,16 +2778,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3423
2778
|
lastUsed: entry.lastUsed ?? null
|
|
3424
2779
|
}));
|
|
3425
2780
|
const failedProviders = rawFailedProviders.map(
|
|
3426
|
-
(url) =>
|
|
2781
|
+
(url) => normalizeBaseUrl3(url)
|
|
3427
2782
|
);
|
|
3428
2783
|
const lastFailed = Object.fromEntries(
|
|
3429
2784
|
Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
|
|
3430
|
-
|
|
2785
|
+
normalizeBaseUrl3(baseUrl),
|
|
3431
2786
|
timestamp
|
|
3432
2787
|
])
|
|
3433
2788
|
);
|
|
3434
2789
|
const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
|
|
3435
|
-
baseUrl:
|
|
2790
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3436
2791
|
timestamp: entry.timestamp
|
|
3437
2792
|
}));
|
|
3438
2793
|
store.setState({
|
|
@@ -3474,31 +2829,13 @@ var isBrowser2 = () => {
|
|
|
3474
2829
|
return false;
|
|
3475
2830
|
}
|
|
3476
2831
|
};
|
|
3477
|
-
var isNode = () => {
|
|
3478
|
-
try {
|
|
3479
|
-
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
3480
|
-
} catch {
|
|
3481
|
-
return false;
|
|
3482
|
-
}
|
|
3483
|
-
};
|
|
3484
2832
|
var defaultDriver = null;
|
|
3485
|
-
var isBun3 = () => {
|
|
3486
|
-
return typeof process.versions.bun !== "undefined";
|
|
3487
|
-
};
|
|
3488
2833
|
var getDefaultSdkDriver = () => {
|
|
3489
2834
|
if (defaultDriver) return defaultDriver;
|
|
3490
2835
|
if (isBrowser2()) {
|
|
3491
2836
|
defaultDriver = localStorageDriver;
|
|
3492
2837
|
return defaultDriver;
|
|
3493
2838
|
}
|
|
3494
|
-
if (isBun3()) {
|
|
3495
|
-
defaultDriver = createMemoryDriver();
|
|
3496
|
-
return defaultDriver;
|
|
3497
|
-
}
|
|
3498
|
-
if (isNode()) {
|
|
3499
|
-
defaultDriver = createSqliteDriver();
|
|
3500
|
-
return defaultDriver;
|
|
3501
|
-
}
|
|
3502
2839
|
defaultDriver = createMemoryDriver();
|
|
3503
2840
|
return defaultDriver;
|
|
3504
2841
|
};
|
|
@@ -3519,49 +2856,209 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
3519
2856
|
});
|
|
3520
2857
|
return defaultUsageTrackingDriver;
|
|
3521
2858
|
}
|
|
3522
|
-
if (isBun3()) {
|
|
3523
|
-
defaultUsageTrackingDriver = createBunSqliteUsageTrackingDriver();
|
|
3524
|
-
return defaultUsageTrackingDriver;
|
|
3525
|
-
}
|
|
3526
|
-
if (isNode()) {
|
|
3527
|
-
defaultUsageTrackingDriver = createSqliteUsageTrackingDriver({
|
|
3528
|
-
legacyStorageDriver: storageDriver
|
|
3529
|
-
});
|
|
3530
|
-
return defaultUsageTrackingDriver;
|
|
3531
|
-
}
|
|
3532
2859
|
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
3533
2860
|
return defaultUsageTrackingDriver;
|
|
3534
2861
|
};
|
|
2862
|
+
|
|
2863
|
+
// client/usage.ts
|
|
2864
|
+
var numOrUndef = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2865
|
+
function extractCostBreakdown(costObj) {
|
|
2866
|
+
if (!costObj || typeof costObj !== "object") return {};
|
|
2867
|
+
return {
|
|
2868
|
+
baseMsats: numOrUndef(costObj.base_msats),
|
|
2869
|
+
inputMsats: numOrUndef(costObj.input_msats),
|
|
2870
|
+
outputMsats: numOrUndef(costObj.output_msats),
|
|
2871
|
+
totalMsats: numOrUndef(costObj.total_msats),
|
|
2872
|
+
totalUsd: numOrUndef(costObj.total_usd),
|
|
2873
|
+
cacheReadInputTokens: numOrUndef(costObj.cache_read_input_tokens),
|
|
2874
|
+
cacheCreationInputTokens: numOrUndef(costObj.cache_creation_input_tokens),
|
|
2875
|
+
cacheReadMsats: numOrUndef(costObj.cache_read_msats),
|
|
2876
|
+
cacheCreationMsats: numOrUndef(costObj.cache_creation_msats),
|
|
2877
|
+
remainingBalanceMsats: numOrUndef(costObj.remaining_balance_msats)
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
function extractUsageFromResponseBody(body, fallbackSatsCost = 0) {
|
|
2881
|
+
if (!body || typeof body !== "object") return null;
|
|
2882
|
+
const usage = body.usage;
|
|
2883
|
+
if (!usage || typeof usage !== "object") return null;
|
|
2884
|
+
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
2885
|
+
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
2886
|
+
const totalTokens = Number(usage.total_tokens ?? 0);
|
|
2887
|
+
const costValue = usage.cost;
|
|
2888
|
+
let cost = 0;
|
|
2889
|
+
let satsCost = fallbackSatsCost;
|
|
2890
|
+
let breakdown = {};
|
|
2891
|
+
if (typeof costValue === "number") {
|
|
2892
|
+
cost = costValue;
|
|
2893
|
+
} else if (costValue && typeof costValue === "object") {
|
|
2894
|
+
const costObj = costValue;
|
|
2895
|
+
const totalUsd = costObj.total_usd;
|
|
2896
|
+
const totalMsats = costObj.total_msats;
|
|
2897
|
+
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
2898
|
+
if (typeof totalMsats === "number") {
|
|
2899
|
+
satsCost = totalMsats / 1e3;
|
|
2900
|
+
}
|
|
2901
|
+
breakdown = extractCostBreakdown(costObj);
|
|
2902
|
+
}
|
|
2903
|
+
const provider = typeof body.provider === "string" ? body.provider : void 0;
|
|
2904
|
+
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
2905
|
+
return null;
|
|
2906
|
+
}
|
|
2907
|
+
return {
|
|
2908
|
+
promptTokens,
|
|
2909
|
+
completionTokens,
|
|
2910
|
+
totalTokens,
|
|
2911
|
+
cost,
|
|
2912
|
+
satsCost,
|
|
2913
|
+
provider,
|
|
2914
|
+
...breakdown
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
function extractResponseId(body) {
|
|
2918
|
+
if (!body || typeof body !== "object") return void 0;
|
|
2919
|
+
const id = body.id;
|
|
2920
|
+
if (typeof id !== "string") return void 0;
|
|
2921
|
+
const trimmed = id.trim();
|
|
2922
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
2923
|
+
}
|
|
2924
|
+
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
2925
|
+
if (!parsed || typeof parsed !== "object") {
|
|
2926
|
+
return null;
|
|
2927
|
+
}
|
|
2928
|
+
const provider = typeof parsed.provider === "string" ? parsed.provider : void 0;
|
|
2929
|
+
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
2930
|
+
const costObj = parsed.cost;
|
|
2931
|
+
const msats2 = costObj.total_msats ?? 0;
|
|
2932
|
+
const cost2 = costObj.total_usd ?? 0;
|
|
2933
|
+
if (msats2 === 0 && cost2 === 0) return null;
|
|
2934
|
+
return {
|
|
2935
|
+
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
2936
|
+
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
2937
|
+
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
2938
|
+
cost: Number(cost2),
|
|
2939
|
+
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost,
|
|
2940
|
+
provider,
|
|
2941
|
+
...extractCostBreakdown(costObj)
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
if (!parsed.usage) {
|
|
2945
|
+
return null;
|
|
2946
|
+
}
|
|
2947
|
+
const usage = parsed.usage;
|
|
2948
|
+
const usageCost = usage.cost;
|
|
2949
|
+
let cost = 0;
|
|
2950
|
+
let msats = 0;
|
|
2951
|
+
let breakdown = {};
|
|
2952
|
+
if (typeof usageCost === "number") {
|
|
2953
|
+
cost = usageCost;
|
|
2954
|
+
} else if (usageCost && typeof usageCost === "object") {
|
|
2955
|
+
cost = usageCost.total_usd ?? 0;
|
|
2956
|
+
msats = usageCost.total_msats ?? 0;
|
|
2957
|
+
breakdown = extractCostBreakdown(usageCost);
|
|
2958
|
+
}
|
|
2959
|
+
const routstrCost = parsed.metadata?.routstr?.cost;
|
|
2960
|
+
if (routstrCost && typeof routstrCost === "object") {
|
|
2961
|
+
breakdown = { ...extractCostBreakdown(routstrCost), ...breakdown };
|
|
2962
|
+
}
|
|
2963
|
+
if (cost === 0) {
|
|
2964
|
+
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
2965
|
+
}
|
|
2966
|
+
if (msats === 0) {
|
|
2967
|
+
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
2968
|
+
}
|
|
2969
|
+
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
2970
|
+
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
2971
|
+
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
2972
|
+
const result = {
|
|
2973
|
+
promptTokens,
|
|
2974
|
+
completionTokens,
|
|
2975
|
+
totalTokens,
|
|
2976
|
+
cost: Number(cost ?? 0),
|
|
2977
|
+
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost,
|
|
2978
|
+
provider,
|
|
2979
|
+
...breakdown
|
|
2980
|
+
};
|
|
2981
|
+
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
2982
|
+
return null;
|
|
2983
|
+
}
|
|
2984
|
+
return result;
|
|
2985
|
+
}
|
|
2986
|
+
function toUsageStats(usage) {
|
|
2987
|
+
if (!usage) return void 0;
|
|
2988
|
+
return {
|
|
2989
|
+
total_tokens: usage.totalTokens,
|
|
2990
|
+
prompt_tokens: usage.promptTokens,
|
|
2991
|
+
completion_tokens: usage.completionTokens,
|
|
2992
|
+
cost: usage.cost,
|
|
2993
|
+
sats_cost: usage.satsCost
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
3535
2996
|
function mergeUsage(previous, next) {
|
|
3536
2997
|
if (!previous) return next;
|
|
2998
|
+
const pickNum = (n, p) => typeof n === "number" && n > 0 ? n : p ?? n;
|
|
3537
2999
|
return {
|
|
3538
3000
|
promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
|
|
3539
3001
|
completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
|
|
3540
3002
|
totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
|
|
3541
3003
|
cost: next.cost > 0 ? next.cost : previous.cost,
|
|
3542
|
-
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3004
|
+
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost,
|
|
3005
|
+
provider: next.provider ?? previous.provider,
|
|
3006
|
+
baseMsats: pickNum(next.baseMsats, previous.baseMsats),
|
|
3007
|
+
inputMsats: pickNum(next.inputMsats, previous.inputMsats),
|
|
3008
|
+
outputMsats: pickNum(next.outputMsats, previous.outputMsats),
|
|
3009
|
+
totalMsats: pickNum(next.totalMsats, previous.totalMsats),
|
|
3010
|
+
totalUsd: pickNum(next.totalUsd, previous.totalUsd),
|
|
3011
|
+
cacheReadInputTokens: pickNum(
|
|
3012
|
+
next.cacheReadInputTokens,
|
|
3013
|
+
previous.cacheReadInputTokens
|
|
3014
|
+
),
|
|
3015
|
+
cacheCreationInputTokens: pickNum(
|
|
3016
|
+
next.cacheCreationInputTokens,
|
|
3017
|
+
previous.cacheCreationInputTokens
|
|
3018
|
+
),
|
|
3019
|
+
cacheReadMsats: pickNum(next.cacheReadMsats, previous.cacheReadMsats),
|
|
3020
|
+
cacheCreationMsats: pickNum(
|
|
3021
|
+
next.cacheCreationMsats,
|
|
3022
|
+
previous.cacheCreationMsats
|
|
3023
|
+
),
|
|
3024
|
+
remainingBalanceMsats: pickNum(
|
|
3025
|
+
next.remainingBalanceMsats,
|
|
3026
|
+
previous.remainingBalanceMsats
|
|
3027
|
+
)
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
function hasUsageChanged(previous, next) {
|
|
3546
3031
|
if (!previous) return true;
|
|
3547
|
-
return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
|
|
3032
|
+
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;
|
|
3033
|
+
}
|
|
3034
|
+
function isInspectionComplete(responseIdCaptured, usage) {
|
|
3035
|
+
return responseIdCaptured && !!usage && usage.totalTokens > 0 && typeof usage.totalMsats === "number" && !!usage.provider;
|
|
3548
3036
|
}
|
|
3549
|
-
async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
3037
|
+
async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
|
|
3550
3038
|
const reader = stream.getReader();
|
|
3551
3039
|
const decoder = new TextDecoder("utf-8");
|
|
3552
3040
|
let buffer = "";
|
|
3553
3041
|
let capturedUsage = null;
|
|
3554
3042
|
let capturedResponseId;
|
|
3555
3043
|
let responseIdCaptured = false;
|
|
3044
|
+
let rawChunkSequence = 0;
|
|
3556
3045
|
const inspectDataPayload = (jsonText) => {
|
|
3557
|
-
|
|
3046
|
+
const trimmed = jsonText.trim();
|
|
3047
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
3048
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
3049
|
+
return;
|
|
3050
|
+
}
|
|
3051
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
3052
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
3558
3053
|
return;
|
|
3559
3054
|
}
|
|
3560
|
-
const trimmed = jsonText.trim();
|
|
3561
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
3562
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
3563
3055
|
try {
|
|
3564
3056
|
const data = JSON.parse(trimmed);
|
|
3057
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
3058
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
3059
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3565
3062
|
if (!responseIdCaptured) {
|
|
3566
3063
|
const responseId = data?.id;
|
|
3567
3064
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -3572,19 +3069,21 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
3572
3069
|
}
|
|
3573
3070
|
const usage = extractUsageFromSSEJson(data);
|
|
3574
3071
|
if (usage) {
|
|
3072
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
3575
3073
|
const merged = mergeUsage(capturedUsage, usage);
|
|
3576
3074
|
if (hasUsageChanged(capturedUsage, merged)) {
|
|
3577
3075
|
capturedUsage = merged;
|
|
3076
|
+
console.log("[routstr:sse] \u2192 merged (changed):", merged);
|
|
3578
3077
|
onUsage(merged);
|
|
3078
|
+
} else {
|
|
3079
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
3579
3080
|
}
|
|
3580
3081
|
}
|
|
3581
3082
|
} catch {
|
|
3083
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
3582
3084
|
}
|
|
3583
3085
|
};
|
|
3584
3086
|
const inspectEventBlock = (eventBlock) => {
|
|
3585
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3586
|
-
return;
|
|
3587
|
-
}
|
|
3588
3087
|
const lines = eventBlock.split(/\r?\n/);
|
|
3589
3088
|
const dataParts = [];
|
|
3590
3089
|
for (const line of lines) {
|
|
@@ -3613,7 +3112,9 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
3613
3112
|
const { value, done } = await reader.read();
|
|
3614
3113
|
if (done) break;
|
|
3615
3114
|
if (value && value.byteLength > 0) {
|
|
3616
|
-
|
|
3115
|
+
const text = decoder.decode(value, { stream: true });
|
|
3116
|
+
void options?.onRawChunk?.(value, rawChunkSequence++, text);
|
|
3117
|
+
buffer += text;
|
|
3617
3118
|
drainBufferedEvents();
|
|
3618
3119
|
}
|
|
3619
3120
|
}
|
|
@@ -3642,14 +3143,22 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3642
3143
|
let capturedUsage = null;
|
|
3643
3144
|
let responseIdCaptured = false;
|
|
3644
3145
|
const inspectDataPayload = (jsonText) => {
|
|
3645
|
-
|
|
3146
|
+
const trimmed = jsonText.trim();
|
|
3147
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
3148
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
3149
|
+
return;
|
|
3150
|
+
}
|
|
3151
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
3152
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
3646
3153
|
return;
|
|
3647
3154
|
}
|
|
3648
|
-
const trimmed = jsonText.trim();
|
|
3649
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
3650
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
3651
3155
|
try {
|
|
3652
3156
|
const data = JSON.parse(trimmed);
|
|
3157
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
3158
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
3159
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3653
3162
|
if (!responseIdCaptured) {
|
|
3654
3163
|
const responseId = data?.id;
|
|
3655
3164
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -3659,19 +3168,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3659
3168
|
}
|
|
3660
3169
|
const usage = extractUsageFromSSEJson(data);
|
|
3661
3170
|
if (usage) {
|
|
3171
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
3662
3172
|
const mergedUsage = mergeUsage(capturedUsage, usage);
|
|
3663
3173
|
if (hasUsageChanged(capturedUsage, mergedUsage)) {
|
|
3664
3174
|
capturedUsage = mergedUsage;
|
|
3175
|
+
console.log("[routstr:sse] \u2192 merged (changed):", mergedUsage);
|
|
3665
3176
|
onUsage(mergedUsage);
|
|
3177
|
+
} else {
|
|
3178
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
3666
3179
|
}
|
|
3667
3180
|
}
|
|
3668
3181
|
} catch {
|
|
3182
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
3669
3183
|
}
|
|
3670
3184
|
};
|
|
3671
3185
|
const inspectEventBlock = (eventBlock) => {
|
|
3672
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3673
|
-
return;
|
|
3674
|
-
}
|
|
3675
3186
|
const lines = eventBlock.split(/\r?\n/);
|
|
3676
3187
|
const dataParts = [];
|
|
3677
3188
|
for (const line of lines) {
|
|
@@ -3744,11 +3255,11 @@ var RoutstrClient = class {
|
|
|
3744
3255
|
this.balanceManager,
|
|
3745
3256
|
this.logger
|
|
3746
3257
|
);
|
|
3747
|
-
this.streamProcessor = new StreamProcessor();
|
|
3748
3258
|
this.alertLevel = alertLevel;
|
|
3749
3259
|
this.mode = mode;
|
|
3750
3260
|
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
3751
3261
|
this.sdkStore = options.sdkStore;
|
|
3262
|
+
this.requestResponseLogSink = options.requestResponseLogSink;
|
|
3752
3263
|
this.providerManager = options.providerManager ?? new ProviderManager(providerRegistry, this.sdkStore, this.logger);
|
|
3753
3264
|
}
|
|
3754
3265
|
walletAdapter;
|
|
@@ -3756,7 +3267,6 @@ var RoutstrClient = class {
|
|
|
3756
3267
|
providerRegistry;
|
|
3757
3268
|
cashuSpender;
|
|
3758
3269
|
balanceManager;
|
|
3759
|
-
streamProcessor;
|
|
3760
3270
|
providerManager;
|
|
3761
3271
|
alertLevel;
|
|
3762
3272
|
mode;
|
|
@@ -3764,6 +3274,7 @@ var RoutstrClient = class {
|
|
|
3764
3274
|
usageTrackingDriver;
|
|
3765
3275
|
sdkStore;
|
|
3766
3276
|
logger;
|
|
3277
|
+
requestResponseLogSink;
|
|
3767
3278
|
/**
|
|
3768
3279
|
* Get the current client mode
|
|
3769
3280
|
*/
|
|
@@ -3954,6 +3465,7 @@ var RoutstrClient = class {
|
|
|
3954
3465
|
let usagePromise = Promise.resolve({});
|
|
3955
3466
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
3956
3467
|
const [clientStream, inspectStream] = response.body.tee();
|
|
3468
|
+
const requestResponseLogId = response.requestResponseLogId;
|
|
3957
3469
|
processedResponse = new Response(clientStream, {
|
|
3958
3470
|
status: response.status,
|
|
3959
3471
|
statusText: response.statusText,
|
|
@@ -3961,6 +3473,7 @@ var RoutstrClient = class {
|
|
|
3961
3473
|
});
|
|
3962
3474
|
processedResponse.baseUrl = response.baseUrl;
|
|
3963
3475
|
processedResponse.token = response.token;
|
|
3476
|
+
processedResponse.requestResponseLogId = requestResponseLogId;
|
|
3964
3477
|
usagePromise = inspectSSEWebStream(
|
|
3965
3478
|
inspectStream,
|
|
3966
3479
|
(usage) => {
|
|
@@ -3970,8 +3483,23 @@ var RoutstrClient = class {
|
|
|
3970
3483
|
(responseId) => {
|
|
3971
3484
|
capturedResponseId = responseId;
|
|
3972
3485
|
processedResponse.requestId = responseId;
|
|
3486
|
+
},
|
|
3487
|
+
{
|
|
3488
|
+
onRawChunk: (_chunk, sequence, text) => {
|
|
3489
|
+
void this.requestResponseLogSink?.logResponseChunk?.(
|
|
3490
|
+
requestResponseLogId,
|
|
3491
|
+
sequence,
|
|
3492
|
+
text
|
|
3493
|
+
);
|
|
3494
|
+
}
|
|
3973
3495
|
}
|
|
3974
|
-
)
|
|
3496
|
+
).then(async (result) => {
|
|
3497
|
+
await this.requestResponseLogSink?.logResponseEnd?.(requestResponseLogId);
|
|
3498
|
+
return result;
|
|
3499
|
+
}).catch(async (error) => {
|
|
3500
|
+
await this.requestResponseLogSink?.logResponseError?.(requestResponseLogId, error);
|
|
3501
|
+
throw error;
|
|
3502
|
+
});
|
|
3975
3503
|
processedResponse.usagePromise = usagePromise;
|
|
3976
3504
|
}
|
|
3977
3505
|
return {
|
|
@@ -3998,153 +3526,6 @@ var RoutstrClient = class {
|
|
|
3998
3526
|
}
|
|
3999
3527
|
return void 0;
|
|
4000
3528
|
}
|
|
4001
|
-
/**
|
|
4002
|
-
* Fetch AI response with streaming
|
|
4003
|
-
*/
|
|
4004
|
-
async fetchAIResponse(options, callbacks) {
|
|
4005
|
-
const {
|
|
4006
|
-
messageHistory,
|
|
4007
|
-
selectedModel,
|
|
4008
|
-
baseUrl,
|
|
4009
|
-
mintUrl,
|
|
4010
|
-
balance,
|
|
4011
|
-
transactionHistory,
|
|
4012
|
-
maxTokens,
|
|
4013
|
-
headers
|
|
4014
|
-
} = options;
|
|
4015
|
-
const apiMessages = await this._convertMessages(messageHistory);
|
|
4016
|
-
const requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
4017
|
-
selectedModel,
|
|
4018
|
-
apiMessages,
|
|
4019
|
-
maxTokens
|
|
4020
|
-
);
|
|
4021
|
-
try {
|
|
4022
|
-
await this._checkBalance();
|
|
4023
|
-
callbacks.onPaymentProcessing?.(true);
|
|
4024
|
-
const spendResult = await this._spendToken({
|
|
4025
|
-
mintUrl,
|
|
4026
|
-
amount: requiredSats,
|
|
4027
|
-
baseUrl
|
|
4028
|
-
});
|
|
4029
|
-
let token = spendResult.token;
|
|
4030
|
-
let tokenBalance = spendResult.tokenBalance;
|
|
4031
|
-
let tokenBalanceUnit = spendResult.tokenBalanceUnit;
|
|
4032
|
-
let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
4033
|
-
let initialTokenBalanceUnknown = spendResult.tokenBalanceUnknown;
|
|
4034
|
-
callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
|
|
4035
|
-
const baseHeaders = this._buildBaseHeaders(headers);
|
|
4036
|
-
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
4037
|
-
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
4038
|
-
const providerVersion = providerInfo?.version ?? "";
|
|
4039
|
-
let modelIdForRequest = selectedModel.id;
|
|
4040
|
-
if (/^0\.1\./.test(providerVersion)) {
|
|
4041
|
-
const newModel = await this.providerManager.getModelForProvider(
|
|
4042
|
-
baseUrl,
|
|
4043
|
-
selectedModel.id
|
|
4044
|
-
);
|
|
4045
|
-
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
4046
|
-
}
|
|
4047
|
-
const body = {
|
|
4048
|
-
model: modelIdForRequest,
|
|
4049
|
-
messages: apiMessages,
|
|
4050
|
-
stream: true
|
|
4051
|
-
};
|
|
4052
|
-
if (maxTokens !== void 0) {
|
|
4053
|
-
body.max_tokens = maxTokens;
|
|
4054
|
-
}
|
|
4055
|
-
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
4056
|
-
body.tools = [{ type: "web_search" }];
|
|
4057
|
-
}
|
|
4058
|
-
const response = await this._makeRequest({
|
|
4059
|
-
path: "/v1/chat/completions",
|
|
4060
|
-
method: "POST",
|
|
4061
|
-
body,
|
|
4062
|
-
selectedModel,
|
|
4063
|
-
baseUrl,
|
|
4064
|
-
mintUrl,
|
|
4065
|
-
token,
|
|
4066
|
-
requiredSats,
|
|
4067
|
-
maxTokens,
|
|
4068
|
-
headers: requestHeaders,
|
|
4069
|
-
baseHeaders
|
|
4070
|
-
});
|
|
4071
|
-
if (!response.body) {
|
|
4072
|
-
throw new Error("Response body is not available");
|
|
4073
|
-
}
|
|
4074
|
-
if (response.status === 200) {
|
|
4075
|
-
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
4076
|
-
const responseToken = response.token || token;
|
|
4077
|
-
if (baseUrlUsed !== baseUrl || responseToken !== token) {
|
|
4078
|
-
token = responseToken;
|
|
4079
|
-
if (typeof response.initialTokenBalanceInSats === "number") {
|
|
4080
|
-
tokenBalanceInSats = response.initialTokenBalanceInSats;
|
|
4081
|
-
initialTokenBalanceUnknown = Boolean(
|
|
4082
|
-
response.initialTokenBalanceUnknown
|
|
4083
|
-
);
|
|
4084
|
-
} else {
|
|
4085
|
-
initialTokenBalanceUnknown = true;
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4088
|
-
const streamingResult = await this.streamProcessor.process(
|
|
4089
|
-
response,
|
|
4090
|
-
{
|
|
4091
|
-
onContent: callbacks.onStreamingUpdate,
|
|
4092
|
-
onThinking: callbacks.onThinkingUpdate
|
|
4093
|
-
},
|
|
4094
|
-
selectedModel.id
|
|
4095
|
-
);
|
|
4096
|
-
if (streamingResult.finish_reason === "content_filter") {
|
|
4097
|
-
callbacks.onMessageAppend({
|
|
4098
|
-
role: "assistant",
|
|
4099
|
-
content: "Your request was denied due to content filtering."
|
|
4100
|
-
});
|
|
4101
|
-
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
4102
|
-
const message = await this._createAssistantMessage(streamingResult);
|
|
4103
|
-
callbacks.onMessageAppend(message);
|
|
4104
|
-
} else {
|
|
4105
|
-
callbacks.onMessageAppend({
|
|
4106
|
-
role: "system",
|
|
4107
|
-
content: "The provider did not respond to this request."
|
|
4108
|
-
});
|
|
4109
|
-
}
|
|
4110
|
-
callbacks.onStreamingUpdate("");
|
|
4111
|
-
callbacks.onThinkingUpdate("");
|
|
4112
|
-
const isApikeysEstimate = this.mode === "apikeys";
|
|
4113
|
-
let satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
4114
|
-
token,
|
|
4115
|
-
baseUrl: baseUrlUsed,
|
|
4116
|
-
mintUrl,
|
|
4117
|
-
initialTokenBalance: tokenBalanceInSats,
|
|
4118
|
-
initialTokenBalanceUnknown,
|
|
4119
|
-
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
4120
|
-
response,
|
|
4121
|
-
modelId: selectedModel.id,
|
|
4122
|
-
usage: streamingResult.usage ? {
|
|
4123
|
-
promptTokens: Number(streamingResult.usage.prompt_tokens ?? 0),
|
|
4124
|
-
completionTokens: Number(
|
|
4125
|
-
streamingResult.usage.completion_tokens ?? 0
|
|
4126
|
-
),
|
|
4127
|
-
totalTokens: Number(streamingResult.usage.total_tokens ?? 0),
|
|
4128
|
-
cost: Number(streamingResult.usage.cost ?? 0),
|
|
4129
|
-
satsCost: Number(streamingResult.usage.sats_cost ?? 0)
|
|
4130
|
-
} : void 0,
|
|
4131
|
-
requestId: streamingResult.responseId
|
|
4132
|
-
});
|
|
4133
|
-
const estimatedCosts = this._getEstimatedCosts(
|
|
4134
|
-
selectedModel,
|
|
4135
|
-
streamingResult
|
|
4136
|
-
);
|
|
4137
|
-
const onLastMessageSatsUpdate = callbacks.onLastMessageSatsUpdate;
|
|
4138
|
-
onLastMessageSatsUpdate?.(satsSpent, estimatedCosts);
|
|
4139
|
-
} else {
|
|
4140
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
4141
|
-
}
|
|
4142
|
-
} catch (error) {
|
|
4143
|
-
this._handleError(error, callbacks);
|
|
4144
|
-
} finally {
|
|
4145
|
-
callbacks.onPaymentProcessing?.(false);
|
|
4146
|
-
}
|
|
4147
|
-
}
|
|
4148
3529
|
/**
|
|
4149
3530
|
* Make the API request with failover support
|
|
4150
3531
|
*/
|
|
@@ -4152,16 +3533,30 @@ var RoutstrClient = class {
|
|
|
4152
3533
|
const { path, method, body, baseUrl, token, headers } = params;
|
|
4153
3534
|
try {
|
|
4154
3535
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
3536
|
+
const requestBodyText = body === void 0 || method === "GET" ? void 0 : JSON.stringify(body);
|
|
3537
|
+
const requestLogId = await this.requestResponseLogSink?.logRequest?.({
|
|
3538
|
+
method,
|
|
3539
|
+
url,
|
|
3540
|
+
path,
|
|
3541
|
+
baseUrl,
|
|
3542
|
+
headers,
|
|
3543
|
+
body,
|
|
3544
|
+
rawBody: requestBodyText
|
|
3545
|
+
});
|
|
4155
3546
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
4156
3547
|
const response = await fetch(url, {
|
|
4157
3548
|
method,
|
|
4158
3549
|
headers,
|
|
4159
|
-
body:
|
|
3550
|
+
body: requestBodyText
|
|
4160
3551
|
});
|
|
4161
3552
|
if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
|
|
4162
3553
|
response.baseUrl = baseUrl;
|
|
4163
3554
|
response.token = token;
|
|
3555
|
+
response.requestResponseLogId = requestLogId;
|
|
3556
|
+
await this.requestResponseLogSink?.logResponseStart?.(requestLogId, response);
|
|
3557
|
+
const contentType = response.headers.get("content-type") || "";
|
|
4164
3558
|
if (!response.ok) {
|
|
3559
|
+
void this.requestResponseLogSink?.logResponseBody?.(requestLogId, response.clone());
|
|
4165
3560
|
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
4166
3561
|
let bodyText;
|
|
4167
3562
|
try {
|
|
@@ -4179,6 +3574,9 @@ var RoutstrClient = class {
|
|
|
4179
3574
|
params.retryCount ?? 0
|
|
4180
3575
|
);
|
|
4181
3576
|
}
|
|
3577
|
+
if (!contentType.includes("text/event-stream")) {
|
|
3578
|
+
void this.requestResponseLogSink?.logResponseBody?.(requestLogId, response.clone());
|
|
3579
|
+
}
|
|
4182
3580
|
return response;
|
|
4183
3581
|
} catch (error) {
|
|
4184
3582
|
if (isNetworkErrorMessage(error?.message || "")) {
|
|
@@ -4669,264 +4067,520 @@ var RoutstrClient = class {
|
|
|
4669
4067
|
}
|
|
4670
4068
|
}
|
|
4671
4069
|
/**
|
|
4672
|
-
*
|
|
4070
|
+
* Check wallet balance and throw if insufficient
|
|
4673
4071
|
*/
|
|
4674
|
-
async
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
);
|
|
4072
|
+
async _checkBalance() {
|
|
4073
|
+
const balances = await this.walletAdapter.getBalances();
|
|
4074
|
+
const totalBalance = Object.values(balances).reduce((sum, v) => sum + v, 0);
|
|
4075
|
+
if (totalBalance <= 0) {
|
|
4076
|
+
throw new InsufficientBalanceError(1, 0);
|
|
4077
|
+
}
|
|
4681
4078
|
}
|
|
4682
4079
|
/**
|
|
4683
|
-
*
|
|
4080
|
+
* Spend a token using CashuSpender with standardized error handling
|
|
4684
4081
|
*/
|
|
4685
|
-
async
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4082
|
+
async _spendToken(params) {
|
|
4083
|
+
const { mintUrl, amount, baseUrl } = params;
|
|
4084
|
+
this._log(
|
|
4085
|
+
"DEBUG",
|
|
4086
|
+
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
4087
|
+
);
|
|
4088
|
+
if (this.mode === "apikeys") {
|
|
4089
|
+
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
4090
|
+
if (!parentApiKey) {
|
|
4091
|
+
this._log(
|
|
4092
|
+
"DEBUG",
|
|
4093
|
+
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
4094
|
+
);
|
|
4095
|
+
const spendResult2 = await this.cashuSpender.spend({
|
|
4096
|
+
mintUrl,
|
|
4097
|
+
amount: amount * TOPUP_MARGIN,
|
|
4098
|
+
baseUrl: "",
|
|
4099
|
+
reuseToken: false
|
|
4695
4100
|
});
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4101
|
+
if (!spendResult2.token) {
|
|
4102
|
+
this._log(
|
|
4103
|
+
"ERROR",
|
|
4104
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
4105
|
+
spendResult2.error
|
|
4106
|
+
);
|
|
4107
|
+
throw new Error(
|
|
4108
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
4109
|
+
);
|
|
4110
|
+
} else {
|
|
4111
|
+
this._log(
|
|
4112
|
+
"DEBUG",
|
|
4113
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
4114
|
+
);
|
|
4115
|
+
}
|
|
4116
|
+
this._log(
|
|
4117
|
+
"DEBUG",
|
|
4118
|
+
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
4119
|
+
);
|
|
4120
|
+
try {
|
|
4121
|
+
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
4122
|
+
} catch (error) {
|
|
4123
|
+
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
4124
|
+
const receiveResult = await this.cashuSpender.receiveToken(
|
|
4125
|
+
spendResult2.token
|
|
4126
|
+
);
|
|
4127
|
+
if (receiveResult.success) {
|
|
4128
|
+
this._log(
|
|
4129
|
+
"DEBUG",
|
|
4130
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
4131
|
+
);
|
|
4132
|
+
} else {
|
|
4133
|
+
this._log(
|
|
4134
|
+
"DEBUG",
|
|
4135
|
+
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
4136
|
+
);
|
|
4137
|
+
}
|
|
4138
|
+
this._log(
|
|
4139
|
+
"DEBUG",
|
|
4140
|
+
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
4141
|
+
);
|
|
4142
|
+
} else {
|
|
4143
|
+
throw error;
|
|
4702
4144
|
}
|
|
4703
|
-
}
|
|
4145
|
+
}
|
|
4146
|
+
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
4147
|
+
} else {
|
|
4148
|
+
this._log(
|
|
4149
|
+
"DEBUG",
|
|
4150
|
+
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
4151
|
+
);
|
|
4704
4152
|
}
|
|
4153
|
+
let tokenBalance = 0;
|
|
4154
|
+
let tokenBalanceUnit = "sat";
|
|
4155
|
+
let tokenBalanceUnknown = false;
|
|
4156
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
4157
|
+
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
4158
|
+
(d) => d.baseUrl === baseUrl
|
|
4159
|
+
);
|
|
4160
|
+
if (distributionForBaseUrl) {
|
|
4161
|
+
tokenBalance = distributionForBaseUrl.amount;
|
|
4162
|
+
}
|
|
4163
|
+
if (tokenBalance === 0 && parentApiKey) {
|
|
4164
|
+
try {
|
|
4165
|
+
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
4166
|
+
parentApiKey.key,
|
|
4167
|
+
baseUrl
|
|
4168
|
+
);
|
|
4169
|
+
tokenBalance = balanceInfo.amount;
|
|
4170
|
+
tokenBalanceUnit = balanceInfo.unit;
|
|
4171
|
+
tokenBalanceUnknown = Boolean(balanceInfo.balanceUnknown);
|
|
4172
|
+
} catch (e) {
|
|
4173
|
+
this._log("WARN", "Could not get initial API key balance:", e);
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
this._log(
|
|
4177
|
+
"DEBUG",
|
|
4178
|
+
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
4179
|
+
);
|
|
4705
4180
|
return {
|
|
4706
|
-
|
|
4707
|
-
|
|
4181
|
+
token: parentApiKey?.key ?? "",
|
|
4182
|
+
tokenBalance,
|
|
4183
|
+
tokenBalanceUnit,
|
|
4184
|
+
tokenBalanceUnknown
|
|
4708
4185
|
};
|
|
4709
4186
|
}
|
|
4187
|
+
this._log(
|
|
4188
|
+
"DEBUG",
|
|
4189
|
+
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
4190
|
+
);
|
|
4191
|
+
const spendResult = await this.cashuSpender.spend({
|
|
4192
|
+
mintUrl,
|
|
4193
|
+
amount,
|
|
4194
|
+
baseUrl: "",
|
|
4195
|
+
reuseToken: false
|
|
4196
|
+
});
|
|
4197
|
+
if (!spendResult.token) {
|
|
4198
|
+
this._log(
|
|
4199
|
+
"ERROR",
|
|
4200
|
+
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
4201
|
+
spendResult.error
|
|
4202
|
+
);
|
|
4203
|
+
} else {
|
|
4204
|
+
this._log(
|
|
4205
|
+
"DEBUG",
|
|
4206
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
4207
|
+
);
|
|
4208
|
+
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
4209
|
+
}
|
|
4710
4210
|
return {
|
|
4711
|
-
|
|
4712
|
-
|
|
4211
|
+
token: spendResult.token,
|
|
4212
|
+
tokenBalance: spendResult.balance,
|
|
4213
|
+
tokenBalanceUnit: spendResult.unit ?? "sat",
|
|
4214
|
+
tokenBalanceUnknown: false
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4217
|
+
/**
|
|
4218
|
+
* Build request headers with common defaults and dev mock controls
|
|
4219
|
+
*/
|
|
4220
|
+
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
4221
|
+
const headers = {
|
|
4222
|
+
...additionalHeaders,
|
|
4223
|
+
"Content-Type": "application/json"
|
|
4713
4224
|
};
|
|
4225
|
+
return headers;
|
|
4714
4226
|
}
|
|
4715
4227
|
/**
|
|
4716
|
-
*
|
|
4228
|
+
* Attach auth headers using the active client mode
|
|
4717
4229
|
*/
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
if (
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4230
|
+
_withAuthHeader(headers, token) {
|
|
4231
|
+
const nextHeaders = { ...headers };
|
|
4232
|
+
if (this.mode === "xcashu") {
|
|
4233
|
+
nextHeaders["X-Cashu"] = token;
|
|
4234
|
+
} else {
|
|
4235
|
+
nextHeaders["Authorization"] = `Bearer ${token}`;
|
|
4236
|
+
}
|
|
4237
|
+
return nextHeaders;
|
|
4238
|
+
}
|
|
4239
|
+
};
|
|
4240
|
+
|
|
4241
|
+
// client/StreamProcessor.ts
|
|
4242
|
+
var StreamProcessor = class {
|
|
4243
|
+
accumulatedContent = "";
|
|
4244
|
+
accumulatedThinking = "";
|
|
4245
|
+
accumulatedImages = [];
|
|
4246
|
+
isInThinking = false;
|
|
4247
|
+
isInContent = false;
|
|
4248
|
+
/**
|
|
4249
|
+
* Process a streaming response
|
|
4250
|
+
*/
|
|
4251
|
+
async process(response, callbacks, modelId) {
|
|
4252
|
+
if (!response.body) {
|
|
4253
|
+
throw new Error("Response body is not available");
|
|
4254
|
+
}
|
|
4255
|
+
const reader = response.body.getReader();
|
|
4256
|
+
const decoder = new TextDecoder("utf-8");
|
|
4257
|
+
let buffer = "";
|
|
4258
|
+
this.accumulatedContent = "";
|
|
4259
|
+
this.accumulatedThinking = "";
|
|
4260
|
+
this.accumulatedImages = [];
|
|
4261
|
+
this.isInThinking = false;
|
|
4262
|
+
this.isInContent = false;
|
|
4263
|
+
let usage;
|
|
4264
|
+
let model;
|
|
4265
|
+
let finish_reason;
|
|
4266
|
+
let citations;
|
|
4267
|
+
let annotations;
|
|
4268
|
+
let responseId;
|
|
4269
|
+
try {
|
|
4270
|
+
while (true) {
|
|
4271
|
+
const { done, value } = await reader.read();
|
|
4272
|
+
if (done) {
|
|
4273
|
+
break;
|
|
4274
|
+
}
|
|
4275
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
4276
|
+
buffer += chunk;
|
|
4277
|
+
const lines = buffer.split("\n");
|
|
4278
|
+
buffer = lines.pop() || "";
|
|
4279
|
+
for (const line of lines) {
|
|
4280
|
+
const parsed = this._parseLine(line);
|
|
4281
|
+
if (!parsed) continue;
|
|
4282
|
+
if (parsed.content) {
|
|
4283
|
+
this._handleContent(parsed.content, callbacks, modelId);
|
|
4284
|
+
}
|
|
4285
|
+
if (parsed.reasoning) {
|
|
4286
|
+
this._handleThinking(parsed.reasoning, callbacks);
|
|
4287
|
+
}
|
|
4288
|
+
if (parsed.usage) {
|
|
4289
|
+
usage = parsed.usage;
|
|
4290
|
+
}
|
|
4291
|
+
if (parsed.model) {
|
|
4292
|
+
model = parsed.model;
|
|
4293
|
+
}
|
|
4294
|
+
if (parsed.finish_reason) {
|
|
4295
|
+
finish_reason = parsed.finish_reason;
|
|
4296
|
+
}
|
|
4297
|
+
if (parsed.responseId) {
|
|
4298
|
+
responseId = parsed.responseId;
|
|
4299
|
+
}
|
|
4300
|
+
if (parsed.citations) {
|
|
4301
|
+
citations = parsed.citations;
|
|
4302
|
+
}
|
|
4303
|
+
if (parsed.annotations) {
|
|
4304
|
+
annotations = parsed.annotations;
|
|
4305
|
+
}
|
|
4306
|
+
if (parsed.images) {
|
|
4307
|
+
this._mergeImages(parsed.images);
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4724
4310
|
}
|
|
4311
|
+
} finally {
|
|
4312
|
+
reader.releaseLock();
|
|
4725
4313
|
}
|
|
4726
|
-
return
|
|
4314
|
+
return {
|
|
4315
|
+
content: this.accumulatedContent,
|
|
4316
|
+
thinking: this.accumulatedThinking || void 0,
|
|
4317
|
+
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
4318
|
+
usage,
|
|
4319
|
+
model,
|
|
4320
|
+
responseId,
|
|
4321
|
+
finish_reason,
|
|
4322
|
+
citations,
|
|
4323
|
+
annotations
|
|
4324
|
+
};
|
|
4727
4325
|
}
|
|
4728
4326
|
/**
|
|
4729
|
-
*
|
|
4327
|
+
* Parse a single SSE line
|
|
4730
4328
|
*/
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4329
|
+
_parseLine(line) {
|
|
4330
|
+
if (!line.trim()) return null;
|
|
4331
|
+
if (!line.startsWith("data: ")) {
|
|
4332
|
+
return null;
|
|
4333
|
+
}
|
|
4334
|
+
const jsonData = line.slice(6);
|
|
4335
|
+
if (jsonData === "[DONE]") {
|
|
4336
|
+
return null;
|
|
4337
|
+
}
|
|
4338
|
+
try {
|
|
4339
|
+
const parsed = JSON.parse(jsonData);
|
|
4340
|
+
const result = {};
|
|
4341
|
+
if (parsed.choices?.[0]?.delta?.content) {
|
|
4342
|
+
result.content = parsed.choices[0].delta.content;
|
|
4343
|
+
}
|
|
4344
|
+
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
4345
|
+
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
4346
|
+
}
|
|
4347
|
+
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
4348
|
+
if (extractedUsage) {
|
|
4349
|
+
result.usage = toUsageStats(extractedUsage);
|
|
4350
|
+
} else if (parsed.usage) {
|
|
4351
|
+
result.usage = {
|
|
4352
|
+
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
4353
|
+
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
4354
|
+
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4357
|
+
if (parsed.id) {
|
|
4358
|
+
result.responseId = parsed.id;
|
|
4359
|
+
}
|
|
4360
|
+
if (parsed.model) {
|
|
4361
|
+
result.model = parsed.model;
|
|
4362
|
+
}
|
|
4363
|
+
if (parsed.citations) {
|
|
4364
|
+
result.citations = parsed.citations;
|
|
4365
|
+
}
|
|
4366
|
+
if (parsed.annotations) {
|
|
4367
|
+
result.annotations = parsed.annotations;
|
|
4368
|
+
}
|
|
4369
|
+
if (parsed.choices?.[0]?.finish_reason) {
|
|
4370
|
+
result.finish_reason = parsed.choices[0].finish_reason;
|
|
4371
|
+
}
|
|
4372
|
+
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
4373
|
+
if (images && Array.isArray(images)) {
|
|
4374
|
+
result.images = images;
|
|
4375
|
+
}
|
|
4376
|
+
return result;
|
|
4377
|
+
} catch {
|
|
4378
|
+
return null;
|
|
4379
|
+
}
|
|
4734
4380
|
}
|
|
4735
4381
|
/**
|
|
4736
|
-
* Handle
|
|
4382
|
+
* Handle content delta with thinking support
|
|
4737
4383
|
*/
|
|
4738
|
-
|
|
4739
|
-
this.
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
this.
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
);
|
|
4747
|
-
callbacks.onMessageAppend({
|
|
4748
|
-
role: "system",
|
|
4749
|
-
content: "Uncaught Error: " + modifiedErrorMsg + (this.alertLevel === "max" ? " | " + error.stack : "")
|
|
4750
|
-
});
|
|
4384
|
+
_handleContent(content, callbacks, modelId) {
|
|
4385
|
+
if (this.isInThinking && !this.isInContent) {
|
|
4386
|
+
this.accumulatedThinking += "</thinking>";
|
|
4387
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
4388
|
+
this.isInThinking = false;
|
|
4389
|
+
this.isInContent = true;
|
|
4390
|
+
}
|
|
4391
|
+
if (modelId) {
|
|
4392
|
+
this._extractThinkingFromContent(content, callbacks);
|
|
4751
4393
|
} else {
|
|
4752
|
-
|
|
4753
|
-
role: "system",
|
|
4754
|
-
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
4755
|
-
});
|
|
4394
|
+
this.accumulatedContent += content;
|
|
4756
4395
|
}
|
|
4396
|
+
callbacks.onContent(this.accumulatedContent);
|
|
4757
4397
|
}
|
|
4758
4398
|
/**
|
|
4759
|
-
*
|
|
4399
|
+
* Handle thinking/reasoning content
|
|
4760
4400
|
*/
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
throw new InsufficientBalanceError(1, 0);
|
|
4401
|
+
_handleThinking(reasoning, callbacks) {
|
|
4402
|
+
if (!this.isInThinking) {
|
|
4403
|
+
this.accumulatedThinking += "<thinking> ";
|
|
4404
|
+
this.isInThinking = true;
|
|
4766
4405
|
}
|
|
4406
|
+
this.accumulatedThinking += reasoning;
|
|
4407
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
4767
4408
|
}
|
|
4768
4409
|
/**
|
|
4769
|
-
*
|
|
4410
|
+
* Extract thinking blocks from content (for models with inline thinking)
|
|
4770
4411
|
*/
|
|
4771
|
-
|
|
4772
|
-
const
|
|
4773
|
-
|
|
4774
|
-
"
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
4779
|
-
if (!parentApiKey) {
|
|
4780
|
-
this._log(
|
|
4781
|
-
"DEBUG",
|
|
4782
|
-
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
4783
|
-
);
|
|
4784
|
-
const spendResult2 = await this.cashuSpender.spend({
|
|
4785
|
-
mintUrl,
|
|
4786
|
-
amount: amount * TOPUP_MARGIN,
|
|
4787
|
-
baseUrl: "",
|
|
4788
|
-
reuseToken: false
|
|
4789
|
-
});
|
|
4790
|
-
if (!spendResult2.token) {
|
|
4791
|
-
this._log(
|
|
4792
|
-
"ERROR",
|
|
4793
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
4794
|
-
spendResult2.error
|
|
4795
|
-
);
|
|
4796
|
-
throw new Error(
|
|
4797
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
4798
|
-
);
|
|
4799
|
-
} else {
|
|
4800
|
-
this._log(
|
|
4801
|
-
"DEBUG",
|
|
4802
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
4803
|
-
);
|
|
4804
|
-
}
|
|
4805
|
-
this._log(
|
|
4806
|
-
"DEBUG",
|
|
4807
|
-
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
4808
|
-
);
|
|
4809
|
-
try {
|
|
4810
|
-
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
4811
|
-
} catch (error) {
|
|
4812
|
-
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
4813
|
-
const receiveResult = await this.cashuSpender.receiveToken(
|
|
4814
|
-
spendResult2.token
|
|
4815
|
-
);
|
|
4816
|
-
if (receiveResult.success) {
|
|
4817
|
-
this._log(
|
|
4818
|
-
"DEBUG",
|
|
4819
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
4820
|
-
);
|
|
4821
|
-
} else {
|
|
4822
|
-
this._log(
|
|
4823
|
-
"DEBUG",
|
|
4824
|
-
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
4825
|
-
);
|
|
4826
|
-
}
|
|
4827
|
-
this._log(
|
|
4828
|
-
"DEBUG",
|
|
4829
|
-
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
4830
|
-
);
|
|
4831
|
-
} else {
|
|
4832
|
-
throw error;
|
|
4833
|
-
}
|
|
4412
|
+
_extractThinkingFromContent(content, callbacks) {
|
|
4413
|
+
const parts = content.split(/(<thinking>|<\/thinking>)/);
|
|
4414
|
+
for (const part of parts) {
|
|
4415
|
+
if (part === "<thinking>") {
|
|
4416
|
+
this.isInThinking = true;
|
|
4417
|
+
if (!this.accumulatedThinking.includes("<thinking>")) {
|
|
4418
|
+
this.accumulatedThinking += "<thinking> ";
|
|
4834
4419
|
}
|
|
4835
|
-
|
|
4420
|
+
} else if (part === "</thinking>") {
|
|
4421
|
+
this.isInThinking = false;
|
|
4422
|
+
this.accumulatedThinking += "</thinking>";
|
|
4423
|
+
} else if (this.isInThinking) {
|
|
4424
|
+
this.accumulatedThinking += part;
|
|
4836
4425
|
} else {
|
|
4837
|
-
this.
|
|
4838
|
-
"DEBUG",
|
|
4839
|
-
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
4840
|
-
);
|
|
4841
|
-
}
|
|
4842
|
-
let tokenBalance = 0;
|
|
4843
|
-
let tokenBalanceUnit = "sat";
|
|
4844
|
-
let tokenBalanceUnknown = false;
|
|
4845
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
4846
|
-
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
4847
|
-
(d) => d.baseUrl === baseUrl
|
|
4848
|
-
);
|
|
4849
|
-
if (distributionForBaseUrl) {
|
|
4850
|
-
tokenBalance = distributionForBaseUrl.amount;
|
|
4426
|
+
this.accumulatedContent += part;
|
|
4851
4427
|
}
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
/**
|
|
4431
|
+
* Merge images into accumulated array, avoiding duplicates
|
|
4432
|
+
*/
|
|
4433
|
+
_mergeImages(newImages) {
|
|
4434
|
+
for (const img of newImages) {
|
|
4435
|
+
const newUrl = img.image_url?.url;
|
|
4436
|
+
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
4437
|
+
const existingUrl = existing.image_url?.url;
|
|
4438
|
+
if (newUrl && existingUrl) {
|
|
4439
|
+
return existingUrl === newUrl;
|
|
4863
4440
|
}
|
|
4441
|
+
if (img.index !== void 0 && existing.index !== void 0) {
|
|
4442
|
+
return existing.index === img.index;
|
|
4443
|
+
}
|
|
4444
|
+
return false;
|
|
4445
|
+
});
|
|
4446
|
+
if (existingIndex === -1) {
|
|
4447
|
+
this.accumulatedImages.push(img);
|
|
4448
|
+
} else {
|
|
4449
|
+
this.accumulatedImages[existingIndex] = img;
|
|
4864
4450
|
}
|
|
4865
|
-
this._log(
|
|
4866
|
-
"DEBUG",
|
|
4867
|
-
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
4868
|
-
);
|
|
4869
|
-
return {
|
|
4870
|
-
token: parentApiKey?.key ?? "",
|
|
4871
|
-
tokenBalance,
|
|
4872
|
-
tokenBalanceUnit,
|
|
4873
|
-
tokenBalanceUnknown
|
|
4874
|
-
};
|
|
4875
4451
|
}
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4452
|
+
}
|
|
4453
|
+
};
|
|
4454
|
+
|
|
4455
|
+
// client/fetchAIResponse.ts
|
|
4456
|
+
async function fetchAIResponse(options, callbacks, deps) {
|
|
4457
|
+
const {
|
|
4458
|
+
messageHistory,
|
|
4459
|
+
selectedModel,
|
|
4460
|
+
baseUrl,
|
|
4461
|
+
mintUrl,
|
|
4462
|
+
maxTokens,
|
|
4463
|
+
headers
|
|
4464
|
+
} = options;
|
|
4465
|
+
try {
|
|
4466
|
+
const apiMessages = await convertMessages(messageHistory);
|
|
4467
|
+
callbacks.onPaymentProcessing?.(true);
|
|
4468
|
+
callbacks.onTokenCreated?.(deps.getPendingCashuTokenAmount?.() ?? 0);
|
|
4469
|
+
const body = {
|
|
4470
|
+
model: selectedModel.id,
|
|
4471
|
+
messages: apiMessages,
|
|
4472
|
+
stream: true
|
|
4473
|
+
};
|
|
4474
|
+
if (maxTokens !== void 0) {
|
|
4475
|
+
body.max_tokens = maxTokens;
|
|
4476
|
+
}
|
|
4477
|
+
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
4478
|
+
body.tools = [{ type: "web_search" }];
|
|
4479
|
+
}
|
|
4480
|
+
const response = await deps.client.routeRequest({
|
|
4481
|
+
path: "/v1/chat/completions",
|
|
4482
|
+
method: "POST",
|
|
4483
|
+
body,
|
|
4484
|
+
headers,
|
|
4485
|
+
baseUrl,
|
|
4881
4486
|
mintUrl,
|
|
4882
|
-
|
|
4883
|
-
baseUrl: "",
|
|
4884
|
-
reuseToken: false
|
|
4487
|
+
modelId: selectedModel.id
|
|
4885
4488
|
});
|
|
4886
|
-
if (!
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4489
|
+
if (!response.body) {
|
|
4490
|
+
throw new Error("Response body is not available");
|
|
4491
|
+
}
|
|
4492
|
+
if (response.status !== 200) {
|
|
4493
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
4494
|
+
}
|
|
4495
|
+
const streamProcessor = new StreamProcessor();
|
|
4496
|
+
const streamingResult = await streamProcessor.process(
|
|
4497
|
+
response,
|
|
4498
|
+
{
|
|
4499
|
+
onContent: callbacks.onStreamingUpdate,
|
|
4500
|
+
onThinking: callbacks.onThinkingUpdate
|
|
4501
|
+
},
|
|
4502
|
+
selectedModel.id
|
|
4503
|
+
);
|
|
4504
|
+
if (streamingResult.finish_reason === "content_filter") {
|
|
4505
|
+
callbacks.onMessageAppend({
|
|
4506
|
+
role: "assistant",
|
|
4507
|
+
content: "Your request was denied due to content filtering."
|
|
4508
|
+
});
|
|
4509
|
+
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
4510
|
+
const message = await createAssistantMessage(streamingResult);
|
|
4511
|
+
callbacks.onMessageAppend(message);
|
|
4892
4512
|
} else {
|
|
4893
|
-
|
|
4894
|
-
"
|
|
4895
|
-
|
|
4896
|
-
);
|
|
4897
|
-
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
4513
|
+
callbacks.onMessageAppend({
|
|
4514
|
+
role: "system",
|
|
4515
|
+
content: "The provider did not respond to this request."
|
|
4516
|
+
});
|
|
4898
4517
|
}
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4518
|
+
callbacks.onStreamingUpdate("");
|
|
4519
|
+
callbacks.onThinkingUpdate("");
|
|
4520
|
+
} catch (error) {
|
|
4521
|
+
handleError(error, callbacks, deps.alertLevel, deps.logger);
|
|
4522
|
+
} finally {
|
|
4523
|
+
callbacks.onPaymentProcessing?.(false);
|
|
4905
4524
|
}
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4525
|
+
}
|
|
4526
|
+
async function convertMessages(messages) {
|
|
4527
|
+
return Promise.all(
|
|
4528
|
+
messages.filter((m) => m.role !== "system").map(async (m) => ({
|
|
4529
|
+
role: m.role,
|
|
4530
|
+
content: typeof m.content === "string" ? m.content : m.content
|
|
4531
|
+
}))
|
|
4532
|
+
);
|
|
4533
|
+
}
|
|
4534
|
+
async function createAssistantMessage(result) {
|
|
4535
|
+
if (result.images && result.images.length > 0) {
|
|
4536
|
+
const content = [];
|
|
4537
|
+
if (result.content) {
|
|
4538
|
+
content.push({
|
|
4539
|
+
type: "text",
|
|
4540
|
+
text: result.content,
|
|
4541
|
+
thinking: result.thinking,
|
|
4542
|
+
citations: result.citations,
|
|
4543
|
+
annotations: result.annotations
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
for (const img of result.images) {
|
|
4547
|
+
content.push({
|
|
4548
|
+
type: "image_url",
|
|
4549
|
+
image_url: {
|
|
4550
|
+
url: img.image_url.url
|
|
4551
|
+
}
|
|
4552
|
+
});
|
|
4553
|
+
}
|
|
4554
|
+
return {
|
|
4555
|
+
role: "assistant",
|
|
4556
|
+
content
|
|
4913
4557
|
};
|
|
4914
|
-
return headers;
|
|
4915
4558
|
}
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4559
|
+
return {
|
|
4560
|
+
role: "assistant",
|
|
4561
|
+
content: result.content || ""
|
|
4562
|
+
};
|
|
4563
|
+
}
|
|
4564
|
+
function handleError(error, callbacks, alertLevel, logger) {
|
|
4565
|
+
logger.error("[fetchAIResponse] Error occurred", error);
|
|
4566
|
+
if (error instanceof Error) {
|
|
4567
|
+
const isStreamError = error.message.includes("Error in input stream") || error.message.includes("Load failed");
|
|
4568
|
+
const modifiedErrorMsg = isStreamError ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
|
|
4569
|
+
logger.error(
|
|
4570
|
+
`[fetchAIResponse] Error type=${error.constructor.name}, message=${modifiedErrorMsg}, isStreamError=${isStreamError}`
|
|
4571
|
+
);
|
|
4572
|
+
callbacks.onMessageAppend({
|
|
4573
|
+
role: "system",
|
|
4574
|
+
content: "Uncaught Error: " + modifiedErrorMsg + (alertLevel === "max" ? " | " + error.stack : "")
|
|
4575
|
+
});
|
|
4576
|
+
} else {
|
|
4577
|
+
callbacks.onMessageAppend({
|
|
4578
|
+
role: "system",
|
|
4579
|
+
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
4580
|
+
});
|
|
4927
4581
|
}
|
|
4928
|
-
}
|
|
4582
|
+
}
|
|
4929
4583
|
|
|
4930
|
-
export { ProviderManager, RoutstrClient, StreamProcessor, createSSEParserTransform, inspectSSEWebStream };
|
|
4584
|
+
export { ProviderManager, RoutstrClient, StreamProcessor, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, inspectSSEWebStream, toUsageStats };
|
|
4931
4585
|
//# sourceMappingURL=index.mjs.map
|
|
4932
4586
|
//# sourceMappingURL=index.mjs.map
|