@ai-matrx/agents 0.10.3 → 0.10.4

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.
@@ -34,17 +34,33 @@ function normalizeMatrxStreamEnvelope(value) {
34
34
  if (!isRecord(value)) return null;
35
35
  if (typeof value.event === "string") {
36
36
  const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
37
+ const streamId = typeof value.stream_id === "string" ? value.stream_id : void 0;
37
38
  return {
38
39
  event: value.event,
39
40
  data: value.data,
41
+ ...streamId === void 0 ? {} : { stream_id: streamId },
40
42
  ...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
41
43
  };
42
44
  }
43
45
  if (value.e === "c" && typeof value.t === "string") {
44
- return { event: "chunk", data: { text: value.t } };
46
+ const streamId = typeof value.stream_id === "string" ? value.stream_id : void 0;
47
+ const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
48
+ return {
49
+ event: "chunk",
50
+ data: { text: value.t },
51
+ ...streamId === void 0 ? {} : { stream_id: streamId },
52
+ ...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
53
+ };
45
54
  }
46
55
  if (value.e === "r" && typeof value.t === "string") {
47
- return { event: "reasoning_chunk", data: { text: value.t } };
56
+ const streamId = typeof value.stream_id === "string" ? value.stream_id : void 0;
57
+ const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
58
+ return {
59
+ event: "reasoning_chunk",
60
+ data: { text: value.t },
61
+ ...streamId === void 0 ? {} : { stream_id: streamId },
62
+ ...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
63
+ };
48
64
  }
49
65
  return null;
50
66
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../stream/ndjson.ts"],"sourcesContent":["/**\n * Canonical AI Matrx NDJSON wire kernel.\n *\n * This module is deliberately independent of React, Redux, Next.js, Supabase,\n * and generated application types. Every Matrx client uses it to turn the\n * backend's byte stream into the same normalized `{ event, data }` envelopes.\n * Host runtimes remain responsible for HTTP/auth errors and for deciding what\n * each event means in their state model.\n */\n\nexport interface MatrxStreamEnvelope<TData = unknown> {\n event: string;\n data: TData;\n /** Monotonic transport sequence from full envelopes, when supplied. */\n stream_seq?: number;\n}\n\nexport interface MatrxNdjsonIssue {\n line: string;\n error: unknown;\n /** One-based physical NDJSON line number. */\n lineNumber: number;\n /** True when an unterminated trailing fragment was parsed by `finish()`. */\n atCompletion: boolean;\n}\n\nexport interface MatrxStreamEnvelopeObservation {\n /** Exact parsed JSON value before compact/full normalization. */\n raw: unknown;\n envelope: MatrxStreamEnvelope;\n line: string;\n lineNumber: number;\n atCompletion: boolean;\n}\n\nexport interface MatrxNdjsonFramerOptions {\n /** Malformed JSON is non-fatal, but it must never disappear silently. */\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n /** Valid JSON with no recognized Matrx event envelope is also non-fatal. */\n onUnknownEnvelope?: (value: unknown) => void;\n /** Observe every valid envelope without changing or consuming it. */\n onValidEnvelope?: (observation: MatrxStreamEnvelopeObservation) => void;\n}\n\nexport interface ReadMatrxNdjsonOptions extends MatrxNdjsonFramerOptions {\n signal?: AbortSignal;\n /** Maximum normalized events read ahead of the iterator consumer. */\n maxReadAhead?: number;\n}\n\nexport interface MatrxNdjsonFramer {\n /** Push a text fragment. Fragments may split JSON tokens or line endings. */\n pushText(fragment: string): MatrxStreamEnvelope[];\n /** Push a byte fragment. UTF-8 code points may span calls. */\n pushBytes(fragment: Uint8Array): MatrxStreamEnvelope[];\n /** Parse the final unterminated line and flush any pending UTF-8 bytes. */\n finish(): MatrxStreamEnvelope[];\n}\n\ntype QueueItem =\n | { kind: \"event\"; value: MatrxStreamEnvelope }\n | { kind: \"error\"; error: unknown }\n | { kind: \"done\" };\n\nexport const DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Normalize both supported Matrx wire shapes:\n *\n * - full: `{ \"event\": \"chunk\", \"data\": { \"text\": \"...\" } }`\n * - compact chunk: `{ \"e\": \"c\", \"t\": \"...\" }`\n * - compact reasoning: `{ \"e\": \"r\", \"t\": \"...\" }`\n */\nexport function normalizeMatrxStreamEnvelope(\n value: unknown,\n): MatrxStreamEnvelope | null {\n if (!isRecord(value)) return null;\n\n if (typeof value.event === \"string\") {\n const streamSeq =\n typeof value.stream_seq === \"number\" && Number.isFinite(value.stream_seq)\n ? value.stream_seq\n : undefined;\n return {\n event: value.event,\n data: value.data,\n ...(streamSeq === undefined ? {} : { stream_seq: streamSeq }),\n };\n }\n if (value.e === \"c\" && typeof value.t === \"string\") {\n return { event: \"chunk\", data: { text: value.t } };\n }\n if (value.e === \"r\" && typeof value.t === \"string\") {\n return { event: \"reasoning_chunk\", data: { text: value.t } };\n }\n return null;\n}\n\n/**\n * Create the transport-independent NDJSON framer used by the stream reader.\n * Browser extensions, desktop bridges, WebSockets, and tests can feed it\n * fragmented strings or bytes without constructing a `ReadableStream`.\n */\nexport function createMatrxNdjsonFramer(\n options: MatrxNdjsonFramerOptions = {},\n): MatrxNdjsonFramer {\n const decoder = new TextDecoder();\n let buffer = \"\";\n let lineNumber = 0;\n let finished = false;\n\n const assertOpen = (): void => {\n if (finished) throw new Error(\"Matrx NDJSON framer is already finished\");\n };\n\n const parseLine = (\n line: string,\n atCompletion: boolean,\n ): MatrxStreamEnvelope | null => {\n const currentLineNumber = ++lineNumber;\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed) as unknown;\n } catch (error) {\n options.onMalformedLine?.({\n line: trimmed,\n error,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return null;\n }\n\n const envelope = normalizeMatrxStreamEnvelope(parsed);\n if (!envelope) {\n options.onUnknownEnvelope?.(parsed);\n return null;\n }\n options.onValidEnvelope?.({\n raw: parsed,\n envelope,\n line: trimmed,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return envelope;\n };\n\n const pushDecodedText = (fragment: string): MatrxStreamEnvelope[] => {\n buffer += fragment;\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n const envelopes: MatrxStreamEnvelope[] = [];\n for (const line of lines) {\n const envelope = parseLine(line, false);\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n };\n\n return {\n pushText(fragment) {\n assertOpen();\n // Flush any pending byte fragment before switching to explicit text.\n return pushDecodedText(decoder.decode() + fragment);\n },\n pushBytes(fragment) {\n assertOpen();\n return pushDecodedText(decoder.decode(fragment, { stream: true }));\n },\n finish() {\n assertOpen();\n finished = true;\n const envelopes = pushDecodedText(decoder.decode());\n if (buffer.length > 0) {\n const envelope = parseLine(buffer, true);\n buffer = \"\";\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n },\n };\n}\n\nfunction readAheadLimit(value: number | undefined): number {\n const limit = value ?? DEFAULT_MATRX_NDJSON_READ_AHEAD;\n if (!Number.isSafeInteger(limit) || limit < 1) {\n throw new RangeError(\"maxReadAhead must be a positive safe integer\");\n }\n return limit;\n}\n\n/**\n * Read and normalize a Matrx NDJSON response body with bounded background\n * read-ahead. Consumer work does not stall the network until `maxReadAhead`\n * complete events are waiting; the bound prevents an abandoned or blocked\n * consumer from growing memory without limit.\n */\nexport async function* readMatrxNdjsonStream(\n body: ReadableStream<Uint8Array>,\n options: ReadMatrxNdjsonOptions = {},\n): AsyncGenerator<MatrxStreamEnvelope, void, undefined> {\n const maxReadAhead = readAheadLimit(options.maxReadAhead);\n const queue: QueueItem[] = [];\n let queuedEventCount = 0;\n let wakeConsumer: (() => void) | null = null;\n let wakeProducer: (() => void) | null = null;\n let readerFinished = false;\n let consumerClosed = false;\n\n const wakeWaitingConsumer = (): void => {\n const wake = wakeConsumer;\n wakeConsumer = null;\n wake?.();\n };\n const wakeWaitingProducer = (): void => {\n const wake = wakeProducer;\n wakeProducer = null;\n wake?.();\n };\n const enqueueTerminal = (item: QueueItem): void => {\n if (consumerClosed) return;\n queue.push(item);\n wakeWaitingConsumer();\n };\n const enqueueEvent = async (value: MatrxStreamEnvelope): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n if (consumerClosed || options.signal?.aborted) return false;\n queue.push({ kind: \"event\", value });\n queuedEventCount += 1;\n wakeWaitingConsumer();\n return true;\n };\n const waitForReadCapacity = async (): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n return !consumerClosed && !options.signal?.aborted;\n };\n\n const reader = body.getReader();\n const framer = createMatrxNdjsonFramer(options);\n\n const onAbort = (): void => {\n wakeWaitingProducer();\n wakeWaitingConsumer();\n void reader.cancel(options.signal?.reason).catch(() => undefined);\n };\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (options.signal?.aborted) onAbort();\n\n const readerPromise = (async (): Promise<void> => {\n try {\n while (!options.signal?.aborted && !consumerClosed) {\n if (!(await waitForReadCapacity())) return;\n const { value, done } = await reader.read();\n if (done) break;\n for (const envelope of framer.pushBytes(value)) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n\n if (!options.signal?.aborted && !consumerClosed) {\n for (const envelope of framer.finish()) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n } catch (error) {\n const aborted =\n options.signal?.aborted ||\n consumerClosed ||\n (error instanceof Error && error.name === \"AbortError\");\n if (!aborted) enqueueTerminal({ kind: \"error\", error });\n } finally {\n readerFinished = true;\n reader.releaseLock();\n enqueueTerminal({ kind: \"done\" });\n }\n })();\n\n try {\n while (true) {\n if (queue.length === 0) {\n if (options.signal?.aborted || readerFinished) return;\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n }\n\n const item = queue.shift();\n if (item?.kind === \"event\") queuedEventCount -= 1;\n wakeWaitingProducer();\n if (!item || item.kind === \"done\") return;\n if (item.kind === \"error\") throw item.error;\n yield item.value;\n }\n } finally {\n consumerClosed = true;\n wakeWaitingProducer();\n wakeWaitingConsumer();\n options.signal?.removeEventListener(\"abort\", onAbort);\n if (!readerFinished) {\n await reader.cancel().catch(() => undefined);\n }\n await readerPromise;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../stream/ndjson.ts"],"sourcesContent":["/**\n * Canonical AI Matrx NDJSON wire kernel.\n *\n * This module is deliberately independent of React, Redux, Next.js, Supabase,\n * and generated application types. Every Matrx client uses it to turn the\n * backend's byte stream into the same normalized `{ event, data }` envelopes.\n * Host runtimes remain responsible for HTTP/auth errors and for deciding what\n * each event means in their state model.\n */\n\nexport interface MatrxStreamEnvelope<TData = unknown> {\n event: string;\n /** Immutable emitter segment; transport sequence is scoped to this id. */\n stream_id?: string;\n data: TData;\n /** Monotonic transport sequence from full envelopes, when supplied. */\n stream_seq?: number;\n}\n\nexport interface MatrxNdjsonIssue {\n line: string;\n error: unknown;\n /** One-based physical NDJSON line number. */\n lineNumber: number;\n /** True when an unterminated trailing fragment was parsed by `finish()`. */\n atCompletion: boolean;\n}\n\nexport interface MatrxStreamEnvelopeObservation {\n /** Exact parsed JSON value before compact/full normalization. */\n raw: unknown;\n envelope: MatrxStreamEnvelope;\n line: string;\n lineNumber: number;\n atCompletion: boolean;\n}\n\nexport interface MatrxNdjsonFramerOptions {\n /** Malformed JSON is non-fatal, but it must never disappear silently. */\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n /** Valid JSON with no recognized Matrx event envelope is also non-fatal. */\n onUnknownEnvelope?: (value: unknown) => void;\n /** Observe every valid envelope without changing or consuming it. */\n onValidEnvelope?: (observation: MatrxStreamEnvelopeObservation) => void;\n}\n\nexport interface ReadMatrxNdjsonOptions extends MatrxNdjsonFramerOptions {\n signal?: AbortSignal;\n /** Maximum normalized events read ahead of the iterator consumer. */\n maxReadAhead?: number;\n}\n\nexport interface MatrxNdjsonFramer {\n /** Push a text fragment. Fragments may split JSON tokens or line endings. */\n pushText(fragment: string): MatrxStreamEnvelope[];\n /** Push a byte fragment. UTF-8 code points may span calls. */\n pushBytes(fragment: Uint8Array): MatrxStreamEnvelope[];\n /** Parse the final unterminated line and flush any pending UTF-8 bytes. */\n finish(): MatrxStreamEnvelope[];\n}\n\ntype QueueItem =\n | { kind: \"event\"; value: MatrxStreamEnvelope }\n | { kind: \"error\"; error: unknown }\n | { kind: \"done\" };\n\nexport const DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Normalize both supported Matrx wire shapes:\n *\n * - full: `{ \"event\": \"chunk\", \"data\": { \"text\": \"...\" } }`\n * - compact chunk: `{ \"e\": \"c\", \"t\": \"...\" }`\n * - compact reasoning: `{ \"e\": \"r\", \"t\": \"...\" }`\n */\nexport function normalizeMatrxStreamEnvelope(\n value: unknown,\n): MatrxStreamEnvelope | null {\n if (!isRecord(value)) return null;\n\n if (typeof value.event === \"string\") {\n const streamSeq =\n typeof value.stream_seq === \"number\" && Number.isFinite(value.stream_seq)\n ? value.stream_seq\n : undefined;\n const streamId = typeof value.stream_id === \"string\" ? value.stream_id : undefined;\n return {\n event: value.event,\n data: value.data,\n ...(streamId === undefined ? {} : { stream_id: streamId }),\n ...(streamSeq === undefined ? {} : { stream_seq: streamSeq }),\n };\n }\n if (value.e === \"c\" && typeof value.t === \"string\") {\n const streamId = typeof value.stream_id === \"string\" ? value.stream_id : undefined;\n const streamSeq =\n typeof value.stream_seq === \"number\" && Number.isFinite(value.stream_seq)\n ? value.stream_seq\n : undefined;\n return {\n event: \"chunk\",\n data: { text: value.t },\n ...(streamId === undefined ? {} : { stream_id: streamId }),\n ...(streamSeq === undefined ? {} : { stream_seq: streamSeq }),\n };\n }\n if (value.e === \"r\" && typeof value.t === \"string\") {\n const streamId = typeof value.stream_id === \"string\" ? value.stream_id : undefined;\n const streamSeq =\n typeof value.stream_seq === \"number\" && Number.isFinite(value.stream_seq)\n ? value.stream_seq\n : undefined;\n return {\n event: \"reasoning_chunk\",\n data: { text: value.t },\n ...(streamId === undefined ? {} : { stream_id: streamId }),\n ...(streamSeq === undefined ? {} : { stream_seq: streamSeq }),\n };\n }\n return null;\n}\n\n/**\n * Create the transport-independent NDJSON framer used by the stream reader.\n * Browser extensions, desktop bridges, WebSockets, and tests can feed it\n * fragmented strings or bytes without constructing a `ReadableStream`.\n */\nexport function createMatrxNdjsonFramer(\n options: MatrxNdjsonFramerOptions = {},\n): MatrxNdjsonFramer {\n const decoder = new TextDecoder();\n let buffer = \"\";\n let lineNumber = 0;\n let finished = false;\n\n const assertOpen = (): void => {\n if (finished) throw new Error(\"Matrx NDJSON framer is already finished\");\n };\n\n const parseLine = (\n line: string,\n atCompletion: boolean,\n ): MatrxStreamEnvelope | null => {\n const currentLineNumber = ++lineNumber;\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed) as unknown;\n } catch (error) {\n options.onMalformedLine?.({\n line: trimmed,\n error,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return null;\n }\n\n const envelope = normalizeMatrxStreamEnvelope(parsed);\n if (!envelope) {\n options.onUnknownEnvelope?.(parsed);\n return null;\n }\n options.onValidEnvelope?.({\n raw: parsed,\n envelope,\n line: trimmed,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return envelope;\n };\n\n const pushDecodedText = (fragment: string): MatrxStreamEnvelope[] => {\n buffer += fragment;\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n const envelopes: MatrxStreamEnvelope[] = [];\n for (const line of lines) {\n const envelope = parseLine(line, false);\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n };\n\n return {\n pushText(fragment) {\n assertOpen();\n // Flush any pending byte fragment before switching to explicit text.\n return pushDecodedText(decoder.decode() + fragment);\n },\n pushBytes(fragment) {\n assertOpen();\n return pushDecodedText(decoder.decode(fragment, { stream: true }));\n },\n finish() {\n assertOpen();\n finished = true;\n const envelopes = pushDecodedText(decoder.decode());\n if (buffer.length > 0) {\n const envelope = parseLine(buffer, true);\n buffer = \"\";\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n },\n };\n}\n\nfunction readAheadLimit(value: number | undefined): number {\n const limit = value ?? DEFAULT_MATRX_NDJSON_READ_AHEAD;\n if (!Number.isSafeInteger(limit) || limit < 1) {\n throw new RangeError(\"maxReadAhead must be a positive safe integer\");\n }\n return limit;\n}\n\n/**\n * Read and normalize a Matrx NDJSON response body with bounded background\n * read-ahead. Consumer work does not stall the network until `maxReadAhead`\n * complete events are waiting; the bound prevents an abandoned or blocked\n * consumer from growing memory without limit.\n */\nexport async function* readMatrxNdjsonStream(\n body: ReadableStream<Uint8Array>,\n options: ReadMatrxNdjsonOptions = {},\n): AsyncGenerator<MatrxStreamEnvelope, void, undefined> {\n const maxReadAhead = readAheadLimit(options.maxReadAhead);\n const queue: QueueItem[] = [];\n let queuedEventCount = 0;\n let wakeConsumer: (() => void) | null = null;\n let wakeProducer: (() => void) | null = null;\n let readerFinished = false;\n let consumerClosed = false;\n\n const wakeWaitingConsumer = (): void => {\n const wake = wakeConsumer;\n wakeConsumer = null;\n wake?.();\n };\n const wakeWaitingProducer = (): void => {\n const wake = wakeProducer;\n wakeProducer = null;\n wake?.();\n };\n const enqueueTerminal = (item: QueueItem): void => {\n if (consumerClosed) return;\n queue.push(item);\n wakeWaitingConsumer();\n };\n const enqueueEvent = async (value: MatrxStreamEnvelope): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n if (consumerClosed || options.signal?.aborted) return false;\n queue.push({ kind: \"event\", value });\n queuedEventCount += 1;\n wakeWaitingConsumer();\n return true;\n };\n const waitForReadCapacity = async (): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n return !consumerClosed && !options.signal?.aborted;\n };\n\n const reader = body.getReader();\n const framer = createMatrxNdjsonFramer(options);\n\n const onAbort = (): void => {\n wakeWaitingProducer();\n wakeWaitingConsumer();\n void reader.cancel(options.signal?.reason).catch(() => undefined);\n };\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (options.signal?.aborted) onAbort();\n\n const readerPromise = (async (): Promise<void> => {\n try {\n while (!options.signal?.aborted && !consumerClosed) {\n if (!(await waitForReadCapacity())) return;\n const { value, done } = await reader.read();\n if (done) break;\n for (const envelope of framer.pushBytes(value)) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n\n if (!options.signal?.aborted && !consumerClosed) {\n for (const envelope of framer.finish()) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n } catch (error) {\n const aborted =\n options.signal?.aborted ||\n consumerClosed ||\n (error instanceof Error && error.name === \"AbortError\");\n if (!aborted) enqueueTerminal({ kind: \"error\", error });\n } finally {\n readerFinished = true;\n reader.releaseLock();\n enqueueTerminal({ kind: \"done\" });\n }\n })();\n\n try {\n while (true) {\n if (queue.length === 0) {\n if (options.signal?.aborted || readerFinished) return;\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n }\n\n const item = queue.shift();\n if (item?.kind === \"event\") queuedEventCount -= 1;\n wakeWaitingProducer();\n if (!item || item.kind === \"done\") return;\n if (item.kind === \"error\") throw item.error;\n yield item.value;\n }\n } finally {\n consumerClosed = true;\n wakeWaitingProducer();\n wakeWaitingConsumer();\n options.signal?.removeEventListener(\"abort\", onAbort);\n if (!readerFinished) {\n await reader.cancel().catch(() => undefined);\n }\n await readerPromise;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkEO,IAAM,kCAAkC;AAE/C,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AASO,SAAS,6BACd,OAC4B;AAC5B,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAE7B,MAAI,OAAO,MAAM,UAAU,UAAU;AACnC,UAAM,YACJ,OAAO,MAAM,eAAe,YAAY,OAAO,SAAS,MAAM,UAAU,IACpE,MAAM,aACN;AACN,UAAM,WAAW,OAAO,MAAM,cAAc,WAAW,MAAM,YAAY;AACzE,WAAO;AAAA,MACL,OAAO,MAAM;AAAA,MACb,MAAM,MAAM;AAAA,MACZ,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,WAAW,SAAS;AAAA,MACxD,GAAI,cAAc,SAAY,CAAC,IAAI,EAAE,YAAY,UAAU;AAAA,IAC7D;AAAA,EACF;AACA,MAAI,MAAM,MAAM,OAAO,OAAO,MAAM,MAAM,UAAU;AAClD,UAAM,WAAW,OAAO,MAAM,cAAc,WAAW,MAAM,YAAY;AACzE,UAAM,YACJ,OAAO,MAAM,eAAe,YAAY,OAAO,SAAS,MAAM,UAAU,IACpE,MAAM,aACN;AACN,WAAO;AAAA,MACL,OAAO;AAAA,MACP,MAAM,EAAE,MAAM,MAAM,EAAE;AAAA,MACtB,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,WAAW,SAAS;AAAA,MACxD,GAAI,cAAc,SAAY,CAAC,IAAI,EAAE,YAAY,UAAU;AAAA,IAC7D;AAAA,EACF;AACA,MAAI,MAAM,MAAM,OAAO,OAAO,MAAM,MAAM,UAAU;AAClD,UAAM,WAAW,OAAO,MAAM,cAAc,WAAW,MAAM,YAAY;AACzE,UAAM,YACJ,OAAO,MAAM,eAAe,YAAY,OAAO,SAAS,MAAM,UAAU,IACpE,MAAM,aACN;AACN,WAAO;AAAA,MACL,OAAO;AAAA,MACP,MAAM,EAAE,MAAM,MAAM,EAAE;AAAA,MACtB,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,WAAW,SAAS;AAAA,MACxD,GAAI,cAAc,SAAY,CAAC,IAAI,EAAE,YAAY,UAAU;AAAA,IAC7D;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,wBACd,UAAoC,CAAC,GAClB;AACnB,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,SAAS;AACb,MAAI,aAAa;AACjB,MAAI,WAAW;AAEf,QAAM,aAAa,MAAY;AAC7B,QAAI,SAAU,OAAM,IAAI,MAAM,yCAAyC;AAAA,EACzE;AAEA,QAAM,YAAY,CAChB,MACA,iBAC+B;AAC/B,UAAM,oBAAoB,EAAE;AAC5B,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS,QAAO;AAErB,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,OAAO;AAAA,IAC7B,SAAS,OAAO;AACd,cAAQ,kBAAkB;AAAA,QACxB,MAAM;AAAA,QACN;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,MACF,CAAC;AACD,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,6BAA6B,MAAM;AACpD,QAAI,CAAC,UAAU;AACb,cAAQ,oBAAoB,MAAM;AAClC,aAAO;AAAA,IACT;AACA,YAAQ,kBAAkB;AAAA,MACxB,KAAK;AAAA,MACL;AAAA,MACA,MAAM;AAAA,MACN,YAAY;AAAA,MACZ;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB,CAAC,aAA4C;AACnE,cAAU;AACV,UAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,aAAS,MAAM,IAAI,KAAK;AACxB,UAAM,YAAmC,CAAC;AAC1C,eAAW,QAAQ,OAAO;AACxB,YAAM,WAAW,UAAU,MAAM,KAAK;AACtC,UAAI,SAAU,WAAU,KAAK,QAAQ;AAAA,IACvC;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,SAAS,UAAU;AACjB,iBAAW;AAEX,aAAO,gBAAgB,QAAQ,OAAO,IAAI,QAAQ;AAAA,IACpD;AAAA,IACA,UAAU,UAAU;AAClB,iBAAW;AACX,aAAO,gBAAgB,QAAQ,OAAO,UAAU,EAAE,QAAQ,KAAK,CAAC,CAAC;AAAA,IACnE;AAAA,IACA,SAAS;AACP,iBAAW;AACX,iBAAW;AACX,YAAM,YAAY,gBAAgB,QAAQ,OAAO,CAAC;AAClD,UAAI,OAAO,SAAS,GAAG;AACrB,cAAM,WAAW,UAAU,QAAQ,IAAI;AACvC,iBAAS;AACT,YAAI,SAAU,WAAU,KAAK,QAAQ;AAAA,MACvC;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,SAAS,eAAe,OAAmC;AACzD,QAAM,QAAQ,SAAS;AACvB,MAAI,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ,GAAG;AAC7C,UAAM,IAAI,WAAW,8CAA8C;AAAA,EACrE;AACA,SAAO;AACT;AAQA,gBAAuB,sBACrB,MACA,UAAkC,CAAC,GACmB;AACtD,QAAM,eAAe,eAAe,QAAQ,YAAY;AACxD,QAAM,QAAqB,CAAC;AAC5B,MAAI,mBAAmB;AACvB,MAAI,eAAoC;AACxC,MAAI,eAAoC;AACxC,MAAI,iBAAiB;AACrB,MAAI,iBAAiB;AAErB,QAAM,sBAAsB,MAAY;AACtC,UAAM,OAAO;AACb,mBAAe;AACf,WAAO;AAAA,EACT;AACA,QAAM,sBAAsB,MAAY;AACtC,UAAM,OAAO;AACb,mBAAe;AACf,WAAO;AAAA,EACT;AACA,QAAM,kBAAkB,CAAC,SAA0B;AACjD,QAAI,eAAgB;AACpB,UAAM,KAAK,IAAI;AACf,wBAAoB;AAAA,EACtB;AACA,QAAM,eAAe,OAAO,UAAiD;AAC3E,WACE,oBAAoB,gBACpB,CAAC,kBACD,CAAC,QAAQ,QAAQ,SACjB;AACA,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,uBAAe;AAAA,MACjB,CAAC;AAAA,IACH;AACA,QAAI,kBAAkB,QAAQ,QAAQ,QAAS,QAAO;AACtD,UAAM,KAAK,EAAE,MAAM,SAAS,MAAM,CAAC;AACnC,wBAAoB;AACpB,wBAAoB;AACpB,WAAO;AAAA,EACT;AACA,QAAM,sBAAsB,YAA8B;AACxD,WACE,oBAAoB,gBACpB,CAAC,kBACD,CAAC,QAAQ,QAAQ,SACjB;AACA,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,uBAAe;AAAA,MACjB,CAAC;AAAA,IACH;AACA,WAAO,CAAC,kBAAkB,CAAC,QAAQ,QAAQ;AAAA,EAC7C;AAEA,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,SAAS,wBAAwB,OAAO;AAE9C,QAAM,UAAU,MAAY;AAC1B,wBAAoB;AACpB,wBAAoB;AACpB,SAAK,OAAO,OAAO,QAAQ,QAAQ,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,EAClE;AACA,UAAQ,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AACjE,MAAI,QAAQ,QAAQ,QAAS,SAAQ;AAErC,QAAM,iBAAiB,YAA2B;AAChD,QAAI;AACF,aAAO,CAAC,QAAQ,QAAQ,WAAW,CAAC,gBAAgB;AAClD,YAAI,CAAE,MAAM,oBAAoB,EAAI;AACpC,cAAM,EAAE,OAAO,KAAK,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AACV,mBAAW,YAAY,OAAO,UAAU,KAAK,GAAG;AAC9C,cAAI,CAAE,MAAM,aAAa,QAAQ,EAAI;AAAA,QACvC;AAAA,MACF;AAEA,UAAI,CAAC,QAAQ,QAAQ,WAAW,CAAC,gBAAgB;AAC/C,mBAAW,YAAY,OAAO,OAAO,GAAG;AACtC,cAAI,CAAE,MAAM,aAAa,QAAQ,EAAI;AAAA,QACvC;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,YAAM,UACJ,QAAQ,QAAQ,WAChB,kBACC,iBAAiB,SAAS,MAAM,SAAS;AAC5C,UAAI,CAAC,QAAS,iBAAgB,EAAE,MAAM,SAAS,MAAM,CAAC;AAAA,IACxD,UAAE;AACA,uBAAiB;AACjB,aAAO,YAAY;AACnB,sBAAgB,EAAE,MAAM,OAAO,CAAC;AAAA,IAClC;AAAA,EACF,GAAG;AAEH,MAAI;AACF,WAAO,MAAM;AACX,UAAI,MAAM,WAAW,GAAG;AACtB,YAAI,QAAQ,QAAQ,WAAW,eAAgB;AAC/C,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,yBAAe;AAAA,QACjB,CAAC;AAAA,MACH;AAEA,YAAM,OAAO,MAAM,MAAM;AACzB,UAAI,MAAM,SAAS,QAAS,qBAAoB;AAChD,0BAAoB;AACpB,UAAI,CAAC,QAAQ,KAAK,SAAS,OAAQ;AACnC,UAAI,KAAK,SAAS,QAAS,OAAM,KAAK;AACtC,YAAM,KAAK;AAAA,IACb;AAAA,EACF,UAAE;AACA,qBAAiB;AACjB,wBAAoB;AACpB,wBAAoB;AACpB,YAAQ,QAAQ,oBAAoB,SAAS,OAAO;AACpD,QAAI,CAAC,gBAAgB;AACnB,YAAM,OAAO,OAAO,EAAE,MAAM,MAAM,MAAS;AAAA,IAC7C;AACA,UAAM;AAAA,EACR;AACF;","names":[]}
@@ -9,6 +9,8 @@
9
9
  */
10
10
  interface MatrxStreamEnvelope<TData = unknown> {
11
11
  event: string;
12
+ /** Immutable emitter segment; transport sequence is scoped to this id. */
13
+ stream_id?: string;
12
14
  data: TData;
13
15
  /** Monotonic transport sequence from full envelopes, when supplied. */
14
16
  stream_seq?: number;
@@ -9,6 +9,8 @@
9
9
  */
10
10
  interface MatrxStreamEnvelope<TData = unknown> {
11
11
  event: string;
12
+ /** Immutable emitter segment; transport sequence is scoped to this id. */
13
+ stream_id?: string;
12
14
  data: TData;
13
15
  /** Monotonic transport sequence from full envelopes, when supplied. */
14
16
  stream_seq?: number;
@@ -7,17 +7,33 @@ function normalizeMatrxStreamEnvelope(value) {
7
7
  if (!isRecord(value)) return null;
8
8
  if (typeof value.event === "string") {
9
9
  const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
10
+ const streamId = typeof value.stream_id === "string" ? value.stream_id : void 0;
10
11
  return {
11
12
  event: value.event,
12
13
  data: value.data,
14
+ ...streamId === void 0 ? {} : { stream_id: streamId },
13
15
  ...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
14
16
  };
15
17
  }
16
18
  if (value.e === "c" && typeof value.t === "string") {
17
- return { event: "chunk", data: { text: value.t } };
19
+ const streamId = typeof value.stream_id === "string" ? value.stream_id : void 0;
20
+ const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
21
+ return {
22
+ event: "chunk",
23
+ data: { text: value.t },
24
+ ...streamId === void 0 ? {} : { stream_id: streamId },
25
+ ...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
26
+ };
18
27
  }
19
28
  if (value.e === "r" && typeof value.t === "string") {
20
- return { event: "reasoning_chunk", data: { text: value.t } };
29
+ const streamId = typeof value.stream_id === "string" ? value.stream_id : void 0;
30
+ const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
31
+ return {
32
+ event: "reasoning_chunk",
33
+ data: { text: value.t },
34
+ ...streamId === void 0 ? {} : { stream_id: streamId },
35
+ ...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
36
+ };
21
37
  }
22
38
  return null;
23
39
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../stream/ndjson.ts"],"sourcesContent":["/**\n * Canonical AI Matrx NDJSON wire kernel.\n *\n * This module is deliberately independent of React, Redux, Next.js, Supabase,\n * and generated application types. Every Matrx client uses it to turn the\n * backend's byte stream into the same normalized `{ event, data }` envelopes.\n * Host runtimes remain responsible for HTTP/auth errors and for deciding what\n * each event means in their state model.\n */\n\nexport interface MatrxStreamEnvelope<TData = unknown> {\n event: string;\n data: TData;\n /** Monotonic transport sequence from full envelopes, when supplied. */\n stream_seq?: number;\n}\n\nexport interface MatrxNdjsonIssue {\n line: string;\n error: unknown;\n /** One-based physical NDJSON line number. */\n lineNumber: number;\n /** True when an unterminated trailing fragment was parsed by `finish()`. */\n atCompletion: boolean;\n}\n\nexport interface MatrxStreamEnvelopeObservation {\n /** Exact parsed JSON value before compact/full normalization. */\n raw: unknown;\n envelope: MatrxStreamEnvelope;\n line: string;\n lineNumber: number;\n atCompletion: boolean;\n}\n\nexport interface MatrxNdjsonFramerOptions {\n /** Malformed JSON is non-fatal, but it must never disappear silently. */\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n /** Valid JSON with no recognized Matrx event envelope is also non-fatal. */\n onUnknownEnvelope?: (value: unknown) => void;\n /** Observe every valid envelope without changing or consuming it. */\n onValidEnvelope?: (observation: MatrxStreamEnvelopeObservation) => void;\n}\n\nexport interface ReadMatrxNdjsonOptions extends MatrxNdjsonFramerOptions {\n signal?: AbortSignal;\n /** Maximum normalized events read ahead of the iterator consumer. */\n maxReadAhead?: number;\n}\n\nexport interface MatrxNdjsonFramer {\n /** Push a text fragment. Fragments may split JSON tokens or line endings. */\n pushText(fragment: string): MatrxStreamEnvelope[];\n /** Push a byte fragment. UTF-8 code points may span calls. */\n pushBytes(fragment: Uint8Array): MatrxStreamEnvelope[];\n /** Parse the final unterminated line and flush any pending UTF-8 bytes. */\n finish(): MatrxStreamEnvelope[];\n}\n\ntype QueueItem =\n | { kind: \"event\"; value: MatrxStreamEnvelope }\n | { kind: \"error\"; error: unknown }\n | { kind: \"done\" };\n\nexport const DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Normalize both supported Matrx wire shapes:\n *\n * - full: `{ \"event\": \"chunk\", \"data\": { \"text\": \"...\" } }`\n * - compact chunk: `{ \"e\": \"c\", \"t\": \"...\" }`\n * - compact reasoning: `{ \"e\": \"r\", \"t\": \"...\" }`\n */\nexport function normalizeMatrxStreamEnvelope(\n value: unknown,\n): MatrxStreamEnvelope | null {\n if (!isRecord(value)) return null;\n\n if (typeof value.event === \"string\") {\n const streamSeq =\n typeof value.stream_seq === \"number\" && Number.isFinite(value.stream_seq)\n ? value.stream_seq\n : undefined;\n return {\n event: value.event,\n data: value.data,\n ...(streamSeq === undefined ? {} : { stream_seq: streamSeq }),\n };\n }\n if (value.e === \"c\" && typeof value.t === \"string\") {\n return { event: \"chunk\", data: { text: value.t } };\n }\n if (value.e === \"r\" && typeof value.t === \"string\") {\n return { event: \"reasoning_chunk\", data: { text: value.t } };\n }\n return null;\n}\n\n/**\n * Create the transport-independent NDJSON framer used by the stream reader.\n * Browser extensions, desktop bridges, WebSockets, and tests can feed it\n * fragmented strings or bytes without constructing a `ReadableStream`.\n */\nexport function createMatrxNdjsonFramer(\n options: MatrxNdjsonFramerOptions = {},\n): MatrxNdjsonFramer {\n const decoder = new TextDecoder();\n let buffer = \"\";\n let lineNumber = 0;\n let finished = false;\n\n const assertOpen = (): void => {\n if (finished) throw new Error(\"Matrx NDJSON framer is already finished\");\n };\n\n const parseLine = (\n line: string,\n atCompletion: boolean,\n ): MatrxStreamEnvelope | null => {\n const currentLineNumber = ++lineNumber;\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed) as unknown;\n } catch (error) {\n options.onMalformedLine?.({\n line: trimmed,\n error,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return null;\n }\n\n const envelope = normalizeMatrxStreamEnvelope(parsed);\n if (!envelope) {\n options.onUnknownEnvelope?.(parsed);\n return null;\n }\n options.onValidEnvelope?.({\n raw: parsed,\n envelope,\n line: trimmed,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return envelope;\n };\n\n const pushDecodedText = (fragment: string): MatrxStreamEnvelope[] => {\n buffer += fragment;\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n const envelopes: MatrxStreamEnvelope[] = [];\n for (const line of lines) {\n const envelope = parseLine(line, false);\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n };\n\n return {\n pushText(fragment) {\n assertOpen();\n // Flush any pending byte fragment before switching to explicit text.\n return pushDecodedText(decoder.decode() + fragment);\n },\n pushBytes(fragment) {\n assertOpen();\n return pushDecodedText(decoder.decode(fragment, { stream: true }));\n },\n finish() {\n assertOpen();\n finished = true;\n const envelopes = pushDecodedText(decoder.decode());\n if (buffer.length > 0) {\n const envelope = parseLine(buffer, true);\n buffer = \"\";\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n },\n };\n}\n\nfunction readAheadLimit(value: number | undefined): number {\n const limit = value ?? DEFAULT_MATRX_NDJSON_READ_AHEAD;\n if (!Number.isSafeInteger(limit) || limit < 1) {\n throw new RangeError(\"maxReadAhead must be a positive safe integer\");\n }\n return limit;\n}\n\n/**\n * Read and normalize a Matrx NDJSON response body with bounded background\n * read-ahead. Consumer work does not stall the network until `maxReadAhead`\n * complete events are waiting; the bound prevents an abandoned or blocked\n * consumer from growing memory without limit.\n */\nexport async function* readMatrxNdjsonStream(\n body: ReadableStream<Uint8Array>,\n options: ReadMatrxNdjsonOptions = {},\n): AsyncGenerator<MatrxStreamEnvelope, void, undefined> {\n const maxReadAhead = readAheadLimit(options.maxReadAhead);\n const queue: QueueItem[] = [];\n let queuedEventCount = 0;\n let wakeConsumer: (() => void) | null = null;\n let wakeProducer: (() => void) | null = null;\n let readerFinished = false;\n let consumerClosed = false;\n\n const wakeWaitingConsumer = (): void => {\n const wake = wakeConsumer;\n wakeConsumer = null;\n wake?.();\n };\n const wakeWaitingProducer = (): void => {\n const wake = wakeProducer;\n wakeProducer = null;\n wake?.();\n };\n const enqueueTerminal = (item: QueueItem): void => {\n if (consumerClosed) return;\n queue.push(item);\n wakeWaitingConsumer();\n };\n const enqueueEvent = async (value: MatrxStreamEnvelope): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n if (consumerClosed || options.signal?.aborted) return false;\n queue.push({ kind: \"event\", value });\n queuedEventCount += 1;\n wakeWaitingConsumer();\n return true;\n };\n const waitForReadCapacity = async (): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n return !consumerClosed && !options.signal?.aborted;\n };\n\n const reader = body.getReader();\n const framer = createMatrxNdjsonFramer(options);\n\n const onAbort = (): void => {\n wakeWaitingProducer();\n wakeWaitingConsumer();\n void reader.cancel(options.signal?.reason).catch(() => undefined);\n };\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (options.signal?.aborted) onAbort();\n\n const readerPromise = (async (): Promise<void> => {\n try {\n while (!options.signal?.aborted && !consumerClosed) {\n if (!(await waitForReadCapacity())) return;\n const { value, done } = await reader.read();\n if (done) break;\n for (const envelope of framer.pushBytes(value)) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n\n if (!options.signal?.aborted && !consumerClosed) {\n for (const envelope of framer.finish()) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n } catch (error) {\n const aborted =\n options.signal?.aborted ||\n consumerClosed ||\n (error instanceof Error && error.name === \"AbortError\");\n if (!aborted) enqueueTerminal({ kind: \"error\", error });\n } finally {\n readerFinished = true;\n reader.releaseLock();\n enqueueTerminal({ kind: \"done\" });\n }\n })();\n\n try {\n while (true) {\n if (queue.length === 0) {\n if (options.signal?.aborted || readerFinished) return;\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n }\n\n const item = queue.shift();\n if (item?.kind === \"event\") queuedEventCount -= 1;\n wakeWaitingProducer();\n if (!item || item.kind === \"done\") return;\n if (item.kind === \"error\") throw item.error;\n yield item.value;\n }\n } finally {\n consumerClosed = true;\n wakeWaitingProducer();\n wakeWaitingConsumer();\n options.signal?.removeEventListener(\"abort\", onAbort);\n if (!readerFinished) {\n await reader.cancel().catch(() => undefined);\n }\n await readerPromise;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../stream/ndjson.ts"],"sourcesContent":["/**\n * Canonical AI Matrx NDJSON wire kernel.\n *\n * This module is deliberately independent of React, Redux, Next.js, Supabase,\n * and generated application types. Every Matrx client uses it to turn the\n * backend's byte stream into the same normalized `{ event, data }` envelopes.\n * Host runtimes remain responsible for HTTP/auth errors and for deciding what\n * each event means in their state model.\n */\n\nexport interface MatrxStreamEnvelope<TData = unknown> {\n event: string;\n /** Immutable emitter segment; transport sequence is scoped to this id. */\n stream_id?: string;\n data: TData;\n /** Monotonic transport sequence from full envelopes, when supplied. */\n stream_seq?: number;\n}\n\nexport interface MatrxNdjsonIssue {\n line: string;\n error: unknown;\n /** One-based physical NDJSON line number. */\n lineNumber: number;\n /** True when an unterminated trailing fragment was parsed by `finish()`. */\n atCompletion: boolean;\n}\n\nexport interface MatrxStreamEnvelopeObservation {\n /** Exact parsed JSON value before compact/full normalization. */\n raw: unknown;\n envelope: MatrxStreamEnvelope;\n line: string;\n lineNumber: number;\n atCompletion: boolean;\n}\n\nexport interface MatrxNdjsonFramerOptions {\n /** Malformed JSON is non-fatal, but it must never disappear silently. */\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n /** Valid JSON with no recognized Matrx event envelope is also non-fatal. */\n onUnknownEnvelope?: (value: unknown) => void;\n /** Observe every valid envelope without changing or consuming it. */\n onValidEnvelope?: (observation: MatrxStreamEnvelopeObservation) => void;\n}\n\nexport interface ReadMatrxNdjsonOptions extends MatrxNdjsonFramerOptions {\n signal?: AbortSignal;\n /** Maximum normalized events read ahead of the iterator consumer. */\n maxReadAhead?: number;\n}\n\nexport interface MatrxNdjsonFramer {\n /** Push a text fragment. Fragments may split JSON tokens or line endings. */\n pushText(fragment: string): MatrxStreamEnvelope[];\n /** Push a byte fragment. UTF-8 code points may span calls. */\n pushBytes(fragment: Uint8Array): MatrxStreamEnvelope[];\n /** Parse the final unterminated line and flush any pending UTF-8 bytes. */\n finish(): MatrxStreamEnvelope[];\n}\n\ntype QueueItem =\n | { kind: \"event\"; value: MatrxStreamEnvelope }\n | { kind: \"error\"; error: unknown }\n | { kind: \"done\" };\n\nexport const DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Normalize both supported Matrx wire shapes:\n *\n * - full: `{ \"event\": \"chunk\", \"data\": { \"text\": \"...\" } }`\n * - compact chunk: `{ \"e\": \"c\", \"t\": \"...\" }`\n * - compact reasoning: `{ \"e\": \"r\", \"t\": \"...\" }`\n */\nexport function normalizeMatrxStreamEnvelope(\n value: unknown,\n): MatrxStreamEnvelope | null {\n if (!isRecord(value)) return null;\n\n if (typeof value.event === \"string\") {\n const streamSeq =\n typeof value.stream_seq === \"number\" && Number.isFinite(value.stream_seq)\n ? value.stream_seq\n : undefined;\n const streamId = typeof value.stream_id === \"string\" ? value.stream_id : undefined;\n return {\n event: value.event,\n data: value.data,\n ...(streamId === undefined ? {} : { stream_id: streamId }),\n ...(streamSeq === undefined ? {} : { stream_seq: streamSeq }),\n };\n }\n if (value.e === \"c\" && typeof value.t === \"string\") {\n const streamId = typeof value.stream_id === \"string\" ? value.stream_id : undefined;\n const streamSeq =\n typeof value.stream_seq === \"number\" && Number.isFinite(value.stream_seq)\n ? value.stream_seq\n : undefined;\n return {\n event: \"chunk\",\n data: { text: value.t },\n ...(streamId === undefined ? {} : { stream_id: streamId }),\n ...(streamSeq === undefined ? {} : { stream_seq: streamSeq }),\n };\n }\n if (value.e === \"r\" && typeof value.t === \"string\") {\n const streamId = typeof value.stream_id === \"string\" ? value.stream_id : undefined;\n const streamSeq =\n typeof value.stream_seq === \"number\" && Number.isFinite(value.stream_seq)\n ? value.stream_seq\n : undefined;\n return {\n event: \"reasoning_chunk\",\n data: { text: value.t },\n ...(streamId === undefined ? {} : { stream_id: streamId }),\n ...(streamSeq === undefined ? {} : { stream_seq: streamSeq }),\n };\n }\n return null;\n}\n\n/**\n * Create the transport-independent NDJSON framer used by the stream reader.\n * Browser extensions, desktop bridges, WebSockets, and tests can feed it\n * fragmented strings or bytes without constructing a `ReadableStream`.\n */\nexport function createMatrxNdjsonFramer(\n options: MatrxNdjsonFramerOptions = {},\n): MatrxNdjsonFramer {\n const decoder = new TextDecoder();\n let buffer = \"\";\n let lineNumber = 0;\n let finished = false;\n\n const assertOpen = (): void => {\n if (finished) throw new Error(\"Matrx NDJSON framer is already finished\");\n };\n\n const parseLine = (\n line: string,\n atCompletion: boolean,\n ): MatrxStreamEnvelope | null => {\n const currentLineNumber = ++lineNumber;\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed) as unknown;\n } catch (error) {\n options.onMalformedLine?.({\n line: trimmed,\n error,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return null;\n }\n\n const envelope = normalizeMatrxStreamEnvelope(parsed);\n if (!envelope) {\n options.onUnknownEnvelope?.(parsed);\n return null;\n }\n options.onValidEnvelope?.({\n raw: parsed,\n envelope,\n line: trimmed,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return envelope;\n };\n\n const pushDecodedText = (fragment: string): MatrxStreamEnvelope[] => {\n buffer += fragment;\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n const envelopes: MatrxStreamEnvelope[] = [];\n for (const line of lines) {\n const envelope = parseLine(line, false);\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n };\n\n return {\n pushText(fragment) {\n assertOpen();\n // Flush any pending byte fragment before switching to explicit text.\n return pushDecodedText(decoder.decode() + fragment);\n },\n pushBytes(fragment) {\n assertOpen();\n return pushDecodedText(decoder.decode(fragment, { stream: true }));\n },\n finish() {\n assertOpen();\n finished = true;\n const envelopes = pushDecodedText(decoder.decode());\n if (buffer.length > 0) {\n const envelope = parseLine(buffer, true);\n buffer = \"\";\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n },\n };\n}\n\nfunction readAheadLimit(value: number | undefined): number {\n const limit = value ?? DEFAULT_MATRX_NDJSON_READ_AHEAD;\n if (!Number.isSafeInteger(limit) || limit < 1) {\n throw new RangeError(\"maxReadAhead must be a positive safe integer\");\n }\n return limit;\n}\n\n/**\n * Read and normalize a Matrx NDJSON response body with bounded background\n * read-ahead. Consumer work does not stall the network until `maxReadAhead`\n * complete events are waiting; the bound prevents an abandoned or blocked\n * consumer from growing memory without limit.\n */\nexport async function* readMatrxNdjsonStream(\n body: ReadableStream<Uint8Array>,\n options: ReadMatrxNdjsonOptions = {},\n): AsyncGenerator<MatrxStreamEnvelope, void, undefined> {\n const maxReadAhead = readAheadLimit(options.maxReadAhead);\n const queue: QueueItem[] = [];\n let queuedEventCount = 0;\n let wakeConsumer: (() => void) | null = null;\n let wakeProducer: (() => void) | null = null;\n let readerFinished = false;\n let consumerClosed = false;\n\n const wakeWaitingConsumer = (): void => {\n const wake = wakeConsumer;\n wakeConsumer = null;\n wake?.();\n };\n const wakeWaitingProducer = (): void => {\n const wake = wakeProducer;\n wakeProducer = null;\n wake?.();\n };\n const enqueueTerminal = (item: QueueItem): void => {\n if (consumerClosed) return;\n queue.push(item);\n wakeWaitingConsumer();\n };\n const enqueueEvent = async (value: MatrxStreamEnvelope): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n if (consumerClosed || options.signal?.aborted) return false;\n queue.push({ kind: \"event\", value });\n queuedEventCount += 1;\n wakeWaitingConsumer();\n return true;\n };\n const waitForReadCapacity = async (): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n return !consumerClosed && !options.signal?.aborted;\n };\n\n const reader = body.getReader();\n const framer = createMatrxNdjsonFramer(options);\n\n const onAbort = (): void => {\n wakeWaitingProducer();\n wakeWaitingConsumer();\n void reader.cancel(options.signal?.reason).catch(() => undefined);\n };\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (options.signal?.aborted) onAbort();\n\n const readerPromise = (async (): Promise<void> => {\n try {\n while (!options.signal?.aborted && !consumerClosed) {\n if (!(await waitForReadCapacity())) return;\n const { value, done } = await reader.read();\n if (done) break;\n for (const envelope of framer.pushBytes(value)) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n\n if (!options.signal?.aborted && !consumerClosed) {\n for (const envelope of framer.finish()) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n } catch (error) {\n const aborted =\n options.signal?.aborted ||\n consumerClosed ||\n (error instanceof Error && error.name === \"AbortError\");\n if (!aborted) enqueueTerminal({ kind: \"error\", error });\n } finally {\n readerFinished = true;\n reader.releaseLock();\n enqueueTerminal({ kind: \"done\" });\n }\n })();\n\n try {\n while (true) {\n if (queue.length === 0) {\n if (options.signal?.aborted || readerFinished) return;\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n }\n\n const item = queue.shift();\n if (item?.kind === \"event\") queuedEventCount -= 1;\n wakeWaitingProducer();\n if (!item || item.kind === \"done\") return;\n if (item.kind === \"error\") throw item.error;\n yield item.value;\n }\n } finally {\n consumerClosed = true;\n wakeWaitingProducer();\n wakeWaitingConsumer();\n options.signal?.removeEventListener(\"abort\", onAbort);\n if (!readerFinished) {\n await reader.cancel().catch(() => undefined);\n }\n await readerPromise;\n }\n}\n"],"mappings":"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","names":[]}