@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/index.d.mts CHANGED
@@ -7,12 +7,13 @@ import { W as WalletAdapter, S as StorageAdapter, P as ProviderRegistry } from '
7
7
  export { A as ApiKeyEntry, C as ChildKeyEntry, R as RoutstrClientOptions, a as StreamingCallbacks, X as XCashuTokenEntry } from './interfaces-Csn8Uq04.mjs';
8
8
  export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.mjs';
9
9
  import { DebugLevel, ProviderManager, RequestResponseLogSink, RoutstrClient } from './client/index.mjs';
10
- export { AlertLevel, FetchAIResponseDeps, ModelProviderPrice, RequestResponseLogRequestInput, RouteRequestParams, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, UsageTrackingData, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, inspectSSEWebStream, toUsageStats } from './client/index.mjs';
10
+ export { AlertLevel, FetchAIResponseDeps, ModelProviderPrice, RequestResponseLogRequestInput, RouteRequestParams, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, TinfoilClientContext, TinfoilClientOptions, UsageTrackingData, clearTinfoilClientCache, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromResponseHeaders, extractUsageFromSSEJson, fetchAIResponse, fetchTinfoilPreservingPlaintextErrors, getTinfoilUpstreamModelId, inspectSSEWebStream, isTinfoilModel, prepareTinfoilClient, toUsageStats } from './client/index.mjs';
11
11
  export { SDK_STORAGE_KEYS, ShardedDiscoveryAdapterOptions, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromDiscoveryAdapter, createShardedDiscoveryAdapter, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, localStorageDriver, setDefaultUsageTrackingDriver } from './storage/index.mjs';
12
12
  import { c as UsageTrackingDriver, S as SdkStore } from './store-CuXwe5Rg.mjs';
13
13
  export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, a as StorageDriver, U as UsageAggregateRow, b as UsageGroupBy, d as UsageTrackingEntry, e as createDiscoveryAdapterFromStore, f as createProviderRegistryFromStore, g as createSdkStore, h as createStorageAdapterFromStore } from './store-CuXwe5Rg.mjs';
14
14
  import 'applesauce-core';
15
15
  import 'stream';
16
+ import 'tinfoil';
16
17
  import 'zustand/vanilla';
17
18
 
18
19
  /**
package/dist/index.d.ts CHANGED
@@ -7,12 +7,13 @@ import { W as WalletAdapter, S as StorageAdapter, P as ProviderRegistry } from '
7
7
  export { A as ApiKeyEntry, C as ChildKeyEntry, R as RoutstrClientOptions, a as StreamingCallbacks, X as XCashuTokenEntry } from './interfaces-C-DYd9Jy.js';
8
8
  export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.js';
9
9
  import { DebugLevel, ProviderManager, RequestResponseLogSink, RoutstrClient } from './client/index.js';
10
- export { AlertLevel, FetchAIResponseDeps, ModelProviderPrice, RequestResponseLogRequestInput, RouteRequestParams, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, UsageTrackingData, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, inspectSSEWebStream, toUsageStats } from './client/index.js';
10
+ export { AlertLevel, FetchAIResponseDeps, ModelProviderPrice, RequestResponseLogRequestInput, RouteRequestParams, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, TinfoilClientContext, TinfoilClientOptions, UsageTrackingData, clearTinfoilClientCache, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromResponseHeaders, extractUsageFromSSEJson, fetchAIResponse, fetchTinfoilPreservingPlaintextErrors, getTinfoilUpstreamModelId, inspectSSEWebStream, isTinfoilModel, prepareTinfoilClient, toUsageStats } from './client/index.js';
11
11
  export { SDK_STORAGE_KEYS, ShardedDiscoveryAdapterOptions, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromDiscoveryAdapter, createShardedDiscoveryAdapter, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, localStorageDriver, setDefaultUsageTrackingDriver } from './storage/index.js';
12
12
  import { c as UsageTrackingDriver, S as SdkStore } from './store-CAQLSbEj.js';
13
13
  export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, a as StorageDriver, U as UsageAggregateRow, b as UsageGroupBy, d as UsageTrackingEntry, e as createDiscoveryAdapterFromStore, f as createProviderRegistryFromStore, g as createSdkStore, h as createStorageAdapterFromStore } from './store-CAQLSbEj.js';
14
14
  import 'applesauce-core';
15
15
  import 'stream';
16
+ import 'tinfoil';
16
17
  import 'zustand/vanilla';
17
18
 
18
19
  /**
package/dist/index.js CHANGED
@@ -1860,15 +1860,27 @@ var BalanceManager = class _BalanceManager {
1860
1860
  clearTimeout(timeoutId);
1861
1861
  const requestId = response.headers.get("x-routstr-request-id") || void 0;
1862
1862
  if (!response.ok) {
1863
- const errorData = await response.json().catch(() => ({}));
1864
- this.logger.warn(
1865
- `fetchRefundToken: non-ok response for ${url} status=${response.status} statusText=${response.statusText}`,
1866
- errorData
1867
- );
1863
+ const responseBody = await response.text().catch(() => void 0);
1864
+ let errorData = {};
1865
+ if (responseBody) {
1866
+ try {
1867
+ errorData = JSON.parse(responseBody);
1868
+ } catch {
1869
+ errorData = {};
1870
+ }
1871
+ }
1872
+ this.logger.error("Upstream wallet refund error response", {
1873
+ baseUrl,
1874
+ url,
1875
+ status: response.status,
1876
+ statusText: response.statusText,
1877
+ requestId,
1878
+ body: responseBody ?? "<unable to read response body>"
1879
+ });
1868
1880
  return {
1869
1881
  success: false,
1870
1882
  requestId,
1871
- error: `API key refund failed: ${errorData?.detail || response.statusText}`
1883
+ error: `API key refund failed: ${errorData?.detail || responseBody || response.statusText}`
1872
1884
  };
1873
1885
  }
1874
1886
  const data = await response.json();
@@ -2204,11 +2216,27 @@ var BalanceManager = class _BalanceManager {
2204
2216
  clearTimeout(timeoutId);
2205
2217
  const requestId = response.headers.get("x-routstr-request-id") || void 0;
2206
2218
  if (!response.ok) {
2207
- const errorData = await response.json().catch(() => ({}));
2219
+ const responseBody = await response.text().catch(() => void 0);
2220
+ let errorData = {};
2221
+ if (responseBody) {
2222
+ try {
2223
+ errorData = JSON.parse(responseBody);
2224
+ } catch {
2225
+ errorData = {};
2226
+ }
2227
+ }
2228
+ this.logger.error("Upstream wallet topup error response", {
2229
+ baseUrl,
2230
+ url,
2231
+ status: response.status,
2232
+ statusText: response.statusText,
2233
+ requestId,
2234
+ body: responseBody ?? "<unable to read response body>"
2235
+ });
2208
2236
  return {
2209
2237
  success: false,
2210
2238
  requestId,
2211
- error: errorData?.detail || `Top up failed with status ${response.status}`
2239
+ error: errorData?.detail || responseBody || `Top up failed with status ${response.status}`
2212
2240
  };
2213
2241
  }
2214
2242
  return { success: true, requestId };
@@ -3041,7 +3069,7 @@ var openDatabase = (dbName, storeName) => {
3041
3069
  return Promise.reject(new Error("IndexedDB is not available"));
3042
3070
  }
3043
3071
  return new Promise((resolve, reject) => {
3044
- const request = indexedDB.open(dbName, 2);
3072
+ const request = indexedDB.open(dbName, 3);
3045
3073
  request.onupgradeneeded = () => {
3046
3074
  const db = request.result;
3047
3075
  if (!db.objectStoreNames.contains(storeName)) {
@@ -3054,6 +3082,7 @@ var openDatabase = (dbName, storeName) => {
3054
3082
  utStore.createIndex("baseUrl", "baseUrl", { unique: false });
3055
3083
  utStore.createIndex("sessionId", "sessionId", { unique: false });
3056
3084
  utStore.createIndex("client", "client", { unique: false });
3085
+ utStore.createIndex("provider", "provider", { unique: false });
3057
3086
  }
3058
3087
  if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
3059
3088
  db.createObjectStore("sdk_storage");
@@ -4616,6 +4645,29 @@ function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
4616
4645
  }
4617
4646
  return result;
4618
4647
  }
4648
+ function extractUsageFromResponseHeaders(headers) {
4649
+ const get = (name) => {
4650
+ if (headers instanceof Headers) return headers.get(name);
4651
+ const lower = name.toLowerCase();
4652
+ for (const [k, v] of Object.entries(headers)) {
4653
+ if (k.toLowerCase() === lower) return v;
4654
+ }
4655
+ return null;
4656
+ };
4657
+ const totalMsats = Number(get("X-Routstr-Cost-Msats"));
4658
+ if (!totalMsats || !Number.isFinite(totalMsats)) return null;
4659
+ return {
4660
+ promptTokens: 0,
4661
+ completionTokens: 0,
4662
+ totalTokens: 0,
4663
+ cost: Number(get("X-Routstr-Cost-Usd")) || 0,
4664
+ satsCost: totalMsats / 1e3,
4665
+ totalMsats,
4666
+ inputMsats: Number(get("X-Routstr-Input-Cost-Msats")) || 0,
4667
+ outputMsats: Number(get("X-Routstr-Output-Cost-Msats")) || 0,
4668
+ totalUsd: Number(get("X-Routstr-Cost-Usd")) || void 0
4669
+ };
4670
+ }
4619
4671
  function toUsageStats(usage) {
4620
4672
  if (!usage) return void 0;
4621
4673
  return {
@@ -4702,14 +4754,11 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
4702
4754
  }
4703
4755
  const usage = extractUsageFromSSEJson(data);
4704
4756
  if (usage) {
4705
- console.log("[routstr:sse] \u2192 usage detected:", usage);
4706
4757
  const merged = mergeUsage(capturedUsage, usage);
4707
4758
  if (hasUsageChanged(capturedUsage, merged)) {
4708
4759
  capturedUsage = merged;
4709
- console.log("[routstr:sse] \u2192 merged (changed):", merged);
4710
4760
  onUsage(merged);
4711
4761
  } else {
4712
- console.log("[routstr:sse] \u2192 merged (no change)");
4713
4762
  }
4714
4763
  }
4715
4764
  } catch {
@@ -4866,6 +4915,177 @@ function createSSEParserTransform(onUsage, onResponseId) {
4866
4915
  });
4867
4916
  }
4868
4917
 
4918
+ // client/TinfoilSecure.ts
4919
+ var TINFOIL_MODEL_PREFIX = "tinfoil-";
4920
+ var clientCache = /* @__PURE__ */ new Map();
4921
+ function isTinfoilModel(modelId) {
4922
+ return modelId.startsWith(TINFOIL_MODEL_PREFIX);
4923
+ }
4924
+ function getTinfoilUpstreamModelId(modelId) {
4925
+ return modelId.slice(TINFOIL_MODEL_PREFIX.length);
4926
+ }
4927
+ function normalizeBaseUrl5(baseUrl) {
4928
+ return baseUrl.replace(/\/+$/, "");
4929
+ }
4930
+ function cacheKey(options) {
4931
+ return JSON.stringify({
4932
+ baseUrl: options.baseUrl,
4933
+ attestationBundleURL: options.attestationBundleURL,
4934
+ enclaveURL: options.enclaveURL,
4935
+ configRepo: options.configRepo
4936
+ });
4937
+ }
4938
+ function envOrUndefined(name) {
4939
+ const maybeProcess = globalThis;
4940
+ const value = maybeProcess.process?.env?.[name];
4941
+ return value && value.trim() ? value.trim() : void 0;
4942
+ }
4943
+ function resolveOptions(options) {
4944
+ return {
4945
+ ...options,
4946
+ baseUrl: normalizeBaseUrl5(options.baseUrl),
4947
+ attestationBundleURL: options.attestationBundleURL ?? envOrUndefined("ROUTSTR_TINFOIL_ATTESTATION_BUNDLE_URL"),
4948
+ enclaveURL: options.enclaveURL ?? envOrUndefined("ROUTSTR_TINFOIL_ENCLAVE_URL"),
4949
+ configRepo: options.configRepo ?? envOrUndefined("ROUTSTR_TINFOIL_CONFIG_REPO")
4950
+ };
4951
+ }
4952
+ async function prepareTinfoilClient(options) {
4953
+ const resolved = resolveOptions(options);
4954
+ const key = cacheKey(resolved);
4955
+ let pending = clientCache.get(key);
4956
+ if (!pending) {
4957
+ pending = (async () => {
4958
+ const { SecureClient } = await import('tinfoil');
4959
+ const client = new SecureClient({
4960
+ // baseURL is the proxy/provider URL that receives the EHBP request.
4961
+ baseURL: resolved.baseUrl,
4962
+ // Leave undefined by default so tinfoil uses its public ATC. If set,
4963
+ // SecureClient will fetch `${attestationBundleURL}/attestation`.
4964
+ attestationBundleURL: resolved.attestationBundleURL,
4965
+ enclaveURL: resolved.enclaveURL,
4966
+ configRepo: resolved.configRepo,
4967
+ transport: "ehbp"
4968
+ });
4969
+ await client.ready();
4970
+ const verification = client.getVerificationDocument();
4971
+ return { client, verification };
4972
+ })();
4973
+ clientCache.set(key, pending);
4974
+ }
4975
+ try {
4976
+ return await pending;
4977
+ } catch (error) {
4978
+ clientCache.delete(key);
4979
+ throw error;
4980
+ }
4981
+ }
4982
+ function normalizeFetchArgs(input, init) {
4983
+ if (typeof input === "string") {
4984
+ return { url: input, init };
4985
+ }
4986
+ if (input instanceof URL) {
4987
+ return { url: input.toString(), init };
4988
+ }
4989
+ const cloned = input.clone();
4990
+ return {
4991
+ url: cloned.url,
4992
+ init: {
4993
+ method: cloned.method,
4994
+ headers: new Headers(cloned.headers),
4995
+ body: cloned.body ?? void 0,
4996
+ signal: cloned.signal,
4997
+ ...init
4998
+ }
4999
+ };
5000
+ }
5001
+ function isProblemJsonContentType(contentType) {
5002
+ const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
5003
+ return mediaType === "application/problem+json";
5004
+ }
5005
+ async function isEhbpKeyConfigMismatchResponse(response, protocol) {
5006
+ if (response.status !== 422) {
5007
+ return false;
5008
+ }
5009
+ if (!isProblemJsonContentType(response.headers.get("content-type"))) {
5010
+ return false;
5011
+ }
5012
+ try {
5013
+ const problem = await response.clone().json();
5014
+ return problem?.type === protocol.KEY_CONFIG_PROBLEM_TYPE;
5015
+ } catch {
5016
+ return false;
5017
+ }
5018
+ }
5019
+ async function fetchTinfoilEhbpOnce(context, options, normalized, ehbp) {
5020
+ const { Identity, PROTOCOL, decryptResponseWithToken, extractSessionRecoveryToken } = ehbp;
5021
+ const resolved = resolveOptions(options);
5022
+ const baseURL = context.client.getBaseURL() ?? resolved.baseUrl;
5023
+ const enclaveURL = context.client.getEnclaveURL();
5024
+ const baseOrigin = new URL(baseURL).origin;
5025
+ const allowedOrigins = /* @__PURE__ */ new Set([baseOrigin]);
5026
+ if (enclaveURL) {
5027
+ allowedOrigins.add(new URL(enclaveURL).origin);
5028
+ }
5029
+ const targetUrl = new URL(normalized.url, baseURL);
5030
+ if (!allowedOrigins.has(targetUrl.origin)) {
5031
+ throw new Error(
5032
+ `refusing to send Tinfoil request to ${targetUrl.origin}: client is bound to the verified enclave/proxy`
5033
+ );
5034
+ }
5035
+ const headers = new Headers(normalized.init?.headers);
5036
+ if (enclaveURL && new URL(enclaveURL).origin !== baseOrigin) {
5037
+ headers.set("X-Tinfoil-Enclave-Url", enclaveURL);
5038
+ }
5039
+ const method = normalized.init?.method ?? "GET";
5040
+ const body = normalized.init?.body ?? null;
5041
+ const serverIdentity = await Identity.fromPublicKeyHex(
5042
+ context.verification.hpkePublicKey
5043
+ );
5044
+ const request = new Request(targetUrl.toString(), {
5045
+ method,
5046
+ headers,
5047
+ body,
5048
+ duplex: "half"
5049
+ });
5050
+ const { request: encryptedRequest, context: requestContext } = await serverIdentity.encryptRequestWithContext(request);
5051
+ const response = await fetch(encryptedRequest);
5052
+ if (!requestContext) {
5053
+ return response;
5054
+ }
5055
+ if (await isEhbpKeyConfigMismatchResponse(response, PROTOCOL)) {
5056
+ throw new ehbp.KeyConfigMismatchError("EHBP key configuration mismatch");
5057
+ }
5058
+ if (!response.headers.get(PROTOCOL.RESPONSE_NONCE_HEADER)) {
5059
+ return response;
5060
+ }
5061
+ const token = await extractSessionRecoveryToken(requestContext);
5062
+ return await decryptResponseWithToken(response, token);
5063
+ }
5064
+ async function fetchTinfoilPreservingPlaintextErrors(options, input, init) {
5065
+ const context = await prepareTinfoilClient(options);
5066
+ const ehbp = await import('ehbp');
5067
+ const normalized = normalizeFetchArgs(input, init);
5068
+ try {
5069
+ return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
5070
+ } catch (error) {
5071
+ if (error instanceof ehbp.KeyConfigMismatchError) {
5072
+ context.client.reset();
5073
+ try {
5074
+ await context.client.ready();
5075
+ context.verification = context.client.getVerificationDocument();
5076
+ } catch (reattestError) {
5077
+ clientCache.delete(cacheKey(resolveOptions(options)));
5078
+ throw reattestError;
5079
+ }
5080
+ return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
5081
+ }
5082
+ throw error;
5083
+ }
5084
+ }
5085
+ function clearTinfoilClientCache() {
5086
+ clientCache.clear();
5087
+ }
5088
+
4869
5089
  // client/RoutstrClient.ts
4870
5090
  var TOPUP_MARGIN = 1.2;
4871
5091
  var RoutstrClient = class {
@@ -5051,11 +5271,6 @@ var RoutstrClient = class {
5051
5271
  );
5052
5272
  }
5053
5273
  }
5054
- const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = await this._spendToken({
5055
- mintUrl,
5056
- amount: requiredSats,
5057
- baseUrl
5058
- });
5059
5274
  let requestBody = body;
5060
5275
  if (body && typeof body === "object") {
5061
5276
  const bodyObj = body;
@@ -5064,7 +5279,36 @@ var RoutstrClient = class {
5064
5279
  }
5065
5280
  }
5066
5281
  const baseHeaders = this._buildBaseHeaders();
5067
- const requestHeaders = this._withAuthHeader(baseHeaders, token);
5282
+ const tinfoilEnabled = Boolean(modelId && isTinfoilModel(modelId));
5283
+ if (tinfoilEnabled) {
5284
+ this._log(
5285
+ "DEBUG",
5286
+ `[RoutstrClient] Attesting Tinfoil model ${modelId} before spend`
5287
+ );
5288
+ const { verification } = await prepareTinfoilClient({ baseUrl });
5289
+ this._log(
5290
+ "DEBUG",
5291
+ `[RoutstrClient] Tinfoil attestation passed, enclave=${verification.enclaveHost}, codeFingerprint=${verification.codeFingerprint.slice(0, 16)}...`
5292
+ );
5293
+ if (requestBody && typeof requestBody === "object" && modelId) {
5294
+ requestBody = {
5295
+ ...requestBody,
5296
+ model: getTinfoilUpstreamModelId(modelId)
5297
+ };
5298
+ }
5299
+ }
5300
+ const spendResult = await this._spendToken({
5301
+ mintUrl,
5302
+ amount: requiredSats,
5303
+ baseUrl
5304
+ });
5305
+ const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = spendResult;
5306
+ const finalHeaders = this._withAuthAndTinfoilHeaders(
5307
+ baseHeaders,
5308
+ token,
5309
+ tinfoilEnabled,
5310
+ modelId
5311
+ );
5068
5312
  const response = await this._makeRequest({
5069
5313
  path: requestPath,
5070
5314
  method,
@@ -5073,9 +5317,10 @@ var RoutstrClient = class {
5073
5317
  mintUrl,
5074
5318
  token,
5075
5319
  requiredSats,
5076
- headers: requestHeaders,
5320
+ headers: finalHeaders,
5077
5321
  baseHeaders,
5078
- selectedModel
5322
+ selectedModel,
5323
+ tinfoilEnabled
5079
5324
  });
5080
5325
  let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
5081
5326
  let initialTokenBalanceUnknown = tokenBalanceUnknown;
@@ -5163,7 +5408,7 @@ var RoutstrClient = class {
5163
5408
  * Make the API request with failover support
5164
5409
  */
5165
5410
  async _makeRequest(params) {
5166
- const { path, method, body, baseUrl, token, headers } = params;
5411
+ const { path, method, body, baseUrl, token, headers, tinfoilEnabled } = params;
5167
5412
  try {
5168
5413
  const url = `${baseUrl.replace(/\/$/, "")}${path}`;
5169
5414
  const requestBodyText = body === void 0 || method === "GET" ? void 0 : JSON.stringify(body);
@@ -5177,7 +5422,15 @@ var RoutstrClient = class {
5177
5422
  rawBody: requestBodyText
5178
5423
  });
5179
5424
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
5180
- const response = await fetch(url, {
5425
+ const response = tinfoilEnabled ? await fetchTinfoilPreservingPlaintextErrors(
5426
+ { baseUrl },
5427
+ url,
5428
+ {
5429
+ method,
5430
+ headers,
5431
+ body: requestBodyText
5432
+ }
5433
+ ) : await fetch(url, {
5181
5434
  method,
5182
5435
  headers,
5183
5436
  body: requestBodyText
@@ -5197,6 +5450,15 @@ var RoutstrClient = class {
5197
5450
  } catch (e) {
5198
5451
  bodyText = void 0;
5199
5452
  }
5453
+ this._log("ERROR", "[RoutstrClient] Upstream error response", {
5454
+ baseUrl,
5455
+ url,
5456
+ path,
5457
+ status: response.status,
5458
+ statusText: response.statusText,
5459
+ requestId,
5460
+ body: bodyText ?? "<unable to read response body>"
5461
+ });
5200
5462
  return await this._handleErrorResponse(
5201
5463
  params,
5202
5464
  token,
@@ -5227,6 +5489,9 @@ var RoutstrClient = class {
5227
5489
  throw error;
5228
5490
  }
5229
5491
  }
5492
+ /**
5493
+ * Store request details to a file in the reqs/ folder before fetch.
5494
+ */
5230
5495
  /**
5231
5496
  * Handle error responses with failover
5232
5497
  */
@@ -5378,7 +5643,12 @@ var RoutstrClient = class {
5378
5643
  return this._makeRequest({
5379
5644
  ...params,
5380
5645
  token: params.token,
5381
- headers: this._withAuthHeader(params.baseHeaders, params.token),
5646
+ headers: this._withAuthAndTinfoilHeaders(
5647
+ params.baseHeaders,
5648
+ params.token,
5649
+ params.tinfoilEnabled,
5650
+ params.selectedModel?.id
5651
+ ),
5382
5652
  retryCount: retryCount + 1
5383
5653
  });
5384
5654
  } else {
@@ -5439,7 +5709,12 @@ var RoutstrClient = class {
5439
5709
  return this._makeRequest({
5440
5710
  ...params,
5441
5711
  token: retryToken,
5442
- headers: this._withAuthHeader(params.baseHeaders, retryToken),
5712
+ headers: this._withAuthAndTinfoilHeaders(
5713
+ params.baseHeaders,
5714
+ retryToken,
5715
+ params.tinfoilEnabled,
5716
+ params.selectedModel?.id
5717
+ ),
5443
5718
  retryCount: retryCount + 1
5444
5719
  });
5445
5720
  } else {
@@ -5534,6 +5809,13 @@ var RoutstrClient = class {
5534
5809
  messagesForPricing,
5535
5810
  params.maxTokens
5536
5811
  );
5812
+ if (params.tinfoilEnabled) {
5813
+ this._log(
5814
+ "DEBUG",
5815
+ `[RoutstrClient] _handleErrorResponse: Attesting Tinfoil failover provider ${nextProvider} before spend`
5816
+ );
5817
+ await prepareTinfoilClient({ baseUrl: nextProvider });
5818
+ }
5537
5819
  this._log(
5538
5820
  "DEBUG",
5539
5821
  `[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
@@ -5552,7 +5834,12 @@ var RoutstrClient = class {
5552
5834
  selectedModel: newModel,
5553
5835
  token: spendResult.token,
5554
5836
  requiredSats: newRequiredSats,
5555
- headers: this._withAuthHeader(params.baseHeaders, spendResult.token),
5837
+ headers: this._withAuthAndTinfoilHeaders(
5838
+ params.baseHeaders,
5839
+ spendResult.token,
5840
+ params.tinfoilEnabled,
5841
+ newModel.id
5842
+ ),
5556
5843
  retryCount: 0
5557
5844
  });
5558
5845
  retryResponse.initialTokenBalanceInSats = spendResult.tokenBalanceUnit === "msat" ? spendResult.tokenBalance / 1e3 : spendResult.tokenBalance;
@@ -5619,10 +5906,10 @@ var RoutstrClient = class {
5619
5906
  if (latestTokenBalance !== void 0) {
5620
5907
  this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
5621
5908
  }
5622
- satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? 0;
5909
+ satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
5623
5910
  } catch (e) {
5624
5911
  this._log("WARN", "Could not get updated API key balance:", e);
5625
- satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? 0;
5912
+ satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
5626
5913
  }
5627
5914
  }
5628
5915
  await this._trackResponseUsage({
@@ -5639,6 +5926,15 @@ var RoutstrClient = class {
5639
5926
  })();
5640
5927
  return satsSpent;
5641
5928
  }
5929
+ /**
5930
+ * Extract sats cost from EHBP/Tinfoil response headers as a last-resort
5931
+ * fallback when neither balance delta nor SSE/body usage provides a cost.
5932
+ */
5933
+ _headerSatsCost(response) {
5934
+ if (!response) return void 0;
5935
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
5936
+ return headerUsage?.satsCost;
5937
+ }
5642
5938
  async _trackResponseUsage(params) {
5643
5939
  const {
5644
5940
  token,
@@ -5672,7 +5968,24 @@ var RoutstrClient = class {
5672
5968
  }
5673
5969
  }
5674
5970
  if (!usage) {
5675
- return;
5971
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
5972
+ if (headerUsage) {
5973
+ usage = headerUsage;
5974
+ } else {
5975
+ return;
5976
+ }
5977
+ } else {
5978
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
5979
+ if (headerUsage) {
5980
+ if (headerUsage.totalMsats) {
5981
+ usage.totalMsats = headerUsage.totalMsats;
5982
+ usage.satsCost = headerUsage.satsCost;
5983
+ }
5984
+ if (headerUsage.cost) usage.cost = headerUsage.cost;
5985
+ if (headerUsage.inputMsats) usage.inputMsats = headerUsage.inputMsats;
5986
+ if (headerUsage.outputMsats) usage.outputMsats = headerUsage.outputMsats;
5987
+ if (headerUsage.totalUsd) usage.totalUsd = headerUsage.totalUsd;
5988
+ }
5676
5989
  }
5677
5990
  const finalRequestId = requestId || "unknown";
5678
5991
  const store = this.sdkStore ?? await getDefaultSdkStore();
@@ -5869,6 +6182,19 @@ var RoutstrClient = class {
5869
6182
  }
5870
6183
  return nextHeaders;
5871
6184
  }
6185
+ /**
6186
+ * Attach auth headers and preserve the plaintext model hint required by the
6187
+ * Routstr proxy for Tinfoil/EHBP requests. EHBP encrypts the JSON body, so
6188
+ * retries/failover must not rebuild headers from baseHeaders alone or the
6189
+ * proxy cannot route/price the encrypted request.
6190
+ */
6191
+ _withAuthAndTinfoilHeaders(headers, token, tinfoilEnabled, modelId) {
6192
+ const nextHeaders = this._withAuthHeader(headers, token);
6193
+ if (tinfoilEnabled && modelId) {
6194
+ nextHeaders["X-Routstr-Model"] = modelId;
6195
+ }
6196
+ return nextHeaders;
6197
+ }
5872
6198
  };
5873
6199
 
5874
6200
  // client/StreamProcessor.ts
@@ -6376,6 +6702,7 @@ exports.SDK_STORAGE_KEYS = SDK_STORAGE_KEYS;
6376
6702
  exports.StreamProcessor = StreamProcessor;
6377
6703
  exports.StreamingError = StreamingError;
6378
6704
  exports.TokenOperationError = TokenOperationError;
6705
+ exports.clearTinfoilClientCache = clearTinfoilClientCache;
6379
6706
  exports.consoleLogger = consoleLogger;
6380
6707
  exports.createDiscoveryAdapterFromStore = createDiscoveryAdapterFromStore;
6381
6708
  exports.createIndexedDBDriver = createIndexedDBDriver;
@@ -6390,8 +6717,10 @@ exports.createShardedDiscoveryAdapter = createShardedDiscoveryAdapter;
6390
6717
  exports.createStorageAdapterFromStore = createStorageAdapterFromStore;
6391
6718
  exports.extractResponseId = extractResponseId;
6392
6719
  exports.extractUsageFromResponseBody = extractUsageFromResponseBody;
6720
+ exports.extractUsageFromResponseHeaders = extractUsageFromResponseHeaders;
6393
6721
  exports.extractUsageFromSSEJson = extractUsageFromSSEJson;
6394
6722
  exports.fetchAIResponse = fetchAIResponse;
6723
+ exports.fetchTinfoilPreservingPlaintextErrors = fetchTinfoilPreservingPlaintextErrors;
6395
6724
  exports.filterBaseUrlsForTor = filterBaseUrlsForTor;
6396
6725
  exports.getDefaultDiscoveryAdapter = getDefaultDiscoveryAdapter;
6397
6726
  exports.getDefaultProviderRegistry = getDefaultProviderRegistry;
@@ -6400,12 +6729,15 @@ exports.getDefaultSdkStore = getDefaultSdkStore;
6400
6729
  exports.getDefaultStorageAdapter = getDefaultStorageAdapter;
6401
6730
  exports.getDefaultUsageTrackingDriver = getDefaultUsageTrackingDriver;
6402
6731
  exports.getProviderEndpoints = getProviderEndpoints;
6732
+ exports.getTinfoilUpstreamModelId = getTinfoilUpstreamModelId;
6403
6733
  exports.inspectSSEWebStream = inspectSSEWebStream;
6404
6734
  exports.isOnionUrl = isOnionUrl;
6735
+ exports.isTinfoilModel = isTinfoilModel;
6405
6736
  exports.isTorContext = isTorContext;
6406
6737
  exports.localStorageDriver = localStorageDriver;
6407
6738
  exports.noopLogger = noopLogger;
6408
6739
  exports.normalizeProviderUrl = normalizeProviderUrl;
6740
+ exports.prepareTinfoilClient = prepareTinfoilClient;
6409
6741
  exports.routeRequests = routeRequests;
6410
6742
  exports.setDefaultUsageTrackingDriver = setDefaultUsageTrackingDriver;
6411
6743
  exports.toUsageStats = toUsageStats;