@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.
@@ -56,6 +56,7 @@ __export(index_exports, {
56
56
  GeminiAdapter: () => GeminiAdapter,
57
57
  LangChainAdapter: () => LangChainAdapter,
58
58
  ManualAdapter: () => ManualAdapter,
59
+ MockToolRegistry: () => MockToolRegistry,
59
60
  OTLPConversionError: () => OTLPConversionError,
60
61
  OTelAdapter: () => OTelAdapter,
61
62
  OUTCOME_FAIL: () => OUTCOME_FAIL,
@@ -75,6 +76,7 @@ __export(index_exports, {
75
76
  REASON_REPLAY_MISS: () => REASON_REPLAY_MISS,
76
77
  REASON_SHORT_CIRCUITED: () => REASON_SHORT_CIRCUITED,
77
78
  REASON_SIMULATION: () => REASON_SIMULATION,
79
+ RepeatResult: () => RepeatResult,
78
80
  ReplayError: () => ReplayError,
79
81
  ReplayMissError: () => ReplayMissError,
80
82
  ReplayNotEvaluatedError: () => ReplayNotEvaluatedError,
@@ -87,6 +89,7 @@ __export(index_exports, {
87
89
  STEP_RETRIEVAL: () => STEP_RETRIEVAL,
88
90
  STEP_TOOL_CALL: () => STEP_TOOL_CALL,
89
91
  Sampler: () => Sampler,
92
+ ScenarioResult: () => ScenarioResult,
90
93
  TIER_1: () => TIER_1,
91
94
  TIER_2: () => TIER_2,
92
95
  TIER_3: () => TIER_3,
@@ -121,6 +124,7 @@ __export(index_exports, {
121
124
  executePluginAssertion: () => executePluginAssertion,
122
125
  extractId: () => extractId,
123
126
  extractResult: () => extractResult,
127
+ faultInject: () => faultInject,
124
128
  firstMiss: () => firstMiss,
125
129
  formatUsage: () => formatUsage,
126
130
  getSampleRate: () => getSampleRate,
@@ -132,17 +136,23 @@ __export(index_exports, {
132
136
  loadFixture: () => loadFixture,
133
137
  loadLabelsCSV: () => loadLabelsCSV,
134
138
  loadLabelsJSONL: () => loadLabelsJSONL,
139
+ loadPackagePlugins: () => loadPackagePlugins,
140
+ mockTool: () => mockTool,
135
141
  parseUsage: () => parseUsage,
136
142
  previewLine: () => previewLine,
137
143
  promptHash: () => promptHash,
138
144
  record: () => record,
145
+ registerPlugin: () => registerPlugin,
139
146
  renderAssertionFailure: () => renderAssertionFailure,
140
147
  renderDiagnostics: () => renderDiagnostics,
141
148
  renderSummary: () => renderSummary,
149
+ renderVerdictDiagnostics: () => renderVerdictDiagnostics,
142
150
  renderVitest: () => renderVitest,
151
+ repeat: () => repeat,
143
152
  requireSimulationCapability: () => requireSimulationCapability,
144
153
  resetConfig: () => resetConfig,
145
154
  resolveMode: () => resolveMode,
155
+ scenario: () => scenario,
146
156
  simulationEnvEnabled: () => simulationEnvEnabled,
147
157
  startReplay: () => startReplay,
148
158
  startWorld: () => startWorld,
@@ -377,8 +387,8 @@ function extractId(response) {
377
387
  }
378
388
 
379
389
  // src/version.ts
380
- var VERSION = "1.0.0-rc.4";
381
- var ENGINE_VERSION = "1.0.0-rc.4";
390
+ var VERSION = "1.0.0-rc.5";
391
+ var ENGINE_VERSION = "1.0.0-rc.5";
382
392
 
383
393
  // src/engine-manager.ts
384
394
  var import_node_child_process = require("child_process");
@@ -446,7 +456,7 @@ function httpsGet(url, redirectCount = 0) {
446
456
  new Error(`Too many redirects (>${MAX_REDIRECTS}) following ${url}`)
447
457
  );
448
458
  }
449
- return new Promise((resolve4, reject) => {
459
+ return new Promise((resolve5, reject) => {
450
460
  const client = url.startsWith("https:") ? https : http;
451
461
  client.get(url, (res) => {
452
462
  const status = res.statusCode ?? 0;
@@ -457,7 +467,7 @@ function httpsGet(url, redirectCount = 0) {
457
467
  return;
458
468
  }
459
469
  res.resume();
460
- resolve4(httpsGet(location, redirectCount + 1));
470
+ resolve5(httpsGet(location, redirectCount + 1));
461
471
  return;
462
472
  }
463
473
  if (status < 200 || status >= 300) {
@@ -467,7 +477,7 @@ function httpsGet(url, redirectCount = 0) {
467
477
  }
468
478
  const chunks = [];
469
479
  res.on("data", (chunk) => chunks.push(chunk));
470
- res.on("end", () => resolve4(Buffer.concat(chunks)));
480
+ res.on("end", () => resolve5(Buffer.concat(chunks)));
471
481
  res.on("error", reject);
472
482
  }).on("error", reject);
473
483
  });
@@ -694,11 +704,11 @@ var EngineManager = class {
694
704
  }
695
705
  this.requestId += 1;
696
706
  const requestStr = encodeRequest(this.requestId, method, params);
697
- return new Promise((resolve4, reject) => {
707
+ return new Promise((resolve5, reject) => {
698
708
  const onLine = (line) => {
699
709
  try {
700
710
  const response = decodeResponse(line);
701
- resolve4(extractResult(response));
711
+ resolve5(extractResult(response));
702
712
  } catch (err) {
703
713
  reject(err);
704
714
  }
@@ -1065,12 +1075,12 @@ var AttestClient = class {
1065
1075
  throw new Error("Engine process not started.");
1066
1076
  }
1067
1077
  let capturedReqId;
1068
- const requestPromise = new Promise((resolve4, reject) => {
1078
+ const requestPromise = new Promise((resolve5, reject) => {
1069
1079
  this.writeChain = this.writeChain.then(() => {
1070
1080
  this.requestId += 1;
1071
1081
  const reqId = this.requestId;
1072
1082
  capturedReqId = reqId;
1073
- this.pending.set(reqId, { resolve: resolve4, reject });
1083
+ this.pending.set(reqId, { resolve: resolve5, reject });
1074
1084
  const requestStr = encodeRequest(reqId, method, params);
1075
1085
  cp.stdin.write(requestStr, (err) => {
1076
1086
  if (err) {
@@ -1185,6 +1195,26 @@ function requireSimulationCapability(init) {
1185
1195
 
1186
1196
  // src/trace.ts
1187
1197
  var import_node_crypto = require("crypto");
1198
+
1199
+ // src/simulation/context.ts
1200
+ var import_node_async_hooks = require("async_hooks");
1201
+ var activeMockRegistry = new import_node_async_hooks.AsyncLocalStorage();
1202
+ function createRng(seed) {
1203
+ if (seed === void 0) return Math.random;
1204
+ let state = seed >>> 0;
1205
+ return () => {
1206
+ state = state + 1831565813 >>> 0;
1207
+ let t = state;
1208
+ t = Math.imul(t ^ t >>> 15, t | 1);
1209
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
1210
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1211
+ };
1212
+ }
1213
+ function sleep(ms) {
1214
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
1215
+ }
1216
+
1217
+ // src/trace.ts
1188
1218
  var TraceBuilder = class {
1189
1219
  traceId;
1190
1220
  agentId;
@@ -1216,11 +1246,22 @@ var TraceBuilder = class {
1216
1246
  return this;
1217
1247
  }
1218
1248
  addToolCall(name, options2) {
1249
+ const mock = activeMockRegistry.getStore()?.get(name);
1250
+ let result = options2?.result;
1251
+ if (mock !== void 0) {
1252
+ const mocked = mock(options2?.args ?? {});
1253
+ if (typeof mocked?.then === "function") {
1254
+ throw new Error(`mock for tool '${name}' returned a Promise; mock tools must be synchronous`);
1255
+ }
1256
+ if (typeof mocked === "object" && mocked !== null && !Array.isArray(mocked)) {
1257
+ result = mocked;
1258
+ }
1259
+ }
1219
1260
  this.steps.push({
1220
1261
  type: STEP_TOOL_CALL,
1221
1262
  name,
1222
1263
  args: options2?.args,
1223
- result: options2?.result,
1264
+ result,
1224
1265
  metadata: options2?.metadata
1225
1266
  });
1226
1267
  return this;
@@ -2875,18 +2916,18 @@ function serve(engine, args) {
2875
2916
  let stdout = "";
2876
2917
  let stderr = "";
2877
2918
  let resolveReady = () => void 0;
2878
- const ready = new Promise((resolve4) => {
2879
- resolveReady = resolve4;
2919
+ const ready = new Promise((resolve5) => {
2920
+ resolveReady = resolve5;
2880
2921
  });
2881
- const exited = new Promise((resolve4) => {
2922
+ const exited = new Promise((resolve5) => {
2882
2923
  child.on("close", (code, signal) => {
2883
2924
  resolveReady(null);
2884
- resolve4({ code, signal });
2925
+ resolve5({ code, signal });
2885
2926
  });
2886
2927
  child.on("error", (err) => {
2887
2928
  stderr += err.message;
2888
2929
  resolveReady(null);
2889
- resolve4({ code: null, signal: null });
2930
+ resolve5({ code: null, signal: null });
2890
2931
  });
2891
2932
  });
2892
2933
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
@@ -3143,18 +3184,18 @@ async function start(name, mode, opts) {
3143
3184
  let stdout = "";
3144
3185
  let stderr = "";
3145
3186
  let resolveReady = () => void 0;
3146
- const ready = new Promise((resolve4) => {
3147
- resolveReady = resolve4;
3187
+ const ready = new Promise((resolve5) => {
3188
+ resolveReady = resolve5;
3148
3189
  });
3149
- const exited = new Promise((resolve4) => {
3190
+ const exited = new Promise((resolve5) => {
3150
3191
  child.on("close", (code) => {
3151
3192
  resolveReady(null);
3152
- resolve4(code);
3193
+ resolve5(code);
3153
3194
  });
3154
3195
  child.on("error", (err) => {
3155
3196
  stderr += err.message;
3156
3197
  resolveReady(null);
3157
- resolve4(null);
3198
+ resolve5(null);
3158
3199
  });
3159
3200
  });
3160
3201
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
@@ -3279,18 +3320,18 @@ async function start2(file, opts) {
3279
3320
  let stdout = "";
3280
3321
  let stderr = "";
3281
3322
  let resolveReady = () => void 0;
3282
- const ready = new Promise((resolve4) => {
3283
- resolveReady = resolve4;
3323
+ const ready = new Promise((resolve5) => {
3324
+ resolveReady = resolve5;
3284
3325
  });
3285
- const exited = new Promise((resolve4) => {
3326
+ const exited = new Promise((resolve5) => {
3286
3327
  child.on("close", (code) => {
3287
3328
  resolveReady(null);
3288
- resolve4(code);
3329
+ resolve5(code);
3289
3330
  });
3290
3331
  child.on("error", (err) => {
3291
3332
  stderr += err.message;
3292
3333
  resolveReady(null);
3293
- resolve4(null);
3334
+ resolve5(null);
3294
3335
  });
3295
3336
  });
3296
3337
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
@@ -3371,8 +3412,8 @@ function parseFinal(out) {
3371
3412
  }
3372
3413
 
3373
3414
  // src/context.ts
3374
- var import_node_async_hooks = require("async_hooks");
3375
- var activeBuilder = new import_node_async_hooks.AsyncLocalStorage();
3415
+ var import_node_async_hooks2 = require("async_hooks");
3416
+ var activeBuilder = new import_node_async_hooks2.AsyncLocalStorage();
3376
3417
 
3377
3418
  // src/agent.ts
3378
3419
  var Agent = class {
@@ -3459,6 +3500,9 @@ function tier(level) {
3459
3500
  }
3460
3501
 
3461
3502
  // src/plugins.ts
3503
+ var import_node_fs2 = require("fs");
3504
+ var import_node_path2 = require("path");
3505
+ var import_node_url2 = require("url");
3462
3506
  var PluginRegistry = class {
3463
3507
  plugins = /* @__PURE__ */ new Map();
3464
3508
  register(name, plugin) {
@@ -3489,10 +3533,19 @@ var PluginRegistry = class {
3489
3533
  };
3490
3534
  async function executePluginAssertion(plugin, trace, spec, client, traceId2, assertionId2, timeoutMs = 3e4) {
3491
3535
  const resultPromise = Promise.resolve(plugin.execute(trace, spec));
3536
+ let timer;
3492
3537
  const timeoutPromise = new Promise((_, reject) => {
3493
- setTimeout(() => reject(new Error(`Plugin '${plugin.name}' timed out after ${timeoutMs}ms`)), timeoutMs);
3538
+ timer = setTimeout(
3539
+ () => reject(new Error(`Plugin '${plugin.name}' timed out after ${timeoutMs}ms`)),
3540
+ timeoutMs
3541
+ );
3494
3542
  });
3495
- const result = await Promise.race([resultPromise, timeoutPromise]);
3543
+ let result;
3544
+ try {
3545
+ result = await Promise.race([resultPromise, timeoutPromise]);
3546
+ } finally {
3547
+ clearTimeout(timer);
3548
+ }
3496
3549
  await client.submitPluginResult(
3497
3550
  traceId2,
3498
3551
  plugin.name,
@@ -3503,6 +3556,100 @@ async function executePluginAssertion(plugin, trace, spec, client, traceId2, ass
3503
3556
  );
3504
3557
  return result;
3505
3558
  }
3559
+ function registerPlugin(registry, name, plugin) {
3560
+ registry.register(name, plugin);
3561
+ }
3562
+ function isAttestPlugin(value) {
3563
+ if (typeof value !== "object" || value === null) return false;
3564
+ const v = value;
3565
+ return typeof v["name"] === "string" && typeof v["pluginType"] === "string" && typeof v["execute"] === "function";
3566
+ }
3567
+ function readJson(path5) {
3568
+ return JSON.parse((0, import_node_fs2.readFileSync)(path5, "utf8"));
3569
+ }
3570
+ function findInstalledPackage(projectRoot, name) {
3571
+ let dir = (0, import_node_path2.resolve)(projectRoot);
3572
+ for (; ; ) {
3573
+ const candidate = (0, import_node_path2.join)(dir, "node_modules", name);
3574
+ if ((0, import_node_fs2.existsSync)((0, import_node_path2.join)(candidate, "package.json"))) return candidate;
3575
+ const parent = (0, import_node_path2.dirname)(dir);
3576
+ if (parent === dir) return void 0;
3577
+ dir = parent;
3578
+ }
3579
+ }
3580
+ function declaredPluginPaths(pkgJson, dep) {
3581
+ const attest = pkgJson["attest"];
3582
+ if (attest === void 0) return [];
3583
+ const plugins = typeof attest === "object" && attest !== null ? attest["plugins"] : void 0;
3584
+ if (plugins === void 0) return [];
3585
+ if (!Array.isArray(plugins) || !plugins.every((p) => typeof p === "string")) {
3586
+ throw new Error(`'${dep}': "attest.plugins" must be an array of strings`);
3587
+ }
3588
+ return plugins;
3589
+ }
3590
+ function pluginsOf(mod) {
3591
+ const def = mod["default"];
3592
+ const nested = typeof def === "object" && def !== null ? def : void 0;
3593
+ for (const candidate of [def, nested?.["default"]]) {
3594
+ if (isAttestPlugin(candidate)) return [candidate];
3595
+ }
3596
+ return [.../* @__PURE__ */ new Set([...Object.values(mod), ...Object.values(nested ?? {})])].filter(isAttestPlugin);
3597
+ }
3598
+ function isInside(root, file) {
3599
+ const within = (r, f) => {
3600
+ const rel = (0, import_node_path2.relative)(r, f);
3601
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${import_node_path2.sep}`) && !(0, import_node_path2.isAbsolute)(rel);
3602
+ };
3603
+ return within(root, file) && within((0, import_node_fs2.realpathSync)(root), (0, import_node_fs2.realpathSync)(file));
3604
+ }
3605
+ async function loadPackagePlugins(registry, projectRoot = process.cwd()) {
3606
+ let project;
3607
+ try {
3608
+ project = readJson((0, import_node_path2.join)(projectRoot, "package.json"));
3609
+ } catch (err) {
3610
+ console.error(`attest: cannot read ${(0, import_node_path2.join)(projectRoot, "package.json")}; no plugins loaded:`, err);
3611
+ return 0;
3612
+ }
3613
+ const depNames = /* @__PURE__ */ new Set();
3614
+ for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
3615
+ const deps = project[field];
3616
+ if (typeof deps === "object" && deps !== null) {
3617
+ for (const dep of Object.keys(deps)) depNames.add(dep);
3618
+ }
3619
+ }
3620
+ let loaded = 0;
3621
+ for (const dep of depNames) {
3622
+ const pkgDir = findInstalledPackage(projectRoot, dep);
3623
+ if (pkgDir === void 0) continue;
3624
+ let entries;
3625
+ try {
3626
+ entries = declaredPluginPaths(readJson((0, import_node_path2.join)(pkgDir, "package.json")), dep);
3627
+ } catch (err) {
3628
+ console.error(`attest: failed to read plugins declared by '${dep}':`, err);
3629
+ continue;
3630
+ }
3631
+ for (const entry of entries) {
3632
+ try {
3633
+ const file = (0, import_node_path2.resolve)(pkgDir, entry);
3634
+ if (!isInside(pkgDir, file)) {
3635
+ throw new Error(`plugin path '${entry}' escapes package '${dep}'`);
3636
+ }
3637
+ const mod = await import((0, import_node_url2.pathToFileURL)(file).href);
3638
+ const plugins = pluginsOf(mod);
3639
+ if (plugins.length === 0) {
3640
+ throw new Error(`'${entry}' exports no plugin (name, pluginType, execute)`);
3641
+ }
3642
+ for (const plugin of plugins) {
3643
+ registry.register(plugin.name, plugin);
3644
+ loaded++;
3645
+ }
3646
+ } catch (err) {
3647
+ console.error(`attest: failed to load plugin '${entry}' from '${dep}':`, err);
3648
+ }
3649
+ }
3650
+ }
3651
+ return loaded;
3652
+ }
3506
3653
 
3507
3654
  // src/continuous.ts
3508
3655
  var import_node_events = require("events");
@@ -3882,6 +4029,172 @@ the assistant's response meets your needs. Keep responses concise (1-3 sentences
3882
4029
  max_tokens: 200
3883
4030
  };
3884
4031
 
4032
+ // src/simulation/mock-tools.ts
4033
+ var MockToolRegistry = class {
4034
+ tools = /* @__PURE__ */ new Map();
4035
+ register(name, fn) {
4036
+ this.tools.set(name, fn);
4037
+ }
4038
+ /**
4039
+ * Run `fn` with the registered mocks active. `TraceBuilder.addToolCall`
4040
+ * returns the mock's result for a registered tool name. Scopes are
4041
+ * isolated per async call chain (AsyncLocalStorage).
4042
+ */
4043
+ run(fn) {
4044
+ return activeMockRegistry.run(new Map(this.tools), fn);
4045
+ }
4046
+ };
4047
+ function mockTool(name, fn) {
4048
+ const wrapper = ((args) => fn(args));
4049
+ return Object.assign(wrapper, { mockToolName: name });
4050
+ }
4051
+
4052
+ // src/simulation/fault-inject.ts
4053
+ function faultInject(options2, fn) {
4054
+ const errorRate = options2.errorRate ?? 0;
4055
+ const latencyJitterMs = options2.latencyJitterMs ?? 0;
4056
+ const rng = createRng(options2.seed);
4057
+ return async (...args) => {
4058
+ if (errorRate > 0 && rng() < errorRate) {
4059
+ throw new Error(`Injected fault in ${fn.name || "anonymous"}`);
4060
+ }
4061
+ if (latencyJitterMs > 0) {
4062
+ await sleep(rng() * latencyJitterMs);
4063
+ }
4064
+ return fn(...args);
4065
+ };
4066
+ }
4067
+
4068
+ // src/simulation/verdict.ts
4069
+ function aggregateOutcomes(outcomes) {
4070
+ if (outcomes.includes(OUTCOME_FAIL)) return OUTCOME_FAIL;
4071
+ if (outcomes.includes(OUTCOME_SKIP)) return OUTCOME_SKIP;
4072
+ return OUTCOME_PASS;
4073
+ }
4074
+ function sumNotEvaluatedReasons(results) {
4075
+ const counts = {};
4076
+ for (const r of results) {
4077
+ for (const [reason, n] of Object.entries(r.notEvaluatedReasons)) {
4078
+ counts[reason] = (counts[reason] ?? 0) + n;
4079
+ }
4080
+ }
4081
+ return counts;
4082
+ }
4083
+ function formatSkipReason(counts) {
4084
+ const parts = Object.entries(counts).map(([reason, n]) => n === 1 ? reason : `${reason} \xD7${n}`);
4085
+ return `not evaluated: ${parts.join(", ")}`;
4086
+ }
4087
+ function renderVerdictDiagnostics(verdict, color = false) {
4088
+ return verdict instanceof AgentResult ? renderDiagnostics(verdict, { color }) : verdict.renderDiagnostics?.({ color }) ?? "";
4089
+ }
4090
+ function verdictsDiagnostics(results, label, color = false) {
4091
+ const blocks = results.flatMap(
4092
+ (r, i) => r.outcome() === OUTCOME_PASS ? [] : [`${label} ${i + 1}/${results.length}:${renderVerdictDiagnostics(r, color)}`]
4093
+ );
4094
+ return blocks.length > 0 ? blocks.join("\n") : ` (no failing ${label}s)
4095
+ `;
4096
+ }
4097
+
4098
+ // src/simulation/repeat.ts
4099
+ var RepeatResult = class {
4100
+ results;
4101
+ constructor(results = []) {
4102
+ this.results = results;
4103
+ }
4104
+ get count() {
4105
+ return this.results.length;
4106
+ }
4107
+ /** Trials skipped because an assertion was not evaluated. */
4108
+ get notEvaluated() {
4109
+ return this.results.filter((r) => r.outcome() === OUTCOME_SKIP).length;
4110
+ }
4111
+ /** Passing / evaluated trials; null when no trial was evaluated. */
4112
+ get passRate() {
4113
+ const evaluated = this.count - this.notEvaluated;
4114
+ if (evaluated === 0) return null;
4115
+ return this.results.filter((r) => r.outcome() === OUTCOME_PASS).length / evaluated;
4116
+ }
4117
+ /** True when every trial's outcome is pass (soft failures included). */
4118
+ get allPassed() {
4119
+ return this.results.every((r) => r.outcome() === OUTCOME_PASS);
4120
+ }
4121
+ outcome(options2) {
4122
+ return aggregateOutcomes(this.results.map((r) => r.outcome(options2)));
4123
+ }
4124
+ get notEvaluatedReasons() {
4125
+ return sumNotEvaluatedReasons(this.results);
4126
+ }
4127
+ skipReason() {
4128
+ return formatSkipReason(this.notEvaluatedReasons);
4129
+ }
4130
+ renderDiagnostics(options2) {
4131
+ return verdictsDiagnostics(this.results, "trial", options2?.color);
4132
+ }
4133
+ };
4134
+ function repeat(n, fn) {
4135
+ return async (...args) => {
4136
+ const results = [];
4137
+ for (let i = 0; i < n; i++) {
4138
+ results.push(await fn(...args));
4139
+ }
4140
+ return new RepeatResult(results);
4141
+ };
4142
+ }
4143
+
4144
+ // src/simulation/scenario.ts
4145
+ var ScenarioResult = class {
4146
+ config;
4147
+ results;
4148
+ constructor(config2, results = []) {
4149
+ this.config = config2;
4150
+ this.results = results;
4151
+ }
4152
+ /** True when the aggregate `outcome()` is pass. */
4153
+ get passed() {
4154
+ return this.outcome() === OUTCOME_PASS;
4155
+ }
4156
+ outcome(options2) {
4157
+ return aggregateOutcomes(this.results.map((r) => r.outcome(options2)));
4158
+ }
4159
+ get notEvaluatedReasons() {
4160
+ return sumNotEvaluatedReasons(this.results);
4161
+ }
4162
+ skipReason() {
4163
+ return formatSkipReason(this.notEvaluatedReasons);
4164
+ }
4165
+ renderDiagnostics(options2) {
4166
+ return verdictsDiagnostics(this.results, "turn", options2?.color);
4167
+ }
4168
+ };
4169
+ function scenario(options2, fn) {
4170
+ const config2 = {
4171
+ persona: options2.persona,
4172
+ mockTools: { ...options2.mockTools },
4173
+ faultErrorRate: options2.faultErrorRate ?? 0,
4174
+ faultLatencyJitterMs: options2.faultLatencyJitterMs ?? 0,
4175
+ maxTurns: options2.maxTurns ?? 1,
4176
+ seed: options2.seed
4177
+ };
4178
+ return async (...args) => {
4179
+ const run = async () => {
4180
+ const results = [];
4181
+ const rng = createRng(config2.seed);
4182
+ for (let turn = 0; turn < config2.maxTurns; turn++) {
4183
+ if (config2.faultErrorRate > 0 && rng() < config2.faultErrorRate) {
4184
+ throw new Error(`Injected fault at turn ${turn}`);
4185
+ }
4186
+ if (config2.faultLatencyJitterMs > 0) {
4187
+ await sleep(rng() * config2.faultLatencyJitterMs);
4188
+ }
4189
+ results.push(await fn({ persona: config2.persona, turn }, ...args));
4190
+ }
4191
+ return new ScenarioResult(config2, results);
4192
+ };
4193
+ const mocks = new Map(Object.entries(config2.mockTools));
4194
+ return mocks.size > 0 ? activeMockRegistry.run(mocks, run) : run();
4195
+ };
4196
+ }
4197
+
3885
4198
  // src/adapters/manual.ts
3886
4199
  var ManualAdapter = class {
3887
4200
  agentId;
@@ -4093,7 +4406,7 @@ var OTelAdapter = class _OTelAdapter {
4093
4406
  }
4094
4407
  };
4095
4408
  function runConvert(engine, args, payload) {
4096
- return new Promise((resolve4, reject) => {
4409
+ return new Promise((resolve5, reject) => {
4097
4410
  const child = (0, import_node_child_process5.spawn)(engine, args, { stdio: ["pipe", "pipe", "pipe"] });
4098
4411
  let timer;
4099
4412
  child.on("error", (e) => {
@@ -4146,7 +4459,7 @@ function runConvert(engine, args, payload) {
4146
4459
  );
4147
4460
  return;
4148
4461
  }
4149
- resolve4(Buffer.concat(out).toString("utf8"));
4462
+ resolve5(Buffer.concat(out).toString("utf8"));
4150
4463
  });
4151
4464
  stdin.end(payload);
4152
4465
  });
@@ -4314,6 +4627,7 @@ var LangChainAdapter = class {
4314
4627
  GeminiAdapter,
4315
4628
  LangChainAdapter,
4316
4629
  ManualAdapter,
4630
+ MockToolRegistry,
4317
4631
  OTLPConversionError,
4318
4632
  OTelAdapter,
4319
4633
  OUTCOME_FAIL,
@@ -4333,6 +4647,7 @@ var LangChainAdapter = class {
4333
4647
  REASON_REPLAY_MISS,
4334
4648
  REASON_SHORT_CIRCUITED,
4335
4649
  REASON_SIMULATION,
4650
+ RepeatResult,
4336
4651
  ReplayError,
4337
4652
  ReplayMissError,
4338
4653
  ReplayNotEvaluatedError,
@@ -4345,6 +4660,7 @@ var LangChainAdapter = class {
4345
4660
  STEP_RETRIEVAL,
4346
4661
  STEP_TOOL_CALL,
4347
4662
  Sampler,
4663
+ ScenarioResult,
4348
4664
  TIER_1,
4349
4665
  TIER_2,
4350
4666
  TIER_3,
@@ -4379,6 +4695,7 @@ var LangChainAdapter = class {
4379
4695
  executePluginAssertion,
4380
4696
  extractId,
4381
4697
  extractResult,
4698
+ faultInject,
4382
4699
  firstMiss,
4383
4700
  formatUsage,
4384
4701
  getSampleRate,
@@ -4390,17 +4707,23 @@ var LangChainAdapter = class {
4390
4707
  loadFixture,
4391
4708
  loadLabelsCSV,
4392
4709
  loadLabelsJSONL,
4710
+ loadPackagePlugins,
4711
+ mockTool,
4393
4712
  parseUsage,
4394
4713
  previewLine,
4395
4714
  promptHash,
4396
4715
  record,
4716
+ registerPlugin,
4397
4717
  renderAssertionFailure,
4398
4718
  renderDiagnostics,
4399
4719
  renderSummary,
4720
+ renderVerdictDiagnostics,
4400
4721
  renderVitest,
4722
+ repeat,
4401
4723
  requireSimulationCapability,
4402
4724
  resetConfig,
4403
4725
  resolveMode,
4726
+ scenario,
4404
4727
  simulationEnvEnabled,
4405
4728
  startReplay,
4406
4729
  startWorld,