@apifuse/provider-sdk 2.2.0-beta.7 → 2.2.0-beta.70

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 (478) hide show
  1. package/AUTHORING.md +943 -51
  2. package/CHANGELOG.md +273 -1
  3. package/LICENSE +21 -0
  4. package/README.md +117 -29
  5. package/SUBMISSION.md +3 -2
  6. package/bin/apifuse-check.ts +293 -13
  7. package/bin/apifuse-dev.ts +42 -19
  8. package/bin/apifuse-migrate-deployment.ts +200 -0
  9. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  10. package/bin/apifuse-migrate-shape.ts +202 -0
  11. package/bin/apifuse-pack-check.ts +19 -6
  12. package/bin/apifuse-pack-smoke.ts +57 -2
  13. package/bin/apifuse-pack-types.ts +736 -38
  14. package/bin/apifuse-perf.ts +59 -23
  15. package/bin/apifuse-record.ts +923 -94
  16. package/bin/apifuse-submit-check.ts +2313 -281
  17. package/bin/apifuse-test.ts +3 -1
  18. package/bin/submit-check-delimited-text.ts +50 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth-turn/index.js +1 -1
  21. package/dist/auth.d.ts +14 -0
  22. package/dist/auth.js +67 -0
  23. package/dist/ceremonies/index.d.ts +16 -0
  24. package/dist/ceremonies/index.js +141 -36
  25. package/dist/cli/commands.d.ts +1 -1
  26. package/dist/cli/commands.js +30 -0
  27. package/dist/cli/create.d.ts +1 -1
  28. package/dist/cli/create.js +8 -2
  29. package/dist/cli/deployment-intent.d.ts +178 -0
  30. package/dist/cli/deployment-intent.js +277 -0
  31. package/dist/cli/local-connection.d.ts +111 -0
  32. package/dist/cli/local-connection.js +151 -0
  33. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  34. package/dist/cli/migrate-deployment-intent.js +1552 -0
  35. package/dist/cli/migrate-operation-declaration.d.ts +70 -0
  36. package/dist/cli/migrate-operation-declaration.js +2206 -0
  37. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  38. package/dist/cli/migrate-operation-shape.js +113 -0
  39. package/dist/cli/migrate-provider-shape.d.ts +56 -0
  40. package/dist/cli/migrate-provider-shape.js +580 -0
  41. package/dist/cli/resolver-runtime.d.ts +21 -0
  42. package/dist/cli/resolver-runtime.js +31 -0
  43. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  44. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  45. package/dist/cli/templates/provider/README.md.tpl +41 -19
  46. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  47. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  48. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  49. package/dist/config/loader.d.ts +202 -16
  50. package/dist/config/loader.js +491 -141
  51. package/dist/contract-serialization.js +4 -8
  52. package/dist/contract-types.d.ts +12 -5
  53. package/dist/contract.js +24 -10
  54. package/dist/declaration-validation.d.ts +34 -0
  55. package/dist/declaration-validation.js +305 -0
  56. package/dist/define.d.ts +71 -29
  57. package/dist/define.js +807 -69
  58. package/dist/dev.d.ts +5 -0
  59. package/dist/dev.js +8 -1
  60. package/dist/engine.d.ts +129 -0
  61. package/dist/engine.js +209 -0
  62. package/dist/error-observability.d.ts +7 -0
  63. package/dist/error-observability.js +61 -0
  64. package/dist/error-resolution.d.ts +18 -0
  65. package/dist/error-resolution.js +243 -0
  66. package/dist/errors.d.ts +74 -0
  67. package/dist/errors.js +99 -0
  68. package/dist/fixture-sanitization.d.ts +28 -0
  69. package/dist/fixture-sanitization.js +371 -0
  70. package/dist/handle-meta.d.ts +90 -0
  71. package/dist/handle-meta.js +73 -0
  72. package/dist/handle.d.ts +226 -0
  73. package/dist/handle.js +345 -0
  74. package/dist/health-scenario.d.ts +1901 -0
  75. package/dist/health-scenario.js +653 -0
  76. package/dist/i18n/catalog.js +21 -1
  77. package/dist/i18n/error-messages.d.ts +86 -0
  78. package/dist/i18n/error-messages.js +192 -0
  79. package/dist/i18n/index.d.ts +1 -0
  80. package/dist/i18n/index.js +1 -0
  81. package/dist/i18n/keys.d.ts +12 -0
  82. package/dist/i18n/keys.js +76 -5
  83. package/dist/i18n/operation-locale-namespace.d.ts +2 -0
  84. package/dist/i18n/operation-locale-namespace.js +7 -0
  85. package/dist/index.d.ts +40 -15
  86. package/dist/index.js +29 -11
  87. package/dist/lint.d.ts +43 -11
  88. package/dist/lint.js +2512 -177
  89. package/dist/native-address.d.ts +43 -0
  90. package/dist/native-address.js +281 -0
  91. package/dist/native-egress-policy.d.ts +31 -0
  92. package/dist/native-egress-policy.js +288 -0
  93. package/dist/observability.d.ts +5 -2
  94. package/dist/observability.js +48 -1
  95. package/dist/provider.d.ts +18 -8
  96. package/dist/provider.js +11 -6
  97. package/dist/runtime/auth-flow.d.ts +6 -1
  98. package/dist/runtime/auth-flow.js +8 -0
  99. package/dist/runtime/bounded-expiring-map.d.ts +21 -0
  100. package/dist/runtime/bounded-expiring-map.js +64 -0
  101. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  102. package/dist/runtime/browser-telemetry-binding.js +83 -0
  103. package/dist/runtime/browser-telemetry.d.ts +76 -0
  104. package/dist/runtime/browser-telemetry.js +165 -0
  105. package/dist/runtime/browser.d.ts +4 -0
  106. package/dist/runtime/browser.js +688 -83
  107. package/dist/runtime/cache-telemetry.d.ts +71 -0
  108. package/dist/runtime/cache-telemetry.js +177 -0
  109. package/dist/runtime/cache.d.ts +3 -0
  110. package/dist/runtime/cache.js +245 -23
  111. package/dist/runtime/capability-response.d.ts +3 -0
  112. package/dist/runtime/capability-response.js +56 -0
  113. package/dist/runtime/capability-telemetry.d.ts +46 -0
  114. package/dist/runtime/capability-telemetry.js +68 -0
  115. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  116. package/dist/runtime/chrome149-header-order.js +289 -0
  117. package/dist/runtime/diagnostic-env-values.d.ts +5 -0
  118. package/dist/runtime/diagnostic-env-values.js +57 -0
  119. package/dist/runtime/diagnostic-env.d.ts +14 -0
  120. package/dist/runtime/diagnostic-env.js +44 -0
  121. package/dist/runtime/diagnostic-redactor.d.ts +54 -0
  122. package/dist/runtime/diagnostic-redactor.js +571 -0
  123. package/dist/runtime/egress-lease.d.ts +65 -0
  124. package/dist/runtime/egress-lease.js +236 -0
  125. package/dist/runtime/engine-mode.d.ts +74 -0
  126. package/dist/runtime/engine-mode.js +152 -0
  127. package/dist/runtime/env.js +14 -1
  128. package/dist/runtime/executor.d.ts +3 -2
  129. package/dist/runtime/executor.js +23 -26
  130. package/dist/runtime/handle-wordlist.d.ts +43 -0
  131. package/dist/runtime/handle-wordlist.js +256 -0
  132. package/dist/runtime/handle.d.ts +71 -0
  133. package/dist/runtime/handle.js +908 -0
  134. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  135. package/dist/runtime/http-telemetry-binding.js +152 -0
  136. package/dist/runtime/http-telemetry-guard.d.ts +16 -0
  137. package/dist/runtime/http-telemetry-guard.js +202 -0
  138. package/dist/runtime/http-telemetry.d.ts +104 -0
  139. package/dist/runtime/http-telemetry.js +190 -0
  140. package/dist/runtime/http.d.ts +34 -0
  141. package/dist/runtime/http.js +680 -121
  142. package/dist/runtime/insights.js +2 -2
  143. package/dist/runtime/instrumentation.d.ts +2 -2
  144. package/dist/runtime/instrumentation.js +462 -19
  145. package/dist/runtime/native-network-errors.d.ts +33 -0
  146. package/dist/runtime/native-network-errors.js +69 -0
  147. package/dist/runtime/native-network.d.ts +105 -0
  148. package/dist/runtime/native-network.js +1433 -0
  149. package/dist/runtime/native-telemetry.d.ts +142 -0
  150. package/dist/runtime/native-telemetry.js +231 -0
  151. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  152. package/dist/runtime/ocr-telemetry.js +192 -0
  153. package/dist/runtime/ocr.d.ts +29 -0
  154. package/dist/runtime/ocr.js +468 -0
  155. package/dist/runtime/otlp.d.ts +71 -2
  156. package/dist/runtime/otlp.js +398 -16
  157. package/dist/runtime/provider.d.ts +7 -0
  158. package/dist/runtime/provider.js +14 -0
  159. package/dist/runtime/proxy-errors.js +6 -2
  160. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  161. package/dist/runtime/proxy-nodemaven.js +147 -0
  162. package/dist/runtime/proxy-retry-policy.d.ts +4 -1
  163. package/dist/runtime/proxy-retry-policy.js +11 -2
  164. package/dist/runtime/proxy-telemetry.d.ts +133 -3
  165. package/dist/runtime/proxy-telemetry.js +218 -70
  166. package/dist/runtime/redirect-hop-headers.d.ts +19 -0
  167. package/dist/runtime/redirect-hop-headers.js +58 -0
  168. package/dist/runtime/redirects.d.ts +61 -0
  169. package/dist/runtime/redirects.js +149 -0
  170. package/dist/runtime/redis.d.ts +1 -1
  171. package/dist/runtime/redis.js +4 -2
  172. package/dist/runtime/request-options.d.ts +75 -1
  173. package/dist/runtime/request-options.js +588 -0
  174. package/dist/runtime/request-telemetry.d.ts +70 -0
  175. package/dist/runtime/request-telemetry.js +229 -0
  176. package/dist/runtime/resolver-config.d.ts +13 -0
  177. package/dist/runtime/resolver-config.js +14 -0
  178. package/dist/runtime/resolver-public.d.ts +3 -0
  179. package/dist/runtime/resolver-public.js +1 -0
  180. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  181. package/dist/runtime/resolver-runtime-options.js +33 -0
  182. package/dist/runtime/resolver-shared.d.ts +24 -0
  183. package/dist/runtime/resolver-shared.js +69 -0
  184. package/dist/runtime/resolver-span-attributes.d.ts +34 -0
  185. package/dist/runtime/resolver-span-attributes.js +93 -0
  186. package/dist/runtime/resolver-telemetry.d.ts +135 -0
  187. package/dist/runtime/resolver-telemetry.js +204 -0
  188. package/dist/runtime/resolver-usage.d.ts +32 -0
  189. package/dist/runtime/resolver-usage.js +54 -0
  190. package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
  191. package/dist/runtime/resolver-vendors/bindings.js +45 -0
  192. package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
  193. package/dist/runtime/resolver-vendors/browser.js +423 -0
  194. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  195. package/dist/runtime/resolver-vendors/capsolver.js +544 -0
  196. package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
  197. package/dist/runtime/resolver-vendors/hosts.js +68 -0
  198. package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
  199. package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
  200. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  201. package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
  202. package/dist/runtime/resolver-vendors/types.d.ts +125 -0
  203. package/dist/runtime/resolver-vendors/types.js +106 -0
  204. package/dist/runtime/resolver.d.ts +79 -0
  205. package/dist/runtime/resolver.js +1094 -0
  206. package/dist/runtime/state-telemetry.d.ts +79 -0
  207. package/dist/runtime/state-telemetry.js +194 -0
  208. package/dist/runtime/state.d.ts +10 -1
  209. package/dist/runtime/state.js +322 -82
  210. package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
  211. package/dist/runtime/stealth-akamai-sbsd.js +142 -0
  212. package/dist/runtime/stealth-cookies.d.ts +24 -0
  213. package/dist/runtime/stealth-cookies.js +131 -0
  214. package/dist/runtime/stealth-owned-headers.d.ts +39 -0
  215. package/dist/runtime/stealth-owned-headers.js +57 -0
  216. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  217. package/dist/runtime/stealth-telemetry.js +179 -0
  218. package/dist/runtime/stealth.d.ts +45 -6
  219. package/dist/runtime/stealth.js +1775 -320
  220. package/dist/runtime/stt-telemetry.d.ts +77 -0
  221. package/dist/runtime/stt-telemetry.js +176 -0
  222. package/dist/runtime/stt.js +18 -19
  223. package/dist/runtime/timeout.d.ts +5 -0
  224. package/dist/runtime/timeout.js +12 -0
  225. package/dist/runtime/trace-config.d.ts +12 -0
  226. package/dist/runtime/trace-config.js +62 -0
  227. package/dist/runtime/trace.d.ts +17 -0
  228. package/dist/runtime/trace.js +176 -25
  229. package/dist/runtime-boundary-lint.d.ts +137 -0
  230. package/dist/runtime-boundary-lint.js +590 -0
  231. package/dist/schema.d.ts +20 -0
  232. package/dist/schema.js +72 -2
  233. package/dist/serve.d.ts +1 -1
  234. package/dist/serve.js +1 -1
  235. package/dist/server/error-observability.d.ts +1 -0
  236. package/dist/server/error-observability.js +1 -0
  237. package/dist/server/error-stack-frames.d.ts +35 -0
  238. package/dist/server/error-stack-frames.js +219 -0
  239. package/dist/server/index.d.ts +21 -2
  240. package/dist/server/index.js +11 -2
  241. package/dist/server/runtime-diagnostics.d.ts +165 -0
  242. package/dist/server/runtime-diagnostics.js +360 -0
  243. package/dist/server/sdk-version.d.ts +3 -0
  244. package/dist/server/sdk-version.js +19 -0
  245. package/dist/server/self-test-input-tokens.d.ts +2 -1
  246. package/dist/server/self-test-input-tokens.js +18 -14
  247. package/dist/server/self-test-redaction.js +8 -33
  248. package/dist/server/self-test.d.ts +18 -3
  249. package/dist/server/self-test.js +139 -76
  250. package/dist/server/sensitive-values.d.ts +26 -0
  251. package/dist/server/sensitive-values.js +137 -0
  252. package/dist/server/serve-implementation.d.ts +285 -0
  253. package/dist/server/serve-implementation.js +3172 -0
  254. package/dist/server/serve.d.ts +1 -75
  255. package/dist/server/serve.js +1 -1170
  256. package/dist/server/state-backend-report.d.ts +70 -0
  257. package/dist/server/state-backend-report.js +113 -0
  258. package/dist/server/trace-output.d.ts +7 -0
  259. package/dist/server/trace-output.js +111 -0
  260. package/dist/server/types.d.ts +37 -5
  261. package/dist/server/types.js +34 -1
  262. package/dist/stateful/errors.d.ts +19 -0
  263. package/dist/stateful/errors.js +24 -0
  264. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  265. package/dist/stateful/http-provider-event-emitter.js +237 -0
  266. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  267. package/dist/stateful/http-session-owner-registry.js +210 -0
  268. package/dist/stateful/index.d.ts +18 -0
  269. package/dist/stateful/index.js +18 -0
  270. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  271. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  272. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  273. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  274. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  275. package/dist/stateful/provider-event-pipeline.js +1 -0
  276. package/dist/stateful/provider-events.d.ts +101 -0
  277. package/dist/stateful/provider-events.js +289 -0
  278. package/dist/stateful/session-key.d.ts +15 -0
  279. package/dist/stateful/session-key.js +86 -0
  280. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  281. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  282. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  283. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  284. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  285. package/dist/stateful/stateful-provider-adapter.js +287 -0
  286. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  287. package/dist/stateful/stateful-provider-observability.js +161 -0
  288. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  289. package/dist/stateful/stateful-provider-owner-forwarder.js +237 -0
  290. package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
  291. package/dist/stateful/stateful-provider-runtime-context.js +61 -0
  292. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  293. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  294. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  295. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  296. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  297. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  298. package/dist/stateful-signing.d.ts +18 -0
  299. package/dist/stateful-signing.js +27 -0
  300. package/dist/stealth/profiles.d.ts +15 -4
  301. package/dist/stealth/profiles.js +169 -211
  302. package/dist/stream-evidence.d.ts +74 -0
  303. package/dist/stream-evidence.js +785 -0
  304. package/dist/stream.js +7 -1
  305. package/dist/testing/index.d.ts +2 -1
  306. package/dist/testing/index.js +2 -1
  307. package/dist/testing/run.d.ts +32 -2
  308. package/dist/testing/run.js +509 -25
  309. package/dist/trace-sanitization.d.ts +6 -0
  310. package/dist/trace-sanitization.js +68 -0
  311. package/dist/types.d.ts +899 -210
  312. package/dist/types.js +1 -0
  313. package/dist/typescript-module.d.ts +39 -0
  314. package/dist/typescript-module.js +72 -0
  315. package/package.json +52 -6
  316. package/src/auth-turn/index.ts +1 -1
  317. package/src/auth.ts +118 -0
  318. package/src/ceremonies/index.ts +189 -46
  319. package/src/cli/commands.ts +37 -0
  320. package/src/cli/create.ts +8 -2
  321. package/src/cli/deployment-intent.ts +364 -0
  322. package/src/cli/local-connection.ts +221 -0
  323. package/src/cli/migrate-deployment-intent.ts +1908 -0
  324. package/src/cli/migrate-operation-declaration.ts +3026 -0
  325. package/src/cli/migrate-operation-shape.ts +184 -0
  326. package/src/cli/migrate-provider-shape.ts +778 -0
  327. package/src/cli/resolver-runtime.ts +72 -0
  328. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  329. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  330. package/src/cli/templates/provider/README.md.tpl +41 -19
  331. package/src/cli/templates/provider/index.ts.tpl +6 -3
  332. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  333. package/src/cli/templates/provider/provider.json.tpl +6 -0
  334. package/src/config/loader.ts +774 -181
  335. package/src/contract-serialization.ts +5 -7
  336. package/src/contract-types.ts +12 -5
  337. package/src/contract.ts +24 -10
  338. package/src/declaration-validation.ts +376 -0
  339. package/src/define.ts +1105 -131
  340. package/src/dev.ts +13 -1
  341. package/src/engine.ts +390 -0
  342. package/src/error-observability.ts +64 -0
  343. package/src/error-resolution.ts +269 -0
  344. package/src/errors.ts +157 -0
  345. package/src/fixture-sanitization.ts +422 -0
  346. package/src/handle-meta.ts +143 -0
  347. package/src/handle.ts +694 -0
  348. package/src/health-scenario.ts +960 -0
  349. package/src/i18n/catalog.ts +23 -1
  350. package/src/i18n/error-messages.ts +255 -0
  351. package/src/i18n/index.ts +1 -0
  352. package/src/i18n/keys.ts +80 -6
  353. package/src/i18n/operation-locale-namespace.ts +10 -0
  354. package/src/index.ts +390 -23
  355. package/src/lint.ts +2975 -234
  356. package/src/native-address.ts +340 -0
  357. package/src/native-egress-policy.ts +358 -0
  358. package/src/observability.ts +51 -1
  359. package/src/provider.ts +207 -27
  360. package/src/runtime/auth-flow.ts +15 -0
  361. package/src/runtime/bounded-expiring-map.ts +67 -0
  362. package/src/runtime/browser-telemetry-binding.ts +92 -0
  363. package/src/runtime/browser-telemetry.ts +289 -0
  364. package/src/runtime/browser.ts +882 -99
  365. package/src/runtime/cache-telemetry.ts +247 -0
  366. package/src/runtime/cache.ts +258 -22
  367. package/src/runtime/capability-response.ts +53 -0
  368. package/src/runtime/capability-telemetry.ts +101 -0
  369. package/src/runtime/chrome149-header-order.ts +330 -0
  370. package/src/runtime/diagnostic-env-values.ts +56 -0
  371. package/src/runtime/diagnostic-env.ts +65 -0
  372. package/src/runtime/diagnostic-redactor.ts +668 -0
  373. package/src/runtime/egress-lease.ts +341 -0
  374. package/src/runtime/engine-mode.ts +233 -0
  375. package/src/runtime/env.ts +15 -1
  376. package/src/runtime/executor.ts +39 -32
  377. package/src/runtime/handle-wordlist.ts +266 -0
  378. package/src/runtime/handle.ts +1180 -0
  379. package/src/runtime/http-telemetry-binding.ts +162 -0
  380. package/src/runtime/http-telemetry-guard.ts +208 -0
  381. package/src/runtime/http-telemetry.ts +292 -0
  382. package/src/runtime/http.ts +896 -150
  383. package/src/runtime/insights.ts +2 -2
  384. package/src/runtime/instrumentation.ts +628 -28
  385. package/src/runtime/native-network-errors.ts +99 -0
  386. package/src/runtime/native-network.ts +1853 -0
  387. package/src/runtime/native-telemetry.ts +366 -0
  388. package/src/runtime/ocr-telemetry.ts +308 -0
  389. package/src/runtime/ocr.ts +564 -0
  390. package/src/runtime/otlp.ts +468 -21
  391. package/src/runtime/provider.ts +15 -0
  392. package/src/runtime/proxy-errors.ts +12 -4
  393. package/src/runtime/proxy-nodemaven.ts +222 -0
  394. package/src/runtime/proxy-retry-policy.ts +12 -1
  395. package/src/runtime/proxy-telemetry.ts +358 -103
  396. package/src/runtime/redirect-hop-headers.ts +71 -0
  397. package/src/runtime/redirects.ts +215 -0
  398. package/src/runtime/redis.ts +7 -2
  399. package/src/runtime/request-options.ts +728 -1
  400. package/src/runtime/request-telemetry.ts +375 -0
  401. package/src/runtime/resolver-config.ts +18 -0
  402. package/src/runtime/resolver-public.ts +44 -0
  403. package/src/runtime/resolver-runtime-options.ts +50 -0
  404. package/src/runtime/resolver-shared.ts +113 -0
  405. package/src/runtime/resolver-span-attributes.ts +139 -0
  406. package/src/runtime/resolver-telemetry.ts +362 -0
  407. package/src/runtime/resolver-usage.ts +99 -0
  408. package/src/runtime/resolver-vendors/bindings.ts +61 -0
  409. package/src/runtime/resolver-vendors/browser.ts +591 -0
  410. package/src/runtime/resolver-vendors/capsolver.ts +727 -0
  411. package/src/runtime/resolver-vendors/hosts.ts +81 -0
  412. package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
  413. package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
  414. package/src/runtime/resolver-vendors/types.ts +261 -0
  415. package/src/runtime/resolver.ts +1543 -0
  416. package/src/runtime/state-telemetry.ts +298 -0
  417. package/src/runtime/state.ts +435 -89
  418. package/src/runtime/stealth-akamai-sbsd.ts +207 -0
  419. package/src/runtime/stealth-cookies.ts +155 -0
  420. package/src/runtime/stealth-owned-headers.ts +67 -0
  421. package/src/runtime/stealth-telemetry.ts +309 -0
  422. package/src/runtime/stealth.ts +2275 -372
  423. package/src/runtime/stt-telemetry.ts +282 -0
  424. package/src/runtime/stt.ts +96 -91
  425. package/src/runtime/timeout.ts +18 -0
  426. package/src/runtime/trace-config.ts +78 -0
  427. package/src/runtime/trace.ts +257 -35
  428. package/src/runtime-boundary-lint.ts +707 -0
  429. package/src/schema.ts +80 -2
  430. package/src/serve.ts +6 -1
  431. package/src/server/error-observability.ts +1 -0
  432. package/src/server/error-stack-frames.ts +214 -0
  433. package/src/server/index.ts +184 -2
  434. package/src/server/runtime-diagnostics.ts +522 -0
  435. package/src/server/sdk-version.ts +22 -0
  436. package/src/server/self-test-input-tokens.ts +29 -14
  437. package/src/server/self-test-redaction.ts +10 -40
  438. package/src/server/self-test.ts +205 -89
  439. package/src/server/sensitive-values.ts +211 -0
  440. package/src/server/serve-implementation.ts +4596 -0
  441. package/src/server/serve.ts +1 -1685
  442. package/src/server/state-backend-report.ts +185 -0
  443. package/src/server/trace-output.ts +189 -0
  444. package/src/server/types.ts +34 -1
  445. package/src/stateful/README.md +146 -0
  446. package/src/stateful/errors.ts +35 -0
  447. package/src/stateful/http-provider-event-emitter.ts +314 -0
  448. package/src/stateful/http-session-owner-registry.ts +306 -0
  449. package/src/stateful/index.ts +18 -0
  450. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  451. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  452. package/src/stateful/provider-event-pipeline.ts +61 -0
  453. package/src/stateful/provider-events.ts +462 -0
  454. package/src/stateful/session-key.ts +111 -0
  455. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  456. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  457. package/src/stateful/stateful-provider-adapter.ts +562 -0
  458. package/src/stateful/stateful-provider-observability.ts +261 -0
  459. package/src/stateful/stateful-provider-owner-forwarder.ts +310 -0
  460. package/src/stateful/stateful-provider-runtime-context.ts +96 -0
  461. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  462. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  463. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  464. package/src/stateful-signing.ts +46 -0
  465. package/src/stealth/profiles.ts +212 -231
  466. package/src/stream-evidence.ts +988 -0
  467. package/src/stream.ts +8 -1
  468. package/src/testing/index.ts +10 -1
  469. package/src/testing/run.ts +685 -19
  470. package/src/trace-sanitization.ts +110 -0
  471. package/src/types.ts +1014 -273
  472. package/src/typescript-module.ts +100 -0
  473. package/dist/choice-token.d.ts +0 -24
  474. package/dist/choice-token.js +0 -74
  475. package/dist/runtime/choice.d.ts +0 -15
  476. package/dist/runtime/choice.js +0 -413
  477. package/src/choice-token.ts +0 -165
  478. package/src/runtime/choice.ts +0 -686
@@ -0,0 +1,1543 @@
1
+ import { readDiagnosticEnv } from "./diagnostic-env.js";
2
+ import { createHash } from "node:crypto";
3
+
4
+ import {
5
+ resolveProxyConfigAsync,
6
+ type ProxyResolutionOptions,
7
+ type ProxyUserAgentSource,
8
+ } from "../config/loader.js";
9
+ import { ProviderError } from "../errors.js";
10
+ import { getStealthProfile } from "../stealth/profiles.js";
11
+ import type {
12
+ ChallengeSolution,
13
+ ProviderCache,
14
+ ProviderChallenge,
15
+ ProviderChallengeKind,
16
+ ProviderProxyMode,
17
+ ProviderResolverConfig,
18
+ ProviderResolverVendor,
19
+ ResolverContext,
20
+ } from "../types.js";
21
+ import { isProviderCacheBypassed } from "./cache.js";
22
+ import {
23
+ RESOLVER_CHALLENGE_BINDINGS,
24
+ resolverChallengeAllowsDirectCache,
25
+ resolverChallengeIsCacheable,
26
+ resolverChallengeIsIdentityScoped,
27
+ resolverChallengeIssuingIdentity,
28
+ } from "./resolver-vendors/bindings.js";
29
+ import { createBrowserResolverVendorAdapter } from "./resolver-vendors/browser.js";
30
+ import { createCapsolverResolverVendorAdapter } from "./resolver-vendors/capsolver.js";
31
+ import { createHypersolutionsResolverVendorAdapter } from "./resolver-vendors/hypersolutions.js";
32
+ import {
33
+ assertResolverHostAllowed,
34
+ assertResolverVendorTransportHosts,
35
+ } from "./resolver-vendors/hosts.js";
36
+ import { createTwoCaptchaResolverVendorAdapter } from "./resolver-vendors/twocaptcha.js";
37
+ import {
38
+ RESOLVER_VENDOR_CAPABILITIES,
39
+ type ResolverIdentity,
40
+ type ResolverIssuingIdentity,
41
+ type ResolverPaidUsageContext,
42
+ type ResolverVendorAdapter,
43
+ type ResolverVendorTransport,
44
+ ResolverVendorUnavailableError,
45
+ attachResolverVendorPollObserver,
46
+ type ResolverVendorUnavailableReason,
47
+ resolveProviderResolverVendors,
48
+ resolverVendorSupports,
49
+ } from "./resolver-vendors/types.js";
50
+ import {
51
+ createUnsupportedResolverClient,
52
+ inheritResolverTelemetryBinding,
53
+ registerResolverTelemetryBinding,
54
+ RESOLVER_INSTRUMENTATION_METADATA,
55
+ type ResolverSolveWithRecorder,
56
+ } from "./resolver-shared.js";
57
+ import {
58
+ APIFUSE__CDP_POOL__URL,
59
+ APIFUSE__RESOLVER__2CAPTCHA__API_KEY,
60
+ APIFUSE__RESOLVER__CAPMONSTER__API_KEY,
61
+ APIFUSE__RESOLVER__CAPSOLVER__API_KEY,
62
+ APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY,
63
+ APIFUSE__RESOLVER__TIMEOUT_MS,
64
+ DEFAULT_RESOLVER_TIMEOUT_MS,
65
+ kindRequiresClientProfile,
66
+ } from "./resolver-config.js";
67
+ import { resolverSpanAttributes } from "./resolver-span-attributes.js";
68
+ import { DEFAULT_STEALTH_PROFILE } from "./stealth.js";
69
+ import type { TraceRecorder } from "./trace.js";
70
+ import type { BrowserTelemetrySink } from "./browser-telemetry.js";
71
+ import type {
72
+ ResolverTelemetryErrorClass,
73
+ ResolverTelemetryPhase,
74
+ ResolverTelemetrySink,
75
+ } from "./resolver-telemetry.js";
76
+
77
+ export {
78
+ createUnsupportedResolverClient,
79
+ RESOLVER_INSTRUMENTATION_METADATA,
80
+ } from "./resolver-shared.js";
81
+ export {
82
+ APIFUSE__CDP_POOL__URL,
83
+ APIFUSE__RESOLVER__2CAPTCHA__API_KEY,
84
+ APIFUSE__RESOLVER__CAPMONSTER__API_KEY,
85
+ APIFUSE__RESOLVER__CAPSOLVER__API_KEY,
86
+ APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY,
87
+ APIFUSE__RESOLVER__TIMEOUT_MS,
88
+ DEFAULT_RESOLVER_TIMEOUT_MS,
89
+ } from "./resolver-config.js";
90
+ export {
91
+ DEFAULT_RESOLVER_VENDOR_PREFERENCE,
92
+ resolveProviderResolverVendors,
93
+ } from "./resolver-vendors/types.js";
94
+
95
+ type EnvLike = Record<string, string | undefined>;
96
+
97
+ type ResolvedResolverVendor =
98
+ | {
99
+ readonly vendor: Exclude<ProviderResolverVendor, "custom">;
100
+ readonly available: true;
101
+ readonly configuration: string | undefined;
102
+ }
103
+ | {
104
+ readonly vendor: ProviderResolverVendor;
105
+ readonly available: false;
106
+ readonly reason: ResolverVendorUnavailableReason;
107
+ };
108
+
109
+ type ResolverChainAttempt = {
110
+ readonly vendor: ProviderResolverVendor;
111
+ readonly reason: ResolverVendorUnavailableReason;
112
+ readonly missingFields?: readonly string[];
113
+ readonly cause?: {
114
+ readonly name: string;
115
+ readonly message: string;
116
+ };
117
+ readonly upstreamHost?: string;
118
+ readonly phase?: string;
119
+ readonly round?: number;
120
+ };
121
+
122
+ type ResolverChainClient = ResolverContext & {
123
+ solve(
124
+ challenge: ProviderChallenge,
125
+ signal?: AbortSignal,
126
+ traceRecorder?: TraceRecorder,
127
+ ): Promise<ChallengeSolution>;
128
+ };
129
+
130
+ export interface ResolverRuntimeOptions {
131
+ readonly allowedHosts?: readonly string[];
132
+ readonly cache?: ProviderCache;
133
+ /** Optional request-scoped observability sink; never used as resolver identity. */
134
+ readonly telemetry?: ResolverTelemetrySink;
135
+ /** Request-scoped browser sink used by the resolver-owned browser vendor. */
136
+ readonly browserTelemetry?: BrowserTelemetrySink;
137
+ /** Inputs for SDK-owned lazy proxy resolution. The SDK never accepts a caller-built identity. */
138
+ readonly proxyIntent?: {
139
+ readonly mode: ProviderProxyMode;
140
+ readonly upstream: NonNullable<ProxyResolutionOptions["upstream"]>;
141
+ readonly affinityKey?: ProxyResolutionOptions["affinityKey"];
142
+ readonly telemetry?: ProxyResolutionOptions["telemetry"];
143
+ readonly userAgent?: string;
144
+ };
145
+ /** Server-owned context/proxy scope used only for identity-bound cache entries. */
146
+ readonly identityScope?: string;
147
+ /** SDK-owned transport already bound to the resolved proxy lease and client profile. */
148
+ readonly transport?: ResolverVendorTransport;
149
+ /** Creates an SDK-owned transport bound to the declared profile and server-owned scope. */
150
+ readonly createTransport?: (input: {
151
+ readonly clientProfile?: string;
152
+ /** Server-owned proxy/context scope; the SDK never accepts a caller-built identity. */
153
+ readonly identityScope?: string;
154
+ }) => ResolverVendorTransport;
155
+ }
156
+
157
+ type CachedResolverSolution = {
158
+ readonly expiresAtMs: number;
159
+ readonly issuerDigest: string;
160
+ readonly solution: ChallengeSolution;
161
+ };
162
+
163
+ type ResolverCacheIndex = {
164
+ readonly entries: readonly {
165
+ readonly direct: true;
166
+ readonly expiresAtMs: number;
167
+ readonly issuerDigest: string;
168
+ }[];
169
+ };
170
+
171
+ export type ResolverSolutionSource = "cache" | "vendor";
172
+
173
+ type ResolverSolutionInvalidationOutcome =
174
+ | "cache_disabled"
175
+ | "entry_deleted"
176
+ | "index_entry_deleted"
177
+ | "not_cookie_solution"
178
+ | "solution_not_cached";
179
+
180
+ const RESOLVER_SOLUTION_CACHE_NAMESPACE = "resolver-solution";
181
+ const RESOLVER_SOLUTION_INDEX_CACHE_NAMESPACE = "resolver-solution-index";
182
+ const MIN_RESOLVER_CACHE_TTL_MS = 1_000;
183
+ const resolverCaches = new WeakMap<object, ProviderCache | null>();
184
+ const solutionIssuerDigests = new WeakMap<object, string>();
185
+ const resolverSolutionSources = new WeakMap<object, ResolverSolutionSource>();
186
+ const SAFE_CAUSE_MESSAGE_WORDS: ReadonlySet<string> = new Set([
187
+ "abort",
188
+ "aborted",
189
+ "at",
190
+ "closed",
191
+ "connect",
192
+ "connection",
193
+ "dns",
194
+ "during",
195
+ "econnrefused",
196
+ "econnreset",
197
+ "error",
198
+ "etimedout",
199
+ "failed",
200
+ "failure",
201
+ "fetch",
202
+ "from",
203
+ "get",
204
+ "lookup",
205
+ "network",
206
+ "post",
207
+ "reading",
208
+ "refused",
209
+ "request",
210
+ "reset",
211
+ "response",
212
+ "socket",
213
+ "timed",
214
+ "timeout",
215
+ "tls",
216
+ "to",
217
+ "unavailable",
218
+ "upstream",
219
+ "while",
220
+ "writing",
221
+ ]);
222
+
223
+ export type ResolverInstrumentationMetadata = {
224
+ readonly target: ResolverContext;
225
+ readonly traceRecorder: TraceRecorder;
226
+ };
227
+
228
+ export type ResolverAdapterFactory = (
229
+ configuration: string | undefined,
230
+ timeoutMs: number,
231
+ allowedHosts: readonly string[],
232
+ telemetry?: BrowserTelemetrySink,
233
+ ) => ResolverVendorAdapter;
234
+
235
+ const resolverAdapterRegistry: Partial<Record<ProviderResolverVendor, ResolverAdapterFactory>> = {
236
+ "2captcha"(configuration, timeoutMs, allowedHosts) {
237
+ if (configuration === undefined) {
238
+ throw new Error("2captcha resolver adapter factory requires an API key");
239
+ }
240
+ return createTwoCaptchaResolverVendorAdapter({
241
+ allowedHosts,
242
+ apiKey: configuration,
243
+ timeoutMs,
244
+ });
245
+ },
246
+ capsolver(configuration, timeoutMs, allowedHosts) {
247
+ return createCapsolverResolverVendorAdapter({
248
+ allowedHosts,
249
+ apiKey: configuration,
250
+ timeoutMs,
251
+ });
252
+ },
253
+ browser(configuration, timeoutMs, allowedHosts, telemetry) {
254
+ return createBrowserResolverVendorAdapter({
255
+ allowedHosts,
256
+ cdpUrl: configuration,
257
+ timeoutMs,
258
+ telemetry,
259
+ });
260
+ },
261
+ hypersolutions(configuration, timeoutMs, allowedHosts) {
262
+ return createHypersolutionsResolverVendorAdapter({
263
+ allowedHosts,
264
+ apiKey: configuration,
265
+ timeoutMs,
266
+ });
267
+ },
268
+ };
269
+
270
+ export const RESOLVER_ADAPTER_REGISTRY: Partial<
271
+ Readonly<Record<ProviderResolverVendor, ResolverAdapterFactory>>
272
+ > = resolverAdapterRegistry;
273
+
274
+ export function swapResolverAdapterFactoryForTests(
275
+ vendor: ProviderResolverVendor,
276
+ factory: ResolverAdapterFactory | undefined,
277
+ ): () => void {
278
+ const original = resolverAdapterRegistry[vendor];
279
+ if (factory === undefined) delete resolverAdapterRegistry[vendor];
280
+ else resolverAdapterRegistry[vendor] = factory;
281
+ let restored = false;
282
+ return () => {
283
+ if (restored) return;
284
+ restored = true;
285
+ if (original === undefined) delete resolverAdapterRegistry[vendor];
286
+ else resolverAdapterRegistry[vendor] = original;
287
+ };
288
+ }
289
+
290
+ let resolveDefaultResolverUserAgent: () => string | undefined = () =>
291
+ getStealthProfile(DEFAULT_STEALTH_PROFILE).userAgent;
292
+
293
+ /** Internal test seam; deliberately not re-exported from the package root. */
294
+ export function swapResolverDefaultUserAgentForTests(
295
+ resolver: (() => string | undefined) | undefined,
296
+ ): () => void {
297
+ const original = resolveDefaultResolverUserAgent;
298
+ resolveDefaultResolverUserAgent =
299
+ resolver ?? (() => getStealthProfile(DEFAULT_STEALTH_PROFILE).userAgent);
300
+ let restored = false;
301
+ return () => {
302
+ if (restored) return;
303
+ restored = true;
304
+ resolveDefaultResolverUserAgent = original;
305
+ };
306
+ }
307
+
308
+ // This is the sole allowlist for declared vendors whose registry entry may be absent.
309
+ // Remove a vendor here when its adapter is registered.
310
+ const KNOWN_UNIMPLEMENTED_RESOLVER_VENDORS: ReadonlySet<ProviderResolverVendor> = new Set([
311
+ "capmonster",
312
+ "custom",
313
+ ]);
314
+
315
+ type ResolverChainEntry = {
316
+ readonly id: ProviderResolverVendor;
317
+ supports(kind: ProviderChallengeKind): boolean;
318
+ createAdapter(browserTelemetry?: BrowserTelemetrySink): ResolverVendorAdapter;
319
+ };
320
+
321
+ function normalizedEnvValue(env: EnvLike, key: string): string | undefined {
322
+ const value = readDiagnosticEnv(key, env)?.trim();
323
+ return value ? value : undefined;
324
+ }
325
+
326
+ function readPositiveIntegerEnv(env: EnvLike, name: string): string | undefined {
327
+ const raw = readDiagnosticEnv(name, env)?.trim();
328
+ if (!raw) return undefined;
329
+ if (!/^[1-9]\d*$/.test(raw)) {
330
+ throw new Error(`${name} must be a positive integer`);
331
+ }
332
+ return raw;
333
+ }
334
+
335
+ function assertDeclaredKind(
336
+ requestedKind: ProviderChallengeKind,
337
+ declaredKinds: readonly ProviderChallengeKind[],
338
+ ): void {
339
+ if (!Object.hasOwn(RESOLVER_CHALLENGE_BINDINGS, requestedKind)) {
340
+ throw new ProviderError(`Unknown resolver kind "${requestedKind}"`, {
341
+ code: "RESOLVER_KIND_NOT_DECLARED",
342
+ fix: "Use a challenge kind exported by @apifuse/provider-sdk.",
343
+ });
344
+ }
345
+ if (declaredKinds.includes(requestedKind)) return;
346
+
347
+ const declared = declaredKinds.length > 0 ? declaredKinds.join(", ") : "none";
348
+ throw new ProviderError(
349
+ `Resolver kind "${requestedKind}" is not declared; declared kinds: ${declared}`,
350
+ {
351
+ code: "RESOLVER_KIND_NOT_DECLARED",
352
+ fix: `Add "${requestedKind}" to the provider's resolver.kinds declaration.`,
353
+ },
354
+ );
355
+ }
356
+
357
+ function createUnavailableAdapter(
358
+ vendor: ProviderResolverVendor,
359
+ reason: ResolverVendorUnavailableReason,
360
+ ): ResolverVendorAdapter {
361
+ return {
362
+ id: vendor,
363
+ supports(kind) {
364
+ return resolverVendorSupports(vendor, kind);
365
+ },
366
+ async solve() {
367
+ throw new ResolverVendorUnavailableError(vendor, reason);
368
+ },
369
+ };
370
+ }
371
+
372
+ function createAdapter(
373
+ vendor: ResolvedResolverVendor,
374
+ timeoutMs: number,
375
+ allowedHosts: readonly string[],
376
+ adapterFactories: Partial<Readonly<Record<ProviderResolverVendor, ResolverAdapterFactory>>>,
377
+ browserTelemetry?: BrowserTelemetrySink,
378
+ ): ResolverVendorAdapter {
379
+ const factory = adapterFactories[vendor.vendor];
380
+ if (!factory && !KNOWN_UNIMPLEMENTED_RESOLVER_VENDORS.has(vendor.vendor)) {
381
+ throw new Error(
382
+ `Resolver adapter factory is missing for implemented vendor "${vendor.vendor}"`,
383
+ );
384
+ }
385
+ if (!vendor.available) {
386
+ return createUnavailableAdapter(vendor.vendor, vendor.reason);
387
+ }
388
+
389
+ if (factory) {
390
+ return factory(vendor.configuration, timeoutMs, allowedHosts, browserTelemetry);
391
+ }
392
+ return createUnavailableAdapter(vendor.vendor, "not_implemented");
393
+ }
394
+
395
+ function assertKnownResolverVendor(vendor: string): asserts vendor is ProviderResolverVendor {
396
+ if (Object.hasOwn(RESOLVER_VENDOR_CAPABILITIES, vendor)) return;
397
+ throw new Error(`Unknown resolver vendor "${vendor}" in resolver configuration`);
398
+ }
399
+
400
+ function throwUnsupportedKind(kind: ProviderChallengeKind, usingDefaultVendors: boolean): never {
401
+ throw new ProviderError(`Resolver vendor chain does not support kind "${kind}"`, {
402
+ code: "RESOLVER_KIND_UNSUPPORTED_BY_CHAIN",
403
+ fix: usingDefaultVendors
404
+ ? `No SDK default resolver vendor supports "${kind}". Declare an explicit resolver.vendors override with a supporting vendor.`
405
+ : `Add a resolver vendor that supports "${kind}" to the provider's resolver.vendors declaration.`,
406
+ });
407
+ }
408
+
409
+ function throwExhausted(
410
+ challengeKind: ProviderChallengeKind,
411
+ attempts: readonly ResolverChainAttempt[],
412
+ executedAttemptCount = attempts.length,
413
+ ): never {
414
+ const hasMissingChallengeInput = attempts.some(
415
+ (attempt) => attempt.reason === "missing_challenge_input",
416
+ );
417
+ throw new ProviderError("The challenge could not be resolved.", {
418
+ code: "RESOLVER_CHAIN_EXHAUSTED",
419
+ fix: hasMissingChallengeInput
420
+ ? "Capture all required challenge fields and retry the request."
421
+ : "Retry the request or contact support if the challenge continues.",
422
+ retryable: false,
423
+ details: {
424
+ challengeKind,
425
+ attempts: executedAttemptCount,
426
+ outcome: "exhausted",
427
+ retryable: false,
428
+ },
429
+ });
430
+ }
431
+
432
+ function assertClientProfileTransportContract(
433
+ clientProfile: string | undefined,
434
+ transport: ResolverVendorTransport | undefined,
435
+ ): void {
436
+ if (clientProfile === undefined || transport === undefined) return;
437
+ throw new ProviderError(
438
+ `Resolver client profile "${clientProfile}" cannot be applied to a pre-bound transport`,
439
+ {
440
+ code: "RESOLVER_CLIENT_PROFILE_TRANSPORT_CONFLICT",
441
+ fix: "Remove the pre-bound transport and provide createTransport({ clientProfile, identityScope }) so the SDK can apply the provider-declared profile.",
442
+ },
443
+ );
444
+ }
445
+
446
+ function adapterRequiresTransport(
447
+ adapter: ResolverVendorAdapter,
448
+ kind: ProviderChallengeKind,
449
+ ): boolean {
450
+ return typeof adapter.requiresTransport === "function"
451
+ ? adapter.requiresTransport(kind)
452
+ : adapter.requiresTransport === true;
453
+ }
454
+
455
+ function sanitizeDiagnosticUrl(rawUrl: string): string {
456
+ try {
457
+ const parsed = new URL(rawUrl);
458
+ if (parsed.username || parsed.password) return "[REDACTED_PROXY_URL]";
459
+ return `${parsed.protocol}//${parsed.host}`;
460
+ } catch {
461
+ return "[REDACTED_URL]";
462
+ }
463
+ }
464
+
465
+ function sanitizeCauseMessage(message: string): string {
466
+ const withoutUrls = message.replace(/\b[a-z][a-z\d+.-]*:\/\/[^\s"'<>]+/gi, sanitizeDiagnosticUrl);
467
+ const withoutAssignments = withoutUrls.replace(/(?:^|\s)[^\s=]+=[^\s]*/g, " [REDACTED]");
468
+ const safeTokens = withoutAssignments
469
+ .replaceAll("\r", " ")
470
+ .replaceAll("\n", " ")
471
+ .split(/\s+/)
472
+ .filter(Boolean)
473
+ .map((token) => {
474
+ if (
475
+ token === "[REDACTED]" ||
476
+ token === "[REDACTED_PROXY_URL]" ||
477
+ /^[a-z][a-z\d+.-]*:\/\/[^\s]+$/i.test(token)
478
+ )
479
+ return token;
480
+ const word = token.replace(/^[^a-z\d]+|[^a-z\d]+$/gi, "");
481
+ return word.length > 0 &&
482
+ word.length <= 32 &&
483
+ SAFE_CAUSE_MESSAGE_WORDS.has(word.toLowerCase())
484
+ ? token
485
+ : "[REDACTED]";
486
+ });
487
+ return safeTokens
488
+ .filter((token, index) => token !== "[REDACTED]" || safeTokens[index - 1] !== token)
489
+ .join(" ")
490
+ .slice(0, 512);
491
+ }
492
+
493
+ function assertResolverTransportLive(revoked: boolean): void {
494
+ if (!revoked) return;
495
+ throw new ProviderError("Resolver transport was revoked when the vendor solve settled", {
496
+ code: "RESOLVER_TRANSPORT_REVOKED",
497
+ fix: "Complete every bound-transport call before solve() settles; the transport is per-attempt and must not be retained.",
498
+ });
499
+ }
500
+
501
+ function restrictResolverTransport(
502
+ transport: ResolverVendorTransport,
503
+ allowedHosts: readonly string[],
504
+ ): { readonly transport: ResolverVendorTransport; revoke(): void } {
505
+ // A retained reference must fail closed once the adapter's solve has settled.
506
+ let revoked = false;
507
+ const restricted: ResolverVendorTransport = {
508
+ sessionHeaders: transport.sessionHeaders,
509
+ ...(transport.getCookie
510
+ ? {
511
+ getCookie(name, url) {
512
+ assertResolverTransportLive(revoked);
513
+ // Jar reads are gated like fetches: an adapter may only read state for declared hosts.
514
+ assertResolverHostAllowed(url, allowedHosts);
515
+ return transport.getCookie?.(name, url);
516
+ },
517
+ }
518
+ : {}),
519
+ async fetch(url, init) {
520
+ assertResolverTransportLive(revoked);
521
+ // Empty declarations remain deny-by-default, matching the adapter-factory/browser path.
522
+ assertResolverHostAllowed(url, allowedHosts);
523
+ const response = await transport.fetch(url, { ...init, redirect: "manual" });
524
+ const hasLocationHeader = Object.keys(response.headers).some(
525
+ (name) => name.toLowerCase() === "location",
526
+ );
527
+ if (response.status >= 300 && response.status < 400 && hasLocationHeader) {
528
+ throw new ProviderError("Resolver transport refused a redirect response", {
529
+ code: "RESOLVER_HOST_NOT_ALLOWED",
530
+ fix: "Use a non-redirecting http or https URL on a host declared in allowedHosts.",
531
+ });
532
+ }
533
+ return response;
534
+ },
535
+ };
536
+ return {
537
+ transport: restricted,
538
+ revoke() {
539
+ revoked = true;
540
+ },
541
+ };
542
+ }
543
+
544
+ function safeCause(error: ResolverVendorUnavailableError): ResolverChainAttempt["cause"] {
545
+ if (error.cause === undefined) return undefined;
546
+ const cause = error.cause;
547
+ const rawName = cause instanceof Error ? cause.name : "Error";
548
+ return {
549
+ name: /^[a-z\d_.:-]{1,64}$/i.test(rawName) ? rawName : "Error",
550
+ message: sanitizeCauseMessage(cause instanceof Error ? cause.message : String(cause)),
551
+ };
552
+ }
553
+
554
+ function safeUpstreamHost(host: string | undefined): string | undefined {
555
+ if (host === undefined) return undefined;
556
+ const trimmed = host.trim();
557
+ if (/^[a-z\d.-]+$/i.test(trimmed)) return trimmed.toLowerCase();
558
+ try {
559
+ return new URL(trimmed).hostname.toLowerCase();
560
+ } catch {
561
+ return undefined;
562
+ }
563
+ }
564
+
565
+ function safePhase(phase: string | undefined): string | undefined {
566
+ return phase !== undefined && /^[a-z\d_.:-]{1,64}$/i.test(phase) ? phase : undefined;
567
+ }
568
+
569
+ function unavailableAttempt(
570
+ error: ResolverVendorUnavailableError,
571
+ forTelemetry = false,
572
+ ): ResolverChainAttempt {
573
+ // Preserve the full input for the collector's redact-then-bound path. Span
574
+ // attributes retain their existing sanitization and phase validation.
575
+ const cause =
576
+ forTelemetry && error.cause !== undefined
577
+ ? {
578
+ name: error.cause instanceof Error ? error.cause.name : "Error",
579
+ message: error.cause instanceof Error ? error.cause.message : String(error.cause),
580
+ }
581
+ : safeCause(error);
582
+ const upstreamHost = safeUpstreamHost(error.upstreamHost);
583
+ const phase = forTelemetry ? error.phase : safePhase(error.phase);
584
+ const round =
585
+ Number.isSafeInteger(error.round) && (error.round as number) > 0 ? error.round : undefined;
586
+ return {
587
+ vendor: error.vendor,
588
+ reason: error.reason,
589
+ ...(error.missingFields ? { missingFields: [...error.missingFields] } : {}),
590
+ ...(cause ? { cause } : {}),
591
+ ...(upstreamHost ? { upstreamHost } : {}),
592
+ ...(phase ? { phase } : {}),
593
+ ...(round !== undefined ? { round } : {}),
594
+ };
595
+ }
596
+
597
+ function unavailableSpanAttributes(error: ResolverVendorUnavailableError): Record<string, unknown> {
598
+ const attempt = unavailableAttempt(error);
599
+ return resolverSpanAttributes({
600
+ unavailability_reason: error.reason,
601
+ missing_fields: error.missingFields,
602
+ cause_name: attempt.cause?.name,
603
+ cause_message: attempt.cause?.message,
604
+ upstream_host: attempt.upstreamHost,
605
+ transport_phase: attempt.phase,
606
+ transport_round: attempt.round,
607
+ });
608
+ }
609
+
610
+ type ResolverAttemptDiagnostics = {
611
+ phase?: string;
612
+ pollCount?: number;
613
+ vendorErrorCode?: string;
614
+ vendorErrorDescription?: string;
615
+ };
616
+
617
+ function resolverTelemetryPhase(value: unknown): ResolverTelemetryPhase | undefined {
618
+ return value === "create_task" ||
619
+ value === "poll_result" ||
620
+ value === "cleanup" ||
621
+ value === "measure_ip" ||
622
+ value === "fetch_script" ||
623
+ value === "generate_payload" ||
624
+ value === "post_payload"
625
+ ? value
626
+ : undefined;
627
+ }
628
+
629
+ function diagnosticString(value: unknown): string | undefined {
630
+ return typeof value === "string" && value.length > 0 ? value : undefined;
631
+ }
632
+
633
+ function diagnosticCount(value: unknown): number | undefined {
634
+ return typeof value === "number" && Number.isFinite(value)
635
+ ? Math.max(0, Math.floor(value))
636
+ : undefined;
637
+ }
638
+
639
+ function createResolverTelemetryTraceRecorder(input: {
640
+ base?: TraceRecorder;
641
+ vendor: ProviderResolverVendor;
642
+ attemptIndex: number;
643
+ telemetry?: ResolverTelemetrySink;
644
+ diagnostics: ResolverAttemptDiagnostics;
645
+ }): TraceRecorder | undefined {
646
+ if (!input.base && !input.telemetry) return undefined;
647
+ const recorder: TraceRecorder = {
648
+ async runSpan<T>(
649
+ name: string,
650
+ fn: () => Promise<T> | T,
651
+ spanOptions: {
652
+ attributes?: Record<string, unknown>;
653
+ onSuccess?: (value: T) => Record<string, unknown> | undefined;
654
+ onError?: (error: unknown) => Record<string, unknown> | undefined;
655
+ } = {},
656
+ ) {
657
+ const adapterPhase = name.startsWith("resolver.vendor.")
658
+ ? diagnosticString(name.replace("resolver.vendor.", ""))
659
+ : undefined;
660
+ const phase = resolverTelemetryPhase(adapterPhase);
661
+ const startedAt = Date.now();
662
+ let attributes: Record<string, unknown> | undefined;
663
+ let spanOutcome: "ok" | "error" = "ok";
664
+ const wrappedOptions = {
665
+ ...spanOptions,
666
+ onSuccess(value: T) {
667
+ attributes = spanOptions.onSuccess?.(value);
668
+ return attributes;
669
+ },
670
+ onError(error: unknown) {
671
+ attributes = spanOptions.onError?.(error);
672
+ return attributes;
673
+ },
674
+ };
675
+ try {
676
+ if (input.base) return await input.base.runSpan(name, fn, wrappedOptions);
677
+ const value = await fn();
678
+ attributes = spanOptions.onSuccess?.(value);
679
+ return value;
680
+ } catch (error) {
681
+ spanOutcome = "error";
682
+ if (!input.base) attributes = spanOptions.onError?.(error);
683
+ throw error;
684
+ } finally {
685
+ if (adapterPhase && adapterPhase !== "attempt") {
686
+ const reportedPhase = diagnosticString(attributes?.transport_phase) ?? adapterPhase;
687
+ const pollCount = diagnosticCount(attributes?.poll_count);
688
+ const vendorErrorCode = diagnosticString(attributes?.vendor_error_code);
689
+ const vendorErrorDescription = diagnosticString(
690
+ attributes?.vendor_error_description ?? attributes?.error_message,
691
+ );
692
+ if (phase === "cleanup") {
693
+ input.telemetry?.recordVendorAttempt({
694
+ vendor: input.vendor,
695
+ phase: "cleanup",
696
+ diagnostics: { phase: reportedPhase, attemptIndex: input.attemptIndex },
697
+ outcome: spanOutcome,
698
+ ms: Date.now() - startedAt,
699
+ ...(pollCount === undefined ? {} : { pollCount }),
700
+ ...(vendorErrorCode ? { vendorErrorCode } : {}),
701
+ ...(vendorErrorDescription ? { vendorErrorDescription } : {}),
702
+ ...(spanOutcome === "error" ? { errorClass: "unexpected" as const } : {}),
703
+ });
704
+ } else {
705
+ input.diagnostics.phase = reportedPhase;
706
+ if (pollCount !== undefined) input.diagnostics.pollCount = pollCount;
707
+ if (vendorErrorCode) input.diagnostics.vendorErrorCode = vendorErrorCode;
708
+ if (vendorErrorDescription) {
709
+ input.diagnostics.vendorErrorDescription = vendorErrorDescription;
710
+ }
711
+ }
712
+ }
713
+ }
714
+ },
715
+ };
716
+ return attachResolverVendorPollObserver(recorder, () => {
717
+ input.diagnostics.pollCount = (input.diagnostics.pollCount ?? 0) + 1;
718
+ });
719
+ }
720
+
721
+ function resolverAttemptErrorClass(
722
+ error: unknown,
723
+ signal: AbortSignal,
724
+ ): ResolverTelemetryErrorClass {
725
+ if (signal.aborted) return "aborted";
726
+ return error instanceof ResolverVendorUnavailableError ? error.reason : "unexpected";
727
+ }
728
+
729
+ function challengeOrigin(challenge: ProviderChallenge): string {
730
+ return new URL(challenge.pageUrl).origin;
731
+ }
732
+
733
+ function resolverIdentityDigest(identity: ResolverIssuingIdentity): string {
734
+ return createHash("sha256")
735
+ .update(
736
+ JSON.stringify({
737
+ proxyUrl: identity.proxyUrl ?? null,
738
+ userAgent: identity.userAgent,
739
+ }),
740
+ )
741
+ .digest("hex");
742
+ }
743
+
744
+ function resolverIdentityScopeDigest(identityScope: string): string {
745
+ return createHash("sha256").update(JSON.stringify({ identityScope })).digest("hex");
746
+ }
747
+
748
+ function resolverSolutionCacheKey(
749
+ cache: ProviderCache,
750
+ challenge: ProviderChallenge,
751
+ issuerDigest: string,
752
+ ): string {
753
+ return cache.key(RESOLVER_SOLUTION_CACHE_NAMESPACE, {
754
+ kind: challenge.kind,
755
+ origin: challengeOrigin(challenge),
756
+ issuerDigest,
757
+ });
758
+ }
759
+
760
+ function resolverSolutionIndexCacheKey(cache: ProviderCache, challenge: ProviderChallenge): string {
761
+ return cache.key(RESOLVER_SOLUTION_INDEX_CACHE_NAMESPACE, {
762
+ kind: challenge.kind,
763
+ origin: challengeOrigin(challenge),
764
+ });
765
+ }
766
+
767
+ function isCachedResolverSolution(value: unknown): value is CachedResolverSolution {
768
+ try {
769
+ if (value === null || typeof value !== "object") return false;
770
+ const candidate = value as Partial<CachedResolverSolution>;
771
+ if (
772
+ typeof candidate.expiresAtMs !== "number" ||
773
+ typeof candidate.issuerDigest !== "string" ||
774
+ candidate.solution === null ||
775
+ typeof candidate.solution !== "object" ||
776
+ candidate.solution.form !== "cookies" ||
777
+ typeof candidate.solution.userAgent !== "string"
778
+ ) {
779
+ return false;
780
+ }
781
+
782
+ const cookies: unknown = candidate.solution.cookies;
783
+ return (
784
+ cookies !== null &&
785
+ typeof cookies === "object" &&
786
+ !Array.isArray(cookies) &&
787
+ Object.values(cookies).every((cookie) => typeof cookie === "string")
788
+ );
789
+ } catch {
790
+ return false;
791
+ }
792
+ }
793
+
794
+ function isResolverCacheIndex(value: unknown): value is ResolverCacheIndex {
795
+ if (value === null || typeof value !== "object") return false;
796
+ const entries = (value as Partial<ResolverCacheIndex>).entries;
797
+ return (
798
+ Array.isArray(entries) &&
799
+ entries.every(
800
+ (entry) =>
801
+ entry !== null &&
802
+ typeof entry === "object" &&
803
+ entry.direct === true &&
804
+ typeof entry.expiresAtMs === "number" &&
805
+ typeof entry.issuerDigest === "string",
806
+ )
807
+ );
808
+ }
809
+
810
+ function solutionExpiryMs(solution: ChallengeSolution): number | undefined {
811
+ if (solution.form !== "cookies") return undefined;
812
+ const expires = solution.expires ?? solution.sdkEstimatedExpires;
813
+ if (typeof expires !== "number" || !Number.isFinite(expires)) return undefined;
814
+ return expires * 1_000;
815
+ }
816
+
817
+ function rememberSolutionIssuer(solution: ChallengeSolution, issuerDigest: string): void {
818
+ if (typeof solution === "object" && solution !== null) {
819
+ solutionIssuerDigests.set(solution, issuerDigest);
820
+ }
821
+ }
822
+
823
+ function rememberSolutionSource(solution: ChallengeSolution, source: ResolverSolutionSource): void {
824
+ if (typeof solution === "object" && solution !== null) {
825
+ resolverSolutionSources.set(solution, source);
826
+ }
827
+ }
828
+
829
+ /**
830
+ * Identify whether this exact SDK-returned solution object came from the cache
831
+ * or a vendor solve. Returns undefined for copied or caller-created objects.
832
+ */
833
+ export function getResolverSolutionSource(
834
+ solution: ChallengeSolution,
835
+ ): ResolverSolutionSource | undefined {
836
+ return resolverSolutionSources.get(solution);
837
+ }
838
+
839
+ async function readCachedSolution(
840
+ cache: ProviderCache,
841
+ challenge: ProviderChallenge,
842
+ issuerDigest: string,
843
+ now: number,
844
+ ): Promise<ChallengeSolution | undefined> {
845
+ const cached = await cache.get(resolverSolutionCacheKey(cache, challenge, issuerDigest));
846
+ if (!cached || !isCachedResolverSolution(cached.value)) return undefined;
847
+ if (cached.value.issuerDigest !== issuerDigest || cached.value.expiresAtMs <= now)
848
+ return undefined;
849
+ if (cached.value.solution.form !== "cookies") return undefined;
850
+ const solution = {
851
+ ...cached.value.solution,
852
+ cookies: { ...cached.value.solution.cookies },
853
+ };
854
+ rememberSolutionIssuer(solution, issuerDigest);
855
+ rememberSolutionSource(solution, "cache");
856
+ return solution;
857
+ }
858
+
859
+ async function findCachedSolution(
860
+ cache: ProviderCache,
861
+ challenge: ProviderChallenge,
862
+ identity: ResolverIssuingIdentity | undefined,
863
+ identityScope: string | undefined,
864
+ ): Promise<ChallengeSolution | undefined> {
865
+ const now = Date.now();
866
+ if (identityScope !== undefined && resolverChallengeIsIdentityScoped(challenge)) {
867
+ return await readCachedSolution(
868
+ cache,
869
+ challenge,
870
+ resolverIdentityScopeDigest(identityScope),
871
+ now,
872
+ );
873
+ }
874
+ if (identity) {
875
+ const lookupIdentity = resolverChallengeIssuingIdentity(challenge, identity);
876
+ return await readCachedSolution(cache, challenge, resolverIdentityDigest(lookupIdentity), now);
877
+ }
878
+
879
+ const index = await cache.get(resolverSolutionIndexCacheKey(cache, challenge));
880
+ if (!index || !isResolverCacheIndex(index.value)) return undefined;
881
+ for (const entry of index.value.entries) {
882
+ if (entry.expiresAtMs <= now) continue;
883
+ const solution = await readCachedSolution(cache, challenge, entry.issuerDigest, now);
884
+ if (solution) return solution;
885
+ }
886
+ return undefined;
887
+ }
888
+
889
+ async function writeResolverCacheIndex(
890
+ cache: ProviderCache,
891
+ challenge: ProviderChallenge,
892
+ entries: ResolverCacheIndex["entries"],
893
+ now: number,
894
+ ): Promise<void> {
895
+ const indexKey = resolverSolutionIndexCacheKey(cache, challenge);
896
+ const liveEntries = entries.filter((entry) => entry.expiresAtMs > now);
897
+ if (liveEntries.length === 0) {
898
+ await cache.delete(indexKey);
899
+ return;
900
+ }
901
+
902
+ const ttlMs = Math.max(
903
+ 1,
904
+ Math.floor(Math.max(...liveEntries.map((entry) => entry.expiresAtMs)) - now),
905
+ );
906
+ await cache.set(indexKey, { entries: liveEntries } satisfies ResolverCacheIndex, { ttlMs });
907
+ }
908
+
909
+ async function cacheResolverSolution(
910
+ cache: ProviderCache,
911
+ challenge: ProviderChallenge,
912
+ solution: ChallengeSolution,
913
+ identity: ResolverIssuingIdentity,
914
+ identityScope: string | undefined,
915
+ ): Promise<{ written: boolean; reason?: "no_expires" | "not_cacheable" }> {
916
+ if (!resolverChallengeIsCacheable(challenge)) return { written: false, reason: "not_cacheable" };
917
+ const expiresAtMs = solutionExpiryMs(solution);
918
+ const now = Date.now();
919
+ if (expiresAtMs === undefined) return { written: false, reason: "no_expires" };
920
+ const ttlMs = Math.floor(expiresAtMs - now);
921
+ if (ttlMs <= MIN_RESOLVER_CACHE_TTL_MS) return { written: false, reason: "no_expires" };
922
+
923
+ const scopedDigest =
924
+ identityScope !== undefined && resolverChallengeIsIdentityScoped(challenge)
925
+ ? resolverIdentityScopeDigest(identityScope)
926
+ : undefined;
927
+ if (
928
+ !resolverChallengeAllowsDirectCache(challenge) &&
929
+ scopedDigest === undefined &&
930
+ identity.proxyUrl === undefined
931
+ ) {
932
+ return { written: false, reason: "not_cacheable" };
933
+ }
934
+ const issuerDigest = scopedDigest ?? resolverIdentityDigest(identity);
935
+ await cache.set(
936
+ resolverSolutionCacheKey(cache, challenge, issuerDigest),
937
+ { expiresAtMs, issuerDigest, solution } satisfies CachedResolverSolution,
938
+ { ttlMs },
939
+ );
940
+ rememberSolutionIssuer(solution, issuerDigest);
941
+ if (scopedDigest !== undefined || identity.proxyUrl !== undefined) return { written: true };
942
+
943
+ const indexKey = resolverSolutionIndexCacheKey(cache, challenge);
944
+ const current = await cache.get(indexKey);
945
+ const currentEntries = isResolverCacheIndex(current?.value) ? current.value.entries : [];
946
+ await writeResolverCacheIndex(
947
+ cache,
948
+ challenge,
949
+ [
950
+ ...currentEntries.filter((entry) => entry.issuerDigest !== issuerDigest),
951
+ { direct: true, expiresAtMs, issuerDigest },
952
+ ],
953
+ now,
954
+ );
955
+ return { written: true };
956
+ }
957
+
958
+ async function invalidateResolverSolutionWithOutcome(
959
+ resolver: ResolverContext,
960
+ challenge: ProviderChallenge,
961
+ solution: ChallengeSolution,
962
+ ): Promise<ResolverSolutionInvalidationOutcome> {
963
+ const metadata = (
964
+ resolver as ResolverContext & {
965
+ readonly [RESOLVER_INSTRUMENTATION_METADATA]?: ResolverInstrumentationMetadata;
966
+ }
967
+ )[RESOLVER_INSTRUMENTATION_METADATA];
968
+ const cacheOwner = metadata?.target ?? resolver;
969
+ const invalidate = async (): Promise<ResolverSolutionInvalidationOutcome> => {
970
+ if (solution.form !== "cookies") return "not_cookie_solution";
971
+ if (!resolverCaches.has(cacheOwner)) {
972
+ throw new Error("Resolver cache registration lookup failed during solution invalidation");
973
+ }
974
+ const cache = resolverCaches.get(cacheOwner);
975
+ if (cache === null || cache === undefined) return "cache_disabled";
976
+ const issuerDigest = solutionIssuerDigests.get(solution);
977
+ if (!issuerDigest) return "solution_not_cached";
978
+ await cache.delete(resolverSolutionCacheKey(cache, challenge, issuerDigest));
979
+
980
+ const index = await cache.get(resolverSolutionIndexCacheKey(cache, challenge));
981
+ if (!index) return "entry_deleted";
982
+ if (!isResolverCacheIndex(index.value)) {
983
+ throw new Error("Resolver solution cache index is malformed during invalidation");
984
+ }
985
+ await writeResolverCacheIndex(
986
+ cache,
987
+ challenge,
988
+ index.value.entries.filter((entry) => entry.issuerDigest !== issuerDigest),
989
+ Date.now(),
990
+ );
991
+ return "index_entry_deleted";
992
+ };
993
+
994
+ if (!metadata) {
995
+ return await invalidate();
996
+ }
997
+ return await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
998
+ attributes: resolverSpanAttributes({ challenge_kind: challenge.kind }),
999
+ onSuccess: (outcome) => resolverSpanAttributes({ outcome }),
1000
+ });
1001
+ }
1002
+
1003
+ /** Remove the cached entry for the exact solution object returned by this resolver. */
1004
+ export async function invalidateResolverSolution(
1005
+ resolver: ResolverContext,
1006
+ challenge: ProviderChallenge,
1007
+ solution: ChallengeSolution,
1008
+ ): Promise<void> {
1009
+ await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
1010
+ }
1011
+
1012
+ /**
1013
+ * Remove a solution only when it was returned from the resolver cache. Providers
1014
+ * should call this when an upstream serves the same challenge after a cached
1015
+ * solution was applied, so the next `solve()` mints a fresh solution.
1016
+ */
1017
+ export async function invalidateCachedResolverSolution(
1018
+ resolver: ResolverContext,
1019
+ challenge: ProviderChallenge,
1020
+ solution: ChallengeSolution,
1021
+ ): Promise<boolean> {
1022
+ if (getResolverSolutionSource(solution) !== "cache") return false;
1023
+ const outcome = await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
1024
+ return outcome === "entry_deleted" || outcome === "index_entry_deleted";
1025
+ }
1026
+
1027
+ async function resolveResolverIdentity(
1028
+ proxyIntent: NonNullable<ResolverRuntimeOptions["proxyIntent"]>,
1029
+ ): Promise<{
1030
+ readonly identity?: ResolverIdentity;
1031
+ readonly unavailableReason?: ResolverVendorUnavailableReason;
1032
+ readonly userAgentSource?: ProxyUserAgentSource;
1033
+ }> {
1034
+ const userAgentSource: ProxyUserAgentSource = proxyIntent.userAgent ? "declared" : "defaulted";
1035
+ let proxyUrl: string | undefined;
1036
+ try {
1037
+ const resolved = await resolveProxyConfigAsync({
1038
+ upstream: proxyIntent.upstream,
1039
+ affinityKey: proxyIntent.affinityKey,
1040
+ telemetry: proxyIntent.telemetry
1041
+ ? {
1042
+ ...proxyIntent.telemetry,
1043
+ recordProxyResolution(event) {
1044
+ proxyIntent.telemetry?.recordProxyResolution({
1045
+ ...event,
1046
+ userAgentSource,
1047
+ });
1048
+ },
1049
+ }
1050
+ : undefined,
1051
+ });
1052
+ proxyUrl = resolved.url;
1053
+ if (!proxyUrl) return { unavailableReason: "missing_proxy_identity", userAgentSource };
1054
+ } catch {
1055
+ // Lease failures contain infrastructure detail that must not cross the resolver
1056
+ // boundary. A required policy is classified by the existing fail-closed guard.
1057
+ return { unavailableReason: "missing_proxy_identity", userAgentSource };
1058
+ }
1059
+
1060
+ try {
1061
+ const userAgent = proxyIntent.userAgent || resolveDefaultResolverUserAgent();
1062
+ if (!userAgent) return { unavailableReason: "missing_client_profile", userAgentSource };
1063
+ return {
1064
+ identity: { proxyUrl, userAgent },
1065
+ userAgentSource,
1066
+ };
1067
+ } catch {
1068
+ return { unavailableReason: "missing_client_profile", userAgentSource };
1069
+ }
1070
+ }
1071
+
1072
+ function createChainTelemetryBinding(options: Parameters<typeof createResolverChainClient>[0]) {
1073
+ return (telemetry: ResolverTelemetrySink, browserTelemetry?: BrowserTelemetrySink) =>
1074
+ createResolverChainClient({ ...options, telemetry, browserTelemetry });
1075
+ }
1076
+
1077
+ function createResolverChainClient(options: {
1078
+ readonly kinds: readonly ProviderChallengeKind[];
1079
+ readonly entries: readonly ResolverChainEntry[];
1080
+ readonly unavailableReason?: string;
1081
+ readonly usingDefaultVendors?: boolean;
1082
+ readonly cache?: ProviderCache;
1083
+ readonly identity?: ResolverIdentity;
1084
+ readonly proxyIntent?: ResolverRuntimeOptions["proxyIntent"];
1085
+ readonly identityScope?: string;
1086
+ readonly transport?: ResolverVendorTransport;
1087
+ readonly createTransport?: ResolverRuntimeOptions["createTransport"];
1088
+ readonly clientProfile?: string;
1089
+ readonly allowedHosts?: readonly string[];
1090
+ readonly telemetry?: ResolverTelemetrySink;
1091
+ readonly browserTelemetry?: BrowserTelemetrySink;
1092
+ }): ResolverChainClient {
1093
+ assertClientProfileTransportContract(options.clientProfile, options.transport);
1094
+ const cache =
1095
+ options.cache && !isProviderCacheBypassed(options.cache) ? options.cache : undefined;
1096
+ const client: ResolverChainClient = {
1097
+ async solve(
1098
+ challenge: ProviderChallenge,
1099
+ signal: AbortSignal = new AbortController().signal,
1100
+ traceRecorder?: TraceRecorder,
1101
+ ) {
1102
+ const solveStartedAt = Date.now();
1103
+ let terminalOutcome: "solved" | "cached" | "exhausted" | "aborted" | "error" = "error";
1104
+ try {
1105
+ assertDeclaredKind(challenge.kind, options.kinds);
1106
+ if (options.unavailableReason) {
1107
+ throw new ProviderError(options.unavailableReason, {
1108
+ code: "RESOLVER_UNAVAILABLE",
1109
+ fix: "Configure at least one declared resolver vendor or provide a test ResolverContext override.",
1110
+ });
1111
+ }
1112
+
1113
+ const supportingEntries = options.entries.filter((entry) => entry.supports(challenge.kind));
1114
+ if (supportingEntries.length === 0) {
1115
+ throwUnsupportedKind(challenge.kind, options.usingDefaultVendors ?? false);
1116
+ }
1117
+ if (kindRequiresClientProfile(challenge.kind) && !options.clientProfile?.trim()) {
1118
+ options.telemetry?.recordIdentity({ source: "none", failure: "missing_client_profile" });
1119
+ throwExhausted(
1120
+ challenge.kind,
1121
+ supportingEntries.map((entry) => ({
1122
+ vendor: entry.id,
1123
+ reason: "missing_client_profile",
1124
+ })),
1125
+ 0,
1126
+ );
1127
+ }
1128
+ signal.throwIfAborted();
1129
+ const identityResolution = options.proxyIntent
1130
+ ? await resolveResolverIdentity(options.proxyIntent)
1131
+ : { identity: options.identity };
1132
+ const identity = identityResolution.identity;
1133
+ options.telemetry?.recordIdentity({
1134
+ // For SBSD, "declared" identifies the configured client profile and its bound
1135
+ // session identity; the engine independently supplies solver credentials.
1136
+ source:
1137
+ kindRequiresClientProfile(challenge.kind) && options.clientProfile
1138
+ ? "declared"
1139
+ : options.proxyIntent
1140
+ ? (identityResolution.userAgentSource ?? "defaulted")
1141
+ : identity
1142
+ ? "declared"
1143
+ : "none",
1144
+ ...(identityResolution.unavailableReason
1145
+ ? { failure: identityResolution.unavailableReason }
1146
+ : {}),
1147
+ });
1148
+ signal.throwIfAborted();
1149
+ const requiredProxyIdentityMissing =
1150
+ options.proxyIntent?.mode === "required" && identity === undefined;
1151
+ if (requiredProxyIdentityMissing) {
1152
+ const reason = identityResolution.unavailableReason ?? "missing_proxy_identity";
1153
+ for (const [entryIndex, entry] of supportingEntries.entries()) {
1154
+ options.telemetry?.recordVendorAttempt({
1155
+ vendor: entry.id,
1156
+ phase: "create_task",
1157
+ diagnostics: { attemptIndex: entryIndex + 1 },
1158
+ outcome: "error",
1159
+ ms: 0,
1160
+ errorClass: reason,
1161
+ });
1162
+ const next = supportingEntries[entryIndex + 1];
1163
+ if (next) {
1164
+ options.telemetry?.recordFailover({ from: entry.id, to: next.id, reason });
1165
+ }
1166
+ }
1167
+ throwExhausted(
1168
+ challenge.kind,
1169
+ supportingEntries.map((entry) => ({
1170
+ vendor: entry.id,
1171
+ reason,
1172
+ })),
1173
+ );
1174
+ }
1175
+ if (!cache) {
1176
+ options.telemetry?.recordCacheRead({ status: "disabled", challengeKind: challenge.kind });
1177
+ } else if (!resolverChallengeIsCacheable(challenge)) {
1178
+ options.telemetry?.recordCacheRead({
1179
+ status: "not_cacheable",
1180
+ challengeKind: challenge.kind,
1181
+ });
1182
+ } else {
1183
+ const cached = await findCachedSolution(
1184
+ cache,
1185
+ challenge,
1186
+ identity,
1187
+ options.identityScope,
1188
+ );
1189
+ options.telemetry?.recordCacheRead({
1190
+ status: cached ? "hit" : "miss",
1191
+ challengeKind: challenge.kind,
1192
+ });
1193
+ if (cached) {
1194
+ terminalOutcome = "cached";
1195
+ return cached;
1196
+ }
1197
+ }
1198
+ const attempts: ResolverChainAttempt[] = [];
1199
+ for (const [entryIndex, entry] of supportingEntries.entries()) {
1200
+ const adapter = entry.createAdapter(options.browserTelemetry);
1201
+ const attemptStartedAt = Date.now();
1202
+ const diagnostics: ResolverAttemptDiagnostics = {};
1203
+ const telemetryTraceRecorder = createResolverTelemetryTraceRecorder({
1204
+ base: traceRecorder,
1205
+ vendor: adapter.id,
1206
+ attemptIndex: entryIndex + 1,
1207
+ telemetry: options.telemetry,
1208
+ diagnostics,
1209
+ });
1210
+ let solution: ChallengeSolution;
1211
+ try {
1212
+ const solveAttempt = async () => {
1213
+ // Boundary first: an overreaching declaration is a caller fault even when no
1214
+ // transport is configured, not a "missing_transport" vendor unavailability.
1215
+ const adapterHosts = adapter.transportAllowedHosts ?? [];
1216
+ assertResolverVendorTransportHosts(adapter.id, adapterHosts);
1217
+ const requiresTransport = adapterRequiresTransport(adapter, challenge.kind);
1218
+ const unrestrictedTransport =
1219
+ options.transport ??
1220
+ (requiresTransport
1221
+ ? options.createTransport?.({
1222
+ clientProfile: options.clientProfile,
1223
+ identityScope: options.identityScope,
1224
+ })
1225
+ : undefined);
1226
+ if (requiresTransport && unrestrictedTransport === undefined) {
1227
+ throw new ResolverVendorUnavailableError(adapter.id, "missing_transport");
1228
+ }
1229
+ const boundTransport = unrestrictedTransport
1230
+ ? restrictResolverTransport(unrestrictedTransport, [
1231
+ ...(options.allowedHosts ?? []),
1232
+ ...adapterHosts,
1233
+ ])
1234
+ : undefined;
1235
+ const usage: ResolverPaidUsageContext = {
1236
+ // Position in the declared chain, not in the kind-filtered list.
1237
+ vendorIndex: options.entries.indexOf(entry) + 1,
1238
+ ...(options.identityScope
1239
+ ? {
1240
+ resolverIdentityScope: createHash("sha256")
1241
+ .update(options.identityScope, "utf8")
1242
+ .digest("hex"),
1243
+ }
1244
+ : {}),
1245
+ };
1246
+ try {
1247
+ return await adapter.solve(
1248
+ challenge,
1249
+ identity,
1250
+ signal,
1251
+ telemetryTraceRecorder,
1252
+ boundTransport?.transport,
1253
+ usage,
1254
+ );
1255
+ } finally {
1256
+ boundTransport?.revoke();
1257
+ }
1258
+ };
1259
+ solution = traceRecorder
1260
+ ? await traceRecorder.runSpan("resolver.vendor.attempt", solveAttempt, {
1261
+ attributes: resolverSpanAttributes({
1262
+ vendor: adapter.id,
1263
+ challenge_kind: challenge.kind,
1264
+ client_profile: options.clientProfile,
1265
+ resolver_identity_source: identityResolution.userAgentSource,
1266
+ }),
1267
+ onError(error) {
1268
+ return error instanceof ResolverVendorUnavailableError
1269
+ ? unavailableSpanAttributes(error)
1270
+ : undefined;
1271
+ },
1272
+ })
1273
+ : await solveAttempt();
1274
+ } catch (error) {
1275
+ const unavailable = error instanceof ResolverVendorUnavailableError ? error : undefined;
1276
+ const attempt = unavailable ? unavailableAttempt(unavailable, true) : undefined;
1277
+ options.telemetry?.recordVendorAttempt({
1278
+ vendor: adapter.id,
1279
+ phase:
1280
+ resolverTelemetryPhase(unavailable?.phase) ??
1281
+ resolverTelemetryPhase(diagnostics.phase) ??
1282
+ "create_task",
1283
+ diagnostics: {
1284
+ ...attempt,
1285
+ attemptIndex: entryIndex + 1,
1286
+ phase: attempt?.phase ?? diagnostics.phase,
1287
+ },
1288
+ outcome: "error",
1289
+ ms: Date.now() - attemptStartedAt,
1290
+ errorClass: resolverAttemptErrorClass(error, signal),
1291
+ ...(diagnostics.pollCount === undefined ? {} : { pollCount: diagnostics.pollCount }),
1292
+ ...(diagnostics.vendorErrorCode
1293
+ ? { vendorErrorCode: diagnostics.vendorErrorCode }
1294
+ : {}),
1295
+ ...(diagnostics.vendorErrorDescription
1296
+ ? { vendorErrorDescription: diagnostics.vendorErrorDescription }
1297
+ : {}),
1298
+ });
1299
+ signal.throwIfAborted();
1300
+ if (!unavailable || !attempt) throw error;
1301
+ attempts.push(attempt);
1302
+ const next = supportingEntries[entryIndex + 1];
1303
+ if (next) {
1304
+ options.telemetry?.recordFailover({
1305
+ from: adapter.id,
1306
+ to: next.id,
1307
+ reason: unavailable.reason,
1308
+ });
1309
+ }
1310
+ continue;
1311
+ }
1312
+ options.telemetry?.recordVendorAttempt({
1313
+ vendor: adapter.id,
1314
+ phase: resolverTelemetryPhase(diagnostics.phase) ?? "poll_result",
1315
+ diagnostics: { attemptIndex: entryIndex + 1, phase: diagnostics.phase },
1316
+ outcome: "ok",
1317
+ ms: Date.now() - attemptStartedAt,
1318
+ ...(diagnostics.pollCount === undefined ? {} : { pollCount: diagnostics.pollCount }),
1319
+ });
1320
+ if (cache) {
1321
+ try {
1322
+ let cacheWrite: { written: boolean; reason?: "no_expires" | "not_cacheable" };
1323
+ if (!resolverChallengeIsCacheable(challenge)) {
1324
+ cacheWrite = { written: false, reason: "not_cacheable" };
1325
+ } else if (solution.form !== "cookies" || solutionExpiryMs(solution) === undefined) {
1326
+ cacheWrite = { written: false, reason: "no_expires" };
1327
+ } else {
1328
+ const issuingIdentity =
1329
+ adapter.getIssuingIdentity?.(solution, identity, challenge) ??
1330
+ resolverChallengeIssuingIdentity(challenge, {
1331
+ ...(identity ? { proxyUrl: identity.proxyUrl } : {}),
1332
+ userAgent: solution.userAgent,
1333
+ });
1334
+ cacheWrite = issuingIdentity
1335
+ ? await cacheResolverSolution(
1336
+ cache,
1337
+ challenge,
1338
+ solution,
1339
+ issuingIdentity,
1340
+ options.identityScope,
1341
+ )
1342
+ : { written: false, reason: "not_cacheable" };
1343
+ }
1344
+ options.telemetry?.recordCacheWrite(cacheWrite);
1345
+ } catch (error) {
1346
+ options.telemetry?.recordCacheWrite({ written: false, reason: "error" });
1347
+ throw error;
1348
+ }
1349
+ }
1350
+ rememberSolutionSource(solution, "vendor");
1351
+ // Adapter success includes SBSD payload acceptance with verified:false.
1352
+ // The caller reports protected-refetch verification separately.
1353
+ terminalOutcome = "solved";
1354
+ return solution;
1355
+ }
1356
+
1357
+ throwExhausted(challenge.kind, attempts);
1358
+ } catch (error) {
1359
+ terminalOutcome = signal.aborted
1360
+ ? "aborted"
1361
+ : error instanceof ProviderError && error.code === "RESOLVER_CHAIN_EXHAUSTED"
1362
+ ? "exhausted"
1363
+ : "error";
1364
+ throw error;
1365
+ } finally {
1366
+ options.telemetry?.recordOutcome({
1367
+ outcome: terminalOutcome,
1368
+ solveMs: Date.now() - solveStartedAt,
1369
+ challengeKind: challenge.kind,
1370
+ });
1371
+ }
1372
+ },
1373
+ };
1374
+ resolverCaches.set(client, cache ?? null);
1375
+ const reusableOptions = { ...options, telemetry: undefined, browserTelemetry: undefined };
1376
+ registerResolverTelemetryBinding(client, createChainTelemetryBinding(reusableOptions));
1377
+ return client;
1378
+ }
1379
+
1380
+ export function createResolverClient(options: {
1381
+ readonly kinds: readonly ProviderChallengeKind[];
1382
+ readonly adapters: readonly ResolverVendorAdapter[];
1383
+ readonly unavailableReason?: string;
1384
+ readonly cache?: ProviderCache;
1385
+ readonly identity?: ResolverIdentity;
1386
+ readonly proxyIntent?: ResolverRuntimeOptions["proxyIntent"];
1387
+ readonly transport?: ResolverVendorTransport;
1388
+ readonly createTransport?: ResolverRuntimeOptions["createTransport"];
1389
+ readonly clientProfile?: string;
1390
+ readonly allowedHosts?: readonly string[];
1391
+ readonly telemetry?: ResolverTelemetrySink;
1392
+ }): ResolverChainClient {
1393
+ for (const adapter of options.adapters) {
1394
+ // Fail at construction, not on the first solve, when a supplied adapter oversteps its vendor.
1395
+ assertResolverVendorTransportHosts(adapter.id, adapter.transportAllowedHosts ?? []);
1396
+ }
1397
+ return createResolverChainClient({
1398
+ kinds: options.kinds,
1399
+ entries: options.adapters.map((adapter) => ({
1400
+ id: adapter.id,
1401
+ supports: (kind) => adapter.supports(kind),
1402
+ createAdapter: () => adapter,
1403
+ })),
1404
+ unavailableReason: options.unavailableReason,
1405
+ cache: options.cache,
1406
+ identity: options.identity,
1407
+ proxyIntent: options.proxyIntent,
1408
+ transport: options.transport,
1409
+ createTransport: options.createTransport,
1410
+ clientProfile: options.clientProfile,
1411
+ allowedHosts: options.allowedHosts,
1412
+ telemetry: options.telemetry,
1413
+ });
1414
+ }
1415
+
1416
+ function resolveVendorAvailability(
1417
+ vendor: ProviderResolverVendor,
1418
+ env: EnvLike,
1419
+ ): ResolvedResolverVendor {
1420
+ if (vendor === "custom") {
1421
+ return {
1422
+ vendor,
1423
+ available: false,
1424
+ reason: "missing_transport",
1425
+ };
1426
+ }
1427
+ if (vendor === "browser") {
1428
+ return {
1429
+ vendor,
1430
+ available: true,
1431
+ configuration: normalizedEnvValue(env, APIFUSE__CDP_POOL__URL),
1432
+ };
1433
+ }
1434
+
1435
+ const envKey =
1436
+ vendor === "2captcha"
1437
+ ? APIFUSE__RESOLVER__2CAPTCHA__API_KEY
1438
+ : vendor === "capsolver"
1439
+ ? APIFUSE__RESOLVER__CAPSOLVER__API_KEY
1440
+ : vendor === "capmonster"
1441
+ ? APIFUSE__RESOLVER__CAPMONSTER__API_KEY
1442
+ : APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY;
1443
+
1444
+ const configuration = normalizedEnvValue(env, envKey);
1445
+ return configuration
1446
+ ? { vendor, available: true, configuration }
1447
+ : { vendor, available: false, reason: "missing_credentials" };
1448
+ }
1449
+
1450
+ export function bindResolverSignal(
1451
+ resolver: ResolverContext,
1452
+ defaultSignal: AbortSignal | undefined,
1453
+ ): ResolverContext {
1454
+ if (!defaultSignal) return resolver;
1455
+ const boundResolver: ResolverContext = {
1456
+ solve(challenge, signal = defaultSignal, traceRecorder?: TraceRecorder) {
1457
+ return (resolver as Partial<ResolverSolveWithRecorder> & ResolverContext).solve(
1458
+ challenge,
1459
+ signal,
1460
+ traceRecorder,
1461
+ );
1462
+ },
1463
+ };
1464
+ if (resolverCaches.has(resolver)) {
1465
+ resolverCaches.set(boundResolver, resolverCaches.get(resolver) ?? null);
1466
+ }
1467
+ inheritResolverTelemetryBinding(resolver, boundResolver);
1468
+ return boundResolver;
1469
+ }
1470
+
1471
+ function createResolverClientFromEnvInternal(
1472
+ config: ProviderResolverConfig | undefined,
1473
+ env: EnvLike,
1474
+ options: ResolverRuntimeOptions,
1475
+ adapterFactories: Partial<Readonly<Record<ProviderResolverVendor, ResolverAdapterFactory>>>,
1476
+ ): ResolverContext {
1477
+ if (!config) {
1478
+ return createUnsupportedResolverClient("Provider does not declare resolver capability");
1479
+ }
1480
+ assertClientProfileTransportContract(config.clientProfile, options.transport);
1481
+
1482
+ const vendors = resolveProviderResolverVendors(config);
1483
+ if (config.vendors !== undefined && config.vendors.length === 0) {
1484
+ return createResolverChainClient({
1485
+ kinds: config.kinds,
1486
+ entries: [],
1487
+ unavailableReason: "Provider resolver vendor chain is empty",
1488
+ telemetry: options.telemetry,
1489
+ });
1490
+ }
1491
+
1492
+ const timeoutValue = readPositiveIntegerEnv(env, APIFUSE__RESOLVER__TIMEOUT_MS);
1493
+ const timeoutMs = timeoutValue === undefined ? DEFAULT_RESOLVER_TIMEOUT_MS : Number(timeoutValue);
1494
+ const allowedHosts = [...(options.allowedHosts ?? [])];
1495
+
1496
+ return createResolverChainClient({
1497
+ kinds: config.kinds,
1498
+ entries: vendors.map((configuredVendor) => {
1499
+ assertKnownResolverVendor(configuredVendor);
1500
+ const vendor = configuredVendor;
1501
+ return {
1502
+ id: vendor,
1503
+ supports: (kind: ProviderChallengeKind) => resolverVendorSupports(vendor, kind),
1504
+ createAdapter: (browserTelemetry) =>
1505
+ createAdapter(
1506
+ resolveVendorAvailability(vendor, env),
1507
+ timeoutMs,
1508
+ allowedHosts,
1509
+ adapterFactories,
1510
+ browserTelemetry,
1511
+ ),
1512
+ };
1513
+ }),
1514
+ usingDefaultVendors: config.vendors === undefined,
1515
+ cache: options.cache,
1516
+ proxyIntent: options.proxyIntent,
1517
+ identityScope: options.identityScope,
1518
+ transport: options.transport,
1519
+ createTransport: options.createTransport,
1520
+ clientProfile: config.clientProfile,
1521
+ allowedHosts,
1522
+ telemetry: options.telemetry,
1523
+ browserTelemetry: options.browserTelemetry,
1524
+ });
1525
+ }
1526
+
1527
+ export function createResolverClientFromEnv(
1528
+ config: ProviderResolverConfig | undefined,
1529
+ env: EnvLike = process.env,
1530
+ options: ResolverRuntimeOptions = {},
1531
+ ): ResolverContext {
1532
+ return createResolverClientFromEnvInternal(config, env, options, RESOLVER_ADAPTER_REGISTRY);
1533
+ }
1534
+
1535
+ /** Internal test seam; deliberately not re-exported from the package root. */
1536
+ export function createResolverClientFromEnvForTests(
1537
+ config: ProviderResolverConfig | undefined,
1538
+ env: EnvLike,
1539
+ options: ResolverRuntimeOptions,
1540
+ adapterFactories: Partial<Readonly<Record<ProviderResolverVendor, ResolverAdapterFactory>>>,
1541
+ ): ResolverContext {
1542
+ return createResolverClientFromEnvInternal(config, env, options, adapterFactories);
1543
+ }