@routstr/sdk 0.3.11 → 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 (47) hide show
  1. package/dist/browser.d.mts +2 -1
  2. package/dist/browser.d.ts +2 -1
  3. package/dist/browser.js +360 -28
  4. package/dist/browser.js.map +1 -1
  5. package/dist/browser.mjs +355 -29
  6. package/dist/browser.mjs.map +1 -1
  7. package/dist/bun.d.mts +2 -1
  8. package/dist/bun.d.ts +2 -1
  9. package/dist/bun.js +367 -31
  10. package/dist/bun.js.map +1 -1
  11. package/dist/bun.mjs +362 -32
  12. package/dist/bun.mjs.map +1 -1
  13. package/dist/client/index.d.mts +85 -1
  14. package/dist/client/index.d.ts +85 -1
  15. package/dist/client/index.js +358 -27
  16. package/dist/client/index.js.map +1 -1
  17. package/dist/client/index.mjs +353 -28
  18. package/dist/client/index.mjs.map +1 -1
  19. package/dist/index.d.mts +2 -1
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.js +360 -28
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +355 -29
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/node.d.mts +2 -1
  26. package/dist/node.d.ts +2 -1
  27. package/dist/node.js +367 -31
  28. package/dist/node.js.map +1 -1
  29. package/dist/node.mjs +362 -32
  30. package/dist/node.mjs.map +1 -1
  31. package/dist/storage/bun.js +6 -1
  32. package/dist/storage/bun.js.map +1 -1
  33. package/dist/storage/bun.mjs +6 -1
  34. package/dist/storage/bun.mjs.map +1 -1
  35. package/dist/storage/index.js +2 -1
  36. package/dist/storage/index.js.map +1 -1
  37. package/dist/storage/index.mjs +2 -1
  38. package/dist/storage/index.mjs.map +1 -1
  39. package/dist/storage/node.js +6 -1
  40. package/dist/storage/node.js.map +1 -1
  41. package/dist/storage/node.mjs +6 -1
  42. package/dist/storage/node.mjs.map +1 -1
  43. package/dist/wallet/index.js +36 -8
  44. package/dist/wallet/index.js.map +1 -1
  45. package/dist/wallet/index.mjs +36 -8
  46. package/dist/wallet/index.mjs.map +1 -1
  47. package/package.json +9 -3
package/dist/node.d.mts CHANGED
@@ -5,12 +5,13 @@ import { ModelManager, ModelManagerConfig } from './discovery/index.mjs';
5
5
  export { MintDiscovery } from './discovery/index.mjs';
6
6
  export { A as ApiKeyEntry, C as ChildKeyEntry, P as ProviderRegistry, R as RoutstrClientOptions, S as StorageAdapter, a as StreamingCallbacks, W as WalletAdapter, X as XCashuTokenEntry } from './interfaces-Csn8Uq04.mjs';
7
7
  export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.mjs';
8
- export { AlertLevel, DebugLevel, FetchAIResponseDeps, ModelProviderPrice, ProviderManager, RequestResponseLogRequestInput, RequestResponseLogSink, RouteRequestParams, RoutstrClient, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, UsageTrackingData, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, inspectSSEWebStream, toUsageStats } from './client/index.mjs';
8
+ export { AlertLevel, DebugLevel, FetchAIResponseDeps, ModelProviderPrice, ProviderManager, RequestResponseLogRequestInput, RequestResponseLogSink, RouteRequestParams, RoutstrClient, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, TinfoilClientContext, TinfoilClientOptions, UsageTrackingData, clearTinfoilClientCache, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromResponseHeaders, extractUsageFromSSEJson, fetchAIResponse, fetchTinfoilPreservingPlaintextErrors, getTinfoilUpstreamModelId, inspectSSEWebStream, isTinfoilModel, prepareTinfoilClient, toUsageStats } from './client/index.mjs';
9
9
  export { SDK_STORAGE_KEYS, ShardedDiscoveryAdapterOptions, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromDiscoveryAdapter, createShardedDiscoveryAdapter, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, localStorageDriver, setDefaultUsageTrackingDriver } from './storage/index.mjs';
10
10
  export { SqliteDriverOptions, SqliteUsageTrackingDriverOptions, createSqliteDriver, createSqliteUsageTrackingDriver } from './storage/node.mjs';
11
11
  export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, S as SdkStore, a as StorageDriver, U as UsageAggregateRow, b as UsageGroupBy, c as UsageTrackingDriver, d as UsageTrackingEntry, e as createDiscoveryAdapterFromStore, f as createProviderRegistryFromStore, g as createSdkStore, h as createStorageAdapterFromStore } from './store-CuXwe5Rg.mjs';
12
12
  import 'applesauce-core';
13
13
  import 'stream';
14
+ import 'tinfoil';
14
15
  import 'zustand/vanilla';
15
16
 
16
17
  declare class NodeModelManager extends ModelManager {
package/dist/node.d.ts CHANGED
@@ -5,12 +5,13 @@ import { ModelManager, ModelManagerConfig } from './discovery/index.js';
5
5
  export { MintDiscovery } from './discovery/index.js';
6
6
  export { A as ApiKeyEntry, C as ChildKeyEntry, P as ProviderRegistry, R as RoutstrClientOptions, S as StorageAdapter, a as StreamingCallbacks, W as WalletAdapter, X as XCashuTokenEntry } from './interfaces-C-DYd9Jy.js';
7
7
  export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.js';
8
- export { AlertLevel, DebugLevel, FetchAIResponseDeps, ModelProviderPrice, ProviderManager, RequestResponseLogRequestInput, RequestResponseLogSink, RouteRequestParams, RoutstrClient, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, UsageTrackingData, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, inspectSSEWebStream, toUsageStats } from './client/index.js';
8
+ export { AlertLevel, DebugLevel, FetchAIResponseDeps, ModelProviderPrice, ProviderManager, RequestResponseLogRequestInput, RequestResponseLogSink, RouteRequestParams, RoutstrClient, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, TinfoilClientContext, TinfoilClientOptions, UsageTrackingData, clearTinfoilClientCache, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromResponseHeaders, extractUsageFromSSEJson, fetchAIResponse, fetchTinfoilPreservingPlaintextErrors, getTinfoilUpstreamModelId, inspectSSEWebStream, isTinfoilModel, prepareTinfoilClient, toUsageStats } from './client/index.js';
9
9
  export { SDK_STORAGE_KEYS, ShardedDiscoveryAdapterOptions, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromDiscoveryAdapter, createShardedDiscoveryAdapter, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, localStorageDriver, setDefaultUsageTrackingDriver } from './storage/index.js';
10
10
  export { SqliteDriverOptions, SqliteUsageTrackingDriverOptions, createSqliteDriver, createSqliteUsageTrackingDriver } from './storage/node.js';
11
11
  export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, S as SdkStore, a as StorageDriver, U as UsageAggregateRow, b as UsageGroupBy, c as UsageTrackingDriver, d as UsageTrackingEntry, e as createDiscoveryAdapterFromStore, f as createProviderRegistryFromStore, g as createSdkStore, h as createStorageAdapterFromStore } from './store-CAQLSbEj.js';
12
12
  import 'applesauce-core';
13
13
  import 'stream';
14
+ import 'tinfoil';
14
15
  import 'zustand/vanilla';
15
16
 
16
17
  declare class NodeModelManager extends ModelManager {
package/dist/node.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");
@@ -4674,6 +4703,29 @@ function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
4674
4703
  }
4675
4704
  return result;
4676
4705
  }
4706
+ function extractUsageFromResponseHeaders(headers) {
4707
+ const get = (name) => {
4708
+ if (headers instanceof Headers) return headers.get(name);
4709
+ const lower = name.toLowerCase();
4710
+ for (const [k, v] of Object.entries(headers)) {
4711
+ if (k.toLowerCase() === lower) return v;
4712
+ }
4713
+ return null;
4714
+ };
4715
+ const totalMsats = Number(get("X-Routstr-Cost-Msats"));
4716
+ if (!totalMsats || !Number.isFinite(totalMsats)) return null;
4717
+ return {
4718
+ promptTokens: 0,
4719
+ completionTokens: 0,
4720
+ totalTokens: 0,
4721
+ cost: Number(get("X-Routstr-Cost-Usd")) || 0,
4722
+ satsCost: totalMsats / 1e3,
4723
+ totalMsats,
4724
+ inputMsats: Number(get("X-Routstr-Input-Cost-Msats")) || 0,
4725
+ outputMsats: Number(get("X-Routstr-Output-Cost-Msats")) || 0,
4726
+ totalUsd: Number(get("X-Routstr-Cost-Usd")) || void 0
4727
+ };
4728
+ }
4677
4729
  function toUsageStats(usage) {
4678
4730
  if (!usage) return void 0;
4679
4731
  return {
@@ -4760,14 +4812,11 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
4760
4812
  }
4761
4813
  const usage = extractUsageFromSSEJson(data);
4762
4814
  if (usage) {
4763
- console.log("[routstr:sse] \u2192 usage detected:", usage);
4764
4815
  const merged = mergeUsage(capturedUsage, usage);
4765
4816
  if (hasUsageChanged(capturedUsage, merged)) {
4766
4817
  capturedUsage = merged;
4767
- console.log("[routstr:sse] \u2192 merged (changed):", merged);
4768
4818
  onUsage(merged);
4769
4819
  } else {
4770
- console.log("[routstr:sse] \u2192 merged (no change)");
4771
4820
  }
4772
4821
  }
4773
4822
  } catch {
@@ -4924,6 +4973,177 @@ function createSSEParserTransform(onUsage, onResponseId) {
4924
4973
  });
4925
4974
  }
4926
4975
 
4976
+ // client/TinfoilSecure.ts
4977
+ var TINFOIL_MODEL_PREFIX = "tinfoil-";
4978
+ var clientCache = /* @__PURE__ */ new Map();
4979
+ function isTinfoilModel(modelId) {
4980
+ return modelId.startsWith(TINFOIL_MODEL_PREFIX);
4981
+ }
4982
+ function getTinfoilUpstreamModelId(modelId) {
4983
+ return modelId.slice(TINFOIL_MODEL_PREFIX.length);
4984
+ }
4985
+ function normalizeBaseUrl5(baseUrl) {
4986
+ return baseUrl.replace(/\/+$/, "");
4987
+ }
4988
+ function cacheKey(options) {
4989
+ return JSON.stringify({
4990
+ baseUrl: options.baseUrl,
4991
+ attestationBundleURL: options.attestationBundleURL,
4992
+ enclaveURL: options.enclaveURL,
4993
+ configRepo: options.configRepo
4994
+ });
4995
+ }
4996
+ function envOrUndefined(name) {
4997
+ const maybeProcess = globalThis;
4998
+ const value = maybeProcess.process?.env?.[name];
4999
+ return value && value.trim() ? value.trim() : void 0;
5000
+ }
5001
+ function resolveOptions(options) {
5002
+ return {
5003
+ ...options,
5004
+ baseUrl: normalizeBaseUrl5(options.baseUrl),
5005
+ attestationBundleURL: options.attestationBundleURL ?? envOrUndefined("ROUTSTR_TINFOIL_ATTESTATION_BUNDLE_URL"),
5006
+ enclaveURL: options.enclaveURL ?? envOrUndefined("ROUTSTR_TINFOIL_ENCLAVE_URL"),
5007
+ configRepo: options.configRepo ?? envOrUndefined("ROUTSTR_TINFOIL_CONFIG_REPO")
5008
+ };
5009
+ }
5010
+ async function prepareTinfoilClient(options) {
5011
+ const resolved = resolveOptions(options);
5012
+ const key = cacheKey(resolved);
5013
+ let pending = clientCache.get(key);
5014
+ if (!pending) {
5015
+ pending = (async () => {
5016
+ const { SecureClient } = await import('tinfoil');
5017
+ const client = new SecureClient({
5018
+ // baseURL is the proxy/provider URL that receives the EHBP request.
5019
+ baseURL: resolved.baseUrl,
5020
+ // Leave undefined by default so tinfoil uses its public ATC. If set,
5021
+ // SecureClient will fetch `${attestationBundleURL}/attestation`.
5022
+ attestationBundleURL: resolved.attestationBundleURL,
5023
+ enclaveURL: resolved.enclaveURL,
5024
+ configRepo: resolved.configRepo,
5025
+ transport: "ehbp"
5026
+ });
5027
+ await client.ready();
5028
+ const verification = client.getVerificationDocument();
5029
+ return { client, verification };
5030
+ })();
5031
+ clientCache.set(key, pending);
5032
+ }
5033
+ try {
5034
+ return await pending;
5035
+ } catch (error) {
5036
+ clientCache.delete(key);
5037
+ throw error;
5038
+ }
5039
+ }
5040
+ function normalizeFetchArgs(input, init) {
5041
+ if (typeof input === "string") {
5042
+ return { url: input, init };
5043
+ }
5044
+ if (input instanceof URL) {
5045
+ return { url: input.toString(), init };
5046
+ }
5047
+ const cloned = input.clone();
5048
+ return {
5049
+ url: cloned.url,
5050
+ init: {
5051
+ method: cloned.method,
5052
+ headers: new Headers(cloned.headers),
5053
+ body: cloned.body ?? void 0,
5054
+ signal: cloned.signal,
5055
+ ...init
5056
+ }
5057
+ };
5058
+ }
5059
+ function isProblemJsonContentType(contentType) {
5060
+ const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
5061
+ return mediaType === "application/problem+json";
5062
+ }
5063
+ async function isEhbpKeyConfigMismatchResponse(response, protocol) {
5064
+ if (response.status !== 422) {
5065
+ return false;
5066
+ }
5067
+ if (!isProblemJsonContentType(response.headers.get("content-type"))) {
5068
+ return false;
5069
+ }
5070
+ try {
5071
+ const problem = await response.clone().json();
5072
+ return problem?.type === protocol.KEY_CONFIG_PROBLEM_TYPE;
5073
+ } catch {
5074
+ return false;
5075
+ }
5076
+ }
5077
+ async function fetchTinfoilEhbpOnce(context, options, normalized, ehbp) {
5078
+ const { Identity, PROTOCOL, decryptResponseWithToken, extractSessionRecoveryToken } = ehbp;
5079
+ const resolved = resolveOptions(options);
5080
+ const baseURL = context.client.getBaseURL() ?? resolved.baseUrl;
5081
+ const enclaveURL = context.client.getEnclaveURL();
5082
+ const baseOrigin = new URL(baseURL).origin;
5083
+ const allowedOrigins = /* @__PURE__ */ new Set([baseOrigin]);
5084
+ if (enclaveURL) {
5085
+ allowedOrigins.add(new URL(enclaveURL).origin);
5086
+ }
5087
+ const targetUrl = new URL(normalized.url, baseURL);
5088
+ if (!allowedOrigins.has(targetUrl.origin)) {
5089
+ throw new Error(
5090
+ `refusing to send Tinfoil request to ${targetUrl.origin}: client is bound to the verified enclave/proxy`
5091
+ );
5092
+ }
5093
+ const headers = new Headers(normalized.init?.headers);
5094
+ if (enclaveURL && new URL(enclaveURL).origin !== baseOrigin) {
5095
+ headers.set("X-Tinfoil-Enclave-Url", enclaveURL);
5096
+ }
5097
+ const method = normalized.init?.method ?? "GET";
5098
+ const body = normalized.init?.body ?? null;
5099
+ const serverIdentity = await Identity.fromPublicKeyHex(
5100
+ context.verification.hpkePublicKey
5101
+ );
5102
+ const request = new Request(targetUrl.toString(), {
5103
+ method,
5104
+ headers,
5105
+ body,
5106
+ duplex: "half"
5107
+ });
5108
+ const { request: encryptedRequest, context: requestContext } = await serverIdentity.encryptRequestWithContext(request);
5109
+ const response = await fetch(encryptedRequest);
5110
+ if (!requestContext) {
5111
+ return response;
5112
+ }
5113
+ if (await isEhbpKeyConfigMismatchResponse(response, PROTOCOL)) {
5114
+ throw new ehbp.KeyConfigMismatchError("EHBP key configuration mismatch");
5115
+ }
5116
+ if (!response.headers.get(PROTOCOL.RESPONSE_NONCE_HEADER)) {
5117
+ return response;
5118
+ }
5119
+ const token = await extractSessionRecoveryToken(requestContext);
5120
+ return await decryptResponseWithToken(response, token);
5121
+ }
5122
+ async function fetchTinfoilPreservingPlaintextErrors(options, input, init) {
5123
+ const context = await prepareTinfoilClient(options);
5124
+ const ehbp = await import('ehbp');
5125
+ const normalized = normalizeFetchArgs(input, init);
5126
+ try {
5127
+ return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
5128
+ } catch (error) {
5129
+ if (error instanceof ehbp.KeyConfigMismatchError) {
5130
+ context.client.reset();
5131
+ try {
5132
+ await context.client.ready();
5133
+ context.verification = context.client.getVerificationDocument();
5134
+ } catch (reattestError) {
5135
+ clientCache.delete(cacheKey(resolveOptions(options)));
5136
+ throw reattestError;
5137
+ }
5138
+ return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
5139
+ }
5140
+ throw error;
5141
+ }
5142
+ }
5143
+ function clearTinfoilClientCache() {
5144
+ clientCache.clear();
5145
+ }
5146
+
4927
5147
  // client/RoutstrClient.ts
4928
5148
  var TOPUP_MARGIN = 1.2;
4929
5149
  var RoutstrClient = class {
@@ -5109,11 +5329,6 @@ var RoutstrClient = class {
5109
5329
  );
5110
5330
  }
5111
5331
  }
5112
- const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = await this._spendToken({
5113
- mintUrl,
5114
- amount: requiredSats,
5115
- baseUrl
5116
- });
5117
5332
  let requestBody = body;
5118
5333
  if (body && typeof body === "object") {
5119
5334
  const bodyObj = body;
@@ -5122,7 +5337,36 @@ var RoutstrClient = class {
5122
5337
  }
5123
5338
  }
5124
5339
  const baseHeaders = this._buildBaseHeaders();
5125
- const requestHeaders = this._withAuthHeader(baseHeaders, token);
5340
+ const tinfoilEnabled = Boolean(modelId && isTinfoilModel(modelId));
5341
+ if (tinfoilEnabled) {
5342
+ this._log(
5343
+ "DEBUG",
5344
+ `[RoutstrClient] Attesting Tinfoil model ${modelId} before spend`
5345
+ );
5346
+ const { verification } = await prepareTinfoilClient({ baseUrl });
5347
+ this._log(
5348
+ "DEBUG",
5349
+ `[RoutstrClient] Tinfoil attestation passed, enclave=${verification.enclaveHost}, codeFingerprint=${verification.codeFingerprint.slice(0, 16)}...`
5350
+ );
5351
+ if (requestBody && typeof requestBody === "object" && modelId) {
5352
+ requestBody = {
5353
+ ...requestBody,
5354
+ model: getTinfoilUpstreamModelId(modelId)
5355
+ };
5356
+ }
5357
+ }
5358
+ const spendResult = await this._spendToken({
5359
+ mintUrl,
5360
+ amount: requiredSats,
5361
+ baseUrl
5362
+ });
5363
+ const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = spendResult;
5364
+ const finalHeaders = this._withAuthAndTinfoilHeaders(
5365
+ baseHeaders,
5366
+ token,
5367
+ tinfoilEnabled,
5368
+ modelId
5369
+ );
5126
5370
  const response = await this._makeRequest({
5127
5371
  path: requestPath,
5128
5372
  method,
@@ -5131,9 +5375,10 @@ var RoutstrClient = class {
5131
5375
  mintUrl,
5132
5376
  token,
5133
5377
  requiredSats,
5134
- headers: requestHeaders,
5378
+ headers: finalHeaders,
5135
5379
  baseHeaders,
5136
- selectedModel
5380
+ selectedModel,
5381
+ tinfoilEnabled
5137
5382
  });
5138
5383
  let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
5139
5384
  let initialTokenBalanceUnknown = tokenBalanceUnknown;
@@ -5221,7 +5466,7 @@ var RoutstrClient = class {
5221
5466
  * Make the API request with failover support
5222
5467
  */
5223
5468
  async _makeRequest(params) {
5224
- const { path, method, body, baseUrl, token, headers } = params;
5469
+ const { path, method, body, baseUrl, token, headers, tinfoilEnabled } = params;
5225
5470
  try {
5226
5471
  const url = `${baseUrl.replace(/\/$/, "")}${path}`;
5227
5472
  const requestBodyText = body === void 0 || method === "GET" ? void 0 : JSON.stringify(body);
@@ -5235,7 +5480,15 @@ var RoutstrClient = class {
5235
5480
  rawBody: requestBodyText
5236
5481
  });
5237
5482
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
5238
- const response = await fetch(url, {
5483
+ const response = tinfoilEnabled ? await fetchTinfoilPreservingPlaintextErrors(
5484
+ { baseUrl },
5485
+ url,
5486
+ {
5487
+ method,
5488
+ headers,
5489
+ body: requestBodyText
5490
+ }
5491
+ ) : await fetch(url, {
5239
5492
  method,
5240
5493
  headers,
5241
5494
  body: requestBodyText
@@ -5255,6 +5508,15 @@ var RoutstrClient = class {
5255
5508
  } catch (e) {
5256
5509
  bodyText = void 0;
5257
5510
  }
5511
+ this._log("ERROR", "[RoutstrClient] Upstream error response", {
5512
+ baseUrl,
5513
+ url,
5514
+ path,
5515
+ status: response.status,
5516
+ statusText: response.statusText,
5517
+ requestId,
5518
+ body: bodyText ?? "<unable to read response body>"
5519
+ });
5258
5520
  return await this._handleErrorResponse(
5259
5521
  params,
5260
5522
  token,
@@ -5285,6 +5547,9 @@ var RoutstrClient = class {
5285
5547
  throw error;
5286
5548
  }
5287
5549
  }
5550
+ /**
5551
+ * Store request details to a file in the reqs/ folder before fetch.
5552
+ */
5288
5553
  /**
5289
5554
  * Handle error responses with failover
5290
5555
  */
@@ -5436,7 +5701,12 @@ var RoutstrClient = class {
5436
5701
  return this._makeRequest({
5437
5702
  ...params,
5438
5703
  token: params.token,
5439
- headers: this._withAuthHeader(params.baseHeaders, params.token),
5704
+ headers: this._withAuthAndTinfoilHeaders(
5705
+ params.baseHeaders,
5706
+ params.token,
5707
+ params.tinfoilEnabled,
5708
+ params.selectedModel?.id
5709
+ ),
5440
5710
  retryCount: retryCount + 1
5441
5711
  });
5442
5712
  } else {
@@ -5497,7 +5767,12 @@ var RoutstrClient = class {
5497
5767
  return this._makeRequest({
5498
5768
  ...params,
5499
5769
  token: retryToken,
5500
- headers: this._withAuthHeader(params.baseHeaders, retryToken),
5770
+ headers: this._withAuthAndTinfoilHeaders(
5771
+ params.baseHeaders,
5772
+ retryToken,
5773
+ params.tinfoilEnabled,
5774
+ params.selectedModel?.id
5775
+ ),
5501
5776
  retryCount: retryCount + 1
5502
5777
  });
5503
5778
  } else {
@@ -5592,6 +5867,13 @@ var RoutstrClient = class {
5592
5867
  messagesForPricing,
5593
5868
  params.maxTokens
5594
5869
  );
5870
+ if (params.tinfoilEnabled) {
5871
+ this._log(
5872
+ "DEBUG",
5873
+ `[RoutstrClient] _handleErrorResponse: Attesting Tinfoil failover provider ${nextProvider} before spend`
5874
+ );
5875
+ await prepareTinfoilClient({ baseUrl: nextProvider });
5876
+ }
5595
5877
  this._log(
5596
5878
  "DEBUG",
5597
5879
  `[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
@@ -5610,7 +5892,12 @@ var RoutstrClient = class {
5610
5892
  selectedModel: newModel,
5611
5893
  token: spendResult.token,
5612
5894
  requiredSats: newRequiredSats,
5613
- headers: this._withAuthHeader(params.baseHeaders, spendResult.token),
5895
+ headers: this._withAuthAndTinfoilHeaders(
5896
+ params.baseHeaders,
5897
+ spendResult.token,
5898
+ params.tinfoilEnabled,
5899
+ newModel.id
5900
+ ),
5614
5901
  retryCount: 0
5615
5902
  });
5616
5903
  retryResponse.initialTokenBalanceInSats = spendResult.tokenBalanceUnit === "msat" ? spendResult.tokenBalance / 1e3 : spendResult.tokenBalance;
@@ -5677,10 +5964,10 @@ var RoutstrClient = class {
5677
5964
  if (latestTokenBalance !== void 0) {
5678
5965
  this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
5679
5966
  }
5680
- satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? 0;
5967
+ satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
5681
5968
  } catch (e) {
5682
5969
  this._log("WARN", "Could not get updated API key balance:", e);
5683
- satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? 0;
5970
+ satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
5684
5971
  }
5685
5972
  }
5686
5973
  await this._trackResponseUsage({
@@ -5697,6 +5984,15 @@ var RoutstrClient = class {
5697
5984
  })();
5698
5985
  return satsSpent;
5699
5986
  }
5987
+ /**
5988
+ * Extract sats cost from EHBP/Tinfoil response headers as a last-resort
5989
+ * fallback when neither balance delta nor SSE/body usage provides a cost.
5990
+ */
5991
+ _headerSatsCost(response) {
5992
+ if (!response) return void 0;
5993
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
5994
+ return headerUsage?.satsCost;
5995
+ }
5700
5996
  async _trackResponseUsage(params) {
5701
5997
  const {
5702
5998
  token,
@@ -5730,7 +6026,24 @@ var RoutstrClient = class {
5730
6026
  }
5731
6027
  }
5732
6028
  if (!usage) {
5733
- return;
6029
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
6030
+ if (headerUsage) {
6031
+ usage = headerUsage;
6032
+ } else {
6033
+ return;
6034
+ }
6035
+ } else {
6036
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
6037
+ if (headerUsage) {
6038
+ if (headerUsage.totalMsats) {
6039
+ usage.totalMsats = headerUsage.totalMsats;
6040
+ usage.satsCost = headerUsage.satsCost;
6041
+ }
6042
+ if (headerUsage.cost) usage.cost = headerUsage.cost;
6043
+ if (headerUsage.inputMsats) usage.inputMsats = headerUsage.inputMsats;
6044
+ if (headerUsage.outputMsats) usage.outputMsats = headerUsage.outputMsats;
6045
+ if (headerUsage.totalUsd) usage.totalUsd = headerUsage.totalUsd;
6046
+ }
5734
6047
  }
5735
6048
  const finalRequestId = requestId || "unknown";
5736
6049
  const store = this.sdkStore ?? await getDefaultSdkStore();
@@ -5927,6 +6240,19 @@ var RoutstrClient = class {
5927
6240
  }
5928
6241
  return nextHeaders;
5929
6242
  }
6243
+ /**
6244
+ * Attach auth headers and preserve the plaintext model hint required by the
6245
+ * Routstr proxy for Tinfoil/EHBP requests. EHBP encrypts the JSON body, so
6246
+ * retries/failover must not rebuild headers from baseHeaders alone or the
6247
+ * proxy cannot route/price the encrypted request.
6248
+ */
6249
+ _withAuthAndTinfoilHeaders(headers, token, tinfoilEnabled, modelId) {
6250
+ const nextHeaders = this._withAuthHeader(headers, token);
6251
+ if (tinfoilEnabled && modelId) {
6252
+ nextHeaders["X-Routstr-Model"] = modelId;
6253
+ }
6254
+ return nextHeaders;
6255
+ }
5930
6256
  };
5931
6257
 
5932
6258
  // client/StreamProcessor.ts
@@ -6448,6 +6774,8 @@ var createSqliteDriver = (options = {}) => {
6448
6774
  const initDb = async () => {
6449
6775
  if (!db) {
6450
6776
  db = await loadDatabase(dbPath);
6777
+ db.exec("PRAGMA journal_mode = WAL");
6778
+ db.exec("PRAGMA busy_timeout = 5000");
6451
6779
  db.exec(
6452
6780
  `CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
6453
6781
  );
@@ -6517,7 +6845,7 @@ var ADDED_COLUMNS = [
6517
6845
  { name: "cache_creation_msats", type: "REAL" },
6518
6846
  { name: "remaining_balance_msats", type: "REAL" }
6519
6847
  ];
6520
- var normalizeBaseUrl5 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
6848
+ var normalizeBaseUrl6 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
6521
6849
  var isBun2 = () => {
6522
6850
  return typeof process.versions.bun !== "undefined";
6523
6851
  };
@@ -6556,7 +6884,7 @@ var buildWhereClause = (options = {}) => {
6556
6884
  }
6557
6885
  if (options.baseUrl) {
6558
6886
  clauses.push("base_url = ?");
6559
- params.push(normalizeBaseUrl5(options.baseUrl));
6887
+ params.push(normalizeBaseUrl6(options.baseUrl));
6560
6888
  }
6561
6889
  if (options.sessionId) {
6562
6890
  clauses.push("session_id = ?");
@@ -6590,6 +6918,8 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
6590
6918
  const initDb = async () => {
6591
6919
  if (!db) {
6592
6920
  db = await loadDatabase2(dbPath);
6921
+ db.exec("PRAGMA journal_mode = WAL");
6922
+ db.exec("PRAGMA busy_timeout = 5000");
6593
6923
  db.exec(`
6594
6924
  CREATE TABLE IF NOT EXISTS ${tableName} (
6595
6925
  id TEXT PRIMARY KEY,
@@ -6650,7 +6980,7 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
6650
6980
  entry.id,
6651
6981
  entry.timestamp,
6652
6982
  entry.modelId,
6653
- normalizeBaseUrl5(entry.baseUrl),
6983
+ normalizeBaseUrl6(entry.baseUrl),
6654
6984
  entry.requestId,
6655
6985
  entry.cost,
6656
6986
  entry.satsCost,
@@ -6817,6 +7147,7 @@ exports.SDK_STORAGE_KEYS = SDK_STORAGE_KEYS;
6817
7147
  exports.StreamProcessor = StreamProcessor;
6818
7148
  exports.StreamingError = StreamingError;
6819
7149
  exports.TokenOperationError = TokenOperationError;
7150
+ exports.clearTinfoilClientCache = clearTinfoilClientCache;
6820
7151
  exports.consoleLogger = consoleLogger;
6821
7152
  exports.createDiscoveryAdapterFromStore = createDiscoveryAdapterFromStore;
6822
7153
  exports.createIndexedDBDriver = createIndexedDBDriver;
@@ -6834,8 +7165,10 @@ exports.createSqliteUsageTrackingDriver = createSqliteUsageTrackingDriver;
6834
7165
  exports.createStorageAdapterFromStore = createStorageAdapterFromStore;
6835
7166
  exports.extractResponseId = extractResponseId;
6836
7167
  exports.extractUsageFromResponseBody = extractUsageFromResponseBody;
7168
+ exports.extractUsageFromResponseHeaders = extractUsageFromResponseHeaders;
6837
7169
  exports.extractUsageFromSSEJson = extractUsageFromSSEJson;
6838
7170
  exports.fetchAIResponse = fetchAIResponse;
7171
+ exports.fetchTinfoilPreservingPlaintextErrors = fetchTinfoilPreservingPlaintextErrors;
6839
7172
  exports.filterBaseUrlsForTor = filterBaseUrlsForTor;
6840
7173
  exports.getDefaultDiscoveryAdapter = getDefaultDiscoveryAdapter;
6841
7174
  exports.getDefaultProviderRegistry = getDefaultProviderRegistry;
@@ -6844,12 +7177,15 @@ exports.getDefaultSdkStore = getDefaultSdkStore;
6844
7177
  exports.getDefaultStorageAdapter = getDefaultStorageAdapter;
6845
7178
  exports.getDefaultUsageTrackingDriver = getDefaultUsageTrackingDriver;
6846
7179
  exports.getProviderEndpoints = getProviderEndpoints;
7180
+ exports.getTinfoilUpstreamModelId = getTinfoilUpstreamModelId;
6847
7181
  exports.inspectSSEWebStream = inspectSSEWebStream;
6848
7182
  exports.isOnionUrl = isOnionUrl;
7183
+ exports.isTinfoilModel = isTinfoilModel;
6849
7184
  exports.isTorContext = isTorContext;
6850
7185
  exports.localStorageDriver = localStorageDriver;
6851
7186
  exports.noopLogger = noopLogger;
6852
7187
  exports.normalizeProviderUrl = normalizeProviderUrl;
7188
+ exports.prepareTinfoilClient = prepareTinfoilClient;
6853
7189
  exports.routeRequests = routeRequests;
6854
7190
  exports.setDefaultUsageTrackingDriver = setDefaultUsageTrackingDriver;
6855
7191
  exports.toUsageStats = toUsageStats;