@x12i/ai-gateway 10.4.3 → 10.4.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.
package/README.md CHANGED
@@ -13,7 +13,7 @@ Unified gateway for LLM provider routing, structured logging, optional Activix a
13
13
  | **Activix** | Optional Mongo-backed activity rows; billing written from gateway-computed slice on **`completeRecord`** (`outer.cost` + root fields). No Activix **`autoCost`** re-pricing. |
14
14
  | **Trace mode** | `diagnostics.mode === 'trace'` adds `metadata.attempts[]`, `metadata.usage`, and per-attempt **`costUsd`** / **`costStatus`**. |
15
15
 
16
- Pinned dependency versions are in `package.json` (currently **Activix ^9.0**, **ai-tools ^3.3**, **ai-profiles ^3.4**, **ai-providers-router ^4.9**, **openrouter-runtime ^1.0**).
16
+ Pinned dependency versions are in `package.json` (currently **Activix ^9.0**, **ai-tools ^3.3**, **ai-profiles ^3.4**, **ai-providers-router ^4.10**, **openrouter-runtime ^1.0.4**).
17
17
 
18
18
  ---
19
19
 
@@ -308,6 +308,17 @@ Do **not** add a direct **`@x12i/ai-tools`** dependency for post-call cost. For
308
308
 
309
309
  Full contract: [AI Gateway invoke execution metadata](./docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md).
310
310
 
311
+ ### Provider failure taxonomy (router ≥ 4.10)
312
+
313
+ When the fallback chain exhausts, **`@x12i/ai-providers-router`** assigns stable sub-codes (`PROVIDER_AUTH_FAILED`, `PROVIDER_RATE_LIMITED`, `PROVIDER_QUOTA_EXCEEDED`, `PROVIDER_MODEL_NOT_FOUND`, `PROVIDER_GATEWAY_UNREACHABLE`, `OPENROUTER_API_KEY_MISSING`, …). The gateway forwards them on:
314
+
315
+ | Surface | Fields |
316
+ |---------|--------|
317
+ | Thrown error | `error.code`, `error.operatorHint`, `error.details.providerAttempts[]` |
318
+ | Rejection metadata | `error.metadata.fallbackAttempts[]`, `error.metadata.providerAttempts[]`, `error.metadata.providerSubCode`, `error.metadata.operatorHint` |
319
+
320
+ Each attempt includes `{ provider, modelId?, httpStatus?, message, code }` — no prose parsing required. Trace-mode exhaustion uses `enrichGatewayFallbackExhaustedError()` before throw. Helpers are re-exported from `@x12i/ai-gateway` via the router (`enrichRunTaskError`, `providerAttemptsFromFallbackAttempts`, `operatorHintForProviderFailureCode`, …).
321
+
311
322
  ### Trace diagnostics
312
323
 
313
324
  ```typescript
@@ -3,6 +3,8 @@
3
3
  * Handles utility functions
4
4
  */
5
5
  import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayFallbackAttempt, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
6
+ import { FallbackExhaustedError } from '@x12i/ai-providers-router';
7
+ import type { ProviderFailureCode } from '@x12i/ai-providers-router';
6
8
  import type { Logxer } from '@x12i/logxer';
7
9
  import { type AiCostResult, type AiModelsCatalogClient, type CostCalculator, type OpenRouterRoutingConfig } from '@x12i/ai-tools';
8
10
  import { pickOpenRouterRuntimeMetadataSlice, enrichTraceOpenRouterRuntimeMetadata } from './openrouter-runtime-adapter/index.js';
@@ -191,9 +193,13 @@ export declare function mapGatewayFallbackAttemptsToRouter(attempts: GatewayFall
191
193
  provider: string;
192
194
  model?: string;
193
195
  httpStatus?: number;
196
+ code?: ProviderFailureCode;
197
+ message?: string;
194
198
  error: Error;
195
199
  responsePreview?: string;
196
200
  }>;
201
+ /** Apply router ≥ 4.10 provider failure enrichment (code, operatorHint, details.providerAttempts). */
202
+ export declare function enrichGatewayFallbackExhaustedError(exhausted: FallbackExhaustedError): FallbackExhaustedError;
197
203
  /**
198
204
  * Log original input vs OpenRouter model id actually sent to the router after catalog resolution.
199
205
  */
@@ -3,7 +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
+ import { FallbackExhaustedError, enrichRunTaskError, providerAttemptsFromFallbackAttempts } from '@x12i/ai-providers-router';
7
7
  import { ModelResolutionError, ModelProfileInputRejectedError, ModelProfileUnroutableError, resolveInvokeModel, } from '@x12i/ai-tools';
8
8
  import { extractHttpStatusCode } from './gateway-retry.js';
9
9
  import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
@@ -980,10 +980,17 @@ export function mapGatewayFallbackAttemptsToRouter(attempts) {
980
980
  provider: a.provider,
981
981
  model: a.model,
982
982
  httpStatus: a.httpStatus,
983
+ ...(a.code !== undefined ? { code: a.code } : {}),
984
+ message: a.error,
983
985
  error: new Error(a.error),
984
- responsePreview: a.responsePreview
986
+ responsePreview: a.responsePreview,
985
987
  }));
986
988
  }
989
+ /** Apply router ≥ 4.10 provider failure enrichment (code, operatorHint, details.providerAttempts). */
990
+ export function enrichGatewayFallbackExhaustedError(exhausted) {
991
+ const routerAttempts = providerAttemptsFromFallbackAttempts(exhausted.attempts);
992
+ return enrichRunTaskError(exhausted, { providerAttempts: routerAttempts });
993
+ }
987
994
  /**
988
995
  * Log original input vs OpenRouter model id actually sent to the router after catalog resolution.
989
996
  */
@@ -1009,20 +1016,58 @@ export function logResolvedModelRouting(logger, request, mergedConfig) {
1009
1016
  }));
1010
1017
  }
1011
1018
  function mapRouterFallbackAttempts(attempts) {
1012
- return attempts.map((attempt) => ({
1013
- provider: String(attempt.provider),
1014
- ...(attempt.model !== undefined ? { model: attempt.model } : {}),
1015
- ...(attempt.httpStatus !== undefined ? { httpStatus: attempt.httpStatus } : {}),
1016
- error: attempt.error instanceof Error ? attempt.error.message : String(attempt.error),
1017
- ...(attempt.responsePreview !== undefined ? { responsePreview: attempt.responsePreview } : {})
1019
+ return attempts.map((attempt) => {
1020
+ const errorMsg = typeof attempt.message === 'string' && attempt.message.trim()
1021
+ ? attempt.message
1022
+ : attempt.error instanceof Error
1023
+ ? attempt.error.message
1024
+ : String(attempt.error);
1025
+ return {
1026
+ provider: String(attempt.provider),
1027
+ ...(attempt.model !== undefined ? { model: attempt.model } : {}),
1028
+ ...(attempt.httpStatus !== undefined ? { httpStatus: attempt.httpStatus } : {}),
1029
+ error: errorMsg,
1030
+ ...(attempt.code !== undefined ? { code: attempt.code } : {}),
1031
+ ...(attempt.responsePreview !== undefined ? { responsePreview: attempt.responsePreview } : {}),
1032
+ };
1033
+ });
1034
+ }
1035
+ function mapProviderAttemptDetailsToGateway(attempts) {
1036
+ return attempts.map((a) => ({
1037
+ provider: a.provider,
1038
+ ...(a.modelId !== undefined ? { model: a.modelId } : {}),
1039
+ ...(a.httpStatus !== undefined ? { httpStatus: a.httpStatus } : {}),
1040
+ error: a.message,
1041
+ ...(a.code !== undefined ? { code: a.code } : {}),
1018
1042
  }));
1019
1043
  }
1044
+ function extractEnrichedFieldsFromError(error) {
1045
+ if (error == null || typeof error !== 'object')
1046
+ return {};
1047
+ const e = error;
1048
+ return {
1049
+ ...(typeof e.code === 'string' ? { providerSubCode: e.code } : {}),
1050
+ ...(typeof e.operatorHint === 'string' ? { operatorHint: e.operatorHint } : {}),
1051
+ ...(e.details?.providerAttempts?.length
1052
+ ? { providerAttempts: e.details.providerAttempts }
1053
+ : {}),
1054
+ };
1055
+ }
1020
1056
  function extractFallbackAttemptsFromError(error) {
1021
- if (error instanceof FallbackExhaustedError && error.attempts.length > 0) {
1022
- return mapRouterFallbackAttempts(error.attempts);
1057
+ if (error instanceof FallbackExhaustedError) {
1058
+ if (error.details?.providerAttempts?.length) {
1059
+ return mapProviderAttemptDetailsToGateway(error.details.providerAttempts);
1060
+ }
1061
+ if (error.attempts.length > 0) {
1062
+ return mapRouterFallbackAttempts(error.attempts);
1063
+ }
1023
1064
  }
1024
1065
  if (error != null && typeof error === 'object') {
1025
1066
  const record = error;
1067
+ const details = record.details;
1068
+ if (details?.providerAttempts?.length) {
1069
+ return mapProviderAttemptDetailsToGateway(details.providerAttempts);
1070
+ }
1026
1071
  if (record.name === 'FallbackExhaustedError' && Array.isArray(record.attempts) && record.attempts.length > 0) {
1027
1072
  return mapRouterFallbackAttempts(record.attempts);
1028
1073
  }
@@ -1064,6 +1109,17 @@ export function buildInvokeRejectionMetadata(args) {
1064
1109
  const fallbackAttempts = args.error !== undefined
1065
1110
  ? tryExtractFallbackAttemptsFromErrorChain(args.error)
1066
1111
  : undefined;
1112
+ const enriched = args.error !== undefined ? extractEnrichedFieldsFromError(args.error) : {};
1113
+ const providerAttempts = enriched.providerAttempts ??
1114
+ (fallbackAttempts?.length
1115
+ ? fallbackAttempts.map((a) => ({
1116
+ provider: a.provider,
1117
+ ...(a.model ? { modelId: a.model } : {}),
1118
+ ...(a.httpStatus !== undefined ? { httpStatus: a.httpStatus } : {}),
1119
+ message: a.error,
1120
+ ...(a.code ? { code: a.code } : {}),
1121
+ }))
1122
+ : undefined);
1067
1123
  const openrouterFromPartial = partial !== undefined ? pickEnhancedOpenRouterMetadata(partial, false) : {};
1068
1124
  const openrouterFromError = args.error !== undefined ? tryExtractOpenRouterMetadataFromErrorChain(args.error) : {};
1069
1125
  const openrouterMetadata = {
@@ -1090,6 +1146,9 @@ export function buildInvokeRejectionMetadata(args) {
1090
1146
  ...(tokens !== undefined ? { tokens } : {}),
1091
1147
  ...(requestIds !== undefined ? { requestIds } : {}),
1092
1148
  ...(fallbackAttempts !== undefined ? { fallbackAttempts } : {}),
1149
+ ...(providerAttempts?.length ? { providerAttempts } : {}),
1150
+ ...(enriched.providerSubCode ? { providerSubCode: enriched.providerSubCode } : {}),
1151
+ ...(enriched.operatorHint ? { operatorHint: enriched.operatorHint } : {}),
1093
1152
  ...(mc === undefined ? { mergeConfigUnavailable: true } : {}),
1094
1153
  ...(openrouterMetadata.serverTools ? { serverTools: openrouterMetadata.serverTools } : {}),
1095
1154
  ...(openrouterMetadata.citations?.length ? { citations: openrouterMetadata.citations } : {}),
package/dist/gateway.js CHANGED
@@ -11,7 +11,7 @@ import { resolveRetryConfig } from './gateway-defaults.js';
11
11
  import { buildMessages } from './message-builder.js';
12
12
  import { extractJsonFromFlexMd } from './flex-md-loader.js';
13
13
  import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
14
- import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, pickEnhancedOpenRouterMetadata, resolveCostCompletionWithAiTools, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, } from './gateway-utils.js';
14
+ import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, enrichGatewayFallbackExhaustedError, mapGatewayFallbackAttemptsToRouter, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, pickEnhancedOpenRouterMetadata, resolveCostCompletionWithAiTools, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, } from './gateway-utils.js';
15
15
  import { buildTraceAttemptOpenRouterRuntimeSlice } from './openrouter-runtime-adapter/index.js';
16
16
  import { getAiToolsClient } from './ai-tools-client.js';
17
17
  import { autoRegisterProviders } from './gateway-provider-auto-register.js';
@@ -512,7 +512,7 @@ export class AIGateway {
512
512
  actual: 'All candidates failed or returned no response.'
513
513
  }
514
514
  });
515
- const exhausted = new FallbackExhaustedError(mapGatewayFallbackAttemptsToRouter(fallbackAttempts));
515
+ const exhausted = enrichGatewayFallbackExhaustedError(new FallbackExhaustedError(mapGatewayFallbackAttemptsToRouter(fallbackAttempts)));
516
516
  exhausted.message = formatFallbackExhaustionMessage(fallbackAttempts, deduped);
517
517
  if (lastError) {
518
518
  exhausted.cause = lastError;
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export { LLMProviderRouter } from '@x12i/ai-providers-router';
9
9
  export type { RouterConfig, HealthCheckResult } from '@x12i/ai-providers-router';
10
10
  export { createRouter, createRouterFromConfig } from '@x12i/ai-providers-router';
11
11
  export type { CreateRouterConfig } from '@x12i/ai-providers-router';
12
- export { ProviderNotFoundError, FallbackExhaustedError } from '@x12i/ai-providers-router';
12
+ export { ProviderNotFoundError, FallbackExhaustedError, OpenRouterApiKeyMissingError, } from '@x12i/ai-providers-router';
13
13
  export type { RequestInterceptor, ResponseInterceptor } from '@x12i/ai-providers-router';
14
14
  export type { UsageTracker } from '@x12i/ai-providers-router';
15
15
  export * from '@x12i/ai-providers-router';
@@ -19,7 +19,7 @@ export { normalizeInvokeModelAtIngress } from './invoke-model-ingress.js';
19
19
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
20
20
  export { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, mapGatewayServerTools, } from './openrouter-runtime-adapter/index.js';
21
21
  export type { GatewayConfig, ProviderModelRef, ModelConfig, GatewayModelConfig, GatewayServerToolsConfig, GatewayServerToolMode, GatewayServerToolUsage, GatewayNestedServerTool, GatewayWebSearchToolConfig, GatewayWebFetchToolConfig, GatewayDatetimeToolConfig, GatewayImageGenerationToolConfig, GatewayApplyPatchToolConfig, GatewayFusionToolConfig, GatewayAdvisorToolConfig, GatewaySubagentToolConfig, GatewayOpenRouterErrorMetadata, GatewayOpenRouterConfig, GatewayOpenRouterRuntimeConfig, GatewayServerToolUsageMap, GatewayCitation, GatewayGeneratedImage, GatewayPatchProposal, GatewayOpenRouterRuntimeMetadata, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayFallbackAttempt, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
22
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
22
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, enrichGatewayFallbackExhaustedError, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
23
23
  export { getGatewayOperationalMode, isProdGatewayMode, parseModelProviderSpec } from './gateway-mode.js';
24
24
  export type { GatewayOperationalMode } from './gateway-mode.js';
25
25
  export { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_RETRY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P, resolveRetryConfig } from './gateway-defaults.js';
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ export { LLMProviderRouter } from '@x12i/ai-providers-router';
10
10
  // Re-export factory functions
11
11
  export { createRouter, createRouterFromConfig } from '@x12i/ai-providers-router';
12
12
  // Re-export error classes
13
- export { ProviderNotFoundError, FallbackExhaustedError } from '@x12i/ai-providers-router';
13
+ export { ProviderNotFoundError, FallbackExhaustedError, OpenRouterApiKeyMissingError, } from '@x12i/ai-providers-router';
14
14
  // Re-export all from router (includes everything it exports)
15
15
  export * from '@x12i/ai-providers-router';
16
16
  // Export enhanced gateway
@@ -19,7 +19,7 @@ export { InstructionNotFoundError, InstructionBackendError, ModelRequiredError,
19
19
  export { normalizeInvokeModelAtIngress } from './invoke-model-ingress.js';
20
20
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
21
21
  export { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, mapGatewayServerTools, } from './openrouter-runtime-adapter/index.js';
22
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
22
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, enrichGatewayFallbackExhaustedError, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
23
23
  export { getGatewayOperationalMode, isProdGatewayMode, parseModelProviderSpec } from './gateway-mode.js';
24
24
  export { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_RETRY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P, resolveRetryConfig } from './gateway-defaults.js';
25
25
  export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
package/dist/types.d.ts CHANGED
@@ -359,6 +359,21 @@ export type GatewayInvokeRejectionMetadata = {
359
359
  * Sourced from {@link FallbackExhaustedError.attempts} on the router error chain.
360
360
  */
361
361
  fallbackAttempts?: GatewayFallbackAttempt[];
362
+ /**
363
+ * Structured per-attempt diagnostics (`@x12i/ai-providers-router` ≥ 4.10).
364
+ * Same attempts as {@link fallbackAttempts} when enriched; prefer this for UIs.
365
+ */
366
+ providerAttempts?: Array<{
367
+ provider: string;
368
+ modelId?: string;
369
+ httpStatus?: number;
370
+ message: string;
371
+ code?: string;
372
+ }>;
373
+ /** Dominant stable failure sub-code when fallback chain exhausts (router ≥ 4.10). */
374
+ providerSubCode?: string;
375
+ /** Short remediation hint from router enrichment (router ≥ 4.10). */
376
+ operatorHint?: string;
362
377
  /**
363
378
  * True when {@link mergeConfig} did not run (e.g. message-building threw first).
364
379
  * Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
@@ -378,6 +393,8 @@ export type GatewayFallbackAttempt = {
378
393
  model?: string;
379
394
  httpStatus?: number;
380
395
  error: string;
396
+ /** Stable sub-code from `@x12i/ai-providers-router` ≥ 4.10 (`PROVIDER_AUTH_FAILED`, …). */
397
+ code?: string;
381
398
  responsePreview?: string;
382
399
  };
383
400
  /**
@@ -3,7 +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
+ import { FallbackExhaustedError, enrichRunTaskError, providerAttemptsFromFallbackAttempts } from '@x12i/ai-providers-router';
7
7
  import { ModelResolutionError, ModelProfileInputRejectedError, ModelProfileUnroutableError, resolveInvokeModel, } from '@x12i/ai-tools';
8
8
  import { extractHttpStatusCode } from './gateway-retry.js';
9
9
  import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
@@ -980,10 +980,17 @@ export function mapGatewayFallbackAttemptsToRouter(attempts) {
980
980
  provider: a.provider,
981
981
  model: a.model,
982
982
  httpStatus: a.httpStatus,
983
+ ...(a.code !== undefined ? { code: a.code } : {}),
984
+ message: a.error,
983
985
  error: new Error(a.error),
984
- responsePreview: a.responsePreview
986
+ responsePreview: a.responsePreview,
985
987
  }));
986
988
  }
989
+ /** Apply router ≥ 4.10 provider failure enrichment (code, operatorHint, details.providerAttempts). */
990
+ export function enrichGatewayFallbackExhaustedError(exhausted) {
991
+ const routerAttempts = providerAttemptsFromFallbackAttempts(exhausted.attempts);
992
+ return enrichRunTaskError(exhausted, { providerAttempts: routerAttempts });
993
+ }
987
994
  /**
988
995
  * Log original input vs OpenRouter model id actually sent to the router after catalog resolution.
989
996
  */
@@ -1009,20 +1016,58 @@ export function logResolvedModelRouting(logger, request, mergedConfig) {
1009
1016
  }));
1010
1017
  }
1011
1018
  function mapRouterFallbackAttempts(attempts) {
1012
- return attempts.map((attempt) => ({
1013
- provider: String(attempt.provider),
1014
- ...(attempt.model !== undefined ? { model: attempt.model } : {}),
1015
- ...(attempt.httpStatus !== undefined ? { httpStatus: attempt.httpStatus } : {}),
1016
- error: attempt.error instanceof Error ? attempt.error.message : String(attempt.error),
1017
- ...(attempt.responsePreview !== undefined ? { responsePreview: attempt.responsePreview } : {})
1019
+ return attempts.map((attempt) => {
1020
+ const errorMsg = typeof attempt.message === 'string' && attempt.message.trim()
1021
+ ? attempt.message
1022
+ : attempt.error instanceof Error
1023
+ ? attempt.error.message
1024
+ : String(attempt.error);
1025
+ return {
1026
+ provider: String(attempt.provider),
1027
+ ...(attempt.model !== undefined ? { model: attempt.model } : {}),
1028
+ ...(attempt.httpStatus !== undefined ? { httpStatus: attempt.httpStatus } : {}),
1029
+ error: errorMsg,
1030
+ ...(attempt.code !== undefined ? { code: attempt.code } : {}),
1031
+ ...(attempt.responsePreview !== undefined ? { responsePreview: attempt.responsePreview } : {}),
1032
+ };
1033
+ });
1034
+ }
1035
+ function mapProviderAttemptDetailsToGateway(attempts) {
1036
+ return attempts.map((a) => ({
1037
+ provider: a.provider,
1038
+ ...(a.modelId !== undefined ? { model: a.modelId } : {}),
1039
+ ...(a.httpStatus !== undefined ? { httpStatus: a.httpStatus } : {}),
1040
+ error: a.message,
1041
+ ...(a.code !== undefined ? { code: a.code } : {}),
1018
1042
  }));
1019
1043
  }
1044
+ function extractEnrichedFieldsFromError(error) {
1045
+ if (error == null || typeof error !== 'object')
1046
+ return {};
1047
+ const e = error;
1048
+ return {
1049
+ ...(typeof e.code === 'string' ? { providerSubCode: e.code } : {}),
1050
+ ...(typeof e.operatorHint === 'string' ? { operatorHint: e.operatorHint } : {}),
1051
+ ...(e.details?.providerAttempts?.length
1052
+ ? { providerAttempts: e.details.providerAttempts }
1053
+ : {}),
1054
+ };
1055
+ }
1020
1056
  function extractFallbackAttemptsFromError(error) {
1021
- if (error instanceof FallbackExhaustedError && error.attempts.length > 0) {
1022
- return mapRouterFallbackAttempts(error.attempts);
1057
+ if (error instanceof FallbackExhaustedError) {
1058
+ if (error.details?.providerAttempts?.length) {
1059
+ return mapProviderAttemptDetailsToGateway(error.details.providerAttempts);
1060
+ }
1061
+ if (error.attempts.length > 0) {
1062
+ return mapRouterFallbackAttempts(error.attempts);
1063
+ }
1023
1064
  }
1024
1065
  if (error != null && typeof error === 'object') {
1025
1066
  const record = error;
1067
+ const details = record.details;
1068
+ if (details?.providerAttempts?.length) {
1069
+ return mapProviderAttemptDetailsToGateway(details.providerAttempts);
1070
+ }
1026
1071
  if (record.name === 'FallbackExhaustedError' && Array.isArray(record.attempts) && record.attempts.length > 0) {
1027
1072
  return mapRouterFallbackAttempts(record.attempts);
1028
1073
  }
@@ -1064,6 +1109,17 @@ export function buildInvokeRejectionMetadata(args) {
1064
1109
  const fallbackAttempts = args.error !== undefined
1065
1110
  ? tryExtractFallbackAttemptsFromErrorChain(args.error)
1066
1111
  : undefined;
1112
+ const enriched = args.error !== undefined ? extractEnrichedFieldsFromError(args.error) : {};
1113
+ const providerAttempts = enriched.providerAttempts ??
1114
+ (fallbackAttempts?.length
1115
+ ? fallbackAttempts.map((a) => ({
1116
+ provider: a.provider,
1117
+ ...(a.model ? { modelId: a.model } : {}),
1118
+ ...(a.httpStatus !== undefined ? { httpStatus: a.httpStatus } : {}),
1119
+ message: a.error,
1120
+ ...(a.code ? { code: a.code } : {}),
1121
+ }))
1122
+ : undefined);
1067
1123
  const openrouterFromPartial = partial !== undefined ? pickEnhancedOpenRouterMetadata(partial, false) : {};
1068
1124
  const openrouterFromError = args.error !== undefined ? tryExtractOpenRouterMetadataFromErrorChain(args.error) : {};
1069
1125
  const openrouterMetadata = {
@@ -1090,6 +1146,9 @@ export function buildInvokeRejectionMetadata(args) {
1090
1146
  ...(tokens !== undefined ? { tokens } : {}),
1091
1147
  ...(requestIds !== undefined ? { requestIds } : {}),
1092
1148
  ...(fallbackAttempts !== undefined ? { fallbackAttempts } : {}),
1149
+ ...(providerAttempts?.length ? { providerAttempts } : {}),
1150
+ ...(enriched.providerSubCode ? { providerSubCode: enriched.providerSubCode } : {}),
1151
+ ...(enriched.operatorHint ? { operatorHint: enriched.operatorHint } : {}),
1093
1152
  ...(mc === undefined ? { mergeConfigUnavailable: true } : {}),
1094
1153
  ...(openrouterMetadata.serverTools ? { serverTools: openrouterMetadata.serverTools } : {}),
1095
1154
  ...(openrouterMetadata.citations?.length ? { citations: openrouterMetadata.citations } : {}),
@@ -3,6 +3,8 @@
3
3
  * Handles utility functions
4
4
  */
5
5
  import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayFallbackAttempt, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
6
+ import { FallbackExhaustedError } from '@x12i/ai-providers-router';
7
+ import type { ProviderFailureCode } from '@x12i/ai-providers-router';
6
8
  import type { Logxer } from '@x12i/logxer';
7
9
  import { type AiCostResult, type AiModelsCatalogClient, type CostCalculator, type OpenRouterRoutingConfig } from '@x12i/ai-tools';
8
10
  import { pickOpenRouterRuntimeMetadataSlice, enrichTraceOpenRouterRuntimeMetadata } from './openrouter-runtime-adapter/index.js';
@@ -191,9 +193,13 @@ export declare function mapGatewayFallbackAttemptsToRouter(attempts: GatewayFall
191
193
  provider: string;
192
194
  model?: string;
193
195
  httpStatus?: number;
196
+ code?: ProviderFailureCode;
197
+ message?: string;
194
198
  error: Error;
195
199
  responsePreview?: string;
196
200
  }>;
201
+ /** Apply router ≥ 4.10 provider failure enrichment (code, operatorHint, details.providerAttempts). */
202
+ export declare function enrichGatewayFallbackExhaustedError(exhausted: FallbackExhaustedError): FallbackExhaustedError;
197
203
  /**
198
204
  * Log original input vs OpenRouter model id actually sent to the router after catalog resolution.
199
205
  */
@@ -11,7 +11,7 @@ import { resolveRetryConfig } from './gateway-defaults.js';
11
11
  import { buildMessages } from './message-builder.js';
12
12
  import { extractJsonFromFlexMd } from './flex-md-loader.js';
13
13
  import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
14
- import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, pickEnhancedOpenRouterMetadata, resolveCostCompletionWithAiTools, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, } from './gateway-utils.js';
14
+ import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, enrichGatewayFallbackExhaustedError, mapGatewayFallbackAttemptsToRouter, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, pickEnhancedOpenRouterMetadata, resolveCostCompletionWithAiTools, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, } from './gateway-utils.js';
15
15
  import { buildTraceAttemptOpenRouterRuntimeSlice } from './openrouter-runtime-adapter/index.js';
16
16
  import { getAiToolsClient } from './ai-tools-client.js';
17
17
  import { autoRegisterProviders } from './gateway-provider-auto-register.js';
@@ -512,7 +512,7 @@ export class AIGateway {
512
512
  actual: 'All candidates failed or returned no response.'
513
513
  }
514
514
  });
515
- const exhausted = new FallbackExhaustedError(mapGatewayFallbackAttemptsToRouter(fallbackAttempts));
515
+ const exhausted = enrichGatewayFallbackExhaustedError(new FallbackExhaustedError(mapGatewayFallbackAttemptsToRouter(fallbackAttempts)));
516
516
  exhausted.message = formatFallbackExhaustionMessage(fallbackAttempts, deduped);
517
517
  if (lastError) {
518
518
  exhausted.cause = lastError;
@@ -10,7 +10,7 @@ export { LLMProviderRouter } from '@x12i/ai-providers-router';
10
10
  // Re-export factory functions
11
11
  export { createRouter, createRouterFromConfig } from '@x12i/ai-providers-router';
12
12
  // Re-export error classes
13
- export { ProviderNotFoundError, FallbackExhaustedError } from '@x12i/ai-providers-router';
13
+ export { ProviderNotFoundError, FallbackExhaustedError, OpenRouterApiKeyMissingError, } from '@x12i/ai-providers-router';
14
14
  // Re-export all from router (includes everything it exports)
15
15
  export * from '@x12i/ai-providers-router';
16
16
  // Export enhanced gateway
@@ -19,7 +19,7 @@ export { InstructionNotFoundError, InstructionBackendError, ModelRequiredError,
19
19
  export { normalizeInvokeModelAtIngress } from './invoke-model-ingress.js';
20
20
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
21
21
  export { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, mapGatewayServerTools, } from './openrouter-runtime-adapter/index.js';
22
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
22
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, enrichGatewayFallbackExhaustedError, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
23
23
  export { getGatewayOperationalMode, isProdGatewayMode, parseModelProviderSpec } from './gateway-mode.js';
24
24
  export { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_RETRY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P, resolveRetryConfig } from './gateway-defaults.js';
25
25
  export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
@@ -9,7 +9,7 @@ export { LLMProviderRouter } from '@x12i/ai-providers-router';
9
9
  export type { RouterConfig, HealthCheckResult } from '@x12i/ai-providers-router';
10
10
  export { createRouter, createRouterFromConfig } from '@x12i/ai-providers-router';
11
11
  export type { CreateRouterConfig } from '@x12i/ai-providers-router';
12
- export { ProviderNotFoundError, FallbackExhaustedError } from '@x12i/ai-providers-router';
12
+ export { ProviderNotFoundError, FallbackExhaustedError, OpenRouterApiKeyMissingError, } from '@x12i/ai-providers-router';
13
13
  export type { RequestInterceptor, ResponseInterceptor } from '@x12i/ai-providers-router';
14
14
  export type { UsageTracker } from '@x12i/ai-providers-router';
15
15
  export * from '@x12i/ai-providers-router';
@@ -19,7 +19,7 @@ export { normalizeInvokeModelAtIngress } from './invoke-model-ingress.js';
19
19
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
20
20
  export { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, mapGatewayServerTools, } from './openrouter-runtime-adapter/index.js';
21
21
  export type { GatewayConfig, ProviderModelRef, ModelConfig, GatewayModelConfig, GatewayServerToolsConfig, GatewayServerToolMode, GatewayServerToolUsage, GatewayNestedServerTool, GatewayWebSearchToolConfig, GatewayWebFetchToolConfig, GatewayDatetimeToolConfig, GatewayImageGenerationToolConfig, GatewayApplyPatchToolConfig, GatewayFusionToolConfig, GatewayAdvisorToolConfig, GatewaySubagentToolConfig, GatewayOpenRouterErrorMetadata, GatewayOpenRouterConfig, GatewayOpenRouterRuntimeConfig, GatewayServerToolUsageMap, GatewayCitation, GatewayGeneratedImage, GatewayPatchProposal, GatewayOpenRouterRuntimeMetadata, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayFallbackAttempt, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
22
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
22
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractOpenRouterMetadataFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, enrichGatewayFallbackExhaustedError, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
23
23
  export { getGatewayOperationalMode, isProdGatewayMode, parseModelProviderSpec } from './gateway-mode.js';
24
24
  export type { GatewayOperationalMode } from './gateway-mode.js';
25
25
  export { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_RETRY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P, resolveRetryConfig } from './gateway-defaults.js';
@@ -359,6 +359,21 @@ export type GatewayInvokeRejectionMetadata = {
359
359
  * Sourced from {@link FallbackExhaustedError.attempts} on the router error chain.
360
360
  */
361
361
  fallbackAttempts?: GatewayFallbackAttempt[];
362
+ /**
363
+ * Structured per-attempt diagnostics (`@x12i/ai-providers-router` ≥ 4.10).
364
+ * Same attempts as {@link fallbackAttempts} when enriched; prefer this for UIs.
365
+ */
366
+ providerAttempts?: Array<{
367
+ provider: string;
368
+ modelId?: string;
369
+ httpStatus?: number;
370
+ message: string;
371
+ code?: string;
372
+ }>;
373
+ /** Dominant stable failure sub-code when fallback chain exhausts (router ≥ 4.10). */
374
+ providerSubCode?: string;
375
+ /** Short remediation hint from router enrichment (router ≥ 4.10). */
376
+ operatorHint?: string;
362
377
  /**
363
378
  * True when {@link mergeConfig} did not run (e.g. message-building threw first).
364
379
  * Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
@@ -378,6 +393,8 @@ export type GatewayFallbackAttempt = {
378
393
  model?: string;
379
394
  httpStatus?: number;
380
395
  error: string;
396
+ /** Stable sub-code from `@x12i/ai-providers-router` ≥ 4.10 (`PROVIDER_AUTH_FAILED`, …). */
397
+ code?: string;
381
398
  responsePreview?: string;
382
399
  };
383
400
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x12i/ai-gateway",
3
- "version": "10.4.3",
3
+ "version": "10.4.4",
4
4
  "description": "AI Gateway - Unified interface for LLM provider routing and management",
5
5
  "type": "module",
6
6
  "exports": {
@@ -44,11 +44,11 @@
44
44
  "dependencies": {
45
45
  "@x12i/activix": "^9.0.2",
46
46
  "@x12i/ai-profiles": "^3.4.1",
47
- "@x12i/ai-providers-router": "^4.9.2",
47
+ "@x12i/ai-providers-router": "^4.10.0",
48
48
  "@x12i/ai-tools": "^3.3.5",
49
49
  "@x12i/flex-md": "^4.8.0",
50
50
  "@x12i/logxer": "^5.1.0",
51
- "@x12i/openrouter-runtime": "^1.0.3",
51
+ "@x12i/openrouter-runtime": "^1.0.4",
52
52
  "@x12i/rendrix": "^4.3.0"
53
53
  },
54
54
  "devDependencies": {