@ai-gui/core 0.21.1 → 0.22.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 +6 -2
- package/dist/index.cjs +68 -3
- package/dist/index.d.cts +64 -1
- package/dist/index.d.ts +64 -1
- package/dist/index.js +67 -4
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -110,12 +110,16 @@ 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.
|
|
116
|
+
- 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.
|
|
114
117
|
- `StreamRouter` — demultiplex one stream into named channels: `.channel(name, sink)`, `.on(name, cb)`, `.feed(source)`.
|
|
115
118
|
- `CardRegistry` — `register(def)`, `parse(type, rawJson)`, `getRender(type)`, `toPromptSpec()`, `toJSONSchema()`.
|
|
116
119
|
- `CardStore` — `register`, `get`, `list`, `subscribe`, `apply`, `applyAll`, `delete`, `clear`, `snapshot`, and `restore` for Cards with stable IDs.
|
|
117
120
|
- `ActionRegistry`, `ActionRuntime`, `createActionRuntime`, `getActionKey`, `getIdleActionState` — validated application-owned action execution and observable lifecycle state.
|
|
118
|
-
- `buildSystemPrompt({ base?, registry?, plugins? })`.
|
|
121
|
+
- `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.
|
|
122
|
+
- `loadPlugins(source)` / `samePlugins(a, b)` — normalize `AIGuiPlugin[] | (() => Promise<AIGuiPlugin[]>)` and compare two lists by members.
|
|
119
123
|
- Utilities: `parsePartialJSON`, `repairMarkdown`, `sanitizeHtml`, `createParser`, `diffAst`, `collectNodeRenderers`.
|
|
120
124
|
- Types: `ASTNode`, `Patch`, `RenderOutput` (`html | element | card | mount`), `CardDef`, `AIGuiPlugin`, `NodeRenderer`, `RendererOptions`, `JSONSchema`.
|
|
121
125
|
|
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
//#endregion
|
|
25
25
|
const dompurify = __toESM(require("dompurify"));
|
|
26
26
|
const markdown_it = __toESM(require("markdown-it"));
|
|
27
|
+
const markdown_it_cjk_friendly = __toESM(require("markdown-it-cjk-friendly"));
|
|
27
28
|
|
|
28
29
|
//#region src/partial-json.ts
|
|
29
30
|
/**
|
|
@@ -1443,6 +1444,28 @@ function settleCardAction(settle) {
|
|
|
1443
1444
|
|
|
1444
1445
|
//#endregion
|
|
1445
1446
|
//#region src/plugins.ts
|
|
1447
|
+
/**
|
|
1448
|
+
* Resolve a plugin source to what the caller can act on now: the array itself, or a promise of it.
|
|
1449
|
+
*
|
|
1450
|
+
* The array form must stay synchronous. Deferring it by a microtask would render the first chunk
|
|
1451
|
+
* of every answer under the plain-markdown grammar and then redraw it, which is a visible flash
|
|
1452
|
+
* for a host that had its plugins all along.
|
|
1453
|
+
*/
|
|
1454
|
+
function loadPlugins(source) {
|
|
1455
|
+
if (!source) return [];
|
|
1456
|
+
return Array.isArray(source) ? source : source();
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Whether two lists hold the same plugins in the same order.
|
|
1460
|
+
*
|
|
1461
|
+
* `plugins={[chart, katex]}` is a new array on every render and the same two plugins every time.
|
|
1462
|
+
* What matters is the members, not the array.
|
|
1463
|
+
*/
|
|
1464
|
+
function samePlugins(a, b) {
|
|
1465
|
+
if (a === b) return true;
|
|
1466
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
1467
|
+
return a.every((plugin, index) => plugin === b[index]);
|
|
1468
|
+
}
|
|
1446
1469
|
/** Merge every plugin's `nodeRenderers` into a single map (later plugins win). */
|
|
1447
1470
|
function collectNodeRenderers(plugins = [], debugOptions = {}) {
|
|
1448
1471
|
const map = {};
|
|
@@ -1529,9 +1552,10 @@ function createParser(options = {}) {
|
|
|
1529
1552
|
/** Build a parser that also reports the source range for each top-level block. */
|
|
1530
1553
|
function createParserWithMetadata(options = {}) {
|
|
1531
1554
|
const md = new markdown_it.default({
|
|
1532
|
-
html:
|
|
1555
|
+
html: options.rawHtml !== false,
|
|
1533
1556
|
linkify: true
|
|
1534
1557
|
});
|
|
1558
|
+
md.use(markdown_it_cjk_friendly.default);
|
|
1535
1559
|
options.configureMd?.(md);
|
|
1536
1560
|
for (const plugin of options.plugins ?? []) plugin.extendParser?.(md);
|
|
1537
1561
|
const hasParserExtensions = Boolean(options.configureMd || options.plugins?.some((plugin) => plugin.extendParser));
|
|
@@ -2047,12 +2071,51 @@ var Renderer = class {
|
|
|
2047
2071
|
this.options = options;
|
|
2048
2072
|
this.debug = new DebugEmitter(this.debugSource, options);
|
|
2049
2073
|
this.sanitize = options.sanitize === false ? false : typeof options.sanitize === "object" ? options.sanitize : {};
|
|
2050
|
-
|
|
2074
|
+
this.registerPluginCards(options.plugins);
|
|
2051
2075
|
this.parse = createParserWithMetadata({
|
|
2052
2076
|
registry: options.registry,
|
|
2053
|
-
plugins: options.plugins
|
|
2077
|
+
plugins: options.plugins,
|
|
2078
|
+
rawHtml: options.rawHtml
|
|
2054
2079
|
});
|
|
2055
2080
|
}
|
|
2081
|
+
/** The plugins currently parsing and rendering this renderer's output. */
|
|
2082
|
+
get plugins() {
|
|
2083
|
+
return this.options.plugins ?? [];
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Swap the plugins in and redraw the answer already buffered.
|
|
2087
|
+
*
|
|
2088
|
+
* A plugin bundle is worth deferring — diagrams, maths and charts together outweigh everything
|
|
2089
|
+
* else a page loads — but the stream does not wait for the import, so whatever arrived meanwhile
|
|
2090
|
+
* was parsed under the plain-markdown grammar. Reparsing the buffer this renderer still holds is
|
|
2091
|
+
* what turns that text into diagrams, which is why a host does not have to remember what it
|
|
2092
|
+
* pushed and replay it once the chunk lands.
|
|
2093
|
+
*
|
|
2094
|
+
* Passing the same plugins again is a no-op, so a host may call this on every render.
|
|
2095
|
+
*/
|
|
2096
|
+
setPlugins(plugins) {
|
|
2097
|
+
if (samePlugins(this.options.plugins, plugins)) return;
|
|
2098
|
+
this.options = {
|
|
2099
|
+
...this.options,
|
|
2100
|
+
plugins
|
|
2101
|
+
};
|
|
2102
|
+
this.registerPluginCards(plugins);
|
|
2103
|
+
this.parse = createParserWithMetadata({
|
|
2104
|
+
registry: this.options.registry,
|
|
2105
|
+
plugins,
|
|
2106
|
+
rawHtml: this.options.rawHtml
|
|
2107
|
+
});
|
|
2108
|
+
this.parsed = void 0;
|
|
2109
|
+
this.renderScheduled = false;
|
|
2110
|
+
this.scheduleGeneration++;
|
|
2111
|
+
if (this.debug.active) this.debug.emit("plugins-changed", { plugins: (plugins ?? []).map((plugin) => plugin.name) });
|
|
2112
|
+
this.render();
|
|
2113
|
+
}
|
|
2114
|
+
registerPluginCards(plugins) {
|
|
2115
|
+
const registry = this.options.registry;
|
|
2116
|
+
if (!registry) return;
|
|
2117
|
+
for (const plugin of plugins ?? []) for (const card of plugin.cards ?? []) registry.register(card);
|
|
2118
|
+
}
|
|
2056
2119
|
get debugEnabled() {
|
|
2057
2120
|
return this.debug.available;
|
|
2058
2121
|
}
|
|
@@ -2844,6 +2907,7 @@ exports.getIdleActionState = getIdleActionState
|
|
|
2844
2907
|
exports.injectPluginStyles = injectPluginStyles
|
|
2845
2908
|
exports.isCardPatchResult = isCardPatchResult
|
|
2846
2909
|
exports.jsonLines = jsonLines
|
|
2910
|
+
exports.loadPlugins = loadPlugins
|
|
2847
2911
|
exports.mockModelStream = mockModelStream
|
|
2848
2912
|
exports.ndjson = ndjson
|
|
2849
2913
|
exports.parsePartialJSON = parsePartialJSON
|
|
@@ -2853,6 +2917,7 @@ exports.readableBytes = readableBytes
|
|
|
2853
2917
|
exports.repairMarkdown = repairMarkdown
|
|
2854
2918
|
exports.resolveMessages = resolveMessages
|
|
2855
2919
|
exports.safeDebugValue = safeDebugValue
|
|
2920
|
+
exports.samePlugins = samePlugins
|
|
2856
2921
|
exports.sanitizeHtml = sanitizeHtml
|
|
2857
2922
|
exports.sanitizeRenderedHtml = sanitizeRenderedHtml
|
|
2858
2923
|
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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import DOMPurify from "dompurify";
|
|
2
2
|
import MarkdownIt from "markdown-it";
|
|
3
|
+
import cjkFriendly from "markdown-it-cjk-friendly";
|
|
3
4
|
|
|
4
5
|
//#region src/partial-json.ts
|
|
5
6
|
/**
|
|
@@ -1419,6 +1420,28 @@ function settleCardAction(settle) {
|
|
|
1419
1420
|
|
|
1420
1421
|
//#endregion
|
|
1421
1422
|
//#region src/plugins.ts
|
|
1423
|
+
/**
|
|
1424
|
+
* Resolve a plugin source to what the caller can act on now: the array itself, or a promise of it.
|
|
1425
|
+
*
|
|
1426
|
+
* The array form must stay synchronous. Deferring it by a microtask would render the first chunk
|
|
1427
|
+
* of every answer under the plain-markdown grammar and then redraw it, which is a visible flash
|
|
1428
|
+
* for a host that had its plugins all along.
|
|
1429
|
+
*/
|
|
1430
|
+
function loadPlugins(source) {
|
|
1431
|
+
if (!source) return [];
|
|
1432
|
+
return Array.isArray(source) ? source : source();
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Whether two lists hold the same plugins in the same order.
|
|
1436
|
+
*
|
|
1437
|
+
* `plugins={[chart, katex]}` is a new array on every render and the same two plugins every time.
|
|
1438
|
+
* What matters is the members, not the array.
|
|
1439
|
+
*/
|
|
1440
|
+
function samePlugins(a, b) {
|
|
1441
|
+
if (a === b) return true;
|
|
1442
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
1443
|
+
return a.every((plugin, index) => plugin === b[index]);
|
|
1444
|
+
}
|
|
1422
1445
|
/** Merge every plugin's `nodeRenderers` into a single map (later plugins win). */
|
|
1423
1446
|
function collectNodeRenderers(plugins = [], debugOptions = {}) {
|
|
1424
1447
|
const map = {};
|
|
@@ -1505,9 +1528,10 @@ function createParser(options = {}) {
|
|
|
1505
1528
|
/** Build a parser that also reports the source range for each top-level block. */
|
|
1506
1529
|
function createParserWithMetadata(options = {}) {
|
|
1507
1530
|
const md = new MarkdownIt({
|
|
1508
|
-
html:
|
|
1531
|
+
html: options.rawHtml !== false,
|
|
1509
1532
|
linkify: true
|
|
1510
1533
|
});
|
|
1534
|
+
md.use(cjkFriendly);
|
|
1511
1535
|
options.configureMd?.(md);
|
|
1512
1536
|
for (const plugin of options.plugins ?? []) plugin.extendParser?.(md);
|
|
1513
1537
|
const hasParserExtensions = Boolean(options.configureMd || options.plugins?.some((plugin) => plugin.extendParser));
|
|
@@ -2023,12 +2047,51 @@ var Renderer = class {
|
|
|
2023
2047
|
this.options = options;
|
|
2024
2048
|
this.debug = new DebugEmitter(this.debugSource, options);
|
|
2025
2049
|
this.sanitize = options.sanitize === false ? false : typeof options.sanitize === "object" ? options.sanitize : {};
|
|
2026
|
-
|
|
2050
|
+
this.registerPluginCards(options.plugins);
|
|
2027
2051
|
this.parse = createParserWithMetadata({
|
|
2028
2052
|
registry: options.registry,
|
|
2029
|
-
plugins: options.plugins
|
|
2053
|
+
plugins: options.plugins,
|
|
2054
|
+
rawHtml: options.rawHtml
|
|
2030
2055
|
});
|
|
2031
2056
|
}
|
|
2057
|
+
/** The plugins currently parsing and rendering this renderer's output. */
|
|
2058
|
+
get plugins() {
|
|
2059
|
+
return this.options.plugins ?? [];
|
|
2060
|
+
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Swap the plugins in and redraw the answer already buffered.
|
|
2063
|
+
*
|
|
2064
|
+
* A plugin bundle is worth deferring — diagrams, maths and charts together outweigh everything
|
|
2065
|
+
* else a page loads — but the stream does not wait for the import, so whatever arrived meanwhile
|
|
2066
|
+
* was parsed under the plain-markdown grammar. Reparsing the buffer this renderer still holds is
|
|
2067
|
+
* what turns that text into diagrams, which is why a host does not have to remember what it
|
|
2068
|
+
* pushed and replay it once the chunk lands.
|
|
2069
|
+
*
|
|
2070
|
+
* Passing the same plugins again is a no-op, so a host may call this on every render.
|
|
2071
|
+
*/
|
|
2072
|
+
setPlugins(plugins) {
|
|
2073
|
+
if (samePlugins(this.options.plugins, plugins)) return;
|
|
2074
|
+
this.options = {
|
|
2075
|
+
...this.options,
|
|
2076
|
+
plugins
|
|
2077
|
+
};
|
|
2078
|
+
this.registerPluginCards(plugins);
|
|
2079
|
+
this.parse = createParserWithMetadata({
|
|
2080
|
+
registry: this.options.registry,
|
|
2081
|
+
plugins,
|
|
2082
|
+
rawHtml: this.options.rawHtml
|
|
2083
|
+
});
|
|
2084
|
+
this.parsed = void 0;
|
|
2085
|
+
this.renderScheduled = false;
|
|
2086
|
+
this.scheduleGeneration++;
|
|
2087
|
+
if (this.debug.active) this.debug.emit("plugins-changed", { plugins: (plugins ?? []).map((plugin) => plugin.name) });
|
|
2088
|
+
this.render();
|
|
2089
|
+
}
|
|
2090
|
+
registerPluginCards(plugins) {
|
|
2091
|
+
const registry = this.options.registry;
|
|
2092
|
+
if (!registry) return;
|
|
2093
|
+
for (const plugin of plugins ?? []) for (const card of plugin.cards ?? []) registry.register(card);
|
|
2094
|
+
}
|
|
2032
2095
|
get debugEnabled() {
|
|
2033
2096
|
return this.debug.available;
|
|
2034
2097
|
}
|
|
@@ -2772,4 +2835,4 @@ function delay(ms, signal) {
|
|
|
2772
2835
|
}
|
|
2773
2836
|
|
|
2774
2837
|
//#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 };
|
|
2838
|
+
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.
|
|
3
|
+
"version": "0.22.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",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"dompurify": "^3.4.12",
|
|
55
|
-
"markdown-it": "^14.1.0"
|
|
55
|
+
"markdown-it": "^14.1.0",
|
|
56
|
+
"markdown-it-cjk-friendly": "^2.0.2"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@types/markdown-it": "^14.1.2",
|