@x12i/ai-gateway 7.9.2 → 9.0.1

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 +4 -4
@@ -6,6 +6,7 @@
6
6
  * Preserves both rawText and parsedContent for downstream consumers.
7
7
  */
8
8
  import type { LLMResponse } from '../types.js';
9
+ import type { Logxer } from '@x12i/logxer';
9
10
  import type { NormalizedContent } from './types.js';
10
11
  /**
11
12
  * Normalizes content from router response
@@ -20,7 +21,7 @@ import type { NormalizedContent } from './types.js';
20
21
  * @param response - Router response object
21
22
  * @returns Normalized content with metadata
22
23
  */
23
- export declare function normalizeContent(response: LLMResponse): NormalizedContent;
24
+ export declare function normalizeContent(response: LLMResponse, logger?: Logxer): NormalizedContent;
24
25
  /**
25
26
  * Checks if content is empty
26
27
  *
@@ -182,14 +182,14 @@ function determineContentType(value, parsedContent) {
182
182
  * @param response - Router response object
183
183
  * @returns Normalized content with metadata
184
184
  */
185
- export function normalizeContent(response) {
185
+ export function normalizeContent(response, logger) {
186
186
  // Extract content value from response
187
187
  const contentValue = extractContentValue(response);
188
188
  // CRITICAL: Check if router already converted object to "[object Object]" string
189
189
  // This happens when router uses String() on objects before returning
190
190
  if (typeof contentValue === 'string' && contentValue === '[object Object]') {
191
191
  // Router already broke it - try to recover from other fields
192
- console.error('[content-normalizer] Router returned "[object Object]" string - attempting recovery', {
192
+ logger?.error('[content-normalizer] Router returned "[object Object]" string - attempting recovery', {
193
193
  responseKeys: Object.keys(response),
194
194
  hasOutput: !!response.output,
195
195
  hasRawText: !!response.rawText,
@@ -230,7 +230,7 @@ export function normalizeContent(response) {
230
230
  };
231
231
  }
232
232
  // If we can't recover, log error and return empty
233
- console.error('[content-normalizer] Cannot recover from "[object Object]" - no valid object found in response');
233
+ logger?.error('[content-normalizer] Cannot recover from "[object Object]" - no valid object found in response');
234
234
  return {
235
235
  content: '',
236
236
  rawText: undefined,
@@ -319,7 +319,7 @@ export function normalizeContent(response) {
319
319
  // Last resort: use String() but this should never happen
320
320
  normalizedString = String(contentValue);
321
321
  // This will be "[object Object]" but we've exhausted all options
322
- console.error('[content-normalizer] Failed to stringify object and no rawText available', {
322
+ logger?.error('[content-normalizer] Failed to stringify object and no rawText available', {
323
323
  error,
324
324
  contentValueType: typeof contentValue,
325
325
  isArray: Array.isArray(contentValue)
@@ -342,7 +342,7 @@ export function normalizeContent(response) {
342
342
  }
343
343
  catch {
344
344
  // If this also fails, we're stuck with "[object Object]"
345
- console.error('[content-normalizer] Cannot recover from "[object Object]" - all recovery attempts failed');
345
+ logger?.error('[content-normalizer] Cannot recover from "[object Object]" - all recovery attempts failed');
346
346
  }
347
347
  }
348
348
  }
@@ -13,6 +13,7 @@
13
13
  * - Native output format support: Extracts and uses flex-md native output formats from instructions
14
14
  * based on compliance level when no explicit format is provided
15
15
  */
16
+ import { type Logxer } from '@x12i/logxer';
16
17
  /**
17
18
  * Load flex-md module (ES Module) asynchronously
18
19
  * Caches the result to avoid multiple imports
@@ -38,7 +39,7 @@ export declare function loadFlexMd(): Promise<any>;
38
39
  * - Other flex-md options
39
40
  * @returns Parsed JSON object and method used, or null if parsing fails
40
41
  */
41
- export declare function extractJsonFromFlexMd(content: string): Promise<{
42
+ export declare function extractJsonFromFlexMd(content: string, logger?: Logxer): Promise<{
42
43
  json: any;
43
44
  method: string;
44
45
  } | null>;
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import * as path from 'path';
17
17
  import { pathToFileURL } from 'url';
18
+ import { DebugLogAbstract } from '@x12i/logxer';
18
19
  let flexMdModule = null;
19
20
  let flexMdLoadError = null;
20
21
  let flexMdLoading = null;
@@ -328,7 +329,7 @@ async function extractFlexMdOutputFormatFromInstructions(instructions, complianc
328
329
  * - Other flex-md options
329
330
  * @returns Parsed JSON object and method used, or null if parsing fails
330
331
  */
331
- export async function extractJsonFromFlexMd(content) {
332
+ export async function extractJsonFromFlexMd(content, logger) {
332
333
  // ARCHITECTURE: ai-gateway layer only handles unstructured parsing
333
334
  // Schema-driven parsing is handled at skills layer with nx-md-parser
334
335
  // No schema parameter - this layer doesn't know about schemas
@@ -346,8 +347,9 @@ export async function extractJsonFromFlexMd(content) {
346
347
  }
347
348
  }
348
349
  catch (error) {
349
- // markdownToJson failed, continue to other methods
350
- console.warn('markdownToJson failed:', error instanceof Error ? error.message : String(error));
350
+ logger?.warn('markdownToJson failed', {
351
+ error: error instanceof Error ? error.message : String(error)
352
+ });
351
353
  }
352
354
  }
353
355
  // Secondary approach: Use extractFromMarkdown for low-level section extraction
@@ -357,15 +359,16 @@ export async function extractJsonFromFlexMd(content) {
357
359
  if (result && typeof result === 'object' && result !== null) {
358
360
  const json = result.json || result.data || result.result || result;
359
361
  if (json && typeof json === 'object' && json !== null) {
360
- console.debug('extractJsonFromFlexMd: using extractFromMarkdown');
361
- console.debug('extractJsonFromFlexMd: json:', json);
362
+ logger?.debug('extractJsonFromFlexMd: using extractFromMarkdown', { debugKind: DebugLogAbstract.TRACE });
363
+ logger?.debug('extractJsonFromFlexMd: parsed json', { json, debugKind: DebugLogAbstract.STATE });
362
364
  return { json, method: 'extractFromMarkdown' };
363
365
  }
364
366
  }
365
367
  }
366
368
  catch (error) {
367
- // extractFromMarkdown failed, continue
368
- console.warn('extractFromMarkdown failed:', error instanceof Error ? error.message : String(error));
369
+ logger?.warn('extractFromMarkdown failed', {
370
+ error: error instanceof Error ? error.message : String(error)
371
+ });
369
372
  }
370
373
  }
371
374
  }
@@ -373,13 +376,14 @@ export async function extractJsonFromFlexMd(content) {
373
376
  // flex-md loading failed, log once and continue to fallback.
374
377
  if (!hasLoggedFlexMdLoadWarning) {
375
378
  hasLoggedFlexMdLoadWarning = true;
376
- console.warn('extractJsonFromFlexMd: flex-md library failed to load, using markdown parser fallback:', error instanceof Error ? error.message : String(error));
379
+ logger?.warn('extractJsonFromFlexMd: flex-md library failed to load, using markdown parser fallback', {
380
+ error: error instanceof Error ? error.message : String(error)
381
+ });
377
382
  }
378
383
  }
379
- console.debug('extractJsonFromFlexMd: using markdown parser fallback');
380
- console.debug('extractJsonFromFlexMd: content:', content);
384
+ logger?.debug('extractJsonFromFlexMd: using markdown parser fallback', { contentLength: content.length });
381
385
  // Final fallback: Parse markdown sections
382
- return fallbackMarkdownParser(content);
386
+ return fallbackMarkdownParser(content, logger);
383
387
  }
384
388
  /**
385
389
  * Helper function to detect and parse markdown lists into arrays
@@ -437,7 +441,7 @@ function parseMarkdownList(content) {
437
441
  /**
438
442
  * Simple markdown parser for unstructured content (fallback when flex-md unavailable)
439
443
  */
440
- function fallbackMarkdownParser(content) {
444
+ function fallbackMarkdownParser(content, logger) {
441
445
  const result = {};
442
446
  const detectedHeaders = [];
443
447
  // Simple section extraction based on markdown headers
@@ -480,12 +484,11 @@ function fallbackMarkdownParser(content) {
480
484
  result[key] = parseMarkdownList(currentContent);
481
485
  detectedHeaders.push(key);
482
486
  }
483
- // Debug logging for incomplete parsing
484
- console.log('Fallback parser detected headers:', detectedHeaders);
487
+ logger?.debug('Fallback parser detected headers', { detectedHeaders, debugKind: DebugLogAbstract.STATE });
485
488
  const expectedHeaders = ['shortAnswer', 'fullAnswer', 'assumptions', 'unknowns', 'evidence'];
486
489
  const missingHeaders = expectedHeaders.filter(h => !detectedHeaders.includes(h));
487
490
  if (missingHeaders.length > 0) {
488
- console.log('Fallback parser missing expected headers:', missingHeaders);
491
+ logger?.debug('Fallback parser missing expected headers', { missingHeaders, debugKind: DebugLogAbstract.STATE });
489
492
  }
490
493
  // If we extracted some structure, return it
491
494
  if (Object.keys(result).length > 0) {
@@ -20,10 +20,10 @@ export interface GatewayConfigContext {
20
20
  messageBuilderConfig: MessageBuilderConfig;
21
21
  }
22
22
  /**
23
- * Loads configuration from JSON files (model config and instructionsBlocks)
24
- * Note: Called before logger is initialized, so no logging here
23
+ * Loads configuration from JSON files (model config and instructionsBlocks).
24
+ * Pass a {@link Logxer} instance so load diagnostics go through logxer (not console).
25
25
  */
26
- export declare function loadConfig(): {
26
+ export declare function loadConfig(logger: Logxer): {
27
27
  defaultModelConfig: Record<string, unknown>;
28
28
  defaultInstructionsBlocks: Record<string, any>;
29
29
  defaultTemplateRendering?: TemplateRenderOptions;
@@ -40,7 +40,7 @@ export declare function setupRequestInterceptor(router: LLMProviderRouter, logge
40
40
  /**
41
41
  * Initializes gateway components
42
42
  */
43
- export declare function initializeGatewayComponents(config: GatewayConfig, defaultModelConfig: Record<string, unknown>, defaultInstructionsBlocks: Record<string, any>, defaultTemplateRendering?: TemplateRenderOptions): {
43
+ export declare function initializeGatewayComponents(config: GatewayConfig): {
44
44
  logger: Logxer;
45
45
  router: LLMProviderRouter;
46
46
  activityManager: ActivityManager;
@@ -53,10 +53,10 @@ import { mergeTemplateRenderOptions } from './template-render-merge.js';
53
53
  import { GatewayRateLimiter } from './gateway-rate-limiter.js';
54
54
  import { DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS, DEFAULT_RATE_LIMIT_ENABLED } from './gateway-rate-limiter-constants.js';
55
55
  /**
56
- * Loads configuration from JSON files (model config and instructionsBlocks)
57
- * Note: Called before logger is initialized, so no logging here
56
+ * Loads configuration from JSON files (model config and instructionsBlocks).
57
+ * Pass a {@link Logxer} instance so load diagnostics go through logxer (not console).
58
58
  */
59
- export function loadConfig() {
59
+ export function loadConfig(logger) {
60
60
  const defaultModelConfig = {};
61
61
  const defaultInstructionsBlocks = {};
62
62
  let defaultTemplateRendering;
@@ -81,26 +81,22 @@ export function loadConfig() {
81
81
  const parsed = JSON.parse(content);
82
82
  // Use Object.assign to merge, preserving nested structure
83
83
  Object.assign(defaultInstructionsBlocks, parsed);
84
- // Debug: Log what was loaded (only in development)
85
- if (process.env.NODE_ENV !== 'production') {
86
- console.log('Loaded instructions blocks:', {
87
- topLevelKeys: Object.keys(defaultInstructionsBlocks),
88
- hasOutput: 'output' in defaultInstructionsBlocks,
89
- outputKeys: 'output' in defaultInstructionsBlocks ? Object.keys(defaultInstructionsBlocks.output) : []
90
- });
91
- }
84
+ logger.debug('Loaded instructions blocks from defaults', {
85
+ topLevelKeys: Object.keys(defaultInstructionsBlocks),
86
+ hasOutput: 'output' in defaultInstructionsBlocks,
87
+ outputKeys: 'output' in defaultInstructionsBlocks ? Object.keys(defaultInstructionsBlocks.output) : []
88
+ });
92
89
  }
93
90
  else {
94
- // Optional file: fallback defaults below still keep the gateway functional.
95
- if (process.env.NODE_ENV !== 'production') {
96
- console.debug('Optional instructions blocks file not found at:', instructionsBlocksPath);
97
- }
91
+ logger.verbose('Optional instructions blocks file not found; using packaged fallbacks', {
92
+ instructionsBlocksPath
93
+ });
98
94
  }
99
95
  }
100
96
  catch (error) {
101
- // Can't log here as logger isn't initialized yet
102
- // Error will be logged after logger initialization in constructor
103
- console.warn('Failed to load defaults from JSON files:', error);
97
+ logger.warn('Failed to load defaults from JSON files', {
98
+ error: error instanceof Error ? error.message : String(error)
99
+ });
104
100
  }
105
101
  // Ensure critical blocks exist even if file loading failed
106
102
  if (!defaultInstructionsBlocks['outputObjectPrefix']) {
@@ -128,10 +124,10 @@ export function getFlexMdMinComplianceLevel() {
128
124
  export function setupRequestInterceptor(router, logger) {
129
125
  logger.debug('Setting up request interceptor for jobId propagation and config cleanup');
130
126
  router.addRequestInterceptor(async (request, provider) => {
131
- // Propagate jobId to request metadata
132
- if (request.jobId) {
133
- logger.verbose('Propagating jobId to request metadata', {
134
- jobId: request.jobId,
127
+ const identityJobId = request?.identity?.jobId;
128
+ if (typeof identityJobId === 'string' && identityJobId.trim().length > 0) {
129
+ logger.verbose('Propagating identity.jobId to request metadata', {
130
+ jobId: identityJobId,
135
131
  provider: provider?.getProviderName?.() || 'unknown'
136
132
  });
137
133
  if (!request.config) {
@@ -140,7 +136,7 @@ export function setupRequestInterceptor(router, logger) {
140
136
  if (!request.config.metadata) {
141
137
  request.config.metadata = {};
142
138
  }
143
- request.config.metadata.jobId = request.jobId;
139
+ request.config.metadata.jobId = identityJobId;
144
140
  }
145
141
  // Remove 'provider' from config - router uses it for routing but providers don't accept it
146
142
  // Router reads config.provider to determine which provider to call, but then passes
@@ -159,14 +155,14 @@ export function setupRequestInterceptor(router, logger) {
159
155
  /**
160
156
  * Initializes gateway components
161
157
  */
162
- export function initializeGatewayComponents(config, defaultModelConfig, defaultInstructionsBlocks, defaultTemplateRendering) {
158
+ export function initializeGatewayComponents(config) {
163
159
  // Initialize logger FIRST (before other components that might need it)
164
160
  const logger = createGatewayLogger({
165
161
  enableLogging: config.enableLogging ?? true,
166
162
  packageName: config.packageName,
167
163
  customLogger: config.logger
168
164
  });
169
- // Now that logger is initialized, log the defaults loading
165
+ const { defaultModelConfig, defaultInstructionsBlocks, defaultTemplateRendering } = loadConfig(logger);
170
166
  logger.verbose('Gateway initializing', {
171
167
  defaultEngine: config.defaultEngine,
172
168
  hasDefaultInstructionsBlocks: Object.keys(defaultInstructionsBlocks).length > 0
@@ -39,7 +39,7 @@ export async function invokeStructuredText(request, gateway) {
39
39
  // Create a new request with structured-text mode
40
40
  const structuredRequest = {
41
41
  ...request,
42
- jobId: `${request.jobId}-step1`
42
+ identity: { ...request.identity }
43
43
  };
44
44
  // Make the call (this will recursively call invoke, but with structured-text mode)
45
45
  return await gateway.invoke(structuredRequest);
@@ -81,7 +81,7 @@ export async function convertStructuredToJson(structuredText, originalRequest, c
81
81
  });
82
82
  try {
83
83
  // Use flex-md 3.0.0 to extract and parse JSON
84
- const flexMdResult = await extractJsonFromFlexMd(structuredText);
84
+ const flexMdResult = await extractJsonFromFlexMd(structuredText, logger);
85
85
  if (!flexMdResult || !flexMdResult.json) {
86
86
  throw new Error('flex-md SDK could not extract JSON from structured text');
87
87
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Maps gateway {@link ActivityIdentity} into Logxer {@link LogMeta} fields for correlation and querying.
3
+ */
4
+ import type { LogMeta } from '@x12i/logxer';
5
+ import type { ActivityIdentity } from './types.js';
6
+ /** Subset of identity forwarded on every structured log line (plus full envelope under `identity`). */
7
+ export declare function activityIdentityToLogMeta(identity: Partial<ActivityIdentity> | undefined): LogMeta;
8
+ export declare function withActivityIdentity(identity: Partial<ActivityIdentity> | undefined, data?: LogMeta, debugKind?: LogMeta['debugKind']): LogMeta;
9
+ export declare const gatewayLogDebug: {
10
+ readonly anomaly: "ANOMALY";
11
+ readonly trace: "TRACE";
12
+ readonly intent: "INTENT";
13
+ readonly event: "EVENT";
14
+ readonly state: "STATE";
15
+ };
@@ -0,0 +1,36 @@
1
+ import { DebugLogAbstract } from '@x12i/logxer';
2
+ /** Subset of identity forwarded on every structured log line (plus full envelope under `identity`). */
3
+ export function activityIdentityToLogMeta(identity) {
4
+ if (!identity) {
5
+ return {};
6
+ }
7
+ const meta = {
8
+ identity: JSON.stringify({
9
+ sessionId: identity.sessionId,
10
+ aiRequestId: identity.aiRequestId,
11
+ jobId: identity.jobId,
12
+ taskId: identity.taskId,
13
+ agentId: identity.agentId
14
+ }),
15
+ jobId: identity.jobId,
16
+ sessionId: identity.sessionId,
17
+ correlationId: identity.aiRequestId,
18
+ taskId: identity.taskId,
19
+ activityIdentity: identity
20
+ };
21
+ return meta;
22
+ }
23
+ export function withActivityIdentity(identity, data, debugKind) {
24
+ return {
25
+ ...activityIdentityToLogMeta(identity),
26
+ ...(data || {}),
27
+ ...(debugKind !== undefined ? { debugKind } : {})
28
+ };
29
+ }
30
+ export const gatewayLogDebug = {
31
+ anomaly: DebugLogAbstract.ANOMALY,
32
+ trace: DebugLogAbstract.TRACE,
33
+ intent: DebugLogAbstract.INTENT,
34
+ event: DebugLogAbstract.EVENT,
35
+ state: DebugLogAbstract.STATE
36
+ };
@@ -113,8 +113,8 @@ export function buildWorkingMemory(request, existingWorkingMemory, otherMemories
113
113
  if (!workingMemory.job.narrative && request.prompt) {
114
114
  workingMemory.job.narrative = request.prompt;
115
115
  }
116
- if (!workingMemory.job.id && request.jobId) {
117
- workingMemory.job.id = request.jobId;
116
+ if (!workingMemory.job.id && request.identity.jobId) {
117
+ workingMemory.job.id = request.identity.jobId;
118
118
  }
119
119
  // Populate task fields from request if not already present in existing memory
120
120
  if (!workingMemory.task.objective && request.instructions) {
@@ -124,8 +124,8 @@ export function buildWorkingMemory(request, existingWorkingMemory, otherMemories
124
124
  workingMemory.task.context = request.context;
125
125
  }
126
126
  // Input field has been removed - data should come from workingMemory.input
127
- if (!workingMemory.task.id && request.taskId) {
128
- workingMemory.task.id = request.taskId;
127
+ if (!workingMemory.task.id && request.identity.taskId) {
128
+ workingMemory.task.id = request.identity.taskId;
129
129
  }
130
130
  // Add entity information if not present
131
131
  if (!workingMemory.entity && request.agentId) {
@@ -182,7 +182,7 @@ export async function resolveTemplateParams(request, config, logger) {
182
182
  // If memory component fails, fall back to request-only mode
183
183
  logger?.warn('Memory component resolution failed, using request-only mode', {
184
184
  error: error instanceof Error ? error.message : String(error),
185
- jobId: request.jobId
185
+ jobId: request.identity.jobId
186
186
  });
187
187
  }
188
188
  }
@@ -194,7 +194,7 @@ export async function resolveTemplateParams(request, config, logger) {
194
194
  // Rendrix priority: shortTermMemory > workingMemory > experienceMemory > knowledgeMemory
195
195
  if (request.templateTokens && Object.keys(request.templateTokens).length > 0) {
196
196
  logger?.debug('Merged templateTokens into shortTermMemory (tier 1 - highest priority)', {
197
- jobId: request.jobId,
197
+ jobId: request.identity.jobId,
198
198
  tokenKeys: Object.keys(request.templateTokens)
199
199
  });
200
200
  }
@@ -24,7 +24,7 @@ function isAIRequest(request) {
24
24
  */
25
25
  export async function constructMessages(request, config, logger, parsedSnapshot) {
26
26
  logger.verbose('Constructing messages from request', {
27
- jobId: request.jobId,
27
+ jobId: request.identity.jobId,
28
28
  agentId: request.agentId,
29
29
  hasInstructions: !!request.instructions,
30
30
  hasContext: !!request.context,
@@ -32,11 +32,11 @@ export async function constructMessages(request, config, logger, parsedSnapshot)
32
32
  hasWorkingMemory: !!request.workingMemory
33
33
  });
34
34
  // LOGGING: Construct messages phase (instructions should already be resolved)
35
- const traceId = request.jobId || `trace-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
35
+ const traceId = request.identity.jobId || request.aiRequestId;
36
36
  const agentId = request.agentId;
37
37
  logger.info('instructions.constructMessages.entry', {
38
38
  traceId,
39
- jobId: request.jobId,
39
+ jobId: request.identity.jobId,
40
40
  agentId,
41
41
  'instructionsType': typeof request.instructions,
42
42
  'instructionsLength': typeof request.instructions === 'string' ? request.instructions.length : 'N/A',
@@ -59,7 +59,7 @@ export async function constructMessages(request, config, logger, parsedSnapshot)
59
59
  if (typeof finalInstructions === 'string') {
60
60
  finalInstructions = `${finalInstructions}\n\nExamples:\n${fewShotExamples}`;
61
61
  logger.debug('Added few-shot examples to instructions', {
62
- jobId: request.jobId,
62
+ jobId: request.identity.jobId,
63
63
  exampleCount: Math.min(examples.length, 3),
64
64
  totalExamples: examples.length
65
65
  });
@@ -2,13 +2,15 @@
2
2
  * Gateway Meta Operations Module
3
3
  * Handles meta operations like instruction optimization and testing
4
4
  */
5
- import type { EnhancedLLMResponse, GatewayConfig } from './types.js';
5
+ import type { ActivityIdentity, EnhancedLLMResponse, GatewayConfig } from './types.js';
6
6
  import type { Logxer } from '@x12i/logxer';
7
7
  import type { AIGateway } from './gateway.js';
8
8
  /**
9
9
  * Test instructions by running them and analyzing the response
10
10
  */
11
11
  export declare function testInstructions(instructions: string, testInput: string, expectedSchema: Record<string, unknown> | undefined, options: {
12
+ /** Mandatory runtime identity for the internal test invoke (upstream job/task correlation). */
13
+ identity: ActivityIdentity;
12
14
  agentId?: string;
13
15
  model?: string;
14
16
  provider?: string;
@@ -17,12 +17,19 @@ export async function testInstructions(instructions, testInput, expectedSchema,
17
17
  if (!model) {
18
18
  throw new Error('Model must be provided in options.model or configured as default');
19
19
  }
20
+ const aiRequestId = `test-instructions-${Date.now()}`;
21
+ const runtimeIdentity = {
22
+ ...options.identity,
23
+ aiRequestId,
24
+ agentId
25
+ };
20
26
  // Create test request with internal system action config
21
27
  const testRequest = {
22
- jobId: `test-instructions-${Date.now()}`,
28
+ aiRequestId,
23
29
  agentId,
24
30
  instructions,
25
- input: testInput,
31
+ identity: runtimeIdentity,
32
+ workingMemory: { input: testInput },
26
33
  config: {
27
34
  model,
28
35
  provider,
@@ -22,7 +22,7 @@ export async function ensureTaskTypeId(request, logger) {
22
22
  // Generate MD5 hash
23
23
  const taskTypeId = generateMD5Hash(preParsedInstructions);
24
24
  logger.debug('Auto-generated taskTypeId from instructions MD5 hash', {
25
- jobId: request.jobId,
25
+ jobId: request.identity.jobId,
26
26
  taskTypeId,
27
27
  instructionsLength: preParsedInstructions.length,
28
28
  instructionsPreview: preParsedInstructions.substring(0, 100) + (preParsedInstructions.length > 100 ? '...' : '')
@@ -41,7 +41,7 @@ export async function mergeConfig(request, config, logger) {
41
41
  : config.internalSystemActions?.skillAudit)
42
42
  : undefined;
43
43
  logger.verbose('Merging request config with gateway defaults', {
44
- jobId: request.jobId,
44
+ jobId: request.identity.jobId,
45
45
  hasRequestConfig: !!request.config,
46
46
  hasModelConfig: !!request.modelConfig,
47
47
  requestModel: request.config?.model,
@@ -96,7 +96,7 @@ export async function mergeConfig(request, config, logger) {
96
96
  // Log if using default model
97
97
  if (!request.config?.model && !internalDefaults?.model) {
98
98
  logger.info('Using default model: gpt-5-nano (no model provided in request)', {
99
- jobId: request.jobId,
99
+ jobId: request.identity.jobId,
100
100
  note: 'Default model ensures requests always work regardless of configuration'
101
101
  });
102
102
  }
@@ -112,7 +112,7 @@ export async function mergeConfig(request, config, logger) {
112
112
  if (flexMdMaxTokens && flexMdMaxTokens > 0) {
113
113
  merged.maxTokens = flexMdMaxTokens;
114
114
  logger.debug('Using maxTokens from flex-md', {
115
- jobId: request.jobId,
115
+ jobId: request.identity.jobId,
116
116
  model: merged.model,
117
117
  provider: merged.provider,
118
118
  maxTokens: merged.maxTokens,
@@ -123,7 +123,7 @@ export async function mergeConfig(request, config, logger) {
123
123
  // flex-md doesn't have model info or returned invalid value - use fallback
124
124
  merged.maxTokens = 2000;
125
125
  logger.debug('Using fallback maxTokens (flex-md unavailable or no model info)', {
126
- jobId: request.jobId,
126
+ jobId: request.identity.jobId,
127
127
  model: merged.model,
128
128
  provider: merged.provider,
129
129
  maxTokens: merged.maxTokens,
@@ -135,7 +135,7 @@ export async function mergeConfig(request, config, logger) {
135
135
  // Error loading flex-md or getting model info - use fallback
136
136
  merged.maxTokens = 2000;
137
137
  logger.debug('Using fallback maxTokens (flex-md error)', {
138
- jobId: request.jobId,
138
+ jobId: request.identity.jobId,
139
139
  model: merged.model,
140
140
  provider: merged.provider,
141
141
  maxTokens: merged.maxTokens,
@@ -149,7 +149,7 @@ export async function mergeConfig(request, config, logger) {
149
149
  // This should rarely happen, but handle edge cases
150
150
  merged.maxTokens = 2000;
151
151
  logger.debug('Using fallback maxTokens (not auto-detected and not explicitly set)', {
152
- jobId: request.jobId,
152
+ jobId: request.identity.jobId,
153
153
  model: merged.model,
154
154
  provider: merged.provider,
155
155
  maxTokens: merged.maxTokens,
@@ -162,7 +162,7 @@ export async function mergeConfig(request, config, logger) {
162
162
  internalDefaults?.maxTokens !== undefined ? `internalSystemActions.${useInternalDefaults}` :
163
163
  'gateway.config';
164
164
  logger.debug('Using explicitly set maxTokens', {
165
- jobId: request.jobId,
165
+ jobId: request.identity.jobId,
166
166
  model: merged.model,
167
167
  provider: merged.provider,
168
168
  maxTokens: merged.maxTokens,
@@ -170,7 +170,7 @@ export async function mergeConfig(request, config, logger) {
170
170
  });
171
171
  }
172
172
  logger.debug('Config merged', {
173
- jobId: request.jobId,
173
+ jobId: request.identity.jobId,
174
174
  finalModel: merged.model,
175
175
  finalProvider: merged.provider,
176
176
  finalTemperature: merged.temperature,
@@ -2,6 +2,13 @@
2
2
  * Gateway Validation Module
3
3
  * Basic validation for clean proxy implementation
4
4
  */
5
+ function validateMandatoryRuntimeIdentity(request) {
6
+ const id = request.identity;
7
+ if (id === undefined || id === null || typeof id !== 'object') {
8
+ throw new Error('identity is required on every request (mandatory runtime identity from upstream)');
9
+ }
10
+ // identity.jobId / identity.taskId: TypeScript requires them; at runtime empty values are logged as WARN in ensureGatewayRequestIdentity.
11
+ }
5
12
  /**
6
13
  * Validates ChatRequest has required fields
7
14
  */
@@ -12,6 +19,7 @@ export function validateChatRequest(request) {
12
19
  if (!request.agentId) {
13
20
  throw new Error('agentId is required');
14
21
  }
22
+ validateMandatoryRuntimeIdentity(request);
15
23
  // Reject input field - it has been removed
16
24
  if ('input' in request && request.input !== undefined) {
17
25
  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.`);
@@ -34,6 +42,7 @@ export function validateAIRequest(request) {
34
42
  if (!request.agentId) {
35
43
  throw new Error('agentId is required for AI requests');
36
44
  }
45
+ validateMandatoryRuntimeIdentity(request);
37
46
  // Reject input field - it has been removed
38
47
  if ('input' in request && request.input !== undefined) {
39
48
  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.`);