@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.
- package/README.md +6 -19
- package/dist/browser.d.mts +2 -1
- package/dist/browser.d.ts +2 -1
- package/dist/browser.js +360 -28
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +355 -29
- package/dist/browser.mjs.map +1 -1
- package/dist/bun.d.mts +2 -1
- package/dist/bun.d.ts +2 -1
- package/dist/bun.js +363 -31
- package/dist/bun.js.map +1 -1
- package/dist/bun.mjs +358 -32
- package/dist/bun.mjs.map +1 -1
- package/dist/client/index.d.mts +85 -1
- package/dist/client/index.d.ts +85 -1
- package/dist/client/index.js +358 -27
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +353 -28
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +360 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +355 -29
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +2 -1
- package/dist/node.d.ts +2 -1
- package/dist/node.js +363 -31
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +358 -32
- package/dist/node.mjs.map +1 -1
- package/dist/storage/bun.js +2 -1
- package/dist/storage/bun.js.map +1 -1
- package/dist/storage/bun.mjs +2 -1
- package/dist/storage/bun.mjs.map +1 -1
- package/dist/storage/index.js +2 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +2 -1
- package/dist/storage/index.mjs.map +1 -1
- package/dist/storage/node.js +2 -1
- package/dist/storage/node.js.map +1 -1
- package/dist/storage/node.mjs +2 -1
- package/dist/storage/node.mjs.map +1 -1
- package/dist/wallet/index.js +36 -8
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +36 -8
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/client/index.js
CHANGED
|
@@ -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
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|