prefix-safe-json 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +27 -4
- package/README.md +273 -398
- package/dist/ajv.d.ts +2 -0
- package/dist/ajv.d.ts.map +1 -0
- package/dist/ajv.js +19 -0
- package/dist/ajv.js.map +1 -0
- package/dist/conformance/runner.d.ts +19 -0
- package/dist/conformance/runner.d.ts.map +1 -0
- package/dist/conformance/runner.js +78 -0
- package/dist/conformance/runner.js.map +1 -0
- package/dist/conformance/types.d.ts +115 -0
- package/dist/conformance/types.d.ts.map +1 -0
- package/dist/conformance/types.js +2 -0
- package/dist/conformance/types.js.map +1 -0
- package/dist/conformance.d.ts +3 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +11 -0
- package/dist/conformance.js.map +1 -0
- package/dist/coordinator/coordinator.d.ts +25 -11
- package/dist/coordinator/coordinator.d.ts.map +1 -1
- package/dist/coordinator/coordinator.js +140 -25
- package/dist/coordinator/coordinator.js.map +1 -1
- package/dist/coordinator/types.d.ts +3 -2
- package/dist/coordinator/types.d.ts.map +1 -1
- package/dist/gate/gate.d.ts +2 -2
- package/dist/gate/gate.d.ts.map +1 -1
- package/dist/gate/gate.js +4 -4
- package/dist/gate/gate.js.map +1 -1
- package/dist/guard/provider-guard.js +1 -1
- package/dist/guard/provider-guard.js.map +1 -1
- package/dist/guard/types.d.ts +11 -4
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +89 -8
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/gemini.d.ts +122 -0
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +442 -25
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts +47 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +381 -84
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/providers/openai.d.ts +7 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +459 -67
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts.map +1 -1
- package/dist/providers/openrouter.js +14 -2
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/standard-schema.d.ts +2 -0
- package/dist/standard-schema.d.ts.map +1 -0
- package/dist/standard-schema.js +11 -0
- package/dist/standard-schema.js.map +1 -0
- package/dist/validation/ajv-validator.d.ts +43 -0
- package/dist/validation/ajv-validator.d.ts.map +1 -0
- package/dist/validation/ajv-validator.js +63 -0
- package/dist/validation/ajv-validator.js.map +1 -0
- package/dist/validation/standard-schema-validator.d.ts +41 -0
- package/dist/validation/standard-schema-validator.d.ts.map +1 -0
- package/dist/validation/standard-schema-validator.js +131 -0
- package/dist/validation/standard-schema-validator.js.map +1 -0
- package/dist/validation/types.d.ts +60 -0
- package/dist/validation/types.d.ts.map +1 -0
- package/dist/validation/types.js +77 -0
- package/dist/validation/types.js.map +1 -0
- package/package.json +16 -1
package/dist/ajv.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ajv.d.ts","sourceRoot":"","sources":["../src/ajv.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC"}
|
package/dist/ajv.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// prefix-safe-json/ajv — explicit Ajv adapter entry point.
|
|
3
|
+
//
|
|
4
|
+
// `ajv` is a hard runtime dependency of the core execution-authority path
|
|
5
|
+
// (`.`) today, not an optional or lazy one: `coordinator.ts` statically
|
|
6
|
+
// imports `buildValidatorMap` from `./validation/types.js`, which statically
|
|
7
|
+
// imports `createAjvValidator` from `./ajv-validator.js`, which statically
|
|
8
|
+
// imports `ajv` itself - so merely importing the package root loads `ajv`
|
|
9
|
+
// into memory, whether or not a caller ever registers a `schemas` entry.
|
|
10
|
+
// See docs/VALIDATION.md's "Ajv loading" section for why this static-import
|
|
11
|
+
// shape was deliberately kept over a lazy-load alternative.
|
|
12
|
+
//
|
|
13
|
+
// Importing from here instead of the root is only useful if you want an
|
|
14
|
+
// explicit, discoverable Ajv entry point - e.g. to compile one validator up
|
|
15
|
+
// front and reuse it across multiple coordinators/gates. See
|
|
16
|
+
// docs/VALIDATION.md.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
export { createAjvValidator } from "./validation/ajv-validator.js";
|
|
19
|
+
//# sourceMappingURL=ajv.js.map
|
package/dist/ajv.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ajv.js","sourceRoot":"","sources":["../src/ajv.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,2DAA2D;AAC3D,EAAE;AACF,0EAA0E;AAC1E,wEAAwE;AACxE,6EAA6E;AAC7E,2EAA2E;AAC3E,0EAA0E;AAC1E,yEAAyE;AACzE,4EAA4E;AAC5E,4DAA4D;AAC5D,EAAE;AACF,wEAAwE;AACxE,4EAA4E;AAC5E,6DAA6D;AAC7D,sBAAsB;AACtB,8EAA8E;AAE9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ConformanceFixture, ConformanceFixtureResult, ConformanceSuiteResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* @public (Experimental)
|
|
4
|
+
* Runs one conformance fixture against this package's real, unmodified
|
|
5
|
+
* `createToolCallExecutionGate()` - not a reimplementation - and compares
|
|
6
|
+
* the resulting decisions against `fixture.expected`. No network, no
|
|
7
|
+
* provider API key, no model call: `fixture.events` are pushed directly,
|
|
8
|
+
* exactly as a provider adapter would produce them.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runToolCallIntegrityFixture(fixture: ConformanceFixture): ConformanceFixtureResult;
|
|
11
|
+
/**
|
|
12
|
+
* @public (Experimental)
|
|
13
|
+
* Runs every fixture in `fixtures` through {@link runToolCallIntegrityFixture}
|
|
14
|
+
* and aggregates the results. Deterministic, synchronous, no I/O of its
|
|
15
|
+
* own - callers own reading fixture files from disk (see
|
|
16
|
+
* `conformance/fixtures/` and `docs/CONFORMANCE.md`).
|
|
17
|
+
*/
|
|
18
|
+
export declare function runToolCallIntegritySuite(fixtures: readonly ConformanceFixture[]): ConformanceSuiteResult;
|
|
19
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/conformance/runner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAoBpB;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,kBAAkB,GAAG,wBAAwB,CA6CjG;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GAAG,sBAAsB,CAGzG"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createToolCallExecutionGate } from "../gate/gate.js";
|
|
2
|
+
function matchDecision(expected, decisions, claimed) {
|
|
3
|
+
for (const decision of decisions) {
|
|
4
|
+
if (claimed.has(decision))
|
|
5
|
+
continue;
|
|
6
|
+
if (expected.name !== undefined && decision.name !== expected.name)
|
|
7
|
+
continue;
|
|
8
|
+
if (expected.toolIndex !== undefined && decision.toolIndex !== expected.toolIndex)
|
|
9
|
+
continue;
|
|
10
|
+
if (expected.name === undefined && expected.toolIndex === undefined) {
|
|
11
|
+
// No correlation key given - fall back to first unclaimed decision,
|
|
12
|
+
// in the order the gate produced them.
|
|
13
|
+
}
|
|
14
|
+
return decision;
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @public (Experimental)
|
|
20
|
+
* Runs one conformance fixture against this package's real, unmodified
|
|
21
|
+
* `createToolCallExecutionGate()` - not a reimplementation - and compares
|
|
22
|
+
* the resulting decisions against `fixture.expected`. No network, no
|
|
23
|
+
* provider API key, no model call: `fixture.events` are pushed directly,
|
|
24
|
+
* exactly as a provider adapter would produce them.
|
|
25
|
+
*/
|
|
26
|
+
export function runToolCallIntegrityFixture(fixture) {
|
|
27
|
+
// `schemaVersion`/`profile` are typed as literal `1`/`"normalized-gate"`,
|
|
28
|
+
// but that is a compile-time guarantee only - fixtures are ordinary JSON
|
|
29
|
+
// read off disk (or constructed by a JS caller), so both are checked at
|
|
30
|
+
// runtime here rather than trusted from the type. v1 only; no migration
|
|
31
|
+
// machinery for a schemaVersion that doesn't exist yet.
|
|
32
|
+
if (fixture.schemaVersion !== 1) {
|
|
33
|
+
throw new Error(`prefix-safe-json: fixture "${fixture.id}" declares schemaVersion ${JSON.stringify(fixture.schemaVersion)}, ` +
|
|
34
|
+
`which this runner does not support (only schemaVersion 1 exists). See docs/CONFORMANCE.md.`);
|
|
35
|
+
}
|
|
36
|
+
if (fixture.profile !== "normalized-gate") {
|
|
37
|
+
throw new Error(`prefix-safe-json: fixture "${fixture.id}" declares profile ${JSON.stringify(fixture.profile)}, ` +
|
|
38
|
+
`which this runner does not support (only "normalized-gate" exists in v1). See docs/CONFORMANCE.md.`);
|
|
39
|
+
}
|
|
40
|
+
const gate = createToolCallExecutionGate(undefined, undefined, fixture.toolSchemas);
|
|
41
|
+
for (const event of fixture.events)
|
|
42
|
+
gate.push(event);
|
|
43
|
+
const { decisions } = gate.finish();
|
|
44
|
+
const claimed = new Set();
|
|
45
|
+
const calls = fixture.expected.map((expected) => {
|
|
46
|
+
const actual = matchDecision(expected, decisions, claimed);
|
|
47
|
+
if (!actual) {
|
|
48
|
+
return { expected, pass: false, failureReason: "no matching decision was produced" };
|
|
49
|
+
}
|
|
50
|
+
claimed.add(actual);
|
|
51
|
+
const pass = actual.action === expected.action && actual.reason === expected.reason;
|
|
52
|
+
return {
|
|
53
|
+
expected,
|
|
54
|
+
pass,
|
|
55
|
+
actual: { action: actual.action, reason: actual.reason, name: actual.name, toolIndex: actual.toolIndex },
|
|
56
|
+
...(pass ? {} : { failureReason: `expected ${expected.action}/${expected.reason}, got ${actual.action}/${actual.reason}` }),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
const unmatchedActualCount = decisions.length - claimed.size;
|
|
60
|
+
return {
|
|
61
|
+
id: fixture.id,
|
|
62
|
+
pass: calls.every((c) => c.pass) && unmatchedActualCount === 0,
|
|
63
|
+
calls,
|
|
64
|
+
unmatchedActualCount,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* @public (Experimental)
|
|
69
|
+
* Runs every fixture in `fixtures` through {@link runToolCallIntegrityFixture}
|
|
70
|
+
* and aggregates the results. Deterministic, synchronous, no I/O of its
|
|
71
|
+
* own - callers own reading fixture files from disk (see
|
|
72
|
+
* `conformance/fixtures/` and `docs/CONFORMANCE.md`).
|
|
73
|
+
*/
|
|
74
|
+
export function runToolCallIntegritySuite(fixtures) {
|
|
75
|
+
const results = fixtures.map(runToolCallIntegrityFixture);
|
|
76
|
+
return { pass: results.every((r) => r.pass), results };
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/conformance/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { ExecutionAction, ExecutionReason } from "../gate/types.js";
|
|
2
|
+
import type { NormalizedToolStreamEvent } from "../coordinator/protocol.js";
|
|
3
|
+
/**
|
|
4
|
+
* @public (Experimental)
|
|
5
|
+
* How a fixture's event sequence relates to what a real provider can
|
|
6
|
+
* actually send:
|
|
7
|
+
*
|
|
8
|
+
* - `"protocol-realistic"`: this exact sequence has been observed, or is
|
|
9
|
+
* directly documented as possible, in a real provider's stream.
|
|
10
|
+
* - `"sdk-representable"`: the installed provider SDK's own types permit
|
|
11
|
+
* constructing and receiving this sequence, but it has not been
|
|
12
|
+
* independently confirmed that a real provider sends it.
|
|
13
|
+
* - `"synthetic-adversarial"`: constructed to stress a specific boundary
|
|
14
|
+
* (a resource limit, a pathological ordering) with no claim that any
|
|
15
|
+
* provider produces it. Still a legitimate conformance case - not every
|
|
16
|
+
* invariant needs a real-world trigger to be worth holding - but never
|
|
17
|
+
* to be read as an observed production bug.
|
|
18
|
+
*/
|
|
19
|
+
export type FixtureClassification = "protocol-realistic" | "sdk-representable" | "synthetic-adversarial";
|
|
20
|
+
/**
|
|
21
|
+
* @public (Experimental)
|
|
22
|
+
* Where a fixture came from, for provenance. `classification` is required;
|
|
23
|
+
* everything else is free-form context for a human reader, not consumed by
|
|
24
|
+
* the runner.
|
|
25
|
+
*/
|
|
26
|
+
export interface FixtureProvenance {
|
|
27
|
+
readonly classification: FixtureClassification;
|
|
28
|
+
/** e.g. "vercel/ai", "continuedev/continue" - omitted for fixtures with no external origin. */
|
|
29
|
+
readonly project?: string;
|
|
30
|
+
/** An issue/PR/commit URL substantiating this fixture, when one exists. */
|
|
31
|
+
readonly reference?: string;
|
|
32
|
+
/** Free-text note, e.g. why a case is classified the way it is. */
|
|
33
|
+
readonly note?: string;
|
|
34
|
+
}
|
|
35
|
+
/** @public (Experimental) One call's expected execution-authority outcome. */
|
|
36
|
+
export interface ConformanceExpectedCall {
|
|
37
|
+
/** Correlates against the matching decision's own `name`, when present. */
|
|
38
|
+
readonly name?: string;
|
|
39
|
+
/** Correlates against the matching decision's own `toolIndex`, when present. */
|
|
40
|
+
readonly toolIndex?: number;
|
|
41
|
+
readonly action: ExecutionAction;
|
|
42
|
+
readonly reason: ExecutionReason;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* @public (Experimental)
|
|
46
|
+
* Which conformance claim a fixture makes, made explicit rather than
|
|
47
|
+
* implied by the corpus's own name. `"normalized-gate"` is the only
|
|
48
|
+
* profile v1 defines:
|
|
49
|
+
*
|
|
50
|
+
* A `"normalized-gate"` fixture tests the coordinator/execution-gate's
|
|
51
|
+
* response to an already-normalized `NormalizedToolStreamEvent` sequence -
|
|
52
|
+
* identity correlation, lifecycle/terminal-state handling, and decision
|
|
53
|
+
* composition, entirely *after* a provider adapter has already produced
|
|
54
|
+
* that sequence. It does NOT test, and must never be described as
|
|
55
|
+
* testing, whether an adapter correctly derives that sequence (or a
|
|
56
|
+
* `provider_diagnostic` within it) from a specific provider's own raw
|
|
57
|
+
* wire format - that is provider-adapter conformance, a distinct,
|
|
58
|
+
* currently unwritten profile. See `docs/CONFORMANCE.md`'s "Provider-
|
|
59
|
+
* adapter conformance — future profile / not covered by normalized-gate
|
|
60
|
+
* v1" section for exactly what this excludes and why.
|
|
61
|
+
*/
|
|
62
|
+
export type ConformanceProfile = "normalized-gate";
|
|
63
|
+
/**
|
|
64
|
+
* @public (Experimental)
|
|
65
|
+
* A single, portable tool-call-integrity conformance fixture: a
|
|
66
|
+
* provider-neutral normalized event sequence and the execution-authority
|
|
67
|
+
* decision it must produce. Deliberately expressed in terms of
|
|
68
|
+
* `NormalizedToolStreamEvent` - the same shape every provider adapter in
|
|
69
|
+
* this package already emits - rather than any raw provider wire format,
|
|
70
|
+
* so a fixture never requires understanding a specific SDK's shape to
|
|
71
|
+
* read or reuse. See `docs/CONFORMANCE.md`.
|
|
72
|
+
*/
|
|
73
|
+
export interface ConformanceFixture {
|
|
74
|
+
readonly schemaVersion: 1;
|
|
75
|
+
readonly profile: ConformanceProfile;
|
|
76
|
+
readonly id: string;
|
|
77
|
+
readonly description: string;
|
|
78
|
+
readonly provenance: FixtureProvenance;
|
|
79
|
+
readonly events: readonly NormalizedToolStreamEvent[];
|
|
80
|
+
readonly expected: readonly ConformanceExpectedCall[];
|
|
81
|
+
/**
|
|
82
|
+
* Optional per-tool JSON Schema (draft-07), keyed by tool name, for
|
|
83
|
+
* fixtures that specifically exercise schema-validity outcomes (e.g.
|
|
84
|
+
* `reason: "schema_invalid"`). Deliberately raw JSON Schema only, not a
|
|
85
|
+
* `ToolInputValidator` - fixtures must stay JSON-serializable. See
|
|
86
|
+
* `docs/VALIDATION.md` for how validators plug in outside fixtures.
|
|
87
|
+
*/
|
|
88
|
+
readonly toolSchemas?: Record<string, object>;
|
|
89
|
+
}
|
|
90
|
+
/** @public (Experimental) One expected call matched (or not) against the real decisions produced. */
|
|
91
|
+
export interface ConformanceCallResult {
|
|
92
|
+
readonly expected: ConformanceExpectedCall;
|
|
93
|
+
readonly pass: boolean;
|
|
94
|
+
readonly actual?: {
|
|
95
|
+
readonly action: ExecutionAction;
|
|
96
|
+
readonly reason: ExecutionReason;
|
|
97
|
+
readonly name?: string;
|
|
98
|
+
readonly toolIndex?: number;
|
|
99
|
+
};
|
|
100
|
+
readonly failureReason?: string;
|
|
101
|
+
}
|
|
102
|
+
/** @public (Experimental) The result of running one fixture. */
|
|
103
|
+
export interface ConformanceFixtureResult {
|
|
104
|
+
readonly id: string;
|
|
105
|
+
readonly pass: boolean;
|
|
106
|
+
readonly calls: readonly ConformanceCallResult[];
|
|
107
|
+
/** Present when the fixture produced more or fewer decisions than `expected` entries. */
|
|
108
|
+
readonly unmatchedActualCount: number;
|
|
109
|
+
}
|
|
110
|
+
/** @public (Experimental) The result of running a whole fixture suite. */
|
|
111
|
+
export interface ConformanceSuiteResult {
|
|
112
|
+
readonly pass: boolean;
|
|
113
|
+
readonly results: readonly ConformanceFixtureResult[];
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conformance/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAE5E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG,mBAAmB,GAAG,uBAAuB,CAAC;AAEzG;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC;IAC/C,+FAA+F;IAC/F,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,8EAA8E;AAC9E,MAAM,WAAW,uBAAuB;IACtC,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAEnD;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,SAAS,yBAAyB,EAAE,CAAC;IACtD,QAAQ,CAAC,QAAQ,EAAE,SAAS,uBAAuB,EAAE,CAAC;IACtD;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C;AAED,qGAAqG;AACrG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9I,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,gEAAgE;AAChE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACjD,yFAAyF;IACzF,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC;AAED,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACvD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/conformance/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { runToolCallIntegrityFixture, runToolCallIntegritySuite } from "./conformance/runner.js";
|
|
2
|
+
export type { ConformanceFixture, ConformanceFixtureResult, ConformanceSuiteResult, ConformanceCallResult, ConformanceExpectedCall, FixtureProvenance, FixtureClassification, } from "./conformance/types.js";
|
|
3
|
+
//# sourceMappingURL=conformance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,2BAA2B,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACjG,YAAY,EACV,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// prefix-safe-json/conformance — Tool Call Integrity conformance runner.
|
|
3
|
+
//
|
|
4
|
+
// Runs portable, provider-neutral fixtures (see conformance/fixtures/ and
|
|
5
|
+
// docs/CONFORMANCE.md) against this package's own real execution-authority
|
|
6
|
+
// gate. Useful even to projects that do not install this package at
|
|
7
|
+
// runtime: the fixture format and expected outcomes are meaningful on
|
|
8
|
+
// their own, against any implementation of this problem class.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export { runToolCallIntegrityFixture, runToolCallIntegritySuite } from "./conformance/runner.js";
|
|
11
|
+
//# sourceMappingURL=conformance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.js","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,yEAAyE;AACzE,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,oEAAoE;AACpE,sEAAsE;AACtE,+DAA+D;AAC/D,8EAA8E;AAE9E,OAAO,EAAE,2BAA2B,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ParserOptions } from "../types.js";
|
|
2
|
+
import { type ToolInputValidator } from "../validation/types.js";
|
|
2
3
|
import { NormalizedToolStreamEvent, StreamEndReason } from "./protocol.js";
|
|
3
|
-
import { ToolCallStreamCoordinator, ToolCallCoordinatorSnapshot, ToolCallCoordinatorEvent, ToolCallCoordinatorFinalResult, CoordinatorPushResult, CoordinatorLimits
|
|
4
|
+
import { ToolCallStreamCoordinator, ToolCallCoordinatorSnapshot, ToolCallCoordinatorEvent, ToolCallCoordinatorFinalResult, CoordinatorPushResult, CoordinatorLimits } from "./types.js";
|
|
4
5
|
export declare class DefaultToolCallStreamCoordinator implements ToolCallStreamCoordinator {
|
|
5
6
|
private readonly limits;
|
|
6
7
|
private readonly parserOptions;
|
|
@@ -21,17 +22,30 @@ export declare class DefaultToolCallStreamCoordinator implements ToolCallStreamC
|
|
|
21
22
|
* underlying per-call IncrementalJsonParser (limits, repairs). Previously
|
|
22
23
|
* there was no way to pass the latter through at all; every parser the
|
|
23
24
|
* coordinator created was hardcoded to library defaults.
|
|
24
|
-
* @param toolSchemas Optional JSON Schema (draft-07) per tool name
|
|
25
|
-
*
|
|
26
|
-
* `
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
25
|
+
* @param toolSchemas Optional raw JSON Schema (draft-07) per tool name,
|
|
26
|
+
* compiled internally through `prefix-safe-json/ajv`'s
|
|
27
|
+
* `createAjvValidator()`. This is the pre-0.5 option, unchanged in
|
|
28
|
+
* shape - existing callers need no code change.
|
|
29
|
+
* @param validators Optional {@link ToolInputValidator} per tool name -
|
|
30
|
+
* additive, 0.5.0+. Use this to plug in Zod, TypeBox, Valibot, a
|
|
31
|
+
* Standard Schema-compliant validator, or a hand-written check, with no
|
|
32
|
+
* forced dependency on this package's own validation stack. A tool name
|
|
33
|
+
* present in both `toolSchemas` and `validators` is a construction-time
|
|
34
|
+
* error - there is no structural discrimination or silent precedence
|
|
35
|
+
* between the two; each tool's validation comes from exactly one
|
|
36
|
+
* explicit source. See `docs/VALIDATION.md`.
|
|
37
|
+
*
|
|
38
|
+
* When a call for a registered tool reaches outcome "complete", its
|
|
39
|
+
* `stableValue` is validated. Structural prefix-safety (no
|
|
40
|
+
* truncated/fabricated values) and validator-reported validity are
|
|
41
|
+
* independent concerns — a value can be genuinely complete and still
|
|
42
|
+
* not match what the tool declared it needs (wrong type, missing
|
|
30
43
|
* required field the model never provided at all). Both must hold for
|
|
31
|
-
* `executable` to be true.
|
|
32
|
-
* malformed schema
|
|
44
|
+
* `executable` to be true. Entries are resolved eagerly here so a
|
|
45
|
+
* malformed schema (or a misconfigured validator's constructor-time
|
|
46
|
+
* setup) fails fast at construction, not mid-stream.
|
|
33
47
|
*/
|
|
34
|
-
constructor(limits?: Partial<CoordinatorLimits>, parserOptions?: ParserOptions, toolSchemas?: Record<string,
|
|
48
|
+
constructor(limits?: Partial<CoordinatorLimits>, parserOptions?: ParserOptions, toolSchemas?: Record<string, object>, validators?: Record<string, ToolInputValidator>);
|
|
35
49
|
push(event: NormalizedToolStreamEvent): CoordinatorPushResult;
|
|
36
50
|
private handleStart;
|
|
37
51
|
private handleIdentity;
|
|
@@ -51,5 +65,5 @@ export declare class DefaultToolCallStreamCoordinator implements ToolCallStreamC
|
|
|
51
65
|
providerReason?: string;
|
|
52
66
|
}): ToolCallCoordinatorFinalResult;
|
|
53
67
|
}
|
|
54
|
-
export declare function createToolCallStreamCoordinator(limits?: Partial<CoordinatorLimits>, parserOptions?: ParserOptions, toolSchemas?: Record<string,
|
|
68
|
+
export declare function createToolCallStreamCoordinator(limits?: Partial<CoordinatorLimits>, parserOptions?: ParserOptions, toolSchemas?: Record<string, object>, validators?: Record<string, ToolInputValidator>): ToolCallStreamCoordinator;
|
|
55
69
|
//# sourceMappingURL=coordinator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coordinator.d.ts","sourceRoot":"","sources":["../../src/coordinator/coordinator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"coordinator.d.ts","sourceRoot":"","sources":["../../src/coordinator/coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAqB,KAAK,kBAAkB,EAA6B,MAAM,wBAAwB,CAAC;AAC/G,OAAO,EAAE,yBAAyB,EAAgB,eAAe,EAAE,MAAM,eAAe,CAAC;AACzF,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,EACxB,8BAA8B,EAC9B,qBAAqB,EACrB,iBAAiB,EAIlB,MAAM,YAAY,CAAC;AAyFpB,qBAAa,gCAAiC,YAAW,yBAAyB;IAChF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAC1D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAkC;IACjE,OAAO,CAAC,KAAK,CAAgD;IAC7D,OAAO,CAAC,qBAAqB,CAAkC;IAC/D,OAAO,CAAC,0BAA0B,CAAmD;IACrF,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAC,CAAkB;IAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;gBAED,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,EACnC,aAAa,CAAC,EAAE,aAAa,EAC7B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC;IAOjD,IAAI,CAAC,KAAK,EAAE,yBAAyB,GAAG,qBAAqB;IAuE7D,OAAO,CAAC,WAAW;IAiEnB,OAAO,CAAC,cAAc;IAgDtB,OAAO,CAAC,eAAe;IAqCvB,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,aAAa;IA6BrB,OAAO,CAAC,UAAU;IAgIlB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,wBAAwB;IAwChC,OAAO,CAAC,eAAe;IA0BvB,OAAO,CAAC,OAAO;IAcf,OAAO,CAAC,aAAa;IASrB,QAAQ,IAAI,2BAA2B;IAmBvC,WAAW,IAAI,SAAS,wBAAwB,EAAE;IAMlD,MAAM,CAAC,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,eAAe,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,8BAA8B;CAcrG;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,EACnC,aAAa,CAAC,EAAE,aAAa,EAC7B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAC9C,yBAAyB,CAE3B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Ajv } from "ajv";
|
|
2
1
|
import { createParser } from "../parser.js";
|
|
3
|
-
import {
|
|
2
|
+
import { buildValidatorMap } from "../validation/types.js";
|
|
3
|
+
import { DEFAULT_COORDINATOR_LIMITS, } from "./types.js";
|
|
4
4
|
import { AUTHORITY_PROTOCOL_VIOLATION_CODES, DUPLICATE_TOOL_CALL_START_DIAGNOSTIC_CODE, SDK_EXECUTION_OBSERVED_DIAGNOSTIC_CODE, SDK_EXECUTION_ERROR_DIAGNOSTIC_CODE, EVENT_AFTER_STREAM_END_DIAGNOSTIC_CODE, TERMINAL_REASON_CONFLICT_DIAGNOSTIC_CODE, } from "./diagnostic-codes.js";
|
|
5
5
|
// A `provider_diagnostic` carrying either code is proof that a provider SDK's
|
|
6
6
|
// own tool loop already invoked this call's `execute` callback (see the two
|
|
@@ -12,6 +12,53 @@ const SDK_EXECUTION_OBSERVED_CODES = new Set([
|
|
|
12
12
|
SDK_EXECUTION_OBSERVED_DIAGNOSTIC_CODE,
|
|
13
13
|
SDK_EXECUTION_ERROR_DIAGNOSTIC_CODE,
|
|
14
14
|
]);
|
|
15
|
+
/**
|
|
16
|
+
* @internal
|
|
17
|
+
* Runtime shape check for what `validator.validate()` actually returned.
|
|
18
|
+
* `ToolInputValidator` is a TypeScript interface, not a runtime guarantee -
|
|
19
|
+
* `result && result.valid === true/false` alone is not a safe discriminator:
|
|
20
|
+
* an array is `typeof "object"` and could carry an incidental `.valid`
|
|
21
|
+
* property; a function can too (`fn.valid = true` is legal JS), and
|
|
22
|
+
* `typeof fn !== "object"` is what actually excludes it here. Also rejects
|
|
23
|
+
* internally contradictory shapes a conforming implementation never
|
|
24
|
+
* produces: `valid: true` alongside a defined `errors` field, or
|
|
25
|
+
* `valid: false` with an `errors` field that is anything other than absent
|
|
26
|
+
* or a `string[]` - a bare string or a non-string array would otherwise
|
|
27
|
+
* reach `(result.errors ?? []).join("; ")` below, which throws on a string
|
|
28
|
+
* (no `.join`) and silently stringifies non-string elements on an array.
|
|
29
|
+
*/
|
|
30
|
+
function isWellFormedValidationResult(result) {
|
|
31
|
+
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
32
|
+
return false;
|
|
33
|
+
const value = result;
|
|
34
|
+
if (value.valid === true)
|
|
35
|
+
return value.errors === undefined;
|
|
36
|
+
if (value.valid === false) {
|
|
37
|
+
if (value.errors === undefined)
|
|
38
|
+
return true;
|
|
39
|
+
return Array.isArray(value.errors) && value.errors.every((e) => typeof e === "string");
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @internal
|
|
45
|
+
* Describes an arbitrary, possibly-malformed validator result for a
|
|
46
|
+
* diagnostic message without ever throwing. A circular object or a value
|
|
47
|
+
* containing a BigInt both make `JSON.stringify` throw - a diagnostic
|
|
48
|
+
* describing *why* a result was rejected must never itself crash the
|
|
49
|
+
* stream it exists to fail closed.
|
|
50
|
+
*/
|
|
51
|
+
function describeMalformedResult(value) {
|
|
52
|
+
if (typeof value === "object") {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.stringify(value);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return Object.prototype.toString.call(value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return `${typeof value} ${String(value)}`;
|
|
61
|
+
}
|
|
15
62
|
class CoordinatorCallState {
|
|
16
63
|
internalId;
|
|
17
64
|
provider;
|
|
@@ -49,26 +96,33 @@ export class DefaultToolCallStreamCoordinator {
|
|
|
49
96
|
* underlying per-call IncrementalJsonParser (limits, repairs). Previously
|
|
50
97
|
* there was no way to pass the latter through at all; every parser the
|
|
51
98
|
* coordinator created was hardcoded to library defaults.
|
|
52
|
-
* @param toolSchemas Optional JSON Schema (draft-07) per tool name
|
|
53
|
-
*
|
|
54
|
-
* `
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
99
|
+
* @param toolSchemas Optional raw JSON Schema (draft-07) per tool name,
|
|
100
|
+
* compiled internally through `prefix-safe-json/ajv`'s
|
|
101
|
+
* `createAjvValidator()`. This is the pre-0.5 option, unchanged in
|
|
102
|
+
* shape - existing callers need no code change.
|
|
103
|
+
* @param validators Optional {@link ToolInputValidator} per tool name -
|
|
104
|
+
* additive, 0.5.0+. Use this to plug in Zod, TypeBox, Valibot, a
|
|
105
|
+
* Standard Schema-compliant validator, or a hand-written check, with no
|
|
106
|
+
* forced dependency on this package's own validation stack. A tool name
|
|
107
|
+
* present in both `toolSchemas` and `validators` is a construction-time
|
|
108
|
+
* error - there is no structural discrimination or silent precedence
|
|
109
|
+
* between the two; each tool's validation comes from exactly one
|
|
110
|
+
* explicit source. See `docs/VALIDATION.md`.
|
|
111
|
+
*
|
|
112
|
+
* When a call for a registered tool reaches outcome "complete", its
|
|
113
|
+
* `stableValue` is validated. Structural prefix-safety (no
|
|
114
|
+
* truncated/fabricated values) and validator-reported validity are
|
|
115
|
+
* independent concerns — a value can be genuinely complete and still
|
|
116
|
+
* not match what the tool declared it needs (wrong type, missing
|
|
58
117
|
* required field the model never provided at all). Both must hold for
|
|
59
|
-
* `executable` to be true.
|
|
60
|
-
* malformed schema
|
|
118
|
+
* `executable` to be true. Entries are resolved eagerly here so a
|
|
119
|
+
* malformed schema (or a misconfigured validator's constructor-time
|
|
120
|
+
* setup) fails fast at construction, not mid-stream.
|
|
61
121
|
*/
|
|
62
|
-
constructor(limits, parserOptions, toolSchemas) {
|
|
122
|
+
constructor(limits, parserOptions, toolSchemas, validators) {
|
|
63
123
|
this.limits = { ...DEFAULT_COORDINATOR_LIMITS, ...limits };
|
|
64
124
|
this.parserOptions = parserOptions;
|
|
65
|
-
this.toolValidators =
|
|
66
|
-
if (toolSchemas) {
|
|
67
|
-
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
68
|
-
for (const [toolName, schema] of Object.entries(toolSchemas)) {
|
|
69
|
-
this.toolValidators.set(toolName, ajv.compile(schema));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
125
|
+
this.toolValidators = buildValidatorMap(toolSchemas, validators);
|
|
72
126
|
}
|
|
73
127
|
push(event) {
|
|
74
128
|
if (this.isFinished) {
|
|
@@ -365,14 +419,53 @@ export class DefaultToolCallStreamCoordinator {
|
|
|
365
419
|
// Schema validation is a separate concern from structural
|
|
366
420
|
// prefix-safety: only meaningful once the value is genuinely complete,
|
|
367
421
|
// and only for tools with a registered schema.
|
|
422
|
+
//
|
|
423
|
+
// Stryker disable next-line ConditionalExpression: `call.name !==
|
|
424
|
+
// undefined` cannot observably differ from `true` here - the outcome
|
|
425
|
+
// assignment above already forces outcome to "invalid" whenever
|
|
426
|
+
// call.name is undefined, so reaching outcome === "complete" already
|
|
427
|
+
// guarantees call.name is defined. This conjunct exists for TypeScript's
|
|
428
|
+
// type narrowing (removing it is a real compile error at
|
|
429
|
+
// `this.toolValidators.get(call.name)` below, verified directly), not
|
|
430
|
+
// as an independently reachable runtime branch.
|
|
368
431
|
if (outcome === "complete" && call.name !== undefined) {
|
|
369
432
|
const validator = this.toolValidators.get(call.name);
|
|
370
433
|
if (validator) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
434
|
+
// A validator that throws (e.g. a Standard Schema adapter refusing
|
|
435
|
+
// an async validator - see docs/VALIDATION.md) fails this one call
|
|
436
|
+
// closed rather than aborting the whole stream: caught here, not
|
|
437
|
+
// left to propagate out of push()/finish() and take every other
|
|
438
|
+
// in-flight call down with it.
|
|
439
|
+
let didThrow = false;
|
|
440
|
+
let thrown;
|
|
441
|
+
// Deliberately `unknown`, not `ToolValidationResult` - the interface
|
|
442
|
+
// declares validate()'s return type, but a plain-JS implementation
|
|
443
|
+
// (or an `as`-cast) can hand back anything at runtime, and this
|
|
444
|
+
// value is trusted no further than any other external input until
|
|
445
|
+
// isWellFormedValidationResult() below actually proves the shape.
|
|
446
|
+
let result;
|
|
447
|
+
try {
|
|
448
|
+
result = validator.validate(res.stableValue);
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
didThrow = true;
|
|
452
|
+
thrown = error;
|
|
453
|
+
}
|
|
454
|
+
if (didThrow) {
|
|
455
|
+
call.schemaValid = false;
|
|
456
|
+
this.addDiagnostic({
|
|
457
|
+
code: "E_SCHEMA_VALIDATION_FAILED",
|
|
458
|
+
severity: "error",
|
|
459
|
+
internalId: call.internalId,
|
|
460
|
+
message: `Validator for "${call.name}" threw instead of returning a result: ${thrown instanceof Error ? thrown.message : String(thrown)}`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
else if (isWellFormedValidationResult(result) && result.valid === true) {
|
|
464
|
+
call.schemaValid = true;
|
|
465
|
+
}
|
|
466
|
+
else if (isWellFormedValidationResult(result) && result.valid === false) {
|
|
467
|
+
call.schemaValid = false;
|
|
468
|
+
const errors = (result.errors ?? []).join("; ");
|
|
376
469
|
this.addDiagnostic({
|
|
377
470
|
code: "E_SCHEMA_VALIDATION_FAILED",
|
|
378
471
|
severity: "error",
|
|
@@ -380,6 +473,28 @@ export class DefaultToolCallStreamCoordinator {
|
|
|
380
473
|
message: `Tool call arguments for "${call.name}" do not match its schema: ${errors || "unknown validation error"}`,
|
|
381
474
|
});
|
|
382
475
|
}
|
|
476
|
+
else {
|
|
477
|
+
// A conforming ToolInputValidator never reaches here - validate()
|
|
478
|
+
// is typed to return exactly {valid: true} or {valid: false,
|
|
479
|
+
// ...}. This branch exists for every shape the type system cannot
|
|
480
|
+
// prevent at runtime: undefined/null, an array, a function
|
|
481
|
+
// (including one with an incidental `.valid` property), a
|
|
482
|
+
// non-boolean `valid`, `valid: true` alongside a defined `errors`
|
|
483
|
+
// field, or `valid: false` with `errors` that isn't absent or a
|
|
484
|
+
// plain `string[]` - see isWellFormedValidationResult() above.
|
|
485
|
+
// Fails closed the same as a thrown error - the alternative
|
|
486
|
+
// (falling through with schemaValid left unset) would let
|
|
487
|
+
// executable's `call.schemaValid !== false` check treat an
|
|
488
|
+
// unproven, malformed validator result as if no validator had run
|
|
489
|
+
// at all.
|
|
490
|
+
call.schemaValid = false;
|
|
491
|
+
this.addDiagnostic({
|
|
492
|
+
code: "E_SCHEMA_VALIDATION_FAILED",
|
|
493
|
+
severity: "error",
|
|
494
|
+
internalId: call.internalId,
|
|
495
|
+
message: `Validator for "${call.name}" returned a malformed result instead of {valid: true} or {valid: false, ...}: ${describeMalformedResult(result)}`,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
383
498
|
}
|
|
384
499
|
}
|
|
385
500
|
// Determine executable according to Executable Policy — structural
|
|
@@ -515,7 +630,7 @@ export class DefaultToolCallStreamCoordinator {
|
|
|
515
630
|
};
|
|
516
631
|
}
|
|
517
632
|
}
|
|
518
|
-
export function createToolCallStreamCoordinator(limits, parserOptions, toolSchemas) {
|
|
519
|
-
return new DefaultToolCallStreamCoordinator(limits, parserOptions, toolSchemas);
|
|
633
|
+
export function createToolCallStreamCoordinator(limits, parserOptions, toolSchemas, validators) {
|
|
634
|
+
return new DefaultToolCallStreamCoordinator(limits, parserOptions, toolSchemas, validators);
|
|
520
635
|
}
|
|
521
636
|
//# sourceMappingURL=coordinator.js.map
|