@apifuse/provider-sdk 2.2.0-beta.2 → 2.2.0-beta.21

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.
Files changed (291) hide show
  1. package/AUTHORING.md +487 -0
  2. package/CHANGELOG.md +90 -0
  3. package/README.md +49 -3
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +14 -0
  9. package/bin/apifuse-pack-smoke.ts +36 -81
  10. package/bin/apifuse-pack-types.ts +305 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +659 -111
  13. package/bin/apifuse-submit-check.ts +546 -48
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth.d.ts +2 -2
  21. package/dist/auth.js +9 -18
  22. package/dist/ceremonies/index.d.ts +9 -1
  23. package/dist/ceremonies/index.js +65 -18
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/config/loader.d.ts +176 -8
  32. package/dist/config/loader.js +424 -95
  33. package/dist/contract-serialization.d.ts +2 -2
  34. package/dist/contract-serialization.js +3 -6
  35. package/dist/contract-types.d.ts +2 -2
  36. package/dist/contract.d.ts +3 -3
  37. package/dist/contract.js +4 -6
  38. package/dist/define.d.ts +9 -1
  39. package/dist/define.js +304 -119
  40. package/dist/dev.d.ts +1 -1
  41. package/dist/dev.js +1 -1
  42. package/dist/error-resolution.d.ts +2 -0
  43. package/dist/error-resolution.js +90 -0
  44. package/dist/errors.d.ts +22 -1
  45. package/dist/errors.js +88 -0
  46. package/dist/fixture-sanitization.d.ts +26 -0
  47. package/dist/fixture-sanitization.js +216 -0
  48. package/dist/i18n/catalog.d.ts +2 -2
  49. package/dist/i18n/catalog.js +4 -10
  50. package/dist/i18n/index.d.ts +2 -2
  51. package/dist/i18n/index.js +2 -2
  52. package/dist/i18n/keys.d.ts +2 -2
  53. package/dist/index.d.ts +46 -42
  54. package/dist/index.js +41 -37
  55. package/dist/lint.d.ts +1 -1
  56. package/dist/lint.js +8 -15
  57. package/dist/native-address.d.ts +43 -0
  58. package/dist/native-address.js +281 -0
  59. package/dist/native-egress-policy.d.ts +31 -0
  60. package/dist/native-egress-policy.js +288 -0
  61. package/dist/observability.d.ts +5 -2
  62. package/dist/observability.js +48 -1
  63. package/dist/provider.d.ts +12 -11
  64. package/dist/provider.js +10 -9
  65. package/dist/public-schema-field-lint.d.ts +1 -1
  66. package/dist/recipes/gov-api.js +1 -1
  67. package/dist/runtime/auth-flow.d.ts +2 -1
  68. package/dist/runtime/auth-flow.js +4 -3
  69. package/dist/runtime/browser.d.ts +1 -1
  70. package/dist/runtime/browser.js +15 -29
  71. package/dist/runtime/cache.d.ts +1 -1
  72. package/dist/runtime/cache.js +4 -8
  73. package/dist/runtime/choice.d.ts +1 -1
  74. package/dist/runtime/choice.js +31 -35
  75. package/dist/runtime/credential.d.ts +1 -1
  76. package/dist/runtime/credential.js +1 -1
  77. package/dist/runtime/env.d.ts +1 -1
  78. package/dist/runtime/executor.d.ts +1 -1
  79. package/dist/runtime/executor.js +31 -4
  80. package/dist/runtime/http.d.ts +2 -2
  81. package/dist/runtime/http.js +387 -47
  82. package/dist/runtime/insights.d.ts +1 -1
  83. package/dist/runtime/insights.js +6 -13
  84. package/dist/runtime/instrumentation.d.ts +2 -2
  85. package/dist/runtime/instrumentation.js +345 -22
  86. package/dist/runtime/keyring.js +1 -1
  87. package/dist/runtime/namespace.js +1 -1
  88. package/dist/runtime/native-network.d.ts +127 -0
  89. package/dist/runtime/native-network.js +1298 -0
  90. package/dist/runtime/otlp.d.ts +1 -1
  91. package/dist/runtime/perf.d.ts +1 -1
  92. package/dist/runtime/provider.d.ts +1 -1
  93. package/dist/runtime/provider.js +1 -2
  94. package/dist/runtime/proxy-errors.d.ts +1 -1
  95. package/dist/runtime/proxy-errors.js +9 -7
  96. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  97. package/dist/runtime/proxy-nodemaven.js +146 -0
  98. package/dist/runtime/proxy-retry-policy.d.ts +2 -2
  99. package/dist/runtime/proxy-retry-policy.js +2 -2
  100. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  101. package/dist/runtime/proxy-telemetry.js +55 -52
  102. package/dist/runtime/redirects.d.ts +29 -0
  103. package/dist/runtime/redirects.js +36 -0
  104. package/dist/runtime/redis.d.ts +1 -1
  105. package/dist/runtime/redis.js +2 -4
  106. package/dist/runtime/request-options.d.ts +68 -1
  107. package/dist/runtime/request-options.js +548 -0
  108. package/dist/runtime/secrets.d.ts +27 -0
  109. package/dist/runtime/secrets.js +51 -0
  110. package/dist/runtime/state.d.ts +2 -2
  111. package/dist/runtime/state.js +238 -26
  112. package/dist/runtime/stealth.d.ts +5 -3
  113. package/dist/runtime/stealth.js +423 -143
  114. package/dist/runtime/stt.d.ts +1 -1
  115. package/dist/runtime/stt.js +11 -15
  116. package/dist/runtime/trace.d.ts +2 -2
  117. package/dist/runtime/trace.js +2 -4
  118. package/dist/runtime/waterfall.d.ts +1 -1
  119. package/dist/schema.d.ts +1 -1
  120. package/dist/schema.js +7 -15
  121. package/dist/serve.d.ts +1 -1
  122. package/dist/serve.js +1 -1
  123. package/dist/server/index.d.ts +7 -7
  124. package/dist/server/index.js +6 -6
  125. package/dist/server/self-test-input-tokens.d.ts +2 -1
  126. package/dist/server/self-test-input-tokens.js +18 -14
  127. package/dist/server/self-test-redaction.d.ts +1 -1
  128. package/dist/server/self-test-redaction.js +1 -1
  129. package/dist/server/self-test.d.ts +104 -3
  130. package/dist/server/self-test.js +673 -115
  131. package/dist/server/serve.d.ts +116 -4
  132. package/dist/server/serve.js +799 -128
  133. package/dist/server/types.d.ts +34 -9
  134. package/dist/server/types.js +8 -1
  135. package/dist/stateful/errors.d.ts +14 -0
  136. package/dist/stateful/errors.js +14 -0
  137. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  138. package/dist/stateful/http-provider-event-emitter.js +237 -0
  139. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  140. package/dist/stateful/http-session-owner-registry.js +210 -0
  141. package/dist/stateful/index.d.ts +18 -0
  142. package/dist/stateful/index.js +18 -0
  143. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  144. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  145. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  146. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  147. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  148. package/dist/stateful/provider-event-pipeline.js +1 -0
  149. package/dist/stateful/provider-events.d.ts +101 -0
  150. package/dist/stateful/provider-events.js +289 -0
  151. package/dist/stateful/session-key.d.ts +15 -0
  152. package/dist/stateful/session-key.js +86 -0
  153. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  154. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  155. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  156. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  157. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  158. package/dist/stateful/stateful-provider-adapter.js +287 -0
  159. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  160. package/dist/stateful/stateful-provider-observability.js +161 -0
  161. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  162. package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
  163. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  164. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  165. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  166. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  167. package/dist/stateful/stateful-provider-session-routing.d.ts +71 -0
  168. package/dist/stateful/stateful-provider-session-routing.js +353 -0
  169. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  170. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  171. package/dist/stateful-signing.d.ts +18 -0
  172. package/dist/stateful-signing.js +27 -0
  173. package/dist/stealth/profiles.d.ts +1 -1
  174. package/dist/stealth/profiles.js +5 -14
  175. package/dist/stream-evidence.d.ts +74 -0
  176. package/dist/stream-evidence.js +785 -0
  177. package/dist/stream.d.ts +1 -1
  178. package/dist/testing/index.d.ts +2 -2
  179. package/dist/testing/index.js +2 -2
  180. package/dist/testing/run.d.ts +32 -2
  181. package/dist/testing/run.js +478 -28
  182. package/dist/types.d.ts +342 -14
  183. package/dist/types.js +1 -0
  184. package/dist/user-input.d.ts +30 -0
  185. package/dist/user-input.js +66 -0
  186. package/package.json +16 -5
  187. package/src/auth-turn/index.ts +1 -1
  188. package/src/auth.ts +28 -86
  189. package/src/ceremonies/index.ts +103 -78
  190. package/src/cli/commands.ts +10 -0
  191. package/src/cli/create.ts +42 -35
  192. package/src/cli/prompt-assets.ts +865 -0
  193. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  194. package/src/config/loader.ts +652 -204
  195. package/src/contract-serialization.ts +5 -11
  196. package/src/contract-types.ts +2 -2
  197. package/src/contract.ts +12 -28
  198. package/src/define.ts +499 -498
  199. package/src/dev.ts +4 -9
  200. package/src/error-resolution.ts +91 -0
  201. package/src/errors.ts +110 -5
  202. package/src/fixture-sanitization.ts +247 -0
  203. package/src/i18n/catalog.ts +10 -32
  204. package/src/i18n/index.ts +2 -2
  205. package/src/i18n/keys.ts +5 -11
  206. package/src/index.ts +112 -42
  207. package/src/lint.ts +88 -152
  208. package/src/native-address.ts +340 -0
  209. package/src/native-egress-policy.ts +358 -0
  210. package/src/observability.ts +51 -1
  211. package/src/provider.ts +66 -11
  212. package/src/public-schema-field-lint.ts +7 -33
  213. package/src/recipes/gov-api.ts +2 -5
  214. package/src/runtime/auth-flow.ts +7 -7
  215. package/src/runtime/browser.ts +64 -187
  216. package/src/runtime/cache.ts +20 -67
  217. package/src/runtime/choice.ts +79 -132
  218. package/src/runtime/credential.ts +2 -2
  219. package/src/runtime/env.ts +1 -1
  220. package/src/runtime/executor.ts +43 -20
  221. package/src/runtime/http.ts +494 -57
  222. package/src/runtime/insights.ts +15 -53
  223. package/src/runtime/instrumentation.ts +495 -66
  224. package/src/runtime/keyring.ts +7 -19
  225. package/src/runtime/namespace.ts +2 -7
  226. package/src/runtime/native-network.ts +1686 -0
  227. package/src/runtime/otlp.ts +12 -23
  228. package/src/runtime/perf.ts +1 -1
  229. package/src/runtime/provider.ts +4 -9
  230. package/src/runtime/proxy-errors.ts +29 -42
  231. package/src/runtime/proxy-nodemaven.ts +221 -0
  232. package/src/runtime/proxy-retry-policy.ts +3 -3
  233. package/src/runtime/proxy-telemetry.ts +79 -77
  234. package/src/runtime/redirects.ts +66 -0
  235. package/src/runtime/redis.ts +4 -12
  236. package/src/runtime/request-options.ts +679 -9
  237. package/src/runtime/secrets.ts +64 -0
  238. package/src/runtime/state.ts +353 -133
  239. package/src/runtime/stealth.ts +505 -154
  240. package/src/runtime/stt.ts +38 -94
  241. package/src/runtime/trace.ts +14 -44
  242. package/src/runtime/waterfall.ts +5 -18
  243. package/src/schema.ts +23 -84
  244. package/src/serve.ts +1 -1
  245. package/src/server/index.ts +29 -7
  246. package/src/server/self-test-input-tokens.ts +29 -14
  247. package/src/server/self-test-redaction.ts +2 -2
  248. package/src/server/self-test.ts +857 -132
  249. package/src/server/serve.ts +1151 -328
  250. package/src/server/types.ts +12 -13
  251. package/src/stateful/README.md +146 -0
  252. package/src/stateful/errors.ts +23 -0
  253. package/src/stateful/http-provider-event-emitter.ts +314 -0
  254. package/src/stateful/http-session-owner-registry.ts +306 -0
  255. package/src/stateful/index.ts +18 -0
  256. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  257. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  258. package/src/stateful/provider-event-pipeline.ts +61 -0
  259. package/src/stateful/provider-events.ts +462 -0
  260. package/src/stateful/session-key.ts +111 -0
  261. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  262. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  263. package/src/stateful/stateful-provider-adapter.ts +562 -0
  264. package/src/stateful/stateful-provider-observability.ts +261 -0
  265. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  266. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  267. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  268. package/src/stateful/stateful-provider-session-routing.ts +555 -0
  269. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  270. package/src/stateful-signing.ts +46 -0
  271. package/src/stealth/profiles.ts +10 -26
  272. package/src/stream-evidence.ts +988 -0
  273. package/src/stream.ts +8 -19
  274. package/src/testing/index.ts +10 -2
  275. package/src/testing/run.ts +653 -74
  276. package/src/types.ts +408 -28
  277. package/src/user-input.ts +118 -0
  278. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  279. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  280. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  281. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  282. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  283. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  284. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  285. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  286. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  287. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  288. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  289. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  290. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  291. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -1,20 +1,29 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Impit } from "impit";
3
- import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, ProxyResolutionError, resolveProxyConfigAsync, SMARTPROXY_MAX_POOL_SIZE, } from "../config/loader";
4
- import { SDKError, TransportError } from "../errors";
5
- import { getStealthProfile } from "../stealth/profiles";
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
- import { appendQueryParams } from "./request-options";
3
+ import { Cookie, CookieJar as ToughCookieJar } from "tough-cookie";
4
+ import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, ProxyResolutionError, policyResolvesRegistryVendorChain, resolvePolicyProxyPoolSpan, resolvePolicyTransportAttemptCap, resolveProxyConfigAsync, vendorFromResolvedSource, } from "../config/loader.js";
5
+ import { SDKError, StealthCookieStoreVersionError, TransportError } from "../errors.js";
6
+ import { getStealthProfile } from "../stealth/profiles.js";
7
+ import { createProxyAuthIpDeniedError, createProxyEdgeAuthRejectedError, createProxyEdgeTlsRejectedError, createProxyPoolExhaustedError, createProxyPoolStaleError, isProxyAuthIpDeniedMessage, isProxyEdgeAuthRejectedMessage, isProxyEdgeTlsRejectedResponse, isProxyPoolRefreshableError, isProxyPoolStaleMessage, isProxyPoolStaleStatus, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_STALE_CODE, } from "./proxy-errors.js";
8
+ import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, normalizeProxyTransportRetryOptions, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy.js";
9
+ import { evaluateRedirectHop, isRedirectStatus, resolveRedirectUrl, } from "./redirects.js";
10
+ import { isSensitiveKey, redactSensitiveError, redactSensitiveRequestError, redactSensitiveText, redactUrlQueryParams, normalizeSensitiveParams, serializeRequestUrl, } from "./request-options.js";
9
11
  const DEFAULT_PROFILE = "chrome-146";
10
12
  const MISSING_PROXY_WARNING = "[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
11
- const MAX_POLICY_PROXY_RETRY_ATTEMPTS = SMARTPROXY_MAX_POOL_SIZE;
12
13
  const MAX_POLICY_PROXY_POOL_REFRESHES = 1;
13
14
  const PROXY_CONNECT_FAILURE_CODE = "proxy_connect_failed";
14
15
  const PROXY_CONNECT_FAILURE_BODY_PATTERN = /\bproxy\b.*\b(non[\s-]?200|connect|tunnel)|\bconnect\b.*\bproxy\b|\btunnel\b/i;
15
16
  const PROXY_AUTH_DIAGNOSTIC_URL = "http://example.com/";
16
17
  const PROXY_AUTH_DIAGNOSTIC_TIMEOUT_MS = 5_000;
17
18
  const STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES = [PROXY_CONNECT_FAILURE_CODE];
19
+ function sensitiveQueryParamNames(url) {
20
+ const queryStart = url.indexOf("?");
21
+ if (queryStart === -1)
22
+ return [];
23
+ const fragmentStart = url.indexOf("#", queryStart);
24
+ const query = url.slice(queryStart + 1, fragmentStart === -1 ? undefined : fragmentStart);
25
+ return [...new URLSearchParams(query).keys()].filter(isSensitiveKey);
26
+ }
18
27
  const REMOVED_CHROME_PROFILE_NAMES = new Set([
19
28
  "chrome-120",
20
29
  "chrome-124",
@@ -50,69 +59,128 @@ const FIREFOX_IMPIT_BY_MAJOR = {
50
59
  function isRecord(value) {
51
60
  return typeof value === "object" && value !== null;
52
61
  }
62
+ const LEGACY_COOKIE_ORIGIN = "https://legacy-cookie.invalid/";
53
63
  class CookieJarImpl {
54
64
  cookies;
55
- constructor(cookieStrings) {
56
- this.cookies = {};
65
+ defaultUrl;
66
+ constructor(cookieStrings, defaultUrl = LEGACY_COOKIE_ORIGIN) {
67
+ this.cookies = new ToughCookieJar(undefined, {
68
+ allowSecureOnLocal: false,
69
+ rejectPublicSuffixes: true,
70
+ });
71
+ this.defaultUrl = this.normalizeUrl(defaultUrl) ?? LEGACY_COOKIE_ORIGIN;
57
72
  this.setFromCookieStrings(cookieStrings);
58
73
  }
59
- setFromCookieStrings(cookieStrings) {
74
+ /**
75
+ * URL-less legacy operations are scoped to this jar's default URL. Session
76
+ * jars use the client's base URL and response jars use the response URL. A
77
+ * flat restore has no attributes to recover, so it creates host-only Path=/
78
+ * cookies for that default URL instead of making them visible to every host.
79
+ */
80
+ setFromCookieStrings(cookieStrings, url = this.defaultUrl) {
81
+ const cookieUrl = this.normalizeUrl(url);
82
+ if (!cookieUrl)
83
+ return;
60
84
  for (const cookieString of cookieStrings) {
61
- const [nameValue] = cookieString.split(";");
62
- if (!nameValue) {
63
- continue;
64
- }
65
- const separatorIndex = nameValue.indexOf("=");
66
- if (separatorIndex === -1) {
67
- continue;
68
- }
69
- const name = nameValue.slice(0, separatorIndex).trim();
70
- const value = nameValue.slice(separatorIndex + 1).trim();
71
- if (name)
72
- this.cookies[name] = value;
85
+ this.cookies.setCookieSync(cookieString, cookieUrl, { ignoreError: true });
73
86
  }
74
87
  }
75
- get(name) {
76
- return this.cookies[name];
88
+ get(name, url) {
89
+ return this.getAll(url)[name];
77
90
  }
78
- getAll() {
79
- return { ...this.cookies };
91
+ getAll(url) {
92
+ return Object.fromEntries(this.getUniqueCookies(url ?? this.defaultUrl).map((cookie) => [cookie.key, cookie.value]));
80
93
  }
81
- has(name) {
82
- return Object.hasOwn(this.cookies, name);
94
+ has(name, url) {
95
+ return Object.hasOwn(this.getAll(url), name);
83
96
  }
84
- toString() {
85
- return Object.entries(this.cookies)
86
- .map(([name, value]) => `${name}=${value}`)
97
+ toString(url) {
98
+ return this.getUniqueCookies(url ?? this.defaultUrl)
99
+ .map((cookie) => cookie.cookieString())
87
100
  .join("; ");
88
101
  }
89
- toHeader() {
90
- return this.toString();
102
+ toHeader(url) {
103
+ return this.toString(url);
91
104
  }
92
105
  snapshot() {
93
- return this.getAll();
106
+ // This compatibility view deliberately enumerates the serialized store,
107
+ // not getAll(defaultUrl): persistence must include sibling hosts and paths.
108
+ // Duplicate names still collapse because a flat map cannot represent them.
109
+ const entries = [];
110
+ for (const cookie of this.serialize().jar.cookies) {
111
+ if (typeof cookie.key === "string" && typeof cookie.value === "string" && cookie.key) {
112
+ entries.push([cookie.key, cookie.value]);
113
+ }
114
+ }
115
+ return Object.fromEntries(entries);
94
116
  }
95
117
  restore(cookies) {
96
118
  this.clear();
97
119
  for (const [name, value] of Object.entries(cookies)) {
98
- if (name)
99
- this.cookies[name] = value;
120
+ if (!name)
121
+ continue;
122
+ this.cookies.setCookieSync(new Cookie({ key: name, path: "/", value }), this.defaultUrl, {
123
+ ignoreError: true,
124
+ });
100
125
  }
101
126
  }
102
- clear() {
103
- for (const name of Object.keys(this.cookies)) {
104
- delete this.cookies[name];
127
+ serialize() {
128
+ const jar = this.cookies.serializeSync();
129
+ if (!jar) {
130
+ throw new SDKError("Stealth cookie store could not be serialized", {
131
+ code: "stealth_cookie_store_serialize_failed",
132
+ });
133
+ }
134
+ return { version: 1, jar };
135
+ }
136
+ deserialize(state) {
137
+ const version = isRecord(state) ? state.version : undefined;
138
+ if (version !== 1) {
139
+ throw new StealthCookieStoreVersionError(version);
105
140
  }
141
+ // Deserialize into a new jar first so invalid state cannot partially clear
142
+ // or replace a live session. tough-cookie restores the cookie attributes and
143
+ // matching semantics represented in its own serialized format.
144
+ const restored = ToughCookieJar.deserializeSync(state.jar);
145
+ // tough-cookie 6 does not include this option in serializeSync(). Preserve
146
+ // the SDK's stricter setting across restoration.
147
+ Reflect.set(restored, "allowSecureOnLocal", false);
148
+ this.cookies = restored;
106
149
  }
107
- find(predicate) {
108
- for (const [name, value] of Object.entries(this.cookies)) {
109
- const cookie = `${name}=${value}`;
110
- if (predicate(cookie)) {
111
- return cookie;
150
+ clear() {
151
+ this.cookies.removeAllCookiesSync();
152
+ }
153
+ find(predicate, url) {
154
+ for (const cookie of this.getUniqueCookies(url ?? this.defaultUrl)) {
155
+ const cookieString = cookie.cookieString();
156
+ if (predicate(cookieString)) {
157
+ return cookieString;
112
158
  }
113
159
  }
114
160
  return undefined;
115
161
  }
162
+ normalizeUrl(url) {
163
+ try {
164
+ return new URL(url).toString();
165
+ }
166
+ catch {
167
+ return undefined;
168
+ }
169
+ }
170
+ getUniqueCookies(url) {
171
+ const cookieUrl = this.normalizeUrl(url);
172
+ if (!cookieUrl)
173
+ return [];
174
+ // tough-cookie returns longer (more-specific) paths first. Keeping the
175
+ // first cookie for each name prevents ambiguous duplicate-name headers.
176
+ const names = new Set();
177
+ return this.cookies.getCookiesSync(cookieUrl).filter((cookie) => {
178
+ if (names.has(cookie.key))
179
+ return false;
180
+ names.add(cookie.key);
181
+ return true;
182
+ });
183
+ }
116
184
  }
117
185
  function closestImpitBrowser(major, candidates) {
118
186
  let closestMajor;
@@ -176,13 +244,13 @@ function hasOwn(object, key) {
176
244
  }
177
245
  function toImpitCookieJar(cookieJar) {
178
246
  return {
179
- setCookie(cookie, _url, cb) {
180
- cookieJar.setFromCookieStrings([cookie]);
247
+ setCookie(cookie, url, cb) {
248
+ cookieJar.setFromCookieStrings([cookie], url);
181
249
  if (typeof cb === "function")
182
250
  cb();
183
251
  },
184
- getCookieString(_url) {
185
- return cookieJar.toString();
252
+ getCookieString(url) {
253
+ return cookieJar.toHeader(url);
186
254
  },
187
255
  };
188
256
  }
@@ -234,10 +302,12 @@ function splitCombinedSetCookieHeader(headerValue) {
234
302
  cookieStrings.push(finalCookie);
235
303
  return cookieStrings;
236
304
  }
237
- export async function normalizeResponse(response, requestUrl) {
305
+ export async function normalizeResponse(response, requestUrl, maxBodyBytes) {
238
306
  const headers = Object.fromEntries(response.headers.entries());
239
- const cookies = new CookieJarImpl(setCookieHeadersFromResponse(response.headers));
240
- const bodyBytes = await response.arrayBuffer();
307
+ const cookies = new CookieJarImpl(setCookieHeadersFromResponse(response.headers), response.url ?? requestUrl);
308
+ const bodyBytes = maxBodyBytes === undefined
309
+ ? await response.arrayBuffer()
310
+ : await readResponseBodyWithLimit(response, maxBodyBytes);
241
311
  const body = new TextDecoder().decode(bodyBytes);
242
312
  return {
243
313
  status: response.status,
@@ -263,6 +333,75 @@ export async function normalizeResponse(response, requestUrl) {
263
333
  },
264
334
  };
265
335
  }
336
+ function responseTooLargeError(maxBodyBytes, observedBytes) {
337
+ return new TransportError(`Response body exceeded maxBodyBytes limit of ${maxBodyBytes} bytes (observed ${observedBytes} bytes)`, {
338
+ code: "response_too_large",
339
+ category: "upstream_http",
340
+ retryable: false,
341
+ status: 0,
342
+ });
343
+ }
344
+ function declaredContentLength(headers) {
345
+ const contentLength = headers.get("content-length")?.trim();
346
+ if (!contentLength || !/^\d+$/.test(contentLength))
347
+ return undefined;
348
+ const parsed = Number(contentLength);
349
+ return Number.isFinite(parsed) ? parsed : undefined;
350
+ }
351
+ function abortTransportResponse(response) {
352
+ if (!response.abort)
353
+ return false;
354
+ try {
355
+ response.abort();
356
+ }
357
+ catch {
358
+ // The size error remains the primary failure if impit has already closed the response.
359
+ }
360
+ return true;
361
+ }
362
+ async function readResponseBodyWithLimit(response, maxBodyBytes) {
363
+ const contentLength = declaredContentLength(response.headers);
364
+ if (contentLength !== undefined && contentLength > maxBodyBytes) {
365
+ if (!abortTransportResponse(response)) {
366
+ await response.body?.cancel().catch(() => undefined);
367
+ }
368
+ throw responseTooLargeError(maxBodyBytes, contentLength);
369
+ }
370
+ if (!response.body) {
371
+ throw new TransportError("Response body stream is unavailable", {
372
+ code: "transport_stream_unavailable",
373
+ category: "upstream_http",
374
+ status: 0,
375
+ });
376
+ }
377
+ const reader = response.body.getReader();
378
+ const chunks = [];
379
+ let receivedBytes = 0;
380
+ try {
381
+ while (true) {
382
+ const { done, value } = await reader.read();
383
+ if (done)
384
+ break;
385
+ receivedBytes += value.byteLength;
386
+ if (receivedBytes > maxBodyBytes) {
387
+ await reader.cancel().catch(() => undefined);
388
+ abortTransportResponse(response);
389
+ throw responseTooLargeError(maxBodyBytes, receivedBytes);
390
+ }
391
+ chunks.push(value);
392
+ }
393
+ }
394
+ finally {
395
+ reader.releaseLock();
396
+ }
397
+ const bodyBytes = new Uint8Array(receivedBytes);
398
+ let offset = 0;
399
+ for (const chunk of chunks) {
400
+ bodyBytes.set(chunk, offset);
401
+ offset += chunk.byteLength;
402
+ }
403
+ return bodyBytes.buffer;
404
+ }
266
405
  function normalizeBody(body) {
267
406
  if (body === undefined) {
268
407
  return "";
@@ -420,16 +559,6 @@ function normalizeMethod(method) {
420
559
  throw new SDKError(`Unsupported stealth method: ${method}`);
421
560
  }
422
561
  }
423
- function isRedirectStatus(status) {
424
- return [301, 302, 303, 307, 308].includes(status);
425
- }
426
- function nextRedirectMethod(status, method) {
427
- if (status === 303 && method !== "HEAD")
428
- return "GET";
429
- if ((status === 301 || status === 302) && method === "POST")
430
- return "GET";
431
- return method;
432
- }
433
562
  function locationHeader(headers) {
434
563
  for (const [name, value] of Object.entries(headers)) {
435
564
  if (name.toLowerCase() === "location")
@@ -442,7 +571,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
442
571
  let closed = false;
443
572
  let hasWarnedMissingProxy = false;
444
573
  const warn = clientOptions.warn ?? console.warn;
445
- const cookieJar = new CookieJarImpl([]);
574
+ const cookieJar = new CookieJarImpl([], baseUrl);
446
575
  const impitCookieJar = toImpitCookieJar(cookieJar);
447
576
  function getClient(profileName, proxyUrl, ignoreTlsErrors) {
448
577
  if (closed) {
@@ -463,7 +592,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
463
592
  }
464
593
  return client;
465
594
  }
466
- async function resolveRequestProxy(options, proxyAttempt) {
595
+ async function resolveRequestProxy(options, proxyAttempt, refreshEpoch) {
467
596
  const resolvedProxy = await resolveProxyConfigAsync({
468
597
  proxy: options?.proxy ?? clientOptions.proxy,
469
598
  upstream: clientOptions.upstream,
@@ -474,6 +603,10 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
474
603
  proxyAttemptOffset: options?.proxyAttemptOffset,
475
604
  retryAttemptOffset: proxyAttempt,
476
605
  }),
606
+ // The impit stealth transport tunnels both HTTP CONNECT and SOCKS5,
607
+ // preserving the client TLS fingerprint end-to-end.
608
+ transportProtocols: ["http", "socks5"],
609
+ ...(refreshEpoch === undefined ? {} : { proxyRefreshEpoch: refreshEpoch }),
477
610
  telemetry: clientOptions.telemetry,
478
611
  });
479
612
  if (resolvedProxy.shouldWarn && !hasWarnedMissingProxy) {
@@ -484,34 +617,59 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
484
617
  url: resolvedProxy.url,
485
618
  poolIndex: proxyPoolIndexFromDiagnostics(resolvedProxy.diagnostics),
486
619
  proxyHash: proxyEndpointHash(resolvedProxy.url),
620
+ vendor: vendorFromResolvedSource(resolvedProxy.source),
487
621
  };
488
622
  }
489
623
  const session = {
490
624
  async fetch(url, options = {}) {
491
- const method = normalizeMethod(options.method ?? "GET");
492
- const hasExplicitRetryPolicy = options.retry !== undefined;
493
- const stealthRetryOptions = normalizeProxyTransportRetryOptions(options.retry, {
494
- extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
495
- label: "Stealth",
496
- }) ??
497
- (hasExplicitRetryPolicy
498
- ? undefined
499
- : createDefaultProxyTransportRetryOptions({
625
+ const { hasExplicitRetryPolicy, method, stealthRetryOptions } = (() => {
626
+ try {
627
+ const method = normalizeMethod(options.method ?? "GET");
628
+ const hasExplicitRetryPolicy = options.retry !== undefined;
629
+ const stealthRetryOptions = normalizeProxyTransportRetryOptions(options.retry, {
500
630
  extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
501
631
  label: "Stealth",
502
- }));
503
- if (stealthRetryOptions) {
504
- validateUnsafeProxyTransportRetryMethods(stealthRetryOptions, "Stealth");
505
- }
632
+ }) ??
633
+ (hasExplicitRetryPolicy
634
+ ? undefined
635
+ : createDefaultProxyTransportRetryOptions({
636
+ extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
637
+ label: "Stealth",
638
+ }));
639
+ if (stealthRetryOptions) {
640
+ validateUnsafeProxyTransportRetryMethods(stealthRetryOptions, "Stealth");
641
+ }
642
+ return { hasExplicitRetryPolicy, method, stealthRetryOptions };
643
+ }
644
+ catch (error) {
645
+ throw redactSensitiveRequestError(error, url, options.sensitiveParams);
646
+ }
647
+ })();
506
648
  const hasPolicyProxy = isPolicyManagedProxy(clientOptions);
507
649
  const usesPolicyAllocator = hasPolicyProxy && !options.proxy && !clientOptions.proxy;
508
650
  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 ??
651
+ // Span the whole vendor chain: successive attempts rotate one vendor's
652
+ // pool, then fail over to the next vendor via the flat attempt index.
653
+ const policyProxy = clientOptions.proxyPolicy ??
510
654
  (typeof clientOptions.upstream?.proxy === "object"
511
- ? clientOptions.upstream.proxy.session?.poolSize
512
- : undefined) ??
513
- DEFAULT_SMARTPROXY_POOL_SIZE));
514
- const maxAttempts = usesPolicyAllocator ? policyProxyAttemptCap : retryAttemptCap;
655
+ ? clientOptions.upstream.proxy
656
+ : undefined);
657
+ // The pool span is already bounded by each vendor's max pool size
658
+ // (smartproxy ≤20, nodemaven ≤50), so the configured span never exceeds
659
+ // the chain's true maximum — a large NodeMaven pool stays fully
660
+ // reachable rather than being truncated at an arbitrary ceiling.
661
+ const policyProxyAttemptCap = Math.max(1, policyProxy ? resolvePolicyProxyPoolSpan(policyProxy) : DEFAULT_SMARTPROXY_POOL_SIZE);
662
+ // A registry vendor chain (smartproxy/nodemaven) is the only policy whose
663
+ // successive attempts resolve a *different* endpoint, so it is the only one
664
+ // that may widen the attempt cap to the pool span, de-duplicate endpoints,
665
+ // and drive allocator stale-pool refresh. A static custom/decodo policy
666
+ // resolves the same URL every attempt: widening/refreshing it would resend
667
+ // the request dozens of times (up to maxAttempts × refreshes) and bypass
668
+ // retry:false and unsafe-method controls. Static policies therefore follow
669
+ // the ordinary transport-retry budget instead.
670
+ const rotatesRegistryChain = usesPolicyAllocator && policyResolvesRegistryVendorChain(policyProxy);
671
+ const maxAttempts = rotatesRegistryChain ? policyProxyAttemptCap : retryAttemptCap;
672
+ const dedupeAllocatorEndpoints = rotatesRegistryChain;
515
673
  let lastError;
516
674
  for (let refreshAttempt = 0; refreshAttempt <= MAX_POLICY_PROXY_POOL_REFRESHES; refreshAttempt += 1) {
517
675
  let stalePoolError;
@@ -520,6 +678,11 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
520
678
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
521
679
  let proxy;
522
680
  let attemptProxy;
681
+ // Reuse the exact serialization used by this outbound attempt in its catch path.
682
+ let serializedUrl;
683
+ let fallbackSensitiveValues = [];
684
+ let fallbackRequestUrl;
685
+ let fallbackRedactedUrl;
523
686
  const attemptStartedAt = Date.now();
524
687
  let attemptRecorded = false;
525
688
  const recordProxyAttempt = (outcome, errorCode, status) => {
@@ -527,7 +690,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
527
690
  return;
528
691
  attemptRecorded = true;
529
692
  clientOptions.telemetry?.recordProxyAttempt?.({
530
- provider: "smartproxy",
693
+ provider: attemptProxy?.vendor ?? "smartproxy",
531
694
  attempt: attempt + 1,
532
695
  ...(attemptProxy?.poolIndex === undefined
533
696
  ? {}
@@ -540,22 +703,38 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
540
703
  });
541
704
  };
542
705
  try {
706
+ const sensitiveParams = normalizeSensitiveParams(options.sensitiveParams);
707
+ const structural = redactUrlQueryParams(url, Object.keys(sensitiveParams ?? {}));
708
+ fallbackSensitiveValues = [
709
+ ...new Set([
710
+ ...Object.values(sensitiveParams ?? {}).map(String),
711
+ ...structural.sensitiveValues,
712
+ ]),
713
+ ].filter((value) => value !== "");
714
+ fallbackRequestUrl = url;
715
+ fallbackRedactedUrl = structural.redactedUrl;
543
716
  assertNoUnsupportedFingerprintOverrides(options);
544
- attemptProxy = await resolveRequestProxy(options, attempt);
717
+ attemptProxy = await resolveRequestProxy(options, attempt, refreshAttempt);
545
718
  proxy = attemptProxy.url;
546
- if (proxy && usesPolicyAllocator) {
719
+ if (proxy && dedupeAllocatorEndpoints) {
720
+ // An under-filled allocation repeats endpoints (via the modulo
721
+ // pool mapping) before the flat offset crosses into the next
722
+ // vendor. Skip an already-tried endpoint and advance the offset
723
+ // rather than breaking — breaking here would strand the request on
724
+ // the primary vendor and never reach the fallback leg.
547
725
  if (attemptedProxies.has(proxy)) {
548
- break;
726
+ continue;
549
727
  }
550
728
  attemptedProxies.add(proxy);
551
729
  }
552
730
  const ignoreTlsErrors = Boolean(options.stealth?.insecureSkipVerify ??
553
731
  (!hasPolicyProxy && proxy && clientOptions.proxyStealth?.insecureSkipVerify));
554
732
  const profileName = options.profile ?? defaultProfile;
555
- const requestUrl = appendQueryParams(resolveUrl(baseUrl, url), options.params);
733
+ serializedUrl = serializeRequestUrl(resolveUrl(baseUrl, url), options.params, sensitiveParams);
734
+ const { requestUrl } = serializedUrl;
556
735
  const headers = { ...(options.headers ?? {}) };
557
736
  if (!hasHeader(headers, "Cookie")) {
558
- const cookieHeader = cookieJar.toString();
737
+ const cookieHeader = cookieJar.toHeader(requestUrl);
559
738
  if (cookieHeader)
560
739
  headers.Cookie = cookieHeader;
561
740
  }
@@ -569,8 +748,8 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
569
748
  requestInit.body = normalizeBody(options.body);
570
749
  }
571
750
  const response = await getClient(profileName, proxy, ignoreTlsErrors).fetch(requestUrl, requestInit);
572
- const normalized = await normalizeResponse(response, requestUrl);
573
- cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers));
751
+ const normalized = await normalizeResponse(response, requestUrl, options.maxBodyBytes);
752
+ cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers), response.url ?? requestUrl);
574
753
  if (proxy && isProxyConnectFailureResponse(response, normalized.body)) {
575
754
  throw createProxyConnectFailureError(normalized.body);
576
755
  }
@@ -602,12 +781,23 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
602
781
  return normalized;
603
782
  }
604
783
  catch (error) {
605
- const normalizedError = normalizeStealthTransportError(error);
784
+ const sensitiveValues = serializedUrl?.sensitiveValues ?? fallbackSensitiveValues;
785
+ let normalizedError;
786
+ try {
787
+ normalizedError = normalizeStealthTransportError(error);
788
+ }
789
+ catch (normalizationError) {
790
+ throw redactSensitiveError(normalizationError, sensitiveValues, serializedUrl?.requestUrl ?? fallbackRequestUrl, serializedUrl?.redactedUrl ?? fallbackRedactedUrl);
791
+ }
792
+ const retryErrorCode = proxyAttemptErrorCode(normalizedError);
793
+ const refreshableProxyError = isProxyPoolRefreshableError(normalizedError);
794
+ const runProxyAuthDiagnostic = shouldRunProxyAuthDiagnostic(normalizedError);
795
+ normalizedError = redactSensitiveError(normalizedError, sensitiveValues, serializedUrl?.requestUrl ?? fallbackRequestUrl, serializedUrl?.redactedUrl ?? fallbackRedactedUrl);
606
796
  recordProxyAttempt("error", proxyAttemptErrorCode(normalizedError), proxyAttemptStatus(normalizedError));
607
797
  lastError = normalizedError;
608
- if (proxy && usesPolicyAllocator && isProxyPoolRefreshableError(normalizedError)) {
798
+ if (proxy && rotatesRegistryChain && refreshableProxyError) {
609
799
  stalePoolError = normalizedError;
610
- if (shouldRunProxyAuthDiagnostic(normalizedError)) {
800
+ if (runProxyAuthDiagnostic) {
611
801
  stalePoolDiagnosticProxy = proxy;
612
802
  }
613
803
  if (attempt + 1 < maxAttempts) {
@@ -615,12 +805,29 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
615
805
  }
616
806
  break;
617
807
  }
618
- if (attempt + 1 <
619
- (stealthRetryOptions
620
- ? Math.min(maxAttempts, stealthRetryOptions.attempts)
621
- : maxAttempts) &&
808
+ // Cap the number of transport retries. For a policy-allocator chain,
809
+ // every attempt resolves a *different* endpoint/vendor (poolIndex
810
+ // rotates across the concatenated vendor pool spans), so a transport
811
+ // failure is a signal to advance to the next endpoint — potentially
812
+ // crossing into the fallback vendor — not to retry the same endpoint.
813
+ // Truncating that rotation at the per-endpoint retry budget would
814
+ // strand the request on the primary vendor and never reach the
815
+ // fallback, since the crossover only happens once the flat attempt
816
+ // index exceeds the primary vendor's pool size (~10-20).
817
+ // resolvePolicyTransportAttemptCap widens to the full chain span only
818
+ // for implicit, safe-method allocator requests; explicit retry
819
+ // policies (their documented `attempts` ceiling), unsafe methods, and
820
+ // static/non-registry vendors keep the per-endpoint retry budget.
821
+ const transportRetryCap = resolvePolicyTransportAttemptCap({
822
+ policy: policyProxy,
823
+ usesPolicyAllocator,
824
+ retryAttempts: stealthRetryOptions?.attempts ?? 1,
825
+ explicitRetry: hasExplicitRetryPolicy,
826
+ method,
827
+ });
828
+ if (attempt + 1 < transportRetryCap &&
622
829
  shouldRetryProxyTransportAttempt({
623
- error: normalizedError,
830
+ error: { code: retryErrorCode },
624
831
  explicitRetry: hasExplicitRetryPolicy,
625
832
  method,
626
833
  options: stealthRetryOptions,
@@ -634,7 +841,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
634
841
  throw normalizedError;
635
842
  }
636
843
  }
637
- if (usesPolicyAllocator &&
844
+ if (rotatesRegistryChain &&
638
845
  stalePoolError &&
639
846
  refreshAttempt < MAX_POLICY_PROXY_POOL_REFRESHES) {
640
847
  await invalidateProxyResolutionCacheAsync({
@@ -670,81 +877,153 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
670
877
  const maxHops = options.maxHops === undefined || !Number.isFinite(options.maxHops)
671
878
  ? 10
672
879
  : Math.max(0, Math.floor(options.maxHops));
880
+ const { url: _url, maxHops: _maxHops, stopWhen, params, sensitiveParams, ...fetchOptions } = options;
673
881
  const hops = [];
674
- let currentUrl = resolveUrl(baseUrl, options.url);
675
882
  let method = normalizeMethod(options.method ?? "GET");
676
883
  let body = options.body;
677
884
  let response;
678
885
  const visitedRequests = new Set();
679
- const { url: _url, maxHops: _maxHops, stopWhen, params, ...fetchOptions } = options;
886
+ const initialParams = params
887
+ ? Object.fromEntries(Object.entries(params).map(([key, value]) => [
888
+ key,
889
+ Array.isArray(value) ? [...value] : value,
890
+ ]))
891
+ : undefined;
892
+ const normalizedSensitiveParams = normalizeSensitiveParams(sensitiveParams);
893
+ const initialSensitiveParams = normalizedSensitiveParams
894
+ ? { ...normalizedSensitiveParams }
895
+ : undefined;
896
+ const sensitiveParamNames = initialSensitiveParams
897
+ ? Object.keys(initialSensitiveParams)
898
+ : [];
899
+ const callerStructural = redactUrlQueryParams(options.url, sensitiveParamNames);
900
+ const sensitiveValues = new Set([
901
+ ...Object.values(initialSensitiveParams ?? {}),
902
+ ...callerStructural.sensitiveValues,
903
+ ].filter((value) => value !== ""));
904
+ const redactRedirectUrl = (value) => {
905
+ const structural = redactUrlQueryParams(value, [
906
+ ...new Set([...sensitiveParamNames, ...sensitiveQueryParamNames(value)]),
907
+ ]);
908
+ for (const sensitiveValue of structural.sensitiveValues) {
909
+ sensitiveValues.add(sensitiveValue);
910
+ }
911
+ return redactSensitiveText(structural.redactedUrl, [...sensitiveValues]);
912
+ };
913
+ let currentUrl;
914
+ let initialUrl;
915
+ try {
916
+ currentUrl = resolveUrl(baseUrl, options.url);
917
+ redactRedirectUrl(currentUrl);
918
+ initialUrl = serializeRequestUrl(currentUrl, initialParams, initialSensitiveParams);
919
+ for (const value of initialUrl.sensitiveValues) {
920
+ if (value !== "")
921
+ sensitiveValues.add(value);
922
+ }
923
+ }
924
+ catch (error) {
925
+ throw redactSensitiveError(error, [...sensitiveValues], options.url, redactRedirectUrl(options.url));
926
+ }
680
927
  for (let hopIndex = 0; hopIndex <= maxHops; hopIndex += 1) {
681
- visitedRequests.add(`${method} ${currentUrl}`);
682
- response = await session.fetch(currentUrl, {
683
- ...fetchOptions,
684
- body,
685
- method,
686
- ...(hopIndex === 0 && params ? { params } : {}),
687
- redirect: "manual",
688
- throwOnHttpError: false,
689
- });
928
+ const outboundUrl = hopIndex === 0 ? initialUrl.requestUrl : serializeRequestUrl(currentUrl).requestUrl;
929
+ // Preserve params-only loop bookkeeping from before sensitiveParams:
930
+ // the first visited key is the caller's resolved URL, not its expanded query.
931
+ const visitedUrl = hopIndex === 0 && !initialSensitiveParams ? currentUrl : outboundUrl;
932
+ visitedRequests.add(`${method} ${visitedUrl}`);
933
+ try {
934
+ response = await session.fetch(currentUrl, {
935
+ ...fetchOptions,
936
+ body,
937
+ method,
938
+ ...(hopIndex === 0 && initialParams ? { params: initialParams } : {}),
939
+ ...(hopIndex === 0 && initialSensitiveParams
940
+ ? { sensitiveParams: initialSensitiveParams }
941
+ : {}),
942
+ redirect: "manual",
943
+ throwOnHttpError: false,
944
+ });
945
+ }
946
+ catch (error) {
947
+ throw redactSensitiveError(error, [...sensitiveValues], outboundUrl, redactRedirectUrl(outboundUrl));
948
+ }
949
+ // StealthResponse.url is programmatic metadata and remains raw. Only the
950
+ // redirect hop emitted below is a diagnostic surface.
951
+ const responseUrl = response.url ?? (hopIndex === 0 && initialSensitiveParams ? outboundUrl : currentUrl);
690
952
  if (!isRedirectStatus(response.status)) {
691
953
  return {
692
954
  final: response,
693
955
  hops,
694
956
  reason: "completed",
695
957
  cookies: cookieJar.snapshot(),
958
+ cookieStore: cookieJar.serialize(),
696
959
  };
697
960
  }
698
961
  const location = locationHeader(response.headers);
699
- const nextUrl = location
700
- ? new URL(location, response.url ?? currentUrl).toString()
701
- : undefined;
702
- const hop = {
703
- url: response.url ?? currentUrl,
962
+ const redactedResponseUrl = redactRedirectUrl(responseUrl);
963
+ const redactedLocation = location ? redactRedirectUrl(location) : undefined;
964
+ let nextUrl;
965
+ try {
966
+ nextUrl = resolveRedirectUrl(location, responseUrl);
967
+ }
968
+ catch (error) {
969
+ throw redactSensitiveError(error, [...sensitiveValues], location, redactedLocation);
970
+ }
971
+ const realHop = {
972
+ url: responseUrl,
704
973
  status: response.status,
705
974
  method,
706
975
  ...(location ? { location } : {}),
707
976
  ...(nextUrl ? { nextUrl } : {}),
708
977
  };
978
+ const hop = {
979
+ ...realHop,
980
+ url: redactedResponseUrl,
981
+ ...(redactedLocation ? { location: redactedLocation } : {}),
982
+ ...(nextUrl ? { nextUrl: redactRedirectUrl(nextUrl) } : {}),
983
+ };
709
984
  hops.push(hop);
710
- if (stopWhen && (await stopWhen(hop))) {
711
- return {
712
- final: response,
713
- hops,
714
- reason: "stopped",
715
- cookies: cookieJar.snapshot(),
716
- };
717
- }
718
- if (!nextUrl) {
719
- return {
720
- final: response,
721
- hops,
722
- reason: "missing_location",
723
- cookies: cookieJar.snapshot(),
724
- };
985
+ let shouldStop = false;
986
+ if (stopWhen) {
987
+ try {
988
+ shouldStop = await stopWhen(realHop);
989
+ }
990
+ catch (error) {
991
+ let sanitizedError = error;
992
+ for (const [rawUrl, safeUrl] of [
993
+ [responseUrl, redactedResponseUrl],
994
+ [location, redactedLocation],
995
+ [nextUrl, nextUrl ? redactRedirectUrl(nextUrl) : undefined],
996
+ ]) {
997
+ if (!rawUrl || !safeUrl)
998
+ continue;
999
+ sanitizedError = redactSensitiveError(sanitizedError, [...sensitiveValues], rawUrl, safeUrl);
1000
+ }
1001
+ throw sanitizedError;
1002
+ }
725
1003
  }
726
- if (hops.length > maxHops) {
1004
+ const decision = evaluateRedirectHop({
1005
+ status: response.status,
1006
+ method,
1007
+ nextUrl,
1008
+ shouldStop,
1009
+ redirectCount: hops.length,
1010
+ maxHops,
1011
+ visitedRequests,
1012
+ });
1013
+ if (decision.kind === "stop") {
727
1014
  return {
728
1015
  final: response,
729
1016
  hops,
730
- reason: "max_hops",
1017
+ reason: decision.reason,
731
1018
  cookies: cookieJar.snapshot(),
1019
+ cookieStore: cookieJar.serialize(),
732
1020
  };
733
1021
  }
734
- const nextMethod = nextRedirectMethod(response.status, method);
735
- if (nextMethod !== method) {
1022
+ if (decision.nextMethod !== method) {
736
1023
  body = undefined;
737
1024
  }
738
- if (visitedRequests.has(`${nextMethod} ${nextUrl}`)) {
739
- return {
740
- final: response,
741
- hops,
742
- reason: "loop",
743
- cookies: cookieJar.snapshot(),
744
- };
745
- }
746
- method = nextMethod;
747
- currentUrl = nextUrl;
1025
+ method = decision.nextMethod;
1026
+ currentUrl = decision.nextUrl;
748
1027
  }
749
1028
  if (!response) {
750
1029
  response = await session.fetch(currentUrl, {
@@ -761,6 +1040,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
761
1040
  hops,
762
1041
  reason: "max_hops",
763
1042
  cookies: cookieJar.snapshot(),
1043
+ cookieStore: cookieJar.serialize(),
764
1044
  };
765
1045
  },
766
1046
  },