@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/index.d.mts
CHANGED
|
@@ -7,12 +7,13 @@ import { W as WalletAdapter, S as StorageAdapter, P as ProviderRegistry } from '
|
|
|
7
7
|
export { A as ApiKeyEntry, C as ChildKeyEntry, R as RoutstrClientOptions, a as StreamingCallbacks, X as XCashuTokenEntry } from './interfaces-Csn8Uq04.mjs';
|
|
8
8
|
export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.mjs';
|
|
9
9
|
import { DebugLevel, ProviderManager, RequestResponseLogSink, RoutstrClient } from './client/index.mjs';
|
|
10
|
-
export { AlertLevel, FetchAIResponseDeps, ModelProviderPrice, RequestResponseLogRequestInput, RouteRequestParams, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, UsageTrackingData, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, inspectSSEWebStream, toUsageStats } from './client/index.mjs';
|
|
10
|
+
export { AlertLevel, FetchAIResponseDeps, ModelProviderPrice, RequestResponseLogRequestInput, RouteRequestParams, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, TinfoilClientContext, TinfoilClientOptions, UsageTrackingData, clearTinfoilClientCache, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromResponseHeaders, extractUsageFromSSEJson, fetchAIResponse, fetchTinfoilPreservingPlaintextErrors, getTinfoilUpstreamModelId, inspectSSEWebStream, isTinfoilModel, prepareTinfoilClient, toUsageStats } from './client/index.mjs';
|
|
11
11
|
export { SDK_STORAGE_KEYS, ShardedDiscoveryAdapterOptions, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromDiscoveryAdapter, createShardedDiscoveryAdapter, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, localStorageDriver, setDefaultUsageTrackingDriver } from './storage/index.mjs';
|
|
12
12
|
import { c as UsageTrackingDriver, S as SdkStore } from './store-CuXwe5Rg.mjs';
|
|
13
13
|
export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, a as StorageDriver, U as UsageAggregateRow, b as UsageGroupBy, d as UsageTrackingEntry, e as createDiscoveryAdapterFromStore, f as createProviderRegistryFromStore, g as createSdkStore, h as createStorageAdapterFromStore } from './store-CuXwe5Rg.mjs';
|
|
14
14
|
import 'applesauce-core';
|
|
15
15
|
import 'stream';
|
|
16
|
+
import 'tinfoil';
|
|
16
17
|
import 'zustand/vanilla';
|
|
17
18
|
|
|
18
19
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -7,12 +7,13 @@ import { W as WalletAdapter, S as StorageAdapter, P as ProviderRegistry } from '
|
|
|
7
7
|
export { A as ApiKeyEntry, C as ChildKeyEntry, R as RoutstrClientOptions, a as StreamingCallbacks, X as XCashuTokenEntry } from './interfaces-C-DYd9Jy.js';
|
|
8
8
|
export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.js';
|
|
9
9
|
import { DebugLevel, ProviderManager, RequestResponseLogSink, RoutstrClient } from './client/index.js';
|
|
10
|
-
export { AlertLevel, FetchAIResponseDeps, ModelProviderPrice, RequestResponseLogRequestInput, RouteRequestParams, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, UsageTrackingData, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, inspectSSEWebStream, toUsageStats } from './client/index.js';
|
|
10
|
+
export { AlertLevel, FetchAIResponseDeps, ModelProviderPrice, RequestResponseLogRequestInput, RouteRequestParams, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, TinfoilClientContext, TinfoilClientOptions, UsageTrackingData, clearTinfoilClientCache, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromResponseHeaders, extractUsageFromSSEJson, fetchAIResponse, fetchTinfoilPreservingPlaintextErrors, getTinfoilUpstreamModelId, inspectSSEWebStream, isTinfoilModel, prepareTinfoilClient, toUsageStats } from './client/index.js';
|
|
11
11
|
export { SDK_STORAGE_KEYS, ShardedDiscoveryAdapterOptions, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromDiscoveryAdapter, createShardedDiscoveryAdapter, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, localStorageDriver, setDefaultUsageTrackingDriver } from './storage/index.js';
|
|
12
12
|
import { c as UsageTrackingDriver, S as SdkStore } from './store-CAQLSbEj.js';
|
|
13
13
|
export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, a as StorageDriver, U as UsageAggregateRow, b as UsageGroupBy, d as UsageTrackingEntry, e as createDiscoveryAdapterFromStore, f as createProviderRegistryFromStore, g as createSdkStore, h as createStorageAdapterFromStore } from './store-CAQLSbEj.js';
|
|
14
14
|
import 'applesauce-core';
|
|
15
15
|
import 'stream';
|
|
16
|
+
import 'tinfoil';
|
|
16
17
|
import 'zustand/vanilla';
|
|
17
18
|
|
|
18
19
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1860,15 +1860,27 @@ var BalanceManager = class _BalanceManager {
|
|
|
1860
1860
|
clearTimeout(timeoutId);
|
|
1861
1861
|
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
1862
1862
|
if (!response.ok) {
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1863
|
+
const responseBody = await response.text().catch(() => void 0);
|
|
1864
|
+
let errorData = {};
|
|
1865
|
+
if (responseBody) {
|
|
1866
|
+
try {
|
|
1867
|
+
errorData = JSON.parse(responseBody);
|
|
1868
|
+
} catch {
|
|
1869
|
+
errorData = {};
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
this.logger.error("Upstream wallet refund error response", {
|
|
1873
|
+
baseUrl,
|
|
1874
|
+
url,
|
|
1875
|
+
status: response.status,
|
|
1876
|
+
statusText: response.statusText,
|
|
1877
|
+
requestId,
|
|
1878
|
+
body: responseBody ?? "<unable to read response body>"
|
|
1879
|
+
});
|
|
1868
1880
|
return {
|
|
1869
1881
|
success: false,
|
|
1870
1882
|
requestId,
|
|
1871
|
-
error: `API key refund failed: ${errorData?.detail || response.statusText}`
|
|
1883
|
+
error: `API key refund failed: ${errorData?.detail || responseBody || response.statusText}`
|
|
1872
1884
|
};
|
|
1873
1885
|
}
|
|
1874
1886
|
const data = await response.json();
|
|
@@ -2204,11 +2216,27 @@ var BalanceManager = class _BalanceManager {
|
|
|
2204
2216
|
clearTimeout(timeoutId);
|
|
2205
2217
|
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
2206
2218
|
if (!response.ok) {
|
|
2207
|
-
const
|
|
2219
|
+
const responseBody = await response.text().catch(() => void 0);
|
|
2220
|
+
let errorData = {};
|
|
2221
|
+
if (responseBody) {
|
|
2222
|
+
try {
|
|
2223
|
+
errorData = JSON.parse(responseBody);
|
|
2224
|
+
} catch {
|
|
2225
|
+
errorData = {};
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
this.logger.error("Upstream wallet topup error response", {
|
|
2229
|
+
baseUrl,
|
|
2230
|
+
url,
|
|
2231
|
+
status: response.status,
|
|
2232
|
+
statusText: response.statusText,
|
|
2233
|
+
requestId,
|
|
2234
|
+
body: responseBody ?? "<unable to read response body>"
|
|
2235
|
+
});
|
|
2208
2236
|
return {
|
|
2209
2237
|
success: false,
|
|
2210
2238
|
requestId,
|
|
2211
|
-
error: errorData?.detail || `Top up failed with status ${response.status}`
|
|
2239
|
+
error: errorData?.detail || responseBody || `Top up failed with status ${response.status}`
|
|
2212
2240
|
};
|
|
2213
2241
|
}
|
|
2214
2242
|
return { success: true, requestId };
|
|
@@ -3041,7 +3069,7 @@ var openDatabase = (dbName, storeName) => {
|
|
|
3041
3069
|
return Promise.reject(new Error("IndexedDB is not available"));
|
|
3042
3070
|
}
|
|
3043
3071
|
return new Promise((resolve, reject) => {
|
|
3044
|
-
const request = indexedDB.open(dbName,
|
|
3072
|
+
const request = indexedDB.open(dbName, 3);
|
|
3045
3073
|
request.onupgradeneeded = () => {
|
|
3046
3074
|
const db = request.result;
|
|
3047
3075
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
@@ -3054,6 +3082,7 @@ var openDatabase = (dbName, storeName) => {
|
|
|
3054
3082
|
utStore.createIndex("baseUrl", "baseUrl", { unique: false });
|
|
3055
3083
|
utStore.createIndex("sessionId", "sessionId", { unique: false });
|
|
3056
3084
|
utStore.createIndex("client", "client", { unique: false });
|
|
3085
|
+
utStore.createIndex("provider", "provider", { unique: false });
|
|
3057
3086
|
}
|
|
3058
3087
|
if (storeName !== "sdk_storage" && !db.objectStoreNames.contains("sdk_storage")) {
|
|
3059
3088
|
db.createObjectStore("sdk_storage");
|
|
@@ -4616,6 +4645,29 @@ function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
|
4616
4645
|
}
|
|
4617
4646
|
return result;
|
|
4618
4647
|
}
|
|
4648
|
+
function extractUsageFromResponseHeaders(headers) {
|
|
4649
|
+
const get = (name) => {
|
|
4650
|
+
if (headers instanceof Headers) return headers.get(name);
|
|
4651
|
+
const lower = name.toLowerCase();
|
|
4652
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
4653
|
+
if (k.toLowerCase() === lower) return v;
|
|
4654
|
+
}
|
|
4655
|
+
return null;
|
|
4656
|
+
};
|
|
4657
|
+
const totalMsats = Number(get("X-Routstr-Cost-Msats"));
|
|
4658
|
+
if (!totalMsats || !Number.isFinite(totalMsats)) return null;
|
|
4659
|
+
return {
|
|
4660
|
+
promptTokens: 0,
|
|
4661
|
+
completionTokens: 0,
|
|
4662
|
+
totalTokens: 0,
|
|
4663
|
+
cost: Number(get("X-Routstr-Cost-Usd")) || 0,
|
|
4664
|
+
satsCost: totalMsats / 1e3,
|
|
4665
|
+
totalMsats,
|
|
4666
|
+
inputMsats: Number(get("X-Routstr-Input-Cost-Msats")) || 0,
|
|
4667
|
+
outputMsats: Number(get("X-Routstr-Output-Cost-Msats")) || 0,
|
|
4668
|
+
totalUsd: Number(get("X-Routstr-Cost-Usd")) || void 0
|
|
4669
|
+
};
|
|
4670
|
+
}
|
|
4619
4671
|
function toUsageStats(usage) {
|
|
4620
4672
|
if (!usage) return void 0;
|
|
4621
4673
|
return {
|
|
@@ -4702,14 +4754,11 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
|
|
|
4702
4754
|
}
|
|
4703
4755
|
const usage = extractUsageFromSSEJson(data);
|
|
4704
4756
|
if (usage) {
|
|
4705
|
-
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
4706
4757
|
const merged = mergeUsage(capturedUsage, usage);
|
|
4707
4758
|
if (hasUsageChanged(capturedUsage, merged)) {
|
|
4708
4759
|
capturedUsage = merged;
|
|
4709
|
-
console.log("[routstr:sse] \u2192 merged (changed):", merged);
|
|
4710
4760
|
onUsage(merged);
|
|
4711
4761
|
} else {
|
|
4712
|
-
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
4713
4762
|
}
|
|
4714
4763
|
}
|
|
4715
4764
|
} catch {
|
|
@@ -4866,6 +4915,177 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
4866
4915
|
});
|
|
4867
4916
|
}
|
|
4868
4917
|
|
|
4918
|
+
// client/TinfoilSecure.ts
|
|
4919
|
+
var TINFOIL_MODEL_PREFIX = "tinfoil-";
|
|
4920
|
+
var clientCache = /* @__PURE__ */ new Map();
|
|
4921
|
+
function isTinfoilModel(modelId) {
|
|
4922
|
+
return modelId.startsWith(TINFOIL_MODEL_PREFIX);
|
|
4923
|
+
}
|
|
4924
|
+
function getTinfoilUpstreamModelId(modelId) {
|
|
4925
|
+
return modelId.slice(TINFOIL_MODEL_PREFIX.length);
|
|
4926
|
+
}
|
|
4927
|
+
function normalizeBaseUrl5(baseUrl) {
|
|
4928
|
+
return baseUrl.replace(/\/+$/, "");
|
|
4929
|
+
}
|
|
4930
|
+
function cacheKey(options) {
|
|
4931
|
+
return JSON.stringify({
|
|
4932
|
+
baseUrl: options.baseUrl,
|
|
4933
|
+
attestationBundleURL: options.attestationBundleURL,
|
|
4934
|
+
enclaveURL: options.enclaveURL,
|
|
4935
|
+
configRepo: options.configRepo
|
|
4936
|
+
});
|
|
4937
|
+
}
|
|
4938
|
+
function envOrUndefined(name) {
|
|
4939
|
+
const maybeProcess = globalThis;
|
|
4940
|
+
const value = maybeProcess.process?.env?.[name];
|
|
4941
|
+
return value && value.trim() ? value.trim() : void 0;
|
|
4942
|
+
}
|
|
4943
|
+
function resolveOptions(options) {
|
|
4944
|
+
return {
|
|
4945
|
+
...options,
|
|
4946
|
+
baseUrl: normalizeBaseUrl5(options.baseUrl),
|
|
4947
|
+
attestationBundleURL: options.attestationBundleURL ?? envOrUndefined("ROUTSTR_TINFOIL_ATTESTATION_BUNDLE_URL"),
|
|
4948
|
+
enclaveURL: options.enclaveURL ?? envOrUndefined("ROUTSTR_TINFOIL_ENCLAVE_URL"),
|
|
4949
|
+
configRepo: options.configRepo ?? envOrUndefined("ROUTSTR_TINFOIL_CONFIG_REPO")
|
|
4950
|
+
};
|
|
4951
|
+
}
|
|
4952
|
+
async function prepareTinfoilClient(options) {
|
|
4953
|
+
const resolved = resolveOptions(options);
|
|
4954
|
+
const key = cacheKey(resolved);
|
|
4955
|
+
let pending = clientCache.get(key);
|
|
4956
|
+
if (!pending) {
|
|
4957
|
+
pending = (async () => {
|
|
4958
|
+
const { SecureClient } = await import('tinfoil');
|
|
4959
|
+
const client = new SecureClient({
|
|
4960
|
+
// baseURL is the proxy/provider URL that receives the EHBP request.
|
|
4961
|
+
baseURL: resolved.baseUrl,
|
|
4962
|
+
// Leave undefined by default so tinfoil uses its public ATC. If set,
|
|
4963
|
+
// SecureClient will fetch `${attestationBundleURL}/attestation`.
|
|
4964
|
+
attestationBundleURL: resolved.attestationBundleURL,
|
|
4965
|
+
enclaveURL: resolved.enclaveURL,
|
|
4966
|
+
configRepo: resolved.configRepo,
|
|
4967
|
+
transport: "ehbp"
|
|
4968
|
+
});
|
|
4969
|
+
await client.ready();
|
|
4970
|
+
const verification = client.getVerificationDocument();
|
|
4971
|
+
return { client, verification };
|
|
4972
|
+
})();
|
|
4973
|
+
clientCache.set(key, pending);
|
|
4974
|
+
}
|
|
4975
|
+
try {
|
|
4976
|
+
return await pending;
|
|
4977
|
+
} catch (error) {
|
|
4978
|
+
clientCache.delete(key);
|
|
4979
|
+
throw error;
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
function normalizeFetchArgs(input, init) {
|
|
4983
|
+
if (typeof input === "string") {
|
|
4984
|
+
return { url: input, init };
|
|
4985
|
+
}
|
|
4986
|
+
if (input instanceof URL) {
|
|
4987
|
+
return { url: input.toString(), init };
|
|
4988
|
+
}
|
|
4989
|
+
const cloned = input.clone();
|
|
4990
|
+
return {
|
|
4991
|
+
url: cloned.url,
|
|
4992
|
+
init: {
|
|
4993
|
+
method: cloned.method,
|
|
4994
|
+
headers: new Headers(cloned.headers),
|
|
4995
|
+
body: cloned.body ?? void 0,
|
|
4996
|
+
signal: cloned.signal,
|
|
4997
|
+
...init
|
|
4998
|
+
}
|
|
4999
|
+
};
|
|
5000
|
+
}
|
|
5001
|
+
function isProblemJsonContentType(contentType) {
|
|
5002
|
+
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
5003
|
+
return mediaType === "application/problem+json";
|
|
5004
|
+
}
|
|
5005
|
+
async function isEhbpKeyConfigMismatchResponse(response, protocol) {
|
|
5006
|
+
if (response.status !== 422) {
|
|
5007
|
+
return false;
|
|
5008
|
+
}
|
|
5009
|
+
if (!isProblemJsonContentType(response.headers.get("content-type"))) {
|
|
5010
|
+
return false;
|
|
5011
|
+
}
|
|
5012
|
+
try {
|
|
5013
|
+
const problem = await response.clone().json();
|
|
5014
|
+
return problem?.type === protocol.KEY_CONFIG_PROBLEM_TYPE;
|
|
5015
|
+
} catch {
|
|
5016
|
+
return false;
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
async function fetchTinfoilEhbpOnce(context, options, normalized, ehbp) {
|
|
5020
|
+
const { Identity, PROTOCOL, decryptResponseWithToken, extractSessionRecoveryToken } = ehbp;
|
|
5021
|
+
const resolved = resolveOptions(options);
|
|
5022
|
+
const baseURL = context.client.getBaseURL() ?? resolved.baseUrl;
|
|
5023
|
+
const enclaveURL = context.client.getEnclaveURL();
|
|
5024
|
+
const baseOrigin = new URL(baseURL).origin;
|
|
5025
|
+
const allowedOrigins = /* @__PURE__ */ new Set([baseOrigin]);
|
|
5026
|
+
if (enclaveURL) {
|
|
5027
|
+
allowedOrigins.add(new URL(enclaveURL).origin);
|
|
5028
|
+
}
|
|
5029
|
+
const targetUrl = new URL(normalized.url, baseURL);
|
|
5030
|
+
if (!allowedOrigins.has(targetUrl.origin)) {
|
|
5031
|
+
throw new Error(
|
|
5032
|
+
`refusing to send Tinfoil request to ${targetUrl.origin}: client is bound to the verified enclave/proxy`
|
|
5033
|
+
);
|
|
5034
|
+
}
|
|
5035
|
+
const headers = new Headers(normalized.init?.headers);
|
|
5036
|
+
if (enclaveURL && new URL(enclaveURL).origin !== baseOrigin) {
|
|
5037
|
+
headers.set("X-Tinfoil-Enclave-Url", enclaveURL);
|
|
5038
|
+
}
|
|
5039
|
+
const method = normalized.init?.method ?? "GET";
|
|
5040
|
+
const body = normalized.init?.body ?? null;
|
|
5041
|
+
const serverIdentity = await Identity.fromPublicKeyHex(
|
|
5042
|
+
context.verification.hpkePublicKey
|
|
5043
|
+
);
|
|
5044
|
+
const request = new Request(targetUrl.toString(), {
|
|
5045
|
+
method,
|
|
5046
|
+
headers,
|
|
5047
|
+
body,
|
|
5048
|
+
duplex: "half"
|
|
5049
|
+
});
|
|
5050
|
+
const { request: encryptedRequest, context: requestContext } = await serverIdentity.encryptRequestWithContext(request);
|
|
5051
|
+
const response = await fetch(encryptedRequest);
|
|
5052
|
+
if (!requestContext) {
|
|
5053
|
+
return response;
|
|
5054
|
+
}
|
|
5055
|
+
if (await isEhbpKeyConfigMismatchResponse(response, PROTOCOL)) {
|
|
5056
|
+
throw new ehbp.KeyConfigMismatchError("EHBP key configuration mismatch");
|
|
5057
|
+
}
|
|
5058
|
+
if (!response.headers.get(PROTOCOL.RESPONSE_NONCE_HEADER)) {
|
|
5059
|
+
return response;
|
|
5060
|
+
}
|
|
5061
|
+
const token = await extractSessionRecoveryToken(requestContext);
|
|
5062
|
+
return await decryptResponseWithToken(response, token);
|
|
5063
|
+
}
|
|
5064
|
+
async function fetchTinfoilPreservingPlaintextErrors(options, input, init) {
|
|
5065
|
+
const context = await prepareTinfoilClient(options);
|
|
5066
|
+
const ehbp = await import('ehbp');
|
|
5067
|
+
const normalized = normalizeFetchArgs(input, init);
|
|
5068
|
+
try {
|
|
5069
|
+
return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
|
|
5070
|
+
} catch (error) {
|
|
5071
|
+
if (error instanceof ehbp.KeyConfigMismatchError) {
|
|
5072
|
+
context.client.reset();
|
|
5073
|
+
try {
|
|
5074
|
+
await context.client.ready();
|
|
5075
|
+
context.verification = context.client.getVerificationDocument();
|
|
5076
|
+
} catch (reattestError) {
|
|
5077
|
+
clientCache.delete(cacheKey(resolveOptions(options)));
|
|
5078
|
+
throw reattestError;
|
|
5079
|
+
}
|
|
5080
|
+
return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
|
|
5081
|
+
}
|
|
5082
|
+
throw error;
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
function clearTinfoilClientCache() {
|
|
5086
|
+
clientCache.clear();
|
|
5087
|
+
}
|
|
5088
|
+
|
|
4869
5089
|
// client/RoutstrClient.ts
|
|
4870
5090
|
var TOPUP_MARGIN = 1.2;
|
|
4871
5091
|
var RoutstrClient = class {
|
|
@@ -5051,11 +5271,6 @@ var RoutstrClient = class {
|
|
|
5051
5271
|
);
|
|
5052
5272
|
}
|
|
5053
5273
|
}
|
|
5054
|
-
const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = await this._spendToken({
|
|
5055
|
-
mintUrl,
|
|
5056
|
-
amount: requiredSats,
|
|
5057
|
-
baseUrl
|
|
5058
|
-
});
|
|
5059
5274
|
let requestBody = body;
|
|
5060
5275
|
if (body && typeof body === "object") {
|
|
5061
5276
|
const bodyObj = body;
|
|
@@ -5064,7 +5279,36 @@ var RoutstrClient = class {
|
|
|
5064
5279
|
}
|
|
5065
5280
|
}
|
|
5066
5281
|
const baseHeaders = this._buildBaseHeaders();
|
|
5067
|
-
const
|
|
5282
|
+
const tinfoilEnabled = Boolean(modelId && isTinfoilModel(modelId));
|
|
5283
|
+
if (tinfoilEnabled) {
|
|
5284
|
+
this._log(
|
|
5285
|
+
"DEBUG",
|
|
5286
|
+
`[RoutstrClient] Attesting Tinfoil model ${modelId} before spend`
|
|
5287
|
+
);
|
|
5288
|
+
const { verification } = await prepareTinfoilClient({ baseUrl });
|
|
5289
|
+
this._log(
|
|
5290
|
+
"DEBUG",
|
|
5291
|
+
`[RoutstrClient] Tinfoil attestation passed, enclave=${verification.enclaveHost}, codeFingerprint=${verification.codeFingerprint.slice(0, 16)}...`
|
|
5292
|
+
);
|
|
5293
|
+
if (requestBody && typeof requestBody === "object" && modelId) {
|
|
5294
|
+
requestBody = {
|
|
5295
|
+
...requestBody,
|
|
5296
|
+
model: getTinfoilUpstreamModelId(modelId)
|
|
5297
|
+
};
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
const spendResult = await this._spendToken({
|
|
5301
|
+
mintUrl,
|
|
5302
|
+
amount: requiredSats,
|
|
5303
|
+
baseUrl
|
|
5304
|
+
});
|
|
5305
|
+
const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = spendResult;
|
|
5306
|
+
const finalHeaders = this._withAuthAndTinfoilHeaders(
|
|
5307
|
+
baseHeaders,
|
|
5308
|
+
token,
|
|
5309
|
+
tinfoilEnabled,
|
|
5310
|
+
modelId
|
|
5311
|
+
);
|
|
5068
5312
|
const response = await this._makeRequest({
|
|
5069
5313
|
path: requestPath,
|
|
5070
5314
|
method,
|
|
@@ -5073,9 +5317,10 @@ var RoutstrClient = class {
|
|
|
5073
5317
|
mintUrl,
|
|
5074
5318
|
token,
|
|
5075
5319
|
requiredSats,
|
|
5076
|
-
headers:
|
|
5320
|
+
headers: finalHeaders,
|
|
5077
5321
|
baseHeaders,
|
|
5078
|
-
selectedModel
|
|
5322
|
+
selectedModel,
|
|
5323
|
+
tinfoilEnabled
|
|
5079
5324
|
});
|
|
5080
5325
|
let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
5081
5326
|
let initialTokenBalanceUnknown = tokenBalanceUnknown;
|
|
@@ -5163,7 +5408,7 @@ var RoutstrClient = class {
|
|
|
5163
5408
|
* Make the API request with failover support
|
|
5164
5409
|
*/
|
|
5165
5410
|
async _makeRequest(params) {
|
|
5166
|
-
const { path, method, body, baseUrl, token, headers } = params;
|
|
5411
|
+
const { path, method, body, baseUrl, token, headers, tinfoilEnabled } = params;
|
|
5167
5412
|
try {
|
|
5168
5413
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
5169
5414
|
const requestBodyText = body === void 0 || method === "GET" ? void 0 : JSON.stringify(body);
|
|
@@ -5177,7 +5422,15 @@ var RoutstrClient = class {
|
|
|
5177
5422
|
rawBody: requestBodyText
|
|
5178
5423
|
});
|
|
5179
5424
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
5180
|
-
const response = await
|
|
5425
|
+
const response = tinfoilEnabled ? await fetchTinfoilPreservingPlaintextErrors(
|
|
5426
|
+
{ baseUrl },
|
|
5427
|
+
url,
|
|
5428
|
+
{
|
|
5429
|
+
method,
|
|
5430
|
+
headers,
|
|
5431
|
+
body: requestBodyText
|
|
5432
|
+
}
|
|
5433
|
+
) : await fetch(url, {
|
|
5181
5434
|
method,
|
|
5182
5435
|
headers,
|
|
5183
5436
|
body: requestBodyText
|
|
@@ -5197,6 +5450,15 @@ var RoutstrClient = class {
|
|
|
5197
5450
|
} catch (e) {
|
|
5198
5451
|
bodyText = void 0;
|
|
5199
5452
|
}
|
|
5453
|
+
this._log("ERROR", "[RoutstrClient] Upstream error response", {
|
|
5454
|
+
baseUrl,
|
|
5455
|
+
url,
|
|
5456
|
+
path,
|
|
5457
|
+
status: response.status,
|
|
5458
|
+
statusText: response.statusText,
|
|
5459
|
+
requestId,
|
|
5460
|
+
body: bodyText ?? "<unable to read response body>"
|
|
5461
|
+
});
|
|
5200
5462
|
return await this._handleErrorResponse(
|
|
5201
5463
|
params,
|
|
5202
5464
|
token,
|
|
@@ -5227,6 +5489,9 @@ var RoutstrClient = class {
|
|
|
5227
5489
|
throw error;
|
|
5228
5490
|
}
|
|
5229
5491
|
}
|
|
5492
|
+
/**
|
|
5493
|
+
* Store request details to a file in the reqs/ folder before fetch.
|
|
5494
|
+
*/
|
|
5230
5495
|
/**
|
|
5231
5496
|
* Handle error responses with failover
|
|
5232
5497
|
*/
|
|
@@ -5378,7 +5643,12 @@ var RoutstrClient = class {
|
|
|
5378
5643
|
return this._makeRequest({
|
|
5379
5644
|
...params,
|
|
5380
5645
|
token: params.token,
|
|
5381
|
-
headers: this.
|
|
5646
|
+
headers: this._withAuthAndTinfoilHeaders(
|
|
5647
|
+
params.baseHeaders,
|
|
5648
|
+
params.token,
|
|
5649
|
+
params.tinfoilEnabled,
|
|
5650
|
+
params.selectedModel?.id
|
|
5651
|
+
),
|
|
5382
5652
|
retryCount: retryCount + 1
|
|
5383
5653
|
});
|
|
5384
5654
|
} else {
|
|
@@ -5439,7 +5709,12 @@ var RoutstrClient = class {
|
|
|
5439
5709
|
return this._makeRequest({
|
|
5440
5710
|
...params,
|
|
5441
5711
|
token: retryToken,
|
|
5442
|
-
headers: this.
|
|
5712
|
+
headers: this._withAuthAndTinfoilHeaders(
|
|
5713
|
+
params.baseHeaders,
|
|
5714
|
+
retryToken,
|
|
5715
|
+
params.tinfoilEnabled,
|
|
5716
|
+
params.selectedModel?.id
|
|
5717
|
+
),
|
|
5443
5718
|
retryCount: retryCount + 1
|
|
5444
5719
|
});
|
|
5445
5720
|
} else {
|
|
@@ -5534,6 +5809,13 @@ var RoutstrClient = class {
|
|
|
5534
5809
|
messagesForPricing,
|
|
5535
5810
|
params.maxTokens
|
|
5536
5811
|
);
|
|
5812
|
+
if (params.tinfoilEnabled) {
|
|
5813
|
+
this._log(
|
|
5814
|
+
"DEBUG",
|
|
5815
|
+
`[RoutstrClient] _handleErrorResponse: Attesting Tinfoil failover provider ${nextProvider} before spend`
|
|
5816
|
+
);
|
|
5817
|
+
await prepareTinfoilClient({ baseUrl: nextProvider });
|
|
5818
|
+
}
|
|
5537
5819
|
this._log(
|
|
5538
5820
|
"DEBUG",
|
|
5539
5821
|
`[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
|
|
@@ -5552,7 +5834,12 @@ var RoutstrClient = class {
|
|
|
5552
5834
|
selectedModel: newModel,
|
|
5553
5835
|
token: spendResult.token,
|
|
5554
5836
|
requiredSats: newRequiredSats,
|
|
5555
|
-
headers: this.
|
|
5837
|
+
headers: this._withAuthAndTinfoilHeaders(
|
|
5838
|
+
params.baseHeaders,
|
|
5839
|
+
spendResult.token,
|
|
5840
|
+
params.tinfoilEnabled,
|
|
5841
|
+
newModel.id
|
|
5842
|
+
),
|
|
5556
5843
|
retryCount: 0
|
|
5557
5844
|
});
|
|
5558
5845
|
retryResponse.initialTokenBalanceInSats = spendResult.tokenBalanceUnit === "msat" ? spendResult.tokenBalance / 1e3 : spendResult.tokenBalance;
|
|
@@ -5619,10 +5906,10 @@ var RoutstrClient = class {
|
|
|
5619
5906
|
if (latestTokenBalance !== void 0) {
|
|
5620
5907
|
this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
|
|
5621
5908
|
}
|
|
5622
|
-
satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
5909
|
+
satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
|
|
5623
5910
|
} catch (e) {
|
|
5624
5911
|
this._log("WARN", "Could not get updated API key balance:", e);
|
|
5625
|
-
satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
5912
|
+
satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
|
|
5626
5913
|
}
|
|
5627
5914
|
}
|
|
5628
5915
|
await this._trackResponseUsage({
|
|
@@ -5639,6 +5926,15 @@ var RoutstrClient = class {
|
|
|
5639
5926
|
})();
|
|
5640
5927
|
return satsSpent;
|
|
5641
5928
|
}
|
|
5929
|
+
/**
|
|
5930
|
+
* Extract sats cost from EHBP/Tinfoil response headers as a last-resort
|
|
5931
|
+
* fallback when neither balance delta nor SSE/body usage provides a cost.
|
|
5932
|
+
*/
|
|
5933
|
+
_headerSatsCost(response) {
|
|
5934
|
+
if (!response) return void 0;
|
|
5935
|
+
const headerUsage = extractUsageFromResponseHeaders(response.headers);
|
|
5936
|
+
return headerUsage?.satsCost;
|
|
5937
|
+
}
|
|
5642
5938
|
async _trackResponseUsage(params) {
|
|
5643
5939
|
const {
|
|
5644
5940
|
token,
|
|
@@ -5672,7 +5968,24 @@ var RoutstrClient = class {
|
|
|
5672
5968
|
}
|
|
5673
5969
|
}
|
|
5674
5970
|
if (!usage) {
|
|
5675
|
-
|
|
5971
|
+
const headerUsage = extractUsageFromResponseHeaders(response.headers);
|
|
5972
|
+
if (headerUsage) {
|
|
5973
|
+
usage = headerUsage;
|
|
5974
|
+
} else {
|
|
5975
|
+
return;
|
|
5976
|
+
}
|
|
5977
|
+
} else {
|
|
5978
|
+
const headerUsage = extractUsageFromResponseHeaders(response.headers);
|
|
5979
|
+
if (headerUsage) {
|
|
5980
|
+
if (headerUsage.totalMsats) {
|
|
5981
|
+
usage.totalMsats = headerUsage.totalMsats;
|
|
5982
|
+
usage.satsCost = headerUsage.satsCost;
|
|
5983
|
+
}
|
|
5984
|
+
if (headerUsage.cost) usage.cost = headerUsage.cost;
|
|
5985
|
+
if (headerUsage.inputMsats) usage.inputMsats = headerUsage.inputMsats;
|
|
5986
|
+
if (headerUsage.outputMsats) usage.outputMsats = headerUsage.outputMsats;
|
|
5987
|
+
if (headerUsage.totalUsd) usage.totalUsd = headerUsage.totalUsd;
|
|
5988
|
+
}
|
|
5676
5989
|
}
|
|
5677
5990
|
const finalRequestId = requestId || "unknown";
|
|
5678
5991
|
const store = this.sdkStore ?? await getDefaultSdkStore();
|
|
@@ -5869,6 +6182,19 @@ var RoutstrClient = class {
|
|
|
5869
6182
|
}
|
|
5870
6183
|
return nextHeaders;
|
|
5871
6184
|
}
|
|
6185
|
+
/**
|
|
6186
|
+
* Attach auth headers and preserve the plaintext model hint required by the
|
|
6187
|
+
* Routstr proxy for Tinfoil/EHBP requests. EHBP encrypts the JSON body, so
|
|
6188
|
+
* retries/failover must not rebuild headers from baseHeaders alone or the
|
|
6189
|
+
* proxy cannot route/price the encrypted request.
|
|
6190
|
+
*/
|
|
6191
|
+
_withAuthAndTinfoilHeaders(headers, token, tinfoilEnabled, modelId) {
|
|
6192
|
+
const nextHeaders = this._withAuthHeader(headers, token);
|
|
6193
|
+
if (tinfoilEnabled && modelId) {
|
|
6194
|
+
nextHeaders["X-Routstr-Model"] = modelId;
|
|
6195
|
+
}
|
|
6196
|
+
return nextHeaders;
|
|
6197
|
+
}
|
|
5872
6198
|
};
|
|
5873
6199
|
|
|
5874
6200
|
// client/StreamProcessor.ts
|
|
@@ -6376,6 +6702,7 @@ exports.SDK_STORAGE_KEYS = SDK_STORAGE_KEYS;
|
|
|
6376
6702
|
exports.StreamProcessor = StreamProcessor;
|
|
6377
6703
|
exports.StreamingError = StreamingError;
|
|
6378
6704
|
exports.TokenOperationError = TokenOperationError;
|
|
6705
|
+
exports.clearTinfoilClientCache = clearTinfoilClientCache;
|
|
6379
6706
|
exports.consoleLogger = consoleLogger;
|
|
6380
6707
|
exports.createDiscoveryAdapterFromStore = createDiscoveryAdapterFromStore;
|
|
6381
6708
|
exports.createIndexedDBDriver = createIndexedDBDriver;
|
|
@@ -6390,8 +6717,10 @@ exports.createShardedDiscoveryAdapter = createShardedDiscoveryAdapter;
|
|
|
6390
6717
|
exports.createStorageAdapterFromStore = createStorageAdapterFromStore;
|
|
6391
6718
|
exports.extractResponseId = extractResponseId;
|
|
6392
6719
|
exports.extractUsageFromResponseBody = extractUsageFromResponseBody;
|
|
6720
|
+
exports.extractUsageFromResponseHeaders = extractUsageFromResponseHeaders;
|
|
6393
6721
|
exports.extractUsageFromSSEJson = extractUsageFromSSEJson;
|
|
6394
6722
|
exports.fetchAIResponse = fetchAIResponse;
|
|
6723
|
+
exports.fetchTinfoilPreservingPlaintextErrors = fetchTinfoilPreservingPlaintextErrors;
|
|
6395
6724
|
exports.filterBaseUrlsForTor = filterBaseUrlsForTor;
|
|
6396
6725
|
exports.getDefaultDiscoveryAdapter = getDefaultDiscoveryAdapter;
|
|
6397
6726
|
exports.getDefaultProviderRegistry = getDefaultProviderRegistry;
|
|
@@ -6400,12 +6729,15 @@ exports.getDefaultSdkStore = getDefaultSdkStore;
|
|
|
6400
6729
|
exports.getDefaultStorageAdapter = getDefaultStorageAdapter;
|
|
6401
6730
|
exports.getDefaultUsageTrackingDriver = getDefaultUsageTrackingDriver;
|
|
6402
6731
|
exports.getProviderEndpoints = getProviderEndpoints;
|
|
6732
|
+
exports.getTinfoilUpstreamModelId = getTinfoilUpstreamModelId;
|
|
6403
6733
|
exports.inspectSSEWebStream = inspectSSEWebStream;
|
|
6404
6734
|
exports.isOnionUrl = isOnionUrl;
|
|
6735
|
+
exports.isTinfoilModel = isTinfoilModel;
|
|
6405
6736
|
exports.isTorContext = isTorContext;
|
|
6406
6737
|
exports.localStorageDriver = localStorageDriver;
|
|
6407
6738
|
exports.noopLogger = noopLogger;
|
|
6408
6739
|
exports.normalizeProviderUrl = normalizeProviderUrl;
|
|
6740
|
+
exports.prepareTinfoilClient = prepareTinfoilClient;
|
|
6409
6741
|
exports.routeRequests = routeRequests;
|
|
6410
6742
|
exports.setDefaultUsageTrackingDriver = setDefaultUsageTrackingDriver;
|
|
6411
6743
|
exports.toUsageStats = toUsageStats;
|