@x12i/ai-gateway 9.2.0 → 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.
Files changed (69) hide show
  1. package/README.md +45 -0
  2. package/dist/ai-tools-client.d.ts +20 -0
  3. package/dist/ai-tools-client.js +91 -0
  4. package/dist/gateway-config.d.ts +2 -0
  5. package/dist/gateway-config.js +2 -1
  6. package/dist/gateway-mode.d.ts +40 -0
  7. package/dist/gateway-mode.js +75 -0
  8. package/dist/gateway-utils.d.ts +28 -1
  9. package/dist/gateway-utils.js +137 -12
  10. package/dist/gateway.d.ts +3 -0
  11. package/dist/gateway.js +29 -5
  12. package/dist/index.d.ts +3 -1
  13. package/dist/index.js +2 -1
  14. package/dist/types.d.ts +21 -0
  15. package/dist-cjs/activity-manager.cjs +14 -19
  16. package/dist-cjs/ai-tools-client.cjs +91 -0
  17. package/dist-cjs/ai-tools-client.d.ts +20 -0
  18. package/dist-cjs/config/activity-tracking-config.cjs +1 -4
  19. package/dist-cjs/content-normalizer/content-normalizer.cjs +3 -8
  20. package/dist-cjs/content-normalizer/index.cjs +1 -7
  21. package/dist-cjs/content-normalizer/types.cjs +1 -2
  22. package/dist-cjs/flex-md-loader.cjs +20 -67
  23. package/dist-cjs/gateway-config.cjs +25 -63
  24. package/dist-cjs/gateway-config.d.ts +2 -0
  25. package/dist-cjs/gateway-conversion.cjs +10 -48
  26. package/dist-cjs/gateway-instructions.cjs +5 -10
  27. package/dist-cjs/gateway-log-meta.cjs +9 -14
  28. package/dist-cjs/gateway-memory.cjs +2 -6
  29. package/dist-cjs/gateway-messages.cjs +3 -6
  30. package/dist-cjs/gateway-meta.cjs +1 -4
  31. package/dist-cjs/gateway-mode.cjs +75 -0
  32. package/dist-cjs/gateway-mode.d.ts +40 -0
  33. package/dist-cjs/gateway-provider-auto-register.cjs +2 -38
  34. package/dist-cjs/gateway-provider.cjs +10 -22
  35. package/dist-cjs/gateway-rate-limiter-constants.cjs +2 -5
  36. package/dist-cjs/gateway-rate-limiter.cjs +5 -9
  37. package/dist-cjs/gateway-retry.cjs +6 -14
  38. package/dist-cjs/gateway-utils.cjs +160 -89
  39. package/dist-cjs/gateway-utils.d.ts +28 -1
  40. package/dist-cjs/gateway-validation.cjs +2 -6
  41. package/dist-cjs/gateway.cjs +86 -66
  42. package/dist-cjs/gateway.d.ts +3 -0
  43. package/dist-cjs/index.cjs +22 -98
  44. package/dist-cjs/index.d.ts +3 -1
  45. package/dist-cjs/instruction-errors.cjs +2 -7
  46. package/dist-cjs/instruction-optimizer.cjs +4 -10
  47. package/dist-cjs/instructions-parser.cjs +5 -10
  48. package/dist-cjs/logger-factory.cjs +3 -6
  49. package/dist-cjs/memory-path-resolution.cjs +8 -18
  50. package/dist-cjs/message-builder.cjs +11 -47
  51. package/dist-cjs/object-types-library-integration.cjs +3 -8
  52. package/dist-cjs/object-types-library.cjs +5 -10
  53. package/dist-cjs/output-auditor.cjs +1 -4
  54. package/dist-cjs/output-contract-normalizer.cjs +9 -14
  55. package/dist-cjs/request-report-generator.cjs +1 -4
  56. package/dist-cjs/response-analyzer/format-type-detector.cjs +1 -5
  57. package/dist-cjs/response-analyzer/index.cjs +3 -9
  58. package/dist-cjs/response-analyzer/object-type-detector.cjs +1 -5
  59. package/dist-cjs/response-analyzer/response-analyzer.cjs +6 -10
  60. package/dist-cjs/response-analyzer/types.cjs +1 -2
  61. package/dist-cjs/response-fallback-fixer.cjs +1 -4
  62. package/dist-cjs/runtime-objects.cjs +7 -13
  63. package/dist-cjs/template-parser.cjs +5 -42
  64. package/dist-cjs/template-render-merge.cjs +2 -6
  65. package/dist-cjs/troubleshooting-helper.cjs +13 -28
  66. package/dist-cjs/types.cjs +1 -2
  67. package/dist-cjs/types.d.ts +21 -0
  68. package/dist-cjs/usage-tracker.cjs +3 -7
  69. 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
- exports.DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS = 500;
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
- exports.DEFAULT_RATE_LIMIT_ENABLED = true;
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
- Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.GatewayRateLimiter = void 0;
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 = gateway_rate_limiter_constants_js_1.DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS, providerIntervals, logger) {
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 (0, gateway_retry_js_1.sleep)(waitTime);
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,78 +1,27 @@
1
- "use strict";
2
1
  /**
3
2
  * Gateway Utilities Module
4
3
  * Handles utility functions
5
4
  */
6
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
- if (k2 === undefined) k2 = k;
8
- var desc = Object.getOwnPropertyDescriptor(m, k);
9
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
- desc = { enumerable: true, get: function() { return m[k]; } };
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.hasNonZeroTokenUsage = hasNonZeroTokenUsage;
48
- exports.resolveActivityCostCompletion = resolveActivityCostCompletion;
49
- exports.resolveCostCompletionForActivity = resolveCostCompletionForActivity;
50
- exports.pickInvokeRoutingMetadataSlice = pickInvokeRoutingMetadataSlice;
51
- exports.pickEffectiveModelConfigForMetadata = pickEffectiveModelConfigForMetadata;
52
- exports.pickTraceMergedRouterConfig = pickTraceMergedRouterConfig;
53
- exports.pickEffectiveModelConfigFromInvokeRequest = pickEffectiveModelConfigFromInvokeRequest;
54
- exports.tryExtractRouterLikePayloadFromErrorChain = tryExtractRouterLikePayloadFromErrorChain;
55
- exports.pickRequestIdsFromRouterLike = pickRequestIdsFromRouterLike;
56
- exports.buildInvokeRejectionMetadata = buildInvokeRejectionMetadata;
57
- exports.attachGatewayInvokeRejectionMetadata = attachGatewayInvokeRejectionMetadata;
58
- exports.capActivityFullResponsePayload = capActivityFullResponsePayload;
59
- const crypto = __importStar(require("crypto"));
60
- const gateway_instructions_js_1 = require("./gateway-instructions.cjs");
61
- 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';
62
11
  /**
63
12
  * Generates MD5 hash of a string
64
13
  */
65
- function generateMD5Hash(text) {
14
+ export function generateMD5Hash(text) {
66
15
  return crypto.createHash('md5').update(text).digest('hex');
67
16
  }
68
17
  /**
69
18
  * Auto-generates taskTypeId from MD5 hash of pre-parsed instructions if not provided
70
19
  */
71
- async function ensureTaskTypeId(request, logger) {
20
+ export async function ensureTaskTypeId(request, logger) {
72
21
  if (request.taskTypeId) {
73
22
  return request.taskTypeId;
74
23
  }
75
- const preParsedInstructions = (0, gateway_instructions_js_1.getPreParsedInstructions)(request.instructions);
24
+ const preParsedInstructions = getPreParsedInstructions(request.instructions);
76
25
  // Generate MD5 hash
77
26
  const taskTypeId = generateMD5Hash(preParsedInstructions);
78
27
  logger.debug('Auto-generated taskTypeId from instructions MD5 hash', {
@@ -83,11 +32,34 @@ async function ensureTaskTypeId(request, logger) {
83
32
  });
84
33
  return taskTypeId;
85
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
+ }
86
58
  /**
87
59
  * Merges config with defaults
88
60
  * Supports using internal system action defaults (internalSkill or skillAudit) when useInternalDefaults is set
89
61
  */
90
- async function mergeConfig(request, config, logger) {
62
+ export async function mergeConfig(request, config, logger, mergeOptions) {
91
63
  const useInternalDefaults = request.useInternalDefaults;
92
64
  const internalDefaults = useInternalDefaults
93
65
  ? (useInternalDefaults === 'skill'
@@ -106,8 +78,8 @@ async function mergeConfig(request, config, logger) {
106
78
  useInternalDefaults,
107
79
  hasInternalDefaults: !!internalDefaults
108
80
  });
109
- // Default model to "gpt-5-nano" if nothing is provided (most permissive - always works)
110
- const defaultModel = 'gpt-5-nano';
81
+ const operationalMode = getGatewayOperationalMode(config);
82
+ const resolveModels = config.aiTools?.resolveModels !== false;
111
83
  // Priority: modelConfig > request.config > internalSystemActions[useInternalDefaults] > gateway defaults
112
84
  // First, merge modelConfig into a config-like object if present
113
85
  const modelConfigAsConfig = request.modelConfig ? {
@@ -141,18 +113,67 @@ async function mergeConfig(request, config, logger) {
141
113
  ...request.config,
142
114
  // ModelConfig overrides (highest priority) - merge only defined values
143
115
  ...(modelConfigAsConfig ? Object.fromEntries(Object.entries(modelConfigAsConfig).filter(([_, value]) => value !== undefined)) : {}),
144
- // Ensure model is set: modelConfig > request.config > internalDefaults > default
145
- model: modelConfigAsConfig?.model || request.config?.model || internalDefaults?.model || defaultModel,
116
+ // Model resolved below (catalog, default chain, or explicit pass-through)
117
+ model: modelConfigAsConfig?.model || request.config?.model || internalDefaults?.model,
146
118
  // Ensure provider is set: modelConfig > request.config > internalDefaults > gateway default
147
- // Provider is required for router to know which provider to use
148
119
  provider: modelConfigAsConfig?.provider || request.config?.provider || internalDefaults?.engine || config.defaultEngine
149
120
  };
150
- // Log if using default model
151
- if (!request.config?.model && !internalDefaults?.model) {
152
- logger.info('Using default model: gpt-5-nano (no model provided in request)', {
153
- jobId: request.identity.jobId,
154
- note: 'Default model ensures requests always work regardless of configuration'
155
- });
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');
156
177
  }
157
178
  // Auto-get maxTokens from flex-md if not explicitly set in ANY config source
158
179
  // Check all possible sources: request.config, internalDefaults, gateway config
@@ -162,7 +183,7 @@ async function mergeConfig(request, config, logger) {
162
183
  if (!maxTokensExplicitlySet && merged.model && merged.provider) {
163
184
  // Try to get maxTokens from flex-md
164
185
  try {
165
- const flexMdMaxTokens = await (0, flex_md_loader_js_1.getModelMaxTokensFromFlexMd)(merged.provider, merged.model);
186
+ const flexMdMaxTokens = await getModelMaxTokensFromFlexMd(merged.provider, merged.model);
166
187
  if (flexMdMaxTokens && flexMdMaxTokens > 0) {
167
188
  merged.maxTokens = flexMdMaxTokens;
168
189
  logger.debug('Using maxTokens from flex-md', {
@@ -252,7 +273,7 @@ function isNonZeroTokenCount(n) {
252
273
  * Maps provider/router usage objects to gateway token counts (`metadata.tokens`, Activix, trace attempts).
253
274
  * Handles promptTokens/inputTokens, OpenAI-style snake_case, Responses-style input/output tokens, and missing total (sum prompt+completion).
254
275
  */
255
- function normalizeRouterUsageTokens(usage) {
276
+ export function normalizeRouterUsageTokens(usage) {
256
277
  if (usage == null || typeof usage !== 'object')
257
278
  return undefined;
258
279
  const u = usage;
@@ -300,7 +321,7 @@ function firstNonZeroUsageFromBuckets(buckets) {
300
321
  * Reads token usage from every stable location the router may populate (see docs/PROVIDERS_ROUTER_DIAGNOSTICS_TRACE_REQUIREMENTS.md).
301
322
  * Prefers the raw/provider body (`rawResponse` / `raw`) when it carries non-zero usage before re-reading the outer envelope.
302
323
  */
303
- function extractTokenUsageFromRouterResponse(routerResponse) {
324
+ export function extractTokenUsageFromRouterResponse(routerResponse) {
304
325
  const zeros = { prompt: 0, completion: 0, total: 0 };
305
326
  if (routerResponse == null || typeof routerResponse !== 'object') {
306
327
  return zeros;
@@ -327,7 +348,7 @@ function extractTokenUsageFromRouterResponse(routerResponse) {
327
348
  * metadata.attempts[].costUsd, response root, then common raw payload locations.
328
349
  * Does not compute cost from tokens — adapters must populate normalized fields or raw usage.cost-style keys.
329
350
  */
330
- function extractCostUsdFromRouterResponse(routerResponse) {
351
+ export function extractCostUsdFromRouterResponse(routerResponse) {
331
352
  if (routerResponse == null || typeof routerResponse !== 'object')
332
353
  return undefined;
333
354
  const r = routerResponse;
@@ -369,7 +390,7 @@ function extractCostUsdFromRouterResponse(routerResponse) {
369
390
  }
370
391
  return undefined;
371
392
  }
372
- function hasNonZeroTokenUsage(tokens) {
393
+ export function hasNonZeroTokenUsage(tokens) {
373
394
  return !!(tokens.prompt || tokens.completion || tokens.total);
374
395
  }
375
396
  function pickRouterCostStatus(routerResponse) {
@@ -386,7 +407,7 @@ function pickRouterCostStatus(routerResponse) {
386
407
  * Gateway fallback when the router does not set `metadata.costStatus`.
387
408
  * Prefer {@link resolveCostCompletionForActivity} at invoke boundaries.
388
409
  */
389
- function resolveActivityCostCompletion(tokens, costUsd) {
410
+ export function resolveActivityCostCompletion(tokens, costUsd) {
390
411
  if (typeof costUsd === 'number' && Number.isFinite(costUsd)) {
391
412
  return { cost: costUsd, costStatus: 'priced' };
392
413
  }
@@ -399,7 +420,7 @@ function resolveActivityCostCompletion(tokens, costUsd) {
399
420
  * Activity cost slice for Activix: router `metadata.costStatus` / cost wins when present;
400
421
  * otherwise gateway applies the G8 fallback (usage + no price → `unpriced`).
401
422
  */
402
- function resolveCostCompletionForActivity(routerResponse, tokens) {
423
+ export function resolveCostCompletionForActivity(routerResponse, tokens) {
403
424
  const routerStatus = pickRouterCostStatus(routerResponse);
404
425
  const costUsd = extractCostUsdFromRouterResponse(routerResponse);
405
426
  if (routerStatus === 'priced') {
@@ -413,11 +434,61 @@ function resolveCostCompletionForActivity(routerResponse, tokens) {
413
434
  }
414
435
  return resolveActivityCostCompletion(tokens, costUsd);
415
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
+ }
416
487
  /**
417
488
  * Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
418
489
  * Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
419
490
  */
420
- function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
491
+ export function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
421
492
  const rr = routerResponse != null && typeof routerResponse === 'object' ? routerResponse : {};
422
493
  const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
423
494
  const cfg = mergedConfig != null && typeof mergedConfig === 'object' ? mergedConfig : {};
@@ -443,7 +514,7 @@ function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
443
514
  /**
444
515
  * Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
445
516
  */
446
- function pickEffectiveModelConfigForMetadata(mergedConfig) {
517
+ export function pickEffectiveModelConfigForMetadata(mergedConfig) {
447
518
  if (mergedConfig == null || typeof mergedConfig !== 'object')
448
519
  return undefined;
449
520
  const c = mergedConfig;
@@ -466,7 +537,7 @@ const TRACE_MERGED_ROUTER_NUMERIC_KEYS = [
466
537
  /**
467
538
  * Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
468
539
  */
469
- function pickTraceMergedRouterConfig(mergedConfig) {
540
+ export function pickTraceMergedRouterConfig(mergedConfig) {
470
541
  if (mergedConfig == null || typeof mergedConfig !== 'object')
471
542
  return undefined;
472
543
  const c = mergedConfig;
@@ -495,7 +566,7 @@ const EFFECTIVE_MODEL_CONFIG_KEYS = ['model', 'modelId', 'provider', 'temperatur
495
566
  * Allowlisted generation fields from request only (before mergeConfig / flex-md).
496
567
  * Priority matches mergeConfig: modelConfig overrides request.config per key.
497
568
  */
498
- function pickEffectiveModelConfigFromInvokeRequest(request) {
569
+ export function pickEffectiveModelConfigFromInvokeRequest(request) {
499
570
  const cfg = (request.config ?? {});
500
571
  const mc = (request.modelConfig ?? {});
501
572
  const out = {};
@@ -523,7 +594,7 @@ function isRouterLikeEnvelope(value) {
523
594
  * Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
524
595
  * to find a router-shaped object for token / correlation extraction.
525
596
  */
526
- function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
597
+ export function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
527
598
  const seen = new Set();
528
599
  let cur = error;
529
600
  for (let i = 0; i < maxDepth && cur != null; i++) {
@@ -544,7 +615,7 @@ function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
544
615
  }
545
616
  return undefined;
546
617
  }
547
- function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
618
+ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
548
619
  if (typeof gatewayAiRequestId !== 'string' || gatewayAiRequestId.length === 0) {
549
620
  return undefined;
550
621
  }
@@ -570,7 +641,7 @@ function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
570
641
  }
571
642
  return out;
572
643
  }
573
- function buildInvokeRejectionMetadata(args) {
644
+ export function buildInvokeRejectionMetadata(args) {
574
645
  const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
575
646
  const partial = args.partialRouterPayload;
576
647
  const mc = args.mergedConfig;
@@ -595,16 +666,16 @@ function buildInvokeRejectionMetadata(args) {
595
666
  ...(mc === undefined ? { mergeConfigUnavailable: true } : {})
596
667
  };
597
668
  }
598
- function attachGatewayInvokeRejectionMetadata(err, metadata) {
669
+ export function attachGatewayInvokeRejectionMetadata(err, metadata) {
599
670
  err.metadata = metadata;
600
671
  }
601
672
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
602
- exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
673
+ export const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
603
674
  /**
604
675
  * Size-cap a provider/router payload before storing on an activity record.
605
676
  * Non-serializable values become a small marker object instead of throwing.
606
677
  */
607
- function capActivityFullResponsePayload(payload, maxChars = exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS) {
678
+ export function capActivityFullResponsePayload(payload, maxChars = DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS) {
608
679
  if (payload == null)
609
680
  return payload;
610
681
  let serialized;