@routstr/sdk 0.2.11 → 0.3.0
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/client/index.d.mts +30 -15
- package/dist/client/index.d.ts +30 -15
- package/dist/client/index.js +289 -141
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +290 -143
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -10
- package/dist/index.d.ts +3 -10
- package/dist/index.js +289 -163
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +290 -164
- package/dist/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +13 -0
- package/dist/wallet/index.d.ts +13 -0
- package/dist/wallet/index.js +71 -2
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +71 -2
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var cashuTs = require('@cashu/cashu-ts');
|
|
4
4
|
var vanilla = require('zustand/vanilla');
|
|
5
5
|
var stream = require('stream');
|
|
6
|
+
var string_decoder = require('string_decoder');
|
|
6
7
|
|
|
7
8
|
// core/errors.ts
|
|
8
9
|
var InsufficientBalanceError = class extends Error {
|
|
@@ -638,7 +639,7 @@ var CashuSpender = class {
|
|
|
638
639
|
};
|
|
639
640
|
|
|
640
641
|
// wallet/BalanceManager.ts
|
|
641
|
-
var BalanceManager = class {
|
|
642
|
+
var BalanceManager = class _BalanceManager {
|
|
642
643
|
constructor(walletAdapter, storageAdapter, providerRegistry, cashuSpender) {
|
|
643
644
|
this.walletAdapter = walletAdapter;
|
|
644
645
|
this.storageAdapter = storageAdapter;
|
|
@@ -655,6 +656,47 @@ var BalanceManager = class {
|
|
|
655
656
|
}
|
|
656
657
|
}
|
|
657
658
|
cashuSpender;
|
|
659
|
+
/** In-memory guard for per-provider wallet mutations (topup / refund) */
|
|
660
|
+
providerWalletOps = /* @__PURE__ */ new Map();
|
|
661
|
+
/** Cooldown (ms) between opposite operations on the same provider */
|
|
662
|
+
static PROVIDER_WALLET_COOLDOWN_MS = 1e4;
|
|
663
|
+
/**
|
|
664
|
+
* Check whether a wallet operation (topup/refund) may run for a provider.
|
|
665
|
+
* Returns the reason when blocked.
|
|
666
|
+
*/
|
|
667
|
+
_canRunProviderWalletOperation(baseUrl, type) {
|
|
668
|
+
const existing = this.providerWalletOps.get(baseUrl);
|
|
669
|
+
if (!existing) {
|
|
670
|
+
return { allowed: true };
|
|
671
|
+
}
|
|
672
|
+
if (existing.type === type) {
|
|
673
|
+
return { allowed: true };
|
|
674
|
+
}
|
|
675
|
+
if (!existing.endTime) {
|
|
676
|
+
return {
|
|
677
|
+
allowed: false,
|
|
678
|
+
reason: `Provider wallet operation locked; ${existing.type} in progress`
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
const elapsed = Date.now() - existing.endTime;
|
|
682
|
+
if (elapsed < _BalanceManager.PROVIDER_WALLET_COOLDOWN_MS) {
|
|
683
|
+
return {
|
|
684
|
+
allowed: false,
|
|
685
|
+
reason: `Provider wallet operation locked; recent ${existing.type} completed ${Math.round(elapsed / 1e3)}s ago`
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
this.providerWalletOps.delete(baseUrl);
|
|
689
|
+
return { allowed: true };
|
|
690
|
+
}
|
|
691
|
+
_beginProviderWalletOperation(baseUrl, type) {
|
|
692
|
+
this.providerWalletOps.set(baseUrl, { type, startTime: Date.now() });
|
|
693
|
+
}
|
|
694
|
+
_endProviderWalletOperation(baseUrl, type) {
|
|
695
|
+
const existing = this.providerWalletOps.get(baseUrl);
|
|
696
|
+
if (existing && existing.type === type) {
|
|
697
|
+
existing.endTime = Date.now();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
658
700
|
async getBalanceState() {
|
|
659
701
|
const mintBalances = await this.walletAdapter.getBalances();
|
|
660
702
|
const units = this.walletAdapter.getMintUnits();
|
|
@@ -689,6 +731,20 @@ var BalanceManager = class {
|
|
|
689
731
|
* @returns Refund result
|
|
690
732
|
*/
|
|
691
733
|
async refundApiKey(options) {
|
|
734
|
+
const { mintUrl, baseUrl, apiKey, forceRefund } = options;
|
|
735
|
+
const guard = this._canRunProviderWalletOperation(baseUrl, "refund");
|
|
736
|
+
if (!guard.allowed) {
|
|
737
|
+
console.log(`[BalanceManager] Skipping refund for ${baseUrl} - ${guard.reason}`);
|
|
738
|
+
return { success: false, message: guard.reason };
|
|
739
|
+
}
|
|
740
|
+
this._beginProviderWalletOperation(baseUrl, "refund");
|
|
741
|
+
try {
|
|
742
|
+
return await this._refundApiKeyImpl({ mintUrl, baseUrl, apiKey, forceRefund });
|
|
743
|
+
} finally {
|
|
744
|
+
this._endProviderWalletOperation(baseUrl, "refund");
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async _refundApiKeyImpl(options) {
|
|
692
748
|
const { mintUrl, baseUrl, apiKey, forceRefund } = options;
|
|
693
749
|
if (!apiKey) {
|
|
694
750
|
return { success: false, message: "No API key to refund" };
|
|
@@ -817,6 +873,20 @@ var BalanceManager = class {
|
|
|
817
873
|
* Top up API key balance with a cashu token
|
|
818
874
|
*/
|
|
819
875
|
async topUp(options) {
|
|
876
|
+
const { mintUrl, baseUrl, amount, token: providedToken } = options;
|
|
877
|
+
const guard = this._canRunProviderWalletOperation(baseUrl, "topup");
|
|
878
|
+
if (!guard.allowed) {
|
|
879
|
+
console.log(`[BalanceManager] Skipping topup for ${baseUrl} - ${guard.reason}`);
|
|
880
|
+
return { success: false, message: guard.reason };
|
|
881
|
+
}
|
|
882
|
+
this._beginProviderWalletOperation(baseUrl, "topup");
|
|
883
|
+
try {
|
|
884
|
+
return await this._topUpImpl({ mintUrl, baseUrl, amount, token: providedToken });
|
|
885
|
+
} finally {
|
|
886
|
+
this._endProviderWalletOperation(baseUrl, "topup");
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
async _topUpImpl(options) {
|
|
820
890
|
const { mintUrl, baseUrl, amount, token: providedToken } = options;
|
|
821
891
|
if (!amount || amount <= 0) {
|
|
822
892
|
return { success: false, message: "Invalid top up amount" };
|
|
@@ -977,7 +1047,7 @@ var BalanceManager = class {
|
|
|
977
1047
|
p2pkPubkey
|
|
978
1048
|
);
|
|
979
1049
|
console.log(
|
|
980
|
-
`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`
|
|
1050
|
+
`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}, all mint balances: ${JSON.stringify(Object.fromEntries(Object.entries(balances).map(([mint, balance]) => [mint, getBalanceInSats(balance, units[mint])])))}`
|
|
981
1051
|
);
|
|
982
1052
|
return {
|
|
983
1053
|
success: true,
|
|
@@ -1727,6 +1797,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
1727
1797
|
}
|
|
1728
1798
|
/**
|
|
1729
1799
|
* Clean up expired cooldown entries
|
|
1800
|
+
* Also removes the provider from failedProviders so it can be retried
|
|
1730
1801
|
*/
|
|
1731
1802
|
cleanupExpiredCooldowns() {
|
|
1732
1803
|
const now = Date.now();
|
|
@@ -1739,6 +1810,10 @@ var ProviderManager = class _ProviderManager {
|
|
|
1739
1810
|
console.log(
|
|
1740
1811
|
`[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
|
|
1741
1812
|
);
|
|
1813
|
+
this.failedProviders.delete(url);
|
|
1814
|
+
if (this.store) {
|
|
1815
|
+
this.store.getState().removeFailedProvider(url);
|
|
1816
|
+
}
|
|
1742
1817
|
}
|
|
1743
1818
|
return !isExpired;
|
|
1744
1819
|
}
|
|
@@ -1912,60 +1987,47 @@ var ProviderManager = class _ProviderManager {
|
|
|
1912
1987
|
const disabledProviders = new Set(
|
|
1913
1988
|
this.providerRegistry.getDisabledProviders()
|
|
1914
1989
|
);
|
|
1915
|
-
console.log(
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
console.log(
|
|
1919
|
-
|
|
1920
|
-
|
|
1990
|
+
console.log(
|
|
1991
|
+
`[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`
|
|
1992
|
+
);
|
|
1993
|
+
console.log(
|
|
1994
|
+
`[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`
|
|
1995
|
+
);
|
|
1996
|
+
console.log(
|
|
1997
|
+
`[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`
|
|
1998
|
+
);
|
|
1921
1999
|
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
1922
|
-
console.log(
|
|
2000
|
+
console.log(
|
|
2001
|
+
`[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`
|
|
2002
|
+
);
|
|
1923
2003
|
const candidates = [];
|
|
1924
|
-
let skippedCurrent = 0, skippedFailed = 0, skippedDisabled = 0, skippedCooldown = 0, skippedOnion = 0, skippedNoModel = 0;
|
|
1925
2004
|
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
1926
2005
|
if (baseUrl === currentBaseUrl) {
|
|
1927
|
-
console.log(
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
}
|
|
1931
|
-
if (this.failedProviders.has(baseUrl)) {
|
|
1932
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (failed): ${baseUrl}`);
|
|
1933
|
-
skippedFailed++;
|
|
2006
|
+
console.log(
|
|
2007
|
+
`[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`
|
|
2008
|
+
);
|
|
1934
2009
|
continue;
|
|
1935
2010
|
}
|
|
1936
2011
|
if (disabledProviders.has(baseUrl)) {
|
|
1937
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (disabled): ${baseUrl}`);
|
|
1938
|
-
skippedDisabled++;
|
|
1939
2012
|
continue;
|
|
1940
2013
|
}
|
|
1941
2014
|
if (this.isOnCooldown(baseUrl)) {
|
|
1942
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (cooldown): ${baseUrl}`);
|
|
1943
|
-
skippedCooldown++;
|
|
1944
2015
|
continue;
|
|
1945
2016
|
}
|
|
1946
2017
|
if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
|
|
1947
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (onion/http): ${baseUrl}`);
|
|
1948
|
-
skippedOnion++;
|
|
1949
2018
|
continue;
|
|
1950
2019
|
}
|
|
1951
2020
|
const model = models.find((m) => m.id === modelId);
|
|
1952
2021
|
if (!model) {
|
|
1953
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (no model ${modelId}): ${baseUrl} has models: ${models.map((m) => m.id).join(", ")}`);
|
|
1954
|
-
skippedNoModel++;
|
|
1955
2022
|
continue;
|
|
1956
2023
|
}
|
|
1957
2024
|
const cost = model.sats_pricing?.completion ?? 0;
|
|
1958
|
-
console.log(`[findNextBestProvider:${this.instanceId}] CANDIDATE: ${baseUrl} cost: ${cost}`);
|
|
1959
2025
|
candidates.push({ baseUrl, model, cost });
|
|
1960
2026
|
}
|
|
1961
|
-
console.log(`[findNextBestProvider:${this.instanceId}] Skipped: current=${skippedCurrent}, failed=${skippedFailed}, disabled=${skippedDisabled}, cooldown=${skippedCooldown}, onion=${skippedOnion}, noModel=${skippedNoModel}`);
|
|
1962
|
-
console.log(`[findNextBestProvider:${this.instanceId}] Total candidates: ${candidates.length}`);
|
|
1963
2027
|
candidates.sort((a, b) => a.cost - b.cost);
|
|
1964
2028
|
if (candidates.length > 0) {
|
|
1965
|
-
console.log(`[findNextBestProvider:${this.instanceId}] Selected provider: ${candidates[0].baseUrl} with cost: ${candidates[0].cost}`);
|
|
1966
2029
|
return candidates[0].baseUrl;
|
|
1967
2030
|
} else {
|
|
1968
|
-
console.log(`[findNextBestProvider:${this.instanceId}] No candidate providers found`);
|
|
1969
2031
|
return null;
|
|
1970
2032
|
}
|
|
1971
2033
|
} catch (error) {
|
|
@@ -2124,7 +2186,9 @@ var ProviderManager = class _ProviderManager {
|
|
|
2124
2186
|
const approximateTokens = apiMessagesNoImages ? Math.ceil(JSON.stringify(apiMessagesNoImages, null, 2).length / 2.84) : 1e4;
|
|
2125
2187
|
const totalInputTokens = approximateTokens + imageTokens;
|
|
2126
2188
|
const sp = model?.sats_pricing;
|
|
2127
|
-
if (!sp)
|
|
2189
|
+
if (!sp) {
|
|
2190
|
+
return 0;
|
|
2191
|
+
}
|
|
2128
2192
|
if (!sp.max_completion_cost) {
|
|
2129
2193
|
return sp.max_cost ?? 50;
|
|
2130
2194
|
}
|
|
@@ -3436,12 +3500,119 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
3436
3500
|
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
3437
3501
|
return defaultUsageTrackingDriver;
|
|
3438
3502
|
};
|
|
3503
|
+
function mergeUsage(previous, next) {
|
|
3504
|
+
if (!previous) return next;
|
|
3505
|
+
return {
|
|
3506
|
+
promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
|
|
3507
|
+
completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
|
|
3508
|
+
totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
|
|
3509
|
+
cost: next.cost > 0 ? next.cost : previous.cost,
|
|
3510
|
+
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
function hasUsageChanged(previous, next) {
|
|
3514
|
+
if (!previous) return true;
|
|
3515
|
+
return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
|
|
3516
|
+
}
|
|
3517
|
+
async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
3518
|
+
const reader = stream.getReader();
|
|
3519
|
+
const decoder = new TextDecoder("utf-8");
|
|
3520
|
+
let buffer = "";
|
|
3521
|
+
let capturedUsage = null;
|
|
3522
|
+
let capturedResponseId;
|
|
3523
|
+
let responseIdCaptured = false;
|
|
3524
|
+
const inspectDataPayload = (jsonText) => {
|
|
3525
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3526
|
+
return;
|
|
3527
|
+
}
|
|
3528
|
+
const trimmed = jsonText.trim();
|
|
3529
|
+
if (!trimmed || trimmed === "[DONE]") return;
|
|
3530
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
3531
|
+
try {
|
|
3532
|
+
const data = JSON.parse(trimmed);
|
|
3533
|
+
if (!responseIdCaptured) {
|
|
3534
|
+
const responseId = data?.id;
|
|
3535
|
+
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
3536
|
+
capturedResponseId = responseId.trim();
|
|
3537
|
+
onResponseId?.(capturedResponseId);
|
|
3538
|
+
responseIdCaptured = true;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
const usage = extractUsageFromSSEJson(data);
|
|
3542
|
+
if (usage) {
|
|
3543
|
+
const merged = mergeUsage(capturedUsage, usage);
|
|
3544
|
+
if (hasUsageChanged(capturedUsage, merged)) {
|
|
3545
|
+
capturedUsage = merged;
|
|
3546
|
+
onUsage(merged);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
} catch {
|
|
3550
|
+
}
|
|
3551
|
+
};
|
|
3552
|
+
const inspectEventBlock = (eventBlock) => {
|
|
3553
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
const lines = eventBlock.split(/\r?\n/);
|
|
3557
|
+
const dataParts = [];
|
|
3558
|
+
for (const line of lines) {
|
|
3559
|
+
if (!line || line.startsWith(":")) continue;
|
|
3560
|
+
if (line.startsWith("data:")) {
|
|
3561
|
+
const value = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
|
|
3562
|
+
dataParts.push(value);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
if (dataParts.length === 0) return;
|
|
3566
|
+
inspectDataPayload(dataParts.join("\n"));
|
|
3567
|
+
};
|
|
3568
|
+
const drainBufferedEvents = () => {
|
|
3569
|
+
const terminator = /\r?\n\r?\n/g;
|
|
3570
|
+
let lastIndex = 0;
|
|
3571
|
+
let match;
|
|
3572
|
+
while ((match = terminator.exec(buffer)) !== null) {
|
|
3573
|
+
const block = buffer.slice(lastIndex, match.index);
|
|
3574
|
+
lastIndex = match.index + match[0].length;
|
|
3575
|
+
if (block.length > 0) inspectEventBlock(block);
|
|
3576
|
+
}
|
|
3577
|
+
if (lastIndex > 0) buffer = buffer.slice(lastIndex);
|
|
3578
|
+
};
|
|
3579
|
+
try {
|
|
3580
|
+
while (true) {
|
|
3581
|
+
const { value, done } = await reader.read();
|
|
3582
|
+
if (done) break;
|
|
3583
|
+
if (value && value.byteLength > 0) {
|
|
3584
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3585
|
+
drainBufferedEvents();
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
buffer += decoder.decode();
|
|
3589
|
+
drainBufferedEvents();
|
|
3590
|
+
if (buffer.length > 0) {
|
|
3591
|
+
const tail = buffer.replace(/\r?\n+$/, "");
|
|
3592
|
+
if (tail.length > 0) inspectEventBlock(tail);
|
|
3593
|
+
buffer = "";
|
|
3594
|
+
}
|
|
3595
|
+
} catch {
|
|
3596
|
+
} finally {
|
|
3597
|
+
try {
|
|
3598
|
+
reader.releaseLock();
|
|
3599
|
+
} catch {
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
return {
|
|
3603
|
+
capturedUsage: capturedUsage ?? void 0,
|
|
3604
|
+
capturedResponseId
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3439
3607
|
function createSSEParserTransform(onUsage, onResponseId) {
|
|
3440
3608
|
let buffer = "";
|
|
3441
|
-
|
|
3609
|
+
const decoder = new string_decoder.StringDecoder("utf8");
|
|
3610
|
+
let capturedUsage = null;
|
|
3442
3611
|
let responseIdCaptured = false;
|
|
3443
3612
|
const inspectDataPayload = (jsonText) => {
|
|
3444
|
-
if (
|
|
3613
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3445
3616
|
const trimmed = jsonText.trim();
|
|
3446
3617
|
if (!trimmed || trimmed === "[DONE]") return;
|
|
3447
3618
|
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
@@ -3454,18 +3625,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3454
3625
|
responseIdCaptured = true;
|
|
3455
3626
|
}
|
|
3456
3627
|
}
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3628
|
+
const usage = extractUsageFromSSEJson(data);
|
|
3629
|
+
if (usage) {
|
|
3630
|
+
const mergedUsage = mergeUsage(capturedUsage, usage);
|
|
3631
|
+
if (hasUsageChanged(capturedUsage, mergedUsage)) {
|
|
3632
|
+
capturedUsage = mergedUsage;
|
|
3633
|
+
onUsage(mergedUsage);
|
|
3462
3634
|
}
|
|
3463
3635
|
}
|
|
3464
3636
|
} catch {
|
|
3465
3637
|
}
|
|
3466
3638
|
};
|
|
3467
3639
|
const inspectEventBlock = (eventBlock) => {
|
|
3468
|
-
if (
|
|
3640
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3641
|
+
return;
|
|
3642
|
+
}
|
|
3469
3643
|
const lines = eventBlock.split(/\r?\n/);
|
|
3470
3644
|
const dataParts = [];
|
|
3471
3645
|
for (const line of lines) {
|
|
@@ -3479,32 +3653,35 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3479
3653
|
const payload = dataParts.join("\n");
|
|
3480
3654
|
inspectDataPayload(payload);
|
|
3481
3655
|
};
|
|
3482
|
-
const
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3656
|
+
const processBufferedEvents = () => {
|
|
3657
|
+
const terminator = /\r?\n\r?\n/g;
|
|
3658
|
+
let lastIndex = 0;
|
|
3659
|
+
let match;
|
|
3660
|
+
while ((match = terminator.exec(buffer)) !== null) {
|
|
3661
|
+
const block = buffer.slice(lastIndex, match.index);
|
|
3662
|
+
lastIndex = match.index + match[0].length;
|
|
3663
|
+
if (block.length > 0) {
|
|
3664
|
+
inspectEventBlock(block);
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
if (lastIndex > 0) {
|
|
3668
|
+
buffer = buffer.slice(lastIndex);
|
|
3669
|
+
}
|
|
3486
3670
|
};
|
|
3487
3671
|
return new stream.Transform({
|
|
3488
3672
|
transform(chunk, _encoding, callback) {
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
let match;
|
|
3493
|
-
while ((match = terminator.exec(buffer)) !== null) {
|
|
3494
|
-
const block = buffer.slice(lastIndex, match.index);
|
|
3495
|
-
lastIndex = match.index + match[0].length;
|
|
3496
|
-
emitEventBlock(this, block);
|
|
3497
|
-
}
|
|
3498
|
-
if (lastIndex > 0) {
|
|
3499
|
-
buffer = buffer.slice(lastIndex);
|
|
3500
|
-
}
|
|
3673
|
+
this.push(chunk);
|
|
3674
|
+
buffer += decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
3675
|
+
processBufferedEvents();
|
|
3501
3676
|
callback();
|
|
3502
3677
|
},
|
|
3503
3678
|
flush(callback) {
|
|
3679
|
+
buffer += decoder.end();
|
|
3680
|
+
processBufferedEvents();
|
|
3504
3681
|
if (buffer.length > 0) {
|
|
3505
3682
|
const tail = buffer.replace(/\r?\n+$/, "");
|
|
3506
3683
|
if (tail.length > 0) {
|
|
3507
|
-
|
|
3684
|
+
inspectEventBlock(tail);
|
|
3508
3685
|
}
|
|
3509
3686
|
buffer = "";
|
|
3510
3687
|
}
|
|
@@ -3512,6 +3689,8 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3512
3689
|
}
|
|
3513
3690
|
});
|
|
3514
3691
|
}
|
|
3692
|
+
|
|
3693
|
+
// client/RoutstrClient.ts
|
|
3515
3694
|
var TOPUP_MARGIN = 1.2;
|
|
3516
3695
|
var RoutstrClient = class {
|
|
3517
3696
|
constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
|
|
@@ -3611,31 +3790,12 @@ var RoutstrClient = class {
|
|
|
3611
3790
|
*/
|
|
3612
3791
|
async routeRequest(params) {
|
|
3613
3792
|
const prepared = await this._prepareRoutedRequest(params);
|
|
3614
|
-
const
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
modelId: prepared.modelId,
|
|
3621
|
-
usage: prepared.capturedUsage,
|
|
3622
|
-
requestId: prepared.capturedResponseId,
|
|
3623
|
-
clientApiKey: prepared.clientApiKey
|
|
3624
|
-
});
|
|
3625
|
-
prepared.response.satsSpent = satsSpent;
|
|
3626
|
-
prepared.response.usage = prepared.capturedUsage;
|
|
3627
|
-
prepared.response.requestId = prepared.capturedResponseId;
|
|
3628
|
-
return prepared.response;
|
|
3629
|
-
}
|
|
3630
|
-
async routeRequestToNodeResponse(params) {
|
|
3631
|
-
const { res } = params;
|
|
3632
|
-
const prepared = await this._prepareRoutedRequest(params);
|
|
3633
|
-
res.statusCode = prepared.response.status;
|
|
3634
|
-
prepared.response.headers.forEach((value, key) => {
|
|
3635
|
-
res.setHeader(key, value);
|
|
3636
|
-
});
|
|
3637
|
-
const body = prepared.response.body;
|
|
3638
|
-
if (!body) {
|
|
3793
|
+
const contentType = prepared.response.headers.get("content-type") || "";
|
|
3794
|
+
const isSSE = contentType.includes("text/event-stream");
|
|
3795
|
+
const runFinalize = async () => {
|
|
3796
|
+
const { capturedUsage, capturedResponseId } = await prepared.usagePromise;
|
|
3797
|
+
const usage = capturedUsage ?? prepared.capturedUsage;
|
|
3798
|
+
const requestId = capturedResponseId ?? prepared.capturedResponseId;
|
|
3639
3799
|
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3640
3800
|
token: prepared.tokenUsed,
|
|
3641
3801
|
baseUrl: prepared.baseUrlUsed,
|
|
@@ -3643,55 +3803,29 @@ var RoutstrClient = class {
|
|
|
3643
3803
|
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3644
3804
|
response: prepared.response,
|
|
3645
3805
|
modelId: prepared.modelId,
|
|
3646
|
-
usage
|
|
3647
|
-
requestId
|
|
3806
|
+
usage,
|
|
3807
|
+
requestId,
|
|
3648
3808
|
clientApiKey: prepared.clientApiKey
|
|
3649
3809
|
});
|
|
3650
3810
|
prepared.response.satsSpent = satsSpent;
|
|
3651
|
-
|
|
3652
|
-
|
|
3811
|
+
prepared.response.usage = usage;
|
|
3812
|
+
prepared.response.requestId = requestId;
|
|
3813
|
+
return satsSpent;
|
|
3814
|
+
};
|
|
3815
|
+
if (isSSE) {
|
|
3816
|
+
const finalizePromise = runFinalize().catch((error) => {
|
|
3817
|
+
this._log("ERROR", "[RoutstrClient] SSE finalize failed:", error);
|
|
3818
|
+
return 0;
|
|
3819
|
+
});
|
|
3820
|
+
prepared.response.finalize = () => finalizePromise;
|
|
3821
|
+
return prepared.response;
|
|
3653
3822
|
}
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
let settled = false;
|
|
3657
|
-
const finish = async () => {
|
|
3658
|
-
if (settled) return;
|
|
3659
|
-
settled = true;
|
|
3660
|
-
try {
|
|
3661
|
-
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3662
|
-
token: prepared.tokenUsed,
|
|
3663
|
-
baseUrl: prepared.baseUrlUsed,
|
|
3664
|
-
mintUrl: params.mintUrl,
|
|
3665
|
-
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3666
|
-
response: prepared.response,
|
|
3667
|
-
modelId: prepared.modelId,
|
|
3668
|
-
usage: prepared.capturedUsage,
|
|
3669
|
-
requestId: prepared.capturedResponseId,
|
|
3670
|
-
clientApiKey: prepared.clientApiKey
|
|
3671
|
-
});
|
|
3672
|
-
prepared.response.satsSpent = satsSpent;
|
|
3673
|
-
prepared.response.usage = prepared.capturedUsage;
|
|
3674
|
-
prepared.response.requestId = prepared.capturedResponseId;
|
|
3675
|
-
resolve();
|
|
3676
|
-
} catch (error) {
|
|
3677
|
-
reject(error);
|
|
3678
|
-
}
|
|
3679
|
-
};
|
|
3680
|
-
const fail = (error) => {
|
|
3681
|
-
if (settled) return;
|
|
3682
|
-
settled = true;
|
|
3683
|
-
reject(error);
|
|
3684
|
-
};
|
|
3685
|
-
res.once("finish", finish);
|
|
3686
|
-
res.once("close", finish);
|
|
3687
|
-
res.once("error", fail);
|
|
3688
|
-
nodeReadable.once("error", fail);
|
|
3689
|
-
nodeReadable.pipe(res);
|
|
3690
|
-
});
|
|
3823
|
+
await runFinalize();
|
|
3824
|
+
return prepared.response;
|
|
3691
3825
|
}
|
|
3692
3826
|
async _prepareRoutedRequest(params) {
|
|
3693
3827
|
const {
|
|
3694
|
-
path,
|
|
3828
|
+
path: requestPath,
|
|
3695
3829
|
method,
|
|
3696
3830
|
body,
|
|
3697
3831
|
headers = {},
|
|
@@ -3711,9 +3845,23 @@ var RoutstrClient = class {
|
|
|
3711
3845
|
);
|
|
3712
3846
|
selectedModel = providerModel ?? void 0;
|
|
3713
3847
|
if (selectedModel) {
|
|
3848
|
+
const requestMessages = Array.isArray(
|
|
3849
|
+
body?.messages
|
|
3850
|
+
) ? body.messages : [];
|
|
3851
|
+
const requestMaxTokens = typeof body?.max_tokens === "number" ? body.max_tokens : void 0;
|
|
3852
|
+
this._log(
|
|
3853
|
+
"DEBUG",
|
|
3854
|
+
"[RoutstrClient] generic request pricing input",
|
|
3855
|
+
{
|
|
3856
|
+
modelId: selectedModel.id,
|
|
3857
|
+
messageCount: requestMessages.length,
|
|
3858
|
+
maxTokens: requestMaxTokens
|
|
3859
|
+
}
|
|
3860
|
+
);
|
|
3714
3861
|
requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
3715
3862
|
selectedModel,
|
|
3716
|
-
|
|
3863
|
+
requestMessages,
|
|
3864
|
+
requestMaxTokens
|
|
3717
3865
|
);
|
|
3718
3866
|
}
|
|
3719
3867
|
}
|
|
@@ -3732,7 +3880,7 @@ var RoutstrClient = class {
|
|
|
3732
3880
|
const baseHeaders = this._buildBaseHeaders();
|
|
3733
3881
|
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
3734
3882
|
const response = await this._makeRequest({
|
|
3735
|
-
path,
|
|
3883
|
+
path: requestPath,
|
|
3736
3884
|
method,
|
|
3737
3885
|
body: method === "GET" ? void 0 : requestBody,
|
|
3738
3886
|
baseUrl,
|
|
@@ -3750,9 +3898,18 @@ var RoutstrClient = class {
|
|
|
3750
3898
|
let processedResponse = response;
|
|
3751
3899
|
let capturedUsage;
|
|
3752
3900
|
let capturedResponseId;
|
|
3901
|
+
let usagePromise = Promise.resolve({});
|
|
3753
3902
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
3754
|
-
const
|
|
3755
|
-
|
|
3903
|
+
const [clientStream, inspectStream] = response.body.tee();
|
|
3904
|
+
processedResponse = new Response(clientStream, {
|
|
3905
|
+
status: response.status,
|
|
3906
|
+
statusText: response.statusText,
|
|
3907
|
+
headers: response.headers
|
|
3908
|
+
});
|
|
3909
|
+
processedResponse.baseUrl = response.baseUrl;
|
|
3910
|
+
processedResponse.token = response.token;
|
|
3911
|
+
usagePromise = inspectSSEWebStream(
|
|
3912
|
+
inspectStream,
|
|
3756
3913
|
(usage) => {
|
|
3757
3914
|
capturedUsage = usage;
|
|
3758
3915
|
processedResponse.usage = usage;
|
|
@@ -3762,17 +3919,7 @@ var RoutstrClient = class {
|
|
|
3762
3919
|
processedResponse.requestId = responseId;
|
|
3763
3920
|
}
|
|
3764
3921
|
);
|
|
3765
|
-
|
|
3766
|
-
const webStream = stream.Readable.toWeb(
|
|
3767
|
-
transformed
|
|
3768
|
-
);
|
|
3769
|
-
processedResponse = new Response(webStream, {
|
|
3770
|
-
status: response.status,
|
|
3771
|
-
statusText: response.statusText,
|
|
3772
|
-
headers: response.headers
|
|
3773
|
-
});
|
|
3774
|
-
processedResponse.baseUrl = response.baseUrl;
|
|
3775
|
-
processedResponse.token = response.token;
|
|
3922
|
+
processedResponse.usagePromise = usagePromise;
|
|
3776
3923
|
}
|
|
3777
3924
|
return {
|
|
3778
3925
|
response: processedResponse,
|
|
@@ -3782,7 +3929,8 @@ var RoutstrClient = class {
|
|
|
3782
3929
|
modelId,
|
|
3783
3930
|
capturedUsage,
|
|
3784
3931
|
capturedResponseId,
|
|
3785
|
-
clientApiKey
|
|
3932
|
+
clientApiKey,
|
|
3933
|
+
usagePromise
|
|
3786
3934
|
};
|
|
3787
3935
|
}
|
|
3788
3936
|
/**
|
|
@@ -3831,7 +3979,6 @@ var RoutstrClient = class {
|
|
|
3831
3979
|
callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
|
|
3832
3980
|
const baseHeaders = this._buildBaseHeaders(headers);
|
|
3833
3981
|
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
3834
|
-
this.providerManager.resetFailedProviders();
|
|
3835
3982
|
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
3836
3983
|
const providerVersion = providerInfo?.version ?? "";
|
|
3837
3984
|
let modelIdForRequest = selectedModel.id;
|
|
@@ -4059,7 +4206,7 @@ var RoutstrClient = class {
|
|
|
4059
4206
|
);
|
|
4060
4207
|
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
4061
4208
|
const shortfall = Math.max(0, params.requiredSats - currentBalance);
|
|
4062
|
-
topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
|
|
4209
|
+
topupAmount = shortfall > 0.21 * params.requiredSats ? shortfall : 0.21 * params.requiredSats;
|
|
4063
4210
|
this._log(
|
|
4064
4211
|
"DEBUG",
|
|
4065
4212
|
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
|
|
@@ -4695,5 +4842,6 @@ exports.ProviderManager = ProviderManager;
|
|
|
4695
4842
|
exports.RoutstrClient = RoutstrClient;
|
|
4696
4843
|
exports.StreamProcessor = StreamProcessor;
|
|
4697
4844
|
exports.createSSEParserTransform = createSSEParserTransform;
|
|
4845
|
+
exports.inspectSSEWebStream = inspectSSEWebStream;
|
|
4698
4846
|
//# sourceMappingURL=index.js.map
|
|
4699
4847
|
//# sourceMappingURL=index.js.map
|