@studious-lms/server 1.4.9 → 1.6.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.
- package/dist/index.js +15 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/config/cors.d.ts.map +1 -1
- package/dist/lib/config/cors.js +2 -3
- package/dist/lib/config/cors.js.map +1 -1
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +4 -5
- package/dist/lib/fileUpload.js.map +1 -1
- package/dist/lib/pusher.d.ts +7 -1
- package/dist/lib/pusher.d.ts.map +1 -1
- package/dist/lib/pusher.js +9 -3
- package/dist/lib/pusher.js.map +1 -1
- package/dist/models/attendance.d.ts +16 -7
- package/dist/models/attendance.d.ts.map +1 -1
- package/dist/models/attendance.js +17 -2
- package/dist/models/attendance.js.map +1 -1
- package/dist/models/auth.d.ts +1 -0
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +3 -2
- package/dist/models/auth.js.map +1 -1
- package/dist/models/comment.d.ts +0 -5
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/conversation.d.ts +17 -16
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +2 -1
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/message.d.ts +6 -3
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/school.d.ts +0 -22
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +2 -14
- package/dist/models/school.js.map +1 -1
- package/dist/models/worksheet.d.ts +76 -19
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +2 -2
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/aiLabChat.d.ts +20 -9
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +63 -75
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/aiNewtonChat.d.ts +13 -4
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
- package/dist/pipelines/aiNewtonChat.js +39 -27
- package/dist/pipelines/aiNewtonChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +5 -3
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +60 -24
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/pipelines/pdfToWorksheet.d.ts +34 -2
- package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -1
- package/dist/pipelines/pdfToWorksheet.js +205 -32
- package/dist/pipelines/pdfToWorksheet.js.map +1 -1
- package/dist/routers/_app.d.ts +106 -48
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/announcement.d.ts +0 -4
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +0 -8
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +64 -4
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/auth.d.ts +1 -0
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/comment.d.ts +0 -3
- package/dist/routers/comment.d.ts.map +1 -1
- package/dist/routers/conversation.d.ts +2 -1
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/labChat.d.ts +3 -1
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/message.d.ts +3 -0
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/newtonChat.d.ts +1 -0
- package/dist/routers/newtonChat.d.ts.map +1 -1
- package/dist/routers/school.d.ts +6 -0
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +7 -2
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/worksheet.d.ts +37 -7
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +16 -6
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/services/announcement.js +4 -4
- package/dist/services/announcement.js.map +1 -1
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +16 -6
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/attendance.d.ts.map +1 -1
- package/dist/services/attendance.js +68 -4
- package/dist/services/attendance.js.map +1 -1
- package/dist/services/auth.d.ts +1 -0
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/comment.d.ts +0 -3
- package/dist/services/comment.d.ts.map +1 -1
- package/dist/services/conversation.d.ts +2 -1
- package/dist/services/conversation.d.ts.map +1 -1
- package/dist/services/labChat.d.ts +10 -2
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +103 -44
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/message.d.ts +3 -0
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +20 -2
- package/dist/services/message.js.map +1 -1
- package/dist/services/newtonChat.d.ts +1 -0
- package/dist/services/newtonChat.d.ts.map +1 -1
- package/dist/services/newtonChat.js +40 -17
- package/dist/services/newtonChat.js.map +1 -1
- package/dist/services/school.d.ts +6 -0
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +71 -6
- package/dist/services/school.js.map +1 -1
- package/dist/services/worksheet.d.ts +48 -11
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +73 -29
- package/dist/services/worksheet.js.map +1 -1
- package/dist/utils/generateUsername.d.ts +8 -0
- package/dist/utils/generateUsername.d.ts.map +1 -0
- package/dist/utils/generateUsername.js +103 -0
- package/dist/utils/generateUsername.js.map +1 -0
- package/dist/utils/inference.d.ts +50 -1
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +167 -2
- package/dist/utils/inference.js.map +1 -1
- package/dist/workers/connection.d.ts +3 -0
- package/dist/workers/connection.d.ts.map +1 -0
- package/dist/workers/connection.js +33 -0
- package/dist/workers/connection.js.map +1 -0
- package/dist/workers/index.d.ts +10 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +63 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/processors/aiLabChat.d.ts +7 -0
- package/dist/workers/processors/aiLabChat.d.ts.map +1 -0
- package/dist/workers/processors/aiLabChat.js +52 -0
- package/dist/workers/processors/aiLabChat.js.map +1 -0
- package/dist/workers/processors/aiNewtonChat.d.ts +7 -0
- package/dist/workers/processors/aiNewtonChat.d.ts.map +1 -0
- package/dist/workers/processors/aiNewtonChat.js +57 -0
- package/dist/workers/processors/aiNewtonChat.js.map +1 -0
- package/dist/workers/processors/createRunJobWorker.d.ts +46 -0
- package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -0
- package/dist/workers/processors/createRunJobWorker.js +118 -0
- package/dist/workers/processors/createRunJobWorker.js.map +1 -0
- package/dist/workers/processors/gradeWorksheet.d.ts +6 -0
- package/dist/workers/processors/gradeWorksheet.d.ts.map +1 -0
- package/dist/workers/processors/gradeWorksheet.js +19 -0
- package/dist/workers/processors/gradeWorksheet.js.map +1 -0
- package/dist/workers/processors/pdfToWorksheet.d.ts +8 -0
- package/dist/workers/processors/pdfToWorksheet.d.ts.map +1 -0
- package/dist/workers/processors/pdfToWorksheet.js +20 -0
- package/dist/workers/processors/pdfToWorksheet.js.map +1 -0
- package/dist/workers/processors/studentProgressChat.d.ts +8 -0
- package/dist/workers/processors/studentProgressChat.d.ts.map +1 -0
- package/dist/workers/processors/studentProgressChat.js +27 -0
- package/dist/workers/processors/studentProgressChat.js.map +1 -0
- package/dist/workers/queues.d.ts +85 -0
- package/dist/workers/queues.d.ts.map +1 -0
- package/dist/workers/queues.js +119 -0
- package/dist/workers/queues.js.map +1 -0
- package/package.json +6 -3
- package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +166 -0
- package/prisma/schema.prisma +465 -387
- package/src/index.ts +12 -1
- package/src/lib/config/cors.ts +0 -1
- package/src/lib/fileUpload.ts +2 -4
- package/src/lib/pusher.ts +7 -1
- package/src/models/attendance.ts +15 -0
- package/src/models/auth.ts +1 -0
- package/src/models/school.ts +0 -14
- package/src/models/worksheet.ts +2 -2
- package/src/pipelines/aiLabChat.ts +71 -79
- package/src/pipelines/aiNewtonChat.ts +47 -31
- package/src/pipelines/gradeWorksheet.ts +63 -25
- package/src/pipelines/pdfToWorksheet.ts +272 -34
- package/src/routers/assignment.ts +74 -3
- package/src/routers/school.ts +5 -0
- package/src/routers/worksheet.ts +25 -4
- package/src/services/announcement.ts +2 -2
- package/src/services/assignment.ts +18 -4
- package/src/services/attendance.ts +87 -2
- package/src/services/labChat.ts +108 -48
- package/src/services/message.ts +19 -0
- package/src/services/newtonChat.ts +46 -25
- package/src/services/school.ts +89 -6
- package/src/services/worksheet.ts +87 -28
- package/src/utils/generateUsername.ts +108 -0
- package/src/utils/inference.ts +209 -1
- package/src/workers/connection.ts +37 -0
- package/src/workers/index.ts +71 -0
- package/src/workers/processors/aiLabChat.ts +65 -0
- package/src/workers/processors/aiNewtonChat.ts +75 -0
- package/src/workers/processors/createRunJobWorker.ts +164 -0
- package/src/workers/processors/gradeWorksheet.ts +16 -0
- package/src/workers/processors/pdfToWorksheet.ts +17 -0
- package/src/workers/processors/studentProgressChat.ts +28 -0
- package/src/workers/queues.ts +174 -0
- package/tests/{globalSetup.ts → integration/globalSetup.ts} +4 -4
- package/tests/{helpers.ts → integration/helpers.ts} +2 -2
- package/tests/{pipelines → integration/pipelines}/pdfToWorksheet.test.ts +55 -43
- package/tests/{routers → integration/routers}/attendance.test.ts +61 -2
- package/tests/{routers → integration/routers}/auth.test.ts +3 -3
- package/tests/{routers → integration/routers}/class.test.ts +3 -17
- package/tests/{routers → integration/routers}/conversation.test.ts +1 -1
- package/tests/{routers → integration/routers}/event.test.ts +1 -1
- package/tests/{routers → integration/routers}/message.test.ts +1 -1
- package/tests/{routers → integration/routers}/newtonChat.test.ts +1 -1
- package/tests/{routers → integration/routers}/studentProgress.test.ts +1 -1
- package/tests/{user.test.ts → integration/routers/user.test.ts} +2 -2
- package/tests/integration/routers/worksheet.test.ts +448 -0
- package/tests/{setup.ts → integration/setup.ts} +4 -4
- package/tests/{lib → unit/lib}/cors.test.ts +1 -1
- package/tests/{middleware → unit/middleware}/security.test.ts +1 -1
- package/tests/{pipelines → unit/pipelines}/aiLabChat.test.ts +1 -1
- package/tests/{lib → unit/pipelines}/aiLabChatContract.test.ts +1 -1
- package/tests/{utils/aiLabChatPrompt.test.ts → unit/pipelines/labChatPrompt.test.ts} +2 -2
- package/tests/{utils → unit/services}/studentProgress.test.ts +5 -5
- package/tests/{utils → unit/utils}/aiUser.test.ts +1 -1
- package/tests/{utils → unit/utils}/generateInviteCode.test.ts +1 -1
- package/tests/{utils → unit/utils}/inference.test.ts +6 -6
- package/tests/{utils → unit/utils}/logger.test.ts +1 -1
- package/tests/{utils → unit/utils}/prismaErrorHandler.test.ts +1 -1
- package/tests/{utils → unit/utils}/prismaWrapper.test.ts +1 -1
- package/vitest.config.ts +11 -3
- package/vitest.unit.config.ts +10 -10
- package/tests/routers/worksheet.test.ts +0 -194
- package/tests/startup.test.ts +0 -5
- package/tests/worksheet.test.ts +0 -181
- /package/tests/{routers → integration/routers}/agenda.test.ts +0 -0
- /package/tests/{routers → integration/routers}/announcement.test.ts +0 -0
- /package/tests/{routers → integration/routers}/assignment.test.ts +0 -0
- /package/tests/{routers → integration/routers}/comment.test.ts +0 -0
- /package/tests/{routers → integration/routers}/folder.test.ts +0 -0
- /package/tests/{routers → integration/routers}/labChat.test.ts +0 -0
- /package/tests/{routers → integration/routers}/marketing.test.ts +0 -0
- /package/tests/{routers → integration/routers}/notification.test.ts +0 -0
- /package/tests/{routers → integration/routers}/search.test.ts +0 -0
- /package/tests/{routers → integration/routers}/section.test.ts +0 -0
- /package/tests/{server → unit/middleware}/rateLimit.test.ts +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Username generator – produces playful adjective+noun+number combos like "greenegg6767".
|
|
3
|
+
* Pass an `isTaken` checker to retry until a unique username is found.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
!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]="bcb62540-cee1-519f-a4b2-9aaf05873b47")}catch(e){}}();
|
|
7
|
+
const ADJECTIVES = [
|
|
8
|
+
"green",
|
|
9
|
+
"blue",
|
|
10
|
+
"red",
|
|
11
|
+
"purple",
|
|
12
|
+
"yellow",
|
|
13
|
+
"orange",
|
|
14
|
+
"pink",
|
|
15
|
+
"silver",
|
|
16
|
+
"golden",
|
|
17
|
+
"fuzzy",
|
|
18
|
+
"happy",
|
|
19
|
+
"sleepy",
|
|
20
|
+
"sneaky",
|
|
21
|
+
"silly",
|
|
22
|
+
"brave",
|
|
23
|
+
"clever",
|
|
24
|
+
"lucky",
|
|
25
|
+
"mighty",
|
|
26
|
+
"quiet",
|
|
27
|
+
"quick",
|
|
28
|
+
"loud",
|
|
29
|
+
"tiny",
|
|
30
|
+
"giant",
|
|
31
|
+
"spicy",
|
|
32
|
+
"jolly",
|
|
33
|
+
"wild",
|
|
34
|
+
"shiny",
|
|
35
|
+
"cosmic",
|
|
36
|
+
"mystic",
|
|
37
|
+
"swift",
|
|
38
|
+
"lazy",
|
|
39
|
+
"grumpy",
|
|
40
|
+
"fluffy",
|
|
41
|
+
"zesty",
|
|
42
|
+
];
|
|
43
|
+
const NOUNS = [
|
|
44
|
+
"egg",
|
|
45
|
+
"cat",
|
|
46
|
+
"dog",
|
|
47
|
+
"fox",
|
|
48
|
+
"owl",
|
|
49
|
+
"bear",
|
|
50
|
+
"duck",
|
|
51
|
+
"frog",
|
|
52
|
+
"wolf",
|
|
53
|
+
"panda",
|
|
54
|
+
"tiger",
|
|
55
|
+
"otter",
|
|
56
|
+
"moose",
|
|
57
|
+
"shark",
|
|
58
|
+
"whale",
|
|
59
|
+
"raven",
|
|
60
|
+
"robin",
|
|
61
|
+
"koala",
|
|
62
|
+
"llama",
|
|
63
|
+
"sloth",
|
|
64
|
+
"narwhal",
|
|
65
|
+
"penguin",
|
|
66
|
+
"dragon",
|
|
67
|
+
"phoenix",
|
|
68
|
+
"muffin",
|
|
69
|
+
"pickle",
|
|
70
|
+
"noodle",
|
|
71
|
+
"potato",
|
|
72
|
+
"taco",
|
|
73
|
+
"pancake",
|
|
74
|
+
"waffle",
|
|
75
|
+
"cookie",
|
|
76
|
+
"biscuit",
|
|
77
|
+
"donut",
|
|
78
|
+
];
|
|
79
|
+
function pick(items) {
|
|
80
|
+
return items[Math.floor(Math.random() * items.length)];
|
|
81
|
+
}
|
|
82
|
+
function randomDigits(n) {
|
|
83
|
+
let out = "";
|
|
84
|
+
for (let i = 0; i < n; i += 1) {
|
|
85
|
+
out += Math.floor(Math.random() * 10).toString();
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
export function generateRandomUsername() {
|
|
90
|
+
return `${pick(ADJECTIVES)}${pick(NOUNS)}${randomDigits(4)}`;
|
|
91
|
+
}
|
|
92
|
+
/** Generate a unique username, calling `isTaken` until one is free. */
|
|
93
|
+
export async function generateUniqueUsername(isTaken, maxAttempts = 10) {
|
|
94
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
95
|
+
const candidate = generateRandomUsername();
|
|
96
|
+
if (!(await isTaken(candidate))) {
|
|
97
|
+
return candidate;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return `${generateRandomUsername()}${randomDigits(4)}`;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=generateUsername.js.map
|
|
103
|
+
//# debugId=bcb62540-cee1-519f-a4b2-9aaf05873b47
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateUsername.js","sources":["utils/generateUsername.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Username generator – produces playful adjective+noun+number combos like \"greenegg6767\".\n * Pass an `isTaken` checker to retry until a unique username is found.\n */\n\nconst ADJECTIVES = [\n \"green\",\n \"blue\",\n \"red\",\n \"purple\",\n \"yellow\",\n \"orange\",\n \"pink\",\n \"silver\",\n \"golden\",\n \"fuzzy\",\n \"happy\",\n \"sleepy\",\n \"sneaky\",\n \"silly\",\n \"brave\",\n \"clever\",\n \"lucky\",\n \"mighty\",\n \"quiet\",\n \"quick\",\n \"loud\",\n \"tiny\",\n \"giant\",\n \"spicy\",\n \"jolly\",\n \"wild\",\n \"shiny\",\n \"cosmic\",\n \"mystic\",\n \"swift\",\n \"lazy\",\n \"grumpy\",\n \"fluffy\",\n \"zesty\",\n];\n\nconst NOUNS = [\n \"egg\",\n \"cat\",\n \"dog\",\n \"fox\",\n \"owl\",\n \"bear\",\n \"duck\",\n \"frog\",\n \"wolf\",\n \"panda\",\n \"tiger\",\n \"otter\",\n \"moose\",\n \"shark\",\n \"whale\",\n \"raven\",\n \"robin\",\n \"koala\",\n \"llama\",\n \"sloth\",\n \"narwhal\",\n \"penguin\",\n \"dragon\",\n \"phoenix\",\n \"muffin\",\n \"pickle\",\n \"noodle\",\n \"potato\",\n \"taco\",\n \"pancake\",\n \"waffle\",\n \"cookie\",\n \"biscuit\",\n \"donut\",\n];\n\nfunction pick<T>(items: readonly T[]) {\n return items[Math.floor(Math.random() * items.length)]!;\n}\n\nfunction randomDigits(n: number) {\n let out = \"\";\n for (let i = 0; i < n; i += 1) {\n out += Math.floor(Math.random() * 10).toString();\n }\n return out;\n}\n\nexport function generateRandomUsername() {\n return `${pick(ADJECTIVES)}${pick(NOUNS)}${randomDigits(4)}`;\n}\n\n/** Generate a unique username, calling `isTaken` until one is free. */\nexport async function generateUniqueUsername(\n isTaken: (username: string) => Promise<boolean>,\n maxAttempts = 10\n) {\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n const candidate = generateRandomUsername();\n if (!(await isTaken(candidate))) {\n return candidate;\n }\n }\n return `${generateRandomUsername()}${randomDigits(4)}`;\n}\n"],"names":[],"mappings":"AAAA;;;GAGG;;;AAEH,MAAM,UAAU,GAAG;IACjB,OAAO;IACP,MAAM;IACN,KAAK;IACL,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,OAAO;CACR,CAAC;AAEF,MAAM,KAAK,GAAG;IACZ,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,SAAS;IACT,SAAS;IACT,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,OAAO;CACR,CAAC;AAEF,SAAS,IAAI,CAAI,KAAmB;IAClC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAE,CAAC;AAC1D,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/D,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,OAA+C,EAC/C,WAAW,GAAG,EAAE;IAEhB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,sBAAsB,EAAE,CAAC;QAC3C,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YAChC,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,sBAAsB,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;AACzD,CAAC","debug_id":"bcb62540-cee1-519f-a4b2-9aaf05873b47"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import OpenAI from 'openai';
|
|
5
5
|
import type { ResponseInput } from "openai/resources/responses/responses.js";
|
|
6
|
-
import { Prisma } from '@prisma/client';
|
|
6
|
+
import { JobStatus, Prisma } from '@prisma/client';
|
|
7
7
|
import { ZodSchema } from 'zod';
|
|
8
8
|
export declare const inferenceClient: OpenAI;
|
|
9
9
|
export declare const openAIClient: OpenAI;
|
|
@@ -55,6 +55,54 @@ export declare function sendAIMessage(content: string, conversationId: string, o
|
|
|
55
55
|
createdAt: Date;
|
|
56
56
|
meta?: Record<string, any>;
|
|
57
57
|
}>;
|
|
58
|
+
interface AIMessageSenderInfo {
|
|
59
|
+
displayName?: string;
|
|
60
|
+
profilePicture?: string | null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create an AI message placeholder in PENDING state and broadcast it.
|
|
64
|
+
*
|
|
65
|
+
* The AI message lifecycle == job lifecycle: this row is the bubble the
|
|
66
|
+
* client renders for "AI is thinking..." and is later flipped by
|
|
67
|
+
* {@link completePendingAIMessage} / {@link failPendingAIMessage}.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createPendingAIMessage(conversationId: string, options?: {
|
|
70
|
+
customSender?: AIMessageSenderInfo;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
id: string;
|
|
73
|
+
conversationId: string;
|
|
74
|
+
createdAt: Date;
|
|
75
|
+
senderId: string;
|
|
76
|
+
status: JobStatus;
|
|
77
|
+
}>;
|
|
78
|
+
/**
|
|
79
|
+
* Fill a PENDING AI message with its generated content and flip it to
|
|
80
|
+
* COMPLETED. Broadcasts a `message-updated` event so clients that already
|
|
81
|
+
* rendered the placeholder can patch it in place.
|
|
82
|
+
*/
|
|
83
|
+
export declare function completePendingAIMessage(messageId: string, content: string, options?: {
|
|
84
|
+
attachments?: {
|
|
85
|
+
connect: {
|
|
86
|
+
id: string;
|
|
87
|
+
}[];
|
|
88
|
+
};
|
|
89
|
+
meta?: Record<string, any>;
|
|
90
|
+
customSender?: AIMessageSenderInfo;
|
|
91
|
+
}): Promise<{
|
|
92
|
+
id: string;
|
|
93
|
+
content: string;
|
|
94
|
+
conversationId: string;
|
|
95
|
+
createdAt: Date;
|
|
96
|
+
status: JobStatus;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* Flip a PENDING AI message to FAILED and broadcast the status change.
|
|
100
|
+
* Does NOT throw — failure paths in the pipelines call this in catch blocks
|
|
101
|
+
* and we do not want broadcast/DB errors to mask the original error.
|
|
102
|
+
*/
|
|
103
|
+
export declare function failPendingAIMessage(messageId: string, options?: {
|
|
104
|
+
reason?: string;
|
|
105
|
+
}): Promise<void>;
|
|
58
106
|
export declare function inference<T>(content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[] | ResponseInput, format?: ZodSchema, options?: InferenceOptions): Promise<T>;
|
|
59
107
|
/**
|
|
60
108
|
* Get available inference models (for admin/config purposes)
|
|
@@ -64,4 +112,5 @@ export declare function getAvailableModels(): Promise<string[]>;
|
|
|
64
112
|
* Estimate token count for a message (rough approximation)
|
|
65
113
|
*/
|
|
66
114
|
export declare function estimateTokenCount(text: string): number;
|
|
115
|
+
export {};
|
|
67
116
|
//# sourceMappingURL=inference.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inference.d.ts","sourceRoot":"/","sources":["utils/inference.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAM7E,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"inference.d.ts","sourceRoot":"/","sources":["utils/inference.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/inference.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
* Inference util – OpenAI client, inference() for structured output, sendAIMessage for chat.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
!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]="
|
|
5
|
+
!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]="7fa949f6-b6c7-5f82-bc61-5de02fa8f775")}catch(e){}}();
|
|
6
6
|
import OpenAI from 'openai';
|
|
7
7
|
import { logger } from './logger.js';
|
|
8
8
|
import { prisma } from '../lib/prisma.js';
|
|
9
9
|
import { chatChannel, pusher } from '../lib/pusher.js';
|
|
10
10
|
import { ensureAIUserExists, getAIUserId } from './aiUser.js';
|
|
11
11
|
import { env } from '../lib/config/env.js';
|
|
12
|
+
import { JobStatus } from '@prisma/client';
|
|
12
13
|
import { zodTextFormat } from 'openai/helpers/zod';
|
|
13
14
|
export const inferenceClient = new OpenAI({
|
|
14
15
|
apiKey: env.INFERENCE_API_KEY,
|
|
@@ -163,6 +164,170 @@ export async function sendAIMessage(content, conversationId, options = {}) {
|
|
|
163
164
|
meta: aiMessage.meta,
|
|
164
165
|
};
|
|
165
166
|
}
|
|
167
|
+
function buildAISenderInfo(customSender) {
|
|
168
|
+
return {
|
|
169
|
+
id: getAIUserId(),
|
|
170
|
+
username: 'Newton_AI',
|
|
171
|
+
profile: {
|
|
172
|
+
displayName: customSender?.displayName ?? 'Newton AI',
|
|
173
|
+
profilePicture: customSender?.profilePicture ?? null,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Create an AI message placeholder in PENDING state and broadcast it.
|
|
179
|
+
*
|
|
180
|
+
* The AI message lifecycle == job lifecycle: this row is the bubble the
|
|
181
|
+
* client renders for "AI is thinking..." and is later flipped by
|
|
182
|
+
* {@link completePendingAIMessage} / {@link failPendingAIMessage}.
|
|
183
|
+
*/
|
|
184
|
+
export async function createPendingAIMessage(conversationId, options = {}) {
|
|
185
|
+
await ensureAIUserExists();
|
|
186
|
+
const aiMessage = await prisma.message.create({
|
|
187
|
+
data: {
|
|
188
|
+
content: '',
|
|
189
|
+
senderId: getAIUserId(),
|
|
190
|
+
conversationId,
|
|
191
|
+
status: JobStatus.PENDING,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
logger.info('AI placeholder message created', {
|
|
195
|
+
messageId: aiMessage.id,
|
|
196
|
+
conversationId,
|
|
197
|
+
});
|
|
198
|
+
const senderInfo = buildAISenderInfo(options.customSender);
|
|
199
|
+
try {
|
|
200
|
+
await pusher.trigger(chatChannel(conversationId), 'new-message', {
|
|
201
|
+
id: aiMessage.id,
|
|
202
|
+
content: '',
|
|
203
|
+
senderId: getAIUserId(),
|
|
204
|
+
conversationId: aiMessage.conversationId,
|
|
205
|
+
status: JobStatus.PENDING,
|
|
206
|
+
createdAt: aiMessage.createdAt,
|
|
207
|
+
sender: senderInfo,
|
|
208
|
+
mentionedUserIds: [],
|
|
209
|
+
meta: {},
|
|
210
|
+
attachments: [],
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
logger.error('Failed to broadcast pending AI message:', {
|
|
215
|
+
error,
|
|
216
|
+
messageId: aiMessage.id,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
id: aiMessage.id,
|
|
221
|
+
conversationId: aiMessage.conversationId,
|
|
222
|
+
createdAt: aiMessage.createdAt,
|
|
223
|
+
senderId: aiMessage.senderId,
|
|
224
|
+
status: JobStatus.PENDING,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Fill a PENDING AI message with its generated content and flip it to
|
|
229
|
+
* COMPLETED. Broadcasts a `message-updated` event so clients that already
|
|
230
|
+
* rendered the placeholder can patch it in place.
|
|
231
|
+
*/
|
|
232
|
+
export async function completePendingAIMessage(messageId, content, options = {}) {
|
|
233
|
+
const updated = await prisma.message.update({
|
|
234
|
+
where: { id: messageId },
|
|
235
|
+
data: {
|
|
236
|
+
content,
|
|
237
|
+
status: JobStatus.COMPLETED,
|
|
238
|
+
...(options.attachments && {
|
|
239
|
+
attachments: {
|
|
240
|
+
connect: options.attachments.connect,
|
|
241
|
+
},
|
|
242
|
+
}),
|
|
243
|
+
...(options.meta !== undefined && {
|
|
244
|
+
meta: options.meta,
|
|
245
|
+
}),
|
|
246
|
+
},
|
|
247
|
+
include: {
|
|
248
|
+
attachments: true,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
logger.info('AI message completed', {
|
|
252
|
+
messageId: updated.id,
|
|
253
|
+
conversationId: updated.conversationId,
|
|
254
|
+
contentLength: content.length,
|
|
255
|
+
});
|
|
256
|
+
const senderInfo = buildAISenderInfo(options.customSender);
|
|
257
|
+
try {
|
|
258
|
+
await pusher.trigger(chatChannel(updated.conversationId), 'message-updated', {
|
|
259
|
+
id: updated.id,
|
|
260
|
+
content: updated.content,
|
|
261
|
+
senderId: updated.senderId,
|
|
262
|
+
conversationId: updated.conversationId,
|
|
263
|
+
status: updated.status,
|
|
264
|
+
createdAt: updated.createdAt,
|
|
265
|
+
sender: senderInfo,
|
|
266
|
+
mentionedUserIds: [],
|
|
267
|
+
meta: updated.meta ?? {},
|
|
268
|
+
attachments: updated.attachments.map((attachment) => ({
|
|
269
|
+
id: attachment.id,
|
|
270
|
+
attachmentId: attachment.id,
|
|
271
|
+
name: attachment.name,
|
|
272
|
+
type: attachment.type,
|
|
273
|
+
size: attachment.size,
|
|
274
|
+
path: attachment.path,
|
|
275
|
+
})),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
logger.error('Failed to broadcast completed AI message:', {
|
|
280
|
+
error,
|
|
281
|
+
messageId,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
id: updated.id,
|
|
286
|
+
content: updated.content,
|
|
287
|
+
conversationId: updated.conversationId,
|
|
288
|
+
createdAt: updated.createdAt,
|
|
289
|
+
status: (updated.status ?? JobStatus.COMPLETED),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Flip a PENDING AI message to FAILED and broadcast the status change.
|
|
294
|
+
* Does NOT throw — failure paths in the pipelines call this in catch blocks
|
|
295
|
+
* and we do not want broadcast/DB errors to mask the original error.
|
|
296
|
+
*/
|
|
297
|
+
export async function failPendingAIMessage(messageId, options = {}) {
|
|
298
|
+
try {
|
|
299
|
+
const updated = await prisma.message.update({
|
|
300
|
+
where: { id: messageId },
|
|
301
|
+
data: { status: JobStatus.FAILED },
|
|
302
|
+
select: { id: true, conversationId: true, status: true },
|
|
303
|
+
});
|
|
304
|
+
logger.info('AI message marked as failed', {
|
|
305
|
+
messageId: updated.id,
|
|
306
|
+
conversationId: updated.conversationId,
|
|
307
|
+
reason: options.reason,
|
|
308
|
+
});
|
|
309
|
+
try {
|
|
310
|
+
await pusher.trigger(chatChannel(updated.conversationId), 'message-updated', {
|
|
311
|
+
id: updated.id,
|
|
312
|
+
conversationId: updated.conversationId,
|
|
313
|
+
status: updated.status,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
catch (broadcastError) {
|
|
317
|
+
logger.error('Failed to broadcast failed AI message:', {
|
|
318
|
+
error: broadcastError,
|
|
319
|
+
messageId,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
logger.error('Failed to mark AI message as failed:', {
|
|
325
|
+
error,
|
|
326
|
+
messageId,
|
|
327
|
+
reason: options.reason,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
166
331
|
/** Responses API user parts use `type: "input_*"`; chat completions use `text` / `image_url` / etc. */
|
|
167
332
|
function isResponseInputWithInputParts(content) {
|
|
168
333
|
if (!Array.isArray(content))
|
|
@@ -304,4 +469,4 @@ export function estimateTokenCount(text) {
|
|
|
304
469
|
return Math.ceil(text.length / 4);
|
|
305
470
|
}
|
|
306
471
|
//# sourceMappingURL=inference.js.map
|
|
307
|
-
//# debugId=
|
|
472
|
+
//# debugId=7fa949f6-b6c7-5f82-bc61-5de02fa8f775
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inference.js","sources":["utils/inference.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Inference util – OpenAI client, inference() for structured output, sendAIMessage for chat.\n */\nimport OpenAI from 'openai';\nimport type { ResponseInput } from \"openai/resources/responses/responses.js\";\nimport { logger } from './logger.js';\nimport { prisma } from '../lib/prisma.js';\nimport { chatChannel, pusher } from '../lib/pusher.js';\nimport { ensureAIUserExists, getAIUserId } from './aiUser.js';\nimport { env } from '../lib/config/env.js';\nimport { Prisma } from '@prisma/client';\nimport { ZodSchema } from 'zod';\nimport { zodTextFormat } from 'openai/helpers/zod';\n\nexport const inferenceClient = new OpenAI({\n apiKey: env.INFERENCE_API_KEY,\n baseURL: env.INFERENCE_API_BASE_URL,\n});\n\nexport const openAIClient = new OpenAI();\n\nconst DEFAULT_INFERENCE_MODEL = 'gpt-5-nano';\n\ntype InferenceMessage = {\n role: 'user' | 'assistant' | 'system';\n content: string;\n};\n\nexport interface InferenceOptions {\n reason?: string;\n metadata?: Prisma.InputJsonValue;\n model?: string;\n temperature?: number;\n maxTokens?: number;\n}\n\ninterface InferenceUsage {\n promptTokens?: number;\n completionTokens?: number;\n totalTokens?: number;\n}\n\ninterface InferenceAuditLog {\n model: string;\n operation: 'chat.completions' | 'responses.parse';\n success: boolean;\n estimatedInputTokens: number;\n reason: string;\n metadata?: Prisma.InputJsonValue;\n usage?: InferenceUsage;\n error?: unknown;\n}\n\n// Types for lab chat context\nexport interface LabChatContext {\n subject: string;\n topic: string;\n difficulty: 'beginner' | 'intermediate' | 'advanced';\n objectives: string[];\n resources?: string[];\n persona: string;\n constraints: string[];\n examples?: any[];\n metadata?: Record<string, any>;\n}\n\nexport interface InferenceResponse {\n content: string;\n model: string;\n tokensUsed: number;\n finishReason: string;\n}\n\nfunction normalizeInferenceInput(\n content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[],\n): InferenceMessage[] {\n if (typeof content === 'string') {\n return [{ role: 'user', content }];\n }\n\n return content.map((message) => ({\n role: message.role as InferenceMessage['role'],\n content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content),\n }));\n}\n\nfunction getInferenceReason(options?: InferenceOptions) {\n return options?.reason?.trim() || 'unspecified';\n}\n\nfunction serializeInferenceError(error: unknown) {\n if (error instanceof Error) {\n return error.message;\n }\n\n if (typeof error === 'string') {\n return error;\n }\n\n try {\n return JSON.stringify(error);\n } catch {\n return 'Unknown inference error';\n }\n}\n\nasync function logInferenceUsage({\n model,\n operation,\n success,\n estimatedInputTokens,\n reason,\n metadata,\n usage,\n error,\n}: InferenceAuditLog) {\n const context = {\n reason,\n model,\n operation,\n success,\n estimatedInputTokens,\n promptTokens: usage?.promptTokens,\n completionTokens: usage?.completionTokens,\n totalTokens: usage?.totalTokens,\n metadata,\n ...(error ? { error: serializeInferenceError(error) } : {}),\n };\n\n try {\n await prisma.aiUsageLog.create({\n data: {\n reason,\n model,\n operation,\n success,\n estimatedInputTokens,\n promptTokens: usage?.promptTokens,\n completionTokens: usage?.completionTokens,\n totalTokens: usage?.totalTokens,\n ...(metadata !== undefined ? { metadata } : {}),\n ...(error ? { error: serializeInferenceError(error) } : {}),\n },\n });\n } catch (loggingError) {\n logger.error('Failed to persist AI usage log', {\n ...context,\n loggingError: serializeInferenceError(loggingError),\n });\n return;\n }\n\n logger.info('AI usage logged', context);\n}\n\nfunction getChatCompletionUsage(completion: {\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n };\n}): InferenceUsage {\n return {\n promptTokens: completion.usage?.prompt_tokens,\n completionTokens: completion.usage?.completion_tokens,\n totalTokens: completion.usage?.total_tokens,\n };\n}\n\nfunction getResponseUsage(completion: {\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n };\n}): InferenceUsage {\n return {\n promptTokens: completion.usage?.input_tokens,\n completionTokens: completion.usage?.output_tokens,\n totalTokens: completion.usage?.total_tokens,\n };\n}\n\n/**\n * Centralized function to send AI messages to conversations\n * Handles database storage and Pusher broadcasting\n */\nexport async function sendAIMessage(\n content: string,\n conversationId: string,\n options: {\n subject?: string;\n attachments?: {\n connect: { id: string }[];\n };\n meta?: Record<string, any>;\n customSender?: {\n displayName: string;\n profilePicture?: string | null;\n };\n } = {}\n): Promise<{\n id: string;\n content: string;\n senderId: string;\n conversationId: string;\n createdAt: Date;\n meta?: Record<string, any>;\n}> {\n await ensureAIUserExists();\n\n const aiMessage = await prisma.message.create({\n data: {\n content,\n senderId: getAIUserId(),\n conversationId,\n ...(options.attachments && {\n attachments: {\n connect: options.attachments.connect,\n },\n }),\n ...(options.meta && {\n meta: options.meta,\n }),\n },\n include: {\n attachments: true,\n },\n });\n\n logger.info('AI Message sent', {\n messageId: aiMessage.id,\n conversationId,\n contentLength: content.length,\n });\n\n const senderInfo = {\n id: getAIUserId(),\n username: 'Newton_AI',\n profile: {\n displayName: 'Newton AI',\n profilePicture: options.customSender?.profilePicture || null,\n },\n };\n\n try {\n await pusher.trigger(chatChannel(conversationId), 'new-message', {\n id: aiMessage.id,\n content: aiMessage.content,\n senderId: getAIUserId(),\n conversationId: aiMessage.conversationId,\n createdAt: aiMessage.createdAt,\n sender: senderInfo,\n mentionedUserIds: [],\n meta: aiMessage.meta,\n attachments: aiMessage.attachments.map((attachment) => ({\n id: attachment.id,\n attachmentId: attachment.id,\n name: attachment.name,\n type: attachment.type,\n size: attachment.size,\n path: attachment.path,\n })),\n });\n } catch (error) {\n logger.error('Failed to broadcast AI message:', { error, messageId: aiMessage.id });\n }\n\n return {\n id: aiMessage.id,\n content: aiMessage.content,\n senderId: getAIUserId(),\n conversationId: aiMessage.conversationId,\n createdAt: aiMessage.createdAt,\n meta: aiMessage.meta as Record<string, any>,\n };\n}\n\n/** Responses API user parts use `type: \"input_*\"`; chat completions use `text` / `image_url` / etc. */\nfunction isResponseInputWithInputParts(\n content: OpenAI.Chat.Completions.ChatCompletionMessageParam[] | ResponseInput\n): content is ResponseInput {\n if (!Array.isArray(content)) return false;\n for (const msg of content) {\n if (!msg || typeof msg !== \"object\" || !(\"content\" in msg)) continue;\n const parts = (msg as { content?: unknown }).content;\n if (!Array.isArray(parts)) continue;\n for (const part of parts) {\n if (\n part &&\n typeof part === \"object\" &&\n \"type\" in part &&\n typeof (part as { type: string }).type === \"string\" &&\n (part as { type: string }).type.startsWith(\"input_\")\n ) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction buildResponsesParseInput(\n content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[] | ResponseInput\n): ResponseInput {\n if (typeof content === \"string\") {\n return [{ role: \"user\", content: [{ type: \"input_text\", text: content }] }];\n }\n if (isResponseInputWithInputParts(content)) {\n return content;\n }\n return content as unknown as ResponseInput;\n}\n\nexport async function inference<T>(\n content:\n | string\n | OpenAI.Chat.Completions.ChatCompletionMessageParam[]\n | ResponseInput,\n format?: ZodSchema,\n options?: InferenceOptions,\n): Promise<T> {\n const reason = getInferenceReason(options);\n const model = options?.model ?? DEFAULT_INFERENCE_MODEL;\n const estimatedInputTokens =\n typeof content === 'string'\n ? estimateTokenCount(content)\n : isResponseInputWithInputParts(content)\n ? estimateTokenCount(\n content\n .flatMap((item) =>\n typeof (item as any)?.content === 'string'\n ? [(item as any).content]\n : Array.isArray((item as any)?.content)\n ? (item as any).content\n .filter((p: any) => p?.type === 'input_text')\n .map((p: any) => p?.text ?? '')\n : [],\n )\n .join('\\n'),\n )\n : (content as OpenAI.Chat.Completions.ChatCompletionMessageParam[]).reduce(\n (total, message) =>\n total +\n estimateTokenCount(\n typeof message.content === 'string' ? message.content : '',\n ),\n 0,\n );\n const operation = format ? 'responses.parse' : 'chat.completions';\n\n try {\n if (!format) {\n if (typeof content !== 'string' && isResponseInputWithInputParts(content)) {\n throw new Error(\n 'normalizeInferenceInput cannot handle ResponseInput content with input_* parts when format is undefined; pass a format to use responses.parse.',\n );\n }\n\n const messages = normalizeInferenceInput(\n content as string | OpenAI.Chat.Completions.ChatCompletionMessageParam[],\n );\n const completion = await openAIClient.chat.completions.create({\n model,\n messages,\n ...(options?.temperature !== undefined\n ? { temperature: options.temperature }\n : {}),\n ...(options?.maxTokens !== undefined\n ? { max_tokens: options.maxTokens }\n : {}),\n });\n\n await logInferenceUsage({\n model,\n operation,\n success: true,\n estimatedInputTokens,\n reason,\n metadata: options?.metadata,\n usage: getChatCompletionUsage(completion),\n });\n\n return completion.choices[0]?.message?.content as T;\n }\n\n const input = buildResponsesParseInput(content);\n const completion = await openAIClient.responses.parse({\n model,\n input,\n text: {\n format: zodTextFormat(format, 'newton_response_format'),\n },\n });\n\n if (!completion) {\n throw new Error('No response generated from inference API');\n }\n\n await logInferenceUsage({\n model,\n operation,\n success: true,\n estimatedInputTokens,\n reason,\n metadata: options?.metadata,\n usage: getResponseUsage(completion),\n });\n\n return completion.output_parsed as T;\n } catch (error) {\n await logInferenceUsage({\n model,\n operation,\n success: false,\n estimatedInputTokens,\n reason,\n metadata: options?.metadata,\n error,\n });\n\n logger.error('Failed to generate inference response', {\n error,\n reason,\n model,\n operation,\n metadata: options?.metadata,\n });\n throw error;\n }\n}\n\n/**\n * Get available inference models (for admin/config purposes)\n */\nexport async function getAvailableModels(): Promise<string[]> {\n try {\n const models = await inferenceClient.models.list();\n return models.data\n .filter(model => model.id.includes('command'))\n .map(model => model.id)\n .sort();\n } catch (error) {\n logger.error('Failed to fetch inference models', { error });\n return ['command-r-plus', 'command-r', 'command-light'];\n }\n}\n\n/**\n * Estimate token count for a message (rough approximation)\n */\nexport function estimateTokenCount(text: string): number {\n return Math.ceil(text.length / 4);\n}\n"],"names":[],"mappings":"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","debug_id":"621b1d59-2865-5a51-8bea-88c27023731f"}
|
|
1
|
+
{"version":3,"file":"inference.js","sources":["utils/inference.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Inference util – OpenAI client, inference() for structured output, sendAIMessage for chat.\n */\nimport OpenAI from 'openai';\nimport type { ResponseInput } from \"openai/resources/responses/responses.js\";\nimport { logger } from './logger.js';\nimport { prisma } from '../lib/prisma.js';\nimport { chatChannel, pusher } from '../lib/pusher.js';\nimport { ensureAIUserExists, getAIUserId } from './aiUser.js';\nimport { env } from '../lib/config/env.js';\nimport { JobStatus, Prisma } from '@prisma/client';\nimport { ZodSchema } from 'zod';\nimport { zodTextFormat } from 'openai/helpers/zod';\n\nexport const inferenceClient = new OpenAI({\n apiKey: env.INFERENCE_API_KEY,\n baseURL: env.INFERENCE_API_BASE_URL,\n});\n\nexport const openAIClient = new OpenAI();\n\nconst DEFAULT_INFERENCE_MODEL = 'gpt-5-nano';\n\ntype InferenceMessage = {\n role: 'user' | 'assistant' | 'system';\n content: string;\n};\n\nexport interface InferenceOptions {\n reason?: string;\n metadata?: Prisma.InputJsonValue;\n model?: string;\n temperature?: number;\n maxTokens?: number;\n}\n\ninterface InferenceUsage {\n promptTokens?: number;\n completionTokens?: number;\n totalTokens?: number;\n}\n\ninterface InferenceAuditLog {\n model: string;\n operation: 'chat.completions' | 'responses.parse';\n success: boolean;\n estimatedInputTokens: number;\n reason: string;\n metadata?: Prisma.InputJsonValue;\n usage?: InferenceUsage;\n error?: unknown;\n}\n\n// Types for lab chat context\nexport interface LabChatContext {\n subject: string;\n topic: string;\n difficulty: 'beginner' | 'intermediate' | 'advanced';\n objectives: string[];\n resources?: string[];\n persona: string;\n constraints: string[];\n examples?: any[];\n metadata?: Record<string, any>;\n}\n\nexport interface InferenceResponse {\n content: string;\n model: string;\n tokensUsed: number;\n finishReason: string;\n}\n\nfunction normalizeInferenceInput(\n content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[],\n): InferenceMessage[] {\n if (typeof content === 'string') {\n return [{ role: 'user', content }];\n }\n\n return content.map((message) => ({\n role: message.role as InferenceMessage['role'],\n content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content),\n }));\n}\n\nfunction getInferenceReason(options?: InferenceOptions) {\n return options?.reason?.trim() || 'unspecified';\n}\n\nfunction serializeInferenceError(error: unknown) {\n if (error instanceof Error) {\n return error.message;\n }\n\n if (typeof error === 'string') {\n return error;\n }\n\n try {\n return JSON.stringify(error);\n } catch {\n return 'Unknown inference error';\n }\n}\n\nasync function logInferenceUsage({\n model,\n operation,\n success,\n estimatedInputTokens,\n reason,\n metadata,\n usage,\n error,\n}: InferenceAuditLog) {\n const context = {\n reason,\n model,\n operation,\n success,\n estimatedInputTokens,\n promptTokens: usage?.promptTokens,\n completionTokens: usage?.completionTokens,\n totalTokens: usage?.totalTokens,\n metadata,\n ...(error ? { error: serializeInferenceError(error) } : {}),\n };\n\n try {\n await prisma.aiUsageLog.create({\n data: {\n reason,\n model,\n operation,\n success,\n estimatedInputTokens,\n promptTokens: usage?.promptTokens,\n completionTokens: usage?.completionTokens,\n totalTokens: usage?.totalTokens,\n ...(metadata !== undefined ? { metadata } : {}),\n ...(error ? { error: serializeInferenceError(error) } : {}),\n },\n });\n } catch (loggingError) {\n logger.error('Failed to persist AI usage log', {\n ...context,\n loggingError: serializeInferenceError(loggingError),\n });\n return;\n }\n\n logger.info('AI usage logged', context);\n}\n\nfunction getChatCompletionUsage(completion: {\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n };\n}): InferenceUsage {\n return {\n promptTokens: completion.usage?.prompt_tokens,\n completionTokens: completion.usage?.completion_tokens,\n totalTokens: completion.usage?.total_tokens,\n };\n}\n\nfunction getResponseUsage(completion: {\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n };\n}): InferenceUsage {\n return {\n promptTokens: completion.usage?.input_tokens,\n completionTokens: completion.usage?.output_tokens,\n totalTokens: completion.usage?.total_tokens,\n };\n}\n\n/**\n * Centralized function to send AI messages to conversations\n * Handles database storage and Pusher broadcasting\n */\nexport async function sendAIMessage(\n content: string,\n conversationId: string,\n options: {\n subject?: string;\n attachments?: {\n connect: { id: string }[];\n };\n meta?: Record<string, any>;\n customSender?: {\n displayName: string;\n profilePicture?: string | null;\n };\n } = {}\n): Promise<{\n id: string;\n content: string;\n senderId: string;\n conversationId: string;\n createdAt: Date;\n meta?: Record<string, any>;\n}> {\n await ensureAIUserExists();\n\n const aiMessage = await prisma.message.create({\n data: {\n content,\n senderId: getAIUserId(),\n conversationId,\n ...(options.attachments && {\n attachments: {\n connect: options.attachments.connect,\n },\n }),\n ...(options.meta && {\n meta: options.meta,\n }),\n },\n include: {\n attachments: true,\n },\n });\n\n logger.info('AI Message sent', {\n messageId: aiMessage.id,\n conversationId,\n contentLength: content.length,\n });\n\n const senderInfo = {\n id: getAIUserId(),\n username: 'Newton_AI',\n profile: {\n displayName: 'Newton AI',\n profilePicture: options.customSender?.profilePicture || null,\n },\n };\n\n try {\n await pusher.trigger(chatChannel(conversationId), 'new-message', {\n id: aiMessage.id,\n content: aiMessage.content,\n senderId: getAIUserId(),\n conversationId: aiMessage.conversationId,\n createdAt: aiMessage.createdAt,\n sender: senderInfo,\n mentionedUserIds: [],\n meta: aiMessage.meta,\n attachments: aiMessage.attachments.map((attachment) => ({\n id: attachment.id,\n attachmentId: attachment.id,\n name: attachment.name,\n type: attachment.type,\n size: attachment.size,\n path: attachment.path,\n })),\n });\n } catch (error) {\n logger.error('Failed to broadcast AI message:', { error, messageId: aiMessage.id });\n }\n\n return {\n id: aiMessage.id,\n content: aiMessage.content,\n senderId: getAIUserId(),\n conversationId: aiMessage.conversationId,\n createdAt: aiMessage.createdAt,\n meta: aiMessage.meta as Record<string, any>,\n };\n}\n\ninterface AIMessageSenderInfo {\n displayName?: string;\n profilePicture?: string | null;\n}\n\nfunction buildAISenderInfo(customSender?: AIMessageSenderInfo) {\n return {\n id: getAIUserId(),\n username: 'Newton_AI',\n profile: {\n displayName: customSender?.displayName ?? 'Newton AI',\n profilePicture: customSender?.profilePicture ?? null,\n },\n };\n}\n\n/**\n * Create an AI message placeholder in PENDING state and broadcast it.\n *\n * The AI message lifecycle == job lifecycle: this row is the bubble the\n * client renders for \"AI is thinking...\" and is later flipped by\n * {@link completePendingAIMessage} / {@link failPendingAIMessage}.\n */\nexport async function createPendingAIMessage(\n conversationId: string,\n options: {\n customSender?: AIMessageSenderInfo;\n } = {},\n): Promise<{\n id: string;\n conversationId: string;\n createdAt: Date;\n senderId: string;\n status: JobStatus;\n}> {\n await ensureAIUserExists();\n\n const aiMessage = await prisma.message.create({\n data: {\n content: '',\n senderId: getAIUserId(),\n conversationId,\n status: JobStatus.PENDING,\n },\n });\n\n logger.info('AI placeholder message created', {\n messageId: aiMessage.id,\n conversationId,\n });\n\n const senderInfo = buildAISenderInfo(options.customSender);\n\n try {\n await pusher.trigger(chatChannel(conversationId), 'new-message', {\n id: aiMessage.id,\n content: '',\n senderId: getAIUserId(),\n conversationId: aiMessage.conversationId,\n status: JobStatus.PENDING,\n createdAt: aiMessage.createdAt,\n sender: senderInfo,\n mentionedUserIds: [],\n meta: {},\n attachments: [],\n });\n } catch (error) {\n logger.error('Failed to broadcast pending AI message:', {\n error,\n messageId: aiMessage.id,\n });\n }\n\n return {\n id: aiMessage.id,\n conversationId: aiMessage.conversationId,\n createdAt: aiMessage.createdAt,\n senderId: aiMessage.senderId,\n status: JobStatus.PENDING,\n };\n}\n\n/**\n * Fill a PENDING AI message with its generated content and flip it to\n * COMPLETED. Broadcasts a `message-updated` event so clients that already\n * rendered the placeholder can patch it in place.\n */\nexport async function completePendingAIMessage(\n messageId: string,\n content: string,\n options: {\n attachments?: { connect: { id: string }[] };\n meta?: Record<string, any>;\n customSender?: AIMessageSenderInfo;\n } = {},\n): Promise<{\n id: string;\n content: string;\n conversationId: string;\n createdAt: Date;\n status: JobStatus;\n}> {\n const updated = await prisma.message.update({\n where: { id: messageId },\n data: {\n content,\n status: JobStatus.COMPLETED,\n ...(options.attachments && {\n attachments: {\n connect: options.attachments.connect,\n },\n }),\n ...(options.meta !== undefined && {\n meta: options.meta as Prisma.InputJsonValue,\n }),\n },\n include: {\n attachments: true,\n },\n });\n\n logger.info('AI message completed', {\n messageId: updated.id,\n conversationId: updated.conversationId,\n contentLength: content.length,\n });\n\n const senderInfo = buildAISenderInfo(options.customSender);\n\n try {\n await pusher.trigger(chatChannel(updated.conversationId), 'message-updated', {\n id: updated.id,\n content: updated.content,\n senderId: updated.senderId,\n conversationId: updated.conversationId,\n status: updated.status,\n createdAt: updated.createdAt,\n sender: senderInfo,\n mentionedUserIds: [],\n meta: updated.meta ?? {},\n attachments: updated.attachments.map((attachment) => ({\n id: attachment.id,\n attachmentId: attachment.id,\n name: attachment.name,\n type: attachment.type,\n size: attachment.size,\n path: attachment.path,\n })),\n });\n } catch (error) {\n logger.error('Failed to broadcast completed AI message:', {\n error,\n messageId,\n });\n }\n\n return {\n id: updated.id,\n content: updated.content,\n conversationId: updated.conversationId,\n createdAt: updated.createdAt,\n status: (updated.status ?? JobStatus.COMPLETED) as JobStatus,\n };\n}\n\n/**\n * Flip a PENDING AI message to FAILED and broadcast the status change.\n * Does NOT throw — failure paths in the pipelines call this in catch blocks\n * and we do not want broadcast/DB errors to mask the original error.\n */\nexport async function failPendingAIMessage(\n messageId: string,\n options: { reason?: string } = {},\n): Promise<void> {\n try {\n const updated = await prisma.message.update({\n where: { id: messageId },\n data: { status: JobStatus.FAILED },\n select: { id: true, conversationId: true, status: true },\n });\n\n logger.info('AI message marked as failed', {\n messageId: updated.id,\n conversationId: updated.conversationId,\n reason: options.reason,\n });\n\n try {\n await pusher.trigger(chatChannel(updated.conversationId), 'message-updated', {\n id: updated.id,\n conversationId: updated.conversationId,\n status: updated.status,\n });\n } catch (broadcastError) {\n logger.error('Failed to broadcast failed AI message:', {\n error: broadcastError,\n messageId,\n });\n }\n } catch (error) {\n logger.error('Failed to mark AI message as failed:', {\n error,\n messageId,\n reason: options.reason,\n });\n }\n}\n\n/** Responses API user parts use `type: \"input_*\"`; chat completions use `text` / `image_url` / etc. */\nfunction isResponseInputWithInputParts(\n content: OpenAI.Chat.Completions.ChatCompletionMessageParam[] | ResponseInput\n): content is ResponseInput {\n if (!Array.isArray(content)) return false;\n for (const msg of content) {\n if (!msg || typeof msg !== \"object\" || !(\"content\" in msg)) continue;\n const parts = (msg as { content?: unknown }).content;\n if (!Array.isArray(parts)) continue;\n for (const part of parts) {\n if (\n part &&\n typeof part === \"object\" &&\n \"type\" in part &&\n typeof (part as { type: string }).type === \"string\" &&\n (part as { type: string }).type.startsWith(\"input_\")\n ) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction buildResponsesParseInput(\n content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[] | ResponseInput\n): ResponseInput {\n if (typeof content === \"string\") {\n return [{ role: \"user\", content: [{ type: \"input_text\", text: content }] }];\n }\n if (isResponseInputWithInputParts(content)) {\n return content;\n }\n return content as unknown as ResponseInput;\n}\n\nexport async function inference<T>(\n content:\n | string\n | OpenAI.Chat.Completions.ChatCompletionMessageParam[]\n | ResponseInput,\n format?: ZodSchema,\n options?: InferenceOptions,\n): Promise<T> {\n const reason = getInferenceReason(options);\n const model = options?.model ?? DEFAULT_INFERENCE_MODEL;\n const estimatedInputTokens =\n typeof content === 'string'\n ? estimateTokenCount(content)\n : isResponseInputWithInputParts(content)\n ? estimateTokenCount(\n content\n .flatMap((item) =>\n typeof (item as any)?.content === 'string'\n ? [(item as any).content]\n : Array.isArray((item as any)?.content)\n ? (item as any).content\n .filter((p: any) => p?.type === 'input_text')\n .map((p: any) => p?.text ?? '')\n : [],\n )\n .join('\\n'),\n )\n : (content as OpenAI.Chat.Completions.ChatCompletionMessageParam[]).reduce(\n (total, message) =>\n total +\n estimateTokenCount(\n typeof message.content === 'string' ? message.content : '',\n ),\n 0,\n );\n const operation = format ? 'responses.parse' : 'chat.completions';\n\n try {\n if (!format) {\n if (typeof content !== 'string' && isResponseInputWithInputParts(content)) {\n throw new Error(\n 'normalizeInferenceInput cannot handle ResponseInput content with input_* parts when format is undefined; pass a format to use responses.parse.',\n );\n }\n\n const messages = normalizeInferenceInput(\n content as string | OpenAI.Chat.Completions.ChatCompletionMessageParam[],\n );\n const completion = await openAIClient.chat.completions.create({\n model,\n messages,\n ...(options?.temperature !== undefined\n ? { temperature: options.temperature }\n : {}),\n ...(options?.maxTokens !== undefined\n ? { max_tokens: options.maxTokens }\n : {}),\n });\n\n await logInferenceUsage({\n model,\n operation,\n success: true,\n estimatedInputTokens,\n reason,\n metadata: options?.metadata,\n usage: getChatCompletionUsage(completion),\n });\n\n return completion.choices[0]?.message?.content as T;\n }\n\n const input = buildResponsesParseInput(content);\n const completion = await openAIClient.responses.parse({\n model,\n input,\n text: {\n format: zodTextFormat(format, 'newton_response_format'),\n },\n });\n\n if (!completion) {\n throw new Error('No response generated from inference API');\n }\n\n await logInferenceUsage({\n model,\n operation,\n success: true,\n estimatedInputTokens,\n reason,\n metadata: options?.metadata,\n usage: getResponseUsage(completion),\n });\n\n return completion.output_parsed as T;\n } catch (error) {\n await logInferenceUsage({\n model,\n operation,\n success: false,\n estimatedInputTokens,\n reason,\n metadata: options?.metadata,\n error,\n });\n\n logger.error('Failed to generate inference response', {\n error,\n reason,\n model,\n operation,\n metadata: options?.metadata,\n });\n throw error;\n }\n}\n\n/**\n * Get available inference models (for admin/config purposes)\n */\nexport async function getAvailableModels(): Promise<string[]> {\n try {\n const models = await inferenceClient.models.list();\n return models.data\n .filter(model => model.id.includes('command'))\n .map(model => model.id)\n .sort();\n } catch (error) {\n logger.error('Failed to fetch inference models', { error });\n return ['command-r-plus', 'command-r', 'command-light'];\n }\n}\n\n/**\n * Estimate token count for a message (rough approximation)\n */\nexport function estimateTokenCount(text: string): number {\n return Math.ceil(text.length / 4);\n}\n"],"names":[],"mappings":"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","debug_id":"7fa949f6-b6c7-5f82-bc61-5de02fa8f775"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"/","sources":["workers/connection.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAoC9B,eAAO,MAAM,cAAc,SAAwB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
!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]="afa7018f-10b5-59d0-b3b7-2c3033bd6329")}catch(e){}}();
|
|
3
|
+
import IORedis from 'ioredis';
|
|
4
|
+
import { env } from '../lib/config/env.js';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
/**
|
|
7
|
+
* BullMQ-specific Redis connection.
|
|
8
|
+
*
|
|
9
|
+
* Kept separate from `src/lib/redis.ts` (which is used for caching and
|
|
10
|
+
* rate-limiting) because BullMQ requires `maxRetriesPerRequest: null` and
|
|
11
|
+
* `enableReadyCheck: false` on its connection. Mixing those settings with
|
|
12
|
+
* general Redis usage causes unwanted behaviour on transient errors.
|
|
13
|
+
*/
|
|
14
|
+
function buildBullConnection() {
|
|
15
|
+
if (!env.REDIS_URL) {
|
|
16
|
+
throw new Error('REDIS_URL is required for BullMQ. Set it in .env / .env.production / your hosting env.');
|
|
17
|
+
}
|
|
18
|
+
const connection = new IORedis(env.REDIS_URL, {
|
|
19
|
+
maxRetriesPerRequest: null,
|
|
20
|
+
enableReadyCheck: false,
|
|
21
|
+
lazyConnect: false,
|
|
22
|
+
});
|
|
23
|
+
connection.on('error', (err) => {
|
|
24
|
+
logger.error('BullMQ Redis connection error', { error: err.message });
|
|
25
|
+
});
|
|
26
|
+
connection.on('connect', () => {
|
|
27
|
+
logger.info('BullMQ Redis connected');
|
|
28
|
+
});
|
|
29
|
+
return connection;
|
|
30
|
+
}
|
|
31
|
+
export const bullConnection = buildBullConnection();
|
|
32
|
+
//# sourceMappingURL=connection.js.map
|
|
33
|
+
//# debugId=afa7018f-10b5-59d0-b3b7-2c3033bd6329
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sources":["workers/connection.ts"],"sourceRoot":"/","sourcesContent":["import IORedis from 'ioredis';\nimport { env } from '../lib/config/env.js';\nimport { logger } from '../utils/logger.js';\n\n/**\n * BullMQ-specific Redis connection.\n *\n * Kept separate from `src/lib/redis.ts` (which is used for caching and\n * rate-limiting) because BullMQ requires `maxRetriesPerRequest: null` and\n * `enableReadyCheck: false` on its connection. Mixing those settings with\n * general Redis usage causes unwanted behaviour on transient errors.\n */\nfunction buildBullConnection(): IORedis {\n if (!env.REDIS_URL) {\n throw new Error(\n 'REDIS_URL is required for BullMQ. Set it in .env / .env.production / your hosting env.',\n );\n }\n\n const connection = new IORedis(env.REDIS_URL, {\n maxRetriesPerRequest: null,\n enableReadyCheck: false,\n lazyConnect: false,\n });\n\n connection.on('error', (err) => {\n logger.error('BullMQ Redis connection error', { error: err.message });\n });\n\n connection.on('connect', () => {\n logger.info('BullMQ Redis connected');\n });\n\n return connection;\n}\n\nexport const bullConnection = buildBullConnection();\n"],"names":[],"mappings":";;AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;;;;;;GAOG;AACH,SAAS,mBAAmB;IAC1B,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,wFAAwF,CACzF,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE;QAC5C,oBAAoB,EAAE,IAAI;QAC1B,gBAAgB,EAAE,KAAK;QACvB,WAAW,EAAE,KAAK;KACnB,CAAC,CAAC;IAEH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC7B,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QAC5B,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,EAAE,CAAC","debug_id":"afa7018f-10b5-59d0-b3b7-2c3033bd6329"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker process entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* Run separately from the HTTP server (e.g. `node dist/workers/index.js` on
|
|
5
|
+
* Render/Railway/Fly). Importing this file boots every BullMQ Worker
|
|
6
|
+
* declared under `src/workers/processors/*`. Do NOT import this from the
|
|
7
|
+
* HTTP server — that would spin up workers in your serverless functions.
|
|
8
|
+
*/
|
|
9
|
+
import '../instrument.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"/","sources":["workers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker process entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* Run separately from the HTTP server (e.g. `node dist/workers/index.js` on
|
|
5
|
+
* Render/Railway/Fly). Importing this file boots every BullMQ Worker
|
|
6
|
+
* declared under `src/workers/processors/*`. Do NOT import this from the
|
|
7
|
+
* HTTP server — that would spin up workers in your serverless functions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
!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]="07a4db46-6b47-5518-8343-2659c25cad54")}catch(e){}}();
|
|
11
|
+
import '../instrument.js';
|
|
12
|
+
import { prisma } from '../lib/prisma.js';
|
|
13
|
+
import { logger } from '../utils/logger.js';
|
|
14
|
+
import { bullConnection } from './connection.js';
|
|
15
|
+
import { aiLabChatWorker } from './processors/aiLabChat.js';
|
|
16
|
+
import { aiNewtonChatWorker } from './processors/aiNewtonChat.js';
|
|
17
|
+
import { gradeWorksheetWorker } from './processors/gradeWorksheet.js';
|
|
18
|
+
import { pdfToWorksheetWorker } from './processors/pdfToWorksheet.js';
|
|
19
|
+
import { studentProgressChatWorker } from './processors/studentProgressChat.js';
|
|
20
|
+
const workers = [
|
|
21
|
+
pdfToWorksheetWorker,
|
|
22
|
+
gradeWorksheetWorker,
|
|
23
|
+
aiLabChatWorker,
|
|
24
|
+
aiNewtonChatWorker,
|
|
25
|
+
studentProgressChatWorker,
|
|
26
|
+
];
|
|
27
|
+
logger.info('Workers ready', {
|
|
28
|
+
pid: process.pid,
|
|
29
|
+
queues: workers.map((w) => w.name),
|
|
30
|
+
});
|
|
31
|
+
let shuttingDown = false;
|
|
32
|
+
async function shutdown(signal) {
|
|
33
|
+
if (shuttingDown)
|
|
34
|
+
return;
|
|
35
|
+
shuttingDown = true;
|
|
36
|
+
logger.info(`Received ${signal}, draining workers...`);
|
|
37
|
+
// `worker.close()` waits for in-flight jobs to finish (up to the worker's
|
|
38
|
+
// settings) before resolving. Run them in parallel.
|
|
39
|
+
await Promise.allSettled(workers.map((w) => w.close()));
|
|
40
|
+
await Promise.allSettled([
|
|
41
|
+
bullConnection.quit(),
|
|
42
|
+
prisma.$disconnect(),
|
|
43
|
+
]);
|
|
44
|
+
logger.info('Workers shut down cleanly');
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
48
|
+
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
49
|
+
process.on('uncaughtException', (error) => {
|
|
50
|
+
logger.error('Uncaught exception in worker process', {
|
|
51
|
+
error: error.message,
|
|
52
|
+
stack: error.stack,
|
|
53
|
+
});
|
|
54
|
+
process.exit(1);
|
|
55
|
+
});
|
|
56
|
+
process.on('unhandledRejection', (reason) => {
|
|
57
|
+
logger.error('Unhandled rejection in worker process', {
|
|
58
|
+
reason: reason instanceof Error ? reason.message : String(reason),
|
|
59
|
+
});
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
63
|
+
//# debugId=07a4db46-6b47-5518-8343-2659c25cad54
|