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