@ai-gui/core 0.28.0 → 0.29.1

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/README.md CHANGED
@@ -108,6 +108,39 @@ const runtime = createActionRuntime({ registry: actions, cardStore })
108
108
 
109
109
  `CardStore` supports initialize-if-absent registration, immutable records, recursive object merge, replace, atomic patch batches, revision checks, subscriptions, delete/clear, and snapshot/restore. Action patch results use optimistic mutation epochs, so an older Action cannot overwrite a Card changed, deleted, recreated, or restored after that Action started.
110
110
 
111
+ ## One stream, many channels
112
+
113
+ `Renderer` is a single-writer append-only buffer: `push` concatenates, and markdown block boundaries do not survive two sources interleaving into them. So anything arriving *alongside* the answer — progress, a background job, a tool that finished late — goes on its own channel and updates a Card by id, in any order, as many times as it likes.
114
+
115
+ ```ts
116
+ import { CardStore, Renderer, StreamRouter, cardChannel } from "@ai-gui/core"
117
+
118
+ const store = new CardStore({ registry })
119
+ const renderer = new Renderer({ plugins, onPatch })
120
+
121
+ await new StreamRouter()
122
+ .channel("content", renderer) // text deltas → the answer
123
+ .on("cards", cardChannel(store, { onError })) // card messages → the store
124
+ .on("usage", (u) => setTokens(u)) // anything else → your callback
125
+ .feed(response.body)
126
+ ```
127
+
128
+ The wire accepts either form, mixed freely in one stream:
129
+
130
+ ```
131
+ {"ch":"content","delta":"Working"}
132
+ {"ch":"cards","data":{"op":"register","id":"job-7","type":"task","data":{"pct":0}}}
133
+ {"ch":"cards","data":{"op":"merge","cardId":"job-7","data":{"pct":60}}}
134
+ event: usage
135
+ data: {"in":120}
136
+ ```
137
+
138
+ `cardChannel` accepts `register`, `merge`, `replace` and `batch`. Not `delete`: a card the reader is looking at should not vanish because a late frame said so — call `store.delete` from your own handler where you can decide.
139
+
140
+ Send `revision` on a patch when a late frame overwriting newer state would be wrong; the store rejects the stale one. Without it, last write wins.
141
+
142
+ Every failure is reported through `onError` rather than thrown, because the handler runs inside one long `feed` await — a throw there would not just drop the card, it would kill the content channel and stop the answer mid-sentence. Leave `onError` unset and failures go to `console.error`; a silently swallowed one is indistinguishable from a card the model never sent.
143
+
111
144
  ## Exports
112
145
 
113
146
  - `Renderer` — `push(chunk)`, `feed(AsyncIterable | ReadableStream)`, `reset()`, `setPlugins(plugins)`; constructor `{ registry?, plugins?, sanitize?, rawHtml?, onPatch?(patches, nodes) }`.
@@ -115,6 +148,7 @@ const runtime = createActionRuntime({ registry: actions, cardStore })
115
148
  - `rawHtml: false` escapes raw HTML the model wrote instead of interpreting it — a stray `<code>` in prose otherwise swallows the rest of the line.
116
149
  - Emphasis is parsed CJK-friendly, one deliberate deviation from CommonMark. CommonMark will not let `**` close when it follows punctuation and precedes a character that is neither whitespace nor punctuation, so `**严格单调(单射)**的函数` renders its asterisks literally. ASCII is unaffected: `a * b * c` and `snake_case_word` parse exactly as before.
117
150
  - `StreamRouter` — demultiplex one stream into named channels: `.channel(name, sink)`, `.on(name, cb)`, `.feed(source)`.
151
+ - `cardChannel(store, { onError? })` — a `StreamRouter` handler that applies `register` / `merge` / `replace` / `batch` messages to a `CardStore`, reporting failures instead of throwing into the feed.
118
152
  - `CardRegistry` — `register(def)`, `parse(type, rawJson)`, `getRender(type)`, `toPromptSpec()`, `toJSONSchema()`.
119
153
  - `CardStore` — `register`, `get`, `list`, `subscribe`, `apply`, `applyAll`, `delete`, `clear`, `snapshot`, and `restore` for Cards with stable IDs.
120
154
  - `ActionRegistry`, `ActionRuntime`, `createActionRuntime`, `getActionKey`, `getIdleActionState` — validated application-owned action execution and observable lifecycle state.
package/dist/index.cjs CHANGED
@@ -1002,6 +1002,82 @@ function safeCall(listener, ...args) {
1002
1002
  } catch {}
1003
1003
  }
1004
1004
 
1005
+ //#endregion
1006
+ //#region src/card-channel.ts
1007
+ const OPS = "register, merge, replace, batch";
1008
+ /**
1009
+ * Apply card messages arriving on a stream channel to a `CardStore`.
1010
+ *
1011
+ * ```ts
1012
+ * new StreamRouter()
1013
+ * .channel("content", renderer)
1014
+ * .on("cards", cardChannel(store))
1015
+ * ```
1016
+ *
1017
+ * This is the parallel half of the design. The content channel is one append-only buffer with a
1018
+ * single writer, because markdown block boundaries cannot survive two sources interleaving into
1019
+ * them. Cards are addressed by id instead, so anything on the wire — a background job, a tool that
1020
+ * finished late, a second model — can update one without touching the text, in any order, as many
1021
+ * times as it likes.
1022
+ *
1023
+ * Ordering is the store's contract, not this adapter's: a patch carrying `revision` is an
1024
+ * optimistic lock and a stale one is rejected through `onError`; a patch without one is
1025
+ * last-write-wins. Send `revision` when a late frame overwriting a newer state would be wrong.
1026
+ *
1027
+ * A card channel carries whole messages, not text deltas — one JSON object per frame.
1028
+ */
1029
+ function cardChannel(store, options = {}) {
1030
+ const report = (error, message) => {
1031
+ if (options.onError) options.onError(error, message);
1032
+ else console.error("[aigui] card channel:", error);
1033
+ };
1034
+ return (value) => {
1035
+ let message = value;
1036
+ if (typeof message === "string") try {
1037
+ message = JSON.parse(message);
1038
+ } catch {
1039
+ report(new TypeError("Card channel received text that is not a JSON message"), value);
1040
+ return;
1041
+ }
1042
+ if (typeof message !== "object" || message === null || Array.isArray(message)) {
1043
+ report(new TypeError("A card message must be a JSON object"), value);
1044
+ return;
1045
+ }
1046
+ const op = message.op;
1047
+ try {
1048
+ if (op === "register") {
1049
+ const { id, type, data } = message;
1050
+ if (typeof id !== "string" || typeof type !== "string") {
1051
+ report(new TypeError("A register message needs a string id and type"), value);
1052
+ return;
1053
+ }
1054
+ store.register({
1055
+ id,
1056
+ type,
1057
+ data
1058
+ });
1059
+ return;
1060
+ }
1061
+ if (isCardPatchResult(message)) {
1062
+ if (message.op === "batch") store.applyAll(message.patches);
1063
+ else store.apply(message);
1064
+ return;
1065
+ }
1066
+ if (op === "merge" || op === "replace") {
1067
+ report(new TypeError(`A ${op} message needs a string cardId and a data field`), value);
1068
+ return;
1069
+ }
1070
+ if (op === "batch") {
1071
+ report(new TypeError("A batch message needs a patches array of merge or replace patches"), value);
1072
+ return;
1073
+ }
1074
+ report(new TypeError(`Card message op must be one of ${OPS}, not ${JSON.stringify(op)}`), value);
1075
+ } catch (error) {
1076
+ report(error, value);
1077
+ }
1078
+ };
1079
+ }
1080
+
1005
1081
  //#endregion
1006
1082
  //#region src/actions.ts
1007
1083
  let nextRuntimeId = 0;
@@ -2919,6 +2995,7 @@ exports.assertPlugins = assertPlugins
2919
2995
  exports.availableLocales = availableLocales
2920
2996
  exports.baseCss = baseCss
2921
2997
  exports.buildSystemPrompt = buildSystemPrompt
2998
+ exports.cardChannel = cardChannel
2922
2999
  exports.collectNodeRenderers = collectNodeRenderers
2923
3000
  exports.collectPluginStyles = collectPluginStyles
2924
3001
  exports.contentDeltas = contentDeltas
package/dist/index.d.cts CHANGED
@@ -463,6 +463,58 @@ declare class CardLimitError extends CardStoreError {}
463
463
  declare class CardSnapshotError extends CardStoreError {}
464
464
  declare function isCardPatchResult(value: unknown): value is CardPatchResult;
465
465
 
466
+ //#endregion
467
+ //#region src/card-channel.d.ts
468
+ /**
469
+ * A message a stream may send on a card channel.
470
+ *
471
+ * `register` first, then any number of patches against that id. Deletion is deliberately not here:
472
+ * a card the reader is looking at, or has already acted on, should not vanish because a late frame
473
+ * said so. A host that wants it calls `store.delete` from its own handler, where it can decide.
474
+ */
475
+ type CardMessage = {
476
+ op: "register";
477
+ id: string;
478
+ type: string;
479
+ data: unknown;
480
+ } | CardPatch | CardPatchBatch;
481
+ interface CardChannelOptions {
482
+ /**
483
+ * Where a message that could not be applied goes.
484
+ *
485
+ * This is the reason the adapter exists rather than being three lines at the call site. The
486
+ * handler runs inside `StreamRouter.feed`, one long await over the whole response — a throw here
487
+ * does not just drop the card, it kills the content channel with it and the answer stops
488
+ * mid-sentence. So every failure is caught, and reported here instead.
489
+ *
490
+ * Left unset, failures go to `console.error`: a swallowed one looks exactly like a card that the
491
+ * model never sent, which is the hardest version of this bug to find.
492
+ */
493
+ onError?: (error: unknown, message: unknown) => void;
494
+ }
495
+ /**
496
+ * Apply card messages arriving on a stream channel to a `CardStore`.
497
+ *
498
+ * ```ts
499
+ * new StreamRouter()
500
+ * .channel("content", renderer)
501
+ * .on("cards", cardChannel(store))
502
+ * ```
503
+ *
504
+ * This is the parallel half of the design. The content channel is one append-only buffer with a
505
+ * single writer, because markdown block boundaries cannot survive two sources interleaving into
506
+ * them. Cards are addressed by id instead, so anything on the wire — a background job, a tool that
507
+ * finished late, a second model — can update one without touching the text, in any order, as many
508
+ * times as it likes.
509
+ *
510
+ * Ordering is the store's contract, not this adapter's: a patch carrying `revision` is an
511
+ * optimistic lock and a stale one is rejected through `onError`; a patch without one is
512
+ * last-write-wins. Send `revision` when a late frame overwriting a newer state would be wrong.
513
+ *
514
+ * A card channel carries whole messages, not text deltas — one JSON object per frame.
515
+ */
516
+ declare function cardChannel(store: CardStore, options?: CardChannelOptions): (value: unknown) => void;
517
+
466
518
  //#endregion
467
519
  //#region src/json-schema.d.ts
468
520
  interface JSONSchemaValidationResult {
@@ -1000,4 +1052,4 @@ declare function mockModelStream(events: Iterable<ModelStreamEvent> | AsyncItera
1000
1052
  declare function readableBytes(chunks: Iterable<string | Uint8Array> | AsyncIterable<string | Uint8Array>): ReadableStream<Uint8Array>;
1001
1053
 
1002
1054
  //#endregion
1003
- export { AIGuiPlugin, ASTNode, ActionAbortedError, ActionAlreadyRegisteredError, ActionContext, ActionDefinition, ActionDestroyedError, ActionDispatchOptions, ActionErrorEvent, ActionEventBase, ActionExecutionError, ActionNotFoundError, ActionOutcome, ActionRegisterOptions, ActionRegistry, ActionRequest, ActionRuntime, ActionRuntimeError, ActionRuntimeOptions, ActionStartEvent, ActionState, ActionStateListener, ActionStatus, ActionSuccessEvent, ActionTimeoutError, ActionValidationError, BuildSystemPromptOptions, ByteStreamSource, CARD_ID_MAX_LENGTH, CARD_JSON_MAX_DEPTH, CARD_JSON_MAX_NODES, CARD_PATCH_BATCH_MAX_SIZE, CardAction, CardActionError, CardDef, CardJSONError, CardLimitError, CardListener, CardNotFoundError, CardParseResult, CardPatch, CardPatchBatch, CardPatchResult, CardRecord, CardRegistry, CardRevisionConflictError, CardSnapshot, CardSnapshotError, CardStore, CardStoreError, CardStoreListener, CardStoreOptions, CardTypeConflictError, CardValidationError, ChannelSink, Citation, CollectNodeRendererOptions, DEFAULT_LOCALE, DebugEmitter, DebugEvent, DebugEventListener, DebugEventTarget, DebugInstrumentationTarget, DebugOptions, DebugRedactContext, DebugSource, ExportImageOptions, ExportedImage, FeedChunk, FeedOptions, FeedSource, JSONSchema, JSONSchemaValidationResult, Locale, MessageBundle, Messages, ModelStreamEvent, MountCardSlotRequest, MountedCardSlot, NodeRenderContext, NodeRenderer, OutcomeTone, ParseResult, ParserOptions, PartialJSONResult, Patch, PluginCommitContext, PluginSource, PluginStyle, PluginsLoader, RenderMountContext, RenderOutput, Renderer, RendererOptions, SSEEvent, SSEOptions, SafeDebugValueOptions, SanitizeHtmlOptions, SanitizeSetting, SourceBlock, StreamParseOptions, StreamRouter, Usage, actionOutcome, applyPatches, assertPlugins, availableLocales, baseCss, buildSystemPrompt, collectNodeRenderers, collectPluginStyles, contentDeltas, createActionRuntime, createParser, createParserWithMetadata, diffAst, downloadImage, exportRenderedImages, exportSVGToImage, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, loadPlugins, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, samePlugins, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };
1055
+ export { AIGuiPlugin, ASTNode, ActionAbortedError, ActionAlreadyRegisteredError, ActionContext, ActionDefinition, ActionDestroyedError, ActionDispatchOptions, ActionErrorEvent, ActionEventBase, ActionExecutionError, ActionNotFoundError, ActionOutcome, ActionRegisterOptions, ActionRegistry, ActionRequest, ActionRuntime, ActionRuntimeError, ActionRuntimeOptions, ActionStartEvent, ActionState, ActionStateListener, ActionStatus, ActionSuccessEvent, ActionTimeoutError, ActionValidationError, BuildSystemPromptOptions, ByteStreamSource, CARD_ID_MAX_LENGTH, CARD_JSON_MAX_DEPTH, CARD_JSON_MAX_NODES, CARD_PATCH_BATCH_MAX_SIZE, CardAction, CardActionError, CardChannelOptions, CardDef, CardJSONError, CardLimitError, CardListener, CardMessage, CardNotFoundError, CardParseResult, CardPatch, CardPatchBatch, CardPatchResult, CardRecord, CardRegistry, CardRevisionConflictError, CardSnapshot, CardSnapshotError, CardStore, CardStoreError, CardStoreListener, CardStoreOptions, CardTypeConflictError, CardValidationError, ChannelSink, Citation, CollectNodeRendererOptions, DEFAULT_LOCALE, DebugEmitter, DebugEvent, DebugEventListener, DebugEventTarget, DebugInstrumentationTarget, DebugOptions, DebugRedactContext, DebugSource, ExportImageOptions, ExportedImage, FeedChunk, FeedOptions, FeedSource, JSONSchema, JSONSchemaValidationResult, Locale, MessageBundle, Messages, ModelStreamEvent, MountCardSlotRequest, MountedCardSlot, NodeRenderContext, NodeRenderer, OutcomeTone, ParseResult, ParserOptions, PartialJSONResult, Patch, PluginCommitContext, PluginSource, PluginStyle, PluginsLoader, RenderMountContext, RenderOutput, Renderer, RendererOptions, SSEEvent, SSEOptions, SafeDebugValueOptions, SanitizeHtmlOptions, SanitizeSetting, SourceBlock, StreamParseOptions, StreamRouter, Usage, actionOutcome, applyPatches, assertPlugins, availableLocales, baseCss, buildSystemPrompt, cardChannel, collectNodeRenderers, collectPluginStyles, contentDeltas, createActionRuntime, createParser, createParserWithMetadata, diffAst, downloadImage, exportRenderedImages, exportSVGToImage, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, loadPlugins, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, samePlugins, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };
package/dist/index.d.ts CHANGED
@@ -463,6 +463,58 @@ declare class CardLimitError extends CardStoreError {}
463
463
  declare class CardSnapshotError extends CardStoreError {}
464
464
  declare function isCardPatchResult(value: unknown): value is CardPatchResult;
465
465
 
466
+ //#endregion
467
+ //#region src/card-channel.d.ts
468
+ /**
469
+ * A message a stream may send on a card channel.
470
+ *
471
+ * `register` first, then any number of patches against that id. Deletion is deliberately not here:
472
+ * a card the reader is looking at, or has already acted on, should not vanish because a late frame
473
+ * said so. A host that wants it calls `store.delete` from its own handler, where it can decide.
474
+ */
475
+ type CardMessage = {
476
+ op: "register";
477
+ id: string;
478
+ type: string;
479
+ data: unknown;
480
+ } | CardPatch | CardPatchBatch;
481
+ interface CardChannelOptions {
482
+ /**
483
+ * Where a message that could not be applied goes.
484
+ *
485
+ * This is the reason the adapter exists rather than being three lines at the call site. The
486
+ * handler runs inside `StreamRouter.feed`, one long await over the whole response — a throw here
487
+ * does not just drop the card, it kills the content channel with it and the answer stops
488
+ * mid-sentence. So every failure is caught, and reported here instead.
489
+ *
490
+ * Left unset, failures go to `console.error`: a swallowed one looks exactly like a card that the
491
+ * model never sent, which is the hardest version of this bug to find.
492
+ */
493
+ onError?: (error: unknown, message: unknown) => void;
494
+ }
495
+ /**
496
+ * Apply card messages arriving on a stream channel to a `CardStore`.
497
+ *
498
+ * ```ts
499
+ * new StreamRouter()
500
+ * .channel("content", renderer)
501
+ * .on("cards", cardChannel(store))
502
+ * ```
503
+ *
504
+ * This is the parallel half of the design. The content channel is one append-only buffer with a
505
+ * single writer, because markdown block boundaries cannot survive two sources interleaving into
506
+ * them. Cards are addressed by id instead, so anything on the wire — a background job, a tool that
507
+ * finished late, a second model — can update one without touching the text, in any order, as many
508
+ * times as it likes.
509
+ *
510
+ * Ordering is the store's contract, not this adapter's: a patch carrying `revision` is an
511
+ * optimistic lock and a stale one is rejected through `onError`; a patch without one is
512
+ * last-write-wins. Send `revision` when a late frame overwriting a newer state would be wrong.
513
+ *
514
+ * A card channel carries whole messages, not text deltas — one JSON object per frame.
515
+ */
516
+ declare function cardChannel(store: CardStore, options?: CardChannelOptions): (value: unknown) => void;
517
+
466
518
  //#endregion
467
519
  //#region src/json-schema.d.ts
468
520
  interface JSONSchemaValidationResult {
@@ -1000,4 +1052,4 @@ declare function mockModelStream(events: Iterable<ModelStreamEvent> | AsyncItera
1000
1052
  declare function readableBytes(chunks: Iterable<string | Uint8Array> | AsyncIterable<string | Uint8Array>): ReadableStream<Uint8Array>;
1001
1053
 
1002
1054
  //#endregion
1003
- export { AIGuiPlugin, ASTNode, ActionAbortedError, ActionAlreadyRegisteredError, ActionContext, ActionDefinition, ActionDestroyedError, ActionDispatchOptions, ActionErrorEvent, ActionEventBase, ActionExecutionError, ActionNotFoundError, ActionOutcome, ActionRegisterOptions, ActionRegistry, ActionRequest, ActionRuntime, ActionRuntimeError, ActionRuntimeOptions, ActionStartEvent, ActionState, ActionStateListener, ActionStatus, ActionSuccessEvent, ActionTimeoutError, ActionValidationError, BuildSystemPromptOptions, ByteStreamSource, CARD_ID_MAX_LENGTH, CARD_JSON_MAX_DEPTH, CARD_JSON_MAX_NODES, CARD_PATCH_BATCH_MAX_SIZE, CardAction, CardActionError, CardDef, CardJSONError, CardLimitError, CardListener, CardNotFoundError, CardParseResult, CardPatch, CardPatchBatch, CardPatchResult, CardRecord, CardRegistry, CardRevisionConflictError, CardSnapshot, CardSnapshotError, CardStore, CardStoreError, CardStoreListener, CardStoreOptions, CardTypeConflictError, CardValidationError, ChannelSink, Citation, CollectNodeRendererOptions, DEFAULT_LOCALE, DebugEmitter, DebugEvent, DebugEventListener, DebugEventTarget, DebugInstrumentationTarget, DebugOptions, DebugRedactContext, DebugSource, ExportImageOptions, ExportedImage, FeedChunk, FeedOptions, FeedSource, JSONSchema, JSONSchemaValidationResult, Locale, MessageBundle, Messages, ModelStreamEvent, MountCardSlotRequest, MountedCardSlot, NodeRenderContext, NodeRenderer, OutcomeTone, ParseResult, ParserOptions, PartialJSONResult, Patch, PluginCommitContext, PluginSource, PluginStyle, PluginsLoader, RenderMountContext, RenderOutput, Renderer, RendererOptions, SSEEvent, SSEOptions, SafeDebugValueOptions, SanitizeHtmlOptions, SanitizeSetting, SourceBlock, StreamParseOptions, StreamRouter, Usage, actionOutcome, applyPatches, assertPlugins, availableLocales, baseCss, buildSystemPrompt, collectNodeRenderers, collectPluginStyles, contentDeltas, createActionRuntime, createParser, createParserWithMetadata, diffAst, downloadImage, exportRenderedImages, exportSVGToImage, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, loadPlugins, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, samePlugins, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };
1055
+ export { AIGuiPlugin, ASTNode, ActionAbortedError, ActionAlreadyRegisteredError, ActionContext, ActionDefinition, ActionDestroyedError, ActionDispatchOptions, ActionErrorEvent, ActionEventBase, ActionExecutionError, ActionNotFoundError, ActionOutcome, ActionRegisterOptions, ActionRegistry, ActionRequest, ActionRuntime, ActionRuntimeError, ActionRuntimeOptions, ActionStartEvent, ActionState, ActionStateListener, ActionStatus, ActionSuccessEvent, ActionTimeoutError, ActionValidationError, BuildSystemPromptOptions, ByteStreamSource, CARD_ID_MAX_LENGTH, CARD_JSON_MAX_DEPTH, CARD_JSON_MAX_NODES, CARD_PATCH_BATCH_MAX_SIZE, CardAction, CardActionError, CardChannelOptions, CardDef, CardJSONError, CardLimitError, CardListener, CardMessage, CardNotFoundError, CardParseResult, CardPatch, CardPatchBatch, CardPatchResult, CardRecord, CardRegistry, CardRevisionConflictError, CardSnapshot, CardSnapshotError, CardStore, CardStoreError, CardStoreListener, CardStoreOptions, CardTypeConflictError, CardValidationError, ChannelSink, Citation, CollectNodeRendererOptions, DEFAULT_LOCALE, DebugEmitter, DebugEvent, DebugEventListener, DebugEventTarget, DebugInstrumentationTarget, DebugOptions, DebugRedactContext, DebugSource, ExportImageOptions, ExportedImage, FeedChunk, FeedOptions, FeedSource, JSONSchema, JSONSchemaValidationResult, Locale, MessageBundle, Messages, ModelStreamEvent, MountCardSlotRequest, MountedCardSlot, NodeRenderContext, NodeRenderer, OutcomeTone, ParseResult, ParserOptions, PartialJSONResult, Patch, PluginCommitContext, PluginSource, PluginStyle, PluginsLoader, RenderMountContext, RenderOutput, Renderer, RendererOptions, SSEEvent, SSEOptions, SafeDebugValueOptions, SanitizeHtmlOptions, SanitizeSetting, SourceBlock, StreamParseOptions, StreamRouter, Usage, actionOutcome, applyPatches, assertPlugins, availableLocales, baseCss, buildSystemPrompt, cardChannel, collectNodeRenderers, collectPluginStyles, contentDeltas, createActionRuntime, createParser, createParserWithMetadata, diffAst, downloadImage, exportRenderedImages, exportSVGToImage, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, loadPlugins, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, samePlugins, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };
package/dist/index.js CHANGED
@@ -978,6 +978,82 @@ function safeCall(listener, ...args) {
978
978
  } catch {}
979
979
  }
980
980
 
981
+ //#endregion
982
+ //#region src/card-channel.ts
983
+ const OPS = "register, merge, replace, batch";
984
+ /**
985
+ * Apply card messages arriving on a stream channel to a `CardStore`.
986
+ *
987
+ * ```ts
988
+ * new StreamRouter()
989
+ * .channel("content", renderer)
990
+ * .on("cards", cardChannel(store))
991
+ * ```
992
+ *
993
+ * This is the parallel half of the design. The content channel is one append-only buffer with a
994
+ * single writer, because markdown block boundaries cannot survive two sources interleaving into
995
+ * them. Cards are addressed by id instead, so anything on the wire — a background job, a tool that
996
+ * finished late, a second model — can update one without touching the text, in any order, as many
997
+ * times as it likes.
998
+ *
999
+ * Ordering is the store's contract, not this adapter's: a patch carrying `revision` is an
1000
+ * optimistic lock and a stale one is rejected through `onError`; a patch without one is
1001
+ * last-write-wins. Send `revision` when a late frame overwriting a newer state would be wrong.
1002
+ *
1003
+ * A card channel carries whole messages, not text deltas — one JSON object per frame.
1004
+ */
1005
+ function cardChannel(store, options = {}) {
1006
+ const report = (error, message) => {
1007
+ if (options.onError) options.onError(error, message);
1008
+ else console.error("[aigui] card channel:", error);
1009
+ };
1010
+ return (value) => {
1011
+ let message = value;
1012
+ if (typeof message === "string") try {
1013
+ message = JSON.parse(message);
1014
+ } catch {
1015
+ report(new TypeError("Card channel received text that is not a JSON message"), value);
1016
+ return;
1017
+ }
1018
+ if (typeof message !== "object" || message === null || Array.isArray(message)) {
1019
+ report(new TypeError("A card message must be a JSON object"), value);
1020
+ return;
1021
+ }
1022
+ const op = message.op;
1023
+ try {
1024
+ if (op === "register") {
1025
+ const { id, type, data } = message;
1026
+ if (typeof id !== "string" || typeof type !== "string") {
1027
+ report(new TypeError("A register message needs a string id and type"), value);
1028
+ return;
1029
+ }
1030
+ store.register({
1031
+ id,
1032
+ type,
1033
+ data
1034
+ });
1035
+ return;
1036
+ }
1037
+ if (isCardPatchResult(message)) {
1038
+ if (message.op === "batch") store.applyAll(message.patches);
1039
+ else store.apply(message);
1040
+ return;
1041
+ }
1042
+ if (op === "merge" || op === "replace") {
1043
+ report(new TypeError(`A ${op} message needs a string cardId and a data field`), value);
1044
+ return;
1045
+ }
1046
+ if (op === "batch") {
1047
+ report(new TypeError("A batch message needs a patches array of merge or replace patches"), value);
1048
+ return;
1049
+ }
1050
+ report(new TypeError(`Card message op must be one of ${OPS}, not ${JSON.stringify(op)}`), value);
1051
+ } catch (error) {
1052
+ report(error, value);
1053
+ }
1054
+ };
1055
+ }
1056
+
981
1057
  //#endregion
982
1058
  //#region src/actions.ts
983
1059
  let nextRuntimeId = 0;
@@ -2861,4 +2937,4 @@ function delay(ms, signal) {
2861
2937
  }
2862
2938
 
2863
2939
  //#endregion
2864
- export { ActionAbortedError, ActionAlreadyRegisteredError, ActionDestroyedError, ActionExecutionError, ActionNotFoundError, ActionRegistry, ActionRuntime, ActionRuntimeError, ActionTimeoutError, ActionValidationError, CARD_ID_MAX_LENGTH, CARD_JSON_MAX_DEPTH, CARD_JSON_MAX_NODES, CARD_PATCH_BATCH_MAX_SIZE, CardJSONError, CardLimitError, CardNotFoundError, CardRegistry, CardRevisionConflictError, CardSnapshotError, CardStore, CardStoreError, CardTypeConflictError, CardValidationError, DEFAULT_LOCALE, DebugEmitter, Renderer, StreamRouter, actionOutcome, applyPatches, assertPlugins, availableLocales, baseCss, buildSystemPrompt, collectNodeRenderers, collectPluginStyles, contentDeltas, createActionRuntime, createParser, createParserWithMetadata, diffAst, downloadImage, exportRenderedImages, exportSVGToImage, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, loadPlugins, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, samePlugins, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };
2940
+ export { ActionAbortedError, ActionAlreadyRegisteredError, ActionDestroyedError, ActionExecutionError, ActionNotFoundError, ActionRegistry, ActionRuntime, ActionRuntimeError, ActionTimeoutError, ActionValidationError, CARD_ID_MAX_LENGTH, CARD_JSON_MAX_DEPTH, CARD_JSON_MAX_NODES, CARD_PATCH_BATCH_MAX_SIZE, CardJSONError, CardLimitError, CardNotFoundError, CardRegistry, CardRevisionConflictError, CardSnapshotError, CardStore, CardStoreError, CardTypeConflictError, CardValidationError, DEFAULT_LOCALE, DebugEmitter, Renderer, StreamRouter, actionOutcome, applyPatches, assertPlugins, availableLocales, baseCss, buildSystemPrompt, cardChannel, collectNodeRenderers, collectPluginStyles, contentDeltas, createActionRuntime, createParser, createParserWithMetadata, diffAst, downloadImage, exportRenderedImages, exportSVGToImage, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, loadPlugins, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, samePlugins, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-gui/core",
3
- "version": "0.28.0",
3
+ "version": "0.29.1",
4
4
  "description": "Headless streaming renderer core for LLM-generated UI — markdown/JSON repair, card registry, AST diff, plugin engine, sanitizer.",
5
5
  "keywords": [
6
6
  "llm",