@x12i/ai-gateway 9.5.2 → 9.6.0
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 +4 -3
- package/dist/defaults/model-config.json +2 -3
- package/dist/gateway-config.js +1 -1
- package/dist/gateway-meta.js +2 -1
- package/dist/gateway-mode.d.ts +2 -1
- package/dist/gateway-mode.js +2 -1
- package/dist/gateway-provider-auto-register.js +1 -1
- package/dist/gateway-utils.d.ts +6 -1
- package/dist/gateway-utils.js +88 -5
- package/dist/gateway.js +3 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/types.d.ts +13 -0
- package/dist-cjs/defaults/model-config.json +2 -3
- package/dist-cjs/gateway-config.cjs +1 -1
- package/dist-cjs/gateway-meta.cjs +2 -1
- package/dist-cjs/gateway-mode.cjs +2 -1
- package/dist-cjs/gateway-mode.d.ts +2 -1
- package/dist-cjs/gateway-provider-auto-register.cjs +1 -1
- package/dist-cjs/gateway-utils.cjs +88 -5
- package/dist-cjs/gateway-utils.d.ts +6 -1
- package/dist-cjs/gateway.cjs +3 -2
- package/dist-cjs/index.cjs +1 -1
- package/dist-cjs/index.d.ts +2 -2
- package/dist-cjs/types.d.ts +13 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ const response = await gateway.invoke({
|
|
|
79
79
|
agentId: 'agent-456'
|
|
80
80
|
},
|
|
81
81
|
workingMemory: { input: 'Hello!' },
|
|
82
|
-
config: { model: '
|
|
82
|
+
config: { model: 'cheap', provider: 'openrouter' }
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
console.log(response.content, response.metadata?.costUsd, response.metadata?.tokens);
|
|
@@ -87,7 +87,7 @@ console.log(response.content, response.metadata?.costUsd, response.metadata?.tok
|
|
|
87
87
|
|
|
88
88
|
### Providers without manual `register()`
|
|
89
89
|
|
|
90
|
-
- **OpenRouter:** Set
|
|
90
|
+
- **OpenRouter:** Set **`OPENROUTER_API_KEY`** in `.env` (unless `USE_OPENROUTER=false`). The gateway passes this key into the router on init and lazy-registers on first invoke. **`OPEN_ROUTER_KEY`** is a legacy alias still read if `OPENROUTER_API_KEY` is unset — prefer **`OPENROUTER_API_KEY`** so **`@x12i/ai-tools`** model resolution sets `routedViaOpenRouter` correctly (ai-tools does not read `OPEN_ROUTER_KEY`).
|
|
91
91
|
- **Direct providers:** Set `OPENAI_API_KEY`, `GROK_API_KEY`, etc. Same lazy registration.
|
|
92
92
|
|
|
93
93
|
Load `.env` before constructing the gateway if another package creates it first.
|
|
@@ -217,7 +217,7 @@ Set via constructor `mode` or env `mode` / `MODE`.
|
|
|
217
217
|
| `npm run test:flex-md-esm-regression` | ESM build regression for flex-md |
|
|
218
218
|
| `npm run test:prepublish` | `build` + `npm test` |
|
|
219
219
|
|
|
220
|
-
Live tests use `LIVE_TEST_PROVIDER` / `LIVE_TEST_MODEL` (default `openrouter` + `
|
|
220
|
+
Live tests use `LIVE_TEST_PROVIDER` / `LIVE_TEST_MODEL` (default `openrouter` + `cheap`, an ai-tools profile alias). Set `LIVE_SKIP_INVOKE=1` to skip the LLM call.
|
|
221
221
|
|
|
222
222
|
---
|
|
223
223
|
|
|
@@ -230,6 +230,7 @@ Live tests use `LIVE_TEST_PROVIDER` / `LIVE_TEST_MODEL` (default `openrouter` +
|
|
|
230
230
|
| [LOGGER_INITIALIZATION.md](./docs/LOGGER_INITIALIZATION.md) | Logxer setup |
|
|
231
231
|
| [flex-md-compliance.md](./docs/flex-md-compliance.md) | Output format levels |
|
|
232
232
|
| [PROMPT_TEMPLATE_USAGE.md](./docs/PROMPT_TEMPLATE_USAGE.md) | Rendrix templates |
|
|
233
|
+
| [upstream-reports/README.md](./docs/upstream-reports/README.md) | Upstream issues (one file per package/gap) |
|
|
233
234
|
| [UPSTREAM_TEMPLATE_RENDERING_AND_PARSER_V4.md](./docs/UPSTREAM_TEMPLATE_RENDERING_AND_PARSER_V4.md) | Parser v4 |
|
|
234
235
|
| [RUNTIME_OBJECTS_OBSERVABILITY.md](./docs/RUNTIME_OBJECTS_OBSERVABILITY.md) | Runtime object keys |
|
|
235
236
|
| [GRAPH_EXECUTION_SUPPORT.md](./docs/GRAPH_EXECUTION_SUPPORT.md) | Graph / node identity |
|
package/dist/gateway-config.js
CHANGED
|
@@ -197,7 +197,7 @@ export function initializeGatewayComponents(config) {
|
|
|
197
197
|
// Prefer explicit config from consumer (e.g. ai-skills) to avoid env-loading timing; fall back to process.env.
|
|
198
198
|
const explicitOpenRouterKey = config.openrouter?.apiKey;
|
|
199
199
|
const isExplicitKey = typeof explicitOpenRouterKey === 'string' && !explicitOpenRouterKey.startsWith('ENV.');
|
|
200
|
-
const openRouterKey = isExplicitKey ? explicitOpenRouterKey : (process.env.
|
|
200
|
+
const openRouterKey = isExplicitKey ? explicitOpenRouterKey : (process.env.OPENROUTER_API_KEY ?? process.env.OPEN_ROUTER_KEY);
|
|
201
201
|
const useOpenRouter = config.openRouter?.enabled !== undefined ? config.openRouter?.enabled : process.env.USE_OPENROUTER;
|
|
202
202
|
if (openRouterKey && useOpenRouter !== false && useOpenRouter !== 'false') {
|
|
203
203
|
routerConfig.openRouter = { enabled: true };
|
package/dist/gateway-meta.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Gateway Meta Operations Module
|
|
3
3
|
* Handles meta operations like instruction optimization and testing
|
|
4
4
|
*/
|
|
5
|
+
import { CODE_DEFAULT_MODEL } from './gateway-mode.js';
|
|
5
6
|
/**
|
|
6
7
|
* Test instructions by running them and analyzing the response
|
|
7
8
|
*/
|
|
@@ -9,7 +10,7 @@ export async function testInstructions(instructions, testInput, expectedSchema,
|
|
|
9
10
|
// Get internal system action config (instruction audit)
|
|
10
11
|
const internalConfig = config.internalSystemActions?.instructionAudit;
|
|
11
12
|
const defaultEngine = config.defaultEngine || 'openai';
|
|
12
|
-
const defaultModel = internalConfig?.model ||
|
|
13
|
+
const defaultModel = internalConfig?.model || CODE_DEFAULT_MODEL;
|
|
13
14
|
const defaultProvider = internalConfig?.engine || defaultEngine;
|
|
14
15
|
const { agentId = 'instruction-tester', model = options.model || defaultModel, // Use internal config default if not provided
|
|
15
16
|
provider = options.provider || defaultProvider // Use internal config default if not provided
|
package/dist/gateway-mode.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ import type { ActivityIdentity, GatewayConfig } from './types.js';
|
|
|
6
6
|
export type GatewayOperationalMode = 'prod' | 'debug' | 'dev';
|
|
7
7
|
export type GatewayDefaultModelSource = 'env' | 'model-config.json' | 'code';
|
|
8
8
|
export type DefaultModelSubstitutionReason = 'no_model_provided' | 'model_resolution_failed' | 'ai_tools_unavailable';
|
|
9
|
-
|
|
9
|
+
/** Profile name resolved via ai-tools + {@link @x12i/ai-profiles} when catalog is enabled. */
|
|
10
|
+
export declare const CODE_DEFAULT_MODEL = "cheap";
|
|
10
11
|
export type ResolvedGatewayDefault = {
|
|
11
12
|
model: string;
|
|
12
13
|
provider?: string;
|
package/dist/gateway-mode.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Gateway operational mode (prod vs dev/debug) and default model resolution.
|
|
3
3
|
*/
|
|
4
4
|
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
5
|
-
|
|
5
|
+
/** Profile name resolved via ai-tools + {@link @x12i/ai-profiles} when catalog is enabled. */
|
|
6
|
+
export const CODE_DEFAULT_MODEL = 'cheap';
|
|
6
7
|
/**
|
|
7
8
|
* Operational mode: `GatewayConfig.mode` overrides `process.env.mode` / `MODE`.
|
|
8
9
|
* Only `prod` allows silent default-model substitution; all other values are strict.
|
|
@@ -146,7 +146,7 @@ export async function autoRegisterProviders(router, logger) {
|
|
|
146
146
|
optionalEnvVars: PROVIDER_CONFIGS
|
|
147
147
|
.filter(p => p.optional)
|
|
148
148
|
.map(p => p.envVar),
|
|
149
|
-
openRouter: 'Set
|
|
149
|
+
openRouter: 'Set OPENROUTER_API_KEY (and do not set USE_OPENROUTER=false) to use OpenRouter without registering a provider. Legacy OPEN_ROUTER_KEY is still accepted.',
|
|
150
150
|
note: 'You can still manually register providers using gateway.register(provider)'
|
|
151
151
|
});
|
|
152
152
|
}
|
package/dist/gateway-utils.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Gateway Utilities Module
|
|
3
3
|
* Handles utility functions
|
|
4
4
|
*/
|
|
5
|
-
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
|
|
5
|
+
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayFallbackAttempt, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
7
|
import { type AiCostResult, type AiModelsCatalogClient, type CostCalculator } from '@x12i/ai-tools';
|
|
8
8
|
/**
|
|
@@ -155,6 +155,10 @@ export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<
|
|
|
155
155
|
*/
|
|
156
156
|
export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
|
|
157
157
|
export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
|
|
160
|
+
*/
|
|
161
|
+
export declare function tryExtractFallbackAttemptsFromErrorChain(error: unknown, maxDepth?: number): GatewayFallbackAttempt[] | undefined;
|
|
158
162
|
export declare function buildInvokeRejectionMetadata(args: {
|
|
159
163
|
request: Pick<AIInvokeRequest, 'aiRequestId' | 'identity' | 'config' | 'modelConfig'>;
|
|
160
164
|
taskTypeId: string;
|
|
@@ -162,6 +166,7 @@ export declare function buildInvokeRejectionMetadata(args: {
|
|
|
162
166
|
mergedConfig?: unknown;
|
|
163
167
|
partialRouterPayload?: unknown;
|
|
164
168
|
gatewayAiRequestId?: string;
|
|
169
|
+
error?: unknown;
|
|
165
170
|
}): GatewayInvokeRejectionMetadata;
|
|
166
171
|
export declare function attachGatewayInvokeRejectionMetadata(err: Error, metadata: GatewayInvokeRejectionMetadata): void;
|
|
167
172
|
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
package/dist/gateway-utils.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Handles utility functions
|
|
4
4
|
*/
|
|
5
5
|
import * as crypto from 'crypto';
|
|
6
|
+
import { FallbackExhaustedError } from '@x12i/ai-providers-router';
|
|
6
7
|
import { ModelResolutionError } from '@x12i/ai-tools';
|
|
7
8
|
import { getPreParsedInstructions } from './gateway-instructions.js';
|
|
8
9
|
import { getModelMaxTokensFromFlexMd } from './flex-md-loader.js';
|
|
@@ -55,6 +56,44 @@ async function substituteGatewayDefaultModel(merged, request, config, logger, me
|
|
|
55
56
|
});
|
|
56
57
|
applyGatewayDefaultToMerged(merged, defaults, config);
|
|
57
58
|
}
|
|
59
|
+
async function tryResolveSubstitutedDefaultModel(merged, request, config, logger, mergeOptions, original) {
|
|
60
|
+
const resolveModels = config.aiTools?.resolveModels !== false;
|
|
61
|
+
const catalog = mergeOptions?.catalog;
|
|
62
|
+
if (!resolveModels || !catalog || !merged.model) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const resolution = await catalog.resolveModel({
|
|
67
|
+
provider: merged.provider,
|
|
68
|
+
model: merged.model
|
|
69
|
+
});
|
|
70
|
+
if (!resolution.found) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
applyModelResolution(merged, resolution, config.defaultEngine);
|
|
74
|
+
request._modelResolution = {
|
|
75
|
+
modelId: resolution.modelId,
|
|
76
|
+
routedViaOpenRouter: resolution.routedViaOpenRouter,
|
|
77
|
+
confidence: resolution.confidence,
|
|
78
|
+
resolvedVia: resolution.resolvedVia,
|
|
79
|
+
originalProvider: original?.provider ?? merged.provider,
|
|
80
|
+
originalModel: original?.model ?? merged.model
|
|
81
|
+
};
|
|
82
|
+
logger.verbose('Catalog resolved substituted default model', {
|
|
83
|
+
jobId: request.identity.jobId,
|
|
84
|
+
model: merged.model,
|
|
85
|
+
provider: merged.provider,
|
|
86
|
+
resolvedModelId: resolution.modelId
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Prod keeps the substituted bare default when re-resolution fails.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, reason, original) {
|
|
94
|
+
await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, reason, original);
|
|
95
|
+
await tryResolveSubstitutedDefaultModel(merged, request, config, logger, mergeOptions, original);
|
|
96
|
+
}
|
|
58
97
|
/**
|
|
59
98
|
* True when any caller-controlled config source set `maxTokens` (Optimixer should not override).
|
|
60
99
|
*/
|
|
@@ -137,7 +176,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
137
176
|
const originalProvider = merged.provider;
|
|
138
177
|
const originalModel = explicitModel;
|
|
139
178
|
if (!explicitModel) {
|
|
140
|
-
await
|
|
179
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'no_model_provided');
|
|
141
180
|
}
|
|
142
181
|
else if (resolveModels && mergeOptions?.catalog) {
|
|
143
182
|
try {
|
|
@@ -166,7 +205,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
166
205
|
});
|
|
167
206
|
}
|
|
168
207
|
else if (isProdGatewayMode(operationalMode)) {
|
|
169
|
-
await
|
|
208
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'model_resolution_failed', { provider: originalProvider, model: originalModel });
|
|
170
209
|
}
|
|
171
210
|
else {
|
|
172
211
|
throw new ModelResolutionError({ provider: merged.provider, model: explicitModel }, resolution);
|
|
@@ -177,7 +216,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
177
216
|
throw error;
|
|
178
217
|
}
|
|
179
218
|
if (isProdGatewayMode(operationalMode)) {
|
|
180
|
-
await
|
|
219
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
|
|
181
220
|
}
|
|
182
221
|
else {
|
|
183
222
|
throw error;
|
|
@@ -185,10 +224,10 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
185
224
|
}
|
|
186
225
|
}
|
|
187
226
|
else if (resolveModels && !mergeOptions?.catalog && isProdGatewayMode(operationalMode)) {
|
|
188
|
-
await
|
|
227
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
|
|
189
228
|
}
|
|
190
229
|
if (!merged.model) {
|
|
191
|
-
await
|
|
230
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'no_model_provided');
|
|
192
231
|
}
|
|
193
232
|
const maxTokensExplicitlySet = isMaxTokensExplicitlySet(request, config);
|
|
194
233
|
const optimixerWillPredict = config.optimixer?.enabled === true && !maxTokensExplicitlySet;
|
|
@@ -783,6 +822,46 @@ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
|
|
|
783
822
|
}
|
|
784
823
|
return out;
|
|
785
824
|
}
|
|
825
|
+
function mapRouterFallbackAttempts(attempts) {
|
|
826
|
+
return attempts.map((attempt) => ({
|
|
827
|
+
provider: String(attempt.provider),
|
|
828
|
+
...(attempt.model !== undefined ? { model: attempt.model } : {}),
|
|
829
|
+
...(attempt.httpStatus !== undefined ? { httpStatus: attempt.httpStatus } : {}),
|
|
830
|
+
error: attempt.error instanceof Error ? attempt.error.message : String(attempt.error),
|
|
831
|
+
...(attempt.responsePreview !== undefined ? { responsePreview: attempt.responsePreview } : {})
|
|
832
|
+
}));
|
|
833
|
+
}
|
|
834
|
+
function extractFallbackAttemptsFromError(error) {
|
|
835
|
+
if (error instanceof FallbackExhaustedError && error.attempts.length > 0) {
|
|
836
|
+
return mapRouterFallbackAttempts(error.attempts);
|
|
837
|
+
}
|
|
838
|
+
if (error != null && typeof error === 'object') {
|
|
839
|
+
const record = error;
|
|
840
|
+
if (record.name === 'FallbackExhaustedError' && Array.isArray(record.attempts) && record.attempts.length > 0) {
|
|
841
|
+
return mapRouterFallbackAttempts(record.attempts);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return undefined;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
|
|
848
|
+
*/
|
|
849
|
+
export function tryExtractFallbackAttemptsFromErrorChain(error, maxDepth = 8) {
|
|
850
|
+
const seen = new Set();
|
|
851
|
+
let cur = error;
|
|
852
|
+
for (let i = 0; i < maxDepth && cur != null; i++) {
|
|
853
|
+
if (typeof cur !== 'object')
|
|
854
|
+
break;
|
|
855
|
+
if (seen.has(cur))
|
|
856
|
+
break;
|
|
857
|
+
seen.add(cur);
|
|
858
|
+
const attempts = extractFallbackAttemptsFromError(cur);
|
|
859
|
+
if (attempts?.length)
|
|
860
|
+
return attempts;
|
|
861
|
+
cur = cur.cause;
|
|
862
|
+
}
|
|
863
|
+
return undefined;
|
|
864
|
+
}
|
|
786
865
|
export function buildInvokeRejectionMetadata(args) {
|
|
787
866
|
const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
|
|
788
867
|
const partial = args.partialRouterPayload;
|
|
@@ -796,6 +875,9 @@ export function buildInvokeRejectionMetadata(args) {
|
|
|
796
875
|
tokens = undefined;
|
|
797
876
|
}
|
|
798
877
|
const requestIds = pickRequestIdsFromRouterLike(gid, partial);
|
|
878
|
+
const fallbackAttempts = args.error !== undefined
|
|
879
|
+
? tryExtractFallbackAttemptsFromErrorChain(args.error)
|
|
880
|
+
: undefined;
|
|
799
881
|
return {
|
|
800
882
|
aiRequestId: args.request.aiRequestId,
|
|
801
883
|
identity: args.request.identity,
|
|
@@ -805,6 +887,7 @@ export function buildInvokeRejectionMetadata(args) {
|
|
|
805
887
|
...(effective !== undefined ? { effectiveModelConfig: effective } : {}),
|
|
806
888
|
...(tokens !== undefined ? { tokens } : {}),
|
|
807
889
|
...(requestIds !== undefined ? { requestIds } : {}),
|
|
890
|
+
...(fallbackAttempts !== undefined ? { fallbackAttempts } : {}),
|
|
808
891
|
...(mc === undefined ? { mergeConfigUnavailable: true } : {})
|
|
809
892
|
};
|
|
810
893
|
}
|
package/dist/gateway.js
CHANGED
|
@@ -17,7 +17,7 @@ import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
|
17
17
|
import { invokeWithRetry } from './gateway-retry.js';
|
|
18
18
|
/** Error message thrown by the router when no provider is registered or specified */
|
|
19
19
|
const NO_PROVIDER_ERROR = 'No provider specified and no providers registered';
|
|
20
|
-
const NO_PROVIDER_HINT = ' Set
|
|
20
|
+
const NO_PROVIDER_HINT = ' Set OPENROUTER_API_KEY in the environment to use OpenRouter (legacy OPEN_ROUTER_KEY is still read as fallback), or register a provider with the router (e.g. via autoRegisterProviders or gateway config).';
|
|
21
21
|
/** Warn when a successful call reports no tokens and/or explicit zero cost (often missing adapter metadata). */
|
|
22
22
|
function warnIfSuccessfulInvokeReportsZeroUsageOrCost(logger, identity, meta, invokeKind) {
|
|
23
23
|
const { tokens, costUsd, cost } = meta;
|
|
@@ -685,7 +685,8 @@ export class AIGateway {
|
|
|
685
685
|
startTime,
|
|
686
686
|
mergedConfig,
|
|
687
687
|
partialRouterPayload: partial,
|
|
688
|
-
gatewayAiRequestId: request.aiRequestId
|
|
688
|
+
gatewayAiRequestId: request.aiRequestId,
|
|
689
|
+
error: err
|
|
689
690
|
});
|
|
690
691
|
attachGatewayInvokeRejectionMetadata(err, rejectMeta);
|
|
691
692
|
if (err.message.includes(NO_PROVIDER_ERROR)) {
|
package/dist/index.d.ts
CHANGED
|
@@ -16,8 +16,8 @@ export * from '@x12i/ai-providers-router';
|
|
|
16
16
|
export { AIGateway } from './gateway.js';
|
|
17
17
|
export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
|
|
18
18
|
export { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
19
|
-
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
|
|
20
|
-
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
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 } from './gateway-utils.js';
|
|
21
21
|
export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
|
|
22
22
|
export type { GatewayOperationalMode, GatewayDefaultModelSource, DefaultModelSubstitutionReason, ResolvedGatewayDefault } from './gateway-mode.js';
|
|
23
23
|
export type { ActivityCostStatus, ResolvedActivityCost } from './gateway-utils.js';
|
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 } from './instruction-errors.js';
|
|
19
19
|
export { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
20
|
-
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
20
|
+
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
21
21
|
export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
|
|
22
22
|
export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
23
23
|
export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -138,12 +138,25 @@ export type GatewayInvokeRejectionMetadata = {
|
|
|
138
138
|
region?: string;
|
|
139
139
|
effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
|
|
140
140
|
requestIds?: GatewayTraceRequestIds;
|
|
141
|
+
/**
|
|
142
|
+
* Fallback candidates tried before exhaustion (non-trace {@link AIGateway.invoke} only).
|
|
143
|
+
* Sourced from {@link FallbackExhaustedError.attempts} on the router error chain.
|
|
144
|
+
*/
|
|
145
|
+
fallbackAttempts?: GatewayFallbackAttempt[];
|
|
141
146
|
/**
|
|
142
147
|
* True when {@link mergeConfig} did not run (e.g. message-building threw first).
|
|
143
148
|
* Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
|
|
144
149
|
*/
|
|
145
150
|
mergeConfigUnavailable?: true;
|
|
146
151
|
};
|
|
152
|
+
/** Serializable slice of a router fallback attempt for rejection metadata. */
|
|
153
|
+
export type GatewayFallbackAttempt = {
|
|
154
|
+
provider: string;
|
|
155
|
+
model?: string;
|
|
156
|
+
httpStatus?: number;
|
|
157
|
+
error: string;
|
|
158
|
+
responsePreview?: string;
|
|
159
|
+
};
|
|
147
160
|
/**
|
|
148
161
|
* Identity object used for activity linkage.
|
|
149
162
|
* On gateway requests/responses it lives on `identity`. When activity tracking persists via Activix v5+,
|
|
@@ -197,7 +197,7 @@ export function initializeGatewayComponents(config) {
|
|
|
197
197
|
// Prefer explicit config from consumer (e.g. ai-skills) to avoid env-loading timing; fall back to process.env.
|
|
198
198
|
const explicitOpenRouterKey = config.openrouter?.apiKey;
|
|
199
199
|
const isExplicitKey = typeof explicitOpenRouterKey === 'string' && !explicitOpenRouterKey.startsWith('ENV.');
|
|
200
|
-
const openRouterKey = isExplicitKey ? explicitOpenRouterKey : (process.env.
|
|
200
|
+
const openRouterKey = isExplicitKey ? explicitOpenRouterKey : (process.env.OPENROUTER_API_KEY ?? process.env.OPEN_ROUTER_KEY);
|
|
201
201
|
const useOpenRouter = config.openRouter?.enabled !== undefined ? config.openRouter?.enabled : process.env.USE_OPENROUTER;
|
|
202
202
|
if (openRouterKey && useOpenRouter !== false && useOpenRouter !== 'false') {
|
|
203
203
|
routerConfig.openRouter = { enabled: true };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Gateway Meta Operations Module
|
|
3
3
|
* Handles meta operations like instruction optimization and testing
|
|
4
4
|
*/
|
|
5
|
+
import { CODE_DEFAULT_MODEL } from './gateway-mode.js';
|
|
5
6
|
/**
|
|
6
7
|
* Test instructions by running them and analyzing the response
|
|
7
8
|
*/
|
|
@@ -9,7 +10,7 @@ export async function testInstructions(instructions, testInput, expectedSchema,
|
|
|
9
10
|
// Get internal system action config (instruction audit)
|
|
10
11
|
const internalConfig = config.internalSystemActions?.instructionAudit;
|
|
11
12
|
const defaultEngine = config.defaultEngine || 'openai';
|
|
12
|
-
const defaultModel = internalConfig?.model ||
|
|
13
|
+
const defaultModel = internalConfig?.model || CODE_DEFAULT_MODEL;
|
|
13
14
|
const defaultProvider = internalConfig?.engine || defaultEngine;
|
|
14
15
|
const { agentId = 'instruction-tester', model = options.model || defaultModel, // Use internal config default if not provided
|
|
15
16
|
provider = options.provider || defaultProvider // Use internal config default if not provided
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Gateway operational mode (prod vs dev/debug) and default model resolution.
|
|
3
3
|
*/
|
|
4
4
|
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
5
|
-
|
|
5
|
+
/** Profile name resolved via ai-tools + {@link @x12i/ai-profiles} when catalog is enabled. */
|
|
6
|
+
export const CODE_DEFAULT_MODEL = 'cheap';
|
|
6
7
|
/**
|
|
7
8
|
* Operational mode: `GatewayConfig.mode` overrides `process.env.mode` / `MODE`.
|
|
8
9
|
* Only `prod` allows silent default-model substitution; all other values are strict.
|
|
@@ -6,7 +6,8 @@ import type { ActivityIdentity, GatewayConfig } from './types.js';
|
|
|
6
6
|
export type GatewayOperationalMode = 'prod' | 'debug' | 'dev';
|
|
7
7
|
export type GatewayDefaultModelSource = 'env' | 'model-config.json' | 'code';
|
|
8
8
|
export type DefaultModelSubstitutionReason = 'no_model_provided' | 'model_resolution_failed' | 'ai_tools_unavailable';
|
|
9
|
-
|
|
9
|
+
/** Profile name resolved via ai-tools + {@link @x12i/ai-profiles} when catalog is enabled. */
|
|
10
|
+
export declare const CODE_DEFAULT_MODEL = "cheap";
|
|
10
11
|
export type ResolvedGatewayDefault = {
|
|
11
12
|
model: string;
|
|
12
13
|
provider?: string;
|
|
@@ -146,7 +146,7 @@ export async function autoRegisterProviders(router, logger) {
|
|
|
146
146
|
optionalEnvVars: PROVIDER_CONFIGS
|
|
147
147
|
.filter(p => p.optional)
|
|
148
148
|
.map(p => p.envVar),
|
|
149
|
-
openRouter: 'Set
|
|
149
|
+
openRouter: 'Set OPENROUTER_API_KEY (and do not set USE_OPENROUTER=false) to use OpenRouter without registering a provider. Legacy OPEN_ROUTER_KEY is still accepted.',
|
|
150
150
|
note: 'You can still manually register providers using gateway.register(provider)'
|
|
151
151
|
});
|
|
152
152
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Handles utility functions
|
|
4
4
|
*/
|
|
5
5
|
import * as crypto from 'crypto';
|
|
6
|
+
import { FallbackExhaustedError } from '@x12i/ai-providers-router';
|
|
6
7
|
import { ModelResolutionError } from '@x12i/ai-tools';
|
|
7
8
|
import { getPreParsedInstructions } from './gateway-instructions.js';
|
|
8
9
|
import { getModelMaxTokensFromFlexMd } from './flex-md-loader.js';
|
|
@@ -55,6 +56,44 @@ async function substituteGatewayDefaultModel(merged, request, config, logger, me
|
|
|
55
56
|
});
|
|
56
57
|
applyGatewayDefaultToMerged(merged, defaults, config);
|
|
57
58
|
}
|
|
59
|
+
async function tryResolveSubstitutedDefaultModel(merged, request, config, logger, mergeOptions, original) {
|
|
60
|
+
const resolveModels = config.aiTools?.resolveModels !== false;
|
|
61
|
+
const catalog = mergeOptions?.catalog;
|
|
62
|
+
if (!resolveModels || !catalog || !merged.model) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const resolution = await catalog.resolveModel({
|
|
67
|
+
provider: merged.provider,
|
|
68
|
+
model: merged.model
|
|
69
|
+
});
|
|
70
|
+
if (!resolution.found) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
applyModelResolution(merged, resolution, config.defaultEngine);
|
|
74
|
+
request._modelResolution = {
|
|
75
|
+
modelId: resolution.modelId,
|
|
76
|
+
routedViaOpenRouter: resolution.routedViaOpenRouter,
|
|
77
|
+
confidence: resolution.confidence,
|
|
78
|
+
resolvedVia: resolution.resolvedVia,
|
|
79
|
+
originalProvider: original?.provider ?? merged.provider,
|
|
80
|
+
originalModel: original?.model ?? merged.model
|
|
81
|
+
};
|
|
82
|
+
logger.verbose('Catalog resolved substituted default model', {
|
|
83
|
+
jobId: request.identity.jobId,
|
|
84
|
+
model: merged.model,
|
|
85
|
+
provider: merged.provider,
|
|
86
|
+
resolvedModelId: resolution.modelId
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Prod keeps the substituted bare default when re-resolution fails.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, reason, original) {
|
|
94
|
+
await substituteGatewayDefaultModel(merged, request, config, logger, mergeOptions, reason, original);
|
|
95
|
+
await tryResolveSubstitutedDefaultModel(merged, request, config, logger, mergeOptions, original);
|
|
96
|
+
}
|
|
58
97
|
/**
|
|
59
98
|
* True when any caller-controlled config source set `maxTokens` (Optimixer should not override).
|
|
60
99
|
*/
|
|
@@ -137,7 +176,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
137
176
|
const originalProvider = merged.provider;
|
|
138
177
|
const originalModel = explicitModel;
|
|
139
178
|
if (!explicitModel) {
|
|
140
|
-
await
|
|
179
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'no_model_provided');
|
|
141
180
|
}
|
|
142
181
|
else if (resolveModels && mergeOptions?.catalog) {
|
|
143
182
|
try {
|
|
@@ -166,7 +205,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
166
205
|
});
|
|
167
206
|
}
|
|
168
207
|
else if (isProdGatewayMode(operationalMode)) {
|
|
169
|
-
await
|
|
208
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'model_resolution_failed', { provider: originalProvider, model: originalModel });
|
|
170
209
|
}
|
|
171
210
|
else {
|
|
172
211
|
throw new ModelResolutionError({ provider: merged.provider, model: explicitModel }, resolution);
|
|
@@ -177,7 +216,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
177
216
|
throw error;
|
|
178
217
|
}
|
|
179
218
|
if (isProdGatewayMode(operationalMode)) {
|
|
180
|
-
await
|
|
219
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
|
|
181
220
|
}
|
|
182
221
|
else {
|
|
183
222
|
throw error;
|
|
@@ -185,10 +224,10 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
185
224
|
}
|
|
186
225
|
}
|
|
187
226
|
else if (resolveModels && !mergeOptions?.catalog && isProdGatewayMode(operationalMode)) {
|
|
188
|
-
await
|
|
227
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'ai_tools_unavailable', { provider: originalProvider, model: originalModel });
|
|
189
228
|
}
|
|
190
229
|
if (!merged.model) {
|
|
191
|
-
await
|
|
230
|
+
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'no_model_provided');
|
|
192
231
|
}
|
|
193
232
|
const maxTokensExplicitlySet = isMaxTokensExplicitlySet(request, config);
|
|
194
233
|
const optimixerWillPredict = config.optimixer?.enabled === true && !maxTokensExplicitlySet;
|
|
@@ -783,6 +822,46 @@ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
|
|
|
783
822
|
}
|
|
784
823
|
return out;
|
|
785
824
|
}
|
|
825
|
+
function mapRouterFallbackAttempts(attempts) {
|
|
826
|
+
return attempts.map((attempt) => ({
|
|
827
|
+
provider: String(attempt.provider),
|
|
828
|
+
...(attempt.model !== undefined ? { model: attempt.model } : {}),
|
|
829
|
+
...(attempt.httpStatus !== undefined ? { httpStatus: attempt.httpStatus } : {}),
|
|
830
|
+
error: attempt.error instanceof Error ? attempt.error.message : String(attempt.error),
|
|
831
|
+
...(attempt.responsePreview !== undefined ? { responsePreview: attempt.responsePreview } : {})
|
|
832
|
+
}));
|
|
833
|
+
}
|
|
834
|
+
function extractFallbackAttemptsFromError(error) {
|
|
835
|
+
if (error instanceof FallbackExhaustedError && error.attempts.length > 0) {
|
|
836
|
+
return mapRouterFallbackAttempts(error.attempts);
|
|
837
|
+
}
|
|
838
|
+
if (error != null && typeof error === 'object') {
|
|
839
|
+
const record = error;
|
|
840
|
+
if (record.name === 'FallbackExhaustedError' && Array.isArray(record.attempts) && record.attempts.length > 0) {
|
|
841
|
+
return mapRouterFallbackAttempts(record.attempts);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return undefined;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
|
|
848
|
+
*/
|
|
849
|
+
export function tryExtractFallbackAttemptsFromErrorChain(error, maxDepth = 8) {
|
|
850
|
+
const seen = new Set();
|
|
851
|
+
let cur = error;
|
|
852
|
+
for (let i = 0; i < maxDepth && cur != null; i++) {
|
|
853
|
+
if (typeof cur !== 'object')
|
|
854
|
+
break;
|
|
855
|
+
if (seen.has(cur))
|
|
856
|
+
break;
|
|
857
|
+
seen.add(cur);
|
|
858
|
+
const attempts = extractFallbackAttemptsFromError(cur);
|
|
859
|
+
if (attempts?.length)
|
|
860
|
+
return attempts;
|
|
861
|
+
cur = cur.cause;
|
|
862
|
+
}
|
|
863
|
+
return undefined;
|
|
864
|
+
}
|
|
786
865
|
export function buildInvokeRejectionMetadata(args) {
|
|
787
866
|
const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
|
|
788
867
|
const partial = args.partialRouterPayload;
|
|
@@ -796,6 +875,9 @@ export function buildInvokeRejectionMetadata(args) {
|
|
|
796
875
|
tokens = undefined;
|
|
797
876
|
}
|
|
798
877
|
const requestIds = pickRequestIdsFromRouterLike(gid, partial);
|
|
878
|
+
const fallbackAttempts = args.error !== undefined
|
|
879
|
+
? tryExtractFallbackAttemptsFromErrorChain(args.error)
|
|
880
|
+
: undefined;
|
|
799
881
|
return {
|
|
800
882
|
aiRequestId: args.request.aiRequestId,
|
|
801
883
|
identity: args.request.identity,
|
|
@@ -805,6 +887,7 @@ export function buildInvokeRejectionMetadata(args) {
|
|
|
805
887
|
...(effective !== undefined ? { effectiveModelConfig: effective } : {}),
|
|
806
888
|
...(tokens !== undefined ? { tokens } : {}),
|
|
807
889
|
...(requestIds !== undefined ? { requestIds } : {}),
|
|
890
|
+
...(fallbackAttempts !== undefined ? { fallbackAttempts } : {}),
|
|
808
891
|
...(mc === undefined ? { mergeConfigUnavailable: true } : {})
|
|
809
892
|
};
|
|
810
893
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Gateway Utilities Module
|
|
3
3
|
* Handles utility functions
|
|
4
4
|
*/
|
|
5
|
-
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
|
|
5
|
+
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayFallbackAttempt, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
7
|
import { type AiCostResult, type AiModelsCatalogClient, type CostCalculator } from '@x12i/ai-tools';
|
|
8
8
|
/**
|
|
@@ -155,6 +155,10 @@ export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<
|
|
|
155
155
|
*/
|
|
156
156
|
export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
|
|
157
157
|
export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
|
|
160
|
+
*/
|
|
161
|
+
export declare function tryExtractFallbackAttemptsFromErrorChain(error: unknown, maxDepth?: number): GatewayFallbackAttempt[] | undefined;
|
|
158
162
|
export declare function buildInvokeRejectionMetadata(args: {
|
|
159
163
|
request: Pick<AIInvokeRequest, 'aiRequestId' | 'identity' | 'config' | 'modelConfig'>;
|
|
160
164
|
taskTypeId: string;
|
|
@@ -162,6 +166,7 @@ export declare function buildInvokeRejectionMetadata(args: {
|
|
|
162
166
|
mergedConfig?: unknown;
|
|
163
167
|
partialRouterPayload?: unknown;
|
|
164
168
|
gatewayAiRequestId?: string;
|
|
169
|
+
error?: unknown;
|
|
165
170
|
}): GatewayInvokeRejectionMetadata;
|
|
166
171
|
export declare function attachGatewayInvokeRejectionMetadata(err: Error, metadata: GatewayInvokeRejectionMetadata): void;
|
|
167
172
|
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
package/dist-cjs/gateway.cjs
CHANGED
|
@@ -17,7 +17,7 @@ import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
|
17
17
|
import { invokeWithRetry } from './gateway-retry.js';
|
|
18
18
|
/** Error message thrown by the router when no provider is registered or specified */
|
|
19
19
|
const NO_PROVIDER_ERROR = 'No provider specified and no providers registered';
|
|
20
|
-
const NO_PROVIDER_HINT = ' Set
|
|
20
|
+
const NO_PROVIDER_HINT = ' Set OPENROUTER_API_KEY in the environment to use OpenRouter (legacy OPEN_ROUTER_KEY is still read as fallback), or register a provider with the router (e.g. via autoRegisterProviders or gateway config).';
|
|
21
21
|
/** Warn when a successful call reports no tokens and/or explicit zero cost (often missing adapter metadata). */
|
|
22
22
|
function warnIfSuccessfulInvokeReportsZeroUsageOrCost(logger, identity, meta, invokeKind) {
|
|
23
23
|
const { tokens, costUsd, cost } = meta;
|
|
@@ -685,7 +685,8 @@ export class AIGateway {
|
|
|
685
685
|
startTime,
|
|
686
686
|
mergedConfig,
|
|
687
687
|
partialRouterPayload: partial,
|
|
688
|
-
gatewayAiRequestId: request.aiRequestId
|
|
688
|
+
gatewayAiRequestId: request.aiRequestId,
|
|
689
|
+
error: err
|
|
689
690
|
});
|
|
690
691
|
attachGatewayInvokeRejectionMetadata(err, rejectMeta);
|
|
691
692
|
if (err.message.includes(NO_PROVIDER_ERROR)) {
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -17,7 +17,7 @@ export * from '@x12i/ai-providers-router';
|
|
|
17
17
|
export { AIGateway } from './gateway.js';
|
|
18
18
|
export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
|
|
19
19
|
export { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
20
|
-
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
20
|
+
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
21
21
|
export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
|
|
22
22
|
export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
23
23
|
export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
|
package/dist-cjs/index.d.ts
CHANGED
|
@@ -16,8 +16,8 @@ export * from '@x12i/ai-providers-router';
|
|
|
16
16
|
export { AIGateway } from './gateway.js';
|
|
17
17
|
export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
|
|
18
18
|
export { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
19
|
-
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
|
|
20
|
-
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
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 } from './gateway-utils.js';
|
|
21
21
|
export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
|
|
22
22
|
export type { GatewayOperationalMode, GatewayDefaultModelSource, DefaultModelSubstitutionReason, ResolvedGatewayDefault } from './gateway-mode.js';
|
|
23
23
|
export type { ActivityCostStatus, ResolvedActivityCost } from './gateway-utils.js';
|
package/dist-cjs/types.d.ts
CHANGED
|
@@ -138,12 +138,25 @@ export type GatewayInvokeRejectionMetadata = {
|
|
|
138
138
|
region?: string;
|
|
139
139
|
effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
|
|
140
140
|
requestIds?: GatewayTraceRequestIds;
|
|
141
|
+
/**
|
|
142
|
+
* Fallback candidates tried before exhaustion (non-trace {@link AIGateway.invoke} only).
|
|
143
|
+
* Sourced from {@link FallbackExhaustedError.attempts} on the router error chain.
|
|
144
|
+
*/
|
|
145
|
+
fallbackAttempts?: GatewayFallbackAttempt[];
|
|
141
146
|
/**
|
|
142
147
|
* True when {@link mergeConfig} did not run (e.g. message-building threw first).
|
|
143
148
|
* Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
|
|
144
149
|
*/
|
|
145
150
|
mergeConfigUnavailable?: true;
|
|
146
151
|
};
|
|
152
|
+
/** Serializable slice of a router fallback attempt for rejection metadata. */
|
|
153
|
+
export type GatewayFallbackAttempt = {
|
|
154
|
+
provider: string;
|
|
155
|
+
model?: string;
|
|
156
|
+
httpStatus?: number;
|
|
157
|
+
error: string;
|
|
158
|
+
responsePreview?: string;
|
|
159
|
+
};
|
|
147
160
|
/**
|
|
148
161
|
* Identity object used for activity linkage.
|
|
149
162
|
* On gateway requests/responses it lives on `identity`. When activity tracking persists via Activix v5+,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x12i/ai-gateway",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.6.0",
|
|
4
4
|
"description": "AI Gateway - Unified interface for LLM provider routing and management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"author": "x12i",
|
|
42
42
|
"license": "mit",
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@x12i/activix": "^8.0.
|
|
45
|
-
"@x12i/ai-providers-router": "^4.8.
|
|
46
|
-
"@x12i/ai-tools": "^2.0.
|
|
44
|
+
"@x12i/activix": "^8.0.5",
|
|
45
|
+
"@x12i/ai-providers-router": "^4.8.5",
|
|
46
|
+
"@x12i/ai-tools": "^2.0.4",
|
|
47
47
|
"@x12i/flex-md": "^4.8.0",
|
|
48
48
|
"@x12i/logxer": "^4.3.5",
|
|
49
49
|
"@x12i/optimixer": "^0.1.0",
|