@routstr/sdk 0.3.12 → 0.3.13

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.
Files changed (48) hide show
  1. package/README.md +6 -19
  2. package/dist/browser.d.mts +2 -1
  3. package/dist/browser.d.ts +2 -1
  4. package/dist/browser.js +360 -28
  5. package/dist/browser.js.map +1 -1
  6. package/dist/browser.mjs +355 -29
  7. package/dist/browser.mjs.map +1 -1
  8. package/dist/bun.d.mts +2 -1
  9. package/dist/bun.d.ts +2 -1
  10. package/dist/bun.js +363 -31
  11. package/dist/bun.js.map +1 -1
  12. package/dist/bun.mjs +358 -32
  13. package/dist/bun.mjs.map +1 -1
  14. package/dist/client/index.d.mts +85 -1
  15. package/dist/client/index.d.ts +85 -1
  16. package/dist/client/index.js +358 -27
  17. package/dist/client/index.js.map +1 -1
  18. package/dist/client/index.mjs +353 -28
  19. package/dist/client/index.mjs.map +1 -1
  20. package/dist/index.d.mts +2 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.js +360 -28
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +355 -29
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/node.d.mts +2 -1
  27. package/dist/node.d.ts +2 -1
  28. package/dist/node.js +363 -31
  29. package/dist/node.js.map +1 -1
  30. package/dist/node.mjs +358 -32
  31. package/dist/node.mjs.map +1 -1
  32. package/dist/storage/bun.js +2 -1
  33. package/dist/storage/bun.js.map +1 -1
  34. package/dist/storage/bun.mjs +2 -1
  35. package/dist/storage/bun.mjs.map +1 -1
  36. package/dist/storage/index.js +2 -1
  37. package/dist/storage/index.js.map +1 -1
  38. package/dist/storage/index.mjs +2 -1
  39. package/dist/storage/index.mjs.map +1 -1
  40. package/dist/storage/node.js +2 -1
  41. package/dist/storage/node.js.map +1 -1
  42. package/dist/storage/node.mjs +2 -1
  43. package/dist/storage/node.mjs.map +1 -1
  44. package/dist/wallet/index.js +36 -8
  45. package/dist/wallet/index.js.map +1 -1
  46. package/dist/wallet/index.mjs +36 -8
  47. package/dist/wallet/index.mjs.map +1 -1
  48. package/package.json +3 -1
package/dist/node.mjs CHANGED
@@ -1858,15 +1858,27 @@ var BalanceManager = class _BalanceManager {
1858
1858
  clearTimeout(timeoutId);
1859
1859
  const requestId = response.headers.get("x-routstr-request-id") || void 0;
1860
1860
  if (!response.ok) {
1861
- const errorData = await response.json().catch(() => ({}));
1862
- this.logger.warn(
1863
- `fetchRefundToken: non-ok response for ${url} status=${response.status} statusText=${response.statusText}`,
1864
- errorData
1865
- );
1861
+ const responseBody = await response.text().catch(() => void 0);
1862
+ let errorData = {};
1863
+ if (responseBody) {
1864
+ try {
1865
+ errorData = JSON.parse(responseBody);
1866
+ } catch {
1867
+ errorData = {};
1868
+ }
1869
+ }
1870
+ this.logger.error("Upstream wallet refund error response", {
1871
+ baseUrl,
1872
+ url,
1873
+ status: response.status,
1874
+ statusText: response.statusText,
1875
+ requestId,
1876
+ body: responseBody ?? "<unable to read response body>"
1877
+ });
1866
1878
  return {
1867
1879
  success: false,
1868
1880
  requestId,
1869
- error: `API key refund failed: ${errorData?.detail || response.statusText}`
1881
+ error: `API key refund failed: ${errorData?.detail || responseBody || response.statusText}`
1870
1882
  };
1871
1883
  }
1872
1884
  const data = await response.json();
@@ -2202,11 +2214,27 @@ var BalanceManager = class _BalanceManager {
2202
2214
  clearTimeout(timeoutId);
2203
2215
  const requestId = response.headers.get("x-routstr-request-id") || void 0;
2204
2216
  if (!response.ok) {
2205
- const errorData = await response.json().catch(() => ({}));
2217
+ const responseBody = await response.text().catch(() => void 0);
2218
+ let errorData = {};
2219
+ if (responseBody) {
2220
+ try {
2221
+ errorData = JSON.parse(responseBody);
2222
+ } catch {
2223
+ errorData = {};
2224
+ }
2225
+ }
2226
+ this.logger.error("Upstream wallet topup error response", {
2227
+ baseUrl,
2228
+ url,
2229
+ status: response.status,
2230
+ statusText: response.statusText,
2231
+ requestId,
2232
+ body: responseBody ?? "<unable to read response body>"
2233
+ });
2206
2234
  return {
2207
2235
  success: false,
2208
2236
  requestId,
2209
- error: errorData?.detail || `Top up failed with status ${response.status}`
2237
+ error: errorData?.detail || responseBody || `Top up failed with status ${response.status}`
2210
2238
  };
2211
2239
  }
2212
2240
  return { success: true, requestId };
@@ -3039,7 +3067,7 @@ var openDatabase = (dbName, storeName) => {
3039
3067
  return Promise.reject(new Error("IndexedDB is not available"));
3040
3068
  }
3041
3069
  return new Promise((resolve, reject) => {
3042
- const request = indexedDB.open(dbName, 2);
3070
+ const request = indexedDB.open(dbName, 3);
3043
3071
  request.onupgradeneeded = () => {
3044
3072
  const db = request.result;
3045
3073
  if (!db.objectStoreNames.contains(storeName)) {
@@ -3052,6 +3080,7 @@ var openDatabase = (dbName, storeName) => {
3052
3080
  utStore.createIndex("baseUrl", "baseUrl", { unique: false });
3053
3081
  utStore.createIndex("sessionId", "sessionId", { unique: false });
3054
3082
  utStore.createIndex("client", "client", { unique: false });
3083
+ utStore.createIndex("provider", "provider", { unique: false });
3055
3084
  }
3056
3085
  if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
3057
3086
  db.createObjectStore("sdk_storage");
@@ -4672,6 +4701,29 @@ function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
4672
4701
  }
4673
4702
  return result;
4674
4703
  }
4704
+ function extractUsageFromResponseHeaders(headers) {
4705
+ const get = (name) => {
4706
+ if (headers instanceof Headers) return headers.get(name);
4707
+ const lower = name.toLowerCase();
4708
+ for (const [k, v] of Object.entries(headers)) {
4709
+ if (k.toLowerCase() === lower) return v;
4710
+ }
4711
+ return null;
4712
+ };
4713
+ const totalMsats = Number(get("X-Routstr-Cost-Msats"));
4714
+ if (!totalMsats || !Number.isFinite(totalMsats)) return null;
4715
+ return {
4716
+ promptTokens: 0,
4717
+ completionTokens: 0,
4718
+ totalTokens: 0,
4719
+ cost: Number(get("X-Routstr-Cost-Usd")) || 0,
4720
+ satsCost: totalMsats / 1e3,
4721
+ totalMsats,
4722
+ inputMsats: Number(get("X-Routstr-Input-Cost-Msats")) || 0,
4723
+ outputMsats: Number(get("X-Routstr-Output-Cost-Msats")) || 0,
4724
+ totalUsd: Number(get("X-Routstr-Cost-Usd")) || void 0
4725
+ };
4726
+ }
4675
4727
  function toUsageStats(usage) {
4676
4728
  if (!usage) return void 0;
4677
4729
  return {
@@ -4758,14 +4810,11 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
4758
4810
  }
4759
4811
  const usage = extractUsageFromSSEJson(data);
4760
4812
  if (usage) {
4761
- console.log("[routstr:sse] \u2192 usage detected:", usage);
4762
4813
  const merged = mergeUsage(capturedUsage, usage);
4763
4814
  if (hasUsageChanged(capturedUsage, merged)) {
4764
4815
  capturedUsage = merged;
4765
- console.log("[routstr:sse] \u2192 merged (changed):", merged);
4766
4816
  onUsage(merged);
4767
4817
  } else {
4768
- console.log("[routstr:sse] \u2192 merged (no change)");
4769
4818
  }
4770
4819
  }
4771
4820
  } catch {
@@ -4922,6 +4971,177 @@ function createSSEParserTransform(onUsage, onResponseId) {
4922
4971
  });
4923
4972
  }
4924
4973
 
4974
+ // client/TinfoilSecure.ts
4975
+ var TINFOIL_MODEL_PREFIX = "tinfoil-";
4976
+ var clientCache = /* @__PURE__ */ new Map();
4977
+ function isTinfoilModel(modelId) {
4978
+ return modelId.startsWith(TINFOIL_MODEL_PREFIX);
4979
+ }
4980
+ function getTinfoilUpstreamModelId(modelId) {
4981
+ return modelId.slice(TINFOIL_MODEL_PREFIX.length);
4982
+ }
4983
+ function normalizeBaseUrl5(baseUrl) {
4984
+ return baseUrl.replace(/\/+$/, "");
4985
+ }
4986
+ function cacheKey(options) {
4987
+ return JSON.stringify({
4988
+ baseUrl: options.baseUrl,
4989
+ attestationBundleURL: options.attestationBundleURL,
4990
+ enclaveURL: options.enclaveURL,
4991
+ configRepo: options.configRepo
4992
+ });
4993
+ }
4994
+ function envOrUndefined(name) {
4995
+ const maybeProcess = globalThis;
4996
+ const value = maybeProcess.process?.env?.[name];
4997
+ return value && value.trim() ? value.trim() : void 0;
4998
+ }
4999
+ function resolveOptions(options) {
5000
+ return {
5001
+ ...options,
5002
+ baseUrl: normalizeBaseUrl5(options.baseUrl),
5003
+ attestationBundleURL: options.attestationBundleURL ?? envOrUndefined("ROUTSTR_TINFOIL_ATTESTATION_BUNDLE_URL"),
5004
+ enclaveURL: options.enclaveURL ?? envOrUndefined("ROUTSTR_TINFOIL_ENCLAVE_URL"),
5005
+ configRepo: options.configRepo ?? envOrUndefined("ROUTSTR_TINFOIL_CONFIG_REPO")
5006
+ };
5007
+ }
5008
+ async function prepareTinfoilClient(options) {
5009
+ const resolved = resolveOptions(options);
5010
+ const key = cacheKey(resolved);
5011
+ let pending = clientCache.get(key);
5012
+ if (!pending) {
5013
+ pending = (async () => {
5014
+ const { SecureClient } = await import('tinfoil');
5015
+ const client = new SecureClient({
5016
+ // baseURL is the proxy/provider URL that receives the EHBP request.
5017
+ baseURL: resolved.baseUrl,
5018
+ // Leave undefined by default so tinfoil uses its public ATC. If set,
5019
+ // SecureClient will fetch `${attestationBundleURL}/attestation`.
5020
+ attestationBundleURL: resolved.attestationBundleURL,
5021
+ enclaveURL: resolved.enclaveURL,
5022
+ configRepo: resolved.configRepo,
5023
+ transport: "ehbp"
5024
+ });
5025
+ await client.ready();
5026
+ const verification = client.getVerificationDocument();
5027
+ return { client, verification };
5028
+ })();
5029
+ clientCache.set(key, pending);
5030
+ }
5031
+ try {
5032
+ return await pending;
5033
+ } catch (error) {
5034
+ clientCache.delete(key);
5035
+ throw error;
5036
+ }
5037
+ }
5038
+ function normalizeFetchArgs(input, init) {
5039
+ if (typeof input === "string") {
5040
+ return { url: input, init };
5041
+ }
5042
+ if (input instanceof URL) {
5043
+ return { url: input.toString(), init };
5044
+ }
5045
+ const cloned = input.clone();
5046
+ return {
5047
+ url: cloned.url,
5048
+ init: {
5049
+ method: cloned.method,
5050
+ headers: new Headers(cloned.headers),
5051
+ body: cloned.body ?? void 0,
5052
+ signal: cloned.signal,
5053
+ ...init
5054
+ }
5055
+ };
5056
+ }
5057
+ function isProblemJsonContentType(contentType) {
5058
+ const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
5059
+ return mediaType === "application/problem+json";
5060
+ }
5061
+ async function isEhbpKeyConfigMismatchResponse(response, protocol) {
5062
+ if (response.status !== 422) {
5063
+ return false;
5064
+ }
5065
+ if (!isProblemJsonContentType(response.headers.get("content-type"))) {
5066
+ return false;
5067
+ }
5068
+ try {
5069
+ const problem = await response.clone().json();
5070
+ return problem?.type === protocol.KEY_CONFIG_PROBLEM_TYPE;
5071
+ } catch {
5072
+ return false;
5073
+ }
5074
+ }
5075
+ async function fetchTinfoilEhbpOnce(context, options, normalized, ehbp) {
5076
+ const { Identity, PROTOCOL, decryptResponseWithToken, extractSessionRecoveryToken } = ehbp;
5077
+ const resolved = resolveOptions(options);
5078
+ const baseURL = context.client.getBaseURL() ?? resolved.baseUrl;
5079
+ const enclaveURL = context.client.getEnclaveURL();
5080
+ const baseOrigin = new URL(baseURL).origin;
5081
+ const allowedOrigins = /* @__PURE__ */ new Set([baseOrigin]);
5082
+ if (enclaveURL) {
5083
+ allowedOrigins.add(new URL(enclaveURL).origin);
5084
+ }
5085
+ const targetUrl = new URL(normalized.url, baseURL);
5086
+ if (!allowedOrigins.has(targetUrl.origin)) {
5087
+ throw new Error(
5088
+ `refusing to send Tinfoil request to ${targetUrl.origin}: client is bound to the verified enclave/proxy`
5089
+ );
5090
+ }
5091
+ const headers = new Headers(normalized.init?.headers);
5092
+ if (enclaveURL && new URL(enclaveURL).origin !== baseOrigin) {
5093
+ headers.set("X-Tinfoil-Enclave-Url", enclaveURL);
5094
+ }
5095
+ const method = normalized.init?.method ?? "GET";
5096
+ const body = normalized.init?.body ?? null;
5097
+ const serverIdentity = await Identity.fromPublicKeyHex(
5098
+ context.verification.hpkePublicKey
5099
+ );
5100
+ const request = new Request(targetUrl.toString(), {
5101
+ method,
5102
+ headers,
5103
+ body,
5104
+ duplex: "half"
5105
+ });
5106
+ const { request: encryptedRequest, context: requestContext } = await serverIdentity.encryptRequestWithContext(request);
5107
+ const response = await fetch(encryptedRequest);
5108
+ if (!requestContext) {
5109
+ return response;
5110
+ }
5111
+ if (await isEhbpKeyConfigMismatchResponse(response, PROTOCOL)) {
5112
+ throw new ehbp.KeyConfigMismatchError("EHBP key configuration mismatch");
5113
+ }
5114
+ if (!response.headers.get(PROTOCOL.RESPONSE_NONCE_HEADER)) {
5115
+ return response;
5116
+ }
5117
+ const token = await extractSessionRecoveryToken(requestContext);
5118
+ return await decryptResponseWithToken(response, token);
5119
+ }
5120
+ async function fetchTinfoilPreservingPlaintextErrors(options, input, init) {
5121
+ const context = await prepareTinfoilClient(options);
5122
+ const ehbp = await import('ehbp');
5123
+ const normalized = normalizeFetchArgs(input, init);
5124
+ try {
5125
+ return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
5126
+ } catch (error) {
5127
+ if (error instanceof ehbp.KeyConfigMismatchError) {
5128
+ context.client.reset();
5129
+ try {
5130
+ await context.client.ready();
5131
+ context.verification = context.client.getVerificationDocument();
5132
+ } catch (reattestError) {
5133
+ clientCache.delete(cacheKey(resolveOptions(options)));
5134
+ throw reattestError;
5135
+ }
5136
+ return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
5137
+ }
5138
+ throw error;
5139
+ }
5140
+ }
5141
+ function clearTinfoilClientCache() {
5142
+ clientCache.clear();
5143
+ }
5144
+
4925
5145
  // client/RoutstrClient.ts
4926
5146
  var TOPUP_MARGIN = 1.2;
4927
5147
  var RoutstrClient = class {
@@ -5107,11 +5327,6 @@ var RoutstrClient = class {
5107
5327
  );
5108
5328
  }
5109
5329
  }
5110
- const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = await this._spendToken({
5111
- mintUrl,
5112
- amount: requiredSats,
5113
- baseUrl
5114
- });
5115
5330
  let requestBody = body;
5116
5331
  if (body && typeof body === "object") {
5117
5332
  const bodyObj = body;
@@ -5120,7 +5335,36 @@ var RoutstrClient = class {
5120
5335
  }
5121
5336
  }
5122
5337
  const baseHeaders = this._buildBaseHeaders();
5123
- const requestHeaders = this._withAuthHeader(baseHeaders, token);
5338
+ const tinfoilEnabled = Boolean(modelId && isTinfoilModel(modelId));
5339
+ if (tinfoilEnabled) {
5340
+ this._log(
5341
+ "DEBUG",
5342
+ `[RoutstrClient] Attesting Tinfoil model ${modelId} before spend`
5343
+ );
5344
+ const { verification } = await prepareTinfoilClient({ baseUrl });
5345
+ this._log(
5346
+ "DEBUG",
5347
+ `[RoutstrClient] Tinfoil attestation passed, enclave=${verification.enclaveHost}, codeFingerprint=${verification.codeFingerprint.slice(0, 16)}...`
5348
+ );
5349
+ if (requestBody && typeof requestBody === "object" && modelId) {
5350
+ requestBody = {
5351
+ ...requestBody,
5352
+ model: getTinfoilUpstreamModelId(modelId)
5353
+ };
5354
+ }
5355
+ }
5356
+ const spendResult = await this._spendToken({
5357
+ mintUrl,
5358
+ amount: requiredSats,
5359
+ baseUrl
5360
+ });
5361
+ const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = spendResult;
5362
+ const finalHeaders = this._withAuthAndTinfoilHeaders(
5363
+ baseHeaders,
5364
+ token,
5365
+ tinfoilEnabled,
5366
+ modelId
5367
+ );
5124
5368
  const response = await this._makeRequest({
5125
5369
  path: requestPath,
5126
5370
  method,
@@ -5129,9 +5373,10 @@ var RoutstrClient = class {
5129
5373
  mintUrl,
5130
5374
  token,
5131
5375
  requiredSats,
5132
- headers: requestHeaders,
5376
+ headers: finalHeaders,
5133
5377
  baseHeaders,
5134
- selectedModel
5378
+ selectedModel,
5379
+ tinfoilEnabled
5135
5380
  });
5136
5381
  let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
5137
5382
  let initialTokenBalanceUnknown = tokenBalanceUnknown;
@@ -5219,7 +5464,7 @@ var RoutstrClient = class {
5219
5464
  * Make the API request with failover support
5220
5465
  */
5221
5466
  async _makeRequest(params) {
5222
- const { path, method, body, baseUrl, token, headers } = params;
5467
+ const { path, method, body, baseUrl, token, headers, tinfoilEnabled } = params;
5223
5468
  try {
5224
5469
  const url = `${baseUrl.replace(/\/$/, "")}${path}`;
5225
5470
  const requestBodyText = body === void 0 || method === "GET" ? void 0 : JSON.stringify(body);
@@ -5233,7 +5478,15 @@ var RoutstrClient = class {
5233
5478
  rawBody: requestBodyText
5234
5479
  });
5235
5480
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
5236
- const response = await fetch(url, {
5481
+ const response = tinfoilEnabled ? await fetchTinfoilPreservingPlaintextErrors(
5482
+ { baseUrl },
5483
+ url,
5484
+ {
5485
+ method,
5486
+ headers,
5487
+ body: requestBodyText
5488
+ }
5489
+ ) : await fetch(url, {
5237
5490
  method,
5238
5491
  headers,
5239
5492
  body: requestBodyText
@@ -5253,6 +5506,15 @@ var RoutstrClient = class {
5253
5506
  } catch (e) {
5254
5507
  bodyText = void 0;
5255
5508
  }
5509
+ this._log("ERROR", "[RoutstrClient] Upstream error response", {
5510
+ baseUrl,
5511
+ url,
5512
+ path,
5513
+ status: response.status,
5514
+ statusText: response.statusText,
5515
+ requestId,
5516
+ body: bodyText ?? "<unable to read response body>"
5517
+ });
5256
5518
  return await this._handleErrorResponse(
5257
5519
  params,
5258
5520
  token,
@@ -5283,6 +5545,9 @@ var RoutstrClient = class {
5283
5545
  throw error;
5284
5546
  }
5285
5547
  }
5548
+ /**
5549
+ * Store request details to a file in the reqs/ folder before fetch.
5550
+ */
5286
5551
  /**
5287
5552
  * Handle error responses with failover
5288
5553
  */
@@ -5434,7 +5699,12 @@ var RoutstrClient = class {
5434
5699
  return this._makeRequest({
5435
5700
  ...params,
5436
5701
  token: params.token,
5437
- headers: this._withAuthHeader(params.baseHeaders, params.token),
5702
+ headers: this._withAuthAndTinfoilHeaders(
5703
+ params.baseHeaders,
5704
+ params.token,
5705
+ params.tinfoilEnabled,
5706
+ params.selectedModel?.id
5707
+ ),
5438
5708
  retryCount: retryCount + 1
5439
5709
  });
5440
5710
  } else {
@@ -5495,7 +5765,12 @@ var RoutstrClient = class {
5495
5765
  return this._makeRequest({
5496
5766
  ...params,
5497
5767
  token: retryToken,
5498
- headers: this._withAuthHeader(params.baseHeaders, retryToken),
5768
+ headers: this._withAuthAndTinfoilHeaders(
5769
+ params.baseHeaders,
5770
+ retryToken,
5771
+ params.tinfoilEnabled,
5772
+ params.selectedModel?.id
5773
+ ),
5499
5774
  retryCount: retryCount + 1
5500
5775
  });
5501
5776
  } else {
@@ -5590,6 +5865,13 @@ var RoutstrClient = class {
5590
5865
  messagesForPricing,
5591
5866
  params.maxTokens
5592
5867
  );
5868
+ if (params.tinfoilEnabled) {
5869
+ this._log(
5870
+ "DEBUG",
5871
+ `[RoutstrClient] _handleErrorResponse: Attesting Tinfoil failover provider ${nextProvider} before spend`
5872
+ );
5873
+ await prepareTinfoilClient({ baseUrl: nextProvider });
5874
+ }
5593
5875
  this._log(
5594
5876
  "DEBUG",
5595
5877
  `[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
@@ -5608,7 +5890,12 @@ var RoutstrClient = class {
5608
5890
  selectedModel: newModel,
5609
5891
  token: spendResult.token,
5610
5892
  requiredSats: newRequiredSats,
5611
- headers: this._withAuthHeader(params.baseHeaders, spendResult.token),
5893
+ headers: this._withAuthAndTinfoilHeaders(
5894
+ params.baseHeaders,
5895
+ spendResult.token,
5896
+ params.tinfoilEnabled,
5897
+ newModel.id
5898
+ ),
5612
5899
  retryCount: 0
5613
5900
  });
5614
5901
  retryResponse.initialTokenBalanceInSats = spendResult.tokenBalanceUnit === "msat" ? spendResult.tokenBalance / 1e3 : spendResult.tokenBalance;
@@ -5675,10 +5962,10 @@ var RoutstrClient = class {
5675
5962
  if (latestTokenBalance !== void 0) {
5676
5963
  this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
5677
5964
  }
5678
- satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? 0;
5965
+ satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
5679
5966
  } catch (e) {
5680
5967
  this._log("WARN", "Could not get updated API key balance:", e);
5681
- satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? 0;
5968
+ satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
5682
5969
  }
5683
5970
  }
5684
5971
  await this._trackResponseUsage({
@@ -5695,6 +5982,15 @@ var RoutstrClient = class {
5695
5982
  })();
5696
5983
  return satsSpent;
5697
5984
  }
5985
+ /**
5986
+ * Extract sats cost from EHBP/Tinfoil response headers as a last-resort
5987
+ * fallback when neither balance delta nor SSE/body usage provides a cost.
5988
+ */
5989
+ _headerSatsCost(response) {
5990
+ if (!response) return void 0;
5991
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
5992
+ return headerUsage?.satsCost;
5993
+ }
5698
5994
  async _trackResponseUsage(params) {
5699
5995
  const {
5700
5996
  token,
@@ -5728,7 +6024,24 @@ var RoutstrClient = class {
5728
6024
  }
5729
6025
  }
5730
6026
  if (!usage) {
5731
- return;
6027
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
6028
+ if (headerUsage) {
6029
+ usage = headerUsage;
6030
+ } else {
6031
+ return;
6032
+ }
6033
+ } else {
6034
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
6035
+ if (headerUsage) {
6036
+ if (headerUsage.totalMsats) {
6037
+ usage.totalMsats = headerUsage.totalMsats;
6038
+ usage.satsCost = headerUsage.satsCost;
6039
+ }
6040
+ if (headerUsage.cost) usage.cost = headerUsage.cost;
6041
+ if (headerUsage.inputMsats) usage.inputMsats = headerUsage.inputMsats;
6042
+ if (headerUsage.outputMsats) usage.outputMsats = headerUsage.outputMsats;
6043
+ if (headerUsage.totalUsd) usage.totalUsd = headerUsage.totalUsd;
6044
+ }
5732
6045
  }
5733
6046
  const finalRequestId = requestId || "unknown";
5734
6047
  const store = this.sdkStore ?? await getDefaultSdkStore();
@@ -5925,6 +6238,19 @@ var RoutstrClient = class {
5925
6238
  }
5926
6239
  return nextHeaders;
5927
6240
  }
6241
+ /**
6242
+ * Attach auth headers and preserve the plaintext model hint required by the
6243
+ * Routstr proxy for Tinfoil/EHBP requests. EHBP encrypts the JSON body, so
6244
+ * retries/failover must not rebuild headers from baseHeaders alone or the
6245
+ * proxy cannot route/price the encrypted request.
6246
+ */
6247
+ _withAuthAndTinfoilHeaders(headers, token, tinfoilEnabled, modelId) {
6248
+ const nextHeaders = this._withAuthHeader(headers, token);
6249
+ if (tinfoilEnabled && modelId) {
6250
+ nextHeaders["X-Routstr-Model"] = modelId;
6251
+ }
6252
+ return nextHeaders;
6253
+ }
5928
6254
  };
5929
6255
 
5930
6256
  // client/StreamProcessor.ts
@@ -6517,7 +6843,7 @@ var ADDED_COLUMNS = [
6517
6843
  { name: "cache_creation_msats", type: "REAL" },
6518
6844
  { name: "remaining_balance_msats", type: "REAL" }
6519
6845
  ];
6520
- var normalizeBaseUrl5 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
6846
+ var normalizeBaseUrl6 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
6521
6847
  var isBun2 = () => {
6522
6848
  return typeof process.versions.bun !== "undefined";
6523
6849
  };
@@ -6556,7 +6882,7 @@ var buildWhereClause = (options = {}) => {
6556
6882
  }
6557
6883
  if (options.baseUrl) {
6558
6884
  clauses.push("base_url = ?");
6559
- params.push(normalizeBaseUrl5(options.baseUrl));
6885
+ params.push(normalizeBaseUrl6(options.baseUrl));
6560
6886
  }
6561
6887
  if (options.sessionId) {
6562
6888
  clauses.push("session_id = ?");
@@ -6652,7 +6978,7 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
6652
6978
  entry.id,
6653
6979
  entry.timestamp,
6654
6980
  entry.modelId,
6655
- normalizeBaseUrl5(entry.baseUrl),
6981
+ normalizeBaseUrl6(entry.baseUrl),
6656
6982
  entry.requestId,
6657
6983
  entry.cost,
6658
6984
  entry.satsCost,
@@ -6800,6 +7126,6 @@ var NodeModelManager = class extends ModelManager {
6800
7126
  };
6801
7127
  var createNodeModelManager = (adapter, config = {}) => new NodeModelManager(adapter, config);
6802
7128
 
6803
- export { BalanceManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, NodeModelManager as ModelManager, ModelNotFoundError, NoProvidersAvailableError, NodeModelManager, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, consoleLogger, createDiscoveryAdapterFromStore, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createNodeModelManager, createProviderRegistryFromDiscoveryAdapter, createProviderRegistryFromStore, createSSEParserTransform, createSdkStore, createShardedDiscoveryAdapter, createSqliteDriver, createSqliteUsageTrackingDriver, createStorageAdapterFromStore, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, getProviderEndpoints, inspectSSEWebStream, isOnionUrl, isTorContext, localStorageDriver, noopLogger, normalizeProviderUrl, routeRequests, setDefaultUsageTrackingDriver, toUsageStats };
7129
+ export { BalanceManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, NodeModelManager as ModelManager, ModelNotFoundError, NoProvidersAvailableError, NodeModelManager, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, clearTinfoilClientCache, consoleLogger, createDiscoveryAdapterFromStore, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createNodeModelManager, createProviderRegistryFromDiscoveryAdapter, createProviderRegistryFromStore, createSSEParserTransform, createSdkStore, createShardedDiscoveryAdapter, createSqliteDriver, createSqliteUsageTrackingDriver, createStorageAdapterFromStore, extractResponseId, extractUsageFromResponseBody, extractUsageFromResponseHeaders, extractUsageFromSSEJson, fetchAIResponse, fetchTinfoilPreservingPlaintextErrors, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, getProviderEndpoints, getTinfoilUpstreamModelId, inspectSSEWebStream, isOnionUrl, isTinfoilModel, isTorContext, localStorageDriver, noopLogger, normalizeProviderUrl, prepareTinfoilClient, routeRequests, setDefaultUsageTrackingDriver, toUsageStats };
6804
7130
  //# sourceMappingURL=node.mjs.map
6805
7131
  //# sourceMappingURL=node.mjs.map