Plandex 模型回复格式深度解析:PlandexBlock 文件块协议、流式解析与命令实现落地

发布时间:2026/9/14 22:26:50
Plandex 模型回复格式深度解析:PlandexBlock 文件块协议、流式解析与命令实现落地 Plandex 模型回复格式深度解析PlandexBlock 文件块协议、流式解析与命令实现落地【免费下载链接】plandexOpen source AI coding agent. Designed for large projects and real world tasks.项目地址: https://gitcode.com/GitHub_Trending/pl/plandex本篇技术指南以 Plandex 开源仓库中的模型回复测试样例 reply_test_examples/3.md 为核心骨架系统拆解 Plandex 中 AI 模型如何通过PlandexBlock标签携带待写入的文件内容、服务端如何逐块流式解析ReplyParser、如何把协议标签还原为终端可读的 Markdown 代码块并以文档中context rm/context update两个示例命令为线索追踪其从占位函数演进为仓库中真实实现的全过程。读完本文你将掌握 Plandex 模型回复协议的完整链路能够理解pdx会话中模型输出如何被解析为可落盘的文件操作并能在自己的工具链中复现同样的流式解析思路。一、样例文档在仓库中的定位一份模型回复的黄金样本app/server/types/reply_test_examples/目录下存放着 10 份1.md~10.md测试样例它们不是给人读的文档而是模拟 LLM 回复内容的输入样本。每个样例由 reply_test.go 驱动喂给ReplyParser进行解析并断言解析结果。本文聚焦的 3.md 内容如下与2.md相同是同一组断言用例的两份样本模型回复的引导语Sure, heres how you could structure your context rm command and context update command using placeholders for the lib functions…两个待写文件块cmd/context_rm.go与cmd/context_update.go结尾建议In your next iteration, you could implement the functions in lib package such asRemoveAllContexts,RemoveContext, andUpdateContext.从测试断言来看reply_test.go该样例对应两条Operation类型均为OperationTypeFile路径分别是cmd/context_rm.go与cmd/context_update.go。也就是说这份文档本身就是一次模型回复的实录——它示范了 Plandex 期望 LLM 以何种格式在回复中交付文件改动。二、PlandexBlock 文件块协议格式与语法Plandex 对模型回复有一个强约束凡是涉及创建或修改文件必须使用PlandexBlock标签包裹代码块标签必须携带lang与path两个属性格式为PlandexBlock lang语言 path文件路径 文件完整内容 /PlandexBlock样例 3.md 中模型回复的第一个文件块cmd/context_rm.go完整呈现了这一协议package cmd import ( fmt log github.com/spf13/cobra ) // contextRmCmd represents the delete command var contextRmCmd cobra.Command{ Use: rm [context_name], Aliases: []string{delete}, Short: , Long: , Run: contextRm, } func contextRm(cmd *cobra.Command, args []string) { all, err : cmd.Flags().GetBool(all) if err ! nil { log.Fatal(Error parsing all flag: , err) } if all { // Placeholder for lib function err lib.RemoveAllContexts() if err ! nil { log.Fatal(Error removing all contexts: , err) } } else if len(args) 0 { for _, contextName : range args { // Placeholder for lib function err lib.RemoveContext(contextName) if err ! nil { fmt.Printf(Error removing context %s: %v\n, contextName, err) } } } else { fmt.Println(Please provide a context name or use the --all flag to remove all contexts.) } } func init() { contextCmd.AddCommand(contextRmCmd) contextRmCmd.Flags().BoolP(all, a, false, remove all contexts) }第二个文件块cmd/context_update.go同样遵循协议package cmd import ( fmt log github.com/spf13/cobra ) // contextUpdateCmd represents the context command var contextUpdateCmd cobra.Command{ Use: update [file-or-urls...], Short: A brief description of your command, Long: A longer description that spans multiple lines and likely contains examples and usage of using your command. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application., Run: func(cmd *cobra.Command, args []string) { if len(args) 0 { fmt.Println(Please provide one or more files or URLs to update.) return } for _, arg : range args { // Placeholder for lib function err : lib.UpdateContext(arg) if err ! nil { fmt.Printf(Error updating context for %s: %v\n, arg, err) } } }, } func init() { contextCmd.AddCommand(contextUpdateCmd) }值得注意的协议细节文件块前有路径标签每个PlandexBlock块之前模型先输出一行加粗路径**cmd/context_rm.go**作为路径候选行随后紧跟PlandexBlock langgo pathcmd/context_rm.go打开标签两者共同让解析器确认文件路径。lang与path缺一不可服务端正则openingTagRegex PlandexBlock\slang(.?)\spath(.?).*?见 tell_stream_processor.go要求同时捕获语言与路径仅写PlandexBlock不带属性属于被保留的兜底分支。标签与代码块一一对应每个文件块以/PlandexBlock收尾解析器据此关闭当前文件并沉淀出一条Operation。三、ReplyParser把协议标签解析成文件操作的流式状态机解析逻辑集中在 reply.go 的ReplyParser。它是一个逐块chunk驱动的增量状态机核心接口如下NewReplyParser()创建解析器AddChunk(chunk string, addToTotal bool)向解析器喂入文本片段模拟模型流式输出的 tokenRead()读取当前解析状态ReplyParserRes含MaybeFilePath、CurrentFilePath、Operations等FinishAndRead()追加一个换行收尾后返回最终结果。其状态推进逻辑reply.go可概括为四个分支路径候选maybeFilePath当解析到以-、-file:、**包裹、或#结尾带冒号的行时用extractFilePath尝试提取路径并暂存为候选。打开标签确认若下一行以PlandexBlock开头则确认候选路径创建一条OperationTypeFile操作setCurrentFile后续所有行追加到该操作的Content。关闭标签收尾遇到/PlandexBlock时把当前文件操作追加进Operations重置文件状态。文件操作块遇到### Move Files/### Remove Files/### Reset Changes标题则进入对应块逐行解析-开头的路径行直到EndPlandexFileOps/收尾。extractFilePathreply.go对路径行做了大量清洗剥掉**、反引号、引号去掉file:/file path:/File path:前缀并处理:与(分隔对 XML 风格标签则直接用正则path([^])取值。解析产物是shared.Operation定义于 data_models.go其Type有四种类型常量值触发语法文件写入filePlandexBlock lang... path...…/PlandexBlock移动文件move### Move Files下- src → dest 删除文件remove### Remove Files下- path 重置变更reset### Reset Changes下- path Operation.Name()会拼接类型、路径与目标路径如file | cmd/context_rm.go供日志与测试断言使用。四、测试如何验证解析以 5 字符模拟 token 流reply_test.go 的TestReplyParser把每个样例文件读入后按5 个字符一块切分tokenSize : 5模拟 LLM 的流式输出逐块调用parser.AddChunk(chunk, true)最后FinishAndRead()并断言解析出的操作数量与预期一致对 3.md 即 2 条每条操作的Name()与预期Operation一致若预期带Description则逐字符比对描述内容。这种小 chunk 增量解析的测试设计恰好验证了状态机在任意分块边界下都能正确拼合标签——无论/PlandexBlock被切成几个 token最终结果都稳定。这也是生产环境流式场景正确性的重要保证。五、服务端流式处理把协议还原成终端可读的 Markdown解析出Operation只是第一步。在 tell_stream_processor.go 的processChunk中每个流式 chunk 会同时做两件事喂给ReplyParserreplyParser.AddChunk(content, true)后读取parserRes当出现新操作时调用handleNewOperations将其排队BuildModeAuto下立即排入 build 队列见 tell_stream_processor.go。把协议标签翻译给用户bufferOrStreamtell_stream_processor.go负责将PlandexBlock langgo path...替换为go 代码围栏、将 /PlandexBlock 替换为再流式推送StreamMessageReply给 CLI 展示。为保证标签不被切成半个仍能正确替换bufferOrStream实现了等待awaiting机制当 chunk 以PlandexBlock lang的前缀结尾时说明标签可能未完整到达先缓存不输出等后续 chunk 补全后再整体替换。这正是回复在终端中呈现为整洁 Markdown 代码块、而非裸 XML 标签的原因。此外还有一个安全闸门如果模型试图写一个存在于项目路径但不在上下文中的文件active.ContextsByPath[currentFile] nil req.ProjectPaths[currentFile]流程会触发handleMissingFiletell_stream_processor.go暂停流并询问用户如何处理30 分钟超时后恢复。六、从占位符到真实实现context rm / context update 的落地样例中模型建议的下一步是implement the functions in lib package such asRemoveAllContexts,RemoveContext, andUpdateContext。仓库中的真实实现印证了这一演进样例中的两个 Cobra 命令正是 context_rm.go 与 context_update.go现名update.go的前身。真实命令pdx context rm支持--all/-a标志与样例中的设计完全一致。lib包的真实上下文更新逻辑在 context_update.goUpdateContext依据CheckOutdatedContext的结果把变更的文件体/目录树/URL/文件映射map批量提交并在更新前调用checkContextConflicts做冲突检测。真实实现远比占位函数复杂其核心流程context_update.go并行比对对文件、目录树、URL、map 四类上下文分别起 goroutine受ContextMapMaxClientConcurrency信号量约束用 SHA-256 与存量ctx.Sha比对判断是否过期大小与数量限制逐项检查MaxContextBodySize、MaxContextCount、MaxContextMapPaths、ContextMapMaxBatchSize等共享常量超限的项被记录为跳过项filesSkippedTooLarge/filesSkippedAfterSizeLimitToken 增量统计为每个过期上下文计算tokenDiffsById最终汇总新的总 token 数并生成人类可读的摘要SummaryForUpdateContext确认后执行CLI 端展示变更表格后询问Update context now?确认后才调用api.Client.UpdateContext与api.Client.DeleteContext落库。从源码结构看RemoveAllContexts等批量移除能力被整合进DeleteContextAPI 路径而样例中的按名移除、按--all全清的设计语义在真实命令中演化为基于contextId的精确操作参考 context_show.go 中对名称/索引解析为contextId的方式。七、文件操作块Move / Remove / Reset 的精确语法除file块外Plandex 还允许模型通过三个特殊小节执行文件移动、删除与重置其精确语法定义在 file_ops.go 的系统提示中### Move Files - source/path.tsx → dest/path.tsx EndPlandexFileOps/ ### Remove Files - components/page.tsx EndPlandexFileOps/ ### Reset Changes - components/page.tsx EndPlandexFileOps/约束要点file_ops.go每行必须以-开头路径必须用反引号包裹移动操作用Unicode 箭头→不是-只能操作在上下文中或存在待定变更的单个文件不能操作目录每个小节必须以EndPlandexFileOps/收尾否则解析不完整目标路径不能与现有上下文/待定文件冲突避免覆盖移动/删除后模型后续对同一文件的更新须基于新路径/视为新文件。对应解析端extractMoveFile与extractRemoveOrResetFilereply.go按上述语法提取move/remove/reset操作并通过pendingPaths去重。八、写给模型与工具作者的格式清单综合样例 3.md 与上述源码可沉淀出一份可复用的模型回复格式清单写文件路径标签行如**cmd/foo.go**PlandexBlock langgo pathcmd/foo.go 完整文件内容 /PlandexBlocklang与path属性必须齐全。改文件代码块内用// ... existing code ...注释锚定上下文除非是整文件覆盖或改动在文件绝对首尾见 explanation_format.go 中的大量正反示例。文件操作### Move Files/### Remove Files/### Reset Changes-反引号路径 EndPlandexFileOps/。任意分块鲁棒性解析器按任意边界切分 chunk 都必须得到一致结果由 5 字符分块测试保证因此标签必须成对闭合、路径不得换行。与用户展示隔离协议标签只是传输载体服务端会将其翻译为 Markdown 代码围栏后再流式展示最终用户看到的是整洁的代码块而非 XML。通过这条模型输出 → PlandexBlock 协议 → ReplyParser 状态机 → Operation → build 队列/落盘的完整链路Plandex 得以在流式场景下稳定地把 LLM 的任意回复精确还原为对仓库文件的真实改动。深入阅读 reply.go、tell_stream_processor.go 与 context_update.go 三处源码即可掌握该协议的全部实现细节。【免费下载链接】plandexOpen source AI coding agent. Designed for large projects and real world tasks.项目地址: https://gitcode.com/GitHub_Trending/pl/plandex创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考