@studious-lms/server 1.14.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 (196) hide show
  1. package/.github/workflows/deploy.yml +1 -1
  2. package/dist/instrument.js +3 -3
  3. package/dist/instrument.js.map +1 -1
  4. package/dist/lib/inferDocumentFormat.d.ts +10 -0
  5. package/dist/lib/inferDocumentFormat.d.ts.map +1 -0
  6. package/dist/lib/inferDocumentFormat.js +48 -0
  7. package/dist/lib/inferDocumentFormat.js.map +1 -0
  8. package/dist/lib/legal.d.ts +3 -0
  9. package/dist/lib/legal.d.ts.map +1 -0
  10. package/dist/lib/legal.js +6 -0
  11. package/dist/lib/legal.js.map +1 -0
  12. package/dist/models/announcement.d.ts +47 -45
  13. package/dist/models/announcement.d.ts.map +1 -1
  14. package/dist/models/assignment.d.ts +75 -75
  15. package/dist/models/auth.d.ts +8 -0
  16. package/dist/models/auth.d.ts.map +1 -1
  17. package/dist/models/auth.js +4 -2
  18. package/dist/models/auth.js.map +1 -1
  19. package/dist/models/class.d.ts +13 -13
  20. package/dist/models/comment.d.ts +6 -6
  21. package/dist/models/conversation.d.ts +23 -23
  22. package/dist/models/event.d.ts +20 -18
  23. package/dist/models/event.d.ts.map +1 -1
  24. package/dist/models/file.d.ts +33 -9
  25. package/dist/models/file.d.ts.map +1 -1
  26. package/dist/models/labChat.d.ts +4 -4
  27. package/dist/models/marketing.d.ts +2 -15
  28. package/dist/models/marketing.d.ts.map +1 -1
  29. package/dist/models/marketing.js +12 -4
  30. package/dist/models/marketing.js.map +1 -1
  31. package/dist/models/message.d.ts +13 -13
  32. package/dist/models/newtonChat.d.ts +3 -3
  33. package/dist/models/notification.d.ts +4 -4
  34. package/dist/models/worksheet.d.ts +121 -121
  35. package/dist/pipelines/aiLabChat.d.ts +7 -1
  36. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  37. package/dist/pipelines/aiLabChat.js +153 -67
  38. package/dist/pipelines/aiLabChat.js.map +1 -1
  39. package/dist/pipelines/aiLabChatContract.d.ts +34 -195
  40. package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
  41. package/dist/pipelines/aiLabChatContract.js +9 -19
  42. package/dist/pipelines/aiLabChatContract.js.map +1 -1
  43. package/dist/pipelines/gradeWorksheet.d.ts +6 -6
  44. package/dist/pipelines/labChatPrompt.d.ts +8 -3
  45. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  46. package/dist/pipelines/labChatPrompt.js +52 -14
  47. package/dist/pipelines/labChatPrompt.js.map +1 -1
  48. package/dist/pipelines/pdfToWorksheet.d.ts +1 -1
  49. package/dist/routers/_app.d.ts +750 -546
  50. package/dist/routers/_app.d.ts.map +1 -1
  51. package/dist/routers/announcement.d.ts +40 -40
  52. package/dist/routers/assignment.d.ts +131 -121
  53. package/dist/routers/assignment.d.ts.map +1 -1
  54. package/dist/routers/auth.d.ts +2 -0
  55. package/dist/routers/auth.d.ts.map +1 -1
  56. package/dist/routers/auth.js +15 -2
  57. package/dist/routers/auth.js.map +1 -1
  58. package/dist/routers/class.d.ts +12 -8
  59. package/dist/routers/class.d.ts.map +1 -1
  60. package/dist/routers/comment.d.ts +5 -5
  61. package/dist/routers/conversation.d.ts +6 -6
  62. package/dist/routers/discussion.d.ts +3 -3
  63. package/dist/routers/embedding.d.ts +6 -2
  64. package/dist/routers/embedding.d.ts.map +1 -1
  65. package/dist/routers/embedding.js +44 -36
  66. package/dist/routers/embedding.js.map +1 -1
  67. package/dist/routers/event.d.ts +20 -18
  68. package/dist/routers/event.d.ts.map +1 -1
  69. package/dist/routers/file.d.ts +2 -2
  70. package/dist/routers/labChat.d.ts +3 -3
  71. package/dist/routers/marketing.d.ts +0 -13
  72. package/dist/routers/marketing.d.ts.map +1 -1
  73. package/dist/routers/notifications.d.ts +4 -4
  74. package/dist/routers/school.d.ts +4 -0
  75. package/dist/routers/school.d.ts.map +1 -1
  76. package/dist/routers/search.d.ts +2 -2
  77. package/dist/routers/user.d.ts +89 -0
  78. package/dist/routers/user.d.ts.map +1 -1
  79. package/dist/routers/user.js +19 -2
  80. package/dist/routers/user.js.map +1 -1
  81. package/dist/routers/worksheet.d.ts +44 -44
  82. package/dist/routes/aiStream.d.ts.map +1 -1
  83. package/dist/routes/aiStream.js +41 -11
  84. package/dist/routes/aiStream.js.map +1 -1
  85. package/dist/seedDatabase.d.ts +3 -1
  86. package/dist/seedDatabase.d.ts.map +1 -1
  87. package/dist/services/aiChatStream.d.ts +20 -0
  88. package/dist/services/aiChatStream.d.ts.map +1 -1
  89. package/dist/services/aiChatStream.js +110 -13
  90. package/dist/services/aiChatStream.js.map +1 -1
  91. package/dist/services/aiModifierTools.d.ts +0 -16
  92. package/dist/services/aiModifierTools.d.ts.map +1 -1
  93. package/dist/services/aiModifierTools.js +112 -3
  94. package/dist/services/aiModifierTools.js.map +1 -1
  95. package/dist/services/aiProposalTools.d.ts.map +1 -1
  96. package/dist/services/aiProposalTools.js +25 -25
  97. package/dist/services/aiProposalTools.js.map +1 -1
  98. package/dist/services/aiTools.js +3 -3
  99. package/dist/services/aiTools.js.map +1 -1
  100. package/dist/services/announcement.d.ts +34 -34
  101. package/dist/services/assignment.d.ts +121 -111
  102. package/dist/services/assignment.d.ts.map +1 -1
  103. package/dist/services/assignment.js +4 -2
  104. package/dist/services/assignment.js.map +1 -1
  105. package/dist/services/auth.d.ts +2 -0
  106. package/dist/services/auth.d.ts.map +1 -1
  107. package/dist/services/auth.js +13 -4
  108. package/dist/services/auth.js.map +1 -1
  109. package/dist/services/class.d.ts +10 -6
  110. package/dist/services/class.d.ts.map +1 -1
  111. package/dist/services/class.js +7 -3
  112. package/dist/services/class.js.map +1 -1
  113. package/dist/services/comment.d.ts +5 -5
  114. package/dist/services/conversation.d.ts +6 -6
  115. package/dist/services/discussion.d.ts.map +1 -1
  116. package/dist/services/discussion.js +9 -2
  117. package/dist/services/discussion.js.map +1 -1
  118. package/dist/services/embedding.d.ts +19 -1
  119. package/dist/services/embedding.d.ts.map +1 -1
  120. package/dist/services/embedding.js +154 -8
  121. package/dist/services/embedding.js.map +1 -1
  122. package/dist/services/event.d.ts +20 -18
  123. package/dist/services/event.d.ts.map +1 -1
  124. package/dist/services/file.d.ts +4 -4
  125. package/dist/services/folder.d.ts.map +1 -1
  126. package/dist/services/folder.js +32 -2
  127. package/dist/services/folder.js.map +1 -1
  128. package/dist/services/labChat.d.ts +3 -3
  129. package/dist/services/marketing.d.ts +1 -14
  130. package/dist/services/marketing.d.ts.map +1 -1
  131. package/dist/services/marketing.js +5 -5
  132. package/dist/services/marketing.js.map +1 -1
  133. package/dist/services/notification.d.ts +4 -4
  134. package/dist/services/openaiDocumentGen.d.ts +37 -0
  135. package/dist/services/openaiDocumentGen.d.ts.map +1 -0
  136. package/dist/services/openaiDocumentGen.js +262 -0
  137. package/dist/services/openaiDocumentGen.js.map +1 -0
  138. package/dist/services/school.d.ts +4 -0
  139. package/dist/services/school.d.ts.map +1 -1
  140. package/dist/services/search.d.ts +1 -1
  141. package/dist/services/search.d.ts.map +1 -1
  142. package/dist/services/search.js +27 -2
  143. package/dist/services/search.js.map +1 -1
  144. package/dist/services/userPrivacy.d.ts +86 -0
  145. package/dist/services/userPrivacy.d.ts.map +1 -0
  146. package/dist/services/userPrivacy.js +184 -0
  147. package/dist/services/userPrivacy.js.map +1 -0
  148. package/dist/services/worksheet.d.ts +66 -49
  149. package/dist/services/worksheet.d.ts.map +1 -1
  150. package/dist/services/worksheet.js +48 -3
  151. package/dist/services/worksheet.js.map +1 -1
  152. package/dist/utils/email.d.ts.map +1 -1
  153. package/dist/utils/email.js +7 -3
  154. package/dist/utils/email.js.map +1 -1
  155. package/dist/workers/processors/embedContent.d.ts.map +1 -1
  156. package/dist/workers/processors/embedContent.js +16 -25
  157. package/dist/workers/processors/embedContent.js.map +1 -1
  158. package/package.json +1 -1
  159. package/prisma/migrations/20260610140000_expand_embedding_source_types/migration.sql +4 -0
  160. package/prisma/migrations/20260611120000_add_user_terms_acceptance/migration.sql +3 -0
  161. package/prisma/schema.prisma +6 -0
  162. package/src/instrument.ts +1 -1
  163. package/src/lib/inferDocumentFormat.ts +58 -0
  164. package/src/lib/legal.ts +2 -0
  165. package/src/models/auth.ts +4 -0
  166. package/src/models/marketing.ts +10 -2
  167. package/src/pipelines/aiLabChat.ts +198 -74
  168. package/src/pipelines/aiLabChatContract.ts +7 -17
  169. package/src/pipelines/labChatPrompt.ts +64 -14
  170. package/src/routers/auth.ts +13 -0
  171. package/src/routers/embedding.ts +44 -39
  172. package/src/routers/user.ts +26 -0
  173. package/src/routes/aiStream.ts +47 -9
  174. package/src/services/aiChatStream.ts +133 -11
  175. package/src/services/aiModifierTools.ts +154 -1
  176. package/src/services/aiProposalTools.ts +27 -25
  177. package/src/services/aiTools.ts +1 -1
  178. package/src/services/assignment.ts +3 -0
  179. package/src/services/auth.ts +14 -2
  180. package/src/services/class.ts +7 -1
  181. package/src/services/discussion.ts +13 -0
  182. package/src/services/embedding.ts +167 -6
  183. package/src/services/folder.ts +31 -0
  184. package/src/services/marketing.ts +3 -3
  185. package/src/services/openaiDocumentGen.ts +319 -0
  186. package/src/services/search.ts +25 -1
  187. package/src/services/userPrivacy.ts +196 -0
  188. package/src/services/worksheet.ts +68 -1
  189. package/src/utils/email.ts +5 -1
  190. package/src/workers/processors/embedContent.ts +18 -22
  191. package/tests/helpers/registerInput.ts +20 -0
  192. package/tests/integration/globalSetup.ts +10 -18
  193. package/tests/integration/routers/auth.test.ts +13 -18
  194. package/tests/unit/lib/inferDocumentFormat.test.ts +33 -0
  195. package/tests/unit/services/aiChatStream.test.ts +92 -0
  196. package/tests/unit/services/openaiDocumentGen.test.ts +65 -0
@@ -1 +1 @@
1
- {"version":3,"file":"aiStream.js","sources":["routes/aiStream.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Express routes for AI streaming endpoints.\n * These routes use the Vercel AI SDK's data stream protocol for streaming AI responses.\n */\nimport { Router } from 'express';\nimport type { Request, Response } from 'express';\nimport { streamText, stepCountIs, type ModelMessage } from 'ai';\nimport { openai } from '@ai-sdk/openai';\nimport { findUserBySessionToken } from '../models/auth.js';\nimport { prisma } from '../lib/prisma.js';\nimport { logger } from '../utils/logger.js';\nimport { aiTools } from '../services/aiTools.js';\nimport {\n assignmentModifierTools,\n worksheetModifierTools,\n type ModifierFieldUpdate,\n} from '../services/aiModifierTools.js';\nimport { getRedis } from '../lib/redis.js';\nimport { pusher, chatChannel, teacherChannel } from '../lib/pusher.js';\nimport { getAIUserId, ensureAIUserExists, isAIUser } from '../utils/aiUser.js';\nimport { JobStatus } from '@prisma/client';\nimport { updateAssignmentRecord } from '../services/assignment.js';\nimport { updateWorksheetRecord } from '../services/worksheet.js';\n\nexport const aiStreamRouter = Router();\n\n/**\n * Authenticate request from session token (header or cookie).\n */\nasync function authenticateRequest(req: Request) {\n const userHeader = req.headers['x-user'];\n const tokenFromUserHeader = typeof userHeader === 'string' ? userHeader : Array.isArray(userHeader) ? userHeader[0] : undefined;\n const authorizationHeader = req.headers.authorization;\n const tokenFromAuthorization =\n typeof authorizationHeader === 'string' && authorizationHeader.startsWith('Bearer ')\n ? authorizationHeader.slice(7)\n : null;\n const token = tokenFromUserHeader ?? tokenFromAuthorization ?? undefined;\n if (!token) return null;\n return findUserBySessionToken(token);\n}\n\n/**\n * POST /api/ai/chat/stream\n *\n * Streams an AI response for a conversation (Lab Chat or Newton Tutor).\n * Uses the Vercel AI SDK data stream protocol.\n */\naiStreamRouter.post('/chat/stream', async (req: Request, res: Response) => {\n const user = await authenticateRequest(req);\n if (!user) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const { conversationId } = req.body as { conversationId?: string };\n if (!conversationId) {\n return res.status(400).json({ error: 'conversationId is required' });\n }\n\n // Verify user has access to this conversation\n const membership = await prisma.conversationMember.findFirst({\n where: { conversationId, userId: user.id },\n });\n if (!membership) {\n return res.status(403).json({ error: 'Access denied' });\n }\n\n // Determine context: Lab Chat or Newton Chat\n const labChat = await prisma.labChat.findFirst({\n where: { conversationId },\n select: {\n id: true,\n classId: true,\n context: true,\n class: { select: { subject: true, name: true } },\n },\n });\n\n const newtonChat = !labChat ? await prisma.newtonChat.findFirst({\n where: { conversationId },\n select: {\n id: true,\n submission: {\n select: {\n id: true,\n assignment: {\n select: {\n title: true,\n instructions: true,\n aiPolicyLevel: true,\n class: { select: { id: true, subject: true, name: true } },\n },\n },\n },\n },\n },\n }) : null;\n\n if (!labChat && !newtonChat) {\n return res.status(404).json({ error: 'No AI chat context found for this conversation' });\n }\n\n const classId = labChat?.classId || newtonChat?.submission.assignment.class.id;\n if (!classId) {\n return res.status(400).json({ error: 'Unable to determine class context' });\n }\n\n // Build message history from DB\n const recentMessages = await prisma.message.findMany({\n where: { conversationId, status: { not: JobStatus.PENDING } },\n orderBy: { createdAt: 'asc' },\n take: 20,\n select: {\n content: true,\n senderId: true,\n status: true,\n },\n });\n\n const messages: ModelMessage[] = recentMessages\n .filter((m) => m.content && m.content.length > 0)\n .map((m) => ({\n role: isAIUser(m.senderId) ? 'assistant' as const : 'user' as const,\n content: m.content,\n }));\n\n // Build system prompt based on context type\n let systemPrompt: string;\n if (labChat) {\n systemPrompt = buildLabChatSystemPrompt(labChat);\n } else {\n systemPrompt = buildNewtonSystemPrompt(newtonChat!);\n }\n\n // Create pending AI message\n await ensureAIUserExists();\n const aiMessage = await prisma.message.create({\n data: {\n content: '',\n senderId: getAIUserId(),\n conversationId,\n status: JobStatus.PENDING,\n },\n });\n\n // Broadcast pending state\n await pusher.trigger(chatChannel(conversationId), 'new-message', {\n id: aiMessage.id,\n content: '',\n senderId: getAIUserId(),\n conversationId,\n status: JobStatus.PENDING,\n createdAt: aiMessage.createdAt,\n sender: {\n id: getAIUserId(),\n username: 'Newton_AI',\n profile: { displayName: 'Newton AI', profilePicture: null },\n },\n mentionedUserIds: [],\n meta: {},\n attachments: [],\n });\n\n // Cache stream content in Redis\n const redis = getRedis();\n const streamKey = `stream:${aiMessage.id}`;\n if (redis) {\n await redis.set(streamKey, '', 'EX', 600);\n }\n\n try {\n const tools = aiTools(classId);\n\n const result = streamText({\n model: openai('gpt-4o-mini'),\n system: systemPrompt,\n messages,\n tools,\n temperature: 0.7,\n maxOutputTokens: 4096,\n stopWhen: stepCountIs(5),\n onStepFinish: async ({ toolCalls }) => {\n // Broadcast tool call events so all clients can show inline indicators\n if (toolCalls && toolCalls.length > 0) {\n for (const tc of toolCalls) {\n await pusher.trigger(chatChannel(conversationId), 'message-tool-call', {\n messageId: aiMessage.id,\n toolName: tc.toolName,\n status: 'executing',\n }).catch((err: unknown) => {\n logger.error('Failed to broadcast tool call event', { err, messageId: aiMessage.id });\n });\n }\n }\n },\n onFinish: async ({ text }) => {\n // Persist completed message\n await prisma.message.update({\n where: { id: aiMessage.id },\n data: { content: text, status: JobStatus.COMPLETED },\n });\n\n // Broadcast final state\n await pusher.trigger(chatChannel(conversationId), 'message-updated', {\n id: aiMessage.id,\n content: text,\n senderId: getAIUserId(),\n conversationId,\n status: JobStatus.COMPLETED,\n sender: {\n id: getAIUserId(),\n username: 'Newton_AI',\n profile: { displayName: 'Newton AI', profilePicture: null },\n },\n mentionedUserIds: [],\n meta: {},\n attachments: [],\n });\n\n // Clean up Redis\n if (redis) {\n await redis.del(streamKey);\n }\n\n logger.info('AI stream completed', {\n messageId: aiMessage.id,\n conversationId,\n contentLength: text.length,\n });\n },\n });\n\n // Stream to the requesting client via SSE and broadcast chunks via Pusher\n // so other participants also see progressive updates.\n res.writeHead(200, {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Transfer-Encoding': 'chunked',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n\n let chunkBuffer = '';\n for await (const chunk of result.textStream) {\n // Write to HTTP response for the requesting client\n res.write(chunk);\n\n // Buffer chunks to avoid excessive Pusher calls (batch per ~100 chars)\n chunkBuffer += chunk;\n if (chunkBuffer.length >= 100) {\n await pusher.trigger(chatChannel(conversationId), 'message-stream-chunk', {\n messageId: aiMessage.id,\n chunk: chunkBuffer,\n }).catch(() => {});\n // Update Redis cache\n if (redis) {\n await redis.append(streamKey, chunkBuffer);\n }\n chunkBuffer = '';\n }\n }\n\n // Flush remaining buffer\n if (chunkBuffer.length > 0) {\n await pusher.trigger(chatChannel(conversationId), 'message-stream-chunk', {\n messageId: aiMessage.id,\n chunk: chunkBuffer,\n }).catch(() => {});\n if (redis) {\n await redis.append(streamKey, chunkBuffer);\n }\n }\n\n res.end();\n } catch (error) {\n logger.error('AI stream error', { error, conversationId, messageId: aiMessage.id });\n\n await prisma.message.update({\n where: { id: aiMessage.id },\n data: { status: JobStatus.FAILED },\n });\n\n if (redis) {\n await redis.del(streamKey);\n }\n\n if (!res.headersSent) {\n return res.status(500).json({ error: 'Stream failed' });\n }\n }\n});\n\n/**\n * POST /api/ai/modify/stream\n *\n * Streams AI modifications to an assignment or worksheet.\n * Uses ordered-key output format for progressive rendering.\n */\naiStreamRouter.post('/modify/stream', async (req: Request, res: Response) => {\n const user = await authenticateRequest(req);\n if (!user) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const { assignmentId, worksheetId, prompt, classId } = req.body as {\n assignmentId?: string;\n worksheetId?: string;\n prompt?: string;\n classId?: string;\n };\n\n if (!prompt || !classId) {\n return res.status(400).json({ error: 'prompt and classId are required' });\n }\n\n if (!assignmentId && !worksheetId) {\n return res.status(400).json({ error: 'Either assignmentId or worksheetId is required' });\n }\n\n // Verify teacher access to class\n const teacherInClass = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: { some: { id: user.id } },\n },\n });\n if (!teacherInClass) {\n return res.status(403).json({ error: 'Teacher access required' });\n }\n\n // Build context from existing resource and snapshot pre-edit state for undo\n let resourceContext: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let preEditSnapshot: Record<string, any> | null = null;\n if (assignmentId) {\n const assignment = await prisma.assignment.findUnique({\n where: { id: assignmentId },\n include: {\n markScheme: { select: { structured: true } },\n gradingBoundary: { select: { structured: true } },\n section: { select: { name: true } },\n worksheets: { include: { questions: { orderBy: { order: 'asc' } } } },\n },\n });\n if (!assignment) {\n return res.status(404).json({ error: 'Assignment not found' });\n }\n resourceContext = buildAssignmentContext(assignment);\n preEditSnapshot = {\n title: assignment.title,\n instructions: assignment.instructions,\n dueDate: assignment.dueDate?.toISOString() ?? null,\n type: assignment.type,\n maxGrade: assignment.maxGrade,\n graded: assignment.graded,\n weight: assignment.weight,\n acceptFiles: assignment.acceptFiles,\n acceptExtendedResponse: assignment.acceptExtendedResponse,\n acceptWorksheet: assignment.acceptWorksheet,\n aiPolicyLevel: assignment.aiPolicyLevel,\n modifiedAt: assignment.modifiedAt?.toISOString() ?? null,\n };\n } else {\n const worksheet = await prisma.worksheet.findUnique({\n where: { id: worksheetId! },\n include: { questions: { orderBy: { order: 'asc' } } },\n });\n if (!worksheet) {\n return res.status(404).json({ error: 'Worksheet not found' });\n }\n resourceContext = buildWorksheetContext(worksheet);\n preEditSnapshot = {\n name: worksheet.name,\n };\n }\n\n const systemPrompt = buildModifierSystemPrompt(assignmentId ? 'assignment' : 'worksheet');\n\n const messages: ModelMessage[] = [\n {\n role: 'user',\n content: `Here is the current ${assignmentId ? 'assignment' : 'worksheet'}:\\n\\n${resourceContext}\\n\\nTeacher's request: ${prompt}`,\n },\n ];\n\n const resourceId = assignmentId || worksheetId!;\n const resourceType = assignmentId ? 'assignment' : 'worksheet';\n const redis = getRedis();\n const modifyingKey = `aiModifying:${resourceId}`;\n const channel = teacherChannel(classId);\n\n // Set Redis flag so other users know a modification is in progress\n if (redis) {\n await redis.set(modifyingKey, JSON.stringify({\n userId: user.id,\n username: user.username,\n resourceType,\n resourceId,\n startedAt: Date.now(),\n }), 'EX', 300); // 5 min TTL safety net\n\n // Store pre-edit snapshot for undo (30 min TTL)\n if (preEditSnapshot) {\n const snapshotKey = `aiModifierSnapshot:${resourceId}`;\n await redis.set(snapshotKey, JSON.stringify({\n resourceType,\n resourceId,\n classId,\n userId: user.id,\n snapshot: preEditSnapshot,\n createdAt: Date.now(),\n }), 'EX', 1800);\n }\n }\n\n // Broadcast modifier-started to all teachers in the class\n await pusher.trigger(channel, 'modifier-started', {\n resourceId,\n resourceType,\n userId: user.id,\n username: user.username,\n }).catch((err: unknown) => {\n logger.error('Failed to broadcast modifier-started', { err, resourceId });\n });\n\n // Stream to requesting client via SSE; field changes are persisted by the\n // tools and broadcast to all open editors so they update live + stay locked.\n res.writeHead(200, {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Transfer-Encoding': 'chunked',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n\n // Called after a tool persists a single field — apply it live everywhere.\n const onFieldUpdate = async (update: ModifierFieldUpdate) => {\n await pusher.trigger(channel, 'modifier-field-updated', {\n resourceId,\n resourceType,\n field: update.field,\n value: update.value,\n label: update.label,\n }).catch((err: unknown) => {\n logger.warn('Failed to broadcast modifier-field-updated', { err, resourceId });\n });\n // Human-readable progress for the requesting client's bar.\n res.write(`Updated ${update.label}.\\n`);\n };\n\n try {\n const tools = {\n ...aiTools(classId),\n ...(assignmentId\n ? assignmentModifierTools(user.id, assignmentId, classId, onFieldUpdate)\n : worksheetModifierTools(worksheetId!, user.id, onFieldUpdate)),\n };\n\n const result = streamText({\n model: openai('gpt-4o-mini'),\n system: systemPrompt,\n messages,\n tools,\n temperature: 0.3,\n maxOutputTokens: 8192,\n stopWhen: stepCountIs(8),\n });\n\n // Drain the stream. Field writes happen inside the tools' execute (which\n // call onFieldUpdate); any narration text is forwarded to the SSE client.\n for await (const chunk of result.textStream) {\n res.write(chunk);\n }\n\n // Clean up Redis flag and broadcast completion\n if (redis) {\n await redis.del(modifyingKey);\n }\n await pusher.trigger(channel, 'modifier-complete', {\n resourceId,\n resourceType,\n canUndo: Boolean(redis && preEditSnapshot),\n }).catch(() => {});\n\n res.end();\n } catch (error) {\n logger.error('AI modify stream error', { error, assignmentId, worksheetId });\n\n // Clean up Redis flag and broadcast failure\n if (redis) {\n await redis.del(modifyingKey);\n }\n await pusher.trigger(channel, 'modifier-failed', {\n resourceId,\n resourceType,\n }).catch(() => {});\n\n if (!res.headersSent) {\n return res.status(500).json({ error: 'Stream failed' });\n }\n }\n});\n\n/**\n * GET /api/ai/modify/status?resourceId=xxx\n *\n * Returns whether an AI modification is currently in progress for a resource.\n */\naiStreamRouter.get('/modify/status', async (req: Request, res: Response) => {\n const user = await authenticateRequest(req);\n if (!user) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const { resourceId } = req.query as { resourceId?: string };\n if (!resourceId) {\n return res.status(400).json({ error: 'resourceId is required' });\n }\n\n const redis = getRedis();\n if (!redis) {\n return res.json({ isModifying: false });\n }\n\n const data = await redis.get(`aiModifying:${resourceId}`);\n if (!data) {\n return res.json({ isModifying: false });\n }\n\n try {\n const parsed = JSON.parse(data);\n return res.json({\n isModifying: true,\n userId: parsed.userId,\n username: parsed.username,\n resourceType: parsed.resourceType,\n startedAt: parsed.startedAt,\n });\n } catch {\n return res.json({ isModifying: false });\n }\n});\n\n/**\n * POST /api/ai/modify/undo\n *\n * Reverts the most recent AI modifier run for a resource by restoring the\n * pre-edit snapshot stored in Redis.\n */\naiStreamRouter.post('/modify/undo', async (req: Request, res: Response) => {\n const user = await authenticateRequest(req);\n if (!user) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const { resourceId, classId } = req.body as { resourceId?: string; classId?: string };\n if (!resourceId || !classId) {\n return res.status(400).json({ error: 'resourceId and classId are required' });\n }\n\n // Verify teacher access to class\n const teacherInClass = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: { some: { id: user.id } },\n },\n });\n if (!teacherInClass) {\n return res.status(403).json({ error: 'Teacher access required' });\n }\n\n const redis = getRedis();\n if (!redis) {\n return res.status(400).json({ error: 'Undo not available' });\n }\n\n const snapshotKey = `aiModifierSnapshot:${resourceId}`;\n const raw = await redis.get(snapshotKey);\n if (!raw) {\n return res.status(404).json({ error: 'No undo snapshot available (may have expired)' });\n }\n\n let parsed: {\n resourceType: 'assignment' | 'worksheet';\n resourceId: string;\n classId: string;\n userId: string;\n snapshot: Record<string, unknown>;\n createdAt: number;\n };\n try {\n parsed = JSON.parse(raw);\n } catch {\n return res.status(500).json({ error: 'Corrupt snapshot data' });\n }\n\n try {\n if (parsed.resourceType === 'assignment') {\n const { modifiedAt: _modifiedAt, ...fields } = parsed.snapshot;\n await updateAssignmentRecord(user.id, {\n id: resourceId,\n classId,\n ...fields,\n });\n } else {\n await updateWorksheetRecord(resourceId, user.id, parsed.snapshot);\n }\n\n // Remove the snapshot so undo can only be used once\n await redis.del(snapshotKey);\n\n // Broadcast undo to all teachers so they refresh\n const channel = teacherChannel(classId);\n await pusher.trigger(channel, 'modifier-undone', {\n resourceId,\n resourceType: parsed.resourceType,\n }).catch((err: unknown) => {\n logger.warn('Failed to broadcast modifier-undone', { err, resourceId });\n });\n\n return res.json({ success: true });\n } catch (error) {\n logger.error('AI modifier undo failed', { error, resourceId });\n return res.status(500).json({ error: 'Failed to undo changes' });\n }\n});\n\n// --- Helper functions ---\n\nfunction buildLabChatSystemPrompt(labChat: {\n context: string | null;\n class: { subject: string; name: string } | null;\n}): string {\n return `You are Newton AI, an intelligent teaching assistant for the class \"${labChat.class?.name}\" (subject: ${labChat.class?.subject}).\n\nYou help teachers create course materials, plan lessons, and manage their class. You have access to tools that let you search through class materials, assignments, and student data.\n\n${labChat.context ? `Context: ${labChat.context}` : ''}\n\nIMPORTANT RULES:\n- Use plain text responses (no markdown formatting)\n- Be conversational and helpful\n- When you need information about class materials or assignments, use the search_materials or search_assignments tools\n- Only ask clarifying questions about content/pedagogy, never about technical details\n- Make reasonable defaults for colors, formats, and technical choices\n- Keep responses concise (2-4 sentences for chat, longer for content creation)`;\n}\n\nfunction buildNewtonSystemPrompt(newtonChat: {\n submission: {\n assignment: {\n title: string;\n instructions: string | null;\n aiPolicyLevel: number | null;\n class: { subject: string | null; name: string };\n };\n };\n}): string {\n const { assignment } = newtonChat.submission;\n const policyLevel = assignment.aiPolicyLevel || 2;\n\n return `You are Newton, an AI tutor helping a student with their assignment.\n\nAssignment: ${assignment.title}\nSubject: ${assignment.class.subject}\nClass: ${assignment.class.name}\nInstructions: ${assignment.instructions || 'No specific instructions provided'}\nAI Policy Level: ${policyLevel}/5\n\nYour role:\n- Help the student understand concepts related to their assignment\n- Guide them through problems without giving direct answers\n- Use the search_materials tool to find relevant class materials when helpful\n- Encourage critical thinking and self-discovery\n- Be supportive, clear, and educational\n- Use plain text only (no markdown)\n\nAI Policy Level ${policyLevel} means:\n${policyLevel <= 2 ? '- Very limited AI assistance. Do NOT provide direct answers or solutions. Only clarify concepts and ask guiding questions.' : ''}\n${policyLevel === 3 ? '- Moderate AI assistance. You can explain concepts and give examples, but avoid solving the exact assignment problems.' : ''}\n${policyLevel >= 4 ? '- Extended AI assistance allowed. You can provide detailed explanations, examples, and guided solutions.' : ''}\n\nNEVER provide complete answers regardless of policy level. Always guide learning.`;\n}\n\nfunction buildModifierSystemPrompt(resourceType: 'assignment' | 'worksheet'): string {\n if (resourceType === 'assignment') {\n return `You are an AI assistant that edits an existing assignment based on teacher instructions.\n\nHOW TO EDIT: You change the assignment by CALLING TOOLS — never by printing the new value as text.\n- Call \\`update_assignment_field\\` once for every individual field the teacher wants changed.\n Editable fields: title, instructions, dueDate, type, maxGrade, graded, weight,\n acceptFiles, acceptExtendedResponse, acceptWorksheet, aiPolicyLevel.\n- Pass the value as a string: booleans as \"true\"/\"false\", numbers as digits,\n dueDate as an ISO 8601 datetime, and type as one of\n HOMEWORK|QUIZ|TEST|PROJECT|ESSAY|DISCUSSION|PRESENTATION|LAB|OTHER.\n- Use \\`search_materials\\` first only if you need class context.\n\nRULES:\n- Only change the fields the teacher actually asked about. Do not touch anything else.\n- Each tool call saves immediately and is shown to the teacher live.\n- After making all the changes, write a brief conversational summary of what you did.\n Be human and specific — mention the actual values. For example:\n \"Alright, I've changed the title to 'Chapter 5 Review' and pushed the due date to next Friday.\"\n Do NOT dump the full assignment contents — just summarize the changes naturally.`;\n }\n\n return `You are an AI assistant that edits an existing worksheet based on teacher instructions.\n\nHOW TO EDIT: You change the worksheet by CALLING TOOLS — never by printing the new value as text.\n- Call \\`update_worksheet_field\\` for the field the teacher wants changed (currently: name).\n- Use \\`search_materials\\` first only if you need class context.\n\nRULES:\n- Only change what the teacher asked about.\n- Each tool call saves immediately and is shown to the teacher live.\n- After making all the changes, write a brief conversational summary of what you did.\n Be human and specific — mention the actual values. For example:\n \"Done — I've renamed the worksheet to 'Unit 3 Practice Problems'.\"\n Do NOT dump the full worksheet contents — just summarize naturally.`;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction buildAssignmentContext(assignment: any): string {\n let context = `TITLE: ${assignment.title}\nINSTRUCTIONS: ${assignment.instructions || '(none)'}\nDUE_DATE: ${assignment.dueDate.toISOString()}\nTYPE: ${assignment.type}\nMAX_GRADE: ${assignment.maxGrade ?? 100}\nSECTION: ${assignment.section?.name || 'NONE'}\nACCEPT_FILES: ${assignment.acceptFiles}\nACCEPT_EXTENDED_RESPONSE: ${assignment.acceptExtendedResponse}\nACCEPT_WORKSHEET: ${assignment.acceptWorksheet}`;\n\n if (assignment.markScheme) {\n try {\n const ms = JSON.parse(assignment.markScheme.structured);\n context += '\\nMARK_SCHEME_START';\n if (Array.isArray(ms.criteria)) {\n for (const c of ms.criteria) {\n context += `\\nMS_CRITERION: ${c.description} | POINTS: ${c.points}`;\n }\n }\n context += '\\nMARK_SCHEME_END';\n } catch { /* ignore malformed JSON */ }\n }\n\n if (assignment.worksheets && assignment.worksheets.length > 0) {\n context += '\\nQUESTIONS_START';\n for (const ws of assignment.worksheets) {\n for (const q of ws.questions) {\n context += `\\nQ_TYPE: ${q.type}`;\n context += `\\nQ_TEXT: ${q.question}`;\n context += `\\nQ_ANSWER: ${q.answer}`;\n if (q.options && typeof q.options === 'object') {\n const opts = Array.isArray(q.options) ? q.options : (q.options as Record<string, unknown>).options;\n if (Array.isArray(opts)) {\n context += `\\nQ_OPTIONS: ${opts.map((o: { text?: string } | string) => typeof o === 'string' ? o : o.text || '').join('|')}`;\n }\n }\n context += `\\nQ_POINTS: ${q.points}`;\n context += '\\n---';\n }\n }\n context += '\\nQUESTIONS_END';\n }\n\n return context;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction buildWorksheetContext(worksheet: any): string {\n let context = `WORKSHEET_NAME: ${worksheet.name}\\nQUESTIONS_START`;\n\n for (const q of worksheet.questions) {\n context += `\\nQ_TYPE: ${q.type}`;\n context += `\\nQ_TEXT: ${q.question}`;\n context += `\\nQ_ANSWER: ${q.answer}`;\n if (q.options && typeof q.options === 'object') {\n const opts = Array.isArray(q.options) ? q.options : (q.options as Record<string, unknown>).options;\n if (Array.isArray(opts)) {\n context += `\\nQ_OPTIONS: ${opts.map((o: { text?: string } | string) => typeof o === 'string' ? o : o.text || '').join('|')}`;\n }\n }\n context += `\\nQ_POINTS: ${q.points}`;\n context += '\\n---';\n }\n\n context += '\\nQUESTIONS_END';\n return context;\n}\n"],"names":[],"mappings":"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","debug_id":"de32fb67-392b-5976-8b7c-1098752de11a"}
1
+ {"version":3,"file":"aiStream.js","sources":["routes/aiStream.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Express routes for AI streaming endpoints.\n * These routes use the Vercel AI SDK's data stream protocol for streaming AI responses.\n */\nimport { Router } from 'express';\nimport type { Request, Response } from 'express';\nimport { streamText, stepCountIs, type ModelMessage } from 'ai';\nimport { openai } from '@ai-sdk/openai';\nimport { findUserBySessionToken } from '../models/auth.js';\nimport { prisma } from '../lib/prisma.js';\nimport { logger } from '../utils/logger.js';\nimport { aiTools } from '../services/aiTools.js';\nimport {\n assignmentModifierTools,\n worksheetModifierTools,\n type ModifierFieldUpdate,\n} from '../services/aiModifierTools.js';\nimport { getRedis } from '../lib/redis.js';\nimport { pusher, chatChannel, teacherChannel } from '../lib/pusher.js';\nimport { getAIUserId, ensureAIUserExists, isAIUser } from '../utils/aiUser.js';\nimport { JobStatus } from '@prisma/client';\nimport { updateAssignmentRecord } from '../services/assignment.js';\nimport {\n restoreWorksheetQuestions,\n updateWorksheetRecord,\n type WorksheetQuestionSnapshot,\n} from '../services/worksheet.js';\n\nexport const aiStreamRouter = Router();\n\n/**\n * Authenticate request from session token (header or cookie).\n */\nasync function authenticateRequest(req: Request) {\n const userHeader = req.headers['x-user'];\n const tokenFromUserHeader = typeof userHeader === 'string' ? userHeader : Array.isArray(userHeader) ? userHeader[0] : undefined;\n const authorizationHeader = req.headers.authorization;\n const tokenFromAuthorization =\n typeof authorizationHeader === 'string' && authorizationHeader.startsWith('Bearer ')\n ? authorizationHeader.slice(7)\n : null;\n const token = tokenFromUserHeader ?? tokenFromAuthorization ?? undefined;\n if (!token) return null;\n return findUserBySessionToken(token);\n}\n\n/**\n * POST /api/ai/chat/stream\n *\n * Streams an AI response for a conversation (Lab Chat or Newton Tutor).\n * Uses the Vercel AI SDK data stream protocol.\n */\naiStreamRouter.post('/chat/stream', async (req: Request, res: Response) => {\n const user = await authenticateRequest(req);\n if (!user) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const { conversationId } = req.body as { conversationId?: string };\n if (!conversationId) {\n return res.status(400).json({ error: 'conversationId is required' });\n }\n\n // Verify user has access to this conversation\n const membership = await prisma.conversationMember.findFirst({\n where: { conversationId, userId: user.id },\n });\n if (!membership) {\n return res.status(403).json({ error: 'Access denied' });\n }\n\n // Determine context: Lab Chat or Newton Chat\n const labChat = await prisma.labChat.findFirst({\n where: { conversationId },\n select: {\n id: true,\n classId: true,\n context: true,\n class: { select: { subject: true, name: true } },\n },\n });\n\n const newtonChat = !labChat ? await prisma.newtonChat.findFirst({\n where: { conversationId },\n select: {\n id: true,\n submission: {\n select: {\n id: true,\n assignment: {\n select: {\n title: true,\n instructions: true,\n aiPolicyLevel: true,\n class: { select: { id: true, subject: true, name: true } },\n },\n },\n },\n },\n },\n }) : null;\n\n if (!labChat && !newtonChat) {\n return res.status(404).json({ error: 'No AI chat context found for this conversation' });\n }\n\n const classId = labChat?.classId || newtonChat?.submission.assignment.class.id;\n if (!classId) {\n return res.status(400).json({ error: 'Unable to determine class context' });\n }\n\n // Build message history from DB (include attachments so the LLM can see them)\n const recentMessages = await prisma.message.findMany({\n where: { conversationId, status: { not: JobStatus.PENDING } },\n orderBy: { createdAt: 'asc' },\n take: 20,\n select: {\n content: true,\n senderId: true,\n status: true,\n attachments: {\n select: { id: true, name: true, type: true, path: true },\n },\n },\n });\n\n const messages: ModelMessage[] = recentMessages\n .filter((m) => m.content && m.content.length > 0)\n .map((m) => {\n const role = isAIUser(m.senderId) ? 'assistant' as const : 'user' as const;\n if (role === 'assistant' || m.attachments.length === 0) {\n return { role, content: m.content };\n }\n // Append file descriptions to user messages\n const fileList = m.attachments.map((a) => `${a.name} (${a.type})`).join(', ');\n return { role, content: `${m.content}\\n[Attached files: ${fileList}]` };\n });\n\n // Build system prompt based on context type\n let systemPrompt: string;\n if (labChat) {\n systemPrompt = buildLabChatSystemPrompt(labChat);\n } else {\n systemPrompt = buildNewtonSystemPrompt(newtonChat!);\n }\n\n // Create pending AI message\n await ensureAIUserExists();\n const aiMessage = await prisma.message.create({\n data: {\n content: '',\n senderId: getAIUserId(),\n conversationId,\n status: JobStatus.PENDING,\n },\n });\n\n // Broadcast pending state\n await pusher.trigger(chatChannel(conversationId), 'new-message', {\n id: aiMessage.id,\n content: '',\n senderId: getAIUserId(),\n conversationId,\n status: JobStatus.PENDING,\n createdAt: aiMessage.createdAt,\n sender: {\n id: getAIUserId(),\n username: 'Newton_AI',\n profile: { displayName: 'Newton AI', profilePicture: null },\n },\n mentionedUserIds: [],\n meta: {},\n attachments: [],\n });\n\n // Cache stream content in Redis\n const redis = getRedis();\n const streamKey = `stream:${aiMessage.id}`;\n if (redis) {\n await redis.set(streamKey, '', 'EX', 600);\n }\n\n try {\n const tools = aiTools(classId);\n\n const result = streamText({\n model: openai('gpt-4o-mini'),\n system: systemPrompt,\n messages,\n tools,\n temperature: 0.7,\n maxOutputTokens: 4096,\n stopWhen: stepCountIs(5),\n onStepFinish: async ({ toolCalls }) => {\n // Broadcast tool call events so all clients can show inline indicators\n if (toolCalls && toolCalls.length > 0) {\n for (const tc of toolCalls) {\n await pusher.trigger(chatChannel(conversationId), 'message-tool-call', {\n messageId: aiMessage.id,\n toolName: tc.toolName,\n status: 'executing',\n }).catch((err: unknown) => {\n logger.error('Failed to broadcast tool call event', { err, messageId: aiMessage.id });\n });\n }\n }\n },\n onFinish: async ({ text }) => {\n // Persist completed message\n await prisma.message.update({\n where: { id: aiMessage.id },\n data: { content: text, status: JobStatus.COMPLETED },\n });\n\n // Broadcast final state\n await pusher.trigger(chatChannel(conversationId), 'message-updated', {\n id: aiMessage.id,\n content: text,\n senderId: getAIUserId(),\n conversationId,\n status: JobStatus.COMPLETED,\n sender: {\n id: getAIUserId(),\n username: 'Newton_AI',\n profile: { displayName: 'Newton AI', profilePicture: null },\n },\n mentionedUserIds: [],\n meta: {},\n attachments: [],\n });\n\n // Clean up Redis\n if (redis) {\n await redis.del(streamKey);\n }\n\n logger.info('AI stream completed', {\n messageId: aiMessage.id,\n conversationId,\n contentLength: text.length,\n });\n },\n });\n\n // Stream to the requesting client via SSE and broadcast chunks via Pusher\n // so other participants also see progressive updates.\n res.writeHead(200, {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Transfer-Encoding': 'chunked',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n\n let chunkBuffer = '';\n for await (const chunk of result.textStream) {\n // Write to HTTP response for the requesting client\n res.write(chunk);\n\n // Buffer chunks to avoid excessive Pusher calls (batch per ~100 chars)\n chunkBuffer += chunk;\n if (chunkBuffer.length >= 100) {\n await pusher.trigger(chatChannel(conversationId), 'message-stream-chunk', {\n messageId: aiMessage.id,\n chunk: chunkBuffer,\n }).catch(() => {});\n // Update Redis cache\n if (redis) {\n await redis.append(streamKey, chunkBuffer);\n }\n chunkBuffer = '';\n }\n }\n\n // Flush remaining buffer\n if (chunkBuffer.length > 0) {\n await pusher.trigger(chatChannel(conversationId), 'message-stream-chunk', {\n messageId: aiMessage.id,\n chunk: chunkBuffer,\n }).catch(() => {});\n if (redis) {\n await redis.append(streamKey, chunkBuffer);\n }\n }\n\n res.end();\n } catch (error) {\n logger.error('AI stream error', { error, conversationId, messageId: aiMessage.id });\n\n await prisma.message.update({\n where: { id: aiMessage.id },\n data: { status: JobStatus.FAILED },\n });\n\n if (redis) {\n await redis.del(streamKey);\n }\n\n if (!res.headersSent) {\n return res.status(500).json({ error: 'Stream failed' });\n }\n }\n});\n\n/**\n * POST /api/ai/modify/stream\n *\n * Streams AI modifications to an assignment or worksheet.\n * Uses ordered-key output format for progressive rendering.\n */\naiStreamRouter.post('/modify/stream', async (req: Request, res: Response) => {\n const user = await authenticateRequest(req);\n if (!user) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const { assignmentId, worksheetId, prompt, classId } = req.body as {\n assignmentId?: string;\n worksheetId?: string;\n prompt?: string;\n classId?: string;\n };\n\n if (!prompt || !classId) {\n return res.status(400).json({ error: 'prompt and classId are required' });\n }\n\n if (!assignmentId && !worksheetId) {\n return res.status(400).json({ error: 'Either assignmentId or worksheetId is required' });\n }\n\n // Verify teacher access to class\n const teacherInClass = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: { some: { id: user.id } },\n },\n });\n if (!teacherInClass) {\n return res.status(403).json({ error: 'Teacher access required' });\n }\n\n // Build context from existing resource and snapshot pre-edit state for undo\n let resourceContext: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let preEditSnapshot: Record<string, any> | null = null;\n if (assignmentId) {\n const assignment = await prisma.assignment.findUnique({\n where: { id: assignmentId },\n include: {\n markScheme: { select: { structured: true } },\n gradingBoundary: { select: { structured: true } },\n section: { select: { name: true } },\n worksheets: { include: { questions: { orderBy: { order: 'asc' } } } },\n },\n });\n if (!assignment) {\n return res.status(404).json({ error: 'Assignment not found' });\n }\n resourceContext = buildAssignmentContext(assignment);\n preEditSnapshot = {\n title: assignment.title,\n instructions: assignment.instructions,\n dueDate: assignment.dueDate?.toISOString() ?? null,\n type: assignment.type,\n maxGrade: assignment.maxGrade,\n graded: assignment.graded,\n weight: assignment.weight,\n acceptFiles: assignment.acceptFiles,\n acceptExtendedResponse: assignment.acceptExtendedResponse,\n acceptWorksheet: assignment.acceptWorksheet,\n aiPolicyLevel: assignment.aiPolicyLevel,\n modifiedAt: assignment.modifiedAt?.toISOString() ?? null,\n };\n } else {\n const worksheet = await prisma.worksheet.findUnique({\n where: { id: worksheetId! },\n include: { questions: { orderBy: { order: 'asc' } } },\n });\n if (!worksheet) {\n return res.status(404).json({ error: 'Worksheet not found' });\n }\n resourceContext = buildWorksheetContext(worksheet);\n preEditSnapshot = {\n name: worksheet.name,\n questions: worksheet.questions.map((q) => ({\n id: q.id,\n type: q.type,\n question: q.question,\n answer: q.answer,\n options: q.options,\n markScheme: q.markScheme,\n points: q.points,\n order: q.order,\n })),\n };\n }\n\n const systemPrompt = buildModifierSystemPrompt(assignmentId ? 'assignment' : 'worksheet');\n\n const messages: ModelMessage[] = [\n {\n role: 'user',\n content: `Here is the current ${assignmentId ? 'assignment' : 'worksheet'}:\\n\\n${resourceContext}\\n\\nTeacher's request: ${prompt}`,\n },\n ];\n\n const resourceId = assignmentId || worksheetId!;\n const resourceType = assignmentId ? 'assignment' : 'worksheet';\n const redis = getRedis();\n const modifyingKey = `aiModifying:${resourceId}`;\n const channel = teacherChannel(classId);\n\n // Set Redis flag so other users know a modification is in progress\n if (redis) {\n await redis.set(modifyingKey, JSON.stringify({\n userId: user.id,\n username: user.username,\n resourceType,\n resourceId,\n startedAt: Date.now(),\n }), 'EX', 300); // 5 min TTL safety net\n\n // Store pre-edit snapshot for undo (30 min TTL)\n if (preEditSnapshot) {\n const snapshotKey = `aiModifierSnapshot:${resourceId}`;\n await redis.set(snapshotKey, JSON.stringify({\n resourceType,\n resourceId,\n classId,\n userId: user.id,\n snapshot: preEditSnapshot,\n createdAt: Date.now(),\n }), 'EX', 1800);\n }\n }\n\n // Broadcast modifier-started to all teachers in the class\n await pusher.trigger(channel, 'modifier-started', {\n resourceId,\n resourceType,\n userId: user.id,\n username: user.username,\n }).catch((err: unknown) => {\n logger.error('Failed to broadcast modifier-started', { err, resourceId });\n });\n\n // Stream to requesting client via SSE; field changes are persisted by the\n // tools and broadcast to all open editors so they update live + stay locked.\n res.writeHead(200, {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Transfer-Encoding': 'chunked',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n\n // Called after a tool persists a single field — apply it live everywhere.\n const onFieldUpdate = async (update: ModifierFieldUpdate) => {\n await pusher.trigger(channel, 'modifier-field-updated', {\n resourceId,\n resourceType,\n field: update.field,\n value: update.value,\n label: update.label,\n }).catch((err: unknown) => {\n logger.warn('Failed to broadcast modifier-field-updated', { err, resourceId });\n });\n // Human-readable progress for the requesting client's bar.\n res.write(`Updated ${update.label}.\\n`);\n };\n\n try {\n const tools = {\n ...aiTools(classId),\n ...(assignmentId\n ? assignmentModifierTools(user.id, assignmentId, classId, onFieldUpdate)\n : worksheetModifierTools(worksheetId!, user.id, onFieldUpdate)),\n };\n\n const result = streamText({\n model: openai('gpt-4o-mini'),\n system: systemPrompt,\n messages,\n tools,\n temperature: 0.3,\n maxOutputTokens: 8192,\n stopWhen: stepCountIs(16),\n });\n\n // Drain the stream. Field writes happen inside the tools' execute (which\n // call onFieldUpdate); any narration text is forwarded to the SSE client.\n for await (const chunk of result.textStream) {\n res.write(chunk);\n }\n\n // Clean up Redis flag and broadcast completion\n if (redis) {\n await redis.del(modifyingKey);\n }\n await pusher.trigger(channel, 'modifier-complete', {\n resourceId,\n resourceType,\n canUndo: Boolean(redis && preEditSnapshot),\n }).catch(() => {});\n\n res.end();\n } catch (error) {\n logger.error('AI modify stream error', { error, assignmentId, worksheetId });\n\n // Clean up Redis flag and broadcast failure\n if (redis) {\n await redis.del(modifyingKey);\n }\n await pusher.trigger(channel, 'modifier-failed', {\n resourceId,\n resourceType,\n }).catch(() => {});\n\n if (!res.headersSent) {\n return res.status(500).json({ error: 'Stream failed' });\n }\n }\n});\n\n/**\n * GET /api/ai/modify/status?resourceId=xxx\n *\n * Returns whether an AI modification is currently in progress for a resource.\n */\naiStreamRouter.get('/modify/status', async (req: Request, res: Response) => {\n const user = await authenticateRequest(req);\n if (!user) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const { resourceId } = req.query as { resourceId?: string };\n if (!resourceId) {\n return res.status(400).json({ error: 'resourceId is required' });\n }\n\n const redis = getRedis();\n if (!redis) {\n return res.json({ isModifying: false });\n }\n\n const data = await redis.get(`aiModifying:${resourceId}`);\n if (!data) {\n return res.json({ isModifying: false });\n }\n\n try {\n const parsed = JSON.parse(data);\n return res.json({\n isModifying: true,\n userId: parsed.userId,\n username: parsed.username,\n resourceType: parsed.resourceType,\n startedAt: parsed.startedAt,\n });\n } catch {\n return res.json({ isModifying: false });\n }\n});\n\n/**\n * POST /api/ai/modify/undo\n *\n * Reverts the most recent AI modifier run for a resource by restoring the\n * pre-edit snapshot stored in Redis.\n */\naiStreamRouter.post('/modify/undo', async (req: Request, res: Response) => {\n const user = await authenticateRequest(req);\n if (!user) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const { resourceId, classId } = req.body as { resourceId?: string; classId?: string };\n if (!resourceId || !classId) {\n return res.status(400).json({ error: 'resourceId and classId are required' });\n }\n\n // Verify teacher access to class\n const teacherInClass = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: { some: { id: user.id } },\n },\n });\n if (!teacherInClass) {\n return res.status(403).json({ error: 'Teacher access required' });\n }\n\n const redis = getRedis();\n if (!redis) {\n return res.status(400).json({ error: 'Undo not available' });\n }\n\n const snapshotKey = `aiModifierSnapshot:${resourceId}`;\n const raw = await redis.get(snapshotKey);\n if (!raw) {\n return res.status(404).json({ error: 'No undo snapshot available (may have expired)' });\n }\n\n let parsed: {\n resourceType: 'assignment' | 'worksheet';\n resourceId: string;\n classId: string;\n userId: string;\n snapshot: Record<string, unknown>;\n createdAt: number;\n };\n try {\n parsed = JSON.parse(raw);\n } catch {\n return res.status(500).json({ error: 'Corrupt snapshot data' });\n }\n\n try {\n if (parsed.resourceType === 'assignment') {\n const { modifiedAt: _modifiedAt, ...fields } = parsed.snapshot;\n await updateAssignmentRecord(user.id, {\n id: resourceId,\n classId,\n ...fields,\n });\n } else {\n const { questions, ...fields } = parsed.snapshot;\n await updateWorksheetRecord(resourceId, user.id, fields);\n if (Array.isArray(questions)) {\n await restoreWorksheetQuestions(\n resourceId,\n user.id,\n questions as WorksheetQuestionSnapshot[],\n );\n }\n }\n\n // Remove the snapshot so undo can only be used once\n await redis.del(snapshotKey);\n\n // Broadcast undo to all teachers so they refresh\n const channel = teacherChannel(classId);\n await pusher.trigger(channel, 'modifier-undone', {\n resourceId,\n resourceType: parsed.resourceType,\n }).catch((err: unknown) => {\n logger.warn('Failed to broadcast modifier-undone', { err, resourceId });\n });\n\n return res.json({ success: true });\n } catch (error) {\n logger.error('AI modifier undo failed', { error, resourceId });\n return res.status(500).json({ error: 'Failed to undo changes' });\n }\n});\n\n// --- Helper functions ---\n\nfunction buildLabChatSystemPrompt(labChat: {\n context: string | null;\n class: { subject: string; name: string } | null;\n}): string {\n return `You are Newton AI, an intelligent teaching assistant for the class \"${labChat.class?.name}\" (subject: ${labChat.class?.subject}).\n\nYou help teachers create course materials, plan lessons, and manage their class. You have access to tools that let you search through class materials, assignments, and student data.\n\n${labChat.context ? `Context: ${labChat.context}` : ''}\n\nIMPORTANT RULES:\n- Use plain text responses (no markdown formatting)\n- Be conversational and helpful\n- When you need information about class materials or assignments, use the search_materials or search_assignments tools\n- Only ask clarifying questions about content/pedagogy, never about technical details\n- Make reasonable defaults for colors, formats, and technical choices\n- Keep responses concise (2-4 sentences for chat, longer for content creation)`;\n}\n\nfunction buildNewtonSystemPrompt(newtonChat: {\n submission: {\n assignment: {\n title: string;\n instructions: string | null;\n aiPolicyLevel: number | null;\n class: { subject: string | null; name: string };\n };\n };\n}): string {\n const { assignment } = newtonChat.submission;\n const policyLevel = assignment.aiPolicyLevel || 2;\n\n return `You are Newton, an AI tutor helping a student with their assignment.\n\nAssignment: ${assignment.title}\nSubject: ${assignment.class.subject}\nClass: ${assignment.class.name}\nInstructions: ${assignment.instructions || 'No specific instructions provided'}\nAI Policy Level: ${policyLevel}/5\n\nYour role:\n- Help the student understand concepts related to their assignment\n- Guide them through problems without giving direct answers\n- Use the search_materials tool to find relevant class materials when helpful\n- Encourage critical thinking and self-discovery\n- Be supportive, clear, and educational\n- Use plain text only (no markdown)\n\nAI Policy Level ${policyLevel} means:\n${policyLevel <= 2 ? '- Very limited AI assistance. Do NOT provide direct answers or solutions. Only clarify concepts and ask guiding questions.' : ''}\n${policyLevel === 3 ? '- Moderate AI assistance. You can explain concepts and give examples, but avoid solving the exact assignment problems.' : ''}\n${policyLevel >= 4 ? '- Extended AI assistance allowed. You can provide detailed explanations, examples, and guided solutions.' : ''}\n\nNEVER provide complete answers regardless of policy level. Always guide learning.`;\n}\n\nfunction buildModifierSystemPrompt(resourceType: 'assignment' | 'worksheet'): string {\n if (resourceType === 'assignment') {\n return `You are an AI assistant that edits an existing assignment based on teacher instructions.\n\nHOW TO EDIT: You change the assignment by CALLING TOOLS — never by printing the new value as text.\n- Call \\`update_assignment_field\\` once for every individual field the teacher wants changed.\n Editable fields: title, instructions, dueDate, type, maxGrade, graded, weight,\n acceptFiles, acceptExtendedResponse, acceptWorksheet, aiPolicyLevel.\n- Pass the value as a string: booleans as \"true\"/\"false\", numbers as digits,\n dueDate as an ISO 8601 datetime, and type as one of\n HOMEWORK|QUIZ|TEST|PROJECT|ESSAY|DISCUSSION|PRESENTATION|LAB|OTHER.\n- Use \\`search_materials\\` first only if you need class context.\n\nRULES:\n- Only change the fields the teacher actually asked about. Do not touch anything else.\n- Each tool call saves immediately and is shown to the teacher live.\n- After making all the changes, write a brief conversational summary of what you did.\n Be human and specific — mention the actual values. For example:\n \"Alright, I've changed the title to 'Chapter 5 Review' and pushed the due date to next Friday.\"\n Do NOT dump the full assignment contents — just summarize the changes naturally.`;\n }\n\n return `You are an AI assistant that edits an existing worksheet based on teacher instructions.\n\nHOW TO EDIT: You change the worksheet by CALLING TOOLS — never by printing the new value as text.\n- Call \\`update_worksheet_field\\` for the field the teacher wants changed (currently: name).\n- Call \\`add_worksheet_question\\` once per new question. Question types:\n MULTIPLE_CHOICE|TRUE_FALSE|SHORT_ANSWER|LONG_ANSWER|MATH_EXPRESSION|ESSAY.\n For MULTIPLE_CHOICE provide \\`options\\` with exactly one \\`isCorrect: true\\`.\n- Call \\`update_worksheet_question\\` / \\`delete_worksheet_question\\` with the question's\n Q_ID (shown in the worksheet context) to edit or remove existing questions.\n- Use \\`search_materials\\` first only if you need class context.\n\nRULES:\n- Only change what the teacher asked about.\n- Each tool call saves immediately and is shown to the teacher live.\n- Write real, pedagogically sound question content with correct answers — never placeholders.\n- After making all the changes, write a brief conversational summary of what you did.\n Be human and specific — mention the actual values. For example:\n \"Done — I've renamed the worksheet to 'Unit 3 Practice Problems' and added two\n questions on volumes of revolution.\"\n Do NOT dump the full worksheet contents — just summarize naturally.`;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction buildAssignmentContext(assignment: any): string {\n let context = `TITLE: ${assignment.title}\nINSTRUCTIONS: ${assignment.instructions || '(none)'}\nDUE_DATE: ${assignment.dueDate.toISOString()}\nTYPE: ${assignment.type}\nMAX_GRADE: ${assignment.maxGrade ?? 100}\nSECTION: ${assignment.section?.name || 'NONE'}\nACCEPT_FILES: ${assignment.acceptFiles}\nACCEPT_EXTENDED_RESPONSE: ${assignment.acceptExtendedResponse}\nACCEPT_WORKSHEET: ${assignment.acceptWorksheet}`;\n\n if (assignment.markScheme) {\n try {\n const ms = JSON.parse(assignment.markScheme.structured);\n context += '\\nMARK_SCHEME_START';\n if (Array.isArray(ms.criteria)) {\n for (const c of ms.criteria) {\n context += `\\nMS_CRITERION: ${c.description} | POINTS: ${c.points}`;\n }\n }\n context += '\\nMARK_SCHEME_END';\n } catch { /* ignore malformed JSON */ }\n }\n\n if (assignment.worksheets && assignment.worksheets.length > 0) {\n context += '\\nQUESTIONS_START';\n for (const ws of assignment.worksheets) {\n for (const q of ws.questions) {\n context += `\\nQ_TYPE: ${q.type}`;\n context += `\\nQ_TEXT: ${q.question}`;\n context += `\\nQ_ANSWER: ${q.answer}`;\n if (q.options && typeof q.options === 'object') {\n const opts = Array.isArray(q.options) ? q.options : (q.options as Record<string, unknown>).options;\n if (Array.isArray(opts)) {\n context += `\\nQ_OPTIONS: ${opts.map((o: { text?: string } | string) => typeof o === 'string' ? o : o.text || '').join('|')}`;\n }\n }\n context += `\\nQ_POINTS: ${q.points}`;\n context += '\\n---';\n }\n }\n context += '\\nQUESTIONS_END';\n }\n\n return context;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction buildWorksheetContext(worksheet: any): string {\n let context = `WORKSHEET_NAME: ${worksheet.name}\\nQUESTIONS_START`;\n\n for (const q of worksheet.questions) {\n context += `\\nQ_ID: ${q.id}`;\n context += `\\nQ_TYPE: ${q.type}`;\n context += `\\nQ_TEXT: ${q.question}`;\n context += `\\nQ_ANSWER: ${q.answer}`;\n if (q.options && typeof q.options === 'object') {\n const opts = Array.isArray(q.options) ? q.options : (q.options as Record<string, unknown>).options;\n if (Array.isArray(opts)) {\n context += `\\nQ_OPTIONS: ${opts.map((o: { text?: string } | string) => typeof o === 'string' ? o : o.text || '').join('|')}`;\n }\n }\n context += `\\nQ_POINTS: ${q.points}`;\n context += '\\n---';\n }\n\n context += '\\nQUESTIONS_END';\n return context;\n}\n"],"names":[],"mappings":"AAAA;;;GAGG;;;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAqB,MAAM,IAAI,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EACL,uBAAuB,EACvB,sBAAsB,GAEvB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EACL,yBAAyB,EACzB,qBAAqB,GAEtB,MAAM,0BAA0B,CAAC;AAElC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC;AAEvC;;GAEG;AACH,KAAK,UAAU,mBAAmB,CAAC,GAAY;IAC7C,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,mBAAmB,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChI,MAAM,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IACtD,MAAM,sBAAsB,GAC1B,OAAO,mBAAmB,KAAK,QAAQ,IAAI,mBAAmB,CAAC,UAAU,CAAC,SAAS,CAAC;QAClF,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,KAAK,GAAG,mBAAmB,IAAI,sBAAsB,IAAI,SAAS,CAAC;IACzE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,sBAAsB,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,cAAc,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACxE,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,IAAmC,CAAC;IACnE,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,8CAA8C;IAC9C,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC;QAC3D,KAAK,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE;KAC3C,CAAC,CAAC;IACH,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,6CAA6C;IAC7C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;QAC7C,KAAK,EAAE,EAAE,cAAc,EAAE;QACzB,MAAM,EAAE;YACN,EAAE,EAAE,IAAI;YACR,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,IAAI;YACb,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;SACjD;KACF,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;QAC9D,KAAK,EAAE,EAAE,cAAc,EAAE;QACzB,MAAM,EAAE;YACN,EAAE,EAAE,IAAI;YACR,UAAU,EAAE;gBACV,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI;oBACR,UAAU,EAAE;wBACV,MAAM,EAAE;4BACN,KAAK,EAAE,IAAI;4BACX,YAAY,EAAE,IAAI;4BAClB,aAAa,EAAE,IAAI;4BACnB,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;yBAC3D;qBACF;iBACF;aACF;SACF;KACF,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEV,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gDAAgD,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,8EAA8E;IAC9E,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;QACnD,KAAK,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,OAAO,EAAE,EAAE;QAC7D,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;QAC7B,IAAI,EAAE,EAAE;QACR,MAAM,EAAE;YACN,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,IAAI;YACZ,WAAW,EAAE;gBACX,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;aACzD;SACF;KACF,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAmB,cAAc;SAC5C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;SAChD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAoB,CAAC,CAAC,CAAC,MAAe,CAAC;QAC3E,IAAI,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QACtC,CAAC;QACD,4CAA4C;QAC5C,MAAM,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,sBAAsB,QAAQ,GAAG,EAAE,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEL,4CAA4C;IAC5C,IAAI,YAAoB,CAAC;IACzB,IAAI,OAAO,EAAE,CAAC;QACZ,YAAY,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;IACnD,CAAC;SAAM,CAAC;QACN,YAAY,GAAG,uBAAuB,CAAC,UAAW,CAAC,CAAC;IACtD,CAAC;IAED,4BAA4B;IAC5B,MAAM,kBAAkB,EAAE,CAAC;IAC3B,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QAC5C,IAAI,EAAE;YACJ,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,WAAW,EAAE;YACvB,cAAc;YACd,MAAM,EAAE,SAAS,CAAC,OAAO;SAC1B;KACF,CAAC,CAAC;IAEH,0BAA0B;IAC1B,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE;QAC/D,EAAE,EAAE,SAAS,CAAC,EAAE;QAChB,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,WAAW,EAAE;QACvB,cAAc;QACd,MAAM,EAAE,SAAS,CAAC,OAAO;QACzB,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,MAAM,EAAE;YACN,EAAE,EAAE,WAAW,EAAE;YACjB,QAAQ,EAAE,WAAW;YACrB,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,IAAI,EAAE;SAC5D;QACD,gBAAgB,EAAE,EAAE;QACpB,IAAI,EAAE,EAAE;QACR,WAAW,EAAE,EAAE;KAChB,CAAC,CAAC;IAEH,gCAAgC;IAChC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,SAAS,GAAG,UAAU,SAAS,CAAC,EAAE,EAAE,CAAC;IAC3C,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAC5C,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAE/B,MAAM,MAAM,GAAG,UAAU,CAAC;YACxB,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC;YAC5B,MAAM,EAAE,YAAY;YACpB,QAAQ;YACR,KAAK;YACL,WAAW,EAAE,GAAG;YAChB,eAAe,EAAE,IAAI;YACrB,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;YACxB,YAAY,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;gBACpC,uEAAuE;gBACvE,IAAI,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACtC,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;wBAC3B,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,mBAAmB,EAAE;4BACrE,SAAS,EAAE,SAAS,CAAC,EAAE;4BACvB,QAAQ,EAAE,EAAE,CAAC,QAAQ;4BACrB,MAAM,EAAE,WAAW;yBACpB,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;4BACxB,MAAM,CAAC,KAAK,CAAC,qCAAqC,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;wBACxF,CAAC,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;YACD,QAAQ,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;gBAC3B,4BAA4B;gBAC5B,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;oBAC1B,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE;oBAC3B,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,SAAS,EAAE;iBACrD,CAAC,CAAC;gBAEH,wBAAwB;gBACxB,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,iBAAiB,EAAE;oBACnE,EAAE,EAAE,SAAS,CAAC,EAAE;oBAChB,OAAO,EAAE,IAAI;oBACb,QAAQ,EAAE,WAAW,EAAE;oBACvB,cAAc;oBACd,MAAM,EAAE,SAAS,CAAC,SAAS;oBAC3B,MAAM,EAAE;wBACN,EAAE,EAAE,WAAW,EAAE;wBACjB,QAAQ,EAAE,WAAW;wBACrB,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,IAAI,EAAE;qBAC5D;oBACD,gBAAgB,EAAE,EAAE;oBACpB,IAAI,EAAE,EAAE;oBACR,WAAW,EAAE,EAAE;iBAChB,CAAC,CAAC;gBAEH,iBAAiB;gBACjB,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAC7B,CAAC;gBAED,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE;oBACjC,SAAS,EAAE,SAAS,CAAC,EAAE;oBACvB,cAAc;oBACd,aAAa,EAAE,IAAI,CAAC,MAAM;iBAC3B,CAAC,CAAC;YACL,CAAC;SACF,CAAC,CAAC;QAEH,0EAA0E;QAC1E,sDAAsD;QACtD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;YACjB,cAAc,EAAE,2BAA2B;YAC3C,mBAAmB,EAAE,SAAS;YAC9B,eAAe,EAAE,UAAU;YAC3B,UAAU,EAAE,YAAY;SACzB,CAAC,CAAC;QAEH,IAAI,WAAW,GAAG,EAAE,CAAC;QACrB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YAC5C,mDAAmD;YACnD,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAEjB,uEAAuE;YACvE,WAAW,IAAI,KAAK,CAAC;YACrB,IAAI,WAAW,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBAC9B,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,sBAAsB,EAAE;oBACxE,SAAS,EAAE,SAAS,CAAC,EAAE;oBACvB,KAAK,EAAE,WAAW;iBACnB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACnB,qBAAqB;gBACrB,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;gBAC7C,CAAC;gBACD,WAAW,GAAG,EAAE,CAAC;YACnB,CAAC;QACH,CAAC;QAED,yBAAyB;QACzB,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,sBAAsB,EAAE;gBACxE,SAAS,EAAE,SAAS,CAAC,EAAE;gBACvB,KAAK,EAAE,WAAW;aACnB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACnB,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QAED,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;QAEpF,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAC1B,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE;YAC3B,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE;SACnC,CAAC,CAAC;QAEH,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;AACH,CAAC,CAAC,CAAC;AAEH;;;;;GAKG;AACH,cAAc,CAAC,IAAI,CAAC,gBAAgB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IAC1E,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,IAK1D,CAAC;IAEF,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACxB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,IAAI,CAAC,YAAY,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gDAAgD,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,iCAAiC;IACjC,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;QAClD,KAAK,EAAE;YACL,EAAE,EAAE,OAAO;YACX,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE;SACpC;KACF,CAAC,CAAC;IACH,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,4EAA4E;IAC5E,IAAI,eAAuB,CAAC;IAC5B,8DAA8D;IAC9D,IAAI,eAAe,GAA+B,IAAI,CAAC;IACvD,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC;YACpD,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE;YAC3B,OAAO,EAAE;gBACP,UAAU,EAAE,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE;gBAC5C,eAAe,EAAE,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE;gBACjD,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;gBACnC,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;aACtE;SACF,CAAC,CAAC;QACH,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,eAAe,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;QACrD,eAAe,GAAG;YAChB,KAAK,EAAE,UAAU,CAAC,KAAK;YACvB,YAAY,EAAE,UAAU,CAAC,YAAY;YACrC,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,IAAI;YAClD,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,WAAW,EAAE,UAAU,CAAC,WAAW;YACnC,sBAAsB,EAAE,UAAU,CAAC,sBAAsB;YACzD,eAAe,EAAE,UAAU,CAAC,eAAe;YAC3C,aAAa,EAAE,UAAU,CAAC,aAAa;YACvC,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,IAAI;SACzD,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;YAClD,KAAK,EAAE,EAAE,EAAE,EAAE,WAAY,EAAE;YAC3B,OAAO,EAAE,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;SACtD,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,eAAe,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;QACnD,eAAe,GAAG;YAChB,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,SAAS,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACzC,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,KAAK,EAAE,CAAC,CAAC,KAAK;aACf,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,yBAAyB,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAE1F,MAAM,QAAQ,GAAmB;QAC/B;YACE,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,uBAAuB,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,QAAQ,eAAe,0BAA0B,MAAM,EAAE;SACnI;KACF,CAAC;IAEF,MAAM,UAAU,GAAG,YAAY,IAAI,WAAY,CAAC;IAChD,MAAM,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;IAC/D,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,YAAY,GAAG,eAAe,UAAU,EAAE,CAAC;IACjD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAExC,mEAAmE;IACnE,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,KAAK,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC;YAC3C,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,YAAY;YACZ,UAAU;YACV,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,uBAAuB;QAEvC,gDAAgD;QAChD,IAAI,eAAe,EAAE,CAAC;YACpB,MAAM,WAAW,GAAG,sBAAsB,UAAU,EAAE,CAAC;YACvD,MAAM,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC;gBAC1C,YAAY;gBACZ,UAAU;gBACV,OAAO;gBACP,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,QAAQ,EAAE,eAAe;gBACzB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACtB,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,EAAE;QAChD,UAAU;QACV,YAAY;QACZ,MAAM,EAAE,IAAI,CAAC,EAAE;QACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACxB,MAAM,CAAC,KAAK,CAAC,sCAAsC,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,0EAA0E;IAC1E,6EAA6E;IAC7E,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,2BAA2B;QAC3C,mBAAmB,EAAE,SAAS;QAC9B,eAAe,EAAE,UAAU;QAC3B,UAAU,EAAE,YAAY;KACzB,CAAC,CAAC;IAEH,0EAA0E;IAC1E,MAAM,aAAa,GAAG,KAAK,EAAE,MAA2B,EAAE,EAAE;QAC1D,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,wBAAwB,EAAE;YACtD,UAAU;YACV,YAAY;YACZ,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACxB,MAAM,CAAC,IAAI,CAAC,4CAA4C,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;QACjF,CAAC,CAAC,CAAC;QACH,2DAA2D;QAC3D,GAAG,CAAC,KAAK,CAAC,WAAW,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC;IAC1C,CAAC,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,KAAK,GAAG;YACZ,GAAG,OAAO,CAAC,OAAO,CAAC;YACnB,GAAG,CAAC,YAAY;gBACd,CAAC,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,CAAC;gBACxE,CAAC,CAAC,sBAAsB,CAAC,WAAY,EAAE,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;SAClE,CAAC;QAEF,MAAM,MAAM,GAAG,UAAU,CAAC;YACxB,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC;YAC5B,MAAM,EAAE,YAAY;YACpB,QAAQ;YACR,KAAK;YACL,WAAW,EAAE,GAAG;YAChB,eAAe,EAAE,IAAI;YACrB,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC;SAC1B,CAAC,CAAC;QAEH,yEAAyE;QACzE,0EAA0E;QAC1E,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YAC5C,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;QAED,+CAA+C;QAC/C,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAChC,CAAC;QACD,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,mBAAmB,EAAE;YACjD,UAAU;YACV,YAAY;YACZ,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,eAAe,CAAC;SAC3C,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAEnB,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;QAE7E,4CAA4C;QAC5C,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAChC,CAAC;QACD,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE;YAC/C,UAAU;YACV,YAAY;SACb,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAEnB,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;AACH,CAAC,CAAC,CAAC;AAEH;;;;GAIG;AACH,cAAc,CAAC,GAAG,CAAC,gBAAgB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACzE,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,KAAgC,CAAC;IAC5D,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,eAAe,UAAU,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,GAAG,CAAC,IAAI,CAAC;YACd,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC,CAAC,CAAC;AAEH;;;;;GAKG;AACH,cAAc,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACxE,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,IAAiD,CAAC;IACtF,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,iCAAiC;IACjC,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;QAClD,KAAK,EAAE;YACL,EAAE,EAAE,OAAO;YACX,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE;SACpC;KACF,CAAC,CAAC;IACH,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,WAAW,GAAG,sBAAsB,UAAU,EAAE,CAAC;IACvD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,+CAA+C,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,IAAI,MAOH,CAAC;IACF,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,YAAY,KAAK,YAAY,EAAE,CAAC;YACzC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YAC/D,MAAM,sBAAsB,CAAC,IAAI,CAAC,EAAE,EAAE;gBACpC,EAAE,EAAE,UAAU;gBACd,OAAO;gBACP,GAAG,MAAM;aACV,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YACjD,MAAM,qBAAqB,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YACzD,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7B,MAAM,yBAAyB,CAC7B,UAAU,EACV,IAAI,CAAC,EAAE,EACP,SAAwC,CACzC,CAAC;YACJ,CAAC;QACH,CAAC;QAED,oDAAoD;QACpD,MAAM,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAE7B,iDAAiD;QACjD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE;YAC/C,UAAU;YACV,YAAY,EAAE,MAAM,CAAC,YAAY;SAClC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACxB,MAAM,CAAC,IAAI,CAAC,qCAAqC,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;QAC/D,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAC;IACnE,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,2BAA2B;AAE3B,SAAS,wBAAwB,CAAC,OAGjC;IACC,OAAO,uEAAuE,OAAO,CAAC,KAAK,EAAE,IAAI,eAAe,OAAO,CAAC,KAAK,EAAE,OAAO;;;;EAItI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE;;;;;;;;+EAQyB,CAAC;AAChF,CAAC;AAED,SAAS,uBAAuB,CAAC,UAShC;IACC,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC;IAC7C,MAAM,WAAW,GAAG,UAAU,CAAC,aAAa,IAAI,CAAC,CAAC;IAElD,OAAO;;cAEK,UAAU,CAAC,KAAK;WACnB,UAAU,CAAC,KAAK,CAAC,OAAO;SAC1B,UAAU,CAAC,KAAK,CAAC,IAAI;gBACd,UAAU,CAAC,YAAY,IAAI,mCAAmC;mBAC3D,WAAW;;;;;;;;;;kBAUZ,WAAW;EAC3B,WAAW,IAAI,CAAC,CAAC,CAAC,CAAC,4HAA4H,CAAC,CAAC,CAAC,EAAE;EACpJ,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,wHAAwH,CAAC,CAAC,CAAC,EAAE;EACjJ,WAAW,IAAI,CAAC,CAAC,CAAC,CAAC,0GAA0G,CAAC,CAAC,CAAC,EAAE;;kFAElD,CAAC;AACnF,CAAC;AAED,SAAS,yBAAyB,CAAC,YAAwC;IACzE,IAAI,YAAY,KAAK,YAAY,EAAE,CAAC;QAClC,OAAO;;;;;;;;;;;;;;;;;mFAiBwE,CAAC;IAClF,CAAC;IAED,OAAO;;;;;;;;;;;;;;;;;;;sEAmB6D,CAAC;AACvE,CAAC;AAED,8DAA8D;AAC9D,SAAS,sBAAsB,CAAC,UAAe;IAC7C,IAAI,OAAO,GAAG,UAAU,UAAU,CAAC,KAAK;gBAC1B,UAAU,CAAC,YAAY,IAAI,QAAQ;YACvC,UAAU,CAAC,OAAO,CAAC,WAAW,EAAE;QACpC,UAAU,CAAC,IAAI;aACV,UAAU,CAAC,QAAQ,IAAI,GAAG;WAC5B,UAAU,CAAC,OAAO,EAAE,IAAI,IAAI,MAAM;gBAC7B,UAAU,CAAC,WAAW;4BACV,UAAU,CAAC,sBAAsB;oBACzC,UAAU,CAAC,eAAe,EAAE,CAAC;IAE/C,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;YACxD,OAAO,IAAI,qBAAqB,CAAC;YACjC,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC/B,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;oBAC5B,OAAO,IAAI,mBAAmB,CAAC,CAAC,WAAW,cAAc,CAAC,CAAC,MAAM,EAAE,CAAC;gBACtE,CAAC;YACH,CAAC;YACD,OAAO,IAAI,mBAAmB,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,UAAU,CAAC,UAAU,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9D,OAAO,IAAI,mBAAmB,CAAC;QAC/B,KAAK,MAAM,EAAE,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC;YACvC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC;gBAC7B,OAAO,IAAI,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC;gBACjC,OAAO,IAAI,aAAa,CAAC,CAAC,QAAQ,EAAE,CAAC;gBACrC,OAAO,IAAI,eAAe,CAAC,CAAC,MAAM,EAAE,CAAC;gBACrC,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;oBAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC,OAAmC,CAAC,OAAO,CAAC;oBACnG,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;wBACxB,OAAO,IAAI,gBAAgB,IAAI,CAAC,GAAG,CAAC,CAAC,CAA6B,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC/H,CAAC;gBACH,CAAC;gBACD,OAAO,IAAI,eAAe,CAAC,CAAC,MAAM,EAAE,CAAC;gBACrC,OAAO,IAAI,OAAO,CAAC;YACrB,CAAC;QACH,CAAC;QACD,OAAO,IAAI,iBAAiB,CAAC;IAC/B,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8DAA8D;AAC9D,SAAS,qBAAqB,CAAC,SAAc;IAC3C,IAAI,OAAO,GAAG,mBAAmB,SAAS,CAAC,IAAI,mBAAmB,CAAC;IAEnE,KAAK,MAAM,CAAC,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;QACpC,OAAO,IAAI,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC;QAC7B,OAAO,IAAI,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC;QACjC,OAAO,IAAI,aAAa,CAAC,CAAC,QAAQ,EAAE,CAAC;QACrC,OAAO,IAAI,eAAe,CAAC,CAAC,MAAM,EAAE,CAAC;QACrC,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC,OAAmC,CAAC,OAAO,CAAC;YACnG,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxB,OAAO,IAAI,gBAAgB,IAAI,CAAC,GAAG,CAAC,CAAC,CAA6B,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/H,CAAC;QACH,CAAC;QACD,OAAO,IAAI,eAAe,CAAC,CAAC,MAAM,EAAE,CAAC;QACrC,OAAO,IAAI,OAAO,CAAC;IACrB,CAAC;IAED,OAAO,IAAI,iBAAiB,CAAC;IAC7B,OAAO,OAAO,CAAC;AACjB,CAAC","debug_id":"14ec689a-63a6-514d-9ced-078103a2eb8e"}
@@ -5,6 +5,8 @@ export declare function createUser(email: string, password: string, username: st
5
5
  email: string;
6
6
  password: string;
7
7
  verified: boolean;
8
+ termsAcceptedAt: Date | null;
9
+ termsVersion: string | null;
8
10
  schoolId: string | null;
9
11
  }>;
10
12
  export declare function addNotification(userId: string, title: string, content: string): Promise<{
@@ -12,9 +14,9 @@ export declare function addNotification(userId: string, title: string, content:
12
14
  createdAt: Date;
13
15
  title: string;
14
16
  content: string;
17
+ senderId: string | null;
15
18
  read: boolean;
16
19
  receiverId: string;
17
- senderId: string | null;
18
20
  }>;
19
21
  export declare const seedDatabase: () => Promise<void>;
20
22
  //# sourceMappingURL=seedDatabase.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"seedDatabase.d.ts","sourceRoot":"/","sources":["seedDatabase.ts"],"names":[],"mappings":"AAwBA,wBAAsB,aAAa,kBA+ElC;AA6CD,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;GAKjF;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;GAQnF;AA+HD,eAAO,MAAM,YAAY,qBAskDxB,CAAC"}
1
+ {"version":3,"file":"seedDatabase.d.ts","sourceRoot":"/","sources":["seedDatabase.ts"],"names":[],"mappings":"AAwBA,wBAAsB,aAAa,kBA+ElC;AA6CD,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;GAKjF;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;GAQnF;AA+HD,eAAO,MAAM,YAAY,qBAskDxB,CAAC"}
@@ -38,8 +38,28 @@ export type StreamPart = {
38
38
  toolName: string;
39
39
  /** Concrete entity label (e.g. assignment title / search query), if any. */
40
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;
41
46
  status: 'executing' | 'completed' | 'failed';
42
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[];
43
63
  export interface StreamChatOptions {
44
64
  conversationId: string;
45
65
  aiMessageId: string;
@@ -1 +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"}
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"}
@@ -12,7 +12,7 @@
12
12
  * terminal `message-stream-complete` / `message-updated` events.
13
13
  */
14
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){}}();
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
16
  import { streamText, stepCountIs } from 'ai';
17
17
  import { openai } from '@ai-sdk/openai';
18
18
  import { getRedis } from '../lib/redis.js';
@@ -22,15 +22,95 @@ const STREAM_REDIS_TTL = 600; // 10 minutes
22
22
  const STREAM_KEY_PREFIX = 'stream:';
23
23
  /** Default streaming model — matches the existing streamAIResponse path. */
24
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
+ }
25
108
  /** Pull a human-meaningful label out of a tool's input args, if present. */
26
109
  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
- }
110
+ for (const key of ['title', 'name', 'query']) {
111
+ const val = toolFieldFromInput(input, key);
112
+ if (val)
113
+ return val;
34
114
  }
35
115
  return undefined;
36
116
  }
@@ -99,12 +179,14 @@ export async function streamChatResponse(options) {
99
179
  }
100
180
  case 'tool-call': {
101
181
  const title = toolTitleFromInput(part.input);
182
+ const format = toolFieldFromInput(part.input, 'format');
102
183
  toolPartIndexById.set(part.toolCallId, parts.length);
103
184
  parts.push({
104
185
  type: 'tool',
105
186
  toolCallId: part.toolCallId,
106
187
  toolName: part.toolName,
107
188
  ...(title ? { title } : {}),
189
+ ...(format ? { format } : {}),
108
190
  status: 'executing',
109
191
  });
110
192
  await broadcast('message-tool-call', {
@@ -112,30 +194,45 @@ export async function streamChatResponse(options) {
112
194
  toolCallId: part.toolCallId,
113
195
  toolName: part.toolName,
114
196
  ...(title ? { title } : {}),
197
+ ...(format ? { format } : {}),
115
198
  status: 'executing',
116
199
  });
117
200
  break;
118
201
  }
119
202
  case 'tool-result':
120
203
  case 'tool-error': {
121
- const status = part.type === 'tool-error' ? 'failed' : 'completed';
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');
122
215
  const idx = toolPartIndexById.get(part.toolCallId);
123
216
  if (idx !== undefined) {
124
217
  const existing = parts[idx];
125
- if (existing && existing.type === 'tool')
218
+ if (existing && existing.type === 'tool') {
126
219
  existing.status = status;
220
+ if (format)
221
+ existing.format = format;
222
+ }
127
223
  }
128
224
  toolCalls.push({
129
225
  toolName: part.toolName,
130
226
  input: part.input,
131
227
  output: part.type === 'tool-result' ? part.output : undefined,
132
- status,
228
+ status: failed ? 'failed' : 'completed',
133
229
  });
134
230
  await broadcast('message-tool-call', {
135
231
  messageId: aiMessageId,
136
232
  toolCallId: part.toolCallId,
137
233
  toolName: part.toolName,
138
- ...(toolTitleFromInput(part.input) ? { title: toolTitleFromInput(part.input) } : {}),
234
+ ...(title ? { title } : {}),
235
+ ...(format ? { format } : {}),
139
236
  status,
140
237
  });
141
238
  break;
@@ -154,4 +251,4 @@ export async function streamChatResponse(options) {
154
251
  }
155
252
  }
156
253
  //# sourceMappingURL=aiChatStream.js.map
157
- //# debugId=53e385f3-8180-59f0-9c1b-b89457a29317
254
+ //# debugId=5a03c108-e33a-50c7-a5fd-24f570b1b507
@@ -1 +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"}
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"}
@@ -1,19 +1,3 @@
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
1
  import { type ToolSet } from 'ai';
18
2
  /** A single field write that was applied to a resource. */
19
3
  export interface ModifierFieldUpdate {
@@ -1 +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"}
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"}