@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.
@@ -104,10 +104,6 @@ export function buildWorkingMemory(request, existingWorkingMemory, otherMemories
104
104
  if (!workingMemory.job.objective && request.instructions) {
105
105
  workingMemory.job.objective = request.instructions;
106
106
  }
107
- if (!workingMemory.job.context && request.context) {
108
- workingMemory.job.context = request.context;
109
- }
110
- // Input field has been removed - data should come from workingMemory.input
111
107
  if (!workingMemory.job.narrative && request.prompt) {
112
108
  workingMemory.job.narrative = request.prompt;
113
109
  }
@@ -118,9 +114,6 @@ export function buildWorkingMemory(request, existingWorkingMemory, otherMemories
118
114
  if (!workingMemory.task.objective && request.instructions) {
119
115
  workingMemory.task.objective = request.instructions;
120
116
  }
121
- if (!workingMemory.task.context && request.context) {
122
- workingMemory.task.context = request.context;
123
- }
124
117
  // Input field has been removed - data should come from workingMemory.input
125
118
  if (!workingMemory.task.id && request.identity.taskId) {
126
119
  workingMemory.task.id = request.identity.taskId;
@@ -13,21 +13,17 @@ function isAIRequest(request) {
13
13
  'reasoningEncrypted' in request;
14
14
  }
15
15
  /**
16
- * Constructs messages from instructions/prompt/input/context
16
+ * Constructs messages from instructions and prompt (two-message contract).
17
17
  *
18
18
  * Uses direct message builder which handles:
19
- * - Token resolution (3-tier system)
20
19
  * - Template parsing (via Rendrix, @x12i/rendrix)
21
- * - Instruction block resolution and composition
22
- * - Flex-md format specification
23
- * - Message assembly
20
+ * - Message assembly: system (instructions) + user (prompt or input fallback)
24
21
  */
25
22
  export async function constructMessages(request, config, logger, parsedSnapshot) {
26
23
  logger.verbose('Constructing messages from request', {
27
24
  jobId: request.identity.jobId,
28
25
  agentId: request.agentId,
29
26
  hasInstructions: !!request.instructions,
30
- hasContext: !!request.context,
31
27
  hasPrompt: !!request.prompt,
32
28
  hasWorkingMemory: !!request.workingMemory
33
29
  });
@@ -7,14 +7,11 @@ import type { Logxer } from '@x12i/logxer';
7
7
  import { type MessageBuilderConfig } from './message-builder.js';
8
8
  type Request = ChatRequest | AIRequest;
9
9
  /**
10
- * Constructs messages from instructions/prompt/input/context
10
+ * Constructs messages from instructions and prompt (two-message contract).
11
11
  *
12
12
  * Uses direct message builder which handles:
13
- * - Token resolution (3-tier system)
14
13
  * - Template parsing (via Rendrix, @x12i/rendrix)
15
- * - Instruction block resolution and composition
16
- * - Flex-md format specification
17
- * - Message assembly
14
+ * - Message assembly: system (instructions) + user (prompt or input fallback)
18
15
  */
19
16
  export declare function constructMessages(request: Request, config: MessageBuilderConfig, logger: Logxer, parsedSnapshot?: any): Promise<Array<{
20
17
  role: string;
@@ -2,6 +2,17 @@
2
2
  * Gateway Validation Module
3
3
  * Basic validation for clean proxy implementation
4
4
  */
5
+ function rejectRemovedContextField(request) {
6
+ if ('context' in request && request.context !== undefined) {
7
+ const err = new Error(`The 'context' field has been removed. Context is not sent to the LLM. Merge background data into workingMemory upstream and use the prompt template for the user turn.`);
8
+ err.code = 'CONTEXT_FIELD_REMOVED';
9
+ err.details = {
10
+ field: 'context',
11
+ alternative: 'workingMemory + prompt template'
12
+ };
13
+ throw err;
14
+ }
15
+ }
5
16
  function validateMandatoryRuntimeIdentity(request) {
6
17
  const id = request.identity;
7
18
  if (id === undefined || id === null || typeof id !== 'object') {
@@ -20,6 +31,7 @@ export function validateChatRequest(request) {
20
31
  throw new Error('agentId is required');
21
32
  }
22
33
  validateMandatoryRuntimeIdentity(request);
34
+ rejectRemovedContextField(request);
23
35
  // Reject input field - it has been removed
24
36
  if ('input' in request && request.input !== undefined) {
25
37
  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.`);
@@ -44,6 +56,7 @@ export function validateAIRequest(request) {
44
56
  throw new Error('agentId is required for AI requests');
45
57
  }
46
58
  validateMandatoryRuntimeIdentity(request);
59
+ rejectRemovedContextField(request);
47
60
  if (!request.actionType ||
48
61
  !GATEWAY_ACTION_TYPES.includes(request.actionType)) {
49
62
  throw new Error(`actionType is required and must be one of: ${GATEWAY_ACTION_TYPES.join(', ')}`);
@@ -93,8 +93,54 @@ export class AIGateway {
93
93
  const startTime = Date.now();
94
94
  // Generate simple task type ID
95
95
  const taskTypeId = request.taskTypeId || `task-${Date.now()}`;
96
- // Simple message construction
97
- const messages = this.buildSimpleMessages(request);
96
+ const parsedSnapshot = {};
97
+ let messages = [];
98
+ try {
99
+ const builtMessages = await buildMessages(request, this.messageBuilderConfig, {
100
+ parsedSnapshot
101
+ });
102
+ messages = builtMessages.messages;
103
+ }
104
+ catch (error) {
105
+ const err = error instanceof Error ? error : new Error(String(error));
106
+ const endTime = Date.now();
107
+ const duration = endTime - startTime;
108
+ const errWithCode = err;
109
+ const isResolutionError = err.name === 'InstructionNotFoundError' ||
110
+ err.name === 'InstructionBackendError' ||
111
+ err.name === 'TemplateResolutionError' ||
112
+ errWithCode.code === 'PROMPT_NOT_FOUND' ||
113
+ errWithCode.code === 'PROMPT_RESOLUTION_ERROR' ||
114
+ errWithCode.code === 'PROMPT_RENDERED_EMPTY' ||
115
+ errWithCode.code === 'TEMPLATE_RESOLUTION_ERROR' ||
116
+ errWithCode.code === 'TEMPLATE_VARIABLE_MISSING' ||
117
+ errWithCode.code === 'USER_CONTENT_REQUIRED' ||
118
+ err.message.includes('Failed to resolve') ||
119
+ err.message.includes('Failed to render prompt template') ||
120
+ err.message.includes('not found') ||
121
+ err.message.includes('Instruction not found') ||
122
+ err.message.includes('Prompt not found');
123
+ if (isResolutionError && this.activityManager) {
124
+ await this.activityManager.logBadRequest(request, err, {
125
+ endTime,
126
+ duration,
127
+ error: err.message,
128
+ errorType: errWithCode.code || 'MessageBuildError',
129
+ diagnosticInfo: {
130
+ errorCode: errWithCode.code,
131
+ errorName: err.name,
132
+ failureType: 'validation-failure',
133
+ stage: 'message-building',
134
+ prompt: request.prompt,
135
+ instructions: typeof request.instructions === 'string' ? request.instructions.substring(0, 100) : '(object)'
136
+ },
137
+ failureType: 'validation-failure'
138
+ }, startTime);
139
+ }
140
+ throw err;
141
+ }
142
+ parsedSnapshot.messages = messages;
143
+ request._parsedRequest = parsedSnapshot;
98
144
  // Merge config (modelConfig > request.config > gateway defaults)
99
145
  const aiTools = await this.getAiTools();
100
146
  const mergedConfig = await mergeConfig(request, this.config, this.logger, {
@@ -247,6 +293,7 @@ export class AIGateway {
247
293
  errWithCode.code === 'PROMPT_RENDERED_EMPTY' ||
248
294
  errWithCode.code === 'TEMPLATE_RESOLUTION_ERROR' ||
249
295
  errWithCode.code === 'TEMPLATE_VARIABLE_MISSING' ||
296
+ errWithCode.code === 'USER_CONTENT_REQUIRED' ||
250
297
  err.message.includes('Failed to resolve') ||
251
298
  err.message.includes('Failed to render prompt template') ||
252
299
  err.message.includes('not found') ||
@@ -284,9 +331,6 @@ export class AIGateway {
284
331
  parsedSnapshot.messages = messages;
285
332
  // parsed.instructions and parsed.prompt are set by buildMessages to the resolved/rendered content
286
333
  // (after key resolution and Rendrix). Do not overwrite with raw request keys.
287
- if (parsedSnapshot.context === undefined) {
288
- parsedSnapshot.context = request.context;
289
- }
290
334
  // Attach parsedSnapshot to request for activity tracking
291
335
  request._parsedRequest = parsedSnapshot;
292
336
  // Merge config (modelConfig > request.config > gateway defaults)
@@ -775,37 +819,6 @@ export class AIGateway {
775
819
  }
776
820
  });
777
821
  }
778
- /**
779
- * Build simple messages from request (instructions and prompt as literal template text; no registry).
780
- */
781
- buildSimpleMessages(request) {
782
- const messages = [];
783
- // Add instructions as system message if present
784
- if (request.instructions) {
785
- const instructions = typeof request.instructions === 'string'
786
- ? request.instructions
787
- : 'Default instructions';
788
- messages.push({ role: 'system', content: instructions });
789
- }
790
- // Add context as assistant message if present
791
- if (request.context) {
792
- const context = typeof request.context === 'string'
793
- ? request.context
794
- : JSON.stringify(request.context);
795
- messages.push({ role: 'assistant', content: context });
796
- }
797
- // Add prompt/input as user message
798
- // Input field has been removed - prompt template should contain {{input}} which resolves from workingMemory.input
799
- const userContent = request.prompt || '';
800
- if (userContent) {
801
- messages.push({ role: 'user', content: userContent });
802
- }
803
- // Add direct messages if present
804
- if (request.messages) {
805
- messages.push(...request.messages);
806
- }
807
- return messages;
808
- }
809
822
  // Provider management methods
810
823
  register(provider) {
811
824
  this.router.registerProvider(provider);
@@ -29,10 +29,6 @@ export declare class AIGateway {
29
29
  * Invoke AI request (with structured output support)
30
30
  */
31
31
  invoke<TContent = unknown>(request: AIInvokeRequest): Promise<EnhancedLLMResponse<TContent>>;
32
- /**
33
- * Build simple messages from request (instructions and prompt as literal template text; no registry).
34
- */
35
- private buildSimpleMessages;
36
32
  register(provider: any): void;
37
33
  listProviders(): string[];
38
34
  getRouter(): LLMProviderRouter;