@routstr/sdk 0.2.12 → 0.3.1
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 +29 -15
- package/dist/client/index.d.ts +29 -15
- package/dist/client/index.js +267 -166
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +268 -146
- package/dist/client/index.mjs.map +1 -1
- package/dist/discovery/index.js +2 -0
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +2 -0
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -10
- package/dist/index.d.ts +3 -10
- package/dist/index.js +273 -192
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +274 -171
- 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,31 +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
|
|
7
|
-
var path = require('path');
|
|
8
|
-
var os = require('os');
|
|
9
|
-
|
|
10
|
-
function _interopNamespace(e) {
|
|
11
|
-
if (e && e.__esModule) return e;
|
|
12
|
-
var n = Object.create(null);
|
|
13
|
-
if (e) {
|
|
14
|
-
Object.keys(e).forEach(function (k) {
|
|
15
|
-
if (k !== 'default') {
|
|
16
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
17
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
18
|
-
enumerable: true,
|
|
19
|
-
get: function () { return e[k]; }
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
n.default = e;
|
|
25
|
-
return Object.freeze(n);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
29
|
-
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
30
|
-
var os__namespace = /*#__PURE__*/_interopNamespace(os);
|
|
6
|
+
var string_decoder = require('string_decoder');
|
|
31
7
|
|
|
32
8
|
// core/errors.ts
|
|
33
9
|
var InsufficientBalanceError = class extends Error {
|
|
@@ -81,10 +57,10 @@ var AuditLogger = class _AuditLogger {
|
|
|
81
57
|
const logLine = JSON.stringify(fullEntry) + "\n";
|
|
82
58
|
if (typeof window === "undefined") {
|
|
83
59
|
try {
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const logPath =
|
|
87
|
-
|
|
60
|
+
const fs = await import('fs');
|
|
61
|
+
const path = await import('path');
|
|
62
|
+
const logPath = path.join(process.cwd(), "audit.log");
|
|
63
|
+
fs.appendFileSync(logPath, logLine);
|
|
88
64
|
} catch (error) {
|
|
89
65
|
console.error("[AuditLogger] Failed to write to file:", error);
|
|
90
66
|
}
|
|
@@ -663,7 +639,7 @@ var CashuSpender = class {
|
|
|
663
639
|
};
|
|
664
640
|
|
|
665
641
|
// wallet/BalanceManager.ts
|
|
666
|
-
var BalanceManager = class {
|
|
642
|
+
var BalanceManager = class _BalanceManager {
|
|
667
643
|
constructor(walletAdapter, storageAdapter, providerRegistry, cashuSpender) {
|
|
668
644
|
this.walletAdapter = walletAdapter;
|
|
669
645
|
this.storageAdapter = storageAdapter;
|
|
@@ -680,6 +656,47 @@ var BalanceManager = class {
|
|
|
680
656
|
}
|
|
681
657
|
}
|
|
682
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
|
+
}
|
|
683
700
|
async getBalanceState() {
|
|
684
701
|
const mintBalances = await this.walletAdapter.getBalances();
|
|
685
702
|
const units = this.walletAdapter.getMintUnits();
|
|
@@ -714,6 +731,20 @@ var BalanceManager = class {
|
|
|
714
731
|
* @returns Refund result
|
|
715
732
|
*/
|
|
716
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) {
|
|
717
748
|
const { mintUrl, baseUrl, apiKey, forceRefund } = options;
|
|
718
749
|
if (!apiKey) {
|
|
719
750
|
return { success: false, message: "No API key to refund" };
|
|
@@ -842,6 +873,20 @@ var BalanceManager = class {
|
|
|
842
873
|
* Top up API key balance with a cashu token
|
|
843
874
|
*/
|
|
844
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) {
|
|
845
890
|
const { mintUrl, baseUrl, amount, token: providedToken } = options;
|
|
846
891
|
if (!amount || amount <= 0) {
|
|
847
892
|
return { success: false, message: "Invalid top up amount" };
|
|
@@ -1002,7 +1047,7 @@ var BalanceManager = class {
|
|
|
1002
1047
|
p2pkPubkey
|
|
1003
1048
|
);
|
|
1004
1049
|
console.log(
|
|
1005
|
-
`[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])])))}`
|
|
1006
1051
|
);
|
|
1007
1052
|
return {
|
|
1008
1053
|
success: true,
|
|
@@ -2141,7 +2186,9 @@ var ProviderManager = class _ProviderManager {
|
|
|
2141
2186
|
const approximateTokens = apiMessagesNoImages ? Math.ceil(JSON.stringify(apiMessagesNoImages, null, 2).length / 2.84) : 1e4;
|
|
2142
2187
|
const totalInputTokens = approximateTokens + imageTokens;
|
|
2143
2188
|
const sp = model?.sats_pricing;
|
|
2144
|
-
if (!sp)
|
|
2189
|
+
if (!sp) {
|
|
2190
|
+
return 0;
|
|
2191
|
+
}
|
|
2145
2192
|
if (!sp.max_completion_cost) {
|
|
2146
2193
|
return sp.max_cost ?? 50;
|
|
2147
2194
|
}
|
|
@@ -3453,26 +3500,117 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
3453
3500
|
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
3454
3501
|
return defaultUsageTrackingDriver;
|
|
3455
3502
|
};
|
|
3456
|
-
function
|
|
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");
|
|
3457
3520
|
let buffer = "";
|
|
3458
3521
|
let capturedUsage = null;
|
|
3522
|
+
let capturedResponseId;
|
|
3459
3523
|
let responseIdCaptured = false;
|
|
3460
|
-
const
|
|
3461
|
-
if (
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
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"));
|
|
3469
3567
|
};
|
|
3470
|
-
const
|
|
3471
|
-
|
|
3472
|
-
|
|
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
|
|
3473
3605
|
};
|
|
3606
|
+
}
|
|
3607
|
+
function createSSEParserTransform(onUsage, onResponseId) {
|
|
3608
|
+
let buffer = "";
|
|
3609
|
+
const decoder = new string_decoder.StringDecoder("utf8");
|
|
3610
|
+
let capturedUsage = null;
|
|
3611
|
+
let responseIdCaptured = false;
|
|
3474
3612
|
const inspectDataPayload = (jsonText) => {
|
|
3475
|
-
if (responseIdCaptured && capturedUsage
|
|
3613
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3476
3614
|
return;
|
|
3477
3615
|
}
|
|
3478
3616
|
const trimmed = jsonText.trim();
|
|
@@ -3499,7 +3637,7 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3499
3637
|
}
|
|
3500
3638
|
};
|
|
3501
3639
|
const inspectEventBlock = (eventBlock) => {
|
|
3502
|
-
if (responseIdCaptured && capturedUsage
|
|
3640
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3503
3641
|
return;
|
|
3504
3642
|
}
|
|
3505
3643
|
const lines = eventBlock.split(/\r?\n/);
|
|
@@ -3515,32 +3653,35 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3515
3653
|
const payload = dataParts.join("\n");
|
|
3516
3654
|
inspectDataPayload(payload);
|
|
3517
3655
|
};
|
|
3518
|
-
const
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
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
|
+
}
|
|
3522
3670
|
};
|
|
3523
3671
|
return new stream.Transform({
|
|
3524
3672
|
transform(chunk, _encoding, callback) {
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
let match;
|
|
3529
|
-
while ((match = terminator.exec(buffer)) !== null) {
|
|
3530
|
-
const block = buffer.slice(lastIndex, match.index);
|
|
3531
|
-
lastIndex = match.index + match[0].length;
|
|
3532
|
-
emitEventBlock(this, block);
|
|
3533
|
-
}
|
|
3534
|
-
if (lastIndex > 0) {
|
|
3535
|
-
buffer = buffer.slice(lastIndex);
|
|
3536
|
-
}
|
|
3673
|
+
this.push(chunk);
|
|
3674
|
+
buffer += decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
3675
|
+
processBufferedEvents();
|
|
3537
3676
|
callback();
|
|
3538
3677
|
},
|
|
3539
3678
|
flush(callback) {
|
|
3679
|
+
buffer += decoder.end();
|
|
3680
|
+
processBufferedEvents();
|
|
3540
3681
|
if (buffer.length > 0) {
|
|
3541
3682
|
const tail = buffer.replace(/\r?\n+$/, "");
|
|
3542
3683
|
if (tail.length > 0) {
|
|
3543
|
-
|
|
3684
|
+
inspectEventBlock(tail);
|
|
3544
3685
|
}
|
|
3545
3686
|
buffer = "";
|
|
3546
3687
|
}
|
|
@@ -3548,6 +3689,8 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3548
3689
|
}
|
|
3549
3690
|
});
|
|
3550
3691
|
}
|
|
3692
|
+
|
|
3693
|
+
// client/RoutstrClient.ts
|
|
3551
3694
|
var TOPUP_MARGIN = 1.2;
|
|
3552
3695
|
var RoutstrClient = class {
|
|
3553
3696
|
constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
|
|
@@ -3647,31 +3790,12 @@ var RoutstrClient = class {
|
|
|
3647
3790
|
*/
|
|
3648
3791
|
async routeRequest(params) {
|
|
3649
3792
|
const prepared = await this._prepareRoutedRequest(params);
|
|
3650
|
-
const
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
modelId: prepared.modelId,
|
|
3657
|
-
usage: prepared.capturedUsage,
|
|
3658
|
-
requestId: prepared.capturedResponseId,
|
|
3659
|
-
clientApiKey: prepared.clientApiKey
|
|
3660
|
-
});
|
|
3661
|
-
prepared.response.satsSpent = satsSpent;
|
|
3662
|
-
prepared.response.usage = prepared.capturedUsage;
|
|
3663
|
-
prepared.response.requestId = prepared.capturedResponseId;
|
|
3664
|
-
return prepared.response;
|
|
3665
|
-
}
|
|
3666
|
-
async routeRequestToNodeResponse(params) {
|
|
3667
|
-
const { res } = params;
|
|
3668
|
-
const prepared = await this._prepareRoutedRequest(params);
|
|
3669
|
-
res.statusCode = prepared.response.status;
|
|
3670
|
-
prepared.response.headers.forEach((value, key) => {
|
|
3671
|
-
res.setHeader(key, value);
|
|
3672
|
-
});
|
|
3673
|
-
const body = prepared.response.body;
|
|
3674
|
-
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;
|
|
3675
3799
|
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3676
3800
|
token: prepared.tokenUsed,
|
|
3677
3801
|
baseUrl: prepared.baseUrlUsed,
|
|
@@ -3679,51 +3803,25 @@ var RoutstrClient = class {
|
|
|
3679
3803
|
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3680
3804
|
response: prepared.response,
|
|
3681
3805
|
modelId: prepared.modelId,
|
|
3682
|
-
usage
|
|
3683
|
-
requestId
|
|
3806
|
+
usage,
|
|
3807
|
+
requestId,
|
|
3684
3808
|
clientApiKey: prepared.clientApiKey
|
|
3685
3809
|
});
|
|
3686
3810
|
prepared.response.satsSpent = satsSpent;
|
|
3687
|
-
|
|
3688
|
-
|
|
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;
|
|
3689
3822
|
}
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
let settled = false;
|
|
3693
|
-
const finish = async () => {
|
|
3694
|
-
if (settled) return;
|
|
3695
|
-
settled = true;
|
|
3696
|
-
try {
|
|
3697
|
-
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3698
|
-
token: prepared.tokenUsed,
|
|
3699
|
-
baseUrl: prepared.baseUrlUsed,
|
|
3700
|
-
mintUrl: params.mintUrl,
|
|
3701
|
-
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3702
|
-
response: prepared.response,
|
|
3703
|
-
modelId: prepared.modelId,
|
|
3704
|
-
usage: prepared.capturedUsage,
|
|
3705
|
-
requestId: prepared.capturedResponseId,
|
|
3706
|
-
clientApiKey: prepared.clientApiKey
|
|
3707
|
-
});
|
|
3708
|
-
prepared.response.satsSpent = satsSpent;
|
|
3709
|
-
prepared.response.usage = prepared.capturedUsage;
|
|
3710
|
-
prepared.response.requestId = prepared.capturedResponseId;
|
|
3711
|
-
resolve();
|
|
3712
|
-
} catch (error) {
|
|
3713
|
-
reject(error);
|
|
3714
|
-
}
|
|
3715
|
-
};
|
|
3716
|
-
const fail = (error) => {
|
|
3717
|
-
if (settled) return;
|
|
3718
|
-
settled = true;
|
|
3719
|
-
reject(error);
|
|
3720
|
-
};
|
|
3721
|
-
res.once("finish", finish);
|
|
3722
|
-
res.once("close", finish);
|
|
3723
|
-
res.once("error", fail);
|
|
3724
|
-
nodeReadable.once("error", fail);
|
|
3725
|
-
nodeReadable.pipe(res);
|
|
3726
|
-
});
|
|
3823
|
+
await runFinalize();
|
|
3824
|
+
return prepared.response;
|
|
3727
3825
|
}
|
|
3728
3826
|
async _prepareRoutedRequest(params) {
|
|
3729
3827
|
const {
|
|
@@ -3747,9 +3845,23 @@ var RoutstrClient = class {
|
|
|
3747
3845
|
);
|
|
3748
3846
|
selectedModel = providerModel ?? void 0;
|
|
3749
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
|
+
);
|
|
3750
3861
|
requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
3751
3862
|
selectedModel,
|
|
3752
|
-
|
|
3863
|
+
requestMessages,
|
|
3864
|
+
requestMaxTokens
|
|
3753
3865
|
);
|
|
3754
3866
|
}
|
|
3755
3867
|
}
|
|
@@ -3786,22 +3898,18 @@ var RoutstrClient = class {
|
|
|
3786
3898
|
let processedResponse = response;
|
|
3787
3899
|
let capturedUsage;
|
|
3788
3900
|
let capturedResponseId;
|
|
3901
|
+
let usagePromise = Promise.resolve({});
|
|
3789
3902
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
3790
|
-
const
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
const logStream = fs__namespace.createWriteStream(logFile);
|
|
3796
|
-
const nodeReadable = stream.Readable.fromWeb(response.body);
|
|
3797
|
-
const loggingTransform = new stream.Transform({
|
|
3798
|
-
transform(chunk, encoding, callback) {
|
|
3799
|
-
const raw = chunk.toString();
|
|
3800
|
-
logStream.write(JSON.stringify({ raw, timestamp: Date.now() }) + "\n");
|
|
3801
|
-
callback(null, chunk);
|
|
3802
|
-
}
|
|
3903
|
+
const [clientStream, inspectStream] = response.body.tee();
|
|
3904
|
+
processedResponse = new Response(clientStream, {
|
|
3905
|
+
status: response.status,
|
|
3906
|
+
statusText: response.statusText,
|
|
3907
|
+
headers: response.headers
|
|
3803
3908
|
});
|
|
3804
|
-
|
|
3909
|
+
processedResponse.baseUrl = response.baseUrl;
|
|
3910
|
+
processedResponse.token = response.token;
|
|
3911
|
+
usagePromise = inspectSSEWebStream(
|
|
3912
|
+
inspectStream,
|
|
3805
3913
|
(usage) => {
|
|
3806
3914
|
capturedUsage = usage;
|
|
3807
3915
|
processedResponse.usage = usage;
|
|
@@ -3811,17 +3919,7 @@ var RoutstrClient = class {
|
|
|
3811
3919
|
processedResponse.requestId = responseId;
|
|
3812
3920
|
}
|
|
3813
3921
|
);
|
|
3814
|
-
|
|
3815
|
-
const webStream = stream.Readable.toWeb(
|
|
3816
|
-
transformed
|
|
3817
|
-
);
|
|
3818
|
-
processedResponse = new Response(webStream, {
|
|
3819
|
-
status: response.status,
|
|
3820
|
-
statusText: response.statusText,
|
|
3821
|
-
headers: response.headers
|
|
3822
|
-
});
|
|
3823
|
-
processedResponse.baseUrl = response.baseUrl;
|
|
3824
|
-
processedResponse.token = response.token;
|
|
3922
|
+
processedResponse.usagePromise = usagePromise;
|
|
3825
3923
|
}
|
|
3826
3924
|
return {
|
|
3827
3925
|
response: processedResponse,
|
|
@@ -3831,7 +3929,8 @@ var RoutstrClient = class {
|
|
|
3831
3929
|
modelId,
|
|
3832
3930
|
capturedUsage,
|
|
3833
3931
|
capturedResponseId,
|
|
3834
|
-
clientApiKey
|
|
3932
|
+
clientApiKey,
|
|
3933
|
+
usagePromise
|
|
3835
3934
|
};
|
|
3836
3935
|
}
|
|
3837
3936
|
/**
|
|
@@ -3982,9 +4081,9 @@ var RoutstrClient = class {
|
|
|
3982
4081
|
* Make the API request with failover support
|
|
3983
4082
|
*/
|
|
3984
4083
|
async _makeRequest(params) {
|
|
3985
|
-
const { path
|
|
4084
|
+
const { path, method, body, baseUrl, token, headers } = params;
|
|
3986
4085
|
try {
|
|
3987
|
-
const url = `${baseUrl.replace(/\/$/, "")}${
|
|
4086
|
+
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
3988
4087
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
3989
4088
|
const response = await fetch(url, {
|
|
3990
4089
|
method,
|
|
@@ -4034,7 +4133,7 @@ var RoutstrClient = class {
|
|
|
4034
4133
|
*/
|
|
4035
4134
|
async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
|
|
4036
4135
|
const MAX_RETRIES_PER_PROVIDER = 2;
|
|
4037
|
-
const { path
|
|
4136
|
+
const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
|
|
4038
4137
|
let tryNextProvider = false;
|
|
4039
4138
|
const errorMessage = responseBody;
|
|
4040
4139
|
this._log(
|
|
@@ -4106,11 +4205,12 @@ var RoutstrClient = class {
|
|
|
4106
4205
|
baseUrl
|
|
4107
4206
|
);
|
|
4108
4207
|
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
4109
|
-
const
|
|
4110
|
-
|
|
4208
|
+
const reservedBalance = currentBalanceInfo.unit === "msat" ? (currentBalanceInfo.reserved ?? 0) / 1e3 : currentBalanceInfo.reserved ?? 0;
|
|
4209
|
+
const shortfall = Math.max(0, params.requiredSats - currentBalance + reservedBalance);
|
|
4210
|
+
topupAmount = shortfall > 0.21 * params.requiredSats ? shortfall : 0.21 * params.requiredSats;
|
|
4111
4211
|
this._log(
|
|
4112
4212
|
"DEBUG",
|
|
4113
|
-
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
|
|
4213
|
+
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance}. Reserved Balance: ${reservedBalance}. Available Balance: ${currentBalance - reservedBalance}`
|
|
4114
4214
|
);
|
|
4115
4215
|
} catch (e) {
|
|
4116
4216
|
this._log(
|
|
@@ -4255,10 +4355,10 @@ var RoutstrClient = class {
|
|
|
4255
4355
|
tryNextProvider = true;
|
|
4256
4356
|
}
|
|
4257
4357
|
}
|
|
4258
|
-
if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
|
|
4358
|
+
if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 429 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
|
|
4259
4359
|
this._log(
|
|
4260
4360
|
"DEBUG",
|
|
4261
|
-
`[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
|
|
4361
|
+
`[RoutstrClient] _handleErrorResponse: Status ${status} (${status === 429 ? "rate limited" : "auth/server error"}), attempting refund for ${baseUrl}, mode=${this.mode}`
|
|
4262
4362
|
);
|
|
4263
4363
|
if (this.mode === "apikeys") {
|
|
4264
4364
|
this._log(
|
|
@@ -4336,7 +4436,7 @@ var RoutstrClient = class {
|
|
|
4336
4436
|
});
|
|
4337
4437
|
return this._makeRequest({
|
|
4338
4438
|
...params,
|
|
4339
|
-
path
|
|
4439
|
+
path,
|
|
4340
4440
|
method,
|
|
4341
4441
|
body,
|
|
4342
4442
|
baseUrl: nextProvider,
|
|
@@ -4743,5 +4843,6 @@ exports.ProviderManager = ProviderManager;
|
|
|
4743
4843
|
exports.RoutstrClient = RoutstrClient;
|
|
4744
4844
|
exports.StreamProcessor = StreamProcessor;
|
|
4745
4845
|
exports.createSSEParserTransform = createSSEParserTransform;
|
|
4846
|
+
exports.inspectSSEWebStream = inspectSSEWebStream;
|
|
4746
4847
|
//# sourceMappingURL=index.js.map
|
|
4747
4848
|
//# sourceMappingURL=index.js.map
|