@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.
@@ -50,6 +50,7 @@ declare class ProviderManager {
50
50
  getInstanceId(): string;
51
51
  /**
52
52
  * Clean up expired cooldown entries
53
+ * Also removes the provider from failedProviders so it can be retried
53
54
  */
54
55
  private cleanupExpiredCooldowns;
55
56
  /**
@@ -50,6 +50,7 @@ declare class ProviderManager {
50
50
  getInstanceId(): string;
51
51
  /**
52
52
  * Clean up expired cooldown entries
53
+ * Also removes the provider from failedProviders so it can be retried
53
54
  */
54
55
  private cleanupExpiredCooldowns;
55
56
  /**
@@ -3,6 +3,31 @@
3
3
  var cashuTs = require('@cashu/cashu-ts');
4
4
  var vanilla = require('zustand/vanilla');
5
5
  var stream = require('stream');
6
+ var fs = require('fs');
7
+ var path = require('path');
8
+ var os = require('os');
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
29
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
30
+ var os__namespace = /*#__PURE__*/_interopNamespace(os);
6
31
 
7
32
  // core/errors.ts
8
33
  var InsufficientBalanceError = class extends Error {
@@ -56,10 +81,10 @@ var AuditLogger = class _AuditLogger {
56
81
  const logLine = JSON.stringify(fullEntry) + "\n";
57
82
  if (typeof window === "undefined") {
58
83
  try {
59
- const fs = await import('fs');
60
- const path = await import('path');
61
- const logPath = path.join(process.cwd(), "audit.log");
62
- fs.appendFileSync(logPath, logLine);
84
+ const fs2 = await import('fs');
85
+ const path2 = await import('path');
86
+ const logPath = path2.join(process.cwd(), "audit.log");
87
+ fs2.appendFileSync(logPath, logLine);
63
88
  } catch (error) {
64
89
  console.error("[AuditLogger] Failed to write to file:", error);
65
90
  }
@@ -1727,6 +1752,7 @@ var ProviderManager = class _ProviderManager {
1727
1752
  }
1728
1753
  /**
1729
1754
  * Clean up expired cooldown entries
1755
+ * Also removes the provider from failedProviders so it can be retried
1730
1756
  */
1731
1757
  cleanupExpiredCooldowns() {
1732
1758
  const now = Date.now();
@@ -1739,6 +1765,10 @@ var ProviderManager = class _ProviderManager {
1739
1765
  console.log(
1740
1766
  `[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
1741
1767
  );
1768
+ this.failedProviders.delete(url);
1769
+ if (this.store) {
1770
+ this.store.getState().removeFailedProvider(url);
1771
+ }
1742
1772
  }
1743
1773
  return !isExpired;
1744
1774
  }
@@ -1912,60 +1942,47 @@ var ProviderManager = class _ProviderManager {
1912
1942
  const disabledProviders = new Set(
1913
1943
  this.providerRegistry.getDisabledProviders()
1914
1944
  );
1915
- console.log(`[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`);
1916
- console.log(`[findNextBestProvider:${this.instanceId}] currentBaseUrl: ${currentBaseUrl}`);
1917
- console.log(`[findNextBestProvider:${this.instanceId}] torMode: ${torMode}`);
1918
- console.log(`[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`);
1919
- console.log(`[findNextBestProvider:${this.instanceId}] failedProviders: ${[...this.failedProviders]}`);
1920
- console.log(`[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`);
1945
+ console.log(
1946
+ `[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`
1947
+ );
1948
+ console.log(
1949
+ `[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`
1950
+ );
1951
+ console.log(
1952
+ `[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`
1953
+ );
1921
1954
  const allProviders = this.providerRegistry.getAllProvidersModels();
1922
- console.log(`[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`);
1955
+ console.log(
1956
+ `[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`
1957
+ );
1923
1958
  const candidates = [];
1924
- let skippedCurrent = 0, skippedFailed = 0, skippedDisabled = 0, skippedCooldown = 0, skippedOnion = 0, skippedNoModel = 0;
1925
1959
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1926
1960
  if (baseUrl === currentBaseUrl) {
1927
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`);
1928
- skippedCurrent++;
1929
- continue;
1930
- }
1931
- if (this.failedProviders.has(baseUrl)) {
1932
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (failed): ${baseUrl}`);
1933
- skippedFailed++;
1961
+ console.log(
1962
+ `[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`
1963
+ );
1934
1964
  continue;
1935
1965
  }
1936
1966
  if (disabledProviders.has(baseUrl)) {
1937
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (disabled): ${baseUrl}`);
1938
- skippedDisabled++;
1939
1967
  continue;
1940
1968
  }
1941
1969
  if (this.isOnCooldown(baseUrl)) {
1942
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (cooldown): ${baseUrl}`);
1943
- skippedCooldown++;
1944
1970
  continue;
1945
1971
  }
1946
1972
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
1947
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (onion/http): ${baseUrl}`);
1948
- skippedOnion++;
1949
1973
  continue;
1950
1974
  }
1951
1975
  const model = models.find((m) => m.id === modelId);
1952
1976
  if (!model) {
1953
- console.log(`[findNextBestProvider:${this.instanceId}] SKIP (no model ${modelId}): ${baseUrl} has models: ${models.map((m) => m.id).join(", ")}`);
1954
- skippedNoModel++;
1955
1977
  continue;
1956
1978
  }
1957
1979
  const cost = model.sats_pricing?.completion ?? 0;
1958
- console.log(`[findNextBestProvider:${this.instanceId}] CANDIDATE: ${baseUrl} cost: ${cost}`);
1959
1980
  candidates.push({ baseUrl, model, cost });
1960
1981
  }
1961
- console.log(`[findNextBestProvider:${this.instanceId}] Skipped: current=${skippedCurrent}, failed=${skippedFailed}, disabled=${skippedDisabled}, cooldown=${skippedCooldown}, onion=${skippedOnion}, noModel=${skippedNoModel}`);
1962
- console.log(`[findNextBestProvider:${this.instanceId}] Total candidates: ${candidates.length}`);
1963
1982
  candidates.sort((a, b) => a.cost - b.cost);
1964
1983
  if (candidates.length > 0) {
1965
- console.log(`[findNextBestProvider:${this.instanceId}] Selected provider: ${candidates[0].baseUrl} with cost: ${candidates[0].cost}`);
1966
1984
  return candidates[0].baseUrl;
1967
1985
  } else {
1968
- console.log(`[findNextBestProvider:${this.instanceId}] No candidate providers found`);
1969
1986
  return null;
1970
1987
  }
1971
1988
  } catch (error) {
@@ -3438,10 +3455,26 @@ var getDefaultUsageTrackingDriver = () => {
3438
3455
  };
3439
3456
  function createSSEParserTransform(onUsage, onResponseId) {
3440
3457
  let buffer = "";
3441
- let usageCaptured = false;
3458
+ let capturedUsage = null;
3442
3459
  let responseIdCaptured = false;
3460
+ const mergeUsage = (previous, next) => {
3461
+ if (!previous) return next;
3462
+ return {
3463
+ promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
3464
+ completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
3465
+ totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
3466
+ cost: next.cost > 0 ? next.cost : previous.cost,
3467
+ satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
3468
+ };
3469
+ };
3470
+ const hasUsageChanged = (previous, next) => {
3471
+ if (!previous) return true;
3472
+ return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
3473
+ };
3443
3474
  const inspectDataPayload = (jsonText) => {
3444
- if (usageCaptured && responseIdCaptured) return;
3475
+ if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
3476
+ return;
3477
+ }
3445
3478
  const trimmed = jsonText.trim();
3446
3479
  if (!trimmed || trimmed === "[DONE]") return;
3447
3480
  if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
@@ -3454,18 +3487,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
3454
3487
  responseIdCaptured = true;
3455
3488
  }
3456
3489
  }
3457
- if (!usageCaptured) {
3458
- const usage = extractUsageFromSSEJson(data);
3459
- if (usage) {
3460
- onUsage(usage);
3461
- usageCaptured = true;
3490
+ const usage = extractUsageFromSSEJson(data);
3491
+ if (usage) {
3492
+ const mergedUsage = mergeUsage(capturedUsage, usage);
3493
+ if (hasUsageChanged(capturedUsage, mergedUsage)) {
3494
+ capturedUsage = mergedUsage;
3495
+ onUsage(mergedUsage);
3462
3496
  }
3463
3497
  }
3464
3498
  } catch {
3465
3499
  }
3466
3500
  };
3467
3501
  const inspectEventBlock = (eventBlock) => {
3468
- if (usageCaptured && responseIdCaptured) return;
3502
+ if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
3503
+ return;
3504
+ }
3469
3505
  const lines = eventBlock.split(/\r?\n/);
3470
3506
  const dataParts = [];
3471
3507
  for (const line of lines) {
@@ -3691,7 +3727,7 @@ var RoutstrClient = class {
3691
3727
  }
3692
3728
  async _prepareRoutedRequest(params) {
3693
3729
  const {
3694
- path,
3730
+ path: requestPath,
3695
3731
  method,
3696
3732
  body,
3697
3733
  headers = {},
@@ -3732,7 +3768,7 @@ var RoutstrClient = class {
3732
3768
  const baseHeaders = this._buildBaseHeaders();
3733
3769
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
3734
3770
  const response = await this._makeRequest({
3735
- path,
3771
+ path: requestPath,
3736
3772
  method,
3737
3773
  body: method === "GET" ? void 0 : requestBody,
3738
3774
  baseUrl,
@@ -3751,7 +3787,20 @@ var RoutstrClient = class {
3751
3787
  let capturedUsage;
3752
3788
  let capturedResponseId;
3753
3789
  if (contentType.includes("text/event-stream") && response.body) {
3790
+ const logDir = path__namespace.join(os__namespace.homedir(), ".routstrd", "stream-response");
3791
+ if (!fs__namespace.existsSync(logDir)) {
3792
+ fs__namespace.mkdirSync(logDir, { recursive: true });
3793
+ }
3794
+ const logFile = path__namespace.join(logDir, `${Date.now()}.jsonl`);
3795
+ const logStream = fs__namespace.createWriteStream(logFile);
3754
3796
  const nodeReadable = stream.Readable.fromWeb(response.body);
3797
+ const loggingTransform = new stream.Transform({
3798
+ transform(chunk, encoding, callback) {
3799
+ const raw = chunk.toString();
3800
+ logStream.write(JSON.stringify({ raw, timestamp: Date.now() }) + "\n");
3801
+ callback(null, chunk);
3802
+ }
3803
+ });
3755
3804
  const sseParser = createSSEParserTransform(
3756
3805
  (usage) => {
3757
3806
  capturedUsage = usage;
@@ -3762,7 +3811,7 @@ var RoutstrClient = class {
3762
3811
  processedResponse.requestId = responseId;
3763
3812
  }
3764
3813
  );
3765
- const transformed = nodeReadable.pipe(sseParser, { end: true });
3814
+ const transformed = nodeReadable.pipe(loggingTransform).pipe(sseParser, { end: true });
3766
3815
  const webStream = stream.Readable.toWeb(
3767
3816
  transformed
3768
3817
  );
@@ -3831,7 +3880,6 @@ var RoutstrClient = class {
3831
3880
  callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
3832
3881
  const baseHeaders = this._buildBaseHeaders(headers);
3833
3882
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
3834
- this.providerManager.resetFailedProviders();
3835
3883
  const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
3836
3884
  const providerVersion = providerInfo?.version ?? "";
3837
3885
  let modelIdForRequest = selectedModel.id;
@@ -3934,9 +3982,9 @@ var RoutstrClient = class {
3934
3982
  * Make the API request with failover support
3935
3983
  */
3936
3984
  async _makeRequest(params) {
3937
- const { path, method, body, baseUrl, token, headers } = params;
3985
+ const { path: path2, method, body, baseUrl, token, headers } = params;
3938
3986
  try {
3939
- const url = `${baseUrl.replace(/\/$/, "")}${path}`;
3987
+ const url = `${baseUrl.replace(/\/$/, "")}${path2}`;
3940
3988
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
3941
3989
  const response = await fetch(url, {
3942
3990
  method,
@@ -3986,7 +4034,7 @@ var RoutstrClient = class {
3986
4034
  */
3987
4035
  async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
3988
4036
  const MAX_RETRIES_PER_PROVIDER = 2;
3989
- const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
4037
+ const { path: path2, method, body, selectedModel, baseUrl, mintUrl } = params;
3990
4038
  let tryNextProvider = false;
3991
4039
  const errorMessage = responseBody;
3992
4040
  this._log(
@@ -4288,7 +4336,7 @@ var RoutstrClient = class {
4288
4336
  });
4289
4337
  return this._makeRequest({
4290
4338
  ...params,
4291
- path,
4339
+ path: path2,
4292
4340
  method,
4293
4341
  body,
4294
4342
  baseUrl: nextProvider,