@agentuity/runtime 2.0.11 → 3.0.0-alpha.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 (415) hide show
  1. package/dist/index.d.ts +37 -65
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +59 -61
  4. package/dist/index.js.map +1 -1
  5. package/package.json +9 -38
  6. package/src/index.ts +58 -259
  7. package/AGENTS.md +0 -116
  8. package/dist/_config.d.ts +0 -100
  9. package/dist/_config.d.ts.map +0 -1
  10. package/dist/_config.js +0 -147
  11. package/dist/_config.js.map +0 -1
  12. package/dist/_context.d.ts +0 -80
  13. package/dist/_context.d.ts.map +0 -1
  14. package/dist/_context.js +0 -160
  15. package/dist/_context.js.map +0 -1
  16. package/dist/_events.d.ts +0 -64
  17. package/dist/_events.d.ts.map +0 -1
  18. package/dist/_events.js +0 -92
  19. package/dist/_events.js.map +0 -1
  20. package/dist/_globals.d.ts +0 -58
  21. package/dist/_globals.d.ts.map +0 -1
  22. package/dist/_globals.js +0 -71
  23. package/dist/_globals.js.map +0 -1
  24. package/dist/_idle.d.ts +0 -7
  25. package/dist/_idle.d.ts.map +0 -1
  26. package/dist/_idle.js +0 -10
  27. package/dist/_idle.js.map +0 -1
  28. package/dist/_metadata.d.ts +0 -117
  29. package/dist/_metadata.d.ts.map +0 -1
  30. package/dist/_metadata.js +0 -268
  31. package/dist/_metadata.js.map +0 -1
  32. package/dist/_process-protection.d.ts +0 -27
  33. package/dist/_process-protection.d.ts.map +0 -1
  34. package/dist/_process-protection.js +0 -56
  35. package/dist/_process-protection.js.map +0 -1
  36. package/dist/_server.d.ts +0 -50
  37. package/dist/_server.d.ts.map +0 -1
  38. package/dist/_server.js +0 -89
  39. package/dist/_server.js.map +0 -1
  40. package/dist/_services.d.ts +0 -25
  41. package/dist/_services.d.ts.map +0 -1
  42. package/dist/_services.js +0 -286
  43. package/dist/_services.js.map +0 -1
  44. package/dist/_standalone.d.ts +0 -212
  45. package/dist/_standalone.d.ts.map +0 -1
  46. package/dist/_standalone.js +0 -556
  47. package/dist/_standalone.js.map +0 -1
  48. package/dist/_tokens.d.ts +0 -12
  49. package/dist/_tokens.d.ts.map +0 -1
  50. package/dist/_tokens.js +0 -97
  51. package/dist/_tokens.js.map +0 -1
  52. package/dist/_util.d.ts +0 -16
  53. package/dist/_util.d.ts.map +0 -1
  54. package/dist/_util.js +0 -54
  55. package/dist/_util.js.map +0 -1
  56. package/dist/_validation.d.ts +0 -89
  57. package/dist/_validation.d.ts.map +0 -1
  58. package/dist/_validation.js +0 -29
  59. package/dist/_validation.js.map +0 -1
  60. package/dist/_waituntil.d.ts +0 -32
  61. package/dist/_waituntil.d.ts.map +0 -1
  62. package/dist/_waituntil.js +0 -156
  63. package/dist/_waituntil.js.map +0 -1
  64. package/dist/agent.d.ts +0 -1262
  65. package/dist/agent.d.ts.map +0 -1
  66. package/dist/agent.js +0 -981
  67. package/dist/agent.js.map +0 -1
  68. package/dist/app.d.ts +0 -514
  69. package/dist/app.d.ts.map +0 -1
  70. package/dist/app.js +0 -228
  71. package/dist/app.js.map +0 -1
  72. package/dist/bootstrap.d.ts +0 -44
  73. package/dist/bootstrap.d.ts.map +0 -1
  74. package/dist/bootstrap.js +0 -259
  75. package/dist/bootstrap.js.map +0 -1
  76. package/dist/bun-s3-patch.d.ts +0 -37
  77. package/dist/bun-s3-patch.d.ts.map +0 -1
  78. package/dist/bun-s3-patch.js +0 -142
  79. package/dist/bun-s3-patch.js.map +0 -1
  80. package/dist/cors.d.ts +0 -42
  81. package/dist/cors.d.ts.map +0 -1
  82. package/dist/cors.js +0 -117
  83. package/dist/cors.js.map +0 -1
  84. package/dist/dev-patches/aisdk.d.ts +0 -17
  85. package/dist/dev-patches/aisdk.d.ts.map +0 -1
  86. package/dist/dev-patches/aisdk.js +0 -160
  87. package/dist/dev-patches/aisdk.js.map +0 -1
  88. package/dist/dev-patches/gateway.d.ts +0 -16
  89. package/dist/dev-patches/gateway.d.ts.map +0 -1
  90. package/dist/dev-patches/gateway.js +0 -54
  91. package/dist/dev-patches/gateway.js.map +0 -1
  92. package/dist/dev-patches/index.d.ts +0 -21
  93. package/dist/dev-patches/index.d.ts.map +0 -1
  94. package/dist/dev-patches/index.js +0 -33
  95. package/dist/dev-patches/index.js.map +0 -1
  96. package/dist/dev-patches/otel-llm.d.ts +0 -12
  97. package/dist/dev-patches/otel-llm.d.ts.map +0 -1
  98. package/dist/dev-patches/otel-llm.js +0 -352
  99. package/dist/dev-patches/otel-llm.js.map +0 -1
  100. package/dist/devmode.d.ts +0 -3
  101. package/dist/devmode.d.ts.map +0 -1
  102. package/dist/devmode.js +0 -167
  103. package/dist/devmode.js.map +0 -1
  104. package/dist/eval.d.ts +0 -91
  105. package/dist/eval.d.ts.map +0 -1
  106. package/dist/eval.js +0 -16
  107. package/dist/eval.js.map +0 -1
  108. package/dist/handlers/_route-meta.d.ts +0 -22
  109. package/dist/handlers/_route-meta.d.ts.map +0 -1
  110. package/dist/handlers/_route-meta.js +0 -25
  111. package/dist/handlers/_route-meta.js.map +0 -1
  112. package/dist/handlers/cron.d.ts +0 -73
  113. package/dist/handlers/cron.d.ts.map +0 -1
  114. package/dist/handlers/cron.js +0 -43
  115. package/dist/handlers/cron.js.map +0 -1
  116. package/dist/handlers/index.d.ts +0 -6
  117. package/dist/handlers/index.d.ts.map +0 -1
  118. package/dist/handlers/index.js +0 -6
  119. package/dist/handlers/index.js.map +0 -1
  120. package/dist/handlers/sse.d.ts +0 -163
  121. package/dist/handlers/sse.d.ts.map +0 -1
  122. package/dist/handlers/sse.js +0 -175
  123. package/dist/handlers/sse.js.map +0 -1
  124. package/dist/handlers/stream.d.ts +0 -52
  125. package/dist/handlers/stream.d.ts.map +0 -1
  126. package/dist/handlers/stream.js +0 -108
  127. package/dist/handlers/stream.js.map +0 -1
  128. package/dist/handlers/webrtc.d.ts +0 -49
  129. package/dist/handlers/webrtc.d.ts.map +0 -1
  130. package/dist/handlers/webrtc.js +0 -109
  131. package/dist/handlers/webrtc.js.map +0 -1
  132. package/dist/handlers/websocket.d.ts +0 -88
  133. package/dist/handlers/websocket.d.ts.map +0 -1
  134. package/dist/handlers/websocket.js +0 -161
  135. package/dist/handlers/websocket.js.map +0 -1
  136. package/dist/logger/console.d.ts +0 -70
  137. package/dist/logger/console.d.ts.map +0 -1
  138. package/dist/logger/console.js +0 -278
  139. package/dist/logger/console.js.map +0 -1
  140. package/dist/logger/index.d.ts +0 -3
  141. package/dist/logger/index.d.ts.map +0 -1
  142. package/dist/logger/index.js +0 -3
  143. package/dist/logger/index.js.map +0 -1
  144. package/dist/logger/internal.d.ts +0 -79
  145. package/dist/logger/internal.d.ts.map +0 -1
  146. package/dist/logger/internal.js +0 -133
  147. package/dist/logger/internal.js.map +0 -1
  148. package/dist/logger/logger.d.ts +0 -41
  149. package/dist/logger/logger.d.ts.map +0 -1
  150. package/dist/logger/logger.js +0 -2
  151. package/dist/logger/logger.js.map +0 -1
  152. package/dist/logger/user.d.ts +0 -8
  153. package/dist/logger/user.d.ts.map +0 -1
  154. package/dist/logger/user.js +0 -7
  155. package/dist/logger/user.js.map +0 -1
  156. package/dist/logger/util.d.ts +0 -11
  157. package/dist/logger/util.d.ts.map +0 -1
  158. package/dist/logger/util.js +0 -77
  159. package/dist/logger/util.js.map +0 -1
  160. package/dist/middleware.d.ts +0 -105
  161. package/dist/middleware.d.ts.map +0 -1
  162. package/dist/middleware.js +0 -763
  163. package/dist/middleware.js.map +0 -1
  164. package/dist/otel/config.d.ts +0 -19
  165. package/dist/otel/config.d.ts.map +0 -1
  166. package/dist/otel/config.js +0 -26
  167. package/dist/otel/config.js.map +0 -1
  168. package/dist/otel/console.d.ts +0 -33
  169. package/dist/otel/console.d.ts.map +0 -1
  170. package/dist/otel/console.js +0 -86
  171. package/dist/otel/console.js.map +0 -1
  172. package/dist/otel/exporters/index.d.ts +0 -4
  173. package/dist/otel/exporters/index.d.ts.map +0 -1
  174. package/dist/otel/exporters/index.js +0 -4
  175. package/dist/otel/exporters/index.js.map +0 -1
  176. package/dist/otel/exporters/jsonl-log-exporter.d.ts +0 -36
  177. package/dist/otel/exporters/jsonl-log-exporter.d.ts.map +0 -1
  178. package/dist/otel/exporters/jsonl-log-exporter.js +0 -103
  179. package/dist/otel/exporters/jsonl-log-exporter.js.map +0 -1
  180. package/dist/otel/exporters/jsonl-metric-exporter.d.ts +0 -40
  181. package/dist/otel/exporters/jsonl-metric-exporter.d.ts.map +0 -1
  182. package/dist/otel/exporters/jsonl-metric-exporter.js +0 -104
  183. package/dist/otel/exporters/jsonl-metric-exporter.js.map +0 -1
  184. package/dist/otel/exporters/jsonl-trace-exporter.d.ts +0 -36
  185. package/dist/otel/exporters/jsonl-trace-exporter.d.ts.map +0 -1
  186. package/dist/otel/exporters/jsonl-trace-exporter.js +0 -111
  187. package/dist/otel/exporters/jsonl-trace-exporter.js.map +0 -1
  188. package/dist/otel/fetch.d.ts +0 -12
  189. package/dist/otel/fetch.d.ts.map +0 -1
  190. package/dist/otel/fetch.js +0 -82
  191. package/dist/otel/fetch.js.map +0 -1
  192. package/dist/otel/http.d.ts +0 -16
  193. package/dist/otel/http.d.ts.map +0 -1
  194. package/dist/otel/http.js +0 -44
  195. package/dist/otel/http.js.map +0 -1
  196. package/dist/otel/logger.d.ts +0 -37
  197. package/dist/otel/logger.d.ts.map +0 -1
  198. package/dist/otel/logger.js +0 -265
  199. package/dist/otel/logger.js.map +0 -1
  200. package/dist/otel/otel.d.ts +0 -68
  201. package/dist/otel/otel.d.ts.map +0 -1
  202. package/dist/otel/otel.js +0 -245
  203. package/dist/otel/otel.js.map +0 -1
  204. package/dist/otel/tracestate.d.ts +0 -44
  205. package/dist/otel/tracestate.d.ts.map +0 -1
  206. package/dist/otel/tracestate.js +0 -84
  207. package/dist/otel/tracestate.js.map +0 -1
  208. package/dist/router.d.ts +0 -66
  209. package/dist/router.d.ts.map +0 -1
  210. package/dist/router.js +0 -44
  211. package/dist/router.js.map +0 -1
  212. package/dist/services/evalrun/composite.d.ts +0 -21
  213. package/dist/services/evalrun/composite.d.ts.map +0 -1
  214. package/dist/services/evalrun/composite.js +0 -26
  215. package/dist/services/evalrun/composite.js.map +0 -1
  216. package/dist/services/evalrun/http.d.ts +0 -24
  217. package/dist/services/evalrun/http.d.ts.map +0 -1
  218. package/dist/services/evalrun/http.js +0 -115
  219. package/dist/services/evalrun/http.js.map +0 -1
  220. package/dist/services/evalrun/index.d.ts +0 -5
  221. package/dist/services/evalrun/index.d.ts.map +0 -1
  222. package/dist/services/evalrun/index.js +0 -5
  223. package/dist/services/evalrun/index.js.map +0 -1
  224. package/dist/services/evalrun/json.d.ts +0 -21
  225. package/dist/services/evalrun/json.d.ts.map +0 -1
  226. package/dist/services/evalrun/json.js +0 -38
  227. package/dist/services/evalrun/json.js.map +0 -1
  228. package/dist/services/evalrun/local.d.ts +0 -19
  229. package/dist/services/evalrun/local.d.ts.map +0 -1
  230. package/dist/services/evalrun/local.js +0 -22
  231. package/dist/services/evalrun/local.js.map +0 -1
  232. package/dist/services/local/_db.d.ts +0 -4
  233. package/dist/services/local/_db.d.ts.map +0 -1
  234. package/dist/services/local/_db.js +0 -281
  235. package/dist/services/local/_db.js.map +0 -1
  236. package/dist/services/local/_router.d.ts +0 -3
  237. package/dist/services/local/_router.d.ts.map +0 -1
  238. package/dist/services/local/_router.js +0 -28
  239. package/dist/services/local/_router.js.map +0 -1
  240. package/dist/services/local/_util.d.ts +0 -18
  241. package/dist/services/local/_util.d.ts.map +0 -1
  242. package/dist/services/local/_util.js +0 -44
  243. package/dist/services/local/_util.js.map +0 -1
  244. package/dist/services/local/email.d.ts +0 -24
  245. package/dist/services/local/email.d.ts.map +0 -1
  246. package/dist/services/local/email.js +0 -58
  247. package/dist/services/local/email.js.map +0 -1
  248. package/dist/services/local/index.d.ts +0 -10
  249. package/dist/services/local/index.d.ts.map +0 -1
  250. package/dist/services/local/index.js +0 -10
  251. package/dist/services/local/index.js.map +0 -1
  252. package/dist/services/local/keyvalue.d.ts +0 -17
  253. package/dist/services/local/keyvalue.d.ts.map +0 -1
  254. package/dist/services/local/keyvalue.js +0 -133
  255. package/dist/services/local/keyvalue.js.map +0 -1
  256. package/dist/services/local/queue.d.ts +0 -10
  257. package/dist/services/local/queue.d.ts.map +0 -1
  258. package/dist/services/local/queue.js +0 -96
  259. package/dist/services/local/queue.js.map +0 -1
  260. package/dist/services/local/stream.d.ts +0 -12
  261. package/dist/services/local/stream.d.ts.map +0 -1
  262. package/dist/services/local/stream.js +0 -266
  263. package/dist/services/local/stream.js.map +0 -1
  264. package/dist/services/local/task.d.ts +0 -55
  265. package/dist/services/local/task.d.ts.map +0 -1
  266. package/dist/services/local/task.js +0 -1248
  267. package/dist/services/local/task.js.map +0 -1
  268. package/dist/services/local/vector.d.ts +0 -17
  269. package/dist/services/local/vector.d.ts.map +0 -1
  270. package/dist/services/local/vector.js +0 -303
  271. package/dist/services/local/vector.js.map +0 -1
  272. package/dist/services/sandbox/http.d.ts +0 -23
  273. package/dist/services/sandbox/http.d.ts.map +0 -1
  274. package/dist/services/sandbox/http.js +0 -327
  275. package/dist/services/sandbox/http.js.map +0 -1
  276. package/dist/services/sandbox/index.d.ts +0 -2
  277. package/dist/services/sandbox/index.d.ts.map +0 -1
  278. package/dist/services/sandbox/index.js +0 -2
  279. package/dist/services/sandbox/index.js.map +0 -1
  280. package/dist/services/session/composite.d.ts +0 -21
  281. package/dist/services/session/composite.d.ts.map +0 -1
  282. package/dist/services/session/composite.js +0 -26
  283. package/dist/services/session/composite.js.map +0 -1
  284. package/dist/services/session/http.d.ts +0 -34
  285. package/dist/services/session/http.d.ts.map +0 -1
  286. package/dist/services/session/http.js +0 -124
  287. package/dist/services/session/http.js.map +0 -1
  288. package/dist/services/session/index.d.ts +0 -5
  289. package/dist/services/session/index.d.ts.map +0 -1
  290. package/dist/services/session/index.js +0 -5
  291. package/dist/services/session/index.js.map +0 -1
  292. package/dist/services/session/json.d.ts +0 -22
  293. package/dist/services/session/json.d.ts.map +0 -1
  294. package/dist/services/session/json.js +0 -35
  295. package/dist/services/session/json.js.map +0 -1
  296. package/dist/services/session/local.d.ts +0 -19
  297. package/dist/services/session/local.d.ts.map +0 -1
  298. package/dist/services/session/local.js +0 -23
  299. package/dist/services/session/local.js.map +0 -1
  300. package/dist/services/thread/local.d.ts +0 -20
  301. package/dist/services/thread/local.d.ts.map +0 -1
  302. package/dist/services/thread/local.js +0 -158
  303. package/dist/services/thread/local.js.map +0 -1
  304. package/dist/session.d.ts +0 -734
  305. package/dist/session.d.ts.map +0 -1
  306. package/dist/session.js +0 -1140
  307. package/dist/session.js.map +0 -1
  308. package/dist/signature.d.ts +0 -22
  309. package/dist/signature.d.ts.map +0 -1
  310. package/dist/signature.js +0 -63
  311. package/dist/signature.js.map +0 -1
  312. package/dist/validator.d.ts +0 -142
  313. package/dist/validator.d.ts.map +0 -1
  314. package/dist/validator.js +0 -149
  315. package/dist/validator.js.map +0 -1
  316. package/dist/version-check.d.ts +0 -20
  317. package/dist/version-check.d.ts.map +0 -1
  318. package/dist/version-check.js +0 -157
  319. package/dist/version-check.js.map +0 -1
  320. package/dist/web.d.ts +0 -8
  321. package/dist/web.d.ts.map +0 -1
  322. package/dist/web.js +0 -67
  323. package/dist/web.js.map +0 -1
  324. package/dist/webrtc-signaling.d.ts +0 -80
  325. package/dist/webrtc-signaling.d.ts.map +0 -1
  326. package/dist/webrtc-signaling.js +0 -237
  327. package/dist/webrtc-signaling.js.map +0 -1
  328. package/dist/workbench.d.ts +0 -17
  329. package/dist/workbench.d.ts.map +0 -1
  330. package/dist/workbench.js +0 -605
  331. package/dist/workbench.js.map +0 -1
  332. package/src/_config.ts +0 -163
  333. package/src/_context.ts +0 -240
  334. package/src/_events.ts +0 -142
  335. package/src/_globals.ts +0 -92
  336. package/src/_idle.ts +0 -10
  337. package/src/_metadata.ts +0 -407
  338. package/src/_process-protection.ts +0 -71
  339. package/src/_server.ts +0 -109
  340. package/src/_services.ts +0 -379
  341. package/src/_standalone.ts +0 -710
  342. package/src/_tokens.ts +0 -114
  343. package/src/_util.ts +0 -62
  344. package/src/_validation.ts +0 -119
  345. package/src/_waituntil.ts +0 -188
  346. package/src/agent.ts +0 -2739
  347. package/src/app.ts +0 -769
  348. package/src/bootstrap.ts +0 -321
  349. package/src/bun-s3-patch.ts +0 -224
  350. package/src/cors.ts +0 -137
  351. package/src/dev-patches/aisdk.ts +0 -169
  352. package/src/dev-patches/gateway.ts +0 -68
  353. package/src/dev-patches/index.ts +0 -37
  354. package/src/dev-patches/otel-llm.ts +0 -405
  355. package/src/devmode.ts +0 -171
  356. package/src/eval.ts +0 -109
  357. package/src/globals.d.ts +0 -28
  358. package/src/handlers/_route-meta.ts +0 -33
  359. package/src/handlers/cron.ts +0 -141
  360. package/src/handlers/index.ts +0 -18
  361. package/src/handlers/sse.ts +0 -358
  362. package/src/handlers/stream.ts +0 -121
  363. package/src/handlers/webrtc.ts +0 -125
  364. package/src/handlers/websocket.ts +0 -203
  365. package/src/logger/console.ts +0 -323
  366. package/src/logger/index.ts +0 -2
  367. package/src/logger/internal.ts +0 -165
  368. package/src/logger/logger.ts +0 -44
  369. package/src/logger/user.ts +0 -15
  370. package/src/logger/util.ts +0 -80
  371. package/src/middleware.ts +0 -1095
  372. package/src/otel/config.ts +0 -47
  373. package/src/otel/console.ts +0 -91
  374. package/src/otel/exporters/README.md +0 -217
  375. package/src/otel/exporters/index.ts +0 -3
  376. package/src/otel/exporters/jsonl-log-exporter.ts +0 -113
  377. package/src/otel/exporters/jsonl-metric-exporter.ts +0 -120
  378. package/src/otel/exporters/jsonl-trace-exporter.ts +0 -121
  379. package/src/otel/fetch.ts +0 -105
  380. package/src/otel/http.ts +0 -53
  381. package/src/otel/logger.ts +0 -293
  382. package/src/otel/otel.ts +0 -354
  383. package/src/otel/tracestate.ts +0 -108
  384. package/src/router.ts +0 -75
  385. package/src/services/evalrun/composite.ts +0 -34
  386. package/src/services/evalrun/http.ts +0 -167
  387. package/src/services/evalrun/index.ts +0 -4
  388. package/src/services/evalrun/json.ts +0 -46
  389. package/src/services/evalrun/local.ts +0 -28
  390. package/src/services/local/README.md +0 -1576
  391. package/src/services/local/_db.ts +0 -353
  392. package/src/services/local/_router.ts +0 -40
  393. package/src/services/local/_util.ts +0 -55
  394. package/src/services/local/email.ts +0 -91
  395. package/src/services/local/index.ts +0 -9
  396. package/src/services/local/keyvalue.ts +0 -174
  397. package/src/services/local/queue.ts +0 -145
  398. package/src/services/local/stream.ts +0 -358
  399. package/src/services/local/task.ts +0 -1711
  400. package/src/services/local/vector.ts +0 -438
  401. package/src/services/sandbox/http.ts +0 -522
  402. package/src/services/sandbox/index.ts +0 -1
  403. package/src/services/session/composite.ts +0 -33
  404. package/src/services/session/http.ts +0 -167
  405. package/src/services/session/index.ts +0 -4
  406. package/src/services/session/json.ts +0 -42
  407. package/src/services/session/local.ts +0 -33
  408. package/src/services/thread/local.ts +0 -199
  409. package/src/session.ts +0 -1960
  410. package/src/signature.ts +0 -82
  411. package/src/validator.ts +0 -283
  412. package/src/version-check.ts +0 -184
  413. package/src/web.ts +0 -76
  414. package/src/webrtc-signaling.ts +0 -288
  415. package/src/workbench.ts +0 -725
package/src/middleware.ts DELETED
@@ -1,1095 +0,0 @@
1
- /**
2
- * Middleware factories for Vite-native architecture
3
- * Extracted from _server.ts to be used by generated entry files
4
- */
5
-
6
- import { createMiddleware } from 'hono/factory';
7
- import { cors } from 'hono/cors';
8
- import { compress } from 'hono/compress';
9
- import { setSignedCookie } from 'hono/cookie';
10
- import type { Env, CompressionConfig, CorsConfig } from './app';
11
- import { createTrustedCorsOrigin } from './cors';
12
- import type { Logger } from './logger';
13
-
14
- import { generateId } from './session';
15
- import { runInHTTPContext } from './_context';
16
- import { DURATION_HEADER, TOKENS_HEADER } from './_tokens';
17
- import { extractTraceContextFromRequest } from './otel/http';
18
- import { enrichContextWithTraceState } from './otel/tracestate';
19
- import { context, SpanKind, SpanStatusCode, trace, propagation } from '@opentelemetry/api';
20
- import type { Meter, Tracer } from '@opentelemetry/api';
21
- import * as runtimeConfig from './_config';
22
- import { getSessionEventProvider } from './_services';
23
- import { internal } from './logger/internal';
24
- import { STREAM_DONE_PROMISE_KEY, IS_STREAMING_RESPONSE_KEY } from './handlers/sse';
25
- import { WS_DONE_PROMISE_KEY } from './handlers/websocket';
26
- import { loadBuildMetadata } from './_metadata';
27
-
28
- const SESSION_HEADER = 'x-session-id';
29
- const THREAD_HEADER = 'x-thread-id';
30
- const DEPLOYMENT_HEADER = 'x-deployment';
31
-
32
- /**
33
- * Paths that should skip OTEL session event tracking.
34
- * These routes still get thread/session setup but won't create session start/complete events.
35
- */
36
- const OTEL_SESSION_EVENT_SKIP_PATHS = new Set([
37
- '/_agentuity/workbench/ws',
38
- '/_agentuity/workbench/sample',
39
- '/_agentuity/workbench/state',
40
- '/_agentuity/workbench/metadata.json',
41
- '/_agentuity/webanalytics/analytics.js',
42
- '/_agentuity/webanalytics/session.js',
43
- ]);
44
-
45
- /**
46
- * Paths that should skip thread/session setup entirely.
47
- * These are lightweight endpoints that don't need any context.
48
- */
49
- const OTEL_FULL_SKIP_PATHS = new Set([
50
- '/_agentuity/workbench/metadata.json',
51
- '/_agentuity/webanalytics/analytics.js',
52
- '/_agentuity/webanalytics/session.js',
53
- ]);
54
-
55
- export const AGENT_CONTEXT_PROPERTIES = [
56
- 'logger',
57
- 'tracer',
58
- 'sessionId',
59
- 'kv',
60
- 'stream',
61
- 'vector',
62
- 'sandbox',
63
- 'queue',
64
- 'email',
65
- 'schedule',
66
- 'task',
67
- 'state',
68
- 'thread',
69
- 'session',
70
- 'config',
71
- 'app',
72
- ] as const;
73
-
74
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
- function installContextPropertyHelpers(c: any): void {
76
- for (const property of AGENT_CONTEXT_PROPERTIES) {
77
- if (Object.hasOwn(c, property)) {
78
- continue;
79
- }
80
-
81
- Object.defineProperty(c, property, {
82
- get() {
83
- throw new Error(
84
- `In route handlers, use c.var.${property} instead of c.${property}. ` +
85
- `The property '${property}' is available on AgentContext (for agent handlers) ` +
86
- `but must be accessed via c.var in HonoContext (route handlers).`
87
- );
88
- },
89
- configurable: true,
90
- enumerable: false,
91
- });
92
- }
93
- }
94
-
95
- export interface MiddlewareConfig {
96
- logger: Logger;
97
- tracer: Tracer;
98
- meter: Meter;
99
- corsOptions?: Parameters<typeof cors>[0];
100
- }
101
-
102
- /**
103
- * Create base middleware that sets up context variables
104
- */
105
- export function createBaseMiddleware(config: MiddlewareConfig) {
106
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
- return createMiddleware<Env<any>>(async (c, next) => {
108
- c.set('logger', config.logger);
109
- c.set('tracer', config.tracer);
110
- c.set('meter', config.meter);
111
-
112
- // Import services dynamically to avoid circular deps
113
- const { getServices } = await import('./_services');
114
- const services = getServices();
115
- c.set('kv', services.kv);
116
- c.set('stream', services.stream);
117
- c.set('vector', services.vector);
118
- c.set('sandbox', services.sandbox);
119
- c.set('queue', services.queue);
120
- c.set('email', services.email);
121
- c.set('schedule', services.schedule);
122
- c.set('task', services.task);
123
-
124
- installContextPropertyHelpers(c);
125
-
126
- const isWebSocket = c.req.header('upgrade')?.toLowerCase() === 'websocket';
127
- const skipLogging = c.req.path.startsWith('/_agentuity/');
128
- const started = performance.now();
129
-
130
- if (!skipLogging) {
131
- config.logger.debug('%s %s started', c.req.method, c.req.path);
132
- }
133
-
134
- await runInHTTPContext(c, next);
135
-
136
- if (!isWebSocket) {
137
- const endTime = performance.now();
138
- const duration = ((endTime - started) / 1000).toFixed(1);
139
- c.header(DURATION_HEADER, `${duration}s`);
140
-
141
- // Set deployment header for all routes
142
- const deploymentId = runtimeConfig.getDeploymentId();
143
- if (deploymentId) {
144
- c.header(DEPLOYMENT_HEADER, deploymentId);
145
- }
146
- }
147
-
148
- if (!skipLogging && !isWebSocket) {
149
- config.logger.debug(
150
- '%s %s completed (%d) in %sms',
151
- c.req.method,
152
- c.req.path,
153
- c.res.status,
154
- Number(performance.now() - started).toFixed(2)
155
- );
156
- }
157
- });
158
- }
159
-
160
- /**
161
- * Create CORS middleware with lazy config resolution.
162
- *
163
- * Handles Cross-Origin Resource Sharing (CORS) headers for API routes.
164
- * Config is resolved at request time, allowing it to be set via createApp().
165
- * Static options passed here take precedence over app config.
166
- *
167
- * Default behavior:
168
- * - Reflects the request origin (allows any origin)
169
- * - Allows common headers: Content-Type, Authorization, Accept, Origin, X-Requested-With
170
- * - Allows all standard HTTP methods
171
- * - Enables credentials
172
- * - Sets max-age to 600 seconds (10 minutes)
173
- *
174
- * @param staticOptions - Optional static CORS options that override app config
175
- *
176
- * @example
177
- * ```typescript
178
- * // Use with default settings
179
- * app.use('/api/*', createCorsMiddleware());
180
- *
181
- * // Or configure via createApp
182
- * const app = await createApp({
183
- * cors: {
184
- * origin: 'https://example.com',
185
- * allowHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header'],
186
- * maxAge: 3600,
187
- * }
188
- * });
189
- *
190
- * // Or pass static options directly (overrides app config)
191
- * app.use('/api/*', createCorsMiddleware({
192
- * origin: ['https://app.example.com', 'https://admin.example.com'],
193
- * credentials: true,
194
- * }));
195
- * ```
196
- */
197
- export function createCorsMiddleware(staticOptions?: CorsConfig) {
198
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
- return createMiddleware<Env<any>>(async (c, next) => {
200
- const corsOptions = { ...staticOptions };
201
-
202
- // Extract Agentuity-specific options
203
- const { sameOrigin, allowedOrigins, ...honoCorsOptions } = corsOptions;
204
-
205
- // Determine origin handler based on sameOrigin setting
206
- let originHandler: NonNullable<Parameters<typeof cors>[0]>['origin'];
207
- if (sameOrigin) {
208
- // Use trusted origins (env vars + allowedOrigins + same-origin)
209
- originHandler = createTrustedCorsOrigin({ allowedOrigins });
210
- } else if (honoCorsOptions.origin !== undefined) {
211
- // Use explicitly provided origin
212
- originHandler = honoCorsOptions.origin;
213
- } else {
214
- // Default: reflect any origin (backwards compatible)
215
- originHandler = (origin: string) => origin;
216
- }
217
-
218
- // Required headers that must always be allowed/exposed for runtime functionality
219
- const requiredAllowHeaders = [THREAD_HEADER];
220
- const requiredExposeHeaders = [
221
- TOKENS_HEADER,
222
- DURATION_HEADER,
223
- THREAD_HEADER,
224
- SESSION_HEADER,
225
- DEPLOYMENT_HEADER,
226
- ];
227
-
228
- // Default headers to allow (used if none specified)
229
- const defaultAllowHeaders = [
230
- 'Content-Type',
231
- 'Authorization',
232
- 'Accept',
233
- 'Origin',
234
- 'X-Requested-With',
235
- ];
236
-
237
- // Default headers to expose (used if none specified)
238
- const defaultExposeHeaders = ['Content-Length'];
239
-
240
- const finalAllowHeaders = [
241
- ...(honoCorsOptions.allowHeaders ?? defaultAllowHeaders),
242
- ...requiredAllowHeaders,
243
- ];
244
-
245
- const corsMiddleware = cors({
246
- ...honoCorsOptions,
247
- origin: originHandler,
248
- // Always include required headers, merge with user-provided or defaults
249
- allowHeaders: finalAllowHeaders,
250
- allowMethods: honoCorsOptions.allowMethods ?? [
251
- 'POST',
252
- 'GET',
253
- 'OPTIONS',
254
- 'HEAD',
255
- 'PUT',
256
- 'DELETE',
257
- 'PATCH',
258
- ],
259
- // Always include required headers, merge with user-provided or defaults
260
- exposeHeaders: [
261
- ...(honoCorsOptions.exposeHeaders ?? defaultExposeHeaders),
262
- ...requiredExposeHeaders,
263
- ],
264
- maxAge: honoCorsOptions.maxAge ?? 600,
265
- credentials: honoCorsOptions.credentials ?? originHandler !== '*',
266
- });
267
-
268
- return corsMiddleware(c, next);
269
- });
270
- }
271
-
272
- /**
273
- * Create OpenTelemetry middleware for session/thread tracking
274
- * This is the critical middleware that creates AgentContext
275
- */
276
- export function createOtelMiddleware() {
277
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
278
- return createMiddleware<Env<any>>(async (c, next) => {
279
- // Skip thread/session setup entirely for lightweight endpoints
280
- if (OTEL_FULL_SKIP_PATHS.has(c.req.path)) {
281
- return next();
282
- }
283
-
284
- // Check if we should skip session events (but still set up thread/session)
285
- const skipSessionEvents = OTEL_SESSION_EVENT_SKIP_PATHS.has(c.req.path);
286
-
287
- // Import providers dynamically to avoid circular deps
288
- const { getThreadProvider, getSessionProvider } = await import('./_services');
289
- const WaitUntilHandler = (await import('./_waituntil')).default;
290
-
291
- const extractedContext = extractTraceContextFromRequest(c.req.raw);
292
- const method = c.req.method;
293
- const url = new URL(c.req.url);
294
- const threadProvider = getThreadProvider();
295
- const sessionProvider = getSessionProvider();
296
-
297
- await context.with(extractedContext, async (): Promise<void> => {
298
- const tracer = trace.getTracer('http-server');
299
-
300
- // Build enriched traceState BEFORE span creation so the
301
- // recording span inherits it and it gets exported to OTLP.
302
- // Previously, traceState was set on a NonRecordingSpan *after*
303
- // the recording span was created, which meant it was propagated
304
- // to outbound requests but never appeared in the exported span
305
- // data (ClickHouse TraceState column was empty).
306
- const projectId = runtimeConfig.getProjectId();
307
- const orgId = runtimeConfig.getOrganizationId();
308
- const deploymentId = runtimeConfig.getDeploymentId();
309
- const isDevMode = runtimeConfig.isDevMode();
310
-
311
- internal.info(
312
- '[session] config: orgId=%s, projectId=%s, deploymentId=%s, isDevMode=%s',
313
- orgId ?? 'NOT SET (AGENTUITY_CLOUD_ORG_ID)',
314
- projectId ?? 'NOT SET (AGENTUITY_CLOUD_PROJECT_ID)',
315
- deploymentId ?? 'none',
316
- isDevMode
317
- );
318
-
319
- const enrichedContext = enrichContextWithTraceState(context.active(), {
320
- pid: projectId,
321
- oid: orgId,
322
- did: deploymentId,
323
- d: isDevMode ? '1' : undefined,
324
- });
325
-
326
- await tracer.startActiveSpan(
327
- `${method} ${url.pathname}`,
328
- {
329
- kind: SpanKind.SERVER,
330
- attributes: {
331
- 'http.method': method,
332
- 'http.host': url.host,
333
- 'http.user_agent': c.req.header('user-agent') || '',
334
- 'http.path': url.pathname,
335
- },
336
- },
337
- enrichedContext,
338
- async (span): Promise<void> => {
339
- // Track request duration from the SDK's perspective
340
- const requestStartTime = performance.now();
341
- const sctx = span.spanContext();
342
- const sessionId = sctx?.traceId ? `sess_${sctx.traceId}` : generateId('sess');
343
-
344
- const thread = await threadProvider.restore(c);
345
- const session = await sessionProvider.restore(thread, sessionId);
346
- const handler = new WaitUntilHandler(tracer);
347
- const isWsUpgrade = c.req.header('upgrade')?.toLowerCase() === 'websocket';
348
-
349
- c.set('sessionId', sessionId);
350
- c.set('thread', thread);
351
- c.set('session', session);
352
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
353
- (c as any).set('waitUntilHandler', handler);
354
- const agentIds = new Set<string>();
355
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
356
- (c as any).set('agentIds', agentIds);
357
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
358
- (c as any).set('trigger', isWsUpgrade ? 'websocket' : 'api');
359
-
360
- // Send session start event (so evalruns can reference this session)
361
- // The provider decides whether to send based on available data (orgId, projectId, etc.)
362
- // Skip for workbench routes that don't need session tracking
363
- const sessionEventProvider = getSessionEventProvider();
364
- if (sessionEventProvider && !skipSessionEvents) {
365
- try {
366
- // Look up routeId from build metadata by matching method and path
367
- // We need to do this here because the router wrapper hasn't run yet
368
- const metadata = loadBuildMetadata();
369
- const methodUpper = c.req.method.toUpperCase();
370
-
371
- // Normalize paths: trim trailing slashes for consistent matching
372
- const normalizePath = (p: string) => {
373
- const decoded = decodeURIComponent(p);
374
- return decoded.endsWith('/') && decoded.length > 1
375
- ? decoded.slice(0, -1)
376
- : decoded;
377
- };
378
- const requestPath = normalizePath(c.req.path);
379
-
380
- // Helper to check if requestPath ends with routePath at a segment boundary
381
- // e.g., "/api/translate" matches "/translate" but "/api/translate-v2" does not
382
- const matchesAtSegmentBoundary = (reqPath: string, routePath: string) => {
383
- if (reqPath === routePath) return true;
384
- if (!reqPath.endsWith(routePath)) return false;
385
- // Check that the character before the match is a path separator
386
- const charBeforeMatch = reqPath[reqPath.length - routePath.length - 1];
387
- return charBeforeMatch === '/';
388
- };
389
-
390
- // Try matching by exact normalized path first
391
- let route = metadata?.routes?.find(
392
- (r) =>
393
- r.method.toUpperCase() === methodUpper &&
394
- normalizePath(r.path) === requestPath
395
- );
396
- // Fall back to segment-boundary matching (handles /api/translate matching /translate)
397
- if (!route) {
398
- route = metadata?.routes?.find(
399
- (r) =>
400
- r.method.toUpperCase() === methodUpper &&
401
- matchesAtSegmentBoundary(requestPath, normalizePath(r.path))
402
- );
403
- }
404
- const routeId = route?.id || '';
405
-
406
- await sessionEventProvider.start({
407
- id: sessionId,
408
- threadId: thread.id,
409
- orgId: orgId || '',
410
- projectId: projectId || '',
411
- deploymentId: deploymentId || undefined,
412
- devmode: isDevMode,
413
- trigger: isWsUpgrade ? 'websocket' : 'api',
414
- routeId,
415
- environment: runtimeConfig.getEnvironment(),
416
- url: c.req.path,
417
- method: c.req.method,
418
- });
419
- } catch (_ex) {
420
- // Silently ignore session start errors - don't block request
421
- }
422
- }
423
-
424
- // Factor out finalization logic so it can run synchronously or deferred
425
- const finalizeSession = async (statusCode?: number, error?: string) => {
426
- internal.info(
427
- '[session] saving session %s (thread: %s) (error: %s)',
428
- sessionId,
429
- thread.id,
430
- error
431
- );
432
- await sessionProvider.save(session);
433
- internal.info('[session] session saved, now saving thread');
434
- await threadProvider.save(thread);
435
- internal.info('[session] thread saved');
436
-
437
- // Send session complete event (skip for workbench routes)
438
- if (sessionEventProvider && !skipSessionEvents) {
439
- try {
440
- const userData = session.serializeUserData();
441
- internal.info(
442
- '[session] sending session complete event, userData: %s',
443
- userData ? `${userData.length} bytes` : 'none'
444
- );
445
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
446
- const agentIdsSet = (c as any).get('agentIds') as Set<string> | undefined;
447
- const agentIds = agentIdsSet ? [...agentIdsSet].filter(Boolean) : undefined;
448
- internal.info('[session] agentIds: %o', agentIds);
449
- const isEmpty = await thread.empty();
450
- await sessionEventProvider.complete({
451
- id: sessionId,
452
- threadId: isEmpty ? null : thread.id,
453
- statusCode: statusCode ?? c.res?.status ?? 200,
454
- error,
455
- agentIds: agentIds?.length ? agentIds : undefined,
456
- userData,
457
- });
458
- internal.info('[session] session complete event sent');
459
- } catch (ex) {
460
- internal.info(
461
- '[session] session complete event failed: %s',
462
- ex instanceof Error ? ex.message : ex
463
- );
464
- // Silently ignore session complete errors - don't block response
465
- }
466
- }
467
- };
468
-
469
- // Track state for finalization
470
- let responseStatus = 200;
471
- let errorMessage: string | undefined;
472
- let handlerDurationMs = 0;
473
- // Track whether span should be ended in finally block (false for streaming - ended in waitUntil)
474
- let shouldEndSpanInFinally = true;
475
-
476
- try {
477
- internal.info(
478
- '[request] %s %s - handler starting (session: %s)',
479
- method,
480
- url.pathname,
481
- sessionId
482
- );
483
-
484
- await next();
485
-
486
- // Capture timing immediately after next() returns - this is when the handler completed
487
- // This is the HTTP response time we want to report (excludes waitUntil/finalization)
488
- handlerDurationMs = performance.now() - requestStartTime;
489
-
490
- internal.info(
491
- '[request] %s %s - handler completed in %sms (session: %s)',
492
- method,
493
- url.pathname,
494
- handlerDurationMs.toFixed(2),
495
- sessionId
496
- );
497
-
498
- // Check if this is a streaming response that needs deferred finalization
499
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
500
- const streamDone = (c as any).get(STREAM_DONE_PROMISE_KEY) as
501
- | Promise<void>
502
- | undefined;
503
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
504
- const isStreaming = Boolean((c as any).get(IS_STREAMING_RESPONSE_KEY));
505
-
506
- // Check if this is a WebSocket response that needs deferred finalization
507
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
508
- const wsDone = (c as any).get(WS_DONE_PROMISE_KEY) as Promise<void> | undefined;
509
-
510
- // Check if Hono caught an error (c.error is set by Hono's error handler)
511
- // or if the response status indicates an error
512
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
513
- const honoError = (c as any).error as Error | undefined;
514
- responseStatus = c.res?.status ?? 200;
515
- const isError = honoError || responseStatus >= 500;
516
-
517
- internal.info(
518
- '[request] %s %s - status: %d, streaming: %s, websocket: %s, error: %s (session: %s)',
519
- method,
520
- url.pathname,
521
- responseStatus,
522
- isStreaming,
523
- Boolean(wsDone),
524
- isError,
525
- sessionId
526
- );
527
-
528
- if (isError) {
529
- // Capture error message for finalization
530
- errorMessage = honoError
531
- ? (honoError.stack ?? honoError.message)
532
- : `HTTP ${responseStatus}`;
533
- span.setStatus({
534
- code: SpanStatusCode.ERROR,
535
- message: honoError?.message ?? errorMessage,
536
- });
537
- if (honoError) {
538
- span.recordException(honoError);
539
- }
540
- } else {
541
- span.setStatus({ code: SpanStatusCode.OK });
542
- }
543
-
544
- // For streaming responses, defer everything until stream completes
545
- if (isStreaming && streamDone) {
546
- internal.info(
547
- '[request] %s %s - streaming response, deferring finalization (session: %s)',
548
- method,
549
- url.pathname,
550
- sessionId
551
- );
552
-
553
- // For streaming, we end the span inside waitUntil after setting attributes
554
- shouldEndSpanInFinally = false;
555
-
556
- // Capture pending promises BEFORE adding finalization waitUntil to avoid deadlock
557
- const pendingPromises = handler.getPendingSnapshot();
558
- const hasPendingTasks = pendingPromises.length > 0;
559
-
560
- if (hasPendingTasks) {
561
- internal.info(
562
- '[request] %s %s - %d pending waitUntil tasks to wait for after stream (session: %s)',
563
- method,
564
- url.pathname,
565
- pendingPromises.length,
566
- sessionId
567
- );
568
- }
569
-
570
- // Capture values needed for span attributes (responseStatus already captured above)
571
- const capturedResponseStatus = responseStatus;
572
- const capturedErrorMessage = errorMessage;
573
-
574
- // Use waitUntil to handle stream completion and finalization
575
- // This runs AFTER the response is sent to the client
576
- // Note: We intentionally do NOT use noSpan here - the waitUntil span helps
577
- // track the streaming finalization work in telemetry
578
- handler.waitUntil(async () => {
579
- // Track if stream ended with error so we can update finalization status
580
- let streamError: unknown = undefined;
581
-
582
- try {
583
- await streamDone;
584
- internal.info(
585
- '[request] %s %s - stream completed (session: %s)',
586
- method,
587
- url.pathname,
588
- sessionId
589
- );
590
- } catch (ex) {
591
- streamError = ex;
592
- internal.info(
593
- '[request] %s %s - stream ended with error: %s (session: %s)',
594
- method,
595
- url.pathname,
596
- ex,
597
- sessionId
598
- );
599
- }
600
-
601
- // Record duration now that stream is complete - set attributes BEFORE ending span
602
- const streamDurationMs = performance.now() - requestStartTime;
603
- const durationNs = Math.round(streamDurationMs * 1_000_000);
604
- internal.info(
605
- '[request] %s %s - recording stream duration: %sms (session: %s)',
606
- method,
607
- url.pathname,
608
- streamDurationMs.toFixed(2),
609
- sessionId
610
- );
611
-
612
- // Determine final status - use stream error if present
613
- const finalStatus = streamError ? 500 : capturedResponseStatus;
614
- const finalErrorMessage = streamError
615
- ? streamError instanceof Error
616
- ? (streamError.stack ?? streamError.message)
617
- : String(streamError)
618
- : capturedErrorMessage;
619
-
620
- try {
621
- // Wait for pending tasks (evals, etc.) captured BEFORE this waitUntil was added
622
- if (hasPendingTasks) {
623
- internal.info(
624
- '[request] %s %s - waiting for %d pending waitUntil tasks (session: %s)',
625
- method,
626
- url.pathname,
627
- pendingPromises.length,
628
- sessionId
629
- );
630
- const logger = c.get('logger');
631
- await handler.waitForPromises(pendingPromises, logger, sessionId);
632
- internal.info(
633
- '[request] %s %s - all waitUntil tasks complete (session: %s)',
634
- method,
635
- url.pathname,
636
- sessionId
637
- );
638
- }
639
-
640
- // Finalize session after stream completes and evals finish
641
- await finalizeSession(
642
- finalStatus >= 500 ? finalStatus : undefined,
643
- finalErrorMessage
644
- );
645
- internal.info(
646
- '[request] %s %s - stream session finalization complete (session: %s)',
647
- method,
648
- url.pathname,
649
- sessionId
650
- );
651
- } finally {
652
- // Set span attributes and end span AFTER all work is done
653
- span.setAttribute('@agentuity/request.duration', durationNs);
654
- span.setAttribute('http.status_code', finalStatus);
655
-
656
- // Set span status based on whether there was an error
657
- if (streamError) {
658
- span.setStatus({
659
- code: SpanStatusCode.ERROR,
660
- message: finalErrorMessage ?? 'Stream ended with error',
661
- });
662
- if (streamError instanceof Error) {
663
- span.recordException(streamError);
664
- }
665
- } else {
666
- span.setStatus({ code: SpanStatusCode.OK });
667
- }
668
-
669
- span.end();
670
- internal.info(
671
- '[request] %s %s - stream span ended (session: %s)',
672
- method,
673
- url.pathname,
674
- sessionId
675
- );
676
- // Note: We don't call waitUntilAll() here because this waitUntil callback
677
- // IS the final cleanup task. Calling waitUntilAll() would deadlock since
678
- // it would wait for this very promise to complete.
679
- }
680
- });
681
- } else if (wsDone) {
682
- // WebSocket upgrade — end the span immediately (short-lived upgrade span)
683
- // Per OTel conventions, spans should be short-lived. The HTTP upgrade
684
- // request is a discrete event that completes in milliseconds. Keeping
685
- // a span open for the entire WS connection lifetime (minutes/hours) is
686
- // non-standard and causes issues with OTel backends.
687
- internal.info(
688
- '[request] %s %s - websocket upgrade, ending span immediately (session: %s)',
689
- method,
690
- url.pathname,
691
- sessionId
692
- );
693
-
694
- // End the upgrade span now with the upgrade duration
695
- const upgradeDurationNs = Math.round(handlerDurationMs * 1_000_000);
696
- span.setAttribute('@agentuity/request.duration', upgradeDurationNs);
697
- span.setAttribute('http.status_code', responseStatus);
698
- span.setStatus({ code: SpanStatusCode.OK });
699
- span.end();
700
- shouldEndSpanInFinally = false; // already ended
701
-
702
- // Session finalization still defers until WS close, but without holding a span
703
- const pendingPromises = handler.getPendingSnapshot();
704
- const hasPendingTasks = pendingPromises.length > 0;
705
-
706
- if (hasPendingTasks) {
707
- internal.info(
708
- '[request] %s %s - %d pending waitUntil tasks to wait for after websocket close (session: %s)',
709
- method,
710
- url.pathname,
711
- pendingPromises.length,
712
- sessionId
713
- );
714
- }
715
-
716
- handler.waitUntil(async () => {
717
- let wsError: unknown = undefined;
718
-
719
- try {
720
- await wsDone;
721
- internal.info(
722
- '[request] %s %s - websocket closed (session: %s)',
723
- method,
724
- url.pathname,
725
- sessionId
726
- );
727
- } catch (ex) {
728
- wsError = ex;
729
- internal.info(
730
- '[request] %s %s - websocket closed with error: %s (session: %s)',
731
- method,
732
- url.pathname,
733
- ex,
734
- sessionId
735
- );
736
- }
737
-
738
- const finalStatus = wsError ? 500 : responseStatus;
739
- const finalErrorMessage = wsError
740
- ? wsError instanceof Error
741
- ? (wsError.stack ?? wsError.message)
742
- : String(wsError)
743
- : errorMessage;
744
-
745
- // Wait for pending tasks captured BEFORE this waitUntil was added
746
- if (hasPendingTasks) {
747
- internal.info(
748
- '[request] %s %s - waiting for %d pending waitUntil tasks (session: %s)',
749
- method,
750
- url.pathname,
751
- pendingPromises.length,
752
- sessionId
753
- );
754
- const logger = c.get('logger');
755
- await handler.waitForPromises(pendingPromises, logger, sessionId);
756
- internal.info(
757
- '[request] %s %s - all waitUntil tasks complete (session: %s)',
758
- method,
759
- url.pathname,
760
- sessionId
761
- );
762
- }
763
-
764
- // Finalize session after WebSocket closes
765
- await finalizeSession(
766
- finalStatus >= 500 ? finalStatus : undefined,
767
- finalErrorMessage
768
- );
769
- internal.info(
770
- '[request] %s %s - websocket session finalization complete (session: %s)',
771
- method,
772
- url.pathname,
773
- sessionId
774
- );
775
- });
776
- } else {
777
- // Non-streaming: record duration immediately
778
- const durationNs = Math.round(handlerDurationMs * 1_000_000);
779
- internal.info(
780
- '[request] %s %s - recording duration: %sms (%dns) (session: %s)',
781
- method,
782
- url.pathname,
783
- handlerDurationMs.toFixed(2),
784
- durationNs,
785
- sessionId
786
- );
787
- span.setAttribute('@agentuity/request.duration', durationNs);
788
- span.setAttribute('http.status_code', responseStatus);
789
-
790
- // Capture pending promises BEFORE adding finalization waitUntil to avoid deadlock.
791
- // If we called waitUntilAll inside waitUntil, it would wait for itself.
792
- const pendingPromises = handler.getPendingSnapshot();
793
- const hasPendingTasks = pendingPromises.length > 0;
794
-
795
- if (hasPendingTasks) {
796
- internal.info(
797
- '[request] %s %s - %d pending waitUntil tasks to wait for (session: %s)',
798
- method,
799
- url.pathname,
800
- pendingPromises.length,
801
- sessionId
802
- );
803
- }
804
-
805
- // Capture values for use in waitUntil callback
806
- const capturedResponseStatus = responseStatus;
807
- const capturedErrorMessage = errorMessage;
808
-
809
- // Defer session finalization to run AFTER response is sent
810
- // Use noSpan: true since finalizeSession creates its own Session End span
811
- handler.waitUntil(
812
- async () => {
813
- // Wait for the snapshot of pending tasks (evals, etc.) captured BEFORE this waitUntil was added
814
- if (hasPendingTasks) {
815
- internal.info(
816
- '[request] %s %s - waiting for %d pending waitUntil tasks (session: %s)',
817
- method,
818
- url.pathname,
819
- pendingPromises.length,
820
- sessionId
821
- );
822
- const logger = c.get('logger');
823
- await handler.waitForPromises(pendingPromises, logger, sessionId);
824
- internal.info(
825
- '[request] %s %s - all waitUntil tasks complete (session: %s)',
826
- method,
827
- url.pathname,
828
- sessionId
829
- );
830
- }
831
-
832
- // Finalize session - this is the actual work
833
- internal.info(
834
- '[request] %s %s - starting session finalization (session: %s)',
835
- method,
836
- url.pathname,
837
- sessionId
838
- );
839
- try {
840
- await finalizeSession(
841
- capturedResponseStatus >= 500 ? capturedResponseStatus : undefined,
842
- capturedErrorMessage
843
- );
844
- internal.info(
845
- '[request] %s %s - session finalization complete (session: %s)',
846
- method,
847
- url.pathname,
848
- sessionId
849
- );
850
- } catch (ex) {
851
- internal.error(
852
- '[request] %s %s - session finalization failed: %s (session: %s)',
853
- method,
854
- url.pathname,
855
- ex,
856
- sessionId
857
- );
858
- }
859
- // Note: We don't call waitUntilAll() here because this waitUntil callback
860
- // IS the final cleanup task. Calling waitUntilAll() would deadlock since
861
- // it would wait for this very promise to complete.
862
- },
863
- { noSpan: true }
864
- );
865
- }
866
- } catch (ex) {
867
- // Record request metrics even on exceptions (500 status)
868
- const exceptionDurationMs = performance.now() - requestStartTime;
869
- const durationNs = Math.round(exceptionDurationMs * 1_000_000);
870
- internal.info(
871
- '[request] %s %s - recording exception duration: %sms (session: %s)',
872
- method,
873
- url.pathname,
874
- exceptionDurationMs.toFixed(2),
875
- sessionId
876
- );
877
- span.setAttribute('@agentuity/request.duration', durationNs);
878
- span.setAttribute('http.status_code', 500);
879
-
880
- if (ex instanceof Error) {
881
- span.recordException(ex);
882
- }
883
- errorMessage = ex instanceof Error ? (ex.stack ?? ex.message) : String(ex);
884
- responseStatus = 500;
885
- span.setStatus({
886
- code: SpanStatusCode.ERROR,
887
- message: ex instanceof Error ? ex.message : String(ex),
888
- });
889
-
890
- // Capture error message for use in waitUntil callback
891
- const capturedErrorMessage = errorMessage;
892
-
893
- // Capture pending promises BEFORE adding finalization waitUntil to avoid deadlock
894
- const pendingPromises = handler.getPendingSnapshot();
895
- const hasPendingTasks = pendingPromises.length > 0;
896
-
897
- if (hasPendingTasks) {
898
- internal.info(
899
- '[request] %s %s - %d pending waitUntil tasks to wait for after error (session: %s)',
900
- method,
901
- url.pathname,
902
- pendingPromises.length,
903
- sessionId
904
- );
905
- }
906
-
907
- // Still defer finalization even on error
908
- // Use noSpan: true since finalizeSession creates its own Session End span
909
- handler.waitUntil(
910
- async () => {
911
- // Wait for pending tasks (evals, etc.) captured BEFORE this waitUntil was added
912
- if (hasPendingTasks) {
913
- internal.info(
914
- '[request] %s %s - waiting for %d pending waitUntil tasks (session: %s)',
915
- method,
916
- url.pathname,
917
- pendingPromises.length,
918
- sessionId
919
- );
920
- const logger = c.get('logger');
921
- await handler.waitForPromises(pendingPromises, logger, sessionId);
922
- internal.info(
923
- '[request] %s %s - all waitUntil tasks complete (session: %s)',
924
- method,
925
- url.pathname,
926
- sessionId
927
- );
928
- }
929
-
930
- try {
931
- await finalizeSession(500, capturedErrorMessage);
932
- } catch (finalizeEx) {
933
- internal.error(
934
- '[request] %s %s - error session finalization failed: %s (session: %s)',
935
- method,
936
- url.pathname,
937
- finalizeEx,
938
- sessionId
939
- );
940
- }
941
- // Note: We don't call waitUntilAll() here because this waitUntil callback
942
- // IS the final cleanup task. Calling waitUntilAll() would deadlock since
943
- // it would wait for this very promise to complete.
944
- },
945
- { noSpan: true }
946
- );
947
-
948
- throw ex;
949
- } finally {
950
- // Set response headers - this is the only thing that should block the response
951
- const headers: Record<string, string> = {};
952
- propagation.inject(context.active(), headers);
953
- for (const key of Object.keys(headers)) {
954
- c.header(key, headers[key]);
955
- }
956
- const traceId = sctx?.traceId || sessionId.replace(/^sess_/, '');
957
- c.header(SESSION_HEADER, `sess_${traceId}`);
958
-
959
- internal.info(
960
- '[request] %s %s - response ready, duration: %sms (session: %s)',
961
- method,
962
- url.pathname,
963
- handlerDurationMs.toFixed(2),
964
- sessionId
965
- );
966
-
967
- // Only end span here for non-streaming responses
968
- // For streaming, span is ended in the waitUntil callback after setting duration attributes
969
- if (shouldEndSpanInFinally) {
970
- span.end();
971
- }
972
- }
973
- }
974
- );
975
- });
976
- });
977
- }
978
-
979
- /**
980
- * Create compression middleware with lazy config resolution.
981
- *
982
- * Compresses response bodies using gzip or deflate based on the Accept-Encoding header.
983
- * Config is resolved at request time, allowing it to be set via createApp().
984
- *
985
- * @param staticConfig - Optional static config that overrides app config
986
- *
987
- * @example
988
- * ```typescript
989
- * // Use with default settings
990
- * app.use('*', createCompressionMiddleware());
991
- *
992
- * // Or configure via createApp
993
- * const app = await createApp({
994
- * compression: {
995
- * threshold: 2048,
996
- * }
997
- * });
998
- * ```
999
- */
1000
- export function createCompressionMiddleware(staticConfig?: CompressionConfig | false) {
1001
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1002
- return createMiddleware<Env<any>>(async (c, next) => {
1003
- // Check if compression is explicitly disabled
1004
- if (staticConfig === false || staticConfig?.enabled === false) {
1005
- return next();
1006
- }
1007
-
1008
- const config: CompressionConfig = { ...staticConfig };
1009
-
1010
- const { enabled = true, threshold = 1024, filter, honoOptions } = config;
1011
-
1012
- // Skip if explicitly disabled
1013
- if (!enabled) {
1014
- return next();
1015
- }
1016
-
1017
- // Skip WebSocket upgrade requests
1018
- const upgrade = c.req.header('upgrade');
1019
- if (upgrade && upgrade.toLowerCase() === 'websocket') {
1020
- return next();
1021
- }
1022
-
1023
- // Skip if no Accept-Encoding header
1024
- const acceptEncoding = c.req.header('accept-encoding');
1025
- if (!acceptEncoding) {
1026
- return next();
1027
- }
1028
-
1029
- // Check custom filter
1030
- if (filter && !filter(c)) {
1031
- return next();
1032
- }
1033
-
1034
- // Create and run the Hono compress middleware
1035
- const compressMiddleware = compress({
1036
- threshold,
1037
- ...honoOptions,
1038
- });
1039
-
1040
- await compressMiddleware(c, next);
1041
- });
1042
- }
1043
-
1044
- /**
1045
- * Create lightweight thread middleware for web routes (analytics).
1046
- *
1047
- * Sets thread cookie that persists across page views for client-side analytics.
1048
- * This middleware does NOT:
1049
- * - Create or track sessions (no session ID)
1050
- * - Set session/thread response headers
1051
- * - Send events to Catalyst sessions table
1052
- *
1053
- * This is intentionally separate from createOtelMiddleware to avoid
1054
- * polluting the sessions table with web browsing activity.
1055
- *
1056
- * - Thread cookie (atid_a): Analytics-readable copy, 1-week expiry
1057
- */
1058
- export function createWebSessionMiddleware() {
1059
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1060
- return createMiddleware<Env<any>>(async (c, next) => {
1061
- // Import providers dynamically to avoid circular deps
1062
- const { getThreadProvider } = await import('./_services');
1063
-
1064
- const secret = getSessionSecret();
1065
-
1066
- // Use ThreadProvider.restore() to get/create thread (handles header, cookie, generation)
1067
- const threadProvider = getThreadProvider();
1068
- const thread = await threadProvider.restore(c);
1069
-
1070
- // Set thread cookie for analytics
1071
- // httpOnly: false so beacon script can read it
1072
- const isSecure = c.req.url.startsWith('https://');
1073
- await setSignedCookie(c, 'atid_a', thread.id, secret, {
1074
- httpOnly: false, // Readable by JavaScript for analytics
1075
- secure: isSecure,
1076
- sameSite: 'Lax',
1077
- path: '/',
1078
- maxAge: 604800, // 1 week
1079
- });
1080
-
1081
- // Store in context for handler to access in same request
1082
- // (cookies aren't readable until the next request)
1083
- c.set('_webThreadId', thread.id);
1084
-
1085
- await next();
1086
- });
1087
- }
1088
-
1089
- /**
1090
- * Get the secret used for signing session/thread cookies.
1091
- * Uses AGENTUITY_SDK_KEY if available, falls back to 'agentuity'.
1092
- */
1093
- export function getSessionSecret(): string {
1094
- return process.env.AGENTUITY_SDK_KEY || 'agentuity';
1095
- }