@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
@@ -0,0 +1,221 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+
3
+ import type { ProviderProxyPolicy } from "../types.js";
4
+
5
+ export const NODEMAVEN_USERNAME_ENV = "APIFUSE__PROXY__NODEMAVEN_USERNAME";
6
+ export const NODEMAVEN_PASSWORD_ENV = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
7
+ export const NODEMAVEN_FILTER_ENV = "APIFUSE__PROXY__NODEMAVEN_FILTER";
8
+
9
+ export const NODEMAVEN_GATEWAY_HOST = "gate.nodemaven.com";
10
+
11
+ export type NodemavenCredentials = {
12
+ readonly username: string;
13
+ readonly password: string;
14
+ readonly filter?: string;
15
+ };
16
+
17
+ /** Both schemes tunnel bytes end-to-end, preserving the client TLS handshake. */
18
+ export type ProxyProtocol = "http" | "socks5";
19
+
20
+ /**
21
+ * NodeMaven's fastest protocol: HTTP CONNECT. Benchmarks (KR, cold + warm)
22
+ * showed socks5 through the gateway adds ~500ms per request over http, so
23
+ * NodeMaven never defaults to socks5.
24
+ */
25
+ export const NODEMAVEN_DEFAULT_PROTOCOL: ProxyProtocol = "http";
26
+
27
+ /** NodeMaven gateway port ranges per protocol (docs: HTTP 8080-9080, SOCKS5 1080-2080). */
28
+ const NODEMAVEN_PORTS: Record<ProxyProtocol, { min: number; max: number }> = {
29
+ http: { min: 8080, max: 9080 },
30
+ socks5: { min: 1080, max: 2080 },
31
+ };
32
+
33
+ const NODEMAVEN_FILTERS = new Set(["medium", "high"]);
34
+ const DEFAULT_NODEMAVEN_FILTER = "medium";
35
+ const DEFAULT_NODEMAVEN_POOL_SIZE = 20;
36
+ export const NODEMAVEN_MAX_POOL_SIZE = 50;
37
+ /** NodeMaven sticky sessions persist up to 24h server-side, keyed by the sid. */
38
+ const NODEMAVEN_MAX_LIFETIME_MINUTES = 1440;
39
+ const SID_LENGTH = 10;
40
+
41
+ export function hasNodemavenCredentials(): boolean {
42
+ return Boolean(readNodemavenUsername() && readNodemavenPassword());
43
+ }
44
+
45
+ function readNodemavenUsername(): string | undefined {
46
+ return process.env[NODEMAVEN_USERNAME_ENV]?.trim() || undefined;
47
+ }
48
+
49
+ function readNodemavenPassword(): string | undefined {
50
+ return process.env[NODEMAVEN_PASSWORD_ENV]?.trim() || undefined;
51
+ }
52
+
53
+ function resolveNodemavenFilter(value: string | undefined): string {
54
+ const raw = value?.trim().toLowerCase();
55
+ if (!raw) return DEFAULT_NODEMAVEN_FILTER;
56
+ if (!NODEMAVEN_FILTERS.has(raw)) {
57
+ throw new Error(`${NODEMAVEN_FILTER_ENV} must be "medium" or "high"`);
58
+ }
59
+ return raw;
60
+ }
61
+
62
+ export function nodemavenPoolSize(policy: ProviderProxyPolicy): number {
63
+ return Math.min(
64
+ NODEMAVEN_MAX_POOL_SIZE,
65
+ Math.max(1, Math.floor(policy.session?.poolSize ?? DEFAULT_NODEMAVEN_POOL_SIZE)),
66
+ );
67
+ }
68
+
69
+ export function nodemavenLifetimeMinutes(policy: ProviderProxyPolicy): number {
70
+ const configured = policy.session?.lifetimeMinutes;
71
+ if (typeof configured !== "number" || !Number.isFinite(configured) || configured <= 0) {
72
+ return NODEMAVEN_MAX_LIFETIME_MINUTES;
73
+ }
74
+ return Math.min(NODEMAVEN_MAX_LIFETIME_MINUTES, Math.max(1, Math.floor(configured)));
75
+ }
76
+
77
+ export type NodemavenSessionWindow = {
78
+ refreshEpoch: number;
79
+ expiresAt: string;
80
+ };
81
+
82
+ /** Stable wall-clock window shared by every process deriving the same sticky sid. */
83
+ export function nodemavenSessionWindow(
84
+ policy: ProviderProxyPolicy,
85
+ now = Date.now(),
86
+ ): NodemavenSessionWindow {
87
+ const lifetimeMs = nodemavenLifetimeMinutes(policy) * 60_000;
88
+ const refreshEpoch = Math.floor(now / lifetimeMs);
89
+ return {
90
+ refreshEpoch,
91
+ expiresAt: new Date((refreshEpoch + 1) * lifetimeMs).toISOString(),
92
+ };
93
+ }
94
+
95
+ /** NodeMaven username tokens accept `[a-z0-9]`; slugify geo values to that set. */
96
+ function slugifyGeo(value: string | undefined): string | undefined {
97
+ if (!value) return undefined;
98
+ const slug = value
99
+ .trim()
100
+ .toLowerCase()
101
+ .replace(/[^a-z0-9]+/g, "");
102
+ return slug || undefined;
103
+ }
104
+
105
+ function isStickyAffinity(policy: ProviderProxyPolicy): boolean {
106
+ return (policy.session?.affinity ?? "request") !== "request";
107
+ }
108
+
109
+ /**
110
+ * A sticky sid is deterministic from the affinity key so every process serving
111
+ * the same connection derives the same egress IP without shared storage. A
112
+ * rotating sid is random per call (a fresh egress IP per request).
113
+ */
114
+ function deriveSid(
115
+ policy: ProviderProxyPolicy,
116
+ affinityKey: string | undefined,
117
+ poolIndex: number,
118
+ refreshEpoch: number,
119
+ ): string {
120
+ if (!isStickyAffinity(policy) || !affinityKey) {
121
+ return randomBytes(SID_LENGTH).toString("hex").slice(0, SID_LENGTH);
122
+ }
123
+ const digest = createHash("sha256")
124
+ .update(`${affinityKey}:${refreshEpoch}:${poolIndex}`)
125
+ .digest("hex");
126
+ // hex digits are a subset of the allowed [a-z0-9] sid charset.
127
+ return digest.slice(0, SID_LENGTH);
128
+ }
129
+
130
+ function selectPort(protocol: ProxyProtocol, sid: string, poolIndex: number): number {
131
+ const { min, max } = NODEMAVEN_PORTS[protocol];
132
+ const span = max - min + 1;
133
+ const hashInt = Number.parseInt(
134
+ createHash("sha256").update(`${sid}:${poolIndex}`).digest("hex").slice(0, 8),
135
+ 16,
136
+ );
137
+ return min + (hashInt % span);
138
+ }
139
+
140
+ export type NodemavenSynthesisInput = {
141
+ policy: ProviderProxyPolicy;
142
+ /** Explicit credentials; synthesis never reads process-global state. */
143
+ credentials: NodemavenCredentials;
144
+ affinityKey: string | undefined;
145
+ protocol: ProxyProtocol;
146
+ poolIndex: number;
147
+ refreshEpoch: number;
148
+ /** ISO 3166-1 alpha-2, already resolved by the caller (falls back to env). */
149
+ country?: string;
150
+ /** Clock injection used by native sticky-expiry lifecycle tests. */
151
+ now?: number;
152
+ /** Stable caller-owned hard expiry when the refresh epoch is wall-clock-derived. */
153
+ expiresAt?: string;
154
+ };
155
+
156
+ export type NodemavenSynthesis = {
157
+ url: string;
158
+ protocol: ProxyProtocol;
159
+ sticky: boolean;
160
+ sessionId: string;
161
+ expiresAt?: string;
162
+ diagnostics: Record<string, string | number | boolean>;
163
+ };
164
+
165
+ /**
166
+ * Synthesize a NodeMaven gateway proxy URL locally from static credentials.
167
+ * There is no allocation API — geo/session are encoded in the username.
168
+ */
169
+ export function synthesizeNodemavenProxy(input: NodemavenSynthesisInput): NodemavenSynthesis {
170
+ const username = input.credentials.username.trim();
171
+ const password = input.credentials.password.trim();
172
+ if (!username || !password) {
173
+ throw new Error(
174
+ `NodeMaven credentials missing: set ${NODEMAVEN_USERNAME_ENV} and ${NODEMAVEN_PASSWORD_ENV}.`,
175
+ );
176
+ }
177
+
178
+ const filter = resolveNodemavenFilter(input.credentials.filter);
179
+ const sid = deriveSid(input.policy, input.affinityKey, input.poolIndex, input.refreshEpoch);
180
+ const port = selectPort(input.protocol, sid, input.poolIndex);
181
+ const lifetimeMinutes = nodemavenLifetimeMinutes(input.policy);
182
+ const sticky = isStickyAffinity(input.policy);
183
+
184
+ const country = slugifyGeo(input.country ?? input.policy.geo?.country);
185
+ const region = slugifyGeo(input.policy.geo?.subdivision);
186
+ const city = slugifyGeo(input.policy.geo?.city);
187
+
188
+ const tokens = [username];
189
+ if (country) tokens.push("country", country);
190
+ if (region) tokens.push("region", region);
191
+ if (city) tokens.push("city", city);
192
+ tokens.push("sid", sid);
193
+ tokens.push("filter", filter);
194
+ tokens.push("ipv4", "true");
195
+ const proxyUsername = tokens.join("-");
196
+
197
+ // Username tokens are [a-z0-9-] only, which survive URL encoding unchanged.
198
+ const url = `${input.protocol}://${proxyUsername}:${encodeURIComponent(password)}@${NODEMAVEN_GATEWAY_HOST}:${port}`;
199
+
200
+ return {
201
+ url,
202
+ protocol: input.protocol,
203
+ sticky,
204
+ sessionId: sid,
205
+ ...(sticky
206
+ ? {
207
+ expiresAt:
208
+ input.expiresAt ??
209
+ new Date((input.now ?? Date.now()) + lifetimeMinutes * 60_000).toISOString(),
210
+ }
211
+ : {}),
212
+ diagnostics: {
213
+ vendor: "nodemaven",
214
+ protocol: input.protocol,
215
+ sticky,
216
+ filter,
217
+ lifetimeMinutes,
218
+ ...(country ? { country } : {}),
219
+ },
220
+ };
221
+ }
@@ -1,48 +1,107 @@
1
1
  import type {
2
2
  ProxyAttemptTelemetryEvent,
3
3
  ProxyCacheStatus,
4
+ ProxyProtocol,
4
5
  ProxyResolutionTelemetryEvent,
5
6
  ProxyTelemetrySink,
7
+ ProxyUserAgentSource,
8
+ ProxyVendorFailoverTelemetryEvent,
9
+ ProxyVendorName,
6
10
  SmartproxyAllocatorBodyClass,
7
11
  } from "../config/loader.js";
8
12
 
9
13
  export const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
10
14
 
11
- type ProviderTelemetryHeader = {
12
- v: 1;
13
- proxy?: {
14
- provider: "smartproxy";
15
- cacheStatus: ProxyCacheStatus;
16
- cacheHit: boolean;
17
- resolutionMs: number;
18
- allocatorMs?: number;
19
- allocatorStatus?: number;
20
- allocatorBodyClass?: SmartproxyAllocatorBodyClass;
21
- allocatorAttempts?: number;
22
- lockWaitMs?: number;
23
- redisReadMs?: number;
24
- redisWriteMs?: number;
25
- poolAgeMs?: number;
26
- poolExpiresInMs?: number;
27
- attempts: number;
28
- refreshes?: number;
29
- attemptSamples?: CompactProxyAttemptSample[];
30
- };
15
+ export type ProxyTelemetryResolvedPayload = {
16
+ kind: "resolved";
17
+ provider: ProxyVendorName;
18
+ userAgentSource?: ProxyUserAgentSource;
19
+ protocol?: ProxyProtocol;
20
+ cacheStatus: ProxyCacheStatus;
21
+ cacheHit: boolean;
22
+ resolutionMs: number;
23
+ allocatorMs?: number;
24
+ allocatorStatus?: number;
25
+ allocatorBodyClass?: SmartproxyAllocatorBodyClass;
26
+ allocatorAttempts?: number;
27
+ lockWaitMs?: number;
28
+ redisReadMs?: number;
29
+ redisWriteMs?: number;
30
+ poolAgeMs?: number;
31
+ poolExpiresInMs?: number;
32
+ attempts: number;
33
+ refreshes?: number;
34
+ attemptSamples?: {
35
+ n: number;
36
+ a: number;
37
+ i?: number;
38
+ h?: string;
39
+ o: ProxyAttemptTelemetryEvent["outcome"];
40
+ c?: string;
41
+ s?: number;
42
+ d?: number;
43
+ }[];
44
+ /** Distinct vendors attempted across the resolution chain, in order seen. */
45
+ vendors?: ProxyVendorName[];
46
+ /** Cross-vendor failover events (bounded). */
47
+ failovers?: {
48
+ v: ProxyVendorName;
49
+ nx?: ProxyVendorName;
50
+ p: ProxyVendorFailoverTelemetryEvent["phase"];
51
+ r: ProxyVendorFailoverTelemetryEvent["reason"];
52
+ a?: number;
53
+ }[];
54
+ };
55
+
56
+ export type ProxyTelemetryUnresolvedPayload = {
57
+ kind: "unresolved";
58
+ /** Distinct vendors that failed resolution or recorded a failover, in order seen. */
59
+ vendors: ProxyVendorName[];
60
+ cacheStatus?: ProxyCacheStatus;
61
+ cacheHit?: boolean;
62
+ resolutionMs?: number;
63
+ allocatorMs?: number;
64
+ allocatorStatus?: number;
65
+ allocatorBodyClass?: SmartproxyAllocatorBodyClass;
66
+ allocatorAttempts?: number;
67
+ lockWaitMs?: number;
68
+ redisReadMs?: number;
69
+ redisWriteMs?: number;
70
+ poolAgeMs?: number;
71
+ poolExpiresInMs?: number;
72
+ attempts?: number;
73
+ refreshes?: number;
74
+ failovers?: {
75
+ v: ProxyVendorName;
76
+ nx?: ProxyVendorName;
77
+ p: ProxyVendorFailoverTelemetryEvent["phase"];
78
+ r: ProxyVendorFailoverTelemetryEvent["reason"];
79
+ a?: number;
80
+ }[];
81
+ attemptSamples?: {
82
+ n: number;
83
+ a: number;
84
+ i?: number;
85
+ h?: string;
86
+ o: ProxyAttemptTelemetryEvent["outcome"];
87
+ c?: string;
88
+ s?: number;
89
+ d?: number;
90
+ }[];
31
91
  };
32
92
 
33
- type CompactProxyAttemptSample = {
34
- n: number;
35
- a: number;
36
- i?: number;
37
- h?: string;
38
- o: "ok" | "error";
39
- c?: string;
40
- s?: number;
41
- d?: number;
93
+ export type ProxyTelemetryLogPayload =
94
+ | ProxyTelemetryResolvedPayload
95
+ | ProxyTelemetryUnresolvedPayload;
96
+
97
+ type ProviderTelemetryHeader = {
98
+ v: 1;
99
+ proxy?: ProxyTelemetryLogPayload;
42
100
  };
43
101
 
44
102
  const MAX_HEADER_BYTES = 4_096;
45
103
  const MAX_PROXY_ATTEMPT_SAMPLES = 24;
104
+ const MAX_PROXY_FAILOVER_SAMPLES = 12;
46
105
 
47
106
  const CACHE_STATUS_SEVERITY: Record<ProxyCacheStatus, number> = {
48
107
  disabled: 0,
@@ -76,10 +135,16 @@ function encodeBase64Url(value: string): string {
76
135
  export class ProxyTelemetryCollector implements ProxyTelemetrySink {
77
136
  #events: ProxyResolutionTelemetryEvent[] = [];
78
137
  #attempts: ProxyAttemptTelemetryEvent[] = [];
138
+ #failovers: ProxyVendorFailoverTelemetryEvent[] = [];
139
+ #unresolvedVendors: ProxyVendorName[] = [];
79
140
 
80
141
  recordProxyResolution(event: ProxyResolutionTelemetryEvent): void {
142
+ const outcome = event.outcome === "error" ? "error" : "ok";
81
143
  this.#events.push({
82
- provider: "smartproxy",
144
+ provider: event.provider,
145
+ outcome,
146
+ ...(event.userAgentSource ? { userAgentSource: event.userAgentSource } : {}),
147
+ ...(event.protocol ? { protocol: event.protocol } : {}),
83
148
  cacheStatus: event.cacheStatus,
84
149
  cacheHit: event.cacheHit,
85
150
  resolutionMs: Math.max(0, Math.floor(event.resolutionMs)),
@@ -110,12 +175,29 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
110
175
  refreshes:
111
176
  event.refreshes === undefined ? undefined : Math.max(0, Math.floor(event.refreshes)),
112
177
  });
178
+ if (outcome === "error" && !this.#unresolvedVendors.includes(event.provider)) {
179
+ this.#unresolvedVendors.push(event.provider);
180
+ }
181
+ }
182
+
183
+ recordProxyVendorFailover(event: ProxyVendorFailoverTelemetryEvent): void {
184
+ if (this.#failovers.length >= MAX_PROXY_FAILOVER_SAMPLES) return;
185
+ this.#failovers.push({
186
+ vendor: event.vendor,
187
+ ...(event.nextVendor ? { nextVendor: event.nextVendor } : {}),
188
+ phase: event.phase,
189
+ reason: event.reason,
190
+ ...(event.attempt === undefined ? {} : { attempt: Math.max(0, Math.floor(event.attempt)) }),
191
+ });
192
+ if (!this.#unresolvedVendors.includes(event.vendor)) {
193
+ this.#unresolvedVendors.push(event.vendor);
194
+ }
113
195
  }
114
196
 
115
197
  recordProxyAttempt(event: ProxyAttemptTelemetryEvent): void {
116
198
  if (this.#attempts.length >= MAX_PROXY_ATTEMPT_SAMPLES) return;
117
199
  this.#attempts.push({
118
- provider: "smartproxy",
200
+ provider: event.provider,
119
201
  attempt: Math.max(1, Math.floor(event.attempt || 1)),
120
202
  ...(event.poolIndex === undefined
121
203
  ? {}
@@ -130,13 +212,105 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
130
212
  });
131
213
  }
132
214
 
133
- toHeaderValue(): string | undefined {
215
+ toLogPayload(): ProxyTelemetryLogPayload | undefined {
134
216
  const [first, ...rest] = this.#events;
135
- if (!first) return undefined;
217
+ const okEvents = this.#events.filter((event) => event.outcome !== "error");
218
+ const attemptSamples = this.#attempts.map((attempt, index) => ({
219
+ n: index + 1,
220
+ a: attempt.attempt,
221
+ ...(attempt.poolIndex === undefined ? {} : { i: attempt.poolIndex }),
222
+ ...(attempt.proxyHash ? { h: attempt.proxyHash } : {}),
223
+ o: attempt.outcome,
224
+ ...(attempt.errorCode ? { c: attempt.errorCode } : {}),
225
+ ...(attempt.status === undefined ? {} : { s: attempt.status }),
226
+ ...(attempt.durationMs === undefined ? {} : { d: attempt.durationMs }),
227
+ }));
228
+ const failovers = this.#failovers.map((failover) => ({
229
+ v: failover.vendor,
230
+ ...(failover.nextVendor ? { nx: failover.nextVendor } : {}),
231
+ p: failover.phase,
232
+ r: failover.reason,
233
+ ...(failover.attempt === undefined ? {} : { a: failover.attempt }),
234
+ }));
235
+
236
+ if (okEvents.length === 0) {
237
+ if (!first && failovers.length === 0) return undefined;
238
+ const failureEvents = this.#events.filter((event) => event.outcome === "error");
239
+ const [firstFailure, ...remainingFailures] = failureEvents;
240
+ const aggregate = firstFailure
241
+ ? remainingFailures.reduce<ProxyResolutionTelemetryEvent>(
242
+ (acc, event) => ({
243
+ provider: event.provider,
244
+ outcome: "error",
245
+ cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
246
+ cacheHit: acc.cacheHit && event.cacheHit,
247
+ resolutionMs: acc.resolutionMs + event.resolutionMs,
248
+ allocatorMs: sumOptional(acc.allocatorMs, event.allocatorMs),
249
+ allocatorStatus: event.allocatorStatus ?? acc.allocatorStatus,
250
+ allocatorBodyClass: event.allocatorBodyClass ?? acc.allocatorBodyClass,
251
+ allocatorAttempts: sumOptional(acc.allocatorAttempts, event.allocatorAttempts),
252
+ lockWaitMs: sumOptional(acc.lockWaitMs, event.lockWaitMs),
253
+ redisReadMs: sumOptional(acc.redisReadMs, event.redisReadMs),
254
+ redisWriteMs: sumOptional(acc.redisWriteMs, event.redisWriteMs),
255
+ poolAgeMs: maxOptional(acc.poolAgeMs, event.poolAgeMs),
256
+ poolExpiresInMs: maxOptional(acc.poolExpiresInMs, event.poolExpiresInMs),
257
+ attempts: acc.attempts + event.attempts,
258
+ refreshes: sumOptional(acc.refreshes, event.refreshes),
259
+ }),
260
+ firstFailure,
261
+ )
262
+ : undefined;
263
+ return {
264
+ kind: "unresolved",
265
+ vendors: [...this.#unresolvedVendors],
266
+ ...(aggregate
267
+ ? {
268
+ cacheStatus: aggregate.cacheStatus,
269
+ cacheHit: aggregate.cacheHit,
270
+ resolutionMs: aggregate.resolutionMs,
271
+ ...(aggregate.allocatorMs !== undefined
272
+ ? { allocatorMs: aggregate.allocatorMs }
273
+ : {}),
274
+ ...(aggregate.allocatorStatus !== undefined
275
+ ? { allocatorStatus: aggregate.allocatorStatus }
276
+ : {}),
277
+ ...(aggregate.allocatorBodyClass !== undefined
278
+ ? { allocatorBodyClass: aggregate.allocatorBodyClass }
279
+ : {}),
280
+ ...(aggregate.allocatorAttempts !== undefined
281
+ ? { allocatorAttempts: aggregate.allocatorAttempts }
282
+ : {}),
283
+ ...(aggregate.lockWaitMs !== undefined ? { lockWaitMs: aggregate.lockWaitMs } : {}),
284
+ ...(aggregate.redisReadMs !== undefined
285
+ ? { redisReadMs: aggregate.redisReadMs }
286
+ : {}),
287
+ ...(aggregate.redisWriteMs !== undefined
288
+ ? { redisWriteMs: aggregate.redisWriteMs }
289
+ : {}),
290
+ ...(aggregate.poolAgeMs !== undefined ? { poolAgeMs: aggregate.poolAgeMs } : {}),
291
+ ...(aggregate.poolExpiresInMs !== undefined
292
+ ? { poolExpiresInMs: aggregate.poolExpiresInMs }
293
+ : {}),
294
+ attempts: aggregate.attempts,
295
+ ...(aggregate.refreshes !== undefined ? { refreshes: aggregate.refreshes } : {}),
296
+ }
297
+ : {}),
298
+ ...(failovers.length > 0 ? { failovers } : {}),
299
+ ...(attemptSamples.length > 0 ? { attemptSamples } : {}),
300
+ };
301
+ }
302
+
303
+ // The serving vendor/protocol is the last successful resolution.
304
+ const serving = okEvents[okEvents.length - 1] ?? first;
305
+ const vendors: ProxyVendorName[] = [];
306
+ for (const event of this.#events) {
307
+ if (!vendors.includes(event.provider)) vendors.push(event.provider);
308
+ }
136
309
 
137
310
  const aggregate = rest.reduce<ProxyResolutionTelemetryEvent>(
138
311
  (acc, event) => ({
139
- provider: "smartproxy",
312
+ provider: event.provider,
313
+ userAgentSource: event.userAgentSource ?? acc.userAgentSource,
140
314
  cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
141
315
  cacheHit: acc.cacheHit && event.cacheHit,
142
316
  resolutionMs: acc.resolutionMs + event.resolutionMs,
@@ -154,48 +328,43 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
154
328
  }),
155
329
  first,
156
330
  );
157
- const payload: ProviderTelemetryHeader = {
158
- v: 1,
159
- proxy: {
160
- provider: "smartproxy",
161
- cacheStatus: aggregate.cacheStatus,
162
- cacheHit: aggregate.cacheHit,
163
- resolutionMs: aggregate.resolutionMs,
164
- ...(aggregate.allocatorMs !== undefined ? { allocatorMs: aggregate.allocatorMs } : {}),
165
- ...(aggregate.allocatorStatus !== undefined
166
- ? { allocatorStatus: aggregate.allocatorStatus }
167
- : {}),
168
- ...(aggregate.allocatorBodyClass !== undefined
169
- ? { allocatorBodyClass: aggregate.allocatorBodyClass }
170
- : {}),
171
- ...(aggregate.allocatorAttempts !== undefined
172
- ? { allocatorAttempts: aggregate.allocatorAttempts }
173
- : {}),
174
- ...(aggregate.lockWaitMs !== undefined ? { lockWaitMs: aggregate.lockWaitMs } : {}),
175
- ...(aggregate.redisReadMs !== undefined ? { redisReadMs: aggregate.redisReadMs } : {}),
176
- ...(aggregate.redisWriteMs !== undefined ? { redisWriteMs: aggregate.redisWriteMs } : {}),
177
- ...(aggregate.poolAgeMs !== undefined ? { poolAgeMs: aggregate.poolAgeMs } : {}),
178
- ...(aggregate.poolExpiresInMs !== undefined
179
- ? { poolExpiresInMs: aggregate.poolExpiresInMs }
180
- : {}),
181
- attempts: aggregate.attempts,
182
- ...(aggregate.refreshes !== undefined ? { refreshes: aggregate.refreshes } : {}),
183
- ...(this.#attempts.length > 0
184
- ? {
185
- attemptSamples: this.#attempts.map((attempt, index) => ({
186
- n: index + 1,
187
- a: attempt.attempt,
188
- ...(attempt.poolIndex === undefined ? {} : { i: attempt.poolIndex }),
189
- ...(attempt.proxyHash ? { h: attempt.proxyHash } : {}),
190
- o: attempt.outcome,
191
- ...(attempt.errorCode ? { c: attempt.errorCode } : {}),
192
- ...(attempt.status === undefined ? {} : { s: attempt.status }),
193
- ...(attempt.durationMs === undefined ? {} : { d: attempt.durationMs }),
194
- })),
195
- }
196
- : {}),
197
- },
331
+ return {
332
+ kind: "resolved",
333
+ provider: serving.provider,
334
+ ...(aggregate.userAgentSource ? { userAgentSource: aggregate.userAgentSource } : {}),
335
+ ...(serving.protocol ? { protocol: serving.protocol } : {}),
336
+ cacheStatus: aggregate.cacheStatus,
337
+ cacheHit: aggregate.cacheHit,
338
+ resolutionMs: aggregate.resolutionMs,
339
+ ...(aggregate.allocatorMs !== undefined ? { allocatorMs: aggregate.allocatorMs } : {}),
340
+ ...(aggregate.allocatorStatus !== undefined
341
+ ? { allocatorStatus: aggregate.allocatorStatus }
342
+ : {}),
343
+ ...(aggregate.allocatorBodyClass !== undefined
344
+ ? { allocatorBodyClass: aggregate.allocatorBodyClass }
345
+ : {}),
346
+ ...(aggregate.allocatorAttempts !== undefined
347
+ ? { allocatorAttempts: aggregate.allocatorAttempts }
348
+ : {}),
349
+ ...(aggregate.lockWaitMs !== undefined ? { lockWaitMs: aggregate.lockWaitMs } : {}),
350
+ ...(aggregate.redisReadMs !== undefined ? { redisReadMs: aggregate.redisReadMs } : {}),
351
+ ...(aggregate.redisWriteMs !== undefined ? { redisWriteMs: aggregate.redisWriteMs } : {}),
352
+ ...(aggregate.poolAgeMs !== undefined ? { poolAgeMs: aggregate.poolAgeMs } : {}),
353
+ ...(aggregate.poolExpiresInMs !== undefined
354
+ ? { poolExpiresInMs: aggregate.poolExpiresInMs }
355
+ : {}),
356
+ attempts: aggregate.attempts,
357
+ ...(aggregate.refreshes !== undefined ? { refreshes: aggregate.refreshes } : {}),
358
+ ...(attemptSamples.length > 0 ? { attemptSamples } : {}),
359
+ ...(vendors.length > 1 ? { vendors } : {}),
360
+ ...(failovers.length > 0 ? { failovers } : {}),
198
361
  };
362
+ }
363
+
364
+ toHeaderValue(): string | undefined {
365
+ const proxy = this.toLogPayload();
366
+ if (!proxy) return undefined;
367
+ const payload: ProviderTelemetryHeader = { v: 1, proxy };
199
368
 
200
369
  const encoded = encodeBase64Url(JSON.stringify(payload));
201
370
  if (encoded.length > MAX_HEADER_BYTES) return undefined;
@@ -0,0 +1,66 @@
1
+ import type { HttpRedirectFailureReason } from "../types.js";
2
+
3
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
4
+
5
+ export type RedirectHopDecision<TMethod extends string> =
6
+ | {
7
+ kind: "follow";
8
+ nextMethod: TMethod | "GET";
9
+ nextUrl: string;
10
+ }
11
+ | {
12
+ kind: "stop";
13
+ reason: HttpRedirectFailureReason;
14
+ nextUrl?: string;
15
+ };
16
+
17
+ export function isRedirectStatus(status: number): boolean {
18
+ return REDIRECT_STATUSES.has(status);
19
+ }
20
+
21
+ /** Shared fetch-compatible redirect method rewriting for stealth and ctx.http. */
22
+ export function nextRedirectMethod<TMethod extends string>(
23
+ status: number,
24
+ method: TMethod,
25
+ ): TMethod | "GET" {
26
+ if (status === 303 && method !== "HEAD") return "GET";
27
+ if ((status === 301 || status === 302) && method === "POST") return "GET";
28
+ return method;
29
+ }
30
+
31
+ /** Resolves a Location value against the response URL without issuing a request. */
32
+ export function resolveRedirectUrl(
33
+ location: string | undefined,
34
+ responseUrl: string,
35
+ ): string | undefined {
36
+ return location ? new URL(location, responseUrl).toString() : undefined;
37
+ }
38
+
39
+ /**
40
+ * Shared post-response decision ordering for both redirect walkers. The
41
+ * caller-owned stop hook is checked first, matching stealth's pre-follow
42
+ * contract, then structural termination and loop checks run before follow.
43
+ */
44
+ export function evaluateRedirectHop<TMethod extends string>(input: {
45
+ status: number;
46
+ method: TMethod;
47
+ nextUrl: string | undefined;
48
+ shouldStop: boolean;
49
+ redirectCount: number;
50
+ maxHops: number;
51
+ visitedRequests: ReadonlySet<string>;
52
+ }): RedirectHopDecision<TMethod> {
53
+ if (input.shouldStop) {
54
+ return { kind: "stop", reason: "stopped", ...(input.nextUrl ? { nextUrl: input.nextUrl } : {}) };
55
+ }
56
+ if (!input.nextUrl) return { kind: "stop", reason: "missing_location" };
57
+ if (input.redirectCount > input.maxHops) {
58
+ return { kind: "stop", reason: "max_hops", nextUrl: input.nextUrl };
59
+ }
60
+
61
+ const nextMethod = nextRedirectMethod(input.status, input.method);
62
+ if (input.visitedRequests.has(`${nextMethod} ${input.nextUrl}`)) {
63
+ return { kind: "stop", reason: "loop", nextUrl: input.nextUrl };
64
+ }
65
+ return { kind: "follow", nextMethod, nextUrl: input.nextUrl };
66
+ }
@@ -1,4 +1,8 @@
1
- import { Redis } from "ioredis";
1
+ import { createRequire } from "node:module";
2
+
3
+ import type { Redis } from "ioredis";
4
+
5
+ const require = createRequire(import.meta.url);
2
6
 
3
7
  export type ProviderRedisClient = Redis;
4
8
 
@@ -17,7 +21,8 @@ type RedisTimeoutOptions<T> = {
17
21
  export function createProviderRedisClient(
18
22
  options: ProviderRedisClientOptions,
19
23
  ): ProviderRedisClient {
20
- const redis = new Redis(options.redisUrl, {
24
+ const { Redis: RedisClient } = require("ioredis") as typeof import("ioredis");
25
+ const redis = new RedisClient(options.redisUrl, {
21
26
  connectTimeout: options.timeoutMs,
22
27
  enableOfflineQueue: false,
23
28
  lazyConnect: true,