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