@memberjunction/ai-agents 2.111.1 → 2.113.0

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.
@@ -31,6 +31,7 @@ const global_1 = require("@memberjunction/global");
31
31
  const aiengine_1 = require("@memberjunction/aiengine");
32
32
  const actions_1 = require("@memberjunction/actions");
33
33
  const ai_engine_base_1 = require("@memberjunction/ai-engine-base");
34
+ const agent_context_injector_1 = require("./agent-context-injector");
34
35
  const ai_core_plus_1 = require("@memberjunction/ai-core-plus");
35
36
  const AgentRunner_1 = require("./AgentRunner");
36
37
  const PayloadManager_1 = require("./PayloadManager");
@@ -51,7 +52,10 @@ class BaseAgent {
51
52
  this._subAgentRuns = [];
52
53
  this._payloadManager = new PayloadManager_1.PayloadManager();
53
54
  this._validationRetryCount = 0;
54
- this._contextRecoveryAttempted = false;
55
+ this._contextRecoveryAttempts = 0;
56
+ this.MAX_RECOVERY_ATTEMPTS = 1;
57
+ this._memoryContext = '';
58
+ this._injectedMemory = { notes: [], examples: [] };
55
59
  }
56
60
  get AgentTypeState() {
57
61
  return this._agentTypeState;
@@ -208,7 +212,7 @@ class BaseAgent {
208
212
  await this.initializeAgentRun(wrappedParams);
209
213
  this._validationRetryCount = 0;
210
214
  this._generalValidationRetryCount = 0;
211
- this._contextRecoveryAttempted = false;
215
+ this._contextRecoveryAttempts = 0;
212
216
  this._messageLifecycleCallback = params.onMessageLifecycle;
213
217
  await this.initializeEngines(params.contextUser);
214
218
  if (params.cancellationToken?.aborted) {
@@ -237,6 +241,13 @@ class BaseAgent {
237
241
  }
238
242
  await this.preloadAgentData(wrappedParams);
239
243
  await this.initializeAgentType(wrappedParams, config);
244
+ const userId = params.userId || params.contextUser?.ID;
245
+ const companyId = params.companyId;
246
+ const lastUserMessage = params.conversationMessages
247
+ .filter(m => m.role === 'user')
248
+ .pop();
249
+ const inputText = lastUserMessage?.content || '';
250
+ await this.InjectContextMemory(typeof inputText === 'string' ? inputText : '', params.agent, userId, companyId, params.contextUser, wrappedParams.conversationMessages);
240
251
  this.logStatus(`🚀 Executing agent '${params.agent.Name}' internal logic`, true, params);
241
252
  const executionResult = await this.executeAgentInternal(wrappedParams, config);
242
253
  wrappedParams.onProgress?.({
@@ -298,6 +309,50 @@ class BaseAgent {
298
309
  await aiengine_1.AIEngine.Instance.Config(false, contextUser);
299
310
  await actions_1.ActionEngineServer.Instance.Config(false, contextUser);
300
311
  }
312
+ async InjectContextMemory(input, agent, userId, companyId, contextUser, conversationMessages) {
313
+ if (!agent.InjectNotes && !agent.InjectExamples) {
314
+ return { notes: [], examples: [] };
315
+ }
316
+ const injector = new agent_context_injector_1.AgentContextInjector();
317
+ const notes = agent.InjectNotes
318
+ ? await injector.GetNotesForContext({
319
+ agentId: agent.ID,
320
+ userId,
321
+ companyId,
322
+ currentInput: input,
323
+ strategy: agent.NoteInjectionStrategy,
324
+ maxNotes: agent.MaxNotesToInject || 5,
325
+ contextUser: contextUser
326
+ })
327
+ : [];
328
+ const examples = agent.InjectExamples
329
+ ? await injector.GetExamplesForContext({
330
+ agentId: agent.ID,
331
+ userId,
332
+ companyId,
333
+ currentInput: input,
334
+ strategy: agent.ExampleInjectionStrategy,
335
+ maxExamples: agent.MaxExamplesToInject || 3,
336
+ contextUser: contextUser
337
+ })
338
+ : [];
339
+ if ((notes.length > 0 || examples.length > 0) && conversationMessages) {
340
+ const notesText = injector.FormatNotesForInjection(notes);
341
+ const examplesText = injector.FormatExamplesForInjection(examples);
342
+ this._memoryContext = '';
343
+ if (notesText)
344
+ this._memoryContext += notesText + '\n\n';
345
+ if (examplesText)
346
+ this._memoryContext += examplesText + '\n\n';
347
+ conversationMessages.unshift({
348
+ role: 'system',
349
+ content: this._memoryContext
350
+ });
351
+ this.logStatus(`💾 Injected ${notes.length} notes and ${examples.length} examples into conversation context`, true);
352
+ }
353
+ this._injectedMemory = { notes, examples };
354
+ return { notes, examples };
355
+ }
301
356
  async validateRunChain(lastRunId, contextUser) {
302
357
  const visitedRunIds = new Set();
303
358
  visitedRunIds.add(this._agentRun.ID);
@@ -1073,55 +1128,242 @@ class BaseAgent {
1073
1128
  originalLength
1074
1129
  };
1075
1130
  }
1076
- async attemptContextRecovery(params, payload, errorMessage) {
1077
- this.logStatus(`⚠️ Context length exceeded - attempting recovery by trimming conversation messages`, true, params);
1078
- const messages = params.conversationMessages;
1131
+ recoveryStrategy_RemoveOldestActionResults(params, tokensToSave, currentStepCount, minAge = 5) {
1132
+ let tokensSaved = 0;
1133
+ const removedIndices = [];
1134
+ const candidates = params.conversationMessages
1135
+ .map((msg, index) => ({
1136
+ message: msg,
1137
+ index: index,
1138
+ age: msg.metadata?.turnAdded
1139
+ ? currentStepCount - msg.metadata.turnAdded
1140
+ : 0,
1141
+ tokens: this.estimateTokens(msg.content),
1142
+ isActionResult: msg.metadata?.messageType === 'action-result'
1143
+ }))
1144
+ .filter(c => c.isActionResult && c.age >= minAge)
1145
+ .sort((a, b) => b.age - a.age);
1146
+ for (const candidate of candidates) {
1147
+ if (tokensSaved >= tokensToSave)
1148
+ break;
1149
+ removedIndices.push(candidate.index);
1150
+ tokensSaved += candidate.tokens;
1151
+ this.logStatus(`Removing action-result from ${candidate.age} turns ago (${candidate.tokens} tokens)`, true, params);
1152
+ }
1153
+ removedIndices.sort((a, b) => b - a).forEach(index => {
1154
+ const removed = params.conversationMessages.splice(index, 1)[0];
1155
+ this.emitMessageLifecycleEvent({
1156
+ type: 'message-removed',
1157
+ turn: currentStepCount,
1158
+ messageIndex: index,
1159
+ message: removed,
1160
+ reason: 'Context recovery - oldest action results',
1161
+ tokensSaved: this.estimateTokens(removed.content)
1162
+ });
1163
+ });
1164
+ return {
1165
+ tokensSaved,
1166
+ strategyName: `Removed ${removedIndices.length} old action-results (${minAge}+ turns)`
1167
+ };
1168
+ }
1169
+ async recoveryStrategy_CompactOldActionResults(params, tokensToSave, currentStepCount, minAge = 3) {
1170
+ let tokensSaved = 0;
1171
+ let compactedCount = 0;
1172
+ const candidates = params.conversationMessages
1173
+ .map((msg, index) => ({
1174
+ message: msg,
1175
+ index: index,
1176
+ age: msg.metadata?.turnAdded
1177
+ ? currentStepCount - msg.metadata.turnAdded
1178
+ : 0,
1179
+ tokens: this.estimateTokens(msg.content),
1180
+ isActionResult: msg.metadata?.messageType === 'action-result',
1181
+ alreadyCompacted: msg.metadata?.wasCompacted === true
1182
+ }))
1183
+ .filter(c => c.isActionResult && c.age >= minAge && !c.alreadyCompacted)
1184
+ .sort((a, b) => b.age - a.age);
1185
+ for (const candidate of candidates) {
1186
+ if (tokensSaved >= tokensToSave)
1187
+ break;
1188
+ const originalTokens = candidate.tokens;
1189
+ const originalMessage = candidate.message;
1190
+ const originalContent = typeof originalMessage.content === 'string'
1191
+ ? originalMessage.content
1192
+ : JSON.stringify(originalMessage.content);
1193
+ const compactedContent = await this.compactMessage(originalMessage, {
1194
+ compactMode: 'First N Chars',
1195
+ compactLength: 500,
1196
+ compactPromptId: '',
1197
+ originalLength: originalContent.length
1198
+ }, params);
1199
+ const newTokens = this.estimateTokens(compactedContent);
1200
+ const saved = originalTokens - newTokens;
1201
+ if (saved > 0) {
1202
+ params.conversationMessages[candidate.index] = {
1203
+ ...originalMessage,
1204
+ content: compactedContent,
1205
+ metadata: {
1206
+ ...originalMessage.metadata,
1207
+ wasCompacted: true,
1208
+ originalLength: originalContent.length,
1209
+ tokensSaved: saved
1210
+ }
1211
+ };
1212
+ tokensSaved += saved;
1213
+ compactedCount++;
1214
+ this.logStatus(`Compacted action-result from ${candidate.age} turns ago (saved ${saved} tokens)`, true, params);
1215
+ }
1216
+ }
1217
+ return {
1218
+ tokensSaved,
1219
+ strategyName: `Compacted ${compactedCount} old action-results (${minAge}+ turns)`
1220
+ };
1221
+ }
1222
+ async recoveryStrategy_CompactAllActionResults(params, tokensToSave) {
1223
+ let tokensSaved = 0;
1224
+ let compactedCount = 0;
1225
+ const candidates = params.conversationMessages
1226
+ .map((msg, index) => ({
1227
+ message: msg,
1228
+ index: index,
1229
+ tokens: this.estimateTokens(msg.content),
1230
+ isActionResult: msg.metadata?.messageType === 'action-result',
1231
+ alreadyCompacted: msg.metadata?.wasCompacted === true
1232
+ }))
1233
+ .filter(c => c.isActionResult && !c.alreadyCompacted && c.tokens > 200)
1234
+ .sort((a, b) => b.tokens - a.tokens);
1235
+ for (const candidate of candidates) {
1236
+ if (tokensSaved >= tokensToSave)
1237
+ break;
1238
+ const originalTokens = candidate.tokens;
1239
+ const originalMessage = candidate.message;
1240
+ const originalContent = typeof originalMessage.content === 'string'
1241
+ ? originalMessage.content
1242
+ : JSON.stringify(originalMessage.content);
1243
+ const compactedContent = await this.compactMessage(originalMessage, {
1244
+ compactMode: 'First N Chars',
1245
+ compactLength: 200,
1246
+ compactPromptId: '',
1247
+ originalLength: originalContent.length
1248
+ }, params);
1249
+ const newTokens = this.estimateTokens(compactedContent);
1250
+ const saved = originalTokens - newTokens;
1251
+ if (saved > 0) {
1252
+ params.conversationMessages[candidate.index] = {
1253
+ ...originalMessage,
1254
+ content: compactedContent,
1255
+ metadata: {
1256
+ ...originalMessage.metadata,
1257
+ wasCompacted: true,
1258
+ originalLength: originalContent.length,
1259
+ tokensSaved: saved
1260
+ }
1261
+ };
1262
+ tokensSaved += saved;
1263
+ compactedCount++;
1264
+ }
1265
+ }
1266
+ return {
1267
+ tokensSaved,
1268
+ strategyName: `Aggressively compacted ${compactedCount} action-results`
1269
+ };
1270
+ }
1271
+ recoveryStrategy_TrimLastUserMessage(params, tokensToSave) {
1079
1272
  let lastUserMessageIndex = -1;
1080
- for (let i = messages.length - 1; i >= 0; i--) {
1081
- if (messages[i].role === 'user') {
1273
+ for (let i = params.conversationMessages.length - 1; i >= 0; i--) {
1274
+ if (params.conversationMessages[i].role === 'user') {
1082
1275
  lastUserMessageIndex = i;
1083
1276
  break;
1084
1277
  }
1085
1278
  }
1086
1279
  if (lastUserMessageIndex === -1) {
1087
- this.logStatus(`❌ Context recovery failed: No trimmable user messages found`, true, params);
1280
+ return { tokensSaved: 0, strategyName: 'No user message to trim' };
1281
+ }
1282
+ const lastUserMessage = params.conversationMessages[lastUserMessageIndex];
1283
+ const originalTokens = this.estimateTokens(lastUserMessage.content);
1284
+ const contentString = this.contentToString(lastUserMessage.content);
1285
+ const targetChars = 1000;
1286
+ if (contentString.length <= targetChars) {
1287
+ return { tokensSaved: 0, strategyName: 'User message already short' };
1288
+ }
1289
+ const trimResult = this.smartTrimContent(contentString, targetChars);
1290
+ const newContent = trimResult.trimmed +
1291
+ '\n\n<CONTEXT_LIMIT_REACHED>\n' +
1292
+ 'Note: The conversation has exceeded the model\'s context window. ' +
1293
+ 'The remainder of your message was trimmed to fit within the limit. ' +
1294
+ 'The beginning of your request (shown above) has been preserved.\n' +
1295
+ '</CONTEXT_LIMIT_REACHED>';
1296
+ const newTokens = this.estimateTokens(newContent);
1297
+ const saved = originalTokens - newTokens;
1298
+ if (saved > 0) {
1299
+ params.conversationMessages[lastUserMessageIndex] = {
1300
+ ...lastUserMessage,
1301
+ content: newContent
1302
+ };
1303
+ }
1304
+ return {
1305
+ tokensSaved: saved,
1306
+ strategyName: 'Preserved beginning of user message with context limit marker'
1307
+ };
1308
+ }
1309
+ async attemptContextRecovery(params, payload, errorMessage, modelSelectionInfo) {
1310
+ this.logStatus(`⚠️ Context length exceeded - attempting recovery with multi-strategy approach`, true, params);
1311
+ const modelLimit = this.getModelContextLimit(modelSelectionInfo);
1312
+ const currentTokens = this.estimateConversationTokens(params.conversationMessages);
1313
+ const tokensToSave = currentTokens - Math.floor(modelLimit * 0.9);
1314
+ this.logStatus(`Need to save ~${tokensToSave} tokens (current: ${currentTokens}, limit: ${modelLimit})`, true, params);
1315
+ if (tokensToSave <= 0) {
1316
+ this.logStatus(`Already under context limit, retrying...`, true, params);
1317
+ return {
1318
+ step: 'Retry',
1319
+ retryReason: 'Context recovery - already under limit',
1320
+ retryInstructions: 'The context is now within limits.',
1321
+ terminate: false,
1322
+ previousPayload: payload,
1323
+ newPayload: payload
1324
+ };
1325
+ }
1326
+ const currentStepCount = this._agentRun?.Steps?.length || 0;
1327
+ const strategies = [
1328
+ () => this.recoveryStrategy_RemoveOldestActionResults(params, tokensToSave, currentStepCount, 5),
1329
+ () => this.recoveryStrategy_CompactOldActionResults(params, tokensToSave, currentStepCount, 3),
1330
+ () => this.recoveryStrategy_RemoveOldestActionResults(params, tokensToSave, currentStepCount, 2),
1331
+ () => this.recoveryStrategy_CompactAllActionResults(params, tokensToSave),
1332
+ () => Promise.resolve(this.recoveryStrategy_TrimLastUserMessage(params, tokensToSave))
1333
+ ];
1334
+ let tokensSaved = 0;
1335
+ const strategiesUsed = [];
1336
+ for (const strategy of strategies) {
1337
+ const result = await strategy();
1338
+ tokensSaved += result.tokensSaved;
1339
+ if (result.tokensSaved > 0) {
1340
+ strategiesUsed.push(result.strategyName);
1341
+ this.logStatus(`${result.strategyName}: saved ${result.tokensSaved} tokens`, true, params);
1342
+ }
1343
+ if (tokensSaved >= tokensToSave) {
1344
+ break;
1345
+ }
1346
+ }
1347
+ if (tokensSaved < tokensToSave * 0.5) {
1348
+ this.logStatus(`❌ Context recovery insufficient: only saved ${tokensSaved}/${tokensToSave} tokens`, true, params);
1088
1349
  return {
1089
- errorMessage: `Context overflow with no trimmable messages: ${errorMessage}`,
1350
+ errorMessage: `Context recovery failed: only saved ${tokensSaved}/${tokensToSave} tokens. ${errorMessage}`,
1090
1351
  step: 'Failed',
1091
1352
  terminate: true,
1092
1353
  previousPayload: payload,
1093
1354
  newPayload: payload
1094
1355
  };
1095
1356
  }
1096
- const originalMessage = messages[lastUserMessageIndex];
1097
- const contentString = this.contentToString(originalMessage.content);
1098
- const trimResult = this.smartTrimContent(contentString, 1000);
1099
- this.logStatus(`✂️ Trimmed message from ${trimResult.originalLength.toLocaleString()} to ${trimResult.trimmed.length.toLocaleString()} characters using ${trimResult.strategy}`, true, params);
1100
- messages[lastUserMessageIndex] = {
1101
- role: 'user',
1102
- content: `⚠️ CONTEXT OVERFLOW RECOVERY ⚠️
1103
-
1104
- The previous step returned a result that exceeded the context window (${(trimResult.originalLength - trimResult.trimmed.length).toLocaleString()} characters truncated).
1105
-
1106
- Here is a PARTIAL result from the previous action:
1107
- ---
1108
- ${trimResult.trimmed}
1109
- ---
1110
-
1111
- ❗ THE ABOVE IS INCOMPLETE - the full result was too large for the context window.
1112
-
1113
- RECOMMENDED ACTIONS:
1114
- 1. Use a different action with more specific filters to get smaller result sets
1115
- 2. Request data in batches or pages instead of all at once
1116
- 3. Ask the user to clarify scope to narrow the query
1117
- 4. If you need the full data, acknowledge the limitation and ask the user how to proceed
1118
-
1119
- Please choose an alternative approach to complete your task.`
1120
- };
1357
+ this.logStatus(`✅ Context recovery successful: saved ${tokensSaved} tokens using ${strategiesUsed.length} strategies`, true, params);
1358
+ const strategyDescriptions = strategiesUsed.map((strategy, index) => `${index + 1}. ${strategy}`).join('\n');
1121
1359
  return {
1122
1360
  step: 'Retry',
1123
- retryReason: 'Context length recovery - previous result too large',
1124
- retryInstructions: 'The previous action result exceeded context limits. Use more specific filters or request data in smaller batches.',
1361
+ retryReason: `Context recovery successful - ${tokensSaved} tokens freed`,
1362
+ retryInstructions: `The conversation exceeded the model's context limit (${modelLimit} tokens). I've applied the following context recovery strategies to free up ${tokensSaved} tokens:
1363
+
1364
+ ${strategyDescriptions}
1365
+
1366
+ The context is now within limits. Please retry your request with the recovered context.`,
1125
1367
  terminate: false,
1126
1368
  previousPayload: payload,
1127
1369
  newPayload: payload
@@ -1236,18 +1478,61 @@ Please choose an alternative approach to complete your task.`
1236
1478
  throw new Error(`Error executing actions: ${error.message}`);
1237
1479
  }
1238
1480
  }
1481
+ prepareSubAgentMessages(params, subAgentRequest, subAgent, contextMessage) {
1482
+ const engine = aiengine_1.AIEngine.Instance;
1483
+ let messages = [];
1484
+ const relationship = engine.AgentRelationships.find(r => r.AgentID === params.agent.ID && r.SubAgentID === subAgent.ID);
1485
+ let messageMode = relationship?.MessageMode || subAgent.MessageMode || 'None';
1486
+ let maxMessages = relationship?.MaxMessages || subAgent.MaxMessages || null;
1487
+ switch (messageMode) {
1488
+ case 'None':
1489
+ break;
1490
+ case 'All':
1491
+ messages = [...params.conversationMessages];
1492
+ break;
1493
+ case 'Latest':
1494
+ if (maxMessages && maxMessages > 0) {
1495
+ messages = params.conversationMessages.slice(-maxMessages);
1496
+ }
1497
+ else {
1498
+ messages = [...params.conversationMessages];
1499
+ }
1500
+ break;
1501
+ case 'Bookend':
1502
+ if (maxMessages && maxMessages > 2 && params.conversationMessages.length > maxMessages) {
1503
+ const firstTwo = params.conversationMessages.slice(0, 2);
1504
+ const remaining = params.conversationMessages.slice(-(maxMessages - 2));
1505
+ const omittedCount = params.conversationMessages.length - maxMessages;
1506
+ messages = [
1507
+ ...firstTwo,
1508
+ {
1509
+ role: 'system',
1510
+ content: `[${omittedCount} messages omitted for context management]`
1511
+ },
1512
+ ...remaining
1513
+ ];
1514
+ }
1515
+ else {
1516
+ messages = [...params.conversationMessages];
1517
+ }
1518
+ break;
1519
+ default:
1520
+ break;
1521
+ }
1522
+ if (contextMessage) {
1523
+ messages.push(contextMessage);
1524
+ }
1525
+ messages.push({
1526
+ role: 'user',
1527
+ content: subAgentRequest.message
1528
+ });
1529
+ return messages;
1530
+ }
1239
1531
  async ExecuteSubAgent(params, subAgentRequest, subAgent, stepEntity, payload, contextMessage) {
1240
1532
  try {
1241
1533
  this.logStatus(`🤖 Executing sub-agent '${subAgentRequest.name}'`, true, params);
1242
1534
  const runner = new AgentRunner_1.AgentRunner();
1243
- const subAgentMessages = [];
1244
- if (contextMessage) {
1245
- subAgentMessages.push(contextMessage);
1246
- }
1247
- subAgentMessages.push({
1248
- role: 'user',
1249
- content: subAgentRequest.message
1250
- });
1535
+ const subAgentMessages = this.prepareSubAgentMessages(params, subAgentRequest, subAgent, contextMessage);
1251
1536
  this.logStatus(`📨 Sub-agent message: "${subAgentRequest.message}"`, true, params);
1252
1537
  if (subAgentRequest.templateParameters) {
1253
1538
  this.logStatus(`📎 Template parameters: ${JSON.stringify(subAgentRequest.templateParameters)}`, true, params);
@@ -1781,10 +2066,10 @@ Please choose an alternative approach to complete your task.`
1781
2066
  await stepEntity.Save();
1782
2067
  };
1783
2068
  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
- }
2069
+ params.conversationMessages = params.conversationMessages.filter(m => {
2070
+ const metadata = m.metadata;
2071
+ return !metadata?._loopResults && !metadata?._subAgentResult;
2072
+ });
1788
2073
  if (promptResult.promptRun?.ID) {
1789
2074
  stepEntity.TargetLogID = promptResult.promptRun.ID;
1790
2075
  stepEntity.PromptRun = promptResult.promptRun;
@@ -1794,10 +2079,10 @@ Please choose an alternative approach to complete your task.`
1794
2079
  const isFatal = this.isFatalPromptError(promptResult);
1795
2080
  const isContextOverflow = isFatal &&
1796
2081
  promptResult.chatResult?.errorInfo?.errorType === 'ContextLengthExceeded';
1797
- if (isContextOverflow && !this._contextRecoveryAttempted) {
1798
- this._contextRecoveryAttempted = true;
2082
+ if (isContextOverflow && this._contextRecoveryAttempts < this.MAX_RECOVERY_ATTEMPTS) {
2083
+ this._contextRecoveryAttempts++;
1799
2084
  this.logStatus(`⚠️ Context length exceeded - attempting recovery by trimming conversation`, true, params);
1800
- return await this.attemptContextRecovery(params, payload, promptResult.errorMessage || 'Context length exceeded');
2085
+ return await this.attemptContextRecovery(params, payload, promptResult.errorMessage || 'Context length exceeded', promptResult.modelSelectionInfo);
1801
2086
  }
1802
2087
  this.logStatus(`❌ Prompt execution failed: ${promptResult.errorMessage} (fatal: ${isFatal})`, true, params);
1803
2088
  return {
@@ -2084,7 +2369,13 @@ Please choose an alternative approach to complete your task.`
2084
2369
  : `Sub-agent failed:\n${JSON.stringify(subAgentSummary, null, 2)}`;
2085
2370
  params.conversationMessages.push({
2086
2371
  role: 'user',
2087
- content: resultMessage
2372
+ content: resultMessage,
2373
+ metadata: {
2374
+ _temporary: true,
2375
+ _subAgentResult: true,
2376
+ subAgentName: subAgentRequest.name,
2377
+ subAgentId: subAgentEntity.ID
2378
+ }
2088
2379
  });
2089
2380
  if (stepEntity) {
2090
2381
  stepEntity.PayloadAtEnd = JSON.stringify(mergedPayload);
@@ -3100,7 +3391,10 @@ Please choose an alternative approach to complete your task.`
3100
3391
  success: finalStep.step === 'Success' || finalStep.step === 'Chat',
3101
3392
  payload,
3102
3393
  agentRun: this._agentRun,
3103
- suggestedResponses: finalStep.suggestedResponses
3394
+ suggestedResponses: finalStep.suggestedResponses,
3395
+ memoryContext: this._injectedMemory.notes.length > 0 || this._injectedMemory.examples.length > 0
3396
+ ? this._injectedMemory
3397
+ : undefined
3104
3398
  };
3105
3399
  }
3106
3400
  calculateTokenStats() {
@@ -3353,11 +3647,65 @@ Please choose an alternative approach to complete your task.`
3353
3647
  }
3354
3648
  return prompt.ID;
3355
3649
  }
3356
- estimateTokens(content) {
3650
+ estimateTokens(content, modelName) {
3357
3651
  const text = typeof content === 'string'
3358
3652
  ? content
3359
3653
  : JSON.stringify(content);
3360
- return Math.ceil(text.length / 4);
3654
+ return this.heuristicTokenCount(text);
3655
+ }
3656
+ heuristicTokenCount(text) {
3657
+ const charCount = text.length;
3658
+ const structuralChars = (text.match(/[\{\}\[\],:]/g) || []).length;
3659
+ const whitespaceChars = (text.match(/\s/g) || []).length;
3660
+ const effectiveChars = charCount - (whitespaceChars * 0.5);
3661
+ const baseTokens = effectiveChars / 4;
3662
+ const structuralTokens = structuralChars * 0.05;
3663
+ return Math.ceil(baseTokens + structuralTokens);
3664
+ }
3665
+ getModelContextLimit(modelSelectionInfo) {
3666
+ const DEFAULT_LIMIT = 8000;
3667
+ if (!modelSelectionInfo) {
3668
+ this.logStatus(`No model selection info available, using default limit: ${DEFAULT_LIMIT}`, true);
3669
+ return DEFAULT_LIMIT;
3670
+ }
3671
+ try {
3672
+ const modelSelected = modelSelectionInfo.modelSelected;
3673
+ const vendorSelected = modelSelectionInfo.vendorSelected;
3674
+ if (!modelSelected) {
3675
+ this.logStatus(`No model selected in model selection info, using default limit: ${DEFAULT_LIMIT}`, true);
3676
+ return DEFAULT_LIMIT;
3677
+ }
3678
+ if (!vendorSelected) {
3679
+ this.logStatus(`No vendor selected, using default limit: ${DEFAULT_LIMIT}`, true);
3680
+ return DEFAULT_LIMIT;
3681
+ }
3682
+ const modelVendors = modelSelected.ModelVendors;
3683
+ if (!modelVendors || modelVendors.length === 0) {
3684
+ this.logStatus(`No ModelVendors array found on model, using default limit: ${DEFAULT_LIMIT}`, true);
3685
+ return DEFAULT_LIMIT;
3686
+ }
3687
+ const vendorEntry = modelVendors.find((mv) => mv.VendorID === vendorSelected.ID);
3688
+ if (!vendorEntry) {
3689
+ this.logStatus(`No matching vendor entry found in ModelVendors, using default limit: ${DEFAULT_LIMIT}`, true);
3690
+ return DEFAULT_LIMIT;
3691
+ }
3692
+ const maxInputTokens = vendorEntry.MaxInputTokens;
3693
+ if (!maxInputTokens || maxInputTokens <= 0) {
3694
+ this.logStatus(`MaxInputTokens not set or invalid on vendor entry, using default limit: ${DEFAULT_LIMIT}`, true);
3695
+ return DEFAULT_LIMIT;
3696
+ }
3697
+ this.logStatus(`Using vendor-specific MaxInputTokens: ${maxInputTokens} (Model: ${modelSelected.Name}, Vendor: ${vendorSelected.Name})`, true);
3698
+ return maxInputTokens;
3699
+ }
3700
+ catch (error) {
3701
+ this.logStatus(`Error extracting model context limit: ${error}, using default limit: ${DEFAULT_LIMIT}`, true);
3702
+ return DEFAULT_LIMIT;
3703
+ }
3704
+ }
3705
+ estimateConversationTokens(messages) {
3706
+ return messages.reduce((total, msg) => {
3707
+ return total + this.estimateTokens(msg.content);
3708
+ }, 0);
3361
3709
  }
3362
3710
  emitMessageLifecycleEvent(event) {
3363
3711
  if (this._messageLifecycleCallback) {