@x12i/ai-gateway 9.6.5 → 9.6.8

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.
@@ -4,8 +4,14 @@
4
4
  * Creates and configures logxer instances for the gateway
5
5
  */
6
6
  import { createLogxer, getStationRuntimeIdentity, mergeRuntimeIdentity } from '@x12i/logxer';
7
- /** Stable ERC 2.0 env prefix — controls `AI_GATEWAY_LOGS_LEVEL`, etc. */
8
- export const GATEWAY_LOG_ENV_PREFIX = 'AI_GATEWAY';
7
+ import { resolveLogDiagnosticsCatalogPath } from './gateway-log-diagnostics.js';
8
+ import { GATEWAY_LOG_ENV_PREFIX, initializeGatewayPackageLogLevels } from './gateway-log-levels.js';
9
+ export { GATEWAY_LOG_ENV_PREFIX };
10
+ const NOOP_GATEWAY_LOGGER = Symbol('noop-gateway-logger');
11
+ /** True for the internal no-op logger when `enableLogging` is false. */
12
+ export function isNoOpGatewayLogger(logger) {
13
+ return logger[NOOP_GATEWAY_LOGGER] === true;
14
+ }
9
15
  function resolveGatewayRuntimeIdentity(packageName) {
10
16
  return mergeRuntimeIdentity(getStationRuntimeIdentity(), {
11
17
  service: process.env.AI_GATEWAY_LOG_SERVICE ?? packageName ?? GATEWAY_LOG_ENV_PREFIX,
@@ -25,26 +31,46 @@ export function createGatewayLogger(config) {
25
31
  if (!config.enableLogging) {
26
32
  return createNoOpLogger();
27
33
  }
34
+ initializeGatewayPackageLogLevels({ packageLogLevels: config.packageLogLevels });
35
+ const catalogPath = resolveLogDiagnosticsCatalogPath();
28
36
  return createLogxer({
29
37
  packageName: config.packageName || GATEWAY_LOG_ENV_PREFIX,
30
38
  envPrefix: GATEWAY_LOG_ENV_PREFIX,
31
39
  debugNamespace: 'ai-gateway'
32
40
  }, {
33
- // Level/format/sinks: ERC 2.0 env discovery (`AI_GATEWAY_LOGS_LEVEL`, host-level format, …).
34
- runtimeIdentity: resolveGatewayRuntimeIdentity(config.packageName)
41
+ runtimeIdentity: resolveGatewayRuntimeIdentity(config.packageName),
42
+ stack: config.logging,
43
+ ...(config.logLevel !== undefined ? { logLevel: config.logLevel } : {}),
44
+ ...(catalogPath
45
+ ? {
46
+ diagnostics: {
47
+ catalogPath,
48
+ consoleMode: 'summary'
49
+ }
50
+ }
51
+ : {})
35
52
  });
36
53
  }
37
54
  function createNoOpLogger() {
38
55
  const noop = () => { };
39
- return {
56
+ const noopAsync = async () => ({ lines: [], total: 0 });
57
+ const logger = {
40
58
  verbose: noop,
41
59
  debug: noop,
42
60
  info: noop,
43
61
  warn: noop,
44
62
  error: noop,
45
63
  success: noop,
64
+ infoCode: noop,
65
+ warnCode: noop,
66
+ errorCode: noop,
67
+ diagnostic: noop,
68
+ getJobLogs: noopAsync,
69
+ scopeLogs: noopAsync,
46
70
  isLevelEnabled: () => false,
47
71
  getConfig: () => ({}),
48
- shadow: {}
72
+ shadow: {},
73
+ [NOOP_GATEWAY_LOGGER]: true
49
74
  };
75
+ return logger;
50
76
  }
@@ -1,4 +1,5 @@
1
1
  import { Optimixer } from '@x12i/optimixer';
2
+ import { exceptionEvidence, fieldEvidence, GatewayLogCode, gatewayWarnCode } from './gateway-log-diagnostics.js';
2
3
  import { resolveActivityTrackingConfig } from './config/activity-tracking-config.js';
3
4
  import { estimateMessagesTokenSizes } from './token-estimate.js';
4
5
  /** Optimixer bucket key: prefer taskTypeId (template), then identity actionType, else gateway default. */
@@ -47,8 +48,9 @@ export class OptimixerManager {
47
48
  async initialize() {
48
49
  const activix = await this.getActivix();
49
50
  if (!activix) {
50
- this.logger.warn('Optimixer enabled but Activix is unavailable; adaptive max_tokens disabled', {
51
- activixCollection: this.activixCollection
51
+ gatewayWarnCode(this.logger, GatewayLogCode.OPTIMIXER_ACTIVIX_UNAVAILABLE, undefined, {
52
+ activixCollection: this.activixCollection,
53
+ evidence: [fieldEvidence('activixCollection', this.activixCollection)]
52
54
  });
53
55
  return;
54
56
  }
@@ -65,8 +67,12 @@ export class OptimixerManager {
65
67
  });
66
68
  }
67
69
  catch (error) {
68
- this.logger.warn('Optimixer initialization failed; adaptive max_tokens disabled', {
69
- error: error instanceof Error ? error.message : String(error)
70
+ gatewayWarnCode(this.logger, GatewayLogCode.OPTIMIXER_INIT_FAILED, undefined, {
71
+ error: error instanceof Error ? error.message : String(error),
72
+ evidence: [
73
+ fieldEvidence('activixCollection', this.activixCollection),
74
+ ...(error instanceof Error ? [exceptionEvidence(error)] : [])
75
+ ]
70
76
  });
71
77
  this.optimixer = undefined;
72
78
  }
@@ -93,9 +99,13 @@ export class OptimixerManager {
93
99
  });
94
100
  }
95
101
  catch (error) {
96
- this.logger.warn('Optimixer predictAiMaxTokens failed; caller should use fallback max_tokens', {
102
+ gatewayWarnCode(this.logger, GatewayLogCode.OPTIMIXER_PREDICT_FAILED, request.identity, {
97
103
  error: error instanceof Error ? error.message : String(error),
98
- aiRequestId: request.aiRequestId
104
+ aiRequestId: request.aiRequestId,
105
+ evidence: [
106
+ fieldEvidence('aiRequestId', request.aiRequestId),
107
+ ...(error instanceof Error ? [exceptionEvidence(error)] : [])
108
+ ]
99
109
  });
100
110
  return undefined;
101
111
  }
@@ -2,7 +2,7 @@ import type { Logxer } from '@x12i/logxer';
2
2
  import type { Activix, ActivixQueryableClient } from '@x12i/activix';
3
3
  export type { ActivixQueryableClient } from '@x12i/activix';
4
4
  export type { GetJobLogsInput, GetJobLogsResult } from '@x12i/logxer';
5
- export type LogxerQueryableClient = Pick<Logxer, 'getJobLogs'>;
5
+ export type LogxerQueryableClient = Pick<Logxer, 'getJobLogs'> & Partial<Pick<Logxer, 'scopeLogs'>>;
6
6
  export type PackageRuntimeObjects = {
7
7
  name: string;
8
8
  activixClient?: ActivixQueryableClient;
@@ -1,3 +1,4 @@
1
+ import { isNoOpGatewayLogger } from './logger-factory.js';
1
2
  export let runtimeObjects = createRuntimeObjectsForMode();
2
3
  export function setGatewayLastJobId(jobId) {
3
4
  const objects = ensureRuntimeObjectsForCurrentMode();
@@ -41,6 +42,14 @@ function asActivixQueryableClient(activix) {
41
42
  return typeof candidate.getJobActivities === 'function' ? candidate : undefined;
42
43
  }
43
44
  function asLogxerQueryableClient(logger) {
45
+ if (isNoOpGatewayLogger(logger)) {
46
+ return undefined;
47
+ }
44
48
  const candidate = logger;
45
- return typeof candidate.getJobLogs === 'function' ? candidate : undefined;
49
+ if (typeof candidate.getJobLogs !== 'function') {
50
+ return undefined;
51
+ }
52
+ return typeof candidate.scopeLogs === 'function'
53
+ ? { getJobLogs: candidate.getJobLogs.bind(candidate), scopeLogs: candidate.scopeLogs.bind(candidate) }
54
+ : { getJobLogs: candidate.getJobLogs.bind(candidate) };
46
55
  }
package/dist/types.d.ts CHANGED
@@ -9,7 +9,7 @@ type AIModel = string;
9
9
  export type UsageTier = string;
10
10
  import type { Activix } from '@x12i/activix';
11
11
  import type { SmartInputConfig, SmartInputRenderOptions, TemplateRenderOptions } from '@x12i/rendrix';
12
- import type { Logxer } from '@x12i/logxer';
12
+ import type { Logxer, PackageLogLevelsConfig } from '@x12i/logxer';
13
13
  /**
14
14
  * Diagnostics options for opt-in authoritative tracing.
15
15
  * Default behavior must remain minimal when diagnostics are not enabled.
@@ -349,6 +349,13 @@ export interface GatewayConfig extends Omit<RouterConfig, 'defaultEngine' | 'log
349
349
  * @default true
350
350
  */
351
351
  enableLogging?: boolean;
352
+ /**
353
+ * Process-wide package log level registry (logxer ≥ 4.5).
354
+ * Merged after `applyPackageLogLevelsFromEnv()` on first gateway init.
355
+ * `logging` (from {@link RouterConfig}) is forwarded to the gateway logger and router.
356
+ * @example `{ default: 'warn', levels: { AI_GATEWAY: 'info', AI_PROVIDER_ROUTER: 'debug' } }`
357
+ */
358
+ packageLogLevels?: PackageLogLevelsConfig;
352
359
  /**
353
360
  * Custom logger instance (optional)
354
361
  */
@@ -0,0 +1,176 @@
1
+ {
2
+ "GATEWAY_ZERO_USAGE_OR_COST": {
3
+ "defaultLevel": "warn",
4
+ "title": "Successful invoke reported zero token usage and/or zero cost",
5
+ "impact": "Billing and usage metadata may be missing from the provider adapter.",
6
+ "possibleCauses": [
7
+ "Router adapter did not populate usage fields.",
8
+ "Provider response omitted token counts.",
9
+ "Cost calculation was skipped or returned zero."
10
+ ],
11
+ "remediation": [
12
+ "Verify router adapter usage extraction.",
13
+ "Confirm ai-tools cost resolution is enabled when pricing is expected."
14
+ ],
15
+ "retryable": false,
16
+ "userActionRequired": false,
17
+ "confidence": "medium"
18
+ },
19
+ "GATEWAY_FALLBACK_CHAIN_EXHAUSTED": {
20
+ "defaultLevel": "error",
21
+ "title": "Provider fallback chain exhausted without a successful response",
22
+ "impact": "The AI request failed after all configured provider/model candidates were tried.",
23
+ "possibleCauses": [
24
+ "All providers in the chain returned errors.",
25
+ "Rate limits or auth failures across candidates.",
26
+ "Model or profile misconfiguration."
27
+ ],
28
+ "remediation": [
29
+ "Inspect fallbackAttempts in the log evidence.",
30
+ "Verify API keys and provider registration.",
31
+ "Review trace-mode metadata.attempts when enabled."
32
+ ],
33
+ "retryable": true,
34
+ "userActionRequired": true,
35
+ "confidence": "high"
36
+ },
37
+ "GATEWAY_FLEX_MD_EXTRACTION_FAILED": {
38
+ "defaultLevel": "warn",
39
+ "title": "Flex-md extraction did not produce structured output",
40
+ "impact": "Parsed content falls back to a rawText wrapper; downstream contract validation may fail.",
41
+ "possibleCauses": [
42
+ "Model response was plain text without expected markdown structure.",
43
+ "flex-md headers or fenced blocks were missing.",
44
+ "Response was truncated before structured content completed."
45
+ ],
46
+ "remediation": [
47
+ "Inspect raw response sample.",
48
+ "Tighten instructions for markdown/JSON output format.",
49
+ "Check max token allocation."
50
+ ],
51
+ "retryable": true,
52
+ "userActionRequired": false,
53
+ "confidence": "medium"
54
+ },
55
+ "GATEWAY_FLEX_MD_EXTRACTION_ERROR": {
56
+ "defaultLevel": "warn",
57
+ "title": "Flex-md extraction threw during parsing",
58
+ "impact": "Structured parsing failed; gateway uses rawText fallback.",
59
+ "possibleCauses": [
60
+ "flex-md library compatibility or loader issue.",
61
+ "Malformed markdown in the model response.",
62
+ "Unexpected response shape from the provider."
63
+ ],
64
+ "remediation": [
65
+ "Review the exception evidence.",
66
+ "Confirm flex-md version alignment with the gateway.",
67
+ "Inspect raw provider content."
68
+ ],
69
+ "retryable": true,
70
+ "userActionRequired": false,
71
+ "confidence": "medium"
72
+ },
73
+ "GATEWAY_DEFAULT_MODEL_SUBSTITUTED": {
74
+ "defaultLevel": "warn",
75
+ "title": "Gateway substituted the configured default model",
76
+ "impact": "The request may run on a different provider/model than the caller specified.",
77
+ "possibleCauses": [
78
+ "Request omitted model while gateway defaults apply.",
79
+ "Operational mode requires a packaged default engine.",
80
+ "Profile resolution fell back to gateway defaults."
81
+ ],
82
+ "remediation": [
83
+ "Pass an explicit model on the request when substitution is undesired.",
84
+ "Review default model configuration and AI_TOOLS routing."
85
+ ],
86
+ "retryable": false,
87
+ "userActionRequired": false,
88
+ "confidence": "high"
89
+ },
90
+ "GATEWAY_RETRY_MAX_EXCEEDED": {
91
+ "defaultLevel": "warn",
92
+ "title": "Provider invoke retries exhausted",
93
+ "impact": "The router call failed after the configured retry budget.",
94
+ "possibleCauses": [
95
+ "Transient network or server errors persisted.",
96
+ "Throttling (429) without sufficient backoff.",
97
+ "Non-recoverable provider outage."
98
+ ],
99
+ "remediation": [
100
+ "Inspect error and status code evidence.",
101
+ "Increase retry limits or backoff if appropriate.",
102
+ "Check provider status and rate limits."
103
+ ],
104
+ "retryable": true,
105
+ "userActionRequired": false,
106
+ "confidence": "high"
107
+ },
108
+ "GATEWAY_RETRY_ATTEMPT": {
109
+ "defaultLevel": "warn",
110
+ "title": "Retrying provider invoke after error",
111
+ "impact": "Request latency increases while the gateway retries.",
112
+ "possibleCauses": [
113
+ "Transient HTTP or network failure.",
114
+ "Rate limiting with backoff.",
115
+ "Timeout from the provider."
116
+ ],
117
+ "remediation": [
118
+ "Monitor retry counts per job.",
119
+ "Tune retry policy if retries are frequent."
120
+ ],
121
+ "retryable": true,
122
+ "userActionRequired": false,
123
+ "confidence": "medium"
124
+ },
125
+ "GATEWAY_OPTIMIXER_ACTIVIX_UNAVAILABLE": {
126
+ "defaultLevel": "warn",
127
+ "title": "Optimixer enabled but Activix is unavailable",
128
+ "impact": "Adaptive max_tokens prediction is disabled for this gateway instance.",
129
+ "possibleCauses": [
130
+ "Activity tracking is disabled or Activix failed to initialize.",
131
+ "Mongo connection or collection configuration is missing."
132
+ ],
133
+ "remediation": [
134
+ "Enable activity tracking with a working Activix connection.",
135
+ "Verify activixCollection configuration."
136
+ ],
137
+ "retryable": false,
138
+ "userActionRequired": true,
139
+ "confidence": "high"
140
+ },
141
+ "GATEWAY_OPTIMIXER_INIT_FAILED": {
142
+ "defaultLevel": "warn",
143
+ "title": "Optimixer initialization failed",
144
+ "impact": "Adaptive max_tokens prediction is disabled.",
145
+ "possibleCauses": [
146
+ "Activix schema or collection mismatch.",
147
+ "Optimixer configuration error.",
148
+ "Dependency or network failure during create()."
149
+ ],
150
+ "remediation": [
151
+ "Check Activix connectivity and collection names.",
152
+ "Review optimixer gateway config."
153
+ ],
154
+ "retryable": false,
155
+ "userActionRequired": true,
156
+ "confidence": "high"
157
+ },
158
+ "GATEWAY_OPTIMIXER_PREDICT_FAILED": {
159
+ "defaultLevel": "warn",
160
+ "title": "Optimixer predictAiMaxTokens failed",
161
+ "impact": "Caller should use fallback max_tokens for the invoke.",
162
+ "possibleCauses": [
163
+ "Insufficient historical samples for the template.",
164
+ "Token estimation or profile resolution failed.",
165
+ "Optimixer internal error."
166
+ ],
167
+ "remediation": [
168
+ "Set explicit max_tokens on the request.",
169
+ "Verify templateId and model profile fields.",
170
+ "Check prediction history in Activix."
171
+ ],
172
+ "retryable": true,
173
+ "userActionRequired": false,
174
+ "confidence": "medium"
175
+ }
176
+ }
@@ -162,7 +162,10 @@ export function initializeGatewayComponents(config) {
162
162
  const logger = createGatewayLogger({
163
163
  enableLogging: config.enableLogging ?? true,
164
164
  packageName: config.packageName,
165
- customLogger: config.logger
165
+ customLogger: config.logger,
166
+ logging: config.logging,
167
+ packageLogLevels: config.packageLogLevels,
168
+ logLevel: config.logLevel
166
169
  });
167
170
  const { defaultModelConfig, defaultInstructionsBlocks, defaultTemplateRendering } = loadConfig(logger);
168
171
  logger.verbose('Gateway initializing', {
@@ -194,6 +197,12 @@ export function initializeGatewayComponents(config) {
194
197
  routerConfig.autoDiscover = config.autoDiscover;
195
198
  if (config.usageTracker !== undefined)
196
199
  routerConfig.usageTracker = config.usageTracker;
200
+ if (config.verbose !== undefined)
201
+ routerConfig.verbose = config.verbose;
202
+ if (config.logLevel !== undefined)
203
+ routerConfig.logLevel = config.logLevel;
204
+ if (config.logging !== undefined)
205
+ routerConfig.logging = config.logging;
197
206
  // OpenRouter: always pass apiKey when set (fallback for providers without direct keys).
198
207
  // USE_OPENROUTER=false only disables *preferring* OpenRouter when direct provider keys exist.
199
208
  const openRouterKey = resolveOpenRouterApiKey(config);
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Logxer 4.4+ diagnostic catalog codes and helpers for structured anomaly logging.
3
+ */
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { exceptionEvidence, fieldEvidence } from '@x12i/logxer';
8
+ import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
9
+ export { exceptionEvidence, fieldEvidence };
10
+ /** Stable diagnostic codes backed by `src/defaults/log-diagnostics.json`. */
11
+ export const GatewayLogCode = {
12
+ ZERO_USAGE_OR_COST: 'GATEWAY_ZERO_USAGE_OR_COST',
13
+ FALLBACK_CHAIN_EXHAUSTED: 'GATEWAY_FALLBACK_CHAIN_EXHAUSTED',
14
+ FLEX_MD_EXTRACTION_FAILED: 'GATEWAY_FLEX_MD_EXTRACTION_FAILED',
15
+ FLEX_MD_EXTRACTION_ERROR: 'GATEWAY_FLEX_MD_EXTRACTION_ERROR',
16
+ DEFAULT_MODEL_SUBSTITUTED: 'GATEWAY_DEFAULT_MODEL_SUBSTITUTED',
17
+ RETRY_MAX_EXCEEDED: 'GATEWAY_RETRY_MAX_EXCEEDED',
18
+ RETRY_ATTEMPT: 'GATEWAY_RETRY_ATTEMPT',
19
+ OPTIMIXER_ACTIVIX_UNAVAILABLE: 'GATEWAY_OPTIMIXER_ACTIVIX_UNAVAILABLE',
20
+ OPTIMIXER_INIT_FAILED: 'GATEWAY_OPTIMIXER_INIT_FAILED',
21
+ OPTIMIXER_PREDICT_FAILED: 'GATEWAY_OPTIMIXER_PREDICT_FAILED'
22
+ };
23
+ function getModuleDir() {
24
+ if (typeof __dirname !== 'undefined') {
25
+ return __dirname;
26
+ }
27
+ try {
28
+ const getMetaUrl = new Function('return import.meta.url');
29
+ const metaUrl = getMetaUrl();
30
+ if (metaUrl && metaUrl.startsWith('file:')) {
31
+ return path.dirname(fileURLToPath(metaUrl));
32
+ }
33
+ }
34
+ catch {
35
+ // fall through
36
+ }
37
+ return process.cwd();
38
+ }
39
+ /** Resolve packaged `defaults/log-diagnostics.json` for createLogxer diagnostics.catalogPath. */
40
+ export function resolveLogDiagnosticsCatalogPath() {
41
+ const moduleDir = getModuleDir();
42
+ const cwd = process.cwd();
43
+ const candidates = [
44
+ moduleDir,
45
+ path.resolve(moduleDir, '..'),
46
+ path.resolve(moduleDir, '../dist'),
47
+ path.resolve(moduleDir, '../dist-cjs'),
48
+ path.resolve(moduleDir, '../src'),
49
+ path.join(cwd, 'dist'),
50
+ path.join(cwd, 'dist-cjs'),
51
+ path.join(cwd, 'src')
52
+ ];
53
+ for (const dir of candidates) {
54
+ const catalogPath = path.join(dir, 'defaults', 'log-diagnostics.json');
55
+ if (fs.existsSync(catalogPath)) {
56
+ return catalogPath;
57
+ }
58
+ }
59
+ return undefined;
60
+ }
61
+ export function gatewayWarnCode(logger, code, identity, data) {
62
+ logger.warnCode(code, withActivityIdentity(identity, data));
63
+ }
64
+ export function gatewayErrorCode(logger, code, identity, data) {
65
+ logger.errorCode(code, withActivityIdentity(identity, data));
66
+ }
67
+ export function gatewayInfoCode(logger, code, identity, data) {
68
+ logger.infoCode(code, withActivityIdentity(identity, data));
69
+ }
70
+ export function gatewayAnomalyMeta(identity, data) {
71
+ return withActivityIdentity(identity, data, gatewayLogDebug.anomaly);
72
+ }
@@ -0,0 +1,23 @@
1
+ import { exceptionEvidence, fieldEvidence, type LogMeta, type Logxer } from '@x12i/logxer';
2
+ import type { ActivityIdentity } from './types.js';
3
+ export { exceptionEvidence, fieldEvidence };
4
+ /** Stable diagnostic codes backed by `src/defaults/log-diagnostics.json`. */
5
+ export declare const GatewayLogCode: {
6
+ readonly ZERO_USAGE_OR_COST: "GATEWAY_ZERO_USAGE_OR_COST";
7
+ readonly FALLBACK_CHAIN_EXHAUSTED: "GATEWAY_FALLBACK_CHAIN_EXHAUSTED";
8
+ readonly FLEX_MD_EXTRACTION_FAILED: "GATEWAY_FLEX_MD_EXTRACTION_FAILED";
9
+ readonly FLEX_MD_EXTRACTION_ERROR: "GATEWAY_FLEX_MD_EXTRACTION_ERROR";
10
+ readonly DEFAULT_MODEL_SUBSTITUTED: "GATEWAY_DEFAULT_MODEL_SUBSTITUTED";
11
+ readonly RETRY_MAX_EXCEEDED: "GATEWAY_RETRY_MAX_EXCEEDED";
12
+ readonly RETRY_ATTEMPT: "GATEWAY_RETRY_ATTEMPT";
13
+ readonly OPTIMIXER_ACTIVIX_UNAVAILABLE: "GATEWAY_OPTIMIXER_ACTIVIX_UNAVAILABLE";
14
+ readonly OPTIMIXER_INIT_FAILED: "GATEWAY_OPTIMIXER_INIT_FAILED";
15
+ readonly OPTIMIXER_PREDICT_FAILED: "GATEWAY_OPTIMIXER_PREDICT_FAILED";
16
+ };
17
+ export type GatewayLogCode = (typeof GatewayLogCode)[keyof typeof GatewayLogCode];
18
+ /** Resolve packaged `defaults/log-diagnostics.json` for createLogxer diagnostics.catalogPath. */
19
+ export declare function resolveLogDiagnosticsCatalogPath(): string | undefined;
20
+ export declare function gatewayWarnCode(logger: Logxer, code: GatewayLogCode | string, identity: Partial<ActivityIdentity> | undefined, data?: LogMeta): void;
21
+ export declare function gatewayErrorCode(logger: Logxer, code: GatewayLogCode | string, identity: Partial<ActivityIdentity> | undefined, data?: LogMeta): void;
22
+ export declare function gatewayInfoCode(logger: Logxer, code: GatewayLogCode | string, identity: Partial<ActivityIdentity> | undefined, data?: LogMeta): void;
23
+ export declare function gatewayAnomalyMeta(identity: Partial<ActivityIdentity> | undefined, data?: LogMeta): LogMeta;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Logxer ≥ 4.5 — process-wide package log levels for the gateway dependency stack.
3
+ * @see node_modules/@x12i/logxer/docs/package-log-levels-stack.md
4
+ */
5
+ import { applyPackageLogLevelsFromEnv, mergePackageLogLevelsConfig } from '@x12i/logxer';
6
+ import { ROUTER_LOG_ENV_PREFIX } from '@x12i/ai-providers-router';
7
+ /** Stable ERC 2.0 env prefix — controls `AI_GATEWAY_LOGS_LEVEL`, etc. */
8
+ export const GATEWAY_LOG_ENV_PREFIX = 'AI_GATEWAY';
9
+ /** Known `envPrefix` values in this package's dependency stack (for hosts / `.env`). */
10
+ export const GATEWAY_STACK_LOG_PREFIXES = {
11
+ gateway: GATEWAY_LOG_ENV_PREFIX,
12
+ router: ROUTER_LOG_ENV_PREFIX,
13
+ flexMd: 'FLEX_MD',
14
+ optimixer: 'OPTIMIXER'
15
+ };
16
+ let packageLogLevelsInitialized = false;
17
+ /**
18
+ * Load bulk env (`LOGXER_PACKAGE_LEVELS`, `LOGXER_PACKAGE_LOGS_DEFAULT`) and merge optional host config.
19
+ * Idempotent — safe to call from `createGatewayLogger` / `initializeGatewayComponents`.
20
+ */
21
+ export function initializeGatewayPackageLogLevels(options) {
22
+ if (!packageLogLevelsInitialized) {
23
+ applyPackageLogLevelsFromEnv();
24
+ packageLogLevelsInitialized = true;
25
+ }
26
+ if (options?.packageLogLevels) {
27
+ mergePackageLogLevelsConfig(options.packageLogLevels);
28
+ }
29
+ }
30
+ /** Reset init flag (tests). */
31
+ export function resetGatewayPackageLogLevelsInit() {
32
+ packageLogLevelsInitialized = false;
33
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Logxer ≥ 4.5 — process-wide package log levels for the gateway dependency stack.
3
+ * @see node_modules/@x12i/logxer/docs/package-log-levels-stack.md
4
+ */
5
+ import { type PackageLogLevelsConfig } from '@x12i/logxer';
6
+ /** Stable ERC 2.0 env prefix — controls `AI_GATEWAY_LOGS_LEVEL`, etc. */
7
+ export declare const GATEWAY_LOG_ENV_PREFIX = "AI_GATEWAY";
8
+ /** Known `envPrefix` values in this package's dependency stack (for hosts / `.env`). */
9
+ export declare const GATEWAY_STACK_LOG_PREFIXES: {
10
+ readonly gateway: "AI_GATEWAY";
11
+ readonly router: "AI_PROVIDER_ROUTER";
12
+ readonly flexMd: "FLEX_MD";
13
+ readonly optimixer: "OPTIMIXER";
14
+ };
15
+ /**
16
+ * Load bulk env (`LOGXER_PACKAGE_LEVELS`, `LOGXER_PACKAGE_LOGS_DEFAULT`) and merge optional host config.
17
+ * Idempotent — safe to call from `createGatewayLogger` / `initializeGatewayComponents`.
18
+ */
19
+ export declare function initializeGatewayPackageLogLevels(options?: {
20
+ packageLogLevels?: PackageLogLevelsConfig;
21
+ }): void;
22
+ /** Reset init flag (tests). */
23
+ export declare function resetGatewayPackageLogLevelsInit(): void;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Gateway operational mode (prod vs dev/debug) and default model resolution.
3
3
  */
4
- import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
4
+ import { gatewayLogDebug } from './gateway-log-meta.js';
5
+ import { fieldEvidence, GatewayLogCode, gatewayWarnCode } from './gateway-log-diagnostics.js';
5
6
  /** Profile name resolved via ai-tools + {@link @x12i/ai-profiles} when catalog is enabled. */
6
7
  export const CODE_DEFAULT_MODEL = 'cheap';
7
8
  /**
@@ -69,8 +70,16 @@ export function resolveGatewayDefaultModel(defaultModelConfig, gatewayDefaultEng
69
70
  };
70
71
  }
71
72
  export function warnDefaultModelSubstitution(logger, identity, details) {
72
- logger.warn('Gateway substituted default model for request', withActivityIdentity(identity, {
73
+ gatewayWarnCode(logger, GatewayLogCode.DEFAULT_MODEL_SUBSTITUTED, identity, {
73
74
  ...details,
74
- debugKind: gatewayLogDebug.anomaly
75
- }));
75
+ debugKind: gatewayLogDebug.anomaly,
76
+ evidence: [
77
+ fieldEvidence('defaultModel', details.defaultModel),
78
+ fieldEvidence('defaultSource', details.defaultSource),
79
+ fieldEvidence('reason', details.reason),
80
+ fieldEvidence('mode', details.mode),
81
+ ...(details.originalModel ? [fieldEvidence('originalModel', details.originalModel)] : []),
82
+ ...(details.originalProvider ? [fieldEvidence('originalProvider', details.originalProvider)] : [])
83
+ ]
84
+ });
76
85
  }
@@ -5,6 +5,7 @@
5
5
  * NOTE: Retry delays use SIMPLE SLEEP (not smart rate limiting).
6
6
  * Between-calls rate limiting is handled separately in gateway-rate-limiter.ts (smart).
7
7
  */
8
+ import { exceptionEvidence, fieldEvidence, GatewayLogCode, gatewayWarnCode } from './gateway-log-diagnostics.js';
8
9
  /**
9
10
  * Determines if an error is a network error (fetch failed, DNS, connectivity)
10
11
  */
@@ -146,12 +147,18 @@ export async function invokeWithRetry(routerRequest, retryConfig, jobId, router,
146
147
  }
147
148
  // If this was the last attempt, throw the error
148
149
  if (attempt >= maxRetries) {
149
- logger.warn('Max retries exceeded', {
150
+ gatewayWarnCode(logger, GatewayLogCode.RETRY_MAX_EXCEEDED, undefined, {
150
151
  jobId,
151
152
  error: lastError.message,
152
153
  errorName: lastError.name,
153
154
  totalAttempts: attempt + 1,
154
- maxRetries
155
+ maxRetries,
156
+ evidence: [
157
+ fieldEvidence('jobId', jobId),
158
+ fieldEvidence('totalAttempts', attempt + 1),
159
+ fieldEvidence('maxRetries', maxRetries),
160
+ exceptionEvidence(lastError)
161
+ ]
155
162
  });
156
163
  throw lastError;
157
164
  }
@@ -187,7 +194,7 @@ export async function invokeWithRetry(routerRequest, retryConfig, jobId, router,
187
194
  errorType,
188
195
  delayMs: Math.round(delay)
189
196
  });
190
- logger.warn('Retrying request after error', {
197
+ gatewayWarnCode(logger, GatewayLogCode.RETRY_ATTEMPT, undefined, {
191
198
  jobId,
192
199
  error: lastError.message,
193
200
  errorName: lastError.name,
@@ -195,7 +202,15 @@ export async function invokeWithRetry(routerRequest, retryConfig, jobId, router,
195
202
  attempt: attempt + 1,
196
203
  maxRetries: maxRetries + 1,
197
204
  delayMs: Math.round(delay),
198
- statusCode
205
+ statusCode,
206
+ evidence: [
207
+ fieldEvidence('jobId', jobId),
208
+ fieldEvidence('attempt', attempt + 1),
209
+ fieldEvidence('errorType', errorType),
210
+ fieldEvidence('delayMs', Math.round(delay)),
211
+ ...(statusCode !== undefined ? [fieldEvidence('statusCode', statusCode)] : []),
212
+ exceptionEvidence(lastError)
213
+ ]
199
214
  });
200
215
  await sleep(delay);
201
216
  }