@x12i/ai-gateway 10.0.2 → 10.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -417,6 +417,11 @@ export interface GatewayConfig extends Omit<RouterConfig, 'defaultEngine' | 'log
417
417
  bundledOnly?: boolean;
418
418
  /** @default true */
419
419
  resolveModels?: boolean;
420
+ /**
421
+ * When true (default), reject profile/choice keys and shortcuts (`cheap/default`, `cheapest`, …).
422
+ * Only concrete catalog model ids are accepted; ai-tools still normalizes aliases and routing.
423
+ */
424
+ modelsOnly?: boolean;
420
425
  /** @default true */
421
426
  calculateCost?: boolean;
422
427
  costIncludeBreakdown?: boolean;
@@ -1,25 +1,35 @@
1
1
  /**
2
- * Lazy @x12i/ai-tools catalog + cost calculator bootstrap.
2
+ * @x12i/ai-tools invoke client bootstrap for the gateway.
3
+ * Model resolution orchestration lives in ai-tools ≥ 2.5.0 (`resolveInvokeModel`).
3
4
  */
4
- import { AiModelsCatalogClient, CostCalculator, isEffectiveOpenRouterTransport, loadOpenRouterRoutingEnv, resolveModelVendorFromResolution } from '@x12i/ai-tools';
5
+ import { getAiToolsInvokeClient, resetAiToolsInvokeClientForTests as resetAiToolsInvokeClientForTestsUpstream, mapResolutionToRouterConfig, buildInvokeModelResolverOptions, } from '@x12i/ai-tools';
5
6
  import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
6
7
  import { resolvePreferOpenRouter } from './openrouter-routing.js';
7
- let sharedClientPromise = null;
8
- let sharedConfigKey;
8
+ export { resolveInvokeModel, applyOpenRouterInvokePolicy, buildInvokeModelResolverOptions, enrichModelResolutionError, mapResolutionToRouterConfig, ModelProfileUnroutableError, ModelProfileInputRejectedError, MODEL_PROFILE_UNROUTABLE, getAiToolsInvokeClient, resetAiToolsInvokeClientForTests as resetAiToolsInvokeClientForTestsUpstream, createAiToolsInvokeClient, } from '@x12i/ai-tools';
9
+ export { resolveOpenRouterApiKey, resolvePreferOpenRouter, readPreferOpenRouterFromEnv } from './openrouter-routing.js';
9
10
  let bootstrapFailedLogged = false;
10
- function configKey(config) {
11
- return `${config.aiTools?.cacheTtlMs ?? ''}:${config.aiTools?.costIncludeBreakdown ?? ''}:${config.aiTools?.bundledOnly ?? ''}`;
11
+ function invokeClientOptions(config) {
12
+ return {
13
+ cacheTtlMs: config.aiTools?.cacheTtlMs,
14
+ ...(config.aiTools?.bundledOnly ? { bundledOnly: true } : {}),
15
+ ...(config.aiTools?.costIncludeBreakdown ? { costIncludeBreakdown: true } : {}),
16
+ cacheKey: `${config.aiTools?.cacheTtlMs ?? ''}:${config.aiTools?.costIncludeBreakdown ?? ''}:${config.aiTools?.bundledOnly ?? ''}`,
17
+ };
18
+ }
19
+ /** @deprecated Use buildInvokeModelResolverOptions */
20
+ export function buildModelResolverOptions(config, routingEnv) {
21
+ return buildInvokeModelResolverOptions({
22
+ routingEnv,
23
+ preferOpenRouter: resolvePreferOpenRouter(config),
24
+ });
12
25
  }
13
26
  /**
14
- * Per-invoke resolver options: ai-tools decides OpenRouter vs direct from env + optional gateway prefer override.
27
+ * @deprecated Use mapResolutionToRouterConfig from @x12i/ai-tools
15
28
  */
16
- export function buildModelResolverOptions(config, routingEnv) {
17
- const env = routingEnv ?? loadOpenRouterRoutingEnv();
18
- const prefer = resolvePreferOpenRouter(config);
19
- return {
20
- routingEnv: env,
21
- ...(prefer ? { routeViaOpenRouter: true } : {}),
22
- };
29
+ export function applyModelResolution(merged, resolution, gatewayDefaultEngine, inputModel) {
30
+ const mapped = mapResolutionToRouterConfig(resolution, { provider: merged.provider, model: inputModel ?? merged.model ?? '' }, gatewayDefaultEngine);
31
+ merged.provider = mapped.provider;
32
+ merged.model = mapped.model;
23
33
  }
24
34
  /**
25
35
  * Returns catalog + calculator, or null when disabled or bootstrap fails.
@@ -28,102 +38,26 @@ export async function getAiToolsClient(config, logger) {
28
38
  if (config.aiTools?.enabled === false) {
29
39
  return null;
30
40
  }
31
- const key = configKey(config);
32
- if (sharedClientPromise && sharedConfigKey !== key) {
33
- sharedClientPromise = null;
34
- }
35
- sharedConfigKey = key;
36
- if (!sharedClientPromise) {
37
- sharedClientPromise = bootstrapAiTools(config, logger);
38
- }
39
- return sharedClientPromise;
40
- }
41
- /** Reset singleton (tests). */
42
- export function resetAiToolsClientForTests() {
43
- sharedClientPromise = null;
44
- sharedConfigKey = undefined;
45
- bootstrapFailedLogged = false;
46
- }
47
- async function bootstrapAiTools(config, logger) {
48
- try {
49
- const routingEnv = loadOpenRouterRoutingEnv();
50
- const catalog = new AiModelsCatalogClient({
51
- cacheTtlMs: config.aiTools?.cacheTtlMs,
52
- ...(config.aiTools?.bundledOnly ? { bundledOnly: true } : {}),
53
- resolverOptions: { routingEnv },
54
- });
55
- const calculator = new CostCalculator(catalog, {
56
- includeBreakdown: config.aiTools?.costIncludeBreakdown === true,
57
- });
41
+ const client = await getAiToolsInvokeClient(invokeClientOptions(config), {
42
+ warn: (msg, err) => {
43
+ if (!bootstrapFailedLogged) {
44
+ bootstrapFailedLogged = true;
45
+ logger.warn(msg, withActivityIdentity(undefined, {
46
+ error: err instanceof Error ? err.message : String(err),
47
+ debugKind: gatewayLogDebug.anomaly,
48
+ }));
49
+ }
50
+ },
51
+ });
52
+ if (client) {
58
53
  logger.debug('ai-tools catalog client ready', {
59
54
  debugKind: gatewayLogDebug.state,
60
55
  });
61
- return { catalog, calculator, routingEnv };
62
- }
63
- catch (error) {
64
- if (!bootstrapFailedLogged) {
65
- bootstrapFailedLogged = true;
66
- logger.warn('ai-tools catalog bootstrap failed; model resolution and catalog cost calculation disabled', withActivityIdentity(undefined, {
67
- error: error instanceof Error ? error.message : String(error),
68
- debugKind: gatewayLogDebug.anomaly,
69
- }));
70
- }
71
- return null;
72
56
  }
57
+ return client;
73
58
  }
74
- /**
75
- * Map catalog resolution to router `{ provider, model }` (agnostic to openrouter vs vendor input).
76
- */
77
- export function applyModelResolution(merged, resolution, gatewayDefaultEngine, inputModel) {
78
- const ref = resolveModelVendorFromResolution(resolution, inputModel ?? merged.model ?? '', {
79
- asOpenRouter: resolution.routedViaOpenRouter,
80
- });
81
- if (ref) {
82
- merged.provider = ref.provider;
83
- merged.model = ref.model;
84
- return;
85
- }
86
- if (resolution.routedViaOpenRouter) {
87
- merged.provider = 'openrouter';
88
- merged.model = resolution.modelId;
89
- return;
90
- }
91
- const slash = resolution.modelId.indexOf('/');
92
- if (slash > 0) {
93
- merged.provider = resolution.record?.providerId ?? resolution.modelId.slice(0, slash);
94
- merged.model = resolution.modelId.slice(slash + 1);
95
- }
96
- else {
97
- merged.model = resolution.modelId;
98
- if (resolution.record?.providerId) {
99
- merged.provider = resolution.record.providerId;
100
- }
101
- }
102
- if (!merged.provider && gatewayDefaultEngine) {
103
- merged.provider = gatewayDefaultEngine;
104
- }
105
- }
106
- /**
107
- * Router invoke flags after mergeConfig + ai-tools resolution (OpenRouter vs direct transport).
108
- */
109
- export function applyOpenRouterInvokePolicy(merged, options) {
110
- if (!options.openRouterApiKey?.trim())
111
- return;
112
- const routingEnv = options.routingEnv ?? loadOpenRouterRoutingEnv();
113
- const viaOpenRouter = options.resolution?.routedViaOpenRouter === true ||
114
- (options.resolution?.routedViaOpenRouter !== false &&
115
- isEffectiveOpenRouterTransport(routingEnv, {
116
- provider: merged.provider,
117
- modelId: merged.model,
118
- routeViaOpenRouter: options.preferOpenRouter ? true : undefined,
119
- }));
120
- if (viaOpenRouter) {
121
- merged.allowOpenRouterProxy = true;
122
- if (merged.provider && merged.provider !== 'openrouter') {
123
- merged.providerProxy = 'openrouter';
124
- }
125
- }
126
- else {
127
- merged.allowOpenRouterProxy = false;
128
- }
59
+ /** Reset singleton (tests). */
60
+ export function resetAiToolsClientForTests() {
61
+ resetAiToolsInvokeClientForTestsUpstream();
62
+ bootstrapFailedLogged = false;
129
63
  }
@@ -1,44 +1,26 @@
1
1
  /**
2
- * Lazy @x12i/ai-tools catalog + cost calculator bootstrap.
2
+ * @x12i/ai-tools invoke client bootstrap for the gateway.
3
+ * Model resolution orchestration lives in ai-tools ≥ 2.5.0 (`resolveInvokeModel`).
3
4
  */
4
- import { AiModelsCatalogClient, CostCalculator, type ModelResolutionSuccess, type ModelResolverOptions, type OpenRouterRoutingConfig } from '@x12i/ai-tools';
5
+ import { type AiToolsInvokeClient, type ModelResolutionSuccess, type OpenRouterRoutingConfig } from '@x12i/ai-tools';
5
6
  import type { Logxer } from '@x12i/logxer';
6
- import type { ChatRequest, GatewayConfig } from './types.js';
7
- export type AiToolsClientBundle = {
8
- catalog: AiModelsCatalogClient;
9
- calculator: CostCalculator;
10
- routingEnv: OpenRouterRoutingConfig;
11
- };
7
+ import type { GatewayConfig } from './types.js';
8
+ export type AiToolsClientBundle = AiToolsInvokeClient;
9
+ export { resolveInvokeModel, applyOpenRouterInvokePolicy, buildInvokeModelResolverOptions, enrichModelResolutionError, mapResolutionToRouterConfig, ModelProfileUnroutableError, ModelProfileInputRejectedError, MODEL_PROFILE_UNROUTABLE, getAiToolsInvokeClient, resetAiToolsInvokeClientForTests as resetAiToolsInvokeClientForTestsUpstream, createAiToolsInvokeClient, } from '@x12i/ai-tools';
10
+ export { resolveOpenRouterApiKey, resolvePreferOpenRouter, readPreferOpenRouterFromEnv } from './openrouter-routing.js';
11
+ export type { InvokeModelResolutionDiagnostics, InvokeModelResolutionInput, InvokeModelResolutionOptions, InvokeModelResolutionResult, InvokeRouterConfigSlice, AiToolsInvokeClient, } from '@x12i/ai-tools';
12
+ /** @deprecated Use buildInvokeModelResolverOptions */
13
+ export declare function buildModelResolverOptions(config: GatewayConfig, routingEnv?: OpenRouterRoutingConfig): import("@x12i/ai-tools").ModelResolverOptions;
12
14
  /**
13
- * Per-invoke resolver options: ai-tools decides OpenRouter vs direct from env + optional gateway prefer override.
15
+ * @deprecated Use mapResolutionToRouterConfig from @x12i/ai-tools
14
16
  */
15
- export declare function buildModelResolverOptions(config: GatewayConfig, routingEnv?: OpenRouterRoutingConfig): ModelResolverOptions;
17
+ export declare function applyModelResolution(merged: {
18
+ provider?: string;
19
+ model?: string;
20
+ }, resolution: ModelResolutionSuccess, gatewayDefaultEngine?: string, inputModel?: string): void;
16
21
  /**
17
22
  * Returns catalog + calculator, or null when disabled or bootstrap fails.
18
23
  */
19
24
  export declare function getAiToolsClient(config: GatewayConfig, logger: Logxer): Promise<AiToolsClientBundle | null>;
20
25
  /** Reset singleton (tests). */
21
26
  export declare function resetAiToolsClientForTests(): void;
22
- /**
23
- * Map catalog resolution to router `{ provider, model }` (agnostic to openrouter vs vendor input).
24
- */
25
- export declare function applyModelResolution(merged: NonNullable<ChatRequest['config']>, resolution: ModelResolutionSuccess, gatewayDefaultEngine?: string, inputModel?: string): void;
26
- type RouterConfigSlice = {
27
- provider?: string;
28
- model?: string;
29
- allowOpenRouterProxy?: boolean;
30
- providerProxy?: string;
31
- };
32
- type ModelResolutionMeta = {
33
- routedViaOpenRouter?: boolean;
34
- };
35
- /**
36
- * Router invoke flags after mergeConfig + ai-tools resolution (OpenRouter vs direct transport).
37
- */
38
- export declare function applyOpenRouterInvokePolicy(merged: RouterConfigSlice, options: {
39
- openRouterApiKey?: string;
40
- preferOpenRouter?: boolean;
41
- routingEnv?: OpenRouterRoutingConfig;
42
- resolution?: ModelResolutionMeta;
43
- }): void;
44
- export {};
@@ -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,62 @@ 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
+ });
112
+ merged.provider = resolved.router.provider;
113
+ merged.model = resolved.router.model;
114
+ if (resolved.router.allowOpenRouterProxy !== undefined) {
115
+ merged.allowOpenRouterProxy = resolved.router.allowOpenRouterProxy;
127
116
  }
128
- else {
129
- throw buildModelResolutionFailureError(explicitModel, merged.provider, resolution);
117
+ if (resolved.router.providerProxy !== undefined) {
118
+ merged.providerProxy = resolved.router.providerProxy;
130
119
  }
120
+ request._modelResolution = resolved.diagnostics;
121
+ logger.verbose('Catalog resolved model name', {
122
+ jobId: request.identity.jobId,
123
+ originalModel,
124
+ originalProvider,
125
+ resolvedModelId: resolved.diagnostics.modelId,
126
+ provider: merged.provider,
127
+ model: merged.model,
128
+ confidence: resolved.diagnostics.confidence,
129
+ resolvedVia: resolved.diagnostics.resolvedVia,
130
+ routedViaOpenRouter: resolved.diagnostics.routedViaOpenRouter,
131
+ });
131
132
  }
132
133
  catch (error) {
133
- if (error instanceof ModelResolutionError) {
134
- throw error;
135
- }
136
- if (error instanceof ModelProfileUnroutableError) {
134
+ if (error instanceof ModelResolutionError ||
135
+ error instanceof ModelProfileUnroutableError ||
136
+ error instanceof ModelProfileInputRejectedError) {
137
137
  throw error;
138
138
  }
139
139
  throw error;
140
140
  }
141
141
  }
142
+ else if (mergeOptions?.openRouterApiKey) {
143
+ const resolved = await resolveInvokeModel({ provider: merged.provider, model: explicitModel }, {
144
+ resolveModels: false,
145
+ routingEnv: mergeOptions.routingEnv,
146
+ openRouterApiKey: mergeOptions.openRouterApiKey,
147
+ preferOpenRouter: mergeOptions.preferOpenRouter,
148
+ defaultProvider: config.defaultEngine,
149
+ });
150
+ merged.provider = resolved.router.provider;
151
+ merged.model = resolved.router.model;
152
+ if (resolved.router.allowOpenRouterProxy !== undefined) {
153
+ merged.allowOpenRouterProxy = resolved.router.allowOpenRouterProxy;
154
+ }
155
+ if (resolved.router.providerProxy !== undefined) {
156
+ merged.providerProxy = resolved.router.providerProxy;
157
+ }
158
+ }
142
159
  if (!merged.model) {
143
160
  throw new ModelRequiredError();
144
161
  }
@@ -679,31 +696,6 @@ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
679
696
  }
680
697
  return out;
681
698
  }
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
699
  /**
708
700
  * Build rejection-metadata fallback attempts from trace-mode {@link GatewayTraceAttempt}s.
709
701
  */
@@ -772,7 +764,7 @@ export function mapGatewayFallbackAttemptsToRouter(attempts) {
772
764
  }));
773
765
  }
774
766
  /**
775
- * Log profile alias vs OpenRouter model id actually sent to the router after catalog resolution.
767
+ * Log original input vs OpenRouter model id actually sent to the router after catalog resolution.
776
768
  */
777
769
  export function logResolvedModelRouting(logger, request, mergedConfig) {
778
770
  const res = request._modelResolution;
@@ -780,14 +772,14 @@ export function logResolvedModelRouting(logger, request, mergedConfig) {
780
772
  return;
781
773
  }
782
774
  const profileAlias = res.originalModel ?? mergedConfig?.model;
783
- const invokedModelId = res.modelId ?? mergedConfig?.model;
775
+ const invokedModelId = res.invokedModelId ?? res.modelId ?? mergedConfig?.model;
784
776
  const provider = mergedConfig?.provider;
785
777
  const openRouterPath = res.routedViaOpenRouter === true || provider === 'openrouter';
786
778
  if (!openRouterPath) {
787
779
  return;
788
780
  }
789
- logger.info('OpenRouter routing: profile alias resolved to model id for invoke', withActivityIdentity(request.identity, {
790
- profileAlias,
781
+ logger.info('OpenRouter routing: input model resolved to catalog id for invoke', withActivityIdentity(request.identity, {
782
+ inputModel: profileAlias,
791
783
  invokedOpenRouterModelId: invokedModelId,
792
784
  provider,
793
785
  routedViaOpenRouter: res.routedViaOpenRouter,
@@ -16,7 +16,10 @@ export declare function ensureTaskTypeId(request: ChatRequest, logger: Logxer):
16
16
  export type MergeConfigOptions = {
17
17
  catalog?: AiModelsCatalogClient | null;
18
18
  routingEnv?: OpenRouterRoutingConfig;
19
+ openRouterApiKey?: string;
20
+ preferOpenRouter?: boolean;
19
21
  };
22
+ export { MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, } from '@x12i/ai-tools';
20
23
  /**
21
24
  * Merges config with defaults
22
25
  * Supports using internal system action defaults (internalSkill or skillAudit) when useInternalDefaults is set
@@ -149,14 +152,6 @@ export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<
149
152
  */
150
153
  export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
151
154
  export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
152
- /** Error code hint when a bundled profile name cannot be routed to a catalog target. */
153
- export declare const MODEL_PROFILE_UNROUTABLE = "MODEL_PROFILE_UNROUTABLE";
154
- export declare class ModelProfileUnroutableError extends Error {
155
- readonly profileAlias: string;
156
- readonly provider: string | undefined;
157
- readonly code = "MODEL_PROFILE_UNROUTABLE";
158
- constructor(profileAlias: string, provider: string | undefined, cause?: unknown);
159
- }
160
155
  type ModelResolutionCandidate = {
161
156
  provider: string;
162
157
  model: string;
@@ -175,7 +170,7 @@ export declare function mapGatewayFallbackAttemptsToRouter(attempts: GatewayFall
175
170
  responsePreview?: string;
176
171
  }>;
177
172
  /**
178
- * Log profile alias vs OpenRouter model id actually sent to the router after catalog resolution.
173
+ * Log original input vs OpenRouter model id actually sent to the router after catalog resolution.
179
174
  */
180
175
  export declare function logResolvedModelRouting(logger: Logxer, request: ChatRequest, mergedConfig: ChatRequest['config']): void;
181
176
  /**
@@ -14,7 +14,6 @@ import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys }
14
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';
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);
@@ -292,14 +287,10 @@ export class AIGateway {
292
287
  const mergedConfig = await mergeConfig(request, this.config, this.logger, {
293
288
  catalog: aiTools?.catalog ?? null,
294
289
  routingEnv: aiTools?.routingEnv,
295
- });
296
- request._mergedRouterConfig = mergedConfig;
297
- applyOpenRouterInvokePolicy(mergedConfig, {
298
- preferOpenRouter: this.preferOpenRouter,
299
290
  openRouterApiKey: this.openRouterApiKey,
300
- routingEnv: aiTools?.routingEnv,
301
- resolution: request._modelResolution,
291
+ preferOpenRouter: this.preferOpenRouter,
302
292
  });
293
+ request._mergedRouterConfig = mergedConfig;
303
294
  logResolvedModelRouting(this.logger, request, mergedConfig);
304
295
  const diagnosticsMode = request.diagnostics?.mode;
305
296
  const traceEnabled = diagnosticsMode === 'trace';
@@ -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, 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';
@@ -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, 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';
@@ -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
  }