@smythos/sre 1.7.16 → 1.7.17

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.
@@ -10,6 +10,7 @@ export declare class Agent implements IAgent {
10
10
  name: any;
11
11
  data: any;
12
12
  teamId: any;
13
+ conversationId: string;
13
14
  components: any;
14
15
  connections: any;
15
16
  endpoints: any;
@@ -51,7 +52,7 @@ export declare class Agent implements IAgent {
51
52
  private parseVariables;
52
53
  process(endpointPath: any, input: any): Promise<any>;
53
54
  private updateTasksCount;
54
- postProcess(result: any): Promise<any>;
55
+ postProcess(_result: any): Promise<any>;
55
56
  private hasLoopAncestor;
56
57
  private clearChildLoopRuntimeComponentData;
57
58
  private getComponentMissingInputs;
@@ -5,4 +5,5 @@ export declare class EmbodimentSettings {
5
5
  init(agentId: any): Promise<void>;
6
6
  ready(maxWait?: number): Promise<unknown>;
7
7
  get(embodimentType: string, key?: string): any;
8
+ getAll(): {};
8
9
  }
@@ -11,6 +11,7 @@ export type TAgentProcessParams = {
11
11
  params?: any;
12
12
  };
13
13
  export interface IAgent {
14
+ conversationId: string;
14
15
  id: any;
15
16
  jobID: any;
16
17
  async: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smythos/sre",
3
- "version": "1.7.16",
3
+ "version": "1.7.17",
4
4
  "description": "Smyth Runtime Environment",
5
5
  "author": "Alaa-eddine KADDOURI",
6
6
  "license": "MIT",
@@ -78,25 +78,6 @@ export class APIEndpoint extends Component {
78
78
  let query = req && !isTrigger ? req.query : {};
79
79
  const _authInfo = req ? req._agent_authinfo : undefined;
80
80
 
81
- // parse template variables
82
- for (const [key, value] of Object.entries(body)) {
83
- if (isKeyTemplateVar(value as string)) {
84
- body[key] = await parseKey(value as string, agent?.teamId);
85
- } else if (isTemplateVar(value as string)) {
86
- //body[key] = parseTemplate(value as string, input, { escapeString: false });
87
- body[key] = TemplateString(value as string).parse(input).result;
88
- }
89
- }
90
-
91
- for (const [key, value] of Object.entries(query)) {
92
- if (isKeyTemplateVar(value as string)) {
93
- query[key] = await parseKey(value as string, agent?.teamId);
94
- } else if (isTemplateVar(value as string)) {
95
- //query[key] = parseTemplate(value as string, input, { escapeString: false });
96
- query[key] = TemplateString(value as string).parse(input).result;
97
- }
98
- }
99
-
100
81
  // set default value and agent variables
101
82
  const inputsWithDefaultValue = config.inputs.filter(
102
83
  (input) => input.defaultVal !== undefined && input.defaultVal !== '' && input.defaultVal !== null
@@ -158,15 +139,23 @@ export class APIEndpoint extends Component {
158
139
  //body = input;
159
140
  }
160
141
 
161
- // ensure strong data type
142
+ // #region parse all template variables (after debugger injection and defaults are set)
143
+ body = await resolveTemplateVariables(body, input, agent);
144
+ query = await resolveTemplateVariables(query, input, agent);
145
+ // #endregion parse all template variables
146
+
147
+ // #region ensure strong data type
162
148
  body = await performTypeInference(body, config.inputs, agent);
163
149
  query = await performTypeInference(query, config.inputs, agent);
150
+ // #endregion ensure strong data type
164
151
 
152
+ // #region log inputs
165
153
  logger.debug('Parsing inputs');
166
154
  logger.debug(' Headers', headers);
167
155
  logger.debug(' Body', body);
168
156
  logger.debug(' Params', params);
169
157
  logger.debug(' Query', query);
158
+ // #endregion log inputs
170
159
 
171
160
  //Handle JSON Data
172
161
  //FIXME : this is a workaround that parses any json string in the body, we should only parse the json string in the body if the data type is explicitely set to JSON
@@ -251,3 +240,20 @@ export class APIEndpoint extends Component {
251
240
  return output;
252
241
  }
253
242
  }
243
+
244
+ async function resolveTemplateVariables(data: any, input: any, agent: Agent): Promise<any> {
245
+ for (const [key, value] of Object.entries(data)) {
246
+ if (isKeyTemplateVar(value as string)) {
247
+ data[key] = await parseKey(value as string, agent.teamId);
248
+ } else if (isTemplateVar(value as string)) {
249
+ // Parse using input values first, then agent variables.
250
+ // This correctly resolves cases where input values reference agent variables with the same name.
251
+ // Example: agent variables { user_id: "123" }, input { user_id: "{{user_id}}" }.
252
+ data[key] = TemplateString(value as string)
253
+ .parse(input)
254
+ .parse(agent.agentVariables).result;
255
+ }
256
+ }
257
+
258
+ return data;
259
+ }
@@ -5,9 +5,9 @@ import { ConnectorService } from '@sre/Core/ConnectorsService';
5
5
  import { AWSCredentials, AWSRegionConfig } from '@sre/types/AWS.types';
6
6
  import { calculateExecutionCost, generateCodeFromLegacyComponent, getLambdaCredentials, reportUsage } from '@sre/helpers/AWSLambdaCode.helper';
7
7
  import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class';
8
+ import { TemplateString } from '@sre/helpers/TemplateString.helper';
8
9
 
9
10
  export class ServerlessCode extends Component {
10
-
11
11
  protected configSchema = Joi.object({
12
12
  code_imports: Joi.string().max(1000).allow('').label('Imports'),
13
13
  code_body: Joi.string().max(500000).allow('').label('Code'),
@@ -26,10 +26,10 @@ export class ServerlessCode extends Component {
26
26
  constructor() {
27
27
  super();
28
28
  }
29
- init() { }
29
+ init() {}
30
30
 
31
31
  async process(input, config, agent: Agent) {
32
- await new Promise(resolve => setTimeout(resolve, 10000));
32
+ await new Promise((resolve) => setTimeout(resolve, 10000));
33
33
  await super.process(input, config, agent);
34
34
  const logger = this.createComponentLogger(agent, config);
35
35
  try {
@@ -39,22 +39,29 @@ export class ServerlessCode extends Component {
39
39
  const componentInputs = agent.components[config.id]?.inputs || {};
40
40
 
41
41
  let codeInputs = {};
42
+
42
43
  for (let field of componentInputs) {
43
- const _type = typeof input[field.name];
44
+ // Parse using input values first, then agent variables.
45
+ // This correctly resolves cases where input values reference agent variables with the same name.
46
+ // Example: agent variables { user_id: "123" }, input { user_id: "{{user_id}}" }.
47
+ const inputValue = TemplateString(input[field.name]).parse(input).parse(agent.agentVariables).result;
48
+
49
+ const _type = typeof inputValue;
50
+
44
51
  switch (_type) {
45
52
  case 'string':
46
53
  try {
47
- codeInputs[field.name] = JSON.parse(input[field.name].replace(/\\"/g, '"'));
54
+ codeInputs[field.name] = JSON.parse(inputValue.replace(/\\"/g, '"'));
48
55
  } catch (error) {
49
- codeInputs[field.name] = `${input[field.name]}`;
56
+ codeInputs[field.name] = `${inputValue}`;
50
57
  }
51
58
  break;
52
59
  case 'number':
53
60
  case 'boolean':
54
- codeInputs[field.name] = input[field.name];
61
+ codeInputs[field.name] = inputValue;
55
62
  break;
56
63
  default:
57
- codeInputs[field.name] = input[field.name];
64
+ codeInputs[field.name] = inputValue;
58
65
  break;
59
66
  }
60
67
  }
@@ -62,35 +69,38 @@ export class ServerlessCode extends Component {
62
69
  logger.debug(`\nInput Variables: \n${JSON.stringify(codeInputs, null, 2)}\n`);
63
70
 
64
71
  let codeConnector = ConnectorService.getCodeConnector();
65
- let codeCredentials: AWSCredentials & AWSRegionConfig & { isUserProvidedKeys: boolean } =
66
- await getLambdaCredentials(agent, config);
72
+ let codeCredentials: AWSCredentials & AWSRegionConfig & { isUserProvidedKeys: boolean } = await getLambdaCredentials(agent, config);
67
73
 
68
74
  if (codeCredentials.isUserProvidedKeys) {
69
75
  codeConnector = codeConnector.instance({
70
76
  region: codeCredentials.region,
71
77
  accessKeyId: codeCredentials.accessKeyId,
72
78
  secretAccessKey: codeCredentials.secretAccessKey,
73
- })
79
+ });
74
80
  }
75
81
  let code = config?.data?.code;
76
82
  if (!code) {
77
- code = generateCodeFromLegacyComponent(config.data.code_body, config.data.code_imports, Object.keys(codeInputs))
83
+ code = generateCodeFromLegacyComponent(config.data.code_body, config.data.code_imports, Object.keys(codeInputs));
78
84
  }
79
85
  // Deploy lambda function if it doesn't exist or the code hash is different
80
- await codeConnector.agent(agent.id)
81
- .deploy(config.id, {
86
+ await codeConnector.agent(agent.id).deploy(
87
+ config.id,
88
+ {
82
89
  code,
83
90
  inputs: codeInputs,
84
- }, {
91
+ },
92
+ {
85
93
  runtime: 'nodejs',
86
- });
94
+ }
95
+ );
87
96
 
88
97
  try {
89
98
  const executionResponse = await codeConnector.agent(agent.id).execute(config.id, codeInputs);
90
99
  const executionTime = executionResponse.executionTime;
91
100
  logger.debug(
92
- `Code result:\n ${typeof executionResponse.output === 'object' ? JSON.stringify(executionResponse.output, null, 2) : executionResponse.output
93
- }\n`,
101
+ `Code result:\n ${
102
+ typeof executionResponse.output === 'object' ? JSON.stringify(executionResponse.output, null, 2) : executionResponse.output
103
+ }\n`
94
104
  );
95
105
  logger.debug(`Execution time: ${executionTime}ms\n`);
96
106
 
@@ -697,7 +697,7 @@ export class Conversation extends EventEmitter {
697
697
 
698
698
  reqConfig.headers['X-CACHE-ID'] = this._context?.llmCache?.id;
699
699
 
700
- reqConfig.headers['X-REQUEST-TAG'] = this.id;
700
+ //reqConfig.headers['X-REQUEST-TAG'] = this.id;
701
701
  /*
702
702
  * Objective for the following conditions:
703
703
  * - In case it is not a debug call and there is no monitor id, then we need to run the agent locally to reduce latency
@@ -717,6 +717,10 @@ export class Conversation extends EventEmitter {
717
717
  //reqConfig.headers['X-AGENT-HAS-ATTACHMENTS'] !== undefined;
718
718
  reqConfig.headers['x-conversation-id'] !== undefined;
719
719
 
720
+ //we force the conversationId header after checking that it was not remotely set
721
+ if (!reqConfig.headers['x-conversation-id']) {
722
+ reqConfig.headers['x-conversation-id'] = this.id;
723
+ }
720
724
  if (canRunLocally && !requiresRemoteCall) {
721
725
  console.log('RUNNING AGENT LOCALLY');
722
726
  let agentProcess;
@@ -754,7 +758,7 @@ export class Conversation extends EventEmitter {
754
758
  });
755
759
  };
756
760
 
757
- const eventSource = new EventSource(monitUrl, {
761
+ eventSource = new EventSource(monitUrl, {
758
762
  fetch: customFetch,
759
763
  });
760
764
  let monitorId = '';
@@ -24,6 +24,8 @@ export class Agent implements IAgent {
24
24
  public name: any;
25
25
  public data: any;
26
26
  public teamId: any;
27
+ //if the agent was triggered from a conversation, this will be the conversation id
28
+ public conversationId: string;
27
29
  public components: any;
28
30
  public connections: any;
29
31
  public endpoints: any = {};
@@ -406,7 +408,8 @@ export class Agent implements IAgent {
406
408
  }
407
409
 
408
410
  @hookAsync('SREAgent.postProcess')
409
- public async postProcess(result) {
411
+ public async postProcess(_result) {
412
+ let result = JSON.parse(JSON.stringify(_result)); //deep clone the result to avoid modifying the original object
410
413
  if (Array.isArray(result)) result = result.flat(Infinity);
411
414
  if (!Array.isArray(result)) result = [result];
412
415
 
@@ -90,6 +90,7 @@ export class AgentRuntime {
90
90
  this.xDebugRead = undefined;
91
91
  }
92
92
 
93
+ this.agent.conversationId = agent.agentRequest.header('X-CONVERSATION-ID');
93
94
  this.xDebugId = this.xDebugStop || this.xDebugRun || this.xDebugRead;
94
95
 
95
96
  //if (req.body) {
@@ -44,4 +44,12 @@ export class EmbodimentSettings {
44
44
  }
45
45
  return _embodiment?.properties;
46
46
  }
47
+
48
+ public getAll() {
49
+ const obj = {};
50
+ this._embodiments.forEach((embodiment: any) => {
51
+ obj[embodiment.type.toLowerCase()] = embodiment.properties;
52
+ });
53
+ return obj;
54
+ }
47
55
  }
@@ -289,7 +289,7 @@ export class MilvusVectorDB extends VectorDBConnector {
289
289
  //const teamId = await this.accountConnector.getCandidateTeam(acRequest.candidate);
290
290
  const preparedNs = this.constructNsName(acRequest.candidate as AccessCandidate, namespace);
291
291
 
292
- const isDeleteByFilter = typeof deleteTarget === 'object';
292
+ const isDeleteByFilter = typeof deleteTarget === 'object' && !Array.isArray(deleteTarget);
293
293
  if (isDeleteByFilter) {
294
294
  const supportedFields: SchemaFieldNames[] = ['datasourceId'];
295
295
  if (!supportedFields.some((field) => field in deleteTarget)) {
@@ -139,6 +139,7 @@ export class PineconeVectorDB extends VectorDBConnector {
139
139
  protected async deleteNamespace(acRequest: AccessRequest, namespace: string): Promise<void> {
140
140
  //const teamId = await this.accountConnector.getCandidateTeam(acRequest.candidate);
141
141
  //const candidate = AccessCandidate.team(teamId);
142
+
142
143
  const preparedNs = this.constructNsName(acRequest.candidate as AccessCandidate, namespace);
143
144
 
144
145
  await this.client
@@ -146,6 +147,7 @@ export class PineconeVectorDB extends VectorDBConnector {
146
147
  .namespace(this.constructNsName(acRequest.candidate as AccessCandidate, namespace))
147
148
  .deleteAll()
148
149
  .catch((e) => {
150
+ console.error(`Error deleting namespace ${namespace}: ${e}`);
149
151
  if (e?.name == 'PineconeNotFoundError') {
150
152
  console.warn(`Namespace ${namespace} does not exist and was requested to be deleted`);
151
153
  return;
@@ -153,6 +155,11 @@ export class PineconeVectorDB extends VectorDBConnector {
153
155
  throw e;
154
156
  });
155
157
 
158
+ // delete the linked datasources from nkv
159
+ await this.nkvConnector
160
+ .requester(acRequest.candidate as AccessCandidate)
161
+ .deleteAll(`vectorDB:${this.id}:namespaces:${preparedNs}:datasources`);
162
+
156
163
  await this.deleteACL(AccessCandidate.clone(acRequest.candidate), namespace);
157
164
  }
158
165
 
@@ -258,7 +265,7 @@ export class PineconeVectorDB extends VectorDBConnector {
258
265
 
259
266
  @SecureConnector.AccessControl
260
267
  protected async delete(acRequest: AccessRequest, namespace: string, deleteTarget: DeleteTarget): Promise<void> {
261
- const isDeleteByFilter = typeof deleteTarget === 'object';
268
+ const isDeleteByFilter = typeof deleteTarget === 'object' && !Array.isArray(deleteTarget);
262
269
 
263
270
  if (isDeleteByFilter) {
264
271
  // TODO: handle delete by filter logic
@@ -447,6 +447,7 @@ export class OTel extends TelemetryConnector {
447
447
  // nested process has a subID that needs to be removed
448
448
  // a process can be nested if it was called by a parent process : e.g conversation => agent , agent => sub-agent, agent => forked process ....etc
449
449
  const agentProcessId = agent.agentRuntime.processID;
450
+ const conversationId = agent.conversationId || agent.agentRequest?.header('X-CONVERSATION-ID');
450
451
  const processId = agentProcessId.split(':').shift();
451
452
 
452
453
  const agentId = agent.id;
@@ -462,7 +463,7 @@ export class OTel extends TelemetryConnector {
462
463
  const input = { body, query, headers, processInput: agentInput };
463
464
 
464
465
  let convSpan;
465
- const ctx = OTelContextRegistry.get(agentId, processId);
466
+ const ctx = OTelContextRegistry.get(agentId, processId) || OTelContextRegistry.get(agentId, conversationId);
466
467
 
467
468
  if (ctx) {
468
469
  convSpan = ctx.rootSpan;
@@ -15,6 +15,8 @@ export type TAgentProcessParams = {
15
15
 
16
16
  //TODO : refactor & document this interface
17
17
  export interface IAgent {
18
+ //if the agent was triggered from a conversation, this will be the conversation id
19
+ conversationId: string;
18
20
  id: any;
19
21
  jobID: any; //forkedAgent
20
22
  async: boolean; //forkedAgent