cognitive-modules-cli 2.2.11 → 2.2.12
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/CHANGELOG.md +5 -0
- package/README.md +47 -40
- package/dist/cli-args.d.ts +34 -0
- package/dist/cli-args.js +149 -0
- package/dist/cli.js +86 -75
- package/dist/commands/add.js +2 -2
- package/dist/commands/compose.js +1 -24
- package/dist/commands/conformance.d.ts +39 -0
- package/dist/commands/conformance.js +517 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/pipe.js +1 -16
- package/dist/commands/run.js +1 -22
- package/dist/modules/composition.d.ts +3 -3
- package/dist/modules/composition.js +4 -3
- package/dist/modules/json-extract.d.ts +7 -0
- package/dist/modules/json-extract.js +132 -0
- package/dist/modules/runner.d.ts +11 -1
- package/dist/modules/runner.js +614 -47
- package/dist/policy-summary.d.ts +16 -0
- package/dist/policy-summary.js +33 -0
- package/dist/profile.d.ts +1 -0
- package/dist/profile.js +38 -17
- package/dist/providers/base.d.ts +2 -1
- package/dist/providers/base.js +6 -0
- package/dist/providers/gemini.d.ts +5 -0
- package/dist/providers/gemini.js +28 -4
- package/dist/providers/index.d.ts +10 -1
- package/dist/providers/index.js +34 -8
- package/dist/providers/moonshot.d.ts +3 -0
- package/dist/providers/moonshot.js +58 -13
- package/dist/registry/assets.d.ts +8 -0
- package/dist/registry/assets.js +48 -13
- package/dist/types.d.ts +43 -3
- package/package.json +3 -2
package/dist/modules/runner.js
CHANGED
|
@@ -9,10 +9,37 @@ import * as fs from 'node:fs/promises';
|
|
|
9
9
|
import * as path from 'node:path';
|
|
10
10
|
import { aggregateRisk, isV22Envelope } from '../types.js';
|
|
11
11
|
import { readModuleProvenance, verifyModuleIntegrity } from '../provenance.js';
|
|
12
|
+
import { extractJsonCandidates } from './json-extract.js';
|
|
13
|
+
import { compactReason, formatPolicySummaryLine } from '../policy-summary.js';
|
|
12
14
|
// =============================================================================
|
|
13
15
|
// Schema Validation
|
|
14
16
|
// =============================================================================
|
|
15
17
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
18
|
+
function safeSnippet(s, max = 500) {
|
|
19
|
+
const raw = String(s ?? '');
|
|
20
|
+
if (raw.length <= max)
|
|
21
|
+
return raw;
|
|
22
|
+
return raw.slice(0, max) + `…(+${raw.length - max} chars)`;
|
|
23
|
+
}
|
|
24
|
+
function parseJsonWithCandidates(raw) {
|
|
25
|
+
const candidates = extractJsonCandidates(raw);
|
|
26
|
+
const attempts = [];
|
|
27
|
+
for (const c of candidates) {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(c.json.trim());
|
|
30
|
+
return { parsed, extracted: c, attempts };
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
attempts.push({ strategy: c.strategy, error: e.message });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const err = new Error(attempts[0]?.error ?? 'Unable to parse JSON');
|
|
37
|
+
err.details = {
|
|
38
|
+
parse_attempts: attempts,
|
|
39
|
+
raw_response_snippet: safeSnippet(raw, 500),
|
|
40
|
+
};
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
16
43
|
/**
|
|
17
44
|
* Validate data against JSON schema. Returns list of errors.
|
|
18
45
|
*/
|
|
@@ -1061,7 +1088,7 @@ async function enforcePolicyGates(module, policy) {
|
|
|
1061
1088
|
explain: 'Refused by execution policy.',
|
|
1062
1089
|
confidence: 1.0,
|
|
1063
1090
|
risk: 'none',
|
|
1064
|
-
suggestion: 'Migrate the module to v2.2, or
|
|
1091
|
+
suggestion: 'Migrate the module to v2.2, or rerun with --profile standard.',
|
|
1065
1092
|
});
|
|
1066
1093
|
}
|
|
1067
1094
|
}
|
|
@@ -1088,7 +1115,7 @@ async function enforcePolicyGates(module, policy) {
|
|
|
1088
1115
|
explain: 'Refused by execution policy.',
|
|
1089
1116
|
confidence: 1.0,
|
|
1090
1117
|
risk: 'none',
|
|
1091
|
-
suggestion: 'Install the module via `cog add <name>` (registry tarball) or
|
|
1118
|
+
suggestion: 'Install the module via `cog add <name>` (registry tarball) or rerun with --profile standard.',
|
|
1092
1119
|
});
|
|
1093
1120
|
}
|
|
1094
1121
|
}
|
|
@@ -1120,7 +1147,7 @@ async function enforcePolicyGates(module, policy) {
|
|
|
1120
1147
|
explain: 'Refused by execution policy.',
|
|
1121
1148
|
confidence: 1.0,
|
|
1122
1149
|
risk: 'none',
|
|
1123
|
-
suggestion: 'Reinstall the module from a registry tarball and retry, or
|
|
1150
|
+
suggestion: 'Reinstall the module from a registry tarball and retry, or rerun with --profile standard.',
|
|
1124
1151
|
});
|
|
1125
1152
|
}
|
|
1126
1153
|
// Integrity check (tamper detection).
|
|
@@ -1142,11 +1169,209 @@ async function enforcePolicyGates(module, policy) {
|
|
|
1142
1169
|
return null;
|
|
1143
1170
|
return null;
|
|
1144
1171
|
}
|
|
1172
|
+
function resolveValidationFlags(module, policy, validateInputOpt, validateOutputOpt) {
|
|
1173
|
+
// Explicit overrides win.
|
|
1174
|
+
if (typeof validateInputOpt === 'boolean' || typeof validateOutputOpt === 'boolean') {
|
|
1175
|
+
const validateInput = typeof validateInputOpt === 'boolean' ? validateInputOpt : true;
|
|
1176
|
+
const validateOutput = typeof validateOutputOpt === 'boolean' ? validateOutputOpt : true;
|
|
1177
|
+
return { validateInput, validateOutput, reason: 'explicit validateInput/validateOutput' };
|
|
1178
|
+
}
|
|
1179
|
+
if (!policy) {
|
|
1180
|
+
return { validateInput: true, validateOutput: true, reason: 'no policy (default on)' };
|
|
1181
|
+
}
|
|
1182
|
+
if (policy.validate === 'off') {
|
|
1183
|
+
return { validateInput: false, validateOutput: false, reason: 'policy.validate=off' };
|
|
1184
|
+
}
|
|
1185
|
+
if (policy.validate === 'on') {
|
|
1186
|
+
return { validateInput: true, validateOutput: true, reason: 'policy.validate=on' };
|
|
1187
|
+
}
|
|
1188
|
+
// auto: decide based on module intent.
|
|
1189
|
+
const tier = module.tier ?? 'decision';
|
|
1190
|
+
const strictness = module.schemaStrictness ?? 'medium';
|
|
1191
|
+
// Certified flows already set policy.validate=on in resolveExecutionPolicy().
|
|
1192
|
+
// Keep auto conservative for exec/decision.
|
|
1193
|
+
// For exploration: do not validate inputs by default, but still validate outputs post-hoc.
|
|
1194
|
+
// This preserves "5-minute path" ergonomics while keeping envelopes structurally reliable.
|
|
1195
|
+
if (strictness === 'high') {
|
|
1196
|
+
return { validateInput: true, validateOutput: true, reason: `auto: schema_strictness=high (tier=${tier})` };
|
|
1197
|
+
}
|
|
1198
|
+
if (tier === 'exploration') {
|
|
1199
|
+
return { validateInput: false, validateOutput: true, reason: 'auto: tier=exploration (post-hoc output only)' };
|
|
1200
|
+
}
|
|
1201
|
+
// exec + decision default to validation on.
|
|
1202
|
+
return { validateInput: true, validateOutput: true, reason: `auto: tier=${tier}` };
|
|
1203
|
+
}
|
|
1204
|
+
function getProviderCapabilities(provider) {
|
|
1205
|
+
const caps = provider.getCapabilities?.();
|
|
1206
|
+
if (caps)
|
|
1207
|
+
return caps;
|
|
1208
|
+
return {
|
|
1209
|
+
structuredOutput: 'prompt',
|
|
1210
|
+
streaming: provider.supportsStreaming?.() ?? false,
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function providerSupportsNativeStructuredOutput(caps) {
|
|
1214
|
+
return caps.structuredOutput === 'native';
|
|
1215
|
+
}
|
|
1216
|
+
function providerSupportsNativeJsonSchema(caps) {
|
|
1217
|
+
if (!providerSupportsNativeStructuredOutput(caps))
|
|
1218
|
+
return false;
|
|
1219
|
+
const dialect = caps.nativeSchemaDialect ?? 'json-schema';
|
|
1220
|
+
return dialect === 'json-schema';
|
|
1221
|
+
}
|
|
1222
|
+
function schemaByteLength(schema) {
|
|
1223
|
+
try {
|
|
1224
|
+
return Buffer.byteLength(JSON.stringify(schema), 'utf8');
|
|
1225
|
+
}
|
|
1226
|
+
catch {
|
|
1227
|
+
// If something is non-serializable, treat as huge and disable native.
|
|
1228
|
+
return Number.MAX_SAFE_INTEGER;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
function resolveJsonSchemaParams(module, provider, validateOutput, structured) {
|
|
1232
|
+
if (!validateOutput)
|
|
1233
|
+
return {};
|
|
1234
|
+
if (!module.outputSchema)
|
|
1235
|
+
return {};
|
|
1236
|
+
const pref = structured ?? 'auto';
|
|
1237
|
+
if (pref === 'off')
|
|
1238
|
+
return { policy: { requested: pref, resolved: 'off', reason: 'structured=off' } };
|
|
1239
|
+
if (pref === 'prompt') {
|
|
1240
|
+
return { jsonSchema: module.outputSchema, jsonSchemaMode: 'prompt', allowSchemaFallback: false, policy: { requested: pref, resolved: 'prompt', reason: 'structured=prompt' } };
|
|
1241
|
+
}
|
|
1242
|
+
const caps = getProviderCapabilities(provider);
|
|
1243
|
+
if (caps.structuredOutput === 'none')
|
|
1244
|
+
return {};
|
|
1245
|
+
if (pref === 'native') {
|
|
1246
|
+
// "native" means "prefer native", not "fail hard".
|
|
1247
|
+
// If the provider doesn't support native structured output at all, safely downgrade to prompt guidance.
|
|
1248
|
+
//
|
|
1249
|
+
// Important: "native" only covers JSON Schema-native providers. For providers whose schema dialect is
|
|
1250
|
+
// not JSON Schema (e.g. Gemini responseSchema), we do NOT attempt to translate arbitrary JSON Schema
|
|
1251
|
+
// into the provider dialect (too many subset incompatibilities).
|
|
1252
|
+
//
|
|
1253
|
+
// Instead, we safely downgrade to prompt-only JSON guidance and rely on post-validation. This avoids
|
|
1254
|
+
// user-visible 400s and keeps the protocol contract stable.
|
|
1255
|
+
if (!providerSupportsNativeStructuredOutput(caps)) {
|
|
1256
|
+
const dialect = caps.nativeSchemaDialect ?? 'unknown';
|
|
1257
|
+
return {
|
|
1258
|
+
jsonSchema: module.outputSchema,
|
|
1259
|
+
jsonSchemaMode: 'prompt',
|
|
1260
|
+
allowSchemaFallback: false,
|
|
1261
|
+
policy: {
|
|
1262
|
+
requested: pref,
|
|
1263
|
+
resolved: 'prompt',
|
|
1264
|
+
reason: `provider lacks native structured output (${caps.structuredOutput}); dialect=${dialect}`,
|
|
1265
|
+
},
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
const dialect = caps.nativeSchemaDialect ?? 'json-schema';
|
|
1269
|
+
if (dialect !== 'json-schema') {
|
|
1270
|
+
return {
|
|
1271
|
+
jsonSchema: module.outputSchema,
|
|
1272
|
+
jsonSchemaMode: 'prompt',
|
|
1273
|
+
allowSchemaFallback: false,
|
|
1274
|
+
policy: {
|
|
1275
|
+
requested: pref,
|
|
1276
|
+
resolved: 'prompt',
|
|
1277
|
+
reason: `native schema dialect is not JSON Schema (${dialect}); using prompt-only guidance`,
|
|
1278
|
+
},
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
const maxBytes = caps.maxNativeSchemaBytes;
|
|
1282
|
+
if (typeof maxBytes === 'number' && maxBytes > 0) {
|
|
1283
|
+
const bytes = schemaByteLength(module.outputSchema);
|
|
1284
|
+
if (bytes > maxBytes) {
|
|
1285
|
+
return {
|
|
1286
|
+
jsonSchema: module.outputSchema,
|
|
1287
|
+
jsonSchemaMode: 'prompt',
|
|
1288
|
+
allowSchemaFallback: false,
|
|
1289
|
+
policy: { requested: pref, resolved: 'prompt', reason: `native schema too large (${bytes}B > ${maxBytes}B)` },
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
// Some providers accept only a restricted schema subset and can reject otherwise-valid JSON Schema.
|
|
1294
|
+
// Retrying once in prompt mode yields a much better UX while still keeping the initial attempt "native".
|
|
1295
|
+
return {
|
|
1296
|
+
jsonSchema: module.outputSchema,
|
|
1297
|
+
jsonSchemaMode: 'native',
|
|
1298
|
+
allowSchemaFallback: true,
|
|
1299
|
+
policy: {
|
|
1300
|
+
requested: pref,
|
|
1301
|
+
resolved: 'native',
|
|
1302
|
+
reason: 'structured=native',
|
|
1303
|
+
},
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
// auto: choose based on provider capabilities.
|
|
1307
|
+
let jsonSchemaMode = providerSupportsNativeJsonSchema(caps) ? 'native' : 'prompt';
|
|
1308
|
+
let sizeReason = null;
|
|
1309
|
+
if (jsonSchemaMode === 'native') {
|
|
1310
|
+
const maxBytes = caps.maxNativeSchemaBytes;
|
|
1311
|
+
if (typeof maxBytes === 'number' && maxBytes > 0) {
|
|
1312
|
+
const bytes = schemaByteLength(module.outputSchema);
|
|
1313
|
+
if (bytes > maxBytes) {
|
|
1314
|
+
jsonSchemaMode = 'prompt';
|
|
1315
|
+
sizeReason = `native schema too large (${bytes}B > ${maxBytes}B)`;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return {
|
|
1320
|
+
jsonSchema: module.outputSchema,
|
|
1321
|
+
jsonSchemaMode,
|
|
1322
|
+
allowSchemaFallback: true,
|
|
1323
|
+
policy: {
|
|
1324
|
+
requested: pref,
|
|
1325
|
+
resolved: jsonSchemaMode,
|
|
1326
|
+
reason: sizeReason
|
|
1327
|
+
? `auto: ${sizeReason}`
|
|
1328
|
+
: providerSupportsNativeJsonSchema(caps)
|
|
1329
|
+
? `auto: native JSON Schema supported`
|
|
1330
|
+
: caps.structuredOutput === 'native'
|
|
1331
|
+
? `auto: native schema dialect is not JSON Schema (${caps.nativeSchemaDialect ?? 'unknown'})`
|
|
1332
|
+
: `auto: provider structuredOutput=${caps.structuredOutput}`,
|
|
1333
|
+
},
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function isSchemaCompatibilityError(e) {
|
|
1337
|
+
const msg = e instanceof Error ? e.message : String(e ?? '');
|
|
1338
|
+
return (msg.includes('responseSchema') ||
|
|
1339
|
+
msg.includes('response_schema') ||
|
|
1340
|
+
msg.includes('Invalid JSON payload') ||
|
|
1341
|
+
msg.includes('INVALID_ARGUMENT') ||
|
|
1342
|
+
msg.includes('Unknown name') ||
|
|
1343
|
+
msg.includes('should be non-empty for OBJECT type'));
|
|
1344
|
+
}
|
|
1345
|
+
function resolveStructuredSchemaPlan(module, provider, validateOutput, structuredPref, policy) {
|
|
1346
|
+
// If output validation is disabled, never pass schema hints to providers.
|
|
1347
|
+
if (!validateOutput)
|
|
1348
|
+
return {};
|
|
1349
|
+
if (!module.outputSchema)
|
|
1350
|
+
return {};
|
|
1351
|
+
const requested = structuredPref ?? 'auto';
|
|
1352
|
+
// Progressive Complexity trigger:
|
|
1353
|
+
// When validate is `auto` and tier=exploration, default to "post-hoc validation only":
|
|
1354
|
+
// do not enforce/guide with schemas at the provider layer unless the user explicitly opts in.
|
|
1355
|
+
const tier = module.tier ?? 'decision';
|
|
1356
|
+
const strictness = module.schemaStrictness ?? 'medium';
|
|
1357
|
+
if (requested === 'auto' && policy?.validate === 'auto' && tier === 'exploration' && strictness !== 'high') {
|
|
1358
|
+
return {
|
|
1359
|
+
policy: {
|
|
1360
|
+
requested,
|
|
1361
|
+
resolved: 'off',
|
|
1362
|
+
reason: 'auto: tier=exploration defaults to post-hoc validation (no provider schema hints)',
|
|
1363
|
+
},
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
return resolveJsonSchemaParams(module, provider, validateOutput, requested);
|
|
1367
|
+
}
|
|
1145
1368
|
// =============================================================================
|
|
1146
1369
|
// Main Runner
|
|
1147
1370
|
// =============================================================================
|
|
1148
1371
|
export async function runModule(module, provider, options = {}) {
|
|
1149
|
-
const { args, input, verbose = false, validateInput
|
|
1372
|
+
const { args, input, verbose = false, validateInput: validateInputOpt, validateOutput: validateOutputOpt, useEnvelope, useV22, enableRepair: enableRepairOpt, traceId, model: modelOverride, policy, structured: structuredOverride, } = options;
|
|
1373
|
+
const { validateInput, validateOutput, reason: validateReason } = resolveValidationFlags(module, policy, validateInputOpt, validateOutputOpt);
|
|
1374
|
+
const enableRepair = enableRepairOpt ?? policy?.enableRepair ?? true;
|
|
1150
1375
|
const startTime = Date.now();
|
|
1151
1376
|
const gate = await enforcePolicyGates(module, policy);
|
|
1152
1377
|
if (gate) {
|
|
@@ -1204,11 +1429,34 @@ export async function runModule(module, provider, options = {}) {
|
|
|
1204
1429
|
}
|
|
1205
1430
|
// Build prompt with clean substitution
|
|
1206
1431
|
const prompt = buildPrompt(module, inputData);
|
|
1432
|
+
const effectiveStructuredPref = structuredOverride ?? policy?.structured;
|
|
1433
|
+
const structuredPlan = resolveStructuredSchemaPlan(module, provider, validateOutput, effectiveStructuredPref, policy);
|
|
1207
1434
|
if (verbose) {
|
|
1208
1435
|
console.error('--- Module ---');
|
|
1209
1436
|
console.error(`Name: ${module.name} (${module.format})`);
|
|
1210
1437
|
console.error(`Responsibility: ${module.responsibility}`);
|
|
1211
1438
|
console.error(`Envelope: ${shouldUseEnvelope}`);
|
|
1439
|
+
if (policy) {
|
|
1440
|
+
console.error('--- Policy ---');
|
|
1441
|
+
const requested = effectiveStructuredPref ?? 'auto';
|
|
1442
|
+
const applied = structuredPlan.jsonSchemaMode ?? 'off';
|
|
1443
|
+
const sReason = structuredPlan.policy?.reason ??
|
|
1444
|
+
(structuredPlan.jsonSchemaMode ? 'auto: schema hints enabled' : 'auto: schema hints disabled');
|
|
1445
|
+
console.error(formatPolicySummaryLine(policy, { validateInput, validateOutput, reason: validateReason }, { requested, applied, reason: sReason }, { enableRepair, requireV22: policy.requireV22 }));
|
|
1446
|
+
console.error(JSON.stringify({
|
|
1447
|
+
profile: policy.profile,
|
|
1448
|
+
validate: policy.validate,
|
|
1449
|
+
validateInput,
|
|
1450
|
+
validateOutput,
|
|
1451
|
+
validate_reason: validateReason,
|
|
1452
|
+
audit: policy.audit,
|
|
1453
|
+
enableRepair,
|
|
1454
|
+
structured: requested,
|
|
1455
|
+
structured_effective: applied,
|
|
1456
|
+
structured_reason: structuredPlan.policy?.reason ?? null,
|
|
1457
|
+
requireV22: policy.requireV22,
|
|
1458
|
+
}, null, 2));
|
|
1459
|
+
}
|
|
1212
1460
|
console.error('--- Input ---');
|
|
1213
1461
|
console.error(JSON.stringify(inputData, null, 2));
|
|
1214
1462
|
console.error('--- Prompt ---');
|
|
@@ -1278,11 +1526,58 @@ export async function runModule(module, provider, options = {}) {
|
|
|
1278
1526
|
];
|
|
1279
1527
|
try {
|
|
1280
1528
|
// Invoke provider
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1529
|
+
const { allowSchemaFallback, policy: structuredPolicy, ...invokeSchemaParams } = structuredPlan;
|
|
1530
|
+
const invokeParams = { ...invokeSchemaParams };
|
|
1531
|
+
const capsSnapshot = getProviderCapabilities(provider);
|
|
1532
|
+
const plannedSchemaMode = invokeParams.jsonSchema && typeof invokeParams.jsonSchema === 'object'
|
|
1533
|
+
? (invokeParams.jsonSchemaMode ?? 'prompt')
|
|
1534
|
+
: 'off';
|
|
1535
|
+
let appliedSchemaMode = plannedSchemaMode;
|
|
1536
|
+
let schemaFallbackAttempted = false;
|
|
1537
|
+
let schemaFallbackReason = null;
|
|
1538
|
+
let result;
|
|
1539
|
+
try {
|
|
1540
|
+
result = await provider.invoke({
|
|
1541
|
+
messages,
|
|
1542
|
+
// Progressive Complexity: only enforce schema at the provider layer when validation is enabled.
|
|
1543
|
+
...invokeParams,
|
|
1544
|
+
temperature: 0.3,
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
catch (e) {
|
|
1548
|
+
// If the provider rejects native structured output schemas, retry once in prompt mode.
|
|
1549
|
+
if (allowSchemaFallback &&
|
|
1550
|
+
invokeParams.jsonSchema &&
|
|
1551
|
+
invokeParams.jsonSchemaMode === 'native' &&
|
|
1552
|
+
isSchemaCompatibilityError(e)) {
|
|
1553
|
+
schemaFallbackAttempted = true;
|
|
1554
|
+
schemaFallbackReason = compactReason(e instanceof Error ? e.message : String(e ?? ''), 180);
|
|
1555
|
+
invokeParams.jsonSchemaMode = 'prompt';
|
|
1556
|
+
appliedSchemaMode = 'prompt';
|
|
1557
|
+
result = await provider.invoke({
|
|
1558
|
+
messages,
|
|
1559
|
+
...invokeParams,
|
|
1560
|
+
temperature: 0.3,
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
else {
|
|
1564
|
+
throw e;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
const structuredPolicyMeta = structuredPolicy
|
|
1568
|
+
? {
|
|
1569
|
+
...structuredPolicy,
|
|
1570
|
+
planned: structuredPolicy.resolved,
|
|
1571
|
+
applied: appliedSchemaMode,
|
|
1572
|
+
downgraded: appliedSchemaMode !== structuredPolicy.resolved,
|
|
1573
|
+
fallback: schemaFallbackAttempted ? { attempted: true, reason: schemaFallbackReason ?? undefined } : { attempted: false },
|
|
1574
|
+
provider: {
|
|
1575
|
+
structuredOutput: capsSnapshot.structuredOutput,
|
|
1576
|
+
nativeSchemaDialect: capsSnapshot.nativeSchemaDialect,
|
|
1577
|
+
maxNativeSchemaBytes: capsSnapshot.maxNativeSchemaBytes,
|
|
1578
|
+
},
|
|
1579
|
+
}
|
|
1580
|
+
: undefined;
|
|
1286
1581
|
if (verbose) {
|
|
1287
1582
|
console.error('--- Response ---');
|
|
1288
1583
|
console.error(result.content);
|
|
@@ -1292,21 +1587,87 @@ export async function runModule(module, provider, options = {}) {
|
|
|
1292
1587
|
const latencyMs = Date.now() - startTime;
|
|
1293
1588
|
// Parse response
|
|
1294
1589
|
let parsed;
|
|
1590
|
+
let parseExtracted = null;
|
|
1591
|
+
let parseAttempts = [];
|
|
1592
|
+
let parseRetries = 0;
|
|
1295
1593
|
try {
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
|
|
1594
|
+
const r = parseJsonWithCandidates(result.content);
|
|
1595
|
+
parsed = r.parsed;
|
|
1596
|
+
parseExtracted = r.extracted;
|
|
1597
|
+
parseAttempts = r.attempts;
|
|
1299
1598
|
}
|
|
1300
1599
|
catch (e) {
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1600
|
+
const allowParseRetry = policy?.profile !== 'certified';
|
|
1601
|
+
const firstDetails = e?.details;
|
|
1602
|
+
if (firstDetails && typeof firstDetails === 'object' && Array.isArray(firstDetails.parse_attempts)) {
|
|
1603
|
+
parseAttempts = firstDetails.parse_attempts;
|
|
1604
|
+
}
|
|
1605
|
+
if (!allowParseRetry) {
|
|
1606
|
+
const details = typeof firstDetails === 'object' && firstDetails
|
|
1607
|
+
? firstDetails
|
|
1608
|
+
: { raw_response_snippet: safeSnippet(result.content, 500) };
|
|
1609
|
+
const errorResult = makeErrorResponse({
|
|
1610
|
+
code: 'E1000', // PARSE_ERROR
|
|
1611
|
+
message: `Failed to parse JSON response: ${e.message}`,
|
|
1612
|
+
explain: 'Failed to parse LLM response as JSON.',
|
|
1613
|
+
details: {
|
|
1614
|
+
...details,
|
|
1615
|
+
parse_retry: { attempted: false, reason: 'profile=certified (fail-fast)' },
|
|
1616
|
+
},
|
|
1617
|
+
suggestion: 'The LLM response was not valid JSON. Fix the module/provider output or switch provider.',
|
|
1618
|
+
});
|
|
1619
|
+
_invokeErrorHooks(module.name, e, null);
|
|
1620
|
+
return errorResult;
|
|
1621
|
+
}
|
|
1622
|
+
// Retry once with stronger formatting instructions (prompt-only enforcement).
|
|
1623
|
+
parseRetries = 1;
|
|
1624
|
+
const retryMessages = [
|
|
1625
|
+
...messages,
|
|
1626
|
+
{
|
|
1627
|
+
role: 'user',
|
|
1628
|
+
content: 'Your previous response was not valid JSON.\n\nReturn ONLY a single valid JSON value (no markdown, no code fences, no commentary, no trailing text).',
|
|
1629
|
+
},
|
|
1630
|
+
];
|
|
1631
|
+
try {
|
|
1632
|
+
const retryResult = await provider.invoke({
|
|
1633
|
+
messages: retryMessages,
|
|
1634
|
+
...invokeParams,
|
|
1635
|
+
temperature: 0.3,
|
|
1636
|
+
});
|
|
1637
|
+
if (verbose) {
|
|
1638
|
+
console.error('--- Response (retry) ---');
|
|
1639
|
+
console.error(retryResult.content);
|
|
1640
|
+
console.error('--- End Response (retry) ---');
|
|
1641
|
+
}
|
|
1642
|
+
const r2 = parseJsonWithCandidates(retryResult.content);
|
|
1643
|
+
parsed = r2.parsed;
|
|
1644
|
+
parseExtracted = r2.extracted;
|
|
1645
|
+
parseAttempts = [...parseAttempts, ...r2.attempts];
|
|
1646
|
+
}
|
|
1647
|
+
catch (e2) {
|
|
1648
|
+
const combinedAttempts = [
|
|
1649
|
+
...parseAttempts,
|
|
1650
|
+
...((typeof e2?.details === 'object' && e2?.details && Array.isArray(e2.details.parse_attempts))
|
|
1651
|
+
? e2.details.parse_attempts
|
|
1652
|
+
: []),
|
|
1653
|
+
];
|
|
1654
|
+
const details = typeof e2?.details === 'object' && e2?.details
|
|
1655
|
+
? e2.details
|
|
1656
|
+
: { raw_response_snippet: safeSnippet(result.content, 500) };
|
|
1657
|
+
const errorResult = makeErrorResponse({
|
|
1658
|
+
code: 'E1000', // PARSE_ERROR
|
|
1659
|
+
message: `Failed to parse JSON response: ${e2.message}`,
|
|
1660
|
+
explain: 'Failed to parse LLM response as JSON.',
|
|
1661
|
+
details: {
|
|
1662
|
+
...details,
|
|
1663
|
+
parse_attempts: combinedAttempts.length ? combinedAttempts : undefined,
|
|
1664
|
+
parse_retry: { attempted: true, count: 1 },
|
|
1665
|
+
},
|
|
1666
|
+
suggestion: 'The LLM response was not valid JSON. Try again or adjust the prompt.',
|
|
1667
|
+
});
|
|
1668
|
+
_invokeErrorHooks(module.name, e2, null);
|
|
1669
|
+
return errorResult;
|
|
1670
|
+
}
|
|
1310
1671
|
}
|
|
1311
1672
|
// Convert to v2.2 envelope
|
|
1312
1673
|
let response;
|
|
@@ -1323,6 +1684,37 @@ export async function runModule(module, provider, options = {}) {
|
|
|
1323
1684
|
response.version = ENVELOPE_VERSION;
|
|
1324
1685
|
if (response.meta) {
|
|
1325
1686
|
response.meta.latency_ms = latencyMs;
|
|
1687
|
+
if (structuredPolicyMeta) {
|
|
1688
|
+
// Publish-grade parity: record how structured output was applied for this run.
|
|
1689
|
+
// This is intentionally small and stable, so users can reason about provider differences.
|
|
1690
|
+
response.meta.policy = {
|
|
1691
|
+
...(typeof response.meta.policy === 'object' && response.meta.policy ? response.meta.policy : {}),
|
|
1692
|
+
structured: structuredPolicyMeta,
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
if (policy) {
|
|
1696
|
+
response.meta.policy = {
|
|
1697
|
+
...(typeof response.meta.policy === 'object' && response.meta.policy ? response.meta.policy : {}),
|
|
1698
|
+
validation: {
|
|
1699
|
+
mode: policy.validate,
|
|
1700
|
+
input: validateInput,
|
|
1701
|
+
output: validateOutput,
|
|
1702
|
+
reason: validateReason,
|
|
1703
|
+
},
|
|
1704
|
+
audit: { enabled: policy.audit === true },
|
|
1705
|
+
repair: { enabled: enableRepair === true },
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
// Record parse strategy and retry count for publish-grade diagnostics.
|
|
1709
|
+
const includeParseAttempts = verbose || policy?.profile !== 'core';
|
|
1710
|
+
response.meta.policy = {
|
|
1711
|
+
...(typeof response.meta.policy === 'object' && response.meta.policy ? response.meta.policy : {}),
|
|
1712
|
+
parse: {
|
|
1713
|
+
strategy: parseExtracted?.strategy ?? null,
|
|
1714
|
+
retries: parseRetries,
|
|
1715
|
+
attempts: includeParseAttempts && parseAttempts.length ? parseAttempts : undefined,
|
|
1716
|
+
},
|
|
1717
|
+
};
|
|
1326
1718
|
if (traceId) {
|
|
1327
1719
|
response.meta.trace_id = traceId;
|
|
1328
1720
|
}
|
|
@@ -1451,7 +1843,9 @@ export async function runModule(module, provider, options = {}) {
|
|
|
1451
1843
|
* }
|
|
1452
1844
|
*/
|
|
1453
1845
|
export async function* runModuleStream(module, provider, options = {}) {
|
|
1454
|
-
const { input, args, validateInput
|
|
1846
|
+
const { input, args, validateInput: validateInputOpt, validateOutput: validateOutputOpt, useV22 = true, enableRepair: enableRepairOpt, traceId, model, policy, structured: structuredOverride, } = options;
|
|
1847
|
+
const { validateInput, validateOutput, reason: validateReason } = resolveValidationFlags(module, policy, validateInputOpt, validateOutputOpt);
|
|
1848
|
+
const enableRepair = enableRepairOpt ?? policy?.enableRepair ?? true;
|
|
1455
1849
|
const startTime = Date.now();
|
|
1456
1850
|
const moduleName = module.name;
|
|
1457
1851
|
const providerName = provider?.name;
|
|
@@ -1485,6 +1879,13 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1485
1879
|
inputData.query = args;
|
|
1486
1880
|
}
|
|
1487
1881
|
}
|
|
1882
|
+
// Single-file core modules promise "missing fields are empty".
|
|
1883
|
+
if (typeof module.location === 'string' && /\.(md|markdown)$/i.test(module.location)) {
|
|
1884
|
+
if (inputData.query === undefined)
|
|
1885
|
+
inputData.query = '';
|
|
1886
|
+
if (inputData.code === undefined)
|
|
1887
|
+
inputData.code = '';
|
|
1888
|
+
}
|
|
1488
1889
|
_invokeBeforeHooks(module.name, inputData, module);
|
|
1489
1890
|
// Validate input if enabled
|
|
1490
1891
|
if (validateInput && module.inputSchema && Object.keys(module.inputSchema).length > 0) {
|
|
@@ -1508,6 +1909,8 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1508
1909
|
const riskRule = module.metaConfig?.risk_rule ?? 'max_changes_risk';
|
|
1509
1910
|
// Build prompt
|
|
1510
1911
|
const prompt = buildPrompt(module, inputData);
|
|
1912
|
+
const effectiveStructuredPref = structuredOverride ?? policy?.structured;
|
|
1913
|
+
const structuredPlan = resolveStructuredSchemaPlan(module, provider, validateOutput, effectiveStructuredPref, policy);
|
|
1511
1914
|
// Build messages
|
|
1512
1915
|
const systemParts = [
|
|
1513
1916
|
`You are executing the "${module.name}" Cognitive Module.`,
|
|
@@ -1526,52 +1929,188 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1526
1929
|
];
|
|
1527
1930
|
// Invoke provider with streaming if supported
|
|
1528
1931
|
let fullContent;
|
|
1932
|
+
const { allowSchemaFallback, policy: structuredPolicy, ...invokeSchemaParams } = structuredPlan;
|
|
1933
|
+
const invokeParams = { ...invokeSchemaParams };
|
|
1934
|
+
const capsSnapshot = getProviderCapabilities(provider);
|
|
1935
|
+
const plannedSchemaMode = invokeParams.jsonSchema && typeof invokeParams.jsonSchema === 'object'
|
|
1936
|
+
? (invokeParams.jsonSchemaMode ?? 'prompt')
|
|
1937
|
+
: 'off';
|
|
1938
|
+
let appliedSchemaMode = plannedSchemaMode;
|
|
1939
|
+
let schemaFallbackAttempted = false;
|
|
1940
|
+
let schemaFallbackReason = null;
|
|
1529
1941
|
if (provider.supportsStreaming?.() && provider.invokeStream) {
|
|
1530
1942
|
// Use true streaming
|
|
1531
1943
|
const stream = provider.invokeStream({
|
|
1532
1944
|
messages,
|
|
1533
|
-
|
|
1945
|
+
...invokeParams,
|
|
1534
1946
|
temperature: 0.3,
|
|
1535
1947
|
});
|
|
1536
1948
|
// Iterate through the async generator, yielding chunks as they arrive
|
|
1537
1949
|
let streamResult;
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1950
|
+
try {
|
|
1951
|
+
while (!(streamResult = await stream.next()).done) {
|
|
1952
|
+
const chunk = streamResult.value;
|
|
1953
|
+
yield makeEvent('delta', { delta: chunk });
|
|
1954
|
+
}
|
|
1955
|
+
// Get the final result (returned from the generator)
|
|
1956
|
+
fullContent = streamResult.value.content;
|
|
1957
|
+
}
|
|
1958
|
+
catch (e) {
|
|
1959
|
+
// If streaming fails (e.g., schema rejected), retry once non-streaming in prompt mode.
|
|
1960
|
+
if (allowSchemaFallback &&
|
|
1961
|
+
invokeParams.jsonSchema &&
|
|
1962
|
+
invokeParams.jsonSchemaMode === 'native' &&
|
|
1963
|
+
isSchemaCompatibilityError(e)) {
|
|
1964
|
+
schemaFallbackAttempted = true;
|
|
1965
|
+
schemaFallbackReason = compactReason(e instanceof Error ? e.message : String(e ?? ''), 180);
|
|
1966
|
+
invokeParams.jsonSchemaMode = 'prompt';
|
|
1967
|
+
appliedSchemaMode = 'prompt';
|
|
1968
|
+
const result = await provider.invoke({
|
|
1969
|
+
messages,
|
|
1970
|
+
...invokeParams,
|
|
1971
|
+
temperature: 0.3,
|
|
1972
|
+
});
|
|
1973
|
+
fullContent = result.content;
|
|
1974
|
+
yield makeEvent('delta', { delta: result.content });
|
|
1975
|
+
}
|
|
1976
|
+
else {
|
|
1977
|
+
throw e;
|
|
1978
|
+
}
|
|
1541
1979
|
}
|
|
1542
|
-
// Get the final result (returned from the generator)
|
|
1543
|
-
fullContent = streamResult.value.content;
|
|
1544
1980
|
}
|
|
1545
1981
|
else {
|
|
1546
1982
|
// Fallback to non-streaming invoke
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1983
|
+
let result;
|
|
1984
|
+
try {
|
|
1985
|
+
result = await provider.invoke({
|
|
1986
|
+
messages,
|
|
1987
|
+
...invokeParams,
|
|
1988
|
+
temperature: 0.3,
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
catch (e) {
|
|
1992
|
+
if (allowSchemaFallback &&
|
|
1993
|
+
invokeParams.jsonSchema &&
|
|
1994
|
+
invokeParams.jsonSchemaMode === 'native' &&
|
|
1995
|
+
isSchemaCompatibilityError(e)) {
|
|
1996
|
+
schemaFallbackAttempted = true;
|
|
1997
|
+
schemaFallbackReason = compactReason(e instanceof Error ? e.message : String(e ?? ''), 180);
|
|
1998
|
+
invokeParams.jsonSchemaMode = 'prompt';
|
|
1999
|
+
appliedSchemaMode = 'prompt';
|
|
2000
|
+
result = await provider.invoke({
|
|
2001
|
+
messages,
|
|
2002
|
+
...invokeParams,
|
|
2003
|
+
temperature: 0.3,
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
else {
|
|
2007
|
+
throw e;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1552
2010
|
fullContent = result.content;
|
|
1553
2011
|
// Emit chunk event with full response
|
|
1554
2012
|
yield makeEvent('delta', { delta: result.content });
|
|
1555
2013
|
}
|
|
2014
|
+
const structuredPolicyMeta = structuredPolicy
|
|
2015
|
+
? {
|
|
2016
|
+
...structuredPolicy,
|
|
2017
|
+
planned: structuredPolicy.resolved,
|
|
2018
|
+
applied: appliedSchemaMode,
|
|
2019
|
+
downgraded: appliedSchemaMode !== structuredPolicy.resolved,
|
|
2020
|
+
fallback: schemaFallbackAttempted ? { attempted: true, reason: schemaFallbackReason ?? undefined } : { attempted: false },
|
|
2021
|
+
provider: {
|
|
2022
|
+
structuredOutput: capsSnapshot.structuredOutput,
|
|
2023
|
+
nativeSchemaDialect: capsSnapshot.nativeSchemaDialect,
|
|
2024
|
+
maxNativeSchemaBytes: capsSnapshot.maxNativeSchemaBytes,
|
|
2025
|
+
},
|
|
2026
|
+
}
|
|
2027
|
+
: undefined;
|
|
1556
2028
|
// Parse response
|
|
1557
2029
|
let parsed;
|
|
2030
|
+
let parseExtracted = null;
|
|
2031
|
+
let parseAttempts = [];
|
|
2032
|
+
let parseRetries = 0;
|
|
1558
2033
|
try {
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
|
|
2034
|
+
const r = parseJsonWithCandidates(fullContent);
|
|
2035
|
+
parsed = r.parsed;
|
|
2036
|
+
parseExtracted = r.extracted;
|
|
2037
|
+
parseAttempts = r.attempts;
|
|
1562
2038
|
}
|
|
1563
2039
|
catch (e) {
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
2040
|
+
const firstDetails = e?.details;
|
|
2041
|
+
if (firstDetails && typeof firstDetails === 'object' && Array.isArray(firstDetails.parse_attempts)) {
|
|
2042
|
+
parseAttempts = firstDetails.parse_attempts;
|
|
2043
|
+
}
|
|
2044
|
+
const allowParseRetry = policy?.profile !== 'certified';
|
|
2045
|
+
if (!allowParseRetry) {
|
|
2046
|
+
const details = typeof firstDetails === 'object' && firstDetails
|
|
2047
|
+
? firstDetails
|
|
2048
|
+
: { raw_response_snippet: safeSnippet(fullContent, 500) };
|
|
2049
|
+
const errorResult = makeErrorResponse({
|
|
2050
|
+
code: 'E1000', // PARSE_ERROR
|
|
2051
|
+
message: `Failed to parse JSON: ${e.message}`,
|
|
2052
|
+
details: {
|
|
2053
|
+
...details,
|
|
2054
|
+
parse_retry: { attempted: false, reason: 'profile=certified (fail-fast)' },
|
|
2055
|
+
},
|
|
2056
|
+
suggestion: 'The LLM response was not valid JSON. Fix the module/provider output or switch provider.',
|
|
2057
|
+
});
|
|
2058
|
+
_invokeErrorHooks(module.name, e, null);
|
|
2059
|
+
const errorObj = errorResult.error;
|
|
2060
|
+
yield makeEvent('error', { error: errorObj });
|
|
2061
|
+
yield makeEvent('end', { result: errorResult });
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
// Retry once (non-streaming) with stronger JSON-only instructions.
|
|
2065
|
+
parseRetries = 1;
|
|
2066
|
+
const retryMessages = [
|
|
2067
|
+
...messages,
|
|
2068
|
+
{
|
|
2069
|
+
role: 'user',
|
|
2070
|
+
content: 'Your previous response was not valid JSON.\n\nReturn ONLY a single valid JSON value (no markdown, no code fences, no commentary, no trailing text).',
|
|
2071
|
+
},
|
|
2072
|
+
];
|
|
2073
|
+
try {
|
|
2074
|
+
const retryResult = await provider.invoke({
|
|
2075
|
+
messages: retryMessages,
|
|
2076
|
+
...invokeParams,
|
|
2077
|
+
temperature: 0.3,
|
|
2078
|
+
});
|
|
2079
|
+
fullContent = retryResult.content; // do not emit delta for retry; clients should rely on final envelope
|
|
2080
|
+
const r2 = parseJsonWithCandidates(fullContent);
|
|
2081
|
+
parsed = r2.parsed;
|
|
2082
|
+
parseExtracted = r2.extracted;
|
|
2083
|
+
parseAttempts = [...parseAttempts, ...r2.attempts];
|
|
2084
|
+
}
|
|
2085
|
+
catch (e2) {
|
|
2086
|
+
const combinedAttempts = [
|
|
2087
|
+
...parseAttempts,
|
|
2088
|
+
...((typeof e2?.details === 'object' &&
|
|
2089
|
+
e2?.details &&
|
|
2090
|
+
Array.isArray(e2.details.parse_attempts))
|
|
2091
|
+
? e2.details.parse_attempts
|
|
2092
|
+
: []),
|
|
2093
|
+
];
|
|
2094
|
+
const details = typeof e2?.details === 'object' && e2?.details
|
|
2095
|
+
? e2.details
|
|
2096
|
+
: { raw_response_snippet: safeSnippet(fullContent, 500) };
|
|
2097
|
+
const errorResult = makeErrorResponse({
|
|
2098
|
+
code: 'E1000', // PARSE_ERROR
|
|
2099
|
+
message: `Failed to parse JSON: ${e2.message}`,
|
|
2100
|
+
details: {
|
|
2101
|
+
...details,
|
|
2102
|
+
parse_attempts: combinedAttempts.length ? combinedAttempts : undefined,
|
|
2103
|
+
parse_retry: { attempted: true, count: 1 },
|
|
2104
|
+
},
|
|
2105
|
+
suggestion: 'The LLM response was not valid JSON. Try again or adjust the prompt.',
|
|
2106
|
+
});
|
|
2107
|
+
_invokeErrorHooks(module.name, e2, null);
|
|
2108
|
+
// errorResult is always an error response from makeErrorResponse
|
|
2109
|
+
const errorObj = errorResult.error;
|
|
2110
|
+
yield makeEvent('error', { error: errorObj });
|
|
2111
|
+
yield makeEvent('end', { result: errorResult });
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
1575
2114
|
}
|
|
1576
2115
|
// Convert to v2.2 envelope
|
|
1577
2116
|
let response;
|
|
@@ -1589,6 +2128,34 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1589
2128
|
const latencyMs = Date.now() - startTime;
|
|
1590
2129
|
if (response.meta) {
|
|
1591
2130
|
response.meta.latency_ms = latencyMs;
|
|
2131
|
+
if (structuredPolicyMeta) {
|
|
2132
|
+
response.meta.policy = {
|
|
2133
|
+
...(typeof response.meta.policy === 'object' && response.meta.policy ? response.meta.policy : {}),
|
|
2134
|
+
structured: structuredPolicyMeta,
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
if (policy) {
|
|
2138
|
+
response.meta.policy = {
|
|
2139
|
+
...(typeof response.meta.policy === 'object' && response.meta.policy ? response.meta.policy : {}),
|
|
2140
|
+
validation: {
|
|
2141
|
+
mode: policy.validate,
|
|
2142
|
+
input: validateInput,
|
|
2143
|
+
output: validateOutput,
|
|
2144
|
+
reason: validateReason,
|
|
2145
|
+
},
|
|
2146
|
+
audit: { enabled: policy.audit === true },
|
|
2147
|
+
repair: { enabled: enableRepair === true },
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
const includeParseAttempts = policy?.profile !== 'core';
|
|
2151
|
+
response.meta.policy = {
|
|
2152
|
+
...(typeof response.meta.policy === 'object' && response.meta.policy ? response.meta.policy : {}),
|
|
2153
|
+
parse: {
|
|
2154
|
+
strategy: parseExtracted?.strategy ?? null,
|
|
2155
|
+
retries: parseRetries,
|
|
2156
|
+
attempts: includeParseAttempts && parseAttempts.length ? parseAttempts : undefined,
|
|
2157
|
+
},
|
|
2158
|
+
};
|
|
1592
2159
|
if (traceId) {
|
|
1593
2160
|
response.meta.trace_id = traceId;
|
|
1594
2161
|
}
|