@m6d/cortex-server 1.8.0 → 2.0.0

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 (352) hide show
  1. package/README.md +8 -3
  2. package/contracts/README.md +23 -0
  3. package/contracts/graph/embed.ts +50 -0
  4. package/{src/lib → contracts}/graph/helpers.ts +4 -10
  5. package/{src/lib → contracts}/graph/neo4j.ts +14 -27
  6. package/contracts/graph/schema.ts +65 -0
  7. package/{src/lib → contracts}/graph/types.ts +2 -29
  8. package/contracts/graph.ts +36 -0
  9. package/contracts/runtime.ts +208 -0
  10. package/contracts/wire.ts +143 -0
  11. package/dist/contracts/graph/embed.d.ts +22 -0
  12. package/dist/{src/lib → contracts}/graph/helpers.d.ts +16 -14
  13. package/dist/{src/lib → contracts}/graph/neo4j.d.ts +5 -5
  14. package/dist/contracts/graph/schema.d.ts +62 -0
  15. package/dist/{src/lib → contracts}/graph/types.d.ts +1 -1
  16. package/dist/contracts/graph.d.ts +14 -0
  17. package/dist/contracts/runtime.d.ts +278 -0
  18. package/dist/contracts/wire.d.ts +120 -0
  19. package/dist/src/lib/adapters/database/index.d.ts +61 -0
  20. package/dist/src/lib/adapters/database/message-content.d.ts +30 -0
  21. package/dist/src/lib/adapters/database/mssql/attachments.d.ts +100 -0
  22. package/dist/src/lib/adapters/database/mssql/client.d.ts +8 -0
  23. package/dist/src/lib/adapters/database/mssql/index.d.ts +194 -0
  24. package/dist/src/lib/adapters/database/mssql/llm-requests.d.ts +17 -0
  25. package/dist/src/lib/adapters/database/mssql/messages.d.ts +37 -0
  26. package/dist/src/lib/adapters/database/mssql/threads.d.ts +50 -0
  27. package/dist/src/lib/adapters/database/postgres/attachments.d.ts +113 -0
  28. package/dist/src/lib/adapters/database/postgres/client.d.ts +8 -0
  29. package/dist/src/lib/adapters/database/postgres/index.d.ts +207 -0
  30. package/dist/src/lib/adapters/database/postgres/llm-requests.d.ts +17 -0
  31. package/dist/src/lib/adapters/database/postgres/messages.d.ts +37 -0
  32. package/dist/src/lib/adapters/database/postgres/threads.d.ts +50 -0
  33. package/{src/lib/adapters/storage.ts → dist/src/lib/adapters/storage/index.d.ts} +1 -1
  34. package/dist/src/lib/adapters/{minio.d.ts → storage/minio.d.ts} +1 -1
  35. package/dist/src/lib/ai/active-runs.d.ts +63 -0
  36. package/dist/src/lib/ai/attachments.d.ts +30 -0
  37. package/dist/src/lib/ai/backend-url.d.ts +16 -0
  38. package/dist/src/lib/ai/cc-runtime.d.ts +61 -0
  39. package/dist/src/lib/ai/commit-gate.d.ts +21 -0
  40. package/dist/src/lib/ai/context/builder.d.ts +6 -7
  41. package/dist/src/lib/ai/context/compression-middleware.d.ts +15 -0
  42. package/dist/src/lib/ai/context/compressor.d.ts +2 -13
  43. package/dist/src/lib/ai/context/intra-turn-compressor.d.ts +9 -9
  44. package/dist/src/lib/ai/context/optimize-thread-context.d.ts +9 -0
  45. package/dist/src/lib/ai/context/summarizer.d.ts +3 -3
  46. package/dist/src/lib/ai/context/token-estimator.d.ts +5 -8
  47. package/dist/src/lib/ai/fetch.d.ts +1 -1
  48. package/dist/src/lib/ai/finish-turn.d.ts +23 -0
  49. package/dist/src/lib/ai/helpers.d.ts +37 -4
  50. package/dist/src/lib/ai/index.d.ts +6 -3
  51. package/dist/src/lib/ai/inspector.d.ts +41 -0
  52. package/dist/src/lib/ai/interceptors/request-interceptor.d.ts +6 -5
  53. package/dist/src/lib/ai/prompt.d.ts +5 -4
  54. package/dist/src/lib/ai/redis-runs.d.ts +41 -0
  55. package/dist/src/lib/ai/redis-stream-log.d.ts +39 -0
  56. package/dist/src/lib/ai/tools/execute-code.tool.d.ts +18 -4
  57. package/dist/src/lib/ai/tools/query-graph.tool.d.ts +13 -5
  58. package/dist/src/lib/ai/tools/read-attachment.tool.d.ts +21 -0
  59. package/dist/src/lib/ai/tools/search-common.d.ts +17 -0
  60. package/dist/src/lib/ai/tools/search-knowledge.tool.d.ts +15 -0
  61. package/dist/src/lib/ai/tools/search-services.tool.d.ts +12 -0
  62. package/dist/src/lib/ai/tools/search-tools.tool.d.ts +12 -0
  63. package/dist/src/lib/ai/turn-tools.d.ts +39 -0
  64. package/dist/src/lib/ai/vision/rasterize-pdf.d.ts +26 -0
  65. package/dist/src/lib/ai/vision/rasterize-pdf.worker.d.ts +1 -0
  66. package/dist/src/lib/ai/vision/vision-reader.d.ts +45 -0
  67. package/dist/src/lib/cc/client.d.ts +183 -0
  68. package/dist/src/lib/cc/config-cache.d.ts +20 -0
  69. package/dist/src/lib/cc/format.d.ts +23 -0
  70. package/dist/src/lib/cc/registry.d.ts +34 -0
  71. package/dist/src/lib/cc/types.d.ts +27 -0
  72. package/dist/src/lib/config.d.ts +81 -64
  73. package/dist/src/lib/db/drizzle.config.mssql.d.ts +2 -0
  74. package/dist/src/lib/db/drizzle.config.pg.d.ts +2 -0
  75. package/dist/src/lib/db/migrate.d.ts +7 -1
  76. package/dist/src/lib/db/{schema.d.ts → schema.mssql.d.ts} +138 -29
  77. package/dist/src/lib/db/schema.pg.d.ts +546 -0
  78. package/dist/src/lib/factory.d.ts +2 -12
  79. package/dist/src/lib/graph/index.d.ts +10 -11
  80. package/dist/src/lib/graph/resolver.d.ts +2 -3
  81. package/dist/src/lib/index.d.ts +3 -3
  82. package/dist/src/lib/redis.d.ts +19 -0
  83. package/dist/src/lib/routes/chat.d.ts +1 -1
  84. package/dist/src/lib/routes/files.d.ts +1 -1
  85. package/dist/src/lib/routes/owned-thread.d.ts +17 -0
  86. package/dist/src/lib/routes/threads.d.ts +1 -1
  87. package/dist/src/lib/routes/ws.d.ts +1 -1
  88. package/dist/src/lib/types.d.ts +20 -37
  89. package/dist/src/lib/ws/connections.d.ts +2 -1
  90. package/package.json +33 -18
  91. package/src/lib/adapters/database/index.ts +78 -0
  92. package/src/lib/adapters/database/message-content.ts +39 -0
  93. package/src/lib/adapters/database/mssql/attachments.ts +90 -0
  94. package/src/lib/adapters/database/mssql/client.ts +11 -0
  95. package/src/lib/adapters/database/mssql/index.ts +23 -0
  96. package/src/lib/adapters/database/mssql/llm-requests.ts +43 -0
  97. package/src/lib/adapters/database/mssql/messages.ts +92 -0
  98. package/src/lib/adapters/database/mssql/threads.ts +83 -0
  99. package/src/lib/adapters/database/postgres/attachments.ts +88 -0
  100. package/src/lib/adapters/database/postgres/client.ts +11 -0
  101. package/src/lib/adapters/database/postgres/index.ts +23 -0
  102. package/src/lib/adapters/database/postgres/llm-requests.ts +45 -0
  103. package/src/lib/adapters/database/postgres/messages.ts +92 -0
  104. package/src/lib/adapters/database/postgres/threads.ts +84 -0
  105. package/{dist/src/lib/adapters/storage.d.ts → src/lib/adapters/storage/index.ts} +1 -1
  106. package/src/lib/adapters/{minio.ts → storage/minio.ts} +5 -5
  107. package/src/lib/ai/active-runs.ts +157 -0
  108. package/src/lib/ai/attachments.ts +144 -0
  109. package/src/lib/ai/backend-url.ts +27 -0
  110. package/src/lib/ai/cc-runtime.ts +63 -0
  111. package/src/lib/ai/commit-gate.ts +116 -0
  112. package/src/lib/ai/context/builder.ts +12 -14
  113. package/src/lib/ai/context/compression-middleware.ts +44 -0
  114. package/src/lib/ai/context/compressor.ts +67 -59
  115. package/src/lib/ai/context/intra-turn-compressor.ts +121 -204
  116. package/src/lib/ai/context/optimize-thread-context.ts +50 -0
  117. package/src/lib/ai/context/summarizer.ts +28 -30
  118. package/src/lib/ai/context/token-estimator.ts +35 -30
  119. package/src/lib/ai/fetch.ts +10 -5
  120. package/src/lib/ai/finish-turn.ts +75 -0
  121. package/src/lib/ai/helpers.ts +83 -15
  122. package/src/lib/ai/index.ts +359 -329
  123. package/src/lib/ai/inspector.ts +112 -0
  124. package/src/lib/ai/interceptors/request-interceptor.ts +70 -42
  125. package/src/lib/ai/prompt.ts +126 -125
  126. package/src/lib/ai/redis-runs.ts +167 -0
  127. package/src/lib/ai/redis-stream-log.ts +219 -0
  128. package/src/lib/ai/tools/execute-code.tool.ts +178 -67
  129. package/src/lib/ai/tools/query-graph.tool.ts +7 -8
  130. package/src/lib/ai/tools/read-attachment.tool.ts +59 -0
  131. package/src/lib/ai/tools/search-common.ts +15 -0
  132. package/src/lib/ai/tools/search-knowledge.tool.ts +33 -0
  133. package/src/lib/ai/tools/search-services.tool.ts +31 -0
  134. package/src/lib/ai/tools/search-tools.tool.ts +29 -0
  135. package/src/lib/ai/turn-tools.ts +154 -0
  136. package/src/lib/ai/vision/rasterize-pdf.ts +69 -0
  137. package/src/lib/ai/vision/rasterize-pdf.worker.ts +81 -0
  138. package/src/lib/ai/vision/vision-reader.ts +130 -0
  139. package/src/lib/auth/middleware.ts +5 -3
  140. package/src/lib/cc/client.ts +273 -0
  141. package/src/lib/cc/config-cache.ts +32 -0
  142. package/src/lib/cc/format.ts +96 -0
  143. package/src/lib/cc/registry.ts +62 -0
  144. package/src/lib/cc/types.ts +44 -0
  145. package/src/lib/config.ts +102 -71
  146. package/src/lib/db/drizzle.config.mssql.ts +9 -0
  147. package/src/lib/db/drizzle.config.pg.ts +9 -0
  148. package/src/lib/db/migrate.ts +45 -10
  149. package/src/lib/db/migrations/mssql/20260729092726_talented_wilson_fisk/migration.sql +20 -0
  150. package/src/lib/db/migrations/mssql/20260729092726_talented_wilson_fisk/snapshot.json +497 -0
  151. package/src/lib/db/migrations/mssql/20260801003652_perpetual_blue_shield/migration.sql +1 -0
  152. package/src/lib/db/migrations/mssql/20260801003652_perpetual_blue_shield/snapshot.json +516 -0
  153. package/src/lib/db/migrations/mssql/20260812174642_user_id_text/migration.sql +2 -0
  154. package/src/lib/db/migrations/mssql/20260812174642_user_id_text/snapshot.json +516 -0
  155. package/src/lib/db/migrations/pg/20260801084117_thick_ben_grimm/migration.sql +53 -0
  156. package/src/lib/db/migrations/pg/20260801084117_thick_ben_grimm/snapshot.json +584 -0
  157. package/src/lib/db/migrations/pg/20260812174622_user_id_text/migration.sql +2 -0
  158. package/src/lib/db/migrations/pg/20260812174622_user_id_text/snapshot.json +584 -0
  159. package/src/lib/db/{schema.ts → schema.mssql.ts} +29 -18
  160. package/src/lib/db/schema.pg.ts +95 -0
  161. package/src/lib/factory.ts +44 -89
  162. package/src/lib/graph/index.ts +12 -40
  163. package/src/lib/graph/resolver.ts +146 -116
  164. package/src/lib/index.ts +11 -4
  165. package/src/lib/redis.ts +44 -0
  166. package/src/lib/routes/chat.ts +91 -62
  167. package/src/lib/routes/files.ts +105 -83
  168. package/src/lib/routes/owned-thread.ts +17 -0
  169. package/src/lib/routes/threads.ts +14 -30
  170. package/src/lib/routes/ws.ts +9 -3
  171. package/src/lib/types.ts +37 -36
  172. package/src/lib/ws/connections.ts +9 -1
  173. package/tsconfig.json +48 -0
  174. package/dist/src/lib/adapters/database.d.ts +0 -45
  175. package/dist/src/lib/adapters/mssql.d.ts +0 -3
  176. package/dist/src/lib/ai/active-streams.d.ts +0 -14
  177. package/dist/src/lib/ai/context/index.d.ts +0 -16
  178. package/dist/src/lib/ai/tools/capture-files.tool.d.ts +0 -6
  179. package/dist/src/lib/cli/extract-endpoints.d.ts +0 -6
  180. package/dist/src/lib/graph/expand-domains.d.ts +0 -2
  181. package/dist/src/lib/graph/generate-cypher.d.ts +0 -22
  182. package/dist/src/lib/graph/seed.d.ts +0 -19
  183. package/dist/src/lib/graph/validate.d.ts +0 -2
  184. package/dist/src/lib/routes/index.d.ts +0 -4
  185. package/dist/src/lib/ws/events.d.ts +0 -39
  186. package/dist/src/lib/ws/index.d.ts +0 -3
  187. package/dist/src/lib/ws/notify.d.ts +0 -2
  188. package/src/lib/adapters/database.ts +0 -48
  189. package/src/lib/adapters/mssql.ts +0 -231
  190. package/src/lib/ai/active-streams.ts +0 -123
  191. package/src/lib/ai/context/index.ts +0 -80
  192. package/src/lib/ai/tools/capture-files.tool.ts +0 -20
  193. package/src/lib/cli/extract-endpoints.ts +0 -550
  194. package/src/lib/graph/expand-domains.ts +0 -276
  195. package/src/lib/graph/generate-cypher.ts +0 -192
  196. package/src/lib/graph/seed.ts +0 -162
  197. package/src/lib/graph/validate.ts +0 -78
  198. package/src/lib/routes/index.ts +0 -4
  199. package/src/lib/ws/events.ts +0 -39
  200. package/src/lib/ws/index.ts +0 -11
  201. package/src/lib/ws/notify.ts +0 -9
  202. package/src/sample/db/client.ts +0 -9
  203. package/src/sample/db/migrations/20260411023125_rich_purple_man/migration.sql +0 -5
  204. package/src/sample/db/migrations/20260411023125_rich_purple_man/snapshot.json +0 -50
  205. package/src/sample/db/schema.ts +0 -10
  206. package/src/sample/domains/account/concepts/session.concept.ts +0 -17
  207. package/src/sample/domains/account/endpoints/listCurrentSessions.endpoint.ts +0 -31
  208. package/src/sample/domains/account/endpoints/revokeAllOtherSessions.endpoint.ts +0 -22
  209. package/src/sample/domains/account/endpoints/revokeCurrentSession.endpoint.ts +0 -33
  210. package/src/sample/domains/account/index.ts +0 -18
  211. package/src/sample/domains/appraisals/concepts/appraisalCycle.concept.ts +0 -15
  212. package/src/sample/domains/appraisals/concepts/appraisalRecord.concept.ts +0 -20
  213. package/src/sample/domains/appraisals/concepts/appraisalRecordCompetency.concept.ts +0 -10
  214. package/src/sample/domains/appraisals/concepts/appraisalRecordFunctionalRequirement.concept.ts +0 -10
  215. package/src/sample/domains/appraisals/concepts/appraisalRecordGoal.concept.ts +0 -17
  216. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordById.endpoint.ts +0 -321
  217. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordCompetencies.endpoint.ts +0 -120
  218. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordFunctionalRequirements.endpoint.ts +0 -126
  219. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordGoals.endpoint.ts +0 -104
  220. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordScore.endpoint.ts +0 -58
  221. package/src/sample/domains/appraisals/endpoints/listAppraisalCyclesSimple.endpoint.ts +0 -52
  222. package/src/sample/domains/appraisals/endpoints/listAppraisalRecords.endpoint.ts +0 -245
  223. package/src/sample/domains/appraisals/endpoints/listAppraisalTemplatesSimple.endpoint.ts +0 -52
  224. package/src/sample/domains/appraisals/index.ts +0 -40
  225. package/src/sample/domains/appraisals/rules/appraisalRecordScoping.rule.ts +0 -7
  226. package/src/sample/domains/attendance/concepts/attendancePermission.concept.ts +0 -17
  227. package/src/sample/domains/attendance/concepts/attendanceTransaction.concept.ts +0 -17
  228. package/src/sample/domains/attendance/concepts/publicHoliday.concept.ts +0 -10
  229. package/src/sample/domains/attendance/concepts/punch.concept.ts +0 -19
  230. package/src/sample/domains/attendance/endpoints/getAttendanceTransactionById.endpoint.ts +0 -444
  231. package/src/sample/domains/attendance/endpoints/getCurrentAttendanceProfile.endpoint.ts +0 -886
  232. package/src/sample/domains/attendance/endpoints/getCurrentAttendanceStatistics.endpoint.ts +0 -49
  233. package/src/sample/domains/attendance/endpoints/listAttendancePermissions.endpoint.ts +0 -176
  234. package/src/sample/domains/attendance/endpoints/listAttendanceTransactionStates.endpoint.ts +0 -26
  235. package/src/sample/domains/attendance/endpoints/listAttendanceTransactions.endpoint.ts +0 -373
  236. package/src/sample/domains/attendance/endpoints/listPublicHolidaysSimple.endpoint.ts +0 -52
  237. package/src/sample/domains/attendance/endpoints/listPunchTypes.endpoint.ts +0 -25
  238. package/src/sample/domains/attendance/endpoints/listPunches.endpoint.ts +0 -178
  239. package/src/sample/domains/attendance/index.ts +0 -41
  240. package/src/sample/domains/attendance/services/attendancePermissions.service.ts +0 -15
  241. package/src/sample/domains/employees/concepts/employee.concept.ts +0 -20
  242. package/src/sample/domains/employees/concepts/employeeBankAccount.concept.ts +0 -19
  243. package/src/sample/domains/employees/concepts/employeeChild.concept.ts +0 -19
  244. package/src/sample/domains/employees/concepts/employeeDocument.concept.ts +0 -21
  245. package/src/sample/domains/employees/concepts/employeeEvent.concept.ts +0 -21
  246. package/src/sample/domains/employees/concepts/employeeExperience.concept.ts +0 -18
  247. package/src/sample/domains/employees/concepts/employeeJobLevelChange.concept.ts +0 -21
  248. package/src/sample/domains/employees/concepts/employeeMedal.concept.ts +0 -20
  249. package/src/sample/domains/employees/concepts/employeeQualification.concept.ts +0 -20
  250. package/src/sample/domains/employees/concepts/employeeSpouse.concept.ts +0 -21
  251. package/src/sample/domains/employees/concepts/employeeViolation.concept.ts +0 -19
  252. package/src/sample/domains/employees/endpoints/getCurrentEmployee.endpoint.ts +0 -1651
  253. package/src/sample/domains/employees/endpoints/getCurrentIdentity.endpoint.ts +0 -197
  254. package/src/sample/domains/employees/endpoints/getCurrentManagers.endpoint.ts +0 -142
  255. package/src/sample/domains/employees/endpoints/getEmployeeById.endpoint.ts +0 -1659
  256. package/src/sample/domains/employees/endpoints/getEmployeeStatistics.endpoint.ts +0 -43
  257. package/src/sample/domains/employees/endpoints/listBankAccounts.endpoint.ts +0 -223
  258. package/src/sample/domains/employees/endpoints/listChildren.endpoint.ts +0 -211
  259. package/src/sample/domains/employees/endpoints/listDocuments.endpoint.ts +0 -222
  260. package/src/sample/domains/employees/endpoints/listEmployeesSimple.endpoint.ts +0 -156
  261. package/src/sample/domains/employees/endpoints/listEvents.endpoint.ts +0 -120
  262. package/src/sample/domains/employees/endpoints/listExperiences.endpoint.ts +0 -188
  263. package/src/sample/domains/employees/endpoints/listJobLevelChanges.endpoint.ts +0 -240
  264. package/src/sample/domains/employees/endpoints/listMedals.endpoint.ts +0 -266
  265. package/src/sample/domains/employees/endpoints/listQualifications.endpoint.ts +0 -265
  266. package/src/sample/domains/employees/endpoints/listSpouses.endpoint.ts +0 -278
  267. package/src/sample/domains/employees/endpoints/listViolations.endpoint.ts +0 -245
  268. package/src/sample/domains/employees/index.ts +0 -75
  269. package/src/sample/domains/employees/services/employees.service.ts +0 -181
  270. package/src/sample/domains/index.ts +0 -10
  271. package/src/sample/domains/leaves/concepts/leave.concept.ts +0 -21
  272. package/src/sample/domains/leaves/concepts/leaveBalance.concept.ts +0 -17
  273. package/src/sample/domains/leaves/concepts/leaveBalanceTransaction.concept.ts +0 -16
  274. package/src/sample/domains/leaves/concepts/leaveType.concept.ts +0 -13
  275. package/src/sample/domains/leaves/concepts/leaveTypes.concept.ts +0 -205
  276. package/src/sample/domains/leaves/endpoints/getBalance.endpoint.ts +0 -53
  277. package/src/sample/domains/leaves/endpoints/getLeaveById.endpoint.ts +0 -53
  278. package/src/sample/domains/leaves/endpoints/listBalanceTransactions.endpoint.ts +0 -196
  279. package/src/sample/domains/leaves/endpoints/listBalanceTypes.endpoint.ts +0 -26
  280. package/src/sample/domains/leaves/endpoints/listBalances.endpoint.ts +0 -201
  281. package/src/sample/domains/leaves/endpoints/listLeaveTypes.endpoint.ts +0 -28
  282. package/src/sample/domains/leaves/endpoints/listLeaves.endpoint.ts +0 -234
  283. package/src/sample/domains/leaves/index.ts +0 -38
  284. package/src/sample/domains/leaves/services/leaveRequests.service.ts +0 -232
  285. package/src/sample/domains/notifications/concepts/notification.concept.ts +0 -19
  286. package/src/sample/domains/notifications/endpoints/countMyNotifications.endpoint.ts +0 -21
  287. package/src/sample/domains/notifications/endpoints/getMyNotification.endpoint.ts +0 -57
  288. package/src/sample/domains/notifications/endpoints/listMyNotifications.endpoint.ts +0 -59
  289. package/src/sample/domains/notifications/endpoints/markMyNotificationAsRead.endpoint.ts +0 -32
  290. package/src/sample/domains/notifications/index.ts +0 -20
  291. package/src/sample/domains/payroll/concepts/payslip.concept.ts +0 -18
  292. package/src/sample/domains/payroll/concepts/salaryCertificate.concept.ts +0 -17
  293. package/src/sample/domains/payroll/concepts/salaryCertificateRequestingEntity.concept.ts +0 -8
  294. package/src/sample/domains/payroll/endpoints/getPayslipById.endpoint.ts +0 -238
  295. package/src/sample/domains/payroll/endpoints/listPayslips.endpoint.ts +0 -216
  296. package/src/sample/domains/payroll/endpoints/listSalaryCertificateLanguages.endpoint.ts +0 -25
  297. package/src/sample/domains/payroll/endpoints/listSalaryCertificateRequestingEntities.endpoint.ts +0 -62
  298. package/src/sample/domains/payroll/endpoints/listSalaryCertificateTypes.endpoint.ts +0 -24
  299. package/src/sample/domains/payroll/endpoints/listSalaryCertificates.endpoint.ts +0 -122
  300. package/src/sample/domains/payroll/endpoints/previewSalaryCertificate.endpoint.ts +0 -45
  301. package/src/sample/domains/payroll/index.ts +0 -32
  302. package/src/sample/domains/payroll/rules/payrollEmployeeContext.rule.ts +0 -7
  303. package/src/sample/domains/payroll/services/salaryCertificates.service.ts +0 -15
  304. package/src/sample/domains/servicing/concepts/service.concept.ts +0 -6
  305. package/src/sample/domains/servicing/concepts/serviceRequest.concept.ts +0 -12
  306. package/src/sample/domains/servicing/concepts/serviceRequestApproval.concept.ts +0 -9
  307. package/src/sample/domains/servicing/endpoints/cancelServiceRequest.endpoint.ts +0 -21
  308. package/src/sample/domains/servicing/endpoints/createServiceRequest.endpoint.ts +0 -511
  309. package/src/sample/domains/servicing/endpoints/getServiceRequest.endpoint.ts +0 -352
  310. package/src/sample/domains/servicing/endpoints/getServiceRequestFormItems.endpoint.ts +0 -219
  311. package/src/sample/domains/servicing/endpoints/getServiceRequestTransactionFormItems.endpoint.ts +0 -216
  312. package/src/sample/domains/servicing/endpoints/getServiceStates.endpoint.ts +0 -47
  313. package/src/sample/domains/servicing/endpoints/invokeServiceRequestAction.endpoint.ts +0 -139
  314. package/src/sample/domains/servicing/endpoints/listServiceRequests.endpoint.ts +0 -292
  315. package/src/sample/domains/servicing/endpoints/listServices.endpoint.ts +0 -108
  316. package/src/sample/domains/servicing/endpoints/updateServiceRequest.endpoint.ts +0 -355
  317. package/src/sample/domains/servicing/index.ts +0 -44
  318. package/src/sample/domains/servicing/rules/checkValidActions.rule.ts +0 -7
  319. package/src/sample/domains/servicing/rules/createServiceRequest.rule.ts +0 -7
  320. package/src/sample/domains/servicing/rules/filterServiceRequestsByState.rule.ts +0 -7
  321. package/src/sample/domains/servicing/rules/passingOptionFields.rule.ts +0 -7
  322. package/src/sample/domains/shared/rules/pagination.rule.ts +0 -7
  323. package/src/sample/domains/suggestions/concepts/suggestion.concept.ts +0 -19
  324. package/src/sample/domains/suggestions/concepts/suggestionCategory.concept.ts +0 -7
  325. package/src/sample/domains/suggestions/concepts/suggestionImpactLevel.concept.ts +0 -7
  326. package/src/sample/domains/suggestions/endpoints/createSuggestion.endpoint.ts +0 -705
  327. package/src/sample/domains/suggestions/endpoints/deleteSuggestion.endpoint.ts +0 -32
  328. package/src/sample/domains/suggestions/endpoints/getSuggestionById.endpoint.ts +0 -635
  329. package/src/sample/domains/suggestions/endpoints/listSuggestionCategories.endpoint.ts +0 -62
  330. package/src/sample/domains/suggestions/endpoints/listSuggestionImpactLevels.endpoint.ts +0 -62
  331. package/src/sample/domains/suggestions/endpoints/listSuggestionStates.endpoint.ts +0 -24
  332. package/src/sample/domains/suggestions/endpoints/listSuggestions.endpoint.ts +0 -132
  333. package/src/sample/domains/suggestions/endpoints/updateSuggestion.endpoint.ts +0 -690
  334. package/src/sample/domains/suggestions/endpoints/updateSuggestionState.endpoint.ts +0 -39
  335. package/src/sample/domains/suggestions/index.ts +0 -34
  336. package/src/sample/domains/suggestions/rules/suggestionScope.rule.ts +0 -7
  337. package/src/sample/domains/surveys/concepts/survey.concept.ts +0 -10
  338. package/src/sample/domains/surveys/concepts/surveyResponse.concept.ts +0 -14
  339. package/src/sample/domains/surveys/endpoints/getSurveyResponseById.endpoint.ts +0 -131
  340. package/src/sample/domains/surveys/endpoints/getTargetedSurvey.endpoint.ts +0 -129
  341. package/src/sample/domains/surveys/endpoints/listTargetedSurveys.endpoint.ts +0 -160
  342. package/src/sample/domains/surveys/endpoints/respondToSurvey.endpoint.ts +0 -144
  343. package/src/sample/domains/surveys/index.ts +0 -21
  344. package/src/sample/drizzle.config.ts +0 -11
  345. package/src/sample/index.bak.ts +0 -341
  346. package/src/sample/index.ts +0 -318
  347. package/src/sample/official-statements/defaults.ts +0 -57
  348. package/src/sample/official-statements/routes.ts +0 -137
  349. package/src/sample/official-statements/service.ts +0 -148
  350. package/src/sample/official-statements/types.ts +0 -23
  351. /package/src/lib/db/migrations/{20260326231647_nice_speedball → mssql/20260326231647_nice_speedball}/migration.sql +0 -0
  352. /package/src/lib/db/migrations/{20260326231647_nice_speedball → mssql/20260326231647_nice_speedball}/snapshot.json +0 -0
@@ -6,14 +6,23 @@
6
6
  * or environment variables.
7
7
  */
8
8
 
9
- import type { EmbeddingModel } from "ai";
10
- import { embed } from "ai";
11
- import type { Neo4jClient } from "./neo4j.ts";
12
- import type { ConceptDef } from "./types.ts";
9
+ import {
10
+ GRAPH_SCHEMA,
11
+ GRAPH_SCHEMA_VERSION,
12
+ type ConceptDef,
13
+ type EmbedFn,
14
+ type Neo4jClient,
15
+ } from "@cortex/contracts/graph";
13
16
 
14
- // ---------------------------------------------------------------------------
15
- // Config
16
- // ---------------------------------------------------------------------------
17
+ // The graph's vocabulary, aliased short so the Cypher below still reads as Cypher.
18
+ const { label: L, rel: R, prop: P } = GRAPH_SCHEMA;
19
+
20
+ /**
21
+ * Seed data, not schema: the concept a project names to expose its generic
22
+ * service-request endpoints, which is why it stays here rather than in
23
+ * `schema.ts` — the seeder writes whatever concepts a domain declares.
24
+ */
25
+ const SERVICE_REQUEST = "ServiceRequest";
17
26
 
18
27
  export type RerankerConfig = {
19
28
  url: string;
@@ -22,14 +31,10 @@ export type RerankerConfig = {
22
31
 
23
32
  export type ResolverConfig = {
24
33
  neo4j: Neo4jClient;
25
- embeddingModel: EmbeddingModel;
34
+ embed: EmbedFn;
26
35
  reranker?: RerankerConfig;
27
36
  };
28
37
 
29
- // ---------------------------------------------------------------------------
30
- // Result types
31
- // ---------------------------------------------------------------------------
32
-
33
38
  type EndpointDependency = {
34
39
  depPath: string;
35
40
  depMethod: string;
@@ -39,6 +44,8 @@ type EndpointDependency = {
39
44
 
40
45
  type EndpointRelation = "read" | "write";
41
46
 
47
+ type RelationType = typeof R.queriedVia | typeof R.mutatedVia;
48
+
42
49
  export type ResolvedEndpoint = {
43
50
  concept: string;
44
51
  relation: EndpointRelation;
@@ -68,13 +75,9 @@ export type ResolvedContext = {
68
75
  services: ResolvedService[];
69
76
  };
70
77
 
71
- // ---------------------------------------------------------------------------
72
- // Internal row types
73
- // ---------------------------------------------------------------------------
74
-
75
78
  type EndpointRow = {
76
79
  concept: string;
77
- relationType: "QUERIED_VIA" | "MUTATED_VIA";
80
+ relationType: RelationType;
78
81
  name: string;
79
82
  path: string;
80
83
  method: string;
@@ -95,11 +98,63 @@ type ServiceRow = {
95
98
  rules: (string | null)[];
96
99
  };
97
100
 
98
- // ---------------------------------------------------------------------------
99
- // Public API
100
- // ---------------------------------------------------------------------------
101
+ /**
102
+ * Cleared only by a restart, which is also the only thing that can change which
103
+ * schema this process reads with. A re-seed under a running server is the case
104
+ * this misses, and re-seeding at the same version is the overwhelmingly common one.
105
+ */
106
+ let versionChecked = false;
107
+
108
+ /**
109
+ * `@m6d/cortex-cli` stamps the schema version it seeded onto the graph; this reads
110
+ * it back before any Cypher below runs. Lazily, on the first resolve rather than at
111
+ * boot, so a Neo4j that happens to be down cannot stop `serve()`.
112
+ */
113
+ async function assertGraphVersion(neo4j: Neo4jClient) {
114
+ if (versionChecked) return;
115
+
116
+ const rows: unknown = await neo4j
117
+ .query(
118
+ `OPTIONAL MATCH (g:${L.marker}) WITH g LIMIT 1
119
+ OPTIONAL MATCH (c:${L.concept})
120
+ RETURN g.${P.version} AS version, count(c) AS concepts`,
121
+ )
122
+ .then(JSON.parse)
123
+ .catch(() => undefined);
124
+
125
+ // An unreachable graph answers with an error object rather than a row array.
126
+ // That is the resolve's own failure to report, not schema skew.
127
+ if (!Array.isArray(rows)) return;
128
+
129
+ // OPTIONAL MATCH answers a missing marker with `null`, not with a missing row.
130
+ const { version: stamped, concepts } =
131
+ (rows as { version?: number | null; concepts?: number }[])[0] ?? {};
132
+ if (stamped === GRAPH_SCHEMA_VERSION) {
133
+ versionChecked = true;
134
+ return;
135
+ }
136
+
137
+ // No stamp over an empty graph is a project mid-setup (Neo4j configured, seed
138
+ // not yet run), not skew: resolve as the empty context it is. Not cached, so
139
+ // the stamp is validated as soon as a seed lands.
140
+ if (stamped == null && (concepts ?? 0) === 0) return;
141
+
142
+ throw new Error(
143
+ [
144
+ stamped == null
145
+ ? `The Neo4j graph holds data but no schema stamp, and this server reads graph schema v${GRAPH_SCHEMA_VERSION}.`
146
+ : `The Neo4j graph was written with graph schema v${stamped}, and this server reads v${GRAPH_SCHEMA_VERSION}.`,
147
+ "Writer and reader disagree on the labels, relationships and property names, so retrieval would return empty or wrong results instead of failing.",
148
+ "Re-seed the graph with `bunx @m6d/cortex-cli graph seed`, or install the @m6d/cortex-server that matches the graph you have.",
149
+ ].join(" "),
150
+ );
151
+ }
101
152
 
102
153
  export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
154
+ // Before the first query and outside the catch below: skew is invisible to
155
+ // every check further down, which is the whole reason it is checked here.
156
+ await assertGraphVersion(config.neo4j);
157
+
103
158
  const empty: ResolvedContext = {
104
159
  readEndpoints: [],
105
160
  writeEndpoints: [],
@@ -107,16 +162,13 @@ export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
107
162
  };
108
163
 
109
164
  try {
110
- const { embedding } = await embed({
111
- model: config.embeddingModel,
112
- value: prompt,
113
- });
165
+ const embedding = (await config.embed([prompt]))[0]!;
114
166
 
115
167
  const conceptsRaw = await config.neo4j.query(
116
- `CALL db.index.vector.queryNodes('concept_embeddings', 10, $embedding)
168
+ `CALL db.index.vector.queryNodes('${GRAPH_SCHEMA.conceptEmbeddingsIndex}', 10, $embedding)
117
169
  YIELD node, score
118
170
  WHERE score > 0.3
119
- RETURN node.name AS name, node.description AS description, score
171
+ RETURN node.${P.name} AS name, node.${P.description} AS description, score
120
172
  ORDER BY score DESC`,
121
173
  { embedding },
122
174
  );
@@ -139,74 +191,55 @@ export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
139
191
 
140
192
  const endpointsRaw = await config.neo4j.query(
141
193
  `UNWIND $names AS conceptName
142
- MATCH (c:Concept {name: conceptName})-[:SPECIALIZES*0..3]->(ancestor:Concept)
194
+ MATCH (c:${L.concept} {${P.name}: conceptName})-[:${R.specializes}*0..3]->(ancestor:${L.concept})
143
195
  WITH conceptName, collect(DISTINCT ancestor) AS family
144
196
  UNWIND family AS related
145
- MATCH (related)-[rel:QUERIED_VIA|MUTATED_VIA]->(e:Endpoint)
197
+ MATCH (related)-[rel:${R.queriedVia}|${R.mutatedVia}]->(e:${L.endpoint})
146
198
  WITH conceptName, related, rel, e
147
- OPTIONAL MATCH (e)-[d:DEPENDS_ON]->(dep:Endpoint)
199
+ OPTIONAL MATCH (e)-[d:${R.dependsOn}]->(dep:${L.endpoint})
148
200
  WITH conceptName, related, rel, e,
149
201
  collect(DISTINCT {
150
- depPath: dep.path,
151
- depMethod: dep.method,
152
- paramName: d.paramName,
153
- fromField: d.fromField
202
+ depPath: dep.${P.path},
203
+ depMethod: dep.${P.method},
204
+ paramName: d.${P.paramName},
205
+ fromField: d.${P.fromField}
154
206
  }) AS dependencies
155
- OPTIONAL MATCH (rEndpoint:Rule)-[:GOVERNS]->(e)
156
- OPTIONAL MATCH (rConcept:Rule)-[:GOVERNS]->(related)
207
+ OPTIONAL MATCH (rEndpoint:${L.rule})-[:${R.governs}]->(e)
208
+ OPTIONAL MATCH (rConcept:${L.rule})-[:${R.governs}]->(related)
157
209
  RETURN conceptName AS concept,
158
210
  type(rel) AS relationType,
159
- e.name AS name,
160
- e.path AS path,
161
- e.method AS method,
162
- e.params AS params,
163
- e.body AS body,
164
- e.response AS response,
165
- e.metadata AS metadata,
211
+ e.${P.name} AS name,
212
+ e.${P.path} AS path,
213
+ e.${P.method} AS method,
214
+ e.${P.params} AS params,
215
+ e.${P.body} AS body,
216
+ e.${P.response} AS response,
217
+ e.${P.metadata} AS metadata,
166
218
  dependencies,
167
- collect(DISTINCT rEndpoint.description) +
168
- collect(DISTINCT rConcept.description) AS rules`,
219
+ collect(DISTINCT rEndpoint.${P.description}) +
220
+ collect(DISTINCT rConcept.${P.description}) AS rules`,
169
221
  { names: conceptNames },
170
222
  );
171
223
 
172
224
  const endpointRows = asArray<EndpointRow>(JSON.parse(endpointsRaw));
173
- const allEndpoints = dedupeEndpoints(
174
- endpointRows.map(function (row) {
175
- return {
176
- concept: row.concept,
177
- relation:
178
- row.relationType === "MUTATED_VIA" ? ("write" as const) : ("read" as const),
179
- name: row.name,
180
- path: row.path,
181
- method: row.method,
182
- params: row.params ?? "[]",
183
- body: row.body ?? "[]",
184
- response: row.response ?? "[]",
185
- metadata: row.metadata ?? "{}",
186
- dependencies: row.dependencies.filter(
187
- (dep): dep is EndpointDependency => dep.depPath != null,
188
- ),
189
- rules: row.rules.filter((rule): rule is string => Boolean(rule)),
190
- };
191
- }),
192
- );
225
+ const allEndpoints = dedupeEndpoints(endpointRows.map(toResolvedEndpoint));
193
226
 
194
227
  const servicesRaw = await config.neo4j.query(
195
228
  `UNWIND $names AS conceptName
196
- MATCH (c:Concept {name: conceptName})-[:SPECIALIZES*0..3]->(ancestor:Concept)
229
+ MATCH (c:${L.concept} {${P.name}: conceptName})-[:${R.specializes}*0..3]->(ancestor:${L.concept})
197
230
  WITH conceptName, collect(DISTINCT ancestor) AS family
198
231
  UNWIND family AS related
199
- MATCH (related)-[:REQUESTED_VIA]->(svc:Service)
232
+ MATCH (related)-[:${R.requestedVia}]->(svc:${L.service})
200
233
  WITH conceptName, related, svc
201
- OPTIONAL MATCH (rService:Rule)-[:GOVERNS]->(svc)
202
- OPTIONAL MATCH (rConcept:Rule)-[:GOVERNS]->(related)
234
+ OPTIONAL MATCH (rService:${L.rule})-[:${R.governs}]->(svc)
235
+ OPTIONAL MATCH (rConcept:${L.rule})-[:${R.governs}]->(related)
203
236
  RETURN conceptName AS concept,
204
- svc.name AS serviceName,
205
- svc.builtInId AS builtInId,
206
- svc.description AS description,
207
- svc.metadata AS metadata,
208
- collect(DISTINCT rService.description) +
209
- collect(DISTINCT rConcept.description) AS rules`,
237
+ svc.${P.name} AS serviceName,
238
+ svc.${P.builtInId} AS builtInId,
239
+ svc.${P.description} AS description,
240
+ svc.${P.metadata} AS metadata,
241
+ collect(DISTINCT rService.${P.description}) +
242
+ collect(DISTINCT rConcept.${P.description}) AS rules`,
210
243
  { names: conceptNames },
211
244
  );
212
245
 
@@ -228,53 +261,34 @@ export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
228
261
  let servicingEndpoints: ResolvedEndpoint[] = [];
229
262
  if (services.length > 0) {
230
263
  const servicingRaw = await config.neo4j.query(
231
- `MATCH (c:Concept {name: 'ServiceRequest'})-[rel:QUERIED_VIA|MUTATED_VIA]->(e:Endpoint)
232
- OPTIONAL MATCH (e)-[d:DEPENDS_ON]->(dep:Endpoint)
264
+ `MATCH (c:${L.concept} {${P.name}: '${SERVICE_REQUEST}'})-[rel:${R.queriedVia}|${R.mutatedVia}]->(e:${L.endpoint})
265
+ OPTIONAL MATCH (e)-[d:${R.dependsOn}]->(dep:${L.endpoint})
233
266
  WITH c, rel, e,
234
267
  collect(DISTINCT {
235
- depPath: dep.path,
236
- depMethod: dep.method,
237
- paramName: d.paramName,
238
- fromField: d.fromField
268
+ depPath: dep.${P.path},
269
+ depMethod: dep.${P.method},
270
+ paramName: d.${P.paramName},
271
+ fromField: d.${P.fromField}
239
272
  }) AS dependencies
240
- OPTIONAL MATCH (rEndpoint:Rule)-[:GOVERNS]->(e)
241
- OPTIONAL MATCH (rConcept:Rule)-[:GOVERNS]->(c)
242
- RETURN 'ServiceRequest' AS concept,
273
+ OPTIONAL MATCH (rEndpoint:${L.rule})-[:${R.governs}]->(e)
274
+ OPTIONAL MATCH (rConcept:${L.rule})-[:${R.governs}]->(c)
275
+ RETURN '${SERVICE_REQUEST}' AS concept,
243
276
  type(rel) AS relationType,
244
- e.name AS name,
245
- e.path AS path,
246
- e.method AS method,
247
- e.params AS params,
248
- e.body AS body,
249
- e.response AS response,
250
- e.metadata AS metadata,
277
+ e.${P.name} AS name,
278
+ e.${P.path} AS path,
279
+ e.${P.method} AS method,
280
+ e.${P.params} AS params,
281
+ e.${P.body} AS body,
282
+ e.${P.response} AS response,
283
+ e.${P.metadata} AS metadata,
251
284
  dependencies,
252
- collect(DISTINCT rEndpoint.description) +
253
- collect(DISTINCT rConcept.description) AS rules`,
285
+ collect(DISTINCT rEndpoint.${P.description}) +
286
+ collect(DISTINCT rConcept.${P.description}) AS rules`,
254
287
  {},
255
288
  );
256
289
 
257
290
  servicingEndpoints = dedupeEndpoints(
258
- asArray<EndpointRow>(JSON.parse(servicingRaw)).map(function (row) {
259
- return {
260
- concept: row.concept,
261
- relation:
262
- row.relationType === "MUTATED_VIA"
263
- ? ("write" as const)
264
- : ("read" as const),
265
- name: row.name,
266
- path: row.path,
267
- method: row.method,
268
- params: row.params ?? "[]",
269
- body: row.body ?? "[]",
270
- response: row.response ?? "[]",
271
- metadata: row.metadata ?? "{}",
272
- dependencies: row.dependencies.filter(
273
- (dep): dep is EndpointDependency => dep.depPath != null,
274
- ),
275
- rules: row.rules.filter((rule): rule is string => Boolean(rule)),
276
- };
277
- }),
291
+ asArray<EndpointRow>(JSON.parse(servicingRaw)).map(toResolvedEndpoint),
278
292
  );
279
293
  }
280
294
 
@@ -291,14 +305,30 @@ export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
291
305
  }
292
306
  }
293
307
 
294
- // ---------------------------------------------------------------------------
295
- // Internal helpers
296
- // ---------------------------------------------------------------------------
297
-
298
308
  function asArray<T>(value: unknown) {
299
309
  return Array.isArray(value) ? (value as T[]) : ([] as T[]);
300
310
  }
301
311
 
312
+ /** Maps a Cypher row to an endpoint. The string defaults stand in for columns
313
+ * Neo4j returns as null, which downstream JSON.parse calls would choke on. */
314
+ function toResolvedEndpoint(row: EndpointRow) {
315
+ return {
316
+ concept: row.concept,
317
+ relation: row.relationType === R.mutatedVia ? ("write" as const) : ("read" as const),
318
+ name: row.name,
319
+ path: row.path,
320
+ method: row.method,
321
+ params: row.params ?? "[]",
322
+ body: row.body ?? "[]",
323
+ response: row.response ?? "[]",
324
+ metadata: row.metadata ?? "{}",
325
+ dependencies: row.dependencies.filter(
326
+ (dep): dep is EndpointDependency => dep.depPath != null,
327
+ ),
328
+ rules: row.rules.filter((rule): rule is string => Boolean(rule)),
329
+ } satisfies ResolvedEndpoint;
330
+ }
331
+
302
332
  function dedupeEndpoints(endpoints: ResolvedEndpoint[]) {
303
333
  const seen = new Set<string>();
304
334
  const deduped: ResolvedEndpoint[] = [];
package/src/lib/index.ts CHANGED
@@ -2,15 +2,21 @@
2
2
  export type {
3
3
  CortexConfig,
4
4
  CortexAgentDefinition,
5
+ ControlCenterConfig,
5
6
  KnowledgeConfig,
6
7
  DatabaseConfig,
8
+ RedisConfig,
7
9
  StorageConfig,
8
10
  PromptContext,
9
11
  ToolContext,
12
+ ToolSet,
10
13
  ToolSetFactory,
11
14
  } from "./config";
12
15
  export { defineAgent } from "./config";
13
16
 
17
+ // Provider shape, so a consumer can name what it passes to the model helpers.
18
+ export type { ProviderConfig } from "./ai/helpers";
19
+
14
20
  export type { ContextConfig, ThreadContextMeta } from "./ai/context/types";
15
21
 
16
22
  // Types consumers may need
@@ -28,12 +34,13 @@ export type {
28
34
  export { createRequestInterceptor } from "./ai/interceptors/request-interceptor";
29
35
 
30
36
  // Tools (consumers may register custom tools or use built-in ones)
31
- export { captureFilesTool } from "./ai/tools/capture-files.tool";
32
37
  export { createQueryGraphTool } from "./ai/tools/query-graph.tool";
33
38
  export { createExecuteCodeTool } from "./ai/tools/execute-code.tool";
34
39
 
35
- // Graph (consumers may use independently)
40
+ // Graph (consumers may use independently). The contract itself lives in
41
+ // @cortex/contracts/graph — see ./graph/index — and is re-exported through here.
36
42
  export * from "./graph/index";
37
43
 
38
- // WebSocket event types
39
- export type { WsEvent, ThreadTitleUpdatedEvent } from "./ws/index";
44
+ // The wire contract, re-exported so consumers get it without a second import path.
45
+ // Its home is @cortex/contracts/wire, which the client SDKs compile in too.
46
+ export * from "@cortex/contracts/wire";
@@ -0,0 +1,44 @@
1
+ import { Redis } from "ioredis";
2
+
3
+ /**
4
+ * The two long-lived connections everything Redis-backed shares. A subscriber
5
+ * connection cannot issue normal commands, so pub/sub listening gets its own;
6
+ * the publisher carries everything else (claims, stream appends, PUBLISH).
7
+ * Blocking stream reads never run on either — each reader duplicates its own
8
+ * connection, or one blocked XREAD would stall every claim and append behind it.
9
+ */
10
+ export type RedisConnections = {
11
+ publisher: Redis;
12
+ subscriber: Redis;
13
+ };
14
+
15
+ /**
16
+ * Without an `error` listener ioredis raises unhandled-error events, which can
17
+ * crash the process — with one, a flapping or unreachable Redis becomes a log
18
+ * line while ioredis reconnects underneath.
19
+ */
20
+ export function withRedisLogging(client: Redis, role: string) {
21
+ client.on("error", (error: unknown) => console.error(`[cortex-server] redis ${role}:`, error));
22
+ return client;
23
+ }
24
+
25
+ /**
26
+ * Cached per URL on `globalThis` so hot reloads and repeated `createCortex`
27
+ * calls reuse the connections instead of opening new ones each time.
28
+ */
29
+ const store = globalThis as typeof globalThis & {
30
+ __cortexRedis?: Map<string, RedisConnections>;
31
+ };
32
+
33
+ export function getRedisConnections(url: string) {
34
+ store.__cortexRedis ??= new Map();
35
+ let connections = store.__cortexRedis.get(url);
36
+ if (!connections) {
37
+ connections = {
38
+ publisher: withRedisLogging(new Redis(url), "publisher"),
39
+ subscriber: withRedisLogging(new Redis(url), "subscriber"),
40
+ };
41
+ store.__cortexRedis.set(url, connections);
42
+ }
43
+ return connections;
44
+ }
@@ -1,85 +1,114 @@
1
+ import { EventType, chatParamsFromRequest, resumeServerSentEventsResponse } from "@tanstack/ai";
2
+ import type { AbortRunResult } from "@cortex/contracts/wire";
1
3
  import { Hono } from "hono";
2
- import { zValidator } from "@hono/zod-validator";
3
- import z from "zod";
4
+ import { bodyLimit } from "hono/body-limit";
4
5
  import { HTTPException } from "hono/http-exception";
5
- import type { CortexAppEnv } from "../types.ts";
6
- import { requireAuth } from "../auth/middleware.ts";
7
- import { stream } from "../ai/index.ts";
8
- import { subscribe, abortStream } from "../ai/active-streams.ts";
9
- import { notify } from "../ws/index.ts";
10
- import { toThreadSummary } from "../types.ts";
6
+ import type { CortexAppEnv } from "@/types";
7
+ import { requireAuth } from "@/auth/middleware";
8
+ import { startTurn } from "@/ai/index";
9
+ import { abortRun, getRunId, joinLog, offsetBelongsToRun } from "@/ai/active-runs";
10
+ import { notify } from "@/ws/connections";
11
+ import { requireOwnedThread } from "./owned-thread";
12
+ import { toThreadSummary } from "@/types";
13
+
14
+ const CHAT_BODY_MAX_BYTES = 1024 * 1024;
15
+
16
+ /**
17
+ * The answer to "attach me to the run on this thread" when there is no longer
18
+ * one. An empty body would be the obvious reply, but the client reads every
19
+ * attach as a stream and treats a missing one as a transport failure — so the
20
+ * run is reported over, which is what actually happened, and the client settles
21
+ * the turn from the database like any other ending.
22
+ */
23
+ function runEnded() {
24
+ const finished = { type: EventType.RUN_FINISHED, timestamp: Date.now() };
25
+
26
+ return new Response(`data: ${JSON.stringify(finished)}\n\n`, {
27
+ headers: {
28
+ "Content-Type": "text/event-stream",
29
+ "Cache-Control": "no-cache",
30
+ Connection: "keep-alive",
31
+ },
32
+ });
33
+ }
11
34
 
12
35
  export function createChatRoutes() {
13
36
  const app = new Hono<CortexAppEnv>();
14
37
 
15
- app.post(
16
- "/chat",
17
- requireAuth,
18
- zValidator(
19
- "json",
20
- z.object({
21
- id: z.string(),
22
- messages: z.array(z.unknown()),
23
- }),
24
- ),
25
- async function (c) {
26
- const config = c.get("agentConfig");
27
- const agentId = c.get("agentId");
28
- const { id: userId, token } = c.get("user");
29
- const { messages, id: threadId } = c.req.valid("json");
30
- const requestContext = config.resolveRequestContext
31
- ? await config.resolveRequestContext(c.req.raw)
32
- : {};
33
-
34
- const thread = await config.db.threads.getById(userId, threadId);
35
-
36
- if (!thread || thread.agentId !== agentId) {
37
- throw new HTTPException(404, { message: "Not found" });
38
+ app.post("/chat", requireAuth, bodyLimit({ maxSize: CHAT_BODY_MAX_BYTES }), async function (c) {
39
+ const config = c.get("agentConfig");
40
+ const { id: userId, token } = c.get("user");
41
+
42
+ let params: Awaited<ReturnType<typeof chatParamsFromRequest>>;
43
+ try {
44
+ params = await chatParamsFromRequest(c.req.raw);
45
+ } catch (thrown) {
46
+ // The AG-UI validator rejects a malformed body by throwing the
47
+ // 400 it wants sent; Hono has no such convention, so return it.
48
+ if (thrown instanceof Response) return thrown;
49
+ throw thrown;
50
+ }
51
+
52
+ const thread = await requireOwnedThread(c, params.threadId);
53
+
54
+ // A dropped connection re-POSTs the same body with Last-Event-ID.
55
+ // The run is already producing into its log, so this replays from
56
+ // where the client left off rather than starting a second turn. A
57
+ // run that has since finished sends the client back to the messages
58
+ // endpoint, which by then holds everything the log would have.
59
+ const resumeOffset = c.req.header("Last-Event-ID");
60
+ if (resumeOffset) {
61
+ const runId = await getRunId(thread.id);
62
+ if (!runId) return runEnded();
63
+ if (!offsetBelongsToRun(resumeOffset, runId)) {
64
+ throw new HTTPException(400, { message: "Invalid resume offset" });
38
65
  }
39
66
 
40
- return stream(messages, thread, userId, token, requestContext, config);
41
- },
42
- );
67
+ return resumeServerSentEventsResponse({
68
+ adapter: joinLog(thread.id, runId, resumeOffset),
69
+ });
70
+ }
71
+
72
+ const requestContext = config.resolveRequestContext
73
+ ? await config.resolveRequestContext(c.req.raw)
74
+ : {};
75
+
76
+ // Null means a stop beat the run to its own announcement; the run is
77
+ // over, which is what actually happened.
78
+ return (
79
+ (await startTurn(params, thread, userId, token, requestContext, config)) ?? runEnded()
80
+ );
81
+ });
43
82
 
83
+ /** Attaches a reload, a thread switch, or a second tab to the thread's live run. */
44
84
  app.get("/chat/:chatId/stream", requireAuth, async function (c) {
45
- const config = c.get("agentConfig");
46
- const agentId = c.get("agentId");
47
85
  const chatId = c.req.param("chatId");
48
- const thread = await config.db.threads.getById(c.get("user").id, chatId);
49
- if (!thread || thread.agentId !== agentId) {
50
- throw new HTTPException(404, { message: "Not found" });
51
- }
86
+ await requireOwnedThread(c, chatId);
52
87
 
53
- const subscriberStream = subscribe(chatId);
54
- if (!subscriberStream) {
55
- return c.body(null, 204);
56
- }
88
+ const runId = await getRunId(chatId);
89
+ if (!runId) return runEnded();
57
90
 
58
- return new Response(subscriberStream.pipeThrough(new TextEncoderStream()), {
59
- headers: {
60
- "Content-Type": "text/event-stream",
61
- "Cache-Control": "no-cache",
62
- Connection: "keep-alive",
63
- },
64
- });
91
+ return resumeServerSentEventsResponse({ adapter: joinLog(chatId, runId) });
65
92
  });
66
93
 
67
94
  app.post("/chat/:chatId/abort", requireAuth, async function (c) {
68
- const config = c.get("agentConfig");
69
95
  const agentId = c.get("agentId");
70
96
  const userId = c.get("user").id;
71
97
  const chatId = c.req.param("chatId");
72
- const thread = await config.db.threads.getById(userId, chatId);
73
- if (!thread || thread.agentId !== agentId) {
74
- throw new HTTPException(404, { message: "Not found" });
75
- }
98
+ const thread = await requireOwnedThread(c, chatId);
76
99
 
77
- abortStream(chatId);
78
- notify(userId, agentId, {
79
- type: "thread:run-finished",
80
- payload: { thread: toThreadSummary(thread, false) },
81
- });
82
- return c.body(null, 204);
100
+ // Only announce an ending that happened: the client's stop button can
101
+ // outlive the server run (it drains the durability log after the
102
+ // producer commits), and a run-finished for a run that already ended
103
+ // can land before its own run-started and pin the thread as running.
104
+ const aborted = await abortRun(chatId);
105
+ if (aborted) {
106
+ notify(userId, agentId, {
107
+ type: "thread:run-finished",
108
+ payload: { thread: toThreadSummary(thread, false) },
109
+ });
110
+ }
111
+ return c.json({ aborted } satisfies AbortRunResult);
83
112
  });
84
113
 
85
114
  return app;