@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.
- 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 +367 -31
- package/dist/bun.js.map +1 -1
- package/dist/bun.mjs +362 -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 +367 -31
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +362 -32
- package/dist/node.mjs.map +1 -1
- package/dist/storage/bun.js +6 -1
- package/dist/storage/bun.js.map +1 -1
- package/dist/storage/bun.mjs +6 -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 +6 -1
- package/dist/storage/node.js.map +1 -1
- package/dist/storage/node.mjs +6 -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 +9 -3
package/dist/client/index.mjs
CHANGED
|
@@ -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
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|