Skip to content

流式 Markdown

Agent 的输出通常不是一次性返回,而是 token 流。

如果前端把这些 token 直接拼进 DOM,就会出现很差的体验:

  • 半截表格先渲染成乱码
  • fenced code block 没闭合就提前落成错误结构
  • HTML / markdown 结构反复抖动

Agentdown 的处理方式是:

把流式文本先交给 assembler,再决定什么时候真正渲染成稳定 block。

默认用 createMarkdownAssembler()

ts
import {
  createMarkdownAssembler,
  defineEventProtocol,
  useSseBridge
} from 'agentdown';

const protocol = defineEventProtocol({
  content: (event) => ({
    type: 'content.append',
    id: 'stream:assistant',
    content: event.text
  })
});

const session = useSseBridge({
  source: '/api/agent/sse',
  protocol,
  assemblers: {
    markdown: createMarkdownAssembler()
  }
});

draft 和 stable

流式 markdown 不是“来了就立刻渲染”,而是有两个状态:

状态含义
draft内容还在增长,结构可能不完整
stable内容足够稳定,可以作为正式 block 落地

典型规则:

  • 表头没完整,不渲染 table
  • fenced code block 没闭合,不渲染 code block
  • 某些复杂 inline / html 结构没稳定,不强行提前渲染

本地 token 流例子

ts
import {
  createMarkdownAssembler,
  defineEventProtocol,
  useAsyncIterableBridge
} from 'agentdown';

type Packet = {
  event: 'token';
  text: string;
};

async function* createTokenStream(): AsyncIterable<Packet> {
  const tokens = [
    { event: 'token', text: '# 标题\\n\\n' },
    { event: 'token', text: '| 城市 | 天气 |\\n' },
    { event: 'token', text: '| --- | --- |\\n' },
    { event: 'token', text: '| 北京 | 晴 |\\n' }
  ] as const;

  for (const token of tokens) {
    await new Promise((resolve) => setTimeout(resolve, 120));
    yield token;
  }
}

const session = useAsyncIterableBridge<Packet>({
  source: createTokenStream(),
  protocol: defineEventProtocol<Packet>({
    token: (event) => ({
      type: 'content.append',
      id: 'stream:assistant',
      content: event.text
    })
  }),
  assemblers: {
    markdown: createMarkdownAssembler()
  }
});

MarkdownRenderer 也有窗口化

如果你是在“长文阅读”场景,而不是聊天 surface,也可以直接把完整 markdown 丢给 MarkdownRenderer

它本身也带:

  • text slab
  • markdown windowing
  • telemetry

所以它并不只是一个“parse 一次然后全量渲染”的组件。

HTML 安全

MarkdownRenderer 默认 allowUnsafeHtml = false

也就是说:

  • 原生 HTML 默认不会直接无条件注入
  • 更复杂的 HTML 会优先走 safe fallback / block 渲染

如果你的场景确实要开放不安全 HTML,再显式打开。

调试 draft 阶段

开发阶段如果你想看“为什么这段内容还没 stable”,可以配合:

  • RunSurfaceDraftOverlay
  • Agent Devtools

这对表格、代码块、长列表这些结构特别有用。

Released under the MIT License.