@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
@@ -6,6 +6,7 @@ import { test, expect, describe } from 'vitest';
6
6
  import { appRouter } from '../../../src/routers/_app';
7
7
  import { createTRPCContext } from '../../../src/trpc';
8
8
  import { prisma } from '../../../src/lib/prisma';
9
+ import { registerInput } from '../../helpers/registerInput.js';
9
10
  import {
10
11
  caller,
11
12
  login1,
@@ -64,12 +65,9 @@ describe('Auth Router', () => {
64
65
  test('check fails when unauthenticated', () => expectUnauth((c) => c.auth.check()));
65
66
 
66
67
  test('resendVerificationEmail succeeds for existing user', async () => {
67
- await caller.auth.register({
68
- username: 'resendtest',
69
- email: 'resend@studious.sh',
70
- password: 'password_is_1234',
71
- confirmPassword: 'password_is_1234',
72
- });
68
+ await caller.auth.register(
69
+ registerInput({ username: 'resendtest', email: 'resend@studious.sh' }),
70
+ );
73
71
  const result = await caller.auth.resendVerificationEmail({
74
72
  email: 'resend@studious.sh',
75
73
  });
@@ -227,23 +225,20 @@ describe('Auth Router', () => {
227
225
 
228
226
  test('register fails when username already exists', async () => {
229
227
  await expect(
230
- caller.auth.register({
231
- username: 'testuser1',
232
- email: 'newemail@studious.sh',
233
- password: 'password_is_1234',
234
- confirmPassword: 'password_is_1234',
235
- })
228
+ caller.auth.register(
229
+ registerInput({
230
+ username: 'testuser1',
231
+ email: 'newemail@studious.sh',
232
+ }),
233
+ )
236
234
  ).rejects.toThrow();
237
235
  });
238
236
 
239
237
  test('register fails when email already exists', async () => {
240
238
  await expect(
241
- caller.auth.register({
242
- username: 'newuser',
243
- email: 'test@studious.sh',
244
- password: 'password_is_1234',
245
- confirmPassword: 'password_is_1234',
246
- })
239
+ caller.auth.register(
240
+ registerInput({ username: 'newuser', email: 'test@studious.sh' }),
241
+ )
247
242
  ).rejects.toThrow();
248
243
  });
249
244
 
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { inferDocumentFormat } from '../../../src/lib/inferDocumentFormat.js';
3
+
4
+ describe('inferDocumentFormat', () => {
5
+ it('respects explicit non-pdf format', () => {
6
+ expect(inferDocumentFormat('Title', 'body', 'xlsx')).toBe('xlsx');
7
+ });
8
+
9
+ it('overrides model pdf when title says presentation', () => {
10
+ expect(
11
+ inferDocumentFormat(
12
+ 'Volume of Revolution Presentation',
13
+ 'Slide 1: intro',
14
+ 'pdf',
15
+ ),
16
+ ).toBe('pptx');
17
+ });
18
+
19
+ it('uses teacher message for format when model picked pdf', () => {
20
+ expect(
21
+ inferDocumentFormat(
22
+ 'Calculus Unit 3',
23
+ 'Content here',
24
+ 'pdf',
25
+ 'Can you make a PowerPoint for this lesson?',
26
+ ),
27
+ ).toBe('pptx');
28
+ });
29
+
30
+ it('defaults to pdf for generic handouts', () => {
31
+ expect(inferDocumentFormat('Reading Guide', 'Chapter summary', 'pdf')).toBe('pdf');
32
+ });
33
+ });
@@ -1,32 +1,49 @@
1
1
  import { describe, expect, test } from "vitest";
2
- import { labChatArrayFieldInstructions, labChatResponseFormat, labChatResponseSchema } from "../../../src/pipelines/aiLabChatContract.js";
2
+ import {
3
+ assignmentToCreateSchema,
4
+ sectionToCreateSchema,
5
+ worksheetQuestionSchema,
6
+ } from "../../../src/pipelines/aiLabChatContract.js";
3
7
 
4
8
  describe("aiLabChat contract", () => {
5
- test("defaults worksheet and section collections to empty arrays", () => {
6
- const parsed = labChatResponseSchema.parse({
7
- text: "Summary",
8
- docs: null,
9
- assignmentsToCreate: null,
9
+ test("worksheet questions default options, mark scheme, and points", () => {
10
+ const parsed = worksheetQuestionSchema.parse({
11
+ type: "SHORT_ANSWER",
12
+ question: "What is osmosis?",
13
+ answer: "Diffusion of water across a membrane",
14
+ order: 0,
10
15
  });
11
16
 
12
- expect(parsed.worksheetsToCreate).toEqual([]);
13
- expect(parsed.sectionsToCreate).toEqual([]);
17
+ expect(parsed.options).toEqual([]);
18
+ expect(parsed.markScheme).toEqual([]);
19
+ expect(parsed.points).toBe(0);
14
20
  });
15
21
 
16
- test("rejects null worksheet and section collections", () => {
17
- expect(() => labChatResponseSchema.parse({
18
- text: "Summary",
19
- docs: null,
20
- worksheetsToCreate: null,
21
- sectionsToCreate: null,
22
- assignmentsToCreate: null,
23
- })).toThrow();
22
+ test("section color must be a hex value when provided", () => {
23
+ expect(() => sectionToCreateSchema.parse({ name: "Unit 1", color: "blue" })).toThrow();
24
+ expect(sectionToCreateSchema.parse({ name: "Unit 1", color: "#3B82F6" }).color).toBe(
25
+ "#3B82F6",
26
+ );
27
+ expect(sectionToCreateSchema.parse({ name: "Unit 1" }).color).toBeUndefined();
24
28
  });
25
29
 
26
- test("prompt instructions require arrays for worksheet and section fields", () => {
27
- expect(labChatResponseFormat).toContain(`"worksheetsToCreate": array`);
28
- expect(labChatResponseFormat).toContain(`"sectionsToCreate": array`);
29
- expect(labChatArrayFieldInstructions).toContain(`Use [] when there are no worksheets to create.`);
30
- expect(labChatArrayFieldInstructions).toContain(`Use [] when there are no sections to create.`);
30
+ test("assignment requires an ISO datetime due date", () => {
31
+ const base = {
32
+ title: "Osmosis Quiz",
33
+ instructions: "Answer all questions.",
34
+ acceptFiles: false,
35
+ acceptExtendedResponse: false,
36
+ acceptWorksheet: true,
37
+ maxGrade: 10,
38
+ worksheetIds: [],
39
+ studentIds: [],
40
+ type: "QUIZ" as const,
41
+ attachments: [],
42
+ };
43
+
44
+ expect(() => assignmentToCreateSchema.parse({ ...base, dueDate: "not-a-date" })).toThrow();
45
+ expect(
46
+ assignmentToCreateSchema.parse({ ...base, dueDate: "2026-04-08T12:00:00Z" }).dueDate,
47
+ ).toBe("2026-04-08T12:00:00Z");
31
48
  });
32
49
  });
@@ -1,26 +1,24 @@
1
1
  import { describe, expect, test } from "vitest";
2
2
 
3
3
  import {
4
- buildLabChatResponseMessages,
5
- buildLabChatSystemPrompt,
4
+ buildLabChatAgentMessages,
5
+ buildLabChatAgentSystemPrompt,
6
6
  } from "../../../src/pipelines/labChatPrompt";
7
- import { labChatArrayFieldInstructions, labChatResponseFormat } from "../../../src/pipelines/aiLabChatContract";
8
7
 
9
- describe("buildLabChatSystemPrompt", () => {
10
- test("requires arrays for worksheet and section structured output fields", () => {
11
- const prompt = buildLabChatSystemPrompt("Context");
8
+ describe("buildLabChatAgentSystemPrompt", () => {
9
+ test("instructs the model to create content via proposal tools", () => {
10
+ const prompt = buildLabChatAgentSystemPrompt("Context");
12
11
 
13
- expect(prompt).toContain(labChatResponseFormat);
14
- expect(prompt).toContain(labChatArrayFieldInstructions);
15
- expect(prompt).toContain('Do not return null for "worksheetsToCreate" or "sectionsToCreate".');
16
- expect(prompt).not.toContain('"worksheetsToCreate": null | array');
17
- expect(prompt).not.toContain('"sectionsToCreate": null | array');
12
+ expect(prompt).toContain("Context");
13
+ expect(prompt).toContain("HOW TO CREATE CONTENT (TOOLS):");
14
+ expect(prompt).toContain("propose_assignment");
15
+ expect(prompt).toContain("propose_worksheet");
18
16
  });
19
17
  });
20
18
 
21
- describe("buildLabChatResponseMessages", () => {
22
- test("builds response messages from history without replaying the latest teacher turn", () => {
23
- const messages = buildLabChatResponseMessages({
19
+ describe("buildLabChatAgentMessages", () => {
20
+ test("folds the class context into the system prompt, not the message list", () => {
21
+ const { system, messages } = buildLabChatAgentMessages({
24
22
  context: "Topic context",
25
23
  classContext: "CLASS: Biology",
26
24
  recentMessages: [
@@ -32,30 +30,19 @@ describe("buildLabChatResponseMessages", () => {
32
30
  profile: { displayName: "Ms Patel" },
33
31
  },
34
32
  },
35
- {
36
- senderId: "ai-user",
37
- content: "I can do that.",
38
- },
39
33
  ],
40
34
  isAIUser: (senderId) => senderId === "ai-user",
41
35
  });
42
36
 
43
- const teacherTurns = messages.filter(
44
- (message) =>
45
- message.role === "user" &&
46
- typeof message.content === "string" &&
47
- message.content.includes("Create a quiz on osmosis"),
48
- );
49
-
50
- expect(teacherTurns).toHaveLength(1);
51
- expect(messages.at(-2)).toMatchObject({
52
- role: "developer",
53
- content: expect.stringContaining("CLASS CONTEXT"),
54
- });
37
+ expect(system).toContain("CLASS CONTEXT");
38
+ expect(system).toContain("CLASS: Biology");
39
+ expect(messages).toEqual([
40
+ { role: "user", content: "Ms Patel: Create a quiz on osmosis" },
41
+ ]);
55
42
  });
56
43
 
57
44
  test("sorts timestamped history into chronological order before prompt assembly", () => {
58
- const messages = buildLabChatResponseMessages({
45
+ const { messages } = buildLabChatAgentMessages({
59
46
  context: "Topic context",
60
47
  classContext: "CLASS: Biology",
61
48
  recentMessages: [
@@ -79,18 +66,18 @@ describe("buildLabChatResponseMessages", () => {
79
66
  isAIUser: (senderId) => senderId === "ai-user",
80
67
  });
81
68
 
82
- expect(messages[1]).toMatchObject({
69
+ expect(messages[0]).toMatchObject({
83
70
  role: "user",
84
71
  content: "Ms Patel: First request",
85
72
  });
86
- expect(messages[2]).toMatchObject({
73
+ expect(messages[1]).toMatchObject({
87
74
  role: "assistant",
88
75
  content: "Second response",
89
76
  });
90
77
  });
91
78
 
92
79
  test("keeps original order among messages without timestamps", () => {
93
- const messages = buildLabChatResponseMessages({
80
+ const { messages } = buildLabChatAgentMessages({
94
81
  context: "Topic context",
95
82
  classContext: "CLASS: Biology",
96
83
  recentMessages: [
@@ -114,11 +101,11 @@ describe("buildLabChatResponseMessages", () => {
114
101
  isAIUser: () => false,
115
102
  });
116
103
 
117
- expect(messages[1]).toMatchObject({
104
+ expect(messages[0]).toMatchObject({
118
105
  role: "user",
119
106
  content: "Ms Patel: First untimed request",
120
107
  });
121
- expect(messages[2]).toMatchObject({
108
+ expect(messages[1]).toMatchObject({
122
109
  role: "user",
123
110
  content: "Ms Patel: Second untimed request",
124
111
  });
@@ -0,0 +1,92 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ enrichStreamPartsWithGeneratedFiles,
4
+ finalizeDeferredToolParts,
5
+ } from '../../../src/services/aiChatStream.js';
6
+
7
+ describe('enrichStreamPartsWithGeneratedFiles', () => {
8
+ it('links generated files to generate_document tool parts by title', () => {
9
+ const parts = enrichStreamPartsWithGeneratedFiles(
10
+ [
11
+ { type: 'text', text: 'Here is your worksheet.' },
12
+ {
13
+ type: 'tool',
14
+ toolCallId: 'tc-1',
15
+ toolName: 'generate_document',
16
+ title: 'Algebra Review',
17
+ status: 'completed',
18
+ },
19
+ ],
20
+ [{ title: 'Algebra Review', fileId: 'file-1', fileName: 'Algebra_Review.pdf' }],
21
+ );
22
+
23
+ expect(parts[1]).toMatchObject({
24
+ type: 'tool',
25
+ fileId: 'file-1',
26
+ fileName: 'Algebra_Review.pdf',
27
+ });
28
+ });
29
+
30
+ it('falls back to order when titles do not match', () => {
31
+ const parts = enrichStreamPartsWithGeneratedFiles(
32
+ [
33
+ {
34
+ type: 'tool',
35
+ toolCallId: 'tc-1',
36
+ toolName: 'generate_document',
37
+ status: 'completed',
38
+ },
39
+ ],
40
+ [{ title: 'Doc A', fileId: 'file-a', fileName: 'doc_a.pdf' }],
41
+ );
42
+
43
+ expect(parts[0]).toMatchObject({ fileId: 'file-a' });
44
+ });
45
+
46
+ it('marks linked generate_document tools completed', () => {
47
+ const parts = enrichStreamPartsWithGeneratedFiles(
48
+ [
49
+ {
50
+ type: 'tool',
51
+ toolCallId: 'tc-1',
52
+ toolName: 'generate_document',
53
+ title: 'Slides',
54
+ status: 'executing',
55
+ },
56
+ ],
57
+ [{ title: 'Slides', fileId: 'file-1', fileName: 'slides.pptx' }],
58
+ );
59
+
60
+ expect(parts[0]).toMatchObject({ status: 'completed', fileId: 'file-1' });
61
+ });
62
+ });
63
+
64
+ describe('finalizeDeferredToolParts', () => {
65
+ it('marks generate_document without a file as failed', () => {
66
+ const parts = finalizeDeferredToolParts([
67
+ {
68
+ type: 'tool',
69
+ toolCallId: 'tc-1',
70
+ toolName: 'generate_document',
71
+ title: 'Missing',
72
+ status: 'executing',
73
+ },
74
+ ]);
75
+
76
+ expect(parts[0]).toMatchObject({ status: 'failed' });
77
+ });
78
+
79
+ it('leaves other tools unchanged', () => {
80
+ const parts = finalizeDeferredToolParts([
81
+ {
82
+ type: 'tool',
83
+ toolCallId: 'tc-1',
84
+ toolName: 'propose_assignment',
85
+ title: 'HW',
86
+ status: 'completed',
87
+ },
88
+ ]);
89
+
90
+ expect(parts[0]).toMatchObject({ status: 'completed' });
91
+ });
92
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ extractContainerGeneratedFiles,
4
+ mimeTypeForFormat,
5
+ normalizeDocumentFormat,
6
+ } from '../../../src/services/openaiDocumentGen.js';
7
+ import type { Response } from 'openai/resources/responses/responses.js';
8
+
9
+ describe('openaiDocumentGen', () => {
10
+ it('normalizes unknown formats to pdf', () => {
11
+ expect(normalizeDocumentFormat('docx')).toBe('docx');
12
+ expect(normalizeDocumentFormat('.PDF')).toBe('pdf');
13
+ expect(normalizeDocumentFormat('bogus')).toBe('pdf');
14
+ });
15
+
16
+ it('maps formats to mime types', () => {
17
+ expect(mimeTypeForFormat('pdf')).toBe('application/pdf');
18
+ expect(mimeTypeForFormat('xlsx')).toBe(
19
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
20
+ );
21
+ expect(mimeTypeForFormat('png')).toBe('image/png');
22
+ });
23
+
24
+ it('collects container_file_citation annotations for the requested extension', () => {
25
+ const response = {
26
+ output: [
27
+ {
28
+ type: 'message',
29
+ content: [
30
+ {
31
+ type: 'output_text',
32
+ text: 'Here is your spreadsheet.',
33
+ annotations: [
34
+ {
35
+ type: 'container_file_citation',
36
+ container_id: 'cntr_1',
37
+ file_id: 'cfile_1',
38
+ filename: 'Grades.xlsx',
39
+ start_index: 0,
40
+ end_index: 0,
41
+ },
42
+ {
43
+ type: 'container_file_citation',
44
+ container_id: 'cntr_1',
45
+ file_id: 'cfile_2',
46
+ filename: 'Notes.pdf',
47
+ start_index: 0,
48
+ end_index: 0,
49
+ },
50
+ ],
51
+ },
52
+ ],
53
+ },
54
+ ],
55
+ } as unknown as Response;
56
+
57
+ expect(extractContainerGeneratedFiles(response, 'xlsx')).toEqual([
58
+ {
59
+ containerId: 'cntr_1',
60
+ fileId: 'cfile_1',
61
+ filename: 'Grades.xlsx',
62
+ },
63
+ ]);
64
+ });
65
+ });