@memberjunction/ai-agent-manager 2.107.0 → 2.109.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +32 -0
- package/dist/agent-spec-sync.d.ts +370 -0
- package/dist/agent-spec-sync.d.ts.map +1 -0
- package/dist/agent-spec-sync.js +846 -0
- package/dist/agent-spec-sync.js.map +1 -0
- package/dist/agents/architect-agent.d.ts +52 -0
- package/dist/agents/architect-agent.d.ts.map +1 -0
- package/dist/agents/architect-agent.js +247 -0
- package/dist/agents/architect-agent.js.map +1 -0
- package/dist/agents/builder-agent.d.ts +29 -0
- package/dist/agents/builder-agent.d.ts.map +1 -0
- package/dist/agents/builder-agent.js +84 -0
- package/dist/agents/builder-agent.js.map +1 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/dist/{interfaces → old}/agent-definition.interface.d.ts +1 -0
- package/dist/old/agent-definition.interface.d.ts.map +1 -0
- package/dist/old/agent-definition.interface.js.map +1 -0
- package/package.json +9 -2
- package/src/agent-spec-sync.ts +1000 -0
- package/src/agents/architect-agent.ts +293 -0
- package/src/agents/builder-agent.ts +89 -0
- package/src/index.ts +23 -2
- package/src/{interfaces → old}/agent-definition.interface.ts +1 -0
- package/tsconfig.json +3 -1
- package/dist/interfaces/agent-definition.interface.d.ts.map +0 -1
- package/dist/interfaces/agent-definition.interface.js.map +0 -1
- /package/dist/{interfaces → old}/agent-definition.interface.js +0 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentSpecSync = void 0;
|
|
4
|
+
const core_1 = require("@memberjunction/core");
|
|
5
|
+
/**
|
|
6
|
+
* AgentSpecSync provides a high-level interface for working with AI Agent metadata in MemberJunction.
|
|
7
|
+
*
|
|
8
|
+
* This class serves as a bi-directional bridge between the simple, serializable {@link AgentSpec}
|
|
9
|
+
* format and the complex MemberJunction database metadata across three core entities:
|
|
10
|
+
* - {@link AIAgentEntity} - Core agent configuration
|
|
11
|
+
* - {@link AIAgentActionEntity} - Agent-action relationships
|
|
12
|
+
* - {@link AIAgentRelationshipEntity} - Parent-child agent relationships
|
|
13
|
+
*
|
|
14
|
+
* ## Key Features
|
|
15
|
+
*
|
|
16
|
+
* - **Load from Database**: Load complete agent hierarchies including all sub-agents and actions
|
|
17
|
+
* - **Save to Database**: Persist changes atomically with full validation
|
|
18
|
+
* - **Recursive Support**: Handles n-level agent hierarchies automatically
|
|
19
|
+
* - **Type Safety**: Full TypeScript typing with proper entity types
|
|
20
|
+
* - **Dirty Tracking**: Knows when changes need to be saved
|
|
21
|
+
* - **JSON Serialization**: Easy export for APIs and storage
|
|
22
|
+
*
|
|
23
|
+
* ## Usage Examples
|
|
24
|
+
*
|
|
25
|
+
* ### Load an existing agent
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const spec = await AgentSpecSync.LoadFromDatabase('agent-uuid', contextUser);
|
|
28
|
+
* console.log('Agent Name:', spec.spec.Name);
|
|
29
|
+
* console.log('Actions:', spec.spec.Actions?.length);
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* ### Create a new agent
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const newAgent = new AgentSpecSync({
|
|
35
|
+
* Name: 'My New Agent',
|
|
36
|
+
* Description: 'Does amazing things',
|
|
37
|
+
* IconClass: 'fa-robot',
|
|
38
|
+
* InvocationMode: 'Any'
|
|
39
|
+
* }, contextUser);
|
|
40
|
+
*
|
|
41
|
+
* const agentId = await newAgent.SaveToDatabase();
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ### Modify and save
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const spec = await AgentSpecSync.LoadFromDatabase('agent-uuid', contextUser);
|
|
47
|
+
* spec.spec.Description = 'Updated description';
|
|
48
|
+
* spec.spec.MaxCostPerRun = 10.00;
|
|
49
|
+
* spec.markDirty();
|
|
50
|
+
* await spec.SaveToDatabase();
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @module @memberjunction/ai-agent-manager
|
|
54
|
+
*/
|
|
55
|
+
class AgentSpecSync {
|
|
56
|
+
/**
|
|
57
|
+
* Create a new AgentSpecSync instance.
|
|
58
|
+
*
|
|
59
|
+
* Note: This constructor is typically not called directly. Instead, use the static factory methods:
|
|
60
|
+
* - {@link LoadFromDatabase} - Load existing agent from database
|
|
61
|
+
* - {@link LoadByName} - Load agent by name
|
|
62
|
+
* - {@link FromRawSpec} - Create from raw spec data
|
|
63
|
+
*
|
|
64
|
+
* @param spec - Optional initial spec data (for creating new agents or working with existing data)
|
|
65
|
+
* @param contextUser - Optional context user (required for server-side operations)
|
|
66
|
+
*/
|
|
67
|
+
constructor(spec, contextUser) {
|
|
68
|
+
/**
|
|
69
|
+
* Tracks whether this spec has been loaded from the database
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
this._isLoaded = false;
|
|
73
|
+
/**
|
|
74
|
+
* Tracks whether this spec has unsaved changes
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
this._isDirty = false;
|
|
78
|
+
if (spec) {
|
|
79
|
+
this.spec = this.initializeSpec(spec);
|
|
80
|
+
this._isDirty = true;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Create minimal empty spec
|
|
84
|
+
this.spec = {
|
|
85
|
+
ID: '', // Will be set on save if empty
|
|
86
|
+
Name: '',
|
|
87
|
+
StartingPayloadValidationMode: 'Fail',
|
|
88
|
+
Actions: [],
|
|
89
|
+
SubAgents: []
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
this._contextUser = contextUser;
|
|
93
|
+
}
|
|
94
|
+
// ===== STATIC FACTORY METHODS =====
|
|
95
|
+
/**
|
|
96
|
+
* Load an agent and its complete hierarchy from the database by ID.
|
|
97
|
+
*
|
|
98
|
+
* This method efficiently loads the agent along with all its actions and sub-agents
|
|
99
|
+
* using batched queries to minimize database round trips. When `includeSubAgents` is true,
|
|
100
|
+
* it recursively loads the entire agent hierarchy.
|
|
101
|
+
*
|
|
102
|
+
* @param agentId - The unique ID of the agent to load
|
|
103
|
+
* @param contextUser - Optional context user (required for server-side operations)
|
|
104
|
+
* @param includeSubAgents - Whether to recursively load all sub-agents (default: true)
|
|
105
|
+
* @returns Promise resolving to AgentSpecSync instance with loaded data
|
|
106
|
+
* @throws {Error} If agent with specified ID is not found
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* // Load agent with all sub-agents
|
|
111
|
+
* const spec = await AgentSpecSync.LoadFromDatabase(
|
|
112
|
+
* 'agent-uuid-here',
|
|
113
|
+
* contextUser,
|
|
114
|
+
* true
|
|
115
|
+
* );
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
static async LoadFromDatabase(agentId, contextUser, includeSubAgents = true) {
|
|
119
|
+
const instance = new AgentSpecSync(undefined, contextUser);
|
|
120
|
+
await instance.loadFromEntities(agentId, includeSubAgents);
|
|
121
|
+
return instance;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Load an agent by name (must be unique).
|
|
125
|
+
*
|
|
126
|
+
* Searches for an agent with the specified name and loads it. If multiple agents
|
|
127
|
+
* have the same name, an error is thrown. Agent names should be unique within
|
|
128
|
+
* the system for this method to work reliably.
|
|
129
|
+
*
|
|
130
|
+
* @param agentName - The name of the agent to load
|
|
131
|
+
* @param contextUser - Optional context user (required for server-side operations)
|
|
132
|
+
* @param includeSubAgents - Whether to recursively load sub-agents (default: true)
|
|
133
|
+
* @returns Promise resolving to AgentSpecSync instance with loaded data
|
|
134
|
+
* @throws {Error} If agent is not found or multiple agents have the same name
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const spec = await AgentSpecSync.LoadByName('My Agent', contextUser);
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
static async LoadByName(agentName, contextUser, includeSubAgents = true) {
|
|
142
|
+
// Find agent by name
|
|
143
|
+
const rv = new core_1.RunView();
|
|
144
|
+
const result = await rv.RunView({
|
|
145
|
+
EntityName: 'AI Agents',
|
|
146
|
+
ExtraFilter: `Name='${agentName.replace(/'/g, "''")}'`,
|
|
147
|
+
ResultType: 'entity_object'
|
|
148
|
+
}, contextUser);
|
|
149
|
+
if (!result.Success) {
|
|
150
|
+
throw new Error(`Failed to find agent by name: ${result.ErrorMessage}`);
|
|
151
|
+
}
|
|
152
|
+
if (!result.Results || result.Results.length === 0) {
|
|
153
|
+
throw new Error(`Agent with name '${agentName}' not found`);
|
|
154
|
+
}
|
|
155
|
+
if (result.Results.length > 1) {
|
|
156
|
+
throw new Error(`Multiple agents found with name '${agentName}'. Use LoadFromDatabase with specific ID instead.`);
|
|
157
|
+
}
|
|
158
|
+
const agent = result.Results[0];
|
|
159
|
+
return AgentSpecSync.LoadFromDatabase(agent.ID, contextUser, includeSubAgents);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Create a new agent spec from a raw specification.
|
|
163
|
+
*
|
|
164
|
+
* This creates an in-memory AgentSpecSync instance from raw data. The agent is not
|
|
165
|
+
* saved to the database until {@link SaveToDatabase} is called.
|
|
166
|
+
*
|
|
167
|
+
* @param rawSpec - The raw spec data conforming to {@link AgentSpec} interface
|
|
168
|
+
* @param contextUser - Optional context user (required when saving server-side)
|
|
169
|
+
* @returns New AgentSpecSync instance (not yet saved to database)
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* const rawSpec: AgentSpec = {
|
|
174
|
+
* ID: '',
|
|
175
|
+
* Name: 'New Agent',
|
|
176
|
+
* Description: 'Agent description',
|
|
177
|
+
* InvocationMode: 'Any',
|
|
178
|
+
* Actions: [],
|
|
179
|
+
* SubAgents: []
|
|
180
|
+
* };
|
|
181
|
+
* const spec = AgentSpecSync.FromRawSpec(rawSpec, contextUser);
|
|
182
|
+
* await spec.SaveToDatabase();
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
static FromRawSpec(rawSpec, contextUser) {
|
|
186
|
+
return new AgentSpecSync(rawSpec, contextUser);
|
|
187
|
+
}
|
|
188
|
+
// ===== LOADING METHODS =====
|
|
189
|
+
/**
|
|
190
|
+
* Load the complete agent specification from database entities.
|
|
191
|
+
*
|
|
192
|
+
* This method orchestrates loading from AIAgent, AIAgentAction, and AIAgentRelationship tables
|
|
193
|
+
* using batched queries for optimal performance. It handles both child agents (ParentID-based)
|
|
194
|
+
* and related agents (relationship-based).
|
|
195
|
+
*
|
|
196
|
+
* @private
|
|
197
|
+
* @param agentId - The agent ID to load
|
|
198
|
+
* @param includeSubAgents - Whether to recursively load sub-agents
|
|
199
|
+
* @throws {Error} If agent is not found
|
|
200
|
+
*/
|
|
201
|
+
async loadFromEntities(agentId, includeSubAgents) {
|
|
202
|
+
const md = new core_1.Metadata();
|
|
203
|
+
const rv = new core_1.RunView();
|
|
204
|
+
// Step 1: Load the main agent entity
|
|
205
|
+
const agentEntity = await md.GetEntityObject('AI Agents', this._contextUser);
|
|
206
|
+
const loaded = await agentEntity.Load(agentId);
|
|
207
|
+
if (!loaded) {
|
|
208
|
+
throw new Error(`Agent with ID ${agentId} not found`);
|
|
209
|
+
}
|
|
210
|
+
// Step 2: Batch load related entities using RunViews for optimal performance
|
|
211
|
+
const [actionsResult, childAgentsResult, relatedAgentsResult] = await rv.RunViews([
|
|
212
|
+
{
|
|
213
|
+
EntityName: 'AI Agent Actions',
|
|
214
|
+
ExtraFilter: `AgentID='${agentId}'`,
|
|
215
|
+
OrderBy: 'Status, ActionID',
|
|
216
|
+
ResultType: 'entity_object'
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
EntityName: 'AI Agents',
|
|
220
|
+
ExtraFilter: `ParentID='${agentId}'`,
|
|
221
|
+
OrderBy: 'ExecutionOrder, Name',
|
|
222
|
+
ResultType: 'entity_object'
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
EntityName: 'MJ: AI Agent Relationships',
|
|
226
|
+
ExtraFilter: `AgentID='${agentId}' AND Status='Active'`,
|
|
227
|
+
OrderBy: '__mj_CreatedAt',
|
|
228
|
+
ResultType: 'entity_object'
|
|
229
|
+
}
|
|
230
|
+
], this._contextUser);
|
|
231
|
+
// Check for errors
|
|
232
|
+
if (!actionsResult.Success) {
|
|
233
|
+
throw new Error(`Failed to load agent actions: ${actionsResult.ErrorMessage}`);
|
|
234
|
+
}
|
|
235
|
+
if (!childAgentsResult.Success) {
|
|
236
|
+
throw new Error(`Failed to load child agents: ${childAgentsResult.ErrorMessage}`);
|
|
237
|
+
}
|
|
238
|
+
if (!relatedAgentsResult.Success) {
|
|
239
|
+
throw new Error(`Failed to load related agents: ${relatedAgentsResult.ErrorMessage}`);
|
|
240
|
+
}
|
|
241
|
+
// Step 3: Map entities to raw spec format
|
|
242
|
+
this.spec = this.mapEntitiesToRawSpec(agentEntity, actionsResult.Results || [], childAgentsResult.Results || [], relatedAgentsResult.Results || []);
|
|
243
|
+
// Step 4: Recursively load sub-agents if requested
|
|
244
|
+
if (includeSubAgents && this.spec.SubAgents && this.spec.SubAgents.length > 0) {
|
|
245
|
+
// Note: We don't recursively populate the full spec here since SubAgentSpec
|
|
246
|
+
// only contains the ID and relationship metadata. To get full details,
|
|
247
|
+
// users can call LoadFromDatabase on the SubAgentID separately if needed.
|
|
248
|
+
}
|
|
249
|
+
this._isLoaded = true;
|
|
250
|
+
this._isDirty = false;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Map database entities to AgentSpec format.
|
|
254
|
+
*
|
|
255
|
+
* Transforms the normalized database entities into a single denormalized specification
|
|
256
|
+
* object that's easy to work with in code. Handles JSON parsing for all structured fields.
|
|
257
|
+
*
|
|
258
|
+
* @private
|
|
259
|
+
* @param agent - The main agent entity
|
|
260
|
+
* @param actions - Array of agent action entities
|
|
261
|
+
* @param childAgents - Array of child agent entities (ParentID-based)
|
|
262
|
+
* @param relatedAgents - Array of related agent relationship entities
|
|
263
|
+
* @returns Fully populated AgentSpec object
|
|
264
|
+
*/
|
|
265
|
+
mapEntitiesToRawSpec(agent, actions, childAgents, relatedAgents) {
|
|
266
|
+
// Map all agent fields to spec
|
|
267
|
+
const spec = {
|
|
268
|
+
ID: agent.ID,
|
|
269
|
+
Name: agent.Name || '',
|
|
270
|
+
Description: agent.Description || undefined,
|
|
271
|
+
IconClass: agent.IconClass || undefined,
|
|
272
|
+
LogoURL: agent.LogoURL || undefined,
|
|
273
|
+
ParentID: agent.ParentID || undefined,
|
|
274
|
+
DriverClass: agent.DriverClass || undefined,
|
|
275
|
+
ModelSelectionMode: agent.ModelSelectionMode,
|
|
276
|
+
// Parse JSON array fields
|
|
277
|
+
PayloadDownstreamPaths: this.parseJsonField(agent.PayloadDownstreamPaths),
|
|
278
|
+
PayloadUpstreamPaths: this.parseJsonField(agent.PayloadUpstreamPaths),
|
|
279
|
+
PayloadSelfReadPaths: this.parseJsonField(agent.PayloadSelfReadPaths),
|
|
280
|
+
PayloadSelfWritePaths: this.parseJsonField(agent.PayloadSelfWritePaths),
|
|
281
|
+
PayloadScope: agent.PayloadScope || undefined,
|
|
282
|
+
// Validation fields
|
|
283
|
+
FinalPayloadValidation: agent.FinalPayloadValidation || null,
|
|
284
|
+
FinalPayloadValidationMode: agent.FinalPayloadValidationMode,
|
|
285
|
+
FinalPayloadValidationMaxRetries: agent.FinalPayloadValidationMaxRetries || undefined,
|
|
286
|
+
StartingPayloadValidation: agent.StartingPayloadValidation || null,
|
|
287
|
+
StartingPayloadValidationMode: agent.StartingPayloadValidationMode,
|
|
288
|
+
// Resource limits
|
|
289
|
+
MaxCostPerRun: agent.MaxCostPerRun || null,
|
|
290
|
+
MaxTokensPerRun: agent.MaxTokensPerRun || null,
|
|
291
|
+
MaxIterationsPerRun: agent.MaxIterationsPerRun || null,
|
|
292
|
+
MaxTimePerRun: agent.MaxTimePerRun || undefined,
|
|
293
|
+
// Execution frequency
|
|
294
|
+
MinExecutionsPerRun: agent.MinExecutionsPerRun || undefined,
|
|
295
|
+
MaxExecutionsPerRun: agent.MaxExecutionsPerRun || undefined,
|
|
296
|
+
// Other config
|
|
297
|
+
DefaultPromptEffortLevel: agent.DefaultPromptEffortLevel || undefined,
|
|
298
|
+
ChatHandlingOption: agent.ChatHandlingOption || undefined,
|
|
299
|
+
DefaultArtifactTypeID: agent.DefaultArtifactTypeID || undefined,
|
|
300
|
+
OwnerUserID: agent.OwnerUserID || undefined,
|
|
301
|
+
InvocationMode: agent.InvocationMode,
|
|
302
|
+
// Map actions
|
|
303
|
+
Actions: actions.map(action => this.mapActionEntityToSpec(action)),
|
|
304
|
+
// Map sub-agents (both child and related)
|
|
305
|
+
SubAgents: [
|
|
306
|
+
...childAgents.map(child => this.mapChildAgentToSpec(child)),
|
|
307
|
+
...relatedAgents.map(rel => this.mapRelatedAgentToSpec(rel))
|
|
308
|
+
]
|
|
309
|
+
};
|
|
310
|
+
return spec;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Map AIAgentActionEntity to AgentActionSpec format.
|
|
314
|
+
*
|
|
315
|
+
* @private
|
|
316
|
+
* @param action - The agent action entity from the database
|
|
317
|
+
* @returns Mapped action spec
|
|
318
|
+
*/
|
|
319
|
+
mapActionEntityToSpec(action) {
|
|
320
|
+
return {
|
|
321
|
+
AgentActionID: action.ID,
|
|
322
|
+
ActionID: action.ActionID || '',
|
|
323
|
+
Status: action.Status,
|
|
324
|
+
MaxExecutionsPerRun: action.MaxExecutionsPerRun || undefined,
|
|
325
|
+
ResultExpirationTurns: action.ResultExpirationTurns || undefined,
|
|
326
|
+
ResultExpirationMode: action.ResultExpirationMode || undefined,
|
|
327
|
+
CompactMode: action.CompactMode || undefined,
|
|
328
|
+
CompactLength: action.CompactLength || undefined,
|
|
329
|
+
CompactPromptID: action.CompactPromptID || null
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Map child agent (ParentID-based) to SubAgentSpec format.
|
|
334
|
+
*
|
|
335
|
+
* @private
|
|
336
|
+
* @param childAgent - The child agent entity
|
|
337
|
+
* @returns Mapped sub-agent spec
|
|
338
|
+
*/
|
|
339
|
+
mapChildAgentToSpec(childAgent) {
|
|
340
|
+
return {
|
|
341
|
+
Type: 'child',
|
|
342
|
+
SubAgent: {
|
|
343
|
+
ID: childAgent.ID,
|
|
344
|
+
Name: childAgent.Name || '',
|
|
345
|
+
StartingPayloadValidationMode: 'Fail'
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Map related agent (relationship-based) to SubAgentSpec format.
|
|
351
|
+
*
|
|
352
|
+
* @private
|
|
353
|
+
* @param relationship - The agent relationship entity
|
|
354
|
+
* @returns Mapped sub-agent spec
|
|
355
|
+
*/
|
|
356
|
+
mapRelatedAgentToSpec(relationship) {
|
|
357
|
+
return {
|
|
358
|
+
Type: 'related',
|
|
359
|
+
SubAgent: {
|
|
360
|
+
ID: relationship.SubAgentID,
|
|
361
|
+
Name: relationship.SubAgent || '',
|
|
362
|
+
StartingPayloadValidationMode: 'Fail'
|
|
363
|
+
},
|
|
364
|
+
AgentRelationshipID: relationship.ID,
|
|
365
|
+
SubAgentInputMapping: this.parseJsonField(relationship.SubAgentInputMapping),
|
|
366
|
+
SubAgentOutputMapping: this.parseJsonField(relationship.SubAgentOutputMapping),
|
|
367
|
+
SubAgentContextPaths: this.parseJsonField(relationship.SubAgentContextPaths)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
// ===== SAVING METHODS =====
|
|
371
|
+
/**
|
|
372
|
+
* Save the current spec to the database.
|
|
373
|
+
*
|
|
374
|
+
* This will create new records or update existing ones based on whether IDs exist.
|
|
375
|
+
* The save operation is performed atomically - if any part fails, no changes are committed.
|
|
376
|
+
*
|
|
377
|
+
* Validation is performed before saving. All entity-level validations defined in the
|
|
378
|
+
* database schema are executed, and any failures will prevent the save.
|
|
379
|
+
*
|
|
380
|
+
* @param validate - Whether to validate before saving (default: true)
|
|
381
|
+
* @returns Promise resolving to the saved agent ID
|
|
382
|
+
* @throws {Error} If validation fails or save operation fails
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```typescript
|
|
386
|
+
* const spec = new AgentSpecSync({
|
|
387
|
+
* Name: 'New Agent',
|
|
388
|
+
* InvocationMode: 'Any'
|
|
389
|
+
* }, contextUser);
|
|
390
|
+
*
|
|
391
|
+
* const agentId = await spec.SaveToDatabase();
|
|
392
|
+
* console.log('Saved with ID:', agentId);
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
async SaveToDatabase(validate = true) {
|
|
396
|
+
if (!this._isDirty && this._isLoaded) {
|
|
397
|
+
// No changes to save
|
|
398
|
+
return this.spec.ID;
|
|
399
|
+
}
|
|
400
|
+
// Step 1: Save main agent entity
|
|
401
|
+
const agentId = await this.saveAgentEntity(validate);
|
|
402
|
+
// Step 2: Save actions
|
|
403
|
+
await this.saveActions(agentId);
|
|
404
|
+
// Step 3: Save sub-agents (both child and related)
|
|
405
|
+
await this.saveSubAgents(agentId);
|
|
406
|
+
// Step 4: Save prompts
|
|
407
|
+
await this.savePrompts(agentId);
|
|
408
|
+
this._isDirty = false;
|
|
409
|
+
this._isLoaded = true;
|
|
410
|
+
return agentId;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Save the main AIAgent entity.
|
|
414
|
+
*
|
|
415
|
+
* @private
|
|
416
|
+
* @param validate - Whether to perform validation before saving
|
|
417
|
+
* @returns Promise resolving to the saved agent ID
|
|
418
|
+
* @throws {Error} If validation fails or save fails
|
|
419
|
+
*/
|
|
420
|
+
async saveAgentEntity(validate) {
|
|
421
|
+
const md = new core_1.Metadata();
|
|
422
|
+
const agentEntity = await md.GetEntityObject('AI Agents', this._contextUser);
|
|
423
|
+
// If ID exists, load existing record
|
|
424
|
+
if (this.spec.ID) {
|
|
425
|
+
const loaded = await agentEntity.Load(this.spec.ID);
|
|
426
|
+
if (!loaded) {
|
|
427
|
+
throw new Error(`Cannot update non-existent agent with ID ${this.spec.ID}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Map spec to entity fields
|
|
431
|
+
agentEntity.Name = this.spec.Name;
|
|
432
|
+
agentEntity.Description = this.spec.Description || null;
|
|
433
|
+
agentEntity.IconClass = this.spec.IconClass || null;
|
|
434
|
+
agentEntity.LogoURL = this.spec.LogoURL || null;
|
|
435
|
+
agentEntity.ParentID = this.spec.ParentID || null;
|
|
436
|
+
agentEntity.DriverClass = this.spec.DriverClass || null;
|
|
437
|
+
agentEntity.ModelSelectionMode = this.spec.ModelSelectionMode || 'Agent Type';
|
|
438
|
+
// Serialize JSON fields
|
|
439
|
+
agentEntity.PayloadDownstreamPaths = JSON.stringify(this.spec.PayloadDownstreamPaths || ['*']);
|
|
440
|
+
agentEntity.PayloadUpstreamPaths = JSON.stringify(this.spec.PayloadUpstreamPaths || ['*']);
|
|
441
|
+
agentEntity.PayloadSelfReadPaths = this.spec.PayloadSelfReadPaths
|
|
442
|
+
? JSON.stringify(this.spec.PayloadSelfReadPaths)
|
|
443
|
+
: null;
|
|
444
|
+
agentEntity.PayloadSelfWritePaths = this.spec.PayloadSelfWritePaths
|
|
445
|
+
? JSON.stringify(this.spec.PayloadSelfWritePaths)
|
|
446
|
+
: null;
|
|
447
|
+
agentEntity.PayloadScope = this.spec.PayloadScope || null;
|
|
448
|
+
// Validation fields
|
|
449
|
+
agentEntity.FinalPayloadValidation = this.spec.FinalPayloadValidation || null;
|
|
450
|
+
agentEntity.FinalPayloadValidationMode = this.spec.FinalPayloadValidationMode || 'Retry';
|
|
451
|
+
agentEntity.FinalPayloadValidationMaxRetries = this.spec.FinalPayloadValidationMaxRetries || 3;
|
|
452
|
+
agentEntity.StartingPayloadValidation = this.spec.StartingPayloadValidation || null;
|
|
453
|
+
agentEntity.StartingPayloadValidationMode = this.spec.StartingPayloadValidationMode || 'Fail';
|
|
454
|
+
// Resource limits
|
|
455
|
+
agentEntity.MaxCostPerRun = this.spec.MaxCostPerRun || null;
|
|
456
|
+
agentEntity.MaxTokensPerRun = this.spec.MaxTokensPerRun || null;
|
|
457
|
+
agentEntity.MaxIterationsPerRun = this.spec.MaxIterationsPerRun || null;
|
|
458
|
+
agentEntity.MaxTimePerRun = this.spec.MaxTimePerRun || null;
|
|
459
|
+
// Execution frequency
|
|
460
|
+
agentEntity.MinExecutionsPerRun = this.spec.MinExecutionsPerRun || null;
|
|
461
|
+
agentEntity.MaxExecutionsPerRun = this.spec.MaxExecutionsPerRun || null;
|
|
462
|
+
// Other config
|
|
463
|
+
agentEntity.DefaultPromptEffortLevel = this.spec.DefaultPromptEffortLevel || null;
|
|
464
|
+
agentEntity.ChatHandlingOption = this.spec.ChatHandlingOption || null;
|
|
465
|
+
agentEntity.DefaultArtifactTypeID = this.spec.DefaultArtifactTypeID || null;
|
|
466
|
+
if (this.spec.OwnerUserID) {
|
|
467
|
+
agentEntity.OwnerUserID = this.spec.OwnerUserID;
|
|
468
|
+
}
|
|
469
|
+
agentEntity.InvocationMode = this.spec.InvocationMode || 'Any';
|
|
470
|
+
// Validate if requested
|
|
471
|
+
if (validate) {
|
|
472
|
+
const validation = agentEntity.Validate();
|
|
473
|
+
if (!validation.Success) {
|
|
474
|
+
const errors = validation.Errors.map(e => e.Message).join(', ');
|
|
475
|
+
throw new Error(`Agent validation failed: ${errors}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Save
|
|
479
|
+
const saved = await agentEntity.Save();
|
|
480
|
+
if (!saved) {
|
|
481
|
+
throw new Error('Failed to save agent entity');
|
|
482
|
+
}
|
|
483
|
+
// Update spec with saved ID
|
|
484
|
+
this.spec.ID = agentEntity.ID;
|
|
485
|
+
return agentEntity.ID;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Save all actions for this agent.
|
|
489
|
+
*
|
|
490
|
+
* Creates or updates agent action records. Existing actions are updated, new actions
|
|
491
|
+
* are created. This method does not delete actions that are no longer in the spec -
|
|
492
|
+
* use a separate delete method for that.
|
|
493
|
+
*
|
|
494
|
+
* @private
|
|
495
|
+
* @param agentId - The parent agent ID
|
|
496
|
+
* @throws {Error} If any action save fails
|
|
497
|
+
*/
|
|
498
|
+
async saveActions(agentId) {
|
|
499
|
+
if (!this.spec.Actions || this.spec.Actions.length === 0) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const md = new core_1.Metadata();
|
|
503
|
+
for (const actionSpec of this.spec.Actions) {
|
|
504
|
+
const actionEntity = await md.GetEntityObject('AI Agent Actions', this._contextUser);
|
|
505
|
+
// Load existing if ID provided
|
|
506
|
+
if (actionSpec.AgentActionID) {
|
|
507
|
+
await actionEntity.Load(actionSpec.AgentActionID);
|
|
508
|
+
}
|
|
509
|
+
// Map fields
|
|
510
|
+
actionEntity.AgentID = agentId;
|
|
511
|
+
actionEntity.ActionID = actionSpec.ActionID;
|
|
512
|
+
actionEntity.Status = actionSpec.Status;
|
|
513
|
+
actionEntity.MaxExecutionsPerRun = actionSpec.MaxExecutionsPerRun || null;
|
|
514
|
+
actionEntity.ResultExpirationTurns = actionSpec.ResultExpirationTurns || null;
|
|
515
|
+
actionEntity.ResultExpirationMode = actionSpec.ResultExpirationMode || 'None';
|
|
516
|
+
actionEntity.CompactMode = actionSpec.CompactMode || null;
|
|
517
|
+
actionEntity.CompactLength = actionSpec.CompactLength || null;
|
|
518
|
+
actionEntity.CompactPromptID = actionSpec.CompactPromptID || null;
|
|
519
|
+
const saved = await actionEntity.Save();
|
|
520
|
+
if (!saved) {
|
|
521
|
+
throw new Error(`Failed to save action ${actionSpec.ActionID}`);
|
|
522
|
+
}
|
|
523
|
+
// Update spec with saved ID
|
|
524
|
+
actionSpec.AgentActionID = actionEntity.ID;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Save all sub-agents (both child and related types).
|
|
529
|
+
*
|
|
530
|
+
* Handles both ParentID-based child agents and relationship-based related agents.
|
|
531
|
+
* For child agents, updates the ParentID on the sub-agent entity. For related agents,
|
|
532
|
+
* creates or updates the relationship record.
|
|
533
|
+
*
|
|
534
|
+
* @private
|
|
535
|
+
* @param agentId - The parent agent ID
|
|
536
|
+
* @throws {Error} If any sub-agent save fails
|
|
537
|
+
*/
|
|
538
|
+
async saveSubAgents(agentId) {
|
|
539
|
+
if (!this.spec.SubAgents || this.spec.SubAgents.length === 0) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
for (const SubAgentSpec of this.spec.SubAgents) {
|
|
543
|
+
if (SubAgentSpec.Type === 'child') {
|
|
544
|
+
await this.saveChildSubAgent(agentId, SubAgentSpec);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
await this.saveRelatedSubAgent(agentId, SubAgentSpec);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Save a child sub-agent (ParentID-based relationship).
|
|
553
|
+
*
|
|
554
|
+
* For child agents, the relationship is established by setting the ParentID field
|
|
555
|
+
* on the child agent entity. If the SubAgent.ID is empty, this creates a new
|
|
556
|
+
* child agent recursively using AgentSpecSync.
|
|
557
|
+
*
|
|
558
|
+
* This enables creating complete agent hierarchies in one call - sub-agents are
|
|
559
|
+
* created first (depth-first), then parent references them via ParentID.
|
|
560
|
+
*
|
|
561
|
+
* @private
|
|
562
|
+
* @param parentId - The parent agent ID
|
|
563
|
+
* @param SubAgentSpec - The sub-agent specification
|
|
564
|
+
* @throws {Error} If child agent save fails
|
|
565
|
+
*/
|
|
566
|
+
async saveChildSubAgent(parentId, SubAgentSpec) {
|
|
567
|
+
console.log(`🔗 saveChildSubAgent: Processing child "${SubAgentSpec.SubAgent?.Name}", ID="${SubAgentSpec.SubAgent?.ID}"`);
|
|
568
|
+
// If SubAgent.ID is empty or missing, create the sub-agent recursively
|
|
569
|
+
if (!SubAgentSpec.SubAgent?.ID || SubAgentSpec.SubAgent.ID === '') {
|
|
570
|
+
console.log(`🔨 saveChildSubAgent: Creating new child sub-agent "${SubAgentSpec.SubAgent.Name}"...`);
|
|
571
|
+
// Set ParentID in the sub-agent spec before creating it
|
|
572
|
+
const childSpec = {
|
|
573
|
+
...SubAgentSpec.SubAgent,
|
|
574
|
+
ParentID: parentId
|
|
575
|
+
};
|
|
576
|
+
// Recursively create the sub-agent using AgentSpecSync
|
|
577
|
+
const childSync = new AgentSpecSync(childSpec, this._contextUser);
|
|
578
|
+
childSync.markDirty();
|
|
579
|
+
const childId = await childSync.SaveToDatabase();
|
|
580
|
+
// Update the SubAgentSpec with the created ID so parent can reference it
|
|
581
|
+
SubAgentSpec.SubAgent.ID = childId;
|
|
582
|
+
console.log(`✅ saveChildSubAgent: Created child sub-agent with ID: ${childId}`);
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
// SubAgent already exists - just ensure ParentID is set correctly
|
|
586
|
+
console.log(`🔗 saveChildSubAgent: Updating existing child sub-agent "${SubAgentSpec.SubAgent.ID}"...`);
|
|
587
|
+
const md = new core_1.Metadata();
|
|
588
|
+
const childEntity = await md.GetEntityObject('AI Agents', this._contextUser);
|
|
589
|
+
const loaded = await childEntity.Load(SubAgentSpec.SubAgent.ID);
|
|
590
|
+
if (!loaded) {
|
|
591
|
+
throw new Error(`Child agent ${SubAgentSpec.SubAgent.ID} not found in database`);
|
|
592
|
+
}
|
|
593
|
+
// Update parent ID if needed
|
|
594
|
+
if (childEntity.ParentID !== parentId) {
|
|
595
|
+
console.log(`🔗 saveChildSubAgent: Setting ParentID to ${parentId}`);
|
|
596
|
+
childEntity.ParentID = parentId;
|
|
597
|
+
const saved = await childEntity.Save();
|
|
598
|
+
if (!saved) {
|
|
599
|
+
throw new Error(`Failed to update ParentID for child agent ${SubAgentSpec.SubAgent.ID}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
console.log(`✅ saveChildSubAgent: ParentID already correct`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Save a related sub-agent (relationship-based).
|
|
609
|
+
*
|
|
610
|
+
* Creates or updates an AIAgentRelationship record that links the parent and
|
|
611
|
+
* sub-agent. This includes the input/output mapping and context path configurations.
|
|
612
|
+
*
|
|
613
|
+
* @private
|
|
614
|
+
* @param agentId - The parent agent ID
|
|
615
|
+
* @param SubAgentSpec - The sub-agent specification
|
|
616
|
+
* @throws {Error} If relationship save fails
|
|
617
|
+
*/
|
|
618
|
+
async saveRelatedSubAgent(agentId, SubAgentSpec) {
|
|
619
|
+
const md = new core_1.Metadata();
|
|
620
|
+
const relationshipEntity = await md.GetEntityObject('MJ: AI Agent Relationships', this._contextUser);
|
|
621
|
+
// Load existing if ID provided
|
|
622
|
+
if (SubAgentSpec.AgentRelationshipID) {
|
|
623
|
+
await relationshipEntity.Load(SubAgentSpec.AgentRelationshipID);
|
|
624
|
+
}
|
|
625
|
+
// Map fields
|
|
626
|
+
relationshipEntity.AgentID = agentId;
|
|
627
|
+
relationshipEntity.SubAgentID = SubAgentSpec.SubAgent.ID;
|
|
628
|
+
relationshipEntity.Status = 'Active';
|
|
629
|
+
// Serialize mapping fields
|
|
630
|
+
if (SubAgentSpec.SubAgentInputMapping) {
|
|
631
|
+
relationshipEntity.SubAgentInputMapping = JSON.stringify(SubAgentSpec.SubAgentInputMapping);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
relationshipEntity.SubAgentInputMapping = null;
|
|
635
|
+
}
|
|
636
|
+
if (SubAgentSpec.SubAgentOutputMapping) {
|
|
637
|
+
relationshipEntity.SubAgentOutputMapping = JSON.stringify(SubAgentSpec.SubAgentOutputMapping);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
relationshipEntity.SubAgentOutputMapping = null;
|
|
641
|
+
}
|
|
642
|
+
if (SubAgentSpec.SubAgentContextPaths) {
|
|
643
|
+
relationshipEntity.SubAgentContextPaths = JSON.stringify(SubAgentSpec.SubAgentContextPaths);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
relationshipEntity.SubAgentContextPaths = null;
|
|
647
|
+
}
|
|
648
|
+
const saved = await relationshipEntity.Save();
|
|
649
|
+
if (!saved) {
|
|
650
|
+
throw new Error(`Failed to save relationship for sub-agent ${SubAgentSpec.SubAgent.ID}`);
|
|
651
|
+
}
|
|
652
|
+
// Update spec with saved ID
|
|
653
|
+
SubAgentSpec.AgentRelationshipID = relationshipEntity.ID;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Save all prompts for this agent.
|
|
657
|
+
*
|
|
658
|
+
* Creates AIPrompt records (with template) and AIAgentPrompt junction records.
|
|
659
|
+
* Supports simplified prompt format from Architect Agent with just PromptText,
|
|
660
|
+
* PromptRole, and PromptPosition.
|
|
661
|
+
*
|
|
662
|
+
* @private
|
|
663
|
+
* @param agentId - The parent agent ID
|
|
664
|
+
* @throws {Error} If any prompt save fails
|
|
665
|
+
*/
|
|
666
|
+
async savePrompts(agentId) {
|
|
667
|
+
console.log(`💬 savePrompts: Called with agentId=${agentId}, Prompts=${this.spec.Prompts ? this.spec.Prompts.length : 'undefined'}`);
|
|
668
|
+
if (!this.spec.Prompts || this.spec.Prompts.length === 0) {
|
|
669
|
+
console.log('💬 savePrompts: No prompts to save, returning early');
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
console.log(`💬 savePrompts: Processing ${this.spec.Prompts.length} prompt(s)...`);
|
|
673
|
+
const md = new core_1.Metadata();
|
|
674
|
+
for (let i = 0; i < this.spec.Prompts.length; i++) {
|
|
675
|
+
const promptSpec = this.spec.Prompts[i];
|
|
676
|
+
// Create AIPrompt entity
|
|
677
|
+
const promptEntity = await md.GetEntityObject('AI Prompts', this._contextUser);
|
|
678
|
+
// Set required fields
|
|
679
|
+
promptEntity.Name = `${this.spec.Name} - Prompt ${i + 1}`;
|
|
680
|
+
promptEntity.Description = `Agent prompt ${i + 1} for ${this.spec.Name}`;
|
|
681
|
+
promptEntity.TypeID = 'a6da423e-f36b-1410-8dac-00021f8b792e'; // Chat type
|
|
682
|
+
promptEntity.Status = 'Active';
|
|
683
|
+
promptEntity.ResponseFormat = 'JSON';
|
|
684
|
+
// Handle prompt text - supports both string and object formats
|
|
685
|
+
if (typeof promptSpec.PromptText === 'string') {
|
|
686
|
+
promptEntity.TemplateText = promptSpec.PromptText;
|
|
687
|
+
}
|
|
688
|
+
else if (typeof promptSpec.PromptText === 'object') {
|
|
689
|
+
// Architect may send PromptText as {text: "...", json: {...}}
|
|
690
|
+
const promptTextObj = promptSpec.PromptText;
|
|
691
|
+
let combinedText = promptTextObj.text || '';
|
|
692
|
+
if (promptTextObj.json) {
|
|
693
|
+
combinedText += '\n\n```json\n' + JSON.stringify(promptTextObj.json, null, 2) + '\n```';
|
|
694
|
+
}
|
|
695
|
+
promptEntity.TemplateText = combinedText;
|
|
696
|
+
}
|
|
697
|
+
// Set prompt role and position if provided
|
|
698
|
+
if (promptSpec.PromptRole) {
|
|
699
|
+
promptEntity.PromptRole = promptSpec.PromptRole;
|
|
700
|
+
}
|
|
701
|
+
if (promptSpec.PromptPosition) {
|
|
702
|
+
promptEntity.PromptPosition = promptSpec.PromptPosition;
|
|
703
|
+
}
|
|
704
|
+
const saved = await promptEntity.Save();
|
|
705
|
+
if (!saved) {
|
|
706
|
+
throw new Error(`Failed to save prompt ${i + 1} for agent ${this.spec.Name}`);
|
|
707
|
+
}
|
|
708
|
+
console.log(`✅ savePrompts: Created AIPrompt with ID: ${promptEntity.ID}`);
|
|
709
|
+
// Create AIAgentPrompt junction
|
|
710
|
+
const agentPromptEntity = await md.GetEntityObject('MJ: AI Agent Prompts', this._contextUser);
|
|
711
|
+
agentPromptEntity.AgentID = agentId;
|
|
712
|
+
agentPromptEntity.PromptID = promptEntity.ID;
|
|
713
|
+
agentPromptEntity.ExecutionOrder = i;
|
|
714
|
+
agentPromptEntity.Status = 'Active';
|
|
715
|
+
const junctionSaved = await agentPromptEntity.Save();
|
|
716
|
+
if (!junctionSaved) {
|
|
717
|
+
throw new Error(`Failed to save agent-prompt junction for prompt ${i + 1}`);
|
|
718
|
+
}
|
|
719
|
+
console.log(`✅ savePrompts: Created AIAgentPrompt junction with ID: ${agentPromptEntity.ID}`);
|
|
720
|
+
}
|
|
721
|
+
console.log(`✅ savePrompts: Successfully saved all ${this.spec.Prompts.length} prompt(s)`);
|
|
722
|
+
}
|
|
723
|
+
// ===== UTILITY METHODS =====
|
|
724
|
+
/**
|
|
725
|
+
* Parse a JSON string field, returning undefined if null/empty.
|
|
726
|
+
*
|
|
727
|
+
* Safely parses JSON fields from the database, handling null/undefined values
|
|
728
|
+
* and logging errors if parsing fails.
|
|
729
|
+
*
|
|
730
|
+
* @private
|
|
731
|
+
* @param jsonString - The JSON string to parse
|
|
732
|
+
* @returns Parsed object or undefined if null/empty/invalid
|
|
733
|
+
*/
|
|
734
|
+
parseJsonField(jsonString) {
|
|
735
|
+
if (!jsonString)
|
|
736
|
+
return undefined;
|
|
737
|
+
try {
|
|
738
|
+
return JSON.parse(jsonString);
|
|
739
|
+
}
|
|
740
|
+
catch (error) {
|
|
741
|
+
(0, core_1.LogError)(`Failed to parse JSON field: ${error}`);
|
|
742
|
+
return undefined;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Initialize a spec with defaults for any missing required fields.
|
|
747
|
+
*
|
|
748
|
+
* Takes a partial spec and fills in defaults for any missing fields to ensure
|
|
749
|
+
* a valid AgentSpec structure.
|
|
750
|
+
*
|
|
751
|
+
* @private
|
|
752
|
+
* @param partial - Partial agent specification
|
|
753
|
+
* @returns Complete AgentSpec with defaults
|
|
754
|
+
*/
|
|
755
|
+
initializeSpec(partial) {
|
|
756
|
+
return {
|
|
757
|
+
ID: partial.ID || '',
|
|
758
|
+
Name: partial.Name || '',
|
|
759
|
+
Description: partial.Description,
|
|
760
|
+
IconClass: partial.IconClass,
|
|
761
|
+
LogoURL: partial.LogoURL,
|
|
762
|
+
ParentID: partial.ParentID,
|
|
763
|
+
DriverClass: partial.DriverClass,
|
|
764
|
+
ModelSelectionMode: partial.ModelSelectionMode || 'Agent Type',
|
|
765
|
+
PayloadDownstreamPaths: partial.PayloadDownstreamPaths,
|
|
766
|
+
PayloadUpstreamPaths: partial.PayloadUpstreamPaths,
|
|
767
|
+
PayloadSelfReadPaths: partial.PayloadSelfReadPaths,
|
|
768
|
+
PayloadSelfWritePaths: partial.PayloadSelfWritePaths,
|
|
769
|
+
PayloadScope: partial.PayloadScope,
|
|
770
|
+
FinalPayloadValidation: partial.FinalPayloadValidation,
|
|
771
|
+
FinalPayloadValidationMode: partial.FinalPayloadValidationMode || 'Retry',
|
|
772
|
+
FinalPayloadValidationMaxRetries: partial.FinalPayloadValidationMaxRetries,
|
|
773
|
+
MaxCostPerRun: partial.MaxCostPerRun,
|
|
774
|
+
MaxTokensPerRun: partial.MaxTokensPerRun,
|
|
775
|
+
MaxIterationsPerRun: partial.MaxIterationsPerRun,
|
|
776
|
+
MaxTimePerRun: partial.MaxTimePerRun,
|
|
777
|
+
MinExecutionsPerRun: partial.MinExecutionsPerRun,
|
|
778
|
+
MaxExecutionsPerRun: partial.MaxExecutionsPerRun,
|
|
779
|
+
StartingPayloadValidation: partial.StartingPayloadValidation,
|
|
780
|
+
StartingPayloadValidationMode: partial.StartingPayloadValidationMode || 'Fail',
|
|
781
|
+
DefaultPromptEffortLevel: partial.DefaultPromptEffortLevel,
|
|
782
|
+
ChatHandlingOption: partial.ChatHandlingOption,
|
|
783
|
+
DefaultArtifactTypeID: partial.DefaultArtifactTypeID,
|
|
784
|
+
OwnerUserID: partial.OwnerUserID,
|
|
785
|
+
InvocationMode: partial.InvocationMode || 'Any',
|
|
786
|
+
Actions: partial.Actions || [],
|
|
787
|
+
SubAgents: partial.SubAgents || [],
|
|
788
|
+
Prompts: partial.Prompts || []
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Get a clean serializable version of the spec.
|
|
793
|
+
*
|
|
794
|
+
* Returns a plain JavaScript object suitable for JSON serialization,
|
|
795
|
+
* API responses, or storage. This is useful when you need to send the
|
|
796
|
+
* agent spec over the wire or store it in a file.
|
|
797
|
+
*
|
|
798
|
+
* @returns Clean copy of the agent specification
|
|
799
|
+
*
|
|
800
|
+
* @example
|
|
801
|
+
* ```typescript
|
|
802
|
+
* const spec = await AgentSpecSync.LoadFromDatabase('agent-uuid', contextUser);
|
|
803
|
+
* const json = spec.toJSON();
|
|
804
|
+
* res.json(json); // Send as API response
|
|
805
|
+
* ```
|
|
806
|
+
*/
|
|
807
|
+
toJSON() {
|
|
808
|
+
return { ...this.spec };
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Check if this spec has unsaved changes.
|
|
812
|
+
*
|
|
813
|
+
* @returns True if there are unsaved changes, false otherwise
|
|
814
|
+
*/
|
|
815
|
+
get isDirty() {
|
|
816
|
+
return this._isDirty;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Check if this spec has been loaded from the database.
|
|
820
|
+
*
|
|
821
|
+
* @returns True if loaded from database, false if created in memory
|
|
822
|
+
*/
|
|
823
|
+
get isLoaded() {
|
|
824
|
+
return this._isLoaded;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Mark the spec as having changes.
|
|
828
|
+
*
|
|
829
|
+
* Call this method after modifying the spec to indicate that changes need to be saved.
|
|
830
|
+
* The spec is automatically marked dirty when created with the constructor, but if you
|
|
831
|
+
* load a spec and then modify it, you should call this method.
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* ```typescript
|
|
835
|
+
* const spec = await AgentSpecSync.LoadFromDatabase('agent-uuid', contextUser);
|
|
836
|
+
* spec.spec.Description = 'New description';
|
|
837
|
+
* spec.markDirty();
|
|
838
|
+
* await spec.SaveToDatabase();
|
|
839
|
+
* ```
|
|
840
|
+
*/
|
|
841
|
+
markDirty() {
|
|
842
|
+
this._isDirty = true;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
exports.AgentSpecSync = AgentSpecSync;
|
|
846
|
+
//# sourceMappingURL=agent-spec-sync.js.map
|