@apifuse/provider-sdk 2.2.0-beta.7 → 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 +8 -0
- package/dist/auth-turn/index.d.ts +2 -2
- package/dist/config/loader.d.ts +147 -6
- package/dist/config/loader.js +360 -48
- package/dist/define.js +63 -8
- package/dist/runtime/choice.js +20 -2
- package/dist/runtime/http.js +115 -9
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +35 -0
- package/dist/runtime/proxy-nodemaven.js +128 -0
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +39 -4
- package/dist/runtime/state.js +12 -1
- package/dist/runtime/stealth.js +60 -18
- package/dist/server/serve.js +11 -0
- package/dist/server/types.d.ts +9 -9
- package/dist/types.d.ts +30 -1
- package/package.json +4 -3
- package/src/config/loader.ts +514 -61
- package/src/define.ts +76 -13
- package/src/runtime/choice.ts +25 -4
- package/src/runtime/http.ts +134 -11
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +178 -0
- package/src/runtime/proxy-telemetry.ts +56 -5
- package/src/runtime/state.ts +11 -1
- package/src/runtime/stealth.ts +68 -22
- package/src/server/serve.ts +11 -0
- package/src/types.ts +30 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ProxyAttemptTelemetryEvent,
|
|
3
3
|
ProxyCacheStatus,
|
|
4
|
+
ProxyProtocol,
|
|
4
5
|
ProxyResolutionTelemetryEvent,
|
|
5
6
|
ProxyTelemetrySink,
|
|
7
|
+
ProxyVendorFailoverTelemetryEvent,
|
|
8
|
+
ProxyVendorName,
|
|
6
9
|
SmartproxyAllocatorBodyClass,
|
|
7
10
|
} from "../config/loader.js";
|
|
8
11
|
|
|
@@ -11,7 +14,8 @@ export const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
|
|
|
11
14
|
type ProviderTelemetryHeader = {
|
|
12
15
|
v: 1;
|
|
13
16
|
proxy?: {
|
|
14
|
-
provider:
|
|
17
|
+
provider: ProxyVendorName;
|
|
18
|
+
protocol?: ProxyProtocol;
|
|
15
19
|
cacheStatus: ProxyCacheStatus;
|
|
16
20
|
cacheHit: boolean;
|
|
17
21
|
resolutionMs: number;
|
|
@@ -27,6 +31,10 @@ type ProviderTelemetryHeader = {
|
|
|
27
31
|
attempts: number;
|
|
28
32
|
refreshes?: number;
|
|
29
33
|
attemptSamples?: CompactProxyAttemptSample[];
|
|
34
|
+
/** Distinct vendors attempted across the resolution chain, in order seen. */
|
|
35
|
+
vendors?: ProxyVendorName[];
|
|
36
|
+
/** Cross-vendor failover events (bounded). */
|
|
37
|
+
failovers?: CompactVendorFailoverSample[];
|
|
30
38
|
};
|
|
31
39
|
};
|
|
32
40
|
|
|
@@ -41,8 +49,17 @@ type CompactProxyAttemptSample = {
|
|
|
41
49
|
d?: number;
|
|
42
50
|
};
|
|
43
51
|
|
|
52
|
+
type CompactVendorFailoverSample = {
|
|
53
|
+
v: ProxyVendorName;
|
|
54
|
+
nx?: ProxyVendorName;
|
|
55
|
+
p: "resolution" | "transport";
|
|
56
|
+
r: ProxyVendorFailoverTelemetryEvent["reason"];
|
|
57
|
+
a?: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
44
60
|
const MAX_HEADER_BYTES = 4_096;
|
|
45
61
|
const MAX_PROXY_ATTEMPT_SAMPLES = 24;
|
|
62
|
+
const MAX_PROXY_FAILOVER_SAMPLES = 12;
|
|
46
63
|
|
|
47
64
|
const CACHE_STATUS_SEVERITY: Record<ProxyCacheStatus, number> = {
|
|
48
65
|
disabled: 0,
|
|
@@ -76,10 +93,12 @@ function encodeBase64Url(value: string): string {
|
|
|
76
93
|
export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
77
94
|
#events: ProxyResolutionTelemetryEvent[] = [];
|
|
78
95
|
#attempts: ProxyAttemptTelemetryEvent[] = [];
|
|
96
|
+
#failovers: ProxyVendorFailoverTelemetryEvent[] = [];
|
|
79
97
|
|
|
80
98
|
recordProxyResolution(event: ProxyResolutionTelemetryEvent): void {
|
|
81
99
|
this.#events.push({
|
|
82
|
-
provider:
|
|
100
|
+
provider: event.provider,
|
|
101
|
+
...(event.protocol ? { protocol: event.protocol } : {}),
|
|
83
102
|
cacheStatus: event.cacheStatus,
|
|
84
103
|
cacheHit: event.cacheHit,
|
|
85
104
|
resolutionMs: Math.max(0, Math.floor(event.resolutionMs)),
|
|
@@ -112,10 +131,21 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
|
112
131
|
});
|
|
113
132
|
}
|
|
114
133
|
|
|
134
|
+
recordProxyVendorFailover(event: ProxyVendorFailoverTelemetryEvent): void {
|
|
135
|
+
if (this.#failovers.length >= MAX_PROXY_FAILOVER_SAMPLES) return;
|
|
136
|
+
this.#failovers.push({
|
|
137
|
+
vendor: event.vendor,
|
|
138
|
+
...(event.nextVendor ? { nextVendor: event.nextVendor } : {}),
|
|
139
|
+
phase: event.phase,
|
|
140
|
+
reason: event.reason,
|
|
141
|
+
...(event.attempt === undefined ? {} : { attempt: Math.max(0, Math.floor(event.attempt)) }),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
115
145
|
recordProxyAttempt(event: ProxyAttemptTelemetryEvent): void {
|
|
116
146
|
if (this.#attempts.length >= MAX_PROXY_ATTEMPT_SAMPLES) return;
|
|
117
147
|
this.#attempts.push({
|
|
118
|
-
provider:
|
|
148
|
+
provider: event.provider,
|
|
119
149
|
attempt: Math.max(1, Math.floor(event.attempt || 1)),
|
|
120
150
|
...(event.poolIndex === undefined
|
|
121
151
|
? {}
|
|
@@ -134,9 +164,17 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
|
134
164
|
const [first, ...rest] = this.#events;
|
|
135
165
|
if (!first) return undefined;
|
|
136
166
|
|
|
167
|
+
// The serving vendor/protocol is the last recorded resolution (a failed
|
|
168
|
+
// vendor records first, the vendor that served records last).
|
|
169
|
+
const serving = this.#events[this.#events.length - 1] ?? first;
|
|
170
|
+
const vendors: ProxyVendorName[] = [];
|
|
171
|
+
for (const event of this.#events) {
|
|
172
|
+
if (!vendors.includes(event.provider)) vendors.push(event.provider);
|
|
173
|
+
}
|
|
174
|
+
|
|
137
175
|
const aggregate = rest.reduce<ProxyResolutionTelemetryEvent>(
|
|
138
176
|
(acc, event) => ({
|
|
139
|
-
provider:
|
|
177
|
+
provider: event.provider,
|
|
140
178
|
cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
|
|
141
179
|
cacheHit: acc.cacheHit && event.cacheHit,
|
|
142
180
|
resolutionMs: acc.resolutionMs + event.resolutionMs,
|
|
@@ -157,7 +195,8 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
|
157
195
|
const payload: ProviderTelemetryHeader = {
|
|
158
196
|
v: 1,
|
|
159
197
|
proxy: {
|
|
160
|
-
provider:
|
|
198
|
+
provider: serving.provider,
|
|
199
|
+
...(serving.protocol ? { protocol: serving.protocol } : {}),
|
|
161
200
|
cacheStatus: aggregate.cacheStatus,
|
|
162
201
|
cacheHit: aggregate.cacheHit,
|
|
163
202
|
resolutionMs: aggregate.resolutionMs,
|
|
@@ -194,6 +233,18 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
|
194
233
|
})),
|
|
195
234
|
}
|
|
196
235
|
: {}),
|
|
236
|
+
...(vendors.length > 1 ? { vendors } : {}),
|
|
237
|
+
...(this.#failovers.length > 0
|
|
238
|
+
? {
|
|
239
|
+
failovers: this.#failovers.map((failover) => ({
|
|
240
|
+
v: failover.vendor,
|
|
241
|
+
...(failover.nextVendor ? { nx: failover.nextVendor } : {}),
|
|
242
|
+
p: failover.phase,
|
|
243
|
+
r: failover.reason,
|
|
244
|
+
...(failover.attempt === undefined ? {} : { a: failover.attempt }),
|
|
245
|
+
})),
|
|
246
|
+
}
|
|
247
|
+
: {}),
|
|
197
248
|
},
|
|
198
249
|
};
|
|
199
250
|
|
package/src/runtime/state.ts
CHANGED
|
@@ -115,7 +115,17 @@ function envelopeFromJson(
|
|
|
115
115
|
// biome-ignore lint/suspicious/noExplicitAny: state envelopes deserialize caller-owned generic values.
|
|
116
116
|
): StateValue<any> | null {
|
|
117
117
|
if (!raw) return null;
|
|
118
|
-
|
|
118
|
+
// A corrupt/undecodable persisted envelope must be treated as absent rather
|
|
119
|
+
// than throwing a raw JSON.parse SyntaxError: an uncaught SyntaxError escapes
|
|
120
|
+
// the provider error taxonomy, is masked as internal_error 500, and is then
|
|
121
|
+
// retried by the hub (2026-07-22 catchtable reserve RCA, candidate A). Returning
|
|
122
|
+
// null also keeps list() from aborting the whole scan on a single bad entry.
|
|
123
|
+
let parsed: unknown;
|
|
124
|
+
try {
|
|
125
|
+
parsed = JSON.parse(raw);
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
119
129
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
120
130
|
return null;
|
|
121
131
|
}
|
package/src/runtime/stealth.ts
CHANGED
|
@@ -2,13 +2,16 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import type { Browser, ImpitOptions, ImpitResponse, RequestInit } from "impit";
|
|
3
3
|
import { Impit } from "impit";
|
|
4
4
|
|
|
5
|
-
import type { ProxyResolutionOptions } from "../config/loader.js";
|
|
5
|
+
import type { ProxyResolutionOptions, ProxyVendorName } from "../config/loader.js";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_SMARTPROXY_POOL_SIZE,
|
|
8
8
|
invalidateProxyResolutionCacheAsync,
|
|
9
|
+
policyResolvesRegistryVendorChain,
|
|
9
10
|
ProxyResolutionError,
|
|
11
|
+
resolvePolicyProxyPoolSpan,
|
|
12
|
+
resolvePolicyTransportAttemptCap,
|
|
10
13
|
resolveProxyConfigAsync,
|
|
11
|
-
|
|
14
|
+
vendorFromResolvedSource,
|
|
12
15
|
} from "../config/loader.js";
|
|
13
16
|
import { SDKError, TransportError } from "../errors.js";
|
|
14
17
|
import { getStealthProfile } from "../stealth/profiles.js";
|
|
@@ -51,7 +54,6 @@ const DEFAULT_PROFILE = "chrome-146";
|
|
|
51
54
|
const MISSING_PROXY_WARNING =
|
|
52
55
|
"[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
|
|
53
56
|
|
|
54
|
-
const MAX_POLICY_PROXY_RETRY_ATTEMPTS = SMARTPROXY_MAX_POOL_SIZE;
|
|
55
57
|
const MAX_POLICY_PROXY_POOL_REFRESHES = 1;
|
|
56
58
|
const PROXY_CONNECT_FAILURE_CODE = "proxy_connect_failed";
|
|
57
59
|
const PROXY_CONNECT_FAILURE_BODY_PATTERN =
|
|
@@ -426,6 +428,7 @@ type ResolvedAttemptProxy = {
|
|
|
426
428
|
url?: string;
|
|
427
429
|
poolIndex?: number;
|
|
428
430
|
proxyHash?: string;
|
|
431
|
+
vendor?: ProxyVendorName;
|
|
429
432
|
};
|
|
430
433
|
|
|
431
434
|
function proxyPoolIndexFromDiagnostics(
|
|
@@ -615,6 +618,7 @@ function createSessionFetcher(
|
|
|
615
618
|
async function resolveRequestProxy(
|
|
616
619
|
options?: StealthFetchOptions,
|
|
617
620
|
proxyAttempt?: number,
|
|
621
|
+
refreshEpoch?: number,
|
|
618
622
|
): Promise<ResolvedAttemptProxy> {
|
|
619
623
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
620
624
|
proxy: options?.proxy ?? clientOptions.proxy,
|
|
@@ -626,6 +630,10 @@ function createSessionFetcher(
|
|
|
626
630
|
proxyAttemptOffset: options?.proxyAttemptOffset,
|
|
627
631
|
retryAttemptOffset: proxyAttempt,
|
|
628
632
|
}),
|
|
633
|
+
// The impit stealth transport tunnels both HTTP CONNECT and SOCKS5,
|
|
634
|
+
// preserving the client TLS fingerprint end-to-end.
|
|
635
|
+
transportProtocols: ["http", "socks5"],
|
|
636
|
+
...(refreshEpoch === undefined ? {} : { proxyRefreshEpoch: refreshEpoch }),
|
|
629
637
|
telemetry: clientOptions.telemetry,
|
|
630
638
|
});
|
|
631
639
|
|
|
@@ -638,6 +646,7 @@ function createSessionFetcher(
|
|
|
638
646
|
url: resolvedProxy.url,
|
|
639
647
|
poolIndex: proxyPoolIndexFromDiagnostics(resolvedProxy.diagnostics),
|
|
640
648
|
proxyHash: proxyEndpointHash(resolvedProxy.url),
|
|
649
|
+
vendor: vendorFromResolvedSource(resolvedProxy.source),
|
|
641
650
|
};
|
|
642
651
|
}
|
|
643
652
|
|
|
@@ -662,18 +671,33 @@ function createSessionFetcher(
|
|
|
662
671
|
const hasPolicyProxy = isPolicyManagedProxy(clientOptions);
|
|
663
672
|
const usesPolicyAllocator = hasPolicyProxy && !options.proxy && !clientOptions.proxy;
|
|
664
673
|
const retryAttemptCap = Math.max(1, stealthRetryOptions?.attempts ?? 1);
|
|
674
|
+
// Span the whole vendor chain: successive attempts rotate one vendor's
|
|
675
|
+
// pool, then fail over to the next vendor via the flat attempt index.
|
|
676
|
+
const policyProxy =
|
|
677
|
+
clientOptions.proxyPolicy ??
|
|
678
|
+
(typeof clientOptions.upstream?.proxy === "object"
|
|
679
|
+
? clientOptions.upstream.proxy
|
|
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.
|
|
665
685
|
const policyProxyAttemptCap = Math.max(
|
|
666
686
|
1,
|
|
667
|
-
|
|
668
|
-
MAX_POLICY_PROXY_RETRY_ATTEMPTS,
|
|
669
|
-
clientOptions.proxyPolicy?.session?.poolSize ??
|
|
670
|
-
(typeof clientOptions.upstream?.proxy === "object"
|
|
671
|
-
? clientOptions.upstream.proxy.session?.poolSize
|
|
672
|
-
: undefined) ??
|
|
673
|
-
DEFAULT_SMARTPROXY_POOL_SIZE,
|
|
674
|
-
),
|
|
687
|
+
policyProxy ? resolvePolicyProxyPoolSpan(policyProxy) : DEFAULT_SMARTPROXY_POOL_SIZE,
|
|
675
688
|
);
|
|
676
|
-
|
|
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;
|
|
677
701
|
let lastError: unknown;
|
|
678
702
|
|
|
679
703
|
for (
|
|
@@ -698,7 +722,7 @@ function createSessionFetcher(
|
|
|
698
722
|
if (attemptRecorded || !proxy) return;
|
|
699
723
|
attemptRecorded = true;
|
|
700
724
|
clientOptions.telemetry?.recordProxyAttempt?.({
|
|
701
|
-
provider: "smartproxy",
|
|
725
|
+
provider: attemptProxy?.vendor ?? "smartproxy",
|
|
702
726
|
attempt: attempt + 1,
|
|
703
727
|
...(attemptProxy?.poolIndex === undefined
|
|
704
728
|
? {}
|
|
@@ -712,11 +736,16 @@ function createSessionFetcher(
|
|
|
712
736
|
};
|
|
713
737
|
try {
|
|
714
738
|
assertNoUnsupportedFingerprintOverrides(options);
|
|
715
|
-
attemptProxy = await resolveRequestProxy(options, attempt);
|
|
739
|
+
attemptProxy = await resolveRequestProxy(options, attempt, refreshAttempt);
|
|
716
740
|
proxy = attemptProxy.url;
|
|
717
|
-
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.
|
|
718
747
|
if (attemptedProxies.has(proxy)) {
|
|
719
|
-
|
|
748
|
+
continue;
|
|
720
749
|
}
|
|
721
750
|
attemptedProxies.add(proxy);
|
|
722
751
|
}
|
|
@@ -796,7 +825,7 @@ function createSessionFetcher(
|
|
|
796
825
|
proxyAttemptStatus(normalizedError),
|
|
797
826
|
);
|
|
798
827
|
lastError = normalizedError;
|
|
799
|
-
if (proxy &&
|
|
828
|
+
if (proxy && rotatesRegistryChain && isProxyPoolRefreshableError(normalizedError)) {
|
|
800
829
|
stalePoolError = normalizedError;
|
|
801
830
|
if (shouldRunProxyAuthDiagnostic(normalizedError)) {
|
|
802
831
|
stalePoolDiagnosticProxy = proxy;
|
|
@@ -806,11 +835,28 @@ function createSessionFetcher(
|
|
|
806
835
|
}
|
|
807
836
|
break;
|
|
808
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
|
+
});
|
|
809
858
|
if (
|
|
810
|
-
attempt + 1 <
|
|
811
|
-
(stealthRetryOptions
|
|
812
|
-
? Math.min(maxAttempts, stealthRetryOptions.attempts)
|
|
813
|
-
: maxAttempts) &&
|
|
859
|
+
attempt + 1 < transportRetryCap &&
|
|
814
860
|
shouldRetryProxyTransportAttempt({
|
|
815
861
|
error: normalizedError,
|
|
816
862
|
explicitRetry: hasExplicitRetryPolicy,
|
|
@@ -829,7 +875,7 @@ function createSessionFetcher(
|
|
|
829
875
|
}
|
|
830
876
|
|
|
831
877
|
if (
|
|
832
|
-
|
|
878
|
+
rotatesRegistryChain &&
|
|
833
879
|
stalePoolError &&
|
|
834
880
|
refreshAttempt < MAX_POLICY_PROXY_POOL_REFRESHES
|
|
835
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
|
}
|
package/src/types.ts
CHANGED
|
@@ -769,7 +769,25 @@ export type ProviderAccessVisibility = "public" | "early_access";
|
|
|
769
769
|
|
|
770
770
|
export type ProviderProxyMode = "disabled" | "optional" | "required";
|
|
771
771
|
|
|
772
|
-
|
|
772
|
+
/**
|
|
773
|
+
* Proxy egress vendors. These are FOUR DISTINCT services — do not conflate them
|
|
774
|
+
* (a common mistake because the names collide with a well-known rebrand):
|
|
775
|
+
*
|
|
776
|
+
* - `smartproxy` — **api.smartproxy.org**, a residential proxy with an IP
|
|
777
|
+
* *extraction/allocation* API (app_key → a pool of raw `ip:port` CONNECT
|
|
778
|
+
* endpoints). This is our own vendor. It is NOT the company formerly named
|
|
779
|
+
* "Smartproxy". Credentials: `APIFUSE__PROXY__SMARTPROXY_APP_KEY`.
|
|
780
|
+
* - `nodemaven` — **gate.nodemaven.com**, a *gateway* proxy with static
|
|
781
|
+
* credentials; geo/session encoded in the username, no allocation API.
|
|
782
|
+
* - `decodo` — **decodo.com**, the *gateway* proxy that was named "Smartproxy"
|
|
783
|
+
* (smartproxy.com) before its 2025 rebrand to Decodo. Sticky sessions via
|
|
784
|
+
* username params. A different company from `smartproxy` above.
|
|
785
|
+
* **@deprecated** — unused; no managed adapter. Use `smartproxy`/`nodemaven`,
|
|
786
|
+
* or the `APIFUSE__PROXY__URL` bring-your-own escape hatch.
|
|
787
|
+
* - `custom` — **@deprecated** bring-your-own static proxy URL marker. The
|
|
788
|
+
* `APIFUSE__PROXY__URL` env still works without declaring this value.
|
|
789
|
+
*/
|
|
790
|
+
export type ProviderProxyProvider = "smartproxy" | "nodemaven" | "decodo" | "custom";
|
|
773
791
|
|
|
774
792
|
export type ProviderProxySessionAffinity =
|
|
775
793
|
| "request"
|
|
@@ -783,7 +801,18 @@ export interface ProviderProxyPolicy {
|
|
|
783
801
|
* certificate verification, and vendor allocator endpoints are SDK-owned.
|
|
784
802
|
*/
|
|
785
803
|
mode: ProviderProxyMode;
|
|
804
|
+
/**
|
|
805
|
+
* @deprecated Use `providers: [...]` to declare an ordered vendor fallback
|
|
806
|
+
* chain. A single-element `providers` list is equivalent to this field.
|
|
807
|
+
*/
|
|
786
808
|
provider?: ProviderProxyProvider;
|
|
809
|
+
/**
|
|
810
|
+
* Ordered proxy-vendor fallback chain. The SDK tries each vendor in order and
|
|
811
|
+
* fails over to the next when a vendor lacks credentials or its allocation /
|
|
812
|
+
* transport is exhausted. When omitted, `provider` (or the platform default)
|
|
813
|
+
* is used as a single-vendor chain.
|
|
814
|
+
*/
|
|
815
|
+
providers?: ProviderProxyProvider[];
|
|
787
816
|
geo?: {
|
|
788
817
|
/** ISO 3166-1 alpha-2 country code, for example KR or US. */
|
|
789
818
|
country?: Iso3166Alpha2CountryCode;
|