ag-ui-validate 0.2.0 → 0.4.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.
Files changed (168) hide show
  1. package/README.md +82 -23
  2. package/dist/{catalog-BglXBNbL.js → catalog-3e6INLCp.js} +122 -4
  3. package/dist/catalog-3e6INLCp.js.map +1 -0
  4. package/dist/{catalog-Ci9dqc1a.cjs → catalog-DH5gDIbo.cjs} +122 -4
  5. package/dist/catalog-DH5gDIbo.cjs.map +1 -0
  6. package/dist/cli.js +641 -63
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{index-Hmqj3r_r.d.cts → index-Cydfs-rB.d.ts} +4 -4
  9. package/dist/{index-oNG1kOp9.d.ts → index-DsVmjcLO.d.cts} +4 -4
  10. package/dist/index.cjs +2 -2
  11. package/dist/index.d.cts +2 -2
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +2 -2
  14. package/dist/report.cjs +11 -2
  15. package/dist/report.cjs.map +1 -1
  16. package/dist/report.d.cts +21 -12
  17. package/dist/report.d.ts +21 -12
  18. package/dist/report.js +11 -2
  19. package/dist/report.js.map +1 -1
  20. package/dist/{src-rGZ2G4qA.js → src-CezZ73Mm.js} +494 -59
  21. package/dist/src-CezZ73Mm.js.map +1 -0
  22. package/dist/{src-HmI-kxef.cjs → src-CyugLoks.cjs} +494 -59
  23. package/dist/src-CyugLoks.cjs.map +1 -0
  24. package/dist/transport.cjs +1 -1
  25. package/dist/transport.cjs.map +1 -1
  26. package/dist/transport.d.cts +1 -1
  27. package/dist/transport.d.ts +1 -1
  28. package/dist/transport.js +1 -1
  29. package/dist/transport.js.map +1 -1
  30. package/dist/{types-oH_QTnn2.d.ts → types-CUIgyj2N.d.cts} +3 -1
  31. package/dist/{types-oH_QTnn2.d.cts → types-CUIgyj2N.d.ts} +3 -1
  32. package/dist/vitest.js +616 -61
  33. package/dist/vitest.js.map +1 -1
  34. package/{src → js/src}/cli-args.ts +16 -0
  35. package/{src → js/src}/cli.ts +1 -1
  36. package/{src → js/src}/index.ts +5 -0
  37. package/{src → js/src}/protocol/event-table.ts +93 -2
  38. package/js/src/report/json.ts +31 -0
  39. package/{src → js/src}/report/sarif.ts +3 -6
  40. package/{src → js/src}/rules/catalog.ts +5 -1
  41. package/{src → js/src}/rules/checks/context.ts +48 -3
  42. package/{src → js/src}/rules/checks/lifecycle.ts +5 -2
  43. package/js/src/rules/checks/subagents.ts +78 -0
  44. package/{src → js/src}/rules/checks/text.ts +11 -3
  45. package/{src → js/src}/rules/checks/toolcalls.ts +8 -2
  46. package/{src → js/src}/transport/index.ts +1 -1
  47. package/package.json +15 -14
  48. package/{src/rules → spec}/catalog.json +109 -2
  49. package/spec/event-categories.json +60 -0
  50. package/spec/fixtures/README.md +91 -0
  51. package/spec/fixtures/invalid/AGUI001-first-event-not-run-started/expected.json +10 -0
  52. package/spec/fixtures/invalid/AGUI001-first-event-not-run-started/stream.jsonl +4 -0
  53. package/spec/fixtures/invalid/AGUI002-duplicate-run-started/expected.json +11 -0
  54. package/spec/fixtures/invalid/AGUI002-duplicate-run-started/stream.jsonl +3 -0
  55. package/spec/fixtures/invalid/AGUI003-run-never-terminated/expected.json +10 -0
  56. package/spec/fixtures/invalid/AGUI003-run-never-terminated/stream.jsonl +4 -0
  57. package/spec/fixtures/invalid/AGUI004-event-after-terminal/expected.json +11 -0
  58. package/spec/fixtures/invalid/AGUI004-event-after-terminal/stream.jsonl +3 -0
  59. package/spec/fixtures/invalid/AGUI005-finished-and-error/expected.json +11 -0
  60. package/spec/fixtures/invalid/AGUI005-finished-and-error/stream.jsonl +3 -0
  61. package/spec/fixtures/invalid/AGUI006-step-finished-unmatched/expected.json +11 -0
  62. package/spec/fixtures/invalid/AGUI006-step-finished-unmatched/stream.jsonl +3 -0
  63. package/spec/fixtures/invalid/AGUI007-step-unterminated/expected.json +10 -0
  64. package/spec/fixtures/invalid/AGUI007-step-unterminated/stream.jsonl +3 -0
  65. package/spec/fixtures/invalid/AGUI008-unstable-run-ids/expected.json +12 -0
  66. package/spec/fixtures/invalid/AGUI008-unstable-run-ids/stream.jsonl +2 -0
  67. package/spec/fixtures/invalid/AGUI101-content-without-start/expected.json +11 -0
  68. package/spec/fixtures/invalid/AGUI101-content-without-start/stream.jsonl +3 -0
  69. package/spec/fixtures/invalid/AGUI102-end-without-start/expected.json +11 -0
  70. package/spec/fixtures/invalid/AGUI102-end-without-start/stream.jsonl +3 -0
  71. package/spec/fixtures/invalid/AGUI103-message-unterminated/expected.json +10 -0
  72. package/spec/fixtures/invalid/AGUI103-message-unterminated/stream.jsonl +4 -0
  73. package/spec/fixtures/invalid/AGUI104-duplicate-message-id/expected.json +12 -0
  74. package/spec/fixtures/invalid/AGUI104-duplicate-message-id/stream.jsonl +6 -0
  75. package/spec/fixtures/invalid/AGUI105-empty-content-delta/expected.json +12 -0
  76. package/spec/fixtures/invalid/AGUI105-empty-content-delta/stream.jsonl +5 -0
  77. package/spec/fixtures/invalid/AGUI106-interleaved-same-message-id/expected.json +12 -0
  78. package/spec/fixtures/invalid/AGUI106-interleaved-same-message-id/stream.jsonl +5 -0
  79. package/spec/fixtures/invalid/AGUI201-args-without-start/expected.json +11 -0
  80. package/spec/fixtures/invalid/AGUI201-args-without-start/stream.jsonl +3 -0
  81. package/spec/fixtures/invalid/AGUI202-end-without-start/expected.json +11 -0
  82. package/spec/fixtures/invalid/AGUI202-end-without-start/stream.jsonl +3 -0
  83. package/spec/fixtures/invalid/AGUI203-unterminated-tool-call/expected.json +10 -0
  84. package/spec/fixtures/invalid/AGUI203-unterminated-tool-call/stream.jsonl +4 -0
  85. package/spec/fixtures/invalid/AGUI204-args-not-json/expected.json +10 -0
  86. package/spec/fixtures/invalid/AGUI204-args-not-json/stream.jsonl +6 -0
  87. package/spec/fixtures/invalid/AGUI205-duplicate-tool-call-id/expected.json +12 -0
  88. package/spec/fixtures/invalid/AGUI205-duplicate-tool-call-id/stream.jsonl +5 -0
  89. package/spec/fixtures/invalid/AGUI206-result-before-end/expected.json +12 -0
  90. package/spec/fixtures/invalid/AGUI206-result-before-end/stream.jsonl +5 -0
  91. package/spec/fixtures/invalid/AGUI207-result-unknown-id/expected.json +11 -0
  92. package/spec/fixtures/invalid/AGUI207-result-unknown-id/stream.jsonl +3 -0
  93. package/spec/fixtures/invalid/AGUI208-unknown-parent-message-id/expected.json +11 -0
  94. package/spec/fixtures/invalid/AGUI208-unknown-parent-message-id/stream.jsonl +4 -0
  95. package/spec/fixtures/invalid/AGUI301-delta-before-snapshot/expected.json +10 -0
  96. package/spec/fixtures/invalid/AGUI301-delta-before-snapshot/stream.jsonl +3 -0
  97. package/spec/fixtures/invalid/AGUI302-delta-failed-to-apply/expected.json +11 -0
  98. package/spec/fixtures/invalid/AGUI302-delta-failed-to-apply/stream.jsonl +4 -0
  99. package/spec/fixtures/invalid/AGUI303-invalid-patch-document/expected.json +11 -0
  100. package/spec/fixtures/invalid/AGUI303-invalid-patch-document/stream.jsonl +4 -0
  101. package/spec/fixtures/invalid/AGUI304-midrun-snapshot-discards-deltas/expected.json +10 -0
  102. package/spec/fixtures/invalid/AGUI304-midrun-snapshot-discards-deltas/stream.jsonl +5 -0
  103. package/spec/fixtures/invalid/AGUI305-shared-state-never-established/expected.json +9 -0
  104. package/spec/fixtures/invalid/AGUI305-shared-state-never-established/options.json +5 -0
  105. package/spec/fixtures/invalid/AGUI305-shared-state-never-established/stream.jsonl +5 -0
  106. package/spec/fixtures/invalid/AGUI401-reasoning-content-without-start/expected.json +11 -0
  107. package/spec/fixtures/invalid/AGUI401-reasoning-content-without-start/stream.jsonl +3 -0
  108. package/spec/fixtures/invalid/AGUI402-reasoning-unterminated/expected.json +10 -0
  109. package/spec/fixtures/invalid/AGUI402-reasoning-unterminated/stream.jsonl +3 -0
  110. package/spec/fixtures/invalid/AGUI501-missing-data-prefix/expected.json +9 -0
  111. package/spec/fixtures/invalid/AGUI501-missing-data-prefix/scenario.json +17 -0
  112. package/spec/fixtures/invalid/AGUI502-payload-not-json/expected.json +10 -0
  113. package/spec/fixtures/invalid/AGUI502-payload-not-json/stream.jsonl +3 -0
  114. package/spec/fixtures/invalid/AGUI503-unknown-event-type/expected.json +10 -0
  115. package/spec/fixtures/invalid/AGUI503-unknown-event-type/stream.jsonl +3 -0
  116. package/spec/fixtures/invalid/AGUI504-schema-violation/expected.json +11 -0
  117. package/spec/fixtures/invalid/AGUI504-schema-violation/stream.jsonl +4 -0
  118. package/spec/fixtures/invalid/AGUI505-unexpected-content-type/expected.json +9 -0
  119. package/spec/fixtures/invalid/AGUI505-unexpected-content-type/scenario.json +25 -0
  120. package/spec/fixtures/invalid/AGUI506-keepalive-gap/expected.json +9 -0
  121. package/spec/fixtures/invalid/AGUI506-keepalive-gap/scenario.json +25 -0
  122. package/spec/fixtures/invalid/AGUI507-buffered-response/expected.json +9 -0
  123. package/spec/fixtures/invalid/AGUI507-buffered-response/scenario.json +9 -0
  124. package/spec/fixtures/invalid/AGUI508-connection-dropped/expected.json +17 -0
  125. package/spec/fixtures/invalid/AGUI508-connection-dropped/scenario.json +10 -0
  126. package/spec/fixtures/invalid/AGUI601-duplicate-subagent-started/expected.json +12 -0
  127. package/spec/fixtures/invalid/AGUI601-duplicate-subagent-started/stream.jsonl +5 -0
  128. package/spec/fixtures/invalid/AGUI602-finished-without-start/expected.json +11 -0
  129. package/spec/fixtures/invalid/AGUI602-finished-without-start/stream.jsonl +3 -0
  130. package/spec/fixtures/invalid/AGUI603-error-without-start/expected.json +11 -0
  131. package/spec/fixtures/invalid/AGUI603-error-without-start/stream.jsonl +3 -0
  132. package/spec/fixtures/invalid/AGUI604-subagent-unterminated/expected.json +10 -0
  133. package/spec/fixtures/invalid/AGUI604-subagent-unterminated/stream.jsonl +3 -0
  134. package/spec/fixtures/invalid/AGUI605-unknown-parent-subagent-run-id/expected.json +11 -0
  135. package/spec/fixtures/invalid/AGUI605-unknown-parent-subagent-run-id/stream.jsonl +4 -0
  136. package/spec/fixtures/invalid/AGUI606-continuation-owner-mismatch/expected.json +11 -0
  137. package/spec/fixtures/invalid/AGUI606-continuation-owner-mismatch/stream.jsonl +6 -0
  138. package/spec/fixtures/invalid/AGUI901-raw-wraps-typed-event/expected.json +11 -0
  139. package/spec/fixtures/invalid/AGUI901-raw-wraps-typed-event/stream.jsonl +3 -0
  140. package/spec/fixtures/invalid/AGUI902-no-timestamps/expected.json +9 -0
  141. package/spec/fixtures/invalid/AGUI902-no-timestamps/stream.jsonl +2 -0
  142. package/spec/fixtures/invalid/AGUI903-custom-name-not-namespaced/expected.json +11 -0
  143. package/spec/fixtures/invalid/AGUI903-custom-name-not-namespaced/stream.jsonl +3 -0
  144. package/spec/fixtures/valid/agentic-chat.jsonl +14 -0
  145. package/spec/fixtures/valid/agentic-generative-ui.jsonl +13 -0
  146. package/spec/fixtures/valid/backend-tool-rendering.jsonl +14 -0
  147. package/spec/fixtures/valid/human-in-the-loop.jsonl +13 -0
  148. package/spec/fixtures/valid/predictive-state-updates.expected.json +11 -0
  149. package/spec/fixtures/valid/predictive-state-updates.jsonl +11 -0
  150. package/spec/fixtures/valid/shared-state.jsonl +8 -0
  151. package/spec/fixtures/valid/tool-based-generative-ui.jsonl +5 -0
  152. package/dist/catalog-BglXBNbL.js.map +0 -1
  153. package/dist/catalog-Ci9dqc1a.cjs.map +0 -1
  154. package/dist/src-HmI-kxef.cjs.map +0 -1
  155. package/dist/src-rGZ2G4qA.js.map +0 -1
  156. package/src/report/json.ts +0 -20
  157. /package/{src → js/src}/protocol/jsonpatch.ts +0 -0
  158. /package/{src → js/src}/report/index.ts +0 -0
  159. /package/{src → js/src}/report/junit.ts +0 -0
  160. /package/{src → js/src}/report/pretty.ts +0 -0
  161. /package/{src → js/src}/rules/checks/reasoning.ts +0 -0
  162. /package/{src → js/src}/rules/checks/state.ts +0 -0
  163. /package/{src → js/src}/rules/checks/transport.ts +0 -0
  164. /package/{src → js/src}/transport/ndjson.ts +0 -0
  165. /package/{src → js/src}/transport/sse.ts +0 -0
  166. /package/{src → js/src}/types.ts +0 -0
  167. /package/{src → js/src}/vitest/index.ts +0 -0
  168. /package/{src → js/src}/vitest/matcher.ts +0 -0
package/README.md CHANGED
@@ -15,10 +15,10 @@ spec section.
15
15
  2 errors, 0 warnings, 1 info — 3 of 7 AG-UI features exercised
16
16
  ```
17
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.
18
+ > **Status:** released to both npm (`npm install ag-ui-validate`) and PyPI
19
+ > (`pip install ag-ui-validate`). Both packages version in lockstep same
20
+ > rule catalog, same fixture corpus, byte-identical CLI; see
21
+ > [Python](#python) below.
22
22
 
23
23
  ## Why
24
24
 
@@ -29,7 +29,7 @@ section*. This project is that tool — the AG-UI analogue of what
29
29
 
30
30
  Three design commitments make it trustworthy:
31
31
 
32
- - **Every diagnostic cites the spec.** Each of the 40 rules carries a
32
+ - **Every diagnostic cites the spec.** Each of the 46 rules carries a
33
33
  `specUrl` (and where possible an exact `specQuote`) pointing at the
34
34
  governing section of [docs.ag-ui.com](https://docs.ag-ui.com) or the WHATWG
35
35
  SSE spec. Behaviour the spec doesn't clearly govern is reported at `info`
@@ -39,7 +39,7 @@ Three design commitments make it trustworthy:
39
39
  unknown event types, hostile objects — all diagnostics, never exceptions
40
40
  (fuzz-tested against 50k hostile inputs).
41
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
42
+ grounded in `@ag-ui/core` v0.0.59 and the current docs; where the two
43
43
  disagree, the SDK wins and the discrepancy is recorded.
44
44
 
45
45
  ## Quickstart
@@ -56,10 +56,10 @@ npx ag-ui-validate run.jsonl # recorded stream (NDJSON/JSONL
56
56
  cat run.jsonl | npx ag-ui-validate - # stdin
57
57
  ```
58
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.
59
+ Exit codes: `0` clean, `1` findings at or above the `--fail-on` threshold
60
+ (`error` by default, or warnings over `--max-warnings`), `2` tool failure.
61
+ Timing-based transport rules are meaningless for recordings, so they are
62
+ reported as *skipped with a reason* rather than risking false positives.
63
63
 
64
64
  Useful flags (see `--help` for all):
65
65
 
@@ -70,20 +70,23 @@ Useful flags (see `--help` for all):
70
70
  | `--rule AGUI105=error`, `--off AGUI902` | per-rule severity overrides |
71
71
  | `--features shared-state,...` | declare exercised features (enables e.g. AGUI305) |
72
72
  | `--max-warnings 0` | fail CI on any warning |
73
+ | `--fail-on <error\|warning\|none>` | severity that triggers a nonzero exit (default `error`; `none` for report-only runs) |
73
74
  | `--header "Authorization: Bearer …"`, `--timeout 30` | endpoint options |
74
75
 
75
76
  ### Validate in CI (GitHub Action)
76
77
 
77
78
  ```yaml
78
- - uses: langport-dev/ag-ui-validate/action@main
79
+ - uses: langport-dev/ag-ui-validate-action@v1
79
80
  with:
80
81
  target: http://localhost:8000/agui # or a recorded .jsonl file
81
82
  sarif-file: agui.sarif # optional: upload via codeql-action
82
83
  ```
83
84
 
84
- The step fails on error-severity findings, writes a findings table to the job
85
- summary, and exposes `errors`/`warnings`/`info` outputs — see
86
- [action/README.md](action/README.md).
85
+ The step fails on error-severity findings, writes a findings table (with a
86
+ counts-by-category breakdown) to the job summary, annotates the PR inline
87
+ with `::error`/`::warning` for each finding, and exposes
88
+ `errors`/`warnings`/`info`/`sarif-path`/`report-path` outputs — see
89
+ [langport-dev/ag-ui-validate-action](https://github.com/langport-dev/ag-ui-validate-action).
87
90
 
88
91
  ### Test your agent in Vitest
89
92
 
@@ -124,7 +127,7 @@ const { diagnostics, summary, features, skipped } = v.report()
124
127
  ```
125
128
 
126
129
  The core is a pure function over an event sequence: zero I/O, zero runtime
127
- dependencies, isomorphic across Node 20+, browsers, Deno, and Workers.
130
+ dependencies, isomorphic across Node 22+, browsers, Deno, and Workers.
128
131
 
129
132
  ### Validate a live endpoint
130
133
 
@@ -170,12 +173,67 @@ import { formatReportSummary, toSarif, toJUnit } from "ag-ui-validate/report"
170
173
  }
171
174
  ```
172
175
 
176
+ ## Python
177
+
178
+ A native Python port ships in [py/](py/) — the same rule catalog, the same
179
+ fixture corpus, and a CLI with byte-identical flags, error messages, and
180
+ JSON/SARIF/JUnit output to the TypeScript one above. It's checked against
181
+ the TypeScript implementation on every PR by Parity CI (see
182
+ [docs/TESTING.md](docs/TESTING.md) for how, and
183
+ [docs/PYTHON-PORT-PLAN.md](docs/PYTHON-PORT-PLAN.md) for the port's full
184
+ milestone history).
185
+
186
+ ```bash
187
+ pip install ag-ui-validate # or "ag-ui-validate[transport]" for just the endpoint-validating extras
188
+ ```
189
+
190
+ ### CLI
191
+
192
+ ```bash
193
+ ag-ui-validate http://localhost:8000/agui # live endpoint (POSTs a RunAgentInput)
194
+ ag-ui-validate run.jsonl # recorded stream (NDJSON/JSONL or SSE capture)
195
+ cat run.jsonl | ag-ui-validate - # stdin
196
+ ```
197
+
198
+ Same exit codes and the same flags as the [CLI](#cli) above (see
199
+ `ag-ui-validate --help`) — the Python argument parser is a direct,
200
+ hand-rolled port of the TypeScript one rather than `argparse`/`click`,
201
+ specifically to keep both CLIs' invocation and error messages
202
+ byte-for-byte identical.
203
+
204
+ ### Test your agent in pytest
205
+
206
+ ```python
207
+ from ag_ui_validate.pytest_plugin import assert_valid_agui
208
+
209
+ def test_my_agent_stream(captured_events):
210
+ assert_valid_agui(captured_events, features=["shared-state"], max_warnings=0)
211
+ ```
212
+
213
+ `assert_valid_agui` accepts a list of events (dicts or JSON strings) or a
214
+ whole JSONL capture as one string, and raises `AssertionError` with every
215
+ finding's rule ID and spec link on failure. It registers automatically as a
216
+ pytest plugin on install — no `conftest.py` setup needed. An async
217
+ counterpart validates a live endpoint directly from a test:
218
+
219
+ ```python
220
+ from ag_ui_validate.pytest_plugin import assert_valid_agui_endpoint
221
+
222
+ async def test_my_live_agent():
223
+ await assert_valid_agui_endpoint("http://localhost:8000/agui", max_warnings=0)
224
+ ```
225
+
226
+ `assert_valid_agui` has zero runtime dependencies; only the
227
+ endpoint-validating helpers (`assert_valid_agui_endpoint`,
228
+ `validate_agui_endpoint`) pull in `httpx`
229
+ (`pip install ag-ui-validate[transport]`).
230
+
173
231
  ## The rule catalog
174
232
 
175
- 40 rules, maintained as **data** in
176
- [src/rules/catalog.json](src/rules/catalog.json) so other implementations
177
- (Python, Go, ) can share them. Every rule has its own page — spec grounding,
178
- severity, and a violating example from the corpus in the
233
+ 46 rules, maintained as **data** in
234
+ [spec/catalog.json](spec/catalog.json), shared by both implementations in
235
+ this repo ([TypeScript](.) and [Python](py/)) rather than duplicated.
236
+ Every rule has its own pagespec grounding,
179
237
  **[rule index](docs/rules/README.md)** (generated from the catalog,
180
238
  drift-checked in CI):
181
239
 
@@ -187,17 +245,18 @@ drift-checked in CI):
187
245
  | State | AGUI301–305 | RFC 6902 patch validity, deltas that fail to apply to reconstructed state |
188
246
  | Reasoning | AGUI401–402 | reasoning content without an open reasoning message |
189
247
  | Transport | AGUI501–508 | SSE framing, Content-Type, keepalive gaps, buffering, dropped connections |
248
+ | Subagents | AGUI601–606 | duplicate/unmatched `SUBAGENT_STARTED`/`FINISHED`/`ERROR`, unterminated subagents, unknown `parentSubagentRunId`, continuation events that disagree with their entity's owner |
190
249
  | Hygiene | AGUI901–903 | `RAW`-wrapping typed events, missing timestamps, un-namespaced `CUSTOM` names |
191
250
 
192
- The event taxonomy itself (33 wire types, field schemas) is derived from
251
+ The event taxonomy itself (36 wire types, field schemas) is derived from
193
252
  [`@ag-ui/core`](https://www.npmjs.com/package/@ag-ui/core)'s own schemas and
194
253
  drift-tested against the installed SDK on every run.
195
254
 
196
255
  ## The fixture corpus
197
256
 
198
- [fixtures/](fixtures/README.md) is a language-neutral conformance corpus:
257
+ [spec/fixtures/](spec/fixtures/README.md) is a language-neutral conformance corpus:
199
258
  7 valid streams (one per canonical AG-UI feature — the false-positive guards)
200
- and 40 invalid fixtures (one per rule) with exact expected diagnostics. Any
259
+ and 46 invalid fixtures (one per rule) with exact expected diagnostics. Any
201
260
  validator implementation that consumes the shared catalog can be tested
202
261
  against it; the replay protocol is documented in the corpus README.
203
262
 
@@ -219,7 +278,7 @@ Component-by-component instructions live in
219
278
  rules are tracked in [docs/spec-questions.md](docs/spec-questions.md).
220
279
 
221
280
  Adding a rule: add the catalog entry (with its `specUrl`), add the fixture
222
- stream + intended findings to `scripts/build-fixtures.mjs`, and run
281
+ stream + intended findings to `js/scripts/build-fixtures.mjs`, and run
223
282
  `npm run fixtures:build` — the meta-tests fail until both exist. Rule
224
283
  *proposals* belong upstream as issues on
225
284
  [`ag-ui-protocol/ag-ui`](https://github.com/ag-ui-protocol/ag-ui) first; this
@@ -1,13 +1,14 @@
1
- //#region src/rules/catalog.json
1
+ //#region ../spec/catalog.json
2
2
  var catalog_default = {
3
3
  $comment: "AG-UI conformance rule catalog. Data, not code: a Python/Go implementation shares these rules. Severities follow the working agreement that behaviour the spec does not clearly govern is at most 'info' — entries with 'specQuestion' were downgraded accordingly; see docs/spec-questions.md.",
4
- catalogVersion: "0.1.0",
4
+ catalogVersion: "0.4.0",
5
5
  spec: "0.x",
6
6
  rules: [
7
7
  {
8
8
  "id": "AGUI001",
9
9
  "severity": "error",
10
10
  "title": "Run does not start with RUN_STARTED",
11
+ "category": "lifecycle",
11
12
  "messageTemplate": "First event of the run is {type}; expected RUN_STARTED",
12
13
  "specUrl": "https://docs.ag-ui.com/concepts/events#runstarted",
13
14
  "specQuote": "The RunStarted event is the first event emitted when an agent begins processing a request.",
@@ -18,6 +19,7 @@ var catalog_default = {
18
19
  "id": "AGUI002",
19
20
  "severity": "error",
20
21
  "title": "Multiple RUN_STARTED in one run",
22
+ "category": "lifecycle",
21
23
  "messageTemplate": "Duplicate RUN_STARTED (runId '{runId}') before the active run terminated",
22
24
  "specUrl": "https://docs.ag-ui.com/concepts/events#lifecycle-events",
23
25
  "specQuote": "The RunStarted and either RunFinished or RunError events are mandatory, forming the boundaries of an agent run.",
@@ -28,6 +30,7 @@ var catalog_default = {
28
30
  "id": "AGUI003",
29
31
  "severity": "error",
30
32
  "title": "Run not terminated",
33
+ "category": "lifecycle",
31
34
  "messageTemplate": "Run '{runId}' ended without RUN_FINISHED or RUN_ERROR",
32
35
  "specUrl": "https://docs.ag-ui.com/concepts/events#runfinished",
33
36
  "specQuote": "Every run terminates with either RunFinished or RunError.",
@@ -38,6 +41,7 @@ var catalog_default = {
38
41
  "id": "AGUI004",
39
42
  "severity": "error",
40
43
  "title": "Event after terminal event",
44
+ "category": "lifecycle",
41
45
  "messageTemplate": "{type} follows the run's terminal {terminalType}",
42
46
  "specUrl": "https://docs.ag-ui.com/concepts/events#lifecycle-events",
43
47
  "specQuote": "The RunStarted and either RunFinished or RunError events are mandatory, forming the boundaries of an agent run.",
@@ -48,6 +52,7 @@ var catalog_default = {
48
52
  "id": "AGUI005",
49
53
  "severity": "error",
50
54
  "title": "RUN_FINISHED and RUN_ERROR are mutually exclusive",
55
+ "category": "lifecycle",
51
56
  "messageTemplate": "{type} emitted after the run already terminated with {terminalType}",
52
57
  "specUrl": "https://docs.ag-ui.com/concepts/events#runfinished",
53
58
  "specQuote": "Every run terminates with either RunFinished or RunError.",
@@ -58,6 +63,7 @@ var catalog_default = {
58
63
  "id": "AGUI006",
59
64
  "severity": "error",
60
65
  "title": "STEP_FINISHED without matching STEP_STARTED",
66
+ "category": "lifecycle",
61
67
  "messageTemplate": "STEP_FINISHED '{stepName}' has no open STEP_STARTED",
62
68
  "specUrl": "https://docs.ag-ui.com/concepts/events#stepfinished",
63
69
  "specQuote": "The stepName must match the corresponding StepStarted event.",
@@ -68,6 +74,7 @@ var catalog_default = {
68
74
  "id": "AGUI007",
69
75
  "severity": "error",
70
76
  "title": "Step unterminated at run end",
77
+ "category": "lifecycle",
71
78
  "messageTemplate": "STEP_STARTED '{stepName}' never finished",
72
79
  "specUrl": "https://docs.ag-ui.com/concepts/events#stepstarted",
73
80
  "specQuote": "The stepName must match the corresponding StepStarted event.",
@@ -78,6 +85,7 @@ var catalog_default = {
78
85
  "id": "AGUI008",
79
86
  "severity": "warning",
80
87
  "title": "Unstable threadId/runId across the run",
88
+ "category": "lifecycle",
81
89
  "messageTemplate": "RUN_FINISHED {field} '{actual}' does not match RUN_STARTED {field} '{expected}'",
82
90
  "specUrl": "https://docs.ag-ui.com/concepts/events#runstarted",
83
91
  "specQuote": "It also provides crucial identifiers that can be used to associate subsequent events with this specific run.",
@@ -88,6 +96,7 @@ var catalog_default = {
88
96
  "id": "AGUI101",
89
97
  "severity": "error",
90
98
  "title": "TEXT_MESSAGE_CONTENT without start",
99
+ "category": "text",
91
100
  "messageTemplate": "TEXT_MESSAGE_CONTENT for messageId '{messageId}' with no open TEXT_MESSAGE_START",
92
101
  "specUrl": "https://docs.ag-ui.com/concepts/events#text-message-events",
93
102
  "specQuote": "A message begins with a TextMessageStart event, followed by one or more TextMessageContent events that deliver chunks of text as they become available, and concludes with a TextMessageEnd event.",
@@ -99,6 +108,7 @@ var catalog_default = {
99
108
  "id": "AGUI102",
100
109
  "severity": "error",
101
110
  "title": "TEXT_MESSAGE_END without start",
111
+ "category": "text",
102
112
  "messageTemplate": "TEXT_MESSAGE_END for messageId '{messageId}' with no open TEXT_MESSAGE_START",
103
113
  "specUrl": "https://docs.ag-ui.com/concepts/events#textmessageend",
104
114
  "specQuote": "messageId: Matches the ID from TextMessageStart",
@@ -110,6 +120,7 @@ var catalog_default = {
110
120
  "id": "AGUI103",
111
121
  "severity": "error",
112
122
  "title": "Text message unterminated at run end",
123
+ "category": "text",
113
124
  "messageTemplate": "TEXT_MESSAGE_START messageId '{messageId}' never ended",
114
125
  "specUrl": "https://docs.ag-ui.com/concepts/events#text-message-events",
115
126
  "specQuote": "A message begins with a TextMessageStart event, followed by one or more TextMessageContent events that deliver chunks of text as they become available, and concludes with a TextMessageEnd event.",
@@ -121,6 +132,7 @@ var catalog_default = {
121
132
  "id": "AGUI104",
122
133
  "severity": "error",
123
134
  "title": "Duplicate messageId within a run",
135
+ "category": "text",
124
136
  "messageTemplate": "messageId '{messageId}' was already used by a completed message",
125
137
  "specUrl": "https://docs.ag-ui.com/concepts/events#textmessagestart",
126
138
  "specQuote": "It establishes a unique messageId that will be referenced by subsequent content chunks and the end event.",
@@ -132,6 +144,7 @@ var catalog_default = {
132
144
  "id": "AGUI105",
133
145
  "severity": "warning",
134
146
  "title": "Empty content delta",
147
+ "category": "text",
135
148
  "messageTemplate": "TEXT_MESSAGE_CONTENT for messageId '{messageId}' has an empty delta",
136
149
  "specUrl": "https://docs.ag-ui.com/concepts/events#textmessagecontent",
137
150
  "specQuote": "delta: Text content chunk (non-empty)",
@@ -144,6 +157,7 @@ var catalog_default = {
144
157
  "id": "AGUI106",
145
158
  "severity": "error",
146
159
  "title": "Interleaved message streams sharing a messageId",
160
+ "category": "text",
147
161
  "messageTemplate": "TEXT_MESSAGE_START for messageId '{messageId}', which is already open",
148
162
  "specUrl": "https://docs.ag-ui.com/concepts/events#implementation-considerations",
149
163
  "specQuote": "Events with the same ID (e.g., messageId, toolCallId) belong to the same logical stream",
@@ -155,6 +169,7 @@ var catalog_default = {
155
169
  "id": "AGUI201",
156
170
  "severity": "error",
157
171
  "title": "TOOL_CALL_ARGS without start",
172
+ "category": "toolcall",
158
173
  "messageTemplate": "TOOL_CALL_ARGS for toolCallId '{toolCallId}' with no open TOOL_CALL_START",
159
174
  "specUrl": "https://docs.ag-ui.com/concepts/events#tool-call-events",
160
175
  "specQuote": "When an agent needs to use a tool, it emits a ToolCallStart event, followed by one or more ToolCallArgs events that stream the arguments being passed to the tool, and concludes with a ToolCallEnd event.",
@@ -166,6 +181,7 @@ var catalog_default = {
166
181
  "id": "AGUI202",
167
182
  "severity": "error",
168
183
  "title": "TOOL_CALL_END without start",
184
+ "category": "toolcall",
169
185
  "messageTemplate": "TOOL_CALL_END for toolCallId '{toolCallId}' with no open TOOL_CALL_START",
170
186
  "specUrl": "https://docs.ag-ui.com/concepts/events#toolcallend",
171
187
  "specQuote": "toolCallId: Matches the ID from ToolCallStart",
@@ -177,6 +193,7 @@ var catalog_default = {
177
193
  "id": "AGUI203",
178
194
  "severity": "error",
179
195
  "title": "Unterminated tool call",
196
+ "category": "toolcall",
180
197
  "messageTemplate": "TOOL_CALL_START id '{toolCallId}' never terminated",
181
198
  "specUrl": "https://docs.ag-ui.com/concepts/events#tool-call-events",
182
199
  "specQuote": "When an agent needs to use a tool, it emits a ToolCallStart event, followed by one or more ToolCallArgs events that stream the arguments being passed to the tool, and concludes with a ToolCallEnd event.",
@@ -188,6 +205,7 @@ var catalog_default = {
188
205
  "id": "AGUI204",
189
206
  "severity": "error",
190
207
  "title": "Tool call arguments are not valid JSON",
208
+ "category": "toolcall",
191
209
  "messageTemplate": "Concatenated TOOL_CALL_ARGS for toolCallId '{toolCallId}' do not parse as JSON: {error}",
192
210
  "specUrl": "https://docs.ag-ui.com/concepts/events#toolcallargs",
193
211
  "specQuote": "Frontends should concatenate these deltas in the order received to construct the complete arguments object.",
@@ -199,6 +217,7 @@ var catalog_default = {
199
217
  "id": "AGUI205",
200
218
  "severity": "error",
201
219
  "title": "Duplicate toolCallId within a run",
220
+ "category": "toolcall",
202
221
  "messageTemplate": "toolCallId '{toolCallId}' was already used by a completed tool call",
203
222
  "specUrl": "https://docs.ag-ui.com/concepts/events#toolcallstart",
204
223
  "specQuote": "toolCallId: Unique identifier for the tool call",
@@ -210,6 +229,7 @@ var catalog_default = {
210
229
  "id": "AGUI206",
211
230
  "severity": "warning",
212
231
  "title": "TOOL_CALL_RESULT before TOOL_CALL_END",
232
+ "category": "toolcall",
213
233
  "messageTemplate": "TOOL_CALL_RESULT for toolCallId '{toolCallId}' arrived while the call is still open",
214
234
  "specUrl": "https://docs.ag-ui.com/concepts/events#toolcallresult",
215
235
  "specQuote": "This event is sent after the tool has been executed by the system and contains the actual output generated by the tool.",
@@ -221,6 +241,7 @@ var catalog_default = {
221
241
  "id": "AGUI207",
222
242
  "severity": "error",
223
243
  "title": "TOOL_CALL_RESULT references unknown toolCallId",
244
+ "category": "toolcall",
224
245
  "messageTemplate": "TOOL_CALL_RESULT references toolCallId '{toolCallId}', which was never started",
225
246
  "specUrl": "https://docs.ag-ui.com/concepts/events#toolcallresult",
226
247
  "specQuote": "toolCallId: Matches the ID from the corresponding ToolCallStart event",
@@ -232,6 +253,7 @@ var catalog_default = {
232
253
  "id": "AGUI208",
233
254
  "severity": "info",
234
255
  "title": "parentMessageId references unknown message",
256
+ "category": "toolcall",
235
257
  "messageTemplate": "TOOL_CALL_START parentMessageId '{parentMessageId}' matches no message observed in this stream",
236
258
  "specUrl": "https://docs.ag-ui.com/concepts/events#toolcallstart",
237
259
  "specQuote": "The optional parentMessageId allows linking the tool call to a specific message in the conversation, providing context for why the tool is being used.",
@@ -244,6 +266,7 @@ var catalog_default = {
244
266
  "id": "AGUI301",
245
267
  "severity": "info",
246
268
  "title": "STATE_DELTA before any STATE_SNAPSHOT",
269
+ "category": "state",
247
270
  "messageTemplate": "STATE_DELTA precedes any STATE_SNAPSHOT; the base it applies to is not observable on this stream",
248
271
  "specUrl": "https://docs.ag-ui.com/concepts/events#statesnapshot",
249
272
  "specQuote": "This event is typically sent at the beginning of an interaction or when synchronization is needed.",
@@ -256,6 +279,7 @@ var catalog_default = {
256
279
  "id": "AGUI302",
257
280
  "severity": "error",
258
281
  "title": "STATE_DELTA failed to apply",
282
+ "category": "state",
259
283
  "messageTemplate": "STATE_DELTA failed to apply: {error}",
260
284
  "specUrl": "https://docs.ag-ui.com/concepts/events#statedelta",
261
285
  "specQuote": "Each delta represents specific changes to apply to the current state model.",
@@ -267,6 +291,7 @@ var catalog_default = {
267
291
  "id": "AGUI303",
268
292
  "severity": "error",
269
293
  "title": "STATE_DELTA is not a valid RFC 6902 patch document",
294
+ "category": "state",
270
295
  "messageTemplate": "STATE_DELTA is not a valid RFC 6902 patch document: {error}",
271
296
  "specUrl": "https://docs.ag-ui.com/concepts/events#statedelta",
272
297
  "specQuote": "The StateDelta event contains incremental updates to the agent's state in the form of JSON Patch operations (as defined in RFC 6902).",
@@ -278,6 +303,7 @@ var catalog_default = {
278
303
  "id": "AGUI304",
279
304
  "severity": "info",
280
305
  "title": "Mid-run STATE_SNAPSHOT discards accumulated deltas",
306
+ "category": "state",
281
307
  "messageTemplate": "STATE_SNAPSHOT replaces state previously built from {deltaCount} delta(s)",
282
308
  "specUrl": "https://docs.ag-ui.com/concepts/events#statesnapshot",
283
309
  "specQuote": "This event is typically sent at the beginning of an interaction or when synchronization is needed.",
@@ -289,6 +315,7 @@ var catalog_default = {
289
315
  "id": "AGUI305",
290
316
  "severity": "warning",
291
317
  "title": "Shared state declared but never established",
318
+ "category": "state",
292
319
  "messageTemplate": "features include 'shared-state' but no STATE_SNAPSHOT was emitted",
293
320
  "specUrl": "https://docs.ag-ui.com/concepts/events#state-management-events",
294
321
  "specQuote": "These events are used to manage and synchronize the agent's state with the frontend.",
@@ -301,6 +328,7 @@ var catalog_default = {
301
328
  "id": "AGUI401",
302
329
  "severity": "error",
303
330
  "title": "REASONING_MESSAGE_CONTENT without start",
331
+ "category": "reasoning",
304
332
  "messageTemplate": "REASONING_MESSAGE_CONTENT for messageId '{messageId}' with no open REASONING_MESSAGE_START",
305
333
  "specUrl": "https://docs.ag-ui.com/concepts/events#reasoningmessagecontent",
306
334
  "specQuote": "Multiple content events with the same messageId should be concatenated to form the complete visible reasoning.",
@@ -312,6 +340,7 @@ var catalog_default = {
312
340
  "id": "AGUI402",
313
341
  "severity": "warning",
314
342
  "title": "Reasoning unterminated at run end",
343
+ "category": "reasoning",
315
344
  "messageTemplate": "{startType} messageId '{messageId}' never ended",
316
345
  "specUrl": "https://docs.ag-ui.com/concepts/events#reasoning-events",
317
346
  "specQuote": "Reasoning events support LLM reasoning visibility and continuity, enabling chain-of-thought reasoning while maintaining privacy.",
@@ -323,6 +352,7 @@ var catalog_default = {
323
352
  "id": "AGUI501",
324
353
  "severity": "error",
325
354
  "title": "Malformed SSE framing",
355
+ "category": "transport",
326
356
  "messageTemplate": "Malformed SSE framing: {detail}",
327
357
  "specUrl": "https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation",
328
358
  "since": "0.x",
@@ -332,6 +362,7 @@ var catalog_default = {
332
362
  "id": "AGUI502",
333
363
  "severity": "error",
334
364
  "title": "Event payload is not valid JSON",
365
+ "category": "transport",
335
366
  "messageTemplate": "Event payload is not valid JSON: {error}",
336
367
  "specUrl": "https://docs.ag-ui.com/concepts/events#base-event-properties",
337
368
  "specQuote": "All events share a common set of base properties.",
@@ -342,7 +373,8 @@ var catalog_default = {
342
373
  "id": "AGUI503",
343
374
  "severity": "error",
344
375
  "title": "Unknown event type",
345
- "messageTemplate": "Unknown event type '{type}' (not in @ag-ui/core v{sdkVersion}, and not RAW or CUSTOM)",
376
+ "category": "transport",
377
+ "messageTemplate": "Unknown event type '{type}' (not in the installed AG-UI SDK v{sdkVersion}, and not RAW or CUSTOM)",
346
378
  "specUrl": "https://docs.ag-ui.com/concepts/events#event-types-overview",
347
379
  "specQuote": "Events in the protocol are categorized by their purpose.",
348
380
  "since": "0.x",
@@ -352,6 +384,7 @@ var catalog_default = {
352
384
  "id": "AGUI504",
353
385
  "severity": "error",
354
386
  "title": "Event fails schema validation for its declared type",
387
+ "category": "transport",
355
388
  "messageTemplate": "{type}: {detail}",
356
389
  "specUrl": "https://docs.ag-ui.com/concepts/events#base-event-properties",
357
390
  "specQuote": "All events share a common set of base properties.",
@@ -362,6 +395,7 @@ var catalog_default = {
362
395
  "id": "AGUI505",
363
396
  "severity": "warning",
364
397
  "title": "Unexpected Content-Type",
398
+ "category": "transport",
365
399
  "messageTemplate": "Content-Type '{contentType}' is neither text/event-stream nor application/x-ndjson",
366
400
  "specUrl": "https://html.spec.whatwg.org/multipage/server-sent-events.html#sse-processing-model",
367
401
  "since": "0.x",
@@ -371,6 +405,7 @@ var catalog_default = {
371
405
  "id": "AGUI506",
372
406
  "severity": "info",
373
407
  "title": "No keepalive frame within the configured window",
408
+ "category": "transport",
374
409
  "messageTemplate": "No event or keepalive frame for {seconds}s",
375
410
  "specUrl": "https://docs.ag-ui.com/concepts/architecture#standard-http-client",
376
411
  "since": "0.x",
@@ -381,6 +416,7 @@ var catalog_default = {
381
416
  "id": "AGUI507",
382
417
  "severity": "info",
383
418
  "title": "Response appears buffered rather than incrementally flushed",
419
+ "category": "transport",
384
420
  "messageTemplate": "Response appears buffered: {detail}",
385
421
  "specUrl": "https://docs.ag-ui.com/concepts/architecture#standard-http-client",
386
422
  "since": "0.x",
@@ -391,16 +427,85 @@ var catalog_default = {
391
427
  "id": "AGUI508",
392
428
  "severity": "error",
393
429
  "title": "Stream ended without a terminal event",
430
+ "category": "transport",
394
431
  "messageTemplate": "Connection ended mid-run '{runId}' without RUN_FINISHED or RUN_ERROR",
395
432
  "specUrl": "https://docs.ag-ui.com/concepts/events#runfinished",
396
433
  "specQuote": "Every run terminates with either RunFinished or RunError.",
397
434
  "since": "0.x",
398
435
  "checkedIn": "transport"
399
436
  },
437
+ {
438
+ "id": "AGUI601",
439
+ "severity": "error",
440
+ "title": "Duplicate SUBAGENT_STARTED for a subagentRunId",
441
+ "category": "subagent",
442
+ "messageTemplate": "SUBAGENT_STARTED reuses subagentRunId '{subagentRunId}', which is already in use this run",
443
+ "specUrl": "https://docs.ag-ui.com/concepts/subagents#rules-clients-enforce",
444
+ "specQuote": "a subagent is not started twice within a run",
445
+ "since": "0.x",
446
+ "checkedIn": "core"
447
+ },
448
+ {
449
+ "id": "AGUI602",
450
+ "severity": "error",
451
+ "title": "SUBAGENT_FINISHED without matching SUBAGENT_STARTED",
452
+ "category": "subagent",
453
+ "messageTemplate": "SUBAGENT_FINISHED for subagentRunId '{subagentRunId}' with no open SUBAGENT_STARTED",
454
+ "specUrl": "https://docs.ag-ui.com/concepts/subagents#rules-clients-enforce",
455
+ "specQuote": "SubagentFinished and SubagentError name a subagent that is currently active",
456
+ "since": "0.x",
457
+ "checkedIn": "core"
458
+ },
459
+ {
460
+ "id": "AGUI603",
461
+ "severity": "error",
462
+ "title": "SUBAGENT_ERROR without matching SUBAGENT_STARTED",
463
+ "category": "subagent",
464
+ "messageTemplate": "SUBAGENT_ERROR for subagentRunId '{subagentRunId}' with no open SUBAGENT_STARTED",
465
+ "specUrl": "https://docs.ag-ui.com/concepts/subagents#rules-clients-enforce",
466
+ "specQuote": "SubagentFinished and SubagentError name a subagent that is currently active",
467
+ "since": "0.x",
468
+ "checkedIn": "core"
469
+ },
470
+ {
471
+ "id": "AGUI604",
472
+ "severity": "error",
473
+ "title": "Subagent unterminated at run end",
474
+ "category": "subagent",
475
+ "messageTemplate": "SUBAGENT_STARTED subagentRunId '{subagentRunId}' never closed with SUBAGENT_FINISHED or SUBAGENT_ERROR",
476
+ "specUrl": "https://docs.ag-ui.com/concepts/subagents#rules-clients-enforce",
477
+ "specQuote": "Every started subagent is closed before RunFinished.",
478
+ "since": "0.x",
479
+ "checkedIn": "core"
480
+ },
481
+ {
482
+ "id": "AGUI605",
483
+ "severity": "warning",
484
+ "title": "parentSubagentRunId references a subagent never started",
485
+ "category": "subagent",
486
+ "messageTemplate": "SUBAGENT_STARTED parentSubagentRunId '{parentSubagentRunId}' matches no subagent observed in this stream",
487
+ "specUrl": "https://docs.ag-ui.com/concepts/subagents#nesting-and-concurrency",
488
+ "specQuote": "parentSubagentRunId need only name a subagent that has been started, not one still active",
489
+ "since": "0.x",
490
+ "checkedIn": "core"
491
+ },
492
+ {
493
+ "id": "AGUI606",
494
+ "severity": "warning",
495
+ "title": "Continuation event's subagentRunId disagrees with its entity's owner",
496
+ "category": "subagent",
497
+ "messageTemplate": "{type} declares subagentRunId '{actual}', but {entityType} '{entityId}' was opened under {expected}",
498
+ "specUrl": "https://docs.ag-ui.com/concepts/subagents#rules-clients-enforce",
499
+ "specQuote": "Continuation and close events agree with the owner their entity was created under.",
500
+ "since": "0.x",
501
+ "specQuestion": "SQ-15",
502
+ "checkedIn": "core"
503
+ },
400
504
  {
401
505
  "id": "AGUI901",
402
506
  "severity": "info",
403
507
  "title": "RAW event wraps a typed AG-UI event",
508
+ "category": "hygiene",
404
509
  "messageTemplate": "RAW event wraps an event of type '{wrappedType}', which has a typed AG-UI equivalent",
405
510
  "specUrl": "https://docs.ag-ui.com/concepts/events#raw",
406
511
  "specQuote": "The Raw event acts as a container for events originating from external systems or sources that don't natively follow the Agent UI Protocol.",
@@ -411,6 +516,7 @@ var catalog_default = {
411
516
  "id": "AGUI902",
412
517
  "severity": "info",
413
518
  "title": "Events carry no timestamps",
519
+ "category": "hygiene",
414
520
  "messageTemplate": "None of the {eventCount} events carry the optional timestamp property",
415
521
  "specUrl": "https://docs.ag-ui.com/concepts/events#base-event-properties",
416
522
  "specQuote": "timestamp: Optional timestamp indicating when the event was created",
@@ -421,6 +527,7 @@ var catalog_default = {
421
527
  "id": "AGUI903",
422
528
  "severity": "info",
423
529
  "title": "CUSTOM event name is not namespaced",
530
+ "category": "hygiene",
424
531
  "messageTemplate": "CUSTOM event name '{name}' has no namespace prefix (e.g. 'vendor.event')",
425
532
  "specUrl": "https://docs.ag-ui.com/concepts/events#custom",
426
533
  "specQuote": "Teams should document their custom events to ensure consistent implementation across frontends and agents.",
@@ -438,6 +545,16 @@ const SEVERITIES = [
438
545
  "info"
439
546
  ];
440
547
  const LAYERS = ["core", "transport"];
548
+ const CATEGORIES = [
549
+ "lifecycle",
550
+ "text",
551
+ "toolcall",
552
+ "state",
553
+ "reasoning",
554
+ "transport",
555
+ "hygiene",
556
+ "subagent"
557
+ ];
441
558
  /** Validates catalog data and returns it typed. Throws on structural problems. */
442
559
  function validateCatalog(data) {
443
560
  const problems = [];
@@ -451,6 +568,7 @@ function validateCatalog(data) {
451
568
  seen.add(rule.id);
452
569
  if (!SEVERITIES.includes(rule.severity)) problems.push(`${where}: bad severity '${rule.severity}'`);
453
570
  if (!rule.title) problems.push(`${where}: missing title`);
571
+ if (!CATEGORIES.includes(rule.category)) problems.push(`${where}: bad category '${rule.category}'`);
454
572
  if (!rule.messageTemplate) problems.push(`${where}: missing messageTemplate`);
455
573
  if (!rule.specUrl?.startsWith("https://")) problems.push(`${where}: specUrl must be an https URL`);
456
574
  if (!rule.since) problems.push(`${where}: missing since`);
@@ -469,4 +587,4 @@ function formatMessage(rule, params) {
469
587
  //#endregion
470
588
  export { validateCatalog as i, RULES as n, formatMessage as r, CATALOG as t };
471
589
 
472
- //# sourceMappingURL=catalog-BglXBNbL.js.map
590
+ //# sourceMappingURL=catalog-3e6INLCp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-3e6INLCp.js","names":["catalogJson"],"sources":["../spec/catalog.json","../js/src/rules/catalog.ts"],"sourcesContent":["","// Typed loader for the rule catalog. The catalog itself is data\n// (catalog.json) so other implementations can share it; this module gives it\n// types and validates its invariants once at load time.\n//\n// Note the asymmetry: the validator never throws on stream input, but a\n// malformed catalog is a programming error in this package, so the loader\n// throws loudly at import time.\n\nimport catalogJson from \"../../../spec/catalog.json\"\n\nexport type Severity = \"error\" | \"warning\" | \"info\"\nexport type SeverityOrOff = Severity | \"off\"\n\nconst SEVERITIES: readonly string[] = [\"error\", \"warning\", \"info\"]\nconst LAYERS: readonly string[] = [\"core\", \"transport\"]\nexport const CATEGORIES: readonly string[] = [\"lifecycle\", \"text\", \"toolcall\", \"state\", \"reasoning\", \"transport\", \"hygiene\", \"subagent\"]\n\nexport interface RuleDefinition {\n /** e.g. \"AGUI203\" */\n id: string\n severity: Severity\n title: string\n /** Which part of the protocol this rule governs, e.g. \"toolcall\". */\n category: string\n /** Human template with {placeholder} slots filled per diagnostic. */\n messageTemplate: string\n /** Governing spec section. Mandatory: rules that cannot cite one don't ship. */\n specUrl: string\n /** Exact sentence from the spec section, where one exists. */\n specQuote?: string\n since: string\n /** Canonical AG-UI feature this rule relates to, if any. */\n feature?: string\n /** True when the rule only fires if opts.features declares `feature`. */\n requiresFeature?: boolean\n /** Cross-reference into docs/spec-questions.md for downgraded/ambiguous rules. */\n specQuestion?: string\n /** Where the rule is evaluated. Transport rules are skipped (and the skip\n * reported) when validating recorded input with no transport in play. */\n checkedIn: \"core\" | \"transport\"\n}\n\nexport interface Catalog {\n catalogVersion: string\n spec: string\n rules: readonly RuleDefinition[]\n}\n\n/** Validates catalog data and returns it typed. Throws on structural problems. */\nexport function validateCatalog(data: unknown): Catalog {\n const problems: string[] = []\n const cat = data as Catalog\n if (typeof cat !== \"object\" || cat === null || !Array.isArray(cat.rules)) {\n throw new Error(\"rule catalog: expected an object with a rules array\")\n }\n const seen = new Set<string>()\n for (const rule of cat.rules) {\n const where = rule?.id ?? \"<missing id>\"\n if (!/^AGUI\\d{3}$/.test(rule.id ?? \"\")) problems.push(`${where}: id must match AGUI###`)\n if (seen.has(rule.id)) problems.push(`${where}: duplicate id`)\n seen.add(rule.id)\n if (!SEVERITIES.includes(rule.severity)) problems.push(`${where}: bad severity '${rule.severity}'`)\n if (!rule.title) problems.push(`${where}: missing title`)\n if (!CATEGORIES.includes(rule.category)) problems.push(`${where}: bad category '${rule.category}'`)\n if (!rule.messageTemplate) problems.push(`${where}: missing messageTemplate`)\n if (!rule.specUrl?.startsWith(\"https://\")) problems.push(`${where}: specUrl must be an https URL`)\n if (!rule.since) problems.push(`${where}: missing since`)\n if (!LAYERS.includes(rule.checkedIn)) problems.push(`${where}: bad checkedIn '${rule.checkedIn}'`)\n if (rule.requiresFeature && !rule.feature) problems.push(`${where}: requiresFeature without feature`)\n }\n if (problems.length > 0) {\n throw new Error(`rule catalog is invalid:\\n ${problems.join(\"\\n \")}`)\n }\n return cat\n}\n\nexport const CATALOG: Catalog = validateCatalog(catalogJson)\n\nexport const RULES: ReadonlyMap<string, RuleDefinition> = new Map(\n CATALOG.rules.map((r) => [r.id, r]),\n)\n\n/** Fills a rule's messageTemplate. Unknown placeholders are left intact. */\nexport function formatMessage(rule: RuleDefinition, params: Record<string, unknown>): string {\n return rule.messageTemplate.replace(/\\{(\\w+)\\}/g, (whole, key: string) =>\n key in params ? String(params[key]) : whole,\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACaA,MAAM,aAAgC;CAAC;CAAS;CAAW;AAAM;AACjE,MAAM,SAA4B,CAAC,QAAQ,WAAW;AACtD,MAAa,aAAgC;CAAC;CAAa;CAAQ;CAAY;CAAS;CAAa;CAAa;CAAW;AAAU;;AAkCvI,SAAgB,gBAAgB,MAAwB;CACtD,MAAM,WAAqB,CAAC;CAC5B,MAAM,MAAM;CACZ,IAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,CAAC,MAAM,QAAQ,IAAI,KAAK,GACrE,MAAM,IAAI,MAAM,qDAAqD;CAEvE,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,QAAQ,IAAI,OAAO;EAC5B,MAAM,QAAQ,MAAM,MAAM;EAC1B,IAAI,CAAC,cAAc,KAAK,KAAK,MAAM,EAAE,GAAG,SAAS,KAAK,GAAG,MAAM,wBAAwB;EACvF,IAAI,KAAK,IAAI,KAAK,EAAE,GAAG,SAAS,KAAK,GAAG,MAAM,eAAe;EAC7D,KAAK,IAAI,KAAK,EAAE;EAChB,IAAI,CAAC,WAAW,SAAS,KAAK,QAAQ,GAAG,SAAS,KAAK,GAAG,MAAM,kBAAkB,KAAK,SAAS,EAAE;EAClG,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,GAAG,MAAM,gBAAgB;EACxD,IAAI,CAAC,WAAW,SAAS,KAAK,QAAQ,GAAG,SAAS,KAAK,GAAG,MAAM,kBAAkB,KAAK,SAAS,EAAE;EAClG,IAAI,CAAC,KAAK,iBAAiB,SAAS,KAAK,GAAG,MAAM,0BAA0B;EAC5E,IAAI,CAAC,KAAK,SAAS,WAAW,UAAU,GAAG,SAAS,KAAK,GAAG,MAAM,+BAA+B;EACjG,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,GAAG,MAAM,gBAAgB;EACxD,IAAI,CAAC,OAAO,SAAS,KAAK,SAAS,GAAG,SAAS,KAAK,GAAG,MAAM,mBAAmB,KAAK,UAAU,EAAE;EACjG,IAAI,KAAK,mBAAmB,CAAC,KAAK,SAAS,SAAS,KAAK,GAAG,MAAM,kCAAkC;CACtG;CACA,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,MAAM,+BAA+B,SAAS,KAAK,MAAM,GAAG;CAExE,OAAO;AACT;AAEA,MAAa,UAAmB,gBAAgBA,eAAW;AAE3D,MAAa,QAA6C,IAAI,IAC5D,QAAQ,MAAM,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CACpC;;AAGA,SAAgB,cAAc,MAAsB,QAAyC;CAC3F,OAAO,KAAK,gBAAgB,QAAQ,eAAe,OAAO,QACxD,OAAO,SAAS,OAAO,OAAO,IAAI,IAAI,KACxC;AACF"}