@routstr/sdk 0.2.12 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,31 +6,7 @@ var rxjs = require('rxjs');
6
6
  var cashuTs = require('@cashu/cashu-ts');
7
7
  var vanilla = require('zustand/vanilla');
8
8
  var stream = require('stream');
9
- var fs = require('fs');
10
- var path = require('path');
11
- var os = require('os');
12
-
13
- function _interopNamespace(e) {
14
- if (e && e.__esModule) return e;
15
- var n = Object.create(null);
16
- if (e) {
17
- Object.keys(e).forEach(function (k) {
18
- if (k !== 'default') {
19
- var d = Object.getOwnPropertyDescriptor(e, k);
20
- Object.defineProperty(n, k, d.get ? d : {
21
- enumerable: true,
22
- get: function () { return e[k]; }
23
- });
24
- }
25
- });
26
- }
27
- n.default = e;
28
- return Object.freeze(n);
29
- }
30
-
31
- var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
32
- var path__namespace = /*#__PURE__*/_interopNamespace(path);
33
- var os__namespace = /*#__PURE__*/_interopNamespace(os);
9
+ var string_decoder = require('string_decoder');
34
10
 
35
11
  // core/errors.ts
36
12
  var InsufficientBalanceError = class extends Error {
@@ -411,6 +387,7 @@ var ModelManager = class _ModelManager {
411
387
  if (!(error instanceof Error)) return false;
412
388
  const msg = error.message.toLowerCase();
413
389
  if (msg.includes("fetch failed")) return true;
390
+ if (msg.includes("429")) return true;
414
391
  if (msg.includes("502")) return true;
415
392
  if (msg.includes("503")) return true;
416
393
  if (msg.includes("504")) return true;
@@ -689,6 +666,7 @@ var MintDiscovery = class {
689
666
  if (!(error instanceof Error)) return false;
690
667
  const msg = error.message.toLowerCase();
691
668
  if (msg.includes("fetch failed")) return true;
669
+ if (msg.includes("429")) return true;
692
670
  if (msg.includes("502")) return true;
693
671
  if (msg.includes("503")) return true;
694
672
  if (msg.includes("504")) return true;
@@ -715,10 +693,10 @@ var AuditLogger = class _AuditLogger {
715
693
  const logLine = JSON.stringify(fullEntry) + "\n";
716
694
  if (typeof window === "undefined") {
717
695
  try {
718
- const fs2 = await import('fs');
719
- const path2 = await import('path');
720
- const logPath = path2.join(process.cwd(), "audit.log");
721
- fs2.appendFileSync(logPath, logLine);
696
+ const fs = await import('fs');
697
+ const path = await import('path');
698
+ const logPath = path.join(process.cwd(), "audit.log");
699
+ fs.appendFileSync(logPath, logLine);
722
700
  } catch (error) {
723
701
  console.error("[AuditLogger] Failed to write to file:", error);
724
702
  }
@@ -1297,7 +1275,7 @@ var CashuSpender = class {
1297
1275
  };
1298
1276
 
1299
1277
  // wallet/BalanceManager.ts
1300
- var BalanceManager = class {
1278
+ var BalanceManager = class _BalanceManager {
1301
1279
  constructor(walletAdapter, storageAdapter, providerRegistry, cashuSpender) {
1302
1280
  this.walletAdapter = walletAdapter;
1303
1281
  this.storageAdapter = storageAdapter;
@@ -1314,6 +1292,47 @@ var BalanceManager = class {
1314
1292
  }
1315
1293
  }
1316
1294
  cashuSpender;
1295
+ /** In-memory guard for per-provider wallet mutations (topup / refund) */
1296
+ providerWalletOps = /* @__PURE__ */ new Map();
1297
+ /** Cooldown (ms) between opposite operations on the same provider */
1298
+ static PROVIDER_WALLET_COOLDOWN_MS = 1e4;
1299
+ /**
1300
+ * Check whether a wallet operation (topup/refund) may run for a provider.
1301
+ * Returns the reason when blocked.
1302
+ */
1303
+ _canRunProviderWalletOperation(baseUrl, type) {
1304
+ const existing = this.providerWalletOps.get(baseUrl);
1305
+ if (!existing) {
1306
+ return { allowed: true };
1307
+ }
1308
+ if (existing.type === type) {
1309
+ return { allowed: true };
1310
+ }
1311
+ if (!existing.endTime) {
1312
+ return {
1313
+ allowed: false,
1314
+ reason: `Provider wallet operation locked; ${existing.type} in progress`
1315
+ };
1316
+ }
1317
+ const elapsed = Date.now() - existing.endTime;
1318
+ if (elapsed < _BalanceManager.PROVIDER_WALLET_COOLDOWN_MS) {
1319
+ return {
1320
+ allowed: false,
1321
+ reason: `Provider wallet operation locked; recent ${existing.type} completed ${Math.round(elapsed / 1e3)}s ago`
1322
+ };
1323
+ }
1324
+ this.providerWalletOps.delete(baseUrl);
1325
+ return { allowed: true };
1326
+ }
1327
+ _beginProviderWalletOperation(baseUrl, type) {
1328
+ this.providerWalletOps.set(baseUrl, { type, startTime: Date.now() });
1329
+ }
1330
+ _endProviderWalletOperation(baseUrl, type) {
1331
+ const existing = this.providerWalletOps.get(baseUrl);
1332
+ if (existing && existing.type === type) {
1333
+ existing.endTime = Date.now();
1334
+ }
1335
+ }
1317
1336
  async getBalanceState() {
1318
1337
  const mintBalances = await this.walletAdapter.getBalances();
1319
1338
  const units = this.walletAdapter.getMintUnits();
@@ -1348,6 +1367,20 @@ var BalanceManager = class {
1348
1367
  * @returns Refund result
1349
1368
  */
1350
1369
  async refundApiKey(options) {
1370
+ const { mintUrl, baseUrl, apiKey, forceRefund } = options;
1371
+ const guard = this._canRunProviderWalletOperation(baseUrl, "refund");
1372
+ if (!guard.allowed) {
1373
+ console.log(`[BalanceManager] Skipping refund for ${baseUrl} - ${guard.reason}`);
1374
+ return { success: false, message: guard.reason };
1375
+ }
1376
+ this._beginProviderWalletOperation(baseUrl, "refund");
1377
+ try {
1378
+ return await this._refundApiKeyImpl({ mintUrl, baseUrl, apiKey, forceRefund });
1379
+ } finally {
1380
+ this._endProviderWalletOperation(baseUrl, "refund");
1381
+ }
1382
+ }
1383
+ async _refundApiKeyImpl(options) {
1351
1384
  const { mintUrl, baseUrl, apiKey, forceRefund } = options;
1352
1385
  if (!apiKey) {
1353
1386
  return { success: false, message: "No API key to refund" };
@@ -1476,6 +1509,20 @@ var BalanceManager = class {
1476
1509
  * Top up API key balance with a cashu token
1477
1510
  */
1478
1511
  async topUp(options) {
1512
+ const { mintUrl, baseUrl, amount, token: providedToken } = options;
1513
+ const guard = this._canRunProviderWalletOperation(baseUrl, "topup");
1514
+ if (!guard.allowed) {
1515
+ console.log(`[BalanceManager] Skipping topup for ${baseUrl} - ${guard.reason}`);
1516
+ return { success: false, message: guard.reason };
1517
+ }
1518
+ this._beginProviderWalletOperation(baseUrl, "topup");
1519
+ try {
1520
+ return await this._topUpImpl({ mintUrl, baseUrl, amount, token: providedToken });
1521
+ } finally {
1522
+ this._endProviderWalletOperation(baseUrl, "topup");
1523
+ }
1524
+ }
1525
+ async _topUpImpl(options) {
1479
1526
  const { mintUrl, baseUrl, amount, token: providedToken } = options;
1480
1527
  if (!amount || amount <= 0) {
1481
1528
  return { success: false, message: "Invalid top up amount" };
@@ -1636,7 +1683,7 @@ var BalanceManager = class {
1636
1683
  p2pkPubkey
1637
1684
  );
1638
1685
  console.log(
1639
- `[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`
1686
+ `[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}, all mint balances: ${JSON.stringify(Object.fromEntries(Object.entries(balances).map(([mint, balance]) => [mint, getBalanceInSats(balance, units[mint])])))}`
1640
1687
  );
1641
1688
  return {
1642
1689
  success: true,
@@ -2831,7 +2878,9 @@ var ProviderManager = class _ProviderManager {
2831
2878
  const approximateTokens = apiMessagesNoImages ? Math.ceil(JSON.stringify(apiMessagesNoImages, null, 2).length / 2.84) : 1e4;
2832
2879
  const totalInputTokens = approximateTokens + imageTokens;
2833
2880
  const sp = model?.sats_pricing;
2834
- if (!sp) return 0;
2881
+ if (!sp) {
2882
+ return 0;
2883
+ }
2835
2884
  if (!sp.max_completion_cost) {
2836
2885
  return sp.max_cost ?? 50;
2837
2886
  }
@@ -4540,26 +4589,117 @@ var setDefaultUsageTrackingDriver = (driver) => {
4540
4589
  var getDefaultDiscoveryAdapter = async () => createDiscoveryAdapterFromStore(await getDefaultSdkStore());
4541
4590
  var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
4542
4591
  var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
4543
- function createSSEParserTransform(onUsage, onResponseId) {
4592
+ function mergeUsage(previous, next) {
4593
+ if (!previous) return next;
4594
+ return {
4595
+ promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
4596
+ completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
4597
+ totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
4598
+ cost: next.cost > 0 ? next.cost : previous.cost,
4599
+ satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
4600
+ };
4601
+ }
4602
+ function hasUsageChanged(previous, next) {
4603
+ if (!previous) return true;
4604
+ return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
4605
+ }
4606
+ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
4607
+ const reader = stream.getReader();
4608
+ const decoder = new TextDecoder("utf-8");
4544
4609
  let buffer = "";
4545
4610
  let capturedUsage = null;
4611
+ let capturedResponseId;
4546
4612
  let responseIdCaptured = false;
4547
- const mergeUsage = (previous, next) => {
4548
- if (!previous) return next;
4549
- return {
4550
- promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
4551
- completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
4552
- totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
4553
- cost: next.cost > 0 ? next.cost : previous.cost,
4554
- satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
4555
- };
4613
+ const inspectDataPayload = (jsonText) => {
4614
+ if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
4615
+ return;
4616
+ }
4617
+ const trimmed = jsonText.trim();
4618
+ if (!trimmed || trimmed === "[DONE]") return;
4619
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
4620
+ try {
4621
+ const data = JSON.parse(trimmed);
4622
+ if (!responseIdCaptured) {
4623
+ const responseId = data?.id;
4624
+ if (typeof responseId === "string" && responseId.trim().length > 0) {
4625
+ capturedResponseId = responseId.trim();
4626
+ onResponseId?.(capturedResponseId);
4627
+ responseIdCaptured = true;
4628
+ }
4629
+ }
4630
+ const usage = extractUsageFromSSEJson(data);
4631
+ if (usage) {
4632
+ const merged = mergeUsage(capturedUsage, usage);
4633
+ if (hasUsageChanged(capturedUsage, merged)) {
4634
+ capturedUsage = merged;
4635
+ onUsage(merged);
4636
+ }
4637
+ }
4638
+ } catch {
4639
+ }
4640
+ };
4641
+ const inspectEventBlock = (eventBlock) => {
4642
+ if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
4643
+ return;
4644
+ }
4645
+ const lines = eventBlock.split(/\r?\n/);
4646
+ const dataParts = [];
4647
+ for (const line of lines) {
4648
+ if (!line || line.startsWith(":")) continue;
4649
+ if (line.startsWith("data:")) {
4650
+ const value = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
4651
+ dataParts.push(value);
4652
+ }
4653
+ }
4654
+ if (dataParts.length === 0) return;
4655
+ inspectDataPayload(dataParts.join("\n"));
4556
4656
  };
4557
- const hasUsageChanged = (previous, next) => {
4558
- if (!previous) return true;
4559
- return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
4657
+ const drainBufferedEvents = () => {
4658
+ const terminator = /\r?\n\r?\n/g;
4659
+ let lastIndex = 0;
4660
+ let match;
4661
+ while ((match = terminator.exec(buffer)) !== null) {
4662
+ const block = buffer.slice(lastIndex, match.index);
4663
+ lastIndex = match.index + match[0].length;
4664
+ if (block.length > 0) inspectEventBlock(block);
4665
+ }
4666
+ if (lastIndex > 0) buffer = buffer.slice(lastIndex);
4560
4667
  };
4668
+ try {
4669
+ while (true) {
4670
+ const { value, done } = await reader.read();
4671
+ if (done) break;
4672
+ if (value && value.byteLength > 0) {
4673
+ buffer += decoder.decode(value, { stream: true });
4674
+ drainBufferedEvents();
4675
+ }
4676
+ }
4677
+ buffer += decoder.decode();
4678
+ drainBufferedEvents();
4679
+ if (buffer.length > 0) {
4680
+ const tail = buffer.replace(/\r?\n+$/, "");
4681
+ if (tail.length > 0) inspectEventBlock(tail);
4682
+ buffer = "";
4683
+ }
4684
+ } catch {
4685
+ } finally {
4686
+ try {
4687
+ reader.releaseLock();
4688
+ } catch {
4689
+ }
4690
+ }
4691
+ return {
4692
+ capturedUsage: capturedUsage ?? void 0,
4693
+ capturedResponseId
4694
+ };
4695
+ }
4696
+ function createSSEParserTransform(onUsage, onResponseId) {
4697
+ let buffer = "";
4698
+ const decoder = new string_decoder.StringDecoder("utf8");
4699
+ let capturedUsage = null;
4700
+ let responseIdCaptured = false;
4561
4701
  const inspectDataPayload = (jsonText) => {
4562
- if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
4702
+ if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
4563
4703
  return;
4564
4704
  }
4565
4705
  const trimmed = jsonText.trim();
@@ -4586,7 +4726,7 @@ function createSSEParserTransform(onUsage, onResponseId) {
4586
4726
  }
4587
4727
  };
4588
4728
  const inspectEventBlock = (eventBlock) => {
4589
- if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
4729
+ if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
4590
4730
  return;
4591
4731
  }
4592
4732
  const lines = eventBlock.split(/\r?\n/);
@@ -4602,32 +4742,35 @@ function createSSEParserTransform(onUsage, onResponseId) {
4602
4742
  const payload = dataParts.join("\n");
4603
4743
  inspectDataPayload(payload);
4604
4744
  };
4605
- const emitEventBlock = (self, eventBlock) => {
4606
- if (eventBlock.length === 0) return;
4607
- inspectEventBlock(eventBlock);
4608
- self.push(eventBlock + "\n\n");
4745
+ const processBufferedEvents = () => {
4746
+ const terminator = /\r?\n\r?\n/g;
4747
+ let lastIndex = 0;
4748
+ let match;
4749
+ while ((match = terminator.exec(buffer)) !== null) {
4750
+ const block = buffer.slice(lastIndex, match.index);
4751
+ lastIndex = match.index + match[0].length;
4752
+ if (block.length > 0) {
4753
+ inspectEventBlock(block);
4754
+ }
4755
+ }
4756
+ if (lastIndex > 0) {
4757
+ buffer = buffer.slice(lastIndex);
4758
+ }
4609
4759
  };
4610
4760
  return new stream.Transform({
4611
4761
  transform(chunk, _encoding, callback) {
4612
- buffer += chunk.toString();
4613
- const terminator = /\r?\n\r?\n/g;
4614
- let lastIndex = 0;
4615
- let match;
4616
- while ((match = terminator.exec(buffer)) !== null) {
4617
- const block = buffer.slice(lastIndex, match.index);
4618
- lastIndex = match.index + match[0].length;
4619
- emitEventBlock(this, block);
4620
- }
4621
- if (lastIndex > 0) {
4622
- buffer = buffer.slice(lastIndex);
4623
- }
4762
+ this.push(chunk);
4763
+ buffer += decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
4764
+ processBufferedEvents();
4624
4765
  callback();
4625
4766
  },
4626
4767
  flush(callback) {
4768
+ buffer += decoder.end();
4769
+ processBufferedEvents();
4627
4770
  if (buffer.length > 0) {
4628
4771
  const tail = buffer.replace(/\r?\n+$/, "");
4629
4772
  if (tail.length > 0) {
4630
- emitEventBlock(this, tail);
4773
+ inspectEventBlock(tail);
4631
4774
  }
4632
4775
  buffer = "";
4633
4776
  }
@@ -4635,6 +4778,8 @@ function createSSEParserTransform(onUsage, onResponseId) {
4635
4778
  }
4636
4779
  });
4637
4780
  }
4781
+
4782
+ // client/RoutstrClient.ts
4638
4783
  var TOPUP_MARGIN = 1.2;
4639
4784
  var RoutstrClient = class {
4640
4785
  constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
@@ -4734,31 +4879,12 @@ var RoutstrClient = class {
4734
4879
  */
4735
4880
  async routeRequest(params) {
4736
4881
  const prepared = await this._prepareRoutedRequest(params);
4737
- const satsSpent = await this._handlePostResponseBalanceUpdate({
4738
- token: prepared.tokenUsed,
4739
- baseUrl: prepared.baseUrlUsed,
4740
- mintUrl: params.mintUrl,
4741
- initialTokenBalance: prepared.tokenBalanceInSats,
4742
- response: prepared.response,
4743
- modelId: prepared.modelId,
4744
- usage: prepared.capturedUsage,
4745
- requestId: prepared.capturedResponseId,
4746
- clientApiKey: prepared.clientApiKey
4747
- });
4748
- prepared.response.satsSpent = satsSpent;
4749
- prepared.response.usage = prepared.capturedUsage;
4750
- prepared.response.requestId = prepared.capturedResponseId;
4751
- return prepared.response;
4752
- }
4753
- async routeRequestToNodeResponse(params) {
4754
- const { res } = params;
4755
- const prepared = await this._prepareRoutedRequest(params);
4756
- res.statusCode = prepared.response.status;
4757
- prepared.response.headers.forEach((value, key) => {
4758
- res.setHeader(key, value);
4759
- });
4760
- const body = prepared.response.body;
4761
- if (!body) {
4882
+ const contentType = prepared.response.headers.get("content-type") || "";
4883
+ const isSSE = contentType.includes("text/event-stream");
4884
+ const runFinalize = async () => {
4885
+ const { capturedUsage, capturedResponseId } = await prepared.usagePromise;
4886
+ const usage = capturedUsage ?? prepared.capturedUsage;
4887
+ const requestId = capturedResponseId ?? prepared.capturedResponseId;
4762
4888
  const satsSpent = await this._handlePostResponseBalanceUpdate({
4763
4889
  token: prepared.tokenUsed,
4764
4890
  baseUrl: prepared.baseUrlUsed,
@@ -4766,51 +4892,25 @@ var RoutstrClient = class {
4766
4892
  initialTokenBalance: prepared.tokenBalanceInSats,
4767
4893
  response: prepared.response,
4768
4894
  modelId: prepared.modelId,
4769
- usage: prepared.capturedUsage,
4770
- requestId: prepared.capturedResponseId,
4895
+ usage,
4896
+ requestId,
4771
4897
  clientApiKey: prepared.clientApiKey
4772
4898
  });
4773
4899
  prepared.response.satsSpent = satsSpent;
4774
- res.end();
4775
- return;
4900
+ prepared.response.usage = usage;
4901
+ prepared.response.requestId = requestId;
4902
+ return satsSpent;
4903
+ };
4904
+ if (isSSE) {
4905
+ const finalizePromise = runFinalize().catch((error) => {
4906
+ this._log("ERROR", "[RoutstrClient] SSE finalize failed:", error);
4907
+ return 0;
4908
+ });
4909
+ prepared.response.finalize = () => finalizePromise;
4910
+ return prepared.response;
4776
4911
  }
4777
- const nodeReadable = stream.Readable.fromWeb(body);
4778
- await new Promise((resolve, reject) => {
4779
- let settled = false;
4780
- const finish = async () => {
4781
- if (settled) return;
4782
- settled = true;
4783
- try {
4784
- const satsSpent = await this._handlePostResponseBalanceUpdate({
4785
- token: prepared.tokenUsed,
4786
- baseUrl: prepared.baseUrlUsed,
4787
- mintUrl: params.mintUrl,
4788
- initialTokenBalance: prepared.tokenBalanceInSats,
4789
- response: prepared.response,
4790
- modelId: prepared.modelId,
4791
- usage: prepared.capturedUsage,
4792
- requestId: prepared.capturedResponseId,
4793
- clientApiKey: prepared.clientApiKey
4794
- });
4795
- prepared.response.satsSpent = satsSpent;
4796
- prepared.response.usage = prepared.capturedUsage;
4797
- prepared.response.requestId = prepared.capturedResponseId;
4798
- resolve();
4799
- } catch (error) {
4800
- reject(error);
4801
- }
4802
- };
4803
- const fail = (error) => {
4804
- if (settled) return;
4805
- settled = true;
4806
- reject(error);
4807
- };
4808
- res.once("finish", finish);
4809
- res.once("close", finish);
4810
- res.once("error", fail);
4811
- nodeReadable.once("error", fail);
4812
- nodeReadable.pipe(res);
4813
- });
4912
+ await runFinalize();
4913
+ return prepared.response;
4814
4914
  }
4815
4915
  async _prepareRoutedRequest(params) {
4816
4916
  const {
@@ -4834,9 +4934,23 @@ var RoutstrClient = class {
4834
4934
  );
4835
4935
  selectedModel = providerModel ?? void 0;
4836
4936
  if (selectedModel) {
4937
+ const requestMessages = Array.isArray(
4938
+ body?.messages
4939
+ ) ? body.messages : [];
4940
+ const requestMaxTokens = typeof body?.max_tokens === "number" ? body.max_tokens : void 0;
4941
+ this._log(
4942
+ "DEBUG",
4943
+ "[RoutstrClient] generic request pricing input",
4944
+ {
4945
+ modelId: selectedModel.id,
4946
+ messageCount: requestMessages.length,
4947
+ maxTokens: requestMaxTokens
4948
+ }
4949
+ );
4837
4950
  requiredSats = this.providerManager.getRequiredSatsForModel(
4838
4951
  selectedModel,
4839
- []
4952
+ requestMessages,
4953
+ requestMaxTokens
4840
4954
  );
4841
4955
  }
4842
4956
  }
@@ -4873,22 +4987,18 @@ var RoutstrClient = class {
4873
4987
  let processedResponse = response;
4874
4988
  let capturedUsage;
4875
4989
  let capturedResponseId;
4990
+ let usagePromise = Promise.resolve({});
4876
4991
  if (contentType.includes("text/event-stream") && response.body) {
4877
- const logDir = path__namespace.join(os__namespace.homedir(), ".routstrd", "stream-response");
4878
- if (!fs__namespace.existsSync(logDir)) {
4879
- fs__namespace.mkdirSync(logDir, { recursive: true });
4880
- }
4881
- const logFile = path__namespace.join(logDir, `${Date.now()}.jsonl`);
4882
- const logStream = fs__namespace.createWriteStream(logFile);
4883
- const nodeReadable = stream.Readable.fromWeb(response.body);
4884
- const loggingTransform = new stream.Transform({
4885
- transform(chunk, encoding, callback) {
4886
- const raw = chunk.toString();
4887
- logStream.write(JSON.stringify({ raw, timestamp: Date.now() }) + "\n");
4888
- callback(null, chunk);
4889
- }
4992
+ const [clientStream, inspectStream] = response.body.tee();
4993
+ processedResponse = new Response(clientStream, {
4994
+ status: response.status,
4995
+ statusText: response.statusText,
4996
+ headers: response.headers
4890
4997
  });
4891
- const sseParser = createSSEParserTransform(
4998
+ processedResponse.baseUrl = response.baseUrl;
4999
+ processedResponse.token = response.token;
5000
+ usagePromise = inspectSSEWebStream(
5001
+ inspectStream,
4892
5002
  (usage) => {
4893
5003
  capturedUsage = usage;
4894
5004
  processedResponse.usage = usage;
@@ -4898,17 +5008,7 @@ var RoutstrClient = class {
4898
5008
  processedResponse.requestId = responseId;
4899
5009
  }
4900
5010
  );
4901
- const transformed = nodeReadable.pipe(loggingTransform).pipe(sseParser, { end: true });
4902
- const webStream = stream.Readable.toWeb(
4903
- transformed
4904
- );
4905
- processedResponse = new Response(webStream, {
4906
- status: response.status,
4907
- statusText: response.statusText,
4908
- headers: response.headers
4909
- });
4910
- processedResponse.baseUrl = response.baseUrl;
4911
- processedResponse.token = response.token;
5011
+ processedResponse.usagePromise = usagePromise;
4912
5012
  }
4913
5013
  return {
4914
5014
  response: processedResponse,
@@ -4918,7 +5018,8 @@ var RoutstrClient = class {
4918
5018
  modelId,
4919
5019
  capturedUsage,
4920
5020
  capturedResponseId,
4921
- clientApiKey
5021
+ clientApiKey,
5022
+ usagePromise
4922
5023
  };
4923
5024
  }
4924
5025
  /**
@@ -5069,9 +5170,9 @@ var RoutstrClient = class {
5069
5170
  * Make the API request with failover support
5070
5171
  */
5071
5172
  async _makeRequest(params) {
5072
- const { path: path2, method, body, baseUrl, token, headers } = params;
5173
+ const { path, method, body, baseUrl, token, headers } = params;
5073
5174
  try {
5074
- const url = `${baseUrl.replace(/\/$/, "")}${path2}`;
5175
+ const url = `${baseUrl.replace(/\/$/, "")}${path}`;
5075
5176
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
5076
5177
  const response = await fetch(url, {
5077
5178
  method,
@@ -5121,7 +5222,7 @@ var RoutstrClient = class {
5121
5222
  */
5122
5223
  async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
5123
5224
  const MAX_RETRIES_PER_PROVIDER = 2;
5124
- const { path: path2, method, body, selectedModel, baseUrl, mintUrl } = params;
5225
+ const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
5125
5226
  let tryNextProvider = false;
5126
5227
  const errorMessage = responseBody;
5127
5228
  this._log(
@@ -5193,11 +5294,12 @@ var RoutstrClient = class {
5193
5294
  baseUrl
5194
5295
  );
5195
5296
  const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
5196
- const shortfall = Math.max(0, params.requiredSats - currentBalance);
5197
- topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
5297
+ const reservedBalance = currentBalanceInfo.unit === "msat" ? (currentBalanceInfo.reserved ?? 0) / 1e3 : currentBalanceInfo.reserved ?? 0;
5298
+ const shortfall = Math.max(0, params.requiredSats - currentBalance + reservedBalance);
5299
+ topupAmount = shortfall > 0.21 * params.requiredSats ? shortfall : 0.21 * params.requiredSats;
5198
5300
  this._log(
5199
5301
  "DEBUG",
5200
- `The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
5302
+ `The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance}. Reserved Balance: ${reservedBalance}. Available Balance: ${currentBalance - reservedBalance}`
5201
5303
  );
5202
5304
  } catch (e) {
5203
5305
  this._log(
@@ -5342,10 +5444,10 @@ var RoutstrClient = class {
5342
5444
  tryNextProvider = true;
5343
5445
  }
5344
5446
  }
5345
- if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
5447
+ if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 429 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
5346
5448
  this._log(
5347
5449
  "DEBUG",
5348
- `[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
5450
+ `[RoutstrClient] _handleErrorResponse: Status ${status} (${status === 429 ? "rate limited" : "auth/server error"}), attempting refund for ${baseUrl}, mode=${this.mode}`
5349
5451
  );
5350
5452
  if (this.mode === "apikeys") {
5351
5453
  this._log(
@@ -5423,7 +5525,7 @@ var RoutstrClient = class {
5423
5525
  });
5424
5526
  return this._makeRequest({
5425
5527
  ...params,
5426
- path: path2,
5528
+ path,
5427
5529
  method,
5428
5530
  body,
5429
5531
  baseUrl: nextProvider,
@@ -5831,7 +5933,7 @@ async function resolveRouteRequestContext(options) {
5831
5933
  const {
5832
5934
  modelId,
5833
5935
  requestBody,
5834
- path: path2 = "/v1/chat/completions",
5936
+ path = "/v1/chat/completions",
5835
5937
  headers = {},
5836
5938
  forcedProvider,
5837
5939
  walletAdapter,
@@ -5930,17 +6032,17 @@ async function resolveRouteRequestContext(options) {
5930
6032
  client,
5931
6033
  baseUrl,
5932
6034
  mintUrl,
5933
- path: path2,
6035
+ path,
5934
6036
  headers,
5935
6037
  modelId,
5936
6038
  proxiedBody
5937
6039
  };
5938
6040
  }
5939
6041
  async function routeRequests(options) {
5940
- const { client, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
6042
+ const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5941
6043
  try {
5942
6044
  const response = await client.routeRequest({
5943
- path: path2,
6045
+ path,
5944
6046
  method: "POST",
5945
6047
  body: proxiedBody,
5946
6048
  headers,
@@ -5959,27 +6061,6 @@ async function routeRequests(options) {
5959
6061
  throw error;
5960
6062
  }
5961
6063
  }
5962
- async function routeRequestsToNodeResponse(options) {
5963
- const { res } = options;
5964
- const { client, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5965
- try {
5966
- await client.routeRequestToNodeResponse({
5967
- path: path2,
5968
- method: "POST",
5969
- body: proxiedBody,
5970
- headers,
5971
- baseUrl,
5972
- mintUrl,
5973
- modelId,
5974
- res
5975
- });
5976
- } catch (error) {
5977
- if (error instanceof Error && (error.message.includes("401") || error.message.includes("402") || error.message.includes("403"))) {
5978
- throw new Error(`Authentication failed: ${error.message}`);
5979
- }
5980
- throw error;
5981
- }
5982
- }
5983
6064
  function extractMaxTokens(requestBody) {
5984
6065
  if (!requestBody || typeof requestBody !== "object") {
5985
6066
  return void 0;
@@ -6036,12 +6117,12 @@ exports.getDefaultSdkStore = getDefaultSdkStore;
6036
6117
  exports.getDefaultStorageAdapter = getDefaultStorageAdapter;
6037
6118
  exports.getDefaultUsageTrackingDriver = getDefaultUsageTrackingDriver;
6038
6119
  exports.getProviderEndpoints = getProviderEndpoints;
6120
+ exports.inspectSSEWebStream = inspectSSEWebStream;
6039
6121
  exports.isOnionUrl = isOnionUrl;
6040
6122
  exports.isTorContext = isTorContext;
6041
6123
  exports.localStorageDriver = localStorageDriver;
6042
6124
  exports.normalizeProviderUrl = normalizeProviderUrl;
6043
6125
  exports.routeRequests = routeRequests;
6044
- exports.routeRequestsToNodeResponse = routeRequestsToNodeResponse;
6045
6126
  exports.setDefaultUsageTrackingDriver = setDefaultUsageTrackingDriver;
6046
6127
  //# sourceMappingURL=index.js.map
6047
6128
  //# sourceMappingURL=index.js.map