@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.
- package/lib/answerers/terminal/index.js +1 -1
- package/lib/answerers/terminal/index.js.map +1 -1
- package/lib/assertions/core/helpers.d.ts +2 -7
- package/lib/assertions/core/helpers.d.ts.map +1 -1
- package/lib/assertions/core/helpers.js +2 -6
- package/lib/assertions/core/helpers.js.map +1 -1
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +6 -2
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/render/reporter.d.ts.map +1 -1
- package/lib/cli/render/reporter.js +16 -3
- package/lib/cli/render/reporter.js.map +1 -1
- package/lib/cli/run-options.d.ts +1 -0
- package/lib/cli/run-options.d.ts.map +1 -1
- package/lib/cli/run-options.js.map +1 -1
- package/lib/core/artifact-evidence.d.ts +8 -0
- package/lib/core/artifact-evidence.d.ts.map +1 -1
- package/lib/core/artifact-evidence.js +36 -4
- package/lib/core/artifact-evidence.js.map +1 -1
- package/lib/core/assertion.d.ts +12 -0
- package/lib/core/assertion.d.ts.map +1 -1
- package/lib/core/assertion.js +13 -0
- package/lib/core/assertion.js.map +1 -1
- package/lib/core/events.d.ts +2 -0
- package/lib/core/events.d.ts.map +1 -1
- package/lib/core/events.js +1 -0
- package/lib/core/events.js.map +1 -1
- package/lib/core/run.d.ts +1 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/wire.d.ts +1 -0
- package/lib/core/wire.d.ts.map +1 -1
- package/lib/core/wire.js +5 -1
- package/lib/core/wire.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +0 -2
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/grader-experiment.d.ts +12 -0
- package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
- package/lib/orchestrator/grading.d.ts +2 -1
- package/lib/orchestrator/grading.d.ts.map +1 -1
- package/lib/orchestrator/grading.js +16 -4
- package/lib/orchestrator/grading.js.map +1 -1
- package/lib/orchestrator/orchestrator.d.ts +3 -0
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +3 -0
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/regrade.d.ts +6 -0
- package/lib/orchestrator/regrade.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +2 -2
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/package.json +1 -2
- package/skills/lux/SKILL.md +7 -3
- package/skills/lux/references/cli.md +25 -2
- package/skills/lux/references/eval-authoring.md +156 -4
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/answerers/terminal/index.ts +1 -1
- package/src/assertions/core/helpers.ts +3 -6
- package/src/cli/program.ts +9 -2
- package/src/cli/render/reporter.ts +18 -3
- package/src/cli/run-options.ts +1 -0
- package/src/core/artifact-evidence.ts +47 -4
- package/src/core/assertion.ts +15 -0
- package/src/core/events.ts +1 -0
- package/src/core/wire.ts +5 -1
- package/src/index.ts +0 -5
- package/src/orchestrator/config.ts +0 -2
- package/src/orchestrator/grading.ts +21 -2
- package/src/orchestrator/orchestrator.ts +6 -0
- package/src/orchestrator/run-execution.ts +2 -1
- package/lib/answerers/scripted/index.d.ts +0 -8
- package/lib/answerers/scripted/index.d.ts.map +0 -1
- package/lib/answerers/scripted/index.js +0 -163
- package/lib/answerers/scripted/index.js.map +0 -1
- 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 :
|
|
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
|
-
};
|