@memberjunction/ai-agents 2.110.1 → 2.111.1

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.
@@ -45,6 +45,7 @@ class BaseAgent {
45
45
  this._generalValidationRetryCount = 0;
46
46
  this._agentRun = null;
47
47
  this._agentHierarchy = [];
48
+ this._iterationContext = null;
48
49
  this._depth = 0;
49
50
  this._allProgressSteps = [];
50
51
  this._subAgentRuns = [];
@@ -548,9 +549,8 @@ class BaseAgent {
548
549
  return await this._promptRunner.ExecutePrompt(newParams);
549
550
  }
550
551
  async determineNextStep(params, agentType, promptResult, currentPayload) {
551
- this.logStatus(`🤔 Processing next step for agent '${params.agent.Name}' with agent type '${agentType.Name}'`, true, params);
552
552
  this.logStatus(`🎯 Agent type '${agentType.Name}' determining next step`, true, params);
553
- const nextStep = await this.AgentTypeInstance.DetermineNextStep(promptResult, params, currentPayload, this._agentTypeState);
553
+ const nextStep = await this.AgentTypeInstance.DetermineNextStep(promptResult, params, currentPayload, this.AgentTypeState);
554
554
  return nextStep;
555
555
  }
556
556
  async validateNextStep(params, nextStep, currentPayload, agentRun, currentStep) {
@@ -567,6 +567,10 @@ class BaseAgent {
567
567
  return this.validateRetryNextStep(params, nextStep, currentPayload, agentRun, currentStep);
568
568
  case 'Failed':
569
569
  return this.validateFailedNextStep(params, nextStep, currentPayload, agentRun, currentStep);
570
+ case 'ForEach':
571
+ return nextStep;
572
+ case 'While':
573
+ return nextStep;
570
574
  default:
571
575
  this.logError(`Invalid next step '${nextStep.step}' for agent '${params.agent.Name}'`, {
572
576
  agent: params.agent,
@@ -623,7 +627,23 @@ class BaseAgent {
623
627
  }
624
628
  async validateActionsNextStep(params, nextStep, currentPayload, agentRun, currentStep) {
625
629
  const curAgentActions = aiengine_1.AIEngine.Instance.AgentActions.filter(aa => aa.AgentID === params.agent.ID && aa.Status === 'Active');
626
- const missingActions = nextStep.actions?.filter(action => !curAgentActions.some(aa => aa.Action.trim().toLowerCase() === action.name.trim().toLowerCase()));
630
+ const missingActions = nextStep.actions?.filter(action => {
631
+ const actionName = action.name.trim().toLowerCase();
632
+ const exactMatch = curAgentActions.find(aa => aa.Action.trim().toLowerCase() === actionName);
633
+ if (exactMatch)
634
+ return false;
635
+ const containsMatches = curAgentActions.filter(aa => aa.Action.trim().toLowerCase().includes(actionName));
636
+ if (containsMatches.length === 1) {
637
+ const correctedName = containsMatches[0].Action;
638
+ this.logStatus(`Action name fuzzy matched: '${action.name}' → '${correctedName}'`, true, params);
639
+ action.name = correctedName;
640
+ return false;
641
+ }
642
+ if (containsMatches.length > 1) {
643
+ this.logStatus(`Ambiguous action name '${action.name}' matches ${containsMatches.length} actions: ${containsMatches.map(a => a.Action).join(', ')}`, true, params);
644
+ }
645
+ return true;
646
+ });
627
647
  if (missingActions && missingActions.length > 0) {
628
648
  const missingActionNames = missingActions.map(a => a.name).join(', ');
629
649
  this.logError(`Actions '${missingActionNames}' not found or not active for agent '${params.agent.Name}'`, {
@@ -1465,11 +1485,11 @@ Please choose an alternative approach to complete your task.`
1465
1485
  try {
1466
1486
  const validationResult = await this.validateAgent(agent);
1467
1487
  if (validationResult) {
1468
- const stepEntity = await this.createStepEntity('Validation', 'Agent Validation', contextUser);
1488
+ const stepEntity = await this.createStepEntity({ stepType: 'Validation', stepName: 'Agent Validation', contextUser });
1469
1489
  await this.finalizeStepEntity(stepEntity, false, validationResult.agentRun?.ErrorMessage);
1470
1490
  return await this.createFailureResult(validationResult.agentRun?.ErrorMessage || 'Validation failed', contextUser);
1471
1491
  }
1472
- const stepEntity = await this.createStepEntity('Validation', 'Agent Validation', contextUser);
1492
+ const stepEntity = await this.createStepEntity({ stepType: 'Validation', stepName: 'Agent Validation', contextUser });
1473
1493
  await this.finalizeStepEntity(stepEntity, true);
1474
1494
  return null;
1475
1495
  }
@@ -1477,26 +1497,27 @@ Please choose an alternative approach to complete your task.`
1477
1497
  return await this.createFailureResult(`Validation error: ${error.message}`, contextUser);
1478
1498
  }
1479
1499
  }
1480
- async createStepEntity(stepType, stepName, contextUser, targetId, inputData, targetLogId, payloadAtStart, payloadAtEnd) {
1481
- const stepEntity = await this._metadata.GetEntityObject('MJ: AI Agent Run Steps', contextUser);
1500
+ async createStepEntity(params) {
1501
+ const stepEntity = await this._metadata.GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
1482
1502
  stepEntity.AgentRunID = this._agentRun.ID;
1483
1503
  stepEntity.StepNumber = (this._agentRun.Steps?.length || 0) + 1;
1484
- stepEntity.StepType = stepType;
1485
- stepEntity.StepName = this.formatHierarchicalMessage(stepName);
1486
- if (targetId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(targetId)) {
1487
- console.warn(`Invalid target ID format: ${targetId}`);
1504
+ stepEntity.StepType = params.stepType;
1505
+ stepEntity.StepName = this.formatHierarchicalMessage(params.stepName);
1506
+ if (params.targetId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(params.targetId)) {
1507
+ console.warn(`Invalid target ID format: ${params.targetId}`);
1488
1508
  }
1489
1509
  else {
1490
- stepEntity.TargetID = targetId || null;
1510
+ stepEntity.TargetID = params.targetId || null;
1491
1511
  }
1492
- stepEntity.TargetLogID = targetLogId || null;
1512
+ stepEntity.TargetLogID = params.targetLogId || null;
1513
+ stepEntity.ParentID = params.parentId || null;
1493
1514
  stepEntity.Status = 'Running';
1494
1515
  stepEntity.StartedAt = new Date();
1495
- stepEntity.PayloadAtStart = payloadAtStart ? JSON.stringify(payloadAtStart) : null;
1496
- stepEntity.PayloadAtEnd = payloadAtEnd ? JSON.stringify(payloadAtEnd) : null;
1497
- if (inputData) {
1516
+ stepEntity.PayloadAtStart = params.payloadAtStart ? JSON.stringify(params.payloadAtStart) : null;
1517
+ stepEntity.PayloadAtEnd = params.payloadAtEnd ? JSON.stringify(params.payloadAtEnd) : null;
1518
+ if (params.inputData) {
1498
1519
  stepEntity.InputData = JSON.stringify({
1499
- ...inputData,
1520
+ ...params.inputData,
1500
1521
  context: {
1501
1522
  agentHierarchy: this._agentHierarchy,
1502
1523
  depth: this._depth,
@@ -1562,6 +1583,66 @@ Please choose an alternative approach to complete your task.`
1562
1583
  console.error('Failed to update agent run step record');
1563
1584
  }
1564
1585
  }
1586
+ getValueFromPath(obj, path) {
1587
+ const parts = path.split('.');
1588
+ let current = obj;
1589
+ for (const part of parts) {
1590
+ if (!part)
1591
+ continue;
1592
+ const arrayMatch = part.match(/^([^[]+)\[(\d+)\]$/);
1593
+ if (arrayMatch) {
1594
+ const arrayName = arrayMatch[1];
1595
+ const index = parseInt(arrayMatch[2], 10);
1596
+ if (current && typeof current === 'object' && arrayName in current) {
1597
+ current = current[arrayName];
1598
+ if (Array.isArray(current) && index >= 0 && index < current.length) {
1599
+ current = current[index];
1600
+ }
1601
+ else {
1602
+ return undefined;
1603
+ }
1604
+ }
1605
+ else {
1606
+ return undefined;
1607
+ }
1608
+ }
1609
+ else {
1610
+ if (current && typeof current === 'object' && part in current) {
1611
+ current = current[part];
1612
+ }
1613
+ else {
1614
+ return undefined;
1615
+ }
1616
+ }
1617
+ }
1618
+ return current;
1619
+ }
1620
+ resolveLoopParams(params, item, index, payload) {
1621
+ const resolved = {};
1622
+ for (const [key, value] of Object.entries(params)) {
1623
+ if (typeof value === 'string') {
1624
+ if (value.startsWith('item.')) {
1625
+ resolved[key] = this.getValueFromPath(item, value.substring(5));
1626
+ }
1627
+ else if (value.startsWith('payload.')) {
1628
+ resolved[key] = this.getValueFromPath(payload, value.substring(8));
1629
+ }
1630
+ else if (value === 'item') {
1631
+ resolved[key] = item;
1632
+ }
1633
+ else if (value === 'index') {
1634
+ resolved[key] = index;
1635
+ }
1636
+ else {
1637
+ resolved[key] = value;
1638
+ }
1639
+ }
1640
+ else {
1641
+ resolved[key] = value;
1642
+ }
1643
+ }
1644
+ return resolved;
1645
+ }
1565
1646
  formatHierarchicalMessage(baseMessage) {
1566
1647
  if (this._depth > 0) {
1567
1648
  const breadcrumb = this._agentHierarchy
@@ -1603,11 +1684,10 @@ Please choose an alternative approach to complete your task.`
1603
1684
  case 'Sub-Agent':
1604
1685
  return await this.processSubAgentStep(params, previousDecision);
1605
1686
  case 'Actions':
1606
- return await this.executeActionsStep(params, config, previousDecision);
1687
+ return await this.executeActionsStep(params, previousDecision, undefined);
1607
1688
  case 'Chat':
1608
1689
  return await this.executeChatStep(params, previousDecision);
1609
1690
  case 'Success':
1610
- const pd = previousDecision;
1611
1691
  if (previousDecision.terminate) {
1612
1692
  return {
1613
1693
  terminate: true,
@@ -1631,6 +1711,10 @@ Please choose an alternative approach to complete your task.`
1631
1711
  else {
1632
1712
  return await this.executePromptStep(params, config, previousDecision);
1633
1713
  }
1714
+ case 'ForEach':
1715
+ return await this.executeForEachLoop(params, config, previousDecision);
1716
+ case 'While':
1717
+ return await this.executeWhileLoop(params, config, previousDecision);
1634
1718
  default:
1635
1719
  throw new Error(`Unsupported next step: ${previousDecision.step}`);
1636
1720
  }
@@ -1650,7 +1734,7 @@ Please choose an alternative approach to complete your task.`
1650
1734
  conversationMessages: params.conversationMessages,
1651
1735
  };
1652
1736
  const payload = previousDecision?.newPayload || params.payload;
1653
- const stepEntity = await this.createStepEntity('Prompt', 'Execute Agent Prompt', params.contextUser, promptId, inputData, undefined, payload);
1737
+ const stepEntity = await this.createStepEntity({ stepType: 'Prompt', stepName: 'Execute Agent Prompt', contextUser: params.contextUser, targetId: promptId, inputData, payloadAtStart: payload });
1654
1738
  try {
1655
1739
  const isRetry = !!previousDecision;
1656
1740
  const promptMessage = isRetry
@@ -1697,6 +1781,10 @@ Please choose an alternative approach to complete your task.`
1697
1781
  await stepEntity.Save();
1698
1782
  };
1699
1783
  const promptResult = await this.executePrompt(promptParams);
1784
+ const loopMsgIndex = params.conversationMessages.findIndex(m => m.metadata?._loopResults === true);
1785
+ if (loopMsgIndex !== -1) {
1786
+ params.conversationMessages.splice(loopMsgIndex, 1);
1787
+ }
1700
1788
  if (promptResult.promptRun?.ID) {
1701
1789
  stepEntity.TargetLogID = promptResult.promptRun.ID;
1702
1790
  stepEntity.PromptRun = promptResult.promptRun;
@@ -1841,7 +1929,7 @@ Please choose an alternative approach to complete your task.`
1841
1929
  if (!subAgentEntity) {
1842
1930
  throw new Error(`Sub-agent '${subAgentRequest.name}' not found`);
1843
1931
  }
1844
- const stepEntity = await this.createStepEntity('Sub-Agent', `Execute Sub-Agent: ${subAgentRequest.name}`, params.contextUser, subAgentEntity.ID, inputData, undefined, previousDecision.newPayload);
1932
+ const stepEntity = await this.createStepEntity({ stepType: 'Sub-Agent', stepName: `Execute Sub-Agent: ${subAgentRequest.name}`, contextUser: params.contextUser, targetId: subAgentEntity.ID, inputData, payloadAtStart: previousDecision.newPayload });
1845
1933
  this.incrementExecutionCount(subAgentEntity.ID);
1846
1934
  try {
1847
1935
  let downstreamPaths = ['*'];
@@ -2093,7 +2181,14 @@ Please choose an alternative approach to complete your task.`
2093
2181
  parentAgentHierarchy: this._agentHierarchy,
2094
2182
  relationshipType: 'related'
2095
2183
  };
2096
- const stepEntity = await this.createStepEntity('Sub-Agent', `Execute Related Sub-Agent: ${subAgentRequest.name}`, params.contextUser, subAgentEntity.ID, inputData, undefined, previousDecision.newPayload);
2184
+ const stepEntity = await this.createStepEntity({
2185
+ stepType: 'Sub-Agent',
2186
+ stepName: `Execute Related Sub-Agent: ${subAgentRequest.name}`,
2187
+ contextUser: params.contextUser,
2188
+ targetId: subAgentEntity.ID,
2189
+ inputData,
2190
+ payloadAtStart: previousDecision.newPayload
2191
+ });
2097
2192
  this.incrementExecutionCount(subAgentEntity.ID);
2098
2193
  try {
2099
2194
  stepEntity.PayloadAtStart = JSON.stringify(previousDecision.newPayload);
@@ -2307,41 +2402,7 @@ Please choose an alternative approach to complete your task.`
2307
2402
  content: `Parent Agent Context:\n\n${contextLines.join('\n\n')}`
2308
2403
  };
2309
2404
  }
2310
- getValueFromPath(obj, path) {
2311
- const parts = path.split('.');
2312
- let current = obj;
2313
- for (const part of parts) {
2314
- if (!part)
2315
- continue;
2316
- const arrayMatch = part.match(/^([^[]+)\[(\d+)\]$/);
2317
- if (arrayMatch) {
2318
- const arrayName = arrayMatch[1];
2319
- const index = parseInt(arrayMatch[2], 10);
2320
- if (current && typeof current === 'object' && arrayName in current) {
2321
- current = current[arrayName];
2322
- if (Array.isArray(current) && index >= 0 && index < current.length) {
2323
- current = current[index];
2324
- }
2325
- else {
2326
- return undefined;
2327
- }
2328
- }
2329
- else {
2330
- return undefined;
2331
- }
2332
- }
2333
- else {
2334
- if (current && typeof current === 'object' && part in current) {
2335
- current = current[part];
2336
- }
2337
- else {
2338
- return undefined;
2339
- }
2340
- }
2341
- }
2342
- return current;
2343
- }
2344
- async executeActionsStep(params, config, previousDecision) {
2405
+ async executeActionsStep(params, previousDecision, parentStepId, addConversationMessage = true) {
2345
2406
  try {
2346
2407
  const currentPayload = previousDecision?.newPayload || previousDecision?.previousPayload || params.payload;
2347
2408
  const actions = previousDecision.actions || [];
@@ -2422,10 +2483,12 @@ Please choose an alternative approach to complete your task.`
2422
2483
  return actionText;
2423
2484
  }).join('\n\n');
2424
2485
  }
2425
- params.conversationMessages.push({
2426
- role: 'assistant',
2427
- content: actionMessage
2428
- });
2486
+ if (addConversationMessage) {
2487
+ params.conversationMessages.push({
2488
+ role: 'assistant',
2489
+ content: actionMessage
2490
+ });
2491
+ }
2429
2492
  const actionEngine = actions_1.ActionEngineServer.Instance;
2430
2493
  const agentActions = aiengine_1.AIEngine.Instance.AgentActions.filter(aa => aa.AgentID === params.agent.ID);
2431
2494
  try {
@@ -2447,7 +2510,7 @@ Please choose an alternative approach to complete your task.`
2447
2510
  actionName: aa.name,
2448
2511
  actionParams: aa.params
2449
2512
  };
2450
- const stepEntity = await this.createStepEntity('Actions', `Execute Action: ${aa.name}`, params.contextUser, actionEntity.ID, actionInputData, undefined, currentPayload, currentPayload);
2513
+ const stepEntity = await this.createStepEntity({ stepType: 'Actions', stepName: `Execute Action: ${aa.name}`, contextUser: params.contextUser, targetId: actionEntity.ID, inputData: actionInputData, payloadAtStart: currentPayload, payloadAtEnd: currentPayload, parentId: parentStepId });
2451
2514
  lastStep = stepEntity;
2452
2515
  stepEntity.StepNumber = baseStepNumber + numActionsProcessed++;
2453
2516
  this.incrementExecutionCount(actionEntity.ID);
@@ -2524,11 +2587,13 @@ Please choose an alternative approach to complete your task.`
2524
2587
  };
2525
2588
  }
2526
2589
  }
2527
- params.conversationMessages.push({
2528
- role: 'user',
2529
- content: resultsMessage,
2530
- metadata: metadata
2531
- });
2590
+ if (addConversationMessage) {
2591
+ params.conversationMessages.push({
2592
+ role: 'user',
2593
+ content: resultsMessage,
2594
+ metadata: metadata
2595
+ });
2596
+ }
2532
2597
  let finalPayload = currentPayload;
2533
2598
  try {
2534
2599
  const actionResultsOnly = actionResults.map(r => r.result).filter(r => r !== undefined);
@@ -2582,7 +2647,7 @@ Please choose an alternative approach to complete your task.`
2582
2647
  }
2583
2648
  }
2584
2649
  async executeChatStep(params, previousDecision) {
2585
- const stepEntity = await this.createStepEntity('Chat', 'User Interaction', params.contextUser);
2650
+ const stepEntity = await this.createStepEntity({ stepType: 'Chat', stepName: 'User Interaction', contextUser: params.contextUser });
2586
2651
  await this.finalizeStepEntity(stepEntity, true);
2587
2652
  return {
2588
2653
  step: 'Chat',
@@ -2596,6 +2661,377 @@ Please choose an alternative approach to complete your task.`
2596
2661
  suggestedResponses: previousDecision.suggestedResponses
2597
2662
  };
2598
2663
  }
2664
+ async executeForEachLoop(params, config, previousDecision) {
2665
+ const forEach = previousDecision.forEach;
2666
+ if (!forEach) {
2667
+ return this.createFailedStep('ForEach configuration missing', previousDecision);
2668
+ }
2669
+ const validationMessage = this.validateForEachOperation(forEach);
2670
+ if (validationMessage) {
2671
+ return this.createFailedStep(`ForEach configuration invalid: ${validationMessage}`, previousDecision);
2672
+ }
2673
+ const currentPayload = previousDecision.newPayload || previousDecision.previousPayload;
2674
+ const collection = this.getCollectionFromPayload(currentPayload, forEach.collectionPath);
2675
+ if (!collection) {
2676
+ return this.createFailedStep(`Collection path "${forEach.collectionPath}" not an array`, previousDecision);
2677
+ }
2678
+ const loopStepEntity = await this.createForEachLoopStep(forEach, collection, currentPayload, params);
2679
+ const loopResults = await this.executeForEachIterations(forEach, collection, currentPayload, loopStepEntity.ID, params, config);
2680
+ return this.completeForEachLoop(forEach, loopStepEntity, loopResults, previousDecision, params);
2681
+ }
2682
+ validateWhileOperation(whileOp) {
2683
+ if (!whileOp.condition || whileOp.condition.trim() === '') {
2684
+ return 'Condition is required';
2685
+ }
2686
+ if (!whileOp.itemVariable || whileOp.itemVariable.trim() === '') {
2687
+ return 'Item variable is required';
2688
+ }
2689
+ if (whileOp.action) {
2690
+ return this.validateActionInAgent(whileOp.action.name);
2691
+ }
2692
+ else if (whileOp.subAgent) {
2693
+ return this.validateSubAgentInAgent(whileOp.subAgent.name);
2694
+ }
2695
+ return null;
2696
+ }
2697
+ validateForEachOperation(forEach) {
2698
+ if (!forEach.itemVariable || forEach.itemVariable.trim() === '') {
2699
+ return 'Item variable is required';
2700
+ }
2701
+ if (!forEach.collectionPath || forEach.collectionPath.trim() === '') {
2702
+ return 'Collection path is required';
2703
+ }
2704
+ if (forEach.action) {
2705
+ return this.validateActionInAgent(forEach.action.name);
2706
+ }
2707
+ else if (forEach.subAgent) {
2708
+ return this.validateSubAgentInAgent(forEach.subAgent.name);
2709
+ }
2710
+ return null;
2711
+ }
2712
+ validateActionInAgent(actionName) {
2713
+ const aa = aiengine_1.AIEngine.Instance.AgentActions.filter(aa => aa.AgentID === this._agentRun.AgentID);
2714
+ let action = aa.filter(a => a.Agent?.trim().toLowerCase() === actionName.trim().toLowerCase());
2715
+ if (!action) {
2716
+ const partialAction = aa.filter(a => a.Agent?.trim().toLowerCase().includes(actionName.trim().toLowerCase() || ''));
2717
+ if (partialAction.length > 1) {
2718
+ return `Ambiguous action '${actionName}' specified`;
2719
+ }
2720
+ else if (partialAction.length === 0) {
2721
+ return `No action '${actionName}' found`;
2722
+ }
2723
+ }
2724
+ return null;
2725
+ }
2726
+ validateSubAgentInAgent(subAgentName) {
2727
+ const relatedAgents = aiengine_1.AIEngine.Instance.AgentRelationships.filter(ar => ar.AgentID === this._agentRun.AgentID);
2728
+ const childAgents = aiengine_1.AIEngine.Instance.Agents.filter(a => a.ParentID === this._agentRun.AgentID);
2729
+ let subAgent = relatedAgents.filter(ra => ra.SubAgent?.trim().toLowerCase() === subAgentName.trim().toLowerCase());
2730
+ if (subAgent.length === 0) {
2731
+ subAgent = relatedAgents.filter(ra => ra.SubAgent?.trim().toLowerCase().includes(subAgentName.trim().toLowerCase() || ''));
2732
+ if (subAgent.length > 1) {
2733
+ return `Ambiguous sub-agent '${subAgentName}' specified`;
2734
+ }
2735
+ else if (subAgent.length === 0) {
2736
+ let childAgent = childAgents.filter(ca => ca.Name?.trim().toLowerCase() === subAgentName.trim().toLowerCase());
2737
+ if (childAgent.length === 0) {
2738
+ childAgent = childAgents.filter(ca => ca.Name?.trim().toLowerCase().includes(subAgentName.trim().toLowerCase() || ''));
2739
+ if (childAgent.length > 1) {
2740
+ return `Ambiguous child agent '${subAgentName}' specified`;
2741
+ }
2742
+ else if (childAgent.length === 0) {
2743
+ return `No child agent '${subAgentName}' found`;
2744
+ }
2745
+ }
2746
+ else {
2747
+ }
2748
+ }
2749
+ }
2750
+ return null;
2751
+ }
2752
+ getCollectionFromPayload(payload, path) {
2753
+ const cleanPath = path.toLowerCase().startsWith('payload.')
2754
+ ? path.substring(8)
2755
+ : path;
2756
+ const value = this.getValueFromPath(payload, cleanPath);
2757
+ return Array.isArray(value) ? value : null;
2758
+ }
2759
+ async createForEachLoopStep(forEach, collection, payload, params) {
2760
+ const stepEntity = await this.createStepEntity({
2761
+ stepType: 'ForEach',
2762
+ stepName: `ForEach: ${forEach.collectionPath} (${collection.length} items)`,
2763
+ contextUser: params.contextUser,
2764
+ inputData: { forEach, count: collection.length },
2765
+ payloadAtStart: payload
2766
+ });
2767
+ return stepEntity;
2768
+ }
2769
+ async executeForEachIterations(forEach, collection, initialPayload, parentStepId, params, config) {
2770
+ const executionMode = forEach.executionMode || 'sequential';
2771
+ if (executionMode === 'parallel') {
2772
+ return this.executeForEachIterationsParallel(forEach, collection, initialPayload, parentStepId, params, config);
2773
+ }
2774
+ else {
2775
+ return this.executeForEachIterationsSequential(forEach, collection, initialPayload, parentStepId, params, config);
2776
+ }
2777
+ }
2778
+ async executeForEachIterationsSequential(forEach, collection, initialPayload, parentStepId, params, config) {
2779
+ let currentPayload = initialPayload;
2780
+ const maxIterations = forEach.maxIterations ?? 1000;
2781
+ const results = [];
2782
+ const errors = [];
2783
+ for (let i = 0; i < Math.min(collection.length, maxIterations); i++) {
2784
+ if (i > 0 && forEach.delayBetweenIterationsMs) {
2785
+ await new Promise(resolve => setTimeout(resolve, forEach.delayBetweenIterationsMs));
2786
+ }
2787
+ const iterResult = await this.executeSingleForEachIteration(forEach, collection[i], i, currentPayload, parentStepId, params, config);
2788
+ if (iterResult.error) {
2789
+ errors.push(iterResult.error);
2790
+ if (!forEach.continueOnError)
2791
+ break;
2792
+ }
2793
+ else {
2794
+ results.push(iterResult.result);
2795
+ currentPayload = iterResult.payload;
2796
+ }
2797
+ }
2798
+ return { results, errors, finalPayload: currentPayload };
2799
+ }
2800
+ async executeForEachIterationsParallel(forEach, collection, initialPayload, parentStepId, params, config) {
2801
+ const maxIterations = forEach.maxIterations ?? 1000;
2802
+ const maxConcurrency = forEach.maxConcurrency ?? 10;
2803
+ const itemsToProcess = Math.min(collection.length, maxIterations);
2804
+ const batches = this.createBatches(collection.slice(0, itemsToProcess), maxConcurrency);
2805
+ const allResults = [];
2806
+ for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
2807
+ const batch = batches[batchIndex];
2808
+ const batchPromises = batch.map(({ item, index }) => this.executeSingleForEachIteration(forEach, item, index, initialPayload, parentStepId, params, config).then(iterResult => ({
2809
+ index,
2810
+ result: iterResult.result,
2811
+ error: iterResult.error,
2812
+ payload: iterResult.payload
2813
+ })));
2814
+ const batchResults = await Promise.all(batchPromises);
2815
+ allResults.push(...batchResults);
2816
+ if (forEach.delayBetweenIterationsMs && batchIndex < batches.length - 1) {
2817
+ await new Promise(resolve => setTimeout(resolve, forEach.delayBetweenIterationsMs));
2818
+ }
2819
+ const hasFailure = batchResults.some(r => r.error);
2820
+ if (hasFailure && !forEach.continueOnError) {
2821
+ break;
2822
+ }
2823
+ }
2824
+ allResults.sort((a, b) => a.index - b.index);
2825
+ return this.applyForEachResultsSequentially(allResults, initialPayload, forEach.continueOnError || false);
2826
+ }
2827
+ createBatches(items, maxConcurrency) {
2828
+ const batches = [];
2829
+ for (let i = 0; i < items.length; i += maxConcurrency) {
2830
+ const batch = items.slice(i, i + maxConcurrency).map((item, batchOffset) => ({
2831
+ item,
2832
+ index: i + batchOffset
2833
+ }));
2834
+ batches.push(batch);
2835
+ }
2836
+ return batches;
2837
+ }
2838
+ applyForEachResultsSequentially(sortedResults, initialPayload, continueOnError) {
2839
+ let currentPayload = initialPayload;
2840
+ const results = [];
2841
+ const errors = [];
2842
+ for (const { result, error, payload } of sortedResults) {
2843
+ if (error) {
2844
+ errors.push(error);
2845
+ if (!continueOnError) {
2846
+ break;
2847
+ }
2848
+ }
2849
+ else if (result) {
2850
+ results.push(result);
2851
+ currentPayload = payload || currentPayload;
2852
+ }
2853
+ }
2854
+ return { results, errors, finalPayload: currentPayload };
2855
+ }
2856
+ async executeSingleForEachIteration(forEach, item, index, currentPayload, parentStepId, params, config) {
2857
+ try {
2858
+ const beforeHook = this.AgentTypeInstance.BeforeLoopIteration?.({ item, itemVariable: forEach.itemVariable, index, payload: currentPayload, loopType: 'ForEach', actionParams: forEach.action?.params || {} });
2859
+ const resolvedParams = beforeHook?.actionParams || forEach.action?.params || {};
2860
+ let result;
2861
+ if (forEach.action) {
2862
+ const matchedAction = this.findAgentActionForLoop(forEach.action.name, params.agent.ID, params.agent.Name, params);
2863
+ const resolvedAction = {
2864
+ name: matchedAction.Action,
2865
+ params: resolvedParams
2866
+ };
2867
+ const actionStep = { step: 'Actions', actions: [resolvedAction], newPayload: currentPayload, previousPayload: currentPayload, terminate: false };
2868
+ result = await this.executeActionsStep(params, actionStep, parentStepId, false);
2869
+ }
2870
+ else if (forEach.subAgent) {
2871
+ const subAgentStep = { step: 'Sub-Agent', subAgent: forEach.subAgent, newPayload: currentPayload, previousPayload: currentPayload };
2872
+ result = await this.processSubAgentStep(params, subAgentStep);
2873
+ }
2874
+ else {
2875
+ throw new Error('ForEach missing action/subAgent');
2876
+ }
2877
+ const iterPayload = result.newPayload || currentPayload;
2878
+ const afterHook = this.AgentTypeInstance.AfterLoopIteration?.({ currentPayload: iterPayload, item, itemVariable: forEach.itemVariable, index, loopContext: { actionOutputMapping: forEach.action?.outputMapping } });
2879
+ return { payload: afterHook || iterPayload, result };
2880
+ }
2881
+ catch (error) {
2882
+ return { error: { index, item, message: error.message } };
2883
+ }
2884
+ }
2885
+ async completeForEachLoop(forEach, loopStepEntity, loopResults, previousDecision, params) {
2886
+ loopStepEntity.PayloadAtEnd = JSON.stringify(loopResults.finalPayload);
2887
+ await this.finalizeStepEntity(loopStepEntity, loopResults.errors.length === 0, loopResults.errors.join('\n\n'), loopResults);
2888
+ if (this.AgentTypeInstance.InjectLoopResultsAsMessage) {
2889
+ this.injectLoopResultsMessage('ForEach', forEach.collectionPath, loopResults.results, loopResults.errors, params);
2890
+ }
2891
+ return {
2892
+ step: 'Retry',
2893
+ retryInstructions: `Completed ForEach loop request using collection at '${forEach.collectionPath}'`,
2894
+ terminate: false,
2895
+ newPayload: loopResults.finalPayload,
2896
+ previousPayload: previousDecision.previousPayload
2897
+ };
2898
+ }
2899
+ injectLoopResultsMessage(loopType, collectionOrCondition, results, errors, params) {
2900
+ const extractedResults = results.map(r => r.priorStepResult);
2901
+ const label = loopType === 'ForEach' ? 'Collection' : 'Condition';
2902
+ params.conversationMessages.push({
2903
+ role: 'user',
2904
+ content: `## Loop Completed\n**Type:** ${loopType}\n**${label}:** ${collectionOrCondition}\n` +
2905
+ `**Processed:** ${results.length}, **Errors:** ${errors.length}\n\n` +
2906
+ `**Results:**\n\`\`\`json\n${JSON.stringify(extractedResults, null, 2)}\n\`\`\``,
2907
+ metadata: { _temporary: true, _loopResults: true }
2908
+ });
2909
+ }
2910
+ findAgentActionForLoop(actionName, agentId, agentName, params) {
2911
+ const agentActions = aiengine_1.AIEngine.Instance.AgentActions.filter(aa => aa.AgentID === agentId && aa.Status === 'Active');
2912
+ let matched = agentActions.find(aa => aa.Action.trim().toLowerCase() === actionName.trim().toLowerCase());
2913
+ if (!matched) {
2914
+ const containsMatches = agentActions.filter(aa => aa.Action.trim().toLowerCase().includes(actionName.trim().toLowerCase()));
2915
+ if (containsMatches.length === 1) {
2916
+ matched = containsMatches[0];
2917
+ this.logStatus(`Action fuzzy matched: '${actionName}' → '${matched.Action}'`, true, params);
2918
+ }
2919
+ else if (containsMatches.length > 1) {
2920
+ throw new Error(`Ambiguous action '${actionName}'. Matches: ${containsMatches.map(a => a.Action).join(', ')}`);
2921
+ }
2922
+ else {
2923
+ throw new Error(`Action '${actionName}' not found for agent '${agentName}'`);
2924
+ }
2925
+ }
2926
+ return matched;
2927
+ }
2928
+ createFailedStep(errorMessage, previousDecision) {
2929
+ return {
2930
+ step: 'Failed',
2931
+ terminate: false,
2932
+ errorMessage,
2933
+ previousPayload: previousDecision.previousPayload,
2934
+ newPayload: previousDecision.newPayload || previousDecision.previousPayload
2935
+ };
2936
+ }
2937
+ async executeWhileLoop(params, config, previousDecision) {
2938
+ const whileOp = previousDecision.while;
2939
+ if (!whileOp) {
2940
+ return this.createFailedStep('While configuration missing', previousDecision);
2941
+ }
2942
+ const validationMessage = this.validateWhileOperation(whileOp);
2943
+ if (validationMessage) {
2944
+ return this.createFailedStep(`While configuration invalid: ${validationMessage}`, previousDecision);
2945
+ }
2946
+ const currentPayload = previousDecision.newPayload || previousDecision.previousPayload;
2947
+ const loopStepEntity = await this.createWhileLoopStep(whileOp, currentPayload, params);
2948
+ const loopResults = await this.executeWhileIterations(whileOp, currentPayload, loopStepEntity.ID, params, config);
2949
+ return this.completeWhileLoop(whileOp, loopStepEntity, loopResults, previousDecision, params);
2950
+ }
2951
+ async createWhileLoopStep(whileOp, payload, params) {
2952
+ const stepEntity = await this.createStepEntity({
2953
+ stepType: 'While',
2954
+ stepName: `While: ${whileOp.condition}`,
2955
+ contextUser: params.contextUser,
2956
+ inputData: { while: whileOp },
2957
+ payloadAtStart: payload
2958
+ });
2959
+ return stepEntity;
2960
+ }
2961
+ async executeWhileIterations(whileOp, initialPayload, parentStepId, params, config) {
2962
+ let currentPayload = initialPayload;
2963
+ const maxIterations = whileOp.maxIterations ?? 100;
2964
+ const results = [];
2965
+ const errors = [];
2966
+ let iterationCount = 0;
2967
+ const { SafeExpressionEvaluator } = require('@memberjunction/global');
2968
+ const evaluator = new SafeExpressionEvaluator();
2969
+ while (iterationCount < maxIterations) {
2970
+ if (iterationCount > 0 && whileOp.delayBetweenIterationsMs) {
2971
+ await new Promise(resolve => setTimeout(resolve, whileOp.delayBetweenIterationsMs));
2972
+ }
2973
+ const evalResult = evaluator.evaluate(whileOp.condition, { payload: currentPayload, results, errors });
2974
+ if (!evalResult.success || !evalResult.value) {
2975
+ break;
2976
+ }
2977
+ const attemptContext = { attemptNumber: iterationCount + 1, totalAttempts: iterationCount };
2978
+ const iterResult = await this.executeSingleWhileIteration(whileOp, attemptContext, iterationCount, currentPayload, parentStepId, params, config);
2979
+ if (iterResult.error) {
2980
+ errors.push(iterResult.error);
2981
+ if (!whileOp.continueOnError)
2982
+ break;
2983
+ }
2984
+ else {
2985
+ results.push(iterResult.result);
2986
+ currentPayload = iterResult.payload;
2987
+ }
2988
+ iterationCount++;
2989
+ }
2990
+ return { results, errors, finalPayload: currentPayload, iterations: iterationCount };
2991
+ }
2992
+ async executeSingleWhileIteration(whileOp, attemptContext, index, currentPayload, parentStepId, params, config) {
2993
+ try {
2994
+ const beforeHook = this.AgentTypeInstance.BeforeLoopIteration?.({ item: attemptContext, index, itemVariable: whileOp.itemVariable, payload: currentPayload, loopType: 'While', actionParams: whileOp.action?.params || {} });
2995
+ const resolvedParams = beforeHook?.actionParams || whileOp.action?.params || {};
2996
+ let result;
2997
+ if (whileOp.action) {
2998
+ const matchedAction = this.findAgentActionForLoop(whileOp.action.name, params.agent.ID, params.agent.Name, params);
2999
+ const resolvedAction = {
3000
+ name: matchedAction.Action,
3001
+ params: resolvedParams
3002
+ };
3003
+ const actionStep = { step: 'Actions', actions: [resolvedAction], newPayload: currentPayload, previousPayload: currentPayload, terminate: false };
3004
+ result = await this.executeActionsStep(params, actionStep, parentStepId, false);
3005
+ }
3006
+ else if (whileOp.subAgent) {
3007
+ const subAgentStep = { step: 'Sub-Agent', subAgent: whileOp.subAgent, newPayload: currentPayload, previousPayload: currentPayload };
3008
+ result = await this.processSubAgentStep(params, subAgentStep);
3009
+ }
3010
+ else {
3011
+ throw new Error('While missing action/subAgent');
3012
+ }
3013
+ const iterPayload = result.newPayload || currentPayload;
3014
+ const afterHook = this.AgentTypeInstance.AfterLoopIteration?.({ currentPayload: iterPayload, item: attemptContext, itemVariable: whileOp.itemVariable, index, loopContext: { actionOutputMapping: whileOp.action?.outputMapping } });
3015
+ return { payload: afterHook || iterPayload, result };
3016
+ }
3017
+ catch (error) {
3018
+ return { error: { index, item: attemptContext, message: error.message } };
3019
+ }
3020
+ }
3021
+ async completeWhileLoop(whileOp, loopStepEntity, loopResults, previousDecision, params) {
3022
+ loopStepEntity.PayloadAtEnd = JSON.stringify(loopResults.finalPayload);
3023
+ await this.finalizeStepEntity(loopStepEntity, loopResults.errors.length === 0, loopResults.errors.join('\n\n'), loopResults);
3024
+ if (this.AgentTypeInstance.InjectLoopResultsAsMessage) {
3025
+ this.injectLoopResultsMessage('While', whileOp.condition, loopResults.results, loopResults.errors, params);
3026
+ }
3027
+ return {
3028
+ step: 'Retry',
3029
+ retryInstructions: `Completed While loop request using condition '${whileOp.condition}' after ${loopResults.iterations} iteration(s)`,
3030
+ terminate: false,
3031
+ newPayload: loopResults.finalPayload,
3032
+ previousPayload: previousDecision.previousPayload
3033
+ };
3034
+ }
2599
3035
  async createFailureResult(errorMessage, contextUser) {
2600
3036
  if (this._agentRun) {
2601
3037
  this._agentRun.Status = 'Failed';