@x12i/ai-gateway 7.9.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 (179) hide show
  1. package/README.md +4259 -0
  2. package/config.defaults.json +31 -0
  3. package/dist/activity-manager.d.ts +206 -0
  4. package/dist/activity-manager.js +1051 -0
  5. package/dist/config/activity-tracking-config.d.ts +11 -0
  6. package/dist/config/activity-tracking-config.js +15 -0
  7. package/dist/config.defaults.json +31 -0
  8. package/dist/content-normalizer/content-normalizer.d.ts +46 -0
  9. package/dist/content-normalizer/content-normalizer.js +393 -0
  10. package/dist/content-normalizer/index.d.ts +7 -0
  11. package/dist/content-normalizer/index.js +6 -0
  12. package/dist/content-normalizer/types.d.ts +33 -0
  13. package/dist/content-normalizer/types.js +4 -0
  14. package/dist/defaults/instructions-blocks.json +61 -0
  15. package/dist/defaults/model-config.json +16 -0
  16. package/dist/defaults/template-rendering.json +6 -0
  17. package/dist/flex-md-loader.d.ts +109 -0
  18. package/dist/flex-md-loader.js +940 -0
  19. package/dist/gateway-config.d.ts +49 -0
  20. package/dist/gateway-config.js +292 -0
  21. package/dist/gateway-conversion.d.ts +29 -0
  22. package/dist/gateway-conversion.js +174 -0
  23. package/dist/gateway-instructions.d.ts +30 -0
  24. package/dist/gateway-instructions.js +62 -0
  25. package/dist/gateway-memory.d.ts +51 -0
  26. package/dist/gateway-memory.js +207 -0
  27. package/dist/gateway-messages.d.ts +23 -0
  28. package/dist/gateway-messages.js +83 -0
  29. package/dist/gateway-meta.d.ts +25 -0
  30. package/dist/gateway-meta.js +87 -0
  31. package/dist/gateway-provider-auto-register.d.ts +17 -0
  32. package/dist/gateway-provider-auto-register.js +159 -0
  33. package/dist/gateway-provider.d.ts +54 -0
  34. package/dist/gateway-provider.js +202 -0
  35. package/dist/gateway-rate-limiter-constants.d.ts +16 -0
  36. package/dist/gateway-rate-limiter-constants.js +16 -0
  37. package/dist/gateway-rate-limiter.d.ts +56 -0
  38. package/dist/gateway-rate-limiter.js +107 -0
  39. package/dist/gateway-retry.d.ts +49 -0
  40. package/dist/gateway-retry.js +204 -0
  41. package/dist/gateway-utils.d.ts +21 -0
  42. package/dist/gateway-utils.js +181 -0
  43. package/dist/gateway-validation.d.ts +13 -0
  44. package/dist/gateway-validation.js +50 -0
  45. package/dist/gateway.d.ts +39 -0
  46. package/dist/gateway.js +430 -0
  47. package/dist/index.d.ts +36 -0
  48. package/dist/index.js +55 -0
  49. package/dist/instruction-errors.d.ts +16 -0
  50. package/dist/instruction-errors.js +29 -0
  51. package/dist/instruction-optimizer.d.ts +113 -0
  52. package/dist/instruction-optimizer.js +293 -0
  53. package/dist/instructions-parser.d.ts +31 -0
  54. package/dist/instructions-parser.js +56 -0
  55. package/dist/logger-factory.d.ts +17 -0
  56. package/dist/logger-factory.js +42 -0
  57. package/dist/message-builder.d.ts +41 -0
  58. package/dist/message-builder.js +522 -0
  59. package/dist/object-types-library-integration.d.ts +22 -0
  60. package/dist/object-types-library-integration.js +27 -0
  61. package/dist/object-types-library.d.ts +351 -0
  62. package/dist/object-types-library.js +210 -0
  63. package/dist/output-auditor.d.ts +44 -0
  64. package/dist/output-auditor.js +49 -0
  65. package/dist/request-report-generator.d.ts +60 -0
  66. package/dist/request-report-generator.js +169 -0
  67. package/dist/response-analyzer/format-type-detector.d.ts +35 -0
  68. package/dist/response-analyzer/format-type-detector.js +115 -0
  69. package/dist/response-analyzer/index.d.ts +9 -0
  70. package/dist/response-analyzer/index.js +8 -0
  71. package/dist/response-analyzer/object-type-detector.d.ts +42 -0
  72. package/dist/response-analyzer/object-type-detector.js +95 -0
  73. package/dist/response-analyzer/response-analyzer.d.ts +38 -0
  74. package/dist/response-analyzer/response-analyzer.js +97 -0
  75. package/dist/response-analyzer/types.d.ts +97 -0
  76. package/dist/response-analyzer/types.js +4 -0
  77. package/dist/response-fallback-fixer.d.ts +11 -0
  78. package/dist/response-fallback-fixer.js +123 -0
  79. package/dist/runtime-objects.d.ts +52 -0
  80. package/dist/runtime-objects.js +46 -0
  81. package/dist/template-parser.d.ts +58 -0
  82. package/dist/template-parser.js +99 -0
  83. package/dist/template-render-merge.d.ts +9 -0
  84. package/dist/template-render-merge.js +40 -0
  85. package/dist/troubleshooting-helper.d.ts +123 -0
  86. package/dist/troubleshooting-helper.js +596 -0
  87. package/dist/types.d.ts +1173 -0
  88. package/dist/types.js +6 -0
  89. package/dist/usage-tracker.d.ts +78 -0
  90. package/dist/usage-tracker.js +79 -0
  91. package/dist-cjs/activity-manager.cjs +1056 -0
  92. package/dist-cjs/activity-manager.d.ts +206 -0
  93. package/dist-cjs/config/activity-tracking-config.cjs +18 -0
  94. package/dist-cjs/config/activity-tracking-config.d.ts +11 -0
  95. package/dist-cjs/config.defaults.json +31 -0
  96. package/dist-cjs/content-normalizer/content-normalizer.cjs +398 -0
  97. package/dist-cjs/content-normalizer/content-normalizer.d.ts +46 -0
  98. package/dist-cjs/content-normalizer/index.cjs +12 -0
  99. package/dist-cjs/content-normalizer/index.d.ts +7 -0
  100. package/dist-cjs/content-normalizer/types.cjs +5 -0
  101. package/dist-cjs/content-normalizer/types.d.ts +33 -0
  102. package/dist-cjs/defaults/instructions-blocks.json +61 -0
  103. package/dist-cjs/defaults/model-config.json +16 -0
  104. package/dist-cjs/defaults/template-rendering.json +6 -0
  105. package/dist-cjs/flex-md-loader.cjs +986 -0
  106. package/dist-cjs/flex-md-loader.d.ts +109 -0
  107. package/dist-cjs/gateway-config.cjs +331 -0
  108. package/dist-cjs/gateway-config.d.ts +49 -0
  109. package/dist-cjs/gateway-conversion.cjs +212 -0
  110. package/dist-cjs/gateway-conversion.d.ts +29 -0
  111. package/dist-cjs/gateway-instructions.cjs +67 -0
  112. package/dist-cjs/gateway-instructions.d.ts +30 -0
  113. package/dist-cjs/gateway-memory.cjs +211 -0
  114. package/dist-cjs/gateway-memory.d.ts +51 -0
  115. package/dist-cjs/gateway-messages.cjs +86 -0
  116. package/dist-cjs/gateway-messages.d.ts +23 -0
  117. package/dist-cjs/gateway-meta.cjs +90 -0
  118. package/dist-cjs/gateway-meta.d.ts +25 -0
  119. package/dist-cjs/gateway-provider-auto-register.cjs +195 -0
  120. package/dist-cjs/gateway-provider-auto-register.d.ts +17 -0
  121. package/dist-cjs/gateway-provider.cjs +214 -0
  122. package/dist-cjs/gateway-provider.d.ts +54 -0
  123. package/dist-cjs/gateway-rate-limiter-constants.cjs +19 -0
  124. package/dist-cjs/gateway-rate-limiter-constants.d.ts +16 -0
  125. package/dist-cjs/gateway-rate-limiter.cjs +111 -0
  126. package/dist-cjs/gateway-rate-limiter.d.ts +56 -0
  127. package/dist-cjs/gateway-retry.cjs +212 -0
  128. package/dist-cjs/gateway-retry.d.ts +49 -0
  129. package/dist-cjs/gateway-utils.cjs +219 -0
  130. package/dist-cjs/gateway-utils.d.ts +21 -0
  131. package/dist-cjs/gateway-validation.cjs +54 -0
  132. package/dist-cjs/gateway-validation.d.ts +13 -0
  133. package/dist-cjs/gateway.cjs +434 -0
  134. package/dist-cjs/gateway.d.ts +39 -0
  135. package/dist-cjs/index.cjs +108 -0
  136. package/dist-cjs/index.d.ts +36 -0
  137. package/dist-cjs/instruction-errors.cjs +34 -0
  138. package/dist-cjs/instruction-errors.d.ts +16 -0
  139. package/dist-cjs/instruction-optimizer.cjs +299 -0
  140. package/dist-cjs/instruction-optimizer.d.ts +113 -0
  141. package/dist-cjs/instructions-parser.cjs +61 -0
  142. package/dist-cjs/instructions-parser.d.ts +31 -0
  143. package/dist-cjs/logger-factory.cjs +45 -0
  144. package/dist-cjs/logger-factory.d.ts +17 -0
  145. package/dist-cjs/message-builder.cjs +558 -0
  146. package/dist-cjs/message-builder.d.ts +41 -0
  147. package/dist-cjs/object-types-library-integration.cjs +32 -0
  148. package/dist-cjs/object-types-library-integration.d.ts +22 -0
  149. package/dist-cjs/object-types-library.cjs +215 -0
  150. package/dist-cjs/object-types-library.d.ts +351 -0
  151. package/dist-cjs/output-auditor.cjs +52 -0
  152. package/dist-cjs/output-auditor.d.ts +44 -0
  153. package/dist-cjs/request-report-generator.cjs +172 -0
  154. package/dist-cjs/request-report-generator.d.ts +60 -0
  155. package/dist-cjs/response-analyzer/format-type-detector.cjs +119 -0
  156. package/dist-cjs/response-analyzer/format-type-detector.d.ts +35 -0
  157. package/dist-cjs/response-analyzer/index.cjs +14 -0
  158. package/dist-cjs/response-analyzer/index.d.ts +9 -0
  159. package/dist-cjs/response-analyzer/object-type-detector.cjs +99 -0
  160. package/dist-cjs/response-analyzer/object-type-detector.d.ts +42 -0
  161. package/dist-cjs/response-analyzer/response-analyzer.cjs +101 -0
  162. package/dist-cjs/response-analyzer/response-analyzer.d.ts +38 -0
  163. package/dist-cjs/response-analyzer/types.cjs +5 -0
  164. package/dist-cjs/response-analyzer/types.d.ts +97 -0
  165. package/dist-cjs/response-fallback-fixer.cjs +126 -0
  166. package/dist-cjs/response-fallback-fixer.d.ts +11 -0
  167. package/dist-cjs/runtime-objects.cjs +52 -0
  168. package/dist-cjs/runtime-objects.d.ts +52 -0
  169. package/dist-cjs/template-parser.cjs +136 -0
  170. package/dist-cjs/template-parser.d.ts +58 -0
  171. package/dist-cjs/template-render-merge.cjs +43 -0
  172. package/dist-cjs/template-render-merge.d.ts +9 -0
  173. package/dist-cjs/troubleshooting-helper.cjs +611 -0
  174. package/dist-cjs/troubleshooting-helper.d.ts +123 -0
  175. package/dist-cjs/types.cjs +7 -0
  176. package/dist-cjs/types.d.ts +1173 -0
  177. package/dist-cjs/usage-tracker.cjs +83 -0
  178. package/dist-cjs/usage-tracker.d.ts +78 -0
  179. package/package.json +91 -0
package/README.md ADDED
@@ -0,0 +1,4259 @@
1
+ # @x12i/ai-gateway
2
+
3
+ Unified gateway for LLM provider routing and management with production-ready features: context propagation, usage tier tracking, activity tracking, and comprehensive metadata. Built on top of `@x12i/ai-providers-router` with integrations for `@x12i/x-models`, `@x12i/activix` **v5.x** (xronox-activitix; this repo pins `^5.0.1` — see `package.json`), and `logs-gateway`.
4
+
5
+ ## Features
6
+
7
+ - **🔀 Provider Routing**: Dynamic provider registration and automatic routing with fallback support
8
+ - **📊 Context Propagation**: `aiRequestId` and identity propagation for distributed tracing (see [Identity contract](./docs/IDENTITY_OBJECT_CONTRACT.md))
9
+ - **⚡ Usage Tier Tracking**: RPM/TPM limit enforcement via `@x12i/x-models`
10
+ - **📈 Activity Tracking**: Comprehensive activity logging via `@x12i/activix` v5 (xronox-activitix), fixed Mongo collections `ai-activities` / `bad-requests`, validated `structure` + `runContext` for Activix 5
11
+ - **📝 Structured Logging**: Production-ready logging via `logs-gateway` with comprehensive diagnostic tracing for instruction resolution and propagation debugging
12
+ - **📋 Rich Metadata**: Detailed execution metadata (latency, tokens, model, cost, `aiRequestId`, `identity`)
13
+ - **🏥 Health Checks**: Monitor provider health and availability
14
+ - **🔄 Request/Response Interceptors**: Modify requests and responses
15
+ - **🔍 Auto-Discovery**: Automatically discover and register installed provider packages
16
+ - **🎯 Object Type Output Support**: Parse responses into typed inference outputs (classification, extraction, Q&A, etc.) via `@x12i/outputs-library`
17
+ - **✅ Enhanced Schema Validation**: Strict/non-strict validation modes, automatic schema resolution from instruction metadata, and graceful outputs library fallback (v1.7.0+)
18
+ - **✅ Graceful Outputs Library Error Handling**: Automatic fallback parsing, clear error detection, and parsing method metadata (v1.7.1+)
19
+ - **✅ Guaranteed Consistent Structure**: Always returns consistent structure at all levels (content, parsedContent, parsedOutput) - JSON is always JSON, text is always text, structures are forced when needed (v1.7.4+)
20
+ - **📋 Automatic Output Schema Guidance**: Automatically extends instructions with JSON schema expectations when outputType/schema is available (v2.1.1+)
21
+ - **🔍 Output Structure Audit**: Automatically audits response structure against schema - identifies missing/extra fields, always available when schema exists (v2.1.1+)
22
+ - **🔄 Automatic Retry**: Intelligent retry logic for network errors, server errors (5xx), and throttling (429) with exponential backoff
23
+ - **📚 Content Resolver (nx-content)**: Resolve instruction keys, prompt keys, and instructions blocks from local folder or git repo via **nx-content**. See [Content Resolver — Upstream Guide](./CONTENT_RESOLVER_UPSTREAM_GUIDE.md).
24
+ - **📋 Instruction Metadata API**: Fetch structured metadata (outputType, schema, validation rules) for metadata-driven inference systems (v1.6.9+)
25
+ - **🔧 Response Transformation Hooks**: Transform responses at different stages (preParse, postParse, preValidate, postValidate) for output mapping and data normalization (v1.6.9+)
26
+ - **🔧 Custom/Dynamic Instructions Mode**: Use instructions that already contain full JSON schema - no schema formatting added, instructions used exactly as provided (v3.0.4+)
27
+ - **🤖 Response Repair Fallback**: In `mode=prod`, performs a minimal in-gateway repair attempt for malformed JSON/Markdown responses (logs a warning when used). In `mode=debug`, parsing failures hard-fail for maximum visibility.
28
+ - **📊 Response Fix Metadata**: Track when and how responses were fixed, including fix strategy, confidence, and warnings (v3.0.4+)
29
+ - **🔍 Instruction Optimizer**: Use AI to analyze and fix poorly-written instructions - meta-feature that improves instruction quality (v3.0.4+)
30
+ - **🧪 Instruction Testing**: Test instructions by running them and analyzing if responses match expected format (v3.0.4+)
31
+ - **📝 Multiple Output Modes**: Support for JSON output, structured text output, and two-step conversion (v3.0.5+)
32
+ - **📋 Dual Instruction Formats**: Support for JSON schema instructions and structured text format specifications (v3.0.5+)
33
+ - **📚 Standard Object Types**: Reference standard object types by name (e.g., `'sentiment-analysis'`) instead of defining schemas manually - includes examples, validation, and structured text instructions (v3.0.6+)
34
+ - **🔍 Auto-Extraction of Output Formats**: Automatically extract output format specifications from instruction templates when using structured-text mode - no need to manually specify `flexMdFormat` or `primaryObjectType` (v3.3.3+)
35
+ - **✅ Output Format Validation**: Validates output format specifications using flex-md SDK before sending to LLM, with configurable minimum compliance level (L0-L3)
36
+ - **📋 Contract Output Parsing**: Parse AI responses against expected schemas and store results in activity records for compliance monitoring (v6.3.1+)
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ npm install @x12i/ai-gateway
42
+ ```
43
+
44
+ **📚 Documentation**: After installation, documentation is available in:
45
+ - `node_modules/@x12i/ai-gateway/CONTENT_RESOLVER_UPSTREAM_GUIDE.md` - **Content resolver (nx-content)**: config, keys, local/git, upstream checklist
46
+ - `node_modules/@x12i/ai-gateway/docs/IDENTITY_OBJECT_CONTRACT.md` - **Identity contract** for Activix (`sessionId` + `instance`)
47
+ - `node_modules/@x12i/ai-gateway/docs/LOGGER_INITIALIZATION.md` - **Required reading**: How to properly initialize logger
48
+ - `node_modules/@x12i/ai-gateway/TROUBLESHOOTING.md` - Troubleshooting guide
49
+ - `node_modules/@x12i/ai-gateway/TROUBLESHOOTING_TOOLBOX.md` - Diagnostic tools
50
+ - `node_modules/@x12i/ai-gateway/INTEGRATION_GUIDANCE.md` - Integration guidance
51
+
52
+ **🔧 Troubleshooting Helpers**: Import diagnostic functions directly:
53
+ ```typescript
54
+ import { validateAIRequest, diagnoseRequest, formatDiagnostic } from '@x12i/ai-gateway';
55
+ ```
56
+
57
+ **🔍 Debugging**: Enable detailed request logging and comprehensive diagnostic tracing:
58
+
59
+ ```bash
60
+ export AI_GATEWAY_DEBUG=true # Basic request logging
61
+ export AI_GATEWAY_DEBUG_REQUEST=true # Detailed request structure
62
+ export FLEX_MD_MIN_COMPLIANCE_LEVEL=L0 # Output format validation level (L0/L1/L2/L3, default: L0)
63
+ ```
64
+
65
+ This logs the exact request structure received by `invoke()`, including property descriptors, which is critical for debugging validation errors like "objectTypes is required".
66
+
67
+ ### 🔍 Advanced Diagnostic Logging
68
+
69
+ The gateway includes comprehensive diagnostic logging for instruction resolution and propagation debugging. When debug logging is enabled, the following diagnostic events are logged:
70
+
71
+ **Phase 2 Instruction Resolution:**
72
+ - `instructions.phase2.validation_inputs` - Logs comparison inputs for key echo validation
73
+ - `instructions.phase2.resolution_result` - Logs resolution status, source, and attempts
74
+
75
+ **Instruction Propagation Chain:**
76
+ - `instructions.propagation.autoExtract.entry` - Instruction hash at auto-extraction
77
+ - `instructions.propagation.constructMessages.entry` - Instruction hash at message construction
78
+ - `instructions.propagation.providerInvoke.entry` - System prompt hash at LLM invocation
79
+
80
+ **Resolution Detection:**
81
+ - `instructions.constructMessages.entry` - Detects if constructMessages receives resolved instructions
82
+
83
+ **Gate Checks:**
84
+ - `gate.activityStart.precheck` - Validates instructions before activity tracking
85
+ - `gate.llmInvoke.precheck` - Validates instructions before LLM calls
86
+
87
+ **Error Handling:**
88
+ - `badRequest.written` - Confirms bad request path execution
89
+
90
+ **Benefits:**
91
+ - **Trace ID**: Each request gets a stable trace ID for correlation across all logs
92
+ - **Hash Chain**: Track instruction content changes through the entire pipeline
93
+ - **Fail-Safe Gating**: Verify that invalid instructions are properly rejected
94
+ - **Resolution Audit**: Detect double-resolution or propagation failures
95
+
96
+ All diagnostic logs include `traceId`, `jobId`, and `agentId` for correlation. Content is safely redacted (first 80 chars only) with hashes for comparison.
97
+
98
+ ## Quick Start
99
+
100
+ ### Basic Usage with Enhanced Gateway
101
+
102
+ ```typescript
103
+ import { AIGateway } from '@x12i/ai-gateway';
104
+ import { OpenAIProvider } from '@x12i/ai-provider-openai';
105
+ import { GrokProvider } from '@x12i/ai-provider-grok';
106
+
107
+ // Create enhanced gateway
108
+ const gateway = new AIGateway({
109
+ defaultProvider: 'openai',
110
+ fallbackChain: ['grok'],
111
+ usageTier: 'tier-3', // RPM/TPM limits
112
+ enableActivityTracking: true,
113
+ enableUsageTracking: true,
114
+ enableLogging: true
115
+ });
116
+
117
+ // Register providers
118
+ gateway.register(new OpenAIProvider({
119
+ apiKey: process.env.OPENAI_API_KEY
120
+ }));
121
+ gateway.register(new GrokProvider({
122
+ apiKey: process.env.GROK_API_KEY
123
+ }));
124
+
125
+ // Invoke with context
126
+ const response = await gateway.invoke({
127
+ messages: [{ role: 'user', content: 'Hello!' }],
128
+ jobId: 'job-123', // Context propagation
129
+ agentId: 'agent-456',
130
+ taskId: 'task-789'
131
+ });
132
+
133
+ // Response includes comprehensive metadata
134
+ console.log(response.metadata);
135
+ // {
136
+ // jobId: 'job-123',
137
+ // latencyMs: 1250,
138
+ // tokens: { prompt: 100, completion: 50, total: 150 },
139
+ // model: 'gpt-4o',
140
+ // provider: 'openai',
141
+ // cost: 0.002
142
+ // }
143
+ ```
144
+
145
+ ### Using Base Router (Direct Access)
146
+
147
+ ```typescript
148
+ import { LLMProviderRouter } from '@x12i/ai-gateway';
149
+
150
+ // Use base router if you don't need enhanced features
151
+ const router = new LLMProviderRouter({
152
+ defaultProvider: 'openai',
153
+ fallbackChain: ['grok']
154
+ });
155
+
156
+ router.register(new OpenAIProvider({
157
+ apiKey: process.env.OPENAI_API_KEY
158
+ }));
159
+
160
+ const response = await router.invoke({
161
+ messages: [{ role: 'user', content: 'Hello!' }]
162
+ });
163
+ ```
164
+
165
+ ### Provider registration and OpenRouter (no manual register required)
166
+
167
+ If you only use the gateway (e.g. via `@woroces/ai-tasks`) and do not call `gateway.register()` or configure the router yourself:
168
+
169
+ - **OpenRouter:** Set `OPEN_ROUTER_KEY` or `OPENROUTER_API_KEY` in the environment and do not set `USE_OPENROUTER=false`. The gateway enables OpenRouter mode so the router can route without any registered provider (requires router support). Load `.env` before any code that creates the gateway; if the gateway is created by another package (e.g. ai-skills) before env is loaded, pass the key explicitly: `openrouter: { apiKey: process.env.OPEN_ROUTER_KEY ?? process.env.OPENROUTER_API_KEY }` in the gateway config.
170
+ - **Direct providers:** Set the relevant API key (e.g. `OPENAI_API_KEY`, `GROK_API_KEY`). The gateway **lazy-auto-registers** these on first `invoke()`/`invokeChat()`, so you do not need to call `autoRegisterProviders` or `register()`.
171
+
172
+ If you see **"No provider specified and no providers registered"** or **"Provider not registered: openrouter"**, set `OPEN_ROUTER_KEY` (or another provider’s API key), ensure `.env` is loaded before the process that creates the gateway, or pass `openrouter: { apiKey }` in the gateway config. See [TROUBLESHOOTING.md](./TROUBLESHOOTING.md#issue-no-provider-specified-and-no-providers-registered) for details.
173
+
174
+ ## Setup Guide
175
+
176
+ This guide shows where and how to configure each functionality of the AI Gateway.
177
+
178
+ ### Configuration Overview
179
+
180
+ The AI Gateway can be configured in multiple ways:
181
+ 1. **Gateway Constructor** - Main configuration when creating the gateway
182
+ 2. **JSON Default Files** - Default configurations loaded from `src/defaults/` (model-config.json, instructions-blocks.json)
183
+ 3. **Environment Variables** - Via `nx-config2` (for logging and other settings)
184
+ - `FLEX_MD_MIN_COMPLIANCE_LEVEL` - Minimum flex-md compliance level for output format validation (default: `L0`). Valid values: `L0`, `L1`, `L2`, `L3`. See [Output Format Validation](#output-format-validation) section for details. When set to `L0` (default), no format validation is required. When set to `L1` or higher, format specifications are required in instructions and validation errors will reject requests.
185
+ 4. **Request-Level** - Override gateway defaults per request
186
+
187
+ ### 1. Logging Configuration (logs-gateway)
188
+
189
+ **Logger Initialization: Two Options**
190
+
191
+ The gateway supports two ways to initialize the logger:
192
+
193
+ 1. **Option 1 (Recommended)**: Provide your project's logger instance - ensures consistent logging
194
+ 2. **Option 2 (Fallback)**: Let the gateway create a default logger - works out of the box
195
+
196
+ **📚 See [Logger Initialization Guide](./docs/LOGGER_INITIALIZATION.md) for complete instructions.**
197
+
198
+ **Where to configure:**
199
+ - Gateway constructor: `enableLogging`, `packageName`, `logger` (**logger is REQUIRED**)
200
+ - Environment variables: **`{PREFIX}_LOGS_LEVEL`** (canonical per-package level; see below), legacy `{PREFIX}_LOG_LEVEL`, plus `{PREFIX}_LOG_FORMAT`, etc. Cross-cutting sink/format options are configured by the host app; see [logs-gateway](https://www.npmjs.com/package/logs-gateway) README and [`docs/package-usage.md`](https://github.com/nx-intelligence/logs-gateway/blob/main/docs/package-usage.md).
201
+
202
+ **How to configure:**
203
+
204
+ ```typescript
205
+ import { AIGateway } from '@x12i/ai-gateway';
206
+ import { createLogger } from 'logs-gateway';
207
+
208
+ // Create logger once at application startup
209
+ const logger = createLogger(
210
+ { packageName: 'MY_APP', envPrefix: 'MY_APP' },
211
+ {
212
+ logLevel: 'info', // verbose|debug|info|warn|error
213
+ logFormat: 'json', // text|json|yaml
214
+ logToFile: true,
215
+ logFilePath: '/var/log/app.log',
216
+ enableUnifiedLogger: true,
217
+ unifiedLogger: {
218
+ transports: { papertrail: true },
219
+ service: 'my-app',
220
+ env: 'production'
221
+ }
222
+ }
223
+ );
224
+
225
+ // Pass the SAME logger instance to gateway (REQUIRED)
226
+ const gateway = new AIGateway({
227
+ enableLogging: true,
228
+ logger: logger, // REQUIRED: Use your project's logger instance
229
+ packageName: 'MY_APP'
230
+ });
231
+ ```
232
+
233
+ **Why Use the Same Logger?**
234
+ - Consistent log format across your application
235
+ - Unified logging destination
236
+ - Proper context and metadata
237
+ - Single point of control for log levels
238
+
239
+ **Per-package log level (logs-gateway ≥ 3.5.x):**
240
+
241
+ - **Canonical:** `{PREFIX}_LOGS_LEVEL` — use the same `envPrefix` you pass to `createLogger` / your `packageName` (e.g. `MY_APP` → `MY_APP_LOGS_LEVEL`).
242
+ - **Legacy:** `{PREFIX}_LOG_LEVEL` is used only if `{PREFIX}_LOGS_LEVEL` is **not** set.
243
+ - **Default** when both are unset: **`warn`** (not `info`, not silent).
244
+ - **Silence** this package’s diagnostics: `off`, `none`, or `silent` (case-insensitive).
245
+ - **Values:** `off` \| `none` \| `silent` \| `error` \| `warn` \| `info` \| `debug` \| `verbose`.
246
+
247
+ If the gateway builds the default logger and you omit `packageName`, the prefix is **`AI_GATEWAY`** → e.g. **`AI_GATEWAY_LOGS_LEVEL`**.
248
+
249
+ **Environment Variables (examples):**
250
+ ```bash
251
+ MY_APP_LOGS_LEVEL=info # raise verbosity (or use debug / verbose)
252
+ MY_APP_LOGS_LEVEL=off # silence this package’s logs
253
+ # MY_APP_LOG_LEVEL=info # legacy; ignored if MY_APP_LOGS_LEVEL is set
254
+
255
+ MY_APP_LOG_FORMAT=json # text|json|yaml
256
+ MY_APP_LOG_TO_FILE=true
257
+ MY_APP_LOG_FILE=/var/log/app.log
258
+ MY_APP_LOG_TO_UNIFIED=true
259
+ DEBUG=my-app # elevates verbose/debug when package is not fully silent
260
+ ```
261
+
262
+ **🔍 Diagnostic Logging:** When debug level is enabled, the gateway provides comprehensive diagnostic logs for instruction resolution and propagation. See the debugging section above for details on the diagnostic events logged.
263
+
264
+ **What Happens If Logger is Not Provided:**
265
+ If `logger` is not provided, the gateway will automatically create a default logger. However, for best results (consistent format, unified destination), it's recommended to provide your project's logger instance.
266
+
267
+ ### 2. Activity Tracking Configuration (xronox-activitix via @x12i/activix v5)
268
+
269
+ **Activix version:** This gateway targets **`@x12i/activix` v5.x** (built on `@xronoces/xronox-store`). The dependency range is declared in `package.json` (currently `^5.0.1`).
270
+
271
+ **Where to configure:**
272
+ - Gateway constructor: `enableActivityTracking`, `activityTracker`
273
+ - **Environment variables** (auto-configured when no custom tracker provided):
274
+ - `MONGO_URI` (required) - MongoDB connection string
275
+ - `MONGO_LOGS_DB` or `MONGO_DB` (required) - Database name
276
+
277
+ **✅ Centralized configuration**
278
+
279
+ The gateway resolves activity tracking from environment variables via `src/config/activity-tracking-config.ts`. **Main and bad-request collection names are fixed at the package level** (not overridden by env): **`ai-activities`** for normal runs and **`bad-requests`** for failed/invalid requests. **`skill-executions`** is used for skill-related flows when applicable.
280
+
281
+ **Environment variable priority (connection only):**
282
+ - **Database**: `MONGO_LOGS_DB` → `MONGO_DB` (no default, must be provided)
283
+ - **URI**: `MONGO_URI` (required)
284
+
285
+ **⚠️ CRITICAL: correlation and identity**
286
+
287
+ - **`aiRequestId`** (required on each gateway request): Primary correlation id for this LLM call; the gateway does **not** invent a `jobId` for you.
288
+ - **Run context** (Activix BSON field `runContext`): `sessionId` plus nested `instance: { instanceId, type }`, plus gateway correlation fields; see [Identity contract](./docs/IDENTITY_OBJECT_CONTRACT.md) (request still uses `identity`; persisted row uses `runContext`).
289
+ - **`jobTypeId`**, **`taskId`**, **`taskTypeId`**: Optional aggregation / grouping fields (unchanged semantics).
290
+ - **Each activity**: Gets its own **unique database record** with unique `_id` (MongoDB ObjectId).
291
+ - **Two-phase tracking**: `startActivity()` creates a new record; `logSuccess()` / `logFailure()` update the same record by that record’s id.
292
+
293
+ **Runtime objects observability (debug only):**
294
+
295
+ `@x12i/ai-gateway` exports `runtimeObjects` for runtime diagnostics. This package is a leaf runtime package, so `runtimeObjects?.packagesRuntimeObjects` is always `[]`.
296
+
297
+ Runtime objects are available only in debug mode:
298
+
299
+ ```env
300
+ mode=debug
301
+ ```
302
+
303
+ `debug` is the default when `mode` is omitted. In production, use:
304
+
305
+ ```env
306
+ mode=prod
307
+ ```
308
+
309
+ When `mode=prod`, `runtimeObjects` is `undefined`.
310
+
311
+ ```typescript
312
+ import { runtimeObjects } from '@x12i/ai-gateway';
313
+
314
+ const activities = await runtimeObjects?.activixClient?.getJobActivities({ jobId });
315
+ const logs = await runtimeObjects?.logxerClient?.getJobLogs({ jobId });
316
+ ```
317
+
318
+ The gateway only exposes official queryable clients. It exposes `activixClient` only when the effective Activix client already implements `getJobActivities()`, and `logxerClient` only when the effective Logxer client already implements `getJobLogs()`. The gateway does not query Mongo, Logxer storage, or private package internals to emulate missing query APIs.
319
+
320
+ See [Runtime Objects Observability Methodology](./docs/RUNTIME_OBJECTS_OBSERVABILITY.md) for the reusable package-level contract.
321
+
322
+ **Recommended (auto-configured from environment variables):**
323
+
324
+ ```typescript
325
+ import { AIGateway } from '@x12i/ai-gateway';
326
+ import { OpenAIProvider } from '@x12i/ai-provider-openai';
327
+
328
+ // Set environment variables:
329
+ // MONGO_URI=mongodb://localhost:27017
330
+ // MONGO_LOGS_DB=logs-db
331
+
332
+ const gateway = new AIGateway({
333
+ enableActivityTracking: true, // default: true
334
+ // Activix is auto-configured; writes use collections ai-activities / bad-requests / skill-executions
335
+ });
336
+
337
+ gateway.register(new OpenAIProvider({
338
+ apiKey: process.env.OPENAI_API_KEY
339
+ }));
340
+ ```
341
+
342
+ **Advanced (custom Activix v5 instance):**
343
+
344
+ If you pass your own `Activix`, configure **the same collection names** the gateway expects so routing matches persistence:
345
+
346
+ ```typescript
347
+ import { AIGateway } from '@x12i/ai-gateway';
348
+ import { Activix } from '@x12i/activix';
349
+
350
+ const statusValues = {
351
+ started: 'started',
352
+ inProgress: 'in_progress',
353
+ completed: 'success',
354
+ failed: 'failed',
355
+ timeout: 'timeout'
356
+ };
357
+
358
+ const activityTracker = new Activix({
359
+ collections: [
360
+ { name: 'ai-activities', statusValues },
361
+ { name: 'skill-executions', statusValues },
362
+ { name: 'bad-requests', statusValues }
363
+ ]
364
+ });
365
+
366
+ const gateway = new AIGateway({
367
+ enableActivityTracking: true, // default: true
368
+ activityTracker, // plug in custom tracker
369
+ });
370
+ ```
371
+
372
+ **What gets tracked (persisted when DB is configured):**
373
+ - **Identity**: `sessionId`, `instance`, **`aiRequestId`** (correlation), optional deprecated `jobId` for compatibility, plus `jobTypeId`, `agentId`, `taskId`, `taskTypeId` as provided
374
+ - **Timing**: `startTime`, `endTime`, `duration`, `status` (`started|success|failed`)
375
+ - **Request data**: Stored in `request` object (instructions, prompt, input, messages, workingMemory)
376
+ - **Config data**: Stored in `config` object (model, provider, temperature, maxTokens)
377
+ - **Response data**: Stored in `response` object (content, metadata)
378
+ - **Cost**: Calculated and stored per activity
379
+
380
+ **Best Practices for Type IDs:**
381
+ - **`jobTypeId`**: Use MD5 hash of your job type string (e.g., `MD5('data-processing-job')`) for consistent job-level aggregation
382
+ - **`taskTypeId`**: Use MD5 hash of your task/instruction text (e.g., `MD5('What is the capital of France?')`) for consistent task-level aggregation
383
+ - If `taskTypeId` is not provided, it's auto-generated from the pre-parsed instructions MD5 hash
384
+ - Same type = same hash = easy aggregation and tracking across multiple jobs/tasks
385
+
386
+ **Key design points:**
387
+ - ✅ Each activity = separate database record with unique `_id`
388
+ - ✅ **`aiRequestId`** = per-request correlation (required); optional `jobId` only if you supply it for grouping
389
+ - ✅ Request data sent once in `startActivity()` (creates new record)
390
+ - ✅ Response data sent once in `logSuccess()` (updates same record by `_id`)
391
+
392
+ **Default:** Activity tracking is enabled by default; without DB config it will log but not persist.
393
+
394
+ **✅ Activix v5 integration**
395
+
396
+ 1. **Configuration** (`activity-tracking-config.ts`):
397
+ - Mongo connection from env; **collection names** `ai-activities` and `bad-requests` are fixed for consistency across deployments.
398
+
399
+ 2. **Lifecycle** (`@x12i/activix` v5):
400
+ - ✅ `startRecord` / `completeRecord` / `failRecord` (two-phase lifecycle)
401
+ - ✅ Status transitions: `started` → `success` or `failed` (per your `statusValues` mapping)
402
+ - ✅ Persistence via xronox-store queue semantics (see Activix package docs)
403
+
404
+ 3. **Testing** (ai-gateway):
405
+ - Standalone test available (`npm run test:activities:standalone`) that bypasses config parsing issues
406
+ - Tests activity lifecycle end-to-end: creation → completion → database persistence
407
+ - See `.tests/TESTING_GUIDE.md` for complete testing documentation
408
+
409
+ **See**: `.reports/new/ACTIVITY_LIFECYCLE_IMPROVEMENTS_VERIFICATION_REPORT.md` for complete verification details.
410
+
411
+ #### Skill Execution Tracking
412
+
413
+ When executing skills (instruction keys starting with `skills/`), the gateway automatically tracks skill executions separately from gateway invocations. Skill executions are stored in the `skill-executions` collection and support parent-child relationships.
414
+
415
+ **Required Fields:**
416
+ - `instructions`: Skill instruction key (e.g., `skills/professional-answer`)
417
+ - `inferenceType`: Recommended - type of inference (e.g., `question-answer`, `classification`)
418
+
419
+ **Optional Fields:**
420
+ - `masterSkillActivityId`: Parent skill activity ID (when a skill calls another skill)
421
+ - `skillId`: Skill identifier (auto-detected from instruction key, can be overridden)
422
+ - `masterSkillId`: Parent skill identifier (when a skill calls another skill)
423
+
424
+ **Example: Basic Skill Execution**
425
+
426
+ ```typescript
427
+ const response = await gateway.invoke({
428
+ jobId: 'job-123',
429
+ agentId: 'my-agent',
430
+ instructions: 'skills/professional-answer',
431
+ inferenceType: 'question-answer', // Recommended
432
+ skillId: 'skills/professional-answer', // Optional, auto-detected from instructions
433
+ input: { question: 'What is...' },
434
+ primaryObjectType: 'professional-answer'
435
+ });
436
+
437
+ // Get the activity ID for linking child skills
438
+ const activityId = response.metadata?.activityId;
439
+ ```
440
+
441
+ **Example: Nested Skill Execution (Skill Calling Another Skill)**
442
+
443
+ ```typescript
444
+ // Parent skill execution
445
+ const parentResponse = await gateway.invoke({
446
+ jobId: 'job-123',
447
+ agentId: 'my-agent',
448
+ instructions: 'skills/parent-skill',
449
+ inferenceType: 'analysis',
450
+ skillId: 'skills/parent-skill'
451
+ });
452
+
453
+ // When parent skill calls child skill, pass parent's activityId and skillId
454
+ const childResponse = await gateway.invoke({
455
+ jobId: 'job-123', // ✅ Same jobId (links activities)
456
+ agentId: 'my-agent',
457
+ instructions: 'skills/child-skill',
458
+ inferenceType: 'question-answer',
459
+ skillId: 'skills/child-skill',
460
+ masterSkillActivityId: parentResponse.metadata?.activityId, // ✅ Parent's activity ID
461
+ masterSkillId: 'skills/parent-skill' // ✅ Parent's skill ID
462
+ });
463
+ ```
464
+
465
+ **Automatic Tracking:**
466
+
467
+ The gateway automatically:
468
+ - Detects skill executions from instruction keys starting with `skills/`
469
+ - Connects to instruction metadata (key, version) from content resolution
470
+ - Routes to `skill-executions` collection (ActivityManager + Activix handle routing)
471
+ - Returns `activityId` in response metadata for linking child skills
472
+ - Supports parent-child skill relationships via `masterSkillActivityId` and `masterSkillId`
473
+
474
+ **For detailed integration guides, see:**
475
+ - [Skill Execution Client Integration Guide](./docs/SKILL_EXECUTION_CLIENT_INTEGRATION.md)
476
+ - [AI Gateway Integration Guide: Skill Requests](./docs/AI_GATEWAY_INTEGRATION_SKILL_REQUESTS.md)
477
+ - [Extending AI Activities with Skill Fields](./docs/EXTENDING_AI_ACTIVITIES_WITH_SKILL_FIELDS.md)
478
+
479
+ ### 3. Usage Tracking Configuration (x-models)
480
+
481
+ **Where to configure:**
482
+ - Gateway constructor: `enableUsageTracking`, `usageTier`
483
+ - JSON defaults: `src/defaults/model-config.json` (not used for usage tracking)
484
+
485
+ **How to configure:**
486
+
487
+ ```typescript
488
+ const gateway = new AIGateway({
489
+ enableUsageTracking: true, // Default: true
490
+ usageTier: 'tier-3' // RPM/TPM limits: 'tier-1' | 'tier-2' | 'tier-3'
491
+ });
492
+ ```
493
+
494
+ **Note:** If `@x12i/x-models` is not available or has export issues, usage tracking will gracefully degrade with warnings logged.
495
+
496
+ **Default:** Usage tracking is enabled by default with `usageTier: 'tier-3'`
497
+
498
+ ### 4. Default Model and Engine Configuration
499
+
500
+ **Where to configure:**
501
+ 1. **JSON Defaults** (lowest priority): `src/defaults/model-config.json`
502
+ 2. **Gateway Constructor** (medium priority): `defaultModel`, `defaultEngine`
503
+ 3. **Request Config** (highest priority): `request.config.model`, `request.config.provider`
504
+
505
+ **How to configure:**
506
+
507
+ **Step 1: Create/Edit JSON defaults** (`src/defaults/model-config.json`):
508
+ ```json
509
+ {
510
+ "defaultModel": "gpt-4o",
511
+ "defaultEngine": "openai",
512
+ "temperature": 0.7,
513
+ "maxTokens": 2000,
514
+ "topP": 1.0,
515
+ "frequencyPenalty": 0.0,
516
+ "presencePenalty": 0.0
517
+ }
518
+ ```
519
+
520
+ **Step 2: Gateway constructor:**
521
+ ```typescript
522
+ const gateway = new AIGateway({
523
+ defaultModel: 'gpt-5-nano', // Overrides JSON default
524
+ defaultEngine: 'openai', // Overrides JSON default
525
+ temperature: 0.9, // Overrides JSON default
526
+ maxTokens: 4000 // Overrides JSON default
527
+ });
528
+ ```
529
+
530
+ **Step 3: Request-level override:**
531
+ ```typescript
532
+ const response = await gateway.invoke({
533
+ jobId: 'job-123',
534
+ agentId: 'agent-456',
535
+ instructions: 'You are helpful',
536
+ input: 'Hello',
537
+ config: {
538
+ model: 'gpt-4o', // Overrides gateway default
539
+ provider: 'openai', // Overrides gateway default
540
+ temperature: 0.5 // Overrides gateway default
541
+ }
542
+ });
543
+ ```
544
+
545
+ **Priority Order:**
546
+ 1. Request config (highest)
547
+ 2. Gateway constructor config
548
+ 3. JSON defaults (lowest)
549
+
550
+ ### 5. InstructionsBlocks Configuration
551
+
552
+ **Where to configure:**
553
+ 1. **JSON Defaults** (lowest priority): `src/defaults/instructions-blocks.json`
554
+ 2. **Content resolver (nx-content)** (medium priority): blocks under e.g. `blocks/{blockName}/{agentId}` (see [Content Resolver — Upstream Guide](./CONTENT_RESOLVER_UPSTREAM_GUIDE.md))
555
+ 3. **Gateway Constructor** (highest priority): `instructionsBlocks` object
556
+
557
+ **How to configure:**
558
+
559
+ **Step 1: Create/Edit JSON defaults** (`src/defaults/instructions-blocks.json`):
560
+ ```json
561
+ {
562
+ "input-prefix": "Please process the following input:",
563
+ "default-prompt": "You are a helpful assistant."
564
+ }
565
+ ```
566
+
567
+ **Step 2: Gateway constructor:**
568
+ ```typescript
569
+ const gateway = new AIGateway({
570
+ instructionsBlocks: {
571
+ 'input-prefix': 'Custom prefix from config:', // Overrides JSON default
572
+ 'custom-block': 'Custom block content'
573
+ }
574
+ });
575
+ ```
576
+
577
+ **Step 3: Content Registry** (if available):
578
+ - Store blocks at paths supported by nx-content (e.g. `blocks/{blockName}/{agentId}` or with taskTypeId). When content resolver is configured (via `contentRegistryConfig` or env vars), instructions blocks are resolved from local or git.
579
+
580
+ **Priority Order** (highest to lowest):
581
+ 1. Gateway constructor `instructionsBlocks` (highest priority)
582
+ 2. Content registry with `taskTypeId` (if taskTypeId provided)
583
+ 3. Content registry without `taskTypeId`
584
+ 4. JSON defaults (lowest priority)
585
+
586
+ **See**: [Content Resolver — Upstream Guide](./CONTENT_RESOLVER_UPSTREAM_GUIDE.md) for configuration, env vars, key vs text rule, and checklist.
587
+
588
+ ### 6. Instruction Resolution (Content Resolver / nx-content)
589
+
590
+ **How it works:** The gateway uses **nx-content** to resolve content. Instruction type is determined by whitespace:
591
+
592
+ - **No spaces** → **Key** (resolved from local folder or git)
593
+ - **Has spaces** → **Literal text** (used as-is)
594
+
595
+ There is **no** option to override this; the spaces rule is the only decision point.
596
+
597
+ **Configuration:** Pass `contentRegistryConfig` (or legacy `contentRegistry`) when creating the gateway:
598
+
599
+ ```typescript
600
+ // Local content only
601
+ const gateway = new AIGateway({
602
+ contentRegistryConfig: {
603
+ localPath: '.metadata' // or absolute path
604
+ }
605
+ });
606
+
607
+ // Local + Git (mode: 'dev' = local wins, 'prod' = git wins)
608
+ const gateway = new AIGateway({
609
+ contentRegistryConfig: {
610
+ localPath: '.metadata',
611
+ mode: 'dev',
612
+ github: {
613
+ repo: process.env.GITHUB_REPO_URL,
614
+ token: process.env.GITHUB_TOKEN,
615
+ branch: 'main'
616
+ }
617
+ }
618
+ });
619
+ ```
620
+
621
+ **Environment variables** (used when no explicit config): `CONTENT_REGISTRY_LOCAL_ROOT`, `CONTENT_REGISTRY_MODE`, `GITHUB_REPO_URL`, `GITHUB_TOKEN`, `CONTENT_REGISTRY_GIT_BRANCH`.
622
+
623
+ **Behavior:**
624
+ - Keys (no spaces) → resolved from nx-content (local or git); **never** sent as message content
625
+ - Literal text (has spaces) → used as-is
626
+ - Unresolvable key → error; no LLM call
627
+
628
+ **File layout:** e.g. `skills/<name>.instructions.md`, `skills/<name>.prompt.md` under the content root. See [Content Resolver — Upstream Guide](./CONTENT_RESOLVER_UPSTREAM_GUIDE.md) for full layout, checklist, and diagnostics.
629
+
630
+ ### 7. Template Parsing Configuration (workingMemory)
631
+
632
+ **Where to configure:**
633
+ - Request-level: `workingMemory` object
634
+
635
+ **How to configure:**
636
+
637
+ ```typescript
638
+ const response = await gateway.invoke({
639
+ jobId: 'job-123',
640
+ agentId: 'agent-456',
641
+ // Instructions can be a key (resolved from content resolver) or text (parsed as template)
642
+ instructions: 'professional-answer.instructions', // Key with suffix
643
+ // OR: instructions: 'You are a {{role}} assistant.', // Text with template variables
644
+ context: 'User is working on {{project}} project.',
645
+ // Prompts can be a key (resolved from content resolver) or text (parsed as template)
646
+ prompt: 'professional-answer.prompt', // Key with suffix
647
+ // OR: prompt: 'Analyze this {{type}}: {{input}}', // Text with template variables
648
+ input: 'This is a review',
649
+ workingMemory: {
650
+ role: 'helpful',
651
+ project: 'AI Gateway',
652
+ type: 'product review',
653
+ input: 'This is a review'
654
+ }
655
+ });
656
+ ```
657
+
658
+ **What gets parsed:**
659
+ - `instructions` - Resolved from content resolver (nx-content) if it's a key (no spaces), or parsed as template if text
660
+ - `context` - Parsed as template with `workingMemory`
661
+ - `prompt` - Resolved from content resolver if it's a key (no spaces), or parsed as template if text
662
+ - All parsed using `@x12i/rendrix` (v4+) with `workingMemory`, `shortTermMemory`, `experienceMemory`, `knowledgeMemory`
663
+
664
+ **Rendrix (@x12i/rendrix) v4 template protocol**
665
+
666
+ - Simple placeholders `{{name}}` or `{{a.b.c}}` are **required** (MUST): if resolution is **`undefined`** after the usual memory merge, rendering throws **`TemplateResolutionError`** from the parser. The gateway **rethrows** that error (it is not converted into a silent fallback).
667
+ - Values that **do not** throw include **`null`**, empty string **`""`**, **`0`**, and **`false`**.
668
+ - **Optional** placeholders: `{{path |}}` (empty if missing) or `{{path | fallback text}}` (literal fallback when missing).
669
+ - Helpers, blocks, `{{file:...}}`, `{{json ...}}`, etc. follow the parser’s own rules; the MUST/optional rules above apply to plain path mustaches.
670
+ - For full parser API details, see **`@x12i/rendrix`** README / `CHANGELOG.md`.
671
+
672
+ **Gateway template options (passthrough)**
673
+
674
+ - **`GatewayConfig.templateRendering`** — default `TemplateRenderOptions` for every `invoke()` render path (merged after packaged **`src/defaults/template-rendering.json`**, which ships with **`subPathSearch.enabled: false`**). Your gateway config overrides that JSON.
675
+ - **`templateRenderOptions` on the request** (`ChatRequest` / `AIRequest`) — merged on top of the gateway default for that call only (per-field override; `subPathSearch` fields merge with request winning).
676
+ - Supported fields match the parser: **`templateId`**, **`subPathSearch`** (`enabled`, `roots`), **`silentMissingMustTokens`** (legacy Handlebars-style silence for missing MUST paths).
677
+ - **Sub-path root priority:** `subPathSearch.roots` is an **ordered** list. The parser tries roots in **array order**; **the first root that resolves the leaf path wins** (see ISSUE-005). There is no separate “priority” field—the order of `roots` *is* the priority. Omit `roots` when `enabled` is true to use **`@x12i/rendrix`** packaged defaults.
678
+
679
+ ```typescript
680
+ // Example: prefer execution.*, then input.*, then inputs.* when a full path misses
681
+ new AIGateway({
682
+ templateRendering: {
683
+ subPathSearch: {
684
+ enabled: true,
685
+ roots: ['execution', 'input', 'inputs']
686
+ }
687
+ }
688
+ });
689
+ ```
690
+ - **Memory overlay priority** for value resolution (highest first): **`templateTokens`** (merged into short-term before render) → **`shortTermMemory`** → **`workingMemory`** → **`experienceMemory`** → **`knowledgeMemory`**.
691
+ - Root **`config.defaults.json`** may include a **`templateRendering`** block for apps that merge this file into `GatewayConfig`. Packaged **`template-rendering.json`** includes a sample **`roots`** order (used when you turn **`enabled`** on; while **`enabled`** is **`false`**, roots are ignored by the parser).
692
+
693
+ **Template-Based Prompts:**
694
+ - Prompts work exactly like instructions - both can be resolved using explicit keys. See [Content Resolver — Upstream Guide](./CONTENT_RESOLVER_UPSTREAM_GUIDE.md).
695
+ - Both instructions and prompts receive the same memory context for template rendering
696
+ - Use explicit keys with suffixes: `professional-answer.instructions` and `professional-answer.prompt`
697
+ - See [Prompt Template Usage Guide](./docs/PROMPT_TEMPLATE_USAGE.md) for details
698
+
699
+ **Note:** Requires `@x12i/rendrix` **^4.x** (already a dependency).
700
+
701
+
702
+ ### 9. Provider Registration
703
+
704
+ **Where to configure:**
705
+ - **Automatic (Recommended)**: Environment variables - providers auto-register on gateway creation
706
+ - **Manual**: Runtime: `gateway.register(provider)`
707
+
708
+ **Automatic Registration (v4.0.7+):**
709
+
710
+ Providers are automatically registered based on environment variables when the gateway is created:
711
+
712
+ ```typescript
713
+ // Set environment variables in .env:
714
+ // OPENAI_API_KEY=sk-...
715
+ // GROK_API_KEY=xai-...
716
+ // ANTHROPIC_API_KEY=sk-ant-... (optional)
717
+ // GOOGLE_API_KEY=... (optional)
718
+
719
+ const gateway = new AIGateway({
720
+ defaultProvider: 'openai',
721
+ fallbackChain: ['grok']
722
+ });
723
+
724
+ // Providers are automatically registered! No manual registration needed.
725
+ // The gateway will log which providers were auto-registered.
726
+ ```
727
+
728
+ **📋 Configuration Reference:**
729
+
730
+ See `.env.example` in the project root for a comprehensive guide to all environment variables, including:
731
+ - Provider API keys (required/optional)
732
+ - MongoDB/activity tracking configuration
733
+ - Content registry setup (S3, GitHub, Redis)
734
+ - Internal system actions configuration
735
+ - Logging and debugging options
736
+
737
+ Copy `.env.example` to `.env` and fill in your values.
738
+
739
+ **Supported Providers (Auto-Registration):**
740
+
741
+ - **OpenAI**: `OPENAI_API_KEY` → Auto-registers `openai` provider
742
+ - **Grok**: `GROK_API_KEY` → Auto-registers `grok` provider
743
+ - **Anthropic**: `ANTHROPIC_API_KEY` → Auto-registers `anthropic` provider (if package installed)
744
+ - **Google**: `GOOGLE_API_KEY` → Auto-registers `google` provider (if package installed)
745
+ - **Cohere**: `COHERE_API_KEY` → Auto-registers `cohere` provider (if package installed)
746
+ - **Mistral**: `MISTRAL_API_KEY` → Auto-registers `mistral` provider (if package installed)
747
+
748
+ **Manual Registration (Optional):**
749
+
750
+ You can still manually register providers if needed:
751
+
752
+ ```typescript
753
+ import { OpenAIProvider } from '@x12i/ai-provider-openai';
754
+ import { GrokProvider } from '@x12i/ai-provider-grok';
755
+
756
+ const gateway = new AIGateway({
757
+ defaultProvider: 'openai',
758
+ fallbackChain: ['grok']
759
+ });
760
+
761
+ // Manual registration (optional - auto-registration handles this if env vars are set)
762
+ gateway.register(new OpenAIProvider({
763
+ apiKey: process.env.OPENAI_API_KEY
764
+ }));
765
+
766
+ gateway.register(new GrokProvider({
767
+ apiKey: process.env.GROK_API_KEY
768
+ }));
769
+ ```
770
+
771
+ **Note:** Auto-registration only registers providers that:
772
+ 1. Have their API key set in environment variables
773
+ 2. Have their provider package installed (e.g., `@x12i/ai-provider-openai`)
774
+
775
+ If a provider package is not installed, auto-registration will skip it gracefully (with a debug log for optional providers, warning for required ones).
776
+
777
+ ### 9. Complete Configuration Example
778
+
779
+ ```typescript
780
+ import { AIGateway } from '@x12i/ai-gateway';
781
+ import { createLogger } from 'logs-gateway';
782
+ import { Activix } from '@x12i/activix';
783
+ import { OpenAIProvider } from '@x12i/ai-provider-openai';
784
+
785
+ // 1. Configure activity tracker (and reuse its logger)
786
+ // Single source of truth: set up the logger once, pass it to the tracker,
787
+ // then reuse the same logger for the gateway.
788
+ const logger = createLogger(
789
+ { packageName: 'MY_APP', envPrefix: 'MY_APP' },
790
+ {
791
+ logLevel: 'info',
792
+ logFormat: 'json',
793
+ enableUnifiedLogger: true
794
+ }
795
+ );
796
+
797
+ const statusValues = {
798
+ started: 'started',
799
+ inProgress: 'in_progress',
800
+ completed: 'success',
801
+ failed: 'failed',
802
+ timeout: 'timeout'
803
+ };
804
+
805
+ const activityTracker = new Activix({
806
+ collections: [
807
+ { name: 'ai-activities', statusValues },
808
+ { name: 'skill-executions', statusValues },
809
+ { name: 'bad-requests', statusValues }
810
+ ]
811
+ });
812
+
813
+ // 2. Create gateway with all configurations
814
+ const gateway = new AIGateway({
815
+ // Provider routing
816
+ defaultProvider: 'openai',
817
+ defaultModel: 'gpt-4o',
818
+ defaultEngine: 'openai',
819
+ fallbackChain: ['grok'],
820
+
821
+ // Usage tracking
822
+ enableUsageTracking: true,
823
+ usageTier: 'tier-3',
824
+
825
+ // Activity tracking
826
+ enableActivityTracking: true,
827
+ activityTracker: activityTracker,
828
+
829
+ // Logging
830
+ enableLogging: true,
831
+ packageName: 'MY_APP',
832
+ logger: logger,
833
+
834
+ // Content resolver (local and/or git)
835
+ contentRegistryConfig: {
836
+ localPath: '.metadata',
837
+ // optional: mode: 'prod', github: { repo: process.env.GITHUB_REPO_URL, token: process.env.GITHUB_TOKEN }
838
+ },
839
+
840
+ // InstructionsBlocks
841
+ instructionsBlocks: {
842
+ 'input-prefix': 'Custom prefix:'
843
+ },
844
+
845
+ // LLM defaults
846
+ temperature: 0.7,
847
+ maxTokens: 2000
848
+ });
849
+
850
+ // 4. Register providers
851
+ gateway.register(new OpenAIProvider({
852
+ apiKey: process.env.OPENAI_API_KEY
853
+ }));
854
+ ```
855
+
856
+ ### Configuration Priority Summary
857
+
858
+ For each configuration option, priority is (highest to lowest):
859
+ 1. **Request-level config** (in `invoke()` call)
860
+ 2. **Gateway constructor config**
861
+ 3. **Content resolver (nx-content)** (for instructionsBlocks only)
862
+ 4. **JSON defaults** (from `src/defaults/`)
863
+
864
+ ## Enhanced Gateway Features
865
+
866
+ ### 1. Context Propagation (Job ID)
867
+
868
+ The gateway automatically propagates `jobId` through the entire request lifecycle for distributed tracing.
869
+
870
+ ```typescript
871
+ const response = await gateway.invoke({
872
+ // Minimum required fields
873
+ jobId: 'job-123', // required
874
+ agentId: 'agent-456', // required
875
+ instructions: 'You are a helpful assistant.', // required
876
+
877
+ // Provide either messages OR prompt/input
878
+ messages: [{ role: 'user', content: 'What is AI?' }],
879
+ // OR:
880
+ // prompt: 'professional-answer.prompt', // Key resolved from content resolver (nx-content)
881
+ // input: 'What is AI?',
882
+ // Optional extra context inserted between instructions and user prompt/input
883
+ // context: 'Only answer with a single sentence.',
884
+
885
+ // Optional
886
+ taskId: 'task-789',
887
+ taskTypeId: 'question-answering', // Or auto-generated from instructions MD5 hash
888
+ graphId: 'graph-123', // Optional: Graph execution context
889
+ nodeId: 'node-456', // Optional: Node execution context
890
+ config: { model: 'gpt-5-nono' }
891
+ });
892
+
893
+ // jobId is automatically:
894
+ // - Attached to request metadata
895
+ // - Included in response metadata
896
+ // - Logged in all log entries
897
+ // - Tracked in activity logs
898
+ ```
899
+
900
+ **Request requirements:**
901
+ - **Required fields**: `jobId`, `agentId`, and `instructions` are mandatory
902
+ - **Content field (choose one)**:
903
+ - `messages` array (for tool calling or custom message sequences)
904
+ - OR `prompt` + `input` (prompt template resolved from content resolver or parsed with template variables)
905
+ - OR `input` alone (uses default prefix from instructionsBlocks)
906
+ - **Optional fields**: `context` (inserted as system message between instructions and user content), `workingMemory` (for template variables), `graphId`/`nodeId`/`coreSkillId` (for graph execution context - see [Graph Execution Support](./docs/GRAPH_EXECUTION_SUPPORT.md))
907
+ - **Model requirement**: `config.model` must be supplied per request (no default model)
908
+
909
+ **Important Notes:**
910
+ - `instructions` is **always required**, even when using `messages` array
911
+ - When `messages` is provided, the gateway constructs system messages from `instructions` (and `context` if provided), then appends your `messages` array
912
+ - `context` is inserted as a separate system message between instructions and the user prompt/input
913
+ - **Template-Based Instructions and Prompts**: Both `instructions` and `prompt` can be resolved from the content resolver (nx-content) using keys (e.g., `professional-answer.instructions` and `professional-answer.prompt`). Both receive the same memory context for template rendering. See [Content Resolver — Upstream Guide](./CONTENT_RESOLVER_UPSTREAM_GUIDE.md) and [Prompt Template Usage Guide](./docs/PROMPT_TEMPLATE_USAGE.md).
914
+ - All template fields (`instructions`, `context`, `prompt`) support template variables via `workingMemory`
915
+
916
+ **Benefits:**
917
+ - Full request traceability across distributed systems
918
+ - Correlate logs, activities, and metrics by jobId
919
+ - Debug complex multi-step AI workflows
920
+
921
+ ### 2. Usage Tier Tracking (RPM/TPM Limits)
922
+
923
+ The gateway integrates with `@x12i/x-models` to enforce usage tier limits and prevent rate limit errors.
924
+
925
+ ```typescript
926
+ import { AIGateway, getTierInfo } from '@x12i/ai-gateway';
927
+
928
+ // Initialize with usage tier
929
+ const gateway = new AIGateway({
930
+ usageTier: 'tier-3', // 5,000 RPM, 2M TPM
931
+ enableUsageTracking: true
932
+ });
933
+
934
+ // Get tier information
935
+ const tierInfo = getTierInfo('tier-3');
936
+ console.log(`RPM Limit: ${tierInfo?.rpm}, TPM Limit: ${tierInfo?.tpm}`);
937
+
938
+ // Gateway automatically:
939
+ // - Records every request to x-models
940
+ // - Calculates RPM/TPM consumption
941
+ // - Logs consumption percentages
942
+ // - Prevents exceeding tier limits
943
+ ```
944
+
945
+ **Available Tiers:**
946
+ - `tier-1`: 500 RPM, 500K TPM
947
+ - `tier-2`: 5,000 RPM, 1M TPM
948
+ - `tier-3`: 5,000 RPM, 2M TPM (default)
949
+ - `tier-4`: 10,000 RPM, 4M TPM
950
+ - `tier-5`: 15,000 RPM, 40M TPM
951
+
952
+ ### 3. Activity Tracking (xronox-activitix via @x12i/activix v5)
953
+
954
+ The gateway uses **`@x12i/activix` v5** (xronox-activitix) for full lifecycle logging. Recommended: enable MongoDB persistence so tracking is automatic. Default collections: **`ai-activities`**, **`bad-requests`**, **`skill-executions`** (see section 2).
955
+
956
+ #### ⚠️ CRITICAL: correlation, identity, and unique record ids
957
+
958
+ **IMPORTANT DESIGN CONCEPTS:**
959
+
960
+ 1. **Per-request correlation**
961
+ - **`aiRequestId`** (required): One id per gateway invocation; used as the primary leaf correlation field (stored on the activity row and inside Activix `runContext`; the gateway does not generate a `jobId` for you).
962
+ - **`jobTypeId`**, **`taskId`**, **`taskTypeId`**: Optional aggregation fields (same ideas as before).
963
+ - **Activity**: Each individual LLM request is a separate **activity** with its own unique record.
964
+
965
+ 2. **Mongo `_id` is the unique row key**
966
+ - Optional **`jobId`** (if you pass it) is only grouping metadata — multiple activities may share it.
967
+ - Activix updates rows by the **record id** from the start phase, not by `jobId`.
968
+
969
+ 3. **Two-phase tracking (Activix v5)**
970
+ - **Phase 1 (start)**: Creates a NEW database record with unique `_id`
971
+ - Sends request-side data: `request`, `config`, `runContext`, `structure`, `startTime`, `status: 'started'` (plus other gateway metadata)
972
+ - Returns metadata containing the unique record id for completion
973
+ - **Phase 2 (complete / fail)**: Updates the SAME record by that id
974
+ - Sends response/error data: `response`, `endTime`, `duration`, `cost`, `status`
975
+ - Does not re-send full request payload
976
+
977
+ 4. **Data structure (v2.6.0+):**
978
+ - Request fields (`messages`, `instructions`, `prompt`, `input`, `context`, `workingMemory`) → **ONLY in `request` object**
979
+ - Config fields (`model`, `provider`, `temperature`, `maxTokens`) → **ONLY in `config` object**
980
+ - Response fields (`content`, `metadata`) → **ONLY in `response` object**
981
+ - **NO duplication**: Fields are NOT at root level, only in their structured objects
982
+
983
+ **Example: same logical job, three LLM calls**
984
+
985
+ Each call must have a **distinct `aiRequestId`**. Optionally pass the same `jobId` (or only `jobTypeId`) if you want to group rows in Mongo.
986
+
987
+ ```typescript
988
+ import * as crypto from 'crypto';
989
+
990
+ function md5(text: string): string {
991
+ return crypto.createHash('md5').update(text).digest('hex');
992
+ }
993
+
994
+ const jobTypeId = md5('data-processing-job');
995
+
996
+ await gateway.invoke({
997
+ aiRequestId: 'req-001',
998
+ jobId: 'job-123', // optional grouping
999
+ jobTypeId,
1000
+ agentId: 'agent-1',
1001
+ sessionId: 'sess-1',
1002
+ instance: { instanceId: 'inst-1', type: 'gateway' },
1003
+ // ...
1004
+ });
1005
+
1006
+ await gateway.invoke({
1007
+ aiRequestId: 'req-002',
1008
+ jobId: 'job-123',
1009
+ jobTypeId,
1010
+ agentId: 'agent-1',
1011
+ sessionId: 'sess-1',
1012
+ instance: { instanceId: 'inst-1', type: 'gateway' },
1013
+ // ...
1014
+ });
1015
+
1016
+ // Query in Mongo (main collection name is ai-activities):
1017
+ // db.getCollection('ai-activities').find({ 'runContext.aiRequestId': 'req-001' })
1018
+ // db.getCollection('ai-activities').find({ 'runContext.jobId': 'job-123' }) // if you set jobId
1019
+ ```
1020
+
1021
+ #### Configuration
1022
+
1023
+ ```typescript
1024
+ import { Activix } from '@x12i/activix';
1025
+
1026
+ const statusValues = {
1027
+ started: 'started',
1028
+ inProgress: 'in_progress',
1029
+ completed: 'success',
1030
+ failed: 'failed',
1031
+ timeout: 'timeout'
1032
+ };
1033
+
1034
+ const activityTracker = new Activix({
1035
+ collections: [
1036
+ { name: 'ai-activities', statusValues },
1037
+ { name: 'skill-executions', statusValues },
1038
+ { name: 'bad-requests', statusValues }
1039
+ ]
1040
+ });
1041
+
1042
+ const gateway = new AIGateway({
1043
+ enableActivityTracking: true,
1044
+ activityTracker
1045
+ });
1046
+
1047
+ // Auto-persisted by the tracker:
1048
+ // - Each activity creates a new record with unique _id
1049
+ // - Start/end/duration, status (started|success|failed)
1050
+ // - Provider, model, cost
1051
+ // - Request/response metadata, errors
1052
+ // - Correlation via runContext (and mirrored top-level fields); optional jobId for grouping
1053
+ ```
1054
+
1055
+ #### Database Record Structure
1056
+
1057
+ ```typescript
1058
+ {
1059
+ // Unique identifier (MongoDB auto-generated)
1060
+ _id: ObjectId('693970636e8d0f171e4aa528'), // ← UNIQUE per activity
1061
+
1062
+ // Activix v5: canonical correlation BSON object `runContext` (gateway builds it from `request.identity`)
1063
+ runContext: {
1064
+ sessionId: 'sess-1',
1065
+ instance: { instanceId: 'gw-1', type: 'gateway' },
1066
+ aiRequestId: 'req-abc',
1067
+ jobId: 'job-123',
1068
+ jobTypeId: 'xyz789...',
1069
+ agentId: 'agent-456',
1070
+ taskId: 'task-789',
1071
+ taskTypeId: 'abc123...',
1072
+ graphId: 'graph-456',
1073
+ nodeId: 'node-789',
1074
+ masterSkillId: '...',
1075
+ masterSkillActivityId: '...'
1076
+ },
1077
+ // Mirrored / denormalized top-level fields may also appear from the gateway payload (query either as needed)
1078
+ aiRequestId: 'req-abc',
1079
+ sessionId: 'sess-1',
1080
+ instance: { instanceId: 'gw-1', type: 'gateway' },
1081
+ jobId: 'job-123',
1082
+ jobTypeId: 'xyz789...',
1083
+ agentId: 'agent-456',
1084
+ taskId: 'task-789',
1085
+ taskTypeId: 'abc123...',
1086
+ graphId: 'graph-456',
1087
+ nodeId: 'node-789',
1088
+
1089
+ // Required activity I/O envelope (`structure` required since Activix v4; unchanged in v5 — see @x12i/activix docs)
1090
+ structure: {
1091
+ outer: { input: { ... }, output: { ... } | null, metadata: { ... } },
1092
+ // inner?: { input, output, metadata }
1093
+ },
1094
+
1095
+ // Timing
1096
+ startTime: 1765372020804,
1097
+ endTime: 1765372021535, // Added by logSuccess
1098
+ duration: 731, // Added by logSuccess
1099
+ status: 'success', // Updated by logSuccess (was 'started')
1100
+
1101
+ // Request data (from startActivity - ONLY in request object)
1102
+ request: {
1103
+ raw: {
1104
+ instructions, // Original instructions (before template parsing)
1105
+ context, // Original context (before template parsing)
1106
+ prompt // Original prompt (before template parsing)
1107
+ },
1108
+ parsed: {
1109
+ instructions, // Parsed instructions (after template parsing with workingMemory)
1110
+ context, // Parsed context (after template parsing with workingMemory)
1111
+ prompt // Parsed prompt (after template parsing with workingMemory, includes input if provided)
1112
+ },
1113
+ input: "...", // Original input text
1114
+ messages: [...], // Final constructed messages array
1115
+ workingMemory: {...} // Working memory used for template parsing
1116
+ },
1117
+
1118
+ // Config data (from startActivity - ONLY in config object)
1119
+ config: {
1120
+ model: 'gpt-5-',
1121
+ provider: 'openai',
1122
+ temperature: 0.7,
1123
+ maxTokens: 1000,
1124
+ rawConfig: {...}
1125
+ },
1126
+
1127
+ // Response data (from logSuccess - ONLY in response object)
1128
+ response: {
1129
+ content: "...",
1130
+ metadata: {...}
1131
+ },
1132
+
1133
+ // Cost (from logSuccess)
1134
+ cost: 0.002,
1135
+
1136
+ // Metadata
1137
+ createdAt: Date,
1138
+ updatedAt: Date
1139
+ }
1140
+ ```
1141
+
1142
+ **Key points:**
1143
+ - ✅ Each activity = separate record with unique `_id`
1144
+ - ✅ **`aiRequestId`** = per-request correlation (required on invoke)
1145
+ - ✅ Optional `jobId` = grouping metadata only
1146
+ - ✅ Request data sent once at activity start; response data on completion
1147
+ - ✅ Updates use Activix record id / `_id`, not `jobId`
1148
+
1149
+ #### Retry Tracking (@x12i/activix v5)
1150
+
1151
+ The gateway automatically retries network errors, server errors (5xx), and throttling (429) with exponential backoff. Retry attempts are tracked and stored in activity records.
1152
+
1153
+ **Retry Metadata Structure:**
1154
+
1155
+ ```typescript
1156
+ // Success case - retry metadata in response.metadata.retries
1157
+ {
1158
+ response: {
1159
+ metadata: {
1160
+ retries: {
1161
+ count: 2, // Number of retry attempts
1162
+ attempts: [
1163
+ {
1164
+ attempt: 1, // 1-based attempt number
1165
+ timestamp: 1234567890, // When retry occurred
1166
+ error: "fetch failed", // Error message
1167
+ errorType: "network", // Error classification
1168
+ delayMs: 1000 // Delay before retry
1169
+ },
1170
+ {
1171
+ attempt: 2,
1172
+ timestamp: 1234568890,
1173
+ error: "fetch failed",
1174
+ errorType: "network",
1175
+ delayMs: 2000
1176
+ }
1177
+ ]
1178
+ }
1179
+ }
1180
+ }
1181
+ }
1182
+
1183
+ // Failure case - retry count in error message
1184
+ {
1185
+ status: "failed",
1186
+ error: "Grok API network error: fetch failed [Retries: 3]"
1187
+ }
1188
+ ```
1189
+
1190
+ **Error Types:**
1191
+ - `network`: Network errors (fetch failed, DNS, connectivity)
1192
+ - `http-429`: Throttling/rate limiting
1193
+ - `http-5xx`: Server errors (500, 502, 503, etc.)
1194
+ - `timeout`: Timeout errors
1195
+
1196
+ **Querying Activities with Retries:**
1197
+
1198
+ ```typescript
1199
+ // Query activities that had retries
1200
+ const activitiesWithRetries = await db.activities.find({
1201
+ 'response.metadata.retries.count': { $gt: 0 }
1202
+ });
1203
+
1204
+ // Query activities with network errors that were retried
1205
+ const networkRetries = await db.activities.find({
1206
+ 'response.metadata.retries.attempts.errorType': 'network'
1207
+ });
1208
+
1209
+ // Query activities that failed after retries
1210
+ const failedAfterRetries = await db.activities.find({
1211
+ status: 'failed',
1212
+ error: /\[Retries: \d+\]/
1213
+ });
1214
+ ```
1215
+
1216
+ **Requirements:**
1217
+ - `@x12i/activix` required for retry tracking metadata persistence
1218
+ - Backward compatible: Works with older versions (retry metadata just won't be stored)
1219
+
1220
+ ### 4. Response Structure (v2.1.0+)
1221
+
1222
+ The gateway returns a comprehensive response structure that captures the full lifecycle: raw provider response, gateway normalization, inference parsing, and calculated metrics.
1223
+
1224
+ #### Complete Response Structure
1225
+
1226
+ ```typescript
1227
+ const response = await gateway.invoke({
1228
+ jobId: 'job-123',
1229
+ agentId: 'agent-456',
1230
+ instructions: 'You are a helpful assistant.',
1231
+ input: 'What is AI?',
1232
+ config: { model: 'gpt-5-nano' }
1233
+ });
1234
+
1235
+ // Response structure:
1236
+ {
1237
+ // ============================================
1238
+ // Raw Provider Response (from router)
1239
+ // ============================================
1240
+ content: string, // Normalized string (always present)
1241
+ rawText?: string, // Original raw text from provider (before parsing)
1242
+
1243
+ // Raw content from provider (if preserved)
1244
+ // Note: response.content is normalized, rawContent would be in routerResponse
1245
+
1246
+ // ============================================
1247
+ // Gateway Normalization & Parsing
1248
+ // ============================================
1249
+ parsedContent?: TContent, // Parsed JSON object/array (if content was JSON)
1250
+
1251
+ metadata: {
1252
+ // Content type classification
1253
+ contentType?: 'string' | 'object' | 'array' | 'null',
1254
+
1255
+ // ============================================
1256
+ // Gateway Calculated Metrics
1257
+ // ============================================
1258
+ jobId?: string, // Job ID for correlation
1259
+ latencyMs: number, // Execution time in milliseconds
1260
+ tokens: {
1261
+ prompt: number, // Input tokens
1262
+ completion: number, // Output tokens
1263
+ total: number, // Total tokens
1264
+ // Cache token support (if available)
1265
+ cacheInputTokens?: number,
1266
+ cacheOutputTokens?: number,
1267
+ cacheTotalTokens?: number
1268
+ },
1269
+ model?: string, // Model ID used (e.g., 'gpt-4o', 'claude-sonnet-4')
1270
+ provider?: string, // Provider used (e.g., 'openai', 'anthropic')
1271
+ cost?: number, // Cost in USD (if available)
1272
+
1273
+ // ============================================
1274
+ // Inference Output Parsing (if inferenceType provided)
1275
+ // ============================================
1276
+ parsedOutput?: unknown, // Typed inference output (classification, Q&A, etc.)
1277
+ inferenceType?: string, // Inference type used (e.g., 'classification')
1278
+ outputValidationErrors?: string[], // Schema validation errors (if validation enabled)
1279
+
1280
+ // ============================================
1281
+ // Provider Metadata (from router)
1282
+ // ============================================
1283
+ // Additional metadata from provider response
1284
+ // (merged from routerResponse.metadata)
1285
+ },
1286
+
1287
+ // ============================================
1288
+ // Usage Information (from router)
1289
+ // ============================================
1290
+ usage?: {
1291
+ cost?: number, // Cost from provider
1292
+ // Additional usage fields from provider
1293
+ }
1294
+ }
1295
+ ```
1296
+
1297
+ #### Response Structure Breakdown
1298
+
1299
+ **1. Raw Provider Response:**
1300
+ - `content` - Normalized string (always present, never "[object Object]")
1301
+ - `rawText` - Original raw text from provider (preserved if available)
1302
+ - `usage` - Usage information from provider (cost, tokens if available)
1303
+ - Provider metadata merged into `response.metadata`
1304
+
1305
+ **2. Gateway Normalization & Parsing:**
1306
+ - `parsedContent` - Parsed JSON object/array (if content was JSON)
1307
+ - `metadata.contentType` - Type classification: `'string' | 'object' | 'array' | 'null'`
1308
+
1309
+ **3. Inference Output Parsing** (if `inferenceType` provided in request):
1310
+ - `metadata.parsedOutput` - Typed inference output (classification, question-answer, extraction, etc.)
1311
+ - `metadata.inferenceType` - Inference type used
1312
+ - `metadata.outputValidationErrors` - Schema validation errors (if validation enabled)
1313
+
1314
+ **4. Gateway Calculated Metrics:**
1315
+ - `metadata.jobId` - Job ID for correlation
1316
+ - `metadata.latencyMs` - Request duration in milliseconds
1317
+ - `metadata.tokens` - Token breakdown (prompt, completion, total, cache tokens)
1318
+ - `metadata.cost` - Cost in USD
1319
+ - `metadata.model` - Model ID used
1320
+ - `metadata.provider` - Provider used
1321
+
1322
+ #### Example: Full Response
1323
+
1324
+ ```typescript
1325
+ const response = await gateway.invoke({
1326
+ jobId: 'job-123',
1327
+ agentId: 'agent-456',
1328
+ instructions: 'Classify sentiment',
1329
+ input: 'I love this product!',
1330
+ inferenceType: 'classification',
1331
+ parseOptions: { classes: ['positive', 'negative', 'neutral'] },
1332
+ config: { model: 'gpt-5-nano' }
1333
+ });
1334
+
1335
+ // Complete response structure:
1336
+ {
1337
+ // Normalized content (always string)
1338
+ content: '{"label":"positive","confidence":0.95}',
1339
+
1340
+ // Raw text from provider
1341
+ rawText: '{"label":"positive","confidence":0.95}',
1342
+
1343
+ // Parsed JSON (if content was JSON)
1344
+ parsedContent: { label: 'positive', confidence: 0.95 },
1345
+
1346
+ metadata: {
1347
+ // Content classification
1348
+ contentType: 'object',
1349
+
1350
+ // Gateway metrics
1351
+ jobId: 'job-123',
1352
+ latencyMs: 1250,
1353
+ tokens: {
1354
+ prompt: 100,
1355
+ completion: 50,
1356
+ total: 150
1357
+ },
1358
+ model: 'gpt-5-mini',
1359
+ provider: 'openai',
1360
+ cost: 0.002,
1361
+
1362
+ // Inference output (parsed)
1363
+ parsedOutput: {
1364
+ label: 'positive',
1365
+ confidence: 0.95
1366
+ },
1367
+ inferenceType: 'classification',
1368
+ outputValidationErrors: undefined // No validation errors
1369
+ }
1370
+ }
1371
+ ```
1372
+
1373
+ **Note:** The response structure captures the full lifecycle from raw provider response through gateway normalization to final parsed inference output, providing complete observability and traceability.
1374
+
1375
+ ### 5. Structured Logging
1376
+
1377
+ The gateway uses `logs-gateway` for production-ready structured logging with correlation support.
1378
+
1379
+ ```typescript
1380
+ const gateway = new AIGateway({
1381
+ enableLogging: true,
1382
+ packageName: 'MY_APP',
1383
+ logger: createLogger(
1384
+ { packageName: 'MY_APP', envPrefix: 'MY_APP' },
1385
+ {
1386
+ logLevel: 'info',
1387
+ logFormat: 'json',
1388
+ enableUnifiedLogger: true
1389
+ }
1390
+ )
1391
+ });
1392
+
1393
+ // All operations are automatically logged:
1394
+ // - Request initiation with jobId
1395
+ // - Provider/model selection
1396
+ // - Usage consumption
1397
+ // - Success/failure with full context
1398
+ ```
1399
+
1400
+ ### 6. Object Type Output Support (@x12i/outputs-library)
1401
+
1402
+ The gateway integrates with `@x12i/outputs-library` to parse LLM responses into typed inference outputs (classification, question-answer, extraction, etc.).
1403
+
1404
+ #### Overview
1405
+
1406
+ When you specify an `inferenceType` in your request, the gateway automatically:
1407
+ 1. Parses the response into a typed output object
1408
+ 2. Validates against JSON Schema (optional)
1409
+ 3. Provides type-safe access to structured data
1410
+
1411
+ #### Installation
1412
+
1413
+ ```bash
1414
+ npm install @x12i/outputs-library
1415
+ ```
1416
+
1417
+ **Note**: `@x12i/outputs-library` is automatically installed as a dependency.
1418
+
1419
+ **Dependency Resolution**: The gateway includes npm overrides to resolve version conflicts between the outputs library and content-registry. The integration uses dynamic imports for graceful degradation - if the outputs library is not available, the gateway will continue to work (parsing will be skipped with a warning).
1420
+
1421
+ **Installation**: The package.json includes overrides to handle version conflicts automatically. If you still encounter issues:
1422
+
1423
+ ```bash
1424
+ npm install --legacy-peer-deps
1425
+ ```
1426
+
1427
+ **Note for Package Maintainers**: The `@x12i/outputs-library` package should update its peer dependency from `@xronoces/content-registry@^1.0.0` to `@xronoces/content-registry@>=1.0.0` or `^1.0.0 || >=2.7.0` to support both versions. See `DEPENDENCY_RESOLUTION.md` for details.
1428
+
1429
+ #### Supported Inference Types
1430
+
1431
+ - `classification` - Classify content into predefined categories
1432
+ - `question-answer` - Answer questions based on context
1433
+ - `extraction` - Extract structured data from unstructured text
1434
+ - `summarization` - Generate summaries of content
1435
+ - `risk-assessment` - Assess risks with scores and factors
1436
+ - `recommendation` - Generate recommendations with priorities
1437
+ - `transformation` - Transform data between formats
1438
+
1439
+ #### Basic Usage
1440
+
1441
+ ```typescript
1442
+ import { AIGateway } from '@x12i/ai-gateway';
1443
+ import type { ClassificationOutput } from '@x12i/ai-gateway';
1444
+
1445
+ const gateway = new AIGateway({
1446
+ defaultProvider: 'openai'
1447
+ });
1448
+
1449
+ // Request with inference type
1450
+ const response = await gateway.invoke({
1451
+ jobId: 'job-123',
1452
+ agentId: 'agent-456',
1453
+ instructions: 'Classify the sentiment of the text.',
1454
+ input: 'This product is amazing!',
1455
+ inferenceType: 'classification',
1456
+ parseOptions: {
1457
+ classes: ['positive', 'negative', 'neutral']
1458
+ }
1459
+ });
1460
+
1461
+ // Access parsed output
1462
+ const classification = response.metadata.parsedOutput as ClassificationOutput;
1463
+ console.log(classification.classes); // ['positive', 'negative', 'neutral']
1464
+ console.log(classification.confidence); // { positive: 0.85, negative: 0.1, neutral: 0.05 }
1465
+ ```
1466
+
1467
+ #### With Schema Validation
1468
+
1469
+ ```typescript
1470
+ const response = await gateway.invoke({
1471
+ jobId: 'job-123',
1472
+ agentId: 'agent-456',
1473
+ instructions: 'Extract user information.',
1474
+ input: 'Name: John Doe, Email: john@example.com',
1475
+ inferenceType: 'extraction',
1476
+ validateOutputSchema: true // Enable validation
1477
+ });
1478
+
1479
+ // Check validation errors (if any)
1480
+ if (response.metadata.outputValidationErrors) {
1481
+ console.warn('Validation errors:', response.metadata.outputValidationErrors);
1482
+ }
1483
+
1484
+ // Access parsed output
1485
+ const extraction = response.metadata.parsedOutput as ExtractionOutput;
1486
+ console.log(extraction.extracted); // { name: 'John Doe', email: 'john@example.com' }
1487
+ ```
1488
+
1489
+ #### Question-Answer Example
1490
+
1491
+ ```typescript
1492
+ const response = await gateway.invoke({
1493
+ jobId: 'job-123',
1494
+ agentId: 'agent-456',
1495
+ instructions: 'Answer the question based on the context.',
1496
+ input: 'What is the capital of France?',
1497
+ inferenceType: 'question-answer',
1498
+ parseOptions: {
1499
+ question: 'What is the capital of France?'
1500
+ }
1501
+ });
1502
+
1503
+ const qa = response.metadata.parsedOutput as QuestionAnswerOutput;
1504
+ console.log(qa.answer); // 'The capital of France is Paris.'
1505
+ console.log(qa.confidence); // 0.95
1506
+ ```
1507
+
1508
+ #### Extraction Example
1509
+
1510
+ ```typescript
1511
+ const response = await gateway.invoke({
1512
+ jobId: 'job-123',
1513
+ agentId: 'agent-456',
1514
+ instructions: 'Extract structured data from the text.',
1515
+ input: 'User: John Doe, Age: 30, Location: New York',
1516
+ inferenceType: 'extraction'
1517
+ });
1518
+
1519
+ const extraction = response.metadata.parsedOutput as ExtractionOutput;
1520
+ console.log(extraction.extracted); // { user: 'John Doe', age: 30, location: 'New York' }
1521
+ ```
1522
+
1523
+ #### Response Structure
1524
+
1525
+ When `inferenceType` is provided, the response includes:
1526
+
1527
+ ```typescript
1528
+ {
1529
+ content: string; // Normalized content (ALWAYS a string - JSON objects are stringified)
1530
+ rawText?: string; // Original raw text (always a string when present)
1531
+ parsedContent?: object | array; // Parsed JSON content (ALWAYS object/array when JSON, forced structure)
1532
+ metadata: {
1533
+ // ... standard metadata ...
1534
+ parsedOutput?: unknown; // Typed inference output (ALWAYS present with consistent structure when inferenceType provided)
1535
+ inferenceType?: string; // The inference type used
1536
+ outputValidationErrors?: string[]; // Validation errors (if enabled)
1537
+ outputValidationPassed?: boolean; // Whether validation passed (v1.7.0+)
1538
+ outputSchema?: Record<string, unknown>; // Schema used for validation (v1.7.0+)
1539
+ outputsLibraryAvailable?: boolean; // Whether outputs library was used (v1.7.0+)
1540
+ parsingMethod?: 'outputs-library' | 'json-parse' | 'raw'; // Parsing method used (v1.7.0+)
1541
+ isFallback?: boolean; // Whether fallback structure was used (v1.7.4+)
1542
+ outputAudit?: { // Structure audit results (v2.1.1+)
1543
+ hasAllRequiredFields: boolean;
1544
+ missingRequiredFields?: string[];
1545
+ extraFields?: string[];
1546
+ matchingFields?: string[];
1547
+ responseFieldCount: number;
1548
+ schemaFieldCount: number;
1549
+ structureMatches: boolean;
1550
+ };
1551
+ }
1552
+ }
1553
+ ```
1554
+
1555
+ **Guaranteed Structure Consistency (v1.7.4+):**
1556
+
1557
+ The gateway ensures structures are always consistent:
1558
+
1559
+ - **`content`**: Always a string (objects/arrays are JSON.stringified)
1560
+ - **`parsedContent`**: Always an object or array when content is JSON (forced structure)
1561
+ - **`parsedOutput`**: Always has type-specific structure when `inferenceType` is provided (never undefined)
1562
+
1563
+ **Forced Structure Examples:**
1564
+
1565
+ ```typescript
1566
+ // Invalid JSON string → Wrapped in object
1567
+ // Input: "not valid json"
1568
+ // parsedContent: { text: "not valid json", _wrapped: true }
1569
+
1570
+ // Plain text → Wrapped in object if expecting JSON
1571
+ // Input: "Hello world"
1572
+ // parsedContent: { text: "Hello world", _wrapped: true }
1573
+
1574
+ // Object → Always preserved as object
1575
+ // Input: { key: "value" }
1576
+ // parsedContent: { key: "value" }
1577
+
1578
+ // Array → Always preserved as array
1579
+ // Input: [1, 2, 3]
1580
+ // parsedContent: [1, 2, 3]
1581
+ ```
1582
+
1583
+ #### Using Outputs Library Directly
1584
+
1585
+ You can also use the outputs library utilities directly:
1586
+
1587
+ ```typescript
1588
+ import {
1589
+ ResponseParser,
1590
+ SchemaValidator,
1591
+ SchemaRegistry,
1592
+ type ClassificationOutput
1593
+ } from '@x12i/ai-gateway';
1594
+
1595
+ // Parse manually
1596
+ const output = ResponseParser.parse<ClassificationOutput>(
1597
+ rawText,
1598
+ parsedContent,
1599
+ contentType,
1600
+ 'classification',
1601
+ { classes: ['positive', 'negative'] }
1602
+ );
1603
+
1604
+ // Validate against schema
1605
+ const schema = SchemaRegistry.getSchema('classification');
1606
+ const validator = new SchemaValidator();
1607
+ if (!validator.validate(output, schema)) {
1608
+ console.error('Validation errors:', validator.getErrors());
1609
+ }
1610
+ ```
1611
+
1612
+ #### Automatic Output Schema Guidance (v2.1.1+)
1613
+
1614
+ When you want to get a JSON response that conforms to a specific schema, you must provide the output object schema. The gateway automatically extends instructions with clear expectations about the JSON structure.
1615
+
1616
+ **How It Works:**
1617
+
1618
+ 1. **Provide `inferenceType`** in your request, OR
1619
+ 2. **Use an instruction key** that has `outputSchema` in its metadata
1620
+
1621
+ The gateway will:
1622
+ - Automatically fetch the schema from instruction metadata (if using content-registry)
1623
+ - Resolve `outputObjectPrefix` from `instructions-blocks.json`
1624
+ - Append detailed schema guidance to instructions, including:
1625
+ - Field descriptions and types
1626
+ - Required vs optional fields
1627
+ - Full JSON schema in a code block
1628
+
1629
+ **Example:**
1630
+
1631
+ ```typescript
1632
+ // Instruction metadata has:
1633
+ // {
1634
+ // instructionKey: 'extraction/user-data',
1635
+ // outputSchema: {
1636
+ // type: 'object',
1637
+ // properties: {
1638
+ // name: { type: 'string', description: 'User full name' },
1639
+ // email: { type: 'string', description: 'User email address' },
1640
+ // age: { type: 'number', description: 'User age' }
1641
+ // },
1642
+ // required: ['name', 'email']
1643
+ // }
1644
+ // }
1645
+
1646
+ const response = await gateway.invoke({
1647
+ jobId: 'job-123',
1648
+ agentId: 'agent-456',
1649
+ instructions: 'extraction/user-data', // Instruction key with outputSchema
1650
+ input: 'John Doe, john@example.com, 30 years old',
1651
+ inferenceType: 'extraction'
1652
+ });
1653
+
1654
+ // Instructions automatically extended with:
1655
+ // "You must respond with a single valid JSON object that strictly conforms to the schema provided below:
1656
+ //
1657
+ // Expected fields:
1658
+ // - name (string) [REQUIRED]: User full name
1659
+ // - email (string) [REQUIRED]: User email address
1660
+ // - age (number) [optional]: User age
1661
+ //
1662
+ // Full JSON Schema:
1663
+ // ```json
1664
+ // { ... schema ... }
1665
+ // ```"
1666
+ ```
1667
+
1668
+ **Configuration:**
1669
+
1670
+ Add `outputObjectPrefix` to `instructions-blocks.json`:
1671
+
1672
+ ```json
1673
+ {
1674
+ "outputObjectPrefix": "You must respond with a single valid JSON object that strictly conforms to the schema provided below:"
1675
+ }
1676
+ ```
1677
+
1678
+ This prefix is automatically appended to instructions when `outputType` or `outputSchema` is available.
1679
+
1680
+ #### Schema Validation (v1.7.0+)
1681
+
1682
+ The gateway supports enhanced schema validation with strict/non-strict modes and automatic schema resolution from instruction metadata.
1683
+
1684
+ **Validation Options:**
1685
+
1686
+ ```typescript
1687
+ const response = await gateway.invoke({
1688
+ jobId: 'job-123',
1689
+ agentId: 'agent-456',
1690
+ instructions: 'classification/vulnerability-severity',
1691
+ input: 'CVE-2024-1234: Critical RCE',
1692
+ inferenceType: 'classification',
1693
+ validateOutputSchema: true, // Enable validation
1694
+ strictValidation: false // Non-strict: log warnings, don't throw (default)
1695
+ });
1696
+ ```
1697
+
1698
+ **Strict Validation Mode:**
1699
+
1700
+ ```typescript
1701
+ const response = await gateway.invoke({
1702
+ jobId: 'job-123',
1703
+ agentId: 'agent-456',
1704
+ instructions: 'classification/vulnerability-severity',
1705
+ input: 'CVE-2024-1234: Critical RCE',
1706
+ inferenceType: 'classification',
1707
+ validateOutputSchema: true,
1708
+ strictValidation: true // Strict: throw error if validation fails
1709
+ });
1710
+
1711
+ // If validation fails, throws error:
1712
+ // Error: Schema validation failed for inference type 'classification': ...
1713
+ ```
1714
+
1715
+ **Automatic Schema Resolution:**
1716
+
1717
+ When `inferenceType` is provided and instruction metadata is available, the gateway automatically:
1718
+ 1. Checks instruction metadata for `outputSchema`
1719
+ 2. Uses it for validation (overrides default schema from outputs-library)
1720
+ 3. Falls back to outputs-library schema registry if not found
1721
+
1722
+ ```typescript
1723
+ // Instruction metadata has:
1724
+ // {
1725
+ // instructionKey: 'classification/vulnerability-severity',
1726
+ // outputSchema: { /* custom schema */ }
1727
+ // }
1728
+
1729
+ const response = await gateway.invoke({
1730
+ jobId: 'job-123',
1731
+ agentId: 'agent-456',
1732
+ instructions: 'classification/vulnerability-severity', // Instruction key
1733
+ input: 'CVE-2024-1234: Critical RCE',
1734
+ inferenceType: 'classification',
1735
+ validateOutputSchema: true
1736
+ // Gateway automatically uses outputSchema from metadata
1737
+ });
1738
+ ```
1739
+
1740
+ **Validation Error Handling:**
1741
+
1742
+ - **Non-strict mode (default)**: Validation failures log warnings but continue
1743
+ - **Strict mode**: Validation failures throw errors and stop execution
1744
+ - **Validation results** are always available in `response.metadata.outputValidationErrors` and `response.metadata.outputValidationPassed`
1745
+
1746
+ #### Output Structure Audit (v2.1.1+)
1747
+
1748
+ The gateway automatically audits the structure of parsed output against the expected schema when a schema is available. This provides detailed analysis of what fields are present, missing, or extra - without affecting the response itself.
1749
+
1750
+ **Automatic Audit:**
1751
+
1752
+ When `outputSchema` is available (from instruction metadata or provided directly), the gateway automatically:
1753
+ - Compares the response structure against the schema
1754
+ - Identifies missing required fields
1755
+ - Identifies extra fields (in response but not in schema)
1756
+ - Reports matching fields
1757
+ - Provides structure match status
1758
+
1759
+ **Audit Results:**
1760
+
1761
+ ```typescript
1762
+ const response = await gateway.invoke({
1763
+ jobId: 'job-123',
1764
+ agentId: 'agent-456',
1765
+ instructions: 'extraction/user-data',
1766
+ input: 'John Doe, john@example.com',
1767
+ inferenceType: 'extraction',
1768
+ validateOutputSchema: true
1769
+ });
1770
+
1771
+ // Audit results in metadata (always available when schema exists)
1772
+ console.log(response.metadata.outputAudit);
1773
+ // {
1774
+ // hasAllRequiredFields: true,
1775
+ // missingRequiredFields: undefined,
1776
+ // extraFields: ['timestamp'], // Extra field not in schema
1777
+ // matchingFields: ['name', 'email'],
1778
+ // responseFieldCount: 3,
1779
+ // schemaFieldCount: 2,
1780
+ // structureMatches: false // false because of extra field
1781
+ // }
1782
+ ```
1783
+
1784
+ **Audit Metadata:**
1785
+
1786
+ - `outputAudit.hasAllRequiredFields` - Whether all required fields are present
1787
+ - `outputAudit.missingRequiredFields` - Array of missing required field names
1788
+ - `outputAudit.extraFields` - Array of fields in response but not in schema
1789
+ - `outputAudit.matchingFields` - Array of fields present in both response and schema
1790
+ - `outputAudit.responseFieldCount` - Total fields in response
1791
+ - `outputAudit.schemaFieldCount` - Total fields expected in schema
1792
+ - `outputAudit.structureMatches` - Whether structure matches exactly (all required present, no extra fields)
1793
+
1794
+ **Note:** Audit is always performed when a schema is available - it's informational only and doesn't affect the response. Clients can use it for monitoring, logging, or quality checks.
1795
+
1796
+ **Graceful Outputs Library Handling:**
1797
+
1798
+ When outputs library is not available:
1799
+ - Gateway automatically falls back to JSON parsing
1800
+ - Response metadata indicates parsing method used (`outputsLibraryAvailable`, `parsingMethod`)
1801
+ - Clear warnings are logged (not generic errors)
1802
+ - Request continues with fallback parsing
1803
+
1804
+ ```typescript
1805
+ // Check parsing method in response
1806
+ if (response.metadata.parsingMethod === 'json-parse') {
1807
+ console.log('Outputs library unavailable, used JSON parsing fallback');
1808
+ }
1809
+ if (!response.metadata.outputsLibraryAvailable) {
1810
+ console.log('Outputs library was not available for this request');
1811
+ }
1812
+
1813
+ // Check if fallback structure was used
1814
+ if (response.metadata.isFallback) {
1815
+ console.log('Parsing failed, using fallback structure');
1816
+ // parsedOutput will have _fallback: true and _raw fields
1817
+ const fallbackOutput = response.metadata.parsedOutput as any;
1818
+ if (fallbackOutput._fallback) {
1819
+ console.log('Raw response:', fallbackOutput._raw);
1820
+ }
1821
+ }
1822
+ ```
1823
+
1824
+ **Guaranteed Consistent Structure (v1.7.4+):**
1825
+
1826
+ The gateway ensures **consistent structure at all levels**:
1827
+
1828
+ 1. **Content Level**: `content` is always a string (JSON objects are stringified)
1829
+ 2. **Parsed Content Level**: `parsedContent` is always an object/array when content is JSON (forced structure)
1830
+ 3. **Parsed Output Level**: `parsedOutput` always has type-specific structure when `inferenceType` is provided
1831
+
1832
+ **Forced Structure Conversion:**
1833
+ - **JSON → Object/Array**: Always parsed into object/array structure (even if invalid JSON, wrapped)
1834
+ - **Text → String**: Always kept as string in `content` field
1835
+ - **Object → JSON String**: Always stringified in `content` field
1836
+ - **Array → JSON String**: Always stringified in `content` field
1837
+
1838
+ When `inferenceType` is provided, the gateway **always** returns a consistent structure in `parsedOutput`, even when parsing fails:
1839
+
1840
+ ```typescript
1841
+ // Success case
1842
+ {
1843
+ metadata: {
1844
+ parsedOutput: {
1845
+ extracted: { ... } // ✅ Structured output
1846
+ },
1847
+ isFallback: false
1848
+ }
1849
+ }
1850
+
1851
+ // Failure case - guaranteed structure
1852
+ {
1853
+ metadata: {
1854
+ parsedOutput: {
1855
+ extracted: parsedContent || {}, // ✅ Always present
1856
+ _fallback: true, // ✅ Indicates parsing failed
1857
+ _raw: rawText // ✅ Original response preserved
1858
+ },
1859
+ isFallback: true,
1860
+ parsingMethod: 'json-parse'
1861
+ }
1862
+ }
1863
+ ```
1864
+
1865
+ **Type-Specific Fallback Structures:**
1866
+
1867
+ Each inference type has a standard fallback structure:
1868
+
1869
+ - **Extraction**: `{ extracted: {}, _fallback: true, _raw: string }`
1870
+ - **Classification**: `{ classes: [], confidence: {}, _fallback: true, _raw: string }`
1871
+ - **Question-Answer**: `{ answer: string, confidence?: number, _fallback: true, _raw: string }`
1872
+ - **Summarization**: `{ summary: string, keyPoints: [], _fallback: true, _raw: string }`
1873
+ - **Sentiment**: `{ sentiment: string, score: number, _fallback: true, _raw: string }`
1874
+
1875
+ This ensures consumers can always safely access expected fields without null checks:
1876
+
1877
+ ```typescript
1878
+ // ✅ Safe - structure is guaranteed
1879
+ const output = response.metadata.parsedOutput as ExtractionOutput;
1880
+ const extracted = output.extracted; // Always present, never undefined
1881
+
1882
+ // Check if it's a fallback
1883
+ if (output._fallback) {
1884
+ console.warn('Parsing failed, using fallback structure');
1885
+ console.log('Raw response:', output._raw);
1886
+ }
1887
+ ```
1888
+
1889
+ **Fallback Parsing Priority:**
1890
+
1891
+ When outputs library is not available, the gateway automatically falls back in this order:
1892
+
1893
+ 1. **Outputs Library** (preferred) - Full parsing with type inference
1894
+ 2. **JSON Parse** - If `parsedContent` is available as object/array
1895
+ 3. **Raw Content** - Last resort, returns raw text/object
1896
+
1897
+ **Error Detection:**
1898
+
1899
+ The gateway automatically detects and handles outputs library errors:
1900
+ - **Module Not Found**: Clear warning logged, fallback parsing used
1901
+ - **Export Missing**: Clear warning logged, fallback parsing used
1902
+ - **Version Mismatch**: Clear warning logged, fallback parsing used
1903
+
1904
+ All errors are logged with clear messages indicating the issue and the fallback method used.
1905
+
1906
+ #### Benefits
1907
+
1908
+ - **Type Safety**: Full TypeScript support with typed outputs
1909
+ - **Consistency**: Standardized object structures across all SDKs
1910
+ - **Validation**: Built-in schema validation with strict/non-strict modes (v1.7.0+)
1911
+ - **Metadata-Driven**: Automatic schema resolution from instruction metadata (v1.7.0+)
1912
+ - **Consistent Structure**: Guaranteed consistent `parsedOutput` structure even when parsing fails (v1.7.4+)
1913
+ - **Flexibility**: Works with any inference type
1914
+ - **Integration**: Seamlessly integrated into gateway responses
1915
+ - **Graceful Degradation**: Automatic fallback when outputs library unavailable (v1.7.0+)
1916
+
1917
+ ### 7. Response Transformation Hooks (v1.6.9+)
1918
+
1919
+ The gateway supports transformation hooks that allow you to modify responses at different stages of processing: before parsing, after parsing, before validation, and after validation. This enables output mapping, data normalization, computed fields, and custom transformations.
1920
+
1921
+ #### Overview
1922
+
1923
+ Transformation hooks are provided via the `transformations` field in `ChatRequest` or `AIRequest`. Each hook receives the data at a specific stage and returns the transformed data.
1924
+
1925
+ **Available Hooks:**
1926
+
1927
+ 1. **`preParse`**: Transform raw text before parsing (applied to `rawText`)
1928
+ 2. **`postParse`**: Transform parsed content after parsing (applied to `parsedOutput` or `parsedContent`)
1929
+ 3. **`preValidate`**: Transform parsed content before schema validation
1930
+ 4. **`postValidate`**: Transform validated content after validation
1931
+
1932
+ #### Interface
1933
+
1934
+ ```typescript
1935
+ interface ResponseTransformationConfig {
1936
+ preParse?: (rawText: string, metadata: any) => string;
1937
+ postParse?: (parsed: any, metadata: any) => any;
1938
+ preValidate?: (parsed: any, schema: any) => any;
1939
+ postValidate?: (validated: any, metadata: any) => any;
1940
+ }
1941
+ ```
1942
+
1943
+ **Metadata Object:**
1944
+
1945
+ The `metadata` parameter passed to hooks contains:
1946
+
1947
+ ```typescript
1948
+ {
1949
+ jobId: string;
1950
+ agentId: string;
1951
+ taskId?: string;
1952
+ taskTypeId?: string;
1953
+ instructionKey: string; // The instruction key or text
1954
+ inferenceType?: string;
1955
+ }
1956
+ ```
1957
+
1958
+ #### Basic Usage
1959
+
1960
+ ```typescript
1961
+ const response = await gateway.invoke({
1962
+ jobId: 'job-1',
1963
+ agentId: 'agent-1',
1964
+ instructions: 'classification/basic',
1965
+ input: 'This is great!',
1966
+ config: { model: 'gpt-4o' },
1967
+ inferenceType: 'classification',
1968
+ transformations: {
1969
+ // Clean raw text before parsing
1970
+ preParse: (rawText, metadata) => {
1971
+ return rawText.trim().replace(/\s+/g, ' ');
1972
+ },
1973
+
1974
+ // Transform parsed output
1975
+ postParse: (parsed, metadata) => {
1976
+ return {
1977
+ ...parsed,
1978
+ transformed: true,
1979
+ timestamp: Date.now()
1980
+ };
1981
+ },
1982
+
1983
+ // Apply output mapping before validation
1984
+ preValidate: (parsed, schema) => {
1985
+ // Map fields to standardized names
1986
+ return {
1987
+ label: parsed.class,
1988
+ score: parsed.confidence,
1989
+ ...parsed
1990
+ };
1991
+ },
1992
+
1993
+ // Add computed fields after validation
1994
+ postValidate: (validated, metadata) => {
1995
+ return {
1996
+ ...validated,
1997
+ computed: {
1998
+ isHighConfidence: validated.confidence > 0.8,
1999
+ category: validated.class === 'positive' ? 'positive' : 'negative'
2000
+ }
2001
+ };
2002
+ }
2003
+ }
2004
+ });
2005
+ ```
2006
+
2007
+ #### Use Cases
2008
+
2009
+ **1. Output Field Mapping**
2010
+
2011
+ Map output fields to standardized names:
2012
+
2013
+ ```typescript
2014
+ transformations: {
2015
+ postParse: (parsed, metadata) => {
2016
+ // Map from LLM output to standardized format
2017
+ const mapping = {
2018
+ 'class': 'label',
2019
+ 'confidence': 'score',
2020
+ 'reason': 'explanation'
2021
+ };
2022
+
2023
+ const mapped: Record<string, any> = {};
2024
+ for (const [key, value] of Object.entries(parsed)) {
2025
+ const mappedKey = mapping[key] || key;
2026
+ mapped[mappedKey] = value;
2027
+ }
2028
+
2029
+ return { ...parsed, ...mapped };
2030
+ }
2031
+ }
2032
+ ```
2033
+
2034
+ **2. Data Normalization**
2035
+
2036
+ Normalize data formats across different providers:
2037
+
2038
+ ```typescript
2039
+ transformations: {
2040
+ postParse: (parsed, metadata) => {
2041
+ // Normalize confidence scores to 0-1 range
2042
+ if (parsed.confidence && parsed.confidence > 1) {
2043
+ parsed.confidence = parsed.confidence / 100;
2044
+ }
2045
+
2046
+ // Normalize class names to lowercase
2047
+ if (parsed.class) {
2048
+ parsed.class = parsed.class.toLowerCase();
2049
+ }
2050
+
2051
+ return parsed;
2052
+ }
2053
+ }
2054
+ ```
2055
+
2056
+ **3. Add Computed Fields**
2057
+
2058
+ Add derived/computed fields:
2059
+
2060
+ ```typescript
2061
+ transformations: {
2062
+ postValidate: (validated, metadata) => {
2063
+ return {
2064
+ ...validated,
2065
+ metadata: {
2066
+ ...validated.metadata,
2067
+ computed: {
2068
+ isPositive: validated.class === 'positive',
2069
+ confidenceLevel: validated.confidence > 0.8 ? 'high' : 'low',
2070
+ processedAt: new Date().toISOString()
2071
+ }
2072
+ }
2073
+ };
2074
+ }
2075
+ }
2076
+ ```
2077
+
2078
+ **4. Response Structure Transformation**
2079
+
2080
+ Restructure response data:
2081
+
2082
+ ```typescript
2083
+ transformations: {
2084
+ postParse: (parsed, metadata) => {
2085
+ // Transform flat structure to nested
2086
+ return {
2087
+ result: {
2088
+ classification: {
2089
+ label: parsed.class,
2090
+ confidence: parsed.confidence
2091
+ },
2092
+ metadata: {
2093
+ jobId: metadata.jobId,
2094
+ agentId: metadata.agentId
2095
+ }
2096
+ }
2097
+ };
2098
+ }
2099
+ }
2100
+ ```
2101
+
2102
+ **5. Clean Raw Text**
2103
+
2104
+ Clean and normalize raw text before parsing:
2105
+
2106
+ ```typescript
2107
+ transformations: {
2108
+ preParse: (rawText, metadata) => {
2109
+ // Remove markdown code blocks if present
2110
+ let cleaned = rawText.replace(/```json\n?/g, '').replace(/```\n?/g, '');
2111
+
2112
+ // Remove leading/trailing whitespace
2113
+ cleaned = cleaned.trim();
2114
+
2115
+ // Fix common JSON issues
2116
+ cleaned = cleaned.replace(/,(\s*[}\]])/g, '$1'); // Remove trailing commas
2117
+
2118
+ return cleaned;
2119
+ }
2120
+ }
2121
+ ```
2122
+
2123
+ #### Error Handling
2124
+
2125
+ Transformation hooks have built-in error handling - if a transformation fails, the gateway logs a warning and continues with the original (untransformed) data. This ensures that transformation errors don't break the request.
2126
+
2127
+ ```typescript
2128
+ // If transformation throws an error, gateway logs:
2129
+ // "postParse transformation failed, using original parsed output"
2130
+ // and continues with the original data
2131
+ ```
2132
+
2133
+ #### Integration with Instruction Metadata
2134
+
2135
+ Combine transformation hooks with instruction metadata for fully metadata-driven systems:
2136
+
2137
+ ```typescript
2138
+ // Fetch metadata
2139
+ const metadata = await gateway.getInstructionMetadata('classification/basic');
2140
+
2141
+ if (metadata?.defaultOutputMapping) {
2142
+ // Use metadata to configure transformation
2143
+ const response = await gateway.invoke({
2144
+ jobId: 'job-1',
2145
+ agentId: 'agent-1',
2146
+ instructions: 'classification/basic',
2147
+ input: 'This is great!',
2148
+ config: { model: 'gpt-4o' },
2149
+ inferenceType: metadata.outputType,
2150
+ transformations: {
2151
+ postParse: (parsed, reqMetadata) => {
2152
+ // Apply output mapping from metadata
2153
+ const mapped: Record<string, any> = {};
2154
+ for (const [key, mappedKey] of Object.entries(metadata.defaultOutputMapping!)) {
2155
+ if (key in parsed) {
2156
+ mapped[mappedKey] = (parsed as any)[key];
2157
+ }
2158
+ }
2159
+ return { ...parsed, ...mapped };
2160
+ }
2161
+ }
2162
+ });
2163
+ }
2164
+ ```
2165
+
2166
+ #### Processing Order
2167
+
2168
+ Transformations are applied in this order:
2169
+
2170
+ 1. **preParse**: Raw text → Transformed raw text
2171
+ 2. **Parsing**: Transformed raw text → Parsed output (if `inferenceType` provided)
2172
+ 3. **postParse**: Parsed output → Transformed parsed output
2173
+ 4. **preValidate**: Transformed parsed output → Pre-validated output (if validation enabled)
2174
+ 5. **Validation**: Pre-validated output → Validated output (if validation enabled)
2175
+ 6. **postValidate**: Validated output → Final transformed output
2176
+
2177
+ If no `inferenceType` is provided, `postParse` is applied to `parsedContent` instead.
2178
+
2179
+ ### 8. Content Registry Integration (Instruction Keys)
2180
+
2181
+ The gateway integrates with `@xronoces/content-registry` to fetch instructions by key instead of hardcoding them. This enables centralized content management, versioning, and zero-deploy updates.
2182
+
2183
+ #### Overview: Two Modes for Instructions
2184
+
2185
+ The gateway supports **two modes** for providing instructions to LLMs:
2186
+
2187
+ 1. **Text Mode (Default)**: Instructions are actual text strings embedded in code
2188
+ 2. **Key Mode (New)**: Instructions are keys that reference content stored in `@xronoces/content-registry`
2189
+
2190
+ Both modes work seamlessly - the gateway automatically detects which mode you're using based on the instruction format.
2191
+
2192
+ #### Installation
2193
+
2194
+ ```bash
2195
+ npm install @xronoces/content-registry
2196
+ ```
2197
+
2198
+ **Note**: `@xronoces/content-registry` (v2.14.0+) is automatically installed as a dependency. This version includes simplified APIs for easier integration.
2199
+
2200
+ **Configuration**: Content registry is used when `contentRegistryConfig` is provided in the gateway configuration.
2201
+
2202
+ **For Internal Use**: Content-registry auto-initializes from environment variables if available, enabling instructionsBlocks resolution without requiring explicit configuration.
2203
+
2204
+ **See**: [Content Resolver — Upstream Guide](./CONTENT_RESOLVER_UPSTREAM_GUIDE.md) for configuration, environment variables, content layout, and upstream checklist.
2205
+
2206
+ #### Configuration
2207
+
2208
+ ```typescript
2209
+ import { AIGateway } from '@x12i/ai-gateway';
2210
+
2211
+ const gateway = new AIGateway({
2212
+ defaultProvider: 'openai',
2213
+ // Enable content-registry mode
2214
+ enableContentRegistry: true,
2215
+ // Configure content registry
2216
+ contentRegistryConfig: {
2217
+ s3Bucket: 'my-content-registry-bucket',
2218
+ cacheTTL: 3600, // Cache content for 1 hour
2219
+ redis: {
2220
+ host: 'localhost',
2221
+ port: 6379,
2222
+ password: process.env.REDIS_PASSWORD
2223
+ },
2224
+ // ... other content-registry config options
2225
+ }
2226
+ });
2227
+ ```
2228
+
2229
+ #### Mode 1: Text Mode (Automatic - Has Spaces)
2230
+
2231
+ **How it works**: Automatically detected when instructions contain whitespace. Used as literal text without any resolution.
2232
+
2233
+ **When used automatically**:
2234
+ - Instructions contain spaces, tabs, or newlines
2235
+ - No content registry lookup performed
2236
+ - Fast processing with no external dependencies
2237
+
2238
+ **Example**:
2239
+ ```typescript
2240
+ // Text mode - instructions as plain text
2241
+ const response = await gateway.invoke({
2242
+ messages: [
2243
+ {
2244
+ role: 'system',
2245
+ content: 'You are a helpful assistant that classifies text into categories: positive, negative, or neutral.'
2246
+ },
2247
+ {
2248
+ role: 'user',
2249
+ content: 'Classify: "This product is amazing!"'
2250
+ }
2251
+ ],
2252
+ jobId: 'job-123'
2253
+ });
2254
+ ```
2255
+
2256
+ **Behavior**:
2257
+ - Gateway uses the text as-is
2258
+ - No content-registry lookup
2259
+ - Fast (no network calls)
2260
+
2261
+ #### Mode 2: Key Mode (Automatic - No Spaces)
2262
+
2263
+ **How it works**: Automatically detected when instructions contain no whitespace. Keys are resolved from content registry.
2264
+
2265
+ **When used automatically**:
2266
+ - Instructions contain no spaces, tabs, or newlines
2267
+ - Content is fetched from configured content registry
2268
+ - Supports template variables and dynamic content
2269
+ - Fail-closed: unresolvable keys become bad requests
2270
+
2271
+ **Example**:
2272
+ ```typescript
2273
+ // Key mode - instructions as keys to content-registry
2274
+ const response = await gateway.invoke({
2275
+ messages: [
2276
+ {
2277
+ role: 'system',
2278
+ content: 'classification/basic' // ✅ Key - will be resolved from content-registry
2279
+ },
2280
+ {
2281
+ role: 'user',
2282
+ content: 'Classify: "This product is amazing!"'
2283
+ }
2284
+ ],
2285
+ jobId: 'job-123',
2286
+ // Optional: variables for template rendering
2287
+ contentVariables: {
2288
+ task: 'classification',
2289
+ context: 'product reviews'
2290
+ }
2291
+ });
2292
+ ```
2293
+
2294
+ **What happens behind the scenes**:
2295
+ 1. Gateway detects `'classification/basic'` is a key (not plain text)
2296
+ 2. Creates ContentResolver with hierarchical patterns for instruction resolution
2297
+ 3. ContentResolver resolves content using configurable fallback chains:
2298
+ - `content/instructions/{contentId}` (primary pattern)
2299
+ - Handles consolidated vs individual file resolution
2300
+ 4. Content-registry returns raw template content with full metadata envelope
2301
+ 5. Instructions parser renders template with `contentVariables` using Rendrix
2302
+ 6. Gateway returns rendered instruction text with metadata
2303
+ 7. Sends the resolved instruction to the LLM
2304
+
2305
+ **Template-Based Prompts (Same as Instructions)**:
2306
+ Prompts work exactly like instructions - both can be resolved from content-registry using explicit keys with suffixes:
2307
+
2308
+ ```typescript
2309
+ const response = await gateway.invoke({
2310
+ jobId: 'job-123',
2311
+ agentId: 'agent-456',
2312
+ instructions: 'professional-answer.instructions', // Key with suffix
2313
+ prompt: 'professional-answer.prompt', // Key with suffix
2314
+ input: 'What is the capital of France?',
2315
+ workingMemory: {
2316
+ taskDescription: 'Answer questions professionally'
2317
+ }
2318
+ });
2319
+ ```
2320
+
2321
+ **File Structure in Content-Registry**:
2322
+ ```
2323
+ .metadata/
2324
+ content/
2325
+ instructions/
2326
+ professional-answer.instructions.md
2327
+ professional-answer.prompt.md
2328
+ ```
2329
+
2330
+ **Key Features**:
2331
+ - Both instructions and prompts use the same memory context (`workingMemory`, `shortTermMemory`, `experienceMemory`, `knowledgeMemory`)
2332
+ - Both use the same template rendering system (Rendrix)
2333
+ - Both support the same key detection logic (no spaces = key, has spaces = text)
2334
+ - Postfix handling (`.instructions` and `.prompt`) is done upstream by AI skills
2335
+
2336
+ See [Prompt Template Usage Guide](./docs/PROMPT_TEMPLATE_USAGE.md) for complete documentation.
2337
+
2338
+ **Behavior**:
2339
+ - Gateway resolves keys automatically using ContentResolver
2340
+ - Requires `contentRegistryConfig` to be set
2341
+ - Uses hierarchical content resolution with configurable patterns
2342
+ - Template rendering handled by dedicated instructions parser
2343
+ - Supports complex variable substitution (workingMemory, shortTermMemory, etc.)
2344
+ - Keys don't include file extensions (e.g., `classification/basic` not `classification/basic.md`)
2345
+ - Access to YAML front matter and version metadata
2346
+ - Enhanced error handling with detailed resolution information
2347
+
2348
+ #### Content Resolution Patterns
2349
+
2350
+ Instruction keys are resolved using configurable hierarchical patterns through the ContentResolver. The resolver tries multiple path patterns in priority order.
2351
+
2352
+ **Resolution Pattern Examples**:
2353
+ ```typescript
2354
+ // For instruction key 'classification/basic':
2355
+ // Pattern 1: content/instructions/classification/basic
2356
+ // Tries: classification/basic.md, classification/basic.txt, etc.
2357
+
2358
+ // For instructionsBlocks 'input' with agentId 'agent-1':
2359
+ // Pattern 1: content/instructions/agent-1/input
2360
+ // Pattern 2: content/instructions/shared/input
2361
+ ```
2362
+
2363
+ **How Resolution Works**:
2364
+ 1. Key is provided: `"classification/basic"` (no file extension)
2365
+ 2. ContentResolver applies hierarchical patterns with `{contentType}`, `{contentId}`, etc.
2366
+ 3. Tries patterns in priority order until content is found
2367
+ 4. Returns raw template content with metadata
2368
+ 5. Instructions parser renders templates with Rendrix
2369
+ 6. Final rendered content sent to LLM
2370
+
2371
+ **Key Detection Rules**:
2372
+ The gateway automatically detects if a string is a key or plain text:
2373
+
2374
+ ```typescript
2375
+ // ✅ Detected as KEYS (will be resolved from content-registry):
2376
+ 'classification/basic' // Path-like structure
2377
+ 'extraction/entities' // Contains slashes
2378
+ 'instructions/path/to/content' // Already normalized format
2379
+ 'short-key' // Short, no spaces
2380
+
2381
+ // ❌ Treated as TEXT (used as-is):
2382
+ 'You are a helpful assistant...' // Contains newlines
2383
+ 'This is a very long instruction that exceeds 200 characters and contains multiple sentences with spaces and newlines...' // Too long
2384
+ 'Instruction with spaces' // Contains spaces (unless very short)
2385
+ ```
2386
+
2387
+ **Detection Logic**:
2388
+ - ✅ **Key if**: Contains `/` (path-like) OR (short length < 100 chars AND no spaces)
2389
+ - ❌ **Text if**: Contains newlines OR length > 200 chars OR has spaces (for longer strings)
2390
+
2391
+ #### Template Variables (Content Registry)
2392
+
2393
+ When using instruction keys, you can pass variables for template rendering. Template rendering is handled by Rendrix after fetching content from content-registry.
2394
+
2395
+ **How it works**:
2396
+ 1. Content stored in registry uses template syntax: `{{variableName}}`
2397
+ 2. Gateway fetches content from content-registry using `fetchContent()`
2398
+ 3. Gateway uses Rendrix to render the template with your variables
2399
+ 4. Resolved instruction is used in the LLM request
2400
+
2401
+ **Example**:
2402
+
2403
+ **Content stored in registry** (`instructions/classification/basic`):
2404
+ ```
2405
+ You are a {{task}} expert.
2406
+ Classify text into the following categories: {{classes}}.
2407
+ Provide confidence scores and reasoning.
2408
+ ```
2409
+
2410
+ **Code**:
2411
+ ```typescript
2412
+ const response = await gateway.invoke({
2413
+ messages: [
2414
+ {
2415
+ role: 'system',
2416
+ content: 'classification/basic' // Key - will be resolved
2417
+ },
2418
+ {
2419
+ role: 'user',
2420
+ content: 'Classify: "This product is amazing!"'
2421
+ }
2422
+ ],
2423
+ contentVariables: {
2424
+ task: 'classification',
2425
+ classes: 'positive, negative, neutral'
2426
+ }
2427
+ });
2428
+ ```
2429
+
2430
+ **Resolved instruction** (what the LLM actually receives):
2431
+ ```
2432
+ You are a classification expert.
2433
+ Classify text into the following categories: positive, negative, neutral.
2434
+ Provide confidence scores and reasoning.
2435
+ ```
2436
+
2437
+ **Benefits**:
2438
+ - Same instruction template, different variables
2439
+ - Dynamic instruction generation
2440
+ - A/B testing with different variable values
2441
+ - Centralized template management
2442
+
2443
+ #### Mixed Mode (Text + Keys)
2444
+
2445
+ You can mix text and keys in the same request. The gateway automatically handles both:
2446
+
2447
+ ```typescript
2448
+ const response = await gateway.invoke({
2449
+ messages: [
2450
+ {
2451
+ role: 'system',
2452
+ content: 'classification/basic' // ✅ Key - resolved from content-registry
2453
+ },
2454
+ {
2455
+ role: 'user',
2456
+ content: 'You are analyzing product reviews.' // ✅ Text - used as-is
2457
+ },
2458
+ {
2459
+ role: 'assistant',
2460
+ content: 'I understand.'
2461
+ },
2462
+ {
2463
+ role: 'user',
2464
+ content: 'Classify: "This product is amazing!"'
2465
+ }
2466
+ ],
2467
+ jobId: 'job-123',
2468
+ contentVariables: {
2469
+ task: 'classification'
2470
+ }
2471
+ });
2472
+ ```
2473
+
2474
+ **What happens**:
2475
+ - `'classification/basic'` → Detected as key → Resolved from content-registry
2476
+ - `'You are analyzing product reviews.'` → Detected as text → Used as-is
2477
+ - Other messages → Used as-is
2478
+
2479
+ **Use cases**:
2480
+ - Mix static instructions (text) with dynamic instructions (keys)
2481
+ - Gradually migrate from text mode to key mode
2482
+ - Use keys for complex instructions, text for simple ones
2483
+
2484
+ #### Content Registry Accessor Methods
2485
+
2486
+ The gateway exposes content-registry methods for pre-validation, batch operations, and debugging without making LLM calls:
2487
+
2488
+ ```typescript
2489
+ const gateway = new AIGateway({
2490
+ enableContentRegistry: true,
2491
+ contentRegistryConfig: { github: { ... } }
2492
+ });
2493
+
2494
+ // Get the underlying content-registry instance
2495
+ const registry = gateway.getContentRegistry();
2496
+ if (registry) {
2497
+ // Use registry methods directly
2498
+ const content = await registry.getByPath({
2499
+ pathKey: 'classification/basic',
2500
+ category: 'instruction'
2501
+ });
2502
+ }
2503
+
2504
+ // Resolve an instruction key with variables (without making LLM call)
2505
+ const resolved = await gateway.resolveInstructionKey('classification/basic', {
2506
+ task: 'classification',
2507
+ classes: 'positive, negative, neutral'
2508
+ });
2509
+ console.log(resolved); // "You are a classification expert. Classify text into: positive, negative, neutral."
2510
+
2511
+ // Check if an instruction key exists
2512
+ const exists = await gateway.hasInstructionKey('classification/basic');
2513
+ if (exists) {
2514
+ // Key exists, safe to use
2515
+ }
2516
+
2517
+ // Get instruction metadata (structured metadata for metadata-driven systems)
2518
+ const metadata = await gateway.getInstructionMetadata('classification/basic');
2519
+ if (metadata) {
2520
+ console.log('Output type:', metadata.outputType); // 'classification'
2521
+ console.log('Required variables:', metadata.requiredVariables); // ['task', 'classes']
2522
+ console.log('Schema:', metadata.outputSchema); // JSONSchema7
2523
+ console.log('Validation rules:', metadata.validationRules);
2524
+ console.log('Output mapping:', metadata.defaultOutputMapping);
2525
+ console.log('Parse options:', metadata.parseOptions);
2526
+ console.log('Version:', metadata.version);
2527
+ }
2528
+ ```
2529
+
2530
+ **Benefits:**
2531
+ - **Pre-validation**: Check if keys exist before making requests
2532
+ - **Batch operations**: Resolve multiple keys in parallel
2533
+ - **Debugging**: Inspect resolved instructions without LLM calls
2534
+ - **Consistency**: Use same content-registry instance as gateway
2535
+
2536
+ **Note**: All methods return `undefined` or throw errors if `contentRegistryConfig` is not set.
2537
+
2538
+ #### Instruction Metadata API (v1.6.9+)
2539
+
2540
+ The `getInstructionMetadata()` method returns structured metadata from content-registry, enabling fully metadata-driven inference systems. This is the primary API for accessing instruction configuration without making LLM calls.
2541
+
2542
+ **Interface:**
2543
+
2544
+ ```typescript
2545
+ interface InstructionMetadata {
2546
+ instructionKey: string; // The instruction key (e.g., 'classification/basic')
2547
+ outputType?: string; // Inference type for parsing (e.g., 'classification')
2548
+ outputSchema?: Record<string, unknown>; // JSONSchema7 for validation
2549
+ requiredVariables?: string[]; // Required template variables
2550
+ optionalVariables?: string[]; // Optional template variables
2551
+ validationRules?: ValidationRule[]; // Validation rules
2552
+ defaultOutputMapping?: Record<string, string>; // Output field mapping
2553
+ parseOptions?: { // Default parse options
2554
+ question?: string;
2555
+ classes?: string[];
2556
+ [key: string]: unknown;
2557
+ };
2558
+ version?: string; // Instruction version
2559
+ [key: string]: unknown; // Additional metadata preserved
2560
+ }
2561
+ ```
2562
+
2563
+ **Use Cases:**
2564
+
2565
+ 1. **Metadata-Driven Inference**: Fetch metadata to configure inference without hardcoding
2566
+ 2. **Variable Validation**: Check required/optional variables before making requests
2567
+ 3. **Schema Validation**: Get output schema for validation
2568
+ 4. **Output Mapping**: Apply default field mappings from metadata
2569
+ 5. **Parse Configuration**: Get default parse options for outputs library
2570
+
2571
+ **Example: Metadata-Driven System**
2572
+
2573
+ ```typescript
2574
+ const gateway = new AIGateway({
2575
+ enableContentRegistry: true,
2576
+ contentRegistryConfig: { github: { ... } }
2577
+ });
2578
+
2579
+ // Fetch instruction metadata
2580
+ const metadata = await gateway.getInstructionMetadata('classification/basic');
2581
+
2582
+ if (metadata) {
2583
+ // Validate required variables
2584
+ const requiredVars = metadata.requiredVariables || [];
2585
+ const providedVars = Object.keys(variables);
2586
+ const missingVars = requiredVars.filter(v => !providedVars.includes(v));
2587
+
2588
+ if (missingVars.length > 0) {
2589
+ throw new Error(`Missing required variables: ${missingVars.join(', ')}`);
2590
+ }
2591
+
2592
+ // Use metadata to configure inference
2593
+ const response = await gateway.invoke({
2594
+ jobId: 'job-1',
2595
+ agentId: 'agent-1',
2596
+ instructions: 'classification/basic',
2597
+ input: 'This is great!',
2598
+ config: { model: 'gpt-4o' },
2599
+ // Use metadata to configure parsing
2600
+ inferenceType: metadata.outputType,
2601
+ parseOptions: {
2602
+ ...metadata.parseOptions,
2603
+ classes: variables.classes || metadata.parseOptions?.classes
2604
+ },
2605
+ validateOutputSchema: !!metadata.outputSchema
2606
+ });
2607
+
2608
+ // Apply output mapping if provided
2609
+ let output = response.metadata.parsedOutput;
2610
+ if (metadata.defaultOutputMapping && output) {
2611
+ const mapped: Record<string, any> = {};
2612
+ for (const [key, mappedKey] of Object.entries(metadata.defaultOutputMapping)) {
2613
+ if (key in output) {
2614
+ mapped[mappedKey] = (output as any)[key];
2615
+ }
2616
+ }
2617
+ output = { ...output, ...mapped };
2618
+ }
2619
+ }
2620
+ ```
2621
+
2622
+ **Metadata Storage in Content-Registry:**
2623
+
2624
+ Store metadata in your content-registry files:
2625
+
2626
+ ```json
2627
+ {
2628
+ "messages": [
2629
+ {
2630
+ "role": "system",
2631
+ "content": "You are a {{task}} expert. Classify text into: {{classes}}."
2632
+ }
2633
+ ],
2634
+ "metadata": {
2635
+ "outputType": "classification",
2636
+ "outputSchema": {
2637
+ "type": "object",
2638
+ "properties": {
2639
+ "class": { "type": "string" },
2640
+ "confidence": { "type": "number" }
2641
+ }
2642
+ },
2643
+ "requiredVariables": ["task", "classes"],
2644
+ "optionalVariables": ["context"],
2645
+ "validationRules": [
2646
+ {
2647
+ "name": "confidence-threshold",
2648
+ "type": "range",
2649
+ "config": { "min": 0, "max": 1 }
2650
+ }
2651
+ ],
2652
+ "defaultOutputMapping": {
2653
+ "class": "label",
2654
+ "confidence": "score"
2655
+ },
2656
+ "parseOptions": {
2657
+ "classes": ["positive", "negative", "neutral"]
2658
+ },
2659
+ "version": "v1.0.0"
2660
+ }
2661
+ }
2662
+ ```
2663
+
2664
+ **Return Value:**
2665
+
2666
+ - Returns `InstructionMetadata | null` (not `undefined`)
2667
+ - Returns `null` if content-registry is not enabled
2668
+ - Returns `null` if instruction key not found
2669
+ - Preserves all additional metadata fields from content-registry
2670
+
2671
+ #### Complete Example: Using Content Registry
2672
+
2673
+ **Step 1: Store content in content-registry**
2674
+
2675
+ ```typescript
2676
+ import { ContentRegistry } from '@xronoces/content-registry';
2677
+
2678
+ const registry = new ContentRegistry({
2679
+ s3Bucket: 'my-content-bucket',
2680
+ // ... config
2681
+ });
2682
+
2683
+ // Store instruction content
2684
+ await registry.createPrompt({
2685
+ id: 'instructions/classification/basic',
2686
+ name: 'Basic Classification',
2687
+ version: 'v1.0.0',
2688
+ messages: [
2689
+ {
2690
+ role: 'system',
2691
+ content: 'You are a {{task}} expert. Classify text into: {{classes}}.'
2692
+ }
2693
+ ]
2694
+ });
2695
+ ```
2696
+
2697
+ **Step 2: Use key mode in gateway**
2698
+
2699
+ ```typescript
2700
+ const gateway = new AIGateway({
2701
+ enableContentRegistry: true,
2702
+ contentRegistryConfig: {
2703
+ s3Bucket: 'my-content-bucket',
2704
+ // ... same config as registry
2705
+ }
2706
+ });
2707
+
2708
+ // Use key instead of text
2709
+ const response = await gateway.invoke({
2710
+ messages: [
2711
+ {
2712
+ role: 'system',
2713
+ content: 'classification/basic' // ✅ Key - automatically resolved
2714
+ },
2715
+ {
2716
+ role: 'user',
2717
+ content: 'Classify: "This is great!"'
2718
+ }
2719
+ ],
2720
+ contentVariables: {
2721
+ task: 'classification',
2722
+ classes: 'positive, negative, neutral'
2723
+ }
2724
+ });
2725
+ ```
2726
+
2727
+ **Step 3: Update content without code changes**
2728
+
2729
+ ```typescript
2730
+ // Update instruction in content-registry (no code deployment needed!)
2731
+ await registry.createPrompt({
2732
+ id: 'instructions/classification/basic',
2733
+ version: 'v1.1.0', // New version
2734
+ messages: [
2735
+ {
2736
+ role: 'system',
2737
+ content: 'You are an advanced {{task}} expert. Classify text with high accuracy into: {{classes}}.'
2738
+ }
2739
+ ]
2740
+ });
2741
+
2742
+ // Gateway automatically uses latest version
2743
+ // No code changes needed!
2744
+ ```
2745
+
2746
+ #### Benefits
2747
+
2748
+ - **Centralized Management**: All instructions stored in one place (S3 + MongoDB via content-registry)
2749
+ - **Versioning**: Track changes and rollback if needed (content-registry supports versioning)
2750
+ - **Zero-Deploy Updates**: Update instructions without code changes
2751
+ - **A/B Testing**: Test different instruction versions
2752
+ - **Template Support**: Use Handlebars templates with variables
2753
+ - **Caching**: Instructions are cached for performance (Redis via content-registry)
2754
+ - **Backward Compatible**: Text mode still works (default) - no breaking changes
2755
+ - **Automatic Detection**: Gateway automatically detects keys vs text - no manual mode switching
2756
+
2757
+ #### Error Handling
2758
+
2759
+ If a key cannot be resolved, the gateway falls back to using the key as text and logs a warning:
2760
+
2761
+ ```typescript
2762
+ // If key 'invalid/key' doesn't exist in registry:
2763
+ const response = await gateway.invoke({
2764
+ messages: [
2765
+ {
2766
+ role: 'system',
2767
+ content: 'invalid/key' // Will fallback to using 'invalid/key' as text
2768
+ }
2769
+ ]
2770
+ });
2771
+
2772
+ // Gateway logs:
2773
+ // WARN: Failed to resolve instruction key: invalid/key
2774
+ // The key will be used as-is (text mode)
2775
+ ```
2776
+
2777
+ #### Advanced: Custom Instruction Resolver
2778
+
2779
+ You can also use the `InstructionResolver` directly:
2780
+
2781
+ ```typescript
2782
+ import { InstructionResolver } from '@x12i/ai-gateway';
2783
+
2784
+ const resolver = new InstructionResolver({
2785
+ enableContentRegistry: true,
2786
+ contentRegistryConfig: {
2787
+ s3Bucket: 'my-bucket',
2788
+ // ... config
2789
+ }
2790
+ });
2791
+
2792
+ // Resolve a key
2793
+ const instructions = await resolver.resolveInstructions(
2794
+ 'classification/basic',
2795
+ { task: 'classification' }
2796
+ );
2797
+
2798
+ console.log(instructions); // Resolved instruction text
2799
+ ```
2800
+
2801
+ ### 9. Contract Output Parsing (v6.3.1+)
2802
+
2803
+ The gateway supports contract output parsing and validation, where AI responses are parsed against expected schemas and the results are stored in activity records for compliance monitoring and debugging.
2804
+
2805
+ #### How It Works
2806
+
2807
+ 1. **Schema Input**: Provide an optional `expectedSchema` parameter in your gateway calls
2808
+ 2. **Response Parsing**: AI responses are automatically parsed using flex-md against the expected schema
2809
+ 3. **Activity Storage**: Parsed results are stored in activity records under `content.contractOutput`
2810
+ 4. **Status Tracking**: Compliance status is tracked in `content.outputStatus`
2811
+
2812
+ #### Basic Usage
2813
+
2814
+ ```typescript
2815
+ import { AIGateway } from '@x12i/ai-gateway';
2816
+
2817
+ const gateway = new AIGateway({ /* config */ });
2818
+
2819
+ // Call with expected schema for contract validation
2820
+ const response = await gateway.invoke({
2821
+ jobId: 'job-123',
2822
+ agentId: 'agent-456',
2823
+ instructions: 'Analyze the following text and provide structured output.',
2824
+
2825
+ // Provide expected schema for contract output parsing
2826
+ expectedSchema: {
2827
+ description: "Analysis results with key insights and recommendations",
2828
+ formatHint: "### Key Insights\n[insights]\n\n### Recommendations\n[recommendations]"
2829
+ },
2830
+
2831
+ messages: [{ role: 'user', content: 'Analyze this product review...' }]
2832
+ });
2833
+
2834
+ // Activity record will contain:
2835
+ // content.contractOutput: { keyInsights: "...", recommendations: "..." }
2836
+ // content.outputStatus: "different" (parsed successfully)
2837
+ ```
2838
+
2839
+ #### Schema Format
2840
+
2841
+ The `expectedSchema` parameter accepts a `StructuredTextSpec`:
2842
+
2843
+ ```typescript
2844
+ interface StructuredTextSpec {
2845
+ description: string; // Human-readable description of expected output
2846
+ formatHint?: string; // Optional flex-md format specification
2847
+ }
2848
+ ```
2849
+
2850
+ #### Activity Record Structure
2851
+
2852
+ When contract output parsing is enabled, activity records include additional fields:
2853
+
2854
+ ```json
2855
+ {
2856
+ "content": {
2857
+ "contractOutput": {
2858
+ // Parsed structured data from AI response
2859
+ // Empty object {} if parsing fails
2860
+ },
2861
+ "outputStatus": "ok" | "different" | undefined
2862
+ }
2863
+ }
2864
+ ```
2865
+
2866
+ #### Status Values
2867
+
2868
+ - `"ok"`: Contract output matches expected schema structure perfectly
2869
+ - `"different"`: Contract output parsed successfully but differs from expected schema
2870
+ - `undefined`: No schema validation performed (when `expectedSchema` not provided)
2871
+
2872
+ #### Use Cases
2873
+
2874
+ **Contract Compliance Monitoring:**
2875
+ ```typescript
2876
+ // Ensure AI responses follow expected structure
2877
+ const result = await gateway.call({
2878
+ messages: [...],
2879
+ expectedSchema: {
2880
+ description: "Professional answer with short summary and detailed analysis",
2881
+ formatHint: "### Short Answer\n[summary]\n\n### Full Answer\n[analysis]"
2882
+ }
2883
+ });
2884
+ ```
2885
+
2886
+ **Debugging Response Parsing:**
2887
+ ```typescript
2888
+ // When AI doesn't follow expected format, contractOutput will be {}
2889
+ // Check activity records to debug parsing issues
2890
+ const result = await gateway.call({
2891
+ messages: [...],
2892
+ expectedSchema: { /* schema */ }
2893
+ });
2894
+ // If parsing fails: content.contractOutput = {}
2895
+ // If parsing succeeds: content.contractOutput = { parsed: "data" }
2896
+ ```
2897
+
2898
+ #### Integration with Activity Tracking
2899
+
2900
+ Contract output parsing integrates seamlessly with `@x12i/activix` v5 (xronox-activitix):
2901
+
2902
+ - **Automatic Processing**: Parsing happens automatically when `expectedSchema` is provided
2903
+ - **Error Resilience**: Parsing failures don't break activity recording
2904
+ - **Performance**: Minimal overhead when schema is provided
2905
+ - **Backward Compatibility**: Existing calls work unchanged
2906
+
2907
+ #### Advanced Configuration
2908
+
2909
+ ```typescript
2910
+ // Detailed schema specification
2911
+ const schema = {
2912
+ description: "Structured analysis with multiple sections",
2913
+ formatHint: `### Executive Summary
2914
+ [summary]
2915
+
2916
+ ### Detailed Analysis
2917
+ [analysis]
2918
+
2919
+ ### Recommendations
2920
+ [recommendations]
2921
+
2922
+ ### Confidence Level
2923
+ [level]`
2924
+ };
2925
+
2926
+ const response = await gateway.invoke({
2927
+ aiRequestId: 'analysis-req-001',
2928
+ jobId: 'analysis-123',
2929
+ agentId: 'analyzer-bot',
2930
+ sessionId: 'sess-analyzer',
2931
+ instance: { instanceId: 'analyzer-1', type: 'gateway' },
2932
+ instructions: 'Analyze the provided data...',
2933
+ expectedSchema: schema,
2934
+ messages: [{ role: 'user', content: data }]
2935
+ });
2936
+ ```
2937
+
2938
+ ## API Reference
2939
+
2940
+ ### AIGateway
2941
+
2942
+ Enhanced gateway class with production-ready features.
2943
+
2944
+ #### Constructor
2945
+
2946
+ ```typescript
2947
+ const gateway = new AIGateway(config?: GatewayConfig);
2948
+ ```
2949
+
2950
+ **GatewayConfig Options:**
2951
+
2952
+ ```typescript
2953
+ interface GatewayConfig extends RouterConfig {
2954
+ usageTier?: UsageTier; // Default: 'tier-3'
2955
+ enableActivityTracking?: boolean; // Default: true
2956
+ enableUsageTracking?: boolean; // Default: true
2957
+ enableLogging?: boolean; // Default: true
2958
+ contentRegistryConfig?: any; // Content registry config for instruction key resolution
2959
+ logger?: LogsGateway; // Custom logger
2960
+ activityTracker?: Activix; // Custom Activix v5 instance (match collection names: ai-activities, bad-requests, skill-executions)
2961
+ packageName?: string; // Default: 'AI_GATEWAY'
2962
+ // ... all RouterConfig options
2963
+ }
2964
+ ```
2965
+
2966
+ #### Methods
2967
+
2968
+ - `register(provider: LLMProviderInterface): void` - Register a provider
2969
+ - `unregister(providerName: LLMProvider): void` - Unregister a provider
2970
+ - `getProvider(providerName: LLMProvider)` - Get a provider instance
2971
+ - `listProviders(): LLMProvider[]` - List all registered providers
2972
+ - `invoke(request: AIRequest): Promise<EnhancedLLMResponse>` - Invoke with structured output (requires `objectTypes`)
2973
+ - `invokeChat(request: ChatRequest): Promise<EnhancedLLMResponse>` - Invoke for chat/conversational requests (optional `objectTypes`)
2974
+ - `setDefaultProvider(provider: LLMProvider): void` - Set the default provider
2975
+ - `setFallbackChain(chain: LLMProvider[]): void` - Configure fallback chain
2976
+ - `addRequestInterceptor(interceptor: RequestInterceptor): void` - Add request interceptor
2977
+ - `addResponseInterceptor(interceptor: ResponseInterceptor): void` - Add response interceptor
2978
+ - `checkHealth(provider: LLMProvider): Promise<HealthCheckResult>` - Check provider health
2979
+ - `checkAllHealth(): Promise<Map<LLMProvider, HealthCheckResult>>` - Check all providers' health
2980
+ - `getRouter(): LLMProviderRouter` - Get underlying router instance
2981
+ - `getLogger(): LogsGateway` - Get logger instance
2982
+ - `setActivityManager(activityManager)` - Advanced/test hook to replace the internal activity manager; typical apps inject **`Activix` via `activityTracker`** in the constructor instead
2983
+ - `getContentRegistry(): ContentRegistry | undefined` - Get underlying content-registry instance (returns undefined if not enabled)
2984
+ - `resolveInstructionKey(key: string, variables?: Record<string, unknown>): Promise<string>` - Resolve instruction key with variables (without making LLM call)
2985
+ - `hasInstructionKey(key: string): Promise<boolean>` - Check if instruction key exists in content-registry
2986
+ - `getInstructionMetadata(key: string): Promise<InstructionMetadata | null>` - Get structured instruction metadata from content-registry (v1.6.9+)
2987
+ - `generateTaskTypeId(instructions: string): Promise<string>` - Generate taskTypeId from instructions (instance method)
2988
+ - `static generateTaskTypeId(instructions: string, options?: { contentRegistryConfig?, agentId? }): Promise<string>` - Generate taskTypeId from instructions (static method)
2989
+ - `optimizeInstructions(originalInstructions: string, options?: OptimizeInstructionsOptions): Promise<InstructionOptimizationResult>` - Optimize AI instructions using AI (v3.0.4+)
2990
+ - `testInstructions(instructions: string, testInput: string, expectedSchema?: Record<string, unknown>, options?: TestInstructionsOptions): Promise<TestInstructionsResult>` - Test instructions by running them and analyzing responses (v3.0.4+)
2991
+ - `generateRequestReport(request: AIRequest): Promise<RequestReport>` - Generate comprehensive request report with validation, examples, and structured text information (v3.0.6+)
2992
+
2993
+ ### ChatRequest and AIRequest
2994
+
2995
+ **⚠️ BREAKING CHANGE**: The old `EnhancedLLMRequest` type has been removed. Use `ChatRequest` or `AIRequest` instead.
2996
+
2997
+ **Two Request Types:**
2998
+
2999
+ 1. **`ChatRequest`** - For chat/conversational requests where `objectTypes` is optional
3000
+ 2. **`AIRequest`** - For structured output requests where `objectTypes` is required
3001
+
3002
+ #### StructuredTextSpec (v6.3.1+)
3003
+
3004
+ Interface for defining expected output schemas used in contract output parsing:
3005
+
3006
+ ```typescript
3007
+ interface StructuredTextSpec {
3008
+ description: string; // Human-readable description of expected output structure
3009
+ formatHint?: string; // Optional flex-md format specification for parsing guidance
3010
+ }
3011
+ ```
3012
+
3013
+ #### ChatRequest
3014
+
3015
+ Base request interface for chat/conversational requests. Use with `invokeChat()` method.
3016
+
3017
+ ```typescript
3018
+ interface ChatRequest extends Omit<LLMRequest, 'messages'> {
3019
+ jobId: string; // Required for context propagation and activity tracking
3020
+ agentId: string; // Required agent identifier
3021
+ instructions: string; // Required instructions (key or text)
3022
+ taskId?: string; // Optional task identifier
3023
+ taskTypeId?: string; // Optional task type ID (auto-generated from instructions MD5 if not provided)
3024
+ graphId?: string; // Optional graph identifier for graph execution context
3025
+ nodeId?: string; // Optional node identifier for graph execution context
3026
+ coreSkillId?: string; // Optional alternative node identifier for graph execution context
3027
+ prompt?: string; // Optional prompt key or text (resolved from content-registry if key, parsed as template if text)
3028
+ input?: string; // Optional input text
3029
+ context?: string; // Optional context text (template with workingMemory)
3030
+ workingMemory?: unknown; // Optional template variables for Rendrix
3031
+ expectedSchema?: StructuredTextSpec; // Optional schema for contract output parsing (v6.3.1+)
3032
+ inferenceType?: string; // Optional inference type for outputs library parsing
3033
+ parseOptions?: Record<string, unknown>; // Optional parse options for outputs library
3034
+ validateOutputSchema?: boolean; // Optional schema validation flag
3035
+ transformations?: ResponseTransformationConfig; // Optional response transformation hooks (v1.6.9+)
3036
+ config?: any; // LLM config (model, temperature, etc.)
3037
+ modelConfig?: ModelConfig; // Model configuration (modelConfig > config > gateway defaults)
3038
+ objectTypes?: Array<{ // Optional object types for structured output
3039
+ type: string;
3040
+ schema?: Record<string, unknown>;
3041
+ whenToUse: string;
3042
+ description?: string;
3043
+ }>;
3044
+ }
3045
+ ```
3046
+
3047
+ #### AIRequest
3048
+
3049
+ Request interface for structured output requests. **Extends `BaseLLMRequest`** with required `objectTypes`. Use with `invoke()` method.
3050
+
3051
+ ```typescript
3052
+ interface AIRequest extends BaseLLMRequest {
3053
+ // REQUIRED - main expected output structure
3054
+ // Can be string (standard type name) or object (custom type with schema)
3055
+ primaryObjectType: string | {
3056
+ type: string; // Required: object type identifier
3057
+ schema?: Record<string, unknown>; // Optional: JSON schema (required for custom types)
3058
+ whenToUse: string; // Required: guidance on when to use this type
3059
+ description?: string; // Optional: description
3060
+ };
3061
+
3062
+ expectedSchema?: StructuredTextSpec; // Optional schema for contract output parsing (v6.3.1+)
3063
+
3064
+ // Optional - alternative output structures
3065
+ // Each item can be string (standard type name) or object (custom type with schema)
3066
+ secondaryObjectTypes?: Array<string | {
3067
+ type: string;
3068
+ schema?: Record<string, unknown>;
3069
+ whenToUse: string;
3070
+ description?: string;
3071
+ }>;
3072
+
3073
+ // Output mode configuration (v3.0.5+)
3074
+ outputMode?: 'json' | 'structured-text' | 'two-step'; // Default: 'json'
3075
+ instructionFormat?: 'json-schema' | 'structured-text-spec'; // Default: 'json-schema'
3076
+ structuredTextSpec?: { // Required when instructionFormat is 'structured-text-spec'
3077
+ description: string; // Free-form description of output structure
3078
+ formatHint?: string; // Optional format hint (markdown, yaml, etc.)
3079
+ };
3080
+ conversionInstructions?: string; // Optional custom conversion instructions for two-step mode
3081
+
3082
+ // Optional graph execution fields (also available via BaseLLMRequest)
3083
+ masterSkillId?: string; // Optional graph identifier (maps to runContext.graphId when persisted)
3084
+ skillId?: string; // Optional node identifier (maps to runContext.nodeId when persisted)
3085
+ masterSkillActivityId?: string; // Optional parent skill activity ID (preserved in identity)
3086
+ }
3087
+ ```
3088
+
3089
+ **Note:** `AIRequest` extends `BaseLLMRequest`, which includes optional graph execution fields (`graphId`, `nodeId`, `coreSkillId`). For graph execution context, you can use either `masterSkillId`/`skillId` (AIRequest only) or `graphId`/`nodeId` (available on all request types). See [Graph Execution Support](./docs/GRAPH_EXECUTION_SUPPORT.md) for details.
3090
+
3091
+ **Key Differences:**
3092
+ - `ChatRequest`: `objectTypes` is **NOT supported** - use with `invokeChat()` for conversational requests
3093
+ - `AIRequest`: `objectTypes` is **REQUIRED** - use with `invoke()` for structured output
3094
+
3095
+ **⚠️ BREAKING CHANGE**: `invoke()` now requires `objectTypes`. For requests without structured output, use `invokeChat()` instead.
3096
+
3097
+ **Output Modes (v3.0.5+):**
3098
+
3099
+ The gateway supports three output modes, each with two instruction formats:
3100
+
3101
+ 1. **JSON Output Mode** (`outputMode: 'json'` - default):
3102
+ - Expects JSON response from LLM
3103
+ - Parses response as JSON object
3104
+ - Works with both instruction formats
3105
+
3106
+ 2. **Structured Text Output Mode** (`outputMode: 'structured-text'`):
3107
+ - Expects structured text (not JSON) - can be stories, narratives, free-form text
3108
+ - Does NOT parse as JSON
3109
+ - Content remains as string
3110
+ - Works with both instruction formats
3111
+ - **Auto-Extraction**: When `outputMode: 'structured-text'` is set and no explicit format (`flexMdFormat` or `primaryObjectType`) is provided, the gateway automatically extracts and validates the output format specification from the instruction template's "OUTPUT FORMAT" section (v3.3.3+)
3112
+
3113
+ 3. **Two-Step Conversion Mode** (`outputMode: 'two-step'`):
3114
+ - Step 1: Get structured text response
3115
+ - Step 2: Automatically convert structured text to JSON using internal conversion instructions
3116
+ - Returns JSON response (from step 2)
3117
+
3118
+ **Instruction Formats:**
3119
+
3120
+ 1. **JSON Schema Format** (`instructionFormat: 'json-schema'` - default):
3121
+ - Instructions include JSON schema embedded
3122
+ - Current behavior - schema is injected into instructions
3123
+
3124
+ 2. **Structured Text Spec Format** (`instructionFormat: 'structured-text-spec'`):
3125
+ - Instructions include free-form description of desired output structure
3126
+ - Example: "a story with character, setting, and conflict"
3127
+ - Requires `structuredTextSpec` field
3128
+
3129
+ **Minimal AIRequest Example (Custom Type):**
3130
+ ```typescript
3131
+ const response = await gateway.invoke({
3132
+ jobId: 'job-123',
3133
+ agentId: 'agent-1',
3134
+ instructions: 'professional-answer/general', // Content registry key
3135
+ input: 'What is the capital of France?',
3136
+ primaryObjectType: {
3137
+ type: 'professional-answer',
3138
+ whenToUse: 'For professional Q&A responses'
3139
+ // schema is optional
3140
+ },
3141
+ modelConfig: {
3142
+ model: 'gpt-4o',
3143
+ provider: 'openai'
3144
+ }
3145
+ });
3146
+ ```
3147
+
3148
+ **Using modelConfig for Model Selection:**
3149
+ ```typescript
3150
+ // modelConfig provides structured model configuration
3151
+ // Priority: modelConfig > config > gateway defaults
3152
+ const response = await gateway.invoke({
3153
+ jobId: 'job-123',
3154
+ agentId: 'agent-1',
3155
+ instructions: 'Analyze the data',
3156
+ primaryObjectType: 'sentiment-analysis',
3157
+ modelConfig: {
3158
+ model: 'gpt-4-turbo',
3159
+ provider: 'openai',
3160
+ temperature: 0.7,
3161
+ maxTokens: 2000,
3162
+ topP: 0.9
3163
+ }
3164
+ });
3165
+ ```
3166
+
3167
+ **modelConfig Overrides config:**
3168
+ ```typescript
3169
+ // modelConfig takes precedence over config
3170
+ const response = await gateway.invoke({
3171
+ jobId: 'job-123',
3172
+ agentId: 'agent-1',
3173
+ instructions: 'Process request',
3174
+ primaryObjectType: 'classification',
3175
+ config: {
3176
+ model: 'gpt-3.5-turbo', // This will be overridden
3177
+ temperature: 0.5 // This will be overridden
3178
+ },
3179
+ modelConfig: {
3180
+ model: 'gpt-4-turbo', // This takes precedence
3181
+ temperature: 0.9 // This takes precedence
3182
+ }
3183
+ });
3184
+ // Result: Uses gpt-4-turbo with temperature 0.9
3185
+ ```
3186
+
3187
+ **Standard Object Type Example (v3.0.6+):**
3188
+ ```typescript
3189
+ // Use standard type from @x12i/outputs-library by name
3190
+ const response = await gateway.invoke({
3191
+ jobId: 'job-123',
3192
+ agentId: 'agent-1',
3193
+ instructions: 'Analyze the sentiment of this text',
3194
+ input: 'I love this product!',
3195
+ primaryObjectType: 'sentiment-analysis', // Standard type name - no schema needed!
3196
+ config: {
3197
+ model: 'gpt-5-nano',
3198
+ provider: 'openai'
3199
+ }
3200
+ });
3201
+
3202
+ // Standard types include:
3203
+ // - Pre-defined schemas
3204
+ // - Few-shot examples (automatically used for better results)
3205
+ // - Validation instructions
3206
+ // - Structured text formatting guidelines
3207
+ ```
3208
+
3209
+ **Available Standard Types:**
3210
+ - `sentiment-analysis` - Sentiment analysis with confidence and reasoning
3211
+ - `classification` - General text classification
3212
+ - `extraction` - Structured data extraction
3213
+ - `question-answer` - Question answering
3214
+ - `email-extraction` - Email address extraction
3215
+ - `person-extraction` - Person information extraction
3216
+ - `location-extraction` - Location name extraction
3217
+ - `weather-report` - Weather information structure
3218
+ - And 10+ more types (see `@x12i/outputs-library` documentation)
3219
+
3220
+ **Note**: Standard types require content-registry to be configured (`contentRegistryConfig`). Custom types work without content-registry.
3221
+
3222
+ ### Output Format Validation
3223
+
3224
+ **Output Format Validation:**
3225
+
3226
+ The gateway validates output format specifications from instruction templates before sending to LLM. Instructions should include an "OUTPUT FORMAT" section that describes the expected output structure.
3227
+
3228
+ **Important: Communication Flow with LLM**
3229
+
3230
+ The gateway uses **flex-md (Markdown-based format)** for all LLM communication, while returning proper JavaScript objects to your code:
3231
+
3232
+ 1. **To LLM**: Instructions should include output format specifications - the gateway validates these but does not inject them
3233
+ 2. **From LLM**: LLM responds with flex-md (Markdown) containing structured data in fenced blocks
3234
+ 3. **Gateway Processing**: Gateway automatically extracts structured data from flex-md (Markdown) fenced blocks using flex-md SDK
3235
+ 4. **To Your Code**: Gateway returns parsed JavaScript objects in `response.parsedContent` - you always get clean objects, never raw Markdown
3236
+
3237
+ **Why flex-md (Markdown) Instead of Direct JSON?**
3238
+
3239
+ - **flex-md is Markdown-based**: We use flex-md format, which is built on Markdown, for all LLM communication
3240
+ - **Better LLM Compliance**: LLMs are more reliable at following Markdown format instructions than raw JSON
3241
+ - **Flexibility**: Allows LLMs to structure responses in Markdown while embedding structured data
3242
+ - **Robust Extraction**: Multiple fallback methods ensure structured data is extracted from Markdown even if format varies slightly
3243
+ - **Consistent API**: Your code always receives clean JavaScript objects, regardless of how the LLM formatted its Markdown response
3244
+
3245
+ **Output Format Validation Process:**
3246
+
3247
+ 1. Gateway extracts output format from instruction templates (if present)
3248
+ 2. Validates format specification using flex-md SDK
3249
+ 3. Checks compliance level against minimum required level (from `FLEX_MD_MIN_COMPLIANCE_LEVEL` environment variable)
3250
+ 4. If validation fails and minimum level > L0, request is rejected with error
3251
+ 5. If validation fails and minimum level is L0, request continues with warning
3252
+ 6. Output format information is included in response metadata and activity tracking
3253
+
3254
+ **Example:**
3255
+
3256
+ ```typescript
3257
+ const response = await gateway.invoke({
3258
+ jobId: 'job-123',
3259
+ agentId: 'agent-1',
3260
+ instructions: 'Classify the sentiment of this text',
3261
+ input: 'This product is amazing!',
3262
+ primaryObjectType: {
3263
+ type: 'classification',
3264
+ schema: {
3265
+ type: 'object',
3266
+ properties: {
3267
+ sentiment: {
3268
+ type: 'string',
3269
+ enum: ['positive', 'negative', 'neutral'],
3270
+ description: 'The sentiment classification'
3271
+ },
3272
+ confidence: {
3273
+ type: 'number',
3274
+ minimum: 0,
3275
+ maximum: 1,
3276
+ description: 'Confidence score'
3277
+ }
3278
+ },
3279
+ required: ['sentiment', 'confidence']
3280
+ },
3281
+ whenToUse: 'For sentiment classification',
3282
+ description: 'Simple sentiment classification'
3283
+ },
3284
+ config: { model: 'gpt-5-nano', provider: 'openai' }
3285
+ });
3286
+
3287
+ // What the LLM receives (system message - using flex-md/Markdown format):
3288
+ // OUTPUT FORMAT:
3289
+ // Output Format: classification
3290
+ // Simple sentiment classification
3291
+ //
3292
+ // Return structured data with the following structure:
3293
+ // [Schema with properties and descriptions]
3294
+ // IMPORTANT: Return your answer as structured data inside the markdown fenced block.
3295
+ //
3296
+ // What the LLM might return (flex-md/Markdown):
3297
+ // ```markdown
3298
+ // {"sentiment": "positive", "confidence": 0.95}
3299
+ // ```
3300
+ //
3301
+ // What you receive (response.parsedContent):
3302
+ // { sentiment: "positive", confidence: 0.95 } // Clean JavaScript object (not Markdown)
3303
+ ```
3304
+
3305
+ **Structured Data Extraction Process:**
3306
+
3307
+ The gateway uses flex-md (Markdown-based) for communication and extracts structured data from Markdown responses using multiple methods (in order of preference):
3308
+
3309
+ 1. **flex-md SDK**: Uses `extractFencedBlocks()` and `detectJsonAll()` to extract structured data from flex-md (Markdown) fenced blocks
3310
+ 2. **Manual Regex Fallback**: Extracts structured data from Markdown fenced blocks if flex-md SDK methods fail
3311
+ 3. **Response Repair (prod only)**: Uses a minimal in-gateway fallback repair (warn-logged) to recover JSON from malformed Markdown/prose. In `mode=debug`, failures throw immediately.
3312
+
3313
+ **Result**: You always receive clean JavaScript objects in `response.parsedContent`, never raw Markdown text.
3314
+
3315
+ **Output Format Validation Configuration:**
3316
+
3317
+ Set the minimum compliance level using the `FLEX_MD_MIN_COMPLIANCE_LEVEL` environment variable:
3318
+
3319
+ ```bash
3320
+ export FLEX_MD_MIN_COMPLIANCE_LEVEL=L0 # Default: allows anything
3321
+ export FLEX_MD_MIN_COMPLIANCE_LEVEL=L1 # Requires section headings
3322
+ export FLEX_MD_MIN_COMPLIANCE_LEVEL=L2 # Requires fenced block + sections
3323
+ export FLEX_MD_MIN_COMPLIANCE_LEVEL=L3 # Maximum strictness
3324
+ ```
3325
+
3326
+ - **L0 (default)**: No format validation required - allows any output format
3327
+ - **L1+**: Format specification required in instructions - validation errors will reject requests if format is missing or invalid
3328
+
3329
+ **Transforming Objects to Instruction Blocks:**
3330
+
3331
+ The gateway automatically transforms instruction blocks (from `instructions-blocks.json` or content-registry) into system message instructions. This allows you to configure instruction behavior declaratively without hardcoding.
3332
+
3333
+ **Supported Features:**
3334
+
3335
+ 1. **Nested Instruction Blocks**: Access nested properties using dot notation
3336
+ - `input.inputPrefix` - Prefix added before user input
3337
+ - `reinforcement.inputAlreadyProvided` - Reinforcement rules
3338
+
3339
+ 2. **Template Resolution**: Instruction blocks can contain template variables that get resolved with request context
3340
+ - `{{taskDescription}}` - Replaced with task description
3341
+ - `{{input}}` - Replaced with actual input value
3342
+
3343
+ 3. **Compliance Level Enforcement**: Use `flexMdComplianceLevel` (L0-L3) to automatically apply appropriate markdown enforcement rules
3344
+ - L0: Plain Markdown (minimum structure)
3345
+ - L1: Sectioned Markdown (headings required)
3346
+ - L2: Single-container Markdown (fenced block required) - **Default**
3347
+ - L3: Fully-typed Markdown (strict formatting rules)
3348
+
3349
+ 4. **Automatic Block Selection**: The gateway automatically selects the right instruction blocks based on:
3350
+ - Request type (AIRequest vs ChatRequest)
3351
+ - Presence of `primaryObjectType` or `flexMdFormat`
3352
+ - Compliance level specified
3353
+ - Agent ID and Task Type ID (for content-registry resolution)
3354
+
3355
+ **Example: Output Format in Instructions**
3356
+
3357
+ Instructions should include output format specifications:
3358
+
3359
+ ```markdown
3360
+ ## Task
3361
+ Classify the sentiment of the input text.
3362
+
3363
+ ## OUTPUT FORMAT
3364
+ Return your answer in the following structure:
3365
+
3366
+ Sentiment:
3367
+ - One of: positive, negative, neutral
3368
+
3369
+ Confidence:
3370
+ - A number between 0 and 1
3371
+
3372
+ Reasoning:
3373
+ - Brief explanation of your classification
3374
+ ```
3375
+
3376
+ The gateway will:
3377
+ 1. Extract and validate the output format from instructions
3378
+ 2. Check compliance level against `FLEX_MD_MIN_COMPLIANCE_LEVEL`
3379
+ 3. Include format information in response metadata (`response.metadata.outputFormat`)
3380
+ 4. Track format information in activity records
3381
+ // 2. Retrieves output.complianceLevels.L2 rules
3382
+ // 3. Applies markdownEnforcement, structuralRule, and immediateComplianceRule
3383
+ // 4. Adds these to the system message
3384
+ ```
3385
+
3386
+ **Example: Custom Instruction Blocks Structure**
3387
+
3388
+ The `instructions-blocks.json` file supports nested objects that get automatically resolved:
3389
+
3390
+ ```json
3391
+ {
3392
+ "input": {
3393
+ "inputPrefix": "INPUT DATA (process this now):",
3394
+ "inputRecognitionRule": "The user message below is the complete input to process."
3395
+ },
3396
+ "output": {
3397
+ "complianceLevels": {
3398
+ "L2": {
3399
+ "markdownEnforcement": "Return your entire answer inside a single ```markdown fenced block.",
3400
+ "structuralRule": "No conversational text before or after the fenced block.",
3401
+ "immediateComplianceRule": "Immediately return your answer inside a single ```markdown fenced block."
3402
+ }
3403
+ }
3404
+ },
3405
+ "reinforcement": {
3406
+ "inputAlreadyProvided": "The input has been provided in the user message. Process it immediately.",
3407
+ "noConversation": "You are not having a conversation."
3408
+ }
3409
+ }
3410
+ ```
3411
+
3412
+ **Resolution Priority:**
3413
+
3414
+ 1. **Content Registry** (highest): `instructions/{agentId}/{taskTypeId}/{blockPath}`
3415
+ 2. **Content Registry (agent-level)**: `instructions/{agentId}/{blockPath}`
3416
+ 3. **Default JSON File**: `src/defaults/instructions-blocks.json`
3417
+ 4. **Hardcoded Fallbacks**: Built-in defaults if file loading fails
3418
+
3419
+ **How Blocks Are Transformed:**
3420
+
3421
+ - **System Message**: Instruction blocks are combined with user instructions, OUTPUT FORMAT, and compliance rules
3422
+ - **User Message**: Input blocks (like `inputPrefix`) are prepended to user input
3423
+ - **Template Variables**: All `{{variable}}` placeholders are resolved using request context
3424
+ - **Nested Access**: Dot notation (e.g., `output.complianceLevels.L2`) automatically traverses nested objects
3425
+
3426
+ **AIRequest with Schema:**
3427
+ ```typescript
3428
+ const response = await gateway.invoke({
3429
+ jobId: 'job-123',
3430
+ agentId: 'agent-1',
3431
+ instructions: 'professional-answer/general',
3432
+ input: 'User question here',
3433
+ inferenceType: 'professional-answer',
3434
+ objectTypes: [
3435
+ {
3436
+ type: 'professional-answer',
3437
+ whenToUse: 'For professional Q&A responses',
3438
+ description: 'Professional answer JSON structure',
3439
+ schema: {
3440
+ type: 'object',
3441
+ properties: {
3442
+ answer: { type: 'string' },
3443
+ reasoning: { type: 'string' },
3444
+ citations: { type: 'array', items: { type: 'string' } }
3445
+ },
3446
+ required: ['answer']
3447
+ }
3448
+ }
3449
+ ],
3450
+ validateOutputSchema: true,
3451
+ config: {
3452
+ model: 'gpt-4o',
3453
+ provider: 'openai'
3454
+ }
3455
+ });
3456
+ ```
3457
+
3458
+ **Content Registry + AIRequest Pattern (Recommended):**
3459
+
3460
+ When using content-registry with AIRequest, the gateway automatically:
3461
+ 1. Resolves instruction keys from content-registry
3462
+ 2. Fetches instruction metadata (outputType, outputSchema)
3463
+ 3. Enforces JSON response format (`response_format: { type: 'json_object' }`)
3464
+ 4. Validates responses against schema if `validateOutputSchema: true`
3465
+
3466
+ ```typescript
3467
+ // Instruction stored in content-registry at: content/instructions/professional-answer/general.md
3468
+ // Metadata can include: outputType, outputSchema, validationRules
3469
+
3470
+ const response = await gateway.invoke({
3471
+ jobId: 'job-123',
3472
+ agentId: 'agent-1',
3473
+ instructions: 'professional-answer/general', // Content registry key
3474
+ input: 'User question',
3475
+ inferenceType: 'professional-answer',
3476
+ objectTypes: [
3477
+ {
3478
+ type: 'professional-answer',
3479
+ whenToUse: 'For professional Q&A responses',
3480
+ // Schema can come from instruction metadata or be provided here
3481
+ schema: instructionMetadata?.outputSchema || {
3482
+ type: 'object',
3483
+ properties: { answer: { type: 'string' } }
3484
+ }
3485
+ }
3486
+ ],
3487
+ validateOutputSchema: true,
3488
+ config: {
3489
+ model: 'gpt-4o',
3490
+ provider: 'openai'
3491
+ }
3492
+ });
3493
+ ```
3494
+
3495
+ **Output Modes Examples (v3.0.5+):**
3496
+
3497
+ **1. JSON Output Mode (Default):**
3498
+ ```typescript
3499
+ const response = await gateway.invoke({
3500
+ jobId: 'job-123',
3501
+ agentId: 'agent-1',
3502
+ instructions: 'Answer the question',
3503
+ prompt: 'What is the capital of France?',
3504
+ primaryObjectType: {
3505
+ type: 'question-answer',
3506
+ schema: {
3507
+ answer: { type: 'string' },
3508
+ confidence: { type: 'number' }
3509
+ },
3510
+ whenToUse: 'For Q&A responses'
3511
+ },
3512
+ outputMode: 'json', // Default - expects JSON response
3513
+ instructionFormat: 'json-schema', // Default - includes JSON schema in instructions
3514
+ config: { model: 'gpt-5-nano', provider: 'openai' }
3515
+ });
3516
+ // Response.parsedContent will be a JSON object
3517
+ ```
3518
+
3519
+ **2. Structured Text Output Mode:**
3520
+ ```typescript
3521
+ const response = await gateway.invoke({
3522
+ jobId: 'job-123',
3523
+ agentId: 'story-teller',
3524
+ instructions: 'Write a short story',
3525
+ prompt: 'Create a story about a brave knight',
3526
+ primaryObjectType: {
3527
+ type: 'story',
3528
+ whenToUse: 'For narrative content'
3529
+ },
3530
+ outputMode: 'structured-text', // Expects structured text, not JSON
3531
+ instructionFormat: 'structured-text-spec',
3532
+ structuredTextSpec: {
3533
+ description: 'A story with a character, setting, conflict, and resolution',
3534
+ formatHint: 'markdown' // Optional
3535
+ },
3536
+ config: { model: 'gpt-5-nano', provider: 'openai' }
3537
+ });
3538
+ // Response.content will be structured text (string)
3539
+ // Response.parsedContent will be undefined
3540
+ ```
3541
+
3542
+ **3. Two-Step Conversion Mode:**
3543
+ ```typescript
3544
+ const response = await gateway.invoke({
3545
+ jobId: 'job-123',
3546
+ agentId: 'story-converter',
3547
+ instructions: 'Write a detailed story',
3548
+ prompt: 'Create a story about space exploration',
3549
+ primaryObjectType: {
3550
+ type: 'story',
3551
+ schema: {
3552
+ character: { type: 'string' },
3553
+ setting: { type: 'string' },
3554
+ conflict: { type: 'string' },
3555
+ resolution: { type: 'string' }
3556
+ },
3557
+ whenToUse: 'For structured stories'
3558
+ },
3559
+ outputMode: 'two-step', // Step 1: Get structured text, Step 2: Convert to JSON
3560
+ instructionFormat: 'structured-text-spec',
3561
+ structuredTextSpec: {
3562
+ description: 'A story with character, setting, conflict, and resolution'
3563
+ },
3564
+ // Optional: Custom conversion instructions
3565
+ // conversionInstructions: 'Your custom conversion instructions here',
3566
+ config: { model: 'gpt-5-nano', provider: 'openai' }
3567
+ });
3568
+ // Step 1: LLM generates structured text
3569
+ // Step 2: Gateway automatically converts structured text to JSON
3570
+ // Response.parsedContent will be a JSON object with the story structure
3571
+ ```
3572
+
3573
+ **Instruction Format Combinations:**
3574
+
3575
+ - **JSON Schema + JSON Mode** (default): Instructions include JSON schema, expects JSON response
3576
+ - **JSON Schema + Structured Text Mode**: Instructions include JSON schema, but expects structured text (schema used as reference)
3577
+ - **Structured Text Spec + JSON Mode**: Instructions describe structure in free-form, expects JSON response
3578
+ - **Structured Text Spec + Structured Text Mode**: Instructions describe structure in free-form, expects structured text
3579
+ - **Structured Text Spec + Two-Step Mode**: Instructions describe structure in free-form, gets structured text first, then converts to JSON
3580
+
3581
+ **Troubleshooting**: If you see `"objectTypes is required for invoke()"`:
3582
+ 1. Verify you're calling `invoke()`, not `invokeChat()`
3583
+ 2. Ensure `primaryObjectType` is provided
3584
+ 3. Use `validateAIRequest()` from troubleshooting helper before sending
3585
+ 4. Enable `AI_GATEWAY_DEBUG_REQUEST=true` to see request at entry point
3586
+ 5. See [TROUBLESHOOTING.md](./TROUBLESHOOTING.md#airequest-validation-issues) for detailed solutions
3587
+
3588
+ #### Task Type ID (taskTypeId)
3589
+
3590
+ The `taskTypeId` field is used to identify recurring tasks that you perform repeatedly with the same instructions but different context, prompts, or inputs. This is especially useful when processing large batches of data (e.g., 15k records) with the same question/instruction pattern.
3591
+
3592
+ **Auto-Generation Behavior:**
3593
+
3594
+ If `taskTypeId` is not provided in the request, the gateway automatically generates it as an MD5 hash of the **pre-parsed instructions** (after resolving from content-registry if it's a key, but before template parsing with workingMemory). This ensures:
3595
+
3596
+ - **Consistency**: All requests with the same base instruction text get the same `taskTypeId`
3597
+ - **Automatic**: No need to manually calculate or provide hashes
3598
+ - **Stable**: The hash is based on the instruction text itself, not variable content
3599
+
3600
+ **Manual Generation (Helper Method):**
3601
+
3602
+ For maximum control and consistency, you can use the gateway's helper method to compute `taskTypeId`:
3603
+
3604
+ ```typescript
3605
+ // Static method (requires content-registry config if instructions is a key)
3606
+ const taskTypeId = await AIGateway.generateTaskTypeId('classification/basic', {
3607
+ contentRegistryConfig: { github: { ... } },
3608
+ agentId: 'agent-1'
3609
+ });
3610
+
3611
+ // Instance method (uses gateway's configured content-registry)
3612
+ const gateway = new AIGateway({ enableContentRegistry: true, ... });
3613
+ const taskTypeId = await gateway.generateTaskTypeId('classification/basic');
3614
+
3615
+ // Example: Processing 15k records with the same question
3616
+ const question = "What is the sentiment of this text?";
3617
+ const taskTypeId = await gateway.generateTaskTypeId(question);
3618
+
3619
+ // All 15k requests will have the same taskTypeId
3620
+ for (const record of records) {
3621
+ await gateway.invoke({
3622
+ jobId: `job-${record.id}`,
3623
+ agentId: 'sentiment-analyzer',
3624
+ instructions: question,
3625
+ input: record.text,
3626
+ taskTypeId, // Same for all records
3627
+ objectTypes: [ /* REQUIRED for invoke() */ ],
3628
+ config: { model: 'gpt-5-nano' }
3629
+ });
3630
+ }
3631
+ ```
3632
+
3633
+ **What Exactly is Hashed:**
3634
+
3635
+ The hash is computed from the **pre-parsed instructions**:
3636
+ 1. If `instructions` is a key (e.g., `'classification/basic'`), it's resolved from content-registry first
3637
+ 2. The resolved instruction text (or original text if not a key) is then hashed
3638
+ 3. Template parsing with `workingMemory` happens **after** hash generation, so variable values don't affect the hash
3639
+
3640
+ This ensures that:
3641
+ - Instruction keys resolve to the same hash across services
3642
+ - Template variables don't affect taskTypeId consistency
3643
+ - The same instruction text always produces the same taskTypeId
3644
+
3645
+ **How It Works:**
3646
+
3647
+ 1. If `taskTypeId` is provided → Uses the provided value
3648
+ 2. If `taskTypeId` is not provided → Gateway automatically:
3649
+ - Resolves instructions from content-registry if it's a key (without workingMemory)
3650
+ - Computes MD5 hash of the resolved instruction text
3651
+ - Uses the hash as `taskTypeId`
3652
+ - Logs the auto-generated value for debugging
3653
+
3654
+ **Use Cases:**
3655
+
3656
+ - **Batch Processing**: Process thousands of records with the same instruction/question
3657
+ - **Task Grouping**: Group related activities in activity tracking
3658
+ - **Content Registry**: Use `taskTypeId` in content-registry paths: `instructions/{agentId}/{taskTypeId}/{blockName}`
3659
+ - **Analytics**: Track performance and costs by task type
3660
+
3661
+ ### EnhancedLLMResponse
3662
+
3663
+ Extended response interface with comprehensive metadata.
3664
+
3665
+ ```typescript
3666
+ interface EnhancedLLMResponse extends LLMResponse {
3667
+ content: string; // Normalized content (always string)
3668
+ rawText?: string; // Original raw text before fixing (v3.0.4+)
3669
+ parsedContent?: TContent; // Parsed JSON content
3670
+ metadata: {
3671
+ jobId?: string;
3672
+ latencyMs: number;
3673
+ tokens: {
3674
+ prompt: number;
3675
+ completion: number;
3676
+ total: number;
3677
+ };
3678
+ // Output mode metadata (v3.0.5+)
3679
+ outputMode?: 'json' | 'structured-text' | 'two-step';
3680
+ instructionFormat?: 'json-schema' | 'structured-text-spec';
3681
+ isTwoStepConversion?: boolean;
3682
+ structuredTextStep?: 'first' | 'second';
3683
+ model?: string;
3684
+ provider?: string;
3685
+ cost?: number;
3686
+ // Response fixer metadata (v3.0.4+)
3687
+ responseWasFixed?: boolean; // Whether a response repair fallback was applied
3688
+ responseFixApplied?: string; // Which fix strategy was used
3689
+ responseFixConfidence?: number; // Confidence level (0-1)
3690
+ responseFixWarnings?: string[]; // Warnings about the fix
3691
+ [key: string]: any;
3692
+ };
3693
+ }
3694
+ ```
3695
+
3696
+ ## Advanced Usage
3697
+
3698
+ ### Custom Activix instance
3699
+
3700
+ Use the same **`collections`** names the gateway writes to (`ai-activities`, `skill-executions`, `bad-requests`) and the same **`statusValues`** mapping as in section 2.
3701
+
3702
+ ```typescript
3703
+ import { Activix } from '@x12i/activix';
3704
+
3705
+ const statusValues = {
3706
+ started: 'started',
3707
+ inProgress: 'in_progress',
3708
+ completed: 'success',
3709
+ failed: 'failed',
3710
+ timeout: 'timeout'
3711
+ };
3712
+
3713
+ const customTracker = new Activix({
3714
+ collections: [
3715
+ { name: 'ai-activities', statusValues },
3716
+ { name: 'skill-executions', statusValues },
3717
+ { name: 'bad-requests', statusValues }
3718
+ ]
3719
+ });
3720
+
3721
+ const gateway = new AIGateway({
3722
+ activityTracker: customTracker,
3723
+ enableActivityTracking: true
3724
+ });
3725
+ ```
3726
+
3727
+ ### Custom Logger
3728
+
3729
+ ```typescript
3730
+ import { createLogger } from 'logs-gateway';
3731
+
3732
+ const customLogger = createLogger(
3733
+ { packageName: 'MY_APP', envPrefix: 'MY_APP' },
3734
+ {
3735
+ logLevel: 'debug',
3736
+ logFormat: 'json',
3737
+ enableUnifiedLogger: true,
3738
+ unifiedLogger: {
3739
+ transports: { papertrail: true },
3740
+ service: 'ai-gateway',
3741
+ env: 'production'
3742
+ }
3743
+ }
3744
+ );
3745
+
3746
+ const gateway = new AIGateway({
3747
+ logger: customLogger,
3748
+ enableLogging: true
3749
+ });
3750
+ ```
3751
+
3752
+ ### Usage Consumption Monitoring
3753
+
3754
+ ```typescript
3755
+ import { calculateAggregateConsumption } from '@x12i/ai-gateway';
3756
+
3757
+ // After making requests, check aggregate consumption
3758
+ const aggregate = calculateAggregateConsumption('openai');
3759
+
3760
+ if (aggregate) {
3761
+ console.log(`Total requests: ${aggregate.totalRequests}`);
3762
+ console.log(`RPM consumption: ${aggregate.rpmConsumption.toFixed(2)}%`);
3763
+ console.log(`TPM consumption: ${aggregate.tpmConsumption.toFixed(2)}%`);
3764
+
3765
+ // Alert if approaching limits
3766
+ if (aggregate.rpmConsumption > 80) {
3767
+ console.warn('⚠️ Approaching RPM limit!');
3768
+ }
3769
+ }
3770
+ ```
3771
+
3772
+ ### Health Checks
3773
+
3774
+ ```typescript
3775
+ // Check single provider
3776
+ const health = await gateway.checkHealth('openai');
3777
+ console.log(health.healthy, health.latencyMs);
3778
+
3779
+ // Check all providers
3780
+ const allHealth = await gateway.checkAllHealth();
3781
+ for (const [provider, result] of allHealth) {
3782
+ console.log(`${provider}: ${result.healthy ? 'healthy' : 'unhealthy'}`);
3783
+ if (!result.healthy) {
3784
+ console.error(`Error: ${result.error}`);
3785
+ }
3786
+ }
3787
+ ```
3788
+
3789
+ ### Interceptors
3790
+
3791
+ ```typescript
3792
+ // Add request interceptor
3793
+ gateway.addRequestInterceptor(async (request, provider) => {
3794
+ console.log(`Request to ${provider}:`, request);
3795
+ // Modify request if needed
3796
+ return request;
3797
+ });
3798
+
3799
+ // Add response interceptor
3800
+ gateway.addResponseInterceptor(async (response, provider) => {
3801
+ console.log(`Response from ${provider}:`, response);
3802
+ // Modify response if needed
3803
+ return response;
3804
+ });
3805
+ ```
3806
+
3807
+ ## Integration Examples
3808
+
3809
+ ### With Agent Framework
3810
+
3811
+ ```typescript
3812
+ import { AIGateway } from '@x12i/ai-gateway';
3813
+
3814
+ const gateway = new AIGateway({
3815
+ defaultProvider: 'openai',
3816
+ usageTier: 'tier-3',
3817
+ enableActivityTracking: true
3818
+ });
3819
+
3820
+ // In your agent execution
3821
+ async function executeAgentTask(task: Task, jobId: string) {
3822
+ // Use invokeChat() for chat requests without structured output
3823
+ const response = await gateway.invokeChat({
3824
+ jobId, // Propagate jobId
3825
+ agentId: task.agentId,
3826
+ instructions: task.instructions,
3827
+ input: task.input,
3828
+ taskId: task.id,
3829
+ taskTypeId: task.typeId // Or use MD5 hash of question/instruction for consistent identification
3830
+ });
3831
+
3832
+ return {
3833
+ content: response.content,
3834
+ metadata: response.metadata // Includes jobId, latency, tokens, etc.
3835
+ };
3836
+ }
3837
+ ```
3838
+
3839
+ ### With x-models for Smart Selection
3840
+
3841
+ ```typescript
3842
+ import { AIGateway } from '@x12i/ai-gateway';
3843
+ import { registry } from '@x12i/x-models';
3844
+
3845
+ // Select optimal model
3846
+ const model = registry.selectModel({
3847
+ strategy: 'cheapest',
3848
+ capabilities: { toolCalling: true },
3849
+ minContext: 16000
3850
+ });
3851
+
3852
+ if (model) {
3853
+ const gateway = new AIGateway({
3854
+ defaultProvider: model.provider
3855
+ });
3856
+
3857
+ const response = await gateway.invokeChat({
3858
+ jobId: 'job-123',
3859
+ agentId: 'agent-1',
3860
+ instructions: 'You are a helpful assistant',
3861
+ input: 'Hello!',
3862
+ config: {
3863
+ model: model.id
3864
+ }
3865
+ });
3866
+ }
3867
+ ```
3868
+
3869
+ ### Unified Reasoning API (OpenRouter Reasoning)
3870
+
3871
+ The gateway supports unified reasoning/thoughts configuration through the OpenRouter Reasoning API. This provides normalized reasoning capabilities across all providers.
3872
+
3873
+ #### Request Configuration
3874
+
3875
+ ```typescript
3876
+ const response = await gateway.invokeChat({
3877
+ jobId: 'reasoning-example',
3878
+ agentId: 'agent-1',
3879
+ instructions: 'Solve this step-by-step',
3880
+ input: 'What is 15 * 23?',
3881
+ config: {
3882
+ provider: 'openai',
3883
+ model: 'gpt-5-nano',
3884
+ // Unified reasoning configuration
3885
+ reasoning: {
3886
+ effort: 'high', // 'none' | 'low' | 'medium' | 'high' | 'xhigh'
3887
+ visibility: 'trace', // 'none' | 'summary' | 'trace'
3888
+ onUnsupported: 'downgrade' // 'error' | 'downgrade' | 'ignore'
3889
+ }
3890
+ }
3891
+ });
3892
+ ```
3893
+
3894
+ **Reasoning Configuration Options:**
3895
+
3896
+ - **`effort`**: Controls reasoning depth
3897
+ - `'none'`: No reasoning (default)
3898
+ - `'low'`: Basic reasoning
3899
+ - `'medium'`: Moderate reasoning
3900
+ - `'high'`: Deep reasoning
3901
+ - `'xhigh'`: Maximum reasoning depth
3902
+
3903
+ - **`visibility`**: Controls what reasoning data is returned
3904
+ - `'none'`: No reasoning in response (default)
3905
+ - `'summary'`: Human-readable reasoning summary
3906
+ - `'trace'`: Detailed reasoning trace
3907
+
3908
+ - **`onUnsupported`**: Behavior when provider doesn't support requested reasoning
3909
+ - `'error'`: Throw error (default)
3910
+ - `'downgrade'`: Automatically downgrade to supported level
3911
+ - `'ignore'`: Proceed without reasoning
3912
+
3913
+ #### Response Structure
3914
+
3915
+ ```typescript
3916
+ interface EnhancedLLMResponse {
3917
+ content: string;
3918
+ // Unified reasoning response object (not array)
3919
+ reasoning?: {
3920
+ requested: {
3921
+ effort?: 'none' | 'low' | 'medium' | 'high' | 'xhigh';
3922
+ visibility?: 'none' | 'summary' | 'trace';
3923
+ };
3924
+ applied: {
3925
+ effort?: 'none' | 'low' | 'medium' | 'high';
3926
+ visibility?: 'none' | 'summary' | 'trace';
3927
+ };
3928
+ artifacts?: {
3929
+ summary?: { text: string; format: string };
3930
+ trace?: { chunks: any[] };
3931
+ encrypted?: Array<{
3932
+ id: string;
3933
+ format: string;
3934
+ type?: string;
3935
+ [key: string]: any;
3936
+ }>;
3937
+ };
3938
+ availability?: {
3939
+ supportsEffort?: boolean;
3940
+ supportsSummary?: boolean;
3941
+ supportsTrace?: boolean;
3942
+ supportsEncrypted?: boolean;
3943
+ };
3944
+ warnings?: Array<{
3945
+ code: 'EFFORT_NORMALIZED' | 'VISIBILITY_DOWNGRADED' | 'EFFORT_IGNORED' | 'REASONING_UNSUPPORTED';
3946
+ message: string;
3947
+ }>;
3948
+ };
3949
+ // ... other response fields
3950
+ }
3951
+ ```
3952
+
3953
+ #### Usage Examples
3954
+
3955
+ **Basic Reasoning Request:**
3956
+ ```typescript
3957
+ const response = await gateway.invokeChat({
3958
+ jobId: 'basic-reasoning',
3959
+ agentId: 'agent-1',
3960
+ instructions: 'Explain your reasoning step by step',
3961
+ input: 'Why is the sky blue?',
3962
+ config: {
3963
+ provider: 'openai',
3964
+ model: 'gpt-5-nano',
3965
+ reasoning: {
3966
+ effort: 'medium',
3967
+ visibility: 'summary'
3968
+ }
3969
+ }
3970
+ });
3971
+
3972
+ // Access reasoning data
3973
+ console.log('Response:', response.content);
3974
+ console.log('Reasoning effort applied:', response.reasoning?.applied.effort);
3975
+ console.log('Reasoning summary:', response.reasoning?.artifacts?.summary?.text);
3976
+ ```
3977
+
3978
+ **Encrypted Reasoning Continuity:**
3979
+ ```typescript
3980
+ // First request with encrypted trace
3981
+ const response1 = await gateway.invokeChat({
3982
+ jobId: 'continuity-1',
3983
+ agentId: 'agent-1',
3984
+ instructions: 'Solve this complex problem',
3985
+ input: 'Calculate the trajectory of a satellite',
3986
+ config: {
3987
+ provider: 'openai',
3988
+ model: 'o1-preview', // OpenAI o-series models support encrypted traces
3989
+ reasoning: {
3990
+ effort: 'xhigh',
3991
+ visibility: 'trace'
3992
+ }
3993
+ }
3994
+ });
3995
+
3996
+ // Check for encrypted artifacts
3997
+ const encryptedArtifacts = response1.reasoning?.artifacts?.encrypted;
3998
+ if (encryptedArtifacts && encryptedArtifacts.length > 0) {
3999
+ console.log(`Found ${encryptedArtifacts.length} encrypted reasoning artifacts`);
4000
+
4001
+ // Second request with continuity (encrypted artifacts would be passed back)
4002
+ // Note: Continuity input format depends on provider-specific implementation
4003
+ const response2 = await gateway.invokeChat({
4004
+ jobId: 'continuity-2',
4005
+ agentId: 'agent-1',
4006
+ instructions: 'Continue from previous reasoning',
4007
+ input: 'Now apply this to Mars orbit',
4008
+ config: {
4009
+ provider: 'openai',
4010
+ model: 'o1-preview',
4011
+ reasoning: {
4012
+ effort: 'xhigh',
4013
+ visibility: 'trace'
4014
+ }
4015
+ // reasoningContinuity: encryptedArtifacts // Format depends on provider
4016
+ }
4017
+ });
4018
+ }
4019
+ ```
4020
+
4021
+ **Provider Support Notes:**
4022
+
4023
+ - **Encrypted reasoning traces**: Currently supported for `openai/o*` models via OpenRouter
4024
+ - **Reasoning effort levels**: Varies by provider and model capabilities
4025
+ - **Automatic fallback**: When `onUnsupported: 'downgrade'` is set, the gateway automatically adjusts to supported reasoning levels
4026
+
4027
+ **Model Recommendations for Reasoning:**
4028
+
4029
+ - **High reasoning**: `openai/o1-preview`, `openai/o1-mini`, `openai/gpt-4o` with reasoning config
4030
+ - **Standard models**: `openai/gpt-5-nano`, `anthropic/claude-3.5-sonnet` (reasoning support varies)
4031
+
4032
+ ## Troubleshooting
4033
+
4034
+ ### Quick Reference
4035
+
4036
+ **📚 Documentation** (included in npm package - accessible after `npm install @x12i/ai-gateway`):
4037
+ - **[TROUBLESHOOTING.md](./TROUBLESHOOTING.md)** - Comprehensive troubleshooting guide with test cases for all common issues
4038
+ - **[TROUBLESHOOTING_TOOLBOX.md](./TROUBLESHOOTING_TOOLBOX.md)** - Diagnostic utilities API and usage examples
4039
+ - **[INTEGRATION_GUIDANCE.md](./INTEGRATION_GUIDANCE.md)** - Official patterns, integration examples, and debugging steps
4040
+
4041
+ **Location after install**: `node_modules/@x12i/ai-gateway/TROUBLESHOOTING.md` (and other .md files)
4042
+
4043
+ **🔧 Troubleshooting Helper Functions** (exported from SDK - use immediately, no file reading needed):
4044
+ ```typescript
4045
+ import {
4046
+ validateAIRequest, // Validate request before sending
4047
+ diagnoseRequest, // Get comprehensive diagnostics
4048
+ formatDiagnostic, // Format diagnostics as text
4049
+ assertValidAIRequest, // Throw if invalid (for testing)
4050
+ extractJSON, // Extract JSON from text/markdown
4051
+ supportsJSONMode // Check provider JSON mode support
4052
+ } from '@x12i/ai-gateway';
4053
+ ```
4054
+
4055
+ **See**: [TROUBLESHOOTING_TOOLBOX.md](./TROUBLESHOOTING_TOOLBOX.md) for complete API documentation.
4056
+
4057
+ ### Common Issue: "objectTypes is required for invoke()"
4058
+
4059
+ **Error**: `Request validation failed: objectTypes is required for invoke()`
4060
+
4061
+ **Quick Fix Checklist**:
4062
+
4063
+ 1. ✅ **Verify you're calling `invoke()`, not `invokeChat()`**
4064
+ ```typescript
4065
+ // ✅ Correct for structured output
4066
+ await gateway.invoke({
4067
+ jobId: 'job-123',
4068
+ agentId: 'agent-1',
4069
+ instructions: 'professional-answer/general',
4070
+ input: 'User question',
4071
+ objectTypes: [
4072
+ {
4073
+ type: 'professional-answer',
4074
+ whenToUse: 'For professional Q&A responses'
4075
+ }
4076
+ ],
4077
+ config: { model: 'gpt-4o', provider: 'openai' }
4078
+ });
4079
+ ```
4080
+
4081
+ 2. ✅ **Use troubleshooting helper to validate before sending**
4082
+ ```typescript
4083
+ import { validateAIRequest, assertValidAIRequest } from '@x12i/ai-gateway';
4084
+
4085
+ // Validate before sending
4086
+ assertValidAIRequest(request);
4087
+ await gateway.invoke(request);
4088
+ ```
4089
+
4090
+ 3. ✅ **Enable debug logging to see request at entry point**
4091
+ ```bash
4092
+ export AI_GATEWAY_DEBUG_REQUEST=true
4093
+ npm run your-test
4094
+ ```
4095
+
4096
+ **See**: [TROUBLESHOOTING.md](./TROUBLESHOOTING.md#airequest-validation-issues) for detailed solutions and test cases.
4097
+
4098
+ ### Using Troubleshooting Tools
4099
+
4100
+ ```typescript
4101
+ import {
4102
+ validateAIRequest,
4103
+ diagnoseRequest,
4104
+ formatDiagnostic,
4105
+ supportsJSONMode
4106
+ } from '@x12i/ai-gateway';
4107
+
4108
+ // Validate request
4109
+ const validation = validateAIRequest(request);
4110
+ if (!validation.valid) {
4111
+ console.error('Errors:', validation.errors);
4112
+ }
4113
+
4114
+ // Get diagnostics
4115
+ const diagnostic = diagnoseRequest(request);
4116
+ console.log(formatDiagnostic(diagnostic));
4117
+ ```
4118
+
4119
+ **See**: [TROUBLESHOOTING_TOOLBOX.md](./TROUBLESHOOTING_TOOLBOX.md) for complete API.
4120
+
4121
+ ## Error Handling
4122
+
4123
+ The gateway throws specific error types:
4124
+
4125
+ - `ProviderNotFoundError`: When a requested provider is not registered
4126
+ - `FallbackExhaustedError`: When all providers in the fallback chain have failed
4127
+
4128
+ ```typescript
4129
+ import { ProviderNotFoundError, FallbackExhaustedError } from '@x12i/ai-gateway';
4130
+
4131
+ try {
4132
+ const response = await gateway.invokeChat({
4133
+ aiRequestId: 'chat-req-001',
4134
+ sessionId: 'sess-1',
4135
+ instance: { instanceId: 'gw-1', type: 'gateway' },
4136
+ jobId: 'job-123',
4137
+ agentId: 'agent-1',
4138
+ instructions: 'You are a helpful assistant',
4139
+ input: 'Hello!'
4140
+ });
4141
+ } catch (error) {
4142
+ if (error instanceof FallbackExhaustedError) {
4143
+ console.error('All providers failed:', error.attempts);
4144
+ // With enableActivityTracking, success/failure is recorded automatically inside the gateway
4145
+ } else if (error instanceof ProviderNotFoundError) {
4146
+ console.error('Provider not found:', error.message);
4147
+ }
4148
+ }
4149
+ ```
4150
+
4151
+ ## Package Integrations
4152
+
4153
+ This package integrates with:
4154
+
4155
+ - **@x12i/ai-providers-router**: Core routing functionality
4156
+ - **@xronoces/content-registry**: Instruction key resolution and content management (optional)
4157
+ - **@x12i/x-models**: Usage tier tracking and model metadata
4158
+ - **@x12i/activix** v5 (xronox-activitix): Activity logging and tracking (`^5.0.1` in this package)
4159
+ - **logs-gateway**: Structured logging with correlation
4160
+ - **nx-config2**: Configuration management (via dependencies)
4161
+
4162
+ ## Related Packages Status
4163
+
4164
+ ### @x12i/ai-gateway
4165
+
4166
+ **Fixes:**
4167
+ - ✅ "[object Object]" bug when router returns objects
4168
+ - ✅ Enhanced `extractRawText()` with multiple safety layers
4169
+
4170
+ **Features:**
4171
+ - ✅ Automatic recovery if "[object Object]" is detected
4172
+ - ✅ Original object preserved in `parsedContent`
4173
+ - ✅ Normalized content as JSON string (backward compatible)
4174
+ - ✅ `metadata.contentType` for type indication
4175
+ - ✅ Content-registry integration for instruction keys
4176
+ - ✅ Centralized activity tracking configuration (v2.0.5+)
4177
+ - ✅ Activity lifecycle verification and enhanced logging (v2.0.5+)
4178
+
4179
+ ### @x12i/ai-providers-router
4180
+
4181
+ **Fixes:**
4182
+ - ✅ Dynamic import registration issue
4183
+
4184
+ **Features:**
4185
+ - ✅ Batch API support
4186
+ - ✅ Enhanced provider management
4187
+
4188
+ ### Integration
4189
+
4190
+ The gateway fix handles cases where the router returns structured data (objects/arrays) instead of plain strings, ensuring:
4191
+
4192
+ - **Backward compatibility** — `content` is always a string
4193
+ - **Data preservation** — original object in `parsedContent`
4194
+ - **Type safety** — `metadata.contentType` indicates the type
4195
+
4196
+ ### Installation
4197
+
4198
+ ```bash
4199
+ npm install @x12i/ai-gateway
4200
+ npm install @x12i/ai-providers-router
4201
+ ```
4202
+
4203
+ **Package compatibility:**
4204
+ - Router handles dynamic imports and batch operations
4205
+ - Gateway handles object responses from the router
4206
+ - Gateway provides content normalization and type safety
4207
+
4208
+ ## Testing
4209
+
4210
+ ### Activity Tracking Tests
4211
+
4212
+ **Standalone Test** (Recommended):
4213
+ ```bash
4214
+ npm run test:activities:standalone
4215
+ ```
4216
+
4217
+ This test bypasses config parsing issues and tests activity lifecycle end-to-end:
4218
+ - ✅ Gateway initialization with activity tracking enabled
4219
+ - ✅ Activity creation with `jobId`, `agentId`, `taskId`
4220
+ - ✅ LLM invocation through gateway
4221
+ - ✅ Activity status update from "started" to "success"
4222
+ - ✅ Database persistence of activity records
4223
+
4224
+ **Standard Test Suite**:
4225
+ ```bash
4226
+ npm test
4227
+ ```
4228
+
4229
+ **Note**: Some tests may be blocked by `nx-config2` Postgres parsing issue. See `.reports/new/nx-config2-skip-config-sections-feature-request.md` for details.
4230
+
4231
+ **See**: `.tests/TESTING_GUIDE.md` for complete testing documentation.
4232
+
4233
+ ## Known Issues
4234
+
4235
+ ### nx-config2 Postgres Parsing Issue
4236
+
4237
+ **Status**: 🟡 **FEATURE REQUEST OPEN** - Not a bug in ai-gateway
4238
+
4239
+ **Issue**: `nx-config2` attempts to parse Postgres configuration even when Postgres is not used, causing test failures.
4240
+
4241
+ **Workaround**: Use standalone test (`npm run test:activities:standalone`) which bypasses `nx-config2`.
4242
+
4243
+ **Feature Request**: See `.reports/new/nx-config2-skip-config-sections-feature-request.md` for complete details. Requesting:
4244
+ 1. Fix/remove broken Postgres port parsing
4245
+ 2. Add selective config loading by sections (from `.env`) and groups (from config map)
4246
+ 3. Ensure ai-gateway and activix configs are covered in DEFAULT_CONFIG_MAP
4247
+
4248
+ ## Requirements
4249
+
4250
+ - Node.js >= 18.0.0
4251
+ - TypeScript >= 5.0.0 (if using TypeScript)
4252
+
4253
+ ## License
4254
+
4255
+ ISC
4256
+
4257
+ ## Repository
4258
+
4259
+ [GitHub](https://github.com/x12i/ai-gateway)