@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
package/src/define.ts CHANGED
@@ -1,7 +1,19 @@
1
1
  import ms from "ms";
2
2
 
3
+ import {
4
+ validateFailClosedOperationDeclaration,
5
+ validateFailClosedProviderDeclaration,
6
+ } from "./declaration-validation.js";
7
+ import { SDK_RUNTIME_OWNED_ERROR_CODES } from "./error-resolution.js";
3
8
  import { ProviderError, ValidationError } from "./errors.js";
9
+ import { isEngineOwnedProxyCredentialName, isEngineOwnedTelemetryEnvName } from "./engine.js";
10
+ import { HealthScenarioSchema } from "./health-scenario.js";
11
+ import {
12
+ NativeEgressPolicyValidationError,
13
+ validateNativeProviderConfig,
14
+ } from "./native-egress-policy.js";
4
15
  import { safeParseSchemaSync } from "./schema.js";
16
+ import { resolveHealthCheckInputDateTokens } from "./server/self-test-input-tokens.js";
5
17
  import type {
6
18
  AuthConfig,
7
19
  BrowserEngine,
@@ -14,6 +26,7 @@ import type {
14
26
  HealthJourneySchedule,
15
27
  HealthScheduleRandomization,
16
28
  InferSchemaOutput,
29
+ NativeProviderConfig,
17
30
  OperationDefinition,
18
31
  OperationHandlerResult,
19
32
  OperationHttpStreamTransport,
@@ -21,18 +34,24 @@ import type {
21
34
  OperationTransport,
22
35
  OperationWebSocketTransport,
23
36
  ProviderAccessConfig,
37
+ ProviderChallengeKind,
38
+ ProviderContext,
24
39
  ProviderDefinition,
25
40
  ProviderDeploymentOverrides,
26
41
  ProviderHealthMonitorConfig,
42
+ ProviderOcrConfig,
27
43
  ProviderProxyConfig,
28
44
  ProviderPublicProfile,
45
+ ProviderResolverConfig,
46
+ ProviderResolverVendor,
29
47
  ProviderReviewed,
48
+ ProviderRuntimeTarget,
30
49
  ProviderSecretDeclaration,
31
50
  ProviderStreamEvent,
32
51
  ProviderSttConfig,
33
52
  SchemaLike,
34
53
  SmsOtpMatcherDefinition,
35
- StealthPlatform,
54
+ StealthProfileSelection,
36
55
  } from "./types.js";
37
56
  import {
38
57
  HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX,
@@ -49,6 +68,7 @@ import {
49
68
  STREAM_IDLE_TIMEOUT_MS_MIN,
50
69
  STREAM_MAX_DURATION_MS_MAX,
51
70
  STREAM_MAX_DURATION_MS_MIN,
71
+ VALID_OPERATION_ERROR_STATUSES,
52
72
  } from "./types.js";
53
73
 
54
74
  type ProviderImplementationSourceAccess =
@@ -75,22 +95,78 @@ interface ProviderImplementationProfile {
75
95
  const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
76
96
  const OPERATION_ID_REGEX = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
77
97
  const VALID_RUNTIMES = ["standard", "shared", "browser"] as const;
78
- const VALID_AUTH_MODES = ["none", "platform-managed", "credentials", "oauth2"] as const;
98
+ const VALID_RUNTIME_TARGETS = ["vanilla", "engine"] as const;
99
+ const VALID_AUTH_MODES = [
100
+ "none",
101
+ "platform-managed",
102
+ "credentials",
103
+ "oauth2",
104
+ "oauth2_proxied",
105
+ ] as const;
106
+ const PROXIED_OAUTH_REQUIRED_FIELDS = [
107
+ "authorizeUrl",
108
+ "tokenUrl",
109
+ "customScheme",
110
+ "rewriteProfile",
111
+ "clientIdEnvKey",
112
+ ] as const;
113
+ const PROXIED_OAUTH_ALLOWED_FIELDS = new Set([
114
+ ...PROXIED_OAUTH_REQUIRED_FIELDS,
115
+ "pkce",
116
+ "authorizeParams",
117
+ "tokenParams",
118
+ ]);
119
+ const PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS = new Set([
120
+ "client_id",
121
+ "response_type",
122
+ "state",
123
+ "code_challenge",
124
+ "code_challenge_method",
125
+ ]);
126
+ const PROXIED_OAUTH_PROFILE_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
127
+ const PROXIED_OAUTH_ENV_KEY_REGEX = /^[A-Z][A-Z0-9_]*__[A-Z0-9_]+$/;
128
+ const CUSTOM_SCHEME_REGEX = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/\S+$/;
79
129
  const VALID_PROVIDER_ACCESS_VISIBILITIES = ["public", "early_access"] as const;
80
130
  const VALID_PROVIDER_PROXY_MODES = ["disabled", "optional", "required"] as const;
81
- const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "decodo", "custom"] as const;
131
+ const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "nodemaven", "decodo", "custom"] as const;
82
132
  const VALID_PROVIDER_PROXY_AFFINITIES = [
83
133
  "request",
84
134
  "operation",
85
135
  "auth-flow",
86
136
  "connection",
87
137
  ] as const;
138
+ const VALID_PROVIDER_OCR_MODES = ["optional", "required"] as const;
88
139
  const VALID_PROVIDER_STT_MODES = ["optional", "required"] as const;
89
- const SMARTPROXY_APP_KEY_SECRET = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
140
+ function exhaustiveLiteralArray<TUnion extends string>() {
141
+ return <const TValues extends readonly TUnion[]>(
142
+ values: TValues,
143
+ ..._missing: Exclude<TUnion, TValues[number]> extends never
144
+ ? []
145
+ : ["Missing runtime values", Exclude<TUnion, TValues[number]>]
146
+ ): TValues => values;
147
+ }
148
+
149
+ export const VALID_PROVIDER_RESOLVER_VENDORS = exhaustiveLiteralArray<ProviderResolverVendor>()([
150
+ "browser",
151
+ "capsolver",
152
+ "capmonster",
153
+ "2captcha",
154
+ "custom",
155
+ ] as const);
156
+ export const VALID_PROVIDER_CHALLENGE_KINDS = exhaustiveLiteralArray<ProviderChallengeKind>()([
157
+ "turnstile",
158
+ "recaptcha_v2",
159
+ "recaptcha_v3",
160
+ "hcaptcha",
161
+ "cloudflare_interstitial",
162
+ "aws_waf",
163
+ "akamai_sec_cpt",
164
+ "akamai_sensor",
165
+ ] as const);
90
166
  const RESERVED_OPERATION_IDS = new Set(["auth", "health"]);
91
- const MCP_TOOL_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_]{0,127}$/;
92
167
  const VALID_OPERATION_RISK_CLASSES = ["read", "write", "destructive", "external-send"] as const;
93
168
  const VALID_OPERATION_APPROVAL_POLICIES = ["never", "risk-based", "always"] as const;
169
+ const VALID_OPERATION_CONNECTION_MODES = ["none", "optional", "required"] as const;
94
170
  const VALID_OPERATION_TRANSPORT_KINDS = ["json", "sse", "http-stream", "websocket"] as const;
95
171
  const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
96
172
  const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
@@ -114,74 +190,388 @@ function parsePositiveMsDuration(value: string): number | undefined {
114
190
  return parsed;
115
191
  }
116
192
 
117
- type ProviderOperation = OperationDefinition<SchemaLike, SchemaLike>;
118
- type OperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
119
- OperationDefinition<TInput, TOutput>,
120
- "handler"
121
- > & {
193
+ type ProviderOperation = OperationDefinition<any, any, any>;
194
+ type OperationConfig<
195
+ TInput extends SchemaLike,
196
+ TOutput extends SchemaLike,
197
+ TContext = ProviderContext,
198
+ > = Omit<OperationDefinition<TInput, TOutput, TContext>, "handler"> & {
122
199
  handler(
123
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
200
+ ctx: TContext,
124
201
  input: InferSchemaOutput<TInput>,
125
202
  ):
126
203
  | OperationHandlerResult<InferSchemaOutput<TOutput>>
127
204
  | Promise<OperationHandlerResult<InferSchemaOutput<TOutput>>>;
128
205
  };
129
- type OperationMapConfig<TOperations extends Record<string, ProviderOperation>> = {
206
+ type OperationMapConfig<
207
+ TOperations extends Record<string, ProviderOperation>,
208
+ TContext = ProviderContext,
209
+ > = {
130
210
  [K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput>
131
- ? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput>
211
+ ? OperationConfig<TInput, TOutput, TContext> | OperationDefinition<TInput, TOutput, TContext>
132
212
  : never;
133
213
  };
134
- type StreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> =
135
- | SseOperationConfig<TInput, TOutput>
136
- | HttpStreamOperationConfig<TInput, TOutput>
137
- | WebSocketOperationConfig<TInput, TOutput>;
138
- type SseOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
139
- OperationConfig<TInput, TOutput>,
140
- "handler" | "transport"
141
- > & {
214
+ type StreamOperationConfig<
215
+ TInput extends SchemaLike,
216
+ TOutput extends SchemaLike,
217
+ TContext = ProviderContext,
218
+ > =
219
+ | SseOperationConfig<TInput, TOutput, TContext>
220
+ | HttpStreamOperationConfig<TInput, TOutput, TContext>
221
+ | WebSocketOperationConfig<TInput, TOutput, TContext>;
222
+ type SseOperationConfig<
223
+ TInput extends SchemaLike,
224
+ TOutput extends SchemaLike,
225
+ TContext = ProviderContext,
226
+ > = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
142
227
  transport: OperationSseTransport;
143
228
  handler(
144
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
229
+ ctx: TContext,
145
230
  input: InferSchemaOutput<TInput>,
146
231
  ): AsyncIterable<ProviderStreamEvent> | Promise<AsyncIterable<ProviderStreamEvent>>;
147
232
  };
148
- type HttpStreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
149
- OperationConfig<TInput, TOutput>,
150
- "handler" | "transport"
151
- > & {
233
+ type HttpStreamOperationConfig<
234
+ TInput extends SchemaLike,
235
+ TOutput extends SchemaLike,
236
+ TContext = ProviderContext,
237
+ > = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
152
238
  transport: OperationHttpStreamTransport;
153
239
  handler(
154
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
240
+ ctx: TContext,
155
241
  input: InferSchemaOutput<TInput>,
156
242
  ): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
157
243
  };
158
- type WebSocketOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
159
- OperationConfig<TInput, TOutput>,
160
- "handler" | "transport"
161
- > & {
244
+ type WebSocketOperationConfig<
245
+ TInput extends SchemaLike,
246
+ TOutput extends SchemaLike,
247
+ TContext = ProviderContext,
248
+ > = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
162
249
  transport: OperationWebSocketTransport;
163
250
  handler(
164
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
251
+ ctx: TContext,
165
252
  input: InferSchemaOutput<TInput>,
166
253
  ): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
167
254
  };
168
255
 
169
- type AuthStartNoInputGuard<TConfig> = TConfig extends {
256
+ type AuthStartHandlerNoInputGuard<TStart> = TStart extends (...args: infer TArgs) => unknown
257
+ ? TArgs["length"] extends 0 | 1
258
+ ? unknown
259
+ : {
260
+ "auth start handlers must not declare input parameters; return a form turn from start and receive user input in continue": never;
261
+ }
262
+ : unknown;
263
+
264
+ export type AuthStartNoInputGuard<TConfig> = TConfig extends {
170
265
  auth?: { flow?: { start: infer TStart } };
171
266
  }
172
- ? TStart extends (...args: infer TArgs) => unknown
173
- ? TArgs extends [unknown]
174
- ? unknown
175
- : {
176
- "auth start handlers must not declare input parameters; return a form turn from start and receive user input in continue": never;
267
+ ? AuthStartHandlerNoInputGuard<TStart>
268
+ : TConfig extends { start: infer TStart }
269
+ ? AuthStartHandlerNoInputGuard<TStart>
270
+ : unknown;
271
+
272
+ function splitAuthStartParameters(parameters: string): string[] | undefined {
273
+ const parts: string[] = [];
274
+ let start = 0;
275
+ let round = 0;
276
+ let square = 0;
277
+ let curly = 0;
278
+ let quote: "'" | '"' | "`" | undefined;
279
+ let escaped = false;
280
+ let lineComment = false;
281
+ let blockComment = false;
282
+ const templateDepths = [0];
283
+ templateDepths.length = 0;
284
+
285
+ for (let index = 0; index < parameters.length; index++) {
286
+ const character = parameters[index];
287
+ const nextCharacter = parameters[index + 1];
288
+ if (lineComment) {
289
+ if (character === "\n" || character === "\r") lineComment = false;
290
+ else continue;
291
+ }
292
+ if (blockComment) {
293
+ if (character === "*" && nextCharacter === "/") {
294
+ blockComment = false;
295
+ index++;
296
+ }
297
+ continue;
298
+ }
299
+ if (quote) {
300
+ if (escaped) {
301
+ escaped = false;
302
+ } else if (character === "\\") {
303
+ escaped = true;
304
+ } else if (quote === "`" && character === "$" && nextCharacter === "{") {
305
+ curly++;
306
+ templateDepths.push(curly);
307
+ quote = undefined;
308
+ index++;
309
+ } else if (character === quote) {
310
+ quote = undefined;
311
+ }
312
+ continue;
313
+ }
314
+ if (character === "'" || character === '"' || character === "`") {
315
+ quote = character;
316
+ continue;
317
+ }
318
+ if (character === "/" && nextCharacter === "/") {
319
+ lineComment = true;
320
+ index++;
321
+ continue;
322
+ }
323
+ if (character === "/" && nextCharacter === "*") {
324
+ blockComment = true;
325
+ index++;
326
+ continue;
327
+ }
328
+ if (character === "/") return undefined;
329
+ // Annex B HTML-like comments are not lexed here; give up rather than
330
+ // risk misreading the parameter list.
331
+ if (character === "<" && parameters.startsWith("!--", index + 1)) return undefined;
332
+ if (character === "-" && parameters.startsWith("->", index + 1)) return undefined;
333
+ if (character === "(") round++;
334
+ else if (character === ")") round--;
335
+ else if (character === "[") square++;
336
+ else if (character === "]") square--;
337
+ else if (character === "{") curly++;
338
+ else if (character === "}") {
339
+ if (templateDepths.at(-1) === curly) {
340
+ templateDepths.pop();
341
+ curly--;
342
+ quote = "`";
343
+ } else curly--;
344
+ } else if (character === "," && round === 0 && square === 0 && curly === 0) {
345
+ parts.push(parameters.slice(start, index));
346
+ start = index + 1;
347
+ }
348
+ if (round < 0 || square < 0 || curly < 0) return undefined;
349
+ }
350
+
351
+ if (
352
+ quote ||
353
+ blockComment ||
354
+ templateDepths.length > 0 ||
355
+ round !== 0 ||
356
+ square !== 0 ||
357
+ curly !== 0
358
+ )
359
+ return undefined;
360
+ parts.push(parameters.slice(start));
361
+ return parts;
362
+ }
363
+
364
+ function authStartParameterList(source: string): string | undefined {
365
+ let index = 0;
366
+ while (index < source.length && /\s/.test(source[index] ?? "")) index++;
367
+ if (index >= source.length) return undefined;
368
+
369
+ let openIndex = -1;
370
+ let parenthesizedArrow = false;
371
+ let asyncMethodOrArrow = false;
372
+ const skipTrivia = () => {
373
+ while (index < source.length) {
374
+ if (/\s/.test(source[index] ?? "")) {
375
+ index++;
376
+ continue;
377
+ }
378
+ if (source[index] === "/" && source[index + 1] === "/") {
379
+ index += 2;
380
+ while (index < source.length && source[index] !== "\n" && source[index] !== "\r") index++;
381
+ continue;
382
+ }
383
+ if (source[index] === "/" && source[index + 1] === "*") {
384
+ const end = source.indexOf("*/", index + 2);
385
+ if (end < 0) {
386
+ index = source.length;
387
+ return;
177
388
  }
178
- : unknown
179
- : unknown;
389
+ index = end + 2;
390
+ continue;
391
+ }
392
+ return;
393
+ }
394
+ };
395
+
396
+ const initial = source.slice(index);
397
+ const isAsync = initial.startsWith("async") && !/[\w$]/.test(initial[5] ?? "");
398
+ if (isAsync) {
399
+ index += 5;
400
+ skipTrivia();
401
+ }
402
+
403
+ const afterAsync = source.slice(index);
404
+ const isFunction = afterAsync.startsWith("function") && !/[\w$]/.test(afterAsync[8] ?? "");
405
+ if (isFunction) {
406
+ index += 8;
407
+ skipTrivia();
408
+ if (source[index] === "*") {
409
+ index++;
410
+ skipTrivia();
411
+ }
412
+ } else if (source[index] === "*") {
413
+ index++;
414
+ skipTrivia();
415
+ }
416
+
417
+ if (source[index] === "(") {
418
+ openIndex = index;
419
+ parenthesizedArrow = !isFunction;
420
+ asyncMethodOrArrow = isAsync && !isFunction;
421
+ } else if (isFunction) {
422
+ if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
423
+ index++;
424
+ while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
425
+ skipTrivia();
426
+ if (source[index] !== "(") return undefined;
427
+ openIndex = index;
428
+ } else {
429
+ const identifierStart = index;
430
+ if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
431
+ index++;
432
+ while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
433
+ const firstIdentifier = source.slice(identifierStart, index);
434
+ skipTrivia();
435
+ if (source[index] === "=" && source[index + 1] === ">") return undefined;
436
+ if (source[index] !== "(") {
437
+ if (firstIdentifier !== "get" && firstIdentifier !== "set") return undefined;
438
+ if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
439
+ index++;
440
+ while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
441
+ skipTrivia();
442
+ }
443
+ if (source[index] !== "(") return undefined;
444
+ openIndex = index;
445
+ }
446
+ if (openIndex < 0) return undefined;
447
+
448
+ let depth = 1;
449
+ let square = 0;
450
+ let curly = 0;
451
+ let quote: "'" | '"' | "`" | undefined;
452
+ let escaped = false;
453
+ let lineComment = false;
454
+ let blockComment = false;
455
+ const templateDepths = [0];
456
+ templateDepths.length = 0;
457
+ for (index = openIndex + 1; index < source.length; index++) {
458
+ const character = source[index];
459
+ const nextCharacter = source[index + 1];
460
+ if (lineComment) {
461
+ if (character === "\n" || character === "\r") lineComment = false;
462
+ else continue;
463
+ }
464
+ if (blockComment) {
465
+ if (character === "*" && nextCharacter === "/") {
466
+ blockComment = false;
467
+ index++;
468
+ }
469
+ continue;
470
+ }
471
+ if (quote) {
472
+ if (escaped) escaped = false;
473
+ else if (character === "\\") escaped = true;
474
+ else if (quote === "`" && character === "$" && nextCharacter === "{") {
475
+ curly++;
476
+ templateDepths.push(curly);
477
+ quote = undefined;
478
+ index++;
479
+ } else if (character === quote) quote = undefined;
480
+ continue;
481
+ }
482
+ if (character === "'" || character === '"' || character === "`") {
483
+ quote = character;
484
+ continue;
485
+ }
486
+ if (character === "/" && nextCharacter === "/") {
487
+ lineComment = true;
488
+ index++;
489
+ continue;
490
+ }
491
+ if (character === "/" && nextCharacter === "*") {
492
+ blockComment = true;
493
+ index++;
494
+ continue;
495
+ }
496
+ if (character === "/") return undefined;
497
+ // Annex B HTML-like comments are not lexed here; give up rather than
498
+ // risk misreading the parameter list.
499
+ if (character === "<" && source.startsWith("!--", index + 1)) return undefined;
500
+ if (character === "-" && source.startsWith("->", index + 1)) return undefined;
501
+ if (character === "(") depth++;
502
+ else if (character === ")") {
503
+ depth--;
504
+ if (depth === 0 && square === 0 && curly === 0) {
505
+ const closeIndex = index;
506
+ if (parenthesizedArrow) {
507
+ index++;
508
+ skipTrivia();
509
+ const hasArrow = source[index] === "=" && source[index + 1] === ">";
510
+ if (!hasArrow && (!asyncMethodOrArrow || source[index] !== "{")) return undefined;
511
+ }
512
+ return source.slice(openIndex + 1, closeIndex);
513
+ }
514
+ if (depth < 0) return undefined;
515
+ } else if (character === "[") square++;
516
+ else if (character === "]") {
517
+ square--;
518
+ if (square < 0) return undefined;
519
+ } else if (character === "{") curly++;
520
+ else if (character === "}") {
521
+ if (templateDepths.at(-1) === curly) {
522
+ templateDepths.pop();
523
+ curly--;
524
+ quote = "`";
525
+ } else curly--;
526
+ if (curly < 0) return undefined;
527
+ }
528
+ }
529
+ return undefined;
530
+ }
531
+
532
+ /**
533
+ * Conservative defense in depth for defaulted second parameters, which
534
+ * JavaScript intentionally omits from Function.length. Ambiguous source is
535
+ * ignored so this check can never reject a valid provider on weak evidence.
536
+ */
537
+ function authStartHasHiddenInput(start: unknown): boolean {
538
+ let source: string;
539
+ try {
540
+ source = Function.prototype.toString.call(start);
541
+ } catch {
542
+ return false;
543
+ }
544
+ if (
545
+ !source ||
546
+ source.includes("[native code]") ||
547
+ /^\s*(?:async\s+)?function\s+bound\b/.test(source)
548
+ )
549
+ return false;
550
+
551
+ const parameters = authStartParameterList(source);
552
+ if (!parameters) return false;
553
+ const parts = splitAuthStartParameters(parameters);
554
+ if (!parts || parts.length < 2) return false;
555
+
556
+ // Require ordinary, readable source formatting. This intentionally fails
557
+ // open for minified output and for transpilers that rewrite defaults.
558
+ const commaIndex = parameters.indexOf(",");
559
+ if (commaIndex < 0 || !/\s/.test(parameters[commaIndex + 1] ?? "")) return false;
560
+ const first = parts[0].trim();
561
+ const second = parts[1].trim();
562
+ const identifier = /^[_$A-Za-z][_$A-Za-z0-9]*/;
563
+ const firstName = first.match(identifier)?.[0];
564
+ const secondName = second.match(identifier)?.[0];
565
+ if (!firstName || !secondName || firstName.length < 3 || secondName.length < 3) return false;
566
+ return /\s=\s/.test(second);
567
+ }
180
568
 
181
- export interface ProviderConfig<TOperations extends Record<string, ProviderOperation>> {
569
+ export interface ProviderDeclaration {
182
570
  id: string;
183
571
  version: string;
184
572
  runtime: "standard" | "shared" | "browser";
573
+ /** Provider business-logic target. Omit only while migrating a legacy provider. */
574
+ runtimeTarget?: ProviderRuntimeTarget;
185
575
  /**
186
576
  * Optional deployment overrides, passed through verbatim onto the returned
187
577
  * provider definition. The SDK types this field but does not deep-validate
@@ -189,20 +579,36 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
189
579
  * resolves omitted fields against the runtime deployment profiles.
190
580
  */
191
581
  deployment?: ProviderDeploymentOverrides;
582
+ /** Declares the HTTP capability binding. A bare object states use without configuration. */
583
+ http?: Record<string, never> | true;
584
+ /** Declares upstream host policy; this does not add a `ctx.allowedHosts` member. */
192
585
  allowedHosts?: string[];
193
- stealth?: {
194
- profile: string;
195
- platform: StealthPlatform;
196
- };
586
+ native?: NativeProviderConfig;
587
+ stealth?: StealthProfileSelection;
588
+ /** Declares proxy policy; this is provider intent and does not add a `ctx.proxy` member. */
197
589
  proxy?: ProviderProxyConfig;
590
+ ocr?: ProviderOcrConfig;
198
591
  stt?: ProviderSttConfig;
592
+ resolver?: ProviderResolverConfig;
199
593
  browser?: { engine: BrowserEngine };
200
594
  auth?: AuthConfig;
595
+ /** Declares the choice capability binding. A bare object states use without configuration. */
596
+ choice?: Record<string, never> | true;
201
597
  reviewed?: ProviderReviewed;
202
598
  access?: ProviderAccessConfig;
599
+ /** Declares secret requirements; this does not add a `ctx.secrets` member. */
203
600
  secrets?: ProviderSecretDeclaration[];
601
+ /** Declares the environment capability binding. A bare object states use without configuration. */
602
+ env?: Record<string, never> | true;
204
603
  credential?: CredentialDeclaration;
604
+ /** Declares provider context metadata; this does not add a `ctx.context` member. */
205
605
  context?: ContextDeclaration;
606
+ /** Declares the state capability binding. A bare object states use without configuration. */
607
+ state?: Record<string, never> | true;
608
+ /** Declares the cache capability binding. A bare object states use without configuration. */
609
+ cache?: Record<string, never> | true;
610
+ /** Declares the files capability binding. A bare object states use without configuration. */
611
+ files?: Record<string, never> | true;
206
612
  meta: {
207
613
  displayName: string;
208
614
  displayNameKey?: string;
@@ -222,27 +628,41 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
222
628
  implementationProfile?: ProviderImplementationProfile;
223
629
  contract?: {
224
630
  publicSchemaFieldNames?: "normalized";
631
+ readonly pinnedWireFieldPaths?: readonly {
632
+ readonly path: string;
633
+ readonly reason: string;
634
+ }[];
225
635
  };
226
636
  };
227
- operations: OperationMapConfig<TOperations>;
228
637
  healthMonitor?: ProviderHealthMonitorConfig;
229
638
  /** New name for `healthMonitor` (transitional alias); declaring both is a ValidationError. */
230
639
  healthProbe?: ProviderHealthMonitorConfig;
231
640
  healthJourneys?: readonly HealthJourneyDefinition[];
232
641
  }
233
642
 
234
- /** Define one provider operation with schema-driven handler inference. */
235
- export function defineOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
236
- operation: OperationConfig<TInput, TOutput>,
237
- ): OperationDefinition<TInput, TOutput> {
238
- return operation;
643
+ interface ProviderConfig<
644
+ TOperations extends Record<string, ProviderOperation>,
645
+ TContext = ProviderContext,
646
+ > extends ProviderDeclaration {
647
+ operations: OperationMapConfig<TOperations, TContext>;
239
648
  }
240
649
 
241
- /** Define a non-JSON provider operation with explicit transport metadata. */
242
- export function defineStreamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
243
- operation: StreamOperationConfig<TInput, TOutput>,
244
- ): OperationDefinition<TInput, TOutput> {
245
- return operation;
650
+ /** Define one factored provider operation with schema-driven handler inference. */
651
+ export function defineOperation<TContext>() {
652
+ return function operation<TInput extends SchemaLike, TOutput extends SchemaLike>(
653
+ config: OperationConfig<TInput, TOutput, TContext>,
654
+ ): OperationDefinition<TInput, TOutput, TContext> {
655
+ return config;
656
+ };
657
+ }
658
+
659
+ /** Define a factored non-JSON operation with explicit transport metadata. */
660
+ export function defineStreamOperation<TContext>() {
661
+ return function streamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
662
+ config: StreamOperationConfig<TInput, TOutput, TContext>,
663
+ ): OperationDefinition<TInput, TOutput, TContext> {
664
+ return config;
665
+ };
246
666
  }
247
667
 
248
668
  function assertObjectConfig(value: unknown): asserts value is Record<string, unknown> {
@@ -279,18 +699,153 @@ function assertLiteralField<TValue extends string>(
279
699
  );
280
700
  }
281
701
  }
282
- function validateProviderShape(config: unknown): void {
702
+
703
+ function validateProxiedOAuthParams(
704
+ value: unknown,
705
+ field: "authorizeParams" | "tokenParams",
706
+ providerId: string,
707
+ ): void {
708
+ if (value === undefined) return;
709
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
710
+ throw new ValidationError(
711
+ `Provider "${providerId}" auth.proxied.${field} must be an object of string values.`,
712
+ );
713
+ }
714
+ for (const [key, paramValue] of Object.entries(value)) {
715
+ if (!key.trim() || typeof paramValue !== "string") {
716
+ throw new ValidationError(
717
+ `Provider "${providerId}" auth.proxied.${field} must contain non-empty keys and string values.`,
718
+ );
719
+ }
720
+ if (
721
+ field === "authorizeParams" &&
722
+ PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())
723
+ ) {
724
+ throw new ValidationError(
725
+ `Provider "${providerId}" auth.proxied.authorizeParams cannot override reserved parameter "${key}".`,
726
+ );
727
+ }
728
+ }
729
+ }
730
+
731
+ function validateProxiedOAuthAuth(auth: Record<string, unknown>, providerId: string): void {
732
+ const proxied = auth.proxied;
733
+ if (auth.mode !== "oauth2_proxied") {
734
+ if (proxied !== undefined) {
735
+ throw new ValidationError(
736
+ `Provider "${providerId}" auth.proxied is only valid when auth.mode is "oauth2_proxied".`,
737
+ );
738
+ }
739
+ return;
740
+ }
741
+ if (!proxied || typeof proxied !== "object" || Array.isArray(proxied)) {
742
+ throw new ValidationError(
743
+ `Provider "${providerId}" with auth.mode "oauth2_proxied" must declare auth.proxied.`,
744
+ );
745
+ }
746
+ const config = Object.fromEntries(Object.entries(proxied));
747
+ for (const key of Object.keys(config)) {
748
+ if (!PROXIED_OAUTH_ALLOWED_FIELDS.has(key)) {
749
+ throw new ValidationError(
750
+ `Provider "${providerId}" has unknown auth.proxied field "${key}".`,
751
+ );
752
+ }
753
+ }
754
+ for (const field of PROXIED_OAUTH_REQUIRED_FIELDS) {
755
+ if (typeof config[field] !== "string" || !config[field].trim()) {
756
+ throw new ValidationError(
757
+ `Provider "${providerId}" auth.proxied.${field} must be a non-empty string.`,
758
+ );
759
+ }
760
+ }
761
+ for (const field of ["authorizeUrl", "tokenUrl"] as const) {
762
+ try {
763
+ const endpoint = new URL(String(config[field]));
764
+ if (
765
+ endpoint.protocol !== "https:" ||
766
+ endpoint.username ||
767
+ endpoint.password ||
768
+ endpoint.hash
769
+ ) {
770
+ throw new Error("invalid endpoint");
771
+ }
772
+ } catch {
773
+ throw new ValidationError(
774
+ `Provider "${providerId}" auth.proxied.${field} must be an absolute HTTPS URL without credentials or a fragment.`,
775
+ );
776
+ }
777
+ }
778
+ const customScheme = String(config.customScheme);
779
+ if (
780
+ !CUSTOM_SCHEME_REGEX.test(customScheme) ||
781
+ customScheme.toLowerCase().startsWith("http://") ||
782
+ customScheme.toLowerCase().startsWith("https://")
783
+ ) {
784
+ throw new ValidationError(
785
+ `Provider "${providerId}" auth.proxied.customScheme must be a non-HTTP custom-scheme URL prefix.`,
786
+ );
787
+ }
788
+ if (!PROXIED_OAUTH_PROFILE_REGEX.test(String(config.rewriteProfile))) {
789
+ throw new ValidationError(
790
+ `Provider "${providerId}" auth.proxied.rewriteProfile must be a kebab-case profile name.`,
791
+ );
792
+ }
793
+ if (!PROXIED_OAUTH_ENV_KEY_REGEX.test(String(config.clientIdEnvKey))) {
794
+ throw new ValidationError(
795
+ `Provider "${providerId}" auth.proxied.clientIdEnvKey must be an APIFuse-style uppercase environment key.`,
796
+ );
797
+ }
798
+ if (config.pkce !== undefined && config.pkce !== "S256" && config.pkce !== "none") {
799
+ throw new ValidationError(
800
+ `Provider "${providerId}" auth.proxied.pkce must be "S256" or "none".`,
801
+ );
802
+ }
803
+ validateProxiedOAuthParams(config.authorizeParams, "authorizeParams", providerId);
804
+ validateProxiedOAuthParams(config.tokenParams, "tokenParams", providerId);
805
+ }
806
+
807
+ function validateProviderDeclarationShape(config: unknown): void {
283
808
  assertObjectConfig(config);
284
809
  assertRequiredField(config, "id");
285
810
  assertRequiredField(config, "version", String(config.id));
286
811
  assertRequiredField(config, "runtime", String(config.id));
287
812
  assertRequiredField(config, "meta", String(config.id));
288
- assertRequiredField(config, "operations", String(config.id));
289
813
  if (typeof config.runtime === "string")
290
814
  assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
815
+ if (config.runtimeTarget !== undefined && typeof config.runtimeTarget !== "string") {
816
+ throw new ValidationError(
817
+ `Provider "${String(config.id)}" has invalid runtimeTarget: expected "vanilla" or "engine"`,
818
+ );
819
+ }
820
+ if (typeof config.runtimeTarget === "string")
821
+ assertLiteralField(
822
+ config.runtimeTarget,
823
+ "runtimeTarget",
824
+ VALID_RUNTIME_TARGETS,
825
+ String(config.id),
826
+ );
827
+ if (config.native !== undefined && config.runtime === "browser") {
828
+ throw new ValidationError(
829
+ `Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`,
830
+ {
831
+ fix: 'Use runtime: "standard" or runtime: "shared", or remove the native declaration.',
832
+ },
833
+ );
834
+ }
835
+ if (config.native !== undefined && config.runtimeTarget === "vanilla") {
836
+ throw new ValidationError(
837
+ `Provider "${String(config.id)}" cannot declare capability "native" with runtime target "vanilla"; native requires an engine-resident runtime`,
838
+ {
839
+ fix: 'Set runtimeTarget: "engine", or remove the native declaration.',
840
+ },
841
+ );
842
+ }
291
843
  const auth = config.auth;
292
844
  if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
293
845
  assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
846
+ if (auth && typeof auth === "object" && !Array.isArray(auth)) {
847
+ validateProxiedOAuthAuth(Object.fromEntries(Object.entries(auth)), String(config.id));
848
+ }
294
849
  if (auth && typeof auth === "object" && "exchange" in auth) {
295
850
  throw new ProviderError(
296
851
  `Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`,
@@ -316,6 +871,24 @@ function validateProviderShape(config: unknown): void {
316
871
  },
317
872
  );
318
873
  }
874
+ if (
875
+ auth &&
876
+ typeof auth === "object" &&
877
+ "flow" in auth &&
878
+ auth.flow &&
879
+ typeof auth.flow === "object" &&
880
+ "start" in auth.flow &&
881
+ typeof auth.flow.start === "function" &&
882
+ auth.flow.start.length <= 1 &&
883
+ authStartHasHiddenInput(auth.flow.start)
884
+ ) {
885
+ throw new ProviderError(
886
+ `Provider "${String(config.id)}" auth.flow.start must not declare an input parameter`,
887
+ {
888
+ fix: "Return a form turn from start(ctx), then receive user input in continue(ctx, input).",
889
+ },
890
+ );
891
+ }
319
892
  const access = config.access;
320
893
  if (access !== undefined) {
321
894
  if (!access || typeof access !== "object" || Array.isArray(access)) {
@@ -354,11 +927,33 @@ function validateProviderShape(config: unknown): void {
354
927
  }
355
928
  }
356
929
 
930
+ function validateProviderImplementationShape(config: { id: string }): void {
931
+ const configRecord = config as unknown as Record<string, unknown>;
932
+ assertRequiredField(configRecord, "operations", String(config.id));
933
+ }
934
+
357
935
  function validateProviderProxy(config: {
358
936
  id: string;
359
937
  proxy?: ProviderProxyConfig;
360
938
  secrets?: ProviderSecretDeclaration[];
361
939
  }): void {
940
+ for (const secret of config.secrets ?? []) {
941
+ if (isEngineOwnedTelemetryEnvName(secret.name)) {
942
+ throw new ValidationError(
943
+ `Provider "${config.id}" cannot declare engine-owned telemetry variable "${secret.name}"`,
944
+ {
945
+ fix: `Remove "${secret.name}" from provider secrets; trace export is configured only on the provider engine.`,
946
+ },
947
+ );
948
+ }
949
+ if (!isEngineOwnedProxyCredentialName(secret.name)) continue;
950
+ throw new ValidationError(
951
+ `Provider "${config.id}" cannot declare engine-owned proxy credential "${secret.name}"`,
952
+ {
953
+ fix: `Remove "${secret.name}" from provider secrets; configure it only on the provider engine.`,
954
+ },
955
+ );
956
+ }
362
957
  const proxy = config.proxy;
363
958
  if (proxy === undefined || typeof proxy === "boolean") {
364
959
  return;
@@ -371,11 +966,24 @@ function validateProviderProxy(config: {
371
966
  },
372
967
  );
373
968
  }
374
- rejectUnknownFields(proxy, new Set(["mode", "provider", "geo", "session"]), "proxy");
969
+ rejectUnknownFields(proxy, new Set(["mode", "provider", "providers", "geo", "session"]), "proxy");
375
970
  assertLiteralField(proxy.mode, "proxy.mode", VALID_PROVIDER_PROXY_MODES, config.id);
376
971
  if (proxy.provider !== undefined) {
377
972
  assertLiteralField(proxy.provider, "proxy.provider", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
378
973
  }
974
+ if (proxy.providers !== undefined) {
975
+ if (!Array.isArray(proxy.providers) || proxy.providers.length === 0) {
976
+ throw new ValidationError(
977
+ `Provider "${config.id}" has invalid proxy.providers: must be a non-empty array of proxy vendors.`,
978
+ {
979
+ fix: `Use proxy.providers: ["smartproxy", "nodemaven"] to declare an ordered fallback chain.`,
980
+ },
981
+ );
982
+ }
983
+ for (const vendor of proxy.providers) {
984
+ assertLiteralField(vendor, "proxy.providers[]", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
985
+ }
986
+ }
379
987
  if (proxy.geo !== undefined) {
380
988
  if (!proxy.geo || typeof proxy.geo !== "object" || Array.isArray(proxy.geo)) {
381
989
  throw new ValidationError(
@@ -409,7 +1017,7 @@ function validateProviderProxy(config: {
409
1017
  }
410
1018
  rejectUnknownFields(
411
1019
  proxy.session,
412
- new Set(["affinity", "lifetimeMinutes", "poolSize"]),
1020
+ new Set(["affinity", "lifetimeMinutes", "poolSize", "drainLeadSeconds"]),
413
1021
  "proxy.session",
414
1022
  );
415
1023
  if (proxy.session.affinity !== undefined) {
@@ -432,20 +1040,63 @@ function validateProviderProxy(config: {
432
1040
  `Provider "${config.id}" has invalid proxy.session.poolSize: must be a positive integer.`,
433
1041
  );
434
1042
  }
435
- }
436
- if (proxy.mode === "required" && proxy.provider === "smartproxy") {
437
- const hasSmartproxySecret = config.secrets?.some(
438
- (secret) => secret.name === SMARTPROXY_APP_KEY_SECRET && secret.required !== false,
439
- );
440
- if (!hasSmartproxySecret) {
1043
+ const drainLeadSeconds = proxy.session.drainLeadSeconds;
1044
+ if (
1045
+ drainLeadSeconds !== undefined &&
1046
+ (!Number.isFinite(drainLeadSeconds) || drainLeadSeconds <= 0)
1047
+ ) {
1048
+ throw new ValidationError(
1049
+ `Provider "${config.id}" has invalid proxy.session.drainLeadSeconds: must be a positive number of seconds.`,
1050
+ {
1051
+ fix: `Use proxy.session.drainLeadSeconds: 120 to receive the sticky-expiry drain event 120s before hard expiry.`,
1052
+ },
1053
+ );
1054
+ }
1055
+ // A drain lead longer than the sticky lifetime would fire the expiring
1056
+ // event before the session is even established, so the provider would
1057
+ // never get a usable window. Reject the contradiction at build time.
1058
+ if (
1059
+ drainLeadSeconds !== undefined &&
1060
+ lifetime !== undefined &&
1061
+ Number.isFinite(lifetime) &&
1062
+ drainLeadSeconds >= lifetime * 60
1063
+ ) {
441
1064
  throw new ValidationError(
442
- `Provider "${config.id}" requires Smartproxy egress but does not declare ${SMARTPROXY_APP_KEY_SECRET}.`,
1065
+ `Provider "${config.id}" has proxy.session.drainLeadSeconds (${drainLeadSeconds}s) greater than or equal to proxy.session.lifetimeMinutes (${lifetime}m).`,
443
1066
  {
444
- fix: `Add secrets: [{ name: "${SMARTPROXY_APP_KEY_SECRET}", required: true }] to the provider.`,
1067
+ fix: `Lower drainLeadSeconds below the sticky lifetime so the drain event leaves a usable session window.`,
445
1068
  },
446
1069
  );
447
1070
  }
448
1071
  }
1072
+ const vendorChain =
1073
+ proxy.providers && proxy.providers.length > 0
1074
+ ? proxy.providers
1075
+ : proxy.provider
1076
+ ? [proxy.provider]
1077
+ : [];
1078
+ // `decodo`/`custom` are deprecated vendor values (string-union members, so the
1079
+ // @deprecated symbol gate can't catch them — warn at validation time instead).
1080
+ const deprecatedVendors = vendorChain.filter(
1081
+ (vendor) => vendor === "decodo" || vendor === "custom",
1082
+ );
1083
+ if (
1084
+ proxy.mode === "required" &&
1085
+ vendorChain.length > 0 &&
1086
+ deprecatedVendors.length === vendorChain.length
1087
+ ) {
1088
+ throw new ValidationError(
1089
+ `Provider "${config.id}" requires proxy egress but declares only deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}.`,
1090
+ {
1091
+ fix: `Use proxy.provider or proxy.providers with "smartproxy" and/or "nodemaven".`,
1092
+ },
1093
+ );
1094
+ }
1095
+ if (deprecatedVendors.length > 0) {
1096
+ console.warn(
1097
+ `[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven".`,
1098
+ );
1099
+ }
449
1100
  }
450
1101
 
451
1102
  function validateProviderStt(config: { id: string; stt?: ProviderSttConfig }): void {
@@ -460,6 +1111,83 @@ function validateProviderStt(config: { id: string; stt?: ProviderSttConfig }): v
460
1111
  assertLiteralField(stt.mode, "stt.mode", VALID_PROVIDER_STT_MODES, config.id);
461
1112
  }
462
1113
 
1114
+ function validateProviderOcr(config: { id: string; ocr?: ProviderOcrConfig }): void {
1115
+ const ocr = config.ocr;
1116
+ if (ocr === undefined) return;
1117
+ if (!ocr || typeof ocr !== "object" || Array.isArray(ocr)) {
1118
+ throw new ValidationError(`Provider "${config.id}" has invalid ocr: must be an object.`, {
1119
+ fix: `Use ocr: { mode: "required" } or ocr: { mode: "optional" }.`,
1120
+ });
1121
+ }
1122
+ rejectUnknownFields(ocr, new Set(["mode"]), "ocr");
1123
+ assertLiteralField(ocr.mode, "ocr.mode", VALID_PROVIDER_OCR_MODES, config.id);
1124
+ }
1125
+
1126
+ function validateProviderResolver(config: { id: string; resolver?: ProviderResolverConfig }): void {
1127
+ const resolver = config.resolver;
1128
+ if (resolver === undefined) return;
1129
+ if (!resolver || typeof resolver !== "object" || Array.isArray(resolver)) {
1130
+ throw new ValidationError(`Provider "${config.id}" has invalid resolver: must be an object.`, {
1131
+ fix: `Set resolver for provider "${config.id}" to { vendors: ["2captcha"], kinds: ["turnstile"] }.`,
1132
+ });
1133
+ }
1134
+ rejectUnknownFields(
1135
+ resolver,
1136
+ new Set(["vendors", "kinds", "clientProfile"]),
1137
+ "resolver",
1138
+ config.id,
1139
+ );
1140
+ if (resolver.vendors !== undefined) {
1141
+ validateResolverLiteralArray(
1142
+ resolver.vendors,
1143
+ "resolver.vendors",
1144
+ VALID_PROVIDER_RESOLVER_VENDORS,
1145
+ config.id,
1146
+ );
1147
+ }
1148
+ validateResolverLiteralArray(
1149
+ resolver.kinds,
1150
+ "resolver.kinds",
1151
+ VALID_PROVIDER_CHALLENGE_KINDS,
1152
+ config.id,
1153
+ );
1154
+ if (
1155
+ resolver.clientProfile !== undefined &&
1156
+ (typeof resolver.clientProfile !== "string" || !resolver.clientProfile.trim())
1157
+ ) {
1158
+ throw new ValidationError(
1159
+ `Provider "${config.id}" has invalid resolver.clientProfile: must be a non-empty string.`,
1160
+ {
1161
+ fix: `Set resolver.clientProfile for provider "${config.id}" to a transport-owned profile name.`,
1162
+ },
1163
+ );
1164
+ }
1165
+ }
1166
+
1167
+ function validateResolverLiteralArray<TValue extends string>(
1168
+ value: readonly TValue[],
1169
+ field: string,
1170
+ validValues: readonly TValue[],
1171
+ providerId: string,
1172
+ ): void {
1173
+ if (!Array.isArray(value)) {
1174
+ throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an array.`, {
1175
+ fix: `Set ${field} for provider "${providerId}" to an array containing only: ${validValues.join(", ")}.`,
1176
+ });
1177
+ }
1178
+ for (const [index, item] of value.entries()) {
1179
+ if (typeof item === "string" && validValues.some((validValue) => validValue === item)) {
1180
+ continue;
1181
+ }
1182
+ throw new ValidationError(
1183
+ `Provider "${providerId}" has invalid ${field}[${index}]: ${JSON.stringify(item)}. Expected one of: ${validValues.join(", ")}`,
1184
+ {
1185
+ fix: `Set ${field}[${index}] for provider "${providerId}" to one of ${validValues.map((validValue) => `"${validValue}"`).join(", ")}.`,
1186
+ },
1187
+ );
1188
+ }
1189
+ }
1190
+
463
1191
  function validateOperationIds(
464
1192
  providerId: string,
465
1193
  operations: Record<string, ProviderOperation>,
@@ -499,52 +1227,40 @@ function assertNonEmptyString(
499
1227
  }
500
1228
  }
501
1229
 
502
- function validateToolRouterMetadata(
1230
+ function validateOperationMetadata(
503
1231
  providerId: string,
504
1232
  operations: Record<string, ProviderOperation>,
505
1233
  ): void {
506
1234
  for (const [operationName, operation] of Object.entries(operations)) {
507
- const toolRouter = operation.toolRouter;
508
- if (toolRouter === undefined) continue;
509
- if (!toolRouter || typeof toolRouter !== "object") {
510
- throw new ValidationError(
511
- `Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter: must be an object.`,
512
- {
513
- fix: `Remove operations.${operationName}.toolRouter or provide MCP-safe metadata.`,
514
- },
515
- );
516
- }
517
- if (toolRouter.name !== undefined && !MCP_TOOL_NAME_REGEX.test(toolRouter.name)) {
518
- throw new ValidationError(
519
- `Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter.name: expected an MCP-safe name.`,
520
- {
521
- fix: `Use letters, numbers, and underscores only, starting with a letter, for example "${providerId.replace(/[^A-Za-z0-9]+/g, "_")}__${operationName.replace(/[^A-Za-z0-9]+/g, "_")}".`,
522
- },
523
- );
524
- }
525
- if (toolRouter.riskClass !== undefined) {
1235
+ assertLiteralField(
1236
+ operation.riskClass,
1237
+ `operations.${operationName}.riskClass`,
1238
+ VALID_OPERATION_RISK_CLASSES,
1239
+ providerId,
1240
+ );
1241
+ if (operation.approval !== undefined) {
526
1242
  assertLiteralField(
527
- toolRouter.riskClass,
528
- `operations.${operationName}.toolRouter.riskClass`,
529
- VALID_OPERATION_RISK_CLASSES,
1243
+ operation.approval,
1244
+ `operations.${operationName}.approval`,
1245
+ VALID_OPERATION_APPROVAL_POLICIES,
530
1246
  providerId,
531
1247
  );
532
1248
  }
533
- if (toolRouter.approval !== undefined) {
1249
+ if (operation.connectionMode !== undefined) {
534
1250
  assertLiteralField(
535
- toolRouter.approval,
536
- `operations.${operationName}.toolRouter.approval`,
537
- VALID_OPERATION_APPROVAL_POLICIES,
1251
+ operation.connectionMode,
1252
+ `operations.${operationName}.connectionMode`,
1253
+ VALID_OPERATION_CONNECTION_MODES,
538
1254
  providerId,
539
1255
  );
540
1256
  }
541
1257
  if (
542
- toolRouter.connectionExternalRefParam !== undefined &&
543
- (typeof toolRouter.connectionExternalRefParam !== "string" ||
544
- toolRouter.connectionExternalRefParam.trim().length === 0)
1258
+ operation.connectionExternalRefParam !== undefined &&
1259
+ (typeof operation.connectionExternalRefParam !== "string" ||
1260
+ operation.connectionExternalRefParam.trim().length === 0)
545
1261
  ) {
546
1262
  throw new ValidationError(
547
- `Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter.connectionExternalRefParam: must be a non-empty string.`,
1263
+ `Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.connectionExternalRefParam: must be a non-empty string.`,
548
1264
  {
549
1265
  fix: `Use "externalRef" unless the operation has a documented public alias.`,
550
1266
  },
@@ -617,16 +1333,14 @@ function validateOperationContracts(
617
1333
  }
618
1334
  }
619
1335
 
620
- function validateOperationAnnotations(
1336
+ function validateOperationTimeouts(
621
1337
  providerId: string,
622
1338
  operations: Record<string, ProviderOperation>,
623
1339
  ): void {
624
1340
  for (const [operationName, operation] of Object.entries(operations)) {
625
- const annotations = operation.annotations;
626
- if (!annotations) continue;
627
- const timeoutMs = annotations.timeoutMs;
1341
+ const timeoutMs = operation.timeoutMs;
628
1342
  if (timeoutMs === undefined) continue;
629
- const field = `operations.${operationName}.annotations.timeoutMs`;
1343
+ const field = `operations.${operationName}.timeoutMs`;
630
1344
  if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs))
631
1345
  throw new ValidationError(
632
1346
  `Provider "${providerId}" has invalid ${field}: must be an integer number of milliseconds.`,
@@ -702,6 +1416,36 @@ function validateOperationObservability(
702
1416
  }
703
1417
  }
704
1418
 
1419
+ function validateOperationErrorCodes(
1420
+ providerId: string,
1421
+ operations: Record<string, ProviderOperation>,
1422
+ ): void {
1423
+ for (const [operationName, operation] of Object.entries(operations)) {
1424
+ for (const [index, errorCode] of (operation.errorCodes ?? []).entries()) {
1425
+ if (
1426
+ errorCode.status !== undefined &&
1427
+ !VALID_OPERATION_ERROR_STATUSES.some((status) => status === errorCode.status)
1428
+ ) {
1429
+ const field = `operations.${operationName}.errorCodes[${index}].status`;
1430
+ throw new ValidationError(
1431
+ `Provider "${providerId}" has invalid ${field}: ${String(errorCode.status)} is not an emittable provider error status.`,
1432
+ {
1433
+ fix: `Set ${field} to one of ${VALID_OPERATION_ERROR_STATUSES.join(", ")}, or omit it.`,
1434
+ },
1435
+ );
1436
+ }
1437
+ if (
1438
+ errorCode.status !== undefined &&
1439
+ SDK_RUNTIME_OWNED_ERROR_CODES.has(errorCode.code)
1440
+ ) {
1441
+ console.warn(
1442
+ `[provider-sdk] Provider "${providerId}" operation "${operationName}" declares status ${errorCode.status} for SDK-owned error code "${errorCode.code}"; the declared status is documentation-only and will be ignored at runtime.`,
1443
+ );
1444
+ }
1445
+ }
1446
+ }
1447
+ }
1448
+
705
1449
  const JSON_TRANSPORT_FIELDS = new Set(["kind"]);
706
1450
  const SSE_TRANSPORT_FIELDS = new Set([
707
1451
  "kind",
@@ -972,6 +1716,7 @@ const HEALTH_CHECK_CASE_FIELDS = new Set([
972
1716
  "input",
973
1717
  "prepareInput",
974
1718
  "assertions",
1719
+ "scenario",
975
1720
  "degradedThresholdMs",
976
1721
  "timeoutMs",
977
1722
  "expectedStatus",
@@ -1027,7 +1772,12 @@ function suggestField(unknown: string, candidates: ReadonlySet<string>): string
1027
1772
  return best;
1028
1773
  }
1029
1774
 
1030
- function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldPath: string): void {
1775
+ function rejectUnknownFields(
1776
+ value: object,
1777
+ allowed: ReadonlySet<string>,
1778
+ fieldPath: string,
1779
+ providerId?: string,
1780
+ ): void {
1031
1781
  for (const key of Object.keys(value)) {
1032
1782
  if (allowed.has(key)) continue;
1033
1783
  const hint = suggestField(key, allowed);
@@ -1035,7 +1785,11 @@ function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldP
1035
1785
  hint
1036
1786
  ? `Unknown field "${key}" on ${fieldPath}. Did you mean "${hint}"?`
1037
1787
  : `Unknown field "${key}" on ${fieldPath}.`,
1038
- { fix: `Remove ${fieldPath}.${key} or rename it.` },
1788
+ {
1789
+ fix: providerId
1790
+ ? `Remove ${fieldPath}.${key} from provider "${providerId}" or rename it.`
1791
+ : `Remove ${fieldPath}.${key} or rename it.`,
1792
+ },
1039
1793
  );
1040
1794
  }
1041
1795
  }
@@ -1212,7 +1966,19 @@ function validateHealthCheckCase(
1212
1966
  throw new ValidationError(
1213
1967
  `Provider "${providerId}" ${fieldPath}.name must be a non-empty string.`,
1214
1968
  );
1215
- if (typeof c.assertions !== "function")
1969
+ const hasScenario = c.scenario !== undefined;
1970
+ const imperativeFields = [
1971
+ ...(c.prepareInput === undefined ? [] : ["prepareInput"]),
1972
+ ...(c.assertions === undefined ? [] : ["assertions"]),
1973
+ ];
1974
+ if (hasScenario && imperativeFields.length > 0)
1975
+ throw new ValidationError(
1976
+ `Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" cannot declare scenario with ${imperativeFields.join(" and ")}.`,
1977
+ {
1978
+ fix: `Remove ${imperativeFields.map((field) => `${fieldPath}.${field}`).join(" and ")} and keep ${fieldPath}.scenario, or remove ${fieldPath}.scenario to keep the imperative hooks.`,
1979
+ },
1980
+ );
1981
+ if (!hasScenario && typeof c.assertions !== "function")
1216
1982
  throw new ValidationError(
1217
1983
  `Provider "${providerId}" ${fieldPath}.assertions must be a function.`,
1218
1984
  {
@@ -1223,6 +1989,24 @@ function validateHealthCheckCase(
1223
1989
  throw new ValidationError(
1224
1990
  `Provider "${providerId}" ${fieldPath}.prepareInput must be a function.`,
1225
1991
  );
1992
+ if (hasScenario) {
1993
+ const parsedScenario = HealthScenarioSchema.safeParse(c.scenario);
1994
+ if (!parsedScenario.success)
1995
+ throw new ValidationError(
1996
+ `Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" has an invalid scenario: it must conform to HealthScenario.`,
1997
+ { fix: `Build ${fieldPath}.scenario with defineHealthScenario().` },
1998
+ );
1999
+ const unrelatedOperation = parsedScenario.data.coversOperations.find(
2000
+ (operationId) => operationId !== operationName,
2001
+ );
2002
+ if (unrelatedOperation !== undefined)
2003
+ throw new ValidationError(
2004
+ `Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" scenario.coversOperations cannot claim unrelated operation "${unrelatedOperation}".`,
2005
+ {
2006
+ fix: `Set ${fieldPath}.scenario.coversOperations to ["${operationName}"].`,
2007
+ },
2008
+ );
2009
+ }
1226
2010
  if (
1227
2011
  c.degradedThresholdMs !== undefined &&
1228
2012
  (typeof c.degradedThresholdMs !== "number" ||
@@ -1372,6 +2156,7 @@ const HEALTH_JOURNEY_FIELDS = new Set([
1372
2156
  "manualTrigger",
1373
2157
  "steps",
1374
2158
  "run",
2159
+ "scenario",
1375
2160
  ]);
1376
2161
  const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set(["kind", "interval", "jitter", "randomize"]);
1377
2162
  const HEALTH_JOURNEY_STEP_FIELDS = new Set([
@@ -1907,6 +2692,14 @@ function validateHealthJourneys(
1907
2692
  }
1908
2693
  if (journey.manualTrigger !== undefined)
1909
2694
  validateHealthJourneyManualTrigger(providerId, journey.id, journey.manualTrigger);
2695
+ if (journey.scenario !== undefined && journey.smsMatchers !== undefined)
2696
+ throw new ValidationError(
2697
+ `Provider "${providerId}" healthJourneys.${journey.id}.smsMatchers is not allowed on declarative scenarios.`,
2698
+ );
2699
+ if (journey.scenario !== undefined && journey.requiredSecrets !== undefined)
2700
+ throw new ValidationError(
2701
+ `Provider "${providerId}" healthJourneys.${journey.id}.requiredSecrets is not allowed on declarative scenarios.`,
2702
+ );
1910
2703
  if (journey.timeout !== undefined)
1911
2704
  assertIsoDuration(
1912
2705
  journey.timeout,
@@ -1977,6 +2770,26 @@ function validateOperationFixtures(
1977
2770
  fix: `Add operations.${operationName}.handler as an async function with signature (ctx, input) => Promise<output>`,
1978
2771
  },
1979
2772
  );
2773
+ if (operation.fixtures?.recordedAt !== undefined) {
2774
+ const recordedAt = operation.fixtures.recordedAt;
2775
+ const parsed =
2776
+ typeof recordedAt === "string"
2777
+ ? new Date(`${recordedAt}T00:00:00.000Z`)
2778
+ : new Date(Number.NaN);
2779
+ const isCalendarDate =
2780
+ typeof recordedAt === "string" &&
2781
+ /^\d{4}-\d{2}-\d{2}$/.test(recordedAt) &&
2782
+ !Number.isNaN(parsed.getTime()) &&
2783
+ parsed.toISOString().slice(0, 10) === recordedAt;
2784
+ const kstToday = new Date(Date.now() + 9 * 60 * 60 * 1000).toISOString().slice(0, 10);
2785
+ if (!isCalendarDate || recordedAt > kstToday)
2786
+ throw new ValidationError(
2787
+ `Fixture recordedAt must be a valid, non-future KST calendar date for provider "${providerId}" operation "${operationName}"`,
2788
+ {
2789
+ fix: `Set operations.${operationName}.fixtures.recordedAt to the KST capture date in YYYY-MM-DD format; it must not be in the future.`,
2790
+ },
2791
+ );
2792
+ }
1980
2793
  if (operation.fixtures?.request !== undefined) {
1981
2794
  const result = safeParseSchemaSync(
1982
2795
  operation.input,
@@ -2010,6 +2823,31 @@ function validateOperationFixtures(
2010
2823
  }
2011
2824
  }
2012
2825
 
2826
+ function resolveOperationFixtureRequests<TOperations extends Record<string, ProviderOperation>>(
2827
+ operations: TOperations,
2828
+ ): TOperations {
2829
+ let changed = false;
2830
+ const resolvedOperations = Object.fromEntries(
2831
+ Object.entries(operations).map(([operationName, operation]) => {
2832
+ if (operation.fixtures?.request === undefined) return [operationName, operation];
2833
+ const request = resolveHealthCheckInputDateTokens(operation.fixtures.request);
2834
+ if (request === operation.fixtures.request) return [operationName, operation];
2835
+ changed = true;
2836
+ return [
2837
+ operationName,
2838
+ {
2839
+ ...operation,
2840
+ fixtures: {
2841
+ ...operation.fixtures,
2842
+ request,
2843
+ },
2844
+ },
2845
+ ];
2846
+ }),
2847
+ ) as TOperations;
2848
+ return changed ? resolvedOperations : operations;
2849
+ }
2850
+
2013
2851
  /**
2014
2852
  * Shallow shape guard only: the `deployment` object is passed through
2015
2853
  * verbatim and deliberately not deep-validated by the SDK — the APIFuse
@@ -2023,33 +2861,57 @@ function validateProviderDeployment(providerId: string, deployment: unknown): vo
2023
2861
  });
2024
2862
  }
2025
2863
 
2026
- export function defineProvider<
2864
+ /** The second authoring phase for a declaration established by defineProvider. */
2865
+ export type ProviderBuilder<TConfig extends ProviderDeclaration> = <
2027
2866
  TOperations extends Record<string, ProviderOperation>,
2028
- TConfig extends ProviderConfig<TOperations>,
2029
2867
  >(
2030
- config: TConfig & AuthStartNoInputGuard<TConfig>,
2031
- ): ProviderDefinition & { operations: OperationMapConfig<TOperations> } {
2032
- validateProviderShape(config);
2868
+ implementation: {
2869
+ operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
2870
+ },
2871
+ ) => Omit<ProviderDefinition, "operations"> & {
2872
+ operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
2873
+ };
2874
+
2875
+ /** Extract the declaration-derived operation context from a provider builder. */
2876
+ export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer TConfig>
2877
+ ? ProviderContext<TConfig>
2878
+ : never;
2879
+
2880
+ /** Annotate an operation while preserving the declaration-derived context. */
2881
+ export type OperationDefinitionFor<
2882
+ TBuilder,
2883
+ TInput extends SchemaLike = SchemaLike,
2884
+ TOutput extends SchemaLike = SchemaLike,
2885
+ > = OperationDefinition<TInput, TOutput, ProviderContextOf<TBuilder>>;
2886
+
2887
+ /** Annotate a built provider while preserving the declaration-derived context. */
2888
+ export type ProviderDefinitionFor<TBuilder> = ProviderDefinition<ProviderContextOf<TBuilder>>;
2889
+
2890
+ /** Establish a provider declaration before its operations are contextually typed. */
2891
+ export function defineProvider<const TConfig extends ProviderDeclaration>(
2892
+ declaration: TConfig &
2893
+ Record<Exclude<keyof TConfig, keyof ProviderDeclaration>, never> &
2894
+ AuthStartNoInputGuard<TConfig>,
2895
+ ): ProviderBuilder<TConfig> {
2896
+ validateProviderDeclaration(declaration);
2897
+ const buildProvider = <TOperations extends Record<string, ProviderOperation>>(
2898
+ implementation: {
2899
+ operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
2900
+ },
2901
+ ) =>
2902
+ finalizeProvider({
2903
+ ...declaration,
2904
+ ...implementation,
2905
+ } as ProviderConfig<TOperations, ProviderContext<TConfig>>);
2906
+ return buildProvider as ProviderBuilder<TConfig>;
2907
+ }
2908
+
2909
+ function validateProviderDeclaration(config: ProviderDeclaration): void {
2910
+ validateProviderDeclarationShape(config);
2033
2911
  if (!CONNECTOR_ID_REGEX.test(config.id))
2034
2912
  throw new ProviderError(`Invalid provider id: "${config.id}"`, {
2035
2913
  fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
2036
2914
  });
2037
- if (Object.keys(config.operations).length === 0)
2038
- throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
2039
- fix: "Add at least one operation to the operations object",
2040
- });
2041
- validateOperationIds(config.id, config.operations);
2042
- validateOperationAnnotations(config.id, config.operations);
2043
- validateOperationObservability(config.id, config.operations);
2044
- validateOperationTransports(config.id, config.operations);
2045
- validateOperationContracts(config.id, config.operations);
2046
- validateToolRouterMetadata(config.id, config.operations);
2047
- const journeyCoveredOperations = validateHealthJourneys(
2048
- config.id,
2049
- config.operations,
2050
- config.healthJourneys,
2051
- );
2052
- validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
2053
2915
  if (config.healthMonitor !== undefined && config.healthProbe !== undefined)
2054
2916
  throw new ValidationError(
2055
2917
  `Provider "${config.id}" declares both healthMonitor and healthProbe. They are aliases; declare exactly one.`,
@@ -2062,10 +2924,18 @@ export function defineProvider<
2062
2924
  config.healthProbe ?? config.healthMonitor,
2063
2925
  config.healthProbe !== undefined ? "healthProbe" : "healthMonitor",
2064
2926
  );
2065
- validateOperationFixtures(config.id, config.operations);
2066
2927
  validateProviderDeployment(config.id, config.deployment);
2928
+ try {
2929
+ validateNativeProviderConfig(config.native);
2930
+ } catch (error) {
2931
+ if (error instanceof NativeEgressPolicyValidationError)
2932
+ throw new ValidationError(error.message);
2933
+ throw error;
2934
+ }
2067
2935
  validateProviderProxy(config);
2936
+ validateProviderOcr(config);
2068
2937
  validateProviderStt(config);
2938
+ validateProviderResolver(config);
2069
2939
  if (config.runtime === "browser" && !config.browser)
2070
2940
  throw new ProviderError(
2071
2941
  `Provider "${config.id}" must define browser.engine when runtime is "browser"`,
@@ -2078,30 +2948,77 @@ export function defineProvider<
2078
2948
  `Provider "${config.id}" cannot define browser config unless runtime is "browser"`,
2079
2949
  { fix: 'Set runtime: "browser" or remove the browser config' },
2080
2950
  );
2081
- return {
2951
+ validateFailClosedProviderDeclaration(config);
2952
+ }
2953
+
2954
+ function finalizeProvider<
2955
+ TOperations extends Record<string, ProviderOperation>,
2956
+ TContext,
2957
+ >(
2958
+ config: ProviderConfig<TOperations, TContext>,
2959
+ ): Omit<ProviderDefinition, "operations"> & {
2960
+ operations: OperationMapConfig<TOperations, TContext>;
2961
+ } {
2962
+ validateProviderImplementationShape(config);
2963
+ const operations = resolveOperationFixtureRequests(config.operations);
2964
+ if (Object.keys(config.operations).length === 0)
2965
+ throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
2966
+ fix: "Add at least one operation to the operations object",
2967
+ });
2968
+ validateOperationIds(config.id, config.operations);
2969
+ validateOperationTimeouts(config.id, config.operations);
2970
+ validateOperationObservability(config.id, config.operations);
2971
+ validateOperationErrorCodes(config.id, config.operations);
2972
+ validateOperationTransports(config.id, config.operations);
2973
+ validateOperationContracts(config.id, config.operations);
2974
+ validateOperationMetadata(config.id, config.operations);
2975
+ const journeyCoveredOperations = validateHealthJourneys(
2976
+ config.id,
2977
+ config.operations,
2978
+ config.healthJourneys,
2979
+ );
2980
+ validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
2981
+ validateOperationFixtures(config.id, operations);
2982
+ const provider: Omit<ProviderDefinition, "operations"> & {
2983
+ operations: OperationMapConfig<TOperations, TContext>;
2984
+ } = {
2082
2985
  id: config.id,
2083
2986
  version: config.version,
2084
2987
  runtime: config.runtime,
2988
+ runtimeTarget: config.runtimeTarget,
2085
2989
  // Verbatim passthrough: deployment validation and profile resolution
2086
2990
  // are owned by the APIFuse registry builder, not the SDK.
2087
2991
  deployment: config.deployment,
2992
+ http: config.http,
2088
2993
  allowedHosts: config.allowedHosts,
2994
+ native: config.native,
2089
2995
  stealth: config.stealth,
2090
2996
  proxy: config.proxy,
2997
+ ocr: config.ocr,
2091
2998
  stt: config.stt,
2999
+ resolver: config.resolver,
2092
3000
  browser: config.browser,
2093
3001
  auth: config.auth,
3002
+ choice: config.choice,
2094
3003
  reviewed: config.reviewed,
2095
3004
  access: config.access,
2096
3005
  secrets: config.secrets,
3006
+ env: config.env,
2097
3007
  credential: config.credential,
2098
3008
  context: config.context,
3009
+ state: config.state,
3010
+ cache: config.cache,
3011
+ files: config.files,
2099
3012
  meta: config.meta,
2100
- operations: config.operations,
3013
+ operations,
2101
3014
  // Transitional healthMonitor → healthProbe alias: mirror whichever field
2102
3015
  // was declared onto both so old and new consumers keep working.
2103
3016
  healthMonitor: config.healthMonitor ?? config.healthProbe,
2104
3017
  healthProbe: config.healthProbe ?? config.healthMonitor,
2105
3018
  healthJourneys: config.healthJourneys,
2106
3019
  };
3020
+ // Declaration validation never invokes handlers, so their declaration-bound
3021
+ // context parameter is irrelevant to the runtime ProviderDefinition shape.
3022
+ validateFailClosedOperationDeclaration(provider as unknown as ProviderDefinition);
3023
+ return provider;
2107
3024
  }