@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.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { getDecodedToken } from '@cashu/cashu-ts';
|
|
2
2
|
import { createStore } from 'zustand/vanilla';
|
|
3
|
-
import { Transform
|
|
4
|
-
import
|
|
5
|
-
import * as path from 'path';
|
|
6
|
-
import * as os from 'os';
|
|
3
|
+
import { Transform } from 'stream';
|
|
4
|
+
import { StringDecoder } from 'string_decoder';
|
|
7
5
|
|
|
8
6
|
// core/errors.ts
|
|
9
7
|
var InsufficientBalanceError = class extends Error {
|
|
@@ -57,10 +55,10 @@ var AuditLogger = class _AuditLogger {
|
|
|
57
55
|
const logLine = JSON.stringify(fullEntry) + "\n";
|
|
58
56
|
if (typeof window === "undefined") {
|
|
59
57
|
try {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const logPath =
|
|
63
|
-
|
|
58
|
+
const fs = await import('fs');
|
|
59
|
+
const path = await import('path');
|
|
60
|
+
const logPath = path.join(process.cwd(), "audit.log");
|
|
61
|
+
fs.appendFileSync(logPath, logLine);
|
|
64
62
|
} catch (error) {
|
|
65
63
|
console.error("[AuditLogger] Failed to write to file:", error);
|
|
66
64
|
}
|
|
@@ -639,7 +637,7 @@ var CashuSpender = class {
|
|
|
639
637
|
};
|
|
640
638
|
|
|
641
639
|
// wallet/BalanceManager.ts
|
|
642
|
-
var BalanceManager = class {
|
|
640
|
+
var BalanceManager = class _BalanceManager {
|
|
643
641
|
constructor(walletAdapter, storageAdapter, providerRegistry, cashuSpender) {
|
|
644
642
|
this.walletAdapter = walletAdapter;
|
|
645
643
|
this.storageAdapter = storageAdapter;
|
|
@@ -656,6 +654,47 @@ var BalanceManager = class {
|
|
|
656
654
|
}
|
|
657
655
|
}
|
|
658
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
|
+
}
|
|
659
698
|
async getBalanceState() {
|
|
660
699
|
const mintBalances = await this.walletAdapter.getBalances();
|
|
661
700
|
const units = this.walletAdapter.getMintUnits();
|
|
@@ -690,6 +729,20 @@ var BalanceManager = class {
|
|
|
690
729
|
* @returns Refund result
|
|
691
730
|
*/
|
|
692
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) {
|
|
693
746
|
const { mintUrl, baseUrl, apiKey, forceRefund } = options;
|
|
694
747
|
if (!apiKey) {
|
|
695
748
|
return { success: false, message: "No API key to refund" };
|
|
@@ -818,6 +871,20 @@ var BalanceManager = class {
|
|
|
818
871
|
* Top up API key balance with a cashu token
|
|
819
872
|
*/
|
|
820
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) {
|
|
821
888
|
const { mintUrl, baseUrl, amount, token: providedToken } = options;
|
|
822
889
|
if (!amount || amount <= 0) {
|
|
823
890
|
return { success: false, message: "Invalid top up amount" };
|
|
@@ -978,7 +1045,7 @@ var BalanceManager = class {
|
|
|
978
1045
|
p2pkPubkey
|
|
979
1046
|
);
|
|
980
1047
|
console.log(
|
|
981
|
-
`[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])])))}`
|
|
982
1049
|
);
|
|
983
1050
|
return {
|
|
984
1051
|
success: true,
|
|
@@ -2117,7 +2184,9 @@ var ProviderManager = class _ProviderManager {
|
|
|
2117
2184
|
const approximateTokens = apiMessagesNoImages ? Math.ceil(JSON.stringify(apiMessagesNoImages, null, 2).length / 2.84) : 1e4;
|
|
2118
2185
|
const totalInputTokens = approximateTokens + imageTokens;
|
|
2119
2186
|
const sp = model?.sats_pricing;
|
|
2120
|
-
if (!sp)
|
|
2187
|
+
if (!sp) {
|
|
2188
|
+
return 0;
|
|
2189
|
+
}
|
|
2121
2190
|
if (!sp.max_completion_cost) {
|
|
2122
2191
|
return sp.max_cost ?? 50;
|
|
2123
2192
|
}
|
|
@@ -3429,26 +3498,117 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
3429
3498
|
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
3430
3499
|
return defaultUsageTrackingDriver;
|
|
3431
3500
|
};
|
|
3432
|
-
function
|
|
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");
|
|
3433
3518
|
let buffer = "";
|
|
3434
3519
|
let capturedUsage = null;
|
|
3520
|
+
let capturedResponseId;
|
|
3435
3521
|
let responseIdCaptured = false;
|
|
3436
|
-
const
|
|
3437
|
-
if (
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
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
|
+
}
|
|
3445
3549
|
};
|
|
3446
|
-
const
|
|
3447
|
-
if (
|
|
3448
|
-
|
|
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
|
|
3449
3603
|
};
|
|
3604
|
+
}
|
|
3605
|
+
function createSSEParserTransform(onUsage, onResponseId) {
|
|
3606
|
+
let buffer = "";
|
|
3607
|
+
const decoder = new StringDecoder("utf8");
|
|
3608
|
+
let capturedUsage = null;
|
|
3609
|
+
let responseIdCaptured = false;
|
|
3450
3610
|
const inspectDataPayload = (jsonText) => {
|
|
3451
|
-
if (responseIdCaptured && capturedUsage
|
|
3611
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3452
3612
|
return;
|
|
3453
3613
|
}
|
|
3454
3614
|
const trimmed = jsonText.trim();
|
|
@@ -3475,7 +3635,7 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3475
3635
|
}
|
|
3476
3636
|
};
|
|
3477
3637
|
const inspectEventBlock = (eventBlock) => {
|
|
3478
|
-
if (responseIdCaptured && capturedUsage
|
|
3638
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
3479
3639
|
return;
|
|
3480
3640
|
}
|
|
3481
3641
|
const lines = eventBlock.split(/\r?\n/);
|
|
@@ -3491,32 +3651,35 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3491
3651
|
const payload = dataParts.join("\n");
|
|
3492
3652
|
inspectDataPayload(payload);
|
|
3493
3653
|
};
|
|
3494
|
-
const
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
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
|
+
}
|
|
3498
3668
|
};
|
|
3499
3669
|
return new Transform({
|
|
3500
3670
|
transform(chunk, _encoding, callback) {
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
let match;
|
|
3505
|
-
while ((match = terminator.exec(buffer)) !== null) {
|
|
3506
|
-
const block = buffer.slice(lastIndex, match.index);
|
|
3507
|
-
lastIndex = match.index + match[0].length;
|
|
3508
|
-
emitEventBlock(this, block);
|
|
3509
|
-
}
|
|
3510
|
-
if (lastIndex > 0) {
|
|
3511
|
-
buffer = buffer.slice(lastIndex);
|
|
3512
|
-
}
|
|
3671
|
+
this.push(chunk);
|
|
3672
|
+
buffer += decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
3673
|
+
processBufferedEvents();
|
|
3513
3674
|
callback();
|
|
3514
3675
|
},
|
|
3515
3676
|
flush(callback) {
|
|
3677
|
+
buffer += decoder.end();
|
|
3678
|
+
processBufferedEvents();
|
|
3516
3679
|
if (buffer.length > 0) {
|
|
3517
3680
|
const tail = buffer.replace(/\r?\n+$/, "");
|
|
3518
3681
|
if (tail.length > 0) {
|
|
3519
|
-
|
|
3682
|
+
inspectEventBlock(tail);
|
|
3520
3683
|
}
|
|
3521
3684
|
buffer = "";
|
|
3522
3685
|
}
|
|
@@ -3524,6 +3687,8 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3524
3687
|
}
|
|
3525
3688
|
});
|
|
3526
3689
|
}
|
|
3690
|
+
|
|
3691
|
+
// client/RoutstrClient.ts
|
|
3527
3692
|
var TOPUP_MARGIN = 1.2;
|
|
3528
3693
|
var RoutstrClient = class {
|
|
3529
3694
|
constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
|
|
@@ -3623,31 +3788,12 @@ var RoutstrClient = class {
|
|
|
3623
3788
|
*/
|
|
3624
3789
|
async routeRequest(params) {
|
|
3625
3790
|
const prepared = await this._prepareRoutedRequest(params);
|
|
3626
|
-
const
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
modelId: prepared.modelId,
|
|
3633
|
-
usage: prepared.capturedUsage,
|
|
3634
|
-
requestId: prepared.capturedResponseId,
|
|
3635
|
-
clientApiKey: prepared.clientApiKey
|
|
3636
|
-
});
|
|
3637
|
-
prepared.response.satsSpent = satsSpent;
|
|
3638
|
-
prepared.response.usage = prepared.capturedUsage;
|
|
3639
|
-
prepared.response.requestId = prepared.capturedResponseId;
|
|
3640
|
-
return prepared.response;
|
|
3641
|
-
}
|
|
3642
|
-
async routeRequestToNodeResponse(params) {
|
|
3643
|
-
const { res } = params;
|
|
3644
|
-
const prepared = await this._prepareRoutedRequest(params);
|
|
3645
|
-
res.statusCode = prepared.response.status;
|
|
3646
|
-
prepared.response.headers.forEach((value, key) => {
|
|
3647
|
-
res.setHeader(key, value);
|
|
3648
|
-
});
|
|
3649
|
-
const body = prepared.response.body;
|
|
3650
|
-
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;
|
|
3651
3797
|
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3652
3798
|
token: prepared.tokenUsed,
|
|
3653
3799
|
baseUrl: prepared.baseUrlUsed,
|
|
@@ -3655,51 +3801,25 @@ var RoutstrClient = class {
|
|
|
3655
3801
|
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3656
3802
|
response: prepared.response,
|
|
3657
3803
|
modelId: prepared.modelId,
|
|
3658
|
-
usage
|
|
3659
|
-
requestId
|
|
3804
|
+
usage,
|
|
3805
|
+
requestId,
|
|
3660
3806
|
clientApiKey: prepared.clientApiKey
|
|
3661
3807
|
});
|
|
3662
3808
|
prepared.response.satsSpent = satsSpent;
|
|
3663
|
-
|
|
3664
|
-
|
|
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;
|
|
3665
3820
|
}
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
let settled = false;
|
|
3669
|
-
const finish = async () => {
|
|
3670
|
-
if (settled) return;
|
|
3671
|
-
settled = true;
|
|
3672
|
-
try {
|
|
3673
|
-
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3674
|
-
token: prepared.tokenUsed,
|
|
3675
|
-
baseUrl: prepared.baseUrlUsed,
|
|
3676
|
-
mintUrl: params.mintUrl,
|
|
3677
|
-
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3678
|
-
response: prepared.response,
|
|
3679
|
-
modelId: prepared.modelId,
|
|
3680
|
-
usage: prepared.capturedUsage,
|
|
3681
|
-
requestId: prepared.capturedResponseId,
|
|
3682
|
-
clientApiKey: prepared.clientApiKey
|
|
3683
|
-
});
|
|
3684
|
-
prepared.response.satsSpent = satsSpent;
|
|
3685
|
-
prepared.response.usage = prepared.capturedUsage;
|
|
3686
|
-
prepared.response.requestId = prepared.capturedResponseId;
|
|
3687
|
-
resolve();
|
|
3688
|
-
} catch (error) {
|
|
3689
|
-
reject(error);
|
|
3690
|
-
}
|
|
3691
|
-
};
|
|
3692
|
-
const fail = (error) => {
|
|
3693
|
-
if (settled) return;
|
|
3694
|
-
settled = true;
|
|
3695
|
-
reject(error);
|
|
3696
|
-
};
|
|
3697
|
-
res.once("finish", finish);
|
|
3698
|
-
res.once("close", finish);
|
|
3699
|
-
res.once("error", fail);
|
|
3700
|
-
nodeReadable.once("error", fail);
|
|
3701
|
-
nodeReadable.pipe(res);
|
|
3702
|
-
});
|
|
3821
|
+
await runFinalize();
|
|
3822
|
+
return prepared.response;
|
|
3703
3823
|
}
|
|
3704
3824
|
async _prepareRoutedRequest(params) {
|
|
3705
3825
|
const {
|
|
@@ -3723,9 +3843,23 @@ var RoutstrClient = class {
|
|
|
3723
3843
|
);
|
|
3724
3844
|
selectedModel = providerModel ?? void 0;
|
|
3725
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
|
+
);
|
|
3726
3859
|
requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
3727
3860
|
selectedModel,
|
|
3728
|
-
|
|
3861
|
+
requestMessages,
|
|
3862
|
+
requestMaxTokens
|
|
3729
3863
|
);
|
|
3730
3864
|
}
|
|
3731
3865
|
}
|
|
@@ -3762,22 +3896,18 @@ var RoutstrClient = class {
|
|
|
3762
3896
|
let processedResponse = response;
|
|
3763
3897
|
let capturedUsage;
|
|
3764
3898
|
let capturedResponseId;
|
|
3899
|
+
let usagePromise = Promise.resolve({});
|
|
3765
3900
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
3766
|
-
const
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
const logStream = fs.createWriteStream(logFile);
|
|
3772
|
-
const nodeReadable = Readable.fromWeb(response.body);
|
|
3773
|
-
const loggingTransform = new Transform({
|
|
3774
|
-
transform(chunk, encoding, callback) {
|
|
3775
|
-
const raw = chunk.toString();
|
|
3776
|
-
logStream.write(JSON.stringify({ raw, timestamp: Date.now() }) + "\n");
|
|
3777
|
-
callback(null, chunk);
|
|
3778
|
-
}
|
|
3901
|
+
const [clientStream, inspectStream] = response.body.tee();
|
|
3902
|
+
processedResponse = new Response(clientStream, {
|
|
3903
|
+
status: response.status,
|
|
3904
|
+
statusText: response.statusText,
|
|
3905
|
+
headers: response.headers
|
|
3779
3906
|
});
|
|
3780
|
-
|
|
3907
|
+
processedResponse.baseUrl = response.baseUrl;
|
|
3908
|
+
processedResponse.token = response.token;
|
|
3909
|
+
usagePromise = inspectSSEWebStream(
|
|
3910
|
+
inspectStream,
|
|
3781
3911
|
(usage) => {
|
|
3782
3912
|
capturedUsage = usage;
|
|
3783
3913
|
processedResponse.usage = usage;
|
|
@@ -3787,17 +3917,7 @@ var RoutstrClient = class {
|
|
|
3787
3917
|
processedResponse.requestId = responseId;
|
|
3788
3918
|
}
|
|
3789
3919
|
);
|
|
3790
|
-
|
|
3791
|
-
const webStream = Readable.toWeb(
|
|
3792
|
-
transformed
|
|
3793
|
-
);
|
|
3794
|
-
processedResponse = new Response(webStream, {
|
|
3795
|
-
status: response.status,
|
|
3796
|
-
statusText: response.statusText,
|
|
3797
|
-
headers: response.headers
|
|
3798
|
-
});
|
|
3799
|
-
processedResponse.baseUrl = response.baseUrl;
|
|
3800
|
-
processedResponse.token = response.token;
|
|
3920
|
+
processedResponse.usagePromise = usagePromise;
|
|
3801
3921
|
}
|
|
3802
3922
|
return {
|
|
3803
3923
|
response: processedResponse,
|
|
@@ -3807,7 +3927,8 @@ var RoutstrClient = class {
|
|
|
3807
3927
|
modelId,
|
|
3808
3928
|
capturedUsage,
|
|
3809
3929
|
capturedResponseId,
|
|
3810
|
-
clientApiKey
|
|
3930
|
+
clientApiKey,
|
|
3931
|
+
usagePromise
|
|
3811
3932
|
};
|
|
3812
3933
|
}
|
|
3813
3934
|
/**
|
|
@@ -3958,9 +4079,9 @@ var RoutstrClient = class {
|
|
|
3958
4079
|
* Make the API request with failover support
|
|
3959
4080
|
*/
|
|
3960
4081
|
async _makeRequest(params) {
|
|
3961
|
-
const { path
|
|
4082
|
+
const { path, method, body, baseUrl, token, headers } = params;
|
|
3962
4083
|
try {
|
|
3963
|
-
const url = `${baseUrl.replace(/\/$/, "")}${
|
|
4084
|
+
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
3964
4085
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
3965
4086
|
const response = await fetch(url, {
|
|
3966
4087
|
method,
|
|
@@ -4010,7 +4131,7 @@ var RoutstrClient = class {
|
|
|
4010
4131
|
*/
|
|
4011
4132
|
async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
|
|
4012
4133
|
const MAX_RETRIES_PER_PROVIDER = 2;
|
|
4013
|
-
const { path
|
|
4134
|
+
const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
|
|
4014
4135
|
let tryNextProvider = false;
|
|
4015
4136
|
const errorMessage = responseBody;
|
|
4016
4137
|
this._log(
|
|
@@ -4082,11 +4203,12 @@ var RoutstrClient = class {
|
|
|
4082
4203
|
baseUrl
|
|
4083
4204
|
);
|
|
4084
4205
|
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
4085
|
-
const
|
|
4086
|
-
|
|
4206
|
+
const reservedBalance = currentBalanceInfo.unit === "msat" ? (currentBalanceInfo.reserved ?? 0) / 1e3 : currentBalanceInfo.reserved ?? 0;
|
|
4207
|
+
const shortfall = Math.max(0, params.requiredSats - currentBalance + reservedBalance);
|
|
4208
|
+
topupAmount = shortfall > 0.21 * params.requiredSats ? shortfall : 0.21 * params.requiredSats;
|
|
4087
4209
|
this._log(
|
|
4088
4210
|
"DEBUG",
|
|
4089
|
-
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
|
|
4211
|
+
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance}. Reserved Balance: ${reservedBalance}. Available Balance: ${currentBalance - reservedBalance}`
|
|
4090
4212
|
);
|
|
4091
4213
|
} catch (e) {
|
|
4092
4214
|
this._log(
|
|
@@ -4231,10 +4353,10 @@ var RoutstrClient = class {
|
|
|
4231
4353
|
tryNextProvider = true;
|
|
4232
4354
|
}
|
|
4233
4355
|
}
|
|
4234
|
-
if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
|
|
4356
|
+
if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 429 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
|
|
4235
4357
|
this._log(
|
|
4236
4358
|
"DEBUG",
|
|
4237
|
-
`[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
|
|
4359
|
+
`[RoutstrClient] _handleErrorResponse: Status ${status} (${status === 429 ? "rate limited" : "auth/server error"}), attempting refund for ${baseUrl}, mode=${this.mode}`
|
|
4238
4360
|
);
|
|
4239
4361
|
if (this.mode === "apikeys") {
|
|
4240
4362
|
this._log(
|
|
@@ -4312,7 +4434,7 @@ var RoutstrClient = class {
|
|
|
4312
4434
|
});
|
|
4313
4435
|
return this._makeRequest({
|
|
4314
4436
|
...params,
|
|
4315
|
-
path
|
|
4437
|
+
path,
|
|
4316
4438
|
method,
|
|
4317
4439
|
body,
|
|
4318
4440
|
baseUrl: nextProvider,
|
|
@@ -4715,6 +4837,6 @@ var RoutstrClient = class {
|
|
|
4715
4837
|
}
|
|
4716
4838
|
};
|
|
4717
4839
|
|
|
4718
|
-
export { ProviderManager, RoutstrClient, StreamProcessor, createSSEParserTransform };
|
|
4840
|
+
export { ProviderManager, RoutstrClient, StreamProcessor, createSSEParserTransform, inspectSSEWebStream };
|
|
4719
4841
|
//# sourceMappingURL=index.mjs.map
|
|
4720
4842
|
//# sourceMappingURL=index.mjs.map
|