ag-ui-validate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/dist/catalog-BglXBNbL.js +472 -0
- package/dist/catalog-BglXBNbL.js.map +1 -0
- package/dist/catalog-Ci9dqc1a.cjs +495 -0
- package/dist/catalog-Ci9dqc1a.cjs.map +1 -0
- package/dist/cli.js +2783 -0
- package/dist/cli.js.map +1 -0
- package/dist/index-Hmqj3r_r.d.cts +52 -0
- package/dist/index-oNG1kOp9.d.ts +52 -0
- package/dist/index.cjs +14 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/report.cjs +139 -0
- package/dist/report.cjs.map +1 -0
- package/dist/report.d.cts +85 -0
- package/dist/report.d.ts +85 -0
- package/dist/report.js +134 -0
- package/dist/report.js.map +1 -0
- package/dist/src-HmI-kxef.cjs +1596 -0
- package/dist/src-HmI-kxef.cjs.map +1 -0
- package/dist/src-rGZ2G4qA.js +1555 -0
- package/dist/src-rGZ2G4qA.js.map +1 -0
- package/dist/transport.cjs +329 -0
- package/dist/transport.cjs.map +1 -0
- package/dist/transport.d.cts +89 -0
- package/dist/transport.d.ts +89 -0
- package/dist/transport.js +323 -0
- package/dist/transport.js.map +1 -0
- package/dist/types-oH_QTnn2.d.cts +148 -0
- package/dist/types-oH_QTnn2.d.ts +148 -0
- package/dist/vitest.d.ts +28 -0
- package/dist/vitest.js +2089 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +127 -0
- package/src/cli-args.ts +202 -0
- package/src/cli.ts +147 -0
- package/src/index.ts +465 -0
- package/src/protocol/event-table.ts +316 -0
- package/src/protocol/jsonpatch.ts +220 -0
- package/src/report/index.ts +10 -0
- package/src/report/json.ts +20 -0
- package/src/report/junit.ts +56 -0
- package/src/report/pretty.ts +59 -0
- package/src/report/sarif.ts +109 -0
- package/src/rules/catalog.json +431 -0
- package/src/rules/catalog.ts +84 -0
- package/src/rules/checks/context.ts +117 -0
- package/src/rules/checks/lifecycle.ts +59 -0
- package/src/rules/checks/reasoning.ts +97 -0
- package/src/rules/checks/state.ts +72 -0
- package/src/rules/checks/text.ts +109 -0
- package/src/rules/checks/toolcalls.ts +167 -0
- package/src/rules/checks/transport.ts +17 -0
- package/src/transport/index.ts +331 -0
- package/src/transport/ndjson.ts +25 -0
- package/src/transport/sse.ts +126 -0
- package/src/types.ts +136 -0
- package/src/vitest/index.ts +19 -0
- package/src/vitest/matcher.ts +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Faraz (langport.dev)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# ag-ui-validate
|
|
2
|
+
|
|
3
|
+
Conformance validator for the [AG-UI protocol](https://docs.ag-ui.com)
|
|
4
|
+
(Agent–User Interaction Protocol). Point it at an AG-UI endpoint — or feed it
|
|
5
|
+
a recorded event stream — and it reports every way the stream violates the
|
|
6
|
+
protocol, with a rule ID, a severity, a location, and a link to the governing
|
|
7
|
+
spec section.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
✖ AGUI203 error event 42 TOOL_CALL_START id 'call_7' never terminated
|
|
11
|
+
✖ AGUI302 error event 51 STATE_DELTA failed to apply: /items/3: '3' is not a valid index for an array of length 0
|
|
12
|
+
✖ AGUI503 error event 60 Unknown event type 'runStarted' — did you mean 'RUN_STARTED'?
|
|
13
|
+
ℹ AGUI902 info — None of the 61 events carry the optional timestamp property
|
|
14
|
+
|
|
15
|
+
2 errors, 0 warnings, 1 info — 3 of 7 AG-UI features exercised
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> **Status: pre-release.** The core validator, the language-neutral fixture
|
|
19
|
+
> corpus, and the transport layer are implemented and tested. The CLI
|
|
20
|
+
> (`npx ag-ui-validate <url|->`), the Vitest matcher, and SARIF/JUnit
|
|
21
|
+
> reporters are in progress.
|
|
22
|
+
|
|
23
|
+
## Why
|
|
24
|
+
|
|
25
|
+
AG-UI has SDKs and integrations, but no conformance tooling: nothing tells an
|
|
26
|
+
implementer *your stream is subtly wrong, here's the rule and the spec
|
|
27
|
+
section*. This project is that tool — the AG-UI analogue of what
|
|
28
|
+
[a2a-inspector](https://github.com/a2aproject/a2a-inspector) is for A2A.
|
|
29
|
+
|
|
30
|
+
Three design commitments make it trustworthy:
|
|
31
|
+
|
|
32
|
+
- **Every diagnostic cites the spec.** Each of the 40 rules carries a
|
|
33
|
+
`specUrl` (and where possible an exact `specQuote`) pointing at the
|
|
34
|
+
governing section of [docs.ag-ui.com](https://docs.ag-ui.com) or the WHATWG
|
|
35
|
+
SSE spec. Behaviour the spec doesn't clearly govern is reported at `info`
|
|
36
|
+
severity at most, and logged in
|
|
37
|
+
[docs/spec-questions.md](docs/spec-questions.md) for filing upstream.
|
|
38
|
+
- **The validator never throws.** Broken input is its input. Malformed JSON,
|
|
39
|
+
unknown event types, hostile objects — all diagnostics, never exceptions
|
|
40
|
+
(fuzz-tested against 50k hostile inputs).
|
|
41
|
+
- **False positives are treated as worse than false negatives.** The rules are
|
|
42
|
+
grounded in `@ag-ui/core` v0.0.58 and the current docs; where the two
|
|
43
|
+
disagree, the SDK wins and the discrepancy is recorded.
|
|
44
|
+
|
|
45
|
+
## Quickstart
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install --save-dev ag-ui-validate
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### CLI
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx ag-ui-validate http://localhost:8000/agui # live endpoint (POSTs a RunAgentInput)
|
|
55
|
+
npx ag-ui-validate run.jsonl # recorded stream (NDJSON/JSONL or SSE capture)
|
|
56
|
+
cat run.jsonl | npx ag-ui-validate - # stdin
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Exit codes: `0` clean, `1` findings at error level (or warnings over
|
|
60
|
+
`--max-warnings`), `2` tool failure. Timing-based transport rules are
|
|
61
|
+
meaningless for recordings, so they are reported as *skipped with a reason*
|
|
62
|
+
rather than risking false positives.
|
|
63
|
+
|
|
64
|
+
Useful flags (see `--help` for all):
|
|
65
|
+
|
|
66
|
+
| Flag | Effect |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `--json` / `--sarif` / `--junit` | machine-readable report on stdout (SARIF 2.1.0 for code scanning, JUnit XML for CI) |
|
|
69
|
+
| `--rule AGUI105=error`, `--off AGUI902` | per-rule severity overrides |
|
|
70
|
+
| `--features shared-state,...` | declare exercised features (enables e.g. AGUI305) |
|
|
71
|
+
| `--max-warnings 0` | fail CI on any warning |
|
|
72
|
+
| `--header "Authorization: Bearer …"`, `--timeout 30` | endpoint options |
|
|
73
|
+
|
|
74
|
+
### Validate in CI (GitHub Action)
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
- uses: langport-dev/ag-ui-validate/action@main
|
|
78
|
+
with:
|
|
79
|
+
target: http://localhost:8000/agui # or a recorded .jsonl file
|
|
80
|
+
sarif-file: agui.sarif # optional: upload via codeql-action
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The step fails on error-severity findings, writes a findings table to the job
|
|
84
|
+
summary, and exposes `errors`/`warnings`/`info` outputs — see
|
|
85
|
+
[action/README.md](action/README.md).
|
|
86
|
+
|
|
87
|
+
### Test your agent in Vitest
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import "ag-ui-validate/vitest" // registers the matcher (put it in setupFiles)
|
|
91
|
+
|
|
92
|
+
it("streams a conformant run", async () => {
|
|
93
|
+
const events = await captureRunEvents(myAgent) // however you record them
|
|
94
|
+
expect(events).toBeValidAGUI()
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The matcher takes an array of events (objects or JSON strings) or a whole
|
|
99
|
+
JSONL capture as one string. Failures print each finding with its rule ID and
|
|
100
|
+
spec link. Options mirror the validator:
|
|
101
|
+
`{ features, severityOverrides, maxWarnings }` — e.g.
|
|
102
|
+
`expect(events).toBeValidAGUI({ maxWarnings: 0 })` to fail on warnings too.
|
|
103
|
+
The raw matcher function is also exported, so Jest users can
|
|
104
|
+
`expect.extend({ toBeValidAGUI })` themselves.
|
|
105
|
+
|
|
106
|
+
### Validate recorded events (pure, runs anywhere)
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { createValidator } from "ag-ui-validate"
|
|
110
|
+
|
|
111
|
+
const v = createValidator({
|
|
112
|
+
features: ["shared-state"], // optional: enables feature-specific rules
|
|
113
|
+
severityOverrides: { AGUI902: "off" }, // optional: tune or disable rules
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
for (const event of events) {
|
|
117
|
+
// feed parsed objects or raw JSON strings — bad JSON is a diagnostic
|
|
118
|
+
const diagnostics = v.feed(event) // findings, as soon as detectable
|
|
119
|
+
}
|
|
120
|
+
v.finalize() // end-of-stream checks
|
|
121
|
+
|
|
122
|
+
const { diagnostics, summary, features, skipped } = v.report()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The core is a pure function over an event sequence: zero I/O, zero runtime
|
|
126
|
+
dependencies, isomorphic across Node 20+, browsers, Deno, and Workers.
|
|
127
|
+
|
|
128
|
+
### Validate a live endpoint
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { validateEndpoint } from "ag-ui-validate/transport"
|
|
132
|
+
|
|
133
|
+
const { report, status, eventCount } = await validateEndpoint(
|
|
134
|
+
"http://localhost:8000/agui",
|
|
135
|
+
{
|
|
136
|
+
headers: { authorization: "Bearer …" },
|
|
137
|
+
onDiagnostic: (d) => console.error(`${d.severity} ${d.rule} ${d.message}`),
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The transport layer POSTs a minimal `RunAgentInput`, consumes the SSE or
|
|
143
|
+
NDJSON response, streams every frame through the core, and additionally
|
|
144
|
+
evaluates the transport-level rules that recorded input can't exercise: SSE
|
|
145
|
+
framing (including the classic missing-`data:`-prefix bug), Content-Type,
|
|
146
|
+
keepalive gaps, buffered-not-flushed responses, and mid-run disconnects.
|
|
147
|
+
|
|
148
|
+
### Render a report
|
|
149
|
+
|
|
150
|
+
The CLI's output formats are plain functions over a `Report`, importable for
|
|
151
|
+
your own tooling:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { formatReportSummary, toSarif, toJUnit } from "ag-ui-validate/report"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Diagnostic shape
|
|
158
|
+
|
|
159
|
+
```jsonc
|
|
160
|
+
{
|
|
161
|
+
"rule": "AGUI203",
|
|
162
|
+
"severity": "error", // "error" | "warning" | "info"
|
|
163
|
+
"message": "TOOL_CALL_START id 'call_7' never terminated",
|
|
164
|
+
"eventIndex": 42, // 0-based; -1 for end-of-stream findings
|
|
165
|
+
"eventType": "RUN_FINISHED", // optional
|
|
166
|
+
"pointer": "/toolCallId", // optional RFC 6901 pointer into the event
|
|
167
|
+
"relatedEventIndex": 17, // optional, e.g. the unterminated start
|
|
168
|
+
"specUrl": "https://docs.ag-ui.com/concepts/events#tool-call-events"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## The rule catalog
|
|
173
|
+
|
|
174
|
+
40 rules, maintained as **data** in
|
|
175
|
+
[src/rules/catalog.json](src/rules/catalog.json) so other implementations
|
|
176
|
+
(Python, Go, …) can share them. Every rule has its own page — spec grounding,
|
|
177
|
+
severity, and a violating example from the corpus — in the
|
|
178
|
+
**[rule index](docs/rules/README.md)** (generated from the catalog,
|
|
179
|
+
drift-checked in CI):
|
|
180
|
+
|
|
181
|
+
| Group | IDs | Examples |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| Lifecycle | AGUI001–008 | run must start with `RUN_STARTED`, terminate with `RUN_FINISHED`/`RUN_ERROR`, nothing after a terminal event |
|
|
184
|
+
| Text messages | AGUI101–106 | content without start, unterminated messages, duplicate `messageId` |
|
|
185
|
+
| Tool calls | AGUI201–208 | unterminated calls, args that don't concatenate to valid JSON, results referencing unknown calls |
|
|
186
|
+
| State | AGUI301–305 | RFC 6902 patch validity, deltas that fail to apply to reconstructed state |
|
|
187
|
+
| Reasoning | AGUI401–402 | reasoning content without an open reasoning message |
|
|
188
|
+
| Transport | AGUI501–508 | SSE framing, Content-Type, keepalive gaps, buffering, dropped connections |
|
|
189
|
+
| Hygiene | AGUI901–903 | `RAW`-wrapping typed events, missing timestamps, un-namespaced `CUSTOM` names |
|
|
190
|
+
|
|
191
|
+
The event taxonomy itself (33 wire types, field schemas) is derived from
|
|
192
|
+
[`@ag-ui/core`](https://www.npmjs.com/package/@ag-ui/core)'s own schemas and
|
|
193
|
+
drift-tested against the installed SDK on every run.
|
|
194
|
+
|
|
195
|
+
## The fixture corpus
|
|
196
|
+
|
|
197
|
+
[fixtures/](fixtures/README.md) is a language-neutral conformance corpus:
|
|
198
|
+
7 valid streams (one per canonical AG-UI feature — the false-positive guards)
|
|
199
|
+
and 40 invalid fixtures (one per rule) with exact expected diagnostics. Any
|
|
200
|
+
validator implementation that consumes the shared catalog can be tested
|
|
201
|
+
against it; the replay protocol is documented in the corpus README.
|
|
202
|
+
|
|
203
|
+
## Development
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
npm ci
|
|
207
|
+
npm run typecheck # includes a src-only pass proving the core uses no Node APIs
|
|
208
|
+
npm run build # dual ESM/CJS via tsdown
|
|
209
|
+
npm test # full suite: unit + corpus + drift + purity + SDK alignment
|
|
210
|
+
npm run demo # pretty-printed findings for a deliberately broken stream
|
|
211
|
+
npm run e2e # live-transport checks against a real local HTTP server
|
|
212
|
+
npm run fuzz # 50k hostile inputs against the never-throws invariant
|
|
213
|
+
npm run links:check # every specUrl resolves and every anchor exists
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Component-by-component instructions live in
|
|
217
|
+
[docs/TESTING.md](docs/TESTING.md). Spec ambiguities found while grounding the
|
|
218
|
+
rules are tracked in [docs/spec-questions.md](docs/spec-questions.md).
|
|
219
|
+
|
|
220
|
+
Adding a rule: add the catalog entry (with its `specUrl`), add the fixture
|
|
221
|
+
stream + intended findings to `scripts/build-fixtures.mjs`, and run
|
|
222
|
+
`npm run fixtures:build` — the meta-tests fail until both exist. Rule
|
|
223
|
+
*proposals* belong upstream as issues on
|
|
224
|
+
[`ag-ui-protocol/ag-ui`](https://github.com/ag-ui-protocol/ag-ui) first; this
|
|
225
|
+
project does not invent rules the spec doesn't support.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT — maintained by [Faraz](https://langport.dev).
|