@routstr/sdk 0.2.3 → 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.
Files changed (38) hide show
  1. package/README.md +9 -0
  2. package/dist/client/index.d.mts +21 -8
  3. package/dist/client/index.d.ts +21 -8
  4. package/dist/client/index.js +1406 -69
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/client/index.mjs +1406 -70
  7. package/dist/client/index.mjs.map +1 -1
  8. package/dist/discovery/index.d.mts +2 -2
  9. package/dist/discovery/index.d.ts +2 -2
  10. package/dist/discovery/index.js +1 -4
  11. package/dist/discovery/index.js.map +1 -1
  12. package/dist/discovery/index.mjs +1 -4
  13. package/dist/discovery/index.mjs.map +1 -1
  14. package/dist/index.d.mts +15 -19
  15. package/dist/index.d.ts +15 -19
  16. package/dist/index.js +2385 -1574
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +2380 -1575
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/{interfaces-DGdP8fQp.d.mts → interfaces-BWJJTCXO.d.mts} +1 -1
  21. package/dist/{interfaces-CC0LT9p9.d.ts → interfaces-BxDEka72.d.ts} +1 -1
  22. package/dist/{interfaces-B85Wx7ni.d.mts → interfaces-C6Dr6hKy.d.mts} +1 -1
  23. package/dist/{interfaces-BVNyAmKu.d.ts → interfaces-CluftN4z.d.ts} +1 -1
  24. package/dist/storage/index.d.mts +56 -34
  25. package/dist/storage/index.d.ts +56 -34
  26. package/dist/storage/index.js +500 -51
  27. package/dist/storage/index.js.map +1 -1
  28. package/dist/storage/index.mjs +497 -52
  29. package/dist/storage/index.mjs.map +1 -1
  30. package/dist/{types-BlHjmWRK.d.mts → types-BYj_8c5c.d.mts} +3 -0
  31. package/dist/{types-BlHjmWRK.d.ts → types-BYj_8c5c.d.ts} +3 -0
  32. package/dist/wallet/index.d.mts +9 -5
  33. package/dist/wallet/index.d.ts +9 -5
  34. package/dist/wallet/index.js +54 -22
  35. package/dist/wallet/index.js.map +1 -1
  36. package/dist/wallet/index.mjs +54 -22
  37. package/dist/wallet/index.mjs.map +1 -1
  38. package/package.json +1 -1
@@ -1,3 +1,14 @@
1
+ import { createStore } from 'zustand/vanilla';
2
+ import { getDecodedToken } from '@cashu/cashu-ts';
3
+ import { Transform, Readable } from 'stream';
4
+
5
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
+ }) : x)(function(x) {
8
+ if (typeof require !== "undefined") return require.apply(this, arguments);
9
+ throw Error('Dynamic require of "' + x + '" is not supported');
10
+ });
11
+
1
12
  // core/errors.ts
2
13
  var InsufficientBalanceError = class extends Error {
3
14
  constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
@@ -466,7 +477,7 @@ var CashuSpender = class {
466
477
  /**
467
478
  * Refund specific providers without retrying spend
468
479
  */
469
- async refundProviders(baseUrls, mintUrl, refundApiKeys = false) {
480
+ async refundProviders(baseUrls, mintUrl, refundApiKeys = false, forceRefund) {
470
481
  const results = [];
471
482
  const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
472
483
  const toRefund = pendingDistribution.filter(
@@ -516,7 +527,8 @@ var CashuSpender = class {
516
527
  const refundResult = await this.balanceManager.refundApiKey({
517
528
  mintUrl,
518
529
  baseUrl: apiKeyEntry.baseUrl,
519
- apiKey: apiKeyEntryFull.key
530
+ apiKey: apiKeyEntryFull.key,
531
+ forceRefund
520
532
  });
521
533
  if (refundResult.success) {
522
534
  this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, 0);
@@ -689,12 +701,29 @@ var BalanceManager = class {
689
701
  }
690
702
  /**
691
703
  * Refund API key balance - convert remaining API key balance to cashu token
704
+ * @param options - Refund options including forceRefund flag
705
+ * @returns Refund result
692
706
  */
693
707
  async refundApiKey(options) {
694
- const { mintUrl, baseUrl, apiKey } = options;
708
+ const { mintUrl, baseUrl, apiKey, forceRefund } = options;
695
709
  if (!apiKey) {
696
710
  return { success: false, message: "No API key to refund" };
697
711
  }
712
+ if (!forceRefund) {
713
+ const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
714
+ if (apiKeyEntry?.lastUsed) {
715
+ const fiveMinutesAgo = Date.now() - 5 * 60 * 1e3;
716
+ if (apiKeyEntry.lastUsed > fiveMinutesAgo) {
717
+ console.log(
718
+ `[BalanceManager] Skipping refund for ${baseUrl} - used ${Math.round((Date.now() - apiKeyEntry.lastUsed) / 1e3)}s ago`
719
+ );
720
+ return {
721
+ success: false,
722
+ message: "API key was used recently, skipping refund"
723
+ };
724
+ }
725
+ }
726
+ }
698
727
  let fetchResult;
699
728
  try {
700
729
  fetchResult = await this._fetchRefundTokenWithApiKey(baseUrl, apiKey);
@@ -866,9 +895,13 @@ var BalanceManager = class {
866
895
  p2pkPubkey
867
896
  } = options;
868
897
  const adjustedAmount = Math.ceil(amount);
869
- console.log(`[BalanceManager.createProviderToken] Starting: baseUrl=${baseUrl}, mintUrl=${mintUrl}, amount=${amount}, adjustedAmount=${adjustedAmount}, retryCount=${retryCount}`);
898
+ console.log(
899
+ `[BalanceManager.createProviderToken] Starting: baseUrl=${baseUrl}, mintUrl=${mintUrl}, amount=${amount}, adjustedAmount=${adjustedAmount}, retryCount=${retryCount}`
900
+ );
870
901
  if (!adjustedAmount || isNaN(adjustedAmount)) {
871
- console.error(`[BalanceManager.createProviderToken] FAILURE: Invalid amount - amount=${amount}, adjustedAmount=${adjustedAmount}`);
902
+ console.error(
903
+ `[BalanceManager.createProviderToken] FAILURE: Invalid amount - amount=${amount}, adjustedAmount=${adjustedAmount}`
904
+ );
872
905
  return { success: false, error: "Invalid top up amount" };
873
906
  }
874
907
  const balanceState = await this.getBalanceState();
@@ -882,8 +915,8 @@ var BalanceManager = class {
882
915
  const refundableProviderBalance = Object.entries(
883
916
  balanceState.providerBalances
884
917
  ).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
885
- if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 1) {
886
- await this._refundOtherProvidersForTopUp(baseUrl, mintUrl);
918
+ if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 2) {
919
+ await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount);
887
920
  return this.createProviderToken({
888
921
  ...options,
889
922
  retryCount: retryCount + 1
@@ -899,15 +932,11 @@ var BalanceManager = class {
899
932
  { url: "", balance: 0 }
900
933
  ).url
901
934
  );
902
- console.error(`[BalanceManager.createProviderToken] FAILURE: Insufficient balance - required=${adjustedAmount}, available=${totalMintBalance + targetProviderBalance}, totalMintBalance=${totalMintBalance}, targetProviderBalance=${targetProviderBalance}, refundableProviderBalance=${refundableProviderBalance}`);
935
+ console.error(
936
+ `[BalanceManager.createProviderToken] FAILURE: Insufficient balance - required=${adjustedAmount}, available=${totalMintBalance + targetProviderBalance}, totalMintBalance=${totalMintBalance}, targetProviderBalance=${targetProviderBalance}, refundableProviderBalance=${refundableProviderBalance}`
937
+ );
903
938
  return { success: false, error: error.message };
904
939
  }
905
- if (targetProviderBalance >= adjustedAmount) {
906
- return {
907
- success: true,
908
- amountSpent: 0
909
- };
910
- }
911
940
  const providerMints = baseUrl && this.providerRegistry ? this.providerRegistry.getProviderMints(baseUrl) : [];
912
941
  let requiredAmount = adjustedAmount;
913
942
  const supportedMintsOnly = providerMints.length > 0;
@@ -941,7 +970,9 @@ var BalanceManager = class {
941
970
  maxMintUrl = mintUrl2;
942
971
  }
943
972
  }
944
- console.error(`[BalanceManager.createProviderToken] FAILURE: No candidate mints found - requiredAmount=${requiredAmount}, totalMintBalance=${totalMintBalance}, maxBalance=${maxBalance}, maxMintUrl=${maxMintUrl}, providerMints=${JSON.stringify(providerMints)}`);
973
+ console.error(
974
+ `[BalanceManager.createProviderToken] FAILURE: No candidate mints found - requiredAmount=${requiredAmount}, totalMintBalance=${totalMintBalance}, maxBalance=${maxBalance}, maxMintUrl=${maxMintUrl}, providerMints=${JSON.stringify(providerMints)}`
975
+ );
945
976
  const error = new InsufficientBalanceError(
946
977
  adjustedAmount,
947
978
  totalMintBalance,
@@ -953,13 +984,17 @@ var BalanceManager = class {
953
984
  let lastError;
954
985
  for (const candidateMint of candidates) {
955
986
  try {
956
- console.log(`[BalanceManager.createProviderToken] Attempting mint: ${candidateMint}, amount: ${requiredAmount}`);
987
+ console.log(
988
+ `[BalanceManager.createProviderToken] Attempting mint: ${candidateMint}, amount: ${requiredAmount}`
989
+ );
957
990
  const token = await this.walletAdapter.sendToken(
958
991
  candidateMint,
959
992
  requiredAmount,
960
993
  p2pkPubkey
961
994
  );
962
- console.log(`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`);
995
+ console.log(
996
+ `[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`
997
+ );
963
998
  return {
964
999
  success: true,
965
1000
  token,
@@ -968,11 +1003,15 @@ var BalanceManager = class {
968
1003
  };
969
1004
  } catch (error) {
970
1005
  const errorMsg = error instanceof Error ? error.message : String(error);
971
- console.error(`[BalanceManager.createProviderToken] FAILURE: Mint ${candidateMint} failed with error: ${errorMsg}`);
1006
+ console.error(
1007
+ `[BalanceManager.createProviderToken] FAILURE: Mint ${candidateMint} failed with error: ${errorMsg}`
1008
+ );
972
1009
  if (error instanceof Error) {
973
1010
  lastError = errorMsg;
974
1011
  if (isNetworkErrorMessage(error.message)) {
975
- console.warn(`[BalanceManager.createProviderToken] Network error from ${candidateMint}, trying next mint...`);
1012
+ console.warn(
1013
+ `[BalanceManager.createProviderToken] Network error from ${candidateMint}, trying next mint...`
1014
+ );
976
1015
  continue;
977
1016
  }
978
1017
  }
@@ -982,7 +1021,9 @@ var BalanceManager = class {
982
1021
  };
983
1022
  }
984
1023
  }
985
- console.error(`[BalanceManager.createProviderToken] FAILURE: All candidate mints exhausted - lastError=${lastError}, candidates=${JSON.stringify(candidates)}`);
1024
+ console.error(
1025
+ `[BalanceManager.createProviderToken] FAILURE: All candidate mints exhausted - lastError=${lastError}, candidates=${JSON.stringify(candidates)}`
1026
+ );
986
1027
  return {
987
1028
  success: false,
988
1029
  error: lastError || "All candidate mints failed while creating top up token"
@@ -1028,9 +1069,10 @@ var BalanceManager = class {
1028
1069
  }
1029
1070
  return candidates;
1030
1071
  }
1031
- async _refundOtherProvidersForTopUp(baseUrl, mintUrl) {
1072
+ async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
1032
1073
  const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1033
1074
  const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1075
+ const forceRefund = retryCount >= 2;
1034
1076
  const toRefund = pendingDistribution.filter(
1035
1077
  (pending) => pending.baseUrl !== baseUrl
1036
1078
  );
@@ -1071,7 +1113,8 @@ var BalanceManager = class {
1071
1113
  const result = await this.refundApiKey({
1072
1114
  mintUrl,
1073
1115
  baseUrl: apiKeyEntry.baseUrl,
1074
- apiKey: fullApiKeyEntry.key
1116
+ apiKey: fullApiKeyEntry.key,
1117
+ forceRefund
1075
1118
  });
1076
1119
  return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
1077
1120
  })
@@ -1325,6 +1368,77 @@ var BalanceManager = class {
1325
1368
  }
1326
1369
  };
1327
1370
 
1371
+ // client/usage.ts
1372
+ function extractUsageFromResponseBody(body, fallbackSatsCost = 0) {
1373
+ if (!body || typeof body !== "object") return null;
1374
+ const usage = body.usage;
1375
+ if (!usage || typeof usage !== "object") return null;
1376
+ const promptTokens = Number(usage.prompt_tokens ?? 0);
1377
+ const completionTokens = Number(usage.completion_tokens ?? 0);
1378
+ const totalTokens = Number(usage.total_tokens ?? 0);
1379
+ const costValue = usage.cost;
1380
+ let cost = 0;
1381
+ let satsCost = fallbackSatsCost;
1382
+ if (typeof costValue === "number") {
1383
+ cost = costValue;
1384
+ } else if (costValue && typeof costValue === "object") {
1385
+ const costObj = costValue;
1386
+ const totalUsd = costObj.total_usd;
1387
+ const totalMsats = costObj.total_msats;
1388
+ cost = typeof totalUsd === "number" ? totalUsd : 0;
1389
+ if (typeof totalMsats === "number") {
1390
+ satsCost = totalMsats / 1e3;
1391
+ }
1392
+ }
1393
+ if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && cost === 0 && satsCost === 0) {
1394
+ return null;
1395
+ }
1396
+ return {
1397
+ promptTokens,
1398
+ completionTokens,
1399
+ totalTokens,
1400
+ cost,
1401
+ satsCost
1402
+ };
1403
+ }
1404
+ function extractResponseId(body) {
1405
+ if (!body || typeof body !== "object") return void 0;
1406
+ const id = body.id;
1407
+ if (typeof id !== "string") return void 0;
1408
+ const trimmed = id.trim();
1409
+ return trimmed.length > 0 ? trimmed : void 0;
1410
+ }
1411
+ function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
1412
+ if (!parsed || typeof parsed !== "object" || !parsed.usage) {
1413
+ return null;
1414
+ }
1415
+ const usage = parsed.usage;
1416
+ const usageCost = usage.cost;
1417
+ const cost = typeof usageCost === "number" ? usageCost : usageCost?.total_usd ?? parsed.metadata?.routstr?.cost?.total_usd ?? 0;
1418
+ const msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
1419
+ const result = {
1420
+ promptTokens: Number(usage.prompt_tokens ?? 0),
1421
+ completionTokens: Number(usage.completion_tokens ?? 0),
1422
+ totalTokens: Number(usage.total_tokens ?? 0),
1423
+ cost: Number(cost ?? 0),
1424
+ satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
1425
+ };
1426
+ if (result.promptTokens === 0 && result.completionTokens === 0 && result.totalTokens === 0 && result.cost === 0 && result.satsCost === 0) {
1427
+ return null;
1428
+ }
1429
+ return result;
1430
+ }
1431
+ function toUsageStats(usage) {
1432
+ if (!usage) return void 0;
1433
+ return {
1434
+ total_tokens: usage.totalTokens,
1435
+ prompt_tokens: usage.promptTokens,
1436
+ completion_tokens: usage.completionTokens,
1437
+ cost: usage.cost,
1438
+ sats_cost: usage.satsCost
1439
+ };
1440
+ }
1441
+
1328
1442
  // client/StreamProcessor.ts
1329
1443
  var StreamProcessor = class {
1330
1444
  accumulatedContent = "";
@@ -1352,6 +1466,7 @@ var StreamProcessor = class {
1352
1466
  let finish_reason;
1353
1467
  let citations;
1354
1468
  let annotations;
1469
+ let responseId;
1355
1470
  try {
1356
1471
  while (true) {
1357
1472
  const { done, value } = await reader.read();
@@ -1380,6 +1495,9 @@ var StreamProcessor = class {
1380
1495
  if (parsed.finish_reason) {
1381
1496
  finish_reason = parsed.finish_reason;
1382
1497
  }
1498
+ if (parsed.responseId) {
1499
+ responseId = parsed.responseId;
1500
+ }
1383
1501
  if (parsed.citations) {
1384
1502
  citations = parsed.citations;
1385
1503
  }
@@ -1400,6 +1518,7 @@ var StreamProcessor = class {
1400
1518
  images: this.accumulatedImages.length > 0 ? this.accumulatedImages : void 0,
1401
1519
  usage,
1402
1520
  model,
1521
+ responseId,
1403
1522
  finish_reason,
1404
1523
  citations,
1405
1524
  annotations
@@ -1427,12 +1546,15 @@ var StreamProcessor = class {
1427
1546
  result.reasoning = parsed.choices[0].delta.reasoning;
1428
1547
  }
1429
1548
  if (parsed.usage) {
1430
- result.usage = {
1549
+ result.usage = toUsageStats(extractUsageFromSSEJson(parsed)) ?? {
1431
1550
  total_tokens: parsed.usage.total_tokens,
1432
1551
  prompt_tokens: parsed.usage.prompt_tokens,
1433
1552
  completion_tokens: parsed.usage.completion_tokens
1434
1553
  };
1435
1554
  }
1555
+ if (parsed.id) {
1556
+ result.responseId = parsed.id;
1557
+ }
1436
1558
  if (parsed.model) {
1437
1559
  result.model = parsed.model;
1438
1560
  }
@@ -1824,7 +1946,6 @@ var ProviderManager = class _ProviderManager {
1824
1946
  * Get providers for a model sorted by prompt+completion pricing
1825
1947
  */
1826
1948
  getProviderPriceRankingForModel(modelId, options = {}) {
1827
- const normalizedId = this.normalizeModelId(modelId);
1828
1949
  const includeDisabled = options.includeDisabled ?? false;
1829
1950
  const torMode = options.torMode ?? false;
1830
1951
  const disabledProviders = new Set(
@@ -1838,9 +1959,7 @@ var ProviderManager = class _ProviderManager {
1838
1959
  if (torMode && !baseUrl.includes(".onion")) continue;
1839
1960
  if (!torMode && (baseUrl.includes(".onion") || isInsecureHttpUrl(baseUrl)))
1840
1961
  continue;
1841
- const match = models.find(
1842
- (model) => this.normalizeModelId(model.id) === normalizedId
1843
- );
1962
+ const match = models.find((model) => model.id === modelId);
1844
1963
  if (!match?.sats_pricing) continue;
1845
1964
  const prompt = match.sats_pricing.prompt;
1846
1965
  const completion = match.sats_pricing.completion;
@@ -1953,7 +2072,1058 @@ var ProviderManager = class _ProviderManager {
1953
2072
  }
1954
2073
  };
1955
2074
 
1956
- // client/RoutstrClient.ts
2075
+ // storage/drivers/localStorage.ts
2076
+ var canUseLocalStorage = () => {
2077
+ return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
2078
+ };
2079
+ var isQuotaExceeded = (error) => {
2080
+ const e = error;
2081
+ return !!e && (e?.name === "QuotaExceededError" || e?.code === 22 || e?.code === 1014);
2082
+ };
2083
+ var NON_CRITICAL_KEYS = /* @__PURE__ */ new Set(["modelsFromAllProviders"]);
2084
+ var localStorageDriver = {
2085
+ async getItem(key, defaultValue) {
2086
+ if (!canUseLocalStorage()) return defaultValue;
2087
+ try {
2088
+ const item = window.localStorage.getItem(key);
2089
+ if (item === null) return defaultValue;
2090
+ try {
2091
+ return JSON.parse(item);
2092
+ } catch (parseError) {
2093
+ if (typeof defaultValue === "string") {
2094
+ return item;
2095
+ }
2096
+ throw parseError;
2097
+ }
2098
+ } catch (error) {
2099
+ console.error(`Error retrieving item with key "${key}":`, error);
2100
+ if (canUseLocalStorage()) {
2101
+ try {
2102
+ window.localStorage.removeItem(key);
2103
+ } catch (removeError) {
2104
+ console.error(
2105
+ `Error removing corrupted item with key "${key}":`,
2106
+ removeError
2107
+ );
2108
+ }
2109
+ }
2110
+ return defaultValue;
2111
+ }
2112
+ },
2113
+ async setItem(key, value) {
2114
+ if (!canUseLocalStorage()) return;
2115
+ try {
2116
+ window.localStorage.setItem(key, JSON.stringify(value));
2117
+ } catch (error) {
2118
+ if (isQuotaExceeded(error)) {
2119
+ if (NON_CRITICAL_KEYS.has(key)) {
2120
+ console.warn(
2121
+ `Storage quota exceeded; skipping non-critical key "${key}".`
2122
+ );
2123
+ return;
2124
+ }
2125
+ try {
2126
+ window.localStorage.removeItem("modelsFromAllProviders");
2127
+ } catch {
2128
+ }
2129
+ try {
2130
+ window.localStorage.setItem(key, JSON.stringify(value));
2131
+ return;
2132
+ } catch (retryError) {
2133
+ console.warn(
2134
+ `Storage quota exceeded; unable to persist key "${key}" after cleanup attempt.`,
2135
+ retryError
2136
+ );
2137
+ return;
2138
+ }
2139
+ }
2140
+ console.error(`Error storing item with key "${key}":`, error);
2141
+ }
2142
+ },
2143
+ async removeItem(key) {
2144
+ if (!canUseLocalStorage()) return;
2145
+ try {
2146
+ window.localStorage.removeItem(key);
2147
+ } catch (error) {
2148
+ console.error(`Error removing item with key "${key}":`, error);
2149
+ }
2150
+ }
2151
+ };
2152
+
2153
+ // storage/drivers/memory.ts
2154
+ var createMemoryDriver = (seed) => {
2155
+ const store = /* @__PURE__ */ new Map();
2156
+ return {
2157
+ async getItem(key, defaultValue) {
2158
+ const item = store.get(key);
2159
+ if (item === void 0) return defaultValue;
2160
+ try {
2161
+ return JSON.parse(item);
2162
+ } catch (parseError) {
2163
+ if (typeof defaultValue === "string") {
2164
+ return item;
2165
+ }
2166
+ throw parseError;
2167
+ }
2168
+ },
2169
+ async setItem(key, value) {
2170
+ store.set(key, JSON.stringify(value));
2171
+ },
2172
+ async removeItem(key) {
2173
+ store.delete(key);
2174
+ }
2175
+ };
2176
+ };
2177
+
2178
+ // storage/drivers/sqlite.ts
2179
+ var isBun = () => {
2180
+ return typeof process.versions.bun !== "undefined";
2181
+ };
2182
+ var createDatabase = (dbPath) => {
2183
+ if (isBun()) {
2184
+ throw new Error(
2185
+ "SQLite driver not supported in Bun. Use createMemoryDriver() instead."
2186
+ );
2187
+ }
2188
+ let Database = null;
2189
+ try {
2190
+ Database = __require("better-sqlite3");
2191
+ } catch (error) {
2192
+ throw new Error(
2193
+ `better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
2194
+ );
2195
+ }
2196
+ return new Database(dbPath);
2197
+ };
2198
+ var createSqliteDriver = (options = {}) => {
2199
+ const dbPath = options.dbPath || "routstr.sqlite";
2200
+ const tableName = options.tableName || "sdk_storage";
2201
+ const db = createDatabase(dbPath);
2202
+ db.exec(
2203
+ `CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
2204
+ );
2205
+ const selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
2206
+ const upsertStmt = db.prepare(
2207
+ `INSERT INTO ${tableName} (key, value) VALUES (?, ?)
2208
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`
2209
+ );
2210
+ const deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
2211
+ return {
2212
+ async getItem(key, defaultValue) {
2213
+ try {
2214
+ const row = selectStmt.get(key);
2215
+ if (!row || typeof row.value !== "string") return defaultValue;
2216
+ try {
2217
+ return JSON.parse(row.value);
2218
+ } catch (parseError) {
2219
+ if (typeof defaultValue === "string") {
2220
+ return row.value;
2221
+ }
2222
+ throw parseError;
2223
+ }
2224
+ } catch (error) {
2225
+ console.error(`SQLite getItem failed for key "${key}":`, error);
2226
+ return defaultValue;
2227
+ }
2228
+ },
2229
+ async setItem(key, value) {
2230
+ try {
2231
+ upsertStmt.run(key, JSON.stringify(value));
2232
+ } catch (error) {
2233
+ console.error(`SQLite setItem failed for key "${key}":`, error);
2234
+ }
2235
+ },
2236
+ async removeItem(key) {
2237
+ try {
2238
+ deleteStmt.run(key);
2239
+ } catch (error) {
2240
+ console.error(`SQLite removeItem failed for key "${key}":`, error);
2241
+ }
2242
+ }
2243
+ };
2244
+ };
2245
+
2246
+ // storage/keys.ts
2247
+ var SDK_STORAGE_KEYS = {
2248
+ MODELS_FROM_ALL_PROVIDERS: "modelsFromAllProviders",
2249
+ LAST_USED_MODEL: "lastUsedModel",
2250
+ BASE_URLS_LIST: "base_urls_list",
2251
+ DISABLED_PROVIDERS: "disabled_providers",
2252
+ MINTS_FROM_ALL_PROVIDERS: "mints_from_all_providers",
2253
+ INFO_FROM_ALL_PROVIDERS: "info_from_all_providers",
2254
+ LAST_MODELS_UPDATE: "lastModelsUpdate",
2255
+ LAST_BASE_URLS_UPDATE: "lastBaseUrlsUpdate",
2256
+ LOCAL_CASHU_TOKENS: "local_cashu_tokens",
2257
+ API_KEYS: "api_keys",
2258
+ CHILD_KEYS: "child_keys",
2259
+ ROUTSTR21_MODELS: "routstr21Models",
2260
+ LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
2261
+ CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
2262
+ USAGE_TRACKING: "usage_tracking",
2263
+ CLIENT_IDS: "client_ids"
2264
+ };
2265
+
2266
+ // storage/usageTracking/indexedDB.ts
2267
+ var DEFAULT_DB_NAME = "routstr-sdk";
2268
+ var DEFAULT_STORE_NAME = "usage_tracking";
2269
+ var MIGRATION_MARKER_KEY = "usage_tracking_migration_v1";
2270
+ var isBrowser = typeof indexedDB !== "undefined";
2271
+ var normalizeBaseUrl = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
2272
+ var openDatabase = (dbName, storeName) => {
2273
+ if (!isBrowser) {
2274
+ return Promise.reject(new Error("IndexedDB is not available"));
2275
+ }
2276
+ return new Promise((resolve, reject) => {
2277
+ const request = indexedDB.open(dbName, 1);
2278
+ request.onupgradeneeded = () => {
2279
+ const db = request.result;
2280
+ if (!db.objectStoreNames.contains(storeName)) {
2281
+ const store = db.createObjectStore(storeName, { keyPath: "id" });
2282
+ store.createIndex("timestamp", "timestamp", { unique: false });
2283
+ store.createIndex("modelId", "modelId", { unique: false });
2284
+ store.createIndex("baseUrl", "baseUrl", { unique: false });
2285
+ store.createIndex("sessionId", "sessionId", { unique: false });
2286
+ store.createIndex("client", "client", { unique: false });
2287
+ }
2288
+ };
2289
+ request.onsuccess = () => resolve(request.result);
2290
+ request.onerror = () => reject(request.error);
2291
+ });
2292
+ };
2293
+ var matchesFilters = (entry, options = {}) => {
2294
+ if (typeof options.before === "number" && entry.timestamp >= options.before) {
2295
+ return false;
2296
+ }
2297
+ if (typeof options.after === "number" && entry.timestamp <= options.after) {
2298
+ return false;
2299
+ }
2300
+ if (options.modelId && entry.modelId !== options.modelId) {
2301
+ return false;
2302
+ }
2303
+ if (options.baseUrl && normalizeBaseUrl(entry.baseUrl) !== normalizeBaseUrl(options.baseUrl)) {
2304
+ return false;
2305
+ }
2306
+ if (options.sessionId && entry.sessionId !== options.sessionId) {
2307
+ return false;
2308
+ }
2309
+ if (options.client && entry.client !== options.client) {
2310
+ return false;
2311
+ }
2312
+ return true;
2313
+ };
2314
+ var createIndexedDBUsageTrackingDriver = (options = {}) => {
2315
+ const dbName = options.dbName || DEFAULT_DB_NAME;
2316
+ const storeName = options.storeName || DEFAULT_STORE_NAME;
2317
+ const legacyStorageDriver = options.legacyStorageDriver;
2318
+ let dbPromise = null;
2319
+ let migrationPromise = null;
2320
+ const getDb = () => {
2321
+ if (!dbPromise) {
2322
+ dbPromise = openDatabase(dbName, storeName);
2323
+ }
2324
+ return dbPromise;
2325
+ };
2326
+ const putMany = async (entries) => {
2327
+ if (entries.length === 0) return;
2328
+ const db = await getDb();
2329
+ await new Promise((resolve, reject) => {
2330
+ const tx = db.transaction(storeName, "readwrite");
2331
+ const store = tx.objectStore(storeName);
2332
+ for (const entry of entries) {
2333
+ store.put({ ...entry, baseUrl: normalizeBaseUrl(entry.baseUrl) });
2334
+ }
2335
+ tx.oncomplete = () => resolve();
2336
+ tx.onerror = () => reject(tx.error);
2337
+ });
2338
+ };
2339
+ const ensureMigrated = async () => {
2340
+ if (!legacyStorageDriver) return;
2341
+ if (!migrationPromise) {
2342
+ migrationPromise = (async () => {
2343
+ const migrated = await legacyStorageDriver.getItem(
2344
+ MIGRATION_MARKER_KEY,
2345
+ false
2346
+ );
2347
+ if (migrated) return;
2348
+ const legacyEntries = await legacyStorageDriver.getItem(
2349
+ SDK_STORAGE_KEYS.USAGE_TRACKING,
2350
+ []
2351
+ );
2352
+ if (legacyEntries.length > 0) {
2353
+ await putMany(legacyEntries);
2354
+ await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
2355
+ }
2356
+ await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY, true);
2357
+ })();
2358
+ }
2359
+ await migrationPromise;
2360
+ };
2361
+ return {
2362
+ async migrate() {
2363
+ await ensureMigrated();
2364
+ },
2365
+ async append(entry) {
2366
+ await ensureMigrated();
2367
+ await putMany([entry]);
2368
+ },
2369
+ async appendMany(entries) {
2370
+ await ensureMigrated();
2371
+ await putMany(entries);
2372
+ },
2373
+ async list(options2 = {}) {
2374
+ await ensureMigrated();
2375
+ const db = await getDb();
2376
+ return new Promise((resolve, reject) => {
2377
+ const tx = db.transaction(storeName, "readonly");
2378
+ const store = tx.objectStore(storeName);
2379
+ const index = store.index("timestamp");
2380
+ const direction = "prev";
2381
+ const request = index.openCursor(null, direction);
2382
+ const results = [];
2383
+ const limit = options2.limit;
2384
+ request.onsuccess = () => {
2385
+ const cursor = request.result;
2386
+ if (!cursor) {
2387
+ resolve(results);
2388
+ return;
2389
+ }
2390
+ const value = cursor.value;
2391
+ if (matchesFilters(value, options2)) {
2392
+ results.push(value);
2393
+ if (typeof limit === "number" && results.length >= limit) {
2394
+ resolve(results);
2395
+ return;
2396
+ }
2397
+ }
2398
+ cursor.continue();
2399
+ };
2400
+ request.onerror = () => reject(request.error);
2401
+ });
2402
+ },
2403
+ async count(options2 = {}) {
2404
+ const results = await this.list(options2);
2405
+ return results.length;
2406
+ },
2407
+ async deleteOlderThan(timestamp) {
2408
+ await ensureMigrated();
2409
+ const db = await getDb();
2410
+ return new Promise((resolve, reject) => {
2411
+ const tx = db.transaction(storeName, "readwrite");
2412
+ const store = tx.objectStore(storeName);
2413
+ const index = store.index("timestamp");
2414
+ const range = IDBKeyRange.upperBound(timestamp, true);
2415
+ const request = index.openCursor(range);
2416
+ let deleted = 0;
2417
+ request.onsuccess = () => {
2418
+ const cursor = request.result;
2419
+ if (!cursor) {
2420
+ resolve(deleted);
2421
+ return;
2422
+ }
2423
+ deleted += 1;
2424
+ cursor.delete();
2425
+ cursor.continue();
2426
+ };
2427
+ request.onerror = () => reject(request.error);
2428
+ });
2429
+ },
2430
+ async clear() {
2431
+ await ensureMigrated();
2432
+ const db = await getDb();
2433
+ await new Promise((resolve, reject) => {
2434
+ const tx = db.transaction(storeName, "readwrite");
2435
+ tx.objectStore(storeName).clear();
2436
+ tx.oncomplete = () => resolve();
2437
+ tx.onerror = () => reject(tx.error);
2438
+ });
2439
+ }
2440
+ };
2441
+ };
2442
+
2443
+ // storage/usageTracking/sqlite.ts
2444
+ var MIGRATION_MARKER_KEY2 = "usage_tracking_migration_v1";
2445
+ var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
2446
+ var isBun2 = () => {
2447
+ return typeof process.versions.bun !== "undefined";
2448
+ };
2449
+ var createDatabase2 = (dbPath) => {
2450
+ if (isBun2()) {
2451
+ throw new Error(
2452
+ "SQLite driver not supported in Bun. Use createMemoryDriver() instead."
2453
+ );
2454
+ }
2455
+ let Database = null;
2456
+ try {
2457
+ Database = __require("better-sqlite3");
2458
+ } catch (error) {
2459
+ throw new Error(
2460
+ `better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
2461
+ );
2462
+ }
2463
+ return new Database(dbPath);
2464
+ };
2465
+ var buildWhereClause = (options = {}) => {
2466
+ const clauses = [];
2467
+ const params = [];
2468
+ if (typeof options.before === "number") {
2469
+ clauses.push("timestamp < ?");
2470
+ params.push(options.before);
2471
+ }
2472
+ if (typeof options.after === "number") {
2473
+ clauses.push("timestamp > ?");
2474
+ params.push(options.after);
2475
+ }
2476
+ if (options.modelId) {
2477
+ clauses.push("model_id = ?");
2478
+ params.push(options.modelId);
2479
+ }
2480
+ if (options.baseUrl) {
2481
+ clauses.push("base_url = ?");
2482
+ params.push(normalizeBaseUrl2(options.baseUrl));
2483
+ }
2484
+ if (options.sessionId) {
2485
+ clauses.push("session_id = ?");
2486
+ params.push(options.sessionId);
2487
+ }
2488
+ if (options.client) {
2489
+ clauses.push("client = ?");
2490
+ params.push(options.client);
2491
+ }
2492
+ return {
2493
+ sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
2494
+ params
2495
+ };
2496
+ };
2497
+ var createSqliteUsageTrackingDriver = (options = {}) => {
2498
+ const dbPath = options.dbPath || "routstr.sqlite";
2499
+ const tableName = options.tableName || "usage_tracking";
2500
+ const db = createDatabase2(dbPath);
2501
+ const legacyStorageDriver = options.legacyStorageDriver;
2502
+ db.exec(`
2503
+ CREATE TABLE IF NOT EXISTS ${tableName} (
2504
+ id TEXT PRIMARY KEY,
2505
+ timestamp INTEGER NOT NULL,
2506
+ model_id TEXT NOT NULL,
2507
+ base_url TEXT NOT NULL,
2508
+ request_id TEXT NOT NULL,
2509
+ cost REAL NOT NULL,
2510
+ sats_cost REAL NOT NULL,
2511
+ prompt_tokens INTEGER NOT NULL,
2512
+ completion_tokens INTEGER NOT NULL,
2513
+ total_tokens INTEGER NOT NULL,
2514
+ client TEXT,
2515
+ session_id TEXT,
2516
+ tags TEXT
2517
+ );
2518
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
2519
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
2520
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
2521
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
2522
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
2523
+ `);
2524
+ const insertStmt = db.prepare(`
2525
+ INSERT OR REPLACE INTO ${tableName} (
2526
+ id, timestamp, model_id, base_url, request_id,
2527
+ cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
2528
+ client, session_id, tags
2529
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2530
+ `);
2531
+ let migrationComplete = false;
2532
+ const appendOne = (entry) => {
2533
+ insertStmt.run(
2534
+ entry.id,
2535
+ entry.timestamp,
2536
+ entry.modelId,
2537
+ normalizeBaseUrl2(entry.baseUrl),
2538
+ entry.requestId,
2539
+ entry.cost,
2540
+ entry.satsCost,
2541
+ entry.promptTokens,
2542
+ entry.completionTokens,
2543
+ entry.totalTokens,
2544
+ entry.client ?? null,
2545
+ entry.sessionId ?? null,
2546
+ JSON.stringify(entry.tags ?? [])
2547
+ );
2548
+ };
2549
+ const ensureMigrated = async () => {
2550
+ if (!legacyStorageDriver || migrationComplete) return;
2551
+ const migrated = await legacyStorageDriver.getItem(
2552
+ MIGRATION_MARKER_KEY2,
2553
+ false
2554
+ );
2555
+ if (migrated) {
2556
+ migrationComplete = true;
2557
+ return;
2558
+ }
2559
+ const legacyEntries = await legacyStorageDriver.getItem(
2560
+ SDK_STORAGE_KEYS.USAGE_TRACKING,
2561
+ []
2562
+ );
2563
+ for (const entry of legacyEntries) {
2564
+ appendOne(entry);
2565
+ }
2566
+ if (legacyEntries.length > 0) {
2567
+ await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
2568
+ }
2569
+ await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY2, true);
2570
+ migrationComplete = true;
2571
+ };
2572
+ const mapRow = (row) => ({
2573
+ id: row.id,
2574
+ timestamp: row.timestamp,
2575
+ modelId: row.model_id,
2576
+ baseUrl: row.base_url,
2577
+ requestId: row.request_id,
2578
+ cost: row.cost,
2579
+ satsCost: row.sats_cost,
2580
+ promptTokens: row.prompt_tokens,
2581
+ completionTokens: row.completion_tokens,
2582
+ totalTokens: row.total_tokens,
2583
+ client: row.client ?? void 0,
2584
+ sessionId: row.session_id ?? void 0,
2585
+ tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
2586
+ });
2587
+ return {
2588
+ async migrate() {
2589
+ await ensureMigrated();
2590
+ },
2591
+ async append(entry) {
2592
+ await ensureMigrated();
2593
+ appendOne(entry);
2594
+ },
2595
+ async appendMany(entries) {
2596
+ await ensureMigrated();
2597
+ for (const entry of entries) {
2598
+ appendOne(entry);
2599
+ }
2600
+ },
2601
+ async list(options2 = {}) {
2602
+ await ensureMigrated();
2603
+ const { sql, params } = buildWhereClause(options2);
2604
+ const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
2605
+ const stmt = db.prepare(
2606
+ `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`
2607
+ );
2608
+ const rows = stmt.all(
2609
+ ...typeof options2.limit === "number" ? [...params, options2.limit] : params
2610
+ );
2611
+ return rows.map(mapRow);
2612
+ },
2613
+ async count(options2 = {}) {
2614
+ await ensureMigrated();
2615
+ const { sql, params } = buildWhereClause(options2);
2616
+ const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
2617
+ const row = stmt.get(...params);
2618
+ return Number(row?.count ?? 0);
2619
+ },
2620
+ async deleteOlderThan(timestamp) {
2621
+ await ensureMigrated();
2622
+ const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
2623
+ const result = stmt.run(timestamp);
2624
+ return result.changes;
2625
+ },
2626
+ async clear() {
2627
+ await ensureMigrated();
2628
+ db.prepare(`DELETE FROM ${tableName}`).run();
2629
+ }
2630
+ };
2631
+ };
2632
+
2633
+ // storage/usageTracking/memory.ts
2634
+ var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
2635
+ var matchesFilters2 = (entry, options = {}) => {
2636
+ if (typeof options.before === "number" && entry.timestamp >= options.before) {
2637
+ return false;
2638
+ }
2639
+ if (typeof options.after === "number" && entry.timestamp <= options.after) {
2640
+ return false;
2641
+ }
2642
+ if (options.modelId && entry.modelId !== options.modelId) {
2643
+ return false;
2644
+ }
2645
+ if (options.baseUrl && normalizeBaseUrl3(entry.baseUrl) !== normalizeBaseUrl3(options.baseUrl)) {
2646
+ return false;
2647
+ }
2648
+ if (options.sessionId && entry.sessionId !== options.sessionId) {
2649
+ return false;
2650
+ }
2651
+ if (options.client && entry.client !== options.client) {
2652
+ return false;
2653
+ }
2654
+ return true;
2655
+ };
2656
+ var createMemoryUsageTrackingDriver = (seed = []) => {
2657
+ const store = /* @__PURE__ */ new Map();
2658
+ for (const entry of seed) {
2659
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
2660
+ }
2661
+ return {
2662
+ async migrate() {
2663
+ return;
2664
+ },
2665
+ async append(entry) {
2666
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
2667
+ },
2668
+ async appendMany(entries) {
2669
+ for (const entry of entries) {
2670
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
2671
+ }
2672
+ },
2673
+ async list(options = {}) {
2674
+ const entries = [...store.values()].filter((entry) => matchesFilters2(entry, options)).sort((a, b) => b.timestamp - a.timestamp);
2675
+ if (typeof options.limit === "number") {
2676
+ return entries.slice(0, options.limit);
2677
+ }
2678
+ return entries;
2679
+ },
2680
+ async count(options = {}) {
2681
+ return (await this.list(options)).length;
2682
+ },
2683
+ async deleteOlderThan(timestamp) {
2684
+ let deleted = 0;
2685
+ for (const [id, entry] of store.entries()) {
2686
+ if (entry.timestamp < timestamp) {
2687
+ store.delete(id);
2688
+ deleted += 1;
2689
+ }
2690
+ }
2691
+ return deleted;
2692
+ },
2693
+ async clear() {
2694
+ store.clear();
2695
+ }
2696
+ };
2697
+ };
2698
+ var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
2699
+ var getCashuTokenBalance = (token) => {
2700
+ try {
2701
+ const decoded = getDecodedToken(token);
2702
+ const unitDivisor = decoded.unit === "msat" ? 1e3 : 1;
2703
+ let sum = 0;
2704
+ for (const proof of decoded.proofs) {
2705
+ sum += proof.amount / unitDivisor;
2706
+ }
2707
+ return sum;
2708
+ } catch {
2709
+ return 0;
2710
+ }
2711
+ };
2712
+ var createEmptyStore = (driver) => createStore((set, get) => ({
2713
+ modelsFromAllProviders: {},
2714
+ lastUsedModel: null,
2715
+ baseUrlsList: [],
2716
+ lastBaseUrlsUpdate: null,
2717
+ disabledProviders: [],
2718
+ mintsFromAllProviders: {},
2719
+ infoFromAllProviders: {},
2720
+ lastModelsUpdate: {},
2721
+ cachedTokens: [],
2722
+ apiKeys: [],
2723
+ childKeys: [],
2724
+ routstr21Models: [],
2725
+ lastRoutstr21ModelsUpdate: null,
2726
+ cachedReceiveTokens: [],
2727
+ clientIds: [],
2728
+ setModelsFromAllProviders: (value) => {
2729
+ const normalized = {};
2730
+ for (const [baseUrl, models] of Object.entries(value)) {
2731
+ normalized[normalizeBaseUrl4(baseUrl)] = models;
2732
+ }
2733
+ void driver.setItem(
2734
+ SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
2735
+ normalized
2736
+ );
2737
+ set({ modelsFromAllProviders: normalized });
2738
+ },
2739
+ setLastUsedModel: (value) => {
2740
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, value);
2741
+ set({ lastUsedModel: value });
2742
+ },
2743
+ setBaseUrlsList: (value) => {
2744
+ const normalized = value.map((url) => normalizeBaseUrl4(url));
2745
+ void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
2746
+ set({ baseUrlsList: normalized });
2747
+ },
2748
+ setBaseUrlsLastUpdate: (value) => {
2749
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, value);
2750
+ set({ lastBaseUrlsUpdate: value });
2751
+ },
2752
+ setDisabledProviders: (value) => {
2753
+ const normalized = value.map((url) => normalizeBaseUrl4(url));
2754
+ void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
2755
+ set({ disabledProviders: normalized });
2756
+ },
2757
+ setMintsFromAllProviders: (value) => {
2758
+ const normalized = {};
2759
+ for (const [baseUrl, mints] of Object.entries(value)) {
2760
+ normalized[normalizeBaseUrl4(baseUrl)] = mints.map(
2761
+ (mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
2762
+ );
2763
+ }
2764
+ void driver.setItem(
2765
+ SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
2766
+ normalized
2767
+ );
2768
+ set({ mintsFromAllProviders: normalized });
2769
+ },
2770
+ setInfoFromAllProviders: (value) => {
2771
+ const normalized = {};
2772
+ for (const [baseUrl, info] of Object.entries(value)) {
2773
+ normalized[normalizeBaseUrl4(baseUrl)] = info;
2774
+ }
2775
+ void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
2776
+ set({ infoFromAllProviders: normalized });
2777
+ },
2778
+ setLastModelsUpdate: (value) => {
2779
+ const normalized = {};
2780
+ for (const [baseUrl, timestamp] of Object.entries(value)) {
2781
+ normalized[normalizeBaseUrl4(baseUrl)] = timestamp;
2782
+ }
2783
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
2784
+ set({ lastModelsUpdate: normalized });
2785
+ },
2786
+ setCachedTokens: (value) => {
2787
+ set((state) => {
2788
+ const updates = typeof value === "function" ? value(state.cachedTokens) : value;
2789
+ const normalized = updates.map((entry) => ({
2790
+ ...entry,
2791
+ baseUrl: normalizeBaseUrl4(entry.baseUrl),
2792
+ balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
2793
+ lastUsed: entry.lastUsed ?? null
2794
+ }));
2795
+ void driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
2796
+ return { cachedTokens: normalized };
2797
+ });
2798
+ },
2799
+ setApiKeys: (value) => {
2800
+ set((state) => {
2801
+ const updates = typeof value === "function" ? value(state.apiKeys) : value;
2802
+ const normalized = updates.map((entry) => ({
2803
+ ...entry,
2804
+ baseUrl: normalizeBaseUrl4(entry.baseUrl),
2805
+ balance: entry.balance ?? 0,
2806
+ lastUsed: entry.lastUsed ?? null
2807
+ }));
2808
+ void driver.setItem(SDK_STORAGE_KEYS.API_KEYS, normalized);
2809
+ return { apiKeys: normalized };
2810
+ });
2811
+ },
2812
+ setChildKeys: (value) => {
2813
+ set((state) => {
2814
+ const updates = typeof value === "function" ? value(state.childKeys) : value;
2815
+ const normalized = updates.map((entry) => ({
2816
+ parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
2817
+ childKey: entry.childKey,
2818
+ balance: entry.balance ?? 0,
2819
+ balanceLimit: entry.balanceLimit,
2820
+ validityDate: entry.validityDate,
2821
+ createdAt: entry.createdAt ?? Date.now()
2822
+ }));
2823
+ void driver.setItem(SDK_STORAGE_KEYS.CHILD_KEYS, normalized);
2824
+ return { childKeys: normalized };
2825
+ });
2826
+ },
2827
+ setRoutstr21Models: (value) => {
2828
+ void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
2829
+ set({ routstr21Models: value });
2830
+ },
2831
+ setRoutstr21ModelsLastUpdate: (value) => {
2832
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE, value);
2833
+ set({ lastRoutstr21ModelsUpdate: value });
2834
+ },
2835
+ setCachedReceiveTokens: (value) => {
2836
+ const normalized = value.map((entry) => ({
2837
+ token: entry.token,
2838
+ amount: entry.amount,
2839
+ unit: entry.unit || "sat",
2840
+ createdAt: entry.createdAt ?? Date.now()
2841
+ }));
2842
+ void driver.setItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, normalized);
2843
+ set({ cachedReceiveTokens: normalized });
2844
+ },
2845
+ setClientIds: (value) => {
2846
+ set((state) => {
2847
+ const updates = typeof value === "function" ? value(state.clientIds) : value;
2848
+ const normalized = updates.map((entry) => ({
2849
+ ...entry,
2850
+ createdAt: entry.createdAt ?? Date.now(),
2851
+ lastUsed: entry.lastUsed ?? null
2852
+ }));
2853
+ void driver.setItem(SDK_STORAGE_KEYS.CLIENT_IDS, normalized);
2854
+ return { clientIds: normalized };
2855
+ });
2856
+ }
2857
+ }));
2858
+ var hydrateStoreFromDriver = async (store, driver) => {
2859
+ const [
2860
+ rawModels,
2861
+ lastUsedModel,
2862
+ rawBaseUrls,
2863
+ lastBaseUrlsUpdate,
2864
+ rawDisabledProviders,
2865
+ rawMints,
2866
+ rawInfo,
2867
+ rawLastModelsUpdate,
2868
+ rawCachedTokens,
2869
+ rawApiKeys,
2870
+ rawChildKeys,
2871
+ rawRoutstr21Models,
2872
+ rawLastRoutstr21ModelsUpdate,
2873
+ rawCachedReceiveTokens,
2874
+ rawClientIds
2875
+ ] = await Promise.all([
2876
+ driver.getItem(
2877
+ SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
2878
+ {}
2879
+ ),
2880
+ driver.getItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, null),
2881
+ driver.getItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, []),
2882
+ driver.getItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, null),
2883
+ driver.getItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, []),
2884
+ driver.getItem(
2885
+ SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
2886
+ {}
2887
+ ),
2888
+ driver.getItem(
2889
+ SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS,
2890
+ {}
2891
+ ),
2892
+ driver.getItem(
2893
+ SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
2894
+ {}
2895
+ ),
2896
+ driver.getItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, []),
2897
+ driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []),
2898
+ driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []),
2899
+ driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
2900
+ driver.getItem(
2901
+ SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
2902
+ null
2903
+ ),
2904
+ driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
2905
+ driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
2906
+ ]);
2907
+ const modelsFromAllProviders = Object.fromEntries(
2908
+ Object.entries(rawModels).map(([baseUrl, models]) => [
2909
+ normalizeBaseUrl4(baseUrl),
2910
+ models
2911
+ ])
2912
+ );
2913
+ const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl4(url));
2914
+ const disabledProviders = rawDisabledProviders.map(
2915
+ (url) => normalizeBaseUrl4(url)
2916
+ );
2917
+ const mintsFromAllProviders = Object.fromEntries(
2918
+ Object.entries(rawMints).map(([baseUrl, mints]) => [
2919
+ normalizeBaseUrl4(baseUrl),
2920
+ mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
2921
+ ])
2922
+ );
2923
+ const infoFromAllProviders = Object.fromEntries(
2924
+ Object.entries(rawInfo).map(([baseUrl, info]) => [
2925
+ normalizeBaseUrl4(baseUrl),
2926
+ info
2927
+ ])
2928
+ );
2929
+ const lastModelsUpdate = Object.fromEntries(
2930
+ Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
2931
+ normalizeBaseUrl4(baseUrl),
2932
+ timestamp
2933
+ ])
2934
+ );
2935
+ const cachedTokens = rawCachedTokens.map((entry) => ({
2936
+ ...entry,
2937
+ baseUrl: normalizeBaseUrl4(entry.baseUrl),
2938
+ balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
2939
+ lastUsed: entry.lastUsed ?? null
2940
+ }));
2941
+ const apiKeys = rawApiKeys.map((entry) => ({
2942
+ ...entry,
2943
+ baseUrl: normalizeBaseUrl4(entry.baseUrl),
2944
+ balance: entry.balance ?? 0,
2945
+ lastUsed: entry.lastUsed ?? null
2946
+ }));
2947
+ const childKeys = rawChildKeys.map((entry) => ({
2948
+ parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
2949
+ childKey: entry.childKey,
2950
+ balance: entry.balance ?? 0,
2951
+ balanceLimit: entry.balanceLimit,
2952
+ validityDate: entry.validityDate,
2953
+ createdAt: entry.createdAt ?? Date.now()
2954
+ }));
2955
+ const routstr21Models = rawRoutstr21Models;
2956
+ const lastRoutstr21ModelsUpdate = rawLastRoutstr21ModelsUpdate;
2957
+ const cachedReceiveTokens = rawCachedReceiveTokens?.map((entry) => ({
2958
+ token: entry.token,
2959
+ amount: entry.amount,
2960
+ unit: entry.unit || "sat",
2961
+ createdAt: entry.createdAt ?? Date.now()
2962
+ }));
2963
+ const clientIds = rawClientIds.map((entry) => ({
2964
+ ...entry,
2965
+ createdAt: entry.createdAt ?? Date.now(),
2966
+ lastUsed: entry.lastUsed ?? null
2967
+ }));
2968
+ store.setState({
2969
+ modelsFromAllProviders,
2970
+ lastUsedModel,
2971
+ baseUrlsList,
2972
+ lastBaseUrlsUpdate,
2973
+ disabledProviders,
2974
+ mintsFromAllProviders,
2975
+ infoFromAllProviders,
2976
+ lastModelsUpdate,
2977
+ cachedTokens,
2978
+ apiKeys,
2979
+ childKeys,
2980
+ routstr21Models,
2981
+ lastRoutstr21ModelsUpdate,
2982
+ cachedReceiveTokens,
2983
+ clientIds
2984
+ });
2985
+ };
2986
+ var createSdkStore = ({
2987
+ driver
2988
+ }) => {
2989
+ const store = createEmptyStore(driver);
2990
+ return {
2991
+ store,
2992
+ hydrate: hydrateStoreFromDriver(store, driver)
2993
+ };
2994
+ };
2995
+
2996
+ // storage/index.ts
2997
+ var isBrowser2 = () => {
2998
+ try {
2999
+ return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
3000
+ } catch {
3001
+ return false;
3002
+ }
3003
+ };
3004
+ var isNode = () => {
3005
+ try {
3006
+ return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
3007
+ } catch {
3008
+ return false;
3009
+ }
3010
+ };
3011
+ var defaultDriver = null;
3012
+ var isBun3 = () => {
3013
+ return typeof process.versions.bun !== "undefined";
3014
+ };
3015
+ var getDefaultSdkDriver = () => {
3016
+ if (defaultDriver) return defaultDriver;
3017
+ if (isBrowser2()) {
3018
+ defaultDriver = localStorageDriver;
3019
+ return defaultDriver;
3020
+ }
3021
+ if (isBun3()) {
3022
+ defaultDriver = createMemoryDriver();
3023
+ return defaultDriver;
3024
+ }
3025
+ if (isNode()) {
3026
+ defaultDriver = createSqliteDriver();
3027
+ return defaultDriver;
3028
+ }
3029
+ defaultDriver = createMemoryDriver();
3030
+ return defaultDriver;
3031
+ };
3032
+ var defaultStore = null;
3033
+ var defaultUsageTrackingDriver = null;
3034
+ var getDefaultSdkStore = () => {
3035
+ if (!defaultStore) {
3036
+ defaultStore = createSdkStore({ driver: getDefaultSdkDriver() });
3037
+ }
3038
+ return defaultStore.hydrate.then(() => defaultStore.store);
3039
+ };
3040
+ var getDefaultUsageTrackingDriver = () => {
3041
+ if (defaultUsageTrackingDriver) return defaultUsageTrackingDriver;
3042
+ const storageDriver = getDefaultSdkDriver();
3043
+ if (isBrowser2()) {
3044
+ defaultUsageTrackingDriver = createIndexedDBUsageTrackingDriver({
3045
+ legacyStorageDriver: storageDriver
3046
+ });
3047
+ return defaultUsageTrackingDriver;
3048
+ }
3049
+ if (isBun3()) {
3050
+ defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
3051
+ return defaultUsageTrackingDriver;
3052
+ }
3053
+ if (isNode()) {
3054
+ defaultUsageTrackingDriver = createSqliteUsageTrackingDriver({
3055
+ legacyStorageDriver: storageDriver
3056
+ });
3057
+ return defaultUsageTrackingDriver;
3058
+ }
3059
+ defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
3060
+ return defaultUsageTrackingDriver;
3061
+ };
3062
+ function createSSEParserTransform(onUsage, onResponseId) {
3063
+ let buffer = "";
3064
+ const maybeCaptureUsageFromJson = (jsonText) => {
3065
+ try {
3066
+ const data = JSON.parse(jsonText);
3067
+ const responseId = data.id;
3068
+ if (typeof responseId === "string" && responseId.trim().length > 0) {
3069
+ onResponseId?.(responseId.trim());
3070
+ }
3071
+ const usage = extractUsageFromSSEJson(data);
3072
+ if (usage) {
3073
+ onUsage(usage);
3074
+ }
3075
+ } catch {
3076
+ }
3077
+ };
3078
+ const processLine = (self, line) => {
3079
+ const trimmed = line.trim();
3080
+ if (!trimmed) {
3081
+ return;
3082
+ }
3083
+ if (trimmed === "data: [DONE]" || trimmed === "[DONE]") {
3084
+ self.push("data: [DONE]\n\n");
3085
+ return;
3086
+ }
3087
+ if (trimmed.startsWith("data:")) {
3088
+ const dataStr = trimmed.startsWith("data: ") ? trimmed.slice(6) : trimmed.slice(5).trimStart();
3089
+ if (dataStr === "[DONE]") {
3090
+ self.push("data: [DONE]\n\n");
3091
+ return;
3092
+ }
3093
+ maybeCaptureUsageFromJson(dataStr);
3094
+ self.push(`data: ${dataStr}
3095
+
3096
+ `);
3097
+ return;
3098
+ }
3099
+ if (trimmed.startsWith("{")) {
3100
+ maybeCaptureUsageFromJson(trimmed);
3101
+ self.push(`data: ${trimmed}
3102
+
3103
+ `);
3104
+ return;
3105
+ }
3106
+ self.push(line + "\n");
3107
+ };
3108
+ return new Transform({
3109
+ transform(chunk, encoding, callback) {
3110
+ buffer += chunk.toString();
3111
+ const lines = buffer.split(/\r?\n/);
3112
+ buffer = lines.pop() || "";
3113
+ for (const line of lines) {
3114
+ processLine(this, line);
3115
+ }
3116
+ callback();
3117
+ },
3118
+ flush(callback) {
3119
+ if (buffer.trim()) {
3120
+ processLine(this, buffer);
3121
+ }
3122
+ buffer = "";
3123
+ callback();
3124
+ }
3125
+ });
3126
+ }
1957
3127
  var TOPUP_MARGIN = 1.2;
1958
3128
  var RoutstrClient = class {
1959
3129
  constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu") {
@@ -1974,7 +3144,13 @@ var RoutstrClient = class {
1974
3144
  this.streamProcessor = new StreamProcessor();
1975
3145
  this.providerManager = new ProviderManager(providerRegistry);
1976
3146
  this.alertLevel = alertLevel;
1977
- this.mode = mode;
3147
+ if (mode === "lazyrefund") {
3148
+ this.mode = "apikeys";
3149
+ } else if (mode === "apikeys") {
3150
+ this.mode = "lazyrefund";
3151
+ } else {
3152
+ this.mode = mode;
3153
+ }
1978
3154
  }
1979
3155
  cashuSpender;
1980
3156
  balanceManager;
@@ -2048,6 +3224,80 @@ var RoutstrClient = class {
2048
3224
  * requests and get responses back.
2049
3225
  */
2050
3226
  async routeRequest(params) {
3227
+ const prepared = await this._prepareRoutedRequest(params);
3228
+ const satsSpent = await this._handlePostResponseBalanceUpdate({
3229
+ token: prepared.tokenUsed,
3230
+ baseUrl: prepared.baseUrlUsed,
3231
+ initialTokenBalance: prepared.tokenBalanceInSats,
3232
+ response: prepared.response,
3233
+ modelId: prepared.modelId,
3234
+ usage: prepared.capturedUsage,
3235
+ requestId: prepared.capturedResponseId
3236
+ });
3237
+ prepared.response.satsSpent = satsSpent;
3238
+ prepared.response.usage = prepared.capturedUsage;
3239
+ prepared.response.requestId = prepared.capturedResponseId;
3240
+ return prepared.response;
3241
+ }
3242
+ async routeRequestToNodeResponse(params) {
3243
+ const { res } = params;
3244
+ const prepared = await this._prepareRoutedRequest(params);
3245
+ res.statusCode = prepared.response.status;
3246
+ prepared.response.headers.forEach((value, key) => {
3247
+ res.setHeader(key, value);
3248
+ });
3249
+ const body = prepared.response.body;
3250
+ if (!body) {
3251
+ const satsSpent = await this._handlePostResponseBalanceUpdate({
3252
+ token: prepared.tokenUsed,
3253
+ baseUrl: prepared.baseUrlUsed,
3254
+ initialTokenBalance: prepared.tokenBalanceInSats,
3255
+ response: prepared.response,
3256
+ modelId: prepared.modelId,
3257
+ usage: prepared.capturedUsage,
3258
+ requestId: prepared.capturedResponseId
3259
+ });
3260
+ prepared.response.satsSpent = satsSpent;
3261
+ res.end();
3262
+ return;
3263
+ }
3264
+ const nodeReadable = Readable.fromWeb(body);
3265
+ await new Promise((resolve, reject) => {
3266
+ let settled = false;
3267
+ const finish = async () => {
3268
+ if (settled) return;
3269
+ settled = true;
3270
+ try {
3271
+ const satsSpent = await this._handlePostResponseBalanceUpdate({
3272
+ token: prepared.tokenUsed,
3273
+ baseUrl: prepared.baseUrlUsed,
3274
+ initialTokenBalance: prepared.tokenBalanceInSats,
3275
+ response: prepared.response,
3276
+ modelId: prepared.modelId,
3277
+ usage: prepared.capturedUsage,
3278
+ requestId: prepared.capturedResponseId
3279
+ });
3280
+ prepared.response.satsSpent = satsSpent;
3281
+ prepared.response.usage = prepared.capturedUsage;
3282
+ prepared.response.requestId = prepared.capturedResponseId;
3283
+ resolve();
3284
+ } catch (error) {
3285
+ reject(error);
3286
+ }
3287
+ };
3288
+ const fail = (error) => {
3289
+ if (settled) return;
3290
+ settled = true;
3291
+ reject(error);
3292
+ };
3293
+ res.once("finish", finish);
3294
+ res.once("close", finish);
3295
+ res.once("error", fail);
3296
+ nodeReadable.once("error", fail);
3297
+ nodeReadable.pipe(res);
3298
+ });
3299
+ }
3300
+ async _prepareRoutedRequest(params) {
2051
3301
  const {
2052
3302
  path,
2053
3303
  method,
@@ -2103,13 +3353,43 @@ var RoutstrClient = class {
2103
3353
  const tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
2104
3354
  const baseUrlUsed = response.baseUrl || baseUrl;
2105
3355
  const tokenUsed = response.token || token;
2106
- await this._handlePostResponseBalanceUpdate({
2107
- token: tokenUsed,
2108
- baseUrl: baseUrlUsed,
2109
- initialTokenBalance: tokenBalanceInSats,
2110
- response
2111
- });
2112
- return response;
3356
+ const contentType = response.headers.get("content-type") || "";
3357
+ let processedResponse = response;
3358
+ let capturedUsage;
3359
+ let capturedResponseId;
3360
+ if (contentType.includes("text/event-stream") && response.body) {
3361
+ const nodeReadable = Readable.fromWeb(response.body);
3362
+ const sseParser = createSSEParserTransform(
3363
+ (usage) => {
3364
+ capturedUsage = usage;
3365
+ processedResponse.usage = usage;
3366
+ },
3367
+ (responseId) => {
3368
+ capturedResponseId = responseId;
3369
+ processedResponse.requestId = responseId;
3370
+ }
3371
+ );
3372
+ const transformed = nodeReadable.pipe(sseParser, { end: true });
3373
+ const webStream = Readable.toWeb(
3374
+ transformed
3375
+ );
3376
+ processedResponse = new Response(webStream, {
3377
+ status: response.status,
3378
+ statusText: response.statusText,
3379
+ headers: response.headers
3380
+ });
3381
+ processedResponse.baseUrl = response.baseUrl;
3382
+ processedResponse.token = response.token;
3383
+ }
3384
+ return {
3385
+ response: processedResponse,
3386
+ tokenUsed,
3387
+ baseUrlUsed,
3388
+ tokenBalanceInSats,
3389
+ modelId,
3390
+ capturedUsage,
3391
+ capturedResponseId
3392
+ };
2113
3393
  }
2114
3394
  /**
2115
3395
  * Fetch AI response with streaming
@@ -2216,7 +3496,18 @@ var RoutstrClient = class {
2216
3496
  baseUrl: baseUrlUsed,
2217
3497
  initialTokenBalance: tokenBalanceInSats,
2218
3498
  fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
2219
- response
3499
+ response,
3500
+ modelId: selectedModel.id,
3501
+ usage: streamingResult.usage ? {
3502
+ promptTokens: Number(streamingResult.usage.prompt_tokens ?? 0),
3503
+ completionTokens: Number(
3504
+ streamingResult.usage.completion_tokens ?? 0
3505
+ ),
3506
+ totalTokens: Number(streamingResult.usage.total_tokens ?? 0),
3507
+ cost: Number(streamingResult.usage.cost ?? 0),
3508
+ satsCost: Number(streamingResult.usage.sats_cost ?? 0)
3509
+ } : void 0,
3510
+ requestId: streamingResult.responseId
2220
3511
  });
2221
3512
  const estimatedCosts = this._getEstimatedCosts(
2222
3513
  selectedModel,
@@ -2633,7 +3924,16 @@ var RoutstrClient = class {
2633
3924
  * Handle post-response balance update for all modes
2634
3925
  */
2635
3926
  async _handlePostResponseBalanceUpdate(params) {
2636
- const { token, baseUrl, initialTokenBalance, fallbackSatsSpent, response } = params;
3927
+ const {
3928
+ token,
3929
+ baseUrl,
3930
+ initialTokenBalance,
3931
+ fallbackSatsSpent,
3932
+ response,
3933
+ modelId,
3934
+ usage,
3935
+ requestId
3936
+ } = params;
2637
3937
  let satsSpent = initialTokenBalance;
2638
3938
  if (this.mode === "xcashu" && response) {
2639
3939
  const refundToken = response.headers.get("x-cashu") ?? void 0;
@@ -2680,8 +3980,75 @@ var RoutstrClient = class {
2680
3980
  satsSpent = fallbackSatsSpent ?? initialTokenBalance;
2681
3981
  }
2682
3982
  }
3983
+ await this._trackResponseUsage({
3984
+ token,
3985
+ baseUrl,
3986
+ response,
3987
+ modelId,
3988
+ satsSpent,
3989
+ usage,
3990
+ requestId
3991
+ });
2683
3992
  return satsSpent;
2684
3993
  }
3994
+ async _trackResponseUsage(params) {
3995
+ const {
3996
+ token,
3997
+ baseUrl,
3998
+ response,
3999
+ modelId,
4000
+ satsSpent,
4001
+ usage: providedUsage,
4002
+ requestId: providedRequestId
4003
+ } = params;
4004
+ if (!response || !modelId) {
4005
+ return;
4006
+ }
4007
+ try {
4008
+ let usage = providedUsage;
4009
+ let requestId = providedRequestId;
4010
+ if (!usage || !requestId) {
4011
+ const contentType = response.headers.get("content-type") || "";
4012
+ if (contentType.includes("text/event-stream")) {
4013
+ usage = usage ?? response.usage;
4014
+ requestId = requestId ?? response.requestId ?? response.headers.get("x-routstr-request-id") ?? void 0;
4015
+ if (!usage) {
4016
+ return;
4017
+ }
4018
+ } else {
4019
+ const cloned = response.clone();
4020
+ const responseBody = await cloned.json();
4021
+ usage = usage ?? extractUsageFromResponseBody(responseBody, satsSpent) ?? void 0;
4022
+ requestId = requestId ?? extractResponseId(responseBody) ?? response.headers.get("x-routstr-request-id") ?? void 0;
4023
+ }
4024
+ }
4025
+ if (!usage) {
4026
+ return;
4027
+ }
4028
+ const finalRequestId = requestId || "unknown";
4029
+ const store = await getDefaultSdkStore();
4030
+ const state = store.getState();
4031
+ const matchingClient = state.clientIds.find(
4032
+ (client) => client.apiKey === token
4033
+ );
4034
+ const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
4035
+ const usageTracking = getDefaultUsageTrackingDriver();
4036
+ const entry = {
4037
+ id: entryId,
4038
+ timestamp: Date.now(),
4039
+ modelId,
4040
+ baseUrl,
4041
+ requestId: finalRequestId,
4042
+ client: matchingClient?.clientId,
4043
+ ...usage
4044
+ };
4045
+ if (this.mode === "xcashu") {
4046
+ entry.satsCost = satsSpent;
4047
+ }
4048
+ await usageTracking.append(entry);
4049
+ } catch (error) {
4050
+ }
4051
+ }
2685
4052
  /**
2686
4053
  * Convert messages for API format
2687
4054
  */
@@ -2726,37 +4093,6 @@ var RoutstrClient = class {
2726
4093
  content: result.content || ""
2727
4094
  };
2728
4095
  }
2729
- /**
2730
- * Create a child key for a parent API key via the provider's API
2731
- * POST /v1/balance/child-key
2732
- */
2733
- async _createChildKey(baseUrl, parentApiKey, options) {
2734
- const response = await fetch(`${baseUrl}v1/balance/child-key`, {
2735
- method: "POST",
2736
- headers: {
2737
- "Content-Type": "application/json",
2738
- Authorization: `Bearer ${parentApiKey}`
2739
- },
2740
- body: JSON.stringify({
2741
- count: options?.count ?? 1,
2742
- balance_limit: options?.balanceLimit,
2743
- balance_limit_reset: options?.balanceLimitReset,
2744
- validity_date: options?.validityDate
2745
- })
2746
- });
2747
- if (!response.ok) {
2748
- throw new Error(
2749
- `Failed to create child key: ${response.status} ${await response.text()}`
2750
- );
2751
- }
2752
- const data = await response.json();
2753
- return {
2754
- childKey: data.api_keys?.[0],
2755
- balance: data.balance ?? 0,
2756
- balanceLimit: data.balance_limit,
2757
- validityDate: data.validity_date
2758
- };
2759
- }
2760
4096
  /**
2761
4097
  * Calculate estimated costs from usage
2762
4098
  */
@@ -2967,6 +4303,6 @@ var RoutstrClient = class {
2967
4303
  }
2968
4304
  };
2969
4305
 
2970
- export { ProviderManager, RoutstrClient, StreamProcessor };
4306
+ export { ProviderManager, RoutstrClient, StreamProcessor, createSSEParserTransform };
2971
4307
  //# sourceMappingURL=index.mjs.map
2972
4308
  //# sourceMappingURL=index.mjs.map