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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/AUTHORING.md +943 -51
  2. package/CHANGELOG.md +273 -1
  3. package/LICENSE +21 -0
  4. package/README.md +117 -29
  5. package/SUBMISSION.md +3 -2
  6. package/bin/apifuse-check.ts +293 -13
  7. package/bin/apifuse-dev.ts +42 -19
  8. package/bin/apifuse-migrate-deployment.ts +200 -0
  9. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  10. package/bin/apifuse-migrate-shape.ts +202 -0
  11. package/bin/apifuse-pack-check.ts +19 -6
  12. package/bin/apifuse-pack-smoke.ts +57 -2
  13. package/bin/apifuse-pack-types.ts +736 -38
  14. package/bin/apifuse-perf.ts +59 -23
  15. package/bin/apifuse-record.ts +923 -94
  16. package/bin/apifuse-submit-check.ts +2313 -281
  17. package/bin/apifuse-test.ts +3 -1
  18. package/bin/submit-check-delimited-text.ts +50 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth-turn/index.js +1 -1
  21. package/dist/auth.d.ts +14 -0
  22. package/dist/auth.js +67 -0
  23. package/dist/ceremonies/index.d.ts +16 -0
  24. package/dist/ceremonies/index.js +141 -36
  25. package/dist/cli/commands.d.ts +1 -1
  26. package/dist/cli/commands.js +30 -0
  27. package/dist/cli/create.d.ts +1 -1
  28. package/dist/cli/create.js +8 -2
  29. package/dist/cli/deployment-intent.d.ts +178 -0
  30. package/dist/cli/deployment-intent.js +277 -0
  31. package/dist/cli/local-connection.d.ts +111 -0
  32. package/dist/cli/local-connection.js +151 -0
  33. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  34. package/dist/cli/migrate-deployment-intent.js +1552 -0
  35. package/dist/cli/migrate-operation-declaration.d.ts +70 -0
  36. package/dist/cli/migrate-operation-declaration.js +2206 -0
  37. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  38. package/dist/cli/migrate-operation-shape.js +113 -0
  39. package/dist/cli/migrate-provider-shape.d.ts +56 -0
  40. package/dist/cli/migrate-provider-shape.js +580 -0
  41. package/dist/cli/resolver-runtime.d.ts +21 -0
  42. package/dist/cli/resolver-runtime.js +31 -0
  43. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  44. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  45. package/dist/cli/templates/provider/README.md.tpl +41 -19
  46. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  47. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  48. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  49. package/dist/config/loader.d.ts +202 -16
  50. package/dist/config/loader.js +491 -141
  51. package/dist/contract-serialization.js +4 -8
  52. package/dist/contract-types.d.ts +12 -5
  53. package/dist/contract.js +24 -10
  54. package/dist/declaration-validation.d.ts +34 -0
  55. package/dist/declaration-validation.js +305 -0
  56. package/dist/define.d.ts +71 -29
  57. package/dist/define.js +807 -69
  58. package/dist/dev.d.ts +5 -0
  59. package/dist/dev.js +8 -1
  60. package/dist/engine.d.ts +129 -0
  61. package/dist/engine.js +209 -0
  62. package/dist/error-observability.d.ts +7 -0
  63. package/dist/error-observability.js +61 -0
  64. package/dist/error-resolution.d.ts +18 -0
  65. package/dist/error-resolution.js +243 -0
  66. package/dist/errors.d.ts +74 -0
  67. package/dist/errors.js +99 -0
  68. package/dist/fixture-sanitization.d.ts +28 -0
  69. package/dist/fixture-sanitization.js +371 -0
  70. package/dist/handle-meta.d.ts +90 -0
  71. package/dist/handle-meta.js +73 -0
  72. package/dist/handle.d.ts +226 -0
  73. package/dist/handle.js +345 -0
  74. package/dist/health-scenario.d.ts +1901 -0
  75. package/dist/health-scenario.js +653 -0
  76. package/dist/i18n/catalog.js +21 -1
  77. package/dist/i18n/error-messages.d.ts +86 -0
  78. package/dist/i18n/error-messages.js +192 -0
  79. package/dist/i18n/index.d.ts +1 -0
  80. package/dist/i18n/index.js +1 -0
  81. package/dist/i18n/keys.d.ts +12 -0
  82. package/dist/i18n/keys.js +76 -5
  83. package/dist/i18n/operation-locale-namespace.d.ts +2 -0
  84. package/dist/i18n/operation-locale-namespace.js +7 -0
  85. package/dist/index.d.ts +40 -15
  86. package/dist/index.js +29 -11
  87. package/dist/lint.d.ts +43 -11
  88. package/dist/lint.js +2512 -177
  89. package/dist/native-address.d.ts +43 -0
  90. package/dist/native-address.js +281 -0
  91. package/dist/native-egress-policy.d.ts +31 -0
  92. package/dist/native-egress-policy.js +288 -0
  93. package/dist/observability.d.ts +5 -2
  94. package/dist/observability.js +48 -1
  95. package/dist/provider.d.ts +18 -8
  96. package/dist/provider.js +11 -6
  97. package/dist/runtime/auth-flow.d.ts +6 -1
  98. package/dist/runtime/auth-flow.js +8 -0
  99. package/dist/runtime/bounded-expiring-map.d.ts +21 -0
  100. package/dist/runtime/bounded-expiring-map.js +64 -0
  101. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  102. package/dist/runtime/browser-telemetry-binding.js +83 -0
  103. package/dist/runtime/browser-telemetry.d.ts +76 -0
  104. package/dist/runtime/browser-telemetry.js +165 -0
  105. package/dist/runtime/browser.d.ts +4 -0
  106. package/dist/runtime/browser.js +688 -83
  107. package/dist/runtime/cache-telemetry.d.ts +71 -0
  108. package/dist/runtime/cache-telemetry.js +177 -0
  109. package/dist/runtime/cache.d.ts +3 -0
  110. package/dist/runtime/cache.js +245 -23
  111. package/dist/runtime/capability-response.d.ts +3 -0
  112. package/dist/runtime/capability-response.js +56 -0
  113. package/dist/runtime/capability-telemetry.d.ts +46 -0
  114. package/dist/runtime/capability-telemetry.js +68 -0
  115. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  116. package/dist/runtime/chrome149-header-order.js +289 -0
  117. package/dist/runtime/diagnostic-env-values.d.ts +5 -0
  118. package/dist/runtime/diagnostic-env-values.js +57 -0
  119. package/dist/runtime/diagnostic-env.d.ts +14 -0
  120. package/dist/runtime/diagnostic-env.js +44 -0
  121. package/dist/runtime/diagnostic-redactor.d.ts +54 -0
  122. package/dist/runtime/diagnostic-redactor.js +571 -0
  123. package/dist/runtime/egress-lease.d.ts +65 -0
  124. package/dist/runtime/egress-lease.js +236 -0
  125. package/dist/runtime/engine-mode.d.ts +74 -0
  126. package/dist/runtime/engine-mode.js +152 -0
  127. package/dist/runtime/env.js +14 -1
  128. package/dist/runtime/executor.d.ts +3 -2
  129. package/dist/runtime/executor.js +23 -26
  130. package/dist/runtime/handle-wordlist.d.ts +43 -0
  131. package/dist/runtime/handle-wordlist.js +256 -0
  132. package/dist/runtime/handle.d.ts +71 -0
  133. package/dist/runtime/handle.js +908 -0
  134. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  135. package/dist/runtime/http-telemetry-binding.js +152 -0
  136. package/dist/runtime/http-telemetry-guard.d.ts +16 -0
  137. package/dist/runtime/http-telemetry-guard.js +202 -0
  138. package/dist/runtime/http-telemetry.d.ts +104 -0
  139. package/dist/runtime/http-telemetry.js +190 -0
  140. package/dist/runtime/http.d.ts +34 -0
  141. package/dist/runtime/http.js +680 -121
  142. package/dist/runtime/insights.js +2 -2
  143. package/dist/runtime/instrumentation.d.ts +2 -2
  144. package/dist/runtime/instrumentation.js +462 -19
  145. package/dist/runtime/native-network-errors.d.ts +33 -0
  146. package/dist/runtime/native-network-errors.js +69 -0
  147. package/dist/runtime/native-network.d.ts +105 -0
  148. package/dist/runtime/native-network.js +1433 -0
  149. package/dist/runtime/native-telemetry.d.ts +142 -0
  150. package/dist/runtime/native-telemetry.js +231 -0
  151. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  152. package/dist/runtime/ocr-telemetry.js +192 -0
  153. package/dist/runtime/ocr.d.ts +29 -0
  154. package/dist/runtime/ocr.js +468 -0
  155. package/dist/runtime/otlp.d.ts +71 -2
  156. package/dist/runtime/otlp.js +398 -16
  157. package/dist/runtime/provider.d.ts +7 -0
  158. package/dist/runtime/provider.js +14 -0
  159. package/dist/runtime/proxy-errors.js +6 -2
  160. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  161. package/dist/runtime/proxy-nodemaven.js +147 -0
  162. package/dist/runtime/proxy-retry-policy.d.ts +4 -1
  163. package/dist/runtime/proxy-retry-policy.js +11 -2
  164. package/dist/runtime/proxy-telemetry.d.ts +133 -3
  165. package/dist/runtime/proxy-telemetry.js +218 -70
  166. package/dist/runtime/redirect-hop-headers.d.ts +19 -0
  167. package/dist/runtime/redirect-hop-headers.js +58 -0
  168. package/dist/runtime/redirects.d.ts +61 -0
  169. package/dist/runtime/redirects.js +149 -0
  170. package/dist/runtime/redis.d.ts +1 -1
  171. package/dist/runtime/redis.js +4 -2
  172. package/dist/runtime/request-options.d.ts +75 -1
  173. package/dist/runtime/request-options.js +588 -0
  174. package/dist/runtime/request-telemetry.d.ts +70 -0
  175. package/dist/runtime/request-telemetry.js +229 -0
  176. package/dist/runtime/resolver-config.d.ts +13 -0
  177. package/dist/runtime/resolver-config.js +14 -0
  178. package/dist/runtime/resolver-public.d.ts +3 -0
  179. package/dist/runtime/resolver-public.js +1 -0
  180. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  181. package/dist/runtime/resolver-runtime-options.js +33 -0
  182. package/dist/runtime/resolver-shared.d.ts +24 -0
  183. package/dist/runtime/resolver-shared.js +69 -0
  184. package/dist/runtime/resolver-span-attributes.d.ts +34 -0
  185. package/dist/runtime/resolver-span-attributes.js +93 -0
  186. package/dist/runtime/resolver-telemetry.d.ts +135 -0
  187. package/dist/runtime/resolver-telemetry.js +204 -0
  188. package/dist/runtime/resolver-usage.d.ts +32 -0
  189. package/dist/runtime/resolver-usage.js +54 -0
  190. package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
  191. package/dist/runtime/resolver-vendors/bindings.js +45 -0
  192. package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
  193. package/dist/runtime/resolver-vendors/browser.js +423 -0
  194. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  195. package/dist/runtime/resolver-vendors/capsolver.js +544 -0
  196. package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
  197. package/dist/runtime/resolver-vendors/hosts.js +68 -0
  198. package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
  199. package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
  200. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  201. package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
  202. package/dist/runtime/resolver-vendors/types.d.ts +125 -0
  203. package/dist/runtime/resolver-vendors/types.js +106 -0
  204. package/dist/runtime/resolver.d.ts +79 -0
  205. package/dist/runtime/resolver.js +1094 -0
  206. package/dist/runtime/state-telemetry.d.ts +79 -0
  207. package/dist/runtime/state-telemetry.js +194 -0
  208. package/dist/runtime/state.d.ts +10 -1
  209. package/dist/runtime/state.js +322 -82
  210. package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
  211. package/dist/runtime/stealth-akamai-sbsd.js +142 -0
  212. package/dist/runtime/stealth-cookies.d.ts +24 -0
  213. package/dist/runtime/stealth-cookies.js +131 -0
  214. package/dist/runtime/stealth-owned-headers.d.ts +39 -0
  215. package/dist/runtime/stealth-owned-headers.js +57 -0
  216. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  217. package/dist/runtime/stealth-telemetry.js +179 -0
  218. package/dist/runtime/stealth.d.ts +45 -6
  219. package/dist/runtime/stealth.js +1775 -320
  220. package/dist/runtime/stt-telemetry.d.ts +77 -0
  221. package/dist/runtime/stt-telemetry.js +176 -0
  222. package/dist/runtime/stt.js +18 -19
  223. package/dist/runtime/timeout.d.ts +5 -0
  224. package/dist/runtime/timeout.js +12 -0
  225. package/dist/runtime/trace-config.d.ts +12 -0
  226. package/dist/runtime/trace-config.js +62 -0
  227. package/dist/runtime/trace.d.ts +17 -0
  228. package/dist/runtime/trace.js +176 -25
  229. package/dist/runtime-boundary-lint.d.ts +137 -0
  230. package/dist/runtime-boundary-lint.js +590 -0
  231. package/dist/schema.d.ts +20 -0
  232. package/dist/schema.js +72 -2
  233. package/dist/serve.d.ts +1 -1
  234. package/dist/serve.js +1 -1
  235. package/dist/server/error-observability.d.ts +1 -0
  236. package/dist/server/error-observability.js +1 -0
  237. package/dist/server/error-stack-frames.d.ts +35 -0
  238. package/dist/server/error-stack-frames.js +219 -0
  239. package/dist/server/index.d.ts +21 -2
  240. package/dist/server/index.js +11 -2
  241. package/dist/server/runtime-diagnostics.d.ts +165 -0
  242. package/dist/server/runtime-diagnostics.js +360 -0
  243. package/dist/server/sdk-version.d.ts +3 -0
  244. package/dist/server/sdk-version.js +19 -0
  245. package/dist/server/self-test-input-tokens.d.ts +2 -1
  246. package/dist/server/self-test-input-tokens.js +18 -14
  247. package/dist/server/self-test-redaction.js +8 -33
  248. package/dist/server/self-test.d.ts +18 -3
  249. package/dist/server/self-test.js +139 -76
  250. package/dist/server/sensitive-values.d.ts +26 -0
  251. package/dist/server/sensitive-values.js +137 -0
  252. package/dist/server/serve-implementation.d.ts +285 -0
  253. package/dist/server/serve-implementation.js +3172 -0
  254. package/dist/server/serve.d.ts +1 -75
  255. package/dist/server/serve.js +1 -1170
  256. package/dist/server/state-backend-report.d.ts +70 -0
  257. package/dist/server/state-backend-report.js +113 -0
  258. package/dist/server/trace-output.d.ts +7 -0
  259. package/dist/server/trace-output.js +111 -0
  260. package/dist/server/types.d.ts +37 -5
  261. package/dist/server/types.js +34 -1
  262. package/dist/stateful/errors.d.ts +19 -0
  263. package/dist/stateful/errors.js +24 -0
  264. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  265. package/dist/stateful/http-provider-event-emitter.js +237 -0
  266. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  267. package/dist/stateful/http-session-owner-registry.js +210 -0
  268. package/dist/stateful/index.d.ts +18 -0
  269. package/dist/stateful/index.js +18 -0
  270. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  271. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  272. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  273. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  274. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  275. package/dist/stateful/provider-event-pipeline.js +1 -0
  276. package/dist/stateful/provider-events.d.ts +101 -0
  277. package/dist/stateful/provider-events.js +289 -0
  278. package/dist/stateful/session-key.d.ts +15 -0
  279. package/dist/stateful/session-key.js +86 -0
  280. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  281. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  282. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  283. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  284. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  285. package/dist/stateful/stateful-provider-adapter.js +287 -0
  286. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  287. package/dist/stateful/stateful-provider-observability.js +161 -0
  288. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  289. package/dist/stateful/stateful-provider-owner-forwarder.js +237 -0
  290. package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
  291. package/dist/stateful/stateful-provider-runtime-context.js +61 -0
  292. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  293. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  294. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  295. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  296. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  297. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  298. package/dist/stateful-signing.d.ts +18 -0
  299. package/dist/stateful-signing.js +27 -0
  300. package/dist/stealth/profiles.d.ts +15 -4
  301. package/dist/stealth/profiles.js +169 -211
  302. package/dist/stream-evidence.d.ts +74 -0
  303. package/dist/stream-evidence.js +785 -0
  304. package/dist/stream.js +7 -1
  305. package/dist/testing/index.d.ts +2 -1
  306. package/dist/testing/index.js +2 -1
  307. package/dist/testing/run.d.ts +32 -2
  308. package/dist/testing/run.js +509 -25
  309. package/dist/trace-sanitization.d.ts +6 -0
  310. package/dist/trace-sanitization.js +68 -0
  311. package/dist/types.d.ts +899 -210
  312. package/dist/types.js +1 -0
  313. package/dist/typescript-module.d.ts +39 -0
  314. package/dist/typescript-module.js +72 -0
  315. package/package.json +52 -6
  316. package/src/auth-turn/index.ts +1 -1
  317. package/src/auth.ts +118 -0
  318. package/src/ceremonies/index.ts +189 -46
  319. package/src/cli/commands.ts +37 -0
  320. package/src/cli/create.ts +8 -2
  321. package/src/cli/deployment-intent.ts +364 -0
  322. package/src/cli/local-connection.ts +221 -0
  323. package/src/cli/migrate-deployment-intent.ts +1908 -0
  324. package/src/cli/migrate-operation-declaration.ts +3026 -0
  325. package/src/cli/migrate-operation-shape.ts +184 -0
  326. package/src/cli/migrate-provider-shape.ts +778 -0
  327. package/src/cli/resolver-runtime.ts +72 -0
  328. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  329. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  330. package/src/cli/templates/provider/README.md.tpl +41 -19
  331. package/src/cli/templates/provider/index.ts.tpl +6 -3
  332. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  333. package/src/cli/templates/provider/provider.json.tpl +6 -0
  334. package/src/config/loader.ts +774 -181
  335. package/src/contract-serialization.ts +5 -7
  336. package/src/contract-types.ts +12 -5
  337. package/src/contract.ts +24 -10
  338. package/src/declaration-validation.ts +376 -0
  339. package/src/define.ts +1105 -131
  340. package/src/dev.ts +13 -1
  341. package/src/engine.ts +390 -0
  342. package/src/error-observability.ts +64 -0
  343. package/src/error-resolution.ts +269 -0
  344. package/src/errors.ts +157 -0
  345. package/src/fixture-sanitization.ts +422 -0
  346. package/src/handle-meta.ts +143 -0
  347. package/src/handle.ts +694 -0
  348. package/src/health-scenario.ts +960 -0
  349. package/src/i18n/catalog.ts +23 -1
  350. package/src/i18n/error-messages.ts +255 -0
  351. package/src/i18n/index.ts +1 -0
  352. package/src/i18n/keys.ts +80 -6
  353. package/src/i18n/operation-locale-namespace.ts +10 -0
  354. package/src/index.ts +390 -23
  355. package/src/lint.ts +2975 -234
  356. package/src/native-address.ts +340 -0
  357. package/src/native-egress-policy.ts +358 -0
  358. package/src/observability.ts +51 -1
  359. package/src/provider.ts +207 -27
  360. package/src/runtime/auth-flow.ts +15 -0
  361. package/src/runtime/bounded-expiring-map.ts +67 -0
  362. package/src/runtime/browser-telemetry-binding.ts +92 -0
  363. package/src/runtime/browser-telemetry.ts +289 -0
  364. package/src/runtime/browser.ts +882 -99
  365. package/src/runtime/cache-telemetry.ts +247 -0
  366. package/src/runtime/cache.ts +258 -22
  367. package/src/runtime/capability-response.ts +53 -0
  368. package/src/runtime/capability-telemetry.ts +101 -0
  369. package/src/runtime/chrome149-header-order.ts +330 -0
  370. package/src/runtime/diagnostic-env-values.ts +56 -0
  371. package/src/runtime/diagnostic-env.ts +65 -0
  372. package/src/runtime/diagnostic-redactor.ts +668 -0
  373. package/src/runtime/egress-lease.ts +341 -0
  374. package/src/runtime/engine-mode.ts +233 -0
  375. package/src/runtime/env.ts +15 -1
  376. package/src/runtime/executor.ts +39 -32
  377. package/src/runtime/handle-wordlist.ts +266 -0
  378. package/src/runtime/handle.ts +1180 -0
  379. package/src/runtime/http-telemetry-binding.ts +162 -0
  380. package/src/runtime/http-telemetry-guard.ts +208 -0
  381. package/src/runtime/http-telemetry.ts +292 -0
  382. package/src/runtime/http.ts +896 -150
  383. package/src/runtime/insights.ts +2 -2
  384. package/src/runtime/instrumentation.ts +628 -28
  385. package/src/runtime/native-network-errors.ts +99 -0
  386. package/src/runtime/native-network.ts +1853 -0
  387. package/src/runtime/native-telemetry.ts +366 -0
  388. package/src/runtime/ocr-telemetry.ts +308 -0
  389. package/src/runtime/ocr.ts +564 -0
  390. package/src/runtime/otlp.ts +468 -21
  391. package/src/runtime/provider.ts +15 -0
  392. package/src/runtime/proxy-errors.ts +12 -4
  393. package/src/runtime/proxy-nodemaven.ts +222 -0
  394. package/src/runtime/proxy-retry-policy.ts +12 -1
  395. package/src/runtime/proxy-telemetry.ts +358 -103
  396. package/src/runtime/redirect-hop-headers.ts +71 -0
  397. package/src/runtime/redirects.ts +215 -0
  398. package/src/runtime/redis.ts +7 -2
  399. package/src/runtime/request-options.ts +728 -1
  400. package/src/runtime/request-telemetry.ts +375 -0
  401. package/src/runtime/resolver-config.ts +18 -0
  402. package/src/runtime/resolver-public.ts +44 -0
  403. package/src/runtime/resolver-runtime-options.ts +50 -0
  404. package/src/runtime/resolver-shared.ts +113 -0
  405. package/src/runtime/resolver-span-attributes.ts +139 -0
  406. package/src/runtime/resolver-telemetry.ts +362 -0
  407. package/src/runtime/resolver-usage.ts +99 -0
  408. package/src/runtime/resolver-vendors/bindings.ts +61 -0
  409. package/src/runtime/resolver-vendors/browser.ts +591 -0
  410. package/src/runtime/resolver-vendors/capsolver.ts +727 -0
  411. package/src/runtime/resolver-vendors/hosts.ts +81 -0
  412. package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
  413. package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
  414. package/src/runtime/resolver-vendors/types.ts +261 -0
  415. package/src/runtime/resolver.ts +1543 -0
  416. package/src/runtime/state-telemetry.ts +298 -0
  417. package/src/runtime/state.ts +435 -89
  418. package/src/runtime/stealth-akamai-sbsd.ts +207 -0
  419. package/src/runtime/stealth-cookies.ts +155 -0
  420. package/src/runtime/stealth-owned-headers.ts +67 -0
  421. package/src/runtime/stealth-telemetry.ts +309 -0
  422. package/src/runtime/stealth.ts +2275 -372
  423. package/src/runtime/stt-telemetry.ts +282 -0
  424. package/src/runtime/stt.ts +96 -91
  425. package/src/runtime/timeout.ts +18 -0
  426. package/src/runtime/trace-config.ts +78 -0
  427. package/src/runtime/trace.ts +257 -35
  428. package/src/runtime-boundary-lint.ts +707 -0
  429. package/src/schema.ts +80 -2
  430. package/src/serve.ts +6 -1
  431. package/src/server/error-observability.ts +1 -0
  432. package/src/server/error-stack-frames.ts +214 -0
  433. package/src/server/index.ts +184 -2
  434. package/src/server/runtime-diagnostics.ts +522 -0
  435. package/src/server/sdk-version.ts +22 -0
  436. package/src/server/self-test-input-tokens.ts +29 -14
  437. package/src/server/self-test-redaction.ts +10 -40
  438. package/src/server/self-test.ts +205 -89
  439. package/src/server/sensitive-values.ts +211 -0
  440. package/src/server/serve-implementation.ts +4596 -0
  441. package/src/server/serve.ts +1 -1685
  442. package/src/server/state-backend-report.ts +185 -0
  443. package/src/server/trace-output.ts +189 -0
  444. package/src/server/types.ts +34 -1
  445. package/src/stateful/README.md +146 -0
  446. package/src/stateful/errors.ts +35 -0
  447. package/src/stateful/http-provider-event-emitter.ts +314 -0
  448. package/src/stateful/http-session-owner-registry.ts +306 -0
  449. package/src/stateful/index.ts +18 -0
  450. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  451. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  452. package/src/stateful/provider-event-pipeline.ts +61 -0
  453. package/src/stateful/provider-events.ts +462 -0
  454. package/src/stateful/session-key.ts +111 -0
  455. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  456. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  457. package/src/stateful/stateful-provider-adapter.ts +562 -0
  458. package/src/stateful/stateful-provider-observability.ts +261 -0
  459. package/src/stateful/stateful-provider-owner-forwarder.ts +310 -0
  460. package/src/stateful/stateful-provider-runtime-context.ts +96 -0
  461. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  462. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  463. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  464. package/src/stateful-signing.ts +46 -0
  465. package/src/stealth/profiles.ts +212 -231
  466. package/src/stream-evidence.ts +988 -0
  467. package/src/stream.ts +8 -1
  468. package/src/testing/index.ts +10 -1
  469. package/src/testing/run.ts +685 -19
  470. package/src/trace-sanitization.ts +110 -0
  471. package/src/types.ts +1014 -273
  472. package/src/typescript-module.ts +100 -0
  473. package/dist/choice-token.d.ts +0 -24
  474. package/dist/choice-token.js +0 -74
  475. package/dist/runtime/choice.d.ts +0 -15
  476. package/dist/runtime/choice.js +0 -413
  477. package/src/choice-token.ts +0 -165
  478. package/src/runtime/choice.ts +0 -686
package/src/define.ts CHANGED
@@ -1,7 +1,27 @@
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 {
10
+ engineOwnedResolverCredentialTarget,
11
+ isEngineOwnedEnvName,
12
+ isEngineOwnedProxyCredentialName,
13
+ isEngineOwnedRuntimeEnvName,
14
+ isEngineOwnedTelemetryEnvName,
15
+ } from "./engine.js";
16
+ import { HealthScenarioSchema } from "./health-scenario.js";
17
+ import { kindRequiresClientProfile } from "./runtime/resolver-config.js";
18
+ import {
19
+ NativeEgressPolicyValidationError,
20
+ validateNativeProviderConfig,
21
+ } from "./native-egress-policy.js";
4
22
  import { safeParseSchemaSync } from "./schema.js";
23
+ import { resolveHealthCheckInputDateTokens } from "./server/self-test-input-tokens.js";
24
+ import type { HandleKind } from "./handle.js";
5
25
  import type {
6
26
  AuthConfig,
7
27
  BrowserEngine,
@@ -14,6 +34,7 @@ import type {
14
34
  HealthJourneySchedule,
15
35
  HealthScheduleRandomization,
16
36
  InferSchemaOutput,
37
+ NativeProviderConfig,
17
38
  OperationDefinition,
18
39
  OperationHandlerResult,
19
40
  OperationHttpStreamTransport,
@@ -21,18 +42,24 @@ import type {
21
42
  OperationTransport,
22
43
  OperationWebSocketTransport,
23
44
  ProviderAccessConfig,
45
+ ProviderChallengeKind,
46
+ ProviderContext,
24
47
  ProviderDefinition,
25
48
  ProviderDeploymentOverrides,
26
49
  ProviderHealthMonitorConfig,
50
+ ProviderOcrConfig,
27
51
  ProviderProxyConfig,
28
52
  ProviderPublicProfile,
53
+ ProviderResolverConfig,
54
+ ProviderResolverVendor,
29
55
  ProviderReviewed,
56
+ ProviderRuntimeTarget,
30
57
  ProviderSecretDeclaration,
31
58
  ProviderStreamEvent,
32
59
  ProviderSttConfig,
60
+ ProviderStealthConfig,
33
61
  SchemaLike,
34
62
  SmsOtpMatcherDefinition,
35
- StealthPlatform,
36
63
  } from "./types.js";
37
64
  import {
38
65
  HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX,
@@ -49,6 +76,7 @@ import {
49
76
  STREAM_IDLE_TIMEOUT_MS_MIN,
50
77
  STREAM_MAX_DURATION_MS_MAX,
51
78
  STREAM_MAX_DURATION_MS_MIN,
79
+ VALID_OPERATION_ERROR_STATUSES,
52
80
  } from "./types.js";
53
81
 
54
82
  type ProviderImplementationSourceAccess =
@@ -75,22 +103,80 @@ interface ProviderImplementationProfile {
75
103
  const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
76
104
  const OPERATION_ID_REGEX = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
77
105
  const VALID_RUNTIMES = ["standard", "shared", "browser"] as const;
78
- const VALID_AUTH_MODES = ["none", "platform-managed", "credentials", "oauth2"] as const;
106
+ const VALID_RUNTIME_TARGETS = ["vanilla", "engine"] as const;
107
+ const VALID_AUTH_MODES = [
108
+ "none",
109
+ "platform-managed",
110
+ "credentials",
111
+ "oauth2",
112
+ "oauth2_proxied",
113
+ ] as const;
114
+ const PROXIED_OAUTH_REQUIRED_FIELDS = [
115
+ "authorizeUrl",
116
+ "tokenUrl",
117
+ "customScheme",
118
+ "rewriteProfile",
119
+ "clientIdEnvKey",
120
+ ] as const;
121
+ const PROXIED_OAUTH_ALLOWED_FIELDS = new Set([
122
+ ...PROXIED_OAUTH_REQUIRED_FIELDS,
123
+ "pkce",
124
+ "authorizeParams",
125
+ "tokenParams",
126
+ ]);
127
+ const PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS = new Set([
128
+ "client_id",
129
+ "response_type",
130
+ "state",
131
+ "code_challenge",
132
+ "code_challenge_method",
133
+ ]);
134
+ const PROXIED_OAUTH_PROFILE_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
135
+ const PROXIED_OAUTH_ENV_KEY_REGEX = /^[A-Z][A-Z0-9_]*__[A-Z0-9_]+$/;
136
+ const CUSTOM_SCHEME_REGEX = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/\S+$/;
79
137
  const VALID_PROVIDER_ACCESS_VISIBILITIES = ["public", "early_access"] as const;
80
138
  const VALID_PROVIDER_PROXY_MODES = ["disabled", "optional", "required"] as const;
81
- const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "decodo", "custom"] as const;
139
+ const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "nodemaven", "decodo", "custom"] as const;
82
140
  const VALID_PROVIDER_PROXY_AFFINITIES = [
83
141
  "request",
84
142
  "operation",
85
143
  "auth-flow",
86
144
  "connection",
87
145
  ] as const;
146
+ const VALID_PROVIDER_OCR_MODES = ["optional", "required"] as const;
88
147
  const VALID_PROVIDER_STT_MODES = ["optional", "required"] as const;
89
- const SMARTPROXY_APP_KEY_SECRET = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
148
+ function exhaustiveLiteralArray<TUnion extends string>() {
149
+ return <const TValues extends readonly TUnion[]>(
150
+ values: TValues,
151
+ ..._missing: Exclude<TUnion, TValues[number]> extends never
152
+ ? []
153
+ : ["Missing runtime values", Exclude<TUnion, TValues[number]>]
154
+ ): TValues => values;
155
+ }
156
+
157
+ export const VALID_PROVIDER_RESOLVER_VENDORS = exhaustiveLiteralArray<ProviderResolverVendor>()([
158
+ "browser",
159
+ "capsolver",
160
+ "capmonster",
161
+ "2captcha",
162
+ "hypersolutions",
163
+ "custom",
164
+ ] as const);
165
+ export const VALID_PROVIDER_CHALLENGE_KINDS = exhaustiveLiteralArray<ProviderChallengeKind>()([
166
+ "turnstile",
167
+ "recaptcha_v2",
168
+ "recaptcha_v3",
169
+ "hcaptcha",
170
+ "cloudflare_interstitial",
171
+ "aws_waf",
172
+ "akamai_sec_cpt",
173
+ "akamai_sensor",
174
+ "akamai_sbsd",
175
+ ] as const);
90
176
  const RESERVED_OPERATION_IDS = new Set(["auth", "health"]);
91
- const MCP_TOOL_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_]{0,127}$/;
92
177
  const VALID_OPERATION_RISK_CLASSES = ["read", "write", "destructive", "external-send"] as const;
93
178
  const VALID_OPERATION_APPROVAL_POLICIES = ["never", "risk-based", "always"] as const;
179
+ const VALID_OPERATION_CONNECTION_MODES = ["none", "optional", "required"] as const;
94
180
  const VALID_OPERATION_TRANSPORT_KINDS = ["json", "sse", "http-stream", "websocket"] as const;
95
181
  const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
96
182
  const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
@@ -114,95 +200,429 @@ function parsePositiveMsDuration(value: string): number | undefined {
114
200
  return parsed;
115
201
  }
116
202
 
117
- type ProviderOperation = OperationDefinition<SchemaLike, SchemaLike>;
118
- type OperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
119
- OperationDefinition<TInput, TOutput>,
120
- "handler"
121
- > & {
203
+ type ProviderOperation = OperationDefinition<any, any, any>;
204
+ type OperationConfig<
205
+ TInput extends SchemaLike,
206
+ TOutput extends SchemaLike,
207
+ TContext = ProviderContext,
208
+ > = Omit<OperationDefinition<TInput, TOutput, TContext>, "handler"> & {
122
209
  handler(
123
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
210
+ ctx: TContext,
124
211
  input: InferSchemaOutput<TInput>,
125
212
  ):
126
213
  | OperationHandlerResult<InferSchemaOutput<TOutput>>
127
214
  | Promise<OperationHandlerResult<InferSchemaOutput<TOutput>>>;
128
215
  };
129
- type OperationMapConfig<TOperations extends Record<string, ProviderOperation>> = {
216
+ type OperationMapConfig<
217
+ TOperations extends Record<string, ProviderOperation>,
218
+ TContext = ProviderContext,
219
+ > = {
130
220
  [K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput>
131
- ? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput>
221
+ ? OperationConfig<TInput, TOutput, TContext> | OperationDefinition<TInput, TOutput, TContext>
132
222
  : never;
133
223
  };
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
- > & {
224
+ type StreamOperationConfig<
225
+ TInput extends SchemaLike,
226
+ TOutput extends SchemaLike,
227
+ TContext = ProviderContext,
228
+ > =
229
+ | SseOperationConfig<TInput, TOutput, TContext>
230
+ | HttpStreamOperationConfig<TInput, TOutput, TContext>
231
+ | WebSocketOperationConfig<TInput, TOutput, TContext>;
232
+ type SseOperationConfig<
233
+ TInput extends SchemaLike,
234
+ TOutput extends SchemaLike,
235
+ TContext = ProviderContext,
236
+ > = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
142
237
  transport: OperationSseTransport;
143
238
  handler(
144
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
239
+ ctx: TContext,
145
240
  input: InferSchemaOutput<TInput>,
146
241
  ): AsyncIterable<ProviderStreamEvent> | Promise<AsyncIterable<ProviderStreamEvent>>;
147
242
  };
148
- type HttpStreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
149
- OperationConfig<TInput, TOutput>,
150
- "handler" | "transport"
151
- > & {
243
+ type HttpStreamOperationConfig<
244
+ TInput extends SchemaLike,
245
+ TOutput extends SchemaLike,
246
+ TContext = ProviderContext,
247
+ > = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
152
248
  transport: OperationHttpStreamTransport;
153
249
  handler(
154
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
250
+ ctx: TContext,
155
251
  input: InferSchemaOutput<TInput>,
156
252
  ): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
157
253
  };
158
- type WebSocketOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
159
- OperationConfig<TInput, TOutput>,
160
- "handler" | "transport"
161
- > & {
254
+ type WebSocketOperationConfig<
255
+ TInput extends SchemaLike,
256
+ TOutput extends SchemaLike,
257
+ TContext = ProviderContext,
258
+ > = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
162
259
  transport: OperationWebSocketTransport;
163
260
  handler(
164
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
261
+ ctx: TContext,
165
262
  input: InferSchemaOutput<TInput>,
166
263
  ): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
167
264
  };
168
265
 
169
- type AuthStartNoInputGuard<TConfig> = TConfig extends {
266
+ type AuthStartHandlerNoInputGuard<TStart> = TStart extends (...args: infer TArgs) => unknown
267
+ ? TArgs["length"] extends 0 | 1
268
+ ? unknown
269
+ : {
270
+ "auth start handlers must not declare input parameters; return a form turn from start and receive user input in continue": never;
271
+ }
272
+ : unknown;
273
+
274
+ export type AuthStartNoInputGuard<TConfig> = TConfig extends {
170
275
  auth?: { flow?: { start: infer TStart } };
171
276
  }
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;
277
+ ? AuthStartHandlerNoInputGuard<TStart>
278
+ : TConfig extends { start: infer TStart }
279
+ ? AuthStartHandlerNoInputGuard<TStart>
280
+ : unknown;
281
+
282
+ function splitAuthStartParameters(parameters: string): string[] | undefined {
283
+ const parts: string[] = [];
284
+ let start = 0;
285
+ let round = 0;
286
+ let square = 0;
287
+ let curly = 0;
288
+ let quote: "'" | '"' | "`" | undefined;
289
+ let escaped = false;
290
+ let lineComment = false;
291
+ let blockComment = false;
292
+ const templateDepths = [0];
293
+ templateDepths.length = 0;
294
+
295
+ for (let index = 0; index < parameters.length; index++) {
296
+ const character = parameters[index];
297
+ const nextCharacter = parameters[index + 1];
298
+ if (lineComment) {
299
+ if (character === "\n" || character === "\r") lineComment = false;
300
+ else continue;
301
+ }
302
+ if (blockComment) {
303
+ if (character === "*" && nextCharacter === "/") {
304
+ blockComment = false;
305
+ index++;
306
+ }
307
+ continue;
308
+ }
309
+ if (quote) {
310
+ if (escaped) {
311
+ escaped = false;
312
+ } else if (character === "\\") {
313
+ escaped = true;
314
+ } else if (quote === "`" && character === "$" && nextCharacter === "{") {
315
+ curly++;
316
+ templateDepths.push(curly);
317
+ quote = undefined;
318
+ index++;
319
+ } else if (character === quote) {
320
+ quote = undefined;
321
+ }
322
+ continue;
323
+ }
324
+ if (character === "'" || character === '"' || character === "`") {
325
+ quote = character;
326
+ continue;
327
+ }
328
+ if (character === "/" && nextCharacter === "/") {
329
+ lineComment = true;
330
+ index++;
331
+ continue;
332
+ }
333
+ if (character === "/" && nextCharacter === "*") {
334
+ blockComment = true;
335
+ index++;
336
+ continue;
337
+ }
338
+ if (character === "/") return undefined;
339
+ // Annex B HTML-like comments are not lexed here; give up rather than
340
+ // risk misreading the parameter list.
341
+ if (character === "<" && parameters.startsWith("!--", index + 1)) return undefined;
342
+ if (character === "-" && parameters.startsWith("->", index + 1)) return undefined;
343
+ if (character === "(") round++;
344
+ else if (character === ")") round--;
345
+ else if (character === "[") square++;
346
+ else if (character === "]") square--;
347
+ else if (character === "{") curly++;
348
+ else if (character === "}") {
349
+ if (templateDepths.at(-1) === curly) {
350
+ templateDepths.pop();
351
+ curly--;
352
+ quote = "`";
353
+ } else curly--;
354
+ } else if (character === "," && round === 0 && square === 0 && curly === 0) {
355
+ parts.push(parameters.slice(start, index));
356
+ start = index + 1;
357
+ }
358
+ if (round < 0 || square < 0 || curly < 0) return undefined;
359
+ }
360
+
361
+ if (
362
+ quote ||
363
+ blockComment ||
364
+ templateDepths.length > 0 ||
365
+ round !== 0 ||
366
+ square !== 0 ||
367
+ curly !== 0
368
+ )
369
+ return undefined;
370
+ parts.push(parameters.slice(start));
371
+ return parts;
372
+ }
373
+
374
+ function authStartParameterList(source: string): string | undefined {
375
+ let index = 0;
376
+ while (index < source.length && /\s/.test(source[index] ?? "")) index++;
377
+ if (index >= source.length) return undefined;
378
+
379
+ let openIndex = -1;
380
+ let parenthesizedArrow = false;
381
+ let asyncMethodOrArrow = false;
382
+ const skipTrivia = () => {
383
+ while (index < source.length) {
384
+ if (/\s/.test(source[index] ?? "")) {
385
+ index++;
386
+ continue;
387
+ }
388
+ if (source[index] === "/" && source[index + 1] === "/") {
389
+ index += 2;
390
+ while (index < source.length && source[index] !== "\n" && source[index] !== "\r") index++;
391
+ continue;
392
+ }
393
+ if (source[index] === "/" && source[index + 1] === "*") {
394
+ const end = source.indexOf("*/", index + 2);
395
+ if (end < 0) {
396
+ index = source.length;
397
+ return;
177
398
  }
178
- : unknown
179
- : unknown;
399
+ index = end + 2;
400
+ continue;
401
+ }
402
+ return;
403
+ }
404
+ };
180
405
 
181
- export interface ProviderConfig<TOperations extends Record<string, ProviderOperation>> {
406
+ const initial = source.slice(index);
407
+ const isAsync = initial.startsWith("async") && !/[\w$]/.test(initial[5] ?? "");
408
+ if (isAsync) {
409
+ index += 5;
410
+ skipTrivia();
411
+ }
412
+
413
+ const afterAsync = source.slice(index);
414
+ const isFunction = afterAsync.startsWith("function") && !/[\w$]/.test(afterAsync[8] ?? "");
415
+ if (isFunction) {
416
+ index += 8;
417
+ skipTrivia();
418
+ if (source[index] === "*") {
419
+ index++;
420
+ skipTrivia();
421
+ }
422
+ } else if (source[index] === "*") {
423
+ index++;
424
+ skipTrivia();
425
+ }
426
+
427
+ if (source[index] === "(") {
428
+ openIndex = index;
429
+ parenthesizedArrow = !isFunction;
430
+ asyncMethodOrArrow = isAsync && !isFunction;
431
+ } else if (isFunction) {
432
+ if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
433
+ index++;
434
+ while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
435
+ skipTrivia();
436
+ if (source[index] !== "(") return undefined;
437
+ openIndex = index;
438
+ } else {
439
+ const identifierStart = index;
440
+ if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
441
+ index++;
442
+ while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
443
+ const firstIdentifier = source.slice(identifierStart, index);
444
+ skipTrivia();
445
+ if (source[index] === "=" && source[index + 1] === ">") return undefined;
446
+ if (source[index] !== "(") {
447
+ if (firstIdentifier !== "get" && firstIdentifier !== "set") return undefined;
448
+ if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
449
+ index++;
450
+ while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
451
+ skipTrivia();
452
+ }
453
+ if (source[index] !== "(") return undefined;
454
+ openIndex = index;
455
+ }
456
+ if (openIndex < 0) return undefined;
457
+
458
+ let depth = 1;
459
+ let square = 0;
460
+ let curly = 0;
461
+ let quote: "'" | '"' | "`" | undefined;
462
+ let escaped = false;
463
+ let lineComment = false;
464
+ let blockComment = false;
465
+ const templateDepths = [0];
466
+ templateDepths.length = 0;
467
+ for (index = openIndex + 1; index < source.length; index++) {
468
+ const character = source[index];
469
+ const nextCharacter = source[index + 1];
470
+ if (lineComment) {
471
+ if (character === "\n" || character === "\r") lineComment = false;
472
+ else continue;
473
+ }
474
+ if (blockComment) {
475
+ if (character === "*" && nextCharacter === "/") {
476
+ blockComment = false;
477
+ index++;
478
+ }
479
+ continue;
480
+ }
481
+ if (quote) {
482
+ if (escaped) escaped = false;
483
+ else if (character === "\\") escaped = true;
484
+ else if (quote === "`" && character === "$" && nextCharacter === "{") {
485
+ curly++;
486
+ templateDepths.push(curly);
487
+ quote = undefined;
488
+ index++;
489
+ } else if (character === quote) quote = undefined;
490
+ continue;
491
+ }
492
+ if (character === "'" || character === '"' || character === "`") {
493
+ quote = character;
494
+ continue;
495
+ }
496
+ if (character === "/" && nextCharacter === "/") {
497
+ lineComment = true;
498
+ index++;
499
+ continue;
500
+ }
501
+ if (character === "/" && nextCharacter === "*") {
502
+ blockComment = true;
503
+ index++;
504
+ continue;
505
+ }
506
+ if (character === "/") return undefined;
507
+ // Annex B HTML-like comments are not lexed here; give up rather than
508
+ // risk misreading the parameter list.
509
+ if (character === "<" && source.startsWith("!--", index + 1)) return undefined;
510
+ if (character === "-" && source.startsWith("->", index + 1)) return undefined;
511
+ if (character === "(") depth++;
512
+ else if (character === ")") {
513
+ depth--;
514
+ if (depth === 0 && square === 0 && curly === 0) {
515
+ const closeIndex = index;
516
+ if (parenthesizedArrow) {
517
+ index++;
518
+ skipTrivia();
519
+ const hasArrow = source[index] === "=" && source[index + 1] === ">";
520
+ if (!hasArrow && (!asyncMethodOrArrow || source[index] !== "{")) return undefined;
521
+ }
522
+ return source.slice(openIndex + 1, closeIndex);
523
+ }
524
+ if (depth < 0) return undefined;
525
+ } else if (character === "[") square++;
526
+ else if (character === "]") {
527
+ square--;
528
+ if (square < 0) return undefined;
529
+ } else if (character === "{") curly++;
530
+ else if (character === "}") {
531
+ if (templateDepths.at(-1) === curly) {
532
+ templateDepths.pop();
533
+ curly--;
534
+ quote = "`";
535
+ } else curly--;
536
+ if (curly < 0) return undefined;
537
+ }
538
+ }
539
+ return undefined;
540
+ }
541
+
542
+ /**
543
+ * Conservative defense in depth for defaulted second parameters, which
544
+ * JavaScript intentionally omits from Function.length. Ambiguous source is
545
+ * ignored so this check can never reject a valid provider on weak evidence.
546
+ */
547
+ function authStartHasHiddenInput(start: unknown): boolean {
548
+ let source: string;
549
+ try {
550
+ source = Function.prototype.toString.call(start);
551
+ } catch {
552
+ return false;
553
+ }
554
+ if (
555
+ !source ||
556
+ source.includes("[native code]") ||
557
+ /^\s*(?:async\s+)?function\s+bound\b/.test(source)
558
+ )
559
+ return false;
560
+
561
+ const parameters = authStartParameterList(source);
562
+ if (!parameters) return false;
563
+ const parts = splitAuthStartParameters(parameters);
564
+ if (!parts || parts.length < 2) return false;
565
+
566
+ // Require ordinary, readable source formatting. This intentionally fails
567
+ // open for minified output and for transpilers that rewrite defaults.
568
+ const commaIndex = parameters.indexOf(",");
569
+ if (commaIndex < 0 || !/\s/.test(parameters[commaIndex + 1] ?? "")) return false;
570
+ const first = parts[0].trim();
571
+ const second = parts[1].trim();
572
+ const identifier = /^[_$A-Za-z][_$A-Za-z0-9]*/;
573
+ const firstName = first.match(identifier)?.[0];
574
+ const secondName = second.match(identifier)?.[0];
575
+ if (!firstName || !secondName || firstName.length < 3 || secondName.length < 3) return false;
576
+ return /\s=\s/.test(second);
577
+ }
578
+
579
+ export interface ProviderDeclaration {
182
580
  id: string;
183
581
  version: string;
184
582
  runtime: "standard" | "shared" | "browser";
583
+ /** Provider business-logic target. Omit only while migrating a legacy provider. */
584
+ runtimeTarget?: ProviderRuntimeTarget;
185
585
  /**
186
586
  * Optional deployment overrides, passed through verbatim onto the returned
187
587
  * provider definition. The SDK types this field but does not deep-validate
188
588
  * it — the APIFuse registry builder owns deployment validation and
189
589
  * resolves omitted fields against the runtime deployment profiles.
590
+ *
591
+ * This is the only authored deployment surface (no standalone
592
+ * `deploy.ts`); declare only the fields that differ from the profile.
190
593
  */
191
594
  deployment?: ProviderDeploymentOverrides;
595
+ /** Declares the HTTP capability binding. A bare object states use without configuration. */
596
+ http?: Record<string, never> | true;
597
+ /** Declares upstream host policy; this does not add a `ctx.allowedHosts` member. */
192
598
  allowedHosts?: string[];
193
- stealth?: {
194
- profile: string;
195
- platform: StealthPlatform;
196
- };
599
+ native?: NativeProviderConfig;
600
+ stealth?: ProviderStealthConfig;
601
+ /** Declares proxy policy; this is provider intent and does not add a `ctx.proxy` member. */
197
602
  proxy?: ProviderProxyConfig;
603
+ ocr?: ProviderOcrConfig;
198
604
  stt?: ProviderSttConfig;
605
+ resolver?: ProviderResolverConfig;
199
606
  browser?: { engine: BrowserEngine };
200
607
  auth?: AuthConfig;
608
+ /** Declares the handle kinds this provider issues and accepts (ADR-0012). */
609
+ handle?: readonly HandleKind[];
201
610
  reviewed?: ProviderReviewed;
202
611
  access?: ProviderAccessConfig;
612
+ /** Declares secret requirements; this does not add a `ctx.secrets` member. */
203
613
  secrets?: ProviderSecretDeclaration[];
614
+ /** Declares the environment capability binding. A bare object states use without configuration. */
615
+ env?: Record<string, never> | true;
616
+ /** Declares credential keys; platform-managed auth declares this capability without key filtering. */
204
617
  credential?: CredentialDeclaration;
618
+ /** Declares provider context metadata; this does not add a `ctx.context` member. */
205
619
  context?: ContextDeclaration;
620
+ /** Declares the state capability binding. A bare object states use without configuration. */
621
+ state?: Record<string, never> | true;
622
+ /** Declares the cache capability binding. A bare object states use without configuration. */
623
+ cache?: Record<string, never> | true;
624
+ /** Declares the files capability binding. A bare object states use without configuration. */
625
+ files?: Record<string, never> | true;
206
626
  meta: {
207
627
  displayName: string;
208
628
  displayNameKey?: string;
@@ -222,27 +642,41 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
222
642
  implementationProfile?: ProviderImplementationProfile;
223
643
  contract?: {
224
644
  publicSchemaFieldNames?: "normalized";
645
+ readonly pinnedWireFieldPaths?: readonly {
646
+ readonly path: string;
647
+ readonly reason: string;
648
+ }[];
225
649
  };
226
650
  };
227
- operations: OperationMapConfig<TOperations>;
228
651
  healthMonitor?: ProviderHealthMonitorConfig;
229
652
  /** New name for `healthMonitor` (transitional alias); declaring both is a ValidationError. */
230
653
  healthProbe?: ProviderHealthMonitorConfig;
231
654
  healthJourneys?: readonly HealthJourneyDefinition[];
232
655
  }
233
656
 
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;
657
+ interface ProviderConfig<
658
+ TOperations extends Record<string, ProviderOperation>,
659
+ TContext = ProviderContext,
660
+ > extends ProviderDeclaration {
661
+ operations: OperationMapConfig<TOperations, TContext>;
239
662
  }
240
663
 
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;
664
+ /** Define one factored provider operation with schema-driven handler inference. */
665
+ export function defineOperation<TContext>() {
666
+ return function operation<TInput extends SchemaLike, TOutput extends SchemaLike>(
667
+ config: OperationConfig<TInput, TOutput, TContext>,
668
+ ): OperationDefinition<TInput, TOutput, TContext> {
669
+ return config;
670
+ };
671
+ }
672
+
673
+ /** Define a factored non-JSON operation with explicit transport metadata. */
674
+ export function defineStreamOperation<TContext>() {
675
+ return function streamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
676
+ config: StreamOperationConfig<TInput, TOutput, TContext>,
677
+ ): OperationDefinition<TInput, TOutput, TContext> {
678
+ return config;
679
+ };
246
680
  }
247
681
 
248
682
  function assertObjectConfig(value: unknown): asserts value is Record<string, unknown> {
@@ -279,18 +713,153 @@ function assertLiteralField<TValue extends string>(
279
713
  );
280
714
  }
281
715
  }
282
- function validateProviderShape(config: unknown): void {
716
+
717
+ function validateProxiedOAuthParams(
718
+ value: unknown,
719
+ field: "authorizeParams" | "tokenParams",
720
+ providerId: string,
721
+ ): void {
722
+ if (value === undefined) return;
723
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
724
+ throw new ValidationError(
725
+ `Provider "${providerId}" auth.proxied.${field} must be an object of string values.`,
726
+ );
727
+ }
728
+ for (const [key, paramValue] of Object.entries(value)) {
729
+ if (!key.trim() || typeof paramValue !== "string") {
730
+ throw new ValidationError(
731
+ `Provider "${providerId}" auth.proxied.${field} must contain non-empty keys and string values.`,
732
+ );
733
+ }
734
+ if (
735
+ field === "authorizeParams" &&
736
+ PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())
737
+ ) {
738
+ throw new ValidationError(
739
+ `Provider "${providerId}" auth.proxied.authorizeParams cannot override reserved parameter "${key}".`,
740
+ );
741
+ }
742
+ }
743
+ }
744
+
745
+ function validateProxiedOAuthAuth(auth: Record<string, unknown>, providerId: string): void {
746
+ const proxied = auth.proxied;
747
+ if (auth.mode !== "oauth2_proxied") {
748
+ if (proxied !== undefined) {
749
+ throw new ValidationError(
750
+ `Provider "${providerId}" auth.proxied is only valid when auth.mode is "oauth2_proxied".`,
751
+ );
752
+ }
753
+ return;
754
+ }
755
+ if (!proxied || typeof proxied !== "object" || Array.isArray(proxied)) {
756
+ throw new ValidationError(
757
+ `Provider "${providerId}" with auth.mode "oauth2_proxied" must declare auth.proxied.`,
758
+ );
759
+ }
760
+ const config = Object.fromEntries(Object.entries(proxied));
761
+ for (const key of Object.keys(config)) {
762
+ if (!PROXIED_OAUTH_ALLOWED_FIELDS.has(key)) {
763
+ throw new ValidationError(
764
+ `Provider "${providerId}" has unknown auth.proxied field "${key}".`,
765
+ );
766
+ }
767
+ }
768
+ for (const field of PROXIED_OAUTH_REQUIRED_FIELDS) {
769
+ if (typeof config[field] !== "string" || !config[field].trim()) {
770
+ throw new ValidationError(
771
+ `Provider "${providerId}" auth.proxied.${field} must be a non-empty string.`,
772
+ );
773
+ }
774
+ }
775
+ for (const field of ["authorizeUrl", "tokenUrl"] as const) {
776
+ try {
777
+ const endpoint = new URL(String(config[field]));
778
+ if (
779
+ endpoint.protocol !== "https:" ||
780
+ endpoint.username ||
781
+ endpoint.password ||
782
+ endpoint.hash
783
+ ) {
784
+ throw new Error("invalid endpoint");
785
+ }
786
+ } catch {
787
+ throw new ValidationError(
788
+ `Provider "${providerId}" auth.proxied.${field} must be an absolute HTTPS URL without credentials or a fragment.`,
789
+ );
790
+ }
791
+ }
792
+ const customScheme = String(config.customScheme);
793
+ if (
794
+ !CUSTOM_SCHEME_REGEX.test(customScheme) ||
795
+ customScheme.toLowerCase().startsWith("http://") ||
796
+ customScheme.toLowerCase().startsWith("https://")
797
+ ) {
798
+ throw new ValidationError(
799
+ `Provider "${providerId}" auth.proxied.customScheme must be a non-HTTP custom-scheme URL prefix.`,
800
+ );
801
+ }
802
+ if (!PROXIED_OAUTH_PROFILE_REGEX.test(String(config.rewriteProfile))) {
803
+ throw new ValidationError(
804
+ `Provider "${providerId}" auth.proxied.rewriteProfile must be a kebab-case profile name.`,
805
+ );
806
+ }
807
+ if (!PROXIED_OAUTH_ENV_KEY_REGEX.test(String(config.clientIdEnvKey))) {
808
+ throw new ValidationError(
809
+ `Provider "${providerId}" auth.proxied.clientIdEnvKey must be an APIFuse-style uppercase environment key.`,
810
+ );
811
+ }
812
+ if (config.pkce !== undefined && config.pkce !== "S256" && config.pkce !== "none") {
813
+ throw new ValidationError(
814
+ `Provider "${providerId}" auth.proxied.pkce must be "S256" or "none".`,
815
+ );
816
+ }
817
+ validateProxiedOAuthParams(config.authorizeParams, "authorizeParams", providerId);
818
+ validateProxiedOAuthParams(config.tokenParams, "tokenParams", providerId);
819
+ }
820
+
821
+ function validateProviderDeclarationShape(config: unknown): void {
283
822
  assertObjectConfig(config);
284
823
  assertRequiredField(config, "id");
285
824
  assertRequiredField(config, "version", String(config.id));
286
825
  assertRequiredField(config, "runtime", String(config.id));
287
826
  assertRequiredField(config, "meta", String(config.id));
288
- assertRequiredField(config, "operations", String(config.id));
289
827
  if (typeof config.runtime === "string")
290
828
  assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
829
+ if (config.runtimeTarget !== undefined && typeof config.runtimeTarget !== "string") {
830
+ throw new ValidationError(
831
+ `Provider "${String(config.id)}" has invalid runtimeTarget: expected "vanilla" or "engine"`,
832
+ );
833
+ }
834
+ if (typeof config.runtimeTarget === "string")
835
+ assertLiteralField(
836
+ config.runtimeTarget,
837
+ "runtimeTarget",
838
+ VALID_RUNTIME_TARGETS,
839
+ String(config.id),
840
+ );
841
+ if (config.native !== undefined && config.runtime === "browser") {
842
+ throw new ValidationError(
843
+ `Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`,
844
+ {
845
+ fix: 'Use runtime: "standard" or runtime: "shared", or remove the native declaration.',
846
+ },
847
+ );
848
+ }
849
+ if (config.native !== undefined && config.runtimeTarget === "vanilla") {
850
+ throw new ValidationError(
851
+ `Provider "${String(config.id)}" cannot declare capability "native" with runtime target "vanilla"; native requires an engine-resident runtime`,
852
+ {
853
+ fix: 'Set runtimeTarget: "engine", or remove the native declaration.',
854
+ },
855
+ );
856
+ }
291
857
  const auth = config.auth;
292
858
  if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
293
859
  assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
860
+ if (auth && typeof auth === "object" && !Array.isArray(auth)) {
861
+ validateProxiedOAuthAuth(Object.fromEntries(Object.entries(auth)), String(config.id));
862
+ }
294
863
  if (auth && typeof auth === "object" && "exchange" in auth) {
295
864
  throw new ProviderError(
296
865
  `Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`,
@@ -316,6 +885,24 @@ function validateProviderShape(config: unknown): void {
316
885
  },
317
886
  );
318
887
  }
888
+ if (
889
+ auth &&
890
+ typeof auth === "object" &&
891
+ "flow" in auth &&
892
+ auth.flow &&
893
+ typeof auth.flow === "object" &&
894
+ "start" in auth.flow &&
895
+ typeof auth.flow.start === "function" &&
896
+ auth.flow.start.length <= 1 &&
897
+ authStartHasHiddenInput(auth.flow.start)
898
+ ) {
899
+ throw new ProviderError(
900
+ `Provider "${String(config.id)}" auth.flow.start must not declare an input parameter`,
901
+ {
902
+ fix: "Return a form turn from start(ctx), then receive user input in continue(ctx, input).",
903
+ },
904
+ );
905
+ }
319
906
  const access = config.access;
320
907
  if (access !== undefined) {
321
908
  if (!access || typeof access !== "object" || Array.isArray(access)) {
@@ -354,11 +941,68 @@ function validateProviderShape(config: unknown): void {
354
941
  }
355
942
  }
356
943
 
944
+ function validateProviderImplementationShape(config: { id: string }): void {
945
+ const configRecord = config as unknown as Record<string, unknown>;
946
+ assertRequiredField(configRecord, "operations", String(config.id));
947
+ }
948
+
357
949
  function validateProviderProxy(config: {
358
950
  id: string;
359
951
  proxy?: ProviderProxyConfig;
360
952
  secrets?: ProviderSecretDeclaration[];
361
953
  }): void {
954
+ for (const secret of config.secrets ?? []) {
955
+ if (
956
+ secret.issuer !== undefined &&
957
+ secret.issuer !== "apifuse" &&
958
+ secret.issuer !== "contributor"
959
+ ) {
960
+ throw new ValidationError(
961
+ `Provider "${config.id}" secret "${secret.name}" has invalid issuer "${String(secret.issuer)}"`,
962
+ { fix: `Set issuer to "apifuse" or "contributor", or omit it.` },
963
+ );
964
+ }
965
+ const engineName = engineOwnedResolverCredentialTarget(secret.name);
966
+ if (engineName !== undefined) {
967
+ throw new ValidationError(
968
+ `Provider "${config.id}" cannot declare engine-owned resolver credential "${secret.name}"`,
969
+ {
970
+ fix: `Remove "${secret.name}" from provider secrets; configure the solver key only on the provider engine as ${engineName}.`,
971
+ },
972
+ );
973
+ }
974
+ if (isEngineOwnedTelemetryEnvName(secret.name)) {
975
+ throw new ValidationError(
976
+ `Provider "${config.id}" cannot declare engine-owned telemetry variable "${secret.name}"`,
977
+ {
978
+ fix: `Remove "${secret.name}" from provider secrets; trace export is configured only on the provider engine.`,
979
+ },
980
+ );
981
+ }
982
+ if (isEngineOwnedRuntimeEnvName(secret.name)) {
983
+ throw new ValidationError(
984
+ `Provider "${config.id}" cannot declare engine-owned runtime variable "${secret.name}"`,
985
+ {
986
+ fix: `Remove "${secret.name}" from provider secrets; the provider engine reads it from its own environment.`,
987
+ },
988
+ );
989
+ }
990
+ if (isEngineOwnedEnvName(secret.name) && !isEngineOwnedProxyCredentialName(secret.name)) {
991
+ throw new ValidationError(
992
+ `Provider "${config.id}" cannot declare engine-owned control variable "${secret.name}"`,
993
+ {
994
+ fix: `Remove "${secret.name}" from provider secrets; configure it only on the provider engine.`,
995
+ },
996
+ );
997
+ }
998
+ if (!isEngineOwnedProxyCredentialName(secret.name)) continue;
999
+ throw new ValidationError(
1000
+ `Provider "${config.id}" cannot declare engine-owned proxy credential "${secret.name}"`,
1001
+ {
1002
+ fix: `Remove "${secret.name}" from provider secrets; configure it only on the provider engine.`,
1003
+ },
1004
+ );
1005
+ }
362
1006
  const proxy = config.proxy;
363
1007
  if (proxy === undefined || typeof proxy === "boolean") {
364
1008
  return;
@@ -371,11 +1015,24 @@ function validateProviderProxy(config: {
371
1015
  },
372
1016
  );
373
1017
  }
374
- rejectUnknownFields(proxy, new Set(["mode", "provider", "geo", "session"]), "proxy");
1018
+ rejectUnknownFields(proxy, new Set(["mode", "provider", "providers", "geo", "session"]), "proxy");
375
1019
  assertLiteralField(proxy.mode, "proxy.mode", VALID_PROVIDER_PROXY_MODES, config.id);
376
1020
  if (proxy.provider !== undefined) {
377
1021
  assertLiteralField(proxy.provider, "proxy.provider", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
378
1022
  }
1023
+ if (proxy.providers !== undefined) {
1024
+ if (!Array.isArray(proxy.providers) || proxy.providers.length === 0) {
1025
+ throw new ValidationError(
1026
+ `Provider "${config.id}" has invalid proxy.providers: must be a non-empty array of proxy vendors.`,
1027
+ {
1028
+ fix: `Use proxy.providers: ["smartproxy", "nodemaven"] to declare an ordered fallback chain.`,
1029
+ },
1030
+ );
1031
+ }
1032
+ for (const vendor of proxy.providers) {
1033
+ assertLiteralField(vendor, "proxy.providers[]", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
1034
+ }
1035
+ }
379
1036
  if (proxy.geo !== undefined) {
380
1037
  if (!proxy.geo || typeof proxy.geo !== "object" || Array.isArray(proxy.geo)) {
381
1038
  throw new ValidationError(
@@ -409,7 +1066,7 @@ function validateProviderProxy(config: {
409
1066
  }
410
1067
  rejectUnknownFields(
411
1068
  proxy.session,
412
- new Set(["affinity", "lifetimeMinutes", "poolSize"]),
1069
+ new Set(["affinity", "lifetimeMinutes", "poolSize", "drainLeadSeconds"]),
413
1070
  "proxy.session",
414
1071
  );
415
1072
  if (proxy.session.affinity !== undefined) {
@@ -432,19 +1089,62 @@ function validateProviderProxy(config: {
432
1089
  `Provider "${config.id}" has invalid proxy.session.poolSize: must be a positive integer.`,
433
1090
  );
434
1091
  }
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) {
1092
+ const drainLeadSeconds = proxy.session.drainLeadSeconds;
1093
+ if (
1094
+ drainLeadSeconds !== undefined &&
1095
+ (!Number.isFinite(drainLeadSeconds) || drainLeadSeconds <= 0)
1096
+ ) {
441
1097
  throw new ValidationError(
442
- `Provider "${config.id}" requires Smartproxy egress but does not declare ${SMARTPROXY_APP_KEY_SECRET}.`,
1098
+ `Provider "${config.id}" has invalid proxy.session.drainLeadSeconds: must be a positive number of seconds.`,
443
1099
  {
444
- fix: `Add secrets: [{ name: "${SMARTPROXY_APP_KEY_SECRET}", required: true }] to the provider.`,
1100
+ fix: `Use proxy.session.drainLeadSeconds: 120 to receive the sticky-expiry drain event 120s before hard expiry.`,
445
1101
  },
446
1102
  );
447
1103
  }
1104
+ // A drain lead longer than the sticky lifetime would fire the expiring
1105
+ // event before the session is even established, so the provider would
1106
+ // never get a usable window. Reject the contradiction at build time.
1107
+ if (
1108
+ drainLeadSeconds !== undefined &&
1109
+ lifetime !== undefined &&
1110
+ Number.isFinite(lifetime) &&
1111
+ drainLeadSeconds >= lifetime * 60
1112
+ ) {
1113
+ throw new ValidationError(
1114
+ `Provider "${config.id}" has proxy.session.drainLeadSeconds (${drainLeadSeconds}s) greater than or equal to proxy.session.lifetimeMinutes (${lifetime}m).`,
1115
+ {
1116
+ fix: `Lower drainLeadSeconds below the sticky lifetime so the drain event leaves a usable session window.`,
1117
+ },
1118
+ );
1119
+ }
1120
+ }
1121
+ const vendorChain =
1122
+ proxy.providers && proxy.providers.length > 0
1123
+ ? proxy.providers
1124
+ : proxy.provider
1125
+ ? [proxy.provider]
1126
+ : [];
1127
+ // `decodo`/`custom` are deprecated vendor values (string-union members, so the
1128
+ // @deprecated symbol gate can't catch them — warn at validation time instead).
1129
+ const deprecatedVendors = vendorChain.filter(
1130
+ (vendor) => vendor === "decodo" || vendor === "custom",
1131
+ );
1132
+ if (
1133
+ proxy.mode === "required" &&
1134
+ vendorChain.length > 0 &&
1135
+ deprecatedVendors.length === vendorChain.length
1136
+ ) {
1137
+ throw new ValidationError(
1138
+ `Provider "${config.id}" requires proxy egress but declares only deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}.`,
1139
+ {
1140
+ fix: `Use proxy.provider or proxy.providers with "smartproxy" and/or "nodemaven".`,
1141
+ },
1142
+ );
1143
+ }
1144
+ if (deprecatedVendors.length > 0) {
1145
+ console.warn(
1146
+ `[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven".`,
1147
+ );
448
1148
  }
449
1149
  }
450
1150
 
@@ -460,6 +1160,91 @@ function validateProviderStt(config: { id: string; stt?: ProviderSttConfig }): v
460
1160
  assertLiteralField(stt.mode, "stt.mode", VALID_PROVIDER_STT_MODES, config.id);
461
1161
  }
462
1162
 
1163
+ function validateProviderOcr(config: { id: string; ocr?: ProviderOcrConfig }): void {
1164
+ const ocr = config.ocr;
1165
+ if (ocr === undefined) return;
1166
+ if (!ocr || typeof ocr !== "object" || Array.isArray(ocr)) {
1167
+ throw new ValidationError(`Provider "${config.id}" has invalid ocr: must be an object.`, {
1168
+ fix: `Use ocr: { mode: "required" } or ocr: { mode: "optional" }.`,
1169
+ });
1170
+ }
1171
+ rejectUnknownFields(ocr, new Set(["mode"]), "ocr");
1172
+ assertLiteralField(ocr.mode, "ocr.mode", VALID_PROVIDER_OCR_MODES, config.id);
1173
+ }
1174
+
1175
+ function validateProviderResolver(config: { id: string; resolver?: ProviderResolverConfig }): void {
1176
+ const resolver = config.resolver;
1177
+ if (resolver === undefined) return;
1178
+ if (!resolver || typeof resolver !== "object" || Array.isArray(resolver)) {
1179
+ throw new ValidationError(`Provider "${config.id}" has invalid resolver: must be an object.`, {
1180
+ fix: `Set resolver for provider "${config.id}" to { vendors: ["2captcha"], kinds: ["turnstile"] }.`,
1181
+ });
1182
+ }
1183
+ rejectUnknownFields(
1184
+ resolver,
1185
+ new Set(["vendors", "kinds", "clientProfile"]),
1186
+ "resolver",
1187
+ config.id,
1188
+ );
1189
+ if (resolver.vendors !== undefined) {
1190
+ validateResolverLiteralArray(
1191
+ resolver.vendors,
1192
+ "resolver.vendors",
1193
+ VALID_PROVIDER_RESOLVER_VENDORS,
1194
+ config.id,
1195
+ );
1196
+ }
1197
+ validateResolverLiteralArray(
1198
+ resolver.kinds,
1199
+ "resolver.kinds",
1200
+ VALID_PROVIDER_CHALLENGE_KINDS,
1201
+ config.id,
1202
+ );
1203
+ if (
1204
+ resolver.clientProfile !== undefined &&
1205
+ (typeof resolver.clientProfile !== "string" || !resolver.clientProfile.trim())
1206
+ ) {
1207
+ throw new ValidationError(
1208
+ `Provider "${config.id}" has invalid resolver.clientProfile: must be a non-empty string.`,
1209
+ {
1210
+ fix: `Set resolver.clientProfile for provider "${config.id}" to a transport-owned profile name.`,
1211
+ },
1212
+ );
1213
+ }
1214
+ if (resolver.kinds.some(kindRequiresClientProfile) && resolver.clientProfile === undefined) {
1215
+ throw new ValidationError(
1216
+ `Provider "${config.id}" must declare resolver.clientProfile for akamai_sbsd.`,
1217
+ {
1218
+ fix: `Set resolver.clientProfile for provider "${config.id}" to the transport-owned profile used by its Akamai session.`,
1219
+ },
1220
+ );
1221
+ }
1222
+ }
1223
+
1224
+ function validateResolverLiteralArray<TValue extends string>(
1225
+ value: readonly TValue[],
1226
+ field: string,
1227
+ validValues: readonly TValue[],
1228
+ providerId: string,
1229
+ ): void {
1230
+ if (!Array.isArray(value)) {
1231
+ throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an array.`, {
1232
+ fix: `Set ${field} for provider "${providerId}" to an array containing only: ${validValues.join(", ")}.`,
1233
+ });
1234
+ }
1235
+ for (const [index, item] of value.entries()) {
1236
+ if (typeof item === "string" && validValues.some((validValue) => validValue === item)) {
1237
+ continue;
1238
+ }
1239
+ throw new ValidationError(
1240
+ `Provider "${providerId}" has invalid ${field}[${index}]: ${JSON.stringify(item)}. Expected one of: ${validValues.join(", ")}`,
1241
+ {
1242
+ fix: `Set ${field}[${index}] for provider "${providerId}" to one of ${validValues.map((validValue) => `"${validValue}"`).join(", ")}.`,
1243
+ },
1244
+ );
1245
+ }
1246
+ }
1247
+
463
1248
  function validateOperationIds(
464
1249
  providerId: string,
465
1250
  operations: Record<string, ProviderOperation>,
@@ -499,52 +1284,40 @@ function assertNonEmptyString(
499
1284
  }
500
1285
  }
501
1286
 
502
- function validateToolRouterMetadata(
1287
+ function validateOperationMetadata(
503
1288
  providerId: string,
504
1289
  operations: Record<string, ProviderOperation>,
505
1290
  ): void {
506
1291
  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) {
1292
+ assertLiteralField(
1293
+ operation.riskClass,
1294
+ `operations.${operationName}.riskClass`,
1295
+ VALID_OPERATION_RISK_CLASSES,
1296
+ providerId,
1297
+ );
1298
+ if (operation.approval !== undefined) {
526
1299
  assertLiteralField(
527
- toolRouter.riskClass,
528
- `operations.${operationName}.toolRouter.riskClass`,
529
- VALID_OPERATION_RISK_CLASSES,
1300
+ operation.approval,
1301
+ `operations.${operationName}.approval`,
1302
+ VALID_OPERATION_APPROVAL_POLICIES,
530
1303
  providerId,
531
1304
  );
532
1305
  }
533
- if (toolRouter.approval !== undefined) {
1306
+ if (operation.connectionMode !== undefined) {
534
1307
  assertLiteralField(
535
- toolRouter.approval,
536
- `operations.${operationName}.toolRouter.approval`,
537
- VALID_OPERATION_APPROVAL_POLICIES,
1308
+ operation.connectionMode,
1309
+ `operations.${operationName}.connectionMode`,
1310
+ VALID_OPERATION_CONNECTION_MODES,
538
1311
  providerId,
539
1312
  );
540
1313
  }
541
1314
  if (
542
- toolRouter.connectionExternalRefParam !== undefined &&
543
- (typeof toolRouter.connectionExternalRefParam !== "string" ||
544
- toolRouter.connectionExternalRefParam.trim().length === 0)
1315
+ operation.connectionExternalRefParam !== undefined &&
1316
+ (typeof operation.connectionExternalRefParam !== "string" ||
1317
+ operation.connectionExternalRefParam.trim().length === 0)
545
1318
  ) {
546
1319
  throw new ValidationError(
547
- `Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter.connectionExternalRefParam: must be a non-empty string.`,
1320
+ `Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.connectionExternalRefParam: must be a non-empty string.`,
548
1321
  {
549
1322
  fix: `Use "externalRef" unless the operation has a documented public alias.`,
550
1323
  },
@@ -617,16 +1390,14 @@ function validateOperationContracts(
617
1390
  }
618
1391
  }
619
1392
 
620
- function validateOperationAnnotations(
1393
+ function validateOperationTimeouts(
621
1394
  providerId: string,
622
1395
  operations: Record<string, ProviderOperation>,
623
1396
  ): void {
624
1397
  for (const [operationName, operation] of Object.entries(operations)) {
625
- const annotations = operation.annotations;
626
- if (!annotations) continue;
627
- const timeoutMs = annotations.timeoutMs;
1398
+ const timeoutMs = operation.timeoutMs;
628
1399
  if (timeoutMs === undefined) continue;
629
- const field = `operations.${operationName}.annotations.timeoutMs`;
1400
+ const field = `operations.${operationName}.timeoutMs`;
630
1401
  if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs))
631
1402
  throw new ValidationError(
632
1403
  `Provider "${providerId}" has invalid ${field}: must be an integer number of milliseconds.`,
@@ -702,6 +1473,36 @@ function validateOperationObservability(
702
1473
  }
703
1474
  }
704
1475
 
1476
+ function validateOperationErrorCodes(
1477
+ providerId: string,
1478
+ operations: Record<string, ProviderOperation>,
1479
+ ): void {
1480
+ for (const [operationName, operation] of Object.entries(operations)) {
1481
+ for (const [index, errorCode] of (operation.errorCodes ?? []).entries()) {
1482
+ if (
1483
+ errorCode.status !== undefined &&
1484
+ !VALID_OPERATION_ERROR_STATUSES.some((status) => status === errorCode.status)
1485
+ ) {
1486
+ const field = `operations.${operationName}.errorCodes[${index}].status`;
1487
+ throw new ValidationError(
1488
+ `Provider "${providerId}" has invalid ${field}: ${String(errorCode.status)} is not an emittable provider error status.`,
1489
+ {
1490
+ fix: `Set ${field} to one of ${VALID_OPERATION_ERROR_STATUSES.join(", ")}, or omit it.`,
1491
+ },
1492
+ );
1493
+ }
1494
+ if (
1495
+ errorCode.status !== undefined &&
1496
+ SDK_RUNTIME_OWNED_ERROR_CODES.has(errorCode.code)
1497
+ ) {
1498
+ console.warn(
1499
+ `[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.`,
1500
+ );
1501
+ }
1502
+ }
1503
+ }
1504
+ }
1505
+
705
1506
  const JSON_TRANSPORT_FIELDS = new Set(["kind"]);
706
1507
  const SSE_TRANSPORT_FIELDS = new Set([
707
1508
  "kind",
@@ -972,6 +1773,7 @@ const HEALTH_CHECK_CASE_FIELDS = new Set([
972
1773
  "input",
973
1774
  "prepareInput",
974
1775
  "assertions",
1776
+ "scenario",
975
1777
  "degradedThresholdMs",
976
1778
  "timeoutMs",
977
1779
  "expectedStatus",
@@ -1027,7 +1829,12 @@ function suggestField(unknown: string, candidates: ReadonlySet<string>): string
1027
1829
  return best;
1028
1830
  }
1029
1831
 
1030
- function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldPath: string): void {
1832
+ function rejectUnknownFields(
1833
+ value: object,
1834
+ allowed: ReadonlySet<string>,
1835
+ fieldPath: string,
1836
+ providerId?: string,
1837
+ ): void {
1031
1838
  for (const key of Object.keys(value)) {
1032
1839
  if (allowed.has(key)) continue;
1033
1840
  const hint = suggestField(key, allowed);
@@ -1035,7 +1842,11 @@ function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldP
1035
1842
  hint
1036
1843
  ? `Unknown field "${key}" on ${fieldPath}. Did you mean "${hint}"?`
1037
1844
  : `Unknown field "${key}" on ${fieldPath}.`,
1038
- { fix: `Remove ${fieldPath}.${key} or rename it.` },
1845
+ {
1846
+ fix: providerId
1847
+ ? `Remove ${fieldPath}.${key} from provider "${providerId}" or rename it.`
1848
+ : `Remove ${fieldPath}.${key} or rename it.`,
1849
+ },
1039
1850
  );
1040
1851
  }
1041
1852
  }
@@ -1212,7 +2023,19 @@ function validateHealthCheckCase(
1212
2023
  throw new ValidationError(
1213
2024
  `Provider "${providerId}" ${fieldPath}.name must be a non-empty string.`,
1214
2025
  );
1215
- if (typeof c.assertions !== "function")
2026
+ const hasScenario = c.scenario !== undefined;
2027
+ const imperativeFields = [
2028
+ ...(c.prepareInput === undefined ? [] : ["prepareInput"]),
2029
+ ...(c.assertions === undefined ? [] : ["assertions"]),
2030
+ ];
2031
+ if (hasScenario && imperativeFields.length > 0)
2032
+ throw new ValidationError(
2033
+ `Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" cannot declare scenario with ${imperativeFields.join(" and ")}.`,
2034
+ {
2035
+ fix: `Remove ${imperativeFields.map((field) => `${fieldPath}.${field}`).join(" and ")} and keep ${fieldPath}.scenario, or remove ${fieldPath}.scenario to keep the imperative hooks.`,
2036
+ },
2037
+ );
2038
+ if (!hasScenario && typeof c.assertions !== "function")
1216
2039
  throw new ValidationError(
1217
2040
  `Provider "${providerId}" ${fieldPath}.assertions must be a function.`,
1218
2041
  {
@@ -1223,6 +2046,24 @@ function validateHealthCheckCase(
1223
2046
  throw new ValidationError(
1224
2047
  `Provider "${providerId}" ${fieldPath}.prepareInput must be a function.`,
1225
2048
  );
2049
+ if (hasScenario) {
2050
+ const parsedScenario = HealthScenarioSchema.safeParse(c.scenario);
2051
+ if (!parsedScenario.success)
2052
+ throw new ValidationError(
2053
+ `Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" has an invalid scenario: it must conform to HealthScenario.`,
2054
+ { fix: `Build ${fieldPath}.scenario with defineHealthScenario().` },
2055
+ );
2056
+ const unrelatedOperation = parsedScenario.data.coversOperations.find(
2057
+ (operationId) => operationId !== operationName,
2058
+ );
2059
+ if (unrelatedOperation !== undefined)
2060
+ throw new ValidationError(
2061
+ `Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" scenario.coversOperations cannot claim unrelated operation "${unrelatedOperation}".`,
2062
+ {
2063
+ fix: `Set ${fieldPath}.scenario.coversOperations to ["${operationName}"].`,
2064
+ },
2065
+ );
2066
+ }
1226
2067
  if (
1227
2068
  c.degradedThresholdMs !== undefined &&
1228
2069
  (typeof c.degradedThresholdMs !== "number" ||
@@ -1372,6 +2213,7 @@ const HEALTH_JOURNEY_FIELDS = new Set([
1372
2213
  "manualTrigger",
1373
2214
  "steps",
1374
2215
  "run",
2216
+ "scenario",
1375
2217
  ]);
1376
2218
  const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set(["kind", "interval", "jitter", "randomize"]);
1377
2219
  const HEALTH_JOURNEY_STEP_FIELDS = new Set([
@@ -1907,6 +2749,14 @@ function validateHealthJourneys(
1907
2749
  }
1908
2750
  if (journey.manualTrigger !== undefined)
1909
2751
  validateHealthJourneyManualTrigger(providerId, journey.id, journey.manualTrigger);
2752
+ if (journey.scenario !== undefined && journey.smsMatchers !== undefined)
2753
+ throw new ValidationError(
2754
+ `Provider "${providerId}" healthJourneys.${journey.id}.smsMatchers is not allowed on declarative scenarios.`,
2755
+ );
2756
+ if (journey.scenario !== undefined && journey.requiredSecrets !== undefined)
2757
+ throw new ValidationError(
2758
+ `Provider "${providerId}" healthJourneys.${journey.id}.requiredSecrets is not allowed on declarative scenarios.`,
2759
+ );
1910
2760
  if (journey.timeout !== undefined)
1911
2761
  assertIsoDuration(
1912
2762
  journey.timeout,
@@ -1977,6 +2827,26 @@ function validateOperationFixtures(
1977
2827
  fix: `Add operations.${operationName}.handler as an async function with signature (ctx, input) => Promise<output>`,
1978
2828
  },
1979
2829
  );
2830
+ if (operation.fixtures?.recordedAt !== undefined) {
2831
+ const recordedAt = operation.fixtures.recordedAt;
2832
+ const parsed =
2833
+ typeof recordedAt === "string"
2834
+ ? new Date(`${recordedAt}T00:00:00.000Z`)
2835
+ : new Date(Number.NaN);
2836
+ const isCalendarDate =
2837
+ typeof recordedAt === "string" &&
2838
+ /^\d{4}-\d{2}-\d{2}$/.test(recordedAt) &&
2839
+ !Number.isNaN(parsed.getTime()) &&
2840
+ parsed.toISOString().slice(0, 10) === recordedAt;
2841
+ const kstToday = new Date(Date.now() + 9 * 60 * 60 * 1000).toISOString().slice(0, 10);
2842
+ if (!isCalendarDate || recordedAt > kstToday)
2843
+ throw new ValidationError(
2844
+ `Fixture recordedAt must be a valid, non-future KST calendar date for provider "${providerId}" operation "${operationName}"`,
2845
+ {
2846
+ fix: `Set operations.${operationName}.fixtures.recordedAt to the KST capture date in YYYY-MM-DD format; it must not be in the future.`,
2847
+ },
2848
+ );
2849
+ }
1980
2850
  if (operation.fixtures?.request !== undefined) {
1981
2851
  const result = safeParseSchemaSync(
1982
2852
  operation.input,
@@ -2010,6 +2880,31 @@ function validateOperationFixtures(
2010
2880
  }
2011
2881
  }
2012
2882
 
2883
+ function resolveOperationFixtureRequests<TOperations extends Record<string, ProviderOperation>>(
2884
+ operations: TOperations,
2885
+ ): TOperations {
2886
+ let changed = false;
2887
+ const resolvedOperations = Object.fromEntries(
2888
+ Object.entries(operations).map(([operationName, operation]) => {
2889
+ if (operation.fixtures?.request === undefined) return [operationName, operation];
2890
+ const request = resolveHealthCheckInputDateTokens(operation.fixtures.request);
2891
+ if (request === operation.fixtures.request) return [operationName, operation];
2892
+ changed = true;
2893
+ return [
2894
+ operationName,
2895
+ {
2896
+ ...operation,
2897
+ fixtures: {
2898
+ ...operation.fixtures,
2899
+ request,
2900
+ },
2901
+ },
2902
+ ];
2903
+ }),
2904
+ ) as TOperations;
2905
+ return changed ? resolvedOperations : operations;
2906
+ }
2907
+
2013
2908
  /**
2014
2909
  * Shallow shape guard only: the `deployment` object is passed through
2015
2910
  * verbatim and deliberately not deep-validated by the SDK — the APIFuse
@@ -2023,33 +2918,57 @@ function validateProviderDeployment(providerId: string, deployment: unknown): vo
2023
2918
  });
2024
2919
  }
2025
2920
 
2026
- export function defineProvider<
2921
+ /** The second authoring phase for a declaration established by defineProvider. */
2922
+ export type ProviderBuilder<TConfig extends ProviderDeclaration> = <
2027
2923
  TOperations extends Record<string, ProviderOperation>,
2028
- TConfig extends ProviderConfig<TOperations>,
2029
2924
  >(
2030
- config: TConfig & AuthStartNoInputGuard<TConfig>,
2031
- ): ProviderDefinition & { operations: OperationMapConfig<TOperations> } {
2032
- validateProviderShape(config);
2925
+ implementation: {
2926
+ operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
2927
+ },
2928
+ ) => Omit<ProviderDefinition, "operations"> & {
2929
+ operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
2930
+ };
2931
+
2932
+ /** Extract the declaration-derived operation context from a provider builder. */
2933
+ export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer TConfig>
2934
+ ? ProviderContext<TConfig>
2935
+ : never;
2936
+
2937
+ /** Annotate an operation while preserving the declaration-derived context. */
2938
+ export type OperationDefinitionFor<
2939
+ TBuilder,
2940
+ TInput extends SchemaLike = SchemaLike,
2941
+ TOutput extends SchemaLike = SchemaLike,
2942
+ > = OperationDefinition<TInput, TOutput, ProviderContextOf<TBuilder>>;
2943
+
2944
+ /** Annotate a built provider while preserving the declaration-derived context. */
2945
+ export type ProviderDefinitionFor<TBuilder> = ProviderDefinition<ProviderContextOf<TBuilder>>;
2946
+
2947
+ /** Establish a provider declaration before its operations are contextually typed. */
2948
+ export function defineProvider<const TConfig extends ProviderDeclaration>(
2949
+ declaration: TConfig &
2950
+ Record<Exclude<keyof TConfig, keyof ProviderDeclaration>, never> &
2951
+ AuthStartNoInputGuard<TConfig>,
2952
+ ): ProviderBuilder<TConfig> {
2953
+ validateProviderDeclaration(declaration);
2954
+ const buildProvider = <TOperations extends Record<string, ProviderOperation>>(
2955
+ implementation: {
2956
+ operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
2957
+ },
2958
+ ) =>
2959
+ finalizeProvider({
2960
+ ...declaration,
2961
+ ...implementation,
2962
+ } as ProviderConfig<TOperations, ProviderContext<TConfig>>);
2963
+ return buildProvider as ProviderBuilder<TConfig>;
2964
+ }
2965
+
2966
+ function validateProviderDeclaration(config: ProviderDeclaration): void {
2967
+ validateProviderDeclarationShape(config);
2033
2968
  if (!CONNECTOR_ID_REGEX.test(config.id))
2034
2969
  throw new ProviderError(`Invalid provider id: "${config.id}"`, {
2035
2970
  fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
2036
2971
  });
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
2972
  if (config.healthMonitor !== undefined && config.healthProbe !== undefined)
2054
2973
  throw new ValidationError(
2055
2974
  `Provider "${config.id}" declares both healthMonitor and healthProbe. They are aliases; declare exactly one.`,
@@ -2062,10 +2981,18 @@ export function defineProvider<
2062
2981
  config.healthProbe ?? config.healthMonitor,
2063
2982
  config.healthProbe !== undefined ? "healthProbe" : "healthMonitor",
2064
2983
  );
2065
- validateOperationFixtures(config.id, config.operations);
2066
2984
  validateProviderDeployment(config.id, config.deployment);
2985
+ try {
2986
+ validateNativeProviderConfig(config.native);
2987
+ } catch (error) {
2988
+ if (error instanceof NativeEgressPolicyValidationError)
2989
+ throw new ValidationError(error.message);
2990
+ throw error;
2991
+ }
2067
2992
  validateProviderProxy(config);
2993
+ validateProviderOcr(config);
2068
2994
  validateProviderStt(config);
2995
+ validateProviderResolver(config);
2069
2996
  if (config.runtime === "browser" && !config.browser)
2070
2997
  throw new ProviderError(
2071
2998
  `Provider "${config.id}" must define browser.engine when runtime is "browser"`,
@@ -2078,30 +3005,77 @@ export function defineProvider<
2078
3005
  `Provider "${config.id}" cannot define browser config unless runtime is "browser"`,
2079
3006
  { fix: 'Set runtime: "browser" or remove the browser config' },
2080
3007
  );
2081
- return {
3008
+ validateFailClosedProviderDeclaration(config);
3009
+ }
3010
+
3011
+ function finalizeProvider<
3012
+ TOperations extends Record<string, ProviderOperation>,
3013
+ TContext,
3014
+ >(
3015
+ config: ProviderConfig<TOperations, TContext>,
3016
+ ): Omit<ProviderDefinition, "operations"> & {
3017
+ operations: OperationMapConfig<TOperations, TContext>;
3018
+ } {
3019
+ validateProviderImplementationShape(config);
3020
+ const operations = resolveOperationFixtureRequests(config.operations);
3021
+ if (Object.keys(config.operations).length === 0)
3022
+ throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
3023
+ fix: "Add at least one operation to the operations object",
3024
+ });
3025
+ validateOperationIds(config.id, config.operations);
3026
+ validateOperationTimeouts(config.id, config.operations);
3027
+ validateOperationObservability(config.id, config.operations);
3028
+ validateOperationErrorCodes(config.id, config.operations);
3029
+ validateOperationTransports(config.id, config.operations);
3030
+ validateOperationContracts(config.id, config.operations);
3031
+ validateOperationMetadata(config.id, config.operations);
3032
+ const journeyCoveredOperations = validateHealthJourneys(
3033
+ config.id,
3034
+ config.operations,
3035
+ config.healthJourneys,
3036
+ );
3037
+ validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
3038
+ validateOperationFixtures(config.id, operations);
3039
+ const provider: Omit<ProviderDefinition, "operations"> & {
3040
+ operations: OperationMapConfig<TOperations, TContext>;
3041
+ } = {
2082
3042
  id: config.id,
2083
3043
  version: config.version,
2084
3044
  runtime: config.runtime,
3045
+ runtimeTarget: config.runtimeTarget,
2085
3046
  // Verbatim passthrough: deployment validation and profile resolution
2086
3047
  // are owned by the APIFuse registry builder, not the SDK.
2087
3048
  deployment: config.deployment,
3049
+ http: config.http,
2088
3050
  allowedHosts: config.allowedHosts,
3051
+ native: config.native,
2089
3052
  stealth: config.stealth,
2090
3053
  proxy: config.proxy,
3054
+ ocr: config.ocr,
2091
3055
  stt: config.stt,
3056
+ resolver: config.resolver,
2092
3057
  browser: config.browser,
2093
3058
  auth: config.auth,
3059
+ handle: config.handle,
2094
3060
  reviewed: config.reviewed,
2095
3061
  access: config.access,
2096
3062
  secrets: config.secrets,
3063
+ env: config.env,
2097
3064
  credential: config.credential,
2098
3065
  context: config.context,
3066
+ state: config.state,
3067
+ cache: config.cache,
3068
+ files: config.files,
2099
3069
  meta: config.meta,
2100
- operations: config.operations,
3070
+ operations,
2101
3071
  // Transitional healthMonitor → healthProbe alias: mirror whichever field
2102
3072
  // was declared onto both so old and new consumers keep working.
2103
3073
  healthMonitor: config.healthMonitor ?? config.healthProbe,
2104
3074
  healthProbe: config.healthProbe ?? config.healthMonitor,
2105
3075
  healthJourneys: config.healthJourneys,
2106
3076
  };
3077
+ // Declaration validation never invokes handlers, so their declaration-bound
3078
+ // context parameter is irrelevant to the runtime ProviderDefinition shape.
3079
+ validateFailClosedOperationDeclaration(provider as unknown as ProviderDefinition);
3080
+ return provider;
2107
3081
  }