@x12i/ai-gateway 9.1.2 → 9.1.5

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
@@ -789,7 +789,9 @@ const response = await gateway.invoke({
789
789
 
790
790
  - **`GatewayConfig.templateRendering`** — default `TemplateRenderOptions` for every `invoke()` render path (merged after packaged **`src/defaults/template-rendering.json`**, which ships with **`subPathSearch.enabled: false`**). Your gateway config overrides that JSON.
791
791
  - **`templateRenderOptions` on the request** (`ChatRequest` / `AIInvokeRequest`) — merged on top of the gateway default for that call only (per-field override; `subPathSearch` fields merge with request winning).
792
- - Supported fields match the parser: **`templateId`**, **`subPathSearch`** (`enabled`, `roots`), **`silentMissingMustTokens`** (legacy Handlebars-style silence for missing MUST paths).
792
+ - **Smart Input (optional shorthand on the request)** top-level **`smartInput`** (`SmartInputConfig`) and **`smartInputRenderOptions`** (`SmartInputRenderOptions`) are merged into the same Rendrix options object **after** `GatewayConfig.templateRendering` and **before** `templateRenderOptions`. If you set the same field both as a shorthand and inside **`templateRenderOptions`**, the nested **`templateRenderOptions`** value wins. Templates use the **`{{smartInput}}`** insertion macro (see **`@x12i/rendrix`**). Types **`SmartInputConfig`** and **`SmartInputRenderOptions`** are re-exported from this package.
793
+ - For programmatic merges (tests or wrappers), use **`mergeGatewayAndRequestTemplateRenderOptions`** (same rules as `buildMessages`).
794
+ - Supported fields match the parser: **`templateId`**, **`subPathSearch`** (`enabled`, `roots`), **`silentMissingMustTokens`** (legacy Handlebars-style silence for missing MUST paths), **`smartInput`**, **`smartInputRenderOptions`**.
793
795
  - **Sub-path root priority:** `subPathSearch.roots` is an **ordered** list. The parser tries roots in **array order**; **the first root that resolves the leaf path wins** (see ISSUE-005). There is no separate “priority” field—the order of `roots` *is* the priority. Omit `roots` when `enabled` is true to use **`@x12i/rendrix`** packaged defaults.
794
796
 
795
797
  ```typescript
@@ -133,6 +133,30 @@ function logUpstreamIdentityWarnings(logger, incomingIdentity, merged) {
133
133
  }));
134
134
  }
135
135
  }
136
+ /** Routing / generation facts from gateway response metadata for Activix `outer.metadata` on completion. */
137
+ function pickActivixCompletionRoutingMetadata(response) {
138
+ if (response == null || typeof response !== 'object')
139
+ return {};
140
+ const meta = response.metadata;
141
+ if (meta == null || typeof meta !== 'object')
142
+ return {};
143
+ const m = meta;
144
+ const out = {};
145
+ if (typeof m.modelUsed === 'string')
146
+ out.modelUsed = m.modelUsed;
147
+ if (typeof m.model === 'string')
148
+ out.model = m.model;
149
+ if (typeof m.provider === 'string')
150
+ out.provider = m.provider;
151
+ if (typeof m.maxTokensRequested === 'number')
152
+ out.maxTokensRequested = m.maxTokensRequested;
153
+ if (typeof m.region === 'string')
154
+ out.region = m.region;
155
+ if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
156
+ out.effectiveModelConfig = m.effectiveModelConfig;
157
+ }
158
+ return out;
159
+ }
136
160
  function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
137
161
  const incomingIdentity = request.identity;
138
162
  const sessionId = resolveSessionIdForRequest(request, incomingIdentity, aiRequestId);
@@ -388,33 +412,37 @@ export class ActivityManager {
388
412
  // - provider, model, temperature, maxTokens → only in config object
389
413
  // - NO response, endTime, duration (these are added via logSuccess)
390
414
  };
391
- // Config snapshot
392
- // CRITICAL: This captures the exact config sent to the router (finalRouterConfig)
393
- // This ensures activity records show the actual config that was sent, including that response_format was removed
394
- if (request.config !== undefined) {
395
- // Verify response_format is not present (for debugging)
396
- const hasResponseFormat = 'responseFormat' in request.config || 'response_format' in request.config;
415
+ // Config snapshot — prefer gateway merge output (`_mergedRouterConfig`) over raw `request.config`.
416
+ // Callers often put model only on `modelConfig`; merge happens in `mergeConfig()` before `router.invoke`.
417
+ const mergedRouterConfig = request._mergedRouterConfig;
418
+ const configSource = mergedRouterConfig != null && typeof mergedRouterConfig === 'object'
419
+ ? mergedRouterConfig
420
+ : request.config;
421
+ // CRITICAL: Capture the exact config sent to the router (finalRouterConfig)
422
+ if (configSource !== undefined) {
423
+ const hasResponseFormat = 'responseFormat' in configSource || 'response_format' in configSource;
397
424
  if (hasResponseFormat) {
398
425
  this.logger.warn('Activity tracking received config with response_format - this should not happen', {
399
426
  aiRequestId,
400
- hasResponseFormat: 'responseFormat' in request.config,
401
- hasResponse_format: 'response_format' in request.config,
402
- configKeys: Object.keys(request.config)
427
+ hasResponseFormat: 'responseFormat' in configSource,
428
+ hasResponse_format: 'response_format' in configSource,
429
+ configKeys: Object.keys(configSource)
403
430
  });
404
431
  }
405
432
  activityMetadata.config = {
406
- model: request.config.model,
407
- provider: request.config.provider || null, // Ensure provider is captured (may be null if not set)
408
- temperature: request.config.temperature,
409
- maxTokens: request.config.maxTokens,
410
- rawConfig: request.config // ✅ Captures finalRouterConfig (exact config sent to router)
433
+ model: configSource.model,
434
+ provider: configSource.provider || null,
435
+ temperature: configSource.temperature,
436
+ maxTokens: configSource.maxTokens,
437
+ rawConfig: configSource
411
438
  };
412
439
  this.logger.debug('Activity tracking config captured', {
413
440
  aiRequestId,
414
- model: request.config.model,
415
- provider: request.config.provider,
416
- hasResponseFormat: hasResponseFormat,
417
- rawConfigKeys: Object.keys(request.config).slice(0, 10)
441
+ model: configSource.model,
442
+ provider: configSource.provider,
443
+ configSource: mergedRouterConfig != null ? '_mergedRouterConfig' : 'request.config',
444
+ hasResponseFormat,
445
+ rawConfigKeys: Object.keys(configSource).slice(0, 10)
418
446
  });
419
447
  }
420
448
  // Build request object snapshots (raw = incoming; parsed = constructed messages/meta)
@@ -605,14 +633,17 @@ export class ActivityManager {
605
633
  ...(aiRequest.masterSkillActivityId && { masterSkillActivityId: aiRequest.masterSkillActivityId }),
606
634
  ...(aiRequest.masterSkillId && { masterSkillId: aiRequest.masterSkillId })
607
635
  };
608
- // Config snapshot (same as startActivity)
609
- if (request.config !== undefined) {
636
+ const mergedRouterConfigSkill = request._mergedRouterConfig;
637
+ const configSourceSkill = mergedRouterConfigSkill != null && typeof mergedRouterConfigSkill === 'object'
638
+ ? mergedRouterConfigSkill
639
+ : request.config;
640
+ if (configSourceSkill !== undefined) {
610
641
  activityMetadata.config = {
611
- model: request.config.model,
612
- provider: request.config.provider || null,
613
- temperature: request.config.temperature,
614
- maxTokens: request.config.maxTokens,
615
- rawConfig: request.config
642
+ model: configSourceSkill.model,
643
+ provider: configSourceSkill.provider || null,
644
+ temperature: configSourceSkill.temperature,
645
+ maxTokens: configSourceSkill.maxTokens,
646
+ rawConfig: configSourceSkill
616
647
  };
617
648
  }
618
649
  // Build request object snapshots (same as startActivity)
@@ -816,7 +847,7 @@ export class ActivityManager {
816
847
  response: details.response,
817
848
  outer: {
818
849
  output: details.response,
819
- metadata: {}
850
+ metadata: pickActivixCompletionRoutingMetadata(details.response)
820
851
  },
821
852
  endTime: details.endTime,
822
853
  duration: details.duration
@@ -2,7 +2,7 @@
2
2
  * Gateway Utilities Module
3
3
  * Handles utility functions
4
4
  */
5
- import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, ModelConfig } from './types.js';
5
+ import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceMergedConfig, 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,10 @@ 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
+ /**
61
+ * Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
62
+ */
63
+ export declare function pickTraceMergedRouterConfig(mergedConfig: unknown): GatewayTraceMergedConfig | undefined;
60
64
  declare const EFFECTIVE_MODEL_CONFIG_KEYS: readonly ["model", "modelId", "provider", "temperature", "maxTokens", "topP"];
61
65
  /**
62
66
  * Allowlisted generation fields from request only (before mergeConfig / flex-md).
@@ -358,6 +358,40 @@ export function pickEffectiveModelConfigForMetadata(mergedConfig) {
358
358
  }
359
359
  return Object.keys(out).length ? out : undefined;
360
360
  }
361
+ const TRACE_MERGED_ROUTER_NUMERIC_KEYS = [
362
+ 'temperature',
363
+ 'maxTokens',
364
+ 'topP',
365
+ 'frequencyPenalty',
366
+ 'presencePenalty'
367
+ ];
368
+ /**
369
+ * Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
370
+ */
371
+ export function pickTraceMergedRouterConfig(mergedConfig) {
372
+ if (mergedConfig == null || typeof mergedConfig !== 'object')
373
+ return undefined;
374
+ const c = mergedConfig;
375
+ const out = {};
376
+ for (const k of ['model', 'modelId', 'provider']) {
377
+ const v = c[k];
378
+ if (typeof v === 'string' && v.length > 0)
379
+ out[k] = v;
380
+ }
381
+ for (const k of TRACE_MERGED_ROUTER_NUMERIC_KEYS) {
382
+ const v = c[k];
383
+ if (typeof v === 'number' && Number.isFinite(v))
384
+ out[k] = v;
385
+ }
386
+ const stop = c.stop;
387
+ if (Array.isArray(stop) && stop.every((x) => typeof x === 'string')) {
388
+ out.stop = stop;
389
+ }
390
+ else if (typeof stop === 'string' && stop.length > 0) {
391
+ out.stop = [stop];
392
+ }
393
+ return Object.keys(out).length ? out : undefined;
394
+ }
361
395
  const EFFECTIVE_MODEL_CONFIG_KEYS = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
362
396
  /**
363
397
  * Allowlisted generation fields from request only (before mergeConfig / flex-md).
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 { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
11
+ import { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, 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';
@@ -77,6 +77,8 @@ export class AIGateway {
77
77
  const messages = this.buildSimpleMessages(request);
78
78
  // Merge config (modelConfig > request.config > gateway defaults)
79
79
  const mergedConfig = await mergeConfig(request, this.config, this.logger);
80
+ // Activix start snapshot must match what the router receives (modelConfig-only callers omit request.config.model).
81
+ request._mergedRouterConfig = mergedConfig;
80
82
  // Lazy auto-register providers from env (OPENAI_API_KEY, etc.) so consumers don't have to call init
81
83
  if (!this._autoRegisterDone) {
82
84
  await autoRegisterProviders(this.router, this.logger);
@@ -244,6 +246,7 @@ export class AIGateway {
244
246
  request._parsedRequest = parsedSnapshot;
245
247
  // Merge config (modelConfig > request.config > gateway defaults)
246
248
  const mergedConfig = await mergeConfig(request, this.config, this.logger);
249
+ request._mergedRouterConfig = mergedConfig;
247
250
  const diagnosticsMode = request.diagnostics?.mode;
248
251
  const traceEnabled = diagnosticsMode === 'trace';
249
252
  const includeRawProviderPayload = request.diagnostics?.includeRawProviderPayload === true;
@@ -533,6 +536,7 @@ export class AIGateway {
533
536
  const routerMetaForCost = routerResponse?.metadata || {};
534
537
  const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
535
538
  const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
539
+ const traceMergedRouterSnapshot = traceEnabled ? pickTraceMergedRouterConfig(mergedConfig) : undefined;
536
540
  const enhancedResponse = {
537
541
  content: content,
538
542
  parsedContent: parsedContent,
@@ -560,7 +564,10 @@ export class AIGateway {
560
564
  requestIds: traceRequestIds,
561
565
  retryCount: traceRetryCount,
562
566
  fallbackCount: traceFallbackCount,
563
- attempts: traceAttempts
567
+ attempts: traceAttempts,
568
+ ...(traceMergedRouterSnapshot !== undefined
569
+ ? { mergedRouterConfig: traceMergedRouterSnapshot }
570
+ : {})
564
571
  }
565
572
  : {})
566
573
  }
package/dist/index.d.ts CHANGED
@@ -16,9 +16,10 @@ 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, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
19
+ export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
20
20
  export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
21
- export { mergeTemplateRenderOptions } from './template-render-merge.js';
21
+ export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
22
+ export type { GatewayTemplateRenderRequestSlice } from './template-render-merge.js';
22
23
  export type { UsageTier } from './types.js';
23
24
  export { Activix } from '@x12i/activix';
24
25
  export type { ActivixRunContext, FindByRunContextCriteria, GetJobActivitiesInput, GetJobActivitiesResult } from '@x12i/activix';
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ 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
20
  export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
21
- export { mergeTemplateRenderOptions } from './template-render-merge.js';
21
+ export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
22
22
  // Usage tracking: UsageTracker class methods are available but consumption calculation is disabled
23
23
  // (x-models was previously used for RPM/TPM tracking but is no longer integrated)
24
24
  // Re-export activity tracking primitives (Activix)
@@ -4,7 +4,7 @@
4
4
  * Handles flex-md format exclusively
5
5
  */
6
6
  import { parseTemplate } from './template-parser.js';
7
- import { mergeTemplateRenderOptions } from './template-render-merge.js';
7
+ import { mergeGatewayAndRequestTemplateRenderOptions } from './template-render-merge.js';
8
8
  import { resolveNestedInstructionsBlock } from './gateway-instructions.js';
9
9
  // Type guard
10
10
  // AIRequest is distinguished by having primaryObjectType or objectTypes
@@ -379,7 +379,7 @@ export async function buildMessages(request, config, options = {}) {
379
379
  const shortTermMemory = options.shortTermMemory;
380
380
  const experienceMemory = options.experienceMemory;
381
381
  const knowledgeMemory = options.knowledgeMemory;
382
- const templateRenderOptions = mergeTemplateRenderOptions(config.templateRendering, request.templateRenderOptions);
382
+ const templateRenderOptions = mergeGatewayAndRequestTemplateRenderOptions(config.templateRendering, request);
383
383
  if (request.instructions) {
384
384
  if (typeof request.instructions === 'string') {
385
385
  logger.info('Using instructions as template text', {
@@ -1,9 +1,20 @@
1
1
  /**
2
2
  * Merge @x12i/rendrix TemplateRenderOptions from gateway defaults and per-request overrides.
3
3
  */
4
- import type { TemplateRenderOptions } from '@x12i/rendrix';
4
+ import type { SmartInputConfig, SmartInputRenderOptions, TemplateRenderOptions } from '@x12i/rendrix';
5
+ /** Request fields that participate in gateway + Rendrix template option merging. */
6
+ export type GatewayTemplateRenderRequestSlice = {
7
+ smartInput?: SmartInputConfig;
8
+ smartInputRenderOptions?: SmartInputRenderOptions;
9
+ templateRenderOptions?: TemplateRenderOptions;
10
+ };
5
11
  /**
6
12
  * Deep-merge template render options. Request/gateway overrides win per field.
7
13
  * For `subPathSearch`, `roots` on the override replace the base list when present.
8
14
  */
9
15
  export declare function mergeTemplateRenderOptions(base?: TemplateRenderOptions, override?: TemplateRenderOptions): TemplateRenderOptions | undefined;
16
+ /**
17
+ * Gateway default `templateRendering` + optional request shorthand (`smartInput`, `smartInputRenderOptions`) + `templateRenderOptions`.
18
+ * Same merge order as {@link buildMessages}.
19
+ */
20
+ export declare function mergeGatewayAndRequestTemplateRenderOptions(gatewayTemplateRendering: TemplateRenderOptions | undefined, request: GatewayTemplateRenderRequestSlice): TemplateRenderOptions | undefined;
@@ -6,6 +6,10 @@ function hasMeaningfulOptions(o) {
6
6
  return true;
7
7
  if (o.silentMissingMustTokens !== undefined)
8
8
  return true;
9
+ if (o.smartInput !== undefined)
10
+ return true;
11
+ if (o.smartInputRenderOptions !== undefined)
12
+ return true;
9
13
  if (o.subPathSearch !== undefined) {
10
14
  const s = o.subPathSearch;
11
15
  return (s.enabled !== undefined ||
@@ -38,3 +42,18 @@ export function mergeTemplateRenderOptions(base, override) {
38
42
  }
39
43
  return hasMeaningfulOptions(merged) ? merged : undefined;
40
44
  }
45
+ /**
46
+ * Gateway default `templateRendering` + optional request shorthand (`smartInput`, `smartInputRenderOptions`) + `templateRenderOptions`.
47
+ * Same merge order as {@link buildMessages}.
48
+ */
49
+ export function mergeGatewayAndRequestTemplateRenderOptions(gatewayTemplateRendering, request) {
50
+ const requestSmartTemplateOpts = request.smartInput !== undefined || request.smartInputRenderOptions !== undefined
51
+ ? {
52
+ ...(request.smartInput !== undefined ? { smartInput: request.smartInput } : {}),
53
+ ...(request.smartInputRenderOptions !== undefined
54
+ ? { smartInputRenderOptions: request.smartInputRenderOptions }
55
+ : {})
56
+ }
57
+ : undefined;
58
+ return mergeTemplateRenderOptions(mergeTemplateRenderOptions(gatewayTemplateRendering, requestSmartTemplateOpts), request.templateRenderOptions);
59
+ }
package/dist/types.d.ts CHANGED
@@ -8,7 +8,7 @@ type AIProvider = string;
8
8
  type AIModel = string;
9
9
  export type UsageTier = string;
10
10
  import type { Activix } from '@x12i/activix';
11
- import type { TemplateRenderOptions } from '@x12i/rendrix';
11
+ import type { SmartInputConfig, SmartInputRenderOptions, TemplateRenderOptions } from '@x12i/rendrix';
12
12
  import type { Logxer } from '@x12i/logxer';
13
13
  /**
14
14
  * Diagnostics options for opt-in authoritative tracing.
@@ -84,6 +84,11 @@ export type GatewayTraceAttempt = {
84
84
  */
85
85
  rawProviderPayload?: unknown;
86
86
  };
87
+ /**
88
+ * Allowlisted merged router/generation config returned in {@link EnhancedLLMResponse.metadata}
89
+ * when `diagnostics.mode === 'trace'`. Omits arbitrary extras and secrets.
90
+ */
91
+ export type GatewayTraceMergedConfig = Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP' | 'frequencyPenalty' | 'presencePenalty' | 'stop'>>;
87
92
  /**
88
93
  * Normalized observability payload attached to thrown errors from {@link AIGateway.invoke}
89
94
  * when the gateway can derive fields (merged config, partial router body on error).
@@ -163,8 +168,8 @@ export type ActivityIdentity = {
163
168
  */
164
169
  actionRef?: string;
165
170
  };
166
- /** Re-export parser template options for gateway consumers (MUST/optional protocol, subPathSearch, etc.). */
167
- export type { TemplateRenderOptions } from '@x12i/rendrix';
171
+ /** Re-export parser template options for gateway consumers (MUST/optional protocol, subPathSearch, smartInput, etc.). */
172
+ export type { SmartInputConfig, SmartInputRenderOptions, TemplateRenderOptions } from '@x12i/rendrix';
168
173
  type LLMRequest = Parameters<LLMProviderRouter['invoke']>[0];
169
174
  type LLMResponse = Awaited<ReturnType<LLMProviderRouter['invoke']>>;
170
175
  /**
@@ -630,6 +635,17 @@ interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input' | 'reques
630
635
  * the full list or only set `enabled` and inherit `roots` from the gateway default merge.
631
636
  */
632
637
  templateRenderOptions?: TemplateRenderOptions;
638
+ /**
639
+ * Smart Input paths for Rendrix `{{smartInput}}` (optional shorthand).
640
+ * Merged into render options after gateway `templateRendering` and before `templateRenderOptions`
641
+ * (nested `templateRenderOptions.smartInput` wins when both are set).
642
+ */
643
+ smartInput?: SmartInputConfig;
644
+ /**
645
+ * Rendering controls for Smart Input markdown (optional shorthand).
646
+ * Merged the same way as `smartInput`; `templateRenderOptions.smartInputRenderOptions` wins when both are set.
647
+ */
648
+ smartInputRenderOptions?: SmartInputRenderOptions;
633
649
  /**
634
650
  * Messages array - Optional, can be used instead of instructions/prompt
635
651
  * If provided, will be appended as-is after built messages; instructions template text is still parsed for the system message when present
@@ -954,6 +970,12 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
954
970
  * Ordered, authoritative attempts across retries and fallbacks (trace mode).
955
971
  */
956
972
  attempts?: GatewayTraceAttempt[];
973
+ /**
974
+ * Merged gateway/router generation config actually used for the invocation (after
975
+ * {@link mergeConfig}: modelConfig / request.config / defaults / flex-md maxTokens).
976
+ * Only populated when diagnostics trace mode is enabled.
977
+ */
978
+ mergedRouterConfig?: GatewayTraceMergedConfig;
957
979
  /**
958
980
  * Content type classification
959
981
  * Indicates whether content is 'string', 'object', 'array', or 'null'
@@ -137,6 +137,30 @@ function logUpstreamIdentityWarnings(logger, incomingIdentity, merged) {
137
137
  }));
138
138
  }
139
139
  }
140
+ /** Routing / generation facts from gateway response metadata for Activix `outer.metadata` on completion. */
141
+ function pickActivixCompletionRoutingMetadata(response) {
142
+ if (response == null || typeof response !== 'object')
143
+ return {};
144
+ const meta = response.metadata;
145
+ if (meta == null || typeof meta !== 'object')
146
+ return {};
147
+ const m = meta;
148
+ const out = {};
149
+ if (typeof m.modelUsed === 'string')
150
+ out.modelUsed = m.modelUsed;
151
+ if (typeof m.model === 'string')
152
+ out.model = m.model;
153
+ if (typeof m.provider === 'string')
154
+ out.provider = m.provider;
155
+ if (typeof m.maxTokensRequested === 'number')
156
+ out.maxTokensRequested = m.maxTokensRequested;
157
+ if (typeof m.region === 'string')
158
+ out.region = m.region;
159
+ if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
160
+ out.effectiveModelConfig = m.effectiveModelConfig;
161
+ }
162
+ return out;
163
+ }
140
164
  function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
141
165
  const incomingIdentity = request.identity;
142
166
  const sessionId = resolveSessionIdForRequest(request, incomingIdentity, aiRequestId);
@@ -392,33 +416,37 @@ class ActivityManager {
392
416
  // - provider, model, temperature, maxTokens → only in config object
393
417
  // - NO response, endTime, duration (these are added via logSuccess)
394
418
  };
395
- // Config snapshot
396
- // CRITICAL: This captures the exact config sent to the router (finalRouterConfig)
397
- // This ensures activity records show the actual config that was sent, including that response_format was removed
398
- if (request.config !== undefined) {
399
- // Verify response_format is not present (for debugging)
400
- const hasResponseFormat = 'responseFormat' in request.config || 'response_format' in request.config;
419
+ // Config snapshot — prefer gateway merge output (`_mergedRouterConfig`) over raw `request.config`.
420
+ // Callers often put model only on `modelConfig`; merge happens in `mergeConfig()` before `router.invoke`.
421
+ const mergedRouterConfig = request._mergedRouterConfig;
422
+ const configSource = mergedRouterConfig != null && typeof mergedRouterConfig === 'object'
423
+ ? mergedRouterConfig
424
+ : request.config;
425
+ // CRITICAL: Capture the exact config sent to the router (finalRouterConfig)
426
+ if (configSource !== undefined) {
427
+ const hasResponseFormat = 'responseFormat' in configSource || 'response_format' in configSource;
401
428
  if (hasResponseFormat) {
402
429
  this.logger.warn('Activity tracking received config with response_format - this should not happen', {
403
430
  aiRequestId,
404
- hasResponseFormat: 'responseFormat' in request.config,
405
- hasResponse_format: 'response_format' in request.config,
406
- configKeys: Object.keys(request.config)
431
+ hasResponseFormat: 'responseFormat' in configSource,
432
+ hasResponse_format: 'response_format' in configSource,
433
+ configKeys: Object.keys(configSource)
407
434
  });
408
435
  }
409
436
  activityMetadata.config = {
410
- model: request.config.model,
411
- provider: request.config.provider || null, // Ensure provider is captured (may be null if not set)
412
- temperature: request.config.temperature,
413
- maxTokens: request.config.maxTokens,
414
- rawConfig: request.config // ✅ Captures finalRouterConfig (exact config sent to router)
437
+ model: configSource.model,
438
+ provider: configSource.provider || null,
439
+ temperature: configSource.temperature,
440
+ maxTokens: configSource.maxTokens,
441
+ rawConfig: configSource
415
442
  };
416
443
  this.logger.debug('Activity tracking config captured', {
417
444
  aiRequestId,
418
- model: request.config.model,
419
- provider: request.config.provider,
420
- hasResponseFormat: hasResponseFormat,
421
- rawConfigKeys: Object.keys(request.config).slice(0, 10)
445
+ model: configSource.model,
446
+ provider: configSource.provider,
447
+ configSource: mergedRouterConfig != null ? '_mergedRouterConfig' : 'request.config',
448
+ hasResponseFormat,
449
+ rawConfigKeys: Object.keys(configSource).slice(0, 10)
422
450
  });
423
451
  }
424
452
  // Build request object snapshots (raw = incoming; parsed = constructed messages/meta)
@@ -609,14 +637,17 @@ class ActivityManager {
609
637
  ...(aiRequest.masterSkillActivityId && { masterSkillActivityId: aiRequest.masterSkillActivityId }),
610
638
  ...(aiRequest.masterSkillId && { masterSkillId: aiRequest.masterSkillId })
611
639
  };
612
- // Config snapshot (same as startActivity)
613
- if (request.config !== undefined) {
640
+ const mergedRouterConfigSkill = request._mergedRouterConfig;
641
+ const configSourceSkill = mergedRouterConfigSkill != null && typeof mergedRouterConfigSkill === 'object'
642
+ ? mergedRouterConfigSkill
643
+ : request.config;
644
+ if (configSourceSkill !== undefined) {
614
645
  activityMetadata.config = {
615
- model: request.config.model,
616
- provider: request.config.provider || null,
617
- temperature: request.config.temperature,
618
- maxTokens: request.config.maxTokens,
619
- rawConfig: request.config
646
+ model: configSourceSkill.model,
647
+ provider: configSourceSkill.provider || null,
648
+ temperature: configSourceSkill.temperature,
649
+ maxTokens: configSourceSkill.maxTokens,
650
+ rawConfig: configSourceSkill
620
651
  };
621
652
  }
622
653
  // Build request object snapshots (same as startActivity)
@@ -820,7 +851,7 @@ class ActivityManager {
820
851
  response: details.response,
821
852
  outer: {
822
853
  output: details.response,
823
- metadata: {}
854
+ metadata: pickActivixCompletionRoutingMetadata(details.response)
824
855
  },
825
856
  endTime: details.endTime,
826
857
  duration: details.duration
@@ -46,6 +46,7 @@ exports.extractTokenUsageFromRouterResponse = extractTokenUsageFromRouterRespons
46
46
  exports.extractCostUsdFromRouterResponse = extractCostUsdFromRouterResponse;
47
47
  exports.pickInvokeRoutingMetadataSlice = pickInvokeRoutingMetadataSlice;
48
48
  exports.pickEffectiveModelConfigForMetadata = pickEffectiveModelConfigForMetadata;
49
+ exports.pickTraceMergedRouterConfig = pickTraceMergedRouterConfig;
49
50
  exports.pickEffectiveModelConfigFromInvokeRequest = pickEffectiveModelConfigFromInvokeRequest;
50
51
  exports.tryExtractRouterLikePayloadFromErrorChain = tryExtractRouterLikePayloadFromErrorChain;
51
52
  exports.pickRequestIdsFromRouterLike = pickRequestIdsFromRouterLike;
@@ -408,6 +409,40 @@ function pickEffectiveModelConfigForMetadata(mergedConfig) {
408
409
  }
409
410
  return Object.keys(out).length ? out : undefined;
410
411
  }
412
+ const TRACE_MERGED_ROUTER_NUMERIC_KEYS = [
413
+ 'temperature',
414
+ 'maxTokens',
415
+ 'topP',
416
+ 'frequencyPenalty',
417
+ 'presencePenalty'
418
+ ];
419
+ /**
420
+ * Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
421
+ */
422
+ function pickTraceMergedRouterConfig(mergedConfig) {
423
+ if (mergedConfig == null || typeof mergedConfig !== 'object')
424
+ return undefined;
425
+ const c = mergedConfig;
426
+ const out = {};
427
+ for (const k of ['model', 'modelId', 'provider']) {
428
+ const v = c[k];
429
+ if (typeof v === 'string' && v.length > 0)
430
+ out[k] = v;
431
+ }
432
+ for (const k of TRACE_MERGED_ROUTER_NUMERIC_KEYS) {
433
+ const v = c[k];
434
+ if (typeof v === 'number' && Number.isFinite(v))
435
+ out[k] = v;
436
+ }
437
+ const stop = c.stop;
438
+ if (Array.isArray(stop) && stop.every((x) => typeof x === 'string')) {
439
+ out.stop = stop;
440
+ }
441
+ else if (typeof stop === 'string' && stop.length > 0) {
442
+ out.stop = [stop];
443
+ }
444
+ return Object.keys(out).length ? out : undefined;
445
+ }
411
446
  const EFFECTIVE_MODEL_CONFIG_KEYS = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
412
447
  /**
413
448
  * Allowlisted generation fields from request only (before mergeConfig / flex-md).
@@ -2,7 +2,7 @@
2
2
  * Gateway Utilities Module
3
3
  * Handles utility functions
4
4
  */
5
- import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, ModelConfig } from './types.js';
5
+ import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceMergedConfig, 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,10 @@ 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
+ /**
61
+ * Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
62
+ */
63
+ export declare function pickTraceMergedRouterConfig(mergedConfig: unknown): GatewayTraceMergedConfig | undefined;
60
64
  declare const EFFECTIVE_MODEL_CONFIG_KEYS: readonly ["model", "modelId", "provider", "temperature", "maxTokens", "topP"];
61
65
  /**
62
66
  * Allowlisted generation fields from request only (before mergeConfig / flex-md).
@@ -80,6 +80,8 @@ class AIGateway {
80
80
  const messages = this.buildSimpleMessages(request);
81
81
  // Merge config (modelConfig > request.config > gateway defaults)
82
82
  const mergedConfig = await (0, gateway_utils_js_1.mergeConfig)(request, this.config, this.logger);
83
+ // Activix start snapshot must match what the router receives (modelConfig-only callers omit request.config.model).
84
+ request._mergedRouterConfig = mergedConfig;
83
85
  // Lazy auto-register providers from env (OPENAI_API_KEY, etc.) so consumers don't have to call init
84
86
  if (!this._autoRegisterDone) {
85
87
  await (0, gateway_provider_auto_register_js_1.autoRegisterProviders)(this.router, this.logger);
@@ -247,6 +249,7 @@ class AIGateway {
247
249
  request._parsedRequest = parsedSnapshot;
248
250
  // Merge config (modelConfig > request.config > gateway defaults)
249
251
  const mergedConfig = await (0, gateway_utils_js_1.mergeConfig)(request, this.config, this.logger);
252
+ request._mergedRouterConfig = mergedConfig;
250
253
  const diagnosticsMode = request.diagnostics?.mode;
251
254
  const traceEnabled = diagnosticsMode === 'trace';
252
255
  const includeRawProviderPayload = request.diagnostics?.includeRawProviderPayload === true;
@@ -536,6 +539,7 @@ class AIGateway {
536
539
  const routerMetaForCost = routerResponse?.metadata || {};
537
540
  const routingMetadataSlice = (0, gateway_utils_js_1.pickInvokeRoutingMetadataSlice)(routerResponse, mergedConfig);
538
541
  const effectiveModelConfig = (0, gateway_utils_js_1.pickEffectiveModelConfigForMetadata)(mergedConfig);
542
+ const traceMergedRouterSnapshot = traceEnabled ? (0, gateway_utils_js_1.pickTraceMergedRouterConfig)(mergedConfig) : undefined;
539
543
  const enhancedResponse = {
540
544
  content: content,
541
545
  parsedContent: parsedContent,
@@ -563,7 +567,10 @@ class AIGateway {
563
567
  requestIds: traceRequestIds,
564
568
  retryCount: traceRetryCount,
565
569
  fallbackCount: traceFallbackCount,
566
- attempts: traceAttempts
570
+ attempts: traceAttempts,
571
+ ...(traceMergedRouterSnapshot !== undefined
572
+ ? { mergedRouterConfig: traceMergedRouterSnapshot }
573
+ : {})
567
574
  }
568
575
  : {})
569
576
  }
@@ -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.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;
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.mergeGatewayAndRequestTemplateRenderOptions = 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; } });
@@ -49,6 +49,7 @@ Object.defineProperty(exports, "buildInvokeRejectionMetadata", { enumerable: tru
49
49
  Object.defineProperty(exports, "tryExtractRouterLikePayloadFromErrorChain", { enumerable: true, get: function () { return gateway_utils_js_1.tryExtractRouterLikePayloadFromErrorChain; } });
50
50
  Object.defineProperty(exports, "pickRequestIdsFromRouterLike", { enumerable: true, get: function () { return gateway_utils_js_1.pickRequestIdsFromRouterLike; } });
51
51
  var template_render_merge_js_1 = require("./template-render-merge.cjs");
52
+ Object.defineProperty(exports, "mergeGatewayAndRequestTemplateRenderOptions", { enumerable: true, get: function () { return template_render_merge_js_1.mergeGatewayAndRequestTemplateRenderOptions; } });
52
53
  Object.defineProperty(exports, "mergeTemplateRenderOptions", { enumerable: true, get: function () { return template_render_merge_js_1.mergeTemplateRenderOptions; } });
53
54
  // Usage tracking: UsageTracker class methods are available but consumption calculation is disabled
54
55
  // (x-models was previously used for RPM/TPM tracking but is no longer integrated)
@@ -16,9 +16,10 @@ 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, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
19
+ export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
20
20
  export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
21
- export { mergeTemplateRenderOptions } from './template-render-merge.js';
21
+ export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
22
+ export type { GatewayTemplateRenderRequestSlice } from './template-render-merge.js';
22
23
  export type { UsageTier } from './types.js';
23
24
  export { Activix } from '@x12i/activix';
24
25
  export type { ActivixRunContext, FindByRunContextCriteria, GetJobActivitiesInput, GetJobActivitiesResult } from '@x12i/activix';
@@ -415,7 +415,7 @@ async function buildMessages(request, config, options = {}) {
415
415
  const shortTermMemory = options.shortTermMemory;
416
416
  const experienceMemory = options.experienceMemory;
417
417
  const knowledgeMemory = options.knowledgeMemory;
418
- const templateRenderOptions = (0, template_render_merge_js_1.mergeTemplateRenderOptions)(config.templateRendering, request.templateRenderOptions);
418
+ const templateRenderOptions = (0, template_render_merge_js_1.mergeGatewayAndRequestTemplateRenderOptions)(config.templateRendering, request);
419
419
  if (request.instructions) {
420
420
  if (typeof request.instructions === 'string') {
421
421
  logger.info('Using instructions as template text', {
@@ -4,11 +4,16 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.mergeTemplateRenderOptions = mergeTemplateRenderOptions;
7
+ exports.mergeGatewayAndRequestTemplateRenderOptions = mergeGatewayAndRequestTemplateRenderOptions;
7
8
  function hasMeaningfulOptions(o) {
8
9
  if (o.templateId !== undefined)
9
10
  return true;
10
11
  if (o.silentMissingMustTokens !== undefined)
11
12
  return true;
13
+ if (o.smartInput !== undefined)
14
+ return true;
15
+ if (o.smartInputRenderOptions !== undefined)
16
+ return true;
12
17
  if (o.subPathSearch !== undefined) {
13
18
  const s = o.subPathSearch;
14
19
  return (s.enabled !== undefined ||
@@ -41,3 +46,18 @@ function mergeTemplateRenderOptions(base, override) {
41
46
  }
42
47
  return hasMeaningfulOptions(merged) ? merged : undefined;
43
48
  }
49
+ /**
50
+ * Gateway default `templateRendering` + optional request shorthand (`smartInput`, `smartInputRenderOptions`) + `templateRenderOptions`.
51
+ * Same merge order as {@link buildMessages}.
52
+ */
53
+ function mergeGatewayAndRequestTemplateRenderOptions(gatewayTemplateRendering, request) {
54
+ const requestSmartTemplateOpts = request.smartInput !== undefined || request.smartInputRenderOptions !== undefined
55
+ ? {
56
+ ...(request.smartInput !== undefined ? { smartInput: request.smartInput } : {}),
57
+ ...(request.smartInputRenderOptions !== undefined
58
+ ? { smartInputRenderOptions: request.smartInputRenderOptions }
59
+ : {})
60
+ }
61
+ : undefined;
62
+ return mergeTemplateRenderOptions(mergeTemplateRenderOptions(gatewayTemplateRendering, requestSmartTemplateOpts), request.templateRenderOptions);
63
+ }
@@ -1,9 +1,20 @@
1
1
  /**
2
2
  * Merge @x12i/rendrix TemplateRenderOptions from gateway defaults and per-request overrides.
3
3
  */
4
- import type { TemplateRenderOptions } from '@x12i/rendrix';
4
+ import type { SmartInputConfig, SmartInputRenderOptions, TemplateRenderOptions } from '@x12i/rendrix';
5
+ /** Request fields that participate in gateway + Rendrix template option merging. */
6
+ export type GatewayTemplateRenderRequestSlice = {
7
+ smartInput?: SmartInputConfig;
8
+ smartInputRenderOptions?: SmartInputRenderOptions;
9
+ templateRenderOptions?: TemplateRenderOptions;
10
+ };
5
11
  /**
6
12
  * Deep-merge template render options. Request/gateway overrides win per field.
7
13
  * For `subPathSearch`, `roots` on the override replace the base list when present.
8
14
  */
9
15
  export declare function mergeTemplateRenderOptions(base?: TemplateRenderOptions, override?: TemplateRenderOptions): TemplateRenderOptions | undefined;
16
+ /**
17
+ * Gateway default `templateRendering` + optional request shorthand (`smartInput`, `smartInputRenderOptions`) + `templateRenderOptions`.
18
+ * Same merge order as {@link buildMessages}.
19
+ */
20
+ export declare function mergeGatewayAndRequestTemplateRenderOptions(gatewayTemplateRendering: TemplateRenderOptions | undefined, request: GatewayTemplateRenderRequestSlice): TemplateRenderOptions | undefined;
@@ -8,7 +8,7 @@ type AIProvider = string;
8
8
  type AIModel = string;
9
9
  export type UsageTier = string;
10
10
  import type { Activix } from '@x12i/activix';
11
- import type { TemplateRenderOptions } from '@x12i/rendrix';
11
+ import type { SmartInputConfig, SmartInputRenderOptions, TemplateRenderOptions } from '@x12i/rendrix';
12
12
  import type { Logxer } from '@x12i/logxer';
13
13
  /**
14
14
  * Diagnostics options for opt-in authoritative tracing.
@@ -84,6 +84,11 @@ export type GatewayTraceAttempt = {
84
84
  */
85
85
  rawProviderPayload?: unknown;
86
86
  };
87
+ /**
88
+ * Allowlisted merged router/generation config returned in {@link EnhancedLLMResponse.metadata}
89
+ * when `diagnostics.mode === 'trace'`. Omits arbitrary extras and secrets.
90
+ */
91
+ export type GatewayTraceMergedConfig = Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP' | 'frequencyPenalty' | 'presencePenalty' | 'stop'>>;
87
92
  /**
88
93
  * Normalized observability payload attached to thrown errors from {@link AIGateway.invoke}
89
94
  * when the gateway can derive fields (merged config, partial router body on error).
@@ -163,8 +168,8 @@ export type ActivityIdentity = {
163
168
  */
164
169
  actionRef?: string;
165
170
  };
166
- /** Re-export parser template options for gateway consumers (MUST/optional protocol, subPathSearch, etc.). */
167
- export type { TemplateRenderOptions } from '@x12i/rendrix';
171
+ /** Re-export parser template options for gateway consumers (MUST/optional protocol, subPathSearch, smartInput, etc.). */
172
+ export type { SmartInputConfig, SmartInputRenderOptions, TemplateRenderOptions } from '@x12i/rendrix';
168
173
  type LLMRequest = Parameters<LLMProviderRouter['invoke']>[0];
169
174
  type LLMResponse = Awaited<ReturnType<LLMProviderRouter['invoke']>>;
170
175
  /**
@@ -630,6 +635,17 @@ interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input' | 'reques
630
635
  * the full list or only set `enabled` and inherit `roots` from the gateway default merge.
631
636
  */
632
637
  templateRenderOptions?: TemplateRenderOptions;
638
+ /**
639
+ * Smart Input paths for Rendrix `{{smartInput}}` (optional shorthand).
640
+ * Merged into render options after gateway `templateRendering` and before `templateRenderOptions`
641
+ * (nested `templateRenderOptions.smartInput` wins when both are set).
642
+ */
643
+ smartInput?: SmartInputConfig;
644
+ /**
645
+ * Rendering controls for Smart Input markdown (optional shorthand).
646
+ * Merged the same way as `smartInput`; `templateRenderOptions.smartInputRenderOptions` wins when both are set.
647
+ */
648
+ smartInputRenderOptions?: SmartInputRenderOptions;
633
649
  /**
634
650
  * Messages array - Optional, can be used instead of instructions/prompt
635
651
  * If provided, will be appended as-is after built messages; instructions template text is still parsed for the system message when present
@@ -954,6 +970,12 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
954
970
  * Ordered, authoritative attempts across retries and fallbacks (trace mode).
955
971
  */
956
972
  attempts?: GatewayTraceAttempt[];
973
+ /**
974
+ * Merged gateway/router generation config actually used for the invocation (after
975
+ * {@link mergeConfig}: modelConfig / request.config / defaults / flex-md maxTokens).
976
+ * Only populated when diagnostics trace mode is enabled.
977
+ */
978
+ mergedRouterConfig?: GatewayTraceMergedConfig;
957
979
  /**
958
980
  * Content type classification
959
981
  * Indicates whether content is 'string', 'object', 'array', or 'null'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x12i/ai-gateway",
3
- "version": "9.1.2",
3
+ "version": "9.1.5",
4
4
  "description": "AI Gateway - Unified interface for LLM provider routing and management",
5
5
  "type": "module",
6
6
  "exports": {
@@ -61,7 +61,7 @@
61
61
  "license": "mit",
62
62
  "dependencies": {
63
63
  "@x12i/ai-providers-router": "^4.7.7",
64
- "@x12i/rendrix": "^4.2.0",
64
+ "@x12i/rendrix": "^4.3.0",
65
65
  "@aws-sdk/s3-request-presigner": "^3.953.0",
66
66
  "@x12i/env": "^4.0.1",
67
67
  "@x12i/logxer": "^4.3.5",