@mupt-ai/dari-gateway 0.2.0 → 0.2.1

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 (366) hide show
  1. package/dist/bun.d.ts +2 -0
  2. package/dist/bun.js +4 -0
  3. package/dist/context/eval_catalog.d.ts +3 -0
  4. package/dist/context/eval_catalog.js +7 -0
  5. package/dist/context/interaction_fingerprint.d.ts +27 -0
  6. package/dist/context/interaction_fingerprint.js +519 -0
  7. package/dist/context/interaction_fingerprint_format.d.ts +42 -0
  8. package/dist/context/interaction_fingerprint_format.js +113 -0
  9. package/dist/context/interaction_lease_depth.d.ts +2 -0
  10. package/dist/context/interaction_lease_depth.js +110 -0
  11. package/dist/context/lease_history.d.ts +7 -0
  12. package/dist/context/lease_history.js +158 -0
  13. package/dist/context/pricing.d.ts +4 -0
  14. package/dist/context/pricing.js +23 -0
  15. package/dist/context/provider_prompt_accounting.d.ts +14 -0
  16. package/dist/context/provider_prompt_accounting.js +187 -0
  17. package/dist/context/routing_context.d.ts +40 -0
  18. package/dist/context/routing_context.js +211 -0
  19. package/dist/http/auth.d.ts +2 -0
  20. package/dist/http/auth.js +35 -0
  21. package/dist/http/errors.d.ts +19 -0
  22. package/dist/http/errors.js +89 -0
  23. package/dist/http/handler.d.ts +12 -0
  24. package/dist/http/handler.js +81 -0
  25. package/dist/http/http2_server.d.ts +6 -0
  26. package/dist/http/http2_server.js +103 -0
  27. package/dist/http/serve.d.ts +20 -0
  28. package/dist/http/serve.js +78 -0
  29. package/dist/index.d.ts +6 -0
  30. package/dist/index.js +8 -0
  31. package/dist/ingress/anthropic_messages.d.ts +3 -0
  32. package/dist/ingress/anthropic_messages.js +122 -0
  33. package/dist/ingress/anthropic_models.d.ts +1 -0
  34. package/dist/ingress/anthropic_models.js +23 -0
  35. package/dist/ingress/chat_completions.d.ts +2 -0
  36. package/dist/ingress/chat_completions.js +37 -0
  37. package/dist/ingress/claude_code/request_context.d.ts +2 -0
  38. package/dist/ingress/claude_code/request_context.js +31 -0
  39. package/dist/ingress/codex.d.ts +3 -0
  40. package/dist/ingress/codex.js +65 -0
  41. package/dist/ingress/codex_models.d.ts +2 -0
  42. package/dist/ingress/codex_models.js +89 -0
  43. package/dist/ingress/json.d.ts +1 -0
  44. package/dist/ingress/json.js +95 -0
  45. package/dist/ingress/models.d.ts +1 -0
  46. package/dist/ingress/models.js +53 -0
  47. package/dist/interaction/capabilities.d.ts +3 -0
  48. package/dist/interaction/capabilities.js +24 -0
  49. package/dist/interaction/image.d.ts +4 -0
  50. package/dist/interaction/image.js +13 -0
  51. package/dist/interaction/portable_reasoning_state.d.ts +4 -0
  52. package/dist/interaction/portable_reasoning_state.js +18 -0
  53. package/dist/interaction/provider_continuation_state.d.ts +6 -0
  54. package/dist/interaction/provider_continuation_state.js +27 -0
  55. package/dist/interaction/tool_identity.d.ts +10 -0
  56. package/dist/interaction/tool_identity.js +41 -0
  57. package/dist/interaction/types.d.ts +203 -0
  58. package/dist/interaction/types.js +5 -0
  59. package/dist/models/context_limits.d.ts +2 -0
  60. package/dist/models/context_limits.js +7 -0
  61. package/dist/models/execution_provider.d.ts +8 -0
  62. package/dist/models/execution_provider.js +13 -0
  63. package/dist/models/gateways.d.ts +11 -0
  64. package/dist/models/gateways.js +45 -0
  65. package/dist/models/ids.d.ts +1 -0
  66. package/dist/models/ids.js +15 -0
  67. package/dist/models/provider_defaults.d.ts +4 -0
  68. package/dist/models/provider_defaults.js +41 -0
  69. package/dist/models/runtime_overrides.d.ts +4 -0
  70. package/dist/models/runtime_overrides.js +209 -0
  71. package/dist/observability/errors.d.ts +8 -0
  72. package/dist/observability/errors.js +11 -0
  73. package/dist/platform/platform.d.ts +152 -0
  74. package/dist/platform/platform.js +11 -0
  75. package/dist/platform/routing_contract.d.ts +2 -0
  76. package/dist/platform/routing_contract.js +5 -0
  77. package/dist/protocol/input/anthropic_messages.d.ts +2 -0
  78. package/dist/protocol/input/anthropic_messages.js +494 -0
  79. package/dist/protocol/input/chat_completions.d.ts +4 -0
  80. package/dist/protocol/input/chat_completions.js +212 -0
  81. package/dist/protocol/input/chat_completions_validation.d.ts +2 -0
  82. package/dist/protocol/input/chat_completions_validation.js +257 -0
  83. package/dist/protocol/input/openai_responses.d.ts +2 -0
  84. package/dist/protocol/input/openai_responses.js +666 -0
  85. package/dist/protocol/input/router_model.d.ts +7 -0
  86. package/dist/protocol/input/router_model.js +43 -0
  87. package/dist/protocol/output/anthropic_messages.d.ts +10 -0
  88. package/dist/protocol/output/anthropic_messages.js +15 -0
  89. package/dist/protocol/output/anthropic_messages_output.d.ts +10 -0
  90. package/dist/protocol/output/anthropic_messages_output.js +124 -0
  91. package/dist/protocol/output/anthropic_messages_stream.d.ts +16 -0
  92. package/dist/protocol/output/anthropic_messages_stream.js +267 -0
  93. package/dist/protocol/output/chat_completions.d.ts +29 -0
  94. package/dist/protocol/output/chat_completions.js +406 -0
  95. package/dist/protocol/output/chat_reasoning_detail.d.ts +2 -0
  96. package/dist/protocol/output/chat_reasoning_detail.js +13 -0
  97. package/dist/protocol/output/delivered_stream_snapshot.d.ts +10 -0
  98. package/dist/protocol/output/delivered_stream_snapshot.js +31 -0
  99. package/dist/protocol/output/openai_responses.d.ts +10 -0
  100. package/dist/protocol/output/openai_responses.js +14 -0
  101. package/dist/protocol/output/openai_responses_output.d.ts +14 -0
  102. package/dist/protocol/output/openai_responses_output.js +125 -0
  103. package/dist/protocol/output/openai_responses_stream.d.ts +18 -0
  104. package/dist/protocol/output/openai_responses_stream.js +546 -0
  105. package/dist/protocol/output/sse_stream.d.ts +31 -0
  106. package/dist/protocol/output/sse_stream.js +67 -0
  107. package/dist/providers/anthropic_safeguards.d.ts +11 -0
  108. package/dist/providers/anthropic_safeguards.js +81 -0
  109. package/dist/providers/aws_assume_role.d.ts +17 -0
  110. package/dist/providers/aws_assume_role.js +107 -0
  111. package/dist/providers/cancel_estimate.d.ts +43 -0
  112. package/dist/providers/cancel_estimate.js +89 -0
  113. package/dist/providers/execution.d.ts +72 -0
  114. package/dist/providers/execution.js +491 -0
  115. package/dist/providers/model_capabilities.d.ts +11 -0
  116. package/dist/providers/model_capabilities.js +29 -0
  117. package/dist/providers/pi.d.ts +65 -0
  118. package/dist/providers/pi.js +319 -0
  119. package/dist/providers/pi_context.d.ts +7 -0
  120. package/dist/providers/pi_context.js +405 -0
  121. package/dist/providers/pi_options.d.ts +11 -0
  122. package/dist/providers/pi_options.js +398 -0
  123. package/dist/providers/pi_output.d.ts +2 -0
  124. package/dist/providers/pi_output.js +453 -0
  125. package/dist/providers/provider_attempt_runner.d.ts +55 -0
  126. package/dist/providers/provider_attempt_runner.js +90 -0
  127. package/dist/providers/provider_call.d.ts +77 -0
  128. package/dist/providers/provider_call.js +292 -0
  129. package/dist/providers/provider_reported_cost.d.ts +10 -0
  130. package/dist/providers/provider_reported_cost.js +123 -0
  131. package/dist/providers/reasoning.d.ts +12 -0
  132. package/dist/providers/reasoning.js +30 -0
  133. package/dist/providers/response_capture.d.ts +23 -0
  134. package/dist/providers/response_capture.js +47 -0
  135. package/dist/providers/stream_finalization.d.ts +46 -0
  136. package/dist/providers/stream_finalization.js +165 -0
  137. package/dist/providers/subscription_limits.d.ts +31 -0
  138. package/dist/providers/subscription_limits.js +163 -0
  139. package/dist/providers/subscription_usage.d.ts +10 -0
  140. package/dist/providers/subscription_usage.js +79 -0
  141. package/dist/runtime/claude_code/auxiliary_routing.d.ts +3 -0
  142. package/dist/runtime/claude_code/auxiliary_routing.js +60 -0
  143. package/dist/runtime/customer_disconnect.d.ts +20 -0
  144. package/dist/runtime/customer_disconnect.js +49 -0
  145. package/dist/runtime/explicit_model.d.ts +4 -0
  146. package/dist/runtime/explicit_model.js +53 -0
  147. package/dist/runtime/harness_identity/conversation.d.ts +33 -0
  148. package/dist/runtime/harness_identity/conversation.js +39 -0
  149. package/dist/runtime/harness_identity/extract.d.ts +8 -0
  150. package/dist/runtime/harness_identity/extract.js +214 -0
  151. package/dist/runtime/harness_identity/index.d.ts +2 -0
  152. package/dist/runtime/harness_identity/index.js +2 -0
  153. package/dist/runtime/prepare_routed_completion.d.ts +33 -0
  154. package/dist/runtime/prepare_routed_completion.js +167 -0
  155. package/dist/runtime/provider_stream_lifecycle.d.ts +31 -0
  156. package/dist/runtime/provider_stream_lifecycle.js +88 -0
  157. package/dist/runtime/provider_stream_outcome.d.ts +23 -0
  158. package/dist/runtime/provider_stream_outcome.js +55 -0
  159. package/dist/runtime/request_tags.d.ts +7 -0
  160. package/dist/runtime/request_tags.js +33 -0
  161. package/dist/runtime/routed_completion.d.ts +17 -0
  162. package/dist/runtime/routed_completion.js +80 -0
  163. package/dist/runtime/routed_protocol.d.ts +30 -0
  164. package/dist/runtime/routed_protocol.js +1 -0
  165. package/dist/runtime/routed_runtime.d.ts +40 -0
  166. package/dist/runtime/routed_runtime.js +535 -0
  167. package/dist/runtime/routing_lease.d.ts +26 -0
  168. package/dist/runtime/routing_lease.js +210 -0
  169. package/dist/runtime/runtime_reporter.d.ts +71 -0
  170. package/dist/runtime/runtime_reporter.js +35 -0
  171. package/dist/self_host/config.d.ts +46 -0
  172. package/dist/self_host/config.js +105 -0
  173. package/dist/self_host/main.d.ts +16 -0
  174. package/dist/self_host/main.js +95 -0
  175. package/dist/self_host/platform.d.ts +13 -0
  176. package/dist/self_host/platform.js +50 -0
  177. package/dist/self_host/prefix_store.d.ts +46 -0
  178. package/dist/self_host/prefix_store.js +261 -0
  179. package/dist/self_host/resolve.d.ts +28 -0
  180. package/dist/self_host/resolve.js +171 -0
  181. package/dist/self_host/subscriptions/access.d.ts +10 -0
  182. package/dist/self_host/subscriptions/access.js +37 -0
  183. package/dist/self_host/subscriptions/http.d.ts +15 -0
  184. package/dist/self_host/subscriptions/http.js +187 -0
  185. package/dist/self_host/subscriptions/index.d.ts +24 -0
  186. package/dist/self_host/subscriptions/index.js +24 -0
  187. package/dist/self_host/subscriptions/oauth.d.ts +55 -0
  188. package/dist/self_host/subscriptions/oauth.js +281 -0
  189. package/dist/self_host/subscriptions/store.d.ts +78 -0
  190. package/dist/self_host/subscriptions/store.js +235 -0
  191. package/dist/strategies/auto_routing_selector.d.ts +12 -0
  192. package/dist/strategies/auto_routing_selector.js +61 -0
  193. package/dist/strategies/eval_relevance.d.ts +17 -0
  194. package/dist/strategies/eval_relevance.js +32 -0
  195. package/dist/strategies/fallback_decision.d.ts +8 -0
  196. package/dist/strategies/fallback_decision.js +17 -0
  197. package/dist/strategies/priority_selector.d.ts +7 -0
  198. package/dist/strategies/priority_selector.js +37 -0
  199. package/dist/strategies/route_decision.d.ts +22 -0
  200. package/dist/strategies/route_decision.js +65 -0
  201. package/dist/strategies/routing.d.ts +74 -0
  202. package/dist/strategies/routing.js +280 -0
  203. package/dist/strategies/selector_projection.d.ts +15 -0
  204. package/dist/strategies/selector_projection.js +161 -0
  205. package/dist/telemetry/background.d.ts +7 -0
  206. package/dist/telemetry/background.js +33 -0
  207. package/dist/telemetry/benchmark_replay.d.ts +37 -0
  208. package/dist/telemetry/benchmark_replay.js +108 -0
  209. package/dist/telemetry/billing_finalization.d.ts +64 -0
  210. package/dist/telemetry/billing_finalization.js +96 -0
  211. package/dist/telemetry/ingest_values.d.ts +4 -0
  212. package/dist/telemetry/ingest_values.js +12 -0
  213. package/dist/telemetry/model_cost.d.ts +3 -0
  214. package/dist/telemetry/model_cost.js +15 -0
  215. package/dist/telemetry/prefix_state.d.ts +47 -0
  216. package/dist/telemetry/prefix_state.js +116 -0
  217. package/dist/telemetry/report.d.ts +88 -0
  218. package/dist/telemetry/report.js +621 -0
  219. package/dist/telemetry/requests/interaction_observability.d.ts +45 -0
  220. package/dist/telemetry/requests/interaction_observability.js +180 -0
  221. package/dist/telemetry/requests/retained_interaction_request.d.ts +8 -0
  222. package/dist/telemetry/requests/retained_interaction_request.js +10 -0
  223. package/dist/telemetry/scrub.d.ts +2 -0
  224. package/dist/telemetry/scrub.js +31 -0
  225. package/dist/telemetry/skills/claude/catalog.d.ts +6 -0
  226. package/dist/telemetry/skills/claude/catalog.js +99 -0
  227. package/dist/telemetry/skills/claude/file_reads.d.ts +4 -0
  228. package/dist/telemetry/skills/claude/file_reads.js +110 -0
  229. package/dist/telemetry/skills/claude/observations.d.ts +3 -0
  230. package/dist/telemetry/skills/claude/observations.js +366 -0
  231. package/dist/telemetry/skills/codex/catalog.d.ts +11 -0
  232. package/dist/telemetry/skills/codex/catalog.js +138 -0
  233. package/dist/telemetry/skills/codex/exec_file_reads.d.ts +9 -0
  234. package/dist/telemetry/skills/codex/exec_file_reads.js +36 -0
  235. package/dist/telemetry/skills/codex/explicit.d.ts +3 -0
  236. package/dist/telemetry/skills/codex/explicit.js +90 -0
  237. package/dist/telemetry/skills/codex/file_reads.d.ts +4 -0
  238. package/dist/telemetry/skills/codex/file_reads.js +37 -0
  239. package/dist/telemetry/skills/codex/identity.d.ts +2 -0
  240. package/dist/telemetry/skills/codex/identity.js +20 -0
  241. package/dist/telemetry/skills/observations.d.ts +21 -0
  242. package/dist/telemetry/skills/observations.js +41 -0
  243. package/dist/telemetry/skills/paths.d.ts +11 -0
  244. package/dist/telemetry/skills/paths.js +61 -0
  245. package/dist/telemetry/skills/pi.d.ts +6 -0
  246. package/dist/telemetry/skills/pi.js +223 -0
  247. package/dist/telemetry/skills/shell_file_reads.d.ts +7 -0
  248. package/dist/telemetry/skills/shell_file_reads.js +226 -0
  249. package/dist/telemetry/skills/shell_words.d.ts +13 -0
  250. package/dist/telemetry/skills/shell_words.js +189 -0
  251. package/dist/telemetry/tools/input.d.ts +2 -0
  252. package/dist/telemetry/tools/input.js +14 -0
  253. package/dist/telemetry/tools/observations.d.ts +29 -0
  254. package/dist/telemetry/tools/observations.js +247 -0
  255. package/dist/telemetry/usage.d.ts +57 -0
  256. package/dist/telemetry/usage.js +136 -0
  257. package/dist/types.d.ts +94 -0
  258. package/dist/types.js +1 -0
  259. package/package.json +21 -6
  260. package/src/bun.ts +2 -2
  261. package/src/context/eval_catalog.ts +1 -1
  262. package/src/context/interaction_fingerprint.ts +9 -9
  263. package/src/context/interaction_fingerprint_format.ts +1 -1
  264. package/src/context/interaction_lease_depth.ts +1 -1
  265. package/src/context/pricing.ts +1 -1
  266. package/src/context/provider_prompt_accounting.ts +5 -5
  267. package/src/context/routing_context.ts +16 -16
  268. package/src/http/auth.ts +1 -1
  269. package/src/http/handler.ts +8 -8
  270. package/src/http/serve.ts +3 -3
  271. package/src/index.ts +6 -6
  272. package/src/ingress/anthropic_messages.ts +12 -12
  273. package/src/ingress/anthropic_models.ts +1 -1
  274. package/src/ingress/chat_completions.ts +9 -9
  275. package/src/ingress/claude_code/request_context.ts +2 -2
  276. package/src/ingress/codex.ts +10 -10
  277. package/src/ingress/codex_models.ts +4 -4
  278. package/src/ingress/json.ts +1 -1
  279. package/src/ingress/models.ts +9 -9
  280. package/src/interaction/capabilities.ts +1 -1
  281. package/src/interaction/portable_reasoning_state.ts +1 -1
  282. package/src/interaction/provider_continuation_state.ts +1 -1
  283. package/src/interaction/tool_identity.ts +2 -2
  284. package/src/models/context_limits.ts +1 -1
  285. package/src/models/execution_provider.ts +1 -1
  286. package/src/models/ids.ts +1 -1
  287. package/src/models/provider_defaults.ts +1 -1
  288. package/src/models/runtime_overrides.ts +1 -1
  289. package/src/observability/errors.ts +2 -2
  290. package/src/platform/platform.ts +4 -4
  291. package/src/protocol/input/anthropic_messages.ts +7 -7
  292. package/src/protocol/input/chat_completions.ts +5 -5
  293. package/src/protocol/input/chat_completions_validation.ts +4 -4
  294. package/src/protocol/input/openai_responses.ts +6 -6
  295. package/src/protocol/output/anthropic_messages.ts +3 -3
  296. package/src/protocol/output/anthropic_messages_output.ts +6 -4
  297. package/src/protocol/output/anthropic_messages_stream.ts +7 -7
  298. package/src/protocol/output/chat_completions.ts +13 -13
  299. package/src/protocol/output/chat_reasoning_detail.ts +1 -1
  300. package/src/protocol/output/openai_responses.ts +3 -3
  301. package/src/protocol/output/openai_responses_output.ts +4 -2
  302. package/src/protocol/output/openai_responses_stream.ts +6 -6
  303. package/src/protocol/output/sse_stream.ts +3 -3
  304. package/src/providers/anthropic_safeguards.ts +2 -2
  305. package/src/providers/aws_assume_role.ts +2 -2
  306. package/src/providers/cancel_estimate.ts +1 -1
  307. package/src/providers/execution.ts +17 -17
  308. package/src/providers/pi.ts +14 -14
  309. package/src/providers/pi_context.ts +6 -6
  310. package/src/providers/pi_options.ts +10 -10
  311. package/src/providers/pi_output.ts +5 -5
  312. package/src/providers/provider_attempt_runner.ts +1 -1
  313. package/src/providers/provider_call.ts +14 -14
  314. package/src/providers/provider_reported_cost.ts +3 -3
  315. package/src/providers/reasoning.ts +2 -2
  316. package/src/providers/stream_finalization.ts +9 -9
  317. package/src/providers/subscription_limits.ts +3 -3
  318. package/src/runtime/claude_code/auxiliary_routing.ts +5 -5
  319. package/src/runtime/customer_disconnect.ts +1 -1
  320. package/src/runtime/explicit_model.ts +4 -4
  321. package/src/runtime/harness_identity/conversation.ts +1 -1
  322. package/src/runtime/harness_identity/extract.ts +3 -3
  323. package/src/runtime/harness_identity/index.ts +2 -2
  324. package/src/runtime/prepare_routed_completion.ts +13 -13
  325. package/src/runtime/provider_stream_lifecycle.ts +1 -1
  326. package/src/runtime/provider_stream_outcome.ts +2 -2
  327. package/src/runtime/request_tags.ts +2 -2
  328. package/src/runtime/routed_completion.ts +11 -11
  329. package/src/runtime/routed_protocol.ts +2 -2
  330. package/src/runtime/routed_runtime.ts +20 -19
  331. package/src/runtime/routing_lease.ts +7 -7
  332. package/src/runtime/runtime_reporter.ts +3 -3
  333. package/src/self_host/main.ts +12 -12
  334. package/src/self_host/platform.ts +5 -5
  335. package/src/self_host/prefix_store.ts +2 -2
  336. package/src/self_host/resolve.ts +6 -6
  337. package/src/self_host/subscriptions/access.ts +2 -2
  338. package/src/self_host/subscriptions/http.ts +2 -2
  339. package/src/self_host/subscriptions/index.ts +4 -4
  340. package/src/self_host/subscriptions/store.ts +1 -1
  341. package/src/strategies/auto_routing_selector.ts +2 -2
  342. package/src/strategies/eval_relevance.ts +3 -3
  343. package/src/strategies/priority_selector.ts +1 -1
  344. package/src/strategies/route_decision.ts +7 -7
  345. package/src/strategies/routing.ts +10 -10
  346. package/src/strategies/selector_projection.ts +2 -2
  347. package/src/telemetry/billing_finalization.ts +3 -3
  348. package/src/telemetry/model_cost.ts +1 -1
  349. package/src/telemetry/prefix_state.ts +4 -4
  350. package/src/telemetry/report.ts +21 -21
  351. package/src/telemetry/requests/interaction_observability.ts +1 -1
  352. package/src/telemetry/requests/retained_interaction_request.ts +1 -1
  353. package/src/telemetry/skills/claude/catalog.ts +1 -1
  354. package/src/telemetry/skills/claude/file_reads.ts +6 -6
  355. package/src/telemetry/skills/claude/observations.ts +3 -3
  356. package/src/telemetry/skills/codex/catalog.ts +3 -3
  357. package/src/telemetry/skills/codex/exec_file_reads.ts +4 -4
  358. package/src/telemetry/skills/codex/explicit.ts +3 -3
  359. package/src/telemetry/skills/codex/file_reads.ts +5 -5
  360. package/src/telemetry/skills/observations.ts +7 -7
  361. package/src/telemetry/skills/pi.ts +3 -3
  362. package/src/telemetry/skills/shell_file_reads.ts +2 -2
  363. package/src/telemetry/skills/shell_words.ts +1 -1
  364. package/src/telemetry/tools/input.ts +1 -1
  365. package/src/telemetry/tools/observations.ts +9 -9
  366. package/src/telemetry/usage.ts +5 -5
@@ -0,0 +1,211 @@
1
+ import { prepareCandidates, } from "@mupt-ai/dari-router/policy-engine";
2
+ import { randomUUID } from "node:crypto";
3
+ import { HttpError } from "../http/errors.js";
4
+ import { effectiveContextWindow } from "../models/context_limits.js";
5
+ import { keySourceForModel } from "../models/execution_provider.js";
6
+ import { requiredCapabilitiesFor } from "../interaction/capabilities.js";
7
+ import { providerIdentityFromModel } from "../interaction/provider_continuation_state.js";
8
+ import { isBackgroundRequest } from "../interaction/types.js";
9
+ import { requireCandidateModels } from "../models/ids.js";
10
+ import { modelMetadataLookup } from "../providers/pi.js";
11
+ import { modelSupportsImageInput, modelSupportsHostedWebSearch, modelSupportsReasoningConfigurationUpdate, providerApiCanSerializeResponseFormat, } from "../providers/model_capabilities.js";
12
+ import { selectorMessagesForInteraction, } from "../strategies/selector_projection.js";
13
+ import { interactionPrefixContext, interactionPrefixLookupHashes, } from "./interaction_fingerprint.js";
14
+ import { pricingLookupForRouter } from "./pricing.js";
15
+ import { providerPromptAccounting } from "./provider_prompt_accounting.js";
16
+ import { leaseHistoryFromPrefixHits } from "./lease_history.js";
17
+ import { interactionLeaseFenceDepth } from "./interaction_lease_depth.js";
18
+ // Router resolution only needs lookup hashes. Model-specific chains are
19
+ // derived after resolve, when the enabled providers and APIs are known.
20
+ export function derivePrefixLookupHashes(routerId, interaction) {
21
+ try {
22
+ return interactionPrefixLookupHashes(routerId, interaction);
23
+ }
24
+ catch (error) {
25
+ console.error("prefix fingerprinting failed", error);
26
+ return [];
27
+ }
28
+ }
29
+ export async function buildRoutingContext(args) {
30
+ const configuredCandidateModels = requireCandidateModels(args.credentials.enabled_models);
31
+ // With credits exhausted, route only among models a personal subscription
32
+ // serves. The credit gate refuses routers with none.
33
+ const fundedCandidateModels = args.credentials.credit_exhausted
34
+ ? configuredCandidateModels.filter((model) => keySourceForModel(args.credentials, model) === "subscription")
35
+ : configuredCandidateModels;
36
+ const { messages: selectorMessages, images: selectorImages } = selectorMessagesForInteraction(args.interaction, {
37
+ imageInput: args.credentials.selector_image_input_enabled === true,
38
+ });
39
+ const leaseDepth = interactionLeaseFenceDepth(args.interaction);
40
+ const metadataLookup = modelMetadataLookup(args.credentials.model_configs);
41
+ const targetsByModel = new Map();
42
+ for (const model of configuredCandidateModels) {
43
+ targetsByModel.set(model, providerIdentityFromModel(metadataLookup(model).model));
44
+ }
45
+ const capabilityLookup = (modelId) => {
46
+ const metadata = metadataLookup(modelId);
47
+ return {
48
+ provider: typeof metadata.model.provider === "string" ? metadata.model.provider : "",
49
+ api: typeof metadata.model.api === "string" ? metadata.model.api : "",
50
+ supportsImageInput: modelSupportsImageInput(metadata),
51
+ supportsHostedWebSearch: modelSupportsHostedWebSearch(metadata),
52
+ supportsReasoningConfigurationUpdate: modelSupportsReasoningConfigurationUpdate(metadata),
53
+ supportsStructuredOutput: providerApiCanSerializeResponseFormat(metadata),
54
+ supportedThinkingLevels: metadata.supportedThinkingLevels,
55
+ };
56
+ };
57
+ // Image capability filtering (router setting, absent = ON) narrows the
58
+ // candidate set before strategy resolution — an image-bearing conversation
59
+ // must not route (or hold a lease) to a text-only model. Prefix chains and
60
+ // provider accounting still cover every configured model.
61
+ const imageFilter = resolveImageCapableCandidates({
62
+ candidateModels: fundedCandidateModels.length > 0 ? fundedCandidateModels : configuredCandidateModels,
63
+ requiredCapabilities: requiredCapabilitiesFor(args.interaction),
64
+ enabled: args.credentials.image_capability_filtering !== false,
65
+ supportsImageInput: (model) => capabilityLookup(model).supportsImageInput,
66
+ });
67
+ const prefix = derivePrefixContext({
68
+ routerId: args.routerId,
69
+ interaction: args.interaction,
70
+ targetsByModel,
71
+ });
72
+ const promptAccounting = providerPromptAccounting({
73
+ request: args.interaction,
74
+ prefix,
75
+ targetsByModel,
76
+ });
77
+ const pricing = pricingLookupForRouter(args.credentials);
78
+ // A validated harness identity is authoritative. Matching text from another
79
+ // thread must not contribute cache warmth or selector continuity.
80
+ const background = isBackgroundRequest(args.interaction);
81
+ const prefixHits = (background ? [] : args.credentials.prefix_hits ?? []).filter((hit) => args.explicitConversationId === undefined
82
+ || hit.conversation_id === args.explicitConversationId);
83
+ const prepared = prepareCandidates({
84
+ candidateModels: imageFilter.candidateModels,
85
+ metadataLookup: capabilityLookup,
86
+ requiredCapabilities: imageFilter.requiredCapabilities,
87
+ requestedReasoningEffort: args.interaction.reasoning?.effort,
88
+ thinkingEnabled: args.interaction.reasoning?.enabled === true,
89
+ toolChoice: args.interaction.toolChoice,
90
+ modelThinkingLevels: args.credentials.model_thinking_levels,
91
+ strategy: args.credentials.routing_strategy === "custom" ? "custom" : "dari",
92
+ customConfig: args.credentials.custom_config,
93
+ modelPrices: args.credentials.model_prices ?? {},
94
+ pricing,
95
+ promptTokenLimitFor: (model) => effectiveContextWindow(args.credentials.model_configs?.[model], args.credentials.allow_long_context === true),
96
+ requiresPromptEstimateFor: (model) => args.credentials.allow_long_context !== true
97
+ && args.credentials.model_configs?.[model]?.standard_context_limit != null,
98
+ averageOutputTokensByModel: Object.fromEntries(Object.entries(args.credentials.model_configs ?? {}).map(([model, config]) => [
99
+ model,
100
+ config.average_output_tokens ?? null,
101
+ ])),
102
+ promptAccounting,
103
+ chainsByModel: prefix.chainByModel,
104
+ // Resolve returns hits up to the platform's conversation lookback;
105
+ // core narrows them to the warmth window for cost estimation and keeps the
106
+ // rest for conversation identity and the previous decision.
107
+ prefixHits,
108
+ nowMs: (args.receivedAt ?? new Date()).getTime(),
109
+ looseChain: prefix.looseChain,
110
+ conversationId: args.explicitConversationId,
111
+ toolChoiceFp: prefix.fingerprints.tool_choice_fp,
112
+ responseFormatFp: prefix.fingerprints.response_format_fp,
113
+ previousDecision: background ? undefined : args.previousDecision,
114
+ });
115
+ for (const warning of prepared.warnings) {
116
+ console.error("cache cost estimation failed", warning.error);
117
+ }
118
+ const conversationId = args.explicitConversationId
119
+ ?? prepared.conversationId
120
+ ?? randomUUID();
121
+ // A harness identity always produces an explicit conversation id, so it
122
+ // cannot distinguish a brand-new session from a recovered one on its own.
123
+ // Prefix lookup is already scoped to that id; no recovered conversation
124
+ // therefore means this is its first observed turn.
125
+ const isNewConversation = prepared.conversationId === null;
126
+ // Lease context (router setting, absent = ON) feeds the selector the
127
+ // original task and reconstructed lease history. Disabled routers skip the
128
+ // reconstruction entirely so their selector inputs stay byte-identical to
129
+ // the pre-lease-context behavior.
130
+ const leaseHistory = !background && args.credentials.lease_context_enabled !== false
131
+ ? leaseHistoryFromPrefixHits({
132
+ prefixHits,
133
+ conversationId,
134
+ candidates: prepared.candidateResolution.candidates,
135
+ messages: selectorMessages,
136
+ })
137
+ : undefined;
138
+ return {
139
+ leaseDepth,
140
+ hits: prepared.hits,
141
+ reportingCostEstimates: prepared.costEstimates,
142
+ ...(prepared.previousDecision !== undefined
143
+ ? { previousDecision: prepared.previousDecision }
144
+ : {}),
145
+ ...(prepared.activeLease !== undefined ? { activeLease: prepared.activeLease } : {}),
146
+ ...(prepared.pendingLease !== undefined ? { pendingLease: prepared.pendingLease } : {}),
147
+ pricing,
148
+ conversationId,
149
+ isNewConversation,
150
+ identityMatchDepth: prepared.identityMatchDepth,
151
+ ...(prepared.identityHit !== undefined
152
+ ? { identityHit: prepared.identityHit }
153
+ : {}),
154
+ zdr: args.credentials.zdr === true,
155
+ candidateResolution: prepared.candidateResolution,
156
+ ...(imageFilter.prunedModels.length > 0
157
+ ? { imageCapabilityPrunedModels: imageFilter.prunedModels }
158
+ : {}),
159
+ prefix,
160
+ targetsByModel,
161
+ selectorMessages,
162
+ selectorImages,
163
+ ...(leaseHistory !== undefined ? { leaseHistory } : {}),
164
+ providerBlockDepthFor: promptAccounting.providerBlockDepthFor,
165
+ };
166
+ }
167
+ // When the filter is disabled, "image_input" is also dropped from the hard
168
+ // capabilities so core preparation keeps today's pass-through behavior instead
169
+ // of re-applying the same exclusion. When enabled, the capability stays as a
170
+ // core-side invariant over the already-narrowed set.
171
+ function resolveImageCapableCandidates(args) {
172
+ if (!args.requiredCapabilities.includes("image_input")) {
173
+ return {
174
+ candidateModels: args.candidateModels,
175
+ requiredCapabilities: args.requiredCapabilities,
176
+ prunedModels: [],
177
+ };
178
+ }
179
+ if (!args.enabled) {
180
+ return {
181
+ candidateModels: args.candidateModels,
182
+ requiredCapabilities: args.requiredCapabilities.filter((capability) => capability !== "image_input"),
183
+ prunedModels: [],
184
+ };
185
+ }
186
+ const capable = args.candidateModels.filter(args.supportsImageInput);
187
+ if (capable.length === 0) {
188
+ throw new HttpError(400, "This conversation contains image input, but no enabled model on this router accepts images.", "no_image_capable_candidates");
189
+ }
190
+ return {
191
+ candidateModels: capable,
192
+ requiredCapabilities: args.requiredCapabilities,
193
+ prunedModels: args.candidateModels
194
+ .filter((model) => !args.supportsImageInput(model))
195
+ .map((model) => ({ model, reason: "image_input_unsupported" })),
196
+ };
197
+ }
198
+ function derivePrefixContext(args) {
199
+ try {
200
+ return interactionPrefixContext(args.routerId, args.interaction, args.targetsByModel);
201
+ }
202
+ catch (error) {
203
+ console.error("prefix fingerprinting failed", error);
204
+ return {
205
+ chainByModel: new Map([...args.targetsByModel].map(([model]) => [model, []])),
206
+ itemIndexByHashByModel: new Map([...args.targetsByModel].map(([model]) => [model, new Map()])),
207
+ looseChain: [],
208
+ fingerprints: { tool_choice_fp: "", response_format_fp: "" },
209
+ };
210
+ }
211
+ }
@@ -0,0 +1,2 @@
1
+ export declare function bearerToken(headers: Headers): string;
2
+ export declare function anthropicToken(headers: Headers): string;
@@ -0,0 +1,35 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import { HttpError } from "./errors.js";
3
+ export function bearerToken(headers) {
4
+ const value = headers.get("authorization") ?? "";
5
+ const [scheme, token] = value.split(/\s+/, 2);
6
+ if (scheme?.toLowerCase() !== "bearer" || !token) {
7
+ throw new HttpError(401, "Missing or invalid routing API key.", "authentication_error");
8
+ }
9
+ return publicRoutingToken(token);
10
+ }
11
+ export function anthropicToken(headers) {
12
+ const authorization = headers.get("authorization");
13
+ if (authorization)
14
+ return bearerToken(headers);
15
+ const apiKey = headers.get("x-api-key");
16
+ if (!apiKey) {
17
+ throw new HttpError(401, "Missing or invalid routing API key.", "authentication_error");
18
+ }
19
+ return publicRoutingToken(apiKey);
20
+ }
21
+ function publicRoutingToken(token) {
22
+ const internalToken = process.env.DARI_ROUTING_INTERNAL_TOKEN;
23
+ if (internalToken && matchesInternalToken(token, internalToken)) {
24
+ throw new HttpError(401, "Internal routing credentials cannot authorize public inference.", "authentication_error");
25
+ }
26
+ return token;
27
+ }
28
+ function matchesInternalToken(token, internalToken) {
29
+ // Compare byte lengths, not character counts: timingSafeEqual throws on
30
+ // buffers of different sizes, and a multi-byte character can match the
31
+ // secret's character count without matching its byte length.
32
+ const candidate = Buffer.from(token);
33
+ const expected = Buffer.from(internalToken);
34
+ return candidate.length === expected.length && timingSafeEqual(candidate, expected);
35
+ }
@@ -0,0 +1,19 @@
1
+ import { RouterCoreError } from "@mupt-ai/dari-router/policy-engine";
2
+ export declare class HttpError extends Error {
3
+ readonly status: number;
4
+ readonly code: string;
5
+ readonly param?: string;
6
+ readonly headers?: Record<string, string>;
7
+ constructor(status: number, message: string, code?: string, param?: string, headers?: Record<string, string>);
8
+ }
9
+ export declare function errorResponseHeaders(error: unknown): Record<string, string>;
10
+ export declare function httpErrorFrom(error: RouterCoreError): HttpError;
11
+ export declare function rethrowFrameworkErrorAsHttp(error: unknown, status: number): never;
12
+ export declare function errorCode(error: unknown, fallback: string): string;
13
+ export declare function errorResponse(error: unknown): Response;
14
+ export declare function normalizeError(error: unknown): {
15
+ status: number;
16
+ message: string;
17
+ code: string;
18
+ param?: string;
19
+ };
@@ -0,0 +1,89 @@
1
+ import { RouterFrameworkError } from "@mupt-ai/dari-router";
2
+ import { RouterCoreError } from "@mupt-ai/dari-router/policy-engine";
3
+ export class HttpError extends Error {
4
+ status;
5
+ code;
6
+ param;
7
+ // Response headers the error carries to the client, such as retry-after.
8
+ headers;
9
+ constructor(status, message, code = "invalid_request_error", param, headers) {
10
+ super(message);
11
+ this.status = status;
12
+ this.code = code;
13
+ this.param = param;
14
+ this.headers = headers;
15
+ }
16
+ }
17
+ export function errorResponseHeaders(error) {
18
+ return error instanceof HttpError && error.headers ? error.headers : {};
19
+ }
20
+ // @mupt-ai/dari-router is transport-agnostic; its error kinds map onto the HTTP
21
+ // statuses this service always used for the same conditions.
22
+ const CORE_ERROR_STATUS = {
23
+ invalid_request: 400,
24
+ selector_output: 502,
25
+ configuration: 500,
26
+ };
27
+ export function httpErrorFrom(error) {
28
+ return new HttpError(CORE_ERROR_STATUS[error.kind], error.message, error.code, error.param);
29
+ }
30
+ export function rethrowFrameworkErrorAsHttp(error, status) {
31
+ throw error instanceof RouterFrameworkError
32
+ ? new HttpError(status, error.message, error.code, error.param)
33
+ : error;
34
+ }
35
+ // Error code for logging/telemetry, covering both transport and core errors.
36
+ export function errorCode(error, fallback) {
37
+ if (error instanceof HttpError
38
+ || error instanceof RouterCoreError
39
+ || error instanceof RouterFrameworkError) {
40
+ return error.code;
41
+ }
42
+ return fallback;
43
+ }
44
+ export function errorResponse(error) {
45
+ const normalized = normalizeError(error);
46
+ return Response.json({
47
+ error: {
48
+ message: normalized.message,
49
+ type: openAiErrorType(normalized.status),
50
+ code: normalized.code,
51
+ ...(normalized.param ? { param: normalized.param } : {}),
52
+ },
53
+ }, { status: normalized.status, headers: errorResponseHeaders(error) });
54
+ }
55
+ function openAiErrorType(status) {
56
+ if (status === 429)
57
+ return "rate_limit_error";
58
+ return status >= 500 ? "server_error" : "invalid_request_error";
59
+ }
60
+ export function normalizeError(error) {
61
+ if (error instanceof HttpError) {
62
+ return {
63
+ status: error.status,
64
+ message: error.message,
65
+ code: error.code,
66
+ param: error.param,
67
+ };
68
+ }
69
+ if (error instanceof RouterCoreError) {
70
+ return {
71
+ status: CORE_ERROR_STATUS[error.kind],
72
+ message: error.message,
73
+ code: error.code,
74
+ param: error.param,
75
+ };
76
+ }
77
+ if (error instanceof RouterFrameworkError) {
78
+ return {
79
+ status: error.status,
80
+ message: error.message,
81
+ code: error.code,
82
+ param: error.param,
83
+ };
84
+ }
85
+ if (error instanceof Error) {
86
+ return { status: 500, message: error.message, code: "internal_error" };
87
+ }
88
+ return { status: 500, message: "Internal server error", code: "internal_error" };
89
+ }
@@ -0,0 +1,12 @@
1
+ import { type GatewayPlatform } from "../platform/platform.js";
2
+ import type { SelectorBackend } from "../strategies/routing.js";
3
+ export type GatewayHandlerOptions = {
4
+ platform: GatewayPlatform;
5
+ selectorBackend: SelectorBackend;
6
+ };
7
+ /**
8
+ * The gateway's routed HTTP surface: `/v1/{chat/completions,responses,
9
+ * messages,models}` on the default router and `/{router_id}/v1/...` on a
10
+ * named one. Binds `options.platform` for every request it serves.
11
+ */
12
+ export declare function createRequestHandler(options: GatewayHandlerOptions): (request: Request) => Promise<Response>;
@@ -0,0 +1,81 @@
1
+ import { handleResponses, handleResponsesWebSocketFallback, } from "../ingress/codex.js";
2
+ import { handleModels } from "../ingress/models.js";
3
+ import { handleChatCompletions } from "../ingress/chat_completions.js";
4
+ import { handleAnthropicMessages } from "../ingress/anthropic_messages.js";
5
+ import { bindGatewayPlatform } from "../platform/platform.js";
6
+ import { DEFAULT_ROUTER_ALIAS } from "../platform/routing_contract.js";
7
+ import { errorResponse, HttpError } from "./errors.js";
8
+ /**
9
+ * The gateway's routed HTTP surface: `/v1/{chat/completions,responses,
10
+ * messages,models}` on the default router and `/{router_id}/v1/...` on a
11
+ * named one. Binds `options.platform` for every request it serves.
12
+ */
13
+ export function createRequestHandler(options) {
14
+ const { selectorBackend } = options;
15
+ bindGatewayPlatform(options.platform);
16
+ return async function handleRequest(request) {
17
+ const url = new URL(request.url);
18
+ if (request.method === "GET" &&
19
+ (url.pathname === "/healthz" || url.pathname === "/health")) {
20
+ return Response.json({ status: "ok" });
21
+ }
22
+ // Claude Code probes this first-party endpoint before its Messages call
23
+ // when ANTHROPIC_BASE_URL points at a compatible gateway.
24
+ if (request.method === "HEAD" && url.pathname === "/api/hello") {
25
+ return new Response(null, { status: 200 });
26
+ }
27
+ const endpoint = routedEndpoint(url.pathname);
28
+ if (endpoint?.protocol === "chat" && request.method === "POST") {
29
+ return await handleChatCompletions(request, endpoint.routerId, selectorBackend);
30
+ }
31
+ if (endpoint?.protocol === "responses") {
32
+ if (request.method === "GET")
33
+ return handleResponsesWebSocketFallback();
34
+ if (request.method === "POST") {
35
+ return await handleResponses(request, endpoint.routerId, selectorBackend);
36
+ }
37
+ }
38
+ if (endpoint?.protocol === "messages" && request.method === "POST") {
39
+ return await handleAnthropicMessages(request, endpoint.routerId, selectorBackend);
40
+ }
41
+ if (endpoint?.protocol === "models" && request.method === "GET") {
42
+ return await handleModels(request, endpoint.routerId);
43
+ }
44
+ return errorResponse(new HttpError(404, "Not found", "not_found"));
45
+ };
46
+ }
47
+ function routedEndpoint(pathname) {
48
+ const defaultProtocol = protocolForSuffix(pathname, "/v1");
49
+ if (defaultProtocol !== null) {
50
+ return { routerId: DEFAULT_ROUTER_ALIAS, protocol: defaultProtocol };
51
+ }
52
+ const match = pathname.match(/^\/([^/]+)(?:\/v1)?\/(chat\/completions|responses|messages|models)$/);
53
+ if (match === null)
54
+ return null;
55
+ return {
56
+ routerId: match[1],
57
+ protocol: protocolForRoute(match[2]),
58
+ };
59
+ }
60
+ function protocolForSuffix(pathname, prefix) {
61
+ if (pathname === `${prefix}/chat/completions`)
62
+ return "chat";
63
+ if (pathname === `${prefix}/responses`)
64
+ return "responses";
65
+ if (pathname === `${prefix}/messages`)
66
+ return "messages";
67
+ if (pathname === `${prefix}/models`)
68
+ return "models";
69
+ return null;
70
+ }
71
+ function protocolForRoute(route) {
72
+ if (route === "chat/completions")
73
+ return "chat";
74
+ if (route === "responses")
75
+ return "responses";
76
+ if (route === "messages")
77
+ return "messages";
78
+ if (route === "models")
79
+ return "models";
80
+ throw new Error(`Unsupported routed endpoint: ${route}`);
81
+ }
@@ -0,0 +1,6 @@
1
+ export type Http2RequestHandler = (request: Request) => Response | Promise<Response>;
2
+ export type Http2RoutingServer = {
3
+ readonly port: number;
4
+ stop(force?: boolean): Promise<void>;
5
+ };
6
+ export declare function startHttp2Server(port: number, handler: Http2RequestHandler): Http2RoutingServer;
@@ -0,0 +1,103 @@
1
+ import { createServer, } from "node:http2";
2
+ import { Readable } from "node:stream";
3
+ import { pipeline } from "node:stream/promises";
4
+ const HTTP2_FORBIDDEN_RESPONSE_HEADERS = new Set([
5
+ "connection",
6
+ "keep-alive",
7
+ "proxy-connection",
8
+ "transfer-encoding",
9
+ "upgrade",
10
+ ]);
11
+ export function startHttp2Server(port, handler) {
12
+ const sessions = new Set();
13
+ let closePromise;
14
+ const server = createServer((request, response) => {
15
+ void handleRequest(request, response, handler);
16
+ });
17
+ server.on("session", (session) => {
18
+ sessions.add(session);
19
+ session.once("close", () => sessions.delete(session));
20
+ });
21
+ server.listen(port, "0.0.0.0");
22
+ const address = server.address();
23
+ if (address === null || typeof address === "string") {
24
+ throw new Error("HTTP/2 routing server did not bind to a TCP port.");
25
+ }
26
+ return {
27
+ port: address.port,
28
+ stop(force = false) {
29
+ for (const session of sessions) {
30
+ if (force)
31
+ session.destroy();
32
+ else
33
+ session.close();
34
+ }
35
+ closePromise ??= new Promise((resolve, reject) => {
36
+ server.close((error) => {
37
+ if (error)
38
+ reject(error);
39
+ else
40
+ resolve();
41
+ });
42
+ });
43
+ return closePromise;
44
+ },
45
+ };
46
+ }
47
+ async function handleRequest(incoming, outgoing, handler) {
48
+ const abort = new AbortController();
49
+ incoming.once("aborted", () => abort.abort());
50
+ outgoing.once("close", () => {
51
+ if (!outgoing.writableEnded)
52
+ abort.abort();
53
+ });
54
+ try {
55
+ const response = await handler(toRequest(incoming, abort.signal));
56
+ outgoing.statusCode = response.status;
57
+ for (const [name, value] of response.headers) {
58
+ if (!HTTP2_FORBIDDEN_RESPONSE_HEADERS.has(name.toLowerCase())) {
59
+ outgoing.setHeader(name, value);
60
+ }
61
+ }
62
+ if (response.body === null || incoming.method === "HEAD") {
63
+ outgoing.end();
64
+ return;
65
+ }
66
+ await pipeline(Readable.fromWeb(response.body), outgoing);
67
+ }
68
+ catch (error) {
69
+ if (abort.signal.aborted || outgoing.destroyed)
70
+ return;
71
+ console.error("routing HTTP/2 request failed", error);
72
+ if (outgoing.headersSent) {
73
+ outgoing.destroy();
74
+ return;
75
+ }
76
+ outgoing.statusCode = 500;
77
+ outgoing.setHeader("content-type", "application/json");
78
+ outgoing.end(JSON.stringify({ error: { message: "Internal server error." } }));
79
+ }
80
+ }
81
+ function toRequest(incoming, signal) {
82
+ const headers = new Headers();
83
+ for (const [name, value] of Object.entries(incoming.headers)) {
84
+ if (name.startsWith(":"))
85
+ continue;
86
+ if (Array.isArray(value)) {
87
+ for (const item of value)
88
+ headers.append(name, item);
89
+ }
90
+ else if (value !== undefined) {
91
+ headers.set(name, value);
92
+ }
93
+ }
94
+ const method = incoming.method ?? "GET";
95
+ const hasBody = method !== "GET" && method !== "HEAD";
96
+ return new Request(`${incoming.scheme ?? "http"}://${incoming.authority ?? "localhost"}${incoming.url}`, {
97
+ method,
98
+ headers,
99
+ body: hasBody ? Readable.toWeb(incoming) : undefined,
100
+ signal,
101
+ duplex: hasBody ? "half" : undefined,
102
+ });
103
+ }
@@ -0,0 +1,20 @@
1
+ type StoppableServer = {
2
+ stop(force?: boolean): Promise<void>;
3
+ };
4
+ export declare function startServer(listenPort: number, handler: (request: Request) => Promise<Response>, options?: {
5
+ http2?: boolean;
6
+ }): import("./http2_server.js").Http2RoutingServer | Bun.Server<undefined>;
7
+ /**
8
+ * On SIGTERM/SIGINT: stop accepting work, let active requests finish, drain
9
+ * background reporting within the shutdown budget, run `closeClients`, exit.
10
+ */
11
+ export declare function installShutdownHandlers(server: StoppableServer, closeClients: () => Promise<unknown>): void;
12
+ /**
13
+ * stop(false) refuses new work and lets active requests finish; if that does
14
+ * not settle in time, one stop(true) destroys what is left so stream
15
+ * cancellation registers its finalizers before the background drain starts.
16
+ * Both calls are bounded: a hung stop promise must not consume the shutdown
17
+ * budget.
18
+ */
19
+ export declare function stopServerForShutdown(server: StoppableServer, gracefulTimeoutMs?: number, forceStopSettleTimeoutMs?: number): Promise<void>;
20
+ export {};
@@ -0,0 +1,78 @@
1
+ import { captureOperationalError } from "../observability/errors.js";
2
+ import { drainBackgroundTelemetryFor } from "../telemetry/background.js";
3
+ import { startHttp2Server } from "./http2_server.js";
4
+ const GRACEFUL_SERVER_STOP_TIMEOUT_MS = 5_000;
5
+ const FORCE_STOP_SETTLE_TIMEOUT_MS = 250;
6
+ // Container platforms commonly send SIGKILL 10 seconds after SIGTERM. Reserve
7
+ // the final 2 seconds for closing telemetry clients and 500ms for process
8
+ // scheduling.
9
+ const SHUTDOWN_WORK_TIMEOUT_MS = 7_500;
10
+ export function startServer(listenPort, handler, options = {}) {
11
+ if (options.http2) {
12
+ return startHttp2Server(listenPort, handler);
13
+ }
14
+ // The selector runs before the first response byte, and routed models can
15
+ // remain silent while reasoning. Disable Bun's default 10s idle timeout;
16
+ // clients and infrastructure still bound total request lifetime.
17
+ return Bun.serve({
18
+ port: listenPort,
19
+ fetch: handler,
20
+ idleTimeout: 0,
21
+ });
22
+ }
23
+ /**
24
+ * On SIGTERM/SIGINT: stop accepting work, let active requests finish, drain
25
+ * background reporting within the shutdown budget, run `closeClients`, exit.
26
+ */
27
+ export function installShutdownHandlers(server, closeClients) {
28
+ let shuttingDown = false;
29
+ const shutdown = async (signal) => {
30
+ if (shuttingDown)
31
+ return;
32
+ shuttingDown = true;
33
+ const workDeadlineAtMs = performance.now() + SHUTDOWN_WORK_TIMEOUT_MS;
34
+ console.info("routing server shutting down", JSON.stringify({ signal }));
35
+ try {
36
+ await stopServerForShutdown(server);
37
+ }
38
+ finally {
39
+ const remaining = await drainBackgroundTelemetryFor(Math.max(0, workDeadlineAtMs - performance.now()));
40
+ if (remaining > 0) {
41
+ console.warn("routing server background drain timed out", JSON.stringify({ remaining_task_count: remaining }));
42
+ captureOperationalError(new Error(`Routing shutdown abandoned ${remaining} background task(s).`), { code: "background_drain_timeout" });
43
+ }
44
+ await closeClients();
45
+ process.exit(0);
46
+ }
47
+ };
48
+ process.once("SIGTERM", () => void shutdown("SIGTERM"));
49
+ process.once("SIGINT", () => void shutdown("SIGINT"));
50
+ }
51
+ /**
52
+ * stop(false) refuses new work and lets active requests finish; if that does
53
+ * not settle in time, one stop(true) destroys what is left so stream
54
+ * cancellation registers its finalizers before the background drain starts.
55
+ * Both calls are bounded: a hung stop promise must not consume the shutdown
56
+ * budget.
57
+ */
58
+ export async function stopServerForShutdown(server, gracefulTimeoutMs = GRACEFUL_SERVER_STOP_TIMEOUT_MS, forceStopSettleTimeoutMs = FORCE_STOP_SETTLE_TIMEOUT_MS) {
59
+ if ((await stopWithin(server, false, gracefulTimeoutMs)) !== "timed_out") {
60
+ return;
61
+ }
62
+ if ((await stopWithin(server, true, forceStopSettleTimeoutMs)) === "timed_out") {
63
+ reportServerStopFailure(new Error("Routing server force-stop settlement timed out."));
64
+ }
65
+ }
66
+ function stopWithin(server, force, timeoutMs) {
67
+ return Promise.race([
68
+ new Promise((resolve) => resolve(server.stop(force))).then(() => "stopped", (error) => {
69
+ reportServerStopFailure(error);
70
+ return "failed";
71
+ }),
72
+ Bun.sleep(timeoutMs).then(() => "timed_out"),
73
+ ]);
74
+ }
75
+ function reportServerStopFailure(error) {
76
+ console.warn("routing server shutdown failed", error instanceof Error ? error.name : "UnknownError");
77
+ captureOperationalError(error, { code: "server_force_stop_failed" });
78
+ }