@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.51

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 (368) hide show
  1. package/AUTHORING.md +537 -33
  2. package/CHANGELOG.md +205 -1
  3. package/README.md +83 -22
  4. package/SUBMISSION.md +2 -2
  5. package/bin/apifuse-check.ts +165 -11
  6. package/bin/apifuse-dev.ts +57 -13
  7. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  8. package/bin/apifuse-migrate-shape.ts +202 -0
  9. package/bin/apifuse-pack-check.ts +22 -2
  10. package/bin/apifuse-pack-smoke.ts +57 -2
  11. package/bin/apifuse-pack-types.ts +357 -38
  12. package/bin/apifuse-perf.ts +14 -13
  13. package/bin/apifuse-record.ts +709 -72
  14. package/bin/apifuse-submit-check.ts +2344 -324
  15. package/bin/apifuse-sync-assets.ts +117 -0
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/dist/auth-turn/index.d.ts +3 -3
  18. package/dist/auth-turn/index.js +1 -1
  19. package/dist/auth.d.ts +14 -0
  20. package/dist/auth.js +67 -0
  21. package/dist/ceremonies/index.d.ts +16 -0
  22. package/dist/ceremonies/index.js +141 -36
  23. package/dist/cli/commands.d.ts +1 -1
  24. package/dist/cli/commands.js +27 -0
  25. package/dist/cli/create.d.ts +4 -1
  26. package/dist/cli/create.js +42 -37
  27. package/dist/cli/migrate-operation-declaration.d.ts +59 -0
  28. package/dist/cli/migrate-operation-declaration.js +1249 -0
  29. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  30. package/dist/cli/migrate-operation-shape.js +113 -0
  31. package/dist/cli/migrate-provider-shape.d.ts +52 -0
  32. package/dist/cli/migrate-provider-shape.js +578 -0
  33. package/dist/cli/prompt-assets.d.ts +80 -0
  34. package/dist/cli/prompt-assets.js +743 -0
  35. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  36. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  37. package/dist/cli/templates/provider/README.md.tpl +5 -5
  38. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  39. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  40. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  41. package/dist/config/loader.d.ts +179 -16
  42. package/dist/config/loader.js +435 -127
  43. package/dist/contract-serialization.js +4 -8
  44. package/dist/contract-types.d.ts +12 -5
  45. package/dist/contract.js +24 -10
  46. package/dist/declaration-validation.d.ts +33 -0
  47. package/dist/declaration-validation.js +266 -0
  48. package/dist/define.d.ts +66 -29
  49. package/dist/define.js +778 -69
  50. package/dist/dev.d.ts +3 -0
  51. package/dist/dev.js +1 -1
  52. package/dist/engine.d.ts +78 -0
  53. package/dist/engine.js +133 -0
  54. package/dist/error-observability.d.ts +7 -0
  55. package/dist/error-observability.js +61 -0
  56. package/dist/error-resolution.d.ts +4 -0
  57. package/dist/error-resolution.js +122 -0
  58. package/dist/errors.d.ts +33 -0
  59. package/dist/errors.js +40 -0
  60. package/dist/fixture-sanitization.d.ts +28 -0
  61. package/dist/fixture-sanitization.js +227 -0
  62. package/dist/health-scenario.d.ts +1842 -0
  63. package/dist/health-scenario.js +624 -0
  64. package/dist/index.d.ts +21 -10
  65. package/dist/index.js +12 -7
  66. package/dist/lint.d.ts +23 -11
  67. package/dist/lint.js +572 -69
  68. package/dist/native-address.d.ts +43 -0
  69. package/dist/native-address.js +281 -0
  70. package/dist/native-egress-policy.d.ts +31 -0
  71. package/dist/native-egress-policy.js +288 -0
  72. package/dist/observability.d.ts +5 -2
  73. package/dist/observability.js +48 -1
  74. package/dist/provider.d.ts +10 -2
  75. package/dist/provider.js +4 -1
  76. package/dist/runtime/auth-flow.d.ts +5 -1
  77. package/dist/runtime/auth-flow.js +6 -0
  78. package/dist/runtime/browser.d.ts +1 -0
  79. package/dist/runtime/browser.js +492 -49
  80. package/dist/runtime/cache.d.ts +1 -0
  81. package/dist/runtime/cache.js +169 -15
  82. package/dist/runtime/choice-wordlist.d.ts +9 -0
  83. package/dist/runtime/choice-wordlist.js +138 -0
  84. package/dist/runtime/choice.d.ts +13 -1
  85. package/dist/runtime/choice.js +490 -102
  86. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  87. package/dist/runtime/chrome149-header-order.js +289 -0
  88. package/dist/runtime/env.js +12 -0
  89. package/dist/runtime/executor.d.ts +3 -2
  90. package/dist/runtime/executor.js +26 -25
  91. package/dist/runtime/http.d.ts +1 -0
  92. package/dist/runtime/http.js +515 -53
  93. package/dist/runtime/insights.js +2 -2
  94. package/dist/runtime/instrumentation.d.ts +2 -2
  95. package/dist/runtime/instrumentation.js +366 -8
  96. package/dist/runtime/native-network-errors.d.ts +33 -0
  97. package/dist/runtime/native-network-errors.js +69 -0
  98. package/dist/runtime/native-network.d.ts +96 -0
  99. package/dist/runtime/native-network.js +1232 -0
  100. package/dist/runtime/ocr.d.ts +29 -0
  101. package/dist/runtime/ocr.js +440 -0
  102. package/dist/runtime/otlp.d.ts +71 -2
  103. package/dist/runtime/otlp.js +397 -16
  104. package/dist/runtime/proxy-errors.js +6 -2
  105. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  106. package/dist/runtime/proxy-nodemaven.js +146 -0
  107. package/dist/runtime/proxy-telemetry.d.ts +80 -1
  108. package/dist/runtime/proxy-telemetry.js +154 -47
  109. package/dist/runtime/redirects.d.ts +29 -0
  110. package/dist/runtime/redirects.js +36 -0
  111. package/dist/runtime/redis.d.ts +1 -1
  112. package/dist/runtime/redis.js +4 -2
  113. package/dist/runtime/request-options.d.ts +68 -1
  114. package/dist/runtime/request-options.js +548 -0
  115. package/dist/runtime/resolver-config.d.ts +6 -0
  116. package/dist/runtime/resolver-config.js +6 -0
  117. package/dist/runtime/resolver-public.d.ts +1 -0
  118. package/dist/runtime/resolver-public.js +1 -0
  119. package/dist/runtime/resolver-shared.d.ts +14 -0
  120. package/dist/runtime/resolver-shared.js +12 -0
  121. package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
  122. package/dist/runtime/resolver-vendors/bindings.js +40 -0
  123. package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
  124. package/dist/runtime/resolver-vendors/browser.js +377 -0
  125. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  126. package/dist/runtime/resolver-vendors/capsolver.js +531 -0
  127. package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
  128. package/dist/runtime/resolver-vendors/hosts.js +33 -0
  129. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  130. package/dist/runtime/resolver-vendors/twocaptcha.js +408 -0
  131. package/dist/runtime/resolver-vendors/types.d.ts +94 -0
  132. package/dist/runtime/resolver-vendors/types.js +96 -0
  133. package/dist/runtime/resolver.d.ts +72 -0
  134. package/dist/runtime/resolver.js +771 -0
  135. package/dist/runtime/secrets.d.ts +27 -0
  136. package/dist/runtime/secrets.js +51 -0
  137. package/dist/runtime/state.d.ts +3 -0
  138. package/dist/runtime/state.js +277 -71
  139. package/dist/runtime/stealth-cookies.d.ts +20 -0
  140. package/dist/runtime/stealth-cookies.js +111 -0
  141. package/dist/runtime/stealth.d.ts +41 -5
  142. package/dist/runtime/stealth.js +997 -291
  143. package/dist/runtime/stt.js +1 -12
  144. package/dist/runtime/timeout.d.ts +5 -0
  145. package/dist/runtime/timeout.js +12 -0
  146. package/dist/runtime/trace-config.d.ts +12 -0
  147. package/dist/runtime/trace-config.js +62 -0
  148. package/dist/runtime/trace.d.ts +5 -0
  149. package/dist/runtime/trace.js +43 -10
  150. package/dist/serve.d.ts +1 -1
  151. package/dist/serve.js +1 -1
  152. package/dist/server/error-observability.d.ts +1 -0
  153. package/dist/server/error-observability.js +1 -0
  154. package/dist/server/index.d.ts +5 -2
  155. package/dist/server/index.js +2 -2
  156. package/dist/server/self-test-input-tokens.d.ts +2 -1
  157. package/dist/server/self-test-input-tokens.js +18 -14
  158. package/dist/server/self-test.d.ts +14 -3
  159. package/dist/server/self-test.js +129 -58
  160. package/dist/server/serve-implementation.d.ts +230 -0
  161. package/dist/server/serve-implementation.js +2265 -0
  162. package/dist/server/serve.d.ts +1 -70
  163. package/dist/server/serve.js +1 -1131
  164. package/dist/server/trace-output.d.ts +6 -0
  165. package/dist/server/trace-output.js +79 -0
  166. package/dist/server/types.d.ts +30 -5
  167. package/dist/server/types.js +13 -1
  168. package/dist/stateful/errors.d.ts +19 -0
  169. package/dist/stateful/errors.js +24 -0
  170. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  171. package/dist/stateful/http-provider-event-emitter.js +237 -0
  172. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  173. package/dist/stateful/http-session-owner-registry.js +210 -0
  174. package/dist/stateful/index.d.ts +18 -0
  175. package/dist/stateful/index.js +18 -0
  176. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  177. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  178. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  179. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  180. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  181. package/dist/stateful/provider-event-pipeline.js +1 -0
  182. package/dist/stateful/provider-events.d.ts +101 -0
  183. package/dist/stateful/provider-events.js +289 -0
  184. package/dist/stateful/session-key.d.ts +15 -0
  185. package/dist/stateful/session-key.js +86 -0
  186. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  187. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  188. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  189. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  190. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  191. package/dist/stateful/stateful-provider-adapter.js +287 -0
  192. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  193. package/dist/stateful/stateful-provider-observability.js +161 -0
  194. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  195. package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
  196. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  197. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  198. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  199. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  200. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  201. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  202. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  203. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  204. package/dist/stateful-signing.d.ts +18 -0
  205. package/dist/stateful-signing.js +27 -0
  206. package/dist/stealth/profiles.d.ts +9 -4
  207. package/dist/stealth/profiles.js +160 -211
  208. package/dist/stream-evidence.d.ts +74 -0
  209. package/dist/stream-evidence.js +785 -0
  210. package/dist/stream.js +7 -1
  211. package/dist/testing/index.d.ts +2 -1
  212. package/dist/testing/index.js +2 -1
  213. package/dist/testing/run.d.ts +32 -2
  214. package/dist/testing/run.js +489 -21
  215. package/dist/trace-sanitization.d.ts +5 -0
  216. package/dist/trace-sanitization.js +45 -0
  217. package/dist/types.d.ts +673 -131
  218. package/dist/types.js +1 -0
  219. package/package.json +45 -5
  220. package/src/auth-turn/index.ts +1 -1
  221. package/src/auth.ts +118 -0
  222. package/src/ceremonies/index.ts +189 -46
  223. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
  224. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
  225. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
  226. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
  227. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +15 -0
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +9 -0
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +24 -0
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
  244. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
  245. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
  246. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
  247. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
  248. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
  249. package/src/cli/commands.ts +33 -0
  250. package/src/cli/create.ts +50 -37
  251. package/src/cli/migrate-operation-declaration.ts +1751 -0
  252. package/src/cli/migrate-operation-shape.ts +184 -0
  253. package/src/cli/migrate-provider-shape.ts +772 -0
  254. package/src/cli/prompt-assets.ts +865 -0
  255. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  256. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  257. package/src/cli/templates/provider/README.md.tpl +5 -5
  258. package/src/cli/templates/provider/index.ts.tpl +6 -3
  259. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  260. package/src/cli/templates/provider/provider.json.tpl +6 -0
  261. package/src/config/loader.ts +690 -163
  262. package/src/contract-serialization.ts +5 -7
  263. package/src/contract-types.ts +12 -5
  264. package/src/contract.ts +24 -10
  265. package/src/declaration-validation.ts +330 -0
  266. package/src/define.ts +1048 -131
  267. package/src/dev.ts +4 -1
  268. package/src/engine.ts +279 -0
  269. package/src/error-observability.ts +64 -0
  270. package/src/error-resolution.ts +127 -0
  271. package/src/errors.ts +68 -0
  272. package/src/fixture-sanitization.ts +264 -0
  273. package/src/health-scenario.ts +875 -0
  274. package/src/index.ts +220 -13
  275. package/src/lint.ts +698 -90
  276. package/src/native-address.ts +340 -0
  277. package/src/native-egress-policy.ts +358 -0
  278. package/src/observability.ts +51 -1
  279. package/src/provider.ts +161 -3
  280. package/src/runtime/auth-flow.ts +12 -0
  281. package/src/runtime/browser.ts +661 -63
  282. package/src/runtime/cache.ts +189 -14
  283. package/src/runtime/choice-wordlist.ts +145 -0
  284. package/src/runtime/choice.ts +631 -120
  285. package/src/runtime/chrome149-header-order.ts +330 -0
  286. package/src/runtime/env.ts +13 -0
  287. package/src/runtime/executor.ts +43 -31
  288. package/src/runtime/http.ts +641 -61
  289. package/src/runtime/insights.ts +2 -2
  290. package/src/runtime/instrumentation.ts +520 -15
  291. package/src/runtime/native-network-errors.ts +99 -0
  292. package/src/runtime/native-network.ts +1605 -0
  293. package/src/runtime/ocr.ts +523 -0
  294. package/src/runtime/otlp.ts +467 -21
  295. package/src/runtime/proxy-errors.ts +12 -4
  296. package/src/runtime/proxy-nodemaven.ts +221 -0
  297. package/src/runtime/proxy-telemetry.ts +244 -75
  298. package/src/runtime/redirects.ts +66 -0
  299. package/src/runtime/redis.ts +7 -2
  300. package/src/runtime/request-options.ts +680 -1
  301. package/src/runtime/resolver-config.ts +6 -0
  302. package/src/runtime/resolver-public.ts +23 -0
  303. package/src/runtime/resolver-shared.ts +33 -0
  304. package/src/runtime/resolver-vendors/bindings.ts +56 -0
  305. package/src/runtime/resolver-vendors/browser.ts +533 -0
  306. package/src/runtime/resolver-vendors/capsolver.ts +707 -0
  307. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  308. package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
  309. package/src/runtime/resolver-vendors/types.ts +212 -0
  310. package/src/runtime/resolver.ts +1157 -0
  311. package/src/runtime/secrets.ts +64 -0
  312. package/src/runtime/state.ts +394 -77
  313. package/src/runtime/stealth-cookies.ts +132 -0
  314. package/src/runtime/stealth.ts +1280 -336
  315. package/src/runtime/stt.ts +1 -19
  316. package/src/runtime/timeout.ts +18 -0
  317. package/src/runtime/trace-config.ts +78 -0
  318. package/src/runtime/trace.ts +57 -17
  319. package/src/serve.ts +6 -1
  320. package/src/server/error-observability.ts +1 -0
  321. package/src/server/index.ts +34 -2
  322. package/src/server/self-test-input-tokens.ts +29 -14
  323. package/src/server/self-test.ts +191 -68
  324. package/src/server/serve-implementation.ts +3378 -0
  325. package/src/server/serve.ts +1 -1632
  326. package/src/server/trace-output.ts +129 -0
  327. package/src/server/types.ts +13 -1
  328. package/src/stateful/README.md +146 -0
  329. package/src/stateful/errors.ts +35 -0
  330. package/src/stateful/http-provider-event-emitter.ts +314 -0
  331. package/src/stateful/http-session-owner-registry.ts +306 -0
  332. package/src/stateful/index.ts +18 -0
  333. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  334. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  335. package/src/stateful/provider-event-pipeline.ts +61 -0
  336. package/src/stateful/provider-events.ts +462 -0
  337. package/src/stateful/session-key.ts +111 -0
  338. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  339. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  340. package/src/stateful/stateful-provider-adapter.ts +562 -0
  341. package/src/stateful/stateful-provider-observability.ts +261 -0
  342. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  343. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  344. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  345. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  346. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  347. package/src/stateful-signing.ts +46 -0
  348. package/src/stealth/profiles.ts +202 -231
  349. package/src/stream-evidence.ts +988 -0
  350. package/src/stream.ts +8 -1
  351. package/src/testing/index.ts +10 -1
  352. package/src/testing/run.ts +658 -15
  353. package/src/trace-sanitization.ts +63 -0
  354. package/src/types.ts +788 -174
  355. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  356. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  357. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  358. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  359. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  360. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  361. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  362. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  363. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  364. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  365. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  366. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  367. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  368. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -1,11 +1,12 @@
1
1
  import { createRequire } from "node:module";
2
- import type { Frame, LaunchOptions, Locator, Page, Request, Route } from "playwright";
2
+ import type { CDPSession, Frame, LaunchOptions, Locator, Page, Request, Route } from "playwright";
3
3
 
4
4
  import { ProviderError } from "../errors.js";
5
5
  import type {
6
6
  BrowserChallengeRequest,
7
7
  BrowserChallengeResult,
8
8
  BrowserClient as BrowserClientContract,
9
+ BrowserCookie,
9
10
  BrowserEngine,
10
11
  BrowserFrame,
11
12
  BrowserLocator,
@@ -42,19 +43,35 @@ type PoolReleaseRequest = {
42
43
  pageId: string;
43
44
  };
44
45
 
45
- type JsonRpcId = number;
46
+ type WebSocketCommandId = number;
46
47
 
47
- type JsonRpcError = {
48
+ type WebSocketCommandError = {
48
49
  code?: number;
49
50
  message?: string;
50
51
  };
51
52
 
52
- type JsonRpcMessage = {
53
- error?: JsonRpcError;
54
- id?: JsonRpcId;
53
+ type WebSocketCommandMessage = {
54
+ error?: WebSocketCommandError;
55
+ id?: WebSocketCommandId;
56
+ jsonrpc?: "2.0";
55
57
  method?: string;
56
58
  params?: Record<string, unknown>;
57
59
  result?: Record<string, unknown>;
60
+ sessionId?: string;
61
+ };
62
+
63
+ type CdpCommandFrame = {
64
+ id: WebSocketCommandId;
65
+ method: string;
66
+ params: Record<string, unknown>;
67
+ sessionId?: string;
68
+ };
69
+
70
+ type JsonRpcCommandFrame = {
71
+ id: WebSocketCommandId;
72
+ jsonrpc: "2.0";
73
+ method: string;
74
+ params: Record<string, unknown>;
58
75
  };
59
76
 
60
77
  type CdpFrameTreeNode = {
@@ -70,13 +87,28 @@ type CdpFrameTreeNode = {
70
87
  type CdpFetchFulfillParams = {
71
88
  readonly requestId: string;
72
89
  readonly responseCode: number;
73
- readonly responseHeaders?: readonly {
90
+ readonly responseHeaders?: {
74
91
  readonly name: string;
75
92
  readonly value: string;
76
93
  }[];
77
94
  readonly body?: string;
78
95
  };
79
96
 
97
+ type CdpPausedDocumentResponse = {
98
+ readonly hasBody: boolean;
99
+ readonly requestId: string;
100
+ readonly responseCode: number;
101
+ readonly responseHeaders: readonly {
102
+ readonly name: string;
103
+ readonly value: string;
104
+ }[];
105
+ };
106
+
107
+ type CdpResponseBody = {
108
+ readonly base64Encoded?: boolean;
109
+ readonly body?: string;
110
+ };
111
+
80
112
  type BrowserPageContract = BrowserPage;
81
113
 
82
114
  function toResourceBody(body: BrowserResourceBody | undefined): Buffer | string | undefined {
@@ -92,7 +124,7 @@ function toResourceBody(body: BrowserResourceBody | undefined): Buffer | string
92
124
  }
93
125
 
94
126
  function isResourceMethod(method: string): method is BrowserResourceMethod {
95
- return method === "GET" || method === "HEAD";
127
+ return method === "GET" || method === "HEAD" || method === "POST";
96
128
  }
97
129
 
98
130
  async function toResourceRequest(request: Request): Promise<BrowserResourceRequest | null> {
@@ -147,6 +179,85 @@ function getCdpPausedRequestId(params: unknown): string | null {
147
179
  return params.requestId;
148
180
  }
149
181
 
182
+ function isCdpResponseStage(params: unknown): boolean {
183
+ return (
184
+ isRecord(params) &&
185
+ (typeof params.responseStatusCode === "number" ||
186
+ typeof params.responseErrorReason === "string")
187
+ );
188
+ }
189
+
190
+ function toCdpPausedDocumentResponse(params: unknown): CdpPausedDocumentResponse | null {
191
+ if (
192
+ !isRecord(params) ||
193
+ typeof params.requestId !== "string" ||
194
+ typeof params.responseStatusCode !== "number" ||
195
+ params.resourceType !== "Document" ||
196
+ !isRecord(params.request)
197
+ ) {
198
+ return null;
199
+ }
200
+
201
+ const responseCode = params.responseStatusCode;
202
+ const responseHeaders = Array.isArray(params.responseHeaders)
203
+ ? params.responseHeaders.flatMap((header) => {
204
+ if (
205
+ !isRecord(header) ||
206
+ typeof header.name !== "string" ||
207
+ typeof header.value !== "string"
208
+ ) {
209
+ return [];
210
+ }
211
+ return [{ name: header.name, value: header.value }];
212
+ })
213
+ : [];
214
+
215
+ return {
216
+ hasBody:
217
+ String(params.request.method ?? "").toUpperCase() !== "HEAD" &&
218
+ responseCode >= 200 &&
219
+ (responseCode < 300 || responseCode >= 400) &&
220
+ responseCode !== 204 &&
221
+ responseCode !== 205 &&
222
+ responseCode !== 304,
223
+ requestId: params.requestId,
224
+ responseCode,
225
+ responseHeaders,
226
+ };
227
+ }
228
+
229
+ function toCdpDocumentFulfillParams(
230
+ response: CdpPausedDocumentResponse,
231
+ body: CdpResponseBody,
232
+ contentSecurityPolicy: string,
233
+ ): CdpFetchFulfillParams {
234
+ const responseBody = typeof body.body === "string" ? body.body : "";
235
+ return {
236
+ body: body.base64Encoded ? responseBody : Buffer.from(responseBody).toString("base64"),
237
+ requestId: response.requestId,
238
+ responseCode: response.responseCode,
239
+ responseHeaders: [
240
+ ...response.responseHeaders,
241
+ { name: "Content-Security-Policy", value: contentSecurityPolicy },
242
+ ],
243
+ };
244
+ }
245
+
246
+ function resourcePolicyFetchPatterns(policy: BrowserResourcePolicy) {
247
+ return [
248
+ { requestStage: "Request" as const, urlPattern: "*" },
249
+ ...(policy.documentContentSecurityPolicy
250
+ ? [
251
+ {
252
+ requestStage: "Response" as const,
253
+ resourceType: "Document" as const,
254
+ urlPattern: "*",
255
+ },
256
+ ]
257
+ : []),
258
+ ];
259
+ }
260
+
150
261
  function toCdpResourceHeaders(value: unknown): Record<string, string> {
151
262
  if (!isRecord(value)) {
152
263
  return {};
@@ -208,11 +319,171 @@ async function fulfillResourceRoute(
208
319
  });
209
320
  }
210
321
 
322
+ async function decideResourceRequest(
323
+ policy: BrowserResourcePolicy,
324
+ request: BrowserResourceRequest,
325
+ ): Promise<BrowserResourceDecision> {
326
+ for (const resourceRoute of policy.routes) {
327
+ if (matchesResourceRoute(resourceRoute.match, request)) {
328
+ return await resourceRoute.handle(request);
329
+ }
330
+ }
331
+
332
+ return { action: "block" };
333
+ }
334
+
335
+ async function abortResourceRoute(route: Route): Promise<void> {
336
+ await route.abort("blockedbyclient");
337
+ }
338
+
339
+ async function handleCdpResourceRequest(
340
+ session: CDPSession,
341
+ policy: BrowserResourcePolicy,
342
+ allowedMethods: ReadonlySet<BrowserResourceMethod>,
343
+ params: unknown,
344
+ ): Promise<void> {
345
+ const paused = toCdpResourceRequest(params);
346
+ const requestId = paused?.requestId ?? getCdpPausedRequestId(params);
347
+ if (!requestId) return;
348
+
349
+ try {
350
+ if (!paused || !allowedMethods.has(paused.request.method)) {
351
+ await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
352
+ return;
353
+ }
354
+
355
+ const decision = await decideResourceRequest(policy, paused.request);
356
+ switch (decision.action) {
357
+ case "continue":
358
+ await session.send("Fetch.continueRequest", { requestId });
359
+ return;
360
+ case "fulfill":
361
+ await session.send("Fetch.fulfillRequest", toCdpFulfillParams(requestId, decision));
362
+ return;
363
+ case "block":
364
+ await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
365
+ return;
366
+ }
367
+ } catch {
368
+ await session
369
+ .send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
370
+ .catch(() => undefined);
371
+ }
372
+ }
373
+
374
+ async function handleCdpDocumentResponse(
375
+ session: CDPSession,
376
+ contentSecurityPolicy: string,
377
+ params: unknown,
378
+ ): Promise<void> {
379
+ const requestId = getCdpPausedRequestId(params);
380
+ if (!requestId) return;
381
+
382
+ try {
383
+ const response = toCdpPausedDocumentResponse(params);
384
+ if (!response) throw new Error("CDP document response metadata is unavailable");
385
+ if (!response.hasBody) {
386
+ await session.send("Fetch.continueResponse", {
387
+ requestId,
388
+ responseCode: response.responseCode,
389
+ responseHeaders: [...response.responseHeaders],
390
+ });
391
+ return;
392
+ }
393
+
394
+ const body = await session.send("Fetch.getResponseBody", {
395
+ requestId: response.requestId,
396
+ });
397
+ await session.send(
398
+ "Fetch.fulfillRequest",
399
+ toCdpDocumentFulfillParams(response, body, contentSecurityPolicy),
400
+ );
401
+ } catch {
402
+ await session
403
+ .send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
404
+ .catch(() => undefined);
405
+ }
406
+ }
407
+
408
+ function getCdpAuthRequiredRequestId(params: unknown): string | null {
409
+ if (!isRecord(params) || typeof params.requestId !== "string") {
410
+ return null;
411
+ }
412
+
413
+ return params.requestId;
414
+ }
415
+
416
+ function isCdpProxyAuthChallenge(params: unknown): boolean {
417
+ return (
418
+ isRecord(params) &&
419
+ isRecord(params.authChallenge) &&
420
+ params.authChallenge.source === "Proxy"
421
+ );
422
+ }
423
+
424
+ async function handleCdpAuthRequired(
425
+ session: CDPSession,
426
+ proxy: PlaywrightProxy & { username: string; password: string },
427
+ authAttempts: Set<string>,
428
+ params: unknown,
429
+ ): Promise<void> {
430
+ const requestId = getCdpAuthRequiredRequestId(params);
431
+ if (!requestId) return;
432
+
433
+ // Chromium can emit another challenge after rejected credentials. Cancel a
434
+ // retry so incorrect credentials fail promptly instead of looping forever.
435
+ const response =
436
+ isCdpProxyAuthChallenge(params) && !authAttempts.has(requestId)
437
+ ? (() => {
438
+ authAttempts.add(requestId);
439
+ return {
440
+ authChallengeResponse: {
441
+ password: proxy.password,
442
+ response: "ProvideCredentials" as const,
443
+ username: proxy.username,
444
+ },
445
+ requestId,
446
+ };
447
+ })()
448
+ : {
449
+ authChallengeResponse: { response: "CancelAuth" as const },
450
+ requestId,
451
+ };
452
+
453
+ await session.send("Fetch.continueWithAuth", response).catch(() => undefined);
454
+ }
455
+
456
+ function createResourcePolicyHandler(
457
+ policy: BrowserResourcePolicy,
458
+ allowedMethods: ReadonlySet<BrowserResourceMethod>,
459
+ ): (route: Route) => Promise<void> {
460
+ return async (route: Route): Promise<void> => {
461
+ const request = await toResourceRequest(route.request());
462
+ if (!request || !allowedMethods.has(request.method)) {
463
+ await abortResourceRoute(route);
464
+ return;
465
+ }
466
+
467
+ const decision = await decideResourceRequest(policy, request);
468
+ switch (decision.action) {
469
+ case "continue":
470
+ await route.continue();
471
+ return;
472
+ case "fulfill":
473
+ await fulfillResourceRoute(route, decision);
474
+ return;
475
+ case "block":
476
+ await abortResourceRoute(route);
477
+ }
478
+ };
479
+ }
480
+
211
481
  export type BrowserClientOptions = BrowserOptions & {
212
482
  allowedHosts?: string[];
213
483
  cdpUrl?: string;
214
484
  executablePath?: string;
215
485
  extraArgs?: string[];
486
+ serviceWorkers?: "allow" | "block";
216
487
  };
217
488
 
218
489
  type SupportedBrowserClient = {
@@ -271,12 +542,70 @@ function formatExpression<T>(fn: string | (() => T)): string {
271
542
  return `(${fn.toString()})()`;
272
543
  }
273
544
 
274
- function toLaunchOptions(options: BrowserClientOptions): LaunchOptions {
545
+ function toLaunchOptions(
546
+ options: BrowserClientOptions,
547
+ proxy: LaunchOptions["proxy"],
548
+ ): LaunchOptions {
275
549
  return {
276
- args: options.extraArgs,
550
+ // `extraArgs` is optional, but playwright-extra's stealth evasions mutate
551
+ // `options.args` unguarded (navigator.webdriver does
552
+ // `options.args.findIndex(...)` in beforeLaunch). Forwarding `undefined`
553
+ // therefore crashes every stealth launch that omits extraArgs with
554
+ // "TypeError: undefined is not an object (evaluating 'options.args.findIndex')".
555
+ // Always hand the launcher a concrete array.
556
+ args: options.extraArgs ?? [],
277
557
  executablePath: options.executablePath,
278
558
  headless: options.headless ?? true,
279
- proxy: options.proxy ? { server: options.proxy } : undefined,
559
+ proxy,
560
+ };
561
+ }
562
+
563
+ type PlaywrightProxy = NonNullable<LaunchOptions["proxy"]>;
564
+
565
+ function hasProxyCredentials(
566
+ proxy: LaunchOptions["proxy"] | undefined,
567
+ ): proxy is PlaywrightProxy & { username: string; password: string } {
568
+ return proxy?.username !== undefined && proxy.password !== undefined;
569
+ }
570
+
571
+ function toPlaywrightProxy(proxy: string | undefined): LaunchOptions["proxy"] {
572
+ if (!proxy) return undefined;
573
+
574
+ const schemeEnd = proxy.indexOf("://");
575
+ const authorityStart = schemeEnd >= 0 ? schemeEnd + 3 : 0;
576
+ const remainder = proxy.slice(authorityStart);
577
+ const authorityEnd = remainder.search(/[/?#]/);
578
+ const authority = remainder.slice(0, authorityEnd >= 0 ? authorityEnd : undefined);
579
+ if (!authority.includes("@")) {
580
+ return { server: proxy };
581
+ }
582
+
583
+ let parsed: URL;
584
+ try {
585
+ parsed = new URL(proxy);
586
+ } catch {
587
+ throw new ProviderError("Browser proxy URL is invalid", {
588
+ code: "BROWSER_PROXY_INVALID",
589
+ fix: "Use a valid proxy URL.",
590
+ });
591
+ }
592
+
593
+ let username: string;
594
+ let password: string;
595
+ try {
596
+ username = decodeURIComponent(parsed.username);
597
+ password = decodeURIComponent(parsed.password);
598
+ } catch {
599
+ throw new ProviderError("Browser proxy credentials use invalid percent-encoding", {
600
+ code: "BROWSER_PROXY_INVALID",
601
+ fix: "Percent-encode the proxy username and password as URL userinfo.",
602
+ });
603
+ }
604
+
605
+ return {
606
+ server: `${parsed.protocol}//${parsed.host}`,
607
+ username,
608
+ password,
280
609
  };
281
610
  }
282
611
 
@@ -470,10 +799,16 @@ class PlaywrightBrowserPage implements BrowserPageContract {
470
799
  readonly id = "main";
471
800
  readonly pageId?: string;
472
801
 
473
- constructor(private readonly page: Page) {}
802
+ constructor(
803
+ private readonly page: Page,
804
+ private readonly proxy: LaunchOptions["proxy"] = undefined,
805
+ ) {}
474
806
 
475
- async goto(url: string): Promise<void> {
476
- await this.page.goto(url);
807
+ async goto(
808
+ url: string,
809
+ options?: { readonly timeout?: number; readonly waitUntil?: "load" | "domcontentloaded" },
810
+ ): Promise<void> {
811
+ await this.page.goto(url, options);
477
812
  }
478
813
 
479
814
  async evaluate<T>(fn: string | (() => T)): Promise<T> {
@@ -484,6 +819,10 @@ class PlaywrightBrowserPage implements BrowserPageContract {
484
819
  return await this.page.evaluate(fn);
485
820
  }
486
821
 
822
+ async userAgent(): Promise<string> {
823
+ return await this.evaluate<string>("navigator.userAgent");
824
+ }
825
+
487
826
  async waitForSelector(selector: string, options?: { timeout?: number }): Promise<void> {
488
827
  await this.page.waitForSelector(selector, options);
489
828
  }
@@ -529,45 +868,108 @@ class PlaywrightBrowserPage implements BrowserPageContract {
529
868
  await this.page.close();
530
869
  }
531
870
 
871
+ async cookies(): Promise<readonly BrowserCookie[]> {
872
+ return (await this.page.context().cookies()).map(toBrowserCookie);
873
+ }
874
+
532
875
  async withResourcePolicy<T>(policy: BrowserResourcePolicy, run: () => Promise<T>): Promise<T> {
533
876
  const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
877
+ const policyHandler = createResourcePolicyHandler(policy, allowedMethods);
878
+ const context = this.page.context();
534
879
  const handler = async (route: Route): Promise<void> => {
535
- const request = await toResourceRequest(route.request());
536
- if (!request || !allowedMethods.has(request.method)) {
537
- await route.abort("blockedbyclient");
880
+ try {
881
+ // Context routing sees a popup's first request before the late `page`
882
+ // event. Fail every secondary page closed so no redirect can escape
883
+ // the per-page CDP interception while that session is being attached.
884
+ if (route.request().frame().page() !== this.page) {
885
+ await abortResourceRoute(route);
886
+ return;
887
+ }
888
+ } catch {
889
+ await abortResourceRoute(route);
538
890
  return;
539
891
  }
540
892
 
541
- for (const resourceRoute of policy.routes) {
542
- if (!matchesResourceRoute(resourceRoute.match, request)) {
543
- continue;
544
- }
893
+ await policyHandler(route);
894
+ };
895
+ const sessions = new Set<CDPSession>();
896
+ const pendingAttachments = new Set<Promise<void>>();
897
+ const proxy = hasProxyCredentials(this.proxy) ? this.proxy : undefined;
898
+ let active = true;
899
+ const attachPage = async (page: Page): Promise<void> => {
900
+ const session = await context.newCDPSession(page);
901
+ if (!active) {
902
+ await session.detach();
903
+ return;
904
+ }
545
905
 
546
- const decision = await resourceRoute.handle(request);
547
- switch (decision.action) {
548
- case "fulfill":
549
- await fulfillResourceRoute(route, decision);
550
- return;
551
- case "block":
552
- await route.abort("blockedbyclient");
553
- return;
906
+ const onRequestPaused = (params: unknown) => {
907
+ if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
908
+ void handleCdpDocumentResponse(session, policy.documentContentSecurityPolicy, params);
909
+ return;
554
910
  }
911
+ void handleCdpResourceRequest(session, policy, allowedMethods, params);
912
+ };
913
+ const authAttempts = new Set<string>();
914
+ const onAuthRequired = (params: unknown) => {
915
+ if (!proxy) return;
916
+ void handleCdpAuthRequired(session, proxy, authAttempts, params);
917
+ };
918
+ session.on("Fetch.requestPaused", onRequestPaused);
919
+ if (proxy) {
920
+ session.on("Fetch.authRequired", onAuthRequired);
555
921
  }
556
-
557
- await route.abort("blockedbyclient");
922
+ try {
923
+ await session.send("Fetch.enable", {
924
+ ...(proxy ? { handleAuthRequests: true } : {}),
925
+ patterns: resourcePolicyFetchPatterns(policy),
926
+ });
927
+ sessions.add(session);
928
+ } catch (error) {
929
+ session.off("Fetch.requestPaused", onRequestPaused);
930
+ if (proxy) {
931
+ session.off("Fetch.authRequired", onAuthRequired);
932
+ }
933
+ await session.detach().catch(() => undefined);
934
+ throw error;
935
+ }
936
+ };
937
+ const onPage = (page: Page) => {
938
+ const attachment = attachPage(page)
939
+ .catch(async () => {
940
+ await page.close().catch(() => undefined);
941
+ })
942
+ .finally(() => pendingAttachments.delete(attachment));
943
+ pendingAttachments.add(attachment);
558
944
  };
559
945
 
560
- await this.page.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
946
+ await context.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
561
947
  try {
562
- return await run();
948
+ context.on("page", onPage);
949
+ try {
950
+ await attachPage(this.page);
951
+ return await run();
952
+ } finally {
953
+ context.off("page", onPage);
954
+ }
563
955
  } finally {
564
- await this.page.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
956
+ active = false;
957
+ await context.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
958
+ await Promise.allSettled(pendingAttachments);
959
+ await Promise.all(
960
+ [...sessions].map(async (session) => {
961
+ await session.send("Fetch.disable").catch(() => undefined);
962
+ await session.detach().catch(() => undefined);
963
+ }),
964
+ );
565
965
  }
566
966
  }
567
967
  }
568
968
 
569
969
  class PlaywrightBrowserClient implements SupportedBrowserClient {
570
970
  private browser: import("playwright").Browser | null = null;
971
+ private parsedProxy: LaunchOptions["proxy"] | undefined;
972
+ private proxyParsed = false;
571
973
  readonly engine = "playwright-stealth" satisfies BrowserEngine;
572
974
 
573
975
  constructor(private readonly options: BrowserClientOptions = {}) {}
@@ -578,7 +980,11 @@ class PlaywrightBrowserClient implements SupportedBrowserClient {
578
980
  }
579
981
 
580
982
  const chromium = await loadChromiumLauncher(this.options);
581
- this.browser = await chromium.launch(toLaunchOptions(this.options));
983
+ if (!this.proxyParsed) {
984
+ this.parsedProxy = toPlaywrightProxy(this.options.proxy);
985
+ this.proxyParsed = true;
986
+ }
987
+ this.browser = await chromium.launch(toLaunchOptions(this.options, this.parsedProxy));
582
988
  return this.browser;
583
989
  }
584
990
 
@@ -586,7 +992,7 @@ class PlaywrightBrowserClient implements SupportedBrowserClient {
586
992
  const browser = await this.ensureBrowser();
587
993
  const page = await browser.newPage();
588
994
 
589
- return new PlaywrightBrowserPage(page);
995
+ return new PlaywrightBrowserPage(page, this.parsedProxy);
590
996
  }
591
997
 
592
998
  async rawPage(): Promise<BrowserPageContract> {
@@ -598,9 +1004,9 @@ class PlaywrightBrowserClient implements SupportedBrowserClient {
598
1004
 
599
1005
  async withIsolatedContext<T>(handler: (page: BrowserPageContract) => Promise<T>): Promise<T> {
600
1006
  const browser = await this.ensureBrowser();
601
- const context = await browser.newContext();
1007
+ const context = await browser.newContext({ serviceWorkers: this.options.serviceWorkers });
602
1008
  const page = await context.newPage();
603
- const browserPage = new PlaywrightBrowserPage(page);
1009
+ const browserPage = new PlaywrightBrowserPage(page, this.parsedProxy);
604
1010
 
605
1011
  try {
606
1012
  return await handler(browserPage);
@@ -641,12 +1047,12 @@ function normalizeWebSocketEndpoint(endpoint: string): string {
641
1047
  throw new Error(`Unsupported WebSocket endpoint protocol: ${url.protocol}`);
642
1048
  }
643
1049
 
644
- class JsonRpcWebSocketClient {
1050
+ abstract class WebSocketCommandClient {
645
1051
  private nextId = 1;
646
1052
  private readonly endpoint: string;
647
1053
  private readonly listeners = new Map<string, Set<(params: unknown) => void>>();
648
1054
  private readonly pending = new Map<
649
- JsonRpcId,
1055
+ WebSocketCommandId,
650
1056
  {
651
1057
  reject: (reason?: unknown) => void;
652
1058
  resolve: (value: Record<string, unknown>) => void;
@@ -682,14 +1088,7 @@ class JsonRpcWebSocketClient {
682
1088
  return await new Promise<Record<string, unknown>>((resolve, reject) => {
683
1089
  this.pending.set(id, { resolve, reject });
684
1090
 
685
- socket.send(
686
- JSON.stringify({
687
- id,
688
- jsonrpc: "2.0",
689
- method,
690
- params,
691
- }),
692
- );
1091
+ socket.send(JSON.stringify(this.createCommandFrame(id, method, params)));
693
1092
  });
694
1093
  }
695
1094
 
@@ -705,6 +1104,12 @@ class JsonRpcWebSocketClient {
705
1104
  this.socketPromise = undefined;
706
1105
  }
707
1106
 
1107
+ protected abstract createCommandFrame(
1108
+ id: WebSocketCommandId,
1109
+ method: string,
1110
+ params: Record<string, unknown>,
1111
+ ): CdpCommandFrame | JsonRpcCommandFrame;
1112
+
708
1113
  private async getSocket(): Promise<WebSocket> {
709
1114
  if (this.socket?.readyState === WebSocket.OPEN) {
710
1115
  return this.socket;
@@ -727,7 +1132,7 @@ class JsonRpcWebSocketClient {
727
1132
  typeof event.data === "string"
728
1133
  ? event.data
729
1134
  : Buffer.from(event.data as ArrayBufferLike).toString("utf8");
730
- const payload = JSON.parse(rawData) as JsonRpcMessage;
1135
+ const payload = JSON.parse(rawData) as WebSocketCommandMessage;
731
1136
 
732
1137
  if (typeof payload.id === "number") {
733
1138
  const pending = this.pending.get(payload.id);
@@ -738,7 +1143,11 @@ class JsonRpcWebSocketClient {
738
1143
  this.pending.delete(payload.id);
739
1144
 
740
1145
  if (payload.error) {
741
- pending.reject(new Error(payload.error.message ?? "JSON-RPC command failed"));
1146
+ const error = new Error(payload.error.message ?? "JSON-RPC command failed");
1147
+ if (typeof payload.error.code === "number") {
1148
+ Object.assign(error, { code: payload.error.code });
1149
+ }
1150
+ pending.reject(error);
742
1151
  return;
743
1152
  }
744
1153
 
@@ -774,6 +1183,38 @@ class JsonRpcWebSocketClient {
774
1183
  }
775
1184
  }
776
1185
 
1186
+ class CdpWebSocketClient extends WebSocketCommandClient {
1187
+ constructor(
1188
+ endpoint: string,
1189
+ private readonly sessionId?: string,
1190
+ ) {
1191
+ super(endpoint);
1192
+ }
1193
+
1194
+ protected createCommandFrame(
1195
+ id: WebSocketCommandId,
1196
+ method: string,
1197
+ params: Record<string, unknown>,
1198
+ ): CdpCommandFrame {
1199
+ return {
1200
+ id,
1201
+ method,
1202
+ params,
1203
+ ...(this.sessionId === undefined ? {} : { sessionId: this.sessionId }),
1204
+ };
1205
+ }
1206
+ }
1207
+
1208
+ class JsonRpcWebSocketClient extends WebSocketCommandClient {
1209
+ protected createCommandFrame(
1210
+ id: WebSocketCommandId,
1211
+ method: string,
1212
+ params: Record<string, unknown>,
1213
+ ): JsonRpcCommandFrame {
1214
+ return { id, jsonrpc: "2.0", method, params };
1215
+ }
1216
+ }
1217
+
777
1218
  function flattenCdpFrameTree(
778
1219
  node: CdpFrameTreeNode | undefined,
779
1220
  out: CdpFrameTreeNode["frame"][] = [],
@@ -794,6 +1235,64 @@ function isRecord(value: unknown): value is Record<string, unknown> {
794
1235
  return value !== null && typeof value === "object" && !Array.isArray(value);
795
1236
  }
796
1237
 
1238
+ function isBrowserCookieSameSite(value: unknown): value is BrowserCookie["sameSite"] {
1239
+ return value === "Strict" || value === "Lax" || value === "None";
1240
+ }
1241
+
1242
+ function toBrowserCookie(cookie: {
1243
+ readonly name: string;
1244
+ readonly value: string;
1245
+ readonly domain: string;
1246
+ readonly path: string;
1247
+ readonly expires?: number;
1248
+ readonly httpOnly: boolean;
1249
+ readonly secure: boolean;
1250
+ readonly sameSite?: unknown;
1251
+ }): BrowserCookie {
1252
+ return {
1253
+ name: cookie.name,
1254
+ value: cookie.value,
1255
+ domain: cookie.domain,
1256
+ path: cookie.path,
1257
+ ...(cookie.expires !== undefined && cookie.expires > 0 ? { expires: cookie.expires } : {}),
1258
+ httpOnly: cookie.httpOnly,
1259
+ secure: cookie.secure,
1260
+ ...(isBrowserCookieSameSite(cookie.sameSite) ? { sameSite: cookie.sameSite } : {}),
1261
+ };
1262
+ }
1263
+
1264
+ function parseCdpCookies(value: unknown): readonly BrowserCookie[] {
1265
+ if (!Array.isArray(value)) {
1266
+ throw new Error("CDP Network.getCookies returned an invalid cookie list");
1267
+ }
1268
+
1269
+ return value.map((cookie) => {
1270
+ if (
1271
+ !isRecord(cookie) ||
1272
+ typeof cookie.name !== "string" ||
1273
+ typeof cookie.value !== "string" ||
1274
+ typeof cookie.domain !== "string" ||
1275
+ typeof cookie.path !== "string" ||
1276
+ typeof cookie.expires !== "number" ||
1277
+ typeof cookie.httpOnly !== "boolean" ||
1278
+ typeof cookie.secure !== "boolean"
1279
+ ) {
1280
+ throw new Error("CDP Network.getCookies returned an invalid cookie");
1281
+ }
1282
+
1283
+ return toBrowserCookie({
1284
+ name: cookie.name,
1285
+ value: cookie.value,
1286
+ domain: cookie.domain,
1287
+ path: cookie.path,
1288
+ expires: cookie.expires,
1289
+ httpOnly: cookie.httpOnly,
1290
+ secure: cookie.secure,
1291
+ sameSite: cookie.sameSite,
1292
+ });
1293
+ });
1294
+ }
1295
+
797
1296
  function parsePoolAcquireResponse(value: unknown): PoolAcquireResponse {
798
1297
  if (
799
1298
  !isRecord(value) ||
@@ -861,6 +1360,20 @@ function getCdpExecutionContext(params: unknown): {
861
1360
  };
862
1361
  }
863
1362
 
1363
+ function getCdpDestroyedExecutionContextId(params: unknown): number | undefined {
1364
+ if (!isRecord(params)) {
1365
+ return undefined;
1366
+ }
1367
+
1368
+ return typeof params.executionContextId === "number"
1369
+ ? params.executionContextId
1370
+ : undefined;
1371
+ }
1372
+
1373
+ function isMissingExecutionContextError(error: unknown): boolean {
1374
+ return error instanceof Error && /\b(?:cannot|failed to) find context\b/i.test(error.message);
1375
+ }
1376
+
864
1377
  class CdpBrowserLocator implements BrowserLocator {
865
1378
  constructor(
866
1379
  private readonly frame: {
@@ -974,7 +1487,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
974
1487
  constructor(
975
1488
  readonly pageId: string,
976
1489
  private readonly browserContextId: string | undefined,
977
- private readonly pageClient: JsonRpcWebSocketClient,
1490
+ private readonly pageClient: CdpWebSocketClient,
978
1491
  private readonly release: (request: PoolReleaseRequest) => Promise<void>,
979
1492
  ) {}
980
1493
 
@@ -982,17 +1495,33 @@ class CdpPoolBrowserPage implements BrowserPageContract {
982
1495
  return this.pageId;
983
1496
  }
984
1497
 
985
- async goto(url: string): Promise<void> {
1498
+ async goto(
1499
+ url: string,
1500
+ options?: { readonly timeout?: number; readonly waitUntil?: "load" | "domcontentloaded" },
1501
+ ): Promise<void> {
986
1502
  await this.initialize();
987
1503
  const startedAt = Date.now();
988
- let loadEventSeen = false;
989
- const unsubscribe = this.pageClient.on("Page.loadEventFired", () => {
990
- loadEventSeen = true;
1504
+ const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
1505
+ const waitUntil = options?.waitUntil ?? "load";
1506
+ let expectedEventSeen = false;
1507
+ const eventName =
1508
+ waitUntil === "domcontentloaded" ? "Page.domContentEventFired" : "Page.loadEventFired";
1509
+ const unsubscribe = this.pageClient.on(eventName, () => {
1510
+ expectedEventSeen = true;
991
1511
  });
992
1512
 
993
1513
  try {
994
- await this.pageClient.send("Page.navigate", { url });
995
- await this.waitForDocumentReady(startedAt + DEFAULT_WAIT_TIMEOUT_MS, () => loadEventSeen);
1514
+ const navigation = (await this.pageClient.send("Page.navigate", { url })) as {
1515
+ readonly errorText?: unknown;
1516
+ };
1517
+ if (typeof navigation.errorText === "string" && navigation.errorText.length > 0) {
1518
+ throw new Error(`Page.navigate failed: ${navigation.errorText} at ${url}`);
1519
+ }
1520
+ await this.waitForDocumentReady(
1521
+ startedAt + timeout,
1522
+ () => expectedEventSeen,
1523
+ waitUntil,
1524
+ );
996
1525
  } finally {
997
1526
  unsubscribe();
998
1527
  }
@@ -1003,10 +1532,27 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1003
1532
  return await this.evaluateWithContext<T>(fn);
1004
1533
  }
1005
1534
 
1535
+ async userAgent(): Promise<string> {
1536
+ return await this.evaluate<string>("navigator.userAgent");
1537
+ }
1538
+
1006
1539
  async evaluateInFrame<T>(frameId: string, fn: string | (() => T)): Promise<T> {
1007
1540
  await this.initialize();
1008
1541
  const contextId = await this.getFrameExecutionContextId(frameId);
1009
- return await this.evaluateWithContext<T>(fn, contextId);
1542
+ try {
1543
+ return await this.evaluateWithContext<T>(fn, contextId);
1544
+ } catch (error) {
1545
+ if (!isMissingExecutionContextError(error)) {
1546
+ throw error;
1547
+ }
1548
+
1549
+ if (this.frameExecutionContexts.get(frameId) === contextId) {
1550
+ this.frameExecutionContexts.delete(frameId);
1551
+ }
1552
+
1553
+ const refreshedContextId = await this.getFrameExecutionContextId(frameId);
1554
+ return await this.evaluateWithContext<T>(fn, refreshedContextId);
1555
+ }
1010
1556
  }
1011
1557
 
1012
1558
  async waitForSelectorInFrame(
@@ -1149,6 +1695,12 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1149
1695
  return Buffer.from(String(result.data ?? ""), "base64");
1150
1696
  }
1151
1697
 
1698
+ async cookies(): Promise<readonly BrowserCookie[]> {
1699
+ await this.initialize();
1700
+ const result = await this.pageClient.send("Network.getCookies");
1701
+ return parseCdpCookies(result.cookies);
1702
+ }
1703
+
1152
1704
  async close(): Promise<void> {
1153
1705
  if (this.closed) {
1154
1706
  return;
@@ -1176,7 +1728,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1176
1728
 
1177
1729
  try {
1178
1730
  await this.pageClient.send("Fetch.enable", {
1179
- patterns: [{ requestStage: "Request", urlPattern: "*" }],
1731
+ patterns: resourcePolicyFetchPatterns(policy),
1180
1732
  });
1181
1733
  } catch (error) {
1182
1734
  unsubscribe();
@@ -1209,6 +1761,28 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1209
1761
  }
1210
1762
 
1211
1763
  try {
1764
+ if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
1765
+ const response = toCdpPausedDocumentResponse(params);
1766
+ if (!response) throw new Error("CDP document response metadata is unavailable");
1767
+ if (!response.hasBody) {
1768
+ await this.pageClient.send("Fetch.continueResponse", {
1769
+ requestId,
1770
+ responseCode: response.responseCode,
1771
+ responseHeaders: [...response.responseHeaders],
1772
+ });
1773
+ return;
1774
+ }
1775
+
1776
+ const body = await this.pageClient.send("Fetch.getResponseBody", {
1777
+ requestId: response.requestId,
1778
+ });
1779
+ await this.pageClient.send(
1780
+ "Fetch.fulfillRequest",
1781
+ toCdpDocumentFulfillParams(response, body, policy.documentContentSecurityPolicy),
1782
+ );
1783
+ return;
1784
+ }
1785
+
1212
1786
  const parsed = toCdpResourceRequest(params);
1213
1787
  if (!parsed || !allowedMethods.has(parsed.request.method)) {
1214
1788
  await this.failCdpResourceRequest(requestId);
@@ -1222,6 +1796,11 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1222
1796
 
1223
1797
  const decision = await resourceRoute.handle(parsed.request);
1224
1798
  switch (decision.action) {
1799
+ case "continue":
1800
+ await this.pageClient.send("Fetch.continueRequest", {
1801
+ requestId: parsed.requestId,
1802
+ });
1803
+ return;
1225
1804
  case "fulfill":
1226
1805
  await this.pageClient.send(
1227
1806
  "Fetch.fulfillRequest",
@@ -1264,6 +1843,21 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1264
1843
  this.frameExecutionContexts.set(context.frameId, context.id);
1265
1844
  }
1266
1845
  });
1846
+ this.pageClient.on("Runtime.executionContextDestroyed", (params) => {
1847
+ const destroyedContextId = getCdpDestroyedExecutionContextId(params);
1848
+ if (destroyedContextId === undefined) {
1849
+ return;
1850
+ }
1851
+
1852
+ for (const [frameId, contextId] of this.frameExecutionContexts) {
1853
+ if (contextId === destroyedContextId) {
1854
+ this.frameExecutionContexts.delete(frameId);
1855
+ }
1856
+ }
1857
+ });
1858
+ this.pageClient.on("Runtime.executionContextsCleared", () => {
1859
+ this.frameExecutionContexts.clear();
1860
+ });
1267
1861
  await this.pageClient.send("Page.enable");
1268
1862
  await this.pageClient.send("Runtime.enable");
1269
1863
  this.initialized = true;
@@ -1291,12 +1885,16 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1291
1885
 
1292
1886
  private async waitForDocumentReady(
1293
1887
  deadline: number,
1294
- isLoadEventSeen: () => boolean,
1888
+ isExpectedEventSeen: () => boolean,
1889
+ waitUntil: "load" | "domcontentloaded",
1295
1890
  ): Promise<void> {
1296
1891
  while (Date.now() < deadline) {
1297
1892
  const readyState = await this.evaluate<string>("document.readyState");
1298
- if (readyState === "complete" || readyState === "interactive") {
1299
- if (isLoadEventSeen() || readyState === "complete") {
1893
+ const documentReady =
1894
+ readyState === "complete" ||
1895
+ (waitUntil === "domcontentloaded" && readyState === "interactive");
1896
+ if (documentReady) {
1897
+ if (isExpectedEventSeen() || readyState === "complete") {
1300
1898
  return;
1301
1899
  }
1302
1900
  }
@@ -1335,7 +1933,7 @@ class CdpPoolBrowserClient implements SupportedBrowserClient {
1335
1933
  ...(options?.isolatedContext ? { isolationMode: "browserContext" } : {}),
1336
1934
  }),
1337
1935
  );
1338
- const pageClient = new JsonRpcWebSocketClient(acquireResult.wsEndpoint);
1936
+ const pageClient = new CdpWebSocketClient(acquireResult.wsEndpoint);
1339
1937
  const page = new CdpPoolBrowserPage(
1340
1938
  acquireResult.pageId,
1341
1939
  acquireResult.browserContextId,