@memberjunction/ai-agents 3.0.0 → 3.1.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.
@@ -52,6 +52,7 @@ class BaseAgent {
52
52
  this._parentStepCounts = [];
53
53
  this._allProgressSteps = [];
54
54
  this._subAgentRuns = [];
55
+ this._mediaOutputs = [];
55
56
  this._payloadManager = new PayloadManager_1.PayloadManager();
56
57
  this._effectiveActions = [];
57
58
  this._dynamicActionLimits = {};
@@ -82,6 +83,163 @@ class BaseAgent {
82
83
  }
83
84
  return aiengine_1.AIEngine.Instance.GetDefaultAgentConfigurationPreset(agentId);
84
85
  }
86
+ promoteMediaOutputs(mediaOutputs) {
87
+ if (mediaOutputs && mediaOutputs.length > 0) {
88
+ this._mediaOutputs.push(...mediaOutputs);
89
+ this.logStatus(`📎 Promoted ${mediaOutputs.length} media output(s) to agent results`, true);
90
+ }
91
+ }
92
+ get MediaOutputs() {
93
+ return [...this._mediaOutputs];
94
+ }
95
+ interceptLargeBinaryContent(actionParams, actionEntity) {
96
+ if (!actionParams || actionParams.length === 0) {
97
+ return actionParams;
98
+ }
99
+ const sanitizedParams = [];
100
+ for (const param of actionParams) {
101
+ if (param.Type !== 'Output' && param.Type !== 'Both') {
102
+ sanitizedParams.push(param);
103
+ continue;
104
+ }
105
+ const paramMetadata = actionEntity?.Params?.find(p => p.Name === param.Name);
106
+ const valueType = paramMetadata?.ValueType;
107
+ const isMediaOutputParam = valueType === 'MediaOutput';
108
+ if (isMediaOutputParam && Array.isArray(param.Value)) {
109
+ const mediaItems = param.Value;
110
+ const references = [];
111
+ let extractedCount = 0;
112
+ for (let i = 0; i < mediaItems.length; i++) {
113
+ const media = mediaItems[i];
114
+ if (media.data && media.data.length > BaseAgent.LARGE_BINARY_THRESHOLD) {
115
+ const refId = `media-${Date.now().toString(36)}-${i}-${Math.random().toString(36).substring(2, 8)}`;
116
+ this._mediaOutputs.push({
117
+ ...media,
118
+ refId,
119
+ persist: false
120
+ });
121
+ references.push(`\${media:${refId}}`);
122
+ extractedCount++;
123
+ }
124
+ }
125
+ if (extractedCount > 0) {
126
+ sanitizedParams.push({
127
+ Name: param.Name,
128
+ Type: param.Type,
129
+ Value: {
130
+ mediaReferences: references,
131
+ count: mediaItems.length,
132
+ note: `${extractedCount} media item(s) extracted. Use placeholder syntax in your response: <img src="${references[0]}" alt="description" />`
133
+ }
134
+ });
135
+ this.logStatus(`📦 Extracted ${extractedCount} ${param.Name} item(s) to media references`, true);
136
+ continue;
137
+ }
138
+ }
139
+ if (typeof param.Value === 'string' && param.Value.length > BaseAgent.LARGE_BINARY_THRESHOLD) {
140
+ const base64Pattern = /^[A-Za-z0-9+/]+=*$/;
141
+ if (isMediaOutputParam || base64Pattern.test(param.Value.substring(0, 1000))) {
142
+ const refId = `data-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 8)}`;
143
+ this._mediaOutputs.push({
144
+ modality: 'Image',
145
+ mimeType: 'application/octet-stream',
146
+ data: param.Value,
147
+ label: `Media data from ${param.Name}`,
148
+ refId,
149
+ persist: false
150
+ });
151
+ sanitizedParams.push({
152
+ Name: param.Name,
153
+ Type: param.Type,
154
+ Value: `\${media:${refId}}`
155
+ });
156
+ this.logStatus(`📦 Extracted large binary content from '${param.Name}' to media reference`, true);
157
+ continue;
158
+ }
159
+ }
160
+ sanitizedParams.push(param);
161
+ }
162
+ return sanitizedParams;
163
+ }
164
+ resolveMediaPlaceholdersInString(text) {
165
+ const hasRefIds = this._mediaOutputs.some(m => m.refId);
166
+ if (!text || !hasRefIds) {
167
+ return text;
168
+ }
169
+ const placeholderRegex = /\$\{media:([a-z0-9-]+)\}/g;
170
+ return text.replace(placeholderRegex, (match, refId) => {
171
+ const media = this._mediaOutputs.find(m => m.refId === refId);
172
+ if (media?.data) {
173
+ media.persist = true;
174
+ return `data:${media.mimeType};base64,${media.data}`;
175
+ }
176
+ this.logStatus(`⚠️ Media reference '${refId}' not found in registry`, true);
177
+ return match;
178
+ });
179
+ }
180
+ resolveMediaPlaceholdersInPayload(payload) {
181
+ const hasRefIds = this._mediaOutputs.some(m => m.refId);
182
+ if (!hasRefIds) {
183
+ return payload;
184
+ }
185
+ const unpersisted = this._mediaOutputs.filter(m => m.refId && m.persist === false).length;
186
+ const resolved = this.resolveMediaPlaceholdersRecursive(payload);
187
+ const persistedAfter = this._mediaOutputs.filter(m => m.refId && m.persist === true).length;
188
+ const resolvedCount = persistedAfter - (unpersisted - this._mediaOutputs.filter(m => m.refId && m.persist === false).length);
189
+ if (resolvedCount > 0) {
190
+ this.logStatus(`✅ Resolved ${resolvedCount} media placeholder(s) in final payload`, true);
191
+ }
192
+ return resolved;
193
+ }
194
+ resolveMediaPlaceholdersRecursive(value) {
195
+ if (value === null || value === undefined) {
196
+ return value;
197
+ }
198
+ if (typeof value === 'string') {
199
+ return this.resolveMediaPlaceholdersInString(value);
200
+ }
201
+ if (Array.isArray(value)) {
202
+ return value.map(item => this.resolveMediaPlaceholdersRecursive(item));
203
+ }
204
+ if (typeof value === 'object') {
205
+ const result = {};
206
+ for (const key of Object.keys(value)) {
207
+ result[key] = this.resolveMediaPlaceholdersRecursive(value[key]);
208
+ }
209
+ return result;
210
+ }
211
+ return value;
212
+ }
213
+ processMessageMediaPlaceholders(message) {
214
+ if (!message) {
215
+ return message;
216
+ }
217
+ const hasRefIds = this._mediaOutputs.some(m => m.refId);
218
+ if (!hasRefIds) {
219
+ return message;
220
+ }
221
+ const placeholderRegex = /\$\{media:([a-zA-Z0-9_-]+)\}/g;
222
+ let match;
223
+ let promotedCount = 0;
224
+ while ((match = placeholderRegex.exec(message)) !== null) {
225
+ const refId = match[1];
226
+ const media = this._mediaOutputs.find(m => m.refId === refId);
227
+ if (media && media.persist !== true) {
228
+ media.persist = true;
229
+ promotedCount++;
230
+ }
231
+ }
232
+ if (promotedCount > 0) {
233
+ this.logStatus(`📎 Auto-promoted ${promotedCount} media output(s) from message placeholders`, true);
234
+ }
235
+ let cleanedMessage = message
236
+ .replace(/<img[^>]*src=["']\$\{media:[^}]+\}["'][^>]*\/?>/gi, '')
237
+ .replace(/<audio[^>]*src=["']\$\{media:[^}]+\}["'][^>]*>.*?<\/audio>/gi, '')
238
+ .replace(/<video[^>]*src=["']\$\{media:[^}]+\}["'][^>]*>.*?<\/video>/gi, '')
239
+ .replace(/\n\s*\n\s*\n/g, '\n\n')
240
+ .trim();
241
+ return cleanedMessage;
242
+ }
85
243
  get ValidationRetryCount() {
86
244
  return this._validationRetryCount;
87
245
  }
@@ -241,6 +399,7 @@ class BaseAgent {
241
399
  this._contextRecoveryAttempts = 0;
242
400
  this._effectiveActions = [];
243
401
  this._dynamicActionLimits = {};
402
+ this._mediaOutputs = [];
244
403
  this._messageLifecycleCallback = params.onMessageLifecycle;
245
404
  await this.initializeEngines(params.contextUser);
246
405
  if (params.cancellationToken?.aborted) {
@@ -321,6 +480,9 @@ class BaseAgent {
321
480
  this.logStatus(`🔄 Executing step ${stepCount + 1} for agent '${params.agent.Name}'`, true, params);
322
481
  const nextStep = await this.executeNextStep(params, config, currentNextStep, stepCount);
323
482
  stepCount++;
483
+ if (nextStep.promoteMediaOutputs && nextStep.promoteMediaOutputs.length > 0) {
484
+ this.promoteMediaOutputs(nextStep.promoteMediaOutputs);
485
+ }
324
486
  if (nextStep.terminate) {
325
487
  continueExecution = false;
326
488
  this.logStatus(`🏁 Agent '${params.agent.Name}' terminating after ${stepCount} steps with result: ${nextStep.step}`, true, params);
@@ -2822,6 +2984,13 @@ The context is now within limits. Please retry your request with the recovered c
2822
2984
  throw new Error('Cancelled during sub-agent execution');
2823
2985
  }
2824
2986
  this._subAgentRuns.push(subAgentResult);
2987
+ if (subAgentResult.mediaOutputs?.length) {
2988
+ this._mediaOutputs.push(...subAgentResult.mediaOutputs);
2989
+ const refCount = subAgentResult.mediaOutputs.filter(m => m.refId).length;
2990
+ if (refCount > 0) {
2991
+ this.logStatus(`📦 Collected ${refCount} media reference(s) from sub-agent '${subAgentRequest.name}'`, true);
2992
+ }
2993
+ }
2825
2994
  const shouldTerminate = subAgentRequest.terminateAfter;
2826
2995
  const outputData = {
2827
2996
  subAgentResult: {
@@ -3006,6 +3175,13 @@ The context is now within limits. Please retry your request with the recovered c
3006
3175
  (0, core_1.LogStatus)(`Related sub-agent '${subAgentRequest.name}' receiving context from paths: ${contextPaths.join(', ')}`);
3007
3176
  }
3008
3177
  const subAgentResult = await this.ExecuteSubAgent(params, subAgentRequest, subAgentEntity, stepEntity, initialSubAgentPayload, contextMessage, stepCount);
3178
+ if (subAgentResult.mediaOutputs?.length) {
3179
+ this._mediaOutputs.push(...subAgentResult.mediaOutputs);
3180
+ const refCount = subAgentResult.mediaOutputs.filter(m => m.refId).length;
3181
+ if (refCount > 0) {
3182
+ this.logStatus(`📦 Collected ${refCount} media reference(s) from related sub-agent '${subAgentRequest.name}'`, true);
3183
+ }
3184
+ }
3009
3185
  let mergedPayload = previousDecision.newPayload;
3010
3186
  let currentStepPayloadChangeResult = undefined;
3011
3187
  if (subAgentResult.success && relationship.SubAgentOutputMapping) {
@@ -3367,10 +3543,12 @@ The context is now within limits. Please retry your request with the recovered c
3367
3543
  }
3368
3544
  const actionSummaries = actionResults.map(result => {
3369
3545
  const actionResult = result.success ? result.result : null;
3546
+ const outputParams = result.result?.Params?.filter(p => p.Type === 'Both' || p.Type === 'Output') || [];
3547
+ const sanitizedParams = this.interceptLargeBinaryContent(outputParams, result.actionEntity);
3370
3548
  return {
3371
3549
  actionName: result.action.name,
3372
3550
  success: result.success,
3373
- params: result.result?.Params.filter(p => p.Type === 'Both' || p.Type === 'Output'),
3551
+ params: sanitizedParams,
3374
3552
  resultCode: actionResult?.Result?.ResultCode || (result.success ? 'SUCCESS' : 'ERROR'),
3375
3553
  message: result.success ? actionResult?.Message || 'Action completed' : result.error
3376
3554
  };
@@ -3914,6 +4092,13 @@ The context is now within limits. Please retry your request with the recovered c
3914
4092
  };
3915
4093
  }
3916
4094
  async finalizeAgentRun(finalStep, payload, contextUser) {
4095
+ const isRootAgent = this._depth === 0;
4096
+ const resolvedPayload = (payload && isRootAgent)
4097
+ ? this.resolveMediaPlaceholdersInPayload(payload)
4098
+ : payload;
4099
+ const processedMessage = (finalStep.message && isRootAgent)
4100
+ ? this.processMessageMediaPlaceholders(finalStep.message)
4101
+ : finalStep.message;
3917
4102
  if (this._agentRun) {
3918
4103
  this._agentRun.CompletedAt = new Date();
3919
4104
  this._agentRun.Success = finalStep.step === 'Success' || finalStep.step === 'Chat';
@@ -3929,11 +4114,11 @@ The context is now within limits. Please retry your request with the recovered c
3929
4114
  else {
3930
4115
  this._agentRun.Status = 'Completed';
3931
4116
  }
3932
- this._agentRun.Result = payload ? JSON.stringify(payload) : null;
4117
+ this._agentRun.Result = resolvedPayload ? JSON.stringify(resolvedPayload) : null;
3933
4118
  this._agentRun.FinalStep = finalStep.step;
3934
- this._agentRun.Message = finalStep.message;
3935
- this._agentRun.FinalPayloadObject = payload;
3936
- this._agentRun.FinalPayload = payload ? JSON.stringify(payload) : null;
4119
+ this._agentRun.Message = processedMessage;
4120
+ this._agentRun.FinalPayloadObject = resolvedPayload;
4121
+ this._agentRun.FinalPayload = resolvedPayload ? JSON.stringify(resolvedPayload) : null;
3937
4122
  const tokenStats = this.calculateTokenStats();
3938
4123
  this._agentRun.TotalTokensUsed = tokenStats.totalTokens;
3939
4124
  this._agentRun.TotalPromptTokensUsed = tokenStats.promptTokens;
@@ -3944,16 +4129,20 @@ The context is now within limits. Please retry your request with the recovered c
3944
4129
  (0, core_1.LogError)(`Failed to finalize agent run ${this._agentRun.ID}`);
3945
4130
  }
3946
4131
  }
4132
+ if (finalStep.promoteMediaOutputs && finalStep.promoteMediaOutputs.length > 0) {
4133
+ this.promoteMediaOutputs(finalStep.promoteMediaOutputs);
4134
+ }
3947
4135
  return {
3948
4136
  success: finalStep.step === 'Success' || finalStep.step === 'Chat',
3949
- payload,
4137
+ payload: resolvedPayload,
3950
4138
  agentRun: this._agentRun,
3951
4139
  responseForm: finalStep.responseForm,
3952
4140
  actionableCommands: finalStep.actionableCommands,
3953
4141
  automaticCommands: finalStep.automaticCommands,
3954
4142
  memoryContext: this._injectedMemory.notes.length > 0 || this._injectedMemory.examples.length > 0
3955
4143
  ? this._injectedMemory
3956
- : undefined
4144
+ : undefined,
4145
+ mediaOutputs: this._mediaOutputs.length > 0 ? this._mediaOutputs : undefined
3957
4146
  };
3958
4147
  }
3959
4148
  calculateTokenStats() {
@@ -4378,4 +4567,5 @@ The context is now within limits. Please retry your request with the recovered c
4378
4567
  }
4379
4568
  exports.BaseAgent = BaseAgent;
4380
4569
  BaseAgent.MAX_VALIDATION_RETRIES = 10;
4570
+ BaseAgent.LARGE_BINARY_THRESHOLD = 10000;
4381
4571
  //# sourceMappingURL=base-agent.js.map