@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.
- package/dist/defaults/log-diagnostics.json +176 -0
- package/dist/gateway-config.js +10 -1
- package/dist/gateway-log-diagnostics.d.ts +23 -0
- package/dist/gateway-log-diagnostics.js +72 -0
- package/dist/gateway-log-levels.d.ts +23 -0
- package/dist/gateway-log-levels.js +33 -0
- package/dist/gateway-mode.js +13 -4
- package/dist/gateway-retry.js +19 -4
- package/dist/gateway.js +44 -18
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -2
- package/dist/logger-factory.d.ts +11 -3
- package/dist/logger-factory.js +32 -6
- package/dist/optimixer-manager.js +16 -6
- package/dist/runtime-objects.d.ts +1 -1
- package/dist/runtime-objects.js +10 -1
- package/dist/types.d.ts +8 -1
- package/dist-cjs/defaults/log-diagnostics.json +176 -0
- package/dist-cjs/gateway-config.cjs +10 -1
- package/dist-cjs/gateway-log-diagnostics.cjs +72 -0
- package/dist-cjs/gateway-log-diagnostics.d.ts +23 -0
- package/dist-cjs/gateway-log-levels.cjs +33 -0
- package/dist-cjs/gateway-log-levels.d.ts +23 -0
- package/dist-cjs/gateway-mode.cjs +13 -4
- package/dist-cjs/gateway-retry.cjs +19 -4
- package/dist-cjs/gateway.cjs +44 -18
- package/dist-cjs/index.cjs +5 -2
- package/dist-cjs/index.d.ts +7 -3
- package/dist-cjs/logger-factory.cjs +32 -6
- package/dist-cjs/logger-factory.d.ts +11 -3
- package/dist-cjs/optimixer-manager.cjs +16 -6
- package/dist-cjs/runtime-objects.cjs +10 -1
- package/dist-cjs/runtime-objects.d.ts +1 -1
- package/dist-cjs/types.d.ts +8 -1
- package/package.json +5 -5
package/dist/logger-factory.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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;
|
package/dist/runtime-objects.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|