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