@x12i/ai-gateway 10.0.2 → 10.0.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.
@@ -4,11 +4,10 @@
4
4
  */
5
5
  import * as crypto from 'crypto';
6
6
  import { FallbackExhaustedError } from '@x12i/ai-providers-router';
7
- import { ModelResolutionError, isKnownProfileChoice } from '@x12i/ai-tools';
7
+ import { ModelResolutionError, ModelProfileInputRejectedError, ModelProfileUnroutableError, resolveInvokeModel, } from '@x12i/ai-tools';
8
8
  import { extractHttpStatusCode } from './gateway-retry.js';
9
9
  import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
10
10
  import { MaxTokensRequiredError, ModelRequiredError } from './instruction-errors.js';
11
- import { applyModelResolution, buildModelResolverOptions } from './ai-tools-client.js';
12
11
  import { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P } from './gateway-defaults.js';
13
12
  function getPreParsedInstructions(instructions) {
14
13
  return instructions ?? '';
@@ -37,6 +36,7 @@ export async function ensureTaskTypeId(request, logger) {
37
36
  });
38
37
  return taskTypeId;
39
38
  }
39
+ export { MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, } from '@x12i/ai-tools';
40
40
  /**
41
41
  * Merges config with defaults
42
42
  * Supports using internal system action defaults (internalSkill or skillAudit) when useInternalDefaults is set
@@ -100,45 +100,64 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
100
100
  }
101
101
  if (resolveModels && mergeOptions?.catalog) {
102
102
  try {
103
- const resolverOptions = buildModelResolverOptions(config, mergeOptions?.routingEnv);
104
- const resolution = await mergeOptions.catalog.resolveModel({
105
- provider: merged.provider,
106
- model: explicitModel,
107
- }, resolverOptions);
108
- if (resolution.found) {
109
- applyModelResolution(merged, resolution, config.defaultEngine, explicitModel);
110
- request._modelResolution = {
111
- modelId: resolution.modelId,
112
- routedViaOpenRouter: resolution.routedViaOpenRouter,
113
- confidence: resolution.confidence,
114
- resolvedVia: resolution.resolvedVia,
115
- originalProvider,
116
- originalModel
117
- };
118
- logger.verbose('Catalog resolved model name', {
119
- jobId: request.identity.jobId,
120
- originalModel,
121
- resolvedModelId: resolution.modelId,
122
- provider: merged.provider,
123
- model: merged.model,
124
- confidence: resolution.confidence,
125
- resolvedVia: resolution.resolvedVia
126
- });
103
+ const resolved = await resolveInvokeModel({ provider: merged.provider, model: explicitModel }, {
104
+ catalog: mergeOptions.catalog,
105
+ routingEnv: mergeOptions.routingEnv,
106
+ openRouterApiKey: mergeOptions.openRouterApiKey,
107
+ preferOpenRouter: mergeOptions.preferOpenRouter,
108
+ defaultProvider: config.defaultEngine,
109
+ resolveModels: true,
110
+ modelsOnly: config.aiTools?.modelsOnly !== false,
111
+ ...(config.aiTools?.catalogLane ? { catalogLane: config.aiTools.catalogLane } : {}),
112
+ ...(config.aiTools?.bundledOnly ? { bundledOnly: true } : {}),
113
+ });
114
+ merged.provider = resolved.router.provider;
115
+ merged.model = resolved.router.model;
116
+ if (resolved.router.allowOpenRouterProxy !== undefined) {
117
+ merged.allowOpenRouterProxy = resolved.router.allowOpenRouterProxy;
127
118
  }
128
- else {
129
- throw buildModelResolutionFailureError(explicitModel, merged.provider, resolution);
119
+ if (resolved.router.providerProxy !== undefined) {
120
+ merged.providerProxy = resolved.router.providerProxy;
130
121
  }
122
+ request._modelResolution = resolved.diagnostics;
123
+ logger.verbose('Catalog resolved model name', {
124
+ jobId: request.identity.jobId,
125
+ originalModel,
126
+ originalProvider,
127
+ resolvedModelId: resolved.diagnostics.modelId,
128
+ provider: merged.provider,
129
+ model: merged.model,
130
+ confidence: resolved.diagnostics.confidence,
131
+ resolvedVia: resolved.diagnostics.resolvedVia,
132
+ routedViaOpenRouter: resolved.diagnostics.routedViaOpenRouter,
133
+ });
131
134
  }
132
135
  catch (error) {
133
- if (error instanceof ModelResolutionError) {
134
- throw error;
135
- }
136
- if (error instanceof ModelProfileUnroutableError) {
136
+ if (error instanceof ModelResolutionError ||
137
+ error instanceof ModelProfileUnroutableError ||
138
+ error instanceof ModelProfileInputRejectedError) {
137
139
  throw error;
138
140
  }
139
141
  throw error;
140
142
  }
141
143
  }
144
+ else if (mergeOptions?.openRouterApiKey) {
145
+ const resolved = await resolveInvokeModel({ provider: merged.provider, model: explicitModel }, {
146
+ resolveModels: false,
147
+ routingEnv: mergeOptions.routingEnv,
148
+ openRouterApiKey: mergeOptions.openRouterApiKey,
149
+ preferOpenRouter: mergeOptions.preferOpenRouter,
150
+ defaultProvider: config.defaultEngine,
151
+ });
152
+ merged.provider = resolved.router.provider;
153
+ merged.model = resolved.router.model;
154
+ if (resolved.router.allowOpenRouterProxy !== undefined) {
155
+ merged.allowOpenRouterProxy = resolved.router.allowOpenRouterProxy;
156
+ }
157
+ if (resolved.router.providerProxy !== undefined) {
158
+ merged.providerProxy = resolved.router.providerProxy;
159
+ }
160
+ }
142
161
  if (!merged.model) {
143
162
  throw new ModelRequiredError();
144
163
  }
@@ -351,42 +370,119 @@ export function resolveCostCompletionForActivity(routerResponse, tokens) {
351
370
  }
352
371
  return resolveActivityCostCompletion(tokens, costUsd);
353
372
  }
354
- /** Record shape for {@link CostCalculator.calculateFromRecord} (router + merged config + usage). */
373
+ /**
374
+ * Best-effort cache/reasoning token counts from router usage buckets
375
+ * (for {@link buildGatewayPricingRecord} / ai-tools {@link CostCalculator.calculateFromRecord}).
376
+ */
377
+ export function extractUsageExtrasFromRouterResponse(routerResponse) {
378
+ if (routerResponse == null || typeof routerResponse !== 'object')
379
+ return {};
380
+ const r = routerResponse;
381
+ const roots = [r.usage];
382
+ const meta = r.metadata != null && typeof r.metadata === 'object'
383
+ ? r.metadata
384
+ : undefined;
385
+ if (meta) {
386
+ roots.push(meta.usage, meta.tokens);
387
+ }
388
+ const raw = r.rawResponse ?? r.raw;
389
+ if (raw != null && typeof raw === 'object') {
390
+ roots.push(raw.usage);
391
+ }
392
+ const extras = {};
393
+ for (const bucket of roots) {
394
+ if (bucket == null || typeof bucket !== 'object')
395
+ continue;
396
+ const u = bucket;
397
+ const cached = firstFiniteNumber(u.cached, u.cached_tokens, u.cachedTokens, u.cache_read_tokens, u.cacheReadTokens);
398
+ const cacheWrite = firstFiniteNumber(u.cacheWrite, u.cache_write_tokens, u.cacheWriteTokens);
399
+ const reasoning = firstFiniteNumber(u.reasoning, u.reasoning_tokens, u.reasoningTokens);
400
+ if (cached !== undefined && extras.cached === undefined)
401
+ extras.cached = cached;
402
+ if (cacheWrite !== undefined && extras.cacheWrite === undefined)
403
+ extras.cacheWrite = cacheWrite;
404
+ if (reasoning !== undefined && extras.reasoning === undefined)
405
+ extras.reasoning = reasoning;
406
+ }
407
+ return extras;
408
+ }
409
+ /**
410
+ * Whether ai-tools catalog pricing is authoritative enough for Step B (`priced`).
411
+ * Matches the generic engine contract: authoritative catalog hit with finite cost ≥ 0.
412
+ */
413
+ export function catalogPricingSucceeded(result) {
414
+ if (result.unknownModel)
415
+ return false;
416
+ if (!result.isAuthoritative)
417
+ return false;
418
+ if (result.source === 'estimate-fallback' || result.source === 'local')
419
+ return false;
420
+ if (typeof result.cost !== 'number' || !Number.isFinite(result.cost) || result.cost < 0) {
421
+ return false;
422
+ }
423
+ return true;
424
+ }
425
+ /** Record shape for {@link CostCalculator.calculateFromRecord} (shared engine contract). */
355
426
  export function buildGatewayPricingRecord(routerResponse, tokens, mergedConfig) {
356
- const base = routerResponse != null && typeof routerResponse === 'object'
357
- ? { ...routerResponse }
358
- : {};
359
- const meta = base.metadata != null && typeof base.metadata === 'object'
360
- ? { ...base.metadata }
361
- : {};
362
427
  const routing = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
428
+ const cfg = mergedConfig != null && typeof mergedConfig === 'object'
429
+ ? mergedConfig
430
+ : {};
431
+ const requestModel = typeof cfg.model === 'string'
432
+ ? cfg.model
433
+ : typeof routing.modelUsed === 'string'
434
+ ? routing.modelUsed
435
+ : undefined;
436
+ const modelUsed = routing.modelUsed ?? requestModel;
437
+ const provider = routing.provider ??
438
+ (typeof cfg.provider === 'string' ? cfg.provider : undefined) ??
439
+ 'openrouter';
440
+ const usageExtras = extractUsageExtrasFromRouterResponse(routerResponse);
441
+ const tokenSlice = {
442
+ prompt: tokens.prompt,
443
+ completion: tokens.completion,
444
+ total: tokens.total,
445
+ ...usageExtras
446
+ };
363
447
  return {
364
- ...base,
448
+ model: modelUsed ?? requestModel ?? '',
449
+ ...(requestModel && modelUsed && requestModel !== modelUsed
450
+ ? { modelAlias: requestModel }
451
+ : {}),
452
+ ...(modelUsed ? { modelUsed, usedModel: modelUsed } : {}),
453
+ provider,
454
+ ...(provider || routing.region
455
+ ? {
456
+ routing: {
457
+ provider,
458
+ ...(routing.region ? { region: routing.region } : {})
459
+ }
460
+ }
461
+ : {}),
365
462
  usage: {
366
- promptTokens: tokens.prompt,
367
- completionTokens: tokens.completion,
368
- totalTokens: tokens.total
463
+ prompt_tokens: tokens.prompt,
464
+ completion_tokens: tokens.completion,
465
+ total_tokens: tokens.total,
466
+ ...(usageExtras.cached !== undefined ? { cachedTokensPrompt: usageExtras.cached } : {}),
467
+ ...(usageExtras.cached !== undefined ? { cachedTokensTotal: usageExtras.cached } : {})
369
468
  },
370
- tokens,
469
+ promptTokens: tokens.prompt,
470
+ completionTokens: tokens.completion,
471
+ totalTokens: tokens.total,
472
+ tokens: tokenSlice,
371
473
  metadata: {
372
- ...meta,
373
- tokens,
374
- ...(routing.provider ? { provider: routing.provider } : {}),
375
- ...(routing.modelUsed
376
- ? { modelUsed: routing.modelUsed, model: routing.modelUsed }
377
- : {})
474
+ provider,
475
+ ...(modelUsed ? { modelUsed, model: modelUsed } : {}),
476
+ ...(routing.maxTokensRequested !== undefined
477
+ ? { maxTokensRequested: routing.maxTokensRequested }
478
+ : {}),
479
+ tokens: tokenSlice
378
480
  },
379
481
  ...(mergedConfig != null ? { config: mergedConfig } : {})
380
482
  };
381
483
  }
382
484
  export function mapAiCostResultToResolvedActivityCost(base, result) {
383
- if (result.unknownModel) {
384
- return base.costStatus ? base : { ...base, costStatus: 'unpriced' };
385
- }
386
- if (typeof result.cost !== 'number' || !Number.isFinite(result.cost)) {
387
- return base;
388
- }
389
- if (!result.isAuthoritative && result.source === 'estimate-fallback') {
485
+ if (!catalogPricingSucceeded(result)) {
390
486
  return base.costStatus ? base : { ...base, costStatus: 'unpriced' };
391
487
  }
392
488
  return {
@@ -395,6 +491,16 @@ export function mapAiCostResultToResolvedActivityCost(base, result) {
395
491
  ...(result.breakdown ? { costBreakdown: result.breakdown } : {})
396
492
  };
397
493
  }
494
+ /**
495
+ * G8 safety net: token usage without a billing signal → `unpriced`.
496
+ * Used at invoke boundaries after {@link resolveCostCompletionWithAiTools}.
497
+ */
498
+ export function ensureInvokeBillingCostStatus(billing, tokens) {
499
+ if (!billing.costStatus && hasNonZeroTokenUsage(tokens)) {
500
+ return { ...billing, costStatus: 'unpriced' };
501
+ }
502
+ return billing;
503
+ }
398
504
  /**
399
505
  * Router cost passthrough, then optional @x12i/ai-tools catalog pricing when still unpriced.
400
506
  */
@@ -419,30 +525,7 @@ export async function resolveCostCompletionWithAiTools(routerResponse, tokens, o
419
525
  return mapAiCostResultToResolvedActivityCost(base, result);
420
526
  }
421
527
  catch {
422
- const routing = pickInvokeRoutingMetadataSlice(routerResponse, options.mergedConfig);
423
- const cfg = options.mergedConfig != null && typeof options.mergedConfig === 'object'
424
- ? options.mergedConfig
425
- : {};
426
- const provider = routing.provider ?? cfg.provider;
427
- const modelUsed = routing.modelUsed ?? cfg.model;
428
- if (!provider || !modelUsed) {
429
- return base;
430
- }
431
- try {
432
- const result = await options.calculator.calculate({
433
- tokens: {
434
- prompt: tokens.prompt,
435
- completion: tokens.completion,
436
- total: tokens.total
437
- },
438
- provider,
439
- usedModel: modelUsed
440
- });
441
- return mapAiCostResultToResolvedActivityCost(base, result);
442
- }
443
- catch {
444
- return base;
445
- }
528
+ return ensureInvokeBillingCostStatus(base, tokens);
446
529
  }
447
530
  }
448
531
  function applyBillingToTraceAttempt(attempt, billing) {
@@ -679,31 +762,6 @@ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
679
762
  }
680
763
  return out;
681
764
  }
682
- /** Error code hint when a bundled profile name cannot be routed to a catalog target. */
683
- export const MODEL_PROFILE_UNROUTABLE = 'MODEL_PROFILE_UNROUTABLE';
684
- export class ModelProfileUnroutableError extends Error {
685
- profileAlias;
686
- provider;
687
- code = MODEL_PROFILE_UNROUTABLE;
688
- constructor(profileAlias, provider, cause) {
689
- super(`${MODEL_PROFILE_UNROUTABLE}: profile "${profileAlias}" is retired or has no routable catalog target` +
690
- (provider ? ` (provider: "${provider}")` : '') +
691
- '. Update @x12i/ai-profiles or choose another profile alias.');
692
- this.profileAlias = profileAlias;
693
- this.provider = provider;
694
- this.name = 'ModelProfileUnroutableError';
695
- if (cause !== undefined) {
696
- this.cause = cause;
697
- }
698
- }
699
- }
700
- function buildModelResolutionFailureError(explicitModel, provider, resolution) {
701
- const base = new ModelResolutionError({ provider, model: explicitModel }, resolution);
702
- if (isKnownProfileChoice(explicitModel)) {
703
- return new ModelProfileUnroutableError(explicitModel, provider, base);
704
- }
705
- return base;
706
- }
707
765
  /**
708
766
  * Build rejection-metadata fallback attempts from trace-mode {@link GatewayTraceAttempt}s.
709
767
  */
@@ -772,7 +830,7 @@ export function mapGatewayFallbackAttemptsToRouter(attempts) {
772
830
  }));
773
831
  }
774
832
  /**
775
- * Log profile alias vs OpenRouter model id actually sent to the router after catalog resolution.
833
+ * Log original input vs OpenRouter model id actually sent to the router after catalog resolution.
776
834
  */
777
835
  export function logResolvedModelRouting(logger, request, mergedConfig) {
778
836
  const res = request._modelResolution;
@@ -780,14 +838,14 @@ export function logResolvedModelRouting(logger, request, mergedConfig) {
780
838
  return;
781
839
  }
782
840
  const profileAlias = res.originalModel ?? mergedConfig?.model;
783
- const invokedModelId = res.modelId ?? mergedConfig?.model;
841
+ const invokedModelId = res.invokedModelId ?? res.modelId ?? mergedConfig?.model;
784
842
  const provider = mergedConfig?.provider;
785
843
  const openRouterPath = res.routedViaOpenRouter === true || provider === 'openrouter';
786
844
  if (!openRouterPath) {
787
845
  return;
788
846
  }
789
- logger.info('OpenRouter routing: profile alias resolved to model id for invoke', withActivityIdentity(request.identity, {
790
- profileAlias,
847
+ logger.info('OpenRouter routing: input model resolved to catalog id for invoke', withActivityIdentity(request.identity, {
848
+ inputModel: profileAlias,
791
849
  invokedOpenRouterModelId: invokedModelId,
792
850
  provider,
793
851
  routedViaOpenRouter: res.routedViaOpenRouter,
package/dist/gateway.js CHANGED
@@ -11,10 +11,9 @@ import { resolveRetryConfig } from './gateway-defaults.js';
11
11
  import { buildMessages } from './message-builder.js';
12
12
  import { extractJsonFromFlexMd } from './flex-md-loader.js';
13
13
  import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
14
- import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter, hasNonZeroTokenUsage, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
14
+ import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, ensureInvokeBillingCostStatus, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
15
15
  import { getAiToolsClient } from './ai-tools-client.js';
16
16
  import { autoRegisterProviders } from './gateway-provider-auto-register.js';
17
- import { applyOpenRouterInvokePolicy } from './ai-tools-client.js';
18
17
  import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
19
18
  import { gatewayLogDebug, withActivityIdentity, withGatewayLogContext } from './gateway-log-meta.js';
20
19
  import { exceptionEvidence, fieldEvidence, GatewayLogCode, gatewayErrorCode, gatewayWarnCode } from './gateway-log-diagnostics.js';
@@ -100,15 +99,11 @@ export class AIGateway {
100
99
  const mergedConfig = await mergeConfig(request, this.config, this.logger, {
101
100
  catalog: aiTools?.catalog ?? null,
102
101
  routingEnv: aiTools?.routingEnv,
102
+ openRouterApiKey: this.openRouterApiKey,
103
+ preferOpenRouter: this.preferOpenRouter,
103
104
  });
104
105
  // Activix start snapshot must match what the router receives (modelConfig-only callers omit request.config.model).
105
106
  request._mergedRouterConfig = mergedConfig;
106
- applyOpenRouterInvokePolicy(mergedConfig, {
107
- preferOpenRouter: this.preferOpenRouter,
108
- openRouterApiKey: this.openRouterApiKey,
109
- routingEnv: aiTools?.routingEnv,
110
- resolution: request._modelResolution,
111
- });
112
107
  // Lazy auto-register providers from env (OPENAI_API_KEY, etc.) so consumers don't have to call init
113
108
  if (!this._autoRegisterDone) {
114
109
  await autoRegisterProviders(this.router, this.logger);
@@ -140,11 +135,12 @@ export class AIGateway {
140
135
  });
141
136
  const metaChat = response?.metadata || {};
142
137
  const tokensChat = extractTokenUsageFromRouterResponse(response);
143
- const costCompletionChat = await resolveCostCompletionWithAiTools(response, tokensChat, {
138
+ let costCompletionChat = await resolveCostCompletionWithAiTools(response, tokensChat, {
144
139
  mergedConfig,
145
140
  calculator: aiTools?.calculator ?? null,
146
141
  calculateCost: this.config.aiTools?.calculateCost
147
142
  });
143
+ costCompletionChat = ensureInvokeBillingCostStatus(costCompletionChat, tokensChat);
148
144
  // Create enhanced response
149
145
  const enhancedResponse = {
150
146
  content: response.content || '',
@@ -292,14 +288,10 @@ export class AIGateway {
292
288
  const mergedConfig = await mergeConfig(request, this.config, this.logger, {
293
289
  catalog: aiTools?.catalog ?? null,
294
290
  routingEnv: aiTools?.routingEnv,
295
- });
296
- request._mergedRouterConfig = mergedConfig;
297
- applyOpenRouterInvokePolicy(mergedConfig, {
298
- preferOpenRouter: this.preferOpenRouter,
299
291
  openRouterApiKey: this.openRouterApiKey,
300
- routingEnv: aiTools?.routingEnv,
301
- resolution: request._modelResolution,
292
+ preferOpenRouter: this.preferOpenRouter,
302
293
  });
294
+ request._mergedRouterConfig = mergedConfig;
303
295
  logResolvedModelRouting(this.logger, request, mergedConfig);
304
296
  const diagnosticsMode = request.diagnostics?.mode;
305
297
  const traceEnabled = diagnosticsMode === 'trace';
@@ -623,9 +615,7 @@ export class AIGateway {
623
615
  calculator: aiTools?.calculator ?? null,
624
616
  calculateCost: this.config.aiTools?.calculateCost
625
617
  });
626
- if (!costCompletion.costStatus && hasNonZeroTokenUsage(tokens)) {
627
- costCompletion = { ...costCompletion, costStatus: 'unpriced' };
628
- }
618
+ costCompletion = ensureInvokeBillingCostStatus(costCompletion, tokens);
629
619
  const routerMetaForCost = routerResponse?.metadata || {};
630
620
  const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
631
621
  const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export { AIGateway } from './gateway.js';
17
17
  export { InstructionNotFoundError, InstructionBackendError, ModelRequiredError, MaxTokensRequiredError } from './instruction-errors.js';
18
18
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
19
19
  export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayFallbackAttempt, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
20
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, ensureInvokeBillingCostStatus, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
21
21
  export { getGatewayOperationalMode, isProdGatewayMode, parseModelProviderSpec } from './gateway-mode.js';
22
22
  export type { GatewayOperationalMode } from './gateway-mode.js';
23
23
  export { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_RETRY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P, resolveRetryConfig } from './gateway-defaults.js';
@@ -40,6 +40,9 @@ export type { GatewayLoggerConfig } from './logger-factory.js';
40
40
  export { GATEWAY_LOG_ENV_PREFIX, GATEWAY_LOGXER_PACKAGE, GATEWAY_STACK_LOG_PREFIXES, initializeGatewayPackageLogLevels, resetGatewayPackageLogLevelsInit } from './gateway-log-levels.js';
41
41
  export { GatewayLogCode, gatewayErrorCode, gatewayInfoCode, gatewayWarnCode, gatewayAnomalyMeta, resolveLogDiagnosticsCatalogPath, exceptionEvidence, fieldEvidence } from './gateway-log-diagnostics.js';
42
42
  export type { GatewayLogCode as GatewayDiagnosticCode } from './gateway-log-diagnostics.js';
43
+ /** Re-export @x12i/ai-tools invoke orchestrator (≥ 2.5.0) for engine callers. */
44
+ export { resolveInvokeModel, applyModelResolution, applyOpenRouterInvokePolicy, mapResolutionToRouterConfig, buildInvokeModelResolverOptions, buildModelResolverOptions, enrichModelResolutionError, getAiToolsClient, resetAiToolsClientForTests, resolveOpenRouterApiKey, resolvePreferOpenRouter, readPreferOpenRouterFromEnv, getAiToolsInvokeClient, createAiToolsInvokeClient, } from './ai-tools-client.js';
45
+ export type { AiToolsClientBundle, AiToolsInvokeClient, InvokeModelResolutionDiagnostics, InvokeModelResolutionInput, InvokeModelResolutionOptions, InvokeModelResolutionResult, InvokeRouterConfigSlice, } from './ai-tools-client.js';
43
46
  export { createLogxer, DebugLogAbstract, runWithLogContext, getStationRuntimeIdentity, mergeRuntimeIdentity, conditionEvidence, sourceEvidence, logReferenceEvidence, readAgentLoggingInstructions, resolveAgentLoggingInstructionsPath, applyPackageLogLevelsFromEnv, configurePackageLogLevels, mergePackageLogLevelsConfig, setPackageLogLevel, resolveStackLogLevelForPrefix, resolvePackageLogsLevel, parseLogxerPackageLevelsEnv, LOGXER_PACKAGE_LEVELS_ENV, LOGXER_PACKAGE_LOGS_DEFAULT_ENV } from '@x12i/logxer';
44
47
  export { ROUTER_LOG_ENV_PREFIX } from '@x12i/ai-providers-router';
45
48
  export type { Logxer, LogMeta, RuntimeIdentity, LogRuntimeContext, GetJobLogsInput, GetJobLogsResult, QueryableLogLine, LogDiagnostics, DiagnosticEvidence, ScopeCriteria, ScopeLogsResult, StackLoggingOptions, PackageLogLevelsConfig, PackageLogLevelSetting } from '@x12i/logxer';
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ export * from '@x12i/ai-providers-router';
17
17
  export { AIGateway } from './gateway.js';
18
18
  export { InstructionNotFoundError, InstructionBackendError, ModelRequiredError, MaxTokensRequiredError } from './instruction-errors.js';
19
19
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
20
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
20
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, ensureInvokeBillingCostStatus, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
21
21
  export { getGatewayOperationalMode, isProdGatewayMode, parseModelProviderSpec } from './gateway-mode.js';
22
22
  export { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_RETRY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P, resolveRetryConfig } from './gateway-defaults.js';
23
23
  export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
@@ -33,6 +33,8 @@ export { activityIdentityToLogContext, activityIdentityToLogMeta, withActivityId
33
33
  export { createGatewayLogger, resolveGatewayVerboseEnabled } from './logger-factory.js';
34
34
  export { GATEWAY_LOG_ENV_PREFIX, GATEWAY_LOGXER_PACKAGE, GATEWAY_STACK_LOG_PREFIXES, initializeGatewayPackageLogLevels, resetGatewayPackageLogLevelsInit } from './gateway-log-levels.js';
35
35
  export { GatewayLogCode, gatewayErrorCode, gatewayInfoCode, gatewayWarnCode, gatewayAnomalyMeta, resolveLogDiagnosticsCatalogPath, exceptionEvidence, fieldEvidence } from './gateway-log-diagnostics.js';
36
+ /** Re-export @x12i/ai-tools invoke orchestrator (≥ 2.5.0) for engine callers. */
37
+ export { resolveInvokeModel, applyModelResolution, applyOpenRouterInvokePolicy, mapResolutionToRouterConfig, buildInvokeModelResolverOptions, buildModelResolverOptions, enrichModelResolutionError, getAiToolsClient, resetAiToolsClientForTests, resolveOpenRouterApiKey, resolvePreferOpenRouter, readPreferOpenRouterFromEnv, getAiToolsInvokeClient, createAiToolsInvokeClient, } from './ai-tools-client.js';
36
38
  // Re-export logging (@x12i/logxer)
37
39
  export { createLogxer, DebugLogAbstract, runWithLogContext, getStationRuntimeIdentity, mergeRuntimeIdentity, conditionEvidence, sourceEvidence, logReferenceEvidence, readAgentLoggingInstructions, resolveAgentLoggingInstructionsPath, applyPackageLogLevelsFromEnv, configurePackageLogLevels, mergePackageLogLevelsConfig, setPackageLogLevel, resolveStackLogLevelForPrefix, resolvePackageLogsLevel, parseLogxerPackageLevelsEnv, LOGXER_PACKAGE_LEVELS_ENV, LOGXER_PACKAGE_LOGS_DEFAULT_ENV } from '@x12i/logxer';
38
40
  export { ROUTER_LOG_ENV_PREFIX } from '@x12i/ai-providers-router';
@@ -1,17 +1,8 @@
1
1
  /**
2
- * OpenRouter API key and gateway-level "prefer OpenRouter" flag.
3
- * Provider/model routing (openrouter vs vendor) is resolved by @x12i/ai-tools in mergeConfig.
2
+ * Gateway-level OpenRouter key + prefer flags mapped to @x12i/ai-tools invoke helpers.
4
3
  */
4
+ import { readPreferOpenRouterFromEnv } from '@x12i/ai-tools';
5
5
  import type { GatewayConfig } from './types.js';
6
+ export { readPreferOpenRouterFromEnv };
6
7
  export declare function resolveOpenRouterApiKey(config?: GatewayConfig): string | undefined;
7
- /**
8
- * Read operator preference from env: `PREFER_OPENROUTER` (current), then deprecated `USE_OPENROUTER`.
9
- * Returns undefined when neither is set.
10
- */
11
- export declare function readPreferOpenRouterFromEnv(): boolean | undefined;
12
- /**
13
- * When true, pass `routeViaOpenRouter: true` into ai-tools resolveModel (prefer OpenRouter when a key exists).
14
- * Default: true. `PREFER_OPENROUTER=false` (or deprecated `USE_OPENROUTER=false`) forces vendor-direct when keys exist;
15
- * ai-tools still falls back to OpenRouter when the requested provider has no direct key.
16
- */
17
8
  export declare function resolvePreferOpenRouter(config?: GatewayConfig): boolean;
@@ -1,37 +1,11 @@
1
1
  /**
2
- * OpenRouter API key and gateway-level "prefer OpenRouter" flag.
3
- * Provider/model routing (openrouter vs vendor) is resolved by @x12i/ai-tools in mergeConfig.
2
+ * Gateway-level OpenRouter key + prefer flags mapped to @x12i/ai-tools invoke helpers.
4
3
  */
4
+ import { readPreferOpenRouterFromEnv, resolveOpenRouterApiKey as resolveOpenRouterApiKeyFromTools, resolvePreferOpenRouter as resolvePreferFromTools, } from '@x12i/ai-tools';
5
+ export { readPreferOpenRouterFromEnv };
5
6
  export function resolveOpenRouterApiKey(config = {}) {
6
- const explicit = config.openrouter?.apiKey;
7
- if (typeof explicit === 'string' && explicit.trim() && !explicit.startsWith('ENV.')) {
8
- return explicit.trim();
9
- }
10
- const env = process.env.OPENROUTER_API_KEY?.trim();
11
- return env || undefined;
7
+ return resolveOpenRouterApiKeyFromTools(config.openrouter?.apiKey);
12
8
  }
13
- /**
14
- * Read operator preference from env: `PREFER_OPENROUTER` (current), then deprecated `USE_OPENROUTER`.
15
- * Returns undefined when neither is set.
16
- */
17
- export function readPreferOpenRouterFromEnv() {
18
- const prefer = process.env.PREFER_OPENROUTER?.trim();
19
- if (prefer === 'false' || prefer === '0')
20
- return false;
21
- if (prefer === 'true' || prefer === '1')
22
- return true;
23
- const legacy = process.env.USE_OPENROUTER?.trim();
24
- if (legacy === 'false' || legacy === '0')
25
- return false;
26
- if (legacy === 'true' || legacy === '1')
27
- return true;
28
- return undefined;
29
- }
30
- /**
31
- * When true, pass `routeViaOpenRouter: true` into ai-tools resolveModel (prefer OpenRouter when a key exists).
32
- * Default: true. `PREFER_OPENROUTER=false` (or deprecated `USE_OPENROUTER=false`) forces vendor-direct when keys exist;
33
- * ai-tools still falls back to OpenRouter when the requested provider has no direct key.
34
- */
35
9
  export function resolvePreferOpenRouter(config = {}) {
36
10
  if (config.openRouter?.prefer === true)
37
11
  return true;
@@ -41,8 +15,5 @@ export function resolvePreferOpenRouter(config = {}) {
41
15
  return true;
42
16
  if (config.openRouter?.enabled === false)
43
17
  return false;
44
- const fromEnv = readPreferOpenRouterFromEnv();
45
- if (fromEnv !== undefined)
46
- return fromEnv;
47
- return true;
18
+ return resolvePreferFromTools();
48
19
  }
package/dist/types.d.ts CHANGED
@@ -9,6 +9,7 @@ type AIModel = string;
9
9
  export type UsageTier = string;
10
10
  import type { Activix } from '@x12i/activix';
11
11
  import type { SmartInputConfig, SmartInputRenderOptions, TemplateRenderOptions } from '@x12i/rendrix';
12
+ import type { ProfileCatalogLane } from '@x12i/ai-profiles';
12
13
  import type { Logxer, PackageLogLevelsConfig } from '@x12i/logxer';
13
14
  /**
14
15
  * Diagnostics options for opt-in authoritative tracing.
@@ -415,10 +416,21 @@ export interface GatewayConfig extends Omit<RouterConfig, 'defaultEngine' | 'log
415
416
  cacheTtlMs?: number;
416
417
  /** Use bundled catalog JSON only (offline / tests). */
417
418
  bundledOnly?: boolean;
419
+ /**
420
+ * Catalog lane for model resolution and cost lookup (`text`, `image`, …).
421
+ * @default `"text"` in ai-tools when omitted.
422
+ */
423
+ catalogLane?: ProfileCatalogLane;
418
424
  /** @default true */
419
425
  resolveModels?: boolean;
426
+ /**
427
+ * When true (default), reject profile/choice keys and shortcuts (`cheap/default`, `cheapest`, …).
428
+ * Only concrete catalog model ids are accepted; ai-tools still normalizes aliases and routing.
429
+ */
430
+ modelsOnly?: boolean;
420
431
  /** @default true */
421
432
  calculateCost?: boolean;
433
+ /** @default false — when true, priced results may include prompt/completion breakdown. */
422
434
  costIncludeBreakdown?: boolean;
423
435
  };
424
436
  /**