@x12i/ai-gateway 9.1.1 → 9.1.2

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.
@@ -2,7 +2,7 @@
2
2
  * Gateway Utilities Module
3
3
  * Handles utility functions
4
4
  */
5
- import type { ChatRequest, GatewayConfig, ModelConfig } from './types.js';
5
+ import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, ModelConfig } from './types.js';
6
6
  import type { Logxer } from '@x12i/logxer';
7
7
  /**
8
8
  * Generates MD5 hash of a string
@@ -57,6 +57,27 @@ export declare function pickInvokeRoutingMetadataSlice(routerResponse: unknown,
57
57
  * Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
58
58
  */
59
59
  export declare function pickEffectiveModelConfigForMetadata(mergedConfig: unknown): Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>> | undefined;
60
+ declare const EFFECTIVE_MODEL_CONFIG_KEYS: readonly ["model", "modelId", "provider", "temperature", "maxTokens", "topP"];
61
+ /**
62
+ * Allowlisted generation fields from request only (before mergeConfig / flex-md).
63
+ * Priority matches mergeConfig: modelConfig overrides request.config per key.
64
+ */
65
+ export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<AIInvokeRequest, 'config' | 'modelConfig'>): Partial<Pick<ModelConfig, (typeof EFFECTIVE_MODEL_CONFIG_KEYS)[number]>> | undefined;
66
+ /**
67
+ * Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
68
+ * to find a router-shaped object for token / correlation extraction.
69
+ */
70
+ export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
71
+ export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
72
+ export declare function buildInvokeRejectionMetadata(args: {
73
+ request: Pick<AIInvokeRequest, 'aiRequestId' | 'identity' | 'config' | 'modelConfig'>;
74
+ taskTypeId: string;
75
+ startTime: number;
76
+ mergedConfig?: unknown;
77
+ partialRouterPayload?: unknown;
78
+ gatewayAiRequestId?: string;
79
+ }): GatewayInvokeRejectionMetadata;
80
+ export declare function attachGatewayInvokeRejectionMetadata(err: Error, metadata: GatewayInvokeRejectionMetadata): void;
60
81
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
61
82
  export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
62
83
  /**
@@ -64,3 +85,4 @@ export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
64
85
  * Non-serializable values become a small marker object instead of throwing.
65
86
  */
66
87
  export declare function capActivityFullResponsePayload(payload: unknown, maxChars?: number): unknown;
88
+ export {};
@@ -358,6 +358,114 @@ export function pickEffectiveModelConfigForMetadata(mergedConfig) {
358
358
  }
359
359
  return Object.keys(out).length ? out : undefined;
360
360
  }
361
+ const EFFECTIVE_MODEL_CONFIG_KEYS = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
362
+ /**
363
+ * Allowlisted generation fields from request only (before mergeConfig / flex-md).
364
+ * Priority matches mergeConfig: modelConfig overrides request.config per key.
365
+ */
366
+ export function pickEffectiveModelConfigFromInvokeRequest(request) {
367
+ const cfg = (request.config ?? {});
368
+ const mc = (request.modelConfig ?? {});
369
+ const out = {};
370
+ for (const k of EFFECTIVE_MODEL_CONFIG_KEYS) {
371
+ const v = mc[k] ?? cfg[k];
372
+ if (v !== undefined)
373
+ out[k] = v;
374
+ }
375
+ const modelFromId = out.model === undefined && typeof mc.modelId === 'string' ? mc.modelId : undefined;
376
+ if (modelFromId !== undefined)
377
+ out.model = modelFromId;
378
+ return Object.keys(out).length ? out : undefined;
379
+ }
380
+ function isRouterLikeEnvelope(value) {
381
+ if (value == null || typeof value !== 'object')
382
+ return false;
383
+ const r = value;
384
+ return ('metadata' in r ||
385
+ 'outputText' in r ||
386
+ 'content' in r ||
387
+ 'requestId' in r ||
388
+ 'usage' in r);
389
+ }
390
+ /**
391
+ * Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
392
+ * to find a router-shaped object for token / correlation extraction.
393
+ */
394
+ export function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
395
+ const seen = new Set();
396
+ let cur = error;
397
+ for (let i = 0; i < maxDepth && cur != null; i++) {
398
+ if (typeof cur !== 'object')
399
+ break;
400
+ if (seen.has(cur))
401
+ break;
402
+ seen.add(cur);
403
+ const o = cur;
404
+ if (isRouterLikeEnvelope(cur))
405
+ return cur;
406
+ const nested = [o.response, o.routerResponse, o.lastResponse, o.body, o.data];
407
+ for (const n of nested) {
408
+ if (isRouterLikeEnvelope(n))
409
+ return n;
410
+ }
411
+ cur = o.cause;
412
+ }
413
+ return undefined;
414
+ }
415
+ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
416
+ if (typeof gatewayAiRequestId !== 'string' || gatewayAiRequestId.length === 0) {
417
+ return undefined;
418
+ }
419
+ const out = { gatewayAiRequestId };
420
+ if (routerLike == null || typeof routerLike !== 'object') {
421
+ return out;
422
+ }
423
+ const rr = routerLike;
424
+ const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
425
+ const routerRequestId = rr.requestId ?? meta.requestId;
426
+ if (typeof routerRequestId === 'string')
427
+ out.routerRequestId = routerRequestId;
428
+ if (typeof meta.providerRequestId === 'string')
429
+ out.providerRequestId = meta.providerRequestId;
430
+ if (typeof meta.openrouterRequestId === 'string')
431
+ out.openrouterRequestId = meta.openrouterRequestId;
432
+ const nested = meta.requestIds;
433
+ if (nested != null && typeof nested === 'object') {
434
+ for (const [k, v] of Object.entries(nested)) {
435
+ if (typeof v === 'string')
436
+ out[k] = v;
437
+ }
438
+ }
439
+ return out;
440
+ }
441
+ export function buildInvokeRejectionMetadata(args) {
442
+ const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
443
+ const partial = args.partialRouterPayload;
444
+ const mc = args.mergedConfig;
445
+ const routing = pickInvokeRoutingMetadataSlice(partial ?? {}, mc ?? {});
446
+ const effective = mc !== undefined
447
+ ? pickEffectiveModelConfigForMetadata(mc)
448
+ : pickEffectiveModelConfigFromInvokeRequest(args.request);
449
+ let tokens = partial !== undefined ? extractTokenUsageFromRouterResponse(partial) : undefined;
450
+ if (tokens && tokens.prompt === 0 && tokens.completion === 0 && tokens.total === 0) {
451
+ tokens = undefined;
452
+ }
453
+ const requestIds = pickRequestIdsFromRouterLike(gid, partial);
454
+ return {
455
+ aiRequestId: args.request.aiRequestId,
456
+ identity: args.request.identity,
457
+ taskTypeId: args.taskTypeId,
458
+ latencyMs: Date.now() - args.startTime,
459
+ ...routing,
460
+ ...(effective !== undefined ? { effectiveModelConfig: effective } : {}),
461
+ ...(tokens !== undefined ? { tokens } : {}),
462
+ ...(requestIds !== undefined ? { requestIds } : {}),
463
+ ...(mc === undefined ? { mergeConfigUnavailable: true } : {})
464
+ };
465
+ }
466
+ export function attachGatewayInvokeRejectionMetadata(err, metadata) {
467
+ err.metadata = metadata;
468
+ }
361
469
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
362
470
  export const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
363
471
  /**
package/dist/gateway.js CHANGED
@@ -8,7 +8,7 @@ import { ensureGatewayRequestIdentity } from './activity-manager.js';
8
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
- import { capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice } from './gateway-utils.js';
11
+ import { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
12
12
  import { autoRegisterProviders } from './gateway-provider-auto-register.js';
13
13
  import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
14
14
  import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
@@ -223,6 +223,13 @@ export class AIGateway {
223
223
  failureType: 'validation-failure'
224
224
  }, startTime);
225
225
  }
226
+ const rejectMeta = buildInvokeRejectionMetadata({
227
+ request,
228
+ taskTypeId,
229
+ startTime,
230
+ gatewayAiRequestId: request.aiRequestId
231
+ });
232
+ attachGatewayInvokeRejectionMetadata(err, rejectMeta);
226
233
  // Re-throw the error so it propagates to the caller
227
234
  throw err;
228
235
  }
@@ -611,8 +618,21 @@ export class AIGateway {
611
618
  }
612
619
  catch (error) {
613
620
  const err = error instanceof Error ? error : new Error(String(error));
621
+ const partial = tryExtractRouterLikePayloadFromErrorChain(err);
622
+ const rejectMeta = buildInvokeRejectionMetadata({
623
+ request,
624
+ taskTypeId,
625
+ startTime,
626
+ mergedConfig,
627
+ partialRouterPayload: partial,
628
+ gatewayAiRequestId: request.aiRequestId
629
+ });
630
+ attachGatewayInvokeRejectionMetadata(err, rejectMeta);
614
631
  if (err.message.includes(NO_PROVIDER_ERROR)) {
615
- throw new Error(err.message + NO_PROVIDER_HINT);
632
+ const wrapped = new Error(err.message + NO_PROVIDER_HINT);
633
+ wrapped.cause = err;
634
+ attachGatewayInvokeRejectionMetadata(wrapped, rejectMeta);
635
+ throw wrapped;
616
636
  }
617
637
  throw err;
618
638
  }
package/dist/index.d.ts CHANGED
@@ -16,7 +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, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
19
+ export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
20
21
  export { mergeTemplateRenderOptions } from './template-render-merge.js';
21
22
  export type { UsageTier } from './types.js';
22
23
  export { Activix } from '@x12i/activix';
package/dist/index.js CHANGED
@@ -17,6 +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 } from './gateway-utils.js';
20
21
  export { mergeTemplateRenderOptions } from './template-render-merge.js';
21
22
  // Usage tracking: UsageTracker class methods are available but consumption calculation is disabled
22
23
  // (x-models was previously used for RPM/TPM tracking but is no longer integrated)
package/dist/types.d.ts CHANGED
@@ -84,6 +84,34 @@ export type GatewayTraceAttempt = {
84
84
  */
85
85
  rawProviderPayload?: unknown;
86
86
  };
87
+ /**
88
+ * Normalized observability payload attached to thrown errors from {@link AIGateway.invoke}
89
+ * when the gateway can derive fields (merged config, partial router body on error).
90
+ * SDKs should read `(error as Error & { metadata?: GatewayInvokeRejectionMetadata }).metadata`
91
+ * (see docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md).
92
+ */
93
+ export type GatewayInvokeRejectionMetadata = {
94
+ aiRequestId?: string;
95
+ identity?: ActivityIdentity;
96
+ taskTypeId?: string;
97
+ latencyMs?: number;
98
+ tokens?: {
99
+ prompt: number;
100
+ completion: number;
101
+ total: number;
102
+ };
103
+ provider?: string;
104
+ modelUsed?: string;
105
+ maxTokensRequested?: number;
106
+ region?: string;
107
+ effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
108
+ requestIds?: GatewayTraceRequestIds;
109
+ /**
110
+ * True when {@link mergeConfig} did not run (e.g. message-building threw first).
111
+ * Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
112
+ */
113
+ mergeConfigUnavailable?: true;
114
+ };
87
115
  /**
88
116
  * Identity object used for activity linkage.
89
117
  * On gateway requests/responses it lives on `identity`. When activity tracking persists via Activix v5+,
@@ -46,6 +46,11 @@ exports.extractTokenUsageFromRouterResponse = extractTokenUsageFromRouterRespons
46
46
  exports.extractCostUsdFromRouterResponse = extractCostUsdFromRouterResponse;
47
47
  exports.pickInvokeRoutingMetadataSlice = pickInvokeRoutingMetadataSlice;
48
48
  exports.pickEffectiveModelConfigForMetadata = pickEffectiveModelConfigForMetadata;
49
+ exports.pickEffectiveModelConfigFromInvokeRequest = pickEffectiveModelConfigFromInvokeRequest;
50
+ exports.tryExtractRouterLikePayloadFromErrorChain = tryExtractRouterLikePayloadFromErrorChain;
51
+ exports.pickRequestIdsFromRouterLike = pickRequestIdsFromRouterLike;
52
+ exports.buildInvokeRejectionMetadata = buildInvokeRejectionMetadata;
53
+ exports.attachGatewayInvokeRejectionMetadata = attachGatewayInvokeRejectionMetadata;
49
54
  exports.capActivityFullResponsePayload = capActivityFullResponsePayload;
50
55
  const crypto = __importStar(require("crypto"));
51
56
  const gateway_instructions_js_1 = require("./gateway-instructions.cjs");
@@ -403,6 +408,114 @@ function pickEffectiveModelConfigForMetadata(mergedConfig) {
403
408
  }
404
409
  return Object.keys(out).length ? out : undefined;
405
410
  }
411
+ const EFFECTIVE_MODEL_CONFIG_KEYS = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
412
+ /**
413
+ * Allowlisted generation fields from request only (before mergeConfig / flex-md).
414
+ * Priority matches mergeConfig: modelConfig overrides request.config per key.
415
+ */
416
+ function pickEffectiveModelConfigFromInvokeRequest(request) {
417
+ const cfg = (request.config ?? {});
418
+ const mc = (request.modelConfig ?? {});
419
+ const out = {};
420
+ for (const k of EFFECTIVE_MODEL_CONFIG_KEYS) {
421
+ const v = mc[k] ?? cfg[k];
422
+ if (v !== undefined)
423
+ out[k] = v;
424
+ }
425
+ const modelFromId = out.model === undefined && typeof mc.modelId === 'string' ? mc.modelId : undefined;
426
+ if (modelFromId !== undefined)
427
+ out.model = modelFromId;
428
+ return Object.keys(out).length ? out : undefined;
429
+ }
430
+ function isRouterLikeEnvelope(value) {
431
+ if (value == null || typeof value !== 'object')
432
+ return false;
433
+ const r = value;
434
+ return ('metadata' in r ||
435
+ 'outputText' in r ||
436
+ 'content' in r ||
437
+ 'requestId' in r ||
438
+ 'usage' in r);
439
+ }
440
+ /**
441
+ * Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
442
+ * to find a router-shaped object for token / correlation extraction.
443
+ */
444
+ function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
445
+ const seen = new Set();
446
+ let cur = error;
447
+ for (let i = 0; i < maxDepth && cur != null; i++) {
448
+ if (typeof cur !== 'object')
449
+ break;
450
+ if (seen.has(cur))
451
+ break;
452
+ seen.add(cur);
453
+ const o = cur;
454
+ if (isRouterLikeEnvelope(cur))
455
+ return cur;
456
+ const nested = [o.response, o.routerResponse, o.lastResponse, o.body, o.data];
457
+ for (const n of nested) {
458
+ if (isRouterLikeEnvelope(n))
459
+ return n;
460
+ }
461
+ cur = o.cause;
462
+ }
463
+ return undefined;
464
+ }
465
+ function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
466
+ if (typeof gatewayAiRequestId !== 'string' || gatewayAiRequestId.length === 0) {
467
+ return undefined;
468
+ }
469
+ const out = { gatewayAiRequestId };
470
+ if (routerLike == null || typeof routerLike !== 'object') {
471
+ return out;
472
+ }
473
+ const rr = routerLike;
474
+ const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
475
+ const routerRequestId = rr.requestId ?? meta.requestId;
476
+ if (typeof routerRequestId === 'string')
477
+ out.routerRequestId = routerRequestId;
478
+ if (typeof meta.providerRequestId === 'string')
479
+ out.providerRequestId = meta.providerRequestId;
480
+ if (typeof meta.openrouterRequestId === 'string')
481
+ out.openrouterRequestId = meta.openrouterRequestId;
482
+ const nested = meta.requestIds;
483
+ if (nested != null && typeof nested === 'object') {
484
+ for (const [k, v] of Object.entries(nested)) {
485
+ if (typeof v === 'string')
486
+ out[k] = v;
487
+ }
488
+ }
489
+ return out;
490
+ }
491
+ function buildInvokeRejectionMetadata(args) {
492
+ const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
493
+ const partial = args.partialRouterPayload;
494
+ const mc = args.mergedConfig;
495
+ const routing = pickInvokeRoutingMetadataSlice(partial ?? {}, mc ?? {});
496
+ const effective = mc !== undefined
497
+ ? pickEffectiveModelConfigForMetadata(mc)
498
+ : pickEffectiveModelConfigFromInvokeRequest(args.request);
499
+ let tokens = partial !== undefined ? extractTokenUsageFromRouterResponse(partial) : undefined;
500
+ if (tokens && tokens.prompt === 0 && tokens.completion === 0 && tokens.total === 0) {
501
+ tokens = undefined;
502
+ }
503
+ const requestIds = pickRequestIdsFromRouterLike(gid, partial);
504
+ return {
505
+ aiRequestId: args.request.aiRequestId,
506
+ identity: args.request.identity,
507
+ taskTypeId: args.taskTypeId,
508
+ latencyMs: Date.now() - args.startTime,
509
+ ...routing,
510
+ ...(effective !== undefined ? { effectiveModelConfig: effective } : {}),
511
+ ...(tokens !== undefined ? { tokens } : {}),
512
+ ...(requestIds !== undefined ? { requestIds } : {}),
513
+ ...(mc === undefined ? { mergeConfigUnavailable: true } : {})
514
+ };
515
+ }
516
+ function attachGatewayInvokeRejectionMetadata(err, metadata) {
517
+ err.metadata = metadata;
518
+ }
406
519
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
407
520
  exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
408
521
  /**
@@ -2,7 +2,7 @@
2
2
  * Gateway Utilities Module
3
3
  * Handles utility functions
4
4
  */
5
- import type { ChatRequest, GatewayConfig, ModelConfig } from './types.js';
5
+ import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, ModelConfig } from './types.js';
6
6
  import type { Logxer } from '@x12i/logxer';
7
7
  /**
8
8
  * Generates MD5 hash of a string
@@ -57,6 +57,27 @@ export declare function pickInvokeRoutingMetadataSlice(routerResponse: unknown,
57
57
  * Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
58
58
  */
59
59
  export declare function pickEffectiveModelConfigForMetadata(mergedConfig: unknown): Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>> | undefined;
60
+ declare const EFFECTIVE_MODEL_CONFIG_KEYS: readonly ["model", "modelId", "provider", "temperature", "maxTokens", "topP"];
61
+ /**
62
+ * Allowlisted generation fields from request only (before mergeConfig / flex-md).
63
+ * Priority matches mergeConfig: modelConfig overrides request.config per key.
64
+ */
65
+ export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<AIInvokeRequest, 'config' | 'modelConfig'>): Partial<Pick<ModelConfig, (typeof EFFECTIVE_MODEL_CONFIG_KEYS)[number]>> | undefined;
66
+ /**
67
+ * Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
68
+ * to find a router-shaped object for token / correlation extraction.
69
+ */
70
+ export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
71
+ export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
72
+ export declare function buildInvokeRejectionMetadata(args: {
73
+ request: Pick<AIInvokeRequest, 'aiRequestId' | 'identity' | 'config' | 'modelConfig'>;
74
+ taskTypeId: string;
75
+ startTime: number;
76
+ mergedConfig?: unknown;
77
+ partialRouterPayload?: unknown;
78
+ gatewayAiRequestId?: string;
79
+ }): GatewayInvokeRejectionMetadata;
80
+ export declare function attachGatewayInvokeRejectionMetadata(err: Error, metadata: GatewayInvokeRejectionMetadata): void;
60
81
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
61
82
  export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
62
83
  /**
@@ -64,3 +85,4 @@ export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
64
85
  * Non-serializable values become a small marker object instead of throwing.
65
86
  */
66
87
  export declare function capActivityFullResponsePayload(payload: unknown, maxChars?: number): unknown;
88
+ export {};
@@ -226,6 +226,13 @@ class AIGateway {
226
226
  failureType: 'validation-failure'
227
227
  }, startTime);
228
228
  }
229
+ const rejectMeta = (0, gateway_utils_js_1.buildInvokeRejectionMetadata)({
230
+ request,
231
+ taskTypeId,
232
+ startTime,
233
+ gatewayAiRequestId: request.aiRequestId
234
+ });
235
+ (0, gateway_utils_js_1.attachGatewayInvokeRejectionMetadata)(err, rejectMeta);
229
236
  // Re-throw the error so it propagates to the caller
230
237
  throw err;
231
238
  }
@@ -614,8 +621,21 @@ class AIGateway {
614
621
  }
615
622
  catch (error) {
616
623
  const err = error instanceof Error ? error : new Error(String(error));
624
+ const partial = (0, gateway_utils_js_1.tryExtractRouterLikePayloadFromErrorChain)(err);
625
+ const rejectMeta = (0, gateway_utils_js_1.buildInvokeRejectionMetadata)({
626
+ request,
627
+ taskTypeId,
628
+ startTime,
629
+ mergedConfig,
630
+ partialRouterPayload: partial,
631
+ gatewayAiRequestId: request.aiRequestId
632
+ });
633
+ (0, gateway_utils_js_1.attachGatewayInvokeRejectionMetadata)(err, rejectMeta);
617
634
  if (err.message.includes(NO_PROVIDER_ERROR)) {
618
- throw new Error(err.message + NO_PROVIDER_HINT);
635
+ const wrapped = new Error(err.message + NO_PROVIDER_HINT);
636
+ wrapped.cause = err;
637
+ (0, gateway_utils_js_1.attachGatewayInvokeRejectionMetadata)(wrapped, rejectMeta);
638
+ throw wrapped;
619
639
  }
620
640
  throw err;
621
641
  }
@@ -21,7 +21,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
21
21
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.resetObjectTypesLibrary = exports.getObjectTypesLibrary = exports.initializeObjectTypesLibrary = exports.getObjectTypesForAgent = exports.getObjectType = exports.OBJECT_TYPES_LIBRARY = exports.assertValidAIRequest = exports.formatDiagnostic = exports.runValidationTests = exports.createValidationTestCases = exports.createTestAIRequest = exports.supportsJSONMode = exports.diagnoseResponse = exports.diagnoseRequest = exports.validateResponse = exports.extractJSON = exports.validateJSON = exports.validateAIRequest = exports.DEFAULT_RATE_LIMIT_ENABLED = exports.DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS = exports.GatewayRateLimiter = exports.runtimeObjects = exports.DebugLogAbstract = exports.createLogxer = exports.gatewayLogDebug = exports.withActivityIdentity = exports.activityIdentityToLogMeta = exports.ensureGatewayRequestIdentity = exports.ActivityManager = exports.Activix = exports.mergeTemplateRenderOptions = exports.autoRegisterProviders = exports.InstructionBackendError = exports.InstructionNotFoundError = exports.AIGateway = exports.FallbackExhaustedError = exports.ProviderNotFoundError = exports.createRouterFromConfig = exports.createRouter = exports.LLMProviderRouter = void 0;
24
+ exports.resetObjectTypesLibrary = exports.getObjectTypesLibrary = exports.initializeObjectTypesLibrary = exports.getObjectTypesForAgent = exports.getObjectType = exports.OBJECT_TYPES_LIBRARY = exports.assertValidAIRequest = exports.formatDiagnostic = exports.runValidationTests = exports.createValidationTestCases = exports.createTestAIRequest = exports.supportsJSONMode = exports.diagnoseResponse = exports.diagnoseRequest = exports.validateResponse = exports.extractJSON = exports.validateJSON = exports.validateAIRequest = exports.DEFAULT_RATE_LIMIT_ENABLED = exports.DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS = exports.GatewayRateLimiter = exports.runtimeObjects = exports.DebugLogAbstract = exports.createLogxer = exports.gatewayLogDebug = exports.withActivityIdentity = exports.activityIdentityToLogMeta = exports.ensureGatewayRequestIdentity = exports.ActivityManager = exports.Activix = exports.mergeTemplateRenderOptions = exports.pickRequestIdsFromRouterLike = exports.tryExtractRouterLikePayloadFromErrorChain = exports.buildInvokeRejectionMetadata = exports.attachGatewayInvokeRejectionMetadata = exports.autoRegisterProviders = exports.InstructionBackendError = exports.InstructionNotFoundError = exports.AIGateway = exports.FallbackExhaustedError = exports.ProviderNotFoundError = exports.createRouterFromConfig = exports.createRouter = exports.LLMProviderRouter = void 0;
25
25
  // Re-export router class and types (base functionality)
26
26
  var ai_providers_router_1 = require("@x12i/ai-providers-router");
27
27
  Object.defineProperty(exports, "LLMProviderRouter", { enumerable: true, get: function () { return ai_providers_router_1.LLMProviderRouter; } });
@@ -43,6 +43,11 @@ Object.defineProperty(exports, "InstructionNotFoundError", { enumerable: true, g
43
43
  Object.defineProperty(exports, "InstructionBackendError", { enumerable: true, get: function () { return instruction_errors_js_1.InstructionBackendError; } });
44
44
  var gateway_provider_auto_register_js_1 = require("./gateway-provider-auto-register.cjs");
45
45
  Object.defineProperty(exports, "autoRegisterProviders", { enumerable: true, get: function () { return gateway_provider_auto_register_js_1.autoRegisterProviders; } });
46
+ var gateway_utils_js_1 = require("./gateway-utils.cjs");
47
+ Object.defineProperty(exports, "attachGatewayInvokeRejectionMetadata", { enumerable: true, get: function () { return gateway_utils_js_1.attachGatewayInvokeRejectionMetadata; } });
48
+ Object.defineProperty(exports, "buildInvokeRejectionMetadata", { enumerable: true, get: function () { return gateway_utils_js_1.buildInvokeRejectionMetadata; } });
49
+ Object.defineProperty(exports, "tryExtractRouterLikePayloadFromErrorChain", { enumerable: true, get: function () { return gateway_utils_js_1.tryExtractRouterLikePayloadFromErrorChain; } });
50
+ Object.defineProperty(exports, "pickRequestIdsFromRouterLike", { enumerable: true, get: function () { return gateway_utils_js_1.pickRequestIdsFromRouterLike; } });
46
51
  var template_render_merge_js_1 = require("./template-render-merge.cjs");
47
52
  Object.defineProperty(exports, "mergeTemplateRenderOptions", { enumerable: true, get: function () { return template_render_merge_js_1.mergeTemplateRenderOptions; } });
48
53
  // Usage tracking: UsageTracker class methods are available but consumption calculation is disabled
@@ -16,7 +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, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
19
+ export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
20
21
  export { mergeTemplateRenderOptions } from './template-render-merge.js';
21
22
  export type { UsageTier } from './types.js';
22
23
  export { Activix } from '@x12i/activix';
@@ -84,6 +84,34 @@ export type GatewayTraceAttempt = {
84
84
  */
85
85
  rawProviderPayload?: unknown;
86
86
  };
87
+ /**
88
+ * Normalized observability payload attached to thrown errors from {@link AIGateway.invoke}
89
+ * when the gateway can derive fields (merged config, partial router body on error).
90
+ * SDKs should read `(error as Error & { metadata?: GatewayInvokeRejectionMetadata }).metadata`
91
+ * (see docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md).
92
+ */
93
+ export type GatewayInvokeRejectionMetadata = {
94
+ aiRequestId?: string;
95
+ identity?: ActivityIdentity;
96
+ taskTypeId?: string;
97
+ latencyMs?: number;
98
+ tokens?: {
99
+ prompt: number;
100
+ completion: number;
101
+ total: number;
102
+ };
103
+ provider?: string;
104
+ modelUsed?: string;
105
+ maxTokensRequested?: number;
106
+ region?: string;
107
+ effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
108
+ requestIds?: GatewayTraceRequestIds;
109
+ /**
110
+ * True when {@link mergeConfig} did not run (e.g. message-building threw first).
111
+ * Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
112
+ */
113
+ mergeConfigUnavailable?: true;
114
+ };
87
115
  /**
88
116
  * Identity object used for activity linkage.
89
117
  * 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.1.1",
3
+ "version": "9.1.2",
4
4
  "description": "AI Gateway - Unified interface for LLM provider routing and management",
5
5
  "type": "module",
6
6
  "exports": {