@routstr/sdk 0.2.4 → 0.2.6
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/README.md +10 -2
- package/dist/client/index.d.mts +38 -11
- package/dist/client/index.d.ts +38 -11
- package/dist/client/index.js +1771 -391
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +1771 -392
- package/dist/client/index.mjs.map +1 -1
- package/dist/discovery/index.d.mts +2 -2
- package/dist/discovery/index.d.ts +2 -2
- package/dist/discovery/index.js +1 -4
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +1 -4
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +26 -22
- package/dist/index.d.ts +26 -22
- package/dist/index.js +3005 -2107
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2997 -2108
- package/dist/index.mjs.map +1 -1
- package/dist/{interfaces-B85Wx7ni.d.mts → interfaces-B62Rw-dd.d.ts} +20 -15
- package/dist/{interfaces-DGdP8fQp.d.mts → interfaces-BWJJTCXO.d.mts} +1 -1
- package/dist/{interfaces-CC0LT9p9.d.ts → interfaces-BxDEka72.d.ts} +1 -1
- package/dist/{interfaces-BVNyAmKu.d.ts → interfaces-C5fLD3jB.d.mts} +20 -15
- package/dist/storage/index.d.mts +38 -142
- package/dist/storage/index.d.ts +38 -142
- package/dist/storage/index.js +852 -158
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +846 -159
- package/dist/storage/index.mjs.map +1 -1
- package/dist/store-BJlwiDX5.d.ts +151 -0
- package/dist/store-C5lnyX8k.d.mts +151 -0
- package/dist/{types-BlHjmWRK.d.mts → types-BYj_8c5c.d.mts} +3 -0
- package/dist/{types-BlHjmWRK.d.ts → types-BYj_8c5c.d.ts} +3 -0
- package/dist/wallet/index.d.mts +24 -26
- package/dist/wallet/index.d.ts +24 -26
- package/dist/wallet/index.js +130 -259
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +130 -259
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var vanilla = require('zustand/vanilla');
|
|
4
|
+
var stream = require('stream');
|
|
5
|
+
|
|
3
6
|
// core/errors.ts
|
|
4
7
|
var InsufficientBalanceError = class extends Error {
|
|
5
8
|
constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
|
|
@@ -143,13 +146,8 @@ var CashuSpender = class {
|
|
|
143
146
|
normalizedMintBalances[url] = balanceInSats;
|
|
144
147
|
totalMintBalance += balanceInSats;
|
|
145
148
|
}
|
|
146
|
-
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
147
149
|
const providerBalances = {};
|
|
148
150
|
let totalProviderBalance = 0;
|
|
149
|
-
for (const pending of pendingDistribution) {
|
|
150
|
-
providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
|
|
151
|
-
totalProviderBalance += pending.amount;
|
|
152
|
-
}
|
|
153
151
|
const apiKeys = this.storageAdapter.getAllApiKeys();
|
|
154
152
|
for (const apiKey of apiKeys) {
|
|
155
153
|
if (!providerBalances[apiKey.baseUrl]) {
|
|
@@ -370,27 +368,11 @@ var CashuSpender = class {
|
|
|
370
368
|
};
|
|
371
369
|
}
|
|
372
370
|
}
|
|
373
|
-
if (token
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
this._log(
|
|
379
|
-
"DEBUG",
|
|
380
|
-
`[CashuSpender] _spendInternal: Token already exists for ${baseUrl}, receiving newly created token and using existing`
|
|
381
|
-
);
|
|
382
|
-
const receiveResult = await this.receiveToken(token);
|
|
383
|
-
if (receiveResult.success) {
|
|
384
|
-
this._log(
|
|
385
|
-
"DEBUG",
|
|
386
|
-
`[CashuSpender] _spendInternal: Token restored successfully, amount=${receiveResult.amount}`
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
token = this.storageAdapter.getToken(baseUrl);
|
|
390
|
-
} else {
|
|
391
|
-
throw error;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
371
|
+
if (token) {
|
|
372
|
+
this._log(
|
|
373
|
+
"DEBUG",
|
|
374
|
+
`[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
|
|
375
|
+
);
|
|
394
376
|
}
|
|
395
377
|
this._logTransaction("spend", {
|
|
396
378
|
amount: spentAmount,
|
|
@@ -411,19 +393,19 @@ var CashuSpender = class {
|
|
|
411
393
|
};
|
|
412
394
|
}
|
|
413
395
|
/**
|
|
414
|
-
* Try to reuse an existing
|
|
396
|
+
* Try to reuse an existing API key
|
|
415
397
|
*/
|
|
416
398
|
async _tryReuseToken(baseUrl, amount, mintUrl) {
|
|
417
|
-
const
|
|
418
|
-
if (!
|
|
419
|
-
const
|
|
420
|
-
const balanceForBaseUrl =
|
|
421
|
-
this._log("DEBUG", "
|
|
399
|
+
const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
400
|
+
if (!apiKeyEntry) return null;
|
|
401
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
402
|
+
const balanceForBaseUrl = apiKeyDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
|
|
403
|
+
this._log("DEBUG", "Reusing API key", balanceForBaseUrl, amount);
|
|
422
404
|
if (balanceForBaseUrl > amount) {
|
|
423
405
|
const units = this.walletAdapter.getMintUnits();
|
|
424
406
|
const unit = units[mintUrl] || "sat";
|
|
425
407
|
return {
|
|
426
|
-
token:
|
|
408
|
+
token: apiKeyEntry.key,
|
|
427
409
|
status: "success",
|
|
428
410
|
balance: balanceForBaseUrl,
|
|
429
411
|
unit
|
|
@@ -434,7 +416,8 @@ var CashuSpender = class {
|
|
|
434
416
|
const topUpResult = await this.balanceManager.topUp({
|
|
435
417
|
mintUrl,
|
|
436
418
|
baseUrl,
|
|
437
|
-
amount: topUpAmount
|
|
419
|
+
amount: topUpAmount,
|
|
420
|
+
token: apiKeyEntry.key
|
|
438
421
|
});
|
|
439
422
|
this._log("DEBUG", "TOPUP ", topUpResult);
|
|
440
423
|
if (topUpResult.success && topUpResult.toppedUpAmount) {
|
|
@@ -448,7 +431,7 @@ var CashuSpender = class {
|
|
|
448
431
|
status: "success"
|
|
449
432
|
});
|
|
450
433
|
return {
|
|
451
|
-
token:
|
|
434
|
+
token: apiKeyEntry.key,
|
|
452
435
|
status: "success",
|
|
453
436
|
balance: newBalance,
|
|
454
437
|
unit
|
|
@@ -456,83 +439,108 @@ var CashuSpender = class {
|
|
|
456
439
|
}
|
|
457
440
|
const providerBalance = await this._getProviderTokenBalance(
|
|
458
441
|
baseUrl,
|
|
459
|
-
|
|
442
|
+
apiKeyEntry.key
|
|
460
443
|
);
|
|
461
444
|
this._log("DEBUG", providerBalance);
|
|
462
445
|
if (providerBalance <= 0) {
|
|
463
|
-
this.storageAdapter.
|
|
446
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
464
447
|
}
|
|
465
448
|
}
|
|
466
449
|
return null;
|
|
467
450
|
}
|
|
468
451
|
/**
|
|
469
|
-
* Refund
|
|
452
|
+
* Refund all xcashu tokens from storage and increment tryCounts on failure.
|
|
453
|
+
* Reuses receiveToken from BalanceManager/CashuSpender for receiving refunds.
|
|
454
|
+
* @param mintUrl - The mint URL for receiving tokens
|
|
455
|
+
* @param excludeBaseUrls - Base URLs to exclude from refund (optional)
|
|
456
|
+
* @returns Results for each xcashu token refund attempt
|
|
470
457
|
*/
|
|
471
|
-
async
|
|
458
|
+
async refundXcashuTokens(mintUrl, excludeBaseUrls) {
|
|
472
459
|
const results = [];
|
|
473
|
-
const
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
(r) => r.status === "fulfilled" ? r.value : { baseUrl: "", success: false }
|
|
506
|
-
)
|
|
507
|
-
);
|
|
508
|
-
if (refundApiKeys) {
|
|
509
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
510
|
-
const apiKeysToRefund = apiKeyDistribution.filter(
|
|
511
|
-
(p) => baseUrls.includes(p.baseUrl)
|
|
512
|
-
);
|
|
513
|
-
for (const apiKeyEntry of apiKeysToRefund) {
|
|
514
|
-
const apiKeyEntryFull = this.storageAdapter.getApiKey(
|
|
515
|
-
apiKeyEntry.baseUrl
|
|
516
|
-
);
|
|
517
|
-
if (apiKeyEntryFull && this.balanceManager) {
|
|
518
|
-
const refundResult = await this.balanceManager.refundApiKey({
|
|
519
|
-
mintUrl,
|
|
520
|
-
baseUrl: apiKeyEntry.baseUrl,
|
|
521
|
-
apiKey: apiKeyEntryFull.key
|
|
522
|
-
});
|
|
523
|
-
if (refundResult.success) {
|
|
524
|
-
this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, 0);
|
|
460
|
+
const xcashuTokens = this.storageAdapter.getXcashuTokens();
|
|
461
|
+
const excludedUrls = new Set(excludeBaseUrls || []);
|
|
462
|
+
for (const [baseUrl, tokens] of Object.entries(xcashuTokens)) {
|
|
463
|
+
if (excludedUrls.has(baseUrl)) continue;
|
|
464
|
+
for (const xcashuToken of tokens) {
|
|
465
|
+
try {
|
|
466
|
+
const receiveResult = await this.receiveToken(xcashuToken.token);
|
|
467
|
+
if (receiveResult.success) {
|
|
468
|
+
this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
|
|
469
|
+
results.push({
|
|
470
|
+
baseUrl,
|
|
471
|
+
token: xcashuToken.token,
|
|
472
|
+
success: true
|
|
473
|
+
});
|
|
474
|
+
this._log(
|
|
475
|
+
"DEBUG",
|
|
476
|
+
`[CashuSpender] refundXcashuTokens: Successfully refunded xcashu token for ${baseUrl}, amount=${receiveResult.amount}`
|
|
477
|
+
);
|
|
478
|
+
} else {
|
|
479
|
+
const currentTryCount = xcashuToken.tryCount ?? 0;
|
|
480
|
+
const newTryCount = currentTryCount + 1;
|
|
481
|
+
this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
|
|
482
|
+
results.push({
|
|
483
|
+
baseUrl,
|
|
484
|
+
token: xcashuToken.token,
|
|
485
|
+
success: false,
|
|
486
|
+
error: receiveResult.message ?? "Refund failed"
|
|
487
|
+
});
|
|
488
|
+
this._log(
|
|
489
|
+
"DEBUG",
|
|
490
|
+
`[CashuSpender] refundXcashuTokens: Failed to refund xcashu token for ${baseUrl}, incremented tryCount to ${newTryCount}`
|
|
491
|
+
);
|
|
525
492
|
}
|
|
493
|
+
} catch (error) {
|
|
494
|
+
const currentTryCount = xcashuToken.tryCount ?? 0;
|
|
495
|
+
const newTryCount = currentTryCount + 1;
|
|
496
|
+
this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
|
|
497
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
526
498
|
results.push({
|
|
527
|
-
baseUrl
|
|
528
|
-
|
|
499
|
+
baseUrl,
|
|
500
|
+
token: xcashuToken.token,
|
|
501
|
+
success: false,
|
|
502
|
+
error: errorMessage
|
|
529
503
|
});
|
|
504
|
+
this._log(
|
|
505
|
+
"ERROR",
|
|
506
|
+
`[CashuSpender] refundXcashuTokens: Exception during refund for ${baseUrl}: ${errorMessage}, incremented tryCount to ${newTryCount}`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return results;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Refund specific providers without retrying spend
|
|
515
|
+
*/
|
|
516
|
+
async refundProviders(mintUrl, forceRefund) {
|
|
517
|
+
const results = [];
|
|
518
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
519
|
+
for (const apiKeyEntry of apiKeyDistribution) {
|
|
520
|
+
const apiKeyEntryFull = this.storageAdapter.getApiKey(
|
|
521
|
+
apiKeyEntry.baseUrl
|
|
522
|
+
);
|
|
523
|
+
if (apiKeyEntryFull && this.balanceManager) {
|
|
524
|
+
const refundResult = await this.balanceManager.refundApiKey({
|
|
525
|
+
mintUrl,
|
|
526
|
+
baseUrl: apiKeyEntry.baseUrl,
|
|
527
|
+
apiKey: apiKeyEntryFull.key,
|
|
528
|
+
forceRefund
|
|
529
|
+
});
|
|
530
|
+
if (refundResult.success) {
|
|
531
|
+
this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
|
|
530
532
|
} else {
|
|
531
|
-
|
|
532
|
-
baseUrl: apiKeyEntry.baseUrl,
|
|
533
|
-
success: false
|
|
534
|
-
});
|
|
533
|
+
this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, apiKeyEntry.amount);
|
|
535
534
|
}
|
|
535
|
+
results.push({
|
|
536
|
+
baseUrl: apiKeyEntry.baseUrl,
|
|
537
|
+
success: refundResult.success
|
|
538
|
+
});
|
|
539
|
+
} else {
|
|
540
|
+
results.push({
|
|
541
|
+
baseUrl: apiKeyEntry.baseUrl,
|
|
542
|
+
success: false
|
|
543
|
+
});
|
|
536
544
|
}
|
|
537
545
|
}
|
|
538
546
|
return results;
|
|
@@ -617,13 +625,8 @@ var BalanceManager = class {
|
|
|
617
625
|
normalizedMintBalances[url] = balanceInSats;
|
|
618
626
|
totalMintBalance += balanceInSats;
|
|
619
627
|
}
|
|
620
|
-
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
621
628
|
const providerBalances = {};
|
|
622
629
|
let totalProviderBalance = 0;
|
|
623
|
-
for (const pending of pendingDistribution) {
|
|
624
|
-
providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
|
|
625
|
-
totalProviderBalance += pending.amount;
|
|
626
|
-
}
|
|
627
630
|
const apiKeys = this.storageAdapter.getAllApiKeys();
|
|
628
631
|
for (const apiKey of apiKeys) {
|
|
629
632
|
if (!providerBalances[apiKey.baseUrl]) {
|
|
@@ -638,65 +641,31 @@ var BalanceManager = class {
|
|
|
638
641
|
mintBalances: normalizedMintBalances
|
|
639
642
|
};
|
|
640
643
|
}
|
|
641
|
-
/**
|
|
642
|
-
* Unified refund - handles both NIP-60 and legacy wallet refunds
|
|
643
|
-
*/
|
|
644
|
-
async refund(options) {
|
|
645
|
-
const { mintUrl, baseUrl, token: providedToken } = options;
|
|
646
|
-
const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
|
|
647
|
-
if (!storedToken) {
|
|
648
|
-
console.log("[BalanceManager] No token to refund, returning early");
|
|
649
|
-
return { success: true, message: "No API key to refund" };
|
|
650
|
-
}
|
|
651
|
-
let fetchResult;
|
|
652
|
-
try {
|
|
653
|
-
fetchResult = await this._fetchRefundToken(baseUrl, storedToken);
|
|
654
|
-
if (!fetchResult.success) {
|
|
655
|
-
return {
|
|
656
|
-
success: false,
|
|
657
|
-
message: fetchResult.error || "Refund failed",
|
|
658
|
-
requestId: fetchResult.requestId
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
if (!fetchResult.token) {
|
|
662
|
-
return {
|
|
663
|
-
success: false,
|
|
664
|
-
message: "No token received from refund",
|
|
665
|
-
requestId: fetchResult.requestId
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
if (fetchResult.error === "No balance to refund") {
|
|
669
|
-
console.log(
|
|
670
|
-
"[BalanceManager] No balance to refund, removing stored token"
|
|
671
|
-
);
|
|
672
|
-
this.storageAdapter.removeToken(baseUrl);
|
|
673
|
-
return { success: true, message: "No balance to refund" };
|
|
674
|
-
}
|
|
675
|
-
const receiveResult = await this.cashuSpender.receiveToken(
|
|
676
|
-
fetchResult.token
|
|
677
|
-
);
|
|
678
|
-
const totalAmountMsat = receiveResult.unit === "msat" ? receiveResult.amount : receiveResult.amount * 1e3;
|
|
679
|
-
if (!providedToken) {
|
|
680
|
-
this.storageAdapter.removeToken(baseUrl);
|
|
681
|
-
}
|
|
682
|
-
return {
|
|
683
|
-
success: receiveResult.success,
|
|
684
|
-
refundedAmount: totalAmountMsat,
|
|
685
|
-
requestId: fetchResult.requestId
|
|
686
|
-
};
|
|
687
|
-
} catch (error) {
|
|
688
|
-
console.error("[BalanceManager] Refund error", error);
|
|
689
|
-
return this._handleRefundError(error, mintUrl, fetchResult?.requestId);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
644
|
/**
|
|
693
645
|
* Refund API key balance - convert remaining API key balance to cashu token
|
|
646
|
+
* @param options - Refund options including forceRefund flag
|
|
647
|
+
* @returns Refund result
|
|
694
648
|
*/
|
|
695
649
|
async refundApiKey(options) {
|
|
696
|
-
const { mintUrl, baseUrl, apiKey } = options;
|
|
650
|
+
const { mintUrl, baseUrl, apiKey, forceRefund } = options;
|
|
697
651
|
if (!apiKey) {
|
|
698
652
|
return { success: false, message: "No API key to refund" };
|
|
699
653
|
}
|
|
654
|
+
if (!forceRefund) {
|
|
655
|
+
const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
656
|
+
if (apiKeyEntry?.lastUsed) {
|
|
657
|
+
const fiveMinutesAgo = Date.now() - 5 * 60 * 1e3;
|
|
658
|
+
if (apiKeyEntry.lastUsed > fiveMinutesAgo) {
|
|
659
|
+
console.log(
|
|
660
|
+
`[BalanceManager] Skipping refund for ${baseUrl} - used ${Math.round((Date.now() - apiKeyEntry.lastUsed) / 1e3)}s ago`
|
|
661
|
+
);
|
|
662
|
+
return {
|
|
663
|
+
success: false,
|
|
664
|
+
message: "API key was used recently, skipping refund"
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
700
669
|
let fetchResult;
|
|
701
670
|
try {
|
|
702
671
|
fetchResult = await this._fetchRefundTokenWithApiKey(baseUrl, apiKey);
|
|
@@ -807,8 +776,9 @@ var BalanceManager = class {
|
|
|
807
776
|
if (!amount || amount <= 0) {
|
|
808
777
|
return { success: false, message: "Invalid top up amount" };
|
|
809
778
|
}
|
|
810
|
-
const
|
|
811
|
-
|
|
779
|
+
const apiKeyEntry = providedToken ? null : this.storageAdapter.getApiKey(baseUrl);
|
|
780
|
+
const apiKey = providedToken || apiKeyEntry?.key;
|
|
781
|
+
if (!apiKey) {
|
|
812
782
|
return { success: false, message: "No API key available for top up" };
|
|
813
783
|
}
|
|
814
784
|
let cashuToken = null;
|
|
@@ -828,7 +798,7 @@ var BalanceManager = class {
|
|
|
828
798
|
cashuToken = tokenResult.token;
|
|
829
799
|
const topUpResult = await this._postTopUp(
|
|
830
800
|
baseUrl,
|
|
831
|
-
|
|
801
|
+
apiKey,
|
|
832
802
|
cashuToken
|
|
833
803
|
);
|
|
834
804
|
requestId = topUpResult.requestId;
|
|
@@ -888,8 +858,8 @@ var BalanceManager = class {
|
|
|
888
858
|
const refundableProviderBalance = Object.entries(
|
|
889
859
|
balanceState.providerBalances
|
|
890
860
|
).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
|
|
891
|
-
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount <
|
|
892
|
-
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl);
|
|
861
|
+
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 2) {
|
|
862
|
+
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount);
|
|
893
863
|
return this.createProviderToken({
|
|
894
864
|
...options,
|
|
895
865
|
retryCount: retryCount + 1
|
|
@@ -1042,38 +1012,12 @@ var BalanceManager = class {
|
|
|
1042
1012
|
}
|
|
1043
1013
|
return candidates;
|
|
1044
1014
|
}
|
|
1045
|
-
async _refundOtherProvidersForTopUp(baseUrl, mintUrl) {
|
|
1046
|
-
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
1015
|
+
async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
|
|
1047
1016
|
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
1048
|
-
const
|
|
1049
|
-
(pending) => pending.baseUrl !== baseUrl
|
|
1050
|
-
);
|
|
1017
|
+
const forceRefund = retryCount >= 2;
|
|
1051
1018
|
const apiKeysToRefund = apiKeyDistribution.filter(
|
|
1052
1019
|
(apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
|
|
1053
1020
|
);
|
|
1054
|
-
const tokenRefundResults = await Promise.allSettled(
|
|
1055
|
-
toRefund.map(async (pending) => {
|
|
1056
|
-
const token = this.storageAdapter.getToken(pending.baseUrl);
|
|
1057
|
-
if (!token) {
|
|
1058
|
-
return { baseUrl: pending.baseUrl, success: false };
|
|
1059
|
-
}
|
|
1060
|
-
const tokenBalance = await this.getTokenBalance(token, pending.baseUrl);
|
|
1061
|
-
if (tokenBalance.reserved > 0) {
|
|
1062
|
-
return { baseUrl: pending.baseUrl, success: false };
|
|
1063
|
-
}
|
|
1064
|
-
const result = await this.refund({
|
|
1065
|
-
mintUrl,
|
|
1066
|
-
baseUrl: pending.baseUrl,
|
|
1067
|
-
token
|
|
1068
|
-
});
|
|
1069
|
-
return { baseUrl: pending.baseUrl, success: result.success };
|
|
1070
|
-
})
|
|
1071
|
-
);
|
|
1072
|
-
for (const result of tokenRefundResults) {
|
|
1073
|
-
if (result.status === "fulfilled" && result.value.success) {
|
|
1074
|
-
this.storageAdapter.removeToken(result.value.baseUrl);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
1021
|
const apiKeyRefundResults = await Promise.allSettled(
|
|
1078
1022
|
apiKeysToRefund.map(async (apiKeyEntry) => {
|
|
1079
1023
|
const fullApiKeyEntry = this.storageAdapter.getApiKey(
|
|
@@ -1085,7 +1029,8 @@ var BalanceManager = class {
|
|
|
1085
1029
|
const result = await this.refundApiKey({
|
|
1086
1030
|
mintUrl,
|
|
1087
1031
|
baseUrl: apiKeyEntry.baseUrl,
|
|
1088
|
-
apiKey: fullApiKeyEntry.key
|
|
1032
|
+
apiKey: fullApiKeyEntry.key,
|
|
1033
|
+
forceRefund
|
|
1089
1034
|
});
|
|
1090
1035
|
return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
|
|
1091
1036
|
})
|
|
@@ -1096,77 +1041,6 @@ var BalanceManager = class {
|
|
|
1096
1041
|
}
|
|
1097
1042
|
}
|
|
1098
1043
|
}
|
|
1099
|
-
/**
|
|
1100
|
-
* Fetch refund token from provider API
|
|
1101
|
-
*/
|
|
1102
|
-
async _fetchRefundToken(baseUrl, storedToken) {
|
|
1103
|
-
if (!baseUrl) {
|
|
1104
|
-
return {
|
|
1105
|
-
success: false,
|
|
1106
|
-
error: "No base URL configured"
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
1110
|
-
const url = `${normalizedBaseUrl}v1/wallet/refund`;
|
|
1111
|
-
const controller = new AbortController();
|
|
1112
|
-
const timeoutId = setTimeout(() => {
|
|
1113
|
-
controller.abort();
|
|
1114
|
-
}, 6e4);
|
|
1115
|
-
try {
|
|
1116
|
-
const response = await fetch(url, {
|
|
1117
|
-
method: "POST",
|
|
1118
|
-
headers: {
|
|
1119
|
-
Authorization: `Bearer ${storedToken}`,
|
|
1120
|
-
"Content-Type": "application/json"
|
|
1121
|
-
},
|
|
1122
|
-
signal: controller.signal
|
|
1123
|
-
});
|
|
1124
|
-
clearTimeout(timeoutId);
|
|
1125
|
-
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
1126
|
-
if (!response.ok) {
|
|
1127
|
-
const errorData = await response.json().catch(() => ({}));
|
|
1128
|
-
if (response.status === 400 && errorData?.detail === "No balance to refund") {
|
|
1129
|
-
this.storageAdapter.removeToken(baseUrl);
|
|
1130
|
-
return {
|
|
1131
|
-
success: false,
|
|
1132
|
-
requestId,
|
|
1133
|
-
error: "No balance to refund"
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
return {
|
|
1137
|
-
success: false,
|
|
1138
|
-
requestId,
|
|
1139
|
-
error: `Refund request failed with status ${response.status}: ${errorData?.detail || response.statusText}`
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
const data = await response.json();
|
|
1143
|
-
console.log("refund rsule", data);
|
|
1144
|
-
return {
|
|
1145
|
-
success: true,
|
|
1146
|
-
token: data.token,
|
|
1147
|
-
requestId
|
|
1148
|
-
};
|
|
1149
|
-
} catch (error) {
|
|
1150
|
-
clearTimeout(timeoutId);
|
|
1151
|
-
console.error("[BalanceManager._fetchRefundToken] Fetch error", error);
|
|
1152
|
-
if (error instanceof Error) {
|
|
1153
|
-
if (error.name === "AbortError") {
|
|
1154
|
-
return {
|
|
1155
|
-
success: false,
|
|
1156
|
-
error: "Request timed out after 1 minute"
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
return {
|
|
1160
|
-
success: false,
|
|
1161
|
-
error: error.message
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
return {
|
|
1165
|
-
success: false,
|
|
1166
|
-
error: "Unknown error occurred during refund request"
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
1044
|
/**
|
|
1171
1045
|
* Post topup request to provider API
|
|
1172
1046
|
*/
|
|
@@ -1339,6 +1213,77 @@ var BalanceManager = class {
|
|
|
1339
1213
|
}
|
|
1340
1214
|
};
|
|
1341
1215
|
|
|
1216
|
+
// client/usage.ts
|
|
1217
|
+
function extractUsageFromResponseBody(body, fallbackSatsCost = 0) {
|
|
1218
|
+
if (!body || typeof body !== "object") return null;
|
|
1219
|
+
const usage = body.usage;
|
|
1220
|
+
if (!usage || typeof usage !== "object") return null;
|
|
1221
|
+
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
1222
|
+
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
1223
|
+
const totalTokens = Number(usage.total_tokens ?? 0);
|
|
1224
|
+
const costValue = usage.cost;
|
|
1225
|
+
let cost = 0;
|
|
1226
|
+
let satsCost = fallbackSatsCost;
|
|
1227
|
+
if (typeof costValue === "number") {
|
|
1228
|
+
cost = costValue;
|
|
1229
|
+
} else if (costValue && typeof costValue === "object") {
|
|
1230
|
+
const costObj = costValue;
|
|
1231
|
+
const totalUsd = costObj.total_usd;
|
|
1232
|
+
const totalMsats = costObj.total_msats;
|
|
1233
|
+
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
1234
|
+
if (typeof totalMsats === "number") {
|
|
1235
|
+
satsCost = totalMsats / 1e3;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
promptTokens,
|
|
1243
|
+
completionTokens,
|
|
1244
|
+
totalTokens,
|
|
1245
|
+
cost,
|
|
1246
|
+
satsCost
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function extractResponseId(body) {
|
|
1250
|
+
if (!body || typeof body !== "object") return void 0;
|
|
1251
|
+
const id = body.id;
|
|
1252
|
+
if (typeof id !== "string") return void 0;
|
|
1253
|
+
const trimmed = id.trim();
|
|
1254
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
1255
|
+
}
|
|
1256
|
+
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
1257
|
+
if (!parsed || typeof parsed !== "object" || !parsed.usage) {
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
const usage = parsed.usage;
|
|
1261
|
+
const usageCost = usage.cost;
|
|
1262
|
+
const cost = typeof usageCost === "number" ? usageCost : usageCost?.total_usd ?? parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
1263
|
+
const msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
1264
|
+
const result = {
|
|
1265
|
+
promptTokens: Number(usage.prompt_tokens ?? 0),
|
|
1266
|
+
completionTokens: Number(usage.completion_tokens ?? 0),
|
|
1267
|
+
totalTokens: Number(usage.total_tokens ?? 0),
|
|
1268
|
+
cost: Number(cost ?? 0),
|
|
1269
|
+
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
|
|
1270
|
+
};
|
|
1271
|
+
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
return result;
|
|
1275
|
+
}
|
|
1276
|
+
function toUsageStats(usage) {
|
|
1277
|
+
if (!usage) return void 0;
|
|
1278
|
+
return {
|
|
1279
|
+
total_tokens: usage.totalTokens,
|
|
1280
|
+
prompt_tokens: usage.promptTokens,
|
|
1281
|
+
completion_tokens: usage.completionTokens,
|
|
1282
|
+
cost: usage.cost,
|
|
1283
|
+
sats_cost: usage.satsCost
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1342
1287
|
// client/StreamProcessor.ts
|
|
1343
1288
|
var StreamProcessor = class {
|
|
1344
1289
|
accumulatedContent = "";
|
|
@@ -1366,6 +1311,7 @@ var StreamProcessor = class {
|
|
|
1366
1311
|
let finish_reason;
|
|
1367
1312
|
let citations;
|
|
1368
1313
|
let annotations;
|
|
1314
|
+
let responseId;
|
|
1369
1315
|
try {
|
|
1370
1316
|
while (true) {
|
|
1371
1317
|
const { done, value } = await reader.read();
|
|
@@ -1394,6 +1340,9 @@ var StreamProcessor = class {
|
|
|
1394
1340
|
if (parsed.finish_reason) {
|
|
1395
1341
|
finish_reason = parsed.finish_reason;
|
|
1396
1342
|
}
|
|
1343
|
+
if (parsed.responseId) {
|
|
1344
|
+
responseId = parsed.responseId;
|
|
1345
|
+
}
|
|
1397
1346
|
if (parsed.citations) {
|
|
1398
1347
|
citations = parsed.citations;
|
|
1399
1348
|
}
|
|
@@ -1414,6 +1363,7 @@ var StreamProcessor = class {
|
|
|
1414
1363
|
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
1415
1364
|
usage,
|
|
1416
1365
|
model,
|
|
1366
|
+
responseId,
|
|
1417
1367
|
finish_reason,
|
|
1418
1368
|
citations,
|
|
1419
1369
|
annotations
|
|
@@ -1441,12 +1391,15 @@ var StreamProcessor = class {
|
|
|
1441
1391
|
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
1442
1392
|
}
|
|
1443
1393
|
if (parsed.usage) {
|
|
1444
|
-
result.usage = {
|
|
1394
|
+
result.usage = toUsageStats(extractUsageFromSSEJson(parsed)) ?? {
|
|
1445
1395
|
total_tokens: parsed.usage.total_tokens,
|
|
1446
1396
|
prompt_tokens: parsed.usage.prompt_tokens,
|
|
1447
1397
|
completion_tokens: parsed.usage.completion_tokens
|
|
1448
1398
|
};
|
|
1449
1399
|
}
|
|
1400
|
+
if (parsed.id) {
|
|
1401
|
+
result.responseId = parsed.id;
|
|
1402
|
+
}
|
|
1450
1403
|
if (parsed.model) {
|
|
1451
1404
|
result.model = parsed.model;
|
|
1452
1405
|
}
|
|
@@ -1838,7 +1791,6 @@ var ProviderManager = class _ProviderManager {
|
|
|
1838
1791
|
* Get providers for a model sorted by prompt+completion pricing
|
|
1839
1792
|
*/
|
|
1840
1793
|
getProviderPriceRankingForModel(modelId, options = {}) {
|
|
1841
|
-
const normalizedId = this.normalizeModelId(modelId);
|
|
1842
1794
|
const includeDisabled = options.includeDisabled ?? false;
|
|
1843
1795
|
const torMode = options.torMode ?? false;
|
|
1844
1796
|
const disabledProviders = new Set(
|
|
@@ -1852,9 +1804,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
1852
1804
|
if (torMode && !baseUrl.includes(".onion")) continue;
|
|
1853
1805
|
if (!torMode && (baseUrl.includes(".onion") || isInsecureHttpUrl(baseUrl)))
|
|
1854
1806
|
continue;
|
|
1855
|
-
const match = models.find(
|
|
1856
|
-
(model) => this.normalizeModelId(model.id) === normalizedId
|
|
1857
|
-
);
|
|
1807
|
+
const match = models.find((model) => model.id === modelId);
|
|
1858
1808
|
if (!match?.sats_pricing) continue;
|
|
1859
1809
|
const prompt = match.sats_pricing.prompt;
|
|
1860
1810
|
const completion = match.sats_pricing.completion;
|
|
@@ -1967,36 +1917,1311 @@ var ProviderManager = class _ProviderManager {
|
|
|
1967
1917
|
}
|
|
1968
1918
|
};
|
|
1969
1919
|
|
|
1970
|
-
//
|
|
1971
|
-
var
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
);
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1920
|
+
// storage/drivers/localStorage.ts
|
|
1921
|
+
var canUseLocalStorage = () => {
|
|
1922
|
+
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
1923
|
+
};
|
|
1924
|
+
var isQuotaExceeded = (error) => {
|
|
1925
|
+
const e = error;
|
|
1926
|
+
return !!e && (e?.name === "QuotaExceededError" || e?.code === 22 || e?.code === 1014);
|
|
1927
|
+
};
|
|
1928
|
+
var NON_CRITICAL_KEYS = /* @__PURE__ */ new Set(["modelsFromAllProviders"]);
|
|
1929
|
+
var localStorageDriver = {
|
|
1930
|
+
async getItem(key, defaultValue) {
|
|
1931
|
+
if (!canUseLocalStorage()) return defaultValue;
|
|
1932
|
+
try {
|
|
1933
|
+
const item = window.localStorage.getItem(key);
|
|
1934
|
+
if (item === null) return defaultValue;
|
|
1935
|
+
try {
|
|
1936
|
+
return JSON.parse(item);
|
|
1937
|
+
} catch (parseError) {
|
|
1938
|
+
if (typeof defaultValue === "string") {
|
|
1939
|
+
return item;
|
|
1940
|
+
}
|
|
1941
|
+
throw parseError;
|
|
1942
|
+
}
|
|
1943
|
+
} catch (error) {
|
|
1944
|
+
console.error(`Error retrieving item with key "${key}":`, error);
|
|
1945
|
+
if (canUseLocalStorage()) {
|
|
1946
|
+
try {
|
|
1947
|
+
window.localStorage.removeItem(key);
|
|
1948
|
+
} catch (removeError) {
|
|
1949
|
+
console.error(
|
|
1950
|
+
`Error removing corrupted item with key "${key}":`,
|
|
1951
|
+
removeError
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
return defaultValue;
|
|
1956
|
+
}
|
|
1957
|
+
},
|
|
1958
|
+
async setItem(key, value) {
|
|
1959
|
+
if (!canUseLocalStorage()) return;
|
|
1960
|
+
try {
|
|
1961
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
1962
|
+
} catch (error) {
|
|
1963
|
+
if (isQuotaExceeded(error)) {
|
|
1964
|
+
if (NON_CRITICAL_KEYS.has(key)) {
|
|
1965
|
+
console.warn(
|
|
1966
|
+
`Storage quota exceeded; skipping non-critical key "${key}".`
|
|
1967
|
+
);
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
try {
|
|
1971
|
+
window.localStorage.removeItem("modelsFromAllProviders");
|
|
1972
|
+
} catch {
|
|
1973
|
+
}
|
|
1974
|
+
try {
|
|
1975
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
1976
|
+
return;
|
|
1977
|
+
} catch (retryError) {
|
|
1978
|
+
console.warn(
|
|
1979
|
+
`Storage quota exceeded; unable to persist key "${key}" after cleanup attempt.`,
|
|
1980
|
+
retryError
|
|
1981
|
+
);
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
console.error(`Error storing item with key "${key}":`, error);
|
|
1986
|
+
}
|
|
1987
|
+
},
|
|
1988
|
+
async removeItem(key) {
|
|
1989
|
+
if (!canUseLocalStorage()) return;
|
|
1990
|
+
try {
|
|
1991
|
+
window.localStorage.removeItem(key);
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
console.error(`Error removing item with key "${key}":`, error);
|
|
1994
|
+
}
|
|
1992
1995
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
+
};
|
|
1997
|
+
|
|
1998
|
+
// storage/drivers/memory.ts
|
|
1999
|
+
var createMemoryDriver = (seed) => {
|
|
2000
|
+
const store = /* @__PURE__ */ new Map();
|
|
2001
|
+
return {
|
|
2002
|
+
async getItem(key, defaultValue) {
|
|
2003
|
+
const item = store.get(key);
|
|
2004
|
+
if (item === void 0) return defaultValue;
|
|
2005
|
+
try {
|
|
2006
|
+
return JSON.parse(item);
|
|
2007
|
+
} catch (parseError) {
|
|
2008
|
+
if (typeof defaultValue === "string") {
|
|
2009
|
+
return item;
|
|
2010
|
+
}
|
|
2011
|
+
throw parseError;
|
|
2012
|
+
}
|
|
2013
|
+
},
|
|
2014
|
+
async setItem(key, value) {
|
|
2015
|
+
store.set(key, JSON.stringify(value));
|
|
2016
|
+
},
|
|
2017
|
+
async removeItem(key) {
|
|
2018
|
+
store.delete(key);
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
};
|
|
2022
|
+
|
|
2023
|
+
// storage/drivers/sqlite.ts
|
|
2024
|
+
var isBun = () => {
|
|
2025
|
+
return typeof process.versions.bun !== "undefined";
|
|
2026
|
+
};
|
|
2027
|
+
var cachedDbModule = null;
|
|
2028
|
+
var loadDatabase = async (dbPath) => {
|
|
2029
|
+
if (isBun()) {
|
|
2030
|
+
throw new Error(
|
|
2031
|
+
"SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
try {
|
|
2035
|
+
if (!cachedDbModule) {
|
|
2036
|
+
cachedDbModule = (await import('better-sqlite3')).default;
|
|
2037
|
+
}
|
|
2038
|
+
return new cachedDbModule(dbPath);
|
|
2039
|
+
} catch (error) {
|
|
2040
|
+
throw new Error(
|
|
2041
|
+
`better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
};
|
|
2045
|
+
var createSqliteDriver = (options = {}) => {
|
|
2046
|
+
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2047
|
+
const tableName = options.tableName || "sdk_storage";
|
|
2048
|
+
let db;
|
|
2049
|
+
let selectStmt;
|
|
2050
|
+
let upsertStmt;
|
|
2051
|
+
let deleteStmt;
|
|
2052
|
+
const initDb = async () => {
|
|
2053
|
+
if (!db) {
|
|
2054
|
+
db = await loadDatabase(dbPath);
|
|
2055
|
+
db.exec(
|
|
2056
|
+
`CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
2057
|
+
);
|
|
2058
|
+
selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
|
|
2059
|
+
upsertStmt = db.prepare(
|
|
2060
|
+
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)
|
|
2061
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
2062
|
+
);
|
|
2063
|
+
deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
|
|
2064
|
+
}
|
|
2065
|
+
};
|
|
2066
|
+
const ensureInit = async () => {
|
|
2067
|
+
if (!db) {
|
|
2068
|
+
await initDb();
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
return {
|
|
2072
|
+
async getItem(key, defaultValue) {
|
|
2073
|
+
try {
|
|
2074
|
+
await ensureInit();
|
|
2075
|
+
const row = selectStmt.get(key);
|
|
2076
|
+
if (!row || typeof row.value !== "string") return defaultValue;
|
|
2077
|
+
try {
|
|
2078
|
+
return JSON.parse(row.value);
|
|
2079
|
+
} catch (parseError) {
|
|
2080
|
+
if (typeof defaultValue === "string") {
|
|
2081
|
+
return row.value;
|
|
2082
|
+
}
|
|
2083
|
+
throw parseError;
|
|
2084
|
+
}
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
console.error(`SQLite getItem failed for key "${key}":`, error);
|
|
2087
|
+
return defaultValue;
|
|
2088
|
+
}
|
|
2089
|
+
},
|
|
2090
|
+
async setItem(key, value) {
|
|
2091
|
+
try {
|
|
2092
|
+
await ensureInit();
|
|
2093
|
+
upsertStmt.run(key, JSON.stringify(value));
|
|
2094
|
+
} catch (error) {
|
|
2095
|
+
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
2096
|
+
}
|
|
2097
|
+
},
|
|
2098
|
+
async removeItem(key) {
|
|
2099
|
+
try {
|
|
2100
|
+
await ensureInit();
|
|
2101
|
+
deleteStmt.run(key);
|
|
2102
|
+
} catch (error) {
|
|
2103
|
+
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
// storage/keys.ts
|
|
2110
|
+
var SDK_STORAGE_KEYS = {
|
|
2111
|
+
MODELS_FROM_ALL_PROVIDERS: "modelsFromAllProviders",
|
|
2112
|
+
LAST_USED_MODEL: "lastUsedModel",
|
|
2113
|
+
BASE_URLS_LIST: "base_urls_list",
|
|
2114
|
+
DISABLED_PROVIDERS: "disabled_providers",
|
|
2115
|
+
MINTS_FROM_ALL_PROVIDERS: "mints_from_all_providers",
|
|
2116
|
+
INFO_FROM_ALL_PROVIDERS: "info_from_all_providers",
|
|
2117
|
+
LAST_MODELS_UPDATE: "lastModelsUpdate",
|
|
2118
|
+
LAST_BASE_URLS_UPDATE: "lastBaseUrlsUpdate",
|
|
2119
|
+
API_KEYS: "api_keys",
|
|
2120
|
+
CHILD_KEYS: "child_keys",
|
|
2121
|
+
XCASHU_TOKENS: "xcashu_tokens",
|
|
2122
|
+
ROUTSTR21_MODELS: "routstr21Models",
|
|
2123
|
+
LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
|
|
2124
|
+
CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
|
|
2125
|
+
USAGE_TRACKING: "usage_tracking",
|
|
2126
|
+
CLIENT_IDS: "client_ids"
|
|
2127
|
+
};
|
|
2128
|
+
|
|
2129
|
+
// storage/usageTracking/indexedDB.ts
|
|
2130
|
+
var DEFAULT_DB_NAME = "routstr-sdk";
|
|
2131
|
+
var DEFAULT_STORE_NAME = "usage_tracking";
|
|
2132
|
+
var MIGRATION_MARKER_KEY = "usage_tracking_migration_v1";
|
|
2133
|
+
var isBrowser = typeof indexedDB !== "undefined";
|
|
2134
|
+
var normalizeBaseUrl = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2135
|
+
var openDatabase = (dbName, storeName) => {
|
|
2136
|
+
if (!isBrowser) {
|
|
2137
|
+
return Promise.reject(new Error("IndexedDB is not available"));
|
|
2138
|
+
}
|
|
2139
|
+
return new Promise((resolve, reject) => {
|
|
2140
|
+
const request = indexedDB.open(dbName, 1);
|
|
2141
|
+
request.onupgradeneeded = () => {
|
|
2142
|
+
const db = request.result;
|
|
2143
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
2144
|
+
const store = db.createObjectStore(storeName, { keyPath: "id" });
|
|
2145
|
+
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
2146
|
+
store.createIndex("modelId", "modelId", { unique: false });
|
|
2147
|
+
store.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
2148
|
+
store.createIndex("sessionId", "sessionId", { unique: false });
|
|
2149
|
+
store.createIndex("client", "client", { unique: false });
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
request.onsuccess = () => resolve(request.result);
|
|
2153
|
+
request.onerror = () => reject(request.error);
|
|
2154
|
+
});
|
|
2155
|
+
};
|
|
2156
|
+
var matchesFilters = (entry, options = {}) => {
|
|
2157
|
+
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
2158
|
+
return false;
|
|
2159
|
+
}
|
|
2160
|
+
if (typeof options.after === "number" && entry.timestamp <= options.after) {
|
|
2161
|
+
return false;
|
|
2162
|
+
}
|
|
2163
|
+
if (options.modelId && entry.modelId !== options.modelId) {
|
|
2164
|
+
return false;
|
|
2165
|
+
}
|
|
2166
|
+
if (options.baseUrl && normalizeBaseUrl(entry.baseUrl) !== normalizeBaseUrl(options.baseUrl)) {
|
|
2167
|
+
return false;
|
|
2168
|
+
}
|
|
2169
|
+
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
2170
|
+
return false;
|
|
2171
|
+
}
|
|
2172
|
+
if (options.client && entry.client !== options.client) {
|
|
2173
|
+
return false;
|
|
2174
|
+
}
|
|
2175
|
+
return true;
|
|
2176
|
+
};
|
|
2177
|
+
var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
2178
|
+
const dbName = options.dbName || DEFAULT_DB_NAME;
|
|
2179
|
+
const storeName = options.storeName || DEFAULT_STORE_NAME;
|
|
2180
|
+
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2181
|
+
let dbPromise = null;
|
|
2182
|
+
let migrationPromise = null;
|
|
2183
|
+
const getDb = () => {
|
|
2184
|
+
if (!dbPromise) {
|
|
2185
|
+
dbPromise = openDatabase(dbName, storeName);
|
|
2186
|
+
}
|
|
2187
|
+
return dbPromise;
|
|
2188
|
+
};
|
|
2189
|
+
const putMany = async (entries) => {
|
|
2190
|
+
if (entries.length === 0) return;
|
|
2191
|
+
const db = await getDb();
|
|
2192
|
+
await new Promise((resolve, reject) => {
|
|
2193
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
2194
|
+
const store = tx.objectStore(storeName);
|
|
2195
|
+
for (const entry of entries) {
|
|
2196
|
+
store.put({ ...entry, baseUrl: normalizeBaseUrl(entry.baseUrl) });
|
|
2197
|
+
}
|
|
2198
|
+
tx.oncomplete = () => resolve();
|
|
2199
|
+
tx.onerror = () => reject(tx.error);
|
|
2200
|
+
});
|
|
2201
|
+
};
|
|
2202
|
+
const ensureMigrated = async () => {
|
|
2203
|
+
if (!legacyStorageDriver) return;
|
|
2204
|
+
if (!migrationPromise) {
|
|
2205
|
+
migrationPromise = (async () => {
|
|
2206
|
+
const migrated = await legacyStorageDriver.getItem(
|
|
2207
|
+
MIGRATION_MARKER_KEY,
|
|
2208
|
+
false
|
|
2209
|
+
);
|
|
2210
|
+
if (migrated) return;
|
|
2211
|
+
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2212
|
+
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2213
|
+
[]
|
|
2214
|
+
);
|
|
2215
|
+
if (legacyEntries.length > 0) {
|
|
2216
|
+
await putMany(legacyEntries);
|
|
2217
|
+
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2218
|
+
}
|
|
2219
|
+
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY, true);
|
|
2220
|
+
})();
|
|
2221
|
+
}
|
|
2222
|
+
await migrationPromise;
|
|
2223
|
+
};
|
|
2224
|
+
return {
|
|
2225
|
+
async migrate() {
|
|
2226
|
+
await ensureMigrated();
|
|
2227
|
+
},
|
|
2228
|
+
async append(entry) {
|
|
2229
|
+
await ensureMigrated();
|
|
2230
|
+
await putMany([entry]);
|
|
2231
|
+
},
|
|
2232
|
+
async appendMany(entries) {
|
|
2233
|
+
await ensureMigrated();
|
|
2234
|
+
await putMany(entries);
|
|
2235
|
+
},
|
|
2236
|
+
async list(options2 = {}) {
|
|
2237
|
+
await ensureMigrated();
|
|
2238
|
+
const db = await getDb();
|
|
2239
|
+
return new Promise((resolve, reject) => {
|
|
2240
|
+
const tx = db.transaction(storeName, "readonly");
|
|
2241
|
+
const store = tx.objectStore(storeName);
|
|
2242
|
+
const index = store.index("timestamp");
|
|
2243
|
+
const direction = "prev";
|
|
2244
|
+
const request = index.openCursor(null, direction);
|
|
2245
|
+
const results = [];
|
|
2246
|
+
const limit = options2.limit;
|
|
2247
|
+
request.onsuccess = () => {
|
|
2248
|
+
const cursor = request.result;
|
|
2249
|
+
if (!cursor) {
|
|
2250
|
+
resolve(results);
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
const value = cursor.value;
|
|
2254
|
+
if (matchesFilters(value, options2)) {
|
|
2255
|
+
results.push(value);
|
|
2256
|
+
if (typeof limit === "number" && results.length >= limit) {
|
|
2257
|
+
resolve(results);
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
cursor.continue();
|
|
2262
|
+
};
|
|
2263
|
+
request.onerror = () => reject(request.error);
|
|
2264
|
+
});
|
|
2265
|
+
},
|
|
2266
|
+
async count(options2 = {}) {
|
|
2267
|
+
const results = await this.list(options2);
|
|
2268
|
+
return results.length;
|
|
2269
|
+
},
|
|
2270
|
+
async deleteOlderThan(timestamp) {
|
|
2271
|
+
await ensureMigrated();
|
|
2272
|
+
const db = await getDb();
|
|
2273
|
+
return new Promise((resolve, reject) => {
|
|
2274
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
2275
|
+
const store = tx.objectStore(storeName);
|
|
2276
|
+
const index = store.index("timestamp");
|
|
2277
|
+
const range = IDBKeyRange.upperBound(timestamp, true);
|
|
2278
|
+
const request = index.openCursor(range);
|
|
2279
|
+
let deleted = 0;
|
|
2280
|
+
request.onsuccess = () => {
|
|
2281
|
+
const cursor = request.result;
|
|
2282
|
+
if (!cursor) {
|
|
2283
|
+
resolve(deleted);
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
deleted += 1;
|
|
2287
|
+
cursor.delete();
|
|
2288
|
+
cursor.continue();
|
|
2289
|
+
};
|
|
2290
|
+
request.onerror = () => reject(request.error);
|
|
2291
|
+
});
|
|
2292
|
+
},
|
|
2293
|
+
async clear() {
|
|
2294
|
+
await ensureMigrated();
|
|
2295
|
+
const db = await getDb();
|
|
2296
|
+
await new Promise((resolve, reject) => {
|
|
2297
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
2298
|
+
tx.objectStore(storeName).clear();
|
|
2299
|
+
tx.oncomplete = () => resolve();
|
|
2300
|
+
tx.onerror = () => reject(tx.error);
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
};
|
|
2304
|
+
};
|
|
2305
|
+
|
|
2306
|
+
// storage/usageTracking/sqlite.ts
|
|
2307
|
+
var MIGRATION_MARKER_KEY2 = "usage_tracking_migration_v1";
|
|
2308
|
+
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2309
|
+
var isBun2 = () => {
|
|
2310
|
+
return typeof process.versions.bun !== "undefined";
|
|
2311
|
+
};
|
|
2312
|
+
var cachedDbModule2 = null;
|
|
2313
|
+
var loadDatabase2 = async (dbPath) => {
|
|
2314
|
+
if (isBun2()) {
|
|
2315
|
+
throw new Error(
|
|
2316
|
+
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
2317
|
+
);
|
|
2318
|
+
}
|
|
2319
|
+
try {
|
|
2320
|
+
if (!cachedDbModule2) {
|
|
2321
|
+
cachedDbModule2 = (await import('better-sqlite3')).default;
|
|
2322
|
+
}
|
|
2323
|
+
return new cachedDbModule2(dbPath);
|
|
2324
|
+
} catch (error) {
|
|
2325
|
+
throw new Error(
|
|
2326
|
+
`better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
var buildWhereClause = (options = {}) => {
|
|
2331
|
+
const clauses = [];
|
|
2332
|
+
const params = [];
|
|
2333
|
+
if (typeof options.before === "number") {
|
|
2334
|
+
clauses.push("timestamp < ?");
|
|
2335
|
+
params.push(options.before);
|
|
2336
|
+
}
|
|
2337
|
+
if (typeof options.after === "number") {
|
|
2338
|
+
clauses.push("timestamp > ?");
|
|
2339
|
+
params.push(options.after);
|
|
2340
|
+
}
|
|
2341
|
+
if (options.modelId) {
|
|
2342
|
+
clauses.push("model_id = ?");
|
|
2343
|
+
params.push(options.modelId);
|
|
2344
|
+
}
|
|
2345
|
+
if (options.baseUrl) {
|
|
2346
|
+
clauses.push("base_url = ?");
|
|
2347
|
+
params.push(normalizeBaseUrl2(options.baseUrl));
|
|
2348
|
+
}
|
|
2349
|
+
if (options.sessionId) {
|
|
2350
|
+
clauses.push("session_id = ?");
|
|
2351
|
+
params.push(options.sessionId);
|
|
2352
|
+
}
|
|
2353
|
+
if (options.client) {
|
|
2354
|
+
clauses.push("client = ?");
|
|
2355
|
+
params.push(options.client);
|
|
2356
|
+
}
|
|
2357
|
+
return {
|
|
2358
|
+
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
2359
|
+
params
|
|
2360
|
+
};
|
|
2361
|
+
};
|
|
2362
|
+
var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
2363
|
+
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2364
|
+
const tableName = options.tableName || "usage_tracking";
|
|
2365
|
+
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2366
|
+
let db;
|
|
2367
|
+
let insertStmt;
|
|
2368
|
+
let migrationComplete = false;
|
|
2369
|
+
const initDb = async () => {
|
|
2370
|
+
if (!db) {
|
|
2371
|
+
db = await loadDatabase2(dbPath);
|
|
2372
|
+
db.exec(`
|
|
2373
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2374
|
+
id TEXT PRIMARY KEY,
|
|
2375
|
+
timestamp INTEGER NOT NULL,
|
|
2376
|
+
model_id TEXT NOT NULL,
|
|
2377
|
+
base_url TEXT NOT NULL,
|
|
2378
|
+
request_id TEXT NOT NULL,
|
|
2379
|
+
cost REAL NOT NULL,
|
|
2380
|
+
sats_cost REAL NOT NULL,
|
|
2381
|
+
prompt_tokens INTEGER NOT NULL,
|
|
2382
|
+
completion_tokens INTEGER NOT NULL,
|
|
2383
|
+
total_tokens INTEGER NOT NULL,
|
|
2384
|
+
client TEXT,
|
|
2385
|
+
session_id TEXT,
|
|
2386
|
+
tags TEXT
|
|
2387
|
+
);
|
|
2388
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
2389
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
2390
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
2391
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
2392
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
2393
|
+
`);
|
|
2394
|
+
insertStmt = db.prepare(`
|
|
2395
|
+
INSERT OR REPLACE INTO ${tableName} (
|
|
2396
|
+
id, timestamp, model_id, base_url, request_id,
|
|
2397
|
+
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2398
|
+
client, session_id, tags
|
|
2399
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2400
|
+
`);
|
|
2401
|
+
}
|
|
2402
|
+
};
|
|
2403
|
+
const ensureInit = async () => {
|
|
2404
|
+
if (!db) {
|
|
2405
|
+
await initDb();
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
const appendOne = (entry) => {
|
|
2409
|
+
insertStmt.run(
|
|
2410
|
+
entry.id,
|
|
2411
|
+
entry.timestamp,
|
|
2412
|
+
entry.modelId,
|
|
2413
|
+
normalizeBaseUrl2(entry.baseUrl),
|
|
2414
|
+
entry.requestId,
|
|
2415
|
+
entry.cost,
|
|
2416
|
+
entry.satsCost,
|
|
2417
|
+
entry.promptTokens,
|
|
2418
|
+
entry.completionTokens,
|
|
2419
|
+
entry.totalTokens,
|
|
2420
|
+
entry.client ?? null,
|
|
2421
|
+
entry.sessionId ?? null,
|
|
2422
|
+
JSON.stringify(entry.tags ?? [])
|
|
2423
|
+
);
|
|
2424
|
+
};
|
|
2425
|
+
const ensureMigrated = async () => {
|
|
2426
|
+
if (!legacyStorageDriver || migrationComplete) return;
|
|
2427
|
+
const migrated = await legacyStorageDriver.getItem(
|
|
2428
|
+
MIGRATION_MARKER_KEY2,
|
|
2429
|
+
false
|
|
2430
|
+
);
|
|
2431
|
+
if (migrated) {
|
|
2432
|
+
migrationComplete = true;
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2436
|
+
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2437
|
+
[]
|
|
2438
|
+
);
|
|
2439
|
+
for (const entry of legacyEntries) {
|
|
2440
|
+
appendOne(entry);
|
|
2441
|
+
}
|
|
2442
|
+
if (legacyEntries.length > 0) {
|
|
2443
|
+
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2444
|
+
}
|
|
2445
|
+
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
2446
|
+
migrationComplete = true;
|
|
2447
|
+
};
|
|
2448
|
+
const mapRow = (row) => ({
|
|
2449
|
+
id: row.id,
|
|
2450
|
+
timestamp: row.timestamp,
|
|
2451
|
+
modelId: row.model_id,
|
|
2452
|
+
baseUrl: row.base_url,
|
|
2453
|
+
requestId: row.request_id,
|
|
2454
|
+
cost: row.cost,
|
|
2455
|
+
satsCost: row.sats_cost,
|
|
2456
|
+
promptTokens: row.prompt_tokens,
|
|
2457
|
+
completionTokens: row.completion_tokens,
|
|
2458
|
+
totalTokens: row.total_tokens,
|
|
2459
|
+
client: row.client ?? void 0,
|
|
2460
|
+
sessionId: row.session_id ?? void 0,
|
|
2461
|
+
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
2462
|
+
});
|
|
2463
|
+
return {
|
|
2464
|
+
async migrate() {
|
|
2465
|
+
await ensureInit();
|
|
2466
|
+
await ensureMigrated();
|
|
2467
|
+
},
|
|
2468
|
+
async append(entry) {
|
|
2469
|
+
await ensureInit();
|
|
2470
|
+
await ensureMigrated();
|
|
2471
|
+
appendOne(entry);
|
|
2472
|
+
},
|
|
2473
|
+
async appendMany(entries) {
|
|
2474
|
+
await ensureInit();
|
|
2475
|
+
await ensureMigrated();
|
|
2476
|
+
for (const entry of entries) {
|
|
2477
|
+
appendOne(entry);
|
|
2478
|
+
}
|
|
2479
|
+
},
|
|
2480
|
+
async list(options2 = {}) {
|
|
2481
|
+
await ensureInit();
|
|
2482
|
+
await ensureMigrated();
|
|
2483
|
+
const { sql, params } = buildWhereClause(options2);
|
|
2484
|
+
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
2485
|
+
const stmt = db.prepare(
|
|
2486
|
+
`SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`
|
|
2487
|
+
);
|
|
2488
|
+
const rows = stmt.all(
|
|
2489
|
+
...typeof options2.limit === "number" ? [...params, options2.limit] : params
|
|
2490
|
+
);
|
|
2491
|
+
return rows.map(mapRow);
|
|
2492
|
+
},
|
|
2493
|
+
async count(options2 = {}) {
|
|
2494
|
+
await ensureInit();
|
|
2495
|
+
await ensureMigrated();
|
|
2496
|
+
const { sql, params } = buildWhereClause(options2);
|
|
2497
|
+
const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
|
|
2498
|
+
const row = stmt.get(...params);
|
|
2499
|
+
return Number(row?.count ?? 0);
|
|
2500
|
+
},
|
|
2501
|
+
async deleteOlderThan(timestamp) {
|
|
2502
|
+
await ensureInit();
|
|
2503
|
+
await ensureMigrated();
|
|
2504
|
+
const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
|
|
2505
|
+
const result = stmt.run(timestamp);
|
|
2506
|
+
return result.changes;
|
|
2507
|
+
},
|
|
2508
|
+
async clear() {
|
|
2509
|
+
await ensureInit();
|
|
2510
|
+
await ensureMigrated();
|
|
2511
|
+
db.prepare(`DELETE FROM ${tableName}`).run();
|
|
2512
|
+
}
|
|
2513
|
+
};
|
|
2514
|
+
};
|
|
2515
|
+
|
|
2516
|
+
// storage/usageTracking/bunSqlite.ts
|
|
2517
|
+
var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
|
|
2518
|
+
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2519
|
+
var buildWhereClause2 = (options = {}) => {
|
|
2520
|
+
const clauses = [];
|
|
2521
|
+
const params = [];
|
|
2522
|
+
if (typeof options.before === "number") {
|
|
2523
|
+
clauses.push("timestamp < ?");
|
|
2524
|
+
params.push(options.before);
|
|
2525
|
+
}
|
|
2526
|
+
if (typeof options.after === "number") {
|
|
2527
|
+
clauses.push("timestamp > ?");
|
|
2528
|
+
params.push(options.after);
|
|
2529
|
+
}
|
|
2530
|
+
if (options.modelId) {
|
|
2531
|
+
clauses.push("model_id = ?");
|
|
2532
|
+
params.push(options.modelId);
|
|
2533
|
+
}
|
|
2534
|
+
if (options.baseUrl) {
|
|
2535
|
+
clauses.push("base_url = ?");
|
|
2536
|
+
params.push(normalizeBaseUrl3(options.baseUrl));
|
|
2537
|
+
}
|
|
2538
|
+
if (options.sessionId) {
|
|
2539
|
+
clauses.push("session_id = ?");
|
|
2540
|
+
params.push(options.sessionId);
|
|
2541
|
+
}
|
|
2542
|
+
if (options.client) {
|
|
2543
|
+
clauses.push("client = ?");
|
|
2544
|
+
params.push(options.client);
|
|
2545
|
+
}
|
|
2546
|
+
return {
|
|
2547
|
+
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
2548
|
+
params
|
|
2549
|
+
};
|
|
2550
|
+
};
|
|
2551
|
+
var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
2552
|
+
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2553
|
+
const tableName = options.tableName || "usage_tracking";
|
|
2554
|
+
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2555
|
+
const SQLiteDatabase = options.sqlite?.Database;
|
|
2556
|
+
let migrationPromise = null;
|
|
2557
|
+
if (!SQLiteDatabase) {
|
|
2558
|
+
throw new Error(
|
|
2559
|
+
"Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
const db = new SQLiteDatabase(dbPath);
|
|
2563
|
+
db.run(`
|
|
2564
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2565
|
+
id TEXT PRIMARY KEY,
|
|
2566
|
+
timestamp INTEGER NOT NULL,
|
|
2567
|
+
model_id TEXT NOT NULL,
|
|
2568
|
+
base_url TEXT NOT NULL,
|
|
2569
|
+
request_id TEXT NOT NULL,
|
|
2570
|
+
cost REAL NOT NULL,
|
|
2571
|
+
sats_cost REAL NOT NULL,
|
|
2572
|
+
prompt_tokens INTEGER NOT NULL,
|
|
2573
|
+
completion_tokens INTEGER NOT NULL,
|
|
2574
|
+
total_tokens INTEGER NOT NULL,
|
|
2575
|
+
client TEXT,
|
|
2576
|
+
session_id TEXT,
|
|
2577
|
+
tags TEXT
|
|
2578
|
+
)
|
|
2579
|
+
`);
|
|
2580
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
|
|
2581
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
|
|
2582
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
|
|
2583
|
+
const appendOne = (entry) => {
|
|
2584
|
+
db.query(`
|
|
2585
|
+
INSERT OR REPLACE INTO ${tableName} (
|
|
2586
|
+
id, timestamp, model_id, base_url, request_id,
|
|
2587
|
+
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2588
|
+
client, session_id, tags
|
|
2589
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2590
|
+
`).run(
|
|
2591
|
+
entry.id,
|
|
2592
|
+
entry.timestamp,
|
|
2593
|
+
entry.modelId,
|
|
2594
|
+
normalizeBaseUrl3(entry.baseUrl),
|
|
2595
|
+
entry.requestId,
|
|
2596
|
+
entry.cost,
|
|
2597
|
+
entry.satsCost,
|
|
2598
|
+
entry.promptTokens,
|
|
2599
|
+
entry.completionTokens,
|
|
2600
|
+
entry.totalTokens,
|
|
2601
|
+
entry.client ?? null,
|
|
2602
|
+
entry.sessionId ?? null,
|
|
2603
|
+
JSON.stringify(entry.tags ?? [])
|
|
2604
|
+
);
|
|
2605
|
+
};
|
|
2606
|
+
const mapRow = (row) => ({
|
|
2607
|
+
id: row.id,
|
|
2608
|
+
timestamp: row.timestamp,
|
|
2609
|
+
modelId: row.model_id,
|
|
2610
|
+
baseUrl: row.base_url,
|
|
2611
|
+
requestId: row.request_id,
|
|
2612
|
+
cost: row.cost,
|
|
2613
|
+
satsCost: row.sats_cost,
|
|
2614
|
+
promptTokens: row.prompt_tokens,
|
|
2615
|
+
completionTokens: row.completion_tokens,
|
|
2616
|
+
totalTokens: row.total_tokens,
|
|
2617
|
+
client: row.client ?? void 0,
|
|
2618
|
+
sessionId: row.session_id ?? void 0,
|
|
2619
|
+
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
2620
|
+
});
|
|
2621
|
+
const ensureMigrated = async () => {
|
|
2622
|
+
if (!legacyStorageDriver) return;
|
|
2623
|
+
if (!migrationPromise) {
|
|
2624
|
+
migrationPromise = (async () => {
|
|
2625
|
+
const migrated = await legacyStorageDriver.getItem(
|
|
2626
|
+
MIGRATION_MARKER_KEY3,
|
|
2627
|
+
false
|
|
2628
|
+
);
|
|
2629
|
+
if (migrated) return;
|
|
2630
|
+
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2631
|
+
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2632
|
+
[]
|
|
2633
|
+
);
|
|
2634
|
+
if (legacyEntries.length > 0) {
|
|
2635
|
+
for (const entry of legacyEntries) {
|
|
2636
|
+
appendOne(entry);
|
|
2637
|
+
}
|
|
2638
|
+
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2639
|
+
}
|
|
2640
|
+
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
|
|
2641
|
+
})();
|
|
2642
|
+
}
|
|
2643
|
+
await migrationPromise;
|
|
2644
|
+
};
|
|
2645
|
+
return {
|
|
2646
|
+
async migrate() {
|
|
2647
|
+
await ensureMigrated();
|
|
2648
|
+
},
|
|
2649
|
+
async append(entry) {
|
|
2650
|
+
await ensureMigrated();
|
|
2651
|
+
appendOne(entry);
|
|
2652
|
+
},
|
|
2653
|
+
async appendMany(entries) {
|
|
2654
|
+
await ensureMigrated();
|
|
2655
|
+
for (const entry of entries) {
|
|
2656
|
+
appendOne(entry);
|
|
2657
|
+
}
|
|
2658
|
+
},
|
|
2659
|
+
async list(options2 = {}) {
|
|
2660
|
+
await ensureMigrated();
|
|
2661
|
+
const { sql, params } = buildWhereClause2(options2);
|
|
2662
|
+
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
2663
|
+
const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
|
|
2664
|
+
let rows;
|
|
2665
|
+
if (typeof options2.limit === "number") {
|
|
2666
|
+
rows = db.query(query).all(...params, options2.limit);
|
|
2667
|
+
} else {
|
|
2668
|
+
rows = db.query(query).all(...params);
|
|
2669
|
+
}
|
|
2670
|
+
return rows.map(mapRow);
|
|
2671
|
+
},
|
|
2672
|
+
async count(options2 = {}) {
|
|
2673
|
+
const { sql, params } = buildWhereClause2(options2);
|
|
2674
|
+
const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
|
|
2675
|
+
const row = db.query(query).get(...params);
|
|
2676
|
+
return Number(row?.count ?? 0);
|
|
2677
|
+
},
|
|
2678
|
+
async deleteOlderThan(timestamp) {
|
|
2679
|
+
await ensureMigrated();
|
|
2680
|
+
const before = timestamp;
|
|
2681
|
+
const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
|
|
2682
|
+
return result.changes ?? 0;
|
|
2683
|
+
},
|
|
2684
|
+
async clear() {
|
|
2685
|
+
await ensureMigrated();
|
|
2686
|
+
db.query(`DELETE FROM ${tableName}`).run();
|
|
2687
|
+
}
|
|
2688
|
+
};
|
|
2689
|
+
};
|
|
2690
|
+
|
|
2691
|
+
// storage/usageTracking/memory.ts
|
|
2692
|
+
var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2693
|
+
var matchesFilters2 = (entry, options = {}) => {
|
|
2694
|
+
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
2695
|
+
return false;
|
|
2696
|
+
}
|
|
2697
|
+
if (typeof options.after === "number" && entry.timestamp <= options.after) {
|
|
2698
|
+
return false;
|
|
2699
|
+
}
|
|
2700
|
+
if (options.modelId && entry.modelId !== options.modelId) {
|
|
2701
|
+
return false;
|
|
2702
|
+
}
|
|
2703
|
+
if (options.baseUrl && normalizeBaseUrl4(entry.baseUrl) !== normalizeBaseUrl4(options.baseUrl)) {
|
|
2704
|
+
return false;
|
|
2705
|
+
}
|
|
2706
|
+
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
2707
|
+
return false;
|
|
2708
|
+
}
|
|
2709
|
+
if (options.client && entry.client !== options.client) {
|
|
2710
|
+
return false;
|
|
2711
|
+
}
|
|
2712
|
+
return true;
|
|
2713
|
+
};
|
|
2714
|
+
var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
2715
|
+
const store = /* @__PURE__ */ new Map();
|
|
2716
|
+
for (const entry of seed) {
|
|
2717
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
|
|
2718
|
+
}
|
|
2719
|
+
return {
|
|
2720
|
+
async migrate() {
|
|
2721
|
+
return;
|
|
2722
|
+
},
|
|
2723
|
+
async append(entry) {
|
|
2724
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
|
|
2725
|
+
},
|
|
2726
|
+
async appendMany(entries) {
|
|
2727
|
+
for (const entry of entries) {
|
|
2728
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
|
|
2729
|
+
}
|
|
2730
|
+
},
|
|
2731
|
+
async list(options = {}) {
|
|
2732
|
+
const entries = [...store.values()].filter((entry) => matchesFilters2(entry, options)).sort((a, b) => b.timestamp - a.timestamp);
|
|
2733
|
+
if (typeof options.limit === "number") {
|
|
2734
|
+
return entries.slice(0, options.limit);
|
|
2735
|
+
}
|
|
2736
|
+
return entries;
|
|
2737
|
+
},
|
|
2738
|
+
async count(options = {}) {
|
|
2739
|
+
return (await this.list(options)).length;
|
|
2740
|
+
},
|
|
2741
|
+
async deleteOlderThan(timestamp) {
|
|
2742
|
+
let deleted = 0;
|
|
2743
|
+
for (const [id, entry] of store.entries()) {
|
|
2744
|
+
if (entry.timestamp < timestamp) {
|
|
2745
|
+
store.delete(id);
|
|
2746
|
+
deleted += 1;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
return deleted;
|
|
2750
|
+
},
|
|
2751
|
+
async clear() {
|
|
2752
|
+
store.clear();
|
|
2753
|
+
}
|
|
2754
|
+
};
|
|
2755
|
+
};
|
|
2756
|
+
var normalizeBaseUrl5 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2757
|
+
var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
2758
|
+
modelsFromAllProviders: {},
|
|
2759
|
+
lastUsedModel: null,
|
|
2760
|
+
baseUrlsList: [],
|
|
2761
|
+
lastBaseUrlsUpdate: null,
|
|
2762
|
+
disabledProviders: [],
|
|
2763
|
+
mintsFromAllProviders: {},
|
|
2764
|
+
infoFromAllProviders: {},
|
|
2765
|
+
lastModelsUpdate: {},
|
|
2766
|
+
apiKeys: [],
|
|
2767
|
+
childKeys: [],
|
|
2768
|
+
xcashuTokens: {},
|
|
2769
|
+
routstr21Models: [],
|
|
2770
|
+
lastRoutstr21ModelsUpdate: null,
|
|
2771
|
+
cachedReceiveTokens: [],
|
|
2772
|
+
clientIds: [],
|
|
2773
|
+
setModelsFromAllProviders: (value) => {
|
|
2774
|
+
const normalized = {};
|
|
2775
|
+
for (const [baseUrl, models] of Object.entries(value)) {
|
|
2776
|
+
normalized[normalizeBaseUrl5(baseUrl)] = models;
|
|
2777
|
+
}
|
|
2778
|
+
void driver.setItem(
|
|
2779
|
+
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
2780
|
+
normalized
|
|
2781
|
+
);
|
|
2782
|
+
set({ modelsFromAllProviders: normalized });
|
|
2783
|
+
},
|
|
2784
|
+
setLastUsedModel: (value) => {
|
|
2785
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, value);
|
|
2786
|
+
set({ lastUsedModel: value });
|
|
2787
|
+
},
|
|
2788
|
+
setBaseUrlsList: (value) => {
|
|
2789
|
+
const normalized = value.map((url) => normalizeBaseUrl5(url));
|
|
2790
|
+
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
2791
|
+
set({ baseUrlsList: normalized });
|
|
2792
|
+
},
|
|
2793
|
+
setBaseUrlsLastUpdate: (value) => {
|
|
2794
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, value);
|
|
2795
|
+
set({ lastBaseUrlsUpdate: value });
|
|
2796
|
+
},
|
|
2797
|
+
setDisabledProviders: (value) => {
|
|
2798
|
+
const normalized = value.map((url) => normalizeBaseUrl5(url));
|
|
2799
|
+
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
2800
|
+
set({ disabledProviders: normalized });
|
|
2801
|
+
},
|
|
2802
|
+
setMintsFromAllProviders: (value) => {
|
|
2803
|
+
const normalized = {};
|
|
2804
|
+
for (const [baseUrl, mints] of Object.entries(value)) {
|
|
2805
|
+
normalized[normalizeBaseUrl5(baseUrl)] = mints.map(
|
|
2806
|
+
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
2807
|
+
);
|
|
2808
|
+
}
|
|
2809
|
+
void driver.setItem(
|
|
2810
|
+
SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
|
|
2811
|
+
normalized
|
|
2812
|
+
);
|
|
2813
|
+
set({ mintsFromAllProviders: normalized });
|
|
2814
|
+
},
|
|
2815
|
+
setInfoFromAllProviders: (value) => {
|
|
2816
|
+
const normalized = {};
|
|
2817
|
+
for (const [baseUrl, info] of Object.entries(value)) {
|
|
2818
|
+
normalized[normalizeBaseUrl5(baseUrl)] = info;
|
|
2819
|
+
}
|
|
2820
|
+
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
2821
|
+
set({ infoFromAllProviders: normalized });
|
|
2822
|
+
},
|
|
2823
|
+
setLastModelsUpdate: (value) => {
|
|
2824
|
+
const normalized = {};
|
|
2825
|
+
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
2826
|
+
normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
|
|
2827
|
+
}
|
|
2828
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
2829
|
+
set({ lastModelsUpdate: normalized });
|
|
2830
|
+
},
|
|
2831
|
+
setApiKeys: (value) => {
|
|
2832
|
+
set((state) => {
|
|
2833
|
+
const updates = typeof value === "function" ? value(state.apiKeys) : value;
|
|
2834
|
+
const normalized = updates.map((entry) => ({
|
|
2835
|
+
...entry,
|
|
2836
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
2837
|
+
balance: entry.balance ?? 0,
|
|
2838
|
+
lastUsed: entry.lastUsed ?? null
|
|
2839
|
+
}));
|
|
2840
|
+
void driver.setItem(SDK_STORAGE_KEYS.API_KEYS, normalized);
|
|
2841
|
+
return { apiKeys: normalized };
|
|
2842
|
+
});
|
|
2843
|
+
},
|
|
2844
|
+
setChildKeys: (value) => {
|
|
2845
|
+
set((state) => {
|
|
2846
|
+
const updates = typeof value === "function" ? value(state.childKeys) : value;
|
|
2847
|
+
const normalized = updates.map((entry) => ({
|
|
2848
|
+
parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
|
|
2849
|
+
childKey: entry.childKey,
|
|
2850
|
+
balance: entry.balance ?? 0,
|
|
2851
|
+
balanceLimit: entry.balanceLimit,
|
|
2852
|
+
validityDate: entry.validityDate,
|
|
2853
|
+
createdAt: entry.createdAt ?? Date.now()
|
|
2854
|
+
}));
|
|
2855
|
+
void driver.setItem(SDK_STORAGE_KEYS.CHILD_KEYS, normalized);
|
|
2856
|
+
return { childKeys: normalized };
|
|
2857
|
+
});
|
|
2858
|
+
},
|
|
2859
|
+
setXcashuTokens: (value) => {
|
|
2860
|
+
const normalized = {};
|
|
2861
|
+
for (const [baseUrl, tokens] of Object.entries(value)) {
|
|
2862
|
+
normalized[normalizeBaseUrl5(baseUrl)] = tokens.map((entry) => ({
|
|
2863
|
+
...entry,
|
|
2864
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
2865
|
+
createdAt: entry.createdAt ?? Date.now(),
|
|
2866
|
+
tryCount: entry.tryCount ?? 0
|
|
2867
|
+
}));
|
|
2868
|
+
}
|
|
2869
|
+
void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, normalized);
|
|
2870
|
+
set({ xcashuTokens: normalized });
|
|
2871
|
+
},
|
|
2872
|
+
updateXcashuTokenTryCount: (token, tryCount) => {
|
|
2873
|
+
const currentTokens = get().xcashuTokens;
|
|
2874
|
+
const updatedTokens = {};
|
|
2875
|
+
for (const [baseUrl, tokens] of Object.entries(currentTokens)) {
|
|
2876
|
+
updatedTokens[baseUrl] = tokens.map(
|
|
2877
|
+
(entry) => entry.token === token ? { ...entry, tryCount } : entry
|
|
2878
|
+
);
|
|
2879
|
+
}
|
|
2880
|
+
void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, updatedTokens);
|
|
2881
|
+
set({ xcashuTokens: updatedTokens });
|
|
2882
|
+
},
|
|
2883
|
+
setRoutstr21Models: (value) => {
|
|
2884
|
+
void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
|
|
2885
|
+
set({ routstr21Models: value });
|
|
2886
|
+
},
|
|
2887
|
+
setRoutstr21ModelsLastUpdate: (value) => {
|
|
2888
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE, value);
|
|
2889
|
+
set({ lastRoutstr21ModelsUpdate: value });
|
|
2890
|
+
},
|
|
2891
|
+
setCachedReceiveTokens: (value) => {
|
|
2892
|
+
const normalized = value.map((entry) => ({
|
|
2893
|
+
token: entry.token,
|
|
2894
|
+
amount: entry.amount,
|
|
2895
|
+
unit: entry.unit || "sat",
|
|
2896
|
+
createdAt: entry.createdAt ?? Date.now()
|
|
2897
|
+
}));
|
|
2898
|
+
void driver.setItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, normalized);
|
|
2899
|
+
set({ cachedReceiveTokens: normalized });
|
|
2900
|
+
},
|
|
2901
|
+
setClientIds: (value) => {
|
|
2902
|
+
set((state) => {
|
|
2903
|
+
const updates = typeof value === "function" ? value(state.clientIds) : value;
|
|
2904
|
+
const normalized = updates.map((entry) => ({
|
|
2905
|
+
...entry,
|
|
2906
|
+
createdAt: entry.createdAt ?? Date.now(),
|
|
2907
|
+
lastUsed: entry.lastUsed ?? null
|
|
2908
|
+
}));
|
|
2909
|
+
void driver.setItem(SDK_STORAGE_KEYS.CLIENT_IDS, normalized);
|
|
2910
|
+
return { clientIds: normalized };
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
}));
|
|
2914
|
+
var hydrateStoreFromDriver = async (store, driver) => {
|
|
2915
|
+
const [
|
|
2916
|
+
rawModels,
|
|
2917
|
+
lastUsedModel,
|
|
2918
|
+
rawBaseUrls,
|
|
2919
|
+
lastBaseUrlsUpdate,
|
|
2920
|
+
rawDisabledProviders,
|
|
2921
|
+
rawMints,
|
|
2922
|
+
rawInfo,
|
|
2923
|
+
rawLastModelsUpdate,
|
|
2924
|
+
rawApiKeys,
|
|
2925
|
+
rawChildKeys,
|
|
2926
|
+
rawXcashuTokens,
|
|
2927
|
+
rawRoutstr21Models,
|
|
2928
|
+
rawLastRoutstr21ModelsUpdate,
|
|
2929
|
+
rawCachedReceiveTokens,
|
|
2930
|
+
rawClientIds
|
|
2931
|
+
] = await Promise.all([
|
|
2932
|
+
driver.getItem(
|
|
2933
|
+
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
2934
|
+
{}
|
|
2935
|
+
),
|
|
2936
|
+
driver.getItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, null),
|
|
2937
|
+
driver.getItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, []),
|
|
2938
|
+
driver.getItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, null),
|
|
2939
|
+
driver.getItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, []),
|
|
2940
|
+
driver.getItem(
|
|
2941
|
+
SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
|
|
2942
|
+
{}
|
|
2943
|
+
),
|
|
2944
|
+
driver.getItem(
|
|
2945
|
+
SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS,
|
|
2946
|
+
{}
|
|
2947
|
+
),
|
|
2948
|
+
driver.getItem(
|
|
2949
|
+
SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
|
|
2950
|
+
{}
|
|
2951
|
+
),
|
|
2952
|
+
driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []),
|
|
2953
|
+
driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []),
|
|
2954
|
+
driver.getItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, {}),
|
|
2955
|
+
driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
|
|
2956
|
+
driver.getItem(
|
|
2957
|
+
SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
|
|
2958
|
+
null
|
|
2959
|
+
),
|
|
2960
|
+
driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
|
|
2961
|
+
driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
|
|
2962
|
+
]);
|
|
2963
|
+
const modelsFromAllProviders = Object.fromEntries(
|
|
2964
|
+
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
2965
|
+
normalizeBaseUrl5(baseUrl),
|
|
2966
|
+
models
|
|
2967
|
+
])
|
|
2968
|
+
);
|
|
2969
|
+
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl5(url));
|
|
2970
|
+
const disabledProviders = rawDisabledProviders.map(
|
|
2971
|
+
(url) => normalizeBaseUrl5(url)
|
|
2972
|
+
);
|
|
2973
|
+
const mintsFromAllProviders = Object.fromEntries(
|
|
2974
|
+
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
2975
|
+
normalizeBaseUrl5(baseUrl),
|
|
2976
|
+
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
2977
|
+
])
|
|
2978
|
+
);
|
|
2979
|
+
const infoFromAllProviders = Object.fromEntries(
|
|
2980
|
+
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
2981
|
+
normalizeBaseUrl5(baseUrl),
|
|
2982
|
+
info
|
|
2983
|
+
])
|
|
2984
|
+
);
|
|
2985
|
+
const lastModelsUpdate = Object.fromEntries(
|
|
2986
|
+
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
2987
|
+
normalizeBaseUrl5(baseUrl),
|
|
2988
|
+
timestamp
|
|
2989
|
+
])
|
|
2990
|
+
);
|
|
2991
|
+
const apiKeys = rawApiKeys.map((entry) => ({
|
|
2992
|
+
...entry,
|
|
2993
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
2994
|
+
balance: entry.balance ?? 0,
|
|
2995
|
+
lastUsed: entry.lastUsed ?? null
|
|
2996
|
+
}));
|
|
2997
|
+
const childKeys = rawChildKeys.map((entry) => ({
|
|
2998
|
+
parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
|
|
2999
|
+
childKey: entry.childKey,
|
|
3000
|
+
balance: entry.balance ?? 0,
|
|
3001
|
+
balanceLimit: entry.balanceLimit,
|
|
3002
|
+
validityDate: entry.validityDate,
|
|
3003
|
+
createdAt: entry.createdAt ?? Date.now()
|
|
3004
|
+
}));
|
|
3005
|
+
const xcashuTokens = Object.fromEntries(
|
|
3006
|
+
Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
|
|
3007
|
+
normalizeBaseUrl5(baseUrl),
|
|
3008
|
+
tokens.map((entry) => ({
|
|
3009
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
3010
|
+
token: entry.token,
|
|
3011
|
+
createdAt: entry.createdAt ?? Date.now(),
|
|
3012
|
+
tryCount: entry.tryCount ?? 0
|
|
3013
|
+
}))
|
|
3014
|
+
])
|
|
3015
|
+
);
|
|
3016
|
+
const routstr21Models = rawRoutstr21Models;
|
|
3017
|
+
const lastRoutstr21ModelsUpdate = rawLastRoutstr21ModelsUpdate;
|
|
3018
|
+
const cachedReceiveTokens = rawCachedReceiveTokens?.map((entry) => ({
|
|
3019
|
+
token: entry.token,
|
|
3020
|
+
amount: entry.amount,
|
|
3021
|
+
unit: entry.unit || "sat",
|
|
3022
|
+
createdAt: entry.createdAt ?? Date.now()
|
|
3023
|
+
}));
|
|
3024
|
+
const clientIds = rawClientIds.map((entry) => ({
|
|
3025
|
+
...entry,
|
|
3026
|
+
createdAt: entry.createdAt ?? Date.now(),
|
|
3027
|
+
lastUsed: entry.lastUsed ?? null
|
|
3028
|
+
}));
|
|
3029
|
+
store.setState({
|
|
3030
|
+
modelsFromAllProviders,
|
|
3031
|
+
lastUsedModel,
|
|
3032
|
+
baseUrlsList,
|
|
3033
|
+
lastBaseUrlsUpdate,
|
|
3034
|
+
disabledProviders,
|
|
3035
|
+
mintsFromAllProviders,
|
|
3036
|
+
infoFromAllProviders,
|
|
3037
|
+
lastModelsUpdate,
|
|
3038
|
+
apiKeys,
|
|
3039
|
+
childKeys,
|
|
3040
|
+
xcashuTokens,
|
|
3041
|
+
routstr21Models,
|
|
3042
|
+
lastRoutstr21ModelsUpdate,
|
|
3043
|
+
cachedReceiveTokens,
|
|
3044
|
+
clientIds
|
|
3045
|
+
});
|
|
3046
|
+
};
|
|
3047
|
+
var createSdkStore = ({
|
|
3048
|
+
driver
|
|
3049
|
+
}) => {
|
|
3050
|
+
const store = createEmptyStore(driver);
|
|
3051
|
+
return {
|
|
3052
|
+
store,
|
|
3053
|
+
hydrate: hydrateStoreFromDriver(store, driver)
|
|
3054
|
+
};
|
|
3055
|
+
};
|
|
3056
|
+
|
|
3057
|
+
// storage/index.ts
|
|
3058
|
+
var isBrowser2 = () => {
|
|
3059
|
+
try {
|
|
3060
|
+
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
3061
|
+
} catch {
|
|
3062
|
+
return false;
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
var isNode = () => {
|
|
3066
|
+
try {
|
|
3067
|
+
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
3068
|
+
} catch {
|
|
3069
|
+
return false;
|
|
3070
|
+
}
|
|
3071
|
+
};
|
|
3072
|
+
var defaultDriver = null;
|
|
3073
|
+
var isBun3 = () => {
|
|
3074
|
+
return typeof process.versions.bun !== "undefined";
|
|
3075
|
+
};
|
|
3076
|
+
var getDefaultSdkDriver = () => {
|
|
3077
|
+
if (defaultDriver) return defaultDriver;
|
|
3078
|
+
if (isBrowser2()) {
|
|
3079
|
+
defaultDriver = localStorageDriver;
|
|
3080
|
+
return defaultDriver;
|
|
3081
|
+
}
|
|
3082
|
+
if (isBun3()) {
|
|
3083
|
+
defaultDriver = createMemoryDriver();
|
|
3084
|
+
return defaultDriver;
|
|
3085
|
+
}
|
|
3086
|
+
if (isNode()) {
|
|
3087
|
+
defaultDriver = createSqliteDriver();
|
|
3088
|
+
return defaultDriver;
|
|
3089
|
+
}
|
|
3090
|
+
defaultDriver = createMemoryDriver();
|
|
3091
|
+
return defaultDriver;
|
|
3092
|
+
};
|
|
3093
|
+
var defaultStore = null;
|
|
3094
|
+
var defaultUsageTrackingDriver = null;
|
|
3095
|
+
var getDefaultSdkStore = () => {
|
|
3096
|
+
if (!defaultStore) {
|
|
3097
|
+
defaultStore = createSdkStore({ driver: getDefaultSdkDriver() });
|
|
3098
|
+
}
|
|
3099
|
+
return defaultStore.hydrate.then(() => defaultStore.store);
|
|
3100
|
+
};
|
|
3101
|
+
var getDefaultUsageTrackingDriver = () => {
|
|
3102
|
+
if (defaultUsageTrackingDriver) return defaultUsageTrackingDriver;
|
|
3103
|
+
const storageDriver = getDefaultSdkDriver();
|
|
3104
|
+
if (isBrowser2()) {
|
|
3105
|
+
defaultUsageTrackingDriver = createIndexedDBUsageTrackingDriver({
|
|
3106
|
+
legacyStorageDriver: storageDriver
|
|
3107
|
+
});
|
|
3108
|
+
return defaultUsageTrackingDriver;
|
|
3109
|
+
}
|
|
3110
|
+
if (isBun3()) {
|
|
3111
|
+
defaultUsageTrackingDriver = createBunSqliteUsageTrackingDriver();
|
|
3112
|
+
return defaultUsageTrackingDriver;
|
|
3113
|
+
}
|
|
3114
|
+
if (isNode()) {
|
|
3115
|
+
defaultUsageTrackingDriver = createSqliteUsageTrackingDriver({
|
|
3116
|
+
legacyStorageDriver: storageDriver
|
|
3117
|
+
});
|
|
3118
|
+
return defaultUsageTrackingDriver;
|
|
3119
|
+
}
|
|
3120
|
+
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
3121
|
+
return defaultUsageTrackingDriver;
|
|
3122
|
+
};
|
|
3123
|
+
function createSSEParserTransform(onUsage, onResponseId) {
|
|
3124
|
+
let buffer = "";
|
|
3125
|
+
let usageCaptured = false;
|
|
3126
|
+
let responseIdCaptured = false;
|
|
3127
|
+
const maybeCaptureUsageFromJson = (jsonText) => {
|
|
3128
|
+
try {
|
|
3129
|
+
const data = JSON.parse(jsonText);
|
|
3130
|
+
const responseId = data.id;
|
|
3131
|
+
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
3132
|
+
onResponseId?.(responseId.trim());
|
|
3133
|
+
responseIdCaptured = true;
|
|
3134
|
+
}
|
|
3135
|
+
const usage = extractUsageFromSSEJson(data);
|
|
3136
|
+
if (usage) {
|
|
3137
|
+
onUsage(usage);
|
|
3138
|
+
usageCaptured = true;
|
|
3139
|
+
}
|
|
3140
|
+
} catch {
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
const processLine = (self, line) => {
|
|
3144
|
+
const trimmed = line.trim();
|
|
3145
|
+
if (!trimmed) {
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
if (trimmed === "data: [DONE]" || trimmed === "[DONE]") {
|
|
3149
|
+
self.push("data: [DONE]\n\n");
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
if (trimmed.startsWith("data:")) {
|
|
3153
|
+
const dataStr = trimmed.startsWith("data: ") ? trimmed.slice(6) : trimmed.slice(5).trimStart();
|
|
3154
|
+
if (dataStr === "[DONE]") {
|
|
3155
|
+
self.push("data: [DONE]\n\n");
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
maybeCaptureUsageFromJson(dataStr);
|
|
3159
|
+
self.push(`data: ${dataStr}
|
|
3160
|
+
|
|
3161
|
+
`);
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
if (trimmed.startsWith("{")) {
|
|
3165
|
+
maybeCaptureUsageFromJson(trimmed);
|
|
3166
|
+
self.push(`data: ${trimmed}
|
|
3167
|
+
|
|
3168
|
+
`);
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
self.push(line + "\n");
|
|
3172
|
+
};
|
|
3173
|
+
return new stream.Transform({
|
|
3174
|
+
transform(chunk, encoding, callback) {
|
|
3175
|
+
buffer += chunk.toString();
|
|
3176
|
+
const lines = buffer.split(/\r?\n/);
|
|
3177
|
+
buffer = lines.pop() || "";
|
|
3178
|
+
for (const line of lines) {
|
|
3179
|
+
processLine(this, line);
|
|
3180
|
+
}
|
|
3181
|
+
callback();
|
|
3182
|
+
},
|
|
3183
|
+
flush(callback) {
|
|
3184
|
+
if (buffer.trim()) {
|
|
3185
|
+
processLine(this, buffer);
|
|
3186
|
+
}
|
|
3187
|
+
buffer = "";
|
|
3188
|
+
callback();
|
|
3189
|
+
}
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3192
|
+
var TOPUP_MARGIN = 1.2;
|
|
3193
|
+
var RoutstrClient = class {
|
|
3194
|
+
constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
|
|
3195
|
+
this.walletAdapter = walletAdapter;
|
|
3196
|
+
this.storageAdapter = storageAdapter;
|
|
3197
|
+
this.providerRegistry = providerRegistry;
|
|
3198
|
+
this.balanceManager = new BalanceManager(
|
|
3199
|
+
walletAdapter,
|
|
3200
|
+
storageAdapter,
|
|
3201
|
+
providerRegistry
|
|
3202
|
+
);
|
|
3203
|
+
this.cashuSpender = new CashuSpender(
|
|
3204
|
+
walletAdapter,
|
|
3205
|
+
storageAdapter,
|
|
3206
|
+
providerRegistry,
|
|
3207
|
+
this.balanceManager
|
|
3208
|
+
);
|
|
3209
|
+
this.streamProcessor = new StreamProcessor();
|
|
3210
|
+
this.providerManager = new ProviderManager(providerRegistry);
|
|
3211
|
+
this.alertLevel = alertLevel;
|
|
3212
|
+
this.mode = mode;
|
|
3213
|
+
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
3214
|
+
this.sdkStore = options.sdkStore;
|
|
3215
|
+
}
|
|
3216
|
+
cashuSpender;
|
|
3217
|
+
balanceManager;
|
|
3218
|
+
streamProcessor;
|
|
1996
3219
|
providerManager;
|
|
1997
3220
|
alertLevel;
|
|
1998
3221
|
mode;
|
|
1999
3222
|
debugLevel = "WARN";
|
|
3223
|
+
usageTrackingDriver;
|
|
3224
|
+
sdkStore;
|
|
2000
3225
|
/**
|
|
2001
3226
|
* Get the current client mode
|
|
2002
3227
|
*/
|
|
@@ -2062,6 +3287,86 @@ var RoutstrClient = class {
|
|
|
2062
3287
|
* requests and get responses back.
|
|
2063
3288
|
*/
|
|
2064
3289
|
async routeRequest(params) {
|
|
3290
|
+
const prepared = await this._prepareRoutedRequest(params);
|
|
3291
|
+
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3292
|
+
token: prepared.tokenUsed,
|
|
3293
|
+
baseUrl: prepared.baseUrlUsed,
|
|
3294
|
+
mintUrl: params.mintUrl,
|
|
3295
|
+
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3296
|
+
response: prepared.response,
|
|
3297
|
+
modelId: prepared.modelId,
|
|
3298
|
+
usage: prepared.capturedUsage,
|
|
3299
|
+
requestId: prepared.capturedResponseId,
|
|
3300
|
+
clientApiKey: prepared.clientApiKey
|
|
3301
|
+
});
|
|
3302
|
+
prepared.response.satsSpent = satsSpent;
|
|
3303
|
+
prepared.response.usage = prepared.capturedUsage;
|
|
3304
|
+
prepared.response.requestId = prepared.capturedResponseId;
|
|
3305
|
+
return prepared.response;
|
|
3306
|
+
}
|
|
3307
|
+
async routeRequestToNodeResponse(params) {
|
|
3308
|
+
const { res } = params;
|
|
3309
|
+
const prepared = await this._prepareRoutedRequest(params);
|
|
3310
|
+
res.statusCode = prepared.response.status;
|
|
3311
|
+
prepared.response.headers.forEach((value, key) => {
|
|
3312
|
+
res.setHeader(key, value);
|
|
3313
|
+
});
|
|
3314
|
+
const body = prepared.response.body;
|
|
3315
|
+
if (!body) {
|
|
3316
|
+
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3317
|
+
token: prepared.tokenUsed,
|
|
3318
|
+
baseUrl: prepared.baseUrlUsed,
|
|
3319
|
+
mintUrl: params.mintUrl,
|
|
3320
|
+
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3321
|
+
response: prepared.response,
|
|
3322
|
+
modelId: prepared.modelId,
|
|
3323
|
+
usage: prepared.capturedUsage,
|
|
3324
|
+
requestId: prepared.capturedResponseId,
|
|
3325
|
+
clientApiKey: prepared.clientApiKey
|
|
3326
|
+
});
|
|
3327
|
+
prepared.response.satsSpent = satsSpent;
|
|
3328
|
+
res.end();
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3331
|
+
const nodeReadable = stream.Readable.fromWeb(body);
|
|
3332
|
+
await new Promise((resolve, reject) => {
|
|
3333
|
+
let settled = false;
|
|
3334
|
+
const finish = async () => {
|
|
3335
|
+
if (settled) return;
|
|
3336
|
+
settled = true;
|
|
3337
|
+
try {
|
|
3338
|
+
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3339
|
+
token: prepared.tokenUsed,
|
|
3340
|
+
baseUrl: prepared.baseUrlUsed,
|
|
3341
|
+
mintUrl: params.mintUrl,
|
|
3342
|
+
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3343
|
+
response: prepared.response,
|
|
3344
|
+
modelId: prepared.modelId,
|
|
3345
|
+
usage: prepared.capturedUsage,
|
|
3346
|
+
requestId: prepared.capturedResponseId,
|
|
3347
|
+
clientApiKey: prepared.clientApiKey
|
|
3348
|
+
});
|
|
3349
|
+
prepared.response.satsSpent = satsSpent;
|
|
3350
|
+
prepared.response.usage = prepared.capturedUsage;
|
|
3351
|
+
prepared.response.requestId = prepared.capturedResponseId;
|
|
3352
|
+
resolve();
|
|
3353
|
+
} catch (error) {
|
|
3354
|
+
reject(error);
|
|
3355
|
+
}
|
|
3356
|
+
};
|
|
3357
|
+
const fail = (error) => {
|
|
3358
|
+
if (settled) return;
|
|
3359
|
+
settled = true;
|
|
3360
|
+
reject(error);
|
|
3361
|
+
};
|
|
3362
|
+
res.once("finish", finish);
|
|
3363
|
+
res.once("close", finish);
|
|
3364
|
+
res.once("error", fail);
|
|
3365
|
+
nodeReadable.once("error", fail);
|
|
3366
|
+
nodeReadable.pipe(res);
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
async _prepareRoutedRequest(params) {
|
|
2065
3370
|
const {
|
|
2066
3371
|
path,
|
|
2067
3372
|
method,
|
|
@@ -2069,8 +3374,10 @@ var RoutstrClient = class {
|
|
|
2069
3374
|
headers = {},
|
|
2070
3375
|
baseUrl,
|
|
2071
3376
|
mintUrl,
|
|
2072
|
-
modelId
|
|
3377
|
+
modelId,
|
|
3378
|
+
clientApiKey: providedClientApiKey
|
|
2073
3379
|
} = params;
|
|
3380
|
+
const clientApiKey = providedClientApiKey ?? this._extractClientApiKey(headers);
|
|
2074
3381
|
await this._checkBalance();
|
|
2075
3382
|
let requiredSats = 1;
|
|
2076
3383
|
let selectedModel;
|
|
@@ -2092,7 +3399,6 @@ var RoutstrClient = class {
|
|
|
2092
3399
|
amount: requiredSats,
|
|
2093
3400
|
baseUrl
|
|
2094
3401
|
});
|
|
2095
|
-
this._log("DEBUG", token, baseUrl);
|
|
2096
3402
|
let requestBody = body;
|
|
2097
3403
|
if (body && typeof body === "object") {
|
|
2098
3404
|
const bodyObj = body;
|
|
@@ -2100,7 +3406,7 @@ var RoutstrClient = class {
|
|
|
2100
3406
|
requestBody = { ...bodyObj, stream: false };
|
|
2101
3407
|
}
|
|
2102
3408
|
}
|
|
2103
|
-
const baseHeaders = this._buildBaseHeaders(
|
|
3409
|
+
const baseHeaders = this._buildBaseHeaders();
|
|
2104
3410
|
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
2105
3411
|
const response = await this._makeRequest({
|
|
2106
3412
|
path,
|
|
@@ -2117,13 +3423,55 @@ var RoutstrClient = class {
|
|
|
2117
3423
|
const tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
2118
3424
|
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
2119
3425
|
const tokenUsed = response.token || token;
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
3426
|
+
const contentType = response.headers.get("content-type") || "";
|
|
3427
|
+
let processedResponse = response;
|
|
3428
|
+
let capturedUsage;
|
|
3429
|
+
let capturedResponseId;
|
|
3430
|
+
if (contentType.includes("text/event-stream") && response.body) {
|
|
3431
|
+
const nodeReadable = stream.Readable.fromWeb(response.body);
|
|
3432
|
+
const sseParser = createSSEParserTransform(
|
|
3433
|
+
(usage) => {
|
|
3434
|
+
capturedUsage = usage;
|
|
3435
|
+
processedResponse.usage = usage;
|
|
3436
|
+
},
|
|
3437
|
+
(responseId) => {
|
|
3438
|
+
capturedResponseId = responseId;
|
|
3439
|
+
processedResponse.requestId = responseId;
|
|
3440
|
+
}
|
|
3441
|
+
);
|
|
3442
|
+
const transformed = nodeReadable.pipe(sseParser, { end: true });
|
|
3443
|
+
const webStream = stream.Readable.toWeb(
|
|
3444
|
+
transformed
|
|
3445
|
+
);
|
|
3446
|
+
processedResponse = new Response(webStream, {
|
|
3447
|
+
status: response.status,
|
|
3448
|
+
statusText: response.statusText,
|
|
3449
|
+
headers: response.headers
|
|
3450
|
+
});
|
|
3451
|
+
processedResponse.baseUrl = response.baseUrl;
|
|
3452
|
+
processedResponse.token = response.token;
|
|
3453
|
+
}
|
|
3454
|
+
return {
|
|
3455
|
+
response: processedResponse,
|
|
3456
|
+
tokenUsed,
|
|
3457
|
+
baseUrlUsed,
|
|
3458
|
+
tokenBalanceInSats,
|
|
3459
|
+
modelId,
|
|
3460
|
+
capturedUsage,
|
|
3461
|
+
capturedResponseId,
|
|
3462
|
+
clientApiKey
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3465
|
+
/**
|
|
3466
|
+
* Extract clientApiKey from Authorization Bearer token if present
|
|
3467
|
+
*/
|
|
3468
|
+
_extractClientApiKey(headers) {
|
|
3469
|
+
const authHeader = headers["Authorization"] || headers["authorization"];
|
|
3470
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
3471
|
+
const extractedKey = authHeader.slice(7);
|
|
3472
|
+
return extractedKey;
|
|
3473
|
+
}
|
|
3474
|
+
return void 0;
|
|
2127
3475
|
}
|
|
2128
3476
|
/**
|
|
2129
3477
|
* Fetch AI response with streaming
|
|
@@ -2228,9 +3576,21 @@ var RoutstrClient = class {
|
|
|
2228
3576
|
let satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
2229
3577
|
token,
|
|
2230
3578
|
baseUrl: baseUrlUsed,
|
|
3579
|
+
mintUrl,
|
|
2231
3580
|
initialTokenBalance: tokenBalanceInSats,
|
|
2232
3581
|
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
2233
|
-
response
|
|
3582
|
+
response,
|
|
3583
|
+
modelId: selectedModel.id,
|
|
3584
|
+
usage: streamingResult.usage ? {
|
|
3585
|
+
promptTokens: Number(streamingResult.usage.prompt_tokens ?? 0),
|
|
3586
|
+
completionTokens: Number(
|
|
3587
|
+
streamingResult.usage.completion_tokens ?? 0
|
|
3588
|
+
),
|
|
3589
|
+
totalTokens: Number(streamingResult.usage.total_tokens ?? 0),
|
|
3590
|
+
cost: Number(streamingResult.usage.cost ?? 0),
|
|
3591
|
+
satsCost: Number(streamingResult.usage.sats_cost ?? 0)
|
|
3592
|
+
} : void 0,
|
|
3593
|
+
requestId: streamingResult.responseId
|
|
2234
3594
|
});
|
|
2235
3595
|
const estimatedCosts = this._getEstimatedCosts(
|
|
2236
3596
|
selectedModel,
|
|
@@ -2255,7 +3615,6 @@ var RoutstrClient = class {
|
|
|
2255
3615
|
try {
|
|
2256
3616
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
2257
3617
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
2258
|
-
this._log("DEBUG", "HEADERS,", headers);
|
|
2259
3618
|
const response = await fetch(url, {
|
|
2260
3619
|
method,
|
|
2261
3620
|
headers,
|
|
@@ -2324,8 +3683,6 @@ var RoutstrClient = class {
|
|
|
2324
3683
|
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
|
|
2325
3684
|
);
|
|
2326
3685
|
tryNextProvider = true;
|
|
2327
|
-
if (this.mode === "lazyrefund")
|
|
2328
|
-
this.storageAdapter.removeToken(baseUrl);
|
|
2329
3686
|
} else {
|
|
2330
3687
|
this._log(
|
|
2331
3688
|
"DEBUG",
|
|
@@ -2373,22 +3730,15 @@ var RoutstrClient = class {
|
|
|
2373
3730
|
);
|
|
2374
3731
|
}
|
|
2375
3732
|
}
|
|
2376
|
-
if (status === 402 && !tryNextProvider &&
|
|
3733
|
+
if (status === 402 && !tryNextProvider && this.mode === "apikeys") {
|
|
2377
3734
|
this.storageAdapter.getApiKey(baseUrl);
|
|
2378
3735
|
let topupAmount = params.requiredSats;
|
|
2379
3736
|
try {
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
);
|
|
2386
|
-
currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
2387
|
-
} else if (this.mode === "lazyrefund") {
|
|
2388
|
-
const distribution = this.storageAdapter.getCachedTokenDistribution();
|
|
2389
|
-
const tokenEntry = distribution.find((t) => t.baseUrl === baseUrl);
|
|
2390
|
-
currentBalance = tokenEntry?.amount ?? 0;
|
|
2391
|
-
}
|
|
3737
|
+
const currentBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
3738
|
+
params.token,
|
|
3739
|
+
baseUrl
|
|
3740
|
+
);
|
|
3741
|
+
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
2392
3742
|
const shortfall = Math.max(0, params.requiredSats - currentBalance);
|
|
2393
3743
|
topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
|
|
2394
3744
|
} catch (e) {
|
|
@@ -2525,34 +3875,7 @@ var RoutstrClient = class {
|
|
|
2525
3875
|
"DEBUG",
|
|
2526
3876
|
`[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
|
|
2527
3877
|
);
|
|
2528
|
-
if (this.mode === "
|
|
2529
|
-
try {
|
|
2530
|
-
const refundResult = await this.balanceManager.refund({
|
|
2531
|
-
mintUrl,
|
|
2532
|
-
baseUrl,
|
|
2533
|
-
token: params.token
|
|
2534
|
-
});
|
|
2535
|
-
this._log(
|
|
2536
|
-
"DEBUG",
|
|
2537
|
-
`[RoutstrClient] _handleErrorResponse: Lazyrefund result: success=${refundResult.success}`
|
|
2538
|
-
);
|
|
2539
|
-
if (refundResult.success) this.storageAdapter.removeToken(baseUrl);
|
|
2540
|
-
else
|
|
2541
|
-
throw new ProviderError(
|
|
2542
|
-
baseUrl,
|
|
2543
|
-
status,
|
|
2544
|
-
"refund failed",
|
|
2545
|
-
requestId
|
|
2546
|
-
);
|
|
2547
|
-
} catch (error) {
|
|
2548
|
-
throw new ProviderError(
|
|
2549
|
-
baseUrl,
|
|
2550
|
-
status,
|
|
2551
|
-
"Failed to refund token",
|
|
2552
|
-
requestId
|
|
2553
|
-
);
|
|
2554
|
-
}
|
|
2555
|
-
} else if (this.mode === "apikeys") {
|
|
3878
|
+
if (this.mode === "apikeys") {
|
|
2556
3879
|
this._log(
|
|
2557
3880
|
"DEBUG",
|
|
2558
3881
|
`[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
|
|
@@ -2568,7 +3891,8 @@ var RoutstrClient = class {
|
|
|
2568
3891
|
const refundResult = await this.balanceManager.refundApiKey({
|
|
2569
3892
|
mintUrl,
|
|
2570
3893
|
baseUrl,
|
|
2571
|
-
apiKey: token
|
|
3894
|
+
apiKey: token,
|
|
3895
|
+
forceRefund: true
|
|
2572
3896
|
});
|
|
2573
3897
|
this._log(
|
|
2574
3898
|
"DEBUG",
|
|
@@ -2647,26 +3971,32 @@ var RoutstrClient = class {
|
|
|
2647
3971
|
* Handle post-response balance update for all modes
|
|
2648
3972
|
*/
|
|
2649
3973
|
async _handlePostResponseBalanceUpdate(params) {
|
|
2650
|
-
const {
|
|
3974
|
+
const {
|
|
3975
|
+
token,
|
|
3976
|
+
baseUrl,
|
|
3977
|
+
mintUrl,
|
|
3978
|
+
initialTokenBalance,
|
|
3979
|
+
fallbackSatsSpent,
|
|
3980
|
+
response,
|
|
3981
|
+
modelId,
|
|
3982
|
+
usage,
|
|
3983
|
+
requestId,
|
|
3984
|
+
clientApiKey
|
|
3985
|
+
} = params;
|
|
2651
3986
|
let satsSpent = initialTokenBalance;
|
|
2652
3987
|
if (this.mode === "xcashu" && response) {
|
|
2653
3988
|
const refundToken = response.headers.get("x-cashu") ?? void 0;
|
|
2654
3989
|
if (refundToken) {
|
|
2655
3990
|
try {
|
|
2656
3991
|
const receiveResult = await this.cashuSpender.receiveToken(refundToken);
|
|
2657
|
-
|
|
3992
|
+
if (receiveResult.success) {
|
|
3993
|
+
this.storageAdapter.removeXcashuToken(baseUrl, token);
|
|
3994
|
+
satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
|
|
3995
|
+
}
|
|
2658
3996
|
} catch (error) {
|
|
2659
3997
|
this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
|
|
2660
3998
|
}
|
|
2661
3999
|
}
|
|
2662
|
-
} else if (this.mode === "lazyrefund") {
|
|
2663
|
-
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
2664
|
-
token,
|
|
2665
|
-
baseUrl
|
|
2666
|
-
);
|
|
2667
|
-
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
2668
|
-
this.storageAdapter.updateTokenBalance(baseUrl, latestTokenBalance);
|
|
2669
|
-
satsSpent = initialTokenBalance - latestTokenBalance;
|
|
2670
4000
|
} else if (this.mode === "apikeys") {
|
|
2671
4001
|
try {
|
|
2672
4002
|
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
@@ -2694,8 +4024,87 @@ var RoutstrClient = class {
|
|
|
2694
4024
|
satsSpent = fallbackSatsSpent ?? initialTokenBalance;
|
|
2695
4025
|
}
|
|
2696
4026
|
}
|
|
4027
|
+
await this._trackResponseUsage({
|
|
4028
|
+
token,
|
|
4029
|
+
baseUrl,
|
|
4030
|
+
response,
|
|
4031
|
+
modelId,
|
|
4032
|
+
satsSpent,
|
|
4033
|
+
usage,
|
|
4034
|
+
requestId,
|
|
4035
|
+
clientApiKey
|
|
4036
|
+
});
|
|
4037
|
+
(async () => {
|
|
4038
|
+
try {
|
|
4039
|
+
const xcashuResults = await this.cashuSpender.refundXcashuTokens(mintUrl);
|
|
4040
|
+
this._log("DEBUG", "Refund xcashu tokens results:", xcashuResults);
|
|
4041
|
+
const results = await this.cashuSpender.refundProviders(mintUrl);
|
|
4042
|
+
} catch (error) {
|
|
4043
|
+
this._log("ERROR", "Failed to refund providers:", error);
|
|
4044
|
+
}
|
|
4045
|
+
})();
|
|
2697
4046
|
return satsSpent;
|
|
2698
4047
|
}
|
|
4048
|
+
async _trackResponseUsage(params) {
|
|
4049
|
+
const {
|
|
4050
|
+
token,
|
|
4051
|
+
baseUrl,
|
|
4052
|
+
response,
|
|
4053
|
+
modelId,
|
|
4054
|
+
satsSpent,
|
|
4055
|
+
usage: providedUsage,
|
|
4056
|
+
requestId: providedRequestId,
|
|
4057
|
+
clientApiKey
|
|
4058
|
+
} = params;
|
|
4059
|
+
if (!response || !modelId) {
|
|
4060
|
+
return;
|
|
4061
|
+
}
|
|
4062
|
+
try {
|
|
4063
|
+
let usage = providedUsage;
|
|
4064
|
+
let requestId = providedRequestId;
|
|
4065
|
+
if (!usage || !requestId) {
|
|
4066
|
+
const contentType = response.headers.get("content-type") || "";
|
|
4067
|
+
if (contentType.includes("text/event-stream")) {
|
|
4068
|
+
usage = usage ?? response.usage;
|
|
4069
|
+
requestId = requestId ?? response.requestId ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
4070
|
+
if (!usage) {
|
|
4071
|
+
return;
|
|
4072
|
+
}
|
|
4073
|
+
} else {
|
|
4074
|
+
const cloned = response.clone();
|
|
4075
|
+
const responseBody = await cloned.json();
|
|
4076
|
+
usage = usage ?? extractUsageFromResponseBody(responseBody, satsSpent) ?? void 0;
|
|
4077
|
+
requestId = requestId ?? extractResponseId(responseBody) ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
if (!usage) {
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
const finalRequestId = requestId || "unknown";
|
|
4084
|
+
const store = this.sdkStore ?? await getDefaultSdkStore();
|
|
4085
|
+
const state = store.getState();
|
|
4086
|
+
const matchKey = clientApiKey ?? token;
|
|
4087
|
+
const matchingClient = state.clientIds.find(
|
|
4088
|
+
(client) => client.apiKey === matchKey
|
|
4089
|
+
);
|
|
4090
|
+
const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
|
|
4091
|
+
const usageTracking = this.usageTrackingDriver ?? getDefaultUsageTrackingDriver();
|
|
4092
|
+
const entry = {
|
|
4093
|
+
id: entryId,
|
|
4094
|
+
timestamp: Date.now(),
|
|
4095
|
+
modelId,
|
|
4096
|
+
baseUrl,
|
|
4097
|
+
requestId: finalRequestId,
|
|
4098
|
+
client: matchingClient?.clientId,
|
|
4099
|
+
...usage
|
|
4100
|
+
};
|
|
4101
|
+
if (this.mode === "xcashu") {
|
|
4102
|
+
entry.satsCost = satsSpent;
|
|
4103
|
+
}
|
|
4104
|
+
await usageTracking.append(entry);
|
|
4105
|
+
} catch (error) {
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
2699
4108
|
/**
|
|
2700
4109
|
* Convert messages for API format
|
|
2701
4110
|
*/
|
|
@@ -2740,37 +4149,6 @@ var RoutstrClient = class {
|
|
|
2740
4149
|
content: result.content || ""
|
|
2741
4150
|
};
|
|
2742
4151
|
}
|
|
2743
|
-
/**
|
|
2744
|
-
* Create a child key for a parent API key via the provider's API
|
|
2745
|
-
* POST /v1/balance/child-key
|
|
2746
|
-
*/
|
|
2747
|
-
async _createChildKey(baseUrl, parentApiKey, options) {
|
|
2748
|
-
const response = await fetch(`${baseUrl}v1/balance/child-key`, {
|
|
2749
|
-
method: "POST",
|
|
2750
|
-
headers: {
|
|
2751
|
-
"Content-Type": "application/json",
|
|
2752
|
-
Authorization: `Bearer ${parentApiKey}`
|
|
2753
|
-
},
|
|
2754
|
-
body: JSON.stringify({
|
|
2755
|
-
count: options?.count ?? 1,
|
|
2756
|
-
balance_limit: options?.balanceLimit,
|
|
2757
|
-
balance_limit_reset: options?.balanceLimitReset,
|
|
2758
|
-
validity_date: options?.validityDate
|
|
2759
|
-
})
|
|
2760
|
-
});
|
|
2761
|
-
if (!response.ok) {
|
|
2762
|
-
throw new Error(
|
|
2763
|
-
`Failed to create child key: ${response.status} ${await response.text()}`
|
|
2764
|
-
);
|
|
2765
|
-
}
|
|
2766
|
-
const data = await response.json();
|
|
2767
|
-
return {
|
|
2768
|
-
childKey: data.api_keys?.[0],
|
|
2769
|
-
balance: data.balance ?? 0,
|
|
2770
|
-
balanceLimit: data.balance_limit,
|
|
2771
|
-
validityDate: data.validity_date
|
|
2772
|
-
};
|
|
2773
|
-
}
|
|
2774
4152
|
/**
|
|
2775
4153
|
* Calculate estimated costs from usage
|
|
2776
4154
|
*/
|
|
@@ -2785,11 +4163,11 @@ var RoutstrClient = class {
|
|
|
2785
4163
|
return estimatedCosts;
|
|
2786
4164
|
}
|
|
2787
4165
|
/**
|
|
2788
|
-
* Get pending
|
|
4166
|
+
* Get pending API key amount
|
|
2789
4167
|
*/
|
|
2790
4168
|
_getPendingCashuTokenAmount() {
|
|
2791
|
-
const
|
|
2792
|
-
return
|
|
4169
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
4170
|
+
return apiKeyDistribution.reduce((total, item) => total + item.amount, 0);
|
|
2793
4171
|
}
|
|
2794
4172
|
/**
|
|
2795
4173
|
* Handle errors and notify callbacks
|
|
@@ -2936,8 +4314,8 @@ var RoutstrClient = class {
|
|
|
2936
4314
|
const spendResult = await this.cashuSpender.spend({
|
|
2937
4315
|
mintUrl,
|
|
2938
4316
|
amount,
|
|
2939
|
-
baseUrl:
|
|
2940
|
-
reuseToken:
|
|
4317
|
+
baseUrl: "",
|
|
4318
|
+
reuseToken: false
|
|
2941
4319
|
});
|
|
2942
4320
|
if (!spendResult.token) {
|
|
2943
4321
|
this._log(
|
|
@@ -2950,6 +4328,7 @@ var RoutstrClient = class {
|
|
|
2950
4328
|
"DEBUG",
|
|
2951
4329
|
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
2952
4330
|
);
|
|
4331
|
+
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
2953
4332
|
}
|
|
2954
4333
|
return {
|
|
2955
4334
|
token: spendResult.token,
|
|
@@ -2984,5 +4363,6 @@ var RoutstrClient = class {
|
|
|
2984
4363
|
exports.ProviderManager = ProviderManager;
|
|
2985
4364
|
exports.RoutstrClient = RoutstrClient;
|
|
2986
4365
|
exports.StreamProcessor = StreamProcessor;
|
|
4366
|
+
exports.createSSEParserTransform = createSSEParserTransform;
|
|
2987
4367
|
//# sourceMappingURL=index.js.map
|
|
2988
4368
|
//# sourceMappingURL=index.js.map
|