@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.
Files changed (115) hide show
  1. package/CHANGELOG +15 -0
  2. package/dist/index.js +66 -58
  3. package/dist/index.js.map +1 -1
  4. package/dist/types/Components/APIEndpoint.class.d.ts +2 -8
  5. package/dist/types/Components/Component.class.d.ts +9 -0
  6. package/dist/types/Components/Triggers/Gmail.trigger.d.ts +0 -17
  7. package/dist/types/Components/Triggers/JobScheduler.trigger.d.ts +10 -0
  8. package/dist/types/Components/Triggers/Trigger.class.d.ts +11 -0
  9. package/dist/types/Components/index.d.ts +6 -0
  10. package/dist/types/Core/Connector.class.d.ts +1 -0
  11. package/dist/types/Core/ConnectorsService.d.ts +2 -0
  12. package/dist/types/Core/HookService.d.ts +1 -1
  13. package/dist/types/helpers/BinaryInput.helper.d.ts +1 -1
  14. package/dist/types/helpers/Conversation.helper.d.ts +2 -0
  15. package/dist/types/helpers/Crypto.helper.d.ts +8 -0
  16. package/dist/types/helpers/LocalCache.helper.d.ts +18 -0
  17. package/dist/types/helpers/TemplateString.helper.d.ts +2 -1
  18. package/dist/types/index.d.ts +13 -0
  19. package/dist/types/subsystems/AgentManager/Agent.class.d.ts +4 -2
  20. package/dist/types/subsystems/AgentManager/AgentData.service/AgentDataConnector.d.ts +13 -0
  21. package/dist/types/subsystems/AgentManager/AgentData.service/connectors/NullAgentData.class.d.ts +1 -4
  22. package/dist/types/subsystems/AgentManager/Scheduler.service/Job.class.d.ts +29 -6
  23. package/dist/types/subsystems/AgentManager/Scheduler.service/SchedulerConnector.d.ts +11 -3
  24. package/dist/types/subsystems/AgentManager/Scheduler.service/connectors/LocalScheduler.class.d.ts +31 -7
  25. package/dist/types/subsystems/IO/VectorDB.service/VectorDBConnector.d.ts +4 -4
  26. package/dist/types/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.d.ts +2 -2
  27. package/dist/types/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.d.ts +2 -2
  28. package/dist/types/subsystems/IO/VectorDB.service/connectors/RAMVecrtorDB.class.d.ts +2 -2
  29. package/dist/types/subsystems/IO/VectorDB.service/embed/BaseEmbedding.d.ts +16 -9
  30. package/dist/types/subsystems/IO/VectorDB.service/embed/index.d.ts +4 -1
  31. package/dist/types/subsystems/LLMManager/LLM.inference.d.ts +36 -2
  32. package/dist/types/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.d.ts +2 -5
  33. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.d.ts +3 -6
  34. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.d.ts +7 -0
  35. package/dist/types/subsystems/LLMManager/LLM.service/connectors/xAI.class.d.ts +2 -5
  36. package/dist/types/types/Agent.types.d.ts +1 -0
  37. package/dist/types/types/LLM.types.d.ts +56 -36
  38. package/dist/types/types/SRE.types.d.ts +4 -1
  39. package/dist/types/types/VectorDB.types.d.ts +6 -3
  40. package/dist/types/utils/string.utils.d.ts +0 -4
  41. package/package.json +6 -2
  42. package/src/Components/APICall/OAuth.helper.ts +30 -35
  43. package/src/Components/APIEndpoint.class.ts +25 -6
  44. package/src/Components/Classifier.class.ts +8 -2
  45. package/src/Components/Component.class.ts +11 -0
  46. package/src/Components/GenAILLM.class.ts +11 -7
  47. package/src/Components/LLMAssistant.class.ts +12 -3
  48. package/src/Components/ScrapflyWebScrape.class.ts +8 -1
  49. package/src/Components/TavilyWebSearch.class.ts +4 -1
  50. package/src/Components/Triggers/Gmail.trigger.ts +282 -0
  51. package/src/Components/Triggers/JobScheduler.trigger.ts +45 -0
  52. package/src/Components/Triggers/README.md +3 -0
  53. package/src/Components/Triggers/Trigger.class.ts +101 -0
  54. package/src/Components/Triggers/WhatsApp.trigger.ts +219 -0
  55. package/src/Components/index.ts +8 -0
  56. package/src/Core/AgentProcess.helper.ts +4 -6
  57. package/src/Core/Connector.class.ts +11 -3
  58. package/src/Core/ConnectorsService.ts +5 -0
  59. package/src/Core/ExternalEventsReceiver.ts +317 -0
  60. package/src/Core/HookService.ts +20 -6
  61. package/src/Core/SmythRuntime.class.ts +20 -2
  62. package/src/Core/SystemEvents.ts +17 -0
  63. package/src/Core/boot.ts +2 -0
  64. package/src/helpers/BinaryInput.helper.ts +8 -8
  65. package/src/helpers/Conversation.helper.ts +46 -12
  66. package/src/helpers/Crypto.helper.ts +28 -0
  67. package/src/helpers/LocalCache.helper.ts +18 -0
  68. package/src/helpers/TemplateString.helper.ts +20 -9
  69. package/src/index.ts +13 -0
  70. package/src/index.ts.bak +13 -0
  71. package/src/subsystems/AGENTS.md +594 -0
  72. package/src/subsystems/AgentManager/Agent.class.ts +73 -21
  73. package/src/subsystems/AgentManager/AgentData.service/AgentDataConnector.ts +30 -6
  74. package/src/subsystems/AgentManager/AgentData.service/connectors/NullAgentData.class.ts +2 -2
  75. package/src/subsystems/AgentManager/AgentLogger.class.ts +1 -1
  76. package/src/subsystems/AgentManager/AgentRuntime.class.ts +34 -5
  77. package/src/subsystems/AgentManager/Scheduler.service/Job.class.ts +414 -0
  78. package/src/subsystems/AgentManager/Scheduler.service/Schedule.class.ts +200 -0
  79. package/src/subsystems/AgentManager/Scheduler.service/SchedulerConnector.ts +200 -0
  80. package/src/subsystems/AgentManager/Scheduler.service/connectors/LocalScheduler.class.ts +767 -0
  81. package/src/subsystems/AgentManager/Scheduler.service/index.ts +11 -0
  82. package/src/subsystems/IO/VectorDB.service/VectorDBConnector.ts +15 -4
  83. package/src/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.ts +32 -11
  84. package/src/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.ts +27 -10
  85. package/src/subsystems/IO/VectorDB.service/connectors/RAMVecrtorDB.class.ts +25 -9
  86. package/src/subsystems/IO/VectorDB.service/embed/BaseEmbedding.ts +182 -12
  87. package/src/subsystems/IO/VectorDB.service/embed/GoogleEmbedding.ts +1 -1
  88. package/src/subsystems/IO/VectorDB.service/embed/OpenAIEmbedding.ts +1 -1
  89. package/src/subsystems/IO/VectorDB.service/embed/index.ts +12 -2
  90. package/src/subsystems/LLMManager/LLM.inference.ts +76 -17
  91. package/src/subsystems/LLMManager/LLM.service/LLMCredentials.helper.ts +61 -2
  92. package/src/subsystems/LLMManager/LLM.service/connectors/Anthropic.class.ts +3 -0
  93. package/src/subsystems/LLMManager/LLM.service/connectors/Bedrock.class.ts +3 -1
  94. package/src/subsystems/LLMManager/LLM.service/connectors/Echo.class.ts +5 -1
  95. package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +247 -56
  96. package/src/subsystems/LLMManager/LLM.service/connectors/Groq.class.ts +3 -0
  97. package/src/subsystems/LLMManager/LLM.service/connectors/Ollama.class.ts +28 -21
  98. package/src/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.ts +3 -0
  99. package/src/subsystems/LLMManager/LLM.service/connectors/VertexAI.class.ts +121 -33
  100. package/src/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.ts +38 -27
  101. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ChatCompletionsApiInterface.ts +3 -2
  102. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.ts +117 -20
  103. package/src/subsystems/LLMManager/LLM.service/connectors/xAI.class.ts +3 -0
  104. package/src/subsystems/LLMManager/ModelsProvider.service/ModelsProviderConnector.ts +3 -8
  105. package/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts +4 -1
  106. package/src/subsystems/MemoryManager/Cache.service/connectors/RedisCache.class.ts +12 -0
  107. package/src/subsystems/MemoryManager/LLMContext.ts +3 -8
  108. package/src/subsystems/MemoryManager/RuntimeContext.ts +10 -9
  109. package/src/subsystems/Security/Credentials/Credentials.class.ts +1 -0
  110. package/src/subsystems/Security/Credentials/ManagedOAuth2Credentials.class.ts +106 -0
  111. package/src/types/Agent.types.ts +1 -0
  112. package/src/types/LLM.types.ts +68 -40
  113. package/src/types/SRE.types.ts +3 -0
  114. package/src/types/VectorDB.types.ts +7 -3
  115. 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-9]+([-_][a-zA-Z0-9]+)*$/)
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({ query: prompt, params: { ...config, agentId: agent.id } })
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.log(' Classifier result\n', parsed);
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('content', (content) => {
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('thinking', (thinking) => {
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('end', () => {
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('interrupted', (reason) => {
520
+ eventEmitter.on(TLLMEvent.Interrupted, (reason) => {
517
521
  finishReason = reason || 'stop';
518
522
  });
519
523
 
520
- eventEmitter.on('error', (error) => {
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
- // // If the model stopped before completing the response, this is usually due to output token limit reached.
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}\n\n`);
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({ contextWindow: messages, params: { ...config, agentId: agent.id } })
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
- const scrapeUrls = this.extractUrls(input);
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: config.data.excludeDomains?.length ? config.data.excludeDomains.split(',') : [],
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
+ }
@@ -0,0 +1,3 @@
1
+ # Triggers System
2
+
3
+ ### !!!Work in progress!!! This is still a work in progress, the system is not yet fully implemented.