T3 Code 非官方中文翻译不是官方产品,也不是 T3 Tools 官方文档。源提交 d2c25950b4d9英文原文GitHub 源文件
Skip to content

Composer 上下文引用 ​

给维护者。在使用 T3 Code?见 docs/user。

内联上下文引用让一条用户消息从散文里的精确位置指向一份带类型的载荷:一张图片、一个文件、一段终端摘录、一个选中的页面元素、一条预览批注、一条审查评论、一次文件提及,或一项技能。本文覆盖线路契约和纯编解码器。编辑器、渲染和剪贴板行为落在后来的 PR 里,到达时会在这里有自己的小节。

两个相联的概念 ​

  • 上下文记录是载荷。它住在 message.context.records 里,以 contextId 为键。记录从不包含字节:图片和文件记录按 id 绑定到已有的 ChatAttachment。
  • 上下文引用是文档里的一次出现。它是 message.text 里的一个 Markdown 链接,只携带种类和 contextId。两个引用可以指向一条记录。引用的标签是显示文本,从不是身份。

composerContext.ts 定义记录联合的第 1 版。每条记录都有 version、contextId、kind 和 label,加上长度有界的、按种类区分的字段。联合是开放的:这个构建不认识的种类解码为 UnknownContextRecord,并保留它的 payload,已知种类被排除在该成员之外,因此一条畸形的图片记录会让自己的模式失败,而不是未经检查地滑过去。OrchestrationMessageContext 用 ForwardCompatibleArray 包装记录,因此一条无法解码的记录会被丢掉,而不是让整条消息失败。该字段在 V2 对话消息以及开始或排队一个回合的命令上是可选的;编排器原样把它带过去。

身份命名空间 ​

  • ComposerContextId:持久的载荷身份。带品牌。值匹配 [a-z0-9_-]+,不要求 ctx_ 前缀。
  • ComposerContextReferenceId(ref_…):文档里的一次出现。带品牌。住在编辑器状态里,不在线路上,规范 Markdown 被重新解析时会重新生成。
  • ChatAttachmentId:已有的、服务器拥有的附件资源。记录的 attachmentId 是绑定,不是芯片的身份,因此上传规范化可以重命名资源而不重写引用。

客户端铸造上下文 id 和引用 id。共享代码不这样做,因为 Effect lint 插件在那里拒绝直接的 crypto.randomUUID()。

规范引用语法 ​

composerContextReferences.ts 拥有这套语法:

text
[label](t3-context://v1/<kind>/<contextId>)
![label](t3-context://v1/image/<contextId>)

解析器精确接受 t3-context: 方案、v1 主机、一个匹配 [a-z][a-z0-9-]{0,39} 的种类段,以及一个不区分大小写地匹配 [a-z0-9_-]{1,128} 的 id 段。查询字符串、片段、凭据和额外的段都会被拒绝。标签被清理,以便在 Markdown 链接里存活(没有方括号或换行,最多 200 个字符,绝不为空)。解析失败的链接是普通文本。collectComposerInlineTokens 已经拒绝文件链接的 URI 方案,因此上下文链接从不会被误认为提及。

提供方投影 ​

projectComposerContextForProvider({ text, records }) 构建提供方读到的内容:

  1. 每个引用变成原地标记:[Image: shot.png; ref=ctx_1]。
  2. 尾部的 <t3_context version="1"> 信封为每个唯一的被引用 id 保存一条 <context kind id> 条目,按首次引用的顺序。从未被引用的记录不会发出。没有记录的被引用 id 变成 <context … unavailable="true"/>。提及和技能记录产生标记,但没有条目。未知种类把它们的载荷作为 JSON 发出。
  3. 捕获的文本是数据:任何会打开或关闭 t3_context 或 context 的 < 都会被转义,因此一行终端或一条 PR 评论不能伪造一条记录。

附件字节走已有的附件通道;信封只携带元数据。没有引用的文本原样返回。

遗留消息 ​

此功能之前发送的消息携带尾部的 <terminal_context>、<element_context> 和 <preview_annotation> 块、<review_comment> 块,以及 U+FFFC 终端占位符。composerContextLegacy.ts 在内存里升级它们:

  • 审查块就地变成引用。跟在原文后面的块最后追加,与旧的发送顺序一致。
  • 尾部块按相反的发送顺序从末尾剥下(预览、元素、终端)。
  • 占位符按顺序绑定到终端条目;没有占位符的条目被追加。
  • Id 是确定的(legacy_<kind>_<n>),因此重复运行升级是幂等的。

事件历史从不被改写。apps/web/src/lib/ 里现有的 Web 解析器会保留,直到记录渲染器改用记录。

编辑器模型(Web 和桌面) ​

ComposerPromptEditorTiptap.tsx 里的 ComposerContextReferenceExtension 是每种上下文的内联 Tiptap 节点。它存储 kind、contextId、label 和一个每次出现的 referenceId,它的文本内容是规范链接。提示字符串携带载荷身份和位置,因此重建编辑器会恢复等价的芯片;它不保留编辑器本地的出现 id。使用 U+FFFC 序号占位符的旧草稿在注水时迁移:占位符按数组顺序绑定到终端上下文,然后提示没有提到的任何上下文会作为链接被前置。

记录目前仍留在草稿存储的带类型数组里。编辑器从它们构建一个以 contextId 为键的 Map(composerContextRecordsFromDraft),并通过 ComposerContextRecordsContext 提供它。ComposerContextReferenceChip 查找记录并渲染该种类的芯片;未知种类或缺失的记录渲染未解析芯片,而不是消失。移除一个芯片只移除那一次出现;编写器的更改处理器把被引用的 id 和草稿数组比较,丢掉没有芯片指向的记录。

第 1 版有意让种类的呈现在每个客户端里保持显式,而不是暴露一个运行时处理器注册表。契约和编解码器是共享的;Web/桌面渲染丰富的芯片,移动端渲染可读标签。只有当第三方或运行时定义的种类必须提供无法随客户端发布的行为时,才添加注册表。同样,持久的出现 id 只有在未来的功能需要跨序列化边界指向一次出现时,才属于规范引用语法。

终端上下文现在走和其他每条上下文记录相同的发送路径:持久化的消息保留它的规范链接和结构化记录,而提供方投影把链接换成可读标记,并在上下文信封里包含摘录一次。遗留的尾部 <terminal_context> 形式只在阅读较旧客户端发送的消息时解析。

发送和阅读消息 ​

编写器把 message.text 作为带引用链接的规范散文发送,message.context.records 从草稿构建(apps/web/src/lib/composerContextRecords.ts 里的 buildMessageContext)。过期的终端摘录会从两边丢掉。服务器在回合开始时投影提供方文本(projectComposerContextForProvider),因此持久化的消息保持可读,提供方收到标记加上一个信封。

审查评论和预览批注通过存储变更器进入草稿。已挂载的编写器注册一个上下文插入处理器,使来自面板的引用落到它当前或上次已知的插入点;没有编写器挂载时,存储把它们追加进去。终端摘录和附件使用同样的插入点优先行为。在编辑器里移除芯片会移除记录;移除仍被引用的预览截图缩略图会移除它的批注和芯片。

记录用 resolveUserMessageContext 解析一条消息:结构化上下文按原样使用,较旧的消息在内存里升级。ChatMarkdown 通过 renderContextReference 渲染 t3-context:// 链接,时间线再通过 Web 上下文呈现注册表把它们映射成芯片。注册表为每个已知种类声明紧凑、详情和展开能力,拒绝重复的表面处理器,并提供未解析回退。终端摘录、元素、审查评论和预览批注打开结构化详情弹出层;图片和视频使用共享的媒体模态框。移动端把上下文链接渲染成它们的标签。

拉取请求摘要目前作为带可选类型化 pullRequest 元数据的审查评论记录传送。元数据是附加时的编号、标题、URL、分支、状态和草稿标志的快照。Web 和桌面渲染紧凑的 #number 标签,并从该快照派生它的状态语气。悬停显示快照详情;激活时对照当前环境解析 URL,并在对话线程的右侧面板打开拉取请求。元数据加入之前写下的记录保留它们的遗留详情和中性的拉取请求语气。

附件 ​

图片和文件记录使用草稿附件的本地 id 作为 contextId,并携带 attachmentId 绑定。编写器发送上传的待定 id(或在数据 URL 路径上发送本地 id,经由 UploadChatImageAttachment 上可选的 id);服务器的 Normalizer 把每条图片和文件记录改写成它分配的已持久化 id,因此存储的消息把记录绑定到真实资源。乐观行绑定到本地 id,并被服务器副本替换。

在编写器里,附加文件或图片会在插入点插入一个芯片(编辑器不能接受输入时则追加)。文件只作为芯片存在:最后一个芯片被删除的文件会被移除并释放它的上传,早于引用的草稿在注水时会追加一个芯片。图片把缩略图架保留为它们的清单;删除芯片会留下图片,移除仍被引用的缩略图会在同时移除两者之前请求确认。旧草稿不会获得图片芯片。

在记录里,图片芯片打开图库预览,视频芯片打开媒体预览,其他文件芯片打开或下载文件。图库仍然显示每张图片;文件行只留给没有芯片引用的文件。

剪贴板 ​

每条复制路径都把规范 Markdown 写成 text/plain,并且当选区包含芯片时,在 web application/x-t3-context-fragment+json 下写一个结构化片段(ComposerContextClipboardFragment:版本、来源环境/对话线程/消息、记录;没有字节,没有 URL)。编写器的复制和剪切通过一个 Lexical 命令监听器添加它;记录选区复制定用户消息正文上的 onCopyCapture 添加它,同时芯片通过 data-markdown-copy 重新发出它们的链接;整条消息按钮通过 ClipboardItem 写两者,并回退到纯文本。

粘贴时,编写器先解码片段,再处理纯文本。草稿还没有的记录会被导入:终端摘录和审查评论按原样,预览批注从它们的记录重建,图片或文件通过来源环境的资源 URL 重新获取,并在一个新的本地 id 下附加,粘贴的链接被改写成那个 id。粘贴的二进制在字节到达之前显示为未解析芯片;来自另一个环境的片段会让二进制保持未解析。渲染后的芯片和已发送的消息从不会被一次粘贴改变。

跨对话线程、项目或环境粘贴使用同一条路径:客户端从来源环境铸造一个资源 URL,下载字节,并在这里附加它们。没有服务器端克隆;如果来源不可达或附件已经消失,一条提示会说明这一点,芯片保持未解析。

Id、持久化与暂存 ​

生产者保留自己的 id 语法;toComposerContextId 在引用和记录边界,确定性地把 [a-z0-9_-] 之外的任何东西折成一个短标识加上哈希。预览批注的上下文 id 从 annotation-<id> 派生,因此它和截图保持不同,截图的附件 id 就是批注 id;记录通过 screenshotContextId 把两者连起来。

projection_thread_messages.context_json 持久化记录,因此重启或投影重新加载之后芯片仍然可以解析。提示暂存条目为终端摘录、审查评论和预览批注携带 records;暂存把它们移出草稿,恢复时通过粘贴路径使用的同一个导入器把它们导回来。

其他面板产生的上下文通过 setContextInsertionHandler 到达插入点:已挂载的编写器为自己的草稿注册一个插入器,存储则回退到追加。

非官方译文 · 源提交 d2c25950b4d9 · 不代表 T3 Tools