@attest-ai/core 0.4.2 → 0.6.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.
Files changed (116) hide show
  1. package/dist/cjs/index.cjs +2595 -0
  2. package/dist/cjs/index.cjs.map +1 -0
  3. package/dist/cjs/index.d.cts +1111 -0
  4. package/dist/cjs/proto/index.cjs +208 -0
  5. package/dist/cjs/proto/index.cjs.map +1 -0
  6. package/dist/cjs/proto/index.d.cts +269 -0
  7. package/dist/esm/cli.d.ts +9 -0
  8. package/dist/esm/cli.js +154 -0
  9. package/dist/esm/cli.js.map +1 -0
  10. package/dist/esm/index.d.ts +1111 -0
  11. package/dist/esm/index.js +2490 -0
  12. package/dist/esm/index.js.map +1 -0
  13. package/dist/esm/proto/index.d.ts +269 -0
  14. package/dist/esm/proto/index.js +152 -0
  15. package/dist/esm/proto/index.js.map +1 -0
  16. package/package.json +37 -11
  17. package/dist/adapters/anthropic.d.ts +0 -31
  18. package/dist/adapters/anthropic.d.ts.map +0 -1
  19. package/dist/adapters/anthropic.js +0 -42
  20. package/dist/adapters/anthropic.js.map +0 -1
  21. package/dist/adapters/gemini.d.ts +0 -30
  22. package/dist/adapters/gemini.d.ts.map +0 -1
  23. package/dist/adapters/gemini.js +0 -41
  24. package/dist/adapters/gemini.js.map +0 -1
  25. package/dist/adapters/index.d.ts +0 -11
  26. package/dist/adapters/index.d.ts.map +0 -1
  27. package/dist/adapters/index.js +0 -7
  28. package/dist/adapters/index.js.map +0 -1
  29. package/dist/adapters/manual.d.ts +0 -9
  30. package/dist/adapters/manual.d.ts.map +0 -1
  31. package/dist/adapters/manual.js +0 -16
  32. package/dist/adapters/manual.js.map +0 -1
  33. package/dist/adapters/ollama.d.ts +0 -19
  34. package/dist/adapters/ollama.d.ts.map +0 -1
  35. package/dist/adapters/ollama.js +0 -30
  36. package/dist/adapters/ollama.js.map +0 -1
  37. package/dist/adapters/openai.d.ts +0 -34
  38. package/dist/adapters/openai.d.ts.map +0 -1
  39. package/dist/adapters/openai.js +0 -42
  40. package/dist/adapters/openai.js.map +0 -1
  41. package/dist/adapters/otel.d.ts +0 -25
  42. package/dist/adapters/otel.d.ts.map +0 -1
  43. package/dist/adapters/otel.js +0 -133
  44. package/dist/adapters/otel.js.map +0 -1
  45. package/dist/agent.d.ts +0 -14
  46. package/dist/agent.d.ts.map +0 -1
  47. package/dist/agent.js +0 -54
  48. package/dist/agent.js.map +0 -1
  49. package/dist/client.d.ts +0 -19
  50. package/dist/client.d.ts.map +0 -1
  51. package/dist/client.js +0 -132
  52. package/dist/client.js.map +0 -1
  53. package/dist/context.d.ts +0 -4
  54. package/dist/context.d.ts.map +0 -1
  55. package/dist/context.js +0 -3
  56. package/dist/context.js.map +0 -1
  57. package/dist/delegate.d.ts +0 -3
  58. package/dist/delegate.d.ts.map +0 -1
  59. package/dist/delegate.js +0 -30
  60. package/dist/delegate.js.map +0 -1
  61. package/dist/engine-downloader.d.ts +0 -7
  62. package/dist/engine-downloader.d.ts.map +0 -1
  63. package/dist/engine-downloader.js +0 -136
  64. package/dist/engine-downloader.js.map +0 -1
  65. package/dist/engine-manager.d.ts +0 -26
  66. package/dist/engine-manager.d.ts.map +0 -1
  67. package/dist/engine-manager.js +0 -172
  68. package/dist/engine-manager.js.map +0 -1
  69. package/dist/expect.d.ts +0 -133
  70. package/dist/expect.d.ts.map +0 -1
  71. package/dist/expect.js +0 -330
  72. package/dist/expect.js.map +0 -1
  73. package/dist/index.d.ts +0 -15
  74. package/dist/index.d.ts.map +0 -1
  75. package/dist/index.js +0 -14
  76. package/dist/index.js.map +0 -1
  77. package/dist/proto/codec.d.ts +0 -20
  78. package/dist/proto/codec.d.ts.map +0 -1
  79. package/dist/proto/codec.js +0 -71
  80. package/dist/proto/codec.js.map +0 -1
  81. package/dist/proto/constants.d.ts +0 -21
  82. package/dist/proto/constants.d.ts.map +0 -1
  83. package/dist/proto/constants.js +0 -25
  84. package/dist/proto/constants.js.map +0 -1
  85. package/dist/proto/errors.d.ts +0 -8
  86. package/dist/proto/errors.d.ts.map +0 -1
  87. package/dist/proto/errors.js +0 -13
  88. package/dist/proto/errors.js.map +0 -1
  89. package/dist/proto/index.d.ts +0 -5
  90. package/dist/proto/index.d.ts.map +0 -1
  91. package/dist/proto/index.js +0 -5
  92. package/dist/proto/index.js.map +0 -1
  93. package/dist/proto/types.d.ts +0 -104
  94. package/dist/proto/types.d.ts.map +0 -1
  95. package/dist/proto/types.js +0 -2
  96. package/dist/proto/types.js.map +0 -1
  97. package/dist/result.d.ts +0 -15
  98. package/dist/result.d.ts.map +0 -1
  99. package/dist/result.js +0 -32
  100. package/dist/result.js.map +0 -1
  101. package/dist/tier.d.ts +0 -5
  102. package/dist/tier.d.ts.map +0 -1
  103. package/dist/tier.js +0 -10
  104. package/dist/tier.js.map +0 -1
  105. package/dist/trace-tree.d.ts +0 -20
  106. package/dist/trace-tree.d.ts.map +0 -1
  107. package/dist/trace-tree.js +0 -92
  108. package/dist/trace-tree.js.map +0 -1
  109. package/dist/trace.d.ts +0 -40
  110. package/dist/trace.d.ts.map +0 -1
  111. package/dist/trace.js +0 -85
  112. package/dist/trace.js.map +0 -1
  113. package/dist/version.d.ts +0 -3
  114. package/dist/version.d.ts.map +0 -1
  115. package/dist/version.js +0 -3
  116. package/dist/version.js.map +0 -1
@@ -0,0 +1,2595 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ADVERSARIAL_USER: () => ADVERSARIAL_USER,
34
+ Agent: () => Agent,
35
+ AgentResult: () => AgentResult,
36
+ AlertDispatcher: () => AlertDispatcher,
37
+ AnthropicAdapter: () => AnthropicAdapter,
38
+ AttestClient: () => AttestClient,
39
+ CONFUSED_USER: () => CONFUSED_USER,
40
+ COOPERATIVE_USER: () => COOPERATIVE_USER,
41
+ ContinuousEvalRunner: () => ContinuousEvalRunner,
42
+ ENGINE_VERSION: () => ENGINE_VERSION,
43
+ ERR_ASSERTION_ERROR: () => ERR_ASSERTION_ERROR,
44
+ ERR_ENGINE_ERROR: () => ERR_ENGINE_ERROR,
45
+ ERR_INVALID_TRACE: () => ERR_INVALID_TRACE,
46
+ ERR_PROVIDER_ERROR: () => ERR_PROVIDER_ERROR,
47
+ ERR_SESSION_ERROR: () => ERR_SESSION_ERROR,
48
+ ERR_TIMEOUT: () => ERR_TIMEOUT,
49
+ EngineManager: () => EngineManager,
50
+ EngineTimeoutError: () => EngineTimeoutError,
51
+ ExpectChain: () => ExpectChain,
52
+ FRIENDLY_USER: () => FRIENDLY_USER,
53
+ GeminiAdapter: () => GeminiAdapter,
54
+ LangChainAdapter: () => LangChainAdapter,
55
+ ManualAdapter: () => ManualAdapter,
56
+ OTelAdapter: () => OTelAdapter,
57
+ OllamaAdapter: () => OllamaAdapter,
58
+ OpenAIAdapter: () => OpenAIAdapter,
59
+ PluginRegistry: () => PluginRegistry,
60
+ ProtocolError: () => ProtocolError,
61
+ STATUS_HARD_FAIL: () => STATUS_HARD_FAIL,
62
+ STATUS_PASS: () => STATUS_PASS,
63
+ STATUS_SOFT_FAIL: () => STATUS_SOFT_FAIL,
64
+ STEP_AGENT_CALL: () => STEP_AGENT_CALL,
65
+ STEP_LLM_CALL: () => STEP_LLM_CALL,
66
+ STEP_RETRIEVAL: () => STEP_RETRIEVAL,
67
+ STEP_TOOL_CALL: () => STEP_TOOL_CALL,
68
+ Sampler: () => Sampler,
69
+ TIER_1: () => TIER_1,
70
+ TIER_2: () => TIER_2,
71
+ TIER_3: () => TIER_3,
72
+ TYPE_CONSTRAINT: () => TYPE_CONSTRAINT,
73
+ TYPE_CONTENT: () => TYPE_CONTENT,
74
+ TYPE_EMBEDDING: () => TYPE_EMBEDDING,
75
+ TYPE_LLM_JUDGE: () => TYPE_LLM_JUDGE,
76
+ TYPE_PLUGIN: () => TYPE_PLUGIN,
77
+ TYPE_SCHEMA: () => TYPE_SCHEMA,
78
+ TYPE_TRACE: () => TYPE_TRACE,
79
+ TYPE_TRACE_TREE: () => TYPE_TRACE_TREE,
80
+ TraceBuilder: () => TraceBuilder,
81
+ TraceTree: () => TraceTree,
82
+ VERSION: () => VERSION,
83
+ activeBuilder: () => activeBuilder,
84
+ agent: () => agent,
85
+ agentId: () => agentId,
86
+ assertionId: () => assertionId,
87
+ attestExpect: () => attestExpect,
88
+ config: () => config,
89
+ decodeResponse: () => decodeResponse,
90
+ delegate: () => delegate,
91
+ encodeRequest: () => encodeRequest,
92
+ executePluginAssertion: () => executePluginAssertion,
93
+ extractId: () => extractId,
94
+ extractResult: () => extractResult,
95
+ getSampleRate: () => getSampleRate,
96
+ isSimulationMode: () => isSimulationMode,
97
+ resetConfig: () => resetConfig,
98
+ simulationEvaluateBatch: () => simulationEvaluateBatch,
99
+ tier: () => tier,
100
+ traceId: () => traceId
101
+ });
102
+ module.exports = __toCommonJS(index_exports);
103
+
104
+ // src/proto/constants.ts
105
+ var STEP_LLM_CALL = "llm_call";
106
+ var STEP_TOOL_CALL = "tool_call";
107
+ var STEP_RETRIEVAL = "retrieval";
108
+ var STEP_AGENT_CALL = "agent_call";
109
+ var STATUS_PASS = "pass";
110
+ var STATUS_SOFT_FAIL = "soft_fail";
111
+ var STATUS_HARD_FAIL = "hard_fail";
112
+ var TYPE_SCHEMA = "schema";
113
+ var TYPE_CONSTRAINT = "constraint";
114
+ var TYPE_TRACE = "trace";
115
+ var TYPE_CONTENT = "content";
116
+ var TYPE_EMBEDDING = "embedding";
117
+ var TYPE_LLM_JUDGE = "llm_judge";
118
+ var TYPE_TRACE_TREE = "trace_tree";
119
+ var TYPE_PLUGIN = "plugin";
120
+ var ERR_INVALID_TRACE = 1001;
121
+ var ERR_ASSERTION_ERROR = 1002;
122
+ var ERR_PROVIDER_ERROR = 2001;
123
+ var ERR_ENGINE_ERROR = 3001;
124
+ var ERR_TIMEOUT = 3002;
125
+ var ERR_SESSION_ERROR = 3003;
126
+
127
+ // src/proto/types.ts
128
+ function traceId(id) {
129
+ return id;
130
+ }
131
+ function assertionId(id) {
132
+ return id;
133
+ }
134
+ function agentId(id) {
135
+ return id;
136
+ }
137
+
138
+ // src/proto/errors.ts
139
+ var ProtocolError = class extends Error {
140
+ code;
141
+ errorMessage;
142
+ data;
143
+ constructor(code, message, data) {
144
+ super(message);
145
+ this.name = "ProtocolError";
146
+ this.code = code;
147
+ this.errorMessage = message;
148
+ this.data = data;
149
+ }
150
+ };
151
+ var EngineTimeoutError = class extends Error {
152
+ method;
153
+ timeoutMs;
154
+ constructor(method, timeoutMs) {
155
+ super(
156
+ `Engine request '${method}' timed out after ${timeoutMs}ms. Set ATTEST_ENGINE_TIMEOUT to increase the limit.`
157
+ );
158
+ this.name = "EngineTimeoutError";
159
+ this.method = method;
160
+ this.timeoutMs = timeoutMs;
161
+ }
162
+ };
163
+
164
+ // src/proto/codec.ts
165
+ function encodeRequest(id, method, params) {
166
+ const msg = {
167
+ jsonrpc: "2.0",
168
+ id,
169
+ method,
170
+ params
171
+ };
172
+ return JSON.stringify(msg) + "\n";
173
+ }
174
+ function decodeResponse(line) {
175
+ const text = line.trim();
176
+ if (!text) {
177
+ throw new Error("empty response line");
178
+ }
179
+ let data;
180
+ try {
181
+ data = JSON.parse(text);
182
+ } catch (e) {
183
+ throw new Error(`malformed JSON response: ${e instanceof Error ? e.message : String(e)}`);
184
+ }
185
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
186
+ throw new Error(`expected JSON object, got ${Array.isArray(data) ? "array" : typeof data}`);
187
+ }
188
+ const obj = data;
189
+ if (obj.jsonrpc !== "2.0") {
190
+ throw new Error(`invalid jsonrpc version: ${String(obj.jsonrpc)}`);
191
+ }
192
+ if (obj.error != null) {
193
+ const err = obj.error;
194
+ let errorData;
195
+ if (err.data != null) {
196
+ const raw = err.data;
197
+ errorData = {
198
+ error_type: raw.error_type ?? "",
199
+ retryable: raw.retryable ?? false,
200
+ detail: raw.detail ?? ""
201
+ };
202
+ }
203
+ throw new ProtocolError(
204
+ err.code ?? -1,
205
+ err.message ?? "unknown error",
206
+ errorData
207
+ );
208
+ }
209
+ return obj;
210
+ }
211
+ function extractResult(response) {
212
+ if (response.result === void 0) {
213
+ throw new Error("response missing 'result' field");
214
+ }
215
+ return response.result;
216
+ }
217
+ function extractId(response) {
218
+ if (response.id === void 0) {
219
+ throw new Error("response missing 'id' field");
220
+ }
221
+ return Number(response.id);
222
+ }
223
+
224
+ // src/version.ts
225
+ var VERSION = "0.6.0";
226
+ var ENGINE_VERSION = "0.6.0";
227
+
228
+ // src/engine-manager.ts
229
+ var import_node_child_process = require("child_process");
230
+ var import_node_readline = require("readline");
231
+ var import_node_fs = require("fs");
232
+ var import_node_path = require("path");
233
+ var import_node_url = require("url");
234
+
235
+ // src/engine-downloader.ts
236
+ var https = __toESM(require("https"), 1);
237
+ var http = __toESM(require("http"), 1);
238
+ var fs = __toESM(require("fs"), 1);
239
+ var path = __toESM(require("path"), 1);
240
+ var os = __toESM(require("os"), 1);
241
+ var crypto = __toESM(require("crypto"), 1);
242
+ var RELEASE_BASE = `https://github.com/attest-framework/attest/releases/download/v${ENGINE_VERSION}`;
243
+ var CHECKSUMS_URL = `${RELEASE_BASE}/checksums-sha256.txt`;
244
+ var MAX_REDIRECTS = 5;
245
+ function attestBinDir() {
246
+ const dir = path.join(os.homedir(), ".attest", "bin");
247
+ fs.mkdirSync(dir, { recursive: true });
248
+ return dir;
249
+ }
250
+ function platformKey() {
251
+ const platform2 = os.platform();
252
+ const arch2 = os.arch();
253
+ const mapping = {
254
+ darwin: { arm64: "darwin-arm64", x64: "darwin-amd64" },
255
+ linux: { arm64: "linux-arm64", x64: "linux-amd64" },
256
+ win32: { x64: "windows-amd64", arm64: "windows-arm64" }
257
+ };
258
+ const osMap = mapping[platform2];
259
+ if (!osMap) {
260
+ throw new Error(
261
+ `Unsupported platform: ${platform2}. Supported: darwin, linux, win32.`
262
+ );
263
+ }
264
+ const key = osMap[arch2];
265
+ if (!key) {
266
+ throw new Error(
267
+ `Unsupported architecture '${arch2}' on platform '${platform2}'. Supported architectures: ${Object.keys(osMap).join(", ")}.`
268
+ );
269
+ }
270
+ return key;
271
+ }
272
+ function binaryFilename() {
273
+ return os.platform() === "win32" ? "attest-engine.exe" : "attest-engine";
274
+ }
275
+ function cachedEnginePath() {
276
+ const dir = path.join(os.homedir(), ".attest", "bin");
277
+ const binPath = path.join(dir, binaryFilename());
278
+ const versionFile = path.join(dir, ".engine-version");
279
+ if (!fs.existsSync(binPath)) {
280
+ return null;
281
+ }
282
+ if (!fs.existsSync(versionFile)) {
283
+ return null;
284
+ }
285
+ const cachedVersion = fs.readFileSync(versionFile, "utf-8").trim();
286
+ if (cachedVersion !== ENGINE_VERSION) {
287
+ return null;
288
+ }
289
+ return binPath;
290
+ }
291
+ function parseChecksums(text) {
292
+ const checksums = /* @__PURE__ */ new Map();
293
+ for (const line of text.split("\n")) {
294
+ const trimmed = line.trim();
295
+ if (!trimmed) continue;
296
+ const match = trimmed.match(/^([a-f0-9]{64})\s+(.+)$/);
297
+ if (match) {
298
+ checksums.set(match[2], match[1]);
299
+ }
300
+ }
301
+ return checksums;
302
+ }
303
+ function httpsGet(url, redirectCount = 0) {
304
+ if (redirectCount > MAX_REDIRECTS) {
305
+ return Promise.reject(
306
+ new Error(`Too many redirects (>${MAX_REDIRECTS}) following ${url}`)
307
+ );
308
+ }
309
+ return new Promise((resolve2, reject) => {
310
+ const client = url.startsWith("https:") ? https : http;
311
+ client.get(url, (res) => {
312
+ const status = res.statusCode ?? 0;
313
+ if (status === 301 || status === 302) {
314
+ const location = res.headers.location;
315
+ if (!location) {
316
+ reject(new Error(`Redirect ${status} without Location header from ${url}`));
317
+ return;
318
+ }
319
+ res.resume();
320
+ resolve2(httpsGet(location, redirectCount + 1));
321
+ return;
322
+ }
323
+ if (status < 200 || status >= 300) {
324
+ res.resume();
325
+ reject(new Error(`HTTP ${status} fetching ${url}`));
326
+ return;
327
+ }
328
+ const chunks = [];
329
+ res.on("data", (chunk) => chunks.push(chunk));
330
+ res.on("end", () => resolve2(Buffer.concat(chunks)));
331
+ res.on("error", reject);
332
+ }).on("error", reject);
333
+ });
334
+ }
335
+ async function downloadEngine() {
336
+ const key = platformKey();
337
+ const filename = `attest-engine-${key}`;
338
+ const binaryUrl = `${RELEASE_BASE}/${filename}`;
339
+ process.stderr.write(`[attest] Downloading engine v${ENGINE_VERSION} for ${key}...
340
+ `);
341
+ process.stderr.write(`[attest] Fetching checksums...
342
+ `);
343
+ const checksumsText = (await httpsGet(CHECKSUMS_URL)).toString("utf-8");
344
+ const checksums = parseChecksums(checksumsText);
345
+ const expectedHash = checksums.get(filename);
346
+ if (!expectedHash) {
347
+ throw new Error(
348
+ `No checksum found for '${filename}' in checksums-sha256.txt. Available files: ${[...checksums.keys()].join(", ")}`
349
+ );
350
+ }
351
+ process.stderr.write(`[attest] Downloading ${binaryUrl}...
352
+ `);
353
+ const data = await httpsGet(binaryUrl);
354
+ const actualHash = crypto.createHash("sha256").update(data).digest("hex");
355
+ if (actualHash !== expectedHash) {
356
+ throw new Error(
357
+ `SHA256 mismatch for ${filename}. Expected: ${expectedHash}, got: ${actualHash}. The download may be corrupted. Delete ~/.attest/bin/ and retry.`
358
+ );
359
+ }
360
+ process.stderr.write(`[attest] Checksum verified.
361
+ `);
362
+ const dir = attestBinDir();
363
+ const destFilename = binaryFilename();
364
+ const destPath = path.join(dir, destFilename);
365
+ const tmpPath = path.join(dir, `.${destFilename}.tmp.${process.pid}`);
366
+ fs.writeFileSync(tmpPath, data);
367
+ fs.chmodSync(tmpPath, 493);
368
+ fs.renameSync(tmpPath, destPath);
369
+ fs.writeFileSync(path.join(dir, ".engine-version"), ENGINE_VERSION);
370
+ process.stderr.write(`[attest] Engine installed at ${destPath}
371
+ `);
372
+ return destPath;
373
+ }
374
+
375
+ // src/engine-manager.ts
376
+ var import_meta = {};
377
+ var ENGINE_BINARY_NAME = "attest-engine";
378
+ async function findEngineBinary() {
379
+ const envPath = process.env["ATTEST_ENGINE_PATH"];
380
+ if (envPath) {
381
+ if (!(0, import_node_fs.existsSync)(envPath)) {
382
+ throw new Error(
383
+ `ATTEST_ENGINE_PATH is set to '${envPath}' but the file does not exist.`
384
+ );
385
+ }
386
+ return envPath;
387
+ }
388
+ try {
389
+ const cmd = process.platform === "win32" ? "where" : "which";
390
+ const found = (0, import_node_child_process.execSync)(`${cmd} ${ENGINE_BINARY_NAME}`, { encoding: "utf-8" }).trim();
391
+ if (found) return found;
392
+ } catch {
393
+ }
394
+ const cached = cachedEnginePath();
395
+ if (cached) return cached;
396
+ const candidates = [
397
+ (0, import_node_path.join)((0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "..", "..", "..", "..", "bin", ENGINE_BINARY_NAME),
398
+ (0, import_node_path.join)(process.cwd(), "bin", ENGINE_BINARY_NAME)
399
+ ];
400
+ for (const candidate of candidates) {
401
+ const resolved = (0, import_node_path.resolve)(candidate);
402
+ try {
403
+ (0, import_node_fs.accessSync)(resolved, import_node_fs.constants.X_OK);
404
+ if ((0, import_node_fs.existsSync)(resolved)) return resolved;
405
+ } catch {
406
+ }
407
+ }
408
+ const noDownload = process.env["ATTEST_ENGINE_NO_DOWNLOAD"];
409
+ if (noDownload === "1" || noDownload === "true" || noDownload === "yes") {
410
+ throw new Error(
411
+ `Cannot find '${ENGINE_BINARY_NAME}' binary and auto-download is disabled (ATTEST_ENGINE_NO_DOWNLOAD is set). Install the engine manually:
412
+ 1. Download from https://github.com/attest-framework/attest/releases
413
+ 2. Place the binary on your PATH or set ATTEST_ENGINE_PATH`
414
+ );
415
+ }
416
+ return downloadEngine();
417
+ }
418
+ var EngineManager = class {
419
+ enginePath;
420
+ logLevel;
421
+ process = null;
422
+ reader = null;
423
+ initialized = false;
424
+ requestId = 0;
425
+ initResult = null;
426
+ constructor(options) {
427
+ this.enginePath = options?.enginePath ?? "";
428
+ this.logLevel = options?.logLevel ?? "warn";
429
+ }
430
+ async start() {
431
+ if (!this.enginePath) {
432
+ this.enginePath = await findEngineBinary();
433
+ }
434
+ this.process = (0, import_node_child_process.spawn)(this.enginePath, [`--log-level=${this.logLevel}`], {
435
+ stdio: ["pipe", "pipe", "pipe"]
436
+ });
437
+ this.reader = (0, import_node_readline.createInterface)({ input: this.process.stdout });
438
+ const result = await this.sendRequestInternal("initialize", {
439
+ sdk_name: "attest-typescript",
440
+ sdk_version: VERSION,
441
+ protocol_version: 1,
442
+ required_capabilities: ["layers_1_4"],
443
+ preferred_encoding: "json"
444
+ });
445
+ this.initResult = result;
446
+ if (!this.initResult.compatible) {
447
+ throw new Error(
448
+ `Engine incompatible. Missing capabilities: ${JSON.stringify(this.initResult.missing)}`
449
+ );
450
+ }
451
+ this.initialized = true;
452
+ return this.initResult;
453
+ }
454
+ async stop() {
455
+ if (this.process === null) return;
456
+ if (this.initialized) {
457
+ try {
458
+ await this.sendRequestInternal("shutdown", {});
459
+ } catch {
460
+ }
461
+ }
462
+ if (this.reader) {
463
+ this.reader.close();
464
+ this.reader = null;
465
+ }
466
+ if (this.process.exitCode === null) {
467
+ this.process.kill("SIGTERM");
468
+ await new Promise((res) => {
469
+ const timer = setTimeout(() => {
470
+ this.process?.kill("SIGKILL");
471
+ }, 5e3);
472
+ this.process.on("exit", () => {
473
+ clearTimeout(timer);
474
+ res();
475
+ });
476
+ });
477
+ }
478
+ this.initialized = false;
479
+ this.process = null;
480
+ }
481
+ async sendRequest(method, params) {
482
+ if (!this.initialized && method !== "initialize") {
483
+ throw new Error("Engine not initialized. Call start() first.");
484
+ }
485
+ return this.sendRequestInternal(method, params);
486
+ }
487
+ async sendRequestInternal(method, params) {
488
+ if (this.process === null || this.process.stdin === null || this.reader === null) {
489
+ throw new Error("Engine process not started.");
490
+ }
491
+ this.requestId += 1;
492
+ const requestStr = encodeRequest(this.requestId, method, params);
493
+ return new Promise((resolve2, reject) => {
494
+ const onLine = (line) => {
495
+ try {
496
+ const response = decodeResponse(line);
497
+ resolve2(extractResult(response));
498
+ } catch (err) {
499
+ reject(err);
500
+ }
501
+ };
502
+ this.reader.once("line", onLine);
503
+ const ok = this.process.stdin.write(requestStr, (err) => {
504
+ if (err) {
505
+ this.reader.removeListener("line", onLine);
506
+ reject(err);
507
+ }
508
+ });
509
+ if (!ok) {
510
+ this.process.stdin.once("drain", () => {
511
+ });
512
+ }
513
+ });
514
+ }
515
+ get isRunning() {
516
+ return this.process !== null && this.process.exitCode === null;
517
+ }
518
+ /** Expose process for AttestClient reader loop access */
519
+ get childProcess() {
520
+ return this.process;
521
+ }
522
+ /** Expose readline interface for AttestClient reader loop */
523
+ get readlineInterface() {
524
+ return this.reader;
525
+ }
526
+ };
527
+
528
+ // src/config.ts
529
+ var _simulationMode = false;
530
+ var _sampleRate = 0;
531
+ function config(options) {
532
+ if (options.simulation !== void 0) {
533
+ _simulationMode = options.simulation;
534
+ }
535
+ if (options.sampleRate !== void 0) {
536
+ if (options.sampleRate < 0 || options.sampleRate > 1) {
537
+ throw new RangeError(
538
+ `sampleRate must be between 0.0 and 1.0, got ${options.sampleRate}`
539
+ );
540
+ }
541
+ _sampleRate = options.sampleRate;
542
+ }
543
+ }
544
+ function isSimulationMode() {
545
+ return _simulationMode || process.env["ATTEST_SIMULATION"] === "1" || process.env["ATTEST_SIMULATION"] === "true" || process.env["ATTEST_SIMULATION"] === "yes";
546
+ }
547
+ function getSampleRate() {
548
+ if (_sampleRate > 0) return _sampleRate;
549
+ const env = process.env["ATTEST_SAMPLE_RATE"];
550
+ if (env !== void 0 && env !== "") {
551
+ const parsed = Number(env);
552
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
553
+ throw new RangeError(
554
+ `ATTEST_SAMPLE_RATE must be between 0.0 and 1.0, got '${env}'`
555
+ );
556
+ }
557
+ return parsed;
558
+ }
559
+ return 0;
560
+ }
561
+ function resetConfig() {
562
+ _simulationMode = false;
563
+ _sampleRate = 0;
564
+ }
565
+
566
+ // src/simulation.ts
567
+ function simulationEvaluateBatch(assertions) {
568
+ const results = assertions.map((assertion) => ({
569
+ assertion_id: assertion.assertion_id,
570
+ status: "pass",
571
+ score: 1,
572
+ explanation: `[simulation] assertion '${assertion.assertion_id}' passed (type: ${assertion.type})`,
573
+ cost: 0,
574
+ duration_ms: 0
575
+ }));
576
+ return {
577
+ results,
578
+ total_cost: 0,
579
+ total_duration_ms: 0
580
+ };
581
+ }
582
+
583
+ // src/client.ts
584
+ var DEFAULT_TIMEOUT_MS = 3e4;
585
+ function resolveTimeoutMs(override) {
586
+ if (override !== void 0) return override;
587
+ const env = process.env["ATTEST_ENGINE_TIMEOUT"];
588
+ if (env !== void 0 && env !== "") {
589
+ const parsed = Number(env);
590
+ if (!Number.isFinite(parsed) || parsed <= 0) {
591
+ throw new Error(
592
+ `ATTEST_ENGINE_TIMEOUT must be a positive number of milliseconds, got: '${env}'`
593
+ );
594
+ }
595
+ return parsed;
596
+ }
597
+ return DEFAULT_TIMEOUT_MS;
598
+ }
599
+ function makeTimeoutPromise(method, timeoutMs) {
600
+ return new Promise((_, reject) => {
601
+ setTimeout(() => {
602
+ reject(new EngineTimeoutError(method, timeoutMs));
603
+ }, timeoutMs);
604
+ });
605
+ }
606
+ var AttestClient = class {
607
+ engine;
608
+ requestId = 0;
609
+ pending = /* @__PURE__ */ new Map();
610
+ writeChain = Promise.resolve();
611
+ readerActive = false;
612
+ lineHandler = null;
613
+ constructor(engine) {
614
+ this.engine = engine;
615
+ }
616
+ startReader() {
617
+ if (this.readerActive) return;
618
+ const rl = this.engine.readlineInterface;
619
+ if (rl === null) {
620
+ throw new Error("Engine readline interface not available. Call engine.start() first.");
621
+ }
622
+ this.readerActive = true;
623
+ this.lineHandler = (line) => this.handleLine(line);
624
+ rl.on("line", this.lineHandler);
625
+ rl.on("close", () => {
626
+ this.failAll(new Error("Engine closed stdout."));
627
+ this.readerActive = false;
628
+ });
629
+ }
630
+ stopReader() {
631
+ if (!this.readerActive) return;
632
+ const rl = this.engine.readlineInterface;
633
+ if (rl !== null && this.lineHandler !== null) {
634
+ rl.removeListener("line", this.lineHandler);
635
+ }
636
+ this.lineHandler = null;
637
+ this.readerActive = false;
638
+ }
639
+ handleLine(line) {
640
+ let reqId;
641
+ try {
642
+ const response = decodeResponse(line);
643
+ reqId = extractId(response);
644
+ const pending = this.pending.get(reqId);
645
+ if (pending === void 0) return;
646
+ this.pending.delete(reqId);
647
+ try {
648
+ const result = extractResult(response);
649
+ pending.resolve(result);
650
+ } catch (err) {
651
+ pending.reject(err);
652
+ }
653
+ } catch (err) {
654
+ if (err instanceof ProtocolError) {
655
+ try {
656
+ const raw = JSON.parse(line.trim());
657
+ reqId = Number(raw.id ?? -1);
658
+ } catch {
659
+ reqId = -1;
660
+ }
661
+ const pending = this.pending.get(reqId);
662
+ if (pending !== void 0) {
663
+ this.pending.delete(reqId);
664
+ pending.reject(err);
665
+ }
666
+ }
667
+ }
668
+ }
669
+ failAll(err) {
670
+ for (const pending of this.pending.values()) {
671
+ pending.reject(err);
672
+ }
673
+ this.pending.clear();
674
+ }
675
+ async sendRequest(method, params, timeoutMs) {
676
+ const resolvedTimeout = resolveTimeoutMs(timeoutMs);
677
+ if (!this.readerActive) {
678
+ return Promise.race([
679
+ this.engine.sendRequest(method, params),
680
+ makeTimeoutPromise(method, resolvedTimeout)
681
+ ]);
682
+ }
683
+ const cp = this.engine.childProcess;
684
+ if (cp === null || cp.stdin === null) {
685
+ throw new Error("Engine process not started.");
686
+ }
687
+ let capturedReqId;
688
+ const requestPromise = new Promise((resolve2, reject) => {
689
+ this.writeChain = this.writeChain.then(() => {
690
+ this.requestId += 1;
691
+ const reqId = this.requestId;
692
+ capturedReqId = reqId;
693
+ this.pending.set(reqId, { resolve: resolve2, reject });
694
+ const requestStr = encodeRequest(reqId, method, params);
695
+ cp.stdin.write(requestStr, (err) => {
696
+ if (err) {
697
+ this.pending.delete(reqId);
698
+ reject(err);
699
+ }
700
+ });
701
+ });
702
+ });
703
+ const timeoutPromise = makeTimeoutPromise(method, resolvedTimeout);
704
+ return Promise.race([requestPromise, timeoutPromise]).catch((err) => {
705
+ if (err instanceof EngineTimeoutError && capturedReqId !== void 0) {
706
+ this.pending.delete(capturedReqId);
707
+ }
708
+ throw err;
709
+ });
710
+ }
711
+ async evaluateBatch(trace, assertions, options) {
712
+ if (isSimulationMode()) {
713
+ return simulationEvaluateBatch(assertions);
714
+ }
715
+ const params = {
716
+ trace,
717
+ assertions: [...assertions]
718
+ };
719
+ const raw = await this.sendRequest(
720
+ "evaluate_batch",
721
+ params,
722
+ options?.timeout
723
+ );
724
+ return raw;
725
+ }
726
+ async submitPluginResult(traceId2, pluginName, assertionId2, status, score, explanation, options) {
727
+ const result = {
728
+ assertion_id: assertionId2,
729
+ status,
730
+ score,
731
+ explanation,
732
+ cost: 0,
733
+ duration_ms: 0
734
+ };
735
+ const params = {
736
+ trace_id: traceId2,
737
+ plugin_name: pluginName,
738
+ assertion_id: assertionId2,
739
+ result
740
+ };
741
+ const raw = await this.sendRequest(
742
+ "submit_plugin_result",
743
+ params,
744
+ options?.timeout
745
+ );
746
+ return Boolean(raw?.accepted ?? false);
747
+ }
748
+ async queryDrift(assertionId2, windowSize = 50, options) {
749
+ if (isSimulationMode()) {
750
+ return {
751
+ assertion_id: assertionId2,
752
+ mean: 1,
753
+ stddev: 0,
754
+ count: 0,
755
+ latest_score: 1,
756
+ deviation: 0,
757
+ status: "no_data"
758
+ };
759
+ }
760
+ const raw = await this.sendRequest(
761
+ "query_drift",
762
+ { assertion_id: assertionId2, window_size: windowSize },
763
+ options?.timeout
764
+ );
765
+ return raw.report;
766
+ }
767
+ async generateUserMessage(persona, conversationHistory, faultConfig, options) {
768
+ if (isSimulationMode()) {
769
+ return `[simulation] Hello from ${persona.name}`;
770
+ }
771
+ const params = {
772
+ persona,
773
+ conversation_history: conversationHistory
774
+ };
775
+ if (faultConfig !== void 0) {
776
+ params.fault_config = faultConfig;
777
+ }
778
+ const raw = await this.sendRequest(
779
+ "generate_user_message",
780
+ params,
781
+ options?.timeout
782
+ );
783
+ return String(raw.message);
784
+ }
785
+ };
786
+
787
+ // src/trace.ts
788
+ var import_node_crypto = require("crypto");
789
+ var TraceBuilder = class {
790
+ traceId;
791
+ agentId;
792
+ input;
793
+ steps = [];
794
+ output;
795
+ metadata;
796
+ parentTraceId;
797
+ constructor(agentId2) {
798
+ this.traceId = `trc_${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12)}`;
799
+ this.agentId = agentId2;
800
+ }
801
+ setTraceId(traceId2) {
802
+ this.traceId = traceId2;
803
+ return this;
804
+ }
805
+ setInput(input) {
806
+ this.input = input;
807
+ return this;
808
+ }
809
+ addLlmCall(name, options) {
810
+ this.steps.push({
811
+ type: STEP_LLM_CALL,
812
+ name,
813
+ args: options?.args,
814
+ result: options?.result,
815
+ metadata: options?.metadata
816
+ });
817
+ return this;
818
+ }
819
+ addToolCall(name, options) {
820
+ this.steps.push({
821
+ type: STEP_TOOL_CALL,
822
+ name,
823
+ args: options?.args,
824
+ result: options?.result,
825
+ metadata: options?.metadata
826
+ });
827
+ return this;
828
+ }
829
+ addRetrieval(name, options) {
830
+ this.steps.push({
831
+ type: STEP_RETRIEVAL,
832
+ name,
833
+ args: options?.args,
834
+ result: options?.result,
835
+ metadata: options?.metadata
836
+ });
837
+ return this;
838
+ }
839
+ addStep(step) {
840
+ this.steps.push(step);
841
+ return this;
842
+ }
843
+ setOutput(output) {
844
+ this.output = output;
845
+ return this;
846
+ }
847
+ setMetadata(metadata) {
848
+ this.metadata = metadata;
849
+ return this;
850
+ }
851
+ getTraceId() {
852
+ return this.traceId;
853
+ }
854
+ setParentTraceId(parentId) {
855
+ this.parentTraceId = parentId;
856
+ return this;
857
+ }
858
+ build() {
859
+ if (this.output === void 0) {
860
+ throw new Error("Trace output is required. Call setOutput() before build().");
861
+ }
862
+ return {
863
+ trace_id: this.traceId,
864
+ output: this.output,
865
+ schema_version: 1,
866
+ agent_id: this.agentId,
867
+ input: this.input,
868
+ steps: [...this.steps],
869
+ metadata: this.metadata,
870
+ parent_trace_id: this.parentTraceId
871
+ };
872
+ }
873
+ };
874
+
875
+ // src/result.ts
876
+ var AgentResult = class {
877
+ trace;
878
+ assertionResults;
879
+ totalCost;
880
+ totalDurationMs;
881
+ constructor(trace, assertionResults = [], totalCost = 0, totalDurationMs = 0) {
882
+ this.trace = trace;
883
+ this.assertionResults = assertionResults;
884
+ this.totalCost = totalCost;
885
+ this.totalDurationMs = totalDurationMs;
886
+ }
887
+ get passed() {
888
+ return this.assertionResults.every((r) => r.status === STATUS_PASS);
889
+ }
890
+ get failedAssertions() {
891
+ return this.assertionResults.filter((r) => r.status !== STATUS_PASS);
892
+ }
893
+ get hardFailures() {
894
+ return this.assertionResults.filter((r) => r.status === STATUS_HARD_FAIL);
895
+ }
896
+ get softFailures() {
897
+ return this.assertionResults.filter((r) => r.status === STATUS_SOFT_FAIL);
898
+ }
899
+ get passCount() {
900
+ return this.assertionResults.filter((r) => r.status === STATUS_PASS).length;
901
+ }
902
+ get failCount() {
903
+ return this.assertionResults.length - this.passCount;
904
+ }
905
+ };
906
+
907
+ // src/trace-tree.ts
908
+ var TraceTree = class {
909
+ root;
910
+ constructor(root) {
911
+ this.root = root;
912
+ }
913
+ get agents() {
914
+ const result = [];
915
+ this.collectAgents(this.root, result);
916
+ return result;
917
+ }
918
+ collectAgents(trace, acc) {
919
+ if (trace.agent_id != null) {
920
+ acc.push(trace.agent_id);
921
+ }
922
+ for (const step of trace.steps) {
923
+ if (step.type === STEP_AGENT_CALL && step.sub_trace != null) {
924
+ this.collectAgents(step.sub_trace, acc);
925
+ }
926
+ }
927
+ }
928
+ findAgent(agentId2) {
929
+ return this.findAgentIn(this.root, agentId2);
930
+ }
931
+ findAgentIn(trace, agentId2) {
932
+ if (trace.agent_id === agentId2) return trace;
933
+ for (const step of trace.steps) {
934
+ if (step.type === STEP_AGENT_CALL && step.sub_trace != null) {
935
+ const found = this.findAgentIn(step.sub_trace, agentId2);
936
+ if (found !== void 0) return found;
937
+ }
938
+ }
939
+ return void 0;
940
+ }
941
+ get depth() {
942
+ return this.computeDepth(this.root);
943
+ }
944
+ computeDepth(trace) {
945
+ let maxChild = -1;
946
+ for (const step of trace.steps) {
947
+ if (step.type === STEP_AGENT_CALL && step.sub_trace != null) {
948
+ const childDepth = this.computeDepth(step.sub_trace);
949
+ if (childDepth > maxChild) maxChild = childDepth;
950
+ }
951
+ }
952
+ return maxChild >= 0 ? maxChild + 1 : 0;
953
+ }
954
+ flatten() {
955
+ const result = [];
956
+ this.flattenInto(this.root, result);
957
+ return result;
958
+ }
959
+ flattenInto(trace, acc) {
960
+ acc.push(trace);
961
+ for (const step of trace.steps) {
962
+ if (step.type === STEP_AGENT_CALL && step.sub_trace != null) {
963
+ this.flattenInto(step.sub_trace, acc);
964
+ }
965
+ }
966
+ }
967
+ get delegations() {
968
+ const result = [];
969
+ this.collectDelegations(this.root, result);
970
+ return result;
971
+ }
972
+ collectDelegations(trace, acc) {
973
+ const parentId = trace.agent_id ?? "";
974
+ for (const step of trace.steps) {
975
+ if (step.type === STEP_AGENT_CALL && step.sub_trace != null) {
976
+ const childId = step.sub_trace.agent_id ?? "";
977
+ acc.push([parentId, childId]);
978
+ this.collectDelegations(step.sub_trace, acc);
979
+ }
980
+ }
981
+ }
982
+ allToolCalls() {
983
+ return this.flatten().flatMap(
984
+ (t) => t.steps.filter((step) => step.type === STEP_TOOL_CALL)
985
+ );
986
+ }
987
+ get aggregateTokens() {
988
+ return this.flatten().reduce(
989
+ (sum, t) => sum + (t.metadata?.total_tokens ?? 0),
990
+ 0
991
+ );
992
+ }
993
+ get aggregateCost() {
994
+ return this.flatten().reduce(
995
+ (sum, t) => sum + (t.metadata?.cost_usd ?? 0),
996
+ 0
997
+ );
998
+ }
999
+ get aggregateLatency() {
1000
+ return this.flatten().reduce(
1001
+ (sum, t) => sum + (t.metadata?.latency_ms ?? 0),
1002
+ 0
1003
+ );
1004
+ }
1005
+ };
1006
+
1007
+ // src/expect.ts
1008
+ var import_node_crypto2 = require("crypto");
1009
+ var ExpectChain = class {
1010
+ _result;
1011
+ _assertions = [];
1012
+ constructor(result) {
1013
+ this._result = result;
1014
+ }
1015
+ get assertions() {
1016
+ return [...this._assertions];
1017
+ }
1018
+ get trace() {
1019
+ return this._result.trace;
1020
+ }
1021
+ add(assertionType, spec) {
1022
+ this._assertions.push({
1023
+ assertion_id: `assert_${(0, import_node_crypto2.randomUUID)().replace(/-/g, "").slice(0, 8)}`,
1024
+ type: assertionType,
1025
+ spec
1026
+ });
1027
+ return this;
1028
+ }
1029
+ // -- Layer 1: Schema --
1030
+ /**
1031
+ * Assert that `output.structured` conforms to the given JSON Schema.
1032
+ *
1033
+ * @param schema - A JSON Schema object to validate against.
1034
+ * @returns The chain for fluent composition.
1035
+ *
1036
+ * @example
1037
+ * ```ts
1038
+ * expect(result).outputMatchesSchema({ type: "object", required: ["answer"] });
1039
+ * ```
1040
+ */
1041
+ outputMatchesSchema(schema) {
1042
+ return this.add(TYPE_SCHEMA, { target: "output.structured", schema });
1043
+ }
1044
+ /**
1045
+ * Assert that a specific output field conforms to the given JSON Schema.
1046
+ *
1047
+ * @param field - Dot-delimited path under `output` (e.g. `"structured.items"`).
1048
+ * @param schema - A JSON Schema object to validate against.
1049
+ * @returns The chain for fluent composition.
1050
+ *
1051
+ * @example
1052
+ * ```ts
1053
+ * expect(result).outputFieldMatchesSchema("structured.items", { type: "array" });
1054
+ * ```
1055
+ */
1056
+ outputFieldMatchesSchema(field, schema) {
1057
+ return this.add(TYPE_SCHEMA, { target: `output.${field}`, schema });
1058
+ }
1059
+ /**
1060
+ * Assert that a named tool's arguments conform to the given JSON Schema.
1061
+ *
1062
+ * @param toolName - The tool name to match in `steps`.
1063
+ * @param schema - A JSON Schema object to validate against.
1064
+ * @returns The chain for fluent composition.
1065
+ *
1066
+ * @example
1067
+ * ```ts
1068
+ * expect(result).toolArgsMatchSchema("search", { type: "object", required: ["query"] });
1069
+ * ```
1070
+ */
1071
+ toolArgsMatchSchema(toolName, schema) {
1072
+ return this.add(TYPE_SCHEMA, {
1073
+ target: `steps[?name=='${toolName}'].args`,
1074
+ schema
1075
+ });
1076
+ }
1077
+ /**
1078
+ * Assert that a named tool's result conforms to the given JSON Schema.
1079
+ *
1080
+ * @param toolName - The tool name to match in `steps`.
1081
+ * @param schema - A JSON Schema object to validate against.
1082
+ * @returns The chain for fluent composition.
1083
+ *
1084
+ * @example
1085
+ * ```ts
1086
+ * expect(result).toolResultMatchesSchema("search", { type: "object" });
1087
+ * ```
1088
+ */
1089
+ toolResultMatchesSchema(toolName, schema) {
1090
+ return this.add(TYPE_SCHEMA, {
1091
+ target: `steps[?name=='${toolName}'].result`,
1092
+ schema
1093
+ });
1094
+ }
1095
+ // -- Layer 2: Constraints --
1096
+ /**
1097
+ * Assert total cost (in USD) is at or below `maxCost`.
1098
+ *
1099
+ * @param maxCost - Maximum allowed cost in USD.
1100
+ * @param opts.soft - When `true` the failure is non-blocking.
1101
+ * @returns The chain for fluent composition.
1102
+ *
1103
+ * @example
1104
+ * ```ts
1105
+ * expect(result).costUnder(0.05);
1106
+ * expect(result).costUnder(0.05, { soft: true });
1107
+ * ```
1108
+ */
1109
+ costUnder(maxCost, opts) {
1110
+ return this.add(TYPE_CONSTRAINT, {
1111
+ field: "metadata.cost_usd",
1112
+ operator: "lte",
1113
+ value: maxCost,
1114
+ soft: opts?.soft ?? false
1115
+ });
1116
+ }
1117
+ /**
1118
+ * Assert total latency is at or below `maxMs` milliseconds.
1119
+ *
1120
+ * @param maxMs - Maximum allowed latency in milliseconds.
1121
+ * @param opts.soft - When `true` the failure is non-blocking.
1122
+ * @returns The chain for fluent composition.
1123
+ *
1124
+ * @example
1125
+ * ```ts
1126
+ * expect(result).latencyUnder(2000);
1127
+ * ```
1128
+ */
1129
+ latencyUnder(maxMs, opts) {
1130
+ return this.add(TYPE_CONSTRAINT, {
1131
+ field: "metadata.latency_ms",
1132
+ operator: "lte",
1133
+ value: maxMs,
1134
+ soft: opts?.soft ?? false
1135
+ });
1136
+ }
1137
+ /**
1138
+ * Assert total token usage is at or below `maxTokens`.
1139
+ *
1140
+ * @param maxTokens - Maximum allowed token count.
1141
+ * @param opts.soft - When `true` the failure is non-blocking.
1142
+ * @returns The chain for fluent composition.
1143
+ *
1144
+ * @example
1145
+ * ```ts
1146
+ * expect(result).tokensUnder(500);
1147
+ * ```
1148
+ */
1149
+ tokensUnder(maxTokens, opts) {
1150
+ return this.add(TYPE_CONSTRAINT, {
1151
+ field: "metadata.total_tokens",
1152
+ operator: "lte",
1153
+ value: maxTokens,
1154
+ soft: opts?.soft ?? false
1155
+ });
1156
+ }
1157
+ /**
1158
+ * Assert total token usage is between `minTokens` and `maxTokens` (inclusive).
1159
+ *
1160
+ * @param minTokens - Minimum allowed token count.
1161
+ * @param maxTokens - Maximum allowed token count.
1162
+ * @param opts.soft - When `true` the failure is non-blocking.
1163
+ * @returns The chain for fluent composition.
1164
+ *
1165
+ * @example
1166
+ * ```ts
1167
+ * expect(result).tokensBetween(100, 500);
1168
+ * ```
1169
+ */
1170
+ tokensBetween(minTokens, maxTokens, opts) {
1171
+ return this.add(TYPE_CONSTRAINT, {
1172
+ field: "metadata.total_tokens",
1173
+ operator: "between",
1174
+ min: minTokens,
1175
+ max: maxTokens,
1176
+ soft: opts?.soft ?? false
1177
+ });
1178
+ }
1179
+ /**
1180
+ * Assert the number of steps satisfies the given comparison.
1181
+ *
1182
+ * @param operator - Comparison operator (e.g. `"eq"`, `"lte"`, `"gte"`).
1183
+ * @param value - The reference count to compare against.
1184
+ * @param opts.soft - When `true` the failure is non-blocking.
1185
+ * @returns The chain for fluent composition.
1186
+ *
1187
+ * @example
1188
+ * ```ts
1189
+ * expect(result).stepCount("lte", 5);
1190
+ * ```
1191
+ */
1192
+ stepCount(operator, value, opts) {
1193
+ return this.add(TYPE_CONSTRAINT, {
1194
+ field: "steps.length",
1195
+ operator,
1196
+ value,
1197
+ soft: opts?.soft ?? false
1198
+ });
1199
+ }
1200
+ /**
1201
+ * Assert the number of tool-call steps satisfies the given comparison.
1202
+ *
1203
+ * @param operator - Comparison operator (e.g. `"eq"`, `"lte"`, `"gte"`).
1204
+ * @param value - The reference count to compare against.
1205
+ * @param opts.soft - When `true` the failure is non-blocking.
1206
+ * @returns The chain for fluent composition.
1207
+ *
1208
+ * @example
1209
+ * ```ts
1210
+ * expect(result).toolCallCount("gte", 1);
1211
+ * ```
1212
+ */
1213
+ toolCallCount(operator, value, opts) {
1214
+ return this.add(TYPE_CONSTRAINT, {
1215
+ field: "steps[?type=='tool_call'].length",
1216
+ operator,
1217
+ value,
1218
+ soft: opts?.soft ?? false
1219
+ });
1220
+ }
1221
+ /**
1222
+ * Generic constraint assertion on any field.
1223
+ *
1224
+ * @param field - Dot-delimited path into the trace (e.g. `"metadata.cost_usd"`).
1225
+ * @param operator - Comparison operator.
1226
+ * @param opts.value - Reference value for single-operand operators.
1227
+ * @param opts.min - Minimum for `"between"` operator.
1228
+ * @param opts.max - Maximum for `"between"` operator.
1229
+ * @param opts.soft - When `true` the failure is non-blocking.
1230
+ * @returns The chain for fluent composition.
1231
+ *
1232
+ * @example
1233
+ * ```ts
1234
+ * expect(result).constraint("metadata.cost_usd", "lte", { value: 0.1 });
1235
+ * ```
1236
+ */
1237
+ constraint(field, operator, opts) {
1238
+ const spec = {
1239
+ field,
1240
+ operator,
1241
+ soft: opts?.soft ?? false
1242
+ };
1243
+ if (opts?.value !== void 0) spec.value = opts.value;
1244
+ if (opts?.min !== void 0) spec.min = opts.min;
1245
+ if (opts?.max !== void 0) spec.max = opts.max;
1246
+ return this.add(TYPE_CONSTRAINT, spec);
1247
+ }
1248
+ // -- Layer 3: Trace --
1249
+ /**
1250
+ * Assert that tools were called in the given order (non-contiguous allowed).
1251
+ *
1252
+ * @param tools - Ordered list of tool names expected.
1253
+ * @param opts.soft - When `true` the failure is non-blocking.
1254
+ * @returns The chain for fluent composition.
1255
+ *
1256
+ * @example
1257
+ * ```ts
1258
+ * expect(result).toolsCalledInOrder(["search", "write"]);
1259
+ * ```
1260
+ */
1261
+ toolsCalledInOrder(tools, opts) {
1262
+ return this.add(TYPE_TRACE, {
1263
+ check: "contains_in_order",
1264
+ tools,
1265
+ soft: opts?.soft ?? false
1266
+ });
1267
+ }
1268
+ /**
1269
+ * Assert that tools were called in exact contiguous order.
1270
+ *
1271
+ * @param tools - Exact ordered list of tool names.
1272
+ * @param opts.soft - When `true` the failure is non-blocking.
1273
+ * @returns The chain for fluent composition.
1274
+ *
1275
+ * @example
1276
+ * ```ts
1277
+ * expect(result).toolsCalledExactly(["search", "summarize", "respond"]);
1278
+ * ```
1279
+ */
1280
+ toolsCalledExactly(tools, opts) {
1281
+ return this.add(TYPE_TRACE, {
1282
+ check: "exact_order",
1283
+ tools,
1284
+ soft: opts?.soft ?? false
1285
+ });
1286
+ }
1287
+ /**
1288
+ * Assert a tool is not called more than `maxRepetitions` times consecutively.
1289
+ *
1290
+ * @param tool - The tool name to check.
1291
+ * @param maxRepetitions - Maximum allowed consecutive calls (default `1`).
1292
+ * @param opts.soft - When `true` the failure is non-blocking.
1293
+ * @returns The chain for fluent composition.
1294
+ *
1295
+ * @example
1296
+ * ```ts
1297
+ * expect(result).noToolLoops("fetch", 2);
1298
+ * ```
1299
+ */
1300
+ noToolLoops(tool, maxRepetitions = 1, opts) {
1301
+ return this.add(TYPE_TRACE, {
1302
+ check: "loop_detection",
1303
+ tool,
1304
+ max_repetitions: maxRepetitions,
1305
+ soft: opts?.soft ?? false
1306
+ });
1307
+ }
1308
+ /**
1309
+ * Assert no tool is called more than once.
1310
+ *
1311
+ * @param opts.soft - When `true` the failure is non-blocking.
1312
+ * @returns The chain for fluent composition.
1313
+ *
1314
+ * @example
1315
+ * ```ts
1316
+ * expect(result).noDuplicateTools();
1317
+ * ```
1318
+ */
1319
+ noDuplicateTools(opts) {
1320
+ return this.add(TYPE_TRACE, { check: "no_duplicates", soft: opts?.soft ?? false });
1321
+ }
1322
+ /**
1323
+ * Assert that all listed tools were called at least once.
1324
+ *
1325
+ * @param tools - Tool names that must appear in the trace.
1326
+ * @param opts.soft - When `true` the failure is non-blocking.
1327
+ * @returns The chain for fluent composition.
1328
+ *
1329
+ * @example
1330
+ * ```ts
1331
+ * expect(result).requiredTools(["search", "respond"]);
1332
+ * ```
1333
+ */
1334
+ requiredTools(tools, opts) {
1335
+ return this.add(TYPE_TRACE, {
1336
+ check: "required_tools",
1337
+ tools,
1338
+ soft: opts?.soft ?? false
1339
+ });
1340
+ }
1341
+ /**
1342
+ * Assert that none of the listed tools were called.
1343
+ *
1344
+ * @param tools - Tool names that must not appear in the trace.
1345
+ * @param opts.soft - When `true` the failure is non-blocking.
1346
+ * @returns The chain for fluent composition.
1347
+ *
1348
+ * @example
1349
+ * ```ts
1350
+ * expect(result).forbiddenTools(["delete_user", "drop_table"]);
1351
+ * ```
1352
+ */
1353
+ forbiddenTools(tools, opts) {
1354
+ return this.add(TYPE_TRACE, {
1355
+ check: "forbidden_tools",
1356
+ tools,
1357
+ soft: opts?.soft ?? false
1358
+ });
1359
+ }
1360
+ // -- Layer 4: Content --
1361
+ /**
1362
+ * Assert the output message contains the given string.
1363
+ *
1364
+ * @param value - Substring to search for.
1365
+ * @param opts.caseSensitive - Enable case-sensitive matching (default `false`).
1366
+ * @param opts.soft - When `true` the failure is non-blocking.
1367
+ * @returns The chain for fluent composition.
1368
+ *
1369
+ * @example
1370
+ * ```ts
1371
+ * expect(result).outputContains("refund approved");
1372
+ * ```
1373
+ */
1374
+ outputContains(value, opts) {
1375
+ return this.add(TYPE_CONTENT, {
1376
+ target: "output.message",
1377
+ check: "contains",
1378
+ value,
1379
+ case_sensitive: opts?.caseSensitive ?? false,
1380
+ soft: opts?.soft ?? false
1381
+ });
1382
+ }
1383
+ /**
1384
+ * Assert the output message does not contain the given string.
1385
+ *
1386
+ * @param value - Substring that must be absent.
1387
+ * @param opts.caseSensitive - Enable case-sensitive matching (default `false`).
1388
+ * @param opts.soft - When `true` the failure is non-blocking.
1389
+ * @returns The chain for fluent composition.
1390
+ *
1391
+ * @example
1392
+ * ```ts
1393
+ * expect(result).outputNotContains("internal error");
1394
+ * ```
1395
+ */
1396
+ outputNotContains(value, opts) {
1397
+ return this.add(TYPE_CONTENT, {
1398
+ target: "output.message",
1399
+ check: "not_contains",
1400
+ value,
1401
+ case_sensitive: opts?.caseSensitive ?? false,
1402
+ soft: opts?.soft ?? false
1403
+ });
1404
+ }
1405
+ /**
1406
+ * Assert the output message matches the given regular expression.
1407
+ *
1408
+ * @param pattern - Regex pattern string.
1409
+ * @param opts.soft - When `true` the failure is non-blocking.
1410
+ * @returns The chain for fluent composition.
1411
+ *
1412
+ * @example
1413
+ * ```ts
1414
+ * expect(result).outputMatchesRegex("order #\\d{5}");
1415
+ * ```
1416
+ */
1417
+ outputMatchesRegex(pattern, opts) {
1418
+ return this.add(TYPE_CONTENT, {
1419
+ target: "output.message",
1420
+ check: "regex_match",
1421
+ value: pattern,
1422
+ soft: opts?.soft ?? false
1423
+ });
1424
+ }
1425
+ /**
1426
+ * Assert the output message contains all of the given keywords.
1427
+ *
1428
+ * @param keywords - Keywords that must all be present.
1429
+ * @param opts.caseSensitive - Enable case-sensitive matching (default `false`).
1430
+ * @param opts.soft - When `true` the failure is non-blocking.
1431
+ * @returns The chain for fluent composition.
1432
+ *
1433
+ * @example
1434
+ * ```ts
1435
+ * expect(result).outputHasAllKeywords(["refund", "processed"]);
1436
+ * ```
1437
+ */
1438
+ outputHasAllKeywords(keywords, opts) {
1439
+ return this.add(TYPE_CONTENT, {
1440
+ target: "output.message",
1441
+ check: "keyword_all",
1442
+ values: keywords,
1443
+ case_sensitive: opts?.caseSensitive ?? false,
1444
+ soft: opts?.soft ?? false
1445
+ });
1446
+ }
1447
+ /**
1448
+ * Assert the output message contains at least one of the given keywords.
1449
+ *
1450
+ * @param keywords - Keywords where at least one must be present.
1451
+ * @param opts.caseSensitive - Enable case-sensitive matching (default `false`).
1452
+ * @param opts.soft - When `true` the failure is non-blocking.
1453
+ * @returns The chain for fluent composition.
1454
+ *
1455
+ * @example
1456
+ * ```ts
1457
+ * expect(result).outputHasAnyKeyword(["yes", "confirmed"]);
1458
+ * ```
1459
+ */
1460
+ outputHasAnyKeyword(keywords, opts) {
1461
+ return this.add(TYPE_CONTENT, {
1462
+ target: "output.message",
1463
+ check: "keyword_any",
1464
+ values: keywords,
1465
+ case_sensitive: opts?.caseSensitive ?? false,
1466
+ soft: opts?.soft ?? false
1467
+ });
1468
+ }
1469
+ /**
1470
+ * Assert the output message contains none of the forbidden terms.
1471
+ *
1472
+ * @param terms - Terms that must not appear in the output.
1473
+ * @returns The chain for fluent composition.
1474
+ *
1475
+ * @example
1476
+ * ```ts
1477
+ * expect(result).outputForbids(["password", "api_key"]);
1478
+ * ```
1479
+ */
1480
+ outputForbids(terms) {
1481
+ return this.add(TYPE_CONTENT, {
1482
+ target: "output.message",
1483
+ check: "forbidden",
1484
+ values: terms
1485
+ });
1486
+ }
1487
+ /**
1488
+ * Generic content-contains check on any target path.
1489
+ *
1490
+ * @param target - Dot-delimited path into the trace.
1491
+ * @param value - Substring to search for.
1492
+ * @param opts.caseSensitive - Enable case-sensitive matching (default `false`).
1493
+ * @param opts.soft - When `true` the failure is non-blocking.
1494
+ * @returns The chain for fluent composition.
1495
+ *
1496
+ * @example
1497
+ * ```ts
1498
+ * expect(result).contentContains("steps[0].result.text", "success");
1499
+ * ```
1500
+ */
1501
+ contentContains(target, value, opts) {
1502
+ return this.add(TYPE_CONTENT, {
1503
+ target,
1504
+ check: "contains",
1505
+ value,
1506
+ case_sensitive: opts?.caseSensitive ?? false,
1507
+ soft: opts?.soft ?? false
1508
+ });
1509
+ }
1510
+ // -- Layer 5: Embedding Similarity --
1511
+ /**
1512
+ * Assert the output is semantically similar to a reference string via embeddings.
1513
+ *
1514
+ * @param reference - The reference text to compare against.
1515
+ * @param opts.threshold - Minimum cosine similarity (default `0.8`).
1516
+ * @param opts.model - Override the embedding model.
1517
+ * @param opts.soft - When `true` the failure is non-blocking.
1518
+ * @returns The chain for fluent composition.
1519
+ *
1520
+ * @example
1521
+ * ```ts
1522
+ * expect(result).outputSimilarTo("The order has been refunded", { threshold: 0.85 });
1523
+ * ```
1524
+ */
1525
+ outputSimilarTo(reference, opts) {
1526
+ return this.add(TYPE_EMBEDDING, {
1527
+ target: "output.message",
1528
+ reference,
1529
+ threshold: opts?.threshold ?? 0.8,
1530
+ model: opts?.model,
1531
+ soft: opts?.soft ?? false
1532
+ });
1533
+ }
1534
+ // -- Layer 6: LLM Judge --
1535
+ /**
1536
+ * Assert the output passes LLM judge evaluation against given criteria.
1537
+ *
1538
+ * @param criteria - Natural language description of what makes a good response.
1539
+ * @param opts.rubric - Rubric name (default `"default"`).
1540
+ * @param opts.threshold - Minimum passing score (default `0.8`).
1541
+ * @param opts.model - Override the judge model.
1542
+ * @param opts.soft - When `true` the failure is non-blocking.
1543
+ * @returns The chain for fluent composition.
1544
+ *
1545
+ * @example
1546
+ * ```ts
1547
+ * expect(result).passesJudge("Response is polite and helpful", { threshold: 0.9 });
1548
+ * ```
1549
+ */
1550
+ passesJudge(criteria, opts) {
1551
+ return this.add(TYPE_LLM_JUDGE, {
1552
+ target: "output.message",
1553
+ criteria,
1554
+ rubric: opts?.rubric ?? "default",
1555
+ threshold: opts?.threshold ?? 0.8,
1556
+ model: opts?.model,
1557
+ soft: opts?.soft ?? false
1558
+ });
1559
+ }
1560
+ // -- Layer 7: Trace Tree (Multi-Agent) --
1561
+ /**
1562
+ * Assert that a specific agent was called in the trace tree.
1563
+ *
1564
+ * @param agentId - The agent identifier to look for.
1565
+ * @param opts.soft - When `true` the failure is non-blocking.
1566
+ * @returns The chain for fluent composition.
1567
+ *
1568
+ * @example
1569
+ * ```ts
1570
+ * expect(result).agentCalled("summarizer");
1571
+ * ```
1572
+ */
1573
+ agentCalled(agentId2, opts) {
1574
+ return this.add(TYPE_TRACE_TREE, {
1575
+ check: "agent_called",
1576
+ agent_id: agentId2,
1577
+ soft: opts?.soft ?? false
1578
+ });
1579
+ }
1580
+ /**
1581
+ * Assert the trace tree delegation depth does not exceed `maxDepth`.
1582
+ *
1583
+ * @param maxDepth - Maximum allowed delegation depth.
1584
+ * @param opts.soft - When `true` the failure is non-blocking.
1585
+ * @returns The chain for fluent composition.
1586
+ *
1587
+ * @example
1588
+ * ```ts
1589
+ * expect(result).delegationDepth(3);
1590
+ * ```
1591
+ */
1592
+ delegationDepth(maxDepth, opts) {
1593
+ return this.add(TYPE_TRACE_TREE, {
1594
+ check: "delegation_depth",
1595
+ max_depth: maxDepth,
1596
+ soft: opts?.soft ?? false
1597
+ });
1598
+ }
1599
+ /**
1600
+ * Assert a sub-agent's output contains the given string.
1601
+ *
1602
+ * @param agentId - The agent whose output to check.
1603
+ * @param value - Substring to search for.
1604
+ * @param opts.caseSensitive - Enable case-sensitive matching (default `false`).
1605
+ * @param opts.soft - When `true` the failure is non-blocking.
1606
+ * @returns The chain for fluent composition.
1607
+ *
1608
+ * @example
1609
+ * ```ts
1610
+ * expect(result).agentOutputContains("summarizer", "key findings");
1611
+ * ```
1612
+ */
1613
+ agentOutputContains(agentId2, value, opts) {
1614
+ return this.add(TYPE_TRACE_TREE, {
1615
+ check: "agent_output_contains",
1616
+ agent_id: agentId2,
1617
+ value,
1618
+ case_sensitive: opts?.caseSensitive ?? false,
1619
+ soft: opts?.soft ?? false
1620
+ });
1621
+ }
1622
+ /**
1623
+ * Assert data flows from one agent's output to another agent's input.
1624
+ *
1625
+ * @param fromAgent - Source agent ID.
1626
+ * @param toAgent - Destination agent ID.
1627
+ * @param field - The data field that must flow between agents.
1628
+ * @param opts.soft - When `true` the failure is non-blocking.
1629
+ * @returns The chain for fluent composition.
1630
+ *
1631
+ * @example
1632
+ * ```ts
1633
+ * expect(result).crossAgentDataFlow("researcher", "writer", "findings");
1634
+ * ```
1635
+ */
1636
+ crossAgentDataFlow(fromAgent, toAgent, field, opts) {
1637
+ return this.add(TYPE_TRACE_TREE, {
1638
+ check: "cross_agent_data_flow",
1639
+ from_agent: fromAgent,
1640
+ to_agent: toAgent,
1641
+ field,
1642
+ soft: opts?.soft ?? false
1643
+ });
1644
+ }
1645
+ /**
1646
+ * Assert agent delegations follow the specified transitions.
1647
+ *
1648
+ * @param transitions - Array of `[fromAgent, toAgent]` pairs.
1649
+ * @param opts.soft - When `true` the failure is non-blocking.
1650
+ * @returns The chain for fluent composition.
1651
+ *
1652
+ * @example
1653
+ * ```ts
1654
+ * expect(result).followsTransitions([["root", "planner"], ["planner", "executor"]]);
1655
+ * ```
1656
+ */
1657
+ followsTransitions(transitions, opts) {
1658
+ return this.add(TYPE_TRACE_TREE, {
1659
+ check: "follows_transitions",
1660
+ transitions,
1661
+ soft: opts?.soft ?? false
1662
+ });
1663
+ }
1664
+ /**
1665
+ * Assert aggregate cost across the entire trace tree is at or below `maxCost`.
1666
+ *
1667
+ * @param maxCost - Maximum allowed aggregate cost in USD.
1668
+ * @param opts.soft - When `true` the failure is non-blocking.
1669
+ * @returns The chain for fluent composition.
1670
+ *
1671
+ * @example
1672
+ * ```ts
1673
+ * expect(result).aggregateCostUnder(0.50);
1674
+ * ```
1675
+ */
1676
+ aggregateCostUnder(maxCost, opts) {
1677
+ return this.add(TYPE_TRACE_TREE, {
1678
+ check: "aggregate_cost",
1679
+ operator: "lte",
1680
+ value: maxCost,
1681
+ soft: opts?.soft ?? false
1682
+ });
1683
+ }
1684
+ /**
1685
+ * Assert aggregate tokens across the entire trace tree is at or below `maxTokens`.
1686
+ *
1687
+ * @param maxTokens - Maximum allowed aggregate token count.
1688
+ * @param opts.soft - When `true` the failure is non-blocking.
1689
+ * @returns The chain for fluent composition.
1690
+ *
1691
+ * @example
1692
+ * ```ts
1693
+ * expect(result).aggregateTokensUnder(10000);
1694
+ * ```
1695
+ */
1696
+ aggregateTokensUnder(maxTokens, opts) {
1697
+ return this.add(TYPE_TRACE_TREE, {
1698
+ check: "aggregate_tokens",
1699
+ operator: "lte",
1700
+ value: maxTokens,
1701
+ soft: opts?.soft ?? false
1702
+ });
1703
+ }
1704
+ /**
1705
+ * Assert `agentA` started before `agentB` in the trace tree.
1706
+ *
1707
+ * @param agentA - The agent expected to run first.
1708
+ * @param agentB - The agent expected to run second.
1709
+ * @param opts.soft - When `true` the failure is non-blocking.
1710
+ * @returns The chain for fluent composition.
1711
+ *
1712
+ * @example
1713
+ * ```ts
1714
+ * expect(result).agentOrderedBefore("planner", "executor");
1715
+ * ```
1716
+ */
1717
+ agentOrderedBefore(agentA, agentB, opts) {
1718
+ return this.add(TYPE_TRACE_TREE, {
1719
+ check: "agent_ordered_before",
1720
+ agent_a: agentA,
1721
+ agent_b: agentB,
1722
+ soft: opts?.soft ?? false
1723
+ });
1724
+ }
1725
+ /**
1726
+ * Assert `agentA` and `agentB` ran concurrently (overlapping wall-clock time).
1727
+ *
1728
+ * @param agentA - First agent ID.
1729
+ * @param agentB - Second agent ID.
1730
+ * @param opts.soft - When `true` the failure is non-blocking.
1731
+ * @returns The chain for fluent composition.
1732
+ *
1733
+ * @example
1734
+ * ```ts
1735
+ * expect(result).agentsOverlap("worker-1", "worker-2");
1736
+ * ```
1737
+ */
1738
+ agentsOverlap(agentA, agentB, opts) {
1739
+ return this.add(TYPE_TRACE_TREE, {
1740
+ check: "agents_overlap",
1741
+ agent_a: agentA,
1742
+ agent_b: agentB,
1743
+ soft: opts?.soft ?? false
1744
+ });
1745
+ }
1746
+ /**
1747
+ * Assert a specific agent's wall-clock duration is under `maxMs` milliseconds.
1748
+ *
1749
+ * @param agentId - The agent to check.
1750
+ * @param maxMs - Maximum allowed wall-clock time in milliseconds.
1751
+ * @param opts.soft - When `true` the failure is non-blocking.
1752
+ * @returns The chain for fluent composition.
1753
+ *
1754
+ * @example
1755
+ * ```ts
1756
+ * expect(result).agentWallTimeUnder("summarizer", 5000);
1757
+ * ```
1758
+ */
1759
+ agentWallTimeUnder(agentId2, maxMs, opts) {
1760
+ return this.add(TYPE_TRACE_TREE, {
1761
+ check: "agent_wall_time_under",
1762
+ agent_id: agentId2,
1763
+ max_ms: maxMs,
1764
+ soft: opts?.soft ?? false
1765
+ });
1766
+ }
1767
+ /**
1768
+ * Assert agents ran in ordered groups (parallel within each group, sequential across groups).
1769
+ *
1770
+ * @param groups - Array of agent-ID arrays. Each sub-array is a parallel group.
1771
+ * @param opts.soft - When `true` the failure is non-blocking.
1772
+ * @returns The chain for fluent composition.
1773
+ *
1774
+ * @example
1775
+ * ```ts
1776
+ * expect(result).orderedAgents([["planner"], ["worker-1", "worker-2"], ["aggregator"]]);
1777
+ * ```
1778
+ */
1779
+ orderedAgents(groups, opts) {
1780
+ return this.add(TYPE_TRACE_TREE, {
1781
+ check: "ordered_agents",
1782
+ groups,
1783
+ soft: opts?.soft ?? false
1784
+ });
1785
+ }
1786
+ // -- Layer 8: Plugin --
1787
+ /**
1788
+ * Assert via a registered plugin (Layer 8).
1789
+ *
1790
+ * @param pluginId - Identifier of the plugin registered with the engine.
1791
+ * @param config - Plugin-specific configuration passed at evaluation time.
1792
+ * @param opts.soft - When `true` the failure is non-blocking.
1793
+ * @returns The chain for fluent composition.
1794
+ *
1795
+ * @example
1796
+ * ```ts
1797
+ * expect(result).plugin("custom-safety-check", { threshold: 0.95 });
1798
+ * ```
1799
+ */
1800
+ plugin(pluginId, config2, opts) {
1801
+ const spec = { plugin_id: pluginId, soft: opts?.soft ?? false };
1802
+ if (config2 !== void 0) spec.config = config2;
1803
+ return this.add(TYPE_PLUGIN, spec);
1804
+ }
1805
+ // -- TraceTree Analytics (P9 parity) --
1806
+ /**
1807
+ * Assert aggregate latency across the trace tree is at or below `maxMs`.
1808
+ *
1809
+ * @param maxMs - Maximum allowed aggregate latency in milliseconds.
1810
+ * @param opts.soft - When `true` the failure is non-blocking.
1811
+ * @returns The chain for fluent composition.
1812
+ *
1813
+ * @example
1814
+ * ```ts
1815
+ * expect(result).aggregateLatencyUnder(5000);
1816
+ * ```
1817
+ */
1818
+ aggregateLatencyUnder(maxMs, opts) {
1819
+ return this.add(TYPE_TRACE_TREE, {
1820
+ check: "aggregate_latency",
1821
+ operator: "lte",
1822
+ value: maxMs,
1823
+ soft: opts?.soft ?? false
1824
+ });
1825
+ }
1826
+ /**
1827
+ * Assert that all specified tools were called across the entire trace tree.
1828
+ *
1829
+ * @param toolNames - Tool names that must appear somewhere in the tree.
1830
+ * @param opts.soft - When `true` the failure is non-blocking.
1831
+ * @returns The chain for fluent composition.
1832
+ *
1833
+ * @example
1834
+ * ```ts
1835
+ * expect(result).allToolsCalled(["search", "summarize"]);
1836
+ * ```
1837
+ */
1838
+ allToolsCalled(toolNames, opts) {
1839
+ return this.add(TYPE_TRACE_TREE, {
1840
+ check: "all_tools_called",
1841
+ tools: toolNames,
1842
+ soft: opts?.soft ?? false
1843
+ });
1844
+ }
1845
+ };
1846
+ function attestExpect(result) {
1847
+ if (result instanceof AgentResult) {
1848
+ return new ExpectChain(result);
1849
+ }
1850
+ return new ExpectChain(new AgentResult(result));
1851
+ }
1852
+
1853
+ // src/context.ts
1854
+ var import_node_async_hooks = require("async_hooks");
1855
+ var activeBuilder = new import_node_async_hooks.AsyncLocalStorage();
1856
+
1857
+ // src/agent.ts
1858
+ var Agent = class {
1859
+ name;
1860
+ fn;
1861
+ constructor(name, fn) {
1862
+ this.name = name;
1863
+ this.fn = fn;
1864
+ }
1865
+ run(args = {}) {
1866
+ if (this.fn === void 0) {
1867
+ throw new Error("No agent function provided. Pass fn to Agent().");
1868
+ }
1869
+ const builder = new TraceBuilder(this.name);
1870
+ builder.setInput(args);
1871
+ const output = activeBuilder.run(builder, () => this.fn(builder, args));
1872
+ this.setOutput(builder, output);
1873
+ return new AgentResult(builder.build());
1874
+ }
1875
+ async arun(args = {}) {
1876
+ if (this.fn === void 0) {
1877
+ throw new Error("No agent function provided. Pass fn to Agent().");
1878
+ }
1879
+ const builder = new TraceBuilder(this.name);
1880
+ builder.setInput(args);
1881
+ const output = await activeBuilder.run(builder, async () => this.fn(builder, args));
1882
+ this.setOutput(builder, output);
1883
+ return new AgentResult(builder.build());
1884
+ }
1885
+ setOutput(builder, output) {
1886
+ if (output !== null && output !== void 0 && typeof output === "object" && !Array.isArray(output)) {
1887
+ builder.setOutput(output);
1888
+ } else if (output !== void 0) {
1889
+ builder.setOutput({ result: output });
1890
+ } else {
1891
+ builder.setOutput({ result: null });
1892
+ }
1893
+ }
1894
+ withTrace(trace) {
1895
+ return new AgentResult(trace);
1896
+ }
1897
+ };
1898
+ function agent(name, fn) {
1899
+ const wrapped = new Agent(name, fn);
1900
+ const wrapper = (args) => {
1901
+ return wrapped.run(args ?? {});
1902
+ };
1903
+ wrapper.agent = wrapped;
1904
+ return wrapper;
1905
+ }
1906
+
1907
+ // src/delegate.ts
1908
+ async function delegate(agentId2, fn) {
1909
+ const parent = activeBuilder.getStore();
1910
+ if (parent === void 0) {
1911
+ throw new Error(
1912
+ "delegate() must be used within an Agent.run() context. No active TraceBuilder found."
1913
+ );
1914
+ }
1915
+ const child = new TraceBuilder(agentId2);
1916
+ child.setParentTraceId(parent.getTraceId());
1917
+ await activeBuilder.run(child, async () => {
1918
+ await fn(child);
1919
+ });
1920
+ const childTrace = child.build();
1921
+ parent.addStep({
1922
+ type: STEP_AGENT_CALL,
1923
+ name: agentId2,
1924
+ args: void 0,
1925
+ result: childTrace.output,
1926
+ sub_trace: childTrace
1927
+ });
1928
+ }
1929
+
1930
+ // src/tier.ts
1931
+ var TIER_1 = 1;
1932
+ var TIER_2 = 2;
1933
+ var TIER_3 = 3;
1934
+ function tier(level) {
1935
+ return (fn) => {
1936
+ fn._attest_tier = level;
1937
+ return fn;
1938
+ };
1939
+ }
1940
+
1941
+ // src/plugins.ts
1942
+ var PluginRegistry = class {
1943
+ plugins = /* @__PURE__ */ new Map();
1944
+ register(name, plugin) {
1945
+ const pluginType = plugin.pluginType;
1946
+ let typeMap = this.plugins.get(pluginType);
1947
+ if (typeMap === void 0) {
1948
+ typeMap = /* @__PURE__ */ new Map();
1949
+ this.plugins.set(pluginType, typeMap);
1950
+ }
1951
+ typeMap.set(name, plugin);
1952
+ }
1953
+ get(pluginType, name) {
1954
+ return this.plugins.get(pluginType)?.get(name);
1955
+ }
1956
+ listPlugins(pluginType) {
1957
+ if (pluginType !== void 0) {
1958
+ const typeMap = this.plugins.get(pluginType);
1959
+ return typeMap !== void 0 ? [...typeMap.keys()] : [];
1960
+ }
1961
+ const result = [];
1962
+ for (const [ptype, typeMap] of this.plugins) {
1963
+ for (const pname of typeMap.keys()) {
1964
+ result.push(`${ptype}/${pname}`);
1965
+ }
1966
+ }
1967
+ return result;
1968
+ }
1969
+ };
1970
+ async function executePluginAssertion(plugin, trace, spec, client, traceId2, assertionId2, timeoutMs = 3e4) {
1971
+ const resultPromise = Promise.resolve(plugin.execute(trace, spec));
1972
+ const timeoutPromise = new Promise((_, reject) => {
1973
+ setTimeout(() => reject(new Error(`Plugin '${plugin.name}' timed out after ${timeoutMs}ms`)), timeoutMs);
1974
+ });
1975
+ const result = await Promise.race([resultPromise, timeoutPromise]);
1976
+ await client.submitPluginResult(
1977
+ traceId2,
1978
+ plugin.name,
1979
+ assertionId2,
1980
+ result.status,
1981
+ result.score,
1982
+ result.explanation
1983
+ );
1984
+ return result;
1985
+ }
1986
+
1987
+ // src/continuous.ts
1988
+ var import_node_events = require("events");
1989
+ var DEFAULT_QUEUE_SIZE = 1e3;
1990
+ function resolveQueueSize() {
1991
+ const raw = process.env["ATTEST_CONTINUOUS_QUEUE_SIZE"];
1992
+ if (raw !== void 0 && raw !== "") {
1993
+ const parsed = Number(raw);
1994
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
1995
+ }
1996
+ return DEFAULT_QUEUE_SIZE;
1997
+ }
1998
+ var Sampler = class {
1999
+ rate;
2000
+ constructor(rate) {
2001
+ if (rate < 0 || rate > 1) {
2002
+ throw new Error(`sample_rate must be in [0.0, 1.0], got ${rate}`);
2003
+ }
2004
+ this.rate = rate;
2005
+ }
2006
+ shouldSample() {
2007
+ return Math.random() < this.rate;
2008
+ }
2009
+ };
2010
+ var AlertDispatcher = class extends import_node_events.EventEmitter {
2011
+ webhookUrl;
2012
+ slackUrl;
2013
+ constructor(webhookUrl, slackUrl) {
2014
+ super();
2015
+ this.webhookUrl = webhookUrl;
2016
+ this.slackUrl = slackUrl;
2017
+ }
2018
+ async dispatch(alert) {
2019
+ this.emit("alert", alert);
2020
+ const tasks = [];
2021
+ if (this.webhookUrl) {
2022
+ tasks.push(this.postJson(this.webhookUrl, alert));
2023
+ }
2024
+ if (this.slackUrl) {
2025
+ const text = `[attest] drift alert -- type=${alert.drift_type} score=${alert.score} trace_id=${alert.trace_id}`;
2026
+ tasks.push(this.postJson(this.slackUrl, { text }));
2027
+ }
2028
+ const results = await Promise.allSettled(tasks);
2029
+ for (const result of results) {
2030
+ if (result.status === "rejected") {
2031
+ this.emit("alertError", result.reason);
2032
+ }
2033
+ }
2034
+ }
2035
+ async postJson(url, payload) {
2036
+ const response = await fetch(url, {
2037
+ method: "POST",
2038
+ headers: { "Content-Type": "application/json" },
2039
+ body: JSON.stringify(payload),
2040
+ signal: AbortSignal.timeout(1e4)
2041
+ });
2042
+ if (!response.ok) {
2043
+ throw new Error(`POST to ${url} returned ${response.status}`);
2044
+ }
2045
+ }
2046
+ };
2047
+ var ContinuousEvalRunner = class {
2048
+ client;
2049
+ assertions;
2050
+ sampler;
2051
+ dispatcher;
2052
+ queue = [];
2053
+ maxSize;
2054
+ running = false;
2055
+ loopTimer = null;
2056
+ constructor(client, assertions, options) {
2057
+ this.client = client;
2058
+ this.assertions = assertions;
2059
+ this.sampler = new Sampler(options?.sampleRate ?? 1);
2060
+ this.dispatcher = new AlertDispatcher(options?.alertWebhook, options?.alertSlackUrl);
2061
+ this.maxSize = options?.maxSize ?? resolveQueueSize();
2062
+ }
2063
+ get alertEmitter() {
2064
+ return this.dispatcher;
2065
+ }
2066
+ async evaluateTrace(trace) {
2067
+ if (!this.sampler.shouldSample()) return null;
2068
+ return this.client.evaluateBatch(trace, this.assertions);
2069
+ }
2070
+ submit(trace) {
2071
+ if (this.queue.length >= this.maxSize) {
2072
+ return false;
2073
+ }
2074
+ this.queue.push(trace);
2075
+ return true;
2076
+ }
2077
+ start() {
2078
+ if (this.running) return;
2079
+ this.running = true;
2080
+ this.scheduleNext();
2081
+ }
2082
+ stop() {
2083
+ this.running = false;
2084
+ if (this.loopTimer !== null) {
2085
+ clearTimeout(this.loopTimer);
2086
+ this.loopTimer = null;
2087
+ }
2088
+ }
2089
+ get queueLength() {
2090
+ return this.queue.length;
2091
+ }
2092
+ get isRunning() {
2093
+ return this.running;
2094
+ }
2095
+ scheduleNext() {
2096
+ if (!this.running) return;
2097
+ this.loopTimer = setTimeout(async () => {
2098
+ if (!this.running) return;
2099
+ const trace = this.queue.shift();
2100
+ if (trace !== void 0) {
2101
+ try {
2102
+ await this.evaluateTrace(trace);
2103
+ } catch {
2104
+ }
2105
+ }
2106
+ this.scheduleNext();
2107
+ }, this.queue.length > 0 ? 0 : 100);
2108
+ }
2109
+ };
2110
+
2111
+ // src/personas.ts
2112
+ var FRIENDLY_USER = {
2113
+ name: "FriendlyUser",
2114
+ system_prompt: `You are a friendly, cooperative user interacting with an AI assistant.
2115
+ You make clear, well-formed requests. You respond positively to helpful answers and
2116
+ ask straightforward follow-up questions. Keep responses concise (1-3 sentences).`,
2117
+ style: "friendly",
2118
+ temperature: 0.7,
2119
+ max_tokens: 200
2120
+ };
2121
+ var ADVERSARIAL_USER = {
2122
+ name: "AdversarialUser",
2123
+ system_prompt: `You are an adversarial user testing the limits of an AI assistant.
2124
+ You ask edge case questions, make ambiguous requests, and probe boundary conditions.
2125
+ Try to find inconsistencies or unexpected behaviors. Keep responses concise (1-3 sentences).`,
2126
+ style: "adversarial",
2127
+ temperature: 0.9,
2128
+ max_tokens: 200
2129
+ };
2130
+ var CONFUSED_USER = {
2131
+ name: "ConfusedUser",
2132
+ system_prompt: `You are a confused user who has trouble articulating what you want.
2133
+ You make vague requests, sometimes contradict yourself, and frequently ask for clarification.
2134
+ You may misunderstand the assistant's responses. Keep responses concise (1-3 sentences).`,
2135
+ style: "confused",
2136
+ temperature: 0.8,
2137
+ max_tokens: 200
2138
+ };
2139
+ var COOPERATIVE_USER = {
2140
+ name: "CooperativeUser",
2141
+ system_prompt: `You are a cooperative user who actively helps the AI assistant succeed.
2142
+ You provide clear context, answer clarifying questions thoroughly, and confirm when
2143
+ the assistant's response meets your needs. Keep responses concise (1-3 sentences).`,
2144
+ style: "cooperative",
2145
+ temperature: 0.6,
2146
+ max_tokens: 200
2147
+ };
2148
+
2149
+ // src/adapters/manual.ts
2150
+ var ManualAdapter = class {
2151
+ agentId;
2152
+ constructor(agentId2) {
2153
+ this.agentId = agentId2;
2154
+ }
2155
+ capture(builderFn) {
2156
+ const builder = new TraceBuilder(this.agentId);
2157
+ builderFn(builder);
2158
+ return builder.build();
2159
+ }
2160
+ createBuilder() {
2161
+ return new TraceBuilder(this.agentId);
2162
+ }
2163
+ };
2164
+
2165
+ // src/adapters/openai.ts
2166
+ var OpenAIAdapter = class {
2167
+ agentId;
2168
+ constructor(agentId2) {
2169
+ this.agentId = agentId2;
2170
+ }
2171
+ traceFromResponse(response, options) {
2172
+ const builder = new TraceBuilder(this.agentId);
2173
+ if (options?.inputMessages) {
2174
+ builder.setInput({ messages: options.inputMessages });
2175
+ }
2176
+ const message = response.choices[0].message;
2177
+ const completionText = message.content ?? "";
2178
+ const stepArgs = {};
2179
+ if (response.model !== void 0) stepArgs.model = response.model;
2180
+ const stepResult = { completion: completionText };
2181
+ if (response.usage?.total_tokens !== void 0) {
2182
+ stepResult.tokens = response.usage.total_tokens;
2183
+ }
2184
+ builder.addLlmCall("completion", { args: stepArgs, result: stepResult });
2185
+ if (message.tool_calls) {
2186
+ for (const tc of message.tool_calls) {
2187
+ builder.addToolCall(tc.function.name, {
2188
+ args: { arguments: tc.function.arguments }
2189
+ });
2190
+ }
2191
+ }
2192
+ builder.setOutput({
2193
+ message: completionText,
2194
+ structured: options?.structuredOutput ?? {}
2195
+ });
2196
+ builder.setMetadata({
2197
+ total_tokens: response.usage?.total_tokens,
2198
+ cost_usd: options?.costUsd,
2199
+ latency_ms: options?.latencyMs,
2200
+ model: response.model
2201
+ });
2202
+ return builder.build();
2203
+ }
2204
+ };
2205
+
2206
+ // src/adapters/anthropic.ts
2207
+ var AnthropicAdapter = class {
2208
+ agentId;
2209
+ constructor(agentId2) {
2210
+ this.agentId = agentId2;
2211
+ }
2212
+ traceFromResponse(response, options) {
2213
+ const builder = new TraceBuilder(this.agentId);
2214
+ if (options?.inputMessages) {
2215
+ builder.setInput({ messages: options.inputMessages });
2216
+ }
2217
+ const completionParts = [];
2218
+ for (const block of response.content) {
2219
+ if (block.type === "text") {
2220
+ completionParts.push(block.text);
2221
+ } else if (block.type === "tool_use") {
2222
+ builder.addToolCall(block.name, {
2223
+ args: block.input
2224
+ });
2225
+ }
2226
+ }
2227
+ const completionText = completionParts.join("\n");
2228
+ const stepArgs = {};
2229
+ if (response.model !== void 0) stepArgs.model = response.model;
2230
+ builder.addLlmCall("completion", { args: stepArgs, result: { completion: completionText } });
2231
+ builder.setOutput({ message: completionText });
2232
+ let totalTokens;
2233
+ if (response.usage) {
2234
+ totalTokens = response.usage.input_tokens + response.usage.output_tokens;
2235
+ }
2236
+ builder.setMetadata({
2237
+ total_tokens: totalTokens,
2238
+ cost_usd: options?.costUsd,
2239
+ latency_ms: options?.latencyMs,
2240
+ model: response.model
2241
+ });
2242
+ return builder.build();
2243
+ }
2244
+ };
2245
+
2246
+ // src/adapters/gemini.ts
2247
+ var GeminiAdapter = class {
2248
+ agentId;
2249
+ constructor(agentId2) {
2250
+ this.agentId = agentId2;
2251
+ }
2252
+ traceFromResponse(response, options) {
2253
+ const builder = new TraceBuilder(this.agentId);
2254
+ if (options?.inputText) {
2255
+ builder.setInput({ text: options.inputText });
2256
+ }
2257
+ let completionText = "";
2258
+ if (response.text != null) {
2259
+ completionText = response.text;
2260
+ } else if (response.candidates && response.candidates.length > 0) {
2261
+ completionText = response.candidates[0].content.parts.filter((p) => p.text != null).map((p) => p.text).join("");
2262
+ }
2263
+ if (response.candidates && response.candidates.length > 0) {
2264
+ for (const part of response.candidates[0].content.parts) {
2265
+ if (part.functionCall) {
2266
+ builder.addToolCall(part.functionCall.name, {
2267
+ args: part.functionCall.args ?? {}
2268
+ });
2269
+ }
2270
+ }
2271
+ }
2272
+ builder.addLlmCall("completion", { result: { completion: completionText } });
2273
+ builder.setOutput({ message: completionText });
2274
+ builder.setMetadata({
2275
+ cost_usd: options?.costUsd,
2276
+ latency_ms: options?.latencyMs,
2277
+ model: options?.model
2278
+ });
2279
+ return builder.build();
2280
+ }
2281
+ };
2282
+
2283
+ // src/adapters/ollama.ts
2284
+ var OllamaAdapter = class {
2285
+ agentId;
2286
+ constructor(agentId2) {
2287
+ this.agentId = agentId2;
2288
+ }
2289
+ traceFromResponse(response, options) {
2290
+ const builder = new TraceBuilder(this.agentId);
2291
+ if (options?.inputMessages) {
2292
+ builder.setInput({ messages: options.inputMessages });
2293
+ }
2294
+ const completionText = response.message?.content ?? "";
2295
+ builder.addLlmCall("completion", {
2296
+ args: { model: response.model ?? "" },
2297
+ result: { completion: completionText }
2298
+ });
2299
+ builder.setOutput({ message: completionText });
2300
+ let totalTokens;
2301
+ if (response.eval_count !== void 0 && response.prompt_eval_count !== void 0) {
2302
+ totalTokens = response.eval_count + response.prompt_eval_count;
2303
+ }
2304
+ builder.setMetadata({
2305
+ total_tokens: totalTokens,
2306
+ latency_ms: options?.latencyMs,
2307
+ model: response.model
2308
+ });
2309
+ return builder.build();
2310
+ }
2311
+ };
2312
+
2313
+ // src/adapters/otel.ts
2314
+ var OTelAdapter = class _OTelAdapter {
2315
+ agentId;
2316
+ constructor(agentId2) {
2317
+ this.agentId = agentId2;
2318
+ }
2319
+ static fromSpans(spans, agentId2) {
2320
+ const adapter = new _OTelAdapter(agentId2);
2321
+ return adapter.buildTrace(spans);
2322
+ }
2323
+ buildTrace(spans) {
2324
+ const sorted = [...spans].sort((a, b) => {
2325
+ const aTime = a.startTime[0] * 1e9 + a.startTime[1];
2326
+ const bTime = b.startTime[0] * 1e9 + b.startTime[1];
2327
+ return aTime - bTime;
2328
+ });
2329
+ const builder = new TraceBuilder(this.agentId);
2330
+ const rootSpan = this.findRootSpan(sorted);
2331
+ if (rootSpan) {
2332
+ const traceId2 = rootSpan.spanContext().traceId;
2333
+ if (traceId2) {
2334
+ builder.setTraceId(`otel_${traceId2.slice(0, 16)}`);
2335
+ }
2336
+ }
2337
+ let outputMessage = "";
2338
+ let totalTokens;
2339
+ let latencyMs;
2340
+ let model;
2341
+ for (const span of sorted) {
2342
+ const attrs = span.attributes ?? {};
2343
+ const stepType = this.classifySpan(attrs, span.name);
2344
+ if (stepType === "llm_call") {
2345
+ const [stepArgs, stepResult] = this.extractLlmStep(attrs);
2346
+ builder.addLlmCall(span.name, {
2347
+ args: stepArgs,
2348
+ result: stepResult,
2349
+ metadata: this.spanMetadata(span)
2350
+ });
2351
+ const completion = attrs["gen_ai.completion"];
2352
+ if (completion) outputMessage = String(completion);
2353
+ const inputTokens = Number(attrs["gen_ai.usage.input_tokens"] ?? 0);
2354
+ const outputTokens = Number(attrs["gen_ai.usage.output_tokens"] ?? 0);
2355
+ const spanTokens = inputTokens + outputTokens;
2356
+ if (spanTokens > 0) totalTokens = (totalTokens ?? 0) + spanTokens;
2357
+ if (model === void 0) {
2358
+ const responseModel = attrs["gen_ai.response.model"];
2359
+ const requestModel = attrs["gen_ai.request.model"];
2360
+ if (responseModel) model = String(responseModel);
2361
+ else if (requestModel) model = String(requestModel);
2362
+ }
2363
+ } else if (stepType === "tool_call") {
2364
+ const [stepArgs, stepResult] = this.extractToolStep(attrs);
2365
+ const toolName = String(attrs["gen_ai.tool.name"] ?? span.name);
2366
+ builder.addToolCall(toolName, {
2367
+ args: stepArgs,
2368
+ result: stepResult,
2369
+ metadata: this.spanMetadata(span)
2370
+ });
2371
+ }
2372
+ }
2373
+ if (rootSpan) {
2374
+ const startNs = rootSpan.startTime[0] * 1e9 + rootSpan.startTime[1];
2375
+ const endNs = rootSpan.endTime[0] * 1e9 + rootSpan.endTime[1];
2376
+ latencyMs = Math.floor((endNs - startNs) / 1e6);
2377
+ }
2378
+ builder.setOutput({ message: outputMessage });
2379
+ builder.setMetadata({ total_tokens: totalTokens, latency_ms: latencyMs, model });
2380
+ return builder.build();
2381
+ }
2382
+ findRootSpan(spans) {
2383
+ for (const span of spans) {
2384
+ if (!span.parentSpanId) return span;
2385
+ }
2386
+ return spans[0];
2387
+ }
2388
+ classifySpan(attrs, name) {
2389
+ const op = String(attrs["gen_ai.operation.name"] ?? "");
2390
+ if (op === "chat" || op === "completion" || op === "generate_content" || "gen_ai.completion" in attrs) {
2391
+ return "llm_call";
2392
+ }
2393
+ if (op === "tool" || "gen_ai.tool.name" in attrs) {
2394
+ return "tool_call";
2395
+ }
2396
+ const lower = name.toLowerCase();
2397
+ if (lower.includes("completion") || lower.includes("chat")) return "llm_call";
2398
+ if (lower.includes("tool")) return "tool_call";
2399
+ return void 0;
2400
+ }
2401
+ extractLlmStep(attrs) {
2402
+ const args = {};
2403
+ const result = {};
2404
+ if ("gen_ai.request.model" in attrs) args.model = String(attrs["gen_ai.request.model"]);
2405
+ if ("gen_ai.system" in attrs) args.system = String(attrs["gen_ai.system"]);
2406
+ if ("gen_ai.prompt" in attrs) args.prompt = String(attrs["gen_ai.prompt"]);
2407
+ if ("gen_ai.completion" in attrs) result.completion = String(attrs["gen_ai.completion"]);
2408
+ if ("gen_ai.usage.input_tokens" in attrs) result.input_tokens = Number(attrs["gen_ai.usage.input_tokens"]);
2409
+ if ("gen_ai.usage.output_tokens" in attrs) result.output_tokens = Number(attrs["gen_ai.usage.output_tokens"]);
2410
+ if ("gen_ai.response.model" in attrs) result.model = String(attrs["gen_ai.response.model"]);
2411
+ return [args, result];
2412
+ }
2413
+ extractToolStep(attrs) {
2414
+ const args = {};
2415
+ const result = {};
2416
+ if ("gen_ai.tool.call.id" in attrs) args.call_id = String(attrs["gen_ai.tool.call.id"]);
2417
+ if ("gen_ai.tool.parameters" in attrs) args.parameters = attrs["gen_ai.tool.parameters"];
2418
+ if ("gen_ai.tool.output" in attrs) result.output = attrs["gen_ai.tool.output"];
2419
+ return [args, result];
2420
+ }
2421
+ spanMetadata(span) {
2422
+ const startNs = span.startTime[0] * 1e9 + span.startTime[1];
2423
+ const endNs = span.endTime[0] * 1e9 + span.endTime[1];
2424
+ return { duration_ms: Math.floor((endNs - startNs) / 1e6) };
2425
+ }
2426
+ };
2427
+
2428
+ // src/adapters/langchain.ts
2429
+ var LangChainAdapter = class {
2430
+ agentId;
2431
+ llmCalls = [];
2432
+ toolCalls = [];
2433
+ chainOutput = "";
2434
+ startTime = 0;
2435
+ constructor(agentId2) {
2436
+ this.agentId = agentId2;
2437
+ }
2438
+ /**
2439
+ * Reset accumulated state for a new trace capture session.
2440
+ */
2441
+ reset() {
2442
+ this.llmCalls.length = 0;
2443
+ this.toolCalls.length = 0;
2444
+ this.chainOutput = "";
2445
+ this.startTime = 0;
2446
+ }
2447
+ /**
2448
+ * Call this when a chain starts (maps to `handleChainStart`).
2449
+ */
2450
+ handleChainStart() {
2451
+ if (this.startTime === 0) {
2452
+ this.startTime = Date.now();
2453
+ }
2454
+ }
2455
+ /**
2456
+ * Call this when an LLM call completes (maps to `handleLLMEnd`).
2457
+ */
2458
+ handleLLMEnd(output) {
2459
+ const text = output.generations[0]?.[0]?.text ?? "";
2460
+ const tokens = output.llmOutput?.["tokenUsage"];
2461
+ const model = output.llmOutput?.["modelName"];
2462
+ this.llmCalls.push({
2463
+ name: "completion",
2464
+ text,
2465
+ tokens: tokens?.totalTokens,
2466
+ model
2467
+ });
2468
+ }
2469
+ /**
2470
+ * Call this when a tool call completes (maps to `handleToolEnd`).
2471
+ */
2472
+ handleToolEnd(output, metadata) {
2473
+ this.toolCalls.push({
2474
+ name: metadata?.name ?? "unknown_tool",
2475
+ output
2476
+ });
2477
+ }
2478
+ /**
2479
+ * Call this when a chain completes (maps to `handleChainEnd`).
2480
+ */
2481
+ handleChainEnd(output) {
2482
+ const text = output["output"] ?? output["text"] ?? output["result"];
2483
+ if (typeof text === "string") {
2484
+ this.chainOutput = text;
2485
+ }
2486
+ }
2487
+ /**
2488
+ * Build a Trace from accumulated callback events.
2489
+ *
2490
+ * @param options.costUsd - Total cost override.
2491
+ * @param options.model - Model name override.
2492
+ * @returns A finalized Trace object.
2493
+ */
2494
+ buildTrace(options) {
2495
+ const builder = new TraceBuilder(this.agentId);
2496
+ const latencyMs = this.startTime > 0 ? Date.now() - this.startTime : void 0;
2497
+ let totalTokens;
2498
+ for (const llm of this.llmCalls) {
2499
+ const args = {};
2500
+ if (llm.model ?? options?.model) args.model = llm.model ?? options?.model;
2501
+ const result = { completion: llm.text };
2502
+ if (llm.tokens !== void 0) {
2503
+ result.tokens = llm.tokens;
2504
+ totalTokens = (totalTokens ?? 0) + llm.tokens;
2505
+ }
2506
+ builder.addLlmCall(llm.name, { args, result });
2507
+ }
2508
+ for (const tool of this.toolCalls) {
2509
+ builder.addToolCall(tool.name, {
2510
+ result: { output: tool.output }
2511
+ });
2512
+ }
2513
+ const outputMessage = this.chainOutput || (this.llmCalls.length > 0 ? this.llmCalls[this.llmCalls.length - 1].text : "");
2514
+ builder.setOutput({ message: outputMessage });
2515
+ builder.setMetadata({
2516
+ total_tokens: totalTokens,
2517
+ cost_usd: options?.costUsd,
2518
+ latency_ms: latencyMs,
2519
+ model: options?.model ?? this.llmCalls[0]?.model
2520
+ });
2521
+ return builder.build();
2522
+ }
2523
+ };
2524
+ // Annotate the CommonJS export names for ESM import in node:
2525
+ 0 && (module.exports = {
2526
+ ADVERSARIAL_USER,
2527
+ Agent,
2528
+ AgentResult,
2529
+ AlertDispatcher,
2530
+ AnthropicAdapter,
2531
+ AttestClient,
2532
+ CONFUSED_USER,
2533
+ COOPERATIVE_USER,
2534
+ ContinuousEvalRunner,
2535
+ ENGINE_VERSION,
2536
+ ERR_ASSERTION_ERROR,
2537
+ ERR_ENGINE_ERROR,
2538
+ ERR_INVALID_TRACE,
2539
+ ERR_PROVIDER_ERROR,
2540
+ ERR_SESSION_ERROR,
2541
+ ERR_TIMEOUT,
2542
+ EngineManager,
2543
+ EngineTimeoutError,
2544
+ ExpectChain,
2545
+ FRIENDLY_USER,
2546
+ GeminiAdapter,
2547
+ LangChainAdapter,
2548
+ ManualAdapter,
2549
+ OTelAdapter,
2550
+ OllamaAdapter,
2551
+ OpenAIAdapter,
2552
+ PluginRegistry,
2553
+ ProtocolError,
2554
+ STATUS_HARD_FAIL,
2555
+ STATUS_PASS,
2556
+ STATUS_SOFT_FAIL,
2557
+ STEP_AGENT_CALL,
2558
+ STEP_LLM_CALL,
2559
+ STEP_RETRIEVAL,
2560
+ STEP_TOOL_CALL,
2561
+ Sampler,
2562
+ TIER_1,
2563
+ TIER_2,
2564
+ TIER_3,
2565
+ TYPE_CONSTRAINT,
2566
+ TYPE_CONTENT,
2567
+ TYPE_EMBEDDING,
2568
+ TYPE_LLM_JUDGE,
2569
+ TYPE_PLUGIN,
2570
+ TYPE_SCHEMA,
2571
+ TYPE_TRACE,
2572
+ TYPE_TRACE_TREE,
2573
+ TraceBuilder,
2574
+ TraceTree,
2575
+ VERSION,
2576
+ activeBuilder,
2577
+ agent,
2578
+ agentId,
2579
+ assertionId,
2580
+ attestExpect,
2581
+ config,
2582
+ decodeResponse,
2583
+ delegate,
2584
+ encodeRequest,
2585
+ executePluginAssertion,
2586
+ extractId,
2587
+ extractResult,
2588
+ getSampleRate,
2589
+ isSimulationMode,
2590
+ resetConfig,
2591
+ simulationEvaluateBatch,
2592
+ tier,
2593
+ traceId
2594
+ });
2595
+ //# sourceMappingURL=index.cjs.map