@x12i/ai-gateway 7.9.2 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +107 -94
  2. package/dist/activity-manager.d.ts +3 -1
  3. package/dist/activity-manager.js +48 -17
  4. package/dist/content-normalizer/content-normalizer.d.ts +2 -1
  5. package/dist/content-normalizer/content-normalizer.js +5 -5
  6. package/dist/flex-md-loader.d.ts +2 -1
  7. package/dist/flex-md-loader.js +18 -15
  8. package/dist/gateway-config.d.ts +4 -4
  9. package/dist/gateway-config.js +21 -25
  10. package/dist/gateway-conversion.js +2 -2
  11. package/dist/gateway-log-meta.d.ts +15 -0
  12. package/dist/gateway-log-meta.js +36 -0
  13. package/dist/gateway-memory.js +6 -6
  14. package/dist/gateway-messages.js +4 -4
  15. package/dist/gateway-meta.d.ts +3 -1
  16. package/dist/gateway-meta.js +9 -2
  17. package/dist/gateway-utils.js +9 -9
  18. package/dist/gateway-validation.js +9 -0
  19. package/dist/gateway.js +32 -33
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.js +2 -1
  22. package/dist/instruction-optimizer.d.ts +6 -1
  23. package/dist/instruction-optimizer.js +10 -3
  24. package/dist/instructions-parser.d.ts +2 -1
  25. package/dist/instructions-parser.js +2 -2
  26. package/dist/logger-factory.js +12 -1
  27. package/dist/message-builder.js +19 -19
  28. package/dist/request-report-generator.js +1 -1
  29. package/dist/template-parser.d.ts +3 -1
  30. package/dist/template-parser.js +3 -2
  31. package/dist/troubleshooting-helper.d.ts +1 -1
  32. package/dist/troubleshooting-helper.js +2 -2
  33. package/dist/types.d.ts +16 -10
  34. package/dist-cjs/activity-manager.cjs +48 -17
  35. package/dist-cjs/activity-manager.d.ts +3 -1
  36. package/dist-cjs/content-normalizer/content-normalizer.cjs +5 -5
  37. package/dist-cjs/content-normalizer/content-normalizer.d.ts +2 -1
  38. package/dist-cjs/flex-md-loader.cjs +18 -15
  39. package/dist-cjs/flex-md-loader.d.ts +2 -1
  40. package/dist-cjs/gateway-config.cjs +21 -25
  41. package/dist-cjs/gateway-config.d.ts +4 -4
  42. package/dist-cjs/gateway-conversion.cjs +2 -2
  43. package/dist-cjs/gateway-log-meta.cjs +41 -0
  44. package/dist-cjs/gateway-log-meta.d.ts +15 -0
  45. package/dist-cjs/gateway-memory.cjs +6 -6
  46. package/dist-cjs/gateway-messages.cjs +4 -4
  47. package/dist-cjs/gateway-meta.cjs +9 -2
  48. package/dist-cjs/gateway-meta.d.ts +3 -1
  49. package/dist-cjs/gateway-utils.cjs +9 -9
  50. package/dist-cjs/gateway-validation.cjs +9 -0
  51. package/dist-cjs/gateway.cjs +31 -32
  52. package/dist-cjs/index.cjs +6 -1
  53. package/dist-cjs/index.d.ts +3 -2
  54. package/dist-cjs/instruction-optimizer.cjs +10 -3
  55. package/dist-cjs/instruction-optimizer.d.ts +6 -1
  56. package/dist-cjs/instructions-parser.cjs +2 -2
  57. package/dist-cjs/instructions-parser.d.ts +2 -1
  58. package/dist-cjs/logger-factory.cjs +12 -1
  59. package/dist-cjs/message-builder.cjs +19 -19
  60. package/dist-cjs/request-report-generator.cjs +1 -1
  61. package/dist-cjs/template-parser.cjs +3 -2
  62. package/dist-cjs/template-parser.d.ts +3 -1
  63. package/dist-cjs/troubleshooting-helper.cjs +2 -2
  64. package/dist-cjs/troubleshooting-helper.d.ts +1 -1
  65. package/dist-cjs/types.d.ts +16 -10
  66. package/package.json +3 -3
package/README.md CHANGED
@@ -1,14 +1,24 @@
1
1
  # @x12i/ai-gateway
2
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`.
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` (see `package.json` for pinned versions), and **`@x12i/logxer`** for structured logging.
4
+
5
+ ## Mandatory runtime identity (v9+)
6
+
7
+ Every **`invoke`** / **`invokeChat`** request **must** include **`identity`**: the full runtime envelope from the **upstream client** (not invented inside the gateway).
8
+
9
+ - **`identity.jobId`** and **`identity.taskId`** are **only** taken from that upstream object. The gateway **never** generates, rewrites, or back-fills them from deprecated top-level `jobId` / `taskId` fields.
10
+ - If `identity` is missing or `jobId` / `taskId` are empty, the gateway logs **`warn`** via Logxer (`missingRuntimeIdentityObject` / `missingUpstreamIdentityFields`) when a logger is configured, and still attaches the merged envelope so the rest of the pipeline can proceed.
11
+ - The same merged object is **`request.identity`**, forwarded to the router, returned as **`response.metadata.identity`**, and persisted on Activix as **`runContext`** (same reference as `request.identity`).
12
+
13
+ See [Identity contract](./docs/IDENTITY_OBJECT_CONTRACT.md) and [Logger initialization](./docs/LOGGER_INITIALIZATION.md).
4
14
 
5
15
  ## Features
6
16
 
7
17
  - **🔀 Provider Routing**: Dynamic provider registration and automatic routing with fallback support
8
18
  - **📊 Context Propagation**: `aiRequestId` and identity propagation for distributed tracing (see [Identity contract](./docs/IDENTITY_OBJECT_CONTRACT.md))
9
19
  - **⚡ 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
20
+ - **📈 Activity Tracking**: Comprehensive activity logging via `@x12i/activix` v6 (xronox-activitix), fixed Mongo collections `ai-activities` / `bad-requests`, validated root-level **`outer` / `inner`** I/O plus **`runContext`** for Activix 6
21
+ - **📝 Structured Logging**: Production-ready logging via **`@x12i/logxer`** (LogMeta `jobId` / `sessionId` / `correlationId`, optional `debugKind`, default `runtimeIdentity` from env) with diagnostic tracing for instruction resolution and propagation debugging
12
22
  - **📋 Rich Metadata**: Detailed execution metadata (latency, tokens, model, cost, `aiRequestId`, `identity`)
13
23
  - **🏥 Health Checks**: Monitor provider health and availability
14
24
  - **🔄 Request/Response Interceptors**: Modify requests and responses
@@ -122,24 +132,25 @@ gateway.register(new GrokProvider({
122
132
  apiKey: process.env.GROK_API_KEY
123
133
  }));
124
134
 
125
- // Invoke with context
135
+ // Invoke with mandatory runtime identity (upstream job/task correlation)
126
136
  const response = await gateway.invoke({
127
- messages: [{ role: 'user', content: 'Hello!' }],
128
- jobId: 'job-123', // Context propagation
137
+ aiRequestId: 'call-001',
129
138
  agentId: 'agent-456',
130
- taskId: 'task-789'
139
+ instructions: 'Reply briefly.',
140
+ identity: {
141
+ sessionId: 'run-1',
142
+ instance: { instanceId: 'agent-456', type: 'ai-reasoner' },
143
+ aiRequestId: 'call-001',
144
+ jobId: 'job-123',
145
+ taskId: 'task-789',
146
+ agentId: 'agent-456'
147
+ },
148
+ workingMemory: { input: 'Hello!' }
149
+ // … primaryObjectType / flexMdFormat / messages as required by your request type
131
150
  });
132
151
 
133
- // Response includes comprehensive metadata
152
+ // Response includes comprehensive metadata (including `identity`)
134
153
  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
154
  ```
144
155
 
145
156
  ### Using Base Router (Direct Access)
@@ -184,33 +195,26 @@ The AI Gateway can be configured in multiple ways:
184
195
  - `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
196
  4. **Request-Level** - Override gateway defaults per request
186
197
 
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
198
+ ### 1. Logging Configuration (@x12i/logxer)
195
199
 
196
- **📚 See [Logger Initialization Guide](./docs/LOGGER_INITIALIZATION.md) for complete instructions.**
200
+ **Logger initialization:** The gateway uses **`@x12i/logxer`**. Pass a **`Logxer`** from **`createLogxer`**, or omit `logger` and let the gateway build a default (still Logxer-based). See **[Logger Initialization Guide](./docs/LOGGER_INITIALIZATION.md)** for mandatory **`identity`** on requests and **`LogMeta`** / **`debugKind`** usage.
197
201
 
198
202
  **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).
203
+ - Gateway constructor: `enableLogging`, `packageName`, `logger`
204
+ - Environment variables: **`{PREFIX}_LOGS_LEVEL`** (canonical per-package level), legacy **`{PREFIX}_LOG_LEVEL`**, plus **`{PREFIX}_LOG_FORMAT`**, file sinks, unified logger, etc., as documented for **`@x12i/logxer`**.
201
205
 
202
206
  **How to configure:**
203
207
 
204
208
  ```typescript
205
209
  import { AIGateway } from '@x12i/ai-gateway';
206
- import { createLogger } from 'logs-gateway';
210
+ import { createLogxer } from '@x12i/logxer';
207
211
 
208
212
  // Create logger once at application startup
209
- const logger = createLogger(
210
- { packageName: 'MY_APP', envPrefix: 'MY_APP' },
213
+ const logger = createLogxer(
214
+ { packageName: 'MY_APP', envPrefix: 'MY_APP', debugNamespace: 'my-app' },
211
215
  {
212
216
  logLevel: 'info', // verbose|debug|info|warn|error
213
- logFormat: 'json', // text|json|yaml
217
+ logFormat: 'json', // text|json|yaml|table
214
218
  logToFile: true,
215
219
  logFilePath: '/var/log/app.log',
216
220
  enableUnifiedLogger: true,
@@ -218,27 +222,31 @@ const logger = createLogger(
218
222
  transports: { papertrail: true },
219
223
  service: 'my-app',
220
224
  env: 'production'
225
+ },
226
+ runtimeIdentity: {
227
+ service: 'my-app',
228
+ env: process.env.NODE_ENV,
229
+ version: process.env.npm_package_version
221
230
  }
222
231
  }
223
232
  );
224
233
 
225
- // Pass the SAME logger instance to gateway (REQUIRED)
226
234
  const gateway = new AIGateway({
227
235
  enableLogging: true,
228
- logger: logger, // REQUIRED: Use your project's logger instance
236
+ logger,
229
237
  packageName: 'MY_APP'
230
238
  });
231
239
  ```
232
240
 
233
- **Why Use the Same Logger?**
241
+ **Why use the same Logxer?**
234
242
  - Consistent log format across your application
235
243
  - Unified logging destination
236
- - Proper context and metadata
244
+ - **`LogMeta`** correlation (`jobId`, `sessionId`, `correlationId`, `debugKind`, `runtimeIdentity`) matches gateway and Activix fields
237
245
  - Single point of control for log levels
238
246
 
239
- **Per-package log level (logs-gateway ≥ 3.5.x):**
247
+ **Per-package log level (`@x12i/logxer`):**
240
248
 
241
- - **Canonical:** `{PREFIX}_LOGS_LEVEL` — use the same `envPrefix` you pass to `createLogger` / your `packageName` (e.g. `MY_APP` → `MY_APP_LOGS_LEVEL`).
249
+ - **Canonical:** `{PREFIX}_LOGS_LEVEL` — same `envPrefix` as **`createLogxer`** / your `packageName` (e.g. `MY_APP` → `MY_APP_LOGS_LEVEL`).
242
250
  - **Legacy:** `{PREFIX}_LOG_LEVEL` is used only if `{PREFIX}_LOGS_LEVEL` is **not** set.
243
251
  - **Default** when both are unset: **`warn`** (not `info`, not silent).
244
252
  - **Silence** this package’s diagnostics: `off`, `none`, or `silent` (case-insensitive).
@@ -246,27 +254,26 @@ const gateway = new AIGateway({
246
254
 
247
255
  If the gateway builds the default logger and you omit `packageName`, the prefix is **`AI_GATEWAY`** → e.g. **`AI_GATEWAY_LOGS_LEVEL`**.
248
256
 
249
- **Environment Variables (examples):**
257
+ **Environment variables (examples):**
250
258
  ```bash
251
259
  MY_APP_LOGS_LEVEL=info # raise verbosity (or use debug / verbose)
252
260
  MY_APP_LOGS_LEVEL=off # silence this package’s logs
253
261
  # MY_APP_LOG_LEVEL=info # legacy; ignored if MY_APP_LOGS_LEVEL is set
254
262
 
255
- MY_APP_LOG_FORMAT=json # text|json|yaml
263
+ MY_APP_LOG_FORMAT=json # text|json|yaml|table
256
264
  MY_APP_LOG_TO_FILE=true
257
265
  MY_APP_LOG_FILE=/var/log/app.log
258
266
  MY_APP_LOG_TO_UNIFIED=true
259
267
  DEBUG=my-app # elevates verbose/debug when package is not fully silent
260
268
  ```
261
269
 
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.
270
+ **Diagnostic logging:** When debug level is enabled, the gateway emits diagnostic logs for instruction resolution and propagation through the same Logxer pipeline.
263
271
 
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.
272
+ **What happens if `logger` is not provided:** The gateway creates a default **`Logxer`** via **`createLogxer`**. For production, prefer passing your app’s logger so levels, transports, and **`runtimeIdentity`** stay aligned with the rest of your stack.
266
273
 
267
- ### 2. Activity Tracking Configuration (xronox-activitix via @x12i/activix v5)
274
+ ### 2. Activity Tracking Configuration (xronox-activitix via @x12i/activix v6)
268
275
 
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`).
276
+ **Activix version:** This gateway targets **`@x12i/activix` v6.x** (built on `@xronoces/xronox-store`). The dependency range is declared in `package.json` (currently `^6.5.1`). Activity I/O is stored at the **document root** as **`outer`** (and optional **`inner`**); the deprecated nested **`structure`** wrapper is not used.
270
277
 
271
278
  **Where to configure:**
272
279
  - Gateway constructor: `enableActivityTracking`, `activityTracker`
@@ -285,8 +292,8 @@ The gateway resolves activity tracking from environment variables via `src/confi
285
292
  **⚠️ CRITICAL: correlation and identity**
286
293
 
287
294
  - **`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).
295
+ - **Run context** (Activix BSON field `runContext`): Same object as **`request.identity`** (including required upstream **`jobId`** and **`taskId`**), plus `sessionId` and nested `instance: { instanceId, type }` when present; see [Identity contract](./docs/IDENTITY_OBJECT_CONTRACT.md).
296
+ - **`jobTypeId`**, **`taskTypeId`**: Optional aggregation / grouping fields (unchanged semantics).
290
297
  - **Each activity**: Gets its own **unique database record** with unique `_id` (MongoDB ObjectId).
291
298
  - **Two-phase tracking**: `startActivity()` creates a new record; `logSuccess()` / `logFailure()` update the same record by that record’s id.
292
299
 
@@ -339,7 +346,7 @@ gateway.register(new OpenAIProvider({
339
346
  }));
340
347
  ```
341
348
 
342
- **Advanced (custom Activix v5 instance):**
349
+ **Advanced (custom Activix v6 instance):**
343
350
 
344
351
  If you pass your own `Activix`, configure **the same collection names** the gateway expects so routing matches persistence:
345
352
 
@@ -370,7 +377,7 @@ const gateway = new AIGateway({
370
377
  ```
371
378
 
372
379
  **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
380
+ - **Identity**: Fields aligned with **`request.identity`** / Activix **`runContext`**: **`aiRequestId`**, upstream **`jobId`** and **`taskId`**, `sessionId`, `instance`, plus optional `jobTypeId`, `agentId`, `taskTypeId`, etc., as provided
374
381
  - **Timing**: `startTime`, `endTime`, `duration`, `status` (`started|success|failed`)
375
382
  - **Request data**: Stored in `request` object (instructions, prompt, input, messages, workingMemory)
376
383
  - **Config data**: Stored in `config` object (model, provider, temperature, maxTokens)
@@ -385,18 +392,18 @@ const gateway = new AIGateway({
385
392
 
386
393
  **Key design points:**
387
394
  - ✅ Each activity = separate database record with unique `_id`
388
- - ✅ **`aiRequestId`** = per-request correlation (required); optional `jobId` only if you supply it for grouping
395
+ - ✅ **`aiRequestId`** = per-request correlation (required); **`jobId`** / **`taskId`** come from upstream **`identity`** (required on each request; see v9+ contract above)
389
396
  - ✅ Request data sent once in `startActivity()` (creates new record)
390
397
  - ✅ Response data sent once in `logSuccess()` (updates same record by `_id`)
391
398
 
392
399
  **Default:** Activity tracking is enabled by default; without DB config it will log but not persist.
393
400
 
394
- **✅ Activix v5 integration**
401
+ **✅ Activix v6 integration**
395
402
 
396
403
  1. **Configuration** (`activity-tracking-config.ts`):
397
404
  - Mongo connection from env; **collection names** `ai-activities` and `bad-requests` are fixed for consistency across deployments.
398
405
 
399
- 2. **Lifecycle** (`@x12i/activix` v5):
406
+ 2. **Lifecycle** (`@x12i/activix` v6):
400
407
  - ✅ `startRecord` / `completeRecord` / `failRecord` (two-phase lifecycle)
401
408
  - ✅ Status transitions: `started` → `success` or `failed` (per your `statusValues` mapping)
402
409
  - ✅ Persistence via xronox-store queue semantics (see Activix package docs)
@@ -778,15 +785,15 @@ If a provider package is not installed, auto-registration will skip it gracefull
778
785
 
779
786
  ```typescript
780
787
  import { AIGateway } from '@x12i/ai-gateway';
781
- import { createLogger } from 'logs-gateway';
788
+ import { createLogxer } from '@x12i/logxer';
782
789
  import { Activix } from '@x12i/activix';
783
790
  import { OpenAIProvider } from '@x12i/ai-provider-openai';
784
791
 
785
792
  // 1. Configure activity tracker (and reuse its logger)
786
793
  // Single source of truth: set up the logger once, pass it to the tracker,
787
794
  // then reuse the same logger for the gateway.
788
- const logger = createLogger(
789
- { packageName: 'MY_APP', envPrefix: 'MY_APP' },
795
+ const logger = createLogxer(
796
+ { packageName: 'MY_APP', envPrefix: 'MY_APP', debugNamespace: 'my-app' },
790
797
  {
791
798
  logLevel: 'info',
792
799
  logFormat: 'json',
@@ -949,26 +956,27 @@ console.log(`RPM Limit: ${tierInfo?.rpm}, TPM Limit: ${tierInfo?.tpm}`);
949
956
  - `tier-4`: 10,000 RPM, 4M TPM
950
957
  - `tier-5`: 15,000 RPM, 40M TPM
951
958
 
952
- ### 3. Activity Tracking (xronox-activitix via @x12i/activix v5)
959
+ ### 3. Activity Tracking (xronox-activitix via @x12i/activix v6)
953
960
 
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).
961
+ The gateway uses **`@x12i/activix` v6** (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
962
 
956
963
  #### ⚠️ CRITICAL: correlation, identity, and unique record ids
957
964
 
958
965
  **IMPORTANT DESIGN CONCEPTS:**
959
966
 
960
967
  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).
968
+ - **`aiRequestId`** (required): One id per gateway invocation; used as the primary leaf correlation field (stored on the activity row and inside Activix `runContext`).
969
+ - **`identity.jobId`** and **`identity.taskId`** (required): Taken only from the upstream **`identity`** object; the gateway does not invent them.
970
+ - **`jobTypeId`**, **`taskTypeId`**: Optional aggregation fields (same ideas as before).
963
971
  - **Activity**: Each individual LLM request is a separate **activity** with its own unique record.
964
972
 
965
973
  2. **Mongo `_id` is the unique row key**
966
- - Optional **`jobId`** (if you pass it) is only grouping metadata multiple activities may share it.
974
+ - **`jobId`** / **`taskId`** on the row mirror upstream **`identity`** for correlation; multiple activities may share a **`jobId`** when you intend grouping.
967
975
  - Activix updates rows by the **record id** from the start phase, not by `jobId`.
968
976
 
969
- 3. **Two-phase tracking (Activix v5)**
977
+ 3. **Two-phase tracking (Activix v6)**
970
978
  - **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)
979
+ - Sends request-side data: `request`, `config`, `runContext`, root-level **`outer`** (and optional **`inner`**) I/O, `startTime`, `status: 'started'` (plus other gateway metadata)
972
980
  - Returns metadata containing the unique record id for completion
973
981
  - **Phase 2 (complete / fail)**: Updates the SAME record by that id
974
982
  - Sends response/error data: `response`, `endTime`, `duration`, `cost`, `status`
@@ -982,7 +990,7 @@ The gateway uses **`@x12i/activix` v5** (xronox-activitix) for full lifecycle lo
982
990
 
983
991
  **Example: same logical job, three LLM calls**
984
992
 
985
- Each call must have a **distinct `aiRequestId`**. Optionally pass the same `jobId` (or only `jobTypeId`) if you want to group rows in Mongo.
993
+ Each call must have a **distinct `aiRequestId`** and a full **`identity`** (including **`jobId`** and **`taskId`** from upstream). Use the same **`identity.jobId`** (and distinct **`taskId`** per call, or your own convention) if you want to group rows in Mongo.
986
994
 
987
995
  ```typescript
988
996
  import * as crypto from 'crypto';
@@ -993,29 +1001,31 @@ function md5(text: string): string {
993
1001
 
994
1002
  const jobTypeId = md5('data-processing-job');
995
1003
 
1004
+ const identityBase = {
1005
+ jobId: 'job-123',
1006
+ sessionId: 'sess-1',
1007
+ instance: { instanceId: 'inst-1', type: 'gateway' }
1008
+ };
1009
+
996
1010
  await gateway.invoke({
997
1011
  aiRequestId: 'req-001',
998
- jobId: 'job-123', // optional grouping
1012
+ identity: { ...identityBase, taskId: 'task-001' },
999
1013
  jobTypeId,
1000
1014
  agentId: 'agent-1',
1001
- sessionId: 'sess-1',
1002
- instance: { instanceId: 'inst-1', type: 'gateway' },
1003
- // ...
1015
+ // objectTypes, messages, ...
1004
1016
  });
1005
1017
 
1006
1018
  await gateway.invoke({
1007
1019
  aiRequestId: 'req-002',
1008
- jobId: 'job-123',
1020
+ identity: { ...identityBase, taskId: 'task-002' },
1009
1021
  jobTypeId,
1010
1022
  agentId: 'agent-1',
1011
- sessionId: 'sess-1',
1012
- instance: { instanceId: 'inst-1', type: 'gateway' },
1013
- // ...
1023
+ // objectTypes, messages, ...
1014
1024
  });
1015
1025
 
1016
1026
  // Query in Mongo (main collection name is ai-activities):
1017
1027
  // db.getCollection('ai-activities').find({ 'runContext.aiRequestId': 'req-001' })
1018
- // db.getCollection('ai-activities').find({ 'runContext.jobId': 'job-123' }) // if you set jobId
1028
+ // db.getCollection('ai-activities').find({ 'runContext.jobId': 'job-123' })
1019
1029
  ```
1020
1030
 
1021
1031
  #### Configuration
@@ -1059,7 +1069,7 @@ const gateway = new AIGateway({
1059
1069
  // Unique identifier (MongoDB auto-generated)
1060
1070
  _id: ObjectId('693970636e8d0f171e4aa528'), // ← UNIQUE per activity
1061
1071
 
1062
- // Activix v5: canonical correlation BSON object `runContext` (gateway builds it from `request.identity`)
1072
+ // Activix v6: canonical correlation BSON object `runContext` (same reference as `request.identity`, merged with gateway fields)
1063
1073
  runContext: {
1064
1074
  sessionId: 'sess-1',
1065
1075
  instance: { instanceId: 'gw-1', type: 'gateway' },
@@ -1086,11 +1096,9 @@ const gateway = new AIGateway({
1086
1096
  graphId: 'graph-456',
1087
1097
  nodeId: 'node-789',
1088
1098
 
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
- },
1099
+ // Required activity I/O: root-level `outer` (optional `inner[]` for steps) Activix v6 (see @x12i/activix docs)
1100
+ outer: { input: { ... }, output: { ... } | null, metadata: { ... } },
1101
+ // inner: [ { input, output, metadata, startedAt, endedAt, ... }, ... ],
1094
1102
 
1095
1103
  // Timing
1096
1104
  startTime: 1765372020804,
@@ -1142,11 +1150,11 @@ const gateway = new AIGateway({
1142
1150
  **Key points:**
1143
1151
  - ✅ Each activity = separate record with unique `_id`
1144
1152
  - ✅ **`aiRequestId`** = per-request correlation (required on invoke)
1145
- - ✅ Optional `jobId` = grouping metadata only
1153
+ - ✅ **`runContext.jobId`** / **`runContext.taskId`** = upstream identity (required on invoke since v9+)
1146
1154
  - ✅ Request data sent once at activity start; response data on completion
1147
1155
  - ✅ Updates use Activix record id / `_id`, not `jobId`
1148
1156
 
1149
- #### Retry Tracking (@x12i/activix v5)
1157
+ #### Retry Tracking (@x12i/activix v6)
1150
1158
 
1151
1159
  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
1160
 
@@ -1374,14 +1382,16 @@ const response = await gateway.invoke({
1374
1382
 
1375
1383
  ### 5. Structured Logging
1376
1384
 
1377
- The gateway uses `logs-gateway` for production-ready structured logging with correlation support.
1385
+ The gateway uses **`@x12i/logxer`** for structured logging with **`LogMeta`** correlation. See [Logger initialization](./docs/LOGGER_INITIALIZATION.md).
1378
1386
 
1379
1387
  ```typescript
1388
+ import { createLogxer } from '@x12i/logxer';
1389
+
1380
1390
  const gateway = new AIGateway({
1381
1391
  enableLogging: true,
1382
1392
  packageName: 'MY_APP',
1383
- logger: createLogger(
1384
- { packageName: 'MY_APP', envPrefix: 'MY_APP' },
1393
+ logger: createLogxer(
1394
+ { packageName: 'MY_APP', envPrefix: 'MY_APP', debugNamespace: 'my-app' },
1385
1395
  {
1386
1396
  logLevel: 'info',
1387
1397
  logFormat: 'json',
@@ -2897,7 +2907,7 @@ const result = await gateway.call({
2897
2907
 
2898
2908
  #### Integration with Activity Tracking
2899
2909
 
2900
- Contract output parsing integrates seamlessly with `@x12i/activix` v5 (xronox-activitix):
2910
+ Contract output parsing integrates seamlessly with `@x12i/activix` v6 (xronox-activitix):
2901
2911
 
2902
2912
  - **Automatic Processing**: Parsing happens automatically when `expectedSchema` is provided
2903
2913
  - **Error Resilience**: Parsing failures don't break activity recording
@@ -2925,10 +2935,13 @@ const schema = {
2925
2935
 
2926
2936
  const response = await gateway.invoke({
2927
2937
  aiRequestId: 'analysis-req-001',
2928
- jobId: 'analysis-123',
2938
+ identity: {
2939
+ jobId: 'analysis-123',
2940
+ taskId: 'analysis-task-001',
2941
+ sessionId: 'sess-analyzer',
2942
+ instance: { instanceId: 'analyzer-1', type: 'gateway' }
2943
+ },
2929
2944
  agentId: 'analyzer-bot',
2930
- sessionId: 'sess-analyzer',
2931
- instance: { instanceId: 'analyzer-1', type: 'gateway' },
2932
2945
  instructions: 'Analyze the provided data...',
2933
2946
  expectedSchema: schema,
2934
2947
  messages: [{ role: 'user', content: data }]
@@ -2956,8 +2969,8 @@ interface GatewayConfig extends RouterConfig {
2956
2969
  enableUsageTracking?: boolean; // Default: true
2957
2970
  enableLogging?: boolean; // Default: true
2958
2971
  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)
2972
+ logger?: import('@x12i/logxer').Logxer; // Custom Logxer (from createLogxer)
2973
+ activityTracker?: Activix; // Custom Activix v6 instance (match collection names: ai-activities, bad-requests, skill-executions)
2961
2974
  packageName?: string; // Default: 'AI_GATEWAY'
2962
2975
  // ... all RouterConfig options
2963
2976
  }
@@ -2978,7 +2991,7 @@ interface GatewayConfig extends RouterConfig {
2978
2991
  - `checkHealth(provider: LLMProvider): Promise<HealthCheckResult>` - Check provider health
2979
2992
  - `checkAllHealth(): Promise<Map<LLMProvider, HealthCheckResult>>` - Check all providers' health
2980
2993
  - `getRouter(): LLMProviderRouter` - Get underlying router instance
2981
- - `getLogger(): LogsGateway` - Get logger instance
2994
+ - `getLogger(): Logxer` - Get logger instance (`@x12i/logxer`)
2982
2995
  - `setActivityManager(activityManager)` - Advanced/test hook to replace the internal activity manager; typical apps inject **`Activix` via `activityTracker`** in the constructor instead
2983
2996
  - `getContentRegistry(): ContentRegistry | undefined` - Get underlying content-registry instance (returns undefined if not enabled)
2984
2997
  - `resolveInstructionKey(key: string, variables?: Record<string, unknown>): Promise<string>` - Resolve instruction key with variables (without making LLM call)
@@ -3727,10 +3740,10 @@ const gateway = new AIGateway({
3727
3740
  ### Custom Logger
3728
3741
 
3729
3742
  ```typescript
3730
- import { createLogger } from 'logs-gateway';
3743
+ import { createLogxer } from '@x12i/logxer';
3731
3744
 
3732
- const customLogger = createLogger(
3733
- { packageName: 'MY_APP', envPrefix: 'MY_APP' },
3745
+ const customLogger = createLogxer(
3746
+ { packageName: 'MY_APP', envPrefix: 'MY_APP', debugNamespace: 'my-app' },
3734
3747
  {
3735
3748
  logLevel: 'debug',
3736
3749
  logFormat: 'json',
@@ -4155,8 +4168,8 @@ This package integrates with:
4155
4168
  - **@x12i/ai-providers-router**: Core routing functionality
4156
4169
  - **@xronoces/content-registry**: Instruction key resolution and content management (optional)
4157
4170
  - **@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
4171
+ - **@x12i/activix** v6 (xronox-activitix): Activity logging and tracking (`^6.5.1` in this package)
4172
+ - **@x12i/logxer**: Structured logging with correlation (`^4.2.1` in this package)
4160
4173
  - **nx-config2**: Configuration management (via dependencies)
4161
4174
 
4162
4175
  ## Related Packages Status
@@ -15,8 +15,10 @@ type Request = ChatRequest | AIRequest;
15
15
  * **Execution context:** treats `request.identity` as received from upstream. Root fields (`sessionId`,
16
16
  * `instance`, and any keys already set on that object) are not replaced by gateway defaults; the gateway
17
17
  * only fills missing required pieces and adds local context (e.g. graph linkage) where absent.
18
+ *
19
+ * @param logger - Optional Logxer used to WARN when mandatory upstream `identity.jobId` / `identity.taskId` are absent.
18
20
  */
19
- export declare function ensureGatewayRequestIdentity(request: ChatRequest | AIRequest, extras?: Partial<ActivityIdentity>): ActivityIdentity;
21
+ export declare function ensureGatewayRequestIdentity(request: ChatRequest | AIRequest, extras?: Partial<ActivityIdentity>, logger?: Logxer): ActivityIdentity;
20
22
  type ActivityMetadata = Record<string, any> & {
21
23
  activityId?: string;
22
24
  recordId?: string;
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { Activix, activixActivityIo, activixOuterTier } from '@x12i/activix';
8
8
  import { resolveActivityTrackingConfig } from './config/activity-tracking-config.js';
9
+ import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
9
10
  function readAiRequestIdFromRequest(request) {
10
11
  const aiRequestId = request.aiRequestId;
11
12
  if (typeof aiRequestId === 'string' && aiRequestId.trim().length > 0) {
@@ -102,40 +103,64 @@ function buildInstanceEnvelope(request) {
102
103
  function resolveSessionIdForRequest(request, incomingIdentity, aiRequestId) {
103
104
  const fromIdentity = trimIdentityString(incomingIdentity?.sessionId);
104
105
  const topLevel = trimIdentityString(request.sessionId);
105
- const jobId = trimIdentityString(request.jobId);
106
+ const identityJobId = trimIdentityString(incomingIdentity?.jobId);
106
107
  if (fromIdentity !== undefined && topLevel !== undefined && fromIdentity !== topLevel) {
107
108
  throw new Error(`identity.sessionId (${fromIdentity}) and top-level sessionId (${topLevel}) must agree`);
108
109
  }
109
- return fromIdentity ?? topLevel ?? jobId ?? aiRequestId;
110
+ return fromIdentity ?? topLevel ?? identityJobId ?? aiRequestId;
111
+ }
112
+ function logUpstreamIdentityWarnings(logger, incomingIdentity, merged) {
113
+ const aiRequestId = merged.aiRequestId;
114
+ if (!incomingIdentity || typeof incomingIdentity !== 'object') {
115
+ logger?.warn('missingRuntimeIdentityObject', withActivityIdentity(merged, {
116
+ message: 'request.identity is missing; upstream must send the mandatory runtime identity object',
117
+ aiRequestId,
118
+ debugKind: gatewayLogDebug.anomaly
119
+ }));
120
+ return;
121
+ }
122
+ const missing = [];
123
+ if (!trimIdentityString(incomingIdentity.jobId))
124
+ missing.push('jobId');
125
+ if (!trimIdentityString(incomingIdentity.taskId))
126
+ missing.push('taskId');
127
+ if (missing.length > 0) {
128
+ logger?.warn('missingUpstreamIdentityFields', withActivityIdentity(merged, {
129
+ message: `Upstream identity is missing required field(s): ${missing.join(', ')}. jobId and taskId must be set on request.identity by the client; the gateway does not generate them.`,
130
+ missingFields: missing,
131
+ aiRequestId,
132
+ debugKind: gatewayLogDebug.anomaly
133
+ }));
134
+ }
110
135
  }
111
136
  function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
112
137
  const incomingIdentity = request.identity;
113
138
  const sessionId = resolveSessionIdForRequest(request, incomingIdentity, aiRequestId);
114
- const legacyJobId = trimIdentityString(incomingIdentity?.jobId) ?? trimIdentityString(request.jobId);
139
+ const upstreamJobId = trimIdentityString(incomingIdentity?.jobId) ?? '';
140
+ const upstreamTaskId = trimIdentityString(incomingIdentity?.taskId) ?? '';
115
141
  const combinedEnrichment = pickGatewayEnrichmentFields({
116
142
  ...graphIdentityExtrasFromRequest(request),
117
143
  ...(extras || {})
118
144
  });
119
145
  const safeEnrichment = mergeActivixEnrichment(incomingIdentity, combinedEnrichment);
120
- // Request fields are defaults for this hop; incoming `identity` from upstream wins on conflict.
121
- // Gateway enrichment (graph/skill, etc.) fills only keys the outer envelope did not already set.
146
+ // jobId / taskId: upstream `request.identity` only (never top-level request.jobId / request.taskId).
122
147
  const merged = {
123
148
  jobTypeId: request.jobTypeId,
124
149
  agentId: request.agentId,
125
- taskId: request.taskId,
126
150
  taskTypeId: request.taskTypeId,
127
151
  ...safeEnrichment,
128
152
  ...(incomingIdentity || {}),
129
153
  sessionId,
130
154
  instance: buildInstanceEnvelope(request),
131
- aiRequestId
155
+ aiRequestId,
156
+ jobId: upstreamJobId,
157
+ taskId: upstreamTaskId
132
158
  };
133
159
  merged.sessionId = sessionId;
134
160
  merged.instance = buildInstanceEnvelope(request);
135
161
  merged.aiRequestId = aiRequestId;
136
- if (legacyJobId !== undefined) {
137
- merged.jobId = legacyJobId;
138
- }
162
+ merged.jobId = upstreamJobId;
163
+ merged.taskId = upstreamTaskId;
139
164
  return merged;
140
165
  }
141
166
  /**
@@ -145,13 +170,16 @@ function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
145
170
  * **Execution context:** treats `request.identity` as received from upstream. Root fields (`sessionId`,
146
171
  * `instance`, and any keys already set on that object) are not replaced by gateway defaults; the gateway
147
172
  * only fills missing required pieces and adds local context (e.g. graph linkage) where absent.
173
+ *
174
+ * @param logger - Optional Logxer used to WARN when mandatory upstream `identity.jobId` / `identity.taskId` are absent.
148
175
  */
149
- export function ensureGatewayRequestIdentity(request, extras) {
176
+ export function ensureGatewayRequestIdentity(request, extras, logger) {
150
177
  const aiRequestId = readAiRequestIdFromRequest(request);
151
178
  const identity = mergeGatewayActivityIdentity(request, aiRequestId, {
152
179
  ...graphIdentityExtrasFromRequest(request),
153
180
  ...(extras || {})
154
181
  });
182
+ logUpstreamIdentityWarnings(logger, request.identity, identity);
155
183
  request.identity = identity;
156
184
  return identity;
157
185
  }
@@ -287,7 +315,7 @@ export class ActivityManager {
287
315
  * @returns Activity metadata (contains unique _id/recordId) or undefined if tracking is disabled
288
316
  */
289
317
  async startActivity(request, startTime) {
290
- const identity = ensureGatewayRequestIdentity(request);
318
+ const identity = ensureGatewayRequestIdentity(request, undefined, this.logger);
291
319
  const aiRequestId = identity.aiRequestId;
292
320
  // Capture ONLY request data - NO response data at this stage
293
321
  // Note: v2.3.2+ excludes flat request/config fields from root level
@@ -307,9 +335,10 @@ export class ActivityManager {
307
335
  }
308
336
  const activityMetadata = {
309
337
  aiRequestId,
338
+ jobId: identity.jobId,
310
339
  jobTypeId: request.jobTypeId,
311
340
  agentId: request.agentId,
312
- taskId: request.taskId,
341
+ taskId: identity.taskId,
313
342
  taskTypeId: request.taskTypeId,
314
343
  startTime,
315
344
  status: 'started',
@@ -503,7 +532,7 @@ export class ActivityManager {
503
532
  }
504
533
  // Determine inference type (will be extracted in gateway.ts and passed via request metadata)
505
534
  const inferenceType = aiRequest.inferenceType || 'question-answer';
506
- const identity = ensureGatewayRequestIdentity(request);
535
+ const identity = ensureGatewayRequestIdentity(request, undefined, this.logger);
507
536
  const aiRequestId = identity.aiRequestId;
508
537
  if (!this.activix) {
509
538
  return undefined;
@@ -521,9 +550,10 @@ export class ActivityManager {
521
550
  skillId: skillId ?? skillKey,
522
551
  inferenceType, // ✅ Recommended: type of inference
523
552
  aiRequestId,
553
+ jobId: identity.jobId,
524
554
  jobTypeId: request.jobTypeId,
525
555
  agentId: request.agentId,
526
- taskId: request.taskId,
556
+ taskId: identity.taskId,
527
557
  taskTypeId: request.taskTypeId,
528
558
  startTime,
529
559
  status: 'started',
@@ -897,7 +927,7 @@ export class ActivityManager {
897
927
  * @param startTime - When the request started
898
928
  */
899
929
  async logBadRequest(request, error, details, startTime) {
900
- const identity = ensureGatewayRequestIdentity(request);
930
+ const identity = ensureGatewayRequestIdentity(request, undefined, this.logger);
901
931
  const aiRequestId = identity.aiRequestId;
902
932
  const endTime = details.endTime;
903
933
  const duration = details.duration;
@@ -922,9 +952,10 @@ export class ActivityManager {
922
952
  const badRequestMetadata = {
923
953
  activityType: 'bad-request',
924
954
  aiRequestId,
955
+ jobId: identity.jobId,
925
956
  jobTypeId: request.jobTypeId,
926
957
  agentId: request.agentId,
927
- taskId: request.taskId,
958
+ taskId: identity.taskId,
928
959
  taskTypeId: request.taskTypeId,
929
960
  startTime,
930
961
  status: 'started',