@x12i/ai-gateway 9.0.9 → 9.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +897 -998
- package/dist/activity-manager.js +46 -6
- package/dist/config/activity-tracking-config.d.ts +2 -1
- package/dist/config/activity-tracking-config.js +3 -2
- package/dist/gateway-memory.d.ts +1 -2
- package/dist/gateway-memory.js +1 -15
- package/dist/gateway-meta.js +3 -0
- package/dist/gateway-utils.d.ts +24 -2
- package/dist/gateway-utils.js +122 -18
- package/dist/gateway-validation.d.ts +3 -3
- package/dist/gateway-validation.js +10 -1
- package/dist/gateway.d.ts +2 -2
- package/dist/gateway.js +30 -24
- package/dist/index.d.ts +2 -2
- package/dist/instruction-optimizer.js +3 -0
- package/dist/runtime-objects.d.ts +2 -13
- package/dist/troubleshooting-helper.d.ts +0 -3
- package/dist/troubleshooting-helper.js +99 -20
- package/dist/types.d.ts +49 -91
- package/dist-cjs/activity-manager.cjs +45 -5
- package/dist-cjs/config/activity-tracking-config.cjs +3 -2
- package/dist-cjs/config/activity-tracking-config.d.ts +2 -1
- package/dist-cjs/gateway-memory.cjs +1 -15
- package/dist-cjs/gateway-memory.d.ts +1 -2
- package/dist-cjs/gateway-meta.cjs +3 -0
- package/dist-cjs/gateway-utils.cjs +126 -18
- package/dist-cjs/gateway-utils.d.ts +24 -2
- package/dist-cjs/gateway-validation.cjs +10 -1
- package/dist-cjs/gateway-validation.d.ts +3 -3
- package/dist-cjs/gateway.cjs +29 -23
- package/dist-cjs/gateway.d.ts +2 -2
- package/dist-cjs/index.d.ts +2 -2
- package/dist-cjs/instruction-optimizer.cjs +3 -0
- package/dist-cjs/runtime-objects.d.ts +2 -13
- package/dist-cjs/troubleshooting-helper.cjs +99 -20
- package/dist-cjs/troubleshooting-helper.d.ts +0 -3
- package/dist-cjs/types.d.ts +49 -91
- package/package.json +2 -2
|
@@ -37,12 +37,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
37
37
|
};
|
|
38
38
|
})();
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = void 0;
|
|
40
41
|
exports.generateMD5Hash = generateMD5Hash;
|
|
41
42
|
exports.ensureTaskTypeId = ensureTaskTypeId;
|
|
42
43
|
exports.mergeConfig = mergeConfig;
|
|
43
44
|
exports.normalizeRouterUsageTokens = normalizeRouterUsageTokens;
|
|
44
45
|
exports.extractTokenUsageFromRouterResponse = extractTokenUsageFromRouterResponse;
|
|
45
46
|
exports.extractCostUsdFromRouterResponse = extractCostUsdFromRouterResponse;
|
|
47
|
+
exports.pickInvokeRoutingMetadataSlice = pickInvokeRoutingMetadataSlice;
|
|
48
|
+
exports.pickEffectiveModelConfigForMetadata = pickEffectiveModelConfigForMetadata;
|
|
49
|
+
exports.capActivityFullResponsePayload = capActivityFullResponsePayload;
|
|
46
50
|
const crypto = __importStar(require("crypto"));
|
|
47
51
|
const gateway_instructions_js_1 = require("./gateway-instructions.cjs");
|
|
48
52
|
const flex_md_loader_js_1 = require("./flex-md-loader.cjs");
|
|
@@ -224,53 +228,90 @@ function firstFiniteNumber(...vals) {
|
|
|
224
228
|
for (const v of vals) {
|
|
225
229
|
if (typeof v === 'number' && Number.isFinite(v))
|
|
226
230
|
return v;
|
|
231
|
+
if (typeof v === 'string' && v.trim() !== '') {
|
|
232
|
+
const n = Number(v);
|
|
233
|
+
if (Number.isFinite(n))
|
|
234
|
+
return n;
|
|
235
|
+
}
|
|
227
236
|
}
|
|
228
237
|
return undefined;
|
|
229
238
|
}
|
|
239
|
+
function isNonZeroTokenCount(n) {
|
|
240
|
+
return !!(n.prompt || n.completion || n.total);
|
|
241
|
+
}
|
|
230
242
|
/**
|
|
231
243
|
* Maps provider/router usage objects to gateway token counts (`metadata.tokens`, Activix, trace attempts).
|
|
232
|
-
* Handles promptTokens/inputTokens, OpenAI-style snake_case, and missing total (sum prompt+completion).
|
|
244
|
+
* Handles promptTokens/inputTokens, OpenAI-style snake_case, Responses-style input/output tokens, and missing total (sum prompt+completion).
|
|
233
245
|
*/
|
|
234
246
|
function normalizeRouterUsageTokens(usage) {
|
|
235
247
|
if (usage == null || typeof usage !== 'object')
|
|
236
248
|
return undefined;
|
|
237
249
|
const u = usage;
|
|
238
|
-
const prompt = firstFiniteNumber(u.promptTokens, u.inputTokens, u.prompt, u.prompt_tokens) ?? 0;
|
|
239
|
-
const completion = firstFiniteNumber(u.completionTokens, u.outputTokens, u.completion, u.completion_tokens) ?? 0;
|
|
240
|
-
let total = firstFiniteNumber(u.totalTokens, u.total_tokens) ?? 0;
|
|
250
|
+
const prompt = firstFiniteNumber(u.promptTokens, u.inputTokens, u.input_tokens, u.prompt, u.prompt_tokens) ?? 0;
|
|
251
|
+
const completion = firstFiniteNumber(u.completionTokens, u.outputTokens, u.output_tokens, u.completion, u.completion_tokens) ?? 0;
|
|
252
|
+
let total = firstFiniteNumber(u.totalTokens, u.total_tokens, u.total) ?? 0;
|
|
241
253
|
if (!total && (prompt || completion))
|
|
242
254
|
total = prompt + completion;
|
|
243
255
|
return { prompt, completion, total };
|
|
244
256
|
}
|
|
245
257
|
/**
|
|
246
|
-
*
|
|
258
|
+
* Collect usage from one router/provider envelope (single object).
|
|
259
|
+
* When followRaw is true, also reads `(rawResponse ?? raw).usage` on that envelope.
|
|
247
260
|
*/
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
const r = routerResponse;
|
|
253
|
-
const meta = r.metadata != null && typeof r.metadata === 'object'
|
|
254
|
-
? r.metadata
|
|
261
|
+
function collectUsageBucketsFromRoot(root, followRaw) {
|
|
262
|
+
const meta = root.metadata != null && typeof root.metadata === 'object'
|
|
263
|
+
? root.metadata
|
|
255
264
|
: undefined;
|
|
256
|
-
const buckets = [
|
|
265
|
+
const buckets = [root.usage];
|
|
257
266
|
if (meta) {
|
|
258
267
|
buckets.push(meta.usage);
|
|
268
|
+
buckets.push(meta.tokens);
|
|
259
269
|
const nested = meta['ai-activities-response'];
|
|
260
270
|
if (nested != null && typeof nested === 'object') {
|
|
261
271
|
buckets.push(nested.usage);
|
|
262
272
|
}
|
|
263
273
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
274
|
+
if (followRaw) {
|
|
275
|
+
const raw = root.rawResponse ?? root.raw;
|
|
276
|
+
if (raw != null && typeof raw === 'object') {
|
|
277
|
+
buckets.push(raw.usage);
|
|
278
|
+
}
|
|
267
279
|
}
|
|
280
|
+
return buckets;
|
|
281
|
+
}
|
|
282
|
+
function firstNonZeroUsageFromBuckets(buckets) {
|
|
268
283
|
for (const b of buckets) {
|
|
269
284
|
const n = normalizeRouterUsageTokens(b);
|
|
270
|
-
if (n && (n
|
|
285
|
+
if (n && isNonZeroTokenCount(n))
|
|
271
286
|
return n;
|
|
272
287
|
}
|
|
273
|
-
return
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Reads token usage from every stable location the router may populate (see docs/PROVIDERS_ROUTER_DIAGNOSTICS_TRACE_REQUIREMENTS.md).
|
|
292
|
+
* Prefers the raw/provider body (`rawResponse` / `raw`) when it carries non-zero usage before re-reading the outer envelope.
|
|
293
|
+
*/
|
|
294
|
+
function extractTokenUsageFromRouterResponse(routerResponse) {
|
|
295
|
+
const zeros = { prompt: 0, completion: 0, total: 0 };
|
|
296
|
+
if (routerResponse == null || typeof routerResponse !== 'object') {
|
|
297
|
+
return zeros;
|
|
298
|
+
}
|
|
299
|
+
const r = routerResponse;
|
|
300
|
+
const raw = r.rawResponse ?? r.raw;
|
|
301
|
+
const inner = raw != null && typeof raw === 'object' ? raw : undefined;
|
|
302
|
+
const roots = inner != null && inner !== r
|
|
303
|
+
? [
|
|
304
|
+
{ root: inner, followRaw: false },
|
|
305
|
+
{ root: r, followRaw: true }
|
|
306
|
+
]
|
|
307
|
+
: [{ root: r, followRaw: true }];
|
|
308
|
+
for (const { root, followRaw } of roots) {
|
|
309
|
+
const buckets = collectUsageBucketsFromRoot(root, followRaw);
|
|
310
|
+
const found = firstNonZeroUsageFromBuckets(buckets);
|
|
311
|
+
if (found)
|
|
312
|
+
return found;
|
|
313
|
+
}
|
|
314
|
+
return zeros;
|
|
274
315
|
}
|
|
275
316
|
/**
|
|
276
317
|
* Best-effort USD cost from router/sync AIResponse shape: metadata.costUsd (preferred),
|
|
@@ -319,3 +360,70 @@ function extractCostUsdFromRouterResponse(routerResponse) {
|
|
|
319
360
|
}
|
|
320
361
|
return undefined;
|
|
321
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
365
|
+
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
|
366
|
+
*/
|
|
367
|
+
function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
|
|
368
|
+
const rr = routerResponse != null && typeof routerResponse === 'object' ? routerResponse : {};
|
|
369
|
+
const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
|
|
370
|
+
const cfg = mergedConfig != null && typeof mergedConfig === 'object' ? mergedConfig : {};
|
|
371
|
+
const provider = meta.provider || rr.provider || cfg.provider;
|
|
372
|
+
const modelUsed = meta.modelUsed || meta.model || rr.model || cfg.model;
|
|
373
|
+
const maxTokensRequested = typeof meta.maxTokensRequested === 'number'
|
|
374
|
+
? meta.maxTokensRequested
|
|
375
|
+
: typeof cfg.maxTokens === 'number'
|
|
376
|
+
? cfg.maxTokens
|
|
377
|
+
: undefined;
|
|
378
|
+
const region = typeof meta.region === 'string' ? meta.region : undefined;
|
|
379
|
+
const out = {};
|
|
380
|
+
if (provider !== undefined && provider !== null)
|
|
381
|
+
out.provider = provider;
|
|
382
|
+
if (modelUsed !== undefined && modelUsed !== null)
|
|
383
|
+
out.modelUsed = modelUsed;
|
|
384
|
+
if (maxTokensRequested !== undefined)
|
|
385
|
+
out.maxTokensRequested = maxTokensRequested;
|
|
386
|
+
if (region !== undefined)
|
|
387
|
+
out.region = region;
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
|
|
392
|
+
*/
|
|
393
|
+
function pickEffectiveModelConfigForMetadata(mergedConfig) {
|
|
394
|
+
if (mergedConfig == null || typeof mergedConfig !== 'object')
|
|
395
|
+
return undefined;
|
|
396
|
+
const c = mergedConfig;
|
|
397
|
+
const keys = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
|
|
398
|
+
const out = {};
|
|
399
|
+
for (const k of keys) {
|
|
400
|
+
const v = c[k];
|
|
401
|
+
if (v !== undefined)
|
|
402
|
+
out[k] = v;
|
|
403
|
+
}
|
|
404
|
+
return Object.keys(out).length ? out : undefined;
|
|
405
|
+
}
|
|
406
|
+
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
407
|
+
exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
|
|
408
|
+
/**
|
|
409
|
+
* Size-cap a provider/router payload before storing on an activity record.
|
|
410
|
+
* Non-serializable values become a small marker object instead of throwing.
|
|
411
|
+
*/
|
|
412
|
+
function capActivityFullResponsePayload(payload, maxChars = exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS) {
|
|
413
|
+
if (payload == null)
|
|
414
|
+
return payload;
|
|
415
|
+
let serialized;
|
|
416
|
+
try {
|
|
417
|
+
serialized = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return { _truncated: true, _reason: 'not_serializable' };
|
|
421
|
+
}
|
|
422
|
+
if (serialized.length <= maxChars)
|
|
423
|
+
return payload;
|
|
424
|
+
return {
|
|
425
|
+
_truncated: true,
|
|
426
|
+
_originalCharLength: serialized.length,
|
|
427
|
+
_preview: serialized.slice(0, maxChars)
|
|
428
|
+
};
|
|
429
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Gateway Utilities Module
|
|
3
3
|
* Handles utility functions
|
|
4
4
|
*/
|
|
5
|
-
import type { ChatRequest, GatewayConfig } from './types.js';
|
|
5
|
+
import type { ChatRequest, GatewayConfig, ModelConfig } from './types.js';
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
7
|
/**
|
|
8
8
|
* Generates MD5 hash of a string
|
|
@@ -21,7 +21,7 @@ export declare function mergeConfig(request: ChatRequest & {
|
|
|
21
21
|
}, config: GatewayConfig, logger: Logxer): Promise<ChatRequest['config']>;
|
|
22
22
|
/**
|
|
23
23
|
* Maps provider/router usage objects to gateway token counts (`metadata.tokens`, Activix, trace attempts).
|
|
24
|
-
* Handles promptTokens/inputTokens, OpenAI-style snake_case, and missing total (sum prompt+completion).
|
|
24
|
+
* Handles promptTokens/inputTokens, OpenAI-style snake_case, Responses-style input/output tokens, and missing total (sum prompt+completion).
|
|
25
25
|
*/
|
|
26
26
|
export declare function normalizeRouterUsageTokens(usage: unknown): {
|
|
27
27
|
prompt: number;
|
|
@@ -30,6 +30,7 @@ export declare function normalizeRouterUsageTokens(usage: unknown): {
|
|
|
30
30
|
} | undefined;
|
|
31
31
|
/**
|
|
32
32
|
* Reads token usage from every stable location the router may populate (see docs/PROVIDERS_ROUTER_DIAGNOSTICS_TRACE_REQUIREMENTS.md).
|
|
33
|
+
* Prefers the raw/provider body (`rawResponse` / `raw`) when it carries non-zero usage before re-reading the outer envelope.
|
|
33
34
|
*/
|
|
34
35
|
export declare function extractTokenUsageFromRouterResponse(routerResponse: unknown): {
|
|
35
36
|
prompt: number;
|
|
@@ -42,3 +43,24 @@ export declare function extractTokenUsageFromRouterResponse(routerResponse: unkn
|
|
|
42
43
|
* Does not compute cost from tokens — adapters must populate normalized fields or raw usage.cost-style keys.
|
|
43
44
|
*/
|
|
44
45
|
export declare function extractCostUsdFromRouterResponse(routerResponse: unknown): number | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
48
|
+
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
|
49
|
+
*/
|
|
50
|
+
export declare function pickInvokeRoutingMetadataSlice(routerResponse: unknown, mergedConfig: unknown): Partial<{
|
|
51
|
+
provider: string;
|
|
52
|
+
modelUsed: string;
|
|
53
|
+
maxTokensRequested: number;
|
|
54
|
+
region: string;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
|
|
58
|
+
*/
|
|
59
|
+
export declare function pickEffectiveModelConfigForMetadata(mergedConfig: unknown): Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>> | undefined;
|
|
60
|
+
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
61
|
+
export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
|
|
62
|
+
/**
|
|
63
|
+
* Size-cap a provider/router payload before storing on an activity record.
|
|
64
|
+
* Non-serializable values become a small marker object instead of throwing.
|
|
65
|
+
*/
|
|
66
|
+
export declare function capActivityFullResponsePayload(payload: unknown, maxChars?: number): unknown;
|
|
@@ -36,8 +36,9 @@ function validateChatRequest(request) {
|
|
|
36
36
|
throw err;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
const GATEWAY_ACTION_TYPES = ['skill', 'preSkill', 'postSkill'];
|
|
39
40
|
/**
|
|
40
|
-
* Validates
|
|
41
|
+
* Validates AIInvokeRequest has required fields
|
|
41
42
|
*/
|
|
42
43
|
function validateAIRequest(request) {
|
|
43
44
|
if (!request.aiRequestId) {
|
|
@@ -47,6 +48,14 @@ function validateAIRequest(request) {
|
|
|
47
48
|
throw new Error('agentId is required for AI requests');
|
|
48
49
|
}
|
|
49
50
|
validateMandatoryRuntimeIdentity(request);
|
|
51
|
+
if (!request.actionType ||
|
|
52
|
+
!GATEWAY_ACTION_TYPES.includes(request.actionType)) {
|
|
53
|
+
throw new Error(`actionType is required and must be one of: ${GATEWAY_ACTION_TYPES.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
const ref = typeof request.actionRef === 'string' ? request.actionRef.trim() : '';
|
|
56
|
+
if (!ref) {
|
|
57
|
+
throw new Error('actionRef is required and must be a non-empty string');
|
|
58
|
+
}
|
|
50
59
|
// Reject input field - it has been removed
|
|
51
60
|
if ('input' in request && request.input !== undefined) {
|
|
52
61
|
const err = new Error(`The 'input' field has been removed. Use workingMemory.input instead for template rendering. Prompt templates should contain {{input}} which will be resolved from workingMemory.input.`);
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Gateway Validation Module
|
|
3
3
|
* Basic validation for clean proxy implementation
|
|
4
4
|
*/
|
|
5
|
-
import type { ChatRequest,
|
|
5
|
+
import type { ChatRequest, AIInvokeRequest } from './types.js';
|
|
6
6
|
/**
|
|
7
7
|
* Validates ChatRequest has required fields
|
|
8
8
|
*/
|
|
9
9
|
export declare function validateChatRequest(request: ChatRequest): void;
|
|
10
10
|
/**
|
|
11
|
-
* Validates
|
|
11
|
+
* Validates AIInvokeRequest has required fields
|
|
12
12
|
*/
|
|
13
|
-
export declare function validateAIRequest(request:
|
|
13
|
+
export declare function validateAIRequest(request: AIInvokeRequest): void;
|
package/dist-cjs/gateway.cjs
CHANGED
|
@@ -516,9 +516,19 @@ class AIGateway {
|
|
|
516
516
|
}
|
|
517
517
|
contentType = 'structured';
|
|
518
518
|
parsingMethod = 'flex-md';
|
|
519
|
-
|
|
519
|
+
let tokens = (0, gateway_utils_js_1.extractTokenUsageFromRouterResponse)(routerResponse);
|
|
520
|
+
if (!(tokens.prompt || tokens.completion || tokens.total)) {
|
|
521
|
+
const alt = routerResponse?.rawResponse ?? routerResponse?.raw;
|
|
522
|
+
if (alt != null && typeof alt === 'object' && alt !== routerResponse) {
|
|
523
|
+
const second = (0, gateway_utils_js_1.extractTokenUsageFromRouterResponse)(alt);
|
|
524
|
+
if (second.prompt || second.completion || second.total)
|
|
525
|
+
tokens = second;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
520
528
|
const resolvedCostUsd = (0, gateway_utils_js_1.extractCostUsdFromRouterResponse)(routerResponse);
|
|
521
529
|
const routerMetaForCost = routerResponse?.metadata || {};
|
|
530
|
+
const routingMetadataSlice = (0, gateway_utils_js_1.pickInvokeRoutingMetadataSlice)(routerResponse, mergedConfig);
|
|
531
|
+
const effectiveModelConfig = (0, gateway_utils_js_1.pickEffectiveModelConfigForMetadata)(mergedConfig);
|
|
522
532
|
const enhancedResponse = {
|
|
523
533
|
content: content,
|
|
524
534
|
parsedContent: parsedContent,
|
|
@@ -531,6 +541,8 @@ class AIGateway {
|
|
|
531
541
|
agentType: 'ai',
|
|
532
542
|
contentType,
|
|
533
543
|
parsingMethod,
|
|
544
|
+
...routingMetadataSlice,
|
|
545
|
+
...(effectiveModelConfig !== undefined ? { effectiveModelConfig } : {}),
|
|
534
546
|
...(typeof resolvedCostUsd === 'number'
|
|
535
547
|
? {
|
|
536
548
|
costUsd: resolvedCostUsd,
|
|
@@ -540,38 +552,32 @@ class AIGateway {
|
|
|
540
552
|
}
|
|
541
553
|
: {}),
|
|
542
554
|
...(traceEnabled
|
|
543
|
-
?
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
? meta.maxTokensRequested
|
|
550
|
-
: typeof mergedConfig?.maxTokens === 'number'
|
|
551
|
-
? mergedConfig.maxTokens
|
|
552
|
-
: undefined;
|
|
553
|
-
return {
|
|
554
|
-
provider,
|
|
555
|
-
region,
|
|
556
|
-
modelUsed,
|
|
557
|
-
maxTokensRequested,
|
|
558
|
-
requestIds: traceRequestIds,
|
|
559
|
-
retryCount: traceRetryCount,
|
|
560
|
-
fallbackCount: traceFallbackCount,
|
|
561
|
-
attempts: traceAttempts
|
|
562
|
-
};
|
|
563
|
-
})()
|
|
555
|
+
? {
|
|
556
|
+
requestIds: traceRequestIds,
|
|
557
|
+
retryCount: traceRetryCount,
|
|
558
|
+
fallbackCount: traceFallbackCount,
|
|
559
|
+
attempts: traceAttempts
|
|
560
|
+
}
|
|
564
561
|
: {})
|
|
565
562
|
}
|
|
566
563
|
};
|
|
567
564
|
// Track activity success if activity was started
|
|
568
565
|
if (activity) {
|
|
569
566
|
try {
|
|
567
|
+
const diag = request.diagnostics;
|
|
568
|
+
const includeFullProviderBlob = diag?.includeFullProviderResponseInActivity !== false;
|
|
569
|
+
const maxFullChars = typeof diag?.activityFullResponseMaxChars === 'number' && diag.activityFullResponseMaxChars > 0
|
|
570
|
+
? diag.activityFullResponseMaxChars
|
|
571
|
+
: gateway_utils_js_1.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS;
|
|
572
|
+
const rawFull = routerResponse.rawResponse || routerResponse;
|
|
573
|
+
const fullResponseForActivity = includeFullProviderBlob
|
|
574
|
+
? (0, gateway_utils_js_1.capActivityFullResponsePayload)(rawFull, maxFullChars)
|
|
575
|
+
: undefined;
|
|
570
576
|
// Create activity response with proper structure for ActivityTracker
|
|
571
577
|
const activityResponse = {
|
|
572
578
|
content: {
|
|
573
579
|
rawContent: content, // Store the actual response content as rawContent
|
|
574
|
-
|
|
580
|
+
...(fullResponseForActivity !== undefined ? { fullResponse: fullResponseForActivity } : {})
|
|
575
581
|
},
|
|
576
582
|
parsed: parsedContent, // Include parsed content in activity record
|
|
577
583
|
metadata: enhancedResponse.metadata,
|
package/dist-cjs/gateway.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Simplified AI Gateway - Clean proxy implementation
|
|
5
5
|
*/
|
|
6
6
|
import { LLMProviderRouter } from '@x12i/ai-providers-router';
|
|
7
|
-
import type { GatewayConfig, ChatRequest,
|
|
7
|
+
import type { GatewayConfig, ChatRequest, AIInvokeRequest, EnhancedLLMResponse } from './types.js';
|
|
8
8
|
import type { Logxer } from '@x12i/logxer';
|
|
9
9
|
import { ActivityManager } from './activity-manager.js';
|
|
10
10
|
/**
|
|
@@ -25,7 +25,7 @@ export declare class AIGateway {
|
|
|
25
25
|
/**
|
|
26
26
|
* Invoke AI request (with structured output support)
|
|
27
27
|
*/
|
|
28
|
-
invoke<TContent = unknown>(request:
|
|
28
|
+
invoke<TContent = unknown>(request: AIInvokeRequest): Promise<EnhancedLLMResponse<TContent>>;
|
|
29
29
|
/**
|
|
30
30
|
* Build simple messages from request (instructions and prompt as literal template text; no registry).
|
|
31
31
|
*/
|
package/dist-cjs/index.d.ts
CHANGED
|
@@ -16,11 +16,11 @@ 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, AIRequest, EnhancedLLMResponse, InstructionMetadata, ValidationRule,
|
|
19
|
+
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
|
|
20
20
|
export { mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
21
21
|
export type { UsageTier } from './types.js';
|
|
22
22
|
export { Activix } from '@x12i/activix';
|
|
23
|
-
export type { ActivixRunContext, FindByRunContextCriteria } from '@x12i/activix';
|
|
23
|
+
export type { ActivixRunContext, FindByRunContextCriteria, GetJobActivitiesInput, GetJobActivitiesResult } from '@x12i/activix';
|
|
24
24
|
export { ActivityManager, ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
25
25
|
export type { ActivityIdentity } from './types.js';
|
|
26
26
|
export { activityIdentityToLogMeta, withActivityIdentity, gatewayLogDebug } from './gateway-log-meta.js';
|
|
@@ -145,8 +145,11 @@ async function optimizeInstructions(gateway, originalInstructions, options) {
|
|
|
145
145
|
const optimizationRequest = {
|
|
146
146
|
aiRequestId,
|
|
147
147
|
agentId,
|
|
148
|
+
actionType: 'skill',
|
|
149
|
+
actionRef: 'internal/instruction-optimizer',
|
|
148
150
|
instructions: INSTRUCTION_OPTIMIZER_INSTRUCTIONS + additionalContext,
|
|
149
151
|
identity,
|
|
152
|
+
prompt: '{{input}}',
|
|
150
153
|
workingMemory: { input: originalInstructions },
|
|
151
154
|
config: {
|
|
152
155
|
model,
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import type { Logxer } from '@x12i/logxer';
|
|
2
|
-
import type { Activix } from '@x12i/activix';
|
|
3
|
-
export type ActivixQueryableClient
|
|
4
|
-
getJobActivities(input: {
|
|
5
|
-
jobId: string;
|
|
6
|
-
graphId?: string;
|
|
7
|
-
nodeId?: string;
|
|
8
|
-
limit?: number;
|
|
9
|
-
}): Promise<{
|
|
10
|
-
jobId: string;
|
|
11
|
-
graphRun?: unknown;
|
|
12
|
-
activities: unknown[];
|
|
13
|
-
}>;
|
|
14
|
-
};
|
|
2
|
+
import type { Activix, ActivixQueryableClient } from '@x12i/activix';
|
|
3
|
+
export type { ActivixQueryableClient } from '@x12i/activix';
|
|
15
4
|
export type LogxerQueryableClient = {
|
|
16
5
|
getJobLogs(input: {
|
|
17
6
|
jobId: string;
|
|
@@ -187,18 +187,27 @@ function validateSingleObjectType(objType, context, index) {
|
|
|
187
187
|
/**
|
|
188
188
|
* Validates AIRequest structure
|
|
189
189
|
*/
|
|
190
|
+
const GATEWAY_ACTION_TYPES_DIAG = ['skill', 'preSkill', 'postSkill'];
|
|
190
191
|
function validateAIRequest(request) {
|
|
191
192
|
const errors = [];
|
|
192
193
|
const warnings = [];
|
|
193
|
-
|
|
194
|
-
if (!request.jobId) {
|
|
195
|
-
errors.push('jobId is required');
|
|
194
|
+
const identityJobId = request.identity && typeof request.identity === 'object' ? request.identity.jobId : undefined;
|
|
195
|
+
if (!identityJobId && !request.jobId) {
|
|
196
|
+
errors.push('identity.jobId is required (legacy top-level jobId is deprecated)');
|
|
196
197
|
}
|
|
197
198
|
if (!request.agentId) {
|
|
198
199
|
errors.push('agentId is required');
|
|
199
200
|
}
|
|
201
|
+
if (!request.actionType ||
|
|
202
|
+
!GATEWAY_ACTION_TYPES_DIAG.includes(request.actionType)) {
|
|
203
|
+
errors.push('actionType is required and must be skill, preSkill, or postSkill');
|
|
204
|
+
}
|
|
205
|
+
const actionRefTrim = typeof request.actionRef === 'string' ? request.actionRef.trim() : '';
|
|
206
|
+
if (!actionRefTrim) {
|
|
207
|
+
errors.push('actionRef is required (non-empty string, e.g. skill id)');
|
|
208
|
+
}
|
|
200
209
|
if (!request.instructions) {
|
|
201
|
-
|
|
210
|
+
errors.push('instructions is required');
|
|
202
211
|
}
|
|
203
212
|
if (!request.prompt) {
|
|
204
213
|
errors.push('Prompt is required (input field has been removed - use workingMemory.input instead)');
|
|
@@ -449,11 +458,23 @@ function supportsJSONMode(provider, model) {
|
|
|
449
458
|
* Creates a test AIRequest with minimal valid structure
|
|
450
459
|
*/
|
|
451
460
|
function createTestAIRequest(overrides) {
|
|
452
|
-
|
|
453
|
-
|
|
461
|
+
const aiRequestId = 'test-ai-req-' + Date.now();
|
|
462
|
+
const base = {
|
|
463
|
+
aiRequestId,
|
|
454
464
|
agentId: 'test-agent',
|
|
465
|
+
actionType: 'skill',
|
|
466
|
+
actionRef: 'tests/createTestAIRequest',
|
|
455
467
|
instructions: 'Test instructions',
|
|
456
|
-
|
|
468
|
+
prompt: '{{input}}',
|
|
469
|
+
workingMemory: { input: 'Test input' },
|
|
470
|
+
identity: {
|
|
471
|
+
sessionId: `session-${aiRequestId}`,
|
|
472
|
+
instance: { instanceId: 'test-agent', type: 'test' },
|
|
473
|
+
aiRequestId,
|
|
474
|
+
jobId: `job-${aiRequestId}`,
|
|
475
|
+
agentId: 'test-agent',
|
|
476
|
+
taskId: `task-${aiRequestId}`
|
|
477
|
+
},
|
|
457
478
|
config: {
|
|
458
479
|
model: 'gpt-4o',
|
|
459
480
|
provider: 'openai'
|
|
@@ -470,20 +491,33 @@ function createTestAIRequest(overrides) {
|
|
|
470
491
|
},
|
|
471
492
|
...overrides
|
|
472
493
|
};
|
|
494
|
+
return base;
|
|
473
495
|
}
|
|
474
496
|
/**
|
|
475
497
|
* Creates test cases for validation
|
|
476
498
|
*/
|
|
477
499
|
function createValidationTestCases() {
|
|
500
|
+
const sampleIdentity = (jobId, agentId) => ({
|
|
501
|
+
sessionId: `session-${jobId}`,
|
|
502
|
+
instance: { instanceId: agentId, type: 'test' },
|
|
503
|
+
aiRequestId: `ai-${jobId}`,
|
|
504
|
+
jobId,
|
|
505
|
+
agentId,
|
|
506
|
+
taskId: `task-${jobId}`
|
|
507
|
+
});
|
|
478
508
|
return [
|
|
479
509
|
{
|
|
480
510
|
name: 'Valid minimal request',
|
|
481
511
|
request: {
|
|
482
|
-
|
|
512
|
+
aiRequestId: 'ai-valid-min',
|
|
483
513
|
agentId: 'agent-1',
|
|
514
|
+
actionType: 'skill',
|
|
515
|
+
actionRef: 'validation/minimal',
|
|
516
|
+
identity: sampleIdentity('test', 'agent-1'),
|
|
484
517
|
instructions: 'Test',
|
|
485
518
|
prompt: 'test.prompt',
|
|
486
|
-
workingMemory: { input: 'Test' }
|
|
519
|
+
workingMemory: { input: 'Test' },
|
|
520
|
+
config: { model: 'gpt-4o', provider: 'openai' }
|
|
487
521
|
},
|
|
488
522
|
shouldFail: false,
|
|
489
523
|
expectedErrors: []
|
|
@@ -491,8 +525,11 @@ function createValidationTestCases() {
|
|
|
491
525
|
{
|
|
492
526
|
name: 'Valid request with config',
|
|
493
527
|
request: {
|
|
494
|
-
|
|
528
|
+
aiRequestId: 'ai-valid-cfg',
|
|
495
529
|
agentId: 'agent-1',
|
|
530
|
+
actionType: 'skill',
|
|
531
|
+
actionRef: 'validation/config',
|
|
532
|
+
identity: sampleIdentity('test', 'agent-1'),
|
|
496
533
|
instructions: 'Test',
|
|
497
534
|
prompt: 'test.prompt',
|
|
498
535
|
workingMemory: { input: 'Test' },
|
|
@@ -505,23 +542,40 @@ function createValidationTestCases() {
|
|
|
505
542
|
expectedErrors: []
|
|
506
543
|
},
|
|
507
544
|
{
|
|
508
|
-
name: 'Missing jobId',
|
|
545
|
+
name: 'Missing identity.jobId',
|
|
509
546
|
request: {
|
|
547
|
+
aiRequestId: 'ai-no-job',
|
|
510
548
|
agentId: 'agent-1',
|
|
549
|
+
actionType: 'skill',
|
|
550
|
+
actionRef: 'validation/x',
|
|
551
|
+
identity: {
|
|
552
|
+
sessionId: 's',
|
|
553
|
+
instance: { instanceId: 'agent-1', type: 'test' },
|
|
554
|
+
aiRequestId: 'ai-no-job',
|
|
555
|
+
jobId: '',
|
|
556
|
+
agentId: 'agent-1',
|
|
557
|
+
taskId: 't'
|
|
558
|
+
},
|
|
511
559
|
instructions: 'Test',
|
|
512
560
|
prompt: 'test.prompt',
|
|
513
|
-
workingMemory: { input: 'Test' }
|
|
561
|
+
workingMemory: { input: 'Test' },
|
|
562
|
+
config: { model: 'gpt-4o', provider: 'openai' }
|
|
514
563
|
},
|
|
515
564
|
shouldFail: true,
|
|
516
|
-
expectedErrors: ['jobId is required']
|
|
565
|
+
expectedErrors: ['identity.jobId is required (legacy top-level jobId is deprecated)']
|
|
517
566
|
},
|
|
518
567
|
{
|
|
519
568
|
name: 'Missing agentId',
|
|
520
569
|
request: {
|
|
521
|
-
|
|
570
|
+
aiRequestId: 'ai-no-agent',
|
|
571
|
+
actionType: 'skill',
|
|
572
|
+
actionRef: 'validation/x',
|
|
573
|
+
identity: sampleIdentity('test', 'agent-1'),
|
|
522
574
|
instructions: 'Test',
|
|
523
575
|
prompt: 'test.prompt',
|
|
524
|
-
workingMemory: { input: 'Test' }
|
|
576
|
+
workingMemory: { input: 'Test' },
|
|
577
|
+
config: { model: 'gpt-4o', provider: 'openai' }
|
|
578
|
+
// intentional: no top-level agentId
|
|
525
579
|
},
|
|
526
580
|
shouldFail: true,
|
|
527
581
|
expectedErrors: ['agentId is required']
|
|
@@ -529,10 +583,14 @@ function createValidationTestCases() {
|
|
|
529
583
|
{
|
|
530
584
|
name: 'Missing instructions',
|
|
531
585
|
request: {
|
|
532
|
-
|
|
586
|
+
aiRequestId: 'ai-no-inst',
|
|
533
587
|
agentId: 'agent-1',
|
|
588
|
+
actionType: 'skill',
|
|
589
|
+
actionRef: 'validation/x',
|
|
590
|
+
identity: sampleIdentity('test', 'agent-1'),
|
|
534
591
|
prompt: 'test.prompt',
|
|
535
|
-
workingMemory: { input: 'Test' }
|
|
592
|
+
workingMemory: { input: 'Test' },
|
|
593
|
+
config: { model: 'gpt-4o', provider: 'openai' }
|
|
536
594
|
},
|
|
537
595
|
shouldFail: true,
|
|
538
596
|
expectedErrors: ['instructions is required']
|
|
@@ -540,12 +598,33 @@ function createValidationTestCases() {
|
|
|
540
598
|
{
|
|
541
599
|
name: 'Missing prompt',
|
|
542
600
|
request: {
|
|
543
|
-
|
|
601
|
+
aiRequestId: 'ai-no-prompt',
|
|
544
602
|
agentId: 'agent-1',
|
|
545
|
-
|
|
603
|
+
actionType: 'skill',
|
|
604
|
+
actionRef: 'validation/x',
|
|
605
|
+
identity: sampleIdentity('test', 'agent-1'),
|
|
606
|
+
instructions: 'Test',
|
|
607
|
+
workingMemory: { input: 'Test' },
|
|
608
|
+
config: { model: 'gpt-4o', provider: 'openai' }
|
|
609
|
+
},
|
|
610
|
+
shouldFail: true,
|
|
611
|
+
expectedErrors: ['Prompt is required (input field has been removed - use workingMemory.input instead)']
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
name: 'Missing actionRef',
|
|
615
|
+
request: {
|
|
616
|
+
aiRequestId: 'ai-no-ref',
|
|
617
|
+
agentId: 'agent-1',
|
|
618
|
+
actionType: 'skill',
|
|
619
|
+
actionRef: ' ',
|
|
620
|
+
identity: sampleIdentity('test', 'agent-1'),
|
|
621
|
+
instructions: 'Test',
|
|
622
|
+
prompt: 'p',
|
|
623
|
+
workingMemory: { input: 'Test' },
|
|
624
|
+
config: { model: 'gpt-4o', provider: 'openai' }
|
|
546
625
|
},
|
|
547
626
|
shouldFail: true,
|
|
548
|
-
expectedErrors: ['
|
|
627
|
+
expectedErrors: ['actionRef is required (non-empty string, e.g. skill id)']
|
|
549
628
|
}
|
|
550
629
|
];
|
|
551
630
|
}
|