ag-ui-validate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +229 -0
  3. package/dist/catalog-BglXBNbL.js +472 -0
  4. package/dist/catalog-BglXBNbL.js.map +1 -0
  5. package/dist/catalog-Ci9dqc1a.cjs +495 -0
  6. package/dist/catalog-Ci9dqc1a.cjs.map +1 -0
  7. package/dist/cli.js +2783 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/index-Hmqj3r_r.d.cts +52 -0
  10. package/dist/index-oNG1kOp9.d.ts +52 -0
  11. package/dist/index.cjs +14 -0
  12. package/dist/index.d.cts +3 -0
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.js +3 -0
  15. package/dist/report.cjs +139 -0
  16. package/dist/report.cjs.map +1 -0
  17. package/dist/report.d.cts +85 -0
  18. package/dist/report.d.ts +85 -0
  19. package/dist/report.js +134 -0
  20. package/dist/report.js.map +1 -0
  21. package/dist/src-HmI-kxef.cjs +1596 -0
  22. package/dist/src-HmI-kxef.cjs.map +1 -0
  23. package/dist/src-rGZ2G4qA.js +1555 -0
  24. package/dist/src-rGZ2G4qA.js.map +1 -0
  25. package/dist/transport.cjs +329 -0
  26. package/dist/transport.cjs.map +1 -0
  27. package/dist/transport.d.cts +89 -0
  28. package/dist/transport.d.ts +89 -0
  29. package/dist/transport.js +323 -0
  30. package/dist/transport.js.map +1 -0
  31. package/dist/types-oH_QTnn2.d.cts +148 -0
  32. package/dist/types-oH_QTnn2.d.ts +148 -0
  33. package/dist/vitest.d.ts +28 -0
  34. package/dist/vitest.js +2089 -0
  35. package/dist/vitest.js.map +1 -0
  36. package/package.json +127 -0
  37. package/src/cli-args.ts +202 -0
  38. package/src/cli.ts +147 -0
  39. package/src/index.ts +465 -0
  40. package/src/protocol/event-table.ts +316 -0
  41. package/src/protocol/jsonpatch.ts +220 -0
  42. package/src/report/index.ts +10 -0
  43. package/src/report/json.ts +20 -0
  44. package/src/report/junit.ts +56 -0
  45. package/src/report/pretty.ts +59 -0
  46. package/src/report/sarif.ts +109 -0
  47. package/src/rules/catalog.json +431 -0
  48. package/src/rules/catalog.ts +84 -0
  49. package/src/rules/checks/context.ts +117 -0
  50. package/src/rules/checks/lifecycle.ts +59 -0
  51. package/src/rules/checks/reasoning.ts +97 -0
  52. package/src/rules/checks/state.ts +72 -0
  53. package/src/rules/checks/text.ts +109 -0
  54. package/src/rules/checks/toolcalls.ts +167 -0
  55. package/src/rules/checks/transport.ts +17 -0
  56. package/src/transport/index.ts +331 -0
  57. package/src/transport/ndjson.ts +25 -0
  58. package/src/transport/sse.ts +126 -0
  59. package/src/types.ts +136 -0
  60. package/src/vitest/index.ts +19 -0
  61. package/src/vitest/matcher.ts +77 -0
@@ -0,0 +1,89 @@
1
+ import { d as ValidatorOptions, o as Report, r as Diagnostic } from "./types-oH_QTnn2.cjs";
2
+ //#region src/transport/ndjson.d.ts
3
+ declare function ndjsonLines(source: AsyncIterable<Uint8Array>): AsyncGenerator<string>;
4
+ //#endregion
5
+ //#region src/transport/sse.d.ts
6
+ type SseProblemCode = "json-line-without-data-prefix" | "truncated-frame";
7
+ type SseItem = {
8
+ kind: "event";
9
+ data: string;
10
+ event?: string;
11
+ id?: string;
12
+ } | {
13
+ kind: "comment";
14
+ text: string;
15
+ } | {
16
+ kind: "problem";
17
+ code: SseProblemCode;
18
+ detail: string;
19
+ };
20
+ declare function sseItems(source: AsyncIterable<Uint8Array>): AsyncGenerator<SseItem>;
21
+ //#endregion
22
+ //#region src/transport/index.d.ts
23
+ interface TransportRequestInit {
24
+ method: string;
25
+ headers: Record<string, string>;
26
+ body?: string;
27
+ signal?: AbortSignal;
28
+ }
29
+ interface TransportResponseLike {
30
+ status: number;
31
+ headers: {
32
+ get(name: string): string | null;
33
+ };
34
+ body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array> | null;
35
+ }
36
+ type FetchLike = (url: string, init: TransportRequestInit) => Promise<TransportResponseLike>;
37
+ /** Operational failure (unreachable endpoint, non-2xx, no body) — distinct
38
+ * from conformance findings, which are diagnostics. */
39
+ declare class TransportError extends Error {
40
+ status?: number;
41
+ constructor(message: string, status?: number);
42
+ }
43
+ interface TransportOptions {
44
+ /** Options passed through to createValidator ("transport" layer is added). */
45
+ validator?: ValidatorOptions;
46
+ /** Extra request headers (validateEndpoint only). */
47
+ headers?: Record<string, string>;
48
+ /** RunAgentInput to POST; defaults to a minimal valid input with one user message. */
49
+ input?: unknown;
50
+ method?: "POST" | "GET";
51
+ /** AGUI506 window; default 30 000 ms. */
52
+ keepaliveWindowMs?: number;
53
+ /** Abort the request after this long (validateEndpoint only). */
54
+ timeoutMs?: number;
55
+ /** Injectable fetch (tests, custom stacks). Default: globalThis.fetch. */
56
+ fetchImpl?: FetchLike;
57
+ /** Injectable clock for keepalive/buffering measurement. Default: Date.now. */
58
+ now?: () => number;
59
+ /**
60
+ * The body is a recording (file, stdin) rather than a live connection.
61
+ * Timing-based rules (AGUI506, AGUI507) and mid-stream disconnect detection
62
+ * (AGUI508) are meaningless for recordings; they are reported as skipped
63
+ * with a reason instead of risking false positives, and read failures become
64
+ * TransportErrors (tool failure) rather than AGUI508 findings.
65
+ */
66
+ recorded?: boolean;
67
+ signal?: AbortSignal;
68
+ /** Called after each fed event with the diagnostics it produced. */
69
+ onEvent?: (raw: string, diagnostics: Diagnostic[]) => void;
70
+ /** Called for every diagnostic as soon as it is detected. */
71
+ onDiagnostic?: (diagnostic: Diagnostic) => void;
72
+ }
73
+ interface TransportResult {
74
+ report: Report;
75
+ /** HTTP status; null when validating a bare body/recording. */
76
+ status: number | null;
77
+ contentType: string | null;
78
+ /** Events fed to the validator (SSE frames / NDJSON lines). */
79
+ eventCount: number;
80
+ /** Present when the connection failed mid-stream (see AGUI508). */
81
+ transportError?: string;
82
+ }
83
+ /** The minimal valid RunAgentInput POSTed when none is supplied. */
84
+ declare function defaultRunAgentInput(): Record<string, unknown>;
85
+ declare function validateBody(body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>, contentType: string | null, opts?: TransportOptions): Promise<TransportResult>;
86
+ declare function validateEndpoint(url: string, opts?: TransportOptions): Promise<TransportResult>;
87
+ //#endregion
88
+ export { FetchLike, type SseItem, type SseProblemCode, TransportError, TransportOptions, TransportRequestInit, TransportResponseLike, TransportResult, defaultRunAgentInput, ndjsonLines, sseItems, validateBody, validateEndpoint };
89
+ //# sourceMappingURL=transport.d.cts.map
@@ -0,0 +1,89 @@
1
+ import { d as ValidatorOptions, o as Report, r as Diagnostic } from "./types-oH_QTnn2.js";
2
+ //#region src/transport/ndjson.d.ts
3
+ declare function ndjsonLines(source: AsyncIterable<Uint8Array>): AsyncGenerator<string>;
4
+ //#endregion
5
+ //#region src/transport/sse.d.ts
6
+ type SseProblemCode = "json-line-without-data-prefix" | "truncated-frame";
7
+ type SseItem = {
8
+ kind: "event";
9
+ data: string;
10
+ event?: string;
11
+ id?: string;
12
+ } | {
13
+ kind: "comment";
14
+ text: string;
15
+ } | {
16
+ kind: "problem";
17
+ code: SseProblemCode;
18
+ detail: string;
19
+ };
20
+ declare function sseItems(source: AsyncIterable<Uint8Array>): AsyncGenerator<SseItem>;
21
+ //#endregion
22
+ //#region src/transport/index.d.ts
23
+ interface TransportRequestInit {
24
+ method: string;
25
+ headers: Record<string, string>;
26
+ body?: string;
27
+ signal?: AbortSignal;
28
+ }
29
+ interface TransportResponseLike {
30
+ status: number;
31
+ headers: {
32
+ get(name: string): string | null;
33
+ };
34
+ body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array> | null;
35
+ }
36
+ type FetchLike = (url: string, init: TransportRequestInit) => Promise<TransportResponseLike>;
37
+ /** Operational failure (unreachable endpoint, non-2xx, no body) — distinct
38
+ * from conformance findings, which are diagnostics. */
39
+ declare class TransportError extends Error {
40
+ status?: number;
41
+ constructor(message: string, status?: number);
42
+ }
43
+ interface TransportOptions {
44
+ /** Options passed through to createValidator ("transport" layer is added). */
45
+ validator?: ValidatorOptions;
46
+ /** Extra request headers (validateEndpoint only). */
47
+ headers?: Record<string, string>;
48
+ /** RunAgentInput to POST; defaults to a minimal valid input with one user message. */
49
+ input?: unknown;
50
+ method?: "POST" | "GET";
51
+ /** AGUI506 window; default 30 000 ms. */
52
+ keepaliveWindowMs?: number;
53
+ /** Abort the request after this long (validateEndpoint only). */
54
+ timeoutMs?: number;
55
+ /** Injectable fetch (tests, custom stacks). Default: globalThis.fetch. */
56
+ fetchImpl?: FetchLike;
57
+ /** Injectable clock for keepalive/buffering measurement. Default: Date.now. */
58
+ now?: () => number;
59
+ /**
60
+ * The body is a recording (file, stdin) rather than a live connection.
61
+ * Timing-based rules (AGUI506, AGUI507) and mid-stream disconnect detection
62
+ * (AGUI508) are meaningless for recordings; they are reported as skipped
63
+ * with a reason instead of risking false positives, and read failures become
64
+ * TransportErrors (tool failure) rather than AGUI508 findings.
65
+ */
66
+ recorded?: boolean;
67
+ signal?: AbortSignal;
68
+ /** Called after each fed event with the diagnostics it produced. */
69
+ onEvent?: (raw: string, diagnostics: Diagnostic[]) => void;
70
+ /** Called for every diagnostic as soon as it is detected. */
71
+ onDiagnostic?: (diagnostic: Diagnostic) => void;
72
+ }
73
+ interface TransportResult {
74
+ report: Report;
75
+ /** HTTP status; null when validating a bare body/recording. */
76
+ status: number | null;
77
+ contentType: string | null;
78
+ /** Events fed to the validator (SSE frames / NDJSON lines). */
79
+ eventCount: number;
80
+ /** Present when the connection failed mid-stream (see AGUI508). */
81
+ transportError?: string;
82
+ }
83
+ /** The minimal valid RunAgentInput POSTed when none is supplied. */
84
+ declare function defaultRunAgentInput(): Record<string, unknown>;
85
+ declare function validateBody(body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>, contentType: string | null, opts?: TransportOptions): Promise<TransportResult>;
86
+ declare function validateEndpoint(url: string, opts?: TransportOptions): Promise<TransportResult>;
87
+ //#endregion
88
+ export { FetchLike, type SseItem, type SseProblemCode, TransportError, TransportOptions, TransportRequestInit, TransportResponseLike, TransportResult, defaultRunAgentInput, ndjsonLines, sseItems, validateBody, validateEndpoint };
89
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1,323 @@
1
+ import { t as createValidator } from "./src-rGZ2G4qA.js";
2
+ //#region src/transport/ndjson.ts
3
+ async function* ndjsonLines(source) {
4
+ const decoder = new TextDecoder("utf-8");
5
+ let buffer = "";
6
+ function clean(line) {
7
+ const trimmed = line.endsWith("\r") ? line.slice(0, -1) : line;
8
+ return trimmed.trim() === "" ? null : trimmed;
9
+ }
10
+ for await (const chunk of source) {
11
+ buffer += decoder.decode(chunk, { stream: true });
12
+ let i;
13
+ while ((i = buffer.indexOf("\n")) !== -1) {
14
+ const line = clean(buffer.slice(0, i));
15
+ buffer = buffer.slice(i + 1);
16
+ if (line !== null) yield line;
17
+ }
18
+ }
19
+ buffer += decoder.decode();
20
+ const last = clean(buffer);
21
+ if (last !== null) yield last;
22
+ }
23
+ //#endregion
24
+ //#region src/transport/sse.ts
25
+ const truncate = (s, n) => s.length > n ? `${s.slice(0, n)}…` : s;
26
+ async function* sseItems(source) {
27
+ const decoder = new TextDecoder("utf-8");
28
+ let buffer = "";
29
+ let sawFirstChars = false;
30
+ let dataLines = [];
31
+ let eventName = "";
32
+ let lastId;
33
+ function handleLine(line) {
34
+ if (line === "") {
35
+ const data = dataLines.join("\n");
36
+ const hadData = dataLines.length > 0;
37
+ dataLines = [];
38
+ const name = eventName;
39
+ eventName = "";
40
+ if (!hadData || data === "") return null;
41
+ const item = {
42
+ kind: "event",
43
+ data
44
+ };
45
+ if (name !== "") item.event = name;
46
+ if (lastId !== void 0) item.id = lastId;
47
+ return item;
48
+ }
49
+ if (line.startsWith(":")) return {
50
+ kind: "comment",
51
+ text: line.slice(1)
52
+ };
53
+ const colon = line.indexOf(":");
54
+ const field = colon === -1 ? line : line.slice(0, colon);
55
+ let value = colon === -1 ? "" : line.slice(colon + 1);
56
+ if (value.startsWith(" ")) value = value.slice(1);
57
+ switch (field) {
58
+ case "data":
59
+ dataLines.push(value);
60
+ return null;
61
+ case "event":
62
+ eventName = value;
63
+ return null;
64
+ case "id":
65
+ if (!value.includes("\0")) lastId = value;
66
+ return null;
67
+ case "retry": return null;
68
+ default: {
69
+ const trimmed = line.trimStart();
70
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return {
71
+ kind: "problem",
72
+ code: "json-line-without-data-prefix",
73
+ detail: `line '${truncate(trimmed, 60)}' looks like a JSON payload but lacks the 'data:' field prefix, so SSE clients silently drop it`
74
+ };
75
+ return null;
76
+ }
77
+ }
78
+ }
79
+ function* drainLines(eof) {
80
+ for (;;) {
81
+ const nl = buffer.indexOf("\n");
82
+ const cr = buffer.indexOf("\r");
83
+ let end;
84
+ let next;
85
+ if (cr !== -1 && (nl === -1 || cr < nl)) {
86
+ if (cr === buffer.length - 1 && !eof) return;
87
+ end = cr;
88
+ next = buffer[cr + 1] === "\n" ? cr + 2 : cr + 1;
89
+ } else if (nl !== -1) {
90
+ end = nl;
91
+ next = nl + 1;
92
+ } else return;
93
+ const line = buffer.slice(0, end);
94
+ buffer = buffer.slice(next);
95
+ const item = handleLine(line);
96
+ if (item !== null) yield item;
97
+ }
98
+ }
99
+ for await (const chunk of source) {
100
+ buffer += decoder.decode(chunk, { stream: true });
101
+ if (!sawFirstChars && buffer.length > 0) {
102
+ if (buffer.startsWith("")) buffer = buffer.slice(1);
103
+ sawFirstChars = true;
104
+ }
105
+ yield* drainLines(false);
106
+ }
107
+ buffer += decoder.decode();
108
+ yield* drainLines(true);
109
+ if (buffer !== "") yield {
110
+ kind: "problem",
111
+ code: "truncated-frame",
112
+ detail: `stream ended mid-line: '${truncate(buffer, 60)}' (no trailing newline; the frame was never dispatched)`
113
+ };
114
+ else if (dataLines.length > 0) yield {
115
+ kind: "problem",
116
+ code: "truncated-frame",
117
+ detail: "stream ended with a pending frame that was never terminated by a blank line"
118
+ };
119
+ }
120
+ //#endregion
121
+ //#region src/transport/index.ts
122
+ const DEFAULT_KEEPALIVE_MS = 3e4;
123
+ /** Operational failure (unreachable endpoint, non-2xx, no body) — distinct
124
+ * from conformance findings, which are diagnostics. */
125
+ var TransportError = class extends Error {
126
+ status;
127
+ constructor(message, status) {
128
+ super(message);
129
+ this.name = "TransportError";
130
+ if (status !== void 0) this.status = status;
131
+ }
132
+ };
133
+ function randomId(prefix) {
134
+ const c = globalThis.crypto;
135
+ return `${prefix}_${c !== void 0 && "randomUUID" in c ? c.randomUUID().slice(0, 8) : Math.random().toString(36).slice(2, 10)}`;
136
+ }
137
+ /** The minimal valid RunAgentInput POSTed when none is supplied. */
138
+ function defaultRunAgentInput() {
139
+ return {
140
+ threadId: randomId("thread"),
141
+ runId: randomId("run"),
142
+ state: {},
143
+ messages: [{
144
+ id: randomId("msg"),
145
+ role: "user",
146
+ content: "Hello! Please respond briefly."
147
+ }],
148
+ tools: [],
149
+ context: [],
150
+ forwardedProps: {}
151
+ };
152
+ }
153
+ async function* iterateBody(body) {
154
+ if (Symbol.asyncIterator in body) {
155
+ yield* body;
156
+ return;
157
+ }
158
+ const reader = body.getReader();
159
+ try {
160
+ for (;;) {
161
+ const { done, value } = await reader.read();
162
+ if (done) return;
163
+ if (value !== void 0) yield value;
164
+ }
165
+ } finally {
166
+ reader.releaseLock();
167
+ }
168
+ }
169
+ /** Buffers up to the first line to guess SSE vs NDJSON, then replays. */
170
+ async function sniffFormat(source) {
171
+ const held = [];
172
+ const decoder = new TextDecoder();
173
+ let text = "";
174
+ while (!text.includes("\n") && text.length < 4096) {
175
+ const { done, value } = await source.next();
176
+ if (done) break;
177
+ held.push(value);
178
+ text += decoder.decode(value, { stream: true });
179
+ }
180
+ const firstLine = (text.split("\n")[0] ?? "").trim();
181
+ const format = firstLine.startsWith("{") || firstLine.startsWith("[") ? "ndjson" : "sse";
182
+ async function* replay() {
183
+ yield* held;
184
+ for (;;) {
185
+ const { done, value } = await source.next();
186
+ if (done) return;
187
+ yield value;
188
+ }
189
+ }
190
+ return {
191
+ format,
192
+ replay: replay()
193
+ };
194
+ }
195
+ async function validateBody(body, contentType, opts = {}) {
196
+ const userLayers = opts.validator?.layers ?? [];
197
+ const layers = [.../* @__PURE__ */ new Set([
198
+ ...userLayers,
199
+ "core",
200
+ "transport"
201
+ ])];
202
+ const v = createValidator({
203
+ ...opts.validator ?? {},
204
+ layers
205
+ });
206
+ const now = opts.now ?? Date.now;
207
+ const keepaliveWindow = opts.keepaliveWindowMs ?? DEFAULT_KEEPALIVE_MS;
208
+ const recorded = opts.recorded === true;
209
+ const emitTransport = (rule, params, extra) => {
210
+ const d = v.emitExternal(rule, params, extra);
211
+ if (d !== null) opts.onDiagnostic?.(d);
212
+ return d;
213
+ };
214
+ let chunkCount = 0;
215
+ let maxGapMs = 0;
216
+ let lastArrival = null;
217
+ async function* tapped() {
218
+ for await (const chunk of iterateBody(body)) {
219
+ const t = now();
220
+ if (lastArrival !== null) maxGapMs = Math.max(maxGapMs, t - lastArrival);
221
+ lastArrival = t;
222
+ chunkCount += 1;
223
+ yield chunk;
224
+ }
225
+ }
226
+ const mime = contentType === null ? null : (contentType.split(";")[0] ?? "").trim().toLowerCase();
227
+ if (contentType === null) v.markSkipped("AGUI505", "no Content-Type header is available for this input");
228
+ else if (mime !== "text/event-stream" && mime !== "application/x-ndjson") emitTransport("AGUI505", { contentType: mime === "" ? "(none)" : mime });
229
+ if (recorded) {
230
+ v.markSkipped("AGUI506", "keepalive timing is not meaningful for recorded input");
231
+ v.markSkipped("AGUI507", "chunk arrival timing is not meaningful for recorded input");
232
+ v.markSkipped("AGUI508", "abnormal disconnects cannot be distinguished from end-of-capture in recorded input");
233
+ }
234
+ let format;
235
+ let stream = tapped();
236
+ if (mime === "text/event-stream") format = "sse";
237
+ else if (mime === "application/x-ndjson") format = "ndjson";
238
+ else ({format, replay: stream} = await sniffFormat(stream));
239
+ if (format === "ndjson") v.markSkipped("AGUI501", "the stream is NDJSON; there is no SSE framing to check");
240
+ let eventCount = 0;
241
+ let runOpen = false;
242
+ let openRunId = null;
243
+ const feedRaw = (raw) => {
244
+ eventCount += 1;
245
+ const diags = v.feed(raw);
246
+ try {
247
+ const parsed = JSON.parse(raw);
248
+ if (parsed?.type === "RUN_STARTED") {
249
+ runOpen = true;
250
+ openRunId = typeof parsed.runId === "string" ? parsed.runId : null;
251
+ } else if (parsed?.type === "RUN_FINISHED" || parsed?.type === "RUN_ERROR") runOpen = false;
252
+ } catch {}
253
+ opts.onEvent?.(raw, diags);
254
+ if (opts.onDiagnostic !== void 0) for (const d of diags) opts.onDiagnostic(d);
255
+ };
256
+ let transportError;
257
+ try {
258
+ if (format === "sse") {
259
+ for await (const item of sseItems(stream)) if (item.kind === "event") feedRaw(item.data);
260
+ else if (item.kind === "problem") emitTransport("AGUI501", { detail: item.detail });
261
+ } else for await (const line of ndjsonLines(stream)) feedRaw(line);
262
+ } catch (e) {
263
+ if (recorded) throw e instanceof TransportError ? e : new TransportError(`failed to read recorded input: ${e instanceof Error ? e.message : String(e)}`);
264
+ transportError = e instanceof Error ? e.message : String(e);
265
+ if (runOpen) emitTransport("AGUI508", { runId: openRunId ?? "(unknown)" });
266
+ }
267
+ if (!recorded) {
268
+ if (lastArrival !== null) maxGapMs = Math.max(maxGapMs, now() - lastArrival);
269
+ if (maxGapMs > keepaliveWindow) emitTransport("AGUI506", { seconds: Math.round(maxGapMs / 1e3) });
270
+ if (chunkCount === 1 && eventCount >= 3) emitTransport("AGUI507", { detail: `entire body (${eventCount} events) arrived in a single chunk` });
271
+ }
272
+ const finalDiags = v.finalize();
273
+ if (opts.onDiagnostic !== void 0) for (const d of finalDiags) opts.onDiagnostic(d);
274
+ const result = {
275
+ report: v.report(),
276
+ status: null,
277
+ contentType,
278
+ eventCount
279
+ };
280
+ if (transportError !== void 0) result.transportError = transportError;
281
+ return result;
282
+ }
283
+ async function validateEndpoint(url, opts = {}) {
284
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
285
+ if (fetchImpl === void 0) throw new TransportError("no fetch implementation available; pass fetchImpl");
286
+ const method = opts.method ?? "POST";
287
+ const headers = {
288
+ accept: "text/event-stream, application/x-ndjson",
289
+ ...method === "POST" ? { "content-type": "application/json" } : {},
290
+ ...opts.headers ?? {}
291
+ };
292
+ const controller = new AbortController();
293
+ const timers = [];
294
+ if (opts.timeoutMs !== void 0) timers.push(setTimeout(() => controller.abort(new TransportError(`timed out after ${opts.timeoutMs}ms`)), opts.timeoutMs));
295
+ opts.signal?.addEventListener("abort", () => controller.abort(opts.signal?.reason), { once: true });
296
+ try {
297
+ let res;
298
+ try {
299
+ const init = {
300
+ method,
301
+ headers,
302
+ signal: controller.signal
303
+ };
304
+ if (method === "POST") init.body = JSON.stringify(opts.input ?? defaultRunAgentInput());
305
+ res = await fetchImpl(url, init);
306
+ } catch (e) {
307
+ throw e instanceof TransportError ? e : new TransportError(`request failed: ${e instanceof Error ? e.message : String(e)}`);
308
+ }
309
+ if (res.status < 200 || res.status >= 300) throw new TransportError(`endpoint responded with HTTP ${res.status}`, res.status);
310
+ if (res.body === null) throw new TransportError("response has no body", res.status);
311
+ const contentType = res.headers.get("content-type") ?? "";
312
+ return {
313
+ ...await validateBody(res.body, contentType, opts),
314
+ status: res.status
315
+ };
316
+ } finally {
317
+ for (const timer of timers) clearTimeout(timer);
318
+ }
319
+ }
320
+ //#endregion
321
+ export { TransportError, defaultRunAgentInput, ndjsonLines, sseItems, validateBody, validateEndpoint };
322
+
323
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","names":[],"sources":["../src/transport/ndjson.ts","../src/transport/sse.ts","../src/transport/index.ts"],"sourcesContent":["// Incremental NDJSON (application/x-ndjson) line splitter. Yields non-empty\n// lines with trailing CR stripped; the core decides whether they parse.\n\nexport async function* ndjsonLines(source: AsyncIterable<Uint8Array>): AsyncGenerator<string> {\n const decoder = new TextDecoder(\"utf-8\")\n let buffer = \"\"\n\n function clean(line: string): string | null {\n const trimmed = line.endsWith(\"\\r\") ? line.slice(0, -1) : line\n return trimmed.trim() === \"\" ? null : trimmed\n }\n\n for await (const chunk of source) {\n buffer += decoder.decode(chunk, { stream: true })\n let i: number\n while ((i = buffer.indexOf(\"\\n\")) !== -1) {\n const line = clean(buffer.slice(0, i))\n buffer = buffer.slice(i + 1)\n if (line !== null) yield line\n }\n }\n buffer += decoder.decode()\n const last = clean(buffer)\n if (last !== null) yield last\n}\n","// WHATWG-compliant incremental SSE (text/event-stream) parser.\n// https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation\n//\n// Beyond plain parsing it surfaces framing anomalies relevant to AGUI501:\n// - json-line-without-data-prefix: a line that looks like a JSON payload but\n// has no \"data:\" field prefix. Spec-wise it is an unknown field and gets\n// silently dropped by every SSE client — the classic broken-server bug.\n// - truncated-frame: the stream ended mid-frame (pending data never\n// dispatched, or a partial final line).\n\nexport type SseProblemCode = \"json-line-without-data-prefix\" | \"truncated-frame\"\n\nexport type SseItem =\n | { kind: \"event\"; data: string; event?: string; id?: string }\n | { kind: \"comment\"; text: string }\n | { kind: \"problem\"; code: SseProblemCode; detail: string }\n\nconst truncate = (s: string, n: number): string => (s.length > n ? `${s.slice(0, n)}…` : s)\n\nexport async function* sseItems(source: AsyncIterable<Uint8Array>): AsyncGenerator<SseItem> {\n const decoder = new TextDecoder(\"utf-8\")\n let buffer = \"\"\n let sawFirstChars = false\n let dataLines: string[] = []\n let eventName = \"\"\n let lastId: string | undefined\n\n function handleLine(line: string): SseItem | null {\n if (line === \"\") {\n // Dispatch. Per spec, an empty data buffer dispatches nothing.\n const data = dataLines.join(\"\\n\")\n const hadData = dataLines.length > 0\n dataLines = []\n const name = eventName\n eventName = \"\"\n if (!hadData || data === \"\") return null\n const item: SseItem = { kind: \"event\", data }\n if (name !== \"\") item.event = name\n if (lastId !== undefined) item.id = lastId\n return item\n }\n if (line.startsWith(\":\")) return { kind: \"comment\", text: line.slice(1) }\n\n const colon = line.indexOf(\":\")\n const field = colon === -1 ? line : line.slice(0, colon)\n let value = colon === -1 ? \"\" : line.slice(colon + 1)\n if (value.startsWith(\" \")) value = value.slice(1)\n\n switch (field) {\n case \"data\":\n dataLines.push(value)\n return null\n case \"event\":\n eventName = value\n return null\n case \"id\":\n if (!value.includes(\"\\0\")) lastId = value\n return null\n case \"retry\":\n return null\n default: {\n // Unknown field: ignored per spec — but a JSON-looking line is almost\n // certainly a payload missing its \"data:\" prefix, silently lost.\n const trimmed = line.trimStart()\n if (trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\")) {\n return {\n kind: \"problem\",\n code: \"json-line-without-data-prefix\",\n detail: `line '${truncate(trimmed, 60)}' looks like a JSON payload but lacks the 'data:' field prefix, so SSE clients silently drop it`,\n }\n }\n return null\n }\n }\n }\n\n function* drainLines(eof: boolean): Generator<SseItem> {\n for (;;) {\n const nl = buffer.indexOf(\"\\n\")\n const cr = buffer.indexOf(\"\\r\")\n let end: number\n let next: number\n if (cr !== -1 && (nl === -1 || cr < nl)) {\n // Hold back a trailing CR mid-stream: it may be a CRLF split across\n // chunk boundaries.\n if (cr === buffer.length - 1 && !eof) return\n end = cr\n next = buffer[cr + 1] === \"\\n\" ? cr + 2 : cr + 1\n } else if (nl !== -1) {\n end = nl\n next = nl + 1\n } else {\n return\n }\n const line = buffer.slice(0, end)\n buffer = buffer.slice(next)\n const item = handleLine(line)\n if (item !== null) yield item\n }\n }\n\n for await (const chunk of source) {\n buffer += decoder.decode(chunk, { stream: true })\n if (!sawFirstChars && buffer.length > 0) {\n if (buffer.startsWith(\"\")) buffer = buffer.slice(1)\n sawFirstChars = true\n }\n yield* drainLines(false)\n }\n buffer += decoder.decode()\n yield* drainLines(true)\n\n if (buffer !== \"\") {\n yield {\n kind: \"problem\",\n code: \"truncated-frame\",\n detail: `stream ended mid-line: '${truncate(buffer, 60)}' (no trailing newline; the frame was never dispatched)`,\n }\n } else if (dataLines.length > 0) {\n yield {\n kind: \"problem\",\n code: \"truncated-frame\",\n detail: \"stream ended with a pending frame that was never terminated by a blank line\",\n }\n }\n}\n","// ag-ui-validate/transport: SSE + NDJSON clients over the pure core.\n// This is the I/O layer — fetch, streams, and clocks live here, never in the\n// core. Uses only web-platform APIs (fetch, TextDecoder, AbortController) so\n// it stays isomorphic: Node 20+, browsers, Deno, Workers.\n\nimport { createValidator } from \"../index.js\"\nimport type { Diagnostic, Report, ValidationLayer, ValidatorOptions } from \"../index.js\"\nimport { ndjsonLines } from \"./ndjson.js\"\nimport { sseItems } from \"./sse.js\"\n\nexport { ndjsonLines } from \"./ndjson.js\"\nexport { sseItems } from \"./sse.js\"\nexport type { SseItem, SseProblemCode } from \"./sse.js\"\n\nconst DEFAULT_KEEPALIVE_MS = 30_000\n\nexport interface TransportRequestInit {\n method: string\n headers: Record<string, string>\n body?: string\n signal?: AbortSignal\n}\n\nexport interface TransportResponseLike {\n status: number\n headers: { get(name: string): string | null }\n body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array> | null\n}\n\nexport type FetchLike = (url: string, init: TransportRequestInit) => Promise<TransportResponseLike>\n\n/** Operational failure (unreachable endpoint, non-2xx, no body) — distinct\n * from conformance findings, which are diagnostics. */\nexport class TransportError extends Error {\n status?: number\n constructor(message: string, status?: number) {\n super(message)\n this.name = \"TransportError\"\n if (status !== undefined) this.status = status\n }\n}\n\nexport interface TransportOptions {\n /** Options passed through to createValidator (\"transport\" layer is added). */\n validator?: ValidatorOptions\n /** Extra request headers (validateEndpoint only). */\n headers?: Record<string, string>\n /** RunAgentInput to POST; defaults to a minimal valid input with one user message. */\n input?: unknown\n method?: \"POST\" | \"GET\"\n /** AGUI506 window; default 30 000 ms. */\n keepaliveWindowMs?: number\n /** Abort the request after this long (validateEndpoint only). */\n timeoutMs?: number\n /** Injectable fetch (tests, custom stacks). Default: globalThis.fetch. */\n fetchImpl?: FetchLike\n /** Injectable clock for keepalive/buffering measurement. Default: Date.now. */\n now?: () => number\n /**\n * The body is a recording (file, stdin) rather than a live connection.\n * Timing-based rules (AGUI506, AGUI507) and mid-stream disconnect detection\n * (AGUI508) are meaningless for recordings; they are reported as skipped\n * with a reason instead of risking false positives, and read failures become\n * TransportErrors (tool failure) rather than AGUI508 findings.\n */\n recorded?: boolean\n signal?: AbortSignal\n /** Called after each fed event with the diagnostics it produced. */\n onEvent?: (raw: string, diagnostics: Diagnostic[]) => void\n /** Called for every diagnostic as soon as it is detected. */\n onDiagnostic?: (diagnostic: Diagnostic) => void\n}\n\nexport interface TransportResult {\n report: Report\n /** HTTP status; null when validating a bare body/recording. */\n status: number | null\n contentType: string | null\n /** Events fed to the validator (SSE frames / NDJSON lines). */\n eventCount: number\n /** Present when the connection failed mid-stream (see AGUI508). */\n transportError?: string\n}\n\nfunction randomId(prefix: string): string {\n const c = globalThis.crypto\n const suffix =\n c !== undefined && \"randomUUID\" in c\n ? c.randomUUID().slice(0, 8)\n : Math.random().toString(36).slice(2, 10)\n return `${prefix}_${suffix}`\n}\n\n/** The minimal valid RunAgentInput POSTed when none is supplied. */\nexport function defaultRunAgentInput(): Record<string, unknown> {\n return {\n threadId: randomId(\"thread\"),\n runId: randomId(\"run\"),\n state: {},\n messages: [{ id: randomId(\"msg\"), role: \"user\", content: \"Hello! Please respond briefly.\" }],\n tools: [],\n context: [],\n forwardedProps: {},\n }\n}\n\nasync function* iterateBody(\n body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>,\n): AsyncGenerator<Uint8Array> {\n if (Symbol.asyncIterator in body) {\n yield* body as AsyncIterable<Uint8Array>\n return\n }\n const reader = (body as ReadableStream<Uint8Array>).getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) return\n if (value !== undefined) yield value\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/** Buffers up to the first line to guess SSE vs NDJSON, then replays. */\nasync function sniffFormat(\n source: AsyncGenerator<Uint8Array>,\n): Promise<{ format: \"sse\" | \"ndjson\"; replay: AsyncGenerator<Uint8Array> }> {\n const held: Uint8Array[] = []\n const decoder = new TextDecoder()\n let text = \"\"\n while (!text.includes(\"\\n\") && text.length < 4096) {\n const { done, value } = await source.next()\n if (done) break\n held.push(value)\n text += decoder.decode(value, { stream: true })\n }\n const firstLine = (text.split(\"\\n\")[0] ?? \"\").trim()\n const format = firstLine.startsWith(\"{\") || firstLine.startsWith(\"[\") ? \"ndjson\" : \"sse\"\n async function* replay(): AsyncGenerator<Uint8Array> {\n yield* held\n for (;;) {\n const { done, value } = await source.next()\n if (done) return\n yield value\n }\n }\n return { format, replay: replay() }\n}\n\nexport async function validateBody(\n body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>,\n contentType: string | null,\n opts: TransportOptions = {},\n): Promise<TransportResult> {\n const userLayers = opts.validator?.layers ?? []\n const layers: ValidationLayer[] = [...new Set<ValidationLayer>([...userLayers, \"core\", \"transport\"])]\n const v = createValidator({ ...(opts.validator ?? {}), layers })\n const now = opts.now ?? Date.now\n const keepaliveWindow = opts.keepaliveWindowMs ?? DEFAULT_KEEPALIVE_MS\n const recorded = opts.recorded === true\n\n const emitTransport: typeof v.emitExternal = (rule, params, extra) => {\n const d = v.emitExternal(rule, params, extra)\n if (d !== null) opts.onDiagnostic?.(d)\n return d\n }\n\n // Byte-level tap: chunk arrival times drive AGUI506 (silent gaps) and\n // AGUI507 (everything in one chunk = buffered, not flushed).\n let chunkCount = 0\n let maxGapMs = 0\n let lastArrival: number | null = null\n async function* tapped(): AsyncGenerator<Uint8Array> {\n for await (const chunk of iterateBody(body)) {\n const t = now()\n if (lastArrival !== null) maxGapMs = Math.max(maxGapMs, t - lastArrival)\n lastArrival = t\n chunkCount += 1\n yield chunk\n }\n }\n\n const mime = contentType === null ? null : (contentType.split(\";\")[0] ?? \"\").trim().toLowerCase()\n if (contentType === null) {\n v.markSkipped(\"AGUI505\", \"no Content-Type header is available for this input\")\n } else if (mime !== \"text/event-stream\" && mime !== \"application/x-ndjson\") {\n emitTransport(\"AGUI505\", { contentType: mime === \"\" ? \"(none)\" : mime })\n }\n if (recorded) {\n v.markSkipped(\"AGUI506\", \"keepalive timing is not meaningful for recorded input\")\n v.markSkipped(\"AGUI507\", \"chunk arrival timing is not meaningful for recorded input\")\n v.markSkipped(\n \"AGUI508\",\n \"abnormal disconnects cannot be distinguished from end-of-capture in recorded input\",\n )\n }\n\n let format: \"sse\" | \"ndjson\"\n let stream: AsyncGenerator<Uint8Array> = tapped()\n if (mime === \"text/event-stream\") format = \"sse\"\n else if (mime === \"application/x-ndjson\") format = \"ndjson\"\n else ({ format, replay: stream } = await sniffFormat(stream))\n if (format === \"ndjson\") {\n v.markSkipped(\"AGUI501\", \"the stream is NDJSON; there is no SSE framing to check\")\n }\n\n let eventCount = 0\n let runOpen = false\n let openRunId: string | null = null\n const feedRaw = (raw: string): void => {\n eventCount += 1\n const diags = v.feed(raw)\n try {\n const parsed = JSON.parse(raw) as { type?: unknown; runId?: unknown }\n if (parsed?.type === \"RUN_STARTED\") {\n runOpen = true\n openRunId = typeof parsed.runId === \"string\" ? parsed.runId : null\n } else if (parsed?.type === \"RUN_FINISHED\" || parsed?.type === \"RUN_ERROR\") {\n runOpen = false\n }\n } catch {\n // unparseable payloads are already AGUI502 diagnostics from the core\n }\n opts.onEvent?.(raw, diags)\n if (opts.onDiagnostic !== undefined) for (const d of diags) opts.onDiagnostic(d)\n }\n\n let transportError: string | undefined\n try {\n if (format === \"sse\") {\n for await (const item of sseItems(stream)) {\n if (item.kind === \"event\") feedRaw(item.data)\n else if (item.kind === \"problem\") emitTransport(\"AGUI501\", { detail: item.detail })\n }\n } else {\n for await (const line of ndjsonLines(stream)) feedRaw(line)\n }\n } catch (e) {\n if (recorded) {\n // A read failure on a recording is a broken input, not an observation\n // about the agent's transport behavior.\n throw e instanceof TransportError\n ? e\n : new TransportError(\n `failed to read recorded input: ${e instanceof Error ? e.message : String(e)}`,\n )\n }\n transportError = e instanceof Error ? e.message : String(e)\n if (runOpen) {\n // The connection died mid-run. The core's finalize will additionally\n // report the run (and any open streams) as unterminated — both are true\n // statements about the observed stream.\n emitTransport(\"AGUI508\", { runId: openRunId ?? \"(unknown)\" })\n }\n }\n\n if (!recorded) {\n if (lastArrival !== null) maxGapMs = Math.max(maxGapMs, now() - lastArrival)\n if (maxGapMs > keepaliveWindow) {\n emitTransport(\"AGUI506\", { seconds: Math.round(maxGapMs / 1000) })\n }\n if (chunkCount === 1 && eventCount >= 3) {\n emitTransport(\"AGUI507\", { detail: `entire body (${eventCount} events) arrived in a single chunk` })\n }\n }\n\n const finalDiags = v.finalize()\n if (opts.onDiagnostic !== undefined) for (const d of finalDiags) opts.onDiagnostic(d)\n\n const result: TransportResult = {\n report: v.report(),\n status: null,\n contentType,\n eventCount,\n }\n if (transportError !== undefined) result.transportError = transportError\n return result\n}\n\nexport async function validateEndpoint(\n url: string,\n opts: TransportOptions = {},\n): Promise<TransportResult> {\n const fetchImpl = opts.fetchImpl ?? (globalThis.fetch as FetchLike | undefined)\n if (fetchImpl === undefined) {\n throw new TransportError(\"no fetch implementation available; pass fetchImpl\")\n }\n\n const method = opts.method ?? \"POST\"\n const headers: Record<string, string> = {\n accept: \"text/event-stream, application/x-ndjson\",\n ...(method === \"POST\" ? { \"content-type\": \"application/json\" } : {}),\n ...(opts.headers ?? {}),\n }\n\n const controller = new AbortController()\n const timers: ReturnType<typeof setTimeout>[] = []\n if (opts.timeoutMs !== undefined) {\n timers.push(\n setTimeout(\n () => controller.abort(new TransportError(`timed out after ${opts.timeoutMs}ms`)),\n opts.timeoutMs,\n ),\n )\n }\n opts.signal?.addEventListener(\"abort\", () => controller.abort(opts.signal?.reason), { once: true })\n\n try {\n let res: TransportResponseLike\n try {\n const init: TransportRequestInit = { method, headers, signal: controller.signal }\n if (method === \"POST\") init.body = JSON.stringify(opts.input ?? defaultRunAgentInput())\n res = await fetchImpl(url, init)\n } catch (e) {\n throw e instanceof TransportError\n ? e\n : new TransportError(`request failed: ${e instanceof Error ? e.message : String(e)}`)\n }\n if (res.status < 200 || res.status >= 300) {\n throw new TransportError(`endpoint responded with HTTP ${res.status}`, res.status)\n }\n if (res.body === null) throw new TransportError(\"response has no body\", res.status)\n const contentType = res.headers.get(\"content-type\") ?? \"\"\n const result = await validateBody(res.body, contentType, opts)\n return { ...result, status: res.status }\n } finally {\n for (const timer of timers) clearTimeout(timer)\n }\n}\n"],"mappings":"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"}