@studious-lms/server 1.13.0 → 1.15.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 (375) hide show
  1. package/.dockerignore +28 -0
  2. package/.env.example +5 -0
  3. package/.github/workflows/deploy.yml +123 -0
  4. package/Dockerfile +54 -0
  5. package/dist/index.js +5 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/instrument.js +3 -3
  8. package/dist/instrument.js.map +1 -1
  9. package/dist/lib/config/cors.d.ts.map +1 -1
  10. package/dist/lib/config/cors.js +31 -11
  11. package/dist/lib/config/cors.js.map +1 -1
  12. package/dist/lib/config/env.d.ts +17 -0
  13. package/dist/lib/config/env.d.ts.map +1 -1
  14. package/dist/lib/config/env.js +8 -2
  15. package/dist/lib/config/env.js.map +1 -1
  16. package/dist/lib/googleCloudStorage.d.ts +10 -0
  17. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  18. package/dist/lib/googleCloudStorage.js +22 -2
  19. package/dist/lib/googleCloudStorage.js.map +1 -1
  20. package/dist/lib/inferDocumentFormat.d.ts +10 -0
  21. package/dist/lib/inferDocumentFormat.d.ts.map +1 -0
  22. package/dist/lib/inferDocumentFormat.js +48 -0
  23. package/dist/lib/inferDocumentFormat.js.map +1 -0
  24. package/dist/lib/legal.d.ts +3 -0
  25. package/dist/lib/legal.d.ts.map +1 -0
  26. package/dist/lib/legal.js +6 -0
  27. package/dist/lib/legal.js.map +1 -0
  28. package/dist/lib/prisma.d.ts +104 -5
  29. package/dist/lib/prisma.d.ts.map +1 -1
  30. package/dist/lib/prisma.js +33 -2
  31. package/dist/lib/prisma.js.map +1 -1
  32. package/dist/models/agenda.d.ts +20 -20
  33. package/dist/models/announcement.d.ts +211 -101
  34. package/dist/models/announcement.d.ts.map +1 -1
  35. package/dist/models/assignment.d.ts +560 -362
  36. package/dist/models/assignment.d.ts.map +1 -1
  37. package/dist/models/attendance.d.ts +197 -71
  38. package/dist/models/attendance.d.ts.map +1 -1
  39. package/dist/models/auth.d.ts +356 -114
  40. package/dist/models/auth.d.ts.map +1 -1
  41. package/dist/models/auth.js +4 -2
  42. package/dist/models/auth.js.map +1 -1
  43. package/dist/models/class.d.ts +327 -201
  44. package/dist/models/class.d.ts.map +1 -1
  45. package/dist/models/class.js +2 -2
  46. package/dist/models/class.js.map +1 -1
  47. package/dist/models/cloudStorage.d.ts +146 -56
  48. package/dist/models/cloudStorage.d.ts.map +1 -1
  49. package/dist/models/comment.d.ts +262 -110
  50. package/dist/models/comment.d.ts.map +1 -1
  51. package/dist/models/conversation.d.ts +206 -80
  52. package/dist/models/conversation.d.ts.map +1 -1
  53. package/dist/models/department.d.ts +196 -70
  54. package/dist/models/department.d.ts.map +1 -1
  55. package/dist/models/event.d.ts +305 -141
  56. package/dist/models/event.d.ts.map +1 -1
  57. package/dist/models/file.d.ts +422 -272
  58. package/dist/models/file.d.ts.map +1 -1
  59. package/dist/models/folder.d.ts +433 -181
  60. package/dist/models/folder.d.ts.map +1 -1
  61. package/dist/models/labChat.d.ts +205 -79
  62. package/dist/models/labChat.d.ts.map +1 -1
  63. package/dist/models/marketing.d.ts +79 -38
  64. package/dist/models/marketing.d.ts.map +1 -1
  65. package/dist/models/marketing.js +12 -4
  66. package/dist/models/marketing.js.map +1 -1
  67. package/dist/models/message.d.ts +97 -43
  68. package/dist/models/message.d.ts.map +1 -1
  69. package/dist/models/newtonChat.d.ts +84 -30
  70. package/dist/models/newtonChat.d.ts.map +1 -1
  71. package/dist/models/notification.d.ts +57 -21
  72. package/dist/models/notification.d.ts.map +1 -1
  73. package/dist/models/school.d.ts +349 -133
  74. package/dist/models/school.d.ts.map +1 -1
  75. package/dist/models/section.d.ts +216 -72
  76. package/dist/models/section.d.ts.map +1 -1
  77. package/dist/models/tag.d.ts +156 -48
  78. package/dist/models/tag.d.ts.map +1 -1
  79. package/dist/models/user.d.ts +82 -28
  80. package/dist/models/user.d.ts.map +1 -1
  81. package/dist/models/worksheet.d.ts +1032 -423
  82. package/dist/models/worksheet.d.ts.map +1 -1
  83. package/dist/models/worksheet.js +8 -2
  84. package/dist/models/worksheet.js.map +1 -1
  85. package/dist/pipelines/aiLabChat.d.ts +7 -9
  86. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  87. package/dist/pipelines/aiLabChat.js +194 -125
  88. package/dist/pipelines/aiLabChat.js.map +1 -1
  89. package/dist/pipelines/aiLabChatContract.d.ts +233 -389
  90. package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
  91. package/dist/pipelines/aiLabChatContract.js +54 -70
  92. package/dist/pipelines/aiLabChatContract.js.map +1 -1
  93. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  94. package/dist/pipelines/aiNewtonChat.js +41 -41
  95. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  96. package/dist/pipelines/gradeWorksheet.d.ts +8 -8
  97. package/dist/pipelines/labChatPrompt.d.ts +18 -11
  98. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  99. package/dist/pipelines/labChatPrompt.js +93 -75
  100. package/dist/pipelines/labChatPrompt.js.map +1 -1
  101. package/dist/pipelines/pdfToWorksheet.d.ts +1 -1
  102. package/dist/routers/_app.d.ts +2601 -2151
  103. package/dist/routers/_app.d.ts.map +1 -1
  104. package/dist/routers/_app.js +4 -2
  105. package/dist/routers/_app.js.map +1 -1
  106. package/dist/routers/admin.d.ts +13 -13
  107. package/dist/routers/agenda.d.ts +18 -18
  108. package/dist/routers/announcement.d.ts +60 -60
  109. package/dist/routers/assignment.d.ts +393 -383
  110. package/dist/routers/assignment.d.ts.map +1 -1
  111. package/dist/routers/attendance.d.ts +10 -10
  112. package/dist/routers/auth.d.ts +4 -2
  113. package/dist/routers/auth.d.ts.map +1 -1
  114. package/dist/routers/auth.js +15 -2
  115. package/dist/routers/auth.js.map +1 -1
  116. package/dist/routers/class.d.ts +157 -153
  117. package/dist/routers/class.d.ts.map +1 -1
  118. package/dist/routers/comment.d.ts +18 -18
  119. package/dist/routers/conversation.d.ts +11 -11
  120. package/dist/routers/department.d.ts +34 -34
  121. package/dist/routers/discussion.d.ts +68 -56
  122. package/dist/routers/discussion.d.ts.map +1 -1
  123. package/dist/routers/discussion.js +6 -2
  124. package/dist/routers/discussion.js.map +1 -1
  125. package/dist/routers/embedding.d.ts +73 -0
  126. package/dist/routers/embedding.d.ts.map +1 -0
  127. package/dist/routers/embedding.js +151 -0
  128. package/dist/routers/embedding.js.map +1 -0
  129. package/dist/routers/event.d.ts +57 -55
  130. package/dist/routers/event.d.ts.map +1 -1
  131. package/dist/routers/file.d.ts +14 -14
  132. package/dist/routers/folder.d.ts +38 -38
  133. package/dist/routers/labChat.d.ts +16 -16
  134. package/dist/routers/marketing.d.ts +1 -14
  135. package/dist/routers/marketing.d.ts.map +1 -1
  136. package/dist/routers/message.d.ts +13 -13
  137. package/dist/routers/newtonChat.d.ts +4 -4
  138. package/dist/routers/notifications.d.ts +5 -5
  139. package/dist/routers/permissions.d.ts +14 -14
  140. package/dist/routers/school.d.ts +59 -55
  141. package/dist/routers/school.d.ts.map +1 -1
  142. package/dist/routers/search.d.ts +22 -1
  143. package/dist/routers/search.d.ts.map +1 -1
  144. package/dist/routers/search.js +9 -3
  145. package/dist/routers/search.js.map +1 -1
  146. package/dist/routers/section.d.ts +4 -4
  147. package/dist/routers/studentProgress.d.ts +1 -1
  148. package/dist/routers/tag.d.ts +7 -7
  149. package/dist/routers/user.d.ts +99 -10
  150. package/dist/routers/user.d.ts.map +1 -1
  151. package/dist/routers/user.js +19 -2
  152. package/dist/routers/user.js.map +1 -1
  153. package/dist/routers/worksheet.d.ts +93 -55
  154. package/dist/routers/worksheet.d.ts.map +1 -1
  155. package/dist/routers/worksheet.js +26 -2
  156. package/dist/routers/worksheet.js.map +1 -1
  157. package/dist/routes/aiStream.d.ts +2 -0
  158. package/dist/routes/aiStream.d.ts.map +1 -0
  159. package/dist/routes/aiStream.js +726 -0
  160. package/dist/routes/aiStream.js.map +1 -0
  161. package/dist/seedDatabase.d.ts +3 -1
  162. package/dist/seedDatabase.d.ts.map +1 -1
  163. package/dist/seedDatabase.js +6 -6
  164. package/dist/seedDatabase.js.map +1 -1
  165. package/dist/services/adminInstitution.d.ts +8 -8
  166. package/dist/services/adminRunJob.d.ts +4 -4
  167. package/dist/services/agenda.d.ts +18 -18
  168. package/dist/services/aiChatStream.d.ts +88 -0
  169. package/dist/services/aiChatStream.d.ts.map +1 -0
  170. package/dist/services/aiChatStream.js +254 -0
  171. package/dist/services/aiChatStream.js.map +1 -0
  172. package/dist/services/aiModifierTools.d.ts +16 -0
  173. package/dist/services/aiModifierTools.d.ts.map +1 -0
  174. package/dist/services/aiModifierTools.js +274 -0
  175. package/dist/services/aiModifierTools.js.map +1 -0
  176. package/dist/services/aiProposalTools.d.ts +32 -0
  177. package/dist/services/aiProposalTools.d.ts.map +1 -0
  178. package/dist/services/aiProposalTools.js +207 -0
  179. package/dist/services/aiProposalTools.js.map +1 -0
  180. package/dist/services/aiStream.d.ts +22 -0
  181. package/dist/services/aiStream.d.ts.map +1 -0
  182. package/dist/services/aiStream.js +142 -0
  183. package/dist/services/aiStream.js.map +1 -0
  184. package/dist/services/aiTools.d.ts +16 -0
  185. package/dist/services/aiTools.d.ts.map +1 -0
  186. package/dist/services/aiTools.js +271 -0
  187. package/dist/services/aiTools.js.map +1 -0
  188. package/dist/services/announcement.d.ts +42 -42
  189. package/dist/services/announcement.d.ts.map +1 -1
  190. package/dist/services/announcement.js +7 -2
  191. package/dist/services/announcement.js.map +1 -1
  192. package/dist/services/assignment.d.ts +355 -345
  193. package/dist/services/assignment.d.ts.map +1 -1
  194. package/dist/services/assignment.js +30 -9
  195. package/dist/services/assignment.js.map +1 -1
  196. package/dist/services/attendance.d.ts +10 -10
  197. package/dist/services/auth.d.ts +4 -2
  198. package/dist/services/auth.d.ts.map +1 -1
  199. package/dist/services/auth.js +13 -4
  200. package/dist/services/auth.js.map +1 -1
  201. package/dist/services/class.d.ts +146 -142
  202. package/dist/services/class.d.ts.map +1 -1
  203. package/dist/services/class.js +24 -4
  204. package/dist/services/class.js.map +1 -1
  205. package/dist/services/comment.d.ts +18 -18
  206. package/dist/services/conversation.d.ts +11 -11
  207. package/dist/services/department.d.ts +33 -33
  208. package/dist/services/discussion.d.ts +58 -58
  209. package/dist/services/discussion.d.ts.map +1 -1
  210. package/dist/services/discussion.js +15 -8
  211. package/dist/services/discussion.js.map +1 -1
  212. package/dist/services/discussionSummary.d.ts +21 -0
  213. package/dist/services/discussionSummary.d.ts.map +1 -0
  214. package/dist/services/discussionSummary.js +179 -0
  215. package/dist/services/discussionSummary.js.map +1 -0
  216. package/dist/services/embedding.d.ts +85 -0
  217. package/dist/services/embedding.d.ts.map +1 -0
  218. package/dist/services/embedding.js +372 -0
  219. package/dist/services/embedding.js.map +1 -0
  220. package/dist/services/event.d.ts +55 -53
  221. package/dist/services/event.d.ts.map +1 -1
  222. package/dist/services/file.d.ts +28 -28
  223. package/dist/services/folder.d.ts +36 -36
  224. package/dist/services/folder.d.ts.map +1 -1
  225. package/dist/services/folder.js +32 -2
  226. package/dist/services/folder.js.map +1 -1
  227. package/dist/services/labChat.d.ts +16 -16
  228. package/dist/services/marketing.d.ts +2 -15
  229. package/dist/services/marketing.d.ts.map +1 -1
  230. package/dist/services/marketing.js +5 -5
  231. package/dist/services/marketing.js.map +1 -1
  232. package/dist/services/message.d.ts +11 -11
  233. package/dist/services/message.d.ts.map +1 -1
  234. package/dist/services/message.js +2 -2
  235. package/dist/services/message.js.map +1 -1
  236. package/dist/services/newtonChat.d.ts +4 -4
  237. package/dist/services/notification.d.ts +5 -5
  238. package/dist/services/notification.d.ts.map +1 -1
  239. package/dist/services/openaiDocumentGen.d.ts +37 -0
  240. package/dist/services/openaiDocumentGen.d.ts.map +1 -0
  241. package/dist/services/openaiDocumentGen.js +262 -0
  242. package/dist/services/openaiDocumentGen.js.map +1 -0
  243. package/dist/services/permissions.d.ts +14 -14
  244. package/dist/services/school.d.ts +54 -50
  245. package/dist/services/school.d.ts.map +1 -1
  246. package/dist/services/search.d.ts +13 -1
  247. package/dist/services/search.d.ts.map +1 -1
  248. package/dist/services/search.js +145 -2
  249. package/dist/services/search.js.map +1 -1
  250. package/dist/services/section.d.ts +3 -3
  251. package/dist/services/tag.d.ts +4 -4
  252. package/dist/services/user.d.ts +4 -4
  253. package/dist/services/user.d.ts.map +1 -1
  254. package/dist/services/user.js +10 -15
  255. package/dist/services/user.js.map +1 -1
  256. package/dist/services/userPrivacy.d.ts +86 -0
  257. package/dist/services/userPrivacy.d.ts.map +1 -0
  258. package/dist/services/userPrivacy.js +184 -0
  259. package/dist/services/userPrivacy.js.map +1 -0
  260. package/dist/services/worksheet.d.ts +86 -57
  261. package/dist/services/worksheet.d.ts.map +1 -1
  262. package/dist/services/worksheet.js +57 -5
  263. package/dist/services/worksheet.js.map +1 -1
  264. package/dist/utils/email.d.ts.map +1 -1
  265. package/dist/utils/email.js +7 -3
  266. package/dist/utils/email.js.map +1 -1
  267. package/dist/utils/streamBroadcast.d.ts +11 -0
  268. package/dist/utils/streamBroadcast.d.ts.map +1 -0
  269. package/dist/utils/streamBroadcast.js +32 -0
  270. package/dist/utils/streamBroadcast.js.map +1 -0
  271. package/dist/workers/connection.d.ts +15 -0
  272. package/dist/workers/connection.d.ts.map +1 -1
  273. package/dist/workers/connection.js +20 -2
  274. package/dist/workers/connection.js.map +1 -1
  275. package/dist/workers/index.js +4 -2
  276. package/dist/workers/index.js.map +1 -1
  277. package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -1
  278. package/dist/workers/processors/createRunJobWorker.js +31 -8
  279. package/dist/workers/processors/createRunJobWorker.js.map +1 -1
  280. package/dist/workers/processors/embedContent.d.ts +8 -0
  281. package/dist/workers/processors/embedContent.d.ts.map +1 -0
  282. package/dist/workers/processors/embedContent.js +67 -0
  283. package/dist/workers/processors/embedContent.js.map +1 -0
  284. package/dist/workers/queues.d.ts +6 -0
  285. package/dist/workers/queues.d.ts.map +1 -1
  286. package/dist/workers/queues.js +5 -3
  287. package/dist/workers/queues.js.map +1 -1
  288. package/package.json +5 -1
  289. package/prisma/migrations/20260609215135_init/migration.sql +1412 -0
  290. package/prisma/migrations/20260610130000_add_worksheet_tags/migration.sql +13 -0
  291. package/prisma/migrations/20260610140000_expand_embedding_source_types/migration.sql +4 -0
  292. package/prisma/migrations/20260611120000_add_user_terms_acceptance/migration.sql +3 -0
  293. package/prisma/schema.prisma +44 -7
  294. package/src/index.ts +4 -0
  295. package/src/instrument.ts +1 -1
  296. package/src/lib/config/cors.ts +32 -9
  297. package/src/lib/config/env.ts +6 -0
  298. package/src/lib/googleCloudStorage.ts +21 -0
  299. package/src/lib/inferDocumentFormat.ts +58 -0
  300. package/src/lib/legal.ts +2 -0
  301. package/src/lib/prisma.ts +40 -1
  302. package/src/models/auth.ts +4 -0
  303. package/src/models/class.ts +3 -2
  304. package/src/models/marketing.ts +10 -2
  305. package/src/models/worksheet.ts +5 -2
  306. package/src/pipelines/aiLabChat.ts +245 -146
  307. package/src/pipelines/aiLabChatContract.ts +59 -69
  308. package/src/pipelines/aiNewtonChat.ts +46 -43
  309. package/src/pipelines/labChatPrompt.ts +115 -90
  310. package/src/routers/_app.ts +2 -0
  311. package/src/routers/auth.ts +13 -0
  312. package/src/routers/discussion.ts +7 -0
  313. package/src/routers/embedding.ts +164 -0
  314. package/src/routers/search.ts +10 -1
  315. package/src/routers/user.ts +26 -0
  316. package/src/routers/worksheet.ts +27 -0
  317. package/src/routes/aiStream.ts +827 -0
  318. package/src/seedDatabase.ts +4 -4
  319. package/src/services/aiChatStream.ts +341 -0
  320. package/src/services/aiModifierTools.ts +341 -0
  321. package/src/services/aiProposalTools.ts +274 -0
  322. package/src/services/aiStream.ts +175 -0
  323. package/src/services/aiTools.ts +290 -0
  324. package/src/services/announcement.ts +7 -0
  325. package/src/services/assignment.ts +31 -7
  326. package/src/services/auth.ts +14 -2
  327. package/src/services/class.ts +27 -2
  328. package/src/services/discussion.ts +22 -8
  329. package/src/services/discussionSummary.ts +226 -0
  330. package/src/services/embedding.ts +446 -0
  331. package/src/services/folder.ts +31 -0
  332. package/src/services/marketing.ts +3 -3
  333. package/src/services/message.ts +2 -2
  334. package/src/services/openaiDocumentGen.ts +319 -0
  335. package/src/services/search.ts +163 -1
  336. package/src/services/user.ts +9 -17
  337. package/src/services/userPrivacy.ts +196 -0
  338. package/src/services/worksheet.ts +77 -3
  339. package/src/utils/email.ts +5 -1
  340. package/src/utils/streamBroadcast.ts +48 -0
  341. package/src/workers/connection.ts +19 -0
  342. package/src/workers/index.ts +2 -0
  343. package/src/workers/processors/createRunJobWorker.ts +32 -6
  344. package/src/workers/processors/embedContent.ts +86 -0
  345. package/src/workers/queues.ts +5 -1
  346. package/tests/helpers/registerInput.ts +20 -0
  347. package/tests/integration/globalSetup.ts +10 -18
  348. package/tests/integration/routers/auth.test.ts +13 -18
  349. package/tests/unit/lib/inferDocumentFormat.test.ts +33 -0
  350. package/tests/unit/pipelines/aiLabChatContract.test.ts +38 -21
  351. package/tests/unit/pipelines/labChatPrompt.test.ts +23 -36
  352. package/tests/unit/services/aiChatStream.test.ts +92 -0
  353. package/tests/unit/services/openaiDocumentGen.test.ts +65 -0
  354. package/prisma/migrations/20250807062924_init/migration.sql +0 -436
  355. package/prisma/migrations/20250919063704_init2/migration.sql +0 -24
  356. package/prisma/migrations/20250920143543_add_profile_fields/migration.sql +0 -15
  357. package/prisma/migrations/20250922165147_add_chat_models/migration.sql +0 -68
  358. package/prisma/migrations/20250922165314_add_chat_roles/migration.sql +0 -5
  359. package/prisma/migrations/20250922171242_refactor_notification_system/migration.sql +0 -20
  360. package/prisma/migrations/20250922172208_add_mentions_system/migration.sql +0 -21
  361. package/prisma/migrations/20250925072732_add_lab_chat_system/migration.sql +0 -25
  362. package/prisma/migrations/20251020151505_add_upload_tracking_fields/migration.sql +0 -57
  363. package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +0 -30
  364. package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +0 -35
  365. package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +0 -14
  366. package/prisma/migrations/20260415103500_add_session_type/migration.sql +0 -33
  367. package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +0 -19
  368. package/prisma/migrations/20260417083000_add_conversation_member_hidden_at/migration.sql +0 -2
  369. package/prisma/migrations/20260502230500_assignment_submission_comments/migration.sql +0 -60
  370. package/prisma/migrations/20260504071500_school_memberships/migration.sql +0 -81
  371. package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +0 -166
  372. package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +0 -184
  373. package/prisma/migrations/20260510170000_add_folder_permissions/migration.sql +0 -93
  374. package/prisma/migrations/20260510183000_add_discussions/migration.sql +0 -151
  375. /package/.github/workflows/{npm.yml → npm-publish.yml} +0 -0
@@ -10,13 +10,13 @@ export declare function listSchoolsForAdmin(input: {
10
10
  classes: number;
11
11
  };
12
12
  } & {
13
- name: string;
14
13
  id: string;
14
+ createdAt: Date;
15
+ updatedAt: Date;
16
+ name: string;
15
17
  description: string | null;
16
18
  logoId: string | null;
17
19
  subdomain: string | null;
18
- createdAt: Date;
19
- updatedAt: Date;
20
20
  })[];
21
21
  total: number;
22
22
  page: number;
@@ -30,8 +30,8 @@ export declare function listDepartmentsForAdmin(input: {
30
30
  }): Promise<{
31
31
  items: ({
32
32
  school: {
33
- name: string;
34
33
  id: string;
34
+ name: string;
35
35
  subdomain: string | null;
36
36
  };
37
37
  _count: {
@@ -39,12 +39,12 @@ export declare function listDepartmentsForAdmin(input: {
39
39
  members: number;
40
40
  };
41
41
  } & {
42
- name: string;
43
42
  id: string;
44
- description: string | null;
45
43
  createdAt: Date;
46
44
  updatedAt: Date;
45
+ name: string;
47
46
  schoolId: string;
47
+ description: string | null;
48
48
  color: string | null;
49
49
  })[];
50
50
  total: number;
@@ -61,10 +61,10 @@ export declare function listSchoolDevelopmentProgramsForAdmin(input: {
61
61
  items: {
62
62
  type: string;
63
63
  status: string;
64
- name: string;
65
64
  id: string;
66
- submittedAt: Date | null;
67
65
  website: string | null;
66
+ name: string;
67
+ submittedAt: Date | null;
68
68
  address: string;
69
69
  city: string;
70
70
  country: string;
@@ -11,13 +11,13 @@ export declare function listRunJobsForAdmin(input: {
11
11
  status: import(".prisma/client").$Enums.RunJobStatus;
12
12
  result: Prisma.JsonValue | null;
13
13
  id: string;
14
+ userId: string | null;
14
15
  createdAt: Date;
15
16
  updatedAt: Date;
16
- userId: string | null;
17
17
  classId: string | null;
18
18
  input: Prisma.JsonValue;
19
- attempts: number;
20
19
  bullJobId: string | null;
20
+ attempts: number;
21
21
  startedAt: Date | null;
22
22
  finishedAt: Date | null;
23
23
  }[];
@@ -32,13 +32,13 @@ export declare function getRunJobForAdmin(id: string): Promise<{
32
32
  status: import(".prisma/client").$Enums.RunJobStatus;
33
33
  result: Prisma.JsonValue | null;
34
34
  id: string;
35
+ userId: string | null;
35
36
  createdAt: Date;
36
37
  updatedAt: Date;
37
- userId: string | null;
38
38
  classId: string | null;
39
39
  input: Prisma.JsonValue;
40
- attempts: number;
41
40
  bullJobId: string | null;
41
+ attempts: number;
42
42
  startedAt: Date | null;
43
43
  finishedAt: Date | null;
44
44
  }>;
@@ -3,12 +3,12 @@ export declare function getAgenda(userId: string, weekStart: string): Promise<{
3
3
  events: {
4
4
  personal: ({
5
5
  class: {
6
- name: string;
6
+ section: string;
7
7
  id: string;
8
+ name: string;
8
9
  schoolId: string | null;
9
- subject: string;
10
10
  color: string | null;
11
- section: string;
11
+ subject: string;
12
12
  syllabus: string | null;
13
13
  departmentId: string | null;
14
14
  discussionsEnabled: boolean;
@@ -20,10 +20,12 @@ export declare function getAgenda(userId: string, weekStart: string): Promise<{
20
20
  id: string;
21
21
  createdAt: Date | null;
22
22
  classId: string;
23
- modifiedAt: Date | null;
24
23
  title: string;
25
- instructions: string;
26
24
  dueDate: Date;
25
+ maxGrade: number | null;
26
+ eventId: string | null;
27
+ instructions: string;
28
+ modifiedAt: Date | null;
27
29
  teacherId: string;
28
30
  acceptFiles: boolean;
29
31
  acceptExtendedResponse: boolean;
@@ -32,34 +34,32 @@ export declare function getAgenda(userId: string, weekStart: string): Promise<{
32
34
  aiPolicyLevel: number;
33
35
  sectionId: string | null;
34
36
  graded: boolean;
35
- maxGrade: number | null;
36
37
  weight: number;
37
38
  inProgress: boolean;
38
39
  template: boolean;
39
- eventId: string | null;
40
40
  markSchemeId: string | null;
41
41
  order: number | null;
42
42
  gradingBoundaryId: string | null;
43
43
  }[];
44
44
  } & {
45
- name: string | null;
46
45
  id: string;
47
46
  userId: string | null;
47
+ location: string | null;
48
+ name: string | null;
48
49
  color: string | null;
49
50
  classId: string | null;
50
51
  startTime: Date;
51
52
  endTime: Date;
52
- location: string | null;
53
53
  remarks: string | null;
54
54
  })[];
55
55
  class: ({
56
56
  class: {
57
- name: string;
57
+ section: string;
58
58
  id: string;
59
+ name: string;
59
60
  schoolId: string | null;
60
- subject: string;
61
61
  color: string | null;
62
- section: string;
62
+ subject: string;
63
63
  syllabus: string | null;
64
64
  departmentId: string | null;
65
65
  discussionsEnabled: boolean;
@@ -71,10 +71,12 @@ export declare function getAgenda(userId: string, weekStart: string): Promise<{
71
71
  id: string;
72
72
  createdAt: Date | null;
73
73
  classId: string;
74
- modifiedAt: Date | null;
75
74
  title: string;
76
- instructions: string;
77
75
  dueDate: Date;
76
+ maxGrade: number | null;
77
+ eventId: string | null;
78
+ instructions: string;
79
+ modifiedAt: Date | null;
78
80
  teacherId: string;
79
81
  acceptFiles: boolean;
80
82
  acceptExtendedResponse: boolean;
@@ -83,24 +85,22 @@ export declare function getAgenda(userId: string, weekStart: string): Promise<{
83
85
  aiPolicyLevel: number;
84
86
  sectionId: string | null;
85
87
  graded: boolean;
86
- maxGrade: number | null;
87
88
  weight: number;
88
89
  inProgress: boolean;
89
90
  template: boolean;
90
- eventId: string | null;
91
91
  markSchemeId: string | null;
92
92
  order: number | null;
93
93
  gradingBoundaryId: string | null;
94
94
  }[];
95
95
  } & {
96
- name: string | null;
97
96
  id: string;
98
97
  userId: string | null;
98
+ location: string | null;
99
+ name: string | null;
99
100
  color: string | null;
100
101
  classId: string | null;
101
102
  startTime: Date;
102
103
  endTime: Date;
103
- location: string | null;
104
104
  remarks: string | null;
105
105
  })[];
106
106
  };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Shared AI chat streaming helper.
3
+ *
4
+ * Runs a genuine token-by-token stream via the Vercel AI SDK (`streamText`)
5
+ * against OpenAI — the same provider the rest of the app's inference uses
6
+ * (`OPENAI_API_KEY`). Real text tokens are broadcast over Pusher as they are
7
+ * produced, and real tool calls are broadcast inline (in the order the model
8
+ * invokes them) instead of being derived after the fact from a JSON blob.
9
+ *
10
+ * Callers own persistence: this helper returns the accumulated text and the
11
+ * ordered tool calls so the pipeline can write `content`/`meta` and emit the
12
+ * terminal `message-stream-complete` / `message-updated` events.
13
+ */
14
+ import { type ModelMessage, type ToolSet } from 'ai';
15
+ /** Default streaming model — matches the existing streamAIResponse path. */
16
+ export declare const DEFAULT_STREAM_MODEL = "gpt-4o-mini";
17
+ export interface StreamedToolCall {
18
+ toolName: string;
19
+ /** Args the model passed to the tool. */
20
+ input: unknown;
21
+ /** Value the tool returned (undefined for client-side / no-result tools). */
22
+ output?: unknown;
23
+ status: 'completed' | 'failed';
24
+ }
25
+ /**
26
+ * An ordered fragment of an AI message. The message is a sequence of these
27
+ * parts in the exact order the model produced them, so a tool call rendered
28
+ * "between paragraph 1 and 2" is represented as
29
+ * `[{text}, {tool}, {text}]` — enabling true inline rendering (and reload
30
+ * persistence) instead of text and tool calls living in separate buckets.
31
+ */
32
+ export type StreamPart = {
33
+ type: 'text';
34
+ text: string;
35
+ } | {
36
+ type: 'tool';
37
+ toolCallId: string;
38
+ toolName: string;
39
+ /** Concrete entity label (e.g. assignment title / search query), if any. */
40
+ title?: string;
41
+ /** Requested output type from the tool args (pdf, pptx, xlsx, …). */
42
+ format?: string;
43
+ /** Set after generated files are persisted on the message. */
44
+ fileId?: string;
45
+ fileName?: string;
46
+ status: 'executing' | 'completed' | 'failed';
47
+ };
48
+ export interface GeneratedDocFileLink {
49
+ title: string;
50
+ fileId: string;
51
+ fileName: string;
52
+ format?: string;
53
+ }
54
+ /** Apply pipeline-resolved formats onto generate_document tool parts. */
55
+ export declare function applyResolvedFormatsToStreamParts(parts: StreamPart[], docs: Array<{
56
+ title: string;
57
+ format: string;
58
+ }>): StreamPart[];
59
+ /** Attach file IDs to `generate_document` tool parts for inline download chips. */
60
+ export declare function enrichStreamPartsWithGeneratedFiles(parts: StreamPart[], files: GeneratedDocFileLink[]): StreamPart[];
61
+ /** Mark deferred tools completed (file linked) or failed (generation did not produce a file). */
62
+ export declare function finalizeDeferredToolParts(parts: StreamPart[]): StreamPart[];
63
+ export interface StreamChatOptions {
64
+ conversationId: string;
65
+ aiMessageId: string;
66
+ system: string;
67
+ messages: ModelMessage[];
68
+ tools?: ToolSet;
69
+ model?: string;
70
+ temperature?: number;
71
+ maxOutputTokens?: number;
72
+ /** Max agent steps (tool round-trips). Defaults to 6. */
73
+ maxSteps?: number;
74
+ }
75
+ export interface StreamChatResult {
76
+ text: string;
77
+ toolCalls: StreamedToolCall[];
78
+ /** Ordered text/tool parts, in the exact order the model produced them. */
79
+ parts: StreamPart[];
80
+ }
81
+ /**
82
+ * Stream a chat completion, broadcasting real text chunks and tool-call
83
+ * events over Pusher. Does NOT persist the message or emit the terminal
84
+ * stream-complete event — the caller does that once it has assembled the
85
+ * final `content`/`meta` (e.g. PDF attachments, structured proposals).
86
+ */
87
+ export declare function streamChatResponse(options: StreamChatOptions): Promise<StreamChatResult>;
88
+ //# sourceMappingURL=aiChatStream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiChatStream.d.ts","sourceRoot":"/","sources":["services/aiChatStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAA2B,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,IAAI,CAAC;AAS9E,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,KAAK,EAAE,OAAO,CAAC;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IACE,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;CAC9C,CAAC;AAEN,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,yEAAyE;AACzE,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,UAAU,EAAE,EACnB,IAAI,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,UAAU,EAAE,CAmBd;AASD,mFAAmF;AACnF,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,UAAU,EAAE,EACnB,KAAK,EAAE,oBAAoB,EAAE,GAC5B,UAAU,EAAE,CAuCd;AAED,iGAAiG;AACjG,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAS3E;AAmBD,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,2EAA2E;IAC3E,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CA8I3B"}
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Shared AI chat streaming helper.
3
+ *
4
+ * Runs a genuine token-by-token stream via the Vercel AI SDK (`streamText`)
5
+ * against OpenAI — the same provider the rest of the app's inference uses
6
+ * (`OPENAI_API_KEY`). Real text tokens are broadcast over Pusher as they are
7
+ * produced, and real tool calls are broadcast inline (in the order the model
8
+ * invokes them) instead of being derived after the fact from a JSON blob.
9
+ *
10
+ * Callers own persistence: this helper returns the accumulated text and the
11
+ * ordered tool calls so the pipeline can write `content`/`meta` and emit the
12
+ * terminal `message-stream-complete` / `message-updated` events.
13
+ */
14
+
15
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5a03c108-e33a-50c7-a5fd-24f570b1b507")}catch(e){}}();
16
+ import { streamText, stepCountIs } from 'ai';
17
+ import { openai } from '@ai-sdk/openai';
18
+ import { getRedis } from '../lib/redis.js';
19
+ import { pusher, chatChannel } from '../lib/pusher.js';
20
+ import { logger } from '../utils/logger.js';
21
+ const STREAM_REDIS_TTL = 600; // 10 minutes
22
+ const STREAM_KEY_PREFIX = 'stream:';
23
+ /** Default streaming model — matches the existing streamAIResponse path. */
24
+ export const DEFAULT_STREAM_MODEL = 'gpt-4o-mini';
25
+ /** Apply pipeline-resolved formats onto generate_document tool parts. */
26
+ export function applyResolvedFormatsToStreamParts(parts, docs) {
27
+ if (docs.length === 0)
28
+ return parts;
29
+ const pending = [...docs];
30
+ const takeDoc = (title) => {
31
+ if (title) {
32
+ const idx = pending.findIndex((d) => d.title.trim().toLowerCase() === title.trim().toLowerCase());
33
+ if (idx >= 0)
34
+ return pending.splice(idx, 1)[0];
35
+ }
36
+ return pending.shift();
37
+ };
38
+ return parts.map((part) => {
39
+ if (part.type !== 'tool' || part.toolName !== 'generate_document')
40
+ return part;
41
+ const doc = takeDoc(part.title);
42
+ return doc ? { ...part, format: doc.format } : part;
43
+ });
44
+ }
45
+ /** Tools that queue async work after the model stream; UI stays loading until the pipeline finishes. */
46
+ const DEFERRED_COMPLETION_TOOLS = new Set(['generate_document']);
47
+ function isDeferredCompletionTool(toolName) {
48
+ return DEFERRED_COMPLETION_TOOLS.has(toolName);
49
+ }
50
+ /** Attach file IDs to `generate_document` tool parts for inline download chips. */
51
+ export function enrichStreamPartsWithGeneratedFiles(parts, files) {
52
+ if (files.length === 0)
53
+ return parts;
54
+ const docToolIndices = parts
55
+ .map((part, index) => ({ part, index }))
56
+ .filter(({ part }) => part.type === 'tool' && part.toolName === 'generate_document');
57
+ const unmatched = [...files];
58
+ const fileForTool = (toolTitle) => {
59
+ if (toolTitle) {
60
+ const idx = unmatched.findIndex((f) => f.title.trim().toLowerCase() === toolTitle.trim().toLowerCase());
61
+ if (idx >= 0)
62
+ return unmatched.splice(idx, 1)[0];
63
+ }
64
+ return unmatched.shift();
65
+ };
66
+ const fileByIndex = new Map();
67
+ for (const { part, index } of docToolIndices) {
68
+ if (part.type !== 'tool')
69
+ continue;
70
+ const file = fileForTool(part.title);
71
+ if (file)
72
+ fileByIndex.set(index, file);
73
+ }
74
+ return parts.map((part, index) => {
75
+ const file = fileByIndex.get(index);
76
+ if (!file || part.type !== 'tool')
77
+ return part;
78
+ return {
79
+ ...part,
80
+ fileId: file.fileId,
81
+ fileName: file.fileName,
82
+ status: 'completed',
83
+ ...(file.format ? { format: file.format } : {}),
84
+ };
85
+ });
86
+ }
87
+ /** Mark deferred tools completed (file linked) or failed (generation did not produce a file). */
88
+ export function finalizeDeferredToolParts(parts) {
89
+ return parts.map((part) => {
90
+ if (part.type !== 'tool' || !isDeferredCompletionTool(part.toolName)) {
91
+ return part;
92
+ }
93
+ if (part.fileId)
94
+ return { ...part, status: 'completed' };
95
+ if (part.status === 'executing')
96
+ return { ...part, status: 'failed' };
97
+ return part;
98
+ });
99
+ }
100
+ function toolFieldFromInput(input, key) {
101
+ if (input && typeof input === 'object') {
102
+ const val = input[key];
103
+ if (typeof val === 'string' && val.trim())
104
+ return val.trim();
105
+ }
106
+ return undefined;
107
+ }
108
+ /** Pull a human-meaningful label out of a tool's input args, if present. */
109
+ function toolTitleFromInput(input) {
110
+ for (const key of ['title', 'name', 'query']) {
111
+ const val = toolFieldFromInput(input, key);
112
+ if (val)
113
+ return val;
114
+ }
115
+ return undefined;
116
+ }
117
+ /**
118
+ * Stream a chat completion, broadcasting real text chunks and tool-call
119
+ * events over Pusher. Does NOT persist the message or emit the terminal
120
+ * stream-complete event — the caller does that once it has assembled the
121
+ * final `content`/`meta` (e.g. PDF attachments, structured proposals).
122
+ */
123
+ export async function streamChatResponse(options) {
124
+ const { conversationId, aiMessageId, system, messages, tools, model = DEFAULT_STREAM_MODEL, temperature = 0.7, maxOutputTokens = 4096, maxSteps = 6, } = options;
125
+ const redis = getRedis();
126
+ const streamKey = `${STREAM_KEY_PREFIX}${aiMessageId}`;
127
+ if (redis) {
128
+ await redis.set(streamKey, '', 'EX', STREAM_REDIS_TTL);
129
+ }
130
+ let fullText = '';
131
+ const parts = [];
132
+ const toolCalls = [];
133
+ // Map AI SDK toolCallId -> index in `parts` so the executing indicator can
134
+ // be flipped to completed/failed in place (preserving its position).
135
+ const toolPartIndexById = new Map();
136
+ const appendText = (text) => {
137
+ fullText += text;
138
+ const last = parts[parts.length - 1];
139
+ if (last && last.type === 'text') {
140
+ last.text += text;
141
+ }
142
+ else {
143
+ parts.push({ type: 'text', text });
144
+ }
145
+ };
146
+ const broadcast = async (event, payload) => {
147
+ try {
148
+ await pusher.trigger(chatChannel(conversationId), event, payload);
149
+ }
150
+ catch (err) {
151
+ logger.warn(`Failed to broadcast ${event}`, { err, aiMessageId });
152
+ }
153
+ };
154
+ try {
155
+ const result = streamText({
156
+ model: openai(model),
157
+ system,
158
+ messages,
159
+ ...(tools ? { tools } : {}),
160
+ temperature,
161
+ maxOutputTokens,
162
+ stopWhen: stepCountIs(maxSteps),
163
+ });
164
+ // `fullStream` yields text deltas AND tool events in a single, correctly
165
+ // ordered sequence — this is what makes inline tool calls possible. We
166
+ // build an ordered `parts` list and broadcast each event as it happens.
167
+ for await (const part of result.fullStream) {
168
+ switch (part.type) {
169
+ case 'text-delta': {
170
+ appendText(part.text);
171
+ if (redis) {
172
+ await redis.append(streamKey, part.text).catch(() => { });
173
+ }
174
+ await broadcast('message-stream-chunk', {
175
+ messageId: aiMessageId,
176
+ chunk: part.text,
177
+ });
178
+ break;
179
+ }
180
+ case 'tool-call': {
181
+ const title = toolTitleFromInput(part.input);
182
+ const format = toolFieldFromInput(part.input, 'format');
183
+ toolPartIndexById.set(part.toolCallId, parts.length);
184
+ parts.push({
185
+ type: 'tool',
186
+ toolCallId: part.toolCallId,
187
+ toolName: part.toolName,
188
+ ...(title ? { title } : {}),
189
+ ...(format ? { format } : {}),
190
+ status: 'executing',
191
+ });
192
+ await broadcast('message-tool-call', {
193
+ messageId: aiMessageId,
194
+ toolCallId: part.toolCallId,
195
+ toolName: part.toolName,
196
+ ...(title ? { title } : {}),
197
+ ...(format ? { format } : {}),
198
+ status: 'executing',
199
+ });
200
+ break;
201
+ }
202
+ case 'tool-result':
203
+ case 'tool-error': {
204
+ const failed = part.type === 'tool-error';
205
+ const deferred = isDeferredCompletionTool(part.toolName);
206
+ // Deferred tools (e.g. generate_document) only queue work during the
207
+ // stream — keep the spinner until the pipeline attaches a file.
208
+ const status = failed
209
+ ? 'failed'
210
+ : deferred
211
+ ? 'executing'
212
+ : 'completed';
213
+ const title = toolTitleFromInput(part.input);
214
+ const format = toolFieldFromInput(part.input, 'format');
215
+ const idx = toolPartIndexById.get(part.toolCallId);
216
+ if (idx !== undefined) {
217
+ const existing = parts[idx];
218
+ if (existing && existing.type === 'tool') {
219
+ existing.status = status;
220
+ if (format)
221
+ existing.format = format;
222
+ }
223
+ }
224
+ toolCalls.push({
225
+ toolName: part.toolName,
226
+ input: part.input,
227
+ output: part.type === 'tool-result' ? part.output : undefined,
228
+ status: failed ? 'failed' : 'completed',
229
+ });
230
+ await broadcast('message-tool-call', {
231
+ messageId: aiMessageId,
232
+ toolCallId: part.toolCallId,
233
+ toolName: part.toolName,
234
+ ...(title ? { title } : {}),
235
+ ...(format ? { format } : {}),
236
+ status,
237
+ });
238
+ break;
239
+ }
240
+ default:
241
+ // ignore reasoning/source/step/finish/raw parts
242
+ break;
243
+ }
244
+ }
245
+ return { text: fullText, toolCalls, parts };
246
+ }
247
+ finally {
248
+ if (redis) {
249
+ await redis.del(streamKey).catch(() => { });
250
+ }
251
+ }
252
+ }
253
+ //# sourceMappingURL=aiChatStream.js.map
254
+ //# debugId=5a03c108-e33a-50c7-a5fd-24f570b1b507
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiChatStream.js","sources":["services/aiChatStream.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Shared AI chat streaming helper.\n *\n * Runs a genuine token-by-token stream via the Vercel AI SDK (`streamText`)\n * against OpenAI — the same provider the rest of the app's inference uses\n * (`OPENAI_API_KEY`). Real text tokens are broadcast over Pusher as they are\n * produced, and real tool calls are broadcast inline (in the order the model\n * invokes them) instead of being derived after the fact from a JSON blob.\n *\n * Callers own persistence: this helper returns the accumulated text and the\n * ordered tool calls so the pipeline can write `content`/`meta` and emit the\n * terminal `message-stream-complete` / `message-updated` events.\n */\nimport { streamText, stepCountIs, type ModelMessage, type ToolSet } from 'ai';\nimport { openai } from '@ai-sdk/openai';\nimport { getRedis } from '../lib/redis.js';\nimport { pusher, chatChannel } from '../lib/pusher.js';\nimport { logger } from '../utils/logger.js';\n\nconst STREAM_REDIS_TTL = 600; // 10 minutes\nconst STREAM_KEY_PREFIX = 'stream:';\n\n/** Default streaming model — matches the existing streamAIResponse path. */\nexport const DEFAULT_STREAM_MODEL = 'gpt-4o-mini';\n\nexport interface StreamedToolCall {\n toolName: string;\n /** Args the model passed to the tool. */\n input: unknown;\n /** Value the tool returned (undefined for client-side / no-result tools). */\n output?: unknown;\n status: 'completed' | 'failed';\n}\n\n/**\n * An ordered fragment of an AI message. The message is a sequence of these\n * parts in the exact order the model produced them, so a tool call rendered\n * \"between paragraph 1 and 2\" is represented as\n * `[{text}, {tool}, {text}]` — enabling true inline rendering (and reload\n * persistence) instead of text and tool calls living in separate buckets.\n */\nexport type StreamPart =\n | { type: 'text'; text: string }\n | {\n type: 'tool';\n toolCallId: string;\n toolName: string;\n /** Concrete entity label (e.g. assignment title / search query), if any. */\n title?: string;\n /** Requested output type from the tool args (pdf, pptx, xlsx, …). */\n format?: string;\n /** Set after generated files are persisted on the message. */\n fileId?: string;\n fileName?: string;\n status: 'executing' | 'completed' | 'failed';\n };\n\nexport interface GeneratedDocFileLink {\n title: string;\n fileId: string;\n fileName: string;\n format?: string;\n}\n\n/** Apply pipeline-resolved formats onto generate_document tool parts. */\nexport function applyResolvedFormatsToStreamParts(\n parts: StreamPart[],\n docs: Array<{ title: string; format: string }>,\n): StreamPart[] {\n if (docs.length === 0) return parts;\n\n const pending = [...docs];\n const takeDoc = (title?: string) => {\n if (title) {\n const idx = pending.findIndex(\n (d) => d.title.trim().toLowerCase() === title.trim().toLowerCase(),\n );\n if (idx >= 0) return pending.splice(idx, 1)[0];\n }\n return pending.shift();\n };\n\n return parts.map((part) => {\n if (part.type !== 'tool' || part.toolName !== 'generate_document') return part;\n const doc = takeDoc(part.title);\n return doc ? { ...part, format: doc.format } : part;\n });\n}\n\n/** Tools that queue async work after the model stream; UI stays loading until the pipeline finishes. */\nconst DEFERRED_COMPLETION_TOOLS = new Set(['generate_document']);\n\nfunction isDeferredCompletionTool(toolName: string): boolean {\n return DEFERRED_COMPLETION_TOOLS.has(toolName);\n}\n\n/** Attach file IDs to `generate_document` tool parts for inline download chips. */\nexport function enrichStreamPartsWithGeneratedFiles(\n parts: StreamPart[],\n files: GeneratedDocFileLink[],\n): StreamPart[] {\n if (files.length === 0) return parts;\n\n const docToolIndices = parts\n .map((part, index) => ({ part, index }))\n .filter(\n ({ part }) =>\n part.type === 'tool' && part.toolName === 'generate_document',\n );\n\n const unmatched = [...files];\n const fileForTool = (toolTitle?: string): GeneratedDocFileLink | undefined => {\n if (toolTitle) {\n const idx = unmatched.findIndex(\n (f) => f.title.trim().toLowerCase() === toolTitle.trim().toLowerCase(),\n );\n if (idx >= 0) return unmatched.splice(idx, 1)[0];\n }\n return unmatched.shift();\n };\n\n const fileByIndex = new Map<number, GeneratedDocFileLink>();\n for (const { part, index } of docToolIndices) {\n if (part.type !== 'tool') continue;\n const file = fileForTool(part.title);\n if (file) fileByIndex.set(index, file);\n }\n\n return parts.map((part, index) => {\n const file = fileByIndex.get(index);\n if (!file || part.type !== 'tool') return part;\n return {\n ...part,\n fileId: file.fileId,\n fileName: file.fileName,\n status: 'completed',\n ...(file.format ? { format: file.format } : {}),\n };\n });\n}\n\n/** Mark deferred tools completed (file linked) or failed (generation did not produce a file). */\nexport function finalizeDeferredToolParts(parts: StreamPart[]): StreamPart[] {\n return parts.map((part) => {\n if (part.type !== 'tool' || !isDeferredCompletionTool(part.toolName)) {\n return part;\n }\n if (part.fileId) return { ...part, status: 'completed' };\n if (part.status === 'executing') return { ...part, status: 'failed' };\n return part;\n });\n}\n\nfunction toolFieldFromInput(input: unknown, key: string): string | undefined {\n if (input && typeof input === 'object') {\n const val = (input as Record<string, unknown>)[key];\n if (typeof val === 'string' && val.trim()) return val.trim();\n }\n return undefined;\n}\n\n/** Pull a human-meaningful label out of a tool's input args, if present. */\nfunction toolTitleFromInput(input: unknown): string | undefined {\n for (const key of ['title', 'name', 'query'] as const) {\n const val = toolFieldFromInput(input, key);\n if (val) return val;\n }\n return undefined;\n}\n\nexport interface StreamChatOptions {\n conversationId: string;\n aiMessageId: string;\n system: string;\n messages: ModelMessage[];\n tools?: ToolSet;\n model?: string;\n temperature?: number;\n maxOutputTokens?: number;\n /** Max agent steps (tool round-trips). Defaults to 6. */\n maxSteps?: number;\n}\n\nexport interface StreamChatResult {\n text: string;\n toolCalls: StreamedToolCall[];\n /** Ordered text/tool parts, in the exact order the model produced them. */\n parts: StreamPart[];\n}\n\n/**\n * Stream a chat completion, broadcasting real text chunks and tool-call\n * events over Pusher. Does NOT persist the message or emit the terminal\n * stream-complete event — the caller does that once it has assembled the\n * final `content`/`meta` (e.g. PDF attachments, structured proposals).\n */\nexport async function streamChatResponse(\n options: StreamChatOptions,\n): Promise<StreamChatResult> {\n const {\n conversationId,\n aiMessageId,\n system,\n messages,\n tools,\n model = DEFAULT_STREAM_MODEL,\n temperature = 0.7,\n maxOutputTokens = 4096,\n maxSteps = 6,\n } = options;\n\n const redis = getRedis();\n const streamKey = `${STREAM_KEY_PREFIX}${aiMessageId}`;\n if (redis) {\n await redis.set(streamKey, '', 'EX', STREAM_REDIS_TTL);\n }\n\n let fullText = '';\n const parts: StreamPart[] = [];\n const toolCalls: StreamedToolCall[] = [];\n // Map AI SDK toolCallId -> index in `parts` so the executing indicator can\n // be flipped to completed/failed in place (preserving its position).\n const toolPartIndexById = new Map<string, number>();\n\n const appendText = (text: string) => {\n fullText += text;\n const last = parts[parts.length - 1];\n if (last && last.type === 'text') {\n last.text += text;\n } else {\n parts.push({ type: 'text', text });\n }\n };\n\n const broadcast = async (event: string, payload: Record<string, unknown>) => {\n try {\n await pusher.trigger(chatChannel(conversationId), event, payload);\n } catch (err) {\n logger.warn(`Failed to broadcast ${event}`, { err, aiMessageId });\n }\n };\n\n try {\n const result = streamText({\n model: openai(model),\n system,\n messages,\n ...(tools ? { tools } : {}),\n temperature,\n maxOutputTokens,\n stopWhen: stepCountIs(maxSteps),\n });\n\n // `fullStream` yields text deltas AND tool events in a single, correctly\n // ordered sequence — this is what makes inline tool calls possible. We\n // build an ordered `parts` list and broadcast each event as it happens.\n for await (const part of result.fullStream) {\n switch (part.type) {\n case 'text-delta': {\n appendText(part.text);\n if (redis) {\n await redis.append(streamKey, part.text).catch(() => {});\n }\n await broadcast('message-stream-chunk', {\n messageId: aiMessageId,\n chunk: part.text,\n });\n break;\n }\n case 'tool-call': {\n const title = toolTitleFromInput(part.input);\n const format = toolFieldFromInput(part.input, 'format');\n toolPartIndexById.set(part.toolCallId, parts.length);\n parts.push({\n type: 'tool',\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n ...(title ? { title } : {}),\n ...(format ? { format } : {}),\n status: 'executing',\n });\n await broadcast('message-tool-call', {\n messageId: aiMessageId,\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n ...(title ? { title } : {}),\n ...(format ? { format } : {}),\n status: 'executing',\n });\n break;\n }\n case 'tool-result':\n case 'tool-error': {\n const failed = part.type === 'tool-error';\n const deferred = isDeferredCompletionTool(part.toolName);\n // Deferred tools (e.g. generate_document) only queue work during the\n // stream — keep the spinner until the pipeline attaches a file.\n const status: 'executing' | 'completed' | 'failed' = failed\n ? 'failed'\n : deferred\n ? 'executing'\n : 'completed';\n const title = toolTitleFromInput(part.input);\n const format = toolFieldFromInput(part.input, 'format');\n const idx = toolPartIndexById.get(part.toolCallId);\n if (idx !== undefined) {\n const existing = parts[idx];\n if (existing && existing.type === 'tool') {\n existing.status = status;\n if (format) existing.format = format;\n }\n }\n toolCalls.push({\n toolName: part.toolName,\n input: part.input,\n output: part.type === 'tool-result' ? part.output : undefined,\n status: failed ? 'failed' : 'completed',\n });\n await broadcast('message-tool-call', {\n messageId: aiMessageId,\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n ...(title ? { title } : {}),\n ...(format ? { format } : {}),\n status,\n });\n break;\n }\n default:\n // ignore reasoning/source/step/finish/raw parts\n break;\n }\n }\n\n return { text: fullText, toolCalls, parts };\n } finally {\n if (redis) {\n await redis.del(streamKey).catch(() => {});\n }\n }\n}\n"],"names":[],"mappings":"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","debug_id":"5a03c108-e33a-50c7-a5fd-24f570b1b507"}
@@ -0,0 +1,16 @@
1
+ import { type ToolSet } from 'ai';
2
+ /** A single field write that was applied to a resource. */
3
+ export interface ModifierFieldUpdate {
4
+ field: string;
5
+ value: unknown;
6
+ /** Human-readable label for the change, e.g. "title" or "due date". */
7
+ label: string;
8
+ }
9
+ /**
10
+ * Build the field-update tool for an assignment. The `onFieldUpdate` callback
11
+ * runs after a successful persist so the route can broadcast the change.
12
+ */
13
+ export declare function assignmentModifierTools(userId: string, assignmentId: string, classId: string, onFieldUpdate: (update: ModifierFieldUpdate) => Promise<void> | void): ToolSet;
14
+ /** Build the field-update tool for a worksheet. */
15
+ export declare function worksheetModifierTools(worksheetId: string, userId: string, onFieldUpdate: (update: ModifierFieldUpdate) => Promise<void> | void): ToolSet;
16
+ //# sourceMappingURL=aiModifierTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiModifierTools.d.ts","sourceRoot":"/","sources":["services/aiModifierTools.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,IAAI,CAAC;AAW9C,2DAA2D;AAC3D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;CACf;AAgHD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GACnE,OAAO,CA+CT;AAED,mDAAmD;AACnD,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GACnE,OAAO,CAmIT"}