@x12i/ai-gateway 9.1.6 → 9.3.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.
- package/README.md +45 -0
- package/dist/activity-manager.d.ts +1 -0
- package/dist/activity-manager.js +7 -0
- package/dist/ai-tools-client.d.ts +20 -0
- package/dist/ai-tools-client.js +91 -0
- package/dist/flex-md-loader.d.ts +5 -0
- package/dist/flex-md-loader.js +16 -0
- package/dist/gateway-config.d.ts +2 -0
- package/dist/gateway-config.js +2 -1
- package/dist/gateway-mode.d.ts +40 -0
- package/dist/gateway-mode.js +75 -0
- package/dist/gateway-utils.d.ts +57 -1
- package/dist/gateway-utils.js +181 -12
- package/dist/gateway.d.ts +3 -0
- package/dist/gateway.js +47 -15
- package/dist/index.d.ts +6 -1
- package/dist/index.js +3 -1
- package/dist/output-contract-normalizer.d.ts +21 -0
- package/dist/output-contract-normalizer.js +121 -0
- package/dist/types.d.ts +35 -0
- package/dist-cjs/activity-manager.cjs +21 -19
- package/dist-cjs/activity-manager.d.ts +1 -0
- package/dist-cjs/ai-tools-client.cjs +91 -0
- package/dist-cjs/ai-tools-client.d.ts +20 -0
- package/dist-cjs/config/activity-tracking-config.cjs +1 -4
- package/dist-cjs/content-normalizer/content-normalizer.cjs +3 -8
- package/dist-cjs/content-normalizer/index.cjs +1 -7
- package/dist-cjs/content-normalizer/types.cjs +1 -2
- package/dist-cjs/flex-md-loader.cjs +35 -65
- package/dist-cjs/flex-md-loader.d.ts +5 -0
- package/dist-cjs/gateway-config.cjs +25 -63
- package/dist-cjs/gateway-config.d.ts +2 -0
- package/dist-cjs/gateway-conversion.cjs +10 -48
- package/dist-cjs/gateway-instructions.cjs +5 -10
- package/dist-cjs/gateway-log-meta.cjs +9 -14
- package/dist-cjs/gateway-memory.cjs +2 -6
- package/dist-cjs/gateway-messages.cjs +3 -6
- package/dist-cjs/gateway-meta.cjs +1 -4
- package/dist-cjs/gateway-mode.cjs +75 -0
- package/dist-cjs/gateway-mode.d.ts +40 -0
- package/dist-cjs/gateway-provider-auto-register.cjs +2 -38
- package/dist-cjs/gateway-provider.cjs +10 -22
- package/dist-cjs/gateway-rate-limiter-constants.cjs +2 -5
- package/dist-cjs/gateway-rate-limiter.cjs +5 -9
- package/dist-cjs/gateway-retry.cjs +6 -14
- package/dist-cjs/gateway-utils.cjs +201 -83
- package/dist-cjs/gateway-utils.d.ts +57 -1
- package/dist-cjs/gateway-validation.cjs +2 -6
- package/dist-cjs/gateway.cjs +100 -72
- package/dist-cjs/gateway.d.ts +3 -0
- package/dist-cjs/index.cjs +22 -91
- package/dist-cjs/index.d.ts +6 -1
- package/dist-cjs/instruction-errors.cjs +2 -7
- package/dist-cjs/instruction-optimizer.cjs +4 -10
- package/dist-cjs/instructions-parser.cjs +5 -10
- package/dist-cjs/logger-factory.cjs +3 -6
- package/dist-cjs/memory-path-resolution.cjs +8 -18
- package/dist-cjs/message-builder.cjs +11 -47
- package/dist-cjs/object-types-library-integration.cjs +3 -8
- package/dist-cjs/object-types-library.cjs +5 -10
- package/dist-cjs/output-auditor.cjs +1 -4
- package/dist-cjs/output-contract-normalizer.cjs +121 -0
- package/dist-cjs/output-contract-normalizer.d.ts +21 -0
- package/dist-cjs/request-report-generator.cjs +1 -4
- package/dist-cjs/response-analyzer/format-type-detector.cjs +1 -5
- package/dist-cjs/response-analyzer/index.cjs +3 -9
- package/dist-cjs/response-analyzer/object-type-detector.cjs +1 -5
- package/dist-cjs/response-analyzer/response-analyzer.cjs +6 -10
- package/dist-cjs/response-analyzer/types.cjs +1 -2
- package/dist-cjs/response-fallback-fixer.cjs +1 -4
- package/dist-cjs/runtime-objects.cjs +7 -13
- package/dist-cjs/template-parser.cjs +5 -42
- package/dist-cjs/template-render-merge.cjs +2 -6
- package/dist-cjs/troubleshooting-helper.cjs +13 -28
- package/dist-cjs/types.cjs +1 -2
- package/dist-cjs/types.d.ts +35 -0
- package/dist-cjs/usage-tracker.cjs +3 -7
- package/package.json +11 -5
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceMergedConfig, GatewayTraceRequestIds, ModelConfig } from './types.js';
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
|
+
import { type AiModelsCatalogClient, type CostCalculator } from '@x12i/ai-tools';
|
|
7
8
|
/**
|
|
8
9
|
* Generates MD5 hash of a string
|
|
9
10
|
*/
|
|
@@ -12,13 +13,17 @@ export declare function generateMD5Hash(text: string): string;
|
|
|
12
13
|
* Auto-generates taskTypeId from MD5 hash of pre-parsed instructions if not provided
|
|
13
14
|
*/
|
|
14
15
|
export declare function ensureTaskTypeId(request: ChatRequest, logger: Logxer): Promise<string>;
|
|
16
|
+
export type MergeConfigOptions = {
|
|
17
|
+
defaultModelConfig?: Record<string, unknown>;
|
|
18
|
+
catalog?: AiModelsCatalogClient | null;
|
|
19
|
+
};
|
|
15
20
|
/**
|
|
16
21
|
* Merges config with defaults
|
|
17
22
|
* Supports using internal system action defaults (internalSkill or skillAudit) when useInternalDefaults is set
|
|
18
23
|
*/
|
|
19
24
|
export declare function mergeConfig(request: ChatRequest & {
|
|
20
25
|
useInternalDefaults?: 'skill' | 'audit';
|
|
21
|
-
}, config: GatewayConfig, logger: Logxer): Promise<ChatRequest['config']>;
|
|
26
|
+
}, config: GatewayConfig, logger: Logxer, mergeOptions?: MergeConfigOptions): Promise<ChatRequest['config']>;
|
|
22
27
|
/**
|
|
23
28
|
* Maps provider/router usage objects to gateway token counts (`metadata.tokens`, Activix, trace attempts).
|
|
24
29
|
* Handles promptTokens/inputTokens, OpenAI-style snake_case, Responses-style input/output tokens, and missing total (sum prompt+completion).
|
|
@@ -43,6 +48,57 @@ export declare function extractTokenUsageFromRouterResponse(routerResponse: unkn
|
|
|
43
48
|
* Does not compute cost from tokens — adapters must populate normalized fields or raw usage.cost-style keys.
|
|
44
49
|
*/
|
|
45
50
|
export declare function extractCostUsdFromRouterResponse(routerResponse: unknown): number | undefined;
|
|
51
|
+
/** Activity billing state when token usage is recorded (Run Analysis G8). */
|
|
52
|
+
export type ActivityCostStatus = 'priced' | 'unpriced';
|
|
53
|
+
export type ResolvedActivityCost = {
|
|
54
|
+
cost?: number;
|
|
55
|
+
costStatus?: ActivityCostStatus;
|
|
56
|
+
costBreakdown?: {
|
|
57
|
+
promptCostUsd: number;
|
|
58
|
+
completionCostUsd: number;
|
|
59
|
+
cachingCostUsd?: number;
|
|
60
|
+
reasoningCostUsd?: number;
|
|
61
|
+
audioCostUsd?: number;
|
|
62
|
+
imageCostUsd?: number;
|
|
63
|
+
requestFlatCostUsd?: number;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export declare function hasNonZeroTokenUsage(tokens: {
|
|
67
|
+
prompt: number;
|
|
68
|
+
completion: number;
|
|
69
|
+
total: number;
|
|
70
|
+
}): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Gateway fallback when the router does not set `metadata.costStatus`.
|
|
73
|
+
* Prefer {@link resolveCostCompletionForActivity} at invoke boundaries.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveActivityCostCompletion(tokens: {
|
|
76
|
+
prompt: number;
|
|
77
|
+
completion: number;
|
|
78
|
+
total: number;
|
|
79
|
+
}, costUsd: number | undefined): ResolvedActivityCost;
|
|
80
|
+
/**
|
|
81
|
+
* Activity cost slice for Activix: router `metadata.costStatus` / cost wins when present;
|
|
82
|
+
* otherwise gateway applies the G8 fallback (usage + no price → `unpriced`).
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolveCostCompletionForActivity(routerResponse: unknown, tokens: {
|
|
85
|
+
prompt: number;
|
|
86
|
+
completion: number;
|
|
87
|
+
total: number;
|
|
88
|
+
}): ResolvedActivityCost;
|
|
89
|
+
export type ResolveCostCompletionOptions = {
|
|
90
|
+
mergedConfig?: unknown;
|
|
91
|
+
calculator?: CostCalculator | null;
|
|
92
|
+
calculateCost?: boolean;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Router cost passthrough, then optional @x12i/ai-tools catalog pricing when still unpriced.
|
|
96
|
+
*/
|
|
97
|
+
export declare function resolveCostCompletionWithAiTools(routerResponse: unknown, tokens: {
|
|
98
|
+
prompt: number;
|
|
99
|
+
completion: number;
|
|
100
|
+
total: number;
|
|
101
|
+
}, options?: ResolveCostCompletionOptions): Promise<ResolvedActivityCost>;
|
|
46
102
|
/**
|
|
47
103
|
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
48
104
|
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Gateway Validation Module
|
|
4
3
|
* Basic validation for clean proxy implementation
|
|
5
4
|
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.validateChatRequest = validateChatRequest;
|
|
8
|
-
exports.validateAIRequest = validateAIRequest;
|
|
9
5
|
function validateMandatoryRuntimeIdentity(request) {
|
|
10
6
|
const id = request.identity;
|
|
11
7
|
if (id === undefined || id === null || typeof id !== 'object') {
|
|
@@ -16,7 +12,7 @@ function validateMandatoryRuntimeIdentity(request) {
|
|
|
16
12
|
/**
|
|
17
13
|
* Validates ChatRequest has required fields
|
|
18
14
|
*/
|
|
19
|
-
function validateChatRequest(request) {
|
|
15
|
+
export function validateChatRequest(request) {
|
|
20
16
|
if (!request.aiRequestId) {
|
|
21
17
|
throw new Error('aiRequestId is required');
|
|
22
18
|
}
|
|
@@ -40,7 +36,7 @@ const GATEWAY_ACTION_TYPES = ['skill', 'preSkill', 'postSkill'];
|
|
|
40
36
|
/**
|
|
41
37
|
* Validates AIInvokeRequest has required fields
|
|
42
38
|
*/
|
|
43
|
-
function validateAIRequest(request) {
|
|
39
|
+
export function validateAIRequest(request) {
|
|
44
40
|
if (!request.aiRequestId) {
|
|
45
41
|
throw new Error('aiRequestId is required for AI requests');
|
|
46
42
|
}
|
package/dist-cjs/gateway.cjs
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* @x12i/ai-gateway
|
|
4
3
|
*
|
|
5
4
|
* Simplified AI Gateway - Clean proxy implementation
|
|
6
5
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
import { validateChatRequest, validateAIRequest } from './gateway-validation.js';
|
|
7
|
+
import { ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
8
|
+
import { initializeGatewayComponents } from './gateway-config.js';
|
|
9
|
+
import { buildMessages } from './message-builder.js';
|
|
10
|
+
import { extractJsonFromFlexMd } from './flex-md-loader.js';
|
|
11
|
+
import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
12
|
+
import { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
|
|
13
|
+
import { getAiToolsClient } from './ai-tools-client.js';
|
|
14
|
+
import { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
15
|
+
import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
|
|
16
|
+
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
17
|
+
import { invokeWithRetry } from './gateway-retry.js';
|
|
19
18
|
/** Error message thrown by the router when no provider is registered or specified */
|
|
20
19
|
const NO_PROVIDER_ERROR = 'No provider specified and no providers registered';
|
|
21
20
|
const NO_PROVIDER_HINT = ' Set OPEN_ROUTER_KEY (or OPENROUTER_API_KEY) in the environment to use OpenRouter, or register a provider with the router (e.g. via autoRegisterProviders or gateway config).';
|
|
@@ -27,7 +26,7 @@ function warnIfSuccessfulInvokeReportsZeroUsageOrCost(logger, identity, meta, in
|
|
|
27
26
|
const zeroCost = typeof cost === 'number' && cost === 0;
|
|
28
27
|
if (!zeroTokens && !zeroCostUsd && !zeroCost)
|
|
29
28
|
return;
|
|
30
|
-
logger.warn('Successful provider response reported zero token usage and/or zero cost; verify router adapter usage and billing metadata',
|
|
29
|
+
logger.warn('Successful provider response reported zero token usage and/or zero cost; verify router adapter usage and billing metadata', withActivityIdentity(identity, {
|
|
31
30
|
invokeKind,
|
|
32
31
|
zeroTokens,
|
|
33
32
|
zeroCostUsd,
|
|
@@ -35,28 +34,31 @@ function warnIfSuccessfulInvokeReportsZeroUsageOrCost(logger, identity, meta, in
|
|
|
35
34
|
tokens,
|
|
36
35
|
costUsd,
|
|
37
36
|
cost,
|
|
38
|
-
debugKind:
|
|
37
|
+
debugKind: gatewayLogDebug.anomaly
|
|
39
38
|
}));
|
|
40
39
|
}
|
|
41
40
|
/**
|
|
42
41
|
* Simplified AI Gateway - Clean proxy implementation
|
|
43
42
|
*/
|
|
44
|
-
class AIGateway {
|
|
43
|
+
export class AIGateway {
|
|
45
44
|
router;
|
|
46
45
|
config;
|
|
47
46
|
logger;
|
|
48
47
|
activityManager;
|
|
49
48
|
messageBuilderConfig;
|
|
49
|
+
defaultModelConfig = {};
|
|
50
50
|
_autoRegisterDone = false;
|
|
51
|
+
_aiToolsClient = null;
|
|
51
52
|
constructor(config = {}, activityManager) {
|
|
52
53
|
this.config = config;
|
|
53
54
|
this.activityManager = activityManager;
|
|
54
|
-
const components =
|
|
55
|
+
const components = initializeGatewayComponents(config);
|
|
55
56
|
this.logger = components.logger;
|
|
56
57
|
this.router = components.router;
|
|
57
58
|
this.activityManager = components.activityManager;
|
|
58
59
|
this.messageBuilderConfig = components.messageBuilderConfig;
|
|
59
|
-
|
|
60
|
+
this.defaultModelConfig = components.defaultModelConfig ?? {};
|
|
61
|
+
setGatewayRuntimeClients({
|
|
60
62
|
activix: this.activityManager?.getTracker(),
|
|
61
63
|
logger: this.logger
|
|
62
64
|
});
|
|
@@ -71,20 +73,24 @@ class AIGateway {
|
|
|
71
73
|
async invokeChat(request) {
|
|
72
74
|
const startTime = Date.now();
|
|
73
75
|
// Basic validation
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
validateChatRequest(request);
|
|
77
|
+
ensureGatewayRequestIdentity(request, undefined, this.logger);
|
|
78
|
+
setGatewayLastJobId(resolveRuntimeJobId(request));
|
|
77
79
|
// Generate simple task type ID
|
|
78
80
|
const taskTypeId = request.taskTypeId || `task-${Date.now()}`;
|
|
79
81
|
// Simple message construction
|
|
80
82
|
const messages = this.buildSimpleMessages(request);
|
|
81
83
|
// Merge config (modelConfig > request.config > gateway defaults)
|
|
82
|
-
const
|
|
84
|
+
const aiTools = await this.getAiTools();
|
|
85
|
+
const mergedConfig = await mergeConfig(request, this.config, this.logger, {
|
|
86
|
+
defaultModelConfig: this.defaultModelConfig,
|
|
87
|
+
catalog: aiTools?.catalog ?? null
|
|
88
|
+
});
|
|
83
89
|
// Activix start snapshot must match what the router receives (modelConfig-only callers omit request.config.model).
|
|
84
90
|
request._mergedRouterConfig = mergedConfig;
|
|
85
91
|
// Lazy auto-register providers from env (OPENAI_API_KEY, etc.) so consumers don't have to call init
|
|
86
92
|
if (!this._autoRegisterDone) {
|
|
87
|
-
await
|
|
93
|
+
await autoRegisterProviders(this.router, this.logger);
|
|
88
94
|
this._autoRegisterDone = true;
|
|
89
95
|
}
|
|
90
96
|
// Start activity tracking if available
|
|
@@ -111,8 +117,13 @@ class AIGateway {
|
|
|
111
117
|
},
|
|
112
118
|
mode: 'sync'
|
|
113
119
|
});
|
|
114
|
-
const costUsdChat = (0, gateway_utils_js_1.extractCostUsdFromRouterResponse)(response);
|
|
115
120
|
const metaChat = response?.metadata || {};
|
|
121
|
+
const tokensChat = extractTokenUsageFromRouterResponse(response);
|
|
122
|
+
const costCompletionChat = await resolveCostCompletionWithAiTools(response, tokensChat, {
|
|
123
|
+
mergedConfig,
|
|
124
|
+
calculator: aiTools?.calculator ?? null,
|
|
125
|
+
calculateCost: this.config.aiTools?.calculateCost
|
|
126
|
+
});
|
|
116
127
|
// Create enhanced response
|
|
117
128
|
const enhancedResponse = {
|
|
118
129
|
content: response.content || '',
|
|
@@ -120,22 +131,25 @@ class AIGateway {
|
|
|
120
131
|
aiRequestId: request.aiRequestId,
|
|
121
132
|
identity: request.identity,
|
|
122
133
|
latencyMs: Date.now() - startTime,
|
|
123
|
-
tokens:
|
|
134
|
+
tokens: tokensChat,
|
|
124
135
|
taskTypeId,
|
|
125
136
|
agentType: 'chat',
|
|
126
|
-
...(
|
|
137
|
+
...(costCompletionChat.costStatus === 'priced'
|
|
127
138
|
? {
|
|
128
|
-
costUsd:
|
|
129
|
-
...(typeof metaChat.cost === 'number'
|
|
139
|
+
costUsd: costCompletionChat.cost,
|
|
140
|
+
...(typeof metaChat.cost === 'number'
|
|
141
|
+
? { cost: metaChat.cost }
|
|
142
|
+
: { cost: costCompletionChat.cost })
|
|
130
143
|
}
|
|
131
|
-
: {})
|
|
144
|
+
: {}),
|
|
145
|
+
...(costCompletionChat.costStatus ? { costStatus: costCompletionChat.costStatus } : {})
|
|
132
146
|
}
|
|
133
147
|
};
|
|
134
148
|
// Track activity success if activity was started
|
|
135
149
|
if (activity) {
|
|
136
150
|
try {
|
|
137
151
|
await this.activityManager.logSuccess(activity, {
|
|
138
|
-
...
|
|
152
|
+
...costCompletionChat,
|
|
139
153
|
response: enhancedResponse,
|
|
140
154
|
endTime: Date.now(),
|
|
141
155
|
duration: Date.now() - startTime
|
|
@@ -170,9 +184,9 @@ class AIGateway {
|
|
|
170
184
|
async invoke(request) {
|
|
171
185
|
const startTime = Date.now();
|
|
172
186
|
// Basic validation
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
187
|
+
validateAIRequest(request);
|
|
188
|
+
ensureGatewayRequestIdentity(request, undefined, this.logger);
|
|
189
|
+
setGatewayLastJobId(resolveRuntimeJobId(request));
|
|
176
190
|
// Generate simple task type ID
|
|
177
191
|
const taskTypeId = request.taskTypeId || `task-${Date.now()}`;
|
|
178
192
|
// Resolve instructions and build messages using proper components
|
|
@@ -183,7 +197,7 @@ class AIGateway {
|
|
|
183
197
|
// Use proper instruction resolution and message building
|
|
184
198
|
let builtMessages;
|
|
185
199
|
try {
|
|
186
|
-
builtMessages = await
|
|
200
|
+
builtMessages = await buildMessages(request, this.messageBuilderConfig, {
|
|
187
201
|
parsedSnapshot
|
|
188
202
|
});
|
|
189
203
|
messages = builtMessages.messages;
|
|
@@ -228,13 +242,13 @@ class AIGateway {
|
|
|
228
242
|
failureType: 'validation-failure'
|
|
229
243
|
}, startTime);
|
|
230
244
|
}
|
|
231
|
-
const rejectMeta =
|
|
245
|
+
const rejectMeta = buildInvokeRejectionMetadata({
|
|
232
246
|
request,
|
|
233
247
|
taskTypeId,
|
|
234
248
|
startTime,
|
|
235
249
|
gatewayAiRequestId: request.aiRequestId
|
|
236
250
|
});
|
|
237
|
-
|
|
251
|
+
attachGatewayInvokeRejectionMetadata(err, rejectMeta);
|
|
238
252
|
// Re-throw the error so it propagates to the caller
|
|
239
253
|
throw err;
|
|
240
254
|
}
|
|
@@ -248,14 +262,18 @@ class AIGateway {
|
|
|
248
262
|
// Attach parsedSnapshot to request for activity tracking
|
|
249
263
|
request._parsedRequest = parsedSnapshot;
|
|
250
264
|
// Merge config (modelConfig > request.config > gateway defaults)
|
|
251
|
-
const
|
|
265
|
+
const aiTools = await this.getAiTools();
|
|
266
|
+
const mergedConfig = await mergeConfig(request, this.config, this.logger, {
|
|
267
|
+
defaultModelConfig: this.defaultModelConfig,
|
|
268
|
+
catalog: aiTools?.catalog ?? null
|
|
269
|
+
});
|
|
252
270
|
request._mergedRouterConfig = mergedConfig;
|
|
253
271
|
const diagnosticsMode = request.diagnostics?.mode;
|
|
254
272
|
const traceEnabled = diagnosticsMode === 'trace';
|
|
255
273
|
const includeRawProviderPayload = request.diagnostics?.includeRawProviderPayload === true;
|
|
256
274
|
// Lazy auto-register providers from env (OPENAI_API_KEY, etc.) so consumers don't have to call init
|
|
257
275
|
if (!this._autoRegisterDone) {
|
|
258
|
-
await
|
|
276
|
+
await autoRegisterProviders(this.router, this.logger);
|
|
259
277
|
this._autoRegisterDone = true;
|
|
260
278
|
}
|
|
261
279
|
// Start activity tracking if available
|
|
@@ -346,7 +364,7 @@ class AIGateway {
|
|
|
346
364
|
// Track per-retry attempt objects through retry hooks.
|
|
347
365
|
const attemptIndexByRetry = new Map();
|
|
348
366
|
try {
|
|
349
|
-
const result = await
|
|
367
|
+
const result = await invokeWithRetry({
|
|
350
368
|
...baseRequest,
|
|
351
369
|
request: {
|
|
352
370
|
...baseRequest.request,
|
|
@@ -386,7 +404,7 @@ class AIGateway {
|
|
|
386
404
|
const respAny = tryResp;
|
|
387
405
|
if (ok && respAny) {
|
|
388
406
|
const meta = respAny.metadata || {};
|
|
389
|
-
const tokenCounts =
|
|
407
|
+
const tokenCounts = extractTokenUsageFromRouterResponse(respAny);
|
|
390
408
|
a.usage = {
|
|
391
409
|
tokens: tokenCounts,
|
|
392
410
|
maxTokensRequested: typeof meta?.maxTokensRequested === 'number'
|
|
@@ -415,7 +433,7 @@ class AIGateway {
|
|
|
415
433
|
a.routing.requestIds = requestIds;
|
|
416
434
|
a.modelUsed =
|
|
417
435
|
meta?.modelUsed || meta?.model || respAny.model || candidate.model;
|
|
418
|
-
const attemptCostUsd =
|
|
436
|
+
const attemptCostUsd = extractCostUsdFromRouterResponse(respAny);
|
|
419
437
|
if (typeof attemptCostUsd === 'number')
|
|
420
438
|
a.costUsd = attemptCostUsd;
|
|
421
439
|
if (includeRawProviderPayload) {
|
|
@@ -480,35 +498,35 @@ class AIGateway {
|
|
|
480
498
|
let parsingMethod = undefined;
|
|
481
499
|
// Actually use flex-md parsing - extract structured data from markdown
|
|
482
500
|
try {
|
|
483
|
-
this.logger.debug('Attempting flex-md extraction',
|
|
501
|
+
this.logger.debug('Attempting flex-md extraction', withActivityIdentity(request.identity, {
|
|
484
502
|
contentLength: content.length,
|
|
485
503
|
hasInstructions: !!resolvedRequest.instructions,
|
|
486
|
-
debugKind:
|
|
504
|
+
debugKind: gatewayLogDebug.intent
|
|
487
505
|
}));
|
|
488
506
|
// Let flex-md extract structured data from the response content
|
|
489
|
-
const extractionResult = await
|
|
490
|
-
this.logger.debug('Flex-md extraction result',
|
|
507
|
+
const extractionResult = await extractJsonFromFlexMd(content, this.logger);
|
|
508
|
+
this.logger.debug('Flex-md extraction result', withActivityIdentity(request.identity, {
|
|
491
509
|
hasResult: !!extractionResult,
|
|
492
510
|
hasJson: !!(extractionResult && extractionResult.json),
|
|
493
511
|
method: extractionResult?.method,
|
|
494
512
|
jsonType: extractionResult?.json ? typeof extractionResult.json : 'none',
|
|
495
|
-
debugKind:
|
|
513
|
+
debugKind: gatewayLogDebug.state
|
|
496
514
|
}));
|
|
497
515
|
if (extractionResult && extractionResult.json) {
|
|
498
516
|
// Successfully extracted structured data
|
|
499
517
|
parsedContent = extractionResult.json;
|
|
500
|
-
this.logger.info('Flex-md extraction successful - parsed into structured object',
|
|
518
|
+
this.logger.info('Flex-md extraction successful - parsed into structured object', withActivityIdentity(request.identity, {
|
|
501
519
|
method: extractionResult.method,
|
|
502
520
|
extractedKeys: Object.keys(extractionResult.json),
|
|
503
|
-
debugKind:
|
|
521
|
+
debugKind: gatewayLogDebug.event
|
|
504
522
|
}));
|
|
505
523
|
}
|
|
506
524
|
else {
|
|
507
525
|
// Extraction failed, fall back to raw text wrapper
|
|
508
|
-
this.logger.warn('Flex-md extraction failed - no structured data extracted',
|
|
526
|
+
this.logger.warn('Flex-md extraction failed - no structured data extracted', withActivityIdentity(request.identity, {
|
|
509
527
|
hasResult: !!extractionResult,
|
|
510
528
|
method: extractionResult?.method || 'none',
|
|
511
|
-
debugKind:
|
|
529
|
+
debugKind: gatewayLogDebug.anomaly
|
|
512
530
|
}));
|
|
513
531
|
parsedContent = { rawText: content };
|
|
514
532
|
}
|
|
@@ -516,30 +534,36 @@ class AIGateway {
|
|
|
516
534
|
catch (extractionError) {
|
|
517
535
|
// Extraction failed, fall back to raw text wrapper
|
|
518
536
|
const errorMessage = extractionError instanceof Error ? extractionError.message : String(extractionError);
|
|
519
|
-
this.logger.warn('Flex-md extraction failed - flex-md library compatibility issue',
|
|
537
|
+
this.logger.warn('Flex-md extraction failed - flex-md library compatibility issue', withActivityIdentity(request.identity, {
|
|
520
538
|
error: errorMessage,
|
|
521
539
|
issue: 'flex-md uses require() in ES module context - needs fixing in flex-md-loader.ts',
|
|
522
540
|
fallback: 'using rawText wrapper',
|
|
523
|
-
debugKind:
|
|
541
|
+
debugKind: gatewayLogDebug.anomaly
|
|
524
542
|
}));
|
|
525
543
|
parsedContent = { rawText: content };
|
|
526
544
|
}
|
|
527
545
|
contentType = 'structured';
|
|
528
546
|
parsingMethod = 'flex-md';
|
|
529
|
-
|
|
547
|
+
const outputContractKeys = resolveOutputContractFieldKeys(request);
|
|
548
|
+
parsedContent = await enrichParsedContentForOutputContract(parsedContent, content, outputContractKeys, this.logger);
|
|
549
|
+
let tokens = extractTokenUsageFromRouterResponse(routerResponse);
|
|
530
550
|
if (!(tokens.prompt || tokens.completion || tokens.total)) {
|
|
531
551
|
const alt = routerResponse?.rawResponse ?? routerResponse?.raw;
|
|
532
552
|
if (alt != null && typeof alt === 'object' && alt !== routerResponse) {
|
|
533
|
-
const second =
|
|
553
|
+
const second = extractTokenUsageFromRouterResponse(alt);
|
|
534
554
|
if (second.prompt || second.completion || second.total)
|
|
535
555
|
tokens = second;
|
|
536
556
|
}
|
|
537
557
|
}
|
|
538
|
-
const
|
|
558
|
+
const costCompletion = await resolveCostCompletionWithAiTools(routerResponse, tokens, {
|
|
559
|
+
mergedConfig,
|
|
560
|
+
calculator: aiTools?.calculator ?? null,
|
|
561
|
+
calculateCost: this.config.aiTools?.calculateCost
|
|
562
|
+
});
|
|
539
563
|
const routerMetaForCost = routerResponse?.metadata || {};
|
|
540
|
-
const routingMetadataSlice =
|
|
541
|
-
const effectiveModelConfig =
|
|
542
|
-
const traceMergedRouterSnapshot = traceEnabled ?
|
|
564
|
+
const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
|
|
565
|
+
const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
|
|
566
|
+
const traceMergedRouterSnapshot = traceEnabled ? pickTraceMergedRouterConfig(mergedConfig) : undefined;
|
|
543
567
|
const enhancedResponse = {
|
|
544
568
|
content: content,
|
|
545
569
|
parsedContent: parsedContent,
|
|
@@ -554,14 +578,15 @@ class AIGateway {
|
|
|
554
578
|
parsingMethod,
|
|
555
579
|
...routingMetadataSlice,
|
|
556
580
|
...(effectiveModelConfig !== undefined ? { effectiveModelConfig } : {}),
|
|
557
|
-
...(
|
|
581
|
+
...(costCompletion.costStatus === 'priced'
|
|
558
582
|
? {
|
|
559
|
-
costUsd:
|
|
583
|
+
costUsd: costCompletion.cost,
|
|
560
584
|
...(typeof routerMetaForCost.cost === 'number'
|
|
561
585
|
? { cost: routerMetaForCost.cost }
|
|
562
|
-
: { cost:
|
|
586
|
+
: { cost: costCompletion.cost })
|
|
563
587
|
}
|
|
564
588
|
: {}),
|
|
589
|
+
...(costCompletion.costStatus ? { costStatus: costCompletion.costStatus } : {}),
|
|
565
590
|
...(traceEnabled
|
|
566
591
|
? {
|
|
567
592
|
requestIds: traceRequestIds,
|
|
@@ -582,10 +607,10 @@ class AIGateway {
|
|
|
582
607
|
const includeFullProviderBlob = diag?.includeFullProviderResponseInActivity !== false;
|
|
583
608
|
const maxFullChars = typeof diag?.activityFullResponseMaxChars === 'number' && diag.activityFullResponseMaxChars > 0
|
|
584
609
|
? diag.activityFullResponseMaxChars
|
|
585
|
-
:
|
|
610
|
+
: DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS;
|
|
586
611
|
const rawFull = routerResponse.rawResponse || routerResponse;
|
|
587
612
|
const fullResponseForActivity = includeFullProviderBlob
|
|
588
|
-
?
|
|
613
|
+
? capActivityFullResponsePayload(rawFull, maxFullChars)
|
|
589
614
|
: undefined;
|
|
590
615
|
// Create activity response with proper structure for ActivityTracker
|
|
591
616
|
const activityResponse = {
|
|
@@ -600,7 +625,7 @@ class AIGateway {
|
|
|
600
625
|
usage: tokens
|
|
601
626
|
};
|
|
602
627
|
await this.activityManager.logSuccess(activity, {
|
|
603
|
-
...
|
|
628
|
+
...costCompletion,
|
|
604
629
|
response: activityResponse,
|
|
605
630
|
endTime: Date.now(),
|
|
606
631
|
duration: Date.now() - startTime
|
|
@@ -619,17 +644,17 @@ class AIGateway {
|
|
|
619
644
|
costUsd: enhancedResponse.metadata.costUsd,
|
|
620
645
|
cost: enhancedResponse.metadata.cost
|
|
621
646
|
}, 'invoke');
|
|
622
|
-
this.logger.debug('gateway: enhancedResponse',
|
|
647
|
+
this.logger.debug('gateway: enhancedResponse', withActivityIdentity(request.identity, {
|
|
623
648
|
latencyMs: enhancedResponse.metadata?.latencyMs,
|
|
624
649
|
contentType: enhancedResponse.metadata?.contentType,
|
|
625
|
-
debugKind:
|
|
650
|
+
debugKind: gatewayLogDebug.state
|
|
626
651
|
}));
|
|
627
652
|
return enhancedResponse;
|
|
628
653
|
}
|
|
629
654
|
catch (error) {
|
|
630
655
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
631
|
-
const partial =
|
|
632
|
-
const rejectMeta =
|
|
656
|
+
const partial = tryExtractRouterLikePayloadFromErrorChain(err);
|
|
657
|
+
const rejectMeta = buildInvokeRejectionMetadata({
|
|
633
658
|
request,
|
|
634
659
|
taskTypeId,
|
|
635
660
|
startTime,
|
|
@@ -637,11 +662,11 @@ class AIGateway {
|
|
|
637
662
|
partialRouterPayload: partial,
|
|
638
663
|
gatewayAiRequestId: request.aiRequestId
|
|
639
664
|
});
|
|
640
|
-
|
|
665
|
+
attachGatewayInvokeRejectionMetadata(err, rejectMeta);
|
|
641
666
|
if (err.message.includes(NO_PROVIDER_ERROR)) {
|
|
642
667
|
const wrapped = new Error(err.message + NO_PROVIDER_HINT);
|
|
643
668
|
wrapped.cause = err;
|
|
644
|
-
|
|
669
|
+
attachGatewayInvokeRejectionMetadata(wrapped, rejectMeta);
|
|
645
670
|
throw wrapped;
|
|
646
671
|
}
|
|
647
672
|
throw err;
|
|
@@ -697,13 +722,16 @@ class AIGateway {
|
|
|
697
722
|
}
|
|
698
723
|
setActivityManager(activityManager) {
|
|
699
724
|
this.activityManager = activityManager;
|
|
700
|
-
|
|
725
|
+
setGatewayRuntimeClients({
|
|
701
726
|
activix: this.activityManager.getTracker(),
|
|
702
727
|
logger: this.logger
|
|
703
728
|
});
|
|
704
729
|
}
|
|
730
|
+
getAiTools() {
|
|
731
|
+
this._aiToolsClient ??= getAiToolsClient(this.config, this.logger);
|
|
732
|
+
return this._aiToolsClient;
|
|
733
|
+
}
|
|
705
734
|
}
|
|
706
|
-
exports.AIGateway = AIGateway;
|
|
707
735
|
function resolveRuntimeJobId(request) {
|
|
708
736
|
return request.identity.jobId || request.identity.sessionId || request.aiRequestId;
|
|
709
737
|
}
|
package/dist-cjs/gateway.d.ts
CHANGED
|
@@ -16,7 +16,9 @@ export declare class AIGateway {
|
|
|
16
16
|
private logger;
|
|
17
17
|
private activityManager?;
|
|
18
18
|
private messageBuilderConfig?;
|
|
19
|
+
private defaultModelConfig;
|
|
19
20
|
private _autoRegisterDone;
|
|
21
|
+
private _aiToolsClient;
|
|
20
22
|
constructor(config?: GatewayConfig, activityManager?: ActivityManager);
|
|
21
23
|
/**
|
|
22
24
|
* Invoke chat request (without structured output requirements)
|
|
@@ -36,4 +38,5 @@ export declare class AIGateway {
|
|
|
36
38
|
getLogger(): Logxer;
|
|
37
39
|
getActivityManager(): ActivityManager | undefined;
|
|
38
40
|
setActivityManager(activityManager: ActivityManager): void;
|
|
41
|
+
private getAiTools;
|
|
39
42
|
}
|