@x12i/ai-gateway 9.1.0 → 9.1.1

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 } from './types.js';
5
+ import type { ChatRequest, GatewayConfig, ModelConfig } from './types.js';
6
6
  import type { Logxer } from '@x12i/logxer';
7
7
  /**
8
8
  * Generates MD5 hash of a string
@@ -43,6 +43,20 @@ export declare function extractTokenUsageFromRouterResponse(routerResponse: unkn
43
43
  * Does not compute cost from tokens — adapters must populate normalized fields or raw usage.cost-style keys.
44
44
  */
45
45
  export declare function extractCostUsdFromRouterResponse(routerResponse: unknown): number | undefined;
46
+ /**
47
+ * Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
48
+ * Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
49
+ */
50
+ export declare function pickInvokeRoutingMetadataSlice(routerResponse: unknown, mergedConfig: unknown): Partial<{
51
+ provider: string;
52
+ modelUsed: string;
53
+ maxTokensRequested: number;
54
+ region: string;
55
+ }>;
56
+ /**
57
+ * Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
58
+ */
59
+ export declare function pickEffectiveModelConfigForMetadata(mergedConfig: unknown): Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>> | undefined;
46
60
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
47
61
  export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
48
62
  /**
@@ -315,6 +315,49 @@ export function extractCostUsdFromRouterResponse(routerResponse) {
315
315
  }
316
316
  return undefined;
317
317
  }
318
+ /**
319
+ * Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
320
+ * Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
321
+ */
322
+ export function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
323
+ const rr = routerResponse != null && typeof routerResponse === 'object' ? routerResponse : {};
324
+ const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
325
+ const cfg = mergedConfig != null && typeof mergedConfig === 'object' ? mergedConfig : {};
326
+ const provider = meta.provider || rr.provider || cfg.provider;
327
+ const modelUsed = meta.modelUsed || meta.model || rr.model || cfg.model;
328
+ const maxTokensRequested = typeof meta.maxTokensRequested === 'number'
329
+ ? meta.maxTokensRequested
330
+ : typeof cfg.maxTokens === 'number'
331
+ ? cfg.maxTokens
332
+ : undefined;
333
+ const region = typeof meta.region === 'string' ? meta.region : undefined;
334
+ const out = {};
335
+ if (provider !== undefined && provider !== null)
336
+ out.provider = provider;
337
+ if (modelUsed !== undefined && modelUsed !== null)
338
+ out.modelUsed = modelUsed;
339
+ if (maxTokensRequested !== undefined)
340
+ out.maxTokensRequested = maxTokensRequested;
341
+ if (region !== undefined)
342
+ out.region = region;
343
+ return out;
344
+ }
345
+ /**
346
+ * Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
347
+ */
348
+ export function pickEffectiveModelConfigForMetadata(mergedConfig) {
349
+ if (mergedConfig == null || typeof mergedConfig !== 'object')
350
+ return undefined;
351
+ const c = mergedConfig;
352
+ const keys = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
353
+ const out = {};
354
+ for (const k of keys) {
355
+ const v = c[k];
356
+ if (v !== undefined)
357
+ out[k] = v;
358
+ }
359
+ return Object.keys(out).length ? out : undefined;
360
+ }
318
361
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
319
362
  export const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
320
363
  /**
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 } from './gateway-utils.js';
11
+ import { capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice } 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';
@@ -524,6 +524,8 @@ export class AIGateway {
524
524
  }
525
525
  const resolvedCostUsd = extractCostUsdFromRouterResponse(routerResponse);
526
526
  const routerMetaForCost = routerResponse?.metadata || {};
527
+ const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
528
+ const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
527
529
  const enhancedResponse = {
528
530
  content: content,
529
531
  parsedContent: parsedContent,
@@ -536,6 +538,8 @@ export class AIGateway {
536
538
  agentType: 'ai',
537
539
  contentType,
538
540
  parsingMethod,
541
+ ...routingMetadataSlice,
542
+ ...(effectiveModelConfig !== undefined ? { effectiveModelConfig } : {}),
539
543
  ...(typeof resolvedCostUsd === 'number'
540
544
  ? {
541
545
  costUsd: resolvedCostUsd,
@@ -545,27 +549,12 @@ export class AIGateway {
545
549
  }
546
550
  : {}),
547
551
  ...(traceEnabled
548
- ? (() => {
549
- const meta = routerResponse?.metadata || {};
550
- const provider = meta.provider || routerResponse?.provider || mergedConfig?.provider;
551
- const region = typeof meta.region === 'string' ? meta.region : undefined;
552
- const modelUsed = meta.modelUsed || meta.model || routerResponse?.model || mergedConfig?.model;
553
- const maxTokensRequested = typeof meta.maxTokensRequested === 'number'
554
- ? meta.maxTokensRequested
555
- : typeof mergedConfig?.maxTokens === 'number'
556
- ? mergedConfig.maxTokens
557
- : undefined;
558
- return {
559
- provider,
560
- region,
561
- modelUsed,
562
- maxTokensRequested,
563
- requestIds: traceRequestIds,
564
- retryCount: traceRetryCount,
565
- fallbackCount: traceFallbackCount,
566
- attempts: traceAttempts
567
- };
568
- })()
552
+ ? {
553
+ requestIds: traceRequestIds,
554
+ retryCount: traceRetryCount,
555
+ fallbackCount: traceFallbackCount,
556
+ attempts: traceAttempts
557
+ }
569
558
  : {})
570
559
  }
571
560
  };
package/dist/types.d.ts CHANGED
@@ -876,7 +876,8 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
876
876
  */
877
877
  model?: string;
878
878
  /**
879
- * Provider used (e.g., 'openai', 'anthropic')
879
+ * Provider used (e.g., 'openai', 'anthropic').
880
+ * Populated on every successful invoke when router or merged config supplies it.
880
881
  */
881
882
  provider?: string;
882
883
  /**
@@ -884,23 +885,30 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
884
885
  */
885
886
  cost?: number;
886
887
  /**
887
- * Cost in USD (preferred, stable key for trace mode).
888
+ * Cost in USD (preferred stable key when the router exposes it).
888
889
  * When both are present, costUsd should mirror cost.
889
890
  */
890
891
  costUsd?: number;
891
892
  /**
892
893
  * Final effective max token cap applied (after merges/normalization), if known.
894
+ * Populated on every successful invoke when router or merged config supplies it.
893
895
  */
894
896
  maxTokensRequested?: number;
895
897
  /**
896
898
  * Model that actually served the response (after routing/fallback), if known.
897
899
  * This is distinct from requested model.
900
+ * Populated on every successful invoke when router or merged config supplies it.
898
901
  */
899
902
  modelUsed?: string;
900
903
  /**
901
904
  * Optional region identifier when applicable (provider-specific).
902
905
  */
903
906
  region?: string;
907
+ /**
908
+ * Sanitized merged generation profile (allowlisted fields only; no secrets).
909
+ * Reflects gateway merge order: modelConfig / request.config / defaults.
910
+ */
911
+ effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
904
912
  /**
905
913
  * Stable request/correlation identifiers across gateway/router/provider layers.
906
914
  * Only populated when diagnostics trace mode is enabled.
@@ -44,6 +44,8 @@ exports.mergeConfig = mergeConfig;
44
44
  exports.normalizeRouterUsageTokens = normalizeRouterUsageTokens;
45
45
  exports.extractTokenUsageFromRouterResponse = extractTokenUsageFromRouterResponse;
46
46
  exports.extractCostUsdFromRouterResponse = extractCostUsdFromRouterResponse;
47
+ exports.pickInvokeRoutingMetadataSlice = pickInvokeRoutingMetadataSlice;
48
+ exports.pickEffectiveModelConfigForMetadata = pickEffectiveModelConfigForMetadata;
47
49
  exports.capActivityFullResponsePayload = capActivityFullResponsePayload;
48
50
  const crypto = __importStar(require("crypto"));
49
51
  const gateway_instructions_js_1 = require("./gateway-instructions.cjs");
@@ -358,6 +360,49 @@ function extractCostUsdFromRouterResponse(routerResponse) {
358
360
  }
359
361
  return undefined;
360
362
  }
363
+ /**
364
+ * Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
365
+ * Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
366
+ */
367
+ function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
368
+ const rr = routerResponse != null && typeof routerResponse === 'object' ? routerResponse : {};
369
+ const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
370
+ const cfg = mergedConfig != null && typeof mergedConfig === 'object' ? mergedConfig : {};
371
+ const provider = meta.provider || rr.provider || cfg.provider;
372
+ const modelUsed = meta.modelUsed || meta.model || rr.model || cfg.model;
373
+ const maxTokensRequested = typeof meta.maxTokensRequested === 'number'
374
+ ? meta.maxTokensRequested
375
+ : typeof cfg.maxTokens === 'number'
376
+ ? cfg.maxTokens
377
+ : undefined;
378
+ const region = typeof meta.region === 'string' ? meta.region : undefined;
379
+ const out = {};
380
+ if (provider !== undefined && provider !== null)
381
+ out.provider = provider;
382
+ if (modelUsed !== undefined && modelUsed !== null)
383
+ out.modelUsed = modelUsed;
384
+ if (maxTokensRequested !== undefined)
385
+ out.maxTokensRequested = maxTokensRequested;
386
+ if (region !== undefined)
387
+ out.region = region;
388
+ return out;
389
+ }
390
+ /**
391
+ * Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
392
+ */
393
+ function pickEffectiveModelConfigForMetadata(mergedConfig) {
394
+ if (mergedConfig == null || typeof mergedConfig !== 'object')
395
+ return undefined;
396
+ const c = mergedConfig;
397
+ const keys = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
398
+ const out = {};
399
+ for (const k of keys) {
400
+ const v = c[k];
401
+ if (v !== undefined)
402
+ out[k] = v;
403
+ }
404
+ return Object.keys(out).length ? out : undefined;
405
+ }
361
406
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
362
407
  exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
363
408
  /**
@@ -2,7 +2,7 @@
2
2
  * Gateway Utilities Module
3
3
  * Handles utility functions
4
4
  */
5
- import type { ChatRequest, GatewayConfig } from './types.js';
5
+ import type { ChatRequest, GatewayConfig, ModelConfig } from './types.js';
6
6
  import type { Logxer } from '@x12i/logxer';
7
7
  /**
8
8
  * Generates MD5 hash of a string
@@ -43,6 +43,20 @@ export declare function extractTokenUsageFromRouterResponse(routerResponse: unkn
43
43
  * Does not compute cost from tokens — adapters must populate normalized fields or raw usage.cost-style keys.
44
44
  */
45
45
  export declare function extractCostUsdFromRouterResponse(routerResponse: unknown): number | undefined;
46
+ /**
47
+ * Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
48
+ * Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
49
+ */
50
+ export declare function pickInvokeRoutingMetadataSlice(routerResponse: unknown, mergedConfig: unknown): Partial<{
51
+ provider: string;
52
+ modelUsed: string;
53
+ maxTokensRequested: number;
54
+ region: string;
55
+ }>;
56
+ /**
57
+ * Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
58
+ */
59
+ export declare function pickEffectiveModelConfigForMetadata(mergedConfig: unknown): Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>> | undefined;
46
60
  /** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
47
61
  export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
48
62
  /**
@@ -527,6 +527,8 @@ class AIGateway {
527
527
  }
528
528
  const resolvedCostUsd = (0, gateway_utils_js_1.extractCostUsdFromRouterResponse)(routerResponse);
529
529
  const routerMetaForCost = routerResponse?.metadata || {};
530
+ const routingMetadataSlice = (0, gateway_utils_js_1.pickInvokeRoutingMetadataSlice)(routerResponse, mergedConfig);
531
+ const effectiveModelConfig = (0, gateway_utils_js_1.pickEffectiveModelConfigForMetadata)(mergedConfig);
530
532
  const enhancedResponse = {
531
533
  content: content,
532
534
  parsedContent: parsedContent,
@@ -539,6 +541,8 @@ class AIGateway {
539
541
  agentType: 'ai',
540
542
  contentType,
541
543
  parsingMethod,
544
+ ...routingMetadataSlice,
545
+ ...(effectiveModelConfig !== undefined ? { effectiveModelConfig } : {}),
542
546
  ...(typeof resolvedCostUsd === 'number'
543
547
  ? {
544
548
  costUsd: resolvedCostUsd,
@@ -548,27 +552,12 @@ class AIGateway {
548
552
  }
549
553
  : {}),
550
554
  ...(traceEnabled
551
- ? (() => {
552
- const meta = routerResponse?.metadata || {};
553
- const provider = meta.provider || routerResponse?.provider || mergedConfig?.provider;
554
- const region = typeof meta.region === 'string' ? meta.region : undefined;
555
- const modelUsed = meta.modelUsed || meta.model || routerResponse?.model || mergedConfig?.model;
556
- const maxTokensRequested = typeof meta.maxTokensRequested === 'number'
557
- ? meta.maxTokensRequested
558
- : typeof mergedConfig?.maxTokens === 'number'
559
- ? mergedConfig.maxTokens
560
- : undefined;
561
- return {
562
- provider,
563
- region,
564
- modelUsed,
565
- maxTokensRequested,
566
- requestIds: traceRequestIds,
567
- retryCount: traceRetryCount,
568
- fallbackCount: traceFallbackCount,
569
- attempts: traceAttempts
570
- };
571
- })()
555
+ ? {
556
+ requestIds: traceRequestIds,
557
+ retryCount: traceRetryCount,
558
+ fallbackCount: traceFallbackCount,
559
+ attempts: traceAttempts
560
+ }
572
561
  : {})
573
562
  }
574
563
  };
@@ -876,7 +876,8 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
876
876
  */
877
877
  model?: string;
878
878
  /**
879
- * Provider used (e.g., 'openai', 'anthropic')
879
+ * Provider used (e.g., 'openai', 'anthropic').
880
+ * Populated on every successful invoke when router or merged config supplies it.
880
881
  */
881
882
  provider?: string;
882
883
  /**
@@ -884,23 +885,30 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
884
885
  */
885
886
  cost?: number;
886
887
  /**
887
- * Cost in USD (preferred, stable key for trace mode).
888
+ * Cost in USD (preferred stable key when the router exposes it).
888
889
  * When both are present, costUsd should mirror cost.
889
890
  */
890
891
  costUsd?: number;
891
892
  /**
892
893
  * Final effective max token cap applied (after merges/normalization), if known.
894
+ * Populated on every successful invoke when router or merged config supplies it.
893
895
  */
894
896
  maxTokensRequested?: number;
895
897
  /**
896
898
  * Model that actually served the response (after routing/fallback), if known.
897
899
  * This is distinct from requested model.
900
+ * Populated on every successful invoke when router or merged config supplies it.
898
901
  */
899
902
  modelUsed?: string;
900
903
  /**
901
904
  * Optional region identifier when applicable (provider-specific).
902
905
  */
903
906
  region?: string;
907
+ /**
908
+ * Sanitized merged generation profile (allowlisted fields only; no secrets).
909
+ * Reflects gateway merge order: modelConfig / request.config / defaults.
910
+ */
911
+ effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
904
912
  /**
905
913
  * Stable request/correlation identifiers across gateway/router/provider layers.
906
914
  * Only populated when diagnostics trace mode is enabled.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x12i/ai-gateway",
3
- "version": "9.1.0",
3
+ "version": "9.1.1",
4
4
  "description": "AI Gateway - Unified interface for LLM provider routing and management",
5
5
  "type": "module",
6
6
  "exports": {