@ai-gui/core 0.21.0 → 0.22.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/README.md CHANGED
@@ -110,12 +110,15 @@ const runtime = createActionRuntime({ registry: actions, cardStore })
110
110
 
111
111
  ## Exports
112
112
 
113
- - `Renderer` — `push(chunk)`, `feed(AsyncIterable | ReadableStream)`, `reset()`; constructor `{ registry?, plugins?, sanitize?, onPatch?(patches, nodes) }`.
113
+ - `Renderer` — `push(chunk)`, `feed(AsyncIterable | ReadableStream)`, `reset()`, `setPlugins(plugins)`; constructor `{ registry?, plugins?, sanitize?, rawHtml?, onPatch?(patches, nodes) }`.
114
+ - `setPlugins` swaps the grammar and reparses the buffered source, so plugins deferred behind a dynamic import can arrive mid-answer without the host replaying what it pushed.
115
+ - `rawHtml: false` escapes raw HTML the model wrote instead of interpreting it — a stray `<code>` in prose otherwise swallows the rest of the line.
114
116
  - `StreamRouter` — demultiplex one stream into named channels: `.channel(name, sink)`, `.on(name, cb)`, `.feed(source)`.
115
117
  - `CardRegistry` — `register(def)`, `parse(type, rawJson)`, `getRender(type)`, `toPromptSpec()`, `toJSONSchema()`.
116
118
  - `CardStore` — `register`, `get`, `list`, `subscribe`, `apply`, `applyAll`, `delete`, `clear`, `snapshot`, and `restore` for Cards with stable IDs.
117
119
  - `ActionRegistry`, `ActionRuntime`, `createActionRuntime`, `getActionKey`, `getIdleActionState` — validated application-owned action execution and observable lifecycle state.
118
- - `buildSystemPrompt({ base?, registry?, plugins? })`.
120
+ - `buildSystemPrompt({ base?, registry?, plugins?, locale? })` — collects the card specs and every plugin's `promptSpec`. Pass `plugins`: installing a plugin teaches the renderer to draw a block, not the model to ask for one.
121
+ - `loadPlugins(source)` / `samePlugins(a, b)` — normalize `AIGuiPlugin[] | (() => Promise<AIGuiPlugin[]>)` and compare two lists by members.
119
122
  - Utilities: `parsePartialJSON`, `repairMarkdown`, `sanitizeHtml`, `createParser`, `diffAst`, `collectNodeRenderers`.
120
123
  - Types: `ASTNode`, `Patch`, `RenderOutput` (`html | element | card | mount`), `CardDef`, `AIGuiPlugin`, `NodeRenderer`, `RendererOptions`, `JSONSchema`.
121
124
 
package/dist/index.cjs CHANGED
@@ -1443,6 +1443,28 @@ function settleCardAction(settle) {
1443
1443
 
1444
1444
  //#endregion
1445
1445
  //#region src/plugins.ts
1446
+ /**
1447
+ * Resolve a plugin source to what the caller can act on now: the array itself, or a promise of it.
1448
+ *
1449
+ * The array form must stay synchronous. Deferring it by a microtask would render the first chunk
1450
+ * of every answer under the plain-markdown grammar and then redraw it, which is a visible flash
1451
+ * for a host that had its plugins all along.
1452
+ */
1453
+ function loadPlugins(source) {
1454
+ if (!source) return [];
1455
+ return Array.isArray(source) ? source : source();
1456
+ }
1457
+ /**
1458
+ * Whether two lists hold the same plugins in the same order.
1459
+ *
1460
+ * `plugins={[chart, katex]}` is a new array on every render and the same two plugins every time.
1461
+ * What matters is the members, not the array.
1462
+ */
1463
+ function samePlugins(a, b) {
1464
+ if (a === b) return true;
1465
+ if (!a || !b || a.length !== b.length) return false;
1466
+ return a.every((plugin, index) => plugin === b[index]);
1467
+ }
1446
1468
  /** Merge every plugin's `nodeRenderers` into a single map (later plugins win). */
1447
1469
  function collectNodeRenderers(plugins = [], debugOptions = {}) {
1448
1470
  const map = {};
@@ -1529,7 +1551,7 @@ function createParser(options = {}) {
1529
1551
  /** Build a parser that also reports the source range for each top-level block. */
1530
1552
  function createParserWithMetadata(options = {}) {
1531
1553
  const md = new markdown_it.default({
1532
- html: true,
1554
+ html: options.rawHtml !== false,
1533
1555
  linkify: true
1534
1556
  });
1535
1557
  options.configureMd?.(md);
@@ -2047,12 +2069,51 @@ var Renderer = class {
2047
2069
  this.options = options;
2048
2070
  this.debug = new DebugEmitter(this.debugSource, options);
2049
2071
  this.sanitize = options.sanitize === false ? false : typeof options.sanitize === "object" ? options.sanitize : {};
2050
- if (options.registry) for (const plugin of options.plugins ?? []) for (const card of plugin.cards ?? []) options.registry.register(card);
2072
+ this.registerPluginCards(options.plugins);
2051
2073
  this.parse = createParserWithMetadata({
2052
2074
  registry: options.registry,
2053
- plugins: options.plugins
2075
+ plugins: options.plugins,
2076
+ rawHtml: options.rawHtml
2054
2077
  });
2055
2078
  }
2079
+ /** The plugins currently parsing and rendering this renderer's output. */
2080
+ get plugins() {
2081
+ return this.options.plugins ?? [];
2082
+ }
2083
+ /**
2084
+ * Swap the plugins in and redraw the answer already buffered.
2085
+ *
2086
+ * A plugin bundle is worth deferring — diagrams, maths and charts together outweigh everything
2087
+ * else a page loads — but the stream does not wait for the import, so whatever arrived meanwhile
2088
+ * was parsed under the plain-markdown grammar. Reparsing the buffer this renderer still holds is
2089
+ * what turns that text into diagrams, which is why a host does not have to remember what it
2090
+ * pushed and replay it once the chunk lands.
2091
+ *
2092
+ * Passing the same plugins again is a no-op, so a host may call this on every render.
2093
+ */
2094
+ setPlugins(plugins) {
2095
+ if (samePlugins(this.options.plugins, plugins)) return;
2096
+ this.options = {
2097
+ ...this.options,
2098
+ plugins
2099
+ };
2100
+ this.registerPluginCards(plugins);
2101
+ this.parse = createParserWithMetadata({
2102
+ registry: this.options.registry,
2103
+ plugins,
2104
+ rawHtml: this.options.rawHtml
2105
+ });
2106
+ this.parsed = void 0;
2107
+ this.renderScheduled = false;
2108
+ this.scheduleGeneration++;
2109
+ if (this.debug.active) this.debug.emit("plugins-changed", { plugins: (plugins ?? []).map((plugin) => plugin.name) });
2110
+ this.render();
2111
+ }
2112
+ registerPluginCards(plugins) {
2113
+ const registry = this.options.registry;
2114
+ if (!registry) return;
2115
+ for (const plugin of plugins ?? []) for (const card of plugin.cards ?? []) registry.register(card);
2116
+ }
2056
2117
  get debugEnabled() {
2057
2118
  return this.debug.available;
2058
2119
  }
@@ -2844,6 +2905,7 @@ exports.getIdleActionState = getIdleActionState
2844
2905
  exports.injectPluginStyles = injectPluginStyles
2845
2906
  exports.isCardPatchResult = isCardPatchResult
2846
2907
  exports.jsonLines = jsonLines
2908
+ exports.loadPlugins = loadPlugins
2847
2909
  exports.mockModelStream = mockModelStream
2848
2910
  exports.ndjson = ndjson
2849
2911
  exports.parsePartialJSON = parsePartialJSON
@@ -2853,6 +2915,7 @@ exports.readableBytes = readableBytes
2853
2915
  exports.repairMarkdown = repairMarkdown
2854
2916
  exports.resolveMessages = resolveMessages
2855
2917
  exports.safeDebugValue = safeDebugValue
2918
+ exports.samePlugins = samePlugins
2856
2919
  exports.sanitizeHtml = sanitizeHtml
2857
2920
  exports.sanitizeRenderedHtml = sanitizeRenderedHtml
2858
2921
  exports.textLines = textLines
package/dist/index.d.cts CHANGED
@@ -265,6 +265,17 @@ interface RendererOptions extends DebugOptions {
265
265
  registry?: CardRegistry;
266
266
  plugins?: AIGuiPlugin[];
267
267
  sanitize?: boolean | SanitizeHtmlOptions;
268
+ /**
269
+ * Whether raw HTML in the model's output is interpreted as markup. On by default.
270
+ *
271
+ * A tag a model wrote inside prose is usually text it is describing, not markup it means: one
272
+ * stray `<code>` in a sentence about code swallows the rest of the line into an element. Turning
273
+ * this off escapes every tag the model writes and shows the characters instead, which is what a
274
+ * product wants when the model is meant to produce markdown and nothing else. It is not a
275
+ * substitute for `sanitize` — a plugin's own markup and the host's cards are unaffected either
276
+ * way.
277
+ */
278
+ rawHtml?: boolean;
268
279
  /** Coalesce multiple pushes by scheduling one render callback. */
269
280
  scheduler?: (render: () => void) => void;
270
281
  onPatch?: (patches: Patch[], nodes: ASTNode[]) => void;
@@ -614,6 +625,17 @@ interface ParserOptions {
614
625
  registry?: CardRegistry;
615
626
  plugins?: AIGuiPlugin[];
616
627
  configureMd?: (md: MarkdownIt) => void;
628
+ /**
629
+ * Whether raw HTML in the model's output is interpreted as markup. On by default.
630
+ *
631
+ * A model writing *about* code emits tags in prose it never meant as markup — a line like
632
+ * `return "done\n<code>"` is text, but interpreting it swallows everything after the tag into an
633
+ * element and the rest of the sentence lands outside it. Sanitizing does not help: `<code>` is a
634
+ * tag any allowlist keeps. Turn this off and every tag the model writes is escaped and shown as
635
+ * the characters it wrote, so a model that gets HTML wrong produces an ugly line rather than a
636
+ * mangled answer.
637
+ */
638
+ rawHtml?: boolean;
617
639
  }
618
640
  interface SourceBlock {
619
641
  start: number;
@@ -636,6 +658,32 @@ declare function createParserWithMetadata(options?: ParserOptions): (src: string
636
658
  interface CollectNodeRendererOptions extends DebugOptions {
637
659
  debugTarget?: DebugInstrumentationTarget;
638
660
  }
661
+ /** A function that produces the plugins, loading them first if they are not in the bundle yet. */
662
+ type PluginsLoader = () => AIGuiPlugin[] | Promise<AIGuiPlugin[]>;
663
+ /**
664
+ * Either the plugins themselves or a function that loads them.
665
+ *
666
+ * Diagrams, maths and charts are the heaviest thing a page carrying them loads, and an answer that
667
+ * never draws one should not pay for them. A loader lets the host defer the import: the renderer
668
+ * shows plain markdown until it resolves and reparses what has arrived by then, so the host does
669
+ * not have to hold the stream or replay it.
670
+ */
671
+ type PluginSource = AIGuiPlugin[] | PluginsLoader;
672
+ /**
673
+ * Resolve a plugin source to what the caller can act on now: the array itself, or a promise of it.
674
+ *
675
+ * The array form must stay synchronous. Deferring it by a microtask would render the first chunk
676
+ * of every answer under the plain-markdown grammar and then redraw it, which is a visible flash
677
+ * for a host that had its plugins all along.
678
+ */
679
+ declare function loadPlugins(source?: PluginSource): AIGuiPlugin[] | Promise<AIGuiPlugin[]>;
680
+ /**
681
+ * Whether two lists hold the same plugins in the same order.
682
+ *
683
+ * `plugins={[chart, katex]}` is a new array on every render and the same two plugins every time.
684
+ * What matters is the members, not the array.
685
+ */
686
+ declare function samePlugins(a?: AIGuiPlugin[], b?: AIGuiPlugin[]): boolean;
639
687
  /** Merge every plugin's `nodeRenderers` into a single map (later plugins win). */
640
688
  declare function collectNodeRenderers(plugins?: AIGuiPlugin[], debugOptions?: CollectNodeRendererOptions): Record<string, NodeRenderer>;
641
689
  /** The set of node types claimed by the given plugins. */
@@ -798,6 +846,21 @@ declare class Renderer {
798
846
  private scheduleGeneration;
799
847
  private readonly debug;
800
848
  constructor(options?: RendererOptions);
849
+ /** The plugins currently parsing and rendering this renderer's output. */
850
+ get plugins(): readonly AIGuiPlugin[];
851
+ /**
852
+ * Swap the plugins in and redraw the answer already buffered.
853
+ *
854
+ * A plugin bundle is worth deferring — diagrams, maths and charts together outweigh everything
855
+ * else a page loads — but the stream does not wait for the import, so whatever arrived meanwhile
856
+ * was parsed under the plain-markdown grammar. Reparsing the buffer this renderer still holds is
857
+ * what turns that text into diagrams, which is why a host does not have to remember what it
858
+ * pushed and replay it once the chunk lands.
859
+ *
860
+ * Passing the same plugins again is a no-op, so a host may call this on every render.
861
+ */
862
+ setPlugins(plugins: AIGuiPlugin[] | undefined): void;
863
+ private registerPluginCards;
801
864
  get debugEnabled(): boolean;
802
865
  emitDebug(type: string, data?: Record<string, unknown>): void;
803
866
  push(chunk: string): void;
@@ -922,4 +985,4 @@ declare function mockModelStream(events: Iterable<ModelStreamEvent> | AsyncItera
922
985
  declare function readableBytes(chunks: Iterable<string | Uint8Array> | AsyncIterable<string | Uint8Array>): ReadableStream<Uint8Array>;
923
986
 
924
987
  //#endregion
925
- 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, PluginStyle, RenderMountContext, RenderOutput, Renderer, RendererOptions, SSEEvent, SSEOptions, SafeDebugValueOptions, SanitizeHtmlOptions, SanitizeSetting, SourceBlock, StreamParseOptions, StreamRouter, Usage, actionOutcome, applyPatches, availableLocales, baseCss, buildSystemPrompt, collectNodeRenderers, collectPluginStyles, contentDeltas, createActionRuntime, createParser, createParserWithMetadata, diffAst, downloadImage, exportRenderedImages, exportSVGToImage, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };
988
+ 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, 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 };
package/dist/index.d.ts CHANGED
@@ -265,6 +265,17 @@ interface RendererOptions extends DebugOptions {
265
265
  registry?: CardRegistry;
266
266
  plugins?: AIGuiPlugin[];
267
267
  sanitize?: boolean | SanitizeHtmlOptions;
268
+ /**
269
+ * Whether raw HTML in the model's output is interpreted as markup. On by default.
270
+ *
271
+ * A tag a model wrote inside prose is usually text it is describing, not markup it means: one
272
+ * stray `<code>` in a sentence about code swallows the rest of the line into an element. Turning
273
+ * this off escapes every tag the model writes and shows the characters instead, which is what a
274
+ * product wants when the model is meant to produce markdown and nothing else. It is not a
275
+ * substitute for `sanitize` — a plugin's own markup and the host's cards are unaffected either
276
+ * way.
277
+ */
278
+ rawHtml?: boolean;
268
279
  /** Coalesce multiple pushes by scheduling one render callback. */
269
280
  scheduler?: (render: () => void) => void;
270
281
  onPatch?: (patches: Patch[], nodes: ASTNode[]) => void;
@@ -614,6 +625,17 @@ interface ParserOptions {
614
625
  registry?: CardRegistry;
615
626
  plugins?: AIGuiPlugin[];
616
627
  configureMd?: (md: MarkdownIt) => void;
628
+ /**
629
+ * Whether raw HTML in the model's output is interpreted as markup. On by default.
630
+ *
631
+ * A model writing *about* code emits tags in prose it never meant as markup — a line like
632
+ * `return "done\n<code>"` is text, but interpreting it swallows everything after the tag into an
633
+ * element and the rest of the sentence lands outside it. Sanitizing does not help: `<code>` is a
634
+ * tag any allowlist keeps. Turn this off and every tag the model writes is escaped and shown as
635
+ * the characters it wrote, so a model that gets HTML wrong produces an ugly line rather than a
636
+ * mangled answer.
637
+ */
638
+ rawHtml?: boolean;
617
639
  }
618
640
  interface SourceBlock {
619
641
  start: number;
@@ -636,6 +658,32 @@ declare function createParserWithMetadata(options?: ParserOptions): (src: string
636
658
  interface CollectNodeRendererOptions extends DebugOptions {
637
659
  debugTarget?: DebugInstrumentationTarget;
638
660
  }
661
+ /** A function that produces the plugins, loading them first if they are not in the bundle yet. */
662
+ type PluginsLoader = () => AIGuiPlugin[] | Promise<AIGuiPlugin[]>;
663
+ /**
664
+ * Either the plugins themselves or a function that loads them.
665
+ *
666
+ * Diagrams, maths and charts are the heaviest thing a page carrying them loads, and an answer that
667
+ * never draws one should not pay for them. A loader lets the host defer the import: the renderer
668
+ * shows plain markdown until it resolves and reparses what has arrived by then, so the host does
669
+ * not have to hold the stream or replay it.
670
+ */
671
+ type PluginSource = AIGuiPlugin[] | PluginsLoader;
672
+ /**
673
+ * Resolve a plugin source to what the caller can act on now: the array itself, or a promise of it.
674
+ *
675
+ * The array form must stay synchronous. Deferring it by a microtask would render the first chunk
676
+ * of every answer under the plain-markdown grammar and then redraw it, which is a visible flash
677
+ * for a host that had its plugins all along.
678
+ */
679
+ declare function loadPlugins(source?: PluginSource): AIGuiPlugin[] | Promise<AIGuiPlugin[]>;
680
+ /**
681
+ * Whether two lists hold the same plugins in the same order.
682
+ *
683
+ * `plugins={[chart, katex]}` is a new array on every render and the same two plugins every time.
684
+ * What matters is the members, not the array.
685
+ */
686
+ declare function samePlugins(a?: AIGuiPlugin[], b?: AIGuiPlugin[]): boolean;
639
687
  /** Merge every plugin's `nodeRenderers` into a single map (later plugins win). */
640
688
  declare function collectNodeRenderers(plugins?: AIGuiPlugin[], debugOptions?: CollectNodeRendererOptions): Record<string, NodeRenderer>;
641
689
  /** The set of node types claimed by the given plugins. */
@@ -798,6 +846,21 @@ declare class Renderer {
798
846
  private scheduleGeneration;
799
847
  private readonly debug;
800
848
  constructor(options?: RendererOptions);
849
+ /** The plugins currently parsing and rendering this renderer's output. */
850
+ get plugins(): readonly AIGuiPlugin[];
851
+ /**
852
+ * Swap the plugins in and redraw the answer already buffered.
853
+ *
854
+ * A plugin bundle is worth deferring — diagrams, maths and charts together outweigh everything
855
+ * else a page loads — but the stream does not wait for the import, so whatever arrived meanwhile
856
+ * was parsed under the plain-markdown grammar. Reparsing the buffer this renderer still holds is
857
+ * what turns that text into diagrams, which is why a host does not have to remember what it
858
+ * pushed and replay it once the chunk lands.
859
+ *
860
+ * Passing the same plugins again is a no-op, so a host may call this on every render.
861
+ */
862
+ setPlugins(plugins: AIGuiPlugin[] | undefined): void;
863
+ private registerPluginCards;
801
864
  get debugEnabled(): boolean;
802
865
  emitDebug(type: string, data?: Record<string, unknown>): void;
803
866
  push(chunk: string): void;
@@ -922,4 +985,4 @@ declare function mockModelStream(events: Iterable<ModelStreamEvent> | AsyncItera
922
985
  declare function readableBytes(chunks: Iterable<string | Uint8Array> | AsyncIterable<string | Uint8Array>): ReadableStream<Uint8Array>;
923
986
 
924
987
  //#endregion
925
- 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, PluginStyle, RenderMountContext, RenderOutput, Renderer, RendererOptions, SSEEvent, SSEOptions, SafeDebugValueOptions, SanitizeHtmlOptions, SanitizeSetting, SourceBlock, StreamParseOptions, StreamRouter, Usage, actionOutcome, applyPatches, availableLocales, baseCss, buildSystemPrompt, collectNodeRenderers, collectPluginStyles, contentDeltas, createActionRuntime, createParser, createParserWithMetadata, diffAst, downloadImage, exportRenderedImages, exportSVGToImage, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };
988
+ 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, 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 };
package/dist/index.js CHANGED
@@ -1419,6 +1419,28 @@ function settleCardAction(settle) {
1419
1419
 
1420
1420
  //#endregion
1421
1421
  //#region src/plugins.ts
1422
+ /**
1423
+ * Resolve a plugin source to what the caller can act on now: the array itself, or a promise of it.
1424
+ *
1425
+ * The array form must stay synchronous. Deferring it by a microtask would render the first chunk
1426
+ * of every answer under the plain-markdown grammar and then redraw it, which is a visible flash
1427
+ * for a host that had its plugins all along.
1428
+ */
1429
+ function loadPlugins(source) {
1430
+ if (!source) return [];
1431
+ return Array.isArray(source) ? source : source();
1432
+ }
1433
+ /**
1434
+ * Whether two lists hold the same plugins in the same order.
1435
+ *
1436
+ * `plugins={[chart, katex]}` is a new array on every render and the same two plugins every time.
1437
+ * What matters is the members, not the array.
1438
+ */
1439
+ function samePlugins(a, b) {
1440
+ if (a === b) return true;
1441
+ if (!a || !b || a.length !== b.length) return false;
1442
+ return a.every((plugin, index) => plugin === b[index]);
1443
+ }
1422
1444
  /** Merge every plugin's `nodeRenderers` into a single map (later plugins win). */
1423
1445
  function collectNodeRenderers(plugins = [], debugOptions = {}) {
1424
1446
  const map = {};
@@ -1505,7 +1527,7 @@ function createParser(options = {}) {
1505
1527
  /** Build a parser that also reports the source range for each top-level block. */
1506
1528
  function createParserWithMetadata(options = {}) {
1507
1529
  const md = new MarkdownIt({
1508
- html: true,
1530
+ html: options.rawHtml !== false,
1509
1531
  linkify: true
1510
1532
  });
1511
1533
  options.configureMd?.(md);
@@ -2023,12 +2045,51 @@ var Renderer = class {
2023
2045
  this.options = options;
2024
2046
  this.debug = new DebugEmitter(this.debugSource, options);
2025
2047
  this.sanitize = options.sanitize === false ? false : typeof options.sanitize === "object" ? options.sanitize : {};
2026
- if (options.registry) for (const plugin of options.plugins ?? []) for (const card of plugin.cards ?? []) options.registry.register(card);
2048
+ this.registerPluginCards(options.plugins);
2027
2049
  this.parse = createParserWithMetadata({
2028
2050
  registry: options.registry,
2029
- plugins: options.plugins
2051
+ plugins: options.plugins,
2052
+ rawHtml: options.rawHtml
2030
2053
  });
2031
2054
  }
2055
+ /** The plugins currently parsing and rendering this renderer's output. */
2056
+ get plugins() {
2057
+ return this.options.plugins ?? [];
2058
+ }
2059
+ /**
2060
+ * Swap the plugins in and redraw the answer already buffered.
2061
+ *
2062
+ * A plugin bundle is worth deferring — diagrams, maths and charts together outweigh everything
2063
+ * else a page loads — but the stream does not wait for the import, so whatever arrived meanwhile
2064
+ * was parsed under the plain-markdown grammar. Reparsing the buffer this renderer still holds is
2065
+ * what turns that text into diagrams, which is why a host does not have to remember what it
2066
+ * pushed and replay it once the chunk lands.
2067
+ *
2068
+ * Passing the same plugins again is a no-op, so a host may call this on every render.
2069
+ */
2070
+ setPlugins(plugins) {
2071
+ if (samePlugins(this.options.plugins, plugins)) return;
2072
+ this.options = {
2073
+ ...this.options,
2074
+ plugins
2075
+ };
2076
+ this.registerPluginCards(plugins);
2077
+ this.parse = createParserWithMetadata({
2078
+ registry: this.options.registry,
2079
+ plugins,
2080
+ rawHtml: this.options.rawHtml
2081
+ });
2082
+ this.parsed = void 0;
2083
+ this.renderScheduled = false;
2084
+ this.scheduleGeneration++;
2085
+ if (this.debug.active) this.debug.emit("plugins-changed", { plugins: (plugins ?? []).map((plugin) => plugin.name) });
2086
+ this.render();
2087
+ }
2088
+ registerPluginCards(plugins) {
2089
+ const registry = this.options.registry;
2090
+ if (!registry) return;
2091
+ for (const plugin of plugins ?? []) for (const card of plugin.cards ?? []) registry.register(card);
2092
+ }
2032
2093
  get debugEnabled() {
2033
2094
  return this.debug.available;
2034
2095
  }
@@ -2772,4 +2833,4 @@ function delay(ms, signal) {
2772
2833
  }
2773
2834
 
2774
2835
  //#endregion
2775
- 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, availableLocales, baseCss, buildSystemPrompt, collectNodeRenderers, collectPluginStyles, contentDeltas, createActionRuntime, createParser, createParserWithMetadata, diffAst, downloadImage, exportRenderedImages, exportSVGToImage, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };
2836
+ 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, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-gui/core",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
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",