@x12i/ai-gateway 9.6.0 → 9.6.2
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/dist/gateway-utils.d.ts +29 -0
- package/dist/gateway-utils.js +120 -2
- package/dist/gateway.js +21 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/optimixer-manager.js +11 -7
- package/dist-cjs/gateway-utils.cjs +120 -2
- package/dist-cjs/gateway-utils.d.ts +29 -0
- package/dist-cjs/gateway.cjs +21 -3
- package/dist-cjs/index.cjs +1 -1
- package/dist-cjs/index.d.ts +1 -1
- package/dist-cjs/optimixer-manager.cjs +11 -7
- package/package.json +4 -4
package/dist/gateway-utils.d.ts
CHANGED
|
@@ -155,6 +155,35 @@ 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
|
+
/** Error code hint when a bundled profile name cannot be routed to a catalog target. */
|
|
159
|
+
export declare const MODEL_PROFILE_UNROUTABLE = "MODEL_PROFILE_UNROUTABLE";
|
|
160
|
+
export declare class ModelProfileUnroutableError extends Error {
|
|
161
|
+
readonly profileAlias: string;
|
|
162
|
+
readonly provider: string | undefined;
|
|
163
|
+
readonly code = "MODEL_PROFILE_UNROUTABLE";
|
|
164
|
+
constructor(profileAlias: string, provider: string | undefined, cause?: unknown);
|
|
165
|
+
}
|
|
166
|
+
type ModelResolutionCandidate = {
|
|
167
|
+
provider: string;
|
|
168
|
+
model: string;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Build rejection-metadata fallback attempts from trace-mode {@link GatewayTraceAttempt}s.
|
|
172
|
+
*/
|
|
173
|
+
export declare function buildGatewayFallbackAttemptsFromTrace(traceAttempts: GatewayTraceAttempt[], candidates: ModelResolutionCandidate[], lastError?: Error): GatewayFallbackAttempt[];
|
|
174
|
+
/** Human-readable exhaustion message for trace fallback chains and rejection logs. */
|
|
175
|
+
export declare function formatFallbackExhaustionMessage(attempts: GatewayFallbackAttempt[], candidates: ModelResolutionCandidate[]): string;
|
|
176
|
+
export declare function mapGatewayFallbackAttemptsToRouter(attempts: GatewayFallbackAttempt[]): Array<{
|
|
177
|
+
provider: string;
|
|
178
|
+
model?: string;
|
|
179
|
+
httpStatus?: number;
|
|
180
|
+
error: Error;
|
|
181
|
+
responsePreview?: string;
|
|
182
|
+
}>;
|
|
183
|
+
/**
|
|
184
|
+
* Log profile alias vs OpenRouter model id actually sent to the router after catalog resolution.
|
|
185
|
+
*/
|
|
186
|
+
export declare function logResolvedModelRouting(logger: Logxer, request: ChatRequest, mergedConfig: ChatRequest['config']): void;
|
|
158
187
|
/**
|
|
159
188
|
* Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
|
|
160
189
|
*/
|
package/dist/gateway-utils.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as crypto from 'crypto';
|
|
6
6
|
import { FallbackExhaustedError } from '@x12i/ai-providers-router';
|
|
7
|
-
import { ModelResolutionError } from '@x12i/ai-tools';
|
|
7
|
+
import { ModelResolutionError, isKnownProfileOrShortcut } from '@x12i/ai-tools';
|
|
8
|
+
import { extractHttpStatusCode } from './gateway-retry.js';
|
|
9
|
+
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
8
10
|
import { getPreParsedInstructions } from './gateway-instructions.js';
|
|
9
11
|
import { getModelMaxTokensFromFlexMd } from './flex-md-loader.js';
|
|
10
12
|
import { applyModelResolution } from './ai-tools-client.js';
|
|
@@ -208,7 +210,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
208
210
|
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'model_resolution_failed', { provider: originalProvider, model: originalModel });
|
|
209
211
|
}
|
|
210
212
|
else {
|
|
211
|
-
throw
|
|
213
|
+
throw buildModelResolutionFailureError(explicitModel, merged.provider, resolution);
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
216
|
catch (error) {
|
|
@@ -822,6 +824,122 @@ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
|
|
|
822
824
|
}
|
|
823
825
|
return out;
|
|
824
826
|
}
|
|
827
|
+
/** Error code hint when a bundled profile name cannot be routed to a catalog target. */
|
|
828
|
+
export const MODEL_PROFILE_UNROUTABLE = 'MODEL_PROFILE_UNROUTABLE';
|
|
829
|
+
export class ModelProfileUnroutableError extends Error {
|
|
830
|
+
profileAlias;
|
|
831
|
+
provider;
|
|
832
|
+
code = MODEL_PROFILE_UNROUTABLE;
|
|
833
|
+
constructor(profileAlias, provider, cause) {
|
|
834
|
+
super(`${MODEL_PROFILE_UNROUTABLE}: profile "${profileAlias}" is retired or has no routable catalog target` +
|
|
835
|
+
(provider ? ` (provider: "${provider}")` : '') +
|
|
836
|
+
'. Update @x12i/ai-profiles or choose another profile alias.');
|
|
837
|
+
this.profileAlias = profileAlias;
|
|
838
|
+
this.provider = provider;
|
|
839
|
+
this.name = 'ModelProfileUnroutableError';
|
|
840
|
+
if (cause !== undefined) {
|
|
841
|
+
this.cause = cause;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function buildModelResolutionFailureError(explicitModel, provider, resolution) {
|
|
846
|
+
const base = new ModelResolutionError({ provider, model: explicitModel }, resolution);
|
|
847
|
+
if (isKnownProfileOrShortcut(explicitModel)) {
|
|
848
|
+
return new ModelProfileUnroutableError(explicitModel, provider, base);
|
|
849
|
+
}
|
|
850
|
+
return base;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Build rejection-metadata fallback attempts from trace-mode {@link GatewayTraceAttempt}s.
|
|
854
|
+
*/
|
|
855
|
+
export function buildGatewayFallbackAttemptsFromTrace(traceAttempts, candidates, lastError) {
|
|
856
|
+
const lastFailedByIndex = new Map();
|
|
857
|
+
for (const attempt of traceAttempts) {
|
|
858
|
+
if (!attempt.ok) {
|
|
859
|
+
lastFailedByIndex.set(attempt.routing.fallbackIndex, attempt);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return candidates.map((candidate, index) => {
|
|
863
|
+
const failed = lastFailedByIndex.get(index);
|
|
864
|
+
const errMsg = failed?.error?.message ??
|
|
865
|
+
(index === candidates.length - 1 && lastError ? lastError.message : 'invoke failed');
|
|
866
|
+
const httpStatus = extractHttpStatusCode(new Error(errMsg));
|
|
867
|
+
let responsePreview;
|
|
868
|
+
const raw = failed?.rawProviderPayload;
|
|
869
|
+
if (raw !== undefined) {
|
|
870
|
+
try {
|
|
871
|
+
const rawStr = typeof raw === 'string' ? raw : JSON.stringify(raw);
|
|
872
|
+
responsePreview = rawStr.length <= 500 ? rawStr : rawStr.slice(0, 500) + '…';
|
|
873
|
+
}
|
|
874
|
+
catch {
|
|
875
|
+
responsePreview = '[Unserializable]';
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
provider: candidate.provider,
|
|
880
|
+
model: candidate.model,
|
|
881
|
+
...(httpStatus !== undefined ? { httpStatus } : {}),
|
|
882
|
+
error: errMsg,
|
|
883
|
+
...(responsePreview !== undefined ? { responsePreview } : {})
|
|
884
|
+
};
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
/** Human-readable exhaustion message for trace fallback chains and rejection logs. */
|
|
888
|
+
export function formatFallbackExhaustionMessage(attempts, candidates) {
|
|
889
|
+
const providersTried = [...new Set(candidates.map((c) => c.provider))];
|
|
890
|
+
const providerNote = providersTried.length > 1
|
|
891
|
+
? `; providers tried: ${providersTried.join(' → ')}`
|
|
892
|
+
: providersTried.length === 1
|
|
893
|
+
? `; provider: ${providersTried[0]}`
|
|
894
|
+
: '';
|
|
895
|
+
const detail = attempts
|
|
896
|
+
.map((a) => {
|
|
897
|
+
const model = a.model ? `${a.provider}/${a.model}` : a.provider;
|
|
898
|
+
const status = a.httpStatus !== undefined ? ` HTTP ${a.httpStatus}` : '';
|
|
899
|
+
const preview = a.responsePreview ? ` body=${a.responsePreview}` : '';
|
|
900
|
+
return `[${model}${status}] ${a.error}${preview}`;
|
|
901
|
+
})
|
|
902
|
+
.join('; ');
|
|
903
|
+
const last = attempts[attempts.length - 1];
|
|
904
|
+
const lastBody = last?.responsePreview && !detail.includes(last.responsePreview)
|
|
905
|
+
? ` Last response preview: ${last.responsePreview}`
|
|
906
|
+
: '';
|
|
907
|
+
return (`All fallback candidates failed (${candidates.length} tried${providerNote}). ` +
|
|
908
|
+
`Attempts: ${detail || 'no attempt details recorded'}.${lastBody}`);
|
|
909
|
+
}
|
|
910
|
+
export function mapGatewayFallbackAttemptsToRouter(attempts) {
|
|
911
|
+
return attempts.map((a) => ({
|
|
912
|
+
provider: a.provider,
|
|
913
|
+
model: a.model,
|
|
914
|
+
httpStatus: a.httpStatus,
|
|
915
|
+
error: new Error(a.error),
|
|
916
|
+
responsePreview: a.responsePreview
|
|
917
|
+
}));
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Log profile alias vs OpenRouter model id actually sent to the router after catalog resolution.
|
|
921
|
+
*/
|
|
922
|
+
export function logResolvedModelRouting(logger, request, mergedConfig) {
|
|
923
|
+
const res = request._modelResolution;
|
|
924
|
+
if (!res?.modelId && res?.originalModel === undefined) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const profileAlias = res.originalModel ?? mergedConfig?.model;
|
|
928
|
+
const invokedModelId = res.modelId ?? mergedConfig?.model;
|
|
929
|
+
const provider = mergedConfig?.provider;
|
|
930
|
+
const openRouterPath = res.routedViaOpenRouter === true || provider === 'openrouter';
|
|
931
|
+
if (!openRouterPath) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
logger.info('OpenRouter routing: profile alias resolved to model id for invoke', withActivityIdentity(request.identity, {
|
|
935
|
+
profileAlias,
|
|
936
|
+
invokedOpenRouterModelId: invokedModelId,
|
|
937
|
+
provider,
|
|
938
|
+
routedViaOpenRouter: res.routedViaOpenRouter,
|
|
939
|
+
resolvedVia: res.resolvedVia,
|
|
940
|
+
debugKind: gatewayLogDebug.trace
|
|
941
|
+
}));
|
|
942
|
+
}
|
|
825
943
|
function mapRouterFallbackAttempts(attempts) {
|
|
826
944
|
return attempts.map((attempt) => ({
|
|
827
945
|
provider: String(attempt.provider),
|
package/dist/gateway.js
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Simplified AI Gateway - Clean proxy implementation
|
|
5
5
|
*/
|
|
6
|
+
import { FallbackExhaustedError } from '@x12i/ai-providers-router';
|
|
6
7
|
import { validateChatRequest, validateAIRequest } from './gateway-validation.js';
|
|
7
8
|
import { ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
8
9
|
import { initializeGatewayComponents } from './gateway-config.js';
|
|
9
10
|
import { buildMessages } from './message-builder.js';
|
|
10
11
|
import { extractJsonFromFlexMd, getModelMaxTokensFromFlexMd } from './flex-md-loader.js';
|
|
11
12
|
import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
12
|
-
import { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, buildOptimixerActualUsage, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, isMaxTokensExplicitlySet, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
|
|
13
|
+
import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter, hasNonZeroTokenUsage, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, buildOptimixerActualUsage, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, isMaxTokensExplicitlySet, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
|
|
13
14
|
import { getAiToolsClient } from './ai-tools-client.js';
|
|
14
15
|
import { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
15
16
|
import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
|
|
@@ -277,6 +278,7 @@ export class AIGateway {
|
|
|
277
278
|
catalog: aiTools?.catalog ?? null
|
|
278
279
|
});
|
|
279
280
|
request._mergedRouterConfig = mergedConfig;
|
|
281
|
+
logResolvedModelRouting(this.logger, request, mergedConfig);
|
|
280
282
|
const diagnosticsMode = request.diagnostics?.mode;
|
|
281
283
|
const traceEnabled = diagnosticsMode === 'trace';
|
|
282
284
|
const includeRawProviderPayload = request.diagnostics?.includeRawProviderPayload === true;
|
|
@@ -468,7 +470,20 @@ export class AIGateway {
|
|
|
468
470
|
}
|
|
469
471
|
}
|
|
470
472
|
if (!response) {
|
|
471
|
-
|
|
473
|
+
const fallbackAttempts = buildGatewayFallbackAttemptsFromTrace(traceAttempts, deduped, lastError);
|
|
474
|
+
const providersTried = [...new Set(deduped.map((c) => c.provider))];
|
|
475
|
+
this.logger.error('Trace fallback chain exhausted', withActivityIdentity(request.identity, {
|
|
476
|
+
providersTried,
|
|
477
|
+
candidates: deduped,
|
|
478
|
+
fallbackAttempts,
|
|
479
|
+
debugKind: gatewayLogDebug.anomaly
|
|
480
|
+
}));
|
|
481
|
+
const exhausted = new FallbackExhaustedError(mapGatewayFallbackAttemptsToRouter(fallbackAttempts));
|
|
482
|
+
exhausted.message = formatFallbackExhaustionMessage(fallbackAttempts, deduped);
|
|
483
|
+
if (lastError) {
|
|
484
|
+
exhausted.cause = lastError;
|
|
485
|
+
}
|
|
486
|
+
throw exhausted;
|
|
472
487
|
}
|
|
473
488
|
// Summary counts + final request ids.
|
|
474
489
|
traceRetryCount = traceAttempts.filter(a => a.routing.retryIndex > 0).length;
|
|
@@ -565,11 +580,14 @@ export class AIGateway {
|
|
|
565
580
|
tokens = second;
|
|
566
581
|
}
|
|
567
582
|
}
|
|
568
|
-
|
|
583
|
+
let costCompletion = await resolveCostCompletionWithAiTools(routerResponse, tokens, {
|
|
569
584
|
mergedConfig,
|
|
570
585
|
calculator: aiTools?.calculator ?? null,
|
|
571
586
|
calculateCost: this.config.aiTools?.calculateCost
|
|
572
587
|
});
|
|
588
|
+
if (!costCompletion.costStatus && hasNonZeroTokenUsage(tokens)) {
|
|
589
|
+
costCompletion = { ...costCompletion, costStatus: 'unpriced' };
|
|
590
|
+
}
|
|
573
591
|
const routerMetaForCost = routerResponse?.metadata || {};
|
|
574
592
|
const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
|
|
575
593
|
const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ 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
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';
|
|
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';
|
|
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, tryExtractFallbackAttemptsFromErrorChain, 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, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } 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';
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Optimixer } from '@x12i/optimixer';
|
|
2
2
|
import { resolveActivityTrackingConfig } from './config/activity-tracking-config.js';
|
|
3
3
|
import { estimateMessagesTokenSizes } from './token-estimate.js';
|
|
4
|
-
|
|
4
|
+
/** Optimixer bucket key: prefer taskTypeId (template), then identity actionType, else gateway default. */
|
|
5
|
+
function resolveTemplateId(request) {
|
|
6
|
+
if (request.taskTypeId && String(request.taskTypeId).trim()) {
|
|
7
|
+
return String(request.taskTypeId).trim();
|
|
8
|
+
}
|
|
5
9
|
const identity = request.identity;
|
|
6
10
|
if (identity?.actionType && String(identity.actionType).trim()) {
|
|
7
11
|
return String(identity.actionType).trim();
|
|
8
12
|
}
|
|
9
|
-
if (request.taskTypeId && String(request.taskTypeId).trim()) {
|
|
10
|
-
return String(request.taskTypeId).trim();
|
|
11
|
-
}
|
|
12
13
|
return 'gateway.invoke';
|
|
13
14
|
}
|
|
14
15
|
function toActivixRunContext(identity) {
|
|
@@ -77,15 +78,18 @@ export class OptimixerManager {
|
|
|
77
78
|
const { request, mergedConfig, messages } = ctx;
|
|
78
79
|
const { inputSize, contextSize } = estimateMessagesTokenSizes(messages);
|
|
79
80
|
const acceptableRisk = this.config?.acceptableRisk ?? 'medium';
|
|
81
|
+
const provider = typeof mergedConfig?.provider === 'string' ? mergedConfig.provider : undefined;
|
|
82
|
+
const model = typeof mergedConfig?.model === 'string' ? mergedConfig.model : undefined;
|
|
80
83
|
try {
|
|
81
84
|
return await optimixer.predictAiMaxTokens({
|
|
82
|
-
|
|
85
|
+
templateId: resolveTemplateId(request),
|
|
83
86
|
inputSize,
|
|
84
87
|
contextSize,
|
|
85
88
|
acceptableRisk,
|
|
86
89
|
runContext: toActivixRunContext(request.identity),
|
|
87
|
-
provider
|
|
88
|
-
|
|
90
|
+
...(provider || model
|
|
91
|
+
? { modelProfile: { ...(provider ? { provider } : {}), ...(model ? { model } : {}) } }
|
|
92
|
+
: {})
|
|
89
93
|
});
|
|
90
94
|
}
|
|
91
95
|
catch (error) {
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as crypto from 'crypto';
|
|
6
6
|
import { FallbackExhaustedError } from '@x12i/ai-providers-router';
|
|
7
|
-
import { ModelResolutionError } from '@x12i/ai-tools';
|
|
7
|
+
import { ModelResolutionError, isKnownProfileOrShortcut } from '@x12i/ai-tools';
|
|
8
|
+
import { extractHttpStatusCode } from './gateway-retry.js';
|
|
9
|
+
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
8
10
|
import { getPreParsedInstructions } from './gateway-instructions.js';
|
|
9
11
|
import { getModelMaxTokensFromFlexMd } from './flex-md-loader.js';
|
|
10
12
|
import { applyModelResolution } from './ai-tools-client.js';
|
|
@@ -208,7 +210,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
208
210
|
await substituteGatewayDefaultModelAndResolve(merged, request, config, logger, mergeOptions, 'model_resolution_failed', { provider: originalProvider, model: originalModel });
|
|
209
211
|
}
|
|
210
212
|
else {
|
|
211
|
-
throw
|
|
213
|
+
throw buildModelResolutionFailureError(explicitModel, merged.provider, resolution);
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
216
|
catch (error) {
|
|
@@ -822,6 +824,122 @@ export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
|
|
|
822
824
|
}
|
|
823
825
|
return out;
|
|
824
826
|
}
|
|
827
|
+
/** Error code hint when a bundled profile name cannot be routed to a catalog target. */
|
|
828
|
+
export const MODEL_PROFILE_UNROUTABLE = 'MODEL_PROFILE_UNROUTABLE';
|
|
829
|
+
export class ModelProfileUnroutableError extends Error {
|
|
830
|
+
profileAlias;
|
|
831
|
+
provider;
|
|
832
|
+
code = MODEL_PROFILE_UNROUTABLE;
|
|
833
|
+
constructor(profileAlias, provider, cause) {
|
|
834
|
+
super(`${MODEL_PROFILE_UNROUTABLE}: profile "${profileAlias}" is retired or has no routable catalog target` +
|
|
835
|
+
(provider ? ` (provider: "${provider}")` : '') +
|
|
836
|
+
'. Update @x12i/ai-profiles or choose another profile alias.');
|
|
837
|
+
this.profileAlias = profileAlias;
|
|
838
|
+
this.provider = provider;
|
|
839
|
+
this.name = 'ModelProfileUnroutableError';
|
|
840
|
+
if (cause !== undefined) {
|
|
841
|
+
this.cause = cause;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function buildModelResolutionFailureError(explicitModel, provider, resolution) {
|
|
846
|
+
const base = new ModelResolutionError({ provider, model: explicitModel }, resolution);
|
|
847
|
+
if (isKnownProfileOrShortcut(explicitModel)) {
|
|
848
|
+
return new ModelProfileUnroutableError(explicitModel, provider, base);
|
|
849
|
+
}
|
|
850
|
+
return base;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Build rejection-metadata fallback attempts from trace-mode {@link GatewayTraceAttempt}s.
|
|
854
|
+
*/
|
|
855
|
+
export function buildGatewayFallbackAttemptsFromTrace(traceAttempts, candidates, lastError) {
|
|
856
|
+
const lastFailedByIndex = new Map();
|
|
857
|
+
for (const attempt of traceAttempts) {
|
|
858
|
+
if (!attempt.ok) {
|
|
859
|
+
lastFailedByIndex.set(attempt.routing.fallbackIndex, attempt);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return candidates.map((candidate, index) => {
|
|
863
|
+
const failed = lastFailedByIndex.get(index);
|
|
864
|
+
const errMsg = failed?.error?.message ??
|
|
865
|
+
(index === candidates.length - 1 && lastError ? lastError.message : 'invoke failed');
|
|
866
|
+
const httpStatus = extractHttpStatusCode(new Error(errMsg));
|
|
867
|
+
let responsePreview;
|
|
868
|
+
const raw = failed?.rawProviderPayload;
|
|
869
|
+
if (raw !== undefined) {
|
|
870
|
+
try {
|
|
871
|
+
const rawStr = typeof raw === 'string' ? raw : JSON.stringify(raw);
|
|
872
|
+
responsePreview = rawStr.length <= 500 ? rawStr : rawStr.slice(0, 500) + '…';
|
|
873
|
+
}
|
|
874
|
+
catch {
|
|
875
|
+
responsePreview = '[Unserializable]';
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
provider: candidate.provider,
|
|
880
|
+
model: candidate.model,
|
|
881
|
+
...(httpStatus !== undefined ? { httpStatus } : {}),
|
|
882
|
+
error: errMsg,
|
|
883
|
+
...(responsePreview !== undefined ? { responsePreview } : {})
|
|
884
|
+
};
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
/** Human-readable exhaustion message for trace fallback chains and rejection logs. */
|
|
888
|
+
export function formatFallbackExhaustionMessage(attempts, candidates) {
|
|
889
|
+
const providersTried = [...new Set(candidates.map((c) => c.provider))];
|
|
890
|
+
const providerNote = providersTried.length > 1
|
|
891
|
+
? `; providers tried: ${providersTried.join(' → ')}`
|
|
892
|
+
: providersTried.length === 1
|
|
893
|
+
? `; provider: ${providersTried[0]}`
|
|
894
|
+
: '';
|
|
895
|
+
const detail = attempts
|
|
896
|
+
.map((a) => {
|
|
897
|
+
const model = a.model ? `${a.provider}/${a.model}` : a.provider;
|
|
898
|
+
const status = a.httpStatus !== undefined ? ` HTTP ${a.httpStatus}` : '';
|
|
899
|
+
const preview = a.responsePreview ? ` body=${a.responsePreview}` : '';
|
|
900
|
+
return `[${model}${status}] ${a.error}${preview}`;
|
|
901
|
+
})
|
|
902
|
+
.join('; ');
|
|
903
|
+
const last = attempts[attempts.length - 1];
|
|
904
|
+
const lastBody = last?.responsePreview && !detail.includes(last.responsePreview)
|
|
905
|
+
? ` Last response preview: ${last.responsePreview}`
|
|
906
|
+
: '';
|
|
907
|
+
return (`All fallback candidates failed (${candidates.length} tried${providerNote}). ` +
|
|
908
|
+
`Attempts: ${detail || 'no attempt details recorded'}.${lastBody}`);
|
|
909
|
+
}
|
|
910
|
+
export function mapGatewayFallbackAttemptsToRouter(attempts) {
|
|
911
|
+
return attempts.map((a) => ({
|
|
912
|
+
provider: a.provider,
|
|
913
|
+
model: a.model,
|
|
914
|
+
httpStatus: a.httpStatus,
|
|
915
|
+
error: new Error(a.error),
|
|
916
|
+
responsePreview: a.responsePreview
|
|
917
|
+
}));
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Log profile alias vs OpenRouter model id actually sent to the router after catalog resolution.
|
|
921
|
+
*/
|
|
922
|
+
export function logResolvedModelRouting(logger, request, mergedConfig) {
|
|
923
|
+
const res = request._modelResolution;
|
|
924
|
+
if (!res?.modelId && res?.originalModel === undefined) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const profileAlias = res.originalModel ?? mergedConfig?.model;
|
|
928
|
+
const invokedModelId = res.modelId ?? mergedConfig?.model;
|
|
929
|
+
const provider = mergedConfig?.provider;
|
|
930
|
+
const openRouterPath = res.routedViaOpenRouter === true || provider === 'openrouter';
|
|
931
|
+
if (!openRouterPath) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
logger.info('OpenRouter routing: profile alias resolved to model id for invoke', withActivityIdentity(request.identity, {
|
|
935
|
+
profileAlias,
|
|
936
|
+
invokedOpenRouterModelId: invokedModelId,
|
|
937
|
+
provider,
|
|
938
|
+
routedViaOpenRouter: res.routedViaOpenRouter,
|
|
939
|
+
resolvedVia: res.resolvedVia,
|
|
940
|
+
debugKind: gatewayLogDebug.trace
|
|
941
|
+
}));
|
|
942
|
+
}
|
|
825
943
|
function mapRouterFallbackAttempts(attempts) {
|
|
826
944
|
return attempts.map((attempt) => ({
|
|
827
945
|
provider: String(attempt.provider),
|
|
@@ -155,6 +155,35 @@ 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
|
+
/** Error code hint when a bundled profile name cannot be routed to a catalog target. */
|
|
159
|
+
export declare const MODEL_PROFILE_UNROUTABLE = "MODEL_PROFILE_UNROUTABLE";
|
|
160
|
+
export declare class ModelProfileUnroutableError extends Error {
|
|
161
|
+
readonly profileAlias: string;
|
|
162
|
+
readonly provider: string | undefined;
|
|
163
|
+
readonly code = "MODEL_PROFILE_UNROUTABLE";
|
|
164
|
+
constructor(profileAlias: string, provider: string | undefined, cause?: unknown);
|
|
165
|
+
}
|
|
166
|
+
type ModelResolutionCandidate = {
|
|
167
|
+
provider: string;
|
|
168
|
+
model: string;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Build rejection-metadata fallback attempts from trace-mode {@link GatewayTraceAttempt}s.
|
|
172
|
+
*/
|
|
173
|
+
export declare function buildGatewayFallbackAttemptsFromTrace(traceAttempts: GatewayTraceAttempt[], candidates: ModelResolutionCandidate[], lastError?: Error): GatewayFallbackAttempt[];
|
|
174
|
+
/** Human-readable exhaustion message for trace fallback chains and rejection logs. */
|
|
175
|
+
export declare function formatFallbackExhaustionMessage(attempts: GatewayFallbackAttempt[], candidates: ModelResolutionCandidate[]): string;
|
|
176
|
+
export declare function mapGatewayFallbackAttemptsToRouter(attempts: GatewayFallbackAttempt[]): Array<{
|
|
177
|
+
provider: string;
|
|
178
|
+
model?: string;
|
|
179
|
+
httpStatus?: number;
|
|
180
|
+
error: Error;
|
|
181
|
+
responsePreview?: string;
|
|
182
|
+
}>;
|
|
183
|
+
/**
|
|
184
|
+
* Log profile alias vs OpenRouter model id actually sent to the router after catalog resolution.
|
|
185
|
+
*/
|
|
186
|
+
export declare function logResolvedModelRouting(logger: Logxer, request: ChatRequest, mergedConfig: ChatRequest['config']): void;
|
|
158
187
|
/**
|
|
159
188
|
* Walk `error` and `error.cause` for {@link FallbackExhaustedError.attempts}.
|
|
160
189
|
*/
|
package/dist-cjs/gateway.cjs
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Simplified AI Gateway - Clean proxy implementation
|
|
5
5
|
*/
|
|
6
|
+
import { FallbackExhaustedError } from '@x12i/ai-providers-router';
|
|
6
7
|
import { validateChatRequest, validateAIRequest } from './gateway-validation.js';
|
|
7
8
|
import { ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
8
9
|
import { initializeGatewayComponents } from './gateway-config.js';
|
|
9
10
|
import { buildMessages } from './message-builder.js';
|
|
10
11
|
import { extractJsonFromFlexMd, getModelMaxTokensFromFlexMd } from './flex-md-loader.js';
|
|
11
12
|
import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
12
|
-
import { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, buildOptimixerActualUsage, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, isMaxTokensExplicitlySet, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
|
|
13
|
+
import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter, hasNonZeroTokenUsage, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, buildOptimixerActualUsage, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, isMaxTokensExplicitlySet, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
|
|
13
14
|
import { getAiToolsClient } from './ai-tools-client.js';
|
|
14
15
|
import { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
15
16
|
import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
|
|
@@ -277,6 +278,7 @@ export class AIGateway {
|
|
|
277
278
|
catalog: aiTools?.catalog ?? null
|
|
278
279
|
});
|
|
279
280
|
request._mergedRouterConfig = mergedConfig;
|
|
281
|
+
logResolvedModelRouting(this.logger, request, mergedConfig);
|
|
280
282
|
const diagnosticsMode = request.diagnostics?.mode;
|
|
281
283
|
const traceEnabled = diagnosticsMode === 'trace';
|
|
282
284
|
const includeRawProviderPayload = request.diagnostics?.includeRawProviderPayload === true;
|
|
@@ -468,7 +470,20 @@ export class AIGateway {
|
|
|
468
470
|
}
|
|
469
471
|
}
|
|
470
472
|
if (!response) {
|
|
471
|
-
|
|
473
|
+
const fallbackAttempts = buildGatewayFallbackAttemptsFromTrace(traceAttempts, deduped, lastError);
|
|
474
|
+
const providersTried = [...new Set(deduped.map((c) => c.provider))];
|
|
475
|
+
this.logger.error('Trace fallback chain exhausted', withActivityIdentity(request.identity, {
|
|
476
|
+
providersTried,
|
|
477
|
+
candidates: deduped,
|
|
478
|
+
fallbackAttempts,
|
|
479
|
+
debugKind: gatewayLogDebug.anomaly
|
|
480
|
+
}));
|
|
481
|
+
const exhausted = new FallbackExhaustedError(mapGatewayFallbackAttemptsToRouter(fallbackAttempts));
|
|
482
|
+
exhausted.message = formatFallbackExhaustionMessage(fallbackAttempts, deduped);
|
|
483
|
+
if (lastError) {
|
|
484
|
+
exhausted.cause = lastError;
|
|
485
|
+
}
|
|
486
|
+
throw exhausted;
|
|
472
487
|
}
|
|
473
488
|
// Summary counts + final request ids.
|
|
474
489
|
traceRetryCount = traceAttempts.filter(a => a.routing.retryIndex > 0).length;
|
|
@@ -565,11 +580,14 @@ export class AIGateway {
|
|
|
565
580
|
tokens = second;
|
|
566
581
|
}
|
|
567
582
|
}
|
|
568
|
-
|
|
583
|
+
let costCompletion = await resolveCostCompletionWithAiTools(routerResponse, tokens, {
|
|
569
584
|
mergedConfig,
|
|
570
585
|
calculator: aiTools?.calculator ?? null,
|
|
571
586
|
calculateCost: this.config.aiTools?.calculateCost
|
|
572
587
|
});
|
|
588
|
+
if (!costCompletion.costStatus && hasNonZeroTokenUsage(tokens)) {
|
|
589
|
+
costCompletion = { ...costCompletion, costStatus: 'unpriced' };
|
|
590
|
+
}
|
|
573
591
|
const routerMetaForCost = routerResponse?.metadata || {};
|
|
574
592
|
const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
|
|
575
593
|
const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
|
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, tryExtractFallbackAttemptsFromErrorChain, 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, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } 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
|
@@ -17,7 +17,7 @@ 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
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';
|
|
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';
|
|
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';
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Optimixer } from '@x12i/optimixer';
|
|
2
2
|
import { resolveActivityTrackingConfig } from './config/activity-tracking-config.js';
|
|
3
3
|
import { estimateMessagesTokenSizes } from './token-estimate.js';
|
|
4
|
-
|
|
4
|
+
/** Optimixer bucket key: prefer taskTypeId (template), then identity actionType, else gateway default. */
|
|
5
|
+
function resolveTemplateId(request) {
|
|
6
|
+
if (request.taskTypeId && String(request.taskTypeId).trim()) {
|
|
7
|
+
return String(request.taskTypeId).trim();
|
|
8
|
+
}
|
|
5
9
|
const identity = request.identity;
|
|
6
10
|
if (identity?.actionType && String(identity.actionType).trim()) {
|
|
7
11
|
return String(identity.actionType).trim();
|
|
8
12
|
}
|
|
9
|
-
if (request.taskTypeId && String(request.taskTypeId).trim()) {
|
|
10
|
-
return String(request.taskTypeId).trim();
|
|
11
|
-
}
|
|
12
13
|
return 'gateway.invoke';
|
|
13
14
|
}
|
|
14
15
|
function toActivixRunContext(identity) {
|
|
@@ -77,15 +78,18 @@ export class OptimixerManager {
|
|
|
77
78
|
const { request, mergedConfig, messages } = ctx;
|
|
78
79
|
const { inputSize, contextSize } = estimateMessagesTokenSizes(messages);
|
|
79
80
|
const acceptableRisk = this.config?.acceptableRisk ?? 'medium';
|
|
81
|
+
const provider = typeof mergedConfig?.provider === 'string' ? mergedConfig.provider : undefined;
|
|
82
|
+
const model = typeof mergedConfig?.model === 'string' ? mergedConfig.model : undefined;
|
|
80
83
|
try {
|
|
81
84
|
return await optimixer.predictAiMaxTokens({
|
|
82
|
-
|
|
85
|
+
templateId: resolveTemplateId(request),
|
|
83
86
|
inputSize,
|
|
84
87
|
contextSize,
|
|
85
88
|
acceptableRisk,
|
|
86
89
|
runContext: toActivixRunContext(request.identity),
|
|
87
|
-
provider
|
|
88
|
-
|
|
90
|
+
...(provider || model
|
|
91
|
+
? { modelProfile: { ...(provider ? { provider } : {}), ...(model ? { model } : {}) } }
|
|
92
|
+
: {})
|
|
89
93
|
});
|
|
90
94
|
}
|
|
91
95
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x12i/ai-gateway",
|
|
3
|
-
"version": "9.6.
|
|
3
|
+
"version": "9.6.2",
|
|
4
4
|
"description": "AI Gateway - Unified interface for LLM provider routing and management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"author": "x12i",
|
|
42
42
|
"license": "mit",
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@x12i/activix": "^8.0
|
|
44
|
+
"@x12i/activix": "^8.3.0",
|
|
45
45
|
"@x12i/ai-providers-router": "^4.8.5",
|
|
46
|
-
"@x12i/ai-tools": "^2.0.
|
|
46
|
+
"@x12i/ai-tools": "^2.0.7",
|
|
47
47
|
"@x12i/flex-md": "^4.8.0",
|
|
48
48
|
"@x12i/logxer": "^4.3.5",
|
|
49
|
-
"@x12i/optimixer": "^
|
|
49
|
+
"@x12i/optimixer": "^2.4.0",
|
|
50
50
|
"@x12i/rendrix": "^4.3.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|