prefix-safe-json 0.4.5 → 0.5.1

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 (52) hide show
  1. package/LICENSE +27 -4
  2. package/README.md +195 -428
  3. package/dist/ajv.d.ts +2 -0
  4. package/dist/ajv.d.ts.map +1 -0
  5. package/dist/ajv.js +19 -0
  6. package/dist/ajv.js.map +1 -0
  7. package/dist/conformance/runner.d.ts +19 -0
  8. package/dist/conformance/runner.d.ts.map +1 -0
  9. package/dist/conformance/runner.js +78 -0
  10. package/dist/conformance/runner.js.map +1 -0
  11. package/dist/conformance/types.d.ts +115 -0
  12. package/dist/conformance/types.d.ts.map +1 -0
  13. package/dist/conformance/types.js +2 -0
  14. package/dist/conformance/types.js.map +1 -0
  15. package/dist/conformance.d.ts +3 -0
  16. package/dist/conformance.d.ts.map +1 -0
  17. package/dist/conformance.js +11 -0
  18. package/dist/conformance.js.map +1 -0
  19. package/dist/coordinator/coordinator.d.ts +25 -11
  20. package/dist/coordinator/coordinator.d.ts.map +1 -1
  21. package/dist/coordinator/coordinator.js +140 -25
  22. package/dist/coordinator/coordinator.js.map +1 -1
  23. package/dist/coordinator/types.d.ts +3 -2
  24. package/dist/coordinator/types.d.ts.map +1 -1
  25. package/dist/gate/gate.d.ts +2 -2
  26. package/dist/gate/gate.d.ts.map +1 -1
  27. package/dist/gate/gate.js +4 -4
  28. package/dist/gate/gate.js.map +1 -1
  29. package/dist/guard/provider-guard.js +1 -1
  30. package/dist/guard/provider-guard.js.map +1 -1
  31. package/dist/guard/types.d.ts +11 -4
  32. package/dist/guard/types.d.ts.map +1 -1
  33. package/dist/index.d.ts +11 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/standard-schema.d.ts +2 -0
  37. package/dist/standard-schema.d.ts.map +1 -0
  38. package/dist/standard-schema.js +11 -0
  39. package/dist/standard-schema.js.map +1 -0
  40. package/dist/validation/ajv-validator.d.ts +43 -0
  41. package/dist/validation/ajv-validator.d.ts.map +1 -0
  42. package/dist/validation/ajv-validator.js +63 -0
  43. package/dist/validation/ajv-validator.js.map +1 -0
  44. package/dist/validation/standard-schema-validator.d.ts +41 -0
  45. package/dist/validation/standard-schema-validator.d.ts.map +1 -0
  46. package/dist/validation/standard-schema-validator.js +131 -0
  47. package/dist/validation/standard-schema-validator.js.map +1 -0
  48. package/dist/validation/types.d.ts +63 -0
  49. package/dist/validation/types.d.ts.map +1 -0
  50. package/dist/validation/types.js +131 -0
  51. package/dist/validation/types.js.map +1 -0
  52. package/package.json +21 -4
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @public (Experimental)
3
+ * The result of validating one tool call's arguments. Structurally minimal
4
+ * on purpose: `errors`, when present, are already-formatted human-readable
5
+ * strings, not ecosystem-specific issue objects (Zod issues, Ajv errors,
6
+ * TypeBox errors, ...) - each adapter is responsible for turning its own
7
+ * validator's native error shape into readable text, so nothing downstream
8
+ * needs to know which validation ecosystem produced a given verdict.
9
+ */
10
+ export type ToolValidationResult = {
11
+ readonly valid: true;
12
+ } | {
13
+ readonly valid: false;
14
+ readonly errors?: readonly string[];
15
+ };
16
+ /**
17
+ * @public (Experimental)
18
+ * The validation boundary this package's execution-authority core depends
19
+ * on. It is deliberately not `ajv`, `zod`, `typebox`, `valibot`, or any
20
+ * other specific ecosystem's own type - implement this against whichever
21
+ * validator your project already uses and pass it in; nothing in this
22
+ * package's core needs to know it exists.
23
+ *
24
+ * A validator verdict is one more piece of evidence, never authority on its
25
+ * own: `valid: false` can make a decision `reject`; `valid: true` never by
26
+ * itself makes a decision `execute` - completeness and lifecycle evidence
27
+ * still have to hold too. See `docs/EXECUTION_GATE.md`.
28
+ *
29
+ * Contract:
30
+ * - `validate` runs synchronously and returns a result. It may throw to
31
+ * signal validator misconfiguration (mirroring how a malformed JSON
32
+ * Schema already fails fast at construction rather than mid-stream); the
33
+ * coordinator treats a thrown `validate()` as `valid: false` for that one
34
+ * call rather than letting the exception abort the whole stream - see
35
+ * `docs/VALIDATION.md#a-validator-that-throws`.
36
+ * - `validate` must be deterministic and free of side effects - it is called
37
+ * with the call's already-parsed, already-complete `stableValue`, and its
38
+ * return value must depend only on that input.
39
+ */
40
+ export interface ToolInputValidator {
41
+ validate(value: unknown): ToolValidationResult;
42
+ }
43
+ /**
44
+ * @internal
45
+ * Builds the coordinator's internal per-tool validator map from the two
46
+ * explicit, separate registration options. There is no structural/duck-type
47
+ * discrimination anywhere in this path - `schemas` entries are always
48
+ * compiled as JSON Schema, `validators` entries are always used as-is, and
49
+ * a tool name present in both is a construction-time error rather than a
50
+ * silently-resolved precedence rule. See `docs/VALIDATION.md`. The one
51
+ * exception is refusal, never re-routing: `assertJsonSchemaDocument` rejects
52
+ * objects that are recognizably not JSON Schema before Ajv sees them.
53
+ *
54
+ * Collision detection runs *before* any schema is compiled, so a colliding
55
+ * tool name is always reported as a collision - deterministically, even
56
+ * when that same tool's `schemas` entry also happens to be malformed JSON
57
+ * Schema (or one of the objects `assertJsonSchemaDocument` refuses).
58
+ * Without this ordering, whichever check happened to run first would decide
59
+ * which error the caller sees, for reasons unrelated to which problem is
60
+ * actually more fundamental.
61
+ */
62
+ export declare function buildValidatorMap(schemas: Record<string, object> | undefined, validators: Record<string, ToolInputValidator> | undefined): Map<string, ToolInputValidator>;
63
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/validation/types.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GACxB;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAAC;CAChD;AA2ED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC3C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,SAAS,GACzD,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CA+CjC"}
@@ -0,0 +1,131 @@
1
+ import { buildSharedAjvValidators } from "./ajv-validator.js";
2
+ /**
3
+ * @internal
4
+ * Runtime shape check for a `validators[toolName]` entry. `ToolInputValidator`
5
+ * is a TypeScript interface, not a runtime guarantee - a plain-JavaScript
6
+ * caller (or a `as never`/`as ToolInputValidator` cast) can hand this
7
+ * anything. An explicitly *registered* malformed validator must never be
8
+ * silently equivalent to "no validator registered": `coordinator.ts`'s
9
+ * `if (validator) { ... }` truthiness check would otherwise skip a `null`/
10
+ * `undefined`/`false`/`0`/`""` entry entirely, letting the call execute
11
+ * with zero diagnostic - the same class of fail-open gap already closed for
12
+ * a validator that returns a malformed *result* (see
13
+ * `docs/VALIDATION.md#a-validator-that-returns-a-malformed-result`). This
14
+ * check exists so that gap cannot reopen one level up, at registration time.
15
+ */
16
+ function isToolInputValidatorShaped(value) {
17
+ return typeof value === "object" && value !== null && typeof value.validate === "function";
18
+ }
19
+ function describeInvalidValidator(value) {
20
+ if (typeof value === "object") {
21
+ try {
22
+ return JSON.stringify(value);
23
+ }
24
+ catch {
25
+ return Object.prototype.toString.call(value);
26
+ }
27
+ }
28
+ return `${typeof value} ${String(value)}`;
29
+ }
30
+ // Registry key the Vercel AI SDK stamps on the objects `jsonSchema()` and
31
+ // `zodSchema()` return (the same global symbol across ai@5, @6 and @7).
32
+ const AI_SDK_SCHEMA_MARKER = Symbol.for("vercel.ai.schema");
33
+ /**
34
+ * @internal
35
+ * Rejects two schema-library objects that are routinely mistaken for JSON
36
+ * Schema documents. This is not structural discrimination - nothing is
37
+ * converted or re-routed; a recognized non-JSON-Schema object is refused, and
38
+ * everything else is still compiled as JSON Schema exactly as before.
39
+ *
40
+ * - An AI SDK `jsonSchema()`/`zodSchema()` wrapper keeps its schema behind a
41
+ * `jsonSchema` getter, and Ajv (`strict: false`) ignores every field it
42
+ * does not recognize, so the wrapper compiles to an accept-everything
43
+ * validator: every call would pass validation. That is the fail-open case
44
+ * this check exists for.
45
+ * - A Standard Schema object (Zod, Valibot, ArkType, ...) either crashes Ajv
46
+ * with an unrelated-looking meta-schema error or, depending on its own
47
+ * fields, compiles to something that validates the wrong thing.
48
+ *
49
+ * Plain JSON Schema documents, including TypeBox schemas (plain objects that
50
+ * only add symbol keys), carry neither marker and are unaffected.
51
+ */
52
+ function assertJsonSchemaDocument(toolName, schema) {
53
+ // Functions too: an ArkType schema is a callable Standard Schema.
54
+ if ((typeof schema !== "object" && typeof schema !== "function") || schema === null)
55
+ return;
56
+ const name = JSON.stringify(toolName);
57
+ if (schema[AI_SDK_SCHEMA_MARKER]) {
58
+ throw new Error(`prefix-safe-json: schemas[${name}] is an AI SDK jsonSchema()/zodSchema() wrapper, not a JSON Schema ` +
59
+ `document - compiled as-is it would validate nothing. Register the raw schema it wraps instead, ` +
60
+ `e.g. schemas: { ${name}: wrapper.jsonSchema }.`);
61
+ }
62
+ const standard = schema["~standard"];
63
+ if (typeof standard === "object" && standard !== null && typeof standard.validate === "function") {
64
+ throw new Error(`prefix-safe-json: schemas[${name}] is a Standard Schema (Zod, Valibot, ArkType, ...), not a JSON Schema ` +
65
+ `document. Register it through "validators" instead: validators: { ${name}: fromStandardSchema(schema) }, ` +
66
+ `with fromStandardSchema imported from "prefix-safe-json/standard-schema".`);
67
+ }
68
+ }
69
+ /**
70
+ * @internal
71
+ * Builds the coordinator's internal per-tool validator map from the two
72
+ * explicit, separate registration options. There is no structural/duck-type
73
+ * discrimination anywhere in this path - `schemas` entries are always
74
+ * compiled as JSON Schema, `validators` entries are always used as-is, and
75
+ * a tool name present in both is a construction-time error rather than a
76
+ * silently-resolved precedence rule. See `docs/VALIDATION.md`. The one
77
+ * exception is refusal, never re-routing: `assertJsonSchemaDocument` rejects
78
+ * objects that are recognizably not JSON Schema before Ajv sees them.
79
+ *
80
+ * Collision detection runs *before* any schema is compiled, so a colliding
81
+ * tool name is always reported as a collision - deterministically, even
82
+ * when that same tool's `schemas` entry also happens to be malformed JSON
83
+ * Schema (or one of the objects `assertJsonSchemaDocument` refuses).
84
+ * Without this ordering, whichever check happened to run first would decide
85
+ * which error the caller sees, for reasons unrelated to which problem is
86
+ * actually more fundamental.
87
+ */
88
+ export function buildValidatorMap(schemas, validators) {
89
+ if (schemas && validators) {
90
+ for (const toolName of Object.keys(validators)) {
91
+ if (Object.prototype.hasOwnProperty.call(schemas, toolName)) {
92
+ throw new Error(`prefix-safe-json: tool ${JSON.stringify(toolName)} is registered in both "schemas" and ` +
93
+ `"validators" - a tool's validation must come from exactly one source. Remove one registration.`);
94
+ }
95
+ }
96
+ }
97
+ const map = new Map();
98
+ if (schemas) {
99
+ // Read every entry exactly once, then use that one snapshot for both the
100
+ // refusal check and compilation. If a `schemas` value were an accessor,
101
+ // iterating `schemas` twice (once here, once inside
102
+ // buildSharedAjvValidators) could let a getter return a plain schema on
103
+ // the checked pass and an AI SDK wrapper on the compiled pass, so the
104
+ // wrapper reaches Ajv unchecked and compiles to accept-everything -
105
+ // exactly what assertJsonSchemaDocument exists to stop.
106
+ const schemaEntries = Object.entries(schemas);
107
+ for (const [toolName, schema] of schemaEntries) {
108
+ assertJsonSchemaDocument(toolName, schema);
109
+ }
110
+ const materializedSchemas = Object.fromEntries(schemaEntries);
111
+ // One shared Ajv instance for every schema in THIS `schemas` object -
112
+ // pre-0.5 behavior, restored here after 0.5's per-schema
113
+ // createAjvValidator() calls regressed it (a schema could no longer
114
+ // $ref another schema registered in the same construction - see
115
+ // buildSharedAjvValidators's own doc comment for the direct proof).
116
+ for (const [toolName, validator] of buildSharedAjvValidators(materializedSchemas)) {
117
+ map.set(toolName, validator);
118
+ }
119
+ }
120
+ if (validators) {
121
+ for (const [toolName, validator] of Object.entries(validators)) {
122
+ if (!isToolInputValidatorShaped(validator)) {
123
+ throw new Error(`prefix-safe-json: validators[${JSON.stringify(toolName)}] is not a valid ToolInputValidator - ` +
124
+ `expected an object with a "validate" function, received: ${describeInvalidValidator(validator)}`);
125
+ }
126
+ map.set(toolName, validator);
127
+ }
128
+ }
129
+ return map;
130
+ }
131
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/validation/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prefix-safe-json",
3
- "version": "0.4.5",
3
+ "version": "0.5.1",
4
4
  "description": "Fail-closed execution integrity for streamed LLM tool calls — reject truncated or unconfirmed arguments before side effects",
5
5
  "keywords": [
6
6
  "json",
@@ -12,7 +12,7 @@
12
12
  "tool-execution",
13
13
  "execution-safety",
14
14
  "function-calling",
15
- "json-repair",
15
+ "vercel-ai-sdk",
16
16
  "json-parser"
17
17
  ],
18
18
  "license": "MIT OR Apache-2.0",
@@ -30,7 +30,23 @@
30
30
  "exports": {
31
31
  ".": {
32
32
  "types": "./dist/index.d.ts",
33
- "import": "./dist/index.js"
33
+ "import": "./dist/index.js",
34
+ "default": "./dist/index.js"
35
+ },
36
+ "./ajv": {
37
+ "types": "./dist/ajv.d.ts",
38
+ "import": "./dist/ajv.js",
39
+ "default": "./dist/ajv.js"
40
+ },
41
+ "./standard-schema": {
42
+ "types": "./dist/standard-schema.d.ts",
43
+ "import": "./dist/standard-schema.js",
44
+ "default": "./dist/standard-schema.js"
45
+ },
46
+ "./conformance": {
47
+ "types": "./dist/conformance.d.ts",
48
+ "import": "./dist/conformance.js",
49
+ "default": "./dist/conformance.js"
34
50
  }
35
51
  },
36
52
  "files": [
@@ -98,7 +114,8 @@
98
114
  "tsx": "^4.16.0",
99
115
  "typescript": "^5.5.0",
100
116
  "typescript-eslint": "^8.0.0",
101
- "vitest": "^3.2.6"
117
+ "vitest": "^3.2.6",
118
+ "zod": "4.4.3"
102
119
  },
103
120
  "pnpm": {
104
121
  "overrides": {