@x12i/ai-gateway 9.2.0 → 9.3.4

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 (72) hide show
  1. package/README.md +105 -13
  2. package/dist/activity-manager.d.ts +1 -0
  3. package/dist/activity-manager.js +123 -26
  4. package/dist/ai-tools-client.d.ts +20 -0
  5. package/dist/ai-tools-client.js +91 -0
  6. package/dist/gateway-config.d.ts +2 -0
  7. package/dist/gateway-config.js +2 -1
  8. package/dist/gateway-mode.d.ts +40 -0
  9. package/dist/gateway-mode.js +75 -0
  10. package/dist/gateway-utils.d.ts +28 -1
  11. package/dist/gateway-utils.js +137 -12
  12. package/dist/gateway.d.ts +3 -0
  13. package/dist/gateway.js +34 -6
  14. package/dist/index.d.ts +3 -1
  15. package/dist/index.js +2 -1
  16. package/dist/types.d.ts +21 -0
  17. package/dist-cjs/activity-manager.cjs +137 -45
  18. package/dist-cjs/activity-manager.d.ts +1 -0
  19. package/dist-cjs/ai-tools-client.cjs +91 -0
  20. package/dist-cjs/ai-tools-client.d.ts +20 -0
  21. package/dist-cjs/config/activity-tracking-config.cjs +1 -4
  22. package/dist-cjs/content-normalizer/content-normalizer.cjs +3 -8
  23. package/dist-cjs/content-normalizer/index.cjs +1 -7
  24. package/dist-cjs/content-normalizer/types.cjs +1 -2
  25. package/dist-cjs/flex-md-loader.cjs +20 -67
  26. package/dist-cjs/gateway-config.cjs +25 -63
  27. package/dist-cjs/gateway-config.d.ts +2 -0
  28. package/dist-cjs/gateway-conversion.cjs +10 -48
  29. package/dist-cjs/gateway-instructions.cjs +5 -10
  30. package/dist-cjs/gateway-log-meta.cjs +9 -14
  31. package/dist-cjs/gateway-memory.cjs +2 -6
  32. package/dist-cjs/gateway-messages.cjs +3 -6
  33. package/dist-cjs/gateway-meta.cjs +1 -4
  34. package/dist-cjs/gateway-mode.cjs +75 -0
  35. package/dist-cjs/gateway-mode.d.ts +40 -0
  36. package/dist-cjs/gateway-provider-auto-register.cjs +2 -38
  37. package/dist-cjs/gateway-provider.cjs +10 -22
  38. package/dist-cjs/gateway-rate-limiter-constants.cjs +2 -5
  39. package/dist-cjs/gateway-rate-limiter.cjs +5 -9
  40. package/dist-cjs/gateway-retry.cjs +6 -14
  41. package/dist-cjs/gateway-utils.cjs +160 -89
  42. package/dist-cjs/gateway-utils.d.ts +28 -1
  43. package/dist-cjs/gateway-validation.cjs +2 -6
  44. package/dist-cjs/gateway.cjs +91 -67
  45. package/dist-cjs/gateway.d.ts +3 -0
  46. package/dist-cjs/index.cjs +22 -98
  47. package/dist-cjs/index.d.ts +3 -1
  48. package/dist-cjs/instruction-errors.cjs +2 -7
  49. package/dist-cjs/instruction-optimizer.cjs +4 -10
  50. package/dist-cjs/instructions-parser.cjs +5 -10
  51. package/dist-cjs/logger-factory.cjs +3 -6
  52. package/dist-cjs/memory-path-resolution.cjs +8 -18
  53. package/dist-cjs/message-builder.cjs +11 -47
  54. package/dist-cjs/object-types-library-integration.cjs +3 -8
  55. package/dist-cjs/object-types-library.cjs +5 -10
  56. package/dist-cjs/output-auditor.cjs +1 -4
  57. package/dist-cjs/output-contract-normalizer.cjs +9 -14
  58. package/dist-cjs/request-report-generator.cjs +1 -4
  59. package/dist-cjs/response-analyzer/format-type-detector.cjs +1 -5
  60. package/dist-cjs/response-analyzer/index.cjs +3 -9
  61. package/dist-cjs/response-analyzer/object-type-detector.cjs +1 -5
  62. package/dist-cjs/response-analyzer/response-analyzer.cjs +6 -10
  63. package/dist-cjs/response-analyzer/types.cjs +1 -2
  64. package/dist-cjs/response-fallback-fixer.cjs +1 -4
  65. package/dist-cjs/runtime-objects.cjs +7 -13
  66. package/dist-cjs/template-parser.cjs +5 -42
  67. package/dist-cjs/template-render-merge.cjs +2 -6
  68. package/dist-cjs/troubleshooting-helper.cjs +13 -28
  69. package/dist-cjs/types.cjs +1 -2
  70. package/dist-cjs/types.d.ts +21 -0
  71. package/dist-cjs/usage-tracker.cjs +3 -7
  72. 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).
@@ -48,6 +53,15 @@ export type ActivityCostStatus = 'priced' | 'unpriced';
48
53
  export type ResolvedActivityCost = {
49
54
  cost?: number;
50
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
+ };
51
65
  };
52
66
  export declare function hasNonZeroTokenUsage(tokens: {
53
67
  prompt: number;
@@ -72,6 +86,19 @@ export declare function resolveCostCompletionForActivity(routerResponse: unknown
72
86
  completion: number;
73
87
  total: number;
74
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>;
75
102
  /**
76
103
  * Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
77
104
  * Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
@@ -3,8 +3,11 @@
3
3
  * Handles utility functions
4
4
  */
5
5
  import * as crypto from 'crypto';
6
+ import { ModelResolutionError } from '@x12i/ai-tools';
6
7
  import { getPreParsedInstructions } from './gateway-instructions.js';
7
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';
8
11
  /**
9
12
  * Generates MD5 hash of a string
10
13
  */
@@ -29,11 +32,34 @@ export async function ensureTaskTypeId(request, logger) {
29
32
  });
30
33
  return taskTypeId;
31
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
+ }
32
58
  /**
33
59
  * Merges config with defaults
34
60
  * Supports using internal system action defaults (internalSkill or skillAudit) when useInternalDefaults is set
35
61
  */
36
- export async function mergeConfig(request, config, logger) {
62
+ export async function mergeConfig(request, config, logger, mergeOptions) {
37
63
  const useInternalDefaults = request.useInternalDefaults;
38
64
  const internalDefaults = useInternalDefaults
39
65
  ? (useInternalDefaults === 'skill'
@@ -52,8 +78,8 @@ export async function mergeConfig(request, config, logger) {
52
78
  useInternalDefaults,
53
79
  hasInternalDefaults: !!internalDefaults
54
80
  });
55
- // Default model to "gpt-5-nano" if nothing is provided (most permissive - always works)
56
- const defaultModel = 'gpt-5-nano';
81
+ const operationalMode = getGatewayOperationalMode(config);
82
+ const resolveModels = config.aiTools?.resolveModels !== false;
57
83
  // Priority: modelConfig > request.config > internalSystemActions[useInternalDefaults] > gateway defaults
58
84
  // First, merge modelConfig into a config-like object if present
59
85
  const modelConfigAsConfig = request.modelConfig ? {
@@ -87,18 +113,67 @@ export async function mergeConfig(request, config, logger) {
87
113
  ...request.config,
88
114
  // ModelConfig overrides (highest priority) - merge only defined values
89
115
  ...(modelConfigAsConfig ? Object.fromEntries(Object.entries(modelConfigAsConfig).filter(([_, value]) => value !== undefined)) : {}),
90
- // Ensure model is set: modelConfig > request.config > internalDefaults > default
91
- 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,
92
118
  // Ensure provider is set: modelConfig > request.config > internalDefaults > gateway default
93
- // Provider is required for router to know which provider to use
94
119
  provider: modelConfigAsConfig?.provider || request.config?.provider || internalDefaults?.engine || config.defaultEngine
95
120
  };
96
- // Log if using default model
97
- if (!request.config?.model && !internalDefaults?.model) {
98
- logger.info('Using default model: gpt-5-nano (no model provided in request)', {
99
- jobId: request.identity.jobId,
100
- note: 'Default model ensures requests always work regardless of configuration'
101
- });
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');
102
177
  }
103
178
  // Auto-get maxTokens from flex-md if not explicitly set in ANY config source
104
179
  // Check all possible sources: request.config, internalDefaults, gateway config
@@ -359,6 +434,56 @@ export function resolveCostCompletionForActivity(routerResponse, tokens) {
359
434
  }
360
435
  return resolveActivityCostCompletion(tokens, costUsd);
361
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
+ }
362
487
  /**
363
488
  * Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
364
489
  * Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
package/dist/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
  }
package/dist/gateway.js CHANGED
@@ -9,7 +9,8 @@ import { initializeGatewayComponents } from './gateway-config.js';
9
9
  import { buildMessages } from './message-builder.js';
10
10
  import { extractJsonFromFlexMd } from './flex-md-loader.js';
11
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, resolveCostCompletionForActivity, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.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';
13
14
  import { autoRegisterProviders } from './gateway-provider-auto-register.js';
14
15
  import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
15
16
  import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
@@ -45,7 +46,9 @@ export class AIGateway {
45
46
  logger;
46
47
  activityManager;
47
48
  messageBuilderConfig;
49
+ defaultModelConfig = {};
48
50
  _autoRegisterDone = false;
51
+ _aiToolsClient = null;
49
52
  constructor(config = {}, activityManager) {
50
53
  this.config = config;
51
54
  this.activityManager = activityManager;
@@ -54,6 +57,7 @@ export class AIGateway {
54
57
  this.router = components.router;
55
58
  this.activityManager = components.activityManager;
56
59
  this.messageBuilderConfig = components.messageBuilderConfig;
60
+ this.defaultModelConfig = components.defaultModelConfig ?? {};
57
61
  setGatewayRuntimeClients({
58
62
  activix: this.activityManager?.getTracker(),
59
63
  logger: this.logger
@@ -77,7 +81,11 @@ export class AIGateway {
77
81
  // Simple message construction
78
82
  const messages = this.buildSimpleMessages(request);
79
83
  // Merge config (modelConfig > request.config > gateway defaults)
80
- const mergedConfig = await mergeConfig(request, this.config, this.logger);
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
+ });
81
89
  // Activix start snapshot must match what the router receives (modelConfig-only callers omit request.config.model).
82
90
  request._mergedRouterConfig = mergedConfig;
83
91
  // Lazy auto-register providers from env (OPENAI_API_KEY, etc.) so consumers don't have to call init
@@ -111,7 +119,11 @@ export class AIGateway {
111
119
  });
112
120
  const metaChat = response?.metadata || {};
113
121
  const tokensChat = extractTokenUsageFromRouterResponse(response);
114
- const costCompletionChat = resolveCostCompletionForActivity(response, tokensChat);
122
+ const costCompletionChat = await resolveCostCompletionWithAiTools(response, tokensChat, {
123
+ mergedConfig,
124
+ calculator: aiTools?.calculator ?? null,
125
+ calculateCost: this.config.aiTools?.calculateCost
126
+ });
115
127
  // Create enhanced response
116
128
  const enhancedResponse = {
117
129
  content: response.content || '',
@@ -130,7 +142,10 @@ export class AIGateway {
130
142
  : { cost: costCompletionChat.cost })
131
143
  }
132
144
  : {}),
133
- ...(costCompletionChat.costStatus ? { costStatus: costCompletionChat.costStatus } : {})
145
+ ...(costCompletionChat.costStatus ? { costStatus: costCompletionChat.costStatus } : {}),
146
+ ...(costCompletionChat.costBreakdown
147
+ ? { costBreakdown: costCompletionChat.costBreakdown }
148
+ : {})
134
149
  }
135
150
  };
136
151
  // Track activity success if activity was started
@@ -250,7 +265,11 @@ export class AIGateway {
250
265
  // Attach parsedSnapshot to request for activity tracking
251
266
  request._parsedRequest = parsedSnapshot;
252
267
  // Merge config (modelConfig > request.config > gateway defaults)
253
- const mergedConfig = await mergeConfig(request, this.config, this.logger);
268
+ const aiTools = await this.getAiTools();
269
+ const mergedConfig = await mergeConfig(request, this.config, this.logger, {
270
+ defaultModelConfig: this.defaultModelConfig,
271
+ catalog: aiTools?.catalog ?? null
272
+ });
254
273
  request._mergedRouterConfig = mergedConfig;
255
274
  const diagnosticsMode = request.diagnostics?.mode;
256
275
  const traceEnabled = diagnosticsMode === 'trace';
@@ -539,7 +558,11 @@ export class AIGateway {
539
558
  tokens = second;
540
559
  }
541
560
  }
542
- const costCompletion = resolveCostCompletionForActivity(routerResponse, tokens);
561
+ const costCompletion = await resolveCostCompletionWithAiTools(routerResponse, tokens, {
562
+ mergedConfig,
563
+ calculator: aiTools?.calculator ?? null,
564
+ calculateCost: this.config.aiTools?.calculateCost
565
+ });
543
566
  const routerMetaForCost = routerResponse?.metadata || {};
544
567
  const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
545
568
  const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
@@ -567,6 +590,7 @@ export class AIGateway {
567
590
  }
568
591
  : {}),
569
592
  ...(costCompletion.costStatus ? { costStatus: costCompletion.costStatus } : {}),
593
+ ...(costCompletion.costBreakdown ? { costBreakdown: costCompletion.costBreakdown } : {}),
570
594
  ...(traceEnabled
571
595
  ? {
572
596
  requestIds: traceRequestIds,
@@ -707,6 +731,10 @@ export class AIGateway {
707
731
  logger: this.logger
708
732
  });
709
733
  }
734
+ getAiTools() {
735
+ this._aiToolsClient ??= getAiToolsClient(this.config, this.logger);
736
+ return this._aiToolsClient;
737
+ }
710
738
  }
711
739
  function resolveRuntimeJobId(request) {
712
740
  return request.identity.jobId || request.identity.sessionId || request.aiRequestId;
package/dist/index.d.ts CHANGED
@@ -17,7 +17,9 @@ export { AIGateway } from './gateway.js';
17
17
  export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
18
18
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
19
19
  export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
20
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, hasNonZeroTokenUsage } from './gateway-utils.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, hasNonZeroTokenUsage } from './gateway-utils.js';
21
+ export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
22
+ export type { GatewayOperationalMode, GatewayDefaultModelSource, DefaultModelSubstitutionReason, ResolvedGatewayDefault } from './gateway-mode.js';
21
23
  export type { ActivityCostStatus, ResolvedActivityCost } from './gateway-utils.js';
22
24
  export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
23
25
  export type { OutputContractSpec } from './output-contract-normalizer.js';
package/dist/index.js CHANGED
@@ -17,7 +17,8 @@ export * from '@x12i/ai-providers-router';
17
17
  export { AIGateway } from './gateway.js';
18
18
  export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
19
19
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
20
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, hasNonZeroTokenUsage } from './gateway-utils.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, hasNonZeroTokenUsage } from './gateway-utils.js';
21
+ export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
21
22
  export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
22
23
  export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
23
24
  export { GATEWAY_DUAL_MEMORY_ROOTS, buildMemoryResolutionRootFromWorkingMemory, coalesceMergedInputBucket, extractCallerInputsBag, mapSmartInputPathsInputsToInput, parseLooseJsonObject, prepareWorkingMemoryForTemplateRender, resolveGatewayMemoryPathValue } from './memory-path-resolution.js';
package/dist/types.d.ts CHANGED
@@ -340,6 +340,27 @@ export interface GatewayConfig extends Omit<RouterConfig, 'defaultEngine' | 'log
340
340
  openRouter?: {
341
341
  enabled?: boolean;
342
342
  };
343
+ /**
344
+ * Operational mode override (`process.env.mode` / `MODE` when omitted).
345
+ * - `prod`: unresolved models fall back to {@link AI_GATEWAY_DEFAULT_MODEL} / packaged default (with Logxer warn).
346
+ * - `dev` / `debug`: unresolved models throw {@link ModelResolutionError} from `@x12i/ai-tools`.
347
+ */
348
+ mode?: 'dev' | 'debug' | 'prod';
349
+ /**
350
+ * @x12i/ai-tools integration: catalog model resolution (request) and cost calculation (response).
351
+ */
352
+ aiTools?: {
353
+ /** @default true */
354
+ enabled?: boolean;
355
+ /** Inject Catalox; otherwise `createCataloxFromEnv()` from `@x12i/catalox/firebase`. */
356
+ catalox?: import('@x12i/catalox').Catalox;
357
+ cacheTtlMs?: number;
358
+ /** @default true */
359
+ resolveModels?: boolean;
360
+ /** @default true */
361
+ calculateCost?: boolean;
362
+ costIncludeBreakdown?: boolean;
363
+ };
343
364
  /**
344
365
  * InstructionsBlocks overrides
345
366
  * Key: block name, Value: block content