# 统一富内容与流式协议 ## 1. 目标 同一套 `ContentBlock` 用于 Native Message、Agent 输出、社区内容、私信、协作评论和结构化产物。用户编辑可接受 Markdown 内嵌 Mermaid/数学语法;服务端在保存或发送前解析并规范化为类型化 Block。 原则: - 类型化 Block 是 API 和存储规范形态; - Markdown 是常用创作输入和可移植表示,不是所有媒体的唯一协议; - 每个 Block 有稳定 ID 和独立状态,可局部流式、报错、重试、替换和无障碍展示; - 不执行 HTML、JavaScript、Mermaid click callback 或不可信媒体脚本。 ## 2. Content 容器 ```json { "content": [ {"id": "block_01...", "type": "markdown", "text": "## 结论\n..."}, {"id": "block_02...", "type": "mermaid", "source": "flowchart LR\nA-->B"}, {"id": "block_03...", "type": "math", "notation": "latex", "display": "block", "source": "E=mc^2"}, {"id": "block_04...", "type": "image", "file_id": "file_01...", "alt": "架构图"} ] } ``` 所有 Block 通用字段: | 字段 | 必需 | 说明 | | --- | --- | --- | | `id` | 是 | Message 内稳定 `block_` ID | | `type` | 是 | Block 类型 | | `status` | 响应中 | `pending`、`streaming`、`completed`、`failed`、`cancelled` | | `annotations` | 否 | citation、来源、敏感度、语言等类型化标注 | | `metadata` | 否 | 只允许 Schema 登记字段,不存任意秘密 | `id` 在创建请求中可省略,由服务端生成;流事件必须携带服务端 Block ID。 ## 3. Block 类型 ### 3.1 文本与结构 | `type` | 关键字段 | 流式策略 | | --- | --- | --- | | `text` | `text`, `language?` | 字符增量 | | `markdown` | `text`, `flavor="hunjing-gfm-v1"` | 字符增量,安全增量解析 | | `code` | `source`, `language?`, `filename?` | 字符增量,Shiki 节流高亮 | | `quote` | `content[]`, `source_ref?` | 子 Block 增量 | | `table` | `columns`, `rows`, `caption?` | 行级追加或完成后展示 | | `callout` | `kind`, `title?`, `content[]` | 子 Block 增量 | Markdown 方言支持 CommonMark/GFM 的标题、段落、列表、引用、表格、任务列表、链接、图片引用、行内代码和 fenced code。原始 HTML 默认禁用。 ### 3.2 数学公式 ```json { "id": "block_math", "type": "math", "notation": "latex", "display": "block", "source": "\\int_0^1 x^2 dx = \\frac{1}{3}", "fallback_text": "从 0 到 1 的 x² 积分等于 1/3", "label": "公式 1" } ``` - `notation` 1.0 为 `latex` 或 `asciimath`;服务端规范化优先输出 LaTeX。 - `display` 为 `inline` 或 `block`。 - `fallback_text` 是可访问性与解析失败降级描述,不是隐藏推理。 - Markdown 输入支持 `$...$`、`$$...$$`、`\(...\)`、`\[...\]`;只有闭合定界符后才转换为 Math Block。 - 常见公式描述应同时包含公式与自然语言说明,例如变量、单位、适用条件和来源,不能只输出不可读符号。 ### 3.3 Mermaid ```json { "id": "block_mermaid", "type": "mermaid", "source": "sequenceDiagram\nA->>B: request", "title": "请求时序", "caption": "客户端到网关的调用", "fallback_code_language": "mermaid" } ``` - 只在 Block `completed` 后渲染;流式期间展示代码/骨架和“图表生成中”。 - 禁止 `click`、外部脚本、HTML label、任意 URL callback 和危险初始化配置。 - 渲染使用 strict security level、隔离 worker/iframe 和超时/节点数/源码长度限制。 - 解析失败展示转义后的 Mermaid 源码与错误摘要,不丢失内容。 - Markdown fenced ` ```mermaid ` 在规范化时转换为 Mermaid Block。 ### 3.4 媒体与文件 | `type` | 关键字段 | | --- | --- | | `image` | `file_id` 或受控 `url_ref`, `alt`, `width?`, `height?`, `caption?` | | `video` | `file_id`, `poster_file_id?`, `caption?`, `transcript_file_id?`, `duration_ms?` | | `audio` | `file_id`, `transcript?`, `duration_ms?`, `waveform?` | | `file` | `file_id`, `filename`, `media_type`, `size_bytes`, `description?` | | `gallery` | `items[]`(image/video refs), `layout?` | | `avatar` | `asset_id`, `format`(live2d/vrm/gltf), `preview_file_id?` | - API 不接受 `file://`、`javascript:`、`data:text/html` 等 URL。 - 内部资源使用 `file_id`,下载通过授权 API 或短期签名 URL。 - 图片 `alt` 必需;视频应提供字幕/转写状态;音频应提供转写或文本摘要。 - 媒体生成流只发送 `asset.progress` 与最终 File/Media Block,不把未完成二进制塞进 SSE JSON。 ### 3.5 Agent 与工具 | `type` | 说明 | | --- | --- | | `tool_call` | 工具名、调用 ID、参数脱敏视图、状态和审批引用 | | `tool_result` | 调用 ID、结构化摘要、文件引用、错误状态 | | `agent_status` | Agent/ChildRun、阶段、进度和等待原因 | | `approval_request` | 动作、影响、参数摘要、有效期和 Approval ID | | `artifact` | Run 产物 ID、类型、版本和下载/打开动作 | | `citation` | 来源 ID、标题、引用文本与定位(文本区间、图像区域、PDF 页区、音视频时间码);保存原文快照,来源正影后提示后继版本 | | `error` | 局部 Block 错误、可否重试和 fallback | 普通 Message API 不允许客户端伪造系统产生的 `tool_call`、`tool_result`、`agent_status` 和 `approval_request`;这些 Block 只由 Runtime 写入。 ## 4. Markdown 输入规范化 当客户端提交 `markdown` Block: 1. 保存用户原始 Markdown 的规范化副本及 hash; 2. 使用确定性 parser 解析 AST; 3. fenced `mermaid` 转为 Mermaid Block; 4. 闭合数学定界符转为 Math Block; 5. fenced 其他语言转为 Code Block; 6. Markdown 图片只允许已上传 `file_id` 语法或经过 URL 安全代理的 HTTPS URL; 7. 输出 canonical ContentBlock 列表和 parser version; 8. 编辑后重新规范化,创建 Message Revision,不静默改旧 Block。 导出可选择 canonical JSON 或重建的 Hunjing Markdown;重建时复杂 Block 使用稳定 fenced/directive 语法。 ## 5. 流式事件 Run SSE 使用统一 envelope;Block 事件如下: ```text content.block.started content.block.delta content.block.progress content.block.completed content.block.failed content.block.cancelled message.completed ``` ### 5.1 Started ```json { "type": "content.block.started", "data": { "message_id": "message_01...", "block": {"id": "block_01...", "type": "markdown", "status": "streaming"}, "index": 0 } } ``` ### 5.2 Delta ```json { "type": "content.block.delta", "data": { "message_id": "message_01...", "block_id": "block_01...", "delta_index": 7, "delta": {"type": "text_append", "text": "新增文本"} } } ``` Delta 类型: - `text_append`:text/markdown/code/math/mermaid source 追加; - `json_patch`:只用于 Schema 明确允许的结构化 Block,采用受限 RFC 6902 子集; - `row_append`:table 追加完整行; - `annotation_add`:追加 citation/来源; - `resource_ready`:绑定已完成 file/media/artifact; - `status_replace`:Agent/tool/approval 状态转换。 每个 Block 的 `delta_index` 从 0 单调递增;同一 delta 可重放,客户端按 `(block_id, delta_index)` 去重。缺口出现时暂停该 Block 增量展示,并通过 Run 重放或最终 Message 对账。 ### 5.3 Completed ```json { "type": "content.block.completed", "data": { "message_id": "message_01...", "block": { "id": "block_01...", "type": "markdown", "status": "completed", "text": "完整权威内容" }, "content_hash": "sha256:..." } } ``` Completed Block 是该 Block 的权威快照。客户端丢弃临时解析状态,以完整 Block 重建并校验 hash。`message.completed` 携带最终 Message ID、Block 顺序和版本,最终 GET Message 可恢复所有内容。 ## 6. 分级实时策略 | 内容 | 流式展示 | | --- | --- | | Plain text / Markdown | 增量显示;解析节流;未闭合结构以文本态保留 | | Code | 增量显示;按行/时间片重新高亮,不逐 token 运行 Shiki | | Inline/Block Math | 累积到完整定界符或 Block 完成后渲染;此前显示源码占位 | | Mermaid | 只在 Block completed 后渲染;此前显示源码/骨架 | | Image/Video/Audio/File | progress 占位;`resource_ready` 后加载;失败可重试 | | Table | 完整行追加,避免半行抖动 | | Tool/Agent/Approval | 状态替换,不拼接自由文本 | ## 7. 限制与安全 - 单 Message、Block、源码、节点、表格、媒体和嵌套深度的限额由配置发布,但 Schema 必须有硬上限。 - Markdown URL 经 scheme/host 校验,外链加 `noopener noreferrer`,可选经安全跳转页。 - HTML 默认不解析;需要 HTML 的可信内部 Block 也必须经 allowlist sanitizer。 - KaTeX/MathJax 禁用信任命令、宏注入和外部资源;限制宏展开和源码长度。 - Shiki 只高亮不执行代码;复制/下载保留原源码。 - Mermaid 渲染隔离且不允许交互脚本。 - 媒体采用 CSP、正确 Content-Type、`nosniff` 和独立对象域。 ## 8. 兼容 API 映射 OpenAI/Anthropic Content Block 在 Gateway Adapter 转为内部 Block,但兼容响应仍保持目标协议。无法映射的 Native 类型: - 优先转换为目标协议支持的 text/image/document/tool block; - Mermaid/math/code 可降级为 Markdown/fenced source; - video/audio/avatar/artifact 使用文本说明 + 受控文件引用; - 不能无损、安全表达时返回明确 unsupported,不伪造协议字段。 ## 9. 分支对话交互态 分支对话(镜树)在流式协议之上的交互态语义(承接旧版镜树专题 PRD,字段与枚举进入 OpenAPI 前以本节为基线): - **临时芽体**(Block `status: streaming` 的未完成答复):完成后才固化为权威快照;流式期间展示为进行态,不进入归真候选。 - **残答**(`interrupted`):被停止或中断的不完整答复封存可查,不作为完成答复;默认上下文不自动包含。 - **续生**(`generation_resume`):从残答继续生成时创建**新** Message 并引用残答来源;残答本身不被修改或"补全"。 - **引用合流**(`citation_intake`):树间内容经显式引用带入另一棵树,以 citation/引文锚表达;不物理合并两树,被引内容保留原树的界域与归真作用域。 - **结题**(`topic_conclusion`):标记主题阶段完成并生成可审阅总结(总结内容走候选确认);结题后仍可复照继续,不删除历史。 - **回响**(`task_echo`):后台受托任务完成后,结果以回响形态回到原分支,作为带来源的观察呈现(`agent_status`/`artifact` Block);经用户接纳并归真后才成为正式答复。 - **上下文镜**(`context_mirror`):发送或继续前向用户渐进展示实际采用的上下文范围、压缩与省略项;与 ContextSnapshot 同源数据,是用户可见的透明视图而非诊断特权。 不变量:重新生成不覆盖旧候答;后代节点绑定用户实际依据的候答;已归真经历不因切换候答、删除旁枝或移动工作入口而回滚;未归真旁枝不自动入忆。