@routstr/sdk 0.2.9 → 0.2.11
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 +15 -0
- package/dist/client/index.d.ts +15 -0
- package/dist/client/index.js +142 -60
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +142 -60
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.js +144 -62
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +144 -62
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client/index.d.mts
CHANGED
|
@@ -372,6 +372,21 @@ interface UsageTrackingData {
|
|
|
372
372
|
satsCost: number;
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
/**
|
|
376
|
+
* SSE parser transform that preserves event boundaries verbatim.
|
|
377
|
+
*
|
|
378
|
+
* Unlike a naive line-splitter, this buffers until a full SSE event is
|
|
379
|
+
* received (terminated by a blank line, per the SSE spec), then forwards the
|
|
380
|
+
* entire event unchanged downstream. This means:
|
|
381
|
+
* - Multi-line events (multiple `data:` lines, plus `event:`/`id:`/`retry:`
|
|
382
|
+
* fields) are preserved.
|
|
383
|
+
* - Comments / keepalives (lines beginning with `:`) are preserved.
|
|
384
|
+
* - Chunks that contain multiple events, or events split across chunks, are
|
|
385
|
+
* handled correctly without merging or losing packets.
|
|
386
|
+
*
|
|
387
|
+
* As a side-effect, it inspects `data:` payloads for usage/responseId and
|
|
388
|
+
* invokes the provided callbacks the first time each is seen.
|
|
389
|
+
*/
|
|
375
390
|
declare function createSSEParserTransform(onUsage: (usage: UsageTrackingData) => void, onResponseId?: (responseId: string) => void): Transform;
|
|
376
391
|
|
|
377
392
|
export { type AlertLevel, type DebugLevel, type FetchOptions, type ModelProviderPrice, ProviderManager, type RouteRequestParams, type RouteRequestToNodeResponseParams, RoutstrClient, type RoutstrClientConfig, type RoutstrClientMode, type StreamCallbacks, StreamProcessor, createSSEParserTransform };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -372,6 +372,21 @@ interface UsageTrackingData {
|
|
|
372
372
|
satsCost: number;
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
/**
|
|
376
|
+
* SSE parser transform that preserves event boundaries verbatim.
|
|
377
|
+
*
|
|
378
|
+
* Unlike a naive line-splitter, this buffers until a full SSE event is
|
|
379
|
+
* received (terminated by a blank line, per the SSE spec), then forwards the
|
|
380
|
+
* entire event unchanged downstream. This means:
|
|
381
|
+
* - Multi-line events (multiple `data:` lines, plus `event:`/`id:`/`retry:`
|
|
382
|
+
* fields) are preserved.
|
|
383
|
+
* - Comments / keepalives (lines beginning with `:`) are preserved.
|
|
384
|
+
* - Chunks that contain multiple events, or events split across chunks, are
|
|
385
|
+
* handled correctly without merging or losing packets.
|
|
386
|
+
*
|
|
387
|
+
* As a side-effect, it inspects `data:` payloads for usage/responseId and
|
|
388
|
+
* invokes the provided callbacks the first time each is seen.
|
|
389
|
+
*/
|
|
375
390
|
declare function createSSEParserTransform(onUsage: (usage: UsageTrackingData) => void, onResponseId?: (responseId: string) => void): Transform;
|
|
376
391
|
|
|
377
392
|
export { type AlertLevel, type DebugLevel, type FetchOptions, type ModelProviderPrice, ProviderManager, type RouteRequestParams, type RouteRequestToNodeResponseParams, RoutstrClient, type RoutstrClientConfig, type RoutstrClientMode, type StreamCallbacks, StreamProcessor, createSSEParserTransform };
|
package/dist/client/index.js
CHANGED
|
@@ -1295,17 +1295,48 @@ function extractResponseId(body) {
|
|
|
1295
1295
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
1296
1296
|
}
|
|
1297
1297
|
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
1298
|
-
if (!parsed || typeof parsed !== "object"
|
|
1298
|
+
if (!parsed || typeof parsed !== "object") {
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
1302
|
+
const costObj = parsed.cost;
|
|
1303
|
+
const msats2 = costObj.total_msats ?? 0;
|
|
1304
|
+
const cost2 = costObj.total_usd ?? 0;
|
|
1305
|
+
if (msats2 === 0 && cost2 === 0) return null;
|
|
1306
|
+
return {
|
|
1307
|
+
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
1308
|
+
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
1309
|
+
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
1310
|
+
cost: Number(cost2),
|
|
1311
|
+
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
if (!parsed.usage) {
|
|
1299
1315
|
return null;
|
|
1300
1316
|
}
|
|
1301
1317
|
const usage = parsed.usage;
|
|
1302
1318
|
const usageCost = usage.cost;
|
|
1303
|
-
|
|
1304
|
-
|
|
1319
|
+
let cost = 0;
|
|
1320
|
+
let msats = 0;
|
|
1321
|
+
if (typeof usageCost === "number") {
|
|
1322
|
+
cost = usageCost;
|
|
1323
|
+
} else if (usageCost && typeof usageCost === "object") {
|
|
1324
|
+
cost = usageCost.total_usd ?? 0;
|
|
1325
|
+
msats = usageCost.total_msats ?? 0;
|
|
1326
|
+
}
|
|
1327
|
+
if (cost === 0) {
|
|
1328
|
+
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
1329
|
+
}
|
|
1330
|
+
if (msats === 0) {
|
|
1331
|
+
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
1332
|
+
}
|
|
1333
|
+
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
1334
|
+
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
1335
|
+
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
1305
1336
|
const result = {
|
|
1306
|
-
promptTokens
|
|
1307
|
-
completionTokens
|
|
1308
|
-
totalTokens
|
|
1337
|
+
promptTokens,
|
|
1338
|
+
completionTokens,
|
|
1339
|
+
totalTokens,
|
|
1309
1340
|
cost: Number(cost ?? 0),
|
|
1310
1341
|
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
|
|
1311
1342
|
};
|
|
@@ -1431,11 +1462,14 @@ var StreamProcessor = class {
|
|
|
1431
1462
|
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
1432
1463
|
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
1433
1464
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1465
|
+
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
1466
|
+
if (extractedUsage) {
|
|
1467
|
+
result.usage = toUsageStats(extractedUsage);
|
|
1468
|
+
} else if (parsed.usage) {
|
|
1469
|
+
result.usage = {
|
|
1470
|
+
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
1471
|
+
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
1472
|
+
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
1439
1473
|
};
|
|
1440
1474
|
}
|
|
1441
1475
|
if (parsed.id) {
|
|
@@ -1878,22 +1912,62 @@ var ProviderManager = class _ProviderManager {
|
|
|
1878
1912
|
const disabledProviders = new Set(
|
|
1879
1913
|
this.providerRegistry.getDisabledProviders()
|
|
1880
1914
|
);
|
|
1915
|
+
console.log(`[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`);
|
|
1916
|
+
console.log(`[findNextBestProvider:${this.instanceId}] currentBaseUrl: ${currentBaseUrl}`);
|
|
1917
|
+
console.log(`[findNextBestProvider:${this.instanceId}] torMode: ${torMode}`);
|
|
1918
|
+
console.log(`[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`);
|
|
1919
|
+
console.log(`[findNextBestProvider:${this.instanceId}] failedProviders: ${[...this.failedProviders]}`);
|
|
1920
|
+
console.log(`[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`);
|
|
1881
1921
|
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
1922
|
+
console.log(`[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`);
|
|
1882
1923
|
const candidates = [];
|
|
1924
|
+
let skippedCurrent = 0, skippedFailed = 0, skippedDisabled = 0, skippedCooldown = 0, skippedOnion = 0, skippedNoModel = 0;
|
|
1883
1925
|
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
1884
|
-
if (baseUrl === currentBaseUrl
|
|
1926
|
+
if (baseUrl === currentBaseUrl) {
|
|
1927
|
+
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`);
|
|
1928
|
+
skippedCurrent++;
|
|
1929
|
+
continue;
|
|
1930
|
+
}
|
|
1931
|
+
if (this.failedProviders.has(baseUrl)) {
|
|
1932
|
+
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (failed): ${baseUrl}`);
|
|
1933
|
+
skippedFailed++;
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
if (disabledProviders.has(baseUrl)) {
|
|
1937
|
+
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (disabled): ${baseUrl}`);
|
|
1938
|
+
skippedDisabled++;
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
if (this.isOnCooldown(baseUrl)) {
|
|
1942
|
+
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (cooldown): ${baseUrl}`);
|
|
1943
|
+
skippedCooldown++;
|
|
1885
1944
|
continue;
|
|
1886
1945
|
}
|
|
1887
1946
|
if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
|
|
1947
|
+
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (onion/http): ${baseUrl}`);
|
|
1948
|
+
skippedOnion++;
|
|
1888
1949
|
continue;
|
|
1889
1950
|
}
|
|
1890
1951
|
const model = models.find((m) => m.id === modelId);
|
|
1891
|
-
if (!model)
|
|
1952
|
+
if (!model) {
|
|
1953
|
+
console.log(`[findNextBestProvider:${this.instanceId}] SKIP (no model ${modelId}): ${baseUrl} has models: ${models.map((m) => m.id).join(", ")}`);
|
|
1954
|
+
skippedNoModel++;
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1892
1957
|
const cost = model.sats_pricing?.completion ?? 0;
|
|
1958
|
+
console.log(`[findNextBestProvider:${this.instanceId}] CANDIDATE: ${baseUrl} cost: ${cost}`);
|
|
1893
1959
|
candidates.push({ baseUrl, model, cost });
|
|
1894
1960
|
}
|
|
1961
|
+
console.log(`[findNextBestProvider:${this.instanceId}] Skipped: current=${skippedCurrent}, failed=${skippedFailed}, disabled=${skippedDisabled}, cooldown=${skippedCooldown}, onion=${skippedOnion}, noModel=${skippedNoModel}`);
|
|
1962
|
+
console.log(`[findNextBestProvider:${this.instanceId}] Total candidates: ${candidates.length}`);
|
|
1895
1963
|
candidates.sort((a, b) => a.cost - b.cost);
|
|
1896
|
-
|
|
1964
|
+
if (candidates.length > 0) {
|
|
1965
|
+
console.log(`[findNextBestProvider:${this.instanceId}] Selected provider: ${candidates[0].baseUrl} with cost: ${candidates[0].cost}`);
|
|
1966
|
+
return candidates[0].baseUrl;
|
|
1967
|
+
} else {
|
|
1968
|
+
console.log(`[findNextBestProvider:${this.instanceId}] No candidate providers found`);
|
|
1969
|
+
return null;
|
|
1970
|
+
}
|
|
1897
1971
|
} catch (error) {
|
|
1898
1972
|
console.error("Error finding next best provider:", error);
|
|
1899
1973
|
return null;
|
|
@@ -3366,67 +3440,74 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3366
3440
|
let buffer = "";
|
|
3367
3441
|
let usageCaptured = false;
|
|
3368
3442
|
let responseIdCaptured = false;
|
|
3369
|
-
const
|
|
3443
|
+
const inspectDataPayload = (jsonText) => {
|
|
3444
|
+
if (usageCaptured && responseIdCaptured) return;
|
|
3445
|
+
const trimmed = jsonText.trim();
|
|
3446
|
+
if (!trimmed || trimmed === "[DONE]") return;
|
|
3447
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
3370
3448
|
try {
|
|
3371
|
-
const data = JSON.parse(
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3449
|
+
const data = JSON.parse(trimmed);
|
|
3450
|
+
if (!responseIdCaptured) {
|
|
3451
|
+
const responseId = data?.id;
|
|
3452
|
+
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
3453
|
+
onResponseId?.(responseId.trim());
|
|
3454
|
+
responseIdCaptured = true;
|
|
3455
|
+
}
|
|
3376
3456
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3457
|
+
if (!usageCaptured) {
|
|
3458
|
+
const usage = extractUsageFromSSEJson(data);
|
|
3459
|
+
if (usage) {
|
|
3460
|
+
onUsage(usage);
|
|
3461
|
+
usageCaptured = true;
|
|
3462
|
+
}
|
|
3381
3463
|
}
|
|
3382
3464
|
} catch {
|
|
3383
3465
|
}
|
|
3384
3466
|
};
|
|
3385
|
-
const
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
if (trimmed.startsWith("data:")) {
|
|
3395
|
-
const dataStr = trimmed.startsWith("data: ") ? trimmed.slice(6) : trimmed.slice(5).trimStart();
|
|
3396
|
-
if (dataStr === "[DONE]") {
|
|
3397
|
-
self.push("data: [DONE]\n\n");
|
|
3398
|
-
return;
|
|
3467
|
+
const inspectEventBlock = (eventBlock) => {
|
|
3468
|
+
if (usageCaptured && responseIdCaptured) return;
|
|
3469
|
+
const lines = eventBlock.split(/\r?\n/);
|
|
3470
|
+
const dataParts = [];
|
|
3471
|
+
for (const line of lines) {
|
|
3472
|
+
if (!line || line.startsWith(":")) continue;
|
|
3473
|
+
if (line.startsWith("data:")) {
|
|
3474
|
+
const value = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
|
|
3475
|
+
dataParts.push(value);
|
|
3399
3476
|
}
|
|
3400
|
-
maybeCaptureUsageFromJson(dataStr);
|
|
3401
|
-
self.push(`data: ${dataStr}
|
|
3402
|
-
|
|
3403
|
-
`);
|
|
3404
|
-
return;
|
|
3405
|
-
}
|
|
3406
|
-
if (trimmed.startsWith("{")) {
|
|
3407
|
-
maybeCaptureUsageFromJson(trimmed);
|
|
3408
|
-
self.push(`data: ${trimmed}
|
|
3409
|
-
|
|
3410
|
-
`);
|
|
3411
|
-
return;
|
|
3412
3477
|
}
|
|
3413
|
-
|
|
3478
|
+
if (dataParts.length === 0) return;
|
|
3479
|
+
const payload = dataParts.join("\n");
|
|
3480
|
+
inspectDataPayload(payload);
|
|
3481
|
+
};
|
|
3482
|
+
const emitEventBlock = (self, eventBlock) => {
|
|
3483
|
+
if (eventBlock.length === 0) return;
|
|
3484
|
+
inspectEventBlock(eventBlock);
|
|
3485
|
+
self.push(eventBlock + "\n\n");
|
|
3414
3486
|
};
|
|
3415
3487
|
return new stream.Transform({
|
|
3416
|
-
transform(chunk,
|
|
3488
|
+
transform(chunk, _encoding, callback) {
|
|
3417
3489
|
buffer += chunk.toString();
|
|
3418
|
-
const
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3490
|
+
const terminator = /\r?\n\r?\n/g;
|
|
3491
|
+
let lastIndex = 0;
|
|
3492
|
+
let match;
|
|
3493
|
+
while ((match = terminator.exec(buffer)) !== null) {
|
|
3494
|
+
const block = buffer.slice(lastIndex, match.index);
|
|
3495
|
+
lastIndex = match.index + match[0].length;
|
|
3496
|
+
emitEventBlock(this, block);
|
|
3497
|
+
}
|
|
3498
|
+
if (lastIndex > 0) {
|
|
3499
|
+
buffer = buffer.slice(lastIndex);
|
|
3422
3500
|
}
|
|
3423
3501
|
callback();
|
|
3424
3502
|
},
|
|
3425
3503
|
flush(callback) {
|
|
3426
|
-
if (buffer.
|
|
3427
|
-
|
|
3504
|
+
if (buffer.length > 0) {
|
|
3505
|
+
const tail = buffer.replace(/\r?\n+$/, "");
|
|
3506
|
+
if (tail.length > 0) {
|
|
3507
|
+
emitEventBlock(this, tail);
|
|
3508
|
+
}
|
|
3509
|
+
buffer = "";
|
|
3428
3510
|
}
|
|
3429
|
-
buffer = "";
|
|
3430
3511
|
callback();
|
|
3431
3512
|
}
|
|
3432
3513
|
});
|
|
@@ -3907,9 +3988,10 @@ var RoutstrClient = class {
|
|
|
3907
3988
|
const MAX_RETRIES_PER_PROVIDER = 2;
|
|
3908
3989
|
const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
|
|
3909
3990
|
let tryNextProvider = false;
|
|
3991
|
+
const errorMessage = responseBody;
|
|
3910
3992
|
this._log(
|
|
3911
3993
|
"DEBUG",
|
|
3912
|
-
`[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}`
|
|
3994
|
+
`[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}, errorMessage=${errorMessage}`
|
|
3913
3995
|
);
|
|
3914
3996
|
this._log(
|
|
3915
3997
|
"DEBUG",
|