@routstr/sdk 0.2.11 → 0.3.0
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/client/index.d.mts +30 -15
- package/dist/client/index.d.ts +30 -15
- package/dist/client/index.js +289 -141
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +290 -143
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -10
- package/dist/index.d.ts +3 -10
- package/dist/index.js +289 -163
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +290 -164
- package/dist/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +13 -0
- package/dist/wallet/index.d.ts +13 -0
- package/dist/wallet/index.js +71 -2
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +71 -2
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,8 @@ import { EventStore } from 'applesauce-core';
|
|
|
3
3
|
import { tap } from 'rxjs';
|
|
4
4
|
import { getDecodedToken } from '@cashu/cashu-ts';
|
|
5
5
|
import { createStore } from 'zustand/vanilla';
|
|
6
|
-
import { Transform
|
|
6
|
+
import { Transform } from 'stream';
|
|
7
|
+
import { StringDecoder } from 'string_decoder';
|
|
7
8
|
|
|
8
9
|
// core/errors.ts
|
|
9
10
|
var InsufficientBalanceError = class extends Error {
|
|
@@ -1270,7 +1271,7 @@ var CashuSpender = class {
|
|
|
1270
1271
|
};
|
|
1271
1272
|
|
|
1272
1273
|
// wallet/BalanceManager.ts
|
|
1273
|
-
var BalanceManager = class {
|
|
1274
|
+
var BalanceManager = class _BalanceManager {
|
|
1274
1275
|
constructor(walletAdapter, storageAdapter, providerRegistry, cashuSpender) {
|
|
1275
1276
|
this.walletAdapter = walletAdapter;
|
|
1276
1277
|
this.storageAdapter = storageAdapter;
|
|
@@ -1287,6 +1288,47 @@ var BalanceManager = class {
|
|
|
1287
1288
|
}
|
|
1288
1289
|
}
|
|
1289
1290
|
cashuSpender;
|
|
1291
|
+
/** In-memory guard for per-provider wallet mutations (topup / refund) */
|
|
1292
|
+
providerWalletOps = /* @__PURE__ */ new Map();
|
|
1293
|
+
/** Cooldown (ms) between opposite operations on the same provider */
|
|
1294
|
+
static PROVIDER_WALLET_COOLDOWN_MS = 1e4;
|
|
1295
|
+
/**
|
|
1296
|
+
* Check whether a wallet operation (topup/refund) may run for a provider.
|
|
1297
|
+
* Returns the reason when blocked.
|
|
1298
|
+
*/
|
|
1299
|
+
_canRunProviderWalletOperation(baseUrl, type) {
|
|
1300
|
+
const existing = this.providerWalletOps.get(baseUrl);
|
|
1301
|
+
if (!existing) {
|
|
1302
|
+
return { allowed: true };
|
|
1303
|
+
}
|
|
1304
|
+
if (existing.type === type) {
|
|
1305
|
+
return { allowed: true };
|
|
1306
|
+
}
|
|
1307
|
+
if (!existing.endTime) {
|
|
1308
|
+
return {
|
|
1309
|
+
allowed: false,
|
|
1310
|
+
reason: `Provider wallet operation locked; ${existing.type} in progress`
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
const elapsed = Date.now() - existing.endTime;
|
|
1314
|
+
if (elapsed < _BalanceManager.PROVIDER_WALLET_COOLDOWN_MS) {
|
|
1315
|
+
return {
|
|
1316
|
+
allowed: false,
|
|
1317
|
+
reason: `Provider wallet operation locked; recent ${existing.type} completed ${Math.round(elapsed / 1e3)}s ago`
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
this.providerWalletOps.delete(baseUrl);
|
|
1321
|
+
return { allowed: true };
|
|
1322
|
+
}
|
|
1323
|
+
_beginProviderWalletOperation(baseUrl, type) {
|
|
1324
|
+
this.providerWalletOps.set(baseUrl, { type, startTime: Date.now() });
|
|
1325
|
+
}
|
|
1326
|
+
_endProviderWalletOperation(baseUrl, type) {
|
|
1327
|
+
const existing = this.providerWalletOps.get(baseUrl);
|
|
1328
|
+
if (existing && existing.type === type) {
|
|
1329
|
+
existing.endTime = Date.now();
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1290
1332
|
async getBalanceState() {
|
|
1291
1333
|
const mintBalances = await this.walletAdapter.getBalances();
|
|
1292
1334
|
const units = this.walletAdapter.getMintUnits();
|
|
@@ -1321,6 +1363,20 @@ var BalanceManager = class {
|
|
|
1321
1363
|
* @returns Refund result
|
|
1322
1364
|
*/
|
|
1323
1365
|
async refundApiKey(options) {
|
|
1366
|
+
const { mintUrl, baseUrl, apiKey, forceRefund } = options;
|
|
1367
|
+
const guard = this._canRunProviderWalletOperation(baseUrl, "refund");
|
|
1368
|
+
if (!guard.allowed) {
|
|
1369
|
+
console.log(`[BalanceManager] Skipping refund for ${baseUrl} - ${guard.reason}`);
|
|
1370
|
+
return { success: false, message: guard.reason };
|
|
1371
|
+
}
|
|
1372
|
+
this._beginProviderWalletOperation(baseUrl, "refund");
|
|
1373
|
+
try {
|
|
1374
|
+
return await this._refundApiKeyImpl({ mintUrl, baseUrl, apiKey, forceRefund });
|
|
1375
|
+
} finally {
|
|
1376
|
+
this._endProviderWalletOperation(baseUrl, "refund");
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
async _refundApiKeyImpl(options) {
|
|
1324
1380
|
const { mintUrl, baseUrl, apiKey, forceRefund } = options;
|
|
1325
1381
|
if (!apiKey) {
|
|
1326
1382
|
return { success: false, message: "No API key to refund" };
|
|
@@ -1449,6 +1505,20 @@ var BalanceManager = class {
|
|
|
1449
1505
|
* Top up API key balance with a cashu token
|
|
1450
1506
|
*/
|
|
1451
1507
|
async topUp(options) {
|
|
1508
|
+
const { mintUrl, baseUrl, amount, token: providedToken } = options;
|
|
1509
|
+
const guard = this._canRunProviderWalletOperation(baseUrl, "topup");
|
|
1510
|
+
if (!guard.allowed) {
|
|
1511
|
+
console.log(`[BalanceManager] Skipping topup for ${baseUrl} - ${guard.reason}`);
|
|
1512
|
+
return { success: false, message: guard.reason };
|
|
1513
|
+
}
|
|
1514
|
+
this._beginProviderWalletOperation(baseUrl, "topup");
|
|
1515
|
+
try {
|
|
1516
|
+
return await this._topUpImpl({ mintUrl, baseUrl, amount, token: providedToken });
|
|
1517
|
+
} finally {
|
|
1518
|
+
this._endProviderWalletOperation(baseUrl, "topup");
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
async _topUpImpl(options) {
|
|
1452
1522
|
const { mintUrl, baseUrl, amount, token: providedToken } = options;
|
|
1453
1523
|
if (!amount || amount <= 0) {
|
|
1454
1524
|
return { success: false, message: "Invalid top up amount" };
|
|
@@ -1609,7 +1679,7 @@ var BalanceManager = class {
|
|
|
1609
1679
|
p2pkPubkey
|
|
1610
1680
|
);
|
|
1611
1681
|
console.log(
|
|
1612
|
-
`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`
|
|
1682
|
+
`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}, all mint balances: ${JSON.stringify(Object.fromEntries(Object.entries(balances).map(([mint, balance]) => [mint, getBalanceInSats(balance, units[mint])])))}`
|
|
1613
1683
|
);
|
|
1614
1684
|
return {
|
|
1615
1685
|
success: true,
|
|
@@ -2415,6 +2485,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
2415
2485
|
}
|
|
2416
2486
|
/**
|
|
2417
2487
|
* Clean up expired cooldown entries
|
|
2488
|
+
* Also removes the provider from failedProviders so it can be retried
|
|
2418
2489
|
*/
|
|
2419
2490
|
cleanupExpiredCooldowns() {
|
|
2420
2491
|
const now = Date.now();
|
|
@@ -2427,6 +2498,10 @@ var ProviderManager = class _ProviderManager {
|
|
|
2427
2498
|
console.log(
|
|
2428
2499
|
`[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
|
|
2429
2500
|
);
|
|
2501
|
+
this.failedProviders.delete(url);
|
|
2502
|
+
if (this.store) {
|
|
2503
|
+
this.store.getState().removeFailedProvider(url);
|
|
2504
|
+
}
|
|
2430
2505
|
}
|
|
2431
2506
|
return !isExpired;
|
|
2432
2507
|
}
|
|
@@ -2600,60 +2675,47 @@ var ProviderManager = class _ProviderManager {
|
|
|
2600
2675
|
const disabledProviders = new Set(
|
|
2601
2676
|
this.providerRegistry.getDisabledProviders()
|
|
2602
2677
|
);
|
|
2603
|
-
console.log(
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
console.log(
|
|
2607
|
-
|
|
2608
|
-
|
|
2678
|
+
console.log(
|
|
2679
|
+
`[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`
|
|
2680
|
+
);
|
|
2681
|
+
console.log(
|
|
2682
|
+
`[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`
|
|
2683
|
+
);
|
|
2684
|
+
console.log(
|
|
2685
|
+
`[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`
|
|
2686
|
+
);
|
|
2609
2687
|
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
2610
|
-
console.log(
|
|
2688
|
+
console.log(
|
|
2689
|
+
`[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`
|
|
2690
|
+
);
|
|
2611
2691
|
const candidates = [];
|
|
2612
|
-
let skippedCurrent = 0, skippedFailed = 0, skippedDisabled = 0, skippedCooldown = 0, skippedOnion = 0, skippedNoModel = 0;
|
|
2613
2692
|
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
2614
2693
|
if (baseUrl === currentBaseUrl) {
|
|
2615
|
-
console.log(
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
}
|
|
2619
|
-
if (this.failedProviders.has(baseUrl)) {
|
|
2620
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (failed): ${baseUrl}`);
|
|
2621
|
-
skippedFailed++;
|
|
2694
|
+
console.log(
|
|
2695
|
+
`[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`
|
|
2696
|
+
);
|
|
2622
2697
|
continue;
|
|
2623
2698
|
}
|
|
2624
2699
|
if (disabledProviders.has(baseUrl)) {
|
|
2625
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (disabled): ${baseUrl}`);
|
|
2626
|
-
skippedDisabled++;
|
|
2627
2700
|
continue;
|
|
2628
2701
|
}
|
|
2629
2702
|
if (this.isOnCooldown(baseUrl)) {
|
|
2630
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (cooldown): ${baseUrl}`);
|
|
2631
|
-
skippedCooldown++;
|
|
2632
2703
|
continue;
|
|
2633
2704
|
}
|
|
2634
2705
|
if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
|
|
2635
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (onion/http): ${baseUrl}`);
|
|
2636
|
-
skippedOnion++;
|
|
2637
2706
|
continue;
|
|
2638
2707
|
}
|
|
2639
2708
|
const model = models.find((m) => m.id === modelId);
|
|
2640
2709
|
if (!model) {
|
|
2641
|
-
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (no model ${modelId}): ${baseUrl} has models: ${models.map((m) => m.id).join(", ")}`);
|
|
2642
|
-
skippedNoModel++;
|
|
2643
2710
|
continue;
|
|
2644
2711
|
}
|
|
2645
2712
|
const cost = model.sats_pricing?.completion ?? 0;
|
|
2646
|
-
console.log(`[findNextBestProvider:${this.instanceId}] CANDIDATE: ${baseUrl} cost: ${cost}`);
|
|
2647
2713
|
candidates.push({ baseUrl, model, cost });
|
|
2648
2714
|
}
|
|
2649
|
-
console.log(`[findNextBestProvider:${this.instanceId}] Skipped: current=${skippedCurrent}, failed=${skippedFailed}, disabled=${skippedDisabled}, cooldown=${skippedCooldown}, onion=${skippedOnion}, noModel=${skippedNoModel}`);
|
|
2650
|
-
console.log(`[findNextBestProvider:${this.instanceId}] Total candidates: ${candidates.length}`);
|
|
2651
2715
|
candidates.sort((a, b) => a.cost - b.cost);
|
|
2652
2716
|
if (candidates.length > 0) {
|
|
2653
|
-
console.log(`[findNextBestProvider:${this.instanceId}] Selected provider: ${candidates[0].baseUrl} with cost: ${candidates[0].cost}`);
|
|
2654
2717
|
return candidates[0].baseUrl;
|
|
2655
2718
|
} else {
|
|
2656
|
-
console.log(`[findNextBestProvider:${this.instanceId}] No candidate providers found`);
|
|
2657
2719
|
return null;
|
|
2658
2720
|
}
|
|
2659
2721
|
} catch (error) {
|
|
@@ -2812,7 +2874,9 @@ var ProviderManager = class _ProviderManager {
|
|
|
2812
2874
|
const approximateTokens = apiMessagesNoImages ? Math.ceil(JSON.stringify(apiMessagesNoImages, null, 2).length / 2.84) : 1e4;
|
|
2813
2875
|
const totalInputTokens = approximateTokens + imageTokens;
|
|
2814
2876
|
const sp = model?.sats_pricing;
|
|
2815
|
-
if (!sp)
|
|
2877
|
+
if (!sp) {
|
|
2878
|
+
return 0;
|
|
2879
|
+
}
|
|
2816
2880
|
if (!sp.max_completion_cost) {
|
|
2817
2881
|
return sp.max_cost ?? 50;
|
|
2818
2882
|
}
|
|
@@ -4521,12 +4585,119 @@ var setDefaultUsageTrackingDriver = (driver) => {
|
|
|
4521
4585
|
var getDefaultDiscoveryAdapter = async () => createDiscoveryAdapterFromStore(await getDefaultSdkStore());
|
|
4522
4586
|
var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
|
|
4523
4587
|
var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
|
|
4588
|
+
function mergeUsage(previous, next) {
|
|
4589
|
+
if (!previous) return next;
|
|
4590
|
+
return {
|
|
4591
|
+
promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
|
|
4592
|
+
completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
|
|
4593
|
+
totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
|
|
4594
|
+
cost: next.cost > 0 ? next.cost : previous.cost,
|
|
4595
|
+
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
function hasUsageChanged(previous, next) {
|
|
4599
|
+
if (!previous) return true;
|
|
4600
|
+
return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
|
|
4601
|
+
}
|
|
4602
|
+
async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
4603
|
+
const reader = stream.getReader();
|
|
4604
|
+
const decoder = new TextDecoder("utf-8");
|
|
4605
|
+
let buffer = "";
|
|
4606
|
+
let capturedUsage = null;
|
|
4607
|
+
let capturedResponseId;
|
|
4608
|
+
let responseIdCaptured = false;
|
|
4609
|
+
const inspectDataPayload = (jsonText) => {
|
|
4610
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
4611
|
+
return;
|
|
4612
|
+
}
|
|
4613
|
+
const trimmed = jsonText.trim();
|
|
4614
|
+
if (!trimmed || trimmed === "[DONE]") return;
|
|
4615
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
4616
|
+
try {
|
|
4617
|
+
const data = JSON.parse(trimmed);
|
|
4618
|
+
if (!responseIdCaptured) {
|
|
4619
|
+
const responseId = data?.id;
|
|
4620
|
+
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
4621
|
+
capturedResponseId = responseId.trim();
|
|
4622
|
+
onResponseId?.(capturedResponseId);
|
|
4623
|
+
responseIdCaptured = true;
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
const usage = extractUsageFromSSEJson(data);
|
|
4627
|
+
if (usage) {
|
|
4628
|
+
const merged = mergeUsage(capturedUsage, usage);
|
|
4629
|
+
if (hasUsageChanged(capturedUsage, merged)) {
|
|
4630
|
+
capturedUsage = merged;
|
|
4631
|
+
onUsage(merged);
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
} catch {
|
|
4635
|
+
}
|
|
4636
|
+
};
|
|
4637
|
+
const inspectEventBlock = (eventBlock) => {
|
|
4638
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
4639
|
+
return;
|
|
4640
|
+
}
|
|
4641
|
+
const lines = eventBlock.split(/\r?\n/);
|
|
4642
|
+
const dataParts = [];
|
|
4643
|
+
for (const line of lines) {
|
|
4644
|
+
if (!line || line.startsWith(":")) continue;
|
|
4645
|
+
if (line.startsWith("data:")) {
|
|
4646
|
+
const value = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
|
|
4647
|
+
dataParts.push(value);
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
if (dataParts.length === 0) return;
|
|
4651
|
+
inspectDataPayload(dataParts.join("\n"));
|
|
4652
|
+
};
|
|
4653
|
+
const drainBufferedEvents = () => {
|
|
4654
|
+
const terminator = /\r?\n\r?\n/g;
|
|
4655
|
+
let lastIndex = 0;
|
|
4656
|
+
let match;
|
|
4657
|
+
while ((match = terminator.exec(buffer)) !== null) {
|
|
4658
|
+
const block = buffer.slice(lastIndex, match.index);
|
|
4659
|
+
lastIndex = match.index + match[0].length;
|
|
4660
|
+
if (block.length > 0) inspectEventBlock(block);
|
|
4661
|
+
}
|
|
4662
|
+
if (lastIndex > 0) buffer = buffer.slice(lastIndex);
|
|
4663
|
+
};
|
|
4664
|
+
try {
|
|
4665
|
+
while (true) {
|
|
4666
|
+
const { value, done } = await reader.read();
|
|
4667
|
+
if (done) break;
|
|
4668
|
+
if (value && value.byteLength > 0) {
|
|
4669
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4670
|
+
drainBufferedEvents();
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
buffer += decoder.decode();
|
|
4674
|
+
drainBufferedEvents();
|
|
4675
|
+
if (buffer.length > 0) {
|
|
4676
|
+
const tail = buffer.replace(/\r?\n+$/, "");
|
|
4677
|
+
if (tail.length > 0) inspectEventBlock(tail);
|
|
4678
|
+
buffer = "";
|
|
4679
|
+
}
|
|
4680
|
+
} catch {
|
|
4681
|
+
} finally {
|
|
4682
|
+
try {
|
|
4683
|
+
reader.releaseLock();
|
|
4684
|
+
} catch {
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
return {
|
|
4688
|
+
capturedUsage: capturedUsage ?? void 0,
|
|
4689
|
+
capturedResponseId
|
|
4690
|
+
};
|
|
4691
|
+
}
|
|
4524
4692
|
function createSSEParserTransform(onUsage, onResponseId) {
|
|
4525
4693
|
let buffer = "";
|
|
4526
|
-
|
|
4694
|
+
const decoder = new StringDecoder("utf8");
|
|
4695
|
+
let capturedUsage = null;
|
|
4527
4696
|
let responseIdCaptured = false;
|
|
4528
4697
|
const inspectDataPayload = (jsonText) => {
|
|
4529
|
-
if (
|
|
4698
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
4699
|
+
return;
|
|
4700
|
+
}
|
|
4530
4701
|
const trimmed = jsonText.trim();
|
|
4531
4702
|
if (!trimmed || trimmed === "[DONE]") return;
|
|
4532
4703
|
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
@@ -4539,18 +4710,21 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
4539
4710
|
responseIdCaptured = true;
|
|
4540
4711
|
}
|
|
4541
4712
|
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4713
|
+
const usage = extractUsageFromSSEJson(data);
|
|
4714
|
+
if (usage) {
|
|
4715
|
+
const mergedUsage = mergeUsage(capturedUsage, usage);
|
|
4716
|
+
if (hasUsageChanged(capturedUsage, mergedUsage)) {
|
|
4717
|
+
capturedUsage = mergedUsage;
|
|
4718
|
+
onUsage(mergedUsage);
|
|
4547
4719
|
}
|
|
4548
4720
|
}
|
|
4549
4721
|
} catch {
|
|
4550
4722
|
}
|
|
4551
4723
|
};
|
|
4552
4724
|
const inspectEventBlock = (eventBlock) => {
|
|
4553
|
-
if (
|
|
4725
|
+
if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
|
|
4726
|
+
return;
|
|
4727
|
+
}
|
|
4554
4728
|
const lines = eventBlock.split(/\r?\n/);
|
|
4555
4729
|
const dataParts = [];
|
|
4556
4730
|
for (const line of lines) {
|
|
@@ -4564,32 +4738,35 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
4564
4738
|
const payload = dataParts.join("\n");
|
|
4565
4739
|
inspectDataPayload(payload);
|
|
4566
4740
|
};
|
|
4567
|
-
const
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4741
|
+
const processBufferedEvents = () => {
|
|
4742
|
+
const terminator = /\r?\n\r?\n/g;
|
|
4743
|
+
let lastIndex = 0;
|
|
4744
|
+
let match;
|
|
4745
|
+
while ((match = terminator.exec(buffer)) !== null) {
|
|
4746
|
+
const block = buffer.slice(lastIndex, match.index);
|
|
4747
|
+
lastIndex = match.index + match[0].length;
|
|
4748
|
+
if (block.length > 0) {
|
|
4749
|
+
inspectEventBlock(block);
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
if (lastIndex > 0) {
|
|
4753
|
+
buffer = buffer.slice(lastIndex);
|
|
4754
|
+
}
|
|
4571
4755
|
};
|
|
4572
4756
|
return new Transform({
|
|
4573
4757
|
transform(chunk, _encoding, callback) {
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
let match;
|
|
4578
|
-
while ((match = terminator.exec(buffer)) !== null) {
|
|
4579
|
-
const block = buffer.slice(lastIndex, match.index);
|
|
4580
|
-
lastIndex = match.index + match[0].length;
|
|
4581
|
-
emitEventBlock(this, block);
|
|
4582
|
-
}
|
|
4583
|
-
if (lastIndex > 0) {
|
|
4584
|
-
buffer = buffer.slice(lastIndex);
|
|
4585
|
-
}
|
|
4758
|
+
this.push(chunk);
|
|
4759
|
+
buffer += decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4760
|
+
processBufferedEvents();
|
|
4586
4761
|
callback();
|
|
4587
4762
|
},
|
|
4588
4763
|
flush(callback) {
|
|
4764
|
+
buffer += decoder.end();
|
|
4765
|
+
processBufferedEvents();
|
|
4589
4766
|
if (buffer.length > 0) {
|
|
4590
4767
|
const tail = buffer.replace(/\r?\n+$/, "");
|
|
4591
4768
|
if (tail.length > 0) {
|
|
4592
|
-
|
|
4769
|
+
inspectEventBlock(tail);
|
|
4593
4770
|
}
|
|
4594
4771
|
buffer = "";
|
|
4595
4772
|
}
|
|
@@ -4597,6 +4774,8 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
4597
4774
|
}
|
|
4598
4775
|
});
|
|
4599
4776
|
}
|
|
4777
|
+
|
|
4778
|
+
// client/RoutstrClient.ts
|
|
4600
4779
|
var TOPUP_MARGIN = 1.2;
|
|
4601
4780
|
var RoutstrClient = class {
|
|
4602
4781
|
constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
|
|
@@ -4696,31 +4875,12 @@ var RoutstrClient = class {
|
|
|
4696
4875
|
*/
|
|
4697
4876
|
async routeRequest(params) {
|
|
4698
4877
|
const prepared = await this._prepareRoutedRequest(params);
|
|
4699
|
-
const
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
modelId: prepared.modelId,
|
|
4706
|
-
usage: prepared.capturedUsage,
|
|
4707
|
-
requestId: prepared.capturedResponseId,
|
|
4708
|
-
clientApiKey: prepared.clientApiKey
|
|
4709
|
-
});
|
|
4710
|
-
prepared.response.satsSpent = satsSpent;
|
|
4711
|
-
prepared.response.usage = prepared.capturedUsage;
|
|
4712
|
-
prepared.response.requestId = prepared.capturedResponseId;
|
|
4713
|
-
return prepared.response;
|
|
4714
|
-
}
|
|
4715
|
-
async routeRequestToNodeResponse(params) {
|
|
4716
|
-
const { res } = params;
|
|
4717
|
-
const prepared = await this._prepareRoutedRequest(params);
|
|
4718
|
-
res.statusCode = prepared.response.status;
|
|
4719
|
-
prepared.response.headers.forEach((value, key) => {
|
|
4720
|
-
res.setHeader(key, value);
|
|
4721
|
-
});
|
|
4722
|
-
const body = prepared.response.body;
|
|
4723
|
-
if (!body) {
|
|
4878
|
+
const contentType = prepared.response.headers.get("content-type") || "";
|
|
4879
|
+
const isSSE = contentType.includes("text/event-stream");
|
|
4880
|
+
const runFinalize = async () => {
|
|
4881
|
+
const { capturedUsage, capturedResponseId } = await prepared.usagePromise;
|
|
4882
|
+
const usage = capturedUsage ?? prepared.capturedUsage;
|
|
4883
|
+
const requestId = capturedResponseId ?? prepared.capturedResponseId;
|
|
4724
4884
|
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
4725
4885
|
token: prepared.tokenUsed,
|
|
4726
4886
|
baseUrl: prepared.baseUrlUsed,
|
|
@@ -4728,55 +4888,29 @@ var RoutstrClient = class {
|
|
|
4728
4888
|
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
4729
4889
|
response: prepared.response,
|
|
4730
4890
|
modelId: prepared.modelId,
|
|
4731
|
-
usage
|
|
4732
|
-
requestId
|
|
4891
|
+
usage,
|
|
4892
|
+
requestId,
|
|
4733
4893
|
clientApiKey: prepared.clientApiKey
|
|
4734
4894
|
});
|
|
4735
4895
|
prepared.response.satsSpent = satsSpent;
|
|
4736
|
-
|
|
4737
|
-
|
|
4896
|
+
prepared.response.usage = usage;
|
|
4897
|
+
prepared.response.requestId = requestId;
|
|
4898
|
+
return satsSpent;
|
|
4899
|
+
};
|
|
4900
|
+
if (isSSE) {
|
|
4901
|
+
const finalizePromise = runFinalize().catch((error) => {
|
|
4902
|
+
this._log("ERROR", "[RoutstrClient] SSE finalize failed:", error);
|
|
4903
|
+
return 0;
|
|
4904
|
+
});
|
|
4905
|
+
prepared.response.finalize = () => finalizePromise;
|
|
4906
|
+
return prepared.response;
|
|
4738
4907
|
}
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
let settled = false;
|
|
4742
|
-
const finish = async () => {
|
|
4743
|
-
if (settled) return;
|
|
4744
|
-
settled = true;
|
|
4745
|
-
try {
|
|
4746
|
-
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
4747
|
-
token: prepared.tokenUsed,
|
|
4748
|
-
baseUrl: prepared.baseUrlUsed,
|
|
4749
|
-
mintUrl: params.mintUrl,
|
|
4750
|
-
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
4751
|
-
response: prepared.response,
|
|
4752
|
-
modelId: prepared.modelId,
|
|
4753
|
-
usage: prepared.capturedUsage,
|
|
4754
|
-
requestId: prepared.capturedResponseId,
|
|
4755
|
-
clientApiKey: prepared.clientApiKey
|
|
4756
|
-
});
|
|
4757
|
-
prepared.response.satsSpent = satsSpent;
|
|
4758
|
-
prepared.response.usage = prepared.capturedUsage;
|
|
4759
|
-
prepared.response.requestId = prepared.capturedResponseId;
|
|
4760
|
-
resolve();
|
|
4761
|
-
} catch (error) {
|
|
4762
|
-
reject(error);
|
|
4763
|
-
}
|
|
4764
|
-
};
|
|
4765
|
-
const fail = (error) => {
|
|
4766
|
-
if (settled) return;
|
|
4767
|
-
settled = true;
|
|
4768
|
-
reject(error);
|
|
4769
|
-
};
|
|
4770
|
-
res.once("finish", finish);
|
|
4771
|
-
res.once("close", finish);
|
|
4772
|
-
res.once("error", fail);
|
|
4773
|
-
nodeReadable.once("error", fail);
|
|
4774
|
-
nodeReadable.pipe(res);
|
|
4775
|
-
});
|
|
4908
|
+
await runFinalize();
|
|
4909
|
+
return prepared.response;
|
|
4776
4910
|
}
|
|
4777
4911
|
async _prepareRoutedRequest(params) {
|
|
4778
4912
|
const {
|
|
4779
|
-
path,
|
|
4913
|
+
path: requestPath,
|
|
4780
4914
|
method,
|
|
4781
4915
|
body,
|
|
4782
4916
|
headers = {},
|
|
@@ -4796,9 +4930,23 @@ var RoutstrClient = class {
|
|
|
4796
4930
|
);
|
|
4797
4931
|
selectedModel = providerModel ?? void 0;
|
|
4798
4932
|
if (selectedModel) {
|
|
4933
|
+
const requestMessages = Array.isArray(
|
|
4934
|
+
body?.messages
|
|
4935
|
+
) ? body.messages : [];
|
|
4936
|
+
const requestMaxTokens = typeof body?.max_tokens === "number" ? body.max_tokens : void 0;
|
|
4937
|
+
this._log(
|
|
4938
|
+
"DEBUG",
|
|
4939
|
+
"[RoutstrClient] generic request pricing input",
|
|
4940
|
+
{
|
|
4941
|
+
modelId: selectedModel.id,
|
|
4942
|
+
messageCount: requestMessages.length,
|
|
4943
|
+
maxTokens: requestMaxTokens
|
|
4944
|
+
}
|
|
4945
|
+
);
|
|
4799
4946
|
requiredSats = this.providerManager.getRequiredSatsForModel(
|
|
4800
4947
|
selectedModel,
|
|
4801
|
-
|
|
4948
|
+
requestMessages,
|
|
4949
|
+
requestMaxTokens
|
|
4802
4950
|
);
|
|
4803
4951
|
}
|
|
4804
4952
|
}
|
|
@@ -4817,7 +4965,7 @@ var RoutstrClient = class {
|
|
|
4817
4965
|
const baseHeaders = this._buildBaseHeaders();
|
|
4818
4966
|
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
4819
4967
|
const response = await this._makeRequest({
|
|
4820
|
-
path,
|
|
4968
|
+
path: requestPath,
|
|
4821
4969
|
method,
|
|
4822
4970
|
body: method === "GET" ? void 0 : requestBody,
|
|
4823
4971
|
baseUrl,
|
|
@@ -4835,9 +4983,18 @@ var RoutstrClient = class {
|
|
|
4835
4983
|
let processedResponse = response;
|
|
4836
4984
|
let capturedUsage;
|
|
4837
4985
|
let capturedResponseId;
|
|
4986
|
+
let usagePromise = Promise.resolve({});
|
|
4838
4987
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
4839
|
-
const
|
|
4840
|
-
|
|
4988
|
+
const [clientStream, inspectStream] = response.body.tee();
|
|
4989
|
+
processedResponse = new Response(clientStream, {
|
|
4990
|
+
status: response.status,
|
|
4991
|
+
statusText: response.statusText,
|
|
4992
|
+
headers: response.headers
|
|
4993
|
+
});
|
|
4994
|
+
processedResponse.baseUrl = response.baseUrl;
|
|
4995
|
+
processedResponse.token = response.token;
|
|
4996
|
+
usagePromise = inspectSSEWebStream(
|
|
4997
|
+
inspectStream,
|
|
4841
4998
|
(usage) => {
|
|
4842
4999
|
capturedUsage = usage;
|
|
4843
5000
|
processedResponse.usage = usage;
|
|
@@ -4847,17 +5004,7 @@ var RoutstrClient = class {
|
|
|
4847
5004
|
processedResponse.requestId = responseId;
|
|
4848
5005
|
}
|
|
4849
5006
|
);
|
|
4850
|
-
|
|
4851
|
-
const webStream = Readable.toWeb(
|
|
4852
|
-
transformed
|
|
4853
|
-
);
|
|
4854
|
-
processedResponse = new Response(webStream, {
|
|
4855
|
-
status: response.status,
|
|
4856
|
-
statusText: response.statusText,
|
|
4857
|
-
headers: response.headers
|
|
4858
|
-
});
|
|
4859
|
-
processedResponse.baseUrl = response.baseUrl;
|
|
4860
|
-
processedResponse.token = response.token;
|
|
5007
|
+
processedResponse.usagePromise = usagePromise;
|
|
4861
5008
|
}
|
|
4862
5009
|
return {
|
|
4863
5010
|
response: processedResponse,
|
|
@@ -4867,7 +5014,8 @@ var RoutstrClient = class {
|
|
|
4867
5014
|
modelId,
|
|
4868
5015
|
capturedUsage,
|
|
4869
5016
|
capturedResponseId,
|
|
4870
|
-
clientApiKey
|
|
5017
|
+
clientApiKey,
|
|
5018
|
+
usagePromise
|
|
4871
5019
|
};
|
|
4872
5020
|
}
|
|
4873
5021
|
/**
|
|
@@ -4916,7 +5064,6 @@ var RoutstrClient = class {
|
|
|
4916
5064
|
callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
|
|
4917
5065
|
const baseHeaders = this._buildBaseHeaders(headers);
|
|
4918
5066
|
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
4919
|
-
this.providerManager.resetFailedProviders();
|
|
4920
5067
|
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
4921
5068
|
const providerVersion = providerInfo?.version ?? "";
|
|
4922
5069
|
let modelIdForRequest = selectedModel.id;
|
|
@@ -5144,7 +5291,7 @@ var RoutstrClient = class {
|
|
|
5144
5291
|
);
|
|
5145
5292
|
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
5146
5293
|
const shortfall = Math.max(0, params.requiredSats - currentBalance);
|
|
5147
|
-
topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
|
|
5294
|
+
topupAmount = shortfall > 0.21 * params.requiredSats ? shortfall : 0.21 * params.requiredSats;
|
|
5148
5295
|
this._log(
|
|
5149
5296
|
"DEBUG",
|
|
5150
5297
|
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
|
|
@@ -5909,27 +6056,6 @@ async function routeRequests(options) {
|
|
|
5909
6056
|
throw error;
|
|
5910
6057
|
}
|
|
5911
6058
|
}
|
|
5912
|
-
async function routeRequestsToNodeResponse(options) {
|
|
5913
|
-
const { res } = options;
|
|
5914
|
-
const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
|
|
5915
|
-
try {
|
|
5916
|
-
await client.routeRequestToNodeResponse({
|
|
5917
|
-
path,
|
|
5918
|
-
method: "POST",
|
|
5919
|
-
body: proxiedBody,
|
|
5920
|
-
headers,
|
|
5921
|
-
baseUrl,
|
|
5922
|
-
mintUrl,
|
|
5923
|
-
modelId,
|
|
5924
|
-
res
|
|
5925
|
-
});
|
|
5926
|
-
} catch (error) {
|
|
5927
|
-
if (error instanceof Error && (error.message.includes("401") || error.message.includes("402") || error.message.includes("403"))) {
|
|
5928
|
-
throw new Error(`Authentication failed: ${error.message}`);
|
|
5929
|
-
}
|
|
5930
|
-
throw error;
|
|
5931
|
-
}
|
|
5932
|
-
}
|
|
5933
6059
|
function extractMaxTokens(requestBody) {
|
|
5934
6060
|
if (!requestBody || typeof requestBody !== "object") {
|
|
5935
6061
|
return void 0;
|
|
@@ -5947,6 +6073,6 @@ function extractStream(requestBody) {
|
|
|
5947
6073
|
return typeof stream === "boolean" ? stream : void 0;
|
|
5948
6074
|
}
|
|
5949
6075
|
|
|
5950
|
-
export { BalanceManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, ModelManager, ModelNotFoundError, NoProvidersAvailableError, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, createBunSqliteDriver, createBunSqliteUsageTrackingDriver, createDiscoveryAdapterFromStore, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromStore, createSSEParserTransform, createSdkStore, createSqliteDriver, createSqliteUsageTrackingDriver, createStorageAdapterFromStore, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, getProviderEndpoints, isOnionUrl, isTorContext, localStorageDriver, normalizeProviderUrl, routeRequests,
|
|
6076
|
+
export { BalanceManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, ModelManager, ModelNotFoundError, NoProvidersAvailableError, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, createBunSqliteDriver, createBunSqliteUsageTrackingDriver, createDiscoveryAdapterFromStore, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromStore, createSSEParserTransform, createSdkStore, createSqliteDriver, createSqliteUsageTrackingDriver, createStorageAdapterFromStore, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, getProviderEndpoints, inspectSSEWebStream, isOnionUrl, isTorContext, localStorageDriver, normalizeProviderUrl, routeRequests, setDefaultUsageTrackingDriver };
|
|
5951
6077
|
//# sourceMappingURL=index.mjs.map
|
|
5952
6078
|
//# sourceMappingURL=index.mjs.map
|