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 +175 -283
- package/dist/validation/types.d.ts +7 -4
- package/dist/validation/types.d.ts.map +1 -1
- package/dist/validation/types.js +59 -5
- package/dist/validation/types.js.map +1 -1
- package/package.json +12 -7
package/README.md
CHANGED
|
@@ -4,334 +4,206 @@
|
|
|
4
4
|
[](https://github.com/canblmz1/prefix-safe-json/actions/workflows/ci.yml)
|
|
5
5
|
[](#license)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
An incremental JSON parser and execution gate for **streamed LLM tool calls**.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
##
|
|
22
|
+
## Safe execution example
|
|
21
23
|
|
|
22
24
|
```javascript
|
|
25
|
+
import { writeFile } from "node:fs/promises";
|
|
23
26
|
import { streamText } from "ai";
|
|
24
|
-
import {
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
74
|
+
Given the prefix:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
{"city":"Tok
|
|
130
78
|
```
|
|
131
79
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
independently
|
|
139
|
-
|
|
140
|
-
##
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
## AI SDK integration
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
213
|
-
`createToolCallExecutionGate()` the same way the high-level AI SDK guard
|
|
214
|
-
does internally:
|
|
132
|
+
Low-level adapters are included for:
|
|
215
133
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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 {
|
|
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))
|
|
152
|
+
for (const event of adapter.push(rawChunk)) {
|
|
153
|
+
gate.push(event);
|
|
154
|
+
}
|
|
227
155
|
}
|
|
228
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
|
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: {
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
191
|
+
## Compatibility
|
|
317
192
|
|
|
318
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
218
|
+
console.log(result.executable);
|
|
345
219
|
```
|
|
346
220
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
243
|
+
Additional release and dependency documentation is available in:
|
|
353
244
|
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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;
|
|
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"}
|
package/dist/validation/types.js
CHANGED
|
@@ -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
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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(
|
|
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
|
|
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.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
|
-
"
|
|
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": {
|