@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.mjs CHANGED
@@ -4,6 +4,9 @@ import { tap } from 'rxjs';
4
4
  import { getDecodedToken } from '@cashu/cashu-ts';
5
5
  import { createStore } from 'zustand/vanilla';
6
6
  import { Transform, Readable } from 'stream';
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as os from 'os';
7
10
 
8
11
  // core/errors.ts
9
12
  var InsufficientBalanceError = class extends Error {
@@ -688,10 +691,10 @@ var AuditLogger = class _AuditLogger {
688
691
  const logLine = JSON.stringify(fullEntry) + "\n";
689
692
  if (typeof window === "undefined") {
690
693
  try {
691
- const fs = await import('fs');
692
- const path = await import('path');
693
- const logPath = path.join(process.cwd(), "audit.log");
694
- fs.appendFileSync(logPath, logLine);
694
+ const fs2 = await import('fs');
695
+ const path2 = await import('path');
696
+ const logPath = path2.join(process.cwd(), "audit.log");
697
+ fs2.appendFileSync(logPath, logLine);
695
698
  } catch (error) {
696
699
  console.error("[AuditLogger] Failed to write to file:", error);
697
700
  }
@@ -2415,6 +2418,7 @@ var ProviderManager = class _ProviderManager {
2415
2418
  }
2416
2419
  /**
2417
2420
  * Clean up expired cooldown entries
2421
+ * Also removes the provider from failedProviders so it can be retried
2418
2422
  */
2419
2423
  cleanupExpiredCooldowns() {
2420
2424
  const now = Date.now();
@@ -2427,6 +2431,10 @@ var ProviderManager = class _ProviderManager {
2427
2431
  console.log(
2428
2432
  `[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
2429
2433
  );
2434
+ this.failedProviders.delete(url);
2435
+ if (this.store) {
2436
+ this.store.getState().removeFailedProvider(url);
2437
+ }
2430
2438
  }
2431
2439
  return !isExpired;
2432
2440
  }
@@ -2600,60 +2608,47 @@ var ProviderManager = class _ProviderManager {
2600
2608
  const disabledProviders = new Set(
2601
2609
  this.providerRegistry.getDisabledProviders()
2602
2610
  );
2603
- console.log(`[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`);
2604
- console.log(`[findNextBestProvider:${this.instanceId}] currentBaseUrl: ${currentBaseUrl}`);
2605
- console.log(`[findNextBestProvider:${this.instanceId}] torMode: ${torMode}`);
2606
- console.log(`[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`);
2607
- console.log(`[findNextBestProvider:${this.instanceId}] failedProviders: ${[...this.failedProviders]}`);
2608
- console.log(`[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`);
2611
+ console.log(
2612
+ `[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`
2613
+ );
2614
+ console.log(
2615
+ `[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`
2616
+ );
2617
+ console.log(
2618
+ `[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`
2619
+ );
2609
2620
  const allProviders = this.providerRegistry.getAllProvidersModels();
2610
- console.log(`[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`);
2621
+ console.log(
2622
+ `[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`
2623
+ );
2611
2624
  const candidates = [];
2612
- let skippedCurrent = 0, skippedFailed = 0, skippedDisabled = 0, skippedCooldown = 0, skippedOnion = 0, skippedNoModel = 0;
2613
2625
  for (const [baseUrl, models] of Object.entries(allProviders)) {
2614
2626
  if (baseUrl === currentBaseUrl) {
2615
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`);
2616
- skippedCurrent++;
2617
- continue;
2618
- }
2619
- if (this.failedProviders.has(baseUrl)) {
2620
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (failed): ${baseUrl}`);
2621
- skippedFailed++;
2627
+ console.log(
2628
+ `[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`
2629
+ );
2622
2630
  continue;
2623
2631
  }
2624
2632
  if (disabledProviders.has(baseUrl)) {
2625
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (disabled): ${baseUrl}`);
2626
- skippedDisabled++;
2627
2633
  continue;
2628
2634
  }
2629
2635
  if (this.isOnCooldown(baseUrl)) {
2630
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (cooldown): ${baseUrl}`);
2631
- skippedCooldown++;
2632
2636
  continue;
2633
2637
  }
2634
2638
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
2635
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (onion/http): ${baseUrl}`);
2636
- skippedOnion++;
2637
2639
  continue;
2638
2640
  }
2639
2641
  const model = models.find((m) => m.id === modelId);
2640
2642
  if (!model) {
2641
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (no model ${modelId}): ${baseUrl} has models: ${models.map((m) => m.id).join(", ")}`);
2642
- skippedNoModel++;
2643
2643
  continue;
2644
2644
  }
2645
2645
  const cost = model.sats_pricing?.completion ?? 0;
2646
- console.log(`[findNextBestProvider:${this.instanceId}] CANDIDATE: ${baseUrl} cost: ${cost}`);
2647
2646
  candidates.push({ baseUrl, model, cost });
2648
2647
  }
2649
- console.log(`[findNextBestProvider:${this.instanceId}] Skipped: current=${skippedCurrent}, failed=${skippedFailed}, disabled=${skippedDisabled}, cooldown=${skippedCooldown}, onion=${skippedOnion}, noModel=${skippedNoModel}`);
2650
- console.log(`[findNextBestProvider:${this.instanceId}] Total candidates: ${candidates.length}`);
2651
2648
  candidates.sort((a, b) => a.cost - b.cost);
2652
2649
  if (candidates.length > 0) {
2653
- console.log(`[findNextBestProvider:${this.instanceId}] Selected provider: ${candidates[0].baseUrl} with cost: ${candidates[0].cost}`);
2654
2650
  return candidates[0].baseUrl;
2655
2651
  } else {
2656
- console.log(`[findNextBestProvider:${this.instanceId}] No candidate providers found`);
2657
2652
  return null;
2658
2653
  }
2659
2654
  } catch (error) {
@@ -4523,10 +4518,26 @@ var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await g
4523
4518
  var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
4524
4519
  function createSSEParserTransform(onUsage, onResponseId) {
4525
4520
  let buffer = "";
4526
- let usageCaptured = false;
4521
+ let capturedUsage = null;
4527
4522
  let responseIdCaptured = false;
4523
+ const mergeUsage = (previous, next) => {
4524
+ if (!previous) return next;
4525
+ return {
4526
+ promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
4527
+ completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
4528
+ totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
4529
+ cost: next.cost > 0 ? next.cost : previous.cost,
4530
+ satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
4531
+ };
4532
+ };
4533
+ const hasUsageChanged = (previous, next) => {
4534
+ if (!previous) return true;
4535
+ return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
4536
+ };
4528
4537
  const inspectDataPayload = (jsonText) => {
4529
- if (usageCaptured && responseIdCaptured) return;
4538
+ if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
4539
+ return;
4540
+ }
4530
4541
  const trimmed = jsonText.trim();
4531
4542
  if (!trimmed || trimmed === "[DONE]") return;
4532
4543
  if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
@@ -4539,18 +4550,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
4539
4550
  responseIdCaptured = true;
4540
4551
  }
4541
4552
  }
4542
- if (!usageCaptured) {
4543
- const usage = extractUsageFromSSEJson(data);
4544
- if (usage) {
4545
- onUsage(usage);
4546
- usageCaptured = true;
4553
+ const usage = extractUsageFromSSEJson(data);
4554
+ if (usage) {
4555
+ const mergedUsage = mergeUsage(capturedUsage, usage);
4556
+ if (hasUsageChanged(capturedUsage, mergedUsage)) {
4557
+ capturedUsage = mergedUsage;
4558
+ onUsage(mergedUsage);
4547
4559
  }
4548
4560
  }
4549
4561
  } catch {
4550
4562
  }
4551
4563
  };
4552
4564
  const inspectEventBlock = (eventBlock) => {
4553
- if (usageCaptured && responseIdCaptured) return;
4565
+ if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
4566
+ return;
4567
+ }
4554
4568
  const lines = eventBlock.split(/\r?\n/);
4555
4569
  const dataParts = [];
4556
4570
  for (const line of lines) {
@@ -4776,7 +4790,7 @@ var RoutstrClient = class {
4776
4790
  }
4777
4791
  async _prepareRoutedRequest(params) {
4778
4792
  const {
4779
- path,
4793
+ path: requestPath,
4780
4794
  method,
4781
4795
  body,
4782
4796
  headers = {},
@@ -4817,7 +4831,7 @@ var RoutstrClient = class {
4817
4831
  const baseHeaders = this._buildBaseHeaders();
4818
4832
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
4819
4833
  const response = await this._makeRequest({
4820
- path,
4834
+ path: requestPath,
4821
4835
  method,
4822
4836
  body: method === "GET" ? void 0 : requestBody,
4823
4837
  baseUrl,
@@ -4836,7 +4850,20 @@ var RoutstrClient = class {
4836
4850
  let capturedUsage;
4837
4851
  let capturedResponseId;
4838
4852
  if (contentType.includes("text/event-stream") && response.body) {
4853
+ const logDir = path.join(os.homedir(), ".routstrd", "stream-response");
4854
+ if (!fs.existsSync(logDir)) {
4855
+ fs.mkdirSync(logDir, { recursive: true });
4856
+ }
4857
+ const logFile = path.join(logDir, `${Date.now()}.jsonl`);
4858
+ const logStream = fs.createWriteStream(logFile);
4839
4859
  const nodeReadable = Readable.fromWeb(response.body);
4860
+ const loggingTransform = new Transform({
4861
+ transform(chunk, encoding, callback) {
4862
+ const raw = chunk.toString();
4863
+ logStream.write(JSON.stringify({ raw, timestamp: Date.now() }) + "\n");
4864
+ callback(null, chunk);
4865
+ }
4866
+ });
4840
4867
  const sseParser = createSSEParserTransform(
4841
4868
  (usage) => {
4842
4869
  capturedUsage = usage;
@@ -4847,7 +4874,7 @@ var RoutstrClient = class {
4847
4874
  processedResponse.requestId = responseId;
4848
4875
  }
4849
4876
  );
4850
- const transformed = nodeReadable.pipe(sseParser, { end: true });
4877
+ const transformed = nodeReadable.pipe(loggingTransform).pipe(sseParser, { end: true });
4851
4878
  const webStream = Readable.toWeb(
4852
4879
  transformed
4853
4880
  );
@@ -4916,7 +4943,6 @@ var RoutstrClient = class {
4916
4943
  callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
4917
4944
  const baseHeaders = this._buildBaseHeaders(headers);
4918
4945
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
4919
- this.providerManager.resetFailedProviders();
4920
4946
  const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
4921
4947
  const providerVersion = providerInfo?.version ?? "";
4922
4948
  let modelIdForRequest = selectedModel.id;
@@ -5019,9 +5045,9 @@ var RoutstrClient = class {
5019
5045
  * Make the API request with failover support
5020
5046
  */
5021
5047
  async _makeRequest(params) {
5022
- const { path, method, body, baseUrl, token, headers } = params;
5048
+ const { path: path2, method, body, baseUrl, token, headers } = params;
5023
5049
  try {
5024
- const url = `${baseUrl.replace(/\/$/, "")}${path}`;
5050
+ const url = `${baseUrl.replace(/\/$/, "")}${path2}`;
5025
5051
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
5026
5052
  const response = await fetch(url, {
5027
5053
  method,
@@ -5071,7 +5097,7 @@ var RoutstrClient = class {
5071
5097
  */
5072
5098
  async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
5073
5099
  const MAX_RETRIES_PER_PROVIDER = 2;
5074
- const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
5100
+ const { path: path2, method, body, selectedModel, baseUrl, mintUrl } = params;
5075
5101
  let tryNextProvider = false;
5076
5102
  const errorMessage = responseBody;
5077
5103
  this._log(
@@ -5373,7 +5399,7 @@ var RoutstrClient = class {
5373
5399
  });
5374
5400
  return this._makeRequest({
5375
5401
  ...params,
5376
- path,
5402
+ path: path2,
5377
5403
  method,
5378
5404
  body,
5379
5405
  baseUrl: nextProvider,
@@ -5781,7 +5807,7 @@ async function resolveRouteRequestContext(options) {
5781
5807
  const {
5782
5808
  modelId,
5783
5809
  requestBody,
5784
- path = "/v1/chat/completions",
5810
+ path: path2 = "/v1/chat/completions",
5785
5811
  headers = {},
5786
5812
  forcedProvider,
5787
5813
  walletAdapter,
@@ -5880,17 +5906,17 @@ async function resolveRouteRequestContext(options) {
5880
5906
  client,
5881
5907
  baseUrl,
5882
5908
  mintUrl,
5883
- path,
5909
+ path: path2,
5884
5910
  headers,
5885
5911
  modelId,
5886
5912
  proxiedBody
5887
5913
  };
5888
5914
  }
5889
5915
  async function routeRequests(options) {
5890
- const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5916
+ const { client, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5891
5917
  try {
5892
5918
  const response = await client.routeRequest({
5893
- path,
5919
+ path: path2,
5894
5920
  method: "POST",
5895
5921
  body: proxiedBody,
5896
5922
  headers,
@@ -5911,10 +5937,10 @@ async function routeRequests(options) {
5911
5937
  }
5912
5938
  async function routeRequestsToNodeResponse(options) {
5913
5939
  const { res } = options;
5914
- const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5940
+ const { client, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5915
5941
  try {
5916
5942
  await client.routeRequestToNodeResponse({
5917
- path,
5943
+ path: path2,
5918
5944
  method: "POST",
5919
5945
  body: proxiedBody,
5920
5946
  headers,