prefix-safe-json 0.5.0 → 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.
package/README.md CHANGED
@@ -4,334 +4,206 @@
4
4
  [![CI](https://github.com/canblmz1/prefix-safe-json/actions/workflows/ci.yml/badge.svg)](https://github.com/canblmz1/prefix-safe-json/actions/workflows/ci.yml)
5
5
  [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)
6
6
 
7
- **Fail-closed execution integrity for streamed LLM tool calls.**
7
+ An incremental JSON parser and execution gate for **streamed LLM tool calls**.
8
8
 
9
- Prevent truncated, conflicting, or unconfirmed streamed tool arguments
10
- from reaching side effects. `prefix-safe-json` distinguishes complete,
11
- unfabricated raw arguments from truncated or unconfirmed input — including
12
- SDK-projected or repaired values that merely *look* complete. **JSON
13
- validity is not execution authority.**
9
+ `prefix-safe-json` keeps incomplete, conflicting, or unconfirmed streamed arguments from being treated as final tool input. It is designed for callers that need to distinguish:
10
+
11
+ 1. JSON that can be parsed right now,
12
+ 2. values that are stable enough to expose, and
13
+ 3. arguments that the provider actually completed and confirmed.
14
+
15
+ It does not execute tools itself.
14
16
 
15
17
  ```bash
16
- pnpm add prefix-safe-json ai
17
- # or: npm install prefix-safe-json ai
18
+ pnpm add prefix-safe-json ai zod
19
+ # or: npm install prefix-safe-json ai zod
18
20
  ```
19
21
 
20
- ## 30-line safe execution example
22
+ ## Safe execution example
21
23
 
22
24
  ```javascript
25
+ import { writeFile } from "node:fs/promises";
23
26
  import { streamText } from "ai";
24
- import { createAiSdkExecutionGuard, createAiSdkExecutionLock } from "prefix-safe-json";
27
+ import { z } from "zod";
28
+ import {
29
+ createAiSdkExecutionGuard,
30
+ createAiSdkExecutionLock,
31
+ } from "prefix-safe-json";
32
+ import { fromStandardSchema } from "prefix-safe-json/standard-schema";
33
+
34
+ const writeFileSchema = z.object({ path: z.string(), content: z.string() });
35
+
36
+ const tools = createAiSdkExecutionLock({
37
+ write_file: {
38
+ description: "Write a UTF-8 text file",
39
+ inputSchema: writeFileSchema,
40
+ },
41
+ });
25
42
 
26
- // 1. Lock tool definitions before the AI SDK sees them - removes every
27
- // SDK-invoked callback that could run your code before this guard does.
28
- const lockedTools = createAiSdkExecutionLock({
29
- write_file: { description: "Write a UTF-8 text file", inputSchema: writeFileSchema },
43
+ const guard = createAiSdkExecutionGuard({
44
+ validators: { write_file: fromStandardSchema(writeFileSchema) },
30
45
  });
31
46
 
32
- // 2. Stream, feeding the real fullStream into the guard.
33
- const result = streamText({ model, prompt, tools: lockedTools });
34
- const guard = createAiSdkExecutionGuard({ schemas: { write_file: writeFileSchema } });
47
+ const result = streamText({ model, prompt, tools });
35
48
  for await (const part of result.fullStream) {
36
49
  guard.push(part);
37
50
  }
38
51
 
39
- // 3. finish() signals the stream ended. Every decision is replayable
40
- // diagnostic state - it does not perform anything by itself.
41
- const final = guard.finish();
42
-
43
- // 4. The caller owns dispatch: take each call's authority exactly once,
44
- // then run the real, irreversible side effect yourself.
45
- for (const observed of final.decisions) {
46
- const authority = guard.takeDecision(observed.internalId);
47
- if (authority) await writeFile(authority.value.path, authority.value.content);
52
+ const { decisions, diagnostics } = guard.finish();
53
+ for (const decision of decisions) {
54
+ const authority = guard.takeDecision(decision.internalId);
55
+ if (authority) {
56
+ await writeFile(authority.value.path, authority.value.content);
57
+ } else {
58
+ console.warn(`skipped ${decision.name}: ${decision.action} (${decision.reason})`);
59
+ }
60
+ }
61
+ if (decisions.length === 0 && diagnostics.length > 0) {
62
+ console.warn("no tool call was executed:", diagnostics);
48
63
  }
49
64
  ```
50
65
 
51
- `prefix-safe-json` never calls `writeFile` (or anything like it) itself.
52
- The last line above is the only place a side effect happens, and it's
53
- entirely yours. See [`examples/`](examples/) for full, runnable
54
- demonstrations against real provider/SDK shapes, no mocked internals.
55
-
56
- ## Why this exists
57
-
58
- LLM providers stream tool-call arguments as small JSON chunks. During
59
- streaming, the JSON is frequently incomplete: strings split mid-word,
60
- UTF-8 characters split mid-byte, containers left unclosed, numbers
61
- terminated at chunk boundaries — and even once the JSON *looks* complete,
62
- nothing about its shape says whether the provider actually confirmed the
63
- call is done, or just stopped for an unrelated reason (a token limit, an
64
- error, a content filter) partway through.
65
-
66
- > **The core invariant: parse completion is not execution authority.**
67
-
68
- Structural validity, confirmed completeness, and execution authority are
69
- three separate questions. Most streaming JSON helpers — including some
70
- already used for LLM tool-call arguments in the wild — are built to show
71
- *something* as early as possible, even if that means guessing. Given a
72
- stream that has so far delivered `{"city":"Tok` (the model is still
73
- typing "Tokyo"):
74
-
75
- | | `city` field after this chunk |
76
- |---|---|
77
- | [vercel/ai's `fixJson`](https://github.com/vercel/ai/blob/main/packages/ai/src/util/fix-json.ts) | `"Tok"` — closes the open string as-is, reports a successful parse |
78
- | [langchain's `parsePartialJson`](https://github.com/langchain-ai/langchainjs/blob/main/libs/langchain-core/src/utils/json.ts) | `"Tok"` — returns whatever string content it collected |
79
- | **`prefix-safe-json`** (`snapshot().stableValue`) | *(absent — `city` is not committed yet)* |
80
-
81
- Verified by cloning both projects and running their actual code
82
- side-by-side with this library. Neither is a bug — they're built for
83
- progressively rendering a value in a UI, where showing `"Tok"` then
84
- `"Tokyo"` a moment later is good UX. `prefix-safe-json` answers a
85
- different question: *is it safe to act on this value yet* — where
86
- treating `"Tok"` as the city would be wrong. If you need live
87
- "filling in..." UI text, a partial-JSON renderer is the right tool. If
88
- you need to know precisely when it's safe to execute a tool call with the
89
- parsed arguments, that's what this library is for.
90
-
91
- ## What `execute` / `retry` / `reject` mean
92
-
93
- Every tool call gets exactly one verdict:
94
-
95
- - **`execute`** — complete, unfabricated, and (if a schema/validator is
96
- registered) valid. `decision.value` is safe to pass to your tool.
97
- - **`retry`** — nothing is wrong with what arrived; there just isn't a
98
- trustworthy complete value yet. Continue generation.
99
- - **`reject`** — the data itself is the problem (malformed JSON, a schema
100
- mismatch, a resource limit, a provider error, a content-policy
101
- termination). Retrying the same input won't help.
102
-
103
- See [`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md) for the full
104
- decision table and every `reason` value.
105
-
106
- ## Failure cases
107
-
108
- The risk above isn't hypothetical. [Cline](https://github.com/cline/cline)
109
- (a coding agent that writes files and runs terminal commands from LLM tool
110
- calls — verified by cloning it, commit `81cce3d70e1`) wires a JSON-repair
111
- step directly into tool execution: whatever
112
- [`repairMalformedToolCall`](https://github.com/cline/cline/blob/81cce3d70e10244cdde40dbd0eb0bb711c93006d/sdk/packages/llms/src/providers/ai-sdk.ts#L1332)
113
- returns *is* the tool call that runs, and its repair path closes an
114
- unterminated string as-is, same as the table above.
115
-
116
- Feeding both implementations the same truncated `write_file` call — a
117
- `content` argument cut off mid-value:
66
+ `model` and `prompt` are yours. The lock stops the SDK from running `write_file` itself; the guard checks each streamed call against the same schema and hands out a one-shot decision. The caller owns dispatch, and a call the guard doesn't hand out never runs.
118
67
 
119
- ```text
120
- input: {"path":"config/database.yml","content":"production:\n host: db.prod.internal\n password: correct-horse-battery-sta
68
+ Using `jsonSchema()` from `ai` instead of Zod? Give the guard the raw schema inside it: `schemas: { write_file: writeFileSchema.jsonSchema }`. The wrapper itself is refused at construction, because it would otherwise validate nothing.
69
+
70
+ ## Why parse success is not enough
121
71
 
122
- Cline's parseJsonStream() -> jsonrepair:
123
- { "path": "config/database.yml",
124
- "content": "production:\n host: db.prod.internal\n password: correct-horse-battery-sta" }
125
- No error, no warning. This is what gets written to disk.
72
+ Tool-call arguments often arrive as partial JSON. A renderer may reasonably repair or close an unfinished prefix so it can show progressive UI. That is useful for display, but it answers a different question from whether a side effect should run.
126
73
 
127
- prefix-safe-json:
128
- outcome: "truncated", executable: false
129
- stableValue: { "path": "config/database.yml" } // content is absent, not guessed
74
+ Given the prefix:
75
+
76
+ ```text
77
+ {"city":"Tok
130
78
  ```
131
79
 
132
- This is one instance of a class of defect independently found across
133
- several unrelated agent runtimes — see
134
- [`docs/REAL_WORLD_FAILURES.md`](docs/REAL_WORLD_FAILURES.md) for the full,
135
- strictly-classified matrix (which reports are upstream-fixed, which are
136
- still open, which are internal reproductions only) and
137
- [`docs/CASE_STUDY_SANDBASE.md`](docs/CASE_STUDY_SANDBASE.md) for how one
138
- independently maintained runtime closed this exact gap in production.
139
-
140
- ## Supported boundaries
141
-
142
- `prefix-safe-json` owns **tool-call execution integrity** — raw argument
143
- evidence, identity correlation, lifecycle completeness, truncation
144
- detection, conflicting evidence, and validator-verdict composition, up
145
- through a one-shot execution-authority decision. It does **not** own tool
146
- permission, application authorization, human approval, sandboxing,
147
- application idempotency, distributed exactly-once semantics, or prompt
148
- injection defense — see
149
- [`docs/PRODUCT_POSITIONING.md`](docs/PRODUCT_POSITIONING.md) for the full
150
- owns/does-not-own boundary and
151
- [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md) for the complete guarantee/
152
- non-guarantee list, including known internal limitations.
153
-
154
- Concretely, it does **not**:
155
-
156
- - defend against prompt injection (it says nothing about whether the
157
- arguments a model *chose* to send are the arguments it *should* have
158
- sent — only whether they're complete and unfabricated)
159
- - resolve authorization (it doesn't know which caller may invoke which
160
- tool)
161
- - sandbox execution (`action: "execute"` means "this is genuinely the
162
- complete value the model produced," not "safe to run without your own
163
- validation and permissions")
164
- - detect a malicious or wrong tool choice (a model calling the wrong tool
165
- with complete, valid arguments is still reported `execute`)
166
-
167
- ## AI SDK integration
168
-
169
- The 30-line example above covers the common case. Three integration
170
- patterns exist, strongest first:
171
-
172
- - **Strongest — `createAiSdkExecutionLock()`.** Wrap your tool
173
- definitions with it before passing them to `streamText()`/
174
- `generateText()`. It removes every AI SDK-invoked callback capable of
175
- running your code before this library's decision — not just `execute`,
176
- but also `onInputStart`, `onInputDelta`, and `onInputAvailable`
177
- (verified directly against `ai@5`/`ai@6`/`ai@7`'s own source: all three
178
- fire unconditionally, independent of `needsApproval`). It also forces
179
- `needsApproval: true` and rejects any provider-tool shape whose real
180
- execution location it cannot verify. Still dispatch manually from
181
- `guard.takeDecision(observed.internalId)` — this only closes the door
182
- on the SDK (or your own callbacks) doing it *for* you.
183
- - **Safe, supported pattern (all majors, including `ai@5`)** — define
184
- your tools *without* an AI SDK-native `execute` callback at all. Not
185
- even a no-op one. Consume `fullStream` yourself and dispatch manually.
186
- `createAiSdkExecutionLock()` gets you this automatically.
187
- - **Unprotected / misuse pattern** — attach the real side effect directly
188
- as `execute`/`onInputStart`/`onInputDelta`/`onInputAvailable` on a tool
189
- definition that bypasses the lock. If that happens, the side effect has
190
- already run before this library ever reaches a decision; nothing here
191
- can retroactively undo it.
192
-
193
- The guard does defend against the misuse pattern in one specific way: a
194
- `tool-result`/`tool-error` part on `fullStream` is direct evidence the
195
- SDK's own loop already invoked *some* call's `execute`, and once observed
196
- this library never again reports `action: "execute"` for the affected
197
- call(s) (`reason: "sdk_execution_observed"` — the highest-priority
198
- rejection reason of any). There is no equivalent detection for a bypassed
199
- `onInputStart`/`onInputDelta`/`onInputAvailable` — those callbacks
200
- produce no observable evidence of having run at all. Full behavior, the
201
- exact test matrix, and every verified `ai@5`/`ai@6`/`ai@7` behavioral
202
- difference:
203
- [`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md#execution-ownership-tool-resulttool-error-as-evidence).
204
-
205
- `AiSdkStreamAdapter`/`createAiSdkExecutionGuard()` target the public
206
- `streamText()`/`generateText()` `fullStream` surface (`finishReason` as a
207
- plain unified string), not the lower-level `@ai-sdk/provider`
208
- `doStream()` boundary.
80
+ some partial-JSON helpers can expose `"Tok"` immediately. `prefix-safe-json` keeps that field uncommitted until the stream provides enough evidence that the value is complete.
81
+
82
+ The core distinction is simple:
83
+
84
+ > **Parse completion is not execution authority.**
85
+
86
+ See [`docs/REAL_WORLD_FAILURES.md`](docs/REAL_WORLD_FAILURES.md) for independently reproduced examples and [`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md) for the decision model.
87
+
88
+ ## Decisions
89
+
90
+ Every observed tool call ends in one of three actions:
91
+
92
+ - **`execute`** — arguments are complete and, when configured, validator/schema checks pass.
93
+ - **`retry`** — no trustworthy complete value exists yet.
94
+ - **`reject`** — the stream contains malformed, conflicting, invalid, or terminally unsafe evidence.
95
+
96
+ The full reason matrix lives in [`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md).
97
+
98
+ ## Scope and non-goals
99
+
100
+ The library covers streamed argument evidence, call identity, lifecycle completion, truncation detection, conflicting evidence, validation composition, and one-shot execution decisions.
101
+
102
+ It does **not** provide:
103
+
104
+ - application authorization,
105
+ - human approval workflows,
106
+ - sandboxing,
107
+ - prompt-injection defense,
108
+ - distributed exactly-once semantics,
109
+ - a guarantee that the model selected the correct tool.
110
+
111
+ An `execute` decision means the tool arguments were observed as complete under the configured boundary. Your application still owns permissions, policy, validation, and the side effect itself.
112
+
113
+ See [`docs/PRODUCT_POSITIONING.md`](docs/PRODUCT_POSITIONING.md) and [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md) for the detailed boundary.
114
+
115
+ ## Vercel AI SDK integration
116
+
117
+ For `streamText()` / `generateText()`, the recommended pattern is:
118
+
119
+ 1. wrap tool definitions with `createAiSdkExecutionLock()`,
120
+ 2. consume `fullStream`,
121
+ 3. feed stream parts into `createAiSdkExecutionGuard()`, and
122
+ 4. dispatch manually only from `takeDecision()`.
123
+
124
+ `createAiSdkExecutionLock()` removes SDK-invoked callbacks that could execute application code before the guard reaches a final decision. A tool definition that bypasses that lock and performs side effects directly is outside the protection boundary.
125
+
126
+ The adapter targets the public AI SDK `fullStream` surface, not the lower-level `@ai-sdk/provider` `doStream()` boundary.
127
+
128
+ Detailed lifecycle behavior and version-specific tests are documented in [`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md).
209
129
 
210
130
  ## Provider adapters
211
131
 
212
- Every provider below has a public low-level adapter that composes with
213
- `createToolCallExecutionGate()` the same way the high-level AI SDK guard
214
- does internally:
132
+ Low-level adapters are included for:
215
133
 
216
- **OpenAI** (legacy `function_call` and Responses API), **Anthropic**,
217
- **Gemini**, **OpenRouter**, generic **OpenAI-compatible** endpoints, and
218
- the **Vercel AI SDK**.
134
+ - OpenAI legacy `function_call`,
135
+ - OpenAI Responses API,
136
+ - Anthropic,
137
+ - Gemini,
138
+ - OpenRouter,
139
+ - generic OpenAI-compatible endpoints,
140
+ - Vercel AI SDK.
219
141
 
220
142
  ```typescript
221
- import { createToolCallExecutionGate, OpenAIStreamAdapter } from "prefix-safe-json";
143
+ import {
144
+ createToolCallExecutionGate,
145
+ OpenAIStreamAdapter,
146
+ } from "prefix-safe-json";
222
147
 
223
148
  const adapter = new OpenAIStreamAdapter();
224
149
  const gate = createToolCallExecutionGate();
150
+
225
151
  for (const rawChunk of stream) {
226
- for (const event of adapter.push(rawChunk)) gate.push(event);
152
+ for (const event of adapter.push(rawChunk)) {
153
+ gate.push(event);
154
+ }
227
155
  }
228
- for (const event of adapter.finish({ reason: "complete" })) gate.push(event);
156
+
157
+ for (const event of adapter.finish({ reason: "complete" })) {
158
+ gate.push(event);
159
+ }
160
+
229
161
  const { decisions } = gate.finish();
230
162
  ```
231
163
 
232
- Notes: Gemini's adapter exposes its structured argument projection for
233
- inspection and validation, but never grants strict execute authority —
234
- Gemini does not provide raw argument text at this seam. OpenAI-compatible
235
- and OpenRouter events require a non-negative integer `choice.index`;
236
- missing, invalid, or duplicate choice identity fails closed instead of
237
- guessing zero. Full semantics, the decision table, and provider
238
- finish-reason mapping: [`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md).
164
+ Gemini exposes structured argument projections at this seam rather than raw argument text, so its adapter does not grant the same strict raw-text execution authority. Provider-specific semantics are documented in [`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md).
165
+
166
+ ## Validation
239
167
 
240
- ### Bring your own validator
168
+ Validation is optional and validator-agnostic. You can use JSON Schema/Ajv, Standard Schema-compatible validators, Zod, TypeBox, Valibot, or a custom validator.
241
169
 
242
- Schema/validator checking is optional and validator-agnostic — plug in
243
- Zod, TypeBox, Valibot, a Standard Schema-compliant validator, or a
244
- hand-written check via the separate `validators` option, alongside or
245
- instead of a raw JSON Schema object via the pre-existing `toolSchemas`/
246
- `schemas` option (statically compiled through Ajv):
170
+ `schemas` takes plain JSON Schema documents (TypeBox schemas count). Zod and other Standard Schema libraries go through `validators` with `fromStandardSchema()` from `prefix-safe-json/standard-schema`. Handing `schemas` a Zod schema or an AI SDK `jsonSchema()` wrapper throws at construction instead of silently validating nothing.
247
171
 
248
172
  ```typescript
249
173
  const gate = createToolCallExecutionGate(undefined, undefined, undefined, {
250
- write_file: { validate: (v) => WriteFileSchema.safeParse(v).success ? { valid: true } : { valid: false } },
174
+ write_file: {
175
+ validate: (value) =>
176
+ WriteFileSchema.safeParse(value).success
177
+ ? { valid: true }
178
+ : { valid: false },
179
+ },
251
180
  });
252
181
  ```
253
182
 
254
- A tool name may be registered in `toolSchemas`/`schemas` or in `validators`,
255
- never both — registering both throws at construction time. No forced
256
- dependency on any specific validation ecosystem. See
257
- [`docs/VALIDATION.md`](docs/VALIDATION.md).
258
-
259
- ## Conformance
260
-
261
- [`conformance/`](conformance/) packages this project's failure corpus as
262
- a portable, provider-neutral fixture format and a small deterministic
263
- runner (`prefix-safe-json/conformance`) — useful even to a project that
264
- never installs this package at runtime, since the fixture format and
265
- expected outcomes are meaningful against any implementation of this
266
- problem class. `prefix-safe-json` is the reference implementation and
267
- dogfoods this exact runner in its own test suite. See
268
- [`docs/CONFORMANCE.md`](docs/CONFORMANCE.md).
269
-
270
- ## Threat model / non-goals
271
-
272
- `prefix-safe-json` is honestly scoped as **execution integrity**, not a
273
- general "AI security platform." The complete guarantee/non-guarantee
274
- list, trust boundaries, and known internal limitations live in
275
- [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md); the product-level
276
- owns/does-not-own boundary this is drawn from is
277
- [`docs/PRODUCT_POSITIONING.md`](docs/PRODUCT_POSITIONING.md).
278
-
279
- ## Supply-chain verification
280
-
281
- Verify releases independently rather than relying on publisher claims.
282
- Published artifacts have npm provenance from this repository's GitHub
283
- Actions publish workflow, and a tarball can be rebuilt and compared
284
- against its release tag with `npm run verify:published-release -- <version>`
285
- from a clone with tags fetched. See the command-driven
286
- [maintainer audit](docs/MAINTAINER_AUDIT.md), the exact
287
- [release/hash mapping](docs/RELEASE_INTEGRITY.md), the
288
- [runtime dependency graph](docs/RUNTIME_DEPENDENCIES.md), and the
289
- [execution-critical source map](docs/EXECUTION_AUDIT_SURFACE.md).
183
+ See [`docs/VALIDATION.md`](docs/VALIDATION.md).
290
184
 
291
- ## Compatibility
292
-
293
- ESM only (`import`, not `require`), Node `>=18.0.0`. Node 18/20 are
294
- end-of-life and no longer receive security patches; this describes the
295
- package's runtime requirement, not a recommendation to operate an
296
- unpatched Node release. Repository development and release jobs run on
297
- newer Node because the toolchain needs it — that requirement does not
298
- apply to the published package's own runtime dependency graph.
299
-
300
- **Vercel AI SDK**, verified directly against each exact pinned major via
301
- a real `streamText()` lifecycle proof (no API key, no network request, no
302
- paid model call — `pnpm run example:ai-sdk-lifecycle-proof`, run in CI on
303
- every push):
185
+ ## Conformance corpus
304
186
 
305
- | Major | Status |
306
- | --- | --- |
307
- | v5 | verified |
308
- | v6 | verified |
309
- | v7 | verified |
187
+ [`conformance/`](conformance/) contains a provider-neutral fixture format and deterministic runner for this problem class. Projects can use the fixtures without adopting this package as their runtime implementation.
310
188
 
311
- Not a claim that every version within a major is tested. Full
312
- per-provider compatibility matrix, exact AI SDK pin requirements, and the
313
- runtime-vs-toolchain distinction:
314
- [`docs/COMPATIBILITY.md`](docs/COMPATIBILITY.md).
189
+ See [`docs/CONFORMANCE.md`](docs/CONFORMANCE.md).
315
190
 
316
- ## Development / audit
191
+ ## Compatibility
317
192
 
318
- ### Current status
193
+ - ESM only. CommonJS code can `require()` it on Node 20.19+ / 22.12+ (Node's `require(esm)`); older runtimes need `import()`.
194
+ - Runtime: Node `>=18.0.0`.
195
+ - Repository development/release tooling uses newer Node versions.
196
+ - CI exercises pinned Vercel AI SDK v5, v6, and v7 integration paths.
319
197
 
320
- **Implemented:** incremental UTF-8 decoder, 17-state lexical JSON state
321
- machine, grammar stack, semantic event emission, duplicate-key rejection,
322
- `push()`/`snapshot()`/`drainEvents()`/`finish()`, configurable resource
323
- limits, a machine-readable test corpus plus the public
324
- [Tool Call Integrity conformance corpus](conformance/), provider stream
325
- adapters (OpenAI legacy + Responses, Anthropic, Gemini, OpenRouter,
326
- OpenAI-compatible, Vercel AI SDK) with a coordinator for concurrent
327
- tool-call streams, validator-agnostic per-tool validation (see
328
- [`docs/VALIDATION.md`](docs/VALIDATION.md)), `createToolCallExecutionGate()`,
329
- `createAiSdkExecutionGuard()`, call-scoped one-shot authority via
330
- `takeDecision(internalId)`, and SDK execution-ownership detection.
198
+ | AI SDK major | CI integration check |
199
+ | --- | --- |
200
+ | v5 | yes |
201
+ | v6 | yes |
202
+ | v7 | yes |
331
203
 
332
- **Not yet implemented:** a CLI tool, network/SSE client integration.
204
+ This is not a claim that every patch release inside each major is tested. Exact pins and provider notes live in [`docs/COMPATIBILITY.md`](docs/COMPATIBILITY.md).
333
205
 
334
- ### Low-level parser API
206
+ ## Low-level parser API
335
207
 
336
208
  ```typescript
337
209
  import { createParser } from "prefix-safe-json";
@@ -339,20 +211,40 @@ import { createParser } from "prefix-safe-json";
339
211
  const parser = createParser();
340
212
  parser.push('{"tool":"calc",');
341
213
  parser.push('"args":{"x":42}}');
214
+
342
215
  console.log(parser.snapshot().stableValue);
216
+
343
217
  const result = parser.finish({ reason: "complete" });
344
- console.log(result.executable); // true if safe to execute
218
+ console.log(result.executable);
345
219
  ```
346
220
 
347
- Runnable, CI-checked, real-API examples (no mocked internals):
348
- [`examples/anthropic-truncation-safety.mjs`](examples/anthropic-truncation-safety.mjs),
349
- [`examples/ai-sdk-execution-gate.mjs`](examples/ai-sdk-execution-gate.mjs),
350
- [`examples/ai-sdk-lifecycle-proof.mjs`](examples/ai-sdk-lifecycle-proof.mjs).
221
+ ## Development
222
+
223
+ Implemented components include the incremental UTF-8 decoder, lexical/grammar state tracking, semantic events, duplicate-key rejection, configurable resource limits, provider adapters, concurrent call coordination, validation hooks, and execution-gate APIs.
224
+
225
+ Not currently provided: a standalone CLI or network/SSE client.
226
+
227
+ Runnable examples:
228
+
229
+ - [`examples/anthropic-truncation-safety.mjs`](examples/anthropic-truncation-safety.mjs)
230
+ - [`examples/ai-sdk-execution-gate.mjs`](examples/ai-sdk-execution-gate.mjs)
231
+ - [`examples/ai-sdk-lifecycle-proof.mjs`](examples/ai-sdk-lifecycle-proof.mjs)
232
+
233
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the development workflow.
234
+
235
+ ## Release verification
236
+
237
+ Published packages use npm provenance from this repository's GitHub Actions release workflow. A release tarball can also be rebuilt and compared with its tag using:
238
+
239
+ ```bash
240
+ npm run verify:published-release -- <version>
241
+ ```
351
242
 
352
- ### Contributing
243
+ Additional release and dependency documentation is available in:
353
244
 
354
- See [`CONTRIBUTING.md`](CONTRIBUTING.md) for how to run tests, add a
355
- provider fixture or conformance case, and propose adapter support.
245
+ - [`docs/MAINTAINER_AUDIT.md`](docs/MAINTAINER_AUDIT.md)
246
+ - [`docs/RELEASE_INTEGRITY.md`](docs/RELEASE_INTEGRITY.md)
247
+ - [`docs/RUNTIME_DEPENDENCIES.md`](docs/RUNTIME_DEPENDENCIES.md)
356
248
 
357
249
  ## License
358
250
 
@@ -47,14 +47,17 @@ export interface ToolInputValidator {
47
47
  * discrimination anywhere in this path - `schemas` entries are always
48
48
  * compiled as JSON Schema, `validators` entries are always used as-is, and
49
49
  * a tool name present in both is a construction-time error rather than a
50
- * silently-resolved precedence rule. See `docs/VALIDATION.md`.
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.
51
53
  *
52
54
  * Collision detection runs *before* any schema is compiled, so a colliding
53
55
  * tool name is always reported as a collision - deterministically, even
54
56
  * when that same tool's `schemas` entry also happens to be malformed JSON
55
- * Schema. Without this ordering, whichever check happened to run first
56
- * would decide which error the caller sees, for reasons unrelated to which
57
- * problem is actually more fundamental.
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.
58
61
  */
59
62
  export declare function buildValidatorMap(schemas: Record<string, object> | undefined, validators: Record<string, ToolInputValidator> | undefined): Map<string, ToolInputValidator>;
60
63
  //# sourceMappingURL=types.d.ts.map
@@ -1 +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;AA+BD;;;;;;;;;;;;;;;GAeG;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,CAmCjC"}
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"}
@@ -27,6 +27,45 @@ function describeInvalidValidator(value) {
27
27
  }
28
28
  return `${typeof value} ${String(value)}`;
29
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
+ }
30
69
  /**
31
70
  * @internal
32
71
  * Builds the coordinator's internal per-tool validator map from the two
@@ -34,14 +73,17 @@ function describeInvalidValidator(value) {
34
73
  * discrimination anywhere in this path - `schemas` entries are always
35
74
  * compiled as JSON Schema, `validators` entries are always used as-is, and
36
75
  * a tool name present in both is a construction-time error rather than a
37
- * silently-resolved precedence rule. See `docs/VALIDATION.md`.
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.
38
79
  *
39
80
  * Collision detection runs *before* any schema is compiled, so a colliding
40
81
  * tool name is always reported as a collision - deterministically, even
41
82
  * when that same tool's `schemas` entry also happens to be malformed JSON
42
- * Schema. Without this ordering, whichever check happened to run first
43
- * would decide which error the caller sees, for reasons unrelated to which
44
- * problem is actually more fundamental.
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.
45
87
  */
46
88
  export function buildValidatorMap(schemas, validators) {
47
89
  if (schemas && validators) {
@@ -54,12 +96,24 @@ export function buildValidatorMap(schemas, validators) {
54
96
  }
55
97
  const map = new Map();
56
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);
57
111
  // One shared Ajv instance for every schema in THIS `schemas` object -
58
112
  // pre-0.5 behavior, restored here after 0.5's per-schema
59
113
  // createAjvValidator() calls regressed it (a schema could no longer
60
114
  // $ref another schema registered in the same construction - see
61
115
  // buildSharedAjvValidators's own doc comment for the direct proof).
62
- for (const [toolName, validator] of buildSharedAjvValidators(schemas)) {
116
+ for (const [toolName, validator] of buildSharedAjvValidators(materializedSchemas)) {
63
117
  map.set(toolName, validator);
64
118
  }
65
119
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/validation/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AA2C9D;;;;;;;;;;;;;GAaG;AACH,SAAS,0BAA0B,CAAC,KAAc;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,OAAQ,KAAgC,CAAC,QAAQ,KAAK,UAAU,CAAC;AACzH,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAc;IAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,OAAO,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAA2C,EAC3C,UAA0D;IAE1D,IAAI,OAAO,IAAI,UAAU,EAAE,CAAC;QAC1B,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/C,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;gBAC5D,MAAM,IAAI,KAAK,CACb,0BAA0B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,uCAAuC;oBACvF,gGAAgG,CACnG,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,EAA8B,CAAC;IAClD,IAAI,OAAO,EAAE,CAAC;QACZ,sEAAsE;QACtE,yDAAyD;QACzD,oEAAoE;QACpE,gEAAgE;QAChE,oEAAoE;QACpE,KAAK,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,wBAAwB,CAAC,OAAO,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,0BAA0B,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3C,MAAM,IAAI,KAAK,CACb,gCAAgC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,wCAAwC;oBAC9F,4DAA4D,wBAAwB,CAAC,SAAS,CAAC,EAAE,CACpG,CAAC;YACJ,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/validation/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AA2C9D;;;;;;;;;;;;;GAaG;AACH,SAAS,0BAA0B,CAAC,KAAc;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,OAAQ,KAAgC,CAAC,QAAQ,KAAK,UAAU,CAAC;AACzH,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAc;IAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,OAAO,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC5C,CAAC;AAED,0EAA0E;AAC1E,wEAAwE;AACxE,MAAM,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,wBAAwB,CAAC,QAAgB,EAAE,MAAe;IACjE,kEAAkE;IAClE,IAAI,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK,UAAU,CAAC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO;IAC5F,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAK,MAAkC,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CACb,6BAA6B,IAAI,qEAAqE;YACpG,iGAAiG;YACjG,mBAAmB,IAAI,yBAAyB,CACnD,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAI,MAAoC,CAAC,WAAW,CAAC,CAAC;IACpE,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAQ,QAAmC,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC7H,MAAM,IAAI,KAAK,CACb,6BAA6B,IAAI,yEAAyE;YACxG,qEAAqE,IAAI,kCAAkC;YAC3G,2EAA2E,CAC9E,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAA2C,EAC3C,UAA0D;IAE1D,IAAI,OAAO,IAAI,UAAU,EAAE,CAAC;QAC1B,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/C,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;gBAC5D,MAAM,IAAI,KAAK,CACb,0BAA0B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,uCAAuC;oBACvF,gGAAgG,CACnG,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,EAA8B,CAAC;IAClD,IAAI,OAAO,EAAE,CAAC;QACZ,yEAAyE;QACzE,wEAAwE;QACxE,oDAAoD;QACpD,wEAAwE;QACxE,sEAAsE;QACtE,oEAAoE;QACpE,wDAAwD;QACxD,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;YAC/C,wBAAwB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,mBAAmB,GAAG,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;QAC9D,sEAAsE;QACtE,yDAAyD;QACzD,oEAAoE;QACpE,gEAAgE;QAChE,oEAAoE;QACpE,KAAK,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,wBAAwB,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAClF,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,0BAA0B,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3C,MAAM,IAAI,KAAK,CACb,gCAAgC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,wCAAwC;oBAC9F,4DAA4D,wBAAwB,CAAC,SAAS,CAAC,EAAE,CACpG,CAAC;YACJ,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prefix-safe-json",
3
- "version": "0.5.0",
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,19 +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"
34
35
  },
35
36
  "./ajv": {
36
37
  "types": "./dist/ajv.d.ts",
37
- "import": "./dist/ajv.js"
38
+ "import": "./dist/ajv.js",
39
+ "default": "./dist/ajv.js"
38
40
  },
39
41
  "./standard-schema": {
40
42
  "types": "./dist/standard-schema.d.ts",
41
- "import": "./dist/standard-schema.js"
43
+ "import": "./dist/standard-schema.js",
44
+ "default": "./dist/standard-schema.js"
42
45
  },
43
46
  "./conformance": {
44
47
  "types": "./dist/conformance.d.ts",
45
- "import": "./dist/conformance.js"
48
+ "import": "./dist/conformance.js",
49
+ "default": "./dist/conformance.js"
46
50
  }
47
51
  },
48
52
  "files": [
@@ -110,7 +114,8 @@
110
114
  "tsx": "^4.16.0",
111
115
  "typescript": "^5.5.0",
112
116
  "typescript-eslint": "^8.0.0",
113
- "vitest": "^3.2.6"
117
+ "vitest": "^3.2.6",
118
+ "zod": "4.4.3"
114
119
  },
115
120
  "pnpm": {
116
121
  "overrides": {