@mastra/evals 1.9.0 → 1.10.0-alpha.1

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 (74) hide show
  1. package/LICENSE.md +6 -4
  2. package/README.md +14 -18
  3. package/dist/docs/SKILL.md +29 -27
  4. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  5. package/dist/docs/references/docs-evals-built-in-scorers.md +3 -0
  6. package/dist/docs/references/docs-evals-overview.md +74 -10
  7. package/dist/docs/references/docs-evals-quick-checks.md +3 -1
  8. package/dist/docs/references/docs-evals-vitest-integration.md +136 -0
  9. package/dist/docs/references/reference-evals-answer-relevancy.md +2 -0
  10. package/dist/docs/references/reference-evals-answer-similarity.md +2 -0
  11. package/dist/docs/references/reference-evals-bias.md +2 -0
  12. package/dist/docs/references/reference-evals-checks.md +8 -0
  13. package/dist/docs/references/reference-evals-completeness.md +2 -0
  14. package/dist/docs/references/reference-evals-content-similarity.md +2 -0
  15. package/dist/docs/references/reference-evals-context-precision.md +2 -0
  16. package/dist/docs/references/reference-evals-context-recall.md +2 -0
  17. package/dist/docs/references/reference-evals-context-relevance.md +2 -0
  18. package/dist/docs/references/reference-evals-faithfulness.md +2 -0
  19. package/dist/docs/references/reference-evals-hallucination.md +2 -0
  20. package/dist/docs/references/reference-evals-keyword-coverage.md +2 -0
  21. package/dist/docs/references/reference-evals-multi-turn-judge.md +103 -0
  22. package/dist/docs/references/reference-evals-noise-sensitivity.md +2 -0
  23. package/dist/docs/references/reference-evals-prompt-alignment.md +2 -0
  24. package/dist/docs/references/reference-evals-rubric.md +3 -1
  25. package/dist/docs/references/reference-evals-scorer-utils.md +2 -0
  26. package/dist/docs/references/reference-evals-summarization.md +2 -0
  27. package/dist/docs/references/reference-evals-textual-difference.md +2 -0
  28. package/dist/docs/references/reference-evals-tone-consistency.md +2 -0
  29. package/dist/docs/references/reference-evals-tool-call-accuracy.md +2 -0
  30. package/dist/docs/references/reference-evals-toxicity.md +2 -0
  31. package/dist/docs/references/reference-evals-trajectory-accuracy.md +2 -0
  32. package/dist/docs/references/reference-migrations-upgrade-to-v1-evals.md +2 -0
  33. package/dist/matchers-D3c6f71B.js +98 -0
  34. package/dist/matchers-D3c6f71B.js.map +1 -0
  35. package/dist/matchers-DzWDR4_h.cjs +109 -0
  36. package/dist/matchers-DzWDR4_h.cjs.map +1 -0
  37. package/dist/scorers/code/completeness/index.d.ts.map +1 -1
  38. package/dist/scorers/code/keyword-coverage/index.d.ts +1 -1
  39. package/dist/scorers/code/keyword-coverage/index.d.ts.map +1 -1
  40. package/dist/scorers/code/tone/index.d.ts +3 -3
  41. package/dist/scorers/code/trajectory/index.d.ts +1 -1
  42. package/dist/scorers/code/trajectory/index.d.ts.map +1 -1
  43. package/dist/scorers/llm/answer-relevancy/prompts.d.ts.map +1 -1
  44. package/dist/scorers/llm/answer-similarity/index.d.ts +1 -1
  45. package/dist/scorers/llm/answer-similarity/prompts.d.ts.map +1 -1
  46. package/dist/scorers/llm/context-relevance/index.d.ts +2 -2
  47. package/dist/scorers/llm/noise-sensitivity/index.d.ts +2 -2
  48. package/dist/scorers/llm/tool-call-accuracy/prompts.d.ts.map +1 -1
  49. package/dist/scorers/llm/trajectory/index.d.ts +1 -1
  50. package/dist/scorers/llm/trajectory/prompts.d.ts.map +1 -1
  51. package/dist/scorers/utils.d.ts +9 -9
  52. package/dist/scorers/utils.d.ts.map +1 -1
  53. package/dist/vitest/expect-evals.d.ts +78 -0
  54. package/dist/vitest/expect-evals.d.ts.map +1 -0
  55. package/dist/vitest/index.cjs +269 -0
  56. package/dist/vitest/index.cjs.map +1 -0
  57. package/dist/vitest/index.d.ts +8 -0
  58. package/dist/vitest/index.d.ts.map +1 -0
  59. package/dist/vitest/index.js +262 -0
  60. package/dist/vitest/index.js.map +1 -0
  61. package/dist/vitest/matchers.d.ts +49 -0
  62. package/dist/vitest/matchers.d.ts.map +1 -0
  63. package/dist/vitest/meta.d.ts +45 -0
  64. package/dist/vitest/meta.d.ts.map +1 -0
  65. package/dist/vitest/reporter.d.ts +29 -0
  66. package/dist/vitest/reporter.d.ts.map +1 -0
  67. package/dist/vitest/setup.cjs +11 -0
  68. package/dist/vitest/setup.cjs.map +1 -0
  69. package/dist/vitest/setup.d.ts +2 -0
  70. package/dist/vitest/setup.d.ts.map +1 -0
  71. package/dist/vitest/setup.js +13 -0
  72. package/dist/vitest/setup.js.map +1 -0
  73. package/package.json +36 -11
  74. package/CHANGELOG.md +0 -3709
@@ -0,0 +1,262 @@
1
+ import { n as registerEvalMatchers, t as evalMatchers } from "../matchers-D3c6f71B.js";
2
+ import { runEvals } from "@mastra/core/evals";
3
+ //#region src/vitest/meta.ts
4
+ /**
5
+ * Flattens the `scores` record of a `RunEvalsResult` into `Record<string, number>`.
6
+ * Categorized results (`agent`, `workflow`, `steps`, `trajectory`) are flattened
7
+ * into dot-separated keys, e.g. `agent.my-scorer` or `steps.step-1.my-scorer`.
8
+ */
9
+ function flattenScores(scores, prefix = "", out = {}) {
10
+ for (const [id, value] of Object.entries(scores)) {
11
+ const key = prefix ? `${prefix}.${id}` : id;
12
+ if (typeof value === "number") out[key] = value;
13
+ else if (value && typeof value === "object") flattenScores(value, key, out);
14
+ }
15
+ return out;
16
+ }
17
+ /**
18
+ * Projects a `RunEvalsResult` into the serializable `MastraEvalMeta` shape
19
+ * stored in `task.meta.mastraEval`.
20
+ */
21
+ function toEvalMeta(result) {
22
+ const meta = {
23
+ scores: flattenScores(result.scores),
24
+ totalItems: result.summary.totalItems
25
+ };
26
+ if (result.verdict !== void 0) meta.verdict = result.verdict;
27
+ if (result.gateResults) meta.gateResults = result.gateResults.map((g) => ({ ...g }));
28
+ if (result.thresholdResults) meta.thresholdResults = result.thresholdResults.map((t) => ({ ...t }));
29
+ if (result.turnResults) meta.turnResults = result.turnResults;
30
+ return meta;
31
+ }
32
+ //#endregion
33
+ //#region src/vitest/expect-evals.ts
34
+ /** Thrown by `expectEval`/`expectEvals` `.toPass()` when the pass rate is not met. */
35
+ var EvalPassRateError = class extends Error {
36
+ result;
37
+ constructor(message, result) {
38
+ super(message);
39
+ this.name = "EvalPassRateError";
40
+ this.result = result;
41
+ }
42
+ };
43
+ async function attachMetaToCurrentTest(result) {
44
+ try {
45
+ const task = (await import("vitest")).TestRunner?.getCurrentTest?.();
46
+ if (task?.meta) {
47
+ task.meta.mastraEval = toEvalMeta(result);
48
+ return;
49
+ }
50
+ const legacyTask = (await import("vitest/suite")).getCurrentTest?.();
51
+ if (legacyTask?.meta) legacyTask.meta.mastraEval = toEvalMeta(result);
52
+ } catch {}
53
+ }
54
+ function assertPassRate(result, minPassRate) {
55
+ if (!Number.isFinite(minPassRate) || minPassRate < 0 || minPassRate > 1) throw new Error(`toPass(minPassRate): minPassRate must be within [0, 1], got ${minPassRate}`);
56
+ const lines = [];
57
+ for (const gate of result.gateResults ?? []) if (gate.score < minPassRate) lines.push(` ✗ gate ${gate.id}: pass rate ${formatRate(gate.score)} < required ${formatRate(minPassRate)}`);
58
+ for (const t of result.thresholdResults ?? []) if (!t.passed) lines.push(` ✗ threshold ${t.id}: average score ${t.averageScore} (threshold: ${JSON.stringify(t.threshold)})`);
59
+ for (const turn of result.turnResults ?? []) {
60
+ for (const gate of turn.gateResults ?? []) if (gate.score < minPassRate) lines.push(` ✗ turn ${turn.index} gate ${gate.id}: pass rate ${formatRate(gate.score)} < required ${formatRate(minPassRate)}`);
61
+ for (const t of turn.thresholdResults ?? []) if (!t.passed) lines.push(` ✗ turn ${turn.index} threshold ${t.id}: average score ${t.averageScore} (threshold: ${JSON.stringify(t.threshold)})`);
62
+ }
63
+ if (lines.length > 0) throw new EvalPassRateError([`Eval did not pass (required pass rate: ${formatRate(minPassRate)}).`, ...lines].join("\n"), result);
64
+ }
65
+ function formatRate(rate) {
66
+ return `${Math.round(rate * 1e3) / 10}%`;
67
+ }
68
+ function makeAssertion(options) {
69
+ return { async toPass(minPassRate = 1) {
70
+ const result = await runEvals(options);
71
+ await attachMetaToCurrentTest(result);
72
+ assertPassRate(result, minPassRate);
73
+ return result;
74
+ } };
75
+ }
76
+ /**
77
+ * Fluent eval assertion over a dataset, for use inside a regular `test()`.
78
+ *
79
+ * Runs `runEvals` with the given options when awaited and asserts the outcome.
80
+ * The run's scores are attached to the current test's meta so
81
+ * `MastraEvalsReporter` displays them in the runner output.
82
+ *
83
+ * Must be awaited — otherwise the test finishes before the eval runs.
84
+ *
85
+ * @example
86
+ * test('capitals agent answers with the expected city', async () => {
87
+ * await expectEvals({
88
+ * target: capitalsAgent,
89
+ * data: [{ input: 'What is the capital of France?', groundTruth: 'Paris' }],
90
+ * gates: [containsGroundTruth],
91
+ * scorers: [{ scorer: createKeywordCoverageScorer(), threshold: 0.4 }],
92
+ * }).toPass(0.8);
93
+ * });
94
+ */
95
+ function expectEvals(options) {
96
+ return makeAssertion(options);
97
+ }
98
+ /**
99
+ * Single-item variant of `expectEvals`: `data` is one item instead of an array.
100
+ *
101
+ * Use it with `test.each`/`it.each` for matrix testing when you want one test
102
+ * (and one reporter entry) per data item instead of one per dataset run.
103
+ *
104
+ * @example
105
+ * test.each([
106
+ * { input: 'What is the capital of France?', groundTruth: 'Paris' },
107
+ * { input: 'What is the capital of Japan?', groundTruth: 'Tokyo' },
108
+ * ])('capitals agent: $input', async item => {
109
+ * await expectEval({
110
+ * target: capitalsAgent,
111
+ * data: item,
112
+ * gates: [containsGroundTruth],
113
+ * }).toPass();
114
+ * });
115
+ */
116
+ function expectEval(options) {
117
+ return makeAssertion({
118
+ ...options,
119
+ data: [options.data]
120
+ });
121
+ }
122
+ //#endregion
123
+ //#region src/vitest/reporter.ts
124
+ const GREEN = "\x1B[32m";
125
+ const RED = "\x1B[31m";
126
+ const YELLOW = "\x1B[33m";
127
+ const BOLD = "\x1B[1m";
128
+ const DIM = "\x1B[2m";
129
+ const RESET = "\x1B[0m";
130
+ function supportsColor() {
131
+ return process.stdout.isTTY === true && process.env.NO_COLOR === void 0;
132
+ }
133
+ /**
134
+ * Vitest reporter that prints a per-test score table for every eval run
135
+ * using `expectEval`/`expectEvals` (or any test that populates `task.meta.mastraEval`).
136
+ *
137
+ * Usage in `vitest.config.ts`:
138
+ * test: { reporters: ['default', new MastraEvalsReporter()] }
139
+ */
140
+ var MastraEvalsReporter = class {
141
+ color;
142
+ constructor(options = {}) {
143
+ this.color = options.color ?? supportsColor();
144
+ }
145
+ onTestRunEnd(testModules) {
146
+ const entries = [];
147
+ for (const module of testModules) for (const test of module.children.allTests()) {
148
+ const meta = test.meta().mastraEval;
149
+ if (meta) entries.push({
150
+ fullName: test.fullName,
151
+ meta
152
+ });
153
+ }
154
+ if (entries.length === 0) return;
155
+ this.print(this.render(entries));
156
+ }
157
+ /** Renders the score report as plain lines. Exposed for testing. */
158
+ render(entries) {
159
+ const lines = [];
160
+ lines.push("");
161
+ lines.push(this.style(BOLD, " Mastra Evals "));
162
+ for (const { fullName, meta } of entries) {
163
+ lines.push("");
164
+ lines.push(`${this.verdictBadge(meta.verdict)} ${this.style(BOLD, fullName)} ${this.style(DIM, `(${meta.totalItems} item${meta.totalItems === 1 ? "" : "s"})`)}`);
165
+ const rows = [];
166
+ for (const gate of meta.gateResults ?? []) rows.push([
167
+ `${gate.id} ${this.style(DIM, "(gate)")}`,
168
+ formatScore(gate.score),
169
+ this.passMark(gate.passed)
170
+ ]);
171
+ for (const t of meta.thresholdResults ?? []) rows.push([
172
+ `${t.id} ${this.style(DIM, `(threshold: ${formatThreshold(t.threshold)})`)}`,
173
+ formatScore(t.averageScore),
174
+ this.passMark(t.passed)
175
+ ]);
176
+ const covered = /* @__PURE__ */ new Set([...(meta.gateResults ?? []).map((g) => g.id), ...(meta.thresholdResults ?? []).map((t) => t.id)]);
177
+ for (const [id, score] of Object.entries(meta.scores)) {
178
+ if (covered.has(id)) continue;
179
+ rows.push([
180
+ id,
181
+ formatScore(score),
182
+ ""
183
+ ]);
184
+ }
185
+ lines.push(...this.table(rows));
186
+ for (const turn of meta.turnResults ?? []) {
187
+ const turnRows = [];
188
+ for (const gate of turn.gateResults ?? []) turnRows.push([
189
+ `${gate.id} ${this.style(DIM, "(gate)")}`,
190
+ formatScore(gate.score),
191
+ this.passMark(gate.passed)
192
+ ]);
193
+ for (const t of turn.thresholdResults ?? []) turnRows.push([
194
+ `${t.id} ${this.style(DIM, `(threshold: ${formatThreshold(t.threshold)})`)}`,
195
+ formatScore(t.averageScore),
196
+ this.passMark(t.passed)
197
+ ]);
198
+ const turnCovered = /* @__PURE__ */ new Set([...(turn.gateResults ?? []).map((g) => g.id), ...(turn.thresholdResults ?? []).map((t) => t.id)]);
199
+ for (const [id, score] of Object.entries(turn.scores ?? {})) {
200
+ if (turnCovered.has(id)) continue;
201
+ turnRows.push([
202
+ id,
203
+ formatScore(score),
204
+ ""
205
+ ]);
206
+ }
207
+ if (turnRows.length === 0) continue;
208
+ lines.push(` ${this.style(DIM, `turn ${turn.index}`)}`);
209
+ lines.push(...this.table(turnRows, " "));
210
+ }
211
+ }
212
+ const withVerdict = entries.filter((e) => e.meta.verdict !== void 0);
213
+ const passed = withVerdict.filter((e) => e.meta.verdict === "passed").length;
214
+ const failed = withVerdict.filter((e) => e.meta.verdict === "failed").length;
215
+ const scored = entries.length - passed - failed;
216
+ lines.push("");
217
+ const parts = [];
218
+ if (passed > 0) parts.push(this.style(GREEN, `${passed} passed`));
219
+ if (failed > 0) parts.push(this.style(RED, `${failed} failed`));
220
+ if (scored > 0) parts.push(this.style(YELLOW, `${scored} scored`));
221
+ lines.push(` Eval runs: ${entries.length} ${this.style(DIM, `(${parts.join(", ")})`)}`);
222
+ lines.push("");
223
+ return lines.join("\n");
224
+ }
225
+ table(rows, indent = "") {
226
+ if (rows.length === 0) return [];
227
+ const nameWidth = Math.max(...rows.map(([name]) => stripAnsi(name).length));
228
+ const scoreWidth = Math.max(5, ...rows.map(([, score]) => score.length));
229
+ return rows.map(([name, score, mark]) => {
230
+ return `${indent} ${name}${" ".repeat(nameWidth - stripAnsi(name).length)} ${score.padStart(scoreWidth)} ${mark}`.trimEnd();
231
+ });
232
+ }
233
+ verdictBadge(verdict) {
234
+ if (verdict === "passed") return this.style(GREEN, "✓");
235
+ if (verdict === "failed") return this.style(RED, "✗");
236
+ return this.style(YELLOW, "•");
237
+ }
238
+ passMark(passed) {
239
+ return passed ? this.style(GREEN, "✓") : this.style(RED, "✗");
240
+ }
241
+ style(code, text) {
242
+ return this.color ? `${code}${text}${RESET}` : text;
243
+ }
244
+ print(output) {
245
+ process.stdout.write(`${output}\n`);
246
+ }
247
+ };
248
+ function formatScore(score) {
249
+ return Number.isInteger(score) ? score.toFixed(1) : score.toFixed(3).replace(/0+$/, "").replace(/\.$/, ".0");
250
+ }
251
+ function formatThreshold(threshold) {
252
+ if (typeof threshold === "number") return `min ${threshold}`;
253
+ return Object.entries(threshold).map(([key, value]) => `${key} ${value}`).join(", ");
254
+ }
255
+ const ANSI_PATTERN = /\u001b\[[0-9;]*m/g;
256
+ function stripAnsi(text) {
257
+ return text.replace(ANSI_PATTERN, "");
258
+ }
259
+ //#endregion
260
+ export { EvalPassRateError, MastraEvalsReporter, evalMatchers, expectEval, expectEvals, registerEvalMatchers, toEvalMeta };
261
+
262
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/vitest/meta.ts","../../src/vitest/expect-evals.ts","../../src/vitest/reporter.ts"],"sourcesContent":["import type { EvalVerdict, GateResult, RunEvalsResult, ThresholdConfig, TurnResult } from '@mastra/core/evals';\n\n/** Per-threshold-scorer result, as reported in `RunEvalsResult.thresholdResults`. */\nexport type EvalThresholdResult = {\n id: string;\n passed: boolean;\n averageScore: number;\n threshold: ThresholdConfig;\n};\n\n/**\n * Serializable projection of a `RunEvalsResult` stored in `task.meta.mastraEval`.\n *\n * This is the contract between the test worker (which runs `runEvals`) and the\n * `MastraEvalsReporter` running in the main process. It intentionally excludes\n * raw agent/workflow payloads: `task.meta` is structured-cloned from worker to\n * main, so only lightweight, serializable data belongs here.\n */\nexport type MastraEvalMeta = {\n /**\n * Average score per scorer id across all data items. Categorized scorer\n * configs are flattened into dot-separated keys (e.g. `agent.my-scorer`,\n * `steps.step-1.my-scorer`).\n */\n scores: Record<string, number>;\n /** Number of data items evaluated. */\n totalItems: number;\n /** Present when gates or threshold-bearing scorers were provided. */\n verdict?: EvalVerdict;\n /** Per-gate results (averaged across all data items). */\n gateResults?: GateResult[];\n /** Per-threshold-scorer results (averaged across all data items). */\n thresholdResults?: EvalThresholdResult[];\n /** Per-turn assertion results, present when data items use `turns`. */\n turnResults?: TurnResult[];\n};\n\ndeclare module 'vitest' {\n interface TaskMeta {\n mastraEval?: MastraEvalMeta;\n }\n}\n\n/**\n * Flattens the `scores` record of a `RunEvalsResult` into `Record<string, number>`.\n * Categorized results (`agent`, `workflow`, `steps`, `trajectory`) are flattened\n * into dot-separated keys, e.g. `agent.my-scorer` or `steps.step-1.my-scorer`.\n */\nfunction flattenScores(scores: Record<string, any>, prefix = '', out: Record<string, number> = {}) {\n for (const [id, value] of Object.entries(scores)) {\n const key = prefix ? `${prefix}.${id}` : id;\n if (typeof value === 'number') {\n out[key] = value;\n } else if (value && typeof value === 'object') {\n flattenScores(value, key, out);\n }\n }\n return out;\n}\n\n/**\n * Projects a `RunEvalsResult` into the serializable `MastraEvalMeta` shape\n * stored in `task.meta.mastraEval`.\n */\nexport function toEvalMeta(result: RunEvalsResult): MastraEvalMeta {\n const meta: MastraEvalMeta = {\n scores: flattenScores(result.scores),\n totalItems: result.summary.totalItems,\n };\n if (result.verdict !== undefined) meta.verdict = result.verdict;\n if (result.gateResults) meta.gateResults = result.gateResults.map(g => ({ ...g }));\n if (result.thresholdResults) meta.thresholdResults = result.thresholdResults.map(t => ({ ...t }));\n if (result.turnResults) meta.turnResults = result.turnResults;\n return meta;\n}\n","import type { RunEvalsConfig, RunEvalsResult } from '@mastra/core/evals';\nimport { runEvals } from '@mastra/core/evals';\n\nimport { toEvalMeta } from './meta';\n\n/**\n * Options accepted by `expectEvals`: exactly the `runEvals` configuration\n * (target, data items, scorers/gates/thresholds, etc.).\n */\nexport type ExpectEvalsOptions = RunEvalsConfig;\n\n/** A single eval data item, as accepted by `runEvals` in its `data` array. */\nexport type EvalDataItem = RunEvalsConfig['data'][number];\n\n/**\n * Options accepted by `expectEval`: same as `expectEvals`, but `data` is a single item.\n * Distributes over the config union so each target keeps its own data-item contract\n * (e.g. `turns` stays agent-only).\n */\nexport type ExpectEvalOptions = RunEvalsConfig extends infer T\n ? T extends RunEvalsConfig\n ? Omit<T, 'data'> & { data: T['data'][number] }\n : never\n : never;\n\n/** Thrown by `expectEval`/`expectEvals` `.toPass()` when the pass rate is not met. */\nexport class EvalPassRateError extends Error {\n readonly result: RunEvalsResult;\n\n constructor(message: string, result: RunEvalsResult) {\n super(message);\n this.name = 'EvalPassRateError';\n this.result = result;\n }\n}\n\n/** Fluent assertion returned by `expectEval` and `expectEvals`. */\nexport type EvalScoresAssertion = {\n /**\n * Runs the eval and asserts it passes.\n *\n * - Every gate's pass rate across data items must be `>= minPassRate`\n * (gates score 1 on pass and 0 on failure, so the averaged gate score\n * is exactly the fraction of items that passed the gate).\n * - Every scorer threshold must pass (thresholds compare the average\n * score across items and are not relaxed by `minPassRate`).\n *\n * @param minPassRate Minimum per-gate pass rate in `[0, 1]`. Defaults to 1\n * (all items must pass every gate).\n * @returns The full `RunEvalsResult` for further assertions.\n */\n toPass(minPassRate?: number): Promise<RunEvalsResult>;\n};\n\nasync function attachMetaToCurrentTest(result: RunEvalsResult): Promise<void> {\n try {\n const vitest = await import('vitest');\n // Vitest >= 4.1\n const task = (vitest as any).TestRunner?.getCurrentTest?.();\n if (task?.meta) {\n task.meta.mastraEval = toEvalMeta(result);\n return;\n }\n // Vitest 3.x / < 4.1\n const suite = await import('vitest/suite');\n const legacyTask = (suite as any).getCurrentTest?.();\n if (legacyTask?.meta) {\n legacyTask.meta.mastraEval = toEvalMeta(result);\n }\n } catch {\n // Outside a Vitest test context — assertion still works, reporter meta is skipped.\n }\n}\n\nfunction assertPassRate(result: RunEvalsResult, minPassRate: number): void {\n if (!Number.isFinite(minPassRate) || minPassRate < 0 || minPassRate > 1) {\n throw new Error(`toPass(minPassRate): minPassRate must be within [0, 1], got ${minPassRate}`);\n }\n\n const lines: string[] = [];\n\n for (const gate of result.gateResults ?? []) {\n if (gate.score < minPassRate) {\n lines.push(` ✗ gate ${gate.id}: pass rate ${formatRate(gate.score)} < required ${formatRate(minPassRate)}`);\n }\n }\n\n for (const t of result.thresholdResults ?? []) {\n if (!t.passed) {\n lines.push(` ✗ threshold ${t.id}: average score ${t.averageScore} (threshold: ${JSON.stringify(t.threshold)})`);\n }\n }\n\n for (const turn of result.turnResults ?? []) {\n for (const gate of turn.gateResults ?? []) {\n if (gate.score < minPassRate) {\n lines.push(\n ` ✗ turn ${turn.index} gate ${gate.id}: pass rate ${formatRate(gate.score)} < required ${formatRate(minPassRate)}`,\n );\n }\n }\n for (const t of turn.thresholdResults ?? []) {\n if (!t.passed) {\n lines.push(\n ` ✗ turn ${turn.index} threshold ${t.id}: average score ${t.averageScore} (threshold: ${JSON.stringify(t.threshold)})`,\n );\n }\n }\n }\n\n if (lines.length > 0) {\n throw new EvalPassRateError(\n [`Eval did not pass (required pass rate: ${formatRate(minPassRate)}).`, ...lines].join('\\n'),\n result,\n );\n }\n}\n\nfunction formatRate(rate: number): string {\n return `${Math.round(rate * 1000) / 10}%`;\n}\n\nfunction makeAssertion(options: ExpectEvalsOptions): EvalScoresAssertion {\n return {\n async toPass(minPassRate = 1): Promise<RunEvalsResult> {\n const result = await runEvals(options as Parameters<typeof runEvals>[0]);\n await attachMetaToCurrentTest(result);\n assertPassRate(result, minPassRate);\n return result;\n },\n };\n}\n\n/**\n * Fluent eval assertion over a dataset, for use inside a regular `test()`.\n *\n * Runs `runEvals` with the given options when awaited and asserts the outcome.\n * The run's scores are attached to the current test's meta so\n * `MastraEvalsReporter` displays them in the runner output.\n *\n * Must be awaited — otherwise the test finishes before the eval runs.\n *\n * @example\n * test('capitals agent answers with the expected city', async () => {\n * await expectEvals({\n * target: capitalsAgent,\n * data: [{ input: 'What is the capital of France?', groundTruth: 'Paris' }],\n * gates: [containsGroundTruth],\n * scorers: [{ scorer: createKeywordCoverageScorer(), threshold: 0.4 }],\n * }).toPass(0.8);\n * });\n */\nexport function expectEvals(options: ExpectEvalsOptions): EvalScoresAssertion {\n return makeAssertion(options);\n}\n\n/**\n * Single-item variant of `expectEvals`: `data` is one item instead of an array.\n *\n * Use it with `test.each`/`it.each` for matrix testing when you want one test\n * (and one reporter entry) per data item instead of one per dataset run.\n *\n * @example\n * test.each([\n * { input: 'What is the capital of France?', groundTruth: 'Paris' },\n * { input: 'What is the capital of Japan?', groundTruth: 'Tokyo' },\n * ])('capitals agent: $input', async item => {\n * await expectEval({\n * target: capitalsAgent,\n * data: item,\n * gates: [containsGroundTruth],\n * }).toPass();\n * });\n */\nexport function expectEval(options: ExpectEvalOptions): EvalScoresAssertion {\n return makeAssertion({ ...options, data: [options.data] } as ExpectEvalsOptions);\n}\n","import type { ThresholdConfig } from '@mastra/core/evals';\nimport type { Reporter, TestCase, TestModule } from 'vitest/node';\n\nimport type { MastraEvalMeta } from './meta';\n\ntype EvalEntry = {\n fullName: string;\n meta: MastraEvalMeta;\n};\n\nconst GREEN = '\\u001b[32m';\nconst RED = '\\u001b[31m';\nconst YELLOW = '\\u001b[33m';\nconst BOLD = '\\u001b[1m';\nconst DIM = '\\u001b[2m';\nconst RESET = '\\u001b[0m';\n\nfunction supportsColor(): boolean {\n return process.stdout.isTTY === true && process.env.NO_COLOR === undefined;\n}\n\n/**\n * Vitest reporter that prints a per-test score table for every eval run\n * using `expectEval`/`expectEvals` (or any test that populates `task.meta.mastraEval`).\n *\n * Usage in `vitest.config.ts`:\n * test: { reporters: ['default', new MastraEvalsReporter()] }\n */\nexport class MastraEvalsReporter implements Reporter {\n private readonly color: boolean;\n\n constructor(options: { color?: boolean } = {}) {\n this.color = options.color ?? supportsColor();\n }\n\n onTestRunEnd(testModules: ReadonlyArray<TestModule>): void {\n const entries: EvalEntry[] = [];\n for (const module of testModules) {\n for (const test of module.children.allTests()) {\n const meta = ((test as TestCase).meta() as { mastraEval?: MastraEvalMeta }).mastraEval;\n if (meta) {\n entries.push({ fullName: test.fullName, meta });\n }\n }\n }\n\n if (entries.length === 0) return;\n\n this.print(this.render(entries));\n }\n\n /** Renders the score report as plain lines. Exposed for testing. */\n render(entries: EvalEntry[]): string {\n const lines: string[] = [];\n lines.push('');\n lines.push(this.style(BOLD, ' Mastra Evals '));\n\n for (const { fullName, meta } of entries) {\n lines.push('');\n lines.push(\n `${this.verdictBadge(meta.verdict)} ${this.style(BOLD, fullName)} ${this.style(DIM, `(${meta.totalItems} item${meta.totalItems === 1 ? '' : 's'})`)}`,\n );\n\n const rows: Array<[string, string, string]> = [];\n for (const gate of meta.gateResults ?? []) {\n rows.push([`${gate.id} ${this.style(DIM, '(gate)')}`, formatScore(gate.score), this.passMark(gate.passed)]);\n }\n for (const t of meta.thresholdResults ?? []) {\n rows.push([\n `${t.id} ${this.style(DIM, `(threshold: ${formatThreshold(t.threshold)})`)}`,\n formatScore(t.averageScore),\n this.passMark(t.passed),\n ]);\n }\n const covered = new Set([\n ...(meta.gateResults ?? []).map(g => g.id),\n ...(meta.thresholdResults ?? []).map(t => t.id),\n ]);\n for (const [id, score] of Object.entries(meta.scores)) {\n if (covered.has(id)) continue;\n rows.push([id, formatScore(score), '']);\n }\n\n lines.push(...this.table(rows));\n\n for (const turn of meta.turnResults ?? []) {\n const turnRows: Array<[string, string, string]> = [];\n for (const gate of turn.gateResults ?? []) {\n turnRows.push([\n `${gate.id} ${this.style(DIM, '(gate)')}`,\n formatScore(gate.score),\n this.passMark(gate.passed),\n ]);\n }\n for (const t of turn.thresholdResults ?? []) {\n turnRows.push([\n `${t.id} ${this.style(DIM, `(threshold: ${formatThreshold(t.threshold)})`)}`,\n formatScore(t.averageScore),\n this.passMark(t.passed),\n ]);\n }\n const turnCovered = new Set([\n ...(turn.gateResults ?? []).map(g => g.id),\n ...(turn.thresholdResults ?? []).map(t => t.id),\n ]);\n for (const [id, score] of Object.entries(turn.scores ?? {})) {\n if (turnCovered.has(id)) continue;\n turnRows.push([id, formatScore(score), '']);\n }\n if (turnRows.length === 0) continue;\n lines.push(` ${this.style(DIM, `turn ${turn.index}`)}`);\n lines.push(...this.table(turnRows, ' '));\n }\n }\n\n const withVerdict = entries.filter(e => e.meta.verdict !== undefined);\n const passed = withVerdict.filter(e => e.meta.verdict === 'passed').length;\n const failed = withVerdict.filter(e => e.meta.verdict === 'failed').length;\n const scored = entries.length - passed - failed;\n lines.push('');\n const parts: string[] = [];\n if (passed > 0) parts.push(this.style(GREEN, `${passed} passed`));\n if (failed > 0) parts.push(this.style(RED, `${failed} failed`));\n if (scored > 0) parts.push(this.style(YELLOW, `${scored} scored`));\n lines.push(` Eval runs: ${entries.length} ${this.style(DIM, `(${parts.join(', ')})`)}`);\n lines.push('');\n\n return lines.join('\\n');\n }\n\n private table(rows: Array<[string, string, string]>, indent = ''): string[] {\n if (rows.length === 0) return [];\n const nameWidth = Math.max(...rows.map(([name]) => stripAnsi(name).length));\n const scoreWidth = Math.max(5, ...rows.map(([, score]) => score.length));\n return rows.map(([name, score, mark]) => {\n const namePad = ' '.repeat(nameWidth - stripAnsi(name).length);\n return `${indent} ${name}${namePad} ${score.padStart(scoreWidth)} ${mark}`.trimEnd();\n });\n }\n\n private verdictBadge(verdict: MastraEvalMeta['verdict']): string {\n if (verdict === 'passed') return this.style(GREEN, '✓');\n if (verdict === 'failed') return this.style(RED, '✗');\n return this.style(YELLOW, '•');\n }\n\n private passMark(passed: boolean): string {\n return passed ? this.style(GREEN, '✓') : this.style(RED, '✗');\n }\n\n private style(code: string, text: string): string {\n return this.color ? `${code}${text}${RESET}` : text;\n }\n\n private print(output: string): void {\n process.stdout.write(`${output}\\n`);\n }\n}\n\nfunction formatScore(score: number): string {\n return Number.isInteger(score) ? score.toFixed(1) : score.toFixed(3).replace(/0+$/, '').replace(/\\.$/, '.0');\n}\n\nfunction formatThreshold(threshold: ThresholdConfig): string {\n if (typeof threshold === 'number') return `min ${threshold}`;\n return Object.entries(threshold)\n .map(([key, value]) => `${key} ${value}`)\n .join(', ');\n}\n\n// eslint-disable-next-line no-control-regex\nconst ANSI_PATTERN = /\\u001b\\[[0-9;]*m/g;\n\nfunction stripAnsi(text: string): string {\n return text.replace(ANSI_PATTERN, '');\n}\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { EvalVerdict } from '@mastra/core/evals';
2
+ /**
3
+ * Custom Vitest matchers operating on a `RunEvalsResult` (the return value of
4
+ * `runEvals`). Registered via `registerEvalMatchers()` or by adding
5
+ * `@mastra/evals/vitest/setup` to `setupFiles`.
6
+ */
7
+ export interface EvalMatchers<R = unknown> {
8
+ /** Asserts the run's verdict (`passed`, `scored`, or `failed`). */
9
+ toHaveVerdict(verdict: EvalVerdict): R;
10
+ /** Asserts the average score of a scorer is strictly above `min`. Supports dot-paths for categorized configs (e.g. `agent.my-scorer`). */
11
+ toHaveScoreAbove(scorerName: string, min: number): R;
12
+ /** Asserts the average score of a scorer is strictly below `max`. Supports dot-paths for categorized configs (e.g. `agent.my-scorer`). */
13
+ toHaveScoreBelow(scorerName: string, max: number): R;
14
+ /** Asserts that all gates passed (fails when no gates were configured). */
15
+ toPassGates(): R;
16
+ /** Asserts that all scorer thresholds passed (fails when no thresholds were configured). */
17
+ toPassThresholds(): R;
18
+ }
19
+ declare module 'vitest' {
20
+ interface Assertion<T = any> extends EvalMatchers<T> {
21
+ }
22
+ interface AsymmetricMatchersContaining extends EvalMatchers {
23
+ }
24
+ }
25
+ export declare const evalMatchers: {
26
+ toHaveVerdict(received: unknown, verdict: EvalVerdict): {
27
+ pass: boolean;
28
+ message: () => string;
29
+ };
30
+ toHaveScoreAbove(received: unknown, scorerName: string, min: number): {
31
+ pass: boolean;
32
+ message: () => string;
33
+ };
34
+ toHaveScoreBelow(received: unknown, scorerName: string, max: number): {
35
+ pass: boolean;
36
+ message: () => string;
37
+ };
38
+ toPassGates(received: unknown): {
39
+ pass: boolean;
40
+ message: () => string;
41
+ };
42
+ toPassThresholds(received: unknown): {
43
+ pass: boolean;
44
+ message: () => string;
45
+ };
46
+ };
47
+ /** Registers the Mastra eval matchers on Vitest's `expect`. */
48
+ export declare function registerEvalMatchers(): void;
49
+ //# sourceMappingURL=matchers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/vitest/matchers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,oBAAoB,CAAC;AAGtE;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,mEAAmE;IACnE,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC;IACvC,0IAA0I;IAC1I,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;IACrD,0IAA0I;IAC1I,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;IACrD,2EAA2E;IAC3E,WAAW,IAAI,CAAC,CAAC;IACjB,4FAA4F;IAC5F,gBAAgB,IAAI,CAAC,CAAC;CACvB;AAED,OAAO,QAAQ,QAAQ,CAAC;IACtB,UAAU,SAAS,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;KAAG;IACvD,UAAU,4BAA6B,SAAQ,YAAY;KAAG;CAC/D;AAuCD,eAAO,MAAM,YAAY;IACvB,aAAa,WAAW,OAAO,WAAW,WAAW;;;;IAYrD,gBAAgB,WAAW,OAAO,cAAc,MAAM,OAAO,MAAM;;;;IAmBnE,gBAAgB,WAAW,OAAO,cAAc,MAAM,OAAO,MAAM;;;;IAmBnE,WAAW,WAAW,OAAO;;;;IAwB7B,gBAAgB,WAAW,OAAO;;;;CAyBnC,CAAC;AAEF,+DAA+D;AAC/D,wBAAgB,oBAAoB,SAEnC"}
@@ -0,0 +1,45 @@
1
+ import type { EvalVerdict, GateResult, RunEvalsResult, ThresholdConfig, TurnResult } from '@mastra/core/evals';
2
+ /** Per-threshold-scorer result, as reported in `RunEvalsResult.thresholdResults`. */
3
+ export type EvalThresholdResult = {
4
+ id: string;
5
+ passed: boolean;
6
+ averageScore: number;
7
+ threshold: ThresholdConfig;
8
+ };
9
+ /**
10
+ * Serializable projection of a `RunEvalsResult` stored in `task.meta.mastraEval`.
11
+ *
12
+ * This is the contract between the test worker (which runs `runEvals`) and the
13
+ * `MastraEvalsReporter` running in the main process. It intentionally excludes
14
+ * raw agent/workflow payloads: `task.meta` is structured-cloned from worker to
15
+ * main, so only lightweight, serializable data belongs here.
16
+ */
17
+ export type MastraEvalMeta = {
18
+ /**
19
+ * Average score per scorer id across all data items. Categorized scorer
20
+ * configs are flattened into dot-separated keys (e.g. `agent.my-scorer`,
21
+ * `steps.step-1.my-scorer`).
22
+ */
23
+ scores: Record<string, number>;
24
+ /** Number of data items evaluated. */
25
+ totalItems: number;
26
+ /** Present when gates or threshold-bearing scorers were provided. */
27
+ verdict?: EvalVerdict;
28
+ /** Per-gate results (averaged across all data items). */
29
+ gateResults?: GateResult[];
30
+ /** Per-threshold-scorer results (averaged across all data items). */
31
+ thresholdResults?: EvalThresholdResult[];
32
+ /** Per-turn assertion results, present when data items use `turns`. */
33
+ turnResults?: TurnResult[];
34
+ };
35
+ declare module 'vitest' {
36
+ interface TaskMeta {
37
+ mastraEval?: MastraEvalMeta;
38
+ }
39
+ }
40
+ /**
41
+ * Projects a `RunEvalsResult` into the serializable `MastraEvalMeta` shape
42
+ * stored in `task.meta.mastraEval`.
43
+ */
44
+ export declare function toEvalMeta(result: RunEvalsResult): MastraEvalMeta;
45
+ //# sourceMappingURL=meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../src/vitest/meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE/G,qFAAqF;AACrF,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,eAAe,CAAC;CAC5B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,yDAAyD;IACzD,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACzC,uEAAuE;IACvE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B,CAAC;AAEF,OAAO,QAAQ,QAAQ,CAAC;IACtB,UAAU,QAAQ;QAChB,UAAU,CAAC,EAAE,cAAc,CAAC;KAC7B;CACF;AAmBD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAUjE"}
@@ -0,0 +1,29 @@
1
+ import type { Reporter, TestModule } from 'vitest/node';
2
+ import type { MastraEvalMeta } from './meta.js';
3
+ type EvalEntry = {
4
+ fullName: string;
5
+ meta: MastraEvalMeta;
6
+ };
7
+ /**
8
+ * Vitest reporter that prints a per-test score table for every eval run
9
+ * using `expectEval`/`expectEvals` (or any test that populates `task.meta.mastraEval`).
10
+ *
11
+ * Usage in `vitest.config.ts`:
12
+ * test: { reporters: ['default', new MastraEvalsReporter()] }
13
+ */
14
+ export declare class MastraEvalsReporter implements Reporter {
15
+ private readonly color;
16
+ constructor(options?: {
17
+ color?: boolean;
18
+ });
19
+ onTestRunEnd(testModules: ReadonlyArray<TestModule>): void;
20
+ /** Renders the score report as plain lines. Exposed for testing. */
21
+ render(entries: EvalEntry[]): string;
22
+ private table;
23
+ private verdictBadge;
24
+ private passMark;
25
+ private style;
26
+ private print;
27
+ }
28
+ export {};
29
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/vitest/reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAY,UAAU,EAAE,MAAM,aAAa,CAAC;AAElE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,KAAK,SAAS,GAAG;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;CACtB,CAAC;AAaF;;;;;;GAMG;AACH,qBAAa,mBAAoB,YAAW,QAAQ;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAEhC,YAAY,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,EAE5C;IAED,YAAY,CAAC,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,IAAI,CAczD;IAED,oEAAoE;IACpE,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,CA4EnC;IAED,OAAO,CAAC,KAAK;IAUb,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,KAAK;CAGd"}
@@ -0,0 +1,11 @@
1
+ //#region src/vitest/setup.ts
2
+ /**
3
+ * Vitest setup file that auto-registers the Mastra eval matchers.
4
+ *
5
+ * Usage in `vitest.config.ts`:
6
+ * test: { setupFiles: ['@mastra/evals/vitest/setup'] }
7
+ */
8
+ require("../matchers-DzWDR4_h.cjs").registerEvalMatchers();
9
+ //#endregion
10
+
11
+ //# sourceMappingURL=setup.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.cjs","names":["registerEvalMatchers"],"sources":["../../src/vitest/setup.ts"],"sourcesContent":["/**\n * Vitest setup file that auto-registers the Mastra eval matchers.\n *\n * Usage in `vitest.config.ts`:\n * test: { setupFiles: ['@mastra/evals/vitest/setup'] }\n */\nimport { registerEvalMatchers } from './matchers';\n\nregisterEvalMatchers();\n"],"mappings":";;;;;;;kCAQAA,CAAAA,CAAAA,qBAAqB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/vitest/setup.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import { n as registerEvalMatchers } from "../matchers-D3c6f71B.js";
2
+ //#region src/vitest/setup.ts
3
+ /**
4
+ * Vitest setup file that auto-registers the Mastra eval matchers.
5
+ *
6
+ * Usage in `vitest.config.ts`:
7
+ * test: { setupFiles: ['@mastra/evals/vitest/setup'] }
8
+ */
9
+ registerEvalMatchers();
10
+ //#endregion
11
+ export {};
12
+
13
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","names":[],"sources":["../../src/vitest/setup.ts"],"sourcesContent":["/**\n * Vitest setup file that auto-registers the Mastra eval matchers.\n *\n * Usage in `vitest.config.ts`:\n * test: { setupFiles: ['@mastra/evals/vitest/setup'] }\n */\nimport { registerEvalMatchers } from './matchers';\n\nregisterEvalMatchers();\n"],"mappings":";;;;;;;;AAQA,qBAAqB"}
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@mastra/evals",
3
- "version": "1.9.0",
3
+ "version": "1.10.0-alpha.1",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "files": [
7
- "dist",
8
- "CHANGELOG.md"
7
+ "dist"
9
8
  ],
10
9
  "homepage": "https://mastra.ai",
11
10
  "repository": {
@@ -59,6 +58,26 @@
59
58
  "default": "./dist/checks.cjs"
60
59
  }
61
60
  },
61
+ "./vitest": {
62
+ "import": {
63
+ "types": "./dist/vitest/index.d.ts",
64
+ "default": "./dist/vitest/index.js"
65
+ },
66
+ "require": {
67
+ "types": "./dist/vitest/index.d.ts",
68
+ "default": "./dist/vitest/index.cjs"
69
+ }
70
+ },
71
+ "./vitest/setup": {
72
+ "import": {
73
+ "types": "./dist/vitest/setup.d.ts",
74
+ "default": "./dist/vitest/setup.js"
75
+ },
76
+ "require": {
77
+ "types": "./dist/vitest/setup.d.ts",
78
+ "default": "./dist/vitest/setup.cjs"
79
+ }
80
+ },
62
81
  "./package.json": "./package.json"
63
82
  },
64
83
  "keywords": [],
@@ -71,7 +90,13 @@
71
90
  "string-similarity": "^4.0.4"
72
91
  },
73
92
  "peerDependencies": {
74
- "@mastra/core": ">=1.0.0-0 <2.0.0-0"
93
+ "@mastra/core": ">=1.0.0-0 <2.0.0-0",
94
+ "vitest": ">=3.0.0 <5.0.0"
95
+ },
96
+ "peerDependenciesMeta": {
97
+ "vitest": {
98
+ "optional": true
99
+ }
75
100
  },
76
101
  "devDependencies": {
77
102
  "@ai-sdk/openai": "^1.3.24",
@@ -85,15 +110,15 @@
85
110
  "eslint": "^10.7.0",
86
111
  "tsdown": "0.22.9",
87
112
  "tsx": "^4.23.1",
88
- "typescript": "^6.0.3",
113
+ "typescript": "^7.0.2",
89
114
  "vitest": "4.1.10",
90
115
  "zod": "^4.4.3",
91
- "@internal/ai-sdk-v5": "0.0.72",
92
- "@internal/lint": "0.0.125",
93
- "@internal/llm-recorder": "0.0.61",
94
- "@internal/test-utils": "0.0.61",
95
- "@internal/types-builder": "0.0.100",
96
- "@mastra/core": "1.61.0"
116
+ "@internal/ai-sdk-v5": "0.0.76",
117
+ "@internal/test-utils": "0.0.65",
118
+ "@internal/lint": "0.0.129",
119
+ "@internal/llm-recorder": "0.0.65",
120
+ "@internal/types-builder": "0.0.104",
121
+ "@mastra/core": "1.64.0-alpha.7"
97
122
  },
98
123
  "engines": {
99
124
  "node": ">=22.13.0"