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