@ikarmic/forge-eval 0.0.0-stage → 4.4.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/LICENSE +21 -0
- package/README.md +95 -2
- package/dist/bin/forge-eval.d.ts +3 -0
- package/dist/bin/forge-eval.d.ts.map +1 -0
- package/dist/bin/forge-eval.js +138 -0
- package/dist/bin/forge-eval.js.map +1 -0
- package/dist/graders/behavior.d.ts +57 -0
- package/dist/graders/behavior.d.ts.map +1 -0
- package/dist/graders/behavior.js +154 -0
- package/dist/graders/behavior.js.map +1 -0
- package/dist/graders/content.d.ts +59 -0
- package/dist/graders/content.d.ts.map +1 -0
- package/dist/graders/content.js +135 -0
- package/dist/graders/content.js.map +1 -0
- package/dist/graders/fs.d.ts +24 -0
- package/dist/graders/fs.d.ts.map +1 -0
- package/dist/graders/fs.js +134 -0
- package/dist/graders/fs.js.map +1 -0
- package/dist/graders/index.d.ts +15 -0
- package/dist/graders/index.d.ts.map +1 -0
- package/dist/graders/index.js +19 -0
- package/dist/graders/index.js.map +1 -0
- package/dist/graders/metrics.d.ts +59 -0
- package/dist/graders/metrics.d.ts.map +1 -0
- package/dist/graders/metrics.js +70 -0
- package/dist/graders/metrics.js.map +1 -0
- package/dist/graders/schema.d.ts +87 -0
- package/dist/graders/schema.d.ts.map +1 -0
- package/dist/graders/schema.js +157 -0
- package/dist/graders/schema.js.map +1 -0
- package/dist/graders/shell.d.ts +36 -0
- package/dist/graders/shell.d.ts.map +1 -0
- package/dist/graders/shell.js +72 -0
- package/dist/graders/shell.js.map +1 -0
- package/dist/graders/text.d.ts +28 -0
- package/dist/graders/text.d.ts.map +1 -0
- package/dist/graders/text.js +70 -0
- package/dist/graders/text.js.map +1 -0
- package/dist/graders/types.d.ts +76 -0
- package/dist/graders/types.d.ts.map +1 -0
- package/dist/graders/types.js +2 -0
- package/dist/graders/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/report.d.ts +39 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +120 -0
- package/dist/report.js.map +1 -0
- package/dist/runner.d.ts +59 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +207 -0
- package/dist/runner.js.map +1 -0
- package/dist/tasks/edit.remove-section.d.ts +15 -0
- package/dist/tasks/edit.remove-section.d.ts.map +1 -0
- package/dist/tasks/edit.remove-section.js +31 -0
- package/dist/tasks/edit.remove-section.js.map +1 -0
- package/dist/tasks/edit.rename-symbol.d.ts +16 -0
- package/dist/tasks/edit.rename-symbol.d.ts.map +1 -0
- package/dist/tasks/edit.rename-symbol.js +34 -0
- package/dist/tasks/edit.rename-symbol.js.map +1 -0
- package/dist/tasks/edit.replace-greeting.d.ts +16 -0
- package/dist/tasks/edit.replace-greeting.d.ts.map +1 -0
- package/dist/tasks/edit.replace-greeting.js +29 -0
- package/dist/tasks/edit.replace-greeting.js.map +1 -0
- package/dist/tasks/edit.write-hello.d.ts +15 -0
- package/dist/tasks/edit.write-hello.d.ts.map +1 -0
- package/dist/tasks/edit.write-hello.js +22 -0
- package/dist/tasks/edit.write-hello.js.map +1 -0
- package/dist/tasks/index.d.ts +9 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +34 -0
- package/dist/tasks/index.js.map +1 -0
- package/dist/tasks/multi.refactor-move.d.ts +16 -0
- package/dist/tasks/multi.refactor-move.d.ts.map +1 -0
- package/dist/tasks/multi.refactor-move.js +37 -0
- package/dist/tasks/multi.refactor-move.js.map +1 -0
- package/dist/tasks/multi.run-and-report.d.ts +16 -0
- package/dist/tasks/multi.run-and-report.d.ts.map +1 -0
- package/dist/tasks/multi.run-and-report.js +26 -0
- package/dist/tasks/multi.run-and-report.js.map +1 -0
- package/dist/tasks/multi.summarize.d.ts +16 -0
- package/dist/tasks/multi.summarize.d.ts.map +1 -0
- package/dist/tasks/multi.summarize.js +35 -0
- package/dist/tasks/multi.summarize.js.map +1 -0
- package/dist/tasks/read.list-files.d.ts +15 -0
- package/dist/tasks/read.list-files.d.ts.map +1 -0
- package/dist/tasks/read.list-files.js +28 -0
- package/dist/tasks/read.list-files.js.map +1 -0
- package/dist/tasks/read.package-version.d.ts +15 -0
- package/dist/tasks/read.package-version.d.ts.map +1 -0
- package/dist/tasks/read.package-version.js +25 -0
- package/dist/tasks/read.package-version.js.map +1 -0
- package/dist/tasks/read.search-todos.d.ts +16 -0
- package/dist/tasks/read.search-todos.d.ts.map +1 -0
- package/dist/tasks/read.search-todos.js +30 -0
- package/dist/tasks/read.search-todos.js.map +1 -0
- package/package.json +57 -3
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grader that extracts the first <code>```<lang> ... ```</code>
|
|
3
|
+
* fenced block from `finalText` and passes when `predicate(block)`
|
|
4
|
+
* returns true. Language matching is case-insensitive.
|
|
5
|
+
*
|
|
6
|
+
* Use this for tasks like "agent produced a runnable Python snippet
|
|
7
|
+
* with a `solve()` function" without parsing the whole response
|
|
8
|
+
* manually.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* graders: [
|
|
12
|
+
* languageBlock('ts', (code) => /export function/.test(code)),
|
|
13
|
+
* ]
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function languageBlock(lang, predicate, options = {}) {
|
|
17
|
+
const maxDetailChars = options.maxDetailChars ?? 320;
|
|
18
|
+
const requireBlock = options.requireBlock ?? true;
|
|
19
|
+
return (ctx) => {
|
|
20
|
+
const text = ctx.result.finalText ?? '';
|
|
21
|
+
const block = extractFencedBlock(text, lang);
|
|
22
|
+
if (block === null) {
|
|
23
|
+
return requireBlock
|
|
24
|
+
? {
|
|
25
|
+
passed: false,
|
|
26
|
+
message: `no \`\`\`${lang} ... \`\`\` block found in finalText`,
|
|
27
|
+
details: truncate(text, maxDetailChars),
|
|
28
|
+
}
|
|
29
|
+
: { passed: true, message: `no \`\`\`${lang} block (not required)` };
|
|
30
|
+
}
|
|
31
|
+
return predicate(block)
|
|
32
|
+
? { passed: true, message: `\`\`\`${lang} block matched predicate` }
|
|
33
|
+
: {
|
|
34
|
+
passed: false,
|
|
35
|
+
message: `\`\`\`${lang} block did not match predicate`,
|
|
36
|
+
details: truncate(block, maxDetailChars),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Predicate-driven JSON shape check. Parses `finalText` as JSON: raw
|
|
42
|
+
* text first, then a single fenced block: then calls
|
|
43
|
+
* `predicate(value)`. Passes when the predicate accepts.
|
|
44
|
+
*
|
|
45
|
+
* Use this instead of {@link jsonSchema} when the field set is loose,
|
|
46
|
+
* you want to assert a single property, or you don't want a Zod
|
|
47
|
+
* dependency in this task's grading.
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* jsonContains((v) => {
|
|
51
|
+
* return v && typeof v === 'object' &&
|
|
52
|
+
* 'name' in v && 'price' in v && (v as any).price > 0;
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function jsonContains(predicate) {
|
|
57
|
+
return (ctx) => {
|
|
58
|
+
const text = ctx.result.finalText ?? '';
|
|
59
|
+
const parsed = tryParseJsonLoose(text);
|
|
60
|
+
if (parsed.parseError) {
|
|
61
|
+
return {
|
|
62
|
+
passed: false,
|
|
63
|
+
message: 'finalText is not valid JSON',
|
|
64
|
+
details: `${parsed.parseError}\n\ntext: ${truncate(text, 240)}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return predicate(parsed.value)
|
|
68
|
+
? { passed: true, message: 'JSON matched predicate' }
|
|
69
|
+
: {
|
|
70
|
+
passed: false,
|
|
71
|
+
message: 'JSON did not match predicate',
|
|
72
|
+
details: `value: ${truncate(JSON.stringify(parsed.value), 240)}`,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Helpers
|
|
77
|
+
/**
|
|
78
|
+
* Extract the first fenced code block matching the language tag.
|
|
79
|
+
* Case-insensitive. The same regex is used for both the typed
|
|
80
|
+
* (`` ```ts ```) and generic (`` ``` ```) cases: passing an empty
|
|
81
|
+
* `lang` matches any fenced block, which `jsonContains` uses as a
|
|
82
|
+
* fallback when the raw text isn't valid JSON.
|
|
83
|
+
*/
|
|
84
|
+
function extractFencedBlock(text, lang) {
|
|
85
|
+
if (lang === '') {
|
|
86
|
+
const fallback = text.match(/```[^\n]*\n?([\s\S]*?)```/);
|
|
87
|
+
if (fallback && typeof fallback[1] === 'string')
|
|
88
|
+
return fallback[1].trim();
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const pattern = new RegExp('```' + escapeRegex(lang.toLowerCase()) + '[\\s\\S]*?\\n([\\s\\S]*?)```', 'i');
|
|
92
|
+
const m = text.match(pattern);
|
|
93
|
+
if (m && typeof m[1] === 'string')
|
|
94
|
+
return m[1].trim();
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Try to parse `text` as JSON. Falls back to extracting a fenced
|
|
99
|
+
* block first. Mirrors `validateFinalText` from `forge-core`'s
|
|
100
|
+
* structured module so the two stay in sync structurally (without
|
|
101
|
+
* forcing this leaf package to depend on core).
|
|
102
|
+
*/
|
|
103
|
+
function tryParseJsonLoose(text) {
|
|
104
|
+
const trimmed = text.trim();
|
|
105
|
+
if (trimmed.length === 0)
|
|
106
|
+
return { value: undefined, parseError: 'response was empty' };
|
|
107
|
+
try {
|
|
108
|
+
return { value: JSON.parse(trimmed), parseError: null };
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
/* try fenced block */
|
|
112
|
+
}
|
|
113
|
+
const fence = extractFencedBlock(trimmed, '');
|
|
114
|
+
if (fence !== null) {
|
|
115
|
+
try {
|
|
116
|
+
return { value: JSON.parse(fence), parseError: null };
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
return {
|
|
120
|
+
value: undefined,
|
|
121
|
+
parseError: `fenced block not valid JSON: ${err.message}`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { value: undefined, parseError: 'no JSON found (and no fenced block)' };
|
|
126
|
+
}
|
|
127
|
+
function escapeRegex(s) {
|
|
128
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
129
|
+
}
|
|
130
|
+
function truncate(s, max) {
|
|
131
|
+
if (s.length <= max)
|
|
132
|
+
return s;
|
|
133
|
+
return s.slice(0, max) + '…';
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/graders/content.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Grader } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Grader that passes when `cwd/<relPath>` exists. Resolves to an absolute
|
|
4
|
+
* path so paths with `..` can't escape the task's cwd.
|
|
5
|
+
*/
|
|
6
|
+
export declare function fileExists(relPath: string): Grader;
|
|
7
|
+
/**
|
|
8
|
+
* Grader that passes when `cwd/<relPath>` exists AND its contents match the
|
|
9
|
+
* given predicate (substring, regex, or equality).
|
|
10
|
+
*/
|
|
11
|
+
export declare function fileMatches(relPath: string, predicate: string | RegExp | {
|
|
12
|
+
equals: string;
|
|
13
|
+
}): Grader;
|
|
14
|
+
/**
|
|
15
|
+
* Grader that passes when `cwd/<relPath>` does NOT exist. Inverse of
|
|
16
|
+
* {@link fileExists} for tasks like "delete stale.txt".
|
|
17
|
+
*/
|
|
18
|
+
export declare function fileMissing(relPath: string): Grader;
|
|
19
|
+
/**
|
|
20
|
+
* Grader that passes when `cwd/<relPath>` is a directory containing at least
|
|
21
|
+
* `min` files matching the (optional) extension filter.
|
|
22
|
+
*/
|
|
23
|
+
export declare function directoryHas(relPath: string, min?: number, ext?: string): Grader;
|
|
24
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/graders/fs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBlD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9C,MAAM,CAsCR;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUnD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAI,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CA0B3E"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve, sep } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Grader that passes when `cwd/<relPath>` exists. Resolves to an absolute
|
|
5
|
+
* path so paths with `..` can't escape the task's cwd.
|
|
6
|
+
*/
|
|
7
|
+
export function fileExists(relPath) {
|
|
8
|
+
return (ctx) => {
|
|
9
|
+
const abs = safeResolve(ctx.cwd, relPath);
|
|
10
|
+
if (abs === null) {
|
|
11
|
+
return {
|
|
12
|
+
passed: false,
|
|
13
|
+
message: `path "${relPath}" escaped task cwd`,
|
|
14
|
+
details: `cwd: ${ctx.cwd}`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (existsSync(abs)) {
|
|
18
|
+
return { passed: true, message: `file exists: ${relPath}` };
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
passed: false,
|
|
22
|
+
message: `file missing: ${relPath}`,
|
|
23
|
+
details: `expected at: ${abs}`,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Grader that passes when `cwd/<relPath>` exists AND its contents match the
|
|
29
|
+
* given predicate (substring, regex, or equality).
|
|
30
|
+
*/
|
|
31
|
+
export function fileMatches(relPath, predicate) {
|
|
32
|
+
return (ctx) => {
|
|
33
|
+
const abs = safeResolve(ctx.cwd, relPath);
|
|
34
|
+
if (abs === null) {
|
|
35
|
+
return { passed: false, message: `path "${relPath}" escaped task cwd` };
|
|
36
|
+
}
|
|
37
|
+
if (!existsSync(abs)) {
|
|
38
|
+
return { passed: false, message: `file missing: ${relPath}`, details: `expected at: ${abs}` };
|
|
39
|
+
}
|
|
40
|
+
const text = readFileSync(abs, 'utf8');
|
|
41
|
+
if (typeof predicate === 'string') {
|
|
42
|
+
return text.includes(predicate)
|
|
43
|
+
? { passed: true, message: `file ${relPath} contains "${predicate}"` }
|
|
44
|
+
: {
|
|
45
|
+
passed: false,
|
|
46
|
+
message: `file ${relPath} does not contain "${predicate}"`,
|
|
47
|
+
details: truncate(text),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (predicate instanceof RegExp) {
|
|
51
|
+
return predicate.test(text)
|
|
52
|
+
? { passed: true, message: `file ${relPath} matches ${predicate}` }
|
|
53
|
+
: {
|
|
54
|
+
passed: false,
|
|
55
|
+
message: `file ${relPath} does not match ${predicate}`,
|
|
56
|
+
details: truncate(text),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const want = predicate.equals.trim();
|
|
60
|
+
const got = text.trim();
|
|
61
|
+
return got === want
|
|
62
|
+
? { passed: true, message: `file ${relPath} equals expected` }
|
|
63
|
+
: {
|
|
64
|
+
passed: false,
|
|
65
|
+
message: `file ${relPath} content mismatch`,
|
|
66
|
+
details: `expected: ${JSON.stringify(want)}\nactual: ${JSON.stringify(got)}`,
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Grader that passes when `cwd/<relPath>` does NOT exist. Inverse of
|
|
72
|
+
* {@link fileExists} for tasks like "delete stale.txt".
|
|
73
|
+
*/
|
|
74
|
+
export function fileMissing(relPath) {
|
|
75
|
+
return (ctx) => {
|
|
76
|
+
const abs = safeResolve(ctx.cwd, relPath);
|
|
77
|
+
if (abs === null) {
|
|
78
|
+
return { passed: true, message: `path "${relPath}" not in cwd (treated as missing)` };
|
|
79
|
+
}
|
|
80
|
+
return !existsSync(abs)
|
|
81
|
+
? { passed: true, message: `file absent: ${relPath}` }
|
|
82
|
+
: { passed: false, message: `file should be missing: ${relPath}`, details: `still exists at: ${abs}` };
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Grader that passes when `cwd/<relPath>` is a directory containing at least
|
|
87
|
+
* `min` files matching the (optional) extension filter.
|
|
88
|
+
*/
|
|
89
|
+
export function directoryHas(relPath, min = 1, ext) {
|
|
90
|
+
return (ctx) => {
|
|
91
|
+
const abs = safeResolve(ctx.cwd, relPath);
|
|
92
|
+
if (abs === null) {
|
|
93
|
+
return { passed: false, message: `path "${relPath}" escaped task cwd` };
|
|
94
|
+
}
|
|
95
|
+
if (!existsSync(abs)) {
|
|
96
|
+
return { passed: false, message: `directory missing: ${relPath}` };
|
|
97
|
+
}
|
|
98
|
+
const st = statSync(abs);
|
|
99
|
+
if (!st.isDirectory()) {
|
|
100
|
+
return { passed: false, message: `not a directory: ${relPath}` };
|
|
101
|
+
}
|
|
102
|
+
const entries = readdirSync(abs);
|
|
103
|
+
const matched = ext ? entries.filter((e) => e.endsWith(ext)) : entries;
|
|
104
|
+
if (matched.length >= min) {
|
|
105
|
+
return {
|
|
106
|
+
passed: true,
|
|
107
|
+
message: `directory ${relPath} has ${matched.length} entries${ext ? ` (${ext})` : ''}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
passed: false,
|
|
112
|
+
message: `directory ${relPath} has only ${matched.length} entries (need ${min}${ext ? ` ${ext}` : ''})`,
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve `relPath` against `cwd` and reject any escape attempts (`..`
|
|
118
|
+
* segments, absolute paths, symlink games are not in scope: we trust the
|
|
119
|
+
* grader author to use safe paths; this just defends against typos).
|
|
120
|
+
*/
|
|
121
|
+
function safeResolve(cwd, relPath) {
|
|
122
|
+
const abs = resolve(cwd, relPath);
|
|
123
|
+
const root = resolve(cwd);
|
|
124
|
+
if (abs !== root && !abs.startsWith(root + sep)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return abs;
|
|
128
|
+
}
|
|
129
|
+
function truncate(s, max = 240) {
|
|
130
|
+
if (s.length <= max)
|
|
131
|
+
return s;
|
|
132
|
+
return s.slice(0, max) + '…';
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/graders/fs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type { Grader, GraderContext, GraderResult, TaskCategory, TaskDefinition, TaskInfo, ToolInvocation, } from './types.js';
|
|
2
|
+
export { regex, contains, equals, type RegexGraderOptions } from './text.js';
|
|
3
|
+
export { fileExists, fileMatches, fileMissing, directoryHas } from './fs.js';
|
|
4
|
+
export { commandRuns, type CommandRunsOptions } from './shell.js';
|
|
5
|
+
export { toolWasCalled, toolNotCalled, toolCalledWith, toolCount, noToolErrors, finalTextNonEmpty, completed, type ToolCountOptions, } from './behavior.js';
|
|
6
|
+
export { jsonSchema, jsonSchemaPartial, type JsonSchemaGraderOptions } from './schema.js';
|
|
7
|
+
export { tokensUnder, costUnder, durationUnder, iterationsUnder, type TokensUnderOptions, } from './metrics.js';
|
|
8
|
+
export { languageBlock, jsonContains, type LanguageBlockOptions, } from './content.js';
|
|
9
|
+
/**
|
|
10
|
+
* Helper for task authors: stamps a `TaskDefinition` with sensible defaults
|
|
11
|
+
* and gives the call site a single import. Functionally identical to the
|
|
12
|
+
* raw object: purely ergonomic.
|
|
13
|
+
*/
|
|
14
|
+
export declare function defineTask<T extends import('./types.js').TaskDefinition>(def: T): T;
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/graders/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,MAAM,EACN,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE1F,OAAO,EACL,WAAW,EACX,SAAS,EACT,aAAa,EACb,eAAe,EACf,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,aAAa,EACb,YAAY,EACZ,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,YAAY,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEnF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { regex, contains, equals } from './text.js';
|
|
2
|
+
export { fileExists, fileMatches, fileMissing, directoryHas } from './fs.js';
|
|
3
|
+
export { commandRuns } from './shell.js';
|
|
4
|
+
export { toolWasCalled, toolNotCalled, toolCalledWith, toolCount, noToolErrors, finalTextNonEmpty, completed, } from './behavior.js';
|
|
5
|
+
// v2.12: JSON-schema / Zod-schema graders for structured-output tasks.
|
|
6
|
+
export { jsonSchema, jsonSchemaPartial } from './schema.js';
|
|
7
|
+
// v2.13: Budget / throughput graders over AgentRunResult aggregates.
|
|
8
|
+
export { tokensUnder, costUnder, durationUnder, iterationsUnder, } from './metrics.js';
|
|
9
|
+
// v2.13: Content extraction graders (fenced code blocks, predicate JSON).
|
|
10
|
+
export { languageBlock, jsonContains, } from './content.js';
|
|
11
|
+
/**
|
|
12
|
+
* Helper for task authors: stamps a `TaskDefinition` with sensible defaults
|
|
13
|
+
* and gives the call site a single import. Functionally identical to the
|
|
14
|
+
* raw object: purely ergonomic.
|
|
15
|
+
*/
|
|
16
|
+
export function defineTask(def) {
|
|
17
|
+
return def;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/graders/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAA2B,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,WAAW,EAA2B,MAAM,YAAY,CAAC;AAClE,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,SAAS,GAEV,MAAM,eAAe,CAAC;AACvB,uEAAuE;AACvE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAgC,MAAM,aAAa,CAAC;AAC1F,qEAAqE;AACrE,OAAO,EACL,WAAW,EACX,SAAS,EACT,aAAa,EACb,eAAe,GAEhB,MAAM,cAAc,CAAC;AACtB,0EAA0E;AAC1E,OAAO,EACL,aAAa,EACb,YAAY,GAEb,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAgD,GAAM;IAC9E,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget / throughput graders for the eval harness. v2.13.
|
|
3
|
+
*
|
|
4
|
+
* These graders inspect {@link AgentRunResult} aggregate fields
|
|
5
|
+
* (tokens, cost, duration, iterations) and pass when they fall under
|
|
6
|
+
* a caller-supplied ceiling. They let eval tasks answer questions
|
|
7
|
+
* like "did the agent come in under budget?" without writing custom
|
|
8
|
+
* predicates against raw numbers.
|
|
9
|
+
*
|
|
10
|
+
* Why a budget grader family?
|
|
11
|
+
* - Cost is a first-class eval signal for an LLM agent. If a task
|
|
12
|
+
* routinely hits 10x expected cost, that's a regression worth
|
|
13
|
+
* catching even if the answer is technically correct.
|
|
14
|
+
* - Latency ceilinging catches "agent goes into a long loop" bugs
|
|
15
|
+
* that correctness graders don't.
|
|
16
|
+
* - Pair `tokensUnder` / `costUnder` with `completed()` to assert
|
|
17
|
+
* "this task finishes correctly AND cheaply".
|
|
18
|
+
*/
|
|
19
|
+
import type { Grader } from './types.js';
|
|
20
|
+
/** Options for {@link tokensUnder}. */
|
|
21
|
+
export interface TokensUnderOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Which token counter to budget against.
|
|
24
|
+
* - `'total'` (default): input + output, the most useful general signal
|
|
25
|
+
* - `'input'`: "agent didn't shovel huge context"
|
|
26
|
+
* - `'output'`: "agent didn't ramble"
|
|
27
|
+
*/
|
|
28
|
+
kind?: 'input' | 'output' | 'total';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Grader that passes when the chosen token counter is `<= max`.
|
|
32
|
+
* The cap is a soft ceiling; the grader doesn't measure cost, just
|
|
33
|
+
* raw token counts. Use {@link costUnder} for budget enforcement in
|
|
34
|
+
* dollars.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* graders: [
|
|
38
|
+
* completed(),
|
|
39
|
+
* tokensUnder(2000), // total <= 2000
|
|
40
|
+
* tokensUnder(800, { kind: 'input' }),// input alone <= 800
|
|
41
|
+
* ],
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function tokensUnder(max: number, options?: TokensUnderOptions): Grader;
|
|
45
|
+
/**
|
|
46
|
+
* Grader that passes when total cost is `<= maxUsd`. Fails when the
|
|
47
|
+
* agent's model has no known pricing (the cost isn't knowable, so
|
|
48
|
+
* "under budget" can't be verified).
|
|
49
|
+
*
|
|
50
|
+
* Used with cost-tracking models; for free-tier / on-device models
|
|
51
|
+
* pricing is unknown and this grader will always fail, so omit it
|
|
52
|
+
* from those tasks.
|
|
53
|
+
*/
|
|
54
|
+
export declare function costUnder(maxUsd: number): Grader;
|
|
55
|
+
/** Grader that passes when the wall-clock run duration is `<= maxMs`. */
|
|
56
|
+
export declare function durationUnder(maxMs: number): Grader;
|
|
57
|
+
/** Grader that passes when the agent finished in `<= max` iterations. */
|
|
58
|
+
export declare function iterationsUnder(max: number): Grader;
|
|
59
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/graders/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAEtE,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAUjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAgBhD;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOnD"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grader that passes when the chosen token counter is `<= max`.
|
|
3
|
+
* The cap is a soft ceiling; the grader doesn't measure cost, just
|
|
4
|
+
* raw token counts. Use {@link costUnder} for budget enforcement in
|
|
5
|
+
* dollars.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* graders: [
|
|
9
|
+
* completed(),
|
|
10
|
+
* tokensUnder(2000), // total <= 2000
|
|
11
|
+
* tokensUnder(800, { kind: 'input' }),// input alone <= 800
|
|
12
|
+
* ],
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function tokensUnder(max, options = {}) {
|
|
16
|
+
const kind = options.kind ?? 'total';
|
|
17
|
+
return (ctx) => {
|
|
18
|
+
const i = ctx.result.inputTokens;
|
|
19
|
+
const o = ctx.result.outputTokens;
|
|
20
|
+
const actual = kind === 'input' ? i : kind === 'output' ? o : i + o;
|
|
21
|
+
return actual <= max
|
|
22
|
+
? { passed: true, message: `${kind} tokens ${actual} <= ${max}` }
|
|
23
|
+
: { passed: false, message: `${kind} tokens ${actual} exceeded ${max}` };
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Grader that passes when total cost is `<= maxUsd`. Fails when the
|
|
28
|
+
* agent's model has no known pricing (the cost isn't knowable, so
|
|
29
|
+
* "under budget" can't be verified).
|
|
30
|
+
*
|
|
31
|
+
* Used with cost-tracking models; for free-tier / on-device models
|
|
32
|
+
* pricing is unknown and this grader will always fail, so omit it
|
|
33
|
+
* from those tasks.
|
|
34
|
+
*/
|
|
35
|
+
export function costUnder(maxUsd) {
|
|
36
|
+
return (ctx) => {
|
|
37
|
+
const r = ctx.result;
|
|
38
|
+
if (!r.costKnown) {
|
|
39
|
+
return {
|
|
40
|
+
passed: false,
|
|
41
|
+
message: `cost unknown for model: cannot enforce <= $${maxUsd}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return r.costUsd <= maxUsd
|
|
45
|
+
? { passed: true, message: `cost $${r.costUsd.toFixed(6)} <= $${maxUsd}` }
|
|
46
|
+
: {
|
|
47
|
+
passed: false,
|
|
48
|
+
message: `cost $${r.costUsd.toFixed(6)} exceeded $${maxUsd}`,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Grader that passes when the wall-clock run duration is `<= maxMs`. */
|
|
53
|
+
export function durationUnder(maxMs) {
|
|
54
|
+
return (ctx) => {
|
|
55
|
+
const actual = ctx.result.durationMs;
|
|
56
|
+
return actual <= maxMs
|
|
57
|
+
? { passed: true, message: `duration ${actual}ms <= ${maxMs}ms` }
|
|
58
|
+
: { passed: false, message: `duration ${actual}ms exceeded ${maxMs}ms` };
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Grader that passes when the agent finished in `<= max` iterations. */
|
|
62
|
+
export function iterationsUnder(max) {
|
|
63
|
+
return (ctx) => {
|
|
64
|
+
const actual = ctx.result.iterations;
|
|
65
|
+
return actual <= max
|
|
66
|
+
? { passed: true, message: `iterations ${actual} <= ${max}` }
|
|
67
|
+
: { passed: false, message: `iterations ${actual} exceeded ${max}` };
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/graders/metrics.ts"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,UAA8B,EAAE;IACvE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC;IACrC,OAAO,CAAC,GAAkB,EAAgB,EAAE;QAC1C,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC;QACjC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACpE,OAAO,MAAM,IAAI,GAAG;YAClB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,WAAW,MAAM,OAAO,GAAG,EAAE,EAAE;YACjE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,WAAW,MAAM,aAAa,GAAG,EAAE,EAAE,CAAC;IAC7E,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,OAAO,CAAC,GAAkB,EAAgB,EAAE;QAC1C,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;YACjB,OAAO;gBACL,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,8CAA8C,MAAM,EAAE;aAChE,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,CAAC,OAAO,IAAI,MAAM;YACxB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,EAAE;YAC1E,CAAC,CAAC;gBACE,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,MAAM,EAAE;aAC7D,CAAC;IACR,CAAC,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,CAAC,GAAkB,EAAgB,EAAE;QAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;QACrC,OAAO,MAAM,IAAI,KAAK;YACpB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,MAAM,SAAS,KAAK,IAAI,EAAE;YACjE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,MAAM,eAAe,KAAK,IAAI,EAAE,CAAC;IAC7E,CAAC,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,CAAC,GAAkB,EAAgB,EAAE;QAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;QACrC,OAAO,MAAM,IAAI,GAAG;YAClB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,MAAM,OAAO,GAAG,EAAE,EAAE;YAC7D,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,MAAM,aAAa,GAAG,EAAE,EAAE,CAAC;IACzE,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-schema / Zod-schema grader for the eval harness. v2.12.
|
|
3
|
+
*
|
|
4
|
+
* Validates `AgentRunResult.finalText` against a user-supplied Zod
|
|
5
|
+
* schema. Pairs naturally with the v2.8 `--schema` mode: run the
|
|
6
|
+
* agent in structured-output mode and grade the result with
|
|
7
|
+
* `jsonSchema` for a fully deterministic CI pipeline (no LLM-as-judge
|
|
8
|
+
* for tasks that have a clear JSON contract).
|
|
9
|
+
*
|
|
10
|
+
* The grader accepts the schema in two forms:
|
|
11
|
+
* - A Zod schema object (`z.object({...})`): the most ergonomic
|
|
12
|
+
* option, full TypeScript inference, no extra parse step.
|
|
13
|
+
* - A path to a `.json` / `.ts` / `.mjs` schema file: the most
|
|
14
|
+
* portable, lets the same schema be reused by the agent's
|
|
15
|
+
* `--schema` flag AND the grader.
|
|
16
|
+
*
|
|
17
|
+
* Extraction follows the same rules as the agent's structured-output
|
|
18
|
+
* mode: try the raw text first, then a single ```json ... ``` fenced
|
|
19
|
+
* block. See {@link validateFinalText} in `@ikarmic/forge-core` for
|
|
20
|
+
* the reference implementation: this grader re-implements it
|
|
21
|
+
* directly so the eval harness stays a leaf package (no dep on the
|
|
22
|
+
* core's internal helpers).
|
|
23
|
+
*/
|
|
24
|
+
import type { z } from 'zod';
|
|
25
|
+
import type { Grader } from './types.js';
|
|
26
|
+
/** Options for {@link jsonSchema}. */
|
|
27
|
+
export interface JsonSchemaGraderOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Only relevant when `schema` is a string. Default true: the
|
|
30
|
+
* string is treated as a path to a schema file (`.ts` / `.js` /
|
|
31
|
+
* `.mjs` / `.cjs` / `.json`) and loaded via {@link loadSchemaFile}
|
|
32
|
+
* from `@ikarmic/forge-core` (dynamic import for JS/TS modules,
|
|
33
|
+
* JSON-Schema-to-Zod conversion for `.json`). Set to false to make
|
|
34
|
+
* the grader throw immediately instead of attempting to load the
|
|
35
|
+
* string as a path.
|
|
36
|
+
*/
|
|
37
|
+
schemaIsPath?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Truncate the raw `finalText` in failure details to this many
|
|
40
|
+
* characters. Default 400: long enough to see the offending JSON,
|
|
41
|
+
* short enough to keep reports scannable.
|
|
42
|
+
*/
|
|
43
|
+
maxDetailChars?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Grader that parses `finalText` as JSON and validates it against the
|
|
47
|
+
* supplied schema. Returns `{ passed: true }` on a successful
|
|
48
|
+
* validation, `{ passed: false, details }` with the validation error
|
|
49
|
+
* + truncated text otherwise.
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { z } from 'zod';
|
|
53
|
+
* import { jsonSchema } from '@ikarmic/forge-eval';
|
|
54
|
+
*
|
|
55
|
+
* const prSchema = z.object({
|
|
56
|
+
* title: z.string(),
|
|
57
|
+
* body: z.string().min(20),
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* const task = defineTask({
|
|
61
|
+
* id: 'summarize-pr',
|
|
62
|
+
* graders: [jsonSchema(prSchema)],
|
|
63
|
+
* ...
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* // Or load the same schema the agent uses via --schema:
|
|
67
|
+
* const task = defineTask({
|
|
68
|
+
* id: 'extract-pr',
|
|
69
|
+
* graders: [jsonSchema('./schemas/pr-summary.ts')],
|
|
70
|
+
* ...
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function jsonSchema(schema: z.ZodTypeAny | string, options?: JsonSchemaGraderOptions): Grader;
|
|
75
|
+
/**
|
|
76
|
+
* Grader that validates the parsed JSON's *subset* of fields: useful
|
|
77
|
+
* when the agent may add extra fields you don't want to grade but
|
|
78
|
+
* the core fields must match. Behaves like {@link jsonSchema} but
|
|
79
|
+
* wraps the user schema in `passthrough()` before validating, so any
|
|
80
|
+
* extra fields are ignored rather than triggering a strict failure.
|
|
81
|
+
*
|
|
82
|
+
* Note: when `schema` is a path, it can't be inspected synchronously,
|
|
83
|
+
* so the `passthrough()` wrapping is only applied to a Zod schema
|
|
84
|
+
* object passed directly: a path-loaded schema is graded as-is.
|
|
85
|
+
*/
|
|
86
|
+
export declare function jsonSchemaPartial(schema: z.ZodTypeAny | string, options?: JsonSchemaGraderOptions): Grader;
|
|
87
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/graders/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAI7B,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAEtE,sCAAsC;AACtC,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,MAAM,EAC7B,OAAO,GAAE,uBAA4B,GACpC,MAAM,CAgCR;AA2BD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,MAAM,EAC7B,OAAO,GAAE,uBAA4B,GACpC,MAAM,CAUR"}
|