@routstr/sdk 0.2.4 → 0.2.5
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 +9 -0
- package/dist/client/index.d.mts +21 -8
- package/dist/client/index.d.ts +21 -8
- package/dist/client/index.js +1379 -54
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +1379 -55
- 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 +15 -19
- package/dist/index.d.ts +15 -19
- package/dist/index.js +2671 -1872
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2666 -1873
- package/dist/index.mjs.map +1 -1
- 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-B85Wx7ni.d.mts → interfaces-C6Dr6hKy.d.mts} +1 -1
- package/dist/{interfaces-BVNyAmKu.d.ts → interfaces-CluftN4z.d.ts} +1 -1
- package/dist/storage/index.d.mts +56 -34
- package/dist/storage/index.d.ts +56 -34
- package/dist/storage/index.js +500 -51
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +497 -52
- package/dist/storage/index.mjs.map +1 -1
- 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 +9 -5
- package/dist/wallet/index.d.ts +9 -5
- package/dist/wallet/index.js +27 -7
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +27 -7
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var applesauceCore = require('applesauce-core');
|
|
|
5
5
|
var rxjs = require('rxjs');
|
|
6
6
|
var vanilla = require('zustand/vanilla');
|
|
7
7
|
var cashuTs = require('@cashu/cashu-ts');
|
|
8
|
+
var stream = require('stream');
|
|
8
9
|
|
|
9
10
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
10
11
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -385,10 +386,7 @@ var ModelManager = class _ModelManager {
|
|
|
385
386
|
throw new Error(`Failed to fetch models: ${res.status}`);
|
|
386
387
|
}
|
|
387
388
|
const json = await res.json();
|
|
388
|
-
const list = Array.isArray(json?.data) ? json.data
|
|
389
|
-
...m,
|
|
390
|
-
id: m.id.split("/").pop() || m.id
|
|
391
|
-
})) : [];
|
|
389
|
+
const list = Array.isArray(json?.data) ? json.data : [];
|
|
392
390
|
return list;
|
|
393
391
|
}
|
|
394
392
|
isProviderDownError(error) {
|
|
@@ -1115,7 +1113,7 @@ var CashuSpender = class {
|
|
|
1115
1113
|
/**
|
|
1116
1114
|
* Refund specific providers without retrying spend
|
|
1117
1115
|
*/
|
|
1118
|
-
async refundProviders(baseUrls, mintUrl, refundApiKeys = false) {
|
|
1116
|
+
async refundProviders(baseUrls, mintUrl, refundApiKeys = false, forceRefund) {
|
|
1119
1117
|
const results = [];
|
|
1120
1118
|
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
1121
1119
|
const toRefund = pendingDistribution.filter(
|
|
@@ -1165,7 +1163,8 @@ var CashuSpender = class {
|
|
|
1165
1163
|
const refundResult = await this.balanceManager.refundApiKey({
|
|
1166
1164
|
mintUrl,
|
|
1167
1165
|
baseUrl: apiKeyEntry.baseUrl,
|
|
1168
|
-
apiKey: apiKeyEntryFull.key
|
|
1166
|
+
apiKey: apiKeyEntryFull.key,
|
|
1167
|
+
forceRefund
|
|
1169
1168
|
});
|
|
1170
1169
|
if (refundResult.success) {
|
|
1171
1170
|
this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, 0);
|
|
@@ -1338,12 +1337,29 @@ var BalanceManager = class {
|
|
|
1338
1337
|
}
|
|
1339
1338
|
/**
|
|
1340
1339
|
* Refund API key balance - convert remaining API key balance to cashu token
|
|
1340
|
+
* @param options - Refund options including forceRefund flag
|
|
1341
|
+
* @returns Refund result
|
|
1341
1342
|
*/
|
|
1342
1343
|
async refundApiKey(options) {
|
|
1343
|
-
const { mintUrl, baseUrl, apiKey } = options;
|
|
1344
|
+
const { mintUrl, baseUrl, apiKey, forceRefund } = options;
|
|
1344
1345
|
if (!apiKey) {
|
|
1345
1346
|
return { success: false, message: "No API key to refund" };
|
|
1346
1347
|
}
|
|
1348
|
+
if (!forceRefund) {
|
|
1349
|
+
const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
1350
|
+
if (apiKeyEntry?.lastUsed) {
|
|
1351
|
+
const fiveMinutesAgo = Date.now() - 5 * 60 * 1e3;
|
|
1352
|
+
if (apiKeyEntry.lastUsed > fiveMinutesAgo) {
|
|
1353
|
+
console.log(
|
|
1354
|
+
`[BalanceManager] Skipping refund for ${baseUrl} - used ${Math.round((Date.now() - apiKeyEntry.lastUsed) / 1e3)}s ago`
|
|
1355
|
+
);
|
|
1356
|
+
return {
|
|
1357
|
+
success: false,
|
|
1358
|
+
message: "API key was used recently, skipping refund"
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1347
1363
|
let fetchResult;
|
|
1348
1364
|
try {
|
|
1349
1365
|
fetchResult = await this._fetchRefundTokenWithApiKey(baseUrl, apiKey);
|
|
@@ -1535,8 +1551,8 @@ var BalanceManager = class {
|
|
|
1535
1551
|
const refundableProviderBalance = Object.entries(
|
|
1536
1552
|
balanceState.providerBalances
|
|
1537
1553
|
).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
|
|
1538
|
-
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount <
|
|
1539
|
-
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl);
|
|
1554
|
+
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 2) {
|
|
1555
|
+
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount);
|
|
1540
1556
|
return this.createProviderToken({
|
|
1541
1557
|
...options,
|
|
1542
1558
|
retryCount: retryCount + 1
|
|
@@ -1689,9 +1705,10 @@ var BalanceManager = class {
|
|
|
1689
1705
|
}
|
|
1690
1706
|
return candidates;
|
|
1691
1707
|
}
|
|
1692
|
-
async _refundOtherProvidersForTopUp(baseUrl, mintUrl) {
|
|
1708
|
+
async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
|
|
1693
1709
|
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
1694
1710
|
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
1711
|
+
const forceRefund = retryCount >= 2;
|
|
1695
1712
|
const toRefund = pendingDistribution.filter(
|
|
1696
1713
|
(pending) => pending.baseUrl !== baseUrl
|
|
1697
1714
|
);
|
|
@@ -1732,7 +1749,8 @@ var BalanceManager = class {
|
|
|
1732
1749
|
const result = await this.refundApiKey({
|
|
1733
1750
|
mintUrl,
|
|
1734
1751
|
baseUrl: apiKeyEntry.baseUrl,
|
|
1735
|
-
apiKey: fullApiKeyEntry.key
|
|
1752
|
+
apiKey: fullApiKeyEntry.key,
|
|
1753
|
+
forceRefund
|
|
1736
1754
|
});
|
|
1737
1755
|
return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
|
|
1738
1756
|
})
|
|
@@ -1986,6 +2004,77 @@ var BalanceManager = class {
|
|
|
1986
2004
|
}
|
|
1987
2005
|
};
|
|
1988
2006
|
|
|
2007
|
+
// client/usage.ts
|
|
2008
|
+
function extractUsageFromResponseBody(body, fallbackSatsCost = 0) {
|
|
2009
|
+
if (!body || typeof body !== "object") return null;
|
|
2010
|
+
const usage = body.usage;
|
|
2011
|
+
if (!usage || typeof usage !== "object") return null;
|
|
2012
|
+
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
2013
|
+
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
2014
|
+
const totalTokens = Number(usage.total_tokens ?? 0);
|
|
2015
|
+
const costValue = usage.cost;
|
|
2016
|
+
let cost = 0;
|
|
2017
|
+
let satsCost = fallbackSatsCost;
|
|
2018
|
+
if (typeof costValue === "number") {
|
|
2019
|
+
cost = costValue;
|
|
2020
|
+
} else if (costValue && typeof costValue === "object") {
|
|
2021
|
+
const costObj = costValue;
|
|
2022
|
+
const totalUsd = costObj.total_usd;
|
|
2023
|
+
const totalMsats = costObj.total_msats;
|
|
2024
|
+
cost = typeof totalUsd === "number" ? totalUsd : 0;
|
|
2025
|
+
if (typeof totalMsats === "number") {
|
|
2026
|
+
satsCost = totalMsats / 1e3;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
|
|
2030
|
+
return null;
|
|
2031
|
+
}
|
|
2032
|
+
return {
|
|
2033
|
+
promptTokens,
|
|
2034
|
+
completionTokens,
|
|
2035
|
+
totalTokens,
|
|
2036
|
+
cost,
|
|
2037
|
+
satsCost
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
function extractResponseId(body) {
|
|
2041
|
+
if (!body || typeof body !== "object") return void 0;
|
|
2042
|
+
const id = body.id;
|
|
2043
|
+
if (typeof id !== "string") return void 0;
|
|
2044
|
+
const trimmed = id.trim();
|
|
2045
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
2046
|
+
}
|
|
2047
|
+
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
2048
|
+
if (!parsed || typeof parsed !== "object" || !parsed.usage) {
|
|
2049
|
+
return null;
|
|
2050
|
+
}
|
|
2051
|
+
const usage = parsed.usage;
|
|
2052
|
+
const usageCost = usage.cost;
|
|
2053
|
+
const cost = typeof usageCost === "number" ? usageCost : usageCost?.total_usd ?? parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
2054
|
+
const msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
2055
|
+
const result = {
|
|
2056
|
+
promptTokens: Number(usage.prompt_tokens ?? 0),
|
|
2057
|
+
completionTokens: Number(usage.completion_tokens ?? 0),
|
|
2058
|
+
totalTokens: Number(usage.total_tokens ?? 0),
|
|
2059
|
+
cost: Number(cost ?? 0),
|
|
2060
|
+
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
|
|
2061
|
+
};
|
|
2062
|
+
if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
|
|
2063
|
+
return null;
|
|
2064
|
+
}
|
|
2065
|
+
return result;
|
|
2066
|
+
}
|
|
2067
|
+
function toUsageStats(usage) {
|
|
2068
|
+
if (!usage) return void 0;
|
|
2069
|
+
return {
|
|
2070
|
+
total_tokens: usage.totalTokens,
|
|
2071
|
+
prompt_tokens: usage.promptTokens,
|
|
2072
|
+
completion_tokens: usage.completionTokens,
|
|
2073
|
+
cost: usage.cost,
|
|
2074
|
+
sats_cost: usage.satsCost
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
|
|
1989
2078
|
// client/StreamProcessor.ts
|
|
1990
2079
|
var StreamProcessor = class {
|
|
1991
2080
|
accumulatedContent = "";
|
|
@@ -2013,6 +2102,7 @@ var StreamProcessor = class {
|
|
|
2013
2102
|
let finish_reason;
|
|
2014
2103
|
let citations;
|
|
2015
2104
|
let annotations;
|
|
2105
|
+
let responseId;
|
|
2016
2106
|
try {
|
|
2017
2107
|
while (true) {
|
|
2018
2108
|
const { done, value } = await reader.read();
|
|
@@ -2041,6 +2131,9 @@ var StreamProcessor = class {
|
|
|
2041
2131
|
if (parsed.finish_reason) {
|
|
2042
2132
|
finish_reason = parsed.finish_reason;
|
|
2043
2133
|
}
|
|
2134
|
+
if (parsed.responseId) {
|
|
2135
|
+
responseId = parsed.responseId;
|
|
2136
|
+
}
|
|
2044
2137
|
if (parsed.citations) {
|
|
2045
2138
|
citations = parsed.citations;
|
|
2046
2139
|
}
|
|
@@ -2061,6 +2154,7 @@ var StreamProcessor = class {
|
|
|
2061
2154
|
images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
|
|
2062
2155
|
usage,
|
|
2063
2156
|
model,
|
|
2157
|
+
responseId,
|
|
2064
2158
|
finish_reason,
|
|
2065
2159
|
citations,
|
|
2066
2160
|
annotations
|
|
@@ -2088,12 +2182,15 @@ var StreamProcessor = class {
|
|
|
2088
2182
|
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
2089
2183
|
}
|
|
2090
2184
|
if (parsed.usage) {
|
|
2091
|
-
result.usage = {
|
|
2185
|
+
result.usage = toUsageStats(extractUsageFromSSEJson(parsed)) ?? {
|
|
2092
2186
|
total_tokens: parsed.usage.total_tokens,
|
|
2093
2187
|
prompt_tokens: parsed.usage.prompt_tokens,
|
|
2094
2188
|
completion_tokens: parsed.usage.completion_tokens
|
|
2095
2189
|
};
|
|
2096
2190
|
}
|
|
2191
|
+
if (parsed.id) {
|
|
2192
|
+
result.responseId = parsed.id;
|
|
2193
|
+
}
|
|
2097
2194
|
if (parsed.model) {
|
|
2098
2195
|
result.model = parsed.model;
|
|
2099
2196
|
}
|
|
@@ -2541,7 +2638,6 @@ var ProviderManager = class _ProviderManager {
|
|
|
2541
2638
|
* Get providers for a model sorted by prompt+completion pricing
|
|
2542
2639
|
*/
|
|
2543
2640
|
getProviderPriceRankingForModel(modelId, options = {}) {
|
|
2544
|
-
const normalizedId = this.normalizeModelId(modelId);
|
|
2545
2641
|
const includeDisabled = options.includeDisabled ?? false;
|
|
2546
2642
|
const torMode = options.torMode ?? false;
|
|
2547
2643
|
const disabledProviders = new Set(
|
|
@@ -2555,9 +2651,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
2555
2651
|
if (torMode && !baseUrl.includes(".onion")) continue;
|
|
2556
2652
|
if (!torMode && (baseUrl.includes(".onion") || isInsecureHttpUrl(baseUrl)))
|
|
2557
2653
|
continue;
|
|
2558
|
-
const match = models.find(
|
|
2559
|
-
(model) => this.normalizeModelId(model.id) === normalizedId
|
|
2560
|
-
);
|
|
2654
|
+
const match = models.find((model) => model.id === modelId);
|
|
2561
2655
|
if (!match?.sats_pricing) continue;
|
|
2562
2656
|
const prompt = match.sats_pricing.prompt;
|
|
2563
2657
|
const completion = match.sats_pricing.completion;
|
|
@@ -2670,1928 +2764,2602 @@ var ProviderManager = class _ProviderManager {
|
|
|
2670
2764
|
}
|
|
2671
2765
|
};
|
|
2672
2766
|
|
|
2673
|
-
//
|
|
2674
|
-
var
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
);
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2767
|
+
// storage/drivers/localStorage.ts
|
|
2768
|
+
var canUseLocalStorage = () => {
|
|
2769
|
+
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
2770
|
+
};
|
|
2771
|
+
var isQuotaExceeded = (error) => {
|
|
2772
|
+
const e = error;
|
|
2773
|
+
return !!e && (e?.name === "QuotaExceededError" || e?.code === 22 || e?.code === 1014);
|
|
2774
|
+
};
|
|
2775
|
+
var NON_CRITICAL_KEYS = /* @__PURE__ */ new Set(["modelsFromAllProviders"]);
|
|
2776
|
+
var localStorageDriver = {
|
|
2777
|
+
async getItem(key, defaultValue) {
|
|
2778
|
+
if (!canUseLocalStorage()) return defaultValue;
|
|
2779
|
+
try {
|
|
2780
|
+
const item = window.localStorage.getItem(key);
|
|
2781
|
+
if (item === null) return defaultValue;
|
|
2782
|
+
try {
|
|
2783
|
+
return JSON.parse(item);
|
|
2784
|
+
} catch (parseError) {
|
|
2785
|
+
if (typeof defaultValue === "string") {
|
|
2786
|
+
return item;
|
|
2787
|
+
}
|
|
2788
|
+
throw parseError;
|
|
2789
|
+
}
|
|
2790
|
+
} catch (error) {
|
|
2791
|
+
console.error(`Error retrieving item with key "${key}":`, error);
|
|
2792
|
+
if (canUseLocalStorage()) {
|
|
2793
|
+
try {
|
|
2794
|
+
window.localStorage.removeItem(key);
|
|
2795
|
+
} catch (removeError) {
|
|
2796
|
+
console.error(
|
|
2797
|
+
`Error removing corrupted item with key "${key}":`,
|
|
2798
|
+
removeError
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
return defaultValue;
|
|
2803
|
+
}
|
|
2804
|
+
},
|
|
2805
|
+
async setItem(key, value) {
|
|
2806
|
+
if (!canUseLocalStorage()) return;
|
|
2807
|
+
try {
|
|
2808
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
2809
|
+
} catch (error) {
|
|
2810
|
+
if (isQuotaExceeded(error)) {
|
|
2811
|
+
if (NON_CRITICAL_KEYS.has(key)) {
|
|
2812
|
+
console.warn(
|
|
2813
|
+
`Storage quota exceeded; skipping non-critical key "${key}".`
|
|
2814
|
+
);
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
try {
|
|
2818
|
+
window.localStorage.removeItem("modelsFromAllProviders");
|
|
2819
|
+
} catch {
|
|
2820
|
+
}
|
|
2821
|
+
try {
|
|
2822
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
2823
|
+
return;
|
|
2824
|
+
} catch (retryError) {
|
|
2825
|
+
console.warn(
|
|
2826
|
+
`Storage quota exceeded; unable to persist key "${key}" after cleanup attempt.`,
|
|
2827
|
+
retryError
|
|
2828
|
+
);
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
console.error(`Error storing item with key "${key}":`, error);
|
|
2833
|
+
}
|
|
2834
|
+
},
|
|
2835
|
+
async removeItem(key) {
|
|
2836
|
+
if (!canUseLocalStorage()) return;
|
|
2837
|
+
try {
|
|
2838
|
+
window.localStorage.removeItem(key);
|
|
2839
|
+
} catch (error) {
|
|
2840
|
+
console.error(`Error removing item with key "${key}":`, error);
|
|
2841
|
+
}
|
|
2695
2842
|
}
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
*/
|
|
2706
|
-
getMode() {
|
|
2707
|
-
return this.mode;
|
|
2843
|
+
};
|
|
2844
|
+
|
|
2845
|
+
// storage/drivers/memory.ts
|
|
2846
|
+
var createMemoryDriver = (seed) => {
|
|
2847
|
+
const store = /* @__PURE__ */ new Map();
|
|
2848
|
+
if (seed) {
|
|
2849
|
+
for (const [key, value] of Object.entries(seed)) {
|
|
2850
|
+
store.set(key, value);
|
|
2851
|
+
}
|
|
2708
2852
|
}
|
|
2709
|
-
|
|
2710
|
-
|
|
2853
|
+
return {
|
|
2854
|
+
async getItem(key, defaultValue) {
|
|
2855
|
+
const item = store.get(key);
|
|
2856
|
+
if (item === void 0) return defaultValue;
|
|
2857
|
+
try {
|
|
2858
|
+
return JSON.parse(item);
|
|
2859
|
+
} catch (parseError) {
|
|
2860
|
+
if (typeof defaultValue === "string") {
|
|
2861
|
+
return item;
|
|
2862
|
+
}
|
|
2863
|
+
throw parseError;
|
|
2864
|
+
}
|
|
2865
|
+
},
|
|
2866
|
+
async setItem(key, value) {
|
|
2867
|
+
store.set(key, JSON.stringify(value));
|
|
2868
|
+
},
|
|
2869
|
+
async removeItem(key) {
|
|
2870
|
+
store.delete(key);
|
|
2871
|
+
}
|
|
2872
|
+
};
|
|
2873
|
+
};
|
|
2874
|
+
|
|
2875
|
+
// storage/drivers/sqlite.ts
|
|
2876
|
+
var isBun = () => {
|
|
2877
|
+
return typeof process.versions.bun !== "undefined";
|
|
2878
|
+
};
|
|
2879
|
+
var createDatabase = (dbPath) => {
|
|
2880
|
+
if (isBun()) {
|
|
2881
|
+
throw new Error(
|
|
2882
|
+
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
2883
|
+
);
|
|
2711
2884
|
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2885
|
+
let Database = null;
|
|
2886
|
+
try {
|
|
2887
|
+
Database = __require("better-sqlite3");
|
|
2888
|
+
} catch (error) {
|
|
2889
|
+
throw new Error(
|
|
2890
|
+
`better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
|
|
2891
|
+
);
|
|
2714
2892
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2893
|
+
return new Database(dbPath);
|
|
2894
|
+
};
|
|
2895
|
+
var createSqliteDriver = (options = {}) => {
|
|
2896
|
+
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2897
|
+
const tableName = options.tableName || "sdk_storage";
|
|
2898
|
+
const db = createDatabase(dbPath);
|
|
2899
|
+
db.exec(
|
|
2900
|
+
`CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
2901
|
+
);
|
|
2902
|
+
const selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
|
|
2903
|
+
const upsertStmt = db.prepare(
|
|
2904
|
+
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)
|
|
2905
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
2906
|
+
);
|
|
2907
|
+
const deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
|
|
2908
|
+
return {
|
|
2909
|
+
async getItem(key, defaultValue) {
|
|
2910
|
+
try {
|
|
2911
|
+
const row = selectStmt.get(key);
|
|
2912
|
+
if (!row || typeof row.value !== "string") return defaultValue;
|
|
2913
|
+
try {
|
|
2914
|
+
return JSON.parse(row.value);
|
|
2915
|
+
} catch (parseError) {
|
|
2916
|
+
if (typeof defaultValue === "string") {
|
|
2917
|
+
return row.value;
|
|
2918
|
+
}
|
|
2919
|
+
throw parseError;
|
|
2920
|
+
}
|
|
2921
|
+
} catch (error) {
|
|
2922
|
+
console.error(`SQLite getItem failed for key "${key}":`, error);
|
|
2923
|
+
return defaultValue;
|
|
2924
|
+
}
|
|
2925
|
+
},
|
|
2926
|
+
async setItem(key, value) {
|
|
2927
|
+
try {
|
|
2928
|
+
upsertStmt.run(key, JSON.stringify(value));
|
|
2929
|
+
} catch (error) {
|
|
2930
|
+
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
2931
|
+
}
|
|
2932
|
+
},
|
|
2933
|
+
async removeItem(key) {
|
|
2934
|
+
try {
|
|
2935
|
+
deleteStmt.run(key);
|
|
2936
|
+
} catch (error) {
|
|
2937
|
+
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
2732
2938
|
}
|
|
2733
2939
|
}
|
|
2940
|
+
};
|
|
2941
|
+
};
|
|
2942
|
+
|
|
2943
|
+
// storage/drivers/indexedDB.ts
|
|
2944
|
+
var isBrowser = typeof indexedDB !== "undefined";
|
|
2945
|
+
var openDatabase = (dbName, storeName) => {
|
|
2946
|
+
if (!isBrowser) {
|
|
2947
|
+
return Promise.reject(new Error("IndexedDB is not available"));
|
|
2734
2948
|
}
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2949
|
+
return new Promise((resolve, reject) => {
|
|
2950
|
+
const request = indexedDB.open(dbName, 1);
|
|
2951
|
+
request.onupgradeneeded = () => {
|
|
2952
|
+
const db = request.result;
|
|
2953
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
2954
|
+
db.createObjectStore(storeName);
|
|
2955
|
+
}
|
|
2956
|
+
};
|
|
2957
|
+
request.onsuccess = () => resolve(request.result);
|
|
2958
|
+
request.onerror = () => reject(request.error);
|
|
2959
|
+
});
|
|
2960
|
+
};
|
|
2961
|
+
var createIndexedDBDriver = (options = {}) => {
|
|
2962
|
+
const dbName = options.dbName || "routstr-sdk";
|
|
2963
|
+
const storeName = options.storeName || "sdk_storage";
|
|
2964
|
+
let dbPromise = null;
|
|
2965
|
+
const getDb = () => {
|
|
2966
|
+
if (!dbPromise) {
|
|
2967
|
+
dbPromise = openDatabase(dbName, storeName);
|
|
2968
|
+
}
|
|
2969
|
+
return dbPromise;
|
|
2970
|
+
};
|
|
2971
|
+
return {
|
|
2972
|
+
async getItem(key, defaultValue) {
|
|
2973
|
+
try {
|
|
2974
|
+
const db = await getDb();
|
|
2975
|
+
return new Promise((resolve, reject) => {
|
|
2976
|
+
const tx = db.transaction(storeName, "readonly");
|
|
2977
|
+
const store = tx.objectStore(storeName);
|
|
2978
|
+
const request = store.get(key);
|
|
2979
|
+
request.onsuccess = () => {
|
|
2980
|
+
const raw = request.result;
|
|
2981
|
+
if (raw === void 0) {
|
|
2982
|
+
resolve(defaultValue);
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
if (typeof raw === "string") {
|
|
2986
|
+
try {
|
|
2987
|
+
resolve(JSON.parse(raw));
|
|
2988
|
+
} catch {
|
|
2989
|
+
if (typeof defaultValue === "string") {
|
|
2990
|
+
resolve(raw);
|
|
2991
|
+
} else {
|
|
2992
|
+
resolve(defaultValue);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
} else {
|
|
2996
|
+
resolve(raw);
|
|
2997
|
+
}
|
|
2998
|
+
};
|
|
2999
|
+
request.onerror = () => reject(request.error);
|
|
3000
|
+
});
|
|
3001
|
+
} catch (error) {
|
|
3002
|
+
console.error(`IndexedDB getItem failed for key "${key}":`, error);
|
|
3003
|
+
return defaultValue;
|
|
3004
|
+
}
|
|
3005
|
+
},
|
|
3006
|
+
async setItem(key, value) {
|
|
3007
|
+
try {
|
|
3008
|
+
const db = await getDb();
|
|
3009
|
+
return new Promise((resolve, reject) => {
|
|
3010
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
3011
|
+
const store = tx.objectStore(storeName);
|
|
3012
|
+
store.put(JSON.stringify(value), key);
|
|
3013
|
+
tx.oncomplete = () => resolve();
|
|
3014
|
+
tx.onerror = () => reject(tx.error);
|
|
3015
|
+
});
|
|
3016
|
+
} catch (error) {
|
|
3017
|
+
console.error(`IndexedDB setItem failed for key "${key}":`, error);
|
|
3018
|
+
}
|
|
3019
|
+
},
|
|
3020
|
+
async removeItem(key) {
|
|
3021
|
+
try {
|
|
3022
|
+
const db = await getDb();
|
|
3023
|
+
return new Promise((resolve, reject) => {
|
|
3024
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
3025
|
+
const store = tx.objectStore(storeName);
|
|
3026
|
+
store.delete(key);
|
|
3027
|
+
tx.oncomplete = () => resolve();
|
|
3028
|
+
tx.onerror = () => reject(tx.error);
|
|
3029
|
+
});
|
|
3030
|
+
} catch (error) {
|
|
3031
|
+
console.error(`IndexedDB removeItem failed for key "${key}":`, error);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
};
|
|
3035
|
+
};
|
|
3036
|
+
|
|
3037
|
+
// storage/keys.ts
|
|
3038
|
+
var SDK_STORAGE_KEYS = {
|
|
3039
|
+
MODELS_FROM_ALL_PROVIDERS: "modelsFromAllProviders",
|
|
3040
|
+
LAST_USED_MODEL: "lastUsedModel",
|
|
3041
|
+
BASE_URLS_LIST: "base_urls_list",
|
|
3042
|
+
DISABLED_PROVIDERS: "disabled_providers",
|
|
3043
|
+
MINTS_FROM_ALL_PROVIDERS: "mints_from_all_providers",
|
|
3044
|
+
INFO_FROM_ALL_PROVIDERS: "info_from_all_providers",
|
|
3045
|
+
LAST_MODELS_UPDATE: "lastModelsUpdate",
|
|
3046
|
+
LAST_BASE_URLS_UPDATE: "lastBaseUrlsUpdate",
|
|
3047
|
+
LOCAL_CASHU_TOKENS: "local_cashu_tokens",
|
|
3048
|
+
API_KEYS: "api_keys",
|
|
3049
|
+
CHILD_KEYS: "child_keys",
|
|
3050
|
+
ROUTSTR21_MODELS: "routstr21Models",
|
|
3051
|
+
LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
|
|
3052
|
+
CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
|
|
3053
|
+
USAGE_TRACKING: "usage_tracking",
|
|
3054
|
+
CLIENT_IDS: "client_ids"
|
|
3055
|
+
};
|
|
3056
|
+
|
|
3057
|
+
// storage/usageTracking/indexedDB.ts
|
|
3058
|
+
var DEFAULT_DB_NAME = "routstr-sdk";
|
|
3059
|
+
var DEFAULT_STORE_NAME = "usage_tracking";
|
|
3060
|
+
var MIGRATION_MARKER_KEY = "usage_tracking_migration_v1";
|
|
3061
|
+
var isBrowser2 = typeof indexedDB !== "undefined";
|
|
3062
|
+
var normalizeBaseUrl = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3063
|
+
var openDatabase2 = (dbName, storeName) => {
|
|
3064
|
+
if (!isBrowser2) {
|
|
3065
|
+
return Promise.reject(new Error("IndexedDB is not available"));
|
|
2740
3066
|
}
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
3067
|
+
return new Promise((resolve, reject) => {
|
|
3068
|
+
const request = indexedDB.open(dbName, 1);
|
|
3069
|
+
request.onupgradeneeded = () => {
|
|
3070
|
+
const db = request.result;
|
|
3071
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
3072
|
+
const store = db.createObjectStore(storeName, { keyPath: "id" });
|
|
3073
|
+
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
3074
|
+
store.createIndex("modelId", "modelId", { unique: false });
|
|
3075
|
+
store.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
3076
|
+
store.createIndex("sessionId", "sessionId", { unique: false });
|
|
3077
|
+
store.createIndex("client", "client", { unique: false });
|
|
3078
|
+
}
|
|
3079
|
+
};
|
|
3080
|
+
request.onsuccess = () => resolve(request.result);
|
|
3081
|
+
request.onerror = () => reject(request.error);
|
|
3082
|
+
});
|
|
3083
|
+
};
|
|
3084
|
+
var matchesFilters = (entry, options = {}) => {
|
|
3085
|
+
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
3086
|
+
return false;
|
|
2746
3087
|
}
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
*/
|
|
2750
|
-
getProviderManager() {
|
|
2751
|
-
return this.providerManager;
|
|
3088
|
+
if (typeof options.after === "number" && entry.timestamp <= options.after) {
|
|
3089
|
+
return false;
|
|
2752
3090
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
*/
|
|
2756
|
-
get isBusy() {
|
|
2757
|
-
return this.cashuSpender.isBusy;
|
|
3091
|
+
if (options.modelId && entry.modelId !== options.modelId) {
|
|
3092
|
+
return false;
|
|
2758
3093
|
}
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
3094
|
+
if (options.baseUrl && normalizeBaseUrl(entry.baseUrl) !== normalizeBaseUrl(options.baseUrl)) {
|
|
3095
|
+
return false;
|
|
3096
|
+
}
|
|
3097
|
+
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
3098
|
+
return false;
|
|
3099
|
+
}
|
|
3100
|
+
if (options.client && entry.client !== options.client) {
|
|
3101
|
+
return false;
|
|
3102
|
+
}
|
|
3103
|
+
return true;
|
|
3104
|
+
};
|
|
3105
|
+
var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
3106
|
+
const dbName = options.dbName || DEFAULT_DB_NAME;
|
|
3107
|
+
const storeName = options.storeName || DEFAULT_STORE_NAME;
|
|
3108
|
+
const legacyStorageDriver = options.legacyStorageDriver;
|
|
3109
|
+
let dbPromise = null;
|
|
3110
|
+
let migrationPromise = null;
|
|
3111
|
+
const getDb = () => {
|
|
3112
|
+
if (!dbPromise) {
|
|
3113
|
+
dbPromise = openDatabase2(dbName, storeName);
|
|
3114
|
+
}
|
|
3115
|
+
return dbPromise;
|
|
3116
|
+
};
|
|
3117
|
+
const putMany = async (entries) => {
|
|
3118
|
+
if (entries.length === 0) return;
|
|
3119
|
+
const db = await getDb();
|
|
3120
|
+
await new Promise((resolve, reject) => {
|
|
3121
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
3122
|
+
const store = tx.objectStore(storeName);
|
|
3123
|
+
for (const entry of entries) {
|
|
3124
|
+
store.put({ ...entry, baseUrl: normalizeBaseUrl(entry.baseUrl) });
|
|
3125
|
+
}
|
|
3126
|
+
tx.oncomplete = () => resolve();
|
|
3127
|
+
tx.onerror = () => reject(tx.error);
|
|
3128
|
+
});
|
|
3129
|
+
};
|
|
3130
|
+
const ensureMigrated = async () => {
|
|
3131
|
+
if (!legacyStorageDriver) return;
|
|
3132
|
+
if (!migrationPromise) {
|
|
3133
|
+
migrationPromise = (async () => {
|
|
3134
|
+
const migrated = await legacyStorageDriver.getItem(
|
|
3135
|
+
MIGRATION_MARKER_KEY,
|
|
3136
|
+
false
|
|
3137
|
+
);
|
|
3138
|
+
if (migrated) return;
|
|
3139
|
+
const legacyEntries = await legacyStorageDriver.getItem(
|
|
3140
|
+
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2789
3141
|
[]
|
|
2790
3142
|
);
|
|
2791
|
-
|
|
3143
|
+
if (legacyEntries.length > 0) {
|
|
3144
|
+
await putMany(legacyEntries);
|
|
3145
|
+
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
3146
|
+
}
|
|
3147
|
+
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY, true);
|
|
3148
|
+
})();
|
|
2792
3149
|
}
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
3150
|
+
await migrationPromise;
|
|
3151
|
+
};
|
|
3152
|
+
return {
|
|
3153
|
+
async migrate() {
|
|
3154
|
+
await ensureMigrated();
|
|
3155
|
+
},
|
|
3156
|
+
async append(entry) {
|
|
3157
|
+
await ensureMigrated();
|
|
3158
|
+
await putMany([entry]);
|
|
3159
|
+
},
|
|
3160
|
+
async appendMany(entries) {
|
|
3161
|
+
await ensureMigrated();
|
|
3162
|
+
await putMany(entries);
|
|
3163
|
+
},
|
|
3164
|
+
async list(options2 = {}) {
|
|
3165
|
+
await ensureMigrated();
|
|
3166
|
+
const db = await getDb();
|
|
3167
|
+
return new Promise((resolve, reject) => {
|
|
3168
|
+
const tx = db.transaction(storeName, "readonly");
|
|
3169
|
+
const store = tx.objectStore(storeName);
|
|
3170
|
+
const index = store.index("timestamp");
|
|
3171
|
+
const direction = "prev";
|
|
3172
|
+
const request = index.openCursor(null, direction);
|
|
3173
|
+
const results = [];
|
|
3174
|
+
const limit = options2.limit;
|
|
3175
|
+
request.onsuccess = () => {
|
|
3176
|
+
const cursor = request.result;
|
|
3177
|
+
if (!cursor) {
|
|
3178
|
+
resolve(results);
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
3181
|
+
const value = cursor.value;
|
|
3182
|
+
if (matchesFilters(value, options2)) {
|
|
3183
|
+
results.push(value);
|
|
3184
|
+
if (typeof limit === "number" && results.length >= limit) {
|
|
3185
|
+
resolve(results);
|
|
3186
|
+
return;
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
cursor.continue();
|
|
3190
|
+
};
|
|
3191
|
+
request.onerror = () => reject(request.error);
|
|
3192
|
+
});
|
|
3193
|
+
},
|
|
3194
|
+
async count(options2 = {}) {
|
|
3195
|
+
const results = await this.list(options2);
|
|
3196
|
+
return results.length;
|
|
3197
|
+
},
|
|
3198
|
+
async deleteOlderThan(timestamp) {
|
|
3199
|
+
await ensureMigrated();
|
|
3200
|
+
const db = await getDb();
|
|
3201
|
+
return new Promise((resolve, reject) => {
|
|
3202
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
3203
|
+
const store = tx.objectStore(storeName);
|
|
3204
|
+
const index = store.index("timestamp");
|
|
3205
|
+
const range = IDBKeyRange.upperBound(timestamp, true);
|
|
3206
|
+
const request = index.openCursor(range);
|
|
3207
|
+
let deleted = 0;
|
|
3208
|
+
request.onsuccess = () => {
|
|
3209
|
+
const cursor = request.result;
|
|
3210
|
+
if (!cursor) {
|
|
3211
|
+
resolve(deleted);
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3214
|
+
deleted += 1;
|
|
3215
|
+
cursor.delete();
|
|
3216
|
+
cursor.continue();
|
|
3217
|
+
};
|
|
3218
|
+
request.onerror = () => reject(request.error);
|
|
3219
|
+
});
|
|
3220
|
+
},
|
|
3221
|
+
async clear() {
|
|
3222
|
+
await ensureMigrated();
|
|
3223
|
+
const db = await getDb();
|
|
3224
|
+
await new Promise((resolve, reject) => {
|
|
3225
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
3226
|
+
tx.objectStore(storeName).clear();
|
|
3227
|
+
tx.oncomplete = () => resolve();
|
|
3228
|
+
tx.onerror = () => reject(tx.error);
|
|
3229
|
+
});
|
|
2805
3230
|
}
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
const tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
2821
|
-
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
2822
|
-
const tokenUsed = response.token || token;
|
|
2823
|
-
await this._handlePostResponseBalanceUpdate({
|
|
2824
|
-
token: tokenUsed,
|
|
2825
|
-
baseUrl: baseUrlUsed,
|
|
2826
|
-
initialTokenBalance: tokenBalanceInSats,
|
|
2827
|
-
response
|
|
2828
|
-
});
|
|
2829
|
-
return response;
|
|
3231
|
+
};
|
|
3232
|
+
};
|
|
3233
|
+
|
|
3234
|
+
// storage/usageTracking/sqlite.ts
|
|
3235
|
+
var MIGRATION_MARKER_KEY2 = "usage_tracking_migration_v1";
|
|
3236
|
+
var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3237
|
+
var isBun2 = () => {
|
|
3238
|
+
return typeof process.versions.bun !== "undefined";
|
|
3239
|
+
};
|
|
3240
|
+
var createDatabase2 = (dbPath) => {
|
|
3241
|
+
if (isBun2()) {
|
|
3242
|
+
throw new Error(
|
|
3243
|
+
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
3244
|
+
);
|
|
2830
3245
|
}
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
selectedModel,
|
|
2838
|
-
baseUrl,
|
|
2839
|
-
mintUrl,
|
|
2840
|
-
balance,
|
|
2841
|
-
transactionHistory,
|
|
2842
|
-
maxTokens,
|
|
2843
|
-
headers
|
|
2844
|
-
} = options;
|
|
2845
|
-
const apiMessages = await this._convertMessages(messageHistory);
|
|
2846
|
-
const requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
2847
|
-
selectedModel,
|
|
2848
|
-
apiMessages,
|
|
2849
|
-
maxTokens
|
|
3246
|
+
let Database = null;
|
|
3247
|
+
try {
|
|
3248
|
+
Database = __require("better-sqlite3");
|
|
3249
|
+
} catch (error) {
|
|
3250
|
+
throw new Error(
|
|
3251
|
+
`better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
|
|
2850
3252
|
);
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
3253
|
+
}
|
|
3254
|
+
return new Database(dbPath);
|
|
3255
|
+
};
|
|
3256
|
+
var buildWhereClause = (options = {}) => {
|
|
3257
|
+
const clauses = [];
|
|
3258
|
+
const params = [];
|
|
3259
|
+
if (typeof options.before === "number") {
|
|
3260
|
+
clauses.push("timestamp < ?");
|
|
3261
|
+
params.push(options.before);
|
|
3262
|
+
}
|
|
3263
|
+
if (typeof options.after === "number") {
|
|
3264
|
+
clauses.push("timestamp > ?");
|
|
3265
|
+
params.push(options.after);
|
|
3266
|
+
}
|
|
3267
|
+
if (options.modelId) {
|
|
3268
|
+
clauses.push("model_id = ?");
|
|
3269
|
+
params.push(options.modelId);
|
|
3270
|
+
}
|
|
3271
|
+
if (options.baseUrl) {
|
|
3272
|
+
clauses.push("base_url = ?");
|
|
3273
|
+
params.push(normalizeBaseUrl2(options.baseUrl));
|
|
3274
|
+
}
|
|
3275
|
+
if (options.sessionId) {
|
|
3276
|
+
clauses.push("session_id = ?");
|
|
3277
|
+
params.push(options.sessionId);
|
|
3278
|
+
}
|
|
3279
|
+
if (options.client) {
|
|
3280
|
+
clauses.push("client = ?");
|
|
3281
|
+
params.push(options.client);
|
|
3282
|
+
}
|
|
3283
|
+
return {
|
|
3284
|
+
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
3285
|
+
params
|
|
3286
|
+
};
|
|
3287
|
+
};
|
|
3288
|
+
var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
3289
|
+
const dbPath = options.dbPath || "routstr.sqlite";
|
|
3290
|
+
const tableName = options.tableName || "usage_tracking";
|
|
3291
|
+
const db = createDatabase2(dbPath);
|
|
3292
|
+
const legacyStorageDriver = options.legacyStorageDriver;
|
|
3293
|
+
db.exec(`
|
|
3294
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
3295
|
+
id TEXT PRIMARY KEY,
|
|
3296
|
+
timestamp INTEGER NOT NULL,
|
|
3297
|
+
model_id TEXT NOT NULL,
|
|
3298
|
+
base_url TEXT NOT NULL,
|
|
3299
|
+
request_id TEXT NOT NULL,
|
|
3300
|
+
cost REAL NOT NULL,
|
|
3301
|
+
sats_cost REAL NOT NULL,
|
|
3302
|
+
prompt_tokens INTEGER NOT NULL,
|
|
3303
|
+
completion_tokens INTEGER NOT NULL,
|
|
3304
|
+
total_tokens INTEGER NOT NULL,
|
|
3305
|
+
client TEXT,
|
|
3306
|
+
session_id TEXT,
|
|
3307
|
+
tags TEXT
|
|
3308
|
+
);
|
|
3309
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
3310
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
3311
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
3312
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
3313
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
3314
|
+
`);
|
|
3315
|
+
const insertStmt = db.prepare(`
|
|
3316
|
+
INSERT OR REPLACE INTO ${tableName} (
|
|
3317
|
+
id, timestamp, model_id, base_url, request_id,
|
|
3318
|
+
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
3319
|
+
client, session_id, tags
|
|
3320
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3321
|
+
`);
|
|
3322
|
+
let migrationComplete = false;
|
|
3323
|
+
const appendOne = (entry) => {
|
|
3324
|
+
insertStmt.run(
|
|
3325
|
+
entry.id,
|
|
3326
|
+
entry.timestamp,
|
|
3327
|
+
entry.modelId,
|
|
3328
|
+
normalizeBaseUrl2(entry.baseUrl),
|
|
3329
|
+
entry.requestId,
|
|
3330
|
+
entry.cost,
|
|
3331
|
+
entry.satsCost,
|
|
3332
|
+
entry.promptTokens,
|
|
3333
|
+
entry.completionTokens,
|
|
3334
|
+
entry.totalTokens,
|
|
3335
|
+
entry.client ?? null,
|
|
3336
|
+
entry.sessionId ?? null,
|
|
3337
|
+
JSON.stringify(entry.tags ?? [])
|
|
3338
|
+
);
|
|
3339
|
+
};
|
|
3340
|
+
const ensureMigrated = async () => {
|
|
3341
|
+
if (!legacyStorageDriver || migrationComplete) return;
|
|
3342
|
+
const migrated = await legacyStorageDriver.getItem(
|
|
3343
|
+
MIGRATION_MARKER_KEY2,
|
|
3344
|
+
false
|
|
3345
|
+
);
|
|
3346
|
+
if (migrated) {
|
|
3347
|
+
migrationComplete = true;
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
const legacyEntries = await legacyStorageDriver.getItem(
|
|
3351
|
+
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
3352
|
+
[]
|
|
3353
|
+
);
|
|
3354
|
+
for (const entry of legacyEntries) {
|
|
3355
|
+
appendOne(entry);
|
|
3356
|
+
}
|
|
3357
|
+
if (legacyEntries.length > 0) {
|
|
3358
|
+
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
3359
|
+
}
|
|
3360
|
+
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY2, true);
|
|
3361
|
+
migrationComplete = true;
|
|
3362
|
+
};
|
|
3363
|
+
const mapRow = (row) => ({
|
|
3364
|
+
id: row.id,
|
|
3365
|
+
timestamp: row.timestamp,
|
|
3366
|
+
modelId: row.model_id,
|
|
3367
|
+
baseUrl: row.base_url,
|
|
3368
|
+
requestId: row.request_id,
|
|
3369
|
+
cost: row.cost,
|
|
3370
|
+
satsCost: row.sats_cost,
|
|
3371
|
+
promptTokens: row.prompt_tokens,
|
|
3372
|
+
completionTokens: row.completion_tokens,
|
|
3373
|
+
totalTokens: row.total_tokens,
|
|
3374
|
+
client: row.client ?? void 0,
|
|
3375
|
+
sessionId: row.session_id ?? void 0,
|
|
3376
|
+
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
3377
|
+
});
|
|
3378
|
+
return {
|
|
3379
|
+
async migrate() {
|
|
3380
|
+
await ensureMigrated();
|
|
3381
|
+
},
|
|
3382
|
+
async append(entry) {
|
|
3383
|
+
await ensureMigrated();
|
|
3384
|
+
appendOne(entry);
|
|
3385
|
+
},
|
|
3386
|
+
async appendMany(entries) {
|
|
3387
|
+
await ensureMigrated();
|
|
3388
|
+
for (const entry of entries) {
|
|
3389
|
+
appendOne(entry);
|
|
2884
3390
|
}
|
|
2885
|
-
|
|
2886
|
-
|
|
3391
|
+
},
|
|
3392
|
+
async list(options2 = {}) {
|
|
3393
|
+
await ensureMigrated();
|
|
3394
|
+
const { sql, params } = buildWhereClause(options2);
|
|
3395
|
+
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
3396
|
+
const stmt = db.prepare(
|
|
3397
|
+
`SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`
|
|
3398
|
+
);
|
|
3399
|
+
const rows = stmt.all(
|
|
3400
|
+
...typeof options2.limit === "number" ? [...params, options2.limit] : params
|
|
3401
|
+
);
|
|
3402
|
+
return rows.map(mapRow);
|
|
3403
|
+
},
|
|
3404
|
+
async count(options2 = {}) {
|
|
3405
|
+
await ensureMigrated();
|
|
3406
|
+
const { sql, params } = buildWhereClause(options2);
|
|
3407
|
+
const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
|
|
3408
|
+
const row = stmt.get(...params);
|
|
3409
|
+
return Number(row?.count ?? 0);
|
|
3410
|
+
},
|
|
3411
|
+
async deleteOlderThan(timestamp) {
|
|
3412
|
+
await ensureMigrated();
|
|
3413
|
+
const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
|
|
3414
|
+
const result = stmt.run(timestamp);
|
|
3415
|
+
return result.changes;
|
|
3416
|
+
},
|
|
3417
|
+
async clear() {
|
|
3418
|
+
await ensureMigrated();
|
|
3419
|
+
db.prepare(`DELETE FROM ${tableName}`).run();
|
|
3420
|
+
}
|
|
3421
|
+
};
|
|
3422
|
+
};
|
|
3423
|
+
|
|
3424
|
+
// storage/usageTracking/memory.ts
|
|
3425
|
+
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3426
|
+
var matchesFilters2 = (entry, options = {}) => {
|
|
3427
|
+
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
3428
|
+
return false;
|
|
3429
|
+
}
|
|
3430
|
+
if (typeof options.after === "number" && entry.timestamp <= options.after) {
|
|
3431
|
+
return false;
|
|
3432
|
+
}
|
|
3433
|
+
if (options.modelId && entry.modelId !== options.modelId) {
|
|
3434
|
+
return false;
|
|
3435
|
+
}
|
|
3436
|
+
if (options.baseUrl && normalizeBaseUrl3(entry.baseUrl) !== normalizeBaseUrl3(options.baseUrl)) {
|
|
3437
|
+
return false;
|
|
3438
|
+
}
|
|
3439
|
+
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
3440
|
+
return false;
|
|
3441
|
+
}
|
|
3442
|
+
if (options.client && entry.client !== options.client) {
|
|
3443
|
+
return false;
|
|
3444
|
+
}
|
|
3445
|
+
return true;
|
|
3446
|
+
};
|
|
3447
|
+
var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
3448
|
+
const store = /* @__PURE__ */ new Map();
|
|
3449
|
+
for (const entry of seed) {
|
|
3450
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
|
|
3451
|
+
}
|
|
3452
|
+
return {
|
|
3453
|
+
async migrate() {
|
|
3454
|
+
return;
|
|
3455
|
+
},
|
|
3456
|
+
async append(entry) {
|
|
3457
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
|
|
3458
|
+
},
|
|
3459
|
+
async appendMany(entries) {
|
|
3460
|
+
for (const entry of entries) {
|
|
3461
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
|
|
2887
3462
|
}
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
baseUrl,
|
|
2894
|
-
mintUrl,
|
|
2895
|
-
token,
|
|
2896
|
-
requiredSats,
|
|
2897
|
-
maxTokens,
|
|
2898
|
-
headers: requestHeaders,
|
|
2899
|
-
baseHeaders
|
|
2900
|
-
});
|
|
2901
|
-
if (!response.body) {
|
|
2902
|
-
throw new Error("Response body is not available");
|
|
3463
|
+
},
|
|
3464
|
+
async list(options = {}) {
|
|
3465
|
+
const entries = [...store.values()].filter((entry) => matchesFilters2(entry, options)).sort((a, b) => b.timestamp - a.timestamp);
|
|
3466
|
+
if (typeof options.limit === "number") {
|
|
3467
|
+
return entries.slice(0, options.limit);
|
|
2903
3468
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
callbacks.onMessageAppend({
|
|
2916
|
-
role: "assistant",
|
|
2917
|
-
content: "Your request was denied due to content filtering."
|
|
2918
|
-
});
|
|
2919
|
-
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
2920
|
-
const message = await this._createAssistantMessage(streamingResult);
|
|
2921
|
-
callbacks.onMessageAppend(message);
|
|
2922
|
-
} else {
|
|
2923
|
-
callbacks.onMessageAppend({
|
|
2924
|
-
role: "system",
|
|
2925
|
-
content: "The provider did not respond to this request."
|
|
2926
|
-
});
|
|
3469
|
+
return entries;
|
|
3470
|
+
},
|
|
3471
|
+
async count(options = {}) {
|
|
3472
|
+
return (await this.list(options)).length;
|
|
3473
|
+
},
|
|
3474
|
+
async deleteOlderThan(timestamp) {
|
|
3475
|
+
let deleted = 0;
|
|
3476
|
+
for (const [id, entry] of store.entries()) {
|
|
3477
|
+
if (entry.timestamp < timestamp) {
|
|
3478
|
+
store.delete(id);
|
|
3479
|
+
deleted += 1;
|
|
2927
3480
|
}
|
|
2928
|
-
callbacks.onStreamingUpdate("");
|
|
2929
|
-
callbacks.onThinkingUpdate("");
|
|
2930
|
-
const isApikeysEstimate = this.mode === "apikeys";
|
|
2931
|
-
let satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
2932
|
-
token,
|
|
2933
|
-
baseUrl: baseUrlUsed,
|
|
2934
|
-
initialTokenBalance: tokenBalanceInSats,
|
|
2935
|
-
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
2936
|
-
response
|
|
2937
|
-
});
|
|
2938
|
-
const estimatedCosts = this._getEstimatedCosts(
|
|
2939
|
-
selectedModel,
|
|
2940
|
-
streamingResult
|
|
2941
|
-
);
|
|
2942
|
-
const onLastMessageSatsUpdate = callbacks.onLastMessageSatsUpdate;
|
|
2943
|
-
onLastMessageSatsUpdate?.(satsSpent, estimatedCosts);
|
|
2944
|
-
} else {
|
|
2945
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
2946
3481
|
}
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
3482
|
+
return deleted;
|
|
3483
|
+
},
|
|
3484
|
+
async clear() {
|
|
3485
|
+
store.clear();
|
|
2951
3486
|
}
|
|
2952
|
-
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
const
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
const response = await fetch(url, {
|
|
2963
|
-
method,
|
|
2964
|
-
headers,
|
|
2965
|
-
body: body === void 0 || method === "GET" ? void 0 : JSON.stringify(body)
|
|
2966
|
-
});
|
|
2967
|
-
if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
|
|
2968
|
-
response.baseUrl = baseUrl;
|
|
2969
|
-
response.token = token;
|
|
2970
|
-
if (!response.ok) {
|
|
2971
|
-
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
2972
|
-
let bodyText;
|
|
2973
|
-
try {
|
|
2974
|
-
bodyText = await response.text();
|
|
2975
|
-
} catch (e) {
|
|
2976
|
-
bodyText = void 0;
|
|
2977
|
-
}
|
|
2978
|
-
return await this._handleErrorResponse(
|
|
2979
|
-
params,
|
|
2980
|
-
token,
|
|
2981
|
-
response.status,
|
|
2982
|
-
requestId,
|
|
2983
|
-
this.mode === "xcashu" ? response.headers.get("x-cashu") ?? void 0 : void 0,
|
|
2984
|
-
bodyText,
|
|
2985
|
-
params.retryCount ?? 0
|
|
2986
|
-
);
|
|
2987
|
-
}
|
|
2988
|
-
return response;
|
|
2989
|
-
} catch (error) {
|
|
2990
|
-
if (isNetworkErrorMessage(error?.message || "")) {
|
|
2991
|
-
return await this._handleErrorResponse(
|
|
2992
|
-
params,
|
|
2993
|
-
token,
|
|
2994
|
-
-1,
|
|
2995
|
-
// just for Network Error to skip all statuses
|
|
2996
|
-
void 0,
|
|
2997
|
-
void 0,
|
|
2998
|
-
void 0,
|
|
2999
|
-
params.retryCount ?? 0
|
|
3000
|
-
);
|
|
3001
|
-
}
|
|
3002
|
-
throw error;
|
|
3487
|
+
};
|
|
3488
|
+
};
|
|
3489
|
+
var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
3490
|
+
var getCashuTokenBalance = (token) => {
|
|
3491
|
+
try {
|
|
3492
|
+
const decoded = cashuTs.getDecodedToken(token);
|
|
3493
|
+
const unitDivisor = decoded.unit === "msat" ? 1e3 : 1;
|
|
3494
|
+
let sum = 0;
|
|
3495
|
+
for (const proof of decoded.proofs) {
|
|
3496
|
+
sum += proof.amount / unitDivisor;
|
|
3003
3497
|
}
|
|
3498
|
+
return sum;
|
|
3499
|
+
} catch {
|
|
3500
|
+
return 0;
|
|
3004
3501
|
}
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
"DEBUG",
|
|
3027
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
|
|
3028
|
-
);
|
|
3029
|
-
tryNextProvider = true;
|
|
3030
|
-
if (this.mode === "lazyrefund")
|
|
3031
|
-
this.storageAdapter.removeToken(baseUrl);
|
|
3032
|
-
} else {
|
|
3033
|
-
this._log(
|
|
3034
|
-
"DEBUG",
|
|
3035
|
-
`[RoutstrClient] _handleErrorResponse: Failed to receive token. `
|
|
3036
|
-
);
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
if (this.mode === "xcashu") {
|
|
3040
|
-
if (xCashuRefundToken) {
|
|
3041
|
-
this._log(
|
|
3042
|
-
"DEBUG",
|
|
3043
|
-
`[RoutstrClient] _handleErrorResponse: Attempting to receive xcashu refund token, preview=${xCashuRefundToken.substring(0, 20)}...`
|
|
3044
|
-
);
|
|
3045
|
-
try {
|
|
3046
|
-
const receiveResult = await this.cashuSpender.receiveToken(xCashuRefundToken);
|
|
3047
|
-
if (receiveResult.success) {
|
|
3048
|
-
this._log(
|
|
3049
|
-
"DEBUG",
|
|
3050
|
-
`[RoutstrClient] _handleErrorResponse: xcashu refund received, amount=${receiveResult.amount}`
|
|
3051
|
-
);
|
|
3052
|
-
tryNextProvider = true;
|
|
3053
|
-
} else
|
|
3054
|
-
throw new ProviderError(
|
|
3055
|
-
baseUrl,
|
|
3056
|
-
status,
|
|
3057
|
-
"xcashu refund failed",
|
|
3058
|
-
requestId
|
|
3059
|
-
);
|
|
3060
|
-
} catch (error) {
|
|
3061
|
-
this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
|
|
3062
|
-
throw new ProviderError(
|
|
3063
|
-
baseUrl,
|
|
3064
|
-
status,
|
|
3065
|
-
"[xcashu] Failed to receive refund token",
|
|
3066
|
-
requestId
|
|
3067
|
-
);
|
|
3068
|
-
}
|
|
3069
|
-
} else {
|
|
3070
|
-
if (!tryNextProvider)
|
|
3071
|
-
throw new ProviderError(
|
|
3072
|
-
baseUrl,
|
|
3073
|
-
status,
|
|
3074
|
-
"[xcashu] Failed to receive refund token",
|
|
3075
|
-
requestId
|
|
3076
|
-
);
|
|
3077
|
-
}
|
|
3502
|
+
};
|
|
3503
|
+
var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
3504
|
+
modelsFromAllProviders: {},
|
|
3505
|
+
lastUsedModel: null,
|
|
3506
|
+
baseUrlsList: [],
|
|
3507
|
+
lastBaseUrlsUpdate: null,
|
|
3508
|
+
disabledProviders: [],
|
|
3509
|
+
mintsFromAllProviders: {},
|
|
3510
|
+
infoFromAllProviders: {},
|
|
3511
|
+
lastModelsUpdate: {},
|
|
3512
|
+
cachedTokens: [],
|
|
3513
|
+
apiKeys: [],
|
|
3514
|
+
childKeys: [],
|
|
3515
|
+
routstr21Models: [],
|
|
3516
|
+
lastRoutstr21ModelsUpdate: null,
|
|
3517
|
+
cachedReceiveTokens: [],
|
|
3518
|
+
clientIds: [],
|
|
3519
|
+
setModelsFromAllProviders: (value) => {
|
|
3520
|
+
const normalized = {};
|
|
3521
|
+
for (const [baseUrl, models] of Object.entries(value)) {
|
|
3522
|
+
normalized[normalizeBaseUrl4(baseUrl)] = models;
|
|
3078
3523
|
}
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
token: params.token
|
|
3109
|
-
});
|
|
3110
|
-
this._log(
|
|
3111
|
-
"DEBUG",
|
|
3112
|
-
`[RoutstrClient] _handleErrorResponse: Topup result for ${baseUrl}: success=${topupResult.success}, message=${topupResult.message}`
|
|
3524
|
+
void driver.setItem(
|
|
3525
|
+
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
3526
|
+
normalized
|
|
3527
|
+
);
|
|
3528
|
+
set({ modelsFromAllProviders: normalized });
|
|
3529
|
+
},
|
|
3530
|
+
setLastUsedModel: (value) => {
|
|
3531
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, value);
|
|
3532
|
+
set({ lastUsedModel: value });
|
|
3533
|
+
},
|
|
3534
|
+
setBaseUrlsList: (value) => {
|
|
3535
|
+
const normalized = value.map((url) => normalizeBaseUrl4(url));
|
|
3536
|
+
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
3537
|
+
set({ baseUrlsList: normalized });
|
|
3538
|
+
},
|
|
3539
|
+
setBaseUrlsLastUpdate: (value) => {
|
|
3540
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, value);
|
|
3541
|
+
set({ lastBaseUrlsUpdate: value });
|
|
3542
|
+
},
|
|
3543
|
+
setDisabledProviders: (value) => {
|
|
3544
|
+
const normalized = value.map((url) => normalizeBaseUrl4(url));
|
|
3545
|
+
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
3546
|
+
set({ disabledProviders: normalized });
|
|
3547
|
+
},
|
|
3548
|
+
setMintsFromAllProviders: (value) => {
|
|
3549
|
+
const normalized = {};
|
|
3550
|
+
for (const [baseUrl, mints] of Object.entries(value)) {
|
|
3551
|
+
normalized[normalizeBaseUrl4(baseUrl)] = mints.map(
|
|
3552
|
+
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
3113
3553
|
);
|
|
3114
|
-
if (!topupResult.success) {
|
|
3115
|
-
const message = topupResult.message || "";
|
|
3116
|
-
if (message.includes("Insufficient balance")) {
|
|
3117
|
-
const needMatch = message.match(/need (\d+)/);
|
|
3118
|
-
const haveMatch = message.match(/have (\d+)/);
|
|
3119
|
-
const required = needMatch ? parseInt(needMatch[1], 10) : params.requiredSats;
|
|
3120
|
-
const available = haveMatch ? parseInt(haveMatch[1], 10) : 0;
|
|
3121
|
-
this._log(
|
|
3122
|
-
"DEBUG",
|
|
3123
|
-
`[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
|
|
3124
|
-
);
|
|
3125
|
-
throw new InsufficientBalanceError(
|
|
3126
|
-
required,
|
|
3127
|
-
available,
|
|
3128
|
-
0,
|
|
3129
|
-
"",
|
|
3130
|
-
message
|
|
3131
|
-
);
|
|
3132
|
-
} else {
|
|
3133
|
-
this._log(
|
|
3134
|
-
"DEBUG",
|
|
3135
|
-
`[RoutstrClient] _handleErrorResponse: Topup failed with non-insufficient-balance error, will try next provider`
|
|
3136
|
-
);
|
|
3137
|
-
tryNextProvider = true;
|
|
3138
|
-
}
|
|
3139
|
-
} else {
|
|
3140
|
-
this._log(
|
|
3141
|
-
"DEBUG",
|
|
3142
|
-
`[RoutstrClient] _handleErrorResponse: Topup successful, will retry with new token`
|
|
3143
|
-
);
|
|
3144
|
-
}
|
|
3145
|
-
if (!tryNextProvider) {
|
|
3146
|
-
if (retryCount < MAX_RETRIES_PER_PROVIDER) {
|
|
3147
|
-
this._log(
|
|
3148
|
-
"DEBUG",
|
|
3149
|
-
`[RoutstrClient] _handleErrorResponse: Retrying 402 (attempt ${retryCount + 1}/${MAX_RETRIES_PER_PROVIDER})`
|
|
3150
|
-
);
|
|
3151
|
-
return this._makeRequest({
|
|
3152
|
-
...params,
|
|
3153
|
-
token: params.token,
|
|
3154
|
-
headers: this._withAuthHeader(params.baseHeaders, params.token),
|
|
3155
|
-
retryCount: retryCount + 1
|
|
3156
|
-
});
|
|
3157
|
-
} else {
|
|
3158
|
-
this._log(
|
|
3159
|
-
"DEBUG",
|
|
3160
|
-
`[RoutstrClient] _handleErrorResponse: 402 retry limit reached (${retryCount}/${MAX_RETRIES_PER_PROVIDER}), failing over to next provider`
|
|
3161
|
-
);
|
|
3162
|
-
tryNextProvider = true;
|
|
3163
|
-
}
|
|
3164
|
-
}
|
|
3165
3554
|
}
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
"DEBUG",
|
|
3177
|
-
`[RoutstrClient] _handleErrorResponse: Invalid API key (proofs already spent), removing for ${baseUrl}`
|
|
3178
|
-
);
|
|
3179
|
-
this.storageAdapter.removeApiKey(baseUrl);
|
|
3180
|
-
tryNextProvider = true;
|
|
3181
|
-
} else {
|
|
3182
|
-
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
3183
|
-
if (latestBalanceInfo.apiKey) {
|
|
3184
|
-
const storedApiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
3185
|
-
if (storedApiKeyEntry?.key !== latestBalanceInfo.apiKey) {
|
|
3186
|
-
if (storedApiKeyEntry) {
|
|
3187
|
-
this.storageAdapter.removeApiKey(baseUrl);
|
|
3188
|
-
}
|
|
3189
|
-
this.storageAdapter.setApiKey(baseUrl, latestBalanceInfo.apiKey);
|
|
3190
|
-
}
|
|
3191
|
-
retryToken = latestBalanceInfo.apiKey;
|
|
3192
|
-
}
|
|
3193
|
-
if (latestTokenBalance >= 0) {
|
|
3194
|
-
this.storageAdapter.updateApiKeyBalance(
|
|
3195
|
-
baseUrl,
|
|
3196
|
-
latestTokenBalance
|
|
3197
|
-
);
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
} catch (error) {
|
|
3201
|
-
this._log(
|
|
3202
|
-
"WARN",
|
|
3203
|
-
`[RoutstrClient] _handleErrorResponse: Failed to refresh API key after 413 insufficient balance for ${baseUrl}`,
|
|
3204
|
-
error
|
|
3205
|
-
);
|
|
3206
|
-
}
|
|
3207
|
-
if (retryCount < MAX_RETRIES_PER_PROVIDER) {
|
|
3208
|
-
this._log(
|
|
3209
|
-
"DEBUG",
|
|
3210
|
-
`[RoutstrClient] _handleErrorResponse: Retrying 413 (attempt ${retryCount + 1}/${MAX_RETRIES_PER_PROVIDER})`
|
|
3211
|
-
);
|
|
3212
|
-
return this._makeRequest({
|
|
3213
|
-
...params,
|
|
3214
|
-
token: retryToken,
|
|
3215
|
-
headers: this._withAuthHeader(params.baseHeaders, retryToken),
|
|
3216
|
-
retryCount: retryCount + 1
|
|
3217
|
-
});
|
|
3218
|
-
} else {
|
|
3219
|
-
this._log(
|
|
3220
|
-
"DEBUG",
|
|
3221
|
-
`[RoutstrClient] _handleErrorResponse: 413 retry limit reached (${retryCount}/${MAX_RETRIES_PER_PROVIDER}), failing over to next provider`
|
|
3222
|
-
);
|
|
3223
|
-
tryNextProvider = true;
|
|
3224
|
-
}
|
|
3555
|
+
void driver.setItem(
|
|
3556
|
+
SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
|
|
3557
|
+
normalized
|
|
3558
|
+
);
|
|
3559
|
+
set({ mintsFromAllProviders: normalized });
|
|
3560
|
+
},
|
|
3561
|
+
setInfoFromAllProviders: (value) => {
|
|
3562
|
+
const normalized = {};
|
|
3563
|
+
for (const [baseUrl, info] of Object.entries(value)) {
|
|
3564
|
+
normalized[normalizeBaseUrl4(baseUrl)] = info;
|
|
3225
3565
|
}
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
const refundResult = await this.balanceManager.refund({
|
|
3234
|
-
mintUrl,
|
|
3235
|
-
baseUrl,
|
|
3236
|
-
token: params.token
|
|
3237
|
-
});
|
|
3238
|
-
this._log(
|
|
3239
|
-
"DEBUG",
|
|
3240
|
-
`[RoutstrClient] _handleErrorResponse: Lazyrefund result: success=${refundResult.success}`
|
|
3241
|
-
);
|
|
3242
|
-
if (refundResult.success) this.storageAdapter.removeToken(baseUrl);
|
|
3243
|
-
else
|
|
3244
|
-
throw new ProviderError(
|
|
3245
|
-
baseUrl,
|
|
3246
|
-
status,
|
|
3247
|
-
"refund failed",
|
|
3248
|
-
requestId
|
|
3249
|
-
);
|
|
3250
|
-
} catch (error) {
|
|
3251
|
-
throw new ProviderError(
|
|
3252
|
-
baseUrl,
|
|
3253
|
-
status,
|
|
3254
|
-
"Failed to refund token",
|
|
3255
|
-
requestId
|
|
3256
|
-
);
|
|
3257
|
-
}
|
|
3258
|
-
} else if (this.mode === "apikeys") {
|
|
3259
|
-
this._log(
|
|
3260
|
-
"DEBUG",
|
|
3261
|
-
`[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
|
|
3262
|
-
);
|
|
3263
|
-
const initialBalance = await this.balanceManager.getTokenBalance(
|
|
3264
|
-
token,
|
|
3265
|
-
baseUrl
|
|
3266
|
-
);
|
|
3267
|
-
this._log(
|
|
3268
|
-
"DEBUG",
|
|
3269
|
-
`[RoutstrClient] _handleErrorResponse: Initial API key balance: ${initialBalance.amount}`
|
|
3270
|
-
);
|
|
3271
|
-
const refundResult = await this.balanceManager.refundApiKey({
|
|
3272
|
-
mintUrl,
|
|
3273
|
-
baseUrl,
|
|
3274
|
-
apiKey: token
|
|
3275
|
-
});
|
|
3276
|
-
this._log(
|
|
3277
|
-
"DEBUG",
|
|
3278
|
-
`[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
|
|
3279
|
-
);
|
|
3280
|
-
if (!refundResult.success && initialBalance.amount > 0) {
|
|
3281
|
-
throw new ProviderError(
|
|
3282
|
-
baseUrl,
|
|
3283
|
-
status,
|
|
3284
|
-
refundResult.message ?? "Unknown error"
|
|
3285
|
-
);
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
}
|
|
3289
|
-
this.providerManager.markFailed(baseUrl);
|
|
3290
|
-
this._log(
|
|
3291
|
-
"DEBUG",
|
|
3292
|
-
`[RoutstrClient] _handleErrorResponse: Marked provider ${baseUrl} as failed`
|
|
3293
|
-
);
|
|
3294
|
-
if (!selectedModel) {
|
|
3295
|
-
throw new ProviderError(
|
|
3296
|
-
baseUrl,
|
|
3297
|
-
status,
|
|
3298
|
-
"Funny, no selected model. HMM. "
|
|
3299
|
-
);
|
|
3300
|
-
}
|
|
3301
|
-
const nextProvider = this.providerManager.findNextBestProvider(
|
|
3302
|
-
selectedModel.id,
|
|
3303
|
-
baseUrl
|
|
3304
|
-
);
|
|
3305
|
-
if (nextProvider) {
|
|
3306
|
-
this._log(
|
|
3307
|
-
"DEBUG",
|
|
3308
|
-
`[RoutstrClient] _handleErrorResponse: Failing over to next provider: ${nextProvider}, model: ${selectedModel.id}`
|
|
3309
|
-
);
|
|
3310
|
-
const newModel = await this.providerManager.getModelForProvider(
|
|
3311
|
-
nextProvider,
|
|
3312
|
-
selectedModel.id
|
|
3313
|
-
) ?? selectedModel;
|
|
3314
|
-
const messagesForPricing = Array.isArray(
|
|
3315
|
-
body?.messages
|
|
3316
|
-
) ? body.messages : [];
|
|
3317
|
-
const newRequiredSats = this.providerManager.getRequiredSatsForModel(
|
|
3318
|
-
newModel,
|
|
3319
|
-
messagesForPricing,
|
|
3320
|
-
params.maxTokens
|
|
3321
|
-
);
|
|
3322
|
-
this._log(
|
|
3323
|
-
"DEBUG",
|
|
3324
|
-
`[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
|
|
3325
|
-
);
|
|
3326
|
-
const spendResult = await this._spendToken({
|
|
3327
|
-
mintUrl,
|
|
3328
|
-
amount: newRequiredSats,
|
|
3329
|
-
baseUrl: nextProvider
|
|
3330
|
-
});
|
|
3331
|
-
return this._makeRequest({
|
|
3332
|
-
...params,
|
|
3333
|
-
path,
|
|
3334
|
-
method,
|
|
3335
|
-
body,
|
|
3336
|
-
baseUrl: nextProvider,
|
|
3337
|
-
selectedModel: newModel,
|
|
3338
|
-
token: spendResult.token,
|
|
3339
|
-
requiredSats: newRequiredSats,
|
|
3340
|
-
headers: this._withAuthHeader(params.baseHeaders, spendResult.token),
|
|
3341
|
-
retryCount: 0
|
|
3342
|
-
});
|
|
3343
|
-
}
|
|
3344
|
-
throw new FailoverError(
|
|
3345
|
-
baseUrl,
|
|
3346
|
-
Array.from(this.providerManager.getFailedProviders())
|
|
3347
|
-
);
|
|
3348
|
-
}
|
|
3349
|
-
/**
|
|
3350
|
-
* Handle post-response balance update for all modes
|
|
3351
|
-
*/
|
|
3352
|
-
async _handlePostResponseBalanceUpdate(params) {
|
|
3353
|
-
const { token, baseUrl, initialTokenBalance, fallbackSatsSpent, response } = params;
|
|
3354
|
-
let satsSpent = initialTokenBalance;
|
|
3355
|
-
if (this.mode === "xcashu" && response) {
|
|
3356
|
-
const refundToken = response.headers.get("x-cashu") ?? void 0;
|
|
3357
|
-
if (refundToken) {
|
|
3358
|
-
try {
|
|
3359
|
-
const receiveResult = await this.cashuSpender.receiveToken(refundToken);
|
|
3360
|
-
satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
|
|
3361
|
-
} catch (error) {
|
|
3362
|
-
this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
|
|
3363
|
-
}
|
|
3364
|
-
}
|
|
3365
|
-
} else if (this.mode === "lazyrefund") {
|
|
3366
|
-
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
3367
|
-
token,
|
|
3368
|
-
baseUrl
|
|
3369
|
-
);
|
|
3370
|
-
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
3371
|
-
this.storageAdapter.updateTokenBalance(baseUrl, latestTokenBalance);
|
|
3372
|
-
satsSpent = initialTokenBalance - latestTokenBalance;
|
|
3373
|
-
} else if (this.mode === "apikeys") {
|
|
3374
|
-
try {
|
|
3375
|
-
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
3376
|
-
token,
|
|
3377
|
-
baseUrl
|
|
3378
|
-
);
|
|
3379
|
-
this._log(
|
|
3380
|
-
"DEBUG",
|
|
3381
|
-
"LATEST Balance",
|
|
3382
|
-
latestBalanceInfo.amount,
|
|
3383
|
-
latestBalanceInfo.reserved,
|
|
3384
|
-
latestBalanceInfo.apiKey,
|
|
3385
|
-
baseUrl
|
|
3386
|
-
);
|
|
3387
|
-
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
3388
|
-
const storedApiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
3389
|
-
if (storedApiKeyEntry?.key.startsWith("cashu") && latestBalanceInfo.apiKey) {
|
|
3390
|
-
this.storageAdapter.removeApiKey(baseUrl);
|
|
3391
|
-
this.storageAdapter.setApiKey(baseUrl, latestBalanceInfo.apiKey);
|
|
3392
|
-
}
|
|
3393
|
-
this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
|
|
3394
|
-
satsSpent = initialTokenBalance - latestTokenBalance;
|
|
3395
|
-
} catch (e) {
|
|
3396
|
-
this._log("WARN", "Could not get updated API key balance:", e);
|
|
3397
|
-
satsSpent = fallbackSatsSpent ?? initialTokenBalance;
|
|
3398
|
-
}
|
|
3399
|
-
}
|
|
3400
|
-
return satsSpent;
|
|
3401
|
-
}
|
|
3402
|
-
/**
|
|
3403
|
-
* Convert messages for API format
|
|
3404
|
-
*/
|
|
3405
|
-
async _convertMessages(messages) {
|
|
3406
|
-
return Promise.all(
|
|
3407
|
-
messages.filter((m) => m.role !== "system").map(async (m) => ({
|
|
3408
|
-
role: m.role,
|
|
3409
|
-
content: typeof m.content === "string" ? m.content : m.content
|
|
3410
|
-
}))
|
|
3411
|
-
);
|
|
3412
|
-
}
|
|
3413
|
-
/**
|
|
3414
|
-
* Create assistant message from streaming result
|
|
3415
|
-
*/
|
|
3416
|
-
async _createAssistantMessage(result) {
|
|
3417
|
-
if (result.images && result.images.length > 0) {
|
|
3418
|
-
const content = [];
|
|
3419
|
-
if (result.content) {
|
|
3420
|
-
content.push({
|
|
3421
|
-
type: "text",
|
|
3422
|
-
text: result.content,
|
|
3423
|
-
thinking: result.thinking,
|
|
3424
|
-
citations: result.citations,
|
|
3425
|
-
annotations: result.annotations
|
|
3426
|
-
});
|
|
3427
|
-
}
|
|
3428
|
-
for (const img of result.images) {
|
|
3429
|
-
content.push({
|
|
3430
|
-
type: "image_url",
|
|
3431
|
-
image_url: {
|
|
3432
|
-
url: img.image_url.url
|
|
3433
|
-
}
|
|
3434
|
-
});
|
|
3435
|
-
}
|
|
3436
|
-
return {
|
|
3437
|
-
role: "assistant",
|
|
3438
|
-
content
|
|
3439
|
-
};
|
|
3566
|
+
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
3567
|
+
set({ infoFromAllProviders: normalized });
|
|
3568
|
+
},
|
|
3569
|
+
setLastModelsUpdate: (value) => {
|
|
3570
|
+
const normalized = {};
|
|
3571
|
+
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
3572
|
+
normalized[normalizeBaseUrl4(baseUrl)] = timestamp;
|
|
3440
3573
|
}
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
Authorization: `Bearer ${parentApiKey}`
|
|
3456
|
-
},
|
|
3457
|
-
body: JSON.stringify({
|
|
3458
|
-
count: options?.count ?? 1,
|
|
3459
|
-
balance_limit: options?.balanceLimit,
|
|
3460
|
-
balance_limit_reset: options?.balanceLimitReset,
|
|
3461
|
-
validity_date: options?.validityDate
|
|
3462
|
-
})
|
|
3574
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
3575
|
+
set({ lastModelsUpdate: normalized });
|
|
3576
|
+
},
|
|
3577
|
+
setCachedTokens: (value) => {
|
|
3578
|
+
set((state) => {
|
|
3579
|
+
const updates = typeof value === "function" ? value(state.cachedTokens) : value;
|
|
3580
|
+
const normalized = updates.map((entry) => ({
|
|
3581
|
+
...entry,
|
|
3582
|
+
baseUrl: normalizeBaseUrl4(entry.baseUrl),
|
|
3583
|
+
balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
|
|
3584
|
+
lastUsed: entry.lastUsed ?? null
|
|
3585
|
+
}));
|
|
3586
|
+
void driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
|
|
3587
|
+
return { cachedTokens: normalized };
|
|
3463
3588
|
});
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
);
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
}
|
|
3477
|
-
/**
|
|
3478
|
-
* Calculate estimated costs from usage
|
|
3479
|
-
*/
|
|
3480
|
-
_getEstimatedCosts(selectedModel, streamingResult) {
|
|
3481
|
-
let estimatedCosts = 0;
|
|
3482
|
-
if (streamingResult.usage) {
|
|
3483
|
-
const { completion_tokens, prompt_tokens } = streamingResult.usage;
|
|
3484
|
-
if (completion_tokens !== void 0 && prompt_tokens !== void 0) {
|
|
3485
|
-
estimatedCosts = (selectedModel.sats_pricing?.completion ?? 0) * completion_tokens + (selectedModel.sats_pricing?.prompt ?? 0) * prompt_tokens;
|
|
3486
|
-
}
|
|
3487
|
-
}
|
|
3488
|
-
return estimatedCosts;
|
|
3489
|
-
}
|
|
3490
|
-
/**
|
|
3491
|
-
* Get pending cashu token amount
|
|
3492
|
-
*/
|
|
3493
|
-
_getPendingCashuTokenAmount() {
|
|
3494
|
-
const distribution = this.storageAdapter.getCachedTokenDistribution();
|
|
3495
|
-
return distribution.reduce((total, item) => total + item.amount, 0);
|
|
3496
|
-
}
|
|
3497
|
-
/**
|
|
3498
|
-
* Handle errors and notify callbacks
|
|
3499
|
-
*/
|
|
3500
|
-
_handleError(error, callbacks) {
|
|
3501
|
-
this._log("ERROR", "[RoutstrClient] _handleError: Error occurred", error);
|
|
3502
|
-
if (error instanceof Error) {
|
|
3503
|
-
const isStreamError = error.message.includes("Error in input stream") || error.message.includes("Load failed");
|
|
3504
|
-
const modifiedErrorMsg = isStreamError ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
|
|
3505
|
-
this._log(
|
|
3506
|
-
"ERROR",
|
|
3507
|
-
`[RoutstrClient] _handleError: Error type=${error.constructor.name}, message=${modifiedErrorMsg}, isStreamError=${isStreamError}`
|
|
3508
|
-
);
|
|
3509
|
-
callbacks.onMessageAppend({
|
|
3510
|
-
role: "system",
|
|
3511
|
-
content: "Uncaught Error: " + modifiedErrorMsg + (this.alertLevel === "max" ? " | " + error.stack : "")
|
|
3512
|
-
});
|
|
3513
|
-
} else {
|
|
3514
|
-
callbacks.onMessageAppend({
|
|
3515
|
-
role: "system",
|
|
3516
|
-
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
3517
|
-
});
|
|
3518
|
-
}
|
|
3519
|
-
}
|
|
3520
|
-
/**
|
|
3521
|
-
* Check wallet balance and throw if insufficient
|
|
3522
|
-
*/
|
|
3523
|
-
async _checkBalance() {
|
|
3524
|
-
const balances = await this.walletAdapter.getBalances();
|
|
3525
|
-
const totalBalance = Object.values(balances).reduce((sum, v) => sum + v, 0);
|
|
3526
|
-
if (totalBalance <= 0) {
|
|
3527
|
-
throw new InsufficientBalanceError(1, 0);
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
/**
|
|
3531
|
-
* Spend a token using CashuSpender with standardized error handling
|
|
3532
|
-
*/
|
|
3533
|
-
async _spendToken(params) {
|
|
3534
|
-
const { mintUrl, amount, baseUrl } = params;
|
|
3535
|
-
this._log(
|
|
3536
|
-
"DEBUG",
|
|
3537
|
-
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
3538
|
-
);
|
|
3539
|
-
if (this.mode === "apikeys") {
|
|
3540
|
-
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
3541
|
-
if (!parentApiKey) {
|
|
3542
|
-
this._log(
|
|
3543
|
-
"DEBUG",
|
|
3544
|
-
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
3545
|
-
);
|
|
3546
|
-
const spendResult2 = await this.cashuSpender.spend({
|
|
3547
|
-
mintUrl,
|
|
3548
|
-
amount: amount * TOPUP_MARGIN,
|
|
3549
|
-
baseUrl: "",
|
|
3550
|
-
reuseToken: false
|
|
3551
|
-
});
|
|
3552
|
-
if (!spendResult2.token) {
|
|
3553
|
-
this._log(
|
|
3554
|
-
"ERROR",
|
|
3555
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
3556
|
-
spendResult2.error
|
|
3557
|
-
);
|
|
3558
|
-
throw new Error(
|
|
3559
|
-
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
3560
|
-
);
|
|
3561
|
-
} else {
|
|
3562
|
-
this._log(
|
|
3563
|
-
"DEBUG",
|
|
3564
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
3565
|
-
);
|
|
3566
|
-
}
|
|
3567
|
-
this._log(
|
|
3568
|
-
"DEBUG",
|
|
3569
|
-
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
3570
|
-
);
|
|
3571
|
-
try {
|
|
3572
|
-
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
3573
|
-
} catch (error) {
|
|
3574
|
-
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
3575
|
-
const tryReceiveTokenResult = await this.cashuSpender.receiveToken(
|
|
3576
|
-
spendResult2.token
|
|
3577
|
-
);
|
|
3578
|
-
if (tryReceiveTokenResult.success) {
|
|
3579
|
-
this._log(
|
|
3580
|
-
"DEBUG",
|
|
3581
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
|
|
3582
|
-
);
|
|
3583
|
-
} else {
|
|
3584
|
-
this._log(
|
|
3585
|
-
"DEBUG",
|
|
3586
|
-
`[RoutstrClient] _handleErrorResponse: Token restore failed or not needed`
|
|
3587
|
-
);
|
|
3588
|
-
}
|
|
3589
|
-
this._log(
|
|
3590
|
-
"DEBUG",
|
|
3591
|
-
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
3592
|
-
);
|
|
3593
|
-
} else {
|
|
3594
|
-
throw error;
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
3598
|
-
} else {
|
|
3599
|
-
this._log(
|
|
3600
|
-
"DEBUG",
|
|
3601
|
-
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
3602
|
-
);
|
|
3603
|
-
}
|
|
3604
|
-
let tokenBalance = 0;
|
|
3605
|
-
let tokenBalanceUnit = "sat";
|
|
3606
|
-
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
3607
|
-
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
3608
|
-
(d) => d.baseUrl === baseUrl
|
|
3609
|
-
);
|
|
3610
|
-
if (distributionForBaseUrl) {
|
|
3611
|
-
tokenBalance = distributionForBaseUrl.amount;
|
|
3612
|
-
}
|
|
3613
|
-
if (tokenBalance === 0 && parentApiKey) {
|
|
3614
|
-
try {
|
|
3615
|
-
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
3616
|
-
parentApiKey.key,
|
|
3617
|
-
baseUrl
|
|
3618
|
-
);
|
|
3619
|
-
tokenBalance = balanceInfo.amount;
|
|
3620
|
-
tokenBalanceUnit = balanceInfo.unit;
|
|
3621
|
-
} catch (e) {
|
|
3622
|
-
this._log("WARN", "Could not get initial API key balance:", e);
|
|
3623
|
-
}
|
|
3624
|
-
}
|
|
3625
|
-
this._log(
|
|
3626
|
-
"DEBUG",
|
|
3627
|
-
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
3628
|
-
);
|
|
3629
|
-
return {
|
|
3630
|
-
token: parentApiKey?.key ?? "",
|
|
3631
|
-
tokenBalance,
|
|
3632
|
-
tokenBalanceUnit
|
|
3633
|
-
};
|
|
3634
|
-
}
|
|
3635
|
-
this._log(
|
|
3636
|
-
"DEBUG",
|
|
3637
|
-
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
3638
|
-
);
|
|
3639
|
-
const spendResult = await this.cashuSpender.spend({
|
|
3640
|
-
mintUrl,
|
|
3641
|
-
amount,
|
|
3642
|
-
baseUrl: this.mode === "lazyrefund" ? baseUrl : "",
|
|
3643
|
-
reuseToken: this.mode === "lazyrefund"
|
|
3589
|
+
},
|
|
3590
|
+
setApiKeys: (value) => {
|
|
3591
|
+
set((state) => {
|
|
3592
|
+
const updates = typeof value === "function" ? value(state.apiKeys) : value;
|
|
3593
|
+
const normalized = updates.map((entry) => ({
|
|
3594
|
+
...entry,
|
|
3595
|
+
baseUrl: normalizeBaseUrl4(entry.baseUrl),
|
|
3596
|
+
balance: entry.balance ?? 0,
|
|
3597
|
+
lastUsed: entry.lastUsed ?? null
|
|
3598
|
+
}));
|
|
3599
|
+
void driver.setItem(SDK_STORAGE_KEYS.API_KEYS, normalized);
|
|
3600
|
+
return { apiKeys: normalized };
|
|
3644
3601
|
});
|
|
3645
|
-
if (!spendResult.token) {
|
|
3646
|
-
this._log(
|
|
3647
|
-
"ERROR",
|
|
3648
|
-
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
3649
|
-
spendResult.error
|
|
3650
|
-
);
|
|
3651
|
-
} else {
|
|
3652
|
-
this._log(
|
|
3653
|
-
"DEBUG",
|
|
3654
|
-
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
3655
|
-
);
|
|
3656
|
-
}
|
|
3657
|
-
return {
|
|
3658
|
-
token: spendResult.token,
|
|
3659
|
-
tokenBalance: spendResult.balance,
|
|
3660
|
-
tokenBalanceUnit: spendResult.unit ?? "sat"
|
|
3661
|
-
};
|
|
3662
|
-
}
|
|
3663
|
-
/**
|
|
3664
|
-
* Build request headers with common defaults and dev mock controls
|
|
3665
|
-
*/
|
|
3666
|
-
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
3667
|
-
const headers = {
|
|
3668
|
-
...additionalHeaders,
|
|
3669
|
-
"Content-Type": "application/json"
|
|
3670
|
-
};
|
|
3671
|
-
return headers;
|
|
3672
|
-
}
|
|
3673
|
-
/**
|
|
3674
|
-
* Attach auth headers using the active client mode
|
|
3675
|
-
*/
|
|
3676
|
-
_withAuthHeader(headers, token) {
|
|
3677
|
-
const nextHeaders = { ...headers };
|
|
3678
|
-
if (this.mode === "xcashu") {
|
|
3679
|
-
nextHeaders["X-Cashu"] = token;
|
|
3680
|
-
} else {
|
|
3681
|
-
nextHeaders["Authorization"] = `Bearer ${token}`;
|
|
3682
|
-
}
|
|
3683
|
-
return nextHeaders;
|
|
3684
|
-
}
|
|
3685
|
-
};
|
|
3686
|
-
|
|
3687
|
-
// storage/drivers/localStorage.ts
|
|
3688
|
-
var canUseLocalStorage = () => {
|
|
3689
|
-
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
3690
|
-
};
|
|
3691
|
-
var isQuotaExceeded = (error) => {
|
|
3692
|
-
const e = error;
|
|
3693
|
-
return !!e && (e?.name === "QuotaExceededError" || e?.code === 22 || e?.code === 1014);
|
|
3694
|
-
};
|
|
3695
|
-
var NON_CRITICAL_KEYS = /* @__PURE__ */ new Set(["modelsFromAllProviders"]);
|
|
3696
|
-
var localStorageDriver = {
|
|
3697
|
-
async getItem(key, defaultValue) {
|
|
3698
|
-
if (!canUseLocalStorage()) return defaultValue;
|
|
3699
|
-
try {
|
|
3700
|
-
const item = window.localStorage.getItem(key);
|
|
3701
|
-
if (item === null) return defaultValue;
|
|
3702
|
-
try {
|
|
3703
|
-
return JSON.parse(item);
|
|
3704
|
-
} catch (parseError) {
|
|
3705
|
-
if (typeof defaultValue === "string") {
|
|
3706
|
-
return item;
|
|
3707
|
-
}
|
|
3708
|
-
throw parseError;
|
|
3709
|
-
}
|
|
3710
|
-
} catch (error) {
|
|
3711
|
-
console.error(`Error retrieving item with key "${key}":`, error);
|
|
3712
|
-
if (canUseLocalStorage()) {
|
|
3713
|
-
try {
|
|
3714
|
-
window.localStorage.removeItem(key);
|
|
3715
|
-
} catch (removeError) {
|
|
3716
|
-
console.error(
|
|
3717
|
-
`Error removing corrupted item with key "${key}":`,
|
|
3718
|
-
removeError
|
|
3719
|
-
);
|
|
3720
|
-
}
|
|
3721
|
-
}
|
|
3722
|
-
return defaultValue;
|
|
3723
|
-
}
|
|
3724
3602
|
},
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
} catch {
|
|
3740
|
-
}
|
|
3741
|
-
try {
|
|
3742
|
-
window.localStorage.setItem(key, JSON.stringify(value));
|
|
3743
|
-
return;
|
|
3744
|
-
} catch (retryError) {
|
|
3745
|
-
console.warn(
|
|
3746
|
-
`Storage quota exceeded; unable to persist key "${key}" after cleanup attempt.`,
|
|
3747
|
-
retryError
|
|
3748
|
-
);
|
|
3749
|
-
return;
|
|
3750
|
-
}
|
|
3751
|
-
}
|
|
3752
|
-
console.error(`Error storing item with key "${key}":`, error);
|
|
3753
|
-
}
|
|
3603
|
+
setChildKeys: (value) => {
|
|
3604
|
+
set((state) => {
|
|
3605
|
+
const updates = typeof value === "function" ? value(state.childKeys) : value;
|
|
3606
|
+
const normalized = updates.map((entry) => ({
|
|
3607
|
+
parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
|
|
3608
|
+
childKey: entry.childKey,
|
|
3609
|
+
balance: entry.balance ?? 0,
|
|
3610
|
+
balanceLimit: entry.balanceLimit,
|
|
3611
|
+
validityDate: entry.validityDate,
|
|
3612
|
+
createdAt: entry.createdAt ?? Date.now()
|
|
3613
|
+
}));
|
|
3614
|
+
void driver.setItem(SDK_STORAGE_KEYS.CHILD_KEYS, normalized);
|
|
3615
|
+
return { childKeys: normalized };
|
|
3616
|
+
});
|
|
3754
3617
|
},
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
}
|
|
3618
|
+
setRoutstr21Models: (value) => {
|
|
3619
|
+
void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
|
|
3620
|
+
set({ routstr21Models: value });
|
|
3621
|
+
},
|
|
3622
|
+
setRoutstr21ModelsLastUpdate: (value) => {
|
|
3623
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE, value);
|
|
3624
|
+
set({ lastRoutstr21ModelsUpdate: value });
|
|
3625
|
+
},
|
|
3626
|
+
setCachedReceiveTokens: (value) => {
|
|
3627
|
+
const normalized = value.map((entry) => ({
|
|
3628
|
+
token: entry.token,
|
|
3629
|
+
amount: entry.amount,
|
|
3630
|
+
unit: entry.unit || "sat",
|
|
3631
|
+
createdAt: entry.createdAt ?? Date.now()
|
|
3632
|
+
}));
|
|
3633
|
+
void driver.setItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, normalized);
|
|
3634
|
+
set({ cachedReceiveTokens: normalized });
|
|
3635
|
+
},
|
|
3636
|
+
setClientIds: (value) => {
|
|
3637
|
+
set((state) => {
|
|
3638
|
+
const updates = typeof value === "function" ? value(state.clientIds) : value;
|
|
3639
|
+
const normalized = updates.map((entry) => ({
|
|
3640
|
+
...entry,
|
|
3641
|
+
createdAt: entry.createdAt ?? Date.now(),
|
|
3642
|
+
lastUsed: entry.lastUsed ?? null
|
|
3643
|
+
}));
|
|
3644
|
+
void driver.setItem(SDK_STORAGE_KEYS.CLIENT_IDS, normalized);
|
|
3645
|
+
return { clientIds: normalized };
|
|
3646
|
+
});
|
|
3762
3647
|
}
|
|
3648
|
+
}));
|
|
3649
|
+
var hydrateStoreFromDriver = async (store, driver) => {
|
|
3650
|
+
const [
|
|
3651
|
+
rawModels,
|
|
3652
|
+
lastUsedModel,
|
|
3653
|
+
rawBaseUrls,
|
|
3654
|
+
lastBaseUrlsUpdate,
|
|
3655
|
+
rawDisabledProviders,
|
|
3656
|
+
rawMints,
|
|
3657
|
+
rawInfo,
|
|
3658
|
+
rawLastModelsUpdate,
|
|
3659
|
+
rawCachedTokens,
|
|
3660
|
+
rawApiKeys,
|
|
3661
|
+
rawChildKeys,
|
|
3662
|
+
rawRoutstr21Models,
|
|
3663
|
+
rawLastRoutstr21ModelsUpdate,
|
|
3664
|
+
rawCachedReceiveTokens,
|
|
3665
|
+
rawClientIds
|
|
3666
|
+
] = await Promise.all([
|
|
3667
|
+
driver.getItem(
|
|
3668
|
+
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
3669
|
+
{}
|
|
3670
|
+
),
|
|
3671
|
+
driver.getItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, null),
|
|
3672
|
+
driver.getItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, []),
|
|
3673
|
+
driver.getItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, null),
|
|
3674
|
+
driver.getItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, []),
|
|
3675
|
+
driver.getItem(
|
|
3676
|
+
SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
|
|
3677
|
+
{}
|
|
3678
|
+
),
|
|
3679
|
+
driver.getItem(
|
|
3680
|
+
SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS,
|
|
3681
|
+
{}
|
|
3682
|
+
),
|
|
3683
|
+
driver.getItem(
|
|
3684
|
+
SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
|
|
3685
|
+
{}
|
|
3686
|
+
),
|
|
3687
|
+
driver.getItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, []),
|
|
3688
|
+
driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []),
|
|
3689
|
+
driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []),
|
|
3690
|
+
driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
|
|
3691
|
+
driver.getItem(
|
|
3692
|
+
SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
|
|
3693
|
+
null
|
|
3694
|
+
),
|
|
3695
|
+
driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
|
|
3696
|
+
driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
|
|
3697
|
+
]);
|
|
3698
|
+
const modelsFromAllProviders = Object.fromEntries(
|
|
3699
|
+
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
3700
|
+
normalizeBaseUrl4(baseUrl),
|
|
3701
|
+
models
|
|
3702
|
+
])
|
|
3703
|
+
);
|
|
3704
|
+
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl4(url));
|
|
3705
|
+
const disabledProviders = rawDisabledProviders.map(
|
|
3706
|
+
(url) => normalizeBaseUrl4(url)
|
|
3707
|
+
);
|
|
3708
|
+
const mintsFromAllProviders = Object.fromEntries(
|
|
3709
|
+
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
3710
|
+
normalizeBaseUrl4(baseUrl),
|
|
3711
|
+
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
3712
|
+
])
|
|
3713
|
+
);
|
|
3714
|
+
const infoFromAllProviders = Object.fromEntries(
|
|
3715
|
+
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
3716
|
+
normalizeBaseUrl4(baseUrl),
|
|
3717
|
+
info
|
|
3718
|
+
])
|
|
3719
|
+
);
|
|
3720
|
+
const lastModelsUpdate = Object.fromEntries(
|
|
3721
|
+
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
3722
|
+
normalizeBaseUrl4(baseUrl),
|
|
3723
|
+
timestamp
|
|
3724
|
+
])
|
|
3725
|
+
);
|
|
3726
|
+
const cachedTokens = rawCachedTokens.map((entry) => ({
|
|
3727
|
+
...entry,
|
|
3728
|
+
baseUrl: normalizeBaseUrl4(entry.baseUrl),
|
|
3729
|
+
balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
|
|
3730
|
+
lastUsed: entry.lastUsed ?? null
|
|
3731
|
+
}));
|
|
3732
|
+
const apiKeys = rawApiKeys.map((entry) => ({
|
|
3733
|
+
...entry,
|
|
3734
|
+
baseUrl: normalizeBaseUrl4(entry.baseUrl),
|
|
3735
|
+
balance: entry.balance ?? 0,
|
|
3736
|
+
lastUsed: entry.lastUsed ?? null
|
|
3737
|
+
}));
|
|
3738
|
+
const childKeys = rawChildKeys.map((entry) => ({
|
|
3739
|
+
parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
|
|
3740
|
+
childKey: entry.childKey,
|
|
3741
|
+
balance: entry.balance ?? 0,
|
|
3742
|
+
balanceLimit: entry.balanceLimit,
|
|
3743
|
+
validityDate: entry.validityDate,
|
|
3744
|
+
createdAt: entry.createdAt ?? Date.now()
|
|
3745
|
+
}));
|
|
3746
|
+
const routstr21Models = rawRoutstr21Models;
|
|
3747
|
+
const lastRoutstr21ModelsUpdate = rawLastRoutstr21ModelsUpdate;
|
|
3748
|
+
const cachedReceiveTokens = rawCachedReceiveTokens?.map((entry) => ({
|
|
3749
|
+
token: entry.token,
|
|
3750
|
+
amount: entry.amount,
|
|
3751
|
+
unit: entry.unit || "sat",
|
|
3752
|
+
createdAt: entry.createdAt ?? Date.now()
|
|
3753
|
+
}));
|
|
3754
|
+
const clientIds = rawClientIds.map((entry) => ({
|
|
3755
|
+
...entry,
|
|
3756
|
+
createdAt: entry.createdAt ?? Date.now(),
|
|
3757
|
+
lastUsed: entry.lastUsed ?? null
|
|
3758
|
+
}));
|
|
3759
|
+
store.setState({
|
|
3760
|
+
modelsFromAllProviders,
|
|
3761
|
+
lastUsedModel,
|
|
3762
|
+
baseUrlsList,
|
|
3763
|
+
lastBaseUrlsUpdate,
|
|
3764
|
+
disabledProviders,
|
|
3765
|
+
mintsFromAllProviders,
|
|
3766
|
+
infoFromAllProviders,
|
|
3767
|
+
lastModelsUpdate,
|
|
3768
|
+
cachedTokens,
|
|
3769
|
+
apiKeys,
|
|
3770
|
+
childKeys,
|
|
3771
|
+
routstr21Models,
|
|
3772
|
+
lastRoutstr21ModelsUpdate,
|
|
3773
|
+
cachedReceiveTokens,
|
|
3774
|
+
clientIds
|
|
3775
|
+
});
|
|
3763
3776
|
};
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
const store =
|
|
3768
|
-
if (seed) {
|
|
3769
|
-
for (const [key, value] of Object.entries(seed)) {
|
|
3770
|
-
store.set(key, value);
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3777
|
+
var createSdkStore = ({
|
|
3778
|
+
driver
|
|
3779
|
+
}) => {
|
|
3780
|
+
const store = createEmptyStore(driver);
|
|
3773
3781
|
return {
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
if (item === void 0) return defaultValue;
|
|
3777
|
-
try {
|
|
3778
|
-
return JSON.parse(item);
|
|
3779
|
-
} catch (parseError) {
|
|
3780
|
-
if (typeof defaultValue === "string") {
|
|
3781
|
-
return item;
|
|
3782
|
-
}
|
|
3783
|
-
throw parseError;
|
|
3784
|
-
}
|
|
3785
|
-
},
|
|
3786
|
-
async setItem(key, value) {
|
|
3787
|
-
store.set(key, JSON.stringify(value));
|
|
3788
|
-
},
|
|
3789
|
-
async removeItem(key) {
|
|
3790
|
-
store.delete(key);
|
|
3791
|
-
}
|
|
3782
|
+
store,
|
|
3783
|
+
hydrate: hydrateStoreFromDriver(store, driver)
|
|
3792
3784
|
};
|
|
3793
3785
|
};
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
);
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
)
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
}
|
|
3845
|
-
},
|
|
3846
|
-
async setItem(key, value) {
|
|
3847
|
-
try {
|
|
3848
|
-
upsertStmt.run(key, JSON.stringify(value));
|
|
3849
|
-
} catch (error) {
|
|
3850
|
-
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
3851
|
-
}
|
|
3852
|
-
},
|
|
3853
|
-
async removeItem(key) {
|
|
3854
|
-
try {
|
|
3855
|
-
deleteStmt.run(key);
|
|
3856
|
-
} catch (error) {
|
|
3857
|
-
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
3858
|
-
}
|
|
3786
|
+
var createDiscoveryAdapterFromStore = (store) => ({
|
|
3787
|
+
getCachedModels: () => store.getState().modelsFromAllProviders,
|
|
3788
|
+
setCachedModels: (models) => store.getState().setModelsFromAllProviders(models),
|
|
3789
|
+
getCachedMints: () => store.getState().mintsFromAllProviders,
|
|
3790
|
+
setCachedMints: (mints) => store.getState().setMintsFromAllProviders(mints),
|
|
3791
|
+
getCachedProviderInfo: () => store.getState().infoFromAllProviders,
|
|
3792
|
+
setCachedProviderInfo: (info) => store.getState().setInfoFromAllProviders(info),
|
|
3793
|
+
getProviderLastUpdate: (baseUrl) => {
|
|
3794
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3795
|
+
const timestamps = store.getState().lastModelsUpdate;
|
|
3796
|
+
return timestamps[normalized] || null;
|
|
3797
|
+
},
|
|
3798
|
+
setProviderLastUpdate: (baseUrl, timestamp) => {
|
|
3799
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3800
|
+
const timestamps = { ...store.getState().lastModelsUpdate };
|
|
3801
|
+
timestamps[normalized] = timestamp;
|
|
3802
|
+
store.getState().setLastModelsUpdate(timestamps);
|
|
3803
|
+
},
|
|
3804
|
+
getLastUsedModel: () => store.getState().lastUsedModel,
|
|
3805
|
+
setLastUsedModel: (modelId) => store.getState().setLastUsedModel(modelId),
|
|
3806
|
+
getDisabledProviders: () => store.getState().disabledProviders,
|
|
3807
|
+
getBaseUrlsList: () => store.getState().baseUrlsList,
|
|
3808
|
+
setBaseUrlsList: (urls) => store.getState().setBaseUrlsList(urls),
|
|
3809
|
+
getBaseUrlsLastUpdate: () => store.getState().lastBaseUrlsUpdate,
|
|
3810
|
+
setBaseUrlsLastUpdate: (timestamp) => store.getState().setBaseUrlsLastUpdate(timestamp),
|
|
3811
|
+
getRoutstr21Models: () => store.getState().routstr21Models,
|
|
3812
|
+
setRoutstr21Models: (models) => store.getState().setRoutstr21Models(models),
|
|
3813
|
+
getRoutstr21ModelsLastUpdate: () => store.getState().lastRoutstr21ModelsUpdate,
|
|
3814
|
+
setRoutstr21ModelsLastUpdate: (timestamp) => store.getState().setRoutstr21ModelsLastUpdate(timestamp)
|
|
3815
|
+
});
|
|
3816
|
+
var createStorageAdapterFromStore = (store) => ({
|
|
3817
|
+
getToken: (baseUrl) => {
|
|
3818
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3819
|
+
const entry = store.getState().cachedTokens.find((token) => token.baseUrl === normalized);
|
|
3820
|
+
if (!entry) return null;
|
|
3821
|
+
const next = store.getState().cachedTokens.map(
|
|
3822
|
+
(token) => token.baseUrl === normalized ? { ...token, lastUsed: Date.now() } : token
|
|
3823
|
+
);
|
|
3824
|
+
store.getState().setCachedTokens(next);
|
|
3825
|
+
return entry.token;
|
|
3826
|
+
},
|
|
3827
|
+
setToken: (baseUrl, token) => {
|
|
3828
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3829
|
+
const tokens = store.getState().cachedTokens;
|
|
3830
|
+
const balance = getCashuTokenBalance(token);
|
|
3831
|
+
const existingIndex = tokens.findIndex(
|
|
3832
|
+
(entry) => entry.baseUrl === normalized
|
|
3833
|
+
);
|
|
3834
|
+
if (existingIndex !== -1) {
|
|
3835
|
+
throw new Error(`Token already exists for baseUrl: ${normalized}`);
|
|
3859
3836
|
}
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
}
|
|
3869
|
-
|
|
3870
|
-
const
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3837
|
+
const next = [...tokens];
|
|
3838
|
+
next.push({
|
|
3839
|
+
baseUrl: normalized,
|
|
3840
|
+
token,
|
|
3841
|
+
balance,
|
|
3842
|
+
lastUsed: Date.now()
|
|
3843
|
+
});
|
|
3844
|
+
store.getState().setCachedTokens(next);
|
|
3845
|
+
},
|
|
3846
|
+
removeToken: (baseUrl) => {
|
|
3847
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3848
|
+
const next = store.getState().cachedTokens.filter((entry) => entry.baseUrl !== normalized);
|
|
3849
|
+
store.getState().setCachedTokens(next);
|
|
3850
|
+
},
|
|
3851
|
+
updateTokenBalance: (baseUrl, balance) => {
|
|
3852
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3853
|
+
const tokens = store.getState().cachedTokens;
|
|
3854
|
+
const next = tokens.map(
|
|
3855
|
+
(entry) => entry.baseUrl === normalized ? { ...entry, balance } : entry
|
|
3856
|
+
);
|
|
3857
|
+
store.getState().setCachedTokens(next);
|
|
3858
|
+
},
|
|
3859
|
+
getCachedTokenDistribution: () => {
|
|
3860
|
+
const cachedTokens = store.getState().cachedTokens;
|
|
3861
|
+
const distributionMap = {};
|
|
3862
|
+
for (const entry of cachedTokens) {
|
|
3863
|
+
const sum = entry.balance || 0;
|
|
3864
|
+
if (sum > 0) {
|
|
3865
|
+
distributionMap[entry.baseUrl] = (distributionMap[entry.baseUrl] || 0) + sum;
|
|
3875
3866
|
}
|
|
3876
|
-
};
|
|
3877
|
-
request.onsuccess = () => resolve(request.result);
|
|
3878
|
-
request.onerror = () => reject(request.error);
|
|
3879
|
-
});
|
|
3880
|
-
};
|
|
3881
|
-
var createIndexedDBDriver = (options = {}) => {
|
|
3882
|
-
const dbName = options.dbName || "routstr-sdk";
|
|
3883
|
-
const storeName = options.storeName || "sdk_storage";
|
|
3884
|
-
let dbPromise = null;
|
|
3885
|
-
const getDb = () => {
|
|
3886
|
-
if (!dbPromise) {
|
|
3887
|
-
dbPromise = openDatabase(dbName, storeName);
|
|
3888
3867
|
}
|
|
3889
|
-
return
|
|
3890
|
-
}
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
const request = store.get(key);
|
|
3899
|
-
request.onsuccess = () => {
|
|
3900
|
-
const raw = request.result;
|
|
3901
|
-
if (raw === void 0) {
|
|
3902
|
-
resolve(defaultValue);
|
|
3903
|
-
return;
|
|
3904
|
-
}
|
|
3905
|
-
if (typeof raw === "string") {
|
|
3906
|
-
try {
|
|
3907
|
-
resolve(JSON.parse(raw));
|
|
3908
|
-
} catch {
|
|
3909
|
-
if (typeof defaultValue === "string") {
|
|
3910
|
-
resolve(raw);
|
|
3911
|
-
} else {
|
|
3912
|
-
resolve(defaultValue);
|
|
3913
|
-
}
|
|
3914
|
-
}
|
|
3915
|
-
} else {
|
|
3916
|
-
resolve(raw);
|
|
3917
|
-
}
|
|
3918
|
-
};
|
|
3919
|
-
request.onerror = () => reject(request.error);
|
|
3920
|
-
});
|
|
3921
|
-
} catch (error) {
|
|
3922
|
-
console.error(`IndexedDB getItem failed for key "${key}":`, error);
|
|
3923
|
-
return defaultValue;
|
|
3924
|
-
}
|
|
3925
|
-
},
|
|
3926
|
-
async setItem(key, value) {
|
|
3927
|
-
try {
|
|
3928
|
-
const db = await getDb();
|
|
3929
|
-
return new Promise((resolve, reject) => {
|
|
3930
|
-
const tx = db.transaction(storeName, "readwrite");
|
|
3931
|
-
const store = tx.objectStore(storeName);
|
|
3932
|
-
store.put(JSON.stringify(value), key);
|
|
3933
|
-
tx.oncomplete = () => resolve();
|
|
3934
|
-
tx.onerror = () => reject(tx.error);
|
|
3935
|
-
});
|
|
3936
|
-
} catch (error) {
|
|
3937
|
-
console.error(`IndexedDB setItem failed for key "${key}":`, error);
|
|
3938
|
-
}
|
|
3939
|
-
},
|
|
3940
|
-
async removeItem(key) {
|
|
3941
|
-
try {
|
|
3942
|
-
const db = await getDb();
|
|
3943
|
-
return new Promise((resolve, reject) => {
|
|
3944
|
-
const tx = db.transaction(storeName, "readwrite");
|
|
3945
|
-
const store = tx.objectStore(storeName);
|
|
3946
|
-
store.delete(key);
|
|
3947
|
-
tx.oncomplete = () => resolve();
|
|
3948
|
-
tx.onerror = () => reject(tx.error);
|
|
3949
|
-
});
|
|
3950
|
-
} catch (error) {
|
|
3951
|
-
console.error(`IndexedDB removeItem failed for key "${key}":`, error);
|
|
3868
|
+
return Object.entries(distributionMap).map(([baseUrl, amt]) => ({ baseUrl, amount: amt })).sort((a, b) => b.amount - a.amount);
|
|
3869
|
+
},
|
|
3870
|
+
getApiKeyDistribution: () => {
|
|
3871
|
+
const apiKeys = store.getState().apiKeys;
|
|
3872
|
+
const distributionMap = {};
|
|
3873
|
+
for (const entry of apiKeys) {
|
|
3874
|
+
const sum = entry.balance || 0;
|
|
3875
|
+
if (sum > 0) {
|
|
3876
|
+
distributionMap[entry.baseUrl] = (distributionMap[entry.baseUrl] || 0) + sum;
|
|
3952
3877
|
}
|
|
3953
3878
|
}
|
|
3954
|
-
|
|
3955
|
-
}
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
sum += proof.amount / unitDivisor;
|
|
3879
|
+
return Object.entries(distributionMap).map(([baseUrl, amt]) => ({ baseUrl, amount: amt })).sort((a, b) => b.amount - a.amount);
|
|
3880
|
+
},
|
|
3881
|
+
saveProviderInfo: (baseUrl, info) => {
|
|
3882
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3883
|
+
const next = { ...store.getState().infoFromAllProviders };
|
|
3884
|
+
next[normalized] = info;
|
|
3885
|
+
store.getState().setInfoFromAllProviders(next);
|
|
3886
|
+
},
|
|
3887
|
+
getProviderInfo: (baseUrl) => {
|
|
3888
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3889
|
+
return store.getState().infoFromAllProviders[normalized] || null;
|
|
3890
|
+
},
|
|
3891
|
+
// ========== API Keys (for apikeys mode) ==========
|
|
3892
|
+
getApiKey: (baseUrl) => {
|
|
3893
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3894
|
+
const entry = store.getState().apiKeys.find((key) => key.baseUrl === normalized);
|
|
3895
|
+
if (!entry) return null;
|
|
3896
|
+
const next = store.getState().apiKeys.map(
|
|
3897
|
+
(key) => key.baseUrl === normalized ? { ...key, lastUsed: Date.now() } : key
|
|
3898
|
+
);
|
|
3899
|
+
store.getState().setApiKeys(next);
|
|
3900
|
+
return entry;
|
|
3901
|
+
},
|
|
3902
|
+
setApiKey: (baseUrl, key) => {
|
|
3903
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3904
|
+
const keys = store.getState().apiKeys;
|
|
3905
|
+
const existingIndex = keys.findIndex(
|
|
3906
|
+
(entry) => entry.baseUrl === normalized
|
|
3907
|
+
);
|
|
3908
|
+
if (existingIndex !== -1) {
|
|
3909
|
+
throw new Error(`ApiKey already exists for baseUrl: ${normalized}`);
|
|
3986
3910
|
}
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
baseUrlsList: [],
|
|
3996
|
-
lastBaseUrlsUpdate: null,
|
|
3997
|
-
disabledProviders: [],
|
|
3998
|
-
mintsFromAllProviders: {},
|
|
3999
|
-
infoFromAllProviders: {},
|
|
4000
|
-
lastModelsUpdate: {},
|
|
4001
|
-
cachedTokens: [],
|
|
4002
|
-
apiKeys: [],
|
|
4003
|
-
childKeys: [],
|
|
4004
|
-
routstr21Models: [],
|
|
4005
|
-
lastRoutstr21ModelsUpdate: null,
|
|
4006
|
-
cachedReceiveTokens: [],
|
|
4007
|
-
usageTracking: [],
|
|
4008
|
-
clientIds: [],
|
|
4009
|
-
setModelsFromAllProviders: (value) => {
|
|
4010
|
-
const normalized = {};
|
|
4011
|
-
for (const [baseUrl, models] of Object.entries(value)) {
|
|
4012
|
-
normalized[normalizeBaseUrl(baseUrl)] = models;
|
|
4013
|
-
}
|
|
4014
|
-
void driver.setItem(
|
|
4015
|
-
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
4016
|
-
normalized
|
|
4017
|
-
);
|
|
4018
|
-
set({ modelsFromAllProviders: normalized });
|
|
3911
|
+
const next = [...keys];
|
|
3912
|
+
next.push({
|
|
3913
|
+
baseUrl: normalized,
|
|
3914
|
+
key,
|
|
3915
|
+
balance: 0,
|
|
3916
|
+
lastUsed: Date.now()
|
|
3917
|
+
});
|
|
3918
|
+
store.getState().setApiKeys(next);
|
|
4019
3919
|
},
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
3920
|
+
updateApiKeyBalance: (baseUrl, balance) => {
|
|
3921
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3922
|
+
const keys = store.getState().apiKeys;
|
|
3923
|
+
const next = keys.map(
|
|
3924
|
+
(entry) => entry.baseUrl === normalized ? { ...entry, balance } : entry
|
|
3925
|
+
);
|
|
3926
|
+
store.getState().setApiKeys(next);
|
|
4023
3927
|
},
|
|
4024
|
-
|
|
4025
|
-
const normalized =
|
|
4026
|
-
|
|
4027
|
-
|
|
3928
|
+
removeApiKey: (baseUrl) => {
|
|
3929
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
3930
|
+
const next = store.getState().apiKeys.filter((entry) => entry.baseUrl !== normalized);
|
|
3931
|
+
store.getState().setApiKeys(next);
|
|
4028
3932
|
},
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
3933
|
+
getAllApiKeys: () => {
|
|
3934
|
+
return store.getState().apiKeys.map((entry) => ({
|
|
3935
|
+
baseUrl: entry.baseUrl,
|
|
3936
|
+
key: entry.key,
|
|
3937
|
+
balance: entry.balance,
|
|
3938
|
+
lastUsed: entry.lastUsed
|
|
3939
|
+
}));
|
|
4032
3940
|
},
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
3941
|
+
// ========== Child Keys ==========
|
|
3942
|
+
getChildKey: (parentBaseUrl) => {
|
|
3943
|
+
const normalized = normalizeBaseUrl4(parentBaseUrl);
|
|
3944
|
+
const entry = store.getState().childKeys.find((key) => key.parentBaseUrl === normalized);
|
|
3945
|
+
if (!entry) return null;
|
|
3946
|
+
return {
|
|
3947
|
+
parentBaseUrl: entry.parentBaseUrl,
|
|
3948
|
+
childKey: entry.childKey,
|
|
3949
|
+
balance: entry.balance,
|
|
3950
|
+
balanceLimit: entry.balanceLimit,
|
|
3951
|
+
validityDate: entry.validityDate,
|
|
3952
|
+
createdAt: entry.createdAt
|
|
3953
|
+
};
|
|
4037
3954
|
},
|
|
4038
|
-
|
|
4039
|
-
const normalized =
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
);
|
|
4044
|
-
}
|
|
4045
|
-
void driver.setItem(
|
|
4046
|
-
SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
|
|
4047
|
-
normalized
|
|
3955
|
+
setChildKey: (parentBaseUrl, childKey, balance, validityDate, balanceLimit) => {
|
|
3956
|
+
const normalized = normalizeBaseUrl4(parentBaseUrl);
|
|
3957
|
+
const keys = store.getState().childKeys;
|
|
3958
|
+
const existingIndex = keys.findIndex(
|
|
3959
|
+
(entry) => entry.parentBaseUrl === normalized
|
|
4048
3960
|
);
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
3961
|
+
if (existingIndex !== -1) {
|
|
3962
|
+
const next = keys.map(
|
|
3963
|
+
(entry) => entry.parentBaseUrl === normalized ? {
|
|
3964
|
+
...entry,
|
|
3965
|
+
childKey,
|
|
3966
|
+
balance: balance ?? 0,
|
|
3967
|
+
validityDate,
|
|
3968
|
+
balanceLimit,
|
|
3969
|
+
createdAt: Date.now()
|
|
3970
|
+
} : entry
|
|
3971
|
+
);
|
|
3972
|
+
store.getState().setChildKeys(next);
|
|
3973
|
+
} else {
|
|
3974
|
+
const next = [...keys];
|
|
3975
|
+
next.push({
|
|
3976
|
+
parentBaseUrl: normalized,
|
|
3977
|
+
childKey,
|
|
3978
|
+
balance: balance ?? 0,
|
|
3979
|
+
validityDate,
|
|
3980
|
+
balanceLimit,
|
|
3981
|
+
createdAt: Date.now()
|
|
3982
|
+
});
|
|
3983
|
+
store.getState().setChildKeys(next);
|
|
4063
3984
|
}
|
|
4064
|
-
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
4065
|
-
set({ lastModelsUpdate: normalized });
|
|
4066
3985
|
},
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
lastUsed: entry.lastUsed ?? null
|
|
4075
|
-
}));
|
|
4076
|
-
void driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
|
|
4077
|
-
return { cachedTokens: normalized };
|
|
4078
|
-
});
|
|
3986
|
+
updateChildKeyBalance: (parentBaseUrl, balance) => {
|
|
3987
|
+
const normalized = normalizeBaseUrl4(parentBaseUrl);
|
|
3988
|
+
const keys = store.getState().childKeys;
|
|
3989
|
+
const next = keys.map(
|
|
3990
|
+
(entry) => entry.parentBaseUrl === normalized ? { ...entry, balance } : entry
|
|
3991
|
+
);
|
|
3992
|
+
store.getState().setChildKeys(next);
|
|
4079
3993
|
},
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
...entry,
|
|
4085
|
-
baseUrl: normalizeBaseUrl(entry.baseUrl),
|
|
4086
|
-
balance: entry.balance ?? 0,
|
|
4087
|
-
lastUsed: entry.lastUsed ?? null
|
|
4088
|
-
}));
|
|
4089
|
-
void driver.setItem(SDK_STORAGE_KEYS.API_KEYS, normalized);
|
|
4090
|
-
return { apiKeys: normalized };
|
|
4091
|
-
});
|
|
3994
|
+
removeChildKey: (parentBaseUrl) => {
|
|
3995
|
+
const normalized = normalizeBaseUrl4(parentBaseUrl);
|
|
3996
|
+
const next = store.getState().childKeys.filter((entry) => entry.parentBaseUrl !== normalized);
|
|
3997
|
+
store.getState().setChildKeys(next);
|
|
4092
3998
|
},
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
createdAt: entry.createdAt ?? Date.now()
|
|
4103
|
-
}));
|
|
4104
|
-
void driver.setItem(SDK_STORAGE_KEYS.CHILD_KEYS, normalized);
|
|
4105
|
-
return { childKeys: normalized };
|
|
4106
|
-
});
|
|
3999
|
+
getAllChildKeys: () => {
|
|
4000
|
+
return store.getState().childKeys.map((entry) => ({
|
|
4001
|
+
parentBaseUrl: entry.parentBaseUrl,
|
|
4002
|
+
childKey: entry.childKey,
|
|
4003
|
+
balance: entry.balance,
|
|
4004
|
+
balanceLimit: entry.balanceLimit,
|
|
4005
|
+
validityDate: entry.validityDate,
|
|
4006
|
+
createdAt: entry.createdAt
|
|
4007
|
+
}));
|
|
4107
4008
|
},
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
set({ routstr21Models: value });
|
|
4009
|
+
getCachedReceiveTokens: () => {
|
|
4010
|
+
return store.getState().cachedReceiveTokens;
|
|
4111
4011
|
},
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4012
|
+
setCachedReceiveTokens: (tokens) => {
|
|
4013
|
+
store.getState().setCachedReceiveTokens(tokens);
|
|
4014
|
+
}
|
|
4015
|
+
});
|
|
4016
|
+
var createProviderRegistryFromStore = (store) => ({
|
|
4017
|
+
getModelsForProvider: (baseUrl) => {
|
|
4018
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4019
|
+
return store.getState().modelsFromAllProviders[normalized] || [];
|
|
4115
4020
|
},
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
unit: entry.unit || "sat",
|
|
4121
|
-
createdAt: entry.createdAt ?? Date.now()
|
|
4122
|
-
}));
|
|
4123
|
-
void driver.setItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, normalized);
|
|
4124
|
-
set({ cachedReceiveTokens: normalized });
|
|
4021
|
+
getDisabledProviders: () => store.getState().disabledProviders,
|
|
4022
|
+
getProviderMints: (baseUrl) => {
|
|
4023
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4024
|
+
return store.getState().mintsFromAllProviders[normalized] || [];
|
|
4125
4025
|
},
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4026
|
+
getProviderInfo: async (baseUrl) => {
|
|
4027
|
+
const normalized = normalizeBaseUrl4(baseUrl);
|
|
4028
|
+
const cached = store.getState().infoFromAllProviders[normalized];
|
|
4029
|
+
if (cached) return cached;
|
|
4030
|
+
try {
|
|
4031
|
+
const response = await fetch(`${normalized}v1/info`);
|
|
4032
|
+
if (!response.ok) {
|
|
4033
|
+
throw new Error(`Failed ${response.status}`);
|
|
4034
|
+
}
|
|
4035
|
+
const info = await response.json();
|
|
4036
|
+
const next = { ...store.getState().infoFromAllProviders };
|
|
4037
|
+
next[normalized] = info;
|
|
4038
|
+
store.getState().setInfoFromAllProviders(next);
|
|
4039
|
+
return info;
|
|
4040
|
+
} catch (error) {
|
|
4041
|
+
console.warn(`Failed to fetch provider info from ${normalized}:`, error);
|
|
4042
|
+
return null;
|
|
4043
|
+
}
|
|
4129
4044
|
},
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
return { clientIds: normalized };
|
|
4140
|
-
});
|
|
4045
|
+
getAllProvidersModels: () => store.getState().modelsFromAllProviders
|
|
4046
|
+
});
|
|
4047
|
+
|
|
4048
|
+
// storage/index.ts
|
|
4049
|
+
var isBrowser3 = () => {
|
|
4050
|
+
try {
|
|
4051
|
+
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
4052
|
+
} catch {
|
|
4053
|
+
return false;
|
|
4141
4054
|
}
|
|
4142
|
-
}));
|
|
4143
|
-
var hydrateStoreFromDriver = async (store, driver) => {
|
|
4144
|
-
const [
|
|
4145
|
-
rawModels,
|
|
4146
|
-
lastUsedModel,
|
|
4147
|
-
rawBaseUrls,
|
|
4148
|
-
lastBaseUrlsUpdate,
|
|
4149
|
-
rawDisabledProviders,
|
|
4150
|
-
rawMints,
|
|
4151
|
-
rawInfo,
|
|
4152
|
-
rawLastModelsUpdate,
|
|
4153
|
-
rawCachedTokens,
|
|
4154
|
-
rawApiKeys,
|
|
4155
|
-
rawChildKeys,
|
|
4156
|
-
rawRoutstr21Models,
|
|
4157
|
-
rawLastRoutstr21ModelsUpdate,
|
|
4158
|
-
rawCachedReceiveTokens,
|
|
4159
|
-
rawUsageTracking,
|
|
4160
|
-
rawClientIds
|
|
4161
|
-
] = await Promise.all([
|
|
4162
|
-
driver.getItem(
|
|
4163
|
-
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
4164
|
-
{}
|
|
4165
|
-
),
|
|
4166
|
-
driver.getItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, null),
|
|
4167
|
-
driver.getItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, []),
|
|
4168
|
-
driver.getItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, null),
|
|
4169
|
-
driver.getItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, []),
|
|
4170
|
-
driver.getItem(
|
|
4171
|
-
SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
|
|
4172
|
-
{}
|
|
4173
|
-
),
|
|
4174
|
-
driver.getItem(
|
|
4175
|
-
SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS,
|
|
4176
|
-
{}
|
|
4177
|
-
),
|
|
4178
|
-
driver.getItem(
|
|
4179
|
-
SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
|
|
4180
|
-
{}
|
|
4181
|
-
),
|
|
4182
|
-
driver.getItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, []),
|
|
4183
|
-
driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []),
|
|
4184
|
-
driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []),
|
|
4185
|
-
driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
|
|
4186
|
-
driver.getItem(
|
|
4187
|
-
SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
|
|
4188
|
-
null
|
|
4189
|
-
),
|
|
4190
|
-
driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
|
|
4191
|
-
driver.getItem(SDK_STORAGE_KEYS.USAGE_TRACKING, []),
|
|
4192
|
-
driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
|
|
4193
|
-
]);
|
|
4194
|
-
const modelsFromAllProviders = Object.fromEntries(
|
|
4195
|
-
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
4196
|
-
normalizeBaseUrl(baseUrl),
|
|
4197
|
-
models
|
|
4198
|
-
])
|
|
4199
|
-
);
|
|
4200
|
-
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl(url));
|
|
4201
|
-
const disabledProviders = rawDisabledProviders.map(
|
|
4202
|
-
(url) => normalizeBaseUrl(url)
|
|
4203
|
-
);
|
|
4204
|
-
const mintsFromAllProviders = Object.fromEntries(
|
|
4205
|
-
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
4206
|
-
normalizeBaseUrl(baseUrl),
|
|
4207
|
-
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
4208
|
-
])
|
|
4209
|
-
);
|
|
4210
|
-
const infoFromAllProviders = Object.fromEntries(
|
|
4211
|
-
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
4212
|
-
normalizeBaseUrl(baseUrl),
|
|
4213
|
-
info
|
|
4214
|
-
])
|
|
4215
|
-
);
|
|
4216
|
-
const lastModelsUpdate = Object.fromEntries(
|
|
4217
|
-
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
4218
|
-
normalizeBaseUrl(baseUrl),
|
|
4219
|
-
timestamp
|
|
4220
|
-
])
|
|
4221
|
-
);
|
|
4222
|
-
const cachedTokens = rawCachedTokens.map((entry) => ({
|
|
4223
|
-
...entry,
|
|
4224
|
-
baseUrl: normalizeBaseUrl(entry.baseUrl),
|
|
4225
|
-
balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
|
|
4226
|
-
lastUsed: entry.lastUsed ?? null
|
|
4227
|
-
}));
|
|
4228
|
-
const apiKeys = rawApiKeys.map((entry) => ({
|
|
4229
|
-
...entry,
|
|
4230
|
-
baseUrl: normalizeBaseUrl(entry.baseUrl),
|
|
4231
|
-
balance: entry.balance ?? 0,
|
|
4232
|
-
lastUsed: entry.lastUsed ?? null
|
|
4233
|
-
}));
|
|
4234
|
-
const childKeys = rawChildKeys.map((entry) => ({
|
|
4235
|
-
parentBaseUrl: normalizeBaseUrl(entry.parentBaseUrl),
|
|
4236
|
-
childKey: entry.childKey,
|
|
4237
|
-
balance: entry.balance ?? 0,
|
|
4238
|
-
balanceLimit: entry.balanceLimit,
|
|
4239
|
-
validityDate: entry.validityDate,
|
|
4240
|
-
createdAt: entry.createdAt ?? Date.now()
|
|
4241
|
-
}));
|
|
4242
|
-
const routstr21Models = rawRoutstr21Models;
|
|
4243
|
-
const lastRoutstr21ModelsUpdate = rawLastRoutstr21ModelsUpdate;
|
|
4244
|
-
const cachedReceiveTokens = rawCachedReceiveTokens?.map((entry) => ({
|
|
4245
|
-
token: entry.token,
|
|
4246
|
-
amount: entry.amount,
|
|
4247
|
-
unit: entry.unit || "sat",
|
|
4248
|
-
createdAt: entry.createdAt ?? Date.now()
|
|
4249
|
-
}));
|
|
4250
|
-
const usageTracking = rawUsageTracking;
|
|
4251
|
-
const clientIds = rawClientIds.map((entry) => ({
|
|
4252
|
-
...entry,
|
|
4253
|
-
createdAt: entry.createdAt ?? Date.now(),
|
|
4254
|
-
lastUsed: entry.lastUsed ?? null
|
|
4255
|
-
}));
|
|
4256
|
-
store.setState({
|
|
4257
|
-
modelsFromAllProviders,
|
|
4258
|
-
lastUsedModel,
|
|
4259
|
-
baseUrlsList,
|
|
4260
|
-
lastBaseUrlsUpdate,
|
|
4261
|
-
disabledProviders,
|
|
4262
|
-
mintsFromAllProviders,
|
|
4263
|
-
infoFromAllProviders,
|
|
4264
|
-
lastModelsUpdate,
|
|
4265
|
-
cachedTokens,
|
|
4266
|
-
apiKeys,
|
|
4267
|
-
childKeys,
|
|
4268
|
-
routstr21Models,
|
|
4269
|
-
lastRoutstr21ModelsUpdate,
|
|
4270
|
-
cachedReceiveTokens,
|
|
4271
|
-
usageTracking,
|
|
4272
|
-
clientIds
|
|
4273
|
-
});
|
|
4274
4055
|
};
|
|
4275
|
-
var
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
hydrate: hydrateStoreFromDriver(store, driver)
|
|
4282
|
-
};
|
|
4056
|
+
var isNode = () => {
|
|
4057
|
+
try {
|
|
4058
|
+
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
4059
|
+
} catch {
|
|
4060
|
+
return false;
|
|
4061
|
+
}
|
|
4283
4062
|
};
|
|
4284
|
-
var
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
}
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4063
|
+
var defaultDriver = null;
|
|
4064
|
+
var isBun3 = () => {
|
|
4065
|
+
return typeof process.versions.bun !== "undefined";
|
|
4066
|
+
};
|
|
4067
|
+
var getDefaultSdkDriver = () => {
|
|
4068
|
+
if (defaultDriver) return defaultDriver;
|
|
4069
|
+
if (isBrowser3()) {
|
|
4070
|
+
defaultDriver = localStorageDriver;
|
|
4071
|
+
return defaultDriver;
|
|
4072
|
+
}
|
|
4073
|
+
if (isBun3()) {
|
|
4074
|
+
defaultDriver = createMemoryDriver();
|
|
4075
|
+
return defaultDriver;
|
|
4076
|
+
}
|
|
4077
|
+
if (isNode()) {
|
|
4078
|
+
defaultDriver = createSqliteDriver();
|
|
4079
|
+
return defaultDriver;
|
|
4080
|
+
}
|
|
4081
|
+
defaultDriver = createMemoryDriver();
|
|
4082
|
+
return defaultDriver;
|
|
4083
|
+
};
|
|
4084
|
+
var defaultStore = null;
|
|
4085
|
+
var defaultUsageTrackingDriver = null;
|
|
4086
|
+
var getDefaultSdkStore = () => {
|
|
4087
|
+
if (!defaultStore) {
|
|
4088
|
+
defaultStore = createSdkStore({ driver: getDefaultSdkDriver() });
|
|
4089
|
+
}
|
|
4090
|
+
return defaultStore.hydrate.then(() => defaultStore.store);
|
|
4091
|
+
};
|
|
4092
|
+
var getDefaultUsageTrackingDriver = () => {
|
|
4093
|
+
if (defaultUsageTrackingDriver) return defaultUsageTrackingDriver;
|
|
4094
|
+
const storageDriver = getDefaultSdkDriver();
|
|
4095
|
+
if (isBrowser3()) {
|
|
4096
|
+
defaultUsageTrackingDriver = createIndexedDBUsageTrackingDriver({
|
|
4097
|
+
legacyStorageDriver: storageDriver
|
|
4098
|
+
});
|
|
4099
|
+
return defaultUsageTrackingDriver;
|
|
4100
|
+
}
|
|
4101
|
+
if (isBun3()) {
|
|
4102
|
+
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
4103
|
+
return defaultUsageTrackingDriver;
|
|
4104
|
+
}
|
|
4105
|
+
if (isNode()) {
|
|
4106
|
+
defaultUsageTrackingDriver = createSqliteUsageTrackingDriver({
|
|
4107
|
+
legacyStorageDriver: storageDriver
|
|
4108
|
+
});
|
|
4109
|
+
return defaultUsageTrackingDriver;
|
|
4110
|
+
}
|
|
4111
|
+
defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
|
|
4112
|
+
return defaultUsageTrackingDriver;
|
|
4113
|
+
};
|
|
4114
|
+
var getDefaultDiscoveryAdapter = async () => createDiscoveryAdapterFromStore(await getDefaultSdkStore());
|
|
4115
|
+
var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
|
|
4116
|
+
var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
|
|
4117
|
+
function createSSEParserTransform(onUsage, onResponseId) {
|
|
4118
|
+
let buffer = "";
|
|
4119
|
+
const maybeCaptureUsageFromJson = (jsonText) => {
|
|
4120
|
+
try {
|
|
4121
|
+
const data = JSON.parse(jsonText);
|
|
4122
|
+
const responseId = data.id;
|
|
4123
|
+
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
4124
|
+
onResponseId?.(responseId.trim());
|
|
4125
|
+
}
|
|
4126
|
+
const usage = extractUsageFromSSEJson(data);
|
|
4127
|
+
if (usage) {
|
|
4128
|
+
onUsage(usage);
|
|
4129
|
+
}
|
|
4130
|
+
} catch {
|
|
4131
|
+
}
|
|
4132
|
+
};
|
|
4133
|
+
const processLine = (self, line) => {
|
|
4134
|
+
const trimmed = line.trim();
|
|
4135
|
+
if (!trimmed) {
|
|
4136
|
+
return;
|
|
4137
|
+
}
|
|
4138
|
+
if (trimmed === "data: [DONE]" || trimmed === "[DONE]") {
|
|
4139
|
+
self.push("data: [DONE]\n\n");
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
if (trimmed.startsWith("data:")) {
|
|
4143
|
+
const dataStr = trimmed.startsWith("data: ") ? trimmed.slice(6) : trimmed.slice(5).trimStart();
|
|
4144
|
+
if (dataStr === "[DONE]") {
|
|
4145
|
+
self.push("data: [DONE]\n\n");
|
|
4146
|
+
return;
|
|
4147
|
+
}
|
|
4148
|
+
maybeCaptureUsageFromJson(dataStr);
|
|
4149
|
+
self.push(`data: ${dataStr}
|
|
4150
|
+
|
|
4151
|
+
`);
|
|
4152
|
+
return;
|
|
4153
|
+
}
|
|
4154
|
+
if (trimmed.startsWith("{")) {
|
|
4155
|
+
maybeCaptureUsageFromJson(trimmed);
|
|
4156
|
+
self.push(`data: ${trimmed}
|
|
4157
|
+
|
|
4158
|
+
`);
|
|
4159
|
+
return;
|
|
4160
|
+
}
|
|
4161
|
+
self.push(line + "\n");
|
|
4162
|
+
};
|
|
4163
|
+
return new stream.Transform({
|
|
4164
|
+
transform(chunk, encoding, callback) {
|
|
4165
|
+
buffer += chunk.toString();
|
|
4166
|
+
const lines = buffer.split(/\r?\n/);
|
|
4167
|
+
buffer = lines.pop() || "";
|
|
4168
|
+
for (const line of lines) {
|
|
4169
|
+
processLine(this, line);
|
|
4170
|
+
}
|
|
4171
|
+
callback();
|
|
4172
|
+
},
|
|
4173
|
+
flush(callback) {
|
|
4174
|
+
if (buffer.trim()) {
|
|
4175
|
+
processLine(this, buffer);
|
|
4176
|
+
}
|
|
4177
|
+
buffer = "";
|
|
4178
|
+
callback();
|
|
4179
|
+
}
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
var TOPUP_MARGIN = 1.2;
|
|
4183
|
+
var RoutstrClient = class {
|
|
4184
|
+
constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu") {
|
|
4185
|
+
this.walletAdapter = walletAdapter;
|
|
4186
|
+
this.storageAdapter = storageAdapter;
|
|
4187
|
+
this.providerRegistry = providerRegistry;
|
|
4188
|
+
this.balanceManager = new BalanceManager(
|
|
4189
|
+
walletAdapter,
|
|
4190
|
+
storageAdapter,
|
|
4191
|
+
providerRegistry
|
|
4192
|
+
);
|
|
4193
|
+
this.cashuSpender = new CashuSpender(
|
|
4194
|
+
walletAdapter,
|
|
4195
|
+
storageAdapter,
|
|
4196
|
+
providerRegistry,
|
|
4197
|
+
this.balanceManager
|
|
4198
|
+
);
|
|
4199
|
+
this.streamProcessor = new StreamProcessor();
|
|
4200
|
+
this.providerManager = new ProviderManager(providerRegistry);
|
|
4201
|
+
this.alertLevel = alertLevel;
|
|
4202
|
+
if (mode === "lazyrefund") {
|
|
4203
|
+
this.mode = "apikeys";
|
|
4204
|
+
} else if (mode === "apikeys") {
|
|
4205
|
+
this.mode = "lazyrefund";
|
|
4206
|
+
} else {
|
|
4207
|
+
this.mode = mode;
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
cashuSpender;
|
|
4211
|
+
balanceManager;
|
|
4212
|
+
streamProcessor;
|
|
4213
|
+
providerManager;
|
|
4214
|
+
alertLevel;
|
|
4215
|
+
mode;
|
|
4216
|
+
debugLevel = "WARN";
|
|
4217
|
+
/**
|
|
4218
|
+
* Get the current client mode
|
|
4219
|
+
*/
|
|
4220
|
+
getMode() {
|
|
4221
|
+
return this.mode;
|
|
4222
|
+
}
|
|
4223
|
+
getDebugLevel() {
|
|
4224
|
+
return this.debugLevel;
|
|
4225
|
+
}
|
|
4226
|
+
setDebugLevel(level) {
|
|
4227
|
+
this.debugLevel = level;
|
|
4228
|
+
}
|
|
4229
|
+
_log(level, ...args) {
|
|
4230
|
+
const levelPriority = {
|
|
4231
|
+
DEBUG: 0,
|
|
4232
|
+
WARN: 1,
|
|
4233
|
+
ERROR: 2
|
|
4234
|
+
};
|
|
4235
|
+
if (levelPriority[level] >= levelPriority[this.debugLevel]) {
|
|
4236
|
+
switch (level) {
|
|
4237
|
+
case "DEBUG":
|
|
4238
|
+
console.log(...args);
|
|
4239
|
+
break;
|
|
4240
|
+
case "WARN":
|
|
4241
|
+
console.warn(...args);
|
|
4242
|
+
break;
|
|
4243
|
+
case "ERROR":
|
|
4244
|
+
console.error(...args);
|
|
4245
|
+
break;
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
/**
|
|
4250
|
+
* Get the CashuSpender instance
|
|
4251
|
+
*/
|
|
4252
|
+
getCashuSpender() {
|
|
4253
|
+
return this.cashuSpender;
|
|
4254
|
+
}
|
|
4255
|
+
/**
|
|
4256
|
+
* Get the BalanceManager instance
|
|
4257
|
+
*/
|
|
4258
|
+
getBalanceManager() {
|
|
4259
|
+
return this.balanceManager;
|
|
4260
|
+
}
|
|
4261
|
+
/**
|
|
4262
|
+
* Get the ProviderManager instance
|
|
4263
|
+
*/
|
|
4264
|
+
getProviderManager() {
|
|
4265
|
+
return this.providerManager;
|
|
4266
|
+
}
|
|
4267
|
+
/**
|
|
4268
|
+
* Check if the client is currently busy (in critical section)
|
|
4269
|
+
*/
|
|
4270
|
+
get isBusy() {
|
|
4271
|
+
return this.cashuSpender.isBusy;
|
|
4272
|
+
}
|
|
4273
|
+
/**
|
|
4274
|
+
* Route an API request to the upstream provider
|
|
4275
|
+
*
|
|
4276
|
+
* This is a simpler alternative to fetchAIResponse that just proxies
|
|
4277
|
+
* the request upstream without the streaming callback machinery.
|
|
4278
|
+
* Useful for daemon-style routing where you just need to forward
|
|
4279
|
+
* requests and get responses back.
|
|
4280
|
+
*/
|
|
4281
|
+
async routeRequest(params) {
|
|
4282
|
+
const prepared = await this._prepareRoutedRequest(params);
|
|
4283
|
+
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
4284
|
+
token: prepared.tokenUsed,
|
|
4285
|
+
baseUrl: prepared.baseUrlUsed,
|
|
4286
|
+
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
4287
|
+
response: prepared.response,
|
|
4288
|
+
modelId: prepared.modelId,
|
|
4289
|
+
usage: prepared.capturedUsage,
|
|
4290
|
+
requestId: prepared.capturedResponseId
|
|
4291
|
+
});
|
|
4292
|
+
prepared.response.satsSpent = satsSpent;
|
|
4293
|
+
prepared.response.usage = prepared.capturedUsage;
|
|
4294
|
+
prepared.response.requestId = prepared.capturedResponseId;
|
|
4295
|
+
return prepared.response;
|
|
4296
|
+
}
|
|
4297
|
+
async routeRequestToNodeResponse(params) {
|
|
4298
|
+
const { res } = params;
|
|
4299
|
+
const prepared = await this._prepareRoutedRequest(params);
|
|
4300
|
+
res.statusCode = prepared.response.status;
|
|
4301
|
+
prepared.response.headers.forEach((value, key) => {
|
|
4302
|
+
res.setHeader(key, value);
|
|
4303
|
+
});
|
|
4304
|
+
const body = prepared.response.body;
|
|
4305
|
+
if (!body) {
|
|
4306
|
+
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
4307
|
+
token: prepared.tokenUsed,
|
|
4308
|
+
baseUrl: prepared.baseUrlUsed,
|
|
4309
|
+
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
4310
|
+
response: prepared.response,
|
|
4311
|
+
modelId: prepared.modelId,
|
|
4312
|
+
usage: prepared.capturedUsage,
|
|
4313
|
+
requestId: prepared.capturedResponseId
|
|
4314
|
+
});
|
|
4315
|
+
prepared.response.satsSpent = satsSpent;
|
|
4316
|
+
res.end();
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
const nodeReadable = stream.Readable.fromWeb(body);
|
|
4320
|
+
await new Promise((resolve, reject) => {
|
|
4321
|
+
let settled = false;
|
|
4322
|
+
const finish = async () => {
|
|
4323
|
+
if (settled) return;
|
|
4324
|
+
settled = true;
|
|
4325
|
+
try {
|
|
4326
|
+
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
4327
|
+
token: prepared.tokenUsed,
|
|
4328
|
+
baseUrl: prepared.baseUrlUsed,
|
|
4329
|
+
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
4330
|
+
response: prepared.response,
|
|
4331
|
+
modelId: prepared.modelId,
|
|
4332
|
+
usage: prepared.capturedUsage,
|
|
4333
|
+
requestId: prepared.capturedResponseId
|
|
4334
|
+
});
|
|
4335
|
+
prepared.response.satsSpent = satsSpent;
|
|
4336
|
+
prepared.response.usage = prepared.capturedUsage;
|
|
4337
|
+
prepared.response.requestId = prepared.capturedResponseId;
|
|
4338
|
+
resolve();
|
|
4339
|
+
} catch (error) {
|
|
4340
|
+
reject(error);
|
|
4341
|
+
}
|
|
4342
|
+
};
|
|
4343
|
+
const fail = (error) => {
|
|
4344
|
+
if (settled) return;
|
|
4345
|
+
settled = true;
|
|
4346
|
+
reject(error);
|
|
4347
|
+
};
|
|
4348
|
+
res.once("finish", finish);
|
|
4349
|
+
res.once("close", finish);
|
|
4350
|
+
res.once("error", fail);
|
|
4351
|
+
nodeReadable.once("error", fail);
|
|
4352
|
+
nodeReadable.pipe(res);
|
|
4353
|
+
});
|
|
4354
|
+
}
|
|
4355
|
+
async _prepareRoutedRequest(params) {
|
|
4356
|
+
const {
|
|
4357
|
+
path,
|
|
4358
|
+
method,
|
|
4359
|
+
body,
|
|
4360
|
+
headers = {},
|
|
4361
|
+
baseUrl,
|
|
4362
|
+
mintUrl,
|
|
4363
|
+
modelId
|
|
4364
|
+
} = params;
|
|
4365
|
+
await this._checkBalance();
|
|
4366
|
+
let requiredSats = 1;
|
|
4367
|
+
let selectedModel;
|
|
4368
|
+
if (modelId) {
|
|
4369
|
+
const providerModel = await this.providerManager.getModelForProvider(
|
|
4370
|
+
baseUrl,
|
|
4371
|
+
modelId
|
|
4372
|
+
);
|
|
4373
|
+
selectedModel = providerModel ?? void 0;
|
|
4374
|
+
if (selectedModel) {
|
|
4375
|
+
requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
4376
|
+
selectedModel,
|
|
4377
|
+
[]
|
|
4378
|
+
);
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
const { token, tokenBalance, tokenBalanceUnit } = await this._spendToken({
|
|
4382
|
+
mintUrl,
|
|
4383
|
+
amount: requiredSats,
|
|
4384
|
+
baseUrl
|
|
4385
|
+
});
|
|
4386
|
+
this._log("DEBUG", token, baseUrl);
|
|
4387
|
+
let requestBody = body;
|
|
4388
|
+
if (body && typeof body === "object") {
|
|
4389
|
+
const bodyObj = body;
|
|
4390
|
+
if (!bodyObj.stream) {
|
|
4391
|
+
requestBody = { ...bodyObj, stream: false };
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
const baseHeaders = this._buildBaseHeaders(headers);
|
|
4395
|
+
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
4396
|
+
const response = await this._makeRequest({
|
|
4397
|
+
path,
|
|
4398
|
+
method,
|
|
4399
|
+
body: method === "GET" ? void 0 : requestBody,
|
|
4400
|
+
baseUrl,
|
|
4401
|
+
mintUrl,
|
|
4402
|
+
token,
|
|
4403
|
+
requiredSats,
|
|
4404
|
+
headers: requestHeaders,
|
|
4405
|
+
baseHeaders,
|
|
4406
|
+
selectedModel
|
|
4407
|
+
});
|
|
4408
|
+
const tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
4409
|
+
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
4410
|
+
const tokenUsed = response.token || token;
|
|
4411
|
+
const contentType = response.headers.get("content-type") || "";
|
|
4412
|
+
let processedResponse = response;
|
|
4413
|
+
let capturedUsage;
|
|
4414
|
+
let capturedResponseId;
|
|
4415
|
+
if (contentType.includes("text/event-stream") && response.body) {
|
|
4416
|
+
const nodeReadable = stream.Readable.fromWeb(response.body);
|
|
4417
|
+
const sseParser = createSSEParserTransform(
|
|
4418
|
+
(usage) => {
|
|
4419
|
+
capturedUsage = usage;
|
|
4420
|
+
processedResponse.usage = usage;
|
|
4421
|
+
},
|
|
4422
|
+
(responseId) => {
|
|
4423
|
+
capturedResponseId = responseId;
|
|
4424
|
+
processedResponse.requestId = responseId;
|
|
4425
|
+
}
|
|
4426
|
+
);
|
|
4427
|
+
const transformed = nodeReadable.pipe(sseParser, { end: true });
|
|
4428
|
+
const webStream = stream.Readable.toWeb(
|
|
4429
|
+
transformed
|
|
4430
|
+
);
|
|
4431
|
+
processedResponse = new Response(webStream, {
|
|
4432
|
+
status: response.status,
|
|
4433
|
+
statusText: response.statusText,
|
|
4434
|
+
headers: response.headers
|
|
4435
|
+
});
|
|
4436
|
+
processedResponse.baseUrl = response.baseUrl;
|
|
4437
|
+
processedResponse.token = response.token;
|
|
4438
|
+
}
|
|
4439
|
+
return {
|
|
4440
|
+
response: processedResponse,
|
|
4441
|
+
tokenUsed,
|
|
4442
|
+
baseUrlUsed,
|
|
4443
|
+
tokenBalanceInSats,
|
|
4444
|
+
modelId,
|
|
4445
|
+
capturedUsage,
|
|
4446
|
+
capturedResponseId
|
|
4447
|
+
};
|
|
4448
|
+
}
|
|
4449
|
+
/**
|
|
4450
|
+
* Fetch AI response with streaming
|
|
4451
|
+
*/
|
|
4452
|
+
async fetchAIResponse(options, callbacks) {
|
|
4453
|
+
const {
|
|
4454
|
+
messageHistory,
|
|
4455
|
+
selectedModel,
|
|
4456
|
+
baseUrl,
|
|
4457
|
+
mintUrl,
|
|
4458
|
+
balance,
|
|
4459
|
+
transactionHistory,
|
|
4460
|
+
maxTokens,
|
|
4461
|
+
headers
|
|
4462
|
+
} = options;
|
|
4463
|
+
const apiMessages = await this._convertMessages(messageHistory);
|
|
4464
|
+
const requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
4465
|
+
selectedModel,
|
|
4466
|
+
apiMessages,
|
|
4467
|
+
maxTokens
|
|
4468
|
+
);
|
|
4469
|
+
try {
|
|
4470
|
+
await this._checkBalance();
|
|
4471
|
+
callbacks.onPaymentProcessing?.(true);
|
|
4472
|
+
const spendResult = await this._spendToken({
|
|
4473
|
+
mintUrl,
|
|
4474
|
+
amount: requiredSats,
|
|
4475
|
+
baseUrl
|
|
4476
|
+
});
|
|
4477
|
+
let token = spendResult.token;
|
|
4478
|
+
let tokenBalance = spendResult.tokenBalance;
|
|
4479
|
+
let tokenBalanceUnit = spendResult.tokenBalanceUnit;
|
|
4480
|
+
const tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
4481
|
+
callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
|
|
4482
|
+
const baseHeaders = this._buildBaseHeaders(headers);
|
|
4483
|
+
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
4484
|
+
this.providerManager.resetFailedProviders();
|
|
4485
|
+
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
4486
|
+
const providerVersion = providerInfo?.version ?? "";
|
|
4487
|
+
let modelIdForRequest = selectedModel.id;
|
|
4488
|
+
if (/^0\.1\./.test(providerVersion)) {
|
|
4489
|
+
const newModel = await this.providerManager.getModelForProvider(
|
|
4490
|
+
baseUrl,
|
|
4491
|
+
selectedModel.id
|
|
4492
|
+
);
|
|
4493
|
+
modelIdForRequest = newModel?.id ?? selectedModel.id;
|
|
4494
|
+
}
|
|
4495
|
+
const body = {
|
|
4496
|
+
model: modelIdForRequest,
|
|
4497
|
+
messages: apiMessages,
|
|
4498
|
+
stream: true
|
|
4499
|
+
};
|
|
4500
|
+
if (maxTokens !== void 0) {
|
|
4501
|
+
body.max_tokens = maxTokens;
|
|
4502
|
+
}
|
|
4503
|
+
if (selectedModel?.name?.startsWith("OpenAI:")) {
|
|
4504
|
+
body.tools = [{ type: "web_search" }];
|
|
4505
|
+
}
|
|
4506
|
+
const response = await this._makeRequest({
|
|
4507
|
+
path: "/v1/chat/completions",
|
|
4508
|
+
method: "POST",
|
|
4509
|
+
body,
|
|
4510
|
+
selectedModel,
|
|
4511
|
+
baseUrl,
|
|
4512
|
+
mintUrl,
|
|
4513
|
+
token,
|
|
4514
|
+
requiredSats,
|
|
4515
|
+
maxTokens,
|
|
4516
|
+
headers: requestHeaders,
|
|
4517
|
+
baseHeaders
|
|
4518
|
+
});
|
|
4519
|
+
if (!response.body) {
|
|
4520
|
+
throw new Error("Response body is not available");
|
|
4521
|
+
}
|
|
4522
|
+
if (response.status === 200) {
|
|
4523
|
+
const baseUrlUsed = response.baseUrl || baseUrl;
|
|
4524
|
+
const streamingResult = await this.streamProcessor.process(
|
|
4525
|
+
response,
|
|
4526
|
+
{
|
|
4527
|
+
onContent: callbacks.onStreamingUpdate,
|
|
4528
|
+
onThinking: callbacks.onThinkingUpdate
|
|
4529
|
+
},
|
|
4530
|
+
selectedModel.id
|
|
4531
|
+
);
|
|
4532
|
+
if (streamingResult.finish_reason === "content_filter") {
|
|
4533
|
+
callbacks.onMessageAppend({
|
|
4534
|
+
role: "assistant",
|
|
4535
|
+
content: "Your request was denied due to content filtering."
|
|
4536
|
+
});
|
|
4537
|
+
} else if (streamingResult.content || streamingResult.images && streamingResult.images.length > 0) {
|
|
4538
|
+
const message = await this._createAssistantMessage(streamingResult);
|
|
4539
|
+
callbacks.onMessageAppend(message);
|
|
4540
|
+
} else {
|
|
4541
|
+
callbacks.onMessageAppend({
|
|
4542
|
+
role: "system",
|
|
4543
|
+
content: "The provider did not respond to this request."
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
callbacks.onStreamingUpdate("");
|
|
4547
|
+
callbacks.onThinkingUpdate("");
|
|
4548
|
+
const isApikeysEstimate = this.mode === "apikeys";
|
|
4549
|
+
let satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
4550
|
+
token,
|
|
4551
|
+
baseUrl: baseUrlUsed,
|
|
4552
|
+
initialTokenBalance: tokenBalanceInSats,
|
|
4553
|
+
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
4554
|
+
response,
|
|
4555
|
+
modelId: selectedModel.id,
|
|
4556
|
+
usage: streamingResult.usage ? {
|
|
4557
|
+
promptTokens: Number(streamingResult.usage.prompt_tokens ?? 0),
|
|
4558
|
+
completionTokens: Number(
|
|
4559
|
+
streamingResult.usage.completion_tokens ?? 0
|
|
4560
|
+
),
|
|
4561
|
+
totalTokens: Number(streamingResult.usage.total_tokens ?? 0),
|
|
4562
|
+
cost: Number(streamingResult.usage.cost ?? 0),
|
|
4563
|
+
satsCost: Number(streamingResult.usage.sats_cost ?? 0)
|
|
4564
|
+
} : void 0,
|
|
4565
|
+
requestId: streamingResult.responseId
|
|
4566
|
+
});
|
|
4567
|
+
const estimatedCosts = this._getEstimatedCosts(
|
|
4568
|
+
selectedModel,
|
|
4569
|
+
streamingResult
|
|
4570
|
+
);
|
|
4571
|
+
const onLastMessageSatsUpdate = callbacks.onLastMessageSatsUpdate;
|
|
4572
|
+
onLastMessageSatsUpdate?.(satsSpent, estimatedCosts);
|
|
4573
|
+
} else {
|
|
4574
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
4575
|
+
}
|
|
4576
|
+
} catch (error) {
|
|
4577
|
+
this._handleError(error, callbacks);
|
|
4578
|
+
} finally {
|
|
4579
|
+
callbacks.onPaymentProcessing?.(false);
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
/**
|
|
4583
|
+
* Make the API request with failover support
|
|
4584
|
+
*/
|
|
4585
|
+
async _makeRequest(params) {
|
|
4586
|
+
const { path, method, body, baseUrl, token, headers } = params;
|
|
4587
|
+
try {
|
|
4588
|
+
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
4589
|
+
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
4590
|
+
this._log("DEBUG", "HEADERS,", headers);
|
|
4591
|
+
const response = await fetch(url, {
|
|
4592
|
+
method,
|
|
4593
|
+
headers,
|
|
4594
|
+
body: body === void 0 || method === "GET" ? void 0 : JSON.stringify(body)
|
|
4595
|
+
});
|
|
4596
|
+
if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
|
|
4597
|
+
response.baseUrl = baseUrl;
|
|
4598
|
+
response.token = token;
|
|
4599
|
+
if (!response.ok) {
|
|
4600
|
+
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
4601
|
+
let bodyText;
|
|
4602
|
+
try {
|
|
4603
|
+
bodyText = await response.text();
|
|
4604
|
+
} catch (e) {
|
|
4605
|
+
bodyText = void 0;
|
|
4606
|
+
}
|
|
4607
|
+
return await this._handleErrorResponse(
|
|
4608
|
+
params,
|
|
4609
|
+
token,
|
|
4610
|
+
response.status,
|
|
4611
|
+
requestId,
|
|
4612
|
+
this.mode === "xcashu" ? response.headers.get("x-cashu") ?? void 0 : void 0,
|
|
4613
|
+
bodyText,
|
|
4614
|
+
params.retryCount ?? 0
|
|
4615
|
+
);
|
|
4616
|
+
}
|
|
4617
|
+
return response;
|
|
4618
|
+
} catch (error) {
|
|
4619
|
+
if (isNetworkErrorMessage(error?.message || "")) {
|
|
4620
|
+
return await this._handleErrorResponse(
|
|
4621
|
+
params,
|
|
4622
|
+
token,
|
|
4623
|
+
-1,
|
|
4624
|
+
// just for Network Error to skip all statuses
|
|
4625
|
+
void 0,
|
|
4626
|
+
void 0,
|
|
4627
|
+
void 0,
|
|
4628
|
+
params.retryCount ?? 0
|
|
4629
|
+
);
|
|
4630
|
+
}
|
|
4631
|
+
throw error;
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
/**
|
|
4635
|
+
* Handle error responses with failover
|
|
4636
|
+
*/
|
|
4637
|
+
async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
|
|
4638
|
+
const MAX_RETRIES_PER_PROVIDER = 2;
|
|
4639
|
+
const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
|
|
4640
|
+
let tryNextProvider = false;
|
|
4641
|
+
this._log(
|
|
4642
|
+
"DEBUG",
|
|
4643
|
+
`[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}`
|
|
4644
|
+
);
|
|
4645
|
+
this._log(
|
|
4646
|
+
"DEBUG",
|
|
4647
|
+
`[RoutstrClient] _handleErrorResponse: Attempting to receive/restore token for ${baseUrl}`
|
|
4648
|
+
);
|
|
4649
|
+
if (params.token.startsWith("cashu")) {
|
|
4650
|
+
const tryReceiveTokenResult = await this.cashuSpender.receiveToken(
|
|
4651
|
+
params.token
|
|
4652
|
+
);
|
|
4653
|
+
if (tryReceiveTokenResult.success) {
|
|
4654
|
+
this._log(
|
|
4655
|
+
"DEBUG",
|
|
4656
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
|
|
4657
|
+
);
|
|
4658
|
+
tryNextProvider = true;
|
|
4659
|
+
if (this.mode === "lazyrefund")
|
|
4660
|
+
this.storageAdapter.removeToken(baseUrl);
|
|
4661
|
+
} else {
|
|
4662
|
+
this._log(
|
|
4663
|
+
"DEBUG",
|
|
4664
|
+
`[RoutstrClient] _handleErrorResponse: Failed to receive token. `
|
|
4665
|
+
);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
if (this.mode === "xcashu") {
|
|
4669
|
+
if (xCashuRefundToken) {
|
|
4670
|
+
this._log(
|
|
4671
|
+
"DEBUG",
|
|
4672
|
+
`[RoutstrClient] _handleErrorResponse: Attempting to receive xcashu refund token, preview=${xCashuRefundToken.substring(0, 20)}...`
|
|
4673
|
+
);
|
|
4674
|
+
try {
|
|
4675
|
+
const receiveResult = await this.cashuSpender.receiveToken(xCashuRefundToken);
|
|
4676
|
+
if (receiveResult.success) {
|
|
4677
|
+
this._log(
|
|
4678
|
+
"DEBUG",
|
|
4679
|
+
`[RoutstrClient] _handleErrorResponse: xcashu refund received, amount=${receiveResult.amount}`
|
|
4680
|
+
);
|
|
4681
|
+
tryNextProvider = true;
|
|
4682
|
+
} else
|
|
4683
|
+
throw new ProviderError(
|
|
4684
|
+
baseUrl,
|
|
4685
|
+
status,
|
|
4686
|
+
"xcashu refund failed",
|
|
4687
|
+
requestId
|
|
4688
|
+
);
|
|
4689
|
+
} catch (error) {
|
|
4690
|
+
this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
|
|
4691
|
+
throw new ProviderError(
|
|
4692
|
+
baseUrl,
|
|
4693
|
+
status,
|
|
4694
|
+
"[xcashu] Failed to receive refund token",
|
|
4695
|
+
requestId
|
|
4696
|
+
);
|
|
4697
|
+
}
|
|
4698
|
+
} else {
|
|
4699
|
+
if (!tryNextProvider)
|
|
4700
|
+
throw new ProviderError(
|
|
4701
|
+
baseUrl,
|
|
4702
|
+
status,
|
|
4703
|
+
"[xcashu] Failed to receive refund token",
|
|
4704
|
+
requestId
|
|
4705
|
+
);
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
if (status === 402 && !tryNextProvider && (this.mode === "apikeys" || this.mode === "lazyrefund")) {
|
|
4709
|
+
this.storageAdapter.getApiKey(baseUrl);
|
|
4710
|
+
let topupAmount = params.requiredSats;
|
|
4711
|
+
try {
|
|
4712
|
+
let currentBalance = 0;
|
|
4713
|
+
if (this.mode === "apikeys") {
|
|
4714
|
+
const currentBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
4715
|
+
params.token,
|
|
4716
|
+
baseUrl
|
|
4717
|
+
);
|
|
4718
|
+
currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
4719
|
+
} else if (this.mode === "lazyrefund") {
|
|
4720
|
+
const distribution = this.storageAdapter.getCachedTokenDistribution();
|
|
4721
|
+
const tokenEntry = distribution.find((t) => t.baseUrl === baseUrl);
|
|
4722
|
+
currentBalance = tokenEntry?.amount ?? 0;
|
|
4723
|
+
}
|
|
4724
|
+
const shortfall = Math.max(0, params.requiredSats - currentBalance);
|
|
4725
|
+
topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
|
|
4726
|
+
} catch (e) {
|
|
4727
|
+
this._log(
|
|
4728
|
+
"WARN",
|
|
4729
|
+
"Could not get current token balance for topup calculation:",
|
|
4730
|
+
e
|
|
4731
|
+
);
|
|
4732
|
+
}
|
|
4733
|
+
const topupResult = await this.balanceManager.topUp({
|
|
4734
|
+
mintUrl,
|
|
4735
|
+
baseUrl,
|
|
4736
|
+
amount: topupAmount * TOPUP_MARGIN,
|
|
4737
|
+
token: params.token
|
|
4738
|
+
});
|
|
4739
|
+
this._log(
|
|
4740
|
+
"DEBUG",
|
|
4741
|
+
`[RoutstrClient] _handleErrorResponse: Topup result for ${baseUrl}: success=${topupResult.success}, message=${topupResult.message}`
|
|
4742
|
+
);
|
|
4743
|
+
if (!topupResult.success) {
|
|
4744
|
+
const message = topupResult.message || "";
|
|
4745
|
+
if (message.includes("Insufficient balance")) {
|
|
4746
|
+
const needMatch = message.match(/need (\d+)/);
|
|
4747
|
+
const haveMatch = message.match(/have (\d+)/);
|
|
4748
|
+
const required = needMatch ? parseInt(needMatch[1], 10) : params.requiredSats;
|
|
4749
|
+
const available = haveMatch ? parseInt(haveMatch[1], 10) : 0;
|
|
4750
|
+
this._log(
|
|
4751
|
+
"DEBUG",
|
|
4752
|
+
`[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
|
|
4753
|
+
);
|
|
4754
|
+
throw new InsufficientBalanceError(
|
|
4755
|
+
required,
|
|
4756
|
+
available,
|
|
4757
|
+
0,
|
|
4758
|
+
"",
|
|
4759
|
+
message
|
|
4760
|
+
);
|
|
4761
|
+
} else {
|
|
4762
|
+
this._log(
|
|
4763
|
+
"DEBUG",
|
|
4764
|
+
`[RoutstrClient] _handleErrorResponse: Topup failed with non-insufficient-balance error, will try next provider`
|
|
4765
|
+
);
|
|
4766
|
+
tryNextProvider = true;
|
|
4767
|
+
}
|
|
4768
|
+
} else {
|
|
4769
|
+
this._log(
|
|
4770
|
+
"DEBUG",
|
|
4771
|
+
`[RoutstrClient] _handleErrorResponse: Topup successful, will retry with new token`
|
|
4772
|
+
);
|
|
4773
|
+
}
|
|
4774
|
+
if (!tryNextProvider) {
|
|
4775
|
+
if (retryCount < MAX_RETRIES_PER_PROVIDER) {
|
|
4776
|
+
this._log(
|
|
4777
|
+
"DEBUG",
|
|
4778
|
+
`[RoutstrClient] _handleErrorResponse: Retrying 402 (attempt ${retryCount + 1}/${MAX_RETRIES_PER_PROVIDER})`
|
|
4779
|
+
);
|
|
4780
|
+
return this._makeRequest({
|
|
4781
|
+
...params,
|
|
4782
|
+
token: params.token,
|
|
4783
|
+
headers: this._withAuthHeader(params.baseHeaders, params.token),
|
|
4784
|
+
retryCount: retryCount + 1
|
|
4785
|
+
});
|
|
4786
|
+
} else {
|
|
4787
|
+
this._log(
|
|
4788
|
+
"DEBUG",
|
|
4789
|
+
`[RoutstrClient] _handleErrorResponse: 402 retry limit reached (${retryCount}/${MAX_RETRIES_PER_PROVIDER}), failing over to next provider`
|
|
4790
|
+
);
|
|
4791
|
+
tryNextProvider = true;
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
const isInsufficientBalance413 = status === 413 && responseBody?.includes("Insufficient balance");
|
|
4796
|
+
if (isInsufficientBalance413 && !tryNextProvider && this.mode === "apikeys") {
|
|
4797
|
+
let retryToken = params.token;
|
|
4798
|
+
try {
|
|
4799
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
4800
|
+
params.token,
|
|
4801
|
+
baseUrl
|
|
4802
|
+
);
|
|
4803
|
+
if (latestBalanceInfo.isInvalidApiKey) {
|
|
4804
|
+
this._log(
|
|
4805
|
+
"DEBUG",
|
|
4806
|
+
`[RoutstrClient] _handleErrorResponse: Invalid API key (proofs already spent), removing for ${baseUrl}`
|
|
4807
|
+
);
|
|
4808
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
4809
|
+
tryNextProvider = true;
|
|
4810
|
+
} else {
|
|
4811
|
+
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
4812
|
+
if (latestBalanceInfo.apiKey) {
|
|
4813
|
+
const storedApiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
4814
|
+
if (storedApiKeyEntry?.key !== latestBalanceInfo.apiKey) {
|
|
4815
|
+
if (storedApiKeyEntry) {
|
|
4816
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
4817
|
+
}
|
|
4818
|
+
this.storageAdapter.setApiKey(baseUrl, latestBalanceInfo.apiKey);
|
|
4819
|
+
}
|
|
4820
|
+
retryToken = latestBalanceInfo.apiKey;
|
|
4821
|
+
}
|
|
4822
|
+
if (latestTokenBalance >= 0) {
|
|
4823
|
+
this.storageAdapter.updateApiKeyBalance(
|
|
4824
|
+
baseUrl,
|
|
4825
|
+
latestTokenBalance
|
|
4826
|
+
);
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
} catch (error) {
|
|
4830
|
+
this._log(
|
|
4831
|
+
"WARN",
|
|
4832
|
+
`[RoutstrClient] _handleErrorResponse: Failed to refresh API key after 413 insufficient balance for ${baseUrl}`,
|
|
4833
|
+
error
|
|
4834
|
+
);
|
|
4835
|
+
}
|
|
4836
|
+
if (retryCount < MAX_RETRIES_PER_PROVIDER) {
|
|
4837
|
+
this._log(
|
|
4838
|
+
"DEBUG",
|
|
4839
|
+
`[RoutstrClient] _handleErrorResponse: Retrying 413 (attempt ${retryCount + 1}/${MAX_RETRIES_PER_PROVIDER})`
|
|
4840
|
+
);
|
|
4841
|
+
return this._makeRequest({
|
|
4842
|
+
...params,
|
|
4843
|
+
token: retryToken,
|
|
4844
|
+
headers: this._withAuthHeader(params.baseHeaders, retryToken),
|
|
4845
|
+
retryCount: retryCount + 1
|
|
4846
|
+
});
|
|
4847
|
+
} else {
|
|
4848
|
+
this._log(
|
|
4849
|
+
"DEBUG",
|
|
4850
|
+
`[RoutstrClient] _handleErrorResponse: 413 retry limit reached (${retryCount}/${MAX_RETRIES_PER_PROVIDER}), failing over to next provider`
|
|
4851
|
+
);
|
|
4852
|
+
tryNextProvider = true;
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
|
|
4856
|
+
this._log(
|
|
4857
|
+
"DEBUG",
|
|
4858
|
+
`[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
|
|
4859
|
+
);
|
|
4860
|
+
if (this.mode === "lazyrefund") {
|
|
4861
|
+
try {
|
|
4862
|
+
const refundResult = await this.balanceManager.refund({
|
|
4863
|
+
mintUrl,
|
|
4864
|
+
baseUrl,
|
|
4865
|
+
token: params.token
|
|
4866
|
+
});
|
|
4867
|
+
this._log(
|
|
4868
|
+
"DEBUG",
|
|
4869
|
+
`[RoutstrClient] _handleErrorResponse: Lazyrefund result: success=${refundResult.success}`
|
|
4870
|
+
);
|
|
4871
|
+
if (refundResult.success) this.storageAdapter.removeToken(baseUrl);
|
|
4872
|
+
else
|
|
4873
|
+
throw new ProviderError(
|
|
4874
|
+
baseUrl,
|
|
4875
|
+
status,
|
|
4876
|
+
"refund failed",
|
|
4877
|
+
requestId
|
|
4878
|
+
);
|
|
4879
|
+
} catch (error) {
|
|
4880
|
+
throw new ProviderError(
|
|
4881
|
+
baseUrl,
|
|
4882
|
+
status,
|
|
4883
|
+
"Failed to refund token",
|
|
4884
|
+
requestId
|
|
4885
|
+
);
|
|
4886
|
+
}
|
|
4887
|
+
} else if (this.mode === "apikeys") {
|
|
4888
|
+
this._log(
|
|
4889
|
+
"DEBUG",
|
|
4890
|
+
`[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
|
|
4891
|
+
);
|
|
4892
|
+
const initialBalance = await this.balanceManager.getTokenBalance(
|
|
4893
|
+
token,
|
|
4894
|
+
baseUrl
|
|
4895
|
+
);
|
|
4896
|
+
this._log(
|
|
4897
|
+
"DEBUG",
|
|
4898
|
+
`[RoutstrClient] _handleErrorResponse: Initial API key balance: ${initialBalance.amount}`
|
|
4899
|
+
);
|
|
4900
|
+
const refundResult = await this.balanceManager.refundApiKey({
|
|
4901
|
+
mintUrl,
|
|
4902
|
+
baseUrl,
|
|
4903
|
+
apiKey: token
|
|
4904
|
+
});
|
|
4905
|
+
this._log(
|
|
4906
|
+
"DEBUG",
|
|
4907
|
+
`[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
|
|
4908
|
+
);
|
|
4909
|
+
if (!refundResult.success && initialBalance.amount > 0) {
|
|
4910
|
+
throw new ProviderError(
|
|
4911
|
+
baseUrl,
|
|
4912
|
+
status,
|
|
4913
|
+
refundResult.message ?? "Unknown error"
|
|
4914
|
+
);
|
|
4915
|
+
}
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
this.providerManager.markFailed(baseUrl);
|
|
4919
|
+
this._log(
|
|
4920
|
+
"DEBUG",
|
|
4921
|
+
`[RoutstrClient] _handleErrorResponse: Marked provider ${baseUrl} as failed`
|
|
4321
4922
|
);
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
const
|
|
4330
|
-
|
|
4923
|
+
if (!selectedModel) {
|
|
4924
|
+
throw new ProviderError(
|
|
4925
|
+
baseUrl,
|
|
4926
|
+
status,
|
|
4927
|
+
"Funny, no selected model. HMM. "
|
|
4928
|
+
);
|
|
4929
|
+
}
|
|
4930
|
+
const nextProvider = this.providerManager.findNextBestProvider(
|
|
4931
|
+
selectedModel.id,
|
|
4932
|
+
baseUrl
|
|
4331
4933
|
);
|
|
4332
|
-
if (
|
|
4333
|
-
|
|
4934
|
+
if (nextProvider) {
|
|
4935
|
+
this._log(
|
|
4936
|
+
"DEBUG",
|
|
4937
|
+
`[RoutstrClient] _handleErrorResponse: Failing over to next provider: ${nextProvider}, model: ${selectedModel.id}`
|
|
4938
|
+
);
|
|
4939
|
+
const newModel = await this.providerManager.getModelForProvider(
|
|
4940
|
+
nextProvider,
|
|
4941
|
+
selectedModel.id
|
|
4942
|
+
) ?? selectedModel;
|
|
4943
|
+
const messagesForPricing = Array.isArray(
|
|
4944
|
+
body?.messages
|
|
4945
|
+
) ? body.messages : [];
|
|
4946
|
+
const newRequiredSats = this.providerManager.getRequiredSatsForModel(
|
|
4947
|
+
newModel,
|
|
4948
|
+
messagesForPricing,
|
|
4949
|
+
params.maxTokens
|
|
4950
|
+
);
|
|
4951
|
+
this._log(
|
|
4952
|
+
"DEBUG",
|
|
4953
|
+
`[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
|
|
4954
|
+
);
|
|
4955
|
+
const spendResult = await this._spendToken({
|
|
4956
|
+
mintUrl,
|
|
4957
|
+
amount: newRequiredSats,
|
|
4958
|
+
baseUrl: nextProvider
|
|
4959
|
+
});
|
|
4960
|
+
return this._makeRequest({
|
|
4961
|
+
...params,
|
|
4962
|
+
path,
|
|
4963
|
+
method,
|
|
4964
|
+
body,
|
|
4965
|
+
baseUrl: nextProvider,
|
|
4966
|
+
selectedModel: newModel,
|
|
4967
|
+
token: spendResult.token,
|
|
4968
|
+
requiredSats: newRequiredSats,
|
|
4969
|
+
headers: this._withAuthHeader(params.baseHeaders, spendResult.token),
|
|
4970
|
+
retryCount: 0
|
|
4971
|
+
});
|
|
4972
|
+
}
|
|
4973
|
+
throw new FailoverError(
|
|
4974
|
+
baseUrl,
|
|
4975
|
+
Array.from(this.providerManager.getFailedProviders())
|
|
4976
|
+
);
|
|
4977
|
+
}
|
|
4978
|
+
/**
|
|
4979
|
+
* Handle post-response balance update for all modes
|
|
4980
|
+
*/
|
|
4981
|
+
async _handlePostResponseBalanceUpdate(params) {
|
|
4982
|
+
const {
|
|
4983
|
+
token,
|
|
4984
|
+
baseUrl,
|
|
4985
|
+
initialTokenBalance,
|
|
4986
|
+
fallbackSatsSpent,
|
|
4987
|
+
response,
|
|
4988
|
+
modelId,
|
|
4989
|
+
usage,
|
|
4990
|
+
requestId
|
|
4991
|
+
} = params;
|
|
4992
|
+
let satsSpent = initialTokenBalance;
|
|
4993
|
+
if (this.mode === "xcashu" && response) {
|
|
4994
|
+
const refundToken = response.headers.get("x-cashu") ?? void 0;
|
|
4995
|
+
if (refundToken) {
|
|
4996
|
+
try {
|
|
4997
|
+
const receiveResult = await this.cashuSpender.receiveToken(refundToken);
|
|
4998
|
+
satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
|
|
4999
|
+
} catch (error) {
|
|
5000
|
+
this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
} else if (this.mode === "lazyrefund") {
|
|
5004
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5005
|
+
token,
|
|
5006
|
+
baseUrl
|
|
5007
|
+
);
|
|
5008
|
+
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
5009
|
+
this.storageAdapter.updateTokenBalance(baseUrl, latestTokenBalance);
|
|
5010
|
+
satsSpent = initialTokenBalance - latestTokenBalance;
|
|
5011
|
+
} else if (this.mode === "apikeys") {
|
|
5012
|
+
try {
|
|
5013
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
5014
|
+
token,
|
|
5015
|
+
baseUrl
|
|
5016
|
+
);
|
|
5017
|
+
this._log(
|
|
5018
|
+
"DEBUG",
|
|
5019
|
+
"LATEST Balance",
|
|
5020
|
+
latestBalanceInfo.amount,
|
|
5021
|
+
latestBalanceInfo.reserved,
|
|
5022
|
+
latestBalanceInfo.apiKey,
|
|
5023
|
+
baseUrl
|
|
5024
|
+
);
|
|
5025
|
+
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
5026
|
+
const storedApiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
5027
|
+
if (storedApiKeyEntry?.key.startsWith("cashu") && latestBalanceInfo.apiKey) {
|
|
5028
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
5029
|
+
this.storageAdapter.setApiKey(baseUrl, latestBalanceInfo.apiKey);
|
|
5030
|
+
}
|
|
5031
|
+
this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
|
|
5032
|
+
satsSpent = initialTokenBalance - latestTokenBalance;
|
|
5033
|
+
} catch (e) {
|
|
5034
|
+
this._log("WARN", "Could not get updated API key balance:", e);
|
|
5035
|
+
satsSpent = fallbackSatsSpent ?? initialTokenBalance;
|
|
5036
|
+
}
|
|
4334
5037
|
}
|
|
4335
|
-
|
|
4336
|
-
next.push({
|
|
4337
|
-
baseUrl: normalized,
|
|
5038
|
+
await this._trackResponseUsage({
|
|
4338
5039
|
token,
|
|
4339
|
-
|
|
4340
|
-
|
|
5040
|
+
baseUrl,
|
|
5041
|
+
response,
|
|
5042
|
+
modelId,
|
|
5043
|
+
satsSpent,
|
|
5044
|
+
usage,
|
|
5045
|
+
requestId
|
|
4341
5046
|
});
|
|
4342
|
-
|
|
4343
|
-
}
|
|
4344
|
-
|
|
4345
|
-
const
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
5047
|
+
return satsSpent;
|
|
5048
|
+
}
|
|
5049
|
+
async _trackResponseUsage(params) {
|
|
5050
|
+
const {
|
|
5051
|
+
token,
|
|
5052
|
+
baseUrl,
|
|
5053
|
+
response,
|
|
5054
|
+
modelId,
|
|
5055
|
+
satsSpent,
|
|
5056
|
+
usage: providedUsage,
|
|
5057
|
+
requestId: providedRequestId
|
|
5058
|
+
} = params;
|
|
5059
|
+
if (!response || !modelId) {
|
|
5060
|
+
return;
|
|
5061
|
+
}
|
|
5062
|
+
try {
|
|
5063
|
+
let usage = providedUsage;
|
|
5064
|
+
let requestId = providedRequestId;
|
|
5065
|
+
if (!usage || !requestId) {
|
|
5066
|
+
const contentType = response.headers.get("content-type") || "";
|
|
5067
|
+
if (contentType.includes("text/event-stream")) {
|
|
5068
|
+
usage = usage ?? response.usage;
|
|
5069
|
+
requestId = requestId ?? response.requestId ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
5070
|
+
if (!usage) {
|
|
5071
|
+
return;
|
|
5072
|
+
}
|
|
5073
|
+
} else {
|
|
5074
|
+
const cloned = response.clone();
|
|
5075
|
+
const responseBody = await cloned.json();
|
|
5076
|
+
usage = usage ?? extractUsageFromResponseBody(responseBody, satsSpent) ?? void 0;
|
|
5077
|
+
requestId = requestId ?? extractResponseId(responseBody) ?? response.headers.get("x-routstr-request-id") ?? void 0;
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
if (!usage) {
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
const finalRequestId = requestId || "unknown";
|
|
5084
|
+
const store = await getDefaultSdkStore();
|
|
5085
|
+
const state = store.getState();
|
|
5086
|
+
const matchingClient = state.clientIds.find(
|
|
5087
|
+
(client) => client.apiKey === token
|
|
5088
|
+
);
|
|
5089
|
+
const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
|
|
5090
|
+
const usageTracking = getDefaultUsageTrackingDriver();
|
|
5091
|
+
const entry = {
|
|
5092
|
+
id: entryId,
|
|
5093
|
+
timestamp: Date.now(),
|
|
5094
|
+
modelId,
|
|
5095
|
+
baseUrl,
|
|
5096
|
+
requestId: finalRequestId,
|
|
5097
|
+
client: matchingClient?.clientId,
|
|
5098
|
+
...usage
|
|
5099
|
+
};
|
|
5100
|
+
if (this.mode === "xcashu") {
|
|
5101
|
+
entry.satsCost = satsSpent;
|
|
5102
|
+
}
|
|
5103
|
+
await usageTracking.append(entry);
|
|
5104
|
+
} catch (error) {
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
/**
|
|
5108
|
+
* Convert messages for API format
|
|
5109
|
+
*/
|
|
5110
|
+
async _convertMessages(messages) {
|
|
5111
|
+
return Promise.all(
|
|
5112
|
+
messages.filter((m) => m.role !== "system").map(async (m) => ({
|
|
5113
|
+
role: m.role,
|
|
5114
|
+
content: typeof m.content === "string" ? m.content : m.content
|
|
5115
|
+
}))
|
|
4354
5116
|
);
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
const
|
|
4362
|
-
if (
|
|
4363
|
-
|
|
5117
|
+
}
|
|
5118
|
+
/**
|
|
5119
|
+
* Create assistant message from streaming result
|
|
5120
|
+
*/
|
|
5121
|
+
async _createAssistantMessage(result) {
|
|
5122
|
+
if (result.images && result.images.length > 0) {
|
|
5123
|
+
const content = [];
|
|
5124
|
+
if (result.content) {
|
|
5125
|
+
content.push({
|
|
5126
|
+
type: "text",
|
|
5127
|
+
text: result.content,
|
|
5128
|
+
thinking: result.thinking,
|
|
5129
|
+
citations: result.citations,
|
|
5130
|
+
annotations: result.annotations
|
|
5131
|
+
});
|
|
5132
|
+
}
|
|
5133
|
+
for (const img of result.images) {
|
|
5134
|
+
content.push({
|
|
5135
|
+
type: "image_url",
|
|
5136
|
+
image_url: {
|
|
5137
|
+
url: img.image_url.url
|
|
5138
|
+
}
|
|
5139
|
+
});
|
|
4364
5140
|
}
|
|
5141
|
+
return {
|
|
5142
|
+
role: "assistant",
|
|
5143
|
+
content
|
|
5144
|
+
};
|
|
4365
5145
|
}
|
|
4366
|
-
return
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
5146
|
+
return {
|
|
5147
|
+
role: "assistant",
|
|
5148
|
+
content: result.content || ""
|
|
5149
|
+
};
|
|
5150
|
+
}
|
|
5151
|
+
/**
|
|
5152
|
+
* Calculate estimated costs from usage
|
|
5153
|
+
*/
|
|
5154
|
+
_getEstimatedCosts(selectedModel, streamingResult) {
|
|
5155
|
+
let estimatedCosts = 0;
|
|
5156
|
+
if (streamingResult.usage) {
|
|
5157
|
+
const { completion_tokens, prompt_tokens } = streamingResult.usage;
|
|
5158
|
+
if (completion_tokens !== void 0 && prompt_tokens !== void 0) {
|
|
5159
|
+
estimatedCosts = (selectedModel.sats_pricing?.completion ?? 0) * completion_tokens + (selectedModel.sats_pricing?.prompt ?? 0) * prompt_tokens;
|
|
4375
5160
|
}
|
|
4376
5161
|
}
|
|
4377
|
-
return
|
|
4378
|
-
}
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
5162
|
+
return estimatedCosts;
|
|
5163
|
+
}
|
|
5164
|
+
/**
|
|
5165
|
+
* Get pending cashu token amount
|
|
5166
|
+
*/
|
|
5167
|
+
_getPendingCashuTokenAmount() {
|
|
5168
|
+
const distribution = this.storageAdapter.getCachedTokenDistribution();
|
|
5169
|
+
return distribution.reduce((total, item) => total + item.amount, 0);
|
|
5170
|
+
}
|
|
5171
|
+
/**
|
|
5172
|
+
* Handle errors and notify callbacks
|
|
5173
|
+
*/
|
|
5174
|
+
_handleError(error, callbacks) {
|
|
5175
|
+
this._log("ERROR", "[RoutstrClient] _handleError: Error occurred", error);
|
|
5176
|
+
if (error instanceof Error) {
|
|
5177
|
+
const isStreamError = error.message.includes("Error in input stream") || error.message.includes("Load failed");
|
|
5178
|
+
const modifiedErrorMsg = isStreamError ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
|
|
5179
|
+
this._log(
|
|
5180
|
+
"ERROR",
|
|
5181
|
+
`[RoutstrClient] _handleError: Error type=${error.constructor.name}, message=${modifiedErrorMsg}, isStreamError=${isStreamError}`
|
|
5182
|
+
);
|
|
5183
|
+
callbacks.onMessageAppend({
|
|
5184
|
+
role: "system",
|
|
5185
|
+
content: "Uncaught Error: " + modifiedErrorMsg + (this.alertLevel === "max" ? " | " + error.stack : "")
|
|
5186
|
+
});
|
|
5187
|
+
} else {
|
|
5188
|
+
callbacks.onMessageAppend({
|
|
5189
|
+
role: "system",
|
|
5190
|
+
content: "Unknown Error: Please tag Routstr on Nostr and/or retry."
|
|
5191
|
+
});
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
/**
|
|
5195
|
+
* Check wallet balance and throw if insufficient
|
|
5196
|
+
*/
|
|
5197
|
+
async _checkBalance() {
|
|
5198
|
+
const balances = await this.walletAdapter.getBalances();
|
|
5199
|
+
const totalBalance = Object.values(balances).reduce((sum, v) => sum + v, 0);
|
|
5200
|
+
if (totalBalance <= 0) {
|
|
5201
|
+
throw new InsufficientBalanceError(1, 0);
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
/**
|
|
5205
|
+
* Spend a token using CashuSpender with standardized error handling
|
|
5206
|
+
*/
|
|
5207
|
+
async _spendToken(params) {
|
|
5208
|
+
const { mintUrl, amount, baseUrl } = params;
|
|
5209
|
+
this._log(
|
|
5210
|
+
"DEBUG",
|
|
5211
|
+
`[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
|
|
4405
5212
|
);
|
|
4406
|
-
if (
|
|
4407
|
-
|
|
5213
|
+
if (this.mode === "apikeys") {
|
|
5214
|
+
let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
5215
|
+
if (!parentApiKey) {
|
|
5216
|
+
this._log(
|
|
5217
|
+
"DEBUG",
|
|
5218
|
+
`[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
|
|
5219
|
+
);
|
|
5220
|
+
const spendResult2 = await this.cashuSpender.spend({
|
|
5221
|
+
mintUrl,
|
|
5222
|
+
amount: amount * TOPUP_MARGIN,
|
|
5223
|
+
baseUrl: "",
|
|
5224
|
+
reuseToken: false
|
|
5225
|
+
});
|
|
5226
|
+
if (!spendResult2.token) {
|
|
5227
|
+
this._log(
|
|
5228
|
+
"ERROR",
|
|
5229
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
|
|
5230
|
+
spendResult2.error
|
|
5231
|
+
);
|
|
5232
|
+
throw new Error(
|
|
5233
|
+
`[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
|
|
5234
|
+
);
|
|
5235
|
+
} else {
|
|
5236
|
+
this._log(
|
|
5237
|
+
"DEBUG",
|
|
5238
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
|
|
5239
|
+
);
|
|
5240
|
+
}
|
|
5241
|
+
this._log(
|
|
5242
|
+
"DEBUG",
|
|
5243
|
+
`[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
|
|
5244
|
+
);
|
|
5245
|
+
try {
|
|
5246
|
+
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
5247
|
+
} catch (error) {
|
|
5248
|
+
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
5249
|
+
const tryReceiveTokenResult = await this.cashuSpender.receiveToken(
|
|
5250
|
+
spendResult2.token
|
|
5251
|
+
);
|
|
5252
|
+
if (tryReceiveTokenResult.success) {
|
|
5253
|
+
this._log(
|
|
5254
|
+
"DEBUG",
|
|
5255
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
|
|
5256
|
+
);
|
|
5257
|
+
} else {
|
|
5258
|
+
this._log(
|
|
5259
|
+
"DEBUG",
|
|
5260
|
+
`[RoutstrClient] _handleErrorResponse: Token restore failed or not needed`
|
|
5261
|
+
);
|
|
5262
|
+
}
|
|
5263
|
+
this._log(
|
|
5264
|
+
"DEBUG",
|
|
5265
|
+
`[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
|
|
5266
|
+
);
|
|
5267
|
+
} else {
|
|
5268
|
+
throw error;
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5271
|
+
parentApiKey = this.storageAdapter.getApiKey(baseUrl);
|
|
5272
|
+
} else {
|
|
5273
|
+
this._log(
|
|
5274
|
+
"DEBUG",
|
|
5275
|
+
`[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
|
|
5276
|
+
);
|
|
5277
|
+
}
|
|
5278
|
+
let tokenBalance = 0;
|
|
5279
|
+
let tokenBalanceUnit = "sat";
|
|
5280
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
5281
|
+
const distributionForBaseUrl = apiKeyDistribution.find(
|
|
5282
|
+
(d) => d.baseUrl === baseUrl
|
|
5283
|
+
);
|
|
5284
|
+
if (distributionForBaseUrl) {
|
|
5285
|
+
tokenBalance = distributionForBaseUrl.amount;
|
|
5286
|
+
}
|
|
5287
|
+
if (tokenBalance === 0 && parentApiKey) {
|
|
5288
|
+
try {
|
|
5289
|
+
const balanceInfo = await this.balanceManager.getTokenBalance(
|
|
5290
|
+
parentApiKey.key,
|
|
5291
|
+
baseUrl
|
|
5292
|
+
);
|
|
5293
|
+
tokenBalance = balanceInfo.amount;
|
|
5294
|
+
tokenBalanceUnit = balanceInfo.unit;
|
|
5295
|
+
} catch (e) {
|
|
5296
|
+
this._log("WARN", "Could not get initial API key balance:", e);
|
|
5297
|
+
}
|
|
5298
|
+
}
|
|
5299
|
+
this._log(
|
|
5300
|
+
"DEBUG",
|
|
5301
|
+
`[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
|
|
5302
|
+
);
|
|
5303
|
+
return {
|
|
5304
|
+
token: parentApiKey?.key ?? "",
|
|
5305
|
+
tokenBalance,
|
|
5306
|
+
tokenBalanceUnit
|
|
5307
|
+
};
|
|
4408
5308
|
}
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
key,
|
|
4413
|
-
balance: 0,
|
|
4414
|
-
lastUsed: Date.now()
|
|
4415
|
-
});
|
|
4416
|
-
store.getState().setApiKeys(next);
|
|
4417
|
-
},
|
|
4418
|
-
updateApiKeyBalance: (baseUrl, balance) => {
|
|
4419
|
-
const normalized = normalizeBaseUrl(baseUrl);
|
|
4420
|
-
const keys = store.getState().apiKeys;
|
|
4421
|
-
const next = keys.map(
|
|
4422
|
-
(entry) => entry.baseUrl === normalized ? { ...entry, balance } : entry
|
|
4423
|
-
);
|
|
4424
|
-
store.getState().setApiKeys(next);
|
|
4425
|
-
},
|
|
4426
|
-
removeApiKey: (baseUrl) => {
|
|
4427
|
-
const normalized = normalizeBaseUrl(baseUrl);
|
|
4428
|
-
const next = store.getState().apiKeys.filter((entry) => entry.baseUrl !== normalized);
|
|
4429
|
-
store.getState().setApiKeys(next);
|
|
4430
|
-
},
|
|
4431
|
-
getAllApiKeys: () => {
|
|
4432
|
-
return store.getState().apiKeys.map((entry) => ({
|
|
4433
|
-
baseUrl: entry.baseUrl,
|
|
4434
|
-
key: entry.key,
|
|
4435
|
-
balance: entry.balance,
|
|
4436
|
-
lastUsed: entry.lastUsed
|
|
4437
|
-
}));
|
|
4438
|
-
},
|
|
4439
|
-
// ========== Child Keys ==========
|
|
4440
|
-
getChildKey: (parentBaseUrl) => {
|
|
4441
|
-
const normalized = normalizeBaseUrl(parentBaseUrl);
|
|
4442
|
-
const entry = store.getState().childKeys.find((key) => key.parentBaseUrl === normalized);
|
|
4443
|
-
if (!entry) return null;
|
|
4444
|
-
return {
|
|
4445
|
-
parentBaseUrl: entry.parentBaseUrl,
|
|
4446
|
-
childKey: entry.childKey,
|
|
4447
|
-
balance: entry.balance,
|
|
4448
|
-
balanceLimit: entry.balanceLimit,
|
|
4449
|
-
validityDate: entry.validityDate,
|
|
4450
|
-
createdAt: entry.createdAt
|
|
4451
|
-
};
|
|
4452
|
-
},
|
|
4453
|
-
setChildKey: (parentBaseUrl, childKey, balance, validityDate, balanceLimit) => {
|
|
4454
|
-
const normalized = normalizeBaseUrl(parentBaseUrl);
|
|
4455
|
-
const keys = store.getState().childKeys;
|
|
4456
|
-
const existingIndex = keys.findIndex(
|
|
4457
|
-
(entry) => entry.parentBaseUrl === normalized
|
|
5309
|
+
this._log(
|
|
5310
|
+
"DEBUG",
|
|
5311
|
+
`[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
|
|
4458
5312
|
);
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
5313
|
+
const spendResult = await this.cashuSpender.spend({
|
|
5314
|
+
mintUrl,
|
|
5315
|
+
amount,
|
|
5316
|
+
baseUrl: this.mode === "lazyrefund" ? baseUrl : "",
|
|
5317
|
+
reuseToken: this.mode === "lazyrefund"
|
|
5318
|
+
});
|
|
5319
|
+
if (!spendResult.token) {
|
|
5320
|
+
this._log(
|
|
5321
|
+
"ERROR",
|
|
5322
|
+
`[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
|
|
5323
|
+
spendResult.error
|
|
4469
5324
|
);
|
|
4470
|
-
store.getState().setChildKeys(next);
|
|
4471
5325
|
} else {
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
balance: balance ?? 0,
|
|
4477
|
-
validityDate,
|
|
4478
|
-
balanceLimit,
|
|
4479
|
-
createdAt: Date.now()
|
|
4480
|
-
});
|
|
4481
|
-
store.getState().setChildKeys(next);
|
|
4482
|
-
}
|
|
4483
|
-
},
|
|
4484
|
-
updateChildKeyBalance: (parentBaseUrl, balance) => {
|
|
4485
|
-
const normalized = normalizeBaseUrl(parentBaseUrl);
|
|
4486
|
-
const keys = store.getState().childKeys;
|
|
4487
|
-
const next = keys.map(
|
|
4488
|
-
(entry) => entry.parentBaseUrl === normalized ? { ...entry, balance } : entry
|
|
4489
|
-
);
|
|
4490
|
-
store.getState().setChildKeys(next);
|
|
4491
|
-
},
|
|
4492
|
-
removeChildKey: (parentBaseUrl) => {
|
|
4493
|
-
const normalized = normalizeBaseUrl(parentBaseUrl);
|
|
4494
|
-
const next = store.getState().childKeys.filter((entry) => entry.parentBaseUrl !== normalized);
|
|
4495
|
-
store.getState().setChildKeys(next);
|
|
4496
|
-
},
|
|
4497
|
-
getAllChildKeys: () => {
|
|
4498
|
-
return store.getState().childKeys.map((entry) => ({
|
|
4499
|
-
parentBaseUrl: entry.parentBaseUrl,
|
|
4500
|
-
childKey: entry.childKey,
|
|
4501
|
-
balance: entry.balance,
|
|
4502
|
-
balanceLimit: entry.balanceLimit,
|
|
4503
|
-
validityDate: entry.validityDate,
|
|
4504
|
-
createdAt: entry.createdAt
|
|
4505
|
-
}));
|
|
4506
|
-
},
|
|
4507
|
-
getCachedReceiveTokens: () => {
|
|
4508
|
-
return store.getState().cachedReceiveTokens;
|
|
4509
|
-
},
|
|
4510
|
-
setCachedReceiveTokens: (tokens) => {
|
|
4511
|
-
store.getState().setCachedReceiveTokens(tokens);
|
|
4512
|
-
}
|
|
4513
|
-
});
|
|
4514
|
-
var createProviderRegistryFromStore = (store) => ({
|
|
4515
|
-
getModelsForProvider: (baseUrl) => {
|
|
4516
|
-
const normalized = normalizeBaseUrl(baseUrl);
|
|
4517
|
-
return store.getState().modelsFromAllProviders[normalized] || [];
|
|
4518
|
-
},
|
|
4519
|
-
getDisabledProviders: () => store.getState().disabledProviders,
|
|
4520
|
-
getProviderMints: (baseUrl) => {
|
|
4521
|
-
const normalized = normalizeBaseUrl(baseUrl);
|
|
4522
|
-
return store.getState().mintsFromAllProviders[normalized] || [];
|
|
4523
|
-
},
|
|
4524
|
-
getProviderInfo: async (baseUrl) => {
|
|
4525
|
-
const normalized = normalizeBaseUrl(baseUrl);
|
|
4526
|
-
const cached = store.getState().infoFromAllProviders[normalized];
|
|
4527
|
-
if (cached) return cached;
|
|
4528
|
-
try {
|
|
4529
|
-
const response = await fetch(`${normalized}v1/info`);
|
|
4530
|
-
if (!response.ok) {
|
|
4531
|
-
throw new Error(`Failed ${response.status}`);
|
|
4532
|
-
}
|
|
4533
|
-
const info = await response.json();
|
|
4534
|
-
const next = { ...store.getState().infoFromAllProviders };
|
|
4535
|
-
next[normalized] = info;
|
|
4536
|
-
store.getState().setInfoFromAllProviders(next);
|
|
4537
|
-
return info;
|
|
4538
|
-
} catch (error) {
|
|
4539
|
-
console.warn(`Failed to fetch provider info from ${normalized}:`, error);
|
|
4540
|
-
return null;
|
|
5326
|
+
this._log(
|
|
5327
|
+
"DEBUG",
|
|
5328
|
+
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
5329
|
+
);
|
|
4541
5330
|
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
var isBrowser2 = () => {
|
|
4548
|
-
try {
|
|
4549
|
-
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
4550
|
-
} catch {
|
|
4551
|
-
return false;
|
|
4552
|
-
}
|
|
4553
|
-
};
|
|
4554
|
-
var isNode = () => {
|
|
4555
|
-
try {
|
|
4556
|
-
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
4557
|
-
} catch {
|
|
4558
|
-
return false;
|
|
4559
|
-
}
|
|
4560
|
-
};
|
|
4561
|
-
var defaultDriver = null;
|
|
4562
|
-
var isBun2 = () => {
|
|
4563
|
-
return typeof process.versions.bun !== "undefined";
|
|
4564
|
-
};
|
|
4565
|
-
var getDefaultSdkDriver = () => {
|
|
4566
|
-
if (defaultDriver) return defaultDriver;
|
|
4567
|
-
if (isBrowser2()) {
|
|
4568
|
-
defaultDriver = localStorageDriver;
|
|
4569
|
-
return defaultDriver;
|
|
4570
|
-
}
|
|
4571
|
-
if (isBun2()) {
|
|
4572
|
-
defaultDriver = createMemoryDriver();
|
|
4573
|
-
return defaultDriver;
|
|
5331
|
+
return {
|
|
5332
|
+
token: spendResult.token,
|
|
5333
|
+
tokenBalance: spendResult.balance,
|
|
5334
|
+
tokenBalanceUnit: spendResult.unit ?? "sat"
|
|
5335
|
+
};
|
|
4574
5336
|
}
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
5337
|
+
/**
|
|
5338
|
+
* Build request headers with common defaults and dev mock controls
|
|
5339
|
+
*/
|
|
5340
|
+
_buildBaseHeaders(additionalHeaders = {}, token) {
|
|
5341
|
+
const headers = {
|
|
5342
|
+
...additionalHeaders,
|
|
5343
|
+
"Content-Type": "application/json"
|
|
5344
|
+
};
|
|
5345
|
+
return headers;
|
|
4578
5346
|
}
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
5347
|
+
/**
|
|
5348
|
+
* Attach auth headers using the active client mode
|
|
5349
|
+
*/
|
|
5350
|
+
_withAuthHeader(headers, token) {
|
|
5351
|
+
const nextHeaders = { ...headers };
|
|
5352
|
+
if (this.mode === "xcashu") {
|
|
5353
|
+
nextHeaders["X-Cashu"] = token;
|
|
5354
|
+
} else {
|
|
5355
|
+
nextHeaders["Authorization"] = `Bearer ${token}`;
|
|
5356
|
+
}
|
|
5357
|
+
return nextHeaders;
|
|
4586
5358
|
}
|
|
4587
|
-
return defaultStore.hydrate.then(() => defaultStore.store);
|
|
4588
5359
|
};
|
|
4589
|
-
var getDefaultDiscoveryAdapter = async () => createDiscoveryAdapterFromStore(await getDefaultSdkStore());
|
|
4590
|
-
var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
|
|
4591
|
-
var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
|
|
4592
5360
|
|
|
4593
5361
|
// routeRequests.ts
|
|
4594
|
-
async function
|
|
5362
|
+
async function resolveRouteRequestContext(options) {
|
|
4595
5363
|
const {
|
|
4596
5364
|
modelId,
|
|
4597
5365
|
requestBody,
|
|
@@ -4665,12 +5433,11 @@ async function routeRequests(options) {
|
|
|
4665
5433
|
if (!mintUrl) {
|
|
4666
5434
|
throw new Error("No mint configured in wallet");
|
|
4667
5435
|
}
|
|
4668
|
-
const alertLevel = "min";
|
|
4669
5436
|
const client = new RoutstrClient(
|
|
4670
5437
|
walletAdapter,
|
|
4671
5438
|
storageAdapter,
|
|
4672
5439
|
providerRegistry,
|
|
4673
|
-
|
|
5440
|
+
"min",
|
|
4674
5441
|
mode
|
|
4675
5442
|
);
|
|
4676
5443
|
if (debugLevel) {
|
|
@@ -4678,18 +5445,27 @@ async function routeRequests(options) {
|
|
|
4678
5445
|
}
|
|
4679
5446
|
const maxTokens = extractMaxTokens(requestBody);
|
|
4680
5447
|
const stream = extractStream(requestBody);
|
|
4681
|
-
|
|
5448
|
+
const proxiedBody = requestBody && typeof requestBody === "object" ? { ...requestBody } : {};
|
|
5449
|
+
proxiedBody.model = selectedModel.id;
|
|
5450
|
+
if (stream !== void 0) {
|
|
5451
|
+
proxiedBody.stream = stream;
|
|
5452
|
+
}
|
|
5453
|
+
if (maxTokens !== void 0) {
|
|
5454
|
+
proxiedBody.max_tokens = maxTokens;
|
|
5455
|
+
}
|
|
5456
|
+
return {
|
|
5457
|
+
client,
|
|
5458
|
+
baseUrl,
|
|
5459
|
+
mintUrl,
|
|
5460
|
+
path,
|
|
5461
|
+
modelId,
|
|
5462
|
+
proxiedBody
|
|
5463
|
+
};
|
|
5464
|
+
}
|
|
5465
|
+
async function routeRequests(options) {
|
|
5466
|
+
const { client, baseUrl, mintUrl, path, modelId, proxiedBody } = await resolveRouteRequestContext(options);
|
|
4682
5467
|
try {
|
|
4683
|
-
const
|
|
4684
|
-
proxiedBody.model = selectedModel.id;
|
|
4685
|
-
if (stream !== void 0) {
|
|
4686
|
-
proxiedBody.stream = stream;
|
|
4687
|
-
}
|
|
4688
|
-
if (maxTokens !== void 0) {
|
|
4689
|
-
proxiedBody.max_tokens = maxTokens;
|
|
4690
|
-
}
|
|
4691
|
-
console.log(modelId);
|
|
4692
|
-
response = await client.routeRequest({
|
|
5468
|
+
const response = await client.routeRequest({
|
|
4693
5469
|
path,
|
|
4694
5470
|
method: "POST",
|
|
4695
5471
|
body: proxiedBody,
|
|
@@ -4708,24 +5484,41 @@ async function routeRequests(options) {
|
|
|
4708
5484
|
throw error;
|
|
4709
5485
|
}
|
|
4710
5486
|
}
|
|
5487
|
+
async function routeRequestsToNodeResponse(options) {
|
|
5488
|
+
const { res } = options;
|
|
5489
|
+
const { client, baseUrl, mintUrl, path, modelId, proxiedBody } = await resolveRouteRequestContext(options);
|
|
5490
|
+
try {
|
|
5491
|
+
await client.routeRequestToNodeResponse({
|
|
5492
|
+
path,
|
|
5493
|
+
method: "POST",
|
|
5494
|
+
body: proxiedBody,
|
|
5495
|
+
baseUrl,
|
|
5496
|
+
mintUrl,
|
|
5497
|
+
modelId,
|
|
5498
|
+
res
|
|
5499
|
+
});
|
|
5500
|
+
} catch (error) {
|
|
5501
|
+
if (error instanceof Error && (error.message.includes("401") || error.message.includes("402") || error.message.includes("403"))) {
|
|
5502
|
+
throw new Error(`Authentication failed: ${error.message}`);
|
|
5503
|
+
}
|
|
5504
|
+
throw error;
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
4711
5507
|
function extractMaxTokens(requestBody) {
|
|
4712
5508
|
if (!requestBody || typeof requestBody !== "object") {
|
|
4713
5509
|
return void 0;
|
|
4714
5510
|
}
|
|
4715
5511
|
const body = requestBody;
|
|
4716
5512
|
const maxTokens = body.max_tokens;
|
|
4717
|
-
|
|
4718
|
-
return maxTokens;
|
|
4719
|
-
}
|
|
4720
|
-
return void 0;
|
|
5513
|
+
return typeof maxTokens === "number" ? maxTokens : void 0;
|
|
4721
5514
|
}
|
|
4722
5515
|
function extractStream(requestBody) {
|
|
4723
5516
|
if (!requestBody || typeof requestBody !== "object") {
|
|
4724
|
-
return
|
|
5517
|
+
return void 0;
|
|
4725
5518
|
}
|
|
4726
5519
|
const body = requestBody;
|
|
4727
5520
|
const stream = body.stream;
|
|
4728
|
-
return stream ===
|
|
5521
|
+
return typeof stream === "boolean" ? stream : void 0;
|
|
4729
5522
|
}
|
|
4730
5523
|
|
|
4731
5524
|
exports.BalanceManager = BalanceManager;
|
|
@@ -4748,10 +5541,14 @@ exports.StreamingError = StreamingError;
|
|
|
4748
5541
|
exports.TokenOperationError = TokenOperationError;
|
|
4749
5542
|
exports.createDiscoveryAdapterFromStore = createDiscoveryAdapterFromStore;
|
|
4750
5543
|
exports.createIndexedDBDriver = createIndexedDBDriver;
|
|
5544
|
+
exports.createIndexedDBUsageTrackingDriver = createIndexedDBUsageTrackingDriver;
|
|
4751
5545
|
exports.createMemoryDriver = createMemoryDriver;
|
|
5546
|
+
exports.createMemoryUsageTrackingDriver = createMemoryUsageTrackingDriver;
|
|
4752
5547
|
exports.createProviderRegistryFromStore = createProviderRegistryFromStore;
|
|
5548
|
+
exports.createSSEParserTransform = createSSEParserTransform;
|
|
4753
5549
|
exports.createSdkStore = createSdkStore;
|
|
4754
5550
|
exports.createSqliteDriver = createSqliteDriver;
|
|
5551
|
+
exports.createSqliteUsageTrackingDriver = createSqliteUsageTrackingDriver;
|
|
4755
5552
|
exports.createStorageAdapterFromStore = createStorageAdapterFromStore;
|
|
4756
5553
|
exports.filterBaseUrlsForTor = filterBaseUrlsForTor;
|
|
4757
5554
|
exports.getDefaultDiscoveryAdapter = getDefaultDiscoveryAdapter;
|
|
@@ -4759,11 +5556,13 @@ exports.getDefaultProviderRegistry = getDefaultProviderRegistry;
|
|
|
4759
5556
|
exports.getDefaultSdkDriver = getDefaultSdkDriver;
|
|
4760
5557
|
exports.getDefaultSdkStore = getDefaultSdkStore;
|
|
4761
5558
|
exports.getDefaultStorageAdapter = getDefaultStorageAdapter;
|
|
5559
|
+
exports.getDefaultUsageTrackingDriver = getDefaultUsageTrackingDriver;
|
|
4762
5560
|
exports.getProviderEndpoints = getProviderEndpoints;
|
|
4763
5561
|
exports.isOnionUrl = isOnionUrl;
|
|
4764
5562
|
exports.isTorContext = isTorContext;
|
|
4765
5563
|
exports.localStorageDriver = localStorageDriver;
|
|
4766
5564
|
exports.normalizeProviderUrl = normalizeProviderUrl;
|
|
4767
5565
|
exports.routeRequests = routeRequests;
|
|
5566
|
+
exports.routeRequestsToNodeResponse = routeRequestsToNodeResponse;
|
|
4768
5567
|
//# sourceMappingURL=index.js.map
|
|
4769
5568
|
//# sourceMappingURL=index.js.map
|