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
@@ -3,6 +3,7 @@
3
3
  import type { Severity } from "./types.js"
4
4
 
5
5
  export type CliFormat = "pretty" | "json" | "sarif" | "junit"
6
+ export type FailOn = "error" | "warning" | "none"
6
7
 
7
8
  export interface CliConfig {
8
9
  /** URL, file path, or "-" for stdin. Empty only with --help/--version. */
@@ -11,6 +12,8 @@ export interface CliConfig {
11
12
  /** true/false forced; null = auto-detect from TTY. */
12
13
  color: boolean | null
13
14
  maxWarnings?: number
15
+ /** Severity threshold for a nonzero exit. Unset behaves like "error", today's default. */
16
+ failOn?: FailOn
14
17
  timeoutMs?: number
15
18
  /** Header names lowercased. */
16
19
  headers: Record<string, string>
@@ -50,6 +53,7 @@ Rules:
50
53
  --off AGUI### shorthand for --rule AGUI###=off
51
54
  --features <a,b,...> declare exercised features (enables e.g. AGUI305)
52
55
  --max-warnings <n> exit 1 when warnings exceed n
56
+ --fail-on <error|warning|none> severity that triggers a nonzero exit (default: error)
53
57
 
54
58
  Endpoint options:
55
59
  --header "Name: value" extra request header (repeatable)
@@ -59,6 +63,7 @@ Exit codes: 0 clean, 1 findings at error level (or warnings over --max-warnings)
59
63
  `
60
64
 
61
65
  const SEVERITIES = new Set(["error", "warning", "info", "off"])
66
+ const FAIL_ON_VALUES = new Set<FailOn>(["error", "warning", "none"])
62
67
  const RULE_ID = /^AGUI\d{3}$/
63
68
 
64
69
  interface Flag {
@@ -106,6 +111,14 @@ const FLAGS: Record<string, Flag> = {
106
111
  return null
107
112
  },
108
113
  },
114
+ "--fail-on": {
115
+ takesValue: true,
116
+ apply: (c, v) => {
117
+ if (!FAIL_ON_VALUES.has(v as FailOn)) return `--fail-on expects error, warning, or none, got '${v}'`
118
+ c.failOn = v as FailOn
119
+ return null
120
+ },
121
+ },
109
122
  "--timeout": {
110
123
  takesValue: true,
111
124
  apply: (c, v) => {
@@ -206,8 +219,11 @@ export function parseCliArgs(argv: string[]): ParseResult {
206
219
  export function decideExitCode(
207
220
  summary: { errors: number; warnings: number; info: number },
208
221
  maxWarnings?: number,
222
+ failOn: FailOn = "error",
209
223
  ): 0 | 1 {
224
+ if (failOn === "none") return 0
210
225
  if (summary.errors > 0) return 1
226
+ if (failOn === "warning" && summary.warnings > 0) return 1
211
227
  if (maxWarnings !== undefined && summary.warnings > maxWarnings) return 1
212
228
  return 0
213
229
  }
@@ -132,7 +132,7 @@ async function main(): Promise<number> {
132
132
  process.stdout.write(toJUnit(report, { name: targetLabel }))
133
133
  }
134
134
 
135
- return decideExitCode(report.summary, config.maxWarnings)
135
+ return decideExitCode(report.summary, config.maxWarnings, config.failOn)
136
136
  }
137
137
 
138
138
  main().then(
@@ -14,6 +14,7 @@ import { newRunState, str } from "./rules/checks/context.js"
14
14
  import { checkRunIdStability, endOfRunSteps, handleStepEvent } from "./rules/checks/lifecycle.js"
15
15
  import { closeReasoningChunk, endOfRunReasoning, handleReasoningEvent } from "./rules/checks/reasoning.js"
16
16
  import { handleStateEvent } from "./rules/checks/state.js"
17
+ import { endOfRunSubagents, handleSubagentEvent } from "./rules/checks/subagents.js"
17
18
  import { closeTextChunk, endOfRunText, handleTextEvent } from "./rules/checks/text.js"
18
19
  import { closeToolChunk, endOfRunToolCalls, handleToolCallEvent } from "./rules/checks/toolcalls.js"
19
20
  import { TRANSPORT_RULE_IDS, TRANSPORT_SKIP_REASON } from "./rules/checks/transport.js"
@@ -156,6 +157,7 @@ export function createValidator(opts: ValidatorOptions = {}): Validator {
156
157
  endOfRunToolCalls(r, emit, atIndex)
157
158
  endOfRunSteps(r, emit, atIndex)
158
159
  endOfRunReasoning(r, emit, atIndex)
160
+ endOfRunSubagents(r, emit, atIndex)
159
161
  }
160
162
 
161
163
  /** Opens the implicit run scope for streams that never announced one. */
@@ -325,6 +327,9 @@ export function createValidator(opts: ValidatorOptions = {}): Validator {
325
327
  case "activity":
326
328
  // No activity rules in the catalog yet.
327
329
  break
330
+ case "subagent":
331
+ handleSubagentEvent(a)
332
+ break
328
333
  case "special":
329
334
  if (type === "RAW") {
330
335
  const wrapped = ev.event
@@ -1,4 +1,4 @@
1
- // AUTO-GENERATED from @ag-ui/core v0.0.58 — do not edit by hand.
1
+ // AUTO-GENERATED from @ag-ui/core v0.0.59 — do not edit by hand.
2
2
  // Regenerate with: node scripts/generate-event-table.mjs
3
3
  // test/protocol-drift.test.ts fails if this file drifts from the installed SDK.
4
4
 
@@ -13,6 +13,7 @@ export type EventCategory =
13
13
  | "reasoning"
14
14
  | "thinking"
15
15
  | "special"
16
+ | "subagent"
16
17
 
17
18
  export interface FieldSpec {
18
19
  kind: FieldKind
@@ -31,7 +32,7 @@ export interface EventSpec {
31
32
  }
32
33
 
33
34
  /** Version of @ag-ui/core this table was derived from. */
34
- export const SDK_VERSION = "0.0.58"
35
+ export const SDK_VERSION = "0.0.59"
35
36
 
36
37
  /** Canonical wire event types and their schemas, derived from @ag-ui/core. */
37
38
  export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
@@ -39,78 +40,96 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
39
40
  category: "text",
40
41
  specUrl: "https://docs.ag-ui.com/concepts/events#textmessagestart",
41
42
  fields: {
43
+ "metadata": { kind: "object", required: false },
42
44
  "messageId": { kind: "string", required: true },
43
45
  "role": { kind: "string", required: false, enum: ["developer","system","assistant","user"] },
44
46
  "name": { kind: "string", required: false },
47
+ "subagentRunId": { kind: "string", required: false },
45
48
  },
46
49
  },
47
50
  "TEXT_MESSAGE_CONTENT": {
48
51
  category: "text",
49
52
  specUrl: "https://docs.ag-ui.com/concepts/events#textmessagecontent",
50
53
  fields: {
54
+ "metadata": { kind: "object", required: false },
51
55
  "messageId": { kind: "string", required: true },
52
56
  "delta": { kind: "string", required: true },
57
+ "subagentRunId": { kind: "string", required: false },
53
58
  },
54
59
  },
55
60
  "TEXT_MESSAGE_END": {
56
61
  category: "text",
57
62
  specUrl: "https://docs.ag-ui.com/concepts/events#textmessageend",
58
63
  fields: {
64
+ "metadata": { kind: "object", required: false },
59
65
  "messageId": { kind: "string", required: true },
66
+ "subagentRunId": { kind: "string", required: false },
60
67
  },
61
68
  },
62
69
  "TEXT_MESSAGE_CHUNK": {
63
70
  category: "text",
64
71
  specUrl: "https://docs.ag-ui.com/concepts/events#textmessagechunk",
65
72
  fields: {
73
+ "metadata": { kind: "object", required: false },
66
74
  "messageId": { kind: "string", required: false },
67
75
  "role": { kind: "string", required: false, enum: ["developer","system","assistant","user"] },
68
76
  "delta": { kind: "string", required: false },
69
77
  "name": { kind: "string", required: false },
78
+ "subagentRunId": { kind: "string", required: false },
70
79
  },
71
80
  },
72
81
  "TOOL_CALL_START": {
73
82
  category: "toolcall",
74
83
  specUrl: "https://docs.ag-ui.com/concepts/events#toolcallstart",
75
84
  fields: {
85
+ "metadata": { kind: "object", required: false },
76
86
  "toolCallId": { kind: "string", required: true },
77
87
  "toolCallName": { kind: "string", required: true },
78
88
  "parentMessageId": { kind: "string", required: false },
89
+ "subagentRunId": { kind: "string", required: false },
79
90
  },
80
91
  },
81
92
  "TOOL_CALL_ARGS": {
82
93
  category: "toolcall",
83
94
  specUrl: "https://docs.ag-ui.com/concepts/events#toolcallargs",
84
95
  fields: {
96
+ "metadata": { kind: "object", required: false },
85
97
  "toolCallId": { kind: "string", required: true },
86
98
  "delta": { kind: "string", required: true },
99
+ "subagentRunId": { kind: "string", required: false },
87
100
  },
88
101
  },
89
102
  "TOOL_CALL_END": {
90
103
  category: "toolcall",
91
104
  specUrl: "https://docs.ag-ui.com/concepts/events#toolcallend",
92
105
  fields: {
106
+ "metadata": { kind: "object", required: false },
93
107
  "toolCallId": { kind: "string", required: true },
108
+ "subagentRunId": { kind: "string", required: false },
94
109
  },
95
110
  },
96
111
  "TOOL_CALL_CHUNK": {
97
112
  category: "toolcall",
98
113
  specUrl: "https://docs.ag-ui.com/concepts/events#toolcallchunk",
99
114
  fields: {
115
+ "metadata": { kind: "object", required: false },
100
116
  "toolCallId": { kind: "string", required: false },
101
117
  "toolCallName": { kind: "string", required: false },
102
118
  "parentMessageId": { kind: "string", required: false },
103
119
  "delta": { kind: "string", required: false },
120
+ "subagentRunId": { kind: "string", required: false },
104
121
  },
105
122
  },
106
123
  "TOOL_CALL_RESULT": {
107
124
  category: "toolcall",
108
125
  specUrl: "https://docs.ag-ui.com/concepts/events#toolcallresult",
109
126
  fields: {
127
+ "metadata": { kind: "object", required: false },
110
128
  "messageId": { kind: "string", required: true },
111
129
  "toolCallId": { kind: "string", required: true },
112
130
  "content": { kind: "string", required: true },
113
131
  "role": { kind: "string", required: false, enum: ["tool"] },
132
+ "subagentRunId": { kind: "string", required: false },
114
133
  },
115
134
  },
116
135
  "THINKING_START": {
@@ -118,6 +137,7 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
118
137
  deprecated: "REASONING_START",
119
138
  specUrl: "https://docs.ag-ui.com/concepts/events#thinking-events-deprecated",
120
139
  fields: {
140
+ "metadata": { kind: "object", required: false },
121
141
  "title": { kind: "string", required: false },
122
142
  },
123
143
  },
@@ -126,6 +146,7 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
126
146
  deprecated: "REASONING_END",
127
147
  specUrl: "https://docs.ag-ui.com/concepts/events#thinking-events-deprecated",
128
148
  fields: {
149
+ "metadata": { kind: "object", required: false },
129
150
  },
130
151
  },
131
152
  "THINKING_TEXT_MESSAGE_START": {
@@ -133,6 +154,7 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
133
154
  deprecated: "REASONING_MESSAGE_START",
134
155
  specUrl: "https://docs.ag-ui.com/concepts/events#thinking-events-deprecated",
135
156
  fields: {
157
+ "metadata": { kind: "object", required: false },
136
158
  },
137
159
  },
138
160
  "THINKING_TEXT_MESSAGE_CONTENT": {
@@ -140,6 +162,7 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
140
162
  deprecated: "REASONING_MESSAGE_CONTENT",
141
163
  specUrl: "https://docs.ag-ui.com/concepts/events#thinking-events-deprecated",
142
164
  fields: {
165
+ "metadata": { kind: "object", required: false },
143
166
  "delta": { kind: "string", required: true },
144
167
  },
145
168
  },
@@ -148,26 +171,32 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
148
171
  deprecated: "REASONING_MESSAGE_END",
149
172
  specUrl: "https://docs.ag-ui.com/concepts/events#thinking-events-deprecated",
150
173
  fields: {
174
+ "metadata": { kind: "object", required: false },
151
175
  },
152
176
  },
153
177
  "STATE_SNAPSHOT": {
154
178
  category: "state",
155
179
  specUrl: "https://docs.ag-ui.com/concepts/events#statesnapshot",
156
180
  fields: {
181
+ "metadata": { kind: "object", required: false },
157
182
  "snapshot": { kind: "any", required: true },
183
+ "subagentRunId": { kind: "string", required: false },
158
184
  },
159
185
  },
160
186
  "STATE_DELTA": {
161
187
  category: "state",
162
188
  specUrl: "https://docs.ag-ui.com/concepts/events#statedelta",
163
189
  fields: {
190
+ "metadata": { kind: "object", required: false },
164
191
  "delta": { kind: "array", required: true },
192
+ "subagentRunId": { kind: "string", required: false },
165
193
  },
166
194
  },
167
195
  "MESSAGES_SNAPSHOT": {
168
196
  category: "state",
169
197
  specUrl: "https://docs.ag-ui.com/concepts/events#messagessnapshot",
170
198
  fields: {
199
+ "metadata": { kind: "object", required: false },
171
200
  "messages": { kind: "array", required: true },
172
201
  },
173
202
  },
@@ -175,41 +204,50 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
175
204
  category: "activity",
176
205
  specUrl: "https://docs.ag-ui.com/concepts/events#activitysnapshot",
177
206
  fields: {
207
+ "metadata": { kind: "object", required: false },
178
208
  "messageId": { kind: "string", required: true },
179
209
  "activityType": { kind: "string", required: true },
180
210
  "content": { kind: "object", required: true },
181
211
  "replace": { kind: "boolean", required: false },
212
+ "subagentRunId": { kind: "string", required: false },
182
213
  },
183
214
  },
184
215
  "ACTIVITY_DELTA": {
185
216
  category: "activity",
186
217
  specUrl: "https://docs.ag-ui.com/concepts/events#activitydelta",
187
218
  fields: {
219
+ "metadata": { kind: "object", required: false },
188
220
  "messageId": { kind: "string", required: true },
189
221
  "activityType": { kind: "string", required: true },
190
222
  "patch": { kind: "array", required: true },
223
+ "subagentRunId": { kind: "string", required: false },
191
224
  },
192
225
  },
193
226
  "RAW": {
194
227
  category: "special",
195
228
  specUrl: "https://docs.ag-ui.com/concepts/events#raw",
196
229
  fields: {
230
+ "metadata": { kind: "object", required: false },
197
231
  "event": { kind: "any", required: true },
198
232
  "source": { kind: "string", required: false },
233
+ "subagentRunId": { kind: "string", required: false },
199
234
  },
200
235
  },
201
236
  "CUSTOM": {
202
237
  category: "special",
203
238
  specUrl: "https://docs.ag-ui.com/concepts/events#custom",
204
239
  fields: {
240
+ "metadata": { kind: "object", required: false },
205
241
  "name": { kind: "string", required: true },
206
242
  "value": { kind: "any", required: true },
243
+ "subagentRunId": { kind: "string", required: false },
207
244
  },
208
245
  },
209
246
  "RUN_STARTED": {
210
247
  category: "lifecycle",
211
248
  specUrl: "https://docs.ag-ui.com/concepts/events#runstarted",
212
249
  fields: {
250
+ "metadata": { kind: "object", required: false },
213
251
  "threadId": { kind: "string", required: true },
214
252
  "runId": { kind: "string", required: true },
215
253
  "parentRunId": { kind: "string", required: false },
@@ -220,6 +258,7 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
220
258
  category: "lifecycle",
221
259
  specUrl: "https://docs.ag-ui.com/concepts/events#runfinished",
222
260
  fields: {
261
+ "metadata": { kind: "object", required: false },
223
262
  "threadId": { kind: "string", required: true },
224
263
  "runId": { kind: "string", required: true },
225
264
  "result": { kind: "any", required: false },
@@ -231,6 +270,7 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
231
270
  category: "lifecycle",
232
271
  specUrl: "https://docs.ag-ui.com/concepts/events#runerror",
233
272
  fields: {
273
+ "metadata": { kind: "object", required: false },
234
274
  "message": { kind: "string", required: true },
235
275
  "code": { kind: "string", required: false },
236
276
  "usage": { kind: "array", required: false },
@@ -240,68 +280,119 @@ export const EVENT_TABLE: Readonly<Record<string, EventSpec>> = {
240
280
  category: "lifecycle",
241
281
  specUrl: "https://docs.ag-ui.com/concepts/events#stepstarted",
242
282
  fields: {
283
+ "metadata": { kind: "object", required: false },
243
284
  "stepName": { kind: "string", required: true },
285
+ "subagentRunId": { kind: "string", required: false },
244
286
  },
245
287
  },
246
288
  "STEP_FINISHED": {
247
289
  category: "lifecycle",
248
290
  specUrl: "https://docs.ag-ui.com/concepts/events#stepfinished",
249
291
  fields: {
292
+ "metadata": { kind: "object", required: false },
250
293
  "stepName": { kind: "string", required: true },
294
+ "subagentRunId": { kind: "string", required: false },
251
295
  },
252
296
  },
253
297
  "REASONING_START": {
254
298
  category: "reasoning",
255
299
  specUrl: "https://docs.ag-ui.com/concepts/events#reasoningstart",
256
300
  fields: {
301
+ "metadata": { kind: "object", required: false },
257
302
  "messageId": { kind: "string", required: true },
303
+ "subagentRunId": { kind: "string", required: false },
258
304
  },
259
305
  },
260
306
  "REASONING_MESSAGE_START": {
261
307
  category: "reasoning",
262
308
  specUrl: "https://docs.ag-ui.com/concepts/events#reasoningmessagestart",
263
309
  fields: {
310
+ "metadata": { kind: "object", required: false },
264
311
  "messageId": { kind: "string", required: true },
265
312
  "role": { kind: "string", required: true, enum: ["reasoning"] },
313
+ "subagentRunId": { kind: "string", required: false },
266
314
  },
267
315
  },
268
316
  "REASONING_MESSAGE_CONTENT": {
269
317
  category: "reasoning",
270
318
  specUrl: "https://docs.ag-ui.com/concepts/events#reasoningmessagecontent",
271
319
  fields: {
320
+ "metadata": { kind: "object", required: false },
272
321
  "messageId": { kind: "string", required: true },
273
322
  "delta": { kind: "string", required: true },
323
+ "subagentRunId": { kind: "string", required: false },
274
324
  },
275
325
  },
276
326
  "REASONING_MESSAGE_END": {
277
327
  category: "reasoning",
278
328
  specUrl: "https://docs.ag-ui.com/concepts/events#reasoningmessageend",
279
329
  fields: {
330
+ "metadata": { kind: "object", required: false },
280
331
  "messageId": { kind: "string", required: true },
332
+ "subagentRunId": { kind: "string", required: false },
281
333
  },
282
334
  },
283
335
  "REASONING_MESSAGE_CHUNK": {
284
336
  category: "reasoning",
285
337
  specUrl: "https://docs.ag-ui.com/concepts/events#reasoningmessagechunk",
286
338
  fields: {
339
+ "metadata": { kind: "object", required: false },
287
340
  "messageId": { kind: "string", required: false },
288
341
  "delta": { kind: "string", required: false },
342
+ "subagentRunId": { kind: "string", required: false },
289
343
  },
290
344
  },
291
345
  "REASONING_END": {
292
346
  category: "reasoning",
293
347
  specUrl: "https://docs.ag-ui.com/concepts/events#reasoningend",
294
348
  fields: {
349
+ "metadata": { kind: "object", required: false },
295
350
  "messageId": { kind: "string", required: true },
351
+ "subagentRunId": { kind: "string", required: false },
296
352
  },
297
353
  },
298
354
  "REASONING_ENCRYPTED_VALUE": {
299
355
  category: "reasoning",
300
356
  specUrl: "https://docs.ag-ui.com/concepts/events#reasoningencryptedvalue",
301
357
  fields: {
358
+ "metadata": { kind: "object", required: false },
302
359
  "subtype": { kind: "string", required: true, enum: ["tool-call","message"] },
303
360
  "entityId": { kind: "string", required: true },
304
361
  "encryptedValue": { kind: "string", required: true },
362
+ "subagentRunId": { kind: "string", required: false },
363
+ },
364
+ },
365
+ "SUBAGENT_STARTED": {
366
+ category: "subagent",
367
+ specUrl: "https://docs.ag-ui.com/concepts/events#subagent-events",
368
+ fields: {
369
+ "metadata": { kind: "object", required: false },
370
+ "subagentRunId": { kind: "string", required: true },
371
+ "name": { kind: "string", required: true },
372
+ "description": { kind: "string", required: false },
373
+ "parentSubagentRunId": { kind: "string", required: false },
374
+ "parentToolCallId": { kind: "string", required: false },
375
+ "parentMessageId": { kind: "string", required: false },
376
+ },
377
+ },
378
+ "SUBAGENT_FINISHED": {
379
+ category: "subagent",
380
+ specUrl: "https://docs.ag-ui.com/concepts/events#subagent-events",
381
+ fields: {
382
+ "metadata": { kind: "object", required: false },
383
+ "subagentRunId": { kind: "string", required: true },
384
+ "result": { kind: "any", required: false },
385
+ "outcome": { kind: "object", required: false },
386
+ },
387
+ },
388
+ "SUBAGENT_ERROR": {
389
+ category: "subagent",
390
+ specUrl: "https://docs.ag-ui.com/concepts/events#subagent-events",
391
+ fields: {
392
+ "metadata": { kind: "object", required: false },
393
+ "subagentRunId": { kind: "string", required: true },
394
+ "message": { kind: "string", required: true },
395
+ "code": { kind: "string", required: false },
305
396
  },
306
397
  },
307
398
  }
@@ -0,0 +1,31 @@
1
+ // Machine-readable report: the core Report plus tool identification, so a
2
+ // stored document is self-describing.
3
+ import { RULES } from "../rules/catalog.js"
4
+ import type { Diagnostic, Report } from "../types.js"
5
+
6
+ export interface JsonReportOptions {
7
+ tool: { name: string; version: string }
8
+ /** What was validated: URL, file path, or "stdin". */
9
+ target?: string
10
+ }
11
+
12
+ export interface JsonDiagnostic extends Diagnostic {
13
+ /** The rule's catalog category, e.g. "toolcall". Joined in from the catalog by rule id. */
14
+ category?: string
15
+ }
16
+
17
+ export interface JsonReportDocument extends Omit<Report, "diagnostics"> {
18
+ tool: { name: string; version: string }
19
+ target?: string
20
+ diagnostics: JsonDiagnostic[]
21
+ }
22
+
23
+ export function toJsonReport(report: Report, opts: JsonReportOptions): JsonReportDocument {
24
+ const diagnostics: JsonDiagnostic[] = report.diagnostics.map((d) => {
25
+ const category = RULES.get(d.rule)?.category
26
+ return category === undefined ? { ...d } : { ...d, category }
27
+ })
28
+ const doc: JsonReportDocument = { tool: opts.tool, ...report, diagnostics }
29
+ if (opts.target !== undefined) doc.target = opts.target
30
+ return doc
31
+ }
@@ -34,12 +34,7 @@ export interface SarifLog {
34
34
  name: string
35
35
  version: string
36
36
  informationUri: string
37
- rules: Array<{
38
- id: string
39
- helpUri: string
40
- shortDescription?: { text: string }
41
- defaultConfiguration?: { level: SarifLevel }
42
- }>
37
+ rules: SarifRule[]
43
38
  }
44
39
  }
45
40
  results: SarifResult[]
@@ -57,6 +52,7 @@ type SarifRule = {
57
52
  helpUri: string
58
53
  shortDescription?: { text: string }
59
54
  defaultConfiguration?: { level: SarifLevel }
55
+ properties?: { tags: string[] }
60
56
  }
61
57
 
62
58
  export function toSarif(report: Report, opts: SarifOptions): SarifLog {
@@ -68,6 +64,7 @@ export function toSarif(report: Report, opts: SarifOptions): SarifLog {
68
64
  if (catalog !== undefined) {
69
65
  entry.shortDescription = { text: catalog.title }
70
66
  entry.defaultConfiguration = { level: LEVEL[catalog.severity] }
67
+ entry.properties = { tags: [catalog.category] }
71
68
  }
72
69
  rules.set(d.rule, entry)
73
70
  }
@@ -6,19 +6,22 @@
6
6
  // malformed catalog is a programming error in this package, so the loader
7
7
  // throws loudly at import time.
8
8
 
9
- import catalogJson from "./catalog.json"
9
+ import catalogJson from "../../../spec/catalog.json"
10
10
 
11
11
  export type Severity = "error" | "warning" | "info"
12
12
  export type SeverityOrOff = Severity | "off"
13
13
 
14
14
  const SEVERITIES: readonly string[] = ["error", "warning", "info"]
15
15
  const LAYERS: readonly string[] = ["core", "transport"]
16
+ export const CATEGORIES: readonly string[] = ["lifecycle", "text", "toolcall", "state", "reasoning", "transport", "hygiene", "subagent"]
16
17
 
17
18
  export interface RuleDefinition {
18
19
  /** e.g. "AGUI203" */
19
20
  id: string
20
21
  severity: Severity
21
22
  title: string
23
+ /** Which part of the protocol this rule governs, e.g. "toolcall". */
24
+ category: string
22
25
  /** Human template with {placeholder} slots filled per diagnostic. */
23
26
  messageTemplate: string
24
27
  /** Governing spec section. Mandatory: rules that cannot cite one don't ship. */
@@ -58,6 +61,7 @@ export function validateCatalog(data: unknown): Catalog {
58
61
  seen.add(rule.id)
59
62
  if (!SEVERITIES.includes(rule.severity)) problems.push(`${where}: bad severity '${rule.severity}'`)
60
63
  if (!rule.title) problems.push(`${where}: missing title`)
64
+ if (!CATEGORIES.includes(rule.category)) problems.push(`${where}: bad category '${rule.category}'`)
61
65
  if (!rule.messageTemplate) problems.push(`${where}: missing messageTemplate`)
62
66
  if (!rule.specUrl?.startsWith("https://")) problems.push(`${where}: specUrl must be an https URL`)
63
67
  if (!rule.since) problems.push(`${where}: missing since`)
@@ -24,6 +24,10 @@ export interface OpenToolCall {
24
24
  startIndex: number
25
25
  args: string
26
26
  sawArgs: boolean
27
+ /** subagentRunId this call belongs to (explicit, or inherited from the
28
+ * parentMessageId's owner - AGUI606's "an untagged tool call inherits the
29
+ * parent message's owner"). undefined means it belongs to the run itself. */
30
+ owner: string | undefined
27
31
  }
28
32
 
29
33
  export interface RunState {
@@ -34,19 +38,33 @@ export interface RunState {
34
38
  implicit: boolean
35
39
  terminal: { type: string; index: number } | null
36
40
 
37
- openMessages: Map<string, { startIndex: number }>
41
+ openMessages: Map<string, { startIndex: number; owner: string | undefined }>
38
42
  /** messageId -> index of the event that closed it. */
39
43
  closedMessages: Map<string, number>
40
44
  /** Every message id observed (starts, chunks, snapshots, tool results). */
41
45
  knownMessageIds: Set<string>
46
+ /** messageId -> owning subagentRunId (undefined for the run), for AGUI606's
47
+ * tool-call-inherits-its-parent-message's-owner resolution. Persists past
48
+ * the message closing, unlike openMessages/closedMessages. */
49
+ messageOwner: Map<string, string | undefined>
42
50
 
43
51
  openToolCalls: Map<string, OpenToolCall>
44
52
  closedToolCalls: Map<string, number>
45
53
  /** Ids also known from MESSAGES_SNAPSHOT history. */
46
54
  knownToolCallIds: Set<string>
47
55
 
48
- /** stepName -> re-entrant open count (SQ-10) and first-open index. */
49
- openSteps: Map<string, { count: number; firstIndex: number }>
56
+ /** stepName -> re-entrant open count (SQ-10), first-open index, and the
57
+ * subagentRunId recorded from the first STEP_STARTED (AGUI606) - a
58
+ * re-entrant reopen does not change it. */
59
+ openSteps: Map<string, { count: number; firstIndex: number; owner: string | undefined }>
60
+
61
+ openSubagents: Map<string, { startIndex: number }>
62
+ /** Closed via SUBAGENT_FINISHED (resumable only when outcome.type ===
63
+ * "suspended" - the one case where a later SUBAGENT_STARTED reusing this
64
+ * id is a legitimate continuation, not a duplicate, AGUI601) or SUBAGENT_ERROR. */
65
+ closedSubagents: Map<string, { index: number; resumable: boolean }>
66
+ /** Every subagentRunId ever started (open or closed), for parentSubagentRunId checks. */
67
+ knownSubagentRunIds: Set<string>
50
68
 
51
69
  openReasoningBlocks: Map<string, number>
52
70
  openReasoningMessages: Map<string, number>
@@ -78,10 +96,14 @@ export function newRunState(init: {
78
96
  openMessages: new Map(),
79
97
  closedMessages: new Map(),
80
98
  knownMessageIds: new Set(),
99
+ messageOwner: new Map(),
81
100
  openToolCalls: new Map(),
82
101
  closedToolCalls: new Map(),
83
102
  knownToolCallIds: new Set(),
84
103
  openSteps: new Map(),
104
+ openSubagents: new Map(),
105
+ closedSubagents: new Map(),
106
+ knownSubagentRunIds: new Set(),
85
107
  openReasoningBlocks: new Map(),
86
108
  openReasoningMessages: new Map(),
87
109
  state: { known: false, value: undefined, deltasSinceSnapshot: 0, snapshotSeen: false, agui301Fired: false },
@@ -115,3 +137,26 @@ export function str(event: Record<string, unknown>, field: string): string | und
115
137
  const v = event[field]
116
138
  return typeof v === "string" ? v : undefined
117
139
  }
140
+
141
+ /** AGUI606 — a continuation/close event's explicit subagentRunId must agree
142
+ * with the owner recorded when its entity opened. Only compared when the
143
+ * event explicitly tags itself; omitting the field is never flagged (SQ-15).
144
+ * Shared by text/toolcalls/lifecycle — not the *_CHUNK forms or
145
+ * TOOL_CALL_RESULT, which are independently attributed by spec. */
146
+ export function checkOwnerConsistency(
147
+ emit: EmitFn,
148
+ type: string,
149
+ entityType: string,
150
+ entityId: string,
151
+ event: Record<string, unknown>,
152
+ owner: string | undefined,
153
+ ): void {
154
+ const actual = str(event, "subagentRunId")
155
+ if (actual !== undefined && actual !== owner) {
156
+ emit(
157
+ "AGUI606",
158
+ { type, actual, entityType, entityId, expected: owner !== undefined ? `'${owner}'` : "the run" },
159
+ { pointer: "/subagentRunId" },
160
+ )
161
+ }
162
+ }