@routstr/sdk 0.3.9 → 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 +801 -1291
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +801 -1292
- package/dist/client/index.mjs.map +1 -1
- package/dist/discovery/index.d.mts +33 -3
- package/dist/discovery/index.d.ts +33 -3
- package/dist/discovery/index.js +28 -21
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +28 -21
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1045 -1564
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1045 -1561
- 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 +4 -30
- package/dist/storage/index.d.ts +4 -30
- package/dist/storage/index.js +139 -650
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +140 -647
- package/dist/storage/index.mjs.map +1 -1
- package/dist/storage/node.d.mts +22 -0
- package/dist/storage/node.d.ts +22 -0
- package/dist/storage/node.js +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/package.json +26 -1
package/dist/client/index.js
CHANGED
|
@@ -1406,322 +1406,6 @@ var BalanceManager = class _BalanceManager {
|
|
|
1406
1406
|
}
|
|
1407
1407
|
};
|
|
1408
1408
|
|
|
1409
|
-
// client/usage.ts
|
|
1410
|
-
function extractUsageFromResponseBody(body, fallbackSatsCost = 0) {
|
|
1411
|
-
if (!body || typeof body !== "object") return null;
|
|
1412
|
-
const usage = body.usage;
|
|
1413
|
-
if (!usage || typeof usage !== "object") return null;
|
|
1414
|
-
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
1415
|
-
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
1416
|
-
const totalTokens = Number(usage.total_tokens ?? 0);
|
|
1417
|
-
const costValue = usage.cost;
|
|
1418
|
-
let cost = 0;
|
|
1419
|
-
let satsCost = fallbackSatsCost;
|
|
1420
|
-
if (typeof costValue === "number") {
|
|
1421
|
-
cost = costValue;
|
|
1422
|
-
} else if (costValue && typeof costValue === "object") {
|
|
1423
|
-
const costObj = costValue;
|
|
1424
|
-
const totalUsd = costObj.total_usd;
|
|
1425
|
-
const totalMsats = costObj.total_msats;
|
|
1426
|
-
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
1427
|
-
if (typeof totalMsats === "number") {
|
|
1428
|
-
satsCost = totalMsats / 1e3;
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
1432
|
-
return null;
|
|
1433
|
-
}
|
|
1434
|
-
return {
|
|
1435
|
-
promptTokens,
|
|
1436
|
-
completionTokens,
|
|
1437
|
-
totalTokens,
|
|
1438
|
-
cost,
|
|
1439
|
-
satsCost
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
|
-
function extractResponseId(body) {
|
|
1443
|
-
if (!body || typeof body !== "object") return void 0;
|
|
1444
|
-
const id = body.id;
|
|
1445
|
-
if (typeof id !== "string") return void 0;
|
|
1446
|
-
const trimmed = id.trim();
|
|
1447
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
1448
|
-
}
|
|
1449
|
-
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
1450
|
-
if (!parsed || typeof parsed !== "object") {
|
|
1451
|
-
return null;
|
|
1452
|
-
}
|
|
1453
|
-
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
1454
|
-
const costObj = parsed.cost;
|
|
1455
|
-
const msats2 = costObj.total_msats ?? 0;
|
|
1456
|
-
const cost2 = costObj.total_usd ?? 0;
|
|
1457
|
-
if (msats2 === 0 && cost2 === 0) return null;
|
|
1458
|
-
return {
|
|
1459
|
-
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
1460
|
-
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
1461
|
-
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
1462
|
-
cost: Number(cost2),
|
|
1463
|
-
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost
|
|
1464
|
-
};
|
|
1465
|
-
}
|
|
1466
|
-
if (!parsed.usage) {
|
|
1467
|
-
return null;
|
|
1468
|
-
}
|
|
1469
|
-
const usage = parsed.usage;
|
|
1470
|
-
const usageCost = usage.cost;
|
|
1471
|
-
let cost = 0;
|
|
1472
|
-
let msats = 0;
|
|
1473
|
-
if (typeof usageCost === "number") {
|
|
1474
|
-
cost = usageCost;
|
|
1475
|
-
} else if (usageCost && typeof usageCost === "object") {
|
|
1476
|
-
cost = usageCost.total_usd ?? 0;
|
|
1477
|
-
msats = usageCost.total_msats ?? 0;
|
|
1478
|
-
}
|
|
1479
|
-
if (cost === 0) {
|
|
1480
|
-
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
1481
|
-
}
|
|
1482
|
-
if (msats === 0) {
|
|
1483
|
-
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
1484
|
-
}
|
|
1485
|
-
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
1486
|
-
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
1487
|
-
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
1488
|
-
const result = {
|
|
1489
|
-
promptTokens,
|
|
1490
|
-
completionTokens,
|
|
1491
|
-
totalTokens,
|
|
1492
|
-
cost: Number(cost ?? 0),
|
|
1493
|
-
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
|
|
1494
|
-
};
|
|
1495
|
-
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
1496
|
-
return null;
|
|
1497
|
-
}
|
|
1498
|
-
return result;
|
|
1499
|
-
}
|
|
1500
|
-
function toUsageStats(usage) {
|
|
1501
|
-
if (!usage) return void 0;
|
|
1502
|
-
return {
|
|
1503
|
-
total_tokens: usage.totalTokens,
|
|
1504
|
-
prompt_tokens: usage.promptTokens,
|
|
1505
|
-
completion_tokens: usage.completionTokens,
|
|
1506
|
-
cost: usage.cost,
|
|
1507
|
-
sats_cost: usage.satsCost
|
|
1508
|
-
};
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
// client/StreamProcessor.ts
|
|
1512
|
-
var StreamProcessor = class {
|
|
1513
|
-
accumulatedContent = "";
|
|
1514
|
-
accumulatedThinking = "";
|
|
1515
|
-
accumulatedImages = [];
|
|
1516
|
-
isInThinking = false;
|
|
1517
|
-
isInContent = false;
|
|
1518
|
-
/**
|
|
1519
|
-
* Process a streaming response
|
|
1520
|
-
*/
|
|
1521
|
-
async process(response, callbacks, modelId) {
|
|
1522
|
-
if (!response.body) {
|
|
1523
|
-
throw new Error("Response body is not available");
|
|
1524
|
-
}
|
|
1525
|
-
const reader = response.body.getReader();
|
|
1526
|
-
const decoder = new TextDecoder("utf-8");
|
|
1527
|
-
let buffer = "";
|
|
1528
|
-
this.accumulatedContent = "";
|
|
1529
|
-
this.accumulatedThinking = "";
|
|
1530
|
-
this.accumulatedImages = [];
|
|
1531
|
-
this.isInThinking = false;
|
|
1532
|
-
this.isInContent = false;
|
|
1533
|
-
let usage;
|
|
1534
|
-
let model;
|
|
1535
|
-
let finish_reason;
|
|
1536
|
-
let citations;
|
|
1537
|
-
let annotations;
|
|
1538
|
-
let responseId;
|
|
1539
|
-
try {
|
|
1540
|
-
while (true) {
|
|
1541
|
-
const { done, value } = await reader.read();
|
|
1542
|
-
if (done) {
|
|
1543
|
-
break;
|
|
1544
|
-
}
|
|
1545
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
1546
|
-
buffer += chunk;
|
|
1547
|
-
const lines = buffer.split("\n");
|
|
1548
|
-
buffer = lines.pop() || "";
|
|
1549
|
-
for (const line of lines) {
|
|
1550
|
-
const parsed = this._parseLine(line);
|
|
1551
|
-
if (!parsed) continue;
|
|
1552
|
-
if (parsed.content) {
|
|
1553
|
-
this._handleContent(parsed.content, callbacks, modelId);
|
|
1554
|
-
}
|
|
1555
|
-
if (parsed.reasoning) {
|
|
1556
|
-
this._handleThinking(parsed.reasoning, callbacks);
|
|
1557
|
-
}
|
|
1558
|
-
if (parsed.usage) {
|
|
1559
|
-
usage = parsed.usage;
|
|
1560
|
-
}
|
|
1561
|
-
if (parsed.model) {
|
|
1562
|
-
model = parsed.model;
|
|
1563
|
-
}
|
|
1564
|
-
if (parsed.finish_reason) {
|
|
1565
|
-
finish_reason = parsed.finish_reason;
|
|
1566
|
-
}
|
|
1567
|
-
if (parsed.responseId) {
|
|
1568
|
-
responseId = parsed.responseId;
|
|
1569
|
-
}
|
|
1570
|
-
if (parsed.citations) {
|
|
1571
|
-
citations = parsed.citations;
|
|
1572
|
-
}
|
|
1573
|
-
if (parsed.annotations) {
|
|
1574
|
-
annotations = parsed.annotations;
|
|
1575
|
-
}
|
|
1576
|
-
if (parsed.images) {
|
|
1577
|
-
this._mergeImages(parsed.images);
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
} finally {
|
|
1582
|
-
reader.releaseLock();
|
|
1583
|
-
}
|
|
1584
|
-
return {
|
|
1585
|
-
content: this.accumulatedContent,
|
|
1586
|
-
thinking: this.accumulatedThinking || void 0,
|
|
1587
|
-
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
1588
|
-
usage,
|
|
1589
|
-
model,
|
|
1590
|
-
responseId,
|
|
1591
|
-
finish_reason,
|
|
1592
|
-
citations,
|
|
1593
|
-
annotations
|
|
1594
|
-
};
|
|
1595
|
-
}
|
|
1596
|
-
/**
|
|
1597
|
-
* Parse a single SSE line
|
|
1598
|
-
*/
|
|
1599
|
-
_parseLine(line) {
|
|
1600
|
-
if (!line.trim()) return null;
|
|
1601
|
-
if (!line.startsWith("data: ")) {
|
|
1602
|
-
return null;
|
|
1603
|
-
}
|
|
1604
|
-
const jsonData = line.slice(6);
|
|
1605
|
-
if (jsonData === "[DONE]") {
|
|
1606
|
-
return null;
|
|
1607
|
-
}
|
|
1608
|
-
try {
|
|
1609
|
-
const parsed = JSON.parse(jsonData);
|
|
1610
|
-
const result = {};
|
|
1611
|
-
if (parsed.choices?.[0]?.delta?.content) {
|
|
1612
|
-
result.content = parsed.choices[0].delta.content;
|
|
1613
|
-
}
|
|
1614
|
-
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
1615
|
-
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
1616
|
-
}
|
|
1617
|
-
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
1618
|
-
if (extractedUsage) {
|
|
1619
|
-
result.usage = toUsageStats(extractedUsage);
|
|
1620
|
-
} else if (parsed.usage) {
|
|
1621
|
-
result.usage = {
|
|
1622
|
-
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
1623
|
-
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
1624
|
-
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
1625
|
-
};
|
|
1626
|
-
}
|
|
1627
|
-
if (parsed.id) {
|
|
1628
|
-
result.responseId = parsed.id;
|
|
1629
|
-
}
|
|
1630
|
-
if (parsed.model) {
|
|
1631
|
-
result.model = parsed.model;
|
|
1632
|
-
}
|
|
1633
|
-
if (parsed.citations) {
|
|
1634
|
-
result.citations = parsed.citations;
|
|
1635
|
-
}
|
|
1636
|
-
if (parsed.annotations) {
|
|
1637
|
-
result.annotations = parsed.annotations;
|
|
1638
|
-
}
|
|
1639
|
-
if (parsed.choices?.[0]?.finish_reason) {
|
|
1640
|
-
result.finish_reason = parsed.choices[0].finish_reason;
|
|
1641
|
-
}
|
|
1642
|
-
const images = parsed.choices?.[0]?.message?.images || parsed.choices?.[0]?.delta?.images;
|
|
1643
|
-
if (images && Array.isArray(images)) {
|
|
1644
|
-
result.images = images;
|
|
1645
|
-
}
|
|
1646
|
-
return result;
|
|
1647
|
-
} catch {
|
|
1648
|
-
return null;
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
/**
|
|
1652
|
-
* Handle content delta with thinking support
|
|
1653
|
-
*/
|
|
1654
|
-
_handleContent(content, callbacks, modelId) {
|
|
1655
|
-
if (this.isInThinking && !this.isInContent) {
|
|
1656
|
-
this.accumulatedThinking += "</thinking>";
|
|
1657
|
-
callbacks.onThinking(this.accumulatedThinking);
|
|
1658
|
-
this.isInThinking = false;
|
|
1659
|
-
this.isInContent = true;
|
|
1660
|
-
}
|
|
1661
|
-
if (modelId) {
|
|
1662
|
-
this._extractThinkingFromContent(content, callbacks);
|
|
1663
|
-
} else {
|
|
1664
|
-
this.accumulatedContent += content;
|
|
1665
|
-
}
|
|
1666
|
-
callbacks.onContent(this.accumulatedContent);
|
|
1667
|
-
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Handle thinking/reasoning content
|
|
1670
|
-
*/
|
|
1671
|
-
_handleThinking(reasoning, callbacks) {
|
|
1672
|
-
if (!this.isInThinking) {
|
|
1673
|
-
this.accumulatedThinking += "<thinking> ";
|
|
1674
|
-
this.isInThinking = true;
|
|
1675
|
-
}
|
|
1676
|
-
this.accumulatedThinking += reasoning;
|
|
1677
|
-
callbacks.onThinking(this.accumulatedThinking);
|
|
1678
|
-
}
|
|
1679
|
-
/**
|
|
1680
|
-
* Extract thinking blocks from content (for models with inline thinking)
|
|
1681
|
-
*/
|
|
1682
|
-
_extractThinkingFromContent(content, callbacks) {
|
|
1683
|
-
const parts = content.split(/(<thinking>|<\/thinking>)/);
|
|
1684
|
-
for (const part of parts) {
|
|
1685
|
-
if (part === "<thinking>") {
|
|
1686
|
-
this.isInThinking = true;
|
|
1687
|
-
if (!this.accumulatedThinking.includes("<thinking>")) {
|
|
1688
|
-
this.accumulatedThinking += "<thinking> ";
|
|
1689
|
-
}
|
|
1690
|
-
} else if (part === "</thinking>") {
|
|
1691
|
-
this.isInThinking = false;
|
|
1692
|
-
this.accumulatedThinking += "</thinking>";
|
|
1693
|
-
} else if (this.isInThinking) {
|
|
1694
|
-
this.accumulatedThinking += part;
|
|
1695
|
-
} else {
|
|
1696
|
-
this.accumulatedContent += part;
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
/**
|
|
1701
|
-
* Merge images into accumulated array, avoiding duplicates
|
|
1702
|
-
*/
|
|
1703
|
-
_mergeImages(newImages) {
|
|
1704
|
-
for (const img of newImages) {
|
|
1705
|
-
const newUrl = img.image_url?.url;
|
|
1706
|
-
const existingIndex = this.accumulatedImages.findIndex((existing) => {
|
|
1707
|
-
const existingUrl = existing.image_url?.url;
|
|
1708
|
-
if (newUrl && existingUrl) {
|
|
1709
|
-
return existingUrl === newUrl;
|
|
1710
|
-
}
|
|
1711
|
-
if (img.index !== void 0 && existing.index !== void 0) {
|
|
1712
|
-
return existing.index === img.index;
|
|
1713
|
-
}
|
|
1714
|
-
return false;
|
|
1715
|
-
});
|
|
1716
|
-
if (existingIndex === -1) {
|
|
1717
|
-
this.accumulatedImages.push(img);
|
|
1718
|
-
} else {
|
|
1719
|
-
this.accumulatedImages[existingIndex] = img;
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
};
|
|
1724
|
-
|
|
1725
1409
|
// utils/torUtils.ts
|
|
1726
1410
|
var TOR_ONION_SUFFIX = ".onion";
|
|
1727
1411
|
var isTorContext = () => {
|
|
@@ -1831,9 +1515,6 @@ function calculateImageTokens(width, height, detail = "auto") {
|
|
|
1831
1515
|
const numTiles = tilesWidth * tilesHeight;
|
|
1832
1516
|
return 85 + 170 * numTiles;
|
|
1833
1517
|
}
|
|
1834
|
-
function isInsecureHttpUrl(url) {
|
|
1835
|
-
return url.startsWith("http://");
|
|
1836
|
-
}
|
|
1837
1518
|
var ProviderManager = class _ProviderManager {
|
|
1838
1519
|
constructor(providerRegistry, store, logger) {
|
|
1839
1520
|
this.providerRegistry = providerRegistry;
|
|
@@ -2050,7 +1731,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
2050
1731
|
if (this.isOnCooldown(baseUrl)) {
|
|
2051
1732
|
continue;
|
|
2052
1733
|
}
|
|
2053
|
-
if (!torMode &&
|
|
1734
|
+
if (!torMode && isOnionUrl(baseUrl)) {
|
|
2054
1735
|
continue;
|
|
2055
1736
|
}
|
|
2056
1737
|
const model = models.find((m) => m.id === modelId);
|
|
@@ -2101,7 +1782,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
2101
1782
|
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
2102
1783
|
if (disabledProviders.has(baseUrl)) continue;
|
|
2103
1784
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
2104
|
-
if (!torMode &&
|
|
1785
|
+
if (!torMode && isOnionUrl(baseUrl))
|
|
2105
1786
|
continue;
|
|
2106
1787
|
const model = models.find((m) => m.id === modelId);
|
|
2107
1788
|
if (!model) continue;
|
|
@@ -2116,16 +1797,18 @@ var ProviderManager = class _ProviderManager {
|
|
|
2116
1797
|
getProviderPriceRankingForModel(modelId, options = {}) {
|
|
2117
1798
|
const includeDisabled = options.includeDisabled ?? false;
|
|
2118
1799
|
const torMode = options.torMode ?? false;
|
|
2119
|
-
const
|
|
2120
|
-
|
|
2121
|
-
)
|
|
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
|
+
}
|
|
2122
1805
|
const allModels = this.providerRegistry.getAllProvidersModels();
|
|
2123
1806
|
const results = [];
|
|
2124
1807
|
for (const [baseUrl, models] of Object.entries(allModels)) {
|
|
2125
1808
|
if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
|
|
2126
1809
|
if (this.isOnCooldown(baseUrl)) continue;
|
|
2127
1810
|
if (torMode && !baseUrl.includes(".onion")) continue;
|
|
2128
|
-
if (!torMode &&
|
|
1811
|
+
if (!torMode && baseUrl.includes(".onion"))
|
|
2129
1812
|
continue;
|
|
2130
1813
|
const match = models.find((model) => model.id === modelId);
|
|
2131
1814
|
if (!match?.sats_pricing) continue;
|
|
@@ -2145,12 +1828,20 @@ var ProviderManager = class _ProviderManager {
|
|
|
2145
1828
|
totalPerMillion
|
|
2146
1829
|
});
|
|
2147
1830
|
}
|
|
2148
|
-
|
|
1831
|
+
results.sort((a, b) => {
|
|
2149
1832
|
if (a.totalPerMillion !== b.totalPerMillion) {
|
|
2150
1833
|
return a.totalPerMillion - b.totalPerMillion;
|
|
2151
1834
|
}
|
|
2152
1835
|
return a.baseUrl.localeCompare(b.baseUrl);
|
|
2153
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;
|
|
2154
1845
|
}
|
|
2155
1846
|
/**
|
|
2156
1847
|
* Get best-priced provider for a specific model
|
|
@@ -2338,92 +2029,6 @@ var createMemoryDriver = (seed) => {
|
|
|
2338
2029
|
};
|
|
2339
2030
|
};
|
|
2340
2031
|
|
|
2341
|
-
// storage/drivers/sqlite.ts
|
|
2342
|
-
var isBun = () => {
|
|
2343
|
-
return typeof process.versions.bun !== "undefined";
|
|
2344
|
-
};
|
|
2345
|
-
var cachedDbModule = null;
|
|
2346
|
-
var loadDatabase = async (dbPath) => {
|
|
2347
|
-
if (isBun()) {
|
|
2348
|
-
throw new Error(
|
|
2349
|
-
"SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
|
|
2350
|
-
);
|
|
2351
|
-
}
|
|
2352
|
-
try {
|
|
2353
|
-
if (!cachedDbModule) {
|
|
2354
|
-
cachedDbModule = (await import('better-sqlite3')).default;
|
|
2355
|
-
}
|
|
2356
|
-
return new cachedDbModule(dbPath);
|
|
2357
|
-
} catch (error) {
|
|
2358
|
-
throw new Error(
|
|
2359
|
-
`better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
|
|
2360
|
-
);
|
|
2361
|
-
}
|
|
2362
|
-
};
|
|
2363
|
-
var createSqliteDriver = (options = {}) => {
|
|
2364
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2365
|
-
const tableName = options.tableName || "sdk_storage";
|
|
2366
|
-
let db;
|
|
2367
|
-
let selectStmt;
|
|
2368
|
-
let upsertStmt;
|
|
2369
|
-
let deleteStmt;
|
|
2370
|
-
const initDb = async () => {
|
|
2371
|
-
if (!db) {
|
|
2372
|
-
db = await loadDatabase(dbPath);
|
|
2373
|
-
db.exec(
|
|
2374
|
-
`CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
2375
|
-
);
|
|
2376
|
-
selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
|
|
2377
|
-
upsertStmt = db.prepare(
|
|
2378
|
-
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)
|
|
2379
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
2380
|
-
);
|
|
2381
|
-
deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
|
|
2382
|
-
}
|
|
2383
|
-
};
|
|
2384
|
-
const ensureInit = async () => {
|
|
2385
|
-
if (!db) {
|
|
2386
|
-
await initDb();
|
|
2387
|
-
}
|
|
2388
|
-
};
|
|
2389
|
-
return {
|
|
2390
|
-
async getItem(key, defaultValue) {
|
|
2391
|
-
try {
|
|
2392
|
-
await ensureInit();
|
|
2393
|
-
const row = selectStmt.get(key);
|
|
2394
|
-
if (!row || typeof row.value !== "string") return defaultValue;
|
|
2395
|
-
try {
|
|
2396
|
-
return JSON.parse(row.value);
|
|
2397
|
-
} catch (parseError) {
|
|
2398
|
-
if (typeof defaultValue === "string") {
|
|
2399
|
-
return row.value;
|
|
2400
|
-
}
|
|
2401
|
-
throw parseError;
|
|
2402
|
-
}
|
|
2403
|
-
} catch (error) {
|
|
2404
|
-
console.error(`SQLite getItem failed for key "${key}":`, error);
|
|
2405
|
-
return defaultValue;
|
|
2406
|
-
}
|
|
2407
|
-
},
|
|
2408
|
-
async setItem(key, value) {
|
|
2409
|
-
try {
|
|
2410
|
-
await ensureInit();
|
|
2411
|
-
upsertStmt.run(key, JSON.stringify(value));
|
|
2412
|
-
} catch (error) {
|
|
2413
|
-
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
2414
|
-
}
|
|
2415
|
-
},
|
|
2416
|
-
async removeItem(key) {
|
|
2417
|
-
try {
|
|
2418
|
-
await ensureInit();
|
|
2419
|
-
deleteStmt.run(key);
|
|
2420
|
-
} catch (error) {
|
|
2421
|
-
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
2422
|
-
}
|
|
2423
|
-
}
|
|
2424
|
-
};
|
|
2425
|
-
};
|
|
2426
|
-
|
|
2427
2032
|
// storage/keys.ts
|
|
2428
2033
|
var SDK_STORAGE_KEYS = {
|
|
2429
2034
|
MODELS_FROM_ALL_PROVIDERS: "modelsFromAllProviders",
|
|
@@ -2458,9 +2063,10 @@ var openDatabase = (dbName, storeName) => {
|
|
|
2458
2063
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
2459
2064
|
}
|
|
2460
2065
|
return new Promise((resolve, reject) => {
|
|
2461
|
-
const request = indexedDB.open(dbName,
|
|
2066
|
+
const request = indexedDB.open(dbName, 3);
|
|
2462
2067
|
request.onupgradeneeded = () => {
|
|
2463
2068
|
const db = request.result;
|
|
2069
|
+
const tx = request.transaction;
|
|
2464
2070
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
2465
2071
|
const store = db.createObjectStore(storeName, { keyPath: "id" });
|
|
2466
2072
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
@@ -2468,10 +2074,25 @@ var openDatabase = (dbName, storeName) => {
|
|
|
2468
2074
|
store.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
2469
2075
|
store.createIndex("sessionId", "sessionId", { unique: false });
|
|
2470
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");
|
|
2471
2086
|
}
|
|
2472
2087
|
};
|
|
2473
2088
|
request.onsuccess = () => resolve(request.result);
|
|
2474
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
|
+
};
|
|
2475
2096
|
});
|
|
2476
2097
|
};
|
|
2477
2098
|
var matchesFilters = (entry, options = {}) => {
|
|
@@ -2493,6 +2114,9 @@ var matchesFilters = (entry, options = {}) => {
|
|
|
2493
2114
|
if (options.client && entry.client !== options.client) {
|
|
2494
2115
|
return false;
|
|
2495
2116
|
}
|
|
2117
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
2118
|
+
return false;
|
|
2119
|
+
}
|
|
2496
2120
|
return true;
|
|
2497
2121
|
};
|
|
2498
2122
|
var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
@@ -2624,393 +2248,8 @@ var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
|
2624
2248
|
};
|
|
2625
2249
|
};
|
|
2626
2250
|
|
|
2627
|
-
// storage/usageTracking/sqlite.ts
|
|
2628
|
-
var MIGRATION_MARKER_KEY2 = "usage_tracking_migration_v1";
|
|
2629
|
-
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2630
|
-
var isBun2 = () => {
|
|
2631
|
-
return typeof process.versions.bun !== "undefined";
|
|
2632
|
-
};
|
|
2633
|
-
var cachedDbModule2 = null;
|
|
2634
|
-
var loadDatabase2 = async (dbPath) => {
|
|
2635
|
-
if (isBun2()) {
|
|
2636
|
-
throw new Error(
|
|
2637
|
-
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
2638
|
-
);
|
|
2639
|
-
}
|
|
2640
|
-
try {
|
|
2641
|
-
if (!cachedDbModule2) {
|
|
2642
|
-
cachedDbModule2 = (await import('better-sqlite3')).default;
|
|
2643
|
-
}
|
|
2644
|
-
return new cachedDbModule2(dbPath);
|
|
2645
|
-
} catch (error) {
|
|
2646
|
-
throw new Error(
|
|
2647
|
-
`better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
|
|
2648
|
-
);
|
|
2649
|
-
}
|
|
2650
|
-
};
|
|
2651
|
-
var buildWhereClause = (options = {}) => {
|
|
2652
|
-
const clauses = [];
|
|
2653
|
-
const params = [];
|
|
2654
|
-
if (typeof options.before === "number") {
|
|
2655
|
-
clauses.push("timestamp < ?");
|
|
2656
|
-
params.push(options.before);
|
|
2657
|
-
}
|
|
2658
|
-
if (typeof options.after === "number") {
|
|
2659
|
-
clauses.push("timestamp > ?");
|
|
2660
|
-
params.push(options.after);
|
|
2661
|
-
}
|
|
2662
|
-
if (options.modelId) {
|
|
2663
|
-
clauses.push("model_id = ?");
|
|
2664
|
-
params.push(options.modelId);
|
|
2665
|
-
}
|
|
2666
|
-
if (options.baseUrl) {
|
|
2667
|
-
clauses.push("base_url = ?");
|
|
2668
|
-
params.push(normalizeBaseUrl2(options.baseUrl));
|
|
2669
|
-
}
|
|
2670
|
-
if (options.sessionId) {
|
|
2671
|
-
clauses.push("session_id = ?");
|
|
2672
|
-
params.push(options.sessionId);
|
|
2673
|
-
}
|
|
2674
|
-
if (options.client) {
|
|
2675
|
-
clauses.push("client = ?");
|
|
2676
|
-
params.push(options.client);
|
|
2677
|
-
}
|
|
2678
|
-
return {
|
|
2679
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
2680
|
-
params
|
|
2681
|
-
};
|
|
2682
|
-
};
|
|
2683
|
-
var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
2684
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2685
|
-
const tableName = options.tableName || "usage_tracking";
|
|
2686
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2687
|
-
let db;
|
|
2688
|
-
let insertStmt;
|
|
2689
|
-
let migrationComplete = false;
|
|
2690
|
-
const initDb = async () => {
|
|
2691
|
-
if (!db) {
|
|
2692
|
-
db = await loadDatabase2(dbPath);
|
|
2693
|
-
db.exec(`
|
|
2694
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2695
|
-
id TEXT PRIMARY KEY,
|
|
2696
|
-
timestamp INTEGER NOT NULL,
|
|
2697
|
-
model_id TEXT NOT NULL,
|
|
2698
|
-
base_url TEXT NOT NULL,
|
|
2699
|
-
request_id TEXT NOT NULL,
|
|
2700
|
-
cost REAL NOT NULL,
|
|
2701
|
-
sats_cost REAL NOT NULL,
|
|
2702
|
-
prompt_tokens INTEGER NOT NULL,
|
|
2703
|
-
completion_tokens INTEGER NOT NULL,
|
|
2704
|
-
total_tokens INTEGER NOT NULL,
|
|
2705
|
-
client TEXT,
|
|
2706
|
-
session_id TEXT,
|
|
2707
|
-
tags TEXT
|
|
2708
|
-
);
|
|
2709
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
2710
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
2711
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
2712
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
2713
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
2714
|
-
`);
|
|
2715
|
-
insertStmt = db.prepare(`
|
|
2716
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
2717
|
-
id, timestamp, model_id, base_url, request_id,
|
|
2718
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2719
|
-
client, session_id, tags
|
|
2720
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2721
|
-
`);
|
|
2722
|
-
}
|
|
2723
|
-
};
|
|
2724
|
-
const ensureInit = async () => {
|
|
2725
|
-
if (!db) {
|
|
2726
|
-
await initDb();
|
|
2727
|
-
}
|
|
2728
|
-
};
|
|
2729
|
-
const appendOne = (entry) => {
|
|
2730
|
-
insertStmt.run(
|
|
2731
|
-
entry.id,
|
|
2732
|
-
entry.timestamp,
|
|
2733
|
-
entry.modelId,
|
|
2734
|
-
normalizeBaseUrl2(entry.baseUrl),
|
|
2735
|
-
entry.requestId,
|
|
2736
|
-
entry.cost,
|
|
2737
|
-
entry.satsCost,
|
|
2738
|
-
entry.promptTokens,
|
|
2739
|
-
entry.completionTokens,
|
|
2740
|
-
entry.totalTokens,
|
|
2741
|
-
entry.client ?? null,
|
|
2742
|
-
entry.sessionId ?? null,
|
|
2743
|
-
JSON.stringify(entry.tags ?? [])
|
|
2744
|
-
);
|
|
2745
|
-
};
|
|
2746
|
-
const ensureMigrated = async () => {
|
|
2747
|
-
if (!legacyStorageDriver || migrationComplete) return;
|
|
2748
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
2749
|
-
MIGRATION_MARKER_KEY2,
|
|
2750
|
-
false
|
|
2751
|
-
);
|
|
2752
|
-
if (migrated) {
|
|
2753
|
-
migrationComplete = true;
|
|
2754
|
-
return;
|
|
2755
|
-
}
|
|
2756
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2757
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2758
|
-
[]
|
|
2759
|
-
);
|
|
2760
|
-
for (const entry of legacyEntries) {
|
|
2761
|
-
appendOne(entry);
|
|
2762
|
-
}
|
|
2763
|
-
if (legacyEntries.length > 0) {
|
|
2764
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2765
|
-
}
|
|
2766
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
2767
|
-
migrationComplete = true;
|
|
2768
|
-
};
|
|
2769
|
-
const mapRow = (row) => ({
|
|
2770
|
-
id: row.id,
|
|
2771
|
-
timestamp: row.timestamp,
|
|
2772
|
-
modelId: row.model_id,
|
|
2773
|
-
baseUrl: row.base_url,
|
|
2774
|
-
requestId: row.request_id,
|
|
2775
|
-
cost: row.cost,
|
|
2776
|
-
satsCost: row.sats_cost,
|
|
2777
|
-
promptTokens: row.prompt_tokens,
|
|
2778
|
-
completionTokens: row.completion_tokens,
|
|
2779
|
-
totalTokens: row.total_tokens,
|
|
2780
|
-
client: row.client ?? void 0,
|
|
2781
|
-
sessionId: row.session_id ?? void 0,
|
|
2782
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
2783
|
-
});
|
|
2784
|
-
return {
|
|
2785
|
-
async migrate() {
|
|
2786
|
-
await ensureInit();
|
|
2787
|
-
await ensureMigrated();
|
|
2788
|
-
},
|
|
2789
|
-
async append(entry) {
|
|
2790
|
-
await ensureInit();
|
|
2791
|
-
await ensureMigrated();
|
|
2792
|
-
appendOne(entry);
|
|
2793
|
-
},
|
|
2794
|
-
async appendMany(entries) {
|
|
2795
|
-
await ensureInit();
|
|
2796
|
-
await ensureMigrated();
|
|
2797
|
-
for (const entry of entries) {
|
|
2798
|
-
appendOne(entry);
|
|
2799
|
-
}
|
|
2800
|
-
},
|
|
2801
|
-
async list(options2 = {}) {
|
|
2802
|
-
await ensureInit();
|
|
2803
|
-
await ensureMigrated();
|
|
2804
|
-
const { sql, params } = buildWhereClause(options2);
|
|
2805
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
2806
|
-
const stmt = db.prepare(
|
|
2807
|
-
`SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`
|
|
2808
|
-
);
|
|
2809
|
-
const rows = stmt.all(
|
|
2810
|
-
...typeof options2.limit === "number" ? [...params, options2.limit] : params
|
|
2811
|
-
);
|
|
2812
|
-
return rows.map(mapRow);
|
|
2813
|
-
},
|
|
2814
|
-
async count(options2 = {}) {
|
|
2815
|
-
await ensureInit();
|
|
2816
|
-
await ensureMigrated();
|
|
2817
|
-
const { sql, params } = buildWhereClause(options2);
|
|
2818
|
-
const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
|
|
2819
|
-
const row = stmt.get(...params);
|
|
2820
|
-
return Number(row?.count ?? 0);
|
|
2821
|
-
},
|
|
2822
|
-
async deleteOlderThan(timestamp) {
|
|
2823
|
-
await ensureInit();
|
|
2824
|
-
await ensureMigrated();
|
|
2825
|
-
const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
|
|
2826
|
-
const result = stmt.run(timestamp);
|
|
2827
|
-
return result.changes;
|
|
2828
|
-
},
|
|
2829
|
-
async clear() {
|
|
2830
|
-
await ensureInit();
|
|
2831
|
-
await ensureMigrated();
|
|
2832
|
-
db.prepare(`DELETE FROM ${tableName}`).run();
|
|
2833
|
-
}
|
|
2834
|
-
};
|
|
2835
|
-
};
|
|
2836
|
-
|
|
2837
|
-
// storage/usageTracking/bunSqlite.ts
|
|
2838
|
-
var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
|
|
2839
|
-
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2840
|
-
var buildWhereClause2 = (options = {}) => {
|
|
2841
|
-
const clauses = [];
|
|
2842
|
-
const params = [];
|
|
2843
|
-
if (typeof options.before === "number") {
|
|
2844
|
-
clauses.push("timestamp < ?");
|
|
2845
|
-
params.push(options.before);
|
|
2846
|
-
}
|
|
2847
|
-
if (typeof options.after === "number") {
|
|
2848
|
-
clauses.push("timestamp > ?");
|
|
2849
|
-
params.push(options.after);
|
|
2850
|
-
}
|
|
2851
|
-
if (options.modelId) {
|
|
2852
|
-
clauses.push("model_id = ?");
|
|
2853
|
-
params.push(options.modelId);
|
|
2854
|
-
}
|
|
2855
|
-
if (options.baseUrl) {
|
|
2856
|
-
clauses.push("base_url = ?");
|
|
2857
|
-
params.push(normalizeBaseUrl3(options.baseUrl));
|
|
2858
|
-
}
|
|
2859
|
-
if (options.sessionId) {
|
|
2860
|
-
clauses.push("session_id = ?");
|
|
2861
|
-
params.push(options.sessionId);
|
|
2862
|
-
}
|
|
2863
|
-
if (options.client) {
|
|
2864
|
-
clauses.push("client = ?");
|
|
2865
|
-
params.push(options.client);
|
|
2866
|
-
}
|
|
2867
|
-
return {
|
|
2868
|
-
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
2869
|
-
params
|
|
2870
|
-
};
|
|
2871
|
-
};
|
|
2872
|
-
var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
2873
|
-
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2874
|
-
const tableName = options.tableName || "usage_tracking";
|
|
2875
|
-
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2876
|
-
const SQLiteDatabase = options.sqlite?.Database;
|
|
2877
|
-
let migrationPromise = null;
|
|
2878
|
-
if (!SQLiteDatabase) {
|
|
2879
|
-
throw new Error(
|
|
2880
|
-
"Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
|
|
2881
|
-
);
|
|
2882
|
-
}
|
|
2883
|
-
const db = new SQLiteDatabase(dbPath);
|
|
2884
|
-
db.run(`
|
|
2885
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2886
|
-
id TEXT PRIMARY KEY,
|
|
2887
|
-
timestamp INTEGER NOT NULL,
|
|
2888
|
-
model_id TEXT NOT NULL,
|
|
2889
|
-
base_url TEXT NOT NULL,
|
|
2890
|
-
request_id TEXT NOT NULL,
|
|
2891
|
-
cost REAL NOT NULL,
|
|
2892
|
-
sats_cost REAL NOT NULL,
|
|
2893
|
-
prompt_tokens INTEGER NOT NULL,
|
|
2894
|
-
completion_tokens INTEGER NOT NULL,
|
|
2895
|
-
total_tokens INTEGER NOT NULL,
|
|
2896
|
-
client TEXT,
|
|
2897
|
-
session_id TEXT,
|
|
2898
|
-
tags TEXT
|
|
2899
|
-
)
|
|
2900
|
-
`);
|
|
2901
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
|
|
2902
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
|
|
2903
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
|
|
2904
|
-
const appendOne = (entry) => {
|
|
2905
|
-
db.query(`
|
|
2906
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
2907
|
-
id, timestamp, model_id, base_url, request_id,
|
|
2908
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2909
|
-
client, session_id, tags
|
|
2910
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2911
|
-
`).run(
|
|
2912
|
-
entry.id,
|
|
2913
|
-
entry.timestamp,
|
|
2914
|
-
entry.modelId,
|
|
2915
|
-
normalizeBaseUrl3(entry.baseUrl),
|
|
2916
|
-
entry.requestId,
|
|
2917
|
-
entry.cost,
|
|
2918
|
-
entry.satsCost,
|
|
2919
|
-
entry.promptTokens,
|
|
2920
|
-
entry.completionTokens,
|
|
2921
|
-
entry.totalTokens,
|
|
2922
|
-
entry.client ?? null,
|
|
2923
|
-
entry.sessionId ?? null,
|
|
2924
|
-
JSON.stringify(entry.tags ?? [])
|
|
2925
|
-
);
|
|
2926
|
-
};
|
|
2927
|
-
const mapRow = (row) => ({
|
|
2928
|
-
id: row.id,
|
|
2929
|
-
timestamp: row.timestamp,
|
|
2930
|
-
modelId: row.model_id,
|
|
2931
|
-
baseUrl: row.base_url,
|
|
2932
|
-
requestId: row.request_id,
|
|
2933
|
-
cost: row.cost,
|
|
2934
|
-
satsCost: row.sats_cost,
|
|
2935
|
-
promptTokens: row.prompt_tokens,
|
|
2936
|
-
completionTokens: row.completion_tokens,
|
|
2937
|
-
totalTokens: row.total_tokens,
|
|
2938
|
-
client: row.client ?? void 0,
|
|
2939
|
-
sessionId: row.session_id ?? void 0,
|
|
2940
|
-
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
2941
|
-
});
|
|
2942
|
-
const ensureMigrated = async () => {
|
|
2943
|
-
if (!legacyStorageDriver) return;
|
|
2944
|
-
if (!migrationPromise) {
|
|
2945
|
-
migrationPromise = (async () => {
|
|
2946
|
-
const migrated = await legacyStorageDriver.getItem(
|
|
2947
|
-
MIGRATION_MARKER_KEY3,
|
|
2948
|
-
false
|
|
2949
|
-
);
|
|
2950
|
-
if (migrated) return;
|
|
2951
|
-
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2952
|
-
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2953
|
-
[]
|
|
2954
|
-
);
|
|
2955
|
-
if (legacyEntries.length > 0) {
|
|
2956
|
-
for (const entry of legacyEntries) {
|
|
2957
|
-
appendOne(entry);
|
|
2958
|
-
}
|
|
2959
|
-
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2960
|
-
}
|
|
2961
|
-
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
|
|
2962
|
-
})();
|
|
2963
|
-
}
|
|
2964
|
-
await migrationPromise;
|
|
2965
|
-
};
|
|
2966
|
-
return {
|
|
2967
|
-
async migrate() {
|
|
2968
|
-
await ensureMigrated();
|
|
2969
|
-
},
|
|
2970
|
-
async append(entry) {
|
|
2971
|
-
await ensureMigrated();
|
|
2972
|
-
appendOne(entry);
|
|
2973
|
-
},
|
|
2974
|
-
async appendMany(entries) {
|
|
2975
|
-
await ensureMigrated();
|
|
2976
|
-
for (const entry of entries) {
|
|
2977
|
-
appendOne(entry);
|
|
2978
|
-
}
|
|
2979
|
-
},
|
|
2980
|
-
async list(options2 = {}) {
|
|
2981
|
-
await ensureMigrated();
|
|
2982
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
2983
|
-
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
2984
|
-
const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
|
|
2985
|
-
let rows;
|
|
2986
|
-
if (typeof options2.limit === "number") {
|
|
2987
|
-
rows = db.query(query).all(...params, options2.limit);
|
|
2988
|
-
} else {
|
|
2989
|
-
rows = db.query(query).all(...params);
|
|
2990
|
-
}
|
|
2991
|
-
return rows.map(mapRow);
|
|
2992
|
-
},
|
|
2993
|
-
async count(options2 = {}) {
|
|
2994
|
-
const { sql, params } = buildWhereClause2(options2);
|
|
2995
|
-
const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
|
|
2996
|
-
const row = db.query(query).get(...params);
|
|
2997
|
-
return Number(row?.count ?? 0);
|
|
2998
|
-
},
|
|
2999
|
-
async deleteOlderThan(timestamp) {
|
|
3000
|
-
await ensureMigrated();
|
|
3001
|
-
const before = timestamp;
|
|
3002
|
-
const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
|
|
3003
|
-
return result.changes ?? 0;
|
|
3004
|
-
},
|
|
3005
|
-
async clear() {
|
|
3006
|
-
await ensureMigrated();
|
|
3007
|
-
db.query(`DELETE FROM ${tableName}`).run();
|
|
3008
|
-
}
|
|
3009
|
-
};
|
|
3010
|
-
};
|
|
3011
|
-
|
|
3012
2251
|
// storage/usageTracking/memory.ts
|
|
3013
|
-
var
|
|
2252
|
+
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3014
2253
|
var matchesFilters2 = (entry, options = {}) => {
|
|
3015
2254
|
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
3016
2255
|
return false;
|
|
@@ -3021,7 +2260,7 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
3021
2260
|
if (options.modelId && entry.modelId !== options.modelId) {
|
|
3022
2261
|
return false;
|
|
3023
2262
|
}
|
|
3024
|
-
if (options.baseUrl &&
|
|
2263
|
+
if (options.baseUrl && normalizeBaseUrl2(entry.baseUrl) !== normalizeBaseUrl2(options.baseUrl)) {
|
|
3025
2264
|
return false;
|
|
3026
2265
|
}
|
|
3027
2266
|
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
@@ -3030,23 +2269,26 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
3030
2269
|
if (options.client && entry.client !== options.client) {
|
|
3031
2270
|
return false;
|
|
3032
2271
|
}
|
|
2272
|
+
if (options.provider && entry.provider !== options.provider) {
|
|
2273
|
+
return false;
|
|
2274
|
+
}
|
|
3033
2275
|
return true;
|
|
3034
2276
|
};
|
|
3035
2277
|
var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
3036
2278
|
const store = /* @__PURE__ */ new Map();
|
|
3037
2279
|
for (const entry of seed) {
|
|
3038
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2280
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
3039
2281
|
}
|
|
3040
2282
|
return {
|
|
3041
2283
|
async migrate() {
|
|
3042
2284
|
return;
|
|
3043
2285
|
},
|
|
3044
2286
|
async append(entry) {
|
|
3045
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2287
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
3046
2288
|
},
|
|
3047
2289
|
async appendMany(entries) {
|
|
3048
2290
|
for (const entry of entries) {
|
|
3049
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2291
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl2(entry.baseUrl) });
|
|
3050
2292
|
}
|
|
3051
2293
|
},
|
|
3052
2294
|
async list(options = {}) {
|
|
@@ -3074,7 +2316,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
3074
2316
|
}
|
|
3075
2317
|
};
|
|
3076
2318
|
};
|
|
3077
|
-
var
|
|
2319
|
+
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3078
2320
|
var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
3079
2321
|
modelsFromAllProviders: {},
|
|
3080
2322
|
lastUsedModel: null,
|
|
@@ -3097,7 +2339,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3097
2339
|
setModelsFromAllProviders: (value) => {
|
|
3098
2340
|
const normalized = {};
|
|
3099
2341
|
for (const [baseUrl, models] of Object.entries(value)) {
|
|
3100
|
-
normalized[
|
|
2342
|
+
normalized[normalizeBaseUrl3(baseUrl)] = models;
|
|
3101
2343
|
}
|
|
3102
2344
|
void driver.setItem(
|
|
3103
2345
|
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
@@ -3110,7 +2352,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3110
2352
|
set({ lastUsedModel: value });
|
|
3111
2353
|
},
|
|
3112
2354
|
setBaseUrlsList: (value) => {
|
|
3113
|
-
const normalized = value.map((url) =>
|
|
2355
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
3114
2356
|
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
3115
2357
|
set({ baseUrlsList: normalized });
|
|
3116
2358
|
},
|
|
@@ -3119,14 +2361,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3119
2361
|
set({ lastBaseUrlsUpdate: value });
|
|
3120
2362
|
},
|
|
3121
2363
|
setDisabledProviders: (value) => {
|
|
3122
|
-
const normalized = value.map((url) =>
|
|
2364
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
3123
2365
|
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
3124
2366
|
set({ disabledProviders: normalized });
|
|
3125
2367
|
},
|
|
3126
2368
|
setMintsFromAllProviders: (value) => {
|
|
3127
2369
|
const normalized = {};
|
|
3128
2370
|
for (const [baseUrl, mints] of Object.entries(value)) {
|
|
3129
|
-
normalized[
|
|
2371
|
+
normalized[normalizeBaseUrl3(baseUrl)] = mints.map(
|
|
3130
2372
|
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
3131
2373
|
);
|
|
3132
2374
|
}
|
|
@@ -3139,7 +2381,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3139
2381
|
setInfoFromAllProviders: (value) => {
|
|
3140
2382
|
const normalized = {};
|
|
3141
2383
|
for (const [baseUrl, info] of Object.entries(value)) {
|
|
3142
|
-
normalized[
|
|
2384
|
+
normalized[normalizeBaseUrl3(baseUrl)] = info;
|
|
3143
2385
|
}
|
|
3144
2386
|
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
3145
2387
|
set({ infoFromAllProviders: normalized });
|
|
@@ -3147,7 +2389,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3147
2389
|
setLastModelsUpdate: (value) => {
|
|
3148
2390
|
const normalized = {};
|
|
3149
2391
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
3150
|
-
normalized[
|
|
2392
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
3151
2393
|
}
|
|
3152
2394
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
3153
2395
|
set({ lastModelsUpdate: normalized });
|
|
@@ -3157,7 +2399,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3157
2399
|
const updates = typeof value === "function" ? value(state.apiKeys) : value;
|
|
3158
2400
|
const normalized = updates.map((entry) => ({
|
|
3159
2401
|
...entry,
|
|
3160
|
-
baseUrl:
|
|
2402
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3161
2403
|
balance: entry.balance ?? 0,
|
|
3162
2404
|
lastUsed: entry.lastUsed ?? null
|
|
3163
2405
|
}));
|
|
@@ -3169,7 +2411,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3169
2411
|
set((state) => {
|
|
3170
2412
|
const updates = typeof value === "function" ? value(state.childKeys) : value;
|
|
3171
2413
|
const normalized = updates.map((entry) => ({
|
|
3172
|
-
parentBaseUrl:
|
|
2414
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
3173
2415
|
childKey: entry.childKey,
|
|
3174
2416
|
balance: entry.balance ?? 0,
|
|
3175
2417
|
balanceLimit: entry.balanceLimit,
|
|
@@ -3183,9 +2425,9 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3183
2425
|
setXcashuTokens: (value) => {
|
|
3184
2426
|
const normalized = {};
|
|
3185
2427
|
for (const [baseUrl, tokens] of Object.entries(value)) {
|
|
3186
|
-
normalized[
|
|
2428
|
+
normalized[normalizeBaseUrl3(baseUrl)] = tokens.map((entry) => ({
|
|
3187
2429
|
...entry,
|
|
3188
|
-
baseUrl:
|
|
2430
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3189
2431
|
createdAt: entry.createdAt ?? Date.now(),
|
|
3190
2432
|
tryCount: entry.tryCount ?? 0
|
|
3191
2433
|
}));
|
|
@@ -3236,12 +2478,12 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3236
2478
|
},
|
|
3237
2479
|
// ========== Failure Tracking ==========
|
|
3238
2480
|
setFailedProviders: (value) => {
|
|
3239
|
-
const normalized = value.map((url) =>
|
|
2481
|
+
const normalized = value.map((url) => normalizeBaseUrl3(url));
|
|
3240
2482
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
|
|
3241
2483
|
set({ failedProviders: normalized });
|
|
3242
2484
|
},
|
|
3243
2485
|
addFailedProvider: (baseUrl) => {
|
|
3244
|
-
const normalized =
|
|
2486
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3245
2487
|
const current = get().failedProviders;
|
|
3246
2488
|
if (!current.includes(normalized)) {
|
|
3247
2489
|
const updated = [...current, normalized];
|
|
@@ -3250,7 +2492,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3250
2492
|
}
|
|
3251
2493
|
},
|
|
3252
2494
|
removeFailedProvider: (baseUrl) => {
|
|
3253
|
-
const normalized =
|
|
2495
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3254
2496
|
const current = get().failedProviders;
|
|
3255
2497
|
const updated = current.filter((url) => url !== normalized);
|
|
3256
2498
|
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
@@ -3259,13 +2501,13 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3259
2501
|
setLastFailed: (value) => {
|
|
3260
2502
|
const normalized = {};
|
|
3261
2503
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
3262
|
-
normalized[
|
|
2504
|
+
normalized[normalizeBaseUrl3(baseUrl)] = timestamp;
|
|
3263
2505
|
}
|
|
3264
2506
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
|
|
3265
2507
|
set({ lastFailed: normalized });
|
|
3266
2508
|
},
|
|
3267
2509
|
setLastFailedTimestamp: (baseUrl, timestamp) => {
|
|
3268
|
-
const normalized =
|
|
2510
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3269
2511
|
const current = get().lastFailed;
|
|
3270
2512
|
const updated = { ...current, [normalized]: timestamp };
|
|
3271
2513
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
|
|
@@ -3273,14 +2515,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3273
2515
|
},
|
|
3274
2516
|
setProvidersOnCooldown: (value) => {
|
|
3275
2517
|
const normalized = value.map((entry) => ({
|
|
3276
|
-
baseUrl:
|
|
2518
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3277
2519
|
timestamp: entry.timestamp
|
|
3278
2520
|
}));
|
|
3279
2521
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
|
|
3280
2522
|
set({ providersOnCooldown: normalized });
|
|
3281
2523
|
},
|
|
3282
2524
|
addProviderOnCooldown: (baseUrl, timestamp) => {
|
|
3283
|
-
const normalized =
|
|
2525
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3284
2526
|
const current = get().providersOnCooldown;
|
|
3285
2527
|
if (!current.some((entry) => entry.baseUrl === normalized)) {
|
|
3286
2528
|
const updated = [...current, { baseUrl: normalized, timestamp }];
|
|
@@ -3289,7 +2531,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
3289
2531
|
}
|
|
3290
2532
|
},
|
|
3291
2533
|
removeProviderFromCooldown: (baseUrl) => {
|
|
3292
|
-
const normalized =
|
|
2534
|
+
const normalized = normalizeBaseUrl3(baseUrl);
|
|
3293
2535
|
const current = get().providersOnCooldown;
|
|
3294
2536
|
const updated = current.filter((entry) => entry.baseUrl !== normalized);
|
|
3295
2537
|
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
@@ -3360,40 +2602,40 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3360
2602
|
]);
|
|
3361
2603
|
const modelsFromAllProviders = Object.fromEntries(
|
|
3362
2604
|
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
3363
|
-
|
|
2605
|
+
normalizeBaseUrl3(baseUrl),
|
|
3364
2606
|
models
|
|
3365
2607
|
])
|
|
3366
2608
|
);
|
|
3367
|
-
const baseUrlsList = rawBaseUrls.map((url) =>
|
|
2609
|
+
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl3(url));
|
|
3368
2610
|
const disabledProviders = rawDisabledProviders.map(
|
|
3369
|
-
(url) =>
|
|
2611
|
+
(url) => normalizeBaseUrl3(url)
|
|
3370
2612
|
);
|
|
3371
2613
|
const mintsFromAllProviders = Object.fromEntries(
|
|
3372
2614
|
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
3373
|
-
|
|
2615
|
+
normalizeBaseUrl3(baseUrl),
|
|
3374
2616
|
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
3375
2617
|
])
|
|
3376
2618
|
);
|
|
3377
2619
|
const infoFromAllProviders = Object.fromEntries(
|
|
3378
2620
|
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
3379
|
-
|
|
2621
|
+
normalizeBaseUrl3(baseUrl),
|
|
3380
2622
|
info
|
|
3381
2623
|
])
|
|
3382
2624
|
);
|
|
3383
2625
|
const lastModelsUpdate = Object.fromEntries(
|
|
3384
2626
|
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
3385
|
-
|
|
2627
|
+
normalizeBaseUrl3(baseUrl),
|
|
3386
2628
|
timestamp
|
|
3387
2629
|
])
|
|
3388
2630
|
);
|
|
3389
2631
|
const apiKeys = rawApiKeys.map((entry) => ({
|
|
3390
2632
|
...entry,
|
|
3391
|
-
baseUrl:
|
|
2633
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3392
2634
|
balance: entry.balance ?? 0,
|
|
3393
2635
|
lastUsed: entry.lastUsed ?? null
|
|
3394
2636
|
}));
|
|
3395
2637
|
const childKeys = rawChildKeys.map((entry) => ({
|
|
3396
|
-
parentBaseUrl:
|
|
2638
|
+
parentBaseUrl: normalizeBaseUrl3(entry.parentBaseUrl),
|
|
3397
2639
|
childKey: entry.childKey,
|
|
3398
2640
|
balance: entry.balance ?? 0,
|
|
3399
2641
|
balanceLimit: entry.balanceLimit,
|
|
@@ -3402,9 +2644,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3402
2644
|
}));
|
|
3403
2645
|
const xcashuTokens = Object.fromEntries(
|
|
3404
2646
|
Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
|
|
3405
|
-
|
|
2647
|
+
normalizeBaseUrl3(baseUrl),
|
|
3406
2648
|
tokens.map((entry) => ({
|
|
3407
|
-
baseUrl:
|
|
2649
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3408
2650
|
token: entry.token,
|
|
3409
2651
|
createdAt: entry.createdAt ?? Date.now(),
|
|
3410
2652
|
tryCount: entry.tryCount ?? 0
|
|
@@ -3425,16 +2667,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3425
2667
|
lastUsed: entry.lastUsed ?? null
|
|
3426
2668
|
}));
|
|
3427
2669
|
const failedProviders = rawFailedProviders.map(
|
|
3428
|
-
(url) =>
|
|
2670
|
+
(url) => normalizeBaseUrl3(url)
|
|
3429
2671
|
);
|
|
3430
2672
|
const lastFailed = Object.fromEntries(
|
|
3431
2673
|
Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
|
|
3432
|
-
|
|
2674
|
+
normalizeBaseUrl3(baseUrl),
|
|
3433
2675
|
timestamp
|
|
3434
2676
|
])
|
|
3435
2677
|
);
|
|
3436
2678
|
const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
|
|
3437
|
-
baseUrl:
|
|
2679
|
+
baseUrl: normalizeBaseUrl3(entry.baseUrl),
|
|
3438
2680
|
timestamp: entry.timestamp
|
|
3439
2681
|
}));
|
|
3440
2682
|
store.setState({
|
|
@@ -3476,77 +2718,210 @@ var isBrowser2 = () => {
|
|
|
3476
2718
|
return false;
|
|
3477
2719
|
}
|
|
3478
2720
|
};
|
|
3479
|
-
var isNode = () => {
|
|
3480
|
-
try {
|
|
3481
|
-
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
3482
|
-
} catch {
|
|
3483
|
-
return false;
|
|
3484
|
-
}
|
|
3485
|
-
};
|
|
3486
2721
|
var defaultDriver = null;
|
|
3487
|
-
var isBun3 = () => {
|
|
3488
|
-
return typeof process.versions.bun !== "undefined";
|
|
3489
|
-
};
|
|
3490
2722
|
var getDefaultSdkDriver = () => {
|
|
3491
2723
|
if (defaultDriver) return defaultDriver;
|
|
3492
2724
|
if (isBrowser2()) {
|
|
3493
2725
|
defaultDriver = localStorageDriver;
|
|
3494
2726
|
return defaultDriver;
|
|
3495
2727
|
}
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
2728
|
+
defaultDriver = createMemoryDriver();
|
|
2729
|
+
return defaultDriver;
|
|
2730
|
+
};
|
|
2731
|
+
var defaultStore = null;
|
|
2732
|
+
var defaultUsageTrackingDriver = null;
|
|
2733
|
+
var getDefaultSdkStore = () => {
|
|
2734
|
+
if (!defaultStore) {
|
|
2735
|
+
defaultStore = createSdkStore({ driver: getDefaultSdkDriver() });
|
|
2736
|
+
}
|
|
2737
|
+
return defaultStore.hydrate.then(() => defaultStore.store);
|
|
2738
|
+
};
|
|
2739
|
+
var getDefaultUsageTrackingDriver = () => {
|
|
2740
|
+
if (defaultUsageTrackingDriver) return defaultUsageTrackingDriver;
|
|
2741
|
+
const storageDriver = getDefaultSdkDriver();
|
|
2742
|
+
if (isBrowser2()) {
|
|
2743
|
+
defaultUsageTrackingDriver = createIndexedDBUsageTrackingDriver({
|
|
2744
|
+
legacyStorageDriver: storageDriver
|
|
2745
|
+
});
|
|
2746
|
+
return defaultUsageTrackingDriver;
|
|
2747
|
+
}
|
|
2748
|
+
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
2749
|
+
return defaultUsageTrackingDriver;
|
|
2750
|
+
};
|
|
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 {};
|
|
2756
|
+
return {
|
|
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)
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
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;
|
|
2789
|
+
}
|
|
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
|
|
2804
|
+
};
|
|
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
|
+
};
|
|
3499
2832
|
}
|
|
3500
|
-
if (
|
|
3501
|
-
|
|
3502
|
-
return defaultDriver;
|
|
2833
|
+
if (!parsed.usage) {
|
|
2834
|
+
return null;
|
|
3503
2835
|
}
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
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);
|
|
3512
2847
|
}
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
if (defaultUsageTrackingDriver) return defaultUsageTrackingDriver;
|
|
3517
|
-
const storageDriver = getDefaultSdkDriver();
|
|
3518
|
-
if (isBrowser2()) {
|
|
3519
|
-
defaultUsageTrackingDriver = createIndexedDBUsageTrackingDriver({
|
|
3520
|
-
legacyStorageDriver: storageDriver
|
|
3521
|
-
});
|
|
3522
|
-
return defaultUsageTrackingDriver;
|
|
2848
|
+
const routstrCost = parsed.metadata?.routstr?.cost;
|
|
2849
|
+
if (routstrCost && typeof routstrCost === "object") {
|
|
2850
|
+
breakdown = { ...extractCostBreakdown(routstrCost), ...breakdown };
|
|
3523
2851
|
}
|
|
3524
|
-
if (
|
|
3525
|
-
|
|
3526
|
-
return defaultUsageTrackingDriver;
|
|
2852
|
+
if (cost === 0) {
|
|
2853
|
+
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
3527
2854
|
}
|
|
3528
|
-
if (
|
|
3529
|
-
|
|
3530
|
-
legacyStorageDriver: storageDriver
|
|
3531
|
-
});
|
|
3532
|
-
return defaultUsageTrackingDriver;
|
|
2855
|
+
if (msats === 0) {
|
|
2856
|
+
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
3533
2857
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
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
|
+
}
|
|
3537
2885
|
function mergeUsage(previous, next) {
|
|
3538
2886
|
if (!previous) return next;
|
|
2887
|
+
const pickNum = (n, p) => typeof n === "number" && n > 0 ? n : p ?? n;
|
|
3539
2888
|
return {
|
|
3540
2889
|
promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
|
|
3541
2890
|
completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
|
|
3542
2891
|
totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
|
|
3543
2892
|
cost: next.cost > 0 ? next.cost : previous.cost,
|
|
3544
|
-
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
|
|
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
|
+
)
|
|
3545
2917
|
};
|
|
3546
2918
|
}
|
|
3547
2919
|
function hasUsageChanged(previous, next) {
|
|
3548
2920
|
if (!previous) return true;
|
|
3549
|
-
return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
|
|
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;
|
|
3550
2925
|
}
|
|
3551
2926
|
async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
3552
2927
|
const reader = stream.getReader();
|
|
@@ -3556,14 +2931,22 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
3556
2931
|
let capturedResponseId;
|
|
3557
2932
|
let responseIdCaptured = false;
|
|
3558
2933
|
const inspectDataPayload = (jsonText) => {
|
|
3559
|
-
|
|
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));
|
|
3560
2941
|
return;
|
|
3561
2942
|
}
|
|
3562
|
-
const trimmed = jsonText.trim();
|
|
3563
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
3564
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
3565
2943
|
try {
|
|
3566
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
|
+
}
|
|
3567
2950
|
if (!responseIdCaptured) {
|
|
3568
2951
|
const responseId = data?.id;
|
|
3569
2952
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -3574,19 +2957,21 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
3574
2957
|
}
|
|
3575
2958
|
const usage = extractUsageFromSSEJson(data);
|
|
3576
2959
|
if (usage) {
|
|
2960
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
3577
2961
|
const merged = mergeUsage(capturedUsage, usage);
|
|
3578
2962
|
if (hasUsageChanged(capturedUsage, merged)) {
|
|
3579
2963
|
capturedUsage = merged;
|
|
2964
|
+
console.log("[routstr:sse] \u2192 merged (changed):", merged);
|
|
3580
2965
|
onUsage(merged);
|
|
2966
|
+
} else {
|
|
2967
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
3581
2968
|
}
|
|
3582
2969
|
}
|
|
3583
2970
|
} catch {
|
|
2971
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
3584
2972
|
}
|
|
3585
2973
|
};
|
|
3586
2974
|
const inspectEventBlock = (eventBlock) => {
|
|
3587
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3588
|
-
return;
|
|
3589
|
-
}
|
|
3590
2975
|
const lines = eventBlock.split(/\r?\n/);
|
|
3591
2976
|
const dataParts = [];
|
|
3592
2977
|
for (const line of lines) {
|
|
@@ -3644,14 +3029,22 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3644
3029
|
let capturedUsage = null;
|
|
3645
3030
|
let responseIdCaptured = false;
|
|
3646
3031
|
const inspectDataPayload = (jsonText) => {
|
|
3647
|
-
|
|
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));
|
|
3648
3039
|
return;
|
|
3649
3040
|
}
|
|
3650
|
-
const trimmed = jsonText.trim();
|
|
3651
|
-
if (!trimmed || trimmed === "[DONE]") return;
|
|
3652
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
3653
3041
|
try {
|
|
3654
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
|
+
}
|
|
3655
3048
|
if (!responseIdCaptured) {
|
|
3656
3049
|
const responseId = data?.id;
|
|
3657
3050
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
@@ -3661,19 +3054,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3661
3054
|
}
|
|
3662
3055
|
const usage = extractUsageFromSSEJson(data);
|
|
3663
3056
|
if (usage) {
|
|
3057
|
+
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
3664
3058
|
const mergedUsage = mergeUsage(capturedUsage, usage);
|
|
3665
3059
|
if (hasUsageChanged(capturedUsage, mergedUsage)) {
|
|
3666
3060
|
capturedUsage = mergedUsage;
|
|
3061
|
+
console.log("[routstr:sse] \u2192 merged (changed):", mergedUsage);
|
|
3667
3062
|
onUsage(mergedUsage);
|
|
3063
|
+
} else {
|
|
3064
|
+
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
3668
3065
|
}
|
|
3669
3066
|
}
|
|
3670
3067
|
} catch {
|
|
3068
|
+
console.log("[routstr:sse] failed to parse payload:", trimmed.slice(0, 200));
|
|
3671
3069
|
}
|
|
3672
3070
|
};
|
|
3673
3071
|
const inspectEventBlock = (eventBlock) => {
|
|
3674
|
-
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3675
|
-
return;
|
|
3676
|
-
}
|
|
3677
3072
|
const lines = eventBlock.split(/\r?\n/);
|
|
3678
3073
|
const dataParts = [];
|
|
3679
3074
|
for (const line of lines) {
|
|
@@ -3746,7 +3141,6 @@ var RoutstrClient = class {
|
|
|
3746
3141
|
this.balanceManager,
|
|
3747
3142
|
this.logger
|
|
3748
3143
|
);
|
|
3749
|
-
this.streamProcessor = new StreamProcessor();
|
|
3750
3144
|
this.alertLevel = alertLevel;
|
|
3751
3145
|
this.mode = mode;
|
|
3752
3146
|
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
@@ -3758,7 +3152,6 @@ var RoutstrClient = class {
|
|
|
3758
3152
|
providerRegistry;
|
|
3759
3153
|
cashuSpender;
|
|
3760
3154
|
balanceManager;
|
|
3761
|
-
streamProcessor;
|
|
3762
3155
|
providerManager;
|
|
3763
3156
|
alertLevel;
|
|
3764
3157
|
mode;
|
|
@@ -4000,153 +3393,6 @@ var RoutstrClient = class {
|
|
|
4000
3393
|
}
|
|
4001
3394
|
return void 0;
|
|
4002
3395
|
}
|
|
4003
|
-
/**
|
|
4004
|
-
* Fetch AI response with streaming
|
|
4005
|
-
*/
|
|
4006
|
-
async fetchAIResponse(options, callbacks) {
|
|
4007
|
-
const {
|
|
4008
|
-
messageHistory,
|
|
4009
|
-
selectedModel,
|
|
4010
|
-
baseUrl,
|
|
4011
|
-
mintUrl,
|
|
4012
|
-
balance,
|
|
4013
|
-
transactionHistory,
|
|
4014
|
-
maxTokens,
|
|
4015
|
-
headers
|
|
4016
|
-
} = options;
|
|
4017
|
-
const apiMessages = await this._convertMessages(messageHistory);
|
|
4018
|
-
const requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
4019
|
-
selectedModel,
|
|
4020
|
-
apiMessages,
|
|
4021
|
-
maxTokens
|
|
4022
|
-
);
|
|
4023
|
-
try {
|
|
4024
|
-
await this._checkBalance();
|
|
4025
|
-
callbacks.onPaymentProcessing?.(true);
|
|
4026
|
-
const spendResult = await this._spendToken({
|
|
4027
|
-
mintUrl,
|
|
4028
|
-
amount: requiredSats,
|
|
4029
|
-
baseUrl
|
|
4030
|
-
});
|
|
4031
|
-
let token = spendResult.token;
|
|
4032
|
-
let tokenBalance = spendResult.tokenBalance;
|
|
4033
|
-
let tokenBalanceUnit = spendResult.tokenBalanceUnit;
|
|
4034
|
-
let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
4035
|
-
let initialTokenBalanceUnknown = spendResult.tokenBalanceUnknown;
|
|
4036
|
-
callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
|
|
4037
|
-
const baseHeaders = this._buildBaseHeaders(headers);
|
|
4038
|
-
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
4039
|
-
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
4040
|
-
const providerVersion = providerInfo?.version ?? "";
|
|
4041
|
-
let modelIdForRequest = selectedModel.id;
|
|
4042
|
-
if (/^0\.1\./.test(providerVersion)) {
|
|
4043
|
-
const newModel = await this.providerManager.getModelForProvider(
|
|
4044
|
-
baseUrl,
|
|
4045
|
-
selectedModel.id
|
|
4046
|
-
);
|
|
4047
|
-
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
4048
|
-
}
|
|
4049
|
-
const body = {
|
|
4050
|
-
model: modelIdForRequest,
|
|
4051
|
-
messages: apiMessages,
|
|
4052
|
-
stream: true
|
|
4053
|
-
};
|
|
4054
|
-
if (maxTokens !== void 0) {
|
|
4055
|
-
body.max_tokens = maxTokens;
|
|
4056
|
-
}
|
|
4057
|
-
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
4058
|
-
body.tools = [{ type: "web_search" }];
|
|
4059
|
-
}
|
|
4060
|
-
const response = await this._makeRequest({
|
|
4061
|
-
path: "/v1/chat/completions",
|
|
4062
|
-
method: "POST",
|
|
4063
|
-
body,
|
|
4064
|
-
selectedModel,
|
|
4065
|
-
baseUrl,
|
|
4066
|
-
mintUrl,
|
|
4067
|
-
token,
|
|
4068
|
-
requiredSats,
|
|
4069
|
-
maxTokens,
|
|
4070
|
-
headers: requestHeaders,
|
|
4071
|
-
baseHeaders
|
|
4072
|
-
});
|
|
4073
|
-
if (!response.body) {
|
|
4074
|
-
throw new Error("Response body is not available");
|
|
4075
|
-
}
|
|
4076
|
-
if (response.status === 200) {
|
|
4077
|
-
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
4078
|
-
const responseToken = response.token || token;
|
|
4079
|
-
if (baseUrlUsed !== baseUrl || responseToken !== token) {
|
|
4080
|
-
token = responseToken;
|
|
4081
|
-
if (typeof response.initialTokenBalanceInSats === "number") {
|
|
4082
|
-
tokenBalanceInSats = response.initialTokenBalanceInSats;
|
|
4083
|
-
initialTokenBalanceUnknown = Boolean(
|
|
4084
|
-
response.initialTokenBalanceUnknown
|
|
4085
|
-
);
|
|
4086
|
-
} else {
|
|
4087
|
-
initialTokenBalanceUnknown = true;
|
|
4088
|
-
}
|
|
4089
|
-
}
|
|
4090
|
-
const streamingResult = await this.streamProcessor.process(
|
|
4091
|
-
response,
|
|
4092
|
-
{
|
|
4093
|
-
onContent: callbacks.onStreamingUpdate,
|
|
4094
|
-
onThinking: callbacks.onThinkingUpdate
|
|
4095
|
-
},
|
|
4096
|
-
selectedModel.id
|
|
4097
|
-
);
|
|
4098
|
-
if (streamingResult.finish_reason === "content_filter") {
|
|
4099
|
-
callbacks.onMessageAppend({
|
|
4100
|
-
role: "assistant",
|
|
4101
|
-
content: "Your request was denied due to content filtering."
|
|
4102
|
-
});
|
|
4103
|
-
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
4104
|
-
const message = await this._createAssistantMessage(streamingResult);
|
|
4105
|
-
callbacks.onMessageAppend(message);
|
|
4106
|
-
} else {
|
|
4107
|
-
callbacks.onMessageAppend({
|
|
4108
|
-
role: "system",
|
|
4109
|
-
content: "The provider did not respond to this request."
|
|
4110
|
-
});
|
|
4111
|
-
}
|
|
4112
|
-
callbacks.onStreamingUpdate("");
|
|
4113
|
-
callbacks.onThinkingUpdate("");
|
|
4114
|
-
const isApikeysEstimate = this.mode === "apikeys";
|
|
4115
|
-
let satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
4116
|
-
token,
|
|
4117
|
-
baseUrl: baseUrlUsed,
|
|
4118
|
-
mintUrl,
|
|
4119
|
-
initialTokenBalance: tokenBalanceInSats,
|
|
4120
|
-
initialTokenBalanceUnknown,
|
|
4121
|
-
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
4122
|
-
response,
|
|
4123
|
-
modelId: selectedModel.id,
|
|
4124
|
-
usage: streamingResult.usage ? {
|
|
4125
|
-
promptTokens: Number(streamingResult.usage.prompt_tokens ?? 0),
|
|
4126
|
-
completionTokens: Number(
|
|
4127
|
-
streamingResult.usage.completion_tokens ?? 0
|
|
4128
|
-
),
|
|
4129
|
-
totalTokens: Number(streamingResult.usage.total_tokens ?? 0),
|
|
4130
|
-
cost: Number(streamingResult.usage.cost ?? 0),
|
|
4131
|
-
satsCost: Number(streamingResult.usage.sats_cost ?? 0)
|
|
4132
|
-
} : void 0,
|
|
4133
|
-
requestId: streamingResult.responseId
|
|
4134
|
-
});
|
|
4135
|
-
const estimatedCosts = this._getEstimatedCosts(
|
|
4136
|
-
selectedModel,
|
|
4137
|
-
streamingResult
|
|
4138
|
-
);
|
|
4139
|
-
const onLastMessageSatsUpdate = callbacks.onLastMessageSatsUpdate;
|
|
4140
|
-
onLastMessageSatsUpdate?.(satsSpent, estimatedCosts);
|
|
4141
|
-
} else {
|
|
4142
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
4143
|
-
}
|
|
4144
|
-
} catch (error) {
|
|
4145
|
-
this._handleError(error, callbacks);
|
|
4146
|
-
} finally {
|
|
4147
|
-
callbacks.onPaymentProcessing?.(false);
|
|
4148
|
-
}
|
|
4149
|
-
}
|
|
4150
3396
|
/**
|
|
4151
3397
|
* Make the API request with failover support
|
|
4152
3398
|
*/
|
|
@@ -4635,304 +3881,568 @@ var RoutstrClient = class {
|
|
|
4635
3881
|
if (!usage) {
|
|
4636
3882
|
return;
|
|
4637
3883
|
}
|
|
4638
|
-
} else {
|
|
4639
|
-
const cloned = response.clone();
|
|
4640
|
-
const responseBody = await cloned.json();
|
|
4641
|
-
usage = usage ?? extractUsageFromResponseBody(responseBody, satsSpent) ?? void 0;
|
|
4642
|
-
requestId = requestId ?? extractResponseId(responseBody) ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
3884
|
+
} else {
|
|
3885
|
+
const cloned = response.clone();
|
|
3886
|
+
const responseBody = await cloned.json();
|
|
3887
|
+
usage = usage ?? extractUsageFromResponseBody(responseBody, satsSpent) ?? void 0;
|
|
3888
|
+
requestId = requestId ?? extractResponseId(responseBody) ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
if (!usage) {
|
|
3892
|
+
return;
|
|
3893
|
+
}
|
|
3894
|
+
const finalRequestId = requestId || "unknown";
|
|
3895
|
+
const store = this.sdkStore ?? await getDefaultSdkStore();
|
|
3896
|
+
const state = store.getState();
|
|
3897
|
+
const matchKey = clientApiKey ?? token;
|
|
3898
|
+
const matchingClient = state.clientIds.find(
|
|
3899
|
+
(client) => client.apiKey === matchKey
|
|
3900
|
+
);
|
|
3901
|
+
const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
|
|
3902
|
+
const usageTracking = this.usageTrackingDriver ?? getDefaultUsageTrackingDriver();
|
|
3903
|
+
const entry = {
|
|
3904
|
+
id: entryId,
|
|
3905
|
+
timestamp: Date.now(),
|
|
3906
|
+
modelId,
|
|
3907
|
+
baseUrl,
|
|
3908
|
+
requestId: finalRequestId,
|
|
3909
|
+
client: matchingClient?.clientId,
|
|
3910
|
+
...usage
|
|
3911
|
+
};
|
|
3912
|
+
if (this.mode === "xcashu") {
|
|
3913
|
+
entry.satsCost = satsSpent;
|
|
3914
|
+
}
|
|
3915
|
+
await usageTracking.append(entry);
|
|
3916
|
+
} catch (error) {
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
/**
|
|
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
|
+
}
|
|
4643
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
|
+
);
|
|
4644
4002
|
}
|
|
4645
|
-
|
|
4646
|
-
|
|
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;
|
|
4647
4012
|
}
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
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}`
|
|
4654
4029
|
);
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
modelId,
|
|
4661
|
-
baseUrl,
|
|
4662
|
-
requestId: finalRequestId,
|
|
4663
|
-
client: matchingClient?.clientId,
|
|
4664
|
-
...usage
|
|
4030
|
+
return {
|
|
4031
|
+
token: parentApiKey?.key ?? "",
|
|
4032
|
+
tokenBalance,
|
|
4033
|
+
tokenBalanceUnit,
|
|
4034
|
+
tokenBalanceUnknown
|
|
4665
4035
|
};
|
|
4666
|
-
if (this.mode === "xcashu") {
|
|
4667
|
-
entry.satsCost = satsSpent;
|
|
4668
|
-
}
|
|
4669
|
-
await usageTracking.append(entry);
|
|
4670
|
-
} catch (error) {
|
|
4671
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
|
+
};
|
|
4672
4066
|
}
|
|
4673
4067
|
/**
|
|
4674
|
-
*
|
|
4068
|
+
* Build request headers with common defaults and dev mock controls
|
|
4675
4069
|
*/
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
);
|
|
4070
|
+
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
4071
|
+
const headers = {
|
|
4072
|
+
...additionalHeaders,
|
|
4073
|
+
"Content-Type": "application/json"
|
|
4074
|
+
};
|
|
4075
|
+
return headers;
|
|
4683
4076
|
}
|
|
4684
4077
|
/**
|
|
4685
|
-
*
|
|
4078
|
+
* Attach auth headers using the active client mode
|
|
4686
4079
|
*/
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
text: result.content,
|
|
4694
|
-
thinking: result.thinking,
|
|
4695
|
-
citations: result.citations,
|
|
4696
|
-
annotations: result.annotations
|
|
4697
|
-
});
|
|
4698
|
-
}
|
|
4699
|
-
for (const img of result.images) {
|
|
4700
|
-
content.push({
|
|
4701
|
-
type: "image_url",
|
|
4702
|
-
image_url: {
|
|
4703
|
-
url: img.image_url.url
|
|
4704
|
-
}
|
|
4705
|
-
});
|
|
4706
|
-
}
|
|
4707
|
-
return {
|
|
4708
|
-
role: "assistant",
|
|
4709
|
-
content
|
|
4710
|
-
};
|
|
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}`;
|
|
4711
4086
|
}
|
|
4712
|
-
return
|
|
4713
|
-
role: "assistant",
|
|
4714
|
-
content: result.content || ""
|
|
4715
|
-
};
|
|
4087
|
+
return nextHeaders;
|
|
4716
4088
|
}
|
|
4089
|
+
};
|
|
4090
|
+
|
|
4091
|
+
// client/StreamProcessor.ts
|
|
4092
|
+
var StreamProcessor = class {
|
|
4093
|
+
accumulatedContent = "";
|
|
4094
|
+
accumulatedThinking = "";
|
|
4095
|
+
accumulatedImages = [];
|
|
4096
|
+
isInThinking = false;
|
|
4097
|
+
isInContent = false;
|
|
4717
4098
|
/**
|
|
4718
|
-
*
|
|
4099
|
+
* Process a streaming response
|
|
4719
4100
|
*/
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
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
|
+
}
|
|
4726
4160
|
}
|
|
4161
|
+
} finally {
|
|
4162
|
+
reader.releaseLock();
|
|
4727
4163
|
}
|
|
4728
|
-
return
|
|
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
|
+
};
|
|
4729
4175
|
}
|
|
4730
4176
|
/**
|
|
4731
|
-
*
|
|
4177
|
+
* Parse a single SSE line
|
|
4732
4178
|
*/
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
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
|
+
}
|
|
4736
4230
|
}
|
|
4737
4231
|
/**
|
|
4738
|
-
* Handle
|
|
4232
|
+
* Handle content delta with thinking support
|
|
4739
4233
|
*/
|
|
4740
|
-
|
|
4741
|
-
this.
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
this.
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
);
|
|
4749
|
-
callbacks.onMessageAppend({
|
|
4750
|
-
role: "system",
|
|
4751
|
-
content: "Uncaught Error: " + modifiedErrorMsg + (this.alertLevel === "max" ? " | " + error.stack : "")
|
|
4752
|
-
});
|
|
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);
|
|
4753
4243
|
} else {
|
|
4754
|
-
|
|
4755
|
-
role: "system",
|
|
4756
|
-
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
4757
|
-
});
|
|
4244
|
+
this.accumulatedContent += content;
|
|
4758
4245
|
}
|
|
4246
|
+
callbacks.onContent(this.accumulatedContent);
|
|
4759
4247
|
}
|
|
4760
4248
|
/**
|
|
4761
|
-
*
|
|
4249
|
+
* Handle thinking/reasoning content
|
|
4762
4250
|
*/
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
throw new InsufficientBalanceError(1, 0);
|
|
4251
|
+
_handleThinking(reasoning, callbacks) {
|
|
4252
|
+
if (!this.isInThinking) {
|
|
4253
|
+
this.accumulatedThinking += "<thinking> ";
|
|
4254
|
+
this.isInThinking = true;
|
|
4768
4255
|
}
|
|
4256
|
+
this.accumulatedThinking += reasoning;
|
|
4257
|
+
callbacks.onThinking(this.accumulatedThinking);
|
|
4769
4258
|
}
|
|
4770
4259
|
/**
|
|
4771
|
-
*
|
|
4260
|
+
* Extract thinking blocks from content (for models with inline thinking)
|
|
4772
4261
|
*/
|
|
4773
|
-
|
|
4774
|
-
const
|
|
4775
|
-
|
|
4776
|
-
"
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
4781
|
-
if (!parentApiKey) {
|
|
4782
|
-
this._log(
|
|
4783
|
-
"DEBUG",
|
|
4784
|
-
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
4785
|
-
);
|
|
4786
|
-
const spendResult2 = await this.cashuSpender.spend({
|
|
4787
|
-
mintUrl,
|
|
4788
|
-
amount: amount * TOPUP_MARGIN,
|
|
4789
|
-
baseUrl: "",
|
|
4790
|
-
reuseToken: false
|
|
4791
|
-
});
|
|
4792
|
-
if (!spendResult2.token) {
|
|
4793
|
-
this._log(
|
|
4794
|
-
"ERROR",
|
|
4795
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
4796
|
-
spendResult2.error
|
|
4797
|
-
);
|
|
4798
|
-
throw new Error(
|
|
4799
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
4800
|
-
);
|
|
4801
|
-
} else {
|
|
4802
|
-
this._log(
|
|
4803
|
-
"DEBUG",
|
|
4804
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
4805
|
-
);
|
|
4806
|
-
}
|
|
4807
|
-
this._log(
|
|
4808
|
-
"DEBUG",
|
|
4809
|
-
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
4810
|
-
);
|
|
4811
|
-
try {
|
|
4812
|
-
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
4813
|
-
} catch (error) {
|
|
4814
|
-
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
4815
|
-
const receiveResult = await this.cashuSpender.receiveToken(
|
|
4816
|
-
spendResult2.token
|
|
4817
|
-
);
|
|
4818
|
-
if (receiveResult.success) {
|
|
4819
|
-
this._log(
|
|
4820
|
-
"DEBUG",
|
|
4821
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
4822
|
-
);
|
|
4823
|
-
} else {
|
|
4824
|
-
this._log(
|
|
4825
|
-
"DEBUG",
|
|
4826
|
-
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
4827
|
-
);
|
|
4828
|
-
}
|
|
4829
|
-
this._log(
|
|
4830
|
-
"DEBUG",
|
|
4831
|
-
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
4832
|
-
);
|
|
4833
|
-
} else {
|
|
4834
|
-
throw error;
|
|
4835
|
-
}
|
|
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> ";
|
|
4836
4269
|
}
|
|
4837
|
-
|
|
4270
|
+
} else if (part === "</thinking>") {
|
|
4271
|
+
this.isInThinking = false;
|
|
4272
|
+
this.accumulatedThinking += "</thinking>";
|
|
4273
|
+
} else if (this.isInThinking) {
|
|
4274
|
+
this.accumulatedThinking += part;
|
|
4838
4275
|
} else {
|
|
4839
|
-
this.
|
|
4840
|
-
"DEBUG",
|
|
4841
|
-
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
4842
|
-
);
|
|
4843
|
-
}
|
|
4844
|
-
let tokenBalance = 0;
|
|
4845
|
-
let tokenBalanceUnit = "sat";
|
|
4846
|
-
let tokenBalanceUnknown = false;
|
|
4847
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
4848
|
-
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
4849
|
-
(d) => d.baseUrl === baseUrl
|
|
4850
|
-
);
|
|
4851
|
-
if (distributionForBaseUrl) {
|
|
4852
|
-
tokenBalance = distributionForBaseUrl.amount;
|
|
4276
|
+
this.accumulatedContent += part;
|
|
4853
4277
|
}
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
/**
|
|
4281
|
+
* Merge images into accumulated array, avoiding duplicates
|
|
4282
|
+
*/
|
|
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;
|
|
4865
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;
|
|
4866
4300
|
}
|
|
4867
|
-
this._log(
|
|
4868
|
-
"DEBUG",
|
|
4869
|
-
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
4870
|
-
);
|
|
4871
|
-
return {
|
|
4872
|
-
token: parentApiKey?.key ?? "",
|
|
4873
|
-
tokenBalance,
|
|
4874
|
-
tokenBalanceUnit,
|
|
4875
|
-
tokenBalanceUnknown
|
|
4876
|
-
};
|
|
4877
4301
|
}
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4302
|
+
}
|
|
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,
|
|
4883
4343
|
mintUrl,
|
|
4884
|
-
|
|
4885
|
-
baseUrl: "",
|
|
4886
|
-
reuseToken: false
|
|
4344
|
+
modelId: selectedModel.id
|
|
4887
4345
|
});
|
|
4888
|
-
if (!
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
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") {
|
|
4362
|
+
callbacks.onMessageAppend({
|
|
4363
|
+
role: "assistant",
|
|
4364
|
+
content: "Your request was denied due to content filtering."
|
|
4365
|
+
});
|
|
4366
|
+
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
4367
|
+
const message = await createAssistantMessage(streamingResult);
|
|
4368
|
+
callbacks.onMessageAppend(message);
|
|
4894
4369
|
} else {
|
|
4895
|
-
|
|
4896
|
-
"
|
|
4897
|
-
|
|
4898
|
-
);
|
|
4899
|
-
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
4370
|
+
callbacks.onMessageAppend({
|
|
4371
|
+
role: "system",
|
|
4372
|
+
content: "The provider did not respond to this request."
|
|
4373
|
+
});
|
|
4900
4374
|
}
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4375
|
+
callbacks.onStreamingUpdate("");
|
|
4376
|
+
callbacks.onThinkingUpdate("");
|
|
4377
|
+
} catch (error) {
|
|
4378
|
+
handleError(error, callbacks, deps.alertLevel, deps.logger);
|
|
4379
|
+
} finally {
|
|
4380
|
+
callbacks.onPaymentProcessing?.(false);
|
|
4907
4381
|
}
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
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
|
+
});
|
|
4402
|
+
}
|
|
4403
|
+
for (const img of result.images) {
|
|
4404
|
+
content.push({
|
|
4405
|
+
type: "image_url",
|
|
4406
|
+
image_url: {
|
|
4407
|
+
url: img.image_url.url
|
|
4408
|
+
}
|
|
4409
|
+
});
|
|
4410
|
+
}
|
|
4411
|
+
return {
|
|
4412
|
+
role: "assistant",
|
|
4413
|
+
content
|
|
4915
4414
|
};
|
|
4916
|
-
return headers;
|
|
4917
4415
|
}
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
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
|
+
});
|
|
4929
4438
|
}
|
|
4930
|
-
}
|
|
4439
|
+
}
|
|
4931
4440
|
|
|
4932
4441
|
exports.ProviderManager = ProviderManager;
|
|
4933
4442
|
exports.RoutstrClient = RoutstrClient;
|
|
4934
4443
|
exports.StreamProcessor = StreamProcessor;
|
|
4935
4444
|
exports.createSSEParserTransform = createSSEParserTransform;
|
|
4445
|
+
exports.fetchAIResponse = fetchAIResponse;
|
|
4936
4446
|
exports.inspectSSEWebStream = inspectSSEWebStream;
|
|
4937
4447
|
//# sourceMappingURL=index.js.map
|
|
4938
4448
|
//# sourceMappingURL=index.js.map
|