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