@ai-matrx/agents 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 — 2026-08-24
4
+
5
+ - Added a transport-independent incremental NDJSON framer for fragmented text
6
+ and bytes, including split UTF-8 and unterminated trailing input.
7
+ - Added exact valid-envelope observation, physical-line malformed diagnostics,
8
+ cancellation proof, bounded configurable read-ahead, and ordered partial
9
+ transport failure behavior.
10
+ - Preserved top-level `stream_seq` through wire normalization so projector
11
+ replay suppression receives the server's transport sequence.
12
+ - Aligned tool status semantics with execution: server `tool_started` remains
13
+ streaming; only `tool_delegated` suspends for client work.
14
+ - Bounded workflow render-block assembly by open sets, frames, and UTF-8 bytes;
15
+ malformed or invalid completions now produce typed diagnostics instead of
16
+ disappearing silently.
17
+ - Expanded TypeScript checking to include every projection source and test.
18
+
19
+ ## 0.2.1 — 2026-08-24
20
+
21
+ - Published the documented workflow projection entry point that workspace
22
+ consumers already used but the immutable 0.2.0 registry artifact omitted.
23
+ - Added the portable workflow `node_stream` projection inlet with strict
24
+ answer/reasoning separation, bounded render-block assembly, replay
25
+ suppression, and shadowed-text state.
26
+ - Added every request and workflow projection test to the normal package test
27
+ gate; the previous Vitest include list silently ran only stream and
28
+ presentation tests.
29
+
3
30
  ## 0.2.0 — 2026-08-22
4
31
 
5
32
  - Added a framework-free request event projector for identity, status, answer/reasoning, phases, operations, tools, render blocks, Content IR metadata, completion, and errors.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @ai-matrx/agents
2
2
 
3
- Portable client-side primitives for AI Matrx agent applications. Version 1 is
4
- intentionally narrow: it standardizes the stream wire boundary and the safe
5
- Creator-facing result boundary without importing React, Redux, Next.js, or any
3
+ Portable client-side primitives for AI Matrx agent applications. The package
4
+ standardizes the stream wire, pure request/workflow projection, and safe
5
+ Creator-facing result boundaries without importing React, Redux, Next.js, or
6
6
  application code.
7
7
 
8
8
  ## Install
@@ -25,8 +25,30 @@ for await (const envelope of readMatrxNdjsonStream(response.body!, {
25
25
  ```
26
26
 
27
27
  The reader preserves split UTF-8, drains the network independently of consumer
28
- work, supports cancellation, normalizes full and compact Matrx envelopes, and
29
- reports malformed or unknown input through explicit callbacks.
28
+ work up to the configurable `maxReadAhead` bound, supports cancellation,
29
+ normalizes full and compact Matrx envelopes, and reports malformed or unknown
30
+ input through explicit callbacks. `onValidEnvelope` observes the exact parsed
31
+ wire value before normalization without consuming it. Full-envelope
32
+ `stream_seq` is preserved for projector replay suppression.
33
+
34
+ ## Frame non-ReadableStream transports
35
+
36
+ ```ts
37
+ import { createMatrxNdjsonFramer } from "@ai-matrx/agents/stream/ndjson";
38
+
39
+ const framer = createMatrxNdjsonFramer({
40
+ onMalformedLine: reportProtocolDamage,
41
+ onValidEnvelope: persistRawEnvelope,
42
+ });
43
+
44
+ handleEvents(framer.pushBytes(extensionMessageBytes));
45
+ handleEvents(framer.pushText(desktopBridgeFragment));
46
+ handleEvents(framer.finish());
47
+ ```
48
+
49
+ The same incremental framer works with browser-extension messages, desktop
50
+ bridges, WebSockets, and tests. `finish()` flushes split UTF-8 and diagnoses an
51
+ invalid unterminated final line with `atCompletion: true`.
30
52
 
31
53
  ## Present a settled result safely
32
54
 
@@ -40,6 +62,25 @@ The projection removes provider-private reasoning blocks and signature material
40
62
  without mutating the execution value. It is only for display, JSON views, and
41
63
  exports. Never persist the projected result or use it to continue an agent run.
42
64
 
65
+ ## Project workflow live output
66
+
67
+ ```ts
68
+ import {
69
+ createWorkflowNodeProjection,
70
+ projectWorkflowNodeEvent,
71
+ } from "@ai-matrx/agents/projection/workflow";
72
+
73
+ let node = createWorkflowNodeProjection({ runId, nodeId });
74
+ node = projectWorkflowNodeEvent(node, nodeStreamFrame);
75
+ ```
76
+
77
+ This is the single workflow presentation inlet. It keeps answer and private
78
+ reasoning separate, rejects replayed frames, assembles bounded server
79
+ `render_block` snapshots, and prevents shadowed text from being interpreted as
80
+ a second copy of the same content. Configure `maxOpenFrameSets`,
81
+ `maxFramesPerBlock`, and `maxBytesPerBlock` when creating the projection;
82
+ rejected or malformed blocks are observable through `lastRenderBlockIssue`.
83
+
43
84
  ## Runtime support
44
85
 
45
86
  The package is framework-free ESM targeting modern browsers, browser-based
package/dist/index.cjs CHANGED
@@ -1,13 +1,19 @@
1
1
  'use strict';
2
2
 
3
3
  // stream/ndjson.ts
4
+ var DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;
4
5
  function isRecord(value) {
5
6
  return typeof value === "object" && value !== null && !Array.isArray(value);
6
7
  }
7
8
  function normalizeMatrxStreamEnvelope(value) {
8
9
  if (!isRecord(value)) return null;
9
10
  if (typeof value.event === "string") {
10
- return { event: value.event, data: value.data };
11
+ const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
12
+ return {
13
+ event: value.event,
14
+ data: value.data,
15
+ ...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
16
+ };
11
17
  }
12
18
  if (value.e === "c" && typeof value.t === "string") {
13
19
  return { event: "chunk", data: { text: value.t } };
@@ -17,74 +23,179 @@ function normalizeMatrxStreamEnvelope(value) {
17
23
  }
18
24
  return null;
19
25
  }
20
- async function* readMatrxNdjsonStream(body, options = {}) {
21
- const queue = [];
22
- let wakeConsumer = null;
23
- let readerFinished = false;
24
- const enqueue = (item) => {
25
- queue.push(item);
26
- const wake = wakeConsumer;
27
- wakeConsumer = null;
28
- wake?.();
29
- };
30
- const reader = body.getReader();
26
+ function createMatrxNdjsonFramer(options = {}) {
31
27
  const decoder = new TextDecoder();
32
- const parseLine = (line) => {
28
+ let buffer = "";
29
+ let lineNumber = 0;
30
+ let finished = false;
31
+ const assertOpen = () => {
32
+ if (finished) throw new Error("Matrx NDJSON framer is already finished");
33
+ };
34
+ const parseLine = (line, atCompletion) => {
35
+ const currentLineNumber = ++lineNumber;
33
36
  const trimmed = line.trim();
34
- if (!trimmed) return;
37
+ if (!trimmed) return null;
35
38
  let parsed;
36
39
  try {
37
40
  parsed = JSON.parse(trimmed);
38
41
  } catch (error) {
39
- options.onMalformedLine?.({ line: trimmed, error });
40
- return;
42
+ options.onMalformedLine?.({
43
+ line: trimmed,
44
+ error,
45
+ lineNumber: currentLineNumber,
46
+ atCompletion
47
+ });
48
+ return null;
41
49
  }
42
50
  const envelope = normalizeMatrxStreamEnvelope(parsed);
43
- if (envelope) {
44
- enqueue({ kind: "event", value: envelope });
45
- } else {
51
+ if (!envelope) {
46
52
  options.onUnknownEnvelope?.(parsed);
53
+ return null;
47
54
  }
55
+ options.onValidEnvelope?.({
56
+ raw: parsed,
57
+ envelope,
58
+ line: trimmed,
59
+ lineNumber: currentLineNumber,
60
+ atCompletion
61
+ });
62
+ return envelope;
48
63
  };
64
+ const pushDecodedText = (fragment) => {
65
+ buffer += fragment;
66
+ const lines = buffer.split("\n");
67
+ buffer = lines.pop() ?? "";
68
+ const envelopes = [];
69
+ for (const line of lines) {
70
+ const envelope = parseLine(line, false);
71
+ if (envelope) envelopes.push(envelope);
72
+ }
73
+ return envelopes;
74
+ };
75
+ return {
76
+ pushText(fragment) {
77
+ assertOpen();
78
+ return pushDecodedText(decoder.decode() + fragment);
79
+ },
80
+ pushBytes(fragment) {
81
+ assertOpen();
82
+ return pushDecodedText(decoder.decode(fragment, { stream: true }));
83
+ },
84
+ finish() {
85
+ assertOpen();
86
+ finished = true;
87
+ const envelopes = pushDecodedText(decoder.decode());
88
+ if (buffer.length > 0) {
89
+ const envelope = parseLine(buffer, true);
90
+ buffer = "";
91
+ if (envelope) envelopes.push(envelope);
92
+ }
93
+ return envelopes;
94
+ }
95
+ };
96
+ }
97
+ function readAheadLimit(value) {
98
+ const limit = value ?? DEFAULT_MATRX_NDJSON_READ_AHEAD;
99
+ if (!Number.isSafeInteger(limit) || limit < 1) {
100
+ throw new RangeError("maxReadAhead must be a positive safe integer");
101
+ }
102
+ return limit;
103
+ }
104
+ async function* readMatrxNdjsonStream(body, options = {}) {
105
+ const maxReadAhead = readAheadLimit(options.maxReadAhead);
106
+ const queue = [];
107
+ let queuedEventCount = 0;
108
+ let wakeConsumer = null;
109
+ let wakeProducer = null;
110
+ let readerFinished = false;
111
+ let consumerClosed = false;
112
+ const wakeWaitingConsumer = () => {
113
+ const wake = wakeConsumer;
114
+ wakeConsumer = null;
115
+ wake?.();
116
+ };
117
+ const wakeWaitingProducer = () => {
118
+ const wake = wakeProducer;
119
+ wakeProducer = null;
120
+ wake?.();
121
+ };
122
+ const enqueueTerminal = (item) => {
123
+ if (consumerClosed) return;
124
+ queue.push(item);
125
+ wakeWaitingConsumer();
126
+ };
127
+ const enqueueEvent = async (value) => {
128
+ while (queuedEventCount >= maxReadAhead && !consumerClosed && !options.signal?.aborted) {
129
+ await new Promise((resolve) => {
130
+ wakeProducer = resolve;
131
+ });
132
+ }
133
+ if (consumerClosed || options.signal?.aborted) return false;
134
+ queue.push({ kind: "event", value });
135
+ queuedEventCount += 1;
136
+ wakeWaitingConsumer();
137
+ return true;
138
+ };
139
+ const waitForReadCapacity = async () => {
140
+ while (queuedEventCount >= maxReadAhead && !consumerClosed && !options.signal?.aborted) {
141
+ await new Promise((resolve) => {
142
+ wakeProducer = resolve;
143
+ });
144
+ }
145
+ return !consumerClosed && !options.signal?.aborted;
146
+ };
147
+ const reader = body.getReader();
148
+ const framer = createMatrxNdjsonFramer(options);
49
149
  const onAbort = () => {
150
+ wakeWaitingProducer();
151
+ wakeWaitingConsumer();
50
152
  void reader.cancel(options.signal?.reason).catch(() => void 0);
51
153
  };
52
154
  options.signal?.addEventListener("abort", onAbort, { once: true });
155
+ if (options.signal?.aborted) onAbort();
53
156
  const readerPromise = (async () => {
54
- let buffer = "";
55
157
  try {
56
- while (!options.signal?.aborted) {
158
+ while (!options.signal?.aborted && !consumerClosed) {
159
+ if (!await waitForReadCapacity()) return;
57
160
  const { value, done } = await reader.read();
58
161
  if (done) break;
59
- buffer += decoder.decode(value, { stream: true });
60
- const lines = buffer.split("\n");
61
- buffer = lines.pop() ?? "";
62
- for (const line of lines) parseLine(line);
162
+ for (const envelope of framer.pushBytes(value)) {
163
+ if (!await enqueueEvent(envelope)) return;
164
+ }
165
+ }
166
+ if (!options.signal?.aborted && !consumerClosed) {
167
+ for (const envelope of framer.finish()) {
168
+ if (!await enqueueEvent(envelope)) return;
169
+ }
63
170
  }
64
- buffer += decoder.decode();
65
- if (!options.signal?.aborted && buffer.trim()) parseLine(buffer);
66
171
  } catch (error) {
67
- const aborted = options.signal?.aborted || error instanceof Error && error.name === "AbortError";
68
- if (!aborted) enqueue({ kind: "error", error });
172
+ const aborted = options.signal?.aborted || consumerClosed || error instanceof Error && error.name === "AbortError";
173
+ if (!aborted) enqueueTerminal({ kind: "error", error });
69
174
  } finally {
70
175
  readerFinished = true;
71
176
  reader.releaseLock();
72
- enqueue({ kind: "done" });
177
+ enqueueTerminal({ kind: "done" });
73
178
  }
74
179
  })();
75
180
  try {
76
181
  while (true) {
77
182
  if (queue.length === 0) {
183
+ if (options.signal?.aborted || readerFinished) return;
78
184
  await new Promise((resolve) => {
79
185
  wakeConsumer = resolve;
80
186
  });
81
187
  }
82
188
  const item = queue.shift();
189
+ if (item?.kind === "event") queuedEventCount -= 1;
190
+ wakeWaitingProducer();
83
191
  if (!item || item.kind === "done") return;
84
192
  if (item.kind === "error") throw item.error;
85
193
  yield item.value;
86
194
  }
87
195
  } finally {
196
+ consumerClosed = true;
197
+ wakeWaitingProducer();
198
+ wakeWaitingConsumer();
88
199
  options.signal?.removeEventListener("abort", onAbort);
89
200
  if (!readerFinished) {
90
201
  await reader.cancel().catch(() => void 0);
@@ -300,7 +411,7 @@ function projectAgentEvent(current, event) {
300
411
  const status = toolStatus(lifecycle);
301
412
  return {
302
413
  ...next,
303
- status: status === "started" || status === "delegated" ? "awaiting-tools" : status === "completed" || status === "error" ? "streaming" : next.status,
414
+ status: status === "delegated" ? "awaiting-tools" : status === "completed" || status === "error" ? "streaming" : next.status,
304
415
  tools: {
305
416
  ...current.tools,
306
417
  [callId]: {
@@ -356,11 +467,254 @@ function projectAgentEvents(initial, events) {
356
467
  return events.reduce(projectAgentEvent, initial);
357
468
  }
358
469
 
470
+ // projection/workflow.ts
471
+ var DEFAULT_WORKFLOW_PROJECTION_LIMITS = {
472
+ maxOpenFrameSets: 32,
473
+ maxFramesPerBlock: 256,
474
+ maxBytesPerBlock: 1048576
475
+ };
476
+ function positiveSafeInteger(name, value) {
477
+ if (!Number.isSafeInteger(value) || value < 1) {
478
+ throw new RangeError(`${name} must be a positive safe integer`);
479
+ }
480
+ return value;
481
+ }
482
+ function resolveLimits(limits) {
483
+ return {
484
+ maxOpenFrameSets: positiveSafeInteger(
485
+ "maxOpenFrameSets",
486
+ limits?.maxOpenFrameSets ?? DEFAULT_WORKFLOW_PROJECTION_LIMITS.maxOpenFrameSets
487
+ ),
488
+ maxFramesPerBlock: positiveSafeInteger(
489
+ "maxFramesPerBlock",
490
+ limits?.maxFramesPerBlock ?? DEFAULT_WORKFLOW_PROJECTION_LIMITS.maxFramesPerBlock
491
+ ),
492
+ maxBytesPerBlock: positiveSafeInteger(
493
+ "maxBytesPerBlock",
494
+ limits?.maxBytesPerBlock ?? DEFAULT_WORKFLOW_PROJECTION_LIMITS.maxBytesPerBlock
495
+ )
496
+ };
497
+ }
498
+ function createWorkflowNodeProjection(input) {
499
+ return {
500
+ runId: input.runId,
501
+ nodeId: input.nodeId,
502
+ answer: "",
503
+ reasoning: "",
504
+ blockShadowed: false,
505
+ renderBlocks: {},
506
+ renderBlockOrder: [],
507
+ openFrames: {},
508
+ limits: resolveLimits(input.limits),
509
+ lastRenderBlockIssue: null,
510
+ chunksReceived: 0,
511
+ charsStreamed: 0,
512
+ lastStreamingTs: null,
513
+ lastPhase: null,
514
+ lastTool: null,
515
+ lastWarning: null,
516
+ lastTransportSeq: 0
517
+ };
518
+ }
519
+ function asRecord2(value) {
520
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
521
+ }
522
+ function asString2(value) {
523
+ return typeof value === "string" ? value : null;
524
+ }
525
+ function asNumber2(value) {
526
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
527
+ }
528
+ function utf8ByteLength(value) {
529
+ return new TextEncoder().encode(value).byteLength;
530
+ }
531
+ function toRenderBlock(value) {
532
+ const data = asRecord2(value);
533
+ if (!data) return null;
534
+ const blockId = asString2(data.blockId);
535
+ const blockIndex = asNumber2(data.blockIndex);
536
+ const type = asString2(data.type);
537
+ if (blockId === null || blockIndex === null || type === null) return null;
538
+ return {
539
+ blockId,
540
+ blockIndex,
541
+ type,
542
+ status: data.status === "complete" || data.status === "error" ? data.status : "streaming",
543
+ content: asString2(data.content),
544
+ data: asRecord2(data.data),
545
+ metadata: asRecord2(data.metadata)
546
+ };
547
+ }
548
+ function withIssue(next, event, code, frameId, message, openFrames = next.openFrames) {
549
+ return {
550
+ ...next,
551
+ openFrames,
552
+ lastRenderBlockIssue: {
553
+ code,
554
+ frameId,
555
+ streamSeq: event.stream_seq,
556
+ message
557
+ }
558
+ };
559
+ }
560
+ function withoutFrame(frames, frameId) {
561
+ if (!Object.hasOwn(frames, frameId)) return frames;
562
+ const next = { ...frames };
563
+ delete next[frameId];
564
+ return next;
565
+ }
566
+ function projectRenderFrame(current, event, next) {
567
+ const frameId = event.frame_id;
568
+ const frameCount = event.frame_count ?? 1;
569
+ const frameIndex = event.frame_index ?? 0;
570
+ if (!frameId || !Number.isSafeInteger(frameCount) || !Number.isSafeInteger(frameIndex) || frameCount < 1 || frameIndex < 0 || frameIndex >= frameCount) {
571
+ return withIssue(
572
+ next,
573
+ event,
574
+ "invalid_frame_metadata",
575
+ frameId ?? null,
576
+ "Render block frame metadata is invalid"
577
+ );
578
+ }
579
+ if (frameCount > current.limits.maxFramesPerBlock) {
580
+ return withIssue(
581
+ next,
582
+ event,
583
+ "frame_limit_exceeded",
584
+ frameId,
585
+ `Render block declares ${frameCount} frames; limit is ${current.limits.maxFramesPerBlock}`,
586
+ withoutFrame(current.openFrames, frameId)
587
+ );
588
+ }
589
+ const prior = current.openFrames[frameId];
590
+ if (prior && prior.frameCount !== frameCount) {
591
+ return withIssue(
592
+ next,
593
+ event,
594
+ "frame_count_mismatch",
595
+ frameId,
596
+ `Render block frame count changed from ${prior.frameCount} to ${frameCount}`,
597
+ withoutFrame(current.openFrames, frameId)
598
+ );
599
+ }
600
+ const priorSlice = prior?.slices[frameIndex];
601
+ const byteLength = (prior?.byteLength ?? 0) - (priorSlice === void 0 ? 0 : utf8ByteLength(priorSlice)) + utf8ByteLength(event.delta);
602
+ if (byteLength > current.limits.maxBytesPerBlock) {
603
+ return withIssue(
604
+ next,
605
+ event,
606
+ "byte_limit_exceeded",
607
+ frameId,
608
+ `Render block exceeds ${current.limits.maxBytesPerBlock} UTF-8 bytes`,
609
+ withoutFrame(current.openFrames, frameId)
610
+ );
611
+ }
612
+ const set = {
613
+ frameCount,
614
+ slices: { ...prior?.slices ?? {}, [frameIndex]: event.delta },
615
+ byteLength
616
+ };
617
+ const openFrames = { ...current.openFrames };
618
+ let limitIssue = null;
619
+ if (!prior && Object.keys(openFrames).length >= current.limits.maxOpenFrameSets) {
620
+ const oldestFrameId = Object.keys(openFrames)[0];
621
+ if (oldestFrameId !== void 0) {
622
+ delete openFrames[oldestFrameId];
623
+ limitIssue = {
624
+ code: "open_frame_limit_exceeded",
625
+ frameId: oldestFrameId,
626
+ streamSeq: event.stream_seq,
627
+ message: `Evicted incomplete render block after reaching ${current.limits.maxOpenFrameSets} open frame sets`
628
+ };
629
+ }
630
+ }
631
+ openFrames[frameId] = set;
632
+ if (Object.keys(set.slices).length !== frameCount) {
633
+ return {
634
+ ...next,
635
+ openFrames,
636
+ lastRenderBlockIssue: limitIssue ?? current.lastRenderBlockIssue
637
+ };
638
+ }
639
+ delete openFrames[frameId];
640
+ const serialized = Array.from(
641
+ { length: frameCount },
642
+ (_, index) => set.slices[index] ?? ""
643
+ ).join("");
644
+ let parsed;
645
+ try {
646
+ parsed = JSON.parse(serialized);
647
+ } catch (error) {
648
+ const detail = error instanceof Error ? `: ${error.message}` : "";
649
+ return withIssue(
650
+ next,
651
+ event,
652
+ "malformed_json",
653
+ frameId,
654
+ `Completed render block is not valid JSON${detail}`,
655
+ openFrames
656
+ );
657
+ }
658
+ const block = toRenderBlock(parsed);
659
+ if (!block) {
660
+ return withIssue(
661
+ next,
662
+ event,
663
+ "invalid_render_block",
664
+ frameId,
665
+ "Completed render block is missing blockId, blockIndex, or type",
666
+ openFrames
667
+ );
668
+ }
669
+ return {
670
+ ...next,
671
+ openFrames,
672
+ lastRenderBlockIssue: limitIssue ?? current.lastRenderBlockIssue,
673
+ renderBlocks: { ...current.renderBlocks, [block.blockId]: block },
674
+ renderBlockOrder: Object.hasOwn(current.renderBlocks, block.blockId) ? current.renderBlockOrder : [...current.renderBlockOrder, block.blockId]
675
+ };
676
+ }
677
+ function projectWorkflowNodeEvent(current, event) {
678
+ if (event.run_id !== current.runId || (event.node_id ?? "") !== current.nodeId) {
679
+ return current;
680
+ }
681
+ if (event.stream_seq <= current.lastTransportSeq) return current;
682
+ const next = {
683
+ ...current,
684
+ chunksReceived: event.chunks_received,
685
+ charsStreamed: event.chars_streamed,
686
+ lastStreamingTs: event.ts,
687
+ lastTransportSeq: event.stream_seq,
688
+ blockShadowed: current.blockShadowed || Boolean(event.block_shadowed)
689
+ };
690
+ switch (event.kind) {
691
+ case "chunk":
692
+ return { ...next, answer: current.answer + event.delta };
693
+ case "reasoning":
694
+ return { ...next, reasoning: current.reasoning + event.delta };
695
+ case "phase":
696
+ return { ...next, lastPhase: event.delta };
697
+ case "tool":
698
+ return { ...next, lastTool: event.delta };
699
+ case "warning":
700
+ return { ...next, lastWarning: event.delta };
701
+ case "render_block":
702
+ return projectRenderFrame(current, event, next);
703
+ default:
704
+ return next;
705
+ }
706
+ }
707
+
708
+ exports.DEFAULT_MATRX_NDJSON_READ_AHEAD = DEFAULT_MATRX_NDJSON_READ_AHEAD;
709
+ exports.DEFAULT_WORKFLOW_PROJECTION_LIMITS = DEFAULT_WORKFLOW_PROJECTION_LIMITS;
359
710
  exports.createAgentRequestProjection = createAgentRequestProjection;
711
+ exports.createMatrxNdjsonFramer = createMatrxNdjsonFramer;
712
+ exports.createWorkflowNodeProjection = createWorkflowNodeProjection;
360
713
  exports.normalizeMatrxStreamEnvelope = normalizeMatrxStreamEnvelope;
361
714
  exports.projectAgentEvent = projectAgentEvent;
362
715
  exports.projectAgentEvents = projectAgentEvents;
363
716
  exports.projectAgentResultForDisplay = projectAgentResultForDisplay;
717
+ exports.projectWorkflowNodeEvent = projectWorkflowNodeEvent;
364
718
  exports.readMatrxNdjsonStream = readMatrxNdjsonStream;
365
719
  //# sourceMappingURL=index.cjs.map
366
720
  //# sourceMappingURL=index.cjs.map