@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/dist/define.js CHANGED
@@ -1,26 +1,82 @@
1
1
  import ms from "ms";
2
+ import { validateFailClosedOperationDeclaration, validateFailClosedProviderDeclaration, } from "./declaration-validation.js";
3
+ import { SDK_RUNTIME_OWNED_ERROR_CODES } from "./error-resolution.js";
2
4
  import { ProviderError, ValidationError } from "./errors.js";
5
+ import { isEngineOwnedProxyCredentialName, isEngineOwnedTelemetryEnvName } from "./engine.js";
6
+ import { HealthScenarioSchema } from "./health-scenario.js";
7
+ import { NativeEgressPolicyValidationError, validateNativeProviderConfig, } from "./native-egress-policy.js";
3
8
  import { safeParseSchemaSync } from "./schema.js";
4
- import { HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX, HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN, HEALTH_CHECK_TIMEOUT_MS_MAX, HEALTH_CHECK_TIMEOUT_MS_MIN, OPERATION_TIMEOUT_MS_MAX, OPERATION_TIMEOUT_MS_MIN, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types.js";
9
+ import { resolveHealthCheckInputDateTokens } from "./server/self-test-input-tokens.js";
10
+ import { HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX, HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN, HEALTH_CHECK_TIMEOUT_MS_MAX, HEALTH_CHECK_TIMEOUT_MS_MIN, OPERATION_TIMEOUT_MS_MAX, OPERATION_TIMEOUT_MS_MIN, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, VALID_OPERATION_ERROR_STATUSES, } from "./types.js";
5
11
  const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
6
12
  const OPERATION_ID_REGEX = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
7
13
  const VALID_RUNTIMES = ["standard", "shared", "browser"];
8
- const VALID_AUTH_MODES = ["none", "platform-managed", "credentials", "oauth2"];
14
+ const VALID_RUNTIME_TARGETS = ["vanilla", "engine"];
15
+ const VALID_AUTH_MODES = [
16
+ "none",
17
+ "platform-managed",
18
+ "credentials",
19
+ "oauth2",
20
+ "oauth2_proxied",
21
+ ];
22
+ const PROXIED_OAUTH_REQUIRED_FIELDS = [
23
+ "authorizeUrl",
24
+ "tokenUrl",
25
+ "customScheme",
26
+ "rewriteProfile",
27
+ "clientIdEnvKey",
28
+ ];
29
+ const PROXIED_OAUTH_ALLOWED_FIELDS = new Set([
30
+ ...PROXIED_OAUTH_REQUIRED_FIELDS,
31
+ "pkce",
32
+ "authorizeParams",
33
+ "tokenParams",
34
+ ]);
35
+ const PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS = new Set([
36
+ "client_id",
37
+ "response_type",
38
+ "state",
39
+ "code_challenge",
40
+ "code_challenge_method",
41
+ ]);
42
+ const PROXIED_OAUTH_PROFILE_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
43
+ const PROXIED_OAUTH_ENV_KEY_REGEX = /^[A-Z][A-Z0-9_]*__[A-Z0-9_]+$/;
44
+ const CUSTOM_SCHEME_REGEX = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/\S+$/;
9
45
  const VALID_PROVIDER_ACCESS_VISIBILITIES = ["public", "early_access"];
10
46
  const VALID_PROVIDER_PROXY_MODES = ["disabled", "optional", "required"];
11
- const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "decodo", "custom"];
47
+ const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "nodemaven", "decodo", "custom"];
12
48
  const VALID_PROVIDER_PROXY_AFFINITIES = [
13
49
  "request",
14
50
  "operation",
15
51
  "auth-flow",
16
52
  "connection",
17
53
  ];
54
+ const VALID_PROVIDER_OCR_MODES = ["optional", "required"];
18
55
  const VALID_PROVIDER_STT_MODES = ["optional", "required"];
19
- const SMARTPROXY_APP_KEY_SECRET = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
56
+ function exhaustiveLiteralArray() {
57
+ return (values, ..._missing) => values;
58
+ }
59
+ export const VALID_PROVIDER_RESOLVER_VENDORS = exhaustiveLiteralArray()([
60
+ "browser",
61
+ "capsolver",
62
+ "capmonster",
63
+ "2captcha",
64
+ "custom",
65
+ ]);
66
+ export const VALID_PROVIDER_CHALLENGE_KINDS = exhaustiveLiteralArray()([
67
+ "turnstile",
68
+ "recaptcha_v2",
69
+ "recaptcha_v3",
70
+ "hcaptcha",
71
+ "cloudflare_interstitial",
72
+ "aws_waf",
73
+ "akamai_sec_cpt",
74
+ "akamai_sensor",
75
+ ]);
20
76
  const RESERVED_OPERATION_IDS = new Set(["auth", "health"]);
21
- const MCP_TOOL_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_]{0,127}$/;
22
77
  const VALID_OPERATION_RISK_CLASSES = ["read", "write", "destructive", "external-send"];
23
78
  const VALID_OPERATION_APPROVAL_POLICIES = ["never", "risk-based", "always"];
79
+ const VALID_OPERATION_CONNECTION_MODES = ["none", "optional", "required"];
24
80
  const VALID_OPERATION_TRANSPORT_KINDS = ["json", "sse", "http-stream", "websocket"];
25
81
  const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
26
82
  const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
@@ -42,13 +98,357 @@ function parsePositiveMsDuration(value) {
42
98
  return undefined;
43
99
  return parsed;
44
100
  }
45
- /** Define one provider operation with schema-driven handler inference. */
46
- export function defineOperation(operation) {
47
- return operation;
101
+ function splitAuthStartParameters(parameters) {
102
+ const parts = [];
103
+ let start = 0;
104
+ let round = 0;
105
+ let square = 0;
106
+ let curly = 0;
107
+ let quote;
108
+ let escaped = false;
109
+ let lineComment = false;
110
+ let blockComment = false;
111
+ const templateDepths = [0];
112
+ templateDepths.length = 0;
113
+ for (let index = 0; index < parameters.length; index++) {
114
+ const character = parameters[index];
115
+ const nextCharacter = parameters[index + 1];
116
+ if (lineComment) {
117
+ if (character === "\n" || character === "\r")
118
+ lineComment = false;
119
+ else
120
+ continue;
121
+ }
122
+ if (blockComment) {
123
+ if (character === "*" && nextCharacter === "/") {
124
+ blockComment = false;
125
+ index++;
126
+ }
127
+ continue;
128
+ }
129
+ if (quote) {
130
+ if (escaped) {
131
+ escaped = false;
132
+ }
133
+ else if (character === "\\") {
134
+ escaped = true;
135
+ }
136
+ else if (quote === "`" && character === "$" && nextCharacter === "{") {
137
+ curly++;
138
+ templateDepths.push(curly);
139
+ quote = undefined;
140
+ index++;
141
+ }
142
+ else if (character === quote) {
143
+ quote = undefined;
144
+ }
145
+ continue;
146
+ }
147
+ if (character === "'" || character === '"' || character === "`") {
148
+ quote = character;
149
+ continue;
150
+ }
151
+ if (character === "/" && nextCharacter === "/") {
152
+ lineComment = true;
153
+ index++;
154
+ continue;
155
+ }
156
+ if (character === "/" && nextCharacter === "*") {
157
+ blockComment = true;
158
+ index++;
159
+ continue;
160
+ }
161
+ if (character === "/")
162
+ return undefined;
163
+ // Annex B HTML-like comments are not lexed here; give up rather than
164
+ // risk misreading the parameter list.
165
+ if (character === "<" && parameters.startsWith("!--", index + 1))
166
+ return undefined;
167
+ if (character === "-" && parameters.startsWith("->", index + 1))
168
+ return undefined;
169
+ if (character === "(")
170
+ round++;
171
+ else if (character === ")")
172
+ round--;
173
+ else if (character === "[")
174
+ square++;
175
+ else if (character === "]")
176
+ square--;
177
+ else if (character === "{")
178
+ curly++;
179
+ else if (character === "}") {
180
+ if (templateDepths.at(-1) === curly) {
181
+ templateDepths.pop();
182
+ curly--;
183
+ quote = "`";
184
+ }
185
+ else
186
+ curly--;
187
+ }
188
+ else if (character === "," && round === 0 && square === 0 && curly === 0) {
189
+ parts.push(parameters.slice(start, index));
190
+ start = index + 1;
191
+ }
192
+ if (round < 0 || square < 0 || curly < 0)
193
+ return undefined;
194
+ }
195
+ if (quote ||
196
+ blockComment ||
197
+ templateDepths.length > 0 ||
198
+ round !== 0 ||
199
+ square !== 0 ||
200
+ curly !== 0)
201
+ return undefined;
202
+ parts.push(parameters.slice(start));
203
+ return parts;
204
+ }
205
+ function authStartParameterList(source) {
206
+ let index = 0;
207
+ while (index < source.length && /\s/.test(source[index] ?? ""))
208
+ index++;
209
+ if (index >= source.length)
210
+ return undefined;
211
+ let openIndex = -1;
212
+ let parenthesizedArrow = false;
213
+ let asyncMethodOrArrow = false;
214
+ const skipTrivia = () => {
215
+ while (index < source.length) {
216
+ if (/\s/.test(source[index] ?? "")) {
217
+ index++;
218
+ continue;
219
+ }
220
+ if (source[index] === "/" && source[index + 1] === "/") {
221
+ index += 2;
222
+ while (index < source.length && source[index] !== "\n" && source[index] !== "\r")
223
+ index++;
224
+ continue;
225
+ }
226
+ if (source[index] === "/" && source[index + 1] === "*") {
227
+ const end = source.indexOf("*/", index + 2);
228
+ if (end < 0) {
229
+ index = source.length;
230
+ return;
231
+ }
232
+ index = end + 2;
233
+ continue;
234
+ }
235
+ return;
236
+ }
237
+ };
238
+ const initial = source.slice(index);
239
+ const isAsync = initial.startsWith("async") && !/[\w$]/.test(initial[5] ?? "");
240
+ if (isAsync) {
241
+ index += 5;
242
+ skipTrivia();
243
+ }
244
+ const afterAsync = source.slice(index);
245
+ const isFunction = afterAsync.startsWith("function") && !/[\w$]/.test(afterAsync[8] ?? "");
246
+ if (isFunction) {
247
+ index += 8;
248
+ skipTrivia();
249
+ if (source[index] === "*") {
250
+ index++;
251
+ skipTrivia();
252
+ }
253
+ }
254
+ else if (source[index] === "*") {
255
+ index++;
256
+ skipTrivia();
257
+ }
258
+ if (source[index] === "(") {
259
+ openIndex = index;
260
+ parenthesizedArrow = !isFunction;
261
+ asyncMethodOrArrow = isAsync && !isFunction;
262
+ }
263
+ else if (isFunction) {
264
+ if (!/[A-Za-z_$]/.test(source[index] ?? ""))
265
+ return undefined;
266
+ index++;
267
+ while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? ""))
268
+ index++;
269
+ skipTrivia();
270
+ if (source[index] !== "(")
271
+ return undefined;
272
+ openIndex = index;
273
+ }
274
+ else {
275
+ const identifierStart = index;
276
+ if (!/[A-Za-z_$]/.test(source[index] ?? ""))
277
+ return undefined;
278
+ index++;
279
+ while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? ""))
280
+ index++;
281
+ const firstIdentifier = source.slice(identifierStart, index);
282
+ skipTrivia();
283
+ if (source[index] === "=" && source[index + 1] === ">")
284
+ return undefined;
285
+ if (source[index] !== "(") {
286
+ if (firstIdentifier !== "get" && firstIdentifier !== "set")
287
+ return undefined;
288
+ if (!/[A-Za-z_$]/.test(source[index] ?? ""))
289
+ return undefined;
290
+ index++;
291
+ while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? ""))
292
+ index++;
293
+ skipTrivia();
294
+ }
295
+ if (source[index] !== "(")
296
+ return undefined;
297
+ openIndex = index;
298
+ }
299
+ if (openIndex < 0)
300
+ return undefined;
301
+ let depth = 1;
302
+ let square = 0;
303
+ let curly = 0;
304
+ let quote;
305
+ let escaped = false;
306
+ let lineComment = false;
307
+ let blockComment = false;
308
+ const templateDepths = [0];
309
+ templateDepths.length = 0;
310
+ for (index = openIndex + 1; index < source.length; index++) {
311
+ const character = source[index];
312
+ const nextCharacter = source[index + 1];
313
+ if (lineComment) {
314
+ if (character === "\n" || character === "\r")
315
+ lineComment = false;
316
+ else
317
+ continue;
318
+ }
319
+ if (blockComment) {
320
+ if (character === "*" && nextCharacter === "/") {
321
+ blockComment = false;
322
+ index++;
323
+ }
324
+ continue;
325
+ }
326
+ if (quote) {
327
+ if (escaped)
328
+ escaped = false;
329
+ else if (character === "\\")
330
+ escaped = true;
331
+ else if (quote === "`" && character === "$" && nextCharacter === "{") {
332
+ curly++;
333
+ templateDepths.push(curly);
334
+ quote = undefined;
335
+ index++;
336
+ }
337
+ else if (character === quote)
338
+ quote = undefined;
339
+ continue;
340
+ }
341
+ if (character === "'" || character === '"' || character === "`") {
342
+ quote = character;
343
+ continue;
344
+ }
345
+ if (character === "/" && nextCharacter === "/") {
346
+ lineComment = true;
347
+ index++;
348
+ continue;
349
+ }
350
+ if (character === "/" && nextCharacter === "*") {
351
+ blockComment = true;
352
+ index++;
353
+ continue;
354
+ }
355
+ if (character === "/")
356
+ return undefined;
357
+ // Annex B HTML-like comments are not lexed here; give up rather than
358
+ // risk misreading the parameter list.
359
+ if (character === "<" && source.startsWith("!--", index + 1))
360
+ return undefined;
361
+ if (character === "-" && source.startsWith("->", index + 1))
362
+ return undefined;
363
+ if (character === "(")
364
+ depth++;
365
+ else if (character === ")") {
366
+ depth--;
367
+ if (depth === 0 && square === 0 && curly === 0) {
368
+ const closeIndex = index;
369
+ if (parenthesizedArrow) {
370
+ index++;
371
+ skipTrivia();
372
+ const hasArrow = source[index] === "=" && source[index + 1] === ">";
373
+ if (!hasArrow && (!asyncMethodOrArrow || source[index] !== "{"))
374
+ return undefined;
375
+ }
376
+ return source.slice(openIndex + 1, closeIndex);
377
+ }
378
+ if (depth < 0)
379
+ return undefined;
380
+ }
381
+ else if (character === "[")
382
+ square++;
383
+ else if (character === "]") {
384
+ square--;
385
+ if (square < 0)
386
+ return undefined;
387
+ }
388
+ else if (character === "{")
389
+ curly++;
390
+ else if (character === "}") {
391
+ if (templateDepths.at(-1) === curly) {
392
+ templateDepths.pop();
393
+ curly--;
394
+ quote = "`";
395
+ }
396
+ else
397
+ curly--;
398
+ if (curly < 0)
399
+ return undefined;
400
+ }
401
+ }
402
+ return undefined;
403
+ }
404
+ /**
405
+ * Conservative defense in depth for defaulted second parameters, which
406
+ * JavaScript intentionally omits from Function.length. Ambiguous source is
407
+ * ignored so this check can never reject a valid provider on weak evidence.
408
+ */
409
+ function authStartHasHiddenInput(start) {
410
+ let source;
411
+ try {
412
+ source = Function.prototype.toString.call(start);
413
+ }
414
+ catch {
415
+ return false;
416
+ }
417
+ if (!source ||
418
+ source.includes("[native code]") ||
419
+ /^\s*(?:async\s+)?function\s+bound\b/.test(source))
420
+ return false;
421
+ const parameters = authStartParameterList(source);
422
+ if (!parameters)
423
+ return false;
424
+ const parts = splitAuthStartParameters(parameters);
425
+ if (!parts || parts.length < 2)
426
+ return false;
427
+ // Require ordinary, readable source formatting. This intentionally fails
428
+ // open for minified output and for transpilers that rewrite defaults.
429
+ const commaIndex = parameters.indexOf(",");
430
+ if (commaIndex < 0 || !/\s/.test(parameters[commaIndex + 1] ?? ""))
431
+ return false;
432
+ const first = parts[0].trim();
433
+ const second = parts[1].trim();
434
+ const identifier = /^[_$A-Za-z][_$A-Za-z0-9]*/;
435
+ const firstName = first.match(identifier)?.[0];
436
+ const secondName = second.match(identifier)?.[0];
437
+ if (!firstName || !secondName || firstName.length < 3 || secondName.length < 3)
438
+ return false;
439
+ return /\s=\s/.test(second);
440
+ }
441
+ /** Define one factored provider operation with schema-driven handler inference. */
442
+ export function defineOperation() {
443
+ return function operation(config) {
444
+ return config;
445
+ };
48
446
  }
49
- /** Define a non-JSON provider operation with explicit transport metadata. */
50
- export function defineStreamOperation(operation) {
51
- return operation;
447
+ /** Define a factored non-JSON operation with explicit transport metadata. */
448
+ export function defineStreamOperation() {
449
+ return function streamOperation(config) {
450
+ return config;
451
+ };
52
452
  }
53
453
  function assertObjectConfig(value) {
54
454
  if (!value || typeof value !== "object") {
@@ -72,18 +472,105 @@ function assertLiteralField(value, field, validValues, providerId) {
72
472
  });
73
473
  }
74
474
  }
75
- function validateProviderShape(config) {
475
+ function validateProxiedOAuthParams(value, field, providerId) {
476
+ if (value === undefined)
477
+ return;
478
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
479
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.${field} must be an object of string values.`);
480
+ }
481
+ for (const [key, paramValue] of Object.entries(value)) {
482
+ if (!key.trim() || typeof paramValue !== "string") {
483
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.${field} must contain non-empty keys and string values.`);
484
+ }
485
+ if (field === "authorizeParams" &&
486
+ PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())) {
487
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.authorizeParams cannot override reserved parameter "${key}".`);
488
+ }
489
+ }
490
+ }
491
+ function validateProxiedOAuthAuth(auth, providerId) {
492
+ const proxied = auth.proxied;
493
+ if (auth.mode !== "oauth2_proxied") {
494
+ if (proxied !== undefined) {
495
+ throw new ValidationError(`Provider "${providerId}" auth.proxied is only valid when auth.mode is "oauth2_proxied".`);
496
+ }
497
+ return;
498
+ }
499
+ if (!proxied || typeof proxied !== "object" || Array.isArray(proxied)) {
500
+ throw new ValidationError(`Provider "${providerId}" with auth.mode "oauth2_proxied" must declare auth.proxied.`);
501
+ }
502
+ const config = Object.fromEntries(Object.entries(proxied));
503
+ for (const key of Object.keys(config)) {
504
+ if (!PROXIED_OAUTH_ALLOWED_FIELDS.has(key)) {
505
+ throw new ValidationError(`Provider "${providerId}" has unknown auth.proxied field "${key}".`);
506
+ }
507
+ }
508
+ for (const field of PROXIED_OAUTH_REQUIRED_FIELDS) {
509
+ if (typeof config[field] !== "string" || !config[field].trim()) {
510
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.${field} must be a non-empty string.`);
511
+ }
512
+ }
513
+ for (const field of ["authorizeUrl", "tokenUrl"]) {
514
+ try {
515
+ const endpoint = new URL(String(config[field]));
516
+ if (endpoint.protocol !== "https:" ||
517
+ endpoint.username ||
518
+ endpoint.password ||
519
+ endpoint.hash) {
520
+ throw new Error("invalid endpoint");
521
+ }
522
+ }
523
+ catch {
524
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.${field} must be an absolute HTTPS URL without credentials or a fragment.`);
525
+ }
526
+ }
527
+ const customScheme = String(config.customScheme);
528
+ if (!CUSTOM_SCHEME_REGEX.test(customScheme) ||
529
+ customScheme.toLowerCase().startsWith("http://") ||
530
+ customScheme.toLowerCase().startsWith("https://")) {
531
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.customScheme must be a non-HTTP custom-scheme URL prefix.`);
532
+ }
533
+ if (!PROXIED_OAUTH_PROFILE_REGEX.test(String(config.rewriteProfile))) {
534
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.rewriteProfile must be a kebab-case profile name.`);
535
+ }
536
+ if (!PROXIED_OAUTH_ENV_KEY_REGEX.test(String(config.clientIdEnvKey))) {
537
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.clientIdEnvKey must be an APIFuse-style uppercase environment key.`);
538
+ }
539
+ if (config.pkce !== undefined && config.pkce !== "S256" && config.pkce !== "none") {
540
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.pkce must be "S256" or "none".`);
541
+ }
542
+ validateProxiedOAuthParams(config.authorizeParams, "authorizeParams", providerId);
543
+ validateProxiedOAuthParams(config.tokenParams, "tokenParams", providerId);
544
+ }
545
+ function validateProviderDeclarationShape(config) {
76
546
  assertObjectConfig(config);
77
547
  assertRequiredField(config, "id");
78
548
  assertRequiredField(config, "version", String(config.id));
79
549
  assertRequiredField(config, "runtime", String(config.id));
80
550
  assertRequiredField(config, "meta", String(config.id));
81
- assertRequiredField(config, "operations", String(config.id));
82
551
  if (typeof config.runtime === "string")
83
552
  assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
553
+ if (config.runtimeTarget !== undefined && typeof config.runtimeTarget !== "string") {
554
+ throw new ValidationError(`Provider "${String(config.id)}" has invalid runtimeTarget: expected "vanilla" or "engine"`);
555
+ }
556
+ if (typeof config.runtimeTarget === "string")
557
+ assertLiteralField(config.runtimeTarget, "runtimeTarget", VALID_RUNTIME_TARGETS, String(config.id));
558
+ if (config.native !== undefined && config.runtime === "browser") {
559
+ throw new ValidationError(`Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`, {
560
+ fix: 'Use runtime: "standard" or runtime: "shared", or remove the native declaration.',
561
+ });
562
+ }
563
+ if (config.native !== undefined && config.runtimeTarget === "vanilla") {
564
+ throw new ValidationError(`Provider "${String(config.id)}" cannot declare capability "native" with runtime target "vanilla"; native requires an engine-resident runtime`, {
565
+ fix: 'Set runtimeTarget: "engine", or remove the native declaration.',
566
+ });
567
+ }
84
568
  const auth = config.auth;
85
569
  if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
86
570
  assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
571
+ if (auth && typeof auth === "object" && !Array.isArray(auth)) {
572
+ validateProxiedOAuthAuth(Object.fromEntries(Object.entries(auth)), String(config.id));
573
+ }
87
574
  if (auth && typeof auth === "object" && "exchange" in auth) {
88
575
  throw new ProviderError(`Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`, {
89
576
  fix: "Use the single canonical auth interface: auth.flow. Gateway calls auth.flow.start/continue/poll/abort/refresh only and persists complete turn data.credential as-is, so put login/token/session exchange inside auth.flow.continue.",
@@ -101,6 +588,19 @@ function validateProviderShape(config) {
101
588
  fix: "Return a form turn from start(ctx), then receive user input in continue(ctx, input).",
102
589
  });
103
590
  }
591
+ if (auth &&
592
+ typeof auth === "object" &&
593
+ "flow" in auth &&
594
+ auth.flow &&
595
+ typeof auth.flow === "object" &&
596
+ "start" in auth.flow &&
597
+ typeof auth.flow.start === "function" &&
598
+ auth.flow.start.length <= 1 &&
599
+ authStartHasHiddenInput(auth.flow.start)) {
600
+ throw new ProviderError(`Provider "${String(config.id)}" auth.flow.start must not declare an input parameter`, {
601
+ fix: "Return a form turn from start(ctx), then receive user input in continue(ctx, input).",
602
+ });
603
+ }
104
604
  const access = config.access;
105
605
  if (access !== undefined) {
106
606
  if (!access || typeof access !== "object" || Array.isArray(access)) {
@@ -127,7 +627,23 @@ function validateProviderShape(config) {
127
627
  }
128
628
  }
129
629
  }
630
+ function validateProviderImplementationShape(config) {
631
+ const configRecord = config;
632
+ assertRequiredField(configRecord, "operations", String(config.id));
633
+ }
130
634
  function validateProviderProxy(config) {
635
+ for (const secret of config.secrets ?? []) {
636
+ if (isEngineOwnedTelemetryEnvName(secret.name)) {
637
+ throw new ValidationError(`Provider "${config.id}" cannot declare engine-owned telemetry variable "${secret.name}"`, {
638
+ fix: `Remove "${secret.name}" from provider secrets; trace export is configured only on the provider engine.`,
639
+ });
640
+ }
641
+ if (!isEngineOwnedProxyCredentialName(secret.name))
642
+ continue;
643
+ throw new ValidationError(`Provider "${config.id}" cannot declare engine-owned proxy credential "${secret.name}"`, {
644
+ fix: `Remove "${secret.name}" from provider secrets; configure it only on the provider engine.`,
645
+ });
646
+ }
131
647
  const proxy = config.proxy;
132
648
  if (proxy === undefined || typeof proxy === "boolean") {
133
649
  return;
@@ -137,11 +653,21 @@ function validateProviderProxy(config) {
137
653
  fix: `Use proxy: { mode: "required", provider: "smartproxy", geo: { country: "KR" }, session: { affinity: "connection", lifetimeMinutes: 30 } }`,
138
654
  });
139
655
  }
140
- rejectUnknownFields(proxy, new Set(["mode", "provider", "geo", "session"]), "proxy");
656
+ rejectUnknownFields(proxy, new Set(["mode", "provider", "providers", "geo", "session"]), "proxy");
141
657
  assertLiteralField(proxy.mode, "proxy.mode", VALID_PROVIDER_PROXY_MODES, config.id);
142
658
  if (proxy.provider !== undefined) {
143
659
  assertLiteralField(proxy.provider, "proxy.provider", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
144
660
  }
661
+ if (proxy.providers !== undefined) {
662
+ if (!Array.isArray(proxy.providers) || proxy.providers.length === 0) {
663
+ throw new ValidationError(`Provider "${config.id}" has invalid proxy.providers: must be a non-empty array of proxy vendors.`, {
664
+ fix: `Use proxy.providers: ["smartproxy", "nodemaven"] to declare an ordered fallback chain.`,
665
+ });
666
+ }
667
+ for (const vendor of proxy.providers) {
668
+ assertLiteralField(vendor, "proxy.providers[]", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
669
+ }
670
+ }
145
671
  if (proxy.geo !== undefined) {
146
672
  if (!proxy.geo || typeof proxy.geo !== "object" || Array.isArray(proxy.geo)) {
147
673
  throw new ValidationError(`Provider "${config.id}" has invalid proxy.geo: must be an object.`, {
@@ -165,7 +691,7 @@ function validateProviderProxy(config) {
165
691
  fix: `Use proxy.session: { affinity: "connection", lifetimeMinutes: 30 }.`,
166
692
  });
167
693
  }
168
- rejectUnknownFields(proxy.session, new Set(["affinity", "lifetimeMinutes", "poolSize"]), "proxy.session");
694
+ rejectUnknownFields(proxy.session, new Set(["affinity", "lifetimeMinutes", "poolSize", "drainLeadSeconds"]), "proxy.session");
169
695
  if (proxy.session.affinity !== undefined) {
170
696
  assertLiteralField(proxy.session.affinity, "proxy.session.affinity", VALID_PROVIDER_PROXY_AFFINITIES, config.id);
171
697
  }
@@ -177,15 +703,43 @@ function validateProviderProxy(config) {
177
703
  if (poolSize !== undefined && (!Number.isInteger(poolSize) || poolSize <= 0)) {
178
704
  throw new ValidationError(`Provider "${config.id}" has invalid proxy.session.poolSize: must be a positive integer.`);
179
705
  }
180
- }
181
- if (proxy.mode === "required" && proxy.provider === "smartproxy") {
182
- const hasSmartproxySecret = config.secrets?.some((secret) => secret.name === SMARTPROXY_APP_KEY_SECRET && secret.required !== false);
183
- if (!hasSmartproxySecret) {
184
- throw new ValidationError(`Provider "${config.id}" requires Smartproxy egress but does not declare ${SMARTPROXY_APP_KEY_SECRET}.`, {
185
- fix: `Add secrets: [{ name: "${SMARTPROXY_APP_KEY_SECRET}", required: true }] to the provider.`,
706
+ const drainLeadSeconds = proxy.session.drainLeadSeconds;
707
+ if (drainLeadSeconds !== undefined &&
708
+ (!Number.isFinite(drainLeadSeconds) || drainLeadSeconds <= 0)) {
709
+ throw new ValidationError(`Provider "${config.id}" has invalid proxy.session.drainLeadSeconds: must be a positive number of seconds.`, {
710
+ fix: `Use proxy.session.drainLeadSeconds: 120 to receive the sticky-expiry drain event 120s before hard expiry.`,
711
+ });
712
+ }
713
+ // A drain lead longer than the sticky lifetime would fire the expiring
714
+ // event before the session is even established, so the provider would
715
+ // never get a usable window. Reject the contradiction at build time.
716
+ if (drainLeadSeconds !== undefined &&
717
+ lifetime !== undefined &&
718
+ Number.isFinite(lifetime) &&
719
+ drainLeadSeconds >= lifetime * 60) {
720
+ throw new ValidationError(`Provider "${config.id}" has proxy.session.drainLeadSeconds (${drainLeadSeconds}s) greater than or equal to proxy.session.lifetimeMinutes (${lifetime}m).`, {
721
+ fix: `Lower drainLeadSeconds below the sticky lifetime so the drain event leaves a usable session window.`,
186
722
  });
187
723
  }
188
724
  }
725
+ const vendorChain = proxy.providers && proxy.providers.length > 0
726
+ ? proxy.providers
727
+ : proxy.provider
728
+ ? [proxy.provider]
729
+ : [];
730
+ // `decodo`/`custom` are deprecated vendor values (string-union members, so the
731
+ // @deprecated symbol gate can't catch them — warn at validation time instead).
732
+ const deprecatedVendors = vendorChain.filter((vendor) => vendor === "decodo" || vendor === "custom");
733
+ if (proxy.mode === "required" &&
734
+ vendorChain.length > 0 &&
735
+ deprecatedVendors.length === vendorChain.length) {
736
+ throw new ValidationError(`Provider "${config.id}" requires proxy egress but declares only deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}.`, {
737
+ fix: `Use proxy.provider or proxy.providers with "smartproxy" and/or "nodemaven".`,
738
+ });
739
+ }
740
+ if (deprecatedVendors.length > 0) {
741
+ console.warn(`[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven".`);
742
+ }
189
743
  }
190
744
  function validateProviderStt(config) {
191
745
  const stt = config.stt;
@@ -199,6 +753,54 @@ function validateProviderStt(config) {
199
753
  rejectUnknownFields(stt, new Set(["mode"]), "stt");
200
754
  assertLiteralField(stt.mode, "stt.mode", VALID_PROVIDER_STT_MODES, config.id);
201
755
  }
756
+ function validateProviderOcr(config) {
757
+ const ocr = config.ocr;
758
+ if (ocr === undefined)
759
+ return;
760
+ if (!ocr || typeof ocr !== "object" || Array.isArray(ocr)) {
761
+ throw new ValidationError(`Provider "${config.id}" has invalid ocr: must be an object.`, {
762
+ fix: `Use ocr: { mode: "required" } or ocr: { mode: "optional" }.`,
763
+ });
764
+ }
765
+ rejectUnknownFields(ocr, new Set(["mode"]), "ocr");
766
+ assertLiteralField(ocr.mode, "ocr.mode", VALID_PROVIDER_OCR_MODES, config.id);
767
+ }
768
+ function validateProviderResolver(config) {
769
+ const resolver = config.resolver;
770
+ if (resolver === undefined)
771
+ return;
772
+ if (!resolver || typeof resolver !== "object" || Array.isArray(resolver)) {
773
+ throw new ValidationError(`Provider "${config.id}" has invalid resolver: must be an object.`, {
774
+ fix: `Set resolver for provider "${config.id}" to { vendors: ["2captcha"], kinds: ["turnstile"] }.`,
775
+ });
776
+ }
777
+ rejectUnknownFields(resolver, new Set(["vendors", "kinds", "clientProfile"]), "resolver", config.id);
778
+ if (resolver.vendors !== undefined) {
779
+ validateResolverLiteralArray(resolver.vendors, "resolver.vendors", VALID_PROVIDER_RESOLVER_VENDORS, config.id);
780
+ }
781
+ validateResolverLiteralArray(resolver.kinds, "resolver.kinds", VALID_PROVIDER_CHALLENGE_KINDS, config.id);
782
+ if (resolver.clientProfile !== undefined &&
783
+ (typeof resolver.clientProfile !== "string" || !resolver.clientProfile.trim())) {
784
+ throw new ValidationError(`Provider "${config.id}" has invalid resolver.clientProfile: must be a non-empty string.`, {
785
+ fix: `Set resolver.clientProfile for provider "${config.id}" to a transport-owned profile name.`,
786
+ });
787
+ }
788
+ }
789
+ function validateResolverLiteralArray(value, field, validValues, providerId) {
790
+ if (!Array.isArray(value)) {
791
+ throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an array.`, {
792
+ fix: `Set ${field} for provider "${providerId}" to an array containing only: ${validValues.join(", ")}.`,
793
+ });
794
+ }
795
+ for (const [index, item] of value.entries()) {
796
+ if (typeof item === "string" && validValues.some((validValue) => validValue === item)) {
797
+ continue;
798
+ }
799
+ throw new ValidationError(`Provider "${providerId}" has invalid ${field}[${index}]: ${JSON.stringify(item)}. Expected one of: ${validValues.join(", ")}`, {
800
+ fix: `Set ${field}[${index}] for provider "${providerId}" to one of ${validValues.map((validValue) => `"${validValue}"`).join(", ")}.`,
801
+ });
802
+ }
803
+ }
202
804
  function validateOperationIds(providerId, operations) {
203
805
  for (const operationName of Object.keys(operations)) {
204
806
  if (!OPERATION_ID_REGEX.test(operationName))
@@ -219,31 +821,19 @@ function assertNonEmptyString(value, field, providerId, operationName) {
219
821
  throw new ValidationError(`Provider "${providerId}" operation "${operationName}" has invalid ${field}: must be a non-empty string.`, { fix: `Set ${field} to a non-empty customer-facing value.` });
220
822
  }
221
823
  }
222
- function validateToolRouterMetadata(providerId, operations) {
824
+ function validateOperationMetadata(providerId, operations) {
223
825
  for (const [operationName, operation] of Object.entries(operations)) {
224
- const toolRouter = operation.toolRouter;
225
- if (toolRouter === undefined)
226
- continue;
227
- if (!toolRouter || typeof toolRouter !== "object") {
228
- throw new ValidationError(`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter: must be an object.`, {
229
- fix: `Remove operations.${operationName}.toolRouter or provide MCP-safe metadata.`,
230
- });
231
- }
232
- if (toolRouter.name !== undefined && !MCP_TOOL_NAME_REGEX.test(toolRouter.name)) {
233
- throw new ValidationError(`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter.name: expected an MCP-safe name.`, {
234
- 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, "_")}".`,
235
- });
826
+ assertLiteralField(operation.riskClass, `operations.${operationName}.riskClass`, VALID_OPERATION_RISK_CLASSES, providerId);
827
+ if (operation.approval !== undefined) {
828
+ assertLiteralField(operation.approval, `operations.${operationName}.approval`, VALID_OPERATION_APPROVAL_POLICIES, providerId);
236
829
  }
237
- if (toolRouter.riskClass !== undefined) {
238
- assertLiteralField(toolRouter.riskClass, `operations.${operationName}.toolRouter.riskClass`, VALID_OPERATION_RISK_CLASSES, providerId);
830
+ if (operation.connectionMode !== undefined) {
831
+ assertLiteralField(operation.connectionMode, `operations.${operationName}.connectionMode`, VALID_OPERATION_CONNECTION_MODES, providerId);
239
832
  }
240
- if (toolRouter.approval !== undefined) {
241
- assertLiteralField(toolRouter.approval, `operations.${operationName}.toolRouter.approval`, VALID_OPERATION_APPROVAL_POLICIES, providerId);
242
- }
243
- if (toolRouter.connectionExternalRefParam !== undefined &&
244
- (typeof toolRouter.connectionExternalRefParam !== "string" ||
245
- toolRouter.connectionExternalRefParam.trim().length === 0)) {
246
- throw new ValidationError(`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter.connectionExternalRefParam: must be a non-empty string.`, {
833
+ if (operation.connectionExternalRefParam !== undefined &&
834
+ (typeof operation.connectionExternalRefParam !== "string" ||
835
+ operation.connectionExternalRefParam.trim().length === 0)) {
836
+ throw new ValidationError(`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.connectionExternalRefParam: must be a non-empty string.`, {
247
837
  fix: `Use "externalRef" unless the operation has a documented public alias.`,
248
838
  });
249
839
  }
@@ -279,15 +869,12 @@ function validateOperationContracts(providerId, operations) {
279
869
  }
280
870
  }
281
871
  }
282
- function validateOperationAnnotations(providerId, operations) {
872
+ function validateOperationTimeouts(providerId, operations) {
283
873
  for (const [operationName, operation] of Object.entries(operations)) {
284
- const annotations = operation.annotations;
285
- if (!annotations)
286
- continue;
287
- const timeoutMs = annotations.timeoutMs;
874
+ const timeoutMs = operation.timeoutMs;
288
875
  if (timeoutMs === undefined)
289
876
  continue;
290
- const field = `operations.${operationName}.annotations.timeoutMs`;
877
+ const field = `operations.${operationName}.timeoutMs`;
291
878
  if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs))
292
879
  throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an integer number of milliseconds.`, {
293
880
  fix: `Set ${field} to an integer in [${OPERATION_TIMEOUT_MS_MIN}, ${OPERATION_TIMEOUT_MS_MAX}] (milliseconds).`,
@@ -335,6 +922,23 @@ function validateOperationObservability(providerId, operations) {
335
922
  }
336
923
  }
337
924
  }
925
+ function validateOperationErrorCodes(providerId, operations) {
926
+ for (const [operationName, operation] of Object.entries(operations)) {
927
+ for (const [index, errorCode] of (operation.errorCodes ?? []).entries()) {
928
+ if (errorCode.status !== undefined &&
929
+ !VALID_OPERATION_ERROR_STATUSES.some((status) => status === errorCode.status)) {
930
+ const field = `operations.${operationName}.errorCodes[${index}].status`;
931
+ throw new ValidationError(`Provider "${providerId}" has invalid ${field}: ${String(errorCode.status)} is not an emittable provider error status.`, {
932
+ fix: `Set ${field} to one of ${VALID_OPERATION_ERROR_STATUSES.join(", ")}, or omit it.`,
933
+ });
934
+ }
935
+ if (errorCode.status !== undefined &&
936
+ SDK_RUNTIME_OWNED_ERROR_CODES.has(errorCode.code)) {
937
+ console.warn(`[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.`);
938
+ }
939
+ }
940
+ }
941
+ }
338
942
  const JSON_TRANSPORT_FIELDS = new Set(["kind"]);
339
943
  const SSE_TRANSPORT_FIELDS = new Set([
340
944
  "kind",
@@ -500,6 +1104,7 @@ const HEALTH_CHECK_CASE_FIELDS = new Set([
500
1104
  "input",
501
1105
  "prepareInput",
502
1106
  "assertions",
1107
+ "scenario",
503
1108
  "degradedThresholdMs",
504
1109
  "timeoutMs",
505
1110
  "expectedStatus",
@@ -556,14 +1161,18 @@ function suggestField(unknown, candidates) {
556
1161
  }
557
1162
  return best;
558
1163
  }
559
- function rejectUnknownFields(value, allowed, fieldPath) {
1164
+ function rejectUnknownFields(value, allowed, fieldPath, providerId) {
560
1165
  for (const key of Object.keys(value)) {
561
1166
  if (allowed.has(key))
562
1167
  continue;
563
1168
  const hint = suggestField(key, allowed);
564
1169
  throw new ValidationError(hint
565
1170
  ? `Unknown field "${key}" on ${fieldPath}. Did you mean "${hint}"?`
566
- : `Unknown field "${key}" on ${fieldPath}.`, { fix: `Remove ${fieldPath}.${key} or rename it.` });
1171
+ : `Unknown field "${key}" on ${fieldPath}.`, {
1172
+ fix: providerId
1173
+ ? `Remove ${fieldPath}.${key} from provider "${providerId}" or rename it.`
1174
+ : `Remove ${fieldPath}.${key} or rename it.`,
1175
+ });
567
1176
  }
568
1177
  }
569
1178
  function assertBoundedIntegerMs(value, fieldPath, options) {
@@ -670,12 +1279,31 @@ function validateHealthCheckCase(providerId, operationName, caseValue, caseIndex
670
1279
  const c = caseValue;
671
1280
  if (typeof c.name !== "string" || c.name.length === 0)
672
1281
  throw new ValidationError(`Provider "${providerId}" ${fieldPath}.name must be a non-empty string.`);
673
- if (typeof c.assertions !== "function")
1282
+ const hasScenario = c.scenario !== undefined;
1283
+ const imperativeFields = [
1284
+ ...(c.prepareInput === undefined ? [] : ["prepareInput"]),
1285
+ ...(c.assertions === undefined ? [] : ["assertions"]),
1286
+ ];
1287
+ if (hasScenario && imperativeFields.length > 0)
1288
+ throw new ValidationError(`Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" cannot declare scenario with ${imperativeFields.join(" and ")}.`, {
1289
+ fix: `Remove ${imperativeFields.map((field) => `${fieldPath}.${field}`).join(" and ")} and keep ${fieldPath}.scenario, or remove ${fieldPath}.scenario to keep the imperative hooks.`,
1290
+ });
1291
+ if (!hasScenario && typeof c.assertions !== "function")
674
1292
  throw new ValidationError(`Provider "${providerId}" ${fieldPath}.assertions must be a function.`, {
675
1293
  fix: `Set ${fieldPath}.assertions to (ctx) => { ... } that throws on failure.`,
676
1294
  });
677
1295
  if (c.prepareInput !== undefined && typeof c.prepareInput !== "function")
678
1296
  throw new ValidationError(`Provider "${providerId}" ${fieldPath}.prepareInput must be a function.`);
1297
+ if (hasScenario) {
1298
+ const parsedScenario = HealthScenarioSchema.safeParse(c.scenario);
1299
+ if (!parsedScenario.success)
1300
+ throw new ValidationError(`Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" has an invalid scenario: it must conform to HealthScenario.`, { fix: `Build ${fieldPath}.scenario with defineHealthScenario().` });
1301
+ const unrelatedOperation = parsedScenario.data.coversOperations.find((operationId) => operationId !== operationName);
1302
+ if (unrelatedOperation !== undefined)
1303
+ throw new ValidationError(`Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" scenario.coversOperations cannot claim unrelated operation "${unrelatedOperation}".`, {
1304
+ fix: `Set ${fieldPath}.scenario.coversOperations to ["${operationName}"].`,
1305
+ });
1306
+ }
679
1307
  if (c.degradedThresholdMs !== undefined &&
680
1308
  (typeof c.degradedThresholdMs !== "number" ||
681
1309
  !Number.isInteger(c.degradedThresholdMs) ||
@@ -776,6 +1404,7 @@ const HEALTH_JOURNEY_FIELDS = new Set([
776
1404
  "manualTrigger",
777
1405
  "steps",
778
1406
  "run",
1407
+ "scenario",
779
1408
  ]);
780
1409
  const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set(["kind", "interval", "jitter", "randomize"]);
781
1410
  const HEALTH_JOURNEY_STEP_FIELDS = new Set([
@@ -1198,6 +1827,10 @@ function validateHealthJourneys(providerId, operations, healthJourneys) {
1198
1827
  }
1199
1828
  if (journey.manualTrigger !== undefined)
1200
1829
  validateHealthJourneyManualTrigger(providerId, journey.id, journey.manualTrigger);
1830
+ if (journey.scenario !== undefined && journey.smsMatchers !== undefined)
1831
+ throw new ValidationError(`Provider "${providerId}" healthJourneys.${journey.id}.smsMatchers is not allowed on declarative scenarios.`);
1832
+ if (journey.scenario !== undefined && journey.requiredSecrets !== undefined)
1833
+ throw new ValidationError(`Provider "${providerId}" healthJourneys.${journey.id}.requiredSecrets is not allowed on declarative scenarios.`);
1201
1834
  if (journey.timeout !== undefined)
1202
1835
  assertIsoDuration(journey.timeout, `Provider "${providerId}" healthJourneys.${journey.id}.timeout`);
1203
1836
  if (journey.cooldown !== undefined)
@@ -1239,6 +1872,21 @@ function validateOperationFixtures(providerId, operations) {
1239
1872
  throw new ValidationError(`Operation handler must be defined for provider "${providerId}" operation "${operationName}"`, {
1240
1873
  fix: `Add operations.${operationName}.handler as an async function with signature (ctx, input) => Promise<output>`,
1241
1874
  });
1875
+ if (operation.fixtures?.recordedAt !== undefined) {
1876
+ const recordedAt = operation.fixtures.recordedAt;
1877
+ const parsed = typeof recordedAt === "string"
1878
+ ? new Date(`${recordedAt}T00:00:00.000Z`)
1879
+ : new Date(Number.NaN);
1880
+ const isCalendarDate = typeof recordedAt === "string" &&
1881
+ /^\d{4}-\d{2}-\d{2}$/.test(recordedAt) &&
1882
+ !Number.isNaN(parsed.getTime()) &&
1883
+ parsed.toISOString().slice(0, 10) === recordedAt;
1884
+ const kstToday = new Date(Date.now() + 9 * 60 * 60 * 1000).toISOString().slice(0, 10);
1885
+ if (!isCalendarDate || recordedAt > kstToday)
1886
+ throw new ValidationError(`Fixture recordedAt must be a valid, non-future KST calendar date for provider "${providerId}" operation "${operationName}"`, {
1887
+ fix: `Set operations.${operationName}.fixtures.recordedAt to the KST capture date in YYYY-MM-DD format; it must not be in the future.`,
1888
+ });
1889
+ }
1242
1890
  if (operation.fixtures?.request !== undefined) {
1243
1891
  const result = safeParseSchemaSync(operation.input, operation.fixtures.request, `operations.${operationName}.fixtures.request`);
1244
1892
  if (!result.success)
@@ -1257,6 +1905,28 @@ function validateOperationFixtures(providerId, operations) {
1257
1905
  }
1258
1906
  }
1259
1907
  }
1908
+ function resolveOperationFixtureRequests(operations) {
1909
+ let changed = false;
1910
+ const resolvedOperations = Object.fromEntries(Object.entries(operations).map(([operationName, operation]) => {
1911
+ if (operation.fixtures?.request === undefined)
1912
+ return [operationName, operation];
1913
+ const request = resolveHealthCheckInputDateTokens(operation.fixtures.request);
1914
+ if (request === operation.fixtures.request)
1915
+ return [operationName, operation];
1916
+ changed = true;
1917
+ return [
1918
+ operationName,
1919
+ {
1920
+ ...operation,
1921
+ fixtures: {
1922
+ ...operation.fixtures,
1923
+ request,
1924
+ },
1925
+ },
1926
+ ];
1927
+ }));
1928
+ return changed ? resolvedOperations : operations;
1929
+ }
1260
1930
  /**
1261
1931
  * Shallow shape guard only: the `deployment` object is passed through
1262
1932
  * verbatim and deliberately not deep-validated by the SDK — the APIFuse
@@ -1270,63 +1940,102 @@ function validateProviderDeployment(providerId, deployment) {
1270
1940
  fix: 'Pass deployment: { runtime: "shared" | "dedicated" | "browser", ... } or remove the field',
1271
1941
  });
1272
1942
  }
1273
- export function defineProvider(config) {
1274
- validateProviderShape(config);
1943
+ /** Establish a provider declaration before its operations are contextually typed. */
1944
+ export function defineProvider(declaration) {
1945
+ validateProviderDeclaration(declaration);
1946
+ const buildProvider = (implementation) => finalizeProvider({
1947
+ ...declaration,
1948
+ ...implementation,
1949
+ });
1950
+ return buildProvider;
1951
+ }
1952
+ function validateProviderDeclaration(config) {
1953
+ validateProviderDeclarationShape(config);
1275
1954
  if (!CONNECTOR_ID_REGEX.test(config.id))
1276
1955
  throw new ProviderError(`Invalid provider id: "${config.id}"`, {
1277
1956
  fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
1278
1957
  });
1279
- if (Object.keys(config.operations).length === 0)
1280
- throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
1281
- fix: "Add at least one operation to the operations object",
1282
- });
1283
- validateOperationIds(config.id, config.operations);
1284
- validateOperationAnnotations(config.id, config.operations);
1285
- validateOperationObservability(config.id, config.operations);
1286
- validateOperationTransports(config.id, config.operations);
1287
- validateOperationContracts(config.id, config.operations);
1288
- validateToolRouterMetadata(config.id, config.operations);
1289
- const journeyCoveredOperations = validateHealthJourneys(config.id, config.operations, config.healthJourneys);
1290
- validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
1291
1958
  if (config.healthMonitor !== undefined && config.healthProbe !== undefined)
1292
1959
  throw new ValidationError(`Provider "${config.id}" declares both healthMonitor and healthProbe. They are aliases; declare exactly one.`, {
1293
1960
  fix: "Keep healthProbe (the new name) and delete the healthMonitor block.",
1294
1961
  });
1295
1962
  validateProviderHealthMonitor(config.id, config.healthProbe ?? config.healthMonitor, config.healthProbe !== undefined ? "healthProbe" : "healthMonitor");
1296
- validateOperationFixtures(config.id, config.operations);
1297
1963
  validateProviderDeployment(config.id, config.deployment);
1964
+ try {
1965
+ validateNativeProviderConfig(config.native);
1966
+ }
1967
+ catch (error) {
1968
+ if (error instanceof NativeEgressPolicyValidationError)
1969
+ throw new ValidationError(error.message);
1970
+ throw error;
1971
+ }
1298
1972
  validateProviderProxy(config);
1973
+ validateProviderOcr(config);
1299
1974
  validateProviderStt(config);
1975
+ validateProviderResolver(config);
1300
1976
  if (config.runtime === "browser" && !config.browser)
1301
1977
  throw new ProviderError(`Provider "${config.id}" must define browser.engine when runtime is "browser"`, {
1302
1978
  fix: 'Add browser: { engine: "playwright-stealth" } for TypeScript providers, or another supported engine for your runtime',
1303
1979
  });
1304
1980
  if (config.browser && config.runtime !== "browser")
1305
1981
  throw new ProviderError(`Provider "${config.id}" cannot define browser config unless runtime is "browser"`, { fix: 'Set runtime: "browser" or remove the browser config' });
1306
- return {
1982
+ validateFailClosedProviderDeclaration(config);
1983
+ }
1984
+ function finalizeProvider(config) {
1985
+ validateProviderImplementationShape(config);
1986
+ const operations = resolveOperationFixtureRequests(config.operations);
1987
+ if (Object.keys(config.operations).length === 0)
1988
+ throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
1989
+ fix: "Add at least one operation to the operations object",
1990
+ });
1991
+ validateOperationIds(config.id, config.operations);
1992
+ validateOperationTimeouts(config.id, config.operations);
1993
+ validateOperationObservability(config.id, config.operations);
1994
+ validateOperationErrorCodes(config.id, config.operations);
1995
+ validateOperationTransports(config.id, config.operations);
1996
+ validateOperationContracts(config.id, config.operations);
1997
+ validateOperationMetadata(config.id, config.operations);
1998
+ const journeyCoveredOperations = validateHealthJourneys(config.id, config.operations, config.healthJourneys);
1999
+ validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
2000
+ validateOperationFixtures(config.id, operations);
2001
+ const provider = {
1307
2002
  id: config.id,
1308
2003
  version: config.version,
1309
2004
  runtime: config.runtime,
2005
+ runtimeTarget: config.runtimeTarget,
1310
2006
  // Verbatim passthrough: deployment validation and profile resolution
1311
2007
  // are owned by the APIFuse registry builder, not the SDK.
1312
2008
  deployment: config.deployment,
2009
+ http: config.http,
1313
2010
  allowedHosts: config.allowedHosts,
2011
+ native: config.native,
1314
2012
  stealth: config.stealth,
1315
2013
  proxy: config.proxy,
2014
+ ocr: config.ocr,
1316
2015
  stt: config.stt,
2016
+ resolver: config.resolver,
1317
2017
  browser: config.browser,
1318
2018
  auth: config.auth,
2019
+ choice: config.choice,
1319
2020
  reviewed: config.reviewed,
1320
2021
  access: config.access,
1321
2022
  secrets: config.secrets,
2023
+ env: config.env,
1322
2024
  credential: config.credential,
1323
2025
  context: config.context,
2026
+ state: config.state,
2027
+ cache: config.cache,
2028
+ files: config.files,
1324
2029
  meta: config.meta,
1325
- operations: config.operations,
2030
+ operations,
1326
2031
  // Transitional healthMonitor → healthProbe alias: mirror whichever field
1327
2032
  // was declared onto both so old and new consumers keep working.
1328
2033
  healthMonitor: config.healthMonitor ?? config.healthProbe,
1329
2034
  healthProbe: config.healthProbe ?? config.healthMonitor,
1330
2035
  healthJourneys: config.healthJourneys,
1331
2036
  };
2037
+ // Declaration validation never invokes handlers, so their declaration-bound
2038
+ // context parameter is irrelevant to the runtime ProviderDefinition shape.
2039
+ validateFailClosedOperationDeclaration(provider);
2040
+ return provider;
1332
2041
  }