@routstr/sdk 0.2.9 → 0.2.12
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 +16 -0
- package/dist/client/index.d.ts +16 -0
- package/dist/client/index.js +199 -69
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +177 -69
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.js +205 -75
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +183 -75
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client/index.mjs
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { getDecodedToken } from '@cashu/cashu-ts';
|
|
2
2
|
import { createStore } from 'zustand/vanilla';
|
|
3
3
|
import { Transform, Readable } from 'stream';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as os from 'os';
|
|
4
7
|
|
|
5
8
|
// core/errors.ts
|
|
6
9
|
var InsufficientBalanceError = class extends Error {
|
|
@@ -54,10 +57,10 @@ var AuditLogger = class _AuditLogger {
|
|
|
54
57
|
const logLine = JSON.stringify(fullEntry) + "\n";
|
|
55
58
|
if (typeof window === "undefined") {
|
|
56
59
|
try {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const logPath =
|
|
60
|
-
|
|
60
|
+
const fs2 = await import('fs');
|
|
61
|
+
const path2 = await import('path');
|
|
62
|
+
const logPath = path2.join(process.cwd(), "audit.log");
|
|
63
|
+
fs2.appendFileSync(logPath, logLine);
|
|
61
64
|
} catch (error) {
|
|
62
65
|
console.error("[AuditLogger] Failed to write to file:", error);
|
|
63
66
|
}
|
|
@@ -1293,17 +1296,48 @@ function extractResponseId(body) {
|
|
|
1293
1296
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
1294
1297
|
}
|
|
1295
1298
|
function extractUsageFromSSEJson(parsed, fallbackSatsCost = 0) {
|
|
1296
|
-
if (!parsed || typeof parsed !== "object"
|
|
1299
|
+
if (!parsed || typeof parsed !== "object") {
|
|
1300
|
+
return null;
|
|
1301
|
+
}
|
|
1302
|
+
if (!parsed.usage && parsed.cost && typeof parsed.cost === "object") {
|
|
1303
|
+
const costObj = parsed.cost;
|
|
1304
|
+
const msats2 = costObj.total_msats ?? 0;
|
|
1305
|
+
const cost2 = costObj.total_usd ?? 0;
|
|
1306
|
+
if (msats2 === 0 && cost2 === 0) return null;
|
|
1307
|
+
return {
|
|
1308
|
+
promptTokens: Number(costObj.input_tokens ?? 0),
|
|
1309
|
+
completionTokens: Number(costObj.output_tokens ?? 0),
|
|
1310
|
+
totalTokens: Number((costObj.input_tokens ?? 0) + (costObj.output_tokens ?? 0)),
|
|
1311
|
+
cost: Number(cost2),
|
|
1312
|
+
satsCost: msats2 > 0 ? msats2 / 1e3 : fallbackSatsCost
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
if (!parsed.usage) {
|
|
1297
1316
|
return null;
|
|
1298
1317
|
}
|
|
1299
1318
|
const usage = parsed.usage;
|
|
1300
1319
|
const usageCost = usage.cost;
|
|
1301
|
-
|
|
1302
|
-
|
|
1320
|
+
let cost = 0;
|
|
1321
|
+
let msats = 0;
|
|
1322
|
+
if (typeof usageCost === "number") {
|
|
1323
|
+
cost = usageCost;
|
|
1324
|
+
} else if (usageCost && typeof usageCost === "object") {
|
|
1325
|
+
cost = usageCost.total_usd ?? 0;
|
|
1326
|
+
msats = usageCost.total_msats ?? 0;
|
|
1327
|
+
}
|
|
1328
|
+
if (cost === 0) {
|
|
1329
|
+
cost = parsed.metadata?.routstr?.cost?.total_usd ?? 0;
|
|
1330
|
+
}
|
|
1331
|
+
if (msats === 0) {
|
|
1332
|
+
msats = parsed.metadata?.routstr?.cost?.total_msats ?? (typeof usage.cost_sats === "number" ? usage.cost_sats * 1e3 : 0);
|
|
1333
|
+
}
|
|
1334
|
+
const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
|
|
1335
|
+
const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens ?? 0);
|
|
1336
|
+
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
1303
1337
|
const result = {
|
|
1304
|
-
promptTokens
|
|
1305
|
-
completionTokens
|
|
1306
|
-
totalTokens
|
|
1338
|
+
promptTokens,
|
|
1339
|
+
completionTokens,
|
|
1340
|
+
totalTokens,
|
|
1307
1341
|
cost: Number(cost ?? 0),
|
|
1308
1342
|
satsCost: msats > 0 ? msats / 1e3 : fallbackSatsCost
|
|
1309
1343
|
};
|
|
@@ -1429,11 +1463,14 @@ var StreamProcessor = class {
|
|
|
1429
1463
|
if (parsed.choices?.[0]?.delta?.reasoning) {
|
|
1430
1464
|
result.reasoning = parsed.choices[0].delta.reasoning;
|
|
1431
1465
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1466
|
+
const extractedUsage = extractUsageFromSSEJson(parsed);
|
|
1467
|
+
if (extractedUsage) {
|
|
1468
|
+
result.usage = toUsageStats(extractedUsage);
|
|
1469
|
+
} else if (parsed.usage) {
|
|
1470
|
+
result.usage = {
|
|
1471
|
+
total_tokens: parsed.usage.total_tokens ?? parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
1472
|
+
prompt_tokens: parsed.usage.prompt_tokens ?? parsed.usage.input_tokens,
|
|
1473
|
+
completion_tokens: parsed.usage.completion_tokens ?? parsed.usage.output_tokens
|
|
1437
1474
|
};
|
|
1438
1475
|
}
|
|
1439
1476
|
if (parsed.id) {
|
|
@@ -1691,6 +1728,7 @@ var ProviderManager = class _ProviderManager {
|
|
|
1691
1728
|
}
|
|
1692
1729
|
/**
|
|
1693
1730
|
* Clean up expired cooldown entries
|
|
1731
|
+
* Also removes the provider from failedProviders so it can be retried
|
|
1694
1732
|
*/
|
|
1695
1733
|
cleanupExpiredCooldowns() {
|
|
1696
1734
|
const now = Date.now();
|
|
@@ -1703,6 +1741,10 @@ var ProviderManager = class _ProviderManager {
|
|
|
1703
1741
|
console.log(
|
|
1704
1742
|
`[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
|
|
1705
1743
|
);
|
|
1744
|
+
this.failedProviders.delete(url);
|
|
1745
|
+
if (this.store) {
|
|
1746
|
+
this.store.getState().removeFailedProvider(url);
|
|
1747
|
+
}
|
|
1706
1748
|
}
|
|
1707
1749
|
return !isExpired;
|
|
1708
1750
|
}
|
|
@@ -1876,22 +1918,49 @@ var ProviderManager = class _ProviderManager {
|
|
|
1876
1918
|
const disabledProviders = new Set(
|
|
1877
1919
|
this.providerRegistry.getDisabledProviders()
|
|
1878
1920
|
);
|
|
1921
|
+
console.log(
|
|
1922
|
+
`[findNextBestProvider:${this.instanceId}] Starting search for model: ${modelId}`
|
|
1923
|
+
);
|
|
1924
|
+
console.log(
|
|
1925
|
+
`[findNextBestProvider:${this.instanceId}] disabledProviders: ${[...disabledProviders]}`
|
|
1926
|
+
);
|
|
1927
|
+
console.log(
|
|
1928
|
+
`[findNextBestProvider:${this.instanceId}] providersOnCooldown: ${this.providersOnCoolDown.map(([url]) => url)}`
|
|
1929
|
+
);
|
|
1879
1930
|
const allProviders = this.providerRegistry.getAllProvidersModels();
|
|
1931
|
+
console.log(
|
|
1932
|
+
`[findNextBestProvider:${this.instanceId}] Total providers in registry: ${Object.keys(allProviders).length}`
|
|
1933
|
+
);
|
|
1880
1934
|
const candidates = [];
|
|
1881
1935
|
for (const [baseUrl, models] of Object.entries(allProviders)) {
|
|
1882
|
-
if (baseUrl === currentBaseUrl
|
|
1936
|
+
if (baseUrl === currentBaseUrl) {
|
|
1937
|
+
console.log(
|
|
1938
|
+
`[findNextBestProvider:${this.instanceId}] SKIP (current): ${baseUrl}`
|
|
1939
|
+
);
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
if (disabledProviders.has(baseUrl)) {
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
if (this.isOnCooldown(baseUrl)) {
|
|
1883
1946
|
continue;
|
|
1884
1947
|
}
|
|
1885
1948
|
if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
|
|
1886
1949
|
continue;
|
|
1887
1950
|
}
|
|
1888
1951
|
const model = models.find((m) => m.id === modelId);
|
|
1889
|
-
if (!model)
|
|
1952
|
+
if (!model) {
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1890
1955
|
const cost = model.sats_pricing?.completion ?? 0;
|
|
1891
1956
|
candidates.push({ baseUrl, model, cost });
|
|
1892
1957
|
}
|
|
1893
1958
|
candidates.sort((a, b) => a.cost - b.cost);
|
|
1894
|
-
|
|
1959
|
+
if (candidates.length > 0) {
|
|
1960
|
+
return candidates[0].baseUrl;
|
|
1961
|
+
} else {
|
|
1962
|
+
return null;
|
|
1963
|
+
}
|
|
1895
1964
|
} catch (error) {
|
|
1896
1965
|
console.error("Error finding next best provider:", error);
|
|
1897
1966
|
return null;
|
|
@@ -3362,69 +3431,95 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
3362
3431
|
};
|
|
3363
3432
|
function createSSEParserTransform(onUsage, onResponseId) {
|
|
3364
3433
|
let buffer = "";
|
|
3365
|
-
let
|
|
3434
|
+
let capturedUsage = null;
|
|
3366
3435
|
let responseIdCaptured = false;
|
|
3367
|
-
const
|
|
3436
|
+
const mergeUsage = (previous, next) => {
|
|
3437
|
+
if (!previous) return next;
|
|
3438
|
+
return {
|
|
3439
|
+
promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
|
|
3440
|
+
completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
|
|
3441
|
+
totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
|
|
3442
|
+
cost: next.cost > 0 ? next.cost : previous.cost,
|
|
3443
|
+
satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
|
|
3444
|
+
};
|
|
3445
|
+
};
|
|
3446
|
+
const hasUsageChanged = (previous, next) => {
|
|
3447
|
+
if (!previous) return true;
|
|
3448
|
+
return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
|
|
3449
|
+
};
|
|
3450
|
+
const inspectDataPayload = (jsonText) => {
|
|
3451
|
+
if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
|
|
3452
|
+
return;
|
|
3453
|
+
}
|
|
3454
|
+
const trimmed = jsonText.trim();
|
|
3455
|
+
if (!trimmed || trimmed === "[DONE]") return;
|
|
3456
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
|
|
3368
3457
|
try {
|
|
3369
|
-
const data = JSON.parse(
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3458
|
+
const data = JSON.parse(trimmed);
|
|
3459
|
+
if (!responseIdCaptured) {
|
|
3460
|
+
const responseId = data?.id;
|
|
3461
|
+
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
3462
|
+
onResponseId?.(responseId.trim());
|
|
3463
|
+
responseIdCaptured = true;
|
|
3464
|
+
}
|
|
3374
3465
|
}
|
|
3375
3466
|
const usage = extractUsageFromSSEJson(data);
|
|
3376
3467
|
if (usage) {
|
|
3377
|
-
|
|
3378
|
-
|
|
3468
|
+
const mergedUsage = mergeUsage(capturedUsage, usage);
|
|
3469
|
+
if (hasUsageChanged(capturedUsage, mergedUsage)) {
|
|
3470
|
+
capturedUsage = mergedUsage;
|
|
3471
|
+
onUsage(mergedUsage);
|
|
3472
|
+
}
|
|
3379
3473
|
}
|
|
3380
3474
|
} catch {
|
|
3381
3475
|
}
|
|
3382
3476
|
};
|
|
3383
|
-
const
|
|
3384
|
-
|
|
3385
|
-
if (!trimmed) {
|
|
3386
|
-
return;
|
|
3387
|
-
}
|
|
3388
|
-
if (trimmed === "data: [DONE]" || trimmed === "[DONE]") {
|
|
3389
|
-
self.push("data: [DONE]\n\n");
|
|
3477
|
+
const inspectEventBlock = (eventBlock) => {
|
|
3478
|
+
if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
|
|
3390
3479
|
return;
|
|
3391
3480
|
}
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3481
|
+
const lines = eventBlock.split(/\r?\n/);
|
|
3482
|
+
const dataParts = [];
|
|
3483
|
+
for (const line of lines) {
|
|
3484
|
+
if (!line || line.startsWith(":")) continue;
|
|
3485
|
+
if (line.startsWith("data:")) {
|
|
3486
|
+
const value = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
|
|
3487
|
+
dataParts.push(value);
|
|
3397
3488
|
}
|
|
3398
|
-
maybeCaptureUsageFromJson(dataStr);
|
|
3399
|
-
self.push(`data: ${dataStr}
|
|
3400
|
-
|
|
3401
|
-
`);
|
|
3402
|
-
return;
|
|
3403
|
-
}
|
|
3404
|
-
if (trimmed.startsWith("{")) {
|
|
3405
|
-
maybeCaptureUsageFromJson(trimmed);
|
|
3406
|
-
self.push(`data: ${trimmed}
|
|
3407
|
-
|
|
3408
|
-
`);
|
|
3409
|
-
return;
|
|
3410
3489
|
}
|
|
3411
|
-
|
|
3490
|
+
if (dataParts.length === 0) return;
|
|
3491
|
+
const payload = dataParts.join("\n");
|
|
3492
|
+
inspectDataPayload(payload);
|
|
3493
|
+
};
|
|
3494
|
+
const emitEventBlock = (self, eventBlock) => {
|
|
3495
|
+
if (eventBlock.length === 0) return;
|
|
3496
|
+
inspectEventBlock(eventBlock);
|
|
3497
|
+
self.push(eventBlock + "\n\n");
|
|
3412
3498
|
};
|
|
3413
3499
|
return new Transform({
|
|
3414
|
-
transform(chunk,
|
|
3500
|
+
transform(chunk, _encoding, callback) {
|
|
3415
3501
|
buffer += chunk.toString();
|
|
3416
|
-
const
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3502
|
+
const terminator = /\r?\n\r?\n/g;
|
|
3503
|
+
let lastIndex = 0;
|
|
3504
|
+
let match;
|
|
3505
|
+
while ((match = terminator.exec(buffer)) !== null) {
|
|
3506
|
+
const block = buffer.slice(lastIndex, match.index);
|
|
3507
|
+
lastIndex = match.index + match[0].length;
|
|
3508
|
+
emitEventBlock(this, block);
|
|
3509
|
+
}
|
|
3510
|
+
if (lastIndex > 0) {
|
|
3511
|
+
buffer = buffer.slice(lastIndex);
|
|
3420
3512
|
}
|
|
3421
3513
|
callback();
|
|
3422
3514
|
},
|
|
3423
3515
|
flush(callback) {
|
|
3424
|
-
if (buffer.
|
|
3425
|
-
|
|
3516
|
+
if (buffer.length > 0) {
|
|
3517
|
+
const tail = buffer.replace(/\r?\n+$/, "");
|
|
3518
|
+
if (tail.length > 0) {
|
|
3519
|
+
emitEventBlock(this, tail);
|
|
3520
|
+
}
|
|
3521
|
+
buffer = "";
|
|
3426
3522
|
}
|
|
3427
|
-
buffer = "";
|
|
3428
3523
|
callback();
|
|
3429
3524
|
}
|
|
3430
3525
|
});
|
|
@@ -3608,7 +3703,7 @@ var RoutstrClient = class {
|
|
|
3608
3703
|
}
|
|
3609
3704
|
async _prepareRoutedRequest(params) {
|
|
3610
3705
|
const {
|
|
3611
|
-
path,
|
|
3706
|
+
path: requestPath,
|
|
3612
3707
|
method,
|
|
3613
3708
|
body,
|
|
3614
3709
|
headers = {},
|
|
@@ -3649,7 +3744,7 @@ var RoutstrClient = class {
|
|
|
3649
3744
|
const baseHeaders = this._buildBaseHeaders();
|
|
3650
3745
|
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
3651
3746
|
const response = await this._makeRequest({
|
|
3652
|
-
path,
|
|
3747
|
+
path: requestPath,
|
|
3653
3748
|
method,
|
|
3654
3749
|
body: method === "GET" ? void 0 : requestBody,
|
|
3655
3750
|
baseUrl,
|
|
@@ -3668,7 +3763,20 @@ var RoutstrClient = class {
|
|
|
3668
3763
|
let capturedUsage;
|
|
3669
3764
|
let capturedResponseId;
|
|
3670
3765
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
3766
|
+
const logDir = path.join(os.homedir(), ".routstrd", "stream-response");
|
|
3767
|
+
if (!fs.existsSync(logDir)) {
|
|
3768
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
3769
|
+
}
|
|
3770
|
+
const logFile = path.join(logDir, `${Date.now()}.jsonl`);
|
|
3771
|
+
const logStream = fs.createWriteStream(logFile);
|
|
3671
3772
|
const nodeReadable = Readable.fromWeb(response.body);
|
|
3773
|
+
const loggingTransform = new Transform({
|
|
3774
|
+
transform(chunk, encoding, callback) {
|
|
3775
|
+
const raw = chunk.toString();
|
|
3776
|
+
logStream.write(JSON.stringify({ raw, timestamp: Date.now() }) + "\n");
|
|
3777
|
+
callback(null, chunk);
|
|
3778
|
+
}
|
|
3779
|
+
});
|
|
3672
3780
|
const sseParser = createSSEParserTransform(
|
|
3673
3781
|
(usage) => {
|
|
3674
3782
|
capturedUsage = usage;
|
|
@@ -3679,7 +3787,7 @@ var RoutstrClient = class {
|
|
|
3679
3787
|
processedResponse.requestId = responseId;
|
|
3680
3788
|
}
|
|
3681
3789
|
);
|
|
3682
|
-
const transformed = nodeReadable.pipe(sseParser, { end: true });
|
|
3790
|
+
const transformed = nodeReadable.pipe(loggingTransform).pipe(sseParser, { end: true });
|
|
3683
3791
|
const webStream = Readable.toWeb(
|
|
3684
3792
|
transformed
|
|
3685
3793
|
);
|
|
@@ -3748,7 +3856,6 @@ var RoutstrClient = class {
|
|
|
3748
3856
|
callbacks.onTokenCreated?.(this._getPendingCashuTokenAmount());
|
|
3749
3857
|
const baseHeaders = this._buildBaseHeaders(headers);
|
|
3750
3858
|
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
3751
|
-
this.providerManager.resetFailedProviders();
|
|
3752
3859
|
const providerInfo = await this.providerRegistry.getProviderInfo(baseUrl);
|
|
3753
3860
|
const providerVersion = providerInfo?.version ?? "";
|
|
3754
3861
|
let modelIdForRequest = selectedModel.id;
|
|
@@ -3851,9 +3958,9 @@ var RoutstrClient = class {
|
|
|
3851
3958
|
* Make the API request with failover support
|
|
3852
3959
|
*/
|
|
3853
3960
|
async _makeRequest(params) {
|
|
3854
|
-
const { path, method, body, baseUrl, token, headers } = params;
|
|
3961
|
+
const { path: path2, method, body, baseUrl, token, headers } = params;
|
|
3855
3962
|
try {
|
|
3856
|
-
const url = `${baseUrl.replace(/\/$/, "")}${
|
|
3963
|
+
const url = `${baseUrl.replace(/\/$/, "")}${path2}`;
|
|
3857
3964
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
3858
3965
|
const response = await fetch(url, {
|
|
3859
3966
|
method,
|
|
@@ -3903,11 +4010,12 @@ var RoutstrClient = class {
|
|
|
3903
4010
|
*/
|
|
3904
4011
|
async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
|
|
3905
4012
|
const MAX_RETRIES_PER_PROVIDER = 2;
|
|
3906
|
-
const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
|
|
4013
|
+
const { path: path2, method, body, selectedModel, baseUrl, mintUrl } = params;
|
|
3907
4014
|
let tryNextProvider = false;
|
|
4015
|
+
const errorMessage = responseBody;
|
|
3908
4016
|
this._log(
|
|
3909
4017
|
"DEBUG",
|
|
3910
|
-
`[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}`
|
|
4018
|
+
`[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}, errorMessage=${errorMessage}`
|
|
3911
4019
|
);
|
|
3912
4020
|
this._log(
|
|
3913
4021
|
"DEBUG",
|
|
@@ -4204,7 +4312,7 @@ var RoutstrClient = class {
|
|
|
4204
4312
|
});
|
|
4205
4313
|
return this._makeRequest({
|
|
4206
4314
|
...params,
|
|
4207
|
-
path,
|
|
4315
|
+
path: path2,
|
|
4208
4316
|
method,
|
|
4209
4317
|
body,
|
|
4210
4318
|
baseUrl: nextProvider,
|