evalkit 0.1.0 → 0.2.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
@@ -15,8 +15,9 @@ npm install evalkit
15
15
  ## Quick Start
16
16
 
17
17
  ```typescript
18
- import { runSuite, printSuiteResult } from 'evalkit';
18
+ import { runSuite } from 'evalkit';
19
19
 
20
+ // Results stream to console as each case completes
20
21
  const result = await runSuite({
21
22
  cases: 'golden-set.yaml',
22
23
  agent: async (query) => {
@@ -28,8 +29,6 @@ const result = await runSuite({
28
29
  };
29
30
  },
30
31
  });
31
-
32
- printSuiteResult(result);
33
32
  // eval-001 What is my portfolio allocation? PASS 1.2s
34
33
  // eval-002 Show me my current holdings FAIL 3.4s
35
34
  // content_match: Missing: $
@@ -110,6 +109,7 @@ const result = await runSuite({
110
109
  agent: myAgentFn, // Your (query: string) => Promise<AgentResult> callback
111
110
  name: 'Portfolio Suite', // Optional suite name (overrides name from file)
112
111
  concurrency: 3, // Run cases in parallel (default: 1, sequential)
112
+ print: true, // Stream results to console (default: true)
113
113
  onCaseComplete: (caseResult) => {
114
114
  console.log(`${caseResult.id}: ${caseResult.passed ? 'PASS' : 'FAIL'}`);
115
115
  }, // Optional progress callback, fired after each case
@@ -261,7 +261,7 @@ evalkit is SDK-agnostic. You write a thin adapter function that calls your agent
261
261
  ### Any agent (generic pattern)
262
262
 
263
263
  ```typescript
264
- import { runSuite, printSuiteResult, AgentFn } from 'evalkit';
264
+ import { runSuite, AgentFn } from 'evalkit';
265
265
 
266
266
  const agent: AgentFn = async (query) => {
267
267
  const start = Date.now();
@@ -276,21 +276,19 @@ const agent: AgentFn = async (query) => {
276
276
  };
277
277
 
278
278
  const result = await runSuite({ cases: 'golden-set.yaml', agent });
279
- printSuiteResult(result);
280
279
  ```
281
280
 
282
281
  ### CI / GitHub Actions
283
282
 
284
283
  ```typescript
285
284
  // eval.ts — run with: npx tsx eval.ts
286
- import { runSuite, printSuiteResult } from 'evalkit';
285
+ import { runSuite } from 'evalkit';
287
286
 
288
287
  const result = await runSuite({
289
288
  cases: 'golden-set.yaml',
290
289
  agent: myAgentFn,
291
290
  });
292
291
 
293
- printSuiteResult(result);
294
292
  process.exit(result.failed > 0 ? 1 : 0);
295
293
  ```
296
294
 
package/dist/index.cjs CHANGED
@@ -676,19 +676,60 @@ function validate(data) {
676
676
  };
677
677
  }
678
678
 
679
+ // src/runner/reporter.ts
680
+ function printCaseResult(c) {
681
+ const status = c.passed ? "PASS" : "FAIL";
682
+ const query = c.query.length > 50 ? c.query.slice(0, 47) + "..." : c.query;
683
+ const latency2 = c.agentResult.latencyMs !== void 0 ? `${(c.agentResult.latencyMs / 1e3).toFixed(1)}s` : "";
684
+ console.log(` ${c.id} ${query.padEnd(50)} ${status} ${latency2}`);
685
+ if (!c.passed) {
686
+ for (const check of c.checks.results) {
687
+ if (!check.passed) {
688
+ console.log(` ${check.key}: ${check.details}`);
689
+ }
690
+ }
691
+ }
692
+ }
693
+ function printSuiteHeader(name) {
694
+ console.log(`
695
+ Suite: ${name}`);
696
+ console.log(`${"=".repeat(60)}`);
697
+ }
698
+ function printSuiteSummary(result) {
699
+ console.log(`
700
+ ${result.passed}/${result.total} passed (${(result.duration / 1e3).toFixed(1)}s)`);
701
+ }
702
+ function printSuiteResult(result) {
703
+ printSuiteHeader(result.name);
704
+ for (const c of result.cases) {
705
+ printCaseResult(c);
706
+ }
707
+ printSuiteSummary(result);
708
+ }
709
+
679
710
  // src/runner/run-suite.ts
680
711
  async function runSuite(options) {
681
712
  const config = typeof options.cases === "string" ? loadCases(options.cases) : loadCases(options.cases);
682
713
  const suiteName = options.name ?? config.name ?? "unnamed";
683
714
  const concurrency = options.concurrency ?? 1;
715
+ const shouldPrint = options.print !== false;
684
716
  const startTime = Date.now();
717
+ if (shouldPrint) {
718
+ printSuiteHeader(suiteName);
719
+ }
685
720
  const cases = config.test_cases;
686
721
  const results = [];
722
+ const onCase = (result) => {
723
+ if (shouldPrint) {
724
+ printCaseResult(result);
725
+ }
726
+ options.onCaseComplete?.(result);
727
+ };
687
728
  if (concurrency <= 1) {
688
729
  for (const tc of cases) {
689
730
  const result = await runCase(tc, options.agent);
690
731
  results.push(result);
691
- options.onCaseComplete?.(result);
732
+ onCase(result);
692
733
  }
693
734
  } else {
694
735
  let idx = 0;
@@ -697,7 +738,7 @@ async function runSuite(options) {
697
738
  const currentIdx = idx++;
698
739
  const result = await runCase(cases[currentIdx], options.agent);
699
740
  results.push(result);
700
- options.onCaseComplete?.(result);
741
+ onCase(result);
701
742
  }
702
743
  };
703
744
  const workers = Array.from(
@@ -708,7 +749,7 @@ async function runSuite(options) {
708
749
  }
709
750
  const passedCount = results.filter((r) => r.passed).length;
710
751
  const duration = Date.now() - startTime;
711
- return {
752
+ const suiteResult = {
712
753
  name: suiteName,
713
754
  passed: passedCount,
714
755
  failed: results.length - passedCount,
@@ -716,6 +757,10 @@ async function runSuite(options) {
716
757
  cases: results,
717
758
  duration
718
759
  };
760
+ if (shouldPrint) {
761
+ printSuiteSummary(suiteResult);
762
+ }
763
+ return suiteResult;
719
764
  }
720
765
  async function runCase(tc, agent) {
721
766
  let agentResult;
@@ -825,28 +870,6 @@ async function runCase(tc, agent) {
825
870
  agentResult
826
871
  };
827
872
  }
828
-
829
- // src/runner/reporter.ts
830
- function printSuiteResult(result) {
831
- console.log(`
832
- Suite: ${result.name}`);
833
- console.log(`${"=".repeat(60)}`);
834
- for (const c of result.cases) {
835
- const status = c.passed ? "PASS" : "FAIL";
836
- const query = c.query.length > 50 ? c.query.slice(0, 47) + "..." : c.query;
837
- const latency2 = c.agentResult.latencyMs !== void 0 ? `${(c.agentResult.latencyMs / 1e3).toFixed(1)}s` : "";
838
- console.log(` ${c.id} ${query.padEnd(50)} ${status} ${latency2}`);
839
- if (!c.passed) {
840
- for (const check of c.checks.results) {
841
- if (!check.passed) {
842
- console.log(` ${check.key}: ${check.details}`);
843
- }
844
- }
845
- }
846
- }
847
- console.log(`
848
- ${result.passed}/${result.total} passed (${(result.duration / 1e3).toFixed(1)}s)`);
849
- }
850
873
  // Annotate the CommonJS export names for ESM import in node:
851
874
  0 && (module.exports = {
852
875
  contentMatch,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/checks/tool-selection.ts","../src/checks/content-match.ts","../src/checks/negative-match.ts","../src/checks/latency.ts","../src/checks/json-valid.ts","../src/checks/schema-match.ts","../src/checks/non-empty.ts","../src/checks/length-bounds.ts","../src/checks/regex-match.ts","../src/checks/tool-call-count.ts","../src/checks/cost-budget.ts","../src/checks/run-checks.ts","../src/runner/loader.ts","../src/runner/run-suite.ts","../src/runner/reporter.ts"],"sourcesContent":["export {\n // Types\n type EvalResult,\n type ToolSelectionResult,\n type ContentMatchResult,\n type NegativeMatchResult,\n type LatencyResult,\n type JsonValidResult,\n type SchemaMatchResult,\n type NonEmptyResult,\n type LengthBoundsResult,\n type RegexMatchResult,\n type ToolCallCountResult,\n type CostBudgetResult,\n type CheckSuiteResult,\n // Evaluators\n toolSelection,\n createToolSelectionEvaluator,\n contentMatch,\n createContentMatchEvaluator,\n negativeMatch,\n createNegativeMatchEvaluator,\n latency,\n createLatencyEvaluator,\n jsonValid,\n createJsonValidEvaluator,\n schemaMatch,\n createSchemaMatchEvaluator,\n nonEmpty,\n createNonEmptyEvaluator,\n lengthBounds,\n createLengthBoundsEvaluator,\n regexMatch,\n createRegexMatchEvaluator,\n toolCallCount,\n createToolCallCountEvaluator,\n costBudget,\n createCostBudgetEvaluator,\n runChecks,\n} from './checks';\n\nexport {\n // Runner types\n type TestCase,\n type TestCaseChecks,\n type SuiteConfig,\n type AgentFn,\n type AgentResult,\n type CaseResult,\n type SuiteResult,\n type RunSuiteOptions,\n // Runner functions\n loadCases,\n loadFile,\n parseYaml,\n parseJson,\n runSuite,\n printSuiteResult,\n} from './runner';\n","import { ToolSelectionResult } from './types';\n\nexport interface ToolSelectionInput {\n expected: string[];\n actual: string[];\n}\n\n/**\n * Strict set equality between expected and actual tool names.\n * Order-independent, deduplicates. Reports missing and unexpected tools.\n */\nexport function toolSelection(input: ToolSelectionInput): ToolSelectionResult {\n const expectedSet = new Set(input.expected);\n const actualSet = new Set(input.actual);\n\n const missing = [...expectedSet].filter((t) => !actualSet.has(t));\n const extra = [...actualSet].filter((t) => !expectedSet.has(t));\n const passed = missing.length === 0 && extra.length === 0;\n\n let details = passed ? 'All expected tools matched' : '';\n\n if (missing.length > 0) {\n details += `Missing: ${missing.join(', ')}`;\n }\n\n if (extra.length > 0) {\n details += `${missing.length > 0 ? '. ' : ''}Unexpected: ${extra.join(', ')}`;\n }\n\n return {\n key: 'tool_selection',\n passed,\n details,\n expected: input.expected,\n actual: [...actualSet],\n };\n}\n\n/**\n * Factory: creates a reusable toolSelection evaluator with fixed expected tools.\n */\nexport function createToolSelectionEvaluator(config: { expected: string[] }) {\n return (input: { actual: string[] }): ToolSelectionResult =>\n toolSelection({ expected: config.expected, actual: input.actual });\n}\n","import { ContentMatchResult } from './types';\n\nexport interface ContentMatchInput {\n responseText: string;\n mustContain: string[];\n}\n\n/**\n * Case-insensitive substring matching: all mustContain strings must appear in responseText.\n */\nexport function contentMatch(input: ContentMatchInput): ContentMatchResult {\n if (!input.mustContain || input.mustContain.length === 0) {\n return {\n key: 'content_match',\n passed: true,\n details: 'No content requirements',\n missing: [],\n };\n }\n\n const lower = input.responseText.toLowerCase();\n const missing = input.mustContain.filter((s) => !lower.includes(s.toLowerCase()));\n const passed = missing.length === 0;\n\n return {\n key: 'content_match',\n passed,\n details: passed\n ? 'All required content found'\n : `Missing: ${missing.join(', ')}`,\n missing,\n };\n}\n\n/**\n * Factory: creates a reusable contentMatch evaluator with fixed mustContain list.\n */\nexport function createContentMatchEvaluator(config: { mustContain: string[] }) {\n return (input: { responseText: string }): ContentMatchResult =>\n contentMatch({ responseText: input.responseText, mustContain: config.mustContain });\n}\n","import { NegativeMatchResult } from './types';\n\nexport interface NegativeMatchInput {\n responseText: string;\n mustNotContain: string[];\n}\n\n/**\n * Case-insensitive substring matching: no mustNotContain strings may appear in responseText.\n */\nexport function negativeMatch(input: NegativeMatchInput): NegativeMatchResult {\n if (!input.mustNotContain || input.mustNotContain.length === 0) {\n return {\n key: 'negative_match',\n passed: true,\n details: 'No negative requirements',\n found: [],\n };\n }\n\n const lower = input.responseText.toLowerCase();\n const found = input.mustNotContain.filter((s) => lower.includes(s.toLowerCase()));\n const passed = found.length === 0;\n\n return {\n key: 'negative_match',\n passed,\n details: passed\n ? 'No forbidden content found'\n : `Found: ${found.join(', ')}`,\n found,\n };\n}\n\n/**\n * Factory: creates a reusable negativeMatch evaluator with fixed mustNotContain list.\n */\nexport function createNegativeMatchEvaluator(config: { mustNotContain: string[] }) {\n return (input: { responseText: string }): NegativeMatchResult =>\n negativeMatch({ responseText: input.responseText, mustNotContain: config.mustNotContain });\n}\n","import { LatencyResult } from './types';\n\nexport interface LatencyInput {\n latencyMs: number;\n thresholdMs?: number;\n}\n\n/**\n * Simple threshold check: latencyMs <= thresholdMs.\n * Default threshold: 20,000ms.\n */\nexport function latency(input: LatencyInput): LatencyResult {\n const threshold = input.thresholdMs ?? 20000;\n const passed = input.latencyMs <= threshold;\n\n return {\n key: 'latency',\n passed,\n details: passed\n ? `${input.latencyMs}ms within ${threshold}ms threshold`\n : `${input.latencyMs}ms exceeded ${threshold}ms threshold`,\n ms: input.latencyMs,\n threshold,\n };\n}\n\n/**\n * Factory: creates a reusable latency evaluator with a fixed threshold.\n */\nexport function createLatencyEvaluator(config: { thresholdMs: number }) {\n return (input: { latencyMs: number }): LatencyResult =>\n latency({ latencyMs: input.latencyMs, thresholdMs: config.thresholdMs });\n}\n","import { JsonValidResult } from './types';\n\nexport interface JsonValidInput {\n text: string;\n /** If true, the parsed result must be an object or array (not a bare primitive). */\n requireObject?: boolean;\n}\n\n/**\n * Validates that a string is parseable JSON.\n * Optionally checks that the parsed result is an object or array.\n */\nexport function jsonValid(input: JsonValidInput): JsonValidResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(input.text);\n } catch {\n return {\n key: 'json_valid',\n passed: false,\n details: 'Invalid JSON',\n };\n }\n\n if (input.requireObject && (parsed === null || typeof parsed !== 'object')) {\n return {\n key: 'json_valid',\n passed: false,\n details: `Expected object or array, got ${parsed === null ? 'null' : typeof parsed}`,\n };\n }\n\n return {\n key: 'json_valid',\n passed: true,\n details: 'Valid JSON',\n };\n}\n\n/**\n * Factory: creates a reusable jsonValid evaluator with fixed options.\n */\nexport function createJsonValidEvaluator(config?: { requireObject?: boolean }) {\n return (input: { text: string }): JsonValidResult =>\n jsonValid({ text: input.text, requireObject: config?.requireObject });\n}\n","import { SchemaMatchResult } from './types';\n\nexport interface SchemaMatchInput {\n /** The object to validate. */\n data: Record<string, unknown>;\n /** Keys that must be present. */\n requiredKeys: string[];\n /** Optional type constraints: key -> expected typeof result. */\n typeChecks?: Record<string, string>;\n}\n\n/**\n * Validates that a parsed object contains required keys and optionally checks value types.\n * Zero-dep — just checks key presence and typeof.\n */\nexport function schemaMatch(input: SchemaMatchInput): SchemaMatchResult {\n const missingKeys = input.requiredKeys.filter((k) => !(k in input.data));\n const typeErrors: string[] = [];\n\n if (input.typeChecks) {\n for (const [key, expectedType] of Object.entries(input.typeChecks)) {\n if (key in input.data) {\n const actualType = Array.isArray(input.data[key]) ? 'array' : typeof input.data[key];\n if (actualType !== expectedType) {\n typeErrors.push(`${key}: expected ${expectedType}, got ${actualType}`);\n }\n }\n }\n }\n\n const passed = missingKeys.length === 0 && typeErrors.length === 0;\n const problems: string[] = [];\n if (missingKeys.length > 0) problems.push(`Missing keys: ${missingKeys.join(', ')}`);\n if (typeErrors.length > 0) problems.push(`Type errors: ${typeErrors.join('; ')}`);\n\n return {\n key: 'schema_match',\n passed,\n details: passed ? 'Schema valid' : problems.join('. '),\n missingKeys,\n typeErrors,\n };\n}\n\n/**\n * Factory: creates a reusable schemaMatch evaluator with fixed schema expectations.\n */\nexport function createSchemaMatchEvaluator(config: {\n requiredKeys: string[];\n typeChecks?: Record<string, string>;\n}) {\n return (input: { data: Record<string, unknown> }): SchemaMatchResult =>\n schemaMatch({\n data: input.data,\n requiredKeys: config.requiredKeys,\n typeChecks: config.typeChecks,\n });\n}\n","import { NonEmptyResult } from './types';\n\nconst DEFAULT_COP_OUT_PHRASES = [\n \"i don't know\",\n \"n/a\",\n \"no information\",\n \"i'm not sure\",\n \"i cannot\",\n \"i can't\",\n \"no data available\",\n];\n\nexport interface NonEmptyInput {\n responseText: string;\n /** Custom cop-out phrases to check against (case-insensitive exact match after trim). */\n copOutPhrases?: string[];\n}\n\n/**\n * Checks that the response is not empty, not just whitespace, and not a cop-out phrase.\n */\nexport function nonEmpty(input: NonEmptyInput): NonEmptyResult {\n const trimmed = input.responseText.trim();\n\n if (trimmed.length === 0) {\n return {\n key: 'non_empty',\n passed: false,\n details: 'Response is empty',\n };\n }\n\n const phrases = input.copOutPhrases ?? DEFAULT_COP_OUT_PHRASES;\n const lower = trimmed.toLowerCase();\n const matchedPhrase = phrases.find((p) => lower === p.toLowerCase());\n\n if (matchedPhrase) {\n return {\n key: 'non_empty',\n passed: false,\n details: `Response is a cop-out phrase: \"${matchedPhrase}\"`,\n };\n }\n\n return {\n key: 'non_empty',\n passed: true,\n details: 'Response is non-empty',\n };\n}\n\n/**\n * Factory: creates a reusable nonEmpty evaluator with fixed cop-out phrases.\n */\nexport function createNonEmptyEvaluator(config?: { copOutPhrases?: string[] }) {\n return (input: { responseText: string }): NonEmptyResult =>\n nonEmpty({ responseText: input.responseText, copOutPhrases: config?.copOutPhrases });\n}\n","import { LengthBoundsResult } from './types';\n\nexport interface LengthBoundsInput {\n responseText: string;\n min?: number;\n max?: number;\n}\n\n/**\n * Checks that the response length (character count) falls within min and/or max bounds.\n */\nexport function lengthBounds(input: LengthBoundsInput): LengthBoundsResult {\n const length = input.responseText.length;\n let passed = true;\n const problems: string[] = [];\n\n if (input.min !== undefined && length < input.min) {\n passed = false;\n problems.push(`${length} chars below minimum ${input.min}`);\n }\n\n if (input.max !== undefined && length > input.max) {\n passed = false;\n problems.push(`${length} chars above maximum ${input.max}`);\n }\n\n return {\n key: 'length_bounds',\n passed,\n details: passed\n ? `${length} chars within bounds`\n : problems.join('. '),\n length,\n min: input.min,\n max: input.max,\n };\n}\n\n/**\n * Factory: creates a reusable lengthBounds evaluator with fixed bounds.\n */\nexport function createLengthBoundsEvaluator(config: { min?: number; max?: number }) {\n return (input: { responseText: string }): LengthBoundsResult =>\n lengthBounds({ responseText: input.responseText, min: config.min, max: config.max });\n}\n","import { RegexMatchResult } from './types';\n\nexport interface RegexMatchInput {\n responseText: string;\n patterns: (string | RegExp)[];\n /** If 'all' (default), every pattern must match. If 'any', at least one must match. */\n mode?: 'all' | 'any';\n}\n\n/**\n * Tests the response against one or more regex patterns.\n */\nexport function regexMatch(input: RegexMatchInput): RegexMatchResult {\n const mode = input.mode ?? 'all';\n const failedPatterns: string[] = [];\n\n for (const pattern of input.patterns) {\n const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\n if (!regex.test(input.responseText)) {\n failedPatterns.push(String(pattern));\n }\n }\n\n let passed: boolean;\n if (mode === 'all') {\n passed = failedPatterns.length === 0;\n } else {\n // 'any' mode: pass if at least one pattern matched\n passed = failedPatterns.length < input.patterns.length;\n }\n\n let details: string;\n if (passed) {\n details = mode === 'all'\n ? 'All patterns matched'\n : 'At least one pattern matched';\n } else {\n details = mode === 'all'\n ? `Failed patterns: ${failedPatterns.join(', ')}`\n : 'No patterns matched';\n }\n\n return {\n key: 'regex_match',\n passed,\n details,\n failedPatterns,\n };\n}\n\n/**\n * Factory: creates a reusable regexMatch evaluator with fixed patterns and mode.\n */\nexport function createRegexMatchEvaluator(config: {\n patterns: (string | RegExp)[];\n mode?: 'all' | 'any';\n}) {\n return (input: { responseText: string }): RegexMatchResult =>\n regexMatch({ responseText: input.responseText, patterns: config.patterns, mode: config.mode });\n}\n","import { ToolCallCountResult } from './types';\n\nexport interface ToolCallCountInput {\n count: number;\n min?: number;\n max?: number;\n}\n\n/**\n * Checks that the number of tool calls falls within expected min/max bounds.\n */\nexport function toolCallCount(input: ToolCallCountInput): ToolCallCountResult {\n let passed = true;\n const problems: string[] = [];\n\n if (input.min !== undefined && input.count < input.min) {\n passed = false;\n problems.push(`${input.count} calls below minimum ${input.min}`);\n }\n\n if (input.max !== undefined && input.count > input.max) {\n passed = false;\n problems.push(`${input.count} calls above maximum ${input.max}`);\n }\n\n return {\n key: 'tool_call_count',\n passed,\n details: passed\n ? `${input.count} tool calls within bounds`\n : problems.join('. '),\n count: input.count,\n min: input.min,\n max: input.max,\n };\n}\n\n/**\n * Factory: creates a reusable toolCallCount evaluator with fixed bounds.\n */\nexport function createToolCallCountEvaluator(config: { min?: number; max?: number }) {\n return (input: { count: number }): ToolCallCountResult =>\n toolCallCount({ count: input.count, min: config.min, max: config.max });\n}\n","import { CostBudgetResult } from './types';\n\nexport interface CostBudgetInput {\n /** Actual cost — can be token count or dollar amount. */\n actual: number;\n /** Budget threshold — same unit as actual. */\n budget: number;\n}\n\n/**\n * Checks that token count or dollar cost stays under a threshold.\n */\nexport function costBudget(input: CostBudgetInput): CostBudgetResult {\n const passed = input.actual <= input.budget;\n\n return {\n key: 'cost_budget',\n passed,\n details: passed\n ? `${input.actual} within budget of ${input.budget}`\n : `${input.actual} exceeded budget of ${input.budget}`,\n actual: input.actual,\n budget: input.budget,\n };\n}\n\n/**\n * Factory: creates a reusable costBudget evaluator with a fixed budget.\n */\nexport function createCostBudgetEvaluator(config: { budget: number }) {\n return (input: { actual: number }): CostBudgetResult =>\n costBudget({ actual: input.actual, budget: config.budget });\n}\n","import { CheckSuiteResult, EvalResult } from './types';\nimport { toolSelection } from './tool-selection';\nimport { contentMatch } from './content-match';\nimport { negativeMatch } from './negative-match';\nimport { latency } from './latency';\nimport { jsonValid } from './json-valid';\nimport { schemaMatch } from './schema-match';\nimport { nonEmpty } from './non-empty';\nimport { lengthBounds } from './length-bounds';\nimport { regexMatch } from './regex-match';\nimport { toolCallCount } from './tool-call-count';\nimport { costBudget } from './cost-budget';\n\nexport interface RunChecksInput {\n responseText?: string;\n expectedTools?: string[];\n actualTools?: string[];\n mustContain?: string[];\n mustNotContain?: string[];\n latencyMs?: number;\n thresholdMs?: number;\n json?: { text: string; requireObject?: boolean };\n schema?: { data: Record<string, unknown>; requiredKeys: string[]; typeChecks?: Record<string, string> };\n copOutPhrases?: string[];\n lengthMin?: number;\n lengthMax?: number;\n regexPatterns?: (string | RegExp)[];\n regexMode?: 'all' | 'any';\n toolCallCountValue?: number;\n toolCallMin?: number;\n toolCallMax?: number;\n costActual?: number;\n costBudget?: number;\n}\n\n/**\n * Runs any combination of checks at once and returns a summary.\n * Only runs checks for which the relevant inputs are provided.\n */\nexport function runChecks(input: RunChecksInput): CheckSuiteResult {\n const results: EvalResult[] = [];\n\n if (input.expectedTools !== undefined && input.actualTools !== undefined) {\n results.push(toolSelection({ expected: input.expectedTools, actual: input.actualTools }));\n }\n\n if (input.mustContain !== undefined && input.responseText !== undefined) {\n results.push(contentMatch({ responseText: input.responseText, mustContain: input.mustContain }));\n }\n\n if (input.mustNotContain !== undefined && input.responseText !== undefined) {\n results.push(negativeMatch({ responseText: input.responseText, mustNotContain: input.mustNotContain }));\n }\n\n if (input.latencyMs !== undefined) {\n results.push(latency({ latencyMs: input.latencyMs, thresholdMs: input.thresholdMs }));\n }\n\n if (input.json !== undefined) {\n results.push(jsonValid(input.json));\n }\n\n if (input.schema !== undefined) {\n results.push(schemaMatch(input.schema));\n }\n\n if (input.copOutPhrases !== undefined && input.responseText !== undefined) {\n results.push(nonEmpty({ responseText: input.responseText, copOutPhrases: input.copOutPhrases }));\n }\n\n if ((input.lengthMin !== undefined || input.lengthMax !== undefined) && input.responseText !== undefined) {\n results.push(lengthBounds({ responseText: input.responseText, min: input.lengthMin, max: input.lengthMax }));\n }\n\n if (input.regexPatterns !== undefined && input.responseText !== undefined) {\n results.push(regexMatch({ responseText: input.responseText, patterns: input.regexPatterns, mode: input.regexMode }));\n }\n\n if (input.toolCallCountValue !== undefined) {\n results.push(toolCallCount({ count: input.toolCallCountValue, min: input.toolCallMin, max: input.toolCallMax }));\n }\n\n if (input.costActual !== undefined && input.costBudget !== undefined) {\n results.push(costBudget({ actual: input.costActual, budget: input.costBudget }));\n }\n\n const passedCount = results.filter((r) => r.passed).length;\n const allPassed = results.length > 0 && passedCount === results.length;\n\n return {\n passed: allPassed,\n results,\n summary: `${passedCount}/${results.length} checks passed`,\n };\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { SuiteConfig, TestCase } from './schema';\n\n// ---------------------------------------------------------------------------\n// Minimal YAML parser — handles the subset needed for test-case files\n// ---------------------------------------------------------------------------\n\ninterface YamlLine {\n indent: number;\n raw: string;\n content: string; // after stripping indent\n}\n\nfunction stripComment(line: string): string {\n // Walk the string respecting quoted regions\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === '#' && !inSingle && !inDouble) {\n return line.slice(0, i).trimEnd();\n }\n }\n return line;\n}\n\nfunction tokenize(content: string): YamlLine[] {\n const lines: YamlLine[] = [];\n for (const raw of content.split('\\n')) {\n const stripped = stripComment(raw);\n if (stripped.trim() === '') continue;\n const indent = stripped.search(/\\S/);\n if (indent === -1) continue;\n lines.push({ indent, raw: stripped, content: stripped.slice(indent) });\n }\n return lines;\n}\n\nfunction parseScalar(value: string): string | number | boolean | null {\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value === 'null' || value === '~') return null;\n\n // Quoted strings\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n return value.slice(1, -1);\n }\n\n // Numbers\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return Number(value);\n }\n\n return value;\n}\n\nfunction parseFlowArray(raw: string): unknown[] {\n // raw is like \"[item1, item2, ...]\"\n const inner = raw.slice(1, -1).trim();\n if (inner === '') return [];\n\n const items: string[] = [];\n let current = '';\n let depth = 0;\n let inSingle = false;\n let inDouble = false;\n\n for (let i = 0; i < inner.length; i++) {\n const ch = inner[i];\n if (ch === \"'\" && !inDouble) { inSingle = !inSingle; current += ch; }\n else if (ch === '\"' && !inSingle) { inDouble = !inDouble; current += ch; }\n else if (ch === '[' && !inSingle && !inDouble) { depth++; current += ch; }\n else if (ch === ']' && !inSingle && !inDouble) { depth--; current += ch; }\n else if (ch === ',' && depth === 0 && !inSingle && !inDouble) {\n items.push(current.trim());\n current = '';\n } else {\n current += ch;\n }\n }\n if (current.trim() !== '') items.push(current.trim());\n\n return items.map((item) => parseScalar(item));\n}\n\nfunction parseBlock(lines: YamlLine[], start: number, baseIndent: number): [Record<string, unknown>, number] {\n const result: Record<string, unknown> = {};\n let i = start;\n\n while (i < lines.length && lines[i].indent >= baseIndent) {\n const line = lines[i];\n\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) break; // belongs to a parent or sibling block\n\n const content = line.content;\n\n // Sequence item at mapping level: \"- key: value\" or \"- value\"\n if (content.startsWith('- ')) {\n break; // sequences are handled by the caller\n }\n\n // Key-value pair\n const colonIdx = findColon(content);\n if (colonIdx === -1) {\n throw new Error(`YAML parse error: expected key-value pair, got \"${content}\"`);\n }\n\n const key = content.slice(0, colonIdx).trim();\n const valueRaw = content.slice(colonIdx + 1).trim();\n\n if (valueRaw === '' || valueRaw === '') {\n // Check next line for nested content\n if (i + 1 < lines.length && lines[i + 1].indent > baseIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n\n if (nextContent.startsWith('- ')) {\n // Block sequence\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n result[key] = arr;\n i = newI;\n } else {\n // Nested mapping\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n result[key] = nested;\n i = newI;\n }\n } else {\n result[key] = null;\n i++;\n }\n } else if (valueRaw.startsWith('[') && valueRaw.endsWith(']')) {\n result[key] = parseFlowArray(valueRaw);\n i++;\n } else {\n result[key] = parseScalar(valueRaw);\n i++;\n }\n }\n\n return [result, i];\n}\n\nfunction parseSequence(lines: YamlLine[], start: number, baseIndent: number): [unknown[], number] {\n const result: unknown[] = [];\n let i = start;\n\n while (i < lines.length && lines[i].indent >= baseIndent) {\n const line = lines[i];\n\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) break;\n\n const content = line.content;\n if (!content.startsWith('- ')) break;\n\n const afterDash = content.slice(2).trim();\n\n if (afterDash === '') {\n // Nested block under sequence item\n if (i + 1 < lines.length && lines[i + 1].indent > baseIndent) {\n const nextIndent = lines[i + 1].indent;\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n result.push(nested);\n i = newI;\n } else {\n result.push(null);\n i++;\n }\n } else if (findColon(afterDash) !== -1 && !afterDash.startsWith('\"') && !afterDash.startsWith(\"'\")) {\n // Inline mapping: \"- key: value\\n more_key: value\"\n // Rewrite as a mapping starting at dash + 2 indent\n const inlineIndent = line.indent + 2;\n // Parse the first key-value from afterDash\n const colonIdx = findColon(afterDash);\n const firstKey = afterDash.slice(0, colonIdx).trim();\n const firstValRaw = afterDash.slice(colonIdx + 1).trim();\n const obj: Record<string, unknown> = {};\n\n if (firstValRaw === '') {\n // Nested value on the next lines\n if (i + 1 < lines.length && lines[i + 1].indent > inlineIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n if (nextContent.startsWith('- ')) {\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n obj[firstKey] = arr;\n i = newI;\n } else {\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n obj[firstKey] = nested;\n i = newI;\n }\n } else {\n obj[firstKey] = null;\n i++;\n }\n } else if (firstValRaw.startsWith('[') && firstValRaw.endsWith(']')) {\n obj[firstKey] = parseFlowArray(firstValRaw);\n i++;\n } else {\n obj[firstKey] = parseScalar(firstValRaw);\n i++;\n }\n\n // Continue reading sibling keys at inlineIndent\n while (i < lines.length && lines[i].indent === inlineIndent) {\n const sibContent = lines[i].content;\n if (sibContent.startsWith('- ')) break;\n\n const sibColon = findColon(sibContent);\n if (sibColon === -1) break;\n\n const sibKey = sibContent.slice(0, sibColon).trim();\n const sibValRaw = sibContent.slice(sibColon + 1).trim();\n\n if (sibValRaw === '') {\n if (i + 1 < lines.length && lines[i + 1].indent > inlineIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n if (nextContent.startsWith('- ')) {\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n obj[sibKey] = arr;\n i = newI;\n } else {\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n obj[sibKey] = nested;\n i = newI;\n }\n } else {\n obj[sibKey] = null;\n i++;\n }\n } else if (sibValRaw.startsWith('[') && sibValRaw.endsWith(']')) {\n obj[sibKey] = parseFlowArray(sibValRaw);\n i++;\n } else {\n obj[sibKey] = parseScalar(sibValRaw);\n i++;\n }\n }\n\n result.push(obj);\n } else if (afterDash.startsWith('[') && afterDash.endsWith(']')) {\n result.push(parseFlowArray(afterDash));\n i++;\n } else {\n result.push(parseScalar(afterDash));\n i++;\n }\n }\n\n return [result, i];\n}\n\n/** Find the first colon that is not inside quotes. */\nfunction findColon(str: string): number {\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < str.length; i++) {\n const ch = str[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === ':' && !inSingle && !inDouble) {\n // A YAML key colon must be followed by space, end-of-string, or nothing\n if (i + 1 >= str.length || str[i + 1] === ' ') {\n return i;\n }\n }\n }\n return -1;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Parse a YAML string into a SuiteConfig. */\nexport function parseYaml(content: string): SuiteConfig {\n const lines = tokenize(content);\n if (lines.length === 0) {\n throw new Error('YAML is empty');\n }\n\n const [parsed] = parseBlock(lines, 0, lines[0].indent);\n return validate(parsed);\n}\n\n/** Parse a JSON string into a SuiteConfig. */\nexport function parseJson(content: string): SuiteConfig {\n const parsed = JSON.parse(content);\n return validate(parsed);\n}\n\n/** Read a file from disk and parse by extension. */\nexport function loadFile(filePath: string): SuiteConfig {\n const ext = path.extname(filePath).toLowerCase();\n\n if (ext !== '.json' && ext !== '.yaml' && ext !== '.yml') {\n throw new Error(`Unsupported file extension: ${ext}. Use .json, .yaml, or .yml`);\n }\n\n const content = fs.readFileSync(filePath, 'utf-8');\n\n if (ext === '.json') {\n return parseJson(content);\n }\n return parseYaml(content);\n}\n\n/**\n * Main entry point. Accepts a file path (string) or an inline SuiteConfig.\n * If a string is provided, delegates to loadFile.\n */\nexport function loadCases(source: string | SuiteConfig): SuiteConfig {\n if (typeof source === 'string') {\n return loadFile(source);\n }\n return validate(source);\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nfunction validate(data: unknown): SuiteConfig {\n if (typeof data !== 'object' || data === null) {\n throw new Error('Invalid suite config: expected an object');\n }\n\n const obj = data as Record<string, unknown>;\n\n if (!Array.isArray(obj.test_cases)) {\n throw new Error('Invalid suite config: missing test_cases array');\n }\n\n for (const tc of obj.test_cases) {\n if (typeof tc !== 'object' || tc === null) {\n throw new Error('Invalid test case: expected an object');\n }\n const c = tc as Record<string, unknown>;\n if (typeof c.id !== 'string') {\n throw new Error('Invalid test case: missing or non-string id');\n }\n if (typeof c.query !== 'string') {\n throw new Error('Invalid test case: missing or non-string query');\n }\n }\n\n return {\n name: typeof obj.name === 'string' ? obj.name : undefined,\n test_cases: obj.test_cases as TestCase[],\n };\n}\n","import { runChecks, RunChecksInput } from '../checks/run-checks';\nimport { SuiteConfig, TestCase } from './schema';\nimport { AgentFn, AgentResult, CaseResult, SuiteResult } from './types';\nimport { loadCases } from './loader';\n\nexport interface RunSuiteOptions {\n cases: string | SuiteConfig;\n agent: AgentFn;\n name?: string;\n concurrency?: number;\n onCaseComplete?: (result: CaseResult) => void;\n}\n\n/** Run a suite of test cases against an agent function. */\nexport async function runSuite(options: RunSuiteOptions): Promise<SuiteResult> {\n const config = typeof options.cases === 'string'\n ? loadCases(options.cases)\n : loadCases(options.cases);\n\n const suiteName = options.name ?? config.name ?? 'unnamed';\n const concurrency = options.concurrency ?? 1;\n const startTime = Date.now();\n\n const cases = config.test_cases;\n const results: CaseResult[] = [];\n\n if (concurrency <= 1) {\n // Sequential execution\n for (const tc of cases) {\n const result = await runCase(tc, options.agent);\n results.push(result);\n options.onCaseComplete?.(result);\n }\n } else {\n // Concurrent execution with limited concurrency\n let idx = 0;\n const runNext = async (): Promise<void> => {\n while (idx < cases.length) {\n const currentIdx = idx++;\n const result = await runCase(cases[currentIdx], options.agent);\n results.push(result);\n options.onCaseComplete?.(result);\n }\n };\n\n const workers = Array.from(\n { length: Math.min(concurrency, cases.length) },\n () => runNext()\n );\n await Promise.all(workers);\n }\n\n const passedCount = results.filter((r) => r.passed).length;\n const duration = Date.now() - startTime;\n\n return {\n name: suiteName,\n passed: passedCount,\n failed: results.length - passedCount,\n total: results.length,\n cases: results,\n duration,\n };\n}\n\nasync function runCase(tc: TestCase, agent: AgentFn): Promise<CaseResult> {\n let agentResult: AgentResult;\n\n try {\n agentResult = await agent(tc.query);\n } catch (err) {\n // Agent error — mark as failed\n agentResult = {\n responseText: '',\n };\n return {\n id: tc.id,\n query: tc.query,\n passed: false,\n checks: {\n passed: false,\n results: [{\n key: 'agent_error',\n passed: false,\n details: `Agent threw: ${err instanceof Error ? err.message : String(err)}`,\n }],\n summary: '0/1 checks passed',\n },\n metadata: tc.metadata,\n agentResult,\n };\n }\n\n const checks = tc.checks ?? {};\n const input: RunChecksInput = {};\n\n // Map responseText\n input.responseText = agentResult.responseText;\n\n // Tool selection\n if (checks.expectedTools !== undefined) {\n input.expectedTools = checks.expectedTools;\n input.actualTools = agentResult.actualTools;\n }\n\n // Content match\n if (checks.mustContain !== undefined) {\n input.mustContain = checks.mustContain;\n }\n\n // Negative match\n if (checks.mustNotContain !== undefined) {\n input.mustNotContain = checks.mustNotContain;\n }\n\n // Latency\n if (agentResult.latencyMs !== undefined) {\n input.latencyMs = agentResult.latencyMs;\n }\n if (checks.thresholdMs !== undefined) {\n input.thresholdMs = checks.thresholdMs;\n }\n\n // JSON validity\n if (checks.json !== undefined) {\n input.json = {\n text: agentResult.responseText,\n requireObject: checks.json.requireObject,\n };\n }\n\n // Schema match\n if (checks.schema !== undefined) {\n try {\n const data = JSON.parse(agentResult.responseText);\n input.schema = {\n data,\n requiredKeys: checks.schema.requiredKeys,\n typeChecks: checks.schema.typeChecks,\n };\n } catch {\n // If JSON parse fails, still run schema check with empty object\n // so it reports a meaningful failure\n input.schema = {\n data: {} as Record<string, unknown>,\n requiredKeys: checks.schema.requiredKeys,\n typeChecks: checks.schema.typeChecks,\n };\n }\n }\n\n // Non-empty / cop-out phrases\n if (checks.copOutPhrases !== undefined) {\n input.copOutPhrases = checks.copOutPhrases;\n }\n\n // Length bounds\n if (checks.lengthMin !== undefined) {\n input.lengthMin = checks.lengthMin;\n }\n if (checks.lengthMax !== undefined) {\n input.lengthMax = checks.lengthMax;\n }\n\n // Regex\n if (checks.regexPatterns !== undefined) {\n input.regexPatterns = checks.regexPatterns.map((p) => new RegExp(p));\n }\n if (checks.regexMode !== undefined) {\n input.regexMode = checks.regexMode;\n }\n\n // Tool call count\n const toolCallCountValue = agentResult.toolCallCount ?? agentResult.actualTools?.length;\n if (toolCallCountValue !== undefined) {\n input.toolCallCountValue = toolCallCountValue;\n }\n if (checks.toolCallMin !== undefined) {\n input.toolCallMin = checks.toolCallMin;\n }\n if (checks.toolCallMax !== undefined) {\n input.toolCallMax = checks.toolCallMax;\n }\n\n // Cost budget\n if (agentResult.cost !== undefined) {\n input.costActual = agentResult.cost;\n }\n if (checks.costBudget !== undefined) {\n input.costBudget = checks.costBudget;\n }\n\n const suiteResult = runChecks(input);\n\n // A case with no checks always passes (smoke test)\n const passed = suiteResult.results.length === 0 ? true : suiteResult.passed;\n\n return {\n id: tc.id,\n query: tc.query,\n passed,\n checks: { ...suiteResult, passed },\n metadata: tc.metadata,\n agentResult,\n };\n}\n","import { SuiteResult } from './types';\n\n/** Prints a human-readable summary of a suite run to the console. */\nexport function printSuiteResult(result: SuiteResult): void {\n console.log(`\\nSuite: ${result.name}`);\n console.log(`${'='.repeat(60)}`);\n\n for (const c of result.cases) {\n const status = c.passed ? 'PASS' : 'FAIL';\n const query = c.query.length > 50 ? c.query.slice(0, 47) + '...' : c.query;\n const latency = c.agentResult.latencyMs !== undefined\n ? `${(c.agentResult.latencyMs / 1000).toFixed(1)}s`\n : '';\n console.log(` ${c.id} ${query.padEnd(50)} ${status} ${latency}`);\n\n if (!c.passed) {\n for (const check of c.checks.results) {\n if (!check.passed) {\n console.log(` ${check.key}: ${check.details}`);\n }\n }\n }\n }\n\n console.log(`\\n${result.passed}/${result.total} passed (${(result.duration / 1000).toFixed(1)}s)`);\n}\n"],"mappings":"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","names":["path","fs","latency"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/checks/tool-selection.ts","../src/checks/content-match.ts","../src/checks/negative-match.ts","../src/checks/latency.ts","../src/checks/json-valid.ts","../src/checks/schema-match.ts","../src/checks/non-empty.ts","../src/checks/length-bounds.ts","../src/checks/regex-match.ts","../src/checks/tool-call-count.ts","../src/checks/cost-budget.ts","../src/checks/run-checks.ts","../src/runner/loader.ts","../src/runner/reporter.ts","../src/runner/run-suite.ts"],"sourcesContent":["export {\n // Types\n type EvalResult,\n type ToolSelectionResult,\n type ContentMatchResult,\n type NegativeMatchResult,\n type LatencyResult,\n type JsonValidResult,\n type SchemaMatchResult,\n type NonEmptyResult,\n type LengthBoundsResult,\n type RegexMatchResult,\n type ToolCallCountResult,\n type CostBudgetResult,\n type CheckSuiteResult,\n // Evaluators\n toolSelection,\n createToolSelectionEvaluator,\n contentMatch,\n createContentMatchEvaluator,\n negativeMatch,\n createNegativeMatchEvaluator,\n latency,\n createLatencyEvaluator,\n jsonValid,\n createJsonValidEvaluator,\n schemaMatch,\n createSchemaMatchEvaluator,\n nonEmpty,\n createNonEmptyEvaluator,\n lengthBounds,\n createLengthBoundsEvaluator,\n regexMatch,\n createRegexMatchEvaluator,\n toolCallCount,\n createToolCallCountEvaluator,\n costBudget,\n createCostBudgetEvaluator,\n runChecks,\n} from './checks';\n\nexport {\n // Runner types\n type TestCase,\n type TestCaseChecks,\n type SuiteConfig,\n type AgentFn,\n type AgentResult,\n type CaseResult,\n type SuiteResult,\n type RunSuiteOptions,\n // Runner functions\n loadCases,\n loadFile,\n parseYaml,\n parseJson,\n runSuite,\n printSuiteResult,\n} from './runner';\n","import { ToolSelectionResult } from './types';\n\nexport interface ToolSelectionInput {\n expected: string[];\n actual: string[];\n}\n\n/**\n * Strict set equality between expected and actual tool names.\n * Order-independent, deduplicates. Reports missing and unexpected tools.\n */\nexport function toolSelection(input: ToolSelectionInput): ToolSelectionResult {\n const expectedSet = new Set(input.expected);\n const actualSet = new Set(input.actual);\n\n const missing = [...expectedSet].filter((t) => !actualSet.has(t));\n const extra = [...actualSet].filter((t) => !expectedSet.has(t));\n const passed = missing.length === 0 && extra.length === 0;\n\n let details = passed ? 'All expected tools matched' : '';\n\n if (missing.length > 0) {\n details += `Missing: ${missing.join(', ')}`;\n }\n\n if (extra.length > 0) {\n details += `${missing.length > 0 ? '. ' : ''}Unexpected: ${extra.join(', ')}`;\n }\n\n return {\n key: 'tool_selection',\n passed,\n details,\n expected: input.expected,\n actual: [...actualSet],\n };\n}\n\n/**\n * Factory: creates a reusable toolSelection evaluator with fixed expected tools.\n */\nexport function createToolSelectionEvaluator(config: { expected: string[] }) {\n return (input: { actual: string[] }): ToolSelectionResult =>\n toolSelection({ expected: config.expected, actual: input.actual });\n}\n","import { ContentMatchResult } from './types';\n\nexport interface ContentMatchInput {\n responseText: string;\n mustContain: string[];\n}\n\n/**\n * Case-insensitive substring matching: all mustContain strings must appear in responseText.\n */\nexport function contentMatch(input: ContentMatchInput): ContentMatchResult {\n if (!input.mustContain || input.mustContain.length === 0) {\n return {\n key: 'content_match',\n passed: true,\n details: 'No content requirements',\n missing: [],\n };\n }\n\n const lower = input.responseText.toLowerCase();\n const missing = input.mustContain.filter((s) => !lower.includes(s.toLowerCase()));\n const passed = missing.length === 0;\n\n return {\n key: 'content_match',\n passed,\n details: passed\n ? 'All required content found'\n : `Missing: ${missing.join(', ')}`,\n missing,\n };\n}\n\n/**\n * Factory: creates a reusable contentMatch evaluator with fixed mustContain list.\n */\nexport function createContentMatchEvaluator(config: { mustContain: string[] }) {\n return (input: { responseText: string }): ContentMatchResult =>\n contentMatch({ responseText: input.responseText, mustContain: config.mustContain });\n}\n","import { NegativeMatchResult } from './types';\n\nexport interface NegativeMatchInput {\n responseText: string;\n mustNotContain: string[];\n}\n\n/**\n * Case-insensitive substring matching: no mustNotContain strings may appear in responseText.\n */\nexport function negativeMatch(input: NegativeMatchInput): NegativeMatchResult {\n if (!input.mustNotContain || input.mustNotContain.length === 0) {\n return {\n key: 'negative_match',\n passed: true,\n details: 'No negative requirements',\n found: [],\n };\n }\n\n const lower = input.responseText.toLowerCase();\n const found = input.mustNotContain.filter((s) => lower.includes(s.toLowerCase()));\n const passed = found.length === 0;\n\n return {\n key: 'negative_match',\n passed,\n details: passed\n ? 'No forbidden content found'\n : `Found: ${found.join(', ')}`,\n found,\n };\n}\n\n/**\n * Factory: creates a reusable negativeMatch evaluator with fixed mustNotContain list.\n */\nexport function createNegativeMatchEvaluator(config: { mustNotContain: string[] }) {\n return (input: { responseText: string }): NegativeMatchResult =>\n negativeMatch({ responseText: input.responseText, mustNotContain: config.mustNotContain });\n}\n","import { LatencyResult } from './types';\n\nexport interface LatencyInput {\n latencyMs: number;\n thresholdMs?: number;\n}\n\n/**\n * Simple threshold check: latencyMs <= thresholdMs.\n * Default threshold: 20,000ms.\n */\nexport function latency(input: LatencyInput): LatencyResult {\n const threshold = input.thresholdMs ?? 20000;\n const passed = input.latencyMs <= threshold;\n\n return {\n key: 'latency',\n passed,\n details: passed\n ? `${input.latencyMs}ms within ${threshold}ms threshold`\n : `${input.latencyMs}ms exceeded ${threshold}ms threshold`,\n ms: input.latencyMs,\n threshold,\n };\n}\n\n/**\n * Factory: creates a reusable latency evaluator with a fixed threshold.\n */\nexport function createLatencyEvaluator(config: { thresholdMs: number }) {\n return (input: { latencyMs: number }): LatencyResult =>\n latency({ latencyMs: input.latencyMs, thresholdMs: config.thresholdMs });\n}\n","import { JsonValidResult } from './types';\n\nexport interface JsonValidInput {\n text: string;\n /** If true, the parsed result must be an object or array (not a bare primitive). */\n requireObject?: boolean;\n}\n\n/**\n * Validates that a string is parseable JSON.\n * Optionally checks that the parsed result is an object or array.\n */\nexport function jsonValid(input: JsonValidInput): JsonValidResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(input.text);\n } catch {\n return {\n key: 'json_valid',\n passed: false,\n details: 'Invalid JSON',\n };\n }\n\n if (input.requireObject && (parsed === null || typeof parsed !== 'object')) {\n return {\n key: 'json_valid',\n passed: false,\n details: `Expected object or array, got ${parsed === null ? 'null' : typeof parsed}`,\n };\n }\n\n return {\n key: 'json_valid',\n passed: true,\n details: 'Valid JSON',\n };\n}\n\n/**\n * Factory: creates a reusable jsonValid evaluator with fixed options.\n */\nexport function createJsonValidEvaluator(config?: { requireObject?: boolean }) {\n return (input: { text: string }): JsonValidResult =>\n jsonValid({ text: input.text, requireObject: config?.requireObject });\n}\n","import { SchemaMatchResult } from './types';\n\nexport interface SchemaMatchInput {\n /** The object to validate. */\n data: Record<string, unknown>;\n /** Keys that must be present. */\n requiredKeys: string[];\n /** Optional type constraints: key -> expected typeof result. */\n typeChecks?: Record<string, string>;\n}\n\n/**\n * Validates that a parsed object contains required keys and optionally checks value types.\n * Zero-dep — just checks key presence and typeof.\n */\nexport function schemaMatch(input: SchemaMatchInput): SchemaMatchResult {\n const missingKeys = input.requiredKeys.filter((k) => !(k in input.data));\n const typeErrors: string[] = [];\n\n if (input.typeChecks) {\n for (const [key, expectedType] of Object.entries(input.typeChecks)) {\n if (key in input.data) {\n const actualType = Array.isArray(input.data[key]) ? 'array' : typeof input.data[key];\n if (actualType !== expectedType) {\n typeErrors.push(`${key}: expected ${expectedType}, got ${actualType}`);\n }\n }\n }\n }\n\n const passed = missingKeys.length === 0 && typeErrors.length === 0;\n const problems: string[] = [];\n if (missingKeys.length > 0) problems.push(`Missing keys: ${missingKeys.join(', ')}`);\n if (typeErrors.length > 0) problems.push(`Type errors: ${typeErrors.join('; ')}`);\n\n return {\n key: 'schema_match',\n passed,\n details: passed ? 'Schema valid' : problems.join('. '),\n missingKeys,\n typeErrors,\n };\n}\n\n/**\n * Factory: creates a reusable schemaMatch evaluator with fixed schema expectations.\n */\nexport function createSchemaMatchEvaluator(config: {\n requiredKeys: string[];\n typeChecks?: Record<string, string>;\n}) {\n return (input: { data: Record<string, unknown> }): SchemaMatchResult =>\n schemaMatch({\n data: input.data,\n requiredKeys: config.requiredKeys,\n typeChecks: config.typeChecks,\n });\n}\n","import { NonEmptyResult } from './types';\n\nconst DEFAULT_COP_OUT_PHRASES = [\n \"i don't know\",\n \"n/a\",\n \"no information\",\n \"i'm not sure\",\n \"i cannot\",\n \"i can't\",\n \"no data available\",\n];\n\nexport interface NonEmptyInput {\n responseText: string;\n /** Custom cop-out phrases to check against (case-insensitive exact match after trim). */\n copOutPhrases?: string[];\n}\n\n/**\n * Checks that the response is not empty, not just whitespace, and not a cop-out phrase.\n */\nexport function nonEmpty(input: NonEmptyInput): NonEmptyResult {\n const trimmed = input.responseText.trim();\n\n if (trimmed.length === 0) {\n return {\n key: 'non_empty',\n passed: false,\n details: 'Response is empty',\n };\n }\n\n const phrases = input.copOutPhrases ?? DEFAULT_COP_OUT_PHRASES;\n const lower = trimmed.toLowerCase();\n const matchedPhrase = phrases.find((p) => lower === p.toLowerCase());\n\n if (matchedPhrase) {\n return {\n key: 'non_empty',\n passed: false,\n details: `Response is a cop-out phrase: \"${matchedPhrase}\"`,\n };\n }\n\n return {\n key: 'non_empty',\n passed: true,\n details: 'Response is non-empty',\n };\n}\n\n/**\n * Factory: creates a reusable nonEmpty evaluator with fixed cop-out phrases.\n */\nexport function createNonEmptyEvaluator(config?: { copOutPhrases?: string[] }) {\n return (input: { responseText: string }): NonEmptyResult =>\n nonEmpty({ responseText: input.responseText, copOutPhrases: config?.copOutPhrases });\n}\n","import { LengthBoundsResult } from './types';\n\nexport interface LengthBoundsInput {\n responseText: string;\n min?: number;\n max?: number;\n}\n\n/**\n * Checks that the response length (character count) falls within min and/or max bounds.\n */\nexport function lengthBounds(input: LengthBoundsInput): LengthBoundsResult {\n const length = input.responseText.length;\n let passed = true;\n const problems: string[] = [];\n\n if (input.min !== undefined && length < input.min) {\n passed = false;\n problems.push(`${length} chars below minimum ${input.min}`);\n }\n\n if (input.max !== undefined && length > input.max) {\n passed = false;\n problems.push(`${length} chars above maximum ${input.max}`);\n }\n\n return {\n key: 'length_bounds',\n passed,\n details: passed\n ? `${length} chars within bounds`\n : problems.join('. '),\n length,\n min: input.min,\n max: input.max,\n };\n}\n\n/**\n * Factory: creates a reusable lengthBounds evaluator with fixed bounds.\n */\nexport function createLengthBoundsEvaluator(config: { min?: number; max?: number }) {\n return (input: { responseText: string }): LengthBoundsResult =>\n lengthBounds({ responseText: input.responseText, min: config.min, max: config.max });\n}\n","import { RegexMatchResult } from './types';\n\nexport interface RegexMatchInput {\n responseText: string;\n patterns: (string | RegExp)[];\n /** If 'all' (default), every pattern must match. If 'any', at least one must match. */\n mode?: 'all' | 'any';\n}\n\n/**\n * Tests the response against one or more regex patterns.\n */\nexport function regexMatch(input: RegexMatchInput): RegexMatchResult {\n const mode = input.mode ?? 'all';\n const failedPatterns: string[] = [];\n\n for (const pattern of input.patterns) {\n const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\n if (!regex.test(input.responseText)) {\n failedPatterns.push(String(pattern));\n }\n }\n\n let passed: boolean;\n if (mode === 'all') {\n passed = failedPatterns.length === 0;\n } else {\n // 'any' mode: pass if at least one pattern matched\n passed = failedPatterns.length < input.patterns.length;\n }\n\n let details: string;\n if (passed) {\n details = mode === 'all'\n ? 'All patterns matched'\n : 'At least one pattern matched';\n } else {\n details = mode === 'all'\n ? `Failed patterns: ${failedPatterns.join(', ')}`\n : 'No patterns matched';\n }\n\n return {\n key: 'regex_match',\n passed,\n details,\n failedPatterns,\n };\n}\n\n/**\n * Factory: creates a reusable regexMatch evaluator with fixed patterns and mode.\n */\nexport function createRegexMatchEvaluator(config: {\n patterns: (string | RegExp)[];\n mode?: 'all' | 'any';\n}) {\n return (input: { responseText: string }): RegexMatchResult =>\n regexMatch({ responseText: input.responseText, patterns: config.patterns, mode: config.mode });\n}\n","import { ToolCallCountResult } from './types';\n\nexport interface ToolCallCountInput {\n count: number;\n min?: number;\n max?: number;\n}\n\n/**\n * Checks that the number of tool calls falls within expected min/max bounds.\n */\nexport function toolCallCount(input: ToolCallCountInput): ToolCallCountResult {\n let passed = true;\n const problems: string[] = [];\n\n if (input.min !== undefined && input.count < input.min) {\n passed = false;\n problems.push(`${input.count} calls below minimum ${input.min}`);\n }\n\n if (input.max !== undefined && input.count > input.max) {\n passed = false;\n problems.push(`${input.count} calls above maximum ${input.max}`);\n }\n\n return {\n key: 'tool_call_count',\n passed,\n details: passed\n ? `${input.count} tool calls within bounds`\n : problems.join('. '),\n count: input.count,\n min: input.min,\n max: input.max,\n };\n}\n\n/**\n * Factory: creates a reusable toolCallCount evaluator with fixed bounds.\n */\nexport function createToolCallCountEvaluator(config: { min?: number; max?: number }) {\n return (input: { count: number }): ToolCallCountResult =>\n toolCallCount({ count: input.count, min: config.min, max: config.max });\n}\n","import { CostBudgetResult } from './types';\n\nexport interface CostBudgetInput {\n /** Actual cost — can be token count or dollar amount. */\n actual: number;\n /** Budget threshold — same unit as actual. */\n budget: number;\n}\n\n/**\n * Checks that token count or dollar cost stays under a threshold.\n */\nexport function costBudget(input: CostBudgetInput): CostBudgetResult {\n const passed = input.actual <= input.budget;\n\n return {\n key: 'cost_budget',\n passed,\n details: passed\n ? `${input.actual} within budget of ${input.budget}`\n : `${input.actual} exceeded budget of ${input.budget}`,\n actual: input.actual,\n budget: input.budget,\n };\n}\n\n/**\n * Factory: creates a reusable costBudget evaluator with a fixed budget.\n */\nexport function createCostBudgetEvaluator(config: { budget: number }) {\n return (input: { actual: number }): CostBudgetResult =>\n costBudget({ actual: input.actual, budget: config.budget });\n}\n","import { CheckSuiteResult, EvalResult } from './types';\nimport { toolSelection } from './tool-selection';\nimport { contentMatch } from './content-match';\nimport { negativeMatch } from './negative-match';\nimport { latency } from './latency';\nimport { jsonValid } from './json-valid';\nimport { schemaMatch } from './schema-match';\nimport { nonEmpty } from './non-empty';\nimport { lengthBounds } from './length-bounds';\nimport { regexMatch } from './regex-match';\nimport { toolCallCount } from './tool-call-count';\nimport { costBudget } from './cost-budget';\n\nexport interface RunChecksInput {\n responseText?: string;\n expectedTools?: string[];\n actualTools?: string[];\n mustContain?: string[];\n mustNotContain?: string[];\n latencyMs?: number;\n thresholdMs?: number;\n json?: { text: string; requireObject?: boolean };\n schema?: { data: Record<string, unknown>; requiredKeys: string[]; typeChecks?: Record<string, string> };\n copOutPhrases?: string[];\n lengthMin?: number;\n lengthMax?: number;\n regexPatterns?: (string | RegExp)[];\n regexMode?: 'all' | 'any';\n toolCallCountValue?: number;\n toolCallMin?: number;\n toolCallMax?: number;\n costActual?: number;\n costBudget?: number;\n}\n\n/**\n * Runs any combination of checks at once and returns a summary.\n * Only runs checks for which the relevant inputs are provided.\n */\nexport function runChecks(input: RunChecksInput): CheckSuiteResult {\n const results: EvalResult[] = [];\n\n if (input.expectedTools !== undefined && input.actualTools !== undefined) {\n results.push(toolSelection({ expected: input.expectedTools, actual: input.actualTools }));\n }\n\n if (input.mustContain !== undefined && input.responseText !== undefined) {\n results.push(contentMatch({ responseText: input.responseText, mustContain: input.mustContain }));\n }\n\n if (input.mustNotContain !== undefined && input.responseText !== undefined) {\n results.push(negativeMatch({ responseText: input.responseText, mustNotContain: input.mustNotContain }));\n }\n\n if (input.latencyMs !== undefined) {\n results.push(latency({ latencyMs: input.latencyMs, thresholdMs: input.thresholdMs }));\n }\n\n if (input.json !== undefined) {\n results.push(jsonValid(input.json));\n }\n\n if (input.schema !== undefined) {\n results.push(schemaMatch(input.schema));\n }\n\n if (input.copOutPhrases !== undefined && input.responseText !== undefined) {\n results.push(nonEmpty({ responseText: input.responseText, copOutPhrases: input.copOutPhrases }));\n }\n\n if ((input.lengthMin !== undefined || input.lengthMax !== undefined) && input.responseText !== undefined) {\n results.push(lengthBounds({ responseText: input.responseText, min: input.lengthMin, max: input.lengthMax }));\n }\n\n if (input.regexPatterns !== undefined && input.responseText !== undefined) {\n results.push(regexMatch({ responseText: input.responseText, patterns: input.regexPatterns, mode: input.regexMode }));\n }\n\n if (input.toolCallCountValue !== undefined) {\n results.push(toolCallCount({ count: input.toolCallCountValue, min: input.toolCallMin, max: input.toolCallMax }));\n }\n\n if (input.costActual !== undefined && input.costBudget !== undefined) {\n results.push(costBudget({ actual: input.costActual, budget: input.costBudget }));\n }\n\n const passedCount = results.filter((r) => r.passed).length;\n const allPassed = results.length > 0 && passedCount === results.length;\n\n return {\n passed: allPassed,\n results,\n summary: `${passedCount}/${results.length} checks passed`,\n };\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { SuiteConfig, TestCase } from './schema';\n\n// ---------------------------------------------------------------------------\n// Minimal YAML parser — handles the subset needed for test-case files\n// ---------------------------------------------------------------------------\n\ninterface YamlLine {\n indent: number;\n raw: string;\n content: string; // after stripping indent\n}\n\nfunction stripComment(line: string): string {\n // Walk the string respecting quoted regions\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === '#' && !inSingle && !inDouble) {\n return line.slice(0, i).trimEnd();\n }\n }\n return line;\n}\n\nfunction tokenize(content: string): YamlLine[] {\n const lines: YamlLine[] = [];\n for (const raw of content.split('\\n')) {\n const stripped = stripComment(raw);\n if (stripped.trim() === '') continue;\n const indent = stripped.search(/\\S/);\n if (indent === -1) continue;\n lines.push({ indent, raw: stripped, content: stripped.slice(indent) });\n }\n return lines;\n}\n\nfunction parseScalar(value: string): string | number | boolean | null {\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value === 'null' || value === '~') return null;\n\n // Quoted strings\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n return value.slice(1, -1);\n }\n\n // Numbers\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return Number(value);\n }\n\n return value;\n}\n\nfunction parseFlowArray(raw: string): unknown[] {\n // raw is like \"[item1, item2, ...]\"\n const inner = raw.slice(1, -1).trim();\n if (inner === '') return [];\n\n const items: string[] = [];\n let current = '';\n let depth = 0;\n let inSingle = false;\n let inDouble = false;\n\n for (let i = 0; i < inner.length; i++) {\n const ch = inner[i];\n if (ch === \"'\" && !inDouble) { inSingle = !inSingle; current += ch; }\n else if (ch === '\"' && !inSingle) { inDouble = !inDouble; current += ch; }\n else if (ch === '[' && !inSingle && !inDouble) { depth++; current += ch; }\n else if (ch === ']' && !inSingle && !inDouble) { depth--; current += ch; }\n else if (ch === ',' && depth === 0 && !inSingle && !inDouble) {\n items.push(current.trim());\n current = '';\n } else {\n current += ch;\n }\n }\n if (current.trim() !== '') items.push(current.trim());\n\n return items.map((item) => parseScalar(item));\n}\n\nfunction parseBlock(lines: YamlLine[], start: number, baseIndent: number): [Record<string, unknown>, number] {\n const result: Record<string, unknown> = {};\n let i = start;\n\n while (i < lines.length && lines[i].indent >= baseIndent) {\n const line = lines[i];\n\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) break; // belongs to a parent or sibling block\n\n const content = line.content;\n\n // Sequence item at mapping level: \"- key: value\" or \"- value\"\n if (content.startsWith('- ')) {\n break; // sequences are handled by the caller\n }\n\n // Key-value pair\n const colonIdx = findColon(content);\n if (colonIdx === -1) {\n throw new Error(`YAML parse error: expected key-value pair, got \"${content}\"`);\n }\n\n const key = content.slice(0, colonIdx).trim();\n const valueRaw = content.slice(colonIdx + 1).trim();\n\n if (valueRaw === '' || valueRaw === '') {\n // Check next line for nested content\n if (i + 1 < lines.length && lines[i + 1].indent > baseIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n\n if (nextContent.startsWith('- ')) {\n // Block sequence\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n result[key] = arr;\n i = newI;\n } else {\n // Nested mapping\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n result[key] = nested;\n i = newI;\n }\n } else {\n result[key] = null;\n i++;\n }\n } else if (valueRaw.startsWith('[') && valueRaw.endsWith(']')) {\n result[key] = parseFlowArray(valueRaw);\n i++;\n } else {\n result[key] = parseScalar(valueRaw);\n i++;\n }\n }\n\n return [result, i];\n}\n\nfunction parseSequence(lines: YamlLine[], start: number, baseIndent: number): [unknown[], number] {\n const result: unknown[] = [];\n let i = start;\n\n while (i < lines.length && lines[i].indent >= baseIndent) {\n const line = lines[i];\n\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) break;\n\n const content = line.content;\n if (!content.startsWith('- ')) break;\n\n const afterDash = content.slice(2).trim();\n\n if (afterDash === '') {\n // Nested block under sequence item\n if (i + 1 < lines.length && lines[i + 1].indent > baseIndent) {\n const nextIndent = lines[i + 1].indent;\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n result.push(nested);\n i = newI;\n } else {\n result.push(null);\n i++;\n }\n } else if (findColon(afterDash) !== -1 && !afterDash.startsWith('\"') && !afterDash.startsWith(\"'\")) {\n // Inline mapping: \"- key: value\\n more_key: value\"\n // Rewrite as a mapping starting at dash + 2 indent\n const inlineIndent = line.indent + 2;\n // Parse the first key-value from afterDash\n const colonIdx = findColon(afterDash);\n const firstKey = afterDash.slice(0, colonIdx).trim();\n const firstValRaw = afterDash.slice(colonIdx + 1).trim();\n const obj: Record<string, unknown> = {};\n\n if (firstValRaw === '') {\n // Nested value on the next lines\n if (i + 1 < lines.length && lines[i + 1].indent > inlineIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n if (nextContent.startsWith('- ')) {\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n obj[firstKey] = arr;\n i = newI;\n } else {\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n obj[firstKey] = nested;\n i = newI;\n }\n } else {\n obj[firstKey] = null;\n i++;\n }\n } else if (firstValRaw.startsWith('[') && firstValRaw.endsWith(']')) {\n obj[firstKey] = parseFlowArray(firstValRaw);\n i++;\n } else {\n obj[firstKey] = parseScalar(firstValRaw);\n i++;\n }\n\n // Continue reading sibling keys at inlineIndent\n while (i < lines.length && lines[i].indent === inlineIndent) {\n const sibContent = lines[i].content;\n if (sibContent.startsWith('- ')) break;\n\n const sibColon = findColon(sibContent);\n if (sibColon === -1) break;\n\n const sibKey = sibContent.slice(0, sibColon).trim();\n const sibValRaw = sibContent.slice(sibColon + 1).trim();\n\n if (sibValRaw === '') {\n if (i + 1 < lines.length && lines[i + 1].indent > inlineIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n if (nextContent.startsWith('- ')) {\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n obj[sibKey] = arr;\n i = newI;\n } else {\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n obj[sibKey] = nested;\n i = newI;\n }\n } else {\n obj[sibKey] = null;\n i++;\n }\n } else if (sibValRaw.startsWith('[') && sibValRaw.endsWith(']')) {\n obj[sibKey] = parseFlowArray(sibValRaw);\n i++;\n } else {\n obj[sibKey] = parseScalar(sibValRaw);\n i++;\n }\n }\n\n result.push(obj);\n } else if (afterDash.startsWith('[') && afterDash.endsWith(']')) {\n result.push(parseFlowArray(afterDash));\n i++;\n } else {\n result.push(parseScalar(afterDash));\n i++;\n }\n }\n\n return [result, i];\n}\n\n/** Find the first colon that is not inside quotes. */\nfunction findColon(str: string): number {\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < str.length; i++) {\n const ch = str[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === ':' && !inSingle && !inDouble) {\n // A YAML key colon must be followed by space, end-of-string, or nothing\n if (i + 1 >= str.length || str[i + 1] === ' ') {\n return i;\n }\n }\n }\n return -1;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Parse a YAML string into a SuiteConfig. */\nexport function parseYaml(content: string): SuiteConfig {\n const lines = tokenize(content);\n if (lines.length === 0) {\n throw new Error('YAML is empty');\n }\n\n const [parsed] = parseBlock(lines, 0, lines[0].indent);\n return validate(parsed);\n}\n\n/** Parse a JSON string into a SuiteConfig. */\nexport function parseJson(content: string): SuiteConfig {\n const parsed = JSON.parse(content);\n return validate(parsed);\n}\n\n/** Read a file from disk and parse by extension. */\nexport function loadFile(filePath: string): SuiteConfig {\n const ext = path.extname(filePath).toLowerCase();\n\n if (ext !== '.json' && ext !== '.yaml' && ext !== '.yml') {\n throw new Error(`Unsupported file extension: ${ext}. Use .json, .yaml, or .yml`);\n }\n\n const content = fs.readFileSync(filePath, 'utf-8');\n\n if (ext === '.json') {\n return parseJson(content);\n }\n return parseYaml(content);\n}\n\n/**\n * Main entry point. Accepts a file path (string) or an inline SuiteConfig.\n * If a string is provided, delegates to loadFile.\n */\nexport function loadCases(source: string | SuiteConfig): SuiteConfig {\n if (typeof source === 'string') {\n return loadFile(source);\n }\n return validate(source);\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nfunction validate(data: unknown): SuiteConfig {\n if (typeof data !== 'object' || data === null) {\n throw new Error('Invalid suite config: expected an object');\n }\n\n const obj = data as Record<string, unknown>;\n\n if (!Array.isArray(obj.test_cases)) {\n throw new Error('Invalid suite config: missing test_cases array');\n }\n\n for (const tc of obj.test_cases) {\n if (typeof tc !== 'object' || tc === null) {\n throw new Error('Invalid test case: expected an object');\n }\n const c = tc as Record<string, unknown>;\n if (typeof c.id !== 'string') {\n throw new Error('Invalid test case: missing or non-string id');\n }\n if (typeof c.query !== 'string') {\n throw new Error('Invalid test case: missing or non-string query');\n }\n }\n\n return {\n name: typeof obj.name === 'string' ? obj.name : undefined,\n test_cases: obj.test_cases as TestCase[],\n };\n}\n","import { CaseResult, SuiteResult } from './types';\n\n/** Prints a single case result line to the console. */\nexport function printCaseResult(c: CaseResult): void {\n const status = c.passed ? 'PASS' : 'FAIL';\n const query = c.query.length > 50 ? c.query.slice(0, 47) + '...' : c.query;\n const latency = c.agentResult.latencyMs !== undefined\n ? `${(c.agentResult.latencyMs / 1000).toFixed(1)}s`\n : '';\n console.log(` ${c.id} ${query.padEnd(50)} ${status} ${latency}`);\n\n if (!c.passed) {\n for (const check of c.checks.results) {\n if (!check.passed) {\n console.log(` ${check.key}: ${check.details}`);\n }\n }\n }\n}\n\n/** Prints suite header. */\nexport function printSuiteHeader(name: string): void {\n console.log(`\\nSuite: ${name}`);\n console.log(`${'='.repeat(60)}`);\n}\n\n/** Prints suite summary footer. */\nexport function printSuiteSummary(result: SuiteResult): void {\n console.log(`\\n${result.passed}/${result.total} passed (${(result.duration / 1000).toFixed(1)}s)`);\n}\n\n/** Prints a full suite result (header + all cases + summary). */\nexport function printSuiteResult(result: SuiteResult): void {\n printSuiteHeader(result.name);\n for (const c of result.cases) {\n printCaseResult(c);\n }\n printSuiteSummary(result);\n}\n","import { runChecks, RunChecksInput } from '../checks/run-checks';\nimport { SuiteConfig, TestCase } from './schema';\nimport { AgentFn, AgentResult, CaseResult, SuiteResult } from './types';\nimport { loadCases } from './loader';\nimport { printCaseResult, printSuiteHeader, printSuiteSummary } from './reporter';\n\nexport interface RunSuiteOptions {\n cases: string | SuiteConfig;\n agent: AgentFn;\n name?: string;\n concurrency?: number;\n onCaseComplete?: (result: CaseResult) => void;\n /** Print results live to console. Default: true. */\n print?: boolean;\n}\n\n/** Run a suite of test cases against an agent function. */\nexport async function runSuite(options: RunSuiteOptions): Promise<SuiteResult> {\n const config = typeof options.cases === 'string'\n ? loadCases(options.cases)\n : loadCases(options.cases);\n\n const suiteName = options.name ?? config.name ?? 'unnamed';\n const concurrency = options.concurrency ?? 1;\n const shouldPrint = options.print !== false;\n const startTime = Date.now();\n\n if (shouldPrint) {\n printSuiteHeader(suiteName);\n }\n\n const cases = config.test_cases;\n const results: CaseResult[] = [];\n\n const onCase = (result: CaseResult): void => {\n if (shouldPrint) {\n printCaseResult(result);\n }\n options.onCaseComplete?.(result);\n };\n\n if (concurrency <= 1) {\n // Sequential execution\n for (const tc of cases) {\n const result = await runCase(tc, options.agent);\n results.push(result);\n onCase(result);\n }\n } else {\n // Concurrent execution with limited concurrency\n let idx = 0;\n const runNext = async (): Promise<void> => {\n while (idx < cases.length) {\n const currentIdx = idx++;\n const result = await runCase(cases[currentIdx], options.agent);\n results.push(result);\n onCase(result);\n }\n };\n\n const workers = Array.from(\n { length: Math.min(concurrency, cases.length) },\n () => runNext()\n );\n await Promise.all(workers);\n }\n\n const passedCount = results.filter((r) => r.passed).length;\n const duration = Date.now() - startTime;\n\n const suiteResult: SuiteResult = {\n name: suiteName,\n passed: passedCount,\n failed: results.length - passedCount,\n total: results.length,\n cases: results,\n duration,\n };\n\n if (shouldPrint) {\n printSuiteSummary(suiteResult);\n }\n\n return suiteResult;\n}\n\nasync function runCase(tc: TestCase, agent: AgentFn): Promise<CaseResult> {\n let agentResult: AgentResult;\n\n try {\n agentResult = await agent(tc.query);\n } catch (err) {\n // Agent error — mark as failed\n agentResult = {\n responseText: '',\n };\n return {\n id: tc.id,\n query: tc.query,\n passed: false,\n checks: {\n passed: false,\n results: [{\n key: 'agent_error',\n passed: false,\n details: `Agent threw: ${err instanceof Error ? err.message : String(err)}`,\n }],\n summary: '0/1 checks passed',\n },\n metadata: tc.metadata,\n agentResult,\n };\n }\n\n const checks = tc.checks ?? {};\n const input: RunChecksInput = {};\n\n // Map responseText\n input.responseText = agentResult.responseText;\n\n // Tool selection\n if (checks.expectedTools !== undefined) {\n input.expectedTools = checks.expectedTools;\n input.actualTools = agentResult.actualTools;\n }\n\n // Content match\n if (checks.mustContain !== undefined) {\n input.mustContain = checks.mustContain;\n }\n\n // Negative match\n if (checks.mustNotContain !== undefined) {\n input.mustNotContain = checks.mustNotContain;\n }\n\n // Latency\n if (agentResult.latencyMs !== undefined) {\n input.latencyMs = agentResult.latencyMs;\n }\n if (checks.thresholdMs !== undefined) {\n input.thresholdMs = checks.thresholdMs;\n }\n\n // JSON validity\n if (checks.json !== undefined) {\n input.json = {\n text: agentResult.responseText,\n requireObject: checks.json.requireObject,\n };\n }\n\n // Schema match\n if (checks.schema !== undefined) {\n try {\n const data = JSON.parse(agentResult.responseText);\n input.schema = {\n data,\n requiredKeys: checks.schema.requiredKeys,\n typeChecks: checks.schema.typeChecks,\n };\n } catch {\n // If JSON parse fails, still run schema check with empty object\n // so it reports a meaningful failure\n input.schema = {\n data: {} as Record<string, unknown>,\n requiredKeys: checks.schema.requiredKeys,\n typeChecks: checks.schema.typeChecks,\n };\n }\n }\n\n // Non-empty / cop-out phrases\n if (checks.copOutPhrases !== undefined) {\n input.copOutPhrases = checks.copOutPhrases;\n }\n\n // Length bounds\n if (checks.lengthMin !== undefined) {\n input.lengthMin = checks.lengthMin;\n }\n if (checks.lengthMax !== undefined) {\n input.lengthMax = checks.lengthMax;\n }\n\n // Regex\n if (checks.regexPatterns !== undefined) {\n input.regexPatterns = checks.regexPatterns.map((p) => new RegExp(p));\n }\n if (checks.regexMode !== undefined) {\n input.regexMode = checks.regexMode;\n }\n\n // Tool call count\n const toolCallCountValue = agentResult.toolCallCount ?? agentResult.actualTools?.length;\n if (toolCallCountValue !== undefined) {\n input.toolCallCountValue = toolCallCountValue;\n }\n if (checks.toolCallMin !== undefined) {\n input.toolCallMin = checks.toolCallMin;\n }\n if (checks.toolCallMax !== undefined) {\n input.toolCallMax = checks.toolCallMax;\n }\n\n // Cost budget\n if (agentResult.cost !== undefined) {\n input.costActual = agentResult.cost;\n }\n if (checks.costBudget !== undefined) {\n input.costBudget = checks.costBudget;\n }\n\n const suiteResult = runChecks(input);\n\n // A case with no checks always passes (smoke test)\n const passed = suiteResult.results.length === 0 ? true : suiteResult.passed;\n\n return {\n id: tc.id,\n query: tc.query,\n passed,\n checks: { ...suiteResult, passed },\n metadata: tc.metadata,\n agentResult,\n };\n}\n"],"mappings":"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","names":["path","fs","latency"]}
package/dist/index.d.cts CHANGED
@@ -392,11 +392,13 @@ interface RunSuiteOptions {
392
392
  name?: string;
393
393
  concurrency?: number;
394
394
  onCaseComplete?: (result: CaseResult) => void;
395
+ /** Print results live to console. Default: true. */
396
+ print?: boolean;
395
397
  }
396
398
  /** Run a suite of test cases against an agent function. */
397
399
  declare function runSuite(options: RunSuiteOptions): Promise<SuiteResult>;
398
400
 
399
- /** Prints a human-readable summary of a suite run to the console. */
401
+ /** Prints a full suite result (header + all cases + summary). */
400
402
  declare function printSuiteResult(result: SuiteResult): void;
401
403
 
402
404
  export { type AgentFn, type AgentResult, type CaseResult, type CheckSuiteResult, type ContentMatchResult, type CostBudgetResult, type EvalResult, type JsonValidResult, type LatencyResult, type LengthBoundsResult, type NegativeMatchResult, type NonEmptyResult, type RegexMatchResult, type RunSuiteOptions, type SchemaMatchResult, type SuiteConfig, type SuiteResult, type TestCase, type TestCaseChecks, type ToolCallCountResult, type ToolSelectionResult, contentMatch, costBudget, createContentMatchEvaluator, createCostBudgetEvaluator, createJsonValidEvaluator, createLatencyEvaluator, createLengthBoundsEvaluator, createNegativeMatchEvaluator, createNonEmptyEvaluator, createRegexMatchEvaluator, createSchemaMatchEvaluator, createToolCallCountEvaluator, createToolSelectionEvaluator, jsonValid, latency, lengthBounds, loadCases, loadFile, negativeMatch, nonEmpty, parseJson, parseYaml, printSuiteResult, regexMatch, runChecks, runSuite, schemaMatch, toolCallCount, toolSelection };
package/dist/index.d.ts CHANGED
@@ -392,11 +392,13 @@ interface RunSuiteOptions {
392
392
  name?: string;
393
393
  concurrency?: number;
394
394
  onCaseComplete?: (result: CaseResult) => void;
395
+ /** Print results live to console. Default: true. */
396
+ print?: boolean;
395
397
  }
396
398
  /** Run a suite of test cases against an agent function. */
397
399
  declare function runSuite(options: RunSuiteOptions): Promise<SuiteResult>;
398
400
 
399
- /** Prints a human-readable summary of a suite run to the console. */
401
+ /** Prints a full suite result (header + all cases + summary). */
400
402
  declare function printSuiteResult(result: SuiteResult): void;
401
403
 
402
404
  export { type AgentFn, type AgentResult, type CaseResult, type CheckSuiteResult, type ContentMatchResult, type CostBudgetResult, type EvalResult, type JsonValidResult, type LatencyResult, type LengthBoundsResult, type NegativeMatchResult, type NonEmptyResult, type RegexMatchResult, type RunSuiteOptions, type SchemaMatchResult, type SuiteConfig, type SuiteResult, type TestCase, type TestCaseChecks, type ToolCallCountResult, type ToolSelectionResult, contentMatch, costBudget, createContentMatchEvaluator, createCostBudgetEvaluator, createJsonValidEvaluator, createLatencyEvaluator, createLengthBoundsEvaluator, createNegativeMatchEvaluator, createNonEmptyEvaluator, createRegexMatchEvaluator, createSchemaMatchEvaluator, createToolCallCountEvaluator, createToolSelectionEvaluator, jsonValid, latency, lengthBounds, loadCases, loadFile, negativeMatch, nonEmpty, parseJson, parseYaml, printSuiteResult, regexMatch, runChecks, runSuite, schemaMatch, toolCallCount, toolSelection };
package/dist/index.js CHANGED
@@ -612,19 +612,60 @@ function validate(data) {
612
612
  };
613
613
  }
614
614
 
615
+ // src/runner/reporter.ts
616
+ function printCaseResult(c) {
617
+ const status = c.passed ? "PASS" : "FAIL";
618
+ const query = c.query.length > 50 ? c.query.slice(0, 47) + "..." : c.query;
619
+ const latency2 = c.agentResult.latencyMs !== void 0 ? `${(c.agentResult.latencyMs / 1e3).toFixed(1)}s` : "";
620
+ console.log(` ${c.id} ${query.padEnd(50)} ${status} ${latency2}`);
621
+ if (!c.passed) {
622
+ for (const check of c.checks.results) {
623
+ if (!check.passed) {
624
+ console.log(` ${check.key}: ${check.details}`);
625
+ }
626
+ }
627
+ }
628
+ }
629
+ function printSuiteHeader(name) {
630
+ console.log(`
631
+ Suite: ${name}`);
632
+ console.log(`${"=".repeat(60)}`);
633
+ }
634
+ function printSuiteSummary(result) {
635
+ console.log(`
636
+ ${result.passed}/${result.total} passed (${(result.duration / 1e3).toFixed(1)}s)`);
637
+ }
638
+ function printSuiteResult(result) {
639
+ printSuiteHeader(result.name);
640
+ for (const c of result.cases) {
641
+ printCaseResult(c);
642
+ }
643
+ printSuiteSummary(result);
644
+ }
645
+
615
646
  // src/runner/run-suite.ts
616
647
  async function runSuite(options) {
617
648
  const config = typeof options.cases === "string" ? loadCases(options.cases) : loadCases(options.cases);
618
649
  const suiteName = options.name ?? config.name ?? "unnamed";
619
650
  const concurrency = options.concurrency ?? 1;
651
+ const shouldPrint = options.print !== false;
620
652
  const startTime = Date.now();
653
+ if (shouldPrint) {
654
+ printSuiteHeader(suiteName);
655
+ }
621
656
  const cases = config.test_cases;
622
657
  const results = [];
658
+ const onCase = (result) => {
659
+ if (shouldPrint) {
660
+ printCaseResult(result);
661
+ }
662
+ options.onCaseComplete?.(result);
663
+ };
623
664
  if (concurrency <= 1) {
624
665
  for (const tc of cases) {
625
666
  const result = await runCase(tc, options.agent);
626
667
  results.push(result);
627
- options.onCaseComplete?.(result);
668
+ onCase(result);
628
669
  }
629
670
  } else {
630
671
  let idx = 0;
@@ -633,7 +674,7 @@ async function runSuite(options) {
633
674
  const currentIdx = idx++;
634
675
  const result = await runCase(cases[currentIdx], options.agent);
635
676
  results.push(result);
636
- options.onCaseComplete?.(result);
677
+ onCase(result);
637
678
  }
638
679
  };
639
680
  const workers = Array.from(
@@ -644,7 +685,7 @@ async function runSuite(options) {
644
685
  }
645
686
  const passedCount = results.filter((r) => r.passed).length;
646
687
  const duration = Date.now() - startTime;
647
- return {
688
+ const suiteResult = {
648
689
  name: suiteName,
649
690
  passed: passedCount,
650
691
  failed: results.length - passedCount,
@@ -652,6 +693,10 @@ async function runSuite(options) {
652
693
  cases: results,
653
694
  duration
654
695
  };
696
+ if (shouldPrint) {
697
+ printSuiteSummary(suiteResult);
698
+ }
699
+ return suiteResult;
655
700
  }
656
701
  async function runCase(tc, agent) {
657
702
  let agentResult;
@@ -761,28 +806,6 @@ async function runCase(tc, agent) {
761
806
  agentResult
762
807
  };
763
808
  }
764
-
765
- // src/runner/reporter.ts
766
- function printSuiteResult(result) {
767
- console.log(`
768
- Suite: ${result.name}`);
769
- console.log(`${"=".repeat(60)}`);
770
- for (const c of result.cases) {
771
- const status = c.passed ? "PASS" : "FAIL";
772
- const query = c.query.length > 50 ? c.query.slice(0, 47) + "..." : c.query;
773
- const latency2 = c.agentResult.latencyMs !== void 0 ? `${(c.agentResult.latencyMs / 1e3).toFixed(1)}s` : "";
774
- console.log(` ${c.id} ${query.padEnd(50)} ${status} ${latency2}`);
775
- if (!c.passed) {
776
- for (const check of c.checks.results) {
777
- if (!check.passed) {
778
- console.log(` ${check.key}: ${check.details}`);
779
- }
780
- }
781
- }
782
- }
783
- console.log(`
784
- ${result.passed}/${result.total} passed (${(result.duration / 1e3).toFixed(1)}s)`);
785
- }
786
809
  export {
787
810
  contentMatch,
788
811
  costBudget,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/checks/tool-selection.ts","../src/checks/content-match.ts","../src/checks/negative-match.ts","../src/checks/latency.ts","../src/checks/json-valid.ts","../src/checks/schema-match.ts","../src/checks/non-empty.ts","../src/checks/length-bounds.ts","../src/checks/regex-match.ts","../src/checks/tool-call-count.ts","../src/checks/cost-budget.ts","../src/checks/run-checks.ts","../src/runner/loader.ts","../src/runner/run-suite.ts","../src/runner/reporter.ts"],"sourcesContent":["import { ToolSelectionResult } from './types';\n\nexport interface ToolSelectionInput {\n expected: string[];\n actual: string[];\n}\n\n/**\n * Strict set equality between expected and actual tool names.\n * Order-independent, deduplicates. Reports missing and unexpected tools.\n */\nexport function toolSelection(input: ToolSelectionInput): ToolSelectionResult {\n const expectedSet = new Set(input.expected);\n const actualSet = new Set(input.actual);\n\n const missing = [...expectedSet].filter((t) => !actualSet.has(t));\n const extra = [...actualSet].filter((t) => !expectedSet.has(t));\n const passed = missing.length === 0 && extra.length === 0;\n\n let details = passed ? 'All expected tools matched' : '';\n\n if (missing.length > 0) {\n details += `Missing: ${missing.join(', ')}`;\n }\n\n if (extra.length > 0) {\n details += `${missing.length > 0 ? '. ' : ''}Unexpected: ${extra.join(', ')}`;\n }\n\n return {\n key: 'tool_selection',\n passed,\n details,\n expected: input.expected,\n actual: [...actualSet],\n };\n}\n\n/**\n * Factory: creates a reusable toolSelection evaluator with fixed expected tools.\n */\nexport function createToolSelectionEvaluator(config: { expected: string[] }) {\n return (input: { actual: string[] }): ToolSelectionResult =>\n toolSelection({ expected: config.expected, actual: input.actual });\n}\n","import { ContentMatchResult } from './types';\n\nexport interface ContentMatchInput {\n responseText: string;\n mustContain: string[];\n}\n\n/**\n * Case-insensitive substring matching: all mustContain strings must appear in responseText.\n */\nexport function contentMatch(input: ContentMatchInput): ContentMatchResult {\n if (!input.mustContain || input.mustContain.length === 0) {\n return {\n key: 'content_match',\n passed: true,\n details: 'No content requirements',\n missing: [],\n };\n }\n\n const lower = input.responseText.toLowerCase();\n const missing = input.mustContain.filter((s) => !lower.includes(s.toLowerCase()));\n const passed = missing.length === 0;\n\n return {\n key: 'content_match',\n passed,\n details: passed\n ? 'All required content found'\n : `Missing: ${missing.join(', ')}`,\n missing,\n };\n}\n\n/**\n * Factory: creates a reusable contentMatch evaluator with fixed mustContain list.\n */\nexport function createContentMatchEvaluator(config: { mustContain: string[] }) {\n return (input: { responseText: string }): ContentMatchResult =>\n contentMatch({ responseText: input.responseText, mustContain: config.mustContain });\n}\n","import { NegativeMatchResult } from './types';\n\nexport interface NegativeMatchInput {\n responseText: string;\n mustNotContain: string[];\n}\n\n/**\n * Case-insensitive substring matching: no mustNotContain strings may appear in responseText.\n */\nexport function negativeMatch(input: NegativeMatchInput): NegativeMatchResult {\n if (!input.mustNotContain || input.mustNotContain.length === 0) {\n return {\n key: 'negative_match',\n passed: true,\n details: 'No negative requirements',\n found: [],\n };\n }\n\n const lower = input.responseText.toLowerCase();\n const found = input.mustNotContain.filter((s) => lower.includes(s.toLowerCase()));\n const passed = found.length === 0;\n\n return {\n key: 'negative_match',\n passed,\n details: passed\n ? 'No forbidden content found'\n : `Found: ${found.join(', ')}`,\n found,\n };\n}\n\n/**\n * Factory: creates a reusable negativeMatch evaluator with fixed mustNotContain list.\n */\nexport function createNegativeMatchEvaluator(config: { mustNotContain: string[] }) {\n return (input: { responseText: string }): NegativeMatchResult =>\n negativeMatch({ responseText: input.responseText, mustNotContain: config.mustNotContain });\n}\n","import { LatencyResult } from './types';\n\nexport interface LatencyInput {\n latencyMs: number;\n thresholdMs?: number;\n}\n\n/**\n * Simple threshold check: latencyMs <= thresholdMs.\n * Default threshold: 20,000ms.\n */\nexport function latency(input: LatencyInput): LatencyResult {\n const threshold = input.thresholdMs ?? 20000;\n const passed = input.latencyMs <= threshold;\n\n return {\n key: 'latency',\n passed,\n details: passed\n ? `${input.latencyMs}ms within ${threshold}ms threshold`\n : `${input.latencyMs}ms exceeded ${threshold}ms threshold`,\n ms: input.latencyMs,\n threshold,\n };\n}\n\n/**\n * Factory: creates a reusable latency evaluator with a fixed threshold.\n */\nexport function createLatencyEvaluator(config: { thresholdMs: number }) {\n return (input: { latencyMs: number }): LatencyResult =>\n latency({ latencyMs: input.latencyMs, thresholdMs: config.thresholdMs });\n}\n","import { JsonValidResult } from './types';\n\nexport interface JsonValidInput {\n text: string;\n /** If true, the parsed result must be an object or array (not a bare primitive). */\n requireObject?: boolean;\n}\n\n/**\n * Validates that a string is parseable JSON.\n * Optionally checks that the parsed result is an object or array.\n */\nexport function jsonValid(input: JsonValidInput): JsonValidResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(input.text);\n } catch {\n return {\n key: 'json_valid',\n passed: false,\n details: 'Invalid JSON',\n };\n }\n\n if (input.requireObject && (parsed === null || typeof parsed !== 'object')) {\n return {\n key: 'json_valid',\n passed: false,\n details: `Expected object or array, got ${parsed === null ? 'null' : typeof parsed}`,\n };\n }\n\n return {\n key: 'json_valid',\n passed: true,\n details: 'Valid JSON',\n };\n}\n\n/**\n * Factory: creates a reusable jsonValid evaluator with fixed options.\n */\nexport function createJsonValidEvaluator(config?: { requireObject?: boolean }) {\n return (input: { text: string }): JsonValidResult =>\n jsonValid({ text: input.text, requireObject: config?.requireObject });\n}\n","import { SchemaMatchResult } from './types';\n\nexport interface SchemaMatchInput {\n /** The object to validate. */\n data: Record<string, unknown>;\n /** Keys that must be present. */\n requiredKeys: string[];\n /** Optional type constraints: key -> expected typeof result. */\n typeChecks?: Record<string, string>;\n}\n\n/**\n * Validates that a parsed object contains required keys and optionally checks value types.\n * Zero-dep — just checks key presence and typeof.\n */\nexport function schemaMatch(input: SchemaMatchInput): SchemaMatchResult {\n const missingKeys = input.requiredKeys.filter((k) => !(k in input.data));\n const typeErrors: string[] = [];\n\n if (input.typeChecks) {\n for (const [key, expectedType] of Object.entries(input.typeChecks)) {\n if (key in input.data) {\n const actualType = Array.isArray(input.data[key]) ? 'array' : typeof input.data[key];\n if (actualType !== expectedType) {\n typeErrors.push(`${key}: expected ${expectedType}, got ${actualType}`);\n }\n }\n }\n }\n\n const passed = missingKeys.length === 0 && typeErrors.length === 0;\n const problems: string[] = [];\n if (missingKeys.length > 0) problems.push(`Missing keys: ${missingKeys.join(', ')}`);\n if (typeErrors.length > 0) problems.push(`Type errors: ${typeErrors.join('; ')}`);\n\n return {\n key: 'schema_match',\n passed,\n details: passed ? 'Schema valid' : problems.join('. '),\n missingKeys,\n typeErrors,\n };\n}\n\n/**\n * Factory: creates a reusable schemaMatch evaluator with fixed schema expectations.\n */\nexport function createSchemaMatchEvaluator(config: {\n requiredKeys: string[];\n typeChecks?: Record<string, string>;\n}) {\n return (input: { data: Record<string, unknown> }): SchemaMatchResult =>\n schemaMatch({\n data: input.data,\n requiredKeys: config.requiredKeys,\n typeChecks: config.typeChecks,\n });\n}\n","import { NonEmptyResult } from './types';\n\nconst DEFAULT_COP_OUT_PHRASES = [\n \"i don't know\",\n \"n/a\",\n \"no information\",\n \"i'm not sure\",\n \"i cannot\",\n \"i can't\",\n \"no data available\",\n];\n\nexport interface NonEmptyInput {\n responseText: string;\n /** Custom cop-out phrases to check against (case-insensitive exact match after trim). */\n copOutPhrases?: string[];\n}\n\n/**\n * Checks that the response is not empty, not just whitespace, and not a cop-out phrase.\n */\nexport function nonEmpty(input: NonEmptyInput): NonEmptyResult {\n const trimmed = input.responseText.trim();\n\n if (trimmed.length === 0) {\n return {\n key: 'non_empty',\n passed: false,\n details: 'Response is empty',\n };\n }\n\n const phrases = input.copOutPhrases ?? DEFAULT_COP_OUT_PHRASES;\n const lower = trimmed.toLowerCase();\n const matchedPhrase = phrases.find((p) => lower === p.toLowerCase());\n\n if (matchedPhrase) {\n return {\n key: 'non_empty',\n passed: false,\n details: `Response is a cop-out phrase: \"${matchedPhrase}\"`,\n };\n }\n\n return {\n key: 'non_empty',\n passed: true,\n details: 'Response is non-empty',\n };\n}\n\n/**\n * Factory: creates a reusable nonEmpty evaluator with fixed cop-out phrases.\n */\nexport function createNonEmptyEvaluator(config?: { copOutPhrases?: string[] }) {\n return (input: { responseText: string }): NonEmptyResult =>\n nonEmpty({ responseText: input.responseText, copOutPhrases: config?.copOutPhrases });\n}\n","import { LengthBoundsResult } from './types';\n\nexport interface LengthBoundsInput {\n responseText: string;\n min?: number;\n max?: number;\n}\n\n/**\n * Checks that the response length (character count) falls within min and/or max bounds.\n */\nexport function lengthBounds(input: LengthBoundsInput): LengthBoundsResult {\n const length = input.responseText.length;\n let passed = true;\n const problems: string[] = [];\n\n if (input.min !== undefined && length < input.min) {\n passed = false;\n problems.push(`${length} chars below minimum ${input.min}`);\n }\n\n if (input.max !== undefined && length > input.max) {\n passed = false;\n problems.push(`${length} chars above maximum ${input.max}`);\n }\n\n return {\n key: 'length_bounds',\n passed,\n details: passed\n ? `${length} chars within bounds`\n : problems.join('. '),\n length,\n min: input.min,\n max: input.max,\n };\n}\n\n/**\n * Factory: creates a reusable lengthBounds evaluator with fixed bounds.\n */\nexport function createLengthBoundsEvaluator(config: { min?: number; max?: number }) {\n return (input: { responseText: string }): LengthBoundsResult =>\n lengthBounds({ responseText: input.responseText, min: config.min, max: config.max });\n}\n","import { RegexMatchResult } from './types';\n\nexport interface RegexMatchInput {\n responseText: string;\n patterns: (string | RegExp)[];\n /** If 'all' (default), every pattern must match. If 'any', at least one must match. */\n mode?: 'all' | 'any';\n}\n\n/**\n * Tests the response against one or more regex patterns.\n */\nexport function regexMatch(input: RegexMatchInput): RegexMatchResult {\n const mode = input.mode ?? 'all';\n const failedPatterns: string[] = [];\n\n for (const pattern of input.patterns) {\n const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\n if (!regex.test(input.responseText)) {\n failedPatterns.push(String(pattern));\n }\n }\n\n let passed: boolean;\n if (mode === 'all') {\n passed = failedPatterns.length === 0;\n } else {\n // 'any' mode: pass if at least one pattern matched\n passed = failedPatterns.length < input.patterns.length;\n }\n\n let details: string;\n if (passed) {\n details = mode === 'all'\n ? 'All patterns matched'\n : 'At least one pattern matched';\n } else {\n details = mode === 'all'\n ? `Failed patterns: ${failedPatterns.join(', ')}`\n : 'No patterns matched';\n }\n\n return {\n key: 'regex_match',\n passed,\n details,\n failedPatterns,\n };\n}\n\n/**\n * Factory: creates a reusable regexMatch evaluator with fixed patterns and mode.\n */\nexport function createRegexMatchEvaluator(config: {\n patterns: (string | RegExp)[];\n mode?: 'all' | 'any';\n}) {\n return (input: { responseText: string }): RegexMatchResult =>\n regexMatch({ responseText: input.responseText, patterns: config.patterns, mode: config.mode });\n}\n","import { ToolCallCountResult } from './types';\n\nexport interface ToolCallCountInput {\n count: number;\n min?: number;\n max?: number;\n}\n\n/**\n * Checks that the number of tool calls falls within expected min/max bounds.\n */\nexport function toolCallCount(input: ToolCallCountInput): ToolCallCountResult {\n let passed = true;\n const problems: string[] = [];\n\n if (input.min !== undefined && input.count < input.min) {\n passed = false;\n problems.push(`${input.count} calls below minimum ${input.min}`);\n }\n\n if (input.max !== undefined && input.count > input.max) {\n passed = false;\n problems.push(`${input.count} calls above maximum ${input.max}`);\n }\n\n return {\n key: 'tool_call_count',\n passed,\n details: passed\n ? `${input.count} tool calls within bounds`\n : problems.join('. '),\n count: input.count,\n min: input.min,\n max: input.max,\n };\n}\n\n/**\n * Factory: creates a reusable toolCallCount evaluator with fixed bounds.\n */\nexport function createToolCallCountEvaluator(config: { min?: number; max?: number }) {\n return (input: { count: number }): ToolCallCountResult =>\n toolCallCount({ count: input.count, min: config.min, max: config.max });\n}\n","import { CostBudgetResult } from './types';\n\nexport interface CostBudgetInput {\n /** Actual cost — can be token count or dollar amount. */\n actual: number;\n /** Budget threshold — same unit as actual. */\n budget: number;\n}\n\n/**\n * Checks that token count or dollar cost stays under a threshold.\n */\nexport function costBudget(input: CostBudgetInput): CostBudgetResult {\n const passed = input.actual <= input.budget;\n\n return {\n key: 'cost_budget',\n passed,\n details: passed\n ? `${input.actual} within budget of ${input.budget}`\n : `${input.actual} exceeded budget of ${input.budget}`,\n actual: input.actual,\n budget: input.budget,\n };\n}\n\n/**\n * Factory: creates a reusable costBudget evaluator with a fixed budget.\n */\nexport function createCostBudgetEvaluator(config: { budget: number }) {\n return (input: { actual: number }): CostBudgetResult =>\n costBudget({ actual: input.actual, budget: config.budget });\n}\n","import { CheckSuiteResult, EvalResult } from './types';\nimport { toolSelection } from './tool-selection';\nimport { contentMatch } from './content-match';\nimport { negativeMatch } from './negative-match';\nimport { latency } from './latency';\nimport { jsonValid } from './json-valid';\nimport { schemaMatch } from './schema-match';\nimport { nonEmpty } from './non-empty';\nimport { lengthBounds } from './length-bounds';\nimport { regexMatch } from './regex-match';\nimport { toolCallCount } from './tool-call-count';\nimport { costBudget } from './cost-budget';\n\nexport interface RunChecksInput {\n responseText?: string;\n expectedTools?: string[];\n actualTools?: string[];\n mustContain?: string[];\n mustNotContain?: string[];\n latencyMs?: number;\n thresholdMs?: number;\n json?: { text: string; requireObject?: boolean };\n schema?: { data: Record<string, unknown>; requiredKeys: string[]; typeChecks?: Record<string, string> };\n copOutPhrases?: string[];\n lengthMin?: number;\n lengthMax?: number;\n regexPatterns?: (string | RegExp)[];\n regexMode?: 'all' | 'any';\n toolCallCountValue?: number;\n toolCallMin?: number;\n toolCallMax?: number;\n costActual?: number;\n costBudget?: number;\n}\n\n/**\n * Runs any combination of checks at once and returns a summary.\n * Only runs checks for which the relevant inputs are provided.\n */\nexport function runChecks(input: RunChecksInput): CheckSuiteResult {\n const results: EvalResult[] = [];\n\n if (input.expectedTools !== undefined && input.actualTools !== undefined) {\n results.push(toolSelection({ expected: input.expectedTools, actual: input.actualTools }));\n }\n\n if (input.mustContain !== undefined && input.responseText !== undefined) {\n results.push(contentMatch({ responseText: input.responseText, mustContain: input.mustContain }));\n }\n\n if (input.mustNotContain !== undefined && input.responseText !== undefined) {\n results.push(negativeMatch({ responseText: input.responseText, mustNotContain: input.mustNotContain }));\n }\n\n if (input.latencyMs !== undefined) {\n results.push(latency({ latencyMs: input.latencyMs, thresholdMs: input.thresholdMs }));\n }\n\n if (input.json !== undefined) {\n results.push(jsonValid(input.json));\n }\n\n if (input.schema !== undefined) {\n results.push(schemaMatch(input.schema));\n }\n\n if (input.copOutPhrases !== undefined && input.responseText !== undefined) {\n results.push(nonEmpty({ responseText: input.responseText, copOutPhrases: input.copOutPhrases }));\n }\n\n if ((input.lengthMin !== undefined || input.lengthMax !== undefined) && input.responseText !== undefined) {\n results.push(lengthBounds({ responseText: input.responseText, min: input.lengthMin, max: input.lengthMax }));\n }\n\n if (input.regexPatterns !== undefined && input.responseText !== undefined) {\n results.push(regexMatch({ responseText: input.responseText, patterns: input.regexPatterns, mode: input.regexMode }));\n }\n\n if (input.toolCallCountValue !== undefined) {\n results.push(toolCallCount({ count: input.toolCallCountValue, min: input.toolCallMin, max: input.toolCallMax }));\n }\n\n if (input.costActual !== undefined && input.costBudget !== undefined) {\n results.push(costBudget({ actual: input.costActual, budget: input.costBudget }));\n }\n\n const passedCount = results.filter((r) => r.passed).length;\n const allPassed = results.length > 0 && passedCount === results.length;\n\n return {\n passed: allPassed,\n results,\n summary: `${passedCount}/${results.length} checks passed`,\n };\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { SuiteConfig, TestCase } from './schema';\n\n// ---------------------------------------------------------------------------\n// Minimal YAML parser — handles the subset needed for test-case files\n// ---------------------------------------------------------------------------\n\ninterface YamlLine {\n indent: number;\n raw: string;\n content: string; // after stripping indent\n}\n\nfunction stripComment(line: string): string {\n // Walk the string respecting quoted regions\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === '#' && !inSingle && !inDouble) {\n return line.slice(0, i).trimEnd();\n }\n }\n return line;\n}\n\nfunction tokenize(content: string): YamlLine[] {\n const lines: YamlLine[] = [];\n for (const raw of content.split('\\n')) {\n const stripped = stripComment(raw);\n if (stripped.trim() === '') continue;\n const indent = stripped.search(/\\S/);\n if (indent === -1) continue;\n lines.push({ indent, raw: stripped, content: stripped.slice(indent) });\n }\n return lines;\n}\n\nfunction parseScalar(value: string): string | number | boolean | null {\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value === 'null' || value === '~') return null;\n\n // Quoted strings\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n return value.slice(1, -1);\n }\n\n // Numbers\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return Number(value);\n }\n\n return value;\n}\n\nfunction parseFlowArray(raw: string): unknown[] {\n // raw is like \"[item1, item2, ...]\"\n const inner = raw.slice(1, -1).trim();\n if (inner === '') return [];\n\n const items: string[] = [];\n let current = '';\n let depth = 0;\n let inSingle = false;\n let inDouble = false;\n\n for (let i = 0; i < inner.length; i++) {\n const ch = inner[i];\n if (ch === \"'\" && !inDouble) { inSingle = !inSingle; current += ch; }\n else if (ch === '\"' && !inSingle) { inDouble = !inDouble; current += ch; }\n else if (ch === '[' && !inSingle && !inDouble) { depth++; current += ch; }\n else if (ch === ']' && !inSingle && !inDouble) { depth--; current += ch; }\n else if (ch === ',' && depth === 0 && !inSingle && !inDouble) {\n items.push(current.trim());\n current = '';\n } else {\n current += ch;\n }\n }\n if (current.trim() !== '') items.push(current.trim());\n\n return items.map((item) => parseScalar(item));\n}\n\nfunction parseBlock(lines: YamlLine[], start: number, baseIndent: number): [Record<string, unknown>, number] {\n const result: Record<string, unknown> = {};\n let i = start;\n\n while (i < lines.length && lines[i].indent >= baseIndent) {\n const line = lines[i];\n\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) break; // belongs to a parent or sibling block\n\n const content = line.content;\n\n // Sequence item at mapping level: \"- key: value\" or \"- value\"\n if (content.startsWith('- ')) {\n break; // sequences are handled by the caller\n }\n\n // Key-value pair\n const colonIdx = findColon(content);\n if (colonIdx === -1) {\n throw new Error(`YAML parse error: expected key-value pair, got \"${content}\"`);\n }\n\n const key = content.slice(0, colonIdx).trim();\n const valueRaw = content.slice(colonIdx + 1).trim();\n\n if (valueRaw === '' || valueRaw === '') {\n // Check next line for nested content\n if (i + 1 < lines.length && lines[i + 1].indent > baseIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n\n if (nextContent.startsWith('- ')) {\n // Block sequence\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n result[key] = arr;\n i = newI;\n } else {\n // Nested mapping\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n result[key] = nested;\n i = newI;\n }\n } else {\n result[key] = null;\n i++;\n }\n } else if (valueRaw.startsWith('[') && valueRaw.endsWith(']')) {\n result[key] = parseFlowArray(valueRaw);\n i++;\n } else {\n result[key] = parseScalar(valueRaw);\n i++;\n }\n }\n\n return [result, i];\n}\n\nfunction parseSequence(lines: YamlLine[], start: number, baseIndent: number): [unknown[], number] {\n const result: unknown[] = [];\n let i = start;\n\n while (i < lines.length && lines[i].indent >= baseIndent) {\n const line = lines[i];\n\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) break;\n\n const content = line.content;\n if (!content.startsWith('- ')) break;\n\n const afterDash = content.slice(2).trim();\n\n if (afterDash === '') {\n // Nested block under sequence item\n if (i + 1 < lines.length && lines[i + 1].indent > baseIndent) {\n const nextIndent = lines[i + 1].indent;\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n result.push(nested);\n i = newI;\n } else {\n result.push(null);\n i++;\n }\n } else if (findColon(afterDash) !== -1 && !afterDash.startsWith('\"') && !afterDash.startsWith(\"'\")) {\n // Inline mapping: \"- key: value\\n more_key: value\"\n // Rewrite as a mapping starting at dash + 2 indent\n const inlineIndent = line.indent + 2;\n // Parse the first key-value from afterDash\n const colonIdx = findColon(afterDash);\n const firstKey = afterDash.slice(0, colonIdx).trim();\n const firstValRaw = afterDash.slice(colonIdx + 1).trim();\n const obj: Record<string, unknown> = {};\n\n if (firstValRaw === '') {\n // Nested value on the next lines\n if (i + 1 < lines.length && lines[i + 1].indent > inlineIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n if (nextContent.startsWith('- ')) {\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n obj[firstKey] = arr;\n i = newI;\n } else {\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n obj[firstKey] = nested;\n i = newI;\n }\n } else {\n obj[firstKey] = null;\n i++;\n }\n } else if (firstValRaw.startsWith('[') && firstValRaw.endsWith(']')) {\n obj[firstKey] = parseFlowArray(firstValRaw);\n i++;\n } else {\n obj[firstKey] = parseScalar(firstValRaw);\n i++;\n }\n\n // Continue reading sibling keys at inlineIndent\n while (i < lines.length && lines[i].indent === inlineIndent) {\n const sibContent = lines[i].content;\n if (sibContent.startsWith('- ')) break;\n\n const sibColon = findColon(sibContent);\n if (sibColon === -1) break;\n\n const sibKey = sibContent.slice(0, sibColon).trim();\n const sibValRaw = sibContent.slice(sibColon + 1).trim();\n\n if (sibValRaw === '') {\n if (i + 1 < lines.length && lines[i + 1].indent > inlineIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n if (nextContent.startsWith('- ')) {\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n obj[sibKey] = arr;\n i = newI;\n } else {\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n obj[sibKey] = nested;\n i = newI;\n }\n } else {\n obj[sibKey] = null;\n i++;\n }\n } else if (sibValRaw.startsWith('[') && sibValRaw.endsWith(']')) {\n obj[sibKey] = parseFlowArray(sibValRaw);\n i++;\n } else {\n obj[sibKey] = parseScalar(sibValRaw);\n i++;\n }\n }\n\n result.push(obj);\n } else if (afterDash.startsWith('[') && afterDash.endsWith(']')) {\n result.push(parseFlowArray(afterDash));\n i++;\n } else {\n result.push(parseScalar(afterDash));\n i++;\n }\n }\n\n return [result, i];\n}\n\n/** Find the first colon that is not inside quotes. */\nfunction findColon(str: string): number {\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < str.length; i++) {\n const ch = str[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === ':' && !inSingle && !inDouble) {\n // A YAML key colon must be followed by space, end-of-string, or nothing\n if (i + 1 >= str.length || str[i + 1] === ' ') {\n return i;\n }\n }\n }\n return -1;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Parse a YAML string into a SuiteConfig. */\nexport function parseYaml(content: string): SuiteConfig {\n const lines = tokenize(content);\n if (lines.length === 0) {\n throw new Error('YAML is empty');\n }\n\n const [parsed] = parseBlock(lines, 0, lines[0].indent);\n return validate(parsed);\n}\n\n/** Parse a JSON string into a SuiteConfig. */\nexport function parseJson(content: string): SuiteConfig {\n const parsed = JSON.parse(content);\n return validate(parsed);\n}\n\n/** Read a file from disk and parse by extension. */\nexport function loadFile(filePath: string): SuiteConfig {\n const ext = path.extname(filePath).toLowerCase();\n\n if (ext !== '.json' && ext !== '.yaml' && ext !== '.yml') {\n throw new Error(`Unsupported file extension: ${ext}. Use .json, .yaml, or .yml`);\n }\n\n const content = fs.readFileSync(filePath, 'utf-8');\n\n if (ext === '.json') {\n return parseJson(content);\n }\n return parseYaml(content);\n}\n\n/**\n * Main entry point. Accepts a file path (string) or an inline SuiteConfig.\n * If a string is provided, delegates to loadFile.\n */\nexport function loadCases(source: string | SuiteConfig): SuiteConfig {\n if (typeof source === 'string') {\n return loadFile(source);\n }\n return validate(source);\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nfunction validate(data: unknown): SuiteConfig {\n if (typeof data !== 'object' || data === null) {\n throw new Error('Invalid suite config: expected an object');\n }\n\n const obj = data as Record<string, unknown>;\n\n if (!Array.isArray(obj.test_cases)) {\n throw new Error('Invalid suite config: missing test_cases array');\n }\n\n for (const tc of obj.test_cases) {\n if (typeof tc !== 'object' || tc === null) {\n throw new Error('Invalid test case: expected an object');\n }\n const c = tc as Record<string, unknown>;\n if (typeof c.id !== 'string') {\n throw new Error('Invalid test case: missing or non-string id');\n }\n if (typeof c.query !== 'string') {\n throw new Error('Invalid test case: missing or non-string query');\n }\n }\n\n return {\n name: typeof obj.name === 'string' ? obj.name : undefined,\n test_cases: obj.test_cases as TestCase[],\n };\n}\n","import { runChecks, RunChecksInput } from '../checks/run-checks';\nimport { SuiteConfig, TestCase } from './schema';\nimport { AgentFn, AgentResult, CaseResult, SuiteResult } from './types';\nimport { loadCases } from './loader';\n\nexport interface RunSuiteOptions {\n cases: string | SuiteConfig;\n agent: AgentFn;\n name?: string;\n concurrency?: number;\n onCaseComplete?: (result: CaseResult) => void;\n}\n\n/** Run a suite of test cases against an agent function. */\nexport async function runSuite(options: RunSuiteOptions): Promise<SuiteResult> {\n const config = typeof options.cases === 'string'\n ? loadCases(options.cases)\n : loadCases(options.cases);\n\n const suiteName = options.name ?? config.name ?? 'unnamed';\n const concurrency = options.concurrency ?? 1;\n const startTime = Date.now();\n\n const cases = config.test_cases;\n const results: CaseResult[] = [];\n\n if (concurrency <= 1) {\n // Sequential execution\n for (const tc of cases) {\n const result = await runCase(tc, options.agent);\n results.push(result);\n options.onCaseComplete?.(result);\n }\n } else {\n // Concurrent execution with limited concurrency\n let idx = 0;\n const runNext = async (): Promise<void> => {\n while (idx < cases.length) {\n const currentIdx = idx++;\n const result = await runCase(cases[currentIdx], options.agent);\n results.push(result);\n options.onCaseComplete?.(result);\n }\n };\n\n const workers = Array.from(\n { length: Math.min(concurrency, cases.length) },\n () => runNext()\n );\n await Promise.all(workers);\n }\n\n const passedCount = results.filter((r) => r.passed).length;\n const duration = Date.now() - startTime;\n\n return {\n name: suiteName,\n passed: passedCount,\n failed: results.length - passedCount,\n total: results.length,\n cases: results,\n duration,\n };\n}\n\nasync function runCase(tc: TestCase, agent: AgentFn): Promise<CaseResult> {\n let agentResult: AgentResult;\n\n try {\n agentResult = await agent(tc.query);\n } catch (err) {\n // Agent error — mark as failed\n agentResult = {\n responseText: '',\n };\n return {\n id: tc.id,\n query: tc.query,\n passed: false,\n checks: {\n passed: false,\n results: [{\n key: 'agent_error',\n passed: false,\n details: `Agent threw: ${err instanceof Error ? err.message : String(err)}`,\n }],\n summary: '0/1 checks passed',\n },\n metadata: tc.metadata,\n agentResult,\n };\n }\n\n const checks = tc.checks ?? {};\n const input: RunChecksInput = {};\n\n // Map responseText\n input.responseText = agentResult.responseText;\n\n // Tool selection\n if (checks.expectedTools !== undefined) {\n input.expectedTools = checks.expectedTools;\n input.actualTools = agentResult.actualTools;\n }\n\n // Content match\n if (checks.mustContain !== undefined) {\n input.mustContain = checks.mustContain;\n }\n\n // Negative match\n if (checks.mustNotContain !== undefined) {\n input.mustNotContain = checks.mustNotContain;\n }\n\n // Latency\n if (agentResult.latencyMs !== undefined) {\n input.latencyMs = agentResult.latencyMs;\n }\n if (checks.thresholdMs !== undefined) {\n input.thresholdMs = checks.thresholdMs;\n }\n\n // JSON validity\n if (checks.json !== undefined) {\n input.json = {\n text: agentResult.responseText,\n requireObject: checks.json.requireObject,\n };\n }\n\n // Schema match\n if (checks.schema !== undefined) {\n try {\n const data = JSON.parse(agentResult.responseText);\n input.schema = {\n data,\n requiredKeys: checks.schema.requiredKeys,\n typeChecks: checks.schema.typeChecks,\n };\n } catch {\n // If JSON parse fails, still run schema check with empty object\n // so it reports a meaningful failure\n input.schema = {\n data: {} as Record<string, unknown>,\n requiredKeys: checks.schema.requiredKeys,\n typeChecks: checks.schema.typeChecks,\n };\n }\n }\n\n // Non-empty / cop-out phrases\n if (checks.copOutPhrases !== undefined) {\n input.copOutPhrases = checks.copOutPhrases;\n }\n\n // Length bounds\n if (checks.lengthMin !== undefined) {\n input.lengthMin = checks.lengthMin;\n }\n if (checks.lengthMax !== undefined) {\n input.lengthMax = checks.lengthMax;\n }\n\n // Regex\n if (checks.regexPatterns !== undefined) {\n input.regexPatterns = checks.regexPatterns.map((p) => new RegExp(p));\n }\n if (checks.regexMode !== undefined) {\n input.regexMode = checks.regexMode;\n }\n\n // Tool call count\n const toolCallCountValue = agentResult.toolCallCount ?? agentResult.actualTools?.length;\n if (toolCallCountValue !== undefined) {\n input.toolCallCountValue = toolCallCountValue;\n }\n if (checks.toolCallMin !== undefined) {\n input.toolCallMin = checks.toolCallMin;\n }\n if (checks.toolCallMax !== undefined) {\n input.toolCallMax = checks.toolCallMax;\n }\n\n // Cost budget\n if (agentResult.cost !== undefined) {\n input.costActual = agentResult.cost;\n }\n if (checks.costBudget !== undefined) {\n input.costBudget = checks.costBudget;\n }\n\n const suiteResult = runChecks(input);\n\n // A case with no checks always passes (smoke test)\n const passed = suiteResult.results.length === 0 ? true : suiteResult.passed;\n\n return {\n id: tc.id,\n query: tc.query,\n passed,\n checks: { ...suiteResult, passed },\n metadata: tc.metadata,\n agentResult,\n };\n}\n","import { SuiteResult } from './types';\n\n/** Prints a human-readable summary of a suite run to the console. */\nexport function printSuiteResult(result: SuiteResult): void {\n console.log(`\\nSuite: ${result.name}`);\n console.log(`${'='.repeat(60)}`);\n\n for (const c of result.cases) {\n const status = c.passed ? 'PASS' : 'FAIL';\n const query = c.query.length > 50 ? c.query.slice(0, 47) + '...' : c.query;\n const latency = c.agentResult.latencyMs !== undefined\n ? `${(c.agentResult.latencyMs / 1000).toFixed(1)}s`\n : '';\n console.log(` ${c.id} ${query.padEnd(50)} ${status} ${latency}`);\n\n if (!c.passed) {\n for (const check of c.checks.results) {\n if (!check.passed) {\n console.log(` ${check.key}: ${check.details}`);\n }\n }\n }\n }\n\n console.log(`\\n${result.passed}/${result.total} passed (${(result.duration / 1000).toFixed(1)}s)`);\n}\n"],"mappings":"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","names":["latency"]}
1
+ {"version":3,"sources":["../src/checks/tool-selection.ts","../src/checks/content-match.ts","../src/checks/negative-match.ts","../src/checks/latency.ts","../src/checks/json-valid.ts","../src/checks/schema-match.ts","../src/checks/non-empty.ts","../src/checks/length-bounds.ts","../src/checks/regex-match.ts","../src/checks/tool-call-count.ts","../src/checks/cost-budget.ts","../src/checks/run-checks.ts","../src/runner/loader.ts","../src/runner/reporter.ts","../src/runner/run-suite.ts"],"sourcesContent":["import { ToolSelectionResult } from './types';\n\nexport interface ToolSelectionInput {\n expected: string[];\n actual: string[];\n}\n\n/**\n * Strict set equality between expected and actual tool names.\n * Order-independent, deduplicates. Reports missing and unexpected tools.\n */\nexport function toolSelection(input: ToolSelectionInput): ToolSelectionResult {\n const expectedSet = new Set(input.expected);\n const actualSet = new Set(input.actual);\n\n const missing = [...expectedSet].filter((t) => !actualSet.has(t));\n const extra = [...actualSet].filter((t) => !expectedSet.has(t));\n const passed = missing.length === 0 && extra.length === 0;\n\n let details = passed ? 'All expected tools matched' : '';\n\n if (missing.length > 0) {\n details += `Missing: ${missing.join(', ')}`;\n }\n\n if (extra.length > 0) {\n details += `${missing.length > 0 ? '. ' : ''}Unexpected: ${extra.join(', ')}`;\n }\n\n return {\n key: 'tool_selection',\n passed,\n details,\n expected: input.expected,\n actual: [...actualSet],\n };\n}\n\n/**\n * Factory: creates a reusable toolSelection evaluator with fixed expected tools.\n */\nexport function createToolSelectionEvaluator(config: { expected: string[] }) {\n return (input: { actual: string[] }): ToolSelectionResult =>\n toolSelection({ expected: config.expected, actual: input.actual });\n}\n","import { ContentMatchResult } from './types';\n\nexport interface ContentMatchInput {\n responseText: string;\n mustContain: string[];\n}\n\n/**\n * Case-insensitive substring matching: all mustContain strings must appear in responseText.\n */\nexport function contentMatch(input: ContentMatchInput): ContentMatchResult {\n if (!input.mustContain || input.mustContain.length === 0) {\n return {\n key: 'content_match',\n passed: true,\n details: 'No content requirements',\n missing: [],\n };\n }\n\n const lower = input.responseText.toLowerCase();\n const missing = input.mustContain.filter((s) => !lower.includes(s.toLowerCase()));\n const passed = missing.length === 0;\n\n return {\n key: 'content_match',\n passed,\n details: passed\n ? 'All required content found'\n : `Missing: ${missing.join(', ')}`,\n missing,\n };\n}\n\n/**\n * Factory: creates a reusable contentMatch evaluator with fixed mustContain list.\n */\nexport function createContentMatchEvaluator(config: { mustContain: string[] }) {\n return (input: { responseText: string }): ContentMatchResult =>\n contentMatch({ responseText: input.responseText, mustContain: config.mustContain });\n}\n","import { NegativeMatchResult } from './types';\n\nexport interface NegativeMatchInput {\n responseText: string;\n mustNotContain: string[];\n}\n\n/**\n * Case-insensitive substring matching: no mustNotContain strings may appear in responseText.\n */\nexport function negativeMatch(input: NegativeMatchInput): NegativeMatchResult {\n if (!input.mustNotContain || input.mustNotContain.length === 0) {\n return {\n key: 'negative_match',\n passed: true,\n details: 'No negative requirements',\n found: [],\n };\n }\n\n const lower = input.responseText.toLowerCase();\n const found = input.mustNotContain.filter((s) => lower.includes(s.toLowerCase()));\n const passed = found.length === 0;\n\n return {\n key: 'negative_match',\n passed,\n details: passed\n ? 'No forbidden content found'\n : `Found: ${found.join(', ')}`,\n found,\n };\n}\n\n/**\n * Factory: creates a reusable negativeMatch evaluator with fixed mustNotContain list.\n */\nexport function createNegativeMatchEvaluator(config: { mustNotContain: string[] }) {\n return (input: { responseText: string }): NegativeMatchResult =>\n negativeMatch({ responseText: input.responseText, mustNotContain: config.mustNotContain });\n}\n","import { LatencyResult } from './types';\n\nexport interface LatencyInput {\n latencyMs: number;\n thresholdMs?: number;\n}\n\n/**\n * Simple threshold check: latencyMs <= thresholdMs.\n * Default threshold: 20,000ms.\n */\nexport function latency(input: LatencyInput): LatencyResult {\n const threshold = input.thresholdMs ?? 20000;\n const passed = input.latencyMs <= threshold;\n\n return {\n key: 'latency',\n passed,\n details: passed\n ? `${input.latencyMs}ms within ${threshold}ms threshold`\n : `${input.latencyMs}ms exceeded ${threshold}ms threshold`,\n ms: input.latencyMs,\n threshold,\n };\n}\n\n/**\n * Factory: creates a reusable latency evaluator with a fixed threshold.\n */\nexport function createLatencyEvaluator(config: { thresholdMs: number }) {\n return (input: { latencyMs: number }): LatencyResult =>\n latency({ latencyMs: input.latencyMs, thresholdMs: config.thresholdMs });\n}\n","import { JsonValidResult } from './types';\n\nexport interface JsonValidInput {\n text: string;\n /** If true, the parsed result must be an object or array (not a bare primitive). */\n requireObject?: boolean;\n}\n\n/**\n * Validates that a string is parseable JSON.\n * Optionally checks that the parsed result is an object or array.\n */\nexport function jsonValid(input: JsonValidInput): JsonValidResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(input.text);\n } catch {\n return {\n key: 'json_valid',\n passed: false,\n details: 'Invalid JSON',\n };\n }\n\n if (input.requireObject && (parsed === null || typeof parsed !== 'object')) {\n return {\n key: 'json_valid',\n passed: false,\n details: `Expected object or array, got ${parsed === null ? 'null' : typeof parsed}`,\n };\n }\n\n return {\n key: 'json_valid',\n passed: true,\n details: 'Valid JSON',\n };\n}\n\n/**\n * Factory: creates a reusable jsonValid evaluator with fixed options.\n */\nexport function createJsonValidEvaluator(config?: { requireObject?: boolean }) {\n return (input: { text: string }): JsonValidResult =>\n jsonValid({ text: input.text, requireObject: config?.requireObject });\n}\n","import { SchemaMatchResult } from './types';\n\nexport interface SchemaMatchInput {\n /** The object to validate. */\n data: Record<string, unknown>;\n /** Keys that must be present. */\n requiredKeys: string[];\n /** Optional type constraints: key -> expected typeof result. */\n typeChecks?: Record<string, string>;\n}\n\n/**\n * Validates that a parsed object contains required keys and optionally checks value types.\n * Zero-dep — just checks key presence and typeof.\n */\nexport function schemaMatch(input: SchemaMatchInput): SchemaMatchResult {\n const missingKeys = input.requiredKeys.filter((k) => !(k in input.data));\n const typeErrors: string[] = [];\n\n if (input.typeChecks) {\n for (const [key, expectedType] of Object.entries(input.typeChecks)) {\n if (key in input.data) {\n const actualType = Array.isArray(input.data[key]) ? 'array' : typeof input.data[key];\n if (actualType !== expectedType) {\n typeErrors.push(`${key}: expected ${expectedType}, got ${actualType}`);\n }\n }\n }\n }\n\n const passed = missingKeys.length === 0 && typeErrors.length === 0;\n const problems: string[] = [];\n if (missingKeys.length > 0) problems.push(`Missing keys: ${missingKeys.join(', ')}`);\n if (typeErrors.length > 0) problems.push(`Type errors: ${typeErrors.join('; ')}`);\n\n return {\n key: 'schema_match',\n passed,\n details: passed ? 'Schema valid' : problems.join('. '),\n missingKeys,\n typeErrors,\n };\n}\n\n/**\n * Factory: creates a reusable schemaMatch evaluator with fixed schema expectations.\n */\nexport function createSchemaMatchEvaluator(config: {\n requiredKeys: string[];\n typeChecks?: Record<string, string>;\n}) {\n return (input: { data: Record<string, unknown> }): SchemaMatchResult =>\n schemaMatch({\n data: input.data,\n requiredKeys: config.requiredKeys,\n typeChecks: config.typeChecks,\n });\n}\n","import { NonEmptyResult } from './types';\n\nconst DEFAULT_COP_OUT_PHRASES = [\n \"i don't know\",\n \"n/a\",\n \"no information\",\n \"i'm not sure\",\n \"i cannot\",\n \"i can't\",\n \"no data available\",\n];\n\nexport interface NonEmptyInput {\n responseText: string;\n /** Custom cop-out phrases to check against (case-insensitive exact match after trim). */\n copOutPhrases?: string[];\n}\n\n/**\n * Checks that the response is not empty, not just whitespace, and not a cop-out phrase.\n */\nexport function nonEmpty(input: NonEmptyInput): NonEmptyResult {\n const trimmed = input.responseText.trim();\n\n if (trimmed.length === 0) {\n return {\n key: 'non_empty',\n passed: false,\n details: 'Response is empty',\n };\n }\n\n const phrases = input.copOutPhrases ?? DEFAULT_COP_OUT_PHRASES;\n const lower = trimmed.toLowerCase();\n const matchedPhrase = phrases.find((p) => lower === p.toLowerCase());\n\n if (matchedPhrase) {\n return {\n key: 'non_empty',\n passed: false,\n details: `Response is a cop-out phrase: \"${matchedPhrase}\"`,\n };\n }\n\n return {\n key: 'non_empty',\n passed: true,\n details: 'Response is non-empty',\n };\n}\n\n/**\n * Factory: creates a reusable nonEmpty evaluator with fixed cop-out phrases.\n */\nexport function createNonEmptyEvaluator(config?: { copOutPhrases?: string[] }) {\n return (input: { responseText: string }): NonEmptyResult =>\n nonEmpty({ responseText: input.responseText, copOutPhrases: config?.copOutPhrases });\n}\n","import { LengthBoundsResult } from './types';\n\nexport interface LengthBoundsInput {\n responseText: string;\n min?: number;\n max?: number;\n}\n\n/**\n * Checks that the response length (character count) falls within min and/or max bounds.\n */\nexport function lengthBounds(input: LengthBoundsInput): LengthBoundsResult {\n const length = input.responseText.length;\n let passed = true;\n const problems: string[] = [];\n\n if (input.min !== undefined && length < input.min) {\n passed = false;\n problems.push(`${length} chars below minimum ${input.min}`);\n }\n\n if (input.max !== undefined && length > input.max) {\n passed = false;\n problems.push(`${length} chars above maximum ${input.max}`);\n }\n\n return {\n key: 'length_bounds',\n passed,\n details: passed\n ? `${length} chars within bounds`\n : problems.join('. '),\n length,\n min: input.min,\n max: input.max,\n };\n}\n\n/**\n * Factory: creates a reusable lengthBounds evaluator with fixed bounds.\n */\nexport function createLengthBoundsEvaluator(config: { min?: number; max?: number }) {\n return (input: { responseText: string }): LengthBoundsResult =>\n lengthBounds({ responseText: input.responseText, min: config.min, max: config.max });\n}\n","import { RegexMatchResult } from './types';\n\nexport interface RegexMatchInput {\n responseText: string;\n patterns: (string | RegExp)[];\n /** If 'all' (default), every pattern must match. If 'any', at least one must match. */\n mode?: 'all' | 'any';\n}\n\n/**\n * Tests the response against one or more regex patterns.\n */\nexport function regexMatch(input: RegexMatchInput): RegexMatchResult {\n const mode = input.mode ?? 'all';\n const failedPatterns: string[] = [];\n\n for (const pattern of input.patterns) {\n const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\n if (!regex.test(input.responseText)) {\n failedPatterns.push(String(pattern));\n }\n }\n\n let passed: boolean;\n if (mode === 'all') {\n passed = failedPatterns.length === 0;\n } else {\n // 'any' mode: pass if at least one pattern matched\n passed = failedPatterns.length < input.patterns.length;\n }\n\n let details: string;\n if (passed) {\n details = mode === 'all'\n ? 'All patterns matched'\n : 'At least one pattern matched';\n } else {\n details = mode === 'all'\n ? `Failed patterns: ${failedPatterns.join(', ')}`\n : 'No patterns matched';\n }\n\n return {\n key: 'regex_match',\n passed,\n details,\n failedPatterns,\n };\n}\n\n/**\n * Factory: creates a reusable regexMatch evaluator with fixed patterns and mode.\n */\nexport function createRegexMatchEvaluator(config: {\n patterns: (string | RegExp)[];\n mode?: 'all' | 'any';\n}) {\n return (input: { responseText: string }): RegexMatchResult =>\n regexMatch({ responseText: input.responseText, patterns: config.patterns, mode: config.mode });\n}\n","import { ToolCallCountResult } from './types';\n\nexport interface ToolCallCountInput {\n count: number;\n min?: number;\n max?: number;\n}\n\n/**\n * Checks that the number of tool calls falls within expected min/max bounds.\n */\nexport function toolCallCount(input: ToolCallCountInput): ToolCallCountResult {\n let passed = true;\n const problems: string[] = [];\n\n if (input.min !== undefined && input.count < input.min) {\n passed = false;\n problems.push(`${input.count} calls below minimum ${input.min}`);\n }\n\n if (input.max !== undefined && input.count > input.max) {\n passed = false;\n problems.push(`${input.count} calls above maximum ${input.max}`);\n }\n\n return {\n key: 'tool_call_count',\n passed,\n details: passed\n ? `${input.count} tool calls within bounds`\n : problems.join('. '),\n count: input.count,\n min: input.min,\n max: input.max,\n };\n}\n\n/**\n * Factory: creates a reusable toolCallCount evaluator with fixed bounds.\n */\nexport function createToolCallCountEvaluator(config: { min?: number; max?: number }) {\n return (input: { count: number }): ToolCallCountResult =>\n toolCallCount({ count: input.count, min: config.min, max: config.max });\n}\n","import { CostBudgetResult } from './types';\n\nexport interface CostBudgetInput {\n /** Actual cost — can be token count or dollar amount. */\n actual: number;\n /** Budget threshold — same unit as actual. */\n budget: number;\n}\n\n/**\n * Checks that token count or dollar cost stays under a threshold.\n */\nexport function costBudget(input: CostBudgetInput): CostBudgetResult {\n const passed = input.actual <= input.budget;\n\n return {\n key: 'cost_budget',\n passed,\n details: passed\n ? `${input.actual} within budget of ${input.budget}`\n : `${input.actual} exceeded budget of ${input.budget}`,\n actual: input.actual,\n budget: input.budget,\n };\n}\n\n/**\n * Factory: creates a reusable costBudget evaluator with a fixed budget.\n */\nexport function createCostBudgetEvaluator(config: { budget: number }) {\n return (input: { actual: number }): CostBudgetResult =>\n costBudget({ actual: input.actual, budget: config.budget });\n}\n","import { CheckSuiteResult, EvalResult } from './types';\nimport { toolSelection } from './tool-selection';\nimport { contentMatch } from './content-match';\nimport { negativeMatch } from './negative-match';\nimport { latency } from './latency';\nimport { jsonValid } from './json-valid';\nimport { schemaMatch } from './schema-match';\nimport { nonEmpty } from './non-empty';\nimport { lengthBounds } from './length-bounds';\nimport { regexMatch } from './regex-match';\nimport { toolCallCount } from './tool-call-count';\nimport { costBudget } from './cost-budget';\n\nexport interface RunChecksInput {\n responseText?: string;\n expectedTools?: string[];\n actualTools?: string[];\n mustContain?: string[];\n mustNotContain?: string[];\n latencyMs?: number;\n thresholdMs?: number;\n json?: { text: string; requireObject?: boolean };\n schema?: { data: Record<string, unknown>; requiredKeys: string[]; typeChecks?: Record<string, string> };\n copOutPhrases?: string[];\n lengthMin?: number;\n lengthMax?: number;\n regexPatterns?: (string | RegExp)[];\n regexMode?: 'all' | 'any';\n toolCallCountValue?: number;\n toolCallMin?: number;\n toolCallMax?: number;\n costActual?: number;\n costBudget?: number;\n}\n\n/**\n * Runs any combination of checks at once and returns a summary.\n * Only runs checks for which the relevant inputs are provided.\n */\nexport function runChecks(input: RunChecksInput): CheckSuiteResult {\n const results: EvalResult[] = [];\n\n if (input.expectedTools !== undefined && input.actualTools !== undefined) {\n results.push(toolSelection({ expected: input.expectedTools, actual: input.actualTools }));\n }\n\n if (input.mustContain !== undefined && input.responseText !== undefined) {\n results.push(contentMatch({ responseText: input.responseText, mustContain: input.mustContain }));\n }\n\n if (input.mustNotContain !== undefined && input.responseText !== undefined) {\n results.push(negativeMatch({ responseText: input.responseText, mustNotContain: input.mustNotContain }));\n }\n\n if (input.latencyMs !== undefined) {\n results.push(latency({ latencyMs: input.latencyMs, thresholdMs: input.thresholdMs }));\n }\n\n if (input.json !== undefined) {\n results.push(jsonValid(input.json));\n }\n\n if (input.schema !== undefined) {\n results.push(schemaMatch(input.schema));\n }\n\n if (input.copOutPhrases !== undefined && input.responseText !== undefined) {\n results.push(nonEmpty({ responseText: input.responseText, copOutPhrases: input.copOutPhrases }));\n }\n\n if ((input.lengthMin !== undefined || input.lengthMax !== undefined) && input.responseText !== undefined) {\n results.push(lengthBounds({ responseText: input.responseText, min: input.lengthMin, max: input.lengthMax }));\n }\n\n if (input.regexPatterns !== undefined && input.responseText !== undefined) {\n results.push(regexMatch({ responseText: input.responseText, patterns: input.regexPatterns, mode: input.regexMode }));\n }\n\n if (input.toolCallCountValue !== undefined) {\n results.push(toolCallCount({ count: input.toolCallCountValue, min: input.toolCallMin, max: input.toolCallMax }));\n }\n\n if (input.costActual !== undefined && input.costBudget !== undefined) {\n results.push(costBudget({ actual: input.costActual, budget: input.costBudget }));\n }\n\n const passedCount = results.filter((r) => r.passed).length;\n const allPassed = results.length > 0 && passedCount === results.length;\n\n return {\n passed: allPassed,\n results,\n summary: `${passedCount}/${results.length} checks passed`,\n };\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { SuiteConfig, TestCase } from './schema';\n\n// ---------------------------------------------------------------------------\n// Minimal YAML parser — handles the subset needed for test-case files\n// ---------------------------------------------------------------------------\n\ninterface YamlLine {\n indent: number;\n raw: string;\n content: string; // after stripping indent\n}\n\nfunction stripComment(line: string): string {\n // Walk the string respecting quoted regions\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === '#' && !inSingle && !inDouble) {\n return line.slice(0, i).trimEnd();\n }\n }\n return line;\n}\n\nfunction tokenize(content: string): YamlLine[] {\n const lines: YamlLine[] = [];\n for (const raw of content.split('\\n')) {\n const stripped = stripComment(raw);\n if (stripped.trim() === '') continue;\n const indent = stripped.search(/\\S/);\n if (indent === -1) continue;\n lines.push({ indent, raw: stripped, content: stripped.slice(indent) });\n }\n return lines;\n}\n\nfunction parseScalar(value: string): string | number | boolean | null {\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value === 'null' || value === '~') return null;\n\n // Quoted strings\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n return value.slice(1, -1);\n }\n\n // Numbers\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return Number(value);\n }\n\n return value;\n}\n\nfunction parseFlowArray(raw: string): unknown[] {\n // raw is like \"[item1, item2, ...]\"\n const inner = raw.slice(1, -1).trim();\n if (inner === '') return [];\n\n const items: string[] = [];\n let current = '';\n let depth = 0;\n let inSingle = false;\n let inDouble = false;\n\n for (let i = 0; i < inner.length; i++) {\n const ch = inner[i];\n if (ch === \"'\" && !inDouble) { inSingle = !inSingle; current += ch; }\n else if (ch === '\"' && !inSingle) { inDouble = !inDouble; current += ch; }\n else if (ch === '[' && !inSingle && !inDouble) { depth++; current += ch; }\n else if (ch === ']' && !inSingle && !inDouble) { depth--; current += ch; }\n else if (ch === ',' && depth === 0 && !inSingle && !inDouble) {\n items.push(current.trim());\n current = '';\n } else {\n current += ch;\n }\n }\n if (current.trim() !== '') items.push(current.trim());\n\n return items.map((item) => parseScalar(item));\n}\n\nfunction parseBlock(lines: YamlLine[], start: number, baseIndent: number): [Record<string, unknown>, number] {\n const result: Record<string, unknown> = {};\n let i = start;\n\n while (i < lines.length && lines[i].indent >= baseIndent) {\n const line = lines[i];\n\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) break; // belongs to a parent or sibling block\n\n const content = line.content;\n\n // Sequence item at mapping level: \"- key: value\" or \"- value\"\n if (content.startsWith('- ')) {\n break; // sequences are handled by the caller\n }\n\n // Key-value pair\n const colonIdx = findColon(content);\n if (colonIdx === -1) {\n throw new Error(`YAML parse error: expected key-value pair, got \"${content}\"`);\n }\n\n const key = content.slice(0, colonIdx).trim();\n const valueRaw = content.slice(colonIdx + 1).trim();\n\n if (valueRaw === '' || valueRaw === '') {\n // Check next line for nested content\n if (i + 1 < lines.length && lines[i + 1].indent > baseIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n\n if (nextContent.startsWith('- ')) {\n // Block sequence\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n result[key] = arr;\n i = newI;\n } else {\n // Nested mapping\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n result[key] = nested;\n i = newI;\n }\n } else {\n result[key] = null;\n i++;\n }\n } else if (valueRaw.startsWith('[') && valueRaw.endsWith(']')) {\n result[key] = parseFlowArray(valueRaw);\n i++;\n } else {\n result[key] = parseScalar(valueRaw);\n i++;\n }\n }\n\n return [result, i];\n}\n\nfunction parseSequence(lines: YamlLine[], start: number, baseIndent: number): [unknown[], number] {\n const result: unknown[] = [];\n let i = start;\n\n while (i < lines.length && lines[i].indent >= baseIndent) {\n const line = lines[i];\n\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) break;\n\n const content = line.content;\n if (!content.startsWith('- ')) break;\n\n const afterDash = content.slice(2).trim();\n\n if (afterDash === '') {\n // Nested block under sequence item\n if (i + 1 < lines.length && lines[i + 1].indent > baseIndent) {\n const nextIndent = lines[i + 1].indent;\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n result.push(nested);\n i = newI;\n } else {\n result.push(null);\n i++;\n }\n } else if (findColon(afterDash) !== -1 && !afterDash.startsWith('\"') && !afterDash.startsWith(\"'\")) {\n // Inline mapping: \"- key: value\\n more_key: value\"\n // Rewrite as a mapping starting at dash + 2 indent\n const inlineIndent = line.indent + 2;\n // Parse the first key-value from afterDash\n const colonIdx = findColon(afterDash);\n const firstKey = afterDash.slice(0, colonIdx).trim();\n const firstValRaw = afterDash.slice(colonIdx + 1).trim();\n const obj: Record<string, unknown> = {};\n\n if (firstValRaw === '') {\n // Nested value on the next lines\n if (i + 1 < lines.length && lines[i + 1].indent > inlineIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n if (nextContent.startsWith('- ')) {\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n obj[firstKey] = arr;\n i = newI;\n } else {\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n obj[firstKey] = nested;\n i = newI;\n }\n } else {\n obj[firstKey] = null;\n i++;\n }\n } else if (firstValRaw.startsWith('[') && firstValRaw.endsWith(']')) {\n obj[firstKey] = parseFlowArray(firstValRaw);\n i++;\n } else {\n obj[firstKey] = parseScalar(firstValRaw);\n i++;\n }\n\n // Continue reading sibling keys at inlineIndent\n while (i < lines.length && lines[i].indent === inlineIndent) {\n const sibContent = lines[i].content;\n if (sibContent.startsWith('- ')) break;\n\n const sibColon = findColon(sibContent);\n if (sibColon === -1) break;\n\n const sibKey = sibContent.slice(0, sibColon).trim();\n const sibValRaw = sibContent.slice(sibColon + 1).trim();\n\n if (sibValRaw === '') {\n if (i + 1 < lines.length && lines[i + 1].indent > inlineIndent) {\n const nextIndent = lines[i + 1].indent;\n const nextContent = lines[i + 1].content;\n if (nextContent.startsWith('- ')) {\n const [arr, newI] = parseSequence(lines, i + 1, nextIndent);\n obj[sibKey] = arr;\n i = newI;\n } else {\n const [nested, newI] = parseBlock(lines, i + 1, nextIndent);\n obj[sibKey] = nested;\n i = newI;\n }\n } else {\n obj[sibKey] = null;\n i++;\n }\n } else if (sibValRaw.startsWith('[') && sibValRaw.endsWith(']')) {\n obj[sibKey] = parseFlowArray(sibValRaw);\n i++;\n } else {\n obj[sibKey] = parseScalar(sibValRaw);\n i++;\n }\n }\n\n result.push(obj);\n } else if (afterDash.startsWith('[') && afterDash.endsWith(']')) {\n result.push(parseFlowArray(afterDash));\n i++;\n } else {\n result.push(parseScalar(afterDash));\n i++;\n }\n }\n\n return [result, i];\n}\n\n/** Find the first colon that is not inside quotes. */\nfunction findColon(str: string): number {\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < str.length; i++) {\n const ch = str[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === ':' && !inSingle && !inDouble) {\n // A YAML key colon must be followed by space, end-of-string, or nothing\n if (i + 1 >= str.length || str[i + 1] === ' ') {\n return i;\n }\n }\n }\n return -1;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Parse a YAML string into a SuiteConfig. */\nexport function parseYaml(content: string): SuiteConfig {\n const lines = tokenize(content);\n if (lines.length === 0) {\n throw new Error('YAML is empty');\n }\n\n const [parsed] = parseBlock(lines, 0, lines[0].indent);\n return validate(parsed);\n}\n\n/** Parse a JSON string into a SuiteConfig. */\nexport function parseJson(content: string): SuiteConfig {\n const parsed = JSON.parse(content);\n return validate(parsed);\n}\n\n/** Read a file from disk and parse by extension. */\nexport function loadFile(filePath: string): SuiteConfig {\n const ext = path.extname(filePath).toLowerCase();\n\n if (ext !== '.json' && ext !== '.yaml' && ext !== '.yml') {\n throw new Error(`Unsupported file extension: ${ext}. Use .json, .yaml, or .yml`);\n }\n\n const content = fs.readFileSync(filePath, 'utf-8');\n\n if (ext === '.json') {\n return parseJson(content);\n }\n return parseYaml(content);\n}\n\n/**\n * Main entry point. Accepts a file path (string) or an inline SuiteConfig.\n * If a string is provided, delegates to loadFile.\n */\nexport function loadCases(source: string | SuiteConfig): SuiteConfig {\n if (typeof source === 'string') {\n return loadFile(source);\n }\n return validate(source);\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nfunction validate(data: unknown): SuiteConfig {\n if (typeof data !== 'object' || data === null) {\n throw new Error('Invalid suite config: expected an object');\n }\n\n const obj = data as Record<string, unknown>;\n\n if (!Array.isArray(obj.test_cases)) {\n throw new Error('Invalid suite config: missing test_cases array');\n }\n\n for (const tc of obj.test_cases) {\n if (typeof tc !== 'object' || tc === null) {\n throw new Error('Invalid test case: expected an object');\n }\n const c = tc as Record<string, unknown>;\n if (typeof c.id !== 'string') {\n throw new Error('Invalid test case: missing or non-string id');\n }\n if (typeof c.query !== 'string') {\n throw new Error('Invalid test case: missing or non-string query');\n }\n }\n\n return {\n name: typeof obj.name === 'string' ? obj.name : undefined,\n test_cases: obj.test_cases as TestCase[],\n };\n}\n","import { CaseResult, SuiteResult } from './types';\n\n/** Prints a single case result line to the console. */\nexport function printCaseResult(c: CaseResult): void {\n const status = c.passed ? 'PASS' : 'FAIL';\n const query = c.query.length > 50 ? c.query.slice(0, 47) + '...' : c.query;\n const latency = c.agentResult.latencyMs !== undefined\n ? `${(c.agentResult.latencyMs / 1000).toFixed(1)}s`\n : '';\n console.log(` ${c.id} ${query.padEnd(50)} ${status} ${latency}`);\n\n if (!c.passed) {\n for (const check of c.checks.results) {\n if (!check.passed) {\n console.log(` ${check.key}: ${check.details}`);\n }\n }\n }\n}\n\n/** Prints suite header. */\nexport function printSuiteHeader(name: string): void {\n console.log(`\\nSuite: ${name}`);\n console.log(`${'='.repeat(60)}`);\n}\n\n/** Prints suite summary footer. */\nexport function printSuiteSummary(result: SuiteResult): void {\n console.log(`\\n${result.passed}/${result.total} passed (${(result.duration / 1000).toFixed(1)}s)`);\n}\n\n/** Prints a full suite result (header + all cases + summary). */\nexport function printSuiteResult(result: SuiteResult): void {\n printSuiteHeader(result.name);\n for (const c of result.cases) {\n printCaseResult(c);\n }\n printSuiteSummary(result);\n}\n","import { runChecks, RunChecksInput } from '../checks/run-checks';\nimport { SuiteConfig, TestCase } from './schema';\nimport { AgentFn, AgentResult, CaseResult, SuiteResult } from './types';\nimport { loadCases } from './loader';\nimport { printCaseResult, printSuiteHeader, printSuiteSummary } from './reporter';\n\nexport interface RunSuiteOptions {\n cases: string | SuiteConfig;\n agent: AgentFn;\n name?: string;\n concurrency?: number;\n onCaseComplete?: (result: CaseResult) => void;\n /** Print results live to console. Default: true. */\n print?: boolean;\n}\n\n/** Run a suite of test cases against an agent function. */\nexport async function runSuite(options: RunSuiteOptions): Promise<SuiteResult> {\n const config = typeof options.cases === 'string'\n ? loadCases(options.cases)\n : loadCases(options.cases);\n\n const suiteName = options.name ?? config.name ?? 'unnamed';\n const concurrency = options.concurrency ?? 1;\n const shouldPrint = options.print !== false;\n const startTime = Date.now();\n\n if (shouldPrint) {\n printSuiteHeader(suiteName);\n }\n\n const cases = config.test_cases;\n const results: CaseResult[] = [];\n\n const onCase = (result: CaseResult): void => {\n if (shouldPrint) {\n printCaseResult(result);\n }\n options.onCaseComplete?.(result);\n };\n\n if (concurrency <= 1) {\n // Sequential execution\n for (const tc of cases) {\n const result = await runCase(tc, options.agent);\n results.push(result);\n onCase(result);\n }\n } else {\n // Concurrent execution with limited concurrency\n let idx = 0;\n const runNext = async (): Promise<void> => {\n while (idx < cases.length) {\n const currentIdx = idx++;\n const result = await runCase(cases[currentIdx], options.agent);\n results.push(result);\n onCase(result);\n }\n };\n\n const workers = Array.from(\n { length: Math.min(concurrency, cases.length) },\n () => runNext()\n );\n await Promise.all(workers);\n }\n\n const passedCount = results.filter((r) => r.passed).length;\n const duration = Date.now() - startTime;\n\n const suiteResult: SuiteResult = {\n name: suiteName,\n passed: passedCount,\n failed: results.length - passedCount,\n total: results.length,\n cases: results,\n duration,\n };\n\n if (shouldPrint) {\n printSuiteSummary(suiteResult);\n }\n\n return suiteResult;\n}\n\nasync function runCase(tc: TestCase, agent: AgentFn): Promise<CaseResult> {\n let agentResult: AgentResult;\n\n try {\n agentResult = await agent(tc.query);\n } catch (err) {\n // Agent error — mark as failed\n agentResult = {\n responseText: '',\n };\n return {\n id: tc.id,\n query: tc.query,\n passed: false,\n checks: {\n passed: false,\n results: [{\n key: 'agent_error',\n passed: false,\n details: `Agent threw: ${err instanceof Error ? err.message : String(err)}`,\n }],\n summary: '0/1 checks passed',\n },\n metadata: tc.metadata,\n agentResult,\n };\n }\n\n const checks = tc.checks ?? {};\n const input: RunChecksInput = {};\n\n // Map responseText\n input.responseText = agentResult.responseText;\n\n // Tool selection\n if (checks.expectedTools !== undefined) {\n input.expectedTools = checks.expectedTools;\n input.actualTools = agentResult.actualTools;\n }\n\n // Content match\n if (checks.mustContain !== undefined) {\n input.mustContain = checks.mustContain;\n }\n\n // Negative match\n if (checks.mustNotContain !== undefined) {\n input.mustNotContain = checks.mustNotContain;\n }\n\n // Latency\n if (agentResult.latencyMs !== undefined) {\n input.latencyMs = agentResult.latencyMs;\n }\n if (checks.thresholdMs !== undefined) {\n input.thresholdMs = checks.thresholdMs;\n }\n\n // JSON validity\n if (checks.json !== undefined) {\n input.json = {\n text: agentResult.responseText,\n requireObject: checks.json.requireObject,\n };\n }\n\n // Schema match\n if (checks.schema !== undefined) {\n try {\n const data = JSON.parse(agentResult.responseText);\n input.schema = {\n data,\n requiredKeys: checks.schema.requiredKeys,\n typeChecks: checks.schema.typeChecks,\n };\n } catch {\n // If JSON parse fails, still run schema check with empty object\n // so it reports a meaningful failure\n input.schema = {\n data: {} as Record<string, unknown>,\n requiredKeys: checks.schema.requiredKeys,\n typeChecks: checks.schema.typeChecks,\n };\n }\n }\n\n // Non-empty / cop-out phrases\n if (checks.copOutPhrases !== undefined) {\n input.copOutPhrases = checks.copOutPhrases;\n }\n\n // Length bounds\n if (checks.lengthMin !== undefined) {\n input.lengthMin = checks.lengthMin;\n }\n if (checks.lengthMax !== undefined) {\n input.lengthMax = checks.lengthMax;\n }\n\n // Regex\n if (checks.regexPatterns !== undefined) {\n input.regexPatterns = checks.regexPatterns.map((p) => new RegExp(p));\n }\n if (checks.regexMode !== undefined) {\n input.regexMode = checks.regexMode;\n }\n\n // Tool call count\n const toolCallCountValue = agentResult.toolCallCount ?? agentResult.actualTools?.length;\n if (toolCallCountValue !== undefined) {\n input.toolCallCountValue = toolCallCountValue;\n }\n if (checks.toolCallMin !== undefined) {\n input.toolCallMin = checks.toolCallMin;\n }\n if (checks.toolCallMax !== undefined) {\n input.toolCallMax = checks.toolCallMax;\n }\n\n // Cost budget\n if (agentResult.cost !== undefined) {\n input.costActual = agentResult.cost;\n }\n if (checks.costBudget !== undefined) {\n input.costBudget = checks.costBudget;\n }\n\n const suiteResult = runChecks(input);\n\n // A case with no checks always passes (smoke test)\n const passed = suiteResult.results.length === 0 ? true : suiteResult.passed;\n\n return {\n id: tc.id,\n query: tc.query,\n passed,\n checks: { ...suiteResult, passed },\n metadata: tc.metadata,\n agentResult,\n };\n}\n"],"mappings":"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","names":["latency"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evalkit",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Lightweight deterministic evaluators for AI agents. Binary pass/fail checks, zero dependencies, no LLM cost.",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -41,11 +41,11 @@
41
41
  "author": "Walid Khori",
42
42
  "repository": {
43
43
  "type": "git",
44
- "url": "https://github.com/wkhori/agentcheck.git"
44
+ "url": "https://github.com/wkhori/evalkit.git"
45
45
  },
46
- "homepage": "https://github.com/wkhori/agentcheck#readme",
46
+ "homepage": "https://github.com/wkhori/evalkit#readme",
47
47
  "bugs": {
48
- "url": "https://github.com/wkhori/agentcheck/issues"
48
+ "url": "https://github.com/wkhori/evalkit/issues"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^25.3.2",