@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
|
@@ -1,23 +1,11 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Gateway Provider Module
|
|
4
3
|
* Handles provider management
|
|
5
4
|
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.registerProvider = registerProvider;
|
|
8
|
-
exports.unregisterProvider = unregisterProvider;
|
|
9
|
-
exports.getProvider = getProvider;
|
|
10
|
-
exports.listProviders = listProviders;
|
|
11
|
-
exports.setDefaultProvider = setDefaultProvider;
|
|
12
|
-
exports.setFallbackChain = setFallbackChain;
|
|
13
|
-
exports.addRequestInterceptor = addRequestInterceptor;
|
|
14
|
-
exports.addResponseInterceptor = addResponseInterceptor;
|
|
15
|
-
exports.checkHealth = checkHealth;
|
|
16
|
-
exports.checkAllHealth = checkAllHealth;
|
|
17
5
|
/**
|
|
18
6
|
* Register a provider with the router
|
|
19
7
|
*/
|
|
20
|
-
function registerProvider(provider, router, logger) {
|
|
8
|
+
export function registerProvider(provider, router, logger) {
|
|
21
9
|
// Get provider name - try multiple methods
|
|
22
10
|
let providerName = 'unknown';
|
|
23
11
|
if (typeof provider.getProviderName === 'function') {
|
|
@@ -103,7 +91,7 @@ function registerProvider(provider, router, logger) {
|
|
|
103
91
|
/**
|
|
104
92
|
* Unregister a provider
|
|
105
93
|
*/
|
|
106
|
-
function unregisterProvider(providerName, router, logger) {
|
|
94
|
+
export function unregisterProvider(providerName, router, logger) {
|
|
107
95
|
try {
|
|
108
96
|
// Try to access registry directly to remove the provider
|
|
109
97
|
const registry = router.getProviderRegistry();
|
|
@@ -128,7 +116,7 @@ function unregisterProvider(providerName, router, logger) {
|
|
|
128
116
|
/**
|
|
129
117
|
* Get a provider module
|
|
130
118
|
*/
|
|
131
|
-
function getProvider(providerName, router) {
|
|
119
|
+
export function getProvider(providerName, router) {
|
|
132
120
|
try {
|
|
133
121
|
const registry = router.getProviderRegistry();
|
|
134
122
|
return registry.get(providerName);
|
|
@@ -141,7 +129,7 @@ function getProvider(providerName, router) {
|
|
|
141
129
|
/**
|
|
142
130
|
* List all registered providers
|
|
143
131
|
*/
|
|
144
|
-
function listProviders(router) {
|
|
132
|
+
export function listProviders(router) {
|
|
145
133
|
return router.listProviders();
|
|
146
134
|
}
|
|
147
135
|
/**
|
|
@@ -151,7 +139,7 @@ function listProviders(router) {
|
|
|
151
139
|
* This method is kept for API compatibility but does nothing.
|
|
152
140
|
* Default provider must be set in router config during initialization.
|
|
153
141
|
*/
|
|
154
|
-
function setDefaultProvider(provider, router, logger) {
|
|
142
|
+
export function setDefaultProvider(provider, router, logger) {
|
|
155
143
|
// Router doesn't support changing default provider after initialization
|
|
156
144
|
// Default provider must be set in router config during initialization
|
|
157
145
|
logger.warn('setDefaultProvider called but router does not support changing default provider after initialization', {
|
|
@@ -166,7 +154,7 @@ function setDefaultProvider(provider, router, logger) {
|
|
|
166
154
|
* This method is kept for API compatibility but does nothing.
|
|
167
155
|
* Fallback chain must be set in router config during initialization.
|
|
168
156
|
*/
|
|
169
|
-
function setFallbackChain(chain, router, logger) {
|
|
157
|
+
export function setFallbackChain(chain, router, logger) {
|
|
170
158
|
// Router doesn't support changing fallback chain after initialization
|
|
171
159
|
// Fallback chain must be set in router config during initialization
|
|
172
160
|
logger.warn('setFallbackChain called but router does not support changing fallback chain after initialization', {
|
|
@@ -177,25 +165,25 @@ function setFallbackChain(chain, router, logger) {
|
|
|
177
165
|
/**
|
|
178
166
|
* Add request interceptor
|
|
179
167
|
*/
|
|
180
|
-
function addRequestInterceptor(interceptor, router) {
|
|
168
|
+
export function addRequestInterceptor(interceptor, router) {
|
|
181
169
|
router.addRequestInterceptor(interceptor);
|
|
182
170
|
}
|
|
183
171
|
/**
|
|
184
172
|
* Add response interceptor
|
|
185
173
|
*/
|
|
186
|
-
function addResponseInterceptor(interceptor, router) {
|
|
174
|
+
export function addResponseInterceptor(interceptor, router) {
|
|
187
175
|
router.addResponseInterceptor(interceptor);
|
|
188
176
|
}
|
|
189
177
|
/**
|
|
190
178
|
* Check health of a provider
|
|
191
179
|
*/
|
|
192
|
-
async function checkHealth(provider, router) {
|
|
180
|
+
export async function checkHealth(provider, router) {
|
|
193
181
|
return router.checkHealth(provider);
|
|
194
182
|
}
|
|
195
183
|
/**
|
|
196
184
|
* Check health of all providers
|
|
197
185
|
*/
|
|
198
|
-
async function checkAllHealth(router) {
|
|
186
|
+
export async function checkAllHealth(router) {
|
|
199
187
|
const providers = listProviders(router);
|
|
200
188
|
const results = {};
|
|
201
189
|
for (const provider of providers) {
|
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Rate Limiter Constants
|
|
4
3
|
*
|
|
5
4
|
* Default values for rate limiting configuration.
|
|
6
5
|
* These defaults ensure the system works safely even if config is missing.
|
|
7
6
|
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.DEFAULT_RATE_LIMIT_ENABLED = exports.DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS = void 0;
|
|
10
7
|
/**
|
|
11
8
|
* Default minimum interval between API calls (in milliseconds)
|
|
12
9
|
* Used when rateLimit.defaultMinIntervalMs is not specified
|
|
13
10
|
*/
|
|
14
|
-
|
|
11
|
+
export const DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS = 500;
|
|
15
12
|
/**
|
|
16
13
|
* Default enabled state for rate limiting
|
|
17
14
|
* Used when rateLimit.enabled is not specified
|
|
18
15
|
*/
|
|
19
|
-
|
|
16
|
+
export const DEFAULT_RATE_LIMIT_ENABLED = true;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Gateway Rate Limiter
|
|
4
3
|
*
|
|
@@ -12,10 +11,8 @@
|
|
|
12
11
|
* and only waits if necessary to maintain minimum intervals between separate API calls.
|
|
13
12
|
* This prevents unnecessary delays when enough time has already passed.
|
|
14
13
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const gateway_retry_js_1 = require("./gateway-retry.cjs");
|
|
18
|
-
const gateway_rate_limiter_constants_js_1 = require("./gateway-rate-limiter-constants.cjs");
|
|
14
|
+
import { sleep } from './gateway-retry.js';
|
|
15
|
+
import { DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS } from './gateway-rate-limiter-constants.js';
|
|
19
16
|
/**
|
|
20
17
|
* Smart Rate Limiter (Between-Calls Only)
|
|
21
18
|
*
|
|
@@ -27,12 +24,12 @@ const gateway_rate_limiter_constants_js_1 = require("./gateway-rate-limiter-cons
|
|
|
27
24
|
*
|
|
28
25
|
* This ensures minimum intervals between separate API calls regardless of what happens between them.
|
|
29
26
|
*/
|
|
30
|
-
class GatewayRateLimiter {
|
|
27
|
+
export class GatewayRateLimiter {
|
|
31
28
|
lastCallTimes = new Map(); // provider -> last call timestamp
|
|
32
29
|
defaultMinIntervalMs;
|
|
33
30
|
providerIntervals = new Map(); // provider -> min interval
|
|
34
31
|
logger;
|
|
35
|
-
constructor(defaultMinIntervalMs =
|
|
32
|
+
constructor(defaultMinIntervalMs = DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS, providerIntervals, logger) {
|
|
36
33
|
this.defaultMinIntervalMs = defaultMinIntervalMs;
|
|
37
34
|
this.logger = logger;
|
|
38
35
|
// Store per-provider intervals
|
|
@@ -69,7 +66,7 @@ class GatewayRateLimiter {
|
|
|
69
66
|
minIntervalMs,
|
|
70
67
|
usingProviderSpecific: this.providerIntervals.has(provider.toLowerCase())
|
|
71
68
|
});
|
|
72
|
-
await
|
|
69
|
+
await sleep(waitTime);
|
|
73
70
|
}
|
|
74
71
|
else {
|
|
75
72
|
this.logger?.verbose('Rate limit OK: enough time has passed', {
|
|
@@ -108,4 +105,3 @@ class GatewayRateLimiter {
|
|
|
108
105
|
}
|
|
109
106
|
}
|
|
110
107
|
}
|
|
111
|
-
exports.GatewayRateLimiter = GatewayRateLimiter;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Gateway Retry Module
|
|
4
3
|
* Handles retry logic for network and server errors
|
|
@@ -6,17 +5,10 @@
|
|
|
6
5
|
* NOTE: Retry delays use SIMPLE SLEEP (not smart rate limiting).
|
|
7
6
|
* Between-calls rate limiting is handled separately in gateway-rate-limiter.ts (smart).
|
|
8
7
|
*/
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.isNetworkError = isNetworkError;
|
|
11
|
-
exports.extractHttpStatusCode = extractHttpStatusCode;
|
|
12
|
-
exports.isRetryableHttpError = isRetryableHttpError;
|
|
13
|
-
exports.isRetryableError = isRetryableError;
|
|
14
|
-
exports.sleep = sleep;
|
|
15
|
-
exports.invokeWithRetry = invokeWithRetry;
|
|
16
8
|
/**
|
|
17
9
|
* Determines if an error is a network error (fetch failed, DNS, connectivity)
|
|
18
10
|
*/
|
|
19
|
-
function isNetworkError(error) {
|
|
11
|
+
export function isNetworkError(error) {
|
|
20
12
|
const message = error.message.toLowerCase();
|
|
21
13
|
const networkPatterns = [
|
|
22
14
|
'fetch failed',
|
|
@@ -35,7 +27,7 @@ function isNetworkError(error) {
|
|
|
35
27
|
/**
|
|
36
28
|
* Extracts HTTP status code from error message or error object properties
|
|
37
29
|
*/
|
|
38
|
-
function extractHttpStatusCode(error) {
|
|
30
|
+
export function extractHttpStatusCode(error) {
|
|
39
31
|
// Try to extract from error message
|
|
40
32
|
const statusMatch = error.message.match(/\b(4\d{2}|5\d{2})\b/);
|
|
41
33
|
if (statusMatch) {
|
|
@@ -59,7 +51,7 @@ function extractHttpStatusCode(error) {
|
|
|
59
51
|
* Retryable: 429 (throttling), 5xx (server errors)
|
|
60
52
|
* Non-retryable: 4xx except 429 (client errors)
|
|
61
53
|
*/
|
|
62
|
-
function isRetryableHttpError(error, statusCode) {
|
|
54
|
+
export function isRetryableHttpError(error, statusCode) {
|
|
63
55
|
// 429 (throttling) is retryable
|
|
64
56
|
if (statusCode === 429) {
|
|
65
57
|
return true;
|
|
@@ -74,7 +66,7 @@ function isRetryableHttpError(error, statusCode) {
|
|
|
74
66
|
/**
|
|
75
67
|
* Determines if an error should be retried
|
|
76
68
|
*/
|
|
77
|
-
function isRetryableError(error) {
|
|
69
|
+
export function isRetryableError(error) {
|
|
78
70
|
// Network errors are always retryable
|
|
79
71
|
if (isNetworkError(error)) {
|
|
80
72
|
return true;
|
|
@@ -94,14 +86,14 @@ function isRetryableError(error) {
|
|
|
94
86
|
/**
|
|
95
87
|
* Sleep helper for retry delays
|
|
96
88
|
*/
|
|
97
|
-
function sleep(ms) {
|
|
89
|
+
export function sleep(ms) {
|
|
98
90
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
99
91
|
}
|
|
100
92
|
/**
|
|
101
93
|
* Invokes router with retry logic for network and server errors
|
|
102
94
|
* Returns response and retry metadata
|
|
103
95
|
*/
|
|
104
|
-
async function invokeWithRetry(routerRequest, retryConfig, jobId, router, logger, hooks) {
|
|
96
|
+
export async function invokeWithRetry(routerRequest, retryConfig, jobId, router, logger, hooks) {
|
|
105
97
|
const maxRetries = retryConfig.maxRetries ?? 3;
|
|
106
98
|
const initialDelay = retryConfig.initialDelay ?? 1000;
|
|
107
99
|
const maxDelay = retryConfig.maxDelay ?? 30000;
|
|
@@ -1,75 +1,27 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Gateway Utilities Module
|
|
4
3
|
* Handles utility functions
|
|
5
4
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Object.defineProperty(o, k2, desc);
|
|
13
|
-
}) : (function(o, m, k, k2) {
|
|
14
|
-
if (k2 === undefined) k2 = k;
|
|
15
|
-
o[k2] = m[k];
|
|
16
|
-
}));
|
|
17
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
-
}) : function(o, v) {
|
|
20
|
-
o["default"] = v;
|
|
21
|
-
});
|
|
22
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
-
var ownKeys = function(o) {
|
|
24
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
-
var ar = [];
|
|
26
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
-
return ar;
|
|
28
|
-
};
|
|
29
|
-
return ownKeys(o);
|
|
30
|
-
};
|
|
31
|
-
return function (mod) {
|
|
32
|
-
if (mod && mod.__esModule) return mod;
|
|
33
|
-
var result = {};
|
|
34
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
-
__setModuleDefault(result, mod);
|
|
36
|
-
return result;
|
|
37
|
-
};
|
|
38
|
-
})();
|
|
39
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
-
exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = void 0;
|
|
41
|
-
exports.generateMD5Hash = generateMD5Hash;
|
|
42
|
-
exports.ensureTaskTypeId = ensureTaskTypeId;
|
|
43
|
-
exports.mergeConfig = mergeConfig;
|
|
44
|
-
exports.normalizeRouterUsageTokens = normalizeRouterUsageTokens;
|
|
45
|
-
exports.extractTokenUsageFromRouterResponse = extractTokenUsageFromRouterResponse;
|
|
46
|
-
exports.extractCostUsdFromRouterResponse = extractCostUsdFromRouterResponse;
|
|
47
|
-
exports.pickInvokeRoutingMetadataSlice = pickInvokeRoutingMetadataSlice;
|
|
48
|
-
exports.pickEffectiveModelConfigForMetadata = pickEffectiveModelConfigForMetadata;
|
|
49
|
-
exports.pickTraceMergedRouterConfig = pickTraceMergedRouterConfig;
|
|
50
|
-
exports.pickEffectiveModelConfigFromInvokeRequest = pickEffectiveModelConfigFromInvokeRequest;
|
|
51
|
-
exports.tryExtractRouterLikePayloadFromErrorChain = tryExtractRouterLikePayloadFromErrorChain;
|
|
52
|
-
exports.pickRequestIdsFromRouterLike = pickRequestIdsFromRouterLike;
|
|
53
|
-
exports.buildInvokeRejectionMetadata = buildInvokeRejectionMetadata;
|
|
54
|
-
exports.attachGatewayInvokeRejectionMetadata = attachGatewayInvokeRejectionMetadata;
|
|
55
|
-
exports.capActivityFullResponsePayload = capActivityFullResponsePayload;
|
|
56
|
-
const crypto = __importStar(require("crypto"));
|
|
57
|
-
const gateway_instructions_js_1 = require("./gateway-instructions.cjs");
|
|
58
|
-
const flex_md_loader_js_1 = require("./flex-md-loader.cjs");
|
|
5
|
+
import * as crypto from 'crypto';
|
|
6
|
+
import { ModelResolutionError } from '@x12i/ai-tools';
|
|
7
|
+
import { getPreParsedInstructions } from './gateway-instructions.js';
|
|
8
|
+
import { getModelMaxTokensFromFlexMd } from './flex-md-loader.js';
|
|
9
|
+
import { applyModelResolution } from './ai-tools-client.js';
|
|
10
|
+
import { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, warnDefaultModelSubstitution } from './gateway-mode.js';
|
|
59
11
|
/**
|
|
60
12
|
* Generates MD5 hash of a string
|
|
61
13
|
*/
|
|
62
|
-
function generateMD5Hash(text) {
|
|
14
|
+
export function generateMD5Hash(text) {
|
|
63
15
|
return crypto.createHash('md5').update(text).digest('hex');
|
|
64
16
|
}
|
|
65
17
|
/**
|
|
66
18
|
* Auto-generates taskTypeId from MD5 hash of pre-parsed instructions if not provided
|
|
67
19
|
*/
|
|
68
|
-
async function ensureTaskTypeId(request, logger) {
|
|
20
|
+
export async function ensureTaskTypeId(request, logger) {
|
|
69
21
|
if (request.taskTypeId) {
|
|
70
22
|
return request.taskTypeId;
|
|
71
23
|
}
|
|
72
|
-
const preParsedInstructions =
|
|
24
|
+
const preParsedInstructions = getPreParsedInstructions(request.instructions);
|
|
73
25
|
// Generate MD5 hash
|
|
74
26
|
const taskTypeId = generateMD5Hash(preParsedInstructions);
|
|
75
27
|
logger.debug('Auto-generated taskTypeId from instructions MD5 hash', {
|
|
@@ -80,11 +32,34 @@ async function ensureTaskTypeId(request, logger) {
|
|
|
80
32
|
});
|
|
81
33
|
return taskTypeId;
|
|
82
34
|
}
|
|
35
|
+
function applyGatewayDefaultToMerged(merged, defaults, config) {
|
|
36
|
+
merged.model = defaults.model;
|
|
37
|
+
if (defaults.provider) {
|
|
38
|
+
merged.provider = defaults.provider;
|
|
39
|
+
}
|
|
40
|
+
else if (!merged.provider) {
|
|
41
|
+
merged.provider = config.defaultEngine;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, reason, original) {
|
|
45
|
+
const operationalMode = getGatewayOperationalMode(config);
|
|
46
|
+
const defaults = resolveGatewayDefaultModel(mergeOptions?.defaultModelConfig, config.defaultEngine);
|
|
47
|
+
warnDefaultModelSubstitution(logger, request.identity, {
|
|
48
|
+
reason,
|
|
49
|
+
mode: operationalMode,
|
|
50
|
+
defaultSource: defaults.source,
|
|
51
|
+
defaultProvider: defaults.provider ?? merged.provider,
|
|
52
|
+
defaultModel: defaults.model,
|
|
53
|
+
originalProvider: original?.provider ?? merged.provider,
|
|
54
|
+
originalModel: original?.model
|
|
55
|
+
});
|
|
56
|
+
applyGatewayDefaultToMerged(merged, defaults, config);
|
|
57
|
+
}
|
|
83
58
|
/**
|
|
84
59
|
* Merges config with defaults
|
|
85
60
|
* Supports using internal system action defaults (internalSkill or skillAudit) when useInternalDefaults is set
|
|
86
61
|
*/
|
|
87
|
-
async function mergeConfig(request, config, logger) {
|
|
62
|
+
export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
88
63
|
const useInternalDefaults = request.useInternalDefaults;
|
|
89
64
|
const internalDefaults = useInternalDefaults
|
|
90
65
|
? (useInternalDefaults === 'skill'
|
|
@@ -103,8 +78,8 @@ async function mergeConfig(request, config, logger) {
|
|
|
103
78
|
useInternalDefaults,
|
|
104
79
|
hasInternalDefaults: !!internalDefaults
|
|
105
80
|
});
|
|
106
|
-
|
|
107
|
-
const
|
|
81
|
+
const operationalMode = getGatewayOperationalMode(config);
|
|
82
|
+
const resolveModels = config.aiTools?.resolveModels !== false;
|
|
108
83
|
// Priority: modelConfig > request.config > internalSystemActions[useInternalDefaults] > gateway defaults
|
|
109
84
|
// First, merge modelConfig into a config-like object if present
|
|
110
85
|
const modelConfigAsConfig = request.modelConfig ? {
|
|
@@ -138,18 +113,67 @@ async function mergeConfig(request, config, logger) {
|
|
|
138
113
|
...request.config,
|
|
139
114
|
// ModelConfig overrides (highest priority) - merge only defined values
|
|
140
115
|
...(modelConfigAsConfig ? Object.fromEntries(Object.entries(modelConfigAsConfig).filter(([_, value]) => value !== undefined)) : {}),
|
|
141
|
-
//
|
|
142
|
-
model: modelConfigAsConfig?.model || request.config?.model || internalDefaults?.model
|
|
116
|
+
// Model resolved below (catalog, default chain, or explicit pass-through)
|
|
117
|
+
model: modelConfigAsConfig?.model || request.config?.model || internalDefaults?.model,
|
|
143
118
|
// Ensure provider is set: modelConfig > request.config > internalDefaults > gateway default
|
|
144
|
-
// Provider is required for router to know which provider to use
|
|
145
119
|
provider: modelConfigAsConfig?.provider || request.config?.provider || internalDefaults?.engine || config.defaultEngine
|
|
146
120
|
};
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
121
|
+
const explicitModel = merged.model;
|
|
122
|
+
const originalProvider = merged.provider;
|
|
123
|
+
const originalModel = explicitModel;
|
|
124
|
+
if (!explicitModel) {
|
|
125
|
+
await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'no_model_provided');
|
|
126
|
+
}
|
|
127
|
+
else if (resolveModels && mergeOptions?.catalog) {
|
|
128
|
+
try {
|
|
129
|
+
const resolution = await mergeOptions.catalog.resolveModel({
|
|
130
|
+
provider: merged.provider,
|
|
131
|
+
model: explicitModel
|
|
132
|
+
});
|
|
133
|
+
if (resolution.found) {
|
|
134
|
+
applyModelResolution(merged, resolution, config.defaultEngine);
|
|
135
|
+
request._modelResolution = {
|
|
136
|
+
modelId: resolution.modelId,
|
|
137
|
+
routedViaOpenRouter: resolution.routedViaOpenRouter,
|
|
138
|
+
confidence: resolution.confidence,
|
|
139
|
+
resolvedVia: resolution.resolvedVia,
|
|
140
|
+
originalProvider,
|
|
141
|
+
originalModel
|
|
142
|
+
};
|
|
143
|
+
logger.verbose('Catalog resolved model name', {
|
|
144
|
+
jobId: request.identity.jobId,
|
|
145
|
+
originalModel,
|
|
146
|
+
resolvedModelId: resolution.modelId,
|
|
147
|
+
provider: merged.provider,
|
|
148
|
+
model: merged.model,
|
|
149
|
+
confidence: resolution.confidence,
|
|
150
|
+
resolvedVia: resolution.resolvedVia
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else if (isProdGatewayMode(operationalMode)) {
|
|
154
|
+
await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'model_resolution_failed', { provider: originalProvider, model: originalModel });
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
throw new ModelResolutionError({ provider: merged.provider, model: explicitModel }, resolution);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (error instanceof ModelResolutionError) {
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
if (isProdGatewayMode(operationalMode)) {
|
|
165
|
+
await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else if (resolveModels && !mergeOptions?.catalog && isProdGatewayMode(operationalMode)) {
|
|
173
|
+
await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
|
|
174
|
+
}
|
|
175
|
+
if (!merged.model) {
|
|
176
|
+
await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'no_model_provided');
|
|
153
177
|
}
|
|
154
178
|
// Auto-get maxTokens from flex-md if not explicitly set in ANY config source
|
|
155
179
|
// Check all possible sources: request.config, internalDefaults, gateway config
|
|
@@ -159,7 +183,7 @@ async function mergeConfig(request, config, logger) {
|
|
|
159
183
|
if (!maxTokensExplicitlySet && merged.model && merged.provider) {
|
|
160
184
|
// Try to get maxTokens from flex-md
|
|
161
185
|
try {
|
|
162
|
-
const flexMdMaxTokens = await
|
|
186
|
+
const flexMdMaxTokens = await getModelMaxTokensFromFlexMd(merged.provider, merged.model);
|
|
163
187
|
if (flexMdMaxTokens && flexMdMaxTokens > 0) {
|
|
164
188
|
merged.maxTokens = flexMdMaxTokens;
|
|
165
189
|
logger.debug('Using maxTokens from flex-md', {
|
|
@@ -249,7 +273,7 @@ function isNonZeroTokenCount(n) {
|
|
|
249
273
|
* Maps provider/router usage objects to gateway token counts (`metadata.tokens`, Activix, trace attempts).
|
|
250
274
|
* Handles promptTokens/inputTokens, OpenAI-style snake_case, Responses-style input/output tokens, and missing total (sum prompt+completion).
|
|
251
275
|
*/
|
|
252
|
-
function normalizeRouterUsageTokens(usage) {
|
|
276
|
+
export function normalizeRouterUsageTokens(usage) {
|
|
253
277
|
if (usage == null || typeof usage !== 'object')
|
|
254
278
|
return undefined;
|
|
255
279
|
const u = usage;
|
|
@@ -297,7 +321,7 @@ function firstNonZeroUsageFromBuckets(buckets) {
|
|
|
297
321
|
* Reads token usage from every stable location the router may populate (see docs/PROVIDERS_ROUTER_DIAGNOSTICS_TRACE_REQUIREMENTS.md).
|
|
298
322
|
* Prefers the raw/provider body (`rawResponse` / `raw`) when it carries non-zero usage before re-reading the outer envelope.
|
|
299
323
|
*/
|
|
300
|
-
function extractTokenUsageFromRouterResponse(routerResponse) {
|
|
324
|
+
export function extractTokenUsageFromRouterResponse(routerResponse) {
|
|
301
325
|
const zeros = { prompt: 0, completion: 0, total: 0 };
|
|
302
326
|
if (routerResponse == null || typeof routerResponse !== 'object') {
|
|
303
327
|
return zeros;
|
|
@@ -324,7 +348,7 @@ function extractTokenUsageFromRouterResponse(routerResponse) {
|
|
|
324
348
|
* metadata.attempts[].costUsd, response root, then common raw payload locations.
|
|
325
349
|
* Does not compute cost from tokens — adapters must populate normalized fields or raw usage.cost-style keys.
|
|
326
350
|
*/
|
|
327
|
-
function extractCostUsdFromRouterResponse(routerResponse) {
|
|
351
|
+
export function extractCostUsdFromRouterResponse(routerResponse) {
|
|
328
352
|
if (routerResponse == null || typeof routerResponse !== 'object')
|
|
329
353
|
return undefined;
|
|
330
354
|
const r = routerResponse;
|
|
@@ -366,11 +390,105 @@ function extractCostUsdFromRouterResponse(routerResponse) {
|
|
|
366
390
|
}
|
|
367
391
|
return undefined;
|
|
368
392
|
}
|
|
393
|
+
export function hasNonZeroTokenUsage(tokens) {
|
|
394
|
+
return !!(tokens.prompt || tokens.completion || tokens.total);
|
|
395
|
+
}
|
|
396
|
+
function pickRouterCostStatus(routerResponse) {
|
|
397
|
+
if (routerResponse == null || typeof routerResponse !== 'object')
|
|
398
|
+
return undefined;
|
|
399
|
+
const r = routerResponse;
|
|
400
|
+
const meta = r.metadata != null && typeof r.metadata === 'object'
|
|
401
|
+
? r.metadata
|
|
402
|
+
: undefined;
|
|
403
|
+
const status = meta?.costStatus ?? r.costStatus;
|
|
404
|
+
return status === 'priced' || status === 'unpriced' ? status : undefined;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Gateway fallback when the router does not set `metadata.costStatus`.
|
|
408
|
+
* Prefer {@link resolveCostCompletionForActivity} at invoke boundaries.
|
|
409
|
+
*/
|
|
410
|
+
export function resolveActivityCostCompletion(tokens, costUsd) {
|
|
411
|
+
if (typeof costUsd === 'number' && Number.isFinite(costUsd)) {
|
|
412
|
+
return { cost: costUsd, costStatus: 'priced' };
|
|
413
|
+
}
|
|
414
|
+
if (hasNonZeroTokenUsage(tokens)) {
|
|
415
|
+
return { costStatus: 'unpriced' };
|
|
416
|
+
}
|
|
417
|
+
return {};
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Activity cost slice for Activix: router `metadata.costStatus` / cost wins when present;
|
|
421
|
+
* otherwise gateway applies the G8 fallback (usage + no price → `unpriced`).
|
|
422
|
+
*/
|
|
423
|
+
export function resolveCostCompletionForActivity(routerResponse, tokens) {
|
|
424
|
+
const routerStatus = pickRouterCostStatus(routerResponse);
|
|
425
|
+
const costUsd = extractCostUsdFromRouterResponse(routerResponse);
|
|
426
|
+
if (routerStatus === 'priced') {
|
|
427
|
+
return {
|
|
428
|
+
...(typeof costUsd === 'number' && Number.isFinite(costUsd) ? { cost: costUsd } : {}),
|
|
429
|
+
costStatus: 'priced'
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (routerStatus === 'unpriced') {
|
|
433
|
+
return { costStatus: 'unpriced' };
|
|
434
|
+
}
|
|
435
|
+
return resolveActivityCostCompletion(tokens, costUsd);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Router cost passthrough, then optional @x12i/ai-tools catalog pricing when still unpriced.
|
|
439
|
+
*/
|
|
440
|
+
export async function resolveCostCompletionWithAiTools(routerResponse, tokens, options) {
|
|
441
|
+
const routerStatus = pickRouterCostStatus(routerResponse);
|
|
442
|
+
const base = resolveCostCompletionForActivity(routerResponse, tokens);
|
|
443
|
+
if (base.costStatus === 'priced') {
|
|
444
|
+
return base;
|
|
445
|
+
}
|
|
446
|
+
if (routerStatus === 'unpriced') {
|
|
447
|
+
return base;
|
|
448
|
+
}
|
|
449
|
+
if (options?.calculateCost === false || !options?.calculator) {
|
|
450
|
+
return base;
|
|
451
|
+
}
|
|
452
|
+
if (!hasNonZeroTokenUsage(tokens)) {
|
|
453
|
+
return base;
|
|
454
|
+
}
|
|
455
|
+
const routing = pickInvokeRoutingMetadataSlice(routerResponse, options.mergedConfig);
|
|
456
|
+
const cfg = options.mergedConfig != null && typeof options.mergedConfig === 'object'
|
|
457
|
+
? options.mergedConfig
|
|
458
|
+
: {};
|
|
459
|
+
const provider = routing.provider ?? cfg.provider;
|
|
460
|
+
const modelUsed = routing.modelUsed ?? cfg.model;
|
|
461
|
+
if (!provider || !modelUsed) {
|
|
462
|
+
return base;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
const result = await options.calculator.calculate({
|
|
466
|
+
tokens: {
|
|
467
|
+
prompt: tokens.prompt,
|
|
468
|
+
completion: tokens.completion,
|
|
469
|
+
total: tokens.total
|
|
470
|
+
},
|
|
471
|
+
provider,
|
|
472
|
+
modelUsed
|
|
473
|
+
});
|
|
474
|
+
if (typeof result.cost === 'number' && Number.isFinite(result.cost)) {
|
|
475
|
+
return {
|
|
476
|
+
cost: result.cost,
|
|
477
|
+
costStatus: 'priced',
|
|
478
|
+
...(result.breakdown ? { costBreakdown: result.breakdown } : {})
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
// Keep router/gateway unpriced fallback
|
|
484
|
+
}
|
|
485
|
+
return base;
|
|
486
|
+
}
|
|
369
487
|
/**
|
|
370
488
|
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
371
489
|
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
|
372
490
|
*/
|
|
373
|
-
function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
|
|
491
|
+
export function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
|
|
374
492
|
const rr = routerResponse != null && typeof routerResponse === 'object' ? routerResponse : {};
|
|
375
493
|
const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
|
|
376
494
|
const cfg = mergedConfig != null && typeof mergedConfig === 'object' ? mergedConfig : {};
|
|
@@ -396,7 +514,7 @@ function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
|
|
|
396
514
|
/**
|
|
397
515
|
* Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
|
|
398
516
|
*/
|
|
399
|
-
function pickEffectiveModelConfigForMetadata(mergedConfig) {
|
|
517
|
+
export function pickEffectiveModelConfigForMetadata(mergedConfig) {
|
|
400
518
|
if (mergedConfig == null || typeof mergedConfig !== 'object')
|
|
401
519
|
return undefined;
|
|
402
520
|
const c = mergedConfig;
|
|
@@ -419,7 +537,7 @@ const TRACE_MERGED_ROUTER_NUMERIC_KEYS = [
|
|
|
419
537
|
/**
|
|
420
538
|
* Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
|
|
421
539
|
*/
|
|
422
|
-
function pickTraceMergedRouterConfig(mergedConfig) {
|
|
540
|
+
export function pickTraceMergedRouterConfig(mergedConfig) {
|
|
423
541
|
if (mergedConfig == null || typeof mergedConfig !== 'object')
|
|
424
542
|
return undefined;
|
|
425
543
|
const c = mergedConfig;
|
|
@@ -448,7 +566,7 @@ const EFFECTIVE_MODEL_CONFIG_KEYS = ['model', 'modelId', 'provider', 'temperatur
|
|
|
448
566
|
* Allowlisted generation fields from request only (before mergeConfig / flex-md).
|
|
449
567
|
* Priority matches mergeConfig: modelConfig overrides request.config per key.
|
|
450
568
|
*/
|
|
451
|
-
function pickEffectiveModelConfigFromInvokeRequest(request) {
|
|
569
|
+
export function pickEffectiveModelConfigFromInvokeRequest(request) {
|
|
452
570
|
const cfg = (request.config ?? {});
|
|
453
571
|
const mc = (request.modelConfig ?? {});
|
|
454
572
|
const out = {};
|
|
@@ -476,7 +594,7 @@ function isRouterLikeEnvelope(value) {
|
|
|
476
594
|
* Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
|
|
477
595
|
* to find a router-shaped object for token / correlation extraction.
|
|
478
596
|
*/
|
|
479
|
-
function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
|
|
597
|
+
export function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
|
|
480
598
|
const seen = new Set();
|
|
481
599
|
let cur = error;
|
|
482
600
|
for (let i = 0; i < maxDepth && cur != null; i++) {
|
|
@@ -497,7 +615,7 @@ function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
|
|
|
497
615
|
}
|
|
498
616
|
return undefined;
|
|
499
617
|
}
|
|
500
|
-
function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
|
|
618
|
+
export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
|
|
501
619
|
if (typeof gatewayAiRequestId !== 'string' || gatewayAiRequestId.length === 0) {
|
|
502
620
|
return undefined;
|
|
503
621
|
}
|
|
@@ -523,7 +641,7 @@ function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
|
|
|
523
641
|
}
|
|
524
642
|
return out;
|
|
525
643
|
}
|
|
526
|
-
function buildInvokeRejectionMetadata(args) {
|
|
644
|
+
export function buildInvokeRejectionMetadata(args) {
|
|
527
645
|
const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
|
|
528
646
|
const partial = args.partialRouterPayload;
|
|
529
647
|
const mc = args.mergedConfig;
|
|
@@ -548,16 +666,16 @@ function buildInvokeRejectionMetadata(args) {
|
|
|
548
666
|
...(mc === undefined ? { mergeConfigUnavailable: true } : {})
|
|
549
667
|
};
|
|
550
668
|
}
|
|
551
|
-
function attachGatewayInvokeRejectionMetadata(err, metadata) {
|
|
669
|
+
export function attachGatewayInvokeRejectionMetadata(err, metadata) {
|
|
552
670
|
err.metadata = metadata;
|
|
553
671
|
}
|
|
554
672
|
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
555
|
-
|
|
673
|
+
export const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
|
|
556
674
|
/**
|
|
557
675
|
* Size-cap a provider/router payload before storing on an activity record.
|
|
558
676
|
* Non-serializable values become a small marker object instead of throwing.
|
|
559
677
|
*/
|
|
560
|
-
function capActivityFullResponsePayload(payload, maxChars =
|
|
678
|
+
export function capActivityFullResponsePayload(payload, maxChars = DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS) {
|
|
561
679
|
if (payload == null)
|
|
562
680
|
return payload;
|
|
563
681
|
let serialized;
|