@studious-lms/server 1.12.0 → 1.14.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 (338) 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/lib/config/cors.d.ts.map +1 -1
  8. package/dist/lib/config/cors.js +31 -11
  9. package/dist/lib/config/cors.js.map +1 -1
  10. package/dist/lib/config/env.d.ts +17 -0
  11. package/dist/lib/config/env.d.ts.map +1 -1
  12. package/dist/lib/config/env.js +8 -2
  13. package/dist/lib/config/env.js.map +1 -1
  14. package/dist/lib/googleCloudStorage.d.ts +10 -0
  15. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  16. package/dist/lib/googleCloudStorage.js +22 -2
  17. package/dist/lib/googleCloudStorage.js.map +1 -1
  18. package/dist/lib/prisma.d.ts +104 -5
  19. package/dist/lib/prisma.d.ts.map +1 -1
  20. package/dist/lib/prisma.js +33 -2
  21. package/dist/lib/prisma.js.map +1 -1
  22. package/dist/models/agenda.d.ts +20 -20
  23. package/dist/models/announcement.d.ts +177 -69
  24. package/dist/models/announcement.d.ts.map +1 -1
  25. package/dist/models/assignment.d.ts +551 -353
  26. package/dist/models/assignment.d.ts.map +1 -1
  27. package/dist/models/attendance.d.ts +197 -71
  28. package/dist/models/attendance.d.ts.map +1 -1
  29. package/dist/models/auth.d.ts +348 -114
  30. package/dist/models/auth.d.ts.map +1 -1
  31. package/dist/models/class.d.ts +324 -188
  32. package/dist/models/class.d.ts.map +1 -1
  33. package/dist/models/class.js +9 -3
  34. package/dist/models/class.js.map +1 -1
  35. package/dist/models/cloudStorage.d.ts +146 -56
  36. package/dist/models/cloudStorage.d.ts.map +1 -1
  37. package/dist/models/comment.d.ts +269 -110
  38. package/dist/models/comment.d.ts.map +1 -1
  39. package/dist/models/comment.js +3 -2
  40. package/dist/models/comment.js.map +1 -1
  41. package/dist/models/conversation.d.ts +206 -63
  42. package/dist/models/conversation.d.ts.map +1 -1
  43. package/dist/models/conversation.js +13 -2
  44. package/dist/models/conversation.js.map +1 -1
  45. package/dist/models/department.d.ts +196 -70
  46. package/dist/models/department.d.ts.map +1 -1
  47. package/dist/models/event.d.ts +287 -125
  48. package/dist/models/event.d.ts.map +1 -1
  49. package/dist/models/file.d.ts +389 -263
  50. package/dist/models/file.d.ts.map +1 -1
  51. package/dist/models/folder.d.ts +433 -181
  52. package/dist/models/folder.d.ts.map +1 -1
  53. package/dist/models/labChat.d.ts +209 -83
  54. package/dist/models/labChat.d.ts.map +1 -1
  55. package/dist/models/marketing.d.ts +80 -26
  56. package/dist/models/marketing.d.ts.map +1 -1
  57. package/dist/models/message.d.ts +95 -41
  58. package/dist/models/message.d.ts.map +1 -1
  59. package/dist/models/newtonChat.d.ts +83 -29
  60. package/dist/models/newtonChat.d.ts.map +1 -1
  61. package/dist/models/notification.d.ts +61 -25
  62. package/dist/models/notification.d.ts.map +1 -1
  63. package/dist/models/school.d.ts +349 -133
  64. package/dist/models/school.d.ts.map +1 -1
  65. package/dist/models/section.d.ts +216 -72
  66. package/dist/models/section.d.ts.map +1 -1
  67. package/dist/models/tag.d.ts +156 -48
  68. package/dist/models/tag.d.ts.map +1 -1
  69. package/dist/models/user.d.ts +82 -28
  70. package/dist/models/user.d.ts.map +1 -1
  71. package/dist/models/worksheet.d.ts +1023 -410
  72. package/dist/models/worksheet.d.ts.map +1 -1
  73. package/dist/models/worksheet.js +19 -7
  74. package/dist/models/worksheet.js.map +1 -1
  75. package/dist/pipelines/aiLabChat.d.ts +0 -8
  76. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  77. package/dist/pipelines/aiLabChat.js +50 -67
  78. package/dist/pipelines/aiLabChat.js.map +1 -1
  79. package/dist/pipelines/aiLabChatContract.d.ts +393 -388
  80. package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
  81. package/dist/pipelines/aiLabChatContract.js +63 -69
  82. package/dist/pipelines/aiLabChatContract.js.map +1 -1
  83. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  84. package/dist/pipelines/aiNewtonChat.js +41 -41
  85. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  86. package/dist/pipelines/gradeWorksheet.d.ts +8 -8
  87. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  88. package/dist/pipelines/gradeWorksheet.js +59 -14
  89. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  90. package/dist/pipelines/labChatPrompt.d.ts +10 -8
  91. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  92. package/dist/pipelines/labChatPrompt.js +47 -67
  93. package/dist/pipelines/labChatPrompt.js.map +1 -1
  94. package/dist/pipelines/pdfToWorksheet.d.ts +2 -2
  95. package/dist/routers/_app.d.ts +2268 -1898
  96. package/dist/routers/_app.d.ts.map +1 -1
  97. package/dist/routers/_app.js +4 -2
  98. package/dist/routers/_app.js.map +1 -1
  99. package/dist/routers/admin.d.ts +13 -13
  100. package/dist/routers/agenda.d.ts +18 -18
  101. package/dist/routers/announcement.d.ts +32 -28
  102. package/dist/routers/announcement.d.ts.map +1 -1
  103. package/dist/routers/announcement.js +6 -5
  104. package/dist/routers/announcement.js.map +1 -1
  105. package/dist/routers/assignment.d.ts +335 -327
  106. package/dist/routers/assignment.d.ts.map +1 -1
  107. package/dist/routers/assignment.js +4 -4
  108. package/dist/routers/assignment.js.map +1 -1
  109. package/dist/routers/attendance.d.ts +10 -10
  110. package/dist/routers/auth.d.ts +2 -2
  111. package/dist/routers/class.d.ts +170 -147
  112. package/dist/routers/class.d.ts.map +1 -1
  113. package/dist/routers/comment.d.ts +40 -16
  114. package/dist/routers/comment.d.ts.map +1 -1
  115. package/dist/routers/comment.js +6 -3
  116. package/dist/routers/comment.js.map +1 -1
  117. package/dist/routers/conversation.d.ts +17 -17
  118. package/dist/routers/department.d.ts +34 -34
  119. package/dist/routers/discussion.d.ts +65 -53
  120. package/dist/routers/discussion.d.ts.map +1 -1
  121. package/dist/routers/discussion.js +6 -2
  122. package/dist/routers/discussion.js.map +1 -1
  123. package/dist/routers/embedding.d.ts +69 -0
  124. package/dist/routers/embedding.d.ts.map +1 -0
  125. package/dist/routers/embedding.js +143 -0
  126. package/dist/routers/embedding.js.map +1 -0
  127. package/dist/routers/event.d.ts +39 -39
  128. package/dist/routers/file.d.ts +12 -12
  129. package/dist/routers/folder.d.ts +38 -38
  130. package/dist/routers/labChat.d.ts +19 -19
  131. package/dist/routers/marketing.d.ts +2 -2
  132. package/dist/routers/message.d.ts +13 -13
  133. package/dist/routers/newtonChat.d.ts +4 -4
  134. package/dist/routers/notifications.d.ts +9 -9
  135. package/dist/routers/permissions.d.ts +14 -14
  136. package/dist/routers/school.d.ts +55 -55
  137. package/dist/routers/search.d.ts +22 -1
  138. package/dist/routers/search.d.ts.map +1 -1
  139. package/dist/routers/search.js +9 -3
  140. package/dist/routers/search.js.map +1 -1
  141. package/dist/routers/section.d.ts +4 -4
  142. package/dist/routers/studentProgress.d.ts +1 -1
  143. package/dist/routers/tag.d.ts +7 -7
  144. package/dist/routers/user.d.ts +10 -10
  145. package/dist/routers/worksheet.d.ts +97 -56
  146. package/dist/routers/worksheet.d.ts.map +1 -1
  147. package/dist/routers/worksheet.js +28 -2
  148. package/dist/routers/worksheet.js.map +1 -1
  149. package/dist/routes/aiStream.d.ts +2 -0
  150. package/dist/routes/aiStream.d.ts.map +1 -0
  151. package/dist/routes/aiStream.js +696 -0
  152. package/dist/routes/aiStream.js.map +1 -0
  153. package/dist/seedDatabase.d.ts +2 -2
  154. package/dist/seedDatabase.js +6 -6
  155. package/dist/seedDatabase.js.map +1 -1
  156. package/dist/services/adminInstitution.d.ts +8 -8
  157. package/dist/services/adminRunJob.d.ts +4 -4
  158. package/dist/services/agenda.d.ts +18 -18
  159. package/dist/services/aiChatStream.d.ts +68 -0
  160. package/dist/services/aiChatStream.d.ts.map +1 -0
  161. package/dist/services/aiChatStream.js +157 -0
  162. package/dist/services/aiChatStream.js.map +1 -0
  163. package/dist/services/aiModifierTools.d.ts +32 -0
  164. package/dist/services/aiModifierTools.d.ts.map +1 -0
  165. package/dist/services/aiModifierTools.js +165 -0
  166. package/dist/services/aiModifierTools.js.map +1 -0
  167. package/dist/services/aiProposalTools.d.ts +32 -0
  168. package/dist/services/aiProposalTools.d.ts.map +1 -0
  169. package/dist/services/aiProposalTools.js +207 -0
  170. package/dist/services/aiProposalTools.js.map +1 -0
  171. package/dist/services/aiStream.d.ts +22 -0
  172. package/dist/services/aiStream.d.ts.map +1 -0
  173. package/dist/services/aiStream.js +142 -0
  174. package/dist/services/aiStream.js.map +1 -0
  175. package/dist/services/aiTools.d.ts +16 -0
  176. package/dist/services/aiTools.d.ts.map +1 -0
  177. package/dist/services/aiTools.js +271 -0
  178. package/dist/services/aiTools.js.map +1 -0
  179. package/dist/services/announcement.d.ts +12 -12
  180. package/dist/services/announcement.d.ts.map +1 -1
  181. package/dist/services/announcement.js +7 -2
  182. package/dist/services/announcement.js.map +1 -1
  183. package/dist/services/assignment.d.ts +293 -293
  184. package/dist/services/assignment.d.ts.map +1 -1
  185. package/dist/services/assignment.js +36 -15
  186. package/dist/services/assignment.js.map +1 -1
  187. package/dist/services/attendance.d.ts +10 -10
  188. package/dist/services/auth.d.ts +2 -2
  189. package/dist/services/class.d.ts +161 -138
  190. package/dist/services/class.d.ts.map +1 -1
  191. package/dist/services/class.js +25 -4
  192. package/dist/services/class.js.map +1 -1
  193. package/dist/services/comment.d.ts +34 -16
  194. package/dist/services/comment.d.ts.map +1 -1
  195. package/dist/services/comment.js +23 -2
  196. package/dist/services/comment.js.map +1 -1
  197. package/dist/services/conversation.d.ts +17 -17
  198. package/dist/services/conversation.d.ts.map +1 -1
  199. package/dist/services/conversation.js +9 -7
  200. package/dist/services/conversation.js.map +1 -1
  201. package/dist/services/department.d.ts +33 -33
  202. package/dist/services/discussion.d.ts +58 -58
  203. package/dist/services/discussion.d.ts.map +1 -1
  204. package/dist/services/discussion.js +8 -8
  205. package/dist/services/discussion.js.map +1 -1
  206. package/dist/services/discussionSummary.d.ts +21 -0
  207. package/dist/services/discussionSummary.d.ts.map +1 -0
  208. package/dist/services/discussionSummary.js +179 -0
  209. package/dist/services/discussionSummary.js.map +1 -0
  210. package/dist/services/embedding.d.ts +67 -0
  211. package/dist/services/embedding.d.ts.map +1 -0
  212. package/dist/services/embedding.js +226 -0
  213. package/dist/services/embedding.js.map +1 -0
  214. package/dist/services/event.d.ts +37 -37
  215. package/dist/services/file.d.ts +24 -24
  216. package/dist/services/folder.d.ts +36 -36
  217. package/dist/services/labChat.d.ts +19 -19
  218. package/dist/services/marketing.d.ts +2 -2
  219. package/dist/services/message.d.ts +11 -11
  220. package/dist/services/message.d.ts.map +1 -1
  221. package/dist/services/message.js +2 -2
  222. package/dist/services/message.js.map +1 -1
  223. package/dist/services/newtonChat.d.ts +4 -4
  224. package/dist/services/notification.d.ts +9 -9
  225. package/dist/services/notification.d.ts.map +1 -1
  226. package/dist/services/permissions.d.ts +14 -14
  227. package/dist/services/school.d.ts +50 -50
  228. package/dist/services/search.d.ts +12 -0
  229. package/dist/services/search.d.ts.map +1 -1
  230. package/dist/services/search.js +120 -2
  231. package/dist/services/search.js.map +1 -1
  232. package/dist/services/section.d.ts +3 -3
  233. package/dist/services/tag.d.ts +4 -4
  234. package/dist/services/user.d.ts +4 -4
  235. package/dist/services/user.d.ts.map +1 -1
  236. package/dist/services/user.js +10 -15
  237. package/dist/services/user.js.map +1 -1
  238. package/dist/services/worksheet.d.ts +62 -47
  239. package/dist/services/worksheet.d.ts.map +1 -1
  240. package/dist/services/worksheet.js +11 -4
  241. package/dist/services/worksheet.js.map +1 -1
  242. package/dist/utils/streamBroadcast.d.ts +11 -0
  243. package/dist/utils/streamBroadcast.d.ts.map +1 -0
  244. package/dist/utils/streamBroadcast.js +32 -0
  245. package/dist/utils/streamBroadcast.js.map +1 -0
  246. package/dist/workers/connection.d.ts +15 -0
  247. package/dist/workers/connection.d.ts.map +1 -1
  248. package/dist/workers/connection.js +20 -2
  249. package/dist/workers/connection.js.map +1 -1
  250. package/dist/workers/index.js +4 -2
  251. package/dist/workers/index.js.map +1 -1
  252. package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -1
  253. package/dist/workers/processors/createRunJobWorker.js +31 -8
  254. package/dist/workers/processors/createRunJobWorker.js.map +1 -1
  255. package/dist/workers/processors/embedContent.d.ts +8 -0
  256. package/dist/workers/processors/embedContent.d.ts.map +1 -0
  257. package/dist/workers/processors/embedContent.js +76 -0
  258. package/dist/workers/processors/embedContent.js.map +1 -0
  259. package/dist/workers/queues.d.ts +6 -0
  260. package/dist/workers/queues.d.ts.map +1 -1
  261. package/dist/workers/queues.js +5 -3
  262. package/dist/workers/queues.js.map +1 -1
  263. package/package.json +5 -1
  264. package/prisma/migrations/20260609215135_init/migration.sql +1412 -0
  265. package/prisma/migrations/20260610130000_add_worksheet_tags/migration.sql +13 -0
  266. package/prisma/schema.prisma +39 -7
  267. package/src/index.ts +4 -0
  268. package/src/lib/config/cors.ts +32 -9
  269. package/src/lib/config/env.ts +6 -0
  270. package/src/lib/googleCloudStorage.ts +21 -0
  271. package/src/lib/prisma.ts +40 -1
  272. package/src/models/class.ts +10 -3
  273. package/src/models/comment.ts +1 -0
  274. package/src/models/conversation.ts +12 -0
  275. package/src/models/worksheet.ts +16 -7
  276. package/src/pipelines/aiLabChat.ts +54 -79
  277. package/src/pipelines/aiLabChatContract.ts +69 -69
  278. package/src/pipelines/aiNewtonChat.ts +46 -43
  279. package/src/pipelines/gradeWorksheet.ts +66 -15
  280. package/src/pipelines/labChatPrompt.ts +63 -88
  281. package/src/routers/_app.ts +2 -0
  282. package/src/routers/announcement.ts +4 -3
  283. package/src/routers/assignment.ts +2 -2
  284. package/src/routers/comment.ts +5 -0
  285. package/src/routers/discussion.ts +7 -0
  286. package/src/routers/embedding.ts +159 -0
  287. package/src/routers/search.ts +10 -1
  288. package/src/routers/worksheet.ts +29 -0
  289. package/src/routes/aiStream.ts +789 -0
  290. package/src/seedDatabase.ts +4 -4
  291. package/src/services/aiChatStream.ts +219 -0
  292. package/src/services/aiModifierTools.ts +188 -0
  293. package/src/services/aiProposalTools.ts +272 -0
  294. package/src/services/aiStream.ts +175 -0
  295. package/src/services/aiTools.ts +290 -0
  296. package/src/services/announcement.ts +7 -0
  297. package/src/services/assignment.ts +36 -14
  298. package/src/services/class.ts +30 -2
  299. package/src/services/comment.ts +22 -0
  300. package/src/services/conversation.ts +9 -4
  301. package/src/services/discussion.ts +9 -8
  302. package/src/services/discussionSummary.ts +226 -0
  303. package/src/services/embedding.ts +285 -0
  304. package/src/services/message.ts +2 -2
  305. package/src/services/search.ts +138 -0
  306. package/src/services/user.ts +9 -17
  307. package/src/services/worksheet.ts +10 -2
  308. package/src/utils/streamBroadcast.ts +48 -0
  309. package/src/workers/connection.ts +19 -0
  310. package/src/workers/index.ts +2 -0
  311. package/src/workers/processors/createRunJobWorker.ts +32 -6
  312. package/src/workers/processors/embedContent.ts +90 -0
  313. package/src/workers/queues.ts +5 -1
  314. package/tests/integration/routers/conversation.test.ts +9 -7
  315. package/tests/unit/pipelines/aiLabChatContract.test.ts +38 -21
  316. package/tests/unit/pipelines/labChatPrompt.test.ts +23 -36
  317. package/prisma/migrations/20250807062924_init/migration.sql +0 -436
  318. package/prisma/migrations/20250919063704_init2/migration.sql +0 -24
  319. package/prisma/migrations/20250920143543_add_profile_fields/migration.sql +0 -15
  320. package/prisma/migrations/20250922165147_add_chat_models/migration.sql +0 -68
  321. package/prisma/migrations/20250922165314_add_chat_roles/migration.sql +0 -5
  322. package/prisma/migrations/20250922171242_refactor_notification_system/migration.sql +0 -20
  323. package/prisma/migrations/20250922172208_add_mentions_system/migration.sql +0 -21
  324. package/prisma/migrations/20250925072732_add_lab_chat_system/migration.sql +0 -25
  325. package/prisma/migrations/20251020151505_add_upload_tracking_fields/migration.sql +0 -57
  326. package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +0 -30
  327. package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +0 -35
  328. package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +0 -14
  329. package/prisma/migrations/20260415103500_add_session_type/migration.sql +0 -33
  330. package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +0 -19
  331. package/prisma/migrations/20260417083000_add_conversation_member_hidden_at/migration.sql +0 -2
  332. package/prisma/migrations/20260502230500_assignment_submission_comments/migration.sql +0 -60
  333. package/prisma/migrations/20260504071500_school_memberships/migration.sql +0 -81
  334. package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +0 -166
  335. package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +0 -184
  336. package/prisma/migrations/20260510170000_add_folder_permissions/migration.sql +0 -93
  337. package/prisma/migrations/20260510183000_add_discussions/migration.sql +0 -151
  338. /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,68 @@
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
+ status: 'executing' | 'completed' | 'failed';
42
+ };
43
+ export interface StreamChatOptions {
44
+ conversationId: string;
45
+ aiMessageId: string;
46
+ system: string;
47
+ messages: ModelMessage[];
48
+ tools?: ToolSet;
49
+ model?: string;
50
+ temperature?: number;
51
+ maxOutputTokens?: number;
52
+ /** Max agent steps (tool round-trips). Defaults to 6. */
53
+ maxSteps?: number;
54
+ }
55
+ export interface StreamChatResult {
56
+ text: string;
57
+ toolCalls: StreamedToolCall[];
58
+ /** Ordered text/tool parts, in the exact order the model produced them. */
59
+ parts: StreamPart[];
60
+ }
61
+ /**
62
+ * Stream a chat completion, broadcasting real text chunks and tool-call
63
+ * events over Pusher. Does NOT persist the message or emit the terminal
64
+ * stream-complete event — the caller does that once it has assembled the
65
+ * final `content`/`meta` (e.g. PDF attachments, structured proposals).
66
+ */
67
+ export declare function streamChatResponse(options: StreamChatOptions): Promise<StreamChatResult>;
68
+ //# 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,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;CAC9C,CAAC;AAcN,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,CA8H3B"}
@@ -0,0 +1,157 @@
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]="53e385f3-8180-59f0-9c1b-b89457a29317")}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
+ /** Pull a human-meaningful label out of a tool's input args, if present. */
26
+ function toolTitleFromInput(input) {
27
+ if (input && typeof input === 'object') {
28
+ const obj = input;
29
+ for (const key of ['title', 'name', 'query']) {
30
+ const val = obj[key];
31
+ if (typeof val === 'string' && val.trim())
32
+ return val.trim();
33
+ }
34
+ }
35
+ return undefined;
36
+ }
37
+ /**
38
+ * Stream a chat completion, broadcasting real text chunks and tool-call
39
+ * events over Pusher. Does NOT persist the message or emit the terminal
40
+ * stream-complete event — the caller does that once it has assembled the
41
+ * final `content`/`meta` (e.g. PDF attachments, structured proposals).
42
+ */
43
+ export async function streamChatResponse(options) {
44
+ const { conversationId, aiMessageId, system, messages, tools, model = DEFAULT_STREAM_MODEL, temperature = 0.7, maxOutputTokens = 4096, maxSteps = 6, } = options;
45
+ const redis = getRedis();
46
+ const streamKey = `${STREAM_KEY_PREFIX}${aiMessageId}`;
47
+ if (redis) {
48
+ await redis.set(streamKey, '', 'EX', STREAM_REDIS_TTL);
49
+ }
50
+ let fullText = '';
51
+ const parts = [];
52
+ const toolCalls = [];
53
+ // Map AI SDK toolCallId -> index in `parts` so the executing indicator can
54
+ // be flipped to completed/failed in place (preserving its position).
55
+ const toolPartIndexById = new Map();
56
+ const appendText = (text) => {
57
+ fullText += text;
58
+ const last = parts[parts.length - 1];
59
+ if (last && last.type === 'text') {
60
+ last.text += text;
61
+ }
62
+ else {
63
+ parts.push({ type: 'text', text });
64
+ }
65
+ };
66
+ const broadcast = async (event, payload) => {
67
+ try {
68
+ await pusher.trigger(chatChannel(conversationId), event, payload);
69
+ }
70
+ catch (err) {
71
+ logger.warn(`Failed to broadcast ${event}`, { err, aiMessageId });
72
+ }
73
+ };
74
+ try {
75
+ const result = streamText({
76
+ model: openai(model),
77
+ system,
78
+ messages,
79
+ ...(tools ? { tools } : {}),
80
+ temperature,
81
+ maxOutputTokens,
82
+ stopWhen: stepCountIs(maxSteps),
83
+ });
84
+ // `fullStream` yields text deltas AND tool events in a single, correctly
85
+ // ordered sequence — this is what makes inline tool calls possible. We
86
+ // build an ordered `parts` list and broadcast each event as it happens.
87
+ for await (const part of result.fullStream) {
88
+ switch (part.type) {
89
+ case 'text-delta': {
90
+ appendText(part.text);
91
+ if (redis) {
92
+ await redis.append(streamKey, part.text).catch(() => { });
93
+ }
94
+ await broadcast('message-stream-chunk', {
95
+ messageId: aiMessageId,
96
+ chunk: part.text,
97
+ });
98
+ break;
99
+ }
100
+ case 'tool-call': {
101
+ const title = toolTitleFromInput(part.input);
102
+ toolPartIndexById.set(part.toolCallId, parts.length);
103
+ parts.push({
104
+ type: 'tool',
105
+ toolCallId: part.toolCallId,
106
+ toolName: part.toolName,
107
+ ...(title ? { title } : {}),
108
+ status: 'executing',
109
+ });
110
+ await broadcast('message-tool-call', {
111
+ messageId: aiMessageId,
112
+ toolCallId: part.toolCallId,
113
+ toolName: part.toolName,
114
+ ...(title ? { title } : {}),
115
+ status: 'executing',
116
+ });
117
+ break;
118
+ }
119
+ case 'tool-result':
120
+ case 'tool-error': {
121
+ const status = part.type === 'tool-error' ? 'failed' : 'completed';
122
+ const idx = toolPartIndexById.get(part.toolCallId);
123
+ if (idx !== undefined) {
124
+ const existing = parts[idx];
125
+ if (existing && existing.type === 'tool')
126
+ existing.status = status;
127
+ }
128
+ toolCalls.push({
129
+ toolName: part.toolName,
130
+ input: part.input,
131
+ output: part.type === 'tool-result' ? part.output : undefined,
132
+ status,
133
+ });
134
+ await broadcast('message-tool-call', {
135
+ messageId: aiMessageId,
136
+ toolCallId: part.toolCallId,
137
+ toolName: part.toolName,
138
+ ...(toolTitleFromInput(part.input) ? { title: toolTitleFromInput(part.input) } : {}),
139
+ status,
140
+ });
141
+ break;
142
+ }
143
+ default:
144
+ // ignore reasoning/source/step/finish/raw parts
145
+ break;
146
+ }
147
+ }
148
+ return { text: fullText, toolCalls, parts };
149
+ }
150
+ finally {
151
+ if (redis) {
152
+ await redis.del(streamKey).catch(() => { });
153
+ }
154
+ }
155
+ }
156
+ //# sourceMappingURL=aiChatStream.js.map
157
+ //# debugId=53e385f3-8180-59f0-9c1b-b89457a29317
@@ -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 status: 'executing' | 'completed' | 'failed';\n };\n\n/** Pull a human-meaningful label out of a tool's input args, if present. */\nfunction toolTitleFromInput(input: unknown): string | undefined {\n if (input && typeof input === 'object') {\n const obj = input as Record<string, unknown>;\n for (const key of ['title', 'name', 'query'] as const) {\n const val = obj[key];\n if (typeof val === 'string' && val.trim()) return val.trim();\n }\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 toolPartIndexById.set(part.toolCallId, parts.length);\n parts.push({\n type: 'tool',\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n ...(title ? { title } : {}),\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 status: 'executing',\n });\n break;\n }\n case 'tool-result':\n case 'tool-error': {\n const status: 'completed' | 'failed' =\n part.type === 'tool-error' ? 'failed' : 'completed';\n const idx = toolPartIndexById.get(part.toolCallId);\n if (idx !== undefined) {\n const existing = parts[idx];\n if (existing && existing.type === 'tool') existing.status = status;\n }\n toolCalls.push({\n toolName: part.toolName,\n input: part.input,\n output: part.type === 'tool-result' ? part.output : undefined,\n status,\n });\n await broadcast('message-tool-call', {\n messageId: aiMessageId,\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n ...(toolTitleFromInput(part.input) ? { title: toolTitleFromInput(part.input) } : {}),\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":"53e385f3-8180-59f0-9c1b-b89457a29317"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * AI modifier "field-update" tools.
3
+ *
4
+ * Unlike the Lab-chat proposal tools (which only *record* a payload for the
5
+ * teacher to accept later), these tools DIRECTLY edit a single key of an
6
+ * existing assignment or worksheet and persist it immediately via the normal
7
+ * service layer. The modifier route locks the resource (Redis flag + Pusher
8
+ * `modifier-started`) while the stream runs, and each successful field write
9
+ * fires a callback so the route can broadcast `modifier-field-updated` to every
10
+ * open editor — they apply the change live and stay read-only until the run
11
+ * completes or fails.
12
+ *
13
+ * NOTE: like `aiTools.ts` / `aiProposalTools.ts`, these intentionally use
14
+ * `jsonSchema()` rather than the zod `tool()` helper to avoid the TS2589
15
+ * "excessively deep" compiler blow-up on nested schemas.
16
+ */
17
+ import { type ToolSet } from 'ai';
18
+ /** A single field write that was applied to a resource. */
19
+ export interface ModifierFieldUpdate {
20
+ field: string;
21
+ value: unknown;
22
+ /** Human-readable label for the change, e.g. "title" or "due date". */
23
+ label: string;
24
+ }
25
+ /**
26
+ * Build the field-update tool for an assignment. The `onFieldUpdate` callback
27
+ * runs after a successful persist so the route can broadcast the change.
28
+ */
29
+ export declare function assignmentModifierTools(userId: string, assignmentId: string, classId: string, onFieldUpdate: (update: ModifierFieldUpdate) => Promise<void> | void): ToolSet;
30
+ /** Build the field-update tool for a worksheet. */
31
+ export declare function worksheetModifierTools(worksheetId: string, userId: string, onFieldUpdate: (update: ModifierFieldUpdate) => Promise<void> | void): ToolSet;
32
+ //# sourceMappingURL=aiModifierTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiModifierTools.d.ts","sourceRoot":"/","sources":["services/aiModifierTools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,IAAI,CAAC;AAI9C,2DAA2D;AAC3D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;CACf;AAkED;;;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,CAgCT"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * AI modifier "field-update" tools.
3
+ *
4
+ * Unlike the Lab-chat proposal tools (which only *record* a payload for the
5
+ * teacher to accept later), these tools DIRECTLY edit a single key of an
6
+ * existing assignment or worksheet and persist it immediately via the normal
7
+ * service layer. The modifier route locks the resource (Redis flag + Pusher
8
+ * `modifier-started`) while the stream runs, and each successful field write
9
+ * fires a callback so the route can broadcast `modifier-field-updated` to every
10
+ * open editor — they apply the change live and stay read-only until the run
11
+ * completes or fails.
12
+ *
13
+ * NOTE: like `aiTools.ts` / `aiProposalTools.ts`, these intentionally use
14
+ * `jsonSchema()` rather than the zod `tool()` helper to avoid the TS2589
15
+ * "excessively deep" compiler blow-up on nested schemas.
16
+ */
17
+
18
+ !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]="d1f30ff9-5af3-5e92-b758-96498a1a819d")}catch(e){}}();
19
+ import { jsonSchema } from 'ai';
20
+ import { updateAssignmentRecord } from './assignment.js';
21
+ import { updateWorksheetRecord } from './worksheet.js';
22
+ const ASSIGNMENT_TYPES = [
23
+ 'HOMEWORK', 'QUIZ', 'TEST', 'PROJECT', 'ESSAY', 'DISCUSSION', 'PRESENTATION', 'LAB', 'OTHER',
24
+ ];
25
+ /**
26
+ * Editable scalar keys of an assignment, with the coercion + human label used
27
+ * when the model passes a (string) value. Keys map 1:1 to
28
+ * `updateAssignmentRecord` input fields.
29
+ */
30
+ const ASSIGNMENT_FIELDS = {
31
+ title: { label: 'title', type: 'string' },
32
+ instructions: { label: 'instructions', type: 'string' },
33
+ dueDate: { label: 'due date', type: 'date' },
34
+ type: { label: 'type', type: 'enum', enumValues: ASSIGNMENT_TYPES },
35
+ maxGrade: { label: 'max grade', type: 'number' },
36
+ graded: { label: 'graded', type: 'boolean' },
37
+ weight: { label: 'weight', type: 'number' },
38
+ acceptFiles: { label: 'file submissions', type: 'boolean' },
39
+ acceptExtendedResponse: { label: 'extended response', type: 'boolean' },
40
+ acceptWorksheet: { label: 'worksheet submissions', type: 'boolean' },
41
+ aiPolicyLevel: { label: 'AI policy level', type: 'number' },
42
+ };
43
+ const WORKSHEET_FIELDS = {
44
+ name: { label: 'name', type: 'string' },
45
+ };
46
+ /** Coerce a model-supplied string value into the type a field expects. */
47
+ function coerceValue(raw, spec) {
48
+ switch (spec.type) {
49
+ case 'number': {
50
+ const n = Number(raw);
51
+ if (Number.isNaN(n))
52
+ throw new Error(`Expected a number but got "${raw}"`);
53
+ return n;
54
+ }
55
+ case 'boolean': {
56
+ const v = raw.trim().toLowerCase();
57
+ if (['true', 'yes', '1'].includes(v))
58
+ return true;
59
+ if (['false', 'no', '0'].includes(v))
60
+ return false;
61
+ throw new Error(`Expected a boolean but got "${raw}"`);
62
+ }
63
+ case 'date': {
64
+ const d = new Date(raw);
65
+ if (Number.isNaN(d.getTime()))
66
+ throw new Error(`Expected an ISO date but got "${raw}"`);
67
+ return d.toISOString();
68
+ }
69
+ case 'enum': {
70
+ const v = raw.trim().toUpperCase();
71
+ if (spec.enumValues && !spec.enumValues.includes(v)) {
72
+ throw new Error(`Expected one of ${spec.enumValues.join(', ')} but got "${raw}"`);
73
+ }
74
+ return v;
75
+ }
76
+ default:
77
+ return raw;
78
+ }
79
+ }
80
+ /**
81
+ * Build the field-update tool for an assignment. The `onFieldUpdate` callback
82
+ * runs after a successful persist so the route can broadcast the change.
83
+ */
84
+ export function assignmentModifierTools(userId, assignmentId, classId, onFieldUpdate) {
85
+ const fieldNames = Object.keys(ASSIGNMENT_FIELDS);
86
+ return {
87
+ update_assignment_field: {
88
+ description: 'Directly update a single field of the assignment and save it immediately. ' +
89
+ 'Call this once per field the teacher wants changed. The assignment is locked ' +
90
+ 'for editing until you finish.',
91
+ inputSchema: jsonSchema({
92
+ type: 'object',
93
+ properties: {
94
+ field: {
95
+ type: 'string',
96
+ enum: fieldNames,
97
+ description: 'The assignment field to update.',
98
+ },
99
+ value: {
100
+ type: 'string',
101
+ description: 'The new value as a string. Booleans use "true"/"false", numbers as digits, ' +
102
+ 'dates as ISO 8601, type as one of the allowed enum values.',
103
+ },
104
+ },
105
+ required: ['field', 'value'],
106
+ }),
107
+ execute: async ({ field, value }) => {
108
+ const spec = ASSIGNMENT_FIELDS[field];
109
+ if (!spec) {
110
+ return `Unknown field "${field}". Allowed: ${fieldNames.join(', ')}.`;
111
+ }
112
+ let coerced;
113
+ try {
114
+ coerced = coerceValue(value, spec);
115
+ }
116
+ catch (err) {
117
+ return `Could not update ${spec.label}: ${err.message}`;
118
+ }
119
+ const updateInput = {
120
+ id: assignmentId,
121
+ classId,
122
+ };
123
+ updateInput[field] = coerced;
124
+ await updateAssignmentRecord(userId, updateInput);
125
+ await onFieldUpdate({ field, value: coerced, label: spec.label });
126
+ return `Updated ${spec.label}.`;
127
+ },
128
+ },
129
+ };
130
+ }
131
+ /** Build the field-update tool for a worksheet. */
132
+ export function worksheetModifierTools(worksheetId, userId, onFieldUpdate) {
133
+ const fieldNames = Object.keys(WORKSHEET_FIELDS);
134
+ return {
135
+ update_worksheet_field: {
136
+ description: 'Directly update a single field of the worksheet and save it immediately. ' +
137
+ 'The worksheet is locked for editing until you finish.',
138
+ inputSchema: jsonSchema({
139
+ type: 'object',
140
+ properties: {
141
+ field: {
142
+ type: 'string',
143
+ enum: fieldNames,
144
+ description: 'The worksheet field to update.',
145
+ },
146
+ value: { type: 'string', description: 'The new value as a string.' },
147
+ },
148
+ required: ['field', 'value'],
149
+ }),
150
+ execute: async ({ field, value }) => {
151
+ const spec = WORKSHEET_FIELDS[field];
152
+ if (!spec) {
153
+ return `Unknown field "${field}". Allowed: ${fieldNames.join(', ')}.`;
154
+ }
155
+ const updateInput = {};
156
+ updateInput[field] = value;
157
+ await updateWorksheetRecord(worksheetId, userId, updateInput);
158
+ await onFieldUpdate({ field, value, label: spec.label });
159
+ return `Updated ${spec.label}.`;
160
+ },
161
+ },
162
+ };
163
+ }
164
+ //# sourceMappingURL=aiModifierTools.js.map
165
+ //# debugId=d1f30ff9-5af3-5e92-b758-96498a1a819d