@attest-ai/core 1.0.0-rc.4 → 1.0.0-rc.5
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 +354 -31
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +144 -4
- package/dist/esm/cli.js +10 -2
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/index.d.ts +144 -4
- package/dist/esm/index.js +344 -31
- 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-rc.
|
|
223
|
-
var ENGINE_VERSION = "1.0.0-rc.
|
|
222
|
+
var VERSION = "1.0.0-rc.5";
|
|
223
|
+
var ENGINE_VERSION = "1.0.0-rc.5";
|
|
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) => {
|
|
@@ -2985,18 +3016,18 @@ async function start(name, mode, opts) {
|
|
|
2985
3016
|
let stdout = "";
|
|
2986
3017
|
let stderr = "";
|
|
2987
3018
|
let resolveReady = () => void 0;
|
|
2988
|
-
const ready = new Promise((
|
|
2989
|
-
resolveReady =
|
|
3019
|
+
const ready = new Promise((resolve5) => {
|
|
3020
|
+
resolveReady = resolve5;
|
|
2990
3021
|
});
|
|
2991
|
-
const exited = new Promise((
|
|
3022
|
+
const exited = new Promise((resolve5) => {
|
|
2992
3023
|
child.on("close", (code) => {
|
|
2993
3024
|
resolveReady(null);
|
|
2994
|
-
|
|
3025
|
+
resolve5(code);
|
|
2995
3026
|
});
|
|
2996
3027
|
child.on("error", (err) => {
|
|
2997
3028
|
stderr += err.message;
|
|
2998
3029
|
resolveReady(null);
|
|
2999
|
-
|
|
3030
|
+
resolve5(null);
|
|
3000
3031
|
});
|
|
3001
3032
|
});
|
|
3002
3033
|
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
@@ -3121,18 +3152,18 @@ async function start2(file, opts) {
|
|
|
3121
3152
|
let stdout = "";
|
|
3122
3153
|
let stderr = "";
|
|
3123
3154
|
let resolveReady = () => void 0;
|
|
3124
|
-
const ready = new Promise((
|
|
3125
|
-
resolveReady =
|
|
3155
|
+
const ready = new Promise((resolve5) => {
|
|
3156
|
+
resolveReady = resolve5;
|
|
3126
3157
|
});
|
|
3127
|
-
const exited = new Promise((
|
|
3158
|
+
const exited = new Promise((resolve5) => {
|
|
3128
3159
|
child.on("close", (code) => {
|
|
3129
3160
|
resolveReady(null);
|
|
3130
|
-
|
|
3161
|
+
resolve5(code);
|
|
3131
3162
|
});
|
|
3132
3163
|
child.on("error", (err) => {
|
|
3133
3164
|
stderr += err.message;
|
|
3134
3165
|
resolveReady(null);
|
|
3135
|
-
|
|
3166
|
+
resolve5(null);
|
|
3136
3167
|
});
|
|
3137
3168
|
});
|
|
3138
3169
|
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
@@ -3213,8 +3244,8 @@ function parseFinal(out) {
|
|
|
3213
3244
|
}
|
|
3214
3245
|
|
|
3215
3246
|
// src/context.ts
|
|
3216
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
3217
|
-
var activeBuilder = new
|
|
3247
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
3248
|
+
var activeBuilder = new AsyncLocalStorage2();
|
|
3218
3249
|
|
|
3219
3250
|
// src/agent.ts
|
|
3220
3251
|
var Agent = class {
|
|
@@ -3301,6 +3332,9 @@ function tier(level) {
|
|
|
3301
3332
|
}
|
|
3302
3333
|
|
|
3303
3334
|
// src/plugins.ts
|
|
3335
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
3336
|
+
import { dirname as dirname2, join as join4, relative as relative2, resolve as resolve4, isAbsolute, sep as sep2 } from "path";
|
|
3337
|
+
import { pathToFileURL } from "url";
|
|
3304
3338
|
var PluginRegistry = class {
|
|
3305
3339
|
plugins = /* @__PURE__ */ new Map();
|
|
3306
3340
|
register(name, plugin) {
|
|
@@ -3331,10 +3365,19 @@ var PluginRegistry = class {
|
|
|
3331
3365
|
};
|
|
3332
3366
|
async function executePluginAssertion(plugin, trace, spec, client, traceId2, assertionId2, timeoutMs = 3e4) {
|
|
3333
3367
|
const resultPromise = Promise.resolve(plugin.execute(trace, spec));
|
|
3368
|
+
let timer;
|
|
3334
3369
|
const timeoutPromise = new Promise((_, reject) => {
|
|
3335
|
-
|
|
3370
|
+
timer = setTimeout(
|
|
3371
|
+
() => reject(new Error(`Plugin '${plugin.name}' timed out after ${timeoutMs}ms`)),
|
|
3372
|
+
timeoutMs
|
|
3373
|
+
);
|
|
3336
3374
|
});
|
|
3337
|
-
|
|
3375
|
+
let result;
|
|
3376
|
+
try {
|
|
3377
|
+
result = await Promise.race([resultPromise, timeoutPromise]);
|
|
3378
|
+
} finally {
|
|
3379
|
+
clearTimeout(timer);
|
|
3380
|
+
}
|
|
3338
3381
|
await client.submitPluginResult(
|
|
3339
3382
|
traceId2,
|
|
3340
3383
|
plugin.name,
|
|
@@ -3345,6 +3388,100 @@ async function executePluginAssertion(plugin, trace, spec, client, traceId2, ass
|
|
|
3345
3388
|
);
|
|
3346
3389
|
return result;
|
|
3347
3390
|
}
|
|
3391
|
+
function registerPlugin(registry, name, plugin) {
|
|
3392
|
+
registry.register(name, plugin);
|
|
3393
|
+
}
|
|
3394
|
+
function isAttestPlugin(value) {
|
|
3395
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3396
|
+
const v = value;
|
|
3397
|
+
return typeof v["name"] === "string" && typeof v["pluginType"] === "string" && typeof v["execute"] === "function";
|
|
3398
|
+
}
|
|
3399
|
+
function readJson(path5) {
|
|
3400
|
+
return JSON.parse(readFileSync2(path5, "utf8"));
|
|
3401
|
+
}
|
|
3402
|
+
function findInstalledPackage(projectRoot, name) {
|
|
3403
|
+
let dir = resolve4(projectRoot);
|
|
3404
|
+
for (; ; ) {
|
|
3405
|
+
const candidate = join4(dir, "node_modules", name);
|
|
3406
|
+
if (existsSync2(join4(candidate, "package.json"))) return candidate;
|
|
3407
|
+
const parent = dirname2(dir);
|
|
3408
|
+
if (parent === dir) return void 0;
|
|
3409
|
+
dir = parent;
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
function declaredPluginPaths(pkgJson, dep) {
|
|
3413
|
+
const attest = pkgJson["attest"];
|
|
3414
|
+
if (attest === void 0) return [];
|
|
3415
|
+
const plugins = typeof attest === "object" && attest !== null ? attest["plugins"] : void 0;
|
|
3416
|
+
if (plugins === void 0) return [];
|
|
3417
|
+
if (!Array.isArray(plugins) || !plugins.every((p) => typeof p === "string")) {
|
|
3418
|
+
throw new Error(`'${dep}': "attest.plugins" must be an array of strings`);
|
|
3419
|
+
}
|
|
3420
|
+
return plugins;
|
|
3421
|
+
}
|
|
3422
|
+
function pluginsOf(mod) {
|
|
3423
|
+
const def = mod["default"];
|
|
3424
|
+
const nested = typeof def === "object" && def !== null ? def : void 0;
|
|
3425
|
+
for (const candidate of [def, nested?.["default"]]) {
|
|
3426
|
+
if (isAttestPlugin(candidate)) return [candidate];
|
|
3427
|
+
}
|
|
3428
|
+
return [.../* @__PURE__ */ new Set([...Object.values(mod), ...Object.values(nested ?? {})])].filter(isAttestPlugin);
|
|
3429
|
+
}
|
|
3430
|
+
function isInside(root, file) {
|
|
3431
|
+
const within = (r, f) => {
|
|
3432
|
+
const rel = relative2(r, f);
|
|
3433
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute(rel);
|
|
3434
|
+
};
|
|
3435
|
+
return within(root, file) && within(realpathSync(root), realpathSync(file));
|
|
3436
|
+
}
|
|
3437
|
+
async function loadPackagePlugins(registry, projectRoot = process.cwd()) {
|
|
3438
|
+
let project;
|
|
3439
|
+
try {
|
|
3440
|
+
project = readJson(join4(projectRoot, "package.json"));
|
|
3441
|
+
} catch (err) {
|
|
3442
|
+
console.error(`attest: cannot read ${join4(projectRoot, "package.json")}; no plugins loaded:`, err);
|
|
3443
|
+
return 0;
|
|
3444
|
+
}
|
|
3445
|
+
const depNames = /* @__PURE__ */ new Set();
|
|
3446
|
+
for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
3447
|
+
const deps = project[field];
|
|
3448
|
+
if (typeof deps === "object" && deps !== null) {
|
|
3449
|
+
for (const dep of Object.keys(deps)) depNames.add(dep);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
let loaded = 0;
|
|
3453
|
+
for (const dep of depNames) {
|
|
3454
|
+
const pkgDir = findInstalledPackage(projectRoot, dep);
|
|
3455
|
+
if (pkgDir === void 0) continue;
|
|
3456
|
+
let entries;
|
|
3457
|
+
try {
|
|
3458
|
+
entries = declaredPluginPaths(readJson(join4(pkgDir, "package.json")), dep);
|
|
3459
|
+
} catch (err) {
|
|
3460
|
+
console.error(`attest: failed to read plugins declared by '${dep}':`, err);
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3463
|
+
for (const entry of entries) {
|
|
3464
|
+
try {
|
|
3465
|
+
const file = resolve4(pkgDir, entry);
|
|
3466
|
+
if (!isInside(pkgDir, file)) {
|
|
3467
|
+
throw new Error(`plugin path '${entry}' escapes package '${dep}'`);
|
|
3468
|
+
}
|
|
3469
|
+
const mod = await import(pathToFileURL(file).href);
|
|
3470
|
+
const plugins = pluginsOf(mod);
|
|
3471
|
+
if (plugins.length === 0) {
|
|
3472
|
+
throw new Error(`'${entry}' exports no plugin (name, pluginType, execute)`);
|
|
3473
|
+
}
|
|
3474
|
+
for (const plugin of plugins) {
|
|
3475
|
+
registry.register(plugin.name, plugin);
|
|
3476
|
+
loaded++;
|
|
3477
|
+
}
|
|
3478
|
+
} catch (err) {
|
|
3479
|
+
console.error(`attest: failed to load plugin '${entry}' from '${dep}':`, err);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
return loaded;
|
|
3484
|
+
}
|
|
3348
3485
|
|
|
3349
3486
|
// src/continuous.ts
|
|
3350
3487
|
import { EventEmitter } from "events";
|
|
@@ -3724,6 +3861,172 @@ the assistant's response meets your needs. Keep responses concise (1-3 sentences
|
|
|
3724
3861
|
max_tokens: 200
|
|
3725
3862
|
};
|
|
3726
3863
|
|
|
3864
|
+
// src/simulation/mock-tools.ts
|
|
3865
|
+
var MockToolRegistry = class {
|
|
3866
|
+
tools = /* @__PURE__ */ new Map();
|
|
3867
|
+
register(name, fn) {
|
|
3868
|
+
this.tools.set(name, fn);
|
|
3869
|
+
}
|
|
3870
|
+
/**
|
|
3871
|
+
* Run `fn` with the registered mocks active. `TraceBuilder.addToolCall`
|
|
3872
|
+
* returns the mock's result for a registered tool name. Scopes are
|
|
3873
|
+
* isolated per async call chain (AsyncLocalStorage).
|
|
3874
|
+
*/
|
|
3875
|
+
run(fn) {
|
|
3876
|
+
return activeMockRegistry.run(new Map(this.tools), fn);
|
|
3877
|
+
}
|
|
3878
|
+
};
|
|
3879
|
+
function mockTool(name, fn) {
|
|
3880
|
+
const wrapper = ((args) => fn(args));
|
|
3881
|
+
return Object.assign(wrapper, { mockToolName: name });
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
// src/simulation/fault-inject.ts
|
|
3885
|
+
function faultInject(options2, fn) {
|
|
3886
|
+
const errorRate = options2.errorRate ?? 0;
|
|
3887
|
+
const latencyJitterMs = options2.latencyJitterMs ?? 0;
|
|
3888
|
+
const rng = createRng(options2.seed);
|
|
3889
|
+
return async (...args) => {
|
|
3890
|
+
if (errorRate > 0 && rng() < errorRate) {
|
|
3891
|
+
throw new Error(`Injected fault in ${fn.name || "anonymous"}`);
|
|
3892
|
+
}
|
|
3893
|
+
if (latencyJitterMs > 0) {
|
|
3894
|
+
await sleep(rng() * latencyJitterMs);
|
|
3895
|
+
}
|
|
3896
|
+
return fn(...args);
|
|
3897
|
+
};
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
// src/simulation/verdict.ts
|
|
3901
|
+
function aggregateOutcomes(outcomes) {
|
|
3902
|
+
if (outcomes.includes(OUTCOME_FAIL)) return OUTCOME_FAIL;
|
|
3903
|
+
if (outcomes.includes(OUTCOME_SKIP)) return OUTCOME_SKIP;
|
|
3904
|
+
return OUTCOME_PASS;
|
|
3905
|
+
}
|
|
3906
|
+
function sumNotEvaluatedReasons(results) {
|
|
3907
|
+
const counts = {};
|
|
3908
|
+
for (const r of results) {
|
|
3909
|
+
for (const [reason, n] of Object.entries(r.notEvaluatedReasons)) {
|
|
3910
|
+
counts[reason] = (counts[reason] ?? 0) + n;
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
return counts;
|
|
3914
|
+
}
|
|
3915
|
+
function formatSkipReason(counts) {
|
|
3916
|
+
const parts = Object.entries(counts).map(([reason, n]) => n === 1 ? reason : `${reason} \xD7${n}`);
|
|
3917
|
+
return `not evaluated: ${parts.join(", ")}`;
|
|
3918
|
+
}
|
|
3919
|
+
function renderVerdictDiagnostics(verdict, color = false) {
|
|
3920
|
+
return verdict instanceof AgentResult ? renderDiagnostics(verdict, { color }) : verdict.renderDiagnostics?.({ color }) ?? "";
|
|
3921
|
+
}
|
|
3922
|
+
function verdictsDiagnostics(results, label, color = false) {
|
|
3923
|
+
const blocks = results.flatMap(
|
|
3924
|
+
(r, i) => r.outcome() === OUTCOME_PASS ? [] : [`${label} ${i + 1}/${results.length}:${renderVerdictDiagnostics(r, color)}`]
|
|
3925
|
+
);
|
|
3926
|
+
return blocks.length > 0 ? blocks.join("\n") : ` (no failing ${label}s)
|
|
3927
|
+
`;
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
// src/simulation/repeat.ts
|
|
3931
|
+
var RepeatResult = class {
|
|
3932
|
+
results;
|
|
3933
|
+
constructor(results = []) {
|
|
3934
|
+
this.results = results;
|
|
3935
|
+
}
|
|
3936
|
+
get count() {
|
|
3937
|
+
return this.results.length;
|
|
3938
|
+
}
|
|
3939
|
+
/** Trials skipped because an assertion was not evaluated. */
|
|
3940
|
+
get notEvaluated() {
|
|
3941
|
+
return this.results.filter((r) => r.outcome() === OUTCOME_SKIP).length;
|
|
3942
|
+
}
|
|
3943
|
+
/** Passing / evaluated trials; null when no trial was evaluated. */
|
|
3944
|
+
get passRate() {
|
|
3945
|
+
const evaluated = this.count - this.notEvaluated;
|
|
3946
|
+
if (evaluated === 0) return null;
|
|
3947
|
+
return this.results.filter((r) => r.outcome() === OUTCOME_PASS).length / evaluated;
|
|
3948
|
+
}
|
|
3949
|
+
/** True when every trial's outcome is pass (soft failures included). */
|
|
3950
|
+
get allPassed() {
|
|
3951
|
+
return this.results.every((r) => r.outcome() === OUTCOME_PASS);
|
|
3952
|
+
}
|
|
3953
|
+
outcome(options2) {
|
|
3954
|
+
return aggregateOutcomes(this.results.map((r) => r.outcome(options2)));
|
|
3955
|
+
}
|
|
3956
|
+
get notEvaluatedReasons() {
|
|
3957
|
+
return sumNotEvaluatedReasons(this.results);
|
|
3958
|
+
}
|
|
3959
|
+
skipReason() {
|
|
3960
|
+
return formatSkipReason(this.notEvaluatedReasons);
|
|
3961
|
+
}
|
|
3962
|
+
renderDiagnostics(options2) {
|
|
3963
|
+
return verdictsDiagnostics(this.results, "trial", options2?.color);
|
|
3964
|
+
}
|
|
3965
|
+
};
|
|
3966
|
+
function repeat(n, fn) {
|
|
3967
|
+
return async (...args) => {
|
|
3968
|
+
const results = [];
|
|
3969
|
+
for (let i = 0; i < n; i++) {
|
|
3970
|
+
results.push(await fn(...args));
|
|
3971
|
+
}
|
|
3972
|
+
return new RepeatResult(results);
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
// src/simulation/scenario.ts
|
|
3977
|
+
var ScenarioResult = class {
|
|
3978
|
+
config;
|
|
3979
|
+
results;
|
|
3980
|
+
constructor(config2, results = []) {
|
|
3981
|
+
this.config = config2;
|
|
3982
|
+
this.results = results;
|
|
3983
|
+
}
|
|
3984
|
+
/** True when the aggregate `outcome()` is pass. */
|
|
3985
|
+
get passed() {
|
|
3986
|
+
return this.outcome() === OUTCOME_PASS;
|
|
3987
|
+
}
|
|
3988
|
+
outcome(options2) {
|
|
3989
|
+
return aggregateOutcomes(this.results.map((r) => r.outcome(options2)));
|
|
3990
|
+
}
|
|
3991
|
+
get notEvaluatedReasons() {
|
|
3992
|
+
return sumNotEvaluatedReasons(this.results);
|
|
3993
|
+
}
|
|
3994
|
+
skipReason() {
|
|
3995
|
+
return formatSkipReason(this.notEvaluatedReasons);
|
|
3996
|
+
}
|
|
3997
|
+
renderDiagnostics(options2) {
|
|
3998
|
+
return verdictsDiagnostics(this.results, "turn", options2?.color);
|
|
3999
|
+
}
|
|
4000
|
+
};
|
|
4001
|
+
function scenario(options2, fn) {
|
|
4002
|
+
const config2 = {
|
|
4003
|
+
persona: options2.persona,
|
|
4004
|
+
mockTools: { ...options2.mockTools },
|
|
4005
|
+
faultErrorRate: options2.faultErrorRate ?? 0,
|
|
4006
|
+
faultLatencyJitterMs: options2.faultLatencyJitterMs ?? 0,
|
|
4007
|
+
maxTurns: options2.maxTurns ?? 1,
|
|
4008
|
+
seed: options2.seed
|
|
4009
|
+
};
|
|
4010
|
+
return async (...args) => {
|
|
4011
|
+
const run = async () => {
|
|
4012
|
+
const results = [];
|
|
4013
|
+
const rng = createRng(config2.seed);
|
|
4014
|
+
for (let turn = 0; turn < config2.maxTurns; turn++) {
|
|
4015
|
+
if (config2.faultErrorRate > 0 && rng() < config2.faultErrorRate) {
|
|
4016
|
+
throw new Error(`Injected fault at turn ${turn}`);
|
|
4017
|
+
}
|
|
4018
|
+
if (config2.faultLatencyJitterMs > 0) {
|
|
4019
|
+
await sleep(rng() * config2.faultLatencyJitterMs);
|
|
4020
|
+
}
|
|
4021
|
+
results.push(await fn({ persona: config2.persona, turn }, ...args));
|
|
4022
|
+
}
|
|
4023
|
+
return new ScenarioResult(config2, results);
|
|
4024
|
+
};
|
|
4025
|
+
const mocks = new Map(Object.entries(config2.mockTools));
|
|
4026
|
+
return mocks.size > 0 ? activeMockRegistry.run(mocks, run) : run();
|
|
4027
|
+
};
|
|
4028
|
+
}
|
|
4029
|
+
|
|
3727
4030
|
// src/adapters/manual.ts
|
|
3728
4031
|
var ManualAdapter = class {
|
|
3729
4032
|
agentId;
|
|
@@ -3935,7 +4238,7 @@ var OTelAdapter = class _OTelAdapter {
|
|
|
3935
4238
|
}
|
|
3936
4239
|
};
|
|
3937
4240
|
function runConvert(engine, args, payload) {
|
|
3938
|
-
return new Promise((
|
|
4241
|
+
return new Promise((resolve5, reject) => {
|
|
3939
4242
|
const child = spawn5(engine, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
3940
4243
|
let timer;
|
|
3941
4244
|
child.on("error", (e) => {
|
|
@@ -3988,7 +4291,7 @@ function runConvert(engine, args, payload) {
|
|
|
3988
4291
|
);
|
|
3989
4292
|
return;
|
|
3990
4293
|
}
|
|
3991
|
-
|
|
4294
|
+
resolve5(Buffer.concat(out).toString("utf8"));
|
|
3992
4295
|
});
|
|
3993
4296
|
stdin.end(payload);
|
|
3994
4297
|
});
|
|
@@ -4155,6 +4458,7 @@ export {
|
|
|
4155
4458
|
GeminiAdapter,
|
|
4156
4459
|
LangChainAdapter,
|
|
4157
4460
|
ManualAdapter,
|
|
4461
|
+
MockToolRegistry,
|
|
4158
4462
|
OTLPConversionError,
|
|
4159
4463
|
OTelAdapter,
|
|
4160
4464
|
OUTCOME_FAIL,
|
|
@@ -4174,6 +4478,7 @@ export {
|
|
|
4174
4478
|
REASON_REPLAY_MISS,
|
|
4175
4479
|
REASON_SHORT_CIRCUITED,
|
|
4176
4480
|
REASON_SIMULATION,
|
|
4481
|
+
RepeatResult,
|
|
4177
4482
|
ReplayError,
|
|
4178
4483
|
ReplayMissError,
|
|
4179
4484
|
ReplayNotEvaluatedError,
|
|
@@ -4186,6 +4491,7 @@ export {
|
|
|
4186
4491
|
STEP_RETRIEVAL,
|
|
4187
4492
|
STEP_TOOL_CALL,
|
|
4188
4493
|
Sampler,
|
|
4494
|
+
ScenarioResult,
|
|
4189
4495
|
TIER_1,
|
|
4190
4496
|
TIER_2,
|
|
4191
4497
|
TIER_3,
|
|
@@ -4220,6 +4526,7 @@ export {
|
|
|
4220
4526
|
executePluginAssertion,
|
|
4221
4527
|
extractId,
|
|
4222
4528
|
extractResult,
|
|
4529
|
+
faultInject,
|
|
4223
4530
|
firstMiss,
|
|
4224
4531
|
formatUsage,
|
|
4225
4532
|
getSampleRate,
|
|
@@ -4231,17 +4538,23 @@ export {
|
|
|
4231
4538
|
loadFixture,
|
|
4232
4539
|
loadLabelsCSV,
|
|
4233
4540
|
loadLabelsJSONL,
|
|
4541
|
+
loadPackagePlugins,
|
|
4542
|
+
mockTool,
|
|
4234
4543
|
parseUsage,
|
|
4235
4544
|
previewLine,
|
|
4236
4545
|
promptHash,
|
|
4237
4546
|
record,
|
|
4547
|
+
registerPlugin,
|
|
4238
4548
|
renderAssertionFailure,
|
|
4239
4549
|
renderDiagnostics,
|
|
4240
4550
|
renderSummary,
|
|
4551
|
+
renderVerdictDiagnostics,
|
|
4241
4552
|
renderVitest,
|
|
4553
|
+
repeat,
|
|
4242
4554
|
requireSimulationCapability,
|
|
4243
4555
|
resetConfig,
|
|
4244
4556
|
resolveMode,
|
|
4557
|
+
scenario,
|
|
4245
4558
|
simulationEnvEnabled,
|
|
4246
4559
|
startReplay,
|
|
4247
4560
|
startWorld,
|