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