@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.50

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 (365) hide show
  1. package/AUTHORING.md +537 -33
  2. package/CHANGELOG.md +201 -1
  3. package/README.md +83 -22
  4. package/SUBMISSION.md +2 -2
  5. package/bin/apifuse-check.ts +165 -11
  6. package/bin/apifuse-dev.ts +57 -13
  7. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  8. package/bin/apifuse-migrate-shape.ts +202 -0
  9. package/bin/apifuse-pack-check.ts +22 -2
  10. package/bin/apifuse-pack-smoke.ts +57 -2
  11. package/bin/apifuse-pack-types.ts +357 -38
  12. package/bin/apifuse-perf.ts +14 -13
  13. package/bin/apifuse-record.ts +709 -72
  14. package/bin/apifuse-submit-check.ts +2344 -324
  15. package/bin/apifuse-sync-assets.ts +117 -0
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/dist/auth-turn/index.d.ts +3 -3
  18. package/dist/auth-turn/index.js +1 -1
  19. package/dist/auth.d.ts +14 -0
  20. package/dist/auth.js +67 -0
  21. package/dist/ceremonies/index.d.ts +16 -0
  22. package/dist/ceremonies/index.js +141 -36
  23. package/dist/cli/commands.d.ts +1 -1
  24. package/dist/cli/commands.js +27 -0
  25. package/dist/cli/create.d.ts +4 -1
  26. package/dist/cli/create.js +42 -37
  27. package/dist/cli/migrate-operation-declaration.d.ts +59 -0
  28. package/dist/cli/migrate-operation-declaration.js +1178 -0
  29. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  30. package/dist/cli/migrate-operation-shape.js +113 -0
  31. package/dist/cli/migrate-provider-shape.d.ts +52 -0
  32. package/dist/cli/migrate-provider-shape.js +578 -0
  33. package/dist/cli/prompt-assets.d.ts +80 -0
  34. package/dist/cli/prompt-assets.js +743 -0
  35. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  36. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  37. package/dist/cli/templates/provider/README.md.tpl +5 -5
  38. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  39. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  40. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  41. package/dist/config/loader.d.ts +179 -16
  42. package/dist/config/loader.js +435 -127
  43. package/dist/contract-serialization.js +4 -8
  44. package/dist/contract-types.d.ts +12 -5
  45. package/dist/contract.js +24 -10
  46. package/dist/declaration-validation.d.ts +33 -0
  47. package/dist/declaration-validation.js +266 -0
  48. package/dist/define.d.ts +66 -29
  49. package/dist/define.js +778 -69
  50. package/dist/dev.d.ts +3 -0
  51. package/dist/dev.js +1 -1
  52. package/dist/engine.d.ts +78 -0
  53. package/dist/engine.js +133 -0
  54. package/dist/error-observability.d.ts +7 -0
  55. package/dist/error-observability.js +61 -0
  56. package/dist/error-resolution.d.ts +4 -0
  57. package/dist/error-resolution.js +122 -0
  58. package/dist/errors.d.ts +33 -0
  59. package/dist/errors.js +40 -0
  60. package/dist/fixture-sanitization.d.ts +28 -0
  61. package/dist/fixture-sanitization.js +227 -0
  62. package/dist/health-scenario.d.ts +1842 -0
  63. package/dist/health-scenario.js +624 -0
  64. package/dist/index.d.ts +21 -10
  65. package/dist/index.js +12 -7
  66. package/dist/lint.d.ts +23 -11
  67. package/dist/lint.js +572 -69
  68. package/dist/native-address.d.ts +43 -0
  69. package/dist/native-address.js +281 -0
  70. package/dist/native-egress-policy.d.ts +31 -0
  71. package/dist/native-egress-policy.js +288 -0
  72. package/dist/observability.d.ts +5 -2
  73. package/dist/observability.js +48 -1
  74. package/dist/provider.d.ts +10 -2
  75. package/dist/provider.js +4 -1
  76. package/dist/runtime/auth-flow.d.ts +5 -1
  77. package/dist/runtime/auth-flow.js +6 -0
  78. package/dist/runtime/browser.d.ts +1 -0
  79. package/dist/runtime/browser.js +492 -49
  80. package/dist/runtime/cache.d.ts +1 -0
  81. package/dist/runtime/cache.js +169 -15
  82. package/dist/runtime/choice-wordlist.d.ts +9 -0
  83. package/dist/runtime/choice-wordlist.js +138 -0
  84. package/dist/runtime/choice.d.ts +13 -1
  85. package/dist/runtime/choice.js +490 -102
  86. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  87. package/dist/runtime/chrome149-header-order.js +289 -0
  88. package/dist/runtime/env.js +12 -0
  89. package/dist/runtime/executor.d.ts +3 -2
  90. package/dist/runtime/executor.js +26 -25
  91. package/dist/runtime/http.d.ts +1 -0
  92. package/dist/runtime/http.js +515 -53
  93. package/dist/runtime/insights.js +2 -2
  94. package/dist/runtime/instrumentation.d.ts +2 -2
  95. package/dist/runtime/instrumentation.js +366 -8
  96. package/dist/runtime/native-network-errors.d.ts +33 -0
  97. package/dist/runtime/native-network-errors.js +69 -0
  98. package/dist/runtime/native-network.d.ts +96 -0
  99. package/dist/runtime/native-network.js +1232 -0
  100. package/dist/runtime/ocr.d.ts +29 -0
  101. package/dist/runtime/ocr.js +440 -0
  102. package/dist/runtime/otlp.d.ts +71 -2
  103. package/dist/runtime/otlp.js +397 -16
  104. package/dist/runtime/proxy-errors.js +6 -2
  105. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  106. package/dist/runtime/proxy-nodemaven.js +146 -0
  107. package/dist/runtime/proxy-telemetry.d.ts +80 -1
  108. package/dist/runtime/proxy-telemetry.js +154 -47
  109. package/dist/runtime/redirects.d.ts +29 -0
  110. package/dist/runtime/redirects.js +36 -0
  111. package/dist/runtime/redis.d.ts +1 -1
  112. package/dist/runtime/redis.js +4 -2
  113. package/dist/runtime/request-options.d.ts +68 -1
  114. package/dist/runtime/request-options.js +548 -0
  115. package/dist/runtime/resolver-config.d.ts +6 -0
  116. package/dist/runtime/resolver-config.js +6 -0
  117. package/dist/runtime/resolver-public.d.ts +1 -0
  118. package/dist/runtime/resolver-public.js +1 -0
  119. package/dist/runtime/resolver-shared.d.ts +14 -0
  120. package/dist/runtime/resolver-shared.js +12 -0
  121. package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
  122. package/dist/runtime/resolver-vendors/bindings.js +40 -0
  123. package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
  124. package/dist/runtime/resolver-vendors/browser.js +377 -0
  125. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  126. package/dist/runtime/resolver-vendors/capsolver.js +531 -0
  127. package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
  128. package/dist/runtime/resolver-vendors/hosts.js +33 -0
  129. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  130. package/dist/runtime/resolver-vendors/twocaptcha.js +408 -0
  131. package/dist/runtime/resolver-vendors/types.d.ts +94 -0
  132. package/dist/runtime/resolver-vendors/types.js +96 -0
  133. package/dist/runtime/resolver.d.ts +72 -0
  134. package/dist/runtime/resolver.js +771 -0
  135. package/dist/runtime/secrets.d.ts +27 -0
  136. package/dist/runtime/secrets.js +51 -0
  137. package/dist/runtime/state.d.ts +3 -0
  138. package/dist/runtime/state.js +277 -71
  139. package/dist/runtime/stealth-cookies.d.ts +20 -0
  140. package/dist/runtime/stealth-cookies.js +111 -0
  141. package/dist/runtime/stealth.d.ts +41 -5
  142. package/dist/runtime/stealth.js +997 -291
  143. package/dist/runtime/stt.js +1 -12
  144. package/dist/runtime/timeout.d.ts +5 -0
  145. package/dist/runtime/timeout.js +12 -0
  146. package/dist/runtime/trace-config.d.ts +12 -0
  147. package/dist/runtime/trace-config.js +62 -0
  148. package/dist/runtime/trace.d.ts +5 -0
  149. package/dist/runtime/trace.js +43 -10
  150. package/dist/serve.d.ts +1 -1
  151. package/dist/serve.js +1 -1
  152. package/dist/server/error-observability.d.ts +1 -0
  153. package/dist/server/error-observability.js +1 -0
  154. package/dist/server/index.d.ts +5 -2
  155. package/dist/server/index.js +2 -2
  156. package/dist/server/self-test-input-tokens.d.ts +2 -1
  157. package/dist/server/self-test-input-tokens.js +18 -14
  158. package/dist/server/self-test.d.ts +14 -3
  159. package/dist/server/self-test.js +129 -58
  160. package/dist/server/serve-implementation.d.ts +230 -0
  161. package/dist/server/serve-implementation.js +2265 -0
  162. package/dist/server/serve.d.ts +1 -70
  163. package/dist/server/serve.js +1 -1131
  164. package/dist/server/trace-output.d.ts +6 -0
  165. package/dist/server/trace-output.js +79 -0
  166. package/dist/server/types.d.ts +30 -5
  167. package/dist/server/types.js +13 -1
  168. package/dist/stateful/errors.d.ts +19 -0
  169. package/dist/stateful/errors.js +24 -0
  170. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  171. package/dist/stateful/http-provider-event-emitter.js +237 -0
  172. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  173. package/dist/stateful/http-session-owner-registry.js +210 -0
  174. package/dist/stateful/index.d.ts +18 -0
  175. package/dist/stateful/index.js +18 -0
  176. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  177. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  178. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  179. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  180. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  181. package/dist/stateful/provider-event-pipeline.js +1 -0
  182. package/dist/stateful/provider-events.d.ts +101 -0
  183. package/dist/stateful/provider-events.js +289 -0
  184. package/dist/stateful/session-key.d.ts +15 -0
  185. package/dist/stateful/session-key.js +86 -0
  186. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  187. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  188. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  189. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  190. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  191. package/dist/stateful/stateful-provider-adapter.js +287 -0
  192. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  193. package/dist/stateful/stateful-provider-observability.js +161 -0
  194. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  195. package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
  196. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  197. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  198. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  199. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  200. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  201. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  202. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  203. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  204. package/dist/stateful-signing.d.ts +18 -0
  205. package/dist/stateful-signing.js +27 -0
  206. package/dist/stealth/profiles.d.ts +9 -4
  207. package/dist/stealth/profiles.js +160 -211
  208. package/dist/stream-evidence.d.ts +74 -0
  209. package/dist/stream-evidence.js +785 -0
  210. package/dist/stream.js +7 -1
  211. package/dist/testing/index.d.ts +2 -1
  212. package/dist/testing/index.js +2 -1
  213. package/dist/testing/run.d.ts +32 -2
  214. package/dist/testing/run.js +489 -21
  215. package/dist/trace-sanitization.d.ts +5 -0
  216. package/dist/trace-sanitization.js +45 -0
  217. package/dist/types.d.ts +673 -131
  218. package/dist/types.js +1 -0
  219. package/package.json +45 -5
  220. package/src/auth-turn/index.ts +1 -1
  221. package/src/auth.ts +118 -0
  222. package/src/ceremonies/index.ts +189 -46
  223. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
  224. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
  225. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
  226. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
  227. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
  244. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
  245. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
  246. package/src/cli/commands.ts +33 -0
  247. package/src/cli/create.ts +50 -37
  248. package/src/cli/migrate-operation-declaration.ts +1654 -0
  249. package/src/cli/migrate-operation-shape.ts +184 -0
  250. package/src/cli/migrate-provider-shape.ts +772 -0
  251. package/src/cli/prompt-assets.ts +865 -0
  252. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  253. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  254. package/src/cli/templates/provider/README.md.tpl +5 -5
  255. package/src/cli/templates/provider/index.ts.tpl +6 -3
  256. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  257. package/src/cli/templates/provider/provider.json.tpl +6 -0
  258. package/src/config/loader.ts +690 -163
  259. package/src/contract-serialization.ts +5 -7
  260. package/src/contract-types.ts +12 -5
  261. package/src/contract.ts +24 -10
  262. package/src/declaration-validation.ts +330 -0
  263. package/src/define.ts +1048 -131
  264. package/src/dev.ts +4 -1
  265. package/src/engine.ts +279 -0
  266. package/src/error-observability.ts +64 -0
  267. package/src/error-resolution.ts +127 -0
  268. package/src/errors.ts +68 -0
  269. package/src/fixture-sanitization.ts +264 -0
  270. package/src/health-scenario.ts +875 -0
  271. package/src/index.ts +220 -13
  272. package/src/lint.ts +698 -90
  273. package/src/native-address.ts +340 -0
  274. package/src/native-egress-policy.ts +358 -0
  275. package/src/observability.ts +51 -1
  276. package/src/provider.ts +161 -3
  277. package/src/runtime/auth-flow.ts +12 -0
  278. package/src/runtime/browser.ts +661 -63
  279. package/src/runtime/cache.ts +189 -14
  280. package/src/runtime/choice-wordlist.ts +145 -0
  281. package/src/runtime/choice.ts +631 -120
  282. package/src/runtime/chrome149-header-order.ts +330 -0
  283. package/src/runtime/env.ts +13 -0
  284. package/src/runtime/executor.ts +43 -31
  285. package/src/runtime/http.ts +641 -61
  286. package/src/runtime/insights.ts +2 -2
  287. package/src/runtime/instrumentation.ts +520 -15
  288. package/src/runtime/native-network-errors.ts +99 -0
  289. package/src/runtime/native-network.ts +1605 -0
  290. package/src/runtime/ocr.ts +523 -0
  291. package/src/runtime/otlp.ts +467 -21
  292. package/src/runtime/proxy-errors.ts +12 -4
  293. package/src/runtime/proxy-nodemaven.ts +221 -0
  294. package/src/runtime/proxy-telemetry.ts +244 -75
  295. package/src/runtime/redirects.ts +66 -0
  296. package/src/runtime/redis.ts +7 -2
  297. package/src/runtime/request-options.ts +680 -1
  298. package/src/runtime/resolver-config.ts +6 -0
  299. package/src/runtime/resolver-public.ts +23 -0
  300. package/src/runtime/resolver-shared.ts +33 -0
  301. package/src/runtime/resolver-vendors/bindings.ts +56 -0
  302. package/src/runtime/resolver-vendors/browser.ts +533 -0
  303. package/src/runtime/resolver-vendors/capsolver.ts +707 -0
  304. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  305. package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
  306. package/src/runtime/resolver-vendors/types.ts +212 -0
  307. package/src/runtime/resolver.ts +1157 -0
  308. package/src/runtime/secrets.ts +64 -0
  309. package/src/runtime/state.ts +394 -77
  310. package/src/runtime/stealth-cookies.ts +132 -0
  311. package/src/runtime/stealth.ts +1280 -336
  312. package/src/runtime/stt.ts +1 -19
  313. package/src/runtime/timeout.ts +18 -0
  314. package/src/runtime/trace-config.ts +78 -0
  315. package/src/runtime/trace.ts +57 -17
  316. package/src/serve.ts +6 -1
  317. package/src/server/error-observability.ts +1 -0
  318. package/src/server/index.ts +34 -2
  319. package/src/server/self-test-input-tokens.ts +29 -14
  320. package/src/server/self-test.ts +191 -68
  321. package/src/server/serve-implementation.ts +3378 -0
  322. package/src/server/serve.ts +1 -1632
  323. package/src/server/trace-output.ts +129 -0
  324. package/src/server/types.ts +13 -1
  325. package/src/stateful/README.md +146 -0
  326. package/src/stateful/errors.ts +35 -0
  327. package/src/stateful/http-provider-event-emitter.ts +314 -0
  328. package/src/stateful/http-session-owner-registry.ts +306 -0
  329. package/src/stateful/index.ts +18 -0
  330. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  331. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  332. package/src/stateful/provider-event-pipeline.ts +61 -0
  333. package/src/stateful/provider-events.ts +462 -0
  334. package/src/stateful/session-key.ts +111 -0
  335. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  336. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  337. package/src/stateful/stateful-provider-adapter.ts +562 -0
  338. package/src/stateful/stateful-provider-observability.ts +261 -0
  339. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  340. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  341. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  342. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  343. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  344. package/src/stateful-signing.ts +46 -0
  345. package/src/stealth/profiles.ts +202 -231
  346. package/src/stream-evidence.ts +988 -0
  347. package/src/stream.ts +8 -1
  348. package/src/testing/index.ts +10 -1
  349. package/src/testing/run.ts +658 -15
  350. package/src/trace-sanitization.ts +63 -0
  351. package/src/types.ts +788 -174
  352. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  353. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  354. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  355. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  356. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  357. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  358. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  359. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  360. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  361. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  362. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  363. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  364. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  365. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -1,1632 +1 @@
1
- import { existsSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- import { Hono } from "hono";
5
- import { z } from "zod";
6
- import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
7
- import {
8
- AuthError,
9
- isProviderError,
10
- isSessionExpiredError,
11
- isTransportError,
12
- ProviderError,
13
- } from "../errors.js";
14
- import {
15
- loadProviderLocaleCatalogs,
16
- localizeAuthTurn,
17
- type ProviderLocaleCatalogMap,
18
- } from "../i18n/catalog.js";
19
- import type { ProviderLocale } from "../i18n/keys.js";
20
- import {
21
- categoryForStatus,
22
- isRetryableCategory,
23
- PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
24
- type ProviderErrorCategory,
25
- } from "../observability.js";
26
- import { createScratchpad } from "../runtime/auth-flow.js";
27
- import { createBrowserClient } from "../runtime/browser.js";
28
- import { createProviderCache } from "../runtime/cache.js";
29
- import {
30
- createProviderChoiceContext,
31
- PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
32
- } from "../runtime/choice.js";
33
- import { createCredentialContext } from "../runtime/credential.js";
34
- import { createEnvContext } from "../runtime/env.js";
35
- import { executeOperation } from "../runtime/executor.js";
36
- import { createHttpClient } from "../runtime/http.js";
37
- import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
38
- import { getProviderBaseUrl } from "../runtime/provider.js";
39
- import {
40
- PROXY_AUTH_IP_DENIED_CODE,
41
- PROXY_EDGE_AUTH_REJECTED_CODE,
42
- PROXY_POOL_EXHAUSTED_CODE,
43
- } from "../runtime/proxy-errors.js";
44
- import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector } from "../runtime/proxy-telemetry.js";
45
- import {
46
- createProviderRuntimeStateFromEnv,
47
- createUnsupportedProviderRuntimeState,
48
- } from "../runtime/state.js";
49
- import { createStealthClient } from "../runtime/stealth.js";
50
- import { createSttClientFromEnv } from "../runtime/stt.js";
51
- import { createTraceContext } from "../runtime/trace.js";
52
- import { parseSchema } from "../schema.js";
53
- import { getStealthProfile } from "../stealth/profiles.js";
54
- import {
55
- APIFUSE_STREAM_DONE_EVENT,
56
- APIFUSE_STREAM_ERROR_EVENT,
57
- encodeSseEvent,
58
- error as streamError,
59
- } from "../stream.js";
60
- import type {
61
- AuthContext,
62
- AuthTurn,
63
- BrowserClient,
64
- FlowContext,
65
- FlowContextStore,
66
- HttpRetrySummary,
67
- OperationDefinition,
68
- OperationHttpStreamTransport,
69
- OperationSseTransport,
70
- ProviderContext,
71
- ProviderDefinition,
72
- ProviderRuntimeState,
73
- ProviderStreamEvent,
74
- StealthClient,
75
- SttContext,
76
- } from "../types.js";
77
- import {
78
- createSelfTestApp,
79
- createSelfTestAuthFlowInvoke,
80
- createSelfTestInvoke,
81
- resolveSelfTestPort,
82
- } from "./self-test.js";
83
- import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
84
- import {
85
- type AuthFlowRequest,
86
- AuthFlowRequestSchema,
87
- type AuthFlowResponse,
88
- type AuthFlowSuccessResponse,
89
- type OperationErrorResponse,
90
- type OperationRequest,
91
- OperationRequestSchema,
92
- type OperationResponse,
93
- type OperationSuccessResponse,
94
- } from "./types.js";
95
-
96
- const DEFAULT_HOST = "0.0.0.0";
97
- const DEFAULT_PORT = 3000;
98
- const AUTH_FLOW_LOCALES = ["en", "ko", "ja"] as const;
99
- const retryResponseMeta = new WeakMap<ProviderContext, HttpRetrySummary>();
100
-
101
- type RequestCleanup = () => void | Promise<void>;
102
-
103
- function createAuthStub(): AuthContext {
104
- return {
105
- async requestField(name) {
106
- throw new ProviderError(`Auth prompt is unavailable for ${name}`, {
107
- code: "AUTH_PROMPT_UNAVAILABLE",
108
- });
109
- },
110
- };
111
- }
112
-
113
- function createBrowserStub(): BrowserClient {
114
- return {
115
- engine: "playwright-stealth",
116
- async close() {},
117
- async newPage() {
118
- throw new ProviderError("Browser runtime is not available", {
119
- code: "BROWSER_RUNTIME_UNSUPPORTED",
120
- });
121
- },
122
- async rawPage() {
123
- throw new ProviderError("Browser runtime is not available", {
124
- code: "BROWSER_RUNTIME_UNSUPPORTED",
125
- });
126
- },
127
- async withIsolatedContext() {
128
- throw new ProviderError("Browser runtime is not available", {
129
- code: "BROWSER_RUNTIME_UNSUPPORTED",
130
- });
131
- },
132
- async solveChallenge() {
133
- throw new ProviderError("Browser runtime is not available", {
134
- code: "BROWSER_RUNTIME_UNSUPPORTED",
135
- });
136
- },
137
- };
138
- }
139
-
140
- function createStealthStub(): StealthClient {
141
- return {
142
- async fetch() {
143
- throw new ProviderError("Stealth runtime is not available", {
144
- code: "STEALTH_RUNTIME_UNSUPPORTED",
145
- });
146
- },
147
- createSession() {
148
- throw new ProviderError("Stealth runtime is not available", {
149
- code: "STEALTH_RUNTIME_UNSUPPORTED",
150
- });
151
- },
152
- close() {
153
- // no-op
154
- },
155
- };
156
- }
157
-
158
- function getProviderStealthBaseUrl(provider: ProviderDefinition): string | undefined {
159
- const baseUrl = getProviderBaseUrl(provider);
160
- if (baseUrl) {
161
- return baseUrl;
162
- }
163
- const firstHost = provider.allowedHosts?.[0];
164
- return firstHost ? `https://${firstHost}` : undefined;
165
- }
166
-
167
- function getProviderStealthProfile(provider: ProviderDefinition) {
168
- return provider.stealth?.profile ? getStealthProfile(provider.stealth.profile) : undefined;
169
- }
170
-
171
- function isProductionProviderBrowserMode(provider: ProviderDefinition, env = process.env): boolean {
172
- if (provider.runtime !== "browser") {
173
- return false;
174
- }
175
-
176
- if (env.APIFUSE__PROVIDER__RUNTIME === "browser") {
177
- return true;
178
- }
179
-
180
- return env.NODE_ENV === "production" && env.APIFUSE__PROVIDER__ID === provider.id;
181
- }
182
-
183
- export function resolveProviderProxyAffinityKey(
184
- provider: ProviderDefinition,
185
- request: OperationRequest,
186
- operationId: string,
187
- ): string {
188
- const connectionKey = resolveOperationConnectionId(request) ?? request.connection?.externalRef;
189
- const affinity =
190
- typeof provider.proxy === "object" ? provider.proxy.session?.affinity : undefined;
191
- if (affinity === "operation") {
192
- return `${provider.id}/${operationId}`;
193
- }
194
- return connectionKey ?? provider.id;
195
- }
196
-
197
- function resolveOperationConnectionId(request: OperationRequest): string | undefined {
198
- return request.connection?.id ?? request.connectionId;
199
- }
200
-
201
- function createProviderContext(
202
- provider: ProviderDefinition,
203
- request: OperationRequest,
204
- operationId: string,
205
- options: ProviderServerOptions = {},
206
- state: ProviderRuntimeState = createUnsupportedProviderRuntimeState(),
207
- proxyTelemetry?: ProxyTelemetryCollector,
208
- ): ProviderContext {
209
- const baseUrl = getProviderBaseUrl(provider);
210
- const stealthBaseUrl = getProviderStealthBaseUrl(provider);
211
- const stealthProfile = getProviderStealthProfile(provider);
212
- const proxyClientOptions = {
213
- upstream: { proxy: provider.proxy },
214
- affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
215
- telemetry: proxyTelemetry,
216
- };
217
- let wrappedContext: ProviderContext | undefined;
218
- const stealthClientOptions = {
219
- upstream: proxyClientOptions.upstream,
220
- affinityKey: proxyClientOptions.affinityKey,
221
- telemetry: proxyTelemetry,
222
- };
223
-
224
- const env = createEnvContext([
225
- ...(provider.secrets?.map((secret) => secret.name) ?? []),
226
- PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
227
- ]);
228
- const credential = createCredentialContext({
229
- allowedKeys: provider.credential?.keys,
230
- mode: request.connection?.mode,
231
- scopes: request.connection?.scopes,
232
- values: request.connection?.secrets,
233
- });
234
- const requestContext = {
235
- connectionId: resolveOperationConnectionId(request),
236
- headers: request.headers ?? {},
237
- };
238
- const context = wrapWithInstrumentation({
239
- env,
240
- credential,
241
- request: requestContext,
242
- http: createHttpClient(baseUrl, {
243
- ...proxyClientOptions,
244
- onRetrySummary: (summary) => {
245
- if (summary.attempts <= 1 || !wrappedContext) return;
246
- retryResponseMeta.set(wrappedContext, summary);
247
- },
248
- }),
249
- cache: createProviderCache({ providerId: provider.id }),
250
- state,
251
- stealth: stealthBaseUrl
252
- ? stealthProfile
253
- ? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
254
- : createStealthClient(stealthBaseUrl, stealthClientOptions)
255
- : createStealthStub(),
256
- browser:
257
- provider.runtime === "browser"
258
- ? createBrowserClient({
259
- allowedHosts: provider.allowedHosts,
260
- cdpUrl: process.env.APIFUSE__CDP_POOL__URL,
261
- headless: true,
262
- requireCdpPool: isProductionProviderBrowserMode(provider),
263
- stealth: true,
264
- engine: provider.browser?.engine,
265
- })
266
- : createBrowserStub(),
267
- trace: createTraceContext(),
268
- auth: createAuthStub(),
269
- stt: options.stt ?? createSttClientFromEnv(provider.stt),
270
- choice: createProviderChoiceContext({
271
- providerId: provider.id,
272
- env,
273
- request: requestContext,
274
- credential,
275
- state,
276
- }),
277
- });
278
- wrappedContext = context;
279
- return context;
280
- }
281
-
282
- function createFlowContextStore(
283
- allowedKeys: string[],
284
- initialContext: Record<string, unknown> = {},
285
- ): {
286
- context: FlowContextStore;
287
- getPatch: () => Record<string, unknown | null> | undefined;
288
- } {
289
- const context = createScratchpad(allowedKeys, initialContext);
290
-
291
- return {
292
- context,
293
- getPatch() {
294
- const next = context.toJSON();
295
- const patch = new Map<string, unknown | null>();
296
-
297
- for (const [key, value] of Object.entries(next)) {
298
- if (initialContext[key] !== value) {
299
- patch.set(key, value);
300
- }
301
- }
302
-
303
- for (const key of Object.keys(initialContext)) {
304
- if (!(key in next)) {
305
- patch.set(key, null);
306
- }
307
- }
308
-
309
- if (patch.size === 0) {
310
- return undefined;
311
- }
312
-
313
- return Object.fromEntries(patch.entries());
314
- },
315
- };
316
- }
317
-
318
- function createAuthFlowContext(
319
- provider: ProviderDefinition,
320
- request: AuthFlowRequest,
321
- options: ProviderServerOptions = {},
322
- signal?: AbortSignal,
323
- ): {
324
- context: FlowContext;
325
- getPatch: () => Record<string, unknown | null> | undefined;
326
- } {
327
- const baseUrl = getProviderBaseUrl(provider);
328
- const stealthBaseUrl = getProviderStealthBaseUrl(provider);
329
- const stealthProfile = getProviderStealthProfile(provider);
330
- const contextData = request.context ?? {};
331
- const flowContextStore = createFlowContextStore(
332
- provider.context?.keys ?? Object.keys(contextData),
333
- contextData,
334
- );
335
- const proxyClientOptions = {
336
- upstream: { proxy: provider.proxy },
337
- affinityKey:
338
- request.connectionId ??
339
- request.externalRef ??
340
- request.tenantId ??
341
- request.providerId ??
342
- provider.id,
343
- };
344
- const stealthClientOptions = {
345
- upstream: proxyClientOptions.upstream,
346
- affinityKey: proxyClientOptions.affinityKey,
347
- };
348
- const credential = request.connection
349
- ? createCredentialContext({
350
- allowedKeys: provider.credential?.keys,
351
- mode: request.connection.mode,
352
- scopes: request.connection.scopes,
353
- values: request.connection.secrets,
354
- })
355
- : undefined;
356
-
357
- return {
358
- context: {
359
- connectionId: request.connectionId,
360
- externalRef: request.externalRef,
361
- tenantId: request.tenantId ?? "",
362
- providerId: request.providerId ?? provider.id,
363
- http: createHttpClient(baseUrl, proxyClientOptions),
364
- stealth: stealthBaseUrl
365
- ? stealthProfile
366
- ? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
367
- : createStealthClient(stealthBaseUrl, stealthClientOptions)
368
- : createStealthStub(),
369
- env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
370
- credential,
371
- context: flowContextStore.context,
372
- stt: options.stt ?? createSttClientFromEnv(provider.stt),
373
- auth: createAuthFlowHelpers({ signal }),
374
- },
375
- getPatch: flowContextStore.getPatch,
376
- };
377
- }
378
-
379
- type ProviderRequestCost = {
380
- durationMs: number;
381
- cpuUserMicros: number;
382
- cpuSystemMicros: number;
383
- cpuTotalMicros: number;
384
- };
385
-
386
- type ProviderServerLogEventBase = ProviderRequestCost & {
387
- providerId: string;
388
- kind: "operation" | "auth";
389
- route: string;
390
- requestId?: string;
391
- status: number;
392
- };
393
-
394
- export type ProviderServerLogEvent =
395
- | (ProviderServerLogEventBase & {
396
- level: "info";
397
- event: "provider_request_completed";
398
- })
399
- | (ProviderServerLogEventBase & {
400
- level: "warn" | "error";
401
- event: "provider_request_failed";
402
- code: string;
403
- errorClass: string;
404
- message: string;
405
- upstreamStatus?: number;
406
- errorCategory?: ProviderErrorCategory;
407
- taxonomyVersion?: string;
408
- retryable?: boolean;
409
- issues?: Array<{ path: string; code: string; message: string }>;
410
- })
411
- | {
412
- level: "warn";
413
- event: "provider_cleanup_failed";
414
- providerId: string;
415
- kind: "operation";
416
- route: string;
417
- requestId?: string;
418
- resource: "browser" | "stealth";
419
- errorClass: string;
420
- message: string;
421
- };
422
-
423
- export type ProviderServerLogger = (event: ProviderServerLogEvent) => void;
424
-
425
- export type ProviderServerOptions = {
426
- logger?: ProviderServerLogger;
427
- /** Optional STT override for tests or custom hosts; local/prod normally resolves from env. */
428
- stt?: SttContext;
429
- /** Optional runtime state override for tests or custom hosts. Production resolves Redis from env and fails closed when unavailable. */
430
- state?: ProviderRuntimeState;
431
- /** Allow process-local runtime state only for local development and tests. */
432
- allowMemoryStateFallback?: boolean;
433
- };
434
-
435
- const defaultProviderServerLogger: ProviderServerLogger = (event) => {
436
- const line = JSON.stringify(event);
437
- if (event.level === "info") {
438
- console.log(line);
439
- return;
440
- }
441
- console.error(line);
442
- };
443
-
444
- function startRequestCost(): {
445
- startedAtMs: number;
446
- cpuStart: NodeJS.CpuUsage;
447
- } {
448
- return {
449
- startedAtMs: performance.now(),
450
- cpuStart: process.cpuUsage(),
451
- };
452
- }
453
-
454
- function finishRequestCost(input: {
455
- startedAtMs: number;
456
- cpuStart: NodeJS.CpuUsage;
457
- }): ProviderRequestCost {
458
- const cpuDelta = process.cpuUsage(input.cpuStart);
459
- return {
460
- durationMs: Math.max(0, Math.round(performance.now() - input.startedAtMs)),
461
- cpuUserMicros: Math.max(0, cpuDelta.user),
462
- cpuSystemMicros: Math.max(0, cpuDelta.system),
463
- cpuTotalMicros: Math.max(0, cpuDelta.user + cpuDelta.system),
464
- };
465
- }
466
-
467
- function zodDetails(error: z.ZodError): Array<{
468
- path: string;
469
- code: string;
470
- message: string;
471
- }> {
472
- return error.issues.map((issue) => ({
473
- path: issue.path.join("."),
474
- code: issue.code,
475
- message: issue.message,
476
- }));
477
- }
478
-
479
- function toErrorResponse(error: unknown, requestId?: string): OperationErrorResponse {
480
- if (isProviderError(error)) {
481
- const details = publicProviderErrorDetails(error);
482
- return {
483
- error: {
484
- code: error.code ?? "provider_error",
485
- message: publicProviderErrorMessage(error),
486
- ...(requestId ? { requestId } : {}),
487
- ...(error.fix ? { fix: error.fix } : {}),
488
- ...(details ? { details } : {}),
489
- },
490
- };
491
- }
492
-
493
- if (error instanceof z.ZodError) {
494
- return {
495
- error: {
496
- code: "invalid_request",
497
- message: "Invalid request body",
498
- ...(requestId ? { requestId } : {}),
499
- details: zodDetails(error),
500
- },
501
- };
502
- }
503
-
504
- return {
505
- error: {
506
- code: "internal_error",
507
- message: "Internal error",
508
- ...(requestId ? { requestId } : {}),
509
- },
510
- };
511
- }
512
-
513
- function publicProviderErrorDetails(error: ProviderError): unknown {
514
- const providerDetails = error.details;
515
- const observabilityDetails = providerObservabilityDetails(error);
516
-
517
- if (providerDetails === undefined) {
518
- return observabilityDetails;
519
- }
520
- if (observabilityDetails === undefined) {
521
- return providerDetails;
522
- }
523
- if (isPlainRecord(providerDetails) && isPlainRecord(observabilityDetails)) {
524
- return { ...providerDetails, ...observabilityDetails };
525
- }
526
- return {
527
- provider: providerDetails,
528
- observability: observabilityDetails,
529
- };
530
- }
531
-
532
- function isPlainRecord(value: unknown): value is Record<string, unknown> {
533
- return value !== null && typeof value === "object" && !Array.isArray(value);
534
- }
535
-
536
- // Accepts `unknown` so the branded guards narrow cleanly from the top: the
537
- // subtype error classes are structurally compatible with ProviderError, so
538
- // narrowing from a ProviderError-typed value would collapse the negative branch
539
- // to `never`. Narrowing from unknown avoids that while still recognizing errors
540
- // from a duplicate SDK module instance.
541
- function providerObservabilityDetails(error: unknown):
542
- | {
543
- category: ProviderErrorCategory;
544
- taxonomyVersion: string;
545
- retryable: boolean;
546
- upstreamStatus?: number;
547
- }
548
- | undefined {
549
- // Session-expiry surfaces the credential_expired category + the opt-in
550
- // retryable signal so Gateway/Credential Service can refresh and re-drive the
551
- // operation (see design.md §4.3 D3). Without this branch the auth error would
552
- // serialize as a bare 401 with no retryable/category, losing the refresh
553
- // signal for exactly the retryOnAuthRefresh operations it is meant to enable.
554
- if (isSessionExpiredError(error)) {
555
- return {
556
- category: error.options?.category ?? "credential_expired",
557
- taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
558
- retryable: error.options?.retryable ?? false,
559
- };
560
- }
561
- if (!isTransportError(error)) {
562
- return undefined;
563
- }
564
- const isProxyPoolCode =
565
- error.code === PROXY_POOL_EXHAUSTED_CODE ||
566
- error.code === PROXY_EDGE_AUTH_REJECTED_CODE ||
567
- error.code === "PROXY_ALLOCATION_FAILED";
568
- const category =
569
- error.options?.category ??
570
- (isProxyPoolCode
571
- ? "proxy_pool"
572
- : error.code === PROXY_AUTH_IP_DENIED_CODE
573
- ? "anti_bot_blocked"
574
- : error.code === "transport_timeout"
575
- ? "timeout"
576
- : error.code === "transport_network_error"
577
- ? "network"
578
- : error.upstreamStatus
579
- ? categoryForStatus(error.upstreamStatus)
580
- : "upstream_http");
581
- return {
582
- category,
583
- taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
584
- retryable:
585
- error.options?.retryable ??
586
- (category === "upstream_http" && error.upstreamStatus
587
- ? error.upstreamStatus >= 500
588
- : isRetryableCategory(category)),
589
- ...(error.upstreamStatus ? { upstreamStatus: error.upstreamStatus } : {}),
590
- };
591
- }
592
-
593
- function publicProviderErrorMessage(error: ProviderError): string {
594
- if (isTransportError(error)) {
595
- if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
596
- return error.message;
597
- }
598
- if (error.code === PROXY_EDGE_AUTH_REJECTED_CODE) {
599
- return error.message;
600
- }
601
- if (error.code === PROXY_POOL_EXHAUSTED_CODE) {
602
- return error.message;
603
- }
604
- if (error.code === "transport_timeout") return "Request timed out";
605
- if (error.code === "transport_network_error") return "Network error";
606
- if (error.code === "upstream_http_error" && error.status) {
607
- return `Upstream request failed with status ${error.status}`;
608
- }
609
- if (error.status) {
610
- return `Upstream request failed with status ${error.status}`;
611
- }
612
- return "Upstream request failed";
613
- }
614
- return error.message;
615
- }
616
-
617
- function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 | 504 {
618
- if (error instanceof z.ZodError) {
619
- return 400;
620
- }
621
-
622
- if (isTransportError(error)) {
623
- return error.code === "transport_timeout" ? 504 : 502;
624
- }
625
-
626
- if (isProviderError(error)) {
627
- switch (error.code) {
628
- case "AUTH_REQUIRED":
629
- case "reauth_required":
630
- return 401;
631
- case "NOT_FOUND":
632
- case "not_found":
633
- case "NO_DATA":
634
- return 404;
635
- case "RATE_LIMITED":
636
- case "UPSTREAM_RATE_LIMIT":
637
- case "LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR":
638
- return 429;
639
- case "UPSTREAM_ERROR":
640
- case "BLOCKED":
641
- return 502;
642
- case "STT_UNAVAILABLE":
643
- case "UNSUPPORTED_STT_BACKEND":
644
- return 503;
645
- }
646
-
647
- return 400;
648
- }
649
-
650
- return 500;
651
- }
652
-
653
- function extractRequestId(raw: unknown): string | undefined {
654
- if (!raw || typeof raw !== "object") {
655
- return undefined;
656
- }
657
-
658
- const value = Object.getOwnPropertyDescriptor(raw, "requestId")?.value;
659
- return typeof value === "string" ? value : undefined;
660
- }
661
-
662
- function logProviderError(
663
- logger: ProviderServerLogger | unknown,
664
- provider: ProviderDefinition,
665
- kind: "operation" | "auth",
666
- route: string,
667
- requestId: string | undefined,
668
- error: unknown,
669
- status: number,
670
- cost: ProviderRequestCost,
671
- ): void {
672
- const code = isProviderError(error)
673
- ? (error.code ?? "provider_error")
674
- : error instanceof z.ZodError
675
- ? "invalid_request"
676
- : "internal_error";
677
- const errorClass = error instanceof Error ? error.name : typeof error;
678
- const message = error instanceof Error ? error.message : String(error);
679
- const details = isProviderError(error) ? providerObservabilityDetails(error) : undefined;
680
- const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
681
- emit({
682
- level: status >= 500 ? "error" : "warn",
683
- event: "provider_request_failed",
684
- providerId: provider.id,
685
- kind,
686
- route,
687
- ...(requestId ? { requestId } : {}),
688
- status,
689
- ...cost,
690
- code,
691
- errorClass,
692
- message,
693
- ...(isTransportError(error) && error.upstreamStatus
694
- ? { upstreamStatus: error.upstreamStatus }
695
- : {}),
696
- ...(details
697
- ? {
698
- errorCategory: details.category,
699
- taxonomyVersion: details.taxonomyVersion,
700
- retryable: details.retryable,
701
- }
702
- : {}),
703
- ...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
704
- });
705
- }
706
-
707
- function logProviderCleanupError(
708
- logger: ProviderServerLogger | unknown,
709
- provider: ProviderDefinition,
710
- operationId: string,
711
- requestId: string | undefined,
712
- resource: "browser" | "stealth",
713
- error: unknown,
714
- ): void {
715
- const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
716
- const errorClass = error instanceof Error ? error.name : typeof error;
717
- const message = error instanceof Error ? error.message : String(error);
718
- emit({
719
- level: "warn",
720
- event: "provider_cleanup_failed",
721
- providerId: provider.id,
722
- kind: "operation",
723
- route: operationId,
724
- ...(requestId ? { requestId } : {}),
725
- resource,
726
- errorClass,
727
- message,
728
- });
729
- }
730
-
731
- function logProviderSuccess(
732
- logger: ProviderServerLogger | unknown,
733
- provider: ProviderDefinition,
734
- kind: "operation" | "auth",
735
- route: string,
736
- requestId: string | undefined,
737
- status: number,
738
- cost: ProviderRequestCost,
739
- ): void {
740
- const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
741
- emit({
742
- level: "info",
743
- event: "provider_request_completed",
744
- providerId: provider.id,
745
- kind,
746
- route,
747
- ...(requestId ? { requestId } : {}),
748
- status,
749
- ...cost,
750
- });
751
- }
752
-
753
- function toJsonSuccessResponse(
754
- result: unknown,
755
- ctx?: ProviderContext,
756
- ): Response | OperationSuccessResponse {
757
- if (result instanceof Response) {
758
- return result;
759
- }
760
-
761
- if (result instanceof ReadableStream) {
762
- return new Response(result);
763
- }
764
-
765
- const cacheMeta = ctx?.cache.responseMeta();
766
- const retryMeta = ctx ? retryResponseMeta.get(ctx) : undefined;
767
- const meta =
768
- cacheMeta || retryMeta
769
- ? {
770
- ...(cacheMeta
771
- ? {
772
- cached: cacheMeta.hit,
773
- stale: cacheMeta.stale,
774
- cache: cacheMeta,
775
- }
776
- : {}),
777
- ...(retryMeta ? { retry: retryMeta } : {}),
778
- }
779
- : undefined;
780
- return {
781
- data: result,
782
- ...(meta ? { meta } : {}),
783
- };
784
- }
785
-
786
- function isAsyncIterable<T = unknown>(value: unknown): value is AsyncIterable<T> {
787
- if (!value || typeof value !== "object") return false;
788
- const iterator = Reflect.get(value, Symbol.asyncIterator);
789
- return typeof iterator === "function";
790
- }
791
-
792
- function responseWithCleanup(response: Response, cleanup: RequestCleanup): Response {
793
- if (!response.body) {
794
- void cleanup();
795
- return response;
796
- }
797
- const reader = response.body.getReader();
798
- let cleaned = false;
799
- const runCleanup = async () => {
800
- if (cleaned) return;
801
- cleaned = true;
802
- await cleanup();
803
- };
804
- const body = new ReadableStream<Uint8Array>({
805
- async pull(controller) {
806
- try {
807
- const { done, value } = await reader.read();
808
- if (done) {
809
- controller.close();
810
- await runCleanup();
811
- return;
812
- }
813
- if (value) controller.enqueue(value);
814
- } catch (error) {
815
- await runCleanup();
816
- controller.error(error);
817
- }
818
- },
819
- async cancel(reason) {
820
- try {
821
- await reader.cancel(reason);
822
- } finally {
823
- await runCleanup();
824
- }
825
- },
826
- });
827
- return new Response(body, {
828
- headers: response.headers,
829
- status: response.status,
830
- statusText: response.statusText,
831
- });
832
- }
833
-
834
- async function validateSseEvent(
835
- operation: OperationDefinition,
836
- event: ProviderStreamEvent,
837
- ): Promise<ProviderStreamEvent> {
838
- const transport = getSseTransport(operation);
839
- const schema = transport?.events?.[event.event];
840
- if (!schema) {
841
- if (event.event === APIFUSE_STREAM_ERROR_EVENT || event.event === APIFUSE_STREAM_DONE_EVENT) {
842
- return event;
843
- }
844
- throw new ProviderError(
845
- `SSE event "${event.event}" is not declared in operation transport.events.`,
846
- {
847
- code: "SSE_EVENT_UNDECLARED",
848
- category: "output_validation",
849
- retryable: false,
850
- fix: `Add "${event.event}" to transport.events or stop emitting that event.`,
851
- },
852
- );
853
- }
854
- const data = await parseSchema(schema, event.data, `transport.events.${event.event}`);
855
- return { ...event, data };
856
- }
857
-
858
- function byteLength(value: Uint8Array | string): number {
859
- if (typeof value === "string") {
860
- return new TextEncoder().encode(value).byteLength;
861
- }
862
- return value.byteLength;
863
- }
864
-
865
- function assertStreamPayloadWithinLimit(
866
- actualBytes: number,
867
- maxBytes: number | undefined,
868
- kind: "event" | "chunk",
869
- ): void {
870
- if (maxBytes === undefined || actualBytes <= maxBytes) return;
871
- throw new ProviderError(
872
- `Stream ${kind} exceeded declared byte limit (${actualBytes} > ${maxBytes}).`,
873
- {
874
- code: kind === "event" ? "STREAM_EVENT_TOO_LARGE" : "STREAM_CHUNK_TOO_LARGE",
875
- retryable: false,
876
- category: "input_validation",
877
- fix:
878
- kind === "event"
879
- ? "Emit smaller SSE events or increase transport.maxEventBytes."
880
- : "Emit smaller stream chunks or increase transport.maxChunkBytes.",
881
- },
882
- );
883
- }
884
-
885
- function toSseResponse(
886
- operation: OperationDefinition,
887
- result: AsyncIterable<ProviderStreamEvent>,
888
- cleanup: RequestCleanup,
889
- requestId?: string,
890
- ): Response {
891
- const encoder = new TextEncoder();
892
- const iterator = result[Symbol.asyncIterator]();
893
- const transport = getSseTransport(operation);
894
- let done = false;
895
- let cleaned = false;
896
- const runCleanup = async () => {
897
- if (cleaned) return;
898
- cleaned = true;
899
- await cleanup();
900
- };
901
- const body = new ReadableStream<Uint8Array>({
902
- async pull(controller) {
903
- try {
904
- if (done) {
905
- controller.close();
906
- await runCleanup();
907
- return;
908
- }
909
- const next = await iterator.next();
910
- if (next.done) {
911
- done = true;
912
- controller.close();
913
- await runCleanup();
914
- return;
915
- }
916
- const validated = await validateSseEvent(operation, next.value);
917
- const encodedEvent = encodeSseEvent(validated);
918
- const encodedBytes = encoder.encode(encodedEvent);
919
- assertStreamPayloadWithinLimit(encodedBytes.byteLength, transport?.maxEventBytes, "event");
920
- controller.enqueue(encodedBytes);
921
- } catch (error) {
922
- const message = error instanceof Error ? error.message : "Stream failed";
923
- controller.enqueue(
924
- encoder.encode(
925
- encodeSseEvent(
926
- streamError("stream_error", message, {
927
- ...(requestId ? { requestId } : {}),
928
- }),
929
- ),
930
- ),
931
- );
932
- controller.close();
933
- done = true;
934
- await runCleanup();
935
- }
936
- },
937
- async cancel(reason) {
938
- try {
939
- await iterator.return?.(reason);
940
- } finally {
941
- await runCleanup();
942
- }
943
- },
944
- });
945
- return new Response(body, {
946
- headers: {
947
- "Cache-Control": "no-cache, no-transform",
948
- Connection: "keep-alive",
949
- "Content-Type": "text/event-stream; charset=utf-8",
950
- },
951
- });
952
- }
953
-
954
- function enforceStreamChunkLimit(
955
- body: ReadableStream<Uint8Array>,
956
- maxChunkBytes: number | undefined,
957
- ): ReadableStream<Uint8Array> {
958
- if (maxChunkBytes === undefined) return body;
959
- const reader = body.getReader();
960
- return new ReadableStream<Uint8Array>({
961
- async pull(controller) {
962
- try {
963
- const { done, value } = await reader.read();
964
- if (done) {
965
- controller.close();
966
- return;
967
- }
968
- if (value) {
969
- assertStreamPayloadWithinLimit(byteLength(value), maxChunkBytes, "chunk");
970
- controller.enqueue(value);
971
- }
972
- } catch (error) {
973
- controller.error(error);
974
- }
975
- },
976
- cancel(reason) {
977
- return reader.cancel(reason);
978
- },
979
- });
980
- }
981
-
982
- function toStreamingResponse(
983
- operation: OperationDefinition,
984
- result: unknown,
985
- cleanup: RequestCleanup,
986
- requestId?: string,
987
- ): Response {
988
- const transport = operation.transport?.kind ?? "json";
989
- if (transport === "sse" && (result instanceof Response || result instanceof ReadableStream)) {
990
- void cleanup();
991
- throw new ProviderError(
992
- "SSE operations must return an AsyncIterable of typed stream.event(...) values.",
993
- {
994
- code: "SSE_RESULT_UNSUPPORTED",
995
- category: "output_validation",
996
- retryable: false,
997
- fix: "Return an async generator that yields stream.event(name, data) so APIFuse can validate event schemas and enforce event byte limits.",
998
- },
999
- );
1000
- }
1001
- if (result instanceof Response) {
1002
- const httpTransport = getHttpStreamTransport(operation);
1003
- if (httpTransport && result.body && httpTransport?.maxChunkBytes !== undefined) {
1004
- return responseWithCleanup(
1005
- new Response(enforceStreamChunkLimit(result.body, httpTransport.maxChunkBytes), {
1006
- headers: result.headers,
1007
- status: result.status,
1008
- statusText: result.statusText,
1009
- }),
1010
- cleanup,
1011
- );
1012
- }
1013
- return responseWithCleanup(result, cleanup);
1014
- }
1015
- if (result instanceof ReadableStream) {
1016
- const httpTransport = getHttpStreamTransport(operation);
1017
- const stream =
1018
- httpTransport !== undefined
1019
- ? enforceStreamChunkLimit(result, httpTransport.maxChunkBytes)
1020
- : result;
1021
- return responseWithCleanup(
1022
- new Response(stream, {
1023
- headers:
1024
- transport === "sse"
1025
- ? { "Content-Type": "text/event-stream; charset=utf-8" }
1026
- : {
1027
- "Content-Type":
1028
- operation.transport?.kind === "http-stream"
1029
- ? (operation.transport.contentType ?? "application/octet-stream")
1030
- : "application/octet-stream",
1031
- },
1032
- }),
1033
- cleanup,
1034
- );
1035
- }
1036
- if (transport === "sse" && isAsyncIterable<ProviderStreamEvent>(result)) {
1037
- return toSseResponse(operation, result, cleanup, requestId);
1038
- }
1039
- void cleanup();
1040
- throw new ProviderError(
1041
- `Streaming operation returned unsupported result for transport "${transport}"`,
1042
- {
1043
- code: "STREAM_RESULT_UNSUPPORTED",
1044
- fix: "Return an AsyncIterable of stream.event(...) values, a ReadableStream, or a Response from streaming operations.",
1045
- },
1046
- );
1047
- }
1048
-
1049
- function getSseTransport(operation: OperationDefinition): OperationSseTransport | undefined {
1050
- return operation.transport?.kind === "sse" ? operation.transport : undefined;
1051
- }
1052
-
1053
- function getHttpStreamTransport(
1054
- operation: OperationDefinition,
1055
- ): OperationHttpStreamTransport | undefined {
1056
- return operation.transport?.kind === "http-stream" ? operation.transport : undefined;
1057
- }
1058
-
1059
- function toAuthFlowResponse(
1060
- result: unknown,
1061
- contextPatch: Record<string, unknown | null> | undefined,
1062
- ): Response | AuthFlowSuccessResponse {
1063
- if (result instanceof Response) {
1064
- return result;
1065
- }
1066
-
1067
- if (result instanceof ReadableStream) {
1068
- return new Response(result);
1069
- }
1070
-
1071
- return {
1072
- data: result,
1073
- ...(contextPatch ? { contextPatch } : {}),
1074
- };
1075
- }
1076
-
1077
- function authFlowLocaleFromHeaders(headers?: Record<string, string>): ProviderLocale {
1078
- const header = Object.entries(headers ?? {}).find(
1079
- ([key]) => key.toLowerCase() === "accept-language",
1080
- )?.[1];
1081
- for (const token of (header ?? "").split(",")) {
1082
- const language = token.trim().split(";")[0]?.split("-")[0]?.toLowerCase();
1083
- if (isAuthFlowLocale(language)) {
1084
- return language;
1085
- }
1086
- }
1087
- return "en";
1088
- }
1089
-
1090
- function isAuthFlowLocale(value: string | undefined): value is ProviderLocale {
1091
- return value === "en" || value === "ko" || value === "ja";
1092
- }
1093
-
1094
- function isAuthTurn(value: unknown): value is AuthTurn {
1095
- return !!value && typeof value === "object" && "kind" in value && "turnId" in value;
1096
- }
1097
-
1098
- function loadAuthFlowLocaleCatalogs(
1099
- provider: ProviderDefinition,
1100
- ): ProviderLocaleCatalogMap | undefined {
1101
- for (const providerDir of [
1102
- process.cwd(),
1103
- join(process.cwd(), "providers", provider.id),
1104
- join(process.cwd(), "providers-staging", provider.id),
1105
- ]) {
1106
- if (!existsSync(join(providerDir, "locales", "en.json"))) continue;
1107
- try {
1108
- return loadProviderLocaleCatalogs({
1109
- providerDir,
1110
- locales: AUTH_FLOW_LOCALES,
1111
- });
1112
- } catch {
1113
- return undefined;
1114
- }
1115
- }
1116
- return undefined;
1117
- }
1118
-
1119
- function materializeAuthFlowTurn(
1120
- provider: ProviderDefinition,
1121
- request: AuthFlowRequest,
1122
- turn: AuthTurn,
1123
- ): AuthTurn {
1124
- const catalogs = loadAuthFlowLocaleCatalogs(provider);
1125
- if (!catalogs) return turn;
1126
- return localizeAuthTurn(turn, {
1127
- catalogs,
1128
- locale: authFlowLocaleFromHeaders(request.headers),
1129
- });
1130
- }
1131
-
1132
- function withAuthRequestHeaders(request: AuthFlowRequest, headers: Headers): AuthFlowRequest {
1133
- return {
1134
- ...request,
1135
- headers: {
1136
- ...(request.headers ?? {}),
1137
- ...Object.fromEntries(headers.entries()),
1138
- },
1139
- };
1140
- }
1141
-
1142
- async function handleOperation(
1143
- provider: ProviderDefinition,
1144
- request: OperationRequest,
1145
- operationId: string,
1146
- options: ProviderServerOptions = {},
1147
- state: ProviderRuntimeState = createUnsupportedProviderRuntimeState(),
1148
- proxyTelemetry?: ProxyTelemetryCollector,
1149
- ): Promise<Response | OperationResponse> {
1150
- const ctx = createProviderContext(provider, request, operationId, options, state, proxyTelemetry);
1151
- const operation = provider.operations[operationId];
1152
- const streaming = operation?.transport?.kind && operation.transport.kind !== "json";
1153
- let cleanupCalled = false;
1154
- const cleanup = async () => {
1155
- if (cleanupCalled) return;
1156
- cleanupCalled = true;
1157
- try {
1158
- ctx.stealth.close?.();
1159
- } catch (error) {
1160
- logProviderCleanupError(
1161
- options.logger,
1162
- provider,
1163
- operationId,
1164
- request.requestId,
1165
- "stealth",
1166
- error,
1167
- );
1168
- }
1169
- try {
1170
- await ctx.browser.close?.();
1171
- } catch (error) {
1172
- logProviderCleanupError(
1173
- options.logger,
1174
- provider,
1175
- operationId,
1176
- request.requestId,
1177
- "browser",
1178
- error,
1179
- );
1180
- }
1181
- };
1182
- try {
1183
- const result = await executeOperation(provider, operationId, ctx, request.input);
1184
- if (streaming && operation) {
1185
- return toStreamingResponse(operation, result, cleanup, request.requestId);
1186
- }
1187
- return toJsonSuccessResponse(result, ctx);
1188
- } catch (error) {
1189
- await cleanup();
1190
- throw error;
1191
- } finally {
1192
- if (!streaming) await cleanup();
1193
- }
1194
- }
1195
-
1196
- function responseWithProviderTelemetry(
1197
- response: Response,
1198
- proxyTelemetry?: ProxyTelemetryCollector,
1199
- ): Response {
1200
- const headerValue = proxyTelemetry?.toHeaderValue();
1201
- const headers = new Headers(response.headers);
1202
- headers.delete(PROVIDER_TELEMETRY_HEADER);
1203
- if (headerValue) headers.set(PROVIDER_TELEMETRY_HEADER, headerValue);
1204
- return new Response(response.body, {
1205
- headers,
1206
- status: response.status,
1207
- statusText: response.statusText,
1208
- });
1209
- }
1210
-
1211
- type AuthRoute = "start" | "continue" | "poll" | "abort" | "refresh";
1212
-
1213
- async function handleAuthFlow(
1214
- provider: ProviderDefinition,
1215
- request: AuthFlowRequest,
1216
- route: AuthRoute,
1217
- options: ProviderServerOptions = {},
1218
- signal?: AbortSignal,
1219
- ): Promise<Response | AuthFlowResponse> {
1220
- const flow = provider.auth?.flow;
1221
- if (!flow) {
1222
- throw new ProviderError("Auth flow is not configured", {
1223
- code: "AUTH_FLOW_NOT_CONFIGURED",
1224
- });
1225
- }
1226
-
1227
- const { context, getPatch } = createAuthFlowContext(provider, request, options, signal);
1228
- try {
1229
- const result =
1230
- route === "start"
1231
- ? await flow.start(context)
1232
- : route === "continue"
1233
- ? await flow.continue(context, request.input ?? {})
1234
- : route === "poll"
1235
- ? flow.poll
1236
- ? await flow.poll(context)
1237
- : null
1238
- : route === "abort"
1239
- ? flow.abort
1240
- ? await flow.abort(context)
1241
- : null
1242
- : flow.refresh
1243
- ? await flow.refresh(context, request.input ?? {})
1244
- : null;
1245
-
1246
- if (route === "refresh" && !flow.refresh) {
1247
- throw new AuthError("Provider auth flow does not support refresh.", {
1248
- code: "refresh_not_supported",
1249
- });
1250
- }
1251
-
1252
- const materializedResult =
1253
- result &&
1254
- !(result instanceof Response) &&
1255
- !(result instanceof ReadableStream) &&
1256
- isAuthTurn(result)
1257
- ? materializeAuthFlowTurn(provider, request, result)
1258
- : result;
1259
- return toAuthFlowResponse(materializedResult, getPatch());
1260
- } catch (error) {
1261
- if (error instanceof AuthAbortError) {
1262
- return toAuthFlowResponse(error.turn, getPatch());
1263
- }
1264
- throw error;
1265
- } finally {
1266
- context.stealth.close?.();
1267
- }
1268
- }
1269
-
1270
- export function createServerApp(
1271
- provider: ProviderDefinition,
1272
- options: ProviderServerOptions = {},
1273
- ): Hono {
1274
- const app = new Hono();
1275
- const logger = options.logger ?? defaultProviderServerLogger;
1276
- const state =
1277
- options.state ??
1278
- createProviderRuntimeStateFromEnv({
1279
- providerId: provider.id,
1280
- allowMemoryFallback: options.allowMemoryStateFallback === true,
1281
- });
1282
-
1283
- app.notFound((c) =>
1284
- c.json(
1285
- {
1286
- error: {
1287
- code: "not_found",
1288
- message: "Not found",
1289
- },
1290
- },
1291
- 404,
1292
- ),
1293
- );
1294
-
1295
- app.get("/health", (c) =>
1296
- c.json({
1297
- status: "ok",
1298
- provider: provider.id,
1299
- version: provider.version,
1300
- }),
1301
- );
1302
-
1303
- app.post("/v1/:operation", async (c) => {
1304
- let rawBody: unknown;
1305
- const operation = c.req.param("operation");
1306
- const proxyTelemetry = new ProxyTelemetryCollector();
1307
- const requestCost = startRequestCost();
1308
- try {
1309
- rawBody = await c.req.raw
1310
- .clone()
1311
- .json()
1312
- .catch(() => undefined);
1313
- const body = OperationRequestSchema.parse(rawBody);
1314
- const requestHeaders = Object.fromEntries(c.req.raw.headers.entries());
1315
- body.headers = { ...requestHeaders, ...body.headers };
1316
- const response = await handleOperation(
1317
- provider,
1318
- body,
1319
- operation,
1320
- options,
1321
- state,
1322
- proxyTelemetry,
1323
- );
1324
- if (response instanceof Response) {
1325
- logProviderSuccess(
1326
- logger,
1327
- provider,
1328
- "operation",
1329
- operation,
1330
- body.requestId,
1331
- response.status,
1332
- finishRequestCost(requestCost),
1333
- );
1334
- return responseWithProviderTelemetry(response, proxyTelemetry);
1335
- }
1336
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1337
- if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1338
- logProviderSuccess(
1339
- logger,
1340
- provider,
1341
- "operation",
1342
- operation,
1343
- body.requestId,
1344
- 200,
1345
- finishRequestCost(requestCost),
1346
- );
1347
- return c.json(response);
1348
- } catch (error) {
1349
- const status = toStatusCode(error);
1350
- const requestId = extractRequestId(rawBody);
1351
- logProviderError(
1352
- logger,
1353
- provider,
1354
- "operation",
1355
- operation,
1356
- requestId,
1357
- error,
1358
- status,
1359
- finishRequestCost(requestCost),
1360
- );
1361
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1362
- if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1363
- return c.json(toErrorResponse(error, requestId), status);
1364
- }
1365
- });
1366
-
1367
- app.post("/auth/start", async (c) => {
1368
- let rawBody: unknown;
1369
- const requestCost = startRequestCost();
1370
- try {
1371
- rawBody = await c.req.raw
1372
- .clone()
1373
- .json()
1374
- .catch(() => undefined);
1375
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1376
- const response = await handleAuthFlow(provider, body, "start", options, c.req.raw.signal);
1377
- logProviderSuccess(
1378
- logger,
1379
- provider,
1380
- "auth",
1381
- "start",
1382
- body.requestId,
1383
- response instanceof Response ? response.status : 200,
1384
- finishRequestCost(requestCost),
1385
- );
1386
- return response instanceof Response ? response : c.json(response);
1387
- } catch (error) {
1388
- const status = toStatusCode(error);
1389
- const requestId = extractRequestId(rawBody);
1390
- logProviderError(
1391
- logger,
1392
- provider,
1393
- "auth",
1394
- "start",
1395
- requestId,
1396
- error,
1397
- status,
1398
- finishRequestCost(requestCost),
1399
- );
1400
- return c.json(toErrorResponse(error, requestId), status);
1401
- }
1402
- });
1403
-
1404
- app.post("/auth/continue", async (c) => {
1405
- let rawBody: unknown;
1406
- const requestCost = startRequestCost();
1407
- try {
1408
- rawBody = await c.req.raw
1409
- .clone()
1410
- .json()
1411
- .catch(() => undefined);
1412
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1413
- const response = await handleAuthFlow(provider, body, "continue", options, c.req.raw.signal);
1414
- logProviderSuccess(
1415
- logger,
1416
- provider,
1417
- "auth",
1418
- "continue",
1419
- body.requestId,
1420
- response instanceof Response ? response.status : 200,
1421
- finishRequestCost(requestCost),
1422
- );
1423
- return response instanceof Response ? response : c.json(response);
1424
- } catch (error) {
1425
- const status = toStatusCode(error);
1426
- const requestId = extractRequestId(rawBody);
1427
- logProviderError(
1428
- logger,
1429
- provider,
1430
- "auth",
1431
- "continue",
1432
- requestId,
1433
- error,
1434
- status,
1435
- finishRequestCost(requestCost),
1436
- );
1437
- return c.json(toErrorResponse(error, requestId), status);
1438
- }
1439
- });
1440
-
1441
- app.post("/auth/poll", async (c) => {
1442
- let rawBody: unknown;
1443
- const requestCost = startRequestCost();
1444
- try {
1445
- rawBody = await c.req.raw
1446
- .clone()
1447
- .json()
1448
- .catch(() => undefined);
1449
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1450
- const response = await handleAuthFlow(provider, body, "poll", options, c.req.raw.signal);
1451
- logProviderSuccess(
1452
- logger,
1453
- provider,
1454
- "auth",
1455
- "poll",
1456
- body.requestId,
1457
- response instanceof Response ? response.status : 200,
1458
- finishRequestCost(requestCost),
1459
- );
1460
- return response instanceof Response ? response : c.json(response);
1461
- } catch (error) {
1462
- const status = toStatusCode(error);
1463
- const requestId = extractRequestId(rawBody);
1464
- logProviderError(
1465
- logger,
1466
- provider,
1467
- "auth",
1468
- "poll",
1469
- requestId,
1470
- error,
1471
- status,
1472
- finishRequestCost(requestCost),
1473
- );
1474
- return c.json(toErrorResponse(error, requestId), status);
1475
- }
1476
- });
1477
-
1478
- app.post("/auth/refresh", async (c) => {
1479
- let rawBody: unknown;
1480
- const requestCost = startRequestCost();
1481
- try {
1482
- rawBody = await c.req.raw
1483
- .clone()
1484
- .json()
1485
- .catch(() => undefined);
1486
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1487
- const response = await handleAuthFlow(provider, body, "refresh", options, c.req.raw.signal);
1488
- logProviderSuccess(
1489
- logger,
1490
- provider,
1491
- "auth",
1492
- "refresh",
1493
- body.requestId,
1494
- response instanceof Response ? response.status : 200,
1495
- finishRequestCost(requestCost),
1496
- );
1497
- return response instanceof Response ? response : c.json(response);
1498
- } catch (error) {
1499
- const status = toStatusCode(error);
1500
- const requestId = extractRequestId(rawBody);
1501
- logProviderError(
1502
- logger,
1503
- provider,
1504
- "auth",
1505
- "refresh",
1506
- requestId,
1507
- error,
1508
- status,
1509
- finishRequestCost(requestCost),
1510
- );
1511
- return c.json(toErrorResponse(error, requestId), status);
1512
- }
1513
- });
1514
-
1515
- app.post("/auth/disconnect", async (c) => {
1516
- let rawBody: unknown;
1517
- const requestCost = startRequestCost();
1518
- try {
1519
- rawBody = await c.req.raw
1520
- .clone()
1521
- .json()
1522
- .catch(() => undefined);
1523
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1524
- const response = await handleAuthFlow(provider, body, "abort", options, c.req.raw.signal);
1525
- logProviderSuccess(
1526
- logger,
1527
- provider,
1528
- "auth",
1529
- "disconnect",
1530
- body.requestId,
1531
- response instanceof Response ? response.status : 200,
1532
- finishRequestCost(requestCost),
1533
- );
1534
- return response instanceof Response ? response : c.json(response);
1535
- } catch (error) {
1536
- const status = toStatusCode(error);
1537
- const requestId = extractRequestId(rawBody);
1538
- logProviderError(
1539
- logger,
1540
- provider,
1541
- "auth",
1542
- "disconnect",
1543
- requestId,
1544
- error,
1545
- status,
1546
- finishRequestCost(requestCost),
1547
- );
1548
- return c.json(toErrorResponse(error, requestId), status);
1549
- }
1550
- });
1551
-
1552
- return app;
1553
- }
1554
-
1555
- type BunServeRuntime = {
1556
- serve: (options: {
1557
- port: number;
1558
- hostname: string;
1559
- fetch: (request: Request) => Response | Promise<Response>;
1560
- }) => unknown;
1561
- };
1562
-
1563
- function getBunServeRuntime(): BunServeRuntime | undefined {
1564
- const bunValue = Object.getOwnPropertyDescriptor(globalThis, "Bun")?.value;
1565
- if (!bunValue || typeof bunValue !== "object") {
1566
- return undefined;
1567
- }
1568
-
1569
- const serve = Object.getOwnPropertyDescriptor(bunValue, "serve")?.value;
1570
- if (typeof serve !== "function") {
1571
- return undefined;
1572
- }
1573
-
1574
- return {
1575
- serve(options) {
1576
- return serve(options);
1577
- },
1578
- };
1579
- }
1580
-
1581
- export interface ServeOptions extends ProviderServerOptions {
1582
- host?: string;
1583
- port?: number;
1584
- /**
1585
- * Port for the internal self-test listener (default 3001 or
1586
- * APIFUSE__PROVIDER_RUNTIME__SELF_TEST_PORT). The listener only starts
1587
- * when APIFUSE__PROVIDER_RUNTIME__SELF_TEST_MASTER_SECRET is present.
1588
- */
1589
- selfTestPort?: number;
1590
- }
1591
-
1592
- export async function serve(
1593
- provider: ProviderDefinition,
1594
- options: ServeOptions = {},
1595
- ): Promise<void> {
1596
- const bunRuntime = getBunServeRuntime();
1597
-
1598
- if (bunRuntime === undefined) {
1599
- throw new ProviderError("Bun runtime is required to start the provider server", {
1600
- code: "RUNTIME_UNSUPPORTED",
1601
- });
1602
- }
1603
-
1604
- const app = createServerApp(provider, {
1605
- logger: options.logger,
1606
- stt: options.stt,
1607
- });
1608
-
1609
- bunRuntime.serve({
1610
- port: options.port ?? DEFAULT_PORT,
1611
- hostname: options.host ?? DEFAULT_HOST,
1612
- fetch: app.fetch,
1613
- });
1614
-
1615
- // Internal self-test listener (health dependency inversion): a SEPARATE
1616
- // socket the tenant-facing gateway never dials. Off by default — it only
1617
- // starts when the shared self-test master secret env is present.
1618
- const selfTestSecrets = resolveSelfTestMasterSecrets();
1619
- if (selfTestSecrets) {
1620
- const selfTestApp = createSelfTestApp(provider, {
1621
- secrets: selfTestSecrets,
1622
- invoke: createSelfTestInvoke(app),
1623
- authFlow: createSelfTestAuthFlowInvoke(app),
1624
- });
1625
- bunRuntime.serve({
1626
- port: options.selfTestPort ?? resolveSelfTestPort(),
1627
- hostname: options.host ?? DEFAULT_HOST,
1628
- fetch: selfTestApp.fetch,
1629
- });
1630
- }
1631
- await Promise.resolve();
1632
- }
1
+ export * from "./serve-implementation.js";