evalkit 0.1.1 → 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 +5 -7
- package/dist/index.cjs +48 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +48 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,8 +15,9 @@ npm install evalkit
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
import { runSuite
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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