@memberjunction/ai-agents 3.4.0 → 4.0.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.
Files changed (97) hide show
  1. package/dist/AgentDataPreloader.d.ts +117 -1
  2. package/dist/AgentDataPreloader.d.ts.map +1 -1
  3. package/dist/AgentDataPreloader.js +156 -58
  4. package/dist/AgentDataPreloader.js.map +1 -1
  5. package/dist/AgentRunner.d.ts +212 -0
  6. package/dist/AgentRunner.d.ts.map +1 -1
  7. package/dist/AgentRunner.js +354 -103
  8. package/dist/AgentRunner.js.map +1 -1
  9. package/dist/PayloadChangeAnalyzer.d.ts +68 -0
  10. package/dist/PayloadChangeAnalyzer.d.ts.map +1 -1
  11. package/dist/PayloadChangeAnalyzer.js +68 -32
  12. package/dist/PayloadChangeAnalyzer.js.map +1 -1
  13. package/dist/PayloadFeedbackManager.d.ts +57 -1
  14. package/dist/PayloadFeedbackManager.d.ts.map +1 -1
  15. package/dist/PayloadFeedbackManager.js +66 -21
  16. package/dist/PayloadFeedbackManager.js.map +1 -1
  17. package/dist/PayloadManager.d.ts +286 -1
  18. package/dist/PayloadManager.d.ts.map +1 -1
  19. package/dist/PayloadManager.js +423 -50
  20. package/dist/PayloadManager.js.map +1 -1
  21. package/dist/__tests__/action-changes.test.d.ts +13 -0
  22. package/dist/__tests__/action-changes.test.d.ts.map +1 -1
  23. package/dist/__tests__/action-changes.test.js +57 -4
  24. package/dist/__tests__/action-changes.test.js.map +1 -1
  25. package/dist/__tests__/agent-memory-features.test.d.ts +52 -0
  26. package/dist/__tests__/agent-memory-features.test.d.ts.map +1 -1
  27. package/dist/__tests__/agent-memory-features.test.js +96 -13
  28. package/dist/__tests__/agent-memory-features.test.js.map +1 -1
  29. package/dist/__tests__/agent-type-prompt-params.test.d.ts +12 -0
  30. package/dist/__tests__/agent-type-prompt-params.test.d.ts.map +1 -1
  31. package/dist/__tests__/agent-type-prompt-params.test.js +112 -20
  32. package/dist/__tests__/agent-type-prompt-params.test.js.map +1 -1
  33. package/dist/__tests__/chat-handling-option.test.d.ts +26 -0
  34. package/dist/__tests__/chat-handling-option.test.d.ts.map +1 -1
  35. package/dist/__tests__/chat-handling-option.test.js +41 -2
  36. package/dist/__tests__/chat-handling-option.test.js.map +1 -1
  37. package/dist/agent-context-injector.d.ts +114 -0
  38. package/dist/agent-context-injector.d.ts.map +1 -1
  39. package/dist/agent-context-injector.js +138 -19
  40. package/dist/agent-context-injector.js.map +1 -1
  41. package/dist/agent-types/base-agent-type.d.ts +354 -0
  42. package/dist/agent-types/base-agent-type.d.ts.map +1 -1
  43. package/dist/agent-types/base-agent-type.js +288 -17
  44. package/dist/agent-types/base-agent-type.js.map +1 -1
  45. package/dist/agent-types/flow-agent-type.d.ts +328 -2
  46. package/dist/agent-types/flow-agent-type.d.ts.map +1 -1
  47. package/dist/agent-types/flow-agent-type.js +503 -63
  48. package/dist/agent-types/flow-agent-type.js.map +1 -1
  49. package/dist/agent-types/index.d.ts +14 -3
  50. package/dist/agent-types/index.d.ts.map +1 -1
  51. package/dist/agent-types/index.js +14 -23
  52. package/dist/agent-types/index.js.map +1 -1
  53. package/dist/agent-types/loop-agent-prompt-params.d.ts +190 -0
  54. package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
  55. package/dist/agent-types/loop-agent-prompt-params.js +23 -6
  56. package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
  57. package/dist/agent-types/loop-agent-response-type.d.ts +62 -0
  58. package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
  59. package/dist/agent-types/loop-agent-response-type.js +3 -2
  60. package/dist/agent-types/loop-agent-response-type.js.map +1 -1
  61. package/dist/agent-types/loop-agent-type.d.ts +139 -2
  62. package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
  63. package/dist/agent-types/loop-agent-type.js +207 -35
  64. package/dist/agent-types/loop-agent-type.js.map +1 -1
  65. package/dist/base-agent.d.ts +1344 -1
  66. package/dist/base-agent.d.ts.map +1 -1
  67. package/dist/base-agent.js +2282 -211
  68. package/dist/base-agent.js.map +1 -1
  69. package/dist/index.d.ts +24 -14
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +25 -35
  72. package/dist/index.js.map +1 -1
  73. package/dist/memory-cleanup-agent.d.ts +53 -1
  74. package/dist/memory-cleanup-agent.d.ts.map +1 -1
  75. package/dist/memory-cleanup-agent.js +89 -21
  76. package/dist/memory-cleanup-agent.js.map +1 -1
  77. package/dist/memory-manager-agent.d.ts +61 -1
  78. package/dist/memory-manager-agent.d.ts.map +1 -1
  79. package/dist/memory-manager-agent.js +260 -116
  80. package/dist/memory-manager-agent.js.map +1 -1
  81. package/dist/services/AgentEmbeddingService.d.ts +16 -0
  82. package/dist/services/AgentEmbeddingService.d.ts.map +1 -0
  83. package/dist/services/AgentEmbeddingService.js +158 -0
  84. package/dist/services/AgentEmbeddingService.js.map +1 -0
  85. package/dist/types/AgentMatchResult.d.ts +18 -0
  86. package/dist/types/AgentMatchResult.d.ts.map +1 -0
  87. package/dist/types/AgentMatchResult.js +3 -0
  88. package/dist/types/AgentMatchResult.js.map +1 -0
  89. package/dist/types/payload-operations.d.ts +51 -0
  90. package/dist/types/payload-operations.d.ts.map +1 -1
  91. package/dist/types/payload-operations.js +54 -15
  92. package/dist/types/payload-operations.js.map +1 -1
  93. package/dist/utils/ConversationMessageResolver.d.ts +79 -1
  94. package/dist/utils/ConversationMessageResolver.d.ts.map +1 -1
  95. package/dist/utils/ConversationMessageResolver.js +99 -9
  96. package/dist/utils/ConversationMessageResolver.js.map +1 -1
  97. package/package.json +20 -19
@@ -1,66 +1,119 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.AgentRunner = void 0;
27
- const core_1 = require("@memberjunction/core");
28
- const global_1 = require("@memberjunction/global");
29
- const aiengine_1 = require("@memberjunction/aiengine");
30
- const base_agent_1 = require("./base-agent");
31
- class AgentRunner {
1
+ /**
2
+ * @fileoverview AgentRunner provides a thin wrapper for executing AI agents
3
+ * using the MemberJunction AI Agent framework.
4
+ *
5
+ * This module handles agent instantiation using the ClassFactory pattern to ensure
6
+ * the correct agent subclass is used based on the agent type's DriverClass property.
7
+ *
8
+ * @module @memberjunction/ai-agents
9
+ * @author MemberJunction.com
10
+ * @since 2.49.0
11
+ */
12
+ import { LogError, LogStatusEx, IsVerboseLoggingEnabled, LogStatus, Metadata, RunView } from '@memberjunction/core';
13
+ import { MJGlobal } from '@memberjunction/global';
14
+ import { AIEngine } from '@memberjunction/aiengine';
15
+ import { BaseAgent } from './base-agent.js';
16
+ /**
17
+ * AgentRunner provides a thin wrapper for executing AI agents.
18
+ *
19
+ * This class handles:
20
+ * - Loading agent type metadata to get the DriverClass
21
+ * - Instantiating the correct agent class using ClassFactory
22
+ * - Passing through to the agent's Execute method
23
+ *
24
+ * @class AgentRunner
25
+ * @example
26
+ * ```typescript
27
+ * const runner = new AgentRunner();
28
+ * const result = await runner.RunAgent({
29
+ * agent: myAgent,
30
+ * conversationMessages: messages,
31
+ * contextUser: currentUser
32
+ * });
33
+ * ```
34
+ */
35
+ export class AgentRunner {
36
+ /**
37
+ * Runs an AI agent with the specified parameters.
38
+ *
39
+ * This method acts as a thin pass-through that:
40
+ * 1. Loads the agent type to get the DriverClass
41
+ * 2. Uses ClassFactory to instantiate the correct agent class
42
+ * 3. Calls Execute on the agent instance and returns the result
43
+ *
44
+ * @param {ExecuteAgentParams} params - Parameters for agent execution (same as BaseAgent.Execute)
45
+ * @template C - The type of the agent's context as provided in the ExecuteAgentParams
46
+ * @template R - The type of the agent's result as returned in ExecuteAgentResult
47
+ * @returns {Promise<ExecuteAgentResult<T>>} The execution result (same as BaseAgent.Execute)
48
+ *
49
+ * @throws {Error} Throws if agent type loading fails or agent instantiation fails
50
+ */
32
51
  async RunAgent(params) {
33
52
  try {
34
- (0, core_1.LogStatusEx)({
53
+ LogStatusEx({
35
54
  message: `AgentRunner: Starting execution for agent: ${params.agent.Name} (ID: ${params.agent.ID})`,
36
55
  verboseOnly: true,
37
- isVerboseEnabled: () => params.verbose === true || (0, core_1.IsVerboseLoggingEnabled)()
56
+ isVerboseEnabled: () => params.verbose === true || IsVerboseLoggingEnabled()
38
57
  });
39
- await aiengine_1.AIEngine.Instance.Config(false, params.contextUser);
40
- const agentType = aiengine_1.AIEngine.Instance.AgentTypes.find(at => at.ID === params.agent.TypeID);
58
+ // Ensure AIEngine is configured
59
+ await AIEngine.Instance.Config(false, params.contextUser);
60
+ // Find the agent type to get the DriverClass
61
+ const agentType = AIEngine.Instance.AgentTypes.find(at => at.ID === params.agent.TypeID);
41
62
  if (!agentType) {
42
63
  throw new Error(`Agent type not found for ID: ${params.agent.TypeID}`);
43
64
  }
65
+ // Get the correct agent class using ClassFactory, prefer the agent's DriverClass if specified, otherwise fallback to Agent Type, otherwise we get BaseAgent from ClassFactory
44
66
  const driverClass = params.agent.DriverClass || agentType.DriverClass;
45
- (0, core_1.LogStatusEx)({
67
+ LogStatusEx({
46
68
  message: `AgentRunner: Using driver class: ${driverClass}`,
47
69
  verboseOnly: true,
48
- isVerboseEnabled: () => params.verbose === true || (0, core_1.IsVerboseLoggingEnabled)()
70
+ isVerboseEnabled: () => params.verbose === true || IsVerboseLoggingEnabled()
49
71
  });
50
- const agentInstance = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(base_agent_1.BaseAgent, driverClass);
72
+ const agentInstance = MJGlobal.Instance.ClassFactory.CreateInstance(BaseAgent, driverClass);
51
73
  if (!agentInstance) {
52
74
  throw new Error(`Failed to create agent instance for driver class: ${driverClass}`);
53
75
  }
76
+ // Execute the agent and return the result directly
54
77
  return await agentInstance.Execute(params);
55
78
  }
56
79
  catch (error) {
57
80
  const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
58
- (0, core_1.LogError)(`AgentRunner execution failed: ${errorMessage}`, undefined, error);
81
+ LogError(`AgentRunner execution failed: ${errorMessage}`, undefined, error);
82
+ // Re-throw the error since we can't create a proper ExecuteAgentResult without an agent run
83
+ // BaseAgent.Execute will handle creating a proper error result with an agent run
59
84
  throw error;
60
85
  }
61
86
  }
87
+ /**
88
+ * Runs an AI agent within a conversation context, handling conversation and artifact management.
89
+ *
90
+ * This method provides a complete workflow for running agents in conversations:
91
+ * 1. Creates or uses existing conversation
92
+ * 2. Creates conversation detail record for the user message
93
+ * 3. Executes the agent
94
+ * 4. Creates artifacts from the agent's payload (if configured)
95
+ * 5. Links artifacts to the conversation detail
96
+ *
97
+ * @param params - Core agent execution parameters
98
+ * @param options - Conversation-specific options
99
+ * @returns Promise containing the agent execution result and conversation/artifact metadata
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const runner = new AgentRunner();
104
+ * const result = await runner.RunAgentInConversation({
105
+ * agent: myAgent,
106
+ * conversationMessages: messages,
107
+ * contextUser: currentUser
108
+ * }, {
109
+ * conversationId: existingConvoId, // Optional - creates new if not provided
110
+ * userMessage: 'User query text',
111
+ * createArtifacts: true
112
+ * });
113
+ * ```
114
+ */
62
115
  async RunAgentInConversation(params, options) {
63
- const md = new core_1.Metadata();
116
+ const md = new Metadata();
64
117
  const contextUser = params.contextUser;
65
118
  if (!contextUser) {
66
119
  throw new Error('contextUser is required for RunAgentInConversation');
@@ -70,29 +123,40 @@ class AgentRunner {
70
123
  let userMessageDetailId;
71
124
  let agentResponseDetailId;
72
125
  let agentResponseDetail;
126
+ // If conversationDetailId is provided, use it (UI-created agent response detail)
73
127
  if (options.conversationDetailId) {
74
128
  agentResponseDetailId = options.conversationDetailId;
129
+ // Load the conversation detail to get the conversation ID AND keep reference for final status update
130
+ // This ensures backend can update Status/Message even if frontend disconnects (browser refresh)
75
131
  agentResponseDetail = await md.GetEntityObject('Conversation Details', contextUser);
76
132
  if (await agentResponseDetail.Load(agentResponseDetailId)) {
77
133
  conversationId = agentResponseDetail.ConversationID;
78
- (0, core_1.LogStatus)(`Using existing conversation ${conversationId} and agent response detail ${agentResponseDetailId}`);
79
- userMessageDetailId = agentResponseDetailId;
134
+ LogStatus(`Using existing conversation ${conversationId} and agent response detail ${agentResponseDetailId}`);
135
+ // Note: In this case, we don't know the user message detail ID
136
+ userMessageDetailId = agentResponseDetailId; // For backward compatibility
80
137
  }
81
138
  else {
82
139
  throw new Error(`Failed to load conversation detail ${agentResponseDetailId}`);
83
140
  }
84
141
  }
85
142
  else {
143
+ // Server creates BOTH user message and agent response details
86
144
  if (!options.userMessage) {
87
145
  throw new Error('userMessage is required when conversationDetailId is not provided');
88
146
  }
147
+ // Step 1: Get or create conversation
89
148
  conversationId = options.conversationId || '';
90
149
  if (!conversationId) {
91
- (0, core_1.LogStatus)('Creating new conversation');
150
+ LogStatus('Creating new conversation');
92
151
  const conversation = await md.GetEntityObject('Conversations', contextUser);
152
+ // Smart conversation naming:
153
+ // 1. Use provided name if given
154
+ // 2. Otherwise, use AI prompt to generate a good name from user message
155
+ // 3. Fallback to "Chat with [AgentName]"
93
156
  let conversationName = options.conversationName;
94
157
  let conversationDescription = null;
95
158
  if (!conversationName && options.userMessage) {
159
+ // Try to generate a name using the "Name Conversation" prompt (same as UI)
96
160
  const nameResult = await this.GenerateConversationName(options.userMessage, contextUser);
97
161
  if (nameResult) {
98
162
  conversationName = nameResult.name;
@@ -103,10 +167,11 @@ class AgentRunner {
103
167
  conversationName = `Chat with ${params.agent.Name}`;
104
168
  }
105
169
  conversation.Name = conversationName;
106
- conversation.Description = conversationDescription || '';
170
+ conversation.Description = conversationDescription || ''; // Set description too (like UI does)
107
171
  conversation.UserID = contextUser.ID;
108
172
  conversation.Status = 'Available';
109
- conversation.DataContextID = null;
173
+ conversation.DataContextID = null; // Can be set by caller if needed
174
+ // Link to test run if provided (for test execution traceability)
110
175
  if (options.testRunId) {
111
176
  conversation.TestRunID = options.testRunId;
112
177
  }
@@ -114,15 +179,17 @@ class AgentRunner {
114
179
  throw new Error('Failed to create conversation');
115
180
  }
116
181
  conversationId = conversation.ID;
117
- (0, core_1.LogStatus)(`Created conversation ${conversationId}: ${conversationName}`);
182
+ LogStatus(`Created conversation ${conversationId}: ${conversationName}`);
118
183
  }
119
- (0, core_1.LogStatus)('Creating conversation detail for user message');
184
+ // Step 2: Create conversation detail for user message
185
+ LogStatus('Creating conversation detail for user message');
120
186
  const userMessageDetail = await md.GetEntityObject('Conversation Details', contextUser);
121
187
  userMessageDetail.ConversationID = conversationId;
122
188
  userMessageDetail.Message = options.userMessage;
123
189
  userMessageDetail.Role = 'User';
124
190
  userMessageDetail.UserID = contextUser.ID;
125
191
  userMessageDetail.HiddenToUser = false;
192
+ // Link to test run if provided (for test execution traceability)
126
193
  if (options.testRunId) {
127
194
  userMessageDetail.TestRunID = options.testRunId;
128
195
  }
@@ -130,8 +197,9 @@ class AgentRunner {
130
197
  throw new Error('Failed to create user message conversation detail');
131
198
  }
132
199
  userMessageDetailId = userMessageDetail.ID;
133
- (0, core_1.LogStatus)(`Created user message detail ${userMessageDetailId}`);
134
- (0, core_1.LogStatus)('Creating conversation detail for agent response');
200
+ LogStatus(`Created user message detail ${userMessageDetailId}`);
201
+ // Step 3: Create conversation detail for agent response (like UI does)
202
+ LogStatus('Creating conversation detail for agent response');
135
203
  agentResponseDetail = await md.GetEntityObject('Conversation Details', contextUser);
136
204
  agentResponseDetail.ConversationID = conversationId;
137
205
  agentResponseDetail.Message = '⏳ Starting...';
@@ -139,6 +207,7 @@ class AgentRunner {
139
207
  agentResponseDetail.Status = 'In-Progress';
140
208
  agentResponseDetail.HiddenToUser = false;
141
209
  agentResponseDetail.AgentID = params.agent.ID;
210
+ // Link to test run if provided (for test execution traceability)
142
211
  if (options.testRunId) {
143
212
  agentResponseDetail.TestRunID = options.testRunId;
144
213
  }
@@ -146,23 +215,32 @@ class AgentRunner {
146
215
  throw new Error('Failed to create agent response conversation detail');
147
216
  }
148
217
  agentResponseDetailId = agentResponseDetail.ID;
149
- (0, core_1.LogStatus)(`Created agent response detail ${agentResponseDetailId}`);
218
+ LogStatus(`Created agent response detail ${agentResponseDetailId}`);
150
219
  }
151
- (0, core_1.LogStatus)(`Executing agent ${params.agent.Name} in conversation context`);
220
+ // Step 4: Execute the agent with conversation context
221
+ LogStatus(`Executing agent ${params.agent.Name} in conversation context`);
222
+ // Flag to prevent late progress callbacks from overwriting final status
223
+ // Progress callbacks fire async and may complete AFTER agent returns
152
224
  let agentExecutionCompleted = false;
225
+ // Wrap progress callback to update agent response detail
153
226
  const originalOnProgress = params.onProgress;
154
227
  const wrappedOnProgress = agentResponseDetail
155
228
  ? async (progress) => {
229
+ // Skip DB save if agent already completed - prevents race condition
230
+ // where late progress overwrites final Status='Complete'
156
231
  if (agentExecutionCompleted) {
232
+ // Still call original callback for PubSub streaming
157
233
  if (originalOnProgress) {
158
234
  await originalOnProgress(progress);
159
235
  }
160
236
  return;
161
237
  }
238
+ // Update the agent response detail with progress message
162
239
  if (agentResponseDetail && progress.message) {
163
240
  agentResponseDetail.Message = progress.message;
164
241
  await agentResponseDetail.Save();
165
242
  }
243
+ // Call original callback if provided
166
244
  if (originalOnProgress) {
167
245
  await originalOnProgress(progress);
168
246
  }
@@ -170,19 +248,26 @@ class AgentRunner {
170
248
  : originalOnProgress;
171
249
  const modifiedParams = {
172
250
  ...params,
173
- data: { ...params.data, conversationId },
251
+ data: { ...params.data, conversationId }, // ensure we pass along OUR conversationId
174
252
  conversationDetailId: agentResponseDetailId,
175
253
  onProgress: wrappedOnProgress
176
254
  };
177
255
  const agentResult = await this.RunAgent(modifiedParams);
256
+ // Mark execution as completed to stop progress saves
178
257
  agentExecutionCompleted = true;
258
+ // Step 5: Update agent response detail with final result
259
+ // ALWAYS update status - don't rely on frontend (browser may refresh during execution)
179
260
  if (agentResponseDetail && agentResponseDetailId) {
261
+ // Wait for any in-flight progress save to complete
262
+ // EnsureSaveComplete() resolves immediately if no save in progress
180
263
  await agentResponseDetail.EnsureSaveComplete();
181
- (0, core_1.LogStatus)('Updating agent response detail with final result');
264
+ LogStatus('Updating agent response detail with final result');
265
+ // Reload to get any updates from agent execution
182
266
  await agentResponseDetail.Load(agentResponseDetailId);
183
267
  agentResponseDetail.Message = agentResult.agentRun?.Message ||
184
268
  (agentResult.success ? '✅ Completed' : '❌ Failed');
185
269
  agentResponseDetail.Status = agentResult.success ? 'Complete' : 'Error';
270
+ // Set response form and command fields
186
271
  if (agentResult.responseForm) {
187
272
  agentResponseDetail.ResponseForm = JSON.stringify(agentResult.responseForm);
188
273
  }
@@ -193,20 +278,28 @@ class AgentRunner {
193
278
  agentResponseDetail.AutomaticCommands = JSON.stringify(agentResult.automaticCommands);
194
279
  }
195
280
  await agentResponseDetail.Save();
196
- (0, core_1.LogStatus)(`Updated agent response detail ${agentResponseDetailId} with final status: ${agentResponseDetail.Status}`);
281
+ LogStatus(`Updated agent response detail ${agentResponseDetailId} with final status: ${agentResponseDetail.Status}`);
197
282
  }
283
+ // Step 6: Process artifacts if requested and agent succeeded
198
284
  let artifactInfo;
199
- const shouldCreateArtifacts = options.createArtifacts !== false;
285
+ const shouldCreateArtifacts = options.createArtifacts !== false; // Default true
200
286
  if (shouldCreateArtifacts && agentResult.success && agentResult.payload) {
287
+ // Artifacts link to agent response detail ID
201
288
  artifactInfo = await this.ProcessAgentArtifacts(agentResult, agentResponseDetailId, options.sourceArtifactId, contextUser);
202
289
  }
290
+ // Step 7: Save media outputs to AIAgentRunMedia and create conversation attachments
203
291
  let mediaIds = [];
204
292
  if (agentResult.mediaOutputs && agentResult.mediaOutputs.length > 0) {
293
+ // Filter to only media that should be persisted (persist !== false)
205
294
  const mediaToSave = agentResult.mediaOutputs.filter(m => m.persist !== false);
206
- (0, core_1.LogStatus)(`Processing ${mediaToSave.length} of ${agentResult.mediaOutputs.length} media outputs (filtered by persist flag)`);
207
- mediaIds = await this.SaveAgentRunMedia(agentResult.agentRun.ID, mediaToSave, contextUser);
295
+ LogStatus(`Processing ${mediaToSave.length} of ${agentResult.mediaOutputs.length} media outputs (filtered by persist flag)`);
296
+ // Save to AIAgentRunMedia for permanent storage
297
+ mediaIds = await this.SaveAgentRunMedia(agentResult.agentRun.ID, mediaToSave, // Pass filtered array
298
+ contextUser);
299
+ // Create ConversationDetailAttachment records for UI display
208
300
  if (agentResponseDetailId && mediaIds.length > 0) {
209
- await this.CreateConversationMediaAttachments(agentResponseDetailId, mediaToSave, mediaIds, contextUser);
301
+ await this.CreateConversationMediaAttachments(agentResponseDetailId, mediaToSave, // Pass same filtered array to keep indices aligned
302
+ mediaIds, contextUser);
210
303
  }
211
304
  }
212
305
  return {
@@ -219,13 +312,28 @@ class AgentRunner {
219
312
  }
220
313
  catch (error) {
221
314
  const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
222
- (0, core_1.LogError)(`RunAgentInConversation failed: ${errorMessage}`, undefined, error);
315
+ LogError(`RunAgentInConversation failed: ${errorMessage}`, undefined, error);
223
316
  throw error;
224
317
  }
225
318
  }
319
+ /**
320
+ * Gets the maximum version number for an artifact.
321
+ * Used when creating new versions of explicitly specified artifacts.
322
+ *
323
+ * @param artifactId - The artifact ID to query
324
+ * @param contextUser - The user context for the query
325
+ * @returns The maximum version number, or 0 if no versions exist
326
+ *
327
+ * @example
328
+ * ```typescript
329
+ * const runner = new AgentRunner();
330
+ * const maxVersion = await runner.GetMaxVersionForArtifact(artifactId, currentUser);
331
+ * const newVersionNumber = maxVersion + 1;
332
+ * ```
333
+ */
226
334
  async GetMaxVersionForArtifact(artifactId, contextUser) {
227
335
  try {
228
- const rv = new core_1.RunView();
336
+ const rv = new RunView();
229
337
  const result = await rv.RunView({
230
338
  EntityName: 'MJ: Artifact Versions',
231
339
  ExtraFilter: `ArtifactID='${artifactId}'`,
@@ -239,13 +347,30 @@ class AgentRunner {
239
347
  return 0;
240
348
  }
241
349
  catch (error) {
242
- (0, core_1.LogError)(`Error getting max version for artifact: ${error.message}`);
350
+ LogError(`Error getting max version for artifact: ${error.message}`);
243
351
  return 0;
244
352
  }
245
353
  }
354
+ /**
355
+ * Finds the most recent artifact for a conversation detail to determine versioning.
356
+ * Queries the junction table to locate artifacts linked to a specific conversation message.
357
+ *
358
+ * @param conversationDetailId - The conversation detail ID to query
359
+ * @param contextUser - The user context for the query
360
+ * @returns Artifact info if exists, null if this is the first artifact for this message
361
+ *
362
+ * @example
363
+ * ```typescript
364
+ * const runner = new AgentRunner();
365
+ * const previousArtifact = await runner.FindPreviousArtifactForMessage(detailId, currentUser);
366
+ * if (previousArtifact) {
367
+ * console.log(`Found artifact ${previousArtifact.artifactId} at version ${previousArtifact.versionNumber}`);
368
+ * }
369
+ * ```
370
+ */
246
371
  async FindPreviousArtifactForMessage(conversationDetailId, contextUser) {
247
372
  try {
248
- const rv = new core_1.RunView();
373
+ const rv = new RunView();
249
374
  const result = await rv.RunView({
250
375
  EntityName: 'MJ: Conversation Detail Artifacts',
251
376
  ExtraFilter: `ConversationDetailID='${conversationDetailId}' AND Direction='Output'`,
@@ -257,7 +382,7 @@ class AgentRunner {
257
382
  return null;
258
383
  }
259
384
  const junction = result.Results[0];
260
- const md = new core_1.Metadata();
385
+ const md = new Metadata();
261
386
  const version = await md.GetEntityObject('MJ: Artifact Versions', contextUser);
262
387
  if (!(await version.Load(junction.ArtifactVersionID))) {
263
388
  return null;
@@ -268,56 +393,98 @@ class AgentRunner {
268
393
  };
269
394
  }
270
395
  catch (error) {
271
- (0, core_1.LogError)(`Error finding previous artifact: ${error.message}`);
396
+ LogError(`Error finding previous artifact: ${error.message}`);
272
397
  return null;
273
398
  }
274
399
  }
400
+ /**
401
+ * Processes agent completion to create artifacts from the agent's payload.
402
+ * Handles artifact creation, versioning, and linking to conversation details.
403
+ *
404
+ * This method implements intelligent artifact versioning:
405
+ * 1. If sourceArtifactId is provided (explicit continuity), creates new version of that artifact
406
+ * 2. Otherwise, checks for previous artifacts on this conversation detail
407
+ * 3. If previous artifact exists, creates new version of it
408
+ * 4. If no previous artifact, creates entirely new artifact
409
+ *
410
+ * Respects the agent's ArtifactCreationMode configuration:
411
+ * - "Never": Skips artifact creation entirely
412
+ * - "System Only": Creates artifact with Visibility='System Only'
413
+ * - Other modes: Creates artifact with Visibility='Always'
414
+ *
415
+ * @param agentResult - The result from agent execution containing the payload
416
+ * @param conversationDetailId - The conversation detail to link the artifact to
417
+ * @param sourceArtifactId - Optional explicit artifact to version from (agent continuity)
418
+ * @param contextUser - The user context for the operation
419
+ * @returns Artifact metadata if created, undefined if skipped or failed
420
+ *
421
+ * @example
422
+ * ```typescript
423
+ * const runner = new AgentRunner();
424
+ * const artifactInfo = await runner.ProcessAgentArtifacts(
425
+ * agentResult,
426
+ * conversationDetailId,
427
+ * sourceArtifactId, // Optional
428
+ * currentUser
429
+ * );
430
+ * if (artifactInfo) {
431
+ * console.log(`Created artifact ${artifactInfo.artifactId} version ${artifactInfo.versionNumber}`);
432
+ * }
433
+ * ```
434
+ */
275
435
  async ProcessAgentArtifacts(agentResult, conversationDetailId, sourceArtifactId, contextUser) {
276
436
  const payload = agentResult.payload;
277
437
  const agentRun = agentResult.agentRun;
278
438
  if (!payload || Object.keys(payload).length === 0) {
279
- (0, core_1.LogStatus)('No payload to create artifact from');
439
+ LogStatus('No payload to create artifact from');
280
440
  return undefined;
281
441
  }
282
- await aiengine_1.AIEngine.Instance.Config(false, contextUser);
283
- const agent = aiengine_1.AIEngine.Instance.Agents.find(a => a.ID === agentRun.AgentID);
442
+ // Check agent's ArtifactCreationMode
443
+ await AIEngine.Instance.Config(false, contextUser);
444
+ const agent = AIEngine.Instance.Agents.find(a => a.ID === agentRun.AgentID);
284
445
  const creationMode = agent?.ArtifactCreationMode;
285
446
  if (creationMode === 'Never') {
286
- (0, core_1.LogStatus)(`Skipping artifact creation - agent "${agent?.Name}" has ArtifactCreationMode='Never'`);
447
+ LogStatus(`Skipping artifact creation - agent "${agent?.Name}" has ArtifactCreationMode='Never'`);
287
448
  return undefined;
288
449
  }
289
450
  try {
290
- const md = new core_1.Metadata();
451
+ const md = new Metadata();
291
452
  const JSON_ARTIFACT_TYPE_ID = 'ae674c7e-ea0d-49ea-89e4-0649f5eb20d4';
453
+ // Determine if creating new artifact or new version
292
454
  let artifactId;
293
455
  let newVersionNumber;
294
456
  let isNewArtifact = false;
457
+ // Priority 1: Use explicit source artifact if provided
295
458
  if (sourceArtifactId) {
296
459
  const maxVersion = await this.GetMaxVersionForArtifact(sourceArtifactId, contextUser);
297
460
  artifactId = sourceArtifactId;
298
461
  newVersionNumber = maxVersion + 1;
299
- (0, core_1.LogStatus)(`Creating version ${newVersionNumber} of source artifact ${artifactId}`);
462
+ LogStatus(`Creating version ${newVersionNumber} of source artifact ${artifactId}`);
300
463
  }
464
+ // Priority 2: Try to find previous artifact for this message
301
465
  else {
302
466
  const previousArtifact = await this.FindPreviousArtifactForMessage(conversationDetailId, contextUser);
303
467
  if (previousArtifact) {
304
468
  artifactId = previousArtifact.artifactId;
305
469
  newVersionNumber = previousArtifact.versionNumber + 1;
306
- (0, core_1.LogStatus)(`Creating version ${newVersionNumber} of existing artifact ${artifactId}`);
470
+ LogStatus(`Creating version ${newVersionNumber} of existing artifact ${artifactId}`);
307
471
  }
308
472
  else {
473
+ // Create new artifact header
309
474
  const artifact = await md.GetEntityObject('MJ: Artifacts', contextUser);
310
475
  const agentName = agent?.Name || 'Agent';
311
476
  artifact.Name = `${agentName} Payload - ${new Date().toLocaleString()}`;
312
477
  artifact.Description = `Payload returned by ${agentName}`;
478
+ // Use agent's DefaultArtifactTypeID if available
313
479
  const defaultArtifactTypeId = agent?.DefaultArtifactTypeID;
314
480
  artifact.TypeID = defaultArtifactTypeId || JSON_ARTIFACT_TYPE_ID;
315
481
  artifact.UserID = contextUser.ID;
316
482
  artifact.EnvironmentID = contextUser.EnvironmentID ||
317
483
  'F51358F3-9447-4176-B313-BF8025FD8D09';
484
+ // Set visibility based on agent's ArtifactCreationMode
318
485
  if (creationMode === 'System Only') {
319
486
  artifact.Visibility = 'System Only';
320
- (0, core_1.LogStatus)(`Artifact marked as "System Only" per agent configuration`);
487
+ LogStatus(`Artifact marked as "System Only" per agent configuration`);
321
488
  }
322
489
  else {
323
490
  artifact.Visibility = 'Always';
@@ -328,9 +495,10 @@ class AgentRunner {
328
495
  artifactId = artifact.ID;
329
496
  newVersionNumber = 1;
330
497
  isNewArtifact = true;
331
- (0, core_1.LogStatus)(`Created new artifact: ${artifact.Name} (${artifactId})`);
498
+ LogStatus(`Created new artifact: ${artifact.Name} (${artifactId})`);
332
499
  }
333
500
  }
501
+ // Create artifact version with content
334
502
  const version = await md.GetEntityObject('MJ: Artifact Versions', contextUser);
335
503
  version.ArtifactID = artifactId;
336
504
  version.VersionNumber = newVersionNumber;
@@ -339,7 +507,8 @@ class AgentRunner {
339
507
  if (!(await version.Save())) {
340
508
  throw new Error('Failed to save artifact version');
341
509
  }
342
- (0, core_1.LogStatus)(`Created artifact version ${newVersionNumber} (${version.ID})`);
510
+ LogStatus(`Created artifact version ${newVersionNumber} (${version.ID})`);
511
+ // If first version of new artifact, check for extracted Name attribute
343
512
  if (isNewArtifact && newVersionNumber === 1) {
344
513
  const nameAttr = version.Attributes?.find((attr) => attr.StandardProperty === 'name' || attr.Name?.toLowerCase() === 'name');
345
514
  let extractedName = nameAttr?.Value?.trim();
@@ -349,11 +518,12 @@ class AgentRunner {
349
518
  if (await artifact.Load(artifactId)) {
350
519
  artifact.Name = extractedName;
351
520
  if (await artifact.Save()) {
352
- (0, core_1.LogStatus)(`Updated artifact name to: ${artifact.Name}`);
521
+ LogStatus(`Updated artifact name to: ${artifact.Name}`);
353
522
  }
354
523
  }
355
524
  }
356
525
  }
526
+ // Create junction record linking artifact to conversation detail
357
527
  const junction = await md.GetEntityObject('MJ: Conversation Detail Artifacts', contextUser);
358
528
  junction.ConversationDetailID = conversationDetailId;
359
529
  junction.ArtifactVersionID = version.ID;
@@ -361,7 +531,7 @@ class AgentRunner {
361
531
  if (!(await junction.Save())) {
362
532
  throw new Error('Failed to create artifact-message association');
363
533
  }
364
- (0, core_1.LogStatus)(`Linked artifact to conversation detail ${conversationDetailId}`);
534
+ LogStatus(`Linked artifact to conversation detail ${conversationDetailId}`);
365
535
  return {
366
536
  artifactId,
367
537
  versionId: version.ID,
@@ -369,21 +539,35 @@ class AgentRunner {
369
539
  };
370
540
  }
371
541
  catch (error) {
372
- (0, core_1.LogError)(`Failed to process agent artifacts: ${error.message}`);
542
+ LogError(`Failed to process agent artifacts: ${error.message}`);
373
543
  return undefined;
374
544
  }
375
545
  }
546
+ /**
547
+ * Generates a conversation name and description using the "Name Conversation" AI prompt.
548
+ * Same approach as UI - uses AI to create a meaningful title and description from the first user message.
549
+ * Falls back to null if prompt not found or execution fails (caller will use fallback).
550
+ *
551
+ * @param userMessage - The first user message to base the name on
552
+ * @param contextUser - User context for prompt execution
553
+ * @returns Generated conversation name and description, or null if generation failed
554
+ * @private
555
+ */
376
556
  async GenerateConversationName(userMessage, contextUser) {
377
557
  try {
378
- const { AIPromptRunner } = await Promise.resolve().then(() => __importStar(require('@memberjunction/ai-prompts')));
379
- const { AIPromptParams } = await Promise.resolve().then(() => __importStar(require('@memberjunction/ai-core-plus')));
380
- const { AIEngine } = await Promise.resolve().then(() => __importStar(require('@memberjunction/aiengine')));
558
+ // Import AIPromptRunner, AIPromptParams, and AIEngine
559
+ const { AIPromptRunner } = await import('@memberjunction/ai-prompts');
560
+ const { AIPromptParams } = await import('@memberjunction/ai-core-plus');
561
+ const { AIEngine } = await import('@memberjunction/aiengine');
562
+ // Ensure AIEngine is configured
381
563
  await AIEngine.Instance.Config(false, contextUser);
564
+ // Find the "Name Conversation" prompt (same as UI)
382
565
  const prompt = AIEngine.Instance.Prompts.find(p => p.Name === 'Name Conversation');
383
566
  if (!prompt) {
384
- (0, core_1.LogStatus)('Name Conversation prompt not found - using fallback naming');
567
+ LogStatus('Name Conversation prompt not found - using fallback naming');
385
568
  return null;
386
569
  }
570
+ // Execute the prompt using AIPromptRunner
387
571
  const promptParams = new AIPromptParams();
388
572
  promptParams.prompt = prompt;
389
573
  promptParams.contextUser = contextUser;
@@ -391,12 +575,13 @@ class AgentRunner {
391
575
  const runner = new AIPromptRunner();
392
576
  const result = await runner.ExecutePrompt(promptParams);
393
577
  if (result && result.success && result.result) {
578
+ // Try to parse the result as JSON to extract name and description
394
579
  const parsed = typeof result.result === 'string'
395
580
  ? JSON.parse(result.result)
396
581
  : result.result;
397
582
  const { name, description } = parsed;
398
583
  if (name) {
399
- (0, core_1.LogStatus)(`Generated conversation name via AI: "${name}" with description: "${description || ''}"`);
584
+ LogStatus(`Generated conversation name via AI: "${name}" with description: "${description || ''}"`);
400
585
  return {
401
586
  name,
402
587
  description: description || ''
@@ -406,46 +591,77 @@ class AgentRunner {
406
591
  return null;
407
592
  }
408
593
  catch (error) {
409
- (0, core_1.LogError)(`Error generating conversation name: ${error.message}`);
594
+ LogError(`Error generating conversation name: ${error.message}`);
410
595
  return null;
411
596
  }
412
597
  }
598
+ /**
599
+ * Saves media outputs to AIAgentRunMedia table for permanent storage.
600
+ * This creates records for each media output promoted during agent execution.
601
+ *
602
+ * Only media with `persist !== false` is saved. Media items with `persist: false`
603
+ * are typically intercepted binary content that was never used in the final output.
604
+ *
605
+ * @param agentRunId - The ID of the agent run
606
+ * @param mediaOutputs - Array of media outputs to save
607
+ * @param contextUser - User context for the operation
608
+ * @returns Array of saved AIAgentRunMedia IDs
609
+ * @since 3.1.0
610
+ *
611
+ * @example
612
+ * ```typescript
613
+ * const runner = new AgentRunner();
614
+ * const mediaIds = await runner.SaveAgentRunMedia(
615
+ * agentResult.agentRun.ID,
616
+ * agentResult.mediaOutputs,
617
+ * currentUser
618
+ * );
619
+ * console.log(`Saved ${mediaIds.length} media outputs`);
620
+ * ```
621
+ */
413
622
  async SaveAgentRunMedia(agentRunId, mediaOutputs, contextUser) {
414
623
  if (!mediaOutputs || mediaOutputs.length === 0) {
415
624
  return [];
416
625
  }
626
+ // Filter to only persist media that should be saved
627
+ // persist=false means intercepted but unused binary content (e.g., images not used in response)
417
628
  const mediaToSave = mediaOutputs.filter(m => m.persist !== false);
418
629
  if (mediaToSave.length === 0) {
419
- (0, core_1.LogStatus)(`All ${mediaOutputs.length} media outputs have persist=false, skipping save`);
630
+ LogStatus(`All ${mediaOutputs.length} media outputs have persist=false, skipping save`);
420
631
  return [];
421
632
  }
422
633
  if (mediaToSave.length < mediaOutputs.length) {
423
- (0, core_1.LogStatus)(`Filtering: ${mediaToSave.length} of ${mediaOutputs.length} media outputs will be persisted`);
634
+ LogStatus(`Filtering: ${mediaToSave.length} of ${mediaOutputs.length} media outputs will be persisted`);
424
635
  }
425
636
  const savedIds = [];
426
- const md = new core_1.Metadata();
637
+ const md = new Metadata();
427
638
  try {
428
- const aiEngine = aiengine_1.AIEngine.Instance;
639
+ // Use AIEngine's cached modalities instead of a fresh DB call
640
+ const aiEngine = AIEngine.Instance;
429
641
  for (let i = 0; i < mediaToSave.length; i++) {
430
642
  const mediaOutput = mediaToSave[i];
431
643
  try {
432
644
  const mediaEntity = await md.GetEntityObject('MJ: AI Agent Run Medias', contextUser);
433
645
  mediaEntity.AgentRunID = agentRunId;
646
+ // Link to source prompt run media if this was promoted
434
647
  if (mediaOutput.promptRunMediaId) {
435
648
  mediaEntity.SourcePromptRunMediaID = mediaOutput.promptRunMediaId;
436
649
  }
650
+ // Get modality ID from cached engine data
437
651
  const modality = aiEngine.GetModalityByName(mediaOutput.modality);
438
652
  if (modality) {
439
653
  mediaEntity.ModalityID = modality.ID;
440
654
  }
441
655
  else {
442
- (0, core_1.LogError)(`Unknown modality: ${mediaOutput.modality}`);
656
+ LogError(`Unknown modality: ${mediaOutput.modality}`);
443
657
  continue;
444
658
  }
445
659
  mediaEntity.MimeType = mediaOutput.mimeType;
660
+ // Only store inline data if NOT from prompt run media
446
661
  if (!mediaOutput.promptRunMediaId && mediaOutput.data) {
447
662
  mediaEntity.InlineData = mediaOutput.data;
448
663
  }
664
+ // Set dimensions if available
449
665
  if (mediaOutput.width) {
450
666
  mediaEntity.Width = mediaOutput.width;
451
667
  }
@@ -455,12 +671,14 @@ class AgentRunner {
455
671
  if (mediaOutput.durationSeconds) {
456
672
  mediaEntity.DurationSeconds = mediaOutput.durationSeconds;
457
673
  }
674
+ // Set label and metadata
458
675
  if (mediaOutput.label) {
459
676
  mediaEntity.Label = mediaOutput.label;
460
677
  }
461
678
  if (mediaOutput.metadata) {
462
679
  mediaEntity.Metadata = JSON.stringify(mediaOutput.metadata);
463
680
  }
681
+ // Set description if available
464
682
  if (mediaOutput.description) {
465
683
  mediaEntity.Description = mediaOutput.description;
466
684
  }
@@ -468,37 +686,60 @@ class AgentRunner {
468
686
  const saved = await mediaEntity.Save();
469
687
  if (saved) {
470
688
  savedIds.push(mediaEntity.ID);
471
- (0, core_1.LogStatus)(`Saved AIAgentRunMedia: ${mediaEntity.ID} (${mediaOutput.modality})`);
689
+ LogStatus(`Saved AIAgentRunMedia: ${mediaEntity.ID} (${mediaOutput.modality})`);
472
690
  }
473
691
  else {
474
- (0, core_1.LogError)(`Failed to save AIAgentRunMedia: ${mediaEntity.LatestResult?.Message}`);
692
+ LogError(`Failed to save AIAgentRunMedia: ${mediaEntity.LatestResult?.Message}`);
475
693
  }
476
694
  }
477
695
  catch (mediaError) {
478
- (0, core_1.LogError)(`Error saving media output ${i}: ${mediaError.message}`);
696
+ LogError(`Error saving media output ${i}: ${mediaError.message}`);
479
697
  }
480
698
  }
481
- (0, core_1.LogStatus)(`Saved ${savedIds.length} of ${mediaToSave.length} media outputs for agent run ${agentRunId}`);
699
+ LogStatus(`Saved ${savedIds.length} of ${mediaToSave.length} media outputs for agent run ${agentRunId}`);
482
700
  return savedIds;
483
701
  }
484
702
  catch (error) {
485
- (0, core_1.LogError)(`Error in SaveAgentRunMedia: ${error.message}`);
703
+ LogError(`Error in SaveAgentRunMedia: ${error.message}`);
486
704
  return savedIds;
487
705
  }
488
706
  }
707
+ /**
708
+ * Creates ConversationDetailAttachment records for media outputs.
709
+ * This enables media to be displayed in the conversation UI.
710
+ *
711
+ * @param conversationDetailId - The conversation detail to attach media to
712
+ * @param mediaOutputs - Array of media outputs
713
+ * @param agentRunMediaIds - Corresponding AIAgentRunMedia IDs
714
+ * @param contextUser - User context for the operation
715
+ * @returns Array of created attachment IDs
716
+ * @since 3.1.0
717
+ *
718
+ * @example
719
+ * ```typescript
720
+ * const runner = new AgentRunner();
721
+ * const attachmentIds = await runner.CreateConversationMediaAttachments(
722
+ * conversationDetailId,
723
+ * mediaOutputs,
724
+ * agentRunMediaIds,
725
+ * currentUser
726
+ * );
727
+ * ```
728
+ */
489
729
  async CreateConversationMediaAttachments(conversationDetailId, mediaOutputs, agentRunMediaIds, contextUser) {
490
730
  if (!mediaOutputs || mediaOutputs.length === 0) {
491
731
  return [];
492
732
  }
493
733
  const attachmentIds = [];
494
- const md = new core_1.Metadata();
734
+ const md = new Metadata();
495
735
  try {
496
- const aiEngine = aiengine_1.AIEngine.Instance;
736
+ // Use AIEngine's cached modalities instead of a fresh DB call
737
+ const aiEngine = AIEngine.Instance;
497
738
  for (let i = 0; i < mediaOutputs.length; i++) {
498
739
  const mediaOutput = mediaOutputs[i];
499
740
  const agentRunMediaId = agentRunMediaIds[i];
500
741
  if (!agentRunMediaId) {
501
- (0, core_1.LogError)(`No AIAgentRunMedia ID for media output ${i}`);
742
+ LogError(`No AIAgentRunMedia ID for media output ${i}`);
502
743
  continue;
503
744
  }
504
745
  try {
@@ -506,20 +747,26 @@ class AgentRunner {
506
747
  attachment.ConversationDetailID = conversationDetailId;
507
748
  attachment.MimeType = mediaOutput.mimeType;
508
749
  attachment.DisplayOrder = i;
750
+ // Get modality ID from cached engine data
509
751
  const modality = aiEngine.GetModalityByName(mediaOutput.modality);
510
752
  if (modality) {
511
753
  attachment.ModalityID = modality.ID;
512
754
  }
513
755
  else {
514
- (0, core_1.LogError)(`Unknown modality: ${mediaOutput.modality}`);
756
+ LogError(`Unknown modality: ${mediaOutput.modality}`);
515
757
  continue;
516
758
  }
759
+ // Generate a filename if none provided
517
760
  const extension = this.getMimeTypeExtension(mediaOutput.mimeType);
518
761
  attachment.FileName = mediaOutput.label || `${mediaOutput.modality.toLowerCase()}_${i + 1}.${extension}`;
762
+ // Store inline data for small media
763
+ // In the future, consider MJStorage for large files
519
764
  if (mediaOutput.data) {
520
765
  attachment.InlineData = mediaOutput.data;
766
+ // Estimate file size from base64 length (base64 is ~33% larger than binary)
521
767
  attachment.FileSizeBytes = Math.ceil(mediaOutput.data.length * 0.75);
522
768
  }
769
+ // Set dimensions if available
523
770
  if (mediaOutput.width) {
524
771
  attachment.Width = mediaOutput.width;
525
772
  }
@@ -529,30 +776,35 @@ class AgentRunner {
529
776
  if (mediaOutput.durationSeconds) {
530
777
  attachment.DurationSeconds = Math.ceil(mediaOutput.durationSeconds);
531
778
  }
779
+ // Set description if available
532
780
  if (mediaOutput.description) {
533
781
  attachment.Description = mediaOutput.description;
534
782
  }
535
783
  const saved = await attachment.Save();
536
784
  if (saved) {
537
785
  attachmentIds.push(attachment.ID);
538
- (0, core_1.LogStatus)(`Created ConversationDetailAttachment: ${attachment.ID} (${mediaOutput.modality})`);
786
+ LogStatus(`Created ConversationDetailAttachment: ${attachment.ID} (${mediaOutput.modality})`);
539
787
  }
540
788
  else {
541
- (0, core_1.LogError)(`Failed to create attachment: ${attachment.LatestResult?.Message}`);
789
+ LogError(`Failed to create attachment: ${attachment.LatestResult?.Message}`);
542
790
  }
543
791
  }
544
792
  catch (attachError) {
545
- (0, core_1.LogError)(`Error creating attachment ${i}: ${attachError.message}`);
793
+ LogError(`Error creating attachment ${i}: ${attachError.message}`);
546
794
  }
547
795
  }
548
- (0, core_1.LogStatus)(`Created ${attachmentIds.length} conversation attachments for detail ${conversationDetailId}`);
796
+ LogStatus(`Created ${attachmentIds.length} conversation attachments for detail ${conversationDetailId}`);
549
797
  return attachmentIds;
550
798
  }
551
799
  catch (error) {
552
- (0, core_1.LogError)(`Error in CreateConversationMediaAttachments: ${error.message}`);
800
+ LogError(`Error in CreateConversationMediaAttachments: ${error.message}`);
553
801
  return attachmentIds;
554
802
  }
555
803
  }
804
+ /**
805
+ * Gets file extension from MIME type
806
+ * @private
807
+ */
556
808
  getMimeTypeExtension(mimeType) {
557
809
  const mimeToExt = {
558
810
  'image/png': 'png',
@@ -571,5 +823,4 @@ class AgentRunner {
571
823
  return mimeToExt[mimeType.toLowerCase()] || 'bin';
572
824
  }
573
825
  }
574
- exports.AgentRunner = AgentRunner;
575
826
  //# sourceMappingURL=AgentRunner.js.map