@solidstarters/solid-core 1.2.148 → 1.2.149
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/controllers/ai-interaction.controller.d.ts +1 -1
- package/dist/controllers/ai-interaction.controller.js +1 -1
- package/dist/dtos/create-ai-interaction.dto.d.ts +3 -0
- package/dist/dtos/create-ai-interaction.dto.d.ts.map +1 -1
- package/dist/dtos/create-ai-interaction.dto.js +20 -1
- package/dist/dtos/create-ai-interaction.dto.js.map +1 -1
- package/dist/dtos/update-ai-interaction.dto.d.ts +3 -0
- package/dist/dtos/update-ai-interaction.dto.d.ts.map +1 -1
- package/dist/dtos/update-ai-interaction.dto.js +19 -1
- package/dist/dtos/update-ai-interaction.dto.js.map +1 -1
- package/dist/entities/ai-interaction.entity.d.ts +3 -0
- package/dist/entities/ai-interaction.entity.d.ts.map +1 -1
- package/dist/entities/ai-interaction.entity.js +17 -1
- package/dist/entities/ai-interaction.entity.js.map +1 -1
- package/dist/helpers/environment.helper.d.ts +2 -0
- package/dist/helpers/environment.helper.d.ts.map +1 -0
- package/dist/helpers/environment.helper.js +11 -0
- package/dist/helpers/environment.helper.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js +2 -0
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +83 -3
- package/dist/services/ai-interaction.service.d.ts +1 -1
- package/dist/services/ai-interaction.service.d.ts.map +1 -1
- package/dist/services/ai-interaction.service.js +5 -1
- package/dist/services/ai-interaction.service.js.map +1 -1
- package/dist/services/mq-message.service.d.ts +9 -0
- package/dist/services/mq-message.service.d.ts.map +1 -1
- package/dist/services/mq-message.service.js +61 -0
- package/dist/services/mq-message.service.js.map +1 -1
- package/dist/services/poller.service.js +1 -1
- package/dist/services/poller.service.js.map +1 -1
- package/dist/services/queues/database-subscriber.service.js +1 -1
- package/dist/services/queues/database-subscriber.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +2 -1
- package/dist/solid-core.module.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/dtos/create-ai-interaction.dto.ts +16 -5
- package/src/dtos/update-ai-interaction.dto.ts +16 -5
- package/src/entities/ai-interaction.entity.ts +11 -3
- package/src/helpers/environment.helper.ts +7 -0
- package/src/index.ts +3 -0
- package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +2 -0
- package/src/seeders/seed-data/solid-core-metadata.json +83 -3
- package/src/services/ai-interaction.service.ts +8 -3
- package/src/services/mq-message.service.ts +116 -0
- package/src/services/poller.service.ts +1 -1
- package/src/services/queues/database-subscriber.service.ts +1 -1
- package/src/solid-core.module.ts +2 -1
|
@@ -4919,7 +4919,7 @@
|
|
|
4919
4919
|
"tableName": "ss_ai_interactions",
|
|
4920
4920
|
"dataSource": "default",
|
|
4921
4921
|
"dataSourceType": "postgres",
|
|
4922
|
-
"userKeyFieldUserKey": "
|
|
4922
|
+
"userKeyFieldUserKey": "externalId",
|
|
4923
4923
|
"isSystem": false,
|
|
4924
4924
|
"fields": [
|
|
4925
4925
|
{
|
|
@@ -4938,6 +4938,35 @@
|
|
|
4938
4938
|
"relationModelModuleName": "solid-core",
|
|
4939
4939
|
"isSystem": true
|
|
4940
4940
|
},
|
|
4941
|
+
{
|
|
4942
|
+
"name": "externalId",
|
|
4943
|
+
"displayName": "External ID",
|
|
4944
|
+
"description": "Used to track using a reference number of each ai interaction.",
|
|
4945
|
+
"type": "computed",
|
|
4946
|
+
"ormType": "varchar",
|
|
4947
|
+
"isSystem": false,
|
|
4948
|
+
"computedFieldValueType": "string",
|
|
4949
|
+
"computedFieldTriggerConfig": [
|
|
4950
|
+
{
|
|
4951
|
+
"modelName": "aiInteraction",
|
|
4952
|
+
"moduleName": "solid-core",
|
|
4953
|
+
"operations": [
|
|
4954
|
+
"before-insert"
|
|
4955
|
+
]
|
|
4956
|
+
}
|
|
4957
|
+
],
|
|
4958
|
+
"computedFieldValueProvider": "AlphaNumExternalIdComputationProvider",
|
|
4959
|
+
"computedFieldValueProviderCtxt": "{\n \"prefix\": \"AI\",\n \"length\": \"10\"\n}",
|
|
4960
|
+
"required": true,
|
|
4961
|
+
"unique": true,
|
|
4962
|
+
"index": true,
|
|
4963
|
+
"private": false,
|
|
4964
|
+
"encrypt": false,
|
|
4965
|
+
"encryptionType": null,
|
|
4966
|
+
"decryptWhen": null,
|
|
4967
|
+
"columnName": null,
|
|
4968
|
+
"isUserKey": true
|
|
4969
|
+
},
|
|
4941
4970
|
{
|
|
4942
4971
|
"name": "threadId",
|
|
4943
4972
|
"displayName": "Thread ID",
|
|
@@ -4950,6 +4979,22 @@
|
|
|
4950
4979
|
"private": false,
|
|
4951
4980
|
"encrypt": false
|
|
4952
4981
|
},
|
|
4982
|
+
{
|
|
4983
|
+
"name": "parentInteraction",
|
|
4984
|
+
"displayName": "Parent Interaction",
|
|
4985
|
+
"type": "relation",
|
|
4986
|
+
"required": false,
|
|
4987
|
+
"unique": false,
|
|
4988
|
+
"index": true,
|
|
4989
|
+
"private": false,
|
|
4990
|
+
"encrypt": false,
|
|
4991
|
+
"relationType": "many-to-one",
|
|
4992
|
+
"relationCoModelSingularName": "aiInteraction",
|
|
4993
|
+
"relationCreateInverse": false,
|
|
4994
|
+
"relationCascade": "set null",
|
|
4995
|
+
"relationModelModuleName": "solid-core",
|
|
4996
|
+
"isSystem": true
|
|
4997
|
+
},
|
|
4953
4998
|
{
|
|
4954
4999
|
"name": "role",
|
|
4955
5000
|
"displayName": "Role",
|
|
@@ -5057,6 +5102,17 @@
|
|
|
5057
5102
|
"index": false,
|
|
5058
5103
|
"private": false,
|
|
5059
5104
|
"encrypt": false
|
|
5105
|
+
},
|
|
5106
|
+
{
|
|
5107
|
+
"name": "isAutoApply",
|
|
5108
|
+
"displayName": "Is Auto Apply",
|
|
5109
|
+
"type": "boolean",
|
|
5110
|
+
"ormType": "boolean",
|
|
5111
|
+
"required": false,
|
|
5112
|
+
"unique": false,
|
|
5113
|
+
"index": false,
|
|
5114
|
+
"private": false,
|
|
5115
|
+
"encrypt": false
|
|
5060
5116
|
}
|
|
5061
5117
|
]
|
|
5062
5118
|
}
|
|
@@ -11867,7 +11923,7 @@
|
|
|
11867
11923
|
}
|
|
11868
11924
|
},
|
|
11869
11925
|
{
|
|
11870
|
-
"name": "
|
|
11926
|
+
"name": "aiInteraction-list-view",
|
|
11871
11927
|
"displayName": "AI Interaction",
|
|
11872
11928
|
"type": "list",
|
|
11873
11929
|
"context": "{}",
|
|
@@ -11900,6 +11956,18 @@
|
|
|
11900
11956
|
"name": "threadId"
|
|
11901
11957
|
}
|
|
11902
11958
|
},
|
|
11959
|
+
{
|
|
11960
|
+
"type": "field",
|
|
11961
|
+
"attrs": {
|
|
11962
|
+
"name": "externalId"
|
|
11963
|
+
}
|
|
11964
|
+
},
|
|
11965
|
+
{
|
|
11966
|
+
"type": "field",
|
|
11967
|
+
"attrs": {
|
|
11968
|
+
"name": "parentInteraction"
|
|
11969
|
+
}
|
|
11970
|
+
},
|
|
11903
11971
|
{
|
|
11904
11972
|
"type": "field",
|
|
11905
11973
|
"attrs": {
|
|
@@ -11934,7 +12002,7 @@
|
|
|
11934
12002
|
}
|
|
11935
12003
|
},
|
|
11936
12004
|
{
|
|
11937
|
-
"name": "
|
|
12005
|
+
"name": "aiInteraction-form-view",
|
|
11938
12006
|
"displayName": "AI Interaction",
|
|
11939
12007
|
"type": "form",
|
|
11940
12008
|
"context": "{}",
|
|
@@ -11987,6 +12055,12 @@
|
|
|
11987
12055
|
"name": "user"
|
|
11988
12056
|
}
|
|
11989
12057
|
},
|
|
12058
|
+
{
|
|
12059
|
+
"type": "field",
|
|
12060
|
+
"attrs": {
|
|
12061
|
+
"name": "externalId"
|
|
12062
|
+
}
|
|
12063
|
+
},
|
|
11990
12064
|
{
|
|
11991
12065
|
"type": "field",
|
|
11992
12066
|
"attrs": {
|
|
@@ -12034,6 +12108,12 @@
|
|
|
12034
12108
|
"attrs": {
|
|
12035
12109
|
"name": "responseTimeMs"
|
|
12036
12110
|
}
|
|
12111
|
+
},
|
|
12112
|
+
{
|
|
12113
|
+
"type": "field",
|
|
12114
|
+
"attrs": {
|
|
12115
|
+
"name": "parentInteraction"
|
|
12116
|
+
}
|
|
12037
12117
|
}
|
|
12038
12118
|
]
|
|
12039
12119
|
}
|
|
@@ -26,7 +26,7 @@ export declare class AiInteractionService extends CRUDService<AiInteraction> {
|
|
|
26
26
|
readonly mcpToolResponseHandlerFactory: McpToolResponseHandlerFactory;
|
|
27
27
|
private readonly logger;
|
|
28
28
|
constructor(modelMetadataService: ModelMetadataService, moduleMetadataService: ModuleMetadataService, configService: ConfigService, fileService: FileService, discoveryService: DiscoveryService, crudHelperService: CrudHelperService, entityManager: EntityManager, repo: Repository<AiInteraction>, moduleRef: ModuleRef, publisherFactory: PublisherFactory<TriggerMcpClientOptions>, requestContextService: RequestContextService, mcpToolResponseHandlerFactory: McpToolResponseHandlerFactory);
|
|
29
|
-
triggerMcpClientJob(prompt: string): Promise<
|
|
29
|
+
triggerMcpClientJob(prompt: string): Promise<any>;
|
|
30
30
|
runMcpPrompt(prompt: string): Promise<McpResponse>;
|
|
31
31
|
cleanResponse(response: string): string;
|
|
32
32
|
applySolidAiInteraction(id: number): Promise<any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-interaction.service.d.ts","sourceRoot":"","sources":["../../src/services/ai-interaction.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,6BAA6B,EAAE,MAAM,wEAAwE,CAAC;AAGvH,qBACa,oBAAqB,SAAQ,WAAW,CAAC,aAAa,CAAC;IAIhE,QAAQ,CAAC,oBAAoB,EAAE,oBAAoB;IACnD,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB;IACrD,QAAQ,CAAC,aAAa,EAAE,aAAa;IACrC,QAAQ,CAAC,WAAW,EAAE,WAAW;IACjC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB;IAC3C,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB;IAE7C,QAAQ,CAAC,aAAa,EAAE,aAAa;IAErC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,SAAS;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,uBAAuB,CAAC;IACpE,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB;IACrD,QAAQ,CAAC,6BAA6B,EAAE,6BAA6B;IAhBvE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;gBAGrD,oBAAoB,EAAE,oBAAoB,EAC1C,qBAAqB,EAAE,qBAAqB,EAC5C,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,gBAAgB,EAClC,iBAAiB,EAAE,iBAAiB,EAEpC,aAAa,EAAE,aAAa,EAE5B,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,EAC/B,SAAS,EAAE,SAAS,EACpB,gBAAgB,EAAE,gBAAgB,CAAC,uBAAuB,CAAC,EAC3D,qBAAqB,EAAE,qBAAqB,EAC5C,6BAA6B,EAAE,6BAA6B;IAMjE,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"ai-interaction.service.d.ts","sourceRoot":"","sources":["../../src/services/ai-interaction.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,6BAA6B,EAAE,MAAM,wEAAwE,CAAC;AAGvH,qBACa,oBAAqB,SAAQ,WAAW,CAAC,aAAa,CAAC;IAIhE,QAAQ,CAAC,oBAAoB,EAAE,oBAAoB;IACnD,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB;IACrD,QAAQ,CAAC,aAAa,EAAE,aAAa;IACrC,QAAQ,CAAC,WAAW,EAAE,WAAW;IACjC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB;IAC3C,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB;IAE7C,QAAQ,CAAC,aAAa,EAAE,aAAa;IAErC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,SAAS;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,uBAAuB,CAAC;IACpE,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB;IACrD,QAAQ,CAAC,6BAA6B,EAAE,6BAA6B;IAhBvE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;gBAGrD,oBAAoB,EAAE,oBAAoB,EAC1C,qBAAqB,EAAE,qBAAqB,EAC5C,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,gBAAgB,EAClC,iBAAiB,EAAE,iBAAiB,EAEpC,aAAa,EAAE,aAAa,EAE5B,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,EAC/B,SAAS,EAAE,SAAS,EACpB,gBAAgB,EAAE,gBAAgB,CAAC,uBAAuB,CAAC,EAC3D,qBAAqB,EAAE,qBAAqB,EAC5C,6BAA6B,EAAE,6BAA6B;IAMjE,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAmCjD,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IA2ExD,aAAa,CAAC,QAAQ,EAAE,MAAM;IAexB,uBAAuB,CAAC,EAAE,EAAE,MAAM;CAiDzC"}
|
|
@@ -101,7 +101,11 @@ let AiInteractionService = AiInteractionService_1 = class AiInteractionService e
|
|
|
101
101
|
parentEntity: 'aiInteraction',
|
|
102
102
|
parentEntityId: aiInteraction.id,
|
|
103
103
|
};
|
|
104
|
-
|
|
104
|
+
const queueMessageId = await this.publisherFactory.publish(m, 'TriggerMcpClientPublisher');
|
|
105
|
+
return {
|
|
106
|
+
queueMessageId: queueMessageId,
|
|
107
|
+
aiInteractionId: aiInteraction.id
|
|
108
|
+
};
|
|
105
109
|
}
|
|
106
110
|
async runMcpPrompt(prompt) {
|
|
107
111
|
const pythonExecutable = process.env.MCP_PYTHON_EXECUTABLE;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-interaction.service.js","sourceRoot":"","sources":["../../src/services/ai-interaction.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAyE;AACzE,6CAAwE;AACxE,uCAA2D;AAC3D,qCAAoD;AAEpD,iDAAwD;AACxD,qEAA2E;AAC3E,uEAA6E;AAC7E,2CAA+C;AAC/C,iDAAwD;AACxD,+DAAqE;AACrE,iDAAsC;AACtC,6EAAkE;AAClE,gDAAkC;AAElC,kFAAsE;AACtE,uEAAkE;AAElE,sIAAuH;AACvH,gEAA8D;AAGvD,IAAM,oBAAoB,4BAA1B,MAAM,oBAAqB,SAAQ,0BAA0B;IAGlE,YACW,oBAA0C,EAC1C,qBAA4C,EAC5C,aAA4B,EAC5B,WAAwB,EACxB,gBAAkC,EAClC,iBAAoC,EAE7C,aAAqC,EAErC,IAAwC,EAC/B,SAAoB,EACpB,gBAA2D,EAC3D,qBAA4C,EAC5C,6BAA4D;QAGrE,KAAK,CAAC,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,IAAI,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QAhB1K,yBAAoB,GAApB,oBAAoB,CAAsB;QAC1C,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,kBAAa,GAAb,aAAa,CAAe;QAC5B,gBAAW,GAAX,WAAW,CAAa;QACxB,qBAAgB,GAAhB,gBAAgB,CAAkB;QAClC,sBAAiB,GAAjB,iBAAiB,CAAmB;QAEpC,kBAAa,GAAb,aAAa,CAAe;QAE5B,SAAI,GAAJ,IAAI,CAA2B;QAC/B,cAAS,GAAT,SAAS,CAAW;QACpB,qBAAgB,GAAhB,gBAAgB,CAA2C;QAC3D,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,kCAA6B,GAA7B,6BAA6B,CAA+B;QAhBtD,WAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IAoBhE,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,MAAc;QACtC,MAAM,UAAU,GAAmB,IAAI,CAAC,qBAAqB,CAAC,aAAa,EAAE,CAAC;QAE9E,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC;YACtC,MAAM,EAAE,UAAU,CAAC,GAAG;YACtB,QAAQ,EAAE,UAAU,UAAU,CAAC,GAAG,EAAE;YACpC,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,EAAE;YAChB,SAAS,EAAE,EAAE;YACb,cAAc,EAAE,CAAC;YACjB,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC;QACH,MAAM,CAAC,GAAG;YACR,OAAO,EAAE;gBACP,eAAe,EAAE,aAAa,CAAC,EAAE;aAClC;YACD,YAAY,EAAE,eAAe;YAC7B,cAAc,EAAE,aAAa,CAAC,EAAE;SACjC,CAAC;QAEF,OAAO,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,EAAE,2BAA2B,CAAC,CAAC;IAC7E,CAAC;IAOD,KAAK,CAAC,YAAY,CAAC,MAAc;QAC/B,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;QAC3D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;QAGzC,IAAI,CAAC,gBAAgB,IAAI,CAAC,SAAS,EAAE,CAAC;YACpC,MAAM,IAAI,4BAAmB,CAAC,+BAAc,CAAC,gCAAgC,CAAC,CAAC;QACjF,CAAC;QAGD,IAAI,CAAC;YACH,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBAC7C,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC;gBACzB,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC;aACnB,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;gBACrB,MAAM,IAAI,4BAAmB,CAAC,6CAA6C,gBAAgB,EAAE,CAAC,CAAC;YACjG,CAAC;YAED,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;gBACzB,MAAM,IAAI,4BAAmB,CAAC,kCAAkC,SAAS,EAAE,CAAC,CAAC;YAC/E,CAAC;QAEH,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,4BAAmB,CAAC,0CAA0C,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACzF,CAAC;QAGD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAA;YAC7C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,IAAI,SAAS,KAAK,MAAM,GAAG,CAAC,CAAC;YAEhE,MAAM,MAAM,GAAG,IAAA,qBAAK,EAAC,gBAAgB,EAAE,CAAC,SAAS,EAAE,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC;YAEnE,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAEhB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAChC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAChC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBAC1B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;gBAE1D,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mDAAmD,MAAM,EAAE,CAAC,CAAC;oBAC/E,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,mDAAmD,MAAM,EAAE,CAAC,CAAC,CAAC;gBACxF,CAAC;gBAED,IAAI,CAAC;oBACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6CAA6C,MAAM,EAAE,CAAC,CAAC;oBACvE,MAAM,GAAG,GAAgB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBAW5C,OAAO,CAAC,GAAG,CAAC,CAAC;gBACf,CAAC;gBAAC,OAAO,GAAQ,EAAE,CAAC;oBAClB,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBAC7D,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,QAAgB;QAC5B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;QAGvD,IAAI,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,CAAC;QACD,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACjD,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,mDAAmD,QAAQ,EAAE,CAAC,CAAC;QAE/E,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,EAAU;QAEtC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE;YAC3C,QAAQ,EAAE,CAAC,MAAM,CAAC;SACnB,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,qFAAqF,EAAE,EAAE,CAAA;YAGnG,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;QAGD,IAAI,QAAQ,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,EAAE,CAAC;YAET,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;QAED,MAAM,YAAY,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC;QAC/C,IAAI,CAAC,YAAY,EAAE,CAAC;YAElB,MAAM,IAAI,KAAK,CAAC,+BAAc,CAAC,+BAA+B,CAAC,CAAC;QAClE,CAAC;QAGD,MAAM,WAAW,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QAIpC,MAAM,cAAc,GAAG,IAAI,CAAC,6BAA6B,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACnF,IAAI,CAAC,cAAc,EAAE,CAAC;YAEpB,MAAM,IAAI,KAAK,CAAC,+BAAc,CAAC,6BAA6B,CAAC,CAAC;QAChE,CAAC;QAED,MAAM,0BAA0B,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAM7E,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAEnE,OAAO,0BAA0B,CAAC;IACpC,CAAC;CACF,CAAA;AAhMY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;IAWR,WAAA,IAAA,6BAAmB,GAAE,CAAA;IAErB,WAAA,IAAA,0BAAgB,EAAC,qCAAa,EAAE,SAAS,CAAC,CAAA;qCARZ,6CAAoB;QACnB,+CAAqB;QAC7B,sBAAa;QACf,0BAAW;QACN,uBAAgB;QACf,uCAAiB;QAErB,uBAAa;QAEtB,oBAAU;QACL,gBAAS;QACF,4CAAgB;QACX,+CAAqB;QACb,yEAA6B;GAjB5D,oBAAoB,CAgMhC","sourcesContent":["import { BadRequestException, Logger, Injectable } from '@nestjs/common';\nimport { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';\nimport { DiscoveryService, ModuleRef } from \"@nestjs/core\";\nimport { EntityManager, Repository } from 'typeorm';\n\nimport { CRUDService } from 'src/services/crud.service';\nimport { ModelMetadataService } from 'src/services/model-metadata.service';\nimport { ModuleMetadataService } from 'src/services/module-metadata.service';\nimport { ConfigService } from '@nestjs/config';\nimport { FileService } from 'src/services/file.service';\nimport { CrudHelperService } from 'src/services/crud-helper.service';\nimport { spawn } from 'child_process';\nimport { AiInteraction } from '../entities/ai-interaction.entity';\nimport * as fs from 'fs/promises';\nimport { McpResponse, TriggerMcpClientOptions } from 'src/interfaces';\nimport { PublisherFactory } from './queues/publisher-factory.service';\nimport { RequestContextService } from './request-context.service';\nimport { ActiveUserData } from 'src/interfaces/active-user-data.interface';\nimport { McpToolResponseHandlerFactory } from './mcp-tool-response-handlers/mcp-tool-response-handler-factory.service';\nimport { ERROR_MESSAGES } from 'src/constants/error-messages';\n\n@Injectable()\nexport class AiInteractionService extends CRUDService<AiInteraction> {\n private readonly logger = new Logger(AiInteractionService.name);\n\n constructor(\n readonly modelMetadataService: ModelMetadataService,\n readonly moduleMetadataService: ModuleMetadataService,\n readonly configService: ConfigService,\n readonly fileService: FileService,\n readonly discoveryService: DiscoveryService,\n readonly crudHelperService: CrudHelperService,\n @InjectEntityManager()\n readonly entityManager: EntityManager,\n @InjectRepository(AiInteraction, 'default')\n readonly repo: Repository<AiInteraction>,\n readonly moduleRef: ModuleRef,\n readonly publisherFactory: PublisherFactory<TriggerMcpClientOptions>,\n readonly requestContextService: RequestContextService,\n readonly mcpToolResponseHandlerFactory: McpToolResponseHandlerFactory,\n\n ) {\n super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'aiInteraction', 'solid-core', moduleRef);\n }\n\n async triggerMcpClientJob(prompt: string): Promise<string> {\n const activeUser: ActiveUserData = this.requestContextService.getActiveUser();\n\n const aiInteraction = await this.create({\n userId: activeUser.sub,\n threadId: `thread-${activeUser.sub}`,\n role: 'human',\n message: prompt,\n contentType: '',\n errorMessage: '',\n modelUsed: '',\n responseTimeMs: 0,\n metadata: ''\n });\n const m = {\n payload: {\n aiInteractionId: aiInteraction.id,\n },\n parentEntity: 'aiInteraction',\n parentEntityId: aiInteraction.id,\n };\n\n return await this.publisherFactory.publish(m, 'TriggerMcpClientPublisher');\n }\n\n /**\n * Runs the Python MCP client with a prompt and returns the parsed JSON embedded in the 'response'.\n * @param prompt - The question or instruction to send to the MCP client.\n * @returns The parsed object inside the 'response' field of the JSON output.\n */\n async runMcpPrompt(prompt: string): Promise<McpResponse> {\n const pythonExecutable = process.env.MCP_PYTHON_EXECUTABLE;\n const mcpClient = process.env.MCP_CLIENT;\n\n // TODO: We can return an error if the above env variables are not properly setup...\n if (!pythonExecutable || !mcpClient) {\n throw new BadRequestException(ERROR_MESSAGES.PYTHON_EXECUTABLE_NOT_CONFIGURED);\n }\n\n // Check if both paths are valid and accessible\n try {\n const [pyStat, clientStat] = await Promise.all([\n fs.stat(pythonExecutable),\n fs.stat(mcpClient),\n ]);\n\n if (!pyStat.isFile()) {\n throw new BadRequestException(`MCP_PYTHON_EXECUTABLE path is not a file: ${pythonExecutable}`);\n }\n\n if (!clientStat.isFile()) {\n throw new BadRequestException(`MCP_CLIENT path is not a file: ${mcpClient}`);\n }\n\n } catch (err: any) {\n throw new BadRequestException(`Invalid MCP executable or client path: ${err.message}`);\n }\n\n // TODO: Refactor to use the command.service.ts instead...\n return new Promise((resolve, reject) => {\n this.logger.log(`Attempting to run command:`)\n this.logger.log(`${pythonExecutable} ${mcpClient} \"${prompt}\"`);\n\n const python = spawn(pythonExecutable, [mcpClient, `\"${prompt}\"`]);\n\n let stdout = '';\n let stderr = '';\n\n python.stdout.on('data', (data) => {\n stdout += data.toString();\n });\n\n python.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n python.on('close', (code) => {\n this.logger.log(`Python script exited with code ${code}`);\n\n if (code !== 0) {\n this.logger.error(`Python script exited with a non-zero exit code: ${stderr}`);\n return reject(new Error(`Python script exited with a non-zero exit code: ${stderr}`));\n }\n\n try {\n this.logger.log(`Python script exited with zero exit code: ${stdout}`);\n const raw: McpResponse = JSON.parse(stdout);\n\n // if (!raw.success) {\n // return reject(new Error(`MCP error: ${raw.errors?.join(', ')}`));\n // }\n // let cleaned = raw.response.trim();\n\n // Don't need to re-parse this...\n // const parsed = JSON.parse(cleaned);\n // resolve(cleaned);\n\n resolve(raw);\n } catch (err: any) {\n reject(new Error(`Mcp Invocation Failed: ${err.message}`));\n }\n });\n });\n }\n\n cleanResponse(response: string) {\n this.logger.log(`mcp server response is: ${response}`);\n\n // Remove markdown-style code block wrapper\n if (response.startsWith('```json')) {\n response = response.replace(/^```json/, '').trim();\n }\n if (response.endsWith('```')) {\n response = response.replace(/```$/, '').trim();\n }\n this.logger.log(`mcp server response after removing doc tags is: ${response}`);\n\n return response;\n }\n\n async applySolidAiInteraction(id: number) {\n // Fetch the aiInteraction\n const aiInteraction = await this.findOne(id, {\n populate: ['user']\n });\n if (!aiInteraction) {\n const m = `Unable to identified the aiInteraction entry that triggered this job... using id: ${id}`\n\n // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....\n throw new Error(m);\n }\n\n // TODO: Validation: Check if JSON.parse(metadata).tools_invoked starts with solid_\n let metadata = {};\n try {\n metadata = JSON.parse(aiInteraction.metadata);\n }\n catch (e) {\n // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....\n throw new Error(e);\n }\n\n const toolsInvoked = metadata['tools_invoked'];\n if (!toolsInvoked) {\n // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....\n throw new Error(ERROR_MESSAGES.UNABLE_TO_RESOLVE_SOLID_COMMAND);\n }\n\n // TODO: OPTIMISATION for chained tool invocation, for now we are assuming only 1 tool was used.\n const toolInvoked = toolsInvoked[0];\n\n // TODO: use the toolInvoked to identify a service using some convention.\n // TODO: Eg. if toolInvoked is solid_create_module <> SolidCreateModuleMcpToolHandler ... create a factory class to do this mapping and identify the relevant provider. \n const mcpToolHandler = this.mcpToolResponseHandlerFactory.getInstance(toolInvoked);\n if (!mcpToolHandler) {\n // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....\n throw new Error(ERROR_MESSAGES.UNABLE_TO_RESOLVE_MCP_HANDLER);\n }\n\n const handlerApplicationResponse = await mcpToolHandler.apply(aiInteraction);\n\n // TODO: This provider to implement an interface - IMcpToolResponseHandler ... apply(aiInteraction: AiInteraction)\n // throw new Error('Method not implemented.');\n \n // Mark the interaction as applied\n await this.update(aiInteraction.id, { isApplied: true }, [], true);\n\n return handlerApplicationResponse;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ai-interaction.service.js","sourceRoot":"","sources":["../../src/services/ai-interaction.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAyE;AACzE,6CAAwE;AACxE,uCAA2D;AAC3D,qCAAoD;AAEpD,iDAAwD;AACxD,qEAA2E;AAC3E,uEAA6E;AAC7E,2CAA+C;AAC/C,iDAAwD;AACxD,+DAAqE;AACrE,iDAAsC;AACtC,6EAAkE;AAClE,gDAAkC;AAElC,kFAAsE;AACtE,uEAAkE;AAElE,sIAAuH;AACvH,gEAA8D;AAGvD,IAAM,oBAAoB,4BAA1B,MAAM,oBAAqB,SAAQ,0BAA0B;IAGlE,YACW,oBAA0C,EAC1C,qBAA4C,EAC5C,aAA4B,EAC5B,WAAwB,EACxB,gBAAkC,EAClC,iBAAoC,EAE7C,aAAqC,EAErC,IAAwC,EAC/B,SAAoB,EACpB,gBAA2D,EAC3D,qBAA4C,EAC5C,6BAA4D;QAGrE,KAAK,CAAC,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,IAAI,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QAhB1K,yBAAoB,GAApB,oBAAoB,CAAsB;QAC1C,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,kBAAa,GAAb,aAAa,CAAe;QAC5B,gBAAW,GAAX,WAAW,CAAa;QACxB,qBAAgB,GAAhB,gBAAgB,CAAkB;QAClC,sBAAiB,GAAjB,iBAAiB,CAAmB;QAEpC,kBAAa,GAAb,aAAa,CAAe;QAE5B,SAAI,GAAJ,IAAI,CAA2B;QAC/B,cAAS,GAAT,SAAS,CAAW;QACpB,qBAAgB,GAAhB,gBAAgB,CAA2C;QAC3D,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,kCAA6B,GAA7B,6BAA6B,CAA+B;QAhBtD,WAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IAoBhE,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,MAAc;QACtC,MAAM,UAAU,GAAmB,IAAI,CAAC,qBAAqB,CAAC,aAAa,EAAE,CAAC;QAE9E,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC;YACtC,MAAM,EAAE,UAAU,CAAC,GAAG;YACtB,QAAQ,EAAE,UAAU,UAAU,CAAC,GAAG,EAAE;YACpC,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,EAAE;YAChB,SAAS,EAAE,EAAE;YACb,cAAc,EAAE,CAAC;YACjB,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC;QACH,MAAM,CAAC,GAAG;YACR,OAAO,EAAE;gBACP,eAAe,EAAE,aAAa,CAAC,EAAE;aAClC;YACD,YAAY,EAAE,eAAe;YAC7B,cAAc,EAAE,aAAa,CAAC,EAAE;SACjC,CAAC;QAEF,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,EAAE,2BAA2B,CAAC,CAAC;QAE3F,OAAO;YACL,cAAc,EAAE,cAAc;YAC9B,eAAe,EAAE,aAAa,CAAC,EAAE;SAClC,CAAA;IACH,CAAC;IAOD,KAAK,CAAC,YAAY,CAAC,MAAc;QAC/B,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;QAC3D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;QAGzC,IAAI,CAAC,gBAAgB,IAAI,CAAC,SAAS,EAAE,CAAC;YACpC,MAAM,IAAI,4BAAmB,CAAC,+BAAc,CAAC,gCAAgC,CAAC,CAAC;QACjF,CAAC;QAGD,IAAI,CAAC;YACH,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBAC7C,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC;gBACzB,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC;aACnB,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;gBACrB,MAAM,IAAI,4BAAmB,CAAC,6CAA6C,gBAAgB,EAAE,CAAC,CAAC;YACjG,CAAC;YAED,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;gBACzB,MAAM,IAAI,4BAAmB,CAAC,kCAAkC,SAAS,EAAE,CAAC,CAAC;YAC/E,CAAC;QAEH,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,4BAAmB,CAAC,0CAA0C,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACzF,CAAC;QAGD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAA;YAC7C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,IAAI,SAAS,KAAK,MAAM,GAAG,CAAC,CAAC;YAEhE,MAAM,MAAM,GAAG,IAAA,qBAAK,EAAC,gBAAgB,EAAE,CAAC,SAAS,EAAE,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC;YAEnE,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAEhB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAChC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAChC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBAC1B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;gBAE1D,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mDAAmD,MAAM,EAAE,CAAC,CAAC;oBAC/E,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,mDAAmD,MAAM,EAAE,CAAC,CAAC,CAAC;gBACxF,CAAC;gBAED,IAAI,CAAC;oBACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6CAA6C,MAAM,EAAE,CAAC,CAAC;oBACvE,MAAM,GAAG,GAAgB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBAW5C,OAAO,CAAC,GAAG,CAAC,CAAC;gBACf,CAAC;gBAAC,OAAO,GAAQ,EAAE,CAAC;oBAClB,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBAC7D,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,QAAgB;QAC5B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;QAGvD,IAAI,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,CAAC;QACD,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACjD,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,mDAAmD,QAAQ,EAAE,CAAC,CAAC;QAE/E,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,EAAU;QAEtC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE;YAC3C,QAAQ,EAAE,CAAC,MAAM,CAAC;SACnB,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,qFAAqF,EAAE,EAAE,CAAA;YAGnG,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;QAGD,IAAI,QAAQ,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,EAAE,CAAC;YAET,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;QAED,MAAM,YAAY,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC;QAC/C,IAAI,CAAC,YAAY,EAAE,CAAC;YAElB,MAAM,IAAI,KAAK,CAAC,+BAAc,CAAC,+BAA+B,CAAC,CAAC;QAClE,CAAC;QAGD,MAAM,WAAW,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QAIpC,MAAM,cAAc,GAAG,IAAI,CAAC,6BAA6B,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACnF,IAAI,CAAC,cAAc,EAAE,CAAC;YAEpB,MAAM,IAAI,KAAK,CAAC,+BAAc,CAAC,6BAA6B,CAAC,CAAC;QAChE,CAAC;QAED,MAAM,0BAA0B,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAM7E,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAEnE,OAAO,0BAA0B,CAAC;IACpC,CAAC;CACF,CAAA;AArMY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;IAWR,WAAA,IAAA,6BAAmB,GAAE,CAAA;IAErB,WAAA,IAAA,0BAAgB,EAAC,qCAAa,EAAE,SAAS,CAAC,CAAA;qCARZ,6CAAoB;QACnB,+CAAqB;QAC7B,sBAAa;QACf,0BAAW;QACN,uBAAgB;QACf,uCAAiB;QAErB,uBAAa;QAEtB,oBAAU;QACL,gBAAS;QACF,4CAAgB;QACX,+CAAqB;QACb,yEAA6B;GAjB5D,oBAAoB,CAqMhC","sourcesContent":["import { BadRequestException, Logger, Injectable } from '@nestjs/common';\nimport { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';\nimport { DiscoveryService, ModuleRef } from \"@nestjs/core\";\nimport { EntityManager, Repository } from 'typeorm';\n\nimport { CRUDService } from 'src/services/crud.service';\nimport { ModelMetadataService } from 'src/services/model-metadata.service';\nimport { ModuleMetadataService } from 'src/services/module-metadata.service';\nimport { ConfigService } from '@nestjs/config';\nimport { FileService } from 'src/services/file.service';\nimport { CrudHelperService } from 'src/services/crud-helper.service';\nimport { spawn } from 'child_process';\nimport { AiInteraction } from '../entities/ai-interaction.entity';\nimport * as fs from 'fs/promises';\nimport { McpResponse, TriggerMcpClientOptions } from 'src/interfaces';\nimport { PublisherFactory } from './queues/publisher-factory.service';\nimport { RequestContextService } from './request-context.service';\nimport { ActiveUserData } from 'src/interfaces/active-user-data.interface';\nimport { McpToolResponseHandlerFactory } from './mcp-tool-response-handlers/mcp-tool-response-handler-factory.service';\nimport { ERROR_MESSAGES } from 'src/constants/error-messages';\n\n@Injectable()\nexport class AiInteractionService extends CRUDService<AiInteraction> {\n private readonly logger = new Logger(AiInteractionService.name);\n\n constructor(\n readonly modelMetadataService: ModelMetadataService,\n readonly moduleMetadataService: ModuleMetadataService,\n readonly configService: ConfigService,\n readonly fileService: FileService,\n readonly discoveryService: DiscoveryService,\n readonly crudHelperService: CrudHelperService,\n @InjectEntityManager()\n readonly entityManager: EntityManager,\n @InjectRepository(AiInteraction, 'default')\n readonly repo: Repository<AiInteraction>,\n readonly moduleRef: ModuleRef,\n readonly publisherFactory: PublisherFactory<TriggerMcpClientOptions>,\n readonly requestContextService: RequestContextService,\n readonly mcpToolResponseHandlerFactory: McpToolResponseHandlerFactory,\n\n ) {\n super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'aiInteraction', 'solid-core', moduleRef);\n }\n\n async triggerMcpClientJob(prompt: string): Promise<any> {\n const activeUser: ActiveUserData = this.requestContextService.getActiveUser();\n\n const aiInteraction = await this.create({\n userId: activeUser.sub,\n threadId: `thread-${activeUser.sub}`,\n role: 'human',\n message: prompt,\n contentType: '',\n errorMessage: '',\n modelUsed: '',\n responseTimeMs: 0,\n metadata: ''\n });\n const m = {\n payload: {\n aiInteractionId: aiInteraction.id,\n },\n parentEntity: 'aiInteraction',\n parentEntityId: aiInteraction.id,\n };\n\n const queueMessageId = await this.publisherFactory.publish(m, 'TriggerMcpClientPublisher');\n\n return {\n queueMessageId: queueMessageId,\n aiInteractionId: aiInteraction.id\n }\n }\n\n /**\n * Runs the Python MCP client with a prompt and returns the parsed JSON embedded in the 'response'.\n * @param prompt - The question or instruction to send to the MCP client.\n * @returns The parsed object inside the 'response' field of the JSON output.\n */\n async runMcpPrompt(prompt: string): Promise<McpResponse> {\n const pythonExecutable = process.env.MCP_PYTHON_EXECUTABLE;\n const mcpClient = process.env.MCP_CLIENT;\n\n // TODO: We can return an error if the above env variables are not properly setup...\n if (!pythonExecutable || !mcpClient) {\n throw new BadRequestException(ERROR_MESSAGES.PYTHON_EXECUTABLE_NOT_CONFIGURED);\n }\n\n // Check if both paths are valid and accessible\n try {\n const [pyStat, clientStat] = await Promise.all([\n fs.stat(pythonExecutable),\n fs.stat(mcpClient),\n ]);\n\n if (!pyStat.isFile()) {\n throw new BadRequestException(`MCP_PYTHON_EXECUTABLE path is not a file: ${pythonExecutable}`);\n }\n\n if (!clientStat.isFile()) {\n throw new BadRequestException(`MCP_CLIENT path is not a file: ${mcpClient}`);\n }\n\n } catch (err: any) {\n throw new BadRequestException(`Invalid MCP executable or client path: ${err.message}`);\n }\n\n // TODO: Refactor to use the command.service.ts instead...\n return new Promise((resolve, reject) => {\n this.logger.log(`Attempting to run command:`)\n this.logger.log(`${pythonExecutable} ${mcpClient} \"${prompt}\"`);\n\n const python = spawn(pythonExecutable, [mcpClient, `\"${prompt}\"`]);\n\n let stdout = '';\n let stderr = '';\n\n python.stdout.on('data', (data) => {\n stdout += data.toString();\n });\n\n python.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n python.on('close', (code) => {\n this.logger.log(`Python script exited with code ${code}`);\n\n if (code !== 0) {\n this.logger.error(`Python script exited with a non-zero exit code: ${stderr}`);\n return reject(new Error(`Python script exited with a non-zero exit code: ${stderr}`));\n }\n\n try {\n this.logger.log(`Python script exited with zero exit code: ${stdout}`);\n const raw: McpResponse = JSON.parse(stdout);\n\n // if (!raw.success) {\n // return reject(new Error(`MCP error: ${raw.errors?.join(', ')}`));\n // }\n // let cleaned = raw.response.trim();\n\n // Don't need to re-parse this...\n // const parsed = JSON.parse(cleaned);\n // resolve(cleaned);\n\n resolve(raw);\n } catch (err: any) {\n reject(new Error(`Mcp Invocation Failed: ${err.message}`));\n }\n });\n });\n }\n\n cleanResponse(response: string) {\n this.logger.log(`mcp server response is: ${response}`);\n\n // Remove markdown-style code block wrapper\n if (response.startsWith('```json')) {\n response = response.replace(/^```json/, '').trim();\n }\n if (response.endsWith('```')) {\n response = response.replace(/```$/, '').trim();\n }\n this.logger.log(`mcp server response after removing doc tags is: ${response}`);\n\n return response;\n }\n\n async applySolidAiInteraction(id: number) {\n // Fetch the aiInteraction\n const aiInteraction = await this.findOne(id, {\n populate: ['user']\n });\n if (!aiInteraction) {\n const m = `Unable to identified the aiInteraction entry that triggered this job... using id: ${id}`\n\n // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....\n throw new Error(m);\n }\n\n // TODO: Validation: Check if JSON.parse(metadata).tools_invoked starts with solid_\n let metadata = {};\n try {\n metadata = JSON.parse(aiInteraction.metadata);\n }\n catch (e) {\n // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....\n throw new Error(e);\n }\n\n const toolsInvoked = metadata['tools_invoked'];\n if (!toolsInvoked) {\n // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....\n throw new Error(ERROR_MESSAGES.UNABLE_TO_RESOLVE_SOLID_COMMAND);\n }\n\n // TODO: OPTIMISATION for chained tool invocation, for now we are assuming only 1 tool was used.\n const toolInvoked = toolsInvoked[0];\n\n // TODO: use the toolInvoked to identify a service using some convention.\n // TODO: Eg. if toolInvoked is solid_create_module <> SolidCreateModuleMcpToolHandler ... create a factory class to do this mapping and identify the relevant provider. \n const mcpToolHandler = this.mcpToolResponseHandlerFactory.getInstance(toolInvoked);\n if (!mcpToolHandler) {\n // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....\n throw new Error(ERROR_MESSAGES.UNABLE_TO_RESOLVE_MCP_HANDLER);\n }\n\n const handlerApplicationResponse = await mcpToolHandler.apply(aiInteraction);\n\n // TODO: This provider to implement an interface - IMcpToolResponseHandler ... apply(aiInteraction: AiInteraction)\n // throw new Error('Method not implemented.');\n\n // Mark the interaction as applied\n await this.update(aiInteraction.id, { isApplied: true }, [], true);\n\n return handlerApplicationResponse;\n }\n}\n"]}
|
|
@@ -20,5 +20,14 @@ export declare class MqMessageService extends CRUDService<MqMessage> {
|
|
|
20
20
|
private readonly logger;
|
|
21
21
|
constructor(modelMetadataService: ModelMetadataService, moduleMetadataService: ModuleMetadataService, configService: ConfigService, fileService: FileService, discoveryService: DiscoveryService, crudHelperService: CrudHelperService, entityManager: EntityManager, repo: Repository<MqMessage>, moduleRef: ModuleRef);
|
|
22
22
|
lockNextPendingMessage(queueName: string): Promise<MqMessage | null>;
|
|
23
|
+
waitForTerminalStatus(messageId: string, opts?: {
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
intervalMs?: number;
|
|
26
|
+
maxIntervalMs?: number;
|
|
27
|
+
throwOnFailure?: boolean;
|
|
28
|
+
parseJson?: boolean;
|
|
29
|
+
logEveryN?: number;
|
|
30
|
+
}): Promise<MqMessage>;
|
|
31
|
+
private safeJsonParse;
|
|
23
32
|
}
|
|
24
33
|
//# sourceMappingURL=mq-message.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mq-message.service.d.ts","sourceRoot":"","sources":["../../src/services/mq-message.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAG1D,qBACa,gBAAiB,SAAQ,WAAW,CAAC,SAAS,CAAC;IAKxD,QAAQ,CAAC,oBAAoB,EAAE,oBAAoB;IACnD,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB;IACrD,QAAQ,CAAC,aAAa,EAAE,aAAa;IACrC,QAAQ,CAAC,WAAW,EAAE,WAAW;IACjC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB;IAC3C,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB;IAE7C,QAAQ,CAAC,aAAa,EAAE,aAAa;IAErC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS;IAd/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;gBAIjD,oBAAoB,EAAE,oBAAoB,EAC1C,qBAAqB,EAAE,qBAAqB,EAC5C,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,gBAAgB,EAClC,iBAAiB,EAAE,iBAAiB,EAEpC,aAAa,EAAE,aAAa,EAE5B,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAC3B,SAAS,EAAE,SAAS;IAKzB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"mq-message.service.d.ts","sourceRoot":"","sources":["../../src/services/mq-message.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAG1D,qBACa,gBAAiB,SAAQ,WAAW,CAAC,SAAS,CAAC;IAKxD,QAAQ,CAAC,oBAAoB,EAAE,oBAAoB;IACnD,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB;IACrD,QAAQ,CAAC,aAAa,EAAE,aAAa;IACrC,QAAQ,CAAC,WAAW,EAAE,WAAW;IACjC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB;IAC3C,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB;IAE7C,QAAQ,CAAC,aAAa,EAAE,aAAa;IAErC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS;IAd/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;gBAIjD,oBAAoB,EAAE,oBAAoB,EAC1C,qBAAqB,EAAE,qBAAqB,EAC5C,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,gBAAgB,EAClC,iBAAiB,EAAE,iBAAiB,EAEpC,aAAa,EAAE,aAAa,EAE5B,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAC3B,SAAS,EAAE,SAAS;IAKzB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA+CpE,qBAAqB,CACzB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,OAAO,CAAC,SAAS,CAAC;IAoFrB,OAAO,CAAC,aAAa;CAWtB"}
|
|
@@ -63,6 +63,67 @@ let MqMessageService = MqMessageService_1 = class MqMessageService extends crud_
|
|
|
63
63
|
return null;
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
|
+
async waitForTerminalStatus(messageId, opts) {
|
|
67
|
+
const { timeoutMs = 60_000, intervalMs = 500, maxIntervalMs = 2_000, throwOnFailure = false, parseJson = true, logEveryN = 10, } = opts || {};
|
|
68
|
+
const start = Date.now();
|
|
69
|
+
let attempt = 0;
|
|
70
|
+
let delay = intervalMs;
|
|
71
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
72
|
+
while (true) {
|
|
73
|
+
attempt++;
|
|
74
|
+
const rec = await this.repo.findOne({
|
|
75
|
+
where: { messageId },
|
|
76
|
+
select: {
|
|
77
|
+
id: true,
|
|
78
|
+
messageId: true,
|
|
79
|
+
stage: true,
|
|
80
|
+
finishedAt: true,
|
|
81
|
+
elapsedMillis: true,
|
|
82
|
+
output: true,
|
|
83
|
+
error: true,
|
|
84
|
+
input: true,
|
|
85
|
+
},
|
|
86
|
+
loadEagerRelations: false,
|
|
87
|
+
});
|
|
88
|
+
if (attempt % logEveryN === 0) {
|
|
89
|
+
this.logger.debug(`waitForTerminalStatus(${messageId}) poll #${attempt} -> ${rec?.stage ?? 'not_found'}`);
|
|
90
|
+
}
|
|
91
|
+
if (!rec) {
|
|
92
|
+
}
|
|
93
|
+
else if (rec.stage === 'succeeded' || rec.stage === 'failed') {
|
|
94
|
+
if (parseJson) {
|
|
95
|
+
rec.output = this.safeJsonParse(rec.output);
|
|
96
|
+
rec.error = this.safeJsonParse(rec.error);
|
|
97
|
+
}
|
|
98
|
+
if (rec.stage === 'failed' && throwOnFailure) {
|
|
99
|
+
throw new Error(`Queue message ${messageId} failed` +
|
|
100
|
+
(rec.error ? `: ${JSON.stringify(rec.error).slice(0, 500)}` : ''));
|
|
101
|
+
}
|
|
102
|
+
return rec;
|
|
103
|
+
}
|
|
104
|
+
const elapsed = Date.now() - start;
|
|
105
|
+
if (elapsed >= timeoutMs) {
|
|
106
|
+
throw new Error(`Timed out after ${timeoutMs}ms waiting for message ${messageId} to reach terminal status`);
|
|
107
|
+
}
|
|
108
|
+
await sleep(delay);
|
|
109
|
+
delay = Math.min(Math.floor(delay * 1.5), maxIntervalMs);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
safeJsonParse(value) {
|
|
113
|
+
if (value == null)
|
|
114
|
+
return value;
|
|
115
|
+
if (typeof value !== 'string')
|
|
116
|
+
return value;
|
|
117
|
+
const s = value.trim();
|
|
118
|
+
if (!s)
|
|
119
|
+
return s;
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(s);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
66
127
|
};
|
|
67
128
|
exports.MqMessageService = MqMessageService;
|
|
68
129
|
exports.MqMessageService = MqMessageService = MqMessageService_1 = __decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mq-message.service.js","sourceRoot":"","sources":["../../src/services/mq-message.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAgE;AAChE,6CAAwE;AACxE,uCAA2D;AAC3D,qCAAoD;AACpD,iDAAwD;AACxD,qEAA2E;AAC3E,uEAA6E;AAC7E,2CAA+C;AAC/C,iDAAwD;AACxD,+DAAqE;AACrE,qEAA0D;AAC1D,2CAAwC;AAGjC,IAAM,gBAAgB,wBAAtB,MAAM,gBAAiB,SAAQ,0BAAsB;IAG1D,YAEE,oBAAmD,EAC1C,qBAA4C,EAC5C,aAA4B,EAC5B,WAAwB,EACxB,gBAAkC,EAClC,iBAAoC,EAE7C,aAAqC,EAErC,IAAoC,EAC3B,SAAoB;QAE7B,KAAK,CAAC,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QAZtK,yBAAoB,GAApB,oBAAoB,CAAsB;QAC1C,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,kBAAa,GAAb,aAAa,CAAe;QAC5B,gBAAW,GAAX,WAAW,CAAa;QACxB,qBAAgB,GAAhB,gBAAgB,CAAkB;QAClC,sBAAiB,GAAjB,iBAAiB,CAAmB;QAEpC,kBAAa,GAAb,aAAa,CAAe;QAE5B,SAAI,GAAJ,IAAI,CAAuB;QAC3B,cAAS,GAAT,SAAS,CAAW;QAdd,WAAM,GAAG,IAAI,eAAM,CAAC,kBAAgB,CAAC,IAAI,CAAC,CAAC;IAiB5D,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,SAAiB;QAG5C,OAAO,MAAM,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,EAAC,OAAO,EAAC,EAAE;YAE1D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC;;;;;;;;mBAQtB,EACX,CAAC,SAAS,CAAC,CACZ,CAAC;YAGF,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAEnC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,6BAAS,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC3H,IAAI,GAAG,EAAE,CAAC;gBAGR,GAAG,CAAC,KAAK,GAAG,WAAW,CAAC;gBACxB,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAExB,OAAO,GAAG,CAAC;YACb,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;CAEF,CAAA;AAxDY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,eAAM,EAAC,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,6CAAoB,CAAC,CAAC,CAAA;IAO9C,WAAA,IAAA,6BAAmB,GAAE,CAAA;IAErB,WAAA,IAAA,0BAAgB,EAAC,6BAAS,CAAC,CAAA;qCARG,6CAAoB;QACnB,+CAAqB;QAC7B,sBAAa;QACf,0BAAW;QACN,uBAAgB;QACf,uCAAiB;QAErB,uBAAa;QAEtB,oBAAU;QACL,gBAAS;GAfpB,gBAAgB,CAwD5B","sourcesContent":["import { forwardRef, Inject, Injectable } from '@nestjs/common';\nimport { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';\nimport { DiscoveryService, ModuleRef } from \"@nestjs/core\";\nimport { EntityManager, Repository } from 'typeorm';\nimport { CRUDService } from 'src/services/crud.service';\nimport { ModelMetadataService } from 'src/services/model-metadata.service';\nimport { ModuleMetadataService } from 'src/services/module-metadata.service';\nimport { ConfigService } from '@nestjs/config';\nimport { FileService } from \"src/services/file.service\";\nimport { CrudHelperService } from \"src/services/crud-helper.service\";\nimport { MqMessage } from '../entities/mq-message.entity';\nimport { Logger } from '@nestjs/common';\n\n@Injectable()\nexport class MqMessageService extends CRUDService<MqMessage> {\n private readonly logger = new Logger(MqMessageService.name);\n\n constructor(\n @Inject(forwardRef(() => ModelMetadataService))\n readonly modelMetadataService: ModelMetadataService,\n readonly moduleMetadataService: ModuleMetadataService,\n readonly configService: ConfigService,\n readonly fileService: FileService,\n readonly discoveryService: DiscoveryService,\n readonly crudHelperService: CrudHelperService,\n @InjectEntityManager()\n readonly entityManager: EntityManager,\n @InjectRepository(MqMessage)\n readonly repo: Repository<MqMessage>,\n readonly moduleRef: ModuleRef\n ) {\n super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'mqMessage', 'solid-core', moduleRef);\n }\n\n async lockNextPendingMessage(queueName: string): Promise<MqMessage | null> {\n // this.logger.debug(`Locking next pending message for queue: ${queueName}`);\n\n return await this.entityManager.transaction(async manager => {\n // Use raw SQL to skip locked rows (FOR UPDATE SKIP LOCKED)\n const rawJob = await manager.query(`\n SELECT ss_mq_message.id\n FROM ss_mq_message\n left join ss_mq_message_queue on ss_mq_message.mq_message_queue_id = ss_mq_message_queue.id\n WHERE ss_mq_message_queue.\"name\" = $1\n AND ss_mq_message.stage = 'pending'\n ORDER BY ss_mq_message.created_at ASC\n FOR UPDATE SKIP LOCKED\n LIMIT 1`,\n [queueName]\n );\n\n // this.logger.debug(`Raw job fetched: ${JSON.stringify(rawJob)}`);\n if (!rawJob || rawJob.length === 0) {\n // this.logger.debug(`No pending job found for queue: ${queueName}`);\n return null;\n }\n const job = await manager.getRepository(MqMessage).findOne({ where: { id: rawJob[0].id }, relations: ['mqMessageQueue'] });\n if (job) {\n // this.logger.debug(`Locked job id: ${job.id}, queue: ${job.mqMessageQueue.name}, stage: ${job.stage}`);\n\n job.stage = 'scheduled';\n await manager.save(job);\n\n return job;\n }\n\n return null;\n });\n }\n\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mq-message.service.js","sourceRoot":"","sources":["../../src/services/mq-message.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { forwardRef, Inject, Injectable } from '@nestjs/common';\nimport { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';\nimport { DiscoveryService, ModuleRef } from \"@nestjs/core\";\nimport { EntityManager, Repository } from 'typeorm';\nimport { CRUDService } from 'src/services/crud.service';\nimport { ModelMetadataService } from 'src/services/model-metadata.service';\nimport { ModuleMetadataService } from 'src/services/module-metadata.service';\nimport { ConfigService } from '@nestjs/config';\nimport { FileService } from \"src/services/file.service\";\nimport { CrudHelperService } from \"src/services/crud-helper.service\";\nimport { MqMessage } from '../entities/mq-message.entity';\nimport { Logger } from '@nestjs/common';\n\n@Injectable()\nexport class MqMessageService extends CRUDService<MqMessage> {\n private readonly logger = new Logger(MqMessageService.name);\n\n constructor(\n @Inject(forwardRef(() => ModelMetadataService))\n readonly modelMetadataService: ModelMetadataService,\n readonly moduleMetadataService: ModuleMetadataService,\n readonly configService: ConfigService,\n readonly fileService: FileService,\n readonly discoveryService: DiscoveryService,\n readonly crudHelperService: CrudHelperService,\n @InjectEntityManager()\n readonly entityManager: EntityManager,\n @InjectRepository(MqMessage)\n readonly repo: Repository<MqMessage>,\n readonly moduleRef: ModuleRef\n ) {\n super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'mqMessage', 'solid-core', moduleRef);\n }\n\n async lockNextPendingMessage(queueName: string): Promise<MqMessage | null> {\n // this.logger.debug(`Locking next pending message for queue: ${queueName}`);\n\n return await this.entityManager.transaction(async manager => {\n // Use raw SQL to skip locked rows (FOR UPDATE SKIP LOCKED)\n const rawJob = await manager.query(`\n SELECT ss_mq_message.id\n FROM ss_mq_message\n left join ss_mq_message_queue on ss_mq_message.mq_message_queue_id = ss_mq_message_queue.id\n WHERE ss_mq_message_queue.\"name\" = $1\n AND ss_mq_message.stage = 'pending'\n ORDER BY ss_mq_message.created_at ASC\n FOR UPDATE SKIP LOCKED\n LIMIT 1`,\n [queueName]\n );\n\n // this.logger.debug(`Raw job fetched: ${JSON.stringify(rawJob)}`);\n if (!rawJob || rawJob.length === 0) {\n // this.logger.debug(`No pending job found for queue: ${queueName}`);\n return null;\n }\n const job = await manager.getRepository(MqMessage).findOne({ where: { id: rawJob[0].id }, relations: ['mqMessageQueue'] });\n if (job) {\n // this.logger.debug(`Locked job id: ${job.id}, queue: ${job.mqMessageQueue.name}, stage: ${job.stage}`);\n\n job.stage = 'scheduled';\n await manager.save(job);\n\n return job;\n }\n\n return null;\n });\n }\n\n/**\n * Wait until a queue message reaches a terminal status (succeeded/failed).\n *\n * @param messageId string – the external message id you store in `ss_mq_message.messageId`\n * @param opts.timeoutMs total time to wait before giving up (default 60s)\n * @param opts.intervalMs initial poll interval (default 500ms)\n * @param opts.maxIntervalMs cap for exponential backoff (default 2000ms)\n * @param opts.throwOnFailure if true, throws when stage === 'failed' (default false)\n * @param opts.parseJson try JSON.parse on `output` and `error` (default true)\n * @returns resolves with the final MqMessage row when terminal, rejects on timeout (or failure if throwOnFailure)\n */\n async waitForTerminalStatus(\n messageId: string,\n opts?: {\n timeoutMs?: number;\n intervalMs?: number;\n maxIntervalMs?: number;\n throwOnFailure?: boolean;\n parseJson?: boolean;\n logEveryN?: number;\n },\n ): Promise<MqMessage> {\n const {\n timeoutMs = 60_000,\n intervalMs = 500,\n maxIntervalMs = 2_000,\n throwOnFailure = false,\n parseJson = true,\n logEveryN = 10, // log every N polls to avoid noisy logs\n } = opts || {};\n\n const start = Date.now();\n let attempt = 0;\n let delay = intervalMs;\n\n // Small helper\n const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n while (true) {\n attempt++;\n\n // Fetch minimal columns needed for quick polling\n const rec = await this.repo.findOne({\n where: { messageId },\n select: {\n id: true,\n messageId: true,\n stage: true,\n finishedAt: true,\n elapsedMillis: true,\n output: true,\n error: true,\n input: true,\n // add other fields if you need to return them\n } as any,\n loadEagerRelations: false,\n });\n\n if (attempt % logEveryN === 0) {\n this.logger.debug(\n `waitForTerminalStatus(${messageId}) poll #${attempt} -> ${rec?.stage ?? 'not_found'}`\n );\n }\n\n if (!rec) {\n // Not found yet – keep waiting until timeout\n } else if (rec.stage === 'succeeded' || rec.stage === 'failed') {\n // Optionally parse output/error if they contain JSON strings\n if (parseJson) {\n rec.output = this.safeJsonParse(rec.output);\n rec.error = this.safeJsonParse(rec.error);\n }\n\n if (rec.stage === 'failed' && throwOnFailure) {\n throw new Error(\n `Queue message ${messageId} failed` +\n (rec.error ? `: ${JSON.stringify(rec.error).slice(0, 500)}` : '')\n );\n }\n return rec;\n }\n\n // Timeout?\n const elapsed = Date.now() - start;\n if (elapsed >= timeoutMs) {\n throw new Error(`Timed out after ${timeoutMs}ms waiting for message ${messageId} to reach terminal status`);\n }\n\n // Backoff with cap\n await sleep(delay);\n delay = Math.min(Math.floor(delay * 1.5), maxIntervalMs);\n }\n }\n\n // /**\n // * Optional wrapper: publish and then wait (if your publisher returns the messageId).\n // */\n // async publishAndWait<T>(\n // publishFn: () => Promise<string>, // returns messageId\n // waitOpts?: Parameters<MqMessageService['waitForTerminalStatus']>[1],\n // ): Promise<MqMessage> {\n // const messageId = await publishFn();\n // return this.waitForTerminalStatus(messageId, waitOpts);\n // }\n\n private safeJsonParse(value: unknown): unknown {\n if (value == null) return value;\n if (typeof value !== 'string') return value;\n const s = value.trim();\n if (!s) return s;\n try {\n return JSON.parse(s);\n } catch {\n return value; // leave as-is if not valid JSON\n }\n }\n}\n"]}
|
|
@@ -22,7 +22,7 @@ let PollerService = PollerService_1 = class PollerService {
|
|
|
22
22
|
const opts = {
|
|
23
23
|
baseDelayMs: options.baseDelayMs ?? 1000,
|
|
24
24
|
maxDelayMs: options.maxDelayMs ?? 30_000,
|
|
25
|
-
timeoutPerIterationMs: options.timeoutPerIterationMs ?? 60_000,
|
|
25
|
+
timeoutPerIterationMs: options.timeoutPerIterationMs ?? 5 * 60_000,
|
|
26
26
|
jitter: options.jitter ?? true,
|
|
27
27
|
};
|
|
28
28
|
const state = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"poller.service.js","sourceRoot":"","sources":["../../src/services/poller.service.ts"],"names":[],"mappings":";;;;;;;;;;AACA,2CAKwB;AA0BjB,IAAM,aAAa,qBAAnB,MAAM,aAAa;IAAnB;QACc,WAAM,GAAG,IAAI,eAAM,CAAC,eAAa,CAAC,IAAI,CAAC,CAAC;QACxC,YAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAgI9D,CAAC;IA9HG,KAAK,CAAC,SAAiB,EAAE,WAA0B,EAAE,UAAuB,EAAE;QAC1E,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,SAAS,8BAA8B,CAAC,CAAC;YACrE,OAAO;QACX,CAAC;QAED,MAAM,IAAI,GAA0B;YAChC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;YACxC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,MAAM;YACxC,qBAAqB,EAAE,OAAO,CAAC,qBAAqB,IAAI,MAAM;
|
|
1
|
+
{"version":3,"file":"poller.service.js","sourceRoot":"","sources":["../../src/services/poller.service.ts"],"names":[],"mappings":";;;;;;;;;;AACA,2CAKwB;AA0BjB,IAAM,aAAa,qBAAnB,MAAM,aAAa;IAAnB;QACc,WAAM,GAAG,IAAI,eAAM,CAAC,eAAa,CAAC,IAAI,CAAC,CAAC;QACxC,YAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAgI9D,CAAC;IA9HG,KAAK,CAAC,SAAiB,EAAE,WAA0B,EAAE,UAAuB,EAAE;QAC1E,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,SAAS,8BAA8B,CAAC,CAAC;YACrE,OAAO;QACX,CAAC;QAED,MAAM,IAAI,GAA0B;YAChC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;YACxC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,MAAM;YACxC,qBAAqB,EAAE,OAAO,CAAC,qBAAqB,IAAI,CAAC,GAAG,MAAM;YAClE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;SACjC,CAAC;QAEF,MAAM,KAAK,GAAgB;YACvB,SAAS;YACT,WAAW;YACX,IAAI;YACJ,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,IAAI,CAAC,WAAW;YACzB,SAAS,EAAE,SAAS;SACvB,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAEnC,YAAY,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,mBAAmB,SAAS,GAAG,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,CAAC,SAAiB;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAClB,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC9B,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;QAChC,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,mBAAmB,SAAS,GAAG,CAAC,CAAC;IACrD,CAAC;IAED,OAAO;QACH,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe;QACjB,IAAI,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,yBAAyB;QAC3B,IAAI,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAIO,KAAK,CAAC,IAAI,CAAC,KAAkB;QACjC,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ;YAAE,OAAO;QAC5C,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;QAEtB,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,WAAW,CAClB,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,EAClC,KAAK,CAAC,IAAI,CAAC,qBAAqB,CACnC,CAAC;YAGF,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC;YAEvC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,SAAS,uBAAuB,GAAG,EAAE,CAAC,CAAC;YAGnE,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACzD,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACnE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC/B,CAAC;gBAAS,CAAC;YACP,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC;QAC3B,CAAC;IACL,CAAC;IAEO,QAAQ,CAAC,KAAkB,EAAE,OAAe;QAChD,IAAI,KAAK,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,KAAK,CAAC,SAAS;YAAE,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAEnD,KAAK,CAAC,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAE9B,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QACtC,CAAC,EAAE,OAAO,CAAC,CAAC;IAChB,CAAC;IAEO,WAAW,CAAC,cAAsB,EAAE,IAA2B;QACnE,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,cAAc,CAAC;QAExC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,cAAc,GAAG,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAEO,KAAK,CAAC,WAAW,CAAI,CAAa,EAAE,EAAU;QAClD,IAAI,KAAiC,CAAC;QACtC,IAAI,CAAC;YACD,OAAO,MAAM,OAAO,CAAC,IAAI,CAAI;gBACzB,CAAC;gBACD,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;oBAC1B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvF,CAAC,CAAC;aACL,CAAC,CAAC;QACP,CAAC;gBAAS,CAAC;YACP,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;IACL,CAAC;IAEO,aAAa,CAAC,GAAY;QAC9B,IAAI,GAAG,YAAY,KAAK;YAAE,OAAO,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC;QAC1D,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;IACL,CAAC;CACJ,CAAA;AAlIY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;GACA,aAAa,CAkIzB","sourcesContent":["// src/common/poller/poller.service.ts\nimport {\n Injectable,\n Logger,\n OnModuleDestroy,\n BeforeApplicationShutdown,\n} from '@nestjs/common';\n\nexport interface PollOptions {\n /** Wait after a successful iteration */\n baseDelayMs?: number; // default 1000\n /** Maximum delay after repeated failures */\n maxDelayMs?: number; // default 30000\n /** Per-iteration timeout guard */\n timeoutPerIterationMs?: number; // default 60000\n /** Add jitter to spread load */\n jitter?: boolean; // default true\n}\n\ntype ProcessNextFn = (queueName: string) => Promise<unknown>;\n\ninterface PollerState {\n queueName: string;\n processNext: ProcessNextFn;\n opts: Required<PollOptions>;\n inFlight: boolean;\n stopped: boolean;\n backoff: number;\n nextTimer?: NodeJS.Timeout;\n}\n\n@Injectable()\nexport class PollerService implements OnModuleDestroy, BeforeApplicationShutdown {\n private readonly logger = new Logger(PollerService.name);\n private readonly pollers = new Map<string, PollerState>();\n\n start(queueName: string, processNext: ProcessNextFn, options: PollOptions = {}): void {\n if (this.pollers.has(queueName)) {\n this.logger.warn(`Poller \"${queueName}\" already started; ignoring.`);\n return;\n }\n\n const opts: Required<PollOptions> = {\n baseDelayMs: options.baseDelayMs ?? 1000,\n maxDelayMs: options.maxDelayMs ?? 30_000,\n timeoutPerIterationMs: options.timeoutPerIterationMs ?? 5 * 60_000,\n jitter: options.jitter ?? true,\n };\n\n const state: PollerState = {\n queueName,\n processNext,\n opts,\n inFlight: false,\n stopped: false,\n backoff: opts.baseDelayMs,\n nextTimer: undefined,\n };\n\n this.pollers.set(queueName, state);\n // kick off on next tick\n setImmediate(() => this.poll(state).catch(() => { }));\n this.logger.log(`Started poller \"${queueName}\"`);\n }\n\n stop(queueName: string): void {\n const state = this.pollers.get(queueName);\n if (!state) return;\n\n state.stopped = true;\n if (state.nextTimer) {\n clearTimeout(state.nextTimer);\n state.nextTimer = undefined;\n }\n this.pollers.delete(queueName);\n this.logger.log(`Stopped poller \"${queueName}\"`);\n }\n\n stopAll(): void {\n for (const name of Array.from(this.pollers.keys())) {\n this.stop(name);\n }\n }\n\n async onModuleDestroy(): Promise<void> {\n this.stopAll();\n }\n\n async beforeApplicationShutdown(): Promise<void> {\n this.stopAll();\n }\n\n // ---- internals ----\n\n private async poll(state: PollerState): Promise<void> {\n if (state.stopped || state.inFlight) return;\n state.inFlight = true;\n\n try {\n await this.withTimeout(\n state.processNext(state.queueName),\n state.opts.timeoutPerIterationMs,\n );\n\n // success: reset backoff and schedule next run after base delay\n state.backoff = state.opts.baseDelayMs;\n // this.logger.debug(`[${state.queueName}] iteration completed`);\n this.schedule(state, state.opts.baseDelayMs);\n } catch (err: unknown) {\n const msg = this.errorToString(err);\n this.logger.error(`[${state.queueName}] iteration failed: ${msg}`);\n\n // failure: schedule with backoff + optional jitter, then increase backoff\n const wait = this.computeWait(state.backoff, state.opts);\n state.backoff = Math.min(state.backoff * 2, state.opts.maxDelayMs);\n this.schedule(state, wait);\n } finally {\n state.inFlight = false;\n }\n }\n\n private schedule(state: PollerState, delayMs: number) {\n if (state.stopped) return;\n if (state.nextTimer) clearTimeout(state.nextTimer);\n\n state.nextTimer = setTimeout(() => {\n // clear reference before calling poll to avoid re-entrancy confusion\n state.nextTimer = undefined;\n this.poll(state).catch(() => { });\n }, delayMs);\n }\n\n private computeWait(currentBackoff: number, opts: Required<PollOptions>): number {\n if (!opts.jitter) return currentBackoff;\n // Full jitter: random in [250ms, currentBackoff * 2], clamped to maxDelayMs\n const doubled = Math.min(currentBackoff * 2, opts.maxDelayMs);\n const jittered = Math.floor(Math.random() * doubled);\n return Math.max(250, jittered);\n }\n\n private async withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n try {\n return await Promise.race<T>([\n p,\n new Promise<never>((_, rej) => {\n timer = setTimeout(() => rej(new Error(`Iteration timed out after ${ms} ms`)), ms);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n\n private errorToString(err: unknown): string {\n if (err instanceof Error) return err.stack ?? err.message;\n try {\n return JSON.stringify(err);\n } catch {\n return String(err);\n }\n }\n}"]}
|
|
@@ -56,7 +56,7 @@ class DatabaseSubscriber {
|
|
|
56
56
|
this.poller.start(queueName, (q) => this.processNext(q), {
|
|
57
57
|
baseDelayMs: 1000,
|
|
58
58
|
maxDelayMs: 30_000,
|
|
59
|
-
timeoutPerIterationMs: 60_000,
|
|
59
|
+
timeoutPerIterationMs: 5 * 60_000,
|
|
60
60
|
jitter: true,
|
|
61
61
|
});
|
|
62
62
|
this.logger.log(`DatabaseSubscriber ready to consume messages: ${JSON.stringify(this.options())}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-subscriber.service.js","sourceRoot":"","sources":["../../../src/services/queues/database-subscriber.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Logger, OnModuleInit } from '@nestjs/common';\nimport { QueuesModuleOptions } from \"../../interfaces\";\nimport { QueueMessage, QueueSubscriber } from '../../interfaces/mq';\nimport { MqMessageQueueService } from '../mq-message-queue.service';\nimport { MqMessageService } from '../mq-message.service';\nimport { PollerService } from '../poller.service';\n\n\nexport abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscriber<T> {\n private readonly logger = new Logger(DatabaseSubscriber.name);\n private readonly url: string;\n private readonly serviceRole: string;\n\n constructor(\n protected readonly mqMessageService: MqMessageService,\n protected readonly mqMessageQueueService: MqMessageQueueService,\n protected readonly poller: PollerService,\n ) {\n this.serviceRole = process.env.QUEUES_SERVICE_ROLE;\n if (!this.serviceRole) {\n this.logger.debug('Queue service Role is not defined in the environment variables');\n }\n this.logger.debug(`DatabaseSubscriber instance created with options: ${JSON.stringify(this.options())}`);\n }\n\n abstract subscribe(message: QueueMessage<T>);\n\n abstract options(): QueuesModuleOptions;\n\n private async processNext(queueName: string) {\n // this.logger.debug(`#### DatabaseSubscriber processing next message from queue: ${queueName}`);\n const job = await this.mqMessageService.lockNextPendingMessage(queueName);\n if (!job) {\n return;\n }\n\n const messageContentString = job.input.toString();\n // this.logger.debug(`DatabaseSubscriber Received raw message: ${messageContentString}`);\n\n let message: QueueMessage<T> = null;\n\n try {\n message = JSON.parse(messageContentString) as QueueMessage<T>;\n\n // this is the first time we are receiving the message so we set the currentRetry to 0\n if (!message.retryCount) message.retryCount = 0;\n if (!message.retryInterval) message.retryInterval = 1000;\n if (!message.currentRetry) message.currentRetry = 0;\n\n await this.processMessage(message);\n }\n catch (error) {\n this.logger.error(`Error processing message: ${error.message}`);\n\n // if an error occurs then if retryCount is set we start retrying. \n if (message) {\n if (message.currentRetry < message.retryCount) {\n await this.updateStatusInDatabase('retrying', message);\n\n message.currentRetry++;\n this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms`);\n setTimeout(() => {\n this.retryMessage(message);\n }, message.retryInterval);\n } else {\n // Discard the message after max retries\n await this.updateStatusInDatabase('failed', message, error.message, '');\n this.logger.error(`Message failed after ${message.retryCount} attempts: ${error.message}`);\n }\n }\n }\n // this.logger.debug(`#### DatabaseSubscriber finished processing message from queue: ${queueName}`);\n }\n\n // async onModuleInit(): Promise<void> {\n // // we will start subscriber only if the current service role is subscriber. \n // if (['both', 'subscriber'].includes(this.serviceRole)) {\n\n // const options = this.options();\n\n // const queueName = options.queueName;\n // // setInterval(() => this.processNext(queueName), 1000);\n // const poll = async () => {\n // try {\n // await this.processNext(queueName);\n // } catch (err) {\n // this.logger.error(`Polling error: ${err.message}`);\n // } finally {\n // setTimeout(poll, 1000); // Wait 1s *after* processing finishes\n // }\n // };\n\n // // start the loop\n // poll();\n\n // this.logger.log(`DatabaseSubscriber ready to consume messages: ${JSON.stringify(this.options())}`);\n // }\n // }\n\n async onModuleInit(): Promise<void> {\n // we will start subscriber only if the current service role is subscriber. \n if (['both', 'subscriber'].includes(this.serviceRole)) {\n\n const options = this.options();\n\n const queueName = options.queueName;\n\n this.poller.start(queueName, (q) => this.processNext(q), {\n baseDelayMs: 1000,\n maxDelayMs: 30_000,\n timeoutPerIterationMs: 60_000,\n jitter: true,\n });\n\n this.logger.log(`DatabaseSubscriber ready to consume messages: ${JSON.stringify(this.options())}`);\n }\n }\n\n onModuleDestroy() {\n const options = this.options();\n const queueName = options.queueName;\n this.poller.stop(queueName);\n }\n\n /**\n * Abstract method for message processing logic.\n */\n protected async processMessage(message: QueueMessage<T>): Promise<void> {\n await this.updateStatusInDatabase('started', message);\n\n // Capture the results of handling the task.\n const result = await this.subscribe(message);\n\n // TODO: Update the database to indicate that the task is finished.\n await this.updateStatusInDatabase('succeeded', message, '', result ? JSON.stringify(result, null, 2) : '');\n }\n\n /**\n * Retry the message by invoking the processing logic again.\n */\n private async retryMessage(message: QueueMessage<T>) {\n try {\n await this.processMessage(message);\n } catch (error) {\n if (message.currentRetry < message.retryCount) {\n await this.updateStatusInDatabase('retrying', message);\n\n message.currentRetry++;\n this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms: ${error.message}`);\n setTimeout(() => {\n this.retryMessage(message);\n }, message.retryInterval);\n } else {\n this.logger.error(`Message failed after ${message.retryCount} attempts: ${error.message}`);\n\n // TODO: Store the error in the database and update the status accordingly.\n await this.updateStatusInDatabase('failed', message, error.message, '');\n\n }\n }\n }\n\n private async updateStatusInDatabase(stage: string, message: QueueMessage<T>, error: string = '', result: string = '') {\n try {\n this.logger.debug(`Updating message status in database: ${stage} for messageId: ${message.messageId}`);\n\n // 1. resolve the queue first\n const mqMessage = await this.mqMessageService.repo.findOne({\n where: {\n messageId: message.messageId,\n }\n });\n\n if (mqMessage) {\n this.logger.debug(`Found message in database: ${JSON.stringify(mqMessage.messageId)}`);\n this.logger.debug(`Updating message status in database: ${stage} for messageId: ${mqMessage.id}`);\n\n const updatedFields = {\n stage: stage\n };\n if (stage === 'failed' || stage === 'succeeded') {\n updatedFields['finishedAt'] = new Date();\n updatedFields['elapsedMillis'] = updatedFields['finishedAt'].getTime() - mqMessage.startedAt.getTime();\n }\n if (stage === 'succeeded') {\n updatedFields['output'] = result;\n }\n if (stage === 'failed') {\n updatedFields['error'] = error;\n }\n await this.mqMessageService.repo.update(mqMessage.id, updatedFields);\n this.logger.debug(`Message status updated to ${stage} for messageId: ${mqMessage.id}`);\n }\n }\n catch (error) {\n this.logger.error(error.message, error.stack);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"database-subscriber.service.js","sourceRoot":"","sources":["../../../src/services/queues/database-subscriber.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Logger, OnModuleInit } from '@nestjs/common';\nimport { QueuesModuleOptions } from \"../../interfaces\";\nimport { QueueMessage, QueueSubscriber } from '../../interfaces/mq';\nimport { MqMessageQueueService } from '../mq-message-queue.service';\nimport { MqMessageService } from '../mq-message.service';\nimport { PollerService } from '../poller.service';\n\n\nexport abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscriber<T> {\n private readonly logger = new Logger(DatabaseSubscriber.name);\n private readonly url: string;\n private readonly serviceRole: string;\n\n constructor(\n protected readonly mqMessageService: MqMessageService,\n protected readonly mqMessageQueueService: MqMessageQueueService,\n protected readonly poller: PollerService,\n ) {\n this.serviceRole = process.env.QUEUES_SERVICE_ROLE;\n if (!this.serviceRole) {\n this.logger.debug('Queue service Role is not defined in the environment variables');\n }\n this.logger.debug(`DatabaseSubscriber instance created with options: ${JSON.stringify(this.options())}`);\n }\n\n abstract subscribe(message: QueueMessage<T>);\n\n abstract options(): QueuesModuleOptions;\n\n private async processNext(queueName: string) {\n // this.logger.debug(`#### DatabaseSubscriber processing next message from queue: ${queueName}`);\n const job = await this.mqMessageService.lockNextPendingMessage(queueName);\n if (!job) {\n return;\n }\n\n const messageContentString = job.input.toString();\n // this.logger.debug(`DatabaseSubscriber Received raw message: ${messageContentString}`);\n\n let message: QueueMessage<T> = null;\n\n try {\n message = JSON.parse(messageContentString) as QueueMessage<T>;\n\n // this is the first time we are receiving the message so we set the currentRetry to 0\n if (!message.retryCount) message.retryCount = 0;\n if (!message.retryInterval) message.retryInterval = 1000;\n if (!message.currentRetry) message.currentRetry = 0;\n\n await this.processMessage(message);\n }\n catch (error) {\n this.logger.error(`Error processing message: ${error.message}`);\n\n // if an error occurs then if retryCount is set we start retrying. \n if (message) {\n if (message.currentRetry < message.retryCount) {\n await this.updateStatusInDatabase('retrying', message);\n\n message.currentRetry++;\n this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms`);\n setTimeout(() => {\n this.retryMessage(message);\n }, message.retryInterval);\n } else {\n // Discard the message after max retries\n await this.updateStatusInDatabase('failed', message, error.message, '');\n this.logger.error(`Message failed after ${message.retryCount} attempts: ${error.message}`);\n }\n }\n }\n // this.logger.debug(`#### DatabaseSubscriber finished processing message from queue: ${queueName}`);\n }\n\n // async onModuleInit(): Promise<void> {\n // // we will start subscriber only if the current service role is subscriber. \n // if (['both', 'subscriber'].includes(this.serviceRole)) {\n\n // const options = this.options();\n\n // const queueName = options.queueName;\n // // setInterval(() => this.processNext(queueName), 1000);\n // const poll = async () => {\n // try {\n // await this.processNext(queueName);\n // } catch (err) {\n // this.logger.error(`Polling error: ${err.message}`);\n // } finally {\n // setTimeout(poll, 1000); // Wait 1s *after* processing finishes\n // }\n // };\n\n // // start the loop\n // poll();\n\n // this.logger.log(`DatabaseSubscriber ready to consume messages: ${JSON.stringify(this.options())}`);\n // }\n // }\n\n async onModuleInit(): Promise<void> {\n // we will start subscriber only if the current service role is subscriber. \n if (['both', 'subscriber'].includes(this.serviceRole)) {\n\n const options = this.options();\n\n const queueName = options.queueName;\n\n this.poller.start(queueName, (q) => this.processNext(q), {\n baseDelayMs: 1000,\n maxDelayMs: 30_000,\n timeoutPerIterationMs: 5 * 60_000,\n jitter: true,\n });\n\n this.logger.log(`DatabaseSubscriber ready to consume messages: ${JSON.stringify(this.options())}`);\n }\n }\n\n onModuleDestroy() {\n const options = this.options();\n const queueName = options.queueName;\n this.poller.stop(queueName);\n }\n\n /**\n * Abstract method for message processing logic.\n */\n protected async processMessage(message: QueueMessage<T>): Promise<void> {\n await this.updateStatusInDatabase('started', message);\n\n // Capture the results of handling the task.\n const result = await this.subscribe(message);\n\n // TODO: Update the database to indicate that the task is finished.\n await this.updateStatusInDatabase('succeeded', message, '', result ? JSON.stringify(result, null, 2) : '');\n }\n\n /**\n * Retry the message by invoking the processing logic again.\n */\n private async retryMessage(message: QueueMessage<T>) {\n try {\n await this.processMessage(message);\n } catch (error) {\n if (message.currentRetry < message.retryCount) {\n await this.updateStatusInDatabase('retrying', message);\n\n message.currentRetry++;\n this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms: ${error.message}`);\n setTimeout(() => {\n this.retryMessage(message);\n }, message.retryInterval);\n } else {\n this.logger.error(`Message failed after ${message.retryCount} attempts: ${error.message}`);\n\n // TODO: Store the error in the database and update the status accordingly.\n await this.updateStatusInDatabase('failed', message, error.message, '');\n\n }\n }\n }\n\n private async updateStatusInDatabase(stage: string, message: QueueMessage<T>, error: string = '', result: string = '') {\n try {\n this.logger.debug(`Updating message status in database: ${stage} for messageId: ${message.messageId}`);\n\n // 1. resolve the queue first\n const mqMessage = await this.mqMessageService.repo.findOne({\n where: {\n messageId: message.messageId,\n }\n });\n\n if (mqMessage) {\n this.logger.debug(`Found message in database: ${JSON.stringify(mqMessage.messageId)}`);\n this.logger.debug(`Updating message status in database: ${stage} for messageId: ${mqMessage.id}`);\n\n const updatedFields = {\n stage: stage\n };\n if (stage === 'failed' || stage === 'succeeded') {\n updatedFields['finishedAt'] = new Date();\n updatedFields['elapsedMillis'] = updatedFields['finishedAt'].getTime() - mqMessage.startedAt.getTime();\n }\n if (stage === 'succeeded') {\n updatedFields['output'] = result;\n }\n if (stage === 'failed') {\n updatedFields['error'] = error;\n }\n await this.mqMessageService.repo.update(mqMessage.id, updatedFields);\n this.logger.debug(`Message status updated to ${stage} for messageId: ${mqMessage.id}`);\n }\n }\n catch (error) {\n this.logger.error(error.message, error.stack);\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solid-core.module.d.ts","sourceRoot":"","sources":["../src/solid-core.module.ts"],"names":[],"mappings":"AA+QA,
|
|
1
|
+
{"version":3,"file":"solid-core.module.d.ts","sourceRoot":"","sources":["../src/solid-core.module.ts"],"names":[],"mappings":"AA+QA,qBAmVa,eAAe;CAAI"}
|
|
@@ -595,7 +595,8 @@ exports.SolidCoreModule = SolidCoreModule = __decorate([
|
|
|
595
595
|
config_1.ConfigModule,
|
|
596
596
|
publisher_factory_service_1.PublisherFactory,
|
|
597
597
|
mail_factory_1.MailFactory,
|
|
598
|
-
poller_service_1.PollerService
|
|
598
|
+
poller_service_1.PollerService,
|
|
599
|
+
ai_interaction_service_1.AiInteractionService,
|
|
599
600
|
],
|
|
600
601
|
})
|
|
601
602
|
], SolidCoreModule);
|