@attest-ai/core 1.0.0-rc.4 → 1.0.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/dist/cjs/index.cjs +359 -33
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +154 -6
- package/dist/esm/cli.js +12 -3
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/index.d.ts +154 -6
- package/dist/esm/index.js +349 -33
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -219,8 +219,8 @@ function extractId(response) {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// src/version.ts
|
|
222
|
-
var VERSION = "1.0.0
|
|
223
|
-
var ENGINE_VERSION = "1.0.0
|
|
222
|
+
var VERSION = "1.0.0";
|
|
223
|
+
var ENGINE_VERSION = "1.0.0";
|
|
224
224
|
|
|
225
225
|
// src/engine-manager.ts
|
|
226
226
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -288,7 +288,7 @@ function httpsGet(url, redirectCount = 0) {
|
|
|
288
288
|
new Error(`Too many redirects (>${MAX_REDIRECTS}) following ${url}`)
|
|
289
289
|
);
|
|
290
290
|
}
|
|
291
|
-
return new Promise((
|
|
291
|
+
return new Promise((resolve5, reject) => {
|
|
292
292
|
const client = url.startsWith("https:") ? https : http;
|
|
293
293
|
client.get(url, (res) => {
|
|
294
294
|
const status = res.statusCode ?? 0;
|
|
@@ -299,7 +299,7 @@ function httpsGet(url, redirectCount = 0) {
|
|
|
299
299
|
return;
|
|
300
300
|
}
|
|
301
301
|
res.resume();
|
|
302
|
-
|
|
302
|
+
resolve5(httpsGet(location, redirectCount + 1));
|
|
303
303
|
return;
|
|
304
304
|
}
|
|
305
305
|
if (status < 200 || status >= 300) {
|
|
@@ -309,7 +309,7 @@ function httpsGet(url, redirectCount = 0) {
|
|
|
309
309
|
}
|
|
310
310
|
const chunks = [];
|
|
311
311
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
312
|
-
res.on("end", () =>
|
|
312
|
+
res.on("end", () => resolve5(Buffer.concat(chunks)));
|
|
313
313
|
res.on("error", reject);
|
|
314
314
|
}).on("error", reject);
|
|
315
315
|
});
|
|
@@ -536,11 +536,11 @@ var EngineManager = class {
|
|
|
536
536
|
}
|
|
537
537
|
this.requestId += 1;
|
|
538
538
|
const requestStr = encodeRequest(this.requestId, method, params);
|
|
539
|
-
return new Promise((
|
|
539
|
+
return new Promise((resolve5, reject) => {
|
|
540
540
|
const onLine = (line) => {
|
|
541
541
|
try {
|
|
542
542
|
const response = decodeResponse(line);
|
|
543
|
-
|
|
543
|
+
resolve5(extractResult(response));
|
|
544
544
|
} catch (err) {
|
|
545
545
|
reject(err);
|
|
546
546
|
}
|
|
@@ -907,12 +907,12 @@ var AttestClient = class {
|
|
|
907
907
|
throw new Error("Engine process not started.");
|
|
908
908
|
}
|
|
909
909
|
let capturedReqId;
|
|
910
|
-
const requestPromise = new Promise((
|
|
910
|
+
const requestPromise = new Promise((resolve5, reject) => {
|
|
911
911
|
this.writeChain = this.writeChain.then(() => {
|
|
912
912
|
this.requestId += 1;
|
|
913
913
|
const reqId = this.requestId;
|
|
914
914
|
capturedReqId = reqId;
|
|
915
|
-
this.pending.set(reqId, { resolve:
|
|
915
|
+
this.pending.set(reqId, { resolve: resolve5, reject });
|
|
916
916
|
const requestStr = encodeRequest(reqId, method, params);
|
|
917
917
|
cp.stdin.write(requestStr, (err) => {
|
|
918
918
|
if (err) {
|
|
@@ -1027,6 +1027,26 @@ function requireSimulationCapability(init) {
|
|
|
1027
1027
|
|
|
1028
1028
|
// src/trace.ts
|
|
1029
1029
|
import { randomUUID } from "crypto";
|
|
1030
|
+
|
|
1031
|
+
// src/simulation/context.ts
|
|
1032
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1033
|
+
var activeMockRegistry = new AsyncLocalStorage();
|
|
1034
|
+
function createRng(seed) {
|
|
1035
|
+
if (seed === void 0) return Math.random;
|
|
1036
|
+
let state = seed >>> 0;
|
|
1037
|
+
return () => {
|
|
1038
|
+
state = state + 1831565813 >>> 0;
|
|
1039
|
+
let t = state;
|
|
1040
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
1041
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
1042
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
function sleep(ms) {
|
|
1046
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/trace.ts
|
|
1030
1050
|
var TraceBuilder = class {
|
|
1031
1051
|
traceId;
|
|
1032
1052
|
agentId;
|
|
@@ -1058,11 +1078,22 @@ var TraceBuilder = class {
|
|
|
1058
1078
|
return this;
|
|
1059
1079
|
}
|
|
1060
1080
|
addToolCall(name, options2) {
|
|
1081
|
+
const mock = activeMockRegistry.getStore()?.get(name);
|
|
1082
|
+
let result = options2?.result;
|
|
1083
|
+
if (mock !== void 0) {
|
|
1084
|
+
const mocked = mock(options2?.args ?? {});
|
|
1085
|
+
if (typeof mocked?.then === "function") {
|
|
1086
|
+
throw new Error(`mock for tool '${name}' returned a Promise; mock tools must be synchronous`);
|
|
1087
|
+
}
|
|
1088
|
+
if (typeof mocked === "object" && mocked !== null && !Array.isArray(mocked)) {
|
|
1089
|
+
result = mocked;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1061
1092
|
this.steps.push({
|
|
1062
1093
|
type: STEP_TOOL_CALL,
|
|
1063
1094
|
name,
|
|
1064
1095
|
args: options2?.args,
|
|
1065
|
-
result
|
|
1096
|
+
result,
|
|
1066
1097
|
metadata: options2?.metadata
|
|
1067
1098
|
});
|
|
1068
1099
|
return this;
|
|
@@ -2717,18 +2748,18 @@ function serve(engine, args) {
|
|
|
2717
2748
|
let stdout = "";
|
|
2718
2749
|
let stderr = "";
|
|
2719
2750
|
let resolveReady = () => void 0;
|
|
2720
|
-
const ready = new Promise((
|
|
2721
|
-
resolveReady =
|
|
2751
|
+
const ready = new Promise((resolve5) => {
|
|
2752
|
+
resolveReady = resolve5;
|
|
2722
2753
|
});
|
|
2723
|
-
const exited = new Promise((
|
|
2754
|
+
const exited = new Promise((resolve5) => {
|
|
2724
2755
|
child.on("close", (code, signal) => {
|
|
2725
2756
|
resolveReady(null);
|
|
2726
|
-
|
|
2757
|
+
resolve5({ code, signal });
|
|
2727
2758
|
});
|
|
2728
2759
|
child.on("error", (err) => {
|
|
2729
2760
|
stderr += err.message;
|
|
2730
2761
|
resolveReady(null);
|
|
2731
|
-
|
|
2762
|
+
resolve5({ code: null, signal: null });
|
|
2732
2763
|
});
|
|
2733
2764
|
});
|
|
2734
2765
|
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
@@ -2820,11 +2851,14 @@ async function recordExclusive(name, fn, opts) {
|
|
|
2820
2851
|
throw new CaptureError(`capture ${name} wrote no fixture (${exitDetail(exit)})`);
|
|
2821
2852
|
}
|
|
2822
2853
|
const outcome = JSON.parse(lines[lines.length - 1] ?? "");
|
|
2823
|
-
if (exit.code !== 0 ||
|
|
2854
|
+
if (exit.code !== 0 || outcome.outcome === "not_verified") {
|
|
2824
2855
|
throw new CaptureError(`capture ${name}: fixture ${outcome.fixture} is not verified; no test drafted`, outcome.fixture);
|
|
2825
2856
|
}
|
|
2857
|
+
if (outcome.outcome === "nothing_to_verify") {
|
|
2858
|
+
return { value, fixture: outcome.fixture, verified: false, outcome: "nothing_to_verify", testFile: null };
|
|
2859
|
+
}
|
|
2826
2860
|
const testFile = opts.emitTest === false ? null : emitVitest(outcome.fixture);
|
|
2827
|
-
return { value, fixture: outcome.fixture, verified: true, testFile };
|
|
2861
|
+
return { value, fixture: outcome.fixture, verified: true, outcome: "verified", testFile };
|
|
2828
2862
|
}
|
|
2829
2863
|
|
|
2830
2864
|
// src/replay.ts
|
|
@@ -2985,18 +3019,18 @@ async function start(name, mode, opts) {
|
|
|
2985
3019
|
let stdout = "";
|
|
2986
3020
|
let stderr = "";
|
|
2987
3021
|
let resolveReady = () => void 0;
|
|
2988
|
-
const ready = new Promise((
|
|
2989
|
-
resolveReady =
|
|
3022
|
+
const ready = new Promise((resolve5) => {
|
|
3023
|
+
resolveReady = resolve5;
|
|
2990
3024
|
});
|
|
2991
|
-
const exited = new Promise((
|
|
3025
|
+
const exited = new Promise((resolve5) => {
|
|
2992
3026
|
child.on("close", (code) => {
|
|
2993
3027
|
resolveReady(null);
|
|
2994
|
-
|
|
3028
|
+
resolve5(code);
|
|
2995
3029
|
});
|
|
2996
3030
|
child.on("error", (err) => {
|
|
2997
3031
|
stderr += err.message;
|
|
2998
3032
|
resolveReady(null);
|
|
2999
|
-
|
|
3033
|
+
resolve5(null);
|
|
3000
3034
|
});
|
|
3001
3035
|
});
|
|
3002
3036
|
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
@@ -3121,18 +3155,18 @@ async function start2(file, opts) {
|
|
|
3121
3155
|
let stdout = "";
|
|
3122
3156
|
let stderr = "";
|
|
3123
3157
|
let resolveReady = () => void 0;
|
|
3124
|
-
const ready = new Promise((
|
|
3125
|
-
resolveReady =
|
|
3158
|
+
const ready = new Promise((resolve5) => {
|
|
3159
|
+
resolveReady = resolve5;
|
|
3126
3160
|
});
|
|
3127
|
-
const exited = new Promise((
|
|
3161
|
+
const exited = new Promise((resolve5) => {
|
|
3128
3162
|
child.on("close", (code) => {
|
|
3129
3163
|
resolveReady(null);
|
|
3130
|
-
|
|
3164
|
+
resolve5(code);
|
|
3131
3165
|
});
|
|
3132
3166
|
child.on("error", (err) => {
|
|
3133
3167
|
stderr += err.message;
|
|
3134
3168
|
resolveReady(null);
|
|
3135
|
-
|
|
3169
|
+
resolve5(null);
|
|
3136
3170
|
});
|
|
3137
3171
|
});
|
|
3138
3172
|
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
@@ -3213,8 +3247,8 @@ function parseFinal(out) {
|
|
|
3213
3247
|
}
|
|
3214
3248
|
|
|
3215
3249
|
// src/context.ts
|
|
3216
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
3217
|
-
var activeBuilder = new
|
|
3250
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
3251
|
+
var activeBuilder = new AsyncLocalStorage2();
|
|
3218
3252
|
|
|
3219
3253
|
// src/agent.ts
|
|
3220
3254
|
var Agent = class {
|
|
@@ -3301,6 +3335,9 @@ function tier(level) {
|
|
|
3301
3335
|
}
|
|
3302
3336
|
|
|
3303
3337
|
// src/plugins.ts
|
|
3338
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
3339
|
+
import { dirname as dirname2, join as join4, relative as relative2, resolve as resolve4, isAbsolute, sep as sep2 } from "path";
|
|
3340
|
+
import { pathToFileURL } from "url";
|
|
3304
3341
|
var PluginRegistry = class {
|
|
3305
3342
|
plugins = /* @__PURE__ */ new Map();
|
|
3306
3343
|
register(name, plugin) {
|
|
@@ -3331,10 +3368,19 @@ var PluginRegistry = class {
|
|
|
3331
3368
|
};
|
|
3332
3369
|
async function executePluginAssertion(plugin, trace, spec, client, traceId2, assertionId2, timeoutMs = 3e4) {
|
|
3333
3370
|
const resultPromise = Promise.resolve(plugin.execute(trace, spec));
|
|
3371
|
+
let timer;
|
|
3334
3372
|
const timeoutPromise = new Promise((_, reject) => {
|
|
3335
|
-
|
|
3373
|
+
timer = setTimeout(
|
|
3374
|
+
() => reject(new Error(`Plugin '${plugin.name}' timed out after ${timeoutMs}ms`)),
|
|
3375
|
+
timeoutMs
|
|
3376
|
+
);
|
|
3336
3377
|
});
|
|
3337
|
-
|
|
3378
|
+
let result;
|
|
3379
|
+
try {
|
|
3380
|
+
result = await Promise.race([resultPromise, timeoutPromise]);
|
|
3381
|
+
} finally {
|
|
3382
|
+
clearTimeout(timer);
|
|
3383
|
+
}
|
|
3338
3384
|
await client.submitPluginResult(
|
|
3339
3385
|
traceId2,
|
|
3340
3386
|
plugin.name,
|
|
@@ -3345,6 +3391,100 @@ async function executePluginAssertion(plugin, trace, spec, client, traceId2, ass
|
|
|
3345
3391
|
);
|
|
3346
3392
|
return result;
|
|
3347
3393
|
}
|
|
3394
|
+
function registerPlugin(registry, name, plugin) {
|
|
3395
|
+
registry.register(name, plugin);
|
|
3396
|
+
}
|
|
3397
|
+
function isAttestPlugin(value) {
|
|
3398
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3399
|
+
const v = value;
|
|
3400
|
+
return typeof v["name"] === "string" && typeof v["pluginType"] === "string" && typeof v["execute"] === "function";
|
|
3401
|
+
}
|
|
3402
|
+
function readJson(path5) {
|
|
3403
|
+
return JSON.parse(readFileSync2(path5, "utf8"));
|
|
3404
|
+
}
|
|
3405
|
+
function findInstalledPackage(projectRoot, name) {
|
|
3406
|
+
let dir = resolve4(projectRoot);
|
|
3407
|
+
for (; ; ) {
|
|
3408
|
+
const candidate = join4(dir, "node_modules", name);
|
|
3409
|
+
if (existsSync2(join4(candidate, "package.json"))) return candidate;
|
|
3410
|
+
const parent = dirname2(dir);
|
|
3411
|
+
if (parent === dir) return void 0;
|
|
3412
|
+
dir = parent;
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
function declaredPluginPaths(pkgJson, dep) {
|
|
3416
|
+
const attest = pkgJson["attest"];
|
|
3417
|
+
if (attest === void 0) return [];
|
|
3418
|
+
const plugins = typeof attest === "object" && attest !== null ? attest["plugins"] : void 0;
|
|
3419
|
+
if (plugins === void 0) return [];
|
|
3420
|
+
if (!Array.isArray(plugins) || !plugins.every((p) => typeof p === "string")) {
|
|
3421
|
+
throw new Error(`'${dep}': "attest.plugins" must be an array of strings`);
|
|
3422
|
+
}
|
|
3423
|
+
return plugins;
|
|
3424
|
+
}
|
|
3425
|
+
function pluginsOf(mod) {
|
|
3426
|
+
const def = mod["default"];
|
|
3427
|
+
const nested = typeof def === "object" && def !== null ? def : void 0;
|
|
3428
|
+
for (const candidate of [def, nested?.["default"]]) {
|
|
3429
|
+
if (isAttestPlugin(candidate)) return [candidate];
|
|
3430
|
+
}
|
|
3431
|
+
return [.../* @__PURE__ */ new Set([...Object.values(mod), ...Object.values(nested ?? {})])].filter(isAttestPlugin);
|
|
3432
|
+
}
|
|
3433
|
+
function isInside(root, file) {
|
|
3434
|
+
const within = (r, f) => {
|
|
3435
|
+
const rel = relative2(r, f);
|
|
3436
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute(rel);
|
|
3437
|
+
};
|
|
3438
|
+
return within(root, file) && within(realpathSync(root), realpathSync(file));
|
|
3439
|
+
}
|
|
3440
|
+
async function loadPackagePlugins(registry, projectRoot = process.cwd()) {
|
|
3441
|
+
let project;
|
|
3442
|
+
try {
|
|
3443
|
+
project = readJson(join4(projectRoot, "package.json"));
|
|
3444
|
+
} catch (err) {
|
|
3445
|
+
console.error(`attest: cannot read ${join4(projectRoot, "package.json")}; no plugins loaded:`, err);
|
|
3446
|
+
return 0;
|
|
3447
|
+
}
|
|
3448
|
+
const depNames = /* @__PURE__ */ new Set();
|
|
3449
|
+
for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
3450
|
+
const deps = project[field];
|
|
3451
|
+
if (typeof deps === "object" && deps !== null) {
|
|
3452
|
+
for (const dep of Object.keys(deps)) depNames.add(dep);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
let loaded = 0;
|
|
3456
|
+
for (const dep of depNames) {
|
|
3457
|
+
const pkgDir = findInstalledPackage(projectRoot, dep);
|
|
3458
|
+
if (pkgDir === void 0) continue;
|
|
3459
|
+
let entries;
|
|
3460
|
+
try {
|
|
3461
|
+
entries = declaredPluginPaths(readJson(join4(pkgDir, "package.json")), dep);
|
|
3462
|
+
} catch (err) {
|
|
3463
|
+
console.error(`attest: failed to read plugins declared by '${dep}':`, err);
|
|
3464
|
+
continue;
|
|
3465
|
+
}
|
|
3466
|
+
for (const entry of entries) {
|
|
3467
|
+
try {
|
|
3468
|
+
const file = resolve4(pkgDir, entry);
|
|
3469
|
+
if (!isInside(pkgDir, file)) {
|
|
3470
|
+
throw new Error(`plugin path '${entry}' escapes package '${dep}'`);
|
|
3471
|
+
}
|
|
3472
|
+
const mod = await import(pathToFileURL(file).href);
|
|
3473
|
+
const plugins = pluginsOf(mod);
|
|
3474
|
+
if (plugins.length === 0) {
|
|
3475
|
+
throw new Error(`'${entry}' exports no plugin (name, pluginType, execute)`);
|
|
3476
|
+
}
|
|
3477
|
+
for (const plugin of plugins) {
|
|
3478
|
+
registry.register(plugin.name, plugin);
|
|
3479
|
+
loaded++;
|
|
3480
|
+
}
|
|
3481
|
+
} catch (err) {
|
|
3482
|
+
console.error(`attest: failed to load plugin '${entry}' from '${dep}':`, err);
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
return loaded;
|
|
3487
|
+
}
|
|
3348
3488
|
|
|
3349
3489
|
// src/continuous.ts
|
|
3350
3490
|
import { EventEmitter } from "events";
|
|
@@ -3724,6 +3864,172 @@ the assistant's response meets your needs. Keep responses concise (1-3 sentences
|
|
|
3724
3864
|
max_tokens: 200
|
|
3725
3865
|
};
|
|
3726
3866
|
|
|
3867
|
+
// src/simulation/mock-tools.ts
|
|
3868
|
+
var MockToolRegistry = class {
|
|
3869
|
+
tools = /* @__PURE__ */ new Map();
|
|
3870
|
+
register(name, fn) {
|
|
3871
|
+
this.tools.set(name, fn);
|
|
3872
|
+
}
|
|
3873
|
+
/**
|
|
3874
|
+
* Run `fn` with the registered mocks active. `TraceBuilder.addToolCall`
|
|
3875
|
+
* returns the mock's result for a registered tool name. Scopes are
|
|
3876
|
+
* isolated per async call chain (AsyncLocalStorage).
|
|
3877
|
+
*/
|
|
3878
|
+
run(fn) {
|
|
3879
|
+
return activeMockRegistry.run(new Map(this.tools), fn);
|
|
3880
|
+
}
|
|
3881
|
+
};
|
|
3882
|
+
function mockTool(name, fn) {
|
|
3883
|
+
const wrapper = ((args) => fn(args));
|
|
3884
|
+
return Object.assign(wrapper, { mockToolName: name });
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
// src/simulation/fault-inject.ts
|
|
3888
|
+
function faultInject(options2, fn) {
|
|
3889
|
+
const errorRate = options2.errorRate ?? 0;
|
|
3890
|
+
const latencyJitterMs = options2.latencyJitterMs ?? 0;
|
|
3891
|
+
const rng = createRng(options2.seed);
|
|
3892
|
+
return async (...args) => {
|
|
3893
|
+
if (errorRate > 0 && rng() < errorRate) {
|
|
3894
|
+
throw new Error(`Injected fault in ${fn.name || "anonymous"}`);
|
|
3895
|
+
}
|
|
3896
|
+
if (latencyJitterMs > 0) {
|
|
3897
|
+
await sleep(rng() * latencyJitterMs);
|
|
3898
|
+
}
|
|
3899
|
+
return fn(...args);
|
|
3900
|
+
};
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
// src/simulation/verdict.ts
|
|
3904
|
+
function aggregateOutcomes(outcomes) {
|
|
3905
|
+
if (outcomes.includes(OUTCOME_FAIL)) return OUTCOME_FAIL;
|
|
3906
|
+
if (outcomes.includes(OUTCOME_SKIP)) return OUTCOME_SKIP;
|
|
3907
|
+
return OUTCOME_PASS;
|
|
3908
|
+
}
|
|
3909
|
+
function sumNotEvaluatedReasons(results) {
|
|
3910
|
+
const counts = {};
|
|
3911
|
+
for (const r of results) {
|
|
3912
|
+
for (const [reason, n] of Object.entries(r.notEvaluatedReasons)) {
|
|
3913
|
+
counts[reason] = (counts[reason] ?? 0) + n;
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
return counts;
|
|
3917
|
+
}
|
|
3918
|
+
function formatSkipReason(counts) {
|
|
3919
|
+
const parts = Object.entries(counts).map(([reason, n]) => n === 1 ? reason : `${reason} \xD7${n}`);
|
|
3920
|
+
return `not evaluated: ${parts.join(", ")}`;
|
|
3921
|
+
}
|
|
3922
|
+
function renderVerdictDiagnostics(verdict, color = false) {
|
|
3923
|
+
return verdict instanceof AgentResult ? renderDiagnostics(verdict, { color }) : verdict.renderDiagnostics?.({ color }) ?? "";
|
|
3924
|
+
}
|
|
3925
|
+
function verdictsDiagnostics(results, label, color = false) {
|
|
3926
|
+
const blocks = results.flatMap(
|
|
3927
|
+
(r, i) => r.outcome() === OUTCOME_PASS ? [] : [`${label} ${i + 1}/${results.length}:${renderVerdictDiagnostics(r, color)}`]
|
|
3928
|
+
);
|
|
3929
|
+
return blocks.length > 0 ? blocks.join("\n") : ` (no failing ${label}s)
|
|
3930
|
+
`;
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
// src/simulation/repeat.ts
|
|
3934
|
+
var RepeatResult = class {
|
|
3935
|
+
results;
|
|
3936
|
+
constructor(results = []) {
|
|
3937
|
+
this.results = results;
|
|
3938
|
+
}
|
|
3939
|
+
get count() {
|
|
3940
|
+
return this.results.length;
|
|
3941
|
+
}
|
|
3942
|
+
/** Trials skipped because an assertion was not evaluated. */
|
|
3943
|
+
get notEvaluated() {
|
|
3944
|
+
return this.results.filter((r) => r.outcome() === OUTCOME_SKIP).length;
|
|
3945
|
+
}
|
|
3946
|
+
/** Passing / evaluated trials; null when no trial was evaluated. */
|
|
3947
|
+
get passRate() {
|
|
3948
|
+
const evaluated = this.count - this.notEvaluated;
|
|
3949
|
+
if (evaluated === 0) return null;
|
|
3950
|
+
return this.results.filter((r) => r.outcome() === OUTCOME_PASS).length / evaluated;
|
|
3951
|
+
}
|
|
3952
|
+
/** True when every trial's outcome is pass (soft failures included). */
|
|
3953
|
+
get allPassed() {
|
|
3954
|
+
return this.results.every((r) => r.outcome() === OUTCOME_PASS);
|
|
3955
|
+
}
|
|
3956
|
+
outcome(options2) {
|
|
3957
|
+
return aggregateOutcomes(this.results.map((r) => r.outcome(options2)));
|
|
3958
|
+
}
|
|
3959
|
+
get notEvaluatedReasons() {
|
|
3960
|
+
return sumNotEvaluatedReasons(this.results);
|
|
3961
|
+
}
|
|
3962
|
+
skipReason() {
|
|
3963
|
+
return formatSkipReason(this.notEvaluatedReasons);
|
|
3964
|
+
}
|
|
3965
|
+
renderDiagnostics(options2) {
|
|
3966
|
+
return verdictsDiagnostics(this.results, "trial", options2?.color);
|
|
3967
|
+
}
|
|
3968
|
+
};
|
|
3969
|
+
function repeat(n, fn) {
|
|
3970
|
+
return async (...args) => {
|
|
3971
|
+
const results = [];
|
|
3972
|
+
for (let i = 0; i < n; i++) {
|
|
3973
|
+
results.push(await fn(...args));
|
|
3974
|
+
}
|
|
3975
|
+
return new RepeatResult(results);
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
// src/simulation/scenario.ts
|
|
3980
|
+
var ScenarioResult = class {
|
|
3981
|
+
config;
|
|
3982
|
+
results;
|
|
3983
|
+
constructor(config2, results = []) {
|
|
3984
|
+
this.config = config2;
|
|
3985
|
+
this.results = results;
|
|
3986
|
+
}
|
|
3987
|
+
/** True when the aggregate `outcome()` is pass. */
|
|
3988
|
+
get passed() {
|
|
3989
|
+
return this.outcome() === OUTCOME_PASS;
|
|
3990
|
+
}
|
|
3991
|
+
outcome(options2) {
|
|
3992
|
+
return aggregateOutcomes(this.results.map((r) => r.outcome(options2)));
|
|
3993
|
+
}
|
|
3994
|
+
get notEvaluatedReasons() {
|
|
3995
|
+
return sumNotEvaluatedReasons(this.results);
|
|
3996
|
+
}
|
|
3997
|
+
skipReason() {
|
|
3998
|
+
return formatSkipReason(this.notEvaluatedReasons);
|
|
3999
|
+
}
|
|
4000
|
+
renderDiagnostics(options2) {
|
|
4001
|
+
return verdictsDiagnostics(this.results, "turn", options2?.color);
|
|
4002
|
+
}
|
|
4003
|
+
};
|
|
4004
|
+
function scenario(options2, fn) {
|
|
4005
|
+
const config2 = {
|
|
4006
|
+
persona: options2.persona,
|
|
4007
|
+
mockTools: { ...options2.mockTools },
|
|
4008
|
+
faultErrorRate: options2.faultErrorRate ?? 0,
|
|
4009
|
+
faultLatencyJitterMs: options2.faultLatencyJitterMs ?? 0,
|
|
4010
|
+
maxTurns: options2.maxTurns ?? 1,
|
|
4011
|
+
seed: options2.seed
|
|
4012
|
+
};
|
|
4013
|
+
return async (...args) => {
|
|
4014
|
+
const run = async () => {
|
|
4015
|
+
const results = [];
|
|
4016
|
+
const rng = createRng(config2.seed);
|
|
4017
|
+
for (let turn = 0; turn < config2.maxTurns; turn++) {
|
|
4018
|
+
if (config2.faultErrorRate > 0 && rng() < config2.faultErrorRate) {
|
|
4019
|
+
throw new Error(`Injected fault at turn ${turn}`);
|
|
4020
|
+
}
|
|
4021
|
+
if (config2.faultLatencyJitterMs > 0) {
|
|
4022
|
+
await sleep(rng() * config2.faultLatencyJitterMs);
|
|
4023
|
+
}
|
|
4024
|
+
results.push(await fn({ persona: config2.persona, turn }, ...args));
|
|
4025
|
+
}
|
|
4026
|
+
return new ScenarioResult(config2, results);
|
|
4027
|
+
};
|
|
4028
|
+
const mocks = new Map(Object.entries(config2.mockTools));
|
|
4029
|
+
return mocks.size > 0 ? activeMockRegistry.run(mocks, run) : run();
|
|
4030
|
+
};
|
|
4031
|
+
}
|
|
4032
|
+
|
|
3727
4033
|
// src/adapters/manual.ts
|
|
3728
4034
|
var ManualAdapter = class {
|
|
3729
4035
|
agentId;
|
|
@@ -3935,7 +4241,7 @@ var OTelAdapter = class _OTelAdapter {
|
|
|
3935
4241
|
}
|
|
3936
4242
|
};
|
|
3937
4243
|
function runConvert(engine, args, payload) {
|
|
3938
|
-
return new Promise((
|
|
4244
|
+
return new Promise((resolve5, reject) => {
|
|
3939
4245
|
const child = spawn5(engine, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
3940
4246
|
let timer;
|
|
3941
4247
|
child.on("error", (e) => {
|
|
@@ -3988,7 +4294,7 @@ function runConvert(engine, args, payload) {
|
|
|
3988
4294
|
);
|
|
3989
4295
|
return;
|
|
3990
4296
|
}
|
|
3991
|
-
|
|
4297
|
+
resolve5(Buffer.concat(out).toString("utf8"));
|
|
3992
4298
|
});
|
|
3993
4299
|
stdin.end(payload);
|
|
3994
4300
|
});
|
|
@@ -4155,6 +4461,7 @@ export {
|
|
|
4155
4461
|
GeminiAdapter,
|
|
4156
4462
|
LangChainAdapter,
|
|
4157
4463
|
ManualAdapter,
|
|
4464
|
+
MockToolRegistry,
|
|
4158
4465
|
OTLPConversionError,
|
|
4159
4466
|
OTelAdapter,
|
|
4160
4467
|
OUTCOME_FAIL,
|
|
@@ -4174,6 +4481,7 @@ export {
|
|
|
4174
4481
|
REASON_REPLAY_MISS,
|
|
4175
4482
|
REASON_SHORT_CIRCUITED,
|
|
4176
4483
|
REASON_SIMULATION,
|
|
4484
|
+
RepeatResult,
|
|
4177
4485
|
ReplayError,
|
|
4178
4486
|
ReplayMissError,
|
|
4179
4487
|
ReplayNotEvaluatedError,
|
|
@@ -4186,6 +4494,7 @@ export {
|
|
|
4186
4494
|
STEP_RETRIEVAL,
|
|
4187
4495
|
STEP_TOOL_CALL,
|
|
4188
4496
|
Sampler,
|
|
4497
|
+
ScenarioResult,
|
|
4189
4498
|
TIER_1,
|
|
4190
4499
|
TIER_2,
|
|
4191
4500
|
TIER_3,
|
|
@@ -4220,6 +4529,7 @@ export {
|
|
|
4220
4529
|
executePluginAssertion,
|
|
4221
4530
|
extractId,
|
|
4222
4531
|
extractResult,
|
|
4532
|
+
faultInject,
|
|
4223
4533
|
firstMiss,
|
|
4224
4534
|
formatUsage,
|
|
4225
4535
|
getSampleRate,
|
|
@@ -4231,17 +4541,23 @@ export {
|
|
|
4231
4541
|
loadFixture,
|
|
4232
4542
|
loadLabelsCSV,
|
|
4233
4543
|
loadLabelsJSONL,
|
|
4544
|
+
loadPackagePlugins,
|
|
4545
|
+
mockTool,
|
|
4234
4546
|
parseUsage,
|
|
4235
4547
|
previewLine,
|
|
4236
4548
|
promptHash,
|
|
4237
4549
|
record,
|
|
4550
|
+
registerPlugin,
|
|
4238
4551
|
renderAssertionFailure,
|
|
4239
4552
|
renderDiagnostics,
|
|
4240
4553
|
renderSummary,
|
|
4554
|
+
renderVerdictDiagnostics,
|
|
4241
4555
|
renderVitest,
|
|
4556
|
+
repeat,
|
|
4242
4557
|
requireSimulationCapability,
|
|
4243
4558
|
resetConfig,
|
|
4244
4559
|
resolveMode,
|
|
4560
|
+
scenario,
|
|
4245
4561
|
simulationEnvEnabled,
|
|
4246
4562
|
startReplay,
|
|
4247
4563
|
startWorld,
|