@x12i/ai-gateway 10.4.4 → 11.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.
@@ -5,19 +5,38 @@
5
5
  */
6
6
  import { parseTemplate } from './template-parser.js';
7
7
  import { mergeGatewayAndRequestTemplateRenderOptions } from './template-render-merge.js';
8
- // Type guard
9
- // AIRequest is distinguished by having primaryObjectType or objectTypes
10
- // ChatRequest does not have these fields
11
- function isAIRequest(request) {
12
- return 'primaryObjectType' in request || ('objectTypes' in request && Array.isArray(request.objectTypes));
8
+ function hasNonEmptyPrompt(prompt) {
9
+ return typeof prompt === 'string' && prompt.trim() !== '';
10
+ }
11
+ function getWorkingMemoryInput(workingMemory) {
12
+ if (workingMemory == null || typeof workingMemory !== 'object')
13
+ return undefined;
14
+ return workingMemory.input;
15
+ }
16
+ function hasResolvableInput(workingMemory) {
17
+ const input = getWorkingMemoryInput(workingMemory);
18
+ return input !== undefined && input !== null;
19
+ }
20
+ /**
21
+ * Serializes workingMemory.input when no prompt template is provided.
22
+ */
23
+ export function formatInputFallback(input) {
24
+ if (input === undefined || input === null) {
25
+ return '';
26
+ }
27
+ if (typeof input === 'string') {
28
+ return input;
29
+ }
30
+ if (typeof input === 'object') {
31
+ return JSON.stringify(input, null, 2);
32
+ }
33
+ return String(input);
13
34
  }
14
35
  /**
15
- * Builds user message (prompt + input)
36
+ * Builds user message from prompt template or workingMemory.input fallback.
16
37
  */
17
38
  async function buildUserMessage(request, config, shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions) {
18
39
  const { logger } = config;
19
- const parts = [];
20
- // Validate that input field is not provided (removed - use workingMemory.input instead)
21
40
  if ('input' in request && request.input !== undefined) {
22
41
  const err = new Error(`The 'input' field has been removed. Use workingMemory.input instead for template rendering. Prompt templates should contain {{input}} which will be resolved from workingMemory.input.`);
23
42
  err.code = 'INPUT_FIELD_DEPRECATED';
@@ -32,213 +51,127 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
32
51
  errorCode: 'INPUT_FIELD_DEPRECATED',
33
52
  hasPrompt: !!request.prompt,
34
53
  hasWorkingMemory: !!request.workingMemory,
35
- hasInputInWorkingMemory: !!request.workingMemory?.input
54
+ hasInputInWorkingMemory: hasResolvableInput(request.workingMemory)
36
55
  });
37
56
  throw err;
38
57
  }
39
- // Determine if we have user content to process (prompt is required for user message)
40
- const hasUserContent = isAIRequest(request) && request.prompt;
41
- // Input prefix is no longer used - prompt templates handle all formatting
42
- // If no prompt is provided, that's an error (prompt is required for user message)
43
- if (isAIRequest(request) && !request.prompt) {
44
- const err = new Error(`Prompt is required for AI requests. Provide prompt template text. The template should contain variables such as {{input}} resolved from workingMemory.input.`);
45
- err.code = 'PROMPT_REQUIRED';
46
- err.details = {
47
- missingField: 'prompt',
48
- hasWorkingMemory: !!request.workingMemory,
49
- hasInputInWorkingMemory: !!request.workingMemory?.input
50
- };
51
- logger.error('Prompt is required for AI requests', {
58
+ if (hasNonEmptyPrompt(request.prompt)) {
59
+ if (typeof request.prompt !== 'string') {
60
+ throw new Error(`Prompt must be a string template, but received: ${typeof request.prompt}`);
61
+ }
62
+ logger.info('Parsing prompt template text', {
52
63
  jobId: request.identity.jobId,
53
64
  agentId: request.agentId,
54
- errorCode: 'PROMPT_REQUIRED'
65
+ promptLength: request.prompt.length,
66
+ promptPreview: request.prompt.substring(0, 200),
67
+ hasWorkingMemory: !!request.workingMemory
55
68
  });
56
- throw err;
57
- }
58
- // Add prompt if provided (always template text; parsed with memory context)
59
- if (request.prompt) {
60
- if (typeof request.prompt === 'string') {
61
- logger.info('Parsing prompt template text', {
62
- jobId: request.identity.jobId,
63
- agentId: request.agentId,
64
- promptLength: request.prompt.length,
65
- promptPreview: request.prompt.substring(0, 200),
66
- hasWorkingMemory: !!request.workingMemory
67
- });
68
- try {
69
- if (!request.workingMemory) {
70
- const err = new Error(`workingMemory is required for prompt template rendering but was not provided.`);
71
- err.code = 'WORKING_MEMORY_REQUIRED';
72
- err.details = { requiresVariables: true };
73
- logger.error('workingMemory required for prompt template rendering', {
74
- jobId: request.identity.jobId,
75
- agentId: request.agentId,
76
- errorCode: 'WORKING_MEMORY_REQUIRED'
77
- });
78
- throw err;
79
- }
80
- const parsedPrompt = await parseTemplate(request.prompt, request.workingMemory, undefined, shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger);
81
- if (!parsedPrompt || parsedPrompt.trim() === '') {
82
- const workingMemoryObj = request.workingMemory;
83
- const err = new Error(`Prompt template rendered to empty string. This may indicate missing template variables or empty template content.`);
84
- err.code = 'PROMPT_RENDERED_EMPTY';
85
- err.details = {
86
- promptLength: request.prompt.length,
87
- renderedLength: 0,
88
- hasWorkingMemory: !!request.workingMemory,
89
- workingMemoryKeys: workingMemoryObj ? Object.keys(workingMemoryObj) : []
90
- };
91
- logger.error('Prompt template rendered to empty string', {
92
- jobId: request.identity.jobId,
93
- agentId: request.agentId,
94
- errorCode: 'PROMPT_RENDERED_EMPTY',
95
- hasWorkingMemory: !!request.workingMemory,
96
- workingMemoryKeys: workingMemoryObj ? Object.keys(workingMemoryObj) : []
97
- });
98
- throw err;
99
- }
100
- logger.info('Prompt text parsed successfully', {
69
+ try {
70
+ if (!request.workingMemory) {
71
+ const err = new Error(`workingMemory is required for prompt template rendering but was not provided.`);
72
+ err.code = 'WORKING_MEMORY_REQUIRED';
73
+ err.details = { requiresVariables: true };
74
+ logger.error('workingMemory required for prompt template rendering', {
101
75
  jobId: request.identity.jobId,
102
76
  agentId: request.agentId,
103
- originalLength: request.prompt.length,
104
- parsedLength: parsedPrompt.length,
105
- parsedPreview: parsedPrompt.substring(0, 200)
77
+ errorCode: 'WORKING_MEMORY_REQUIRED'
106
78
  });
107
- parts.push(parsedPrompt);
79
+ throw err;
108
80
  }
109
- catch (error) {
110
- const err = error instanceof Error ? error : new Error(String(error));
111
- let errorCode = 'PROMPT_TEMPLATE_ERROR';
112
- let errorMessage = `Failed to render prompt template: ${err.message}`;
113
- if (err.message.includes('not found') || err.message.includes('does not exist')) {
114
- errorCode = 'PROMPT_NOT_FOUND';
115
- errorMessage = err.message;
116
- }
117
- else if (err.message.includes('rendered to empty')) {
118
- errorCode = 'PROMPT_RENDERED_EMPTY';
119
- errorMessage = err.message;
120
- }
121
- else if (err.name === 'TemplateResolutionError') {
122
- errorCode = 'TEMPLATE_RESOLUTION_ERROR';
123
- errorMessage = err.message;
124
- }
125
- else if (err.message.includes('Template variable') || err.message.includes('missing')) {
126
- errorCode = 'TEMPLATE_VARIABLE_MISSING';
127
- errorMessage = err.message;
128
- }
129
- logger.error('Failed to render prompt template', {
81
+ const parsedPrompt = await parseTemplate(request.prompt, request.workingMemory, undefined, shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger);
82
+ if (!parsedPrompt || parsedPrompt.trim() === '') {
83
+ const workingMemoryObj = request.workingMemory;
84
+ const err = new Error(`Prompt template rendered to empty string. This may indicate missing template variables or empty template content.`);
85
+ err.code = 'PROMPT_RENDERED_EMPTY';
86
+ err.details = {
87
+ promptLength: request.prompt.length,
88
+ renderedLength: 0,
89
+ hasWorkingMemory: !!request.workingMemory,
90
+ workingMemoryKeys: workingMemoryObj ? Object.keys(workingMemoryObj) : []
91
+ };
92
+ logger.error('Prompt template rendered to empty string', {
130
93
  jobId: request.identity.jobId,
131
94
  agentId: request.agentId,
132
- errorCode,
133
- error: err.message,
134
- errorName: err.name,
135
- errorStack: err.stack
95
+ errorCode: 'PROMPT_RENDERED_EMPTY',
96
+ hasWorkingMemory: !!request.workingMemory,
97
+ workingMemoryKeys: workingMemoryObj ? Object.keys(workingMemoryObj) : []
136
98
  });
137
- const structuredError = new Error(errorMessage);
138
- structuredError.code = errorCode;
139
- structuredError.details = {
140
- type: 'prompt',
141
- originalError: err.message
142
- };
143
- throw structuredError;
99
+ throw err;
144
100
  }
101
+ logger.info('Prompt text parsed successfully', {
102
+ jobId: request.identity.jobId,
103
+ agentId: request.agentId,
104
+ originalLength: request.prompt.length,
105
+ parsedLength: parsedPrompt.length,
106
+ parsedPreview: parsedPrompt.substring(0, 200)
107
+ });
108
+ return parsedPrompt;
145
109
  }
146
- else {
147
- const err = new Error(`Prompt must be a string template, but received: ${typeof request.prompt}`);
148
- logger.error('Prompt provided as non-string - not supported', {
110
+ catch (error) {
111
+ const err = error instanceof Error ? error : new Error(String(error));
112
+ let errorCode = 'PROMPT_TEMPLATE_ERROR';
113
+ let errorMessage = `Failed to render prompt template: ${err.message}`;
114
+ if (err.message.includes('not found') || err.message.includes('does not exist')) {
115
+ errorCode = 'PROMPT_NOT_FOUND';
116
+ errorMessage = err.message;
117
+ }
118
+ else if (err.message.includes('rendered to empty')) {
119
+ errorCode = 'PROMPT_RENDERED_EMPTY';
120
+ errorMessage = err.message;
121
+ }
122
+ else if (err.name === 'TemplateResolutionError') {
123
+ errorCode = 'TEMPLATE_RESOLUTION_ERROR';
124
+ errorMessage = err.message;
125
+ }
126
+ else if (err.message.includes('Template variable') || err.message.includes('missing')) {
127
+ errorCode = 'TEMPLATE_VARIABLE_MISSING';
128
+ errorMessage = err.message;
129
+ }
130
+ logger.error('Failed to render prompt template', {
149
131
  jobId: request.identity.jobId,
150
132
  agentId: request.agentId,
151
- promptType: typeof request.prompt
133
+ errorCode,
134
+ error: err.message,
135
+ errorName: err.name,
136
+ errorStack: err.stack
152
137
  });
153
- throw err;
138
+ const structuredError = new Error(errorMessage);
139
+ structuredError.code = errorCode;
140
+ structuredError.details = {
141
+ type: 'prompt',
142
+ originalError: err.message
143
+ };
144
+ throw structuredError;
154
145
  }
155
146
  }
156
- // Input field has been removed - all input must come from workingMemory.input
157
- // Prompt templates should contain {{input}} which will be resolved from workingMemory.input
158
- // No need to add input separately - it's already in the rendered prompt template
159
- const userMessage = parts.join('\n\n');
160
- // If prompt was provided, we MUST have a non-empty user message
161
- if (request.prompt && (!userMessage || userMessage.trim() === '')) {
162
- const err = new Error(`Prompt template was provided but resulted in empty user message. The template may have rendered to empty or failed to resolve.`);
163
- err.code = 'PROMPT_NO_USER_MESSAGE';
164
- err.details = {
165
- promptPreview: typeof request.prompt === 'string' ? request.prompt.substring(0, 200) : request.prompt,
166
- hasWorkingMemory: !!request.workingMemory,
167
- workingMemoryKeys: request.workingMemory ? Object.keys(request.workingMemory) : [],
168
- partsLength: parts.length
169
- };
170
- logger.error('Prompt provided but resulted in empty user message', {
147
+ if (hasResolvableInput(request.workingMemory)) {
148
+ const fallback = formatInputFallback(getWorkingMemoryInput(request.workingMemory));
149
+ if (!fallback.trim()) {
150
+ const err = new Error(`workingMemory.input is present but empty. Provide a prompt template or non-empty workingMemory.input.`);
151
+ err.code = 'USER_CONTENT_REQUIRED';
152
+ throw err;
153
+ }
154
+ logger.info('Using workingMemory.input fallback for user message', {
171
155
  jobId: request.identity.jobId,
172
156
  agentId: request.agentId,
173
- prompt: request.prompt,
174
- errorCode: 'PROMPT_NO_USER_MESSAGE',
175
- partsLength: parts.length,
176
- parts: parts.map(p => ({ length: p.length, preview: p.substring(0, 50) }))
157
+ fallbackLength: fallback.length,
158
+ fallbackPreview: fallback.substring(0, 200)
177
159
  });
178
- throw err;
179
- }
180
- return userMessage;
181
- }
182
- /**
183
- * Checks if instructions already meet the required flex-md compliance level
184
- * Uses flex-md SDK to validate compliance
185
- *
186
- * @param instructionsText - Instructions to check
187
- * @param complianceLevel - Required compliance level (L0, L1, L2, L3)
188
- * @returns true if instructions meet the compliance level
189
- */
190
- async function hasFlexMdContract(instructionsText, complianceLevel = 'L0') {
191
- if (!instructionsText || instructionsText.trim() === '') {
192
- return false;
193
- }
194
- try {
195
- // Try to use flex-md SDK to validate compliance
196
- const { loadFlexMd } = await import('./flex-md-loader.js');
197
- const flexMd = await loadFlexMd();
198
- // Check if validateMarkdownAgainstOfs is available
199
- if (flexMd.validateMarkdownAgainstOfs && typeof flexMd.validateMarkdownAgainstOfs === 'function') {
200
- try {
201
- // Try to validate - this might require a spec, so we catch errors
202
- const result = flexMd.validateMarkdownAgainstOfs(instructionsText, { strictness: complianceLevel });
203
- // If validation passes, instructions meet compliance
204
- if (result && result.valid !== false) {
205
- return true;
206
- }
207
- }
208
- catch (e) {
209
- // Validation function might need more parameters, fall through to pattern matching
210
- }
211
- }
212
- }
213
- catch (error) {
214
- // flex-md SDK not available or error - fall through to pattern matching
215
- }
216
- // Fallback: Pattern-based checking
217
- const text = instructionsText.toLowerCase();
218
- // Check for key flex-md enforcement phrases
219
- const flexMdIndicators = [
220
- 'markdown',
221
- 'fenced block',
222
- '```markdown',
223
- '```json',
224
- 'reply in markdown',
225
- 'return your entire answer'
226
- ];
227
- // Check for enforcement language based on compliance level
228
- const enforcementIndicators = [];
229
- if (complianceLevel === 'L2' || complianceLevel === 'L3') {
230
- // L2/L3 require fenced block container
231
- enforcementIndicators.push('fenced block', '```markdown', 'single ```markdown', 'entire answer inside', 'nothing else');
232
- }
233
- if (complianceLevel === 'L1' || complianceLevel === 'L2' || complianceLevel === 'L3') {
234
- // L1+ requires section headings
235
- enforcementIndicators.push('section', 'heading', 'include these');
160
+ return fallback;
236
161
  }
237
- // Must have at least one flex-md indicator AND appropriate enforcement indicators
238
- const hasFlexMd = flexMdIndicators.some(indicator => text.includes(indicator));
239
- const hasEnforcement = enforcementIndicators.length === 0 ||
240
- enforcementIndicators.some(indicator => text.includes(indicator));
241
- return hasFlexMd && hasEnforcement;
162
+ const err = new Error(`User message requires prompt template text or workingMemory.input. Provide prompt or workingMemory.input.`);
163
+ err.code = 'USER_CONTENT_REQUIRED';
164
+ err.details = {
165
+ hasPrompt: hasNonEmptyPrompt(request.prompt),
166
+ hasWorkingMemory: !!request.workingMemory,
167
+ hasInputInWorkingMemory: false
168
+ };
169
+ logger.error('No prompt or workingMemory.input for user message', {
170
+ jobId: request.identity.jobId,
171
+ agentId: request.agentId,
172
+ errorCode: 'USER_CONTENT_REQUIRED'
173
+ });
174
+ throw err;
242
175
  }
243
176
  /**
244
177
  * Main function to build messages
@@ -247,13 +180,11 @@ export async function buildMessages(request, config, options = {}) {
247
180
  const { parsedSnapshot } = options;
248
181
  const { logger } = config;
249
182
  const messages = [];
250
- // Step 1: Instructions as template text (parsed with full memory context)
251
- let instructionsText = '';
252
- // Extract memory context from options
253
183
  const shortTermMemory = options.shortTermMemory;
254
184
  const experienceMemory = options.experienceMemory;
255
185
  const knowledgeMemory = options.knowledgeMemory;
256
186
  const templateRenderOptions = mergeGatewayAndRequestTemplateRenderOptions(config.templateRendering, request);
187
+ let instructionsText = '';
257
188
  if (request.instructions) {
258
189
  if (typeof request.instructions === 'string') {
259
190
  logger.info('Using instructions as template text', {
@@ -268,16 +199,9 @@ export async function buildMessages(request, config, options = {}) {
268
199
  instructionsText = JSON.stringify(request.instructions);
269
200
  }
270
201
  }
271
- // NO SYSTEM CONTEXT FALLBACK - removed default instruction fallback
272
- // Instructions must be provided explicitly - no defaults allowed
273
- // If instructionsText is empty here, it's a bad request
274
- // Step 2: Parse instructions template with full memory context
275
- // Rendrix handles token resolution, so we just parse directly
276
202
  if (instructionsText) {
277
- instructionsText = await parseTemplate(instructionsText, request.workingMemory, undefined, // taskConfig removed - no longer used
278
- shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger);
203
+ instructionsText = await parseTemplate(instructionsText, request.workingMemory, undefined, shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger);
279
204
  }
280
- // Instructions must be provided explicitly — no packaged block injection
281
205
  if (!instructionsText || instructionsText.trim() === '') {
282
206
  const errorMessage = 'No instructions available - cannot proceed without clear instructions. This is a bad request.';
283
207
  logger.error(errorMessage, {
@@ -292,11 +216,9 @@ export async function buildMessages(request, config, options = {}) {
292
216
  role: 'system',
293
217
  content: instructionsText
294
218
  });
295
- // Store resolved/parsed content for activity (parsed = content after resolution + Rendrix, not the key)
296
219
  if (parsedSnapshot) {
297
220
  parsedSnapshot.instructions = instructionsText;
298
221
  }
299
- // Log the final system message for verification
300
222
  logger.info('Final system message constructed', {
301
223
  jobId: request.identity.jobId,
302
224
  agentId: request.agentId,
@@ -305,65 +227,34 @@ export async function buildMessages(request, config, options = {}) {
305
227
  hasFencedBlock: instructionsText.includes('```markdown'),
306
228
  hasJson: instructionsText.toLowerCase().includes('json'),
307
229
  messagePreview: instructionsText.substring(0, 500),
308
- fullMessage: instructionsText // Log full message for debugging
230
+ fullMessage: instructionsText
309
231
  });
310
- // Step 7: Add context (if provided) with full memory context
311
- if (request.context) {
312
- const contextText = typeof request.context === 'string' ? request.context : JSON.stringify(request.context);
313
- const parsedContext = await parseTemplate(contextText, request.workingMemory, undefined, // taskConfig removed - no longer used
314
- shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger);
315
- if (parsedContext && parsedContext.trim() !== '') {
316
- messages.push({
317
- role: 'assistant',
318
- content: parsedContext
319
- });
320
- }
321
- }
322
- // Step 8: Build user message with full memory context
323
232
  const userMessage = await buildUserMessage(request, config, shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions);
324
- if (userMessage && userMessage.trim() !== '') {
325
- messages.push({
326
- role: 'user',
327
- content: userMessage
328
- });
329
- // Store resolved/parsed prompt for activity (parsed = content after resolution + Rendrix, not the key)
330
- if (parsedSnapshot) {
331
- parsedSnapshot.prompt = userMessage;
332
- }
333
- // Log the user message for verification
334
- logger.info('Final user message constructed', {
233
+ if (!userMessage || userMessage.trim() === '') {
234
+ const err = new Error(`User message is required but was empty after prompt render or input fallback.`);
235
+ err.code = 'USER_CONTENT_REQUIRED';
236
+ logger.error('User message empty after build', {
335
237
  jobId: request.identity.jobId,
336
238
  agentId: request.agentId,
337
- messageLength: userMessage.length,
338
- messagePreview: userMessage.substring(0, 200),
339
- fullMessage: userMessage // Log full message for debugging
239
+ errorCode: 'USER_CONTENT_REQUIRED',
240
+ hasPrompt: hasNonEmptyPrompt(request.prompt)
340
241
  });
242
+ throw err;
341
243
  }
342
- else {
343
- // If prompt was provided, we MUST have a user message - this is an error
344
- if (request.prompt) {
345
- const err = new Error(`Prompt template was provided but no user message was created. The template may have rendered to empty or failed to resolve.`);
346
- err.code = 'PROMPT_NO_USER_MESSAGE';
347
- err.details = {
348
- promptPreview: typeof request.prompt === 'string' ? request.prompt.substring(0, 200) : request.prompt,
349
- hasWorkingMemory: !!request.workingMemory
350
- };
351
- logger.error('Prompt provided but no user message created', {
352
- jobId: request.identity.jobId,
353
- agentId: request.agentId,
354
- prompt: request.prompt,
355
- errorCode: 'PROMPT_NO_USER_MESSAGE'
356
- });
357
- throw err;
358
- }
359
- // If no prompt was provided, it's just a warning (input-only requests are valid)
360
- logger.warn('No user message to add', {
361
- jobId: request.identity.jobId,
362
- agentId: request.agentId,
363
- hasPrompt: !!request.prompt
364
- });
244
+ messages.push({
245
+ role: 'user',
246
+ content: userMessage
247
+ });
248
+ if (parsedSnapshot) {
249
+ parsedSnapshot.prompt = userMessage;
365
250
  }
366
- // Log complete message structure
251
+ logger.info('Final user message constructed', {
252
+ jobId: request.identity.jobId,
253
+ agentId: request.agentId,
254
+ messageLength: userMessage.length,
255
+ messagePreview: userMessage.substring(0, 200),
256
+ fullMessage: userMessage
257
+ });
367
258
  logger.info('Complete message structure', {
368
259
  jobId: request.identity.jobId,
369
260
  agentId: request.agentId,
@@ -195,7 +195,10 @@ export function validateAIRequest(request) {
195
195
  errors.push('instructions is required');
196
196
  }
197
197
  if (!request.prompt) {
198
- errors.push('Prompt is required (input field has been removed - use workingMemory.input instead)');
198
+ const wmInput = request.workingMemory?.input;
199
+ if (wmInput === undefined || wmInput === null) {
200
+ errors.push('prompt or workingMemory.input is required for the user turn');
201
+ }
199
202
  }
200
203
  // Validate config
201
204
  if (!request.config) {
@@ -581,7 +584,7 @@ export function createValidationTestCases() {
581
584
  expectedErrors: ['instructions is required']
582
585
  },
583
586
  {
584
- name: 'Missing prompt',
587
+ name: 'Missing prompt and workingMemory.input',
585
588
  request: {
586
589
  aiRequestId: 'ai-no-prompt',
587
590
  agentId: 'agent-1',
@@ -589,11 +592,11 @@ export function createValidationTestCases() {
589
592
  actionRef: 'validation/x',
590
593
  identity: sampleIdentity('test', 'agent-1'),
591
594
  instructions: 'Test',
592
- workingMemory: { input: 'Test' },
595
+ workingMemory: {},
593
596
  config: { model: 'gpt-4o', provider: 'openai' }
594
597
  },
595
598
  shouldFail: true,
596
- expectedErrors: ['Prompt is required (input field has been removed - use workingMemory.input instead)']
599
+ expectedErrors: ['prompt or workingMemory.input is required for the user turn']
597
600
  },
598
601
  {
599
602
  name: 'Missing actionRef',
package/dist/types.d.ts CHANGED
@@ -933,15 +933,11 @@ interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input' | 'reques
933
933
  */
934
934
  coreSkillId?: string;
935
935
  /**
936
- * Prompt template text (optional) — parsed with workingMemory and tier memories.
936
+ * Prompt template text (optional) — parsed with workingMemory and sent as the user turn.
937
+ * When omitted, workingMemory.input is serialized as the user message.
937
938
  * Use variables such as {{input}} resolved from workingMemory.input.
938
939
  */
939
940
  prompt?: string;
940
- /**
941
- * Context text (optional) - Template that can use workingMemory
942
- * Added as system message between instructions and prompt
943
- */
944
- context?: string;
945
941
  /**
946
942
  * Working memory (optional) - Affects all template parsing
947
943
  * Passed to Rendrix (v4+) for template variable substitution.
@@ -1042,7 +1038,7 @@ interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input' | 'reques
1042
1038
  * - agentId: Required to identify the agent
1043
1039
  * - instructions: Required (template text)
1044
1040
  *
1045
- * Minimum: instructions + prompt (prompt is required for user message)
1041
+ * Minimum: instructions + (prompt or workingMemory.input)
1046
1042
  *
1047
1043
  * Note: objectTypes is NOT supported in ChatRequest.
1048
1044
  * Use {@link AIInvokeRequest} with invoke() for structured output requests.
@@ -549,7 +549,6 @@ export class ActivityManager {
549
549
  // Build request object snapshots (raw = incoming; parsed = constructed messages/meta)
550
550
  const rawSnapshot = request._rawRequest ?? {
551
551
  instructions: request.instructions,
552
- context: request.context,
553
552
  prompt: request.prompt,
554
553
  messages: request.messages,
555
554
  workingMemory: request.workingMemory,
@@ -559,31 +558,26 @@ export class ActivityManager {
559
558
  const requestData = {};
560
559
  // raw snapshot (only allowed fields)
561
560
  if (rawSnapshot.instructions !== undefined ||
562
- rawSnapshot.context !== undefined ||
563
561
  rawSnapshot.prompt !== undefined) {
564
562
  requestData.raw = {
565
563
  instructions: rawSnapshot.instructions,
566
- context: rawSnapshot.context,
567
564
  prompt: rawSnapshot.prompt
568
565
  };
569
566
  }
570
567
  // parsed snapshot (only allowed fields)
571
568
  // Ensure parsed is populated if parsedSnapshot has data, even if individual fields are undefined
572
569
  if (parsedSnapshot.instructions !== undefined ||
573
- parsedSnapshot.context !== undefined ||
574
570
  parsedSnapshot.prompt !== undefined) {
575
571
  requestData.parsed = {
576
572
  instructions: parsedSnapshot.instructions,
577
- context: parsedSnapshot.context,
578
573
  prompt: parsedSnapshot.prompt
579
574
  };
580
575
  }
581
576
  else if (Object.keys(parsedSnapshot).length > 0) {
582
- // If parsedSnapshot exists but doesn't have instructions/context/prompt,
577
+ // If parsedSnapshot exists but doesn't have instructions/prompt,
583
578
  // still create parsed with what's available (mirror of raw request after processing)
584
579
  requestData.parsed = {
585
580
  instructions: rawSnapshot.instructions,
586
- context: rawSnapshot.context,
587
581
  prompt: rawSnapshot.prompt
588
582
  };
589
583
  }
@@ -631,11 +625,9 @@ export class ActivityManager {
631
625
  // Only attach if any field is present
632
626
  const hasRequest = (requestData.raw &&
633
627
  (requestData.raw.instructions !== undefined ||
634
- requestData.raw.context !== undefined ||
635
628
  requestData.raw.prompt !== undefined)) ||
636
629
  (requestData.parsed &&
637
630
  (requestData.parsed.instructions !== undefined ||
638
- requestData.parsed.context !== undefined ||
639
631
  requestData.parsed.prompt !== undefined)) ||
640
632
  requestData.messages !== undefined ||
641
633
  requestData.workingMemory !== undefined ||
@@ -750,7 +742,6 @@ export class ActivityManager {
750
742
  // Build request object snapshots (same as startActivity)
751
743
  const rawSnapshot = request._rawRequest ?? {
752
744
  instructions: request.instructions,
753
- context: request.context,
754
745
  prompt: request.prompt,
755
746
  messages: request.messages,
756
747
  workingMemory: request.workingMemory,
@@ -760,21 +751,17 @@ export class ActivityManager {
760
751
  const requestData = {};
761
752
  // raw snapshot
762
753
  if (rawSnapshot.instructions !== undefined ||
763
- rawSnapshot.context !== undefined ||
764
754
  rawSnapshot.prompt !== undefined) {
765
755
  requestData.raw = {
766
756
  instructions: rawSnapshot.instructions,
767
- context: rawSnapshot.context,
768
757
  prompt: rawSnapshot.prompt
769
758
  };
770
759
  }
771
760
  // parsed snapshot
772
761
  if (parsedSnapshot.instructions !== undefined ||
773
- parsedSnapshot.context !== undefined ||
774
762
  parsedSnapshot.prompt !== undefined) {
775
763
  requestData.parsed = {
776
764
  instructions: parsedSnapshot.instructions,
777
- context: parsedSnapshot.context,
778
765
  prompt: parsedSnapshot.prompt
779
766
  };
780
767
  }
@@ -802,20 +789,17 @@ export class ActivityManager {
802
789
  requestData.workingMemory = rawSnapshot.workingMemory;
803
790
  }
804
791
  // Add skill-specific request structure
805
- if (rawSnapshot.workingMemory || rawSnapshot.context) {
792
+ if (rawSnapshot.workingMemory) {
806
793
  requestData.skill = {
807
- variables: rawSnapshot.workingMemory,
808
- context: rawSnapshot.context
794
+ variables: rawSnapshot.workingMemory
809
795
  };
810
796
  }
811
797
  // Only attach if any field is present
812
798
  const hasRequest = (requestData.raw &&
813
799
  (requestData.raw.instructions !== undefined ||
814
- requestData.raw.context !== undefined ||
815
800
  requestData.raw.prompt !== undefined)) ||
816
801
  (requestData.parsed &&
817
802
  (requestData.parsed.instructions !== undefined ||
818
- requestData.parsed.context !== undefined ||
819
803
  requestData.parsed.prompt !== undefined)) ||
820
804
  requestData.messages !== undefined ||
821
805
  requestData.workingMemory !== undefined ||