@apifuse/provider-sdk 2.2.0-beta.8 → 2.2.0-beta.9
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 +4 -0
- package/dist/config/loader.d.ts +68 -0
- package/dist/config/loader.js +89 -1
- package/dist/define.js +40 -9
- package/dist/runtime/choice.js +20 -2
- package/dist/runtime/http.js +112 -9
- package/dist/runtime/proxy-nodemaven.d.ts +1 -0
- package/dist/runtime/proxy-nodemaven.js +1 -1
- package/dist/runtime/state.js +12 -1
- package/dist/runtime/stealth.js +47 -16
- package/dist/server/serve.js +11 -0
- package/package.json +1 -1
- package/src/config/loader.ts +109 -0
- package/src/define.ts +45 -14
- package/src/runtime/choice.ts +25 -4
- package/src/runtime/http.ts +131 -11
- package/src/runtime/proxy-nodemaven.ts +1 -1
- package/src/runtime/state.ts +11 -1
- package/src/runtime/stealth.ts +49 -19
- package/src/server/serve.ts +11 -0
package/src/runtime/stealth.ts
CHANGED
|
@@ -6,10 +6,11 @@ import type { ProxyResolutionOptions, ProxyVendorName } from "../config/loader.j
|
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_SMARTPROXY_POOL_SIZE,
|
|
8
8
|
invalidateProxyResolutionCacheAsync,
|
|
9
|
+
policyResolvesRegistryVendorChain,
|
|
9
10
|
ProxyResolutionError,
|
|
10
11
|
resolvePolicyProxyPoolSpan,
|
|
12
|
+
resolvePolicyTransportAttemptCap,
|
|
11
13
|
resolveProxyConfigAsync,
|
|
12
|
-
SMARTPROXY_MAX_POOL_SIZE,
|
|
13
14
|
vendorFromResolvedSource,
|
|
14
15
|
} from "../config/loader.js";
|
|
15
16
|
import { SDKError, TransportError } from "../errors.js";
|
|
@@ -53,11 +54,6 @@ const DEFAULT_PROFILE = "chrome-146";
|
|
|
53
54
|
const MISSING_PROXY_WARNING =
|
|
54
55
|
"[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
|
|
55
56
|
|
|
56
|
-
/**
|
|
57
|
-
* Upper bound on attempts across a multi-vendor chain, so a two-vendor chain can
|
|
58
|
-
* exhaust each vendor's pool before failing over and finally throwing.
|
|
59
|
-
*/
|
|
60
|
-
const MAX_POLICY_PROXY_TOTAL_ATTEMPTS = SMARTPROXY_MAX_POOL_SIZE * 2;
|
|
61
57
|
const MAX_POLICY_PROXY_POOL_REFRESHES = 1;
|
|
62
58
|
const PROXY_CONNECT_FAILURE_CODE = "proxy_connect_failed";
|
|
63
59
|
const PROXY_CONNECT_FAILURE_BODY_PATTERN =
|
|
@@ -682,14 +678,26 @@ function createSessionFetcher(
|
|
|
682
678
|
(typeof clientOptions.upstream?.proxy === "object"
|
|
683
679
|
? clientOptions.upstream.proxy
|
|
684
680
|
: undefined);
|
|
681
|
+
// The pool span is already bounded by each vendor's max pool size
|
|
682
|
+
// (smartproxy ≤20, nodemaven ≤50), so the configured span never exceeds
|
|
683
|
+
// the chain's true maximum — a large NodeMaven pool stays fully
|
|
684
|
+
// reachable rather than being truncated at an arbitrary ceiling.
|
|
685
685
|
const policyProxyAttemptCap = Math.max(
|
|
686
686
|
1,
|
|
687
|
-
|
|
688
|
-
MAX_POLICY_PROXY_TOTAL_ATTEMPTS,
|
|
689
|
-
policyProxy ? resolvePolicyProxyPoolSpan(policyProxy) : DEFAULT_SMARTPROXY_POOL_SIZE,
|
|
690
|
-
),
|
|
687
|
+
policyProxy ? resolvePolicyProxyPoolSpan(policyProxy) : DEFAULT_SMARTPROXY_POOL_SIZE,
|
|
691
688
|
);
|
|
692
|
-
|
|
689
|
+
// A registry vendor chain (smartproxy/nodemaven) is the only policy whose
|
|
690
|
+
// successive attempts resolve a *different* endpoint, so it is the only one
|
|
691
|
+
// that may widen the attempt cap to the pool span, de-duplicate endpoints,
|
|
692
|
+
// and drive allocator stale-pool refresh. A static custom/decodo policy
|
|
693
|
+
// resolves the same URL every attempt: widening/refreshing it would resend
|
|
694
|
+
// the request dozens of times (up to maxAttempts × refreshes) and bypass
|
|
695
|
+
// retry:false and unsafe-method controls. Static policies therefore follow
|
|
696
|
+
// the ordinary transport-retry budget instead.
|
|
697
|
+
const rotatesRegistryChain =
|
|
698
|
+
usesPolicyAllocator && policyResolvesRegistryVendorChain(policyProxy);
|
|
699
|
+
const maxAttempts = rotatesRegistryChain ? policyProxyAttemptCap : retryAttemptCap;
|
|
700
|
+
const dedupeAllocatorEndpoints = rotatesRegistryChain;
|
|
693
701
|
let lastError: unknown;
|
|
694
702
|
|
|
695
703
|
for (
|
|
@@ -730,9 +738,14 @@ function createSessionFetcher(
|
|
|
730
738
|
assertNoUnsupportedFingerprintOverrides(options);
|
|
731
739
|
attemptProxy = await resolveRequestProxy(options, attempt, refreshAttempt);
|
|
732
740
|
proxy = attemptProxy.url;
|
|
733
|
-
if (proxy &&
|
|
741
|
+
if (proxy && dedupeAllocatorEndpoints) {
|
|
742
|
+
// An under-filled allocation repeats endpoints (via the modulo
|
|
743
|
+
// pool mapping) before the flat offset crosses into the next
|
|
744
|
+
// vendor. Skip an already-tried endpoint and advance the offset
|
|
745
|
+
// rather than breaking — breaking here would strand the request on
|
|
746
|
+
// the primary vendor and never reach the fallback leg.
|
|
734
747
|
if (attemptedProxies.has(proxy)) {
|
|
735
|
-
|
|
748
|
+
continue;
|
|
736
749
|
}
|
|
737
750
|
attemptedProxies.add(proxy);
|
|
738
751
|
}
|
|
@@ -812,7 +825,7 @@ function createSessionFetcher(
|
|
|
812
825
|
proxyAttemptStatus(normalizedError),
|
|
813
826
|
);
|
|
814
827
|
lastError = normalizedError;
|
|
815
|
-
if (proxy &&
|
|
828
|
+
if (proxy && rotatesRegistryChain && isProxyPoolRefreshableError(normalizedError)) {
|
|
816
829
|
stalePoolError = normalizedError;
|
|
817
830
|
if (shouldRunProxyAuthDiagnostic(normalizedError)) {
|
|
818
831
|
stalePoolDiagnosticProxy = proxy;
|
|
@@ -822,11 +835,28 @@ function createSessionFetcher(
|
|
|
822
835
|
}
|
|
823
836
|
break;
|
|
824
837
|
}
|
|
838
|
+
// Cap the number of transport retries. For a policy-allocator chain,
|
|
839
|
+
// every attempt resolves a *different* endpoint/vendor (poolIndex
|
|
840
|
+
// rotates across the concatenated vendor pool spans), so a transport
|
|
841
|
+
// failure is a signal to advance to the next endpoint — potentially
|
|
842
|
+
// crossing into the fallback vendor — not to retry the same endpoint.
|
|
843
|
+
// Truncating that rotation at the per-endpoint retry budget would
|
|
844
|
+
// strand the request on the primary vendor and never reach the
|
|
845
|
+
// fallback, since the crossover only happens once the flat attempt
|
|
846
|
+
// index exceeds the primary vendor's pool size (~10-20).
|
|
847
|
+
// resolvePolicyTransportAttemptCap widens to the full chain span only
|
|
848
|
+
// for implicit, safe-method allocator requests; explicit retry
|
|
849
|
+
// policies (their documented `attempts` ceiling), unsafe methods, and
|
|
850
|
+
// static/non-registry vendors keep the per-endpoint retry budget.
|
|
851
|
+
const transportRetryCap = resolvePolicyTransportAttemptCap({
|
|
852
|
+
policy: policyProxy,
|
|
853
|
+
usesPolicyAllocator,
|
|
854
|
+
retryAttempts: stealthRetryOptions?.attempts ?? 1,
|
|
855
|
+
explicitRetry: hasExplicitRetryPolicy,
|
|
856
|
+
method,
|
|
857
|
+
});
|
|
825
858
|
if (
|
|
826
|
-
attempt + 1 <
|
|
827
|
-
(stealthRetryOptions
|
|
828
|
-
? Math.min(maxAttempts, stealthRetryOptions.attempts)
|
|
829
|
-
: maxAttempts) &&
|
|
859
|
+
attempt + 1 < transportRetryCap &&
|
|
830
860
|
shouldRetryProxyTransportAttempt({
|
|
831
861
|
error: normalizedError,
|
|
832
862
|
explicitRetry: hasExplicitRetryPolicy,
|
|
@@ -845,7 +875,7 @@ function createSessionFetcher(
|
|
|
845
875
|
}
|
|
846
876
|
|
|
847
877
|
if (
|
|
848
|
-
|
|
878
|
+
rotatesRegistryChain &&
|
|
849
879
|
stalePoolError &&
|
|
850
880
|
refreshAttempt < MAX_POLICY_PROXY_POOL_REFRESHES
|
|
851
881
|
) {
|
package/src/server/serve.ts
CHANGED
|
@@ -512,11 +512,22 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
|
|
|
512
512
|
};
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
+
// A masked internal error MUST NOT be advertised as retryable: without an
|
|
516
|
+
// explicit retryable:false the hub (bori provider-backed engine) defaults 5xx
|
|
517
|
+
// to retryable:true, which turns a deterministic pre-upstream crash into an
|
|
518
|
+
// infinite START->CONTINUE->restart loop (2026-07-22 catchtable reserve RCA).
|
|
519
|
+
// We still refuse to leak message/stack — only the error class name (or the
|
|
520
|
+
// primitive type for non-Error throwables) is surfaced for ops triage.
|
|
515
521
|
return {
|
|
516
522
|
error: {
|
|
517
523
|
code: "internal_error",
|
|
518
524
|
message: "Internal error",
|
|
519
525
|
...(requestId ? { requestId } : {}),
|
|
526
|
+
details: {
|
|
527
|
+
retryable: false,
|
|
528
|
+
category: "internal_error",
|
|
529
|
+
errorClass: error instanceof Error ? error.name : typeof error,
|
|
530
|
+
},
|
|
520
531
|
},
|
|
521
532
|
};
|
|
522
533
|
}
|