@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.
- package/README.md +2 -2
- package/dist/activity-manager.js +3 -19
- package/dist/gateway-memory.js +0 -7
- package/dist/gateway-messages.d.ts +2 -5
- package/dist/gateway-messages.js +2 -6
- package/dist/gateway-validation.js +13 -0
- package/dist/gateway.d.ts +0 -4
- package/dist/gateway.js +49 -36
- package/dist/message-builder.d.ts +4 -0
- package/dist/message-builder.js +147 -256
- package/dist/troubleshooting-helper.js +7 -4
- package/dist/types.d.ts +3 -7
- package/dist-cjs/activity-manager.cjs +3 -19
- package/dist-cjs/gateway-memory.cjs +0 -7
- package/dist-cjs/gateway-messages.cjs +2 -6
- package/dist-cjs/gateway-messages.d.ts +2 -5
- package/dist-cjs/gateway-validation.cjs +13 -0
- package/dist-cjs/gateway.cjs +49 -36
- package/dist-cjs/gateway.d.ts +0 -4
- package/dist-cjs/message-builder.cjs +147 -256
- package/dist-cjs/message-builder.d.ts +4 -0
- package/dist-cjs/troubleshooting-helper.cjs +7 -4
- package/dist-cjs/types.d.ts +3 -7
- package/package.json +1 -1
|
@@ -5,19 +5,38 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { parseTemplate } from './template-parser.js';
|
|
7
7
|
import { mergeGatewayAndRequestTemplateRenderOptions } from './template-render-merge.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function
|
|
12
|
-
|
|
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
|
|
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:
|
|
54
|
+
hasInputInWorkingMemory: hasResolvableInput(request.workingMemory)
|
|
36
55
|
});
|
|
37
56
|
throw err;
|
|
38
57
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
65
|
+
promptLength: request.prompt.length,
|
|
66
|
+
promptPreview: request.prompt.substring(0, 200),
|
|
67
|
+
hasWorkingMemory: !!request.workingMemory
|
|
55
68
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
104
|
-
parsedLength: parsedPrompt.length,
|
|
105
|
-
parsedPreview: parsedPrompt.substring(0, 200)
|
|
77
|
+
errorCode: 'WORKING_MEMORY_REQUIRED'
|
|
106
78
|
});
|
|
107
|
-
|
|
79
|
+
throw err;
|
|
108
80
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
errorStack: err.stack
|
|
95
|
+
errorCode: 'PROMPT_RENDERED_EMPTY',
|
|
96
|
+
hasWorkingMemory: !!request.workingMemory,
|
|
97
|
+
workingMemoryKeys: workingMemoryObj ? Object.keys(workingMemoryObj) : []
|
|
136
98
|
});
|
|
137
|
-
|
|
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
|
-
|
|
147
|
-
const err =
|
|
148
|
-
|
|
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
|
-
|
|
133
|
+
errorCode,
|
|
134
|
+
error: err.message,
|
|
135
|
+
errorName: err.name,
|
|
136
|
+
errorStack: err.stack
|
|
152
137
|
});
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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,
|
|
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
|
|
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
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -28,6 +28,10 @@ export interface BuiltMessages {
|
|
|
28
28
|
hasObjectTypes?: boolean;
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Serializes workingMemory.input when no prompt template is provided.
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatInputFallback(input: unknown): string;
|
|
31
35
|
/**
|
|
32
36
|
* Main function to build messages
|
|
33
37
|
*/
|
|
@@ -195,7 +195,10 @@ export function validateAIRequest(request) {
|
|
|
195
195
|
errors.push('instructions is required');
|
|
196
196
|
}
|
|
197
197
|
if (!request.prompt) {
|
|
198
|
-
|
|
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: {
|
|
595
|
+
workingMemory: {},
|
|
593
596
|
config: { model: 'gpt-4o', provider: 'openai' }
|
|
594
597
|
},
|
|
595
598
|
shouldFail: true,
|
|
596
|
-
expectedErrors: ['
|
|
599
|
+
expectedErrors: ['prompt or workingMemory.input is required for the user turn']
|
|
597
600
|
},
|
|
598
601
|
{
|
|
599
602
|
name: 'Missing actionRef',
|
package/dist-cjs/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
|
|
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 +
|
|
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.
|