@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.d.mts
CHANGED
|
@@ -7,13 +7,14 @@ import { ModelManager, ModelManagerConfig } from './discovery/index.mjs';
|
|
|
7
7
|
export { MintDiscovery } from './discovery/index.mjs';
|
|
8
8
|
export { A as ApiKeyEntry, C as ChildKeyEntry, P as ProviderRegistry, R as RoutstrClientOptions, S as StorageAdapter, a as StreamingCallbacks, W as WalletAdapter, X as XCashuTokenEntry } from './interfaces-Csn8Uq04.mjs';
|
|
9
9
|
export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.mjs';
|
|
10
|
-
export { AlertLevel, DebugLevel, FetchAIResponseDeps, ModelProviderPrice, ProviderManager, RequestResponseLogRequestInput, RequestResponseLogSink, RouteRequestParams, RoutstrClient, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, UsageTrackingData, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, inspectSSEWebStream, toUsageStats } from './client/index.mjs';
|
|
10
|
+
export { AlertLevel, DebugLevel, FetchAIResponseDeps, ModelProviderPrice, ProviderManager, RequestResponseLogRequestInput, RequestResponseLogSink, RouteRequestParams, RoutstrClient, 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
|
import { B as BunSqliteUsageTrackingDriverOptions } from './bunSqlite-Bro9efsl.mjs';
|
|
12
12
|
export { c as createBunSqliteDriver, a as createBunSqliteUsageTrackingDriverWithDatabase } from './bunSqlite-Bro9efsl.mjs';
|
|
13
13
|
import { c as UsageTrackingDriver, a as StorageDriver } from './store-CuXwe5Rg.mjs';
|
|
14
14
|
export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, S as SdkStore, 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';
|
|
15
15
|
import 'applesauce-core';
|
|
16
16
|
import 'stream';
|
|
17
|
+
import 'tinfoil';
|
|
17
18
|
import 'zustand/vanilla';
|
|
18
19
|
|
|
19
20
|
declare class BunModelManager extends ModelManager {
|
package/dist/bun.d.ts
CHANGED
|
@@ -7,13 +7,14 @@ import { ModelManager, ModelManagerConfig } from './discovery/index.js';
|
|
|
7
7
|
export { MintDiscovery } from './discovery/index.js';
|
|
8
8
|
export { A as ApiKeyEntry, C as ChildKeyEntry, P as ProviderRegistry, R as RoutstrClientOptions, S as StorageAdapter, a as StreamingCallbacks, W as WalletAdapter, X as XCashuTokenEntry } from './interfaces-C-DYd9Jy.js';
|
|
9
9
|
export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, SpendOptions, TopUpOptions } from './wallet/index.js';
|
|
10
|
-
export { AlertLevel, DebugLevel, FetchAIResponseDeps, ModelProviderPrice, ProviderManager, RequestResponseLogRequestInput, RequestResponseLogSink, RouteRequestParams, RoutstrClient, RoutstrClientConfig, RoutstrClientMode, StreamCallbacks, StreamProcessor, UsageTrackingData, createSSEParserTransform, extractResponseId, extractUsageFromResponseBody, extractUsageFromSSEJson, fetchAIResponse, inspectSSEWebStream, toUsageStats } from './client/index.js';
|
|
10
|
+
export { AlertLevel, DebugLevel, FetchAIResponseDeps, ModelProviderPrice, ProviderManager, RequestResponseLogRequestInput, RequestResponseLogSink, RouteRequestParams, RoutstrClient, 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
|
import { B as BunSqliteUsageTrackingDriverOptions } from './bunSqlite-BmXWNc25.js';
|
|
12
12
|
export { c as createBunSqliteDriver, a as createBunSqliteUsageTrackingDriverWithDatabase } from './bunSqlite-BmXWNc25.js';
|
|
13
13
|
import { c as UsageTrackingDriver, a as StorageDriver } from './store-CAQLSbEj.js';
|
|
14
14
|
export { A as AggregateUsageOptions, L as ListUsageTrackingOptions, S as SdkStore, 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';
|
|
15
15
|
import 'applesauce-core';
|
|
16
16
|
import 'stream';
|
|
17
|
+
import 'tinfoil';
|
|
17
18
|
import 'zustand/vanilla';
|
|
18
19
|
|
|
19
20
|
declare class BunModelManager extends ModelManager {
|
package/dist/bun.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");
|
|
@@ -4674,6 +4703,29 @@ function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
|
4674
4703
|
}
|
|
4675
4704
|
return result;
|
|
4676
4705
|
}
|
|
4706
|
+
function extractUsageFromResponseHeaders(headers) {
|
|
4707
|
+
const get = (name) => {
|
|
4708
|
+
if (headers instanceof Headers) return headers.get(name);
|
|
4709
|
+
const lower = name.toLowerCase();
|
|
4710
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
4711
|
+
if (k.toLowerCase() === lower) return v;
|
|
4712
|
+
}
|
|
4713
|
+
return null;
|
|
4714
|
+
};
|
|
4715
|
+
const totalMsats = Number(get("X-Routstr-Cost-Msats"));
|
|
4716
|
+
if (!totalMsats || !Number.isFinite(totalMsats)) return null;
|
|
4717
|
+
return {
|
|
4718
|
+
promptTokens: 0,
|
|
4719
|
+
completionTokens: 0,
|
|
4720
|
+
totalTokens: 0,
|
|
4721
|
+
cost: Number(get("X-Routstr-Cost-Usd")) || 0,
|
|
4722
|
+
satsCost: totalMsats / 1e3,
|
|
4723
|
+
totalMsats,
|
|
4724
|
+
inputMsats: Number(get("X-Routstr-Input-Cost-Msats")) || 0,
|
|
4725
|
+
outputMsats: Number(get("X-Routstr-Output-Cost-Msats")) || 0,
|
|
4726
|
+
totalUsd: Number(get("X-Routstr-Cost-Usd")) || void 0
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4677
4729
|
function toUsageStats(usage) {
|
|
4678
4730
|
if (!usage) return void 0;
|
|
4679
4731
|
return {
|
|
@@ -4760,14 +4812,11 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
|
|
|
4760
4812
|
}
|
|
4761
4813
|
const usage = extractUsageFromSSEJson(data);
|
|
4762
4814
|
if (usage) {
|
|
4763
|
-
console.log("[routstr:sse] \u2192 usage detected:", usage);
|
|
4764
4815
|
const merged = mergeUsage(capturedUsage, usage);
|
|
4765
4816
|
if (hasUsageChanged(capturedUsage, merged)) {
|
|
4766
4817
|
capturedUsage = merged;
|
|
4767
|
-
console.log("[routstr:sse] \u2192 merged (changed):", merged);
|
|
4768
4818
|
onUsage(merged);
|
|
4769
4819
|
} else {
|
|
4770
|
-
console.log("[routstr:sse] \u2192 merged (no change)");
|
|
4771
4820
|
}
|
|
4772
4821
|
}
|
|
4773
4822
|
} catch {
|
|
@@ -4924,6 +4973,177 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
4924
4973
|
});
|
|
4925
4974
|
}
|
|
4926
4975
|
|
|
4976
|
+
// client/TinfoilSecure.ts
|
|
4977
|
+
var TINFOIL_MODEL_PREFIX = "tinfoil-";
|
|
4978
|
+
var clientCache = /* @__PURE__ */ new Map();
|
|
4979
|
+
function isTinfoilModel(modelId) {
|
|
4980
|
+
return modelId.startsWith(TINFOIL_MODEL_PREFIX);
|
|
4981
|
+
}
|
|
4982
|
+
function getTinfoilUpstreamModelId(modelId) {
|
|
4983
|
+
return modelId.slice(TINFOIL_MODEL_PREFIX.length);
|
|
4984
|
+
}
|
|
4985
|
+
function normalizeBaseUrl5(baseUrl) {
|
|
4986
|
+
return baseUrl.replace(/\/+$/, "");
|
|
4987
|
+
}
|
|
4988
|
+
function cacheKey(options) {
|
|
4989
|
+
return JSON.stringify({
|
|
4990
|
+
baseUrl: options.baseUrl,
|
|
4991
|
+
attestationBundleURL: options.attestationBundleURL,
|
|
4992
|
+
enclaveURL: options.enclaveURL,
|
|
4993
|
+
configRepo: options.configRepo
|
|
4994
|
+
});
|
|
4995
|
+
}
|
|
4996
|
+
function envOrUndefined(name) {
|
|
4997
|
+
const maybeProcess = globalThis;
|
|
4998
|
+
const value = maybeProcess.process?.env?.[name];
|
|
4999
|
+
return value && value.trim() ? value.trim() : void 0;
|
|
5000
|
+
}
|
|
5001
|
+
function resolveOptions(options) {
|
|
5002
|
+
return {
|
|
5003
|
+
...options,
|
|
5004
|
+
baseUrl: normalizeBaseUrl5(options.baseUrl),
|
|
5005
|
+
attestationBundleURL: options.attestationBundleURL ?? envOrUndefined("ROUTSTR_TINFOIL_ATTESTATION_BUNDLE_URL"),
|
|
5006
|
+
enclaveURL: options.enclaveURL ?? envOrUndefined("ROUTSTR_TINFOIL_ENCLAVE_URL"),
|
|
5007
|
+
configRepo: options.configRepo ?? envOrUndefined("ROUTSTR_TINFOIL_CONFIG_REPO")
|
|
5008
|
+
};
|
|
5009
|
+
}
|
|
5010
|
+
async function prepareTinfoilClient(options) {
|
|
5011
|
+
const resolved = resolveOptions(options);
|
|
5012
|
+
const key = cacheKey(resolved);
|
|
5013
|
+
let pending = clientCache.get(key);
|
|
5014
|
+
if (!pending) {
|
|
5015
|
+
pending = (async () => {
|
|
5016
|
+
const { SecureClient } = await import('tinfoil');
|
|
5017
|
+
const client = new SecureClient({
|
|
5018
|
+
// baseURL is the proxy/provider URL that receives the EHBP request.
|
|
5019
|
+
baseURL: resolved.baseUrl,
|
|
5020
|
+
// Leave undefined by default so tinfoil uses its public ATC. If set,
|
|
5021
|
+
// SecureClient will fetch `${attestationBundleURL}/attestation`.
|
|
5022
|
+
attestationBundleURL: resolved.attestationBundleURL,
|
|
5023
|
+
enclaveURL: resolved.enclaveURL,
|
|
5024
|
+
configRepo: resolved.configRepo,
|
|
5025
|
+
transport: "ehbp"
|
|
5026
|
+
});
|
|
5027
|
+
await client.ready();
|
|
5028
|
+
const verification = client.getVerificationDocument();
|
|
5029
|
+
return { client, verification };
|
|
5030
|
+
})();
|
|
5031
|
+
clientCache.set(key, pending);
|
|
5032
|
+
}
|
|
5033
|
+
try {
|
|
5034
|
+
return await pending;
|
|
5035
|
+
} catch (error) {
|
|
5036
|
+
clientCache.delete(key);
|
|
5037
|
+
throw error;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
function normalizeFetchArgs(input, init) {
|
|
5041
|
+
if (typeof input === "string") {
|
|
5042
|
+
return { url: input, init };
|
|
5043
|
+
}
|
|
5044
|
+
if (input instanceof URL) {
|
|
5045
|
+
return { url: input.toString(), init };
|
|
5046
|
+
}
|
|
5047
|
+
const cloned = input.clone();
|
|
5048
|
+
return {
|
|
5049
|
+
url: cloned.url,
|
|
5050
|
+
init: {
|
|
5051
|
+
method: cloned.method,
|
|
5052
|
+
headers: new Headers(cloned.headers),
|
|
5053
|
+
body: cloned.body ?? void 0,
|
|
5054
|
+
signal: cloned.signal,
|
|
5055
|
+
...init
|
|
5056
|
+
}
|
|
5057
|
+
};
|
|
5058
|
+
}
|
|
5059
|
+
function isProblemJsonContentType(contentType) {
|
|
5060
|
+
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
5061
|
+
return mediaType === "application/problem+json";
|
|
5062
|
+
}
|
|
5063
|
+
async function isEhbpKeyConfigMismatchResponse(response, protocol) {
|
|
5064
|
+
if (response.status !== 422) {
|
|
5065
|
+
return false;
|
|
5066
|
+
}
|
|
5067
|
+
if (!isProblemJsonContentType(response.headers.get("content-type"))) {
|
|
5068
|
+
return false;
|
|
5069
|
+
}
|
|
5070
|
+
try {
|
|
5071
|
+
const problem = await response.clone().json();
|
|
5072
|
+
return problem?.type === protocol.KEY_CONFIG_PROBLEM_TYPE;
|
|
5073
|
+
} catch {
|
|
5074
|
+
return false;
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
async function fetchTinfoilEhbpOnce(context, options, normalized, ehbp) {
|
|
5078
|
+
const { Identity, PROTOCOL, decryptResponseWithToken, extractSessionRecoveryToken } = ehbp;
|
|
5079
|
+
const resolved = resolveOptions(options);
|
|
5080
|
+
const baseURL = context.client.getBaseURL() ?? resolved.baseUrl;
|
|
5081
|
+
const enclaveURL = context.client.getEnclaveURL();
|
|
5082
|
+
const baseOrigin = new URL(baseURL).origin;
|
|
5083
|
+
const allowedOrigins = /* @__PURE__ */ new Set([baseOrigin]);
|
|
5084
|
+
if (enclaveURL) {
|
|
5085
|
+
allowedOrigins.add(new URL(enclaveURL).origin);
|
|
5086
|
+
}
|
|
5087
|
+
const targetUrl = new URL(normalized.url, baseURL);
|
|
5088
|
+
if (!allowedOrigins.has(targetUrl.origin)) {
|
|
5089
|
+
throw new Error(
|
|
5090
|
+
`refusing to send Tinfoil request to ${targetUrl.origin}: client is bound to the verified enclave/proxy`
|
|
5091
|
+
);
|
|
5092
|
+
}
|
|
5093
|
+
const headers = new Headers(normalized.init?.headers);
|
|
5094
|
+
if (enclaveURL && new URL(enclaveURL).origin !== baseOrigin) {
|
|
5095
|
+
headers.set("X-Tinfoil-Enclave-Url", enclaveURL);
|
|
5096
|
+
}
|
|
5097
|
+
const method = normalized.init?.method ?? "GET";
|
|
5098
|
+
const body = normalized.init?.body ?? null;
|
|
5099
|
+
const serverIdentity = await Identity.fromPublicKeyHex(
|
|
5100
|
+
context.verification.hpkePublicKey
|
|
5101
|
+
);
|
|
5102
|
+
const request = new Request(targetUrl.toString(), {
|
|
5103
|
+
method,
|
|
5104
|
+
headers,
|
|
5105
|
+
body,
|
|
5106
|
+
duplex: "half"
|
|
5107
|
+
});
|
|
5108
|
+
const { request: encryptedRequest, context: requestContext } = await serverIdentity.encryptRequestWithContext(request);
|
|
5109
|
+
const response = await fetch(encryptedRequest);
|
|
5110
|
+
if (!requestContext) {
|
|
5111
|
+
return response;
|
|
5112
|
+
}
|
|
5113
|
+
if (await isEhbpKeyConfigMismatchResponse(response, PROTOCOL)) {
|
|
5114
|
+
throw new ehbp.KeyConfigMismatchError("EHBP key configuration mismatch");
|
|
5115
|
+
}
|
|
5116
|
+
if (!response.headers.get(PROTOCOL.RESPONSE_NONCE_HEADER)) {
|
|
5117
|
+
return response;
|
|
5118
|
+
}
|
|
5119
|
+
const token = await extractSessionRecoveryToken(requestContext);
|
|
5120
|
+
return await decryptResponseWithToken(response, token);
|
|
5121
|
+
}
|
|
5122
|
+
async function fetchTinfoilPreservingPlaintextErrors(options, input, init) {
|
|
5123
|
+
const context = await prepareTinfoilClient(options);
|
|
5124
|
+
const ehbp = await import('ehbp');
|
|
5125
|
+
const normalized = normalizeFetchArgs(input, init);
|
|
5126
|
+
try {
|
|
5127
|
+
return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
|
|
5128
|
+
} catch (error) {
|
|
5129
|
+
if (error instanceof ehbp.KeyConfigMismatchError) {
|
|
5130
|
+
context.client.reset();
|
|
5131
|
+
try {
|
|
5132
|
+
await context.client.ready();
|
|
5133
|
+
context.verification = context.client.getVerificationDocument();
|
|
5134
|
+
} catch (reattestError) {
|
|
5135
|
+
clientCache.delete(cacheKey(resolveOptions(options)));
|
|
5136
|
+
throw reattestError;
|
|
5137
|
+
}
|
|
5138
|
+
return await fetchTinfoilEhbpOnce(context, options, normalized, ehbp);
|
|
5139
|
+
}
|
|
5140
|
+
throw error;
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
function clearTinfoilClientCache() {
|
|
5144
|
+
clientCache.clear();
|
|
5145
|
+
}
|
|
5146
|
+
|
|
4927
5147
|
// client/RoutstrClient.ts
|
|
4928
5148
|
var TOPUP_MARGIN = 1.2;
|
|
4929
5149
|
var RoutstrClient = class {
|
|
@@ -5109,11 +5329,6 @@ var RoutstrClient = class {
|
|
|
5109
5329
|
);
|
|
5110
5330
|
}
|
|
5111
5331
|
}
|
|
5112
|
-
const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = await this._spendToken({
|
|
5113
|
-
mintUrl,
|
|
5114
|
-
amount: requiredSats,
|
|
5115
|
-
baseUrl
|
|
5116
|
-
});
|
|
5117
5332
|
let requestBody = body;
|
|
5118
5333
|
if (body && typeof body === "object") {
|
|
5119
5334
|
const bodyObj = body;
|
|
@@ -5122,7 +5337,36 @@ var RoutstrClient = class {
|
|
|
5122
5337
|
}
|
|
5123
5338
|
}
|
|
5124
5339
|
const baseHeaders = this._buildBaseHeaders();
|
|
5125
|
-
const
|
|
5340
|
+
const tinfoilEnabled = Boolean(modelId && isTinfoilModel(modelId));
|
|
5341
|
+
if (tinfoilEnabled) {
|
|
5342
|
+
this._log(
|
|
5343
|
+
"DEBUG",
|
|
5344
|
+
`[RoutstrClient] Attesting Tinfoil model ${modelId} before spend`
|
|
5345
|
+
);
|
|
5346
|
+
const { verification } = await prepareTinfoilClient({ baseUrl });
|
|
5347
|
+
this._log(
|
|
5348
|
+
"DEBUG",
|
|
5349
|
+
`[RoutstrClient] Tinfoil attestation passed, enclave=${verification.enclaveHost}, codeFingerprint=${verification.codeFingerprint.slice(0, 16)}...`
|
|
5350
|
+
);
|
|
5351
|
+
if (requestBody && typeof requestBody === "object" && modelId) {
|
|
5352
|
+
requestBody = {
|
|
5353
|
+
...requestBody,
|
|
5354
|
+
model: getTinfoilUpstreamModelId(modelId)
|
|
5355
|
+
};
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
5358
|
+
const spendResult = await this._spendToken({
|
|
5359
|
+
mintUrl,
|
|
5360
|
+
amount: requiredSats,
|
|
5361
|
+
baseUrl
|
|
5362
|
+
});
|
|
5363
|
+
const { token, tokenBalance, tokenBalanceUnit, tokenBalanceUnknown } = spendResult;
|
|
5364
|
+
const finalHeaders = this._withAuthAndTinfoilHeaders(
|
|
5365
|
+
baseHeaders,
|
|
5366
|
+
token,
|
|
5367
|
+
tinfoilEnabled,
|
|
5368
|
+
modelId
|
|
5369
|
+
);
|
|
5126
5370
|
const response = await this._makeRequest({
|
|
5127
5371
|
path: requestPath,
|
|
5128
5372
|
method,
|
|
@@ -5131,9 +5375,10 @@ var RoutstrClient = class {
|
|
|
5131
5375
|
mintUrl,
|
|
5132
5376
|
token,
|
|
5133
5377
|
requiredSats,
|
|
5134
|
-
headers:
|
|
5378
|
+
headers: finalHeaders,
|
|
5135
5379
|
baseHeaders,
|
|
5136
|
-
selectedModel
|
|
5380
|
+
selectedModel,
|
|
5381
|
+
tinfoilEnabled
|
|
5137
5382
|
});
|
|
5138
5383
|
let tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
|
|
5139
5384
|
let initialTokenBalanceUnknown = tokenBalanceUnknown;
|
|
@@ -5221,7 +5466,7 @@ var RoutstrClient = class {
|
|
|
5221
5466
|
* Make the API request with failover support
|
|
5222
5467
|
*/
|
|
5223
5468
|
async _makeRequest(params) {
|
|
5224
|
-
const { path, method, body, baseUrl, token, headers } = params;
|
|
5469
|
+
const { path, method, body, baseUrl, token, headers, tinfoilEnabled } = params;
|
|
5225
5470
|
try {
|
|
5226
5471
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
5227
5472
|
const requestBodyText = body === void 0 || method === "GET" ? void 0 : JSON.stringify(body);
|
|
@@ -5235,7 +5480,15 @@ var RoutstrClient = class {
|
|
|
5235
5480
|
rawBody: requestBodyText
|
|
5236
5481
|
});
|
|
5237
5482
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
5238
|
-
const response = await
|
|
5483
|
+
const response = tinfoilEnabled ? await fetchTinfoilPreservingPlaintextErrors(
|
|
5484
|
+
{ baseUrl },
|
|
5485
|
+
url,
|
|
5486
|
+
{
|
|
5487
|
+
method,
|
|
5488
|
+
headers,
|
|
5489
|
+
body: requestBodyText
|
|
5490
|
+
}
|
|
5491
|
+
) : await fetch(url, {
|
|
5239
5492
|
method,
|
|
5240
5493
|
headers,
|
|
5241
5494
|
body: requestBodyText
|
|
@@ -5255,6 +5508,15 @@ var RoutstrClient = class {
|
|
|
5255
5508
|
} catch (e) {
|
|
5256
5509
|
bodyText = void 0;
|
|
5257
5510
|
}
|
|
5511
|
+
this._log("ERROR", "[RoutstrClient] Upstream error response", {
|
|
5512
|
+
baseUrl,
|
|
5513
|
+
url,
|
|
5514
|
+
path,
|
|
5515
|
+
status: response.status,
|
|
5516
|
+
statusText: response.statusText,
|
|
5517
|
+
requestId,
|
|
5518
|
+
body: bodyText ?? "<unable to read response body>"
|
|
5519
|
+
});
|
|
5258
5520
|
return await this._handleErrorResponse(
|
|
5259
5521
|
params,
|
|
5260
5522
|
token,
|
|
@@ -5285,6 +5547,9 @@ var RoutstrClient = class {
|
|
|
5285
5547
|
throw error;
|
|
5286
5548
|
}
|
|
5287
5549
|
}
|
|
5550
|
+
/**
|
|
5551
|
+
* Store request details to a file in the reqs/ folder before fetch.
|
|
5552
|
+
*/
|
|
5288
5553
|
/**
|
|
5289
5554
|
* Handle error responses with failover
|
|
5290
5555
|
*/
|
|
@@ -5436,7 +5701,12 @@ var RoutstrClient = class {
|
|
|
5436
5701
|
return this._makeRequest({
|
|
5437
5702
|
...params,
|
|
5438
5703
|
token: params.token,
|
|
5439
|
-
headers: this.
|
|
5704
|
+
headers: this._withAuthAndTinfoilHeaders(
|
|
5705
|
+
params.baseHeaders,
|
|
5706
|
+
params.token,
|
|
5707
|
+
params.tinfoilEnabled,
|
|
5708
|
+
params.selectedModel?.id
|
|
5709
|
+
),
|
|
5440
5710
|
retryCount: retryCount + 1
|
|
5441
5711
|
});
|
|
5442
5712
|
} else {
|
|
@@ -5497,7 +5767,12 @@ var RoutstrClient = class {
|
|
|
5497
5767
|
return this._makeRequest({
|
|
5498
5768
|
...params,
|
|
5499
5769
|
token: retryToken,
|
|
5500
|
-
headers: this.
|
|
5770
|
+
headers: this._withAuthAndTinfoilHeaders(
|
|
5771
|
+
params.baseHeaders,
|
|
5772
|
+
retryToken,
|
|
5773
|
+
params.tinfoilEnabled,
|
|
5774
|
+
params.selectedModel?.id
|
|
5775
|
+
),
|
|
5501
5776
|
retryCount: retryCount + 1
|
|
5502
5777
|
});
|
|
5503
5778
|
} else {
|
|
@@ -5592,6 +5867,13 @@ var RoutstrClient = class {
|
|
|
5592
5867
|
messagesForPricing,
|
|
5593
5868
|
params.maxTokens
|
|
5594
5869
|
);
|
|
5870
|
+
if (params.tinfoilEnabled) {
|
|
5871
|
+
this._log(
|
|
5872
|
+
"DEBUG",
|
|
5873
|
+
`[RoutstrClient] _handleErrorResponse: Attesting Tinfoil failover provider ${nextProvider} before spend`
|
|
5874
|
+
);
|
|
5875
|
+
await prepareTinfoilClient({ baseUrl: nextProvider });
|
|
5876
|
+
}
|
|
5595
5877
|
this._log(
|
|
5596
5878
|
"DEBUG",
|
|
5597
5879
|
`[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
|
|
@@ -5610,7 +5892,12 @@ var RoutstrClient = class {
|
|
|
5610
5892
|
selectedModel: newModel,
|
|
5611
5893
|
token: spendResult.token,
|
|
5612
5894
|
requiredSats: newRequiredSats,
|
|
5613
|
-
headers: this.
|
|
5895
|
+
headers: this._withAuthAndTinfoilHeaders(
|
|
5896
|
+
params.baseHeaders,
|
|
5897
|
+
spendResult.token,
|
|
5898
|
+
params.tinfoilEnabled,
|
|
5899
|
+
newModel.id
|
|
5900
|
+
),
|
|
5614
5901
|
retryCount: 0
|
|
5615
5902
|
});
|
|
5616
5903
|
retryResponse.initialTokenBalanceInSats = spendResult.tokenBalanceUnit === "msat" ? spendResult.tokenBalance / 1e3 : spendResult.tokenBalance;
|
|
@@ -5677,10 +5964,10 @@ var RoutstrClient = class {
|
|
|
5677
5964
|
if (latestTokenBalance !== void 0) {
|
|
5678
5965
|
this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
|
|
5679
5966
|
}
|
|
5680
|
-
satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
5967
|
+
satsSpent = latestTokenBalance !== void 0 && !initialTokenBalanceUnknown ? Math.max(0, initialTokenBalance - latestTokenBalance) : fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
|
|
5681
5968
|
} catch (e) {
|
|
5682
5969
|
this._log("WARN", "Could not get updated API key balance:", e);
|
|
5683
|
-
satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? 0;
|
|
5970
|
+
satsSpent = fallbackSatsSpent ?? usage?.satsCost ?? this._headerSatsCost(response) ?? 0;
|
|
5684
5971
|
}
|
|
5685
5972
|
}
|
|
5686
5973
|
await this._trackResponseUsage({
|
|
@@ -5697,6 +5984,15 @@ var RoutstrClient = class {
|
|
|
5697
5984
|
})();
|
|
5698
5985
|
return satsSpent;
|
|
5699
5986
|
}
|
|
5987
|
+
/**
|
|
5988
|
+
* Extract sats cost from EHBP/Tinfoil response headers as a last-resort
|
|
5989
|
+
* fallback when neither balance delta nor SSE/body usage provides a cost.
|
|
5990
|
+
*/
|
|
5991
|
+
_headerSatsCost(response) {
|
|
5992
|
+
if (!response) return void 0;
|
|
5993
|
+
const headerUsage = extractUsageFromResponseHeaders(response.headers);
|
|
5994
|
+
return headerUsage?.satsCost;
|
|
5995
|
+
}
|
|
5700
5996
|
async _trackResponseUsage(params) {
|
|
5701
5997
|
const {
|
|
5702
5998
|
token,
|
|
@@ -5730,7 +6026,24 @@ var RoutstrClient = class {
|
|
|
5730
6026
|
}
|
|
5731
6027
|
}
|
|
5732
6028
|
if (!usage) {
|
|
5733
|
-
|
|
6029
|
+
const headerUsage = extractUsageFromResponseHeaders(response.headers);
|
|
6030
|
+
if (headerUsage) {
|
|
6031
|
+
usage = headerUsage;
|
|
6032
|
+
} else {
|
|
6033
|
+
return;
|
|
6034
|
+
}
|
|
6035
|
+
} else {
|
|
6036
|
+
const headerUsage = extractUsageFromResponseHeaders(response.headers);
|
|
6037
|
+
if (headerUsage) {
|
|
6038
|
+
if (headerUsage.totalMsats) {
|
|
6039
|
+
usage.totalMsats = headerUsage.totalMsats;
|
|
6040
|
+
usage.satsCost = headerUsage.satsCost;
|
|
6041
|
+
}
|
|
6042
|
+
if (headerUsage.cost) usage.cost = headerUsage.cost;
|
|
6043
|
+
if (headerUsage.inputMsats) usage.inputMsats = headerUsage.inputMsats;
|
|
6044
|
+
if (headerUsage.outputMsats) usage.outputMsats = headerUsage.outputMsats;
|
|
6045
|
+
if (headerUsage.totalUsd) usage.totalUsd = headerUsage.totalUsd;
|
|
6046
|
+
}
|
|
5734
6047
|
}
|
|
5735
6048
|
const finalRequestId = requestId || "unknown";
|
|
5736
6049
|
const store = this.sdkStore ?? await getDefaultSdkStore();
|
|
@@ -5927,6 +6240,19 @@ var RoutstrClient = class {
|
|
|
5927
6240
|
}
|
|
5928
6241
|
return nextHeaders;
|
|
5929
6242
|
}
|
|
6243
|
+
/**
|
|
6244
|
+
* Attach auth headers and preserve the plaintext model hint required by the
|
|
6245
|
+
* Routstr proxy for Tinfoil/EHBP requests. EHBP encrypts the JSON body, so
|
|
6246
|
+
* retries/failover must not rebuild headers from baseHeaders alone or the
|
|
6247
|
+
* proxy cannot route/price the encrypted request.
|
|
6248
|
+
*/
|
|
6249
|
+
_withAuthAndTinfoilHeaders(headers, token, tinfoilEnabled, modelId) {
|
|
6250
|
+
const nextHeaders = this._withAuthHeader(headers, token);
|
|
6251
|
+
if (tinfoilEnabled && modelId) {
|
|
6252
|
+
nextHeaders["X-Routstr-Model"] = modelId;
|
|
6253
|
+
}
|
|
6254
|
+
return nextHeaders;
|
|
6255
|
+
}
|
|
5930
6256
|
};
|
|
5931
6257
|
|
|
5932
6258
|
// client/StreamProcessor.ts
|
|
@@ -6421,6 +6747,8 @@ async function createBunSqliteDriver(dbPath, options) {
|
|
|
6421
6747
|
const logger = (options?.logger ?? consoleLogger).child("BunSqliteDriver");
|
|
6422
6748
|
const SQLite = (await import('bun:sqlite')).default;
|
|
6423
6749
|
const db = new SQLite(dbPath);
|
|
6750
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
6751
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
6424
6752
|
db.run(`
|
|
6425
6753
|
CREATE TABLE IF NOT EXISTS sdk_storage (
|
|
6426
6754
|
key TEXT PRIMARY KEY,
|
|
@@ -6479,7 +6807,7 @@ var ADDED_COLUMNS = [
|
|
|
6479
6807
|
{ name: "cache_creation_msats", type: "REAL" },
|
|
6480
6808
|
{ name: "remaining_balance_msats", type: "REAL" }
|
|
6481
6809
|
];
|
|
6482
|
-
var
|
|
6810
|
+
var normalizeBaseUrl6 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
6483
6811
|
var buildWhereClause = (options = {}) => {
|
|
6484
6812
|
const clauses = [];
|
|
6485
6813
|
const params = [];
|
|
@@ -6497,7 +6825,7 @@ var buildWhereClause = (options = {}) => {
|
|
|
6497
6825
|
}
|
|
6498
6826
|
if (options.baseUrl) {
|
|
6499
6827
|
clauses.push("base_url = ?");
|
|
6500
|
-
params.push(
|
|
6828
|
+
params.push(normalizeBaseUrl6(options.baseUrl));
|
|
6501
6829
|
}
|
|
6502
6830
|
if (options.sessionId) {
|
|
6503
6831
|
clauses.push("session_id = ?");
|
|
@@ -6533,6 +6861,8 @@ var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
|
6533
6861
|
);
|
|
6534
6862
|
}
|
|
6535
6863
|
const db = new SQLiteDatabase(dbPath);
|
|
6864
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
6865
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
6536
6866
|
db.run(`
|
|
6537
6867
|
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
6538
6868
|
id TEXT PRIMARY KEY,
|
|
@@ -6579,7 +6909,7 @@ var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
|
6579
6909
|
entry.id,
|
|
6580
6910
|
entry.timestamp,
|
|
6581
6911
|
entry.modelId,
|
|
6582
|
-
|
|
6912
|
+
normalizeBaseUrl6(entry.baseUrl),
|
|
6583
6913
|
entry.requestId,
|
|
6584
6914
|
entry.cost,
|
|
6585
6915
|
entry.satsCost,
|
|
@@ -6749,6 +7079,7 @@ exports.SDK_STORAGE_KEYS = SDK_STORAGE_KEYS;
|
|
|
6749
7079
|
exports.StreamProcessor = StreamProcessor;
|
|
6750
7080
|
exports.StreamingError = StreamingError;
|
|
6751
7081
|
exports.TokenOperationError = TokenOperationError;
|
|
7082
|
+
exports.clearTinfoilClientCache = clearTinfoilClientCache;
|
|
6752
7083
|
exports.consoleLogger = consoleLogger;
|
|
6753
7084
|
exports.createBunModelManager = createBunModelManager;
|
|
6754
7085
|
exports.createBunSqliteDriver = createBunSqliteDriver;
|
|
@@ -6768,8 +7099,10 @@ exports.createShardedDiscoveryAdapter = createShardedDiscoveryAdapter;
|
|
|
6768
7099
|
exports.createStorageAdapterFromStore = createStorageAdapterFromStore;
|
|
6769
7100
|
exports.extractResponseId = extractResponseId;
|
|
6770
7101
|
exports.extractUsageFromResponseBody = extractUsageFromResponseBody;
|
|
7102
|
+
exports.extractUsageFromResponseHeaders = extractUsageFromResponseHeaders;
|
|
6771
7103
|
exports.extractUsageFromSSEJson = extractUsageFromSSEJson;
|
|
6772
7104
|
exports.fetchAIResponse = fetchAIResponse;
|
|
7105
|
+
exports.fetchTinfoilPreservingPlaintextErrors = fetchTinfoilPreservingPlaintextErrors;
|
|
6773
7106
|
exports.filterBaseUrlsForTor = filterBaseUrlsForTor;
|
|
6774
7107
|
exports.getDefaultDiscoveryAdapter = getDefaultDiscoveryAdapter;
|
|
6775
7108
|
exports.getDefaultProviderRegistry = getDefaultProviderRegistry;
|
|
@@ -6778,12 +7111,15 @@ exports.getDefaultSdkStore = getDefaultSdkStore;
|
|
|
6778
7111
|
exports.getDefaultStorageAdapter = getDefaultStorageAdapter;
|
|
6779
7112
|
exports.getDefaultUsageTrackingDriver = getDefaultUsageTrackingDriver;
|
|
6780
7113
|
exports.getProviderEndpoints = getProviderEndpoints;
|
|
7114
|
+
exports.getTinfoilUpstreamModelId = getTinfoilUpstreamModelId;
|
|
6781
7115
|
exports.inspectSSEWebStream = inspectSSEWebStream;
|
|
6782
7116
|
exports.isOnionUrl = isOnionUrl;
|
|
7117
|
+
exports.isTinfoilModel = isTinfoilModel;
|
|
6783
7118
|
exports.isTorContext = isTorContext;
|
|
6784
7119
|
exports.localStorageDriver = localStorageDriver;
|
|
6785
7120
|
exports.noopLogger = noopLogger;
|
|
6786
7121
|
exports.normalizeProviderUrl = normalizeProviderUrl;
|
|
7122
|
+
exports.prepareTinfoilClient = prepareTinfoilClient;
|
|
6787
7123
|
exports.routeRequests = routeRequests;
|
|
6788
7124
|
exports.setDefaultUsageTrackingDriver = setDefaultUsageTrackingDriver;
|
|
6789
7125
|
exports.toUsageStats = toUsageStats;
|