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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/AUTHORING.md +943 -51
  2. package/CHANGELOG.md +273 -1
  3. package/LICENSE +21 -0
  4. package/README.md +117 -29
  5. package/SUBMISSION.md +3 -2
  6. package/bin/apifuse-check.ts +293 -13
  7. package/bin/apifuse-dev.ts +42 -19
  8. package/bin/apifuse-migrate-deployment.ts +200 -0
  9. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  10. package/bin/apifuse-migrate-shape.ts +202 -0
  11. package/bin/apifuse-pack-check.ts +19 -6
  12. package/bin/apifuse-pack-smoke.ts +57 -2
  13. package/bin/apifuse-pack-types.ts +736 -38
  14. package/bin/apifuse-perf.ts +59 -23
  15. package/bin/apifuse-record.ts +923 -94
  16. package/bin/apifuse-submit-check.ts +2313 -281
  17. package/bin/apifuse-test.ts +3 -1
  18. package/bin/submit-check-delimited-text.ts +50 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth-turn/index.js +1 -1
  21. package/dist/auth.d.ts +14 -0
  22. package/dist/auth.js +67 -0
  23. package/dist/ceremonies/index.d.ts +16 -0
  24. package/dist/ceremonies/index.js +141 -36
  25. package/dist/cli/commands.d.ts +1 -1
  26. package/dist/cli/commands.js +30 -0
  27. package/dist/cli/create.d.ts +1 -1
  28. package/dist/cli/create.js +8 -2
  29. package/dist/cli/deployment-intent.d.ts +178 -0
  30. package/dist/cli/deployment-intent.js +277 -0
  31. package/dist/cli/local-connection.d.ts +111 -0
  32. package/dist/cli/local-connection.js +151 -0
  33. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  34. package/dist/cli/migrate-deployment-intent.js +1552 -0
  35. package/dist/cli/migrate-operation-declaration.d.ts +70 -0
  36. package/dist/cli/migrate-operation-declaration.js +2206 -0
  37. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  38. package/dist/cli/migrate-operation-shape.js +113 -0
  39. package/dist/cli/migrate-provider-shape.d.ts +56 -0
  40. package/dist/cli/migrate-provider-shape.js +580 -0
  41. package/dist/cli/resolver-runtime.d.ts +21 -0
  42. package/dist/cli/resolver-runtime.js +31 -0
  43. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  44. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  45. package/dist/cli/templates/provider/README.md.tpl +41 -19
  46. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  47. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  48. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  49. package/dist/config/loader.d.ts +202 -16
  50. package/dist/config/loader.js +491 -141
  51. package/dist/contract-serialization.js +4 -8
  52. package/dist/contract-types.d.ts +12 -5
  53. package/dist/contract.js +24 -10
  54. package/dist/declaration-validation.d.ts +34 -0
  55. package/dist/declaration-validation.js +305 -0
  56. package/dist/define.d.ts +71 -29
  57. package/dist/define.js +807 -69
  58. package/dist/dev.d.ts +5 -0
  59. package/dist/dev.js +8 -1
  60. package/dist/engine.d.ts +129 -0
  61. package/dist/engine.js +209 -0
  62. package/dist/error-observability.d.ts +7 -0
  63. package/dist/error-observability.js +61 -0
  64. package/dist/error-resolution.d.ts +18 -0
  65. package/dist/error-resolution.js +243 -0
  66. package/dist/errors.d.ts +74 -0
  67. package/dist/errors.js +99 -0
  68. package/dist/fixture-sanitization.d.ts +28 -0
  69. package/dist/fixture-sanitization.js +371 -0
  70. package/dist/handle-meta.d.ts +90 -0
  71. package/dist/handle-meta.js +73 -0
  72. package/dist/handle.d.ts +226 -0
  73. package/dist/handle.js +345 -0
  74. package/dist/health-scenario.d.ts +1901 -0
  75. package/dist/health-scenario.js +653 -0
  76. package/dist/i18n/catalog.js +21 -1
  77. package/dist/i18n/error-messages.d.ts +86 -0
  78. package/dist/i18n/error-messages.js +192 -0
  79. package/dist/i18n/index.d.ts +1 -0
  80. package/dist/i18n/index.js +1 -0
  81. package/dist/i18n/keys.d.ts +12 -0
  82. package/dist/i18n/keys.js +76 -5
  83. package/dist/i18n/operation-locale-namespace.d.ts +2 -0
  84. package/dist/i18n/operation-locale-namespace.js +7 -0
  85. package/dist/index.d.ts +40 -15
  86. package/dist/index.js +29 -11
  87. package/dist/lint.d.ts +43 -11
  88. package/dist/lint.js +2512 -177
  89. package/dist/native-address.d.ts +43 -0
  90. package/dist/native-address.js +281 -0
  91. package/dist/native-egress-policy.d.ts +31 -0
  92. package/dist/native-egress-policy.js +288 -0
  93. package/dist/observability.d.ts +5 -2
  94. package/dist/observability.js +48 -1
  95. package/dist/provider.d.ts +18 -8
  96. package/dist/provider.js +11 -6
  97. package/dist/runtime/auth-flow.d.ts +6 -1
  98. package/dist/runtime/auth-flow.js +8 -0
  99. package/dist/runtime/bounded-expiring-map.d.ts +21 -0
  100. package/dist/runtime/bounded-expiring-map.js +64 -0
  101. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  102. package/dist/runtime/browser-telemetry-binding.js +83 -0
  103. package/dist/runtime/browser-telemetry.d.ts +76 -0
  104. package/dist/runtime/browser-telemetry.js +165 -0
  105. package/dist/runtime/browser.d.ts +4 -0
  106. package/dist/runtime/browser.js +688 -83
  107. package/dist/runtime/cache-telemetry.d.ts +71 -0
  108. package/dist/runtime/cache-telemetry.js +177 -0
  109. package/dist/runtime/cache.d.ts +3 -0
  110. package/dist/runtime/cache.js +245 -23
  111. package/dist/runtime/capability-response.d.ts +3 -0
  112. package/dist/runtime/capability-response.js +56 -0
  113. package/dist/runtime/capability-telemetry.d.ts +46 -0
  114. package/dist/runtime/capability-telemetry.js +68 -0
  115. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  116. package/dist/runtime/chrome149-header-order.js +289 -0
  117. package/dist/runtime/diagnostic-env-values.d.ts +5 -0
  118. package/dist/runtime/diagnostic-env-values.js +57 -0
  119. package/dist/runtime/diagnostic-env.d.ts +14 -0
  120. package/dist/runtime/diagnostic-env.js +44 -0
  121. package/dist/runtime/diagnostic-redactor.d.ts +54 -0
  122. package/dist/runtime/diagnostic-redactor.js +571 -0
  123. package/dist/runtime/egress-lease.d.ts +65 -0
  124. package/dist/runtime/egress-lease.js +236 -0
  125. package/dist/runtime/engine-mode.d.ts +74 -0
  126. package/dist/runtime/engine-mode.js +152 -0
  127. package/dist/runtime/env.js +14 -1
  128. package/dist/runtime/executor.d.ts +3 -2
  129. package/dist/runtime/executor.js +23 -26
  130. package/dist/runtime/handle-wordlist.d.ts +43 -0
  131. package/dist/runtime/handle-wordlist.js +256 -0
  132. package/dist/runtime/handle.d.ts +71 -0
  133. package/dist/runtime/handle.js +908 -0
  134. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  135. package/dist/runtime/http-telemetry-binding.js +152 -0
  136. package/dist/runtime/http-telemetry-guard.d.ts +16 -0
  137. package/dist/runtime/http-telemetry-guard.js +202 -0
  138. package/dist/runtime/http-telemetry.d.ts +104 -0
  139. package/dist/runtime/http-telemetry.js +190 -0
  140. package/dist/runtime/http.d.ts +34 -0
  141. package/dist/runtime/http.js +680 -121
  142. package/dist/runtime/insights.js +2 -2
  143. package/dist/runtime/instrumentation.d.ts +2 -2
  144. package/dist/runtime/instrumentation.js +462 -19
  145. package/dist/runtime/native-network-errors.d.ts +33 -0
  146. package/dist/runtime/native-network-errors.js +69 -0
  147. package/dist/runtime/native-network.d.ts +105 -0
  148. package/dist/runtime/native-network.js +1433 -0
  149. package/dist/runtime/native-telemetry.d.ts +142 -0
  150. package/dist/runtime/native-telemetry.js +231 -0
  151. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  152. package/dist/runtime/ocr-telemetry.js +192 -0
  153. package/dist/runtime/ocr.d.ts +29 -0
  154. package/dist/runtime/ocr.js +468 -0
  155. package/dist/runtime/otlp.d.ts +71 -2
  156. package/dist/runtime/otlp.js +398 -16
  157. package/dist/runtime/provider.d.ts +7 -0
  158. package/dist/runtime/provider.js +14 -0
  159. package/dist/runtime/proxy-errors.js +6 -2
  160. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  161. package/dist/runtime/proxy-nodemaven.js +147 -0
  162. package/dist/runtime/proxy-retry-policy.d.ts +4 -1
  163. package/dist/runtime/proxy-retry-policy.js +11 -2
  164. package/dist/runtime/proxy-telemetry.d.ts +133 -3
  165. package/dist/runtime/proxy-telemetry.js +218 -70
  166. package/dist/runtime/redirect-hop-headers.d.ts +19 -0
  167. package/dist/runtime/redirect-hop-headers.js +58 -0
  168. package/dist/runtime/redirects.d.ts +61 -0
  169. package/dist/runtime/redirects.js +149 -0
  170. package/dist/runtime/redis.d.ts +1 -1
  171. package/dist/runtime/redis.js +4 -2
  172. package/dist/runtime/request-options.d.ts +75 -1
  173. package/dist/runtime/request-options.js +588 -0
  174. package/dist/runtime/request-telemetry.d.ts +70 -0
  175. package/dist/runtime/request-telemetry.js +229 -0
  176. package/dist/runtime/resolver-config.d.ts +13 -0
  177. package/dist/runtime/resolver-config.js +14 -0
  178. package/dist/runtime/resolver-public.d.ts +3 -0
  179. package/dist/runtime/resolver-public.js +1 -0
  180. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  181. package/dist/runtime/resolver-runtime-options.js +33 -0
  182. package/dist/runtime/resolver-shared.d.ts +24 -0
  183. package/dist/runtime/resolver-shared.js +69 -0
  184. package/dist/runtime/resolver-span-attributes.d.ts +34 -0
  185. package/dist/runtime/resolver-span-attributes.js +93 -0
  186. package/dist/runtime/resolver-telemetry.d.ts +135 -0
  187. package/dist/runtime/resolver-telemetry.js +204 -0
  188. package/dist/runtime/resolver-usage.d.ts +32 -0
  189. package/dist/runtime/resolver-usage.js +54 -0
  190. package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
  191. package/dist/runtime/resolver-vendors/bindings.js +45 -0
  192. package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
  193. package/dist/runtime/resolver-vendors/browser.js +423 -0
  194. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  195. package/dist/runtime/resolver-vendors/capsolver.js +544 -0
  196. package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
  197. package/dist/runtime/resolver-vendors/hosts.js +68 -0
  198. package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
  199. package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
  200. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  201. package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
  202. package/dist/runtime/resolver-vendors/types.d.ts +125 -0
  203. package/dist/runtime/resolver-vendors/types.js +106 -0
  204. package/dist/runtime/resolver.d.ts +79 -0
  205. package/dist/runtime/resolver.js +1094 -0
  206. package/dist/runtime/state-telemetry.d.ts +79 -0
  207. package/dist/runtime/state-telemetry.js +194 -0
  208. package/dist/runtime/state.d.ts +10 -1
  209. package/dist/runtime/state.js +322 -82
  210. package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
  211. package/dist/runtime/stealth-akamai-sbsd.js +142 -0
  212. package/dist/runtime/stealth-cookies.d.ts +24 -0
  213. package/dist/runtime/stealth-cookies.js +131 -0
  214. package/dist/runtime/stealth-owned-headers.d.ts +39 -0
  215. package/dist/runtime/stealth-owned-headers.js +57 -0
  216. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  217. package/dist/runtime/stealth-telemetry.js +179 -0
  218. package/dist/runtime/stealth.d.ts +45 -6
  219. package/dist/runtime/stealth.js +1775 -320
  220. package/dist/runtime/stt-telemetry.d.ts +77 -0
  221. package/dist/runtime/stt-telemetry.js +176 -0
  222. package/dist/runtime/stt.js +18 -19
  223. package/dist/runtime/timeout.d.ts +5 -0
  224. package/dist/runtime/timeout.js +12 -0
  225. package/dist/runtime/trace-config.d.ts +12 -0
  226. package/dist/runtime/trace-config.js +62 -0
  227. package/dist/runtime/trace.d.ts +17 -0
  228. package/dist/runtime/trace.js +176 -25
  229. package/dist/runtime-boundary-lint.d.ts +137 -0
  230. package/dist/runtime-boundary-lint.js +590 -0
  231. package/dist/schema.d.ts +20 -0
  232. package/dist/schema.js +72 -2
  233. package/dist/serve.d.ts +1 -1
  234. package/dist/serve.js +1 -1
  235. package/dist/server/error-observability.d.ts +1 -0
  236. package/dist/server/error-observability.js +1 -0
  237. package/dist/server/error-stack-frames.d.ts +35 -0
  238. package/dist/server/error-stack-frames.js +219 -0
  239. package/dist/server/index.d.ts +21 -2
  240. package/dist/server/index.js +11 -2
  241. package/dist/server/runtime-diagnostics.d.ts +165 -0
  242. package/dist/server/runtime-diagnostics.js +360 -0
  243. package/dist/server/sdk-version.d.ts +3 -0
  244. package/dist/server/sdk-version.js +19 -0
  245. package/dist/server/self-test-input-tokens.d.ts +2 -1
  246. package/dist/server/self-test-input-tokens.js +18 -14
  247. package/dist/server/self-test-redaction.js +8 -33
  248. package/dist/server/self-test.d.ts +18 -3
  249. package/dist/server/self-test.js +139 -76
  250. package/dist/server/sensitive-values.d.ts +26 -0
  251. package/dist/server/sensitive-values.js +137 -0
  252. package/dist/server/serve-implementation.d.ts +285 -0
  253. package/dist/server/serve-implementation.js +3172 -0
  254. package/dist/server/serve.d.ts +1 -75
  255. package/dist/server/serve.js +1 -1170
  256. package/dist/server/state-backend-report.d.ts +70 -0
  257. package/dist/server/state-backend-report.js +113 -0
  258. package/dist/server/trace-output.d.ts +7 -0
  259. package/dist/server/trace-output.js +111 -0
  260. package/dist/server/types.d.ts +37 -5
  261. package/dist/server/types.js +34 -1
  262. package/dist/stateful/errors.d.ts +19 -0
  263. package/dist/stateful/errors.js +24 -0
  264. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  265. package/dist/stateful/http-provider-event-emitter.js +237 -0
  266. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  267. package/dist/stateful/http-session-owner-registry.js +210 -0
  268. package/dist/stateful/index.d.ts +18 -0
  269. package/dist/stateful/index.js +18 -0
  270. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  271. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  272. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  273. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  274. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  275. package/dist/stateful/provider-event-pipeline.js +1 -0
  276. package/dist/stateful/provider-events.d.ts +101 -0
  277. package/dist/stateful/provider-events.js +289 -0
  278. package/dist/stateful/session-key.d.ts +15 -0
  279. package/dist/stateful/session-key.js +86 -0
  280. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  281. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  282. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  283. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  284. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  285. package/dist/stateful/stateful-provider-adapter.js +287 -0
  286. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  287. package/dist/stateful/stateful-provider-observability.js +161 -0
  288. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  289. package/dist/stateful/stateful-provider-owner-forwarder.js +237 -0
  290. package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
  291. package/dist/stateful/stateful-provider-runtime-context.js +61 -0
  292. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  293. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  294. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  295. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  296. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  297. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  298. package/dist/stateful-signing.d.ts +18 -0
  299. package/dist/stateful-signing.js +27 -0
  300. package/dist/stealth/profiles.d.ts +15 -4
  301. package/dist/stealth/profiles.js +169 -211
  302. package/dist/stream-evidence.d.ts +74 -0
  303. package/dist/stream-evidence.js +785 -0
  304. package/dist/stream.js +7 -1
  305. package/dist/testing/index.d.ts +2 -1
  306. package/dist/testing/index.js +2 -1
  307. package/dist/testing/run.d.ts +32 -2
  308. package/dist/testing/run.js +509 -25
  309. package/dist/trace-sanitization.d.ts +6 -0
  310. package/dist/trace-sanitization.js +68 -0
  311. package/dist/types.d.ts +899 -210
  312. package/dist/types.js +1 -0
  313. package/dist/typescript-module.d.ts +39 -0
  314. package/dist/typescript-module.js +72 -0
  315. package/package.json +52 -6
  316. package/src/auth-turn/index.ts +1 -1
  317. package/src/auth.ts +118 -0
  318. package/src/ceremonies/index.ts +189 -46
  319. package/src/cli/commands.ts +37 -0
  320. package/src/cli/create.ts +8 -2
  321. package/src/cli/deployment-intent.ts +364 -0
  322. package/src/cli/local-connection.ts +221 -0
  323. package/src/cli/migrate-deployment-intent.ts +1908 -0
  324. package/src/cli/migrate-operation-declaration.ts +3026 -0
  325. package/src/cli/migrate-operation-shape.ts +184 -0
  326. package/src/cli/migrate-provider-shape.ts +778 -0
  327. package/src/cli/resolver-runtime.ts +72 -0
  328. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  329. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  330. package/src/cli/templates/provider/README.md.tpl +41 -19
  331. package/src/cli/templates/provider/index.ts.tpl +6 -3
  332. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  333. package/src/cli/templates/provider/provider.json.tpl +6 -0
  334. package/src/config/loader.ts +774 -181
  335. package/src/contract-serialization.ts +5 -7
  336. package/src/contract-types.ts +12 -5
  337. package/src/contract.ts +24 -10
  338. package/src/declaration-validation.ts +376 -0
  339. package/src/define.ts +1105 -131
  340. package/src/dev.ts +13 -1
  341. package/src/engine.ts +390 -0
  342. package/src/error-observability.ts +64 -0
  343. package/src/error-resolution.ts +269 -0
  344. package/src/errors.ts +157 -0
  345. package/src/fixture-sanitization.ts +422 -0
  346. package/src/handle-meta.ts +143 -0
  347. package/src/handle.ts +694 -0
  348. package/src/health-scenario.ts +960 -0
  349. package/src/i18n/catalog.ts +23 -1
  350. package/src/i18n/error-messages.ts +255 -0
  351. package/src/i18n/index.ts +1 -0
  352. package/src/i18n/keys.ts +80 -6
  353. package/src/i18n/operation-locale-namespace.ts +10 -0
  354. package/src/index.ts +390 -23
  355. package/src/lint.ts +2975 -234
  356. package/src/native-address.ts +340 -0
  357. package/src/native-egress-policy.ts +358 -0
  358. package/src/observability.ts +51 -1
  359. package/src/provider.ts +207 -27
  360. package/src/runtime/auth-flow.ts +15 -0
  361. package/src/runtime/bounded-expiring-map.ts +67 -0
  362. package/src/runtime/browser-telemetry-binding.ts +92 -0
  363. package/src/runtime/browser-telemetry.ts +289 -0
  364. package/src/runtime/browser.ts +882 -99
  365. package/src/runtime/cache-telemetry.ts +247 -0
  366. package/src/runtime/cache.ts +258 -22
  367. package/src/runtime/capability-response.ts +53 -0
  368. package/src/runtime/capability-telemetry.ts +101 -0
  369. package/src/runtime/chrome149-header-order.ts +330 -0
  370. package/src/runtime/diagnostic-env-values.ts +56 -0
  371. package/src/runtime/diagnostic-env.ts +65 -0
  372. package/src/runtime/diagnostic-redactor.ts +668 -0
  373. package/src/runtime/egress-lease.ts +341 -0
  374. package/src/runtime/engine-mode.ts +233 -0
  375. package/src/runtime/env.ts +15 -1
  376. package/src/runtime/executor.ts +39 -32
  377. package/src/runtime/handle-wordlist.ts +266 -0
  378. package/src/runtime/handle.ts +1180 -0
  379. package/src/runtime/http-telemetry-binding.ts +162 -0
  380. package/src/runtime/http-telemetry-guard.ts +208 -0
  381. package/src/runtime/http-telemetry.ts +292 -0
  382. package/src/runtime/http.ts +896 -150
  383. package/src/runtime/insights.ts +2 -2
  384. package/src/runtime/instrumentation.ts +628 -28
  385. package/src/runtime/native-network-errors.ts +99 -0
  386. package/src/runtime/native-network.ts +1853 -0
  387. package/src/runtime/native-telemetry.ts +366 -0
  388. package/src/runtime/ocr-telemetry.ts +308 -0
  389. package/src/runtime/ocr.ts +564 -0
  390. package/src/runtime/otlp.ts +468 -21
  391. package/src/runtime/provider.ts +15 -0
  392. package/src/runtime/proxy-errors.ts +12 -4
  393. package/src/runtime/proxy-nodemaven.ts +222 -0
  394. package/src/runtime/proxy-retry-policy.ts +12 -1
  395. package/src/runtime/proxy-telemetry.ts +358 -103
  396. package/src/runtime/redirect-hop-headers.ts +71 -0
  397. package/src/runtime/redirects.ts +215 -0
  398. package/src/runtime/redis.ts +7 -2
  399. package/src/runtime/request-options.ts +728 -1
  400. package/src/runtime/request-telemetry.ts +375 -0
  401. package/src/runtime/resolver-config.ts +18 -0
  402. package/src/runtime/resolver-public.ts +44 -0
  403. package/src/runtime/resolver-runtime-options.ts +50 -0
  404. package/src/runtime/resolver-shared.ts +113 -0
  405. package/src/runtime/resolver-span-attributes.ts +139 -0
  406. package/src/runtime/resolver-telemetry.ts +362 -0
  407. package/src/runtime/resolver-usage.ts +99 -0
  408. package/src/runtime/resolver-vendors/bindings.ts +61 -0
  409. package/src/runtime/resolver-vendors/browser.ts +591 -0
  410. package/src/runtime/resolver-vendors/capsolver.ts +727 -0
  411. package/src/runtime/resolver-vendors/hosts.ts +81 -0
  412. package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
  413. package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
  414. package/src/runtime/resolver-vendors/types.ts +261 -0
  415. package/src/runtime/resolver.ts +1543 -0
  416. package/src/runtime/state-telemetry.ts +298 -0
  417. package/src/runtime/state.ts +435 -89
  418. package/src/runtime/stealth-akamai-sbsd.ts +207 -0
  419. package/src/runtime/stealth-cookies.ts +155 -0
  420. package/src/runtime/stealth-owned-headers.ts +67 -0
  421. package/src/runtime/stealth-telemetry.ts +309 -0
  422. package/src/runtime/stealth.ts +2275 -372
  423. package/src/runtime/stt-telemetry.ts +282 -0
  424. package/src/runtime/stt.ts +96 -91
  425. package/src/runtime/timeout.ts +18 -0
  426. package/src/runtime/trace-config.ts +78 -0
  427. package/src/runtime/trace.ts +257 -35
  428. package/src/runtime-boundary-lint.ts +707 -0
  429. package/src/schema.ts +80 -2
  430. package/src/serve.ts +6 -1
  431. package/src/server/error-observability.ts +1 -0
  432. package/src/server/error-stack-frames.ts +214 -0
  433. package/src/server/index.ts +184 -2
  434. package/src/server/runtime-diagnostics.ts +522 -0
  435. package/src/server/sdk-version.ts +22 -0
  436. package/src/server/self-test-input-tokens.ts +29 -14
  437. package/src/server/self-test-redaction.ts +10 -40
  438. package/src/server/self-test.ts +205 -89
  439. package/src/server/sensitive-values.ts +211 -0
  440. package/src/server/serve-implementation.ts +4596 -0
  441. package/src/server/serve.ts +1 -1685
  442. package/src/server/state-backend-report.ts +185 -0
  443. package/src/server/trace-output.ts +189 -0
  444. package/src/server/types.ts +34 -1
  445. package/src/stateful/README.md +146 -0
  446. package/src/stateful/errors.ts +35 -0
  447. package/src/stateful/http-provider-event-emitter.ts +314 -0
  448. package/src/stateful/http-session-owner-registry.ts +306 -0
  449. package/src/stateful/index.ts +18 -0
  450. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  451. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  452. package/src/stateful/provider-event-pipeline.ts +61 -0
  453. package/src/stateful/provider-events.ts +462 -0
  454. package/src/stateful/session-key.ts +111 -0
  455. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  456. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  457. package/src/stateful/stateful-provider-adapter.ts +562 -0
  458. package/src/stateful/stateful-provider-observability.ts +261 -0
  459. package/src/stateful/stateful-provider-owner-forwarder.ts +310 -0
  460. package/src/stateful/stateful-provider-runtime-context.ts +96 -0
  461. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  462. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  463. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  464. package/src/stateful-signing.ts +46 -0
  465. package/src/stealth/profiles.ts +212 -231
  466. package/src/stream-evidence.ts +988 -0
  467. package/src/stream.ts +8 -1
  468. package/src/testing/index.ts +10 -1
  469. package/src/testing/run.ts +685 -19
  470. package/src/trace-sanitization.ts +110 -0
  471. package/src/types.ts +1014 -273
  472. package/src/typescript-module.ts +100 -0
  473. package/dist/choice-token.d.ts +0 -24
  474. package/dist/choice-token.js +0 -74
  475. package/dist/runtime/choice.d.ts +0 -15
  476. package/dist/runtime/choice.js +0 -413
  477. package/src/choice-token.ts +0 -165
  478. package/src/runtime/choice.ts +0 -686
@@ -1,24 +1,55 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
 
3
+ import { ProviderError } from "../errors.js";
3
4
  import { createProviderCache } from "../runtime/cache.js";
4
- import { createTestProviderChoiceContext } from "../runtime/choice.js";
5
+ import { createTestHandleContext } from "../runtime/handle.js";
6
+
7
+ /**
8
+ * Stable connection scope for the local test harness so bound handles (drafts,
9
+ * default cursors) can be exercised exactly as in a connected production call.
10
+ */
11
+ const STANDARD_TEST_CONNECTION_ID = "standard-test-connection";
5
12
  import { createMemoryProviderRuntimeState } from "../runtime/state.js";
13
+ import { assertCallerHeadersSupported } from "../runtime/stealth-owned-headers.js";
14
+ import { createUnsupportedOcrClient } from "../runtime/ocr.js";
6
15
  import { createUnsupportedSttClient } from "../runtime/stt.js";
16
+ import {
17
+ createNativeEgressAuthorization,
18
+ NativeNetworkError,
19
+ snapshotNativeConnectInput,
20
+ snapshotNativeGrantInput,
21
+ } from "../runtime/native-network.js";
7
22
  import { safeParseSchemaSync } from "../schema.js";
23
+ import { requestPathForFixture } from "../fixture-sanitization.js";
24
+ import { findStreamCaptureGroup, replayStreamEvidence } from "../stream-evidence.js";
8
25
  import type {
9
26
  AuthMode,
27
+ BrowserPage,
10
28
  CredentialContext,
11
29
  HttpResponse,
30
+ NativeNetworkConnection,
12
31
  ProviderContext,
13
32
  ProviderDefinition,
33
+ StealthCookieStoreV1,
34
+ StealthResponse,
14
35
  } from "../types.js";
15
36
 
16
37
  // Mirrors CONNECTOR_ID_REGEX in ../define.ts, which defineProvider() enforces.
17
38
  // A single lowercase segment (no hyphen) is a valid id, so the trailing group
18
39
  // is optional (`*`), matching providers like `kakaomap`, `kstartup`, `triple`.
19
40
  const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
20
- const VALID_AUTH_MODES = ["none", "platform-managed", "credentials", "oauth2"] as const;
41
+ const VALID_AUTH_MODES = [
42
+ "none",
43
+ "platform-managed",
44
+ "credentials",
45
+ "oauth2",
46
+ "oauth2_proxied",
47
+ ] as const;
21
48
  const UPDATE_SNAPSHOT_ARGS = new Set(["-u", "--update-snapshots"]);
49
+ const snapshotCaptureStates = new WeakMap<
50
+ ProviderContext,
51
+ { assertConsumed(): void; consumed(): number }
52
+ >();
22
53
 
23
54
  export interface StandardTestsManifest {
24
55
  id?: string;
@@ -44,6 +75,40 @@ export interface StandardTestsOptions {
44
75
  validateAuthMode?: boolean;
45
76
  /** Override inferred __fixtures__ directory for tests generated outside providers/<id>. */
46
77
  fixtureDir?: string;
78
+ /** Opt in to real-handler E2E with strict, offline canned upstream responses. */
79
+ upstreamStub?: StandardTestsUpstreamStub;
80
+ /** Require a committed snapshot; missing files fail unless --update-snapshots is passed. */
81
+ requireSnapshot?: boolean;
82
+ }
83
+
84
+ export interface StandardTestsUpstreamCall {
85
+ /** Operation whose real handler initiated the call. */
86
+ operationName: string;
87
+ /** ProviderContext transport surface used by the handler. */
88
+ transport: "http" | "stealth" | "browser" | "native";
89
+ method: string;
90
+ url?: string;
91
+ body?: unknown;
92
+ options?: unknown;
93
+ }
94
+
95
+ export interface StandardTestsUpstreamResponse {
96
+ status?: number;
97
+ headers?: Readonly<Record<string, string>>;
98
+ /** JSON-compatible values are encoded as JSON; strings and bytes are preserved. */
99
+ body?: unknown;
100
+ }
101
+
102
+ export type StandardTestsUpstreamStub = (
103
+ call: StandardTestsUpstreamCall,
104
+ ) =>
105
+ | Response
106
+ | StandardTestsUpstreamResponse
107
+ | undefined
108
+ | Promise<Response | StandardTestsUpstreamResponse | undefined>;
109
+
110
+ export interface StandardTestsResult {
111
+ warnings: readonly string[];
47
112
  }
48
113
 
49
114
  interface FixtureEnvelope {
@@ -136,11 +201,438 @@ function jsonResponse(data: unknown): HttpResponse {
136
201
  };
137
202
  }
138
203
 
204
+ interface NormalizedUpstreamResponse {
205
+ status: number;
206
+ headers: Record<string, string>;
207
+ data: unknown;
208
+ text: string;
209
+ bytes: Uint8Array;
210
+ }
211
+
212
+ function headersToRecord(headers: Headers): Record<string, string> {
213
+ return Object.fromEntries(headers.entries());
214
+ }
215
+
216
+ async function normalizeUpstreamResponse(
217
+ response: Response | StandardTestsUpstreamResponse,
218
+ ): Promise<NormalizedUpstreamResponse> {
219
+ if (response instanceof Response) {
220
+ const bytes = new Uint8Array(await response.arrayBuffer());
221
+ const text = new TextDecoder().decode(bytes);
222
+ const headers = headersToRecord(response.headers);
223
+ let data: unknown = text;
224
+ if (response.headers.get("content-type")?.includes("application/json")) {
225
+ data = text.length > 0 ? JSON.parse(text) : null;
226
+ }
227
+ return { status: response.status, headers, data, text, bytes };
228
+ }
229
+
230
+ const headers = { ...(response.headers ?? {}) };
231
+ const body = response.body ?? null;
232
+ let bytes: Uint8Array;
233
+ let text: string;
234
+ let data: unknown;
235
+ if (body instanceof Uint8Array) {
236
+ bytes = body.slice(0);
237
+ text = new TextDecoder().decode(bytes);
238
+ data = text;
239
+ } else if (body instanceof ArrayBuffer) {
240
+ bytes = new Uint8Array(body.slice(0));
241
+ text = new TextDecoder().decode(bytes);
242
+ data = text;
243
+ } else if (typeof body === "string") {
244
+ text = body;
245
+ bytes = new TextEncoder().encode(text);
246
+ data = body;
247
+ } else {
248
+ text = JSON.stringify(body);
249
+ bytes = new TextEncoder().encode(text);
250
+ data = body;
251
+ if (!Object.keys(headers).some((name) => name.toLowerCase() === "content-type")) {
252
+ headers["content-type"] = "application/json";
253
+ }
254
+ }
255
+ return { status: response.status ?? 200, headers, data, text, bytes };
256
+ }
257
+
258
+ function toHttpResponse(response: NormalizedUpstreamResponse): HttpResponse {
259
+ return {
260
+ status: response.status,
261
+ ok: response.status >= 200 && response.status < 300,
262
+ headers: response.headers,
263
+ data: response.data,
264
+ json: async <T = unknown>() => JSON.parse(response.text) as T,
265
+ text: async () => response.text,
266
+ arrayBuffer: async () => response.bytes.slice(0).buffer,
267
+ bytes: async () => response.bytes.slice(0),
268
+ };
269
+ }
270
+
271
+ const emptyCookieJar = {
272
+ get: () => undefined,
273
+ getAll: () => ({}),
274
+ toString: () => "",
275
+ };
276
+
277
+ function emptyCookieStore(): StealthCookieStoreV1 {
278
+ return {
279
+ version: 1,
280
+ jar: { cookies: [] } as unknown as StealthCookieStoreV1["jar"],
281
+ };
282
+ }
283
+
284
+ function toStealthResponse(response: NormalizedUpstreamResponse, url?: string): StealthResponse {
285
+ return {
286
+ status: response.status,
287
+ ok: response.status >= 200 && response.status < 300,
288
+ url,
289
+ redirected: false,
290
+ headers: response.headers,
291
+ rawHeaders: Object.entries(response.headers),
292
+ body: response.text,
293
+ cookies: emptyCookieJar,
294
+ json: async <T>() => JSON.parse(response.text) as T,
295
+ arrayBuffer: async () => response.bytes.slice(0).buffer,
296
+ bytes: async () => response.bytes.slice(0),
297
+ };
298
+ }
299
+
300
+ function streamFromBytes(bytes: Uint8Array): ReadableStream<Uint8Array> {
301
+ return new ReadableStream({
302
+ start(controller) {
303
+ controller.enqueue(bytes.slice(0));
304
+ controller.close();
305
+ },
306
+ });
307
+ }
308
+
309
+ async function* singleBytes(bytes: Uint8Array): AsyncIterable<Uint8Array> {
310
+ yield bytes.slice(0);
311
+ }
312
+
313
+ async function* singleText(textValue: string): AsyncIterable<string> {
314
+ yield textValue;
315
+ }
316
+
317
+ function createUpstreamContext(
318
+ provider: ProviderDefinition,
319
+ operationName: string,
320
+ upstreamStub: StandardTestsUpstreamStub,
321
+ ): ProviderContext {
322
+ const credential: CredentialContext = {
323
+ mode: "none",
324
+ get: () => undefined,
325
+ getAll: () => ({}),
326
+ getAccessToken: () => undefined,
327
+ getScopes: () => [],
328
+ };
329
+ const request = { headers: {}, connectionId: STANDARD_TEST_CONNECTION_ID };
330
+ const state = createMemoryProviderRuntimeState();
331
+ const nativeEgress = provider.native
332
+ ? createNativeEgressAuthorization({ egress: provider.native.network })
333
+ : undefined;
334
+ const requireNativeEgress = () => {
335
+ if (!nativeEgress)
336
+ throw new NativeNetworkError(
337
+ "Native egress authorization is unavailable",
338
+ "native_egress_policy_invalid",
339
+ );
340
+ return nativeEgress;
341
+ };
342
+ const dispatch = async (
343
+ call: Omit<StandardTestsUpstreamCall, "operationName">,
344
+ ): Promise<NormalizedUpstreamResponse> => {
345
+ const canned = await upstreamStub({ operationName, ...call });
346
+ if (canned === undefined) {
347
+ throw new Error(
348
+ `Unmatched upstream call for operation "${operationName}": ${call.transport}.${call.method}${call.url ? ` ${call.url}` : ""}. Add a canned response to upstreamStub; live network passthrough is disabled.`,
349
+ );
350
+ }
351
+ return normalizeUpstreamResponse(canned);
352
+ };
353
+ const httpCall = async (
354
+ method: string,
355
+ url: string,
356
+ body?: unknown,
357
+ options?: unknown,
358
+ ): Promise<HttpResponse> =>
359
+ toHttpResponse(await dispatch({ transport: "http", method, url, body, options }));
360
+ // The production transport rejects owned headers (User-Agent, Sec-Fetch-*,
361
+ // sec-ch-ua*, ...) before any byte is sent; the fake applies the same
362
+ // function, so a provider test fails exactly where production would.
363
+ const stealthCall = async (
364
+ url: string,
365
+ options?: {
366
+ method?: string;
367
+ body?: unknown;
368
+ headers?: Readonly<Record<string, string | readonly string[] | undefined>>;
369
+ },
370
+ ) => {
371
+ assertCallerHeadersSupported(options?.headers ?? {});
372
+ return toStealthResponse(
373
+ await dispatch({
374
+ transport: "stealth",
375
+ method: options?.method?.toUpperCase() ?? "GET",
376
+ url,
377
+ body: options?.body,
378
+ options,
379
+ }),
380
+ url,
381
+ );
382
+ };
383
+
384
+ const createBrowserPage = (): BrowserPage => {
385
+ let currentUrl = "about:blank";
386
+ let currentResponse: NormalizedUpstreamResponse | undefined;
387
+ const browserAction = async (method: string, body?: unknown) => {
388
+ currentResponse = await dispatch({
389
+ transport: "browser",
390
+ method,
391
+ url: currentUrl,
392
+ body,
393
+ });
394
+ return currentResponse;
395
+ };
396
+ const page: BrowserPage = {
397
+ id: `standard-test-${operationName}`,
398
+ url: async () => currentUrl,
399
+ title: async () => currentResponse?.text.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1] ?? "",
400
+ userAgent: async () => String((await browserAction("userAgent")).data),
401
+ content: async () => currentResponse?.text ?? "",
402
+ evaluate: async <T>(fn: string | (() => T)) =>
403
+ (await browserAction("evaluate", typeof fn === "string" ? fn : String(fn))).data as T,
404
+ locator: (selector) => ({
405
+ click: async () => {
406
+ await browserAction("locator.click", { selector });
407
+ },
408
+ fill: async (textValue) => {
409
+ await browserAction("locator.fill", { selector, text: textValue });
410
+ },
411
+ textContent: async () => {
412
+ const value = (await browserAction("locator.textContent", { selector })).data;
413
+ return value === null || value === undefined ? null : String(value);
414
+ },
415
+ waitFor: async (options) => {
416
+ await browserAction("locator.waitFor", { selector, options });
417
+ },
418
+ }),
419
+ close: async () => {},
420
+ cookies: async () =>
421
+ (await browserAction("cookies")).data as Awaited<ReturnType<BrowserPage["cookies"]>>,
422
+ fill: async (selector, textValue) => {
423
+ await browserAction("fill", { selector, text: textValue });
424
+ },
425
+ goto: async (url) => {
426
+ currentUrl = url;
427
+ await browserAction("goto");
428
+ },
429
+ screenshot: async (options) =>
430
+ Buffer.from((await browserAction("screenshot", options)).bytes),
431
+ click: async (selector) => {
432
+ await browserAction("click", { selector });
433
+ },
434
+ type: async (selector, textValue) => {
435
+ await browserAction("type", { selector, text: textValue });
436
+ },
437
+ waitForSelector: async (selector, options) => {
438
+ await browserAction("waitForSelector", { selector, options });
439
+ },
440
+ frames: async () => [page],
441
+ withResourcePolicy: async (_policy, run) => run(),
442
+ };
443
+ return page;
444
+ };
445
+
446
+ const createStealthSession = (): ReturnType<ProviderContext["stealth"]["createSession"]> => ({
447
+ fetch: stealthCall,
448
+ async replayChallenged() {
449
+ throw new ProviderError("Recorded transports cannot replay an engine-owned challenge", {
450
+ code: "REPLAY_SESSION_MISMATCH",
451
+ });
452
+ },
453
+ cookies: {
454
+ ...emptyCookieJar,
455
+ has: () => false,
456
+ setFromCookieStrings: () => {},
457
+ toHeader: () => "",
458
+ snapshot: () => ({}),
459
+ restore: () => {},
460
+ serialize: emptyCookieStore,
461
+ deserialize: () => {},
462
+ clear: () => {},
463
+ },
464
+ redirects: {
465
+ run: async (options) => {
466
+ const final = await stealthCall(options.url, options);
467
+ return {
468
+ final,
469
+ hops: [],
470
+ reason: "completed",
471
+ cookies: {},
472
+ cookieStore: emptyCookieStore(),
473
+ };
474
+ },
475
+ },
476
+ close: () => {},
477
+ });
478
+
479
+ return {
480
+ env: { get: () => undefined },
481
+ credential,
482
+ request,
483
+ http: {
484
+ request: (url, options) =>
485
+ httpCall(options?.method?.toUpperCase() ?? "GET", url, options?.body, options),
486
+ get: (url, options) => httpCall("GET", url, undefined, options),
487
+ post: (url, body, options) => httpCall("POST", url, body, options),
488
+ put: (url, body, options) => httpCall("PUT", url, body, options),
489
+ delete: (url, options) => httpCall("DELETE", url, undefined, options),
490
+ stream: async (url, options) => {
491
+ const response = await dispatch({
492
+ transport: "http",
493
+ method: options?.method?.toUpperCase() ?? "GET",
494
+ url,
495
+ body: options?.body,
496
+ options,
497
+ });
498
+ return {
499
+ status: response.status,
500
+ ok: response.status >= 200 && response.status < 300,
501
+ headers: response.headers,
502
+ body: streamFromBytes(response.bytes),
503
+ bytes: () => singleBytes(response.bytes),
504
+ textChunks: () => singleText(response.text),
505
+ lines: () => singleText(response.text),
506
+ };
507
+ },
508
+ sse: async (url, options) => {
509
+ const response = await dispatch({
510
+ transport: "http",
511
+ method: options?.method?.toUpperCase() ?? "GET",
512
+ url,
513
+ body: options?.body,
514
+ options,
515
+ });
516
+ async function* messages() {
517
+ for (const block of response.text.split(/\r?\n\r?\n/)) {
518
+ const data = block
519
+ .split(/\r?\n/)
520
+ .filter((line) => line.startsWith("data:"))
521
+ .map((line) => line.slice(5).trimStart())
522
+ .join("\n");
523
+ if (!data) continue;
524
+ yield { event: "message", data, json: <T>() => JSON.parse(data) as T };
525
+ }
526
+ }
527
+ return messages();
528
+ },
529
+ },
530
+ cache: createProviderCache({ providerId: `standard-test-${operationName}` }),
531
+ state,
532
+ stealth: {
533
+ fetch: stealthCall,
534
+ createSession: createStealthSession,
535
+ },
536
+ browser: {
537
+ engine: "playwright-stealth",
538
+ newPage: async () => createBrowserPage(),
539
+ rawPage: async () => createBrowserPage(),
540
+ withIsolatedContext: async (handler) => handler(createBrowserPage()),
541
+ solveChallenge: async (challenge) =>
542
+ (
543
+ await dispatch({
544
+ transport: "browser",
545
+ method: "solveChallenge",
546
+ body: challenge,
547
+ })
548
+ ).data as Awaited<ReturnType<ProviderContext["browser"]["solveChallenge"]>>,
549
+ },
550
+ ...(provider.native
551
+ ? {
552
+ native: {
553
+ network: {
554
+ connectTcp: async (options) => {
555
+ const request = snapshotNativeConnectInput(options);
556
+ requireNativeEgress().assertConnect(request, "disabled");
557
+ return createNativeConnection(
558
+ await dispatch({
559
+ transport: "native",
560
+ method: "connectTcp",
561
+ url: `tcp://${request.host}:${request.port}`,
562
+ options: request,
563
+ }),
564
+ dispatch,
565
+ `tcp://${request.host}:${request.port}`,
566
+ );
567
+ },
568
+ connectTls: async (options) => {
569
+ const request = snapshotNativeConnectInput(options);
570
+ requireNativeEgress().assertConnect(request, "required");
571
+ return createNativeConnection(
572
+ await dispatch({
573
+ transport: "native",
574
+ method: "connectTls",
575
+ url: `tls://${request.host}:${request.port}`,
576
+ options: request,
577
+ }),
578
+ dispatch,
579
+ `tls://${request.host}:${request.port}`,
580
+ );
581
+ },
582
+ grantTcpEgress: (input) => {
583
+ return requireNativeEgress().grant(snapshotNativeGrantInput(input));
584
+ },
585
+ },
586
+ },
587
+ }
588
+ : {}),
589
+ trace: { span: async (_name, fn) => fn() },
590
+ auth: { requestField: async (name) => unsupported(`ctx.auth.requestField(${name})`) },
591
+ ocr: createUnsupportedOcrClient(
592
+ "Standard test upstream context does not support ctx.ocr.recognize",
593
+ ),
594
+ stt: createUnsupportedSttClient(
595
+ "Standard test upstream context does not support ctx.stt.transcribe",
596
+ ),
597
+ resolver: {
598
+ solve: async () => unsupported("ctx.resolver.solve"),
599
+ },
600
+ handle: createTestHandleContext({
601
+ providerId: `standard-test-${operationName}`,
602
+ request,
603
+ state,
604
+ }),
605
+ } as ProviderContext;
606
+ }
607
+
608
+ function createNativeConnection(
609
+ initialResponse: NormalizedUpstreamResponse,
610
+ dispatch: (
611
+ call: Omit<StandardTestsUpstreamCall, "operationName">,
612
+ ) => Promise<NormalizedUpstreamResponse>,
613
+ url: string,
614
+ ): NativeNetworkConnection {
615
+ let unread = initialResponse.bytes.slice(0);
616
+ return {
617
+ read: async () => {
618
+ if (unread.byteLength === 0) return null;
619
+ const bytes = unread;
620
+ unread = new Uint8Array();
621
+ return bytes;
622
+ },
623
+ write: async (body) => {
624
+ const response = await dispatch({ transport: "native", method: "write", url, body });
625
+ unread = response.bytes.slice(0);
626
+ },
627
+ close: async () => {},
628
+ };
629
+ }
630
+
139
631
  function unsupported(name: string): never {
140
632
  throw new Error(`Standard test snapshot context does not support ${name}`);
141
633
  }
142
634
 
143
- function createSnapshotContext(rawFixture: unknown): ProviderContext {
635
+ export function createSnapshotContext(rawFixture: unknown): ProviderContext {
144
636
  const credential: CredentialContext = {
145
637
  mode: "none",
146
638
  get: () => undefined,
@@ -148,20 +640,63 @@ function createSnapshotContext(rawFixture: unknown): ProviderContext {
148
640
  getAccessToken: () => undefined,
149
641
  getScopes: () => [],
150
642
  };
151
- const request = { headers: {} };
643
+ const request = { headers: {}, connectionId: STANDARD_TEST_CONNECTION_ID };
152
644
  const state = createMemoryProviderRuntimeState();
645
+ const streamCaptureGroup = findStreamCaptureGroup(rawFixture);
646
+ let nextCaptureItem = 0;
647
+ const replayJsonResponse = () => {
648
+ if (!streamCaptureGroup) return jsonResponse(rawFixture);
649
+ const item = streamCaptureGroup.items[nextCaptureItem];
650
+ if (!item) {
651
+ throw new Error(
652
+ `Stream fixture exhausted: no recorded response exists for ordinary HTTP call ${nextCaptureItem + 1}.`,
653
+ );
654
+ }
655
+ if (item.kind !== "response") {
656
+ throw new Error(
657
+ `Stream fixture call-order mismatch: expected a stream call at position ${nextCaptureItem + 1}, received an ordinary HTTP call.`,
658
+ );
659
+ }
660
+ nextCaptureItem += 1;
661
+ return jsonResponse(item.value);
662
+ };
153
663
 
154
- return {
664
+ const context: ProviderContext = {
155
665
  env: { get: () => undefined },
156
666
  credential,
157
667
  request,
158
668
  http: {
159
- request: async () => jsonResponse(rawFixture),
160
- get: async () => jsonResponse(rawFixture),
161
- post: async () => jsonResponse(rawFixture),
162
- put: async () => jsonResponse(rawFixture),
163
- delete: async () => jsonResponse(rawFixture),
164
- stream: async () => unsupported("ctx.http.stream"),
669
+ request: async () => replayJsonResponse(),
670
+ get: async () => replayJsonResponse(),
671
+ post: async () => replayJsonResponse(),
672
+ put: async () => replayJsonResponse(),
673
+ delete: async () => replayJsonResponse(),
674
+ stream: async (...args) => {
675
+ if (!streamCaptureGroup) return unsupported("ctx.http.stream");
676
+ const item = streamCaptureGroup.items[nextCaptureItem];
677
+ if (!item) {
678
+ throw new Error(
679
+ `Stream fixture exhausted: no recorded evidence exists for stream call ${nextCaptureItem + 1}.`,
680
+ );
681
+ }
682
+ if (item.kind !== "stream") {
683
+ throw new Error(
684
+ `Stream fixture call-order mismatch: expected an ordinary HTTP call at position ${nextCaptureItem + 1}, received a stream call.`,
685
+ );
686
+ }
687
+ if (item.evidence.request) {
688
+ const expected = item.evidence.request;
689
+ const actualMethod = (args[1]?.method ?? "GET").toUpperCase();
690
+ const actualPath = replayRequestPath(args[0], expected.path);
691
+ if (actualMethod !== expected.method || actualPath !== expected.path) {
692
+ throw new Error(
693
+ `Stream fixture request mismatch: expected ${expected.method} ${expected.path}, received ${actualMethod} ${actualPath}.`,
694
+ );
695
+ }
696
+ }
697
+ nextCaptureItem += 1;
698
+ return replayStreamEvidence(item.evidence);
699
+ },
165
700
  sse: async () => unsupported("ctx.http.sse"),
166
701
  },
167
702
  cache: createProviderCache({ providerId: "standard-test" }),
@@ -183,16 +718,41 @@ function createSnapshotContext(rawFixture: unknown): ProviderContext {
183
718
  auth: {
184
719
  requestField: async (name) => unsupported(`ctx.auth.requestField(${name})`),
185
720
  },
721
+ ocr: createUnsupportedOcrClient(
722
+ "Standard test snapshot context does not support ctx.ocr.recognize",
723
+ ),
186
724
  stt: createUnsupportedSttClient(
187
725
  "Standard test snapshot context does not support ctx.stt.transcribe",
188
726
  ),
189
- choice: createTestProviderChoiceContext({
727
+ resolver: {
728
+ solve: async () => unsupported("ctx.resolver.solve"),
729
+ },
730
+ handle: createTestHandleContext({
190
731
  providerId: "standard-test",
191
732
  request,
192
- credential,
193
733
  state,
194
734
  }),
195
- };
735
+ } satisfies Omit<ProviderContext, "native"> as unknown as ProviderContext;
736
+ snapshotCaptureStates.set(context, {
737
+ assertConsumed() {
738
+ if (streamCaptureGroup && nextCaptureItem !== streamCaptureGroup.items.length) {
739
+ throw new Error(
740
+ `Stream fixture has ${streamCaptureGroup.items.length - nextCaptureItem} unconsumed capture item${streamCaptureGroup.items.length - nextCaptureItem === 1 ? "" : "s"} after handler completion.`,
741
+ );
742
+ }
743
+ },
744
+ consumed: () => nextCaptureItem,
745
+ });
746
+ return context;
747
+ }
748
+
749
+ function replayRequestPath(requestUrl: string, expectedPath: string): string {
750
+ if (/^[a-z][a-z\d+.-]*:\/\//i.test(requestUrl) || requestUrl.startsWith("/")) {
751
+ return requestPathForFixture(requestUrl);
752
+ }
753
+ return requestPathForFixture(
754
+ new URL(requestUrl, `https://fixture.invalid${expectedPath}`).toString(),
755
+ );
196
756
  }
197
757
 
198
758
  async function transformSnapshotOutput(
@@ -200,14 +760,21 @@ async function transformSnapshotOutput(
200
760
  rawFixture: unknown,
201
761
  ): Promise<unknown> {
202
762
  const entries = Object.entries(provider.operations);
203
- const context = createSnapshotContext(rawFixture);
763
+ const captureStates: Array<{ assertConsumed(): void; consumed(): number }> = [];
204
764
  const outputs = await Promise.all(
205
765
  entries.map(async ([operationName, operation]) => {
766
+ const context = createSnapshotContext(rawFixture);
206
767
  const request = operation.fixtures?.request ?? {};
207
768
  const output = await operation.handler(context, request);
769
+ const captureState = snapshotCaptureStates.get(context);
770
+ if (captureState) captureStates.push(captureState);
771
+ if (captureState?.consumed()) captureState.assertConsumed();
208
772
  return [operationName, output] as const;
209
773
  }),
210
774
  );
775
+ if (findStreamCaptureGroup(rawFixture) && !captureStates.some((state) => state.consumed() > 0)) {
776
+ captureStates[0]?.assertConsumed();
777
+ }
211
778
 
212
779
  if (outputs.length === 1) {
213
780
  return outputs[0]?.[1];
@@ -232,7 +799,7 @@ function formatJsonDiff(current: unknown, expected: unknown): string {
232
799
 
233
800
  if (currentLine === expectedLine) {
234
801
  if (currentLine !== undefined) {
235
- lines.push(` ${currentLine}`);
802
+ lines.push(` ${currentLine}`);
236
803
  }
237
804
  continue;
238
805
  }
@@ -284,6 +851,58 @@ function parseSchemaFixture(
284
851
  );
285
852
  }
286
853
 
854
+ async function materializeHandlerOutput(output: unknown): Promise<unknown> {
855
+ if (!(output instanceof Response)) return output;
856
+ const contentType = output.headers.get("content-type") ?? "";
857
+ if (contentType.includes("application/json")) return output.json();
858
+ return output.text();
859
+ }
860
+
861
+ /** Internal execution seam exported for focused SDK tests; use runStandardTests as public API. */
862
+ export async function executeStandardTestHandler(
863
+ provider: ProviderDefinition,
864
+ operationName: string,
865
+ upstreamStub: StandardTestsUpstreamStub,
866
+ ): Promise<unknown> {
867
+ const operation = provider.operations[operationName];
868
+ if (!operation) throw new Error(`Unknown operation "${operationName}".`);
869
+ if (operation.fixtures?.request === undefined) {
870
+ throw new Error(
871
+ `Operation "${operationName}" has no fixtures.request for handler E2E execution.`,
872
+ );
873
+ }
874
+ const context = createUpstreamContext(provider, operationName, upstreamStub);
875
+ const output = await materializeHandlerOutput(
876
+ await operation.handler(context, operation.fixtures.request),
877
+ );
878
+ const result = safeParseSchemaSync(
879
+ operation.output,
880
+ output,
881
+ `operations.${operationName}.handler.output`,
882
+ );
883
+ if (!result.success) {
884
+ throw new Error(
885
+ [
886
+ `Handler output for operation "${operationName}" failed schema validation.`,
887
+ formatJsonDiff(
888
+ { valid: false, value: output, error: result.error },
889
+ { valid: true, value: output },
890
+ ),
891
+ ].join("\n"),
892
+ );
893
+ }
894
+ return output;
895
+ }
896
+
897
+ function isOptionsShortcut(value: unknown): value is StandardTestsOptions {
898
+ return (
899
+ value !== null &&
900
+ typeof value === "object" &&
901
+ !Array.isArray(value) &&
902
+ Object.hasOwn(value, "upstreamStub")
903
+ );
904
+ }
905
+
287
906
  /**
288
907
  * Run standard SDK tests for a provider in one line.
289
908
  *
@@ -292,13 +911,39 @@ function parseSchemaFixture(
292
911
  * import { runStandardTests } from "@apifuse/provider-sdk/testing";
293
912
  * runStandardTests(myProvider, rawFixture, manifest, { snapshot: true });
294
913
  */
914
+ export function runStandardTests(
915
+ provider: ProviderDefinition,
916
+ options: StandardTestsOptions & { upstreamStub: StandardTestsUpstreamStub },
917
+ ): StandardTestsResult;
295
918
  export function runStandardTests(
296
919
  provider: ProviderDefinition,
297
920
  rawFixture?: unknown,
298
921
  manifest?: StandardTestsManifest,
299
- options: StandardTestsOptions = {},
300
- ): void {
922
+ options?: StandardTestsOptions,
923
+ ): StandardTestsResult;
924
+ export function runStandardTests(
925
+ provider: ProviderDefinition,
926
+ rawFixtureOrOptions?: unknown,
927
+ manifest?: StandardTestsManifest,
928
+ legacyOptions?: StandardTestsOptions,
929
+ ): StandardTestsResult {
930
+ const shortcut =
931
+ manifest === undefined && legacyOptions === undefined && isOptionsShortcut(rawFixtureOrOptions);
932
+ const rawFixture = shortcut ? undefined : rawFixtureOrOptions;
933
+ const options = shortcut ? rawFixtureOrOptions : (legacyOptions ?? {});
301
934
  const operations = Object.entries(provider.operations);
935
+ const warnings = options.upstreamStub
936
+ ? operations
937
+ .filter(([, operation]) => operation.fixtures?.request === undefined)
938
+ .map(
939
+ ([operationName]) =>
940
+ `[provider-sdk] Operation "${provider.id}.${operationName}" has no fixtures.request, so runStandardTests cannot invoke its handler E2E.`,
941
+ )
942
+ : operations.map(
943
+ ([operationName]) =>
944
+ `[provider-sdk] Operation "${provider.id}.${operationName}" has no handler E2E coverage in runStandardTests; configure upstreamStub to invoke the real handler.`,
945
+ );
946
+ for (const warning of warnings) console.warn(warning);
302
947
 
303
948
  const assertFixtureValidation = (): void => {
304
949
  expect(rawFixture).toBeDefined();
@@ -421,7 +1066,13 @@ export function runStandardTests(
421
1066
  const serialized = stableStringify(actual);
422
1067
  const snapshotFile = Bun.file(snapshotPath);
423
1068
 
424
- if (shouldUpdateSnapshots() || !(await snapshotFile.exists())) {
1069
+ const snapshotExists = await snapshotFile.exists();
1070
+ if (!snapshotExists && options.requireSnapshot && !shouldUpdateSnapshots()) {
1071
+ throw new Error(
1072
+ `Required golden snapshot is missing: ${snapshotPath}. Regenerate it with bun test --update-snapshots.`,
1073
+ );
1074
+ }
1075
+ if (shouldUpdateSnapshots() || !snapshotExists) {
425
1076
  await Bun.write(snapshotPath, serialized);
426
1077
  }
427
1078
 
@@ -429,5 +1080,20 @@ export function runStandardTests(
429
1080
  expect(actual).toEqual(expected);
430
1081
  });
431
1082
  }
1083
+
1084
+ if (options.upstreamStub) {
1085
+ for (const [operationName, operation] of operations) {
1086
+ if (operation.fixtures?.request === undefined) continue;
1087
+ it(`invokes the real ${operationName} handler with canned upstream responses`, async () => {
1088
+ await executeStandardTestHandler(
1089
+ provider,
1090
+ operationName,
1091
+ options.upstreamStub as StandardTestsUpstreamStub,
1092
+ );
1093
+ });
1094
+ }
1095
+ }
432
1096
  });
1097
+
1098
+ return { warnings };
433
1099
  }