@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/bun.mjs
CHANGED
|
@@ -1858,15 +1858,27 @@ var BalanceManager = class _BalanceManager {
|
|
|
1858
1858
|
clearTimeout(timeoutId);
|
|
1859
1859
|
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
1860
1860
|
if (!response.ok) {
|
|
1861
|
-
const
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1861
|
+
const responseBody = await response.text().catch(() => void 0);
|
|
1862
|
+
let errorData = {};
|
|
1863
|
+
if (responseBody) {
|
|
1864
|
+
try {
|
|
1865
|
+
errorData = JSON.parse(responseBody);
|
|
1866
|
+
} catch {
|
|
1867
|
+
errorData = {};
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
this.logger.error("Upstream wallet refund error response", {
|
|
1871
|
+
baseUrl,
|
|
1872
|
+
url,
|
|
1873
|
+
status: response.status,
|
|
1874
|
+
statusText: response.statusText,
|
|
1875
|
+
requestId,
|
|
1876
|
+
body: responseBody ?? "<unable to read response body>"
|
|
1877
|
+
});
|
|
1866
1878
|
return {
|
|
1867
1879
|
success: false,
|
|
1868
1880
|
requestId,
|
|
1869
|
-
error: `API key refund failed: ${errorData?.detail || response.statusText}`
|
|
1881
|
+
error: `API key refund failed: ${errorData?.detail || responseBody || response.statusText}`
|
|
1870
1882
|
};
|
|
1871
1883
|
}
|
|
1872
1884
|
const data = await response.json();
|
|
@@ -2202,11 +2214,27 @@ var BalanceManager = class _BalanceManager {
|
|
|
2202
2214
|
clearTimeout(timeoutId);
|
|
2203
2215
|
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
2204
2216
|
if (!response.ok) {
|
|
2205
|
-
const
|
|
2217
|
+
const responseBody = await response.text().catch(() => void 0);
|
|
2218
|
+
let errorData = {};
|
|
2219
|
+
if (responseBody) {
|
|
2220
|
+
try {
|
|
2221
|
+
errorData = JSON.parse(responseBody);
|
|
2222
|
+
} catch {
|
|
2223
|
+
errorData = {};
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
this.logger.error("Upstream wallet topup error response", {
|
|
2227
|
+
baseUrl,
|
|
2228
|
+
url,
|
|
2229
|
+
status: response.status,
|
|
2230
|
+
statusText: response.statusText,
|
|
2231
|
+
requestId,
|
|
2232
|
+
body: responseBody ?? "<unable to read response body>"
|
|
2233
|
+
});
|
|
2206
2234
|
return {
|
|
2207
2235
|
success: false,
|
|
2208
2236
|
requestId,
|
|
2209
|
-
error: errorData?.detail || `Top up failed with status ${response.status}`
|
|
2237
|
+
error: errorData?.detail || responseBody || `Top up failed with status ${response.status}`
|
|
2210
2238
|
};
|
|
2211
2239
|
}
|
|
2212
2240
|
return { success: true, requestId };
|
|
@@ -3039,7 +3067,7 @@ var openDatabase = (dbName, storeName) => {
|
|
|
3039
3067
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
3040
3068
|
}
|
|
3041
3069
|
return new Promise((resolve, reject) => {
|
|
3042
|
-
const request = indexedDB.open(dbName,
|
|
3070
|
+
const request = indexedDB.open(dbName, 3);
|
|
3043
3071
|
request.onupgradeneeded = () => {
|
|
3044
3072
|
const db = request.result;
|
|
3045
3073
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
@@ -3052,6 +3080,7 @@ var openDatabase = (dbName, storeName) => {
|
|
|
3052
3080
|
utStore.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
3053
3081
|
utStore.createIndex("sessionId", "sessionId", { unique: false });
|
|
3054
3082
|
utStore.createIndex("client", "client", { unique: false });
|
|
3083
|
+
utStore.createIndex("provider", "provider", { unique: false });
|
|
3055
3084
|
}
|
|
3056
3085
|
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
3057
3086
|
db.createObjectStore("sdk_storage");
|
|
@@ -4672,6 +4701,29 @@ function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
|
4672
4701
|
}
|
|
4673
4702
|
return result;
|
|
4674
4703
|
}
|
|
4704
|
+
function extractUsageFromResponseHeaders(headers) {
|
|
4705
|
+
const get = (name) => {
|
|
4706
|
+
if (headers instanceof Headers) return headers.get(name);
|
|
4707
|
+
const lower = name.toLowerCase();
|
|
4708
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
4709
|
+
if (k.toLowerCase() === lower) return v;
|
|
4710
|
+
}
|
|
4711
|
+
return null;
|
|
4712
|
+
};
|
|
4713
|
+
const totalMsats = Number(get("X-Routstr-Cost-Msats"));
|
|
4714
|
+
if (!totalMsats || !Number.isFinite(totalMsats)) return null;
|
|
4715
|
+
return {
|
|
4716
|
+
promptTokens: 0,
|
|
4717
|
+
completionTokens: 0,
|
|
4718
|
+
totalTokens: 0,
|
|
4719
|
+
cost: Number(get("X-Routstr-Cost-Usd")) || 0,
|
|
4720
|
+
satsCost: totalMsats / 1e3,
|
|
4721
|
+
totalMsats,
|
|
4722
|
+
inputMsats: Number(get("X-Routstr-Input-Cost-Msats")) || 0,
|
|
4723
|
+
outputMsats: Number(get("X-Routstr-Output-Cost-Msats")) || 0,
|
|
4724
|
+
totalUsd: Number(get("X-Routstr-Cost-Usd")) || void 0
|
|
4725
|
+
};
|
|
4726
|
+
}
|
|
4675
4727
|
function toUsageStats(usage) {
|
|
4676
4728
|
if (!usage) return void 0;
|
|
4677
4729
|
return {
|
|
@@ -4758,14 +4810,11 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
|
|
|
4758
4810
|
}
|
|
4759
4811
|
const usage = extractUsageFromSSEJson(data);
|
|
4760
4812
|
if (usage) {
|
|
4761
|
-
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
4762
4813
|
const merged = mergeUsage(capturedUsage, usage);
|
|
4763
4814
|
if (hasUsageChanged(capturedUsage, merged)) {
|
|
4764
4815
|
capturedUsage = merged;
|
|
4765
|
-
console.log("[routstr:sse] \u2192 merged (changed):", merged);
|
|
4766
4816
|
onUsage(merged);
|
|
4767
4817
|
} else {
|
|
4768
|
-
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
4769
4818
|
}
|
|
4770
4819
|
}
|
|
4771
4820
|
} catch {
|
|
@@ -4922,6 +4971,177 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
4922
4971
|
});
|
|
4923
4972
|
}
|
|
4924
4973
|
|
|
4974
|
+
// client/TinfoilSecure.ts
|
|
4975
|
+
var TINFOIL_MODEL_PREFIX = "tinfoil-";
|
|
4976
|
+
var clientCache = /* @__PURE__ */ new Map();
|
|
4977
|
+
function isTinfoilModel(modelId) {
|
|
4978
|
+
return modelId.startsWith(TINFOIL_MODEL_PREFIX);
|
|
4979
|
+
}
|
|
4980
|
+
function getTinfoilUpstreamModelId(modelId) {
|
|
4981
|
+
return modelId.slice(TINFOIL_MODEL_PREFIX.length);
|
|
4982
|
+
}
|
|
4983
|
+
function normalizeBaseUrl5(baseUrl) {
|
|
4984
|
+
return baseUrl.replace(/\/+$/, "");
|
|
4985
|
+
}
|
|
4986
|
+
function cacheKey(options) {
|
|
4987
|
+
return JSON.stringify({
|
|
4988
|
+
baseUrl: options.baseUrl,
|
|
4989
|
+
attestationBundleURL: options.attestationBundleURL,
|
|
4990
|
+
enclaveURL: options.enclaveURL,
|
|
4991
|
+
configRepo: options.configRepo
|
|
4992
|
+
});
|
|
4993
|
+
}
|
|
4994
|
+
function envOrUndefined(name) {
|
|
4995
|
+
const maybeProcess = globalThis;
|
|
4996
|
+
const value = maybeProcess.process?.env?.[name];
|
|
4997
|
+
return value && value.trim() ? value.trim() : void 0;
|
|
4998
|
+
}
|
|
4999
|
+
function resolveOptions(options) {
|
|
5000
|
+
return {
|
|
5001
|
+
...options,
|
|
5002
|
+
baseUrl: normalizeBaseUrl5(options.baseUrl),
|
|
5003
|
+
attestationBundleURL: options.attestationBundleURL ?? envOrUndefined("ROUTSTR_TINFOIL_ATTESTATION_BUNDLE_URL"),
|
|
5004
|
+
enclaveURL: options.enclaveURL ?? envOrUndefined("ROUTSTR_TINFOIL_ENCLAVE_URL"),
|
|
5005
|
+
configRepo: options.configRepo ?? envOrUndefined("ROUTSTR_TINFOIL_CONFIG_REPO")
|
|
5006
|
+
};
|
|
5007
|
+
}
|
|
5008
|
+
async function prepareTinfoilClient(options) {
|
|
5009
|
+
const resolved = resolveOptions(options);
|
|
5010
|
+
const key = cacheKey(resolved);
|
|
5011
|
+
let pending = clientCache.get(key);
|
|
5012
|
+
if (!pending) {
|
|
5013
|
+
pending = (async () => {
|
|
5014
|
+
const { SecureClient } = await import('tinfoil');
|
|
5015
|
+
const client = new SecureClient({
|
|
5016
|
+
// baseURL is the proxy/provider URL that receives the EHBP request.
|
|
5017
|
+
baseURL: resolved.baseUrl,
|
|
5018
|
+
// Leave undefined by default so tinfoil uses its public ATC. If set,
|
|
5019
|
+
// SecureClient will fetch `${attestationBundleURL}/attestation`.
|
|
5020
|
+
attestationBundleURL: resolved.attestationBundleURL,
|
|
5021
|
+
enclaveURL: resolved.enclaveURL,
|
|
5022
|
+
configRepo: resolved.configRepo,
|
|
5023
|
+
transport: "ehbp"
|
|
5024
|
+
});
|
|
5025
|
+
await client.ready();
|
|
5026
|
+
const verification = client.getVerificationDocument();
|
|
5027
|
+
return { client, verification };
|
|
5028
|
+
})();
|
|
5029
|
+
clientCache.set(key, pending);
|
|
5030
|
+
}
|
|
5031
|
+
try {
|
|
5032
|
+
return await pending;
|
|
5033
|
+
} catch (error) {
|
|
5034
|
+
clientCache.delete(key);
|
|
5035
|
+
throw error;
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
function normalizeFetchArgs(input, init) {
|
|
5039
|
+
if (typeof input === "string") {
|
|
5040
|
+
return { url: input, init };
|
|
5041
|
+
}
|
|
5042
|
+
if (input instanceof URL) {
|
|
5043
|
+
return { url: input.toString(), init };
|
|
5044
|
+
}
|
|
5045
|
+
const cloned = input.clone();
|
|
5046
|
+
return {
|
|
5047
|
+
url: cloned.url,
|
|
5048
|
+
init: {
|
|
5049
|
+
method: cloned.method,
|
|
5050
|
+
headers: new Headers(cloned.headers),
|
|
5051
|
+
body: cloned.body ?? void 0,
|
|
5052
|
+
signal: cloned.signal,
|
|
5053
|
+
...init
|
|
5054
|
+
}
|
|
5055
|
+
};
|
|
5056
|
+
}
|
|
5057
|
+
function isProblemJsonContentType(contentType) {
|
|
5058
|
+
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
5059
|
+
return mediaType === "application/problem+json";
|
|
5060
|
+
}
|
|
5061
|
+
async function isEhbpKeyConfigMismatchResponse(response, protocol) {
|
|
5062
|
+
if (response.status !== 422) {
|
|
5063
|
+
return false;
|
|
5064
|
+
}
|
|
5065
|
+
if (!isProblemJsonContentType(response.headers.get("content-type"))) {
|
|
5066
|
+
return false;
|
|
5067
|
+
}
|
|
5068
|
+
try {
|
|
5069
|
+
const problem = await response.clone().json();
|
|
5070
|
+
return problem?.type === protocol.KEY_CONFIG_PROBLEM_TYPE;
|
|
5071
|
+
} catch {
|
|
5072
|
+
return false;
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
async function fetchTinfoilEhbpOnce(context, options, normalized, ehbp) {
|
|
5076
|
+
const { Identity, PROTOCOL, decryptResponseWithToken, extractSessionRecoveryToken } = ehbp;
|
|
5077
|
+
const resolved = resolveOptions(options);
|
|
5078
|
+
const baseURL = context.client.getBaseURL() ?? resolved.baseUrl;
|
|
5079
|
+
const enclaveURL = context.client.getEnclaveURL();
|
|
5080
|
+
const baseOrigin = new URL(baseURL).origin;
|
|
5081
|
+
const allowedOrigins = /* @__PURE__ */ new Set([baseOrigin]);
|
|
5082
|
+
if (enclaveURL) {
|
|
5083
|
+
allowedOrigins.add(new URL(enclaveURL).origin);
|
|
5084
|
+
}
|
|
5085
|
+
const targetUrl = new URL(normalized.url, baseURL);
|
|
5086
|
+
if (!allowedOrigins.has(targetUrl.origin)) {
|
|
5087
|
+
throw new Error(
|
|
5088
|
+
`refusing to send Tinfoil request to ${targetUrl.origin}: client is bound to the verified enclave/proxy`
|
|
5089
|
+
);
|
|
5090
|
+
}
|
|
5091
|
+
const headers = new Headers(normalized.init?.headers);
|
|
5092
|
+
if (enclaveURL && new URL(enclaveURL).origin !== baseOrigin) {
|
|
5093
|
+
headers.set("X-Tinfoil-Enclave-Url", enclaveURL);
|
|
5094
|
+
}
|
|
5095
|
+
const method = normalized.init?.method ?? "GET";
|
|
5096
|
+
const body = normalized.init?.body ?? null;
|
|
5097
|
+
const serverIdentity = await Identity.fromPublicKeyHex(
|
|
5098
|
+
context.verification.hpkePublicKey
|
|
5099
|
+
);
|
|
5100
|
+
const request = new Request(targetUrl.toString(), {
|
|
5101
|
+
method,
|
|
5102
|
+
headers,
|
|
5103
|
+
body,
|
|
5104
|
+
duplex: "half"
|
|
5105
|
+
});
|
|
5106
|
+
const { request: encryptedRequest, context: requestContext } = await serverIdentity.encryptRequestWithContext(request);
|
|
5107
|
+
const response = await fetch(encryptedRequest);
|
|
5108
|
+
if (!requestContext) {
|
|
5109
|
+
return response;
|
|
5110
|
+
}
|
|
5111
|
+
if (await isEhbpKeyConfigMismatchResponse(response, PROTOCOL)) {
|
|
5112
|
+
throw new ehbp.KeyConfigMismatchError("EHBP key configuration mismatch");
|
|
5113
|
+
}
|
|
5114
|
+
if (!response.headers.get(PROTOCOL.RESPONSE_NONCE_HEADER)) {
|
|
5115
|
+
return response;
|
|
5116
|
+
}
|
|
5117
|
+
const token = await extractSessionRecoveryToken(requestContext);
|
|
5118
|
+
return await decryptResponseWithToken(response, token);
|
|
5119
|
+
}
|
|
5120
|
+
async function fetchTinfoilPreservingPlaintextErrors(options, input, init) {
|
|
5121
|
+
const context = await prepareTinfoilClient(options);
|
|
5122
|
+
const ehbp = await import('ehbp');
|
|
5123
|
+
const normalized = normalizeFetchArgs(input, init);
|
|
5124
|
+
try {
|
|
5125
|
+
return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
|
|
5126
|
+
} catch (error) {
|
|
5127
|
+
if (error instanceof ehbp.KeyConfigMismatchError) {
|
|
5128
|
+
context.client.reset();
|
|
5129
|
+
try {
|
|
5130
|
+
await context.client.ready();
|
|
5131
|
+
context.verification = context.client.getVerificationDocument();
|
|
5132
|
+
} catch (reattestError) {
|
|
5133
|
+
clientCache.delete(cacheKey(resolveOptions(options)));
|
|
5134
|
+
throw reattestError;
|
|
5135
|
+
}
|
|
5136
|
+
return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
|
|
5137
|
+
}
|
|
5138
|
+
throw error;
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
function clearTinfoilClientCache() {
|
|
5142
|
+
clientCache.clear();
|
|
5143
|
+
}
|
|
5144
|
+
|
|
4925
5145
|
// client/RoutstrClient.ts
|
|
4926
5146
|
var TOPUP_MARGIN = 1.2;
|
|
4927
5147
|
var RoutstrClient = class {
|
|
@@ -5107,11 +5327,6 @@ var RoutstrClient = class {
|
|
|
5107
5327
|
);
|
|
5108
5328
|
}
|
|
5109
5329
|
}
|
|
5110
|
-
const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = await this._spendToken({
|
|
5111
|
-
mintUrl,
|
|
5112
|
-
amount: requiredSats,
|
|
5113
|
-
baseUrl
|
|
5114
|
-
});
|
|
5115
5330
|
let requestBody = body;
|
|
5116
5331
|
if (body && typeof body === "object") {
|
|
5117
5332
|
const bodyObj = body;
|
|
@@ -5120,7 +5335,36 @@ var RoutstrClient = class {
|
|
|
5120
5335
|
}
|
|
5121
5336
|
}
|
|
5122
5337
|
const baseHeaders = this._buildBaseHeaders();
|
|
5123
|
-
const
|
|
5338
|
+
const tinfoilEnabled = Boolean(modelId && isTinfoilModel(modelId));
|
|
5339
|
+
if (tinfoilEnabled) {
|
|
5340
|
+
this._log(
|
|
5341
|
+
"DEBUG",
|
|
5342
|
+
`[RoutstrClient] Attesting Tinfoil model ${modelId} before spend`
|
|
5343
|
+
);
|
|
5344
|
+
const { verification } = await prepareTinfoilClient({ baseUrl });
|
|
5345
|
+
this._log(
|
|
5346
|
+
"DEBUG",
|
|
5347
|
+
`[RoutstrClient] Tinfoil attestation passed, enclave=${verification.enclaveHost}, codeFingerprint=${verification.codeFingerprint.slice(0, 16)}...`
|
|
5348
|
+
);
|
|
5349
|
+
if (requestBody && typeof requestBody === "object" && modelId) {
|
|
5350
|
+
requestBody = {
|
|
5351
|
+
...requestBody,
|
|
5352
|
+
model: getTinfoilUpstreamModelId(modelId)
|
|
5353
|
+
};
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5356
|
+
const spendResult = await this._spendToken({
|
|
5357
|
+
mintUrl,
|
|
5358
|
+
amount: requiredSats,
|
|
5359
|
+
baseUrl
|
|
5360
|
+
});
|
|
5361
|
+
const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = spendResult;
|
|
5362
|
+
const finalHeaders = this._withAuthAndTinfoilHeaders(
|
|
5363
|
+
baseHeaders,
|
|
5364
|
+
token,
|
|
5365
|
+
tinfoilEnabled,
|
|
5366
|
+
modelId
|
|
5367
|
+
);
|
|
5124
5368
|
const response = await this._makeRequest({
|
|
5125
5369
|
path: requestPath,
|
|
5126
5370
|
method,
|
|
@@ -5129,9 +5373,10 @@ var RoutstrClient = class {
|
|
|
5129
5373
|
mintUrl,
|
|
5130
5374
|
token,
|
|
5131
5375
|
requiredSats,
|
|
5132
|
-
headers:
|
|
5376
|
+
headers: finalHeaders,
|
|
5133
5377
|
baseHeaders,
|
|
5134
|
-
selectedModel
|
|
5378
|
+
selectedModel,
|
|
5379
|
+
tinfoilEnabled
|
|
5135
5380
|
});
|
|
5136
5381
|
let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
5137
5382
|
let initialTokenBalanceUnknown = tokenBalanceUnknown;
|
|
@@ -5219,7 +5464,7 @@ var RoutstrClient = class {
|
|
|
5219
5464
|
* Make the API request with failover support
|
|
5220
5465
|
*/
|
|
5221
5466
|
async _makeRequest(params) {
|
|
5222
|
-
const { path, method, body, baseUrl, token, headers } = params;
|
|
5467
|
+
const { path, method, body, baseUrl, token, headers, tinfoilEnabled } = params;
|
|
5223
5468
|
try {
|
|
5224
5469
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
5225
5470
|
const requestBodyText = body === void 0 || method === "GET" ? void 0 : JSON.stringify(body);
|
|
@@ -5233,7 +5478,15 @@ var RoutstrClient = class {
|
|
|
5233
5478
|
rawBody: requestBodyText
|
|
5234
5479
|
});
|
|
5235
5480
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
5236
|
-
const response = await
|
|
5481
|
+
const response = tinfoilEnabled ? await fetchTinfoilPreservingPlaintextErrors(
|
|
5482
|
+
{ baseUrl },
|
|
5483
|
+
url,
|
|
5484
|
+
{
|
|
5485
|
+
method,
|
|
5486
|
+
headers,
|
|
5487
|
+
body: requestBodyText
|
|
5488
|
+
}
|
|
5489
|
+
) : await fetch(url, {
|
|
5237
5490
|
method,
|
|
5238
5491
|
headers,
|
|
5239
5492
|
body: requestBodyText
|
|
@@ -5253,6 +5506,15 @@ var RoutstrClient = class {
|
|
|
5253
5506
|
} catch (e) {
|
|
5254
5507
|
bodyText = void 0;
|
|
5255
5508
|
}
|
|
5509
|
+
this._log("ERROR", "[RoutstrClient] Upstream error response", {
|
|
5510
|
+
baseUrl,
|
|
5511
|
+
url,
|
|
5512
|
+
path,
|
|
5513
|
+
status: response.status,
|
|
5514
|
+
statusText: response.statusText,
|
|
5515
|
+
requestId,
|
|
5516
|
+
body: bodyText ?? "<unable to read response body>"
|
|
5517
|
+
});
|
|
5256
5518
|
return await this._handleErrorResponse(
|
|
5257
5519
|
params,
|
|
5258
5520
|
token,
|
|
@@ -5283,6 +5545,9 @@ var RoutstrClient = class {
|
|
|
5283
5545
|
throw error;
|
|
5284
5546
|
}
|
|
5285
5547
|
}
|
|
5548
|
+
/**
|
|
5549
|
+
* Store request details to a file in the reqs/ folder before fetch.
|
|
5550
|
+
*/
|
|
5286
5551
|
/**
|
|
5287
5552
|
* Handle error responses with failover
|
|
5288
5553
|
*/
|
|
@@ -5434,7 +5699,12 @@ var RoutstrClient = class {
|
|
|
5434
5699
|
return this._makeRequest({
|
|
5435
5700
|
...params,
|
|
5436
5701
|
token: params.token,
|
|
5437
|
-
headers: this.
|
|
5702
|
+
headers: this._withAuthAndTinfoilHeaders(
|
|
5703
|
+
params.baseHeaders,
|
|
5704
|
+
params.token,
|
|
5705
|
+
params.tinfoilEnabled,
|
|
5706
|
+
params.selectedModel?.id
|
|
5707
|
+
),
|
|
5438
5708
|
retryCount: retryCount + 1
|
|
5439
5709
|
});
|
|
5440
5710
|
} else {
|
|
@@ -5495,7 +5765,12 @@ var RoutstrClient = class {
|
|
|
5495
5765
|
return this._makeRequest({
|
|
5496
5766
|
...params,
|
|
5497
5767
|
token: retryToken,
|
|
5498
|
-
headers: this.
|
|
5768
|
+
headers: this._withAuthAndTinfoilHeaders(
|
|
5769
|
+
params.baseHeaders,
|
|
5770
|
+
retryToken,
|
|
5771
|
+
params.tinfoilEnabled,
|
|
5772
|
+
params.selectedModel?.id
|
|
5773
|
+
),
|
|
5499
5774
|
retryCount: retryCount + 1
|
|
5500
5775
|
});
|
|
5501
5776
|
} else {
|
|
@@ -5590,6 +5865,13 @@ var RoutstrClient = class {
|
|
|
5590
5865
|
messagesForPricing,
|
|
5591
5866
|
params.maxTokens
|
|
5592
5867
|
);
|
|
5868
|
+
if (params.tinfoilEnabled) {
|
|
5869
|
+
this._log(
|
|
5870
|
+
"DEBUG",
|
|
5871
|
+
`[RoutstrClient] _handleErrorResponse: Attesting Tinfoil failover provider ${nextProvider} before spend`
|
|
5872
|
+
);
|
|
5873
|
+
await prepareTinfoilClient({ baseUrl: nextProvider });
|
|
5874
|
+
}
|
|
5593
5875
|
this._log(
|
|
5594
5876
|
"DEBUG",
|
|
5595
5877
|
`[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
|
|
@@ -5608,7 +5890,12 @@ var RoutstrClient = class {
|
|
|
5608
5890
|
selectedModel: newModel,
|
|
5609
5891
|
token: spendResult.token,
|
|
5610
5892
|
requiredSats: newRequiredSats,
|
|
5611
|
-
headers: this.
|
|
5893
|
+
headers: this._withAuthAndTinfoilHeaders(
|
|
5894
|
+
params.baseHeaders,
|
|
5895
|
+
spendResult.token,
|
|
5896
|
+
params.tinfoilEnabled,
|
|
5897
|
+
newModel.id
|
|
5898
|
+
),
|
|
5612
5899
|
retryCount: 0
|
|
5613
5900
|
});
|
|
5614
5901
|
retryResponse.initialTokenBalanceInSats = spendResult.tokenBalanceUnit === "msat" ? spendResult.tokenBalance / 1e3 : spendResult.tokenBalance;
|
|
@@ -5675,10 +5962,10 @@ var RoutstrClient = class {
|
|
|
5675
5962
|
if (latestTokenBalance !== void 0) {
|
|
5676
5963
|
this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
|
|
5677
5964
|
}
|
|
5678
|
-
satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
5965
|
+
satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
|
|
5679
5966
|
} catch (e) {
|
|
5680
5967
|
this._log("WARN", "Could not get updated API key balance:", e);
|
|
5681
|
-
satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
5968
|
+
satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
|
|
5682
5969
|
}
|
|
5683
5970
|
}
|
|
5684
5971
|
await this._trackResponseUsage({
|
|
@@ -5695,6 +5982,15 @@ var RoutstrClient = class {
|
|
|
5695
5982
|
})();
|
|
5696
5983
|
return satsSpent;
|
|
5697
5984
|
}
|
|
5985
|
+
/**
|
|
5986
|
+
* Extract sats cost from EHBP/Tinfoil response headers as a last-resort
|
|
5987
|
+
* fallback when neither balance delta nor SSE/body usage provides a cost.
|
|
5988
|
+
*/
|
|
5989
|
+
_headerSatsCost(response) {
|
|
5990
|
+
if (!response) return void 0;
|
|
5991
|
+
const headerUsage = extractUsageFromResponseHeaders(response.headers);
|
|
5992
|
+
return headerUsage?.satsCost;
|
|
5993
|
+
}
|
|
5698
5994
|
async _trackResponseUsage(params) {
|
|
5699
5995
|
const {
|
|
5700
5996
|
token,
|
|
@@ -5728,7 +6024,24 @@ var RoutstrClient = class {
|
|
|
5728
6024
|
}
|
|
5729
6025
|
}
|
|
5730
6026
|
if (!usage) {
|
|
5731
|
-
|
|
6027
|
+
const headerUsage = extractUsageFromResponseHeaders(response.headers);
|
|
6028
|
+
if (headerUsage) {
|
|
6029
|
+
usage = headerUsage;
|
|
6030
|
+
} else {
|
|
6031
|
+
return;
|
|
6032
|
+
}
|
|
6033
|
+
} else {
|
|
6034
|
+
const headerUsage = extractUsageFromResponseHeaders(response.headers);
|
|
6035
|
+
if (headerUsage) {
|
|
6036
|
+
if (headerUsage.totalMsats) {
|
|
6037
|
+
usage.totalMsats = headerUsage.totalMsats;
|
|
6038
|
+
usage.satsCost = headerUsage.satsCost;
|
|
6039
|
+
}
|
|
6040
|
+
if (headerUsage.cost) usage.cost = headerUsage.cost;
|
|
6041
|
+
if (headerUsage.inputMsats) usage.inputMsats = headerUsage.inputMsats;
|
|
6042
|
+
if (headerUsage.outputMsats) usage.outputMsats = headerUsage.outputMsats;
|
|
6043
|
+
if (headerUsage.totalUsd) usage.totalUsd = headerUsage.totalUsd;
|
|
6044
|
+
}
|
|
5732
6045
|
}
|
|
5733
6046
|
const finalRequestId = requestId || "unknown";
|
|
5734
6047
|
const store = this.sdkStore ?? await getDefaultSdkStore();
|
|
@@ -5925,6 +6238,19 @@ var RoutstrClient = class {
|
|
|
5925
6238
|
}
|
|
5926
6239
|
return nextHeaders;
|
|
5927
6240
|
}
|
|
6241
|
+
/**
|
|
6242
|
+
* Attach auth headers and preserve the plaintext model hint required by the
|
|
6243
|
+
* Routstr proxy for Tinfoil/EHBP requests. EHBP encrypts the JSON body, so
|
|
6244
|
+
* retries/failover must not rebuild headers from baseHeaders alone or the
|
|
6245
|
+
* proxy cannot route/price the encrypted request.
|
|
6246
|
+
*/
|
|
6247
|
+
_withAuthAndTinfoilHeaders(headers, token, tinfoilEnabled, modelId) {
|
|
6248
|
+
const nextHeaders = this._withAuthHeader(headers, token);
|
|
6249
|
+
if (tinfoilEnabled && modelId) {
|
|
6250
|
+
nextHeaders["X-Routstr-Model"] = modelId;
|
|
6251
|
+
}
|
|
6252
|
+
return nextHeaders;
|
|
6253
|
+
}
|
|
5928
6254
|
};
|
|
5929
6255
|
|
|
5930
6256
|
// client/StreamProcessor.ts
|
|
@@ -6419,6 +6745,8 @@ async function createBunSqliteDriver(dbPath, options) {
|
|
|
6419
6745
|
const logger = (options?.logger ?? consoleLogger).child("BunSqliteDriver");
|
|
6420
6746
|
const SQLite = (await import('bun:sqlite')).default;
|
|
6421
6747
|
const db = new SQLite(dbPath);
|
|
6748
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
6749
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
6422
6750
|
db.run(`
|
|
6423
6751
|
CREATE TABLE IF NOT EXISTS sdk_storage (
|
|
6424
6752
|
key TEXT PRIMARY KEY,
|
|
@@ -6477,7 +6805,7 @@ var ADDED_COLUMNS = [
|
|
|
6477
6805
|
{ name: "cache_creation_msats", type: "REAL" },
|
|
6478
6806
|
{ name: "remaining_balance_msats", type: "REAL" }
|
|
6479
6807
|
];
|
|
6480
|
-
var
|
|
6808
|
+
var normalizeBaseUrl6 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
6481
6809
|
var buildWhereClause = (options = {}) => {
|
|
6482
6810
|
const clauses = [];
|
|
6483
6811
|
const params = [];
|
|
@@ -6495,7 +6823,7 @@ var buildWhereClause = (options = {}) => {
|
|
|
6495
6823
|
}
|
|
6496
6824
|
if (options.baseUrl) {
|
|
6497
6825
|
clauses.push("base_url = ?");
|
|
6498
|
-
params.push(
|
|
6826
|
+
params.push(normalizeBaseUrl6(options.baseUrl));
|
|
6499
6827
|
}
|
|
6500
6828
|
if (options.sessionId) {
|
|
6501
6829
|
clauses.push("session_id = ?");
|
|
@@ -6531,6 +6859,8 @@ var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
|
6531
6859
|
);
|
|
6532
6860
|
}
|
|
6533
6861
|
const db = new SQLiteDatabase(dbPath);
|
|
6862
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
6863
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
6534
6864
|
db.run(`
|
|
6535
6865
|
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
6536
6866
|
id TEXT PRIMARY KEY,
|
|
@@ -6577,7 +6907,7 @@ var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
|
6577
6907
|
entry.id,
|
|
6578
6908
|
entry.timestamp,
|
|
6579
6909
|
entry.modelId,
|
|
6580
|
-
|
|
6910
|
+
normalizeBaseUrl6(entry.baseUrl),
|
|
6581
6911
|
entry.requestId,
|
|
6582
6912
|
entry.cost,
|
|
6583
6913
|
entry.satsCost,
|
|
@@ -6728,6 +7058,6 @@ async function createDefaultBunSqliteDriver(dbPath = "routstr.sqlite", options)
|
|
|
6728
7058
|
return createBunSqliteDriver(dbPath, options);
|
|
6729
7059
|
}
|
|
6730
7060
|
|
|
6731
|
-
export { BalanceManager, BunModelManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, BunModelManager as ModelManager, ModelNotFoundError, NoProvidersAvailableError, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, consoleLogger, createBunModelManager, createBunSqliteDriver, createBunSqliteUsageTrackingDriver2 as createBunSqliteUsageTrackingDriver, createBunSqliteUsageTrackingDriver as createBunSqliteUsageTrackingDriverWithDatabase, createDefaultBunSqliteDriver, createDiscoveryAdapterFromStore, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromDiscoveryAdapter, createProviderRegistryFromStore, createSSEParserTransform, createSdkStore, createShardedDiscoveryAdapter, createStorageAdapterFromStore, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, getProviderEndpoints, inspectSSEWebStream, isOnionUrl, isTorContext, localStorageDriver, noopLogger, normalizeProviderUrl, routeRequests, setDefaultUsageTrackingDriver, toUsageStats };
|
|
7061
|
+
export { BalanceManager, BunModelManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, BunModelManager as ModelManager, ModelNotFoundError, NoProvidersAvailableError, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, clearTinfoilClientCache, consoleLogger, createBunModelManager, createBunSqliteDriver, createBunSqliteUsageTrackingDriver2 as createBunSqliteUsageTrackingDriver, createBunSqliteUsageTrackingDriver as createBunSqliteUsageTrackingDriverWithDatabase, createDefaultBunSqliteDriver, createDiscoveryAdapterFromStore, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromDiscoveryAdapter, createProviderRegistryFromStore, createSSEParserTransform, createSdkStore, createShardedDiscoveryAdapter, createStorageAdapterFromStore, extractResponseId, extractUsageFromResponseBody, extractUsageFromResponseHeaders, extractUsageFromSSEJson, fetchAIResponse, fetchTinfoilPreservingPlaintextErrors, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, getProviderEndpoints, getTinfoilUpstreamModelId, inspectSSEWebStream, isOnionUrl, isTinfoilModel, isTorContext, localStorageDriver, noopLogger, normalizeProviderUrl, prepareTinfoilClient, routeRequests, setDefaultUsageTrackingDriver, toUsageStats };
|
|
6732
7062
|
//# sourceMappingURL=bun.mjs.map
|
|
6733
7063
|
//# sourceMappingURL=bun.mjs.map
|