@drax/ai-back 3.51.1 → 3.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/models/AILogModel.js +1 -0
  2. package/dist/providers/ai/AbstractAiProvider.js +90 -0
  3. package/dist/providers/ai/DeepSeekAiProvider.js +1 -7
  4. package/dist/providers/ai/GoogleAiProvider.js +3 -80
  5. package/dist/providers/ai/OllamaAiProvider.js +3 -80
  6. package/dist/providers/ai/OpenAiProvider.js +4 -81
  7. package/dist/repository/sqlite/AILogSqliteRepository.js +1 -0
  8. package/dist/routes/AILogRoutes.js +5 -5
  9. package/dist/schemas/AILogSchema.js +4 -1
  10. package/package.json +3 -3
  11. package/src/models/AILogModel.ts +1 -0
  12. package/src/providers/ai/AbstractAiProvider.ts +129 -0
  13. package/src/providers/ai/DeepSeekAiProvider.ts +1 -20
  14. package/src/providers/ai/GoogleAiProvider.ts +4 -116
  15. package/src/providers/ai/OllamaAiProvider.ts +4 -116
  16. package/src/providers/ai/OpenAiProvider.ts +5 -117
  17. package/src/repository/sqlite/AILogSqliteRepository.ts +1 -1
  18. package/src/routes/AILogRoutes.ts +14 -14
  19. package/src/schemas/AILogSchema.ts +4 -1
  20. package/tsconfig.tsbuildinfo +1 -1
  21. package/types/models/AILogModel.d.ts.map +1 -1
  22. package/types/providers/ai/AbstractAiProvider.d.ts +29 -0
  23. package/types/providers/ai/AbstractAiProvider.d.ts.map +1 -0
  24. package/types/providers/ai/DeepSeekAiProvider.d.ts +0 -14
  25. package/types/providers/ai/DeepSeekAiProvider.d.ts.map +1 -1
  26. package/types/providers/ai/GoogleAiProvider.d.ts +3 -31
  27. package/types/providers/ai/GoogleAiProvider.d.ts.map +1 -1
  28. package/types/providers/ai/OllamaAiProvider.d.ts +3 -31
  29. package/types/providers/ai/OllamaAiProvider.d.ts.map +1 -1
  30. package/types/providers/ai/OpenAiProvider.d.ts +4 -32
  31. package/types/providers/ai/OpenAiProvider.d.ts.map +1 -1
  32. package/types/repository/sqlite/AILogSqliteRepository.d.ts.map +1 -1
  33. package/types/schemas/AILogSchema.d.ts +4 -0
  34. package/types/schemas/AILogSchema.d.ts.map +1 -1
@@ -1,6 +1,4 @@
1
1
  import OpenAI from "openai";
2
- import type {IAILogBase} from "@drax/ai-share";
3
- import type {IPromptParams} from "../../interfaces/IAIProvider.js";
4
2
  import type {AILogService} from "../../services/AILogService.js";
5
3
  import OpenAiProvider from "./OpenAiProvider.js";
6
4
 
@@ -15,7 +13,7 @@ class DeepSeekAiProvider extends OpenAiProvider{
15
13
  throw new Error("DeepSeek model required")
16
14
  }
17
15
 
18
- super(apiKey, model, visionModel, aiLogService)
16
+ super(apiKey, model, visionModel, aiLogService, "deepseek")
19
17
 
20
18
  if (!baseUrl) {
21
19
  throw new Error("DeepSeek baseUrl required")
@@ -35,23 +33,6 @@ class DeepSeekAiProvider extends OpenAiProvider{
35
33
  return this._client
36
34
  }
37
35
 
38
- protected buildLogPayload(input: IPromptParams, params: {
39
- model: string,
40
- systemPrompt: string,
41
- startedAt: Date,
42
- endedAt?: Date,
43
- inputTokens?: number,
44
- outputTokens?: number,
45
- tokens?: number,
46
- output?: unknown,
47
- success: boolean,
48
- errorMessage?: string,
49
- }): IAILogBase {
50
- return {
51
- ...super.buildLogPayload(input, params),
52
- provider: "deepseek",
53
- }
54
- }
55
36
  }
56
37
 
57
38
  export default DeepSeekAiProvider
@@ -8,7 +8,6 @@ import type {
8
8
  } from "@google/genai";
9
9
  import {toJSONSchema} from "zod";
10
10
  import type {
11
- IAIProvider,
12
11
  IPromptContentPart,
13
12
  IPromptMessage,
14
13
  IPromptParams,
@@ -16,15 +15,14 @@ import type {
16
15
  IPromptTool
17
16
  } from "../../interfaces/IAIProvider.js";
18
17
  import type {AILogService} from "../../services/AILogService.js";
19
- import type {IAILogBase} from "@drax/ai-share";
20
18
  import PromptAudioService from "../../services/PromptAudioService.js";
19
+ import AbstractAiProvider from "./AbstractAiProvider.js";
21
20
 
22
- class GoogleAiProvider implements IAIProvider{
21
+ class GoogleAiProvider extends AbstractAiProvider{
23
22
  protected _apiKey: string
24
23
  protected _model: any
25
24
  protected _visionModel?: string
26
25
  protected _client: GoogleGenAI | undefined
27
- protected _aiLogService?: AILogService
28
26
 
29
27
  constructor(apiKey: string, model: string, visionModel?: string, aiLogService?: AILogService) {
30
28
 
@@ -35,10 +33,11 @@ class GoogleAiProvider implements IAIProvider{
35
33
  throw new Error("Google AI model required")
36
34
  }
37
35
 
36
+ super("googleai", aiLogService)
37
+
38
38
  this._apiKey = apiKey
39
39
  this._model = model
40
40
  this._visionModel = visionModel
41
- this._aiLogService = aiLogService
42
41
  }
43
42
 
44
43
  get model(){
@@ -169,117 +168,6 @@ class GoogleAiProvider implements IAIProvider{
169
168
  })
170
169
  }
171
170
 
172
- protected hasImageInput(input: IPromptParams){
173
- if(input.userImages && input.userImages.length > 0){
174
- return true
175
- }
176
-
177
- if(input.userContent?.some(part => part.type === 'image')){
178
- return true
179
- }
180
-
181
- return input.history?.some(message =>
182
- Array.isArray(message.content) && message.content.some(part => part.type === 'image')
183
- ) ?? false
184
- }
185
-
186
- protected serializePromptInput(input: IPromptParams, systemPrompt: string){
187
- return JSON.stringify({
188
- systemPrompt,
189
- history: input.history,
190
- userInput: input.userInput,
191
- userContent: input.userContent,
192
- memory: input.memory,
193
- knowledgeBase: input.knowledgeBase,
194
- tools: input.tools?.map(tool => ({
195
- name: tool.name,
196
- description: tool.description,
197
- parameters: tool.parameters,
198
- })),
199
- })
200
- }
201
-
202
- protected serializePromptOutput(output: unknown){
203
- if (typeof output === "string") {
204
- return output
205
- }
206
-
207
- if (output === null || output === undefined) {
208
- return undefined
209
- }
210
-
211
- return JSON.stringify(output)
212
- }
213
-
214
- protected buildLogPayload(input: IPromptParams, params: {
215
- model: string,
216
- systemPrompt: string,
217
- startedAt: Date,
218
- endedAt?: Date,
219
- inputTokens?: number,
220
- outputTokens?: number,
221
- tokens?: number,
222
- output?: unknown,
223
- success: boolean,
224
- errorMessage?: string,
225
- }): IAILogBase {
226
- return {
227
- provider: "googleai",
228
- model: params.model,
229
- operationTitle: input.operationTitle,
230
- operationGroup: input.operationGroup,
231
- ip: input.ip,
232
- userAgent: input.userAgent,
233
- input: this.serializePromptInput(input, params.systemPrompt),
234
- inputImages: input.userImages?.map(image => ({
235
- url: image.url,
236
- })) ?? input.userContent
237
- ?.filter(part => part.type === "image")
238
- .map(part => ({
239
- url: part.imageUrl,
240
- })),
241
- inputFiles: input.inputFiles,
242
- inputTokens: params.inputTokens,
243
- outputTokens: params.outputTokens,
244
- tokens: params.tokens,
245
- startedAt: params.startedAt,
246
- endedAt: params.endedAt,
247
- responseTime: params.endedAt ? `${params.endedAt.getTime() - params.startedAt.getTime()}ms` : undefined,
248
- output: this.serializePromptOutput(params.output),
249
- success: params.success,
250
- errorMessage: params.errorMessage,
251
- tenant: input.tenant,
252
- user: input.user,
253
- }
254
- }
255
-
256
- protected async registerPromptLog(input: IPromptParams, params: {
257
- model: string,
258
- systemPrompt: string,
259
- startedAt: Date,
260
- endedAt?: Date,
261
- inputTokens?: number,
262
- outputTokens?: number,
263
- tokens?: number,
264
- output?: unknown,
265
- success: boolean,
266
- errorMessage?: string,
267
- }){
268
- if(!this._aiLogService){
269
- return
270
- }
271
-
272
- try{
273
- await this._aiLogService.create(this.buildLogPayload(input, params))
274
- }catch(e: any){
275
- console.error("Error registerPromptLog", {
276
- name: e?.name,
277
- message: e?.message,
278
- stack: e?.stack,
279
- })
280
- }
281
- }
282
-
283
171
  async generateEmbedding({text, model="text-embedding-004"}: {text:string,model?:string }): Promise<number[]> {
284
172
  const response = await this.client.models.embedContent({
285
173
  model,
@@ -1,6 +1,5 @@
1
1
  import {toJSONSchema} from "zod";
2
2
  import type {
3
- IAIProvider,
4
3
  IPromptContentPart,
5
4
  IPromptMessage,
6
5
  IPromptParams,
@@ -8,8 +7,8 @@ import type {
8
7
  IPromptTool
9
8
  } from "../../interfaces/IAIProvider.js";
10
9
  import type {AILogService} from "../../services/AILogService.js";
11
- import type {IAILogBase} from "@drax/ai-share";
12
10
  import PromptAudioService from "../../services/PromptAudioService.js";
11
+ import AbstractAiProvider from "./AbstractAiProvider.js";
13
12
 
14
13
  type OllamaMessage = {
15
14
  role: "system" | "user" | "assistant" | "tool",
@@ -25,12 +24,11 @@ type OllamaToolCall = {
25
24
  }
26
25
  }
27
26
 
28
- class OllamaAiProvider implements IAIProvider{
27
+ class OllamaAiProvider extends AbstractAiProvider{
29
28
  protected _baseUrl: string
30
29
  protected _model: string
31
30
  protected _visionModel?: string
32
31
  protected _embeddingModel?: string
33
- protected _aiLogService?: AILogService
34
32
 
35
33
  constructor(baseUrl: string, model: string, visionModel?: string, embeddingModel?: string, aiLogService?: AILogService) {
36
34
 
@@ -41,11 +39,12 @@ class OllamaAiProvider implements IAIProvider{
41
39
  throw new Error("Ollama AI model required")
42
40
  }
43
41
 
42
+ super("ollamaai", aiLogService)
43
+
44
44
  this._baseUrl = baseUrl.replace(/\/+$/, "")
45
45
  this._model = model
46
46
  this._visionModel = visionModel
47
47
  this._embeddingModel = embeddingModel
48
- this._aiLogService = aiLogService
49
48
  }
50
49
 
51
50
  get model(){
@@ -154,117 +153,6 @@ class OllamaAiProvider implements IAIProvider{
154
153
  return messages
155
154
  }
156
155
 
157
- protected hasImageInput(input: IPromptParams){
158
- if(input.userImages && input.userImages.length > 0){
159
- return true
160
- }
161
-
162
- if(input.userContent?.some(part => part.type === 'image')){
163
- return true
164
- }
165
-
166
- return input.history?.some(message =>
167
- Array.isArray(message.content) && message.content.some(part => part.type === 'image')
168
- ) ?? false
169
- }
170
-
171
- protected serializePromptInput(input: IPromptParams, systemPrompt: string){
172
- return JSON.stringify({
173
- systemPrompt,
174
- history: input.history,
175
- userInput: input.userInput,
176
- userContent: input.userContent,
177
- memory: input.memory,
178
- knowledgeBase: input.knowledgeBase,
179
- tools: input.tools?.map(tool => ({
180
- name: tool.name,
181
- description: tool.description,
182
- parameters: tool.parameters,
183
- })),
184
- })
185
- }
186
-
187
- protected serializePromptOutput(output: unknown){
188
- if (typeof output === "string") {
189
- return output
190
- }
191
-
192
- if (output === null || output === undefined) {
193
- return undefined
194
- }
195
-
196
- return JSON.stringify(output)
197
- }
198
-
199
- protected buildLogPayload(input: IPromptParams, params: {
200
- model: string,
201
- systemPrompt: string,
202
- startedAt: Date,
203
- endedAt?: Date,
204
- inputTokens?: number,
205
- outputTokens?: number,
206
- tokens?: number,
207
- output?: unknown,
208
- success: boolean,
209
- errorMessage?: string,
210
- }): IAILogBase {
211
- return {
212
- provider: "ollamaai",
213
- model: params.model,
214
- operationTitle: input.operationTitle,
215
- operationGroup: input.operationGroup,
216
- ip: input.ip,
217
- userAgent: input.userAgent,
218
- input: this.serializePromptInput(input, params.systemPrompt),
219
- inputImages: input.userImages?.map(image => ({
220
- url: image.url,
221
- })) ?? input.userContent
222
- ?.filter(part => part.type === "image")
223
- .map(part => ({
224
- url: part.imageUrl,
225
- })),
226
- inputFiles: input.inputFiles,
227
- inputTokens: params.inputTokens,
228
- outputTokens: params.outputTokens,
229
- tokens: params.tokens,
230
- startedAt: params.startedAt,
231
- endedAt: params.endedAt,
232
- responseTime: params.endedAt ? `${params.endedAt.getTime() - params.startedAt.getTime()}ms` : undefined,
233
- output: this.serializePromptOutput(params.output),
234
- success: params.success,
235
- errorMessage: params.errorMessage,
236
- tenant: input.tenant,
237
- user: input.user,
238
- }
239
- }
240
-
241
- protected async registerPromptLog(input: IPromptParams, params: {
242
- model: string,
243
- systemPrompt: string,
244
- startedAt: Date,
245
- endedAt?: Date,
246
- inputTokens?: number,
247
- outputTokens?: number,
248
- tokens?: number,
249
- output?: unknown,
250
- success: boolean,
251
- errorMessage?: string,
252
- }){
253
- if(!this._aiLogService){
254
- return
255
- }
256
-
257
- try{
258
- await this._aiLogService.create(this.buildLogPayload(input, params))
259
- }catch(e: any){
260
- console.error("Error registerPromptLog", {
261
- name: e?.name,
262
- message: e?.message,
263
- stack: e?.stack,
264
- })
265
- }
266
- }
267
-
268
156
  async generateEmbedding({text, model}: {text:string, model?:string }): Promise<number[]> {
269
157
  const response = await this.post<any>("/api/embed", {
270
158
  model: model ?? this.embeddingModel,
@@ -1,7 +1,6 @@
1
1
  import OpenAI from "openai";
2
2
  import { zodResponseFormat } from "openai/helpers/zod";
3
3
  import type {
4
- IAIProvider,
5
4
  IPromptContentPart,
6
5
  IPromptMessage,
7
6
  IPromptParams,
@@ -9,17 +8,16 @@ import type {
9
8
  IPromptTool
10
9
  } from "../../interfaces/IAIProvider.js";
11
10
  import type {AILogService} from "../../services/AILogService.js";
12
- import type {IAILogBase} from "@drax/ai-share";
13
11
  import PromptAudioService from "../../services/PromptAudioService.js";
12
+ import AbstractAiProvider from "./AbstractAiProvider.js";
14
13
 
15
- class OpenAiProvider implements IAIProvider{
14
+ class OpenAiProvider extends AbstractAiProvider{
16
15
  protected _apiKey: string
17
16
  protected _model: any
18
17
  protected _visionModel?: string
19
18
  protected _client: any
20
- protected _aiLogService?: AILogService
21
19
 
22
- constructor(apiKey: string, model: string, visionModel?: string, aiLogService?: AILogService) {
20
+ constructor(apiKey: string, model: string, visionModel?: string, aiLogService?: AILogService, providerName: string = "openai") {
23
21
 
24
22
  if (!apiKey) {
25
23
  throw new Error("OpenAI apiKey required")
@@ -28,10 +26,11 @@ class OpenAiProvider implements IAIProvider{
28
26
  throw new Error("OpenAI model required")
29
27
  }
30
28
 
29
+ super(providerName, aiLogService)
30
+
31
31
  this._apiKey = apiKey
32
32
  this._model = model
33
33
  this._visionModel = visionModel
34
- this._aiLogService = aiLogService
35
34
  }
36
35
 
37
36
  get model(){
@@ -109,117 +108,6 @@ class OpenAiProvider implements IAIProvider{
109
108
  }))
110
109
  }
111
110
 
112
- protected hasImageInput(input: IPromptParams){
113
- if(input.userImages && input.userImages.length > 0){
114
- return true
115
- }
116
-
117
- if(input.userContent?.some(part => part.type === 'image')){
118
- return true
119
- }
120
-
121
- return input.history?.some(message =>
122
- Array.isArray(message.content) && message.content.some(part => part.type === 'image')
123
- ) ?? false
124
- }
125
-
126
- protected serializePromptInput(input: IPromptParams, systemPrompt: string){
127
- return JSON.stringify({
128
- systemPrompt,
129
- history: input.history,
130
- userInput: input.userInput,
131
- userContent: input.userContent,
132
- memory: input.memory,
133
- knowledgeBase: input.knowledgeBase,
134
- tools: input.tools?.map(tool => ({
135
- name: tool.name,
136
- description: tool.description,
137
- parameters: tool.parameters,
138
- })),
139
- })
140
- }
141
-
142
- protected serializePromptOutput(output: unknown){
143
- if (typeof output === "string") {
144
- return output
145
- }
146
-
147
- if (output === null || output === undefined) {
148
- return undefined
149
- }
150
-
151
- return JSON.stringify(output)
152
- }
153
-
154
- protected buildLogPayload(input: IPromptParams, params: {
155
- model: string,
156
- systemPrompt: string,
157
- startedAt: Date,
158
- endedAt?: Date,
159
- inputTokens?: number,
160
- outputTokens?: number,
161
- tokens?: number,
162
- output?: unknown,
163
- success: boolean,
164
- errorMessage?: string,
165
- }): IAILogBase {
166
- return {
167
- provider: "openai",
168
- model: params.model,
169
- operationTitle: input.operationTitle,
170
- operationGroup: input.operationGroup,
171
- ip: input.ip,
172
- userAgent: input.userAgent,
173
- input: this.serializePromptInput(input, params.systemPrompt),
174
- inputImages: input.userImages?.map(image => ({
175
- url: image.url,
176
- })) ?? input.userContent
177
- ?.filter(part => part.type === "image")
178
- .map(part => ({
179
- url: part.imageUrl,
180
- })),
181
- inputFiles: input.inputFiles,
182
- inputTokens: params.inputTokens,
183
- outputTokens: params.outputTokens,
184
- tokens: params.tokens,
185
- startedAt: params.startedAt,
186
- endedAt: params.endedAt,
187
- responseTime: params.endedAt ? `${params.endedAt.getTime() - params.startedAt.getTime()}ms` : undefined,
188
- output: this.serializePromptOutput(params.output),
189
- success: params.success,
190
- errorMessage: params.errorMessage,
191
- tenant: input.tenant,
192
- user: input.user,
193
- }
194
- }
195
-
196
- protected async registerPromptLog(input: IPromptParams, params: {
197
- model: string,
198
- systemPrompt: string,
199
- startedAt: Date,
200
- endedAt?: Date,
201
- inputTokens?: number,
202
- outputTokens?: number,
203
- tokens?: number,
204
- output?: unknown,
205
- success: boolean,
206
- errorMessage?: string,
207
- }){
208
- if(!this._aiLogService){
209
- return
210
- }
211
-
212
- try{
213
- await this._aiLogService.create(this.buildLogPayload(input, params))
214
- }catch(e: any){
215
- console.error("Error registerPromptLog", {
216
- name: e?.name,
217
- message: e?.message,
218
- stack: e?.stack,
219
- })
220
- }
221
- }
222
-
223
111
  async generateEmbedding({text, model="text-embedding-ada-002"}: {text:string,model:string }): Promise<number[]> {
224
112
  const response = await this.client.embeddings.create({
225
113
  model: model,
@@ -37,6 +37,7 @@ class AILogSqliteRepository extends AbstractSqliteRepository<IAILog, IAILogBase,
37
37
  {name: "startedAt", type: "TEXT", unique: false, primary: false},
38
38
  {name: "endedAt", type: "TEXT", unique: false, primary: false},
39
39
  {name: "responseTime", type: "TEXT", unique: false, primary: false},
40
+ {name: "responseTimeMS", type: "REAL", unique: false, primary: false},
40
41
  {name: "output", type: "TEXT", unique: false, primary: false},
41
42
  {name: "success", type: "TEXT", unique: false, primary: false},
42
43
  {name: "statusCode", type: "INTEGER", unique: false, primary: false},
@@ -50,4 +51,3 @@ class AILogSqliteRepository extends AbstractSqliteRepository<IAILog, IAILogBase,
50
51
 
51
52
  export default AILogSqliteRepository
52
53
  export {AILogSqliteRepository}
53
-
@@ -9,29 +9,29 @@ async function AILogFastifyRoutes(fastify, options) {
9
9
  const schemas = new CrudSchemaBuilder(AILogSchema, AILogBaseSchema,AILogBaseSchema, 'AILog', 'openapi-3.0', ['ai']);
10
10
 
11
11
  fastify.get('/api/ailog', {schema: schemas.paginateSchema}, (req,rep) => controller.paginate(req,rep))
12
-
12
+
13
13
  fastify.get('/api/ailog/find', {schema: schemas.findSchema}, (req,rep) => controller.find(req,rep))
14
-
14
+
15
15
  fastify.get('/api/ailog/search', {schema: schemas.searchSchema}, (req,rep) => controller.search(req,rep))
16
-
16
+
17
17
  fastify.get('/api/ailog/:id', {schema: schemas.findByIdSchema}, (req,rep) => controller.findById(req,rep))
18
-
18
+
19
19
  fastify.get('/api/ailog/find-one', {schema: schemas.findOneSchema}, (req,rep) => controller.findOne(req,rep))
20
-
20
+
21
21
  fastify.get('/api/ailog/group-by', {schema: schemas.groupBySchema}, (req,rep) => controller.groupBy(req,rep))
22
22
 
23
- fastify.post('/api/ailog', {schema: schemas.createSchema}, (req,rep) =>controller.create(req,rep))
23
+ // fastify.post('/api/ailog', {schema: schemas.createSchema}, (req,rep) =>controller.create(req,rep))
24
24
 
25
- fastify.put('/api/ailog/:id', {schema: schemas.updateSchema}, (req,rep) =>controller.update(req,rep))
26
-
27
- fastify.patch('/api/ailog/:id', {schema: schemas.updateSchema}, (req,rep) =>controller.updatePartial(req,rep))
25
+ // fastify.put('/api/ailog/:id', {schema: schemas.updateSchema}, (req,rep) =>controller.update(req,rep))
26
+
27
+ // fastify.patch('/api/ailog/:id', {schema: schemas.updateSchema}, (req,rep) =>controller.updatePartial(req,rep))
28
+
29
+ // fastify.delete('/api/ailog/:id', {schema: schemas.deleteSchema}, (req,rep) =>controller.delete(req,rep))
28
30
 
29
- fastify.delete('/api/ailog/:id', {schema: schemas.deleteSchema}, (req,rep) =>controller.delete(req,rep))
30
-
31
31
  fastify.get('/api/ailog/export', (req,rep) =>controller.export(req,rep))
32
-
33
- fastify.post('/api/ailog/import', (req,rep) => controller.import(req,rep))
34
-
32
+
33
+ // fastify.post('/api/ailog/import', (req,rep) => controller.import(req,rep))
34
+
35
35
  }
36
36
 
37
37
  export default AILogFastifyRoutes;
@@ -33,6 +33,7 @@ const AILogBaseSchema = z.object({
33
33
  startedAt: z.coerce.date().nullable().optional(),
34
34
  endedAt: z.coerce.date().nullable().optional(),
35
35
  responseTime: z.string().optional(),
36
+ responseTimeMS: z.number().nullable().optional(),
36
37
  output: z.string().optional(),
37
38
  success: z.boolean().optional(),
38
39
  statusCode: z.number().nullable().optional(),
@@ -45,7 +46,9 @@ const AILogSchema = AILogBaseSchema
45
46
  .extend({
46
47
  _id: z.coerce.string(),
47
48
  tenant: z.object({_id: z.coerce.string(), name: z.string()}).nullable().optional(),
48
- user: z.object({_id: z.coerce.string(), username: z.string()}).nullable().optional()
49
+ user: z.object({_id: z.coerce.string(), username: z.string()}).nullable().optional(),
50
+ createdAt: z.coerce.date().nullable().optional(),
51
+ updatedAt: z.coerce.date().nullable().optional(),
49
52
  })
50
53
 
51
54
  export default AILogSchema;