@x12i/ai-gateway 9.1.1 → 9.1.3
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/activity-manager.js +57 -26
- package/dist/gateway-utils.d.ts +27 -1
- package/dist/gateway-utils.js +142 -0
- package/dist/gateway.js +30 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/types.d.ts +39 -0
- package/dist-cjs/activity-manager.cjs +57 -26
- package/dist-cjs/gateway-utils.cjs +148 -0
- package/dist-cjs/gateway-utils.d.ts +27 -1
- package/dist-cjs/gateway.cjs +29 -2
- package/dist-cjs/index.cjs +6 -1
- package/dist-cjs/index.d.ts +2 -1
- package/dist-cjs/types.d.ts +39 -0
- package/package.json +1 -1
package/dist/activity-manager.js
CHANGED
|
@@ -133,6 +133,30 @@ function logUpstreamIdentityWarnings(logger, incomingIdentity, merged) {
|
|
|
133
133
|
}));
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
+
/** Routing / generation facts from gateway response metadata for Activix `outer.metadata` on completion. */
|
|
137
|
+
function pickActivixCompletionRoutingMetadata(response) {
|
|
138
|
+
if (response == null || typeof response !== 'object')
|
|
139
|
+
return {};
|
|
140
|
+
const meta = response.metadata;
|
|
141
|
+
if (meta == null || typeof meta !== 'object')
|
|
142
|
+
return {};
|
|
143
|
+
const m = meta;
|
|
144
|
+
const out = {};
|
|
145
|
+
if (typeof m.modelUsed === 'string')
|
|
146
|
+
out.modelUsed = m.modelUsed;
|
|
147
|
+
if (typeof m.model === 'string')
|
|
148
|
+
out.model = m.model;
|
|
149
|
+
if (typeof m.provider === 'string')
|
|
150
|
+
out.provider = m.provider;
|
|
151
|
+
if (typeof m.maxTokensRequested === 'number')
|
|
152
|
+
out.maxTokensRequested = m.maxTokensRequested;
|
|
153
|
+
if (typeof m.region === 'string')
|
|
154
|
+
out.region = m.region;
|
|
155
|
+
if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
|
|
156
|
+
out.effectiveModelConfig = m.effectiveModelConfig;
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
136
160
|
function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
|
|
137
161
|
const incomingIdentity = request.identity;
|
|
138
162
|
const sessionId = resolveSessionIdForRequest(request, incomingIdentity, aiRequestId);
|
|
@@ -388,33 +412,37 @@ export class ActivityManager {
|
|
|
388
412
|
// - provider, model, temperature, maxTokens → only in config object
|
|
389
413
|
// - NO response, endTime, duration (these are added via logSuccess)
|
|
390
414
|
};
|
|
391
|
-
// Config snapshot
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
415
|
+
// Config snapshot — prefer gateway merge output (`_mergedRouterConfig`) over raw `request.config`.
|
|
416
|
+
// Callers often put model only on `modelConfig`; merge happens in `mergeConfig()` before `router.invoke`.
|
|
417
|
+
const mergedRouterConfig = request._mergedRouterConfig;
|
|
418
|
+
const configSource = mergedRouterConfig != null && typeof mergedRouterConfig === 'object'
|
|
419
|
+
? mergedRouterConfig
|
|
420
|
+
: request.config;
|
|
421
|
+
// CRITICAL: Capture the exact config sent to the router (finalRouterConfig)
|
|
422
|
+
if (configSource !== undefined) {
|
|
423
|
+
const hasResponseFormat = 'responseFormat' in configSource || 'response_format' in configSource;
|
|
397
424
|
if (hasResponseFormat) {
|
|
398
425
|
this.logger.warn('Activity tracking received config with response_format - this should not happen', {
|
|
399
426
|
aiRequestId,
|
|
400
|
-
hasResponseFormat: 'responseFormat' in
|
|
401
|
-
hasResponse_format: 'response_format' in
|
|
402
|
-
configKeys: Object.keys(
|
|
427
|
+
hasResponseFormat: 'responseFormat' in configSource,
|
|
428
|
+
hasResponse_format: 'response_format' in configSource,
|
|
429
|
+
configKeys: Object.keys(configSource)
|
|
403
430
|
});
|
|
404
431
|
}
|
|
405
432
|
activityMetadata.config = {
|
|
406
|
-
model:
|
|
407
|
-
provider:
|
|
408
|
-
temperature:
|
|
409
|
-
maxTokens:
|
|
410
|
-
rawConfig:
|
|
433
|
+
model: configSource.model,
|
|
434
|
+
provider: configSource.provider || null,
|
|
435
|
+
temperature: configSource.temperature,
|
|
436
|
+
maxTokens: configSource.maxTokens,
|
|
437
|
+
rawConfig: configSource
|
|
411
438
|
};
|
|
412
439
|
this.logger.debug('Activity tracking config captured', {
|
|
413
440
|
aiRequestId,
|
|
414
|
-
model:
|
|
415
|
-
provider:
|
|
416
|
-
|
|
417
|
-
|
|
441
|
+
model: configSource.model,
|
|
442
|
+
provider: configSource.provider,
|
|
443
|
+
configSource: mergedRouterConfig != null ? '_mergedRouterConfig' : 'request.config',
|
|
444
|
+
hasResponseFormat,
|
|
445
|
+
rawConfigKeys: Object.keys(configSource).slice(0, 10)
|
|
418
446
|
});
|
|
419
447
|
}
|
|
420
448
|
// Build request object snapshots (raw = incoming; parsed = constructed messages/meta)
|
|
@@ -605,14 +633,17 @@ export class ActivityManager {
|
|
|
605
633
|
...(aiRequest.masterSkillActivityId && { masterSkillActivityId: aiRequest.masterSkillActivityId }),
|
|
606
634
|
...(aiRequest.masterSkillId && { masterSkillId: aiRequest.masterSkillId })
|
|
607
635
|
};
|
|
608
|
-
|
|
609
|
-
|
|
636
|
+
const mergedRouterConfigSkill = request._mergedRouterConfig;
|
|
637
|
+
const configSourceSkill = mergedRouterConfigSkill != null && typeof mergedRouterConfigSkill === 'object'
|
|
638
|
+
? mergedRouterConfigSkill
|
|
639
|
+
: request.config;
|
|
640
|
+
if (configSourceSkill !== undefined) {
|
|
610
641
|
activityMetadata.config = {
|
|
611
|
-
model:
|
|
612
|
-
provider:
|
|
613
|
-
temperature:
|
|
614
|
-
maxTokens:
|
|
615
|
-
rawConfig:
|
|
642
|
+
model: configSourceSkill.model,
|
|
643
|
+
provider: configSourceSkill.provider || null,
|
|
644
|
+
temperature: configSourceSkill.temperature,
|
|
645
|
+
maxTokens: configSourceSkill.maxTokens,
|
|
646
|
+
rawConfig: configSourceSkill
|
|
616
647
|
};
|
|
617
648
|
}
|
|
618
649
|
// Build request object snapshots (same as startActivity)
|
|
@@ -816,7 +847,7 @@ export class ActivityManager {
|
|
|
816
847
|
response: details.response,
|
|
817
848
|
outer: {
|
|
818
849
|
output: details.response,
|
|
819
|
-
metadata:
|
|
850
|
+
metadata: pickActivixCompletionRoutingMetadata(details.response)
|
|
820
851
|
},
|
|
821
852
|
endTime: details.endTime,
|
|
822
853
|
duration: details.duration
|
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 { ChatRequest, GatewayConfig, ModelConfig } from './types.js';
|
|
5
|
+
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceMergedConfig, GatewayTraceRequestIds, ModelConfig } from './types.js';
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
7
|
/**
|
|
8
8
|
* Generates MD5 hash of a string
|
|
@@ -57,6 +57,31 @@ export declare function pickInvokeRoutingMetadataSlice(routerResponse: unknown,
|
|
|
57
57
|
* Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
|
|
58
58
|
*/
|
|
59
59
|
export declare function pickEffectiveModelConfigForMetadata(mergedConfig: unknown): Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>> | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
|
|
62
|
+
*/
|
|
63
|
+
export declare function pickTraceMergedRouterConfig(mergedConfig: unknown): GatewayTraceMergedConfig | undefined;
|
|
64
|
+
declare const EFFECTIVE_MODEL_CONFIG_KEYS: readonly ["model", "modelId", "provider", "temperature", "maxTokens", "topP"];
|
|
65
|
+
/**
|
|
66
|
+
* Allowlisted generation fields from request only (before mergeConfig / flex-md).
|
|
67
|
+
* Priority matches mergeConfig: modelConfig overrides request.config per key.
|
|
68
|
+
*/
|
|
69
|
+
export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<AIInvokeRequest, 'config' | 'modelConfig'>): Partial<Pick<ModelConfig, (typeof EFFECTIVE_MODEL_CONFIG_KEYS)[number]>> | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
|
|
72
|
+
* to find a router-shaped object for token / correlation extraction.
|
|
73
|
+
*/
|
|
74
|
+
export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
|
|
75
|
+
export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
|
|
76
|
+
export declare function buildInvokeRejectionMetadata(args: {
|
|
77
|
+
request: Pick<AIInvokeRequest, 'aiRequestId' | 'identity' | 'config' | 'modelConfig'>;
|
|
78
|
+
taskTypeId: string;
|
|
79
|
+
startTime: number;
|
|
80
|
+
mergedConfig?: unknown;
|
|
81
|
+
partialRouterPayload?: unknown;
|
|
82
|
+
gatewayAiRequestId?: string;
|
|
83
|
+
}): GatewayInvokeRejectionMetadata;
|
|
84
|
+
export declare function attachGatewayInvokeRejectionMetadata(err: Error, metadata: GatewayInvokeRejectionMetadata): void;
|
|
60
85
|
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
61
86
|
export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
|
|
62
87
|
/**
|
|
@@ -64,3 +89,4 @@ export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
|
|
|
64
89
|
* Non-serializable values become a small marker object instead of throwing.
|
|
65
90
|
*/
|
|
66
91
|
export declare function capActivityFullResponsePayload(payload: unknown, maxChars?: number): unknown;
|
|
92
|
+
export {};
|
package/dist/gateway-utils.js
CHANGED
|
@@ -358,6 +358,148 @@ export function pickEffectiveModelConfigForMetadata(mergedConfig) {
|
|
|
358
358
|
}
|
|
359
359
|
return Object.keys(out).length ? out : undefined;
|
|
360
360
|
}
|
|
361
|
+
const TRACE_MERGED_ROUTER_NUMERIC_KEYS = [
|
|
362
|
+
'temperature',
|
|
363
|
+
'maxTokens',
|
|
364
|
+
'topP',
|
|
365
|
+
'frequencyPenalty',
|
|
366
|
+
'presencePenalty'
|
|
367
|
+
];
|
|
368
|
+
/**
|
|
369
|
+
* Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
|
|
370
|
+
*/
|
|
371
|
+
export function pickTraceMergedRouterConfig(mergedConfig) {
|
|
372
|
+
if (mergedConfig == null || typeof mergedConfig !== 'object')
|
|
373
|
+
return undefined;
|
|
374
|
+
const c = mergedConfig;
|
|
375
|
+
const out = {};
|
|
376
|
+
for (const k of ['model', 'modelId', 'provider']) {
|
|
377
|
+
const v = c[k];
|
|
378
|
+
if (typeof v === 'string' && v.length > 0)
|
|
379
|
+
out[k] = v;
|
|
380
|
+
}
|
|
381
|
+
for (const k of TRACE_MERGED_ROUTER_NUMERIC_KEYS) {
|
|
382
|
+
const v = c[k];
|
|
383
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
384
|
+
out[k] = v;
|
|
385
|
+
}
|
|
386
|
+
const stop = c.stop;
|
|
387
|
+
if (Array.isArray(stop) && stop.every((x) => typeof x === 'string')) {
|
|
388
|
+
out.stop = stop;
|
|
389
|
+
}
|
|
390
|
+
else if (typeof stop === 'string' && stop.length > 0) {
|
|
391
|
+
out.stop = [stop];
|
|
392
|
+
}
|
|
393
|
+
return Object.keys(out).length ? out : undefined;
|
|
394
|
+
}
|
|
395
|
+
const EFFECTIVE_MODEL_CONFIG_KEYS = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
|
|
396
|
+
/**
|
|
397
|
+
* Allowlisted generation fields from request only (before mergeConfig / flex-md).
|
|
398
|
+
* Priority matches mergeConfig: modelConfig overrides request.config per key.
|
|
399
|
+
*/
|
|
400
|
+
export function pickEffectiveModelConfigFromInvokeRequest(request) {
|
|
401
|
+
const cfg = (request.config ?? {});
|
|
402
|
+
const mc = (request.modelConfig ?? {});
|
|
403
|
+
const out = {};
|
|
404
|
+
for (const k of EFFECTIVE_MODEL_CONFIG_KEYS) {
|
|
405
|
+
const v = mc[k] ?? cfg[k];
|
|
406
|
+
if (v !== undefined)
|
|
407
|
+
out[k] = v;
|
|
408
|
+
}
|
|
409
|
+
const modelFromId = out.model === undefined && typeof mc.modelId === 'string' ? mc.modelId : undefined;
|
|
410
|
+
if (modelFromId !== undefined)
|
|
411
|
+
out.model = modelFromId;
|
|
412
|
+
return Object.keys(out).length ? out : undefined;
|
|
413
|
+
}
|
|
414
|
+
function isRouterLikeEnvelope(value) {
|
|
415
|
+
if (value == null || typeof value !== 'object')
|
|
416
|
+
return false;
|
|
417
|
+
const r = value;
|
|
418
|
+
return ('metadata' in r ||
|
|
419
|
+
'outputText' in r ||
|
|
420
|
+
'content' in r ||
|
|
421
|
+
'requestId' in r ||
|
|
422
|
+
'usage' in r);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
|
|
426
|
+
* to find a router-shaped object for token / correlation extraction.
|
|
427
|
+
*/
|
|
428
|
+
export function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
|
|
429
|
+
const seen = new Set();
|
|
430
|
+
let cur = error;
|
|
431
|
+
for (let i = 0; i < maxDepth && cur != null; i++) {
|
|
432
|
+
if (typeof cur !== 'object')
|
|
433
|
+
break;
|
|
434
|
+
if (seen.has(cur))
|
|
435
|
+
break;
|
|
436
|
+
seen.add(cur);
|
|
437
|
+
const o = cur;
|
|
438
|
+
if (isRouterLikeEnvelope(cur))
|
|
439
|
+
return cur;
|
|
440
|
+
const nested = [o.response, o.routerResponse, o.lastResponse, o.body, o.data];
|
|
441
|
+
for (const n of nested) {
|
|
442
|
+
if (isRouterLikeEnvelope(n))
|
|
443
|
+
return n;
|
|
444
|
+
}
|
|
445
|
+
cur = o.cause;
|
|
446
|
+
}
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
export function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
|
|
450
|
+
if (typeof gatewayAiRequestId !== 'string' || gatewayAiRequestId.length === 0) {
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
const out = { gatewayAiRequestId };
|
|
454
|
+
if (routerLike == null || typeof routerLike !== 'object') {
|
|
455
|
+
return out;
|
|
456
|
+
}
|
|
457
|
+
const rr = routerLike;
|
|
458
|
+
const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
|
|
459
|
+
const routerRequestId = rr.requestId ?? meta.requestId;
|
|
460
|
+
if (typeof routerRequestId === 'string')
|
|
461
|
+
out.routerRequestId = routerRequestId;
|
|
462
|
+
if (typeof meta.providerRequestId === 'string')
|
|
463
|
+
out.providerRequestId = meta.providerRequestId;
|
|
464
|
+
if (typeof meta.openrouterRequestId === 'string')
|
|
465
|
+
out.openrouterRequestId = meta.openrouterRequestId;
|
|
466
|
+
const nested = meta.requestIds;
|
|
467
|
+
if (nested != null && typeof nested === 'object') {
|
|
468
|
+
for (const [k, v] of Object.entries(nested)) {
|
|
469
|
+
if (typeof v === 'string')
|
|
470
|
+
out[k] = v;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return out;
|
|
474
|
+
}
|
|
475
|
+
export function buildInvokeRejectionMetadata(args) {
|
|
476
|
+
const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
|
|
477
|
+
const partial = args.partialRouterPayload;
|
|
478
|
+
const mc = args.mergedConfig;
|
|
479
|
+
const routing = pickInvokeRoutingMetadataSlice(partial ?? {}, mc ?? {});
|
|
480
|
+
const effective = mc !== undefined
|
|
481
|
+
? pickEffectiveModelConfigForMetadata(mc)
|
|
482
|
+
: pickEffectiveModelConfigFromInvokeRequest(args.request);
|
|
483
|
+
let tokens = partial !== undefined ? extractTokenUsageFromRouterResponse(partial) : undefined;
|
|
484
|
+
if (tokens && tokens.prompt === 0 && tokens.completion === 0 && tokens.total === 0) {
|
|
485
|
+
tokens = undefined;
|
|
486
|
+
}
|
|
487
|
+
const requestIds = pickRequestIdsFromRouterLike(gid, partial);
|
|
488
|
+
return {
|
|
489
|
+
aiRequestId: args.request.aiRequestId,
|
|
490
|
+
identity: args.request.identity,
|
|
491
|
+
taskTypeId: args.taskTypeId,
|
|
492
|
+
latencyMs: Date.now() - args.startTime,
|
|
493
|
+
...routing,
|
|
494
|
+
...(effective !== undefined ? { effectiveModelConfig: effective } : {}),
|
|
495
|
+
...(tokens !== undefined ? { tokens } : {}),
|
|
496
|
+
...(requestIds !== undefined ? { requestIds } : {}),
|
|
497
|
+
...(mc === undefined ? { mergeConfigUnavailable: true } : {})
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
export function attachGatewayInvokeRejectionMetadata(err, metadata) {
|
|
501
|
+
err.metadata = metadata;
|
|
502
|
+
}
|
|
361
503
|
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
362
504
|
export const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
|
|
363
505
|
/**
|
package/dist/gateway.js
CHANGED
|
@@ -8,7 +8,7 @@ import { ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
|
8
8
|
import { initializeGatewayComponents } from './gateway-config.js';
|
|
9
9
|
import { buildMessages } from './message-builder.js';
|
|
10
10
|
import { extractJsonFromFlexMd } from './flex-md-loader.js';
|
|
11
|
-
import { capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice } from './gateway-utils.js';
|
|
11
|
+
import { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
|
|
12
12
|
import { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
13
13
|
import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
|
|
14
14
|
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
@@ -77,6 +77,8 @@ export class AIGateway {
|
|
|
77
77
|
const messages = this.buildSimpleMessages(request);
|
|
78
78
|
// Merge config (modelConfig > request.config > gateway defaults)
|
|
79
79
|
const mergedConfig = await mergeConfig(request, this.config, this.logger);
|
|
80
|
+
// Activix start snapshot must match what the router receives (modelConfig-only callers omit request.config.model).
|
|
81
|
+
request._mergedRouterConfig = mergedConfig;
|
|
80
82
|
// Lazy auto-register providers from env (OPENAI_API_KEY, etc.) so consumers don't have to call init
|
|
81
83
|
if (!this._autoRegisterDone) {
|
|
82
84
|
await autoRegisterProviders(this.router, this.logger);
|
|
@@ -223,6 +225,13 @@ export class AIGateway {
|
|
|
223
225
|
failureType: 'validation-failure'
|
|
224
226
|
}, startTime);
|
|
225
227
|
}
|
|
228
|
+
const rejectMeta = buildInvokeRejectionMetadata({
|
|
229
|
+
request,
|
|
230
|
+
taskTypeId,
|
|
231
|
+
startTime,
|
|
232
|
+
gatewayAiRequestId: request.aiRequestId
|
|
233
|
+
});
|
|
234
|
+
attachGatewayInvokeRejectionMetadata(err, rejectMeta);
|
|
226
235
|
// Re-throw the error so it propagates to the caller
|
|
227
236
|
throw err;
|
|
228
237
|
}
|
|
@@ -237,6 +246,7 @@ export class AIGateway {
|
|
|
237
246
|
request._parsedRequest = parsedSnapshot;
|
|
238
247
|
// Merge config (modelConfig > request.config > gateway defaults)
|
|
239
248
|
const mergedConfig = await mergeConfig(request, this.config, this.logger);
|
|
249
|
+
request._mergedRouterConfig = mergedConfig;
|
|
240
250
|
const diagnosticsMode = request.diagnostics?.mode;
|
|
241
251
|
const traceEnabled = diagnosticsMode === 'trace';
|
|
242
252
|
const includeRawProviderPayload = request.diagnostics?.includeRawProviderPayload === true;
|
|
@@ -526,6 +536,7 @@ export class AIGateway {
|
|
|
526
536
|
const routerMetaForCost = routerResponse?.metadata || {};
|
|
527
537
|
const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
|
|
528
538
|
const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
|
|
539
|
+
const traceMergedRouterSnapshot = traceEnabled ? pickTraceMergedRouterConfig(mergedConfig) : undefined;
|
|
529
540
|
const enhancedResponse = {
|
|
530
541
|
content: content,
|
|
531
542
|
parsedContent: parsedContent,
|
|
@@ -553,7 +564,10 @@ export class AIGateway {
|
|
|
553
564
|
requestIds: traceRequestIds,
|
|
554
565
|
retryCount: traceRetryCount,
|
|
555
566
|
fallbackCount: traceFallbackCount,
|
|
556
|
-
attempts: traceAttempts
|
|
567
|
+
attempts: traceAttempts,
|
|
568
|
+
...(traceMergedRouterSnapshot !== undefined
|
|
569
|
+
? { mergedRouterConfig: traceMergedRouterSnapshot }
|
|
570
|
+
: {})
|
|
557
571
|
}
|
|
558
572
|
: {})
|
|
559
573
|
}
|
|
@@ -611,8 +625,21 @@ export class AIGateway {
|
|
|
611
625
|
}
|
|
612
626
|
catch (error) {
|
|
613
627
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
628
|
+
const partial = tryExtractRouterLikePayloadFromErrorChain(err);
|
|
629
|
+
const rejectMeta = buildInvokeRejectionMetadata({
|
|
630
|
+
request,
|
|
631
|
+
taskTypeId,
|
|
632
|
+
startTime,
|
|
633
|
+
mergedConfig,
|
|
634
|
+
partialRouterPayload: partial,
|
|
635
|
+
gatewayAiRequestId: request.aiRequestId
|
|
636
|
+
});
|
|
637
|
+
attachGatewayInvokeRejectionMetadata(err, rejectMeta);
|
|
614
638
|
if (err.message.includes(NO_PROVIDER_ERROR)) {
|
|
615
|
-
|
|
639
|
+
const wrapped = new Error(err.message + NO_PROVIDER_HINT);
|
|
640
|
+
wrapped.cause = err;
|
|
641
|
+
attachGatewayInvokeRejectionMetadata(wrapped, rejectMeta);
|
|
642
|
+
throw wrapped;
|
|
616
643
|
}
|
|
617
644
|
throw err;
|
|
618
645
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +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, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
|
|
19
|
+
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
|
|
20
|
+
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
|
|
20
21
|
export { mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
21
22
|
export type { UsageTier } from './types.js';
|
|
22
23
|
export { Activix } from '@x12i/activix';
|
package/dist/index.js
CHANGED
|
@@ -17,6 +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 } from './gateway-utils.js';
|
|
20
21
|
export { mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
21
22
|
// Usage tracking: UsageTracker class methods are available but consumption calculation is disabled
|
|
22
23
|
// (x-models was previously used for RPM/TPM tracking but is no longer integrated)
|
package/dist/types.d.ts
CHANGED
|
@@ -84,6 +84,39 @@ export type GatewayTraceAttempt = {
|
|
|
84
84
|
*/
|
|
85
85
|
rawProviderPayload?: unknown;
|
|
86
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Allowlisted merged router/generation config returned in {@link EnhancedLLMResponse.metadata}
|
|
89
|
+
* when `diagnostics.mode === 'trace'`. Omits arbitrary extras and secrets.
|
|
90
|
+
*/
|
|
91
|
+
export type GatewayTraceMergedConfig = Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP' | 'frequencyPenalty' | 'presencePenalty' | 'stop'>>;
|
|
92
|
+
/**
|
|
93
|
+
* Normalized observability payload attached to thrown errors from {@link AIGateway.invoke}
|
|
94
|
+
* when the gateway can derive fields (merged config, partial router body on error).
|
|
95
|
+
* SDKs should read `(error as Error & { metadata?: GatewayInvokeRejectionMetadata }).metadata`
|
|
96
|
+
* (see docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md).
|
|
97
|
+
*/
|
|
98
|
+
export type GatewayInvokeRejectionMetadata = {
|
|
99
|
+
aiRequestId?: string;
|
|
100
|
+
identity?: ActivityIdentity;
|
|
101
|
+
taskTypeId?: string;
|
|
102
|
+
latencyMs?: number;
|
|
103
|
+
tokens?: {
|
|
104
|
+
prompt: number;
|
|
105
|
+
completion: number;
|
|
106
|
+
total: number;
|
|
107
|
+
};
|
|
108
|
+
provider?: string;
|
|
109
|
+
modelUsed?: string;
|
|
110
|
+
maxTokensRequested?: number;
|
|
111
|
+
region?: string;
|
|
112
|
+
effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
|
|
113
|
+
requestIds?: GatewayTraceRequestIds;
|
|
114
|
+
/**
|
|
115
|
+
* True when {@link mergeConfig} did not run (e.g. message-building threw first).
|
|
116
|
+
* Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
|
|
117
|
+
*/
|
|
118
|
+
mergeConfigUnavailable?: true;
|
|
119
|
+
};
|
|
87
120
|
/**
|
|
88
121
|
* Identity object used for activity linkage.
|
|
89
122
|
* On gateway requests/responses it lives on `identity`. When activity tracking persists via Activix v5+,
|
|
@@ -926,6 +959,12 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
|
|
|
926
959
|
* Ordered, authoritative attempts across retries and fallbacks (trace mode).
|
|
927
960
|
*/
|
|
928
961
|
attempts?: GatewayTraceAttempt[];
|
|
962
|
+
/**
|
|
963
|
+
* Merged gateway/router generation config actually used for the invocation (after
|
|
964
|
+
* {@link mergeConfig}: modelConfig / request.config / defaults / flex-md maxTokens).
|
|
965
|
+
* Only populated when diagnostics trace mode is enabled.
|
|
966
|
+
*/
|
|
967
|
+
mergedRouterConfig?: GatewayTraceMergedConfig;
|
|
929
968
|
/**
|
|
930
969
|
* Content type classification
|
|
931
970
|
* Indicates whether content is 'string', 'object', 'array', or 'null'
|
|
@@ -137,6 +137,30 @@ function logUpstreamIdentityWarnings(logger, incomingIdentity, merged) {
|
|
|
137
137
|
}));
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
+
/** Routing / generation facts from gateway response metadata for Activix `outer.metadata` on completion. */
|
|
141
|
+
function pickActivixCompletionRoutingMetadata(response) {
|
|
142
|
+
if (response == null || typeof response !== 'object')
|
|
143
|
+
return {};
|
|
144
|
+
const meta = response.metadata;
|
|
145
|
+
if (meta == null || typeof meta !== 'object')
|
|
146
|
+
return {};
|
|
147
|
+
const m = meta;
|
|
148
|
+
const out = {};
|
|
149
|
+
if (typeof m.modelUsed === 'string')
|
|
150
|
+
out.modelUsed = m.modelUsed;
|
|
151
|
+
if (typeof m.model === 'string')
|
|
152
|
+
out.model = m.model;
|
|
153
|
+
if (typeof m.provider === 'string')
|
|
154
|
+
out.provider = m.provider;
|
|
155
|
+
if (typeof m.maxTokensRequested === 'number')
|
|
156
|
+
out.maxTokensRequested = m.maxTokensRequested;
|
|
157
|
+
if (typeof m.region === 'string')
|
|
158
|
+
out.region = m.region;
|
|
159
|
+
if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
|
|
160
|
+
out.effectiveModelConfig = m.effectiveModelConfig;
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
140
164
|
function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
|
|
141
165
|
const incomingIdentity = request.identity;
|
|
142
166
|
const sessionId = resolveSessionIdForRequest(request, incomingIdentity, aiRequestId);
|
|
@@ -392,33 +416,37 @@ class ActivityManager {
|
|
|
392
416
|
// - provider, model, temperature, maxTokens → only in config object
|
|
393
417
|
// - NO response, endTime, duration (these are added via logSuccess)
|
|
394
418
|
};
|
|
395
|
-
// Config snapshot
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
419
|
+
// Config snapshot — prefer gateway merge output (`_mergedRouterConfig`) over raw `request.config`.
|
|
420
|
+
// Callers often put model only on `modelConfig`; merge happens in `mergeConfig()` before `router.invoke`.
|
|
421
|
+
const mergedRouterConfig = request._mergedRouterConfig;
|
|
422
|
+
const configSource = mergedRouterConfig != null && typeof mergedRouterConfig === 'object'
|
|
423
|
+
? mergedRouterConfig
|
|
424
|
+
: request.config;
|
|
425
|
+
// CRITICAL: Capture the exact config sent to the router (finalRouterConfig)
|
|
426
|
+
if (configSource !== undefined) {
|
|
427
|
+
const hasResponseFormat = 'responseFormat' in configSource || 'response_format' in configSource;
|
|
401
428
|
if (hasResponseFormat) {
|
|
402
429
|
this.logger.warn('Activity tracking received config with response_format - this should not happen', {
|
|
403
430
|
aiRequestId,
|
|
404
|
-
hasResponseFormat: 'responseFormat' in
|
|
405
|
-
hasResponse_format: 'response_format' in
|
|
406
|
-
configKeys: Object.keys(
|
|
431
|
+
hasResponseFormat: 'responseFormat' in configSource,
|
|
432
|
+
hasResponse_format: 'response_format' in configSource,
|
|
433
|
+
configKeys: Object.keys(configSource)
|
|
407
434
|
});
|
|
408
435
|
}
|
|
409
436
|
activityMetadata.config = {
|
|
410
|
-
model:
|
|
411
|
-
provider:
|
|
412
|
-
temperature:
|
|
413
|
-
maxTokens:
|
|
414
|
-
rawConfig:
|
|
437
|
+
model: configSource.model,
|
|
438
|
+
provider: configSource.provider || null,
|
|
439
|
+
temperature: configSource.temperature,
|
|
440
|
+
maxTokens: configSource.maxTokens,
|
|
441
|
+
rawConfig: configSource
|
|
415
442
|
};
|
|
416
443
|
this.logger.debug('Activity tracking config captured', {
|
|
417
444
|
aiRequestId,
|
|
418
|
-
model:
|
|
419
|
-
provider:
|
|
420
|
-
|
|
421
|
-
|
|
445
|
+
model: configSource.model,
|
|
446
|
+
provider: configSource.provider,
|
|
447
|
+
configSource: mergedRouterConfig != null ? '_mergedRouterConfig' : 'request.config',
|
|
448
|
+
hasResponseFormat,
|
|
449
|
+
rawConfigKeys: Object.keys(configSource).slice(0, 10)
|
|
422
450
|
});
|
|
423
451
|
}
|
|
424
452
|
// Build request object snapshots (raw = incoming; parsed = constructed messages/meta)
|
|
@@ -609,14 +637,17 @@ class ActivityManager {
|
|
|
609
637
|
...(aiRequest.masterSkillActivityId && { masterSkillActivityId: aiRequest.masterSkillActivityId }),
|
|
610
638
|
...(aiRequest.masterSkillId && { masterSkillId: aiRequest.masterSkillId })
|
|
611
639
|
};
|
|
612
|
-
|
|
613
|
-
|
|
640
|
+
const mergedRouterConfigSkill = request._mergedRouterConfig;
|
|
641
|
+
const configSourceSkill = mergedRouterConfigSkill != null && typeof mergedRouterConfigSkill === 'object'
|
|
642
|
+
? mergedRouterConfigSkill
|
|
643
|
+
: request.config;
|
|
644
|
+
if (configSourceSkill !== undefined) {
|
|
614
645
|
activityMetadata.config = {
|
|
615
|
-
model:
|
|
616
|
-
provider:
|
|
617
|
-
temperature:
|
|
618
|
-
maxTokens:
|
|
619
|
-
rawConfig:
|
|
646
|
+
model: configSourceSkill.model,
|
|
647
|
+
provider: configSourceSkill.provider || null,
|
|
648
|
+
temperature: configSourceSkill.temperature,
|
|
649
|
+
maxTokens: configSourceSkill.maxTokens,
|
|
650
|
+
rawConfig: configSourceSkill
|
|
620
651
|
};
|
|
621
652
|
}
|
|
622
653
|
// Build request object snapshots (same as startActivity)
|
|
@@ -820,7 +851,7 @@ class ActivityManager {
|
|
|
820
851
|
response: details.response,
|
|
821
852
|
outer: {
|
|
822
853
|
output: details.response,
|
|
823
|
-
metadata:
|
|
854
|
+
metadata: pickActivixCompletionRoutingMetadata(details.response)
|
|
824
855
|
},
|
|
825
856
|
endTime: details.endTime,
|
|
826
857
|
duration: details.duration
|
|
@@ -46,6 +46,12 @@ exports.extractTokenUsageFromRouterResponse = extractTokenUsageFromRouterRespons
|
|
|
46
46
|
exports.extractCostUsdFromRouterResponse = extractCostUsdFromRouterResponse;
|
|
47
47
|
exports.pickInvokeRoutingMetadataSlice = pickInvokeRoutingMetadataSlice;
|
|
48
48
|
exports.pickEffectiveModelConfigForMetadata = pickEffectiveModelConfigForMetadata;
|
|
49
|
+
exports.pickTraceMergedRouterConfig = pickTraceMergedRouterConfig;
|
|
50
|
+
exports.pickEffectiveModelConfigFromInvokeRequest = pickEffectiveModelConfigFromInvokeRequest;
|
|
51
|
+
exports.tryExtractRouterLikePayloadFromErrorChain = tryExtractRouterLikePayloadFromErrorChain;
|
|
52
|
+
exports.pickRequestIdsFromRouterLike = pickRequestIdsFromRouterLike;
|
|
53
|
+
exports.buildInvokeRejectionMetadata = buildInvokeRejectionMetadata;
|
|
54
|
+
exports.attachGatewayInvokeRejectionMetadata = attachGatewayInvokeRejectionMetadata;
|
|
49
55
|
exports.capActivityFullResponsePayload = capActivityFullResponsePayload;
|
|
50
56
|
const crypto = __importStar(require("crypto"));
|
|
51
57
|
const gateway_instructions_js_1 = require("./gateway-instructions.cjs");
|
|
@@ -403,6 +409,148 @@ function pickEffectiveModelConfigForMetadata(mergedConfig) {
|
|
|
403
409
|
}
|
|
404
410
|
return Object.keys(out).length ? out : undefined;
|
|
405
411
|
}
|
|
412
|
+
const TRACE_MERGED_ROUTER_NUMERIC_KEYS = [
|
|
413
|
+
'temperature',
|
|
414
|
+
'maxTokens',
|
|
415
|
+
'topP',
|
|
416
|
+
'frequencyPenalty',
|
|
417
|
+
'presencePenalty'
|
|
418
|
+
];
|
|
419
|
+
/**
|
|
420
|
+
* Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
|
|
421
|
+
*/
|
|
422
|
+
function pickTraceMergedRouterConfig(mergedConfig) {
|
|
423
|
+
if (mergedConfig == null || typeof mergedConfig !== 'object')
|
|
424
|
+
return undefined;
|
|
425
|
+
const c = mergedConfig;
|
|
426
|
+
const out = {};
|
|
427
|
+
for (const k of ['model', 'modelId', 'provider']) {
|
|
428
|
+
const v = c[k];
|
|
429
|
+
if (typeof v === 'string' && v.length > 0)
|
|
430
|
+
out[k] = v;
|
|
431
|
+
}
|
|
432
|
+
for (const k of TRACE_MERGED_ROUTER_NUMERIC_KEYS) {
|
|
433
|
+
const v = c[k];
|
|
434
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
435
|
+
out[k] = v;
|
|
436
|
+
}
|
|
437
|
+
const stop = c.stop;
|
|
438
|
+
if (Array.isArray(stop) && stop.every((x) => typeof x === 'string')) {
|
|
439
|
+
out.stop = stop;
|
|
440
|
+
}
|
|
441
|
+
else if (typeof stop === 'string' && stop.length > 0) {
|
|
442
|
+
out.stop = [stop];
|
|
443
|
+
}
|
|
444
|
+
return Object.keys(out).length ? out : undefined;
|
|
445
|
+
}
|
|
446
|
+
const EFFECTIVE_MODEL_CONFIG_KEYS = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
|
|
447
|
+
/**
|
|
448
|
+
* Allowlisted generation fields from request only (before mergeConfig / flex-md).
|
|
449
|
+
* Priority matches mergeConfig: modelConfig overrides request.config per key.
|
|
450
|
+
*/
|
|
451
|
+
function pickEffectiveModelConfigFromInvokeRequest(request) {
|
|
452
|
+
const cfg = (request.config ?? {});
|
|
453
|
+
const mc = (request.modelConfig ?? {});
|
|
454
|
+
const out = {};
|
|
455
|
+
for (const k of EFFECTIVE_MODEL_CONFIG_KEYS) {
|
|
456
|
+
const v = mc[k] ?? cfg[k];
|
|
457
|
+
if (v !== undefined)
|
|
458
|
+
out[k] = v;
|
|
459
|
+
}
|
|
460
|
+
const modelFromId = out.model === undefined && typeof mc.modelId === 'string' ? mc.modelId : undefined;
|
|
461
|
+
if (modelFromId !== undefined)
|
|
462
|
+
out.model = modelFromId;
|
|
463
|
+
return Object.keys(out).length ? out : undefined;
|
|
464
|
+
}
|
|
465
|
+
function isRouterLikeEnvelope(value) {
|
|
466
|
+
if (value == null || typeof value !== 'object')
|
|
467
|
+
return false;
|
|
468
|
+
const r = value;
|
|
469
|
+
return ('metadata' in r ||
|
|
470
|
+
'outputText' in r ||
|
|
471
|
+
'content' in r ||
|
|
472
|
+
'requestId' in r ||
|
|
473
|
+
'usage' in r);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
|
|
477
|
+
* to find a router-shaped object for token / correlation extraction.
|
|
478
|
+
*/
|
|
479
|
+
function tryExtractRouterLikePayloadFromErrorChain(error, maxDepth = 8) {
|
|
480
|
+
const seen = new Set();
|
|
481
|
+
let cur = error;
|
|
482
|
+
for (let i = 0; i < maxDepth && cur != null; i++) {
|
|
483
|
+
if (typeof cur !== 'object')
|
|
484
|
+
break;
|
|
485
|
+
if (seen.has(cur))
|
|
486
|
+
break;
|
|
487
|
+
seen.add(cur);
|
|
488
|
+
const o = cur;
|
|
489
|
+
if (isRouterLikeEnvelope(cur))
|
|
490
|
+
return cur;
|
|
491
|
+
const nested = [o.response, o.routerResponse, o.lastResponse, o.body, o.data];
|
|
492
|
+
for (const n of nested) {
|
|
493
|
+
if (isRouterLikeEnvelope(n))
|
|
494
|
+
return n;
|
|
495
|
+
}
|
|
496
|
+
cur = o.cause;
|
|
497
|
+
}
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
500
|
+
function pickRequestIdsFromRouterLike(gatewayAiRequestId, routerLike) {
|
|
501
|
+
if (typeof gatewayAiRequestId !== 'string' || gatewayAiRequestId.length === 0) {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
const out = { gatewayAiRequestId };
|
|
505
|
+
if (routerLike == null || typeof routerLike !== 'object') {
|
|
506
|
+
return out;
|
|
507
|
+
}
|
|
508
|
+
const rr = routerLike;
|
|
509
|
+
const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
|
|
510
|
+
const routerRequestId = rr.requestId ?? meta.requestId;
|
|
511
|
+
if (typeof routerRequestId === 'string')
|
|
512
|
+
out.routerRequestId = routerRequestId;
|
|
513
|
+
if (typeof meta.providerRequestId === 'string')
|
|
514
|
+
out.providerRequestId = meta.providerRequestId;
|
|
515
|
+
if (typeof meta.openrouterRequestId === 'string')
|
|
516
|
+
out.openrouterRequestId = meta.openrouterRequestId;
|
|
517
|
+
const nested = meta.requestIds;
|
|
518
|
+
if (nested != null && typeof nested === 'object') {
|
|
519
|
+
for (const [k, v] of Object.entries(nested)) {
|
|
520
|
+
if (typeof v === 'string')
|
|
521
|
+
out[k] = v;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return out;
|
|
525
|
+
}
|
|
526
|
+
function buildInvokeRejectionMetadata(args) {
|
|
527
|
+
const gid = args.gatewayAiRequestId ?? args.request.aiRequestId;
|
|
528
|
+
const partial = args.partialRouterPayload;
|
|
529
|
+
const mc = args.mergedConfig;
|
|
530
|
+
const routing = pickInvokeRoutingMetadataSlice(partial ?? {}, mc ?? {});
|
|
531
|
+
const effective = mc !== undefined
|
|
532
|
+
? pickEffectiveModelConfigForMetadata(mc)
|
|
533
|
+
: pickEffectiveModelConfigFromInvokeRequest(args.request);
|
|
534
|
+
let tokens = partial !== undefined ? extractTokenUsageFromRouterResponse(partial) : undefined;
|
|
535
|
+
if (tokens && tokens.prompt === 0 && tokens.completion === 0 && tokens.total === 0) {
|
|
536
|
+
tokens = undefined;
|
|
537
|
+
}
|
|
538
|
+
const requestIds = pickRequestIdsFromRouterLike(gid, partial);
|
|
539
|
+
return {
|
|
540
|
+
aiRequestId: args.request.aiRequestId,
|
|
541
|
+
identity: args.request.identity,
|
|
542
|
+
taskTypeId: args.taskTypeId,
|
|
543
|
+
latencyMs: Date.now() - args.startTime,
|
|
544
|
+
...routing,
|
|
545
|
+
...(effective !== undefined ? { effectiveModelConfig: effective } : {}),
|
|
546
|
+
...(tokens !== undefined ? { tokens } : {}),
|
|
547
|
+
...(requestIds !== undefined ? { requestIds } : {}),
|
|
548
|
+
...(mc === undefined ? { mergeConfigUnavailable: true } : {})
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function attachGatewayInvokeRejectionMetadata(err, metadata) {
|
|
552
|
+
err.metadata = metadata;
|
|
553
|
+
}
|
|
406
554
|
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
407
555
|
exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
|
|
408
556
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Gateway Utilities Module
|
|
3
3
|
* Handles utility functions
|
|
4
4
|
*/
|
|
5
|
-
import type { ChatRequest, GatewayConfig, ModelConfig } from './types.js';
|
|
5
|
+
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceMergedConfig, GatewayTraceRequestIds, ModelConfig } from './types.js';
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
7
|
/**
|
|
8
8
|
* Generates MD5 hash of a string
|
|
@@ -57,6 +57,31 @@ export declare function pickInvokeRoutingMetadataSlice(routerResponse: unknown,
|
|
|
57
57
|
* Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
|
|
58
58
|
*/
|
|
59
59
|
export declare function pickEffectiveModelConfigForMetadata(mergedConfig: unknown): Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>> | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Allowlisted snapshot of merged router config for diagnostics trace responses (no arbitrary extras).
|
|
62
|
+
*/
|
|
63
|
+
export declare function pickTraceMergedRouterConfig(mergedConfig: unknown): GatewayTraceMergedConfig | undefined;
|
|
64
|
+
declare const EFFECTIVE_MODEL_CONFIG_KEYS: readonly ["model", "modelId", "provider", "temperature", "maxTokens", "topP"];
|
|
65
|
+
/**
|
|
66
|
+
* Allowlisted generation fields from request only (before mergeConfig / flex-md).
|
|
67
|
+
* Priority matches mergeConfig: modelConfig overrides request.config per key.
|
|
68
|
+
*/
|
|
69
|
+
export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<AIInvokeRequest, 'config' | 'modelConfig'>): Partial<Pick<ModelConfig, (typeof EFFECTIVE_MODEL_CONFIG_KEYS)[number]>> | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Walk `error`, optional `error.cause`, and common adapter fields (`response`, `routerResponse`, …)
|
|
72
|
+
* to find a router-shaped object for token / correlation extraction.
|
|
73
|
+
*/
|
|
74
|
+
export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
|
|
75
|
+
export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
|
|
76
|
+
export declare function buildInvokeRejectionMetadata(args: {
|
|
77
|
+
request: Pick<AIInvokeRequest, 'aiRequestId' | 'identity' | 'config' | 'modelConfig'>;
|
|
78
|
+
taskTypeId: string;
|
|
79
|
+
startTime: number;
|
|
80
|
+
mergedConfig?: unknown;
|
|
81
|
+
partialRouterPayload?: unknown;
|
|
82
|
+
gatewayAiRequestId?: string;
|
|
83
|
+
}): GatewayInvokeRejectionMetadata;
|
|
84
|
+
export declare function attachGatewayInvokeRejectionMetadata(err: Error, metadata: GatewayInvokeRejectionMetadata): void;
|
|
60
85
|
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
61
86
|
export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
|
|
62
87
|
/**
|
|
@@ -64,3 +89,4 @@ export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
|
|
|
64
89
|
* Non-serializable values become a small marker object instead of throwing.
|
|
65
90
|
*/
|
|
66
91
|
export declare function capActivityFullResponsePayload(payload: unknown, maxChars?: number): unknown;
|
|
92
|
+
export {};
|
package/dist-cjs/gateway.cjs
CHANGED
|
@@ -80,6 +80,8 @@ class AIGateway {
|
|
|
80
80
|
const messages = this.buildSimpleMessages(request);
|
|
81
81
|
// Merge config (modelConfig > request.config > gateway defaults)
|
|
82
82
|
const mergedConfig = await (0, gateway_utils_js_1.mergeConfig)(request, this.config, this.logger);
|
|
83
|
+
// Activix start snapshot must match what the router receives (modelConfig-only callers omit request.config.model).
|
|
84
|
+
request._mergedRouterConfig = mergedConfig;
|
|
83
85
|
// Lazy auto-register providers from env (OPENAI_API_KEY, etc.) so consumers don't have to call init
|
|
84
86
|
if (!this._autoRegisterDone) {
|
|
85
87
|
await (0, gateway_provider_auto_register_js_1.autoRegisterProviders)(this.router, this.logger);
|
|
@@ -226,6 +228,13 @@ class AIGateway {
|
|
|
226
228
|
failureType: 'validation-failure'
|
|
227
229
|
}, startTime);
|
|
228
230
|
}
|
|
231
|
+
const rejectMeta = (0, gateway_utils_js_1.buildInvokeRejectionMetadata)({
|
|
232
|
+
request,
|
|
233
|
+
taskTypeId,
|
|
234
|
+
startTime,
|
|
235
|
+
gatewayAiRequestId: request.aiRequestId
|
|
236
|
+
});
|
|
237
|
+
(0, gateway_utils_js_1.attachGatewayInvokeRejectionMetadata)(err, rejectMeta);
|
|
229
238
|
// Re-throw the error so it propagates to the caller
|
|
230
239
|
throw err;
|
|
231
240
|
}
|
|
@@ -240,6 +249,7 @@ class AIGateway {
|
|
|
240
249
|
request._parsedRequest = parsedSnapshot;
|
|
241
250
|
// Merge config (modelConfig > request.config > gateway defaults)
|
|
242
251
|
const mergedConfig = await (0, gateway_utils_js_1.mergeConfig)(request, this.config, this.logger);
|
|
252
|
+
request._mergedRouterConfig = mergedConfig;
|
|
243
253
|
const diagnosticsMode = request.diagnostics?.mode;
|
|
244
254
|
const traceEnabled = diagnosticsMode === 'trace';
|
|
245
255
|
const includeRawProviderPayload = request.diagnostics?.includeRawProviderPayload === true;
|
|
@@ -529,6 +539,7 @@ class AIGateway {
|
|
|
529
539
|
const routerMetaForCost = routerResponse?.metadata || {};
|
|
530
540
|
const routingMetadataSlice = (0, gateway_utils_js_1.pickInvokeRoutingMetadataSlice)(routerResponse, mergedConfig);
|
|
531
541
|
const effectiveModelConfig = (0, gateway_utils_js_1.pickEffectiveModelConfigForMetadata)(mergedConfig);
|
|
542
|
+
const traceMergedRouterSnapshot = traceEnabled ? (0, gateway_utils_js_1.pickTraceMergedRouterConfig)(mergedConfig) : undefined;
|
|
532
543
|
const enhancedResponse = {
|
|
533
544
|
content: content,
|
|
534
545
|
parsedContent: parsedContent,
|
|
@@ -556,7 +567,10 @@ class AIGateway {
|
|
|
556
567
|
requestIds: traceRequestIds,
|
|
557
568
|
retryCount: traceRetryCount,
|
|
558
569
|
fallbackCount: traceFallbackCount,
|
|
559
|
-
attempts: traceAttempts
|
|
570
|
+
attempts: traceAttempts,
|
|
571
|
+
...(traceMergedRouterSnapshot !== undefined
|
|
572
|
+
? { mergedRouterConfig: traceMergedRouterSnapshot }
|
|
573
|
+
: {})
|
|
560
574
|
}
|
|
561
575
|
: {})
|
|
562
576
|
}
|
|
@@ -614,8 +628,21 @@ class AIGateway {
|
|
|
614
628
|
}
|
|
615
629
|
catch (error) {
|
|
616
630
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
631
|
+
const partial = (0, gateway_utils_js_1.tryExtractRouterLikePayloadFromErrorChain)(err);
|
|
632
|
+
const rejectMeta = (0, gateway_utils_js_1.buildInvokeRejectionMetadata)({
|
|
633
|
+
request,
|
|
634
|
+
taskTypeId,
|
|
635
|
+
startTime,
|
|
636
|
+
mergedConfig,
|
|
637
|
+
partialRouterPayload: partial,
|
|
638
|
+
gatewayAiRequestId: request.aiRequestId
|
|
639
|
+
});
|
|
640
|
+
(0, gateway_utils_js_1.attachGatewayInvokeRejectionMetadata)(err, rejectMeta);
|
|
617
641
|
if (err.message.includes(NO_PROVIDER_ERROR)) {
|
|
618
|
-
|
|
642
|
+
const wrapped = new Error(err.message + NO_PROVIDER_HINT);
|
|
643
|
+
wrapped.cause = err;
|
|
644
|
+
(0, gateway_utils_js_1.attachGatewayInvokeRejectionMetadata)(wrapped, rejectMeta);
|
|
645
|
+
throw wrapped;
|
|
619
646
|
}
|
|
620
647
|
throw err;
|
|
621
648
|
}
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -21,7 +21,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
21
21
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.resetObjectTypesLibrary = exports.getObjectTypesLibrary = exports.initializeObjectTypesLibrary = exports.getObjectTypesForAgent = exports.getObjectType = exports.OBJECT_TYPES_LIBRARY = exports.assertValidAIRequest = exports.formatDiagnostic = exports.runValidationTests = exports.createValidationTestCases = exports.createTestAIRequest = exports.supportsJSONMode = exports.diagnoseResponse = exports.diagnoseRequest = exports.validateResponse = exports.extractJSON = exports.validateJSON = exports.validateAIRequest = exports.DEFAULT_RATE_LIMIT_ENABLED = exports.DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS = exports.GatewayRateLimiter = exports.runtimeObjects = exports.DebugLogAbstract = exports.createLogxer = exports.gatewayLogDebug = exports.withActivityIdentity = exports.activityIdentityToLogMeta = exports.ensureGatewayRequestIdentity = exports.ActivityManager = exports.Activix = exports.mergeTemplateRenderOptions = exports.autoRegisterProviders = exports.InstructionBackendError = exports.InstructionNotFoundError = exports.AIGateway = exports.FallbackExhaustedError = exports.ProviderNotFoundError = exports.createRouterFromConfig = exports.createRouter = exports.LLMProviderRouter = void 0;
|
|
24
|
+
exports.resetObjectTypesLibrary = exports.getObjectTypesLibrary = exports.initializeObjectTypesLibrary = exports.getObjectTypesForAgent = exports.getObjectType = exports.OBJECT_TYPES_LIBRARY = exports.assertValidAIRequest = exports.formatDiagnostic = exports.runValidationTests = exports.createValidationTestCases = exports.createTestAIRequest = exports.supportsJSONMode = exports.diagnoseResponse = exports.diagnoseRequest = exports.validateResponse = exports.extractJSON = exports.validateJSON = exports.validateAIRequest = exports.DEFAULT_RATE_LIMIT_ENABLED = exports.DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS = exports.GatewayRateLimiter = exports.runtimeObjects = exports.DebugLogAbstract = exports.createLogxer = exports.gatewayLogDebug = exports.withActivityIdentity = exports.activityIdentityToLogMeta = exports.ensureGatewayRequestIdentity = exports.ActivityManager = exports.Activix = exports.mergeTemplateRenderOptions = exports.pickRequestIdsFromRouterLike = exports.tryExtractRouterLikePayloadFromErrorChain = exports.buildInvokeRejectionMetadata = exports.attachGatewayInvokeRejectionMetadata = exports.autoRegisterProviders = exports.InstructionBackendError = exports.InstructionNotFoundError = exports.AIGateway = exports.FallbackExhaustedError = exports.ProviderNotFoundError = exports.createRouterFromConfig = exports.createRouter = exports.LLMProviderRouter = void 0;
|
|
25
25
|
// Re-export router class and types (base functionality)
|
|
26
26
|
var ai_providers_router_1 = require("@x12i/ai-providers-router");
|
|
27
27
|
Object.defineProperty(exports, "LLMProviderRouter", { enumerable: true, get: function () { return ai_providers_router_1.LLMProviderRouter; } });
|
|
@@ -43,6 +43,11 @@ Object.defineProperty(exports, "InstructionNotFoundError", { enumerable: true, g
|
|
|
43
43
|
Object.defineProperty(exports, "InstructionBackendError", { enumerable: true, get: function () { return instruction_errors_js_1.InstructionBackendError; } });
|
|
44
44
|
var gateway_provider_auto_register_js_1 = require("./gateway-provider-auto-register.cjs");
|
|
45
45
|
Object.defineProperty(exports, "autoRegisterProviders", { enumerable: true, get: function () { return gateway_provider_auto_register_js_1.autoRegisterProviders; } });
|
|
46
|
+
var gateway_utils_js_1 = require("./gateway-utils.cjs");
|
|
47
|
+
Object.defineProperty(exports, "attachGatewayInvokeRejectionMetadata", { enumerable: true, get: function () { return gateway_utils_js_1.attachGatewayInvokeRejectionMetadata; } });
|
|
48
|
+
Object.defineProperty(exports, "buildInvokeRejectionMetadata", { enumerable: true, get: function () { return gateway_utils_js_1.buildInvokeRejectionMetadata; } });
|
|
49
|
+
Object.defineProperty(exports, "tryExtractRouterLikePayloadFromErrorChain", { enumerable: true, get: function () { return gateway_utils_js_1.tryExtractRouterLikePayloadFromErrorChain; } });
|
|
50
|
+
Object.defineProperty(exports, "pickRequestIdsFromRouterLike", { enumerable: true, get: function () { return gateway_utils_js_1.pickRequestIdsFromRouterLike; } });
|
|
46
51
|
var template_render_merge_js_1 = require("./template-render-merge.cjs");
|
|
47
52
|
Object.defineProperty(exports, "mergeTemplateRenderOptions", { enumerable: true, get: function () { return template_render_merge_js_1.mergeTemplateRenderOptions; } });
|
|
48
53
|
// Usage tracking: UsageTracker class methods are available but consumption calculation is disabled
|
package/dist-cjs/index.d.ts
CHANGED
|
@@ -16,7 +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, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
|
|
19
|
+
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
|
|
20
|
+
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
|
|
20
21
|
export { mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
21
22
|
export type { UsageTier } from './types.js';
|
|
22
23
|
export { Activix } from '@x12i/activix';
|
package/dist-cjs/types.d.ts
CHANGED
|
@@ -84,6 +84,39 @@ export type GatewayTraceAttempt = {
|
|
|
84
84
|
*/
|
|
85
85
|
rawProviderPayload?: unknown;
|
|
86
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Allowlisted merged router/generation config returned in {@link EnhancedLLMResponse.metadata}
|
|
89
|
+
* when `diagnostics.mode === 'trace'`. Omits arbitrary extras and secrets.
|
|
90
|
+
*/
|
|
91
|
+
export type GatewayTraceMergedConfig = Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP' | 'frequencyPenalty' | 'presencePenalty' | 'stop'>>;
|
|
92
|
+
/**
|
|
93
|
+
* Normalized observability payload attached to thrown errors from {@link AIGateway.invoke}
|
|
94
|
+
* when the gateway can derive fields (merged config, partial router body on error).
|
|
95
|
+
* SDKs should read `(error as Error & { metadata?: GatewayInvokeRejectionMetadata }).metadata`
|
|
96
|
+
* (see docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md).
|
|
97
|
+
*/
|
|
98
|
+
export type GatewayInvokeRejectionMetadata = {
|
|
99
|
+
aiRequestId?: string;
|
|
100
|
+
identity?: ActivityIdentity;
|
|
101
|
+
taskTypeId?: string;
|
|
102
|
+
latencyMs?: number;
|
|
103
|
+
tokens?: {
|
|
104
|
+
prompt: number;
|
|
105
|
+
completion: number;
|
|
106
|
+
total: number;
|
|
107
|
+
};
|
|
108
|
+
provider?: string;
|
|
109
|
+
modelUsed?: string;
|
|
110
|
+
maxTokensRequested?: number;
|
|
111
|
+
region?: string;
|
|
112
|
+
effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
|
|
113
|
+
requestIds?: GatewayTraceRequestIds;
|
|
114
|
+
/**
|
|
115
|
+
* True when {@link mergeConfig} did not run (e.g. message-building threw first).
|
|
116
|
+
* Routing facts may only reflect request.config / modelConfig, not flex-md defaults.
|
|
117
|
+
*/
|
|
118
|
+
mergeConfigUnavailable?: true;
|
|
119
|
+
};
|
|
87
120
|
/**
|
|
88
121
|
* Identity object used for activity linkage.
|
|
89
122
|
* On gateway requests/responses it lives on `identity`. When activity tracking persists via Activix v5+,
|
|
@@ -926,6 +959,12 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
|
|
|
926
959
|
* Ordered, authoritative attempts across retries and fallbacks (trace mode).
|
|
927
960
|
*/
|
|
928
961
|
attempts?: GatewayTraceAttempt[];
|
|
962
|
+
/**
|
|
963
|
+
* Merged gateway/router generation config actually used for the invocation (after
|
|
964
|
+
* {@link mergeConfig}: modelConfig / request.config / defaults / flex-md maxTokens).
|
|
965
|
+
* Only populated when diagnostics trace mode is enabled.
|
|
966
|
+
*/
|
|
967
|
+
mergedRouterConfig?: GatewayTraceMergedConfig;
|
|
929
968
|
/**
|
|
930
969
|
* Content type classification
|
|
931
970
|
* Indicates whether content is 'string', 'object', 'array', or 'null'
|