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