@smythos/sre 1.6.14 → 1.7.5
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/CHANGELOG +15 -0
- package/dist/index.js +66 -58
- package/dist/index.js.map +1 -1
- package/dist/types/Components/APIEndpoint.class.d.ts +2 -8
- package/dist/types/Components/Component.class.d.ts +9 -0
- package/dist/types/Components/Triggers/Gmail.trigger.d.ts +0 -17
- package/dist/types/Components/Triggers/JobScheduler.trigger.d.ts +10 -0
- package/dist/types/Components/Triggers/Trigger.class.d.ts +11 -0
- package/dist/types/Components/index.d.ts +6 -0
- package/dist/types/Core/Connector.class.d.ts +1 -0
- package/dist/types/Core/ConnectorsService.d.ts +2 -0
- package/dist/types/Core/HookService.d.ts +1 -1
- package/dist/types/helpers/BinaryInput.helper.d.ts +1 -1
- package/dist/types/helpers/Conversation.helper.d.ts +2 -0
- package/dist/types/helpers/Crypto.helper.d.ts +8 -0
- package/dist/types/helpers/LocalCache.helper.d.ts +18 -0
- package/dist/types/helpers/TemplateString.helper.d.ts +2 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/subsystems/AgentManager/Agent.class.d.ts +4 -2
- package/dist/types/subsystems/AgentManager/AgentData.service/AgentDataConnector.d.ts +13 -0
- package/dist/types/subsystems/AgentManager/AgentData.service/connectors/NullAgentData.class.d.ts +1 -4
- package/dist/types/subsystems/AgentManager/Scheduler.service/Job.class.d.ts +29 -6
- package/dist/types/subsystems/AgentManager/Scheduler.service/SchedulerConnector.d.ts +11 -3
- package/dist/types/subsystems/AgentManager/Scheduler.service/connectors/LocalScheduler.class.d.ts +31 -7
- package/dist/types/subsystems/IO/VectorDB.service/VectorDBConnector.d.ts +4 -4
- package/dist/types/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.d.ts +2 -2
- package/dist/types/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.d.ts +2 -2
- package/dist/types/subsystems/IO/VectorDB.service/connectors/RAMVecrtorDB.class.d.ts +2 -2
- package/dist/types/subsystems/IO/VectorDB.service/embed/BaseEmbedding.d.ts +16 -9
- package/dist/types/subsystems/IO/VectorDB.service/embed/index.d.ts +4 -1
- package/dist/types/subsystems/LLMManager/LLM.inference.d.ts +36 -2
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.d.ts +2 -5
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.d.ts +3 -6
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.d.ts +7 -0
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/xAI.class.d.ts +2 -5
- package/dist/types/types/Agent.types.d.ts +1 -0
- package/dist/types/types/LLM.types.d.ts +56 -36
- package/dist/types/types/SRE.types.d.ts +4 -1
- package/dist/types/types/VectorDB.types.d.ts +6 -3
- package/dist/types/utils/string.utils.d.ts +0 -4
- package/package.json +6 -2
- package/src/Components/APICall/OAuth.helper.ts +30 -35
- package/src/Components/APIEndpoint.class.ts +25 -6
- package/src/Components/Classifier.class.ts +8 -2
- package/src/Components/Component.class.ts +11 -0
- package/src/Components/GenAILLM.class.ts +11 -7
- package/src/Components/LLMAssistant.class.ts +12 -3
- package/src/Components/ScrapflyWebScrape.class.ts +8 -1
- package/src/Components/TavilyWebSearch.class.ts +4 -1
- package/src/Components/Triggers/Gmail.trigger.ts +282 -0
- package/src/Components/Triggers/JobScheduler.trigger.ts +45 -0
- package/src/Components/Triggers/README.md +3 -0
- package/src/Components/Triggers/Trigger.class.ts +101 -0
- package/src/Components/Triggers/WhatsApp.trigger.ts +219 -0
- package/src/Components/index.ts +8 -0
- package/src/Core/AgentProcess.helper.ts +4 -6
- package/src/Core/Connector.class.ts +11 -3
- package/src/Core/ConnectorsService.ts +5 -0
- package/src/Core/ExternalEventsReceiver.ts +317 -0
- package/src/Core/HookService.ts +20 -6
- package/src/Core/SmythRuntime.class.ts +20 -2
- package/src/Core/SystemEvents.ts +17 -0
- package/src/Core/boot.ts +2 -0
- package/src/helpers/BinaryInput.helper.ts +8 -8
- package/src/helpers/Conversation.helper.ts +46 -12
- package/src/helpers/Crypto.helper.ts +28 -0
- package/src/helpers/LocalCache.helper.ts +18 -0
- package/src/helpers/TemplateString.helper.ts +20 -9
- package/src/index.ts +13 -0
- package/src/index.ts.bak +13 -0
- package/src/subsystems/AGENTS.md +594 -0
- package/src/subsystems/AgentManager/Agent.class.ts +73 -21
- package/src/subsystems/AgentManager/AgentData.service/AgentDataConnector.ts +30 -6
- package/src/subsystems/AgentManager/AgentData.service/connectors/NullAgentData.class.ts +2 -2
- package/src/subsystems/AgentManager/AgentLogger.class.ts +1 -1
- package/src/subsystems/AgentManager/AgentRuntime.class.ts +34 -5
- package/src/subsystems/AgentManager/Scheduler.service/Job.class.ts +414 -0
- package/src/subsystems/AgentManager/Scheduler.service/Schedule.class.ts +200 -0
- package/src/subsystems/AgentManager/Scheduler.service/SchedulerConnector.ts +200 -0
- package/src/subsystems/AgentManager/Scheduler.service/connectors/LocalScheduler.class.ts +767 -0
- package/src/subsystems/AgentManager/Scheduler.service/index.ts +11 -0
- package/src/subsystems/IO/VectorDB.service/VectorDBConnector.ts +15 -4
- package/src/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.ts +32 -11
- package/src/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.ts +27 -10
- package/src/subsystems/IO/VectorDB.service/connectors/RAMVecrtorDB.class.ts +25 -9
- package/src/subsystems/IO/VectorDB.service/embed/BaseEmbedding.ts +182 -12
- package/src/subsystems/IO/VectorDB.service/embed/GoogleEmbedding.ts +1 -1
- package/src/subsystems/IO/VectorDB.service/embed/OpenAIEmbedding.ts +1 -1
- package/src/subsystems/IO/VectorDB.service/embed/index.ts +12 -2
- package/src/subsystems/LLMManager/LLM.inference.ts +76 -17
- package/src/subsystems/LLMManager/LLM.service/LLMCredentials.helper.ts +61 -2
- package/src/subsystems/LLMManager/LLM.service/connectors/Anthropic.class.ts +3 -0
- package/src/subsystems/LLMManager/LLM.service/connectors/Bedrock.class.ts +3 -1
- package/src/subsystems/LLMManager/LLM.service/connectors/Echo.class.ts +5 -1
- package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +247 -56
- package/src/subsystems/LLMManager/LLM.service/connectors/Groq.class.ts +3 -0
- package/src/subsystems/LLMManager/LLM.service/connectors/Ollama.class.ts +28 -21
- package/src/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.ts +3 -0
- package/src/subsystems/LLMManager/LLM.service/connectors/VertexAI.class.ts +121 -33
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.ts +38 -27
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ChatCompletionsApiInterface.ts +3 -2
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.ts +117 -20
- package/src/subsystems/LLMManager/LLM.service/connectors/xAI.class.ts +3 -0
- package/src/subsystems/LLMManager/ModelsProvider.service/ModelsProviderConnector.ts +3 -8
- package/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts +4 -1
- package/src/subsystems/MemoryManager/Cache.service/connectors/RedisCache.class.ts +12 -0
- package/src/subsystems/MemoryManager/LLMContext.ts +3 -8
- package/src/subsystems/MemoryManager/RuntimeContext.ts +10 -9
- package/src/subsystems/Security/Credentials/Credentials.class.ts +1 -0
- package/src/subsystems/Security/Credentials/ManagedOAuth2Credentials.class.ts +106 -0
- package/src/types/Agent.types.ts +1 -0
- package/src/types/LLM.types.ts +68 -40
- package/src/types/SRE.types.ts +3 -0
- package/src/types/VectorDB.types.ts +7 -3
- package/src/utils/string.utils.ts +193 -191
|
@@ -39,7 +39,7 @@ function parseKey(str: string = '', teamId: string): string {
|
|
|
39
39
|
export class APIEndpoint extends Component {
|
|
40
40
|
protected configSchema = Joi.object({
|
|
41
41
|
endpoint: Joi.string()
|
|
42
|
-
.pattern(/^[a-zA-Z0-
|
|
42
|
+
.pattern(/^[a-zA-Z0-9_]+([-_][a-zA-Z0-9_]+)*$/)
|
|
43
43
|
.max(50)
|
|
44
44
|
.required(),
|
|
45
45
|
method: Joi.string().valid('POST', 'GET').allow(''), //we're accepting empty value because we consider it POST by default.
|
|
@@ -57,13 +57,25 @@ export class APIEndpoint extends Component {
|
|
|
57
57
|
async process(input, config, agent: Agent) {
|
|
58
58
|
await super.process(input, config, agent);
|
|
59
59
|
|
|
60
|
+
if (typeof config.process === 'function') {
|
|
61
|
+
//special case, APIEndpoint has a custom process method.
|
|
62
|
+
//the inputs are not passed directly to APIEndpoints, we need to read them from the context.
|
|
63
|
+
const contextData = agent?.agentRuntime?.getComponentData(config.id);
|
|
64
|
+
|
|
65
|
+
const inputs = Array.isArray(contextData?.input) ? contextData?.input : [contextData?.input];
|
|
66
|
+
|
|
67
|
+
const result = await config.process.apply(null, inputs);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
const req: AgentRequest = agent.agentRequest;
|
|
61
72
|
const logger = this.createComponentLogger(agent, config);
|
|
62
73
|
|
|
74
|
+
const isTrigger = req.path.startsWith(agent.triggerBasePath);
|
|
63
75
|
const headers = req ? req.headers : {};
|
|
64
|
-
let body = req ? req.body : input; //handle debugger injection
|
|
65
|
-
const params = req ? req.params : {};
|
|
66
|
-
let query = req ? req.query : {};
|
|
76
|
+
let body = req && !isTrigger ? req.body : input; //handle debugger injection
|
|
77
|
+
const params = req && !isTrigger ? req.params : {};
|
|
78
|
+
let query = req && !isTrigger ? req.query : {};
|
|
67
79
|
const _authInfo = req ? req._agent_authinfo : undefined;
|
|
68
80
|
|
|
69
81
|
// parse template variables
|
|
@@ -87,7 +99,7 @@ export class APIEndpoint extends Component {
|
|
|
87
99
|
|
|
88
100
|
// set default value and agent variables
|
|
89
101
|
const inputsWithDefaultValue = config.inputs.filter(
|
|
90
|
-
(input) => input.defaultVal !== undefined && input.defaultVal !== '' && input.defaultVal !== null
|
|
102
|
+
(input) => input.defaultVal !== undefined && input.defaultVal !== '' && input.defaultVal !== null
|
|
91
103
|
);
|
|
92
104
|
|
|
93
105
|
const bodyInputNames: string[] = [];
|
|
@@ -218,7 +230,7 @@ export class APIEndpoint extends Component {
|
|
|
218
230
|
return await binaryInput.getJsonData(AccessCandidate.agent(agent.id));
|
|
219
231
|
}
|
|
220
232
|
return null;
|
|
221
|
-
})
|
|
233
|
+
})
|
|
222
234
|
);
|
|
223
235
|
|
|
224
236
|
// Filter out null values and handle single/multiple results
|
|
@@ -231,4 +243,11 @@ export class APIEndpoint extends Component {
|
|
|
231
243
|
|
|
232
244
|
return { headers, body, query, params, _authInfo, _debug: logger.output };
|
|
233
245
|
}
|
|
246
|
+
|
|
247
|
+
async postProcess(output, config, agent: Agent): Promise<any> {
|
|
248
|
+
if (typeof config.process === 'function') {
|
|
249
|
+
return output?.result;
|
|
250
|
+
}
|
|
251
|
+
return output;
|
|
252
|
+
}
|
|
234
253
|
}
|
|
@@ -115,7 +115,13 @@ ${JSON.stringify(categories, null, 2)}`;
|
|
|
115
115
|
|
|
116
116
|
try {
|
|
117
117
|
let response = await llmInference
|
|
118
|
-
.prompt({
|
|
118
|
+
.prompt({
|
|
119
|
+
query: prompt,
|
|
120
|
+
params: { ...config, agentId: agent.id },
|
|
121
|
+
onFallback: (data) => {
|
|
122
|
+
logger.debug(`\n ↩️ Using Fallback Model: ${data.model}`);
|
|
123
|
+
},
|
|
124
|
+
})
|
|
119
125
|
.catch((error) => ({ error: error }));
|
|
120
126
|
|
|
121
127
|
if (response?.error) {
|
|
@@ -146,7 +152,7 @@ ${JSON.stringify(categories, null, 2)}`;
|
|
|
146
152
|
delete parsed.error;
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
logger.
|
|
155
|
+
logger.debug('\n Classifier result\n', parsed);
|
|
150
156
|
|
|
151
157
|
parsed['_debug'] = logger.output;
|
|
152
158
|
|
|
@@ -144,4 +144,15 @@ export class Component {
|
|
|
144
144
|
hasOutput(id, config, agent: Agent): any {
|
|
145
145
|
return false;
|
|
146
146
|
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A generic registration function that can be overridden by child classes to register the trigger with providers
|
|
150
|
+
* this function is usually called outside of a workflow in order to register the trigger with providers
|
|
151
|
+
* @param componentId the id of the component to register (in SRE a single trigger instance handles all the triggers of the same type)
|
|
152
|
+
* @param agent the agent instance
|
|
153
|
+
* @param payload any additional payload to pass to the trigger
|
|
154
|
+
*/
|
|
155
|
+
async register(componentId: string, componentSettings: any, payload?: any) {}
|
|
156
|
+
|
|
157
|
+
async unregister(componentId: string, componentSettings: any, payload?: any) {}
|
|
147
158
|
}
|
|
@@ -7,6 +7,7 @@ import { getMimeType } from '@sre/utils/data.utils';
|
|
|
7
7
|
import { Component } from './Component.class';
|
|
8
8
|
import { formatDataForDebug } from '@sre/utils/data.utils';
|
|
9
9
|
import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class';
|
|
10
|
+
import { TLLMEvent } from '@sre/types/LLM.types';
|
|
10
11
|
|
|
11
12
|
//TODO : better handling of context window exceeding max length
|
|
12
13
|
|
|
@@ -480,13 +481,16 @@ export class GenAILLM extends Component {
|
|
|
480
481
|
...config.data,
|
|
481
482
|
agentId: agent.id,
|
|
482
483
|
},
|
|
484
|
+
onFallback: (fallbackInfo) => {
|
|
485
|
+
logger.debug(`\n ↩️ Using fallback model: ${fallbackInfo.model}`);
|
|
486
|
+
},
|
|
483
487
|
})
|
|
484
488
|
.catch((error) => {
|
|
485
489
|
console.error('Error on promptStream: ', error);
|
|
486
490
|
reject(error);
|
|
487
491
|
});
|
|
488
492
|
|
|
489
|
-
eventEmitter.on(
|
|
493
|
+
eventEmitter.on(TLLMEvent.Content, (content) => {
|
|
490
494
|
if (passThrough) {
|
|
491
495
|
if (typeof agent.callback === 'function') {
|
|
492
496
|
agent.callback({ content });
|
|
@@ -496,7 +500,7 @@ export class GenAILLM extends Component {
|
|
|
496
500
|
_content += content;
|
|
497
501
|
});
|
|
498
502
|
|
|
499
|
-
eventEmitter.on(
|
|
503
|
+
eventEmitter.on(TLLMEvent.Thinking, (thinking) => {
|
|
500
504
|
if (passThrough) {
|
|
501
505
|
if (typeof agent.callback === 'function') {
|
|
502
506
|
agent.callback({ thinking });
|
|
@@ -504,7 +508,7 @@ export class GenAILLM extends Component {
|
|
|
504
508
|
agent.sse.send('llm/passthrough/thinking', thinking.replace(/\n/g, '\\n'));
|
|
505
509
|
}
|
|
506
510
|
});
|
|
507
|
-
eventEmitter.on(
|
|
511
|
+
eventEmitter.on(TLLMEvent.End, () => {
|
|
508
512
|
if (passThrough) {
|
|
509
513
|
if (typeof agent.callback === 'function') {
|
|
510
514
|
agent.callback({ content: '\n' });
|
|
@@ -513,18 +517,18 @@ export class GenAILLM extends Component {
|
|
|
513
517
|
}
|
|
514
518
|
resolve(_content);
|
|
515
519
|
});
|
|
516
|
-
eventEmitter.on(
|
|
520
|
+
eventEmitter.on(TLLMEvent.Interrupted, (reason) => {
|
|
517
521
|
finishReason = reason || 'stop';
|
|
518
522
|
});
|
|
519
523
|
|
|
520
|
-
eventEmitter.on(
|
|
524
|
+
eventEmitter.on(TLLMEvent.Error, (error) => {
|
|
521
525
|
reject(error);
|
|
522
526
|
});
|
|
523
527
|
});
|
|
524
528
|
response = await contentPromise.catch((error) => {
|
|
525
529
|
return { error: error.message || error };
|
|
526
530
|
});
|
|
527
|
-
//
|
|
531
|
+
// If the model stopped before completing the response, this is usually due to output token limit reached.
|
|
528
532
|
if (finishReason !== 'stop') {
|
|
529
533
|
return {
|
|
530
534
|
Reply: response,
|
|
@@ -551,7 +555,7 @@ export class GenAILLM extends Component {
|
|
|
551
555
|
return { _error: Reply.error, _debug: logger.output };
|
|
552
556
|
}
|
|
553
557
|
|
|
554
|
-
logger.debug(' Reply \n', Reply);
|
|
558
|
+
logger.debug('\n Reply \n', Reply);
|
|
555
559
|
|
|
556
560
|
const result = { Reply };
|
|
557
561
|
|
|
@@ -113,7 +113,7 @@ export class LLMAssistant extends Component {
|
|
|
113
113
|
const conversationId = input.ConversationId;
|
|
114
114
|
|
|
115
115
|
let behavior = TemplateString(config.data.behavior).parse(input).result;
|
|
116
|
-
logger.debug(`[Parsed Behavior] \n${behavior}
|
|
116
|
+
logger.debug(`[Parsed Behavior] \n${behavior}`);
|
|
117
117
|
|
|
118
118
|
//#region get max tokens
|
|
119
119
|
let maxTokens = 2048;
|
|
@@ -154,6 +154,9 @@ export class LLMAssistant extends Component {
|
|
|
154
154
|
.promptStream({
|
|
155
155
|
contextWindow: messages,
|
|
156
156
|
params: { ...config, model, agentId: agent.id },
|
|
157
|
+
onFallback: (fallbackInfo) => {
|
|
158
|
+
logger.debug(`\n ↩️ Using fallback model: ${fallbackInfo.model}`);
|
|
159
|
+
},
|
|
157
160
|
})
|
|
158
161
|
.catch((error) => {
|
|
159
162
|
console.error('Error on promptStream: ', error);
|
|
@@ -180,7 +183,13 @@ export class LLMAssistant extends Component {
|
|
|
180
183
|
response = await contentPromise;
|
|
181
184
|
} else {
|
|
182
185
|
response = await llmInference
|
|
183
|
-
.prompt({
|
|
186
|
+
.prompt({
|
|
187
|
+
contextWindow: messages,
|
|
188
|
+
params: { ...config, agentId: agent.id },
|
|
189
|
+
onFallback: (fallbackInfo) => {
|
|
190
|
+
logger.debug(`\n ↩️ Using fallback model: ${fallbackInfo.model}`);
|
|
191
|
+
},
|
|
192
|
+
})
|
|
184
193
|
.catch((error) => ({ error: error }));
|
|
185
194
|
}
|
|
186
195
|
|
|
@@ -199,7 +208,7 @@ export class LLMAssistant extends Component {
|
|
|
199
208
|
messages.push({ role: 'assistant', content: response });
|
|
200
209
|
saveMessagesToSession(agent.id, userId, conversationId, messages, ttl);
|
|
201
210
|
|
|
202
|
-
logger.debug(' Response \n', response);
|
|
211
|
+
logger.debug('\n Response \n', response);
|
|
203
212
|
|
|
204
213
|
const result = { Response: response };
|
|
205
214
|
|
|
@@ -53,7 +53,14 @@ export class ScrapflyWebScrape extends Component {
|
|
|
53
53
|
logger.debug(`=== Web Scrape Log ===`);
|
|
54
54
|
let Output: any = {};
|
|
55
55
|
let _error = undefined;
|
|
56
|
-
|
|
56
|
+
|
|
57
|
+
// Filter inputs to include only the fields defined in the component's configuration.
|
|
58
|
+
// This prevents URLs from global variables from being scraped.
|
|
59
|
+
const componentInputNames = new Set(config.inputs.map((input) => input.name));
|
|
60
|
+
const filteredInput = Object.fromEntries(Object.entries(input).filter(([key]) => componentInputNames.has(key)));
|
|
61
|
+
|
|
62
|
+
const scrapeUrls = this.extractUrls(filteredInput);
|
|
63
|
+
|
|
57
64
|
logger.debug('Payload:', JSON.stringify(config.data));
|
|
58
65
|
logger.debug(`Vaild URLs: ${JSON.stringify(scrapeUrls)}`);
|
|
59
66
|
const teamId = agent.teamId;
|
|
@@ -55,6 +55,9 @@ export class TavilyWebSearch extends Component {
|
|
|
55
55
|
const api_key = await getCredentials(AccessCandidate.team(teamId), 'tavily');
|
|
56
56
|
|
|
57
57
|
logger.debug('Payload:', JSON.stringify(config.data));
|
|
58
|
+
|
|
59
|
+
const excludeDomains = config.data.excludeDomains?.length ? config.data.excludeDomains.split(',').map((d) => d.trim()) : [];
|
|
60
|
+
|
|
58
61
|
const response = await axios({
|
|
59
62
|
method: 'post',
|
|
60
63
|
url: 'https://api.tavily.com/search',
|
|
@@ -62,7 +65,7 @@ export class TavilyWebSearch extends Component {
|
|
|
62
65
|
api_key,
|
|
63
66
|
query: searchQuery,
|
|
64
67
|
topic: config.data.searchTopic,
|
|
65
|
-
exclude_domains:
|
|
68
|
+
exclude_domains: excludeDomains,
|
|
66
69
|
max_results: config.data.sourcesLimit,
|
|
67
70
|
...(config.data.timeRange !== 'None' ? { time_range: config.data.timeRange } : {}),
|
|
68
71
|
...(config.data.includeImages ? { include_images: true } : {}),
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class';
|
|
2
|
+
import { ManagedOAuth2Credentials } from '@sre/Security/Credentials/ManagedOAuth2Credentials.class';
|
|
3
|
+
import { IAgent as Agent } from '@sre/types/Agent.types';
|
|
4
|
+
import { findAll, innerText, isTag, removeElement } from 'domutils';
|
|
5
|
+
import { parseDocument } from 'htmlparser2';
|
|
6
|
+
import { Trigger } from './Trigger.class';
|
|
7
|
+
export type GmailTriggerAttachment = {
|
|
8
|
+
filename: string;
|
|
9
|
+
mimeType: string;
|
|
10
|
+
size: number;
|
|
11
|
+
attachmentId: string;
|
|
12
|
+
};
|
|
13
|
+
export type GmailTriggerMessage = {
|
|
14
|
+
id: string;
|
|
15
|
+
threadId: string;
|
|
16
|
+
labelIds: string[];
|
|
17
|
+
snippet: string;
|
|
18
|
+
sizeEstimate: number;
|
|
19
|
+
internalDate: string;
|
|
20
|
+
headers: {
|
|
21
|
+
from: string;
|
|
22
|
+
to: string;
|
|
23
|
+
cc: string;
|
|
24
|
+
bcc: string;
|
|
25
|
+
subject: string;
|
|
26
|
+
date: string;
|
|
27
|
+
messageId: string;
|
|
28
|
+
};
|
|
29
|
+
body: {
|
|
30
|
+
text: string;
|
|
31
|
+
html: string;
|
|
32
|
+
};
|
|
33
|
+
attachments: GmailTriggerAttachment[];
|
|
34
|
+
isUnread: true;
|
|
35
|
+
};
|
|
36
|
+
export class GmailTrigger extends Trigger {
|
|
37
|
+
async collectPayload(input, config, agent: Agent) {
|
|
38
|
+
const credentialsKey = config?.data?.oauth_cred_id;
|
|
39
|
+
const agentCandidate = AccessCandidate.agent(agent.id);
|
|
40
|
+
const oauth2Credentials = await ManagedOAuth2Credentials.load(credentialsKey, agentCandidate);
|
|
41
|
+
|
|
42
|
+
console.log(
|
|
43
|
+
oauth2Credentials.accessToken,
|
|
44
|
+
oauth2Credentials.refreshToken,
|
|
45
|
+
oauth2Credentials.expiresIn,
|
|
46
|
+
oauth2Credentials.scope,
|
|
47
|
+
oauth2Credentials.tokenUrl,
|
|
48
|
+
oauth2Credentials.service
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
await oauth2Credentials.refreshAccessToken();
|
|
52
|
+
|
|
53
|
+
const messages = await getMostRecentUnreadMessage(0, oauth2Credentials, 5);
|
|
54
|
+
return messages;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Make an authenticated Gmail API request
|
|
60
|
+
*/
|
|
61
|
+
async function gmailApiRequest(endpoint, accessToken) {
|
|
62
|
+
const url = `https://www.googleapis.com/gmail/v1/users/me${endpoint}`;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const result = await fetch(url, {
|
|
66
|
+
headers: {
|
|
67
|
+
Authorization: `Bearer ${accessToken}`,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
const json = await result.json();
|
|
71
|
+
console.log(json);
|
|
72
|
+
return json;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new Error(`Gmail API request failed: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get the most recent unread messages
|
|
80
|
+
*/
|
|
81
|
+
async function getMostRecentUnreadMessage(
|
|
82
|
+
retryCount = 0,
|
|
83
|
+
oauth2Credentials: ManagedOAuth2Credentials,
|
|
84
|
+
numMessages: number = 1,
|
|
85
|
+
excludeMessageId?: string
|
|
86
|
+
) {
|
|
87
|
+
const MAX_RETRIES = 1; // Only retry once to prevent infinite loops
|
|
88
|
+
|
|
89
|
+
let accessToken = oauth2Credentials.accessToken;
|
|
90
|
+
|
|
91
|
+
// If no access token provided, or if requests fail, refresh it
|
|
92
|
+
if (!accessToken) {
|
|
93
|
+
accessToken = await oauth2Credentials.refreshAccessToken();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
console.log('Fetching unread messages...');
|
|
98
|
+
|
|
99
|
+
const collectedIds: string[] = [];
|
|
100
|
+
let pageToken: string | undefined = undefined;
|
|
101
|
+
let reachedBoundary = false;
|
|
102
|
+
|
|
103
|
+
while (collectedIds.length < numMessages && !reachedBoundary) {
|
|
104
|
+
const pageSize = Math.min(Math.max(numMessages, 1), 100);
|
|
105
|
+
const pageQuery = `/messages?q=is:unread&maxResults=${pageSize}${pageToken ? `&pageToken=${pageToken}` : ''}`;
|
|
106
|
+
const listResponse = await gmailApiRequest(pageQuery, accessToken);
|
|
107
|
+
|
|
108
|
+
const messages = Array.isArray(listResponse.messages) ? listResponse.messages : [];
|
|
109
|
+
if (messages.length === 0) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const m of messages) {
|
|
114
|
+
if (excludeMessageId && m.id === excludeMessageId) {
|
|
115
|
+
reachedBoundary = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
collectedIds.push(m.id);
|
|
119
|
+
if (collectedIds.length >= numMessages) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (collectedIds.length >= numMessages || reachedBoundary || !listResponse.nextPageToken) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
pageToken = listResponse.nextPageToken;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (collectedIds.length === 0) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Fetch full message details in parallel
|
|
135
|
+
const details = await Promise.all(collectedIds.map((id) => gmailApiRequest(`/messages/${id}?format=full`, accessToken)));
|
|
136
|
+
const parsed = details.map((d) => parseMessage(d));
|
|
137
|
+
return parsed;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// If token expired, try refreshing and retry once
|
|
140
|
+
if ((error.message.includes('401') || error.message.includes('unauthorized')) && retryCount < MAX_RETRIES) {
|
|
141
|
+
console.log('Access token expired, refreshing...');
|
|
142
|
+
try {
|
|
143
|
+
accessToken = await oauth2Credentials.refreshAccessToken();
|
|
144
|
+
return await getMostRecentUnreadMessage(retryCount + 1, oauth2Credentials, numMessages, excludeMessageId);
|
|
145
|
+
} catch (refreshError) {
|
|
146
|
+
throw new Error(`Authentication failed: ${refreshError.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Parse Gmail message into structured format
|
|
156
|
+
*/
|
|
157
|
+
function parseMessage(message) {
|
|
158
|
+
const headers: any = {};
|
|
159
|
+
const payload = message.payload || {};
|
|
160
|
+
|
|
161
|
+
// Extract headers
|
|
162
|
+
if (payload.headers) {
|
|
163
|
+
payload.headers.forEach((header) => {
|
|
164
|
+
headers[header.name.toLowerCase()] = header.value;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Extract body content
|
|
169
|
+
let textBody = '';
|
|
170
|
+
let htmlBody = '';
|
|
171
|
+
|
|
172
|
+
function extractBody(part) {
|
|
173
|
+
if (part.mimeType === 'text/plain' && part.body && part.body.data) {
|
|
174
|
+
textBody = Buffer.from(part.body.data, 'base64').toString('utf-8');
|
|
175
|
+
} else if (part.mimeType === 'text/html' && part.body && part.body.data) {
|
|
176
|
+
htmlBody = Buffer.from(part.body.data, 'base64').toString('utf-8');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (part.parts) {
|
|
180
|
+
part.parts.forEach(extractBody);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (payload.parts) {
|
|
185
|
+
payload.parts.forEach(extractBody);
|
|
186
|
+
} else if (payload.body && payload.body.data) {
|
|
187
|
+
// Single part message
|
|
188
|
+
if (payload.mimeType === 'text/plain') {
|
|
189
|
+
textBody = Buffer.from(payload.body.data, 'base64').toString('utf-8');
|
|
190
|
+
} else if (payload.mimeType === 'text/html') {
|
|
191
|
+
htmlBody = Buffer.from(payload.body.data, 'base64').toString('utf-8');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (textBody && !htmlBody) {
|
|
196
|
+
htmlBody = textBody;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (htmlBody && !textBody) {
|
|
200
|
+
textBody = htmlToPlainText(htmlBody);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Get attachments info
|
|
204
|
+
const attachments = [];
|
|
205
|
+
function findAttachments(part) {
|
|
206
|
+
if (part.filename && part.filename.length > 0) {
|
|
207
|
+
attachments.push({
|
|
208
|
+
filename: part.filename,
|
|
209
|
+
mimeType: part.mimeType,
|
|
210
|
+
size: part.body ? part.body.size : 0,
|
|
211
|
+
attachmentId: part.body ? part.body.attachmentId : null,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (part.parts) {
|
|
216
|
+
part.parts.forEach(findAttachments);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (payload.parts) {
|
|
221
|
+
payload.parts.forEach(findAttachments);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
email: {
|
|
226
|
+
id: message.id,
|
|
227
|
+
threadId: message.threadId,
|
|
228
|
+
labelIds: message.labelIds || [],
|
|
229
|
+
snippet: message.snippet || '',
|
|
230
|
+
sizeEstimate: message.sizeEstimate || 0,
|
|
231
|
+
internalDate: message.internalDate ? new Date(parseInt(message.internalDate)).toISOString() : null,
|
|
232
|
+
headers: {
|
|
233
|
+
from: headers.from || '',
|
|
234
|
+
to: headers.to || '',
|
|
235
|
+
cc: headers.cc || '',
|
|
236
|
+
bcc: headers.bcc || '',
|
|
237
|
+
subject: headers.subject || '',
|
|
238
|
+
date: headers.date || '',
|
|
239
|
+
messageId: headers['message-id'] || '',
|
|
240
|
+
},
|
|
241
|
+
body: {
|
|
242
|
+
text: textBody,
|
|
243
|
+
html: htmlBody,
|
|
244
|
+
},
|
|
245
|
+
attachments: attachments,
|
|
246
|
+
isUnread: message.labelIds ? message.labelIds.includes('UNREAD') : false,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
var populateChar = function (ch, amount) {
|
|
252
|
+
var result = '';
|
|
253
|
+
for (var i = 0; i < amount; i += 1) {
|
|
254
|
+
result += ch;
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
function htmlToPlainText(htmlText, _styleConfig?) {
|
|
260
|
+
try {
|
|
261
|
+
const document = parseDocument(String(htmlText));
|
|
262
|
+
const nodesToRemove = findAll((node) => isTag(node) && (node.name === 'script' || node.name === 'style'), document.children);
|
|
263
|
+
nodesToRemove.forEach((node) => removeElement(node));
|
|
264
|
+
|
|
265
|
+
let text = innerText(document);
|
|
266
|
+
|
|
267
|
+
text = text.replace(/\u00A0/g, ' ');
|
|
268
|
+
text = text.replace(/\r\n?/g, '\n');
|
|
269
|
+
text = text.replace(/\t+/g, ' ');
|
|
270
|
+
text = text.replace(/[ \t\f]+\n/g, '\n');
|
|
271
|
+
text = text.replace(/\n{3,}/g, '\n\n');
|
|
272
|
+
text = text.replace(/[ ]{2,}/g, ' ');
|
|
273
|
+
text = text.replace(/^\s+|\s+$/g, '');
|
|
274
|
+
|
|
275
|
+
if (text.length === 0 || text.lastIndexOf('\n') !== text.length - 1) {
|
|
276
|
+
text += '\n';
|
|
277
|
+
}
|
|
278
|
+
return text;
|
|
279
|
+
} catch (err) {
|
|
280
|
+
return String(htmlText);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { IAgent as Agent, IAgent } from '@sre/types/Agent.types';
|
|
2
|
+
import { Trigger } from './Trigger.class';
|
|
3
|
+
import { AgentRequest } from '@sre/AgentManager/AgentRequest.class';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { ConnectorService } from '@sre/Core/ConnectorsService';
|
|
6
|
+
import { ISchedulerRequest } from '@sre/AgentManager/Scheduler.service/SchedulerConnector';
|
|
7
|
+
import { Schedule } from '@sre/AgentManager/Scheduler.service/Schedule.class';
|
|
8
|
+
import { Job } from '@sre/AgentManager/Scheduler.service/Job.class';
|
|
9
|
+
import { JSONContent } from '@sre/helpers/JsonContent.helper';
|
|
10
|
+
|
|
11
|
+
export class JobSchedulerTrigger extends Trigger {
|
|
12
|
+
async collectPayload(input, settings, agent: Agent) {
|
|
13
|
+
const req = agent.agentRequest;
|
|
14
|
+
|
|
15
|
+
let payload: any = JSONContent(settings?.data?.payload).tryParse();
|
|
16
|
+
if (typeof payload !== 'object') {
|
|
17
|
+
payload = {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
...payload,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async register(componentId: string, componentSettings: any, payload?: { agentData: IAgent; triggerUrl: string }) {
|
|
28
|
+
try {
|
|
29
|
+
const agent: any = payload?.agentData;
|
|
30
|
+
const triggerName = componentSettings.triggerEndpoint;
|
|
31
|
+
const schedulerConnector = ConnectorService.getSchedulerConnector();
|
|
32
|
+
if (!schedulerConnector) {
|
|
33
|
+
throw new Error('Scheduler connector not found');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const schedulerRequester: ISchedulerRequest = schedulerConnector.agent(agent.id);
|
|
37
|
+
const jobId = `job-${agent.id}-${triggerName}`;
|
|
38
|
+
await schedulerRequester.add(jobId, new Job({ agentId: agent.id, type: 'trigger', triggerName }), Schedule.every('10s'));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error('Failed to schedule job');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async unregister(componentId: string, agent: Agent, payload?: any) {}
|
|
45
|
+
}
|