@vitest-evals/harness-ai-sdk 0.13.1 → 0.15.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/README.md CHANGED
@@ -108,8 +108,9 @@ describeEval("refund agent", {
108
108
 
109
109
  The adapter infers:
110
110
 
111
- - normalized session and tool-call traces from AI SDK `steps`
112
- - usage diagnostics from `totalUsage` / `usage`
111
+ - normalized session transcripts and tool calls from AI SDK `steps`
112
+ - usage diagnostics from `totalUsage`, aggregated `steps[].usage`, or
113
+ top-level `usage` for non-step results
113
114
  - typed `run.output` from explicit `run()` results that return `output`, from
114
115
  common AI SDK provider fields such as `object` and `text`, or from a typed
115
116
  `output` selector when the app deliberately returns a raw provider result
@@ -118,5 +119,10 @@ The adapter infers:
118
119
  `output` selector
119
120
  - replay/cassette metadata for local tools configured with `toolReplay`
120
121
 
122
+ Successful custom `run` entrypoints that do not return AI SDK `steps` use
123
+ normalized transcript events for local tool executions. Output-only custom runs
124
+ still get synthesized input/output messages; return a normalized `session` when
125
+ evals need exact transcript control beyond that.
126
+
121
127
  See the workspace demo app in `apps/demo-ai-sdk` and the RFC notes in
122
128
  `docs/harness-first-rfc.md`.
package/dist/index.js CHANGED
@@ -112,14 +112,14 @@ function createFailedAiSdkRun(input, context, error, harnessName, startedAt) {
112
112
  async function runAiSdkHarness(options, agent, input, context) {
113
113
  const trace = createTraceRecorder(options.name ?? "ai-sdk");
114
114
  const replayMetadataByToolCallId = /* @__PURE__ */ new Map();
115
- const runtimeToolCalls = [];
115
+ const runtimeEvents = [];
116
116
  const tools = createToolset({
117
117
  input,
118
118
  context,
119
119
  tools: options.tools,
120
120
  toolReplay: options.toolReplay,
121
121
  replayMetadataByToolCallId,
122
- runtimeToolCalls
122
+ runtimeEvents
123
123
  });
124
124
  const runtime = {
125
125
  tools,
@@ -149,13 +149,20 @@ async function runAiSdkHarness(options, agent, input, context) {
149
149
  result
150
150
  };
151
151
  const output = options.output ? await options.output(resultArgs) : resolveOutput(result);
152
- const usage = resolveUsage(result, runtimeToolCalls.length);
152
+ const explicitSession = getResultSession(result);
153
+ const useRuntimeEvents = shouldUseRuntimeEvents(result);
154
+ const usage = resolveUsage(
155
+ result,
156
+ useRuntimeEvents ? runtimeEvents : [],
157
+ explicitSession
158
+ );
153
159
  const session = resolveSession(
154
160
  input,
155
161
  result,
156
162
  output,
157
163
  replayMetadataByToolCallId,
158
- runtimeToolCalls
164
+ runtimeEvents,
165
+ explicitSession
159
166
  );
160
167
  const errors = (0, import_harness.resolveHarnessRunErrors)(result);
161
168
  const finishedAt = /* @__PURE__ */ new Date();
@@ -178,14 +185,11 @@ async function runAiSdkHarness(options, agent, input, context) {
178
185
  } catch (error) {
179
186
  const finishedAt = /* @__PURE__ */ new Date();
180
187
  const serializedError = (0, import_harness.serializeError)(error);
181
- const usage = runtimeToolCalls.length > 0 ? { toolCalls: runtimeToolCalls.length } : {};
182
- const session = resolveSession(
183
- input,
184
- void 0,
185
- void 0,
186
- replayMetadataByToolCallId,
187
- runtimeToolCalls
188
- );
188
+ const runtimeToolCallCount = runtimeEvents.filter(
189
+ (event) => event.type === "tool_call"
190
+ ).length;
191
+ const usage = runtimeToolCallCount > 0 ? { toolCalls: runtimeToolCallCount } : {};
192
+ const session = resolveFailureSession(input, runtimeEvents);
189
193
  const run = {
190
194
  session,
191
195
  output: void 0,
@@ -283,11 +287,6 @@ function finishAiSdkTrace(trace, options) {
283
287
  options.result,
284
288
  options.usage
285
289
  );
286
- const toolSpans = (0, import_harness.createToolCallSpans)((0, import_harness.toolCalls)(options.session), {
287
- traceId: trace.id,
288
- parentId: trace.rootSpanId,
289
- spanIdPrefix: `${trace.id}:tool`
290
- });
291
290
  const finishedAt = options.finishedAt;
292
291
  const durationMs = finishedAt.getTime() - trace.startedAt.getTime();
293
292
  const rootError = options.errors?.[0] ? (0, import_harness.normalizeSpanError)(options.errors[0]) : void 0;
@@ -307,7 +306,7 @@ function finishAiSdkTrace(trace, options) {
307
306
  ...(0, import_harness.createGenAiUsageAttributes)(options.usage)
308
307
  })
309
308
  };
310
- const spans = [rootSpan, ...modelSpans, ...toolSpans];
309
+ const spans = [rootSpan, ...modelSpans];
311
310
  return {
312
311
  id: trace.id,
313
312
  name: trace.name,
@@ -321,7 +320,7 @@ function finishAiSdkTrace(trace, options) {
321
320
  };
322
321
  }
323
322
  function createAiSdkModelSpans(trace, result, usage) {
324
- const steps = resolveSteps(result);
323
+ const steps = readAiSdkSteps(result);
325
324
  if (steps.length === 0) {
326
325
  const fallback = createUsageModelSpan(trace, usage);
327
326
  return fallback ? [fallback] : [];
@@ -376,7 +375,7 @@ function createToolset({
376
375
  tools,
377
376
  toolReplay,
378
377
  replayMetadataByToolCallId,
379
- runtimeToolCalls
378
+ runtimeEvents
380
379
  }) {
381
380
  return Object.fromEntries(
382
381
  Object.entries(tools ?? {}).map(([toolName, tool]) => {
@@ -395,6 +394,14 @@ function createToolset({
395
394
  execute: async (toolInput, execution) => {
396
395
  const startedAt = /* @__PURE__ */ new Date();
397
396
  const normalizedArgs = normalizeArguments(toolInput);
397
+ const call = {
398
+ type: "tool_call",
399
+ id: execution.toolCallId,
400
+ name: toolName,
401
+ ...normalizedArgs ? { arguments: normalizedArgs } : {},
402
+ startedAt: startedAt.toISOString()
403
+ };
404
+ runtimeEvents.push(call);
398
405
  const replayContext = {
399
406
  input,
400
407
  signal: context.signal,
@@ -414,7 +421,6 @@ function createToolset({
414
421
  replay: void 0
415
422
  };
416
423
  const finishedAt = /* @__PURE__ */ new Date();
417
- const normalizedResult = (0, import_harness.toJsonValue)(executionResult.result);
418
424
  const replayMetadata = (0, import_replay.normalizeReplayMetadata)(
419
425
  executionResult.replay
420
426
  );
@@ -424,15 +430,20 @@ function createToolset({
424
430
  executionResult.replay
425
431
  );
426
432
  }
427
- runtimeToolCalls.push({
428
- id: execution.toolCallId,
433
+ call.finishedAt = finishedAt.toISOString();
434
+ call.durationMs = finishedAt.getTime() - startedAt.getTime();
435
+ if (replayMetadata) {
436
+ call.metadata = replayMetadata;
437
+ }
438
+ const normalizedResult = (0, import_harness.toJsonValue)(executionResult.result);
439
+ runtimeEvents.push({
440
+ type: "tool_result",
441
+ toolCallId: execution.toolCallId,
429
442
  name: toolName,
430
- ...normalizedArgs ? { arguments: normalizedArgs } : {},
431
- ...normalizedResult !== void 0 ? { result: normalizedResult } : {},
432
- startedAt: startedAt.toISOString(),
433
- finishedAt: finishedAt.toISOString(),
434
- durationMs: finishedAt.getTime() - startedAt.getTime(),
435
- ...replayMetadata ? { metadata: replayMetadata } : {}
443
+ ...normalizedResult !== void 0 ? { content: normalizedResult } : {},
444
+ startedAt: call.startedAt,
445
+ finishedAt: call.finishedAt,
446
+ durationMs: call.durationMs
436
447
  });
437
448
  return executionResult.result;
438
449
  } catch (error) {
@@ -442,15 +453,19 @@ function createToolset({
442
453
  if (replay2) {
443
454
  replayMetadataByToolCallId.set(execution.toolCallId, replay2);
444
455
  }
445
- runtimeToolCalls.push({
446
- id: execution.toolCallId,
456
+ call.finishedAt = finishedAt.toISOString();
457
+ call.durationMs = finishedAt.getTime() - startedAt.getTime();
458
+ if (replayMetadata) {
459
+ call.metadata = replayMetadata;
460
+ }
461
+ runtimeEvents.push({
462
+ type: "tool_result",
463
+ toolCallId: execution.toolCallId,
447
464
  name: toolName,
448
- ...normalizedArgs ? { arguments: normalizedArgs } : {},
449
465
  error: normalizeError(error),
450
- startedAt: startedAt.toISOString(),
451
- finishedAt: finishedAt.toISOString(),
452
- durationMs: finishedAt.getTime() - startedAt.getTime(),
453
- ...replayMetadata ? { metadata: replayMetadata } : {}
466
+ startedAt: call.startedAt,
467
+ finishedAt: call.finishedAt,
468
+ durationMs: call.durationMs
454
469
  });
455
470
  throw error;
456
471
  }
@@ -539,26 +554,26 @@ function toOutputValue(value) {
539
554
  }
540
555
  return void 0;
541
556
  }
542
- function resolveUsage(result, runtimeToolCallCount = 0) {
543
- const steps = resolveSteps(result);
544
- const usage = resolveLanguageModelUsage(result) ?? resolveStepUsage(steps);
557
+ function resolveUsage(result, runtimeEvents = [], explicitSession) {
558
+ const steps = readAiSdkSteps(result);
559
+ const runtimeToolCallCount = countRuntimeToolCalls(runtimeEvents);
560
+ const explicitSessionToolCallCount = explicitSession ? countSessionToolCalls(explicitSession) : void 0;
561
+ const usage = readAiSdkUsage(result, steps);
545
562
  const lastStep = steps.length > 0 ? steps[steps.length - 1] : void 0;
563
+ const toolCallCount = explicitSessionToolCallCount ?? (steps.length > 0 ? countStepToolCalls(steps) : runtimeToolCallCount);
546
564
  if (!usage) {
547
- if (steps.length > 0) {
548
- const toolCallCount2 = countStepToolCalls(steps);
565
+ if (toolCallCount > 0 || steps.length > 0) {
549
566
  return {
550
- provider: lastStep?.model.provider,
551
- model: lastStep?.model.modelId,
552
- ...toolCallCount2 > 0 ? { toolCalls: toolCallCount2 } : {}
567
+ provider: lastStep?.model?.provider,
568
+ model: lastStep?.model?.modelId,
569
+ ...toolCallCount > 0 ? { toolCalls: toolCallCount } : {}
553
570
  };
554
571
  }
555
- return runtimeToolCallCount > 0 ? { toolCalls: runtimeToolCallCount } : {};
572
+ return {};
556
573
  }
557
- const stepToolCallCount = countStepToolCalls(steps);
558
- const toolCallCount = stepToolCallCount > 0 ? stepToolCallCount : runtimeToolCallCount;
559
574
  return {
560
- provider: lastStep?.model.provider,
561
- model: lastStep?.model.modelId,
575
+ provider: lastStep?.model?.provider,
576
+ model: lastStep?.model?.modelId,
562
577
  inputTokens: usage.inputTokens,
563
578
  outputTokens: usage.outputTokens,
564
579
  reasoningTokens: usage.outputTokenDetails?.reasoningTokens ?? usage.reasoningTokens,
@@ -571,8 +586,11 @@ function resolveUsage(result, runtimeToolCallCount = 0) {
571
586
  })
572
587
  };
573
588
  }
589
+ function countRuntimeToolCalls(events) {
590
+ return events.filter((event) => event.type === "tool_call").length;
591
+ }
574
592
  function resolveStepUsage(steps) {
575
- const usages = steps.map((step) => step.usage).filter((usage) => Boolean(usage));
593
+ const usages = steps.map((step) => step.usage).filter(isLanguageModelUsage);
576
594
  if (usages.length === 0) {
577
595
  return void 0;
578
596
  }
@@ -626,86 +644,125 @@ function countStepToolCalls(steps) {
626
644
  0
627
645
  );
628
646
  }
629
- function resolveSession(input, result, output, replayMetadataByToolCallId, runtimeToolCalls = []) {
647
+ function countSessionToolCalls(session) {
648
+ return session.events.filter((event) => event.type === "tool_call").length;
649
+ }
650
+ function shouldUseRuntimeEvents(result) {
630
651
  if ((0, import_harness.isNormalizedSession)(
631
652
  result?.session
632
653
  )) {
633
- return result.session;
654
+ return false;
634
655
  }
635
- if ((0, import_harness.isNormalizedSession)(result?.trace)) {
636
- return result.trace;
656
+ return readAiSdkSteps(result).length === 0;
657
+ }
658
+ function resolveSession(input, result, output, replayMetadataByToolCallId, runtimeEvents = [], explicitSession = getResultSession(result)) {
659
+ if (explicitSession) {
660
+ return explicitSession;
637
661
  }
638
- const steps = resolveSteps(result);
639
- const messages = [
662
+ const steps = readAiSdkSteps(result);
663
+ const events = [
640
664
  {
665
+ type: "message",
641
666
  role: "user",
642
667
  content: (0, import_harness.normalizeContent)(input)
643
668
  }
644
669
  ];
645
- const stepToolCallIds = /* @__PURE__ */ new Set();
646
670
  for (const step of steps) {
647
- for (const toolCall of step.toolCalls ?? []) {
648
- stepToolCallIds.add(toolCall.toolCallId);
649
- }
650
- messages.push(...normalizeStep(step, replayMetadataByToolCallId));
671
+ events.push(...normalizeStep(step, replayMetadataByToolCallId));
651
672
  }
652
- const unmatchedRuntimeToolCalls = runtimeToolCalls.filter(
653
- (call) => call.id === void 0 || !stepToolCallIds.has(call.id)
654
- );
655
- if (unmatchedRuntimeToolCalls.length > 0) {
656
- messages.push(...normalizeRuntimeToolCalls(unmatchedRuntimeToolCalls));
673
+ if (steps.length === 0) {
674
+ events.push(...runtimeEvents);
657
675
  }
658
- if (output !== void 0 && !messages.some(
659
- (message) => message.role === "assistant" && message.content !== void 0
676
+ if (output !== void 0 && !events.some(
677
+ (event) => event.type === "message" && event.role === "assistant" && event.content !== void 0
660
678
  )) {
661
- messages.push({
679
+ events.push({
680
+ type: "message",
662
681
  role: "assistant",
663
682
  content: output
664
683
  });
665
684
  }
666
685
  const lastStep = steps.length > 0 ? steps[steps.length - 1] : void 0;
667
686
  return {
668
- messages,
669
- provider: lastStep?.model.provider,
670
- model: lastStep?.model.modelId
687
+ events,
688
+ provider: lastStep?.model?.provider,
689
+ model: lastStep?.model?.modelId
671
690
  };
672
691
  }
673
- function normalizeRuntimeToolCalls(runtimeToolCalls) {
674
- const messages = [
675
- {
676
- role: "assistant",
677
- toolCalls: runtimeToolCalls
678
- }
679
- ];
680
- for (const call of runtimeToolCalls) {
681
- if (call.result === void 0 && !call.error) {
682
- continue;
683
- }
684
- const content = call.result !== void 0 ? call.result : call.error && call.error.message.length > 0 ? call.error.message : void 0;
685
- messages.push({
686
- role: "tool",
687
- ...content !== void 0 ? { content } : {},
688
- metadata: (0, import_harness.normalizeMetadata)({
689
- name: call.name,
690
- toolCallId: call.id,
691
- isError: Boolean(call.error)
692
- })
693
- });
694
- }
695
- return messages;
692
+ function getResultSession(result) {
693
+ const session = result?.session;
694
+ return (0, import_harness.isNormalizedSession)(session) ? session : void 0;
696
695
  }
697
- function resolveSteps(result) {
696
+ function resolveFailureSession(input, runtimeEvents) {
697
+ return {
698
+ events: [
699
+ {
700
+ type: "message",
701
+ role: "user",
702
+ content: (0, import_harness.normalizeContent)(input)
703
+ },
704
+ ...runtimeEvents
705
+ ]
706
+ };
707
+ }
708
+ function readAiSdkSteps(result) {
698
709
  if (!result || typeof result !== "object") {
699
710
  return [];
700
711
  }
701
- return Array.isArray(result.steps) ? result.steps ?? [] : [];
712
+ if (!Object.prototype.hasOwnProperty.call(result, "steps")) {
713
+ return [];
714
+ }
715
+ const steps = result.steps;
716
+ if (!Array.isArray(steps)) {
717
+ return [];
718
+ }
719
+ if (steps.length > 0 && !steps.every(isAiSdkStepLike)) {
720
+ return [];
721
+ }
722
+ return steps;
723
+ }
724
+ function isAiSdkStepLike(step) {
725
+ if (!step || typeof step !== "object") {
726
+ return false;
727
+ }
728
+ const record = step;
729
+ return Array.isArray(record.content) || Array.isArray(record.toolCalls) || Array.isArray(record.toolResults) || Boolean(record.response && typeof record.response === "object") || Boolean(record.usage && typeof record.usage === "object") || typeof record.finishReason === "string" || typeof record.stepNumber === "number" || typeof record.text === "string";
730
+ }
731
+ function readAiSdkUsage(result, steps) {
732
+ const totalUsage = readUsageField(result, "totalUsage");
733
+ if (totalUsage) {
734
+ return totalUsage;
735
+ }
736
+ if (steps.length > 0) {
737
+ return resolveStepUsage(steps);
738
+ }
739
+ return readUsageField(result, "usage");
702
740
  }
703
- function resolveLanguageModelUsage(result) {
741
+ function readUsageField(result, field) {
704
742
  if (!result || typeof result !== "object") {
705
743
  return void 0;
706
744
  }
707
- const aiResult = result;
708
- return aiResult.totalUsage ?? aiResult.usage;
745
+ const usage = result[field];
746
+ return isLanguageModelUsage(usage) ? usage : void 0;
747
+ }
748
+ function isLanguageModelUsage(value) {
749
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
750
+ return false;
751
+ }
752
+ const usage = value;
753
+ return isOptionalFiniteNumber(usage.inputTokens) && isOptionalFiniteNumber(usage.outputTokens) && isOptionalFiniteNumber(usage.reasoningTokens) && isOptionalFiniteNumber(usage.totalTokens) && isOptionalFiniteNumber(usage.cachedInputTokens) && isUsageDetailObject(usage.inputTokenDetails) && isUsageDetailObject(usage.outputTokenDetails);
754
+ }
755
+ function isUsageDetailObject(value) {
756
+ if (value === void 0) {
757
+ return true;
758
+ }
759
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
760
+ return false;
761
+ }
762
+ return Object.values(value).every(isOptionalFiniteNumber);
763
+ }
764
+ function isOptionalFiniteNumber(value) {
765
+ return value === void 0 || typeof value === "number" && Number.isFinite(value);
709
766
  }
710
767
  function normalizeStep(step, replayMetadataByToolCallId) {
711
768
  const toolResultsById = new Map(
@@ -714,12 +771,15 @@ function normalizeStep(step, replayMetadataByToolCallId) {
714
771
  toolResult
715
772
  ])
716
773
  );
774
+ const toolErrorsById = new Map(
775
+ (step.toolCalls ?? []).filter((toolCall) => toolCall.invalid || toolCall.error !== void 0).map((toolCall) => [
776
+ toolCall.toolCallId,
777
+ normalizeError(toolCall.error ?? toolCall.invalid)
778
+ ])
779
+ );
717
780
  const normalizedCalls = (step.toolCalls ?? []).map(
718
781
  (toolCall) => normalizeToolCall(toolCall, toolResultsById, replayMetadataByToolCallId)
719
782
  );
720
- const normalizedCallsById = new Map(
721
- normalizedCalls.map((toolCall) => [toolCall.id, toolCall])
722
- );
723
783
  const assistantMetadata = (0, import_harness.normalizeMetadata)({
724
784
  stepNumber: step.stepNumber,
725
785
  finishReason: step.finishReason,
@@ -727,24 +787,25 @@ function normalizeStep(step, replayMetadataByToolCallId) {
727
787
  reasoningText: step.reasoningText,
728
788
  response: step.response
729
789
  });
730
- const messages = [];
731
- if (step.text || normalizedCalls.length > 0 || assistantMetadata) {
732
- messages.push({
790
+ const events = [];
791
+ if (step.text || assistantMetadata) {
792
+ events.push({
793
+ type: "message",
733
794
  role: "assistant",
734
795
  ...step.text ? { content: step.text } : {},
735
- ...normalizedCalls.length > 0 ? { toolCalls: normalizedCalls } : {},
736
796
  ...assistantMetadata ? { metadata: assistantMetadata } : {}
737
797
  });
738
798
  }
799
+ events.push(...normalizedCalls);
739
800
  for (const toolResult of step.toolResults ?? []) {
740
801
  const content = toolResult.output === void 0 ? void 0 : (0, import_harness.normalizeContent)(toolResult.output);
741
- messages.push({
742
- role: "tool",
802
+ events.push({
803
+ type: "tool_result",
804
+ toolCallId: toolResult.toolCallId,
805
+ name: toolResult.toolName,
743
806
  ...content !== void 0 ? { content } : {},
807
+ ...toolErrorsById.has(toolResult.toolCallId) ? { error: toolErrorsById.get(toolResult.toolCallId) } : {},
744
808
  metadata: (0, import_harness.normalizeMetadata)({
745
- name: toolResult.toolName,
746
- toolCallId: toolResult.toolCallId,
747
- isError: Boolean(normalizedCallsById.get(toolResult.toolCallId)?.error),
748
809
  preliminary: toolResult.preliminary,
749
810
  providerExecuted: toolResult.providerExecuted,
750
811
  title: toolResult.title,
@@ -752,27 +813,26 @@ function normalizeStep(step, replayMetadataByToolCallId) {
752
813
  })
753
814
  });
754
815
  }
755
- return messages;
816
+ return events;
756
817
  }
757
818
  function normalizeToolCall(toolCall, toolResultsById, replayMetadataByToolCallId) {
758
819
  const toolResult = toolResultsById.get(toolCall.toolCallId);
759
820
  const normalizedArguments = normalizeArguments(toolCall.input);
760
- const normalizedResult = toolResult !== void 0 ? (0, import_harness.toJsonValue)(toolResult.output) : void 0;
761
821
  const errorValue = toolCall.invalid || toolCall.error !== void 0 ? normalizeError(toolCall.error ?? toolCall.invalid) : void 0;
762
822
  const replayMetadata = (0, import_replay.normalizeReplayMetadata)(
763
823
  replayMetadataByToolCallId.get(toolCall.toolCallId)
764
824
  );
765
825
  return {
826
+ type: "tool_call",
766
827
  id: toolCall.toolCallId,
767
828
  name: toolCall.toolName,
768
829
  ...normalizedArguments ? { arguments: normalizedArguments } : {},
769
- ...toolResult && normalizedResult !== void 0 ? { result: normalizedResult } : {},
770
- ...errorValue ? { error: errorValue } : {},
771
830
  metadata: (0, import_harness.normalizeMetadata)({
772
831
  providerExecuted: toolCall.providerExecuted ?? toolResult?.providerExecuted,
773
832
  title: toolCall.title ?? toolResult?.title,
774
833
  dynamic: toolCall.dynamic,
775
834
  invalid: toolCall.invalid,
835
+ ...errorValue ? { error: errorValue } : {},
776
836
  preliminary: toolResult?.preliminary,
777
837
  providerMetadata: toolCall.providerMetadata ?? toolResult?.providerMetadata,
778
838
  ...replayMetadata ?? {}