@memberjunction/testing-engine 6.1.0-edge.5 → 6.1.0-edge.7

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 (68) hide show
  1. package/dist/drivers/AgentPromptComposer.d.ts +50 -0
  2. package/dist/drivers/AgentPromptComposer.d.ts.map +1 -0
  3. package/dist/drivers/AgentPromptComposer.js +60 -0
  4. package/dist/drivers/AgentPromptComposer.js.map +1 -0
  5. package/dist/drivers/PinnedVendorPromptRunner.d.ts +30 -0
  6. package/dist/drivers/PinnedVendorPromptRunner.d.ts.map +1 -0
  7. package/dist/drivers/PinnedVendorPromptRunner.js +30 -0
  8. package/dist/drivers/PinnedVendorPromptRunner.js.map +1 -0
  9. package/dist/drivers/PromptEvalDriver.d.ts +158 -0
  10. package/dist/drivers/PromptEvalDriver.d.ts.map +1 -0
  11. package/dist/drivers/PromptEvalDriver.js +365 -0
  12. package/dist/drivers/PromptEvalDriver.js.map +1 -0
  13. package/dist/drivers/index.d.ts +1 -0
  14. package/dist/drivers/index.d.ts.map +1 -1
  15. package/dist/drivers/index.js +1 -0
  16. package/dist/drivers/index.js.map +1 -1
  17. package/dist/engine/TestEngine.d.ts.map +1 -1
  18. package/dist/engine/TestEngine.js +5 -0
  19. package/dist/engine/TestEngine.js.map +1 -1
  20. package/dist/eval/corpus.d.ts +91 -0
  21. package/dist/eval/corpus.d.ts.map +1 -0
  22. package/dist/eval/corpus.js +141 -0
  23. package/dist/eval/corpus.js.map +1 -0
  24. package/dist/eval/decision.d.ts +156 -0
  25. package/dist/eval/decision.d.ts.map +1 -0
  26. package/dist/eval/decision.js +257 -0
  27. package/dist/eval/decision.js.map +1 -0
  28. package/dist/eval/expectation.d.ts +76 -0
  29. package/dist/eval/expectation.d.ts.map +1 -0
  30. package/dist/eval/expectation.js +160 -0
  31. package/dist/eval/expectation.js.map +1 -0
  32. package/dist/eval/history.d.ts +17 -0
  33. package/dist/eval/history.d.ts.map +1 -0
  34. package/dist/eval/history.js +19 -0
  35. package/dist/eval/history.js.map +1 -0
  36. package/dist/eval/index.d.ts +15 -0
  37. package/dist/eval/index.d.ts.map +1 -0
  38. package/dist/eval/index.js +15 -0
  39. package/dist/eval/index.js.map +1 -0
  40. package/dist/eval/matchers.d.ts +88 -0
  41. package/dist/eval/matchers.d.ts.map +1 -0
  42. package/dist/eval/matchers.js +119 -0
  43. package/dist/eval/matchers.js.map +1 -0
  44. package/dist/eval/subAgentTrace.d.ts +52 -0
  45. package/dist/eval/subAgentTrace.d.ts.map +1 -0
  46. package/dist/eval/subAgentTrace.js +61 -0
  47. package/dist/eval/subAgentTrace.js.map +1 -0
  48. package/dist/eval/wellFormed.d.ts +41 -0
  49. package/dist/eval/wellFormed.d.ts.map +1 -0
  50. package/dist/eval/wellFormed.js +83 -0
  51. package/dist/eval/wellFormed.js.map +1 -0
  52. package/dist/index.d.ts +5 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +5 -0
  55. package/dist/index.js.map +1 -1
  56. package/dist/oracles/AgentDecisionOracle.d.ts +54 -0
  57. package/dist/oracles/AgentDecisionOracle.d.ts.map +1 -0
  58. package/dist/oracles/AgentDecisionOracle.js +107 -0
  59. package/dist/oracles/AgentDecisionOracle.js.map +1 -0
  60. package/dist/oracles/TraceSubAgentValidatorOracle.d.ts +44 -0
  61. package/dist/oracles/TraceSubAgentValidatorOracle.d.ts.map +1 -0
  62. package/dist/oracles/TraceSubAgentValidatorOracle.js +105 -0
  63. package/dist/oracles/TraceSubAgentValidatorOracle.js.map +1 -0
  64. package/dist/oracles/index.d.ts +2 -0
  65. package/dist/oracles/index.d.ts.map +1 -1
  66. package/dist/oracles/index.js +2 -0
  67. package/dist/oracles/index.js.map +1 -1
  68. package/package.json +10 -10
@@ -0,0 +1,91 @@
1
+ /**
2
+ * corpus.ts — the golden-file case format, its loader, and `anyOf`.
3
+ *
4
+ * Corpus cases are golden JSON files (test plan §4.2) rather than rows in a table, because inputs
5
+ * and expectations have to be **reviewable and diffable**: a change to what a case expects is a
6
+ * change to the yardstick, and that must show up in a code review rather than in a metadata push.
7
+ *
8
+ * The on-disk format is the plan's, verbatim — `{ "matcher": "regex", "pattern": … }` with an
9
+ * `optional` flag — while the evaluator's internal {@link ParamMatcher} is a discriminated union.
10
+ * This module is the translation, and it exists precisely so the two can differ: the file format
11
+ * is optimized for a human writing and reviewing it, the internal type for exhaustive checking.
12
+ *
13
+ * Loading VALIDATES. An unknown matcher name, a missing pattern, an unknown expectation kind — all
14
+ * throw here, at load time, naming the case. The alternative is a matcher that silently never
15
+ * matches, which would look exactly like a model failure in the results.
16
+ *
17
+ * FRAMEWORK-FREE (test plan §2.1) — imports nothing from the testing engine.
18
+ */
19
+ import type { DecisionKind, ObservedDecision } from './decision.js';
20
+ import type { ParamMatcher } from './matchers.js';
21
+ import type { ChatMessage } from '@memberjunction/ai';
22
+ import { type DecisionEvaluation, type DecisionExpectation } from './expectation.js';
23
+ /** A parameter matcher as written in a golden file. */
24
+ export interface CorpusParamMatcher {
25
+ matcher: 'exact' | 'regex' | 'oneOf' | 'contains' | 'nonEmpty' | 'numericTolerance' | 'typeOf' | 'absent';
26
+ value?: unknown;
27
+ values?: unknown[];
28
+ pattern?: string;
29
+ flags?: string;
30
+ /** For `numericTolerance`: the permitted absolute deviation from `value`. */
31
+ tolerance?: number;
32
+ /** For `typeOf`. */
33
+ type?: 'string' | 'number' | 'boolean' | 'object' | 'array';
34
+ /** Absent is acceptable; a present value is still checked. */
35
+ optional?: boolean;
36
+ }
37
+ /** One expected action in a golden file. */
38
+ export interface CorpusExpectedAction {
39
+ name: string;
40
+ params?: Record<string, CorpusParamMatcher>;
41
+ }
42
+ /** One expectation in a golden file. `anyOf` nests alternatives. */
43
+ export interface CorpusExpectation {
44
+ kind: DecisionKind | 'anyOf';
45
+ actions?: CorpusExpectedAction[];
46
+ subAgents?: string[];
47
+ message?: CorpusParamMatcher;
48
+ forbiddenActions?: string[];
49
+ ordered?: boolean;
50
+ allowAdditionalActions?: boolean;
51
+ /** Matchers keyed by dotted path into the payload change request, e.g. `newElements.findings`. */
52
+ payload?: Record<string, CorpusParamMatcher>;
53
+ /** Dotted paths the payload change must not touch (payload scoping). */
54
+ forbiddenPayloadPaths?: string[];
55
+ /** Alternatives, when `kind` is `anyOf`. The best-scoring branch wins. */
56
+ anyOf?: CorpusExpectation[];
57
+ }
58
+ /** A corpus case, as it appears on disk. */
59
+ export interface CorpusCase {
60
+ id: string;
61
+ /** The agent whose prompt this case exercises. */
62
+ agent: string;
63
+ description: string;
64
+ input: {
65
+ payload?: Record<string, unknown>;
66
+ /** Prior turns. Multi-turn cases use the neutral tool form: assistant `toolCalls` + a `tool` turn of `tool_result` blocks; `encodeHistoryForArm` renders it per arm. */
67
+ conversationMessages?: ChatMessage[];
68
+ templateData?: Record<string, unknown>;
69
+ /** Forces the model's tool choice on native cells — 'none' for forced-control-flow cases, which production runs at 'none' on the final iteration. */
70
+ toolChoice?: 'auto' | 'none' | 'required';
71
+ };
72
+ expect: CorpusExpectation;
73
+ /** Free-form tags for slicing the corpus — category, provider focus, stage. */
74
+ tags?: string[];
75
+ }
76
+ /** Translates one on-disk matcher into the evaluator's union, rejecting anything malformed. */
77
+ export declare function toParamMatcher(caseId: string, param: string, spec: CorpusParamMatcher): ParamMatcher;
78
+ /** Translates a golden-file expectation into the evaluator's {@link DecisionExpectation}. */
79
+ export declare function toDecisionExpectation(caseId: string, expect: CorpusExpectation): DecisionExpectation;
80
+ /** Validates a parsed golden file and returns it typed. Throws with the case id on any problem. */
81
+ export declare function parseCorpusCase(raw: unknown): CorpusCase;
82
+ /**
83
+ * Scores an observation against a case's expectation, resolving `anyOf`.
84
+ *
85
+ * `anyOf` takes the **best-scoring** branch rather than the first passing one. Both give the same
86
+ * pass/fail, but the score differs when every branch fails, and reporting the closest miss is what
87
+ * makes a failure diagnosable: "expected chat OR action, got unparseable" is less useful than the
88
+ * branch that came nearest.
89
+ */
90
+ export declare function evaluateCorpusExpectation(caseId: string, expect: CorpusExpectation, observed: ObservedDecision): DecisionEvaluation;
91
+ //# sourceMappingURL=corpus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../../src/eval/corpus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ import { evaluateDecision } from './expectation.js';
2
+ function fail(caseId, message) {
3
+ throw new Error(`Corpus case '${caseId}': ${message}`);
4
+ }
5
+ /** Translates one on-disk matcher into the evaluator's union, rejecting anything malformed. */
6
+ export function toParamMatcher(caseId, param, spec) {
7
+ switch (spec.matcher) {
8
+ case 'exact':
9
+ return { kind: 'exact', value: spec.value };
10
+ case 'nonEmpty':
11
+ // A payload path may hold an array or object, not only a string.
12
+ return { kind: 'nonEmpty' };
13
+ case 'contains':
14
+ if (typeof spec.value !== 'string') {
15
+ fail(caseId, `param '${param}': 'contains' requires a string 'value'`);
16
+ }
17
+ return { kind: 'containsIgnoreCase', value: spec.value };
18
+ case 'oneOf':
19
+ if (!Array.isArray(spec.values) || spec.values.length === 0) {
20
+ fail(caseId, `param '${param}': 'oneOf' requires a non-empty 'values' array`);
21
+ }
22
+ return { kind: 'oneOf', values: spec.values };
23
+ case 'regex':
24
+ if (typeof spec.pattern !== 'string') {
25
+ fail(caseId, `param '${param}': 'regex' requires a 'pattern'`);
26
+ }
27
+ // Compile now so a bad pattern fails at load, not silently at scoring time.
28
+ try {
29
+ new RegExp(spec.pattern, spec.flags);
30
+ }
31
+ catch (error) {
32
+ fail(caseId, `param '${param}': invalid regex — ${error.message}`);
33
+ }
34
+ return { kind: 'regex', pattern: spec.pattern, flags: spec.flags };
35
+ case 'numericTolerance': {
36
+ if (typeof spec.value !== 'number' || typeof spec.tolerance !== 'number') {
37
+ fail(caseId, `param '${param}': 'numericTolerance' requires numeric 'value' and 'tolerance'`);
38
+ }
39
+ return { kind: 'numberRange', min: spec.value - spec.tolerance, max: spec.value + spec.tolerance };
40
+ }
41
+ case 'typeOf':
42
+ if (!spec.type) {
43
+ fail(caseId, `param '${param}': 'typeOf' requires a 'type'`);
44
+ }
45
+ return { kind: 'typeOf', type: spec.type };
46
+ case 'absent':
47
+ return { kind: 'absent' };
48
+ default:
49
+ fail(caseId, `param '${param}': unknown matcher '${String(spec.matcher)}'`);
50
+ }
51
+ }
52
+ const DECISION_KINDS = ['action', 'subAgent', 'chat', 'taskComplete', 'payloadChange', 'other', 'unparseable', 'empty'];
53
+ function toParamExpectations(caseId, params) {
54
+ return Object.entries(params ?? {}).map(([param, spec]) => ({
55
+ param,
56
+ matcher: toParamMatcher(caseId, param, spec),
57
+ optional: spec.optional === true
58
+ }));
59
+ }
60
+ function toExpectedActions(caseId, actions) {
61
+ return actions?.map((action) => {
62
+ if (!action.name) {
63
+ fail(caseId, 'every expected action needs a name');
64
+ }
65
+ return { name: action.name, params: toParamExpectations(caseId, action.params) };
66
+ });
67
+ }
68
+ /** Translates a golden-file expectation into the evaluator's {@link DecisionExpectation}. */
69
+ export function toDecisionExpectation(caseId, expect) {
70
+ if (!DECISION_KINDS.includes(expect.kind)) {
71
+ fail(caseId, `unknown expectation kind '${expect.kind}'`);
72
+ }
73
+ if (expect.kind === 'action' && !expect.actions?.length) {
74
+ fail(caseId, "kind 'action' requires at least one expected action");
75
+ }
76
+ return {
77
+ kind: expect.kind,
78
+ actions: toExpectedActions(caseId, expect.actions),
79
+ subAgents: expect.subAgents,
80
+ message: expect.message ? toParamMatcher(caseId, 'message', expect.message) : undefined,
81
+ forbiddenActions: expect.forbiddenActions,
82
+ ordered: expect.ordered,
83
+ allowAdditionalActions: expect.allowAdditionalActions,
84
+ payload: expect.payload ? toParamExpectations(caseId, expect.payload) : undefined,
85
+ forbiddenPayloadPaths: expect.forbiddenPayloadPaths
86
+ };
87
+ }
88
+ /** Validates a parsed golden file and returns it typed. Throws with the case id on any problem. */
89
+ export function parseCorpusCase(raw) {
90
+ const record = raw;
91
+ if (!record || typeof record !== 'object') {
92
+ throw new Error('Corpus case must be a JSON object');
93
+ }
94
+ const id = record.id;
95
+ if (typeof id !== 'string' || id.length === 0) {
96
+ throw new Error("Corpus case must have a non-empty 'id'");
97
+ }
98
+ if (typeof record.agent !== 'string' || record.agent.length === 0) {
99
+ fail(id, "must name the 'agent' whose prompt it exercises");
100
+ }
101
+ if (!record.expect) {
102
+ fail(id, "must declare an 'expect'");
103
+ }
104
+ const toolChoice = record.input?.toolChoice;
105
+ if (toolChoice !== undefined && !['auto', 'none', 'required'].includes(String(toolChoice))) {
106
+ fail(id, `input.toolChoice must be 'auto' | 'none' | 'required', got '${String(toolChoice)}'`);
107
+ }
108
+ // Compile every matcher now: a corpus that loads is a corpus that can score.
109
+ validateExpectation(id, record.expect);
110
+ return { ...record, id, agent: record.agent, description: record.description ?? '', input: record.input ?? {}, expect: record.expect };
111
+ }
112
+ function validateExpectation(caseId, expect) {
113
+ if (expect.kind === 'anyOf') {
114
+ if (!expect.anyOf?.length) {
115
+ fail(caseId, "kind 'anyOf' requires a non-empty 'anyOf' array");
116
+ }
117
+ expect.anyOf.forEach((branch) => validateExpectation(caseId, branch));
118
+ return;
119
+ }
120
+ toDecisionExpectation(caseId, expect);
121
+ }
122
+ /**
123
+ * Scores an observation against a case's expectation, resolving `anyOf`.
124
+ *
125
+ * `anyOf` takes the **best-scoring** branch rather than the first passing one. Both give the same
126
+ * pass/fail, but the score differs when every branch fails, and reporting the closest miss is what
127
+ * makes a failure diagnosable: "expected chat OR action, got unparseable" is less useful than the
128
+ * branch that came nearest.
129
+ */
130
+ export function evaluateCorpusExpectation(caseId, expect, observed) {
131
+ if (expect.kind !== 'anyOf') {
132
+ return evaluateDecision(toDecisionExpectation(caseId, expect), observed);
133
+ }
134
+ const branches = (expect.anyOf ?? []).map((branch) => evaluateCorpusExpectation(caseId, branch, observed));
135
+ const passing = branches.find((b) => b.passed);
136
+ if (passing) {
137
+ return passing;
138
+ }
139
+ return branches.reduce((best, current) => (current.score > best.score ? current : best), branches[0]);
140
+ }
141
+ //# sourceMappingURL=corpus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"corpus.js","sourceRoot":"","sources":["../../src/eval/corpus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * decision.ts — normalizing what an agent DECIDED, independently of how it said so.
3
+ *
4
+ * This is the load-bearing idea of the whole eval harness (test plan §1). A corpus case states the
5
+ * decision it expects — "invoke `Run Ad-hoc Query` with these params" — and never how the wire
6
+ * encodes it. Two encodings exist today and a third is coming:
7
+ *
8
+ * envelope — a `LoopAgentResponse` JSON object with `nextStep.actions[]`
9
+ * native — provider tool calls on the assistant turn
10
+ *
11
+ * Both collapse here into one {@link ObservedDecision} before anything is scored. That is what
12
+ * makes a baseline run and a native run *directly comparable on identical cases*:
13
+ * the only thing that changed between them is the model's behavior, not the yardstick.
14
+ *
15
+ * FRAMEWORK-FREE (test plan §2.1). Nothing in `src/eval/` imports from the testing engine — not
16
+ * `IOracle`, not `TestEngine`, not the driver types. The oracles in `src/oracles/` are thin
17
+ * wrappers over these functions, and a bespoke runner could call them directly. That rule exists so
18
+ * the documented fallback (§2.1, option B) stays cheap; it also happens to make every line here
19
+ * unit-testable without a database.
20
+ */
21
+ /**
22
+ * What kind of decision the agent made, in the vocabulary the corpus uses.
23
+ *
24
+ * These are deliberately the *decision* kinds, not the `nextStep.type` values — `taskComplete` is a
25
+ * decision but not a step type, and `Retry`/`ForEach`/`While` are control flow the corpus does not
26
+ * assert on. Anything the normalizer cannot place lands in `other`, which is a legitimate
27
+ * observation rather than an error.
28
+ */
29
+ export type DecisionKind = 'action' | 'subAgent' | 'chat' | 'taskComplete' | 'payloadChange' | 'other'
30
+ /** Output existed but could not be read as a decision — the malformed class. */
31
+ | 'unparseable'
32
+ /** No usable output at all (empty content, no tool calls). */
33
+ | 'empty';
34
+ /** How the decision arrived on the wire. Recorded so a comparison can attribute a delta to it. */
35
+ export type DecisionEncoding = 'envelope' | 'native' | 'text' | 'none';
36
+ /** What calling a control-flow tool decides. Sub-agent entries carry the agent's name. */
37
+ export type ControlToolRole = {
38
+ kind: 'subAgent';
39
+ name: string;
40
+ } | {
41
+ kind: 'payloadChange';
42
+ } | {
43
+ kind: 'chat';
44
+ };
45
+ /** One action invocation, normalized across both encodings. */
46
+ export interface ObservedAction {
47
+ name: string;
48
+ params: Record<string, unknown>;
49
+ }
50
+ /** One sub-agent dispatch. */
51
+ export interface ObservedSubAgent {
52
+ name: string;
53
+ message?: string;
54
+ }
55
+ /** The normalized decision: everything a corpus case is allowed to assert against. */
56
+ export interface ObservedDecision {
57
+ kind: DecisionKind;
58
+ encoding: DecisionEncoding;
59
+ actions: ObservedAction[];
60
+ subAgents: ObservedSubAgent[];
61
+ /** The user-facing message, when the agent produced one. */
62
+ message?: string;
63
+ /** True when the envelope declared the task finished. */
64
+ taskComplete: boolean;
65
+ /** Present when the turn carried a payload change request. */
66
+ payloadChange?: Record<string, unknown>;
67
+ /** Whether an envelope was expected and successfully parsed. `null` when none was expected. */
68
+ envelopeParsed: boolean | null;
69
+ /**
70
+ * The raw `nextStep.type` exactly as the model wrote it, before any normalization.
71
+ *
72
+ * Kept because the DECISION and the VALIDITY are different questions. `classifyEnvelope` is
73
+ * lenient by design — it infers a kind from the payload when the type is missing or odd, which
74
+ * is what makes the decision comparable across encodings. But the runtime is not lenient: it
75
+ * switches case-sensitively on this string and sends anything unrecognized to a forced Retry.
76
+ * A response can therefore be a perfectly good decision and still be malformed, and
77
+ * {@link evaluateWellFormed} needs the raw value to say so.
78
+ */
79
+ stepType?: string | null;
80
+ /** Why the decision is `unparseable` / `empty`, for the oracle's message. */
81
+ diagnostic?: string;
82
+ /**
83
+ * Native turns only: the model ALSO returned a parseable Loop envelope alongside its tool calls
84
+ * The tool call still wins — this records that something was discarded.
85
+ */
86
+ dualChannel?: boolean;
87
+ /** What that discarded envelope would have decided, when `dualChannel` is true. */
88
+ shadowEnvelopeKind?: DecisionKind;
89
+ /** Native turns only: prose accompanied the call(s) and was not an envelope (Haiku: 30% of call turns in run 7). */
90
+ narrationWithCalls?: boolean;
91
+ /** Native turns only: the calls carried empty or placeholder arguments alongside prose — the Sonnet 5 pattern of results §13.3. */
92
+ placeholderCall?: boolean;
93
+ }
94
+ /** What the harness observed on the wire, before normalization. */
95
+ export interface RawTurn {
96
+ /** The model's text output, if any. */
97
+ text?: string | null;
98
+ /** Native tool calls on the assistant turn, if the driver requested tools. */
99
+ toolCalls?: Array<{
100
+ name: string;
101
+ arguments?: Record<string, unknown>;
102
+ }> | null;
103
+ /**
104
+ * Sanitized tool name → the Action name the framework dispatches for it.
105
+ *
106
+ * Provider tool names are constrained (`^[a-zA-Z0-9_-]+$` and friends), so MJ's Action names
107
+ * are sanitized on the way out: `Execute Code` is declared as `execute_code`. A corpus case
108
+ * names the ACTION, per §1.2 — comparing the raw wire name against it would score every
109
+ * correct native call as the wrong action, measuring the sanitizer rather than the model.
110
+ *
111
+ * The driver supplies the framework's own reverse map, the same one `LoopAgentType` dispatches
112
+ * from. An unmapped name passes through unchanged: a model that invented a tool should be
113
+ * reported under the name it invented, not silently dropped.
114
+ */
115
+ toolNameMap?: Readonly<Record<string, string>> | null;
116
+ /**
117
+ * Which protocol the turn was produced under. `'implicit'` reads plain text with no call as
118
+ * task completion; the other two keep the envelope rules. Absent = `'envelope'`.
119
+ */
120
+ protocol?: 'envelope' | 'hybrid' | 'implicit' | null;
121
+ /** Control-flow tool name → what calling it decides. Absent when no control tools were declared. */
122
+ controlToolMap?: Readonly<Record<string, ControlToolRole>> | null;
123
+ }
124
+ /**
125
+ * Strips a markdown fence so a fenced-but-otherwise-correct envelope still counts as parsed.
126
+ *
127
+ * Fencing is a formatting habit, not a decision error. Counting it as malformed would inflate the
128
+ * exact metric this harness exists to measure honestly — and MJ's own loop parser tolerates it, so
129
+ * scoring it as a failure would also diverge from what production actually does.
130
+ */
131
+ export declare function stripJsonFence(text: string): string;
132
+ /**
133
+ * Counts complete top-level JSON objects in a string that failed to parse as one — the
134
+ * "two envelopes back to back" shape. String-aware brace counting; returns 0 unless at least two
135
+ * objects were found, so a single malformed object is left to the ordinary diagnostic.
136
+ */
137
+ export declare function countConcatenatedObjects(text: string): number;
138
+ /**
139
+ * Spec §8.2: prose accompanied the call(s) and every call has empty arguments, or every string
140
+ * argument is a placeholder token. The Sonnet 5 pattern of results §13.3, made countable.
141
+ */
142
+ export declare function isPlaceholderCall(calls: NonNullable<RawTurn['toolCalls']>, text: string | null | undefined): boolean;
143
+ /**
144
+ * Normalizes one turn into an {@link ObservedDecision}.
145
+ *
146
+ * Native tool calls win when present: a model that emitted both has made its decision natively and
147
+ * the text is at most a narration of it. This is the rule the corpus specifies, and the provider matrix
148
+ * measured why it matters — Gemini 3.7 Flash under JSON mode answers a tool question natively while
149
+ * ignoring the requested envelope entirely, which an envelope-only reader scores as 100% malformed
150
+ * even though the decision was correct.
151
+ *
152
+ * Under the implicit protocol, plain text with no call is the terminal form: it reads as
153
+ * task completion unless it parses as an envelope, in which case the envelope is honoured (§2.1).
154
+ */
155
+ export declare function normalizeDecision(turn: RawTurn): ObservedDecision;
156
+ //# sourceMappingURL=decision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../src/eval/decision.ts"],"names":[],"mappings":"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"}