@vitest-evals/harness-pi-ai 0.9.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,657 @@
1
+ // src/index.ts
2
+ import {
3
+ attachHarnessRunToError,
4
+ isHarnessRun,
5
+ isNormalizedSession,
6
+ normalizeContent,
7
+ resolveHarnessRunErrors,
8
+ serializeError,
9
+ toJsonValue
10
+ } from "vitest-evals/harness";
11
+ import {
12
+ executeWithReplay,
13
+ getReplayMetadataFromError,
14
+ normalizeReplayMetadata
15
+ } from "vitest-evals/replay";
16
+ var ORIGINAL_NATIVE_EXECUTE = Symbol("vitest-evals.originalNativeExecute");
17
+ function piAiHarness(options) {
18
+ return {
19
+ name: options.name ?? "pi-ai",
20
+ run: async (input, context) => {
21
+ const agent = await resolveAgent(options);
22
+ const messages = [
23
+ {
24
+ role: "user",
25
+ content: normalizeContent(input)
26
+ }
27
+ ];
28
+ const inferredTools = resolveInferredToolSurfaces(
29
+ agent
30
+ );
31
+ if (hasExplicitToolset(options)) {
32
+ return executePiHarnessRun(
33
+ options,
34
+ agent,
35
+ input,
36
+ context,
37
+ messages,
38
+ options.tools,
39
+ inferredTools.nativeToolsets
40
+ );
41
+ }
42
+ return executePiHarnessRun(
43
+ options,
44
+ agent,
45
+ input,
46
+ context,
47
+ messages,
48
+ inferredTools.runtimeTools,
49
+ inferredTools.nativeToolsets
50
+ );
51
+ }
52
+ };
53
+ }
54
+ async function executePiHarnessRun(options, agent, input, context, messages, runtimeTools, nativeToolsets) {
55
+ const runtime = createRuntime({
56
+ input,
57
+ context,
58
+ tools: runtimeTools,
59
+ messages
60
+ });
61
+ try {
62
+ const result = await withInstrumentedAgentTools(
63
+ agent,
64
+ nativeToolsets,
65
+ {
66
+ input,
67
+ context,
68
+ messages,
69
+ toolCalls: runtime.toolCalls
70
+ },
71
+ () => runAgent(options, {
72
+ agent,
73
+ input,
74
+ context,
75
+ runtime
76
+ })
77
+ );
78
+ if (isHarnessRun(result) && !hasResultOverrides(options)) {
79
+ if (Object.keys(context.artifacts).length > 0 && !result.artifacts) {
80
+ result.artifacts = context.artifacts;
81
+ }
82
+ return result;
83
+ }
84
+ const normalizeResult = result;
85
+ const resultArgs = {
86
+ agent,
87
+ input,
88
+ context,
89
+ runtime,
90
+ result: normalizeResult
91
+ };
92
+ const output = options.normalize?.output ? await options.normalize.output(resultArgs) : resolveOutput(normalizeResult);
93
+ const usage = options.normalize?.usage ? await options.normalize.usage(resultArgs) : resolveUsage(normalizeResult, runtime.toolCalls.length);
94
+ const session = options.normalize?.session ? await options.normalize.session(resultArgs) : resolveSession(normalizeResult, messages, output, usage);
95
+ return {
96
+ session,
97
+ output,
98
+ usage,
99
+ timings: options.normalize?.timings ? await options.normalize.timings(resultArgs) : void 0,
100
+ artifacts: Object.keys(context.artifacts).length > 0 ? context.artifacts : void 0,
101
+ errors: options.normalize?.errors ? await options.normalize.errors(resultArgs) : resolveErrors(normalizeResult)
102
+ };
103
+ } catch (error) {
104
+ const usage = resolveUsage(void 0, runtime.toolCalls.length);
105
+ const run = {
106
+ session: resolveSession(void 0, messages, void 0, usage),
107
+ output: void 0,
108
+ usage,
109
+ artifacts: Object.keys(context.artifacts).length > 0 ? context.artifacts : void 0,
110
+ errors: [serializeError(error)]
111
+ };
112
+ throw attachHarnessRunToError(error, run);
113
+ }
114
+ }
115
+ async function resolveAgent(options) {
116
+ if (options.agent !== void 0) {
117
+ return options.agent;
118
+ }
119
+ if (options.createAgent) {
120
+ return options.createAgent();
121
+ }
122
+ throw new Error(
123
+ "piAiHarness requires either an agent instance or a createAgent() function."
124
+ );
125
+ }
126
+ function hasResultOverrides(options) {
127
+ return Boolean(
128
+ options.normalize?.output ?? options.normalize?.session ?? options.normalize?.usage ?? options.normalize?.timings ?? options.normalize?.errors
129
+ );
130
+ }
131
+ function resolveInferredToolSurfaces(agent) {
132
+ let runtimeTools;
133
+ const nativeToolsets = [];
134
+ const seenToolsets = /* @__PURE__ */ new Set();
135
+ for (const candidate of getAgentToolCandidates(agent)) {
136
+ const nextRuntimeTools = getRuntimeToolset(candidate);
137
+ if (runtimeTools === void 0 && nextRuntimeTools !== void 0) {
138
+ runtimeTools = nextRuntimeTools;
139
+ }
140
+ const nativeTools = getNativeToolArray(candidate);
141
+ if (nativeTools && !seenToolsets.has(nativeTools)) {
142
+ seenToolsets.add(nativeTools);
143
+ nativeToolsets.push(nativeTools);
144
+ }
145
+ }
146
+ return {
147
+ ...runtimeTools ? { runtimeTools } : {},
148
+ ...nativeToolsets.length > 0 ? { nativeToolsets } : {}
149
+ };
150
+ }
151
+ function getAgentToolCandidates(agent) {
152
+ const roots = getAgentRoots(agent);
153
+ const candidates = [];
154
+ const seen = /* @__PURE__ */ new Set();
155
+ for (const root of roots) {
156
+ addUniqueObject(candidates, seen, root);
157
+ addUniqueObject(candidates, seen, getObjectProperty(root, "state"));
158
+ addUniqueObject(candidates, seen, getObjectProperty(root, "initialState"));
159
+ }
160
+ return candidates;
161
+ }
162
+ function getAgentRoots(agent) {
163
+ return [asObject(agent)].concat(asObject(getObjectProperty(agent, "agent"))).filter((value) => value !== void 0);
164
+ }
165
+ function addUniqueObject(candidates, seen, value) {
166
+ if (!value || typeof value !== "object" || seen.has(value)) {
167
+ return;
168
+ }
169
+ seen.add(value);
170
+ candidates.push(value);
171
+ }
172
+ function asObject(value) {
173
+ return value && typeof value === "object" ? value : void 0;
174
+ }
175
+ function getObjectProperty(value, key) {
176
+ return value && typeof value === "object" ? value[key] : void 0;
177
+ }
178
+ function getRuntimeToolset(value) {
179
+ const candidate = getObjectProperty(value, "tools") ?? getObjectProperty(value, "toolset");
180
+ return isPiAiToolset(candidate) ? candidate : void 0;
181
+ }
182
+ function getNativeToolArray(value) {
183
+ const candidate = getObjectProperty(value, "tools");
184
+ if (isAgentToolArray(candidate)) {
185
+ return candidate;
186
+ }
187
+ return void 0;
188
+ }
189
+ async function runAgent(options, args) {
190
+ if (options.run) {
191
+ return options.run(args);
192
+ }
193
+ if (hasPiAiRunMethod(args.agent)) {
194
+ return args.agent.run(args.input, args.runtime);
195
+ }
196
+ throw new Error(
197
+ "piAiHarness requires a run() function unless the provided agent exposes run(input, runtime)."
198
+ );
199
+ }
200
+ function hasExplicitToolset(options) {
201
+ return options.tools !== void 0;
202
+ }
203
+ function hasPiAiRunMethod(agent) {
204
+ if (!agent || typeof agent !== "object") {
205
+ return false;
206
+ }
207
+ return "run" in agent && typeof agent.run === "function";
208
+ }
209
+ function isPiAiToolset(value) {
210
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
211
+ return false;
212
+ }
213
+ const tools = Object.values(value);
214
+ return tools.length > 0 && tools.every(
215
+ (tool) => Boolean(
216
+ tool && typeof tool === "object" && "execute" in tool && typeof tool.execute === "function"
217
+ )
218
+ );
219
+ }
220
+ function isAgentToolArray(value) {
221
+ return Array.isArray(value) && value.every(
222
+ (tool) => Boolean(
223
+ tool && typeof tool === "object" && "name" in tool && typeof tool.name === "string" && "execute" in tool && typeof tool.execute === "function"
224
+ )
225
+ );
226
+ }
227
+ async function withInstrumentedAgentTools(agent, toolsets, args, callback) {
228
+ if (!toolsets || toolsets.length === 0) {
229
+ return callback();
230
+ }
231
+ const originalExecutions = /* @__PURE__ */ new Map();
232
+ const originalResets = /* @__PURE__ */ new Map();
233
+ const patchTool = (tool) => {
234
+ if (originalExecutions.has(tool)) {
235
+ return;
236
+ }
237
+ const originalExecute = getNativeToolExecuteOrigin(tool.execute);
238
+ originalExecutions.set(tool, originalExecute);
239
+ const instrumentedExecute = async (toolCallId, rawArgs) => {
240
+ const startedAt = /* @__PURE__ */ new Date();
241
+ const toolContext = {
242
+ input: args.input,
243
+ metadata: args.context.metadata,
244
+ signal: args.context.signal,
245
+ setArtifact: args.context.setArtifact
246
+ };
247
+ try {
248
+ const execution = await executeNativeToolWithReplay({
249
+ toolName: tool.name,
250
+ toolCallId,
251
+ execute: originalExecute,
252
+ replay: tool.replay,
253
+ args: rawArgs,
254
+ context: toolContext
255
+ });
256
+ const finishedAt = /* @__PURE__ */ new Date();
257
+ const call = {
258
+ name: tool.name,
259
+ arguments: rawArgs,
260
+ result: execution.normalizedResult,
261
+ startedAt: startedAt.toISOString(),
262
+ finishedAt: finishedAt.toISOString(),
263
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
264
+ metadata: normalizeReplayMetadata(execution.replay)
265
+ };
266
+ args.toolCalls.push(call);
267
+ args.messages.push({
268
+ role: "assistant",
269
+ toolCalls: [call]
270
+ });
271
+ args.messages.push({
272
+ role: "tool",
273
+ content: execution.normalizedResult,
274
+ metadata: {
275
+ name: tool.name
276
+ }
277
+ });
278
+ return execution.result;
279
+ } catch (error) {
280
+ const finishedAt = /* @__PURE__ */ new Date();
281
+ const call = {
282
+ name: tool.name,
283
+ arguments: rawArgs,
284
+ error: serializeToolCallError(error),
285
+ startedAt: startedAt.toISOString(),
286
+ finishedAt: finishedAt.toISOString(),
287
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
288
+ metadata: normalizeReplayMetadata(getReplayMetadataFromError(error))
289
+ };
290
+ args.toolCalls.push(call);
291
+ args.messages.push({
292
+ role: "assistant",
293
+ toolCalls: [call]
294
+ });
295
+ throw error;
296
+ }
297
+ };
298
+ instrumentedExecute[ORIGINAL_NATIVE_EXECUTE] = originalExecute;
299
+ tool.execute = instrumentedExecute;
300
+ };
301
+ const patchToolsets = (nextToolsets) => {
302
+ for (const toolset of nextToolsets) {
303
+ for (const tool of toolset) {
304
+ patchTool(tool);
305
+ }
306
+ }
307
+ };
308
+ patchToolsets(toolsets);
309
+ for (const target of getAgentResetTargets(agent)) {
310
+ const originalReset = target.reset;
311
+ originalResets.set(target, originalReset);
312
+ target.reset = function patchedReset(...resetArgs) {
313
+ const resetResult = originalReset.apply(this, resetArgs);
314
+ if (isPromiseLike(resetResult)) {
315
+ return resetResult.finally(() => {
316
+ patchToolsets(
317
+ resolveInferredNativeToolsets(agent)
318
+ );
319
+ });
320
+ }
321
+ patchToolsets(resolveInferredNativeToolsets(agent));
322
+ return resetResult;
323
+ };
324
+ }
325
+ try {
326
+ return await callback();
327
+ } finally {
328
+ for (const [target, originalReset] of originalResets) {
329
+ target.reset = originalReset;
330
+ }
331
+ for (const [tool, originalExecute] of originalExecutions) {
332
+ tool.execute = originalExecute;
333
+ }
334
+ }
335
+ }
336
+ function getAgentResetTargets(agent) {
337
+ return getAgentRoots(agent).filter(isResettableAgent);
338
+ }
339
+ function isResettableAgent(value) {
340
+ return "reset" in value && typeof value.reset === "function";
341
+ }
342
+ function resolveInferredNativeToolsets(agent) {
343
+ const toolsets = [];
344
+ const seenToolsets = /* @__PURE__ */ new Set();
345
+ for (const candidate of getAgentToolCandidates(agent)) {
346
+ const nativeTools = getNativeToolArray(candidate);
347
+ if (nativeTools && !seenToolsets.has(nativeTools)) {
348
+ seenToolsets.add(nativeTools);
349
+ toolsets.push(nativeTools);
350
+ }
351
+ }
352
+ return toolsets;
353
+ }
354
+ function isPromiseLike(value) {
355
+ return Boolean(
356
+ value && typeof value.then === "function"
357
+ );
358
+ }
359
+ function serializeToolCallError(error) {
360
+ const serialized = serializeError(error);
361
+ const { message, type, ...details } = serialized;
362
+ return {
363
+ ...details,
364
+ message: typeof message === "string" ? message : String(message),
365
+ ...typeof type === "string" ? { type } : {}
366
+ };
367
+ }
368
+ function getNativeToolExecuteOrigin(execute) {
369
+ const nativeExecute = execute;
370
+ return nativeExecute[ORIGINAL_NATIVE_EXECUTE] ?? nativeExecute;
371
+ }
372
+ async function executeNativeToolWithReplay({
373
+ toolName,
374
+ toolCallId,
375
+ execute,
376
+ replay,
377
+ args,
378
+ context
379
+ }) {
380
+ let didExecute = false;
381
+ let liveResult;
382
+ const execution = await executeWithReplay({
383
+ toolName,
384
+ args,
385
+ context,
386
+ execute: async (toolArgs) => {
387
+ didExecute = true;
388
+ liveResult = await execute(toolCallId, toolArgs);
389
+ return createNativeToolReplayEnvelope(liveResult);
390
+ },
391
+ replay
392
+ });
393
+ if (didExecute) {
394
+ return {
395
+ result: liveResult,
396
+ normalizedResult: normalizeReplayToolResult(liveResult),
397
+ replay: execution.replay
398
+ };
399
+ }
400
+ return {
401
+ ...resolveNativeToolReplayResult(execution.result),
402
+ replay: execution.replay
403
+ };
404
+ }
405
+ function createRuntime({
406
+ input,
407
+ context,
408
+ tools,
409
+ messages
410
+ }) {
411
+ const toolCalls = [];
412
+ const eventSink = {
413
+ message: (message) => {
414
+ messages.push(message);
415
+ },
416
+ system: (content, metadata) => {
417
+ messages.push({
418
+ role: "system",
419
+ content,
420
+ metadata
421
+ });
422
+ },
423
+ user: (content, metadata) => {
424
+ messages.push({
425
+ role: "user",
426
+ content,
427
+ metadata
428
+ });
429
+ },
430
+ assistant: (content, metadata) => {
431
+ messages.push({
432
+ role: "assistant",
433
+ content,
434
+ metadata
435
+ });
436
+ },
437
+ tool: (name, content, metadata) => {
438
+ messages.push({
439
+ role: "tool",
440
+ content,
441
+ metadata: {
442
+ name,
443
+ ...metadata ?? {}
444
+ }
445
+ });
446
+ }
447
+ };
448
+ const runtimeTools = Object.fromEntries(
449
+ Object.entries(tools ?? {}).map(([toolName, tool]) => [
450
+ toolName,
451
+ async (args) => {
452
+ const startedAt = /* @__PURE__ */ new Date();
453
+ const toolContext = {
454
+ input,
455
+ metadata: context.metadata,
456
+ signal: context.signal,
457
+ setArtifact: context.setArtifact
458
+ };
459
+ try {
460
+ const execution = await executeToolWithReplay({
461
+ toolName,
462
+ tool,
463
+ args,
464
+ context: toolContext
465
+ });
466
+ const finishedAt = /* @__PURE__ */ new Date();
467
+ const call = {
468
+ name: toolName,
469
+ arguments: args,
470
+ result: execution.result,
471
+ startedAt: startedAt.toISOString(),
472
+ finishedAt: finishedAt.toISOString(),
473
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
474
+ metadata: normalizeReplayMetadata(execution.replay)
475
+ };
476
+ toolCalls.push(call);
477
+ messages.push({
478
+ role: "assistant",
479
+ toolCalls: [call]
480
+ });
481
+ messages.push({
482
+ role: "tool",
483
+ content: execution.result,
484
+ metadata: {
485
+ name: toolName
486
+ }
487
+ });
488
+ return execution.result;
489
+ } catch (error) {
490
+ const finishedAt = /* @__PURE__ */ new Date();
491
+ const call = {
492
+ name: toolName,
493
+ arguments: args,
494
+ error: serializeToolCallError(error),
495
+ startedAt: startedAt.toISOString(),
496
+ finishedAt: finishedAt.toISOString(),
497
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
498
+ metadata: normalizeReplayMetadata(
499
+ getReplayMetadataFromError(error)
500
+ )
501
+ };
502
+ toolCalls.push(call);
503
+ messages.push({
504
+ role: "assistant",
505
+ toolCalls: [call]
506
+ });
507
+ throw error;
508
+ }
509
+ }
510
+ ])
511
+ );
512
+ return {
513
+ tools: runtimeTools,
514
+ events: eventSink,
515
+ signal: context.signal,
516
+ toolCalls
517
+ };
518
+ }
519
+ function resolveOutput(result) {
520
+ if (!result || typeof result !== "object") {
521
+ return toJsonValue(result);
522
+ }
523
+ const candidates = [
524
+ "output",
525
+ "decision",
526
+ "result",
527
+ "final"
528
+ ];
529
+ for (const key of candidates) {
530
+ const value = result[key];
531
+ const normalized = toJsonValue(value);
532
+ if (normalized !== void 0) {
533
+ return normalized;
534
+ }
535
+ }
536
+ return void 0;
537
+ }
538
+ function normalizeToolResult(result) {
539
+ const details = result && typeof result === "object" ? toJsonValue(result.details) : void 0;
540
+ if (details !== void 0) {
541
+ return details;
542
+ }
543
+ return toJsonValue(result) ?? (result === void 0 ? void 0 : String(result));
544
+ }
545
+ function normalizeReplayToolResult(result) {
546
+ return normalizeToolResult(result) ?? null;
547
+ }
548
+ function createNativeToolReplayEnvelope(result) {
549
+ const normalizedResult = normalizeReplayToolResult(result);
550
+ return {
551
+ __vitestEvals: {
552
+ kind: "pi-ai-native-tool-result",
553
+ version: 2
554
+ },
555
+ agentResult: toJsonValue(result) ?? normalizedResult,
556
+ normalizedResult
557
+ };
558
+ }
559
+ function resolveNativeToolReplayResult(result) {
560
+ if (isNativeToolReplayEnvelope(result)) {
561
+ return {
562
+ result: result.agentResult,
563
+ normalizedResult: result.normalizedResult
564
+ };
565
+ }
566
+ if (isLegacyNativeToolReplayEnvelope(result)) {
567
+ return {
568
+ result: result.agentResult ?? result.normalizedResult,
569
+ normalizedResult: result.normalizedResult
570
+ };
571
+ }
572
+ return {
573
+ result,
574
+ normalizedResult: normalizeReplayToolResult(result)
575
+ };
576
+ }
577
+ function isNativeToolReplayEnvelope(value) {
578
+ return Boolean(
579
+ value && typeof value === "object" && "__vitestEvals" in value && isNativeToolReplayMarker(
580
+ value.__vitestEvals
581
+ ) && "agentResult" in value && "normalizedResult" in value
582
+ );
583
+ }
584
+ function isNativeToolReplayMarker(value) {
585
+ return Boolean(
586
+ value && typeof value === "object" && "kind" in value && value.kind === "pi-ai-native-tool-result" && "version" in value && value.version === 2
587
+ );
588
+ }
589
+ function isLegacyNativeToolReplayEnvelope(value) {
590
+ return Boolean(
591
+ value && typeof value === "object" && "__vitestEvals" in value && isLegacyNativeToolReplayMarker(
592
+ value.__vitestEvals
593
+ ) && "normalizedResult" in value
594
+ );
595
+ }
596
+ function isLegacyNativeToolReplayMarker(value) {
597
+ return Boolean(
598
+ value && typeof value === "object" && "kind" in value && value.kind === "pi-ai-native-tool-result" && "version" in value && value.version === 1
599
+ );
600
+ }
601
+ function resolveUsage(result, toolCallCount) {
602
+ if (!result || typeof result !== "object") {
603
+ return toolCallCount > 0 ? { toolCalls: toolCallCount } : {};
604
+ }
605
+ const usageValue = result.usage ?? result.metrics;
606
+ const usage = usageValue && typeof usageValue === "object" ? { ...usageValue } : {};
607
+ if (usage.toolCalls === void 0 && toolCallCount > 0) {
608
+ usage.toolCalls = toolCallCount;
609
+ }
610
+ return usage;
611
+ }
612
+ function resolveSession(result, messages, output, usage) {
613
+ if (isNormalizedSession(
614
+ result?.session
615
+ )) {
616
+ return result.session;
617
+ }
618
+ if (isNormalizedSession(result?.trace)) {
619
+ return result.trace;
620
+ }
621
+ const sessionMessages = [...messages];
622
+ if (output !== void 0 && !sessionMessages.some(
623
+ (message) => message.role === "assistant" && message.content !== void 0
624
+ )) {
625
+ sessionMessages.push({
626
+ role: "assistant",
627
+ content: output
628
+ });
629
+ }
630
+ return {
631
+ messages: sessionMessages,
632
+ outputText: typeof output === "string" ? output : void 0,
633
+ provider: result?.provider ?? usage.provider,
634
+ model: result?.model ?? usage.model
635
+ };
636
+ }
637
+ function resolveErrors(result) {
638
+ return resolveHarnessRunErrors(result);
639
+ }
640
+ async function executeToolWithReplay({
641
+ toolName,
642
+ tool,
643
+ args,
644
+ context
645
+ }) {
646
+ return executeWithReplay({
647
+ toolName,
648
+ args,
649
+ context,
650
+ execute: tool.execute,
651
+ replay: tool.replay
652
+ });
653
+ }
654
+ export {
655
+ piAiHarness
656
+ };
657
+ //# sourceMappingURL=index.mjs.map