@studious-lms/server 1.4.2 → 1.4.3
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/.coderabbit.yaml +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -28
- package/dist/index.js.map +1 -1
- package/dist/lib/fileUpload.d.ts +10 -2
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +38 -6
- package/dist/lib/fileUpload.js.map +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +18 -5
- package/dist/middleware/auth.js.map +1 -1
- package/dist/models/auth.d.ts +19 -14
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +53 -30
- package/dist/models/auth.js.map +1 -1
- package/dist/models/class.d.ts +3 -1
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +16 -4
- package/dist/models/class.js.map +1 -1
- package/dist/models/notification.d.ts +11 -10
- package/dist/models/notification.d.ts.map +1 -1
- package/dist/models/notification.js +13 -9
- package/dist/models/notification.js.map +1 -1
- package/dist/models/worksheet.d.ts +378 -7
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +168 -2
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +11 -3
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/aiLabChatContract.d.ts +3 -3
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
- package/dist/pipelines/aiNewtonChat.js +25 -14
- package/dist/pipelines/aiNewtonChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +10 -3
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/routers/_app.d.ts +124 -18
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/class.d.ts +2 -1
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +3 -3
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/labChat.d.ts +6 -0
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +5 -3
- package/dist/routers/labChat.js.map +1 -1
- package/dist/routers/message.d.ts +31 -0
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +27 -4
- package/dist/routers/message.js.map +1 -1
- package/dist/routers/newtonChat.d.ts +6 -0
- package/dist/routers/newtonChat.d.ts.map +1 -1
- package/dist/routers/newtonChat.js +5 -4
- package/dist/routers/newtonChat.js.map +1 -1
- package/dist/routers/notifications.d.ts +9 -2
- package/dist/routers/notifications.d.ts.map +1 -1
- package/dist/routers/notifications.js +4 -4
- package/dist/routers/notifications.js.map +1 -1
- package/dist/routers/worksheet.d.ts +8 -6
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +18 -16
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/services/auth.d.ts +1 -1
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +19 -12
- package/dist/services/auth.js.map +1 -1
- package/dist/services/class.d.ts +1 -1
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +6 -3
- package/dist/services/class.js.map +1 -1
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/file.js +7 -2
- package/dist/services/file.js.map +1 -1
- package/dist/services/labChat.d.ts +6 -0
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +30 -18
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/message.d.ts +30 -0
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +97 -21
- package/dist/services/message.js.map +1 -1
- package/dist/services/newtonChat.d.ts +6 -1
- package/dist/services/newtonChat.d.ts.map +1 -1
- package/dist/services/newtonChat.js +36 -18
- package/dist/services/newtonChat.js.map +1 -1
- package/dist/services/notification.d.ts +12 -5
- package/dist/services/notification.d.ts.map +1 -1
- package/dist/services/notification.js +24 -9
- package/dist/services/notification.js.map +1 -1
- package/dist/services/studentProgress.d.ts.map +1 -1
- package/dist/services/studentProgress.js +6 -3
- package/dist/services/studentProgress.js.map +1 -1
- package/dist/services/worksheet.d.ts +15 -15
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +138 -54
- package/dist/services/worksheet.js.map +1 -1
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +11 -17
- package/dist/trpc.js.map +1 -1
- package/dist/utils/inference.d.ts +9 -1
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +138 -50
- package/dist/utils/inference.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260415103500_add_session_type/migration.sql +33 -0
- package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +19 -0
- package/prisma/schema.prisma +27 -1
- package/scripts/test-pre-push.ts +6 -2
- package/src/index.ts +80 -28
- package/src/lib/fileUpload.ts +55 -5
- package/src/middleware/auth.ts +19 -4
- package/src/models/auth.ts +59 -29
- package/src/models/class.ts +15 -4
- package/src/models/notification.ts +13 -7
- package/src/models/worksheet.ts +202 -0
- package/src/pipelines/aiLabChat.ts +9 -1
- package/src/pipelines/aiNewtonChat.ts +24 -14
- package/src/pipelines/gradeWorksheet.ts +8 -0
- package/src/routers/class.ts +2 -2
- package/src/routers/labChat.ts +3 -1
- package/src/routers/message.ts +38 -1
- package/src/routers/newtonChat.ts +4 -2
- package/src/routers/notifications.ts +2 -2
- package/src/routers/worksheet.ts +31 -19
- package/src/services/auth.ts +26 -12
- package/src/services/class.ts +4 -1
- package/src/services/file.ts +6 -0
- package/src/services/labChat.ts +42 -18
- package/src/services/message.ts +163 -18
- package/src/services/newtonChat.ts +49 -18
- package/src/services/notification.ts +27 -8
- package/src/services/studentProgress.ts +4 -1
- package/src/services/worksheet.ts +191 -51
- package/src/trpc.ts +12 -18
- package/src/utils/inference.ts +209 -55
- package/tests/globalSetup.ts +13 -4
- package/tests/routers/auth.test.ts +120 -17
- package/tests/routers/class.test.ts +7 -1
- package/tests/routers/message.test.ts +236 -9
- package/tests/routers/newtonChat.test.ts +81 -0
- package/tests/routers/notification.test.ts +12 -0
- package/tests/routers/worksheet.test.ts +194 -0
- package/tests/setup.ts +22 -5
- package/tests/utils/inference.test.ts +218 -0
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
* Inference util – OpenAI client, inference() for structured output, sendAIMessage for chat.
|
|
3
3
|
*/
|
|
4
4
|
import OpenAI from 'openai';
|
|
5
|
+
import { Prisma } from '@prisma/client';
|
|
5
6
|
import { ZodSchema } from 'zod';
|
|
6
7
|
export declare const inferenceClient: OpenAI;
|
|
7
8
|
export declare const openAIClient: OpenAI;
|
|
9
|
+
export interface InferenceOptions {
|
|
10
|
+
reason?: string;
|
|
11
|
+
metadata?: Prisma.InputJsonValue;
|
|
12
|
+
model?: string;
|
|
13
|
+
temperature?: number;
|
|
14
|
+
maxTokens?: number;
|
|
15
|
+
}
|
|
8
16
|
export interface LabChatContext {
|
|
9
17
|
subject: string;
|
|
10
18
|
topic: string;
|
|
@@ -46,7 +54,7 @@ export declare function sendAIMessage(content: string, conversationId: string, o
|
|
|
46
54
|
createdAt: Date;
|
|
47
55
|
meta?: Record<string, any>;
|
|
48
56
|
}>;
|
|
49
|
-
export declare function inference<T>(content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[], format?: ZodSchema): Promise<T>;
|
|
57
|
+
export declare function inference<T>(content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[], format?: ZodSchema, options?: InferenceOptions): Promise<T>;
|
|
50
58
|
/**
|
|
51
59
|
* Get available inference models (for admin/config purposes)
|
|
52
60
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inference.d.ts","sourceRoot":"/","sources":["utils/inference.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAM5B,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"inference.d.ts","sourceRoot":"/","sources":["utils/inference.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAM5B,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AAGhC,eAAO,MAAM,eAAe,QAG1B,CAAC;AAEH,eAAO,MAAM,YAAY,QAAe,CAAC;AASzC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAoBD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,UAAU,GAAG,cAAc,GAAG,UAAU,CAAC;IACrD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAgHD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE;QACZ,OAAO,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KAC3B,CAAC;IACF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,YAAY,CAAC,EAAE;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC,CAAC;CACE,GACL,OAAO,CAAC;IACT,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC5B,CAAC,CAoED;AAED,wBAAsB,SAAS,CAAC,CAAC,EAC/B,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,EACtE,MAAM,CAAC,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,CAAC,CAAC,CA+EZ;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAW5D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
package/dist/utils/inference.js
CHANGED
|
@@ -2,27 +2,104 @@
|
|
|
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]="4274513f-5640-53ae-a6ca-567fff3e8bee")}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 { zodTextFormat } from
|
|
12
|
+
import { zodTextFormat } from 'openai/helpers/zod';
|
|
13
13
|
export const inferenceClient = new OpenAI({
|
|
14
14
|
apiKey: env.INFERENCE_API_KEY,
|
|
15
15
|
baseURL: env.INFERENCE_API_BASE_URL,
|
|
16
16
|
});
|
|
17
17
|
export const openAIClient = new OpenAI();
|
|
18
|
+
const DEFAULT_INFERENCE_MODEL = 'gpt-5-nano';
|
|
19
|
+
function normalizeInferenceInput(content) {
|
|
20
|
+
if (typeof content === 'string') {
|
|
21
|
+
return [{ role: 'user', content }];
|
|
22
|
+
}
|
|
23
|
+
return content.map((message) => ({
|
|
24
|
+
role: message.role,
|
|
25
|
+
content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content),
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
function getInferenceReason(options) {
|
|
29
|
+
return options?.reason?.trim() || 'unspecified';
|
|
30
|
+
}
|
|
31
|
+
function serializeInferenceError(error) {
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
return error.message;
|
|
34
|
+
}
|
|
35
|
+
if (typeof error === 'string') {
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return JSON.stringify(error);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return 'Unknown inference error';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function logInferenceUsage({ model, operation, success, estimatedInputTokens, reason, metadata, usage, error, }) {
|
|
46
|
+
const context = {
|
|
47
|
+
reason,
|
|
48
|
+
model,
|
|
49
|
+
operation,
|
|
50
|
+
success,
|
|
51
|
+
estimatedInputTokens,
|
|
52
|
+
promptTokens: usage?.promptTokens,
|
|
53
|
+
completionTokens: usage?.completionTokens,
|
|
54
|
+
totalTokens: usage?.totalTokens,
|
|
55
|
+
metadata,
|
|
56
|
+
...(error ? { error: serializeInferenceError(error) } : {}),
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
await prisma.aiUsageLog.create({
|
|
60
|
+
data: {
|
|
61
|
+
reason,
|
|
62
|
+
model,
|
|
63
|
+
operation,
|
|
64
|
+
success,
|
|
65
|
+
estimatedInputTokens,
|
|
66
|
+
promptTokens: usage?.promptTokens,
|
|
67
|
+
completionTokens: usage?.completionTokens,
|
|
68
|
+
totalTokens: usage?.totalTokens,
|
|
69
|
+
...(metadata !== undefined ? { metadata } : {}),
|
|
70
|
+
...(error ? { error: serializeInferenceError(error) } : {}),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (loggingError) {
|
|
75
|
+
logger.error('Failed to persist AI usage log', {
|
|
76
|
+
...context,
|
|
77
|
+
loggingError: serializeInferenceError(loggingError),
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
logger.info('AI usage logged', context);
|
|
82
|
+
}
|
|
83
|
+
function getChatCompletionUsage(completion) {
|
|
84
|
+
return {
|
|
85
|
+
promptTokens: completion.usage?.prompt_tokens,
|
|
86
|
+
completionTokens: completion.usage?.completion_tokens,
|
|
87
|
+
totalTokens: completion.usage?.total_tokens,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function getResponseUsage(completion) {
|
|
91
|
+
return {
|
|
92
|
+
promptTokens: completion.usage?.input_tokens,
|
|
93
|
+
completionTokens: completion.usage?.output_tokens,
|
|
94
|
+
totalTokens: completion.usage?.total_tokens,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
18
97
|
/**
|
|
19
98
|
* Centralized function to send AI messages to conversations
|
|
20
99
|
* Handles database storage and Pusher broadcasting
|
|
21
100
|
*/
|
|
22
101
|
export async function sendAIMessage(content, conversationId, options = {}) {
|
|
23
|
-
// Ensure AI user exists
|
|
24
102
|
await ensureAIUserExists();
|
|
25
|
-
// Create message in database
|
|
26
103
|
const aiMessage = await prisma.message.create({
|
|
27
104
|
data: {
|
|
28
105
|
content,
|
|
@@ -46,16 +123,14 @@ export async function sendAIMessage(content, conversationId, options = {}) {
|
|
|
46
123
|
conversationId,
|
|
47
124
|
contentLength: content.length,
|
|
48
125
|
});
|
|
49
|
-
// Prepare sender info
|
|
50
126
|
const senderInfo = {
|
|
51
127
|
id: getAIUserId(),
|
|
52
128
|
username: 'Newton_AI',
|
|
53
129
|
profile: {
|
|
54
|
-
displayName:
|
|
130
|
+
displayName: 'Newton AI',
|
|
55
131
|
profilePicture: options.customSender?.profilePicture || null,
|
|
56
132
|
},
|
|
57
133
|
};
|
|
58
|
-
// Broadcast via Pusher
|
|
59
134
|
try {
|
|
60
135
|
await pusher.trigger(chatChannel(conversationId), 'new-message', {
|
|
61
136
|
id: aiMessage.id,
|
|
@@ -66,7 +141,7 @@ export async function sendAIMessage(content, conversationId, options = {}) {
|
|
|
66
141
|
sender: senderInfo,
|
|
67
142
|
mentionedUserIds: [],
|
|
68
143
|
meta: aiMessage.meta,
|
|
69
|
-
attachments: aiMessage.attachments.map(attachment => ({
|
|
144
|
+
attachments: aiMessage.attachments.map((attachment) => ({
|
|
70
145
|
id: attachment.id,
|
|
71
146
|
attachmentId: attachment.id,
|
|
72
147
|
name: attachment.name,
|
|
@@ -88,59 +163,73 @@ export async function sendAIMessage(content, conversationId, options = {}) {
|
|
|
88
163
|
meta: aiMessage.meta,
|
|
89
164
|
};
|
|
90
165
|
}
|
|
91
|
-
export async function inference(content, format) {
|
|
166
|
+
export async function inference(content, format, options) {
|
|
167
|
+
const input = normalizeInferenceInput(content);
|
|
168
|
+
const reason = getInferenceReason(options);
|
|
169
|
+
const model = options?.model ?? DEFAULT_INFERENCE_MODEL;
|
|
170
|
+
const estimatedInputTokens = input.reduce((total, message) => total + estimateTokenCount(message.content), 0);
|
|
171
|
+
const operation = format ? 'responses.parse' : 'chat.completions';
|
|
92
172
|
try {
|
|
93
173
|
if (!format) {
|
|
94
174
|
const completion = await openAIClient.chat.completions.create({
|
|
95
|
-
model
|
|
96
|
-
messages:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
175
|
+
model,
|
|
176
|
+
messages: input,
|
|
177
|
+
...(options?.temperature !== undefined
|
|
178
|
+
? { temperature: options.temperature }
|
|
179
|
+
: {}),
|
|
180
|
+
...(options?.maxTokens !== undefined
|
|
181
|
+
? { max_tokens: options.maxTokens }
|
|
182
|
+
: {}),
|
|
183
|
+
});
|
|
184
|
+
await logInferenceUsage({
|
|
185
|
+
model,
|
|
186
|
+
operation,
|
|
187
|
+
success: true,
|
|
188
|
+
estimatedInputTokens,
|
|
189
|
+
reason,
|
|
190
|
+
metadata: options?.metadata,
|
|
191
|
+
usage: getChatCompletionUsage(completion),
|
|
102
192
|
});
|
|
103
193
|
return completion.choices[0]?.message?.content;
|
|
104
194
|
}
|
|
105
195
|
const completion = await openAIClient.responses.parse({
|
|
106
|
-
model
|
|
107
|
-
input
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
},
|
|
112
|
-
] : content,
|
|
113
|
-
...(format ? { text: {
|
|
114
|
-
format: zodTextFormat(format, "newton_response_format"),
|
|
115
|
-
},
|
|
116
|
-
} : {}),
|
|
117
|
-
});
|
|
118
|
-
console.log({
|
|
119
|
-
model: 'gpt-5-nano',
|
|
120
|
-
input: typeof content === 'string' ? [
|
|
121
|
-
{
|
|
122
|
-
role: 'user',
|
|
123
|
-
content: content,
|
|
124
|
-
},
|
|
125
|
-
] : content,
|
|
126
|
-
...(format ? { text: {
|
|
127
|
-
format: zodTextFormat(format, "newton_response_format"),
|
|
128
|
-
},
|
|
129
|
-
} : {}),
|
|
196
|
+
model,
|
|
197
|
+
input,
|
|
198
|
+
text: {
|
|
199
|
+
format: zodTextFormat(format, 'newton_response_format'),
|
|
200
|
+
},
|
|
130
201
|
});
|
|
131
202
|
if (!completion) {
|
|
132
203
|
throw new Error('No response generated from inference API');
|
|
133
204
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
205
|
+
await logInferenceUsage({
|
|
206
|
+
model,
|
|
207
|
+
operation,
|
|
208
|
+
success: true,
|
|
209
|
+
estimatedInputTokens,
|
|
210
|
+
reason,
|
|
211
|
+
metadata: options?.metadata,
|
|
212
|
+
usage: getResponseUsage(completion),
|
|
213
|
+
});
|
|
140
214
|
return completion.output_parsed;
|
|
141
215
|
}
|
|
142
216
|
catch (error) {
|
|
143
|
-
|
|
217
|
+
await logInferenceUsage({
|
|
218
|
+
model,
|
|
219
|
+
operation,
|
|
220
|
+
success: false,
|
|
221
|
+
estimatedInputTokens,
|
|
222
|
+
reason,
|
|
223
|
+
metadata: options?.metadata,
|
|
224
|
+
error,
|
|
225
|
+
});
|
|
226
|
+
logger.error('Failed to generate inference response', {
|
|
227
|
+
error,
|
|
228
|
+
reason,
|
|
229
|
+
model,
|
|
230
|
+
operation,
|
|
231
|
+
metadata: options?.metadata,
|
|
232
|
+
});
|
|
144
233
|
throw error;
|
|
145
234
|
}
|
|
146
235
|
}
|
|
@@ -157,15 +246,14 @@ export async function getAvailableModels() {
|
|
|
157
246
|
}
|
|
158
247
|
catch (error) {
|
|
159
248
|
logger.error('Failed to fetch inference models', { error });
|
|
160
|
-
return ['command-r-plus', 'command-r', 'command-light'];
|
|
249
|
+
return ['command-r-plus', 'command-r', 'command-light'];
|
|
161
250
|
}
|
|
162
251
|
}
|
|
163
252
|
/**
|
|
164
253
|
* Estimate token count for a message (rough approximation)
|
|
165
254
|
*/
|
|
166
255
|
export function estimateTokenCount(text) {
|
|
167
|
-
// Rough approximation: 1 token ≈ 4 characters for English text
|
|
168
256
|
return Math.ceil(text.length / 4);
|
|
169
257
|
}
|
|
170
258
|
//# sourceMappingURL=inference.js.map
|
|
171
|
-
//# debugId=
|
|
259
|
+
//# debugId=4274513f-5640-53ae-a6ca-567fff3e8bee
|
|
@@ -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 { 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 { ZodSchema } from 'zod';\nimport { zodTextFormat } from \"openai/helpers/zod\";\n\n\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\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\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 // Ensure AI user exists\n await ensureAIUserExists();\n\n // Create message in database\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 // Prepare sender info\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 // Broadcast via Pusher\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\nexport async function inference<T>(\n content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[],\n format?: ZodSchema\n): Promise<T> {\n try {\n\n if (!format) {\n const completion = await openAIClient.chat.completions.create({\n model: 'gpt-5-nano',\n messages: typeof content === 'string' ? [\n {\n role: 'user',\n content: content,\n },\n ] : content as Array<{ role: 'user' | 'assistant' | 'system'; content: string }>,\n });\n\n return completion.choices[0]?.message?.content as T;\n }\n\n const completion = await openAIClient.responses.parse({\n model: 'gpt-5-nano',\n input: typeof content === 'string' ? [\n {\n role: 'user',\n content: content,\n },\n ] : content as Array<{ role: 'user' | 'assistant' | 'system'; content: string }>,\n ...(format ? { text: {\n format: zodTextFormat(format, \"newton_response_format\"),\n },\n } : {}),\n });\n\n console.log({\n model: 'gpt-5-nano',\n input: typeof content === 'string' ? [\n {\n role: 'user',\n content: content,\n },\n ] : content as Array<{ role: 'user' | 'assistant' | 'system'; content: string }>,\n ...(format ? { text: {\n format: zodTextFormat(format, \"newton_response_format\"),\n },\n } : {}),\n });\n\n\n if (!completion) {\n throw new Error('No response generated from inference API');\n }\n\n // if (format) {\n // if (typeof completion.output === 'string') {\n // return JSON.parse(completion.output);\n // }\n // return JSON.parse(completion.output);\n // }\n\n return completion.output_parsed;\n } catch (error) {\n logger.error('Failed to generate inference response', { error });\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']; // Fallback Cohere models\n }\n}\n\n/**\n * Estimate token count for a message (rough approximation)\n */\nexport function estimateTokenCount(text: string): number {\n // Rough approximation: 1 token ≈ 4 characters for English text\n return Math.ceil(text.length / 4);\n}"],"names":[],"mappings":"AAAA;;GAEG;;;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAInD,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,MAAM,CAAC;IACxC,MAAM,EAAE,GAAG,CAAC,iBAAiB;IAC7B,OAAO,EAAE,GAAG,CAAC,sBAAsB;CACpC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,MAAM,EAAE,CAAC;AAsBzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAe,EACf,cAAsB,EACtB,UAUI,EAAE;IASN,wBAAwB;IACxB,MAAM,kBAAkB,EAAE,CAAC;IAE3B,6BAA6B;IAC7B,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QAC5C,IAAI,EAAE;YACJ,OAAO;YACP,QAAQ,EAAE,WAAW,EAAE;YACvB,cAAc;YACd,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI;gBACzB,WAAW,EAAE;oBACX,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,OAAO;iBACrC;aACF,CAAC;YACF,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI;gBAClB,IAAI,EAAE,OAAO,CAAC,IAAI;aACnB,CAAC;SACH;QACD,OAAO,EAAE;YACP,WAAW,EAAE,IAAI;SAClB;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE;QAC7B,SAAS,EAAE,SAAS,CAAC,EAAE;QACvB,cAAc;QACd,aAAa,EAAE,OAAO,CAAC,MAAM;KAC9B,CAAC,CAAC;IAEH,sBAAsB;IACtB,MAAM,UAAU,GAAG;QACjB,EAAE,EAAE,WAAW,EAAE;QACjB,QAAQ,EAAE,WAAW;QACrB,OAAO,EAAE;YACP,WAAW,EAAE,WAAW;YACxB,cAAc,EAAE,OAAO,CAAC,YAAY,EAAE,cAAc,IAAI,IAAI;SAC7D;KACF,CAAC;IAEF,uBAAuB;IACvB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE;YAC/D,EAAE,EAAE,SAAS,CAAC,EAAE;YAChB,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,QAAQ,EAAE,WAAW,EAAE;YACvB,cAAc,EAAE,SAAS,CAAC,cAAc;YACxC,SAAS,EAAE,SAAS,CAAC,SAAS;YAC9B,MAAM,EAAE,UAAU;YAClB,gBAAgB,EAAE,EAAE;YACpB,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,WAAW,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBACpD,EAAE,EAAE,UAAU,CAAC,EAAE;gBACjB,YAAY,EAAE,UAAU,CAAC,EAAE;gBAC3B,IAAI,EAAE,UAAU,CAAC,IAAI;gBACrB,IAAI,EAAE,UAAU,CAAC,IAAI;gBACrB,IAAI,EAAE,UAAU,CAAC,IAAI;gBACrB,IAAI,EAAE,UAAU,CAAC,IAAI;aACtB,CAAC,CAAC;SACJ,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;IACtF,CAAC;IAED,OAAO;QACL,EAAE,EAAE,SAAS,CAAC,EAAE;QAChB,OAAO,EAAE,SAAS,CAAC,OAAO;QAC1B,QAAQ,EAAE,WAAW,EAAE;QACvB,cAAc,EAAE,SAAS,CAAC,cAAc;QACxC,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,IAAI,EAAE,SAAS,CAAC,IAA2B;KAC5C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAsE,EACtE,MAAkB;IAElB,IAAI,CAAC;QAEH,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;gBAC5D,KAAK,EAAE,YAAY;gBACnB,QAAQ,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC;oBACtC;wBACE,IAAI,EAAE,MAAM;wBACZ,OAAO,EAAE,OAAO;qBACjB;iBACF,CAAC,CAAC,CAAC,OAA4E;aACjF,CAAC,CAAC;YAEH,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAY,CAAC;QACtD,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC;YACpD,KAAK,EAAE,YAAY;YACnB,KAAK,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACnC;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,OAAO;iBACjB;aACF,CAAC,CAAC,CAAC,OAA4E;YAChF,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;oBACjB,MAAM,EAAE,aAAa,CAAC,MAAM,EAAE,wBAAwB,CAAC;iBACxD;aACF,CAAC,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC;YACV,KAAK,EAAE,YAAY;YACnB,KAAK,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACnC;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,OAAO;iBACjB;aACF,CAAC,CAAC,CAAC,OAA4E;YAChF,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;oBACjB,MAAM,EAAE,aAAa,CAAC,MAAM,EAAE,wBAAwB,CAAC;iBACxD;aACF,CAAC,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC;QAGH,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QAED,gBAAgB;QAChB,iDAAiD;QACjD,4CAA4C;QAC5C,MAAM;QACN,0CAA0C;QAC1C,IAAI;QAEJ,OAAO,UAAU,CAAC,aAAa,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,uCAAuC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACjE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACnD,OAAO,MAAM,CAAC,IAAI;aACf,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;aAC7C,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;aACtB,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,kCAAkC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5D,OAAO,CAAC,gBAAgB,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC,CAAC,yBAAyB;IACpF,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,+DAA+D;IAC/D,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC","debug_id":"29f2563b-5001-5af3-9bfe-191889a55b09"}
|
|
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 { 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\nexport async function inference<T>(\n content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[],\n format?: ZodSchema,\n options?: InferenceOptions,\n): Promise<T> {\n const input = normalizeInferenceInput(content);\n const reason = getInferenceReason(options);\n const model = options?.model ?? DEFAULT_INFERENCE_MODEL;\n const estimatedInputTokens = input.reduce(\n (total, message) => total + estimateTokenCount(message.content),\n 0,\n );\n const operation = format ? 'responses.parse' : 'chat.completions';\n\n try {\n if (!format) {\n const completion = await openAIClient.chat.completions.create({\n model,\n messages: input,\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 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":"4274513f-5640-53ae-a6ca-567fff3e8bee"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "SessionType" AS ENUM ('AUTH', 'EMAIL_VERIFICATION', 'PASSWORD_RESET', 'CLASS_INVITE');
|
|
3
|
+
|
|
4
|
+
-- AlterTable
|
|
5
|
+
ALTER TABLE "Session"
|
|
6
|
+
ADD COLUMN "type" "SessionType" NOT NULL DEFAULT 'AUTH';
|
|
7
|
+
|
|
8
|
+
-- Backfill existing non-auth session records when they can be inferred safely.
|
|
9
|
+
UPDATE "Session"
|
|
10
|
+
SET "type" = 'CLASS_INVITE'
|
|
11
|
+
WHERE "classId" IS NOT NULL;
|
|
12
|
+
|
|
13
|
+
-- This Session.type backfill uses the expiresAt <= NOW() + INTERVAL '2 hours'
|
|
14
|
+
-- heuristic to identify password reset tokens. It may misclassify rare short-lived
|
|
15
|
+
-- AUTH sessions, but this one-time migration safely fails those sessions until users re-login.
|
|
16
|
+
UPDATE "Session"
|
|
17
|
+
SET "type" = 'PASSWORD_RESET'
|
|
18
|
+
WHERE "userId" IS NOT NULL
|
|
19
|
+
AND "classId" IS NULL
|
|
20
|
+
AND "expiresAt" IS NOT NULL
|
|
21
|
+
AND "expiresAt" <= NOW() + INTERVAL '2 hours';
|
|
22
|
+
|
|
23
|
+
UPDATE "Session"
|
|
24
|
+
SET "type" = 'EMAIL_VERIFICATION'
|
|
25
|
+
WHERE "userId" IS NOT NULL
|
|
26
|
+
AND "classId" IS NULL
|
|
27
|
+
AND "type" = 'AUTH'
|
|
28
|
+
AND EXISTS (
|
|
29
|
+
SELECT 1
|
|
30
|
+
FROM "User"
|
|
31
|
+
WHERE "User"."id" = "Session"."userId"
|
|
32
|
+
AND "User"."verified" = false
|
|
33
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
CREATE TABLE "AiUsageLog" (
|
|
2
|
+
"id" TEXT NOT NULL,
|
|
3
|
+
"reason" TEXT NOT NULL,
|
|
4
|
+
"provider" TEXT NOT NULL DEFAULT 'openai',
|
|
5
|
+
"model" TEXT NOT NULL,
|
|
6
|
+
"operation" TEXT NOT NULL,
|
|
7
|
+
"success" BOOLEAN NOT NULL DEFAULT true,
|
|
8
|
+
"promptTokens" INTEGER,
|
|
9
|
+
"completionTokens" INTEGER,
|
|
10
|
+
"totalTokens" INTEGER,
|
|
11
|
+
"estimatedInputTokens" INTEGER,
|
|
12
|
+
"metadata" JSONB DEFAULT '{}'::jsonb,
|
|
13
|
+
"error" TEXT,
|
|
14
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
15
|
+
|
|
16
|
+
CONSTRAINT "AiUsageLog_pkey" PRIMARY KEY ("id")
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE INDEX "AiUsageLog_reason_createdAt_idx" ON "AiUsageLog"("reason", "createdAt");
|
package/prisma/schema.prisma
CHANGED
|
@@ -406,10 +406,18 @@ model Section {
|
|
|
406
406
|
order Int?
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
enum SessionType {
|
|
410
|
+
AUTH
|
|
411
|
+
EMAIL_VERIFICATION
|
|
412
|
+
PASSWORD_RESET
|
|
413
|
+
CLASS_INVITE
|
|
414
|
+
}
|
|
415
|
+
|
|
409
416
|
model Session {
|
|
410
417
|
id String @id @default(uuid())
|
|
411
418
|
createdAt DateTime? @default(now())
|
|
412
419
|
expiresAt DateTime?
|
|
420
|
+
type SessionType @default(AUTH)
|
|
413
421
|
userId String?
|
|
414
422
|
user User? @relation(fields: [userId], references: [id], onDelete: NoAction)
|
|
415
423
|
classId String?
|
|
@@ -528,6 +536,24 @@ model Message {
|
|
|
528
536
|
meta Json? @default("{}")
|
|
529
537
|
}
|
|
530
538
|
|
|
539
|
+
model AiUsageLog {
|
|
540
|
+
id String @id @default(uuid())
|
|
541
|
+
reason String
|
|
542
|
+
provider String @default("openai")
|
|
543
|
+
model String
|
|
544
|
+
operation String
|
|
545
|
+
success Boolean @default(true)
|
|
546
|
+
promptTokens Int?
|
|
547
|
+
completionTokens Int?
|
|
548
|
+
totalTokens Int?
|
|
549
|
+
estimatedInputTokens Int?
|
|
550
|
+
metadata Json? @default("{}")
|
|
551
|
+
error String?
|
|
552
|
+
createdAt DateTime @default(now())
|
|
553
|
+
|
|
554
|
+
@@index([reason, createdAt])
|
|
555
|
+
}
|
|
556
|
+
|
|
531
557
|
model Mention {
|
|
532
558
|
id String @id @default(uuid())
|
|
533
559
|
messageId String
|
|
@@ -635,4 +661,4 @@ model EarlyAccessRequest {
|
|
|
635
661
|
email String
|
|
636
662
|
createdAt DateTime @default(now())
|
|
637
663
|
institutionSize String
|
|
638
|
-
}
|
|
664
|
+
}
|
package/scripts/test-pre-push.ts
CHANGED
|
@@ -7,8 +7,12 @@ dotenv.config({ path: resolve(process.cwd(), '.env.test') });
|
|
|
7
7
|
|
|
8
8
|
logger.info("Syncing Prisma schema to test database...");
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
if (process.env.DATABASE_URL?.includes("supabase")) {
|
|
11
|
+
logger.error("Supabase is not supported for test database, please don't clear the production database");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
else execSync("npx prisma db push --force-reset --skip-generate", {
|
|
11
15
|
stdio: 'inherit',
|
|
12
16
|
});
|
|
13
17
|
|
|
14
|
-
logger.info("Test database
|
|
18
|
+
logger.info("Test database run has completed");
|