import MarkdownIt from "markdown-it"; //#region src/partial-json.d.ts interface PartialJSONResult { data: unknown; complete: boolean; } /** * Parse a possibly-incomplete JSON string, as produced by a streaming source. * * If the input is valid JSON, it is returned with `complete: true`. Otherwise * the parser attempts to repair the fragment by dropping any trailing * incomplete token (a dangling key, colon, or comma) and auto-closing unclosed * strings, objects, and arrays, returning whatever data has arrived so far with * `complete: false`. Unparseable input yields `{ data: undefined, complete: false }` * and never throws. */ declare function parsePartialJSON(input: string): PartialJSONResult; //#endregion //#region src/repair-markdown.d.ts /** * Temporarily repair half-finished markdown so a partial streaming buffer * renders smoothly. Operates on a copy of the buffer and returns a new string; * it never mutates the input. Pure function, no dependencies. * * The repairs are intentionally conservative: only unambiguous unclosed * inline/fence syntax is completed. Ambiguous constructs (e.g. a dangling * link text `[docs`) are left untouched to avoid guessing wrong. */ declare function repairMarkdown(buffer: string): string; //#endregion //#region src/sanitizer.d.ts interface SanitizeHtmlOptions { sanitizer?: (html: string) => string; /** Bare SSR either escapes all markup (default) or fails explicitly. */ ssr?: "escape" | "throw"; /** * Whether a plugin may declare its own markup safe, which it does by returning `trusted: true`. * On by default: a plugin is code the host installed, and sanitizing the SVG it drew would * escape it into text. Set to `false` to sanitize every output whatever its origin. */ trustPlugins?: boolean; } /** * Sanitize an HTML string, stripping scripts and unsafe attributes. * * When a DOM is available (browser, or Node with jsdom) DOMPurify is used. In a * bare Node environment without a global `window`, DOMPurify cannot run, so we * fall back to escaping the HTML-significant characters. This never emits raw * markup and never throws. */ declare function sanitizeHtml(html: string, options?: SanitizeHtmlOptions): string; /** What a renderer was told to do about sanitizing: nothing, the default, or these options. */ type SanitizeSetting = boolean | SanitizeHtmlOptions | undefined; /** * Sanitize one piece of rendered HTML, respecting a plugin's claim that it built the markup itself. * * Shared by every framework binding so the three of them cannot drift apart on what "sanitize" * means — and so a diagram is not escaped into its own source text in one of them. */ declare function sanitizeRenderedHtml(html: string, sanitize: SanitizeSetting, trusted?: boolean): string; //#endregion //#region src/debug-events.d.ts type DebugSource = "renderer" | "action-runtime" | "card-store"; interface DebugEvent = Record> { type: string; source: DebugSource; timestamp: number; sequence: number; data: TData; } type DebugEventListener = (event: DebugEvent) => void; interface DebugEventTarget { readonly debugSource: DebugSource; subscribeDebug(listener: DebugEventListener): () => void; } interface DebugOptions { debug?: boolean; onDebugEvent?: DebugEventListener; maxStringLength?: number; maxDepth?: number; maxNodes?: number; redact?: (context: DebugRedactContext) => boolean; } interface DebugRedactContext { key: string; path: readonly (string | number)[]; value: unknown; } interface DebugInstrumentationTarget extends DebugEventTarget { readonly debugEnabled: boolean; emitDebug(type: string, data?: Record): void; } interface SafeDebugValueOptions { maxStringLength?: number; maxDepth?: number; maxNodes?: number; redact?: DebugOptions["redact"]; } declare class DebugEmitter { private readonly enabled; private readonly source; private readonly limits; private readonly listeners; private sequence; constructor(source: DebugSource, options?: DebugOptions); get active(): boolean; get available(): boolean; subscribe(listener: DebugEventListener): () => void; emit(type: string, data?: Record): void; } declare function safeDebugValue(value: unknown, options?: SafeDebugValueOptions): unknown; //#endregion //#region src/types.d.ts /** Framework-agnostic render node. */ interface ASTNode { key: string; type: string; tag?: string; content?: string; html?: string; attrs?: Record; children?: ASTNode[]; /** Whether a streaming block has enough source to invoke its renderer. */ complete?: boolean; /** card-specific payload */ card?: { id?: string; type: string; data: unknown; complete: boolean; valid: boolean; }; } /** Patch event produced by diffing. */ type Patch = { op: "insert"; index: number; node: ASTNode; } | { op: "update"; key: string; node: ASTNode; } | { op: "move"; key: string; index: number; } | { op: "remove"; key: string; }; /** Framework-neutral render descriptor returned by plugin node renderers. */ type RenderOutput = /** * `trusted` marks markup the plugin built itself rather than markup taken from the model. * * A plugin that renders a diagram returns SVG, and sanitizing SVG escapes it — the reader gets * the source text instead of the picture. Hosts worked around that by matching the plugin's * internal id prefix with a regular expression, which breaks the moment the plugin renames its * ids and lets any model output wearing that prefix through unsanitized. A plugin is code the * host chose to install, so it can say so itself; a host that disagrees sets * `sanitize: { trustPlugins: false }`. */ { kind: "html"; html: string; trusted?: boolean; } | { kind: "element"; tag: string; props?: Record; children?: RenderOutput[]; } | { kind: "card"; type: string; data: unknown; } | { kind: "mount"; mount: (el: HTMLElement, context: RenderMountContext) => void | (() => void); }; interface MountCardSlotRequest { type: string; data: unknown; } interface MountedCardSlot { update(data: unknown): void; destroy(): void; } interface RenderMountContext { mountCard?: (host: HTMLElement, request: MountCardSlotRequest) => MountedCardSlot | undefined; } /** What the host can tell a plugin about the surroundings it is rendering into. */ interface NodeRenderContext { /** * The host's colour scheme, "light" or "dark" by convention. * * A diagram or a chart picks its own palette, and a plugin has no way to read the palette of * the page it is embedded in, so without this an answer rendered on a dark page comes back * with white plot areas. */ readonly theme?: string; /** * The host's locale as a BCP-47 tag, e.g. "zh-CN". * * A plugin draws its own labels — a Copy button, an error line — and cannot read the page's * language, so without this a Chinese product renders English chrome around Chinese content. * English is the fallback for anything a plugin has not translated. */ readonly locale?: string; } type NodeRenderer = (node: ASTNode, context?: NodeRenderContext) => RenderOutput | Promise; interface PluginCommitContext { readonly generation: number; emitDebug(type: string, data?: Record): void; } type KnownJSONSchemaType = "object" | "array" | "string" | "number" | "integer" | "boolean" | "null"; interface JSONSchema { type?: KnownJSONSchemaType | (string & {}); properties?: Record; items?: JSONSchema; required?: readonly string[]; additionalProperties?: boolean | JSONSchema; enum?: readonly unknown[]; const?: unknown; minLength?: number; maxLength?: number; pattern?: string; minimum?: number; maximum?: number; minItems?: number; maxItems?: number; [k: string]: unknown; } interface CardDef { type: string; description: string; schema?: JSONSchema; example?: TData; render?: TComponent; validate?: (data: TData) => boolean; } interface AIGuiPlugin { name: string; extendParser?: (md: MarkdownIt) => void; cards?: CardDef[]; nodeRenderers?: Record; isBlockComplete?: (nodeType: string, raw: string) => boolean; /** Runs synchronously after the AST is finalized and before patches are dispatched. */ onASTCommit?: (nodes: readonly ASTNode[], context: PluginCommitContext) => void; css?: string; /** * LLM-facing guidance describing this plugin's fence syntax. * * A host does not read this field itself: `buildSystemPrompt({ registry, plugins, locale })` * collects the card specs and every enabled plugin's spec in one call, already in the product's * language. Assembling it by hand — reading each plugin's spec, joining them, writing the * localized wording again — reinvents that badly, and a plugin added later is then missing from * the prompt while its renderer is installed. * * Receives the locale asked of `buildSystemPrompt`, so the rules can be written in the language * the product answers in — a Chinese persona followed by English rules reads as a contradiction * to the model. Plugins that only ship English simply ignore the argument. */ promptSpec?: string | ((locale?: string) => string); } interface RendererOptions extends DebugOptions { registry?: CardRegistry; plugins?: AIGuiPlugin[]; sanitize?: boolean | SanitizeHtmlOptions; /** * Whether raw HTML in the model's output is interpreted as markup. On by default. * * A tag a model wrote inside prose is usually text it is describing, not markup it means: one * stray `` in a sentence about code swallows the rest of the line into an element. Turning * this off escapes every tag the model writes and shows the characters instead, which is what a * product wants when the model is meant to produce markdown and nothing else. It is not a * substitute for `sanitize` — a plugin's own markup and the host's cards are unaffected either * way. */ rawHtml?: boolean; /** Coalesce multiple pushes by scheduling one render callback. */ scheduler?: (render: () => void) => void; onPatch?: (patches: Patch[], nodes: ASTNode[]) => void; } interface FeedOptions { signal?: AbortSignal; } type FeedChunk = string | Uint8Array; type FeedSource = AsyncIterable | ReadableStream; //#endregion //#region src/card-registry.d.ts interface CardParseResult { data: unknown; complete: boolean; valid: boolean; } declare class CardRegistry { private cards; register(def: CardDef): void; has(type: string): boolean; getRender(type: string): unknown; get(type: string): Readonly | undefined; list(): Readonly[]; parse(type: string, rawJson: string): CardParseResult; validate(type: string, data: unknown): boolean; private validateDefinition; toPromptSpec(): string; get size(): number; toJSONSchema(): JSONSchema; } //#endregion //#region src/action-outcome.d.ts /** * How a completed action turned out, as opposed to whether it completed. * * The lifecycle a card and an action already report — idle, loading, success, error — answers "did * the dispatch run". It cannot answer "was the answer right": a student who picks the wrong option * submits perfectly well, so the action succeeds and nothing on screen says otherwise. Putting a * "warning" beside "error" in that lifecycle would fold a wrong answer in with a failed request, * which is the one distinction a host needs to keep. * * So the outcome travels on its own, returned by the handler that judged it. */ /** How the result should read to the person looking at it. */ type OutcomeTone = "positive" | "warning" | "negative" | "neutral"; interface ActionOutcome { tone: OutcomeTone; /** A sentence to show beside the control — why it is wrong, or what was expected. */ message?: string; /** * Per-field verdicts, so one wrong answer marks the field it came from rather than the whole * form. Keyed by field name. */ fields?: Record; } /** * Read an outcome out of whatever a handler returned. * * A handler answers with its own result type, so the outcome is looked for rather than required: * `{ tone: "warning", message: "…" }` on its own, or under an `outcome` key beside the handler's * own data. Anything else means the handler did not judge, and the host shows nothing. */ declare function actionOutcome(value: unknown): ActionOutcome | undefined; //#endregion //#region src/card-store.d.ts declare const CARD_ID_MAX_LENGTH = 256; declare const CARD_JSON_MAX_DEPTH = 32; declare const CARD_JSON_MAX_NODES = 10000; declare const CARD_PATCH_BATCH_MAX_SIZE = 100; type CardAction = { status: "idle"; } | { status: "loading"; actionId: string; } /** * The dispatch ran. `outcome` carries how it turned out, when the handler judged it — a student * answering wrong submits successfully, so the verdict cannot live in the status. */ | { status: "success"; actionId: string; outcome?: ActionOutcome; } | { status: "error"; actionId: string; error: CardActionError; }; interface CardActionError { name: string; message: string; } interface CardRecord { readonly id: string; readonly type: string; readonly data: unknown; readonly revision: number; readonly action: CardAction; } interface CardPatch { op: "merge" | "replace"; cardId: string; data: unknown; revision?: number; } interface CardPatchBatch { op: "batch"; patches: CardPatch[]; } type CardPatchResult = CardPatch | CardPatchBatch; interface CardSnapshot { version: 1; cards: Array>; } interface CardStoreOptions extends DebugOptions { registry?: CardRegistry; } type CardListener = (card: CardRecord | undefined) => void; type CardStoreListener = (cards: readonly CardRecord[]) => void; declare class CardStore { readonly debugSource: "card-store"; private readonly registry?; private cards; private lastMutationEpoch; private mutationEpoch; private readonly listeners; private readonly allListeners; private readonly debug; constructor(options?: CardStoreOptions); register(input: { id: string; type: string; data: unknown; }): CardRecord; get(id: string): CardRecord | undefined; list(): CardRecord[]; subscribe(id: string, listener: CardListener): () => void; subscribeAll(listener: CardStoreListener): () => void; subscribeDebug(listener: DebugEventListener): () => void; apply(patch: CardPatch): CardRecord; applyAll(patches: readonly CardPatch[]): CardRecord[]; delete(id: string): boolean; clear(): void; snapshot(): CardSnapshot; restore(snapshot: CardSnapshot): void; beginAction(id: string, actionId: string): boolean; succeedAction(id: string, actionId: string, result?: unknown): boolean; failAction(id: string, actionId: string, error: unknown): boolean; cancelAction(id: string, actionId: string): boolean; isActionCurrent(id: string, actionId: string): boolean; revisions(): ReadonlyMap; captureMutationEpoch(): number; applyActionResult(result: CardPatchResult, startedEpoch: number): CardRecord[]; private preparePatch; private assertValid; private setAction; private nextMutationEpoch; private notify; private notifyOne; private notifyAll; } declare class CardStoreError extends Error { constructor(message: string, options?: ErrorOptions); } declare class CardNotFoundError extends CardStoreError { constructor(id: string); } declare class CardTypeConflictError extends CardStoreError { constructor(id: string, currentType: string, nextType: string); } declare class CardValidationError extends CardStoreError { constructor(type: string); } declare class CardRevisionConflictError extends CardStoreError { constructor(id: string, expected: number, actual: number); } declare class CardJSONError extends CardStoreError {} declare class CardLimitError extends CardStoreError {} declare class CardSnapshotError extends CardStoreError {} declare function isCardPatchResult(value: unknown): value is CardPatchResult; //#endregion //#region src/card-channel.d.ts /** * A message a stream may send on a card channel. * * `register` first, then any number of patches against that id. Deletion is deliberately not here: * a card the reader is looking at, or has already acted on, should not vanish because a late frame * said so. A host that wants it calls `store.delete` from its own handler, where it can decide. */ type CardMessage = { op: "register"; id: string; type: string; data: unknown; } | CardPatch | CardPatchBatch; interface CardChannelOptions { /** * Where a message that could not be applied goes. * * This is the reason the adapter exists rather than being three lines at the call site. The * handler runs inside `StreamRouter.feed`, one long await over the whole response — a throw here * does not just drop the card, it kills the content channel with it and the answer stops * mid-sentence. So every failure is caught, and reported here instead. * * Left unset, failures go to `console.error`: a swallowed one looks exactly like a card that the * model never sent, which is the hardest version of this bug to find. */ onError?: (error: unknown, message: unknown) => void; } /** * Apply card messages arriving on a stream channel to a `CardStore`. * * ```ts * new StreamRouter() * .channel("content", renderer) * .on("cards", cardChannel(store)) * ``` * * This is the parallel half of the design. The content channel is one append-only buffer with a * single writer, because markdown block boundaries cannot survive two sources interleaving into * them. Cards are addressed by id instead, so anything on the wire — a background job, a tool that * finished late, a second model — can update one without touching the text, in any order, as many * times as it likes. * * Ordering is the store's contract, not this adapter's: a patch carrying `revision` is an * optimistic lock and a stale one is rejected through `onError`; a patch without one is * last-write-wins. Send `revision` when a late frame overwriting a newer state would be wrong. * * A card channel carries whole messages, not text deltas — one JSON object per frame. */ declare function cardChannel(store: CardStore, options?: CardChannelOptions): (value: unknown) => void; //#endregion //#region src/json-schema.d.ts interface JSONSchemaValidationResult { valid: boolean; issues: string[]; } /** Small, dependency-free validator for the JSON Schema subset used by AIGUI definitions. */ declare function validateJSONSchema(schema: JSONSchema, value: unknown): JSONSchemaValidationResult; //#endregion //#region src/actions.d.ts interface ActionContext { signal: AbortSignal; actionId: string; cardType?: string; cardId?: string; } interface ActionDefinition { type: string; schema?: JSONSchema; run: (params: TParams, context: ActionContext) => TResult | Promise; } interface ActionRegisterOptions { override?: boolean; } declare class ActionRegistry { private actions; register(definition: ActionDefinition, options?: ActionRegisterOptions): void; has(type: string): boolean; get(type: string): ActionDefinition | undefined; list(): ActionDefinition[]; } interface ActionRequest { type: string; params: TParams; cardType?: string; cardId?: string; } interface ActionDispatchOptions { signal?: AbortSignal; timeoutMs?: number; owner?: object; } type ActionStatus = "idle" | "pending" | "success" | "error" | "cancelled"; interface ActionStateBase { key: string; type: string; cardType?: string; cardId?: string; actionId?: string; } type ActionState = (ActionStateBase & { status: "idle"; }) | (ActionStateBase & { status: "pending"; actionId: string; }) | (ActionStateBase & { status: "success"; actionId: string; result: unknown; }) | (ActionStateBase & { status: "error"; actionId: string; error: ActionRuntimeError; }) | (ActionStateBase & { status: "cancelled"; actionId: string; error: ActionAbortedError; }); interface ActionEventBase { key: string; type: string; params: unknown; actionId: string; cardType?: string; cardId?: string; } type ActionStartEvent = ActionEventBase; type ActionSuccessEvent = ActionEventBase & { result: unknown; }; type ActionErrorEvent = ActionEventBase & { error: ActionRuntimeError; }; interface ActionRuntimeOptions extends DebugOptions { registry: ActionRegistry; cardStore?: CardStore; timeoutMs?: number; onActionStart?: (event: ActionStartEvent) => void; onActionSuccess?: (event: ActionSuccessEvent) => void; onActionError?: (event: ActionErrorEvent) => void; } type ActionStateListener = (state: ActionState) => void; declare class ActionRuntime { readonly debugSource: "action-runtime"; private readonly registry; private readonly cardStore?; private readonly defaultTimeoutMs?; private readonly onActionStart?; private readonly onActionSuccess?; private readonly onActionError?; private readonly states; private readonly pending; private readonly listeners; private readonly defaultOwner; private readonly runtimeId; private generation; private nextActionId; private destroyed; private readonly debug; constructor(options: ActionRuntimeOptions); dispatch(request: ActionRequest, options?: ActionDispatchOptions): Promise; getState(key: string): ActionState; /** Check the runtime allowlist without exposing executable action definitions. */ hasAction(type: string): boolean; /** List registered action names without exposing executable definitions. */ listActionTypes(): readonly string[]; /** * The parameter schema of one action, and nothing else. * * A model asked to draw a form that calls an action has to know what the * action wants. Told only the name, it invents plausible parameter names — * `when` for a start time — and every dispatch is rejected before it reaches * the host. Prompt builders read this to describe the shape. * * Deliberately narrower than `registry.get`: a schema is a description, while * an ActionDefinition carries `run`, and handing that out is handing out the * side effect itself. */ describeAction(type: string): JSONSchema | undefined; subscribe(listener: ActionStateListener): () => void; subscribeDebug(listener: DebugEventListener): () => void; cancel(key: string): boolean; reset(): void; destroy(): void; private rejectPreflight; private isPublic; private commit; private notify; } declare function createActionRuntime(options: ActionRuntimeOptions): ActionRuntime; declare class ActionRuntimeError extends Error { constructor(message: string, options?: ErrorOptions); } declare class ActionAlreadyRegisteredError extends ActionRuntimeError { constructor(type: string); } declare class ActionNotFoundError extends ActionRuntimeError { constructor(type: string); } declare class ActionValidationError extends ActionRuntimeError { readonly actionType: string; readonly issues: string[]; constructor(actionType: string, issues: string[]); } declare class ActionExecutionError extends ActionRuntimeError { constructor(type: string, cause: unknown); } declare class ActionAbortedError extends ActionRuntimeError { constructor(type: string); } declare class ActionTimeoutError extends ActionRuntimeError { readonly timeoutMs: number; constructor(type: string, timeoutMs: number); } declare class ActionDestroyedError extends ActionRuntimeError { constructor(); } declare function getActionKey(type: string, cardType?: string, cardId?: string): string; declare function getIdleActionState(key: string): Extract; //#endregion //#region src/parser.d.ts interface ParserOptions { registry?: CardRegistry; plugins?: AIGuiPlugin[]; configureMd?: (md: MarkdownIt) => void; /** * Whether raw HTML in the model's output is interpreted as markup. On by default. * * A model writing *about* code emits tags in prose it never meant as markup — a line like * `return "done\n"` is text, but interpreting it swallows everything after the tag into an * element and the rest of the sentence lands outside it. Sanitizing does not help: `` is a * tag any allowlist keeps. Turn this off and every tag the model writes is escaped and shown as * the characters it wrote, so a model that gets HTML wrong produces an ugly line rather than a * mangled answer. */ rawHtml?: boolean; } interface SourceBlock { start: number; end: number; nodeStart: number; nodeEnd: number; } interface ParseResult { nodes: ASTNode[]; blocks: SourceBlock[]; incrementalSafe: boolean; } /** Build a parser that turns markdown source into a flat list of ASTNodes. */ declare function createParser(options?: ParserOptions): (src: string, rawSrc?: string) => ASTNode[]; /** Build a parser that also reports the source range for each top-level block. */ declare function createParserWithMetadata(options?: ParserOptions): (src: string, rawSrc?: string, sourceOffset?: number) => ParseResult; //#endregion //#region src/plugins.d.ts interface CollectNodeRendererOptions extends DebugOptions { debugTarget?: DebugInstrumentationTarget; } /** A function that produces the plugins, loading them first if they are not in the bundle yet. */ type PluginsLoader = () => AIGuiPlugin[] | Promise; /** * Either the plugins themselves or a function that loads them. * * Diagrams, maths and charts are the heaviest thing a page carrying them loads, and an answer that * never draws one should not pay for them. A loader lets the host defer the import: the renderer * shows plain markdown until it resolves and reparses what has arrived by then, so the host does * not have to hold the stream or replay it. */ type PluginSource = AIGuiPlugin[] | PluginsLoader; /** * Resolve a plugin source to what the caller can act on now: the array itself, or a promise of it. * * The array form must stay synchronous. Deferring it by a microtask would render the first chunk * of every answer under the plain-markdown grammar and then redraw it, which is a visible flash * for a host that had its plugins all along. */ declare function loadPlugins(source?: PluginSource): AIGuiPlugin[] | Promise; /** * Whether two lists hold the same plugins in the same order. * * `plugins={[chart, katex]}` is a new array on every render and the same two plugins every time. * What matters is the members, not the array. */ declare function samePlugins(a?: AIGuiPlugin[], b?: AIGuiPlugin[]): boolean; /** * Reject anything in the list that is not a plugin, naming what to do about it. * * Every plugin package exports a factory, so `plugins: [katex]` instead of `plugins: [katex()]` is * the easiest mistake to make — and it used to be the quietest. A function has a `name` of its * own, `"katex"`, so the renderer accepted it, found no `extendParser` and no `nodeRenderers`, and * did nothing: no error, no warning, markdown still rendering, only the maths and the diagrams * missing. A product can ship like that and nobody notices until someone asks why an equation is * plain text. Misconfiguration should be loud, so this throws. */ declare function assertPlugins(plugins: readonly unknown[] | undefined, label?: string): void; /** Merge every plugin's `nodeRenderers` into a single map (later plugins win). */ declare function collectNodeRenderers(plugins?: AIGuiPlugin[], debugOptions?: CollectNodeRendererOptions): Record; /** The set of node types claimed by the given plugins. */ declare function pluginNodeTypes(plugins?: AIGuiPlugin[]): Set; //#endregion //#region src/plugin-styles.d.ts /** * The stylesheet every renderer needs regardless of which plugins are loaded. * * Model output is written without knowing the viewport, so a wide table, a long code line or a * diagram sized for a desktop will otherwise push the page sideways on a phone. Each block is * made to scroll inside its own box instead of widening the column that holds it. */ declare const baseCss: string; /** One plugin's stylesheet, keyed by the plugin that owns it. */ interface PluginStyle { name: string; css: string; } /** * Collect the stylesheets of the given plugins, base styles first. * * Plugins declare `css` but cannot inject it themselves — they never see the document. Each * plugin appears once even if it is passed twice, and later plugins of the same name win, which * matches how `collectNodeRenderers` resolves duplicates. */ declare function collectPluginStyles(plugins?: AIGuiPlugin[]): PluginStyle[]; /** * Put the plugins' stylesheets in the document, once each. * * Called on every render by every renderer on the page, so it must be idempotent: a stylesheet * already present is left alone rather than duplicated. No-ops without a document, which is what * server-side rendering gets. */ declare function injectPluginStyles(plugins?: AIGuiPlugin[], doc?: Document): void; //#endregion //#region src/i18n.d.ts /** * Locale handling for both sides of a rendered answer: the strings a plugin draws on screen, and * the guidance a plugin gives the model. * * Locales are BCP-47 tags ("zh-CN", "pt-BR", "en"). English is the fallback and is always * complete, so a partial translation degrades to English strings rather than to blank UI. */ /** A locale tag, e.g. "en", "zh-CN". */ type Locale = string; /** The strings of one locale, keyed by a stable id the plugin chooses. */ type Messages = Record; /** Every locale a plugin ships, keyed by tag. `en` is required as the fallback. */ type MessageBundle = Record & { en: Messages; }; /** The default locale, used when a host does not say otherwise. */ declare const DEFAULT_LOCALE = "en"; /** * Pick the messages for a locale: exact match, then the base language, then English. * * "zh-CN" therefore finds a "zh-CN" bundle, falls back to "zh", and finally to English — a host * asking for a regional variant nobody translated still gets the language. */ declare function resolveMessages(bundle: MessageBundle, locale?: Locale): Messages; /** Look up one string, falling back to English and finally to the key itself. */ declare function translate(bundle: MessageBundle, locale: Locale | undefined, key: string): string; /** * A lookup function bound to one bundle and locale. * * Plugins render many strings per node, so resolving the bundle once and closing over it keeps * the per-string cost to a map lookup. */ declare function translator(bundle: MessageBundle, locale?: Locale): (key: string) => string; /** The locales a bundle actually carries. */ declare function availableLocales(bundle: MessageBundle): Locale[]; //#endregion //#region src/export-image.d.ts /** * Turn what a plugin drew into a PNG the reader can keep. * * Charts and diagrams are the point of rendering a model's answer, and the first thing anyone * wants to do with one is save it. There is no browser API for "download this SVG as an image": * it has to be serialized, loaded through an `Image`, and painted onto a canvas. Every host that * needs it rewrites those twenty lines, and gets the same details wrong — a transparent * background that turns black in a viewer, a blurry export on a retina screen, or a light * background hardcoded into a page that has since gone dark. */ interface ExportImageOptions { /** * Device pixels per CSS pixel. Defaults to the screen's own ratio, so an export looks as sharp * as what it was copied from rather than half the resolution on a retina display. */ scale?: number; /** * What to paint behind the drawing. An SVG is usually transparent, which reads as black in most * image viewers. Pass the page's own background — or "transparent" to keep the alpha channel. */ background?: string; /** Overrides for the drawing's own size, in CSS pixels. */ width?: number; height?: number; type?: "image/png" | "image/jpeg" | "image/webp"; quality?: number; /** * Told about each drawing that could not be exported, instead of the whole export failing. * * Some drawings cannot be rasterised at all: a Mermaid diagram lays its labels out in a * ``, and a browser taints the canvas the moment one is drawn onto it, so `toDataURL` * throws `SecurityError`. One of those used to take every other drawing on the page down with it. */ onSkip?: (drawing: SVGElement, reason: unknown) => void; } interface ExportedImage { dataUrl: string; width: number; height: number; } /** * Render an SVG element to a raster data URL. * * The element is serialized as it stands, so whatever a plugin drew — including the theme it drew * it in — is what gets exported. */ declare function exportSVGToImage(svg: SVGElement, options?: ExportImageOptions): Promise; /** * Export every drawing inside a rendered answer. * * A host holds the element it handed to the renderer, not the individual charts, and the plugins * decide what lands in it. */ declare function exportRenderedImages(root: ParentNode, options?: ExportImageOptions): Promise; /** Save an exported image under the given file name. */ declare function downloadImage(image: ExportedImage, filename: string): void; //#endregion //#region src/diff.d.ts /** Produce a minimal set of patches turning `prev` into `next`, keyed by node key. */ declare function diffAst(prev: ASTNode[], next: ASTNode[]): Patch[]; /** Apply patches in order, primarily for framework adapters and verification. */ declare function applyPatches(nodes: ASTNode[], patches: Patch[]): ASTNode[]; //#endregion //#region src/renderer.d.ts /** * Streaming render orchestrator: accumulate incoming markdown chunks, repair the * partial buffer, parse it into an AST, diff against the previous AST, and emit * the resulting patches via `onPatch`. */ declare class Renderer { readonly debugSource: "renderer"; private buffer; private prevAst; private parse; private parsed?; private options; private sanitize; private generation; private activeFeed?; private renderScheduled; private scheduleGeneration; private readonly debug; constructor(options?: RendererOptions); /** The plugins currently parsing and rendering this renderer's output. */ get plugins(): readonly AIGuiPlugin[]; /** * Swap the plugins in and redraw the answer already buffered. * * A plugin bundle is worth deferring — diagrams, maths and charts together outweigh everything * else a page loads — but the stream does not wait for the import, so whatever arrived meanwhile * was parsed under the plain-markdown grammar. Reparsing the buffer this renderer still holds is * what turns that text into diagrams, which is why a host does not have to remember what it * pushed and replay it once the chunk lands. * * Passing the same plugins again is a no-op, so a host may call this on every render. */ setPlugins(plugins: AIGuiPlugin[] | undefined): void; private registerPluginCards; get debugEnabled(): boolean; emitDebug(type: string, data?: Record): void; push(chunk: string): void; subscribeDebug(listener: DebugEventListener): () => void; feed(source: FeedSource, options?: FeedOptions): Promise; reset(): void; /** Immediately render pending buffered input, bypassing the scheduler. */ flush(): void; private scheduleRender; private cancelActiveFeed; private render; private sanitizeNodesWithDebug; } //#endregion //#region src/stream-router.d.ts /** A consumer of a channel's text stream. `Renderer` satisfies this shape. */ interface ChannelSink { push(chunk: string): void; } /** Handler for structured data values (and text deltas as raw strings). */ type ChannelHandler = (value: unknown) => void; type StreamChunk = string | Uint8Array; /** Demultiplex JSON-lines and standards-compliant SSE into named channels. */ declare class StreamRouter { private readonly sinks; private readonly handlers; /** Most recently received SSE `id` field. */ lastEventId: string; /** Most recently received valid non-negative SSE reconnection delay. */ retry: number | undefined; channel(name: string, sink: ChannelSink): this; on(name: string, handler: ChannelHandler): this; feed(source: AsyncIterable | ReadableStream): Promise; private dispatchPayload; private dispatchEnvelope; private routeDelta; private routeData; } //#endregion //#region src/build-system-prompt.d.ts /** * The fencing rule in one locale. * * Exported for hosts that assemble the guidance themselves instead of calling * `buildSystemPrompt`; that function already includes it. */ declare function fencingRule(locale?: string): string; interface BuildSystemPromptOptions { base?: string; registry?: CardRegistry; plugins?: AIGuiPlugin[]; /** * The locale to write the guidance in, as a BCP-47 tag, e.g. "zh-CN". * * A product whose persona says "always answer in Chinese" ends up appending English rules to * it, which reads as a contradiction. Plugins fall back to English for locales they have not * been translated into. */ locale?: string; } /** * Assembles the LLM system-prompt guidance: an optional base, the registered * cards' spec, and every plugin's promptSpec. Consumers prepend this to their * own system prompt so the model knows which fenced blocks it may emit. */ declare function buildSystemPrompt(options?: BuildSystemPromptOptions): string; //#endregion //#region src/model-stream.d.ts interface Citation { type?: string; id?: string; url?: string; title?: string; citedText?: string; [key: string]: unknown; } interface Usage { inputTokens?: number; outputTokens?: number; totalTokens?: number; [key: string]: unknown; } type ModelStreamEvent = { type: "content"; delta: string; } | { type: "reasoning"; delta: string; } | { type: "citation"; data: Citation; } | { type: "usage"; data: Usage; } | { type: "error"; error: unknown; }; type ByteStreamSource = Response | ReadableStream | AsyncIterable; interface StreamParseOptions { signal?: AbortSignal; onMalformed?: (error: Error, input: string) => "skip" | void; } interface SSEOptions extends StreamParseOptions { parseJSON?: boolean; doneData?: string | false; } interface SSEEvent { data: T; event?: string; id?: string; retry?: number; } declare function parseSSE(source: ByteStreamSource, options: SSEOptions & { parseJSON: true; }): AsyncGenerator>; declare function parseSSE(source: ByteStreamSource, options?: SSEOptions): AsyncGenerator>; declare function jsonLines(source: ByteStreamSource, options?: StreamParseOptions): AsyncGenerator; declare const ndjson: typeof jsonLines; declare function textLines(source: ByteStreamSource, options?: Pick): AsyncGenerator; declare function contentDeltas(events: AsyncIterable): AsyncGenerator; declare function mockModelStream(events: Iterable | AsyncIterable, options?: { delayMs?: number; signal?: AbortSignal; }): AsyncGenerator; declare function readableBytes(chunks: Iterable | AsyncIterable): ReadableStream; //#endregion 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, fencingRule, getActionKey, getIdleActionState, injectPluginStyles, isCardPatchResult, jsonLines, loadPlugins, mockModelStream, ndjson, parsePartialJSON, parseSSE, pluginNodeTypes, readableBytes, repairMarkdown, resolveMessages, safeDebugValue, samePlugins, sanitizeHtml, sanitizeRenderedHtml, textLines, translate, translator, validateJSONSchema };