@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/bun.d.mts CHANGED
@@ -7,13 +7,14 @@ import { ModelManager, ModelManagerConfig } from './discovery/index.mjs';
7
7
  export { MintDiscovery } from './discovery/index.mjs';
8
8
  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';
9
9
  export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.mjs';
10
- 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';
10
+ 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';
11
11
  import { B as BunSqliteUsageTrackingDriverOptions } from './bunSqlite-Bro9efsl.mjs';
12
12
  export { c as createBunSqliteDriver, a as createBunSqliteUsageTrackingDriverWithDatabase } from './bunSqlite-Bro9efsl.mjs';
13
13
  import { c as UsageTrackingDriver, a as StorageDriver } from './store-CuXwe5Rg.mjs';
14
14
  export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, S as SdkStore, 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';
15
15
  import 'applesauce-core';
16
16
  import 'stream';
17
+ import 'tinfoil';
17
18
  import 'zustand/vanilla';
18
19
 
19
20
  declare class BunModelManager extends ModelManager {
package/dist/bun.d.ts CHANGED
@@ -7,13 +7,14 @@ import { ModelManager, ModelManagerConfig } from './discovery/index.js';
7
7
  export { MintDiscovery } from './discovery/index.js';
8
8
  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';
9
9
  export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.js';
10
- 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';
10
+ 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';
11
11
  import { B as BunSqliteUsageTrackingDriverOptions } from './bunSqlite-BmXWNc25.js';
12
12
  export { c as createBunSqliteDriver, a as createBunSqliteUsageTrackingDriverWithDatabase } from './bunSqlite-BmXWNc25.js';
13
13
  import { c as UsageTrackingDriver, a as StorageDriver } from './store-CAQLSbEj.js';
14
14
  export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, S as SdkStore, 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';
15
15
  import 'applesauce-core';
16
16
  import 'stream';
17
+ import 'tinfoil';
17
18
  import 'zustand/vanilla';
18
19
 
19
20
  declare class BunModelManager extends ModelManager {
package/dist/bun.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
@@ -6481,7 +6807,7 @@ var ADDED_COLUMNS = [
6481
6807
  { name: "cache_creation_msats", type: "REAL" },
6482
6808
  { name: "remaining_balance_msats", type: "REAL" }
6483
6809
  ];
6484
- var normalizeBaseUrl5 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
6810
+ var normalizeBaseUrl6 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
6485
6811
  var buildWhereClause = (options = {}) => {
6486
6812
  const clauses = [];
6487
6813
  const params = [];
@@ -6499,7 +6825,7 @@ var buildWhereClause = (options = {}) => {
6499
6825
  }
6500
6826
  if (options.baseUrl) {
6501
6827
  clauses.push("base_url = ?");
6502
- params.push(normalizeBaseUrl5(options.baseUrl));
6828
+ params.push(normalizeBaseUrl6(options.baseUrl));
6503
6829
  }
6504
6830
  if (options.sessionId) {
6505
6831
  clauses.push("session_id = ?");
@@ -6583,7 +6909,7 @@ var createBunSqliteUsageTrackingDriver = (options = {}) => {
6583
6909
  entry.id,
6584
6910
  entry.timestamp,
6585
6911
  entry.modelId,
6586
- normalizeBaseUrl5(entry.baseUrl),
6912
+ normalizeBaseUrl6(entry.baseUrl),
6587
6913
  entry.requestId,
6588
6914
  entry.cost,
6589
6915
  entry.satsCost,
@@ -6753,6 +7079,7 @@ exports.SDK_STORAGE_KEYS = SDK_STORAGE_KEYS;
6753
7079
  exports.StreamProcessor = StreamProcessor;
6754
7080
  exports.StreamingError = StreamingError;
6755
7081
  exports.TokenOperationError = TokenOperationError;
7082
+ exports.clearTinfoilClientCache = clearTinfoilClientCache;
6756
7083
  exports.consoleLogger = consoleLogger;
6757
7084
  exports.createBunModelManager = createBunModelManager;
6758
7085
  exports.createBunSqliteDriver = createBunSqliteDriver;
@@ -6772,8 +7099,10 @@ exports.createShardedDiscoveryAdapter = createShardedDiscoveryAdapter;
6772
7099
  exports.createStorageAdapterFromStore = createStorageAdapterFromStore;
6773
7100
  exports.extractResponseId = extractResponseId;
6774
7101
  exports.extractUsageFromResponseBody = extractUsageFromResponseBody;
7102
+ exports.extractUsageFromResponseHeaders = extractUsageFromResponseHeaders;
6775
7103
  exports.extractUsageFromSSEJson = extractUsageFromSSEJson;
6776
7104
  exports.fetchAIResponse = fetchAIResponse;
7105
+ exports.fetchTinfoilPreservingPlaintextErrors = fetchTinfoilPreservingPlaintextErrors;
6777
7106
  exports.filterBaseUrlsForTor = filterBaseUrlsForTor;
6778
7107
  exports.getDefaultDiscoveryAdapter = getDefaultDiscoveryAdapter;
6779
7108
  exports.getDefaultProviderRegistry = getDefaultProviderRegistry;
@@ -6782,12 +7111,15 @@ exports.getDefaultSdkStore = getDefaultSdkStore;
6782
7111
  exports.getDefaultStorageAdapter = getDefaultStorageAdapter;
6783
7112
  exports.getDefaultUsageTrackingDriver = getDefaultUsageTrackingDriver;
6784
7113
  exports.getProviderEndpoints = getProviderEndpoints;
7114
+ exports.getTinfoilUpstreamModelId = getTinfoilUpstreamModelId;
6785
7115
  exports.inspectSSEWebStream = inspectSSEWebStream;
6786
7116
  exports.isOnionUrl = isOnionUrl;
7117
+ exports.isTinfoilModel = isTinfoilModel;
6787
7118
  exports.isTorContext = isTorContext;
6788
7119
  exports.localStorageDriver = localStorageDriver;
6789
7120
  exports.noopLogger = noopLogger;
6790
7121
  exports.normalizeProviderUrl = normalizeProviderUrl;
7122
+ exports.prepareTinfoilClient = prepareTinfoilClient;
6791
7123
  exports.routeRequests = routeRequests;
6792
7124
  exports.setDefaultUsageTrackingDriver = setDefaultUsageTrackingDriver;
6793
7125
  exports.toUsageStats = toUsageStats;