prefix-safe-json 0.0.1-alpha.2 → 0.0.1-alpha.3
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 +98 -4
- package/dist/coordinator/diagnostic-codes.d.ts +11 -0
- package/dist/coordinator/diagnostic-codes.d.ts.map +1 -0
- package/dist/coordinator/diagnostic-codes.js +22 -0
- package/dist/coordinator/diagnostic-codes.js.map +1 -0
- package/dist/coordinator/protocol.d.ts +1 -1
- package/dist/coordinator/protocol.d.ts.map +1 -1
- package/dist/gate/decide.d.ts +20 -0
- package/dist/gate/decide.d.ts.map +1 -0
- package/dist/gate/decide.js +127 -0
- package/dist/gate/decide.js.map +1 -0
- package/dist/gate/gate.d.ts +25 -0
- package/dist/gate/gate.d.ts.map +1 -0
- package/dist/gate/gate.js +80 -0
- package/dist/gate/gate.js.map +1 -0
- package/dist/gate/types.d.ts +122 -0
- package/dist/gate/types.d.ts.map +1 -0
- package/dist/gate/types.js +2 -0
- package/dist/gate/types.js.map +1 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/ai-sdk.d.ts +54 -0
- package/dist/providers/ai-sdk.d.ts.map +1 -0
- package/dist/providers/ai-sdk.js +191 -0
- package/dist/providers/ai-sdk.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,13 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
> ⚠️ **Alpha** — This project is under active development. APIs may change without notice. Do not use in production.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Don't execute incomplete AI tool calls.**
|
|
6
|
+
|
|
7
|
+
`prefix-safe-json` is a fail-closed integrity layer for streamed LLM tool
|
|
8
|
+
calls. It distinguishes complete, unfabricated arguments from truncated
|
|
9
|
+
ones — including ones a syntax-level JSON repairer can make *look* complete
|
|
10
|
+
— before they reach a tool with real side effects (writing a file, running a
|
|
11
|
+
command, sending a request).
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
model output truncated
|
|
15
|
+
↓
|
|
16
|
+
json repair
|
|
17
|
+
↓
|
|
18
|
+
looks valid
|
|
19
|
+
↓
|
|
20
|
+
❌ dangerous execution
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
prefix-safe-json
|
|
25
|
+
↓
|
|
26
|
+
truncated
|
|
27
|
+
↓
|
|
28
|
+
executable: false
|
|
29
|
+
↓
|
|
30
|
+
retry
|
|
31
|
+
```
|
|
6
32
|
|
|
7
33
|

|
|
8
34
|
|
|
9
35
|
*(Real terminal output from [`examples/anthropic-truncation-safety.mjs`](examples/anthropic-truncation-safety.mjs) — not staged. Same script CI runs on every push.)*
|
|
10
36
|
|
|
11
|
-
##
|
|
37
|
+
## Quick start: the execution safety gate
|
|
38
|
+
|
|
39
|
+
The highest-level API answers one question per tool call — is it safe to
|
|
40
|
+
execute right now — without requiring you to know anything about parser
|
|
41
|
+
state, coordinator events, or JSON Pointers:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { createToolCallExecutionGate, AnthropicStreamAdapter } from "prefix-safe-json";
|
|
45
|
+
|
|
46
|
+
const gate = createToolCallExecutionGate(undefined, undefined, {
|
|
47
|
+
write_file: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: { path: { type: "string" }, content: { type: "string" } },
|
|
50
|
+
required: ["path", "content"],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const adapter = new AnthropicStreamAdapter();
|
|
54
|
+
|
|
55
|
+
for (const rawEvent of anthropicSseEvents) {
|
|
56
|
+
for (const normalized of adapter.push(rawEvent)) gate.push(normalized);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const decision of gate.finish().decisions) {
|
|
60
|
+
if (decision.action === "execute") {
|
|
61
|
+
await tools[decision.name](decision.value);
|
|
62
|
+
} else {
|
|
63
|
+
console.warn(`${decision.name}: ${decision.action} (${decision.reason})`); // "retry" or "reject"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Provider adapters exist for OpenAI, Anthropic, Gemini, OpenRouter,
|
|
69
|
+
OpenAI-compatible endpoints, and the [Vercel AI SDK](https://ai-sdk.dev)
|
|
70
|
+
(`ai` package — not a dependency of this library; see
|
|
71
|
+
[`examples/ai-sdk-execution-gate.mjs`](examples/ai-sdk-execution-gate.mjs)).
|
|
72
|
+
Full semantics, the decision table, and provider finish-reason mapping:
|
|
73
|
+
[`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md).
|
|
74
|
+
|
|
75
|
+
## The lower-level problem this is built on
|
|
12
76
|
|
|
13
77
|
LLM providers (OpenAI, Anthropic, Gemini, OpenRouter) stream tool-call arguments as small JSON chunks. During streaming, the JSON is frequently incomplete: strings split mid-word, UTF-8 characters split mid-byte, containers left unclosed, numbers terminated at chunk boundaries.
|
|
14
78
|
|
|
@@ -98,6 +162,25 @@ draws exactly that distinction (`outcome: "truncated"`) and could gate
|
|
|
98
162
|
execution on it without touching the syntax-repair behavior for the other
|
|
99
163
|
case.
|
|
100
164
|
|
|
165
|
+
## Scope: what this does and doesn't protect against
|
|
166
|
+
|
|
167
|
+
This library is honestly scoped as **execution integrity** — complete,
|
|
168
|
+
unfabricated, schema-valid arguments — not a general "AI security platform."
|
|
169
|
+
It does **not**:
|
|
170
|
+
|
|
171
|
+
- defend against prompt injection (it says nothing about whether the
|
|
172
|
+
arguments a model *chose* to send are the arguments it *should* have
|
|
173
|
+
sent — only whether they're complete and unfabricated)
|
|
174
|
+
- resolve authorization (it doesn't know which caller may invoke which tool)
|
|
175
|
+
- sandbox execution (`action: "execute"` means "this is genuinely the
|
|
176
|
+
complete value the model produced," not "safe to run without your own
|
|
177
|
+
validation and permissions")
|
|
178
|
+
- detect a malicious or wrong tool choice (a model calling the wrong tool
|
|
179
|
+
with complete, schema-valid arguments is still reported `execute`)
|
|
180
|
+
|
|
181
|
+
See [`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md#limitations---what-this-does-not-protect-against)
|
|
182
|
+
for the full list, including known internal limitations.
|
|
183
|
+
|
|
101
184
|
## Current Status (Alpha)
|
|
102
185
|
|
|
103
186
|
### Implemented
|
|
@@ -111,8 +194,9 @@ case.
|
|
|
111
194
|
- Configurable resource limits (depth, input size, string length, queued events)
|
|
112
195
|
- Machine-readable test corpus with 25+ canonical fixtures
|
|
113
196
|
- Chunk invariance verification
|
|
114
|
-
- Provider stream adapters for OpenAI (legacy `function_call` and Responses API), Anthropic, Gemini, OpenRouter,
|
|
197
|
+
- Provider stream adapters for OpenAI (legacy `function_call` and Responses API), Anthropic, Gemini, OpenRouter, generic OpenAI-compatible endpoints, and the Vercel AI SDK, plus a coordinator for tracking multiple concurrent tool-call streams
|
|
115
198
|
- Optional per-tool JSON Schema validation on the coordinator (see below) — a value can be structurally complete and still not match what a tool declared it needs
|
|
199
|
+
- `createToolCallExecutionGate()` — a fail-closed `execute` / `retry` / `reject` decision per tool call, built on top of the coordinator (see [`docs/EXECUTION_GATE.md`](docs/EXECUTION_GATE.md))
|
|
116
200
|
|
|
117
201
|
### Not Yet Implemented
|
|
118
202
|
|
|
@@ -155,7 +239,11 @@ A schema mismatch also surfaces as a coordinator diagnostic (`E_SCHEMA_VALIDATIO
|
|
|
155
239
|
npm install prefix-safe-json
|
|
156
240
|
```
|
|
157
241
|
|
|
158
|
-
## Quick Start
|
|
242
|
+
## Low-level API: Quick Start
|
|
243
|
+
|
|
244
|
+
The execution gate (above) is what most consumers want. The low-level
|
|
245
|
+
`createParser()` API it's built on is also fully public, for anyone who
|
|
246
|
+
needs direct access to parser state:
|
|
159
247
|
|
|
160
248
|
```typescript
|
|
161
249
|
import { createParser } from "prefix-safe-json";
|
|
@@ -192,6 +280,12 @@ and asserts the truncated call is never reported executable while a
|
|
|
192
280
|
genuinely complete one is. This example runs in CI on every push, so it
|
|
193
281
|
can't silently rot into a stale claim.
|
|
194
282
|
|
|
283
|
+
For the same demonstration through the high-level execution gate and the
|
|
284
|
+
[Vercel AI SDK](https://ai-sdk.dev)'s `fullStream` shape instead, see
|
|
285
|
+
[`examples/ai-sdk-execution-gate.mjs`](examples/ai-sdk-execution-gate.mjs)
|
|
286
|
+
(`node examples/ai-sdk-execution-gate.mjs` after `pnpm run build`) — also
|
|
287
|
+
run in CI on every push.
|
|
288
|
+
|
|
195
289
|
## License
|
|
196
290
|
|
|
197
291
|
Licensed under either of:
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emitted (as a stream-wide `provider_diagnostic`, no `callRef`) by a
|
|
3
|
+
* provider adapter when the underlying provider reports that generation was
|
|
4
|
+
* stopped by a content-safety/policy filter, as opposed to a generic
|
|
5
|
+
* cancellation. The execution gate matches on this to return a distinct
|
|
6
|
+
* `reason: "content_filtered"` rather than lumping it in with ordinary
|
|
7
|
+
* incomplete-stream retries - a policy termination should not be retried
|
|
8
|
+
* blindly.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CONTENT_FILTERED_DIAGNOSTIC_CODE = "E_CONTENT_FILTERED";
|
|
11
|
+
//# sourceMappingURL=diagnostic-codes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostic-codes.d.ts","sourceRoot":"","sources":["../../src/coordinator/diagnostic-codes.ts"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Cross-module coordinator-level diagnostic codes.
|
|
3
|
+
//
|
|
4
|
+
// Most coordinator diagnostics (see coordinator.ts) are inline string
|
|
5
|
+
// literals local to wherever they're raised - there's no central registry,
|
|
6
|
+
// unlike the parser-level `DiagnosticCode` in diagnostics/codes.ts. This
|
|
7
|
+
// file exists only for the handful of codes that a provider adapter raises
|
|
8
|
+
// and something *outside* that adapter (e.g. the execution gate) needs to
|
|
9
|
+
// recognize by exact code, where a shared constant is safer than either
|
|
10
|
+
// module guessing the other's string literal.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/**
|
|
13
|
+
* Emitted (as a stream-wide `provider_diagnostic`, no `callRef`) by a
|
|
14
|
+
* provider adapter when the underlying provider reports that generation was
|
|
15
|
+
* stopped by a content-safety/policy filter, as opposed to a generic
|
|
16
|
+
* cancellation. The execution gate matches on this to return a distinct
|
|
17
|
+
* `reason: "content_filtered"` rather than lumping it in with ordinary
|
|
18
|
+
* incomplete-stream retries - a policy termination should not be retried
|
|
19
|
+
* blindly.
|
|
20
|
+
*/
|
|
21
|
+
export const CONTENT_FILTERED_DIAGNOSTIC_CODE = "E_CONTENT_FILTERED";
|
|
22
|
+
//# sourceMappingURL=diagnostic-codes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostic-codes.js","sourceRoot":"","sources":["../../src/coordinator/diagnostic-codes.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mDAAmD;AACnD,EAAE;AACF,sEAAsE;AACtE,2EAA2E;AAC3E,yEAAyE;AACzE,2EAA2E;AAC3E,0EAA0E;AAC1E,wEAAwE;AACxE,8CAA8C;AAC9C,8EAA8E;AAE9E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,oBAAoB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ProviderName = "openai" | "anthropic" | "gemini" | "openrouter" | "openai-compatible" | "unknown";
|
|
1
|
+
export type ProviderName = "openai" | "anthropic" | "gemini" | "openrouter" | "openai-compatible" | "ai-sdk" | "unknown";
|
|
2
2
|
export interface NormalizedEventBase {
|
|
3
3
|
/**
|
|
4
4
|
* Deterministic sequence number assigned by the stream coordinator.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/coordinator/protocol.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,YAAY,GACZ,mBAAmB,GACnB,SAAS,CAAC;AAEd,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,MAAM,yBAAyB,GACjC,kBAAkB,GAClB,qBAAqB,GACrB,sBAAsB,GACtB,2BAA2B,GAC3B,gBAAgB,GAChB,uBAAuB,GACvB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAElC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAElC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;CACrC;AAED,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,QAAQ,GACR,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAiB,SAAQ,mBAAmB;IAC3D,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB;IAClE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC;CACpC;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B"}
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/coordinator/protocol.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,YAAY,GACZ,mBAAmB,GACnB,QAAQ,GACR,SAAS,CAAC;AAEd,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,MAAM,yBAAyB,GACjC,kBAAkB,GAClB,qBAAqB,GACrB,sBAAsB,GACtB,2BAA2B,GAC3B,gBAAgB,GAChB,uBAAuB,GACvB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAElC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAElC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;CACrC;AAED,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,QAAQ,GACR,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAiB,SAAQ,mBAAmB;IAC3D,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB;IAClE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC;CACpC;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CoordinatorDiagnostic, ToolCallState } from "../coordinator/types.js";
|
|
2
|
+
import { StreamEndReason } from "../coordinator/protocol.js";
|
|
3
|
+
import { ExecuteDecision, NonExecutableDecision } from "./types.js";
|
|
4
|
+
export interface DecisionContext {
|
|
5
|
+
/**
|
|
6
|
+
* The stream-level end reason - what actually happened to the underlying
|
|
7
|
+
* provider stream, not any single call's own (possibly still-"collecting")
|
|
8
|
+
* status. Stream-wide fail-closed conditions (provider_error) key off this
|
|
9
|
+
* rather than per-call state.
|
|
10
|
+
*/
|
|
11
|
+
readonly streamEndReason: StreamEndReason;
|
|
12
|
+
/**
|
|
13
|
+
* The coordinator's full, unfiltered diagnostics list (`snapshot().diagnostics`).
|
|
14
|
+
* `decideExecution` splits this into per-call (`internalId` matches) and
|
|
15
|
+
* global (`internalId` undefined) subsets itself.
|
|
16
|
+
*/
|
|
17
|
+
readonly diagnostics: readonly CoordinatorDiagnostic[];
|
|
18
|
+
}
|
|
19
|
+
export declare function decideExecution(call: ToolCallState, ctx: DecisionContext): ExecuteDecision | NonExecutableDecision;
|
|
20
|
+
//# sourceMappingURL=decide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decide.d.ts","sourceRoot":"","sources":["../../src/gate/decide.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAmB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAErF,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAE1C;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACxD;AAeD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,eAAe,GACnB,eAAe,GAAG,qBAAqB,CAmHzC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Pure execution-decision mapping.
|
|
3
|
+
//
|
|
4
|
+
// Deliberately a standalone function (no coordinator/adapter dependency) so
|
|
5
|
+
// the entire decision table can be unit-tested directly against hand-built
|
|
6
|
+
// ToolCallState fixtures, without spinning up a full push()/finish()
|
|
7
|
+
// pipeline for every row of the table.
|
|
8
|
+
//
|
|
9
|
+
// Priority order matters and is intentional: every fail-closed disqualifier
|
|
10
|
+
// (resource limits, provider errors, content-policy terminations, schema
|
|
11
|
+
// mismatches, malformed content, positively-observed truncation) is checked
|
|
12
|
+
// before the single positive "execute" branch. "execute" is reached only
|
|
13
|
+
// once nothing above has disqualified the call - it is the last positive
|
|
14
|
+
// case, never the first check made, so a future status value that happens
|
|
15
|
+
// to superficially resemble "complete" still can't skip the checks ahead of
|
|
16
|
+
// it.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
import { DiagnosticCode } from "../diagnostics/codes.js";
|
|
19
|
+
import { CONTENT_FILTERED_DIAGNOSTIC_CODE } from "../coordinator/diagnostic-codes.js";
|
|
20
|
+
const PARSER_RESOURCE_LIMIT_CODES = new Set([
|
|
21
|
+
DiagnosticCode.E_LIMIT_DEPTH,
|
|
22
|
+
DiagnosticCode.E_LIMIT_INPUT_BYTES,
|
|
23
|
+
DiagnosticCode.E_LIMIT_STRING_BYTES,
|
|
24
|
+
DiagnosticCode.E_LIMIT_EVENT_QUEUE,
|
|
25
|
+
]);
|
|
26
|
+
const COORDINATOR_RESOURCE_LIMIT_CODES = new Set([
|
|
27
|
+
"E_COORDINATOR_LIMIT_CALLS",
|
|
28
|
+
"E_COORDINATOR_LIMIT_EVENTS",
|
|
29
|
+
"E_TOOL_NAME_LIMIT",
|
|
30
|
+
]);
|
|
31
|
+
export function decideExecution(call, ctx) {
|
|
32
|
+
const parserDiagnostics = call.parser.diagnostics;
|
|
33
|
+
const callCoordinatorDiagnostics = ctx.diagnostics.filter((d) => d.internalId === call.internalId);
|
|
34
|
+
const globalCoordinatorDiagnostics = ctx.diagnostics.filter((d) => d.internalId === undefined);
|
|
35
|
+
const coordinatorDiagnostics = [
|
|
36
|
+
...globalCoordinatorDiagnostics,
|
|
37
|
+
...callCoordinatorDiagnostics,
|
|
38
|
+
];
|
|
39
|
+
const common = {
|
|
40
|
+
internalId: call.internalId,
|
|
41
|
+
toolCallId: call.toolCallId,
|
|
42
|
+
toolIndex: call.toolIndex,
|
|
43
|
+
name: call.name,
|
|
44
|
+
stableValue: call.parser.stableValue,
|
|
45
|
+
parserDiagnostics,
|
|
46
|
+
coordinatorDiagnostics,
|
|
47
|
+
};
|
|
48
|
+
function reject(reason) {
|
|
49
|
+
return { ...common, action: "reject", executable: false, reason };
|
|
50
|
+
}
|
|
51
|
+
function retry(reason) {
|
|
52
|
+
return { ...common, action: "retry", executable: false, reason };
|
|
53
|
+
}
|
|
54
|
+
// --- Universal fail-closed checks ---------------------------------------
|
|
55
|
+
// Evaluated first and unconditionally, regardless of call.status, so a
|
|
56
|
+
// resource limit, a provider-side failure, or a content-policy
|
|
57
|
+
// termination always wins over whatever the structural status looks like.
|
|
58
|
+
const hasResourceLimit = parserDiagnostics.some((d) => PARSER_RESOURCE_LIMIT_CODES.has(d.code)) ||
|
|
59
|
+
callCoordinatorDiagnostics.some((d) => COORDINATOR_RESOURCE_LIMIT_CODES.has(d.code)) ||
|
|
60
|
+
globalCoordinatorDiagnostics.some((d) => d.code === "E_COORDINATOR_LIMIT_EVENTS");
|
|
61
|
+
if (hasResourceLimit) {
|
|
62
|
+
return reject("resource_limit");
|
|
63
|
+
}
|
|
64
|
+
if (ctx.streamEndReason === "provider_error") {
|
|
65
|
+
return reject("provider_error");
|
|
66
|
+
}
|
|
67
|
+
const isContentFiltered = globalCoordinatorDiagnostics.some((d) => d.code === CONTENT_FILTERED_DIAGNOSTIC_CODE);
|
|
68
|
+
if (isContentFiltered) {
|
|
69
|
+
return reject("content_filtered");
|
|
70
|
+
}
|
|
71
|
+
// --- Status-specific handling --------------------------------------------
|
|
72
|
+
// Exhaustive over ToolCallState["status"] with no `default`: if a future
|
|
73
|
+
// coordinator version adds a new status literal without a matching case
|
|
74
|
+
// here, `noImplicitReturns` fails the build - not a silent runtime gap.
|
|
75
|
+
switch (call.status) {
|
|
76
|
+
case "complete": {
|
|
77
|
+
if (call.schemaValid === false) {
|
|
78
|
+
return reject("schema_invalid");
|
|
79
|
+
}
|
|
80
|
+
// The ONLY positive branch in this entire function, reached only
|
|
81
|
+
// after every disqualifying check above has already passed. The
|
|
82
|
+
// `call.name !== undefined` check is defensive/redundant with the
|
|
83
|
+
// coordinator's own invariant (a call only reaches status "complete"
|
|
84
|
+
// when its name is known) - narrowing on it here is what lets
|
|
85
|
+
// `ExecuteDecision.name` be typed as a required `string` instead of
|
|
86
|
+
// `string | undefined`, so it never needs re-checking at every call
|
|
87
|
+
// site. If the invariant were ever violated, this still fails closed
|
|
88
|
+
// to "stream_incomplete" rather than fabricating a name.
|
|
89
|
+
if (call.parser.executable === true &&
|
|
90
|
+
call.parser.stableValue !== undefined &&
|
|
91
|
+
call.name !== undefined) {
|
|
92
|
+
const decision = {
|
|
93
|
+
...common,
|
|
94
|
+
name: call.name,
|
|
95
|
+
action: "execute",
|
|
96
|
+
executable: true,
|
|
97
|
+
reason: "complete",
|
|
98
|
+
value: call.parser.stableValue,
|
|
99
|
+
};
|
|
100
|
+
return decision;
|
|
101
|
+
}
|
|
102
|
+
// Root closed, syntactically valid, but not trusted: the parser's
|
|
103
|
+
// own executable contract failed it (stream-end-reason mismatch or
|
|
104
|
+
// trailing data), even though nothing here required a repair.
|
|
105
|
+
return retry("stream_incomplete");
|
|
106
|
+
}
|
|
107
|
+
case "truncated":
|
|
108
|
+
// The parser positively observed an open string/number/literal or an
|
|
109
|
+
// unclosed container - a real, raw mid-value cut, not a matter of
|
|
110
|
+
// interpretation.
|
|
111
|
+
return retry("truncated");
|
|
112
|
+
case "invalid":
|
|
113
|
+
// Duplicate key, unexpected token, or a missing/conflicting tool
|
|
114
|
+
// identity - a genuine content defect, independent of how the stream
|
|
115
|
+
// ended.
|
|
116
|
+
return reject("malformed");
|
|
117
|
+
case "salvaged":
|
|
118
|
+
// Root only closed via a safe-container-closing repair; the JSON
|
|
119
|
+
// *looks* complete, but provider metadata never confirmed genuine
|
|
120
|
+
// completion. This is the container-level truncation case.
|
|
121
|
+
return retry("stream_incomplete");
|
|
122
|
+
case "cancelled":
|
|
123
|
+
case "collecting":
|
|
124
|
+
return retry("stream_incomplete");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=decide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decide.js","sourceRoot":"","sources":["../../src/gate/decide.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mCAAmC;AACnC,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,qEAAqE;AACrE,uCAAuC;AACvC,EAAE;AACF,4EAA4E;AAC5E,yEAAyE;AACzE,4EAA4E;AAC5E,yEAAyE;AACzE,yEAAyE;AACzE,0EAA0E;AAC1E,4EAA4E;AAC5E,MAAM;AACN,8EAA8E;AAE9E,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGzD,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AAoBtF,MAAM,2BAA2B,GAAwB,IAAI,GAAG,CAAC;IAC/D,cAAc,CAAC,aAAa;IAC5B,cAAc,CAAC,mBAAmB;IAClC,cAAc,CAAC,oBAAoB;IACnC,cAAc,CAAC,mBAAmB;CACnC,CAAC,CAAC;AAEH,MAAM,gCAAgC,GAAwB,IAAI,GAAG,CAAC;IACpE,2BAA2B;IAC3B,4BAA4B;IAC5B,mBAAmB;CACpB,CAAC,CAAC;AAEH,MAAM,UAAU,eAAe,CAC7B,IAAmB,EACnB,GAAoB;IAEpB,MAAM,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;IAClD,MAAM,0BAA0B,GAAG,GAAG,CAAC,WAAW,CAAC,MAAM,CACvD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,CACxC,CAAC;IACF,MAAM,4BAA4B,GAAG,GAAG,CAAC,WAAW,CAAC,MAAM,CACzD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,SAAS,CAClC,CAAC;IACF,MAAM,sBAAsB,GAAG;QAC7B,GAAG,4BAA4B;QAC/B,GAAG,0BAA0B;KAC9B,CAAC;IAEF,MAAM,MAAM,GAAG;QACb,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;QACpC,iBAAiB;QACjB,sBAAsB;KACvB,CAAC;IAEF,SAAS,MAAM,CAAC,MAA4C;QAC1D,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACpE,CAAC;IACD,SAAS,KAAK,CAAC,MAAyC;QACtD,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACnE,CAAC;IAED,2EAA2E;IAC3E,uEAAuE;IACvE,+DAA+D;IAC/D,0EAA0E;IAE1E,MAAM,gBAAgB,GACpB,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,2BAA2B,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtE,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gCAAgC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpF,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,4BAA4B,CAAC,CAAC;IACpF,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,GAAG,CAAC,eAAe,KAAK,gBAAgB,EAAE,CAAC;QAC7C,OAAO,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,iBAAiB,GAAG,4BAA4B,CAAC,IAAI,CACzD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,gCAAgC,CACnD,CAAC;IACF,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACpC,CAAC;IAED,4EAA4E;IAC5E,yEAAyE;IACzE,wEAAwE;IACxE,wEAAwE;IACxE,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;gBAC/B,OAAO,MAAM,CAAC,gBAAgB,CAAC,CAAC;YAClC,CAAC;YACD,iEAAiE;YACjE,gEAAgE;YAChE,kEAAkE;YAClE,qEAAqE;YACrE,8DAA8D;YAC9D,oEAAoE;YACpE,oEAAoE;YACpE,qEAAqE;YACrE,yDAAyD;YACzD,IACE,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,IAAI;gBAC/B,IAAI,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS;gBACrC,IAAI,CAAC,IAAI,KAAK,SAAS,EACvB,CAAC;gBACD,MAAM,QAAQ,GAAoB;oBAChC,GAAG,MAAM;oBACT,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,MAAM,EAAE,SAAS;oBACjB,UAAU,EAAE,IAAI;oBAChB,MAAM,EAAE,UAAU;oBAClB,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;iBAC/B,CAAC;gBACF,OAAO,QAAQ,CAAC;YAClB,CAAC;YACD,kEAAkE;YAClE,mEAAmE;YACnE,8DAA8D;YAC9D,OAAO,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACpC,CAAC;QAED,KAAK,WAAW;YACd,qEAAqE;YACrE,kEAAkE;YAClE,kBAAkB;YAClB,OAAO,KAAK,CAAC,WAAW,CAAC,CAAC;QAE5B,KAAK,SAAS;YACZ,iEAAiE;YACjE,qEAAqE;YACrE,SAAS;YACT,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC;QAE7B,KAAK,UAAU;YACb,iEAAiE;YACjE,kEAAkE;YAClE,2DAA2D;YAC3D,OAAO,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAEpC,KAAK,WAAW,CAAC;QACjB,KAAK,YAAY;YACf,OAAO,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACtC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CoordinatorLimits, JsonSchemaLike } from "../coordinator/types.js";
|
|
2
|
+
import { ParserOptions } from "../types.js";
|
|
3
|
+
import { ToolCallExecutionGate } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Creates a fail-closed execution-decision gate for streamed LLM tool calls.
|
|
6
|
+
* Wraps a `ToolCallStreamCoordinator` (same constructor arguments) and adds
|
|
7
|
+
* exactly one operation: turn each call's settled state into an
|
|
8
|
+
* `ExecutionDecision` (`execute` / `retry` / `reject`) via a deterministic,
|
|
9
|
+
* fully-typed decision table. See `docs/EXECUTION_GATE.md`.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const gate = createToolCallExecutionGate(undefined, undefined, {
|
|
13
|
+
* write_file: { type: "object", required: ["path", "content"], properties: {
|
|
14
|
+
* path: { type: "string" }, content: { type: "string" } } },
|
|
15
|
+
* });
|
|
16
|
+
* for (const raw of providerEvents) {
|
|
17
|
+
* for (const normalized of adapter.push(raw)) gate.push(normalized);
|
|
18
|
+
* }
|
|
19
|
+
* for (const decision of gate.finish().decisions) {
|
|
20
|
+
* if (decision.action === "execute") await tools[decision.name](decision.value);
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function createToolCallExecutionGate(limits?: Partial<CoordinatorLimits>, parserOptions?: ParserOptions, toolSchemas?: Record<string, JsonSchemaLike>): ToolCallExecutionGate;
|
|
25
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/gate/gate.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,iBAAiB,EAEjB,cAAc,EAGf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAEL,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AA+DpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,EACnC,aAAa,CAAC,EAAE,aAAa,EAC7B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAC3C,qBAAqB,CAEvB"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// ToolCallExecutionGate — high-level execution safety layer.
|
|
3
|
+
//
|
|
4
|
+
// Built by composition over ToolCallStreamCoordinator, not by extending or
|
|
5
|
+
// reimplementing it: the gate holds a coordinator instance and adds exactly
|
|
6
|
+
// one thing coordinator state doesn't already capture - which stream-end
|
|
7
|
+
// reason actually got applied - then maps every ToolCallState through the
|
|
8
|
+
// pure decideExecution() table. No re-parsing, no duplicate event pipeline.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
import { createToolCallStreamCoordinator } from "../coordinator/coordinator.js";
|
|
11
|
+
import { decideExecution } from "./decide.js";
|
|
12
|
+
class DefaultToolCallExecutionGate {
|
|
13
|
+
coordinator;
|
|
14
|
+
// Mirrors the coordinator's own "first provider_stream_end (from push(),
|
|
15
|
+
// or from finish()'s meta if none arrived) wins" rule - see push()/finish()
|
|
16
|
+
// below and DefaultToolCallStreamCoordinator.finish()'s matching
|
|
17
|
+
// `if (!this.isFinished)` guard.
|
|
18
|
+
streamEndReason;
|
|
19
|
+
streamEndCaptured = false;
|
|
20
|
+
constructor(limits, parserOptions, toolSchemas) {
|
|
21
|
+
this.coordinator = createToolCallStreamCoordinator(limits, parserOptions, toolSchemas);
|
|
22
|
+
}
|
|
23
|
+
push(event) {
|
|
24
|
+
if (event.type === "provider_stream_end" && !this.streamEndCaptured) {
|
|
25
|
+
this.streamEndCaptured = true;
|
|
26
|
+
this.streamEndReason = event.reason;
|
|
27
|
+
}
|
|
28
|
+
return this.coordinator.push(event);
|
|
29
|
+
}
|
|
30
|
+
snapshot() {
|
|
31
|
+
const snap = this.coordinator.snapshot();
|
|
32
|
+
const ctx = {
|
|
33
|
+
// Pre-finish, no stream-end reason has been observed yet ("unknown"
|
|
34
|
+
// is a neutral placeholder here, not a claim anything went wrong) -
|
|
35
|
+
// every in-flight call is still "collecting" regardless, so this
|
|
36
|
+
// never influences whether something looks executable.
|
|
37
|
+
streamEndReason: this.streamEndReason ?? "unknown",
|
|
38
|
+
diagnostics: snap.diagnostics,
|
|
39
|
+
};
|
|
40
|
+
return snap.calls.map((call) => decideExecution(call, ctx));
|
|
41
|
+
}
|
|
42
|
+
drainEvents() {
|
|
43
|
+
return this.coordinator.drainEvents();
|
|
44
|
+
}
|
|
45
|
+
finish(meta) {
|
|
46
|
+
if (!this.streamEndCaptured) {
|
|
47
|
+
this.streamEndCaptured = true;
|
|
48
|
+
this.streamEndReason = meta?.reason ?? "unknown";
|
|
49
|
+
}
|
|
50
|
+
const result = this.coordinator.finish(meta);
|
|
51
|
+
const diagnostics = this.coordinator.snapshot().diagnostics;
|
|
52
|
+
const ctx = { streamEndReason: this.streamEndReason ?? "unknown", diagnostics };
|
|
53
|
+
const decisions = result.calls.map((call) => decideExecution(call, ctx));
|
|
54
|
+
return { decisions, diagnostics };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Creates a fail-closed execution-decision gate for streamed LLM tool calls.
|
|
59
|
+
* Wraps a `ToolCallStreamCoordinator` (same constructor arguments) and adds
|
|
60
|
+
* exactly one operation: turn each call's settled state into an
|
|
61
|
+
* `ExecutionDecision` (`execute` / `retry` / `reject`) via a deterministic,
|
|
62
|
+
* fully-typed decision table. See `docs/EXECUTION_GATE.md`.
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* const gate = createToolCallExecutionGate(undefined, undefined, {
|
|
66
|
+
* write_file: { type: "object", required: ["path", "content"], properties: {
|
|
67
|
+
* path: { type: "string" }, content: { type: "string" } } },
|
|
68
|
+
* });
|
|
69
|
+
* for (const raw of providerEvents) {
|
|
70
|
+
* for (const normalized of adapter.push(raw)) gate.push(normalized);
|
|
71
|
+
* }
|
|
72
|
+
* for (const decision of gate.finish().decisions) {
|
|
73
|
+
* if (decision.action === "execute") await tools[decision.name](decision.value);
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function createToolCallExecutionGate(limits, parserOptions, toolSchemas) {
|
|
78
|
+
return new DefaultToolCallExecutionGate(limits, parserOptions, toolSchemas);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/gate/gate.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6DAA6D;AAC7D,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,yEAAyE;AACzE,0EAA0E;AAC1E,4EAA4E;AAC5E,8EAA8E;AAE9E,OAAO,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AAUhF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAO9C,MAAM,4BAA4B;IACf,WAAW,CAA4B;IAExD,yEAAyE;IACzE,4EAA4E;IAC5E,iEAAiE;IACjE,iCAAiC;IACzB,eAAe,CAA8B;IAC7C,iBAAiB,GAAG,KAAK,CAAC;IAElC,YACE,MAAmC,EACnC,aAA6B,EAC7B,WAA4C;QAE5C,IAAI,CAAC,WAAW,GAAG,+BAA+B,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;IACzF,CAAC;IAED,IAAI,CAAC,KAAgC;QACnC,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAqB,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACpE,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;YAC9B,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC,MAAM,CAAC;QACtC,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IAED,QAAQ;QACN,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG;YACV,oEAAoE;YACpE,oEAAoE;YACpE,iEAAiE;YACjE,uDAAuD;YACvD,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,SAAS;YAClD,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;IACxC,CAAC;IAED,MAAM,CAAC,IAGN;QACC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC5B,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;YAC9B,IAAI,CAAC,eAAe,GAAG,IAAI,EAAE,MAAM,IAAI,SAAS,CAAC;QACnD,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC;QAC5D,MAAM,GAAG,GAAG,EAAE,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,SAAS,EAAE,WAAW,EAAE,CAAC;QAChF,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QAEzE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IACpC,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAAmC,EACnC,aAA6B,EAC7B,WAA4C;IAE5C,OAAO,IAAI,4BAA4B,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { JsonValue, Diagnostic } from "../types.js";
|
|
2
|
+
import { CoordinatorDiagnostic, CoordinatorPushResult, ToolCallCoordinatorEvent } from "../coordinator/types.js";
|
|
3
|
+
import { NormalizedToolStreamEvent, StreamEndReason } from "../coordinator/protocol.js";
|
|
4
|
+
/**
|
|
5
|
+
* What to do with a tool call right now.
|
|
6
|
+
*
|
|
7
|
+
* - `"execute"`: the arguments are complete, unfabricated, and (if a schema
|
|
8
|
+
* was registered for this tool) schema-valid. Safe to run.
|
|
9
|
+
* - `"retry"`: the provider's output was incomplete or truncated. There is
|
|
10
|
+
* nothing wrong with the data received so far - there just isn't a
|
|
11
|
+
* trustworthy complete value yet. Re-request/continue generation.
|
|
12
|
+
* - `"reject"`: the data itself is the problem (malformed JSON, a schema
|
|
13
|
+
* mismatch, a resource limit, a provider-side error, or a content-policy
|
|
14
|
+
* termination). Re-sending the exact same partial input will not fix it.
|
|
15
|
+
*/
|
|
16
|
+
export type ExecutionAction = "execute" | "retry" | "reject";
|
|
17
|
+
/**
|
|
18
|
+
* Machine-readable classification of *why* an `ExecutionAction` was chosen.
|
|
19
|
+
* See `docs/EXECUTION_GATE.md` for the full decision table.
|
|
20
|
+
*/
|
|
21
|
+
export type ExecutionReason = "complete" | "truncated" | "stream_incomplete" | "schema_invalid" | "malformed" | "resource_limit" | "provider_error" | "content_filtered" | "unknown";
|
|
22
|
+
interface ExecutionDecisionCommon {
|
|
23
|
+
/** The coordinator's internal call identifier (see `ToolCallState.internalId`). */
|
|
24
|
+
readonly internalId: string;
|
|
25
|
+
readonly toolCallId?: string;
|
|
26
|
+
readonly toolIndex?: number;
|
|
27
|
+
readonly name?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Whatever was safely committed for this call, complete or not - present
|
|
30
|
+
* for both executable and non-executable decisions so a caller can see
|
|
31
|
+
* *what part* of a rejected/retried call was genuinely received (e.g. a
|
|
32
|
+
* `path` field that arrived before a `content` field got cut off).
|
|
33
|
+
* Never the source of truth for whether it's safe to act on - that's
|
|
34
|
+
* `action`/`executable`. Absent only when nothing was ever committed.
|
|
35
|
+
*/
|
|
36
|
+
readonly stableValue?: JsonValue;
|
|
37
|
+
/** This call's own parser diagnostics, verbatim (`ToolCallState.parser.diagnostics`). */
|
|
38
|
+
readonly parserDiagnostics: readonly Diagnostic[];
|
|
39
|
+
/** Coordinator-level diagnostics attributable to this call, verbatim. */
|
|
40
|
+
readonly coordinatorDiagnostics: readonly CoordinatorDiagnostic[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The one and only shape a decision can have when `action === "execute"`.
|
|
44
|
+
* `value` is a real, present `JsonValue` here - never `undefined`, never
|
|
45
|
+
* optional - because this is the single positive case: everything else in
|
|
46
|
+
* the gate exists to rule this out unless it's genuinely earned.
|
|
47
|
+
*/
|
|
48
|
+
export interface ExecuteDecision extends ExecutionDecisionCommon {
|
|
49
|
+
readonly action: "execute";
|
|
50
|
+
readonly executable: true;
|
|
51
|
+
readonly reason: "complete";
|
|
52
|
+
readonly value: JsonValue;
|
|
53
|
+
/**
|
|
54
|
+
* Narrowed to required here (unlike the inherited `name?: string`):
|
|
55
|
+
* the coordinator only ever transitions a call to `status: "complete"`
|
|
56
|
+
* when `name` is defined (see `DefaultToolCallStreamCoordinator.finishCall`
|
|
57
|
+
* - a call with no name is forced to `"invalid"` instead), so every
|
|
58
|
+
* `ExecuteDecision` genuinely has one. Encoding that here means
|
|
59
|
+
* `tools[decision.name](decision.value)` type-checks directly after
|
|
60
|
+
* narrowing on `action === "execute"`, with no non-null assertion needed.
|
|
61
|
+
*/
|
|
62
|
+
readonly name: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Every other outcome. `value` does not exist on this type at all (not
|
|
66
|
+
* `undefined` - absent) - TypeScript itself refuses code that reads a
|
|
67
|
+
* "final executable value" off a call the gate did not clear for execution.
|
|
68
|
+
*/
|
|
69
|
+
export interface NonExecutableDecision extends ExecutionDecisionCommon {
|
|
70
|
+
readonly action: "retry" | "reject";
|
|
71
|
+
readonly executable: false;
|
|
72
|
+
readonly reason: Exclude<ExecutionReason, "complete">;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `ExecuteDecision | NonExecutableDecision` - a discriminated union on
|
|
76
|
+
* `action` (and redundantly on `executable`, for callers who prefer that
|
|
77
|
+
* check). Narrowing on either field gives you the right shape:
|
|
78
|
+
*
|
|
79
|
+
* ```ts
|
|
80
|
+
* if (decision.action === "execute") {
|
|
81
|
+
* await tools[decision.name](decision.value); // JsonValue, not JsonValue|undefined
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export type ExecutionDecision = ExecuteDecision | NonExecutableDecision;
|
|
86
|
+
export interface ToolCallExecutionGateFinalResult {
|
|
87
|
+
/** One decision per tool call the coordinator ever created. */
|
|
88
|
+
readonly decisions: readonly ExecutionDecision[];
|
|
89
|
+
/**
|
|
90
|
+
* The full, unfiltered coordinator diagnostics list (same as
|
|
91
|
+
* `coordinator.snapshot().diagnostics`) - includes diagnostics with no
|
|
92
|
+
* associated call, e.g. `E_COORDINATOR_LIMIT_CALLS` fires for a call that
|
|
93
|
+
* hit the concurrent-call limit before a `ToolCallState` could even be
|
|
94
|
+
* created for it, so it can never appear in `decisions`.
|
|
95
|
+
*/
|
|
96
|
+
readonly diagnostics: readonly CoordinatorDiagnostic[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* High-level fail-closed execution decision layer built on top of
|
|
100
|
+
* `ToolCallStreamCoordinator`. Answers one question per tool call: is it
|
|
101
|
+
* safe to execute right now? Callers never need to touch parser lexical
|
|
102
|
+
* state, coordinator event mechanics, or JSON Pointer paths - push provider
|
|
103
|
+
* events in, read `ExecutionDecision`s out.
|
|
104
|
+
*/
|
|
105
|
+
export interface ToolCallExecutionGate {
|
|
106
|
+
push(event: NormalizedToolStreamEvent): CoordinatorPushResult;
|
|
107
|
+
/**
|
|
108
|
+
* In-flight view. Every decision here is `"retry"` or `"reject"` -
|
|
109
|
+
* `"execute"` is only ever returned once the stream has genuinely
|
|
110
|
+
* finished (see `finish()`), since schema validity and prefix-safety are
|
|
111
|
+
* both only meaningful once a call has settled.
|
|
112
|
+
*/
|
|
113
|
+
snapshot(): readonly ExecutionDecision[];
|
|
114
|
+
/** Drains the underlying coordinator's raw event queue, for advanced/UI consumers. */
|
|
115
|
+
drainEvents(): readonly ToolCallCoordinatorEvent[];
|
|
116
|
+
finish(meta?: {
|
|
117
|
+
reason?: StreamEndReason;
|
|
118
|
+
providerReason?: string;
|
|
119
|
+
}): ToolCallExecutionGateFinalResult;
|
|
120
|
+
}
|
|
121
|
+
export {};
|
|
122
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/gate/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAExF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE7D;;;GAGG;AACH,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,WAAW,GACX,mBAAmB,GACnB,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,gBAAgB,GAChB,kBAAkB,GAClB,SAAS,CAAC;AAEd,UAAU,uBAAuB;IAC/B,mFAAmF;IACnF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IAEjC,yFAAyF;IACzF,QAAQ,CAAC,iBAAiB,EAAE,SAAS,UAAU,EAAE,CAAC;IAElD,yEAAyE;IACzE,QAAQ,CAAC,sBAAsB,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACnE;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAgB,SAAQ,uBAAuB;IAC9D,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAE1B;;;;;;;;OAQG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAsB,SAAQ,uBAAuB;IACpE,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;CACvD;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG,qBAAqB,CAAC;AAExE,MAAM,WAAW,gCAAgC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAEjD;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACxD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,KAAK,EAAE,yBAAyB,GAAG,qBAAqB,CAAC;IAE9D;;;;;OAKG;IACH,QAAQ,IAAI,SAAS,iBAAiB,EAAE,CAAC;IAEzC,sFAAsF;IACtF,WAAW,IAAI,SAAS,wBAAwB,EAAE,CAAC;IAEnD,MAAM,CAAC,IAAI,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,eAAe,CAAC;QACzB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,gCAAgC,CAAC;CACtC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/gate/types.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,17 @@ export { DiagnosticCode } from "./diagnostics/codes.js";
|
|
|
11
11
|
* Creates a stream coordinator for mapping overlapping multi-tool streams.
|
|
12
12
|
*/
|
|
13
13
|
export { createToolCallStreamCoordinator } from "./coordinator/coordinator.js";
|
|
14
|
-
export type { ToolCallStreamCoordinator, ToolCallState, JsonSchemaLike, } from "./coordinator/types.js";
|
|
15
|
-
export type { NormalizedToolStreamEvent } from "./coordinator/protocol.js";
|
|
14
|
+
export type { ToolCallStreamCoordinator, ToolCallState, JsonSchemaLike, CoordinatorLimits, CoordinatorDiagnostic, } from "./coordinator/types.js";
|
|
15
|
+
export type { NormalizedToolStreamEvent, ProviderName } from "./coordinator/protocol.js";
|
|
16
|
+
export { CONTENT_FILTERED_DIAGNOSTIC_CODE } from "./coordinator/diagnostic-codes.js";
|
|
17
|
+
/**
|
|
18
|
+
* @public (Stable)
|
|
19
|
+
* Fail-closed execution-decision layer built on top of the coordinator.
|
|
20
|
+
* Answers one question per tool call: is it safe to execute right now?
|
|
21
|
+
* See docs/EXECUTION_GATE.md.
|
|
22
|
+
*/
|
|
23
|
+
export { createToolCallExecutionGate } from "./gate/gate.js";
|
|
24
|
+
export type { ToolCallExecutionGate, ToolCallExecutionGateFinalResult, ExecutionDecision, ExecuteDecision, NonExecutableDecision, ExecutionAction, ExecutionReason, } from "./gate/types.js";
|
|
16
25
|
export type { ProviderStreamAdapter } from "./providers/adapter.js";
|
|
17
26
|
/**
|
|
18
27
|
* @public (Experimental)
|
|
@@ -24,4 +33,5 @@ export { OpenAICompatibleStreamAdapter } from "./providers/openai-compatible.js"
|
|
|
24
33
|
export { AnthropicStreamAdapter } from "./providers/anthropic.js";
|
|
25
34
|
export { GeminiStreamAdapter } from "./providers/gemini.js";
|
|
26
35
|
export { OpenRouterStreamAdapter } from "./providers/openrouter.js";
|
|
36
|
+
export { AiSdkStreamAdapter } from "./providers/ai-sdk.js";
|
|
27
37
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,YAAY,EAEV,qBAAqB,EACrB,aAAa,EAGb,UAAU,EACV,cAAc,EACd,WAAW,EAGX,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EAGnB,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGxD;;;GAGG;AACH,OAAO,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAC/E,YAAY,EACV,yBAAyB,EACzB,aAAa,EACb,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,YAAY,EAEV,qBAAqB,EACrB,aAAa,EAGb,UAAU,EACV,cAAc,EACd,WAAW,EAGX,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EAGnB,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGxD;;;GAGG;AACH,OAAO,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAC/E,YAAY,EACV,yBAAyB,EACzB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzF,OAAO,EAAE,gCAAgC,EAAE,MAAM,mCAAmC,CAAC;AAGrF;;;;;GAKG;AACH,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAC7D,YAAY,EACV,qBAAqB,EACrB,gCAAgC,EAChC,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,15 @@ export { DiagnosticCode } from "./diagnostics/codes.js";
|
|
|
14
14
|
* Creates a stream coordinator for mapping overlapping multi-tool streams.
|
|
15
15
|
*/
|
|
16
16
|
export { createToolCallStreamCoordinator } from "./coordinator/coordinator.js";
|
|
17
|
+
export { CONTENT_FILTERED_DIAGNOSTIC_CODE } from "./coordinator/diagnostic-codes.js";
|
|
18
|
+
// Execution Safety Gate API
|
|
19
|
+
/**
|
|
20
|
+
* @public (Stable)
|
|
21
|
+
* Fail-closed execution-decision layer built on top of the coordinator.
|
|
22
|
+
* Answers one question per tool call: is it safe to execute right now?
|
|
23
|
+
* See docs/EXECUTION_GATE.md.
|
|
24
|
+
*/
|
|
25
|
+
export { createToolCallExecutionGate } from "./gate/gate.js";
|
|
17
26
|
/**
|
|
18
27
|
* @public (Experimental)
|
|
19
28
|
* Provider adapters for various LLM network shapes.
|
|
@@ -24,4 +33,5 @@ export { OpenAICompatibleStreamAdapter } from "./providers/openai-compatible.js"
|
|
|
24
33
|
export { AnthropicStreamAdapter } from "./providers/anthropic.js";
|
|
25
34
|
export { GeminiStreamAdapter } from "./providers/gemini.js";
|
|
26
35
|
export { OpenRouterStreamAdapter } from "./providers/openrouter.js";
|
|
36
|
+
export { AiSdkStreamAdapter } from "./providers/ai-sdk.js";
|
|
27
37
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiC3C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,kBAAkB;AAClB;;;GAGG;AACH,OAAO,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiC3C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,kBAAkB;AAClB;;;GAGG;AACH,OAAO,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAS/E,OAAO,EAAE,gCAAgC,EAAE,MAAM,mCAAmC,CAAC;AAErF,4BAA4B;AAC5B;;;;;GAKG;AACH,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAc7D;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ProviderName, NormalizedToolStreamEvent, StreamEndReason } from "../coordinator/protocol.js";
|
|
2
|
+
import { ProviderStreamAdapter } from "./adapter.js";
|
|
3
|
+
interface AiSdkToolInputStartPart {
|
|
4
|
+
type: "tool-input-start";
|
|
5
|
+
id?: string;
|
|
6
|
+
toolCallId?: string;
|
|
7
|
+
toolName?: string;
|
|
8
|
+
}
|
|
9
|
+
interface AiSdkToolInputDeltaPart {
|
|
10
|
+
type: "tool-input-delta";
|
|
11
|
+
id?: string;
|
|
12
|
+
toolCallId?: string;
|
|
13
|
+
delta?: string;
|
|
14
|
+
}
|
|
15
|
+
interface AiSdkToolInputEndPart {
|
|
16
|
+
type: "tool-input-end";
|
|
17
|
+
id?: string;
|
|
18
|
+
toolCallId?: string;
|
|
19
|
+
}
|
|
20
|
+
interface AiSdkToolCallPart {
|
|
21
|
+
type: "tool-call";
|
|
22
|
+
toolCallId?: string;
|
|
23
|
+
toolName?: string;
|
|
24
|
+
}
|
|
25
|
+
interface AiSdkToolErrorPart {
|
|
26
|
+
type: "tool-error";
|
|
27
|
+
toolCallId?: string;
|
|
28
|
+
toolName?: string;
|
|
29
|
+
error?: unknown;
|
|
30
|
+
}
|
|
31
|
+
interface AiSdkFinishPart {
|
|
32
|
+
type: "finish";
|
|
33
|
+
finishReason?: string;
|
|
34
|
+
}
|
|
35
|
+
interface AiSdkErrorPart {
|
|
36
|
+
type: "error";
|
|
37
|
+
error?: unknown;
|
|
38
|
+
}
|
|
39
|
+
export type AiSdkStreamPart = AiSdkToolInputStartPart | AiSdkToolInputDeltaPart | AiSdkToolInputEndPart | AiSdkToolCallPart | AiSdkToolErrorPart | AiSdkFinishPart | AiSdkErrorPart | {
|
|
40
|
+
type: string;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
};
|
|
43
|
+
export declare class AiSdkStreamAdapter implements ProviderStreamAdapter<unknown> {
|
|
44
|
+
readonly provider: ProviderName;
|
|
45
|
+
private sequence;
|
|
46
|
+
private finished;
|
|
47
|
+
push(rawEvent: unknown): readonly NormalizedToolStreamEvent[];
|
|
48
|
+
finish(meta?: {
|
|
49
|
+
reason?: StreamEndReason;
|
|
50
|
+
providerReason?: string;
|
|
51
|
+
}): readonly NormalizedToolStreamEvent[];
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=ai-sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk.d.ts","sourceRoot":"","sources":["../../src/providers/ai-sdk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACtG,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAiBrD,UAAU,uBAAuB;IAC/B,IAAI,EAAE,kBAAkB,CAAC;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,uBAAuB;IAC/B,IAAI,EAAE,kBAAkB,CAAC;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,qBAAqB;IAC7B,IAAI,EAAE,gBAAgB,CAAC;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,iBAAiB;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAGnB;AAED,UAAU,kBAAkB;IAC1B,IAAI,EAAE,YAAY,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,UAAU,eAAe;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,eAAe,GACvB,uBAAuB,GACvB,uBAAuB,GACvB,qBAAqB,GACrB,iBAAiB,GACjB,kBAAkB,GAClB,eAAe,GACf,cAAc,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AA4B7C,qBAAa,kBAAmB,YAAW,qBAAqB,CAAC,OAAO,CAAC;IACvE,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAY;IAC3C,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAS;IAEzB,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,yBAAyB,EAAE;IA2J7D,MAAM,CAAC,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,eAAe,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,yBAAyB,EAAE;CAW3G"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { CONTENT_FILTERED_DIAGNOSTIC_CODE } from "../coordinator/diagnostic-codes.js";
|
|
2
|
+
function toolPartId(part) {
|
|
3
|
+
// "tool-input-start"/"tool-input-delta" carry `id`; "tool-input-end" is
|
|
4
|
+
// documented with `toolCallId`. Reading both defensively means this
|
|
5
|
+
// adapter keeps working correlated-by-ID either way, rather than silently
|
|
6
|
+
// dropping events if one part type uses the other field name.
|
|
7
|
+
return part.id ?? part.toolCallId;
|
|
8
|
+
}
|
|
9
|
+
function stringifyError(error) {
|
|
10
|
+
if (error instanceof Error)
|
|
11
|
+
return error.message;
|
|
12
|
+
if (typeof error === "string")
|
|
13
|
+
return error;
|
|
14
|
+
if (error &&
|
|
15
|
+
typeof error === "object" &&
|
|
16
|
+
"message" in error &&
|
|
17
|
+
typeof error.message === "string") {
|
|
18
|
+
return error.message;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return JSON.stringify(error);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return String(error);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class AiSdkStreamAdapter {
|
|
28
|
+
provider = "ai-sdk";
|
|
29
|
+
sequence = 0;
|
|
30
|
+
finished = false;
|
|
31
|
+
push(rawEvent) {
|
|
32
|
+
if (this.finished)
|
|
33
|
+
return [];
|
|
34
|
+
const events = [];
|
|
35
|
+
if (!rawEvent || typeof rawEvent !== "object") {
|
|
36
|
+
events.push({
|
|
37
|
+
type: "provider_diagnostic",
|
|
38
|
+
sequence: ++this.sequence,
|
|
39
|
+
provider: this.provider,
|
|
40
|
+
code: "E_PROVIDER_EVENT_MALFORMED",
|
|
41
|
+
severity: "error",
|
|
42
|
+
message: "Raw event is not an object",
|
|
43
|
+
});
|
|
44
|
+
return events;
|
|
45
|
+
}
|
|
46
|
+
// A single permissive cast (rather than narrowing the `AiSdkStreamPart`
|
|
47
|
+
// union directly) matches the other adapters in this directory - their
|
|
48
|
+
// catch-all `{ type: string; [key: string]: unknown }` union member
|
|
49
|
+
// means the compiler can never fully exclude it inside a `switch (type)`
|
|
50
|
+
// case, so property access still needs a shape where every field used
|
|
51
|
+
// below is already optional.
|
|
52
|
+
const part = rawEvent;
|
|
53
|
+
switch (part.type) {
|
|
54
|
+
case "tool-input-start": {
|
|
55
|
+
const id = toolPartId(part);
|
|
56
|
+
if (id === undefined)
|
|
57
|
+
break;
|
|
58
|
+
events.push({
|
|
59
|
+
type: "tool_call_start",
|
|
60
|
+
sequence: ++this.sequence,
|
|
61
|
+
provider: this.provider,
|
|
62
|
+
callRef: { sourceKey: `tool-input:${id}` },
|
|
63
|
+
toolCallId: id,
|
|
64
|
+
name: part.toolName,
|
|
65
|
+
});
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "tool-input-delta": {
|
|
69
|
+
const id = toolPartId(part);
|
|
70
|
+
if (id === undefined || typeof part.delta !== "string")
|
|
71
|
+
break;
|
|
72
|
+
events.push({
|
|
73
|
+
type: "tool_call_arguments_delta",
|
|
74
|
+
sequence: ++this.sequence,
|
|
75
|
+
provider: this.provider,
|
|
76
|
+
callRef: { sourceKey: `tool-input:${id}` },
|
|
77
|
+
delta: part.delta,
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case "tool-input-end": {
|
|
82
|
+
const id = toolPartId(part);
|
|
83
|
+
if (id === undefined)
|
|
84
|
+
break;
|
|
85
|
+
events.push({
|
|
86
|
+
type: "tool_call_end",
|
|
87
|
+
sequence: ++this.sequence,
|
|
88
|
+
provider: this.provider,
|
|
89
|
+
callRef: { sourceKey: `tool-input:${id}` },
|
|
90
|
+
reason: "complete", // corrected by the stream-level "finish" part below
|
|
91
|
+
});
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "tool-call": {
|
|
95
|
+
// Intentionally a no-op. This part carries the SDK's own resolved
|
|
96
|
+
// `input` for the call, which - per this library's whole reason for
|
|
97
|
+
// existing (see README's Cline / `fixJson` analysis) - may already
|
|
98
|
+
// be silently repaired from a truncated stream. Trusting it here
|
|
99
|
+
// would reintroduce exactly the problem this adapter exists to
|
|
100
|
+
// avoid. The call's real argument text was already captured
|
|
101
|
+
// verbatim from "tool-input-delta" parts above, byte for byte.
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "tool-error": {
|
|
105
|
+
const id = toolPartId(part);
|
|
106
|
+
events.push({
|
|
107
|
+
type: "provider_diagnostic",
|
|
108
|
+
sequence: ++this.sequence,
|
|
109
|
+
provider: this.provider,
|
|
110
|
+
code: "E_PROVIDER_TOOL_ERROR",
|
|
111
|
+
severity: "error",
|
|
112
|
+
message: `Tool "${part.toolName ?? "unknown"}" reported an error: ${stringifyError(part.error)}`,
|
|
113
|
+
...(id !== undefined ? { callRef: { sourceKey: `tool-input:${id}` } } : {}),
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case "finish": {
|
|
118
|
+
const finishReason = part.finishReason;
|
|
119
|
+
let reason = "unknown";
|
|
120
|
+
if (finishReason === "stop" || finishReason === "tool-calls") {
|
|
121
|
+
reason = "complete";
|
|
122
|
+
}
|
|
123
|
+
else if (finishReason === "length") {
|
|
124
|
+
reason = "length";
|
|
125
|
+
}
|
|
126
|
+
else if (finishReason === "content-filter") {
|
|
127
|
+
// Not a generic cancellation: a policy/safety termination should
|
|
128
|
+
// never be blindly retried. Recorded as its own machine-readable
|
|
129
|
+
// diagnostic so the execution gate can reject with reason
|
|
130
|
+
// "content_filtered" instead of the generic "stream_incomplete"
|
|
131
|
+
// it would otherwise fall back to for a "cancelled" end reason.
|
|
132
|
+
reason = "cancelled";
|
|
133
|
+
events.push({
|
|
134
|
+
type: "provider_diagnostic",
|
|
135
|
+
sequence: ++this.sequence,
|
|
136
|
+
provider: this.provider,
|
|
137
|
+
code: CONTENT_FILTERED_DIAGNOSTIC_CODE,
|
|
138
|
+
severity: "error",
|
|
139
|
+
message: "Generation stopped by the model provider's content filter",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else if (finishReason === "error") {
|
|
143
|
+
reason = "provider_error";
|
|
144
|
+
}
|
|
145
|
+
// "other" (and any future/unrecognized literal) intentionally falls
|
|
146
|
+
// through to "unknown" - fail closed on an unclassified reason
|
|
147
|
+
// rather than guessing it means "complete".
|
|
148
|
+
events.push({
|
|
149
|
+
type: "provider_stream_end",
|
|
150
|
+
sequence: ++this.sequence,
|
|
151
|
+
provider: this.provider,
|
|
152
|
+
reason,
|
|
153
|
+
providerReason: finishReason,
|
|
154
|
+
});
|
|
155
|
+
this.finished = true;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case "error": {
|
|
159
|
+
events.push({
|
|
160
|
+
type: "provider_stream_end",
|
|
161
|
+
sequence: ++this.sequence,
|
|
162
|
+
provider: this.provider,
|
|
163
|
+
reason: "provider_error",
|
|
164
|
+
providerReason: stringifyError(part.error),
|
|
165
|
+
});
|
|
166
|
+
this.finished = true;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
// Unrelated part types (text-delta, reasoning-delta, tool-result,
|
|
170
|
+
// start, start-step, finish-step, etc.) carry no tool-argument or
|
|
171
|
+
// stream-termination information for our purposes and must not crash
|
|
172
|
+
// the adapter.
|
|
173
|
+
default:
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
return events;
|
|
177
|
+
}
|
|
178
|
+
finish(meta) {
|
|
179
|
+
if (this.finished)
|
|
180
|
+
return [];
|
|
181
|
+
this.finished = true;
|
|
182
|
+
return [{
|
|
183
|
+
type: "provider_stream_end",
|
|
184
|
+
sequence: ++this.sequence,
|
|
185
|
+
provider: this.provider,
|
|
186
|
+
reason: meta?.reason ?? "unknown",
|
|
187
|
+
providerReason: meta?.providerReason,
|
|
188
|
+
}];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=ai-sdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk.js","sourceRoot":"","sources":["../../src/providers/ai-sdk.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.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.3",
|
|
4
4
|
"description": "Incremental LLM tool-call JSON parser and deterministic repair engine — never reports a value until it's unambiguously complete",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"json",
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
"validate-corpus": "tsx scripts/validate-corpus.ts",
|
|
59
59
|
"clean": "rimraf dist coverage .vitest",
|
|
60
60
|
"pack:check": "pnpm pack --dry-run",
|
|
61
|
-
"example:anthropic": "node examples/anthropic-truncation-safety.mjs"
|
|
61
|
+
"example:anthropic": "node examples/anthropic-truncation-safety.mjs",
|
|
62
|
+
"example:ai-sdk": "node examples/ai-sdk-execution-gate.mjs"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@eslint/js": "^9.8.0",
|