@prismatic-io/lux 0.0.2-preview.37 → 0.0.2-preview.39
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/lib/assertions/core/command-exits-zero.d.ts +2 -2
- package/lib/assertions/core/command-exits-zero.d.ts.map +1 -1
- package/lib/assertions/core/command-exits-zero.js +12 -10
- package/lib/assertions/core/command-exits-zero.js.map +1 -1
- package/lib/assertions/core/safe-regex.d.ts.map +1 -1
- package/lib/assertions/core/safe-regex.js +7 -1
- package/lib/assertions/core/safe-regex.js.map +1 -1
- package/lib/assertions/rubric/evidence.d.ts +88 -0
- package/lib/assertions/rubric/evidence.d.ts.map +1 -0
- package/lib/assertions/rubric/evidence.js +75 -0
- package/lib/assertions/rubric/evidence.js.map +1 -0
- package/lib/assertions/rubric/index.d.ts +72 -43
- package/lib/assertions/rubric/index.d.ts.map +1 -1
- package/lib/assertions/rubric/index.js +12 -51
- package/lib/assertions/rubric/index.js.map +1 -1
- package/lib/assertions/rubric/internal.d.ts +1 -1
- package/lib/assertions/rubric/internal.d.ts.map +1 -1
- package/lib/assertions/rubric/internal.js +4 -4
- package/lib/assertions/rubric/internal.js.map +1 -1
- package/lib/authoring.d.ts +1 -0
- package/lib/authoring.d.ts.map +1 -1
- package/lib/authoring.js.map +1 -1
- package/lib/cli/harness-plan.d.ts +1 -0
- package/lib/cli/harness-plan.d.ts.map +1 -1
- package/lib/cli/harness-plan.js +1 -1
- package/lib/cli/harness-plan.js.map +1 -1
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +8 -4
- package/lib/cli/program.js.map +1 -1
- package/lib/container/driver.d.ts.map +1 -1
- package/lib/container/driver.js +10 -8
- package/lib/container/driver.js.map +1 -1
- package/lib/container/paths.d.ts +3 -0
- package/lib/container/paths.d.ts.map +1 -0
- package/lib/container/paths.js +27 -0
- package/lib/container/paths.js.map +1 -0
- package/lib/container/resources.d.ts +2 -0
- package/lib/container/resources.d.ts.map +1 -1
- package/lib/container/resources.js +17 -9
- package/lib/container/resources.js.map +1 -1
- package/lib/core/case-defaults.d.ts +16 -0
- package/lib/core/case-defaults.d.ts.map +1 -0
- package/lib/core/case-defaults.js +42 -0
- package/lib/core/case-defaults.js.map +1 -0
- package/lib/core/case.d.ts +8 -3
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +33 -4
- package/lib/core/case.js.map +1 -1
- package/lib/core/platform-process.d.ts +2 -2
- package/lib/core/platform-process.d.ts.map +1 -1
- package/lib/core/platform-process.js +8 -8
- package/lib/core/platform-process.js.map +1 -1
- package/lib/core/reference-defaults.d.ts +5 -0
- package/lib/core/reference-defaults.d.ts.map +1 -0
- package/lib/core/reference-defaults.js +42 -0
- package/lib/core/reference-defaults.js.map +1 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/orchestrator/config.d.ts +1 -0
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +3 -0
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/doctor.d.ts.map +1 -1
- package/lib/orchestrator/doctor.js +19 -42
- package/lib/orchestrator/doctor.js.map +1 -1
- package/lib/orchestrator/experiment-context.d.ts.map +1 -1
- package/lib/orchestrator/experiment-context.js +2 -1
- package/lib/orchestrator/experiment-context.js.map +1 -1
- package/lib/orchestrator/experiment-corpus.d.ts.map +1 -1
- package/lib/orchestrator/experiment-corpus.js +1 -1
- package/lib/orchestrator/experiment-corpus.js.map +1 -1
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +1 -0
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-identity.d.ts.map +1 -1
- package/lib/orchestrator/experiment-identity.js +3 -3
- package/lib/orchestrator/experiment-identity.js.map +1 -1
- package/lib/orchestrator/orchestrator.d.ts +3 -1
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +18 -19
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/profile.d.ts +4 -0
- package/lib/orchestrator/profile.d.ts.map +1 -0
- package/lib/orchestrator/profile.js +10 -0
- package/lib/orchestrator/profile.js.map +1 -0
- package/package.json +1 -1
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/assertions/core/command-exits-zero.ts +33 -22
- package/src/assertions/core/safe-regex.ts +16 -6
- package/src/assertions/rubric/evidence.ts +85 -0
- package/src/assertions/rubric/index.ts +12 -59
- package/src/assertions/rubric/internal.ts +7 -6
- package/src/authoring.ts +1 -0
- package/src/cli/harness-plan.ts +7 -1
- package/src/cli/program.ts +8 -4
- package/src/container/driver.ts +10 -8
- package/src/container/paths.ts +29 -0
- package/src/container/resources.ts +21 -9
- package/src/core/case-defaults.ts +65 -0
- package/src/core/case.ts +53 -6
- package/src/core/platform-process.ts +9 -8
- package/src/core/reference-defaults.ts +47 -0
- package/src/index.ts +2 -1
- package/src/orchestrator/config.ts +3 -0
- package/src/orchestrator/doctor.ts +30 -45
- package/src/orchestrator/experiment-context.ts +2 -1
- package/src/orchestrator/experiment-corpus.ts +6 -1
- package/src/orchestrator/experiment-evaluation.ts +1 -0
- package/src/orchestrator/experiment-identity.ts +18 -3
- package/src/orchestrator/orchestrator.ts +28 -17
- package/src/orchestrator/profile.ts +24 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Assertion, EvalCase } from "./case.js";
|
|
2
|
+
import { mergePluginConfig } from "./harness-matrix.js";
|
|
3
|
+
import { mergeAnswererReference, mergeDriverReference } from "./reference-defaults.js";
|
|
4
|
+
|
|
5
|
+
type DeepPartial<T> = T extends readonly unknown[]
|
|
6
|
+
? T
|
|
7
|
+
: T extends (...args: never[]) => unknown
|
|
8
|
+
? T
|
|
9
|
+
: T extends object
|
|
10
|
+
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
11
|
+
: T;
|
|
12
|
+
|
|
13
|
+
export type AssertionDefaults<A extends Assertion = Assertion> = {
|
|
14
|
+
[Type in A["type"]]?: DeepPartial<Omit<Extract<A, { type: Type }>, "type" | "id">>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type EvalCaseDefaults<A extends Assertion = Assertion> = Omit<
|
|
18
|
+
Partial<EvalCase>,
|
|
19
|
+
"id" | "prompt" | "assertions"
|
|
20
|
+
> & { assertions?: A[] | undefined };
|
|
21
|
+
|
|
22
|
+
/** Named assertions replace inherited checks; unnamed checks append in declaration order. */
|
|
23
|
+
const mergeAssertions = (base: Assertion[], local: Assertion[]): Assertion[] => {
|
|
24
|
+
const seen = new Set<string>();
|
|
25
|
+
for (const assertion of local) {
|
|
26
|
+
if (!assertion.id) continue;
|
|
27
|
+
if (seen.has(assertion.id)) throw new Error(`duplicate assertion id '${assertion.id}'`);
|
|
28
|
+
seen.add(assertion.id);
|
|
29
|
+
}
|
|
30
|
+
const result = [...base];
|
|
31
|
+
for (const assertion of local) {
|
|
32
|
+
const index = assertion.id ? result.findIndex((entry) => entry.id === assertion.id) : -1;
|
|
33
|
+
if (index < 0) result.push(assertion);
|
|
34
|
+
else result[index] = assertion;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const mergeCaseDefaults = (
|
|
40
|
+
base: EvalCaseDefaults,
|
|
41
|
+
local: EvalCaseDefaults,
|
|
42
|
+
): EvalCaseDefaults => ({
|
|
43
|
+
...base,
|
|
44
|
+
...local,
|
|
45
|
+
...(base.driver || local.driver
|
|
46
|
+
? { driver: mergeDriverReference(base.driver, local.driver) }
|
|
47
|
+
: {}),
|
|
48
|
+
...(base.answerer || local.answerer
|
|
49
|
+
? { answerer: mergeAnswererReference(base.answerer, local.answerer) }
|
|
50
|
+
: {}),
|
|
51
|
+
...(base.meta || local.meta
|
|
52
|
+
? { meta: mergePluginConfig(base.meta, local.meta) as EvalCase["meta"] }
|
|
53
|
+
: {}),
|
|
54
|
+
...(base.assertions || local.assertions
|
|
55
|
+
? { assertions: mergeAssertions(base.assertions ?? [], local.assertions ?? []) }
|
|
56
|
+
: {}),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const applyAssertionDefaults = (
|
|
60
|
+
assertions: Assertion[],
|
|
61
|
+
defaults: AssertionDefaults,
|
|
62
|
+
): Assertion[] =>
|
|
63
|
+
assertions.map((assertion) => ({
|
|
64
|
+
...(mergePluginConfig(defaults[assertion.type], assertion) as Assertion),
|
|
65
|
+
}));
|
package/src/core/case.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { AssertionPlugin } from "./assertion.js";
|
|
3
3
|
import { collectEvalCaseExtensions, setEvalCaseExtensions } from "./case-authoring-state.js";
|
|
4
|
+
import {
|
|
5
|
+
type AssertionDefaults,
|
|
6
|
+
applyAssertionDefaults,
|
|
7
|
+
type EvalCaseDefaults,
|
|
8
|
+
mergeCaseDefaults,
|
|
9
|
+
} from "./case-defaults.js";
|
|
4
10
|
import { DriverCapabilitySchema } from "./driver.js";
|
|
5
11
|
import { DriverExecutionSchema } from "./driver-execution.js";
|
|
6
12
|
import { EvaluationMetadataSchema } from "./evaluation-clusters.js";
|
|
7
|
-
import { HarnessMatrixSchema } from "./harness-matrix.js";
|
|
13
|
+
import { HarnessMatrixSchema, mergePluginConfig } from "./harness-matrix.js";
|
|
8
14
|
import type { LifecycleHooksPlugin } from "./lifecycle-hooks.js";
|
|
9
15
|
import { formatZodIssues } from "./zod.js";
|
|
10
16
|
|
|
@@ -96,13 +102,18 @@ export type EvalCaseInput<AllowedAssertion extends Assertion = Assertion> = Omit
|
|
|
96
102
|
"assertions"
|
|
97
103
|
> & {
|
|
98
104
|
assertions?: AllowedAssertion[];
|
|
105
|
+
/** Remove named inherited checks without replacing the rest of the suite plan. */
|
|
106
|
+
omitAssertions?: string[];
|
|
99
107
|
};
|
|
100
108
|
|
|
101
109
|
export type EvalCaseAuthoringOptions<
|
|
102
110
|
Plugins extends readonly AssertionPlugin[] = readonly AssertionPlugin[],
|
|
111
|
+
AllowedAssertion extends Assertion = Assertion,
|
|
103
112
|
> = {
|
|
104
113
|
assertions?: Plugins;
|
|
105
114
|
fixtures?: readonly LifecycleHooksPlugin[];
|
|
115
|
+
defaults?: EvalCaseDefaults<AllowedAssertion | AssertionInputOf<Plugins[number]>>;
|
|
116
|
+
assertionDefaults?: AssertionDefaults<AllowedAssertion | AssertionInputOf<Plugins[number]>>;
|
|
106
117
|
};
|
|
107
118
|
|
|
108
119
|
export type EvalCaseDefinition<AllowedAssertion extends Assertion = Assertion> = {
|
|
@@ -112,16 +123,44 @@ export type EvalCaseDefinition<AllowedAssertion extends Assertion = Assertion> =
|
|
|
112
123
|
* Further calls to `extend` append fixtures in setup order.
|
|
113
124
|
*/
|
|
114
125
|
extend<const Plugins extends readonly AssertionPlugin[] = readonly []>(
|
|
115
|
-
options: EvalCaseAuthoringOptions<Plugins>,
|
|
126
|
+
options: EvalCaseAuthoringOptions<Plugins, AllowedAssertion>,
|
|
116
127
|
): EvalCaseDefinition<AllowedAssertion | AssertionInputOf<Plugins[number]>>;
|
|
117
128
|
};
|
|
118
129
|
|
|
119
130
|
export const createEvalCaseDefinition = <AllowedAssertion extends Assertion = Assertion>(
|
|
120
131
|
inheritedAssertions: readonly AssertionPlugin[] = [],
|
|
121
132
|
inheritedFixtures: readonly LifecycleHooksPlugin[] = [],
|
|
133
|
+
defaults: EvalCaseDefaults = {},
|
|
134
|
+
assertionDefaults: AssertionDefaults = {},
|
|
122
135
|
): EvalCaseDefinition<AllowedAssertion> => {
|
|
123
136
|
const define = ((input: EvalCaseInput<AllowedAssertion>): EvalCase => {
|
|
124
|
-
const
|
|
137
|
+
const { omitAssertions = [], ...local } = input;
|
|
138
|
+
for (const id of omitAssertions) {
|
|
139
|
+
if (!defaults.assertions?.some((assertion) => assertion.id === id)) {
|
|
140
|
+
throw new Error(`cannot omit unknown inherited assertion '${id}'`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const resolved = mergeCaseDefaults(
|
|
144
|
+
{
|
|
145
|
+
...defaults,
|
|
146
|
+
...(defaults.assertions
|
|
147
|
+
? {
|
|
148
|
+
assertions: defaults.assertions.filter(
|
|
149
|
+
(assertion) => !omitAssertions.includes(assertion.id ?? ""),
|
|
150
|
+
),
|
|
151
|
+
}
|
|
152
|
+
: {}),
|
|
153
|
+
},
|
|
154
|
+
local,
|
|
155
|
+
);
|
|
156
|
+
const parsed = EvalCaseSchema.safeParse({
|
|
157
|
+
...resolved,
|
|
158
|
+
...("id" in local ? { id: local.id } : {}),
|
|
159
|
+
prompt: local.prompt,
|
|
160
|
+
...(resolved.assertions
|
|
161
|
+
? { assertions: applyAssertionDefaults(resolved.assertions, assertionDefaults) }
|
|
162
|
+
: {}),
|
|
163
|
+
});
|
|
125
164
|
if (!parsed.success) {
|
|
126
165
|
throw new Error(`Invalid eval case: ${formatZodIssues(parsed.error)}`);
|
|
127
166
|
}
|
|
@@ -133,11 +172,19 @@ export const createEvalCaseDefinition = <AllowedAssertion extends Assertion = As
|
|
|
133
172
|
setEvalCaseExtensions(parsed.data, extensions);
|
|
134
173
|
return parsed.data;
|
|
135
174
|
}) as EvalCaseDefinition<AllowedAssertion>;
|
|
136
|
-
define.extend = ((
|
|
137
|
-
|
|
175
|
+
define.extend = ((options: EvalCaseAuthoringOptions) => {
|
|
176
|
+
const { assertions = [], fixtures = [] } = options;
|
|
177
|
+
const inherited = EvalCaseObjectSchema.omit({ id: true, prompt: true })
|
|
178
|
+
.partial()
|
|
179
|
+
.strict()
|
|
180
|
+
.parse(options.defaults ?? {});
|
|
181
|
+
return createEvalCaseDefinition(
|
|
138
182
|
[...inheritedAssertions, ...assertions],
|
|
139
183
|
[...inheritedFixtures, ...fixtures],
|
|
140
|
-
|
|
184
|
+
mergeCaseDefaults(defaults, inherited),
|
|
185
|
+
mergePluginConfig(assertionDefaults, options.assertionDefaults ?? {}) as AssertionDefaults,
|
|
186
|
+
);
|
|
187
|
+
}) as EvalCaseDefinition<AllowedAssertion>["extend"];
|
|
141
188
|
return define;
|
|
142
189
|
};
|
|
143
190
|
|
|
@@ -233,8 +233,9 @@ export const forceWindowsTree = async (
|
|
|
233
233
|
discover: (pid: number) => Promise<IProcessInfo[]> = windowsProcessList,
|
|
234
234
|
signal: (pid: number) => void = signalWindowsProcess,
|
|
235
235
|
discoveryTimeoutMs = 2_000,
|
|
236
|
+
stillOwned: () => boolean = () => true,
|
|
236
237
|
): Promise<void> => {
|
|
237
|
-
if (!safeWindowsSignalTarget(pid)) return;
|
|
238
|
+
if (!stillOwned() || !safeWindowsSignalTarget(pid)) return;
|
|
238
239
|
let processes: IProcessInfo[] = [];
|
|
239
240
|
let timer: NodeJS.Timeout | undefined;
|
|
240
241
|
try {
|
|
@@ -257,7 +258,8 @@ export const forceWindowsTree = async (
|
|
|
257
258
|
} finally {
|
|
258
259
|
if (timer) clearTimeout(timer);
|
|
259
260
|
}
|
|
260
|
-
|
|
261
|
+
// Discovery is asynchronous; an exited leader can already have a recycled PID.
|
|
262
|
+
if (!stillOwned() || windowsAncestorPids(processes, process).has(pid)) return;
|
|
261
263
|
const descendants = windowsDescendants(pid, processes);
|
|
262
264
|
for (const process of descendants.toReversed()) {
|
|
263
265
|
if (safeWindowsSignalTarget(process.pid)) signal(process.pid);
|
|
@@ -300,19 +302,18 @@ export const posixProcessPlatform = (
|
|
|
300
302
|
});
|
|
301
303
|
|
|
302
304
|
export const windowsProcessPlatform = (
|
|
303
|
-
forceTree: (pid: number) => Promise<void> =
|
|
305
|
+
forceTree: (pid: number, stillOwned: () => boolean) => Promise<void> = (pid, stillOwned) =>
|
|
306
|
+
forceWindowsTree(pid, undefined, undefined, undefined, stillOwned),
|
|
304
307
|
): ProcessPlatform => ({
|
|
305
308
|
detached: false,
|
|
306
309
|
processTreeFor: (child) => {
|
|
307
310
|
const pid = child.pid;
|
|
308
311
|
let forced: Promise<void> | undefined;
|
|
312
|
+
const stillOwned = (): boolean => child.exitCode == null && child.signalCode == null;
|
|
309
313
|
const forceOnce = (): Promise<void> => {
|
|
310
314
|
if (forced) return forced;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
(child.signalCode !== null && child.signalCode !== undefined);
|
|
314
|
-
if (pid === undefined || leaderHasExited) forced = Promise.resolve();
|
|
315
|
-
else forced = forceTree(pid);
|
|
315
|
+
if (pid === undefined || !stillOwned()) forced = Promise.resolve();
|
|
316
|
+
else forced = forceTree(pid, stillOwned);
|
|
316
317
|
return forced;
|
|
317
318
|
};
|
|
318
319
|
return {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AnswererRef, DriverRef } from "./case.js";
|
|
2
|
+
import { mergePluginConfig } from "./harness-matrix.js";
|
|
3
|
+
|
|
4
|
+
export const withDriverDefaults = (
|
|
5
|
+
reference: DriverRef | undefined,
|
|
6
|
+
defaults: Record<string, unknown> | undefined,
|
|
7
|
+
): DriverRef | undefined => {
|
|
8
|
+
if (!reference || !defaults) return reference;
|
|
9
|
+
const selected = typeof reference === "string" ? { name: reference } : reference;
|
|
10
|
+
return { ...selected, config: mergePluginConfig(defaults, selected.config) };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const mergeDriverReference = (
|
|
14
|
+
base: DriverRef | undefined,
|
|
15
|
+
local: DriverRef | undefined,
|
|
16
|
+
): DriverRef | undefined => {
|
|
17
|
+
if (local === undefined) return base;
|
|
18
|
+
if (base === undefined) return local;
|
|
19
|
+
const first = typeof base === "string" ? { name: base } : base;
|
|
20
|
+
const second = typeof local === "string" ? { name: local } : local;
|
|
21
|
+
if (first.name !== second.name) return local;
|
|
22
|
+
return {
|
|
23
|
+
name: second.name,
|
|
24
|
+
config: mergePluginConfig(first.config, second.config),
|
|
25
|
+
...((second.execution ?? first.execution)
|
|
26
|
+
? { execution: second.execution ?? first.execution }
|
|
27
|
+
: {}),
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const mergeAnswererReference = (
|
|
32
|
+
base: AnswererRef | undefined,
|
|
33
|
+
local: AnswererRef | undefined,
|
|
34
|
+
): AnswererRef | undefined => {
|
|
35
|
+
const merged = mergeDriverReference(base, local);
|
|
36
|
+
if (typeof merged !== "object" || merged.name !== "persona") return merged;
|
|
37
|
+
const first = typeof base === "object" ? base.config : undefined;
|
|
38
|
+
const second = typeof local === "object" ? local.config : undefined;
|
|
39
|
+
if (!first || !second || typeof first !== "object" || typeof second !== "object") return merged;
|
|
40
|
+
if (!("provider" in second) || ("provider" in first && second.provider === first.provider))
|
|
41
|
+
return merged;
|
|
42
|
+
const config = { ...(merged.config as Record<string, unknown>) };
|
|
43
|
+
for (const key of ["model", "command", "reasoningEffort"]) {
|
|
44
|
+
if (!(key in second)) delete config[key];
|
|
45
|
+
}
|
|
46
|
+
return { name: merged.name, config };
|
|
47
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ export { DurationSchema, durationMs } from "./core/duration.js";
|
|
|
4
4
|
// lifecycles. Explicit exports keep implementation and test helpers private.
|
|
5
5
|
|
|
6
6
|
export { z } from "zod";
|
|
7
|
-
|
|
8
7
|
// Bundled answerers and drivers. Test injection and protocol-normalization
|
|
9
8
|
// helpers stay in their implementation modules.
|
|
10
9
|
export {
|
|
@@ -140,6 +139,7 @@ export {
|
|
|
140
139
|
type RubricConfig,
|
|
141
140
|
RubricConfigSchema,
|
|
142
141
|
type RubricEvidence,
|
|
142
|
+
type RubricEvidenceInput,
|
|
143
143
|
RubricEvidenceSchema,
|
|
144
144
|
RubricModelAccountingDetailSchema,
|
|
145
145
|
rubricAssertion,
|
|
@@ -237,6 +237,7 @@ export {
|
|
|
237
237
|
type PersistedCase,
|
|
238
238
|
PersistedCaseSchema,
|
|
239
239
|
} from "./core/case.js";
|
|
240
|
+
export type { AssertionDefaults, EvalCaseDefaults } from "./core/case-defaults.js";
|
|
240
241
|
export {
|
|
241
242
|
CONVERSATION_TURN_REQUEST,
|
|
242
243
|
type ConversationTurnRequest,
|
|
@@ -84,6 +84,8 @@ export const LuxConfigSchema = z
|
|
|
84
84
|
subjectRoot: z.string().optional(),
|
|
85
85
|
defaultAnswerer: AnswererRefSchema.default("persona"),
|
|
86
86
|
defaultDriver: DriverRefSchema.optional(),
|
|
87
|
+
/** Configuration inherited by every selected driver, then validated by that plugin. */
|
|
88
|
+
driverDefaults: z.record(z.string(), z.unknown()).optional(),
|
|
87
89
|
/** Driver, model, and effort permutations every case runs through by default. */
|
|
88
90
|
matrix: HarnessMatrixSchema.optional(),
|
|
89
91
|
/** Shared provider/model defaults for persona answering and rubric judging. */
|
|
@@ -172,6 +174,7 @@ export const loadLuxConfig = async (cwd: string = process.cwd()): Promise<Resolv
|
|
|
172
174
|
...(validated.fixturesRoot ? { fixturesRoot: resolve(root, validated.fixturesRoot) } : {}),
|
|
173
175
|
defaultAnswerer: validated.defaultAnswerer,
|
|
174
176
|
...(validated.defaultDriver ? { defaultDriver: validated.defaultDriver } : {}),
|
|
177
|
+
...(validated.driverDefaults ? { driverDefaults: validated.driverDefaults } : {}),
|
|
175
178
|
...(validated.matrix ? { matrix: validated.matrix } : {}),
|
|
176
179
|
...(validated.harness ? { harness: validated.harness } : {}),
|
|
177
180
|
concurrency: validated.concurrency,
|
|
@@ -7,6 +7,7 @@ import { RubricConfigSchema } from "../assertions/rubric/index.js";
|
|
|
7
7
|
import { driverExecutionOf } from "../core/driver-execution.js";
|
|
8
8
|
import {
|
|
9
9
|
casePermutations,
|
|
10
|
+
type DriverRef,
|
|
10
11
|
type EvalCase,
|
|
11
12
|
expandHarnessMatrix,
|
|
12
13
|
type HarnessPermutation,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
permutationId,
|
|
15
16
|
type Registry,
|
|
16
17
|
} from "../core/index.js";
|
|
18
|
+
import { mergeDriverReference, withDriverDefaults } from "../core/reference-defaults.js";
|
|
17
19
|
import { registryForEvalCase } from "./case-extensions.js";
|
|
18
20
|
import { buildRegistryFromConfig, loadLuxConfig, type ResolvedLuxConfig } from "./config.js";
|
|
19
21
|
import { type DiscoveredCase, type DiscoveryResult, discoverCases } from "./discover.js";
|
|
@@ -33,6 +35,8 @@ import {
|
|
|
33
35
|
hasHarnessCatalogDrift,
|
|
34
36
|
} from "./harness-catalog-drift.js";
|
|
35
37
|
import { loadEvalCase } from "./loader.js";
|
|
38
|
+
import { resolveAnswererReference } from "./orchestrator.js";
|
|
39
|
+
import { resolveProfile } from "./profile.js";
|
|
36
40
|
import { validatePluginConfig, validatePluginConfigAsync } from "./validate-plugin-config.js";
|
|
37
41
|
|
|
38
42
|
export const DoctorDiagnosticSchema = z
|
|
@@ -120,42 +124,17 @@ const rawRefConfig = (reference: unknown): unknown =>
|
|
|
120
124
|
? (reference as { config?: unknown }).config
|
|
121
125
|
: undefined;
|
|
122
126
|
|
|
123
|
-
const mergeRecords = (base: unknown, local: unknown): unknown => {
|
|
124
|
-
if (
|
|
125
|
-
!base ||
|
|
126
|
-
typeof base !== "object" ||
|
|
127
|
-
Array.isArray(base) ||
|
|
128
|
-
!local ||
|
|
129
|
-
typeof local !== "object" ||
|
|
130
|
-
Array.isArray(local)
|
|
131
|
-
) {
|
|
132
|
-
return local ?? base;
|
|
133
|
-
}
|
|
134
|
-
const merged: Record<string, unknown> = { ...(base as Record<string, unknown>) };
|
|
135
|
-
for (const [key, value] of Object.entries(local as Record<string, unknown>)) {
|
|
136
|
-
merged[key] = key in merged ? mergeRecords(merged[key], value) : value;
|
|
137
|
-
}
|
|
138
|
-
return merged;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const effectiveDriverReference = (local: unknown, fallback: unknown): unknown => {
|
|
142
|
-
if (!local) return fallback;
|
|
143
|
-
if (!fallback || refName(local) !== refName(fallback)) return local;
|
|
144
|
-
return {
|
|
145
|
-
name: refName(local),
|
|
146
|
-
config: mergeRecords(rawRefConfig(fallback), rawRefConfig(local)),
|
|
147
|
-
execution: driverExecutionOf(local) ?? driverExecutionOf(fallback),
|
|
148
|
-
};
|
|
149
|
-
};
|
|
150
|
-
|
|
151
127
|
const boundPermutation = (
|
|
152
128
|
permutation: HarnessPermutation,
|
|
153
129
|
config: ResolvedLuxConfig,
|
|
154
130
|
registry: Registry,
|
|
155
|
-
):
|
|
131
|
+
): DriverRef => {
|
|
156
132
|
const plugin = registry.getDriver(permutation.driver);
|
|
157
133
|
if (!plugin) throw new Error(`unknown driver '${permutation.driver}'`);
|
|
158
|
-
return
|
|
134
|
+
return withDriverDefaults(
|
|
135
|
+
permutationDriverRef(permutation, config.defaultDriver, plugin),
|
|
136
|
+
config.driverDefaults,
|
|
137
|
+
) as DriverRef;
|
|
159
138
|
};
|
|
160
139
|
|
|
161
140
|
const caseDriverReferences = (
|
|
@@ -167,16 +146,20 @@ const caseDriverReferences = (
|
|
|
167
146
|
const permutations = casePermutations(authored, config.matrix);
|
|
168
147
|
if (permutations.length > 0) {
|
|
169
148
|
return permutations.map((permutation) =>
|
|
170
|
-
|
|
149
|
+
mergeDriverReference(boundPermutation(permutation, config, registry), local),
|
|
171
150
|
);
|
|
172
151
|
}
|
|
173
152
|
const fallbacks = [
|
|
174
|
-
config.
|
|
175
|
-
...Object.values(config.profiles ?? {}).map(
|
|
153
|
+
resolveProfile(config).driver,
|
|
154
|
+
...Object.values(config.profiles ?? {}).map(
|
|
155
|
+
(profile) => resolveProfile(config, profile).driver,
|
|
156
|
+
),
|
|
176
157
|
].filter((reference) => reference !== undefined);
|
|
177
158
|
if (!local) return [...new Set(fallbacks)];
|
|
178
|
-
if (fallbacks.length === 0) return [local];
|
|
179
|
-
const references = fallbacks.map((fallback) =>
|
|
159
|
+
if (fallbacks.length === 0) return [withDriverDefaults(local, config.driverDefaults)];
|
|
160
|
+
const references = fallbacks.map((fallback) =>
|
|
161
|
+
withDriverDefaults(mergeDriverReference(fallback, local), config.driverDefaults),
|
|
162
|
+
);
|
|
180
163
|
return [
|
|
181
164
|
...new Map(references.map((reference) => [JSON.stringify(reference), reference])).values(),
|
|
182
165
|
];
|
|
@@ -210,7 +193,9 @@ const rubricCommands = (assertion: unknown, config: ResolvedLuxConfig): string[]
|
|
|
210
193
|
const rubric = RubricConfigSchema.parse(assertion);
|
|
211
194
|
const harnesses = [
|
|
212
195
|
config.harness,
|
|
213
|
-
...Object.values(config.profiles ?? {}).map(
|
|
196
|
+
...Object.values(config.profiles ?? {}).map(
|
|
197
|
+
(profile) => resolveProfile(config, profile).harness,
|
|
198
|
+
),
|
|
214
199
|
];
|
|
215
200
|
const commands = harnesses.map((harness) => {
|
|
216
201
|
const provider = rubric.provider ?? harness?.provider ?? "claude-code";
|
|
@@ -227,7 +212,7 @@ const configuredCliTargets = (config: ResolvedLuxConfig, registry: Registry): Cl
|
|
|
227
212
|
const add = (label: string, command: string | undefined): void => {
|
|
228
213
|
if (command) targets.push({ label, command });
|
|
229
214
|
};
|
|
230
|
-
for (const command of driverCommands(config.
|
|
215
|
+
for (const command of driverCommands(resolveProfile(config).driver, registry))
|
|
231
216
|
add("default driver", command);
|
|
232
217
|
for (const permutation of expandHarnessMatrix(config.matrix ?? [])) {
|
|
233
218
|
try {
|
|
@@ -250,11 +235,11 @@ const configuredCliTargets = (config: ResolvedLuxConfig, registry: Registry): Cl
|
|
|
250
235
|
for (const [id, profile] of Object.entries(config.profiles ?? {}).sort(([a], [b]) =>
|
|
251
236
|
a.localeCompare(b),
|
|
252
237
|
)) {
|
|
253
|
-
for (const command of driverCommands(profile.driver
|
|
238
|
+
for (const command of driverCommands(resolveProfile(config, profile).driver, registry)) {
|
|
254
239
|
add(`profile '${id}' driver`, command);
|
|
255
240
|
}
|
|
256
|
-
add(`profile '${id}' answerer`, answererCommand(profile.answerer
|
|
257
|
-
const harness = profile.harness
|
|
241
|
+
add(`profile '${id}' answerer`, answererCommand(resolveProfile(config, profile).answerer));
|
|
242
|
+
const harness = resolveProfile(config, profile).harness;
|
|
258
243
|
if (harness) {
|
|
259
244
|
add(
|
|
260
245
|
`profile '${id}' harness`,
|
|
@@ -356,18 +341,18 @@ const loadDoctorRegistry = (
|
|
|
356
341
|
const configuredDriverReferences = (
|
|
357
342
|
config: ResolvedLuxConfig,
|
|
358
343
|
): Array<readonly [string, unknown]> => [
|
|
359
|
-
["default", config.
|
|
344
|
+
["default", resolveProfile(config).driver],
|
|
360
345
|
...Object.entries(config.profiles ?? {}).map(
|
|
361
|
-
([id, profile]) => [`profile '${id}'`, profile.driver] as const,
|
|
346
|
+
([id, profile]) => [`profile '${id}'`, resolveProfile(config, profile).driver] as const,
|
|
362
347
|
),
|
|
363
348
|
];
|
|
364
349
|
|
|
365
350
|
const configuredAnswererReferences = (
|
|
366
351
|
config: ResolvedLuxConfig,
|
|
367
352
|
): Array<readonly [string, unknown]> => [
|
|
368
|
-
["default", config.
|
|
353
|
+
["default", resolveProfile(config).answerer],
|
|
369
354
|
...Object.entries(config.profiles ?? {}).map(
|
|
370
|
-
([id, profile]) => [`profile '${id}'`, profile.answerer] as const,
|
|
355
|
+
([id, profile]) => [`profile '${id}'`, resolveProfile(config, profile).answerer] as const,
|
|
371
356
|
),
|
|
372
357
|
];
|
|
373
358
|
|
|
@@ -651,7 +636,7 @@ const inspectCase = async (
|
|
|
651
636
|
const authored = await loadEvalCase(discovered.path);
|
|
652
637
|
const caseRegistry = registryForEvalCase(registry, authored);
|
|
653
638
|
const drivers = caseDriverReferences(authored, config, caseRegistry);
|
|
654
|
-
const answerer = authored.
|
|
639
|
+
const answerer = resolveAnswererReference(authored, config.defaultAnswerer, config.harness);
|
|
655
640
|
validateCaseDrivers(discovered.id, drivers, caseRegistry, recorder, authored.requires);
|
|
656
641
|
validateCaseAnswerer(discovered.id, answerer, caseRegistry, recorder);
|
|
657
642
|
return [
|
|
@@ -2,6 +2,7 @@ import type { Registry } from "../core/index.js";
|
|
|
2
2
|
import type { ResolvedLuxConfig } from "./config.js";
|
|
3
3
|
import type { discoverCases } from "./discover.js";
|
|
4
4
|
import type { LoadedExperimentCampaign } from "./experiment-loader.js";
|
|
5
|
+
import { resolveProfile } from "./profile.js";
|
|
5
6
|
|
|
6
7
|
export type CorpusSelection =
|
|
7
8
|
| {
|
|
@@ -38,5 +39,5 @@ export const profileOrThrow = (config: ResolvedLuxConfig, profileId: string) =>
|
|
|
38
39
|
`experiment profile '${profileId}' is not configured${available.length > 0 ? `; available: ${available.join(", ")}` : ""}`,
|
|
39
40
|
);
|
|
40
41
|
}
|
|
41
|
-
return profile;
|
|
42
|
+
return resolveProfile(config, profile);
|
|
42
43
|
};
|
|
@@ -131,7 +131,12 @@ export const agentCorpusHash = async (
|
|
|
131
131
|
const profile = profileOrThrow(execution.config, profileId);
|
|
132
132
|
const harness = profile.harness ?? execution.config.harness;
|
|
133
133
|
const driver = bindSubjectRoot(
|
|
134
|
-
resolveDriverReference(
|
|
134
|
+
resolveDriverReference(
|
|
135
|
+
bound,
|
|
136
|
+
profile.driver ?? execution.config.defaultDriver,
|
|
137
|
+
undefined,
|
|
138
|
+
execution.config.driverDefaults,
|
|
139
|
+
),
|
|
135
140
|
execution.loaded.subjectRoot,
|
|
136
141
|
);
|
|
137
142
|
const answerer = bindSubjectRoot(
|
|
@@ -512,6 +512,7 @@ export const evaluateAgentCandidate = async (options: {
|
|
|
512
512
|
const defaultDriver = profile.driver ?? execution.config.defaultDriver;
|
|
513
513
|
const registry = registryWithHarness(execution.registry, execution.config, harness);
|
|
514
514
|
const orchestrator = new Orchestrator({
|
|
515
|
+
driverDefaults: execution.config.driverDefaults,
|
|
515
516
|
environment: profile.environment ?? execution.config.environment,
|
|
516
517
|
registry,
|
|
517
518
|
defaultAnswerer: profile.answerer ?? execution.config.defaultAnswerer,
|
|
@@ -132,7 +132,12 @@ const trustedExecutableEntrypoints = async (
|
|
|
132
132
|
for (const profileId of execution.loaded.campaign.profiles) {
|
|
133
133
|
const profile = profileOrThrow(execution.config, profileId);
|
|
134
134
|
const driver = bindSubjectRoot(
|
|
135
|
-
resolveDriverReference(
|
|
135
|
+
resolveDriverReference(
|
|
136
|
+
authored,
|
|
137
|
+
profile.driver ?? execution.config.defaultDriver,
|
|
138
|
+
undefined,
|
|
139
|
+
execution.config.driverDefaults,
|
|
140
|
+
),
|
|
136
141
|
execution.loaded.subjectRoot,
|
|
137
142
|
);
|
|
138
143
|
for (const command of referencedDriverRuntime(driver, execution.registry).commands)
|
|
@@ -198,7 +203,12 @@ export const runtimeCliReferences = async (
|
|
|
198
203
|
const profile = profileOrThrow(execution.config, profileId);
|
|
199
204
|
const harness = profile.harness ?? execution.config.harness;
|
|
200
205
|
const driver = bindSubjectRoot(
|
|
201
|
-
resolveDriverReference(
|
|
206
|
+
resolveDriverReference(
|
|
207
|
+
authored,
|
|
208
|
+
profile.driver ?? execution.config.defaultDriver,
|
|
209
|
+
undefined,
|
|
210
|
+
execution.config.driverDefaults,
|
|
211
|
+
),
|
|
202
212
|
execution.loaded.subjectRoot,
|
|
203
213
|
);
|
|
204
214
|
const answerer = bindSubjectRoot(
|
|
@@ -275,7 +285,12 @@ export const validateAgentCaseProfileDrivers = async (
|
|
|
275
285
|
for (const profileId of execution.loaded.campaign.profiles) {
|
|
276
286
|
const profile = profileOrThrow(execution.config, profileId);
|
|
277
287
|
const reference = bindSubjectRoot(
|
|
278
|
-
resolveDriverReference(
|
|
288
|
+
resolveDriverReference(
|
|
289
|
+
authored,
|
|
290
|
+
profile.driver ?? execution.config.defaultDriver,
|
|
291
|
+
undefined,
|
|
292
|
+
execution.config.driverDefaults,
|
|
293
|
+
),
|
|
279
294
|
execution.loaded.subjectRoot,
|
|
280
295
|
);
|
|
281
296
|
const name = typeof reference === "string" ? reference : reference.name;
|
|
@@ -17,13 +17,17 @@ import {
|
|
|
17
17
|
type HarnessModelConfig,
|
|
18
18
|
type LifecycleHooksPlugin,
|
|
19
19
|
mergeHarnessModelConfig,
|
|
20
|
-
mergePluginConfig,
|
|
21
20
|
type Registry,
|
|
22
21
|
type RunMetadata,
|
|
23
22
|
type RunObserver,
|
|
24
23
|
type SuiteLifecycleHookContext,
|
|
25
24
|
valueHash,
|
|
26
25
|
} from "../core/index.js";
|
|
26
|
+
import {
|
|
27
|
+
mergeAnswererReference,
|
|
28
|
+
mergeDriverReference,
|
|
29
|
+
withDriverDefaults,
|
|
30
|
+
} from "../core/reference-defaults.js";
|
|
27
31
|
import {
|
|
28
32
|
fixturesForEvalCase,
|
|
29
33
|
mergeLifecycleFixtures,
|
|
@@ -116,6 +120,7 @@ export type OrchestratorOptions = {
|
|
|
116
120
|
defaultAnswerer?: AnswererRef;
|
|
117
121
|
/** Driver used when a case names none. */
|
|
118
122
|
defaultDriver?: DriverRef;
|
|
123
|
+
driverDefaults?: Record<string, unknown> | undefined;
|
|
119
124
|
/** Shared provider/model defaults for built-in harness components. */
|
|
120
125
|
harness?: HarnessModelConfig;
|
|
121
126
|
/** Versioned project resource fixtures. */
|
|
@@ -233,6 +238,7 @@ export const resolveDriverReference = (
|
|
|
233
238
|
evalCase: EvalCase,
|
|
234
239
|
fallback: DriverRef | undefined,
|
|
235
240
|
permutation?: DriverRef | undefined,
|
|
241
|
+
defaults?: Record<string, unknown> | undefined,
|
|
236
242
|
): DriverRef => {
|
|
237
243
|
const local = evalCase.driver;
|
|
238
244
|
if (permutation && local && driverNameOf(local) !== driverNameOf(permutation)) {
|
|
@@ -240,20 +246,12 @@ export const resolveDriverReference = (
|
|
|
240
246
|
`case is pinned to driver '${driverNameOf(local)}' but the run selected '${driverNameOf(permutation)}'`,
|
|
241
247
|
);
|
|
242
248
|
}
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// Naming the same driver opts into project defaults. A case object only
|
|
251
|
-
// needs to carry the fields it wants to override (for example a timeout).
|
|
252
|
-
return {
|
|
253
|
-
name: driverNameOf(local),
|
|
254
|
-
config: mergePluginConfig(driverConfigOf(base), driverConfigOf(local)),
|
|
255
|
-
execution: driverExecutionOf(local) ?? driverExecutionOf(base),
|
|
256
|
-
};
|
|
249
|
+
const reference = withDriverDefaults(
|
|
250
|
+
mergeDriverReference(permutation ?? fallback, local),
|
|
251
|
+
defaults,
|
|
252
|
+
);
|
|
253
|
+
if (!reference) throw new Error("case names no driver and no defaultDriver is configured");
|
|
254
|
+
return reference;
|
|
257
255
|
};
|
|
258
256
|
|
|
259
257
|
export const resolveAnswererReference = (
|
|
@@ -262,7 +260,18 @@ export const resolveAnswererReference = (
|
|
|
262
260
|
harness: HarnessModelConfig | undefined,
|
|
263
261
|
override?: string,
|
|
264
262
|
): AnswererRef => {
|
|
265
|
-
const
|
|
263
|
+
const fallbackName = typeof fallback === "string" ? fallback : fallback.name;
|
|
264
|
+
const inherited =
|
|
265
|
+
harness && fallbackName === "persona"
|
|
266
|
+
? {
|
|
267
|
+
name: "persona",
|
|
268
|
+
config: mergeHarnessModelConfig(
|
|
269
|
+
harness,
|
|
270
|
+
typeof fallback === "object" ? fallback.config : undefined,
|
|
271
|
+
),
|
|
272
|
+
}
|
|
273
|
+
: fallback;
|
|
274
|
+
const ref = mergeAnswererReference(inherited, override ?? evalCase.answerer) as AnswererRef;
|
|
266
275
|
const name = typeof ref === "string" ? ref : ref.name;
|
|
267
276
|
if (!harness || name !== "persona") return ref;
|
|
268
277
|
return {
|
|
@@ -310,6 +319,7 @@ export class Orchestrator {
|
|
|
310
319
|
readonly registry: Registry;
|
|
311
320
|
private readonly defaultAnswerer: AnswererRef;
|
|
312
321
|
private readonly defaultDriver: DriverRef | undefined;
|
|
322
|
+
private readonly driverDefaults: Record<string, unknown> | undefined;
|
|
313
323
|
private readonly harness: HarnessModelConfig | undefined;
|
|
314
324
|
private readonly lifecycleFixtures: readonly LifecycleHooksPlugin[];
|
|
315
325
|
|
|
@@ -320,6 +330,7 @@ export class Orchestrator {
|
|
|
320
330
|
this.registry = options.registry;
|
|
321
331
|
this.defaultAnswerer = options.defaultAnswerer ?? "persona";
|
|
322
332
|
this.defaultDriver = options.defaultDriver;
|
|
333
|
+
this.driverDefaults = options.driverDefaults;
|
|
323
334
|
this.harness = options.harness;
|
|
324
335
|
this.lifecycleFixtures = [...(options.lifecycleFixtures ?? [])];
|
|
325
336
|
}
|
|
@@ -359,7 +370,7 @@ export class Orchestrator {
|
|
|
359
370
|
);
|
|
360
371
|
|
|
361
372
|
const driverRef = bindSubjectRoot(
|
|
362
|
-
resolveDriverReference(evalCase, this.defaultDriver, request.driver),
|
|
373
|
+
resolveDriverReference(evalCase, this.defaultDriver, request.driver, this.driverDefaults),
|
|
363
374
|
request.subjectRoot,
|
|
364
375
|
);
|
|
365
376
|
const driverName = driverNameOf(driverRef);
|