@prismatic-io/lux 0.0.2-preview.3 → 0.0.2-preview.4

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 (79) hide show
  1. package/lib/answerers/terminal/index.js +1 -1
  2. package/lib/answerers/terminal/index.js.map +1 -1
  3. package/lib/assertions/core/helpers.d.ts +2 -7
  4. package/lib/assertions/core/helpers.d.ts.map +1 -1
  5. package/lib/assertions/core/helpers.js +2 -6
  6. package/lib/assertions/core/helpers.js.map +1 -1
  7. package/lib/cli/program.d.ts.map +1 -1
  8. package/lib/cli/program.js +6 -2
  9. package/lib/cli/program.js.map +1 -1
  10. package/lib/cli/render/reporter.d.ts.map +1 -1
  11. package/lib/cli/render/reporter.js +16 -3
  12. package/lib/cli/render/reporter.js.map +1 -1
  13. package/lib/cli/run-options.d.ts +1 -0
  14. package/lib/cli/run-options.d.ts.map +1 -1
  15. package/lib/cli/run-options.js.map +1 -1
  16. package/lib/core/artifact-evidence.d.ts +8 -0
  17. package/lib/core/artifact-evidence.d.ts.map +1 -1
  18. package/lib/core/artifact-evidence.js +36 -4
  19. package/lib/core/artifact-evidence.js.map +1 -1
  20. package/lib/core/assertion.d.ts +12 -0
  21. package/lib/core/assertion.d.ts.map +1 -1
  22. package/lib/core/assertion.js +13 -0
  23. package/lib/core/assertion.js.map +1 -1
  24. package/lib/core/events.d.ts +2 -0
  25. package/lib/core/events.d.ts.map +1 -1
  26. package/lib/core/events.js +1 -0
  27. package/lib/core/events.js.map +1 -1
  28. package/lib/core/run.d.ts +1 -0
  29. package/lib/core/run.d.ts.map +1 -1
  30. package/lib/core/wire.d.ts +1 -0
  31. package/lib/core/wire.d.ts.map +1 -1
  32. package/lib/core/wire.js +5 -1
  33. package/lib/core/wire.js.map +1 -1
  34. package/lib/index.d.ts +0 -1
  35. package/lib/index.d.ts.map +1 -1
  36. package/lib/index.js +0 -1
  37. package/lib/index.js.map +1 -1
  38. package/lib/orchestrator/config.d.ts.map +1 -1
  39. package/lib/orchestrator/config.js +0 -2
  40. package/lib/orchestrator/config.js.map +1 -1
  41. package/lib/orchestrator/grader-experiment.d.ts +12 -0
  42. package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
  43. package/lib/orchestrator/grading.d.ts +2 -1
  44. package/lib/orchestrator/grading.d.ts.map +1 -1
  45. package/lib/orchestrator/grading.js +16 -4
  46. package/lib/orchestrator/grading.js.map +1 -1
  47. package/lib/orchestrator/orchestrator.d.ts +3 -0
  48. package/lib/orchestrator/orchestrator.d.ts.map +1 -1
  49. package/lib/orchestrator/orchestrator.js +3 -0
  50. package/lib/orchestrator/orchestrator.js.map +1 -1
  51. package/lib/orchestrator/regrade.d.ts +6 -0
  52. package/lib/orchestrator/regrade.d.ts.map +1 -1
  53. package/lib/orchestrator/run-execution.d.ts.map +1 -1
  54. package/lib/orchestrator/run-execution.js +2 -2
  55. package/lib/orchestrator/run-execution.js.map +1 -1
  56. package/package.json +1 -2
  57. package/skills/lux/SKILL.md +7 -3
  58. package/skills/lux/references/cli.md +25 -2
  59. package/skills/lux/references/eval-authoring.md +156 -4
  60. package/skills/lux-answerer/SKILL.md +1 -1
  61. package/src/answerers/terminal/index.ts +1 -1
  62. package/src/assertions/core/helpers.ts +3 -6
  63. package/src/cli/program.ts +9 -2
  64. package/src/cli/render/reporter.ts +18 -3
  65. package/src/cli/run-options.ts +1 -0
  66. package/src/core/artifact-evidence.ts +47 -4
  67. package/src/core/assertion.ts +15 -0
  68. package/src/core/events.ts +1 -0
  69. package/src/core/wire.ts +5 -1
  70. package/src/index.ts +0 -5
  71. package/src/orchestrator/config.ts +0 -2
  72. package/src/orchestrator/grading.ts +21 -2
  73. package/src/orchestrator/orchestrator.ts +6 -0
  74. package/src/orchestrator/run-execution.ts +2 -1
  75. package/lib/answerers/scripted/index.d.ts +0 -8
  76. package/lib/answerers/scripted/index.d.ts.map +0 -1
  77. package/lib/answerers/scripted/index.js +0 -163
  78. package/lib/answerers/scripted/index.js.map +0 -1
  79. package/src/answerers/scripted/index.ts +0 -186
@@ -128,6 +128,8 @@ export type RunRequest = {
128
128
  /** Root that relative `fixtures` dir paths resolve against. */
129
129
  fixturesRoot?: string | undefined;
130
130
  answererOverride?: string | undefined;
131
+ /** Skip built-in rubric judge assertions without changing subject execution. */
132
+ skipJudge?: boolean | undefined;
131
133
  startedAt?: Date | undefined;
132
134
  /** The `runSuite` invocation this case attempt belongs to. */
133
135
  suiteId?: string | undefined;
@@ -568,6 +570,7 @@ export class Orchestrator {
568
570
  assertions: assertionImplementations,
569
571
  validatedAssertionsHash,
570
572
  graderContext: this.harness,
573
+ skipJudge: request.skipJudge ?? false,
571
574
  });
572
575
  return await this.runWithPlugins(
573
576
  {
@@ -593,6 +596,7 @@ export class Orchestrator {
593
596
  assertions: assertionImplementations,
594
597
  lifecycleFixtures,
595
598
  validatedAssertionsHash,
599
+ skipJudge: request.skipJudge ?? false,
596
600
  }),
597
601
  fixtureValues: valuesForFixtures(lifecycleFixtures),
598
602
  driver,
@@ -617,6 +621,7 @@ export class Orchestrator {
617
621
  runsRoot: string;
618
622
  fixturesRoot?: string | undefined;
619
623
  answererOverride?: string | undefined;
624
+ skipJudge?: boolean | undefined;
620
625
  observer?: SuiteObserver | undefined;
621
626
  /** Aborts the cases in flight and stops the pool pulling new ones. */
622
627
  abortSignal?: AbortSignal | undefined;
@@ -788,6 +793,7 @@ export class Orchestrator {
788
793
  runsRoot: opts.runsRoot,
789
794
  fixturesRoot: opts.fixturesRoot,
790
795
  answererOverride: opts.answererOverride,
796
+ skipJudge: opts.skipJudge,
791
797
  observer: handle ?? observer,
792
798
  abortSignal: opts.abortSignal,
793
799
  suiteId: suite.manifest.suiteId,
@@ -560,7 +560,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
560
560
  const outcome = this.require(this.loopOutcome, "driver outcome");
561
561
  const agentEndedAt = new Date();
562
562
  const exitReason = agentExitReason(outcome, this.collection.error);
563
- const summary = outcome.kind === "done" ? outcome.done.summary : undefined;
563
+ const summary = outcome.kind === "done" ? outcome.done.summary : outcome.error.summary;
564
564
  const driverCost = costFromSummary(summary);
565
565
  const reportedModelAccounting = modelAccountingFromSummary(summary);
566
566
  const answererAccounting = readAnswererModelAccounting(this.args.answerer);
@@ -657,6 +657,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
657
657
  request.recordGradingAssertion,
658
658
  modelAttempts,
659
659
  this.args.validatedAssertionConfigs,
660
+ request.skipJudge ?? false,
660
661
  );
661
662
  }
662
663
 
@@ -1,8 +0,0 @@
1
- import { z } from "zod";
2
- import { type AnswererPlugin } from "../../core/index.js";
3
- export declare const ScriptedAnswererConfigSchema: z.ZodObject<{
4
- fixturePath: z.ZodString;
5
- }, z.core.$strict>;
6
- export type ScriptedAnswererConfig = z.infer<typeof ScriptedAnswererConfigSchema>;
7
- export declare const scriptedAnswerer: AnswererPlugin<ScriptedAnswererConfig>;
8
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/answerers/scripted/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAGL,KAAK,cAAc,EAIpB,MAAM,qBAAqB,CAAC;AA8B7B,eAAO,MAAM,4BAA4B;;kBAAiD,CAAC;AAE3F,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AA0IlF,eAAO,MAAM,gBAAgB,EAAE,cAAc,CAAC,sBAAsB,CAInE,CAAC"}
@@ -1,163 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { resolve } from "node:path";
3
- import { load as loadYaml } from "js-yaml";
4
- import { z } from "zod";
5
- import { formatZodIssues, InterruptKindSchema, } from "../../core/index.js";
6
- import { approveAnswer } from "../shared.js";
7
- const MatchSchema = z
8
- .object({
9
- kind: InterruptKindSchema.optional(),
10
- id: z.string().optional(),
11
- question_contains: z.string().optional(),
12
- question_matches: z.string().optional(),
13
- request_contains: z.string().optional(),
14
- })
15
- .strict();
16
- const AnsweredAskSchema = z.object({ answer: z.string() }).strict();
17
- const AnsweredApproveSchema = z
18
- .object({ approved: z.boolean(), reason: z.string().optional() })
19
- .strict();
20
- const AnsweredCustomSchema = z.object({ payload: z.unknown() }).strict();
21
- const RuleSchema = z.union([
22
- z.object({ match: MatchSchema, answer: z.string() }).strict(),
23
- z.object({ match: MatchSchema, approved: z.boolean(), reason: z.string().optional() }).strict(),
24
- z.object({ match: MatchSchema, payload: z.unknown() }).strict(),
25
- z.object({ default: AnsweredAskSchema }).strict(),
26
- z.object({ default: AnsweredApproveSchema }).strict(),
27
- z.object({ default: AnsweredCustomSchema }).strict(),
28
- ]);
29
- const FixtureSchema = z.array(RuleSchema);
30
- export const ScriptedAnswererConfigSchema = z.object({ fixturePath: z.string() }).strict();
31
- const ruleLabel = (path, index) => `rule ${index + 1} of ${path}`;
32
- const bodyKind = (rule) => {
33
- const body = "default" in rule ? rule.default : rule;
34
- if ("answer" in body)
35
- return "ask";
36
- if ("approved" in body)
37
- return "approve";
38
- return "custom";
39
- };
40
- const matchableKind = (m) => {
41
- if (m.kind)
42
- return m.kind;
43
- if (m.question_contains !== undefined || m.question_matches !== undefined)
44
- return "ask";
45
- if (m.request_contains !== undefined)
46
- return "approve";
47
- return undefined;
48
- };
49
- const compileQuestionPattern = (rule, path, index) => {
50
- if (!("match" in rule) || rule.match.question_matches === undefined)
51
- return undefined;
52
- try {
53
- return new RegExp(rule.match.question_matches);
54
- }
55
- catch (err) {
56
- throw new Error(`scripted: invalid question_matches in ${ruleLabel(path, index)}: ${err instanceof Error ? err.message : String(err)}`);
57
- }
58
- };
59
- // The rule's match fields and answer body are independently valid shapes, so
60
- // their interrupt kinds still need a semantic cross-check.
61
- const prepareRule = (rule, path, index) => {
62
- if ("match" in rule) {
63
- const wanted = matchableKind(rule.match);
64
- const answered = bodyKind(rule);
65
- if (wanted !== undefined && wanted !== answered) {
66
- throw new Error(`scripted: ${ruleLabel(path, index)} matches ${wanted} interrupts but its body answers ${answered}`);
67
- }
68
- }
69
- return { rule, questionPattern: compileQuestionPattern(rule, path, index) };
70
- };
71
- const matches = ({ rule, questionPattern }, interrupt) => {
72
- if (!("match" in rule))
73
- return false;
74
- const m = rule.match;
75
- if (m.kind && m.kind !== interrupt.kind)
76
- return false;
77
- if (m.id && m.id !== interrupt.id)
78
- return false;
79
- if (m.question_contains) {
80
- if (interrupt.kind !== "ask")
81
- return false;
82
- if (!interrupt.question.includes(m.question_contains))
83
- return false;
84
- }
85
- if (questionPattern) {
86
- if (interrupt.kind !== "ask")
87
- return false;
88
- if (!questionPattern.test(interrupt.question))
89
- return false;
90
- }
91
- if (m.request_contains) {
92
- if (interrupt.kind !== "approve")
93
- return false;
94
- if (!interrupt.request.includes(m.request_contains))
95
- return false;
96
- }
97
- return true;
98
- };
99
- // A fixture may carry one default per interrupt kind; the shape of the
100
- // default's body is what says which kind it can answer.
101
- const defaultAnswersKind = (rule, kind) => {
102
- if (!("default" in rule))
103
- return false;
104
- const body = rule.default;
105
- if (kind === "ask")
106
- return "answer" in body;
107
- if (kind === "approve")
108
- return "approved" in body;
109
- return "payload" in body;
110
- };
111
- const buildAnswer = (rule, interrupt) => {
112
- const body = "default" in rule ? rule.default : rule;
113
- if (interrupt.kind === "ask") {
114
- if (!("answer" in body)) {
115
- throw new Error(`scripted: rule for ask interrupt missing 'answer' (id=${interrupt.id})`);
116
- }
117
- return { kind: "ask", id: interrupt.id, text: body.answer };
118
- }
119
- if (interrupt.kind === "approve") {
120
- if (!("approved" in body)) {
121
- throw new Error(`scripted: rule for approve interrupt missing 'approved' (id=${interrupt.id})`);
122
- }
123
- return approveAnswer(interrupt.id, body.approved, body.reason);
124
- }
125
- if (!("payload" in body)) {
126
- throw new Error(`scripted: rule for custom interrupt missing 'payload' (id=${interrupt.id})`);
127
- }
128
- return { kind: "custom", id: interrupt.id, payload: body.payload };
129
- };
130
- class ScriptedAnswererImpl {
131
- rules = [];
132
- config;
133
- constructor(config) {
134
- this.config = config;
135
- }
136
- async prepare() {
137
- const path = resolve(this.config.fixturePath);
138
- const raw = loadYaml(await readFile(path, "utf-8"));
139
- const parsed = FixtureSchema.safeParse(raw);
140
- if (!parsed.success) {
141
- throw new Error(`scripted: invalid fixture at ${path}: ${formatZodIssues(parsed.error)}`);
142
- }
143
- this.rules = parsed.data.map((rule, index) => prepareRule(rule, path, index));
144
- }
145
- async answer(interrupt) {
146
- const matched = this.rules.find((r) => matches(r, interrupt));
147
- if (matched) {
148
- return buildAnswer(matched.rule, interrupt);
149
- }
150
- const fallback = this.rules.find((r) => defaultAnswersKind(r.rule, interrupt.kind));
151
- if (fallback) {
152
- return buildAnswer(fallback.rule, interrupt);
153
- }
154
- throw new Error(`scripted: no rule matched interrupt (kind=${interrupt.kind}, id=${interrupt.id}) and no default provided`);
155
- }
156
- async close() { }
157
- }
158
- export const scriptedAnswerer = {
159
- name: "scripted",
160
- configSchema: ScriptedAnswererConfigSchema,
161
- create: (config) => new ScriptedAnswererImpl(config),
162
- };
163
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/answerers/scripted/index.ts"],"names":[],"mappings":"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"}
@@ -1,186 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { resolve } from "node:path";
3
- import { load as loadYaml } from "js-yaml";
4
- import { z } from "zod";
5
- import {
6
- type Answer,
7
- type Answerer,
8
- type AnswererPlugin,
9
- formatZodIssues,
10
- type Interrupt,
11
- InterruptKindSchema,
12
- } from "../../core/index.js";
13
- import { approveAnswer } from "../shared.js";
14
-
15
- const MatchSchema = z
16
- .object({
17
- kind: InterruptKindSchema.optional(),
18
- id: z.string().optional(),
19
- question_contains: z.string().optional(),
20
- question_matches: z.string().optional(),
21
- request_contains: z.string().optional(),
22
- })
23
- .strict();
24
-
25
- const AnsweredAskSchema = z.object({ answer: z.string() }).strict();
26
- const AnsweredApproveSchema = z
27
- .object({ approved: z.boolean(), reason: z.string().optional() })
28
- .strict();
29
- const AnsweredCustomSchema = z.object({ payload: z.unknown() }).strict();
30
-
31
- const RuleSchema = z.union([
32
- z.object({ match: MatchSchema, answer: z.string() }).strict(),
33
- z.object({ match: MatchSchema, approved: z.boolean(), reason: z.string().optional() }).strict(),
34
- z.object({ match: MatchSchema, payload: z.unknown() }).strict(),
35
- z.object({ default: AnsweredAskSchema }).strict(),
36
- z.object({ default: AnsweredApproveSchema }).strict(),
37
- z.object({ default: AnsweredCustomSchema }).strict(),
38
- ]);
39
-
40
- const FixtureSchema = z.array(RuleSchema);
41
-
42
- export const ScriptedAnswererConfigSchema = z.object({ fixturePath: z.string() }).strict();
43
-
44
- export type ScriptedAnswererConfig = z.infer<typeof ScriptedAnswererConfigSchema>;
45
-
46
- type Rule = z.infer<typeof RuleSchema>;
47
-
48
- /** A rule vetted at load time: kind/body cross-checked, regex pre-compiled. */
49
- type PreparedRule = { rule: Rule; questionPattern: RegExp | undefined };
50
-
51
- const ruleLabel = (path: string, index: number): string => `rule ${index + 1} of ${path}`;
52
-
53
- const bodyKind = (rule: Rule): Interrupt["kind"] => {
54
- const body = "default" in rule ? rule.default : rule;
55
- if ("answer" in body) return "ask";
56
- if ("approved" in body) return "approve";
57
- return "custom";
58
- };
59
-
60
- const matchableKind = (m: z.infer<typeof MatchSchema>): Interrupt["kind"] | undefined => {
61
- if (m.kind) return m.kind;
62
- if (m.question_contains !== undefined || m.question_matches !== undefined) return "ask";
63
- if (m.request_contains !== undefined) return "approve";
64
- return undefined;
65
- };
66
-
67
- const compileQuestionPattern = (rule: Rule, path: string, index: number): RegExp | undefined => {
68
- if (!("match" in rule) || rule.match.question_matches === undefined) return undefined;
69
- try {
70
- return new RegExp(rule.match.question_matches);
71
- } catch (err) {
72
- throw new Error(
73
- `scripted: invalid question_matches in ${ruleLabel(path, index)}: ${
74
- err instanceof Error ? err.message : String(err)
75
- }`,
76
- );
77
- }
78
- };
79
-
80
- // The rule's match fields and answer body are independently valid shapes, so
81
- // their interrupt kinds still need a semantic cross-check.
82
- const prepareRule = (rule: Rule, path: string, index: number): PreparedRule => {
83
- if ("match" in rule) {
84
- const wanted = matchableKind(rule.match);
85
- const answered = bodyKind(rule);
86
- if (wanted !== undefined && wanted !== answered) {
87
- throw new Error(
88
- `scripted: ${ruleLabel(path, index)} matches ${wanted} interrupts but its body answers ${answered}`,
89
- );
90
- }
91
- }
92
- return { rule, questionPattern: compileQuestionPattern(rule, path, index) };
93
- };
94
-
95
- const matches = ({ rule, questionPattern }: PreparedRule, interrupt: Interrupt): boolean => {
96
- if (!("match" in rule)) return false;
97
- const m = rule.match;
98
- if (m.kind && m.kind !== interrupt.kind) return false;
99
- if (m.id && m.id !== interrupt.id) return false;
100
- if (m.question_contains) {
101
- if (interrupt.kind !== "ask") return false;
102
- if (!interrupt.question.includes(m.question_contains)) return false;
103
- }
104
- if (questionPattern) {
105
- if (interrupt.kind !== "ask") return false;
106
- if (!questionPattern.test(interrupt.question)) return false;
107
- }
108
- if (m.request_contains) {
109
- if (interrupt.kind !== "approve") return false;
110
- if (!interrupt.request.includes(m.request_contains)) return false;
111
- }
112
- return true;
113
- };
114
-
115
- // A fixture may carry one default per interrupt kind; the shape of the
116
- // default's body is what says which kind it can answer.
117
- const defaultAnswersKind = (rule: Rule, kind: Interrupt["kind"]): boolean => {
118
- if (!("default" in rule)) return false;
119
- const body = rule.default;
120
- if (kind === "ask") return "answer" in body;
121
- if (kind === "approve") return "approved" in body;
122
- return "payload" in body;
123
- };
124
-
125
- const buildAnswer = (rule: Rule, interrupt: Interrupt): Answer => {
126
- const body = "default" in rule ? rule.default : rule;
127
- if (interrupt.kind === "ask") {
128
- if (!("answer" in body)) {
129
- throw new Error(`scripted: rule for ask interrupt missing 'answer' (id=${interrupt.id})`);
130
- }
131
- return { kind: "ask", id: interrupt.id, text: body.answer };
132
- }
133
- if (interrupt.kind === "approve") {
134
- if (!("approved" in body)) {
135
- throw new Error(
136
- `scripted: rule for approve interrupt missing 'approved' (id=${interrupt.id})`,
137
- );
138
- }
139
- return approveAnswer(interrupt.id, body.approved, body.reason);
140
- }
141
- if (!("payload" in body)) {
142
- throw new Error(`scripted: rule for custom interrupt missing 'payload' (id=${interrupt.id})`);
143
- }
144
- return { kind: "custom", id: interrupt.id, payload: body.payload };
145
- };
146
-
147
- class ScriptedAnswererImpl implements Answerer {
148
- private rules: PreparedRule[] = [];
149
- private readonly config: ScriptedAnswererConfig;
150
-
151
- constructor(config: ScriptedAnswererConfig) {
152
- this.config = config;
153
- }
154
-
155
- async prepare(): Promise<void> {
156
- const path = resolve(this.config.fixturePath);
157
- const raw = loadYaml(await readFile(path, "utf-8"));
158
- const parsed = FixtureSchema.safeParse(raw);
159
- if (!parsed.success) {
160
- throw new Error(`scripted: invalid fixture at ${path}: ${formatZodIssues(parsed.error)}`);
161
- }
162
- this.rules = parsed.data.map((rule, index) => prepareRule(rule, path, index));
163
- }
164
-
165
- async answer(interrupt: Interrupt): Promise<Answer> {
166
- const matched = this.rules.find((r) => matches(r, interrupt));
167
- if (matched) {
168
- return buildAnswer(matched.rule, interrupt);
169
- }
170
- const fallback = this.rules.find((r) => defaultAnswersKind(r.rule, interrupt.kind));
171
- if (fallback) {
172
- return buildAnswer(fallback.rule, interrupt);
173
- }
174
- throw new Error(
175
- `scripted: no rule matched interrupt (kind=${interrupt.kind}, id=${interrupt.id}) and no default provided`,
176
- );
177
- }
178
-
179
- async close(): Promise<void> {}
180
- }
181
-
182
- export const scriptedAnswerer: AnswererPlugin<ScriptedAnswererConfig> = {
183
- name: "scripted",
184
- configSchema: ScriptedAnswererConfigSchema,
185
- create: (config) => new ScriptedAnswererImpl(config),
186
- };