@routstr/sdk 0.2.11 → 0.2.12

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,6 +6,31 @@ 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
34
 
10
35
  // core/errors.ts
11
36
  var InsufficientBalanceError = class extends Error {
@@ -690,10 +715,10 @@ var AuditLogger = class _AuditLogger {
690
715
  const logLine = JSON.stringify(fullEntry) + "\n";
691
716
  if (typeof window === "undefined") {
692
717
  try {
693
- const fs = await import('fs');
694
- const path = await import('path');
695
- const logPath = path.join(process.cwd(), "audit.log");
696
- fs.appendFileSync(logPath, logLine);
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);
697
722
  } catch (error) {
698
723
  console.error("[AuditLogger] Failed to write to file:", error);
699
724
  }
@@ -2417,6 +2442,7 @@ var ProviderManager = class _ProviderManager {
2417
2442
  }
2418
2443
  /**
2419
2444
  * Clean up expired cooldown entries
2445
+ * Also removes the provider from failedProviders so it can be retried
2420
2446
  */
2421
2447
  cleanupExpiredCooldowns() {
2422
2448
  const now = Date.now();
@@ -2429,6 +2455,10 @@ var ProviderManager = class _ProviderManager {
2429
2455
  console.log(
2430
2456
  `[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
2431
2457
  );
2458
+ this.failedProviders.delete(url);
2459
+ if (this.store) {
2460
+ this.store.getState().removeFailedProvider(url);
2461
+ }
2432
2462
  }
2433
2463
  return !isExpired;
2434
2464
  }
@@ -2602,60 +2632,47 @@ var ProviderManager = class _ProviderManager {
2602
2632
  const disabledProviders = new Set(
2603
2633
  this.providerRegistry.getDisabledProviders()
2604
2634
  );
2605
- console.log(`[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`);
2606
- console.log(`[findNextBestProvider:${this.instanceId}] currentBaseUrl: ${currentBaseUrl}`);
2607
- console.log(`[findNextBestProvider:${this.instanceId}] torMode: ${torMode}`);
2608
- console.log(`[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`);
2609
- console.log(`[findNextBestProvider:${this.instanceId}] failedProviders: ${[...this.failedProviders]}`);
2610
- console.log(`[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`);
2635
+ console.log(
2636
+ `[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`
2637
+ );
2638
+ console.log(
2639
+ `[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`
2640
+ );
2641
+ console.log(
2642
+ `[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`
2643
+ );
2611
2644
  const allProviders = this.providerRegistry.getAllProvidersModels();
2612
- console.log(`[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`);
2645
+ console.log(
2646
+ `[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`
2647
+ );
2613
2648
  const candidates = [];
2614
- let skippedCurrent = 0, skippedFailed = 0, skippedDisabled = 0, skippedCooldown = 0, skippedOnion = 0, skippedNoModel = 0;
2615
2649
  for (const [baseUrl, models] of Object.entries(allProviders)) {
2616
2650
  if (baseUrl === currentBaseUrl) {
2617
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`);
2618
- skippedCurrent++;
2619
- continue;
2620
- }
2621
- if (this.failedProviders.has(baseUrl)) {
2622
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (failed): ${baseUrl}`);
2623
- skippedFailed++;
2651
+ console.log(
2652
+ `[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`
2653
+ );
2624
2654
  continue;
2625
2655
  }
2626
2656
  if (disabledProviders.has(baseUrl)) {
2627
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (disabled): ${baseUrl}`);
2628
- skippedDisabled++;
2629
2657
  continue;
2630
2658
  }
2631
2659
  if (this.isOnCooldown(baseUrl)) {
2632
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (cooldown): ${baseUrl}`);
2633
- skippedCooldown++;
2634
2660
  continue;
2635
2661
  }
2636
2662
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
2637
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (onion/http): ${baseUrl}`);
2638
- skippedOnion++;
2639
2663
  continue;
2640
2664
  }
2641
2665
  const model = models.find((m) => m.id === modelId);
2642
2666
  if (!model) {
2643
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (no model ${modelId}): ${baseUrl} has models: ${models.map((m) => m.id).join(", ")}`);
2644
- skippedNoModel++;
2645
2667
  continue;
2646
2668
  }
2647
2669
  const cost = model.sats_pricing?.completion ?? 0;
2648
- console.log(`[findNextBestProvider:${this.instanceId}] CANDIDATE: ${baseUrl} cost: ${cost}`);
2649
2670
  candidates.push({ baseUrl, model, cost });
2650
2671
  }
2651
- console.log(`[findNextBestProvider:${this.instanceId}] Skipped: current=${skippedCurrent}, failed=${skippedFailed}, disabled=${skippedDisabled}, cooldown=${skippedCooldown}, onion=${skippedOnion}, noModel=${skippedNoModel}`);
2652
- console.log(`[findNextBestProvider:${this.instanceId}] Total candidates: ${candidates.length}`);
2653
2672
  candidates.sort((a, b) => a.cost - b.cost);
2654
2673
  if (candidates.length > 0) {
2655
- console.log(`[findNextBestProvider:${this.instanceId}] Selected provider: ${candidates[0].baseUrl} with cost: ${candidates[0].cost}`);
2656
2674
  return candidates[0].baseUrl;
2657
2675
  } else {
2658
- console.log(`[findNextBestProvider:${this.instanceId}] No candidate providers found`);
2659
2676
  return null;
2660
2677
  }
2661
2678
  } catch (error) {
@@ -4525,10 +4542,26 @@ var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await g
4525
4542
  var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
4526
4543
  function createSSEParserTransform(onUsage, onResponseId) {
4527
4544
  let buffer = "";
4528
- let usageCaptured = false;
4545
+ let capturedUsage = null;
4529
4546
  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
+ };
4556
+ };
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;
4560
+ };
4530
4561
  const inspectDataPayload = (jsonText) => {
4531
- if (usageCaptured && responseIdCaptured) return;
4562
+ if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
4563
+ return;
4564
+ }
4532
4565
  const trimmed = jsonText.trim();
4533
4566
  if (!trimmed || trimmed === "[DONE]") return;
4534
4567
  if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
@@ -4541,18 +4574,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
4541
4574
  responseIdCaptured = true;
4542
4575
  }
4543
4576
  }
4544
- if (!usageCaptured) {
4545
- const usage = extractUsageFromSSEJson(data);
4546
- if (usage) {
4547
- onUsage(usage);
4548
- usageCaptured = true;
4577
+ const usage = extractUsageFromSSEJson(data);
4578
+ if (usage) {
4579
+ const mergedUsage = mergeUsage(capturedUsage, usage);
4580
+ if (hasUsageChanged(capturedUsage, mergedUsage)) {
4581
+ capturedUsage = mergedUsage;
4582
+ onUsage(mergedUsage);
4549
4583
  }
4550
4584
  }
4551
4585
  } catch {
4552
4586
  }
4553
4587
  };
4554
4588
  const inspectEventBlock = (eventBlock) => {
4555
- if (usageCaptured && responseIdCaptured) return;
4589
+ if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
4590
+ return;
4591
+ }
4556
4592
  const lines = eventBlock.split(/\r?\n/);
4557
4593
  const dataParts = [];
4558
4594
  for (const line of lines) {
@@ -4778,7 +4814,7 @@ var RoutstrClient = class {
4778
4814
  }
4779
4815
  async _prepareRoutedRequest(params) {
4780
4816
  const {
4781
- path,
4817
+ path: requestPath,
4782
4818
  method,
4783
4819
  body,
4784
4820
  headers = {},
@@ -4819,7 +4855,7 @@ var RoutstrClient = class {
4819
4855
  const baseHeaders = this._buildBaseHeaders();
4820
4856
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
4821
4857
  const response = await this._makeRequest({
4822
- path,
4858
+ path: requestPath,
4823
4859
  method,
4824
4860
  body: method === "GET" ? void 0 : requestBody,
4825
4861
  baseUrl,
@@ -4838,7 +4874,20 @@ var RoutstrClient = class {
4838
4874
  let capturedUsage;
4839
4875
  let capturedResponseId;
4840
4876
  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);
4841
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
+ }
4890
+ });
4842
4891
  const sseParser = createSSEParserTransform(
4843
4892
  (usage) => {
4844
4893
  capturedUsage = usage;
@@ -4849,7 +4898,7 @@ var RoutstrClient = class {
4849
4898
  processedResponse.requestId = responseId;
4850
4899
  }
4851
4900
  );
4852
- const transformed = nodeReadable.pipe(sseParser, { end: true });
4901
+ const transformed = nodeReadable.pipe(loggingTransform).pipe(sseParser, { end: true });
4853
4902
  const webStream = stream.Readable.toWeb(
4854
4903
  transformed
4855
4904
  );
@@ -4918,7 +4967,6 @@ var RoutstrClient = class {
4918
4967
  callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
4919
4968
  const baseHeaders = this._buildBaseHeaders(headers);
4920
4969
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
4921
- this.providerManager.resetFailedProviders();
4922
4970
  const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
4923
4971
  const providerVersion = providerInfo?.version ?? "";
4924
4972
  let modelIdForRequest = selectedModel.id;
@@ -5021,9 +5069,9 @@ var RoutstrClient = class {
5021
5069
  * Make the API request with failover support
5022
5070
  */
5023
5071
  async _makeRequest(params) {
5024
- const { path, method, body, baseUrl, token, headers } = params;
5072
+ const { path: path2, method, body, baseUrl, token, headers } = params;
5025
5073
  try {
5026
- const url = `${baseUrl.replace(/\/$/, "")}${path}`;
5074
+ const url = `${baseUrl.replace(/\/$/, "")}${path2}`;
5027
5075
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
5028
5076
  const response = await fetch(url, {
5029
5077
  method,
@@ -5073,7 +5121,7 @@ var RoutstrClient = class {
5073
5121
  */
5074
5122
  async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
5075
5123
  const MAX_RETRIES_PER_PROVIDER = 2;
5076
- const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
5124
+ const { path: path2, method, body, selectedModel, baseUrl, mintUrl } = params;
5077
5125
  let tryNextProvider = false;
5078
5126
  const errorMessage = responseBody;
5079
5127
  this._log(
@@ -5375,7 +5423,7 @@ var RoutstrClient = class {
5375
5423
  });
5376
5424
  return this._makeRequest({
5377
5425
  ...params,
5378
- path,
5426
+ path: path2,
5379
5427
  method,
5380
5428
  body,
5381
5429
  baseUrl: nextProvider,
@@ -5783,7 +5831,7 @@ async function resolveRouteRequestContext(options) {
5783
5831
  const {
5784
5832
  modelId,
5785
5833
  requestBody,
5786
- path = "/v1/chat/completions",
5834
+ path: path2 = "/v1/chat/completions",
5787
5835
  headers = {},
5788
5836
  forcedProvider,
5789
5837
  walletAdapter,
@@ -5882,17 +5930,17 @@ async function resolveRouteRequestContext(options) {
5882
5930
  client,
5883
5931
  baseUrl,
5884
5932
  mintUrl,
5885
- path,
5933
+ path: path2,
5886
5934
  headers,
5887
5935
  modelId,
5888
5936
  proxiedBody
5889
5937
  };
5890
5938
  }
5891
5939
  async function routeRequests(options) {
5892
- const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5940
+ const { client, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5893
5941
  try {
5894
5942
  const response = await client.routeRequest({
5895
- path,
5943
+ path: path2,
5896
5944
  method: "POST",
5897
5945
  body: proxiedBody,
5898
5946
  headers,
@@ -5913,10 +5961,10 @@ async function routeRequests(options) {
5913
5961
  }
5914
5962
  async function routeRequestsToNodeResponse(options) {
5915
5963
  const { res } = options;
5916
- const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5964
+ const { client, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5917
5965
  try {
5918
5966
  await client.routeRequestToNodeResponse({
5919
- path,
5967
+ path: path2,
5920
5968
  method: "POST",
5921
5969
  body: proxiedBody,
5922
5970
  headers,