@solidstarters/solid-core 1.2.172 → 1.2.174

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 (47) hide show
  1. package/dist/controllers/ai-interaction.controller.js +1 -1
  2. package/dist/controllers/ai-interaction.controller.js.map +1 -1
  3. package/dist/controllers/service.controller.js +2 -1
  4. package/dist/controllers/service.controller.js.map +1 -1
  5. package/dist/dtos/invoke-ai-prompt.dto.d.ts +1 -0
  6. package/dist/dtos/invoke-ai-prompt.dto.d.ts.map +1 -1
  7. package/dist/dtos/invoke-ai-prompt.dto.js +6 -1
  8. package/dist/dtos/invoke-ai-prompt.dto.js.map +1 -1
  9. package/dist/entities/user.entity.d.ts +1 -0
  10. package/dist/entities/user.entity.d.ts.map +1 -1
  11. package/dist/entities/user.entity.js +5 -1
  12. package/dist/entities/user.entity.js.map +1 -1
  13. package/dist/interfaces.d.ts +2 -1
  14. package/dist/interfaces.d.ts.map +1 -1
  15. package/dist/interfaces.js.map +1 -1
  16. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts +3 -2
  17. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts.map +1 -1
  18. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js +61 -22
  19. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -1
  20. package/dist/repository/chatter-message-details.repository.d.ts.map +1 -1
  21. package/dist/repository/chatter-message-details.repository.js +7 -3
  22. package/dist/repository/chatter-message-details.repository.js.map +1 -1
  23. package/dist/repository/chatter-message.repository.d.ts.map +1 -1
  24. package/dist/repository/chatter-message.repository.js +4 -0
  25. package/dist/repository/chatter-message.repository.js.map +1 -1
  26. package/dist/seeders/seed-data/solid-core-metadata.json +39 -4
  27. package/dist/services/ai-interaction.service.d.ts +2 -1
  28. package/dist/services/ai-interaction.service.d.ts.map +1 -1
  29. package/dist/services/ai-interaction.service.js +15 -3
  30. package/dist/services/ai-interaction.service.js.map +1 -1
  31. package/dist/services/user.service.d.ts +2 -1
  32. package/dist/services/user.service.d.ts.map +1 -1
  33. package/dist/services/user.service.js +8 -5
  34. package/dist/services/user.service.js.map +1 -1
  35. package/dist/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +1 -1
  37. package/src/controllers/ai-interaction.controller.ts +1 -1
  38. package/src/controllers/service.controller.ts +2 -2
  39. package/src/dtos/invoke-ai-prompt.dto.ts +5 -1
  40. package/src/entities/user.entity.ts +2 -0
  41. package/src/interfaces.ts +2 -1
  42. package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +108 -25
  43. package/src/repository/chatter-message-details.repository.ts +6 -5
  44. package/src/repository/chatter-message.repository.ts +2 -0
  45. package/src/seeders/seed-data/solid-core-metadata.json +39 -4
  46. package/src/services/ai-interaction.service.ts +24 -3
  47. package/src/services/user.service.ts +4 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidstarters/solid-core",
3
- "version": "1.2.172",
3
+ "version": "1.2.174",
4
4
  "description": "This module is a NestJS module containing all the required core providers required by a Solid application",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -95,7 +95,7 @@ export class AiInteractionController {
95
95
  @ApiBearerAuth("jwt")
96
96
  @Post('/trigger-mcp-client-job')
97
97
  async triggerMcpClientJob(@Body() dto: InvokeAiPromptDto, @ActiveUser() activeUser: ActiveUserData) {
98
- return this.service.triggerMcpClientJob(dto.prompt, activeUser.sub);
98
+ return this.service.triggerMcpClientJob(dto,activeUser.sub);
99
99
  }
100
100
 
101
101
  @ApiBearerAuth("jwt")
@@ -36,9 +36,9 @@ export class ServiceController {
36
36
  // If failure then decide shape to return.
37
37
 
38
38
  const threadId = `pingPongTxn-${activeUser.sub}`;
39
-
39
+ const dto ={prompt:"Can you do 1 + 1", moduleName:"solidCoreModule"}
40
40
  const { queueMessageId, aiInteractionId } = await this.aiInteractionService.triggerMcpClientJob(
41
- `Can you do 1 + 1`,
41
+ dto,
42
42
  activeUser.sub,
43
43
  true,
44
44
  threadId
@@ -1,6 +1,10 @@
1
- import { IsString } from 'class-validator';
1
+ import { IsOptional, IsString } from 'class-validator';
2
2
 
3
3
  export class InvokeAiPromptDto {
4
4
  @IsString()
5
5
  prompt: string;
6
+
7
+ @IsOptional()
8
+ @IsString()
9
+ moduleName: string;
6
10
  }
@@ -111,4 +111,6 @@ export class User extends CommonEntity {
111
111
  // dont send to client
112
112
  @Column({ type: "timestamp", nullable: true })
113
113
  rehashedAt: Date;
114
+ @Expose()
115
+ _media: any;
114
116
  }
package/src/interfaces.ts CHANGED
@@ -66,6 +66,7 @@ export interface CodeGenerationOptions {
66
66
 
67
67
  export interface TriggerMcpClientOptions {
68
68
  aiInteractionId: number;
69
+ moduleName:string;
69
70
  }
70
71
 
71
72
  export interface McpResponse {
@@ -77,7 +78,7 @@ export interface McpResponse {
77
78
  tool_calls?: any[];
78
79
  duration_ms?: number;
79
80
  errors?: string[];
80
- trace?: string[];
81
+ error_trace?: string[];
81
82
  content_type?: string;
82
83
  }
83
84
 
@@ -3,7 +3,7 @@ import { Injectable, Logger } from '@nestjs/common';
3
3
  import { QueueMessage } from 'src/interfaces/mq';
4
4
  import { MqMessageService } from '../../services/mq-message.service';
5
5
  import { MqMessageQueueService } from '../../services/mq-message-queue.service';
6
- import { QueuesModuleOptions, TriggerMcpClientOptions } from "../../interfaces";
6
+ import { McpResponse, QueuesModuleOptions, TriggerMcpClientOptions } from "../../interfaces";
7
7
  import { DatabaseSubscriber } from 'src/services/queues/database-subscriber.service';
8
8
  import triggerMcpClientQueueOptions from "./trigger-mcp-client-queue-options";
9
9
  import { AiInteractionService } from 'src/services/ai-interaction.service';
@@ -28,6 +28,36 @@ export class TriggerMcpClientSubscriberDatabase extends DatabaseSubscriber<Trigg
28
28
  }
29
29
  }
30
30
 
31
+ cleanNestedResponse(aiResponse: McpResponse) {
32
+ let nestedResponse: any;
33
+
34
+ try {
35
+ let raw = aiResponse.response;
36
+
37
+ if (typeof raw === "string") {
38
+ raw = raw.trim();
39
+ try {
40
+ // Try to parse as JSON
41
+ nestedResponse = JSON.parse(raw);
42
+ } catch {
43
+ // Not JSON, just keep as string
44
+ nestedResponse = raw;
45
+ }
46
+ } else if (typeof raw === "object" && raw !== null) {
47
+ // Already JSON
48
+ nestedResponse = raw;
49
+ } else {
50
+ // Fallback
51
+ nestedResponse = String(raw);
52
+ }
53
+ } catch (err) {
54
+ this.triggerMcpClientSubscriberLogger.error("Error processing AI response:", err);
55
+ nestedResponse = `Error handling response: ${err?.message || String(err)}`;
56
+ }
57
+
58
+ return nestedResponse;
59
+ }
60
+
31
61
  async subscribe(message: QueueMessage<TriggerMcpClientOptions>) {
32
62
  this.triggerMcpClientSubscriberLogger.debug(`Received message: ${JSON.stringify(message)}`);
33
63
 
@@ -48,50 +78,103 @@ export class TriggerMcpClientSubscriberDatabase extends DatabaseSubscriber<Trigg
48
78
  // Use this to invoke our mcp client
49
79
  // TODO: try / catch ...
50
80
  // Handle the rejection gracefully...
51
- const aiResponse = await this.aiInteractionService.runMcpPrompt(prompt);
81
+
82
+ // We create the aiInteraction entry first
83
+ const genAiInteraction = await this.aiInteractionService.create({
84
+ userId: aiInteraction.user.id,
85
+ threadId: aiInteraction.threadId,
86
+ parentInteractionId: aiInteraction.id,
87
+ role: 'gen-ai',
88
+ message: '...', // Updated in the tool
89
+ contentType: '', // Updated in the tool
90
+ errorMessage: '', // Updated after we receive the response
91
+ modelUsed: '', // Updated after we receive the response
92
+ responseTimeMs: 0, // Updated after we receive the response
93
+ metadata: '', // Updated in the tool
94
+ isApplied: false, // Updated after we receive the response
95
+ status: '' // Updated after we receive the response
96
+ });
97
+
98
+ const finalPrompt = `
99
+ # User Prompt:
100
+ ${prompt}
101
+
102
+ # System Instructions:
103
+ - aiInteractionId: ${genAiInteraction.id}
104
+ - moduleName:${message.payload.moduleName}
105
+ - You will be invoking tools if needed.
106
+ - If a tool is invoked, you must return **exactly** the raw output from the tool, without any additional formatting, commentary, or text.
107
+ - Do not wrap the result in quotes, JSON, or markdown fences.
108
+ - Do not explain what the result means.
109
+ - Your final response must be identical to the tool output.
110
+ `
111
+
112
+ const aiResponse = await this.aiInteractionService.runMcpPrompt(finalPrompt);
52
113
  this.triggerMcpClientSubscriberLogger.log(`aiResponse: `);
53
114
  this.triggerMcpClientSubscriberLogger.log(JSON.stringify(aiResponse));
54
115
 
55
116
  if (!aiResponse.success) {
56
117
  this.triggerMcpClientSubscriberLogger.log(`Gen ai has returned with a false status code`);
57
118
 
58
- const errorsStr = aiResponse.errors.join('; ');
59
-
60
- await this.aiInteractionService.create({
61
- userId: aiInteraction.user.id,
62
- threadId: aiInteraction.threadId,
63
- parentInteractionId: aiInteraction.id,
64
- role: 'gen-ai',
65
- message: '-',
66
- contentType: aiResponse.content_type,
67
- errorMessage: errorsStr,
119
+ const errorsStr = aiResponse.errors.join('\n ');
120
+ const errorTrace = aiResponse.error_trace.join('\n');
121
+
122
+ // await this.aiInteractionService.create({
123
+ // userId: aiInteraction.user.id,
124
+ // threadId: aiInteraction.threadId,
125
+ // parentInteractionId: aiInteraction.id,
126
+ // role: 'gen-ai',
127
+ // message: '-',
128
+ // contentType: aiResponse.content_type,
129
+ // errorMessage: errorsStr,
130
+ // modelUsed: aiResponse.model,
131
+ // responseTimeMs: aiResponse.duration_ms,
132
+ // metadata: JSON.stringify(aiResponse, null, 2),
133
+ // isApplied: aiInteraction.isApplied,
134
+ // status: aiResponse.success ? 'succeeded' : 'failed'
135
+ // });
136
+
137
+ // TODO: Update the previously created genAiInteraction record with the respective error fields and save to DB
138
+ await this.aiInteractionService.update(genAiInteraction.id, {
139
+ // contentType: aiResponse.content_type,
140
+ errorMessage: `${errorsStr}\n\n${errorTrace}`,
68
141
  modelUsed: aiResponse.model,
69
142
  responseTimeMs: aiResponse.duration_ms,
70
- metadata: JSON.stringify(aiResponse, null, 2),
71
143
  isApplied: aiInteraction.isApplied,
72
144
  status: aiResponse.success ? 'succeeded' : 'failed'
73
- });
145
+ }, [], true);
74
146
 
75
147
  // update the job entry with failure... raising an error will lead the job to be marked as failed...
76
148
  throw new Error(errorsStr);
77
149
  }
78
150
  else {
79
- let nestedResponse = aiResponse.response.trim();
80
-
81
- const genAiInteraction = await this.aiInteractionService.create({
82
- userId: aiInteraction.user.id,
83
- threadId: aiInteraction.threadId,
84
- parentInteractionId: aiInteraction.id,
85
- role: 'gen-ai',
86
- message: nestedResponse,
87
- contentType: aiResponse.content_type,
151
+ // let nestedResponse = this.cleanNestedResponse(aiResponse);
152
+
153
+ // const genAiInteraction = await this.aiInteractionService.create({
154
+ // userId: aiInteraction.user.id,
155
+ // threadId: aiInteraction.threadId,
156
+ // parentInteractionId: aiInteraction.id,
157
+ // role: 'gen-ai',
158
+ // message: nestedResponse,
159
+ // contentType: aiResponse.content_type,
160
+ // errorMessage: '',
161
+ // modelUsed: aiResponse.model,
162
+ // responseTimeMs: aiResponse.duration_ms,
163
+ // metadata: JSON.stringify(aiResponse, null, 2),
164
+ // isApplied: aiInteraction.isApplied,
165
+ // status: aiResponse.success ? 'succeeded' : 'failed'
166
+ // });
167
+
168
+ // TODO: Update the previously created genAiInteraction record with the respective success fields and save to DB
169
+ await this.aiInteractionService.update(genAiInteraction.id, {
170
+ // contentType: aiResponse.content_type,
88
171
  errorMessage: '',
172
+ // message: nestedResponse,
89
173
  modelUsed: aiResponse.model,
90
174
  responseTimeMs: aiResponse.duration_ms,
91
- metadata: JSON.stringify(aiResponse, null, 2),
92
175
  isApplied: aiInteraction.isApplied,
93
176
  status: aiResponse.success ? 'succeeded' : 'failed'
94
- });
177
+ }, [], true);
95
178
 
96
179
  // If the human interaction was with isAutoApply=true, then we can go ahead and autoApply.
97
180
  if (aiInteraction.isAutoApply) {
@@ -37,17 +37,16 @@ export class ChatterMessageDetailsRepository extends SolidBaseRepository<Chatter
37
37
  ): SelectQueryBuilder<ChatterMessageDetails> {
38
38
  const activeUser = this.requestContextService.getActiveUser();
39
39
  let qb = super.createQueryBuilder(alias, queryRunner);
40
-
41
- // Join the real relation so we can access co_model_* fields
42
- qb = qb.leftJoin(`${alias}.chatterMessage`, 'chatterMessage');
40
+ if (!activeUser) return qb;
43
41
 
44
42
  // Example: join the "client" co-model (pass whatever co-model name you need)
45
43
  const [coModelName, coModelAlias] = this.getCoModelNameAndAlias();
44
+ if (!coModelName) return qb;
46
45
 
46
+ // Join the real relation so we can access co_model_* fields
47
+ qb = qb.leftJoin(`${alias}.chatterMessage`, 'chatterMessage');
47
48
  qb = this.leftJoinCoModel(qb, coModelName, 'chatterMessage');
48
49
 
49
- if (!activeUser) return qb;
50
-
51
50
  // If your security rules should apply to the co-model rows, pass the co-model alias.
52
51
  // Here we use the co-model name "client" both as model key and alias base for consistency.
53
52
  return this.securityRuleRepository.applySecurityRules(
@@ -102,6 +101,8 @@ export class ChatterMessageDetailsRepository extends SolidBaseRepository<Chatter
102
101
  if (!requestFilter) return [undefined, undefined];
103
102
 
104
103
  const coModelName = get(requestFilter, this.CO_MODEL_NAME_PATH);
104
+ if (!coModelName) return [undefined, undefined];
105
+
105
106
  const alias = camelize(coModelName);
106
107
  return [coModelName, alias];
107
108
  }
@@ -26,6 +26,7 @@ export class ChatterMessageRepository extends SolidBaseRepository<ChatterMessage
26
26
 
27
27
  //Left join on the associated chatter model entity
28
28
  const [coModelName, coModelAlias] = this.getCoModelNameAndAlias();
29
+ if (!coModelName) return qb;
29
30
  qb = this.leftJoinCoModel(qb, coModelName);
30
31
 
31
32
  return this.securityRuleRepository.applySecurityRules(
@@ -61,6 +62,7 @@ export class ChatterMessageRepository extends SolidBaseRepository<ChatterMessage
61
62
  if (!requestFilter) return [undefined, undefined];
62
63
 
63
64
  const coModelName = get(requestFilter, this.CO_MODEL_NAME_PATH);
65
+ if (!coModelName) return [undefined, undefined];
64
66
  const alias = camelize(coModelName);
65
67
  return [coModelName, alias];
66
68
  }
@@ -12059,7 +12059,7 @@
12059
12059
  {
12060
12060
  "type": "field",
12061
12061
  "attrs": {
12062
- "name": "modelUsed"
12062
+ "name": "contentType"
12063
12063
  }
12064
12064
  },
12065
12065
  {
@@ -12067,6 +12067,24 @@
12067
12067
  "attrs": {
12068
12068
  "name": "responseTimeMs"
12069
12069
  }
12070
+ },
12071
+ {
12072
+ "type": "field",
12073
+ "attrs": {
12074
+ "name": "inputTokens"
12075
+ }
12076
+ },
12077
+ {
12078
+ "type": "field",
12079
+ "attrs": {
12080
+ "name": "outputTokens"
12081
+ }
12082
+ },
12083
+ {
12084
+ "type": "field",
12085
+ "attrs": {
12086
+ "name": "totalTokens"
12087
+ }
12070
12088
  }
12071
12089
  ]
12072
12090
  }
@@ -12184,6 +12202,24 @@
12184
12202
  "attrs": {
12185
12203
  "name": "parentInteraction"
12186
12204
  }
12205
+ },
12206
+ {
12207
+ "type": "field",
12208
+ "attrs": {
12209
+ "name": "inputTokens"
12210
+ }
12211
+ },
12212
+ {
12213
+ "type": "field",
12214
+ "attrs": {
12215
+ "name": "outputTokens"
12216
+ }
12217
+ },
12218
+ {
12219
+ "type": "field",
12220
+ "attrs": {
12221
+ "name": "totalTokens"
12222
+ }
12187
12223
  }
12188
12224
  ]
12189
12225
  }
@@ -12249,7 +12285,7 @@
12249
12285
  "type": "column",
12250
12286
  "attrs": {
12251
12287
  "name": "col-meta",
12252
- "label": "Metadata",
12288
+ "label": "",
12253
12289
  "className": "col-12"
12254
12290
  },
12255
12291
  "children": [
@@ -12257,8 +12293,7 @@
12257
12293
  "type": "field",
12258
12294
  "attrs": {
12259
12295
  "name": "metadata",
12260
- "editWidget": "codeEditor",
12261
- "editorLanguage": "json"
12296
+ "height": "80vh"
12262
12297
  }
12263
12298
  }
12264
12299
  ]
@@ -18,6 +18,7 @@ import { RequestContextService } from './request-context.service';
18
18
  import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
19
19
  import { McpToolResponseHandlerFactory } from './mcp-tool-response-handlers/mcp-tool-response-handler-factory.service';
20
20
  import { ERROR_MESSAGES } from 'src/constants/error-messages';
21
+ import { InvokeAiPromptDto } from 'src/dtos/invoke-ai-prompt.dto';
21
22
 
22
23
  @Injectable()
23
24
  export class AiInteractionService extends CRUDService<AiInteraction> {
@@ -43,14 +44,14 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
43
44
  super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'aiInteraction', 'solid-core', moduleRef);
44
45
  }
45
46
 
46
- async triggerMcpClientJob(prompt: string, userId: number, isAutoApply: boolean = false, threadId: string = null): Promise<any> {
47
+ async triggerMcpClientJob(dto: InvokeAiPromptDto, userId: number, isAutoApply: boolean = false, threadId: string = null): Promise<any> {
47
48
  // const activeUser: ActiveUserData = this.requestContextService.getActiveUser();
48
49
 
49
50
  const aiInteraction = await this.create({
50
51
  userId: userId,
51
52
  threadId: threadId ? threadId : `thread-${userId}`,
52
53
  role: 'human',
53
- message: prompt,
54
+ message: dto.prompt,
54
55
  contentType: '',
55
56
  errorMessage: '',
56
57
  modelUsed: '',
@@ -61,6 +62,7 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
61
62
  const m = {
62
63
  payload: {
63
64
  aiInteractionId: aiInteraction.id,
65
+ moduleName:dto.moduleName
64
66
  },
65
67
  parentEntity: 'aiInteraction',
66
68
  parentEntityId: aiInteraction.id,
@@ -137,6 +139,25 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
137
139
  this.logger.log(`Python script exited with zero exit code: ${stdout}`);
138
140
  const raw: McpResponse = JSON.parse(stdout);
139
141
 
142
+ // Sometimes the raw.response might not be a valid json
143
+ // TODO: examine the content type of the raw response..
144
+ // if (raw.content_type==='json') {
145
+ // }
146
+ let parsedResponse = raw.response;
147
+ try {
148
+ parsedResponse = JSON.parse(raw.response);
149
+ }
150
+ catch (ex) {
151
+ this.logger.warn(`Attempting to parse mcp client response assuming it is JSON, however it is not: ${parsedResponse}`);
152
+ }
153
+ // Parse the response string into an object
154
+ // const parsedResponse = JSON.parse(raw.response);
155
+
156
+ // Replace the string with the parsed object
157
+ const enrichedRaw = {
158
+ ...raw,
159
+ response: parsedResponse,
160
+ };
140
161
  // if (!raw.success) {
141
162
  // return reject(new Error(`MCP error: ${raw.errors?.join(', ')}`));
142
163
  // }
@@ -146,7 +167,7 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
146
167
  // const parsed = JSON.parse(cleaned);
147
168
  // resolve(cleaned);
148
169
 
149
- resolve(raw);
170
+ resolve(enrichedRaw);
150
171
  } catch (err: any) {
151
172
  reject(new Error(`Mcp Invocation Failed: ${err.message}`));
152
173
  }
@@ -31,6 +31,8 @@ export class UserService extends CRUDService<User> {
31
31
  readonly entityManager: EntityManager,
32
32
  // @InjectRepository(User, 'default')
33
33
  readonly repo: UserRepository,
34
+ @InjectRepository(User, 'default')
35
+ readonly nonSecurityRuleAwareRepo : Repository<User>,
34
36
  @InjectRepository(RoleMetadata)
35
37
  private readonly roleRepository: Repository<RoleMetadata>,
36
38
  readonly moduleRef: ModuleRef,
@@ -141,7 +143,7 @@ export class UserService extends CRUDService<User> {
141
143
  }
142
144
 
143
145
  async addRolesToUser(username: string, roleNames: string[]): Promise<User> {
144
- const user = await this.repo.findOne({
146
+ const user = await this.nonSecurityRuleAwareRepo.findOne({
145
147
  where: { username: username },
146
148
  relations: { roles: true }
147
149
  });
@@ -174,7 +176,7 @@ export class UserService extends CRUDService<User> {
174
176
  user.roles = user.roles.filter(role => !rolesToRemove.includes(role));
175
177
  }
176
178
 
177
- return await this.repo.save(user);
179
+ return await this.nonSecurityRuleAwareRepo.save(user);
178
180
  }
179
181
 
180
182