@x12i/ai-gateway 7.9.2 → 9.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.
Files changed (66) hide show
  1. package/README.md +107 -94
  2. package/dist/activity-manager.d.ts +3 -1
  3. package/dist/activity-manager.js +48 -17
  4. package/dist/content-normalizer/content-normalizer.d.ts +2 -1
  5. package/dist/content-normalizer/content-normalizer.js +5 -5
  6. package/dist/flex-md-loader.d.ts +2 -1
  7. package/dist/flex-md-loader.js +18 -15
  8. package/dist/gateway-config.d.ts +4 -4
  9. package/dist/gateway-config.js +21 -25
  10. package/dist/gateway-conversion.js +2 -2
  11. package/dist/gateway-log-meta.d.ts +15 -0
  12. package/dist/gateway-log-meta.js +36 -0
  13. package/dist/gateway-memory.js +6 -6
  14. package/dist/gateway-messages.js +4 -4
  15. package/dist/gateway-meta.d.ts +3 -1
  16. package/dist/gateway-meta.js +9 -2
  17. package/dist/gateway-utils.js +9 -9
  18. package/dist/gateway-validation.js +9 -0
  19. package/dist/gateway.js +32 -33
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.js +2 -1
  22. package/dist/instruction-optimizer.d.ts +6 -1
  23. package/dist/instruction-optimizer.js +10 -3
  24. package/dist/instructions-parser.d.ts +2 -1
  25. package/dist/instructions-parser.js +2 -2
  26. package/dist/logger-factory.js +12 -1
  27. package/dist/message-builder.js +19 -19
  28. package/dist/request-report-generator.js +1 -1
  29. package/dist/template-parser.d.ts +3 -1
  30. package/dist/template-parser.js +3 -2
  31. package/dist/troubleshooting-helper.d.ts +1 -1
  32. package/dist/troubleshooting-helper.js +2 -2
  33. package/dist/types.d.ts +16 -10
  34. package/dist-cjs/activity-manager.cjs +48 -17
  35. package/dist-cjs/activity-manager.d.ts +3 -1
  36. package/dist-cjs/content-normalizer/content-normalizer.cjs +5 -5
  37. package/dist-cjs/content-normalizer/content-normalizer.d.ts +2 -1
  38. package/dist-cjs/flex-md-loader.cjs +18 -15
  39. package/dist-cjs/flex-md-loader.d.ts +2 -1
  40. package/dist-cjs/gateway-config.cjs +21 -25
  41. package/dist-cjs/gateway-config.d.ts +4 -4
  42. package/dist-cjs/gateway-conversion.cjs +2 -2
  43. package/dist-cjs/gateway-log-meta.cjs +41 -0
  44. package/dist-cjs/gateway-log-meta.d.ts +15 -0
  45. package/dist-cjs/gateway-memory.cjs +6 -6
  46. package/dist-cjs/gateway-messages.cjs +4 -4
  47. package/dist-cjs/gateway-meta.cjs +9 -2
  48. package/dist-cjs/gateway-meta.d.ts +3 -1
  49. package/dist-cjs/gateway-utils.cjs +9 -9
  50. package/dist-cjs/gateway-validation.cjs +9 -0
  51. package/dist-cjs/gateway.cjs +31 -32
  52. package/dist-cjs/index.cjs +6 -1
  53. package/dist-cjs/index.d.ts +3 -2
  54. package/dist-cjs/instruction-optimizer.cjs +10 -3
  55. package/dist-cjs/instruction-optimizer.d.ts +6 -1
  56. package/dist-cjs/instructions-parser.cjs +2 -2
  57. package/dist-cjs/instructions-parser.d.ts +2 -1
  58. package/dist-cjs/logger-factory.cjs +12 -1
  59. package/dist-cjs/message-builder.cjs +19 -19
  60. package/dist-cjs/request-report-generator.cjs +1 -1
  61. package/dist-cjs/template-parser.cjs +3 -2
  62. package/dist-cjs/template-parser.d.ts +3 -1
  63. package/dist-cjs/troubleshooting-helper.cjs +2 -2
  64. package/dist-cjs/troubleshooting-helper.d.ts +1 -1
  65. package/dist-cjs/types.d.ts +16 -10
  66. package/package.json +3 -3
package/dist/gateway.js CHANGED
@@ -5,12 +5,13 @@
5
5
  */
6
6
  import { validateChatRequest, validateAIRequest } from './gateway-validation.js';
7
7
  import { ensureGatewayRequestIdentity } from './activity-manager.js';
8
- import { initializeGatewayComponents, loadConfig } from './gateway-config.js';
8
+ import { initializeGatewayComponents } from './gateway-config.js';
9
9
  import { buildMessages } from './message-builder.js';
10
10
  import { extractJsonFromFlexMd } from './flex-md-loader.js';
11
11
  import { mergeConfig } from './gateway-utils.js';
12
12
  import { autoRegisterProviders } from './gateway-provider-auto-register.js';
13
13
  import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
14
+ import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
14
15
  /** Error message thrown by the router when no provider is registered or specified */
15
16
  const NO_PROVIDER_ERROR = 'No provider specified and no providers registered';
16
17
  const NO_PROVIDER_HINT = ' Set OPEN_ROUTER_KEY (or OPENROUTER_API_KEY) in the environment to use OpenRouter, or register a provider with the router (e.g. via autoRegisterProviders or gateway config).';
@@ -27,9 +28,7 @@ export class AIGateway {
27
28
  constructor(config = {}, activityManager) {
28
29
  this.config = config;
29
30
  this.activityManager = activityManager;
30
- // Load configuration and initialize components
31
- const { defaultModelConfig, defaultInstructionsBlocks, defaultTemplateRendering } = loadConfig();
32
- const components = initializeGatewayComponents(config, defaultModelConfig, defaultInstructionsBlocks, defaultTemplateRendering);
31
+ const components = initializeGatewayComponents(config);
33
32
  this.logger = components.logger;
34
33
  this.router = components.router;
35
34
  this.activityManager = components.activityManager;
@@ -50,9 +49,7 @@ export class AIGateway {
50
49
  const startTime = Date.now();
51
50
  // Basic validation
52
51
  validateChatRequest(request);
53
- if (!this.activityManager) {
54
- ensureGatewayRequestIdentity(request);
55
- }
52
+ ensureGatewayRequestIdentity(request, undefined, this.logger);
56
53
  setGatewayLastJobId(resolveRuntimeJobId(request));
57
54
  // Generate simple task type ID
58
55
  const taskTypeId = request.taskTypeId || `task-${Date.now()}`;
@@ -135,9 +132,7 @@ export class AIGateway {
135
132
  const startTime = Date.now();
136
133
  // Basic validation
137
134
  validateAIRequest(request);
138
- if (!this.activityManager) {
139
- ensureGatewayRequestIdentity(request);
140
- }
135
+ ensureGatewayRequestIdentity(request, undefined, this.logger);
141
136
  setGatewayLastJobId(resolveRuntimeJobId(request));
142
137
  // Generate simple task type ID
143
138
  const taskTypeId = request.taskTypeId || `task-${Date.now()}`;
@@ -252,48 +247,48 @@ export class AIGateway {
252
247
  let parsingMethod = undefined;
253
248
  // Actually use flex-md parsing - extract structured data from markdown
254
249
  try {
255
- this.logger.debug('Attempting flex-md extraction', {
256
- aiRequestId: request.aiRequestId,
250
+ this.logger.debug('Attempting flex-md extraction', withActivityIdentity(request.identity, {
257
251
  contentLength: content.length,
258
- hasInstructions: !!resolvedRequest.instructions
259
- });
252
+ hasInstructions: !!resolvedRequest.instructions,
253
+ debugKind: gatewayLogDebug.intent
254
+ }));
260
255
  // Let flex-md extract structured data from the response content
261
- const extractionResult = await extractJsonFromFlexMd(content);
262
- this.logger.debug('Flex-md extraction result', {
263
- aiRequestId: request.aiRequestId,
256
+ const extractionResult = await extractJsonFromFlexMd(content, this.logger);
257
+ this.logger.debug('Flex-md extraction result', withActivityIdentity(request.identity, {
264
258
  hasResult: !!extractionResult,
265
259
  hasJson: !!(extractionResult && extractionResult.json),
266
260
  method: extractionResult?.method,
267
- jsonType: extractionResult?.json ? typeof extractionResult.json : 'none'
268
- });
261
+ jsonType: extractionResult?.json ? typeof extractionResult.json : 'none',
262
+ debugKind: gatewayLogDebug.state
263
+ }));
269
264
  if (extractionResult && extractionResult.json) {
270
265
  // Successfully extracted structured data
271
266
  parsedContent = extractionResult.json;
272
- this.logger.info('Flex-md extraction successful - parsed into structured object', {
273
- aiRequestId: request.aiRequestId,
267
+ this.logger.info('Flex-md extraction successful - parsed into structured object', withActivityIdentity(request.identity, {
274
268
  method: extractionResult.method,
275
- extractedKeys: Object.keys(extractionResult.json)
276
- });
269
+ extractedKeys: Object.keys(extractionResult.json),
270
+ debugKind: gatewayLogDebug.event
271
+ }));
277
272
  }
278
273
  else {
279
274
  // Extraction failed, fall back to raw text wrapper
280
- this.logger.warn('Flex-md extraction failed - no structured data extracted', {
281
- aiRequestId: request.aiRequestId,
275
+ this.logger.warn('Flex-md extraction failed - no structured data extracted', withActivityIdentity(request.identity, {
282
276
  hasResult: !!extractionResult,
283
- method: extractionResult?.method || 'none'
284
- });
277
+ method: extractionResult?.method || 'none',
278
+ debugKind: gatewayLogDebug.anomaly
279
+ }));
285
280
  parsedContent = { rawText: content };
286
281
  }
287
282
  }
288
283
  catch (extractionError) {
289
284
  // Extraction failed, fall back to raw text wrapper
290
285
  const errorMessage = extractionError instanceof Error ? extractionError.message : String(extractionError);
291
- this.logger.warn('Flex-md extraction failed - flex-md library compatibility issue', {
292
- aiRequestId: request.aiRequestId,
286
+ this.logger.warn('Flex-md extraction failed - flex-md library compatibility issue', withActivityIdentity(request.identity, {
293
287
  error: errorMessage,
294
288
  issue: 'flex-md uses require() in ES module context - needs fixing in flex-md-loader.ts',
295
- fallback: 'using rawText wrapper'
296
- });
289
+ fallback: 'using rawText wrapper',
290
+ debugKind: gatewayLogDebug.anomaly
291
+ }));
297
292
  parsedContent = { rawText: content };
298
293
  }
299
294
  contentType = 'structured';
@@ -358,7 +353,11 @@ export class AIGateway {
358
353
  });
359
354
  }
360
355
  }
361
- console.debug('gateway: enhancedResponse:', enhancedResponse);
356
+ this.logger.debug('gateway: enhancedResponse', withActivityIdentity(request.identity, {
357
+ latencyMs: enhancedResponse.metadata?.latencyMs,
358
+ contentType: enhancedResponse.metadata?.contentType,
359
+ debugKind: gatewayLogDebug.state
360
+ }));
362
361
  return enhancedResponse;
363
362
  }
364
363
  catch (error) {
@@ -426,5 +425,5 @@ export class AIGateway {
426
425
  }
427
426
  }
428
427
  function resolveRuntimeJobId(request) {
429
- return request.identity?.jobId ?? request.jobId ?? request.identity?.sessionId ?? request.aiRequestId;
428
+ return request.identity.jobId || request.identity.sessionId || request.aiRequestId;
430
429
  }
package/dist/index.d.ts CHANGED
@@ -23,8 +23,9 @@ export { Activix } from '@x12i/activix';
23
23
  export type { ActivixRunContext, FindByRunContextCriteria } from '@x12i/activix';
24
24
  export { ActivityManager, ensureGatewayRequestIdentity } from './activity-manager.js';
25
25
  export type { ActivityIdentity } from './types.js';
26
- export { createLogxer } from '@x12i/logxer';
27
- export type { Logxer } from '@x12i/logxer';
26
+ export { activityIdentityToLogMeta, withActivityIdentity, gatewayLogDebug } from './gateway-log-meta.js';
27
+ export { createLogxer, DebugLogAbstract } from '@x12i/logxer';
28
+ export type { Logxer, LogMeta, RuntimeIdentity } from '@x12i/logxer';
28
29
  export { runtimeObjects } from './runtime-objects.js';
29
30
  export type { ActivixQueryableClient, LogxerQueryableClient, PackageRuntimeObjects, RuntimeObjects } from './runtime-objects.js';
30
31
  export { GatewayRateLimiter } from './gateway-rate-limiter.js';
package/dist/index.js CHANGED
@@ -23,8 +23,9 @@ export { mergeTemplateRenderOptions } from './template-render-merge.js';
23
23
  // Re-export activity tracking primitives (Activix)
24
24
  export { Activix } from '@x12i/activix';
25
25
  export { ActivityManager, ensureGatewayRequestIdentity } from './activity-manager.js';
26
+ export { activityIdentityToLogMeta, withActivityIdentity, gatewayLogDebug } from './gateway-log-meta.js';
26
27
  // Re-export logging (@x12i/logxer)
27
- export { createLogxer } from '@x12i/logxer';
28
+ export { createLogxer, DebugLogAbstract } from '@x12i/logxer';
28
29
  // Runtime observability surface (leaf package: no downstream runtime objects)
29
30
  export { runtimeObjects } from './runtime-objects.js';
30
31
  // Export rate limiter
@@ -5,6 +5,7 @@
5
5
  * This is a meta-feature that uses the AI Gateway (via router) to improve AI instructions.
6
6
  */
7
7
  import type { AIGateway } from './gateway.js';
8
+ import type { ActivityIdentity } from './types.js';
8
9
  /**
9
10
  * Result from instruction optimization
10
11
  */
@@ -45,6 +46,10 @@ export interface InstructionOptimizationResult {
45
46
  * Options for instruction optimization
46
47
  */
47
48
  export interface OptimizeInstructionsOptions {
49
+ /**
50
+ * Runtime identity for this internal `invoke` (same jobId/taskId as the upstream work unit; gateway never invents them).
51
+ */
52
+ identity: ActivityIdentity;
48
53
  /**
49
54
  * Agent ID for the optimization request
50
55
  */
@@ -85,7 +90,7 @@ export interface OptimizeInstructionsOptions {
85
90
  * @param options - Optimization options
86
91
  * @returns Optimization result with fixed instructions and analysis
87
92
  */
88
- export declare function optimizeInstructions(gateway: AIGateway, originalInstructions: string, options?: OptimizeInstructionsOptions): Promise<InstructionOptimizationResult>;
93
+ export declare function optimizeInstructions(gateway: AIGateway, originalInstructions: string, options: OptimizeInstructionsOptions): Promise<InstructionOptimizationResult>;
89
94
  /**
90
95
  * Utility: Extract only the fixed instructions text
91
96
  *
@@ -105,7 +105,7 @@ If the instructions are already well-written, explain that in the analysis and m
105
105
  * @param options - Optimization options
106
106
  * @returns Optimization result with fixed instructions and analysis
107
107
  */
108
- export async function optimizeInstructions(gateway, originalInstructions, options = {}) {
108
+ export async function optimizeInstructions(gateway, originalInstructions, options) {
109
109
  // Get internal system action config (instruction optimization)
110
110
  const internalConfig = gateway.config?.internalSystemActions?.instructionOptimization;
111
111
  const defaultEngine = gateway.config?.defaultEngine || 'openai';
@@ -128,13 +128,20 @@ export async function optimizeInstructions(gateway, originalInstructions, option
128
128
  if (enforceJsonOutput) {
129
129
  additionalContext += '\n\nIMPORTANT: The fixed instructions MUST include strict JSON-only output enforcement rules.';
130
130
  }
131
+ const aiRequestId = `optimize-instructions-${Date.now()}`;
132
+ const identity = {
133
+ ...options.identity,
134
+ aiRequestId,
135
+ agentId
136
+ };
131
137
  // Create the optimization request - uses gateway.invoke() which goes through router
132
138
  // Use internal system action config for temperature and maxTokens if available
133
139
  const optimizationRequest = {
134
- jobId: `optimize-instructions-${Date.now()}`,
140
+ aiRequestId,
135
141
  agentId,
136
142
  instructions: INSTRUCTION_OPTIMIZER_INSTRUCTIONS + additionalContext,
137
- input: originalInstructions,
143
+ identity,
144
+ workingMemory: { input: originalInstructions },
138
145
  config: {
139
146
  model,
140
147
  provider,
@@ -5,6 +5,7 @@
5
5
  * Separated from instruction resolution to keep concerns clean.
6
6
  */
7
7
  import type { TemplateRenderOptions } from '@x12i/rendrix';
8
+ import type { Logxer } from '@x12i/logxer';
8
9
  export interface TemplateVariables {
9
10
  workingMemory?: Record<string, any>;
10
11
  shortTermMemory?: Record<string, any>;
@@ -20,7 +21,7 @@ export interface RenderedInstruction {
20
21
  /**
21
22
  * Renders instruction templates with variables using Rendrix (@x12i/rendrix)
22
23
  */
23
- export declare function renderInstructionTemplate(template: string, variables?: TemplateVariables, templateRenderOptions?: TemplateRenderOptions): Promise<RenderedInstruction>;
24
+ export declare function renderInstructionTemplate(template: string, variables?: TemplateVariables, templateRenderOptions?: TemplateRenderOptions, logger?: Logxer): Promise<RenderedInstruction>;
24
25
  /**
25
26
  * Checks if a template contains variables that need rendering
26
27
  */
@@ -8,7 +8,7 @@ import { parseTemplate } from './template-parser.js';
8
8
  /**
9
9
  * Renders instruction templates with variables using Rendrix (@x12i/rendrix)
10
10
  */
11
- export async function renderInstructionTemplate(template, variables = {}, templateRenderOptions) {
11
+ export async function renderInstructionTemplate(template, variables = {}, templateRenderOptions, logger) {
12
12
  const startTime = Date.now();
13
13
  // Extract memory contexts for Rendrix
14
14
  // parseTemplate expects: (template, workingMemory, shortTermMemory?, experienceMemory?, knowledgeMemory?)
@@ -19,7 +19,7 @@ export async function renderInstructionTemplate(template, variables = {}, templa
19
19
  // Render template using Rendrix
20
20
  // Pass memory contexts as separate parameters, not as a combined object
21
21
  const renderedText = await parseTemplate(template, workingMemory, undefined, // taskConfig removed - no longer used
22
- shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions);
22
+ shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger);
23
23
  // Combine all memory contexts for return value (for metadata)
24
24
  const combinedVariables = {
25
25
  ...variables,
@@ -4,6 +4,16 @@
4
4
  * Creates and configures logxer instances for the gateway
5
5
  */
6
6
  import { createLogxer } from '@x12i/logxer';
7
+ function resolveDefaultRuntimeIdentity(packageName) {
8
+ const service = process.env.AI_GATEWAY_LOG_SERVICE ?? packageName ?? 'AI_GATEWAY';
9
+ if (!service)
10
+ return undefined;
11
+ return {
12
+ service,
13
+ env: process.env.NODE_ENV,
14
+ version: process.env.npm_package_version
15
+ };
16
+ }
7
17
  /**
8
18
  * Creates a logger instance based on configuration
9
19
  *
@@ -23,7 +33,8 @@ export function createGatewayLogger(config) {
23
33
  debugNamespace: 'ai-gateway'
24
34
  }, {
25
35
  logLevel: 'info',
26
- logFormat: 'json'
36
+ logFormat: 'json',
37
+ runtimeIdentity: resolveDefaultRuntimeIdentity(config.packageName)
27
38
  });
28
39
  }
29
40
  function createNoOpLogger() {
@@ -152,7 +152,7 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
152
152
  message: 'Input data must be provided via workingMemory.input for template rendering'
153
153
  };
154
154
  logger.error('Input field provided but has been removed', {
155
- jobId: request.jobId,
155
+ jobId: request.identity.jobId,
156
156
  agentId: request.agentId,
157
157
  errorCode: 'INPUT_FIELD_DEPRECATED',
158
158
  hasPrompt: !!request.prompt,
@@ -174,7 +174,7 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
174
174
  hasInputInWorkingMemory: !!request.workingMemory?.input
175
175
  };
176
176
  logger.error('Prompt is required for AI requests', {
177
- jobId: request.jobId,
177
+ jobId: request.identity.jobId,
178
178
  agentId: request.agentId,
179
179
  errorCode: 'PROMPT_REQUIRED'
180
180
  });
@@ -184,7 +184,7 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
184
184
  if (request.prompt) {
185
185
  if (typeof request.prompt === 'string') {
186
186
  logger.info('Parsing prompt template text', {
187
- jobId: request.jobId,
187
+ jobId: request.identity.jobId,
188
188
  agentId: request.agentId,
189
189
  promptLength: request.prompt.length,
190
190
  promptPreview: request.prompt.substring(0, 200),
@@ -196,13 +196,13 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
196
196
  err.code = 'WORKING_MEMORY_REQUIRED';
197
197
  err.details = { requiresVariables: true };
198
198
  logger.error('workingMemory required for prompt template rendering', {
199
- jobId: request.jobId,
199
+ jobId: request.identity.jobId,
200
200
  agentId: request.agentId,
201
201
  errorCode: 'WORKING_MEMORY_REQUIRED'
202
202
  });
203
203
  throw err;
204
204
  }
205
- const parsedPrompt = await parseTemplate(request.prompt, request.workingMemory, undefined, shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions);
205
+ const parsedPrompt = await parseTemplate(request.prompt, request.workingMemory, undefined, shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger);
206
206
  if (!parsedPrompt || parsedPrompt.trim() === '') {
207
207
  const workingMemoryObj = request.workingMemory;
208
208
  const err = new Error(`Prompt template rendered to empty string. This may indicate missing template variables or empty template content.`);
@@ -214,7 +214,7 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
214
214
  workingMemoryKeys: workingMemoryObj ? Object.keys(workingMemoryObj) : []
215
215
  };
216
216
  logger.error('Prompt template rendered to empty string', {
217
- jobId: request.jobId,
217
+ jobId: request.identity.jobId,
218
218
  agentId: request.agentId,
219
219
  errorCode: 'PROMPT_RENDERED_EMPTY',
220
220
  hasWorkingMemory: !!request.workingMemory,
@@ -223,7 +223,7 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
223
223
  throw err;
224
224
  }
225
225
  logger.info('Prompt text parsed successfully', {
226
- jobId: request.jobId,
226
+ jobId: request.identity.jobId,
227
227
  agentId: request.agentId,
228
228
  originalLength: request.prompt.length,
229
229
  parsedLength: parsedPrompt.length,
@@ -252,7 +252,7 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
252
252
  errorMessage = err.message;
253
253
  }
254
254
  logger.error('Failed to render prompt template', {
255
- jobId: request.jobId,
255
+ jobId: request.identity.jobId,
256
256
  agentId: request.agentId,
257
257
  errorCode,
258
258
  error: err.message,
@@ -271,7 +271,7 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
271
271
  else {
272
272
  const err = new Error(`Prompt must be a string template, but received: ${typeof request.prompt}`);
273
273
  logger.error('Prompt provided as non-string - not supported', {
274
- jobId: request.jobId,
274
+ jobId: request.identity.jobId,
275
275
  agentId: request.agentId,
276
276
  promptType: typeof request.prompt
277
277
  });
@@ -293,7 +293,7 @@ async function buildUserMessage(request, config, shortTermMemory, experienceMemo
293
293
  partsLength: parts.length
294
294
  };
295
295
  logger.error('Prompt provided but resulted in empty user message', {
296
- jobId: request.jobId,
296
+ jobId: request.identity.jobId,
297
297
  agentId: request.agentId,
298
298
  prompt: request.prompt,
299
299
  errorCode: 'PROMPT_NO_USER_MESSAGE',
@@ -383,7 +383,7 @@ export async function buildMessages(request, config, options = {}) {
383
383
  if (request.instructions) {
384
384
  if (typeof request.instructions === 'string') {
385
385
  logger.info('Using instructions as template text', {
386
- jobId: request.jobId,
386
+ jobId: request.identity.jobId,
387
387
  agentId: request.agentId,
388
388
  instructionsLength: request.instructions.length,
389
389
  instructionsPreview: request.instructions.substring(0, 200)
@@ -401,7 +401,7 @@ export async function buildMessages(request, config, options = {}) {
401
401
  // Rendrix handles token resolution, so we just parse directly
402
402
  if (instructionsText) {
403
403
  instructionsText = await parseTemplate(instructionsText, request.workingMemory, undefined, // taskConfig removed - no longer used
404
- shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions);
404
+ shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger);
405
405
  }
406
406
  // Step 4: Add input recognition rules
407
407
  const inputRules = await buildInputRecognitionRules(request, config, options);
@@ -418,7 +418,7 @@ export async function buildMessages(request, config, options = {}) {
418
418
  if (!instructionsText || instructionsText.trim() === '') {
419
419
  const errorMessage = 'No instructions available - cannot proceed without clear instructions. This is a bad request.';
420
420
  logger.error(errorMessage, {
421
- jobId: request.jobId,
421
+ jobId: request.identity.jobId,
422
422
  agentId: request.agentId,
423
423
  hasRequestInstructions: !!request.instructions,
424
424
  instructionType: typeof request.instructions,
@@ -437,7 +437,7 @@ export async function buildMessages(request, config, options = {}) {
437
437
  }
438
438
  // Log the final system message for verification
439
439
  logger.info('Final system message constructed', {
440
- jobId: request.jobId,
440
+ jobId: request.identity.jobId,
441
441
  agentId: request.agentId,
442
442
  messageLength: instructionsText.length,
443
443
  hasMarkdown: instructionsText.toLowerCase().includes('markdown'),
@@ -450,7 +450,7 @@ export async function buildMessages(request, config, options = {}) {
450
450
  if (request.context) {
451
451
  const contextText = typeof request.context === 'string' ? request.context : JSON.stringify(request.context);
452
452
  const parsedContext = await parseTemplate(contextText, request.workingMemory, undefined, // taskConfig removed - no longer used
453
- shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions);
453
+ shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger);
454
454
  if (parsedContext && parsedContext.trim() !== '') {
455
455
  messages.push({
456
456
  role: 'assistant',
@@ -471,7 +471,7 @@ export async function buildMessages(request, config, options = {}) {
471
471
  }
472
472
  // Log the user message for verification
473
473
  logger.info('Final user message constructed', {
474
- jobId: request.jobId,
474
+ jobId: request.identity.jobId,
475
475
  agentId: request.agentId,
476
476
  messageLength: userMessage.length,
477
477
  messagePreview: userMessage.substring(0, 200),
@@ -488,7 +488,7 @@ export async function buildMessages(request, config, options = {}) {
488
488
  hasWorkingMemory: !!request.workingMemory
489
489
  };
490
490
  logger.error('Prompt provided but no user message created', {
491
- jobId: request.jobId,
491
+ jobId: request.identity.jobId,
492
492
  agentId: request.agentId,
493
493
  prompt: request.prompt,
494
494
  errorCode: 'PROMPT_NO_USER_MESSAGE'
@@ -497,14 +497,14 @@ export async function buildMessages(request, config, options = {}) {
497
497
  }
498
498
  // If no prompt was provided, it's just a warning (input-only requests are valid)
499
499
  logger.warn('No user message to add', {
500
- jobId: request.jobId,
500
+ jobId: request.identity.jobId,
501
501
  agentId: request.agentId,
502
502
  hasPrompt: !!request.prompt
503
503
  });
504
504
  }
505
505
  // Log complete message structure
506
506
  logger.info('Complete message structure', {
507
- jobId: request.jobId,
507
+ jobId: request.identity.jobId,
508
508
  agentId: request.agentId,
509
509
  totalMessages: messages.length,
510
510
  systemMessages: messages.filter(m => m.role === 'system').length,
@@ -148,7 +148,7 @@ function schemasMatch(schema1, schema2) {
148
148
  export async function generateRequestReport(request, library, // ObjectTypesLibrary removed
149
149
  logger) {
150
150
  const report = {
151
- requestId: request.jobId || `req-${Date.now()}`,
151
+ requestId: request.identity.jobId || request.aiRequestId,
152
152
  timestamp: Date.now(),
153
153
  validation: {},
154
154
  examples: { count: 0 },
@@ -5,6 +5,7 @@
5
5
  * TemplateResolutionError from the parser is rethrown; other errors fall back to the raw template.
6
6
  */
7
7
  import type { TemplateRenderOptions } from '@x12i/rendrix';
8
+ import type { Logxer } from '@x12i/logxer';
8
9
  /**
9
10
  * Task configuration type for template rendering
10
11
  * @deprecated taskConfig is no longer used by Rendrix 3.0.0+
@@ -48,10 +49,11 @@ export interface KnowledgeMemory {
48
49
  * @param experienceMemory - Experience memory for learned knowledge (optional)
49
50
  * @param knowledgeMemory - Knowledge memory for reference data (optional)
50
51
  * @param templateRenderOptions - Passed to Rendrix `render` (v4+): templateId, subPathSearch, silentMissingMustTokens
52
+ * @param logger - Optional Logxer for non-fatal parse failures (replaces console).
51
53
  * @returns Parsed template string
52
54
  * @throws {Error} When the parser throws TemplateResolutionError (v4 MUST path missing after merge)
53
55
  */
54
- export declare function parseTemplate(template: string, workingMemory?: unknown, taskConfig?: TaskConfig, shortTermMemory?: ShortTermMemory, experienceMemory?: ExperienceMemory, knowledgeMemory?: KnowledgeMemory, templateRenderOptions?: TemplateRenderOptions): Promise<string>;
56
+ export declare function parseTemplate(template: string, workingMemory?: unknown, taskConfig?: TaskConfig, shortTermMemory?: ShortTermMemory, experienceMemory?: ExperienceMemory, knowledgeMemory?: KnowledgeMemory, templateRenderOptions?: TemplateRenderOptions, logger?: Logxer): Promise<string>;
55
57
  /**
56
58
  * Checks if Rendrix (@x12i/rendrix) is available
57
59
  */
@@ -35,10 +35,11 @@ async function loadRendrix() {
35
35
  * @param experienceMemory - Experience memory for learned knowledge (optional)
36
36
  * @param knowledgeMemory - Knowledge memory for reference data (optional)
37
37
  * @param templateRenderOptions - Passed to Rendrix `render` (v4+): templateId, subPathSearch, silentMissingMustTokens
38
+ * @param logger - Optional Logxer for non-fatal parse failures (replaces console).
38
39
  * @returns Parsed template string
39
40
  * @throws {Error} When the parser throws TemplateResolutionError (v4 MUST path missing after merge)
40
41
  */
41
- export async function parseTemplate(template, workingMemory, taskConfig, shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions) {
42
+ export async function parseTemplate(template, workingMemory, taskConfig, shortTermMemory, experienceMemory, knowledgeMemory, templateRenderOptions, logger) {
42
43
  // If no template or empty, return as-is
43
44
  if (!template || typeof template !== 'string') {
44
45
  return template || '';
@@ -79,7 +80,7 @@ export async function parseTemplate(template, workingMemory, taskConfig, shortTe
79
80
  throw error;
80
81
  }
81
82
  const err = error instanceof Error ? error : new Error(String(error));
82
- console.warn('Template parsing failed, using template as-is:', {
83
+ logger?.warn('Template parsing failed, using template as-is', {
83
84
  error: err.message,
84
85
  templateLength: template.length,
85
86
  hasWorkingMemory: !!workingMemory,
@@ -120,4 +120,4 @@ export declare function formatDiagnostic(diagnostic: DiagnosticInfo): string;
120
120
  /**
121
121
  * Quick validation helper - throws if invalid
122
122
  */
123
- export declare function assertValidAIRequest(request: Partial<AIRequest>): void;
123
+ export declare function assertValidAIRequest(request: Partial<AIRequest>, logger?: import('@x12i/logxer').Logxer): void;
@@ -585,12 +585,12 @@ export function formatDiagnostic(diagnostic) {
585
585
  /**
586
586
  * Quick validation helper - throws if invalid
587
587
  */
588
- export function assertValidAIRequest(request) {
588
+ export function assertValidAIRequest(request, logger) {
589
589
  const validation = validateAIRequest(request);
590
590
  if (!validation.valid) {
591
591
  throw new Error(`AIRequest validation failed:\n${validation.errors.join('\n')}`);
592
592
  }
593
593
  if (validation.warnings.length > 0) {
594
- console.warn('AIRequest validation warnings:', validation.warnings);
594
+ logger?.warn('AIRequest validation warnings', { warnings: validation.warnings });
595
595
  }
596
596
  }
package/dist/types.d.ts CHANGED
@@ -32,11 +32,18 @@ export type ActivityIdentity = {
32
32
  };
33
33
  /** Gateway-level request correlation ID. */
34
34
  aiRequestId: string;
35
- /** @deprecated Use aiRequestId. Kept only for backward compatibility. */
36
- jobId?: string;
35
+ /**
36
+ * Upstream job identifier — must be supplied on `request.identity` only.
37
+ * The gateway copies this value as-is; it never generates or mutates it.
38
+ */
39
+ jobId: string;
37
40
  jobTypeId?: string;
38
41
  agentId: string;
39
- taskId?: string;
42
+ /**
43
+ * Upstream task identifier — must be supplied on `request.identity` only.
44
+ * The gateway copies this value as-is; it never generates or mutates it.
45
+ */
46
+ taskId: string;
40
47
  taskTypeId?: string;
41
48
  /** Graph/node linkage (optional, used by activity identity grouping). */
42
49
  graphId?: string;
@@ -413,7 +420,7 @@ export interface GatewayConfig extends Omit<RouterConfig, 'defaultEngine' | 'log
413
420
  * Extends LLMRequest but omits 'messages' and 'input' to allow custom message handling
414
421
  * and make input optional (router may require it, but gateway allows it to be optional)
415
422
  */
416
- interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input'> {
423
+ interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input' | 'request' | 'mode'> {
417
424
  /**
418
425
  * AI request ID - Required for all invocations and activity tracking
419
426
  */
@@ -422,15 +429,14 @@ interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input'> {
422
429
  jobId?: string;
423
430
  /**
424
431
  * Optional top-level run/session id for Activix. Prefer `identity.sessionId`; if both are set they must match.
425
- * When omitted, `ActivityManager` resolves session from `identity.sessionId`, then `jobId`, then `aiRequestId`.
432
+ * When omitted, `ActivityManager` resolves session from `identity.sessionId`, then `identity.jobId`, then `aiRequestId`.
426
433
  */
427
434
  sessionId?: string;
428
435
  /**
429
- * Optional identity object used for upstream linkage (forwarded through the request).
430
- * May be partial; may include product-specific keys beyond `ActivityIdentity`. When omitted,
431
- * the gateway normalizes and attaches a full envelope (`ensureGatewayRequestIdentity` / activity tracking).
436
+ * Mandatory runtime identity from the upstream client (job/task correlation, Activix `runContext`).
437
+ * Must include at least `jobId` and `taskId` as non-empty strings; the gateway never invents these.
432
438
  */
433
- identity?: ActivityIdentity & Record<string, unknown>;
439
+ identity: ActivityIdentity & Record<string, unknown>;
434
440
  /**
435
441
  * Job type ID (optional) - Short ID or hash to identify recurring job types
436
442
  * Best practice: Use MD5 hash of the job type string for consistent job identification
@@ -459,7 +465,7 @@ interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input'> {
459
465
  */
460
466
  instructions: string;
461
467
  /**
462
- * Task ID (optional)
468
+ * @deprecated Prefer `identity.taskId`. The gateway does not copy this into `identity`.
463
469
  */
464
470
  taskId?: string;
465
471
  /**