@memberjunction/actions 2.51.0 → 2.53.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.
@@ -13,7 +13,8 @@ exports.LoadActionEntityServer = exports.ActionEntityServerEntity = void 0;
13
13
  const core_1 = require("@memberjunction/core");
14
14
  const global_1 = require("@memberjunction/global");
15
15
  const aiengine_1 = require("@memberjunction/aiengine");
16
- const ai_1 = require("@memberjunction/ai");
16
+ //import { SQLServerDataProvider } from "@memberjunction/sqlserver-dataprovider";
17
+ const ai_prompts_1 = require("@memberjunction/ai-prompts");
17
18
  const doc_utils_1 = require("@memberjunction/doc-utils");
18
19
  const actions_base_1 = require("@memberjunction/actions-base");
19
20
  /**
@@ -43,36 +44,66 @@ let ActionEntityServerEntity = class ActionEntityServerEntity extends actions_ba
43
44
  // make sure the ActionEngine is configured
44
45
  await actions_base_1.ActionEngineBase.Instance.Config(false, this.ContextCurrentUser);
45
46
  await doc_utils_1.DocumentationEngine.Instance.Config(false, this.ContextCurrentUser);
46
- let newCodeGenerated = false;
47
- let codeLibraries = [];
48
- if (this.Type === 'Generated' && // only generate when the type is Generated
49
- !this.CodeLocked && // only generate when the code is not locked
50
- (this.GetFieldByName('UserPrompt').Dirty || !this.IsSaved || this.ForceCodeGeneration) // only generate when the UserPrompt field is dirty or this is a new record or we are being asked to FORCE code generation
51
- ) {
52
- // UserPrompt field is dirty, or this is a new record, either way, this is the condition where we want to generate the Code.
53
- const result = await this.GenerateCode();
54
- if (result.Success) {
55
- this.Code = result.Code;
56
- this.CodeComments = result.Comments;
57
- this.CodeApprovalStatus = 'Pending'; // set to pending even if previously approved since we changed the code
58
- this.CodeApprovedAt = null; // reset the approved at date
59
- this.CodeApprovedByUserID = null; // reset the approved by user id
60
- newCodeGenerated = true; // flag for post-save processing of libraries
61
- codeLibraries = result.LibrariesUsed;
47
+ //const provider = Metadata.Provider as SQLServerDataProvider;
48
+ // Start a database transaction
49
+ //FIX: await provider.BeginTransaction();
50
+ try {
51
+ let newCodeGenerated = false;
52
+ let codeLibraries = [];
53
+ let generatedParameters = [];
54
+ let generatedResultCodes = [];
55
+ if (this.Type === 'Generated' && // only generate when the type is Generated
56
+ !this.CodeLocked && // only generate when the code is not locked
57
+ (this.GetFieldByName('UserPrompt').Dirty || !this.IsSaved || this.ForceCodeGeneration) // only generate when the UserPrompt field is dirty or this is a new record or we are being asked to FORCE code generation
58
+ ) {
59
+ // UserPrompt field is dirty, or this is a new record, either way, this is the condition where we want to generate the Code.
60
+ const result = await this.GenerateCode();
61
+ if (result.Success) {
62
+ this.Code = result.Code;
63
+ this.CodeComments = result.Comments;
64
+ this.CodeApprovalStatus = 'Pending'; // set to pending even if previously approved since we changed the code
65
+ this.CodeApprovedAt = null; // reset the approved at date
66
+ this.CodeApprovedByUserID = null; // reset the approved by user id
67
+ newCodeGenerated = true; // flag for post-save processing of libraries
68
+ codeLibraries = result.LibrariesUsed;
69
+ // Store the generated parameters and result codes for later processing
70
+ generatedParameters = result.Parameters || [];
71
+ generatedResultCodes = result.ResultCodes || [];
72
+ }
73
+ else
74
+ throw new Error(`Failed to generate code for Action ${this.Name}.`);
62
75
  }
63
- else
64
- throw new Error(`Failed to generate code for Action ${this.Name}.`);
65
- }
66
- this.ForceCodeGeneration = false; // make sure to reset this flag every time we save, it should never live past one run of the Save method, of course if Save fails, below, then it will not be reset
67
- const wasNewRecord = !this.IsSaved;
68
- if (await super.Save(options)) {
69
- if (newCodeGenerated)
70
- return this.manageLibraries(codeLibraries, wasNewRecord);
71
- else
76
+ this.ForceCodeGeneration = false; // make sure to reset this flag every time we save, it should never live past one run of the Save method
77
+ const wasNewRecord = !this.IsSaved;
78
+ if (await super.Save(options)) {
79
+ // Now handle all the child entities within the same transaction
80
+ if (newCodeGenerated) {
81
+ // Process libraries
82
+ await this.manageLibraries(codeLibraries, wasNewRecord);
83
+ // Process parameters
84
+ if (generatedParameters.length > 0) {
85
+ await this.ProcessGeneratedParameters(generatedParameters);
86
+ }
87
+ // Process result codes
88
+ if (generatedResultCodes.length > 0) {
89
+ await this.ProcessGeneratedResultCodes(generatedResultCodes);
90
+ }
91
+ }
92
+ // Commit the transaction
93
+ // FIX await provider.CommitTransaction();
72
94
  return true;
95
+ }
96
+ else {
97
+ // Rollback on save failure
98
+ // FIX await provider.RollbackTransaction();
99
+ return false;
100
+ }
101
+ }
102
+ catch (e) {
103
+ // Rollback on any error
104
+ // FIX await provider.RollbackTransaction();
105
+ throw e;
73
106
  }
74
- else
75
- return false;
76
107
  }
77
108
  async manageLibraries(codeLibraries, wasNewRecord) {
78
109
  // new code was generated, we need to sync up the ActionLibraries table with the libraries used in the code for this Action
@@ -95,7 +126,6 @@ let ActionEntityServerEntity = class ActionEntityServerEntity extends actions_ba
95
126
  const librariesToRemove = existingLibraries.filter(el => !codeLibraries.some(l => l.LibraryName.trim().toLowerCase() === el.Library.trim().toLowerCase()));
96
127
  const librariesToUpdate = existingLibraries.filter(el => codeLibraries.some(l => l.LibraryName.trim().toLowerCase() === el.Library.trim().toLowerCase()));
97
128
  const md = new core_1.Metadata();
98
- const tg = await md.CreateTransactionGroup();
99
129
  for (const lib of librariesToAdd) {
100
130
  const libMetadata = md.Libraries.find(l => l.Name.trim().toLowerCase() === lib.LibraryName.trim().toLowerCase());
101
131
  if (libMetadata) {
@@ -103,7 +133,6 @@ let ActionEntityServerEntity = class ActionEntityServerEntity extends actions_ba
103
133
  newLib.ActionID = this.ID;
104
134
  newLib.LibraryID = libMetadata.ID;
105
135
  newLib.ItemsUsed = lib.ItemsUsed.join(',');
106
- newLib.TransactionGroup = tg;
107
136
  await newLib.Save();
108
137
  }
109
138
  }
@@ -111,20 +140,13 @@ let ActionEntityServerEntity = class ActionEntityServerEntity extends actions_ba
111
140
  for (const lib of librariesToUpdate) {
112
141
  const newCode = codeLibraries.find(l => l.LibraryName.trim().toLowerCase() === lib.Library.trim().toLowerCase());
113
142
  lib.ItemsUsed = newCode.ItemsUsed.join(',');
114
- lib.TransactionGroup = tg;
115
143
  await lib.Save();
116
144
  }
117
145
  // now remove the libraries that are no longer used
118
146
  for (const lib of librariesToRemove) {
119
147
  // each lib in this array iteration is already a BaseEntity derived object
120
- lib.TransactionGroup = tg;
121
148
  await lib.Delete();
122
149
  }
123
- // now commit the transaction
124
- if (await tg.Submit())
125
- return true;
126
- else
127
- return false;
128
150
  }
129
151
  SendMessage(message) {
130
152
  global_1.MJGlobal.Instance.RaiseEvent({
@@ -142,416 +164,270 @@ let ActionEntityServerEntity = class ActionEntityServerEntity extends actions_ba
142
164
  async GenerateCode(maxAttempts = 3) {
143
165
  try {
144
166
  this.SendMessage('Generating code... ');
145
- const model = await aiengine_1.AIEngine.Instance.GetHighestPowerModel(this.AIVendorName, 'llm', this.ContextCurrentUser);
146
- const llm = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(ai_1.BaseLLM, model.DriverClass, (0, ai_1.GetAIAPIKey)(model.DriverClass));
147
- const chatParams = {
148
- model: model.APINameOrName,
149
- messages: [
150
- {
151
- role: 'system',
152
- content: this.GenerateSysPrompt()
153
- },
154
- {
155
- role: 'user',
156
- content: this.UserPrompt,
157
- },
158
- ],
159
- };
160
- let result = await this.InternalGenerateCode(llm, chatParams, maxAttempts);
161
- if (result.Success) {
162
- //now run the code through the QA agent to make sure the code is valid
163
- let qaResult = await this.ValidateCode(llm, this.UserPrompt, result, maxAttempts);
164
- return qaResult;
165
- }
166
- else {
167
- //something went wrong in the first pass, just return the error
168
- return result;
167
+ // Ensure AIEngine is configured
168
+ await aiengine_1.AIEngine.Instance.Config(false, this.ContextCurrentUser);
169
+ // Load the consolidated prompt template from preloaded prompts
170
+ const aiPrompt = aiengine_1.AIEngine.Instance.Prompts.find(p => p.Name === 'Generate Action Code' &&
171
+ p.Category === 'Actions');
172
+ if (!aiPrompt) {
173
+ throw new Error('Failed to find AI prompt template: Generate Action Code in Actions category');
169
174
  }
170
- }
171
- catch (e) {
172
- (0, core_1.LogError)(e);
173
- throw e;
174
- }
175
- }
176
- async InternalGenerateCode(llm, chatParams, attemptsRemaining) {
177
- try {
178
- const result = await llm.ChatCompletion(chatParams);
179
- if (result && result.data) {
180
- const llmResponse = result.data.choices[0].message.content;
181
- if (llmResponse) {
182
- // try to parse it as JSON
183
- const cleansed = (0, global_1.CleanJSON)(llmResponse);
184
- if (!cleansed)
185
- throw new Error('Invalid JSON response from AI: ' + llmResponse);
186
- const parsed = JSON.parse(cleansed);
187
- if (parsed.code && parsed.code.length > 0) {
188
- const trimmed = parsed.code.trim();
189
- let comments = parsed.explanation;
190
- if (parsed.reflection) {
191
- comments = comments + `\n ${parsed.reflection}`;
192
- }
193
- const result = {
194
- Success: true,
195
- Code: trimmed,
196
- Comments: parsed.explanation,
197
- LibrariesUsed: parsed.libraries
198
- };
199
- return result;
200
- }
201
- else {
202
- throw new Error('Invalid JSON response from AI: ' + llmResponse);
203
- }
204
- }
205
- else
206
- throw new Error('No response from AI');
207
- }
208
- else
209
- throw new Error('No response from AI');
210
- }
211
- catch (e) {
212
- (0, core_1.LogError)(e);
213
- if (attemptsRemaining > 1) {
214
- // try again
215
- return await this.InternalGenerateCode(llm, chatParams, attemptsRemaining - 1);
175
+ // Prepare prompt data
176
+ const promptData = await this.PreparePromptData();
177
+ // Execute the prompt using AIPromptRunner
178
+ const promptRunner = new ai_prompts_1.AIPromptRunner();
179
+ const params = new ai_prompts_1.AIPromptParams();
180
+ params.prompt = aiPrompt;
181
+ params.data = promptData;
182
+ params.contextUser = this.ContextCurrentUser;
183
+ const result = await promptRunner.ExecutePrompt(params);
184
+ if (result.success && result.result) {
185
+ const generatedCode = {
186
+ Success: true,
187
+ Code: result.result.code.trim(),
188
+ Comments: result.result.explanation,
189
+ LibrariesUsed: result.result.libraries,
190
+ Parameters: result.result.parameters,
191
+ ResultCodes: result.result.resultCodes
192
+ };
193
+ // Validate the generated code
194
+ return await this.ValidateGeneratedCode(generatedCode, maxAttempts);
216
195
  }
217
- else
196
+ else {
218
197
  return {
219
198
  Success: false,
220
199
  Code: '',
221
- Comments: `Error communicating with AI: ${e.message}`,
200
+ Comments: result.errorMessage || 'Failed to generate code',
222
201
  LibrariesUsed: []
223
202
  };
203
+ }
224
204
  }
225
- }
226
- async ValidateCode(llm, userRequest, generatedCode, attemptsRemaining) {
227
- this.SendMessage(`Reviewing code...`);
228
- const model = await aiengine_1.AIEngine.Instance.GetHighestPowerModel(this.AIVendorName, 'llm', this.ContextCurrentUser);
229
- const promptMessage = this.GenerateValidateCodePrompt(userRequest, generatedCode);
230
- const validatePrompt = {
231
- role: 'system',
232
- content: promptMessage
233
- };
234
- let chatParams = new ai_1.ChatParams();
235
- chatParams.messages = [validatePrompt];
236
- chatParams.model = model.APINameOrName;
237
- let result = await this.InternalGenerateCode(llm, chatParams, attemptsRemaining);
238
- if (result.Success) {
239
- return result;
240
- }
241
- else if (attemptsRemaining > 1) {
242
- return this.ValidateCode(llm, userRequest, generatedCode, attemptsRemaining - 1);
243
- }
244
- else {
245
- return {
246
- Success: false,
247
- Code: '',
248
- Comments: `Error communicating with AI: ${result.Comments}`,
249
- LibrariesUsed: []
250
- };
205
+ catch (e) {
206
+ (0, core_1.LogError)(e);
207
+ throw e;
251
208
  }
252
209
  }
253
- GenerateSysPrompt() {
254
- const prompt = `<RETURN_FORMAT>
255
- * CRITICAL - I am a bot, I can ONLY understand fully formed JSON responses
256
- * YOUR RESPONSE MUST BE A JSON OBJECT
257
- * MORE INFO BELOW
258
- </RETURN_FORMAT>
259
-
260
- <INTRODUCTION>
261
- You are an expert in TypeScript coding and business applications. You take great pride in easy to read, commented, and nicely formatted code.
262
- You will be provided a request for how to handle a specific type of action that they want created. An action is a "verb" in the MemberJunction framework that can do basically anything the user asks for.
263
- Your job is to write the TypeScript code that will be taken and inserted into a class as shown below using the classes
264
- for inputs/outputs and the ActionResultSimple class that is provided. The code you write will be used by the MemberJunction engine to execute the action when
265
- it is called by the user.
266
- </INTRODUCTION>
267
-
268
- ${this.GenerateContextInfo()}
269
-
270
- Your response must be JSON and parsable into this type:
271
- const returnType = {
272
- code: string, // The typescript code you will create that will work in the context described above. Make sure to include line breaks, but not tabs. That is, pretty format in terms of new lines, but do NOT indent with spaces/tabs, as I'll handle indentation.
273
- explanation: string // an explanation for a semi-technical person explaining what the code does. Here again use line breaks liberally to make it easy to read but do NOT indent with spaces/tabs as I will handle that. Use * lists and numbered lists as appropriate.
274
- libraries: [
275
- {LibraryName: string, ItemsUsed: string[]}, // tell me the libraries you have used in the code you created here in this array of libraries. I need this info to properly import them in the final code.
276
- ]
277
- };
278
- </CRITICAL>
279
- **** REMEMBER **** I am a BOT, do not return anything other than the above JSON format to me or I will choke on your response!
280
- `;
281
- return prompt;
282
- }
283
- GenerateValidateCodePrompt(userRequest, generatedCode) {
284
- return `<RETURN_FORMAT>
285
- * CRITICAL - I am a bot, I can ONLY understand fully formed JSON responses
286
- * YOUR RESPONSE MUST BE A JSON OBJECT
287
- * MORE INFO BELOW
288
- </RETURN_FORMAT>
289
-
290
- <INTRODUCTION>
291
- You are an expert in TypeScript coding and business applications. You take great pride in easy to read, commented, and nicely formatted code.
292
- You are also exceptional in catching errors in other people's code and providing clear and easy to understand bug fixes, with comments explaining the
293
- issues and how you corrected it.
294
- You will be provided a request for how to handle a specific type of action that they want created, as well code written by someone else that attempts
295
- to satisfy the request. An action is a "verb" in the MemberJunction framework that can do basically anything the user asks for.
296
- Your job is to:
297
- 1 - Verify that the given code can successfully compile and run, using the provided libraries and parameters.
298
- 2- Verify that the given code satifies the user's request.
299
- </INTRODUCTION>
300
-
301
- The user's original request was:
302
- ${userRequest}
303
-
304
- The generated code is below:
305
- <GENERATEDCODE>
306
- ${generatedCode.Code}
307
- </GENERATEDCODE>
308
-
309
- Here are some comments about the above code:
310
- <CODECOMMENTS>
311
- ${generatedCode.Comments}
312
- </CODECOMMENTS>
313
-
314
- As well as the libraries used in the above code:
315
- <LIBRARIESUSED>
316
- ${JSON.stringify(generatedCode.LibrariesUsed, null, 2)}
317
- </LIBRARIESUSED>
318
-
319
- <CONTEXTINFO>
320
- Here is some additional info to help you:
321
- ${this.GenerateContextInfo()}
322
- </CONTEXTINFO>
323
-
324
- <RUNVIEWINFO>
325
- Here is some additonal info regarding the RunViewParams object
326
- ${this.GenerateRunViewParamsInfo()}
327
- </RUNVIEWINFO>
210
+ /**
211
+ * Prepares the data context for the prompt template
212
+ */
213
+ async PreparePromptData() {
214
+ const data = {
215
+ userPrompt: this.UserPrompt,
216
+ availableLibraries: this.GenerateLibrariesContext(),
217
+ IsChildAction: !!this.ParentID // Template variable for conditional sections
218
+ };
219
+ // If this is a child action, load parent context and create ChildActionInfo
220
+ if (this.ParentID) {
221
+ const parentAction = await this.LoadParentAction();
222
+ if (parentAction) {
223
+ // Create the ChildActionInfo template variable with all parent details
224
+ data.ChildActionInfo = `
225
+ **Parent Action Name:** ${parentAction.Name}
226
+ **Parent Description:** ${parentAction.Description || 'No description provided'}
328
227
 
329
- Your response must be JSON and parsable into this type:
330
- const returnType = {
331
- reflection: string, //Jot down your notes here on why you think the given code was valid or not. If it wasnt, write down why it isnt. Is it because there are syntax or logic errors, or because it doesnt satisfy the user's request?
332
- success: boolean //whether or not the above code is valid and can successfully compile and run, using the given libraries and data types
333
- code: string, // REMEMBER: your code will be inserted INTO an existing method so do not generate the method signature, just the code that I will drop into my existing method as shown in the <CONTEXTINFO>
334
- explanation: string // If the code is not valid, you will provide an explanation for a semi-technical person explaining what the corrected code does. Here again use line breaks liberally to make it easy to read but do NOT indent with spaces/tabs as I will handle that. Use * lists and numbered lists as appropriate.
335
- libraries: [
336
- {LibraryName: string, ItemsUsed: string[]}, // If the code is not valid, tell me the libraries you have used in the corrected code you created here in this array of libraries. I need this info to properly import them in the final code.
337
- ]
338
- };
228
+ **Parent Parameters:**
229
+ ${JSON.stringify(parentAction.Params.map(p => {
230
+ return {
231
+ Name: p.Name,
232
+ Type: p.Type,
233
+ ValueType: p.ValueType,
234
+ IsArray: p.IsArray,
235
+ IsRequired: p.IsRequired,
236
+ DefaultValue: p.DefaultValue,
237
+ Description: p.Description
238
+ };
239
+ }), null, 2)}
339
240
  `;
340
- }
341
- GenerateContextInfo() {
342
- return `
343
- <CODE_EXAMPLE>
344
- export class ${this.ProgrammaticName}Action extends BaseAction {
345
- public async Run(params: RunActionParams): Promise<ActionResultSimple> {
346
- // IMPORTANT: your code will go here, do not generate the method signature, just the code inside the method
347
- /* FOR THE RETURN VALUE you should return a JavaScript object that has these properties:
348
- {
349
- Success: boolean // or false if the action failed
350
- ResultCode: ${this.ResultCodes && this.ResultCodes.length > 0 ? this.ResultCodes.map(rc => `'${rc.ResultCode}'`).join(" | ") : 'string'} // The result of the action
351
- Message: string // a message to show the user
241
+ // Also include the parent action object for template access
242
+ data.parentAction = {
243
+ Name: parentAction.Name,
244
+ Description: parentAction.Description
245
+ };
352
246
  }
353
- */
247
+ }
248
+ return data;
354
249
  }
355
- }
356
- </CODE_EXAMPLE>
357
-
358
- <AVAILABLE_PARAMETERS>
359
- The params parameter into the Run method has a property called Params which is an array of ActionParam objects. These objects have a Name and Value property and map to the defined parameters for this action. The parameters for this
360
- action are as shown below. For parameters shown as output, make sure you generate the code to handle this and place a new item in the Params array with the Name and Value set to the output parameter name and value respectively. If
361
- the parameter has a type of input/output, you will receive the value as an input, and you can update it if the program you create needs to pass back a different value.
362
- ${JSON.stringify(this.Params)}
363
- </AVAILABLE_PARAMETERS>
364
-
365
- <AVAILABLE_LIBRARIES>
366
- The following libraries are available for use in your code. THEY ARE ALREADY IMPORTED. **DO NOT IMPORT THEM IN YOUR CODE**
367
- Use the code examples and reference information shown below, only use properties and methods shown in the documentation/examples.
368
- IMPORTANT: DO NOT GENERATE IMPORT STATEMENTS FOR THESE LIBRARIES, THEY ARE ALREADY IMPORTED FOR YOU!
369
- ${doc_utils_1.DocumentationEngine.Instance.Libraries.map((library) => {
370
- return library.Items.map((item) => {
371
- return `
372
- {
373
- "LibraryName": ${item.Name},
374
- "Content": ${item.HTMLContent}
375
- }
376
- `;
377
- });
378
- })}
379
- </AVAILABLE_LIBRARIES>
380
-
381
- <REFERENCE_TYPES>
382
250
  /**
383
- * Class that has the result of the individual action execution and used by the engine or other caller
251
+ * Loads the parent action entity if this is a child action
384
252
  */
385
- export class ActionResultSimple {
386
- /**
387
- * Indicates if the action was successful or not.
388
- */
389
- public Success: boolean;
390
-
391
- /**
392
- * A string that indicates the strucutred output/results of the action
393
- */
394
- public ResultCode: string;
395
-
396
- /**
397
- * Optional, additional information about the result of the action
398
- */
399
- public Message?: string;
400
-
401
- /**
402
- * Some actions return output parameters. If the action that was run has outputs, they will be provided here.
403
- */
404
- public Outputs?: ActionParam[];
253
+ async LoadParentAction() {
254
+ if (!this.ParentID)
255
+ return null;
256
+ const md = new core_1.Metadata();
257
+ const parent = await md.GetEntityObject('Actions', this.ContextCurrentUser);
258
+ if (await parent.Load(this.ParentID)) {
259
+ return parent;
260
+ }
261
+ return null;
405
262
  }
406
263
  /**
407
- * Generic class for holding parameters for an action for both inputs and outputs
264
+ * Processes generated parameters and saves them to the database
408
265
  */
409
- export class ActionParam {
410
- /**
411
- * The name of the parameter
412
- */
413
- public Name: string;
414
- /**
415
- * The value of the parameter. This can be any type of object.
416
- */
417
- public Value: any;
266
+ async ProcessGeneratedParameters(parameters) {
267
+ const md = new core_1.Metadata();
268
+ try {
269
+ // First, get existing parameters
270
+ const rv = new core_1.RunView();
271
+ const existingParams = await rv.RunView({
272
+ EntityName: 'Action Params',
273
+ ExtraFilter: `ActionID='${this.ID}'`,
274
+ ResultType: 'entity_object'
275
+ }, this.ContextCurrentUser);
276
+ if (!existingParams.Success) {
277
+ throw new Error(`Failed to load existing parameters: ${existingParams.ErrorMessage}`);
278
+ }
279
+ const existingParamsList = existingParams.Results || [];
280
+ const generatedParamNames = parameters.map(p => p.Name.toLowerCase());
281
+ // Find parameters to add, update, or remove
282
+ const paramsToAdd = parameters.filter(p => !existingParamsList.some(ep => ep.Name.toLowerCase() === p.Name.toLowerCase()));
283
+ const paramsToUpdate = existingParamsList.filter(ep => parameters.some(p => p.Name.toLowerCase() === ep.Name.toLowerCase()));
284
+ const paramsToRemove = existingParamsList.filter(ep => !generatedParamNames.includes(ep.Name.toLowerCase()));
285
+ // Add new parameters
286
+ for (const param of paramsToAdd) {
287
+ const newParam = await md.GetEntityObject('Action Params', this.ContextCurrentUser);
288
+ newParam.ActionID = this.ID;
289
+ newParam.Name = param.Name;
290
+ const t = param.Type;
291
+ if (t === 'Input' || t === 'Output' || t === 'Both') {
292
+ newParam.Type = t;
293
+ }
294
+ else {
295
+ newParam.Type = 'Input'; // Default to Input if type is not recognized and emit a warning
296
+ console.warn(`Action Generator: Unrecognized parameter type "${t}" for parameter "${param.Name}". Defaulting to "Input".`);
297
+ }
298
+ newParam.ValueType = param.ValueType;
299
+ newParam.IsArray = param.IsArray;
300
+ newParam.IsRequired = param.IsRequired;
301
+ newParam.DefaultValue = param.DefaultValue;
302
+ newParam.Description = param.Description;
303
+ await newParam.Save();
304
+ }
305
+ // Update existing parameters if properties changed
306
+ for (const existingParam of paramsToUpdate) {
307
+ const generatedParam = parameters.find(p => p.Name.toLowerCase() === existingParam.Name.toLowerCase());
308
+ if (generatedParam) {
309
+ let hasChanges = false;
310
+ // Check each property for changes
311
+ if (existingParam.Type !== generatedParam.Type) {
312
+ existingParam.Type = generatedParam.Type;
313
+ hasChanges = true;
314
+ }
315
+ if (existingParam.ValueType !== generatedParam.ValueType) {
316
+ existingParam.ValueType = generatedParam.ValueType;
317
+ hasChanges = true;
318
+ }
319
+ if (existingParam.IsArray !== generatedParam.IsArray) {
320
+ existingParam.IsArray = generatedParam.IsArray;
321
+ hasChanges = true;
322
+ }
323
+ if (existingParam.IsRequired !== generatedParam.IsRequired) {
324
+ existingParam.IsRequired = generatedParam.IsRequired;
325
+ hasChanges = true;
326
+ }
327
+ if (existingParam.DefaultValue !== generatedParam.DefaultValue) {
328
+ existingParam.DefaultValue = generatedParam.DefaultValue;
329
+ hasChanges = true;
330
+ }
331
+ if (existingParam.Description !== generatedParam.Description) {
332
+ existingParam.Description = generatedParam.Description;
333
+ hasChanges = true;
334
+ }
335
+ if (hasChanges) {
336
+ await existingParam.Save();
337
+ }
338
+ }
339
+ }
340
+ // Remove parameters that are no longer in the generated list
341
+ for (const paramToRemove of paramsToRemove) {
342
+ await paramToRemove.Delete();
343
+ }
344
+ }
345
+ catch (e) {
346
+ (0, core_1.LogError)('Failed to save generated parameters:', e);
347
+ throw e; // Re-throw since we're in a transaction
348
+ }
418
349
  }
419
350
  /**
420
- * Class that holds the parameters for an action to be run. This is passed to the Run method of an action.
351
+ * Processes generated result codes and saves them to the database
421
352
  */
422
- export class RunActionParams {
423
- public Action: ActionEntity;
424
- public ContextUser: UserInfo;
425
- /**
426
- * Optional, a list of filters that should be run before the action is executed.
427
- */
428
- public Filters: ActionFilterEntity[];
429
- /**
430
- * Optional, the input and output parameters as defined in the metadata for the action.
431
- */
432
- public Params: ActionParam[];
433
- }
434
- </REFERENCE_TYPES>
435
-
436
- <ENTITIES>
437
- Entities in MemberJunction are storage objects and roughly map to database tables. Here are the entities in the system to give you additional context to understand the request:
438
- ${JSON.stringify(core_1.Metadata.Provider.Entities.map(e => {
439
- // for each entity, get the name, description and base view
440
- return {
441
- Entity: e.Name,
442
- SchemaName: e.SchemaName,
443
- Description: e.Description,
444
- BaseView: e.BaseView
445
- };
446
- }))}
447
- </ENTITIES>
448
- The next message, which will be a user message in the conversation, will contain the sys admin's requested behavior for this entity.
449
-
450
- <CRITICAL>
451
- I am a bot and can only understand FULLY FORMED JSON responses that can be parsed with JSON.parse().
452
- `;
353
+ async ProcessGeneratedResultCodes(resultCodes) {
354
+ const md = new core_1.Metadata();
355
+ try {
356
+ // First, get existing result codes
357
+ const rv = new core_1.RunView();
358
+ const existingCodes = await rv.RunView({
359
+ EntityName: 'Action Result Codes',
360
+ ExtraFilter: `ActionID='${this.ID}'`,
361
+ ResultType: 'entity_object'
362
+ }, this.ContextCurrentUser);
363
+ if (!existingCodes.Success) {
364
+ throw new Error(`Failed to load existing result codes: ${existingCodes.ErrorMessage}`);
365
+ }
366
+ const existingCodesList = existingCodes.Results || [];
367
+ const generatedCodeNames = resultCodes.map(rc => rc.ResultCode.toLowerCase());
368
+ // Find result codes to add, update, or remove
369
+ const codesToAdd = resultCodes.filter(rc => !existingCodesList.some(ec => ec.ResultCode.toLowerCase() === rc.ResultCode.toLowerCase()));
370
+ const codesToUpdate = existingCodesList.filter(ec => resultCodes.some(rc => rc.ResultCode.toLowerCase() === ec.ResultCode.toLowerCase()));
371
+ const codesToRemove = existingCodesList.filter(ec => !generatedCodeNames.includes(ec.ResultCode.toLowerCase()));
372
+ // Add new result codes
373
+ for (const resultCode of codesToAdd) {
374
+ const newCode = await md.GetEntityObject('Action Result Codes', this.ContextCurrentUser);
375
+ newCode.ActionID = this.ID;
376
+ newCode.ResultCode = resultCode.ResultCode;
377
+ newCode.Description = resultCode.Description;
378
+ newCode.IsSuccess = resultCode.IsSuccess;
379
+ await newCode.Save();
380
+ }
381
+ // Update existing result codes if properties changed
382
+ for (const existingCode of codesToUpdate) {
383
+ const generatedCode = resultCodes.find(rc => rc.ResultCode.toLowerCase() === existingCode.ResultCode.toLowerCase());
384
+ if (generatedCode) {
385
+ let hasChanges = false;
386
+ // Check each property for changes
387
+ if (existingCode.Description !== generatedCode.Description) {
388
+ existingCode.Description = generatedCode.Description;
389
+ hasChanges = true;
390
+ }
391
+ if (existingCode.IsSuccess !== generatedCode.IsSuccess) {
392
+ existingCode.IsSuccess = generatedCode.IsSuccess;
393
+ hasChanges = true;
394
+ }
395
+ if (hasChanges) {
396
+ await existingCode.Save();
397
+ }
398
+ }
399
+ }
400
+ // Remove result codes that are no longer in the generated list
401
+ for (const codeToRemove of codesToRemove) {
402
+ await codeToRemove.Delete();
403
+ }
404
+ }
405
+ catch (e) {
406
+ (0, core_1.LogError)('Failed to save generated result codes:', e);
407
+ throw e; // Re-throw since we're in a transaction
408
+ }
453
409
  }
454
- GenerateRunViewParamsInfo() {
455
- return `/**
456
- * Parameters for running either a stored or dynamic view.
457
- * A stored view is a view that is saved in the database and can be run either by ID or Name.
458
- * A dynamic view is one that is not stored in the database and you provide parameters to return data as
459
- * desired programatically.
460
- */
461
- export type RunViewParams = {
462
- /**
463
- * optional - ID of the UserView record to run, if provided, ViewName is ignored
464
- */
465
- ViewID?: number
466
- /**
467
- * optional - Name of the UserView record to run, if you are using this, make sure to use a naming convention
468
- * so that your view names are unique. For example use a prefix like __Entity_View_ etc so that you're
469
- * likely to have a single result. If more than one view is available that matches a provided view name an
470
- * exception will be thrown.
471
- */
472
- ViewName?: string
473
- /**
474
- * optional - this is the loaded instance of the BaseEntity (UserViewEntityComplete or a subclass of it).
475
- * This is the preferred parameter to use IF you already have a view entity object loaded up in your code
476
- * becuase by passing this in, the RunView() method doesn't have to lookup all the metadata for the view and it is faster.
477
- * If you provide ViewEntity, ViewID/ViewName are ignored.
478
- */
479
- ViewEntity?: BaseEntity
480
- /**
481
- * optional - this is only used if ViewID/ViewName/ViewEntity are not provided, it is used for
482
- * Dynamic Views in combination with the optional ExtraFilter
483
- */
484
- EntityName?: string
485
- /**
486
- * An optional SQL WHERE clause that you can add to the existing filters on a stored view. For dynamic views, you can either
487
- * run a view without a filter (if the entity definition allows it with AllowAllRowsAPI=1) or filter with any valid SQL WHERE clause.
488
- */
489
- ExtraFilter?: string
490
- /**
491
- * An optional SQL ORDER BY clause that you can use for dynamic views, as well as to OVERRIDE the stored view's sorting order.
492
- */
493
- OrderBy?: string
494
- /**
495
- * An optional array of field names that you want returned. The RunView() function will always return ID so you don't need to ask for that. If you leave this null then
496
- * for a dynamic view all fields are returned, and for stored views, the fields stored in it view configuration are returned.
497
- */
498
- Fields?: string[]
499
- /**
500
- * optional - string that represents a user "search" - typically from a text search option in a UI somewhere. This field is then used in the view filtering to search whichever fields are configured to be included in search in the Entity Fields definition.
501
- * Search String is combined with the stored view filters as well as ExtraFilter with an AND.
502
- */
503
- UserSearchString?: string
504
- /**
505
- * optional - if provided, records that were returned in the specified UserViewRunID will NOT be allowed in the result set.
506
- * This is useful if you want to run a particular view over time and exclude a specific prior run's resulting data set. If you
507
- * want to exclude ALL data returned from ALL prior runs, use the ExcludeDataFromAllPriorViewRuns property instead.
508
- */
509
- ExcludeUserViewRunID?: number
510
- /**
511
- * optional - if set to true, the resulting data will filter out ANY records that were ever returned by this view, when the SaveViewResults property was set to true.
512
- * This is useful if you want to run a particular view over time and make sure the results returned each time are new to the view.
513
- */
514
- ExcludeDataFromAllPriorViewRuns?: boolean
515
- /**
516
- * optional - if you are providing the optional ExcludeUserViewRunID property, you can also optionally provide
517
- * this filter which will negate the specific list of record IDs that are excluded by the ExcludeUserViewRunID property.
518
- * This can be useful if you want to ensure a certain class of data is always allowed into your view and not filtered out
519
- * by a prior view run.
520
- *
521
- */
522
- OverrideExcludeFilter?: string
523
- /**
524
- * optional - if set to true, the LIST OF ID values from the view run will be stored in the User View Runs entity and the
525
- * newly created UserViewRun.ID value will be returned in the RunViewResult that the RunView() function sends back to ya.
526
- */
527
- SaveViewResults?: boolean
528
- /**
529
- * optional - if set to true, if there IS any UserViewMaxRows property set for the entity in question, it will be IGNORED. This is useful in scenarios where you
530
- * want to programmatically run a view and get ALL the data back, regardless of the MaxRows setting on the entity.
531
- */
532
- IgnoreMaxRows?: boolean
533
-
534
- /**
535
- * optional - if provided, and if IgnoreMaxRows = false, this value will be used to constrain the total # of rows returned by the view. If this is not provided, either the default settings at the entity-level will be used, or if the entity has no UserViewMaxRows setting, all rows will be returned that match any filter, if provided.
536
- */
537
- MaxRows?: number
538
- /**
539
- * optional - if set to true, the view run will ALWAYS be logged to the Audit Log, regardless of the entity's property settings for logging view runs.
540
- */
541
- ForceAuditLog?: boolean
542
410
  /**
543
- * optional - if provided and either ForceAuditLog is set, or the entity's property settings for logging view runs are set to true, this will be used as the Audit Log Description.
411
+ * Validates the generated code
544
412
  */
545
- AuditLogDescription?: string
546
-
413
+ async ValidateGeneratedCode(generatedCode, attemptsRemaining) {
414
+ // For now, return the code as-is
415
+ // TODO: Implement validation logic using a separate prompt
416
+ return generatedCode;
417
+ }
547
418
  /**
548
- * Result Type is: 'simple', 'entity_object', or 'count_only' and defaults to 'simple'. If 'entity_object' is specified, the Results[] array will contain
549
- * BaseEntity-derived objects instead of simple objects. This is useful if you want to work with the data in a more strongly typed manner and/or
550
- * if you plan to do any update/delete operations on the data after it is returned. The 'count_only' option will return no rows, but the TotalRowCount property of the RunViewResult object will be populated.
419
+ * Generates the libraries context for the prompt
551
420
  */
552
- ResultType?: 'simple' | 'entity_object' | 'count_only';
553
- }
554
- `;
421
+ GenerateLibrariesContext() {
422
+ return doc_utils_1.DocumentationEngine.Instance.Libraries.map((library) => {
423
+ return library.Items.map((item) => {
424
+ return JSON.stringify({
425
+ LibraryName: library.Name,
426
+ ItemName: item.Name,
427
+ Content: item.HTMLContent
428
+ });
429
+ }).join('\n');
430
+ }).join('\n');
555
431
  }
556
432
  };
557
433
  exports.ActionEntityServerEntity = ActionEntityServerEntity;