@ai-matrx/agents 0.2.1 → 0.5.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/dist/index.js CHANGED
@@ -1,11 +1,17 @@
1
1
  // stream/ndjson.ts
2
+ var DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;
2
3
  function isRecord(value) {
3
4
  return typeof value === "object" && value !== null && !Array.isArray(value);
4
5
  }
5
6
  function normalizeMatrxStreamEnvelope(value) {
6
7
  if (!isRecord(value)) return null;
7
8
  if (typeof value.event === "string") {
8
- return { event: value.event, data: value.data };
9
+ const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
10
+ return {
11
+ event: value.event,
12
+ data: value.data,
13
+ ...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
14
+ };
9
15
  }
10
16
  if (value.e === "c" && typeof value.t === "string") {
11
17
  return { event: "chunk", data: { text: value.t } };
@@ -15,74 +21,179 @@ function normalizeMatrxStreamEnvelope(value) {
15
21
  }
16
22
  return null;
17
23
  }
18
- async function* readMatrxNdjsonStream(body, options = {}) {
19
- const queue = [];
20
- let wakeConsumer = null;
21
- let readerFinished = false;
22
- const enqueue = (item) => {
23
- queue.push(item);
24
- const wake = wakeConsumer;
25
- wakeConsumer = null;
26
- wake?.();
27
- };
28
- const reader = body.getReader();
24
+ function createMatrxNdjsonFramer(options = {}) {
29
25
  const decoder = new TextDecoder();
30
- const parseLine = (line) => {
26
+ let buffer = "";
27
+ let lineNumber = 0;
28
+ let finished = false;
29
+ const assertOpen = () => {
30
+ if (finished) throw new Error("Matrx NDJSON framer is already finished");
31
+ };
32
+ const parseLine = (line, atCompletion) => {
33
+ const currentLineNumber = ++lineNumber;
31
34
  const trimmed = line.trim();
32
- if (!trimmed) return;
35
+ if (!trimmed) return null;
33
36
  let parsed;
34
37
  try {
35
38
  parsed = JSON.parse(trimmed);
36
39
  } catch (error) {
37
- options.onMalformedLine?.({ line: trimmed, error });
38
- return;
40
+ options.onMalformedLine?.({
41
+ line: trimmed,
42
+ error,
43
+ lineNumber: currentLineNumber,
44
+ atCompletion
45
+ });
46
+ return null;
39
47
  }
40
48
  const envelope = normalizeMatrxStreamEnvelope(parsed);
41
- if (envelope) {
42
- enqueue({ kind: "event", value: envelope });
43
- } else {
49
+ if (!envelope) {
44
50
  options.onUnknownEnvelope?.(parsed);
51
+ return null;
52
+ }
53
+ options.onValidEnvelope?.({
54
+ raw: parsed,
55
+ envelope,
56
+ line: trimmed,
57
+ lineNumber: currentLineNumber,
58
+ atCompletion
59
+ });
60
+ return envelope;
61
+ };
62
+ const pushDecodedText = (fragment) => {
63
+ buffer += fragment;
64
+ const lines = buffer.split("\n");
65
+ buffer = lines.pop() ?? "";
66
+ const envelopes = [];
67
+ for (const line of lines) {
68
+ const envelope = parseLine(line, false);
69
+ if (envelope) envelopes.push(envelope);
70
+ }
71
+ return envelopes;
72
+ };
73
+ return {
74
+ pushText(fragment) {
75
+ assertOpen();
76
+ return pushDecodedText(decoder.decode() + fragment);
77
+ },
78
+ pushBytes(fragment) {
79
+ assertOpen();
80
+ return pushDecodedText(decoder.decode(fragment, { stream: true }));
81
+ },
82
+ finish() {
83
+ assertOpen();
84
+ finished = true;
85
+ const envelopes = pushDecodedText(decoder.decode());
86
+ if (buffer.length > 0) {
87
+ const envelope = parseLine(buffer, true);
88
+ buffer = "";
89
+ if (envelope) envelopes.push(envelope);
90
+ }
91
+ return envelopes;
92
+ }
93
+ };
94
+ }
95
+ function readAheadLimit(value) {
96
+ const limit = value ?? DEFAULT_MATRX_NDJSON_READ_AHEAD;
97
+ if (!Number.isSafeInteger(limit) || limit < 1) {
98
+ throw new RangeError("maxReadAhead must be a positive safe integer");
99
+ }
100
+ return limit;
101
+ }
102
+ async function* readMatrxNdjsonStream(body, options = {}) {
103
+ const maxReadAhead = readAheadLimit(options.maxReadAhead);
104
+ const queue = [];
105
+ let queuedEventCount = 0;
106
+ let wakeConsumer = null;
107
+ let wakeProducer = null;
108
+ let readerFinished = false;
109
+ let consumerClosed = false;
110
+ const wakeWaitingConsumer = () => {
111
+ const wake = wakeConsumer;
112
+ wakeConsumer = null;
113
+ wake?.();
114
+ };
115
+ const wakeWaitingProducer = () => {
116
+ const wake = wakeProducer;
117
+ wakeProducer = null;
118
+ wake?.();
119
+ };
120
+ const enqueueTerminal = (item) => {
121
+ if (consumerClosed) return;
122
+ queue.push(item);
123
+ wakeWaitingConsumer();
124
+ };
125
+ const enqueueEvent = async (value) => {
126
+ while (queuedEventCount >= maxReadAhead && !consumerClosed && !options.signal?.aborted) {
127
+ await new Promise((resolve) => {
128
+ wakeProducer = resolve;
129
+ });
45
130
  }
131
+ if (consumerClosed || options.signal?.aborted) return false;
132
+ queue.push({ kind: "event", value });
133
+ queuedEventCount += 1;
134
+ wakeWaitingConsumer();
135
+ return true;
46
136
  };
137
+ const waitForReadCapacity = async () => {
138
+ while (queuedEventCount >= maxReadAhead && !consumerClosed && !options.signal?.aborted) {
139
+ await new Promise((resolve) => {
140
+ wakeProducer = resolve;
141
+ });
142
+ }
143
+ return !consumerClosed && !options.signal?.aborted;
144
+ };
145
+ const reader = body.getReader();
146
+ const framer = createMatrxNdjsonFramer(options);
47
147
  const onAbort = () => {
148
+ wakeWaitingProducer();
149
+ wakeWaitingConsumer();
48
150
  void reader.cancel(options.signal?.reason).catch(() => void 0);
49
151
  };
50
152
  options.signal?.addEventListener("abort", onAbort, { once: true });
153
+ if (options.signal?.aborted) onAbort();
51
154
  const readerPromise = (async () => {
52
- let buffer = "";
53
155
  try {
54
- while (!options.signal?.aborted) {
156
+ while (!options.signal?.aborted && !consumerClosed) {
157
+ if (!await waitForReadCapacity()) return;
55
158
  const { value, done } = await reader.read();
56
159
  if (done) break;
57
- buffer += decoder.decode(value, { stream: true });
58
- const lines = buffer.split("\n");
59
- buffer = lines.pop() ?? "";
60
- for (const line of lines) parseLine(line);
160
+ for (const envelope of framer.pushBytes(value)) {
161
+ if (!await enqueueEvent(envelope)) return;
162
+ }
163
+ }
164
+ if (!options.signal?.aborted && !consumerClosed) {
165
+ for (const envelope of framer.finish()) {
166
+ if (!await enqueueEvent(envelope)) return;
167
+ }
61
168
  }
62
- buffer += decoder.decode();
63
- if (!options.signal?.aborted && buffer.trim()) parseLine(buffer);
64
169
  } catch (error) {
65
- const aborted = options.signal?.aborted || error instanceof Error && error.name === "AbortError";
66
- if (!aborted) enqueue({ kind: "error", error });
170
+ const aborted = options.signal?.aborted || consumerClosed || error instanceof Error && error.name === "AbortError";
171
+ if (!aborted) enqueueTerminal({ kind: "error", error });
67
172
  } finally {
68
173
  readerFinished = true;
69
174
  reader.releaseLock();
70
- enqueue({ kind: "done" });
175
+ enqueueTerminal({ kind: "done" });
71
176
  }
72
177
  })();
73
178
  try {
74
179
  while (true) {
75
180
  if (queue.length === 0) {
181
+ if (options.signal?.aborted || readerFinished) return;
76
182
  await new Promise((resolve) => {
77
183
  wakeConsumer = resolve;
78
184
  });
79
185
  }
80
186
  const item = queue.shift();
187
+ if (item?.kind === "event") queuedEventCount -= 1;
188
+ wakeWaitingProducer();
81
189
  if (!item || item.kind === "done") return;
82
190
  if (item.kind === "error") throw item.error;
83
191
  yield item.value;
84
192
  }
85
193
  } finally {
194
+ consumerClosed = true;
195
+ wakeWaitingProducer();
196
+ wakeWaitingConsumer();
86
197
  options.signal?.removeEventListener("abort", onAbort);
87
198
  if (!readerFinished) {
88
199
  await reader.cancel().catch(() => void 0);
@@ -91,6 +202,68 @@ async function* readMatrxNdjsonStream(body, options = {}) {
91
202
  }
92
203
  }
93
204
 
205
+ // stream/sse.ts
206
+ var FRAME_SEPARATOR = /\r\n\r\n|\n\n|\r\r/;
207
+ var LINE_SEPARATOR = /\r\n|\n|\r/;
208
+ function parseMatrxSseFrame(frame) {
209
+ let event = "message";
210
+ let id = null;
211
+ const dataLines = [];
212
+ let sawData = false;
213
+ for (const line of frame.split(LINE_SEPARATOR)) {
214
+ if (line.startsWith(":")) continue;
215
+ if (line.startsWith("event:")) event = line.slice(6).trim();
216
+ else if (line.startsWith("data:")) {
217
+ sawData = true;
218
+ dataLines.push(line.slice(5).replace(/^ /, ""));
219
+ } else if (line.startsWith("id:")) id = line.slice(3).trim();
220
+ }
221
+ const seqCandidate = id !== null && id !== "" ? Number(id) : NaN;
222
+ const seq = Number.isSafeInteger(seqCandidate) && seqCandidate >= 0 ? seqCandidate : null;
223
+ return { event, id, seq, data: sawData ? dataLines.join("\n") : null };
224
+ }
225
+ function createMatrxSseFramer() {
226
+ let buffer = "";
227
+ return {
228
+ push(chunk) {
229
+ buffer += chunk;
230
+ const frames = [];
231
+ for (; ; ) {
232
+ const sep = FRAME_SEPARATOR.exec(buffer);
233
+ if (sep === null) break;
234
+ const frame = buffer.slice(0, sep.index);
235
+ buffer = buffer.slice(sep.index + sep[0].length);
236
+ frames.push(parseMatrxSseFrame(frame));
237
+ }
238
+ return frames;
239
+ },
240
+ flush() {
241
+ const rest = buffer;
242
+ buffer = "";
243
+ return { incomplete: rest.length > 0 ? rest : null };
244
+ }
245
+ };
246
+ }
247
+ async function* readMatrxSseStream(stream, options = {}) {
248
+ const reader = stream.getReader();
249
+ const decoder = new TextDecoder();
250
+ const framer = createMatrxSseFramer();
251
+ try {
252
+ for (; ; ) {
253
+ const { value, done } = await reader.read();
254
+ if (done) break;
255
+ const frames = framer.push(decoder.decode(value, { stream: true }));
256
+ for (const frame of frames) yield frame;
257
+ }
258
+ const tail = framer.push(decoder.decode());
259
+ for (const frame of tail) yield frame;
260
+ const { incomplete } = framer.flush();
261
+ if (incomplete !== null) options.onIncomplete?.(incomplete);
262
+ } finally {
263
+ reader.releaseLock();
264
+ }
265
+ }
266
+
94
267
  // presentation/result.ts
95
268
  var PRIVATE_REASONING_TYPES = /* @__PURE__ */ new Set([
96
269
  "thinking",
@@ -298,7 +471,7 @@ function projectAgentEvent(current, event) {
298
471
  const status = toolStatus(lifecycle);
299
472
  return {
300
473
  ...next,
301
- status: status === "started" || status === "delegated" ? "awaiting-tools" : status === "completed" || status === "error" ? "streaming" : next.status,
474
+ status: status === "delegated" ? "awaiting-tools" : status === "completed" || status === "error" ? "streaming" : next.status,
302
475
  tools: {
303
476
  ...current.tools,
304
477
  [callId]: {
@@ -355,7 +528,33 @@ function projectAgentEvents(initial, events) {
355
528
  }
356
529
 
357
530
  // projection/workflow.ts
358
- var MAX_OPEN_FRAME_SETS = 32;
531
+ var DEFAULT_WORKFLOW_PROJECTION_LIMITS = {
532
+ maxOpenFrameSets: 32,
533
+ maxFramesPerBlock: 256,
534
+ maxBytesPerBlock: 1048576
535
+ };
536
+ function positiveSafeInteger(name, value) {
537
+ if (!Number.isSafeInteger(value) || value < 1) {
538
+ throw new RangeError(`${name} must be a positive safe integer`);
539
+ }
540
+ return value;
541
+ }
542
+ function resolveLimits(limits) {
543
+ return {
544
+ maxOpenFrameSets: positiveSafeInteger(
545
+ "maxOpenFrameSets",
546
+ limits?.maxOpenFrameSets ?? DEFAULT_WORKFLOW_PROJECTION_LIMITS.maxOpenFrameSets
547
+ ),
548
+ maxFramesPerBlock: positiveSafeInteger(
549
+ "maxFramesPerBlock",
550
+ limits?.maxFramesPerBlock ?? DEFAULT_WORKFLOW_PROJECTION_LIMITS.maxFramesPerBlock
551
+ ),
552
+ maxBytesPerBlock: positiveSafeInteger(
553
+ "maxBytesPerBlock",
554
+ limits?.maxBytesPerBlock ?? DEFAULT_WORKFLOW_PROJECTION_LIMITS.maxBytesPerBlock
555
+ )
556
+ };
557
+ }
359
558
  function createWorkflowNodeProjection(input) {
360
559
  return {
361
560
  runId: input.runId,
@@ -366,6 +565,8 @@ function createWorkflowNodeProjection(input) {
366
565
  renderBlocks: {},
367
566
  renderBlockOrder: [],
368
567
  openFrames: {},
568
+ limits: resolveLimits(input.limits),
569
+ lastRenderBlockIssue: null,
369
570
  chunksReceived: 0,
370
571
  charsStreamed: 0,
371
572
  lastStreamingTs: null,
@@ -384,6 +585,9 @@ function asString2(value) {
384
585
  function asNumber2(value) {
385
586
  return typeof value === "number" && Number.isFinite(value) ? value : null;
386
587
  }
588
+ function utf8ByteLength(value) {
589
+ return new TextEncoder().encode(value).byteLength;
590
+ }
387
591
  function toRenderBlock(value) {
388
592
  const data = asRecord2(value);
389
593
  if (!data) return null;
@@ -401,44 +605,139 @@ function toRenderBlock(value) {
401
605
  metadata: asRecord2(data.metadata)
402
606
  };
403
607
  }
608
+ function withIssue(next, event, code, frameId, message, openFrames = next.openFrames) {
609
+ return {
610
+ ...next,
611
+ openFrames,
612
+ lastRenderBlockIssue: {
613
+ code,
614
+ frameId,
615
+ streamSeq: event.stream_seq,
616
+ message
617
+ }
618
+ };
619
+ }
620
+ function withoutFrame(frames, frameId) {
621
+ if (!Object.hasOwn(frames, frameId)) return frames;
622
+ const next = { ...frames };
623
+ delete next[frameId];
624
+ return next;
625
+ }
404
626
  function projectRenderFrame(current, event, next) {
405
627
  const frameId = event.frame_id;
406
628
  const frameCount = event.frame_count ?? 1;
407
629
  const frameIndex = event.frame_index ?? 0;
408
- if (!frameId || frameCount < 1 || frameIndex < 0 || frameIndex >= frameCount) {
409
- return next;
630
+ if (!frameId || !Number.isSafeInteger(frameCount) || !Number.isSafeInteger(frameIndex) || frameCount < 1 || frameIndex < 0 || frameIndex >= frameCount) {
631
+ return withIssue(
632
+ next,
633
+ event,
634
+ "invalid_frame_metadata",
635
+ frameId ?? null,
636
+ "Render block frame metadata is invalid"
637
+ );
638
+ }
639
+ if (frameCount > current.limits.maxFramesPerBlock) {
640
+ return withIssue(
641
+ next,
642
+ event,
643
+ "frame_limit_exceeded",
644
+ frameId,
645
+ `Render block declares ${frameCount} frames; limit is ${current.limits.maxFramesPerBlock}`,
646
+ withoutFrame(current.openFrames, frameId)
647
+ );
410
648
  }
411
649
  const prior = current.openFrames[frameId];
650
+ if (prior && prior.frameCount !== frameCount) {
651
+ return withIssue(
652
+ next,
653
+ event,
654
+ "frame_count_mismatch",
655
+ frameId,
656
+ `Render block frame count changed from ${prior.frameCount} to ${frameCount}`,
657
+ withoutFrame(current.openFrames, frameId)
658
+ );
659
+ }
660
+ const priorSlice = prior?.slices[frameIndex];
661
+ const byteLength = (prior?.byteLength ?? 0) - (priorSlice === void 0 ? 0 : utf8ByteLength(priorSlice)) + utf8ByteLength(event.delta);
662
+ if (byteLength > current.limits.maxBytesPerBlock) {
663
+ return withIssue(
664
+ next,
665
+ event,
666
+ "byte_limit_exceeded",
667
+ frameId,
668
+ `Render block exceeds ${current.limits.maxBytesPerBlock} UTF-8 bytes`,
669
+ withoutFrame(current.openFrames, frameId)
670
+ );
671
+ }
412
672
  const set = {
413
673
  frameCount,
414
- slices: { ...prior?.frameCount === frameCount ? prior.slices : {}, [frameIndex]: event.delta }
674
+ slices: { ...prior?.slices ?? {}, [frameIndex]: event.delta },
675
+ byteLength
415
676
  };
416
- const openFrames = { ...current.openFrames, [frameId]: set };
417
- const keys = Object.keys(openFrames);
418
- const oldestKey = keys[0];
419
- if (keys.length > MAX_OPEN_FRAME_SETS && oldestKey !== void 0) {
420
- delete openFrames[oldestKey];
677
+ const openFrames = { ...current.openFrames };
678
+ let limitIssue = null;
679
+ if (!prior && Object.keys(openFrames).length >= current.limits.maxOpenFrameSets) {
680
+ const oldestFrameId = Object.keys(openFrames)[0];
681
+ if (oldestFrameId !== void 0) {
682
+ delete openFrames[oldestFrameId];
683
+ limitIssue = {
684
+ code: "open_frame_limit_exceeded",
685
+ frameId: oldestFrameId,
686
+ streamSeq: event.stream_seq,
687
+ message: `Evicted incomplete render block after reaching ${current.limits.maxOpenFrameSets} open frame sets`
688
+ };
689
+ }
690
+ }
691
+ openFrames[frameId] = set;
692
+ if (Object.keys(set.slices).length !== frameCount) {
693
+ return {
694
+ ...next,
695
+ openFrames,
696
+ lastRenderBlockIssue: limitIssue ?? current.lastRenderBlockIssue
697
+ };
421
698
  }
422
- if (Object.keys(set.slices).length !== frameCount) return { ...next, openFrames };
423
699
  delete openFrames[frameId];
424
- let block = null;
700
+ const serialized = Array.from(
701
+ { length: frameCount },
702
+ (_, index) => set.slices[index] ?? ""
703
+ ).join("");
704
+ let parsed;
425
705
  try {
426
- block = toRenderBlock(JSON.parse(
427
- Array.from({ length: frameCount }, (_, index) => set.slices[index] ?? "").join("")
428
- ));
429
- } catch {
430
- return { ...next, openFrames };
706
+ parsed = JSON.parse(serialized);
707
+ } catch (error) {
708
+ const detail = error instanceof Error ? `: ${error.message}` : "";
709
+ return withIssue(
710
+ next,
711
+ event,
712
+ "malformed_json",
713
+ frameId,
714
+ `Completed render block is not valid JSON${detail}`,
715
+ openFrames
716
+ );
717
+ }
718
+ const block = toRenderBlock(parsed);
719
+ if (!block) {
720
+ return withIssue(
721
+ next,
722
+ event,
723
+ "invalid_render_block",
724
+ frameId,
725
+ "Completed render block is missing blockId, blockIndex, or type",
726
+ openFrames
727
+ );
431
728
  }
432
- if (!block) return { ...next, openFrames };
433
729
  return {
434
730
  ...next,
435
731
  openFrames,
732
+ lastRenderBlockIssue: limitIssue ?? current.lastRenderBlockIssue,
436
733
  renderBlocks: { ...current.renderBlocks, [block.blockId]: block },
437
734
  renderBlockOrder: Object.hasOwn(current.renderBlocks, block.blockId) ? current.renderBlockOrder : [...current.renderBlockOrder, block.blockId]
438
735
  };
439
736
  }
440
737
  function projectWorkflowNodeEvent(current, event) {
441
- if (event.run_id !== current.runId || (event.node_id ?? "") !== current.nodeId) return current;
738
+ if (event.run_id !== current.runId || (event.node_id ?? "") !== current.nodeId) {
739
+ return current;
740
+ }
442
741
  if (event.stream_seq <= current.lastTransportSeq) return current;
443
742
  const next = {
444
743
  ...current,
@@ -466,6 +765,460 @@ function projectWorkflowNodeEvent(current, event) {
466
765
  }
467
766
  }
468
767
 
469
- export { createAgentRequestProjection, createWorkflowNodeProjection, normalizeMatrxStreamEnvelope, projectAgentEvent, projectAgentEvents, projectAgentResultForDisplay, projectWorkflowNodeEvent, readMatrxNdjsonStream };
768
+ // matrx/transport.ts
769
+ var MatrxApiError = class extends Error {
770
+ name = "MatrxApiError";
771
+ /** HTTP status of the failed response. */
772
+ status;
773
+ /** Machine code from the server body (`code`, or `detail.code`), when present. */
774
+ code;
775
+ /** The parsed server error body, verbatim (undefined when unparsable). */
776
+ serverDetail;
777
+ /** The request path the failure came from (server-relative). */
778
+ path;
779
+ constructor(args) {
780
+ super(
781
+ args.message ?? extractMatrxErrorMessage(args.serverDetail) ?? `HTTP ${args.status}`
782
+ );
783
+ this.status = args.status;
784
+ this.path = args.path;
785
+ this.serverDetail = args.serverDetail;
786
+ this.code = extractMatrxErrorCode(args.serverDetail);
787
+ }
788
+ };
789
+ function isRecord3(value) {
790
+ return typeof value === "object" && value !== null && !Array.isArray(value);
791
+ }
792
+ function nonBlankString(value) {
793
+ return typeof value === "string" && value.trim() ? value : void 0;
794
+ }
795
+ function extractMatrxErrorMessage(serverDetail) {
796
+ if (!isRecord3(serverDetail)) return void 0;
797
+ const userMessage = nonBlankString(serverDetail.user_message);
798
+ if (userMessage) return userMessage;
799
+ const message = nonBlankString(serverDetail.message);
800
+ if (message) return message;
801
+ if (Array.isArray(serverDetail.details)) {
802
+ const messages = serverDetail.details.map((entry) => {
803
+ if (!isRecord3(entry)) return void 0;
804
+ const detailMessage = nonBlankString(entry.message);
805
+ if (!detailMessage) return void 0;
806
+ const field = nonBlankString(entry.field);
807
+ return field ? `${field}: ${detailMessage}` : detailMessage;
808
+ }).filter((m) => typeof m === "string");
809
+ if (messages.length > 0) return messages.join("; ");
810
+ }
811
+ const detail = serverDetail.detail;
812
+ if (isRecord3(detail)) {
813
+ const detailMessage = nonBlankString(detail.message) ?? nonBlankString(detail.user_message);
814
+ if (detailMessage) return detailMessage;
815
+ }
816
+ if (typeof detail === "string" && detail.trim()) return detail;
817
+ if (Array.isArray(detail)) {
818
+ const messages = detail.map(
819
+ (entry) => isRecord3(entry) ? nonBlankString(entry.msg) : void 0
820
+ ).filter((m) => typeof m === "string");
821
+ if (messages.length > 0) return messages.join("; ");
822
+ }
823
+ return void 0;
824
+ }
825
+ function extractMatrxErrorCode(serverDetail) {
826
+ if (!isRecord3(serverDetail)) return null;
827
+ const topLevel = nonBlankString(serverDetail.code);
828
+ if (topLevel) return topLevel;
829
+ const detail = serverDetail.detail;
830
+ if (isRecord3(detail)) {
831
+ const nested = nonBlankString(detail.code);
832
+ if (nested) return nested;
833
+ }
834
+ return null;
835
+ }
836
+
837
+ // matrx/conversation.ts
838
+ function mintMatrxConversationId() {
839
+ return crypto.randomUUID();
840
+ }
841
+ function newStoredConversationStart(conversationId) {
842
+ return {
843
+ conversation_id: conversationId ?? mintMatrxConversationId(),
844
+ is_new: true,
845
+ store: true
846
+ };
847
+ }
848
+ function continueStoredConversationStart(conversationId) {
849
+ return { conversation_id: conversationId, is_new: false, store: true };
850
+ }
851
+ function newEphemeralConversationStart(conversationId) {
852
+ return {
853
+ conversation_id: conversationId ?? mintMatrxConversationId(),
854
+ is_new: true,
855
+ store: false
856
+ };
857
+ }
858
+ function continueEphemeralConversationStart(conversationId, priorMessages) {
859
+ return {
860
+ conversation_id: conversationId,
861
+ is_new: false,
862
+ store: false,
863
+ prior_messages: priorMessages
864
+ };
865
+ }
866
+
867
+ // matrx/internal.ts
868
+ function encodePathSegment(value) {
869
+ return encodeURIComponent(value);
870
+ }
871
+ function buildQuery(params) {
872
+ const search = new URLSearchParams();
873
+ for (const [key, value] of Object.entries(params)) {
874
+ if (value === void 0) continue;
875
+ if (Array.isArray(value)) {
876
+ for (const entry of value) search.append(key, entry);
877
+ } else {
878
+ search.append(key, String(value));
879
+ }
880
+ }
881
+ const encoded = search.toString();
882
+ return encoded ? `?${encoded}` : "";
883
+ }
884
+ async function readServerDetail(response) {
885
+ try {
886
+ return await response.json();
887
+ } catch {
888
+ return void 0;
889
+ }
890
+ }
891
+ async function throwApiError(path, response) {
892
+ throw new MatrxApiError({
893
+ status: response.status,
894
+ path,
895
+ serverDetail: await readServerDetail(response)
896
+ });
897
+ }
898
+ async function requestJson(transport, path, options) {
899
+ const hasBody = options.method !== "GET" && options.body !== void 0;
900
+ const response = await transport.fetch(path, {
901
+ method: options.method,
902
+ headers: hasBody ? { "Content-Type": "application/json" } : {},
903
+ ...hasBody ? { body: JSON.stringify(options.body) } : {},
904
+ ...options.signal ? { signal: options.signal } : {}
905
+ });
906
+ if (!response.ok) return throwApiError(path, response);
907
+ return await response.json();
908
+ }
909
+ function toRunHandle(response, options) {
910
+ return {
911
+ requestId: response.headers.get("X-Request-ID"),
912
+ conversationId: response.headers.get("X-Conversation-ID"),
913
+ events: readMatrxNdjsonStream(response.body, {
914
+ ...options.signal ? { signal: options.signal } : {},
915
+ ...options.maxReadAhead !== void 0 ? { maxReadAhead: options.maxReadAhead } : {},
916
+ ...options.onMalformedLine ? { onMalformedLine: options.onMalformedLine } : {},
917
+ ...options.onUnknownEnvelope ? { onUnknownEnvelope: options.onUnknownEnvelope } : {},
918
+ ...options.onValidEnvelope ? { onValidEnvelope: options.onValidEnvelope } : {}
919
+ }),
920
+ response
921
+ };
922
+ }
923
+ async function requestStream(transport, path, options) {
924
+ const hasBody = options.method !== "GET" && options.body !== void 0;
925
+ const response = await transport.fetch(path, {
926
+ method: options.method,
927
+ headers: {
928
+ ...hasBody ? { "Content-Type": "application/json" } : {},
929
+ ...options.headers
930
+ },
931
+ ...hasBody ? { body: JSON.stringify(options.body) } : {},
932
+ ...options.signal ? { signal: options.signal } : {}
933
+ });
934
+ if (!response.ok) return throwApiError(path, response);
935
+ if (!response.body) {
936
+ throw new MatrxApiError({
937
+ status: response.status,
938
+ path,
939
+ serverDetail: { code: "missing_response_body" },
940
+ message: "The streaming response carried no body."
941
+ });
942
+ }
943
+ return response;
944
+ }
945
+
946
+ // matrx/run.ts
947
+ async function streamCall(transport, path, body, options) {
948
+ const response = await requestStream(transport, path, {
949
+ method: "POST",
950
+ // This client IS the streaming path — `stream: true` always, last so a
951
+ // caller-supplied value can never flip the response off NDJSON.
952
+ body: { ...body, stream: true },
953
+ ...options.signal ? { signal: options.signal } : {}
954
+ });
955
+ return toRunHandle(response, options);
956
+ }
957
+ function startAgentRun(transport, agentId, request, options = {}) {
958
+ return streamCall(
959
+ transport,
960
+ `/ai/agents/${encodePathSegment(agentId)}`,
961
+ request,
962
+ options
963
+ );
964
+ }
965
+ function continueAgentConversation(transport, conversationId, request, options = {}) {
966
+ return streamCall(
967
+ transport,
968
+ `/ai/conversations/${encodePathSegment(conversationId)}`,
969
+ request,
970
+ options
971
+ );
972
+ }
973
+ function resumeAgentConversation(transport, conversationId, request = {}, options = {}) {
974
+ return streamCall(
975
+ transport,
976
+ `/ai/conversations/${encodePathSegment(conversationId)}/resume`,
977
+ request,
978
+ options
979
+ );
980
+ }
981
+ function cancelAgentRun(transport, requestId, options = {}) {
982
+ const query = buildQuery(
983
+ options.mode === "interrupt" ? { mode: "interrupt" } : {}
984
+ );
985
+ return requestJson(
986
+ transport,
987
+ `/ai/cancel/${encodePathSegment(requestId)}${query}`,
988
+ {
989
+ method: "POST",
990
+ ...options.signal ? { signal: options.signal } : {}
991
+ }
992
+ );
993
+ }
994
+ var MatrxRunError = class extends Error {
995
+ name = "MatrxRunError";
996
+ /** The verbatim `error` event payload, when one fired. */
997
+ errorPayload;
998
+ /** The `user_request` completion status (`"failed"` | `"cancelled"`), when that was the trigger. */
999
+ completionStatus;
1000
+ /** Text streamed before the failure — partial content never vanishes. */
1001
+ partialText;
1002
+ constructor(args) {
1003
+ super(args.message);
1004
+ this.errorPayload = args.errorPayload ?? null;
1005
+ this.completionStatus = args.completionStatus ?? null;
1006
+ this.partialText = args.partialText ?? "";
1007
+ }
1008
+ };
1009
+ function isRecord4(value) {
1010
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1011
+ }
1012
+ function stringField(value, key) {
1013
+ if (!isRecord4(value)) return null;
1014
+ const field = value[key];
1015
+ return typeof field === "string" && field ? field : null;
1016
+ }
1017
+ async function runAgentToCompletion(transport, agentId, request, options = {}) {
1018
+ const handle = await startAgentRun(transport, agentId, request, options);
1019
+ let text = "";
1020
+ let completion = null;
1021
+ let failure = null;
1022
+ for await (const envelope of handle.events) {
1023
+ options.onEvent?.(envelope);
1024
+ if (envelope.event === "chunk") {
1025
+ const chunk = stringField(envelope.data, "text");
1026
+ if (chunk !== null) {
1027
+ text += chunk;
1028
+ options.onChunk?.(text);
1029
+ }
1030
+ continue;
1031
+ }
1032
+ if (envelope.event === "error" && failure === null) {
1033
+ const payload = isRecord4(envelope.data) ? envelope.data : null;
1034
+ failure = new MatrxRunError({
1035
+ message: stringField(payload, "user_message") ?? stringField(payload, "message") ?? "The agent run failed",
1036
+ errorPayload: payload,
1037
+ partialText: text
1038
+ });
1039
+ continue;
1040
+ }
1041
+ if (envelope.event !== "completion" || !isRecord4(envelope.data)) continue;
1042
+ if (envelope.data.operation !== "user_request") continue;
1043
+ completion = envelope.data;
1044
+ const status = envelope.data.status;
1045
+ if ((status === "failed" || status === "cancelled") && failure === null) {
1046
+ const result = isRecord4(envelope.data.result) ? envelope.data.result : null;
1047
+ failure = new MatrxRunError({
1048
+ message: stringField(result, "error") ?? stringField(result, "user_message") ?? `The agent run ${status}`,
1049
+ completionStatus: status,
1050
+ partialText: text
1051
+ });
1052
+ }
1053
+ }
1054
+ if (failure) throw failure;
1055
+ if (!text && completion) {
1056
+ const result = completion.result;
1057
+ const output = stringField(result, "output");
1058
+ if (output !== null) text = output;
1059
+ }
1060
+ return {
1061
+ text,
1062
+ requestId: handle.requestId,
1063
+ conversationId: handle.conversationId,
1064
+ completion
1065
+ };
1066
+ }
1067
+
1068
+ // matrx/operations.ts
1069
+ var TERMINAL_MATRX_RUNTIME_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
1070
+ var RUNTIME_STATUSES = /* @__PURE__ */ new Set([
1071
+ "pending",
1072
+ "running",
1073
+ "paused",
1074
+ "waiting_input",
1075
+ "completed",
1076
+ "failed",
1077
+ "cancelled"
1078
+ ]);
1079
+ async function getRuntimeOperationStatus(transport, requestId, options = {}) {
1080
+ try {
1081
+ return await requestJson(
1082
+ transport,
1083
+ `/runtime/operations/${encodePathSegment(requestId)}`,
1084
+ { method: "GET", ...options.signal ? { signal: options.signal } : {} }
1085
+ );
1086
+ } catch (error) {
1087
+ if (error instanceof MatrxApiError && error.status === 404) return null;
1088
+ throw error;
1089
+ }
1090
+ }
1091
+ async function getRuntimeOperationsByLink(transport, linkKind, linkId, options = {}) {
1092
+ const query = buildQuery(
1093
+ options.limit !== void 0 ? { limit: options.limit } : {}
1094
+ );
1095
+ try {
1096
+ return await requestJson(
1097
+ transport,
1098
+ `/runtime/operations/by-link/${encodePathSegment(linkKind)}/${encodePathSegment(linkId)}${query}`,
1099
+ { method: "GET", ...options.signal ? { signal: options.signal } : {} }
1100
+ );
1101
+ } catch (error) {
1102
+ if (error instanceof MatrxApiError && error.status === 404) return null;
1103
+ throw error;
1104
+ }
1105
+ }
1106
+ function listRuntimeOperationEvents(transport, executionId, options = {}) {
1107
+ const query = buildQuery({
1108
+ ...options.afterSeq !== void 0 ? { after_seq: options.afterSeq } : {},
1109
+ ...options.limit !== void 0 ? { limit: options.limit } : {},
1110
+ ...options.kinds !== void 0 ? { kind: options.kinds } : {}
1111
+ });
1112
+ return requestJson(
1113
+ transport,
1114
+ `/runtime/executions/${encodePathSegment(executionId)}/events${query}`,
1115
+ { method: "GET", ...options.signal ? { signal: options.signal } : {} }
1116
+ );
1117
+ }
1118
+ function parseEndStatus(data) {
1119
+ if (data === null) return null;
1120
+ try {
1121
+ const parsed = JSON.parse(data);
1122
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.status === "string") {
1123
+ const status = parsed.status;
1124
+ return RUNTIME_STATUSES.has(status) ? status : null;
1125
+ }
1126
+ } catch {
1127
+ }
1128
+ return null;
1129
+ }
1130
+ async function* followRuntimeOperationEvents(transport, executionId, options = {}) {
1131
+ let cursor = options.lastEventSeq ?? 0;
1132
+ const headers = { Accept: "text/event-stream" };
1133
+ if (cursor > 0) headers["Last-Event-ID"] = String(cursor);
1134
+ const response = await requestStream(
1135
+ transport,
1136
+ `/runtime/executions/${encodePathSegment(executionId)}/events/stream`,
1137
+ {
1138
+ method: "GET",
1139
+ headers,
1140
+ ...options.signal ? { signal: options.signal } : {}
1141
+ }
1142
+ );
1143
+ const frames = readMatrxSseStream(
1144
+ response.body,
1145
+ options.onIncomplete ? { onIncomplete: options.onIncomplete } : {}
1146
+ );
1147
+ for await (const frame of frames) {
1148
+ if (frame.event === "end") {
1149
+ yield { type: "end", status: parseEndStatus(frame.data), cursor };
1150
+ return;
1151
+ }
1152
+ if (frame.event === "execution_event" && frame.data !== null) {
1153
+ let event;
1154
+ try {
1155
+ event = JSON.parse(frame.data);
1156
+ } catch (error) {
1157
+ options.onMalformedFrame?.(frame, error);
1158
+ yield { type: "liveness", cursor };
1159
+ continue;
1160
+ }
1161
+ if (frame.seq !== null && frame.seq > cursor) cursor = frame.seq;
1162
+ yield { type: "event", event, seq: frame.seq, cursor };
1163
+ continue;
1164
+ }
1165
+ yield { type: "liveness", cursor };
1166
+ }
1167
+ }
1168
+ async function rejoinRuntimeOperation(transport, requestId, options = {}) {
1169
+ const response = await requestStream(
1170
+ transport,
1171
+ `/runtime/operations/${encodePathSegment(requestId)}/rejoin`,
1172
+ {
1173
+ method: "POST",
1174
+ // The route takes no body model; the reference client posts an empty
1175
+ // JSON object. Match it so proxies see an ordinary JSON POST.
1176
+ body: {},
1177
+ ...options.signal ? { signal: options.signal } : {}
1178
+ }
1179
+ );
1180
+ return toRunHandle(response, options);
1181
+ }
1182
+
1183
+ // matrx/tools.ts
1184
+ function submitAgentToolResults(transport, conversationId, results, options = {}) {
1185
+ return requestJson(
1186
+ transport,
1187
+ `/ai/conversations/${encodePathSegment(conversationId)}/tool_results`,
1188
+ {
1189
+ method: "POST",
1190
+ body: {
1191
+ results,
1192
+ ...options.instanceId !== void 0 ? { instance_id: options.instanceId } : {}
1193
+ },
1194
+ ...options.signal ? { signal: options.signal } : {}
1195
+ }
1196
+ );
1197
+ }
1198
+ function listConversationPendingToolCalls(transport, conversationId, options = {}) {
1199
+ return requestJson(
1200
+ transport,
1201
+ `/ai/conversations/${encodePathSegment(conversationId)}/pending_calls`,
1202
+ {
1203
+ method: "GET",
1204
+ ...options.signal ? { signal: options.signal } : {}
1205
+ }
1206
+ );
1207
+ }
1208
+ function listUserPendingToolCalls(transport, options = {}) {
1209
+ const query = buildQuery(
1210
+ options.instanceId !== void 0 ? { instance_id: options.instanceId } : {}
1211
+ );
1212
+ return requestJson(
1213
+ transport,
1214
+ `/ai/user/pending_calls${query}`,
1215
+ {
1216
+ method: "GET",
1217
+ ...options.signal ? { signal: options.signal } : {}
1218
+ }
1219
+ );
1220
+ }
1221
+
1222
+ export { DEFAULT_MATRX_NDJSON_READ_AHEAD, DEFAULT_WORKFLOW_PROJECTION_LIMITS, MatrxApiError, MatrxRunError, TERMINAL_MATRX_RUNTIME_STATUSES, cancelAgentRun, continueAgentConversation, continueEphemeralConversationStart, continueStoredConversationStart, createAgentRequestProjection, createMatrxNdjsonFramer, createMatrxSseFramer, createWorkflowNodeProjection, extractMatrxErrorCode, extractMatrxErrorMessage, followRuntimeOperationEvents, getRuntimeOperationStatus, getRuntimeOperationsByLink, listConversationPendingToolCalls, listRuntimeOperationEvents, listUserPendingToolCalls, mintMatrxConversationId, newEphemeralConversationStart, newStoredConversationStart, normalizeMatrxStreamEnvelope, parseMatrxSseFrame, projectAgentEvent, projectAgentEvents, projectAgentResultForDisplay, projectWorkflowNodeEvent, readMatrxNdjsonStream, readMatrxSseStream, rejoinRuntimeOperation, resumeAgentConversation, runAgentToCompletion, startAgentRun, submitAgentToolResults };
470
1223
  //# sourceMappingURL=index.js.map
471
1224
  //# sourceMappingURL=index.js.map