@apifuse/provider-sdk 2.2.0-beta.1 → 2.2.0-beta.2
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/CHANGELOG.md +5 -0
- package/dist/runtime/http.js +28 -338
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/stealth.js +57 -203
- package/dist/server/serve.js +5 -2
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +1 -0
- package/package.json +1 -1
- package/src/runtime/http.ts +60 -547
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/stealth.ts +100 -361
- package/src/server/serve.ts +8 -2
- package/src/server/types.ts +1 -0
package/dist/runtime/stealth.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Impit } from "impit";
|
|
3
3
|
import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, ProxyResolutionError, resolveProxyConfigAsync, SMARTPROXY_MAX_POOL_SIZE, } from "../config/loader";
|
|
4
|
-
import {
|
|
4
|
+
import { SDKError, TransportError } from "../errors";
|
|
5
5
|
import { getStealthProfile } from "../stealth/profiles";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { createProxyAuthIpDeniedError, createProxyEdgeAuthRejectedError, createProxyEdgeTlsRejectedError, createProxyPoolExhaustedError, createProxyPoolStaleError, isProxyAuthIpDeniedMessage, isProxyEdgeAuthRejectedMessage, isProxyEdgeTlsRejectedResponse, isProxyPoolRefreshableError, isProxyPoolStaleMessage, isProxyPoolStaleStatus, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_STALE_CODE, } from "./proxy-errors";
|
|
7
|
+
import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, normalizeProxyTransportRetryOptions, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy";
|
|
8
8
|
import { appendQueryParams } from "./request-options";
|
|
9
9
|
const DEFAULT_PROFILE = "chrome-146";
|
|
10
10
|
const MISSING_PROXY_WARNING = "[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
|
|
@@ -14,31 +14,7 @@ const PROXY_CONNECT_FAILURE_CODE = "proxy_connect_failed";
|
|
|
14
14
|
const PROXY_CONNECT_FAILURE_BODY_PATTERN = /\bproxy\b.*\b(non[\s-]?200|connect|tunnel)|\bconnect\b.*\bproxy\b|\btunnel\b/i;
|
|
15
15
|
const PROXY_AUTH_DIAGNOSTIC_URL = "http://example.com/";
|
|
16
16
|
const PROXY_AUTH_DIAGNOSTIC_TIMEOUT_MS = 5_000;
|
|
17
|
-
const
|
|
18
|
-
const DEFAULT_STEALTH_RETRY_ERROR_CODES = [
|
|
19
|
-
PROXY_CONNECT_FAILURE_CODE,
|
|
20
|
-
"transport_network_error",
|
|
21
|
-
"transport_timeout",
|
|
22
|
-
];
|
|
23
|
-
const RATE_LIMIT_STEALTH_RETRY_ERROR_CODES = ["transport_timeout"];
|
|
24
|
-
const KNOWN_STEALTH_RETRY_METHODS = new Set([
|
|
25
|
-
"GET",
|
|
26
|
-
"HEAD",
|
|
27
|
-
"POST",
|
|
28
|
-
"PUT",
|
|
29
|
-
"DELETE",
|
|
30
|
-
"OPTIONS",
|
|
31
|
-
"TRACE",
|
|
32
|
-
"PATCH",
|
|
33
|
-
]);
|
|
34
|
-
const UNSAFE_STEALTH_RETRY_METHODS = new Set([
|
|
35
|
-
"POST",
|
|
36
|
-
"PUT",
|
|
37
|
-
"PATCH",
|
|
38
|
-
"DELETE",
|
|
39
|
-
"TRACE",
|
|
40
|
-
]);
|
|
41
|
-
const MAX_STEALTH_RETRY_ATTEMPTS = 8;
|
|
17
|
+
const STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES = [PROXY_CONNECT_FAILURE_CODE];
|
|
42
18
|
const REMOVED_CHROME_PROFILE_NAMES = new Set([
|
|
43
19
|
"chrome-120",
|
|
44
20
|
"chrome-124",
|
|
@@ -303,130 +279,8 @@ function isPolicyManagedProxy(options) {
|
|
|
303
279
|
const policy = options.proxyPolicy ?? options.upstream?.proxy;
|
|
304
280
|
return Boolean(policy && typeof policy === "object");
|
|
305
281
|
}
|
|
306
|
-
function isRetrySafeStealthMethod(method) {
|
|
307
|
-
return method === "GET" || method === "HEAD" || method === "OPTIONS";
|
|
308
|
-
}
|
|
309
|
-
function createStealthRetryOptions(preset) {
|
|
310
|
-
switch (preset) {
|
|
311
|
-
case HttpRetryPreset.Off:
|
|
312
|
-
return {
|
|
313
|
-
attempts: 1,
|
|
314
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
315
|
-
errorCodes: DEFAULT_STEALTH_RETRY_ERROR_CODES,
|
|
316
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
317
|
-
};
|
|
318
|
-
case HttpRetryPreset.AggressiveRead:
|
|
319
|
-
return {
|
|
320
|
-
attempts: 4,
|
|
321
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
322
|
-
errorCodes: DEFAULT_STEALTH_RETRY_ERROR_CODES,
|
|
323
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
324
|
-
};
|
|
325
|
-
case HttpRetryPreset.RateLimitAware:
|
|
326
|
-
return {
|
|
327
|
-
attempts: 3,
|
|
328
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
329
|
-
errorCodes: RATE_LIMIT_STEALTH_RETRY_ERROR_CODES,
|
|
330
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
331
|
-
};
|
|
332
|
-
case HttpRetryPreset.SafeRead:
|
|
333
|
-
case HttpRetryPreset.TransportTransient:
|
|
334
|
-
return {
|
|
335
|
-
attempts: 3,
|
|
336
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
337
|
-
errorCodes: DEFAULT_STEALTH_RETRY_ERROR_CODES,
|
|
338
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
throw new ProviderError(`Unknown stealth retry preset: ${preset}`, {
|
|
342
|
-
code: "retry_invalid_policy",
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
function normalizeStealthRetryOptions(retry) {
|
|
346
|
-
if (retry === undefined)
|
|
347
|
-
return undefined;
|
|
348
|
-
if (retry === false)
|
|
349
|
-
return createStealthRetryOptions(HttpRetryPreset.Off);
|
|
350
|
-
if (retry === true)
|
|
351
|
-
return createStealthRetryOptions(HttpRetryPreset.TransportTransient);
|
|
352
|
-
if (typeof retry === "string") {
|
|
353
|
-
if (!Object.values(HttpRetryPreset).includes(retry)) {
|
|
354
|
-
throw new ProviderError(`Unknown stealth retry preset: ${retry}`, {
|
|
355
|
-
code: "retry_invalid_policy",
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
return createStealthRetryOptions(retry);
|
|
359
|
-
}
|
|
360
|
-
if (typeof retry !== "object" || retry === null || Array.isArray(retry)) {
|
|
361
|
-
throw new ProviderError("Stealth retry policy must be a plain object", {
|
|
362
|
-
code: "retry_invalid_policy",
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
if (retry.unsafeMethodPolicy !== undefined &&
|
|
366
|
-
!Object.values(HttpRetryUnsafeMethodPolicy).includes(retry.unsafeMethodPolicy)) {
|
|
367
|
-
throw new ProviderError(`Unknown stealth retry unsafe method policy: ${String(retry.unsafeMethodPolicy)}`, { code: "retry_invalid_policy" });
|
|
368
|
-
}
|
|
369
|
-
if (retry.methods !== undefined) {
|
|
370
|
-
if (!Array.isArray(retry.methods)) {
|
|
371
|
-
throw new ProviderError("Stealth retry methods must be an array", {
|
|
372
|
-
code: "retry_invalid_policy",
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
const unknownMethods = retry.methods
|
|
376
|
-
.map((method) => (typeof method === "string" ? method.toUpperCase() : ""))
|
|
377
|
-
.filter((method) => !KNOWN_STEALTH_RETRY_METHODS.has(method));
|
|
378
|
-
if (unknownMethods.length > 0) {
|
|
379
|
-
throw new ProviderError(`Unknown stealth retry method(s): ${unknownMethods.join(", ")}`, { code: "retry_invalid_policy" });
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
if (retry.errorCodes !== undefined) {
|
|
383
|
-
if (!Array.isArray(retry.errorCodes) ||
|
|
384
|
-
retry.errorCodes.some((errorCode) => typeof errorCode !== "string")) {
|
|
385
|
-
throw new ProviderError("Stealth retry errorCodes must contain only strings", { code: "retry_invalid_policy" });
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
const base = createStealthRetryOptions(retry.preset ?? HttpRetryPreset.TransportTransient);
|
|
389
|
-
const attempts = retry.attempts === undefined || !Number.isFinite(retry.attempts)
|
|
390
|
-
? base.attempts
|
|
391
|
-
: Math.max(1, Math.min(MAX_STEALTH_RETRY_ATTEMPTS, Math.floor(retry.attempts)));
|
|
392
|
-
const normalized = {
|
|
393
|
-
attempts,
|
|
394
|
-
methods: retry.methods?.map((method) => method.toUpperCase()) ?? base.methods,
|
|
395
|
-
errorCodes: retry.errorCodes ?? base.errorCodes,
|
|
396
|
-
unsafeMethodPolicy: retry.unsafeMethodPolicy ?? base.unsafeMethodPolicy,
|
|
397
|
-
};
|
|
398
|
-
if (normalized.unsafeMethodPolicy !==
|
|
399
|
-
HttpRetryUnsafeMethodPolicy.AllowExplicitUnsafe) {
|
|
400
|
-
const unsafeMethods = normalized.methods.filter((method) => UNSAFE_STEALTH_RETRY_METHODS.has(method.toUpperCase()));
|
|
401
|
-
if (unsafeMethods.length > 0) {
|
|
402
|
-
throw new ProviderError(`Stealth retry methods include unsafe method(s): ${unsafeMethods.join(", ")}`, { code: "retry_unsafe_method" });
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return normalized;
|
|
406
|
-
}
|
|
407
|
-
function isExplicitStealthRetryAllowed(method, error, retryOptions) {
|
|
408
|
-
if (!retryOptions || retryOptions.attempts <= 1)
|
|
409
|
-
return false;
|
|
410
|
-
return (retryOptions.methods.includes(method.toUpperCase()) &&
|
|
411
|
-
retryOptions.errorCodes.includes(proxyAttemptErrorCode(error)));
|
|
412
|
-
}
|
|
413
|
-
function isRetryableProxyTransportError(error) {
|
|
414
|
-
if (error instanceof TransportError) {
|
|
415
|
-
if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
return (error.code === PROXY_CONNECT_FAILURE_CODE ||
|
|
419
|
-
error.code === "transport_network_error" ||
|
|
420
|
-
error.code === "transport_timeout");
|
|
421
|
-
}
|
|
422
|
-
if (error instanceof SDKError) {
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
426
|
-
return /\bproxy\b|\bnon[\s-]?200\b|\bconnect\b|\btunnel\b/i.test(message);
|
|
427
|
-
}
|
|
428
282
|
function isProxyConnectFailureResponse(response, body) {
|
|
429
|
-
return
|
|
283
|
+
return response.status === 0 && PROXY_CONNECT_FAILURE_BODY_PATTERN.test(body ?? "");
|
|
430
284
|
}
|
|
431
285
|
function createProxyConnectFailureError(body, cause) {
|
|
432
286
|
const bodyExcerpt = (body ?? "").trim().slice(0, 1_000);
|
|
@@ -529,8 +383,7 @@ function normalizeStealthTransportError(error) {
|
|
|
529
383
|
return createProxyEdgeAuthRejectedError(error instanceof Error ? error : undefined);
|
|
530
384
|
}
|
|
531
385
|
const proxyTunnelStatus = getProxyTunnelStatus(error);
|
|
532
|
-
if (proxyTunnelStatus !== undefined &&
|
|
533
|
-
isProxyPoolStaleStatus(proxyTunnelStatus)) {
|
|
386
|
+
if (proxyTunnelStatus !== undefined && isProxyPoolStaleStatus(proxyTunnelStatus)) {
|
|
534
387
|
return createProxyPoolStaleError(proxyTunnelStatus, error instanceof Error ? error : undefined);
|
|
535
388
|
}
|
|
536
389
|
if (PROXY_CONNECT_FAILURE_BODY_PATTERN.test(message)) {
|
|
@@ -542,6 +395,9 @@ function normalizeStealthTransportError(error) {
|
|
|
542
395
|
cause: error instanceof Error ? error : undefined,
|
|
543
396
|
});
|
|
544
397
|
}
|
|
398
|
+
function sleep(ms) {
|
|
399
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
400
|
+
}
|
|
545
401
|
function normalizeMethod(method) {
|
|
546
402
|
switch (method.toUpperCase()) {
|
|
547
403
|
case "HEAD":
|
|
@@ -608,18 +464,16 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
608
464
|
return client;
|
|
609
465
|
}
|
|
610
466
|
async function resolveRequestProxy(options, proxyAttempt) {
|
|
611
|
-
const rawProxyAttemptOffset = options?.proxyAttemptOffset ?? 0;
|
|
612
|
-
const proxyAttemptOffset = Number.isFinite(rawProxyAttemptOffset)
|
|
613
|
-
? Math.max(0, Math.floor(rawProxyAttemptOffset))
|
|
614
|
-
: 0;
|
|
615
467
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
616
468
|
proxy: options?.proxy ?? clientOptions.proxy,
|
|
617
469
|
upstream: clientOptions.upstream,
|
|
618
470
|
apifuseConfig: clientOptions.apifuseConfig,
|
|
619
471
|
affinityKey: clientOptions.affinityKey,
|
|
620
|
-
proxyAttempt:
|
|
621
|
-
|
|
622
|
-
: proxyAttemptOffset
|
|
472
|
+
proxyAttempt: computeProxyAttemptIndex({
|
|
473
|
+
baseProxyAttempt: clientOptions.proxyAttempt,
|
|
474
|
+
proxyAttemptOffset: options?.proxyAttemptOffset,
|
|
475
|
+
retryAttemptOffset: proxyAttempt,
|
|
476
|
+
}),
|
|
623
477
|
telemetry: clientOptions.telemetry,
|
|
624
478
|
});
|
|
625
479
|
if (resolvedProxy.shouldWarn && !hasWarnedMissingProxy) {
|
|
@@ -636,16 +490,28 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
636
490
|
async fetch(url, options = {}) {
|
|
637
491
|
const method = normalizeMethod(options.method ?? "GET");
|
|
638
492
|
const hasExplicitRetryPolicy = options.retry !== undefined;
|
|
639
|
-
const stealthRetryOptions =
|
|
493
|
+
const stealthRetryOptions = normalizeProxyTransportRetryOptions(options.retry, {
|
|
494
|
+
extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
|
|
495
|
+
label: "Stealth",
|
|
496
|
+
}) ??
|
|
497
|
+
(hasExplicitRetryPolicy
|
|
498
|
+
? undefined
|
|
499
|
+
: createDefaultProxyTransportRetryOptions({
|
|
500
|
+
extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
|
|
501
|
+
label: "Stealth",
|
|
502
|
+
}));
|
|
503
|
+
if (stealthRetryOptions) {
|
|
504
|
+
validateUnsafeProxyTransportRetryMethods(stealthRetryOptions, "Stealth");
|
|
505
|
+
}
|
|
640
506
|
const hasPolicyProxy = isPolicyManagedProxy(clientOptions);
|
|
641
507
|
const usesPolicyAllocator = hasPolicyProxy && !options.proxy && !clientOptions.proxy;
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
508
|
+
const retryAttemptCap = Math.max(1, stealthRetryOptions?.attempts ?? 1);
|
|
509
|
+
const policyProxyAttemptCap = Math.max(1, Math.min(MAX_POLICY_PROXY_RETRY_ATTEMPTS, clientOptions.proxyPolicy?.session?.poolSize ??
|
|
510
|
+
(typeof clientOptions.upstream?.proxy === "object"
|
|
511
|
+
? clientOptions.upstream.proxy.session?.poolSize
|
|
512
|
+
: undefined) ??
|
|
513
|
+
DEFAULT_SMARTPROXY_POOL_SIZE));
|
|
514
|
+
const maxAttempts = usesPolicyAllocator ? policyProxyAttemptCap : retryAttemptCap;
|
|
649
515
|
let lastError;
|
|
650
516
|
for (let refreshAttempt = 0; refreshAttempt <= MAX_POLICY_PROXY_POOL_REFRESHES; refreshAttempt += 1) {
|
|
651
517
|
let stalePoolError;
|
|
@@ -666,9 +532,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
666
532
|
...(attemptProxy?.poolIndex === undefined
|
|
667
533
|
? {}
|
|
668
534
|
: { poolIndex: attemptProxy.poolIndex }),
|
|
669
|
-
...(attemptProxy?.proxyHash
|
|
670
|
-
? { proxyHash: attemptProxy.proxyHash }
|
|
671
|
-
: {}),
|
|
535
|
+
...(attemptProxy?.proxyHash ? { proxyHash: attemptProxy.proxyHash } : {}),
|
|
672
536
|
outcome,
|
|
673
537
|
...(errorCode ? { errorCode } : {}),
|
|
674
538
|
...(status === undefined ? {} : { status }),
|
|
@@ -679,16 +543,14 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
679
543
|
assertNoUnsupportedFingerprintOverrides(options);
|
|
680
544
|
attemptProxy = await resolveRequestProxy(options, attempt);
|
|
681
545
|
proxy = attemptProxy.url;
|
|
682
|
-
if (proxy) {
|
|
546
|
+
if (proxy && usesPolicyAllocator) {
|
|
683
547
|
if (attemptedProxies.has(proxy)) {
|
|
684
548
|
break;
|
|
685
549
|
}
|
|
686
550
|
attemptedProxies.add(proxy);
|
|
687
551
|
}
|
|
688
552
|
const ignoreTlsErrors = Boolean(options.stealth?.insecureSkipVerify ??
|
|
689
|
-
(!hasPolicyProxy &&
|
|
690
|
-
proxy &&
|
|
691
|
-
clientOptions.proxyStealth?.insecureSkipVerify));
|
|
553
|
+
(!hasPolicyProxy && proxy && clientOptions.proxyStealth?.insecureSkipVerify));
|
|
692
554
|
const profileName = options.profile ?? defaultProfile;
|
|
693
555
|
const requestUrl = appendQueryParams(resolveUrl(baseUrl, url), options.params);
|
|
694
556
|
const headers = { ...(options.headers ?? {}) };
|
|
@@ -709,17 +571,13 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
709
571
|
const response = await getClient(profileName, proxy, ignoreTlsErrors).fetch(requestUrl, requestInit);
|
|
710
572
|
const normalized = await normalizeResponse(response, requestUrl);
|
|
711
573
|
cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers));
|
|
712
|
-
if (proxy &&
|
|
713
|
-
isProxyConnectFailureResponse(response, normalized.body)) {
|
|
574
|
+
if (proxy && isProxyConnectFailureResponse(response, normalized.body)) {
|
|
714
575
|
throw createProxyConnectFailureError(normalized.body);
|
|
715
576
|
}
|
|
716
577
|
if (response.status >= 400) {
|
|
717
578
|
if (proxy &&
|
|
718
579
|
usesPolicyAllocator &&
|
|
719
|
-
isProxyEdgeTlsRejectedResponse(response.status, [
|
|
720
|
-
JSON.stringify(responseHeadersToRecord(response.headers)),
|
|
721
|
-
normalized.body,
|
|
722
|
-
].join("\n"))) {
|
|
580
|
+
isProxyEdgeTlsRejectedResponse(response.status, [JSON.stringify(responseHeadersToRecord(response.headers)), normalized.body].join("\n"))) {
|
|
723
581
|
throw createProxyEdgeTlsRejectedError(response.status);
|
|
724
582
|
}
|
|
725
583
|
if (proxy && isProxyAuthIpDeniedMessage(normalized.body)) {
|
|
@@ -747,9 +605,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
747
605
|
const normalizedError = normalizeStealthTransportError(error);
|
|
748
606
|
recordProxyAttempt("error", proxyAttemptErrorCode(normalizedError), proxyAttemptStatus(normalizedError));
|
|
749
607
|
lastError = normalizedError;
|
|
750
|
-
if (proxy &&
|
|
751
|
-
usesPolicyAllocator &&
|
|
752
|
-
isProxyPoolRefreshableError(normalizedError)) {
|
|
608
|
+
if (proxy && usesPolicyAllocator && isProxyPoolRefreshableError(normalizedError)) {
|
|
753
609
|
stalePoolError = normalizedError;
|
|
754
610
|
if (shouldRunProxyAuthDiagnostic(normalizedError)) {
|
|
755
611
|
stalePoolDiagnosticProxy = proxy;
|
|
@@ -759,15 +615,20 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
759
615
|
}
|
|
760
616
|
break;
|
|
761
617
|
}
|
|
762
|
-
if (
|
|
763
|
-
|
|
764
|
-
(stealthRetryOptions
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
618
|
+
if (attempt + 1 <
|
|
619
|
+
(stealthRetryOptions
|
|
620
|
+
? Math.min(maxAttempts, stealthRetryOptions.attempts)
|
|
621
|
+
: maxAttempts) &&
|
|
622
|
+
shouldRetryProxyTransportAttempt({
|
|
623
|
+
error: normalizedError,
|
|
624
|
+
explicitRetry: hasExplicitRetryPolicy,
|
|
625
|
+
method,
|
|
626
|
+
options: stealthRetryOptions,
|
|
627
|
+
proxyUsed: Boolean(proxy),
|
|
628
|
+
})) {
|
|
629
|
+
if (stealthRetryOptions) {
|
|
630
|
+
await sleep(computeProxyTransportRetryDelayMs(stealthRetryOptions, attempt + 1));
|
|
631
|
+
}
|
|
771
632
|
continue;
|
|
772
633
|
}
|
|
773
634
|
throw normalizedError;
|
|
@@ -920,10 +781,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
920
781
|
}
|
|
921
782
|
catch (error) {
|
|
922
783
|
const message = error instanceof Error
|
|
923
|
-
? [
|
|
924
|
-
error.message,
|
|
925
|
-
error.cause instanceof Error ? error.cause.message : "",
|
|
926
|
-
]
|
|
784
|
+
? [error.message, error.cause instanceof Error ? error.cause.message : ""]
|
|
927
785
|
.filter(Boolean)
|
|
928
786
|
.join(" ")
|
|
929
787
|
: String(error);
|
|
@@ -951,12 +809,8 @@ function hasHeader(headers, name) {
|
|
|
951
809
|
return Object.keys(headers).some((key) => key.toLowerCase() === needle);
|
|
952
810
|
}
|
|
953
811
|
export function createStealthClient(baseUrl, defaultProfileOrOptions = DEFAULT_PROFILE, clientOptions = {}) {
|
|
954
|
-
const defaultProfile = typeof defaultProfileOrOptions === "string"
|
|
955
|
-
|
|
956
|
-
: DEFAULT_PROFILE;
|
|
957
|
-
const resolvedClientOptions = typeof defaultProfileOrOptions === "string"
|
|
958
|
-
? clientOptions
|
|
959
|
-
: defaultProfileOrOptions;
|
|
812
|
+
const defaultProfile = typeof defaultProfileOrOptions === "string" ? defaultProfileOrOptions : DEFAULT_PROFILE;
|
|
813
|
+
const resolvedClientOptions = typeof defaultProfileOrOptions === "string" ? clientOptions : defaultProfileOrOptions;
|
|
960
814
|
let sharedSession = null;
|
|
961
815
|
function getSharedSession() {
|
|
962
816
|
if (!sharedSession) {
|
package/dist/server/serve.js
CHANGED
|
@@ -107,7 +107,7 @@ function isProductionProviderBrowserMode(provider, env = process.env) {
|
|
|
107
107
|
return (env.NODE_ENV === "production" && env.APIFUSE__PROVIDER__ID === provider.id);
|
|
108
108
|
}
|
|
109
109
|
export function resolveProviderProxyAffinityKey(provider, request, operationId) {
|
|
110
|
-
const connectionKey = request
|
|
110
|
+
const connectionKey = resolveOperationConnectionId(request) ?? request.connection?.externalRef;
|
|
111
111
|
const affinity = typeof provider.proxy === "object"
|
|
112
112
|
? provider.proxy.session?.affinity
|
|
113
113
|
: undefined;
|
|
@@ -116,6 +116,9 @@ export function resolveProviderProxyAffinityKey(provider, request, operationId)
|
|
|
116
116
|
}
|
|
117
117
|
return connectionKey ?? provider.id;
|
|
118
118
|
}
|
|
119
|
+
function resolveOperationConnectionId(request) {
|
|
120
|
+
return request.connection?.id ?? request.connectionId;
|
|
121
|
+
}
|
|
119
122
|
function createProviderContext(provider, request, operationId, options = {}, state = createUnsupportedProviderRuntimeState(), proxyTelemetry) {
|
|
120
123
|
const baseUrl = getProviderBaseUrl(provider);
|
|
121
124
|
const stealthBaseUrl = getProviderStealthBaseUrl(provider);
|
|
@@ -142,7 +145,7 @@ function createProviderContext(provider, request, operationId, options = {}, sta
|
|
|
142
145
|
values: request.connection?.secrets,
|
|
143
146
|
});
|
|
144
147
|
const requestContext = {
|
|
145
|
-
connectionId: request
|
|
148
|
+
connectionId: resolveOperationConnectionId(request),
|
|
146
149
|
headers: request.headers ?? {},
|
|
147
150
|
};
|
|
148
151
|
const context = wrapWithInstrumentation({
|
package/dist/server/types.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare const OperationConnectionSchema: z.ZodObject<{
|
|
|
21
21
|
export declare const OperationRequestSchema: z.ZodObject<{
|
|
22
22
|
requestId: z.ZodString;
|
|
23
23
|
input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
24
|
+
connectionId: z.ZodOptional<z.ZodString>;
|
|
24
25
|
connection: z.ZodOptional<z.ZodObject<{
|
|
25
26
|
id: z.ZodString;
|
|
26
27
|
mode: z.ZodEnum<{
|
package/dist/server/types.js
CHANGED
|
@@ -17,6 +17,7 @@ export const OperationConnectionSchema = z.object({
|
|
|
17
17
|
export const OperationRequestSchema = z.object({
|
|
18
18
|
requestId: z.string(),
|
|
19
19
|
input: z.record(z.string(), z.unknown()),
|
|
20
|
+
connectionId: z.string().optional(),
|
|
20
21
|
connection: OperationConnectionSchema.optional(),
|
|
21
22
|
headers: z.record(z.string(), z.string()).optional(),
|
|
22
23
|
trace: z.record(z.string(), z.string()).optional(),
|
package/package.json
CHANGED