@attest-ai/core 1.0.0-rc.3 → 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.3";
381
- var ENGINE_VERSION = "1.0.0-rc.3";
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) {
@@ -1095,7 +1105,8 @@ var AttestClient = class {
1095
1105
  const params = {
1096
1106
  trace,
1097
1107
  assertions: [...assertions],
1098
- ...options2?.budget !== void 0 ? { budget: options2.budget } : {}
1108
+ ...options2?.budget !== void 0 ? { budget: options2.budget } : {},
1109
+ ...options2?.context !== void 0 ? { context: options2.context } : {}
1099
1110
  };
1100
1111
  const raw = await this.sendRequest(
1101
1112
  "evaluate_batch",
@@ -1184,6 +1195,26 @@ function requireSimulationCapability(init) {
1184
1195
 
1185
1196
  // src/trace.ts
1186
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
1187
1218
  var TraceBuilder = class {
1188
1219
  traceId;
1189
1220
  agentId;
@@ -1215,11 +1246,22 @@ var TraceBuilder = class {
1215
1246
  return this;
1216
1247
  }
1217
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
+ }
1218
1260
  this.steps.push({
1219
1261
  type: STEP_TOOL_CALL,
1220
1262
  name,
1221
1263
  args: options2?.args,
1222
- result: options2?.result,
1264
+ result,
1223
1265
  metadata: options2?.metadata
1224
1266
  });
1225
1267
  return this;
@@ -2874,18 +2916,18 @@ function serve(engine, args) {
2874
2916
  let stdout = "";
2875
2917
  let stderr = "";
2876
2918
  let resolveReady = () => void 0;
2877
- const ready = new Promise((resolve4) => {
2878
- resolveReady = resolve4;
2919
+ const ready = new Promise((resolve5) => {
2920
+ resolveReady = resolve5;
2879
2921
  });
2880
- const exited = new Promise((resolve4) => {
2922
+ const exited = new Promise((resolve5) => {
2881
2923
  child.on("close", (code, signal) => {
2882
2924
  resolveReady(null);
2883
- resolve4({ code, signal });
2925
+ resolve5({ code, signal });
2884
2926
  });
2885
2927
  child.on("error", (err) => {
2886
2928
  stderr += err.message;
2887
2929
  resolveReady(null);
2888
- resolve4({ code: null, signal: null });
2930
+ resolve5({ code: null, signal: null });
2889
2931
  });
2890
2932
  });
2891
2933
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
@@ -3142,18 +3184,18 @@ async function start(name, mode, opts) {
3142
3184
  let stdout = "";
3143
3185
  let stderr = "";
3144
3186
  let resolveReady = () => void 0;
3145
- const ready = new Promise((resolve4) => {
3146
- resolveReady = resolve4;
3187
+ const ready = new Promise((resolve5) => {
3188
+ resolveReady = resolve5;
3147
3189
  });
3148
- const exited = new Promise((resolve4) => {
3190
+ const exited = new Promise((resolve5) => {
3149
3191
  child.on("close", (code) => {
3150
3192
  resolveReady(null);
3151
- resolve4(code);
3193
+ resolve5(code);
3152
3194
  });
3153
3195
  child.on("error", (err) => {
3154
3196
  stderr += err.message;
3155
3197
  resolveReady(null);
3156
- resolve4(null);
3198
+ resolve5(null);
3157
3199
  });
3158
3200
  });
3159
3201
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
@@ -3278,18 +3320,18 @@ async function start2(file, opts) {
3278
3320
  let stdout = "";
3279
3321
  let stderr = "";
3280
3322
  let resolveReady = () => void 0;
3281
- const ready = new Promise((resolve4) => {
3282
- resolveReady = resolve4;
3323
+ const ready = new Promise((resolve5) => {
3324
+ resolveReady = resolve5;
3283
3325
  });
3284
- const exited = new Promise((resolve4) => {
3326
+ const exited = new Promise((resolve5) => {
3285
3327
  child.on("close", (code) => {
3286
3328
  resolveReady(null);
3287
- resolve4(code);
3329
+ resolve5(code);
3288
3330
  });
3289
3331
  child.on("error", (err) => {
3290
3332
  stderr += err.message;
3291
3333
  resolveReady(null);
3292
- resolve4(null);
3334
+ resolve5(null);
3293
3335
  });
3294
3336
  });
3295
3337
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
@@ -3370,8 +3412,8 @@ function parseFinal(out) {
3370
3412
  }
3371
3413
 
3372
3414
  // src/context.ts
3373
- var import_node_async_hooks = require("async_hooks");
3374
- 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();
3375
3417
 
3376
3418
  // src/agent.ts
3377
3419
  var Agent = class {
@@ -3458,6 +3500,9 @@ function tier(level) {
3458
3500
  }
3459
3501
 
3460
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");
3461
3506
  var PluginRegistry = class {
3462
3507
  plugins = /* @__PURE__ */ new Map();
3463
3508
  register(name, plugin) {
@@ -3488,10 +3533,19 @@ var PluginRegistry = class {
3488
3533
  };
3489
3534
  async function executePluginAssertion(plugin, trace, spec, client, traceId2, assertionId2, timeoutMs = 3e4) {
3490
3535
  const resultPromise = Promise.resolve(plugin.execute(trace, spec));
3536
+ let timer;
3491
3537
  const timeoutPromise = new Promise((_, reject) => {
3492
- 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
+ );
3493
3542
  });
3494
- 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
+ }
3495
3549
  await client.submitPluginResult(
3496
3550
  traceId2,
3497
3551
  plugin.name,
@@ -3502,6 +3556,100 @@ async function executePluginAssertion(plugin, trace, spec, client, traceId2, ass
3502
3556
  );
3503
3557
  return result;
3504
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
+ }
3505
3653
 
3506
3654
  // src/continuous.ts
3507
3655
  var import_node_events = require("events");
@@ -3881,6 +4029,172 @@ the assistant's response meets your needs. Keep responses concise (1-3 sentences
3881
4029
  max_tokens: 200
3882
4030
  };
3883
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
+
3884
4198
  // src/adapters/manual.ts
3885
4199
  var ManualAdapter = class {
3886
4200
  agentId;
@@ -4092,7 +4406,7 @@ var OTelAdapter = class _OTelAdapter {
4092
4406
  }
4093
4407
  };
4094
4408
  function runConvert(engine, args, payload) {
4095
- return new Promise((resolve4, reject) => {
4409
+ return new Promise((resolve5, reject) => {
4096
4410
  const child = (0, import_node_child_process5.spawn)(engine, args, { stdio: ["pipe", "pipe", "pipe"] });
4097
4411
  let timer;
4098
4412
  child.on("error", (e) => {
@@ -4145,7 +4459,7 @@ function runConvert(engine, args, payload) {
4145
4459
  );
4146
4460
  return;
4147
4461
  }
4148
- resolve4(Buffer.concat(out).toString("utf8"));
4462
+ resolve5(Buffer.concat(out).toString("utf8"));
4149
4463
  });
4150
4464
  stdin.end(payload);
4151
4465
  });
@@ -4313,6 +4627,7 @@ var LangChainAdapter = class {
4313
4627
  GeminiAdapter,
4314
4628
  LangChainAdapter,
4315
4629
  ManualAdapter,
4630
+ MockToolRegistry,
4316
4631
  OTLPConversionError,
4317
4632
  OTelAdapter,
4318
4633
  OUTCOME_FAIL,
@@ -4332,6 +4647,7 @@ var LangChainAdapter = class {
4332
4647
  REASON_REPLAY_MISS,
4333
4648
  REASON_SHORT_CIRCUITED,
4334
4649
  REASON_SIMULATION,
4650
+ RepeatResult,
4335
4651
  ReplayError,
4336
4652
  ReplayMissError,
4337
4653
  ReplayNotEvaluatedError,
@@ -4344,6 +4660,7 @@ var LangChainAdapter = class {
4344
4660
  STEP_RETRIEVAL,
4345
4661
  STEP_TOOL_CALL,
4346
4662
  Sampler,
4663
+ ScenarioResult,
4347
4664
  TIER_1,
4348
4665
  TIER_2,
4349
4666
  TIER_3,
@@ -4378,6 +4695,7 @@ var LangChainAdapter = class {
4378
4695
  executePluginAssertion,
4379
4696
  extractId,
4380
4697
  extractResult,
4698
+ faultInject,
4381
4699
  firstMiss,
4382
4700
  formatUsage,
4383
4701
  getSampleRate,
@@ -4389,17 +4707,23 @@ var LangChainAdapter = class {
4389
4707
  loadFixture,
4390
4708
  loadLabelsCSV,
4391
4709
  loadLabelsJSONL,
4710
+ loadPackagePlugins,
4711
+ mockTool,
4392
4712
  parseUsage,
4393
4713
  previewLine,
4394
4714
  promptHash,
4395
4715
  record,
4716
+ registerPlugin,
4396
4717
  renderAssertionFailure,
4397
4718
  renderDiagnostics,
4398
4719
  renderSummary,
4720
+ renderVerdictDiagnostics,
4399
4721
  renderVitest,
4722
+ repeat,
4400
4723
  requireSimulationCapability,
4401
4724
  resetConfig,
4402
4725
  resolveMode,
4726
+ scenario,
4403
4727
  simulationEnvEnabled,
4404
4728
  startReplay,
4405
4729
  startWorld,