@x12i/ai-gateway 9.0.9 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +9 -1
- package/dist/gateway-utils.js +79 -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 +20 -3
- 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 +39 -89
- 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 +81 -18
- package/dist-cjs/gateway-utils.d.ts +9 -1
- package/dist-cjs/gateway-validation.cjs +10 -1
- package/dist-cjs/gateway-validation.d.ts +3 -3
- package/dist-cjs/gateway.cjs +19 -2
- 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 +39 -89
- package/package.json +2 -2
|
@@ -37,12 +37,14 @@ 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.capActivityFullResponsePayload = capActivityFullResponsePayload;
|
|
46
48
|
const crypto = __importStar(require("crypto"));
|
|
47
49
|
const gateway_instructions_js_1 = require("./gateway-instructions.cjs");
|
|
48
50
|
const flex_md_loader_js_1 = require("./flex-md-loader.cjs");
|
|
@@ -224,53 +226,90 @@ function firstFiniteNumber(...vals) {
|
|
|
224
226
|
for (const v of vals) {
|
|
225
227
|
if (typeof v === 'number' && Number.isFinite(v))
|
|
226
228
|
return v;
|
|
229
|
+
if (typeof v === 'string' && v.trim() !== '') {
|
|
230
|
+
const n = Number(v);
|
|
231
|
+
if (Number.isFinite(n))
|
|
232
|
+
return n;
|
|
233
|
+
}
|
|
227
234
|
}
|
|
228
235
|
return undefined;
|
|
229
236
|
}
|
|
237
|
+
function isNonZeroTokenCount(n) {
|
|
238
|
+
return !!(n.prompt || n.completion || n.total);
|
|
239
|
+
}
|
|
230
240
|
/**
|
|
231
241
|
* 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).
|
|
242
|
+
* Handles promptTokens/inputTokens, OpenAI-style snake_case, Responses-style input/output tokens, and missing total (sum prompt+completion).
|
|
233
243
|
*/
|
|
234
244
|
function normalizeRouterUsageTokens(usage) {
|
|
235
245
|
if (usage == null || typeof usage !== 'object')
|
|
236
246
|
return undefined;
|
|
237
247
|
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;
|
|
248
|
+
const prompt = firstFiniteNumber(u.promptTokens, u.inputTokens, u.input_tokens, u.prompt, u.prompt_tokens) ?? 0;
|
|
249
|
+
const completion = firstFiniteNumber(u.completionTokens, u.outputTokens, u.output_tokens, u.completion, u.completion_tokens) ?? 0;
|
|
250
|
+
let total = firstFiniteNumber(u.totalTokens, u.total_tokens, u.total) ?? 0;
|
|
241
251
|
if (!total && (prompt || completion))
|
|
242
252
|
total = prompt + completion;
|
|
243
253
|
return { prompt, completion, total };
|
|
244
254
|
}
|
|
245
255
|
/**
|
|
246
|
-
*
|
|
256
|
+
* Collect usage from one router/provider envelope (single object).
|
|
257
|
+
* When followRaw is true, also reads `(rawResponse ?? raw).usage` on that envelope.
|
|
247
258
|
*/
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
const r = routerResponse;
|
|
253
|
-
const meta = r.metadata != null && typeof r.metadata === 'object'
|
|
254
|
-
? r.metadata
|
|
259
|
+
function collectUsageBucketsFromRoot(root, followRaw) {
|
|
260
|
+
const meta = root.metadata != null && typeof root.metadata === 'object'
|
|
261
|
+
? root.metadata
|
|
255
262
|
: undefined;
|
|
256
|
-
const buckets = [
|
|
263
|
+
const buckets = [root.usage];
|
|
257
264
|
if (meta) {
|
|
258
265
|
buckets.push(meta.usage);
|
|
266
|
+
buckets.push(meta.tokens);
|
|
259
267
|
const nested = meta['ai-activities-response'];
|
|
260
268
|
if (nested != null && typeof nested === 'object') {
|
|
261
269
|
buckets.push(nested.usage);
|
|
262
270
|
}
|
|
263
271
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
272
|
+
if (followRaw) {
|
|
273
|
+
const raw = root.rawResponse ?? root.raw;
|
|
274
|
+
if (raw != null && typeof raw === 'object') {
|
|
275
|
+
buckets.push(raw.usage);
|
|
276
|
+
}
|
|
267
277
|
}
|
|
278
|
+
return buckets;
|
|
279
|
+
}
|
|
280
|
+
function firstNonZeroUsageFromBuckets(buckets) {
|
|
268
281
|
for (const b of buckets) {
|
|
269
282
|
const n = normalizeRouterUsageTokens(b);
|
|
270
|
-
if (n && (n
|
|
283
|
+
if (n && isNonZeroTokenCount(n))
|
|
271
284
|
return n;
|
|
272
285
|
}
|
|
273
|
-
return
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Reads token usage from every stable location the router may populate (see docs/PROVIDERS_ROUTER_DIAGNOSTICS_TRACE_REQUIREMENTS.md).
|
|
290
|
+
* Prefers the raw/provider body (`rawResponse` / `raw`) when it carries non-zero usage before re-reading the outer envelope.
|
|
291
|
+
*/
|
|
292
|
+
function extractTokenUsageFromRouterResponse(routerResponse) {
|
|
293
|
+
const zeros = { prompt: 0, completion: 0, total: 0 };
|
|
294
|
+
if (routerResponse == null || typeof routerResponse !== 'object') {
|
|
295
|
+
return zeros;
|
|
296
|
+
}
|
|
297
|
+
const r = routerResponse;
|
|
298
|
+
const raw = r.rawResponse ?? r.raw;
|
|
299
|
+
const inner = raw != null && typeof raw === 'object' ? raw : undefined;
|
|
300
|
+
const roots = inner != null && inner !== r
|
|
301
|
+
? [
|
|
302
|
+
{ root: inner, followRaw: false },
|
|
303
|
+
{ root: r, followRaw: true }
|
|
304
|
+
]
|
|
305
|
+
: [{ root: r, followRaw: true }];
|
|
306
|
+
for (const { root, followRaw } of roots) {
|
|
307
|
+
const buckets = collectUsageBucketsFromRoot(root, followRaw);
|
|
308
|
+
const found = firstNonZeroUsageFromBuckets(buckets);
|
|
309
|
+
if (found)
|
|
310
|
+
return found;
|
|
311
|
+
}
|
|
312
|
+
return zeros;
|
|
274
313
|
}
|
|
275
314
|
/**
|
|
276
315
|
* Best-effort USD cost from router/sync AIResponse shape: metadata.costUsd (preferred),
|
|
@@ -319,3 +358,27 @@ function extractCostUsdFromRouterResponse(routerResponse) {
|
|
|
319
358
|
}
|
|
320
359
|
return undefined;
|
|
321
360
|
}
|
|
361
|
+
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
362
|
+
exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
|
|
363
|
+
/**
|
|
364
|
+
* Size-cap a provider/router payload before storing on an activity record.
|
|
365
|
+
* Non-serializable values become a small marker object instead of throwing.
|
|
366
|
+
*/
|
|
367
|
+
function capActivityFullResponsePayload(payload, maxChars = exports.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS) {
|
|
368
|
+
if (payload == null)
|
|
369
|
+
return payload;
|
|
370
|
+
let serialized;
|
|
371
|
+
try {
|
|
372
|
+
serialized = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return { _truncated: true, _reason: 'not_serializable' };
|
|
376
|
+
}
|
|
377
|
+
if (serialized.length <= maxChars)
|
|
378
|
+
return payload;
|
|
379
|
+
return {
|
|
380
|
+
_truncated: true,
|
|
381
|
+
_originalCharLength: serialized.length,
|
|
382
|
+
_preview: serialized.slice(0, maxChars)
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -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,10 @@ 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
|
+
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
47
|
+
export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
|
|
48
|
+
/**
|
|
49
|
+
* Size-cap a provider/router payload before storing on an activity record.
|
|
50
|
+
* Non-serializable values become a small marker object instead of throwing.
|
|
51
|
+
*/
|
|
52
|
+
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,7 +516,15 @@ 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 || {};
|
|
522
530
|
const enhancedResponse = {
|
|
@@ -567,11 +575,20 @@ class AIGateway {
|
|
|
567
575
|
// Track activity success if activity was started
|
|
568
576
|
if (activity) {
|
|
569
577
|
try {
|
|
578
|
+
const diag = request.diagnostics;
|
|
579
|
+
const includeFullProviderBlob = diag?.includeFullProviderResponseInActivity !== false;
|
|
580
|
+
const maxFullChars = typeof diag?.activityFullResponseMaxChars === 'number' && diag.activityFullResponseMaxChars > 0
|
|
581
|
+
? diag.activityFullResponseMaxChars
|
|
582
|
+
: gateway_utils_js_1.DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS;
|
|
583
|
+
const rawFull = routerResponse.rawResponse || routerResponse;
|
|
584
|
+
const fullResponseForActivity = includeFullProviderBlob
|
|
585
|
+
? (0, gateway_utils_js_1.capActivityFullResponsePayload)(rawFull, maxFullChars)
|
|
586
|
+
: undefined;
|
|
570
587
|
// Create activity response with proper structure for ActivityTracker
|
|
571
588
|
const activityResponse = {
|
|
572
589
|
content: {
|
|
573
590
|
rawContent: content, // Store the actual response content as rawContent
|
|
574
|
-
|
|
591
|
+
...(fullResponseForActivity !== undefined ? { fullResponse: fullResponseForActivity } : {})
|
|
575
592
|
},
|
|
576
593
|
parsed: parsedContent, // Include parsed content in activity record
|
|
577
594
|
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
|
}
|