@velum-labs/routekit-eval-setup 1.0.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.
Files changed (56) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +23 -0
  3. package/dist/effect-api.d.ts +12 -0
  4. package/dist/effect-api.js +9 -0
  5. package/dist/errors.d.ts +91 -0
  6. package/dist/errors.js +46 -0
  7. package/dist/host-metadata.d.ts +32 -0
  8. package/dist/host-metadata.js +46 -0
  9. package/dist/index.d.ts +20 -0
  10. package/dist/index.js +13 -0
  11. package/dist/inspection.d.ts +24 -0
  12. package/dist/inspection.js +261 -0
  13. package/dist/model-selection.d.ts +6 -0
  14. package/dist/model-selection.js +37 -0
  15. package/dist/ori-authoring.d.ts +16 -0
  16. package/dist/ori-authoring.js +17 -0
  17. package/dist/ori-result.d.ts +45 -0
  18. package/dist/ori-result.js +1 -0
  19. package/dist/project-artifacts.d.ts +31 -0
  20. package/dist/project-artifacts.js +353 -0
  21. package/dist/project-authoring.d.ts +68 -0
  22. package/dist/project-authoring.js +431 -0
  23. package/dist/project-contracts.d.ts +1197 -0
  24. package/dist/project-contracts.js +396 -0
  25. package/dist/project-store.d.ts +13 -0
  26. package/dist/project-store.js +53 -0
  27. package/dist/project-workflow.d.ts +33 -0
  28. package/dist/project-workflow.js +904 -0
  29. package/dist/questions.d.ts +7 -0
  30. package/dist/questions.js +67 -0
  31. package/dist/runner.d.ts +8 -0
  32. package/dist/runner.js +16 -0
  33. package/dist/service.d.ts +24 -0
  34. package/dist/service.js +279 -0
  35. package/dist/state-store.d.ts +21 -0
  36. package/dist/state-store.js +86 -0
  37. package/dist/test/inspection.test.d.ts +1 -0
  38. package/dist/test/inspection.test.js +68 -0
  39. package/dist/test/model-selection.test.d.ts +1 -0
  40. package/dist/test/model-selection.test.js +15 -0
  41. package/dist/test/project-authoring.test.d.ts +1 -0
  42. package/dist/test/project-authoring.test.js +67 -0
  43. package/dist/test/project-workflow.test.d.ts +1 -0
  44. package/dist/test/project-workflow.test.js +516 -0
  45. package/dist/test/questions.test.d.ts +1 -0
  46. package/dist/test/questions.test.js +48 -0
  47. package/dist/test/skill.test.d.ts +1 -0
  48. package/dist/test/skill.test.js +31 -0
  49. package/dist/test/state-store.test.d.ts +1 -0
  50. package/dist/test/state-store.test.js +30 -0
  51. package/dist/test/workflow.test.d.ts +1 -0
  52. package/dist/test/workflow.test.js +167 -0
  53. package/dist/types.d.ts +77 -0
  54. package/dist/types.js +1 -0
  55. package/package.json +52 -0
  56. package/skills/setup-eval-routing/SKILL.md +149 -0
@@ -0,0 +1,353 @@
1
+ import { createHash } from "node:crypto";
2
+ import { assertRoutingBasis, RoutingBasis as RoutingBasisSchema } from "@velum-labs/routekit-eval-contracts";
3
+ import { writeFileAtomicEffect } from "@velum-labs/routekit-runtime/effect";
4
+ import { Context, Effect, Exit, FileSystem, Layer, Path, Schema } from "effect";
5
+ import { EvalProjectArtifactError } from "./errors.js";
6
+ import { EVAL_PROJECT_VERSION, EvalArtifactApproval as EvalArtifactApprovalSchema, EvalEvaluationProposal as EvalEvaluationProposalSchema, EvalExecutionPlan as EvalExecutionPlanSchema, EvalRunReport as EvalRunReportSchema } from "./project-contracts.js";
7
+ const BASIS_PROPOSAL = "routing-basis.proposed.json";
8
+ const BASIS_APPROVAL = "routing-basis.approval.json";
9
+ const EVALUATIONS_PROPOSAL = "evaluations.proposed.json";
10
+ const EVALUATIONS_APPROVAL = "evaluations.approval.json";
11
+ const ARTIFACT_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
12
+ const renderDimensionSuite = () => `import assert from "node:assert/strict";
13
+ import { test } from "node:test";
14
+ import { setupAgent, setupJudge } from "routekit/eval";
15
+ import cases from "./data/cases.json" with { type: "json" };
16
+ import manifest from "./routekit.eval-manifest.json" with { type: "json" };
17
+
18
+ assert.equal(cases.length, manifest.caseCount);
19
+ assert.deepEqual(cases.map((testCase) => testCase.id), manifest.caseIds);
20
+ const judge = setupJudge({
21
+ agent: setupAgent({ model: manifest.judgeModel }),
22
+ minScore: 0.8
23
+ });
24
+
25
+ for (const model of manifest.candidateModels) {
26
+ const candidate = setupAgent({ model });
27
+ for (const testCase of cases) {
28
+ test(\`\${model} / \${testCase.id}\`, async () => {
29
+ const prompt = testCase.context === undefined
30
+ ? testCase.prompt
31
+ : [testCase.prompt, "", "Reference material:", "-----", testCase.context, "-----"].join("\\n");
32
+ const run = await candidate.run({ prompt, caseId: testCase.id });
33
+ run.toComplete();
34
+ await judge.autoEvals({ criteria: testCase.rubric, prompt, run });
35
+ });
36
+ }
37
+ }
38
+ `;
39
+ const detailOf = (cause) => cause instanceof Error ? cause.message : String(cause);
40
+ const artifactFailure = (operation, path, cause) => new EvalProjectArtifactError({
41
+ operation,
42
+ path,
43
+ detail: detailOf(cause),
44
+ cause
45
+ });
46
+ const requireArtifactId = (kind, id) => ARTIFACT_ID.test(id)
47
+ ? Effect.void
48
+ : Effect.fail(artifactFailure("reading", id, new Error(`${kind} id must be a bounded identifier, not a path`)));
49
+ const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
50
+ export function routingBasisDigest(dimensions) {
51
+ return digest({ version: 2, dimensions });
52
+ }
53
+ export function evaluationProposalDigest(proposal) {
54
+ return digest(proposal);
55
+ }
56
+ function assertEvaluationProposal(proposal) {
57
+ if (proposal.evaluationDigest !==
58
+ evaluationProposalDigest({
59
+ version: proposal.version,
60
+ basisDigest: proposal.basisDigest,
61
+ candidateModels: proposal.candidateModels,
62
+ judgeModel: proposal.judgeModel,
63
+ suites: proposal.suites,
64
+ decompositionBenchmark: proposal.decompositionBenchmark,
65
+ compositionSuite: proposal.compositionSuite
66
+ })) {
67
+ throw new Error("evaluation proposal digest does not match its contents");
68
+ }
69
+ if (proposal.candidateModels.length < 2) {
70
+ throw new Error("evaluation proposal requires at least two candidate models");
71
+ }
72
+ if (new Set(proposal.candidateModels).size !== proposal.candidateModels.length) {
73
+ throw new Error("evaluation proposal candidate models must be unique");
74
+ }
75
+ if (proposal.judgeModel.trim().length === 0) {
76
+ throw new Error("evaluation proposal judge must be explicit");
77
+ }
78
+ const dimensions = new Set();
79
+ for (const suite of proposal.suites) {
80
+ if (dimensions.has(suite.dimensionId)) {
81
+ throw new Error(`duplicate dimension suite ${JSON.stringify(suite.dimensionId)}`);
82
+ }
83
+ dimensions.add(suite.dimensionId);
84
+ assertDimensionSuite(suite);
85
+ }
86
+ if (proposal.decompositionBenchmark.maximumVectorL1Error < 0 ||
87
+ proposal.decompositionBenchmark.maximumVectorL1Error > 2 ||
88
+ proposal.decompositionBenchmark.cases.length < 5) {
89
+ throw new Error("decomposition benchmark must define a reviewed threshold and at least five cases");
90
+ }
91
+ if (proposal.compositionSuite.maximumOutputTokens < 1 ||
92
+ proposal.compositionSuite.minimumWinnerScoreGap < 0 ||
93
+ proposal.compositionSuite.minimumWinnerScoreGap > 1 ||
94
+ proposal.compositionSuite.minimumWinnerAgreement < 0 ||
95
+ proposal.compositionSuite.minimumWinnerAgreement > 1 ||
96
+ proposal.compositionSuite.cases.length < 5) {
97
+ throw new Error("composition benchmark must define reviewed thresholds and at least five cases");
98
+ }
99
+ for (const [label, cases] of [
100
+ ["decomposition", proposal.decompositionBenchmark.cases],
101
+ ["composition", proposal.compositionSuite.cases]
102
+ ]) {
103
+ const ids = new Set();
104
+ for (const testCase of cases) {
105
+ if (testCase.id.trim().length === 0 || ids.has(testCase.id)) {
106
+ throw new Error(`${label} benchmark contains an invalid or duplicate case id`);
107
+ }
108
+ ids.add(testCase.id);
109
+ }
110
+ }
111
+ }
112
+ function assertDimensionSuite(suite) {
113
+ if (suite.maximumOutputTokens < 1) {
114
+ throw new Error(`dimension suite ${JSON.stringify(suite.dimensionId)} has no output allowance`);
115
+ }
116
+ if (suite.cases.length < 5) {
117
+ throw new Error(`dimension suite ${JSON.stringify(suite.dimensionId)} must contain at least five cases`);
118
+ }
119
+ const ids = new Set();
120
+ for (const testCase of suite.cases) {
121
+ if (testCase.id.trim().length === 0 ||
122
+ testCase.prompt.trim().length === 0 ||
123
+ testCase.rubric.trim().length === 0) {
124
+ throw new Error(`dimension suite ${JSON.stringify(suite.dimensionId)} contains an incomplete case`);
125
+ }
126
+ if (ids.has(testCase.id)) {
127
+ throw new Error(`dimension suite ${JSON.stringify(suite.dimensionId)} contains duplicate case ${JSON.stringify(testCase.id)}`);
128
+ }
129
+ ids.add(testCase.id);
130
+ }
131
+ }
132
+ export class EvalProjectArtifacts extends Context.Service()("@velum-labs/routekit-eval-setup/EvalProjectArtifacts") {
133
+ }
134
+ export const makeFileEvalProjectArtifacts = Effect.gen(function* () {
135
+ const fs = yield* FileSystem.FileSystem;
136
+ const paths = yield* Path.Path;
137
+ const root = (repositoryRoot) => paths.join(paths.resolve(repositoryRoot), ".routekit", "evals");
138
+ const artifactPath = (repositoryRoot, name) => paths.join(root(repositoryRoot), name);
139
+ const writeAtomic = (target, content) => writeFileAtomicEffect(target, content, {
140
+ mode: 0o600
141
+ }).pipe(Effect.mapError((cause) => artifactFailure("writing", target, cause)), Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, paths));
142
+ const read = (repositoryRoot, name, decode) => Effect.gen(function* () {
143
+ const target = artifactPath(repositoryRoot, name);
144
+ const exists = yield* fs
145
+ .exists(target)
146
+ .pipe(Effect.mapError((cause) => artifactFailure("checking", target, cause)));
147
+ if (!exists)
148
+ return undefined;
149
+ const raw = yield* fs
150
+ .readFileString(target)
151
+ .pipe(Effect.mapError((cause) => artifactFailure("reading", target, cause)));
152
+ const value = yield* Effect.try({
153
+ try: () => JSON.parse(raw),
154
+ catch: (cause) => artifactFailure("decoding", target, cause)
155
+ });
156
+ return yield* decode(value).pipe(Effect.mapError((cause) => artifactFailure("decoding", target, cause)));
157
+ });
158
+ const writeText = (repositoryRoot, name, content, overwrite = true) => Effect.gen(function* () {
159
+ const target = artifactPath(repositoryRoot, name);
160
+ const exists = yield* fs
161
+ .exists(target)
162
+ .pipe(Effect.mapError((cause) => artifactFailure("checking", target, cause)));
163
+ if (!overwrite && exists) {
164
+ return yield* artifactFailure("writing", target, new Error("artifact already exists"));
165
+ }
166
+ yield* fs
167
+ .makeDirectory(paths.dirname(target), { recursive: true, mode: 0o700 })
168
+ .pipe(Effect.mapError((cause) => artifactFailure("writing", target, cause)));
169
+ yield* writeAtomic(target, content);
170
+ });
171
+ const write = (repositoryRoot, name, value, overwrite = true) => writeText(repositoryRoot, name, `${JSON.stringify(value, null, 2)}\n`, overwrite);
172
+ return EvalProjectArtifacts.of({
173
+ loadBasisProposal: (repositoryRoot) => read(repositoryRoot, BASIS_PROPOSAL, Schema.decodeUnknownEffect(RoutingBasisSchema)).pipe(Effect.tap((basis) => basis === undefined
174
+ ? Effect.void
175
+ : Effect.try({
176
+ try: () => {
177
+ assertRoutingBasis(basis);
178
+ if (basis.basisDigest !== routingBasisDigest(basis.dimensions)) {
179
+ throw new Error("routing basis digest does not match its dimensions");
180
+ }
181
+ },
182
+ catch: (cause) => artifactFailure("decoding", artifactPath(repositoryRoot, BASIS_PROPOSAL), cause)
183
+ }))),
184
+ saveBasisProposal: (repositoryRoot, basis) => Effect.try({
185
+ try: () => {
186
+ assertRoutingBasis(basis);
187
+ if (basis.basisDigest !== routingBasisDigest(basis.dimensions)) {
188
+ throw new Error("routing basis digest does not match its dimensions");
189
+ }
190
+ },
191
+ catch: (cause) => artifactFailure("writing", artifactPath(repositoryRoot, BASIS_PROPOSAL), cause)
192
+ }).pipe(Effect.andThen(write(repositoryRoot, BASIS_PROPOSAL, basis))),
193
+ loadBasisApproval: (repositoryRoot) => read(repositoryRoot, BASIS_APPROVAL, Schema.decodeUnknownEffect(EvalArtifactApprovalSchema)),
194
+ saveBasisApproval: (repositoryRoot, approval) => write(repositoryRoot, BASIS_APPROVAL, approval),
195
+ loadEvaluationProposal: (repositoryRoot) => read(repositoryRoot, EVALUATIONS_PROPOSAL, Schema.decodeUnknownEffect(EvalEvaluationProposalSchema)).pipe(Effect.tap((proposal) => proposal === undefined
196
+ ? Effect.void
197
+ : Effect.try({
198
+ try: () => assertEvaluationProposal(proposal),
199
+ catch: (cause) => artifactFailure("decoding", artifactPath(repositoryRoot, EVALUATIONS_PROPOSAL), cause)
200
+ }))),
201
+ saveEvaluationProposal: (repositoryRoot, proposal) => Effect.gen(function* () {
202
+ yield* Effect.try({
203
+ try: () => assertEvaluationProposal(proposal),
204
+ catch: (cause) => artifactFailure("writing", artifactPath(repositoryRoot, EVALUATIONS_PROPOSAL), cause)
205
+ });
206
+ yield* write(repositoryRoot, EVALUATIONS_PROPOSAL, proposal);
207
+ for (const suite of proposal.suites) {
208
+ const suiteRoot = paths.join("dimensions", suite.dimensionId);
209
+ yield* write(repositoryRoot, paths.join(suiteRoot, "suite.json"), suite);
210
+ yield* writeText(repositoryRoot, paths.join(suiteRoot, `${suite.dimensionId}.eval.ts`), renderDimensionSuite());
211
+ yield* write(repositoryRoot, paths.join(suiteRoot, "data", "cases.json"), suite.cases);
212
+ yield* write(repositoryRoot, paths.join(suiteRoot, "routekit.eval-manifest.json"), {
213
+ version: EVAL_PROJECT_VERSION,
214
+ profileId: suite.dimensionId,
215
+ candidateModels: proposal.candidateModels,
216
+ judgeModel: proposal.judgeModel,
217
+ caseCount: suite.cases.length,
218
+ caseIds: suite.cases.map((testCase) => testCase.id),
219
+ maxOutputTokens: suite.maximumOutputTokens,
220
+ expectedCallCount: suite.cases.length * proposal.candidateModels.length * 2
221
+ });
222
+ }
223
+ yield* write(repositoryRoot, paths.join("benchmarks", "decomposition.json"), proposal.decompositionBenchmark);
224
+ yield* write(repositoryRoot, paths.join("benchmarks", "composition.json"), proposal.compositionSuite);
225
+ }),
226
+ loadEvaluationsApproval: (repositoryRoot) => read(repositoryRoot, EVALUATIONS_APPROVAL, Schema.decodeUnknownEffect(EvalArtifactApprovalSchema)),
227
+ saveEvaluationsApproval: (repositoryRoot, approval) => write(repositoryRoot, EVALUATIONS_APPROVAL, approval),
228
+ savePlan: (repositoryRoot, plan) => write(repositoryRoot, paths.join("plans", `${plan.planId}.json`), plan, false),
229
+ materializePlanSuites: (repositoryRoot, plan, proposal) => Effect.gen(function* () {
230
+ yield* requireArtifactId("plan", plan.planId);
231
+ const suites = new Map(proposal.suites.map((suite) => [suite.dimensionId, suite]));
232
+ for (const selection of plan.selectedCaseIds) {
233
+ if (!ARTIFACT_ID.test(selection.dimensionId)) {
234
+ return yield* artifactFailure("writing", selection.dimensionId, new Error("dimension id must be a bounded identifier, not a path"));
235
+ }
236
+ const suite = suites.get(selection.dimensionId);
237
+ if (suite === undefined) {
238
+ return yield* artifactFailure("writing", selection.dimensionId, new Error("execution plan refers to an unknown dimension suite"));
239
+ }
240
+ const byId = new Map(suite.cases.map((testCase) => [testCase.id, testCase]));
241
+ const cases = selection.caseIds.map((caseId) => {
242
+ const testCase = byId.get(caseId);
243
+ if (testCase === undefined) {
244
+ throw new Error(`execution plan refers to unknown case ${JSON.stringify(caseId)} in ${JSON.stringify(selection.dimensionId)}`);
245
+ }
246
+ return testCase;
247
+ });
248
+ const suiteRoot = paths.join("plans", plan.planId, "dimensions", selection.dimensionId);
249
+ yield* writeText(repositoryRoot, paths.join(suiteRoot, `${selection.dimensionId}.eval.ts`), renderDimensionSuite(), false);
250
+ yield* write(repositoryRoot, paths.join(suiteRoot, "data", "cases.json"), cases, false);
251
+ yield* write(repositoryRoot, paths.join(suiteRoot, "routekit.eval-manifest.json"), {
252
+ version: EVAL_PROJECT_VERSION,
253
+ profileId: selection.dimensionId,
254
+ candidateModels: plan.candidateModels,
255
+ judgeModel: plan.judgeModel,
256
+ caseCount: cases.length,
257
+ caseIds: cases.map((testCase) => testCase.id),
258
+ maxOutputTokens: suite.maximumOutputTokens,
259
+ expectedCallCount: cases.length * plan.candidateModels.length * 2
260
+ }, false);
261
+ }
262
+ const compositionById = new Map(proposal.compositionSuite.cases.map((testCase) => [testCase.id, testCase]));
263
+ const compositionCases = plan.selectedCompositionCaseIds.map((caseId) => {
264
+ const testCase = compositionById.get(caseId);
265
+ if (testCase === undefined) {
266
+ throw new Error(`execution plan refers to unknown composition case ${JSON.stringify(caseId)}`);
267
+ }
268
+ return testCase;
269
+ });
270
+ const compositionRoot = paths.join("plans", plan.planId, "composition");
271
+ yield* writeText(repositoryRoot, paths.join(compositionRoot, "composition.eval.ts"), renderDimensionSuite(), false);
272
+ yield* write(repositoryRoot, paths.join(compositionRoot, "data", "cases.json"), compositionCases, false);
273
+ yield* write(repositoryRoot, paths.join(compositionRoot, "routekit.eval-manifest.json"), {
274
+ version: EVAL_PROJECT_VERSION,
275
+ profileId: "composition",
276
+ candidateModels: plan.candidateModels,
277
+ judgeModel: plan.judgeModel,
278
+ caseCount: compositionCases.length,
279
+ caseIds: compositionCases.map((testCase) => testCase.id),
280
+ maxOutputTokens: proposal.compositionSuite.maximumOutputTokens,
281
+ expectedCallCount: compositionCases.length * plan.candidateModels.length * 2
282
+ }, false);
283
+ }).pipe(Effect.mapError((cause) => cause instanceof EvalProjectArtifactError
284
+ ? cause
285
+ : artifactFailure("writing", artifactPath(repositoryRoot, paths.join("plans", plan.planId)), cause))),
286
+ planSuitePath: (repositoryRoot, planId, dimensionId) => Effect.gen(function* () {
287
+ yield* requireArtifactId("plan", planId);
288
+ if (!ARTIFACT_ID.test(dimensionId)) {
289
+ return yield* artifactFailure("reading", dimensionId, new Error("dimension id must be a bounded identifier, not a path"));
290
+ }
291
+ return artifactPath(repositoryRoot, paths.join("plans", planId, "dimensions", dimensionId, `${dimensionId}.eval.ts`));
292
+ }),
293
+ compositionSuitePath: (repositoryRoot, planId) => requireArtifactId("plan", planId).pipe(Effect.as(artifactPath(repositoryRoot, paths.join("plans", planId, "composition", "composition.eval.ts")))),
294
+ loadPlan: (repositoryRoot, planId) => requireArtifactId("plan", planId).pipe(Effect.andThen(read(repositoryRoot, paths.join("plans", `${planId}.json`), Schema.decodeUnknownEffect(EvalExecutionPlanSchema)))),
295
+ listPlans: (repositoryRoot) => Effect.gen(function* () {
296
+ const directory = artifactPath(repositoryRoot, "plans");
297
+ const exists = yield* fs
298
+ .exists(directory)
299
+ .pipe(Effect.mapError((cause) => artifactFailure("checking", directory, cause)));
300
+ if (!exists)
301
+ return [];
302
+ const entries = yield* fs
303
+ .readDirectory(directory)
304
+ .pipe(Effect.mapError((cause) => artifactFailure("listing", directory, cause)));
305
+ return entries
306
+ .filter((entry) => entry.endsWith(".json"))
307
+ .map((entry) => entry.slice(0, -".json".length))
308
+ .sort((left, right) => left.localeCompare(right));
309
+ }),
310
+ saveRunReport: (repositoryRoot, report) => Effect.gen(function* () {
311
+ yield* requireArtifactId("run", report.runId);
312
+ const name = paths.join("runs", report.runId, "report.json");
313
+ const existing = yield* read(repositoryRoot, name, (value) => Exit.match(Schema.decodeUnknownExit(EvalRunReportSchema)(value), {
314
+ onFailure: Effect.failCause,
315
+ onSuccess: Effect.succeed
316
+ }));
317
+ if (existing !== undefined) {
318
+ if (JSON.stringify(existing) !== JSON.stringify(report)) {
319
+ return yield* artifactFailure("writing", artifactPath(repositoryRoot, name), new Error("run report already exists with different contents"));
320
+ }
321
+ return artifactPath(repositoryRoot, name);
322
+ }
323
+ yield* write(repositoryRoot, name, report, false);
324
+ return artifactPath(repositoryRoot, name);
325
+ }),
326
+ loadRunReport: (repositoryRoot, runId) => requireArtifactId("run", runId).pipe(Effect.andThen(read(repositoryRoot, paths.join("runs", runId, "report.json"), (value) => Exit.match(Schema.decodeUnknownExit(EvalRunReportSchema)(value), {
327
+ onFailure: Effect.failCause,
328
+ onSuccess: Effect.succeed
329
+ })))),
330
+ listRunReports: (repositoryRoot) => Effect.gen(function* () {
331
+ const directory = artifactPath(repositoryRoot, "runs");
332
+ const exists = yield* fs
333
+ .exists(directory)
334
+ .pipe(Effect.mapError((cause) => artifactFailure("checking", directory, cause)));
335
+ if (!exists)
336
+ return [];
337
+ const entries = yield* fs
338
+ .readDirectory(directory)
339
+ .pipe(Effect.mapError((cause) => artifactFailure("listing", directory, cause)));
340
+ const reports = [];
341
+ for (const entry of entries) {
342
+ const report = paths.join(directory, entry, "report.json");
343
+ const reportExists = yield* fs
344
+ .exists(report)
345
+ .pipe(Effect.mapError((cause) => artifactFailure("checking", report, cause)));
346
+ if (reportExists)
347
+ reports.push(entry);
348
+ }
349
+ return reports.sort((left, right) => left.localeCompare(right));
350
+ })
351
+ });
352
+ });
353
+ export const EvalProjectArtifactsLive = Layer.effect(EvalProjectArtifacts, makeFileEvalProjectArtifacts);
@@ -0,0 +1,68 @@
1
+ import { type RoutingBasis } from "@velum-labs/routekit-eval-contracts";
2
+ import { Context, Effect, FileSystem, Layer, Path } from "effect";
3
+ import { EvalProjectAuthoringError } from "./errors.js";
4
+ import { type EvalEvaluationProposal, type EvalProjectConfiguration } from "./project-contracts.js";
5
+ export declare const EVAL_AUTHORING_SOURCE_BYTES = 60000;
6
+ export declare const EVAL_AUTHORING_SOURCE_FILES = 64;
7
+ export declare const EVAL_AUTHORING_CASES_PER_DIMENSION = 20;
8
+ /**
9
+ * Maximum serialized request body admitted for one authoring call.
10
+ *
11
+ * The bound includes the 60 KiB source inventory, worst-case JSON escaping,
12
+ * instructions, configuration, and the strict response schema. The gateway
13
+ * reserves serialized UTF-8 bytes as a conservative input-token upper bound.
14
+ */
15
+ export declare const EVAL_AUTHORING_REQUEST_BYTES = 512000;
16
+ export type EvalAuthoringSource = {
17
+ readonly path: string;
18
+ readonly content: string;
19
+ };
20
+ export type EvalAuthoringCompletion = {
21
+ readonly operationId: string;
22
+ readonly model: string;
23
+ readonly instructions: string;
24
+ readonly input: string;
25
+ readonly schemaName: string;
26
+ readonly jsonSchema: Readonly<Record<string, unknown>>;
27
+ readonly maximumOutputTokens: number;
28
+ };
29
+ export type EvalAuthoringTransportShape = {
30
+ readonly complete: (input: EvalAuthoringCompletion) => Effect.Effect<string, EvalProjectAuthoringError>;
31
+ };
32
+ declare const EvalAuthoringTransport_base: Context.ServiceClass<EvalAuthoringTransport, "@velum-labs/routekit-eval-setup/EvalAuthoringTransport", EvalAuthoringTransportShape>;
33
+ export declare class EvalAuthoringTransport extends EvalAuthoringTransport_base {
34
+ }
35
+ /**
36
+ * Revalidate every selected source at the read boundary. Discovery inventory
37
+ * membership is necessary but not sufficient because the checkout may mutate.
38
+ */
39
+ export declare function readProjectAuthoringSources(input: {
40
+ readonly repositoryRoot: string;
41
+ readonly selectedFiles: readonly string[];
42
+ readonly sourceInventory: readonly string[];
43
+ }): Effect.Effect<readonly EvalAuthoringSource[], EvalProjectAuthoringError, FileSystem.FileSystem | Path.Path>;
44
+ export declare function selectProjectAuthoringSourceFiles(input: {
45
+ readonly repositoryRoot: string;
46
+ readonly sourceInventory: readonly string[];
47
+ }): Effect.Effect<readonly string[], EvalProjectAuthoringError, FileSystem.FileSystem | Path.Path>;
48
+ export type EvalProjectAuthorShape = {
49
+ readonly proposeDimensions: (input: {
50
+ readonly operationId: string;
51
+ readonly repositoryRoot: string;
52
+ readonly sourceInventory: readonly string[];
53
+ readonly configuration: EvalProjectConfiguration;
54
+ }) => Effect.Effect<RoutingBasis["dimensions"], EvalProjectAuthoringError>;
55
+ readonly proposeEvaluations: (input: {
56
+ readonly operationId: string;
57
+ readonly repositoryRoot: string;
58
+ readonly sourceInventory: readonly string[];
59
+ readonly configuration: EvalProjectConfiguration;
60
+ readonly basis: RoutingBasis;
61
+ }) => Effect.Effect<Omit<EvalEvaluationProposal, "version" | "evaluationDigest" | "basisDigest" | "candidateModels" | "judgeModel">, EvalProjectAuthoringError>;
62
+ };
63
+ declare const EvalProjectAuthor_base: Context.ServiceClass<EvalProjectAuthor, "@velum-labs/routekit-eval-setup/EvalProjectAuthor", EvalProjectAuthorShape>;
64
+ export declare class EvalProjectAuthor extends EvalProjectAuthor_base {
65
+ }
66
+ export declare const makeEvalProjectAuthor: Effect.Effect<EvalProjectAuthorShape, never, Path.Path | FileSystem.FileSystem | EvalAuthoringTransport>;
67
+ export declare const EvalProjectAuthorLive: Layer.Layer<EvalProjectAuthor, never, Path.Path | FileSystem.FileSystem | EvalAuthoringTransport>;
68
+ export {};