4.8 KiB
Agent Note: Web 对话中安全的 assistant Markdown
Status: implemented
English | 中文
问题
Web 对话通过会话事件、历史回放与流式累积保留 assistant Markdown 源文本,但其最末端的文本原语会按字面渲染源文本。若修改共享原语,用户消息与 steering(中途引导)消息也会被格式化;若在运行时中解析,则会把呈现状态混入不依赖 React 的会话投影。
决策
@deepseek-ai/dsh-client-ui-primitives 导出 MarkdownText,用作不受信任的 assistant 文本渲染器;ui-conversation 仅为 assistant text 块选择该渲染器。已完成的历史消息、流式输出尾部与被中断的部分输出已经共用 AssistantMarkdown,因此无需更改事件或快照,它们便会采用同一渲染器。用户消息与 steering 消息继续使用 MessageText,并保持按字面渲染。
MarkdownText 使用 react-markdown 与 remark-gfm,从 AST 构建 React 元素。它覆盖 CommonMark 块,以及 GFM 表格、任务列表、删除线与自动链接,且不解析原始 HTML。围栏代码经共享的 CodeBlock 路由;该组件用客户端的 shiki 单例(--shiki-* token)高亮已注册语法,否则回退为纯等宽文本。轮次流式输出期间,围栏停留在纯文本分支,以免每收到一个分片就对增长中的围栏重新分词。
视觉间距、表格、链接、引用块、行内代码与代码块外框遵循 deepsuite @deepseek/md(markdown.css / code-block.css),并使用同一套 --dsw-alias-markdown-*、--dsw-font-markdown-*、--dsw-alias-border-l* 与 --dsw-alias-label-* token。链接使用 --dsw-alias-state-business-primary(deepsuite 的样式表使用 --dsw-alias-brand-text,仅在 newDesign 下为蓝色;design-platform 将 brand-text 保持为近黑色,此处不做重新调色)。CodeBlock 提供语言横幅与复制控件(复制 / 复制成功)。引用胶囊、KaTeX、标题锚点、thinking-small markdown 变体,以及自定义 □/☑ 任务标记均不在范围内,直至存在匹配的产品 DOM;GFM 任务列表继续使用原生复选框。
该依赖在 ui-primitives 中显式声明;由于这一纯库由 Web shell 预置,解析器与高亮器会成为初始浏览器 bundle 的一部分。
不受信任输出策略
assistant 生成的链接目标地址仅限绝对 HTTP、HTTPS 与 mailto URL。HTTP(S) 链接会在新标签页中打开,并带有 rel="noopener noreferrer";相对目标地址与其他协议会渲染为不可导航的文本。Markdown 图片遵循独立的远程图片策略。由于流水线中未引入 HTML 解析器,原始 HTML 仍是不会生效的源文本。Shiki 输出是由围栏文本生成的静态 span 树(不含脚本或用户 HTML)。
围栏代码与 GFM 表格各自处理横向溢出,因此较长内容无法撑宽对话栏。
考虑过的替代方案
**将现有的 mdast 与 micromark 开发依赖提升为正式依赖,并维护自定义 React walker。**此方案避免引入新的解析器体系,但产品需要自行负责每种节点映射、GFM 扩展和安全敏感的渲染分支。专用 React 渲染器将这套遍历交由上游维护,同时保留 AST 到 React 的处理路径。
**将 MessageText 替换为 Markdown 渲染。**这会产生格式化用户提示词与 steering 的副作用。在产品明确选择此行为之前,这两类输入内容仍按字面渲染。
**将 Markdown 解析为会话快照。**这会让 React 节点或呈现层 AST 成为持久的运行时状态,并重新引入最终输出与流式输出之间的模式边界。解析仍留在呈现层的叶节点中。
通过净化启用原始 HTML。 原始 HTML 当前没有产品需求,并且会扩大可执行内容边界,因此保持禁用,无需增加净化器依赖。远程图片由后续的图片策略约束。
**移植 deepsuite 的 Prism highlight.css 与 mdast 管线。**外观一致性由 CSS Modules 与共享的 --dsw-* token 负责;高亮仍走现有的 shiki 允许列表,使客户端不必引入第二套高亮器或 Prism class 契约。
后果
assistant 回复在流式输出与回放期间都会一致地渲染为语义化 Markdown,而工具卡片、推理行、交互、用户气泡和宿主协议保持不变。每次累积更新后,流式输出都会重新解析当前文本;未完成的 Markdown 可能暂时改变结构,但独立的尾部会限定 React 失效范围,最终事件也不会切换渲染器。代码围栏与工具及详情表层共用同一外框与复制路径。初始 Web shell 包含 Markdown 解析器、GFM 运行时与 shiki 允许列表;cite/math/anchor/thinking-small 表层仍暂缓。