@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
@@ -930,15 +930,27 @@ var BalanceManager = class _BalanceManager {
930
930
  clearTimeout(timeoutId);
931
931
  const requestId = response.headers.get("x-routstr-request-id") || void 0;
932
932
  if (!response.ok) {
933
- const errorData = await response.json().catch(() => ({}));
934
- this.logger.warn(
935
- `fetchRefundToken: non-ok response for ${url} status=${response.status} statusText=${response.statusText}`,
936
- errorData
937
- );
933
+ const responseBody = await response.text().catch(() => void 0);
934
+ let errorData = {};
935
+ if (responseBody) {
936
+ try {
937
+ errorData = JSON.parse(responseBody);
938
+ } catch {
939
+ errorData = {};
940
+ }
941
+ }
942
+ this.logger.error("Upstream wallet refund error response", {
943
+ baseUrl,
944
+ url,
945
+ status: response.status,
946
+ statusText: response.statusText,
947
+ requestId,
948
+ body: responseBody ?? "<unable to read response body>"
949
+ });
938
950
  return {
939
951
  success: false,
940
952
  requestId,
941
- error: `API key refund failed: ${errorData?.detail || response.statusText}`
953
+ error: `API key refund failed: ${errorData?.detail || responseBody || response.statusText}`
942
954
  };
943
955
  }
944
956
  const data = await response.json();
@@ -1274,11 +1286,27 @@ var BalanceManager = class _BalanceManager {
1274
1286
  clearTimeout(timeoutId);
1275
1287
  const requestId = response.headers.get("x-routstr-request-id") || void 0;
1276
1288
  if (!response.ok) {
1277
- const errorData = await response.json().catch(() => ({}));
1289
+ const responseBody = await response.text().catch(() => void 0);
1290
+ let errorData = {};
1291
+ if (responseBody) {
1292
+ try {
1293
+ errorData = JSON.parse(responseBody);
1294
+ } catch {
1295
+ errorData = {};
1296
+ }
1297
+ }
1298
+ this.logger.error("Upstream wallet topup error response", {
1299
+ baseUrl,
1300
+ url,
1301
+ status: response.status,
1302
+ statusText: response.statusText,
1303
+ requestId,
1304
+ body: responseBody ?? "<unable to read response body>"
1305
+ });
1278
1306
  return {
1279
1307
  success: false,
1280
1308
  requestId,
1281
- error: errorData?.detail || `Top up failed with status ${response.status}`
1309
+ error: errorData?.detail || responseBody || `Top up failed with status ${response.status}`
1282
1310
  };
1283
1311
  }
1284
1312
  return { success: true, requestId };
@@ -2985,6 +3013,29 @@ function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
2985
3013
  }
2986
3014
  return result;
2987
3015
  }
3016
+ function extractUsageFromResponseHeaders(headers) {
3017
+ const get = (name) => {
3018
+ if (headers instanceof Headers) return headers.get(name);
3019
+ const lower = name.toLowerCase();
3020
+ for (const [k, v] of Object.entries(headers)) {
3021
+ if (k.toLowerCase() === lower) return v;
3022
+ }
3023
+ return null;
3024
+ };
3025
+ const totalMsats = Number(get("X-Routstr-Cost-Msats"));
3026
+ if (!totalMsats || !Number.isFinite(totalMsats)) return null;
3027
+ return {
3028
+ promptTokens: 0,
3029
+ completionTokens: 0,
3030
+ totalTokens: 0,
3031
+ cost: Number(get("X-Routstr-Cost-Usd")) || 0,
3032
+ satsCost: totalMsats / 1e3,
3033
+ totalMsats,
3034
+ inputMsats: Number(get("X-Routstr-Input-Cost-Msats")) || 0,
3035
+ outputMsats: Number(get("X-Routstr-Output-Cost-Msats")) || 0,
3036
+ totalUsd: Number(get("X-Routstr-Cost-Usd")) || void 0
3037
+ };
3038
+ }
2988
3039
  function toUsageStats(usage) {
2989
3040
  if (!usage) return void 0;
2990
3041
  return {
@@ -3071,14 +3122,11 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
3071
3122
  }
3072
3123
  const usage = extractUsageFromSSEJson(data);
3073
3124
  if (usage) {
3074
- console.log("[routstr:sse] \u2192 usage detected:", usage);
3075
3125
  const merged = mergeUsage(capturedUsage, usage);
3076
3126
  if (hasUsageChanged(capturedUsage, merged)) {
3077
3127
  capturedUsage = merged;
3078
- console.log("[routstr:sse] \u2192 merged (changed):", merged);
3079
3128
  onUsage(merged);
3080
3129
  } else {
3081
- console.log("[routstr:sse] \u2192 merged (no change)");
3082
3130
  }
3083
3131
  }
3084
3132
  } catch {
@@ -3235,6 +3283,177 @@ function createSSEParserTransform(onUsage, onResponseId) {
3235
3283
  });
3236
3284
  }
3237
3285
 
3286
+ // client/TinfoilSecure.ts
3287
+ var TINFOIL_MODEL_PREFIX = "tinfoil-";
3288
+ var clientCache = /* @__PURE__ */ new Map();
3289
+ function isTinfoilModel(modelId) {
3290
+ return modelId.startsWith(TINFOIL_MODEL_PREFIX);
3291
+ }
3292
+ function getTinfoilUpstreamModelId(modelId) {
3293
+ return modelId.slice(TINFOIL_MODEL_PREFIX.length);
3294
+ }
3295
+ function normalizeBaseUrl4(baseUrl) {
3296
+ return baseUrl.replace(/\/+$/, "");
3297
+ }
3298
+ function cacheKey(options) {
3299
+ return JSON.stringify({
3300
+ baseUrl: options.baseUrl,
3301
+ attestationBundleURL: options.attestationBundleURL,
3302
+ enclaveURL: options.enclaveURL,
3303
+ configRepo: options.configRepo
3304
+ });
3305
+ }
3306
+ function envOrUndefined(name) {
3307
+ const maybeProcess = globalThis;
3308
+ const value = maybeProcess.process?.env?.[name];
3309
+ return value && value.trim() ? value.trim() : void 0;
3310
+ }
3311
+ function resolveOptions(options) {
3312
+ return {
3313
+ ...options,
3314
+ baseUrl: normalizeBaseUrl4(options.baseUrl),
3315
+ attestationBundleURL: options.attestationBundleURL ?? envOrUndefined("ROUTSTR_TINFOIL_ATTESTATION_BUNDLE_URL"),
3316
+ enclaveURL: options.enclaveURL ?? envOrUndefined("ROUTSTR_TINFOIL_ENCLAVE_URL"),
3317
+ configRepo: options.configRepo ?? envOrUndefined("ROUTSTR_TINFOIL_CONFIG_REPO")
3318
+ };
3319
+ }
3320
+ async function prepareTinfoilClient(options) {
3321
+ const resolved = resolveOptions(options);
3322
+ const key = cacheKey(resolved);
3323
+ let pending = clientCache.get(key);
3324
+ if (!pending) {
3325
+ pending = (async () => {
3326
+ const { SecureClient } = await import('tinfoil');
3327
+ const client = new SecureClient({
3328
+ // baseURL is the proxy/provider URL that receives the EHBP request.
3329
+ baseURL: resolved.baseUrl,
3330
+ // Leave undefined by default so tinfoil uses its public ATC. If set,
3331
+ // SecureClient will fetch `${attestationBundleURL}/attestation`.
3332
+ attestationBundleURL: resolved.attestationBundleURL,
3333
+ enclaveURL: resolved.enclaveURL,
3334
+ configRepo: resolved.configRepo,
3335
+ transport: "ehbp"
3336
+ });
3337
+ await client.ready();
3338
+ const verification = client.getVerificationDocument();
3339
+ return { client, verification };
3340
+ })();
3341
+ clientCache.set(key, pending);
3342
+ }
3343
+ try {
3344
+ return await pending;
3345
+ } catch (error) {
3346
+ clientCache.delete(key);
3347
+ throw error;
3348
+ }
3349
+ }
3350
+ function normalizeFetchArgs(input, init) {
3351
+ if (typeof input === "string") {
3352
+ return { url: input, init };
3353
+ }
3354
+ if (input instanceof URL) {
3355
+ return { url: input.toString(), init };
3356
+ }
3357
+ const cloned = input.clone();
3358
+ return {
3359
+ url: cloned.url,
3360
+ init: {
3361
+ method: cloned.method,
3362
+ headers: new Headers(cloned.headers),
3363
+ body: cloned.body ?? void 0,
3364
+ signal: cloned.signal,
3365
+ ...init
3366
+ }
3367
+ };
3368
+ }
3369
+ function isProblemJsonContentType(contentType) {
3370
+ const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
3371
+ return mediaType === "application/problem+json";
3372
+ }
3373
+ async function isEhbpKeyConfigMismatchResponse(response, protocol) {
3374
+ if (response.status !== 422) {
3375
+ return false;
3376
+ }
3377
+ if (!isProblemJsonContentType(response.headers.get("content-type"))) {
3378
+ return false;
3379
+ }
3380
+ try {
3381
+ const problem = await response.clone().json();
3382
+ return problem?.type === protocol.KEY_CONFIG_PROBLEM_TYPE;
3383
+ } catch {
3384
+ return false;
3385
+ }
3386
+ }
3387
+ async function fetchTinfoilEhbpOnce(context, options, normalized, ehbp) {
3388
+ const { Identity, PROTOCOL, decryptResponseWithToken, extractSessionRecoveryToken } = ehbp;
3389
+ const resolved = resolveOptions(options);
3390
+ const baseURL = context.client.getBaseURL() ?? resolved.baseUrl;
3391
+ const enclaveURL = context.client.getEnclaveURL();
3392
+ const baseOrigin = new URL(baseURL).origin;
3393
+ const allowedOrigins = /* @__PURE__ */ new Set([baseOrigin]);
3394
+ if (enclaveURL) {
3395
+ allowedOrigins.add(new URL(enclaveURL).origin);
3396
+ }
3397
+ const targetUrl = new URL(normalized.url, baseURL);
3398
+ if (!allowedOrigins.has(targetUrl.origin)) {
3399
+ throw new Error(
3400
+ `refusing to send Tinfoil request to ${targetUrl.origin}: client is bound to the verified enclave/proxy`
3401
+ );
3402
+ }
3403
+ const headers = new Headers(normalized.init?.headers);
3404
+ if (enclaveURL && new URL(enclaveURL).origin !== baseOrigin) {
3405
+ headers.set("X-Tinfoil-Enclave-Url", enclaveURL);
3406
+ }
3407
+ const method = normalized.init?.method ?? "GET";
3408
+ const body = normalized.init?.body ?? null;
3409
+ const serverIdentity = await Identity.fromPublicKeyHex(
3410
+ context.verification.hpkePublicKey
3411
+ );
3412
+ const request = new Request(targetUrl.toString(), {
3413
+ method,
3414
+ headers,
3415
+ body,
3416
+ duplex: "half"
3417
+ });
3418
+ const { request: encryptedRequest, context: requestContext } = await serverIdentity.encryptRequestWithContext(request);
3419
+ const response = await fetch(encryptedRequest);
3420
+ if (!requestContext) {
3421
+ return response;
3422
+ }
3423
+ if (await isEhbpKeyConfigMismatchResponse(response, PROTOCOL)) {
3424
+ throw new ehbp.KeyConfigMismatchError("EHBP key configuration mismatch");
3425
+ }
3426
+ if (!response.headers.get(PROTOCOL.RESPONSE_NONCE_HEADER)) {
3427
+ return response;
3428
+ }
3429
+ const token = await extractSessionRecoveryToken(requestContext);
3430
+ return await decryptResponseWithToken(response, token);
3431
+ }
3432
+ async function fetchTinfoilPreservingPlaintextErrors(options, input, init) {
3433
+ const context = await prepareTinfoilClient(options);
3434
+ const ehbp = await import('ehbp');
3435
+ const normalized = normalizeFetchArgs(input, init);
3436
+ try {
3437
+ return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
3438
+ } catch (error) {
3439
+ if (error instanceof ehbp.KeyConfigMismatchError) {
3440
+ context.client.reset();
3441
+ try {
3442
+ await context.client.ready();
3443
+ context.verification = context.client.getVerificationDocument();
3444
+ } catch (reattestError) {
3445
+ clientCache.delete(cacheKey(resolveOptions(options)));
3446
+ throw reattestError;
3447
+ }
3448
+ return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
3449
+ }
3450
+ throw error;
3451
+ }
3452
+ }
3453
+ function clearTinfoilClientCache() {
3454
+ clientCache.clear();
3455
+ }
3456
+
3238
3457
  // client/RoutstrClient.ts
3239
3458
  var TOPUP_MARGIN = 1.2;
3240
3459
  var RoutstrClient = class {
@@ -3420,11 +3639,6 @@ var RoutstrClient = class {
3420
3639
  );
3421
3640
  }
3422
3641
  }
3423
- const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = await this._spendToken({
3424
- mintUrl,
3425
- amount: requiredSats,
3426
- baseUrl
3427
- });
3428
3642
  let requestBody = body;
3429
3643
  if (body && typeof body === "object") {
3430
3644
  const bodyObj = body;
@@ -3433,7 +3647,36 @@ var RoutstrClient = class {
3433
3647
  }
3434
3648
  }
3435
3649
  const baseHeaders = this._buildBaseHeaders();
3436
- const requestHeaders = this._withAuthHeader(baseHeaders, token);
3650
+ const tinfoilEnabled = Boolean(modelId && isTinfoilModel(modelId));
3651
+ if (tinfoilEnabled) {
3652
+ this._log(
3653
+ "DEBUG",
3654
+ `[RoutstrClient] Attesting Tinfoil model ${modelId} before spend`
3655
+ );
3656
+ const { verification } = await prepareTinfoilClient({ baseUrl });
3657
+ this._log(
3658
+ "DEBUG",
3659
+ `[RoutstrClient] Tinfoil attestation passed, enclave=${verification.enclaveHost}, codeFingerprint=${verification.codeFingerprint.slice(0, 16)}...`
3660
+ );
3661
+ if (requestBody && typeof requestBody === "object" && modelId) {
3662
+ requestBody = {
3663
+ ...requestBody,
3664
+ model: getTinfoilUpstreamModelId(modelId)
3665
+ };
3666
+ }
3667
+ }
3668
+ const spendResult = await this._spendToken({
3669
+ mintUrl,
3670
+ amount: requiredSats,
3671
+ baseUrl
3672
+ });
3673
+ const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = spendResult;
3674
+ const finalHeaders = this._withAuthAndTinfoilHeaders(
3675
+ baseHeaders,
3676
+ token,
3677
+ tinfoilEnabled,
3678
+ modelId
3679
+ );
3437
3680
  const response = await this._makeRequest({
3438
3681
  path: requestPath,
3439
3682
  method,
@@ -3442,9 +3685,10 @@ var RoutstrClient = class {
3442
3685
  mintUrl,
3443
3686
  token,
3444
3687
  requiredSats,
3445
- headers: requestHeaders,
3688
+ headers: finalHeaders,
3446
3689
  baseHeaders,
3447
- selectedModel
3690
+ selectedModel,
3691
+ tinfoilEnabled
3448
3692
  });
3449
3693
  let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
3450
3694
  let initialTokenBalanceUnknown = tokenBalanceUnknown;
@@ -3532,7 +3776,7 @@ var RoutstrClient = class {
3532
3776
  * Make the API request with failover support
3533
3777
  */
3534
3778
  async _makeRequest(params) {
3535
- const { path, method, body, baseUrl, token, headers } = params;
3779
+ const { path, method, body, baseUrl, token, headers, tinfoilEnabled } = params;
3536
3780
  try {
3537
3781
  const url = `${baseUrl.replace(/\/$/, "")}${path}`;
3538
3782
  const requestBodyText = body === void 0 || method === "GET" ? void 0 : JSON.stringify(body);
@@ -3546,7 +3790,15 @@ var RoutstrClient = class {
3546
3790
  rawBody: requestBodyText
3547
3791
  });
3548
3792
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
3549
- const response = await fetch(url, {
3793
+ const response = tinfoilEnabled ? await fetchTinfoilPreservingPlaintextErrors(
3794
+ { baseUrl },
3795
+ url,
3796
+ {
3797
+ method,
3798
+ headers,
3799
+ body: requestBodyText
3800
+ }
3801
+ ) : await fetch(url, {
3550
3802
  method,
3551
3803
  headers,
3552
3804
  body: requestBodyText
@@ -3566,6 +3818,15 @@ var RoutstrClient = class {
3566
3818
  } catch (e) {
3567
3819
  bodyText = void 0;
3568
3820
  }
3821
+ this._log("ERROR", "[RoutstrClient] Upstream error response", {
3822
+ baseUrl,
3823
+ url,
3824
+ path,
3825
+ status: response.status,
3826
+ statusText: response.statusText,
3827
+ requestId,
3828
+ body: bodyText ?? "<unable to read response body>"
3829
+ });
3569
3830
  return await this._handleErrorResponse(
3570
3831
  params,
3571
3832
  token,
@@ -3596,6 +3857,9 @@ var RoutstrClient = class {
3596
3857
  throw error;
3597
3858
  }
3598
3859
  }
3860
+ /**
3861
+ * Store request details to a file in the reqs/ folder before fetch.
3862
+ */
3599
3863
  /**
3600
3864
  * Handle error responses with failover
3601
3865
  */
@@ -3747,7 +4011,12 @@ var RoutstrClient = class {
3747
4011
  return this._makeRequest({
3748
4012
  ...params,
3749
4013
  token: params.token,
3750
- headers: this._withAuthHeader(params.baseHeaders, params.token),
4014
+ headers: this._withAuthAndTinfoilHeaders(
4015
+ params.baseHeaders,
4016
+ params.token,
4017
+ params.tinfoilEnabled,
4018
+ params.selectedModel?.id
4019
+ ),
3751
4020
  retryCount: retryCount + 1
3752
4021
  });
3753
4022
  } else {
@@ -3808,7 +4077,12 @@ var RoutstrClient = class {
3808
4077
  return this._makeRequest({
3809
4078
  ...params,
3810
4079
  token: retryToken,
3811
- headers: this._withAuthHeader(params.baseHeaders, retryToken),
4080
+ headers: this._withAuthAndTinfoilHeaders(
4081
+ params.baseHeaders,
4082
+ retryToken,
4083
+ params.tinfoilEnabled,
4084
+ params.selectedModel?.id
4085
+ ),
3812
4086
  retryCount: retryCount + 1
3813
4087
  });
3814
4088
  } else {
@@ -3903,6 +4177,13 @@ var RoutstrClient = class {
3903
4177
  messagesForPricing,
3904
4178
  params.maxTokens
3905
4179
  );
4180
+ if (params.tinfoilEnabled) {
4181
+ this._log(
4182
+ "DEBUG",
4183
+ `[RoutstrClient] _handleErrorResponse: Attesting Tinfoil failover provider ${nextProvider} before spend`
4184
+ );
4185
+ await prepareTinfoilClient({ baseUrl: nextProvider });
4186
+ }
3906
4187
  this._log(
3907
4188
  "DEBUG",
3908
4189
  `[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
@@ -3921,7 +4202,12 @@ var RoutstrClient = class {
3921
4202
  selectedModel: newModel,
3922
4203
  token: spendResult.token,
3923
4204
  requiredSats: newRequiredSats,
3924
- headers: this._withAuthHeader(params.baseHeaders, spendResult.token),
4205
+ headers: this._withAuthAndTinfoilHeaders(
4206
+ params.baseHeaders,
4207
+ spendResult.token,
4208
+ params.tinfoilEnabled,
4209
+ newModel.id
4210
+ ),
3925
4211
  retryCount: 0
3926
4212
  });
3927
4213
  retryResponse.initialTokenBalanceInSats = spendResult.tokenBalanceUnit === "msat" ? spendResult.tokenBalance / 1e3 : spendResult.tokenBalance;
@@ -3988,10 +4274,10 @@ var RoutstrClient = class {
3988
4274
  if (latestTokenBalance !== void 0) {
3989
4275
  this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
3990
4276
  }
3991
- satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? 0;
4277
+ satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
3992
4278
  } catch (e) {
3993
4279
  this._log("WARN", "Could not get updated API key balance:", e);
3994
- satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? 0;
4280
+ satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
3995
4281
  }
3996
4282
  }
3997
4283
  await this._trackResponseUsage({
@@ -4008,6 +4294,15 @@ var RoutstrClient = class {
4008
4294
  })();
4009
4295
  return satsSpent;
4010
4296
  }
4297
+ /**
4298
+ * Extract sats cost from EHBP/Tinfoil response headers as a last-resort
4299
+ * fallback when neither balance delta nor SSE/body usage provides a cost.
4300
+ */
4301
+ _headerSatsCost(response) {
4302
+ if (!response) return void 0;
4303
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
4304
+ return headerUsage?.satsCost;
4305
+ }
4011
4306
  async _trackResponseUsage(params) {
4012
4307
  const {
4013
4308
  token,
@@ -4041,7 +4336,24 @@ var RoutstrClient = class {
4041
4336
  }
4042
4337
  }
4043
4338
  if (!usage) {
4044
- return;
4339
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
4340
+ if (headerUsage) {
4341
+ usage = headerUsage;
4342
+ } else {
4343
+ return;
4344
+ }
4345
+ } else {
4346
+ const headerUsage = extractUsageFromResponseHeaders(response.headers);
4347
+ if (headerUsage) {
4348
+ if (headerUsage.totalMsats) {
4349
+ usage.totalMsats = headerUsage.totalMsats;
4350
+ usage.satsCost = headerUsage.satsCost;
4351
+ }
4352
+ if (headerUsage.cost) usage.cost = headerUsage.cost;
4353
+ if (headerUsage.inputMsats) usage.inputMsats = headerUsage.inputMsats;
4354
+ if (headerUsage.outputMsats) usage.outputMsats = headerUsage.outputMsats;
4355
+ if (headerUsage.totalUsd) usage.totalUsd = headerUsage.totalUsd;
4356
+ }
4045
4357
  }
4046
4358
  const finalRequestId = requestId || "unknown";
4047
4359
  const store = this.sdkStore ?? await getDefaultSdkStore();
@@ -4238,6 +4550,19 @@ var RoutstrClient = class {
4238
4550
  }
4239
4551
  return nextHeaders;
4240
4552
  }
4553
+ /**
4554
+ * Attach auth headers and preserve the plaintext model hint required by the
4555
+ * Routstr proxy for Tinfoil/EHBP requests. EHBP encrypts the JSON body, so
4556
+ * retries/failover must not rebuild headers from baseHeaders alone or the
4557
+ * proxy cannot route/price the encrypted request.
4558
+ */
4559
+ _withAuthAndTinfoilHeaders(headers, token, tinfoilEnabled, modelId) {
4560
+ const nextHeaders = this._withAuthHeader(headers, token);
4561
+ if (tinfoilEnabled && modelId) {
4562
+ nextHeaders["X-Routstr-Model"] = modelId;
4563
+ }
4564
+ return nextHeaders;
4565
+ }
4241
4566
  };
4242
4567
 
4243
4568
  // client/StreamProcessor.ts
@@ -4586,12 +4911,18 @@ function handleError(error, callbacks, alertLevel, logger) {
4586
4911
  exports.ProviderManager = ProviderManager;
4587
4912
  exports.RoutstrClient = RoutstrClient;
4588
4913
  exports.StreamProcessor = StreamProcessor;
4914
+ exports.clearTinfoilClientCache = clearTinfoilClientCache;
4589
4915
  exports.createSSEParserTransform = createSSEParserTransform;
4590
4916
  exports.extractResponseId = extractResponseId;
4591
4917
  exports.extractUsageFromResponseBody = extractUsageFromResponseBody;
4918
+ exports.extractUsageFromResponseHeaders = extractUsageFromResponseHeaders;
4592
4919
  exports.extractUsageFromSSEJson = extractUsageFromSSEJson;
4593
4920
  exports.fetchAIResponse = fetchAIResponse;
4921
+ exports.fetchTinfoilPreservingPlaintextErrors = fetchTinfoilPreservingPlaintextErrors;
4922
+ exports.getTinfoilUpstreamModelId = getTinfoilUpstreamModelId;
4594
4923
  exports.inspectSSEWebStream = inspectSSEWebStream;
4924
+ exports.isTinfoilModel = isTinfoilModel;
4925
+ exports.prepareTinfoilClient = prepareTinfoilClient;
4595
4926
  exports.toUsageStats = toUsageStats;
4596
4927
  //# sourceMappingURL=index.js.map
4597
4928
  //# sourceMappingURL=index.js.map