@routstr/sdk 0.2.9 → 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
  }
@@ -1929,17 +1954,48 @@ function extractResponseId(body) {
1929
1954
  return trimmed.length > 0 ? trimmed : void 0;
1930
1955
  }
1931
1956
  function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
1932
- if (!parsed || typeof parsed !== "object" || !parsed.usage) {
1957
+ if (!parsed || typeof parsed !== "object") {
1958
+ return null;
1959
+ }
1960
+ if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
1961
+ const costObj = parsed.cost;
1962
+ const msats2 = costObj.total_msats ?? 0;
1963
+ const cost2 = costObj.total_usd ?? 0;
1964
+ if (msats2 === 0 && cost2 === 0) return null;
1965
+ return {
1966
+ promptTokens: Number(costObj.input_tokens ?? 0),
1967
+ completionTokens: Number(costObj.output_tokens ?? 0),
1968
+ totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
1969
+ cost: Number(cost2),
1970
+ satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost
1971
+ };
1972
+ }
1973
+ if (!parsed.usage) {
1933
1974
  return null;
1934
1975
  }
1935
1976
  const usage = parsed.usage;
1936
1977
  const usageCost = usage.cost;
1937
- const cost = typeof usageCost === "number" ? usageCost : usageCost?.total_usd ?? parsed.metadata?.routstr?.cost?.total_usd ?? 0;
1938
- const msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
1978
+ let cost = 0;
1979
+ let msats = 0;
1980
+ if (typeof usageCost === "number") {
1981
+ cost = usageCost;
1982
+ } else if (usageCost && typeof usageCost === "object") {
1983
+ cost = usageCost.total_usd ?? 0;
1984
+ msats = usageCost.total_msats ?? 0;
1985
+ }
1986
+ if (cost === 0) {
1987
+ cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
1988
+ }
1989
+ if (msats === 0) {
1990
+ msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
1991
+ }
1992
+ const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
1993
+ const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
1994
+ const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
1939
1995
  const result = {
1940
- promptTokens: Number(usage.prompt_tokens ?? 0),
1941
- completionTokens: Number(usage.completion_tokens ?? 0),
1942
- totalTokens: Number(usage.total_tokens ?? 0),
1996
+ promptTokens,
1997
+ completionTokens,
1998
+ totalTokens,
1943
1999
  cost: Number(cost ?? 0),
1944
2000
  satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
1945
2001
  };
@@ -2065,11 +2121,14 @@ var StreamProcessor = class {
2065
2121
  if (parsed.choices?.[0]?.delta?.reasoning) {
2066
2122
  result.reasoning = parsed.choices[0].delta.reasoning;
2067
2123
  }
2068
- if (parsed.usage) {
2069
- result.usage = toUsageStats(extractUsageFromSSEJson(parsed)) ?? {
2070
- total_tokens: parsed.usage.total_tokens,
2071
- prompt_tokens: parsed.usage.prompt_tokens,
2072
- completion_tokens: parsed.usage.completion_tokens
2124
+ const extractedUsage = extractUsageFromSSEJson(parsed);
2125
+ if (extractedUsage) {
2126
+ result.usage = toUsageStats(extractedUsage);
2127
+ } else if (parsed.usage) {
2128
+ result.usage = {
2129
+ total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
2130
+ prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
2131
+ completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
2073
2132
  };
2074
2133
  }
2075
2134
  if (parsed.id) {
@@ -2383,6 +2442,7 @@ var ProviderManager = class _ProviderManager {
2383
2442
  }
2384
2443
  /**
2385
2444
  * Clean up expired cooldown entries
2445
+ * Also removes the provider from failedProviders so it can be retried
2386
2446
  */
2387
2447
  cleanupExpiredCooldowns() {
2388
2448
  const now = Date.now();
@@ -2395,6 +2455,10 @@ var ProviderManager = class _ProviderManager {
2395
2455
  console.log(
2396
2456
  `[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
2397
2457
  );
2458
+ this.failedProviders.delete(url);
2459
+ if (this.store) {
2460
+ this.store.getState().removeFailedProvider(url);
2461
+ }
2398
2462
  }
2399
2463
  return !isExpired;
2400
2464
  }
@@ -2568,22 +2632,49 @@ var ProviderManager = class _ProviderManager {
2568
2632
  const disabledProviders = new Set(
2569
2633
  this.providerRegistry.getDisabledProviders()
2570
2634
  );
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
+ );
2571
2644
  const allProviders = this.providerRegistry.getAllProvidersModels();
2645
+ console.log(
2646
+ `[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`
2647
+ );
2572
2648
  const candidates = [];
2573
2649
  for (const [baseUrl, models] of Object.entries(allProviders)) {
2574
- if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl) || this.isOnCooldown(baseUrl)) {
2650
+ if (baseUrl === currentBaseUrl) {
2651
+ console.log(
2652
+ `[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`
2653
+ );
2654
+ continue;
2655
+ }
2656
+ if (disabledProviders.has(baseUrl)) {
2657
+ continue;
2658
+ }
2659
+ if (this.isOnCooldown(baseUrl)) {
2575
2660
  continue;
2576
2661
  }
2577
2662
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
2578
2663
  continue;
2579
2664
  }
2580
2665
  const model = models.find((m) => m.id === modelId);
2581
- if (!model) continue;
2666
+ if (!model) {
2667
+ continue;
2668
+ }
2582
2669
  const cost = model.sats_pricing?.completion ?? 0;
2583
2670
  candidates.push({ baseUrl, model, cost });
2584
2671
  }
2585
2672
  candidates.sort((a, b) => a.cost - b.cost);
2586
- return candidates.length > 0 ? candidates[0].baseUrl : null;
2673
+ if (candidates.length > 0) {
2674
+ return candidates[0].baseUrl;
2675
+ } else {
2676
+ return null;
2677
+ }
2587
2678
  } catch (error) {
2588
2679
  console.error("Error finding next best provider:", error);
2589
2680
  return null;
@@ -4451,69 +4542,95 @@ var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await g
4451
4542
  var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
4452
4543
  function createSSEParserTransform(onUsage, onResponseId) {
4453
4544
  let buffer = "";
4454
- let usageCaptured = false;
4545
+ let capturedUsage = null;
4455
4546
  let responseIdCaptured = false;
4456
- const maybeCaptureUsageFromJson = (jsonText) => {
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
+ };
4561
+ const inspectDataPayload = (jsonText) => {
4562
+ if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
4563
+ return;
4564
+ }
4565
+ const trimmed = jsonText.trim();
4566
+ if (!trimmed || trimmed === "[DONE]") return;
4567
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
4457
4568
  try {
4458
- const data = JSON.parse(jsonText);
4459
- const responseId = data.id;
4460
- if (typeof responseId === "string" && responseId.trim().length > 0) {
4461
- onResponseId?.(responseId.trim());
4462
- responseIdCaptured = true;
4569
+ const data = JSON.parse(trimmed);
4570
+ if (!responseIdCaptured) {
4571
+ const responseId = data?.id;
4572
+ if (typeof responseId === "string" && responseId.trim().length > 0) {
4573
+ onResponseId?.(responseId.trim());
4574
+ responseIdCaptured = true;
4575
+ }
4463
4576
  }
4464
4577
  const usage = extractUsageFromSSEJson(data);
4465
4578
  if (usage) {
4466
- onUsage(usage);
4467
- usageCaptured = true;
4579
+ const mergedUsage = mergeUsage(capturedUsage, usage);
4580
+ if (hasUsageChanged(capturedUsage, mergedUsage)) {
4581
+ capturedUsage = mergedUsage;
4582
+ onUsage(mergedUsage);
4583
+ }
4468
4584
  }
4469
4585
  } catch {
4470
4586
  }
4471
4587
  };
4472
- const processLine = (self, line) => {
4473
- const trimmed = line.trim();
4474
- if (!trimmed) {
4588
+ const inspectEventBlock = (eventBlock) => {
4589
+ if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
4475
4590
  return;
4476
4591
  }
4477
- if (trimmed === "data: [DONE]" || trimmed === "[DONE]") {
4478
- self.push("data: [DONE]\n\n");
4479
- return;
4480
- }
4481
- if (trimmed.startsWith("data:")) {
4482
- const dataStr = trimmed.startsWith("data: ") ? trimmed.slice(6) : trimmed.slice(5).trimStart();
4483
- if (dataStr === "[DONE]") {
4484
- self.push("data: [DONE]\n\n");
4485
- return;
4592
+ const lines = eventBlock.split(/\r?\n/);
4593
+ const dataParts = [];
4594
+ for (const line of lines) {
4595
+ if (!line || line.startsWith(":")) continue;
4596
+ if (line.startsWith("data:")) {
4597
+ const value = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
4598
+ dataParts.push(value);
4486
4599
  }
4487
- maybeCaptureUsageFromJson(dataStr);
4488
- self.push(`data: ${dataStr}
4489
-
4490
- `);
4491
- return;
4492
- }
4493
- if (trimmed.startsWith("{")) {
4494
- maybeCaptureUsageFromJson(trimmed);
4495
- self.push(`data: ${trimmed}
4496
-
4497
- `);
4498
- return;
4499
4600
  }
4500
- self.push(line + "\n");
4601
+ if (dataParts.length === 0) return;
4602
+ const payload = dataParts.join("\n");
4603
+ inspectDataPayload(payload);
4604
+ };
4605
+ const emitEventBlock = (self, eventBlock) => {
4606
+ if (eventBlock.length === 0) return;
4607
+ inspectEventBlock(eventBlock);
4608
+ self.push(eventBlock + "\n\n");
4501
4609
  };
4502
4610
  return new stream.Transform({
4503
- transform(chunk, encoding, callback) {
4611
+ transform(chunk, _encoding, callback) {
4504
4612
  buffer += chunk.toString();
4505
- const lines = buffer.split(/\r?\n/);
4506
- buffer = lines.pop() || "";
4507
- for (const line of lines) {
4508
- processLine(this, line);
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);
4509
4623
  }
4510
4624
  callback();
4511
4625
  },
4512
4626
  flush(callback) {
4513
- if (buffer.trim()) {
4514
- processLine(this, buffer);
4627
+ if (buffer.length > 0) {
4628
+ const tail = buffer.replace(/\r?\n+$/, "");
4629
+ if (tail.length > 0) {
4630
+ emitEventBlock(this, tail);
4631
+ }
4632
+ buffer = "";
4515
4633
  }
4516
- buffer = "";
4517
4634
  callback();
4518
4635
  }
4519
4636
  });
@@ -4697,7 +4814,7 @@ var RoutstrClient = class {
4697
4814
  }
4698
4815
  async _prepareRoutedRequest(params) {
4699
4816
  const {
4700
- path,
4817
+ path: requestPath,
4701
4818
  method,
4702
4819
  body,
4703
4820
  headers = {},
@@ -4738,7 +4855,7 @@ var RoutstrClient = class {
4738
4855
  const baseHeaders = this._buildBaseHeaders();
4739
4856
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
4740
4857
  const response = await this._makeRequest({
4741
- path,
4858
+ path: requestPath,
4742
4859
  method,
4743
4860
  body: method === "GET" ? void 0 : requestBody,
4744
4861
  baseUrl,
@@ -4757,7 +4874,20 @@ var RoutstrClient = class {
4757
4874
  let capturedUsage;
4758
4875
  let capturedResponseId;
4759
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);
4760
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
+ });
4761
4891
  const sseParser = createSSEParserTransform(
4762
4892
  (usage) => {
4763
4893
  capturedUsage = usage;
@@ -4768,7 +4898,7 @@ var RoutstrClient = class {
4768
4898
  processedResponse.requestId = responseId;
4769
4899
  }
4770
4900
  );
4771
- const transformed = nodeReadable.pipe(sseParser, { end: true });
4901
+ const transformed = nodeReadable.pipe(loggingTransform).pipe(sseParser, { end: true });
4772
4902
  const webStream = stream.Readable.toWeb(
4773
4903
  transformed
4774
4904
  );
@@ -4837,7 +4967,6 @@ var RoutstrClient = class {
4837
4967
  callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
4838
4968
  const baseHeaders = this._buildBaseHeaders(headers);
4839
4969
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
4840
- this.providerManager.resetFailedProviders();
4841
4970
  const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
4842
4971
  const providerVersion = providerInfo?.version ?? "";
4843
4972
  let modelIdForRequest = selectedModel.id;
@@ -4940,9 +5069,9 @@ var RoutstrClient = class {
4940
5069
  * Make the API request with failover support
4941
5070
  */
4942
5071
  async _makeRequest(params) {
4943
- const { path, method, body, baseUrl, token, headers } = params;
5072
+ const { path: path2, method, body, baseUrl, token, headers } = params;
4944
5073
  try {
4945
- const url = `${baseUrl.replace(/\/$/, "")}${path}`;
5074
+ const url = `${baseUrl.replace(/\/$/, "")}${path2}`;
4946
5075
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
4947
5076
  const response = await fetch(url, {
4948
5077
  method,
@@ -4992,11 +5121,12 @@ var RoutstrClient = class {
4992
5121
  */
4993
5122
  async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
4994
5123
  const MAX_RETRIES_PER_PROVIDER = 2;
4995
- const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
5124
+ const { path: path2, method, body, selectedModel, baseUrl, mintUrl } = params;
4996
5125
  let tryNextProvider = false;
5126
+ const errorMessage = responseBody;
4997
5127
  this._log(
4998
5128
  "DEBUG",
4999
- `[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}`
5129
+ `[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}, errorMessage=${errorMessage}`
5000
5130
  );
5001
5131
  this._log(
5002
5132
  "DEBUG",
@@ -5293,7 +5423,7 @@ var RoutstrClient = class {
5293
5423
  });
5294
5424
  return this._makeRequest({
5295
5425
  ...params,
5296
- path,
5426
+ path: path2,
5297
5427
  method,
5298
5428
  body,
5299
5429
  baseUrl: nextProvider,
@@ -5701,7 +5831,7 @@ async function resolveRouteRequestContext(options) {
5701
5831
  const {
5702
5832
  modelId,
5703
5833
  requestBody,
5704
- path = "/v1/chat/completions",
5834
+ path: path2 = "/v1/chat/completions",
5705
5835
  headers = {},
5706
5836
  forcedProvider,
5707
5837
  walletAdapter,
@@ -5800,17 +5930,17 @@ async function resolveRouteRequestContext(options) {
5800
5930
  client,
5801
5931
  baseUrl,
5802
5932
  mintUrl,
5803
- path,
5933
+ path: path2,
5804
5934
  headers,
5805
5935
  modelId,
5806
5936
  proxiedBody
5807
5937
  };
5808
5938
  }
5809
5939
  async function routeRequests(options) {
5810
- const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5940
+ const { client, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5811
5941
  try {
5812
5942
  const response = await client.routeRequest({
5813
- path,
5943
+ path: path2,
5814
5944
  method: "POST",
5815
5945
  body: proxiedBody,
5816
5946
  headers,
@@ -5831,10 +5961,10 @@ async function routeRequests(options) {
5831
5961
  }
5832
5962
  async function routeRequestsToNodeResponse(options) {
5833
5963
  const { res } = options;
5834
- const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5964
+ const { client, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5835
5965
  try {
5836
5966
  await client.routeRequestToNodeResponse({
5837
- path,
5967
+ path: path2,
5838
5968
  method: "POST",
5839
5969
  body: proxiedBody,
5840
5970
  headers,