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