@x12i/ai-gateway 9.5.3 → 9.6.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 CHANGED
@@ -79,7 +79,7 @@ const response = await gateway.invoke({
79
79
  agentId: 'agent-456'
80
80
  },
81
81
  workingMemory: { input: 'Hello!' },
82
- config: { model: 'openai/gpt-4o-mini', provider: 'openrouter' }
82
+ config: { model: 'cheap', provider: 'openrouter' }
83
83
  });
84
84
 
85
85
  console.log(response.content, response.metadata?.costUsd, response.metadata?.tokens);
@@ -87,7 +87,7 @@ console.log(response.content, response.metadata?.costUsd, response.metadata?.tok
87
87
 
88
88
  ### Providers without manual `register()`
89
89
 
90
- - **OpenRouter:** Set `OPEN_ROUTER_KEY` or `OPENROUTER_API_KEY` (unless `USE_OPENROUTER=false`). The gateway can lazy-register on first invoke.
90
+ - **OpenRouter:** Set **`OPENROUTER_API_KEY`** in `.env` (unless `USE_OPENROUTER=false`). The gateway passes this key into the router on init and lazy-registers on first invoke. **`OPEN_ROUTER_KEY`** is a legacy alias still read if `OPENROUTER_API_KEY` is unset — prefer **`OPENROUTER_API_KEY`** so **`@x12i/ai-tools`** model resolution sets `routedViaOpenRouter` correctly (ai-tools does not read `OPEN_ROUTER_KEY`).
91
91
  - **Direct providers:** Set `OPENAI_API_KEY`, `GROK_API_KEY`, etc. Same lazy registration.
92
92
 
93
93
  Load `.env` before constructing the gateway if another package creates it first.
@@ -217,7 +217,7 @@ Set via constructor `mode` or env `mode` / `MODE`.
217
217
  | `npm run test:flex-md-esm-regression` | ESM build regression for flex-md |
218
218
  | `npm run test:prepublish` | `build` + `npm test` |
219
219
 
220
- Live tests use `LIVE_TEST_PROVIDER` / `LIVE_TEST_MODEL` (default `openrouter` + `openai/gpt-4o-mini`). Set `LIVE_SKIP_INVOKE=1` to skip the LLM call.
220
+ Live tests use `LIVE_TEST_PROVIDER` / `LIVE_TEST_MODEL` (default `openrouter` + `cheap`, an ai-tools profile alias). Set `LIVE_SKIP_INVOKE=1` to skip the LLM call.
221
221
 
222
222
  ---
223
223
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "defaultEngine": "openai",
3
- "defaultModel": "gpt-5-nano",
2
+ "defaultEngine": "openrouter",
3
+ "defaultModel": "cheap",
4
4
  "temperature": 0.7,
5
5
  "topP": 1.0,
6
6
  "frequencyPenalty": 0.0,
@@ -13,4 +13,3 @@
13
13
  "throttlingDelay": 5000
14
14
  }
15
15
  }
16
-
@@ -197,7 +197,7 @@ export function initializeGatewayComponents(config) {
197
197
  // Prefer explicit config from consumer (e.g. ai-skills) to avoid env-loading timing; fall back to process.env.
198
198
  const explicitOpenRouterKey = config.openrouter?.apiKey;
199
199
  const isExplicitKey = typeof explicitOpenRouterKey === 'string' && !explicitOpenRouterKey.startsWith('ENV.');
200
- const openRouterKey = isExplicitKey ? explicitOpenRouterKey : (process.env.OPEN_ROUTER_KEY ?? process.env.OPENROUTER_API_KEY);
200
+ const openRouterKey = isExplicitKey ? explicitOpenRouterKey : (process.env.OPENROUTER_API_KEY ?? process.env.OPEN_ROUTER_KEY);
201
201
  const useOpenRouter = config.openRouter?.enabled !== undefined ? config.openRouter?.enabled : process.env.USE_OPENROUTER;
202
202
  if (openRouterKey && useOpenRouter !== false && useOpenRouter !== 'false') {
203
203
  routerConfig.openRouter = { enabled: true };
@@ -2,6 +2,7 @@
2
2
  * Gateway Meta Operations Module
3
3
  * Handles meta operations like instruction optimization and testing
4
4
  */
5
+ import { CODE_DEFAULT_MODEL } from './gateway-mode.js';
5
6
  /**
6
7
  * Test instructions by running them and analyzing the response
7
8
  */
@@ -9,7 +10,7 @@ export async function testInstructions(instructions, testInput, expectedSchema,
9
10
  // Get internal system action config (instruction audit)
10
11
  const internalConfig = config.internalSystemActions?.instructionAudit;
11
12
  const defaultEngine = config.defaultEngine || 'openai';
12
- const defaultModel = internalConfig?.model || 'gpt-5-nano';
13
+ const defaultModel = internalConfig?.model || CODE_DEFAULT_MODEL;
13
14
  const defaultProvider = internalConfig?.engine || defaultEngine;
14
15
  const { agentId = 'instruction-tester', model = options.model || defaultModel, // Use internal config default if not provided
15
16
  provider = options.provider || defaultProvider // Use internal config default if not provided
@@ -6,7 +6,8 @@ import type { ActivityIdentity, GatewayConfig } from './types.js';
6
6
  export type GatewayOperationalMode = 'prod' | 'debug' | 'dev';
7
7
  export type GatewayDefaultModelSource = 'env' | 'model-config.json' | 'code';
8
8
  export type DefaultModelSubstitutionReason = 'no_model_provided' | 'model_resolution_failed' | 'ai_tools_unavailable';
9
- export declare const CODE_DEFAULT_MODEL = "gpt-5-nano";
9
+ /** Profile name resolved via ai-tools + {@link @x12i/ai-profiles} when catalog is enabled. */
10
+ export declare const CODE_DEFAULT_MODEL = "cheap";
10
11
  export type ResolvedGatewayDefault = {
11
12
  model: string;
12
13
  provider?: string;
@@ -2,7 +2,8 @@
2
2
  * Gateway operational mode (prod vs dev/debug) and default model resolution.
3
3
  */
4
4
  import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
5
- export const CODE_DEFAULT_MODEL = 'gpt-5-nano';
5
+ /** Profile name resolved via ai-tools + {@link @x12i/ai-profiles} when catalog is enabled. */
6
+ export const CODE_DEFAULT_MODEL = 'cheap';
6
7
  /**
7
8
  * Operational mode: `GatewayConfig.mode` overrides `process.env.mode` / `MODE`.
8
9
  * Only `prod` allows silent default-model substitution; all other values are strict.
@@ -146,7 +146,7 @@ export async function autoRegisterProviders(router, logger) {
146
146
  optionalEnvVars: PROVIDER_CONFIGS
147
147
  .filter(p => p.optional)
148
148
  .map(p => p.envVar),
149
- openRouter: 'Set OPEN_ROUTER_KEY or OPENROUTER_API_KEY (and do not set USE_OPENROUTER=false) to use OpenRouter without registering a provider.',
149
+ openRouter: 'Set OPENROUTER_API_KEY (and do not set USE_OPENROUTER=false) to use OpenRouter without registering a provider. Legacy OPEN_ROUTER_KEY is still accepted.',
150
150
  note: 'You can still manually register providers using gateway.register(provider)'
151
151
  });
152
152
  }
@@ -2,7 +2,7 @@
2
2
  * Gateway Utilities Module
3
3
  * Handles utility functions
4
4
  */
5
- import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
5
+ import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayFallbackAttempt, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
6
6
  import type { Logxer } from '@x12i/logxer';
7
7
  import { type AiCostResult, type AiModelsCatalogClient, type CostCalculator } from '@x12i/ai-tools';
8
8
  /**
@@ -155,6 +155,10 @@ export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<
155
155
  */
156
156
  export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
157
157
  export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
158
+ /**
159
+ * Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
160
+ */
161
+ export declare function tryExtractFallbackAttemptsFromErrorChain(error: unknown, maxDepth?: number): GatewayFallbackAttempt[] | undefined;
158
162
  export declare function buildInvokeRejectionMetadata(args: {
159
163
  request: Pick<AIInvokeRequest, 'aiRequestId' | 'identity' | 'config' | 'modelConfig'>;
160
164
  taskTypeId: string;
@@ -162,6 +166,7 @@ export declare function buildInvokeRejectionMetadata(args: {
162
166
  mergedConfig?: unknown;
163
167
  partialRouterPayload?: unknown;
164
168
  gatewayAiRequestId?: string;
169
+ error?: unknown;
165
170
  }): GatewayInvokeRejectionMetadata;
166
171
  export declare function attachGatewayInvokeRejectionMetadata(err: Error, metadata: GatewayInvokeRejectionMetadata): void;
167
172
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
@@ -3,6 +3,7 @@
3
3
  * Handles utility functions
4
4
  */
5
5
  import * as crypto from 'crypto';
6
+ import { FallbackExhaustedError } from '@x12i/ai-providers-router';
6
7
  import { ModelResolutionError } from '@x12i/ai-tools';
7
8
  import { getPreParsedInstructions } from './gateway-instructions.js';
8
9
  import { getModelMaxTokensFromFlexMd } from './flex-md-loader.js';
@@ -55,6 +56,44 @@ async function substituteGatewayDefaultModel(merged, request, config, logger, me
55
56
  });
56
57
  applyGatewayDefaultToMerged(merged, defaults, config);
57
58
  }
59
+ async function tryResolveSubstitutedDefaultModel(merged, request, config, logger, mergeOptions, original) {
60
+ const resolveModels = config.aiTools?.resolveModels !== false;
61
+ const catalog = mergeOptions?.catalog;
62
+ if (!resolveModels || !catalog || !merged.model) {
63
+ return;
64
+ }
65
+ try {
66
+ const resolution = await catalog.resolveModel({
67
+ provider: merged.provider,
68
+ model: merged.model
69
+ });
70
+ if (!resolution.found) {
71
+ return;
72
+ }
73
+ applyModelResolution(merged, resolution, config.defaultEngine);
74
+ request._modelResolution = {
75
+ modelId: resolution.modelId,
76
+ routedViaOpenRouter: resolution.routedViaOpenRouter,
77
+ confidence: resolution.confidence,
78
+ resolvedVia: resolution.resolvedVia,
79
+ originalProvider: original?.provider ?? merged.provider,
80
+ originalModel: original?.model ?? merged.model
81
+ };
82
+ logger.verbose('Catalog resolved substituted default model', {
83
+ jobId: request.identity.jobId,
84
+ model: merged.model,
85
+ provider: merged.provider,
86
+ resolvedModelId: resolution.modelId
87
+ });
88
+ }
89
+ catch {
90
+ // Prod keeps the substituted bare default when re-resolution fails.
91
+ }
92
+ }
93
+ async function substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, reason, original) {
94
+ await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, reason, original);
95
+ await tryResolveSubstitutedDefaultModel(merged, request, config, logger, mergeOptions, original);
96
+ }
58
97
  /**
59
98
  * True when any caller-controlled config source set `maxTokens` (Optimixer should not override).
60
99
  */
@@ -137,7 +176,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
137
176
  const originalProvider = merged.provider;
138
177
  const originalModel = explicitModel;
139
178
  if (!explicitModel) {
140
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'no_model_provided');
179
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'no_model_provided');
141
180
  }
142
181
  else if (resolveModels && mergeOptions?.catalog) {
143
182
  try {
@@ -166,7 +205,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
166
205
  });
167
206
  }
168
207
  else if (isProdGatewayMode(operationalMode)) {
169
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'model_resolution_failed', { provider: originalProvider, model: originalModel });
208
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'model_resolution_failed', { provider: originalProvider, model: originalModel });
170
209
  }
171
210
  else {
172
211
  throw new ModelResolutionError({ provider: merged.provider, model: explicitModel }, resolution);
@@ -177,7 +216,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
177
216
  throw error;
178
217
  }
179
218
  if (isProdGatewayMode(operationalMode)) {
180
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
219
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
181
220
  }
182
221
  else {
183
222
  throw error;
@@ -185,10 +224,10 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
185
224
  }
186
225
  }
187
226
  else if (resolveModels && !mergeOptions?.catalog && isProdGatewayMode(operationalMode)) {
188
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
227
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
189
228
  }
190
229
  if (!merged.model) {
191
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'no_model_provided');
230
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'no_model_provided');
192
231
  }
193
232
  const maxTokensExplicitlySet = isMaxTokensExplicitlySet(request, config);
194
233
  const optimixerWillPredict = config.optimixer?.enabled === true && !maxTokensExplicitlySet;
@@ -783,6 +822,46 @@ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
783
822
  }
784
823
  return out;
785
824
  }
825
+ function mapRouterFallbackAttempts(attempts) {
826
+ return attempts.map((attempt) => ({
827
+ provider: String(attempt.provider),
828
+ ...(attempt.model !== undefined ? { model: attempt.model } : {}),
829
+ ...(attempt.httpStatus !== undefined ? { httpStatus: attempt.httpStatus } : {}),
830
+ error: attempt.error instanceof Error ? attempt.error.message : String(attempt.error),
831
+ ...(attempt.responsePreview !== undefined ? { responsePreview: attempt.responsePreview } : {})
832
+ }));
833
+ }
834
+ function extractFallbackAttemptsFromError(error) {
835
+ if (error instanceof FallbackExhaustedError && error.attempts.length > 0) {
836
+ return mapRouterFallbackAttempts(error.attempts);
837
+ }
838
+ if (error != null && typeof error === 'object') {
839
+ const record = error;
840
+ if (record.name === 'FallbackExhaustedError' && Array.isArray(record.attempts) && record.attempts.length > 0) {
841
+ return mapRouterFallbackAttempts(record.attempts);
842
+ }
843
+ }
844
+ return undefined;
845
+ }
846
+ /**
847
+ * Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
848
+ */
849
+ export function tryExtractFallbackAttemptsFromErrorChain(error, maxDepth = 8) {
850
+ const seen = new Set();
851
+ let cur = error;
852
+ for (let i = 0; i < maxDepth && cur != null; i++) {
853
+ if (typeof cur !== 'object')
854
+ break;
855
+ if (seen.has(cur))
856
+ break;
857
+ seen.add(cur);
858
+ const attempts = extractFallbackAttemptsFromError(cur);
859
+ if (attempts?.length)
860
+ return attempts;
861
+ cur = cur.cause;
862
+ }
863
+ return undefined;
864
+ }
786
865
  export function buildInvokeRejectionMetadata(args) {
787
866
  const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
788
867
  const partial = args.partialRouterPayload;
@@ -796,6 +875,9 @@ export function buildInvokeRejectionMetadata(args) {
796
875
  tokens = undefined;
797
876
  }
798
877
  const requestIds = pickRequestIdsFromRouterLike(gid, partial);
878
+ const fallbackAttempts = args.error !== undefined
879
+ ? tryExtractFallbackAttemptsFromErrorChain(args.error)
880
+ : undefined;
799
881
  return {
800
882
  aiRequestId: args.request.aiRequestId,
801
883
  identity: args.request.identity,
@@ -805,6 +887,7 @@ export function buildInvokeRejectionMetadata(args) {
805
887
  ...(effective !== undefined ? { effectiveModelConfig: effective } : {}),
806
888
  ...(tokens !== undefined ? { tokens } : {}),
807
889
  ...(requestIds !== undefined ? { requestIds } : {}),
890
+ ...(fallbackAttempts !== undefined ? { fallbackAttempts } : {}),
808
891
  ...(mc === undefined ? { mergeConfigUnavailable: true } : {})
809
892
  };
810
893
  }
package/dist/gateway.js CHANGED
@@ -17,7 +17,7 @@ import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
17
17
  import { invokeWithRetry } from './gateway-retry.js';
18
18
  /** Error message thrown by the router when no provider is registered or specified */
19
19
  const NO_PROVIDER_ERROR = 'No provider specified and no providers registered';
20
- const NO_PROVIDER_HINT = ' Set OPEN_ROUTER_KEY (or OPENROUTER_API_KEY) in the environment to use OpenRouter, or register a provider with the router (e.g. via autoRegisterProviders or gateway config).';
20
+ const NO_PROVIDER_HINT = ' Set OPENROUTER_API_KEY in the environment to use OpenRouter (legacy OPEN_ROUTER_KEY is still read as fallback), or register a provider with the router (e.g. via autoRegisterProviders or gateway config).';
21
21
  /** Warn when a successful call reports no tokens and/or explicit zero cost (often missing adapter metadata). */
22
22
  function warnIfSuccessfulInvokeReportsZeroUsageOrCost(logger, identity, meta, invokeKind) {
23
23
  const { tokens, costUsd, cost } = meta;
@@ -685,7 +685,8 @@ export class AIGateway {
685
685
  startTime,
686
686
  mergedConfig,
687
687
  partialRouterPayload: partial,
688
- gatewayAiRequestId: request.aiRequestId
688
+ gatewayAiRequestId: request.aiRequestId,
689
+ error: err
689
690
  });
690
691
  attachGatewayInvokeRejectionMetadata(err, rejectMeta);
691
692
  if (err.message.includes(NO_PROVIDER_ERROR)) {
package/dist/index.d.ts CHANGED
@@ -16,8 +16,8 @@ export * from '@x12i/ai-providers-router';
16
16
  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
- export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
20
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
19
+ export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayFallbackAttempt, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
21
21
  export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
22
22
  export type { GatewayOperationalMode, GatewayDefaultModelSource, DefaultModelSubstitutionReason, ResolvedGatewayDefault } from './gateway-mode.js';
23
23
  export type { ActivityCostStatus, ResolvedActivityCost } from './gateway-utils.js';
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ 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, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
21
21
  export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
22
22
  export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
23
23
  export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
package/dist/types.d.ts CHANGED
@@ -138,12 +138,25 @@ export type GatewayInvokeRejectionMetadata = {
138
138
  region?: string;
139
139
  effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
140
140
  requestIds?: GatewayTraceRequestIds;
141
+ /**
142
+ * Fallback candidates tried before exhaustion (non-trace {@link AIGateway.invoke} only).
143
+ * Sourced from {@link FallbackExhaustedError.attempts} on the router error chain.
144
+ */
145
+ fallbackAttempts?: GatewayFallbackAttempt[];
141
146
  /**
142
147
  * True when {@link mergeConfig} did not run (e.g. message-building threw first).
143
148
  * Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
144
149
  */
145
150
  mergeConfigUnavailable?: true;
146
151
  };
152
+ /** Serializable slice of a router fallback attempt for rejection metadata. */
153
+ export type GatewayFallbackAttempt = {
154
+ provider: string;
155
+ model?: string;
156
+ httpStatus?: number;
157
+ error: string;
158
+ responsePreview?: string;
159
+ };
147
160
  /**
148
161
  * Identity object used for activity linkage.
149
162
  * On gateway requests/responses it lives on `identity`. When activity tracking persists via Activix v5+,
@@ -1,6 +1,6 @@
1
1
  {
2
- "defaultEngine": "openai",
3
- "defaultModel": "gpt-5-nano",
2
+ "defaultEngine": "openrouter",
3
+ "defaultModel": "cheap",
4
4
  "temperature": 0.7,
5
5
  "topP": 1.0,
6
6
  "frequencyPenalty": 0.0,
@@ -13,4 +13,3 @@
13
13
  "throttlingDelay": 5000
14
14
  }
15
15
  }
16
-
@@ -197,7 +197,7 @@ export function initializeGatewayComponents(config) {
197
197
  // Prefer explicit config from consumer (e.g. ai-skills) to avoid env-loading timing; fall back to process.env.
198
198
  const explicitOpenRouterKey = config.openrouter?.apiKey;
199
199
  const isExplicitKey = typeof explicitOpenRouterKey === 'string' && !explicitOpenRouterKey.startsWith('ENV.');
200
- const openRouterKey = isExplicitKey ? explicitOpenRouterKey : (process.env.OPEN_ROUTER_KEY ?? process.env.OPENROUTER_API_KEY);
200
+ const openRouterKey = isExplicitKey ? explicitOpenRouterKey : (process.env.OPENROUTER_API_KEY ?? process.env.OPEN_ROUTER_KEY);
201
201
  const useOpenRouter = config.openRouter?.enabled !== undefined ? config.openRouter?.enabled : process.env.USE_OPENROUTER;
202
202
  if (openRouterKey && useOpenRouter !== false && useOpenRouter !== 'false') {
203
203
  routerConfig.openRouter = { enabled: true };
@@ -2,6 +2,7 @@
2
2
  * Gateway Meta Operations Module
3
3
  * Handles meta operations like instruction optimization and testing
4
4
  */
5
+ import { CODE_DEFAULT_MODEL } from './gateway-mode.js';
5
6
  /**
6
7
  * Test instructions by running them and analyzing the response
7
8
  */
@@ -9,7 +10,7 @@ export async function testInstructions(instructions, testInput, expectedSchema,
9
10
  // Get internal system action config (instruction audit)
10
11
  const internalConfig = config.internalSystemActions?.instructionAudit;
11
12
  const defaultEngine = config.defaultEngine || 'openai';
12
- const defaultModel = internalConfig?.model || 'gpt-5-nano';
13
+ const defaultModel = internalConfig?.model || CODE_DEFAULT_MODEL;
13
14
  const defaultProvider = internalConfig?.engine || defaultEngine;
14
15
  const { agentId = 'instruction-tester', model = options.model || defaultModel, // Use internal config default if not provided
15
16
  provider = options.provider || defaultProvider // Use internal config default if not provided
@@ -2,7 +2,8 @@
2
2
  * Gateway operational mode (prod vs dev/debug) and default model resolution.
3
3
  */
4
4
  import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
5
- export const CODE_DEFAULT_MODEL = 'gpt-5-nano';
5
+ /** Profile name resolved via ai-tools + {@link @x12i/ai-profiles} when catalog is enabled. */
6
+ export const CODE_DEFAULT_MODEL = 'cheap';
6
7
  /**
7
8
  * Operational mode: `GatewayConfig.mode` overrides `process.env.mode` / `MODE`.
8
9
  * Only `prod` allows silent default-model substitution; all other values are strict.
@@ -6,7 +6,8 @@ import type { ActivityIdentity, GatewayConfig } from './types.js';
6
6
  export type GatewayOperationalMode = 'prod' | 'debug' | 'dev';
7
7
  export type GatewayDefaultModelSource = 'env' | 'model-config.json' | 'code';
8
8
  export type DefaultModelSubstitutionReason = 'no_model_provided' | 'model_resolution_failed' | 'ai_tools_unavailable';
9
- export declare const CODE_DEFAULT_MODEL = "gpt-5-nano";
9
+ /** Profile name resolved via ai-tools + {@link @x12i/ai-profiles} when catalog is enabled. */
10
+ export declare const CODE_DEFAULT_MODEL = "cheap";
10
11
  export type ResolvedGatewayDefault = {
11
12
  model: string;
12
13
  provider?: string;
@@ -146,7 +146,7 @@ export async function autoRegisterProviders(router, logger) {
146
146
  optionalEnvVars: PROVIDER_CONFIGS
147
147
  .filter(p => p.optional)
148
148
  .map(p => p.envVar),
149
- openRouter: 'Set OPEN_ROUTER_KEY or OPENROUTER_API_KEY (and do not set USE_OPENROUTER=false) to use OpenRouter without registering a provider.',
149
+ openRouter: 'Set OPENROUTER_API_KEY (and do not set USE_OPENROUTER=false) to use OpenRouter without registering a provider. Legacy OPEN_ROUTER_KEY is still accepted.',
150
150
  note: 'You can still manually register providers using gateway.register(provider)'
151
151
  });
152
152
  }
@@ -3,6 +3,7 @@
3
3
  * Handles utility functions
4
4
  */
5
5
  import * as crypto from 'crypto';
6
+ import { FallbackExhaustedError } from '@x12i/ai-providers-router';
6
7
  import { ModelResolutionError } from '@x12i/ai-tools';
7
8
  import { getPreParsedInstructions } from './gateway-instructions.js';
8
9
  import { getModelMaxTokensFromFlexMd } from './flex-md-loader.js';
@@ -55,6 +56,44 @@ async function substituteGatewayDefaultModel(merged, request, config, logger, me
55
56
  });
56
57
  applyGatewayDefaultToMerged(merged, defaults, config);
57
58
  }
59
+ async function tryResolveSubstitutedDefaultModel(merged, request, config, logger, mergeOptions, original) {
60
+ const resolveModels = config.aiTools?.resolveModels !== false;
61
+ const catalog = mergeOptions?.catalog;
62
+ if (!resolveModels || !catalog || !merged.model) {
63
+ return;
64
+ }
65
+ try {
66
+ const resolution = await catalog.resolveModel({
67
+ provider: merged.provider,
68
+ model: merged.model
69
+ });
70
+ if (!resolution.found) {
71
+ return;
72
+ }
73
+ applyModelResolution(merged, resolution, config.defaultEngine);
74
+ request._modelResolution = {
75
+ modelId: resolution.modelId,
76
+ routedViaOpenRouter: resolution.routedViaOpenRouter,
77
+ confidence: resolution.confidence,
78
+ resolvedVia: resolution.resolvedVia,
79
+ originalProvider: original?.provider ?? merged.provider,
80
+ originalModel: original?.model ?? merged.model
81
+ };
82
+ logger.verbose('Catalog resolved substituted default model', {
83
+ jobId: request.identity.jobId,
84
+ model: merged.model,
85
+ provider: merged.provider,
86
+ resolvedModelId: resolution.modelId
87
+ });
88
+ }
89
+ catch {
90
+ // Prod keeps the substituted bare default when re-resolution fails.
91
+ }
92
+ }
93
+ async function substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, reason, original) {
94
+ await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, reason, original);
95
+ await tryResolveSubstitutedDefaultModel(merged, request, config, logger, mergeOptions, original);
96
+ }
58
97
  /**
59
98
  * True when any caller-controlled config source set `maxTokens` (Optimixer should not override).
60
99
  */
@@ -137,7 +176,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
137
176
  const originalProvider = merged.provider;
138
177
  const originalModel = explicitModel;
139
178
  if (!explicitModel) {
140
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'no_model_provided');
179
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'no_model_provided');
141
180
  }
142
181
  else if (resolveModels && mergeOptions?.catalog) {
143
182
  try {
@@ -166,7 +205,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
166
205
  });
167
206
  }
168
207
  else if (isProdGatewayMode(operationalMode)) {
169
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'model_resolution_failed', { provider: originalProvider, model: originalModel });
208
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'model_resolution_failed', { provider: originalProvider, model: originalModel });
170
209
  }
171
210
  else {
172
211
  throw new ModelResolutionError({ provider: merged.provider, model: explicitModel }, resolution);
@@ -177,7 +216,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
177
216
  throw error;
178
217
  }
179
218
  if (isProdGatewayMode(operationalMode)) {
180
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
219
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
181
220
  }
182
221
  else {
183
222
  throw error;
@@ -185,10 +224,10 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
185
224
  }
186
225
  }
187
226
  else if (resolveModels && !mergeOptions?.catalog && isProdGatewayMode(operationalMode)) {
188
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
227
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
189
228
  }
190
229
  if (!merged.model) {
191
- await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, 'no_model_provided');
230
+ await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'no_model_provided');
192
231
  }
193
232
  const maxTokensExplicitlySet = isMaxTokensExplicitlySet(request, config);
194
233
  const optimixerWillPredict = config.optimixer?.enabled === true && !maxTokensExplicitlySet;
@@ -783,6 +822,46 @@ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
783
822
  }
784
823
  return out;
785
824
  }
825
+ function mapRouterFallbackAttempts(attempts) {
826
+ return attempts.map((attempt) => ({
827
+ provider: String(attempt.provider),
828
+ ...(attempt.model !== undefined ? { model: attempt.model } : {}),
829
+ ...(attempt.httpStatus !== undefined ? { httpStatus: attempt.httpStatus } : {}),
830
+ error: attempt.error instanceof Error ? attempt.error.message : String(attempt.error),
831
+ ...(attempt.responsePreview !== undefined ? { responsePreview: attempt.responsePreview } : {})
832
+ }));
833
+ }
834
+ function extractFallbackAttemptsFromError(error) {
835
+ if (error instanceof FallbackExhaustedError && error.attempts.length > 0) {
836
+ return mapRouterFallbackAttempts(error.attempts);
837
+ }
838
+ if (error != null && typeof error === 'object') {
839
+ const record = error;
840
+ if (record.name === 'FallbackExhaustedError' && Array.isArray(record.attempts) && record.attempts.length > 0) {
841
+ return mapRouterFallbackAttempts(record.attempts);
842
+ }
843
+ }
844
+ return undefined;
845
+ }
846
+ /**
847
+ * Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
848
+ */
849
+ export function tryExtractFallbackAttemptsFromErrorChain(error, maxDepth = 8) {
850
+ const seen = new Set();
851
+ let cur = error;
852
+ for (let i = 0; i < maxDepth && cur != null; i++) {
853
+ if (typeof cur !== 'object')
854
+ break;
855
+ if (seen.has(cur))
856
+ break;
857
+ seen.add(cur);
858
+ const attempts = extractFallbackAttemptsFromError(cur);
859
+ if (attempts?.length)
860
+ return attempts;
861
+ cur = cur.cause;
862
+ }
863
+ return undefined;
864
+ }
786
865
  export function buildInvokeRejectionMetadata(args) {
787
866
  const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
788
867
  const partial = args.partialRouterPayload;
@@ -796,6 +875,9 @@ export function buildInvokeRejectionMetadata(args) {
796
875
  tokens = undefined;
797
876
  }
798
877
  const requestIds = pickRequestIdsFromRouterLike(gid, partial);
878
+ const fallbackAttempts = args.error !== undefined
879
+ ? tryExtractFallbackAttemptsFromErrorChain(args.error)
880
+ : undefined;
799
881
  return {
800
882
  aiRequestId: args.request.aiRequestId,
801
883
  identity: args.request.identity,
@@ -805,6 +887,7 @@ export function buildInvokeRejectionMetadata(args) {
805
887
  ...(effective !== undefined ? { effectiveModelConfig: effective } : {}),
806
888
  ...(tokens !== undefined ? { tokens } : {}),
807
889
  ...(requestIds !== undefined ? { requestIds } : {}),
890
+ ...(fallbackAttempts !== undefined ? { fallbackAttempts } : {}),
808
891
  ...(mc === undefined ? { mergeConfigUnavailable: true } : {})
809
892
  };
810
893
  }
@@ -2,7 +2,7 @@
2
2
  * Gateway Utilities Module
3
3
  * Handles utility functions
4
4
  */
5
- import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
5
+ import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayFallbackAttempt, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
6
6
  import type { Logxer } from '@x12i/logxer';
7
7
  import { type AiCostResult, type AiModelsCatalogClient, type CostCalculator } from '@x12i/ai-tools';
8
8
  /**
@@ -155,6 +155,10 @@ export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<
155
155
  */
156
156
  export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
157
157
  export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
158
+ /**
159
+ * Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
160
+ */
161
+ export declare function tryExtractFallbackAttemptsFromErrorChain(error: unknown, maxDepth?: number): GatewayFallbackAttempt[] | undefined;
158
162
  export declare function buildInvokeRejectionMetadata(args: {
159
163
  request: Pick<AIInvokeRequest, 'aiRequestId' | 'identity' | 'config' | 'modelConfig'>;
160
164
  taskTypeId: string;
@@ -162,6 +166,7 @@ export declare function buildInvokeRejectionMetadata(args: {
162
166
  mergedConfig?: unknown;
163
167
  partialRouterPayload?: unknown;
164
168
  gatewayAiRequestId?: string;
169
+ error?: unknown;
165
170
  }): GatewayInvokeRejectionMetadata;
166
171
  export declare function attachGatewayInvokeRejectionMetadata(err: Error, metadata: GatewayInvokeRejectionMetadata): void;
167
172
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
@@ -17,7 +17,7 @@ import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
17
17
  import { invokeWithRetry } from './gateway-retry.js';
18
18
  /** Error message thrown by the router when no provider is registered or specified */
19
19
  const NO_PROVIDER_ERROR = 'No provider specified and no providers registered';
20
- const NO_PROVIDER_HINT = ' Set OPEN_ROUTER_KEY (or OPENROUTER_API_KEY) in the environment to use OpenRouter, or register a provider with the router (e.g. via autoRegisterProviders or gateway config).';
20
+ const NO_PROVIDER_HINT = ' Set OPENROUTER_API_KEY in the environment to use OpenRouter (legacy OPEN_ROUTER_KEY is still read as fallback), or register a provider with the router (e.g. via autoRegisterProviders or gateway config).';
21
21
  /** Warn when a successful call reports no tokens and/or explicit zero cost (often missing adapter metadata). */
22
22
  function warnIfSuccessfulInvokeReportsZeroUsageOrCost(logger, identity, meta, invokeKind) {
23
23
  const { tokens, costUsd, cost } = meta;
@@ -685,7 +685,8 @@ export class AIGateway {
685
685
  startTime,
686
686
  mergedConfig,
687
687
  partialRouterPayload: partial,
688
- gatewayAiRequestId: request.aiRequestId
688
+ gatewayAiRequestId: request.aiRequestId,
689
+ error: err
689
690
  });
690
691
  attachGatewayInvokeRejectionMetadata(err, rejectMeta);
691
692
  if (err.message.includes(NO_PROVIDER_ERROR)) {
@@ -17,7 +17,7 @@ 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, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
21
21
  export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
22
22
  export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
23
23
  export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
@@ -16,8 +16,8 @@ export * from '@x12i/ai-providers-router';
16
16
  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
- export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
20
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
19
+ export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayFallbackAttempt, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
21
21
  export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
22
22
  export type { GatewayOperationalMode, GatewayDefaultModelSource, DefaultModelSubstitutionReason, ResolvedGatewayDefault } from './gateway-mode.js';
23
23
  export type { ActivityCostStatus, ResolvedActivityCost } from './gateway-utils.js';
@@ -138,12 +138,25 @@ export type GatewayInvokeRejectionMetadata = {
138
138
  region?: string;
139
139
  effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
140
140
  requestIds?: GatewayTraceRequestIds;
141
+ /**
142
+ * Fallback candidates tried before exhaustion (non-trace {@link AIGateway.invoke} only).
143
+ * Sourced from {@link FallbackExhaustedError.attempts} on the router error chain.
144
+ */
145
+ fallbackAttempts?: GatewayFallbackAttempt[];
141
146
  /**
142
147
  * True when {@link mergeConfig} did not run (e.g. message-building threw first).
143
148
  * Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
144
149
  */
145
150
  mergeConfigUnavailable?: true;
146
151
  };
152
+ /** Serializable slice of a router fallback attempt for rejection metadata. */
153
+ export type GatewayFallbackAttempt = {
154
+ provider: string;
155
+ model?: string;
156
+ httpStatus?: number;
157
+ error: string;
158
+ responsePreview?: string;
159
+ };
147
160
  /**
148
161
  * Identity object used for activity linkage.
149
162
  * On gateway requests/responses it lives on `identity`. When activity tracking persists via Activix v5+,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x12i/ai-gateway",
3
- "version": "9.5.3",
3
+ "version": "9.6.0",
4
4
  "description": "AI Gateway - Unified interface for LLM provider routing and management",
5
5
  "type": "module",
6
6
  "exports": {
@@ -41,9 +41,9 @@
41
41
  "author": "x12i",
42
42
  "license": "mit",
43
43
  "dependencies": {
44
- "@x12i/activix": "^8.0.0",
45
- "@x12i/ai-providers-router": "^4.8.0",
46
- "@x12i/ai-tools": "^2.0.0",
44
+ "@x12i/activix": "^8.0.5",
45
+ "@x12i/ai-providers-router": "^4.8.5",
46
+ "@x12i/ai-tools": "^2.0.4",
47
47
  "@x12i/flex-md": "^4.8.0",
48
48
  "@x12i/logxer": "^4.3.5",
49
49
  "@x12i/optimixer": "^0.1.0",