@routstr/sdk 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.mts +12 -0
- package/dist/browser.d.ts +12 -0
- package/dist/browser.js +6278 -0
- package/dist/browser.js.map +1 -0
- package/dist/browser.mjs +6230 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/bun.d.mts +29 -0
- package/dist/bun.d.ts +29 -0
- package/dist/bun.js +6586 -0
- package/dist/bun.js.map +1 -0
- package/dist/bun.mjs +6532 -0
- package/dist/bun.mjs.map +1 -0
- package/dist/bunSqlite-BMTseLIz.d.ts +18 -0
- package/dist/bunSqlite-D6AreVE2.d.mts +18 -0
- package/dist/client/index.d.mts +63 -41
- package/dist/client/index.d.ts +63 -41
- package/dist/client/index.js +1223 -1658
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +1223 -1659
- package/dist/client/index.mjs.map +1 -1
- package/dist/discovery/index.d.mts +67 -3
- package/dist/discovery/index.d.ts +67 -3
- package/dist/discovery/index.js +242 -79
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +242 -79
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1975 -2004
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1973 -2001
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +22 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +6651 -0
- package/dist/node.js.map +1 -0
- package/dist/node.mjs +6599 -0
- package/dist/node.mjs.map +1 -0
- package/dist/storage/bun.d.mts +16 -0
- package/dist/storage/bun.d.ts +16 -0
- package/dist/storage/bun.js +1801 -0
- package/dist/storage/bun.js.map +1 -0
- package/dist/storage/bun.mjs +1777 -0
- package/dist/storage/bun.mjs.map +1 -0
- package/dist/storage/index.d.mts +30 -30
- package/dist/storage/index.d.ts +30 -30
- package/dist/storage/index.js +393 -625
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +392 -622
- package/dist/storage/index.mjs.map +1 -1
- package/dist/storage/node.d.mts +22 -0
- package/dist/storage/node.d.ts +22 -0
- package/dist/storage/node.js +1864 -0
- package/dist/storage/node.js.map +1 -0
- package/dist/storage/node.mjs +1842 -0
- package/dist/storage/node.mjs.map +1 -0
- package/dist/{store-C6dfj1cc.d.mts → store-BiuM2V9N.d.mts} +14 -0
- package/dist/{store-58VcEUoA.d.ts → store-C8MZlfuz.d.ts} +14 -0
- package/dist/wallet/index.d.mts +4 -0
- package/dist/wallet/index.d.ts +4 -0
- package/dist/wallet/index.js +11 -4
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +11 -4
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +28 -2
package/dist/client/index.mjs
CHANGED
|
@@ -603,7 +603,7 @@ var CashuSpender = class {
|
|
|
603
603
|
});
|
|
604
604
|
continue;
|
|
605
605
|
}
|
|
606
|
-
if (balanceResult.amount >= 0) {
|
|
606
|
+
if (balanceResult.amount >= 0 && !balanceResult.balanceUnknown) {
|
|
607
607
|
const balanceSat = balanceResult.unit === "msat" ? Math.floor(balanceResult.amount / 1e3) : balanceResult.amount;
|
|
608
608
|
this.storageAdapter.updateApiKeyBalance(
|
|
609
609
|
apiKeyEntry.baseUrl,
|
|
@@ -1352,17 +1352,24 @@ var BalanceManager = class _BalanceManager {
|
|
|
1352
1352
|
this.logger.warn("getTokenBalance: FAILED", data);
|
|
1353
1353
|
const isInvalidApiKey = response.status === 401 && data?.detail?.error?.code === "invalid_api_key" && data?.detail?.error?.message?.includes("proofs already spent");
|
|
1354
1354
|
return {
|
|
1355
|
-
amount:
|
|
1355
|
+
amount: 0,
|
|
1356
1356
|
reserved: data.reserved ?? 0,
|
|
1357
1357
|
unit: "msat",
|
|
1358
1358
|
apiKey: data.api_key,
|
|
1359
|
-
isInvalidApiKey
|
|
1359
|
+
isInvalidApiKey,
|
|
1360
|
+
balanceUnknown: true
|
|
1360
1361
|
};
|
|
1361
1362
|
}
|
|
1362
1363
|
} catch (error) {
|
|
1363
1364
|
this.logger.error("getTokenBalance error", error);
|
|
1364
1365
|
}
|
|
1365
|
-
return {
|
|
1366
|
+
return {
|
|
1367
|
+
amount: 0,
|
|
1368
|
+
reserved: 0,
|
|
1369
|
+
unit: "sat",
|
|
1370
|
+
apiKey: "",
|
|
1371
|
+
balanceUnknown: true
|
|
1372
|
+
};
|
|
1366
1373
|
}
|
|
1367
1374
|
/**
|
|
1368
1375
|
* Handle topup errors with specific error types
|
|
@@ -1397,726 +1404,409 @@ var BalanceManager = class _BalanceManager {
|
|
|
1397
1404
|
}
|
|
1398
1405
|
};
|
|
1399
1406
|
|
|
1400
|
-
//
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
const totalMsats = costObj.total_msats;
|
|
1417
|
-
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
1418
|
-
if (typeof totalMsats === "number") {
|
|
1419
|
-
satsCost = totalMsats / 1e3;
|
|
1420
|
-
}
|
|
1407
|
+
// utils/torUtils.ts
|
|
1408
|
+
var TOR_ONION_SUFFIX = ".onion";
|
|
1409
|
+
var isTorContext = () => {
|
|
1410
|
+
if (typeof window === "undefined") return false;
|
|
1411
|
+
const hostname = window.location.hostname.toLowerCase();
|
|
1412
|
+
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
1413
|
+
};
|
|
1414
|
+
var isOnionUrl = (url) => {
|
|
1415
|
+
if (!url) return false;
|
|
1416
|
+
const trimmed = url.trim().toLowerCase();
|
|
1417
|
+
if (!trimmed) return false;
|
|
1418
|
+
try {
|
|
1419
|
+
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
1420
|
+
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
1421
|
+
} catch {
|
|
1422
|
+
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
1421
1423
|
}
|
|
1422
|
-
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
// client/ProviderManager.ts
|
|
1427
|
+
function getImageResolutionFromDataUrl(dataUrl) {
|
|
1428
|
+
try {
|
|
1429
|
+
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:"))
|
|
1430
|
+
return null;
|
|
1431
|
+
const commaIdx = dataUrl.indexOf(",");
|
|
1432
|
+
if (commaIdx === -1) return null;
|
|
1433
|
+
const meta = dataUrl.slice(5, commaIdx);
|
|
1434
|
+
const base64 = dataUrl.slice(commaIdx + 1);
|
|
1435
|
+
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
1436
|
+
const len = binary.length;
|
|
1437
|
+
const bytes = new Uint8Array(len);
|
|
1438
|
+
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
1439
|
+
const isPNG = meta.includes("image/png");
|
|
1440
|
+
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
1441
|
+
if (isPNG) {
|
|
1442
|
+
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
1443
|
+
for (let i = 0; i < sig.length; i++) {
|
|
1444
|
+
if (bytes[i] !== sig[i]) return null;
|
|
1445
|
+
}
|
|
1446
|
+
const view = new DataView(
|
|
1447
|
+
bytes.buffer,
|
|
1448
|
+
bytes.byteOffset,
|
|
1449
|
+
bytes.byteLength
|
|
1450
|
+
);
|
|
1451
|
+
const width = view.getUint32(16, false);
|
|
1452
|
+
const height = view.getUint32(20, false);
|
|
1453
|
+
if (width > 0 && height > 0) return { width, height };
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
if (isJPEG) {
|
|
1457
|
+
let offset = 0;
|
|
1458
|
+
if (bytes[offset++] !== 255 || bytes[offset++] !== 216) return null;
|
|
1459
|
+
while (offset < bytes.length) {
|
|
1460
|
+
while (offset < bytes.length && bytes[offset] !== 255) offset++;
|
|
1461
|
+
if (offset + 1 >= bytes.length) break;
|
|
1462
|
+
while (bytes[offset] === 255) offset++;
|
|
1463
|
+
const marker = bytes[offset++];
|
|
1464
|
+
if (marker === 216 || marker === 217) continue;
|
|
1465
|
+
if (offset + 1 >= bytes.length) break;
|
|
1466
|
+
const length = bytes[offset] << 8 | bytes[offset + 1];
|
|
1467
|
+
offset += 2;
|
|
1468
|
+
if (marker === 192 || marker === 194) {
|
|
1469
|
+
if (length < 7 || offset + length - 2 > bytes.length) return null;
|
|
1470
|
+
const precision = bytes[offset];
|
|
1471
|
+
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
1472
|
+
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
1473
|
+
if (precision > 0 && width > 0 && height > 0)
|
|
1474
|
+
return { width, height };
|
|
1475
|
+
return null;
|
|
1476
|
+
} else {
|
|
1477
|
+
offset += length - 2;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1423
1482
|
return null;
|
|
1424
|
-
}
|
|
1425
|
-
return {
|
|
1426
|
-
promptTokens,
|
|
1427
|
-
completionTokens,
|
|
1428
|
-
totalTokens,
|
|
1429
|
-
cost,
|
|
1430
|
-
satsCost
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
function extractResponseId(body) {
|
|
1434
|
-
if (!body || typeof body !== "object") return void 0;
|
|
1435
|
-
const id = body.id;
|
|
1436
|
-
if (typeof id !== "string") return void 0;
|
|
1437
|
-
const trimmed = id.trim();
|
|
1438
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
1439
|
-
}
|
|
1440
|
-
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
1441
|
-
if (!parsed || typeof parsed !== "object") {
|
|
1483
|
+
} catch {
|
|
1442
1484
|
return null;
|
|
1443
1485
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
return null;
|
|
1486
|
+
}
|
|
1487
|
+
function calculateImageTokens(width, height, detail = "auto") {
|
|
1488
|
+
if (detail === "low") return 85;
|
|
1489
|
+
let w = width;
|
|
1490
|
+
let h = height;
|
|
1491
|
+
if (w > 2048 || h > 2048) {
|
|
1492
|
+
const aspectRatio = w / h;
|
|
1493
|
+
if (w > h) {
|
|
1494
|
+
w = 2048;
|
|
1495
|
+
h = Math.floor(w / aspectRatio);
|
|
1496
|
+
} else {
|
|
1497
|
+
h = 2048;
|
|
1498
|
+
w = Math.floor(h * aspectRatio);
|
|
1499
|
+
}
|
|
1459
1500
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1501
|
+
if (w > 768 || h > 768) {
|
|
1502
|
+
const aspectRatio = w / h;
|
|
1503
|
+
if (w > h) {
|
|
1504
|
+
w = 768;
|
|
1505
|
+
h = Math.floor(w / aspectRatio);
|
|
1506
|
+
} else {
|
|
1507
|
+
h = 768;
|
|
1508
|
+
w = Math.floor(h * aspectRatio);
|
|
1509
|
+
}
|
|
1469
1510
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1511
|
+
const tilesWidth = Math.floor((w + 511) / 512);
|
|
1512
|
+
const tilesHeight = Math.floor((h + 511) / 512);
|
|
1513
|
+
const numTiles = tilesWidth * tilesHeight;
|
|
1514
|
+
return 85 + 170 * numTiles;
|
|
1515
|
+
}
|
|
1516
|
+
var ProviderManager = class _ProviderManager {
|
|
1517
|
+
constructor(providerRegistry, store, logger) {
|
|
1518
|
+
this.providerRegistry = providerRegistry;
|
|
1519
|
+
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1520
|
+
this.logger = (logger ?? consoleLogger).child(`ProviderManager:${this.instanceId}`);
|
|
1521
|
+
if (store) {
|
|
1522
|
+
this.store = store;
|
|
1523
|
+
this.hydrateFromStore();
|
|
1524
|
+
}
|
|
1472
1525
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1526
|
+
providerRegistry;
|
|
1527
|
+
failedProviders = /* @__PURE__ */ new Set();
|
|
1528
|
+
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
1529
|
+
lastFailed = /* @__PURE__ */ new Map();
|
|
1530
|
+
/** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
|
|
1531
|
+
providersOnCoolDown = [];
|
|
1532
|
+
/** Cooldown duration in milliseconds (42 seconds) */
|
|
1533
|
+
static COOLDOWN_DURATION_MS = 42 * 1e3;
|
|
1534
|
+
/** Optional persistent store for failure tracking */
|
|
1535
|
+
store = null;
|
|
1536
|
+
/** Instance ID for debugging */
|
|
1537
|
+
instanceId;
|
|
1538
|
+
logger;
|
|
1539
|
+
/**
|
|
1540
|
+
* Hydrate in-memory state from persistent store
|
|
1541
|
+
*/
|
|
1542
|
+
hydrateFromStore() {
|
|
1543
|
+
if (!this.store) return;
|
|
1544
|
+
const state = this.store.getState();
|
|
1545
|
+
this.failedProviders = new Set(state.failedProviders);
|
|
1546
|
+
this.lastFailed = new Map(Object.entries(state.lastFailed));
|
|
1547
|
+
const now = Date.now();
|
|
1548
|
+
this.providersOnCoolDown = state.providersOnCooldown.filter(
|
|
1549
|
+
(entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
|
|
1550
|
+
).map((entry) => [entry.baseUrl, entry.timestamp]);
|
|
1551
|
+
this.logger.log(`Hydrated from store: failedProviders=${this.failedProviders.size} lastFailed=${this.lastFailed.size} providersOnCooldown=${this.providersOnCoolDown.length}`);
|
|
1475
1552
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
completionTokens,
|
|
1482
|
-
totalTokens,
|
|
1483
|
-
cost: Number(cost ?? 0),
|
|
1484
|
-
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
|
|
1485
|
-
};
|
|
1486
|
-
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
1487
|
-
return null;
|
|
1553
|
+
/**
|
|
1554
|
+
* Get instance ID for debugging
|
|
1555
|
+
*/
|
|
1556
|
+
getInstanceId() {
|
|
1557
|
+
return this.instanceId;
|
|
1488
1558
|
}
|
|
1489
|
-
return result;
|
|
1490
|
-
}
|
|
1491
|
-
function toUsageStats(usage) {
|
|
1492
|
-
if (!usage) return void 0;
|
|
1493
|
-
return {
|
|
1494
|
-
total_tokens: usage.totalTokens,
|
|
1495
|
-
prompt_tokens: usage.promptTokens,
|
|
1496
|
-
completion_tokens: usage.completionTokens,
|
|
1497
|
-
cost: usage.cost,
|
|
1498
|
-
sats_cost: usage.satsCost
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
// client/StreamProcessor.ts
|
|
1503
|
-
var StreamProcessor = class {
|
|
1504
|
-
accumulatedContent = "";
|
|
1505
|
-
accumulatedThinking = "";
|
|
1506
|
-
accumulatedImages = [];
|
|
1507
|
-
isInThinking = false;
|
|
1508
|
-
isInContent = false;
|
|
1509
1559
|
/**
|
|
1510
|
-
*
|
|
1560
|
+
* Clean up expired cooldown entries
|
|
1561
|
+
* Also removes the provider from failedProviders so it can be retried
|
|
1511
1562
|
*/
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
let usage;
|
|
1525
|
-
let model;
|
|
1526
|
-
let finish_reason;
|
|
1527
|
-
let citations;
|
|
1528
|
-
let annotations;
|
|
1529
|
-
let responseId;
|
|
1530
|
-
try {
|
|
1531
|
-
while (true) {
|
|
1532
|
-
const { done, value } = await reader.read();
|
|
1533
|
-
if (done) {
|
|
1534
|
-
break;
|
|
1535
|
-
}
|
|
1536
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
1537
|
-
buffer += chunk;
|
|
1538
|
-
const lines = buffer.split("\n");
|
|
1539
|
-
buffer = lines.pop() || "";
|
|
1540
|
-
for (const line of lines) {
|
|
1541
|
-
const parsed = this._parseLine(line);
|
|
1542
|
-
if (!parsed) continue;
|
|
1543
|
-
if (parsed.content) {
|
|
1544
|
-
this._handleContent(parsed.content, callbacks, modelId);
|
|
1545
|
-
}
|
|
1546
|
-
if (parsed.reasoning) {
|
|
1547
|
-
this._handleThinking(parsed.reasoning, callbacks);
|
|
1548
|
-
}
|
|
1549
|
-
if (parsed.usage) {
|
|
1550
|
-
usage = parsed.usage;
|
|
1551
|
-
}
|
|
1552
|
-
if (parsed.model) {
|
|
1553
|
-
model = parsed.model;
|
|
1554
|
-
}
|
|
1555
|
-
if (parsed.finish_reason) {
|
|
1556
|
-
finish_reason = parsed.finish_reason;
|
|
1557
|
-
}
|
|
1558
|
-
if (parsed.responseId) {
|
|
1559
|
-
responseId = parsed.responseId;
|
|
1560
|
-
}
|
|
1561
|
-
if (parsed.citations) {
|
|
1562
|
-
citations = parsed.citations;
|
|
1563
|
-
}
|
|
1564
|
-
if (parsed.annotations) {
|
|
1565
|
-
annotations = parsed.annotations;
|
|
1566
|
-
}
|
|
1567
|
-
if (parsed.images) {
|
|
1568
|
-
this._mergeImages(parsed.images);
|
|
1563
|
+
cleanupExpiredCooldowns() {
|
|
1564
|
+
const now = Date.now();
|
|
1565
|
+
const before = this.providersOnCoolDown.length;
|
|
1566
|
+
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1567
|
+
([url, timestamp]) => {
|
|
1568
|
+
const age = now - timestamp;
|
|
1569
|
+
const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
|
|
1570
|
+
if (isExpired) {
|
|
1571
|
+
this.logger.log(`Removing expired cooldown for ${url} (age: ${age}ms)`);
|
|
1572
|
+
this.failedProviders.delete(url);
|
|
1573
|
+
if (this.store) {
|
|
1574
|
+
this.store.getState().removeFailedProvider(url);
|
|
1569
1575
|
}
|
|
1570
1576
|
}
|
|
1577
|
+
return !isExpired;
|
|
1571
1578
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1579
|
+
);
|
|
1580
|
+
const after = this.providersOnCoolDown.length;
|
|
1581
|
+
if (before !== after) {
|
|
1582
|
+
this.logger.log(`Cleaned up ${before - after} expired cooldown(s), ${after} remaining`);
|
|
1574
1583
|
}
|
|
1575
|
-
return {
|
|
1576
|
-
content: this.accumulatedContent,
|
|
1577
|
-
thinking: this.accumulatedThinking || void 0,
|
|
1578
|
-
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
1579
|
-
usage,
|
|
1580
|
-
model,
|
|
1581
|
-
responseId,
|
|
1582
|
-
finish_reason,
|
|
1583
|
-
citations,
|
|
1584
|
-
annotations
|
|
1585
|
-
};
|
|
1586
1584
|
}
|
|
1587
1585
|
/**
|
|
1588
|
-
*
|
|
1586
|
+
* Get the cooldown duration in milliseconds
|
|
1589
1587
|
*/
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1588
|
+
getCooldownDurationMs() {
|
|
1589
|
+
return _ProviderManager.COOLDOWN_DURATION_MS;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Check if a provider is currently on cooldown
|
|
1593
|
+
*/
|
|
1594
|
+
isOnCooldown(baseUrl) {
|
|
1595
|
+
this.cleanupExpiredCooldowns();
|
|
1596
|
+
const result = this.providersOnCoolDown.some(([url]) => url === baseUrl);
|
|
1597
|
+
return result;
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Get all providers currently on cooldown
|
|
1601
|
+
*/
|
|
1602
|
+
getProvidersOnCooldown() {
|
|
1603
|
+
this.cleanupExpiredCooldowns();
|
|
1604
|
+
return [...this.providersOnCoolDown];
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Reset the failed providers list
|
|
1608
|
+
*/
|
|
1609
|
+
resetFailedProviders() {
|
|
1610
|
+
this.failedProviders.clear();
|
|
1611
|
+
if (this.store) {
|
|
1612
|
+
this.store.getState().setFailedProviders([]);
|
|
1594
1613
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Get the last failed timestamp for a provider
|
|
1617
|
+
*/
|
|
1618
|
+
getLastFailed(baseUrl) {
|
|
1619
|
+
return this.lastFailed.get(baseUrl);
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Get all providers with their last failed timestamps
|
|
1623
|
+
*/
|
|
1624
|
+
getAllLastFailed() {
|
|
1625
|
+
return new Map(this.lastFailed);
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Mark a provider as failed
|
|
1629
|
+
* If a provider fails twice within 5 minutes, it's added to cooldown
|
|
1630
|
+
*/
|
|
1631
|
+
markFailed(baseUrl) {
|
|
1632
|
+
const now = Date.now();
|
|
1633
|
+
const lastFailure = this.lastFailed.get(baseUrl);
|
|
1634
|
+
this.logger.log(`markFailed: ${baseUrl} lastFailure=${lastFailure} now=${now}`);
|
|
1635
|
+
if (lastFailure !== void 0) {
|
|
1636
|
+
const timeSinceLastFailure = now - lastFailure;
|
|
1637
|
+
this.logger.log(`markFailed: timeSinceLastFailure=${timeSinceLastFailure}ms withinCooldown=${timeSinceLastFailure < _ProviderManager.COOLDOWN_DURATION_MS}`);
|
|
1598
1638
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
};
|
|
1617
|
-
}
|
|
1618
|
-
if (parsed.id) {
|
|
1619
|
-
result.responseId = parsed.id;
|
|
1620
|
-
}
|
|
1621
|
-
if (parsed.model) {
|
|
1622
|
-
result.model = parsed.model;
|
|
1623
|
-
}
|
|
1624
|
-
if (parsed.citations) {
|
|
1625
|
-
result.citations = parsed.citations;
|
|
1626
|
-
}
|
|
1627
|
-
if (parsed.annotations) {
|
|
1628
|
-
result.annotations = parsed.annotations;
|
|
1629
|
-
}
|
|
1630
|
-
if (parsed.choices?.[0]?.finish_reason) {
|
|
1631
|
-
result.finish_reason = parsed.choices[0].finish_reason;
|
|
1639
|
+
this.lastFailed.set(baseUrl, now);
|
|
1640
|
+
this.failedProviders.add(baseUrl);
|
|
1641
|
+
if (this.store) {
|
|
1642
|
+
this.store.getState().setLastFailedTimestamp(baseUrl, now);
|
|
1643
|
+
this.store.getState().addFailedProvider(baseUrl);
|
|
1644
|
+
}
|
|
1645
|
+
this.logger.log(`markFailed: updated ${baseUrl} to ${now}, failedProviders=${this.failedProviders.size}`);
|
|
1646
|
+
if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
|
|
1647
|
+
this.logger.log(`markFailed: second failure within cooldown window for ${baseUrl}`);
|
|
1648
|
+
if (!this.isOnCooldown(baseUrl)) {
|
|
1649
|
+
this.providersOnCoolDown.push([baseUrl, now]);
|
|
1650
|
+
if (this.store) {
|
|
1651
|
+
this.store.getState().addProviderOnCooldown(baseUrl, now);
|
|
1652
|
+
}
|
|
1653
|
+
this.logger.log(`markFailed: ${baseUrl} added to cooldown`);
|
|
1654
|
+
} else {
|
|
1655
|
+
this.logger.log(`markFailed: ${baseUrl} already on cooldown`);
|
|
1632
1656
|
}
|
|
1633
|
-
|
|
1634
|
-
if (
|
|
1635
|
-
|
|
1657
|
+
} else {
|
|
1658
|
+
if (lastFailure === void 0) {
|
|
1659
|
+
this.logger.log(`markFailed: first failure for ${baseUrl}`);
|
|
1660
|
+
} else {
|
|
1661
|
+
this.logger.log(`markFailed: failure outside cooldown window for ${baseUrl} (${now - lastFailure}ms ago)`);
|
|
1636
1662
|
}
|
|
1637
|
-
return result;
|
|
1638
|
-
} catch {
|
|
1639
|
-
return null;
|
|
1640
1663
|
}
|
|
1641
1664
|
}
|
|
1642
1665
|
/**
|
|
1643
|
-
*
|
|
1666
|
+
* Remove a provider from cooldown (e.g., after successful request)
|
|
1644
1667
|
*/
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
this.
|
|
1651
|
-
}
|
|
1652
|
-
if (modelId) {
|
|
1653
|
-
this._extractThinkingFromContent(content, callbacks);
|
|
1654
|
-
} else {
|
|
1655
|
-
this.accumulatedContent += content;
|
|
1668
|
+
removeFromCooldown(baseUrl) {
|
|
1669
|
+
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1670
|
+
([url]) => url !== baseUrl
|
|
1671
|
+
);
|
|
1672
|
+
if (this.store) {
|
|
1673
|
+
this.store.getState().removeProviderFromCooldown(baseUrl);
|
|
1656
1674
|
}
|
|
1657
|
-
callbacks.onContent(this.accumulatedContent);
|
|
1658
1675
|
}
|
|
1659
1676
|
/**
|
|
1660
|
-
*
|
|
1677
|
+
* Clear all cooldown tracking
|
|
1661
1678
|
*/
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
this.
|
|
1679
|
+
clearCooldowns() {
|
|
1680
|
+
this.providersOnCoolDown = [];
|
|
1681
|
+
if (this.store) {
|
|
1682
|
+
this.store.getState().clearProvidersOnCooldown();
|
|
1666
1683
|
}
|
|
1667
|
-
this.accumulatedThinking += reasoning;
|
|
1668
|
-
callbacks.onThinking(this.accumulatedThinking);
|
|
1669
1684
|
}
|
|
1670
1685
|
/**
|
|
1671
|
-
*
|
|
1686
|
+
* Clear all failure tracking (lastFailed timestamps)
|
|
1672
1687
|
*/
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
this.isInThinking = true;
|
|
1678
|
-
if (!this.accumulatedThinking.includes("<thinking>")) {
|
|
1679
|
-
this.accumulatedThinking += "<thinking> ";
|
|
1680
|
-
}
|
|
1681
|
-
} else if (part === "</thinking>") {
|
|
1682
|
-
this.isInThinking = false;
|
|
1683
|
-
this.accumulatedThinking += "</thinking>";
|
|
1684
|
-
} else if (this.isInThinking) {
|
|
1685
|
-
this.accumulatedThinking += part;
|
|
1686
|
-
} else {
|
|
1687
|
-
this.accumulatedContent += part;
|
|
1688
|
-
}
|
|
1688
|
+
clearFailureHistory() {
|
|
1689
|
+
this.lastFailed.clear();
|
|
1690
|
+
if (this.store) {
|
|
1691
|
+
this.store.getState().setLastFailed({});
|
|
1689
1692
|
}
|
|
1690
1693
|
}
|
|
1691
1694
|
/**
|
|
1692
|
-
*
|
|
1695
|
+
* Check if a provider has failed
|
|
1693
1696
|
*/
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
const newUrl = img.image_url?.url;
|
|
1697
|
-
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
1698
|
-
const existingUrl = existing.image_url?.url;
|
|
1699
|
-
if (newUrl && existingUrl) {
|
|
1700
|
-
return existingUrl === newUrl;
|
|
1701
|
-
}
|
|
1702
|
-
if (img.index !== void 0 && existing.index !== void 0) {
|
|
1703
|
-
return existing.index === img.index;
|
|
1704
|
-
}
|
|
1705
|
-
return false;
|
|
1706
|
-
});
|
|
1707
|
-
if (existingIndex === -1) {
|
|
1708
|
-
this.accumulatedImages.push(img);
|
|
1709
|
-
} else {
|
|
1710
|
-
this.accumulatedImages[existingIndex] = img;
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1697
|
+
hasFailed(baseUrl) {
|
|
1698
|
+
return this.failedProviders.has(baseUrl);
|
|
1713
1699
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
if (typeof window === "undefined") return false;
|
|
1720
|
-
const hostname = window.location.hostname.toLowerCase();
|
|
1721
|
-
return hostname.endsWith(TOR_ONION_SUFFIX);
|
|
1722
|
-
};
|
|
1723
|
-
var isOnionUrl = (url) => {
|
|
1724
|
-
if (!url) return false;
|
|
1725
|
-
const trimmed = url.trim().toLowerCase();
|
|
1726
|
-
if (!trimmed) return false;
|
|
1727
|
-
try {
|
|
1728
|
-
const candidate = trimmed.startsWith("http") ? trimmed : `http://${trimmed}`;
|
|
1729
|
-
return new URL(candidate).hostname.endsWith(TOR_ONION_SUFFIX);
|
|
1730
|
-
} catch {
|
|
1731
|
-
return trimmed.includes(TOR_ONION_SUFFIX);
|
|
1700
|
+
/**
|
|
1701
|
+
* Get a copy of the failed providers set
|
|
1702
|
+
*/
|
|
1703
|
+
getFailedProviders() {
|
|
1704
|
+
return new Set(this.failedProviders);
|
|
1732
1705
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
1745
|
-
const len = binary.length;
|
|
1746
|
-
const bytes = new Uint8Array(len);
|
|
1747
|
-
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
1748
|
-
const isPNG = meta.includes("image/png");
|
|
1749
|
-
const isJPEG = meta.includes("image/jpeg") || meta.includes("image/jpg");
|
|
1750
|
-
if (isPNG) {
|
|
1751
|
-
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
1752
|
-
for (let i = 0; i < sig.length; i++) {
|
|
1753
|
-
if (bytes[i] !== sig[i]) return null;
|
|
1754
|
-
}
|
|
1755
|
-
const view = new DataView(
|
|
1756
|
-
bytes.buffer,
|
|
1757
|
-
bytes.byteOffset,
|
|
1758
|
-
bytes.byteLength
|
|
1706
|
+
/**
|
|
1707
|
+
* Find the next best provider for a model
|
|
1708
|
+
* @param modelId The model ID to find a provider for
|
|
1709
|
+
* @param currentBaseUrl The current provider to exclude
|
|
1710
|
+
* @returns The best provider URL or null if none available
|
|
1711
|
+
*/
|
|
1712
|
+
findNextBestProvider(modelId, currentBaseUrl) {
|
|
1713
|
+
try {
|
|
1714
|
+
const torMode = isTorContext();
|
|
1715
|
+
const disabledProviders = new Set(
|
|
1716
|
+
this.providerRegistry.getDisabledProviders()
|
|
1759
1717
|
);
|
|
1760
|
-
|
|
1761
|
-
const
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
if (
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
const height = bytes[offset + 1] << 8 | bytes[offset + 2];
|
|
1781
|
-
const width = bytes[offset + 3] << 8 | bytes[offset + 4];
|
|
1782
|
-
if (precision > 0 && width > 0 && height > 0)
|
|
1783
|
-
return { width, height };
|
|
1784
|
-
return null;
|
|
1785
|
-
} else {
|
|
1786
|
-
offset += length - 2;
|
|
1718
|
+
this.logger.log(`findNextBestProvider: model=${modelId} disabled=${[...disabledProviders].length} onCooldown=${this.providersOnCoolDown.length}`);
|
|
1719
|
+
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
1720
|
+
this.logger.log(`findNextBestProvider: total providers=${Object.keys(allProviders).length}`);
|
|
1721
|
+
const candidates = [];
|
|
1722
|
+
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
1723
|
+
if (baseUrl === currentBaseUrl) {
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
if (disabledProviders.has(baseUrl)) {
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
if (this.isOnCooldown(baseUrl)) {
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
if (!torMode && isOnionUrl(baseUrl)) {
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
const model = models.find((m) => m.id === modelId);
|
|
1736
|
+
if (!model) {
|
|
1737
|
+
continue;
|
|
1787
1738
|
}
|
|
1739
|
+
const cost = model.sats_pricing?.completion ?? 0;
|
|
1740
|
+
candidates.push({ baseUrl, model, cost });
|
|
1741
|
+
}
|
|
1742
|
+
candidates.sort((a, b) => a.cost - b.cost);
|
|
1743
|
+
if (candidates.length > 0) {
|
|
1744
|
+
return candidates[0].baseUrl;
|
|
1745
|
+
} else {
|
|
1746
|
+
return null;
|
|
1788
1747
|
}
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
this.logger.error("findNextBestProvider error:", error);
|
|
1789
1750
|
return null;
|
|
1790
1751
|
}
|
|
1791
|
-
return null;
|
|
1792
|
-
} catch {
|
|
1793
|
-
return null;
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
function calculateImageTokens(width, height, detail = "auto") {
|
|
1797
|
-
if (detail === "low") return 85;
|
|
1798
|
-
let w = width;
|
|
1799
|
-
let h = height;
|
|
1800
|
-
if (w > 2048 || h > 2048) {
|
|
1801
|
-
const aspectRatio = w / h;
|
|
1802
|
-
if (w > h) {
|
|
1803
|
-
w = 2048;
|
|
1804
|
-
h = Math.floor(w / aspectRatio);
|
|
1805
|
-
} else {
|
|
1806
|
-
h = 2048;
|
|
1807
|
-
w = Math.floor(h * aspectRatio);
|
|
1808
|
-
}
|
|
1809
|
-
}
|
|
1810
|
-
if (w > 768 || h > 768) {
|
|
1811
|
-
const aspectRatio = w / h;
|
|
1812
|
-
if (w > h) {
|
|
1813
|
-
w = 768;
|
|
1814
|
-
h = Math.floor(w / aspectRatio);
|
|
1815
|
-
} else {
|
|
1816
|
-
h = 768;
|
|
1817
|
-
w = Math.floor(h * aspectRatio);
|
|
1818
|
-
}
|
|
1819
1752
|
}
|
|
1820
|
-
const tilesWidth = Math.floor((w + 511) / 512);
|
|
1821
|
-
const tilesHeight = Math.floor((h + 511) / 512);
|
|
1822
|
-
const numTiles = tilesWidth * tilesHeight;
|
|
1823
|
-
return 85 + 170 * numTiles;
|
|
1824
|
-
}
|
|
1825
|
-
function isInsecureHttpUrl(url) {
|
|
1826
|
-
return url.startsWith("http://");
|
|
1827
|
-
}
|
|
1828
|
-
var ProviderManager = class _ProviderManager {
|
|
1829
|
-
constructor(providerRegistry, store, logger) {
|
|
1830
|
-
this.providerRegistry = providerRegistry;
|
|
1831
|
-
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1832
|
-
this.logger = (logger ?? consoleLogger).child(`ProviderManager:${this.instanceId}`);
|
|
1833
|
-
if (store) {
|
|
1834
|
-
this.store = store;
|
|
1835
|
-
this.hydrateFromStore();
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
providerRegistry;
|
|
1839
|
-
failedProviders = /* @__PURE__ */ new Set();
|
|
1840
|
-
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
1841
|
-
lastFailed = /* @__PURE__ */ new Map();
|
|
1842
|
-
/** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
|
|
1843
|
-
providersOnCoolDown = [];
|
|
1844
|
-
/** Cooldown duration in milliseconds (42 seconds) */
|
|
1845
|
-
static COOLDOWN_DURATION_MS = 42 * 1e3;
|
|
1846
|
-
/** Optional persistent store for failure tracking */
|
|
1847
|
-
store = null;
|
|
1848
|
-
/** Instance ID for debugging */
|
|
1849
|
-
instanceId;
|
|
1850
|
-
logger;
|
|
1851
1753
|
/**
|
|
1852
|
-
*
|
|
1754
|
+
* Find the best model for a provider
|
|
1755
|
+
* Useful when switching providers and need to find equivalent model
|
|
1853
1756
|
*/
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
const
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
(
|
|
1862
|
-
|
|
1863
|
-
|
|
1757
|
+
async getModelForProvider(baseUrl, modelId) {
|
|
1758
|
+
const models = this.providerRegistry.getModelsForProvider(baseUrl);
|
|
1759
|
+
const exactMatch = models.find((m) => m.id === modelId);
|
|
1760
|
+
if (exactMatch) return exactMatch;
|
|
1761
|
+
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
1762
|
+
if (providerInfo?.version && /^0\.1\./.test(providerInfo.version)) {
|
|
1763
|
+
const suffix = modelId.split("/").pop();
|
|
1764
|
+
const suffixMatch = models.find((m) => m.id === suffix);
|
|
1765
|
+
if (suffixMatch) return suffixMatch;
|
|
1766
|
+
}
|
|
1767
|
+
return null;
|
|
1864
1768
|
}
|
|
1865
1769
|
/**
|
|
1866
|
-
* Get
|
|
1770
|
+
* Get all available providers for a model
|
|
1771
|
+
* Returns sorted list by price
|
|
1867
1772
|
*/
|
|
1868
|
-
|
|
1869
|
-
|
|
1773
|
+
getAllProvidersForModel(modelId) {
|
|
1774
|
+
const candidates = [];
|
|
1775
|
+
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
1776
|
+
const disabledProviders = new Set(
|
|
1777
|
+
this.providerRegistry.getDisabledProviders()
|
|
1778
|
+
);
|
|
1779
|
+
const torMode = isTorContext();
|
|
1780
|
+
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
1781
|
+
if (disabledProviders.has(baseUrl)) continue;
|
|
1782
|
+
if (this.isOnCooldown(baseUrl)) continue;
|
|
1783
|
+
if (!torMode && isOnionUrl(baseUrl))
|
|
1784
|
+
continue;
|
|
1785
|
+
const model = models.find((m) => m.id === modelId);
|
|
1786
|
+
if (!model) continue;
|
|
1787
|
+
const cost = model.sats_pricing?.completion ?? 0;
|
|
1788
|
+
candidates.push({ baseUrl, model, cost });
|
|
1789
|
+
}
|
|
1790
|
+
return candidates.sort((a, b) => a.cost - b.cost);
|
|
1870
1791
|
}
|
|
1871
1792
|
/**
|
|
1872
|
-
*
|
|
1873
|
-
* Also removes the provider from failedProviders so it can be retried
|
|
1874
|
-
*/
|
|
1875
|
-
cleanupExpiredCooldowns() {
|
|
1876
|
-
const now = Date.now();
|
|
1877
|
-
const before = this.providersOnCoolDown.length;
|
|
1878
|
-
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1879
|
-
([url, timestamp]) => {
|
|
1880
|
-
const age = now - timestamp;
|
|
1881
|
-
const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
|
|
1882
|
-
if (isExpired) {
|
|
1883
|
-
this.logger.log(`Removing expired cooldown for ${url} (age: ${age}ms)`);
|
|
1884
|
-
this.failedProviders.delete(url);
|
|
1885
|
-
if (this.store) {
|
|
1886
|
-
this.store.getState().removeFailedProvider(url);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
return !isExpired;
|
|
1890
|
-
}
|
|
1891
|
-
);
|
|
1892
|
-
const after = this.providersOnCoolDown.length;
|
|
1893
|
-
if (before !== after) {
|
|
1894
|
-
this.logger.log(`Cleaned up ${before - after} expired cooldown(s), ${after} remaining`);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
/**
|
|
1898
|
-
* Get the cooldown duration in milliseconds
|
|
1899
|
-
*/
|
|
1900
|
-
getCooldownDurationMs() {
|
|
1901
|
-
return _ProviderManager.COOLDOWN_DURATION_MS;
|
|
1902
|
-
}
|
|
1903
|
-
/**
|
|
1904
|
-
* Check if a provider is currently on cooldown
|
|
1905
|
-
*/
|
|
1906
|
-
isOnCooldown(baseUrl) {
|
|
1907
|
-
this.cleanupExpiredCooldowns();
|
|
1908
|
-
const result = this.providersOnCoolDown.some(([url]) => url === baseUrl);
|
|
1909
|
-
return result;
|
|
1910
|
-
}
|
|
1911
|
-
/**
|
|
1912
|
-
* Get all providers currently on cooldown
|
|
1913
|
-
*/
|
|
1914
|
-
getProvidersOnCooldown() {
|
|
1915
|
-
this.cleanupExpiredCooldowns();
|
|
1916
|
-
return [...this.providersOnCoolDown];
|
|
1917
|
-
}
|
|
1918
|
-
/**
|
|
1919
|
-
* Reset the failed providers list
|
|
1920
|
-
*/
|
|
1921
|
-
resetFailedProviders() {
|
|
1922
|
-
this.failedProviders.clear();
|
|
1923
|
-
if (this.store) {
|
|
1924
|
-
this.store.getState().setFailedProviders([]);
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
/**
|
|
1928
|
-
* Get the last failed timestamp for a provider
|
|
1929
|
-
*/
|
|
1930
|
-
getLastFailed(baseUrl) {
|
|
1931
|
-
return this.lastFailed.get(baseUrl);
|
|
1932
|
-
}
|
|
1933
|
-
/**
|
|
1934
|
-
* Get all providers with their last failed timestamps
|
|
1935
|
-
*/
|
|
1936
|
-
getAllLastFailed() {
|
|
1937
|
-
return new Map(this.lastFailed);
|
|
1938
|
-
}
|
|
1939
|
-
/**
|
|
1940
|
-
* Mark a provider as failed
|
|
1941
|
-
* If a provider fails twice within 5 minutes, it's added to cooldown
|
|
1942
|
-
*/
|
|
1943
|
-
markFailed(baseUrl) {
|
|
1944
|
-
const now = Date.now();
|
|
1945
|
-
const lastFailure = this.lastFailed.get(baseUrl);
|
|
1946
|
-
this.logger.log(`markFailed: ${baseUrl} lastFailure=${lastFailure} now=${now}`);
|
|
1947
|
-
if (lastFailure !== void 0) {
|
|
1948
|
-
const timeSinceLastFailure = now - lastFailure;
|
|
1949
|
-
this.logger.log(`markFailed: timeSinceLastFailure=${timeSinceLastFailure}ms withinCooldown=${timeSinceLastFailure < _ProviderManager.COOLDOWN_DURATION_MS}`);
|
|
1950
|
-
}
|
|
1951
|
-
this.lastFailed.set(baseUrl, now);
|
|
1952
|
-
this.failedProviders.add(baseUrl);
|
|
1953
|
-
if (this.store) {
|
|
1954
|
-
this.store.getState().setLastFailedTimestamp(baseUrl, now);
|
|
1955
|
-
this.store.getState().addFailedProvider(baseUrl);
|
|
1956
|
-
}
|
|
1957
|
-
this.logger.log(`markFailed: updated ${baseUrl} to ${now}, failedProviders=${this.failedProviders.size}`);
|
|
1958
|
-
if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
|
|
1959
|
-
this.logger.log(`markFailed: second failure within cooldown window for ${baseUrl}`);
|
|
1960
|
-
if (!this.isOnCooldown(baseUrl)) {
|
|
1961
|
-
this.providersOnCoolDown.push([baseUrl, now]);
|
|
1962
|
-
if (this.store) {
|
|
1963
|
-
this.store.getState().addProviderOnCooldown(baseUrl, now);
|
|
1964
|
-
}
|
|
1965
|
-
this.logger.log(`markFailed: ${baseUrl} added to cooldown`);
|
|
1966
|
-
} else {
|
|
1967
|
-
this.logger.log(`markFailed: ${baseUrl} already on cooldown`);
|
|
1968
|
-
}
|
|
1969
|
-
} else {
|
|
1970
|
-
if (lastFailure === void 0) {
|
|
1971
|
-
this.logger.log(`markFailed: first failure for ${baseUrl}`);
|
|
1972
|
-
} else {
|
|
1973
|
-
this.logger.log(`markFailed: failure outside cooldown window for ${baseUrl} (${now - lastFailure}ms ago)`);
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
/**
|
|
1978
|
-
* Remove a provider from cooldown (e.g., after successful request)
|
|
1979
|
-
*/
|
|
1980
|
-
removeFromCooldown(baseUrl) {
|
|
1981
|
-
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1982
|
-
([url]) => url !== baseUrl
|
|
1983
|
-
);
|
|
1984
|
-
if (this.store) {
|
|
1985
|
-
this.store.getState().removeProviderFromCooldown(baseUrl);
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
/**
|
|
1989
|
-
* Clear all cooldown tracking
|
|
1990
|
-
*/
|
|
1991
|
-
clearCooldowns() {
|
|
1992
|
-
this.providersOnCoolDown = [];
|
|
1993
|
-
if (this.store) {
|
|
1994
|
-
this.store.getState().clearProvidersOnCooldown();
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
/**
|
|
1998
|
-
* Clear all failure tracking (lastFailed timestamps)
|
|
1999
|
-
*/
|
|
2000
|
-
clearFailureHistory() {
|
|
2001
|
-
this.lastFailed.clear();
|
|
2002
|
-
if (this.store) {
|
|
2003
|
-
this.store.getState().setLastFailed({});
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
/**
|
|
2007
|
-
* Check if a provider has failed
|
|
2008
|
-
*/
|
|
2009
|
-
hasFailed(baseUrl) {
|
|
2010
|
-
return this.failedProviders.has(baseUrl);
|
|
2011
|
-
}
|
|
2012
|
-
/**
|
|
2013
|
-
* Get a copy of the failed providers set
|
|
2014
|
-
*/
|
|
2015
|
-
getFailedProviders() {
|
|
2016
|
-
return new Set(this.failedProviders);
|
|
2017
|
-
}
|
|
2018
|
-
/**
|
|
2019
|
-
* Find the next best provider for a model
|
|
2020
|
-
* @param modelId The model ID to find a provider for
|
|
2021
|
-
* @param currentBaseUrl The current provider to exclude
|
|
2022
|
-
* @returns The best provider URL or null if none available
|
|
2023
|
-
*/
|
|
2024
|
-
findNextBestProvider(modelId, currentBaseUrl) {
|
|
2025
|
-
try {
|
|
2026
|
-
const torMode = isTorContext();
|
|
2027
|
-
const disabledProviders = new Set(
|
|
2028
|
-
this.providerRegistry.getDisabledProviders()
|
|
2029
|
-
);
|
|
2030
|
-
this.logger.log(`findNextBestProvider: model=${modelId} disabled=${[...disabledProviders].length} onCooldown=${this.providersOnCoolDown.length}`);
|
|
2031
|
-
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
2032
|
-
this.logger.log(`findNextBestProvider: total providers=${Object.keys(allProviders).length}`);
|
|
2033
|
-
const candidates = [];
|
|
2034
|
-
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
2035
|
-
if (baseUrl === currentBaseUrl) {
|
|
2036
|
-
continue;
|
|
2037
|
-
}
|
|
2038
|
-
if (disabledProviders.has(baseUrl)) {
|
|
2039
|
-
continue;
|
|
2040
|
-
}
|
|
2041
|
-
if (this.isOnCooldown(baseUrl)) {
|
|
2042
|
-
continue;
|
|
2043
|
-
}
|
|
2044
|
-
if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
|
|
2045
|
-
continue;
|
|
2046
|
-
}
|
|
2047
|
-
const model = models.find((m) => m.id === modelId);
|
|
2048
|
-
if (!model) {
|
|
2049
|
-
continue;
|
|
2050
|
-
}
|
|
2051
|
-
const cost = model.sats_pricing?.completion ?? 0;
|
|
2052
|
-
candidates.push({ baseUrl, model, cost });
|
|
2053
|
-
}
|
|
2054
|
-
candidates.sort((a, b) => a.cost - b.cost);
|
|
2055
|
-
if (candidates.length > 0) {
|
|
2056
|
-
return candidates[0].baseUrl;
|
|
2057
|
-
} else {
|
|
2058
|
-
return null;
|
|
2059
|
-
}
|
|
2060
|
-
} catch (error) {
|
|
2061
|
-
this.logger.error("findNextBestProvider error:", error);
|
|
2062
|
-
return null;
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
/**
|
|
2066
|
-
* Find the best model for a provider
|
|
2067
|
-
* Useful when switching providers and need to find equivalent model
|
|
2068
|
-
*/
|
|
2069
|
-
async getModelForProvider(baseUrl, modelId) {
|
|
2070
|
-
const models = this.providerRegistry.getModelsForProvider(baseUrl);
|
|
2071
|
-
const exactMatch = models.find((m) => m.id === modelId);
|
|
2072
|
-
if (exactMatch) return exactMatch;
|
|
2073
|
-
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
2074
|
-
if (providerInfo?.version && /^0\.1\./.test(providerInfo.version)) {
|
|
2075
|
-
const suffix = modelId.split("/").pop();
|
|
2076
|
-
const suffixMatch = models.find((m) => m.id === suffix);
|
|
2077
|
-
if (suffixMatch) return suffixMatch;
|
|
2078
|
-
}
|
|
2079
|
-
return null;
|
|
2080
|
-
}
|
|
2081
|
-
/**
|
|
2082
|
-
* Get all available providers for a model
|
|
2083
|
-
* Returns sorted list by price
|
|
2084
|
-
*/
|
|
2085
|
-
getAllProvidersForModel(modelId) {
|
|
2086
|
-
const candidates = [];
|
|
2087
|
-
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
2088
|
-
const disabledProviders = new Set(
|
|
2089
|
-
this.providerRegistry.getDisabledProviders()
|
|
2090
|
-
);
|
|
2091
|
-
const torMode = isTorContext();
|
|
2092
|
-
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
2093
|
-
if (disabledProviders.has(baseUrl)) continue;
|
|
2094
|
-
if (this.isOnCooldown(baseUrl)) continue;
|
|
2095
|
-
if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl)))
|
|
2096
|
-
continue;
|
|
2097
|
-
const model = models.find((m) => m.id === modelId);
|
|
2098
|
-
if (!model) continue;
|
|
2099
|
-
const cost = model.sats_pricing?.completion ?? 0;
|
|
2100
|
-
candidates.push({ baseUrl, model, cost });
|
|
2101
|
-
}
|
|
2102
|
-
return candidates.sort((a, b) => a.cost - b.cost);
|
|
2103
|
-
}
|
|
2104
|
-
/**
|
|
2105
|
-
* Get providers for a model sorted by prompt+completion pricing
|
|
1793
|
+
* Get providers for a model sorted by prompt+completion pricing
|
|
2106
1794
|
*/
|
|
2107
1795
|
getProviderPriceRankingForModel(modelId, options = {}) {
|
|
2108
1796
|
const includeDisabled = options.includeDisabled ?? false;
|
|
2109
1797
|
const torMode = options.torMode ?? false;
|
|
2110
|
-
const
|
|
2111
|
-
|
|
2112
|
-
)
|
|
1798
|
+
const disabledProviderList = this.providerRegistry.getDisabledProviders();
|
|
1799
|
+
const disabledProviders = new Set(disabledProviderList);
|
|
1800
|
+
if (disabledProviderList.length > 0) {
|
|
1801
|
+
this.logger.log(`getProviderPriceRankingForModel: disabled providers (${disabledProviderList.length}): ${disabledProviderList.join(", ")}`);
|
|
1802
|
+
}
|
|
2113
1803
|
const allModels = this.providerRegistry.getAllProvidersModels();
|
|
2114
1804
|
const results = [];
|
|
2115
1805
|
for (const [baseUrl, models] of Object.entries(allModels)) {
|
|
2116
1806
|
if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
|
|
2117
1807
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
2118
1808
|
if (torMode && !baseUrl.includes(".onion")) continue;
|
|
2119
|
-
if (!torMode &&
|
|
1809
|
+
if (!torMode && baseUrl.includes(".onion"))
|
|
2120
1810
|
continue;
|
|
2121
1811
|
const match = models.find((model) => model.id === modelId);
|
|
2122
1812
|
if (!match?.sats_pricing) continue;
|
|
@@ -2136,12 +1826,20 @@ var ProviderManager = class _ProviderManager {
|
|
|
2136
1826
|
totalPerMillion
|
|
2137
1827
|
});
|
|
2138
1828
|
}
|
|
2139
|
-
|
|
1829
|
+
results.sort((a, b) => {
|
|
2140
1830
|
if (a.totalPerMillion !== b.totalPerMillion) {
|
|
2141
1831
|
return a.totalPerMillion - b.totalPerMillion;
|
|
2142
1832
|
}
|
|
2143
1833
|
return a.baseUrl.localeCompare(b.baseUrl);
|
|
2144
1834
|
});
|
|
1835
|
+
if (results.length > 0) {
|
|
1836
|
+
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");
|
|
1837
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} ranking (${results.length} providers):
|
|
1838
|
+
${ranking}`);
|
|
1839
|
+
} else {
|
|
1840
|
+
this.logger.log(`getProviderPriceRankingForModel: ${modelId} no providers found`);
|
|
1841
|
+
}
|
|
1842
|
+
return results;
|
|
2145
1843
|
}
|
|
2146
1844
|
/**
|
|
2147
1845
|
* Get best-priced provider for a specific model
|
|
@@ -2329,92 +2027,6 @@ var createMemoryDriver = (seed) => {
|
|
|
2329
2027
|
};
|
|
2330
2028
|
};
|
|
2331
2029
|
|
|
2332
|
-
// storage/drivers/sqlite.ts
|
|
2333
|
-
var isBun = () => {
|
|
2334
|
-
return typeof process.versions.bun !== "undefined";
|
|
2335
|
-
};
|
|
2336
|
-
var cachedDbModule = null;
|
|
2337
|
-
var loadDatabase = async (dbPath) => {
|
|
2338
|
-
if (isBun()) {
|
|
2339
|
-
throw new Error(
|
|
2340
|
-
"SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
|
|
2341
|
-
);
|
|
2342
|
-
}
|
|
2343
|
-
try {
|
|
2344
|
-
if (!cachedDbModule) {
|
|
2345
|
-
cachedDbModule = (await import('better-sqlite3')).default;
|
|
2346
|
-
}
|
|
2347
|
-
return new cachedDbModule(dbPath);
|
|
2348
|
-
} catch (error) {
|
|
2349
|
-
throw new Error(
|
|
2350
|
-
`better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
|
|
2351
|
-
);
|
|
2352
|
-
}
|
|
2353
|
-
};
|
|
2354
|
-
var createSqliteDriver = (options = {}) => {
|
|
2355
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2356
|
-
const tableName = options.tableName || "sdk_storage";
|
|
2357
|
-
let db;
|
|
2358
|
-
let selectStmt;
|
|
2359
|
-
let upsertStmt;
|
|
2360
|
-
let deleteStmt;
|
|
2361
|
-
const initDb = async () => {
|
|
2362
|
-
if (!db) {
|
|
2363
|
-
db = await loadDatabase(dbPath);
|
|
2364
|
-
db.exec(
|
|
2365
|
-
`CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
2366
|
-
);
|
|
2367
|
-
selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
|
|
2368
|
-
upsertStmt = db.prepare(
|
|
2369
|
-
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)
|
|
2370
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
2371
|
-
);
|
|
2372
|
-
deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
|
|
2373
|
-
}
|
|
2374
|
-
};
|
|
2375
|
-
const ensureInit = async () => {
|
|
2376
|
-
if (!db) {
|
|
2377
|
-
await initDb();
|
|
2378
|
-
}
|
|
2379
|
-
};
|
|
2380
|
-
return {
|
|
2381
|
-
async getItem(key, defaultValue) {
|
|
2382
|
-
try {
|
|
2383
|
-
await ensureInit();
|
|
2384
|
-
const row = selectStmt.get(key);
|
|
2385
|
-
if (!row || typeof row.value !== "string") return defaultValue;
|
|
2386
|
-
try {
|
|
2387
|
-
return JSON.parse(row.value);
|
|
2388
|
-
} catch (parseError) {
|
|
2389
|
-
if (typeof defaultValue === "string") {
|
|
2390
|
-
return row.value;
|
|
2391
|
-
}
|
|
2392
|
-
throw parseError;
|
|
2393
|
-
}
|
|
2394
|
-
} catch (error) {
|
|
2395
|
-
console.error(`SQLite getItem failed for key "${key}":`, error);
|
|
2396
|
-
return defaultValue;
|
|
2397
|
-
}
|
|
2398
|
-
},
|
|
2399
|
-
async setItem(key, value) {
|
|
2400
|
-
try {
|
|
2401
|
-
await ensureInit();
|
|
2402
|
-
upsertStmt.run(key, JSON.stringify(value));
|
|
2403
|
-
} catch (error) {
|
|
2404
|
-
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
2405
|
-
}
|
|
2406
|
-
},
|
|
2407
|
-
async removeItem(key) {
|
|
2408
|
-
try {
|
|
2409
|
-
await ensureInit();
|
|
2410
|
-
deleteStmt.run(key);
|
|
2411
|
-
} catch (error) {
|
|
2412
|
-
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
};
|
|
2416
|
-
};
|
|
2417
|
-
|
|
2418
2030
|
// storage/keys.ts
|
|
2419
2031
|
var SDK_STORAGE_KEYS = {
|
|
2420
2032
|
MODELS_FROM_ALL_PROVIDERS: "modelsFromAllProviders",
|
|
@@ -2449,9 +2061,10 @@ var openDatabase = (dbName, storeName) => {
|
|
|
2449
2061
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
2450
2062
|
}
|
|
2451
2063
|
return new Promise((resolve, reject) => {
|
|
2452
|
-
const request = indexedDB.open(dbName,
|
|
2064
|
+
const request = indexedDB.open(dbName, 3);
|
|
2453
2065
|
request.onupgradeneeded = () => {
|
|
2454
2066
|
const db = request.result;
|
|
2067
|
+
const tx = request.transaction;
|
|
2455
2068
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
2456
2069
|
const store = db.createObjectStore(storeName, { keyPath: "id" });
|
|
2457
2070
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
@@ -2459,10 +2072,25 @@ var openDatabase = (dbName, storeName) => {
|
|
|
2459
2072
|
store.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
2460
2073
|
store.createIndex("sessionId", "sessionId", { unique: false });
|
|
2461
2074
|
store.createIndex("client", "client", { unique: false });
|
|
2075
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
2076
|
+
} else if (tx) {
|
|
2077
|
+
const store = tx.objectStore(storeName);
|
|
2078
|
+
if (!store.indexNames.contains("provider")) {
|
|
2079
|
+
store.createIndex("provider", "provider", { unique: false });
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
2083
|
+
db.createObjectStore("sdk_storage");
|
|
2462
2084
|
}
|
|
2463
2085
|
};
|
|
2464
2086
|
request.onsuccess = () => resolve(request.result);
|
|
2465
2087
|
request.onerror = () => reject(request.error);
|
|
2088
|
+
request.onblocked = () => {
|
|
2089
|
+
console.warn(
|
|
2090
|
+
`[usageTracking IndexedDB] open blocked for "${dbName}" \u2014 close other tabs using this DB`
|
|
2091
|
+
);
|
|
2092
|
+
reject(new Error(`IndexedDB "${dbName}" blocked by another connection`));
|
|
2093
|
+
};
|
|
2466
2094
|
});
|
|
2467
2095
|
};
|
|
2468
2096
|
var matchesFilters = (entry, options = {}) => {
|
|
@@ -2484,6 +2112,9 @@ var matchesFilters = (entry, options = {}) => {
|
|
|
2484
2112
|
if (options.client && entry.client !== options.client) {
|
|
2485
2113
|
return false;
|
|
2486
2114
|
}
|
|
2115
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
2116
|
+
return false;
|
|
2117
|
+
}
|
|
2487
2118
|
return true;
|
|
2488
2119
|
};
|
|
2489
2120
|
var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
@@ -2615,393 +2246,8 @@ var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
|
2615
2246
|
};
|
|
2616
2247
|
};
|
|
2617
2248
|
|
|
2618
|
-
// storage/usageTracking/sqlite.ts
|
|
2619
|
-
var MIGRATION_MARKER_KEY2 = "usage_tracking_migration_v1";
|
|
2620
|
-
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2621
|
-
var isBun2 = () => {
|
|
2622
|
-
return typeof process.versions.bun !== "undefined";
|
|
2623
|
-
};
|
|
2624
|
-
var cachedDbModule2 = null;
|
|
2625
|
-
var loadDatabase2 = async (dbPath) => {
|
|
2626
|
-
if (isBun2()) {
|
|
2627
|
-
throw new Error(
|
|
2628
|
-
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
2629
|
-
);
|
|
2630
|
-
}
|
|
2631
|
-
try {
|
|
2632
|
-
if (!cachedDbModule2) {
|
|
2633
|
-
cachedDbModule2 = (await import('better-sqlite3')).default;
|
|
2634
|
-
}
|
|
2635
|
-
return new cachedDbModule2(dbPath);
|
|
2636
|
-
} catch (error) {
|
|
2637
|
-
throw new Error(
|
|
2638
|
-
`better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
|
|
2639
|
-
);
|
|
2640
|
-
}
|
|
2641
|
-
};
|
|
2642
|
-
var buildWhereClause = (options = {}) => {
|
|
2643
|
-
const clauses = [];
|
|
2644
|
-
const params = [];
|
|
2645
|
-
if (typeof options.before === "number") {
|
|
2646
|
-
clauses.push("timestamp < ?");
|
|
2647
|
-
params.push(options.before);
|
|
2648
|
-
}
|
|
2649
|
-
if (typeof options.after === "number") {
|
|
2650
|
-
clauses.push("timestamp > ?");
|
|
2651
|
-
params.push(options.after);
|
|
2652
|
-
}
|
|
2653
|
-
if (options.modelId) {
|
|
2654
|
-
clauses.push("model_id = ?");
|
|
2655
|
-
params.push(options.modelId);
|
|
2656
|
-
}
|
|
2657
|
-
if (options.baseUrl) {
|
|
2658
|
-
clauses.push("base_url = ?");
|
|
2659
|
-
params.push(normalizeBaseUrl2(options.baseUrl));
|
|
2660
|
-
}
|
|
2661
|
-
if (options.sessionId) {
|
|
2662
|
-
clauses.push("session_id = ?");
|
|
2663
|
-
params.push(options.sessionId);
|
|
2664
|
-
}
|
|
2665
|
-
if (options.client) {
|
|
2666
|
-
clauses.push("client = ?");
|
|
2667
|
-
params.push(options.client);
|
|
2668
|
-
}
|
|
2669
|
-
return {
|
|
2670
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
2671
|
-
params
|
|
2672
|
-
};
|
|
2673
|
-
};
|
|
2674
|
-
var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
2675
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2676
|
-
const tableName = options.tableName || "usage_tracking";
|
|
2677
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2678
|
-
let db;
|
|
2679
|
-
let insertStmt;
|
|
2680
|
-
let migrationComplete = false;
|
|
2681
|
-
const initDb = async () => {
|
|
2682
|
-
if (!db) {
|
|
2683
|
-
db = await loadDatabase2(dbPath);
|
|
2684
|
-
db.exec(`
|
|
2685
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2686
|
-
id TEXT PRIMARY KEY,
|
|
2687
|
-
timestamp INTEGER NOT NULL,
|
|
2688
|
-
model_id TEXT NOT NULL,
|
|
2689
|
-
base_url TEXT NOT NULL,
|
|
2690
|
-
request_id TEXT NOT NULL,
|
|
2691
|
-
cost REAL NOT NULL,
|
|
2692
|
-
sats_cost REAL NOT NULL,
|
|
2693
|
-
prompt_tokens INTEGER NOT NULL,
|
|
2694
|
-
completion_tokens INTEGER NOT NULL,
|
|
2695
|
-
total_tokens INTEGER NOT NULL,
|
|
2696
|
-
client TEXT,
|
|
2697
|
-
session_id TEXT,
|
|
2698
|
-
tags TEXT
|
|
2699
|
-
);
|
|
2700
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
2701
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
2702
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
2703
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
2704
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
2705
|
-
`);
|
|
2706
|
-
insertStmt = db.prepare(`
|
|
2707
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
2708
|
-
id, timestamp, model_id, base_url, request_id,
|
|
2709
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2710
|
-
client, session_id, tags
|
|
2711
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2712
|
-
`);
|
|
2713
|
-
}
|
|
2714
|
-
};
|
|
2715
|
-
const ensureInit = async () => {
|
|
2716
|
-
if (!db) {
|
|
2717
|
-
await initDb();
|
|
2718
|
-
}
|
|
2719
|
-
};
|
|
2720
|
-
const appendOne = (entry) => {
|
|
2721
|
-
insertStmt.run(
|
|
2722
|
-
entry.id,
|
|
2723
|
-
entry.timestamp,
|
|
2724
|
-
entry.modelId,
|
|
2725
|
-
normalizeBaseUrl2(entry.baseUrl),
|
|
2726
|
-
entry.requestId,
|
|
2727
|
-
entry.cost,
|
|
2728
|
-
entry.satsCost,
|
|
2729
|
-
entry.promptTokens,
|
|
2730
|
-
entry.completionTokens,
|
|
2731
|
-
entry.totalTokens,
|
|
2732
|
-
entry.client ?? null,
|
|
2733
|
-
entry.sessionId ?? null,
|
|
2734
|
-
JSON.stringify(entry.tags ?? [])
|
|
2735
|
-
);
|
|
2736
|
-
};
|
|
2737
|
-
const ensureMigrated = async () => {
|
|
2738
|
-
if (!legacyStorageDriver || migrationComplete) return;
|
|
2739
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
2740
|
-
MIGRATION_MARKER_KEY2,
|
|
2741
|
-
false
|
|
2742
|
-
);
|
|
2743
|
-
if (migrated) {
|
|
2744
|
-
migrationComplete = true;
|
|
2745
|
-
return;
|
|
2746
|
-
}
|
|
2747
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2748
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2749
|
-
[]
|
|
2750
|
-
);
|
|
2751
|
-
for (const entry of legacyEntries) {
|
|
2752
|
-
appendOne(entry);
|
|
2753
|
-
}
|
|
2754
|
-
if (legacyEntries.length > 0) {
|
|
2755
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2756
|
-
}
|
|
2757
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
2758
|
-
migrationComplete = true;
|
|
2759
|
-
};
|
|
2760
|
-
const mapRow = (row) => ({
|
|
2761
|
-
id: row.id,
|
|
2762
|
-
timestamp: row.timestamp,
|
|
2763
|
-
modelId: row.model_id,
|
|
2764
|
-
baseUrl: row.base_url,
|
|
2765
|
-
requestId: row.request_id,
|
|
2766
|
-
cost: row.cost,
|
|
2767
|
-
satsCost: row.sats_cost,
|
|
2768
|
-
promptTokens: row.prompt_tokens,
|
|
2769
|
-
completionTokens: row.completion_tokens,
|
|
2770
|
-
totalTokens: row.total_tokens,
|
|
2771
|
-
client: row.client ?? void 0,
|
|
2772
|
-
sessionId: row.session_id ?? void 0,
|
|
2773
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
2774
|
-
});
|
|
2775
|
-
return {
|
|
2776
|
-
async migrate() {
|
|
2777
|
-
await ensureInit();
|
|
2778
|
-
await ensureMigrated();
|
|
2779
|
-
},
|
|
2780
|
-
async append(entry) {
|
|
2781
|
-
await ensureInit();
|
|
2782
|
-
await ensureMigrated();
|
|
2783
|
-
appendOne(entry);
|
|
2784
|
-
},
|
|
2785
|
-
async appendMany(entries) {
|
|
2786
|
-
await ensureInit();
|
|
2787
|
-
await ensureMigrated();
|
|
2788
|
-
for (const entry of entries) {
|
|
2789
|
-
appendOne(entry);
|
|
2790
|
-
}
|
|
2791
|
-
},
|
|
2792
|
-
async list(options2 = {}) {
|
|
2793
|
-
await ensureInit();
|
|
2794
|
-
await ensureMigrated();
|
|
2795
|
-
const { sql, params } = buildWhereClause(options2);
|
|
2796
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
2797
|
-
const stmt = db.prepare(
|
|
2798
|
-
`SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`
|
|
2799
|
-
);
|
|
2800
|
-
const rows = stmt.all(
|
|
2801
|
-
...typeof options2.limit === "number" ? [...params, options2.limit] : params
|
|
2802
|
-
);
|
|
2803
|
-
return rows.map(mapRow);
|
|
2804
|
-
},
|
|
2805
|
-
async count(options2 = {}) {
|
|
2806
|
-
await ensureInit();
|
|
2807
|
-
await ensureMigrated();
|
|
2808
|
-
const { sql, params } = buildWhereClause(options2);
|
|
2809
|
-
const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
|
|
2810
|
-
const row = stmt.get(...params);
|
|
2811
|
-
return Number(row?.count ?? 0);
|
|
2812
|
-
},
|
|
2813
|
-
async deleteOlderThan(timestamp) {
|
|
2814
|
-
await ensureInit();
|
|
2815
|
-
await ensureMigrated();
|
|
2816
|
-
const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
|
|
2817
|
-
const result = stmt.run(timestamp);
|
|
2818
|
-
return result.changes;
|
|
2819
|
-
},
|
|
2820
|
-
async clear() {
|
|
2821
|
-
await ensureInit();
|
|
2822
|
-
await ensureMigrated();
|
|
2823
|
-
db.prepare(`DELETE FROM ${tableName}`).run();
|
|
2824
|
-
}
|
|
2825
|
-
};
|
|
2826
|
-
};
|
|
2827
|
-
|
|
2828
|
-
// storage/usageTracking/bunSqlite.ts
|
|
2829
|
-
var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
|
|
2830
|
-
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2831
|
-
var buildWhereClause2 = (options = {}) => {
|
|
2832
|
-
const clauses = [];
|
|
2833
|
-
const params = [];
|
|
2834
|
-
if (typeof options.before === "number") {
|
|
2835
|
-
clauses.push("timestamp < ?");
|
|
2836
|
-
params.push(options.before);
|
|
2837
|
-
}
|
|
2838
|
-
if (typeof options.after === "number") {
|
|
2839
|
-
clauses.push("timestamp > ?");
|
|
2840
|
-
params.push(options.after);
|
|
2841
|
-
}
|
|
2842
|
-
if (options.modelId) {
|
|
2843
|
-
clauses.push("model_id = ?");
|
|
2844
|
-
params.push(options.modelId);
|
|
2845
|
-
}
|
|
2846
|
-
if (options.baseUrl) {
|
|
2847
|
-
clauses.push("base_url = ?");
|
|
2848
|
-
params.push(normalizeBaseUrl3(options.baseUrl));
|
|
2849
|
-
}
|
|
2850
|
-
if (options.sessionId) {
|
|
2851
|
-
clauses.push("session_id = ?");
|
|
2852
|
-
params.push(options.sessionId);
|
|
2853
|
-
}
|
|
2854
|
-
if (options.client) {
|
|
2855
|
-
clauses.push("client = ?");
|
|
2856
|
-
params.push(options.client);
|
|
2857
|
-
}
|
|
2858
|
-
return {
|
|
2859
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
2860
|
-
params
|
|
2861
|
-
};
|
|
2862
|
-
};
|
|
2863
|
-
var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
2864
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2865
|
-
const tableName = options.tableName || "usage_tracking";
|
|
2866
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2867
|
-
const SQLiteDatabase = options.sqlite?.Database;
|
|
2868
|
-
let migrationPromise = null;
|
|
2869
|
-
if (!SQLiteDatabase) {
|
|
2870
|
-
throw new Error(
|
|
2871
|
-
"Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
|
|
2872
|
-
);
|
|
2873
|
-
}
|
|
2874
|
-
const db = new SQLiteDatabase(dbPath);
|
|
2875
|
-
db.run(`
|
|
2876
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2877
|
-
id TEXT PRIMARY KEY,
|
|
2878
|
-
timestamp INTEGER NOT NULL,
|
|
2879
|
-
model_id TEXT NOT NULL,
|
|
2880
|
-
base_url TEXT NOT NULL,
|
|
2881
|
-
request_id TEXT NOT NULL,
|
|
2882
|
-
cost REAL NOT NULL,
|
|
2883
|
-
sats_cost REAL NOT NULL,
|
|
2884
|
-
prompt_tokens INTEGER NOT NULL,
|
|
2885
|
-
completion_tokens INTEGER NOT NULL,
|
|
2886
|
-
total_tokens INTEGER NOT NULL,
|
|
2887
|
-
client TEXT,
|
|
2888
|
-
session_id TEXT,
|
|
2889
|
-
tags TEXT
|
|
2890
|
-
)
|
|
2891
|
-
`);
|
|
2892
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
|
|
2893
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
|
|
2894
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
|
|
2895
|
-
const appendOne = (entry) => {
|
|
2896
|
-
db.query(`
|
|
2897
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
2898
|
-
id, timestamp, model_id, base_url, request_id,
|
|
2899
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2900
|
-
client, session_id, tags
|
|
2901
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2902
|
-
`).run(
|
|
2903
|
-
entry.id,
|
|
2904
|
-
entry.timestamp,
|
|
2905
|
-
entry.modelId,
|
|
2906
|
-
normalizeBaseUrl3(entry.baseUrl),
|
|
2907
|
-
entry.requestId,
|
|
2908
|
-
entry.cost,
|
|
2909
|
-
entry.satsCost,
|
|
2910
|
-
entry.promptTokens,
|
|
2911
|
-
entry.completionTokens,
|
|
2912
|
-
entry.totalTokens,
|
|
2913
|
-
entry.client ?? null,
|
|
2914
|
-
entry.sessionId ?? null,
|
|
2915
|
-
JSON.stringify(entry.tags ?? [])
|
|
2916
|
-
);
|
|
2917
|
-
};
|
|
2918
|
-
const mapRow = (row) => ({
|
|
2919
|
-
id: row.id,
|
|
2920
|
-
timestamp: row.timestamp,
|
|
2921
|
-
modelId: row.model_id,
|
|
2922
|
-
baseUrl: row.base_url,
|
|
2923
|
-
requestId: row.request_id,
|
|
2924
|
-
cost: row.cost,
|
|
2925
|
-
satsCost: row.sats_cost,
|
|
2926
|
-
promptTokens: row.prompt_tokens,
|
|
2927
|
-
completionTokens: row.completion_tokens,
|
|
2928
|
-
totalTokens: row.total_tokens,
|
|
2929
|
-
client: row.client ?? void 0,
|
|
2930
|
-
sessionId: row.session_id ?? void 0,
|
|
2931
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
2932
|
-
});
|
|
2933
|
-
const ensureMigrated = async () => {
|
|
2934
|
-
if (!legacyStorageDriver) return;
|
|
2935
|
-
if (!migrationPromise) {
|
|
2936
|
-
migrationPromise = (async () => {
|
|
2937
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
2938
|
-
MIGRATION_MARKER_KEY3,
|
|
2939
|
-
false
|
|
2940
|
-
);
|
|
2941
|
-
if (migrated) return;
|
|
2942
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2943
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2944
|
-
[]
|
|
2945
|
-
);
|
|
2946
|
-
if (legacyEntries.length > 0) {
|
|
2947
|
-
for (const entry of legacyEntries) {
|
|
2948
|
-
appendOne(entry);
|
|
2949
|
-
}
|
|
2950
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2951
|
-
}
|
|
2952
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
|
|
2953
|
-
})();
|
|
2954
|
-
}
|
|
2955
|
-
await migrationPromise;
|
|
2956
|
-
};
|
|
2957
|
-
return {
|
|
2958
|
-
async migrate() {
|
|
2959
|
-
await ensureMigrated();
|
|
2960
|
-
},
|
|
2961
|
-
async append(entry) {
|
|
2962
|
-
await ensureMigrated();
|
|
2963
|
-
appendOne(entry);
|
|
2964
|
-
},
|
|
2965
|
-
async appendMany(entries) {
|
|
2966
|
-
await ensureMigrated();
|
|
2967
|
-
for (const entry of entries) {
|
|
2968
|
-
appendOne(entry);
|
|
2969
|
-
}
|
|
2970
|
-
},
|
|
2971
|
-
async list(options2 = {}) {
|
|
2972
|
-
await ensureMigrated();
|
|
2973
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
2974
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
2975
|
-
const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
|
|
2976
|
-
let rows;
|
|
2977
|
-
if (typeof options2.limit === "number") {
|
|
2978
|
-
rows = db.query(query).all(...params, options2.limit);
|
|
2979
|
-
} else {
|
|
2980
|
-
rows = db.query(query).all(...params);
|
|
2981
|
-
}
|
|
2982
|
-
return rows.map(mapRow);
|
|
2983
|
-
},
|
|
2984
|
-
async count(options2 = {}) {
|
|
2985
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
2986
|
-
const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
|
|
2987
|
-
const row = db.query(query).get(...params);
|
|
2988
|
-
return Number(row?.count ?? 0);
|
|
2989
|
-
},
|
|
2990
|
-
async deleteOlderThan(timestamp) {
|
|
2991
|
-
await ensureMigrated();
|
|
2992
|
-
const before = timestamp;
|
|
2993
|
-
const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
|
|
2994
|
-
return result.changes ?? 0;
|
|
2995
|
-
},
|
|
2996
|
-
async clear() {
|
|
2997
|
-
await ensureMigrated();
|
|
2998
|
-
db.query(`DELETE FROM ${tableName}`).run();
|
|
2999
|
-
}
|
|
3000
|
-
};
|
|
3001
|
-
};
|
|
3002
|
-
|
|
3003
2249
|
// storage/usageTracking/memory.ts
|
|
3004
|
-
var
|
|
2250
|
+
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3005
2251
|
var matchesFilters2 = (entry, options = {}) => {
|
|
3006
2252
|
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
3007
2253
|
return false;
|
|
@@ -3012,7 +2258,7 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
3012
2258
|
if (options.modelId && entry.modelId !== options.modelId) {
|
|
3013
2259
|
return false;
|
|
3014
2260
|
}
|
|
3015
|
-
if (options.baseUrl &&
|
|
2261
|
+
if (options.baseUrl && normalizeBaseUrl2(entry.baseUrl) !== normalizeBaseUrl2(options.baseUrl)) {
|
|
3016
2262
|
return false;
|
|
3017
2263
|
}
|
|
3018
2264
|
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
@@ -3021,23 +2267,26 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
3021
2267
|
if (options.client && entry.client !== options.client) {
|
|
3022
2268
|
return false;
|
|
3023
2269
|
}
|
|
2270
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
2271
|
+
return false;
|
|
2272
|
+
}
|
|
3024
2273
|
return true;
|
|
3025
2274
|
};
|
|
3026
2275
|
var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
3027
2276
|
const store = /* @__PURE__ */ new Map();
|
|
3028
2277
|
for (const entry of seed) {
|
|
3029
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2278
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
3030
2279
|
}
|
|
3031
2280
|
return {
|
|
3032
2281
|
async migrate() {
|
|
3033
2282
|
return;
|
|
3034
2283
|
},
|
|
3035
2284
|
async append(entry) {
|
|
3036
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2285
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
3037
2286
|
},
|
|
3038
2287
|
async appendMany(entries) {
|
|
3039
2288
|
for (const entry of entries) {
|
|
3040
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2289
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
3041
2290
|
}
|
|
3042
2291
|
},
|
|
3043
2292
|
async list(options = {}) {
|
|
@@ -3065,7 +2314,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
3065
2314
|
}
|
|
3066
2315
|
};
|
|
3067
2316
|
};
|
|
3068
|
-
var
|
|
2317
|
+
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3069
2318
|
var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
3070
2319
|
modelsFromAllProviders: {},
|
|
3071
2320
|
lastUsedModel: null,
|
|
@@ -3088,7 +2337,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3088
2337
|
setModelsFromAllProviders: (value) => {
|
|
3089
2338
|
const normalized = {};
|
|
3090
2339
|
for (const [baseUrl, models] of Object.entries(value)) {
|
|
3091
|
-
normalized[
|
|
2340
|
+
normalized[normalizeBaseUrl3(baseUrl)] = models;
|
|
3092
2341
|
}
|
|
3093
2342
|
void driver.setItem(
|
|
3094
2343
|
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
@@ -3101,7 +2350,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3101
2350
|
set({ lastUsedModel: value });
|
|
3102
2351
|
},
|
|
3103
2352
|
setBaseUrlsList: (value) => {
|
|
3104
|
-
const normalized = value.map((url) =>
|
|
2353
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
3105
2354
|
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
3106
2355
|
set({ baseUrlsList: normalized });
|
|
3107
2356
|
},
|
|
@@ -3110,14 +2359,14 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3110
2359
|
set({ lastBaseUrlsUpdate: value });
|
|
3111
2360
|
},
|
|
3112
2361
|
setDisabledProviders: (value) => {
|
|
3113
|
-
const normalized = value.map((url) =>
|
|
2362
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
3114
2363
|
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
3115
2364
|
set({ disabledProviders: normalized });
|
|
3116
2365
|
},
|
|
3117
2366
|
setMintsFromAllProviders: (value) => {
|
|
3118
2367
|
const normalized = {};
|
|
3119
2368
|
for (const [baseUrl, mints] of Object.entries(value)) {
|
|
3120
|
-
normalized[
|
|
2369
|
+
normalized[normalizeBaseUrl3(baseUrl)] = mints.map(
|
|
3121
2370
|
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
3122
2371
|
);
|
|
3123
2372
|
}
|
|
@@ -3130,7 +2379,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3130
2379
|
setInfoFromAllProviders: (value) => {
|
|
3131
2380
|
const normalized = {};
|
|
3132
2381
|
for (const [baseUrl, info] of Object.entries(value)) {
|
|
3133
|
-
normalized[
|
|
2382
|
+
normalized[normalizeBaseUrl3(baseUrl)] = info;
|
|
3134
2383
|
}
|
|
3135
2384
|
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
3136
2385
|
set({ infoFromAllProviders: normalized });
|
|
@@ -3138,7 +2387,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3138
2387
|
setLastModelsUpdate: (value) => {
|
|
3139
2388
|
const normalized = {};
|
|
3140
2389
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
3141
|
-
normalized[
|
|
2390
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
3142
2391
|
}
|
|
3143
2392
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
3144
2393
|
set({ lastModelsUpdate: normalized });
|
|
@@ -3148,7 +2397,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3148
2397
|
const updates = typeof value === "function" ? value(state.apiKeys) : value;
|
|
3149
2398
|
const normalized = updates.map((entry) => ({
|
|
3150
2399
|
...entry,
|
|
3151
|
-
baseUrl:
|
|
2400
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3152
2401
|
balance: entry.balance ?? 0,
|
|
3153
2402
|
lastUsed: entry.lastUsed ?? null
|
|
3154
2403
|
}));
|
|
@@ -3160,7 +2409,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3160
2409
|
set((state) => {
|
|
3161
2410
|
const updates = typeof value === "function" ? value(state.childKeys) : value;
|
|
3162
2411
|
const normalized = updates.map((entry) => ({
|
|
3163
|
-
parentBaseUrl:
|
|
2412
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
3164
2413
|
childKey: entry.childKey,
|
|
3165
2414
|
balance: entry.balance ?? 0,
|
|
3166
2415
|
balanceLimit: entry.balanceLimit,
|
|
@@ -3174,9 +2423,9 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3174
2423
|
setXcashuTokens: (value) => {
|
|
3175
2424
|
const normalized = {};
|
|
3176
2425
|
for (const [baseUrl, tokens] of Object.entries(value)) {
|
|
3177
|
-
normalized[
|
|
2426
|
+
normalized[normalizeBaseUrl3(baseUrl)] = tokens.map((entry) => ({
|
|
3178
2427
|
...entry,
|
|
3179
|
-
baseUrl:
|
|
2428
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3180
2429
|
createdAt: entry.createdAt ?? Date.now(),
|
|
3181
2430
|
tryCount: entry.tryCount ?? 0
|
|
3182
2431
|
}));
|
|
@@ -3227,12 +2476,12 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3227
2476
|
},
|
|
3228
2477
|
// ========== Failure Tracking ==========
|
|
3229
2478
|
setFailedProviders: (value) => {
|
|
3230
|
-
const normalized = value.map((url) =>
|
|
2479
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
3231
2480
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
|
|
3232
2481
|
set({ failedProviders: normalized });
|
|
3233
2482
|
},
|
|
3234
2483
|
addFailedProvider: (baseUrl) => {
|
|
3235
|
-
const normalized =
|
|
2484
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3236
2485
|
const current = get().failedProviders;
|
|
3237
2486
|
if (!current.includes(normalized)) {
|
|
3238
2487
|
const updated = [...current, normalized];
|
|
@@ -3241,7 +2490,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3241
2490
|
}
|
|
3242
2491
|
},
|
|
3243
2492
|
removeFailedProvider: (baseUrl) => {
|
|
3244
|
-
const normalized =
|
|
2493
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3245
2494
|
const current = get().failedProviders;
|
|
3246
2495
|
const updated = current.filter((url) => url !== normalized);
|
|
3247
2496
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
@@ -3250,13 +2499,13 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3250
2499
|
setLastFailed: (value) => {
|
|
3251
2500
|
const normalized = {};
|
|
3252
2501
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
3253
|
-
normalized[
|
|
2502
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
3254
2503
|
}
|
|
3255
2504
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
|
|
3256
2505
|
set({ lastFailed: normalized });
|
|
3257
2506
|
},
|
|
3258
2507
|
setLastFailedTimestamp: (baseUrl, timestamp) => {
|
|
3259
|
-
const normalized =
|
|
2508
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3260
2509
|
const current = get().lastFailed;
|
|
3261
2510
|
const updated = { ...current, [normalized]: timestamp };
|
|
3262
2511
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
|
|
@@ -3264,14 +2513,14 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3264
2513
|
},
|
|
3265
2514
|
setProvidersOnCooldown: (value) => {
|
|
3266
2515
|
const normalized = value.map((entry) => ({
|
|
3267
|
-
baseUrl:
|
|
2516
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3268
2517
|
timestamp: entry.timestamp
|
|
3269
2518
|
}));
|
|
3270
2519
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
|
|
3271
2520
|
set({ providersOnCooldown: normalized });
|
|
3272
2521
|
},
|
|
3273
2522
|
addProviderOnCooldown: (baseUrl, timestamp) => {
|
|
3274
|
-
const normalized =
|
|
2523
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3275
2524
|
const current = get().providersOnCooldown;
|
|
3276
2525
|
if (!current.some((entry) => entry.baseUrl === normalized)) {
|
|
3277
2526
|
const updated = [...current, { baseUrl: normalized, timestamp }];
|
|
@@ -3280,7 +2529,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
3280
2529
|
}
|
|
3281
2530
|
},
|
|
3282
2531
|
removeProviderFromCooldown: (baseUrl) => {
|
|
3283
|
-
const normalized =
|
|
2532
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3284
2533
|
const current = get().providersOnCooldown;
|
|
3285
2534
|
const updated = current.filter((entry) => entry.baseUrl !== normalized);
|
|
3286
2535
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
@@ -3351,40 +2600,40 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3351
2600
|
]);
|
|
3352
2601
|
const modelsFromAllProviders = Object.fromEntries(
|
|
3353
2602
|
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
3354
|
-
|
|
2603
|
+
normalizeBaseUrl3(baseUrl),
|
|
3355
2604
|
models
|
|
3356
2605
|
])
|
|
3357
2606
|
);
|
|
3358
|
-
const baseUrlsList = rawBaseUrls.map((url) =>
|
|
2607
|
+
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl3(url));
|
|
3359
2608
|
const disabledProviders = rawDisabledProviders.map(
|
|
3360
|
-
(url) =>
|
|
2609
|
+
(url) => normalizeBaseUrl3(url)
|
|
3361
2610
|
);
|
|
3362
2611
|
const mintsFromAllProviders = Object.fromEntries(
|
|
3363
2612
|
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
3364
|
-
|
|
2613
|
+
normalizeBaseUrl3(baseUrl),
|
|
3365
2614
|
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
3366
2615
|
])
|
|
3367
2616
|
);
|
|
3368
2617
|
const infoFromAllProviders = Object.fromEntries(
|
|
3369
2618
|
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
3370
|
-
|
|
2619
|
+
normalizeBaseUrl3(baseUrl),
|
|
3371
2620
|
info
|
|
3372
2621
|
])
|
|
3373
2622
|
);
|
|
3374
2623
|
const lastModelsUpdate = Object.fromEntries(
|
|
3375
2624
|
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
3376
|
-
|
|
2625
|
+
normalizeBaseUrl3(baseUrl),
|
|
3377
2626
|
timestamp
|
|
3378
2627
|
])
|
|
3379
2628
|
);
|
|
3380
2629
|
const apiKeys = rawApiKeys.map((entry) => ({
|
|
3381
2630
|
...entry,
|
|
3382
|
-
baseUrl:
|
|
2631
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3383
2632
|
balance: entry.balance ?? 0,
|
|
3384
2633
|
lastUsed: entry.lastUsed ?? null
|
|
3385
2634
|
}));
|
|
3386
2635
|
const childKeys = rawChildKeys.map((entry) => ({
|
|
3387
|
-
parentBaseUrl:
|
|
2636
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
3388
2637
|
childKey: entry.childKey,
|
|
3389
2638
|
balance: entry.balance ?? 0,
|
|
3390
2639
|
balanceLimit: entry.balanceLimit,
|
|
@@ -3393,9 +2642,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3393
2642
|
}));
|
|
3394
2643
|
const xcashuTokens = Object.fromEntries(
|
|
3395
2644
|
Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
|
|
3396
|
-
|
|
2645
|
+
normalizeBaseUrl3(baseUrl),
|
|
3397
2646
|
tokens.map((entry) => ({
|
|
3398
|
-
baseUrl:
|
|
2647
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3399
2648
|
token: entry.token,
|
|
3400
2649
|
createdAt: entry.createdAt ?? Date.now(),
|
|
3401
2650
|
tryCount: entry.tryCount ?? 0
|
|
@@ -3416,16 +2665,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3416
2665
|
lastUsed: entry.lastUsed ?? null
|
|
3417
2666
|
}));
|
|
3418
2667
|
const failedProviders = rawFailedProviders.map(
|
|
3419
|
-
(url) =>
|
|
2668
|
+
(url) => normalizeBaseUrl3(url)
|
|
3420
2669
|
);
|
|
3421
2670
|
const lastFailed = Object.fromEntries(
|
|
3422
2671
|
Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
|
|
3423
|
-
|
|
2672
|
+
normalizeBaseUrl3(baseUrl),
|
|
3424
2673
|
timestamp
|
|
3425
2674
|
])
|
|
3426
2675
|
);
|
|
3427
2676
|
const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
|
|
3428
|
-
baseUrl:
|
|
2677
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3429
2678
|
timestamp: entry.timestamp
|
|
3430
2679
|
}));
|
|
3431
2680
|
store.setState({
|
|
@@ -3467,31 +2716,13 @@ var isBrowser2 = () => {
|
|
|
3467
2716
|
return false;
|
|
3468
2717
|
}
|
|
3469
2718
|
};
|
|
3470
|
-
var isNode = () => {
|
|
3471
|
-
try {
|
|
3472
|
-
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
3473
|
-
} catch {
|
|
3474
|
-
return false;
|
|
3475
|
-
}
|
|
3476
|
-
};
|
|
3477
2719
|
var defaultDriver = null;
|
|
3478
|
-
var isBun3 = () => {
|
|
3479
|
-
return typeof process.versions.bun !== "undefined";
|
|
3480
|
-
};
|
|
3481
2720
|
var getDefaultSdkDriver = () => {
|
|
3482
2721
|
if (defaultDriver) return defaultDriver;
|
|
3483
2722
|
if (isBrowser2()) {
|
|
3484
2723
|
defaultDriver = localStorageDriver;
|
|
3485
2724
|
return defaultDriver;
|
|
3486
2725
|
}
|
|
3487
|
-
if (isBun3()) {
|
|
3488
|
-
defaultDriver = createMemoryDriver();
|
|
3489
|
-
return defaultDriver;
|
|
3490
|
-
}
|
|
3491
|
-
if (isNode()) {
|
|
3492
|
-
defaultDriver = createSqliteDriver();
|
|
3493
|
-
return defaultDriver;
|
|
3494
|
-
}
|
|
3495
2726
|
defaultDriver = createMemoryDriver();
|
|
3496
2727
|
return defaultDriver;
|
|
3497
2728
|
};
|
|
@@ -3512,97 +2743,258 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
3512
2743
|
});
|
|
3513
2744
|
return defaultUsageTrackingDriver;
|
|
3514
2745
|
}
|
|
3515
|
-
if (isBun3()) {
|
|
3516
|
-
defaultUsageTrackingDriver = createBunSqliteUsageTrackingDriver();
|
|
3517
|
-
return defaultUsageTrackingDriver;
|
|
3518
|
-
}
|
|
3519
|
-
if (isNode()) {
|
|
3520
|
-
defaultUsageTrackingDriver = createSqliteUsageTrackingDriver({
|
|
3521
|
-
legacyStorageDriver: storageDriver
|
|
3522
|
-
});
|
|
3523
|
-
return defaultUsageTrackingDriver;
|
|
3524
|
-
}
|
|
3525
2746
|
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
3526
2747
|
return defaultUsageTrackingDriver;
|
|
3527
2748
|
};
|
|
3528
|
-
|
|
3529
|
-
|
|
2749
|
+
|
|
2750
|
+
// client/usage.ts
|
|
2751
|
+
var numOrUndef = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2752
|
+
function extractCostBreakdown(costObj) {
|
|
2753
|
+
if (!costObj || typeof costObj !== "object") return {};
|
|
3530
2754
|
return {
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
2755
|
+
baseMsats: numOrUndef(costObj.base_msats),
|
|
2756
|
+
inputMsats: numOrUndef(costObj.input_msats),
|
|
2757
|
+
outputMsats: numOrUndef(costObj.output_msats),
|
|
2758
|
+
totalMsats: numOrUndef(costObj.total_msats),
|
|
2759
|
+
totalUsd: numOrUndef(costObj.total_usd),
|
|
2760
|
+
cacheReadInputTokens: numOrUndef(costObj.cache_read_input_tokens),
|
|
2761
|
+
cacheCreationInputTokens: numOrUndef(costObj.cache_creation_input_tokens),
|
|
2762
|
+
cacheReadMsats: numOrUndef(costObj.cache_read_msats),
|
|
2763
|
+
cacheCreationMsats: numOrUndef(costObj.cache_creation_msats),
|
|
2764
|
+
remainingBalanceMsats: numOrUndef(costObj.remaining_balance_msats)
|
|
3536
2765
|
};
|
|
3537
2766
|
}
|
|
3538
|
-
function
|
|
3539
|
-
if (!
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
const
|
|
3544
|
-
const
|
|
3545
|
-
|
|
3546
|
-
let
|
|
3547
|
-
let
|
|
3548
|
-
let
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
const
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
if (!responseIdCaptured) {
|
|
3559
|
-
const responseId = data?.id;
|
|
3560
|
-
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
3561
|
-
capturedResponseId = responseId.trim();
|
|
3562
|
-
onResponseId?.(capturedResponseId);
|
|
3563
|
-
responseIdCaptured = true;
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3566
|
-
const usage = extractUsageFromSSEJson(data);
|
|
3567
|
-
if (usage) {
|
|
3568
|
-
const merged = mergeUsage(capturedUsage, usage);
|
|
3569
|
-
if (hasUsageChanged(capturedUsage, merged)) {
|
|
3570
|
-
capturedUsage = merged;
|
|
3571
|
-
onUsage(merged);
|
|
3572
|
-
}
|
|
3573
|
-
}
|
|
3574
|
-
} catch {
|
|
3575
|
-
}
|
|
3576
|
-
};
|
|
3577
|
-
const inspectEventBlock = (eventBlock) => {
|
|
3578
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3579
|
-
return;
|
|
3580
|
-
}
|
|
3581
|
-
const lines = eventBlock.split(/\r?\n/);
|
|
3582
|
-
const dataParts = [];
|
|
3583
|
-
for (const line of lines) {
|
|
3584
|
-
if (!line || line.startsWith(":")) continue;
|
|
3585
|
-
if (line.startsWith("data:")) {
|
|
3586
|
-
const value = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
|
|
3587
|
-
dataParts.push(value);
|
|
3588
|
-
}
|
|
3589
|
-
}
|
|
3590
|
-
if (dataParts.length === 0) return;
|
|
3591
|
-
inspectDataPayload(dataParts.join("\n"));
|
|
3592
|
-
};
|
|
3593
|
-
const drainBufferedEvents = () => {
|
|
3594
|
-
const terminator = /\r?\n\r?\n/g;
|
|
3595
|
-
let lastIndex = 0;
|
|
3596
|
-
let match;
|
|
3597
|
-
while ((match = terminator.exec(buffer)) !== null) {
|
|
3598
|
-
const block = buffer.slice(lastIndex, match.index);
|
|
3599
|
-
lastIndex = match.index + match[0].length;
|
|
3600
|
-
if (block.length > 0) inspectEventBlock(block);
|
|
2767
|
+
function extractUsageFromResponseBody(body, fallbackSatsCost = 0) {
|
|
2768
|
+
if (!body || typeof body !== "object") return null;
|
|
2769
|
+
const usage = body.usage;
|
|
2770
|
+
if (!usage || typeof usage !== "object") return null;
|
|
2771
|
+
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
2772
|
+
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
2773
|
+
const totalTokens = Number(usage.total_tokens ?? 0);
|
|
2774
|
+
const costValue = usage.cost;
|
|
2775
|
+
let cost = 0;
|
|
2776
|
+
let satsCost = fallbackSatsCost;
|
|
2777
|
+
let breakdown = {};
|
|
2778
|
+
if (typeof costValue === "number") {
|
|
2779
|
+
cost = costValue;
|
|
2780
|
+
} else if (costValue && typeof costValue === "object") {
|
|
2781
|
+
const costObj = costValue;
|
|
2782
|
+
const totalUsd = costObj.total_usd;
|
|
2783
|
+
const totalMsats = costObj.total_msats;
|
|
2784
|
+
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
2785
|
+
if (typeof totalMsats === "number") {
|
|
2786
|
+
satsCost = totalMsats / 1e3;
|
|
3601
2787
|
}
|
|
3602
|
-
|
|
2788
|
+
breakdown = extractCostBreakdown(costObj);
|
|
2789
|
+
}
|
|
2790
|
+
const provider = typeof body.provider === "string" ? body.provider : void 0;
|
|
2791
|
+
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
2792
|
+
return null;
|
|
2793
|
+
}
|
|
2794
|
+
return {
|
|
2795
|
+
promptTokens,
|
|
2796
|
+
completionTokens,
|
|
2797
|
+
totalTokens,
|
|
2798
|
+
cost,
|
|
2799
|
+
satsCost,
|
|
2800
|
+
provider,
|
|
2801
|
+
...breakdown
|
|
3603
2802
|
};
|
|
3604
|
-
|
|
3605
|
-
|
|
2803
|
+
}
|
|
2804
|
+
function extractResponseId(body) {
|
|
2805
|
+
if (!body || typeof body !== "object") return void 0;
|
|
2806
|
+
const id = body.id;
|
|
2807
|
+
if (typeof id !== "string") return void 0;
|
|
2808
|
+
const trimmed = id.trim();
|
|
2809
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
2810
|
+
}
|
|
2811
|
+
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
2812
|
+
if (!parsed || typeof parsed !== "object") {
|
|
2813
|
+
return null;
|
|
2814
|
+
}
|
|
2815
|
+
const provider = typeof parsed.provider === "string" ? parsed.provider : void 0;
|
|
2816
|
+
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
2817
|
+
const costObj = parsed.cost;
|
|
2818
|
+
const msats2 = costObj.total_msats ?? 0;
|
|
2819
|
+
const cost2 = costObj.total_usd ?? 0;
|
|
2820
|
+
if (msats2 === 0 && cost2 === 0) return null;
|
|
2821
|
+
return {
|
|
2822
|
+
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
2823
|
+
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
2824
|
+
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
2825
|
+
cost: Number(cost2),
|
|
2826
|
+
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost,
|
|
2827
|
+
provider,
|
|
2828
|
+
...extractCostBreakdown(costObj)
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
if (!parsed.usage) {
|
|
2832
|
+
return null;
|
|
2833
|
+
}
|
|
2834
|
+
const usage = parsed.usage;
|
|
2835
|
+
const usageCost = usage.cost;
|
|
2836
|
+
let cost = 0;
|
|
2837
|
+
let msats = 0;
|
|
2838
|
+
let breakdown = {};
|
|
2839
|
+
if (typeof usageCost === "number") {
|
|
2840
|
+
cost = usageCost;
|
|
2841
|
+
} else if (usageCost && typeof usageCost === "object") {
|
|
2842
|
+
cost = usageCost.total_usd ?? 0;
|
|
2843
|
+
msats = usageCost.total_msats ?? 0;
|
|
2844
|
+
breakdown = extractCostBreakdown(usageCost);
|
|
2845
|
+
}
|
|
2846
|
+
const routstrCost = parsed.metadata?.routstr?.cost;
|
|
2847
|
+
if (routstrCost && typeof routstrCost === "object") {
|
|
2848
|
+
breakdown = { ...extractCostBreakdown(routstrCost), ...breakdown };
|
|
2849
|
+
}
|
|
2850
|
+
if (cost === 0) {
|
|
2851
|
+
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
2852
|
+
}
|
|
2853
|
+
if (msats === 0) {
|
|
2854
|
+
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
2855
|
+
}
|
|
2856
|
+
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
2857
|
+
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
2858
|
+
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
2859
|
+
const result = {
|
|
2860
|
+
promptTokens,
|
|
2861
|
+
completionTokens,
|
|
2862
|
+
totalTokens,
|
|
2863
|
+
cost: Number(cost ?? 0),
|
|
2864
|
+
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost,
|
|
2865
|
+
provider,
|
|
2866
|
+
...breakdown
|
|
2867
|
+
};
|
|
2868
|
+
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
2869
|
+
return null;
|
|
2870
|
+
}
|
|
2871
|
+
return result;
|
|
2872
|
+
}
|
|
2873
|
+
function toUsageStats(usage) {
|
|
2874
|
+
if (!usage) return void 0;
|
|
2875
|
+
return {
|
|
2876
|
+
total_tokens: usage.totalTokens,
|
|
2877
|
+
prompt_tokens: usage.promptTokens,
|
|
2878
|
+
completion_tokens: usage.completionTokens,
|
|
2879
|
+
cost: usage.cost,
|
|
2880
|
+
sats_cost: usage.satsCost
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
function mergeUsage(previous, next) {
|
|
2884
|
+
if (!previous) return next;
|
|
2885
|
+
const pickNum = (n, p) => typeof n === "number" && n > 0 ? n : p ?? n;
|
|
2886
|
+
return {
|
|
2887
|
+
promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
|
|
2888
|
+
completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
|
|
2889
|
+
totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
|
|
2890
|
+
cost: next.cost > 0 ? next.cost : previous.cost,
|
|
2891
|
+
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost,
|
|
2892
|
+
provider: next.provider ?? previous.provider,
|
|
2893
|
+
baseMsats: pickNum(next.baseMsats, previous.baseMsats),
|
|
2894
|
+
inputMsats: pickNum(next.inputMsats, previous.inputMsats),
|
|
2895
|
+
outputMsats: pickNum(next.outputMsats, previous.outputMsats),
|
|
2896
|
+
totalMsats: pickNum(next.totalMsats, previous.totalMsats),
|
|
2897
|
+
totalUsd: pickNum(next.totalUsd, previous.totalUsd),
|
|
2898
|
+
cacheReadInputTokens: pickNum(
|
|
2899
|
+
next.cacheReadInputTokens,
|
|
2900
|
+
previous.cacheReadInputTokens
|
|
2901
|
+
),
|
|
2902
|
+
cacheCreationInputTokens: pickNum(
|
|
2903
|
+
next.cacheCreationInputTokens,
|
|
2904
|
+
previous.cacheCreationInputTokens
|
|
2905
|
+
),
|
|
2906
|
+
cacheReadMsats: pickNum(next.cacheReadMsats, previous.cacheReadMsats),
|
|
2907
|
+
cacheCreationMsats: pickNum(
|
|
2908
|
+
next.cacheCreationMsats,
|
|
2909
|
+
previous.cacheCreationMsats
|
|
2910
|
+
),
|
|
2911
|
+
remainingBalanceMsats: pickNum(
|
|
2912
|
+
next.remainingBalanceMsats,
|
|
2913
|
+
previous.remainingBalanceMsats
|
|
2914
|
+
)
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
function hasUsageChanged(previous, next) {
|
|
2918
|
+
if (!previous) return true;
|
|
2919
|
+
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;
|
|
2920
|
+
}
|
|
2921
|
+
function isInspectionComplete(responseIdCaptured, usage) {
|
|
2922
|
+
return responseIdCaptured && !!usage && usage.totalTokens > 0 && typeof usage.totalMsats === "number" && !!usage.provider;
|
|
2923
|
+
}
|
|
2924
|
+
async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
2925
|
+
const reader = stream.getReader();
|
|
2926
|
+
const decoder = new TextDecoder("utf-8");
|
|
2927
|
+
let buffer = "";
|
|
2928
|
+
let capturedUsage = null;
|
|
2929
|
+
let capturedResponseId;
|
|
2930
|
+
let responseIdCaptured = false;
|
|
2931
|
+
const inspectDataPayload = (jsonText) => {
|
|
2932
|
+
const trimmed = jsonText.trim();
|
|
2933
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
2934
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
2938
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
try {
|
|
2942
|
+
const data = JSON.parse(trimmed);
|
|
2943
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
2944
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
2945
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
if (!responseIdCaptured) {
|
|
2949
|
+
const responseId = data?.id;
|
|
2950
|
+
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
2951
|
+
capturedResponseId = responseId.trim();
|
|
2952
|
+
onResponseId?.(capturedResponseId);
|
|
2953
|
+
responseIdCaptured = true;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
const usage = extractUsageFromSSEJson(data);
|
|
2957
|
+
if (usage) {
|
|
2958
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
2959
|
+
const merged = mergeUsage(capturedUsage, usage);
|
|
2960
|
+
if (hasUsageChanged(capturedUsage, merged)) {
|
|
2961
|
+
capturedUsage = merged;
|
|
2962
|
+
console.log("[routstr:sse] \u2192 merged (changed):", merged);
|
|
2963
|
+
onUsage(merged);
|
|
2964
|
+
} else {
|
|
2965
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
} catch {
|
|
2969
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
2970
|
+
}
|
|
2971
|
+
};
|
|
2972
|
+
const inspectEventBlock = (eventBlock) => {
|
|
2973
|
+
const lines = eventBlock.split(/\r?\n/);
|
|
2974
|
+
const dataParts = [];
|
|
2975
|
+
for (const line of lines) {
|
|
2976
|
+
if (!line || line.startsWith(":")) continue;
|
|
2977
|
+
if (line.startsWith("data:")) {
|
|
2978
|
+
const value = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
|
|
2979
|
+
dataParts.push(value);
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
if (dataParts.length === 0) return;
|
|
2983
|
+
inspectDataPayload(dataParts.join("\n"));
|
|
2984
|
+
};
|
|
2985
|
+
const drainBufferedEvents = () => {
|
|
2986
|
+
const terminator = /\r?\n\r?\n/g;
|
|
2987
|
+
let lastIndex = 0;
|
|
2988
|
+
let match;
|
|
2989
|
+
while ((match = terminator.exec(buffer)) !== null) {
|
|
2990
|
+
const block = buffer.slice(lastIndex, match.index);
|
|
2991
|
+
lastIndex = match.index + match[0].length;
|
|
2992
|
+
if (block.length > 0) inspectEventBlock(block);
|
|
2993
|
+
}
|
|
2994
|
+
if (lastIndex > 0) buffer = buffer.slice(lastIndex);
|
|
2995
|
+
};
|
|
2996
|
+
try {
|
|
2997
|
+
while (true) {
|
|
3606
2998
|
const { value, done } = await reader.read();
|
|
3607
2999
|
if (done) break;
|
|
3608
3000
|
if (value && value.byteLength > 0) {
|
|
@@ -3635,14 +3027,22 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3635
3027
|
let capturedUsage = null;
|
|
3636
3028
|
let responseIdCaptured = false;
|
|
3637
3029
|
const inspectDataPayload = (jsonText) => {
|
|
3638
|
-
|
|
3030
|
+
const trimmed = jsonText.trim();
|
|
3031
|
+
if (!trimmed || trimmed === "[DONE]") {
|
|
3032
|
+
if (trimmed === "[DONE]") console.log("[routstr:sse] [DONE]");
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
3036
|
+
console.log("[routstr:sse] non-JSON payload:", trimmed.slice(0, 200));
|
|
3639
3037
|
return;
|
|
3640
3038
|
}
|
|
3641
|
-
const trimmed = jsonText.trim();
|
|
3642
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
3643
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
3644
3039
|
try {
|
|
3645
3040
|
const data = JSON.parse(trimmed);
|
|
3041
|
+
console.log("[routstr:sse] chunk:", JSON.stringify(data));
|
|
3042
|
+
if (isInspectionComplete(responseIdCaptured, capturedUsage)) {
|
|
3043
|
+
console.log("[routstr:sse] (inspection already complete, skipping)");
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3646
3046
|
if (!responseIdCaptured) {
|
|
3647
3047
|
const responseId = data?.id;
|
|
3648
3048
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -3652,19 +3052,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3652
3052
|
}
|
|
3653
3053
|
const usage = extractUsageFromSSEJson(data);
|
|
3654
3054
|
if (usage) {
|
|
3055
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
3655
3056
|
const mergedUsage = mergeUsage(capturedUsage, usage);
|
|
3656
3057
|
if (hasUsageChanged(capturedUsage, mergedUsage)) {
|
|
3657
3058
|
capturedUsage = mergedUsage;
|
|
3059
|
+
console.log("[routstr:sse] \u2192 merged (changed):", mergedUsage);
|
|
3658
3060
|
onUsage(mergedUsage);
|
|
3061
|
+
} else {
|
|
3062
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
3659
3063
|
}
|
|
3660
3064
|
}
|
|
3661
3065
|
} catch {
|
|
3066
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
3662
3067
|
}
|
|
3663
3068
|
};
|
|
3664
3069
|
const inspectEventBlock = (eventBlock) => {
|
|
3665
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3666
|
-
return;
|
|
3667
|
-
}
|
|
3668
3070
|
const lines = eventBlock.split(/\r?\n/);
|
|
3669
3071
|
const dataParts = [];
|
|
3670
3072
|
for (const line of lines) {
|
|
@@ -3737,7 +3139,6 @@ var RoutstrClient = class {
|
|
|
3737
3139
|
this.balanceManager,
|
|
3738
3140
|
this.logger
|
|
3739
3141
|
);
|
|
3740
|
-
this.streamProcessor = new StreamProcessor();
|
|
3741
3142
|
this.alertLevel = alertLevel;
|
|
3742
3143
|
this.mode = mode;
|
|
3743
3144
|
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
@@ -3749,7 +3150,6 @@ var RoutstrClient = class {
|
|
|
3749
3150
|
providerRegistry;
|
|
3750
3151
|
cashuSpender;
|
|
3751
3152
|
balanceManager;
|
|
3752
|
-
streamProcessor;
|
|
3753
3153
|
providerManager;
|
|
3754
3154
|
alertLevel;
|
|
3755
3155
|
mode;
|
|
@@ -3834,6 +3234,8 @@ var RoutstrClient = class {
|
|
|
3834
3234
|
baseUrl: prepared.baseUrlUsed,
|
|
3835
3235
|
mintUrl: params.mintUrl,
|
|
3836
3236
|
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3237
|
+
initialTokenBalanceUnknown: prepared.tokenBalanceUnknown,
|
|
3238
|
+
fallbackSatsSpent: usage?.satsCost,
|
|
3837
3239
|
response: prepared.response,
|
|
3838
3240
|
modelId: prepared.modelId,
|
|
3839
3241
|
usage,
|
|
@@ -3898,7 +3300,7 @@ var RoutstrClient = class {
|
|
|
3898
3300
|
);
|
|
3899
3301
|
}
|
|
3900
3302
|
}
|
|
3901
|
-
const { token, tokenBalance, tokenBalanceUnit } = await this._spendToken({
|
|
3303
|
+
const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = await this._spendToken({
|
|
3902
3304
|
mintUrl,
|
|
3903
3305
|
amount: requiredSats,
|
|
3904
3306
|
baseUrl
|
|
@@ -3924,9 +3326,20 @@ var RoutstrClient = class {
|
|
|
3924
3326
|
baseHeaders,
|
|
3925
3327
|
selectedModel
|
|
3926
3328
|
});
|
|
3927
|
-
|
|
3329
|
+
let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
3330
|
+
let initialTokenBalanceUnknown = tokenBalanceUnknown;
|
|
3928
3331
|
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
3929
3332
|
const tokenUsed = response.token || token;
|
|
3333
|
+
if (baseUrlUsed !== baseUrl || tokenUsed !== token) {
|
|
3334
|
+
if (typeof response.initialTokenBalanceInSats === "number") {
|
|
3335
|
+
tokenBalanceInSats = response.initialTokenBalanceInSats;
|
|
3336
|
+
initialTokenBalanceUnknown = Boolean(
|
|
3337
|
+
response.initialTokenBalanceUnknown
|
|
3338
|
+
);
|
|
3339
|
+
} else {
|
|
3340
|
+
initialTokenBalanceUnknown = true;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3930
3343
|
const contentType = response.headers.get("content-type") || "";
|
|
3931
3344
|
let processedResponse = response;
|
|
3932
3345
|
let capturedUsage;
|
|
@@ -3959,6 +3372,7 @@ var RoutstrClient = class {
|
|
|
3959
3372
|
tokenUsed,
|
|
3960
3373
|
baseUrlUsed,
|
|
3961
3374
|
tokenBalanceInSats,
|
|
3375
|
+
tokenBalanceUnknown: initialTokenBalanceUnknown,
|
|
3962
3376
|
modelId,
|
|
3963
3377
|
capturedUsage,
|
|
3964
3378
|
capturedResponseId,
|
|
@@ -3977,139 +3391,6 @@ var RoutstrClient = class {
|
|
|
3977
3391
|
}
|
|
3978
3392
|
return void 0;
|
|
3979
3393
|
}
|
|
3980
|
-
/**
|
|
3981
|
-
* Fetch AI response with streaming
|
|
3982
|
-
*/
|
|
3983
|
-
async fetchAIResponse(options, callbacks) {
|
|
3984
|
-
const {
|
|
3985
|
-
messageHistory,
|
|
3986
|
-
selectedModel,
|
|
3987
|
-
baseUrl,
|
|
3988
|
-
mintUrl,
|
|
3989
|
-
balance,
|
|
3990
|
-
transactionHistory,
|
|
3991
|
-
maxTokens,
|
|
3992
|
-
headers
|
|
3993
|
-
} = options;
|
|
3994
|
-
const apiMessages = await this._convertMessages(messageHistory);
|
|
3995
|
-
const requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
3996
|
-
selectedModel,
|
|
3997
|
-
apiMessages,
|
|
3998
|
-
maxTokens
|
|
3999
|
-
);
|
|
4000
|
-
try {
|
|
4001
|
-
await this._checkBalance();
|
|
4002
|
-
callbacks.onPaymentProcessing?.(true);
|
|
4003
|
-
const spendResult = await this._spendToken({
|
|
4004
|
-
mintUrl,
|
|
4005
|
-
amount: requiredSats,
|
|
4006
|
-
baseUrl
|
|
4007
|
-
});
|
|
4008
|
-
let token = spendResult.token;
|
|
4009
|
-
let tokenBalance = spendResult.tokenBalance;
|
|
4010
|
-
let tokenBalanceUnit = spendResult.tokenBalanceUnit;
|
|
4011
|
-
const tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
4012
|
-
callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
|
|
4013
|
-
const baseHeaders = this._buildBaseHeaders(headers);
|
|
4014
|
-
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
4015
|
-
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
4016
|
-
const providerVersion = providerInfo?.version ?? "";
|
|
4017
|
-
let modelIdForRequest = selectedModel.id;
|
|
4018
|
-
if (/^0\.1\./.test(providerVersion)) {
|
|
4019
|
-
const newModel = await this.providerManager.getModelForProvider(
|
|
4020
|
-
baseUrl,
|
|
4021
|
-
selectedModel.id
|
|
4022
|
-
);
|
|
4023
|
-
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
4024
|
-
}
|
|
4025
|
-
const body = {
|
|
4026
|
-
model: modelIdForRequest,
|
|
4027
|
-
messages: apiMessages,
|
|
4028
|
-
stream: true
|
|
4029
|
-
};
|
|
4030
|
-
if (maxTokens !== void 0) {
|
|
4031
|
-
body.max_tokens = maxTokens;
|
|
4032
|
-
}
|
|
4033
|
-
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
4034
|
-
body.tools = [{ type: "web_search" }];
|
|
4035
|
-
}
|
|
4036
|
-
const response = await this._makeRequest({
|
|
4037
|
-
path: "/v1/chat/completions",
|
|
4038
|
-
method: "POST",
|
|
4039
|
-
body,
|
|
4040
|
-
selectedModel,
|
|
4041
|
-
baseUrl,
|
|
4042
|
-
mintUrl,
|
|
4043
|
-
token,
|
|
4044
|
-
requiredSats,
|
|
4045
|
-
maxTokens,
|
|
4046
|
-
headers: requestHeaders,
|
|
4047
|
-
baseHeaders
|
|
4048
|
-
});
|
|
4049
|
-
if (!response.body) {
|
|
4050
|
-
throw new Error("Response body is not available");
|
|
4051
|
-
}
|
|
4052
|
-
if (response.status === 200) {
|
|
4053
|
-
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
4054
|
-
const streamingResult = await this.streamProcessor.process(
|
|
4055
|
-
response,
|
|
4056
|
-
{
|
|
4057
|
-
onContent: callbacks.onStreamingUpdate,
|
|
4058
|
-
onThinking: callbacks.onThinkingUpdate
|
|
4059
|
-
},
|
|
4060
|
-
selectedModel.id
|
|
4061
|
-
);
|
|
4062
|
-
if (streamingResult.finish_reason === "content_filter") {
|
|
4063
|
-
callbacks.onMessageAppend({
|
|
4064
|
-
role: "assistant",
|
|
4065
|
-
content: "Your request was denied due to content filtering."
|
|
4066
|
-
});
|
|
4067
|
-
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
4068
|
-
const message = await this._createAssistantMessage(streamingResult);
|
|
4069
|
-
callbacks.onMessageAppend(message);
|
|
4070
|
-
} else {
|
|
4071
|
-
callbacks.onMessageAppend({
|
|
4072
|
-
role: "system",
|
|
4073
|
-
content: "The provider did not respond to this request."
|
|
4074
|
-
});
|
|
4075
|
-
}
|
|
4076
|
-
callbacks.onStreamingUpdate("");
|
|
4077
|
-
callbacks.onThinkingUpdate("");
|
|
4078
|
-
const isApikeysEstimate = this.mode === "apikeys";
|
|
4079
|
-
let satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
4080
|
-
token,
|
|
4081
|
-
baseUrl: baseUrlUsed,
|
|
4082
|
-
mintUrl,
|
|
4083
|
-
initialTokenBalance: tokenBalanceInSats,
|
|
4084
|
-
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
4085
|
-
response,
|
|
4086
|
-
modelId: selectedModel.id,
|
|
4087
|
-
usage: streamingResult.usage ? {
|
|
4088
|
-
promptTokens: Number(streamingResult.usage.prompt_tokens ?? 0),
|
|
4089
|
-
completionTokens: Number(
|
|
4090
|
-
streamingResult.usage.completion_tokens ?? 0
|
|
4091
|
-
),
|
|
4092
|
-
totalTokens: Number(streamingResult.usage.total_tokens ?? 0),
|
|
4093
|
-
cost: Number(streamingResult.usage.cost ?? 0),
|
|
4094
|
-
satsCost: Number(streamingResult.usage.sats_cost ?? 0)
|
|
4095
|
-
} : void 0,
|
|
4096
|
-
requestId: streamingResult.responseId
|
|
4097
|
-
});
|
|
4098
|
-
const estimatedCosts = this._getEstimatedCosts(
|
|
4099
|
-
selectedModel,
|
|
4100
|
-
streamingResult
|
|
4101
|
-
);
|
|
4102
|
-
const onLastMessageSatsUpdate = callbacks.onLastMessageSatsUpdate;
|
|
4103
|
-
onLastMessageSatsUpdate?.(satsSpent, estimatedCosts);
|
|
4104
|
-
} else {
|
|
4105
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
4106
|
-
}
|
|
4107
|
-
} catch (error) {
|
|
4108
|
-
this._handleError(error, callbacks);
|
|
4109
|
-
} finally {
|
|
4110
|
-
callbacks.onPaymentProcessing?.(false);
|
|
4111
|
-
}
|
|
4112
|
-
}
|
|
4113
3394
|
/**
|
|
4114
3395
|
* Make the API request with failover support
|
|
4115
3396
|
*/
|
|
@@ -4237,14 +3518,24 @@ var RoutstrClient = class {
|
|
|
4237
3518
|
params.token,
|
|
4238
3519
|
baseUrl
|
|
4239
3520
|
);
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
3521
|
+
if (currentBalanceInfo.balanceUnknown) {
|
|
3522
|
+
this._log(
|
|
3523
|
+
"DEBUG",
|
|
3524
|
+
`[RoutstrClient] _handleErrorResponse: Current balance unknown for ${baseUrl}; using default topup amount=${topupAmount}`
|
|
3525
|
+
);
|
|
3526
|
+
} else {
|
|
3527
|
+
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
3528
|
+
const reservedBalance = currentBalanceInfo.unit === "msat" ? (currentBalanceInfo.reserved ?? 0) / 1e3 : currentBalanceInfo.reserved ?? 0;
|
|
3529
|
+
const shortfall = Math.max(
|
|
3530
|
+
0,
|
|
3531
|
+
params.requiredSats - currentBalance + reservedBalance
|
|
3532
|
+
);
|
|
3533
|
+
topupAmount = shortfall > 0.21 * params.requiredSats ? shortfall : 0.21 * params.requiredSats;
|
|
3534
|
+
this._log(
|
|
3535
|
+
"DEBUG",
|
|
3536
|
+
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance}. Reserved Balance: ${reservedBalance}. Available Balance: ${currentBalance - reservedBalance}`
|
|
3537
|
+
);
|
|
3538
|
+
}
|
|
4248
3539
|
} catch (e) {
|
|
4249
3540
|
this._log(
|
|
4250
3541
|
"WARN",
|
|
@@ -4330,7 +3621,7 @@ var RoutstrClient = class {
|
|
|
4330
3621
|
this.storageAdapter.removeApiKey(baseUrl);
|
|
4331
3622
|
tryNextProvider = true;
|
|
4332
3623
|
} else {
|
|
4333
|
-
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
3624
|
+
const latestTokenBalance = latestBalanceInfo.balanceUnknown ? void 0 : latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
4334
3625
|
if (latestBalanceInfo.apiKey) {
|
|
4335
3626
|
const storedApiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
4336
3627
|
if (storedApiKeyEntry?.key !== latestBalanceInfo.apiKey) {
|
|
@@ -4341,7 +3632,7 @@ var RoutstrClient = class {
|
|
|
4341
3632
|
}
|
|
4342
3633
|
retryToken = latestBalanceInfo.apiKey;
|
|
4343
3634
|
}
|
|
4344
|
-
if (latestTokenBalance >= 0) {
|
|
3635
|
+
if (latestTokenBalance !== void 0 && latestTokenBalance >= 0) {
|
|
4345
3636
|
this.storageAdapter.updateApiKeyBalance(
|
|
4346
3637
|
baseUrl,
|
|
4347
3638
|
latestTokenBalance
|
|
@@ -4416,7 +3707,7 @@ var RoutstrClient = class {
|
|
|
4416
3707
|
"DEBUG",
|
|
4417
3708
|
`[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
|
|
4418
3709
|
);
|
|
4419
|
-
if (!refundResult.success && latestBalanceInfo.amount > 0) {
|
|
3710
|
+
if (!refundResult.success && latestBalanceInfo.amount > 0 && !latestBalanceInfo.balanceUnknown) {
|
|
4420
3711
|
throw new ProviderError(
|
|
4421
3712
|
baseUrl,
|
|
4422
3713
|
status,
|
|
@@ -4467,7 +3758,7 @@ var RoutstrClient = class {
|
|
|
4467
3758
|
amount: newRequiredSats,
|
|
4468
3759
|
baseUrl: nextProvider
|
|
4469
3760
|
});
|
|
4470
|
-
|
|
3761
|
+
const retryResponse = await this._makeRequest({
|
|
4471
3762
|
...params,
|
|
4472
3763
|
path,
|
|
4473
3764
|
method,
|
|
@@ -4479,6 +3770,9 @@ var RoutstrClient = class {
|
|
|
4479
3770
|
headers: this._withAuthHeader(params.baseHeaders, spendResult.token),
|
|
4480
3771
|
retryCount: 0
|
|
4481
3772
|
});
|
|
3773
|
+
retryResponse.initialTokenBalanceInSats = spendResult.tokenBalanceUnit === "msat" ? spendResult.tokenBalance / 1e3 : spendResult.tokenBalance;
|
|
3774
|
+
retryResponse.initialTokenBalanceUnknown = spendResult.tokenBalanceUnknown;
|
|
3775
|
+
return retryResponse;
|
|
4482
3776
|
}
|
|
4483
3777
|
throw new FailoverError(
|
|
4484
3778
|
baseUrl,
|
|
@@ -4494,6 +3788,7 @@ var RoutstrClient = class {
|
|
|
4494
3788
|
baseUrl,
|
|
4495
3789
|
mintUrl,
|
|
4496
3790
|
initialTokenBalance,
|
|
3791
|
+
initialTokenBalanceUnknown,
|
|
4497
3792
|
fallbackSatsSpent,
|
|
4498
3793
|
response,
|
|
4499
3794
|
modelId,
|
|
@@ -4530,17 +3825,19 @@ var RoutstrClient = class {
|
|
|
4530
3825
|
latestBalanceInfo.apiKey,
|
|
4531
3826
|
baseUrl
|
|
4532
3827
|
);
|
|
4533
|
-
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
3828
|
+
const latestTokenBalance = latestBalanceInfo.balanceUnknown ? void 0 : latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
4534
3829
|
const storedApiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
4535
3830
|
if (storedApiKeyEntry?.key.startsWith("cashu") && latestBalanceInfo.apiKey) {
|
|
4536
3831
|
this.storageAdapter.removeApiKey(baseUrl);
|
|
4537
3832
|
this.storageAdapter.setApiKey(baseUrl, latestBalanceInfo.apiKey);
|
|
4538
3833
|
}
|
|
4539
|
-
|
|
4540
|
-
|
|
3834
|
+
if (latestTokenBalance !== void 0) {
|
|
3835
|
+
this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
|
|
3836
|
+
}
|
|
3837
|
+
satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
4541
3838
|
} catch (e) {
|
|
4542
3839
|
this._log("WARN", "Could not get updated API key balance:", e);
|
|
4543
|
-
satsSpent = fallbackSatsSpent ??
|
|
3840
|
+
satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
4544
3841
|
}
|
|
4545
3842
|
}
|
|
4546
3843
|
await this._trackResponseUsage({
|
|
@@ -4618,260 +3915,527 @@ var RoutstrClient = class {
|
|
|
4618
3915
|
}
|
|
4619
3916
|
}
|
|
4620
3917
|
/**
|
|
4621
|
-
*
|
|
3918
|
+
* Check wallet balance and throw if insufficient
|
|
3919
|
+
*/
|
|
3920
|
+
async _checkBalance() {
|
|
3921
|
+
const balances = await this.walletAdapter.getBalances();
|
|
3922
|
+
const totalBalance = Object.values(balances).reduce((sum, v) => sum + v, 0);
|
|
3923
|
+
if (totalBalance <= 0) {
|
|
3924
|
+
throw new InsufficientBalanceError(1, 0);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
/**
|
|
3928
|
+
* Spend a token using CashuSpender with standardized error handling
|
|
3929
|
+
*/
|
|
3930
|
+
async _spendToken(params) {
|
|
3931
|
+
const { mintUrl, amount, baseUrl } = params;
|
|
3932
|
+
this._log(
|
|
3933
|
+
"DEBUG",
|
|
3934
|
+
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
3935
|
+
);
|
|
3936
|
+
if (this.mode === "apikeys") {
|
|
3937
|
+
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
3938
|
+
if (!parentApiKey) {
|
|
3939
|
+
this._log(
|
|
3940
|
+
"DEBUG",
|
|
3941
|
+
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
3942
|
+
);
|
|
3943
|
+
const spendResult2 = await this.cashuSpender.spend({
|
|
3944
|
+
mintUrl,
|
|
3945
|
+
amount: amount * TOPUP_MARGIN,
|
|
3946
|
+
baseUrl: "",
|
|
3947
|
+
reuseToken: false
|
|
3948
|
+
});
|
|
3949
|
+
if (!spendResult2.token) {
|
|
3950
|
+
this._log(
|
|
3951
|
+
"ERROR",
|
|
3952
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
3953
|
+
spendResult2.error
|
|
3954
|
+
);
|
|
3955
|
+
throw new Error(
|
|
3956
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
3957
|
+
);
|
|
3958
|
+
} else {
|
|
3959
|
+
this._log(
|
|
3960
|
+
"DEBUG",
|
|
3961
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
3962
|
+
);
|
|
3963
|
+
}
|
|
3964
|
+
this._log(
|
|
3965
|
+
"DEBUG",
|
|
3966
|
+
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
3967
|
+
);
|
|
3968
|
+
try {
|
|
3969
|
+
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
3970
|
+
} catch (error) {
|
|
3971
|
+
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
3972
|
+
const receiveResult = await this.cashuSpender.receiveToken(
|
|
3973
|
+
spendResult2.token
|
|
3974
|
+
);
|
|
3975
|
+
if (receiveResult.success) {
|
|
3976
|
+
this._log(
|
|
3977
|
+
"DEBUG",
|
|
3978
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
3979
|
+
);
|
|
3980
|
+
} else {
|
|
3981
|
+
this._log(
|
|
3982
|
+
"DEBUG",
|
|
3983
|
+
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
3984
|
+
);
|
|
3985
|
+
}
|
|
3986
|
+
this._log(
|
|
3987
|
+
"DEBUG",
|
|
3988
|
+
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
3989
|
+
);
|
|
3990
|
+
} else {
|
|
3991
|
+
throw error;
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
3995
|
+
} else {
|
|
3996
|
+
this._log(
|
|
3997
|
+
"DEBUG",
|
|
3998
|
+
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
3999
|
+
);
|
|
4000
|
+
}
|
|
4001
|
+
let tokenBalance = 0;
|
|
4002
|
+
let tokenBalanceUnit = "sat";
|
|
4003
|
+
let tokenBalanceUnknown = false;
|
|
4004
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
4005
|
+
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
4006
|
+
(d) => d.baseUrl === baseUrl
|
|
4007
|
+
);
|
|
4008
|
+
if (distributionForBaseUrl) {
|
|
4009
|
+
tokenBalance = distributionForBaseUrl.amount;
|
|
4010
|
+
}
|
|
4011
|
+
if (tokenBalance === 0 && parentApiKey) {
|
|
4012
|
+
try {
|
|
4013
|
+
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
4014
|
+
parentApiKey.key,
|
|
4015
|
+
baseUrl
|
|
4016
|
+
);
|
|
4017
|
+
tokenBalance = balanceInfo.amount;
|
|
4018
|
+
tokenBalanceUnit = balanceInfo.unit;
|
|
4019
|
+
tokenBalanceUnknown = Boolean(balanceInfo.balanceUnknown);
|
|
4020
|
+
} catch (e) {
|
|
4021
|
+
this._log("WARN", "Could not get initial API key balance:", e);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
this._log(
|
|
4025
|
+
"DEBUG",
|
|
4026
|
+
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
4027
|
+
);
|
|
4028
|
+
return {
|
|
4029
|
+
token: parentApiKey?.key ?? "",
|
|
4030
|
+
tokenBalance,
|
|
4031
|
+
tokenBalanceUnit,
|
|
4032
|
+
tokenBalanceUnknown
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
this._log(
|
|
4036
|
+
"DEBUG",
|
|
4037
|
+
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
4038
|
+
);
|
|
4039
|
+
const spendResult = await this.cashuSpender.spend({
|
|
4040
|
+
mintUrl,
|
|
4041
|
+
amount,
|
|
4042
|
+
baseUrl: "",
|
|
4043
|
+
reuseToken: false
|
|
4044
|
+
});
|
|
4045
|
+
if (!spendResult.token) {
|
|
4046
|
+
this._log(
|
|
4047
|
+
"ERROR",
|
|
4048
|
+
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
4049
|
+
spendResult.error
|
|
4050
|
+
);
|
|
4051
|
+
} else {
|
|
4052
|
+
this._log(
|
|
4053
|
+
"DEBUG",
|
|
4054
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
4055
|
+
);
|
|
4056
|
+
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
4057
|
+
}
|
|
4058
|
+
return {
|
|
4059
|
+
token: spendResult.token,
|
|
4060
|
+
tokenBalance: spendResult.balance,
|
|
4061
|
+
tokenBalanceUnit: spendResult.unit ?? "sat",
|
|
4062
|
+
tokenBalanceUnknown: false
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
/**
|
|
4066
|
+
* Build request headers with common defaults and dev mock controls
|
|
4067
|
+
*/
|
|
4068
|
+
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
4069
|
+
const headers = {
|
|
4070
|
+
...additionalHeaders,
|
|
4071
|
+
"Content-Type": "application/json"
|
|
4072
|
+
};
|
|
4073
|
+
return headers;
|
|
4074
|
+
}
|
|
4075
|
+
/**
|
|
4076
|
+
* Attach auth headers using the active client mode
|
|
4077
|
+
*/
|
|
4078
|
+
_withAuthHeader(headers, token) {
|
|
4079
|
+
const nextHeaders = { ...headers };
|
|
4080
|
+
if (this.mode === "xcashu") {
|
|
4081
|
+
nextHeaders["X-Cashu"] = token;
|
|
4082
|
+
} else {
|
|
4083
|
+
nextHeaders["Authorization"] = `Bearer ${token}`;
|
|
4084
|
+
}
|
|
4085
|
+
return nextHeaders;
|
|
4086
|
+
}
|
|
4087
|
+
};
|
|
4088
|
+
|
|
4089
|
+
// client/StreamProcessor.ts
|
|
4090
|
+
var StreamProcessor = class {
|
|
4091
|
+
accumulatedContent = "";
|
|
4092
|
+
accumulatedThinking = "";
|
|
4093
|
+
accumulatedImages = [];
|
|
4094
|
+
isInThinking = false;
|
|
4095
|
+
isInContent = false;
|
|
4096
|
+
/**
|
|
4097
|
+
* Process a streaming response
|
|
4098
|
+
*/
|
|
4099
|
+
async process(response, callbacks, modelId) {
|
|
4100
|
+
if (!response.body) {
|
|
4101
|
+
throw new Error("Response body is not available");
|
|
4102
|
+
}
|
|
4103
|
+
const reader = response.body.getReader();
|
|
4104
|
+
const decoder = new TextDecoder("utf-8");
|
|
4105
|
+
let buffer = "";
|
|
4106
|
+
this.accumulatedContent = "";
|
|
4107
|
+
this.accumulatedThinking = "";
|
|
4108
|
+
this.accumulatedImages = [];
|
|
4109
|
+
this.isInThinking = false;
|
|
4110
|
+
this.isInContent = false;
|
|
4111
|
+
let usage;
|
|
4112
|
+
let model;
|
|
4113
|
+
let finish_reason;
|
|
4114
|
+
let citations;
|
|
4115
|
+
let annotations;
|
|
4116
|
+
let responseId;
|
|
4117
|
+
try {
|
|
4118
|
+
while (true) {
|
|
4119
|
+
const { done, value } = await reader.read();
|
|
4120
|
+
if (done) {
|
|
4121
|
+
break;
|
|
4122
|
+
}
|
|
4123
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
4124
|
+
buffer += chunk;
|
|
4125
|
+
const lines = buffer.split("\n");
|
|
4126
|
+
buffer = lines.pop() || "";
|
|
4127
|
+
for (const line of lines) {
|
|
4128
|
+
const parsed = this._parseLine(line);
|
|
4129
|
+
if (!parsed) continue;
|
|
4130
|
+
if (parsed.content) {
|
|
4131
|
+
this._handleContent(parsed.content, callbacks, modelId);
|
|
4132
|
+
}
|
|
4133
|
+
if (parsed.reasoning) {
|
|
4134
|
+
this._handleThinking(parsed.reasoning, callbacks);
|
|
4135
|
+
}
|
|
4136
|
+
if (parsed.usage) {
|
|
4137
|
+
usage = parsed.usage;
|
|
4138
|
+
}
|
|
4139
|
+
if (parsed.model) {
|
|
4140
|
+
model = parsed.model;
|
|
4141
|
+
}
|
|
4142
|
+
if (parsed.finish_reason) {
|
|
4143
|
+
finish_reason = parsed.finish_reason;
|
|
4144
|
+
}
|
|
4145
|
+
if (parsed.responseId) {
|
|
4146
|
+
responseId = parsed.responseId;
|
|
4147
|
+
}
|
|
4148
|
+
if (parsed.citations) {
|
|
4149
|
+
citations = parsed.citations;
|
|
4150
|
+
}
|
|
4151
|
+
if (parsed.annotations) {
|
|
4152
|
+
annotations = parsed.annotations;
|
|
4153
|
+
}
|
|
4154
|
+
if (parsed.images) {
|
|
4155
|
+
this._mergeImages(parsed.images);
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
} finally {
|
|
4160
|
+
reader.releaseLock();
|
|
4161
|
+
}
|
|
4162
|
+
return {
|
|
4163
|
+
content: this.accumulatedContent,
|
|
4164
|
+
thinking: this.accumulatedThinking || void 0,
|
|
4165
|
+
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
4166
|
+
usage,
|
|
4167
|
+
model,
|
|
4168
|
+
responseId,
|
|
4169
|
+
finish_reason,
|
|
4170
|
+
citations,
|
|
4171
|
+
annotations
|
|
4172
|
+
};
|
|
4173
|
+
}
|
|
4174
|
+
/**
|
|
4175
|
+
* Parse a single SSE line
|
|
4176
|
+
*/
|
|
4177
|
+
_parseLine(line) {
|
|
4178
|
+
if (!line.trim()) return null;
|
|
4179
|
+
if (!line.startsWith("data: ")) {
|
|
4180
|
+
return null;
|
|
4181
|
+
}
|
|
4182
|
+
const jsonData = line.slice(6);
|
|
4183
|
+
if (jsonData === "[DONE]") {
|
|
4184
|
+
return null;
|
|
4185
|
+
}
|
|
4186
|
+
try {
|
|
4187
|
+
const parsed = JSON.parse(jsonData);
|
|
4188
|
+
const result = {};
|
|
4189
|
+
if (parsed.choices?.[0]?.delta?.content) {
|
|
4190
|
+
result.content = parsed.choices[0].delta.content;
|
|
4191
|
+
}
|
|
4192
|
+
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
4193
|
+
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
4194
|
+
}
|
|
4195
|
+
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
4196
|
+
if (extractedUsage) {
|
|
4197
|
+
result.usage = toUsageStats(extractedUsage);
|
|
4198
|
+
} else if (parsed.usage) {
|
|
4199
|
+
result.usage = {
|
|
4200
|
+
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
4201
|
+
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
4202
|
+
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
if (parsed.id) {
|
|
4206
|
+
result.responseId = parsed.id;
|
|
4207
|
+
}
|
|
4208
|
+
if (parsed.model) {
|
|
4209
|
+
result.model = parsed.model;
|
|
4210
|
+
}
|
|
4211
|
+
if (parsed.citations) {
|
|
4212
|
+
result.citations = parsed.citations;
|
|
4213
|
+
}
|
|
4214
|
+
if (parsed.annotations) {
|
|
4215
|
+
result.annotations = parsed.annotations;
|
|
4216
|
+
}
|
|
4217
|
+
if (parsed.choices?.[0]?.finish_reason) {
|
|
4218
|
+
result.finish_reason = parsed.choices[0].finish_reason;
|
|
4219
|
+
}
|
|
4220
|
+
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
4221
|
+
if (images && Array.isArray(images)) {
|
|
4222
|
+
result.images = images;
|
|
4223
|
+
}
|
|
4224
|
+
return result;
|
|
4225
|
+
} catch {
|
|
4226
|
+
return null;
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
/**
|
|
4230
|
+
* Handle content delta with thinking support
|
|
4231
|
+
*/
|
|
4232
|
+
_handleContent(content, callbacks, modelId) {
|
|
4233
|
+
if (this.isInThinking && !this.isInContent) {
|
|
4234
|
+
this.accumulatedThinking += "</thinking>";
|
|
4235
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
4236
|
+
this.isInThinking = false;
|
|
4237
|
+
this.isInContent = true;
|
|
4238
|
+
}
|
|
4239
|
+
if (modelId) {
|
|
4240
|
+
this._extractThinkingFromContent(content, callbacks);
|
|
4241
|
+
} else {
|
|
4242
|
+
this.accumulatedContent += content;
|
|
4243
|
+
}
|
|
4244
|
+
callbacks.onContent(this.accumulatedContent);
|
|
4245
|
+
}
|
|
4246
|
+
/**
|
|
4247
|
+
* Handle thinking/reasoning content
|
|
4622
4248
|
*/
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
);
|
|
4249
|
+
_handleThinking(reasoning, callbacks) {
|
|
4250
|
+
if (!this.isInThinking) {
|
|
4251
|
+
this.accumulatedThinking += "<thinking> ";
|
|
4252
|
+
this.isInThinking = true;
|
|
4253
|
+
}
|
|
4254
|
+
this.accumulatedThinking += reasoning;
|
|
4255
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
4630
4256
|
}
|
|
4631
4257
|
/**
|
|
4632
|
-
*
|
|
4258
|
+
* Extract thinking blocks from content (for models with inline thinking)
|
|
4633
4259
|
*/
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
if (
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
}
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
image_url: {
|
|
4650
|
-
url: img.image_url.url
|
|
4651
|
-
}
|
|
4652
|
-
});
|
|
4260
|
+
_extractThinkingFromContent(content, callbacks) {
|
|
4261
|
+
const parts = content.split(/(<thinking>|<\/thinking>)/);
|
|
4262
|
+
for (const part of parts) {
|
|
4263
|
+
if (part === "<thinking>") {
|
|
4264
|
+
this.isInThinking = true;
|
|
4265
|
+
if (!this.accumulatedThinking.includes("<thinking>")) {
|
|
4266
|
+
this.accumulatedThinking += "<thinking> ";
|
|
4267
|
+
}
|
|
4268
|
+
} else if (part === "</thinking>") {
|
|
4269
|
+
this.isInThinking = false;
|
|
4270
|
+
this.accumulatedThinking += "</thinking>";
|
|
4271
|
+
} else if (this.isInThinking) {
|
|
4272
|
+
this.accumulatedThinking += part;
|
|
4273
|
+
} else {
|
|
4274
|
+
this.accumulatedContent += part;
|
|
4653
4275
|
}
|
|
4654
|
-
return {
|
|
4655
|
-
role: "assistant",
|
|
4656
|
-
content
|
|
4657
|
-
};
|
|
4658
4276
|
}
|
|
4659
|
-
return {
|
|
4660
|
-
role: "assistant",
|
|
4661
|
-
content: result.content || ""
|
|
4662
|
-
};
|
|
4663
4277
|
}
|
|
4664
4278
|
/**
|
|
4665
|
-
*
|
|
4279
|
+
* Merge images into accumulated array, avoiding duplicates
|
|
4666
4280
|
*/
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
const
|
|
4671
|
-
|
|
4672
|
-
|
|
4281
|
+
_mergeImages(newImages) {
|
|
4282
|
+
for (const img of newImages) {
|
|
4283
|
+
const newUrl = img.image_url?.url;
|
|
4284
|
+
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
4285
|
+
const existingUrl = existing.image_url?.url;
|
|
4286
|
+
if (newUrl && existingUrl) {
|
|
4287
|
+
return existingUrl === newUrl;
|
|
4288
|
+
}
|
|
4289
|
+
if (img.index !== void 0 && existing.index !== void 0) {
|
|
4290
|
+
return existing.index === img.index;
|
|
4291
|
+
}
|
|
4292
|
+
return false;
|
|
4293
|
+
});
|
|
4294
|
+
if (existingIndex === -1) {
|
|
4295
|
+
this.accumulatedImages.push(img);
|
|
4296
|
+
} else {
|
|
4297
|
+
this.accumulatedImages[existingIndex] = img;
|
|
4673
4298
|
}
|
|
4674
4299
|
}
|
|
4675
|
-
return estimatedCosts;
|
|
4676
|
-
}
|
|
4677
|
-
/**
|
|
4678
|
-
* Get pending API key amount
|
|
4679
|
-
*/
|
|
4680
|
-
_getPendingCashuTokenAmount() {
|
|
4681
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
4682
|
-
return apiKeyDistribution.reduce((total, item) => total + item.amount, 0);
|
|
4683
4300
|
}
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4301
|
+
};
|
|
4302
|
+
|
|
4303
|
+
// client/fetchAIResponse.ts
|
|
4304
|
+
async function fetchAIResponse(options, callbacks, deps) {
|
|
4305
|
+
const {
|
|
4306
|
+
messageHistory,
|
|
4307
|
+
selectedModel,
|
|
4308
|
+
baseUrl,
|
|
4309
|
+
mintUrl,
|
|
4310
|
+
maxTokens,
|
|
4311
|
+
headers
|
|
4312
|
+
} = options;
|
|
4313
|
+
try {
|
|
4314
|
+
const apiMessages = await convertMessages(messageHistory);
|
|
4315
|
+
callbacks.onPaymentProcessing?.(true);
|
|
4316
|
+
callbacks.onTokenCreated?.(deps.getPendingCashuTokenAmount?.() ?? 0);
|
|
4317
|
+
const providerInfo = await deps.providerRegistry.getProviderInfo(baseUrl);
|
|
4318
|
+
const providerVersion = providerInfo?.version ?? "";
|
|
4319
|
+
let modelIdForRequest = selectedModel.id;
|
|
4320
|
+
if (/^0\.1\./.test(providerVersion)) {
|
|
4321
|
+
const newModel = await deps.client.getProviderManager().getModelForProvider(baseUrl, selectedModel.id);
|
|
4322
|
+
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
4323
|
+
}
|
|
4324
|
+
const body = {
|
|
4325
|
+
model: modelIdForRequest,
|
|
4326
|
+
messages: apiMessages,
|
|
4327
|
+
stream: true
|
|
4328
|
+
};
|
|
4329
|
+
if (maxTokens !== void 0) {
|
|
4330
|
+
body.max_tokens = maxTokens;
|
|
4331
|
+
}
|
|
4332
|
+
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
4333
|
+
body.tools = [{ type: "web_search" }];
|
|
4334
|
+
}
|
|
4335
|
+
const response = await deps.client.routeRequest({
|
|
4336
|
+
path: "/v1/chat/completions",
|
|
4337
|
+
method: "POST",
|
|
4338
|
+
body,
|
|
4339
|
+
headers,
|
|
4340
|
+
baseUrl,
|
|
4341
|
+
mintUrl,
|
|
4342
|
+
modelId: selectedModel.id
|
|
4343
|
+
});
|
|
4344
|
+
if (!response.body) {
|
|
4345
|
+
throw new Error("Response body is not available");
|
|
4346
|
+
}
|
|
4347
|
+
if (response.status !== 200) {
|
|
4348
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
4349
|
+
}
|
|
4350
|
+
const streamProcessor = new StreamProcessor();
|
|
4351
|
+
const streamingResult = await streamProcessor.process(
|
|
4352
|
+
response,
|
|
4353
|
+
{
|
|
4354
|
+
onContent: callbacks.onStreamingUpdate,
|
|
4355
|
+
onThinking: callbacks.onThinkingUpdate
|
|
4356
|
+
},
|
|
4357
|
+
selectedModel.id
|
|
4358
|
+
);
|
|
4359
|
+
if (streamingResult.finish_reason === "content_filter") {
|
|
4696
4360
|
callbacks.onMessageAppend({
|
|
4697
|
-
role: "
|
|
4698
|
-
content: "
|
|
4361
|
+
role: "assistant",
|
|
4362
|
+
content: "Your request was denied due to content filtering."
|
|
4699
4363
|
});
|
|
4364
|
+
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
4365
|
+
const message = await createAssistantMessage(streamingResult);
|
|
4366
|
+
callbacks.onMessageAppend(message);
|
|
4700
4367
|
} else {
|
|
4701
4368
|
callbacks.onMessageAppend({
|
|
4702
4369
|
role: "system",
|
|
4703
|
-
content: "
|
|
4370
|
+
content: "The provider did not respond to this request."
|
|
4704
4371
|
});
|
|
4705
4372
|
}
|
|
4373
|
+
callbacks.onStreamingUpdate("");
|
|
4374
|
+
callbacks.onThinkingUpdate("");
|
|
4375
|
+
} catch (error) {
|
|
4376
|
+
handleError(error, callbacks, deps.alertLevel, deps.logger);
|
|
4377
|
+
} finally {
|
|
4378
|
+
callbacks.onPaymentProcessing?.(false);
|
|
4706
4379
|
}
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4380
|
+
}
|
|
4381
|
+
async function convertMessages(messages) {
|
|
4382
|
+
return Promise.all(
|
|
4383
|
+
messages.filter((m) => m.role !== "system").map(async (m) => ({
|
|
4384
|
+
role: m.role,
|
|
4385
|
+
content: typeof m.content === "string" ? m.content : m.content
|
|
4386
|
+
}))
|
|
4387
|
+
);
|
|
4388
|
+
}
|
|
4389
|
+
async function createAssistantMessage(result) {
|
|
4390
|
+
if (result.images && result.images.length > 0) {
|
|
4391
|
+
const content = [];
|
|
4392
|
+
if (result.content) {
|
|
4393
|
+
content.push({
|
|
4394
|
+
type: "text",
|
|
4395
|
+
text: result.content,
|
|
4396
|
+
thinking: result.thinking,
|
|
4397
|
+
citations: result.citations,
|
|
4398
|
+
annotations: result.annotations
|
|
4399
|
+
});
|
|
4715
4400
|
}
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
const { mintUrl, amount, baseUrl } = params;
|
|
4722
|
-
this._log(
|
|
4723
|
-
"DEBUG",
|
|
4724
|
-
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
4725
|
-
);
|
|
4726
|
-
if (this.mode === "apikeys") {
|
|
4727
|
-
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
4728
|
-
if (!parentApiKey) {
|
|
4729
|
-
this._log(
|
|
4730
|
-
"DEBUG",
|
|
4731
|
-
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
4732
|
-
);
|
|
4733
|
-
const spendResult2 = await this.cashuSpender.spend({
|
|
4734
|
-
mintUrl,
|
|
4735
|
-
amount: amount * TOPUP_MARGIN,
|
|
4736
|
-
baseUrl: "",
|
|
4737
|
-
reuseToken: false
|
|
4738
|
-
});
|
|
4739
|
-
if (!spendResult2.token) {
|
|
4740
|
-
this._log(
|
|
4741
|
-
"ERROR",
|
|
4742
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
4743
|
-
spendResult2.error
|
|
4744
|
-
);
|
|
4745
|
-
throw new Error(
|
|
4746
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
4747
|
-
);
|
|
4748
|
-
} else {
|
|
4749
|
-
this._log(
|
|
4750
|
-
"DEBUG",
|
|
4751
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
4752
|
-
);
|
|
4753
|
-
}
|
|
4754
|
-
this._log(
|
|
4755
|
-
"DEBUG",
|
|
4756
|
-
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
4757
|
-
);
|
|
4758
|
-
try {
|
|
4759
|
-
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
4760
|
-
} catch (error) {
|
|
4761
|
-
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
4762
|
-
const receiveResult = await this.cashuSpender.receiveToken(
|
|
4763
|
-
spendResult2.token
|
|
4764
|
-
);
|
|
4765
|
-
if (receiveResult.success) {
|
|
4766
|
-
this._log(
|
|
4767
|
-
"DEBUG",
|
|
4768
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
4769
|
-
);
|
|
4770
|
-
} else {
|
|
4771
|
-
this._log(
|
|
4772
|
-
"DEBUG",
|
|
4773
|
-
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
4774
|
-
);
|
|
4775
|
-
}
|
|
4776
|
-
this._log(
|
|
4777
|
-
"DEBUG",
|
|
4778
|
-
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
4779
|
-
);
|
|
4780
|
-
} else {
|
|
4781
|
-
throw error;
|
|
4782
|
-
}
|
|
4783
|
-
}
|
|
4784
|
-
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
4785
|
-
} else {
|
|
4786
|
-
this._log(
|
|
4787
|
-
"DEBUG",
|
|
4788
|
-
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
4789
|
-
);
|
|
4790
|
-
}
|
|
4791
|
-
let tokenBalance = 0;
|
|
4792
|
-
let tokenBalanceUnit = "sat";
|
|
4793
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
4794
|
-
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
4795
|
-
(d) => d.baseUrl === baseUrl
|
|
4796
|
-
);
|
|
4797
|
-
if (distributionForBaseUrl) {
|
|
4798
|
-
tokenBalance = distributionForBaseUrl.amount;
|
|
4799
|
-
}
|
|
4800
|
-
if (tokenBalance === 0 && parentApiKey) {
|
|
4801
|
-
try {
|
|
4802
|
-
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
4803
|
-
parentApiKey.key,
|
|
4804
|
-
baseUrl
|
|
4805
|
-
);
|
|
4806
|
-
tokenBalance = balanceInfo.amount;
|
|
4807
|
-
tokenBalanceUnit = balanceInfo.unit;
|
|
4808
|
-
} catch (e) {
|
|
4809
|
-
this._log("WARN", "Could not get initial API key balance:", e);
|
|
4401
|
+
for (const img of result.images) {
|
|
4402
|
+
content.push({
|
|
4403
|
+
type: "image_url",
|
|
4404
|
+
image_url: {
|
|
4405
|
+
url: img.image_url.url
|
|
4810
4406
|
}
|
|
4811
|
-
}
|
|
4812
|
-
this._log(
|
|
4813
|
-
"DEBUG",
|
|
4814
|
-
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
4815
|
-
);
|
|
4816
|
-
return {
|
|
4817
|
-
token: parentApiKey?.key ?? "",
|
|
4818
|
-
tokenBalance,
|
|
4819
|
-
tokenBalanceUnit
|
|
4820
|
-
};
|
|
4821
|
-
}
|
|
4822
|
-
this._log(
|
|
4823
|
-
"DEBUG",
|
|
4824
|
-
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
4825
|
-
);
|
|
4826
|
-
const spendResult = await this.cashuSpender.spend({
|
|
4827
|
-
mintUrl,
|
|
4828
|
-
amount,
|
|
4829
|
-
baseUrl: "",
|
|
4830
|
-
reuseToken: false
|
|
4831
|
-
});
|
|
4832
|
-
if (!spendResult.token) {
|
|
4833
|
-
this._log(
|
|
4834
|
-
"ERROR",
|
|
4835
|
-
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
4836
|
-
spendResult.error
|
|
4837
|
-
);
|
|
4838
|
-
} else {
|
|
4839
|
-
this._log(
|
|
4840
|
-
"DEBUG",
|
|
4841
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
4842
|
-
);
|
|
4843
|
-
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
4407
|
+
});
|
|
4844
4408
|
}
|
|
4845
4409
|
return {
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
tokenBalanceUnit: spendResult.unit ?? "sat"
|
|
4849
|
-
};
|
|
4850
|
-
}
|
|
4851
|
-
/**
|
|
4852
|
-
* Build request headers with common defaults and dev mock controls
|
|
4853
|
-
*/
|
|
4854
|
-
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
4855
|
-
const headers = {
|
|
4856
|
-
...additionalHeaders,
|
|
4857
|
-
"Content-Type": "application/json"
|
|
4410
|
+
role: "assistant",
|
|
4411
|
+
content
|
|
4858
4412
|
};
|
|
4859
|
-
return headers;
|
|
4860
4413
|
}
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4414
|
+
return {
|
|
4415
|
+
role: "assistant",
|
|
4416
|
+
content: result.content || ""
|
|
4417
|
+
};
|
|
4418
|
+
}
|
|
4419
|
+
function handleError(error, callbacks, alertLevel, logger) {
|
|
4420
|
+
logger.error("[fetchAIResponse] Error occurred", error);
|
|
4421
|
+
if (error instanceof Error) {
|
|
4422
|
+
const isStreamError = error.message.includes("Error in input stream") || error.message.includes("Load failed");
|
|
4423
|
+
const modifiedErrorMsg = isStreamError ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
|
|
4424
|
+
logger.error(
|
|
4425
|
+
`[fetchAIResponse] Error type=${error.constructor.name}, message=${modifiedErrorMsg}, isStreamError=${isStreamError}`
|
|
4426
|
+
);
|
|
4427
|
+
callbacks.onMessageAppend({
|
|
4428
|
+
role: "system",
|
|
4429
|
+
content: "Uncaught Error: " + modifiedErrorMsg + (alertLevel === "max" ? " | " + error.stack : "")
|
|
4430
|
+
});
|
|
4431
|
+
} else {
|
|
4432
|
+
callbacks.onMessageAppend({
|
|
4433
|
+
role: "system",
|
|
4434
|
+
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
4435
|
+
});
|
|
4872
4436
|
}
|
|
4873
|
-
}
|
|
4437
|
+
}
|
|
4874
4438
|
|
|
4875
|
-
export { ProviderManager, RoutstrClient, StreamProcessor, createSSEParserTransform, inspectSSEWebStream };
|
|
4439
|
+
export { ProviderManager, RoutstrClient, StreamProcessor, createSSEParserTransform, fetchAIResponse, inspectSSEWebStream };
|
|
4876
4440
|
//# sourceMappingURL=index.mjs.map
|
|
4877
4441
|
//# sourceMappingURL=index.mjs.map
|