@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.30

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